Run your coding agent
Give every agent its own VM with root, a package manager and the freedom to run what it just wrote. Hand in Claude, GitHub and your Mac's MCP servers through the broker, never as copies.
Prepare the workspace
A new VM is a plain Ubuntu 24.04 with git, curl and jq. Create one per task and install the agent's toolchain over SSH; this gives a task VM Node 22, Claude Code, gh and rsync in about a minute:
Put that line in a script and run it after every new, or keep a prepared VM stopped and start it per task.
-p 3000 brings the agent's dev server to localhost:3000 on your Mac. Use one VM per task or per untrusted repository, not one VM for everything: whatever a VM is granted, every process in it can use while the broker is up.
The VM keeps no copy of your credentials, but the agent's own model traffic still goes to its provider. skiff isolates the workspace; it does not change where inference happens.
Claude Code
Create a long-lived subscription token on the Mac and store it in the Keychain. This is a one-time step.
Grant claude to the VM and open a session. The broker runs for as long as this terminal is open:
Inside, just run claude in your project. No login screen: the grant set ANTHROPIC_BASE_URL to the broker and CLAUDE_CODE_OAUTH_TOKEN to a placeholder, and the broker swaps the placeholder for your token on every request. On the base profile, install Claude Code first with curl -fsSL https://claude.ai/install.sh | bash.
Usage draws on your own subscription like any other session. Anthropic's terms allow your own subscription in sandboxes you run, not routing other people's requests through your account, so keep the broker personal. Use tmux in the guest if the agent should survive a dropped session; it will lose the broker until you reconnect.
GitHub: clone, push, PRs
Create a fine-grained token on github.com and select only the repositories the agent may touch, with Contents: read and write and, for gh pr, Pull requests: read and write. The token is the boundary: the agent reaches exactly those repositories, through git and gh alike. Store it once:
Grant it to the VM. git in the guest then works with ordinary GitHub URLs, both https://github.com/ and git@github.com:, and gh needs no login:
Commits are authored inside the guest, so set git config --global user.name and user.email there. Which permission each command needs is in What a grant reaches.
MCP servers from your Mac
Some tools only make sense on the Mac: your browser, your local apps. An mcp capability runs the stdio server on the Mac and hands it to Claude Code in the guest as an HTTP MCP server. The repository ships one for Chrome DevTools:
The grant merges mcpServers.chrome-devtools into /root/.claude.json in the guest. The server starts on the first request. It runs on your Mac with your rights, which is why the example pins its version.
SSH keys instead of tokens
If the agent needs your SSH keys (a private git server, a deploy host), forward the agent instead of copying keys:
-c makes macOS ask you to confirm every signature, so you see each time the agent uses a key.
Codex, Gemini and others
Any agent whose CLI accepts a base URL or an API key from the environment can go through the broker. Write a capability with a proxy to the provider's API, put the real key in a header from the Keychain and point the CLI at {{ .BrokerURL }}/<name> with a placeholder key. Writing a capability walks through a complete example.
Agents that insist on an interactive login with a browser or a device code can of course log in inside the VM. That works, but then the credential lives on the VM's disk; remove the VM when the task is done.
Editors and agent runners
With Include ~/.skiff/ssh_config in your ~/.ssh/config, every VM is a plain SSH host named NAME.skiff, so VS Code Remote, rsync and agent runners with their own ssh session work unchanged. Plain ssh does not carry the broker, though. Keep it up in a second terminal:
The tunnel reconnects if the connection drops and ends with Ctrl-C or when the VM stops. skiff ssh task-42 works alongside it and shares the same broker.
Many agents, side by side
VMs cannot reach each other, your Mac or your LAN; only the internet. Running several agents on different branches is just several VMs:
Each VM gets the CPUs and memory you pass with --vcpus and --mem (at least 1 GiB); the Mac's own resources are the only limit.
Pools owned by a tool
A tool that keeps a pool of agent VMs labels them with an owner and a slot. skiff ls shows both in OWNER and the age in AGE:
gc only removes VMs that have an owner; VMs you created by hand are never collected. --older-than 7d selects by age, --dry-run shows the plan, and a plain skiff gc only cleans grants and tokens left on the Mac for VMs that no longer exist.
Driving skiff from a tool
When an orchestrator or an agent on your Mac manages VMs, these keep it reliable:
- Read JSON.
ls,new,inspectandgctake--json; human output goes to stderr then. - Pass argv, not strings.
skiff exec NAME -- git commit -m "it's done"quotes every argument, so the guest sees exactly that argv.skiff ssh NAME cmdjoins words into a shell line, like ssh. - Check exit codes.
sshandexecreturn the remote status; 255 means the connection failed. - Pipe secrets.
echo "$TOKEN" | skiff secret set NAMEreads stdin when it is not a terminal. - Read denials. A
403from the broker carries ahintwith the exactskiff grantcommand. Surface it to the human; do not grant yourself what the hint asks for.
Before you leave
Stop keeps the disk and the grants; skiff start brings both back. When the task is done, skiff rm task-42 deletes the VM together with its grants and placeholder token. Push the agent's work first: the VM disk is the only copy of anything not pushed.