Repository navigation
acpmux: agent work yields CPU and disk to the app (one QoS policy) - #27587
Conversation
Agent hosts and background-task runners re-exec in place under the utility QoS clamp with disk I/O at IOPOL_UTILITY, so the harness and every git, rg and compile it runs inherit both. The daemon keeps its CPU (the app waits on it) and takes disk at utility. Housekeeping passes run their thread in PRIO_DARWIN_BG. ACPMUX_QOS=off turns it off. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-fb75b7af9902231028aa
|
Owned by an agent session · session |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-fb75b7af9902231028aa
|
Review (codex-review-box/gpt-6-astra high, lane-place): BLOCK at 2edf480 BLOCK
Otherwise, inspection found no additional issue with bootstrap-pipe preservation, full multicall argv, signal-mask handling, or marking only the replacement environment. Returning spawn failures preserve the original process, although disk demotion remains applied. Linux implements a nice floor of 5, and the thread guard clears background mode on return/unwind before pool reuse. Static review only; macOS execution was unavailable. |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-fb75b7af9902231028aa
posix_spawnattr_set_qos_clamp_np takes POSIX_SPAWN_PROC_CLAMP_UTILITY (1), not QOS_CLASS_UTILITY: 0x11 is accepted and leaves the process at 31. Found by running the DEV build's host on a fleet mini. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-fb75b7af9902231028aa
|
cmux status (one comment, edited in place; each line is updated by the job that owns it) Review App of this head (app.zip, kept 1 day)
red: cmux-next checks (god files, concurrency, crash safety, l10n); no finished base run on 7665b87 to compare; do not merge until green; see lane-rules |
- The clamp setter takes a uint64_t; pass it as u64. - Log retention and the upload sweep stay at the daemon's priority: both hold a lock chat traffic takes (the store's writers lock, the in-use set). - ACPMUX_QOS reaches agent hosts through their environment allowlist. - A macOS test spawns a child under the clamp and reads its priority. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-fb75b7af9902231028aa
The same harness for this PR's before and after: a 120 Hz frame, uncached reads, write + fsync and an atomic state-file save at the main thread's QoS, idle, under load at the default priority, and under the agent tier. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-fb75b7af9902231028aa
|
Found 7 test failures on Blacksmith runners: Failures
|
Cites cmuxterm-hq#4947 (background class: drag lag 55 -> 6 ms) and why it is not the default: background keeps agent work off the performance cores even on an idle Mac (a CPU job 0.9 s -> 16.6 s on one thread). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-fb75b7af9902231028aa
…iles, fail on probe I/O errors Review fixes (Codex astra at 078cd45): each load worker runs in its own process group, and cleanup signals the whole group, so dd, find, cat and shasum children stop with it. The scratch directory is canonicalized, and paths go to the worker shells as arguments. The writers use a run folder the script makes, and cleanup removes only that folder. The probe exits on any failed open, read, write, fsync, close or rename instead of recording it as a latency sample. Smoke run on cmux-mac-mini (3 s, 1 round, scratch path with a space): all three cells reported, no load process was left, and no run folder remained. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-fb75b7af9902231028aa
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-fb75b7af9902231028aa
|
Review (Codex gpt-6-astra high, fast, review router): SHIP at 9798cd6
|
|
Shipped in:
|
|
Shipped in:
|
![Fix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/fix-with-codesmith-light-3x.png)
Leo (2026-10-29): "well somehow at "100% cpu" usage claude code desktop still seems fast / how does that work? how can cmux next also do that? let's do that"
and: "but i also wonder how much of this is simply putting the desktop at higher priority?" ... "the gui of the desktop app i mean"
What this does
One declared policy (
cmux-tui/crates/acpmux/src/qos.rs,POLICY) says how much each acpmux process yields to the person at the screen, applied where each process starts:IOPOL_UTILITY; CPU unchangedIOPOL_UTILITYPRIO_DARWIN_BGfor the pass, then backACPMUX_QOS=offturns it off.ACPMUX_QOS=backgroundputs agent hosts, and everything they start, in background class instead of utility.Why the daemon keeps its CPU priority. The app waits on the daemon for every streamed frame. Demoting it would put a person's own stream behind agent compiles. Its disk work still yields.
Why a re-exec. A probe on a fleet mini (cmux-mac-mini) found that thread QoS does not survive exec into another program.
posix_spawnattr_set_qos_class_npsets only the child's main thread, and its new threads come back at the default. Neither nice norPRIO_DARWIN_BGmoves the scheduler priority of default-QoS threads in a useful way. The QoS clamp does:posix_spawnattr_set_qos_clamp_np, the call behindtaskpolicy -c utility, put every thread of the child and of its grandchild (through/bin/shexec) at 20. So an agent host re-executes itself in place (POSIX_SPAWN_SETEXEC: same pid, same descriptors, before any thread starts) under the clamp. The harness and its tools inherit the clamp. The clamp setter is looked up withdlsym, so a macOS without it starts unclamped instead of failing to load. The disk I/O policy is process wide and inherited across exec, so it needs no re-exec.No spawn site changes (
agent_host/link.rsis untouched), so this does not collide with the lazy agent-host start work.Review fixes (Codex gpt-6-astra, BLOCK at 2edf480). The clamp now takes
POSIX_SPAWN_PROC_CLAMP_UTILITY(1) as auint64_t.QOS_CLASS_UTILITY(0x11) was accepted and changed nothing; a run of the DEV build's host on the mini showed it at 31. Log retention and the upload sweep stay at the daemon's priority, because both hold a lock that chat traffic takes.ACPMUX_QOSnow reaches hosts through their environment allowlist. A macOS unit test spawns a child under the clamp and checks that it, and the shell it starts, run at priority 20.Checked on a fleet mini with the DEV build (
cmux-ci build, head 86c7b63):cmux acp __agent-hostruns every thread at priority 20 withACPMUX_QOS_CLAMPED=utility, same pid. WithACPMUX_QOS=offit runs at 31.Not done here: raising the focused session. A clamp cannot be lifted from outside, so this PR does not raise the focused session's host while it streams. Inside the agent tier every session is equal, so the focused one competes only with other agent work, and the app's own threads (46-47) stay above all of it.
Performance
Harness:
scripts/measure/qos-load-bench.sh(added here). It runs probes at the app main thread's QoS (user-interactive) on cmux-mac-mini (Mac16,10, 10 cores):Data.write(options: .atomic)at the composer's draft throttle).The load is agent-like: 12 spinning threads on 10 cores, 2 dd writers, 2
findmetadata storms and 2 readers. It runs either at the default priority ("before", which is today) or undertaskpolicy -c utility -d utility("after": the CPU clamp and disk policy this PR applies to agent hosts and everything they start). Each cell is 30 s, over 6 rounds in two runs; values are the median of the rounds' values, and counts are summed.ACPMUX_QOS=background)Building on hq#4947. That issue already measured the agent-priority lever on the Chromium drag: utility class helped only partly (55 to 40 ms), and background class fixed it (55 to 6 ms, and 40% less main-thread CPU per drag). Background is not the default here because it keeps agent work off the performance cores even when the Mac is idle: 18x slower on one thread and 7x on ten. Agents would get slow all the time to win under contention. So the default is utility, which costs agents nothing idle, and
ACPMUX_QOS=backgroundgives the hq#4947 result to anyone who wants it. Raising the renderer of the page being dragged (hq#4947's next step) is app-side work for the follow-up PR.What this says, next to Leo's question about the desktop's priority:
Agent session: agent session
🤖 Generated with Claude Code