Disposable Linux microVMs on your own machine, for AI agents.
Create a sandbox, run commands, read stdout / stderr / exit code, throw it away.
Think "E2B, but on your computer": code never leaves the machine and it costs $0.
- A real Linux machine per agent. Each sandbox is an Ubuntu 24.04 microVM with its own kernel.
- Your repo goes in one way. Changes come back only through
diff/apply/pull, with a backup andundo. - Secrets never enter the VM. The VM sees placeholders; an egress proxy swaps in the real value for allowed hosts.
- Allowlist-only network with presets, a setup phase, and "ask to allow" from the agent.
- Fast. Warm pool, golden snapshots, pause to disk, fork, and restore.
- Works with Cursor, Claude Code, and Codex over MCP, or with anything that speaks HTTP.
- Observable. Prometheus metrics, OpenTelemetry traces, and a Grafana dashboard.
tryboxi/
├── cmd/ # entry points
│ ├── sandboxd/ # host daemon
│ ├── sandboxctl/ # CLI (incl. `mcp install`)
│ ├── sandbox-mcp/ # MCP stdio adapter
│ └── sandbox-agent/ # guest agent, PID 1 in every VM
├── guest/ # code that runs inside the VM
│ ├── initd/ # initramfs switch_root and mounts
│ ├── proc/ # PID 1 reaper
│ ├── execd/ # exec, PTY, timeouts
│ ├── spool/ # unacked output spool
│ ├── files/ # file reads and writes
│ └── server/ # gRPC server over vsock
├── internal/ # host-side packages
│ ├── api/ # HTTP router
│ │ ├── middleware/ # peer creds, token, Origin/Host checks
│ │ └── sse/ # server-sent events
│ ├── service/ # use cases: create, exec, sync, diff, apply, pull, undo,
│ │ # snapshots, fork, restore, recover, warm pool, templates, GC
│ ├── scheduler/ # vCPU / memory ledger, fair queue, memory pressure
│ ├── vmm/ # VM driver interface
│ │ ├── vz/ # macOS Virtualization.framework
│ │ └── firecracker/ # Linux Firecracker
│ ├── network/ # egress control
│ │ ├── policy/ # allow / deny engine
│ │ ├── dns/ # policy-aware resolver
│ │ ├── sniff/ # SNI / Host sniffing
│ │ ├── egress/ # proxy, secret swap, git proxy
│ │ ├── usernet/ # macOS gVisor userspace network
│ │ └── linuxnet/ # Linux netns + nftables
│ ├── workspace/ # delta sync, .git sanitizing, 3-way apply, pull, backup / undo
│ ├── output/ # log files, ring buffers, SSE fan-out, ANSI cleanup, grep
│ ├── store/ # SQLite (WAL) + migrations
│ │ └── db/ # sqlc-generated queries
│ ├── safefs/ # the only code path that deletes outside the data root
│ ├── secrets/ # host-side secret storage
│ ├── telemetry/ # metrics and tracing
│ └── ... # backend, config, events, guestclient, ids, image, platform, version
├── pkg/
│ └── client/ # public Go SDK
├── proto/guest/v1/ # guest.proto (host <-> guest contract)
├── gen/guest/v1/ # generated gRPC code
├── api/ # openapi.yaml (HTTP contract)
├── agent-rules/ # rule / skill text installed for harnesses
├── migrations/ # SQL migrations
├── queries/ # sqlc queries
├── images/base/ # base rootfs Dockerfile
├── scripts/
│ ├── images/ # kernel fetch, rootfs build
│ └── dev/ # Lima + dev loop
├── deploy/
│ ├── lima/ # nested-virt Lima VM for the Firecracker backend
│ ├── observability/ # Prometheus + Grafana + Jaeger (docker compose)
│ └── grafana/ # dashboard JSON
├── examples/
│ ├── go-harness/ # Go SDK example
│ └── python-harness/ # stdlib Python over the socket
├── test/
│ ├── e2e/ # real microVM tests
│ ├── fixtures/ # sample repos
│ └── spike/ # early experiments
├── docs/ # TESTING.md
└── web/ # tryboxi.xyz landing page (Next.js, bun)
| Host | Backend | Status |
|---|---|---|
| macOS 14+ on Apple Silicon | Apple Virtualization.framework, VMs hosted in the daemon | Working |
| Linux (KVM) | Firecracker + per-VM network namespaces | Compiles and is unit-tested; real-VM testing happens in Lima |
| Windows | None yet (would need Hyper-V / WSL2) | Not supported |
Measured on an M4 MacBook (16 GB, macOS 26.5).
| Operation | Time |
|---|---|
| Create a sandbox from the warm pool | 20–50 ms |
| Create from a golden snapshot (incl. workspace sync) | 0.5–0.6 s |
| Resume a sandbox paused to disk | 0.5–0.7 s |
| Fork a running sandbox (per clone, parallel) | ~0.8 s |
| Cold boot to guest agent ready | 1.2 s |
| Golden snapshot of the base image | 0.3–0.8 s |
exec round trip for a trivial command |
a few ms |
A step-by-step guide with more detail lives in docs/TESTING.md.
Requirements: an Apple Silicon Mac on macOS 14+, Go, and Docker Desktop (only to build the base image once). At runtime no Lima, Docker, or root is needed.
git clone https://github.com/karthikponna/tryboxi && cd tryboxi
make images # guest kernel (Kata 6.12, sha256 pinned) + Ubuntu 24.04 rootfs, built in Docker (~3 min)
make build # sandboxd (signed), sandboxctl, sandbox-mcp, and the Linux guest agentEverything lands in _out/ (git-ignored).
# Option A: background service that starts at login (LaunchAgent)
./_out/bin/sandboxd install --artifacts _out
# Option B: foreground, best while testing (Ctrl-C pauses sandboxes to disk)
./_out/bin/sandboxd install --artifacts _out --no-service
./_out/bin/sandboxd serve./_out/bin/sandboxd doctor # host checks: macOS, entitlement, APFS, artifacts, socket
./_out/bin/sandboxctl doctor # daemon health and capacityTip: put the binaries on your PATH:
ln -s "$PWD/_out/bin/"{sandboxd,sandboxctl,sandbox-mcp} ~/.local/bin/ID=$(sandboxctl create -w ~/code/my-app | awk '{print $1}') # sbx_...
sandboxctl exec $ID -- npm test # the repo is synced in before every exec
sandboxctl diff $ID # what changed inside the VM
sandboxctl apply $ID # bring it back, with a backup
sandboxctl rm $IDsandbox-mcp is an MCP server over stdio. sandboxctl mcp install writes the MCP config and the
agent rules that tell the agent when to use the sandbox. Make sure the daemon is running first.
sandboxctl mcp install cursor --project /path/to/your/repo
# or for every project
sandboxctl mcp install cursor --globalThis writes:
| File | Purpose |
|---|---|
.cursor/mcp.json (or ~/.cursor/mcp.json with --global) |
Adds the sandbox server |
.cursor/rules/sandbox.mdc |
Always-applied rule describing when to use the sandbox |
Then open Cursor Settings → MCP, enable sandbox, and reload the window if it is not listed.
Manual config
{
"mcpServers": {
"sandbox": {
"command": "/path/to/tryboxi/_out/bin/sandbox-mcp",
"args": [],
"env": { "SANDBOX_WORKSPACE": "${workspaceFolder}", "SANDBOX_CLIENT": "cursor" }
}
}
}sandboxctl mcp install claude --project /path/to/your/repo
# --global installs for your user instead (claude mcp add --scope user)This writes .claude/skills/sandbox/SKILL.md and registers the server with the claude CLI.
If claude is not on your PATH, the command is printed so you can run it yourself:
claude mcp add --scope project --env SANDBOX_CLIENT=claude sandbox -- /path/to/tryboxi/_out/bin/sandbox-mcpInside Claude Code, run /mcp to confirm the sandbox server is connected.
sandboxctl mcp install codex --project /path/to/your/repoThis appends the server to ~/.codex/config.toml (a backup is kept as config.toml.bak):
[mcp_servers.sandbox]
command = "/path/to/tryboxi/_out/bin/sandbox-mcp"
args = []
env = { SANDBOX_CLIENT = "codex" }
startup_timeout_sec = 30
tool_timeout_sec = 900and writes the sandbox rules to AGENTS.md in your project (appending if the file exists, with a backup).
These flags work with all three harnesses. They are set by you, not chosen by the model.
| Flag | What it does |
|---|---|
--project <dir> |
Project to configure (default: current directory) |
--global |
Install for your user instead of one project (Cursor and Claude Code) |
--auto-pull |
Copy changed manifests and lockfiles back to the host automatically, with a backup |
--setup "npm ci" |
Run a setup command once per sandbox with open network, then lock down (separate several with ;;) |
--secrets NAME,NAME |
Bind stored secrets to every sandbox (the VM only sees placeholders) |
| Tool | What it does |
|---|---|
sandbox_exec |
Run a shell command in the microVM (repo mirrored at /workspace, synced before every run) |
sandbox_read_file |
Read a file from inside the sandbox |
sandbox_write_file |
Write a file inside the sandbox only |
sandbox_diff |
Show changes in /workspace relative to its git HEAD |
sandbox_apply |
Merge the sandbox's changes into your repo (3-way, with a backup) |
sandbox_pull |
Copy files commands changed (formatters, lockfiles, codegen) back to your repo |
sandbox_snapshot |
Checkpoint, restore, fork, or list snapshots |
sandbox_network |
Ask to open network access to a blocked host (you approve the call) |
sandbox_port |
Expose a server's port on the host's 127.0.0.1 |
sandbox_status |
Show the session's sandbox and host capacity |
sandbox_destroy |
Destroy the sandbox and free its memory |
- "Run the test suite in the sandbox and fix any failures."
- "Install the dependencies and start the dev server in the sandbox, then give me the URL."
- "In the sandbox, run
npm run lint -- --fixand pull the changed files back." - "Checkpoint the sandbox, try upgrading all dependencies, run the tests; if they fail, restore."
Watch what the agent does live with sandboxctl events.
The API is plain HTTP/JSON on a unix socket. The contract is in api/openapi.yaml
(also served at GET /v1/openapi.yaml).
curl
S="$HOME/Library/Application Support/sandboxd/sandboxd.sock"
curl -s --unix-socket "$S" -X POST http://localhost/v1/sandboxes \
-d '{"workspace":{"host_path":"'$PWD'"},"template":"auto"}'
curl -s --unix-socket "$S" -X POST http://localhost/v1/sandboxes/$ID/exec -d '{"cmd":"npm test"}'
curl -s --unix-socket "$S" -X DELETE http://localhost/v1/sandboxes/$IDGo uses the SDK in pkg/client; see examples/go-harness.
Python works with the standard library or httpx over the socket; see examples/python-harness.
Over TCP (containers, other users, other languages): set tcp_listen: 127.0.0.1:7070 in
~/Library/Application Support/sandboxd/config.yaml, restart, and send Authorization: Bearer $(sandboxd token).
| Area | Commands |
|---|---|
| Sandboxes | create ps rm pause resume shell port top |
| Commands | exec logs grep |
| Files and repo | sync cp diff apply pull undo artifacts |
| Snapshots | snapshot fork restore recover |
| Network and secrets | net secret |
| Observability | events trace |
| Housekeeping | templates gc doctor |
| Harnesses | mcp install cursor | claude | codex |
| Command | What it does |
|---|---|
install --artifacts _out |
Copy the kernel, guest agent, and rootfs into the data root; set up the LaunchAgent |
serve |
Run the daemon in the foreground |
doctor |
Check the host |
config |
Print the effective configuration |
token |
Print the API bearer token for TCP or forwarded-socket clients |
uninstall |
Remove the service, keep data (--purge deletes it, after confirmation) |
version |
Print the version |
flowchart LR
subgraph clients["Your agents and code"]
agent["Cursor · Claude Code · Codex"]
own["Your harness · Go SDK · curl"]
end
mcp["sandbox-mcp<br/>MCP adapter"]
daemon["sandboxd<br/>host daemon"]
subgraph vms["One microVM per sandbox"]
a1["sandbox-agent<br/>PID 1"]
a2["sandbox-agent<br/>PID 1"]
end
net["Per-VM network<br/>gVisor netstack / netns proxy"]
hosts(("Allowlisted<br/>hosts only"))
agent -- "MCP over stdio" --> mcp
mcp -- "HTTP over unix socket" --> daemon
own -- "HTTP over unix socket or TCP" --> daemon
daemon -- "gRPC over vsock" --> a1
daemon -- "gRPC over vsock" --> a2
vms -- "all egress" --> net
net -- "allowlist" --> hosts
Your agent never talks to a VM directly. It calls sandboxd, which boots or reuses a microVM, syncs your repo
in, and runs the command through the guest agent. Every packet a VM sends goes through its own network
stack and the egress policy, so only allowlisted hosts are reachable.
| Binary | What it does |
|---|---|
sandboxd |
Host daemon: HTTP API on a unix socket (optional TCP + token), scheduler, VM drivers, snapshots, workspace sync, egress policy |
sandbox-agent |
Static Linux binary, PID 1 in every VM (delivered via a tiny initramfs), gRPC over vsock |
sandbox-mcp |
MCP stdio adapter for Cursor, Claude Code, and Codex |
sandboxctl |
The CLI |
Data lives in ~/Library/Application Support/sandboxd/ (macOS) or /var/lib/sandboxd (Linux), never in your repo.
sandboxctl trace <sbx_id|exe_id>prints a per-phase waterfall (scheduler.reserve,vm.restore,guest.configure,workspace.sync,exec, ...).sandboxd serve --otlp-endpoint http://127.0.0.1:4318also exports spans to Jaeger, Grafana Tempo, or any OpenTelemetry collector.- Prometheus metrics are at
/metrics; importdeploy/grafana/sandboxd-dashboard.jsoninto Grafana, or start the stack indeploy/observability.
These rules are enforced in code:
- Sync is one-way, host to VM. Deleting files inside a VM never deletes anything on the host.
- The daemon writes to your repo only through
applyandpull. Both back up every file they are about to change;sandboxctl undo <artifact_id>restores it. applyandpullare careful. They reject absolute paths,.., symlinks that escape the repo, and anything under.git/. They never commit, switch branches, reset, clean, or touch the git index (3-way merges usegit merge-fileon temp files).pullrefuses to overwrite a host file that changed since the last sync unless forced.- Deletes are fenced. Every delete goes through
safefs.RemoveUnder(root, path), which resolves symlinks and refuses anything outside the daemon's data root. - No shared folders. No host folder is ever shared into a VM (no virtio-fs); files move only by explicit copy over vsock.
- Secrets are not synced by default (
.env*,*.pem,*.key,id_*,.npmrc,.aws,.ssh, ...), and the copied.git/confighas credential helpers, auth headers, includes, and URL credentials stripped. - Credentials never enter VMs. Stored secrets reach a sandbox only as placeholders. The egress proxy swaps in the real value for the secret's hosts (TLS terminated with a local CA), scrubs echoed values from responses, and blocks placeholders sent anywhere else. A git proxy adds the git token only for allowed repos and refuses pushes outside allowed branches. Commands, env, stdin, and synced files that contain a stored secret value are rejected.
- Egress is allowlist-only by default (package registries, GitHub, Ubuntu mirrors). Host loopback, LAN, link-local/metadata, and CGNAT ranges are always blocked, and DNS answers pointing there are dropped.
- Uninstall keeps data unless
--purgeis passed and confirmed. GC supports--dry-run, and every deletion is logged as an event.
| Command | What it does |
|---|---|
make images |
Fetch the kernel and build the base rootfs (Docker) |
make build |
Build all binaries for this host (signs sandboxd on macOS) |
make test |
Unit tests |
make lint |
go vet (host + linux/arm64) and gofmt |
make e2e-darwin |
Real microVM tests on this Mac (needs make images) |
make test-linux |
Guest process tests in a Linux container |
make proto / make sqlc |
Regenerate gRPC and SQL code |
See CONTRIBUTING.md for the full workflow.
The website lives in web/: cd web && bun install && bun run dev.
This repository's source code is available under the MIT License. You are free to clone, use, modify, and distribute it, including commercially, as long as the copyright notice is kept.
Thank you for reading till the end, please give a star ⭐