Skip to main content

A filesystem-like API over a tree stored as a string

Project description

Loopy: an in-memory, zero-side-effect filesystem API over a single Python string.

Loopy is a filesystem sandbox whose entire state lives in a single string. It comes with a bash‑style shell so you or your agent can navigate, search, and manipulate a tree‑structured knowledge base with composable commands.

The idea is to simulate a file system to build and navigate a knowledge tree with POSIX-style commands that agents are already familiar with.

from loopy import Loopy
from loopy.shell import run

raw = "<root><concepts><ml><supervised><classification>predicts categories</classification></supervised></ml></concepts></root>"
tree = Loopy(raw)
# tree.raw == "<root><concepts><ml><supervised><classification>predicts categories</classification></supervised></ml></concepts></root>"
# tree.tree("/") ==
# root/
# └── concepts/
#     └── ml/
#         └── supervised/
#             └── classification: predicts categories

output = run("cd /concepts/ml/supervised && cat classification", tree)
# output == "predicts categories"

run('touch /concepts/ml/supervised/regression "predicts continuous values"', tree)

serialized = tree.raw
# serialized == "<root><concepts><ml><supervised><classification>predicts categories</classification><regression>predicts continuous values</regression></supervised></ml></concepts></root>"
# tree.tree("/") ==
# root/
# └── concepts/
#     └── ml/
#         └── supervised/
#             ├── classification: predicts categories
#             └── regression: predicts continuous values

Why?

When an agent processes information, it needs somewhere to put it - somewhere it can search, reorganize, and grow organically. For any type of knowledge base like agent memories, product taxonomies, etc the challenge is to expose CRUD type interactions without a pile of specialized tools (search, create, delete, etc.) that are added to context.

Recursive Language Models (RLMs) introduced the idea of putting the entire context into a Python variable and let the model recursively interact with it, instead of reasoning over everything in one shot. RLMs: https://alexzhang13.github.io/blog/2025/rlm/. I really liked it, but enabling a python REPL seemed like a bad tradeoff for generality.

Loopy imposes a known structure (a tree / filesystem), and replaces the python REPL with a bash syntax over a string. Agents are RL'd in filesystem-like setups, and this provides a sandboxed way to give an agent access to a knowledge base without touching the OS filesystem.

Why this approach:

  • simple - a single string can represent the full data
  • known structure - stored in a file system format agents already know and love
  • composition - compose search commands to quickly navigate the data

How it works

Loopy keeps an in-memory node tree and exposes filesystem-like operations. There is no OS filesystem I/O; all mutations stay in memory until you serialize. The raw string is generated on demand and can be parsed back into the same structure.

<root><concepts><ml><supervised>...</supervised></ml></concepts></root>
from loopy import Loopy

tree = (
    Loopy()
    .mkdir("/concepts/ml/supervised", parents=True)
    .touch("/concepts/ml/supervised/regression", "predicts continuous values")
    .touch("/concepts/ml/supervised/classification", "predicts categories")
    .mkdir("/concepts/ml/unsupervised")
    .touch("/concepts/ml/unsupervised/clustering", "groups similar items")
)

tree.grep("predicts", content=True)  # find concepts by description
tree.find("/concepts", type="f")  # all leaf concepts
tree.ls("/concepts/ml")  # ['supervised', 'unsupervised']

# tree.raw
# <root><concepts><ml><supervised><regression>predicts continuous values</regression><classification>predicts categories</classification></supervised><unsupervised><clustering>groups similar items</clustering></unsupervised></ml></concepts></root>

File-backed

from loopy import FileBackedLoopy, load, save

tree = FileBackedLoopy("notes.loopy")
tree.touch("/ideas/mcp", "Expose shell with MCP")  # auto-saved

tree = load("notes.loopy")
tree.mkdir("/scratch", parents=True)
save(tree, "notes.loopy")  # explicit save for non-file-backed trees

Install

# Local install
uv pip install -e .

# PyPI
uv pip install loopy-fs

API

Core Operations

Method Description
mkdir(path, parents=True) Create directory (category)
touch(path, content) Create file (entity) with content
cat(path) Read content
ls(path) List children
rm(path, recursive=True) Delete node
mv(src, dst) Move/rename
cp(src, dst) Copy
ln(target, link) Create symlink
exists(path) Check existence

Search

Method Description
grep(pattern, content=True) Search by regex (returns paths)
grep(pattern, lines=True) Search content line-by-line (returns path:lineno:line)
find(path, type="f") Find by type (f=file, d=dir, l=link)
glob(pattern) Glob patterns (**/*.py)

Navigation

Method Description
cd(path) Change directory
.cwd Current directory
.. support tree.ls("..") works

Inspection

Method Description
tree(path) Pretty print hierarchy
du(path) Count nodes
info(path) Metadata dict
isdir(path) / isfile(path) Type checks
islink(path) Check if symlink
readlink(path) Get symlink target
backlinks(path) Find all symlinks pointing to path
walk(path) os.walk() style
.raw The underlying string

Utilities

Function Description
slugify(text) Convert any string to a valid path segment
from loopy import slugify

slugify("Hello World!")      # -> "hello-world"
slugify("My File (2).txt")  # -> "my-file-2-.txt"
slugify("café résumé")      # -> "café-résumé"

Path segments only allow alphanumeric characters, underscores, hyphens, and dots. slugify converts anything else into a safe form.

All mutating operations return self for chaining.

Shell

Loopy includes a bash-style command runner designed for agents. Use run() to navigate and compose operations directly against the in-memory tree.

from loopy import Loopy
from loopy.shell import run

tree = (
    Loopy()
    .mkdir("/concepts/ml/supervised", parents=True)
    .touch("/concepts/ml/supervised/classification", "predicts categories")
)

output = run(
    "cd /concepts/ml && ls | grep supervised && cat supervised/classification",
    tree,
)

Start a REPL loop from a file-backed database (auto-saves on every mutation):

uv run python -m loopy.shell notes.loopy

Start a REPL loop with a sample database:

uv run python -m loopy.shell examples/product_catalog.loopy

Quick shell example:

loopy> ls -R sports
sports:
fitness/
outdoor/

loopy> find /sports -type f
/sports/fitness/weights/dumbbells_set
/sports/fitness/cardio/yoga_mat
/sports/outdoor/camping/tent_4person
/sports/outdoor/hiking/backpack_40L

Shell Commands

Command Description
ls [path] [-R] List directory contents
cd <path> Change directory
pwd Print working directory
cat [path] [--range start len] Show file contents
head [path] [-n N] Show first N lines (default 10)
tail [path] [-n N] Show last N lines (default 10)
wc [-lwc] [path] Count lines/words/chars
sort [-rnu] [path] Sort lines
tree [path] Show tree structure
find [path] [-name pat] [-type d|f|l] Find files/directories/symlinks
grep <pat> [path] [-i] [-v] [-c] [-n] Search by regex (-n for line matches)
du [path] [-c] Count nodes or content size
info [path] Show node metadata
touch <path> [content] Create file
write <path> [content] Write to file (overwrites)
mkdir [-p] <path> Create directory
rm [-r] <path> Remove file/directory
mv <src> <dst> Move/rename
cp <src> <dst> Copy
ln <target> <link> Create symlink
readlink <path> Show symlink target
sed <path> <pat> <repl> [-i] [-r] Search and replace
split <delim> [path] Split content by delimiter
echo <text> Print text
printf <fmt> [args] Print formatted text
help Show help

Command Chaining

Operator Description
cmd1 | cmd2 Pipe output of cmd1 to cmd2
cmd1 ; cmd2 Run both, continue on failure
cmd1 && cmd2 Run cmd2 only if cmd1 succeeds
cmd1 || cmd2 Run cmd2 only if cmd1 fails

Example Databases

Loopy ships with example databases in examples/:

  • product_catalog.loopy - E-commerce taxonomy
  • knowledge_graph.loopy - ML/CS concept ontology
  • bookmarks.loopy - Browser bookmarks
  • recipes.loopy - Recipe collection
  • org_chart.loopy - Company org chart
uv run python -m loopy.shell examples/knowledge_graph.loopy
loopy> cat /concepts/ml/deep_learning/architectures/transformer
def:Attention-based architecture|prereq:attention,mlp|paper:attention_is_all_you_need

loopy> grep "prereq:.*transformer" -c
2

loopy> grep -n "price:.*99" /clothing
/clothing/mens/shoes/loafers_brown:1:type:formal|price:89.99|color:brown

Or load in Python:

from loopy.file_store import load

tree = load("examples/product_catalog.loopy")
tree.ls("/clothing/mens/shoes")  # ['loafers_brown']
tree.grep("price:.*99", content=True)  # products ending in .99

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

loopy_fs-0.3.0.tar.gz (62.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

loopy_fs-0.3.0-py3-none-any.whl (27.6 kB view details)

Uploaded Python 3

File details

Details for the file loopy_fs-0.3.0.tar.gz.

File metadata

  • Download URL: loopy_fs-0.3.0.tar.gz
  • Upload date:
  • Size: 62.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.11

File hashes

Hashes for loopy_fs-0.3.0.tar.gz
Algorithm Hash digest
SHA256 94b5807f7a569de0799c5d074f03314ff1488e87d16193e25692bb718a407b1a
MD5 d4e755c8394d654bcf248778f4c463a7
BLAKE2b-256 bf70d7a0d43b7ad273c9c87aadf5b163df4b14e16a405653ed0839469d27820a

See more details on using hashes here.

File details

Details for the file loopy_fs-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: loopy_fs-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 27.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.11

File hashes

Hashes for loopy_fs-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7c73fa5f2e7488d5f1ddd7a24da6a3b50810558813cf556ef62f10e5d35785ab
MD5 a14fbbf7fe9dfb1f062d3bd963b93e35
BLAKE2b-256 a7f8803b9674f8627bb5514ef68b8fd1d0d29b37ffb0274818ee41b39e5a8a99

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page