Files
owui-tools/docs/vm-agent/overview.md
T
2026-09-15 15:24:03 -04:00

3.9 KiB

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:

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.