-
Notifications
You must be signed in to change notification settings - Fork 2
terminal_lifecycle
Runloop tracks a coding-agent terminal as two separate resources:
- Process lease: ownership of the live tmux session.
- Terminal snapshot: the read-only capture rendered in the UI.
The process lease is authoritative for cleanup. Hiding or expiring a snapshot does not release a live process. Bounded workflow-step processes receive a short shutdown grace after completion, while their snapshots can remain visible for the longer provider display-retention period. Main-agent sessions are persistent and use the idle backstop instead of the bounded deadline.
Every observed coding-agent tmux session is tagged with:
@runloop_instance_id@runloop_owner_pid@runloop_owner_started_at@runloop_heartbeat
Startup recovery only removes tagged sessions whose owner PID/start-time pair is no longer live. It preserves untagged legacy sessions and sessions owned by another running backend or test process.
Terminal API state exposes:
-
process_state:live,closing, orclosed -
snapshot_kind:liveorarchived -
close_reason: the lifecycle reconciliation reason, when available
Operator actions follow these semantics:
- Dismiss hides only the snapshot.
- Complete asks the provider to finish and moves the lease toward closing.
- Fail closes the process and records a failed archived capture.
- Kill force-closes the process and records a failed archived capture.
SSE event buffers and session identity are retained for 24 hours after a session reaches a terminal state so reconnect/resume keeps its event cursor and chat context consistent. The event store's per-session count limit bounds memory; terminal process cleanup and terminal snapshot retention remain independent of this session-resume window.
The terminal UI combines data from endpoints with different responsibilities. Keep these rules intact when changing polling, caching, or live attach:
-
The terminal-list poll owns lifecycle state. It decides
state,active,process_state,snapshot_kind, and the tmux identity. A selected terminal detail/history response may contribute only its body (content,content_source, androws). Detail responses can be older than the latest list poll; allowing them to overwrite lifecycle fields makes a live pane briefly appear completed and repeatedly reconnect. -
A completed turn is not necessarily a closed CLI. Interactive main-agent
CLIs remain available for follow-up messages after the current turn reaches
completed. Continue live streaming whileprocess_state=live; stop only when the process closes or the snapshot is archived. -
A live connection has one fixed terminal grid. Do not resize xterm while
bytes from the old grid can still arrive. Geometry changes must suspend old
output, close the socket, fit the new grid, then reconnect for a fresh seed.
Ignore duplicate
ResizeObservernotifications when the containing box did not actually change size. -
Only one browser window owns live geometry. A second viewer supersedes
the first and the backend closes the first WebSocket with code
4001. The superseded viewer must keep its already-rendered local frame. It must not import a snapshot captured at the new owner's grid because reflowing that frame at a different width corrupts wrapping. The user can explicitly Take over, which fits and reconnects using the local grid.
The focused regression coverage is in:
frontend/src/utils/terminalSnapshotIdentity.test.tsfrontend/src/utils/terminalReconnect.test.tsagent_go/cmd/server/terminal_live_attach_test.go
The rail represents one logical execution once, even when that execution emits
events through several wrappers. A background task can have lifecycle events
keyed by background_agent_id while delegated content is keyed by a separate
correlation_id. delegation_start is the canonical link between those IDs;
the terminal store records it and resolves subsequent content to the lifecycle
owner.
Do not solve duplicate entries by hiding cards in React. Producers must emit a
declared execution_kind and stable parent/owner metadata, and the terminal
store must normalize those identities before snapshots reach the frontend.
Internal message-sequence items, scripted turns, and routers fold into their
workflow-step transcript. Independently selectable background agents remain
separate terminals.
The backend regression coverage is in
agent_go/internal/terminals/store_test.go, especially
TestStoreUnifiesBackgroundDelegationContentWithItsLifecycleTerminal.
Auto-synced from docs/ on main. Edit there, not here.