/** * Durable submission runner (v2 migration, phase A5). * * The execution engine over an {@link AgentSubmissionStore}: a claim loop * that takes runnable session heads with lease-fenced CAS claims, an * executor seam that applies the input to the instance session, and a * reconciler that classifies interrupted work after a crash and produces * exactly-once observable outcomes — completed work settles, repairable * work resumes, unstarted work requeues, exhausted work fails with a * terminal advisory. Ported from flue's node agent coordinator + * `reconcileInterruptedSubmission`, adapted to the harness session model * (SessionEntry DAG; `session.prompt({ submission })` is idempotent by * submission id, so re-execution after a crash never double-applies input). */ import type { AgentDispatchAdmission, AgentSubmission, AgentSubmissionStore, SubmissionAttemptRef, SubmissionSettlementObligation } from "./submission-store.js"; import type { AgentSubmissionInput, SubmissionSettledRecord } from "./submission-store.js"; import type { AttachmentStore } from "./attachment-store.js"; import type { DeliveredMessage } from "./delivered-message.js"; import type { DispatchInput, DispatchQueue } from "./dispatch.js"; import type { SubmissionInspection } from "./submission-state.js"; import type { SubmissionTelemetrySink } from "./submission-telemetry.js"; export declare class SubmissionAbortedError extends Error { readonly code = "SUBMISSION_ABORTED"; constructor(); } export declare class SubmissionTimeoutError extends Error { readonly code = "SUBMISSION_TIMEOUT"; constructor(); } export declare class SubmissionRetryExhaustedError extends Error { readonly code = "SUBMISSION_RETRY_EXHAUSTED"; readonly interruptedTools: InterruptedToolCallRef[] | undefined; constructor(options: { attemptCount: number; maxAttempts: number; interruptedTools?: InterruptedToolCallRef[]; }); } export declare class SubmissionInterruptedError extends Error { readonly code = "SUBMISSION_INTERRUPTED"; readonly phase: "retry_exhausted_before_input" | "after_input_application"; constructor(phase: SubmissionInterruptedError["phase"], detail?: string); } /** Grace allowed after a live attempt is signalled before force-settlement. */ export declare const SUBMISSION_SETTLE_GRACE_MS = 60000; export interface SubmissionInterruption { readonly submissionId: string; readonly kind: "dispatch" | "direct"; readonly reason: "interrupted_before_input_marker" | "interrupted_after_input_application" | "exhausted_retry_budget" | "exceeded_timeout" | "aborted"; readonly message: string; } /** A tool call settled with an explicit interrupted-outcome marker at terminalization. */ export interface InterruptedToolCallRef { readonly name: string; readonly id?: string; } export interface SubmissionExecuteOptions { readonly attempt: SubmissionAttemptRef; /** Aborted for graceful shutdown or a durable abort request. */ readonly signal: AbortSignal; /** * Must be invoked at the input-applied boundary (canonical input durable, * no provider work started). Throws when the attempt lost ownership — the * executor must let that abort the turn. */ readonly onInputApplied: () => Promise; /** Claim live deliveries at a model turn boundary. */ readonly takeJoinedInputs: () => Promise Promise; }>>; } /** * How the runner touches sessions. `execute` applies the submission's input * to the addressed instance session and resolves with the turn result; * everything else is store-level and must not require a live agent. * * Contract requirements: * - `execute` must be idempotent by submission id (a resumed attempt whose * input entry already exists must not append it again). * - `recordTerminal` settles the conversation to a deterministic rest state * (unresolved trailing tool calls get explicit interrupted-outcome * markers — NEVER re-executed) and appends a terminal advisory. * - `appendSettlement` appends the canonical `submission_settled` entry, * idempotent by its deterministic record id. */ export interface SubmissionExecutor { execute(submission: AgentSubmission, options: SubmissionExecuteOptions): Promise; inspect(submission: AgentSubmission): Promise; recordTerminal(submission: AgentSubmission, interruption: SubmissionInterruption): Promise; appendSettlement(submission: AgentSubmission, obligation: SubmissionSettlementObligation): Promise; /** The canonical settlement record for the submission, when one exists. */ getSettlement(submission: AgentSubmission): Promise; } export interface SubmissionSettlement { readonly outcome: "completed" | "failed" | "aborted"; readonly result?: unknown; readonly error?: string; } export interface SubmissionRunnerOptions { submissions: AgentSubmissionStore; executor: SubmissionExecutor; /** Validate and optionally enrich an admission target before persisting it. */ validateInput?: (input: AgentSubmissionInput) => Promise | AgentSubmissionInput | undefined; /** * Durable content-addressed attachment storage. When configured, inline * base64 attachments are materialized into refs BEFORE admission so the * persisted submission payload never carries the bytes. Materialization is * deterministic (ids from the submission/dispatch id + index, digests from * content), preserving admission idempotency for exact redeliveries. */ attachments?: { store: AttachmentStore; /** Defaults: 20 attachments, 10 MiB each, 25 MiB total decoded bytes. */ maxCount?: number; maxAttachmentBytes?: number; maxTotalBytes?: number; }; /** * Lifecycle telemetry sink (v2 C4): one event per submission transition * (accepted / started / settled), keyed by the stable submission id. * Failures are reported through `onError` and never affect execution. */ telemetry?: SubmissionTelemetrySink; onSettled?: (submissionId: string, settlement: SubmissionSettlement) => void; onError?: (context: string, error: unknown) => void; /** Lease renewal cadence for active submissions. Default 10s. */ heartbeatIntervalMs?: number; /** Expired-lease discovery cadence. Default 15s. */ leaseScanIntervalMs?: number; } export interface SubmissionRunner { /** Durable dispatch admission; processing is asynchronous. */ admitDispatch(input: DispatchInput): Promise; /** Durable direct-prompt admission; processing is asynchronous. */ admitDirect(input: AgentSubmissionInput): Promise; /** * Record a durable abort request for every unsettled submission of the * session and abort any attempt running in this process. Returns the * affected submission ids (empty when the session was idle). */ requestAbort(storeSessionId: string): Promise; /** Reconcile interrupted work from a previous process; call once at startup. */ reconcile(): Promise; /** Resolve when the submission settles, with its outcome. */ waitForSettlement(submissionId: string, options?: { pollIntervalMs?: number; signal?: AbortSignal; }): Promise; /** Resolve when no active or runnable work remains. */ waitForIdle(): Promise; /** Stop claiming, abort active work at the turn boundary, wait (with timeout). */ shutdown(timeoutMs?: number): Promise; /** Wake the claim loop (new work may be runnable). */ wake(): void; /** A DispatchQueue façade over durable admission. */ createDispatchQueue(): DispatchQueue; } export declare function createSubmissionRunner(options: SubmissionRunnerOptions): SubmissionRunner; /** Store-session FIFO key of a submission (re-exported convenience). */ export declare function submissionSessionKey(input: Pick): string; //# sourceMappingURL=submission-runner.d.ts.map