Fleets and VMs
The safest place for an agent to do risky work is a Mac you can throw away. The single-host half of that shipped: macos-mcp vm clones a golden macOS image, runs the clone on this Mac and destroys it. The hub — the part that turns many Apple Silicon hosts into one pool behind the same MCP config entry as your own Mac — is still in development.
Status: half of this ships today. Shipped in v0.2.0 (2026-09-07): single-host macOS VMs on native Virtualization.framework — macos-mcp vm clone, start, stop, destroy, status, screenshot, ip, input, grant and verify, as CLI subcommands rather than MCP tools. In development: the hub — machine registry and enrollment, leases, the machine parameter that routes any tool to any Mac, display streams and the orchestrator API. That is the project's Phase 3: designed, prototyped and measured on macOS 27, with the working notes in the repository's knowledge/ directory, but not in a release. The single-Mac server on the rest of this site needs none of it.
Shipped today: one host
Everything here runs on a single Apple Silicon Mac, from your shell, with no daemon and no enrollment. Disposable macOS VMs is the hands-on page — commands, output and the failure modes.
- Golden images and instant clones. A clone is an APFS clonefile: instant, and no disk until the guest writes. Each clone gets a fresh
VZMacMachineIdentifier, its own MAC and its own auxiliary storage — the three things that must not be shared — and the identifier is verified by read-back, not assumed. - TCC solved once per image.
vm grantorchestrates the one physical click a golden image needs, probes from fresh ssh sessions until both grants read back, records them in the image's manifest and seals it for cloning. Every clone inherits the grants. - A per-clone acceptance test.
vm verifyproves a clone is drivable with no human inside it: an ssh session reads the accessibility tree, posts a keystroke and screencaptures, while the host confirms the VM window is not black. Nonzero exit when it is not true. - Capacity enforced up front.
CapacityGuardedDriverrefuses the third macOS guest on a host with a typed error instead of letting the kernel refuse it opaquely — the measured quota, in the code path. - Host-side capture.
vm screenshotcaptures the VM window from the host and reports what fraction of it is not black, which is the only honest health signal a guest has.
These are CLI subcommands, not MCP tools: the frozen tool surface is unchanged at 44 tools, 24 on by default.
In development: the hub
- One config entry, many Macs. Every tool takes an optional
machineparameter. The hub routes the call to that machine's agent — a VM or a physical Mac that enrolled with a one-time token — and returns the result unchanged. No per-machine tool copies bloating the context. - Leases. An agent holds a machine for the duration of a task; expiry frees it, release is holder-scoped, and the hub audits everything centrally.
- Display streams. Thumbnails first, live view when wanted — read from the host's view of the VM window, because that is the only place a guest's pixels exist.
- Golden-image pipeline. Building an image from an IPSW is still the recipe in knowledge/ rather than a subcommand: boot straight to a clean desktop, no Setup Assistant, no Apple Account pane, auto-login on. Solved once per image; inherited by every clone.
- Codeman-agnostic. Codeman is the flagship consumer — every machine becomes a tab — but the orchestrator API is plain and documented so any tool can drive it.
What we measured on macOS 27
Most of what is written about macOS virtualization is aspirational. These were verified on a macOS 27 beta host running macOS 27 guests, and they shape the design — and now the shipped code:
| Finding | Consequence |
|---|---|
Two macOS VMs per Apple Silicon host, and it is a kernel quota. The third is refused instantly with VZErrorDomain code 6 while 39% of RAM is free. | Fleet capacity is hosts × 2. More RAM never helps; multi-host scheduling is the answer. Linux guests are uncapped. Shipped: the driver refuses the over-quota clone or start with a typed error, so a scheduler can route elsewhere instead of retrying into a wall. |
| A guest renders nothing unless a view is attached and the host session is logged in and unlocked. Headless, locked and lost-WindowServer all produce an all-zero framebuffer. | UI automation in a VM depends on the host's session. A locked host blacks out every VM on it, so hosts get a specific profile: never lock, never sleep. Shipped: a running VM is a window in the console user's session, launched there through launchctl asuser when the caller is not already in it. |
| Provisioning does not finish the job. Skipping Setup Assistant still leaves the per-user first-login wizard. | The golden-image recipe pre-suppresses it with a verified key set — including the managed SetupAssistant domain, which is what actually silences the Apple Account pane — so a clone boots to a usable desktop. |
Exactly one entitlement — com.apple.security.virtualization. Over-entitling is fatal: an unauthorized networking entitlement is killed at exec with no crash report. | The shipped binary carries that one key and nothing else, under its Developer ID signature; the VM runner is the same binary, launched into the console session. A build from source needs Scripts/sign-dev.sh once per build to get the entitlement. |
| Process health means nothing. Every failure mode presented as a healthy process with a black screen. | Health checks sample the framebuffer for non-black content from the host and watch for WindowServer port death. Shipped: vm screenshot reports a non-black fraction, and vm verify wakes a slept guest display before it samples, because a healthy guest that idled into display sleep also captures black. |
| An ungranted guest has no input channel. Host-synthesized events never reach a guest; a guest's own screen sharing serves black and discards input; its TCC database is SIP-protected; a manual PPPC profile grants nothing. Six routes measured and ruled out. | The accessibility grant costs one physical click per golden image, once. Clones inherit it. That is honest, and it is the design — vm grant around the click, vm verify after it. |
The full write-ups, with build numbers and the measurement recipes, are in the repository: knowledge/README.md.
Why a VM, not a sandbox
Agents make mistakes at machine speed. A confirmation token catches the destructive call you anticipated; a disposable clone catches the one you did not. Risky work — untrusted downloads, installers, “try this script”, a hostile web page — runs on a machine whose entire state is a clone you delete afterwards, while your own Mac's grants stay untouched. The same agent in a VM also gateways to that VM's Safari MCP, so browser work is isolated too.
Roadmap
- Done — VM lifecycle on native Virtualization.framework, behind the
VMDriverprotocol: clone, start, stop, destroy, capture and input on one host (v0.2.0). - Done — golden-image TCC: one physical click per image, none per clone —
vm grantaround the click,vm verifyas the per-clone acceptance. - Hub daemon: machine registry, enrollment, leases, routing via the
machineparameter, central audit. - Golden-image build and clone pipeline as a first-class command, from the verified recipe.
- Display streams and the orchestrator API that Codeman consumes.
Progress is tracked in IMPLEMENTATION.md. If you run Mac fleets today and want to shape this, open a discussion. For the half that already runs, start at disposable macOS VMs.