Skip to main content

sandfs

sandfs is a sandbox filesystem for agents. Mount any data without copying it (a folder, in-memory files, an API, a bucket, a git commit), let the agent read and write through a copy-on-write workspace, fork that workspace in memory in O(1) for every attempt, and collect the files it changed. Files are bytes of any kind: documents, spreadsheets, images, data sets and code alike.

Status: early. The core is Rust (crates/), and the Python package is a thin binding over it. APIs may still change.

Primitives

  • Source: a read-only tree, read only when a path below it is touched. sandfs ships a host directory, in-memory files and an optional git source; anything else is two methods you write, list and read.
  • Workspace: sources mounted at paths, plus a copy-on-write overlay that holds every write, directory and deletion. Sources are never modified.
  • Change: one file that differs from the mounts, returned by changes().

Everything else is a composition. A snapshot is a fork nobody writes to, a working copy on disk is a fork mounted over FUSE, and saving is the harness walking changes() and writing each changed file wherever its data lives.

Installation

pip install "sandfs>=0.4"   # prebuilt wheels for Linux, macOS and Windows, Python 3.11+
cargo add sandfs-core       # the Rust core; sandfs-git adds the git source
cargo install sandfs-mcp    # an MCP server with file tools over a workspace

Until 0.4.0 is published, install from git instead with pip install git+https://github.com/lydakis/sandfs.git, which builds the extension and needs a Rust toolchain.

Quickstart

import sandfs

class Tickets:
    """Any data can be a source: list a directory, read a file."""

    def list(self, path):
        return [sandfs.DirEntry("T-1.json"), sandfs.DirEntry("T-2.json")]

    def read(self, path):
        return fetch_ticket_json(path.removeprefix("/").removesuffix(".json"))

base = sandfs.Workspace()
base.mount("/reports", sandfs.DirSource("./reports"))   # a host folder, never written to
base.mount("/tickets", Tickets())                        # read only when the agent looks
base.mount("/brief", sandfs.MemorySource({"task.md": "Summarise open tickets\n"}))

attempt = base.fork()                                    # one fork per agent or attempt
version = attempt.write("/reports/summary.md", "# Open tickets\n")
attempt.write("/reports/summary.md", "# Open tickets (2)\n", expected_version=version)
attempt.write("/reports/chart.png", png_bytes)

for change in attempt.changes():
    print(change.kind, change.path)                      # added /reports/chart.png, added /reports/summary.md

base is unchanged: forks share the mounts and everything already read, but never see each other's changes.

A source must return the same answer every time, because what it returns is cached and shared between forks. Sizes in DirEntry are optional; stat reads a file once to find a size the source left out.

Large files can be read in pieces: read(path, offset=..., length=...) asks the source for just that range and doesn't cache it, so a multi-gigabyte data set or video is never held in memory whole. DirSource seeks on disk, and a Python source can define read_range(path, offset, length); without it, the range is sliced from a whole read.

sandfs.GitSource(repo, rev="HEAD") mounts a commit of a local repository without a checkout. In a partial clone (git clone --filter=blob:none), git fetches a file's contents only when it is first read.

API

Call Does
mount(path, source) Mount a DirSource(root), MemorySource({name: str or bytes}), GitSource(repo, rev), or any object with list(path) and read(path)
fork() A new workspace with the same mounts and changes, in O(1)
read(path, *, offset=0, length=None), read_text(path) File contents as bytes (all of it, or one range), or as UTF-8 text
list(path="/"), stat(path), exists(path) Entries (name, kind, size), and kind, size and version
write(path, data, *, expected_version=None) Write str or bytes, creating parents, and return the new version
mkdir, remove, copy, move File operations; base files are copied by reference
changes() Change(path, kind) for every added, modified or deleted file

Errors are built-in exceptions (FileNotFoundError, FileExistsError, IsADirectoryError, NotADirectoryError, ValueError, OSError) plus sandfs.VersionMismatch, which carries path, expected and actual. An exception raised inside a Python source surfaces as OSError. Calls into the core release the GIL, so a slow source never blocks other threads. A workspace is not meant to be shared between threads; fork one per thread instead.

Mounting a workspace on disk

The sandfs command (crate sandfs-cli) mounts a workspace as a real directory with FUSE, so any program can read and write it: a data pipeline, a notebook, a build, an agent's tools. The folder underneath is never modified, and forking never copies it. It mounts on Linux with FUSE 3 (fusermount3, package fuse3). On macOS and Windows it does everything but mount, and sandfs serve --mcp SOURCE creates workspaces there.

cargo install --path crates/sandfs-cli
sandfs mount ~/datasets/survey ~/ws/survey       # a host folder, never written to
sandfs fork ~/ws/survey ~/ws/cleanup             # survey's changes so far, without copying the folder
python clean.py ~/ws/cleanup                     # real files, any tool
sandfs changes ~/ws/cleanup                      # A/M/D against the source
sandfs unmount ~/ws/cleanup                      # changes stay saved
sandfs mount cleanup ~/ws/cleanup                # and come back

sandfs serve --mcp NAME gives an unmounted workspace to an agent as MCP file tools instead, with the source's files under /mnt; whatever the agent changes is saved for the next mount or changes. One process serves a workspace at a time: a second mount or MCP session is refused while the first runs, so parallel attempts each get their own fork.

Changes live in memory and are saved under $SANDFS_HOME (default ~/.local/share/sandfs) shortly after each write. A fork on disk costs more than a fork in memory. sandfs fork never copies the source, but it waits for the workspace's latest changes to be saved, links each changed file into the fork (copying it where the disk can't link), and starts a new mount, which reads the source again rather than sharing what the first mount has read. git:<path or url>[@rev] mounts a commit instead of a folder; for a remote repository sandfs fetches only that commit and its folder listings, which takes about a second even for a large repository, and fetches a directory's files in one request when it is first listed. Offline, a commit id that was mounted before still mounts and the folders opened before still read, but any other folder needs the network. A branch or tag name is looked up on the server every time, so it doesn't mount offline.

What sandfs is not

Not an execution sandbox. sandfs does not run commands, and it does not contain the programs that use it. It keeps their writes out of the source, but a program working in a FUSE mount runs with its own process's access and can still read or change anything else its user can. Run untrusted programs in a real sandbox (a container or microVM) and point it at the mount.

Not a snapshot of a host folder. A host folder is read when a path below it is first touched. Directory listings and whole-file reads are kept from then on, but ranged reads (which a FUSE mount uses for files over 64 MiB) go back to the folder every time. If the folder changes while it is mounted, a workspace can show a mix of old and new: a listing or size read before the change next to contents read after it, or two ranges of one file from different versions. Mount a folder nothing else writes to, copy it first, or mount a git commit when the view must not change.

sandfs also has no symlinks and no text-editing or patch tools in the core; those are code concerns that live in the MCP tools, if anywhere. Earlier versions had a bash-like shell, a host-command bridge, a Python executor, access policies and storage adapters. They were dropped to keep the primitive small.

Repository layout

.
├── crates/
│   ├── sandfs-core/    # workspaces, overlay, changes, directory and in-memory sources
│   ├── sandfs-git/     # optional lazy git source
│   ├── sandfs-cli/     # the sandfs command: mount, fork and changes over FUSE
│   ├── sandfs-mcp/     # MCP server: file tools over one workspace
│   └── sandfs-py/      # Python bindings (the sandfs._core extension)
├── docs/               # roadmap, changelog and publishing notes
├── sandfs/             # Python package: re-exports and type stubs
└── tests/              # Python tests

Local development

Building the package compiles the Rust extension, so you need a Rust toolchain (rustup). Re-run the install after changing Rust code.

python -m venv .venv
source .venv/bin/activate
uv pip install -e .[dev]
pre-commit install
uv run pytest
uv run ruff check .
uv run mypy sandfs
cargo test --workspace

License

MIT

Further reading

Metadata

Release files for sandfs 0.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for sandfs 0.4.0
File Size Uploaded Provenance
sandfs-0.4.0.tar.gz 47.8 kB With provenance Details

Built distributions (wheels)

Table of built distributions (wheels) for sandfs 0.4.0
File
sandfs-0.4.0-cp311-abi3-win_amd64.whl CPython 3.11 abi3 Windows x86-64 Details
sandfs-0.4.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.11 abi3 Linux glibc 2.17+ x86-64 Details
sandfs-0.4.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.11 abi3 Linux glibc 2.17+ ARM64 Details
sandfs-0.4.0-cp311-abi3-macosx_11_0_arm64.whl CPython 3.11 abi3 macOS 11.0+ ARM64 Details
sandfs-0.4.0-cp311-abi3-macosx_10_12_x86_64.whl CPython 3.11 abi3 macOS 10.12+ x86-64 Details

Total release size: 2.3 MB

Release files / sandfs-0.4.0.tar.gz

Download URL sandfs-0.4.0.tar.gz
Size 47.8 kB
Tags Source
SHA-256 checksum
How to use checksums
ac4d12d84863cadcac1b1fe69427ca931a06ffc89bf438408c959bde04a8b2f9
BLAKE2b-256 checksum
How to use checksums
0b47a9cec307c120473a286cdf4e552ede3a04f1a75cb4bd8646fddc0445bab2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 19, 2026.

Transparency log

Release files / sandfs-0.4.0-cp311-abi3-win_amd64.whl

Download URL sandfs-0.4.0-cp311-abi3-win_amd64.whl
Size 366.1 kB
Tags CPython 3.11 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
1dc204df226eaf1fe4d9d88dbe5005693dbb5bd4381e21330ecfa1bd9df265c4
BLAKE2b-256 checksum
How to use checksums
bbf8faba9110fe7fadc4f63dcc2d50e4ce1b2c09248a18c6215cad86d9629c3e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 19, 2026.

Transparency log

Release files / sandfs-0.4.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL sandfs-0.4.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 504.2 kB
Tags CPython 3.11 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
6b2e57cc67e6ef4398801d8e5e7f2fdc7528671b248b5b3fcfed806d41d8672b
BLAKE2b-256 checksum
How to use checksums
f216d1e38122a54b6b858ef698551ce02cfd57ba6a60bd90b607338c70b42614
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 19, 2026.

Transparency log

Release files / sandfs-0.4.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL sandfs-0.4.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 494.5 kB
Tags CPython 3.11 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
13f24f768003c48721bfea22fea63478ef41ce86f0e330bb20fdd6e1da541e9e
BLAKE2b-256 checksum
How to use checksums
1075d04a1e7ff65ecbaf089f845a498d2aa9f62eaf955db31bc2658e70999a1b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 19, 2026.

Transparency log

Release files / sandfs-0.4.0-cp311-abi3-macosx_11_0_arm64.whl

Download URL sandfs-0.4.0-cp311-abi3-macosx_11_0_arm64.whl
Size 449.0 kB
Tags CPython 3.11 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
845fca84439e315b74a351a17d360ec250495003895127b91616b1d7cff656e6
BLAKE2b-256 checksum
How to use checksums
a4f0ad9a274f38195cd0f7c3c7afff5ce165f29c0674ceb29e3587c9d3577aad
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 19, 2026.

Transparency log

Release files / sandfs-0.4.0-cp311-abi3-macosx_10_12_x86_64.whl

Download URL sandfs-0.4.0-cp311-abi3-macosx_10_12_x86_64.whl
Size 459.6 kB
Tags CPython 3.11 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
c38eab6dc597c18ba41f92c4362f0d831807a2adbff81ac35d65c3662bfc28e3
BLAKE2b-256 checksum
How to use checksums
1a3bceb15ef66c34446497155191d3fc3abba4c86aab817d2f704b856cbf6fda
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 19, 2026.

Transparency log

Security for sandfs 0.4.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 6 files in this release have been uploaded with provenance metadata.

Select from 6 files

Attestation for sandfs-0.4.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 19, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page