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/concatenate 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 taxonomyknowledge_graph.loopy- ML/CS concept ontologybookmarks.loopy- Browser bookmarksrecipes.loopy- Recipe collectionorg_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
Metadata
Release files for loopy-fs 0.3.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| loopy_fs-0.3.1.tar.gz | 62.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| loopy_fs-0.3.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 90.5 kB
Release files / loopy_fs-0.3.1.tar.gz
| Download URL | loopy_fs-0.3.1.tar.gz |
|---|---|
| Size | 62.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0129cc125cd02c8575229e19cec63dcf4277de5110cd4fce8a10b6e49d3bc47f
|
|
BLAKE2b-256 checksum How to use checksums |
5464b5c5d26a4833a71cdd5ab9b70cc9a660b8af0a9c2dfa842959e96c5981ef
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.11
|
Release files / loopy_fs-0.3.1-py3-none-any.whl
| Download URL | loopy_fs-0.3.1-py3-none-any.whl |
|---|---|
| Size | 27.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f631a612c6527bcdf78240d2697bdf3399895c29cb3646ededa031fa54e80b85
|
|
BLAKE2b-256 checksum How to use checksums |
a8a58b8d31775a20a8008ea262445580c2c04bdb658fc20c8e38f82a6e2f030c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.11
|