Repository navigation
cmux NEXT: host pressure indicator and popover in the top row, off by default (part 2) - #24507
Conversation
A CmuxNextHostPressure package: the host-budget contract's formula and override file, acpmux's `host_pressure` verdict (else the same formula on this Mac's own numbers), and a tracker that turns verdicts into a normal / warning / critical level with hysteresis. Memory pressure comes from DispatchSource.makeMemoryPressureSource; load, swap and acpmux are read on one 15 s timer off the main thread. stop() cancels the source, the timer and any read in flight. No UI yet (the indicator is the next PR). Refs manaflow-ai/cmuxterm-hq#2995 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a1628e-a7d3-7161-ae27-95145ca65e93&host=Air-Blue
…h gates kern.hostname instead of a blocking name lookup, the override file read lazily inside the actor, an optional acpmux reader instead of an async default closure, and the 15 s read timer reviewed in idle-wakeups.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a1628e-a7d3-7161-ae27-95145ca65e93&host=Air-Blue
over is one phrase per reason (load, memory pressure, available RAM, swap), load1 and swap_mb_per_min may be null, pressure may be unknown, and it carries local_turns, background_processes and top for the popover. The local verdict uses the same phrases and tests (swap strictly over). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a1628e-a7d3-7161-ae27-95145ca65e93&host=Air-Blue
Review (codex xhigh) BLOCKs on #24298: - the local verdict adds acpmux's available-RAM reason (kern.memorystatus_level under min_available_ram_fraction); - memory pressure is judged against the formula's pressure allowance, sampled and from events, and a decoded verdict keeps acpmux's; - start() and stop() each begin a run; a source hop or a main-actor delivery queued from an ended run is dropped. Also: one cached host port instead of a mach_host_self() per read, and cores "logical" as acpmux names it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a1628e-a7d3-7161-ae27-95145ca65e93&host=Air-Blue
Review (codex xhigh) BLOCKs on #24298: min_available_ram_fraction takes [0, 1] (0 turns the RAM reason off), and hosts are keyed by the trimmed, lowercased short name as acpmux's host_name() does. The CPU reason names the core kind (P-cores, else cores) as acpmux does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a1628e-a7d3-7161-ae27-95145ca65e93&host=Air-Blue
…s it vm_kernel_page_size is shared mutable state to Swift 6 (the fast tier's test build failed); the swap counter uses getpagesize(). CmuxNextApp depends on CmuxNextHostPressure (its indicator comes next), so the app compile covers it and the canary list needs no entry; the generated target graph is left to the base autofix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a1628e-a7d3-7161-ae27-95145ca65e93&host=Air-Blue
The host-budget contract's fix (deputy-2): on M5 and M6, hw.perflevel0 is only the Super tier, so cosmic-gray (Super 5 + Performance 10) read 5 cores and was always over. Performance cores are now the sum of hw.perflevelN.physicalcpu for every level not named Efficiency, else hw.physicalcpu. Fixtures: cosmic-gray 15, air-blue 4, cmux16 6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a1628e-a7d3-7161-ae27-95145ca65e93&host=Air-Blue
…d of blocking The fast tier refused DispatchSemaphore.wait in an async test. The monitor takes its main-actor hop as an injectable Delivery (a Task hop by default); the test holds the queued change, stops the run, then runs it. The generated target graph matches the merge base again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a1628e-a7d3-7161-ae27-95145ca65e93&host=Air-Blue
A still glyph after the toolbar band and badge (never the sidebar), in the theme's attention color, shown only at warning or critical. Its popover shows the load against its limit and acpmux's other reasons, acpmux's turns and background processes, the busiest sessions with Stop (asked first; `_acpmux/kill` without purge, so the session resumes), and Open Agent Activity. Debug Settings > Host Pressure > Force level shows a level with the cosmic-gray incident's numbers for captures. The monitor runs while a window's indicator subscribes and stops with the last window and at quit. Refs manaflow-ai/cmuxterm-hq#2995 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a1628e-a7d3-7161-ae27-95145ca65e93&host=Air-Blue
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a1628e-a7d3-7161-ae27-95145ca65e93&host=Air-Blue
…ur, no assumeIsolated debug.host_pressure opens and closes the popover for the host-pressure tour; the service follows the quit notification as an async sequence. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a1628e-a7d3-7161-ae27-95145ca65e93&host=Air-Blue
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a1628e-a7d3-7161-ae27-95145ca65e93&host=Air-Blue
…t-pressure-indicator # Conflicts: # Packages/macOS/CmuxNext/Sources/CmuxNextApp/Settings/DebugSettingsService.swift
…ile its window shows A Debug Settings toggle, hostPressure.enabled, default off (cmux#22662: a new dev surface ships off). Off: the indicator never makes the service, so no monitor, no 15 s timer and no reads. On: each window's indicator subscribes only while its window can be seen, and unsubscribes when the toggle goes off or the window hides, minimizes, is fully occluded or closes; the last unsubscribe stops the app's one monitor and its timer. Leaving the window and deinit remove the window observers and cancel the toggle and level observations. The 21-locale HostPressure.xcstrings moves to its own follow-up PR; the English text stays as the strings' default values. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16162-34d9-7263-ad98-cd9522abca42&host=cosmic-gray
…he compiler crashed on the map) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16162-34d9-7263-ad98-cd9522abca42&host=cosmic-gray
Bugbot needs on-demand usage enabledBugbot uses usage-based billing for this team and requires on-demand usage to be enabled. A team admin can enable on-demand usage in the Cursor dashboard. |
|
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 166b27f to compare; do not merge until green; see lane-rules Tour of The path-selected recorder failed for core-after, core-before; see the run artifact and failed manifests. Path-selected interactions
agent-pane-before: the media could not be published (refused 3 file(s) that show private names: 24507/6bddc84f/next-agent-pane-before/shots/02-open-agent-chat.png (fleet host name, fleet Mac mini name); 24507/6bddc84f/next-agent-pane-before/shots/03-que)agent-pane-after: the media could not be published (refused 3 file(s) that show private names: 24507/6bddc84f/next-agent-pane-after/shots/02-open-agent-chat.png (fleet host name, fleet Mac mini name); 24507/6bddc84f/next-agent-pane-after/shots/03-queue)Core UI (before): FAILED — 16 ok, 3 failed, 1 unavailable
Failed steps: 5. Split right Screenshots (12)1. Launch
Failed steps: 5. Split right Screenshots (12)1. Launch
Screenshots (5)1. Launch: the indicator ships off (Debug toggle hostPressure.enabled)
Screenshots (5)1. Launch: the indicator ships off (Debug toggle hostPressure.enabled) Tours are data in |
|
Owned by app-efficiency on cosmic-gray · session |
|
Found 9 test failures on Blacksmith runners: Failures
|
|
Bead |
|
|
…or; app hide stops sampling From the Codex review of #24507: - A HostPressureSubscription token holds the window's subscription. cancel() ends it at once; released without one (the indicator freed off the main thread, where its deinit cannot touch main-actor state) it ends on the main actor, so no subscriber is left to keep the timer running. - The indicator also reconciles on NSApplication didHide / didUnhide. - The debug RPC passes Double frame values; a forced level's Stop asks acpmux nothing (its sessions are samples). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16162-34d9-7263-ad98-cd9522abca42&host=cosmic-gray
From the second Codex pass on #24507: the deinit off the main thread skipped removing observers and cancelling tasks. The window and app notifications are now tasks (NotificationCenter.notifications), and they, the toggle and the level observations live in Sendable task bags that the nonisolated deinit cancels on whatever thread it runs; willClose cancels them too. HostPressureSampling is marked @mainactor explicitly (the target's default already made it so). A test drives the real occlusion, unhide and close notifications. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16162-34d9-7263-ad98-cd9522abca42&host=cosmic-gray
…essure timer; showing the indicator lays out the top row From the third Codex pass on #24507: - Joining and leaving in one main-actor turn queued a start hop and then a stop hop, and the start still ran the monitor (timer and first read) before the stop. Each start and stop now bumps a generation, and a start hop that is no longer current starts nothing. - The indicator's visibility callback now sets the root's needsLayout before laying out, so a newly shown indicator gets its frame. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16162-34d9-7263-ad98-cd9522abca42&host=cosmic-gray
|
|
|
|
|
|
From the fourth Codex pass on #24507: - HostPressureMonitor.start(while:) checks on the actor, right before it builds the timer, that its caller still wants it; the service passes a lock-held generation that each stop bumps, so a stop overtaking a start hop wins. - Every observation body returns when its task was cancelled, so a notification or level value queued before willClose, a window change or leave() does nothing. - The visibility gate also needs the app not hidden (didHide). - A forced level's sample sessions get no Stop button. - Tests await the monitor's own isRunning, and drive didHide. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16162-34d9-7263-ad98-cd9522abca42&host=cosmic-gray
|
|
…he lock closure Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16162-34d9-7263-ad98-cd9522abca42&host=cosmic-gray
|
Review (codex gpt-6.1-sol xhigh): SHIP at 6bddc84 Diff-only review ( |
|
Non-green checks at 6bddc84, classified:
|
|
The feat-cmux-next push run https://github.com/manaflow-ai/cmux/actions/runs/39958936605 at cfa4d89 failed: cmux-next crash ends the app. |
|
Shipped in:
|
…move 11-04) Prove-or-remove step 3 (deletion-wave-list.md, p12-perf-process). Row p12-18 is the switch hostPressure that p12-perf-process-103 (a06867b, db70c38) made from the Debug toggle hostPressure.enabled. No A/B was planned; it has no proof. The host pressure gauge itself (monitor, service, indicator, popover, banner) came from feat (#24298, #24507, #24518) and stays. Removed: the switch json and its generated registry entry, its dogfood-set name, its feature-switch-table row, HostPressureAndSnapshotFeatureTests (it tested only the two removed switches) and the switch inputs of HostPressureIndicatorTests. The indicator reads its Debug toggle again, as before the row and as on feat: enabled and the forced level are one observation, so turning the toggle on or off applies live; the toggle help and the host-pressure media tour are back to the toggle. Default behavior: the switch was off in every channel, so DEV, NIGHTLY and Release do not change (no gauge, no 15 s host read). It was on in the chrome-parity dogfood set: a dogfood build now shows no gauge unless a person turns the toggle on, which is feat's behavior since #32374 (Lawrence 2026-11-02). Red test: HostPressureDogfoodDefaultTests (previous commit), red on the base, green here (fleet).












































![Fix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/fix-with-codesmith-light-3x.png)
cmux NEXT gets a host pressure indicator in the window's top row, after the toolbar band and the incognito badge, with a popover. It's part 2 of cmuxterm-hq#2995, on the monitor that #24298 landed. While the host is normal it shows nothing. At warning or critical it shows a still glyph in the theme's attention color, with no pulse or animation. Clicking it opens a popover with the load against its limit, acpmux's other reasons, its turns and processes, its busiest sessions with Stop (it asks first, and the session stays to resume), and Open Agent Activity.
It ships off by default behind Debug toggle
hostPressure.enabled(cmux#22662). Release and RC never activate the tunable store, so they keep it off.coord-deputy's order, quoted:
Off means inert
With
hostPressure.enabledoff (the default), the indicator never createsHostPressureService. That means no monitor, no memory pressure source, no 15 s timer and no sysctl or acpmux reads. The only thing it keeps is an Observation of that one tunable key, which is event-driven and costs no wakeups.HostPressureForce(thehostPressure.forceDebug Settings choice for captures) does nothing while the toggle is off.The timer: stop condition and cleanup
There is one 15 s timer (2 s leeway) for the whole app:
HostPressureMonitor'sDispatchSourceTimer, not one per window. It runs only while at least one window's indicator is subscribed. An indicator is subscribed whileHostPressureSampling.samplesis true, meaning the toggle is on and its window can be seen: the app isn't hidden, and the window isisVisible, not minimized, andocclusionState.contains(.visible).didChangeOcclusionState,didMiniaturize,didDeminiaturize,willClose, the app'sdidHideanddidUnhide, and the toggle's Observation. When the last indicator unsubscribes,HostPressureServicecallsHostPressureMonitor.stop(). App quit calls it too.stop()cancels the memory pressure source, the timer and any read in flight, and drops a level change that is still queued. Its window and app notifications (throughNotificationCenter.notifications), the toggle Observation and the level Observation are tasks held inHostPressureTasksbags, and each body returns at once if its task was cancelled. A window change,willCloseand the nonisolated deinit cancel them on whatever thread they run, and the indicator unsubscribes. Its subscription is aHostPressureSubscriptiontoken: if the token is released withoutcancel()(an indicator freed off the main thread), it unsubscribes on the main actor, so a window never leaves a subscriber that would keep the timer running (aSubscriptionReleasedWithoutCancelStillUnsubscribes).plans/cmux-next/idle-wakeups.mdhas the same row.HostPressureIndicatorTestscovers the stop condition.theToggleShipsOffAndOffNeverMakesTheServicechecks that the default is off and that, with the toggle off and the window visible, the indicator never creates a service.itSamplesOnlyWhileOnAndVisiblechecks that with the toggle on and the window visible the monitor runs, that it stops when the window becomes invisible, that it runs again when the window is visible again, and that it stops when the toggle goes off.theGateIsTheToggleAndTheWindowchecks the gate's truth table.Timer cost (one harness, fleet mini)
scripts/cmux-next/bench_idle.pyon fleet minicmux20-bench(M-series, 12 cores, macOS 27, logged-in console). Each run is one tagged DEV build launched clean, with 30 s settle and then 300 s measured per scenario. The app's CPU and wakeups come fromproc_pid_rusageat the start and end of each window. Base isc7e203d361and head is9a9b4d7dc8, each with one measurement-only commit (anhp-onscenario that setshostPressure.enabledthroughdebug.tunables; branchesapp-eff/hpi-bench-baseandapp-eff/hpi-bench, not for merge). The later review fixes change when a window subscribes and guard the start, not the timer's interval.What this shows: off = no change. Head with the toggle off measures 251.6/s against base's 251.8/s in the same scenario. The timer's expected cost when on is 4 wakeups/min (0.07/s), which is far below this harness's run-to-run noise. Base alone moves 28/s between its two scenarios, and almost all of the app's ~250/s idle wakeups come from outside this code (the WakeupLedger shows
CEFMessagePumpat about 0.7/s andBusyWatchdogat 0.1/s; the rest isn't attributed). So the on-visible (+33/s) and minimized (-275/s) rows are noise around a large baseline, not this timer. The timer's cost and its stop come from the code and its tests instead: oneDispatchSourceTimerat 15 s with 2 s leeway, so 4 fires/min while running.itSamplesOnlyWhileOnAndVisibleshowsisMonitoringgoing false (timer cancelled) on hide and on toggle-off, andtheToggleShipsOffAndOffNeverMakesTheServiceshows no service, and so no timer, while off.Size
About 1,050 changed lines, over the ~800 target. The popover, indicator, service and their tests are one feature behind one toggle, and the parts that could stand alone (the
HostBudget.swiftsplit and the 21-locale catalog) already landed as #24506 and #24508.Split into PRs that each land on their own
String(localized:)default values.effb5e4019):HostBudget.swiftsplit in two (6 top-level types, limit 3), with no behaviour change.97377b197b):HostPressure.xcstringsin all 21 locales. It comes whole rather than as non-English only, because the localization debt ratchet fails a catalog that has English but lacks the required locales.The Codex review took six passes. They found and fixed these: app hide wasn't observed; an off-main deinit left observers, tasks or a subscriber running (fixed with
HostPressureTasksandHostPressureSubscription); a start hop overtaken by the last leave still started the timer (fixed withHostPressureMonitor.start(while:), which checks on the monitor actor before it builds the timer, so this PR touches the part-1 library by 5 lines); a callback queued before cancellation could still resubscribe; a newly shown indicator didn't get laid out; and a forced level's sample sessions showed a Stop that did nothing (now hidden). The tests await the monitor's ownisRunning(aStartOvertakenByTheLastLeaveStartsNothing,aStartNoLongerWantedStartsNothing,windowAndAppNotificationsStartAndStopSamplingwith occlusion, hide, unhide and close posted to an injected center). This PR also fixes a compiler crash on the branch ("failed to produce diagnostic" atHostPressureService.swift:53). The acpmux reader is now a plain closure instead ofOptional.mapreturning a closure.Observed / Not run
prepush-gate.py(no new violations),cmux-ci compile-check --appand theHostPressureIndicatorTests,HostPressureMonitorTests,HostPressureTrackerTestssuites on the fleet at the head (results in comments), and the idle bench above.host-pressuretour (scripts/cmux-next/media/tours/host-pressure.json) now turns the toggle on first.No capture: a Debug-only surface that is off by default (
hostPressure.enabled); thehost-pressuretour drives it for design review.Takes back #24298: only its
plans/cmux-next/idle-wakeups.mdrow, which part 1 wrote and this part 2 extends with the toggle and window stop condition, and the 5-lineHostPressureMonitor.start(while:)guard; part 1's behaviour is otherwise unchanged.Fast gates note: its
Sidebar-Lookfinding names 163CmuxNextSidebar/Viewsfiles this PR never touches. The job checks out at--depth=2, so the head commit's parent is missing, andgit diff-tree --rootlists the whole tree as that one commit's change. It's a shallow-checkout artifact of the gate, not this diff.Leo-ask: ask-427cc7d8f5
Changelog
none (Debug-only, off by default)
Agent session: cosmic-gray
🤖 Generated with Claude Code