Skip to content

acpmux: agent work yields CPU and disk to the app (one QoS policy) - #27587

Merged
teamleaderleo merged 10 commits into
feat-cmux-nextfrom
leo/next-qos-tiers
Oct 30, 2026
Merged

teamleaderleo merged 10 commits into
feat-cmux-nextfrom
leo/next-qos-tiers

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

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:

Tier Who macOS Linux
Serving the daemon disk I/O at IOPOL_UTILITY; CPU unchanged unchanged
Agent agent hosts, background-task runners, and everything they start (harness, shells, git, rg, compiles) CPU clamped to utility (priority 20) and disk I/O at IOPOL_UTILITY nice +5
Background housekeeping passes in the daemon (log retention, the upload sweep, probe cleanup) that thread in PRIO_DARWIN_BG for the pass, then back unchanged

ACPMUX_QOS=off turns it off. ACPMUX_QOS=background puts 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_np sets only the child's main thread, and its new threads come back at the default. Neither nice nor PRIO_DARWIN_BG moves the scheduler priority of default-QoS threads in a useful way. The QoS clamp does: posix_spawnattr_set_qos_clamp_np, the call behind taskpolicy -c utility, put every thread of the child and of its grandchild (through /bin/sh exec) 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 with dlsym, 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.rs is 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 a uint64_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_QOS now 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-host runs every thread at priority 20 with ACPMUX_QOS_CLAMPED=utility, same pid. With ACPMUX_QOS=off it 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):

  • a 120 Hz frame (wake, then 2 ms of work);
  • a 4 KiB uncached read every 50 ms;
  • a 4 KiB write + fsync every 50 ms;
  • a 64 KiB temp-file-and-rename save every 300 ms (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 find metadata storms and 2 readers. It runs either at the default priority ("before", which is today) or under taskpolicy -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.

metric before after delta harness or run link
frame wake late p99, under load (idle 2.9 ms) 3.0 ms 3.0 ms 0 scripts/measure/qos-load-bench.sh
frames late > 16.7 ms, under load 0 / 21600 0 / 21600 0 same
uncached 4 KiB read p99, under load (idle 4.4 ms) 86 ms 21 ms -76% same
reads > 16.7 ms, under load 69 / 3600 36 / 3600 -48% same
write + fsync p50, under load (idle 0.12 ms; after is bimodal: 0.2-0.4 ms or 16-36 ms) 33 ms 8 ms -76% same
write + fsync > 16.7 ms, under load 2419 / 3600 1739 / 3600 -28% same
atomic 64 KiB save p95, under load (idle 1.0 ms) 1.01 s 1.19 s none (noise) same
atomic saves > 16.7 ms, under load 132 / 300 128 / 300 none same
Chromium pane drag lag p95, heavy page, 14 busy processes (default -> utility) 55.2 ms 40.2 ms -27% manaflow-ai/cmuxterm-hq#4947, cmux20-bench, steps 33cca097, 8da23621
same, load at background class (ACPMUX_QOS=background) 55.2 ms 6.0 ms -89% hq#4947, step 6e012869
agent CPU job, idle Mac, 1 thread (default / utility / background) 0.90 s 0.78 s / 16.6 s utility -13%, background +1750% cmux-mac-mini, 4P+6E, fixed 3e9-iteration loop
agent CPU job, idle Mac, 10 threads (default / utility / background) 3.96 s 3.59 s / 28.5 s utility -9%, background +620% same

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=background gives 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:

  • CPU priority is not what makes the app slow. A user-interactive thread loses nothing at 100% CPU, with or without this PR. The cmux app already runs at 46 like Claude Desktop's 47-54, so raising the GUI further gains nothing.
  • Disk is what hurts, and this PR gives part of it back: slow uncached reads halve, and slow fsyncs drop by about a quarter.
  • A buffered save (temp file + rename) still takes about 1 s at p95 under agent disk load at any tier. So any save on the main thread is a hitch. The app-side PR (next) moves the composer's draft save and the transcript's scroll-place save off the main thread.

Agent session: agent session

🤖 Generated with Claude Code

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
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

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
@github-actions github-actions Bot added the risk:normal Risk tier (docs/ci/risk-tiers.md) label Oct 29, 2026
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex-review-box/gpt-6-astra high, lane-place): BLOCK at 2edf480

BLOCK

  1. [P1] The macOS clamp uses the wrong API value and signature. qos.rs:192 passes QOS_CLASS_UTILITY (0x11). posix_spawnattr_set_qos_clamp_np expects a uint64_t containing POSIX_SPAWN_PROC_CLAMP_UTILITY (1), not a pthread QoS class. The setter rejects this with EINVAL, so ready is false and neither helper re-execs or receives the CPU clamp. Correct both the constant and ClampFn signature; test effective scheduling through a real macOS helper launch.

  2. [P1] Background housekeeping holds locks required by interactive work across disk I/O. retention.rs:132 backgrounds the entire pass, including commit_compaction, which holds the store-wide writers mutex across a synced marker write and segment renames (store/compact.rs:211). Every chat append takes that mutex, and events are broadcast only after append completes (hub/mod.rs:1246). Under disk contention, background throttling can therefore stall all chat streams. file_uploads.rs:149 similarly backgrounds deletion while holding global IN_USE, blocking attachment prompts and upload status. Restore serving priority before these shared critical sections; retaining daemon CPU priority alone does not protect UI progress.

  3. [P2] The advertised opt-out never reaches agent hosts. entry.rs:53 checks QoS inside the host, but agent_host/link.rs:49 clears its environment and rebuilds it through host_environment(), whose allowlist excludes ACPMUX_QOS. Starting the daemon with ACPMUX_QOS=off still demotes newly launched hosts, including Linux nice handling. Explicitly forward the opt-out to hosts.

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.

@teamleaderleo teamleaderleo removed the risk:normal Risk tier (docs/ci/risk-tiers.md) label Oct 29, 2026
@github-actions github-actions Bot added the risk:normal Risk tier (docs/ci/risk-tiers.md) label Oct 29, 2026
teamleaderleo and others added 2 commits October 29, 2026 14:19
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
@github-actions

github-actions Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

cmux status (one comment, edited in place; each line is updated by the job that owns it)

Review 81730da: HQ feed card · gallery before/after runs · checks

App of this head (app.zip, kept 1 day)

gh workflow run reload-build.yml -R manaflow-ai/cmux --ref feat-cmux-next -f tag=pr27587 -f ref=leo/next-qos-tiers -f platform=macos -f backend_mode=local

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

teamleaderleo and others added 2 commits October 29, 2026 14:39
- 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
@blacksmith-sh

blacksmith-sh Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Found 7 test failures on Blacksmith runners:

Failures

Test View Logs
--from must install a complete prebuilt tree/
--from must install a complete prebuilt tree
View Logs
/tmp/tmpbr4z48_r/summary-fail.json (live):/summary-fail.json (live): View Logs
/tmp/tmpxgw3kkqe/summary-missing.json (live):/summary-missing.json (live): View Logs
[new_workspaces] debug.new_workspace timed 0 of 50 new workspace rows/
[new_workspaces] debug.new_workspace timed 0 of 50 new workspace rows
View Logs
[new_workspaces] the window draws no frames/[new_workspaces] the window draws no frames View Logs
[tabs] debug.tab_switch timed 0 of 40 switches/
[tabs] debug.tab_switch timed 0 of 40 switches
View Logs
[tabs] the window draws no frames/[tabs] the window draws no frames View Logs

Fix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

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
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (Codex gpt-6-astra high, fast, review router): SHIP at 9798cd6

  • Review at 078cd45: the production QoS code was found fixed ("Re-exec preserves bootstrap stdio and complete multicall argv; the marker prevents recursion and is excluded from the host allowlist"). It returned BLOCK only on the measurement script: workers' children survived cleanup, relative scratch paths broke, probe I/O errors were ignored, and cleanup removed w* files it did not create.
  • e5dc5ce and 9798cd6 fix those. Each load worker runs in its own process group and cleanup kills the whole group. The writers use a run folder the script makes. The probe exits on any failed I/O call. An empty array no longer counts as unbound under bash 3.2. The delta review returned SHIP with no findings.
  • 81730da is a clean merge of the current base. Nothing was resolved by hand, and the PR's own diff did not change.
  • Fleet evidence:
    • cargo test -p acpmux --lib qos:: on cmux-mac-mini at 078cd45 passed all 3 tests, including a_clamped_child_and_what_it_starts_run_at_utility.
    • The bench smoke run at e5dc5ce, with a scratch path containing a space, reported every cell and left no load process behind.
  • Reds at 9798cd6 were in files this PR does not touch: SSHMachineSecrets namespace lint, base-autofix-push.test.sh, swift-test-summary.test.sh, the sidebar trailer check and release-notary. They came from the branch's old base (6fbb57e). The base's own cmux-next checks is green at 6491653, and 81730da merges that base in.

@teamleaderleo
teamleaderleo marked this pull request as ready for review October 30, 2026 04:32
@teamleaderleo
teamleaderleo merged commit 60a7216 into feat-cmux-next Oct 30, 2026
166 of 178 checks passed
@teamleaderleo
teamleaderleo deleted the leo/next-qos-tiers branch October 30, 2026 04:59
@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

This branch was previously deployed

3 inactive deployments
artifacts — 81730daa Deployed Oct 30, 2026 by teamleaderleo via add the Windows daemon to the tree #80572
sentry-dsyms — 81730daa Deployed Oct 30, 2026 by teamleaderleo via upload cmux-tui dSYMs to Sentry #80572
ci-routing-agent — 81730daa Deployed Oct 30, 2026 by teamleaderleo via cmux-tui tree preflight #80572
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:normal Risk tier (docs/ci/risk-tiers.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant