acpmux: vmHost on by default on a VM cmux created, with a per-machine toggle

Merged
TE
10d ago
+597-18

The ask

claude-sr2, word for word: "vmHost. Leo answered your [taste] question: the coordinator's pick wins (on by default for VMs cmux itself creates, with a per-machine toggle, and personal or SSH-added machines never get it). His words: "Yours is fine. people mildly like sandboxing and security but we wanna make it ergo and just like… shit just works. few permissions. worst cases something happens but you just recover and learn". Build it that way. Codex review, then land it."

This is PR 1 of 2: the daemon default, the marker check, and the toggle (RPC and CLI). PR 2 (#25778) adds the toggle to the app's machine settings, with screenshots.

What changed

  • Default from the creation marker. If config.json has no vmHost key, a machine is a dedicated agent VM only when cmux created it for one owner. That means root-owned /etc/cmux/vm-kind reads owner and /srv/team does not exist, the same rule the Cloud peer bridge's vm_admits uses. It fails closed: only a NotFound counts as no /srv/team, so an unreadable team root (root-owned, restricted on the team image) keeps it off, as team_files_present does. cmux's creation paths already write that marker: the backend machine bind (cmux-host record_owner_kind) and the devbox image's cmux-vm-kind-sync. No creation path changes here (agreed with p0-machines, who owns creation).
  • Read live. The marker is checked at each use (Hub::vm_host), not once at daemon start. A fresh owner VM reads team until its kind sync answers, then turns on with no restart.
  • Config still wins. vmHost: true|false in config.json overrides the marker. Our GCP lane VMs (leo-workspace gcp-session-host vm-host, agent_vm) keep true as before. Config::vm_host is now Option<bool>, and a missing key means "follow the default".
  • The toggle. _acpmux/vm_host {name?, on?} and acpmux host vm-host [NAME] [on|off]:
    • With no on, it only reads and returns {vmHost, cmuxVm, configured}.
    • off writes vmHost: false.
    • on removes the key, which goes back to the default. On a machine cmux did not create, on is refused with vmHost.not_cmux_vm. That covers personal machines, SSH-added machines and team VMs.
    • It applies right away to the sessions started after it, with no restart.
    • With name, the request goes to that connected peer, where it arrives as the owner's peer daemon. A peer's own request is never passed on to another peer, and a Web client is always refused (vmHost.remote).
  • Narrower than "VMs cmux creates". A team-billed VM gets team and stays off. GCP Labs VMs have no owner marker and stay off. Both are deliberate (p0-machines).
  • Trust. Turning it on needs no person proof. On a cmux VM, on is the default, and any same-uid process can already write config.json and reload (the residual is documented in hub/person.rs). No trust or security policy changes. The decision is Leo's, quoted above.
  • Docs (docs/remote-agents-and-cloud.md, the README), the schema doc and the protocol IR are updated.

Tests (red on the test-only commit 29d55e4b, green at the head)

  • New tests/vm_host.rs:
    • a VM cmux created is on by default, then off and on again live, saved to config.json
    • a machine with no marker, a team VM and a VM with team files all read off and refuse on
    • a marker that changes from team to owner turns it on with no restart
    • a fleet-written true still holds
    • a Web client is refused
    • the owner toggles a peer VM over its link
  • tests/remote_sandbox.rs: on a cmux VM, remote Claude runs, is refused once the toggle turns it off, and runs again once it is back on.
  • Unit tests:
    • hub/vm_host.rs: the marker rule, an unreadable /srv/team counting as there (red at 63ba304c as a non-root uid, from the Codex review), and the precedence between config and marker
    • cli/command.rs: the vm-host grammar
    • cli/peer_status.rs: the CLI line

Red, at 29d55e4bf9bf (tests only), cargo test -p acpmux --test vm_host --test remote_sandbox:

EXIT=101
error[E0599]: no method named `set_vm_marker_root` found for struct `Arc<Hub>` in the current scope
error[E0599]: no method named `vm_host` found for struct `Arc<Hub>` in the current scope
error: could not compile `acpmux` (test "vm_host") due to 13 previous errors

Green, at the head (cmux-ci, Linux):

FMT=0
EXIT=0   (--test vm_host: 6 passed; --test remote_sandbox: 6 passed; --test peer_status: 4 passed)
CLIPPY=0 (-p acpmux --all-targets -D warnings)
LIB: 1174 passed; 1 failed: agent_tools::tests::own_servers_run_under_the_daemon_prefix, which fails the same way on base (checked at 29d55e4b, a tests-only commit on base: EXIT=101, same panic at agent_tools_tests.rs:156)

Risk

  • On a cmux owner VM, remote Claude now runs without Seatbelt or bwrap, and the owner's peer sessions start approve-all by default. The VM is the boundary. That is the behaviour Leo asked for.
  • Personal and SSH-added machines are unchanged: no marker means off.
  • No new timer or observer: the marker is read once for each use (one small file read when a session starts).
  • No capture: CLI and daemon only. The app toggle and its screenshots are PR 2.

Stop condition: merged; then p0-machines runs the end-to-end check on a fresh Cloud VM (acpmux host vm-host reads on with no key, off and on with no restart, and on refused without the marker).

Agent session: agent session
🤖 Generated with Claude Code