You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The active pane is only a projection; running state belongs to the session that owns the stream.
fix: reuse inflight session stream #1467 reused the existing EventSource for the same (session_id, stream_id) instead of tearing it down during sidebar switch-back.
These changes fixed important concrete bugs, but they also expose a broader architectural question: which runtime state is pane-owned, which runtime state is session-owned, and what invariants should future PRs preserve?
If this direction matches the intended architecture, this issue could also serve as a tracking issue for small follow-up PRs.
Proposed mental model
The WebUI appears to have two different concepts that should stay separate:
Active pane state
S.session
S.messages
S.busy
S.activeStreamId
currently rendered approval / clarify / composer UI
The active pane is a projection of one session. A running turn belongs to the session that owns the stream, even when that session is not the currently viewed pane.
Why this matters
Long-running Hermes turns may continue while the user:
switches to another sidebar session;
opens the same session in another tab;
reloads /session/<id>;
restores from BFCache;
opens the root / page in a new tab;
receives approval / clarify prompts in the background;
cancels a running session from the sidebar;
hits network drops or EventSource reconnect paths;
eventually needs crash recovery / replay behavior.
If code paths rely too heavily on active-pane globals, several classes of bugs can reappear:
switching panes tears down or duplicates a still-running stream;
a background session completion mutates the active pane;
cancel / approval / clarify actions target the wrong session;
a root tab projects into a saved running session and appears globally blocked;
S.busy prevents unrelated pane actions even though the running turn belongs to another session;
transport fan-out and runtime ownership get conflated with replay / WAL durability.
Suggested invariants
I would like to confirm whether the following invariants match the intended direction:
S.session, S.messages, S.busy, and S.activeStreamId should describe the currently viewed pane only.
Running state should be keyed by session_id / stream_id, not by whichever pane is currently active.
Returning to the same running session should reuse the existing live transport when possible.
Switching away from a running session should not inherently close, hide, or delete that session's runtime state.
Background completion should refresh sidebar / alias / canonical-session metadata without mutating an unrelated active pane.
Approval and clarify state should be owned by the session that requested it, and rendered only when that session is active.
Sidebar row actions should use row-owned runtime metadata, such as session.active_stream_id, not active-pane globals.
Multi-tab live streaming should be handled by stream fan-out; replay / crash recovery should remain a separate WAL or snapshot design.
Root / boot behavior should be considered separately from explicit /session/<id> reload / reattach behavior, especially when the saved session is currently running.
Future refactors should preserve the current no-build-step / vanilla JS architecture and land as small, reviewable PRs.
Possible follow-up PR ladder
If this direction is correct, I think the safest path is not a large runtime rewrite, but a small PR ladder. Examples:
Add/expand regression coverage for remaining running-session ownership paths.
Audit terminal handlers so completion / cancel / error cleanup is scoped to the owning session.
Audit approval / clarify rendering so pending state survives switching away and reappears when returning.
Clarify root / boot policy for saved running sessions vs explicit /session/<id> reload recovery.
Ensure background canonical-session rotation updates sidebar / aliases even when the completed session is not active.
Only after the invariants are pinned, consider centralizing per-session runtime state behind a small helper object.
But this issue is not proposing a broad rewrite up front. The immediate goal is to confirm the ownership model and use it to guide narrow fixes.
Questions
Does the active-pane vs session-owned runtime model above match the intended architecture?
Are there any current globals that should intentionally remain app-wide rather than session-scoped?
Should root / restore a saved running session automatically, or should explicit /session/<id> be the only path that projects into a running session on boot?
Should late subscribers rely only on fan-out from their attach point, or should a compact live-progress snapshot be considered separately from full WAL replay?
Context
Several recent fixes have improved WebUI behavior around running sessions, stream reattachment, reload recovery, and multi-tab streaming:
(session_id, stream_id)instead of tearing it down during sidebar switch-back.pageshowrestore with the normal reload recovery path.active_stream_id, not the active pane global.These changes fixed important concrete bugs, but they also expose a broader architectural question: which runtime state is pane-owned, which runtime state is session-owned, and what invariants should future PRs preserve?
If this direction matches the intended architecture, this issue could also serve as a tracking issue for small follow-up PRs.
Proposed mental model
The WebUI appears to have two different concepts that should stay separate:
Active pane state
S.sessionS.messagesS.busyS.activeStreamIdRunning session state
INFLIGHT[session_id]LIVE_STREAMS[session_id]active_stream_idThe active pane is a projection of one session. A running turn belongs to the session that owns the stream, even when that session is not the currently viewed pane.
Why this matters
Long-running Hermes turns may continue while the user:
/session/<id>;/page in a new tab;If code paths rely too heavily on active-pane globals, several classes of bugs can reappear:
S.busyprevents unrelated pane actions even though the running turn belongs to another session;Suggested invariants
I would like to confirm whether the following invariants match the intended direction:
S.session,S.messages,S.busy, andS.activeStreamIdshould describe the currently viewed pane only.session_id/stream_id, not by whichever pane is currently active.session.active_stream_id, not active-pane globals./boot behavior should be considered separately from explicit/session/<id>reload / reattach behavior, especially when the saved session is currently running.Possible follow-up PR ladder
If this direction is correct, I think the safest path is not a large runtime rewrite, but a small PR ladder. Examples:
/boot policy for saved running sessions vs explicit/session/<id>reload recovery.A possible future shape might be:
But this issue is not proposing a broad rewrite up front. The immediate goal is to confirm the ownership model and use it to guide narrow fixes.
Questions
/restore a saved running session automatically, or should explicit/session/<id>be the only path that projects into a running session on boot?