import type { AgentRuntime, RunningOrg } from './daemon.js'; import type { TokenUsage } from './policy.js'; import type { OrgTask } from './task-dag.js'; /** Checkpoint state for a single agent role */ export interface RoleCheckpoint { /** Message queue content (actual messages, not just count) */ mailboxQueue: string[]; /** Whether the mailbox was closed */ mailboxClosed: boolean; /** Why it was closed, if given a reason (e.g. 'token-budget') — see * isRecoverableCloseReason in mailbox.ts; resume checks this before * re-closing the restored mailbox. */ mailboxCloseReason?: string; /** Total BILLABLE token usage from the policy engine (ADR-O001 D1: input + * output + cache reads + cache writes). Pre-ADR checkpoints hold the old * input+output-only number here; `tokenUsage` tells the two apart. */ tokensUsed: number; /** ADR-O001 D1: the four quantities, separately. Absent in checkpoints * written before this existed — resume falls back to `tokensUsed` on its * original (uncached) basis, so an old file still resumes. */ tokenUsage?: TokenUsage; /** Cost tracking; null = no reported cost (unknown, rev 28). */ costUsd: number | null; /** Last message ID for threading */ lastMessageId?: string; /** #327: turns the role has finished this run, so a resumed role's * `_meta.trace.turn` continues instead of starting over at 1. */ turns?: number; /** Session ID for SDK resume */ sessionId?: string; /** Agent status. 'stopped' marks a role whose session was still live when * the org stopped (the stop checkpoint is captured before sessions drain); * resume brings it back as 'running' — see restoredRoleStatus. */ status: 'running' | 'ended' | 'crashed' | 'stopped'; /** Error message if crashed */ error?: string; /** Terminal scrollback — last N lines of agent output. */ scrollback?: string[]; /** Role-slot generation this checkpoint was captured at (0 = never replaced). */ generation: number; /** Accepted org_respawn_role attempts for this role so far this run. */ respawnCount: number; /** Accepted org_compact_role / org_clear_role calls so far this run (absent = 0). */ resetCount?: number; /** Currently-applied overrides on top of the org definition's role config — * omitted fields mean "keep the org-definition value", not "unset it". */ effectiveRoleOverrides: { runtime?: string; model?: string; providerName?: string; }; /** Messages that arrived while the role was mid-replacement, not yet * delivered into a live mailbox. */ queuedDuringSwap: string[]; /** Usage retired from a previous (replaced) incarnation of this role — * kept separate from the live PolicyEngine's usage so org-wide budget * enforcement can sum both without double-counting or resetting spend. */ retiredUsage: { tokens: number; costUsd: number; }; /** ADR-O001 D7: the loadout this role's session was built with, so a * resumed session is rebuilt with the same system prompt. Absent when the * role had none (and in checkpoints written before D7). */ loadout?: string; } /** Full checkpoint state for an org */ export interface OrgCheckpoint { /** R6: schema version — bumped on any breaking shape change so a future * consumer can detect an old-shape checkpoint instead of failing the * checksum and silently returning null. */ version: number; status: 'running' | 'stopped' | 'crashed'; run: string; pid: number; updated: string; /** Per-role checkpoint state */ roleState: Record; /** Roles not yet spawned (lazy spawn) */ pendingRoles: string[]; /** TaskDag snapshot (TaskDag.toJSON()), so resume can rehydrate it via * TaskDag.fromJSON() instead of discarding all task state. */ tasks?: OrgTask[]; /** Checksum for state validation */ checksum: string; } /** The live status a resumed role starts with: a role recorded as 'stopped' * was running when the org stopped, so resume makes it running again. */ export declare function restoredRoleStatus(rc: Pick | undefined): 'running' | 'ended' | 'crashed'; /** Current checkpoint format version. Bump on breaking shape changes. * v2 (this bump): added generation, respawnCount, effectiveRoleOverrides, * queuedDuringSwap, retiredUsage per RoleCheckpoint for mid-run role * replacement — see migrateCheckpoint() for the v1→v2 upgrade path. */ export declare const CHECKPOINT_VERSION = 2; /** Checkpoint TTL config */ export declare const CHECKPOINT_TTL_MS: number; /** * Extract full checkpoint state from a RunningOrg * Called by persistState() to capture complete state for resume */ export declare function captureCheckpoint(org: RunningOrg, status?: 'running' | 'stopped' | 'crashed'): OrgCheckpoint; /** * Validate checkpoint integrity using checksum * Returns true if checksum matches AND the schema version is current. * * R6: a version field was added so future shape changes can be detected * explicitly. Old checkpoints (no version field) fail with a clear cause * instead of the silent checksum-mismatch → null return that masked the * real reason resume was impossible. */ export declare function validateCheckpoint(checkpoint: OrgCheckpoint): boolean; /** * Check if checkpoint has expired based on TTL * Returns true if checkpoint is stale and should not be used */ export declare function isCheckpointExpired(checkpoint: OrgCheckpoint, ttlMs?: number): boolean; /** Migrate a checkpoint from a known older schema version to * CHECKPOINT_VERSION. Returns the checkpoint unchanged if already current, * or null if its own integrity check fails or its version is neither * current nor a version this build knows how to migrate from — callers * must treat null the same as "no usable checkpoint" (see resumeOrg / * daemon.ts's resume path, which call this before validateCheckpoint). * * v1→v2: old per-role state never went through a replacement, so the only * correct defaults are generation 0, zero respawns, no config overrides * (the org definition's own role config still applies), no swap-queued * messages, and zero retired usage. */ export declare function migrateCheckpoint(checkpoint: OrgCheckpoint): OrgCheckpoint | null; /** * Generate checksum over checkpoint state. * * Uses a stable recursive stringify so semantically-equal checkpoints * (same keys + values, regardless of insertion order) hash identically. * Hashed with SHA-256 (truncated to 16 hex chars = 64 bits — plenty for * tamper detection across realistic checkpoint counts). * * T3 (and a real latent bug): the previous implementation called * `JSON.stringify(state, Object.keys(state).sort())`. Passing an array * as the second argument makes it a *whitelist* — but JSON.stringify * applies that whitelist at every level, not just the top. Since nested * keys like `tokensUsed` / `mailboxQueue` aren't in the top-level keys * list, they were silently stripped from the canonical form. Result: the * checksum was identical for any two checkpoints sharing the same * top-level shape, regardless of roleState values — validateCheckpoint * provided ZERO integrity guarantee. */ export declare function generateChecksum(state: Omit): string; /** * Restore mailbox queue from checkpoint * Replays messages into the mailbox in order */ export declare function restoreMailboxQueue(runtime: AgentRuntime, queue: string[]): void; /** * Merge checkpoint state into running org * Used for resume operations to restore previous state * * NOT currently called from production code — daemon.ts's spawnRole builds * each role's mailbox inline (its own roleCheckpoint-driven restore logic, * duplicating the mailboxClosed/mailboxCloseReason handling below) rather * than calling this. Exercised today only by tests. If you change the * recoverable-close handling here, change it in daemon.ts's spawnRole too — * see isRecoverableCloseReason's doc comment in mailbox.ts. */ export declare function mergeCheckpoint(org: RunningOrg, checkpoint: OrgCheckpoint, restoreMailboxes?: boolean, restorePolicy?: boolean): void; //# sourceMappingURL=checkpoint.d.ts.map