Repository navigation
acpmux: a harness starts at normal CPU class and runs at background once its start is over; a late start says why - #28923
Conversation
|
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)
|
…nknown" apart from "ended", test the hosted path through a real daemon (review of #28923) - Right after every harness spawn (direct, hosted, pooled) the daemon clears a background class another process set (cpu_class::clear_running): the pre_exec self-clear cannot clear an external one. The retry stays for a class set again during the start; it no longer needs its own clear. - HarnessSeen keeps "not started", "ended", "unread" (not macOS, or the kernel refused) and "running" apart: off macOS a deadline error no longer says the agent process is not running. - A retried timeout writes only agent_start_retried_at_normal, not also agent_start_timed_out. - Chief's session/new waits 200 s: two acpmux start budgets. - Docs: a launcher harness's real agent and the utility disk tier are named. - Tests: the retry test fails the first start with an error (no short budget, covers the error path); a real daemon test (agent hosts on) checks that a hosted chat harness runs at background once started; a runner at background class fails with a clear message.
|
Review (independent review subagent, Claude Opus 5.5, correctness first, read-only): FIX at 6a0ec27. No blocking finding, 7 should-fix. The reviewer probed the macOS facts on Darwin 27 with
Resolution, fixed at c972b11:
NITs:
Follow-ups (not in this PR): put the real agent of a launcher harness in background class without a process scan (for example, the agent host lowers its own process group's members as they report through the holder track); give a readopted host's harness its run class. |
|
Found 1 test failure on Blacksmith runners: Failure
|
… the host the hosted test started (gate of #28923) The gate's full run read a background-class agent host at priority 46 once: the kernel raised it for a moment. A class check now takes the lowest of 5 reads (the kernel never lowers a thread below its class). The hosted test ends its host before its checks, so a failed check leaves no __agent-host behind. The real-daemon test creates its HOME. Review NITs 1-2 of #28923.
6f4728d to
e83e364
Compare
…nknown" apart from "ended", test the hosted path through a real daemon (review of #28923) - Right after every harness spawn (direct, hosted, pooled) the daemon clears a background class another process set (cpu_class::clear_running): the pre_exec self-clear cannot clear an external one. The retry stays for a class set again during the start; it no longer needs its own clear. - HarnessSeen keeps "not started", "ended", "unread" (not macOS, or the kernel refused) and "running" apart: off macOS a deadline error no longer says the agent process is not running. - A retried timeout writes only agent_start_retried_at_normal, not also agent_start_timed_out. - Chief's session/new waits 200 s: two acpmux start budgets. - Docs: a launcher harness's real agent and the utility disk tier are named. - Tests: the retry test fails the first start with an error (no short budget, covers the error path); a real daemon test (agent hosts on) checks that a hosted chat harness runs at background once started; a runner at background class fails with a clear message.
… the host the hosted test started (gate of #28923) The gate's full run read a background-class agent host at priority 46 once: the kernel raised it for a moment. A class check now takes the lowest of 5 reads (the kernel never lowers a thread below its class). The hosted test ends its host before its checks, so a failed check leaves no __agent-host behind. The real-daemon test creates its HOME. Review NITs 1-2 of #28923.
e83e364 to
b4996ac
Compare
…nknown" apart from "ended", test the hosted path through a real daemon (review of #28923) - Right after every harness spawn (direct, hosted, pooled) the daemon clears a background class another process set (cpu_class::clear_running): the pre_exec self-clear cannot clear an external one. The retry stays for a class set again during the start; it no longer needs its own clear. - HarnessSeen keeps "not started", "ended", "unread" (not macOS, or the kernel refused) and "running" apart: off macOS a deadline error no longer says the agent process is not running. - A retried timeout writes only agent_start_retried_at_normal, not also agent_start_timed_out. - Chief's session/new waits 200 s: two acpmux start budgets. - Docs: a launcher harness's real agent and the utility disk tier are named. - Tests: the retry test fails the first start with an error (no short budget, covers the error path); a real daemon test (agent hosts on) checks that a hosted chat harness runs at background once started; a runner at background class fails with a clear message.
… the host the hosted test started (gate of #28923) The gate's full run read a background-class agent host at priority 46 once: the kernel raised it for a moment. A class check now takes the lowest of 5 reads (the kernel never lowers a thread below its class). The hosted test ends its host before its checks, so a failed check leaves no __agent-host behind. The real-daemon test creates its HOME. Review NITs 1-2 of #28923.
…names its harness and a background one runs once more (red) Behavior tests through an in-process daemon's ACP client (macOS): the harness answers session/new above background class and runs at it once started; a tool its turn starts inherits it; an agent_start deadline error names the agent process; a start held at background class by another process is retried once. Red on the base: #27600 starts the harness at PRIO_DARWIN_BG before its exec.
… its session's start is over #27600 started every harness at PRIO_DARWIN_BG. On 2026-10-30 (load 125 on 18 cores, du and git scans of a 3.3 TB disk) a claude harness at background class spent 90 s in trust-settings reads with under 1 s of CPU, every Chief turn failed ("session/new: no answer within 60 s", "no harness session within 90s"). - The harness starts at its starter's class and clears an inherited background class at its exec (start_at_normal). Once ensure_child's start is over, the daemon puts the running harness in its class with setpriority(PRIO_DARWIN_PROCESS, pid, PRIO_DARWIN_BG); later tools inherit it. The run class rides on ChildAgent; SpawnSpec.cpu_class is gone. - A cold start that fails or misses agent_start with its harness at background class anyway runs once more, its harness's external class also cleared, and logs agent_start_retried_at_normal. - agent_start and the pool's start deadline errors name the agent process: pid, class, priority, run time, CPU time, threads running (proc_pidinfo of the one pid; no process listing). data.harness carries the same fields. - The hosted behavior test: under a background agent host (ACPMUX_QOS=background) the harness starts above it and runs below once lowered.
…adline, so its reason reaches Chief Chief gave up on session/new after 60 s, before acpmux's own agent_start deadline (90 s), so a failed start showed only "session/new: no answer within 60 s" (2026-10-30). Chief now waits 100 s: acpmux answers first, with what the agent process was doing. Other control requests keep 60 s.
…nknown" apart from "ended", test the hosted path through a real daemon (review of #28923) - Right after every harness spawn (direct, hosted, pooled) the daemon clears a background class another process set (cpu_class::clear_running): the pre_exec self-clear cannot clear an external one. The retry stays for a class set again during the start; it no longer needs its own clear. - HarnessSeen keeps "not started", "ended", "unread" (not macOS, or the kernel refused) and "running" apart: off macOS a deadline error no longer says the agent process is not running. - A retried timeout writes only agent_start_retried_at_normal, not also agent_start_timed_out. - Chief's session/new waits 200 s: two acpmux start budgets. - Docs: a launcher harness's real agent and the utility disk tier are named. - Tests: the retry test fails the first start with an error (no short budget, covers the error path); a real daemon test (agent hosts on) checks that a hosted chat harness runs at background once started; a runner at background class fails with a clear message.
… the host the hosted test started (gate of #28923) The gate's full run read a background-class agent host at priority 46 once: the kernel raised it for a moment. A class check now takes the lowest of 5 reads (the kernel never lowers a thread below its class). The hosted test ends its host before its checks, so a failed check leaves no __agent-host behind. The real-daemon test creates its HOME. Review NITs 1-2 of #28923.
b4996ac to
458e0d2
Compare
…ration's codex btw test no longer overflows the stack The CPU-class retry loop (#28923) kept the whole cold_start future inline beside its own state, on the stack the caller polls it on. The codex btw side-session test, which nests starts, aborted with a stack overflow there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-644bb2d80e903edfe5af
…ration's codex btw test no longer overflows the stack (#29492) The CPU-class retry loop (#28923) kept the whole cold_start future inline beside its own state, on the stack the caller polls it on. The codex btw side-session test, which nests starts, aborted with a stack overflow there. Agent-Session: https://cmux.com/deeplink/agent?id=aid-644bb2d80e903edfe5af Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Base red on feat 30806df (hq run 41212547003): two in-process hub tests abort with "has overflowed its stack": fork_through::btw::a_side_session_opens_beside_a_running_claude_chat_and_never_disturbs_it and fork_through::btw::a_codex_side_session_runs_sandboxed_read_only_and_the_adapter_is_refused. Culprit: d3f317b (the merge that brought in #28923's CPU-class start, debfed3 and 73de34e). It made `reach_or_start`'s cold-start future bigger (a retry loop around `timeout(cold_start)`, `starting_harness`, `enter_run_class`). Bisect: btw passes at its first parent bfa1eb0 (job 1181abd8fbd3f9273eb40775) and at 002bbd3 (485a446dfb0fe2712bd44832), and aborts at d3f317b (29c57fcf1606333d55566cc2). No recursion. The merge adds none (its new awaits are listed in the PR), and the stack the path needs is fixed, near 2 MiB (fleet probe, one stack size per test process): the Claude test needed <= 2048 KiB before the merge and 2049-2304 KiB after; the Codex test needed 2049-2304 KiB before and after (jobs 9e0bf58944fa00ab78483395, cedb1d7882a32555e3edd72a). On a 32 MiB thread both pass (job 6d0c1114b936cdd2022d6b5c). The daemon never polls these futures on 2 MiB: its runtime workers have WORKER_STACK_BYTES, 8 MiB (`cli/entry.rs`, set for this reason after an adoption abort). A test that runs a hub in process polled them on libtest's 2 MiB thread, a stack the daemon never has. The hermetic test environment (`tests/support/daemon_env.rs` `enter`) now sets RUST_MIN_STACK to that same constant, so libtest's test threads and tokio's runtime threads in a test process get the daemon's stack. A test now fails exactly where the daemon would, past 8 MiB. A host's own RUST_MIN_STACK still wins; `acpmux` processes a test starts get the hermetic `vars`, which do not carry it, and keep their own worker stack.
![Fix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/fix-with-codesmith-light-3x.png)
Summary
Fixes the Chief outage on Lawrence's laptop, 2026-10-30 20:40 PDT (NIGHTLY acpmux a112726). Every Chief turn failed with
session/new: no answer within 60 s, and the daemon log saidpooled session failed to start: no harness session within 90s harness=claude. #27600 starts every harness atPRIO_DARWIN_BG. The machine had load 125 on 18 cores anddu/gitscans on a 3.3 TB disk. Theclaudeharness was already past its exec, but its main thread spent 90 s inSecTrustSettingsCopyTrustSettings->open()/ plist parse at disk tier 3, with under 1 s of CPU. Evidence: #28442 (comment).#28442 (open, P-AUSTIN B10) moves the background class from darwinbg before the exec to the posix_spawn QoS clamp. That fixes a slow exec, but the harness still runs at background I/O after the exec, so this hang stays. A clamp also cannot be removed from a running process. This PR changes the order instead: the harness starts at normal class, and the daemon lowers it after the start.
cpu_class::start_at_normal, for example an agent host underACPMUX_QOS=background). Whenensure_child's start is over (initializeandsession/neworsession/loadanswered, including a pool claim and Claude's fork priming), the daemon lowers the running harness withsetpriority(PRIO_DARWIN_PROCESS, pid, PRIO_DARWIN_BG)(ChildAgent::enter_run_class). Every process that the harness starts after that inherits the class, so the builds and tests of its turns stay below the GUI (acpmux: start agent harnesses at background CPU class on macOS, so cmux and its renderers go first #27600's goal). A pooled harness stays at normal class until a chat claims it. Model probes are never lowered (they never run a turn). The "a running process is never reniced" rule is changed on purpose: now the harness itself is reniced, once. A process that the harness started during its start (an MCP server) keeps its class, because acpmux does not list processes (check-no-process-scan.py). The class decision moved fromSpawnSpec.cpu_classto theChildAgent. The field is removed: serde ignores it from an older daemon, and a host is always the daemon's own binary.agent_startand its harness is at background class anyway (from a cause outside acpmux), the start runs one more time. The daemon also clears the second harness's external background flag right after the spawn, and logsagent_start_retried_at_normal(with the harness state) plus a warning.agent_startand pooled-start deadline errors end with, for example,; its agent process (pid 123) is alive at background CPU class (priority 4), ran 92 s, used 0.8 s of CPU, 0 of 14 threads running, oris not running/had not started. The text comes fromproc_pidinfoof that one pid, with no process listing.data.harnesscarries the same fields. The text names no vendor.session/newwaited 60 s, which is less than acpmux's 90 sagent_start, so Chief never saw acpmux's reason. It now waits 100 s (SESSION_NEW_DEADLINE). Other control requests keep 60 s.macOS facts used:
setpriority(PRIO_DARWIN_PROCESS, pid, …)on another process of the same uid sets that task's external darwinbg flag (xnudo_background_proc:curp == targetpgives internal, otherwise external). A self call inpre_execclears the internal flag. Darwinbg is task policy, so it survives exec and children forked later inherit it. Children that already exist are not changed. The behavior tests below check these on a fleet Mac.Docs:
cpu_class.rsmodule doc,docs/background-processes.md("Start (macOS)"),config.rscpuClass.Owner: PL-acpmux (
cpu_class.rs,agent.rs,agent_host); Chief (optchat-chiefrpc/acpmux). Does not touchhub/idle_census.rs.Testing
Behavior tests at the daemon's ACP client (an in-process daemon,
serve_connection) and the agent host link, macOS (tests/cpu_class.rs, stand-intests/fake_agent.py):cpu_class_a_session_starts_at_normal_class_and_runs_at_background_once_started: the harness answerssession/newabove pri 4. After the answer it is at pri 4, and a tool that its turn starts is at pri 4.cpu_class_a_profile_can_keep_its_harness_at_normal_class,cpu_class_a_profile_reference_that_resolves_to_normal_keeps_normal_class: the opt-out still works.cpu_class_a_start_that_misses_its_deadline_says_what_its_harness_was_doing: a harness that cannot finish its start (FAKE_START_GATE) gives anagent_starterror that names its pid, class, run time, CPU and threads. A normal-class start is not retried.cpu_class_a_start_that_fails_at_background_class_runs_once_more: another process puts the first harness in background class during its start, and the harness failsinitializebut stays alive. The start is retried once, logged withbackground: true, timedOut: false, and the session starts at normal class.cpu_class_a_daemon_runs_a_hosted_harness_at_background_once_its_start_is_over: a realacpmux daemon runwith agent hosts on (as in the app), driven byacpmux run "class-probe: …". The chat harness answerssession/newabove pri 4, then runs at pri 4, and a tool from its turn is at pri 4.cpu_class_a_hosted_harness_starts_at_normal_under_a_background_host_then_runs_below: the agent host runs in background class (ACPMUX_QOS=background, pri 4). Its harness and a tool started during the start run above pri 4. Afterenter_run_classthe harness and a tool its turn starts are at pri 4.The test of a harness under load is the mechanism, not a timing: a start that has a deadline does not run at background class.
Focused command:
cmux-ci run --class isolated --label macos-27 --script scripts/ci/cmux-tui-rust-check.sh --arg test --arg cpu_classResults (fleet Macs,
cmux-ciisolated, label macos-27; Linux on rbx):898fcb69on aws-m4pro-4, 3 failed, 4 passed.…runs_at_background_once_startedfailed with "the harness answered session/new at background class". The deadline test failed with the bareagent_start did not finish within 3s. The retry test failed with "the retried start failed: agent_start did not finish within 5s".d46d625f, 8 of 8 passed. macOS clippy step0d8cc078: pass.656de634, cmux10's Mac mini): 9 tests ran. All passed except the in-process hosted test: it read the background host at pri 46 once (a short kernel priority promotion), and it passed alone (FLAKY). Fixed in e83e364: a class read is now the lowest of 5 reads, and the test ends its host before its checks. macOS clippy at c972b11 (gate step8b3181ce): pass. rbx at c972b11:cargo clippy -p acpmux --all-targets -D warnings, plushub_integration agent_start,session_poolandagent_hosttests: pass./tmp/gates/e83e364b…v6.json, all 8 steps green):cpu_class, cmux-ci f68b6198 on aws-m4pro-3: 9 of 9 passed, including the real-daemon hosted test.never_answers_its_start6668d34f: pass.hub_integration agent_start,session_pool(34 of 34),agent_host: pass.sessionfilter) 71f8354c, clippy and fmt: pass.check-no-process-scan: ok.after_a_restart: cx-q0b1zh.close_1012: cx-85z095.acpmux_peer_moved: cx-2hwias.eval_upsell: cx-2ct95t.toolpack_mcp: cx-71g32y.orphan_exit::a_daemon_connecting_while_the_harness_ends_is_told_it_exited: cx-o8vggt. It fails 17 of 20 runs alone at the base and 15 of 20 at the head./tmp/gates/458e0d21….json): all 8 steps green.cpu_class9 of 9, macOS clippy, macOS deadline test.Observed / Not run
cpu_classandstartfilters, clippy, Chief's own tests and the Chief first-turn cost guard ran.Changelog
Fixed: On a busy Mac, starting an agent chat no longer waits behind other disk work, and a start that times out now says what the agent process was doing.
Proof
No capture: the change is in the acpmux daemon and Chief, and has no UI.