Skip to main content

Module session_recovery

Module session_recovery 

Source
Expand description

Session recovery after unclean shutdown (Phase 4.2 — ultra-long-sessions).

When a persistent agent subprocess is running and the server is killed (crash, OS reboot, task-kill), the subprocess dies with it but the block’s session history is preserved in FileStore. On next startup, we want to:

  1. Detect that a session was running when the server died, so we can surface it to the user as “this session was interrupted”.
  2. Let the user resume: the persistent controller already supports --resume <session_id> on the next input, so the recovery UX is simply “click resume, type your next message, get picked up mid-flight”.

The mechanism is a single meta key: session:active_pid (the PID). It’s set on subprocess spawn, cleared on clean exit (graceful or killed). If this flag is still set when the server boots, the process is definitely gone (old PID from a dead process), so we transfer it to session:was_interrupted = true.

session:was_interrupted is a frontend-only signal — the backend doesn’t consume it. The frontend AgentSessionNotices renders a banner when it’s set, and service:update_object_meta clears it when the user dismisses.

A second, unrelated flag lives here for the same reason: session:resume_failed (SPEC_PANE_CLOSE_REOPEN_CONTINUITY_GUARANTEE_2026_07_27.md §4.2) marks a block whose --resume <sid> was rejected by the CLI (stale/unreachable session id — see persistent/mod.rs’s poison_resume) and silently fell through to a fresh conversation. Same shape, same frontend-only contract, different trigger (a resume rejection mid-session, not a stale PID found at boot) — kept in this file rather than a new module since it’s the established home for “frontend-only session-state signal flags.”

Unlike was_interrupted, resume_failed is cleared by the backend as well as by the user: clear_resume_failed retracts it when the recovery scan that follows a rejection succeeds, so the banner can’t claim the conversation was lost while the transcript’s own session-outcome divider says it was resumed.

Constants§

META_SESSION_ACTIVE_PID
PID of the current running subprocess; 0 or missing = no process.
META_SESSION_RESUME_FAILED
Set to true when a --resume <sid> was rejected by the CLI and the controller silently started a fresh conversation instead.
META_SESSION_WAS_INTERRUPTED
Set to true by startup scan when a pre-existing active_pid is found.

Functions§

clear_active_pid
Clear session:active_pid — called when the subprocess exits for any reason.
clear_active_pid_if_pid
Clear session:active_pid only if it still equals expected_pid — a transactional compare-and-clear for exit-handlers that know which process instance they are cleaning up after.
clear_resume_failed
Clear session:resume_failed — the counterpart to mark_resume_failed, called when a resume attempt ultimately resolves as Resumed, or when a fresh spawn carries AgentMux’s record of the conversation forward (persistent/resume_retry.rs’s carry_continuation).
mark_active_pid
Record that a subprocess with pid has been spawned for block_id. Best-effort — logs on failure but never panics.
mark_resume_failed
Mark that block_id’s --resume <sid> was rejected and the controller fell through to a fresh conversation. Best-effort — logs on failure but never panics, matching mark_active_pid’s contract. Called from persistent/stderr_reader.rs’s stderr reader the moment it detects the CLI’s “No conversation found with session ID” line, right alongside the existing core::persist_session_id(block_id, "", ...) clear.
scan_orphans
Scan all blocks at server startup. For any agent block that still has session:active_pid set, transfer the flag to session:was_interrupted.