Skip to content
/ loopy Public

An in-memory, zero-side-effect filesystem API over a single Python string

Notifications You must be signed in to change notification settings

tg1482/loopy

Repository files navigation

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 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

Performance

All tree operations are iterative (no recursion limits). Benchmarked on Python 3.12:

Wide trees (many files)

Operation 10K files (907 KB) 100K files (8.9 MB)
Build 138ms 1.68s
Serialize (.raw) 5ms 53ms
Cached .raw 1us 1us
Parse (from raw) 18ms 232ms
cat (1 file) 0.1ms 0.06ms
cat (10 files) 0.2ms 0.1ms
ls (1 dir) 0.1ms 0.1ms
grep (full tree) 5ms 49ms
find -type f 3ms 29ms
du 0.6ms 7ms

Deep trees (nested directories)

Operation 100 deep 1,000 deep 5,000 deep 10,000 deep
Build 0.3ms 2.5ms 14ms 30ms
Serialize 0.05ms 0.4ms 1.8ms 3.4ms
Parse 0.1ms 1.3ms 7ms 14ms
cat (leaf) 0.05ms 0.4ms 1.9ms 4ms
grep (full) 0.2ms 12ms 258ms 1.01s
find -type f 0.04ms 0.4ms 2.8ms 10ms

Run benchmarks: uv run python stress_test.py

License

MIT

About

An in-memory, zero-side-effect filesystem API over a single Python string

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors