cmux NEXT spaces: a space no window shows hibernates its idle workspaces after 15 minutes
Leo, 2026-10-29: "...new windows being like new spaces, incognito spaces, etc... this could legit make our thing more useful than chrome by having windows be switchable in this way. think of the potential perf savings~"
Leo, 2026-10-31: "the efficiency ramifications we'd need to reconcile and make use of"
Spec: leo-workspace design/spaces.md, "Hibernation and the perf plan" (PR 4 of its plan), and plans/cmux-next/workspace-hibernation-DESIGN.md.
Problem
CmuxNextHibernation freezes hidden browser tabs and unmounts idle hidden workspaces after workspaces.hibernation.idleMinutes (60 by default), but it knows nothing about spaces. After a window switches away from a space, or after Merge All Windows leaves one window showing one space, every workspace of the spaces nobody is looking at stays parked and live until its own 60-minute clock runs out.
What changes
- A space no window shows is hidden. A workspace is in a hidden space when no open window's
WindowState.profileIDis a room it is in (RoomMembership.isHidden: its pin, or the rooms that follow its session, ordefault). A workspace followed by two spaces counts as hidden only when neither is shown. - Hidden-space workspaces hibernate sooner. They wait
workspaces.hibernation.hiddenSpaceMinutesinstead ofidleMinutes. The shorter of the two settings applies. Memory pressure shortens both the same way (warning 10 min, critical 1 min). - The exemptions are the same: shown, pinned, running a command or agent turn, needs attention, Keep Awake, already hibernated, first seen.
- Switching back uses the existing wake path. The workspace the window shows wakes first, and the rest wake as they are shown, so a switch costs one workspace's wake.
- New setting: Settings > General > Hibernation > "Hibernate Hidden Spaces After" (
workspaces.hibernation.hiddenSpaceMinutes, 5 to 1440 minutes, step 5). It ships on, at 15 minutes, not behind Labs or a Debug gate. 15 is a quarter of the normal 60 and longer than the 10-minute warning-pressure wait. See the measurement below for what one hibernation saves and what a wake costs. - The hibernation log line gives
cause=hidden-spacefor these. - Without rooms (a home daemon lacking
profiles-v1) there is one implicit space every window shows, so nothing changes. With no window open at all, every space is hidden.
Timers and observers
No new timer, observer or task.
- The hidden-space test is read once per hibernation pass, when
WindowManager.hibernationResidentsgathers residents (WindowManager+HiddenSpaces.swift). - A pass is still scheduled by the existing
WorkspaceHibernationdeadline (DemandTimer) and by the existing shown-workspaces follow. A window switching space changes its shown workspaces, and that already schedules a pass. - Stop condition and cleanup are unchanged:
WorkspaceHibernation.stop()cancels the follows and the deadline. The plan'snextCheckis nil, and the deadline is cancelled, when nothing will come due.
Where the logic lives
The decision is in the pure WorkspaceHibernationPlanner (CmuxNextHibernation), next to the existing idle logic. The room rule is in the pure RoomMembership value type. Both are working Swift that this extends rather than rewrites. The App adds one closure built per pass.
Commits
test(...):HiddenSpaceHibernationTestsonly. It does not compile against base:Candidate.inHiddenSpace,Policy.hiddenSpaceIdleMinutes,WorkspaceHibernationSettings.hiddenSpaceMinutesandRoomMembership.isHiddendo not exist yet. It covers:- a hidden-space workspace past the threshold is planned for hibernation and a shown-space one is not;
- the exemptions hold;
- the shorter of the two settings applies, and pressure applies too;
nextCheckis the hidden-space deadline;- cmux.json parsing and range;
- the room rule;
- the coordinator passes the flag through.
feat(...): the fix.
Then a base merge (update-branch) and the regenerated settings exports.
Localization
Two new strings, settings.workspaceHibernation.hiddenSpaceMinutes and .help. Both are added in every locale the CmuxNextSettings catalog carries (21, including en, de, fr, ar, es, zh-Hant, zh-Hans, ko, ja). English is translated; the others are needs_review, like the neighbouring hibernation rows. The settings exports (schemas/settings/settings-schema.json, docs/mdm/*, the docs settings reference) are regenerated by scripts/cmux-next/regenerate-settings-exports.sh on the fleet.
Performance
Pending: fleet step 1145fec18b865558829125cb (scripts/measure/tab-hibernation.sh --workspaces=18 --wake=6 --config=bench, exclusive measure worker). This section is filled in from that run before landing.
Follow-ups
- Filled in once the bench lands.
Agent session: agent session
š¤ Generated with Claude Code
Summary by cubic
Spaces no window shows now hibernate their idle workspaces after 15 minutes instead of the normal 60, so parked spaces stop consuming memory once you switch away. A workspace is in a hidden space when no open window shows any room it is in; the shorter of the two idle settings applies, and memory pressure (warning 10 min, critical 1 min) shortens both clocks. Shown, pinned, running, needs-attention, Keep Awake, and already-hibernated workspaces keep their exemptions, and switching back wakes a workspace through the existing path.
Setting
- New "Hibernate Hidden Spaces After" setting (
workspaces.hibernation.hiddenSpaceMinutes, 5ā1440 min, step 5) under Settings > General > Hibernation, on by default at 15 min and localized. - The hidden-space check runs once per hibernation pass, so no new timer or observer; switching a window's space already schedules a pass.
- Hibernation log lines for these use
cause=hidden-space. - Without rooms there's one implicit space every window shows, so nothing changes; with no window open, every space is hidden.
Bench
bench_workspace_hibernation.pygains a--hidden-spacesmode: it builds K spaces of N workspaces, hides them, and measures footprint, RSS, idle CPU, and wakeups before and after the hidden clock runs out, then times switching back; because nothing asks for hibernation, the same harness measures a build without the feature.