acpmux: host load sample and a muted host notice for Cloud right-sizing
What and why
cmux NEXT Cloud machines (Linux VMs running the acpmux daemon) get right-sized automatically by the server (manaflow-ai/cmux-platform). The server takes one load sample a minute by running a command on the machine through the provider's exec. After a resize it tells the chats with one muted line. This PR is the machine side of both.
Leo, 2026-10-31: "we ofc wanted to have the thing be having snapshots of whatever vms to do the right-sizing" and "this goes on automatically right?"
A. acpmux machine load --json (also acpmux host load)
Prints one JSON object with the machine's load now and exits 0. It only reads: it never starts a daemon. If the running daemon doesn't answer within 1.5 s, busy/localTurns are left out. Every field except at is left out when it can't be read. Off Linux it prints at, plus disk on any Unix.
{
"at": 1793500000000,
"cpuBusy": 0.62,
"cpuSomeAvg60": 12.4,
"memSomeAvg60": 3.1,
"memFullAvg60": 0.2,
"memAvailableFraction": 0.18,
"swapUsedFraction": 0.4,
"oomKills": 1,
"diskFreeBytes": 21474836480,
"diskFreeFraction": 0.31,
"busy": true,
"localTurns": 2,
"heavy": { "kind": "build", "need": { "vcpu": 8, "memGiB": 15 } }
}
cpuBusy: two/proc/statcpureads 1 s apart (idle = idle + iowait).cpuSomeAvg60,memSomeAvg60,memFullAvg60: PSI avg60 from/proc/pressure/{cpu,memory}.memAvailableFraction,swapUsedFraction:/proc/meminfo. Swap is left out when SwapTotal is 0.oomKills:/proc/vmstatoom_kill(cumulative).diskFreeBytes,diskFreeFraction: statvfs of$HOME(space available to an unprivileged user, over the total).busy,localTurns:daemon statushost_pressure.local_turns.heavy: one pass over/proc/*/commandcmdline(argv[0..3]). The heaviest known build or test tool wins. The table is one declarative const (host_load.rsHEAVY) taken from cmux#26617's sizing model, and counts on top of the session's own 2 GiB / 2 cores: cargo build/clippy and rustc → build 15 GiB / 8 cores (cargo test → test); swift build/swift-frontend → 13 / 4, swift test → 11 / 6; go build/test, make, cmake, ninja, gcc/clang (and cc1/cc1plus) → 8 / 8; bun/npm/pnpm/yarn test, vitest, jest, playwright → test 2 / 6; tsc and vite build → build 2 / 6. A node or bun script counts as its tool (node …/vitest.mjs).
The new /proc parsers sit beside the existing ones in host_pressure_probe.rs and are pure functions tested on literal file text. The classifier is a pure function over (comm, argv).
B. acpmux machine notice --text "…" → muted notice in every live chat
- New RPC
_acpmux/host_notice {text}(server/host_notice.rs). It refuses Web clients, the same wayvm_hostdoes. The text is trimmed and must be one line of at most 300 characters. The handler appends the mux recordhost_notice{text, source: "cloud"}to every session on this daemon that isn't Closed, and returns{"appended": n}. host_noticeis added toTRANSCRIPT_MUX_KINDS, so clients attached to the transcript get it. It is also indocs/acpmux-schema.json(muxEventKinds,EventKinds, the record type and the method) and in the protocol IR (spec/acpmux-protocol.json, with the webmethods.tsregenerated). It is excluded from peer session forwarding.- Webview (
direct.ts): ahost_noticerecord with non-empty text becomes the row{id: "host-notice-<seq>", kind: "notice", text}, which NoticeRow draws muted. - The CLI talks to the running daemon's socket and never starts a daemon (no daemon means no chats to tell).
Tests (cmux-ci, Linux pool)
cargo test --locked -p acpmux --lib -- host_pressure host_load host_notice hub::paging protocol:: help_examples command::tests: 53 passed, 0 failed. Fleet job87a57c01da760559563011e1. This covers the new parsers, the classifier, the JSON shape, the host_notice journal and transcript test, the paging kinds, the protocol IR being current, and the help examples parsing.cargo clippy --locked -p acpmux --all-targets -- -D warnings: clean. Fleet jobdad636f773a4d2591aa2d586.bun test test/agent-pane-host-notice.test.tsx test/agent-pane-policy-unenforced.test.tsx: 5 passed. Fleet jobcd3366470d2327dfcdc850a7.
Size: about 700 changed lines, a bit over the ~600 target. Roughly half is tests, schema text and the regenerated protocol files. The two halves share the PR because Cloud's right-sizing loop needs both.
Risk: small and additive. There is a new read-only CLI and a new local-only RPC that appends one record per live chat. No Swift.
Agent session: agent session
🤖 Generated with Claude Code
![View with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v3-3x.png)
![Autofix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png)
Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
Adds the machine side of cmux Cloud's right-sizing loop: acpmux machine load --json takes one load sample a minute, and acpmux machine notice --text adds one muted line to every live chat after a resize.
Host load
machine load --jsonprints one JSON object with CPU busy share, PSI avg60, available memory, swap, OOM kills, free disk, and the heaviest running build or test; it reads/proconly and never starts a daemon.- Fields it can't read are left out;
busy/localTurnscome from a running daemon if it answers within 1.5 s. - The classifier reads comm and argv and counts
bun x,bunx, andnpxwrappers as the tool they run; a node or bun script counts as its tool.
Host notice
machine notice --textappends ahost_noticetranscript record to every non-closed session on the daemon via the new local-only_acpmux/host_noticeRPC.- The agent pane draws it as a muted notice row; Web clients and peer forwarding are refused, and the text must be one line of at most 300 characters.
- A notice that arrives mid-reply ends the current assistant or activity segment, so later lines sit below it.