P-SWITCH.1: opening a session never stops the running one unless you choose to (ADR-0403) - #392
Merged
Merged
Conversation
…choose to (ADR-0402, #390) /api/session/load and /api/newSession answer 409 while Main is busy (goal loop, automation, or turn) unless force. The sidebar, New session and the palette ask first and dock a sheet above the composer: Open as a spoke (resume the session in a lane and attach), Stop it and switch, Stay here. A session a live spoke holds attaches to that spoke instead of loading a second copy.
Collaborator
Author
|
Renumbered to ADR-0403: #391 (P-FLEET.L20) already uses ADR-0402. Demo re-run: 60 pass. |
mlcyclops
changed the base branch from
fix/fleet-l17-recover-model
to
master
September 26, 2026 19:27
mlcyclops
approved these changes
Sep 26, 2026
mlcyclops
left a comment
Owner
There was a problem hiding this comment.
Approving. Integration is clean on top of #389 and #391 (tsc clean in root, desktop and server; the full bun suite passes 5881 / 0 fail; make demo-P-SWITCH.1 passes 60 / 60). I also drove it live on an isolated engine with a fake omp in hang mode:
- With Main idle, clicking another session loaded it and no sheet appeared.
- With a turn running,
POST /api/session/loadandPOST /api/newSessionwithoutforcereturned 409 ("a turn is running"), and the turn kept running. - Clicking a second session opened the sheet with focus on Open as a spoke. Esc closed it and the turn kept running.
- Open as a spoke put a promoted lane in awaiting-input with the ON SPOKE banner showing, and Main's turn was still running.
On master, every sidebar click silently cancels the turn, so this is a strict improvement. Review turned up edge cases that are not regressions against master but belong in the #390 follow-ups:
- Forced switch during a goal loop does not stop the loop. When
switchBlocker()says "a goal loop is running",force: truegoes straight toloadSession/newSession, and nothing cancels the goal (goalCancelled/cancelGoal).runGoalcan then continue its checker or next iteration in the newly loaded session. The sheet's "Stop it and switch" promises a stop that does not happen. Cancel the goal (and any Main-owned automation) before the forced load. agent-kind automations block the switch.autoRunningalso covers them, but they run throughstartAgentRun(), not Main's ACP session, so switching would not stop them. Scope the blocker to automations that own Main.- The race path can leave the wrong view. On a 409 caused by a goal or automation between turns,
recoverMasterTurn()has nothing to adopt, so the renderer keeps showing the target session while the engine still holds the old one. Reload the 409'ssessionIdtranscript in that case. - Ownership fails open.
fleetStatus()returning null becomes "no lanes", andstopped/errorlanes count as released even thoughstop()is not awaited anderrordoes not stop the client. Either can plan aloadof a session a lane process may still hold. This is covered once the engine-side owner registry (P-SWITCH.2) lands. - Stale plans are not discarded.
openSessionawaits two preflights before any epoch check, so clicking A then B quickly can let A's late plan win. Take a generation number before the await.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
First increment of #390. Stacked on #389 (P-FLEET.L17): Open as a spoke resumes the session through the
sessionIdspawn path that #389 adds, so this PR targets that branch. Merge #389 first; GitHub then retargets this one tomaster.What changes
AcpBackend.switchBlocker()says what a switch would stop: a goal loop, an automation, or a turn. While it is set,/api/session/loadand/api/newSessionanswer409 {busy, sessionId}unless the body carriesforce: true.GET /api/session/busyreports the same thing. Every client (desktop, PWA, a script) gets the same refusal. The ADR-0385 cancel insideclearTurnRecoveryis unchanged; it now only runs when the user chose to stop.openSession, planned by the puredesktop/renderer/session_switch.ts:fleetSpawnwith the session id, thenpromoteLane. Main keeps running.force.Proof
make demo-P-SWITCH.1: 60 pass. It covers the busy predicate on the real backend over the fake ACP agent, the switch plan, and the sheet wording.tscare clean./app.jscontainsswitchSheetand/api/session/busyand no longer contains the oldvoid resumeSession(s.dataset.sid). Served/styles.csscontains#switchSheet.POST /api/session/loadand/api/newSessionwithoutforcereturned 409 ("a turn is running"), and the turn kept running.awaiting-input, promoted, with the ON SPOKE banner showing. Main's turn was still running.Not in this increment (tracked in #390)
hubSessionIdand orbit grouping by hub.