Files
2026-09-15 15:24:03 -04:00

86 lines
3.9 KiB
Markdown

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