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

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.1.tar.gz (62.9 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.1-py3-none-any.whl (27.6 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: loopy_fs-0.3.1.tar.gz
  • Upload date:
  • Size: 62.9 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.1.tar.gz
Algorithm Hash digest
SHA256 0129cc125cd02c8575229e19cec63dcf4277de5110cd4fce8a10b6e49d3bc47f
MD5 a9ca139598461095c501df5a667980e0
BLAKE2b-256 5464b5c5d26a4833a71cdd5ab9b70cc9a660b8af0a9c2dfa842959e96c5981ef

See more details on using hashes here.

File details

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

File metadata

  • Download URL: loopy_fs-0.3.1-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.1-py3-none-any.whl
Algorithm Hash digest
SHA256 f631a612c6527bcdf78240d2697bdf3399895c29cb3646ededa031fa54e80b85
MD5 7f55400cb73a7b0c8dde04ff0085769c
BLAKE2b-256 a8a58b8d31775a20a8008ea262445580c2c04bdb658fc20c8e38f82a6e2f030c

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