Crackbox VM backend

Planned — not yet shipped. This cookbook documents the design and migration challenges for the crackbox backend. None of it is implemented. It is here to anchor the architecture before the work begins.
The container is an abstraction. The gateway calls container.Run() — swapping Docker for QEMU is a change in that one package.

The current backend runs agents in Docker containers: docker run -i --rm, one container per message invocation. Crackbox replaces Docker with QEMU/KVM virtual machines. The agent inside is identical — same image, same IPC, same skills. The isolation boundary is stronger: a separate kernel, no shared namespace with the host.

What crackbox is

Crackbox is a QEMU/KVM VM platform designed for agent sandboxing:

The seam: container.Run()

container/runner.go implements one function: Run(ctx, cfg, onOutput) error. It starts a Docker container with the given config, streams output to onOutput, and returns when the container exits.

A crackbox backend would implement the same signature, replacing Docker with a QEMU VM boot (or pool claim). The gateway calls runner.Run() and never imports Docker. The gateway does not need to change.

// current: container/runner.go
func Run(ctx context.Context, cfg RunConfig, onOutput func(string)) error {
    // docker run -i --rm ...
}

// crackbox: crackbox/runner.go
func Run(ctx context.Context, cfg RunConfig, onOutput func(string)) error {
    // claim or boot a QEMU VM
    // mount group folder via virtio-9p
    // run agent-runner inside the VM
    // stream output via SSH or serial console
}

Migration challenges

1. Execution model mismatch

The current model is one-shot: docker run -i --rm with JSON on stdin, output on stdout. Crackbox's claude-stream endpoint (as prototyped) takes a different approach. A new /v1/run endpoint that accepts the same start.json format and returns streaming output is needed before the two can be wired together.

2. File mounts

Docker uses bind mounts (-v /host/path:/container/path) to give the container access to the group folder. VMs use virtio-9p (Plan 9 filesystem protocol) for host-to-guest file sharing. The mount behavior is similar but not identical — file locking semantics differ. Needs testing under load.

3. MCP unix socket

The current IPC design uses a unix socket mounted into the container via a volume. Unix sockets do not cross VM boundaries. The crackbox backend needs a TCP bridge instead:

Current (Docker)Crackbox (VM)
socat STDIO UNIX-CONNECT:/workspace/ipc/router.socksocat STDIO TCP:host-gateway:PORT
Unix socket volume-mounted into containerTCP port forwarded through TAP network

The MCP server in the gateway would listen on a TCP port (bound to the TAP network interface) instead of a unix socket. The settings.json socat command in the agent container changes accordingly. Everything else in the MCP stack is unchanged.

4. Host KVM access

arizuko itself runs in Docker (see docker-compose.yml). QEMU/KVM requires /dev/kvm access, which must be passed through from the host:

# docker-compose.yml fragment for crackbox mode
services:
  gated:
    devices:
      - /dev/kvm:/dev/kvm

This works on Linux hosts with KVM enabled. It does not work on Docker Desktop (macOS/Windows) without additional configuration. Crackbox is a Linux-only deployment.

Boot time tradeoff

Docker container start: under 1 second. QEMU VM boot: 15–30 seconds (Alpine, minimal image, KVM acceleration). This makes crackbox unsuitable for per-message invocations in high-frequency conversations. The intended use is long-running conversations — a session that lasts 10 minutes does not notice a 20-second boot.

A warm VM pool mitigates this: boot VMs ahead of time and claim them at invocation. The pool size is a configuration tradeoff between memory cost and boot latency.

How it fits the system

container.Run() is the seam. The VM becomes a different backend implementing the same input/output protocol. The agent inside the VM is identical — same Alpine image, same Claude Code CLI, same IPC, same skill files. From the gateway's perspective, a crackbox run and a Docker run are the same call with the same return contract.

This is why the container runner is isolated in its own package with no gateway-internal imports. The backend is replaceable. The agent is not.