nontainer 📦
Versioned, forkable workspaces for code-using agents.
Give any Python agent loop a stateful terminal and Python tool over a workspace that checkpoints files and cache together, forks in O(1), and rolls back as a unit. Run locally — where agent code can work through whitelisted live host objects — or on a microVM while the workspace history stays in the state layer.
Think of it as a fake little computer with branchable history, packaged as a
library. No Docker, cloud sandbox, or service required for the local default:
pip install nontainer.
Status: pre-alpha. Usable and tested end to end; the API will still move before 1.0.
The core
Nontainer keeps three concerns separate:
| Responsibility | |
|---|---|
WorkspaceProvider |
Where files and cache live, and which history operations are real. The default kvgit provider supplies cheap checkpoints, forks, rollback, and audit; other providers declare narrower capabilities rather than pretending equivalence. |
Executor |
Where terminal and Python code run and how they reach workspace state: locally through sandtrap and monkeyfs, or on a real machine through dud. |
| Adapters | How the two tools enter an existing agent loop: the core Python API, an agno toolkit, or an MCP server. |
The model-facing surface stays small: a terminal and a run_python tool.
Unlike stateless sandbox calls, both are stateful and bound to a session:
the shell's cd sticks, files one call writes the next call reads, and a
cache dict persists for the whole conversation.
Because that state is a versioned workspace, each state-changing call can be checkpointed as one unit. The host can fork a session in O(1), roll back to any commit, or audit its history without teaching the agent a version control protocol.
| Terminal tool | ~33 shell builtins (grep, sed, jq, tar, ...) over the virtual filesystem via termish. |
| Python tool | Policy-gated sandboxed execution via sandtrap; safe stdlib on by default, open()/os/pathlib routed to the workspace via monkeyfs. |
| In-process | Agent code can call your whitelisted host objects -- the live model, the db pool -- under policy. No cloud sandbox can. |
What the sandbox is (and isn't). In-process, the Python sandbox (sandtrap) is a walled garden for cooperative LLM-generated code — it gates what agent code can reach (modules, host objects, the filesystem) to an allowlist you control (safe stdlib on by default, everything else opt-in), not a hardened boundary against code trying to escape. That's the right posture for your own agent's code. For crash containment and kernel-enforced defense-in-depth around cooperative code, use
isolation="process"/"kernel". For actively untrusted code, or execution exposed to anonymous clients, step off the local model and useDudExecutor()'s microVM backend (see Executors). Full framing in the design notes.
The API in one glance
from nontainer import workspace
ws = workspace("user-42") # versioned; a kvgit branch per session
ws.terminal("mkdir -p data && echo 'a,b\n1,2' > data/in.csv")
r = ws.run_python("""
import csv
rows = list(csv.reader(open('data/in.csv'))) # sees the shell's file
cache['n_rows'] = len(rows) # persists across the session
print(rows)
""")
r.checkpoint # commit id this call produced; ws.restore(it) undoes it
fork = ws.fork("what-if") # O(1) branch; the original is untouched
ws.rollback(steps=1) # or time-travel by steps
Checkpoints cover workspace-owned files and cache. Host-object calls and mounts are external effects: their data is not checkpointed, restored, or copied by a fork. A fork does inherit the mount points, and sees the same live directories behind them.
Files live under the workspace root — /workspace by default
(workspace(..., root=)) — and cwd starts there, so relative paths
just work. The root is the one absolute-path contract shared across
executors: a dud VM mounts its guest workspace at the same path, so
/workspace/data/in.csv names the same file whether agent code runs
in the local sandbox or a real machine.
Adapters are one import away:
from nontainer.adapters.agno import WorkspaceTools # agno Toolkit
# or: python -m nontainer.adapters.mcp --session s1 # MCP server (stdio)
Substrates
WorkspaceProvider is the pluggable seam -- one filesystem-and-KV protocol,
capability flags instead of pretended equivalence:
| Provider | versioned | cheap_fork |
sql_audit |
|---|---|---|---|
| kvgit (default) | ✅ | ✅ O(1) | ❌ |
| plain dir | ❌ | ❌ | ❌ |
| AgentFS (spike) | ❌ | ❌ | ✅ |
kvgit for fork/undo/audit, dir when agent code needs real files (C
extensions, subprocesses), AgentFS for the one-file-artifact + SQL story --
or bring your own provider. Full guidance in the API reference.
Executors (the [dud] extra)
The second seam. WorkspaceProvider decides where state lives;
Executor decides where code runs -- and the two are independent,
because the versioning semantics were always properties of the state
layer, not the machine.
| Executor | isolation | fidelity |
|---|---|---|
LocalExecutor (default) |
sandtrap's walled garden; optional process/kernel defense-in-depth | emulated shell + filesystem |
DudExecutor() -- i.e. backend="vm" |
a disposable microVM -- vfkit on macOS, firecracker on Linux/KVM | real machine |
DudExecutor(backend="subprocess") |
none -- host process | real bash, real files |
from nontainer.executor_dud import DudExecutor
ws = workspace("user-42", executor_factory=lambda: DudExecutor())
The default "vm" picks the right hypervisor for the host; name
"vfkit" or "firecracker" directly if you need to pin one. Asking
for one the host can't provide fails closed (IsolationUnavailable)
rather than quietly degrading.
Same terminal / run_python tools, same checkpoints, same O(1)
forks -- dud receives a tree,
executes against a real filesystem, and returns a diff, which the
provider commits exactly as it commits a local one. What you buy is
fidelity: C extensions, real subprocesses, sqlite on real files,
memory-mapped parquet -- the workloads the in-process emulation serves
worst.
Note the last row: backend="subprocess" is real bash and real Python
with no containment at all -- agent code runs as you, with your
network and your files. It buys fidelity, not a boundary, so it's
opt-in rather than the default: it's the only backend that needs no
hypervisor, which makes it the dev/CI floor. If you want policy gating,
crash containment, or kernel defense-in-depth without a VM, use
LocalExecutor, not this.
App handlers (the [apps] extra)
Agents author full-stack apps: a Preact/HTM frontend plus request
handlers -- serverless semantics, not resident servers. A file's path is
its route (/workspace/app/api/scores.py → /api/scores), its exported get/post
are the verbs. The agent builds and verifies entirely in-loop: a curl
builtin hits the dispatcher from the terminal, and test_app runs the app
headlessly through Playwright with the workspace as the origin -- no server,
no Node. To share it, publish a frozen snapshot: build_router serves
the app read-only and concurrently at /apps/{token}/...; mutable app state
lives in an external store injected via host_objects, not the (frozen)
workspace.
Fixed browser files the agent shouldn't author -- a vendored component
library, fonts, a charting bundle -- are declared once as
AppsConfig.static_assets and served alongside the app without entering
the workspace. That's what a house design system rides on, and what makes
an air-gapped deployment work with no CDN in reach.
Full design -- handler contract, execution model, test_app DSL, serving/threat model: docs/apps.md.
Related work
- Cloud sandboxes (E2B, Daytona, Modal, Fly Sprites): real isolation, real infra. They have persistence; none have history, forking, or in-process host-object access.
- mcp-run-python (Pydantic): the incumbent local run-python (Pyodide-in-Deno). Stateless per call, no workspace, needs Deno.
- AgentFS (Turso): SQLite-backed agent FS + KV + SQL-queryable audit, snapshots by file copy. It comes at the problem from storage where nontainer comes from execution -- and nontainer runs on it as one of its backends.
- Val Town: agents-deploying-endpoints as a polished cloud product (TS). The handler design here is the self-hosted, session-scoped, Python, versioned take on the same instinct.
Part of the agex stack
nontainer composes kvgit, monkeyfs, termish, and sandtrap -- each independently useful, each zero/minimal-dep -- and optionally dud when the little computer should be a real one. agex is the full agent framework over the same substrate; nontainer is the environment layer alone, offered to someone else's loop.
Documentation
- Quick Start -- first workspace, sandbox config, backends, adapters, the apps loop; runnable examples
- API Reference -- every class, method, and flag
- Design notes -- why it's shaped this way (execution model, commit granularity, tool exposure) and what's still ahead
- Apps design -- handler contract, execution model, test_app, serving/threat model
- Examples -- live agno agents: a data analyst
(
analyst.py) and a build-and-verify web app (webapp.py)
Install
pip install nontainer # workspace + terminal + run_python
pip install nontainer[agno] # + agno Toolkit adapter
pip install nontainer[mcp] # + MCP server (python -m nontainer.adapters.mcp)
pip install nontainer[apps] # + handlers/curl, Playwright test_app, serving router
pip install nontainer[agentfs] # + AgentFS substrate (agentfs-sdk)
pip install nontainer[dud] # + real-machine / microVM execution (needs 3.11+)
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nontainer-0.3.4.tar.gz.
File metadata
- Download URL: nontainer-0.3.4.tar.gz
- Upload date:
- Size: 449.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ecfc5e9890d130f64799ce933e01771bbb22d802a4944cddfd2339b903d135c5
|
|
| MD5 |
a9c232d084482e2bf9bc5c21c6b4a8ab
|
|
| BLAKE2b-256 |
b6a400726e574abb394da01837a57696395c5997fe4eb87b1053baa88e440712
|
File details
Details for the file nontainer-0.3.4-py3-none-any.whl.
File metadata
- Download URL: nontainer-0.3.4-py3-none-any.whl
- Upload date:
- Size: 157.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3dadc51f463d9a8c2161dc6d99632171617dbd8d80013216a075c7824d8ba07b
|
|
| MD5 |
10879cb64f1e54c81ca4eb91899f05d1
|
|
| BLAKE2b-256 |
206356d5f0b178cfc309636912b6c11cb2b01f8225f3c74a00aa08b58e22ec2b
|