Run in a VM
runtime: vm runs an agent in its own smolvm microVM. It’s for an agent you leave alone with code nobody has reviewed: a smoke tester that installs and runs a fresh build, or an agent that runs whatever a pull request contains. Whatever that code does, it does inside the VM, not to your machine.
A container shares your kernel and usually your harness login. A VM has its own kernel, and starts with nothing from your host except the agent’s workdir, read-only.
Set it up
Install smolvm:
brew install smol-machines/tap/smolvmThen give the agent runtime: vm and a vm: block. The rest of the workforce keeps its own runtime:
runtime: localagents: smoke: runtime: vm acp: { preset: pi } vm: image: ghcr.io/zooid-ai/agent-pi:latest # defaults to the preset's image cpus: 2 memory: 4GiB disk: 16GiB allow_hosts: [github.com, registry.npmjs.org]vm.image is a registry reference, or a local docker save archive or rootfs directory when it starts with /, ./, ../ or ~/.
The daemon creates and starts the machine when it starts, and stops it (keeping its disk) when it stops. Changing image, cpus, memory, disk, allow_hosts or mounts, or rebuilding a local image, recreates the machine and discards its disk. Changing vm.env doesn’t.
What the VM gets
| Workdir | the agent’s workdir, at /workspace, read-only. Not even root in the VM can write it. |
| Network | none, unless you list hosts in vm.allow_hosts. Then exactly those hosts, nothing else. Your host isn’t reachable. |
| Env | only what you list in vm.env, with the same rules as container.env. |
| Mounts | only what you list in vm.mounts, with the same entries as container.mounts. Directories only; rw unless you set mode: ro. |
| Credentials | none of yours. Zooid passes no login or key of its own. |
| Zooid MCP | not yet: a vm agent has no zooid_* tools in this release. |
The daemon refuses to start if a vm agent’s workdir holds .env*, .dev.vars* or .claude/settings.local.json, since the VM can read everything in it.
Giving it a login
An agent needs a model credential. Pass one the same way you would to a container: a key in vm.env, or a login directory in vm.mounts. A login that refreshes its own token needs a rw mount so the refreshed token reaches the next run:
vm: allow_hosts: [chatgpt.com, auth.openai.com, github.com] env: PI_CODING_AGENT_DIR: /pi mounts: - { host: ~/.zooid/smoke-pi, target: /pi, create: true }Log the harness in once with that directory as its config dir, and the agent uses it from then on.