cloud.startChat: a Mac workspace keeps the Cloud machine it picked; workspaces reuse and share machines
Takes back #29582: it builds on that PR rather than undoing it. It adds workspaces to the carry's start-chat plan, pin and ledger (pick.rs, ledger.rs, start_chat_cli.rs and the Swift plan request). The carry's per-send pins, account scoping, copy folders and import all stay. The shared pick now also skips failed or removed machines (SW-05), and the App no longer sends gone machines as stored.
Builds on #29582 (merged as b06e339a, the carry of #29046: cloud.startChat's machine pick, ledger and copy in Rust).
Leo, 2026-10-31: "THE IDEA WAS ONE WORKSPACE PER MACHINE BUT IDK WHY THAT SHOULD BE THE LIMIT. JUST GO ARBITRARILY MANY MAYBE IDK HOW MUCH ONE WORKSPACE ONE MACHINE REALLY SIMPLIFIES THINGS"
Rule: leo-workspace design/cloud.md, "Solo first, then sharing a machine", rule 2: a workspace picks its machine, many workspaces can share one, and a person can have any number of machines.
What it does
When a new chat's first send goes to Cloud (cloud.startChat), its Mac workspace remembers the machine it ran on. Earlier versions of this PR gave each workspace a machine of its own. That limit is gone: workspaces reuse and share machines, and nothing caps how many machines a person has.
- A workspace's first send: it reuses this person's machine, the one named for chats first, else a free one. A new machine is made only when nothing can be reused, and the workspace keeps whichever machine it got.
- Later sends from it: they go back to that machine, even with chats open on it. A paused machine resumes there. One that is pausing or starting again stays the workspace's, even while its link is down.
- Other workspaces: they may pick the same machine. A send from no workspace may take a machine a workspace picked too.
- Account: a workspace's machine is kept under the account (user and team) its send began in, like the carry's per-send pins. After a team switch, the other team's machine is never reused, and switching back finds it again.
- Unusable machine: the workspace drops its machine when that machine is gone from the list, is a teammate's, or is failed, deleting, deleted or destroyed (even while a stale link still reads as connected). Its next send reuses another of the person's machines before making a new one.
- The shared pick (SW-05, hq#5685): it now skips a machine the server failed or is removing while a stale link still reads as connected. So a workspace whose machine failed is never bound back to it, and a send from no workspace never takes it either.
- A destroyed machine left in the App's store: the App no longer sends a failed, destroyed or deleting machine as made since (
stored), so a workspace or send pinned to it moves on to another machine instead of failing to connect (router review at f766845). - Retries: a retry keeps its per-send pin first, so it never opens a second chat elsewhere.
- Unchanged: closing a workspace never deletes its machine (that deletes data). Teammates' machines are never taken.
Where it lives
The rule is in Rust (cmux-tui/crates/cmux-tui/src/link/start_chat_cli/):
pick.rs:destination: the send's pin, then the workspace's machine, then the reuse pick.workspace_machineandis_usable.pickskips gone machines.
- The ledger (
ledger.rs):- It remembers the newest 256 workspaces' machines, apart from the 64 sends, so a workspace keeps its machine however many sends ran since. That number bounds the file only, not how many machines or workspaces a person has: an older workspace's next send just reuses a machine again.
- Both lists live in the same locked
destinations.json, which still reads an older sends-only file.
planandpin:- Both take the workspace; a bad workspace id is refused as
bad_request. planrecords the machine for an unbound workspace.pin --workspacerecords the machine the App just made.
- Both take the workspace; a bad workspace id is refused as
Swift adds the tab's workspace (services.workspaceID(ofTab:)) to the plan request and to pin, and leaves gone machines out of stored (CloudStartChat.stored).
Tests
Rust (start_chat_cli/tests.rs):
workspaces_reuse_a_machine_share_it_and_keep_ita_workspaces_gone_teammates_or_failed_machine_is_dropped_for_another(with the pausing and resuming case)a_failed_or_removed_machine_is_never_picked(SW-05)a_retry_stays_on_its_first_trys_machine_even_after_its_workspace_moved_onplan_and_pin_keep_each_workspaces_machine_across_a_relaunch(also: two workspaces share one machine throughplan, the older ledger format, bad ids)the_ledger_keeps_the_newest_workspaces_apart_from_the_sends
Swift (CloudStartChatMachineTests):
thePlanRequestCarriesTheSendItsAccountWorkspaceAndTheListedMachinesaGoneMachineInTheStoreIsNeverKeptForAPin
Observed / Not run
- Observed: the pre-push gate passed (no new violations, no CmuxNextApp growth). The Rust tests, clippy and the app compile at the head are in a comment.
- Not run: an end-to-end send on a real Cloud machine (needs a signed-in Mac and the Worker).
No capture: no UI changes.
Changelog
- Changed: a Mac workspace's chats sent to Cloud go back to the machine it first used, which other workspaces may share; a new machine is made only when none can be reused.
Agent session: agent session
š¤ Generated with Claude Code