Early · macOS and Linux
Let coding agents run free.
Keep your machine out of reach.
Solitary runs coding agents in hypervisor-isolated cells: disposable virtual machines with no host mounts, narrowly scoped secrets, controlled network access, and a deliberate way to move work in and out.
The problem
A mounted project is not a boundary. It is a way in.
Most tools that sandbox a coding agent hand it a container and mount your project directory into it. An agent that can write to that directory can write to .git/hooks/pre-commit, a package.json script, a Makefile, an .envrc, a .vscode/tasks.json, or a lint plugin resolved from the local tree.
Then you run npm test or git commit on your machine, and the payload runs as you. No escape was necessary. You executed it yourself.
The model
A cell is a VM with a container inside it.
The boundary and the toolset are different things, so you can replace one without disturbing the other.
Your host
Starts machines, holds the secrets, displays results. Never mounted into a cell.
The VM
The boundary
The boundary. A hypervisor, not a shared kernel — root in the container is not root on your machine.
The container
The toolset, and replaceable. Change the image, run up again: same disk, same secrets, different tools.
Nothing is mounted from the host, ever. Clone, build, lint, test and review inside the cell. Core concepts →
See it running
Every cell, and what its network is doing.
solitary dashboard╭─────────────────────╮╭──────────────────────────────────╮
│ cells ││ claude │
│ › ● claude running ││ image build:./Containerfile │
│ ○ demo stopped ││ machine 4 cpus · 4GiB · 40GiB │
╰─────────────────────╯│ ports all reach host localhost │
│ network 2 allowed │
│ vpn up · handshake 12s ago │
│ secrets 2 of 2 set │
╰──────────────────────────────────╯
↑↓ move · ⏎ shell · u up · s stop · e secrets · d rm · q quitsolitary dashboard · t ╭─────────────────────╮╭─────────────────────────────────────────────────╮
│ cells ││ traffic · claude │
│ › ● claude running ││ 12:07:53 query api.github.com ×2 │
╰─────────────────────╯│ 12:07:53 resolved api.github.com → 140.82.121.6 │
│ 12:07:53 refused example.com │
│ 12:07:53 denied 1.1.1.1:443 │
╰─────────────────────────────────────────────────╯
↑↓ scroll · G live · / filter · b refused only · c clear · esc backThe traffic view reads the machine's own log, so a cell cannot see — let alone edit — what is recorded about it. It is also the fastest way to find what an allow list is missing: a refused line names it.
What you control
Authority arrives in named pieces.
Default-deny egress
List the domains a cell may reach and it reaches nothing else — not the rest of the internet, not your machine, not your local network. One list drives both the resolver and the firewall.
A VPN with a kill switch
Point a cell at a WireGuard config and everything it reaches leaves through that tunnel. With the tunnel down, nothing leaves at all — no quiet fallback to the route it came in on.
Secrets whitelisted per cell
This cell sees a GitHub token; that one does not. Values live on the host and are passed in by name, so a cell you destroy is a cell you can rebuild already authenticated.
Ports are the way in
Run a dev server in the cell and open it in the browser on your host. Name the ports you want and only those are forwarded.
An explicit hand-off
The cell publishes; the host collects. Files come out by name, never executable, and never over something already there — because a name chosen inside a cell is untrusted input.
Cells are disposable
Destroy one and make a clean one from the same definition. The disk goes; the definition and the credentials, which were never inside it, stay.
A cell in practice
Define it once. Share it. Rebuild it clean.
The definition says what a cell needs, not the values it receives. Publish it and whoever copies it supplies their own credentials and their own tunnel — so an environment can be reviewed in a pull request before anyone runs it.
Shareable workflows →image: ghcr.io/you/agent:latestsecrets: # only these names are passed in - GITHUB_TOKENports: # only these reach the host - 8080network: # and it reaches nothing else allow: - github.com - registry.npmjs.org vpn: ./vpn.conf # optional: all of it, through this tunnelHonest limits
This does not make an agent harmless.
Isolation stops a compromise of your machine. It does not stop an agent misusing the authority you granted it: it can still push to any repository the token you whitelisted can reach, and reach anything you allowed. A cell with no allow list reaches the whole internet. The image you run is trusted code.
A secret passed to a cell lives inside that cell. Give one only the credentials it needs, and treat a cell's output as data rather than as something to run.
Limitations and trade-offs →Install it and take a cell for a walk.
Every release ships a binary for macOS and Linux, and the source builds in one command. It needs Lima 2.0 or newer, and Go only if you build it yourself. Creating the first cell takes a couple of minutes while it downloads a cloud image and installs podman; everything after that is container-speed.
curl -fsSL -o solitary.tar.gz \ https://github.com/balakin/solitary/releases/latest/download/solitary_darwin_arm64.tar.gztar -xzf solitary.tar.gz solitaryinstall -m 755 solitary /usr/local/bin/solitary