# The VM sub-agent `vm_agent` hands a task to a **second model** that has a real, throwaway machine in front of it — Ubuntu or Windows. That model writes the script, runs it, reads the actual error, fixes it, and comes back with evidence. The assistant can reason, search and calculate. It cannot *try* anything. Asked whether a PowerShell script works, it can only read documentation about PowerShell. This is the missing half. ## What the assistant sees One tool, and none of the machinery. The sub-agent's own thirty-odd tools, its screenshots and its transcript never enter the assistant's context — it gets a task in and a report out. Three outcomes: - **DONE** — a header of verified facts assembled by the daemon (what ran, exit codes, files changed, the commit, the supervisor's independent re-run of the agent's own verification command), plus the agent's summary, clearly delimited and marked untrusted. - **NEEDS INPUT** — it asked something. Answer and call again. - **IN PROGRESS** — rare; the session ran past its wall clock. ## Why the report is assembled by the daemon Everything the guest prints reaches the sub-agent, then the tool result, then the assistant, then you. A script — or a file left in a shared project folder by an earlier session — can emit text shaped like an instruction. So the **facts in the report are produced by the trusted side**: commands run, exit codes, files changed, commit sha, and the re-run verdict. The agent's prose is quoted, capped, and labelled as a claim. If the supervisor's re-run of the verification command fails, the summary is presented as a claim rather than a finding. ## The isolation, in one page **The machine has real internet and no access to your network.** The primary control is the systemd unit, not a firewall: ```ini IPAddressDeny=127.0.0.0/8 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 169.254.0.0/16 … ``` Deny-only, with **no `IPAddressAllow`**. systemd checks Allow *first* and grants on a match, so adding `IPAddressAllow=0.0.0.0/0` to "allow the internet" disables the entire deny list. A second, independent nftables table carries the same drops. Networking is QEMU's user-mode stack, so every guest packet is re-originated by the qemu process as an ordinary socket: no L2 presence, no ARP, no DHCP spoofing, no MAC on a host interface. QEMU runs unprivileged, with an explicit device list and `/dev/kvm` as its only device. **The verification is that each layer works alone.** `tests/vm_host_asserts.sh` deletes the nftables table and re-runs the whole negative suite, which must still pass, then strips `IPAddressDeny` from a scratch unit and runs it again. Two independent proofs, neither trusting the other. ### What it does not prevent The machine can reach anything you publish to the internet, exactly as any stranger can. That is a consequence of giving it real internet access, and it is recorded rather than fixed. ## What survives a session Nothing on the machine. Every session boots a throwaway overlay over a **frozen** base image and the overlay is deleted afterwards. The project folder does survive: a per-project dataset with a quota, shared into the guest, which is also a git working tree. That is what gets committed and published. ## Where the pieces are | Path | What | |---|---| | `vm/host/` | systemd units, the nftables guard, polkit, the installer | | `vm/vmd/` | the privileged daemon: sessions, QMP, the guest channel, git, the task list | | `vm/gui/` | the maintenance app | | `vm/images/` | fetching media and building base images | | `vmagent/` | the agent loop — holds no secrets, reaches exactly one address | | `vmgw/`, `vmguard/` | its one path to Ollama, and the filter that enforces it | | `configs/agent/` | its prompts, tool definitions and parameters | Everything privileged is behind one unix socket with a fixed verb list. The agent container has no Gitea token, no Open WebUI key, and no way to unfreeze a disk.