Warn on runaway agent sessions: sidebar badge and acpmux ls WARN column
Leo, 2026-10-30: "that visibility into [one agent host's] guys was (and is?) pretty bad i feel like we can make more improvements to acpmux/cmux-next"
Leo, on acpmux ls: "and acpmux ls should probably not be overly... verbose? i guess" / "to keep everything efficient... tho idk"
Part of HQ#1920, HQ#5226 and the HQ#5123 telemetry lane (row d: "Show unknown, never a synthesized zero. A metric must trigger an action."). Related: HQ#5266 (a revived session keeps its autocompact cap); this PR never warns on such a session's unknown cap.
A usage audit found four long-running Claude sessions with no autocompact cap carrying 290-330k of context per call and starting 67-90 Opus subagents. Nothing in acpmux or cmux NEXT showed it; finding it took a hand audit of ps lines and transcripts. This PR is the visible warning: a person sees a mark, not a log line.
What changes
-
acpmux decides the warnings, once. A session summary gets
usageWarnings: [{rule, value, limit, text}], computed when the summary is read from itsusageStats(the fields from the companion acpmux usage-stats PR) and the thresholds:noAutocompactCap: a Claude session (familyclaude) whoseusageStats.autocompactisnone.contextTokens:usageStats.contextTokensis above the limit.subagentsPerHour:usageStats.subagents1his above the limit.
The warning list is empty for a closed session or one with no
usageStatsyet, so until the usage-stats PR lands nothing shows. -
Unknown stays unknown. A count acpmux could not read is null, never 0. An
autocompactofunknown(an adopted or revived chat whose launch acpmux never saw, or a setting it could not read) never raises or clears a warning. A value it lacks shows as?, never 0. -
Each warning names the action that answers it (
action: {label, command, available}). The tooltip line ends with it, and nothing runs automatically:rule action command no autocompact cap Rotate onto a capped profile lane-place rotate NAME(coming:session restartkeeps the launch settings, so it adds no cap)context above 400k Compact it acpmux send NAME /compact(Claude Code's own command; the Claude adapter already runs it as a prompt, seecompact_ends_its_prompt_and_reports_the_compaction)subagents above 40/h Open the session to see what it starts; cancel its turn to stop it acpmux session cancel NAME -
One declared place for the thresholds: the new
usageWarningssection of acpmuxconfig.json,{"contextTokens": 400000, "subagentsPerHour": 40, "autocompactCap": true}. Those are the defaults (config/usage_warnings.rs), and 0 or false turns a rule off. Documented in the acpmux README anddocs/acpmux-schema.json. -
acpmux lsstays compact. Defaultlsgets a WARN column holding the single wordwarnper flagged row, shown only while some row has one; with no warnings the output is unchanged. The reasons and commands (ctx 412k: acpmux send NAME /compact; 90 sub/h: acpmux session cancel NAME) go to--wide, which the companion usage-stats PR adds.--jsonrows carry the wholeusageWarnings. Computing the warning reads only the already-cachedusageStatson the summary, and no transcript is read for it. -
The cmux NEXT sidebar row gets a small warning triangle (
exclamationmark.triangle.fill, theme attention color) in the trailing cluster beside the muted bell. It is its own view, not the working-state spinner. Its tooltip lists each warning'stext, e.g. "90 subagents started in the last hour, above 40", and the row's accessibility label includes them. It is on for everyone: it does not depend on thesidebar.laneStatus/rowStatus/nowLinetoggles. The Debugsidebar.laneFixtureworking sample now carries warnings, so captures show the badge.
How N was chosen (subagents per hour)
The rate is the peak count of Agent/Task tool calls in any sliding 60-minute window, measured per main transcript across every Claude transcript on this agent host (63 sessions, the last ~3 days):
| peak subagents in any 60 min | sessions |
|---|---|
| 0 | 47 |
| 1-4 | 13 |
| 5-9 | 2 |
| 10-30 | 1 (the area coordinator, 27, which is intended fan-out) |
| > 30 | 0 |
| the runaway deputies (audit) | 67-90 |
N = 40 sits above every ordinary session, with headroom over the busiest coordinator (27), and well below the runaway deputies. It is a config value, so it can move without a release.
The context limit of 400k is about where sr2's managed --autocompact window sits (380k-420k), so a session that compacts normally never reaches it. Five of the 63 sessions here peaked above 400k; all five are long-lived coordinators with no cap, which is the case this warning is for.
Tests
- Rust
hub/usage_warnings_tests.rscovers:- a runaway session breaks all three rules, with its numbers and text;
- a capped ordinary lane breaks none;
- the limits are exclusive;
- only Claude is held to a cap;
- a closed session, or one with no stats, gets no warnings;
- a session with no cap but no context read yet still warns;
- config overrides and 0/false turn rules off, and an all-default config writes nothing.
- Rust
cli/ls_table_tests.rs:warn_is_one_word_and_shows_only_while_a_row_has_one. The defaultlsshows onewarnword;--wideshows each rule's short form and its commands. - Swift
SidebarUsageWarningTestscovers:- the lane parses
usageWarnings; - the row carries its chats' warnings with
laneStatuson and off; - duplicate lines show once;
- the badge shows, with the tooltip and accessibility text;
- a hovered row keeps the badge, at full opacity and left of its ⋯/× controls (
theBadgeStaysBesideTheHoverControls). This test was committed red first, in 527848c8182, and the fix is 84e7b01d1d1. The first fleet capture showed the hover controls covering it. - a row with no warnings shows no badge.
- the lane parses
- Gallery
AAUsageWarningGalleryTests(CMUX_TEST_REQUIRE_GUI=1, artifactusage-warning-gallery.tar): the real sidebar list drawn light and dark, before (no warnings) and after (the working chat breaks all three rules). Each still is also printed base64 to the log. - The sidebar owner's note (
plans/cmux-next/sidebar-owner.md) logs the look change, and the commit carries aSidebar-Look:trailer.
Observed / Not run
- Observed:
- The subagent-rate distribution above, from this host's transcripts.
- Rust at 46be2bf (the acpmux code has not changed since):
cargo clippy -p acpmux --all-targets --locked -- -D warningsis clean, andcargo test -p acpmux --lib -- usage_warn ls_table protocol_ir_is_current catalog_reload a_cell_without remote_summary peer_cachepasses 32/32. - Independent Codex reviews (gpt-6-astra, high):
- BLOCK at 70d34d29: reload dropped the section. Fixed.
- BLOCK at 8cb0b7a: a peer row's commands lacked the peer prefix. Fixed.
- SHIP at 413c6f8, then SHIP at 46be2bfb59be808978d7244318972690c20568c7.
- SHIP at 891176a87966c39b1fb422902b2fafab89336eff (gpt-6.1-sol, high), after the hover fix and the Debug hover hook fix, with no findings.
scripts/cmux-next/prepush-gate.pypasses at the head.- Reds that fail the same way on base, per test (PR comments): the Rust panic ratchet (
indexing_slicing 700 > 693,string_slice 98 > 94, the same at merge base e863077) and cmux-next checks. The latter's one counted red is String tables, withcheck-l10n.shoutput the same at e863077; its sidebar-trailer annotation names three base commits, and this PR's range has 0. - The defaults guard's five "defaults changed" entries are fingerprints of functions that read Debug tunables. No default value changes, and the review receipt cleared it.
- Not run: the Swift suites (
SidebarUsageWarningTests, the galleryAAUsageWarningGalleryTests). CI'scmux-next swift testjob waits on a fast-tier runner label that has started nothing, and this lane VM cannot build Swift.
Captures
Fleet captures from a build of the head, driven on a capture mini, checked by eye. The stills will be linked here once pr-media uploads work again.
- (A) Rows, light, job 77c9fa09, head 84e7b01d against base e8630779. It ends on the frame with three workspaces and the Debug lane fixture on:
- head: perf-sweep shows the red failed mark, flaky-ci the orange needs-you dot, and fleet-coordinator its working spinner with the warning triangle just left of it;
- base: the same marks and no triangle.
- (B) Tooltip on hover, dark, head 891176a8. It matches.
- The row is hovered with ⋯/∨/× showing; the triangle stays at full opacity just left of ⋯, the pointer rests on it (⋯ is not highlighted), and no row card opens.
- The tooltip lists all three lines with their commands.
- In the still, the first 1-4 characters of each line are cut off at the left. That is only the capture cropping to the app window. The themed tooltip is its own borderless window (
TooltipPanel), centered under the badge and clamped to the screen's visible frame (TooltipPresenter.origin, covered byThemedTooltipTests). So a person sees it whole, and the crop is the roughly 700 pt wide tooltip running past the window's left edge. - An earlier run of B, job 8adf1a57 at 84e7b01d, showed no tooltip, because the Debug hover hook aimed at the badge's resting place, under ⋯. Fixed in 891176a8796.
- Both runs were checked by eye by the capture lane. The stills are kept on the capture host (A:
~/.cache/pr-capture/84e7b01d/steps-ac9b6932cf/, B:~/.cache/pr-capture/891176a8/steps-87a9bbecf9/) until pr-media uploads work.
No capture: the stills above exist (A at 84e7b01d, B at 891176a8, both matched by eye), but pr-media uploads are blocked for now; the image links will replace this line.
Performance
_acpmux/sessions latency (what acpmux ls calls), from the companion usage-stats PR's bench ls_latency: 30 fake-Claude chats that each ran a turn, 50 timed calls per mode, each on a new connection, on one 32-vCPU CI runner. Before is the companion branch b9c165d; after is the same tree plus this PR's daemon and config changes, so usageStats is present and every row's warnings are computed. Both were built once and run alternately (before, after, before, after); each figure is the mean of the two rounds. Command: cargo test -p acpmux --test hub_integration ls_latency -- --ignored --nocapture.
| metric | before | after | delta | harness or run link |
|---|---|---|---|---|
acpmux ls (no usage read) p50 | 21.8 ms | 21.6 ms | -0.2 ms (-1%) | run 41012923271 |
acpmux ls (no usage read) p95 | 28.0 ms | 27.9 ms | -0.1 ms (-0%) | run 41012923271 |
acpmux ls --wide / --json (usage read) p50 | 21.1 ms | 21.3 ms | +0.1 ms (+1%) | run 41012923271 |
acpmux ls --wide / --json (usage read) p95 | 26.6 ms | 27.2 ms | +0.6 ms (+2%) | run 41012923271 |
The deltas are within the spread between the two "before" rounds (p50 21.7 vs 22.0 ms; p95 25.9 vs 27.2 ms). Computing the warnings reads only the usageStats already on the summary: no transcript read, no new timer, no lock wait.
Changelog
- Added: agent chats that run with no autocompact cap, a context over 400k tokens, or more than 40 subagents an hour show a warning badge on their sidebar row, whose tooltip says what to do, and a
warnmark inacpmux ls.
Agent session: agent session
🤖 Generated with Claude Code