docs/design/session-crash-recovery/session-crash-recovery-interruption-detection.md
The Recovery Service is the unified decision layer for session recovery. It reads recovered session history, classifies the current recovery state, builds the protocol repairs and continuation payloads required to proceed, and exposes the same result to the TUI, daemon, SDK, and headless entrypoints.
Existing capabilities include:
tool_use / tool_result repair.The main issue today is not that recovery capability is entirely missing. The issue is that:
The goals of a unified Recovery Service are:
Add a core service:
packages/core/src/core/session-recovery.ts
It does not render UI and does not execute tools. Its only responsibility is to
produce a deterministic SessionRecoveryPlan from the session transcript and
the current chat history.
Suggested types:
export type SessionRecoveryKind =
| 'clean'
| 'interrupted_prompt'
| 'interrupted_turn'
| 'degraded_history';
export type RecoveryRepair =
| { type: 'synthesized_tool_result'; callId: string; name: string }
| { type: 'dropped_duplicate_tool_result'; callId: string; name: string }
| { type: 'history_gap'; childUuid: string; missingParentUuid: string };
export interface SessionRecoveryPlan {
planId: string;
sessionId: string;
kind: SessionRecoveryKind;
originalApiHistory: Content[];
apiHistory: Content[];
repairs: RecoveryRepair[];
canContinue: boolean;
canAutoContinue: boolean;
requiresUserConfirmation: boolean;
visibleNotice?: string;
continuation?: {
mode: 'retry_user_parts' | 'tool_result_parts';
parts: Part[];
displayText: string;
};
}
Suggested entrypoint:
export function buildSessionRecoveryPlan(input: {
sessionId: string;
conversation: ConversationRecord;
historyGaps?: HistoryGap[];
options?: {
allowAutoContinue?: boolean;
};
}): SessionRecoveryPlan;
Core flow:
originalApiHistory from ConversationRecord.historyGaps exist, classify the session as
degraded_history.detectTurnInterruption on originalApiHistory. This must happen
before repair. Otherwise a dangling model[functionCall] would first be
closed by a synthetic functionResponse, making it impossible to classify
the state as interrupted_turn.originalApiHistory into provider-safe history, run the existing
repairOrphanedToolUseTurns on the clone, and store the result in
plan.apiHistory.interrupted_prompt: replay trailing user parts with Retry semantics.interrupted_turn: close dangling tool calls with synthetic error
functionResponse parts.visibleNotice and repairs for UI / daemon / SDK display and
debugging.Naming compatibility:
interrupted_turn; do not add
interrupted_tool_turn. nonInteractive control, ACP, and existing tests
already depend on interrupted_turn, and the Recovery Service should not add
migration cost.A unified service turns the current implicit and scattered recovery behavior into an explicit state machine.
Current state:
tool_use entries, but entrypoints do
not always know that repair happened.SessionService.loadSession returns historyGaps, and TUI / ACP can display
gap notices. However, there is still no unified recovery metadata or
consistent safe-mode policy.After introducing the Recovery Service:
clean,
interrupted_prompt, interrupted_turn, or degraded_history.The robustness gain is that recovery moves from "each place repairs a little as needed" to "each recovery has one unified classification result."
The biggest safety risk in recovery is automatically repeating side-effecting actions, such as shell commands, file writes, or external API calls.
Recovery Service safety principles:
functionResponse parts by default,
and let the model decide whether to retry.interrupted_turn defaults to requiresUserConfirmation = true unless the
caller explicitly opts in.degraded_history is never auto-continued.repairs for logs and debugging.This prioritizes:
The safety value is that recovery does not blindly resume execution. It first repairs protocol shape, then continues with conservative policy.
This design does not immediately solve every crash scenario. It focuses on the states that current capabilities can classify reliably.
Covered immediately:
interrupted_prompt.interrupted_prompt and
replayed with Retry.interrupted_turn, with synthesized error tool results.degraded_history.Not covered yet:
Completeness here does not come from adding a large amount of code at once. It comes from consolidating current capabilities into a unified plan so the states that can be classified today are handled consistently.
The Recovery Service should live in core rather than in CLI, TUI, daemon, or any single entrypoint.
Reasons:
SessionService, buildApiHistoryFromConversation, GeminiChat repair, and
detectTurnInterruption are all in core or core-adjacent layers.Suggested layering:
SessionService
Read JSONL, rebuild ConversationRecord, return historyGaps
SessionRecoveryService
Build RecoveryPlan from ConversationRecord + historyGaps
GeminiClient / GeminiChat
Consume plan.apiHistory to initialize chat
Execute plan.continuation when needed
TUI / headless / ACP / daemon / SDK
Display plan.visibleNotice
Trigger continuation from user or API requests
Benefits of this layering:
The plan produced by the Recovery Service should be convertible into two kinds of output:
The previous session stopped after tool execution. Marked 2 unfinished tool
calls as failed so the history can be sent safely. You can continue the task;
the model will decide whether to retry based on the failure results.
type RecoveryDebugPayload = {
planId: string;
kind: SessionRecoveryKind;
repairs: RecoveryRepair[];
timestamp: string;
};
This information does not enter API history. It is only for diagnostics, export, and debug. Persisting it as a system record can be deferred and is not a hard requirement of this design.
Value:
planId and repairs.After /resume or startup with --resume:
SessionService.loadSession(sessionId).buildSessionRecoveryPlan(...).config.startNewSession(sessionId, sessionData, recoveryPlan), or an
equivalent mechanism to retain the plan.plan.kind !== 'clean', insert an INFO item./continue or a "Continue interrupted turn" action.The TUI does not auto-continue interrupted_turn / degraded_history by
default.
continueInterrupted or continue_last_turn no longer calls scattered
detectors directly. Instead:
plan.canContinue = false, return no-op.plan.continuation.Add recovery metadata to the loadSession / resumeSession response:
{
recovered: boolean;
recoveryKind: SessionRecoveryKind;
canContinue: boolean;
requiresUserConfirmation: boolean;
repairs: {
type: string;
count: number;
}
[];
}
continueLastTurn should also accept / reject based on the plan, then
revalidate immediately before execution.
SDK integration needs to distinguish two categories:
loadSession /
resumeSession responses, shows a recovery banner, and allows the user or
host application to trigger continue.ProcessTransport and uses
--resume / --continue flags. It needs equivalent recovery metadata
exposed through a stream-json system message or an SDK protocol field.Neither SDK category should directly understand low-level JSONL or tool-pair repair. They should only consume the structured recovery result exposed by the entrypoint layer, and they should block auto-continuation in degraded states.
The Recovery Service must have independent unit tests that do not depend on the TUI or a real provider.
Core fixtures:
Clean history:
interrupted_prompt:
interrupted_turn:
Repair:
degraded_history:
historyGaps is non-empty.canAutoContinue = false.visibleNotice includes gap information.Compression checkpoint:
Entrypoint adapter tests:
/resume inserts an INFO item after receiving a non-clean plan.continueInterrupted uses plan continuation and does not duplicate
the user message.continueLastTurn returns the same recovery kind for the same fixture.loadSession response includes recovery metadata.The key test goal is: the same history fixture should produce the same recovery kind in core / TUI / ACP / daemon.
A unified Recovery Service is the highest-value change at this stage because it mostly consolidates existing capabilities instead of introducing many new mechanisms immediately.
Its direct value:
tool_use repair from an implicit 400-prevention step
into an explicit recovery plan.It does not solve every crash problem by itself, especially mid-text stream crashes. This document intentionally keeps those extensions out of scope for this round to avoid over-design. The current goal is to unify the recovery capabilities that already exist and can be classified reliably.