Measured, not promised
Cheap enough to be disposable
Seconds to a clean machine, and a copy of it for every idea. Every number below came from a real run on one Apple M4 mini.
Verified on M4 Two runs of the demo; the timings above are from both. Your Mac, disk and image will differ.
Install
One command, no sudo
The installer downloads the release package for your machine, verifies its SHA-256 and links the binaries into ~/.local/bin. Run it again to upgrade; --version rolls back. Current is 0.4.18 on macOS: fluxctl agent, sessions and --host-gpu are in the package (fluxctl agent --help confirms it).
curl -fsSL https://zyvorai.github.io/zyvor-fluxvm/install.sh | bash # with the daemon at login and an MCP client wired up: curl -fsSL https://zyvorai.github.io/zyvor-fluxvm/install.sh | bash -s -- --service --client claude-code # it verifies the SHA-256, needs no sudo, writes under ~/.local; # --dry-run prints the plan, --version X pins or rolls back, --uninstall reverses it. fluxctl --version # 0.4.18 fluxctl agent --help
macOS 14+ on Apple silicon; Linux x86_64 from the same installer when the release has that asset (check the release page; the package is built in ubuntu:22.04). Every option, upgrade and uninstall · Homebrew notes
The loop
Four ideas. One survivor.
An agent’s test fails. It forks the sandbox into four running copies, tries a different fix in each, runs the tests in all four at once, and keeps whichever passes. This is the real output of examples/agent-sandbox-demo/run.sh.
$ examples/agent-sandbox-demo/run.sh 1. offline sandbox b1c1624a-d1bd-4be8-9542-68bfa337f12f ready in 4.4 s; network: lo UNKNOWN 127.0.0.1/8 ::1/128 2. a project whose test fails before the fix: FAILED (failures=1) | exit=1 3. fork into four running copies 4 copies running in 4.0 s (parent snapshot 677 ms, slowest child 3303 ms) 4. four different fixes, tried in parallel all four tried and tested in 0.3 s attempt-1: tests PASS attempt-2: tests fail (rc=1) attempt-3: tests fail (rc=1) attempt-4: tests fail (rc=1) 5. keep the one that passes - return sum(xs) / len(xs) + 1 # bug: an off-by-one nobody asked for + return sum(xs) / len(xs) the other four are deleted
Every attempt is isolated
A broken rm or a half-applied patch happens in a copy that is deleted a second later. Nothing leaks into the next attempt or onto your Mac.
Parallel, not sequential
The four copies run at the same time, so four attempts cost the time of one. The fork took about four seconds; the four test runs took 0.3.
Plain MCP tools
sandbox_create, sandbox_exec, vm_fork, vm_delete. The script is those same calls over REST; a model drives them the same way.
Agent sessions
One handle, not five primitives
A session wraps a sandbox with a workspace, a network policy, checkpoints and a ledger of what was done. It adds no new isolation: it composes the sandbox, snapshot, fork and changeset calls that already exist.
$ fluxctl agent run codex --policy offline --ttl-seconds 900
session 14bd10ba backend vz capabilities {snapshot: true, fork: true, pty: false}
$ fluxctl agent exec 14bd10ba -- 'echo parent > /tmp/p'
$ fluxctl agent checkpoint 14bd10ba --tag before
$ fluxctl agent fork 14bd10ba --count 2
2 children running in 2.5 s (identity: best-effort)
$ fluxctl agent exec <child-1> -- 'cat /tmp/p; echo child1 > /tmp/who'
parent
$ fluxctl agent exec 14bd10ba -- 'cat /tmp/who'
cat: /tmp/who: No such file or directory # the parent never saw it
$ fluxctl agent timeline 14bd10ba
session.create · exec · checkpoint · forkPolicy, compiled
offline, developer-safe or your own host list, turned into the allow-list the host already enforces. Method and path limits per host are enforced by the egress proxy where the sandbox's traffic reaches it, and refused at create where it cannot.
Checkpoint and roll back
A memory checkpoint reverted a file change on restore. Restore takes seconds, not milliseconds; a disk-only checkpoint is smaller but cold-boots.
Verified on M4A ledger, not a recording
Every exec, checkpoint, restore and fork made through the session is logged with its exit code and duration. Replay and streaming terminals are not built; preview URLs are (run on the Mac NAT).
MVPCreate 4.6 s, first exec 0.4 s, checkpoint 0.7 s, restore 3.9 s, fork into 2 running children 2.5 s: median of 5 runs on one M4 mini, debug build, offline session. The tables · Agent README · API
Controls
What you can hold an agent to
Implemented, reviewed and covered by unit and router tests. Not yet run through a live guest: treat them as ready to trial, and use the checks in the guide on your own hosts before you rely on them.
Filesystem confinement
--confine --fs-write /data: every command, job, file and git call runs under Landlock and seccomp in a Linux guest. Create fails, with the reason, where the guest cannot enforce it.
Approvals
process.ask holds a matching command until an operator approves exactly that command, once. A session token cannot approve its own request.
Scoped tokens
Give an agent a token for one session and a list of operations, not the admin credential. It expires and dies with the session.
Router testsJobs that stream
Long-running commands with separate stdout and stderr, resumable offsets, stdin and EOF, a timeout and tree-wide signals. The command’s own exit status survives the output cap.
TestsCredentials the agent never sees
A grant injects an Authorization header for named hosts, optionally only for given methods and paths, and ends with the session.
Budgets and big files
Limits on commands, compute time, jobs, forks, output and workspace size; resumable, checksum-verified uploads of files up to 2 GiB.
TestsThe practical guide (policy, tokens, approvals, jobs, transfer, budgets, fork and promote, troubleshooting) · API reference
Private by construction
No network card. Still smart.
An offline sandbox has no network device at all: only a loopback. Commands and files still work over vsock. When the agent needs a model, one allow-listed port relays to a server on the Mac, so the code and the prompts never leave the machine.
Offline
sandbox_create {"offline": true}. The guest can reach nothing: ip -br a shows lo only.
Allow-listed
allow_hosts opens ports 80 and 443 to the names you list, through a proxy on the host that refuses everything else. Run through a session: the listed host answered 200, another host was refused, and a direct connection had no route.
Host inference (host_gpu)
fluxctl agent run --host-gpu (or host_gpu: true) relays guest OPENAI_BASE_URL to the Mac’s Metal/MLX or llama.cpp server over vsock. No network card. host-inference
The model run used a deliberately tiny 135M-parameter model, so its answers are weak: it proves the path, not answer quality. A stand-in server (fluxctl infer serve --stand-in) is enough for path tests.
Versus microsandbox
Where we are ahead, and where we are not
microsandbox is a good, fast, embeddable runtime. The left column is what its README and releases say; the right is what we ran. We have not benchmarked them against each other.
| Capability | microsandbox (documented) | FluxVM | Status |
|---|---|---|---|
| Isolation | microVMs (libkrun) | vz on a Mac; KVM, Firecracker, Cloud Hypervisor, QEMU on Linux | Run on vz |
| Startup | under 100 ms guest boot (their M1 figure) | 4.6 s offline, 6.8 s on the NAT (M4, debug build) | Behind |
| Fork a running sandbox | yes | offline vz sessions and in-tree KVM; not a NAT sandbox | Run on vz |
| Checkpoint / restore | full snapshots, branches, --cow-mem | memory+disk or disk-only per session | Run on vz |
| Review before apply | not documented | speculate, changeset, approve, apply | Tests |
| Network policy | host allow-list, CONNECT proxy | host allow-list; eBPF CIDR/DNS/rate rules on Linux | Run on vz; eBPF not run |
| Secrets kept out of the VM | yes (TLS interception) | credential broker with per-sandbox grants | Tests |
| Embeddable, no daemon | yes | no: needs fluxctl serve | Behind |
| SDKs | TS, Rust, Python, Go, Ruby | Go, Python, TypeScript (sessions: Go, Python) | Behind |
| Windows host | yes (WHP) | no | Behind |
| Fleet of Macs, tenants | not documented | central, leases, FluxMesh | Simulator only |
We are behind on startup time, embeddability, SDK languages and Windows. We are different in running one control plane over several VMMs with a fleet layer, and in ending a speculative run as a changeset you can review. The full comparison, with sources
What it costs
Memory you can plan with
Resident size per sandbox as macOS reports it, on a 16 GB M4 mini, idle. It varies a lot, so we show the lowest and the highest value seen, not a single number.
| Profile | Configured | Start | Sandboxes sampled | Resident MiB, lowest to highest |
|---|---|---|---|---|
tiny | 512 MiB | cold boot | 7 | 347 to 779 |
small | 1 GiB | cold boot | 7 | 623 to 788 |
standard | 2 GiB | claims a warm slot | 7 | 205 to 712 |
Plan on about 800 MiB per active sandbox: the lowest value is a floor, not a plan. On a 16 GB Mac that leaves room for roughly a dozen active sandboxes next to macOS and your apps; that last line is an estimate, not a measurement. Idle and hibernated sandboxes cost less. Measured: 21 sandboxes. Not measured: guests doing real work, eight or more at once, Mac Studio sizes. The full table
Plugs into your agent
Two commands from a clean Mac
Claude Code, Cursor, Claude Desktop, Codex, VS Code, Windsurf and Gemini CLI each get a fluxvm MCP server in their own config. Tested end to end from a fresh home directory on the published package: handshake, 66 tools in 0.4.11, a sandbox created, a command run, a fork, a delete.
# once per Mac
curl -fsSL https://zyvorai.github.io/zyvor-fluxvm/install.sh | bash -s -- --service
# once per agent client
fluxctl mcp install claude-code --allow-write
# then, in the agent: sandbox_create {"offline": true}
# sandbox_exec {"vm": id, "command": "pytest -q"}
# vm_fork {"vm": id, "count": 4} ... vm_deleteWrite tools (--allow-write) can create, run commands in and delete VMs; leave them off for a read-only agent. MCP reference
What to know before you rely on it
- Only an offline (or no-NAT) sandbox can fork. A default sandbox sits on the Mac’s NAT, and its copies would share its address, so the daemon refuses.
- Forked copies keep the parent’s identity (hostname, machine id, SSH host keys): an offline sandbox has no guest agent over SSH for the reset. Fine for throwaway attempts; not for anything that registers itself somewhere.
- The first sandbox on a fresh daemon took 61 s while the image was prepared; later ones took about 4.5 s, and about 1 s when claiming a warm slot.
- This page is about Linux guests. macOS guests are a different, less proven workflow: see FluxVM on Mac.
- Everything here ran on one Apple M4 with macOS 27.2. The package is ad hoc signed; vmnet, bridged networking and USB passthrough need a Developer ID build.
Give your agent a clean machine.
curl -fsSL https://zyvorai.github.io/zyvor-fluxvm/install.sh | bash, then run the demo and watch four attempts become one.