86 lines
3.9 KiB
Markdown
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.
|