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:

skiff new task-42 -p 3000
skiff ssh task-42 -- 'curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && apt-get install -y nodejs gh rsync && curl -fsSL https://claude.ai/install.sh | bash'

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.

claude setup-token
skiff secret set claude-token

Grant claude to the VM and open a session. The broker runs for as long as this terminal is open:

skiff grant task-42 claude
skiff ssh task-42

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:

skiff secret set github-token

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:

skiff grant task-42 github
git clone https://github.com/acme/app && cd app

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:

curl -fsSL --create-dirs https://raw.githubusercontent.com/sovyn-eu/skiff/main/examples/caps/chrome-devtools.toml -o ~/.skiff/caps/chrome-devtools.toml
skiff cap lint chrome-devtools && skiff grant task-42 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:

ssh-add -c ~/.ssh/id_ed25519
skiff grant task-42 ssh-agent

-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.

skiff cap init codex

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:

skiff tunnel task-42

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:

for n in a b c; do skiff new agent-$n --grant claude --grant github; done

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:

skiff new paseo-3 --owner paseo --slot 3
skiff gc --owner paseo --keep paseo-1 --keep paseo-2

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, inspect and gc take --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 cmd joins words into a shell line, like ssh.
  • Check exit codes. ssh and exec return the remote status; 255 means the connection failed.
  • Pipe secrets. echo "$TOKEN" | skiff secret set NAME reads stdin when it is not a terminal.
  • Read denials. A 403 from the broker carries a hint with the exact skiff grant command. Surface it to the human; do not grant yourself what the hint asks for.

Before you leave

skiff stop task-42

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.