Repository navigation
perf(cmux-next): hidden agent chat pages freeze like browser tabs; background-opened tabs start their hidden clock - #27047
Conversation
…ckground-opened tabs start their hidden clock Leo, 2026-10-28: "There's just loads of bullshit in my sidebar that I'm not even looking at. So how could rendering be this fucking slow?" A hidden chat's Chromium page now freezes after browser.freezeHiddenTabs (30 s), as a hidden browser tab does, with its own eligibility: a turn that works or waits on the person, an unknown turn state, a pinned tab (unless pinned tabs hibernate), a New Tab page, a Chief chat and a crashed page stay awake. It thaws when shown, when its turn starts, and before a release. A page built while its tab does not draw starts its hidden clock too. ContentLifecycle's .adopted (a tab whose content was made in the background) now conceals it, so its hidden clock starts and it freezes; before, it never froze until shown once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-6c7d0900cab519ec0cb2
|
Owned by an agent session · session |
|
Bead |
…page arms its own freeze Review findings on the hidden chat freeze: - The frame pacer sends a hidden page the pushes it waits on (a reply, a turn's end, a backlog). AgentPaneTransport.willDeliver runs right before a push; a frozen page's hibernation thaws it there and freezes it again freezeHiddenTabs after that if it stays hidden and idle. - A page built while its tab does not draw got no event, so its clock waited for an unrelated replan. AgentTabStore.onPageBuilt now starts its clock and arms the deadline. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-6c7d0900cab519ec0cb2
… is there; the quit's relaunch state thaws frozen chats Second review round: - A Chromium chat page gets its browser after its view, so at build time it had no freeze control and was never planned. It is now planned (AgentPaneView.waitsForChromium) and looked at again, backing off, until its browser is there. - The quit asks every chat page for its relaunch state, and the page's draft ack waits on a JavaScript timer that a frozen page never runs. AgentTabStore.relaunchWakeStates now thaws frozen pages first and keeps every page awake until it ends (AgentPageHibernation.holdAwake). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-6c7d0900cab519ec0cb2
… instead of being lost frozen Third review round: a provisional chat tab can freeze before its creation answers; AgentTabStore.rekey then moves its page to the store's key, and the next plan dropped the frozen entry without thawing it, so no turn start, push or quit collection reached it again. Frozen pages are now kept with the page and the control they froze through: one no longer under its key thaws, starts its hidden clock under the new key and freezes again later; a push finds its page by the page itself. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-6c7d0900cab519ec0cb2
…r key its tab has now Fourth review round: a workspace release right after a provisional tab's rekey (no plan between) thawed by the new key and missed the entry under the old one, so the release asked a frozen page for its state. thaw(key) now also finds the entry by the page the key shows; tests cover push and release after a rekey with no plan between. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-6c7d0900cab519ec0cb2
… a page released under one key never refreezes under another Fifth review round: - Debug and automation actions (DebugAgentPane chat_state, send_prompt, ...) call the page's scripts directly. AgentPaneTransport.willDeliver becomes beforePageWork and also runs at the start of AgentPaneView.callPageScript and pageSnapshot, so a frozen page thaws before any of them reaches it. - With a provisional key's alias still resolving, a page is under two keys; the freeze plan now skips a page being released or already frozen under either. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-6c7d0900cab519ec0cb2
Sixth review round: with a provisional tab's alias kept, one page is under two keys with two hidden clocks; work for it thawed it under one key and the plan that followed froze it again under the other. The freeze plan now groups the hidden keys by page (the latest hide of any key counts), and the wake cooldown is kept per page, so one page is planned, frozen and woken once whatever its keys. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-6c7d0900cab519ec0cb2
swift-frontend crashed (signal 6) type-checking an inline call on a #require result (AgentPageFreezeTests.swift:225); bind it to a constant first, as aPushForAFrozenChatThawsItFirst already does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-d5a1ae14beefc4eef784
|
Found 2 test failures on Blacksmith runners: Failures
|
|
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)
web and size checks: green Shipped in:
|
|
Review (codex gpt-6-astra high, router-review): SHIP at ba18722 SHIP
The focused Swift tests could not run because Swift/Xcode is unavailable in the review environment. |
SW-11 performance review — hidden working chats and repeated page wakesRead at Two specific paths to measure:
Suggested acceptance experiment (hypothesis; no live A/B run from this review): Extend If busy-hidden CPU stays high: the systemic follow-up is to detach the hidden chat's browser projection from live message processing. acpmux already owns session/turn/transcript state (layer-ownership rules 1–3). Keep an offscreen Rust-owned subscription and revision/sequence watermark, update native turn/permission indicators, and hydrate a visible page from a bounded snapshot plus ordered deltas. Validate attach/reveal, draft preservation, permissions, remote sessions, crash/reconnect, and no duplicates before deleting the hidden-page push path. Run behind a rollback switch. This could lower CPU for the very workload whose turns currently exempt those pages. It is a separate scoped project, with the current PR owner retaining this slice. I am recording the broader ranking and other ownership-sensitive findings on HQ #5599 under RESULT — SW-11. |
… to CmuxNextHibernation (#31302) Base red: arch-ratchet [app_lines] CmuxNextApp +209 over e716128..b2ff685, from #27047 (adc50b8), which added the hidden chat page freeze to CmuxNextApp's AgentPageHibernation. The policy and its clocks (hidden times, shown and released pages, the workspace-due set, frozen pages, the wake cooldown, the hold during the quit's collection, the release and freeze plans and their two deadlines) are now HiddenChatPages in CmuxNextHibernation, beside HibernationPlanner, generic over the App's page and freeze control. AgentPageHibernation keeps the App's side: the seams, the tab store's own eligibility (this Mac's chat, New Tab, Chief, crashed, shell runs), the release itself (wake state, frame, displace, tab store) and the wiring. Behavior-neutral: every rule and its order is the same; the tests (AgentPageHibernationTests, AgentPageFreezeTests) call the same surface. No new package edge: CmuxNextHibernation keeps CmuxNextCompat and CmuxNextSettings; the deadlines are WorkspaceHibernationDeadline (the App passes DemandTimer, as for workspace hibernation). Co-authored-by: cmux-bot[bot] <348531757+cmux-bot[bot]@users.noreply.github.com>
![Fix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/fix-with-codesmith-light-3x.png)
Leo, 2026-10-28: "There's just loads of nonsense in my sidebar that I'm not even looking at. So how could rendering be this fing slow? I'm at, what, 40% CPU, 78% RAM? You have room. So you're just not properly fing using it."
What
Hidden cmux pages now freeze the way hidden browser tabs already do.
Hidden agent chat pages freeze (
AgentPageHibernation). A hidden chat's Chromium page freezes afterbrowser.freezeHiddenTabsseconds (30 by default). It uses the tab's ownBrowserFreezingcontrol, the sameCEFTab.setFrozenthat browser tabs use. It thaws:The page keeps everything it holds; nothing reloads.
Before this, a hidden chat page kept running every push, timer and CSS animation until its whole workspace hibernated after 60 idle minutes. The 30 s freeze only covered
cache.browsers(BrowserHibernation.swift:436-437).Freezing has its own eligibility (
keepsAwake), not the release rules:browser.hibernation: offturns it off, as it does for browser tabs.Pages built while their tab is not drawn start their hidden clock. This covers chats restored or opened in the background, which never "hid" and so never froze.
ContentLifecycle.adoptedconceals. A browser tab whose page was made in the background was recorded.mountedHiddenbut got no conceal effect (ContentLifecycle.swift:238-244). SohiddenAtwas never set and it never froze until it had been shown once. It now gets the same conceal as any tab that stops showing:setContentVisible(false), so Chromium counts it hidden and the fork's freeze accepts it;tabDidHide, which starts the hidden clock.The conceal takes no snapshot here, because the page was never shown.
No default changes and nothing is removed.
browser.freezeHiddenTabs,browser.hibernationand the discard rules are unchanged.How this differs from #26975
#26975 (draft) hides the parked New Tab spare and the pooled Settings host. It is in
NewTabSparePool,PageHostPool,AgentPageRedrawandAgentPaneView+Warmup.This PR freezes real hidden chat tabs and background-opened tabs. It touches only
AgentPageHibernationandContentLifecycle, and it excludes New Tab pages so it never meets the spare pool. The two PRs share no files and stack cleanly.Evidence (before)
The before numbers are from a heavily loaded dogfood Mac, measured read-only. It had 134 workspaces, about 3,000 acpmux sessions and 38 streaming, and the person was idle:
Performance
Harness:
scripts/measure/perf-10x-idle.sh(cmux-ci run --class exclusive --measurement, both runs on the same fleet Mac, fresh home),counts=1 agents=20 measure=120: 20 idle agent chats on the fake ACP agent, one workspace each, so 19 hidden; 20 s settle, then 120 s measured. Rows are theidle-20sample. Before is9d2e8cc8(the base with #28433's harness fix, the first commit where this bench opens its chats); after is the same commit plus this PR's diff (7b5c3e85, a measure-only branch). One run each.These chats are idle, so their pages had little to stop: the saving here is the timers and wakeups of hidden idle pages. The case from the Evidence section (hidden chats re-rendering on every update of 38 streaming sessions) is not reproduced by this bench. Before this, the bench measured one workspace instead of 20 on every fleet step (#28433).
Tests
AgentPageFreezeTests(new):freezeHiddenTabsand thaws when shown;WorkspaceHibernationPlannerTests:.adoptednow returns one conceal with a current token.No capture: no visible change; a hidden chat page's renderer is paused and resumes when shown, and the page looks the same.
Timers
One new
DemandTimer(AgentPageFreeze): one deadline for the next hidden page to come due. Its stop condition: it is cancelled whenever no hidden page is due, the setting is off, orfreezeHiddenTabsis unset. Planning stays event-driven (presentation changes, turn-state changes, setting and pressure changes); nothing polls.Risk
Medium.
🤖 Generated with Claude Code
Agent session: agent session