/** * L1 — the event sum type. * * The kernel emits a stream of these. Adapters translate provider-specific * wire events INTO these. Surfaces (TUI, web, logs) consume these. * * WHY a discriminated union instead of `{type: string, ...unknown}`: * every consumer gets exhaustiveness checking for free. Add a variant and * TypeScript breaks every `switch` that forgot it — which is exactly the * moment you want to be interrupted. A loose record defers that failure * to production. * * See ADR-0003. Read it before changing anything in this file. * * `seq` — every event carries a monotonically increasing sequence number, * assigned by the kernel's EventLog at append time. Consumers (surfaces, * persistence, eval) sync by `seq`; a trajectory is the complete replay of * `seq` 0..N. Without `seq`, "what happened" can only be reconstructed by * array-shape heuristics — the exact failure the reference implementation's * transcript sync lives in. See ADR-0002. * * This module is almost types-only: the only runtime value it emits is * `isKisoEvent`, the type guard the session store validates records with. */ /** * Why the model stopped producing. Provider-specific reasons are mapped at * the ADAPTER boundary into this closed union (Area 6) — `refusal`, * `pause_turn`, `content_filter`, and `context_window` are never allowed * to degrade into a normal `end_turn`. A new SDK enum that lacks a mapping * is a compile error in the adapters' exhaustive switches. */ export type StopReason = "end_turn" | "tool_use" | "max_tokens" | "stop_sequence" | "abort" | "error" | "refusal" | "pause_turn" | "content_filter" | "context_window" | "function_call"; /** * Structured failure classification, layered ON TOP OF `isError: boolean`. * It carries WHY a tool failed, not merely THAT it failed. * Only meaningful when `isError` is true. * * - `invalid_input` — arguments are malformed or fail the schema. * - `precondition` — the tool REFUSED to run because a gate was not met. * It never attempted the work. This is the slot that * separates "refused" from "ran and produced nothing" — * a distinction most harnesses collapse, and then cannot * tell a blocked agent from an unproductive one. * - `transient` — retriable (network blip, rate limit). * - `fatal` — unrecoverable (handler threw, invariant broken). * * The kernel never branches on this value. It is a pass-through signal for * the harness above; retry and re-route policy stay product-side. * * See ADR-0020. */ export type ToolErrorKind = "invalid_input" | "precondition" | "transient" | "fatal"; /** * A new assistant text block begins. * Surfaces should open a new paragraph. Subsequent `TextDelta` events with no * intervening `TextStart` belong to the same block. */ export interface TextStart { readonly seq: number; readonly type: "text_start"; /** Provenance of the assistant message this block belongs to (Area 6). */ readonly source?: import("./messages.js").MessageSource; } /** * The explicit boundary of an assistant message (D group). Adapters never emit * these (their implicit boundaries — tool_result/user_input/terminal — are * enough); the seed encoder uses them so ADJACENT assistant messages and * EMPTY assistant messages round-trip losslessly. */ export interface AssistantStart { readonly seq: number; readonly type: "assistant_start"; readonly source?: import("./messages.js").MessageSource; } export interface AssistantEnd { readonly seq: number; readonly type: "assistant_end"; } export interface TextDelta { readonly seq: number; readonly type: "text_delta"; readonly text: string; } /** * The assistant text block closes. Surfaces flush the paragraph here; a * block without an explicit end is closed by the next `TextStart` or the * terminal. Adapters may omit it; the union admits it so fixtures and * replayable trajectories can carry the boundary explicitly (design v3 §4.1). */ export interface TextEnd { readonly seq: number; readonly type: "text_end"; } export interface ToolCallStart { readonly seq: number; readonly type: "tool_call_start"; readonly callId: string; readonly name: string; /** Provenance of the assistant message this call belongs to (Area 6). */ readonly source?: import("./messages.js").MessageSource; } /** * Incremental JSON characters for one call's arguments. * * Concatenating every delta for the same `callId` yields the JSON document the * model emitted. It is NOT valid JSON until `ToolCallEnd` — consumers that * parse mid-stream must tolerate failure, or wait. */ export interface ToolCallInputDelta { readonly seq: number; readonly type: "tool_call_input_delta"; readonly callId: string; readonly inputJsonDelta: string; } /** * The model finished describing this call. * `input` is the parsed document, or `null` when parsing failed — the kernel * then decides whether to repair or reject. Adapters never repair silently: * a null here is a fact about the model's output, not a defect to hide. */ export interface ToolCallEnd { readonly seq: number; readonly type: "tool_call_end"; readonly callId: string; /** The tool being called — the registry lookup key. */ readonly name: string; readonly input: Readonly> | null; } /** * Emitted by the KERNEL (never by an adapter) once a handler returns. * The next `adapter.stream()` call translates it back into a provider message. */ export interface ToolResultEvent { readonly seq: number; readonly type: "tool_result"; readonly callId: string; readonly invocationSeq?: number; /** Full content — blocks preserved losslessly (D group). */ readonly content: string | readonly import("./messages.js").ContentBlock[]; readonly isError: boolean; /** Present only when `isError` is true and the handler classified it. */ readonly errorKind?: ToolErrorKind; /** The execution that produced this result (B group) — receipt pairing key. */ readonly executionId?: string; /** Provenance + product tags — preserved losslessly (Area 6). */ readonly source?: import("./messages.js").MessageSource; readonly tags?: readonly string[]; } /** * A human/user input entered the run. Emitted by the harness (session layer) * or by the loop's seed encoder — never by a provider. This is what makes a * trajectory self-contained: ADR-0002's replay of `seq` 0..N must include the * prompts, or the run cannot be rebuilt from its own log. */ export interface UserInputEvent { readonly seq: number; readonly type: "user_input"; readonly content: string | readonly import("./messages.js").ContentBlock[]; /** Provenance of the prompt (Area 6) — preserved losslessly. */ readonly source?: import("./messages.js").MessageSource; /** 0.40.0 (ADR-0051 §5 rule 1): how a PERSON's turn was composed — a * skill they invoked, and the line they typed. Display provenance only: * `content` is what the model receives and the projection never reads * this, so no byte of it reaches a request. */ readonly via?: UserInputVia; } export interface UserInputVia { readonly kind: "skill"; readonly name: string; readonly line: string; } /** * Compaction happened at this point in the trajectory. The EXACT * replacements are persisted; the projection applies them verbatim — it * never re-runs a future version of the compaction algorithm (A group/D group). * The replay therefore equals the live run byte for byte, independent of * algorithm drift. * * round 5: `eventSeq` is the STABLE identity — the seq of the specific * `tool_result` event that was replaced. The provider callId may repeat * across runs and is correlation-only; `callId` is kept for traceability. * Only THIS turn's NEWLY cleared results are listed, never a cumulative * set of already-cleared markers (round 5). * * round 4: `eventSeq` is OPTIONAL because sessions written by round three * (v1) carry `{callId, content}` entries without it. Those are legal and * replay with v1 semantics (replace every tool result with that callId, * exactly as the old framework did); records written from now on always * carry the eventSeq and replace exactly one result. */ export interface CompactedEvent { readonly seq: number; readonly type: "compacted"; readonly cleared: readonly { readonly eventSeq?: number; readonly callId: string; readonly content: string; }[]; } /** * A tool execution is about to run — the durable START of the side effect * (Phase D / Area 3). `executionId` is the framework-generated, persistent * identity of THIS logical execution; the provider's `callId` only * correlates messages and may repeat across runs. Written BEFORE the * handler is invoked, so an interruption between this event and its result * leaves an auditably UNCERTAIN state that requires a human decision. */ export interface ToolExecutionStarted { readonly seq: number; readonly type: "tool_execution_started"; readonly executionId: string; readonly callId: string; readonly invocationSeq?: number; readonly name: string; readonly input: Readonly>; } /** The side effect completed successfully. A confirmed success never re-runs. */ export interface ToolExecutionSucceeded { readonly seq: number; readonly type: "tool_execution_succeeded"; readonly executionId: string; readonly callId: string; readonly invocationSeq?: number; readonly result: { readonly content: string; readonly isError: false; }; /** round 8: the tags ride on the durable RECEIPT so a crash-window repair * of the tool_result can reproduce the normal path losslessly. */ readonly tags?: readonly string[]; } /** * The side effect ran and FAILED. A complete receipt IS the outcome, so this * execution is "failed" — NEVER "uncertain", whatever `safeToRetry` says * (ADR-0038 superseding ADR-0025 decision #3: uncertainty belongs to the * crash window alone — started, no receipt). The run does not pause here; * siblings continue and the model retries with the error in hand, and that * retry is a NEW call that re-passes the approval chain. * * `safeToRetry` is the tool's own `idempotent: true` declaration, carried * for HISTORY: since ADR-0038 it no longer feeds the ledger's status * derivation (runtime/ledger.ts). Its one live consequence is the honest * note appended to a non-idempotent failure's result (ADR-0038 Amendment 1). */ export interface ToolExecutionFailed { readonly seq: number; readonly type: "tool_execution_failed"; readonly executionId: string; readonly callId: string; readonly invocationSeq?: number; readonly error: string; readonly errorKind?: ToolErrorKind; readonly safeToRetry: boolean; /** round 8: tags on the durable receipt, preserved across crash-window repair. */ readonly tags?: readonly string[]; } /** * A human resolved an execution: "rerun" (the human takes responsibility — * the side effect may run again) or "abandoned" (the attempt is treated as * failed; the trajectory continues with a recorded denial). */ export interface ToolExecutionResolved { readonly seq: number; readonly type: "tool_execution_resolved"; readonly executionId: string; readonly callId: string; readonly invocationSeq?: number; readonly resolution: "rerun" | "abandoned"; } /** * A permission `defer` became a real pause (Phase D): the run yields this * event, persists the request, and waits for the human decision. The * decision id is the durable handle `session.approve(decisionId, ...)` * resolves. */ export interface PermissionRequested { readonly seq: number; readonly type: "permission_requested"; readonly decisionId: string; readonly callId: string; readonly invocationSeq?: number; readonly name: string; readonly input: Readonly>; /** W21: the first non-abstain extension's name — the panel's why-asked * line. Absent on static-hook asks and old logs. */ readonly speaker?: string; } /** The durable answer to a PermissionRequested. */ export interface PermissionDecided { readonly seq: number; readonly type: "permission_decided"; readonly decisionId: string; /** The invocation this decision binds to (B group). */ readonly callId?: string; readonly invocationSeq?: number; readonly decision: "approved" | "denied"; readonly reason?: string; /** E1: the deciding extension's name — present ONLY on policy decisions; * absent = the human decided (old logs stay compatible). */ readonly decidedBy?: string; } /** * A permission request was CLOSED because its run terminated first (B group): * an aborted/completed/error run's dangling approval is dead — it is never * re-presented and a late approve() cannot resurrect the run. */ export interface PermissionExpired { readonly seq: number; readonly type: "permission_expired"; readonly decisionId: string; readonly reason: string; } /** * HISTORICAL — the current kernel NEVER emits this event. * * It marked the C group's failed-receipt pause: a non-idempotent execution * that FAILED paused the run until a human ruled. ADR-0038 removed that * pause (a complete receipt IS the outcome), so nothing appends this any * more — pinned by `packages/core/tests/execution-gate.test.ts` and * `packages/runtime/tests/execution.test.ts`, which assert its ABSENCE. * * The variant stays because the durable contract is frozen (ADR-0051) and * old logs replay theirs verbatim (ADR-0038, Consequences): the projection * renders nothing for it, and the ledger reports those receipted executions * as "failed". The crash window — started, no receipt — is now the only * source of uncertainty, and it is resolved through * `uncertainExecutions()` / `resolveUncertain()`, not through this event. */ export interface UncertainPending { readonly seq: number; readonly type: "uncertain_pending"; readonly executionId: string; readonly callId: string; readonly name: string; readonly error: string; } /** * A user input was VETOED or REWRITTEN by the harness (C group). `replaces` * is the seq of the original user_input; the projection skips the original * and, when `content` is non-null, produces the replacement instead — the * rewritten fact is the ONLY fact every later turn sees. null content = a * true veto (the model never receives the message). */ export interface UserInputReplaced { readonly seq: number; readonly type: "user_input_replaced"; readonly replaces: number; readonly content: string | readonly import("./messages.js").ContentBlock[] | null; /** Provenance of the replacement — preserved from the hook (round 3). */ readonly source?: import("./messages.js").MessageSource; } /** * C area: a MICROCOMPACT boundary — the durable record of a context-clearing * decision. `beforeSeq` is the event seq up to which eligible tool results * are cleared: the projection replaces every tool_result with seq <= * beforeSeq (whose tool is in the compactable whitelist and carries no * do-not-compact tag) with a fixed placeholder derived from the stream * itself. The decision is a PERSISTED FACT — replaying the same events * derives the same messages, byte for byte, after a crash or resume. */ export interface MicroCompactEvent { readonly seq: number; readonly type: "microcompacted"; readonly beforeSeq: number; } /** * ADR-0044 — a model-generated summary replaced the covered conversation * range. `coversToSeq` is the seq of the LAST covered event; the covered * range runs from just past the previous `summarized` event's coversToSeq * (or the trajectory's start for the first) up to coversToSeq. The * projection replaces exactly those events with ONE USER message carrying * the summary behind a fixed framing line (E6's boundary honesty: the * model reads compressed history as CONTEXT, never as a reply it produced * — see SUMMARY_FRAMING in kernel/project.ts). It is not an assistant * message; every `summarized` event always renders its own message. Byte-stable: a summarized event is a persisted fact, so the * same events derive the same messages on every replay. * * The summary is generated OFF-LOOP through the session's own adapter — * the summary request itself never enters the log, and a failed summary * never leaves a record ("nothing happened"). The ORIGINAL events stay on * disk forever: /last, /think, and the raw log still reach them. */ export interface SummarizedEvent { readonly seq: number; readonly type: "summarized"; /** The last covered event's seq; the range is (previous coversToSeq, coversToSeq]. */ readonly coversToSeq: number; /** The model's compression — replaces the covered range in the projection. */ readonly summary: string; } /** * R-E 0.1.43 / ADR-0047 (Gap B) — a model output suffix WITHOUT a committed * stop was abandoned on resume. `voidFromSeq` is the last committed-boundary * event's seq (stop / user_input / terminal / compaction / summarized); the * voided range is (voidFromSeq, this.seq]. The projection skips the range and * the marker itself renders nothing — "a model output suffix without a * committed stop is an incomplete draft and must never become committed * provider history." Kernel-owned: the AdapterEvent whitelist excludes it, so * adapter/extension forgery is invalid_request by construction. */ export interface ModelOutputAbandoned { readonly seq: number; readonly type: "model_output_abandoned"; readonly voidFromSeq: number; readonly reason: string; } /** Extended-thinking content. Providers without it emit nothing here. */ export interface Thinking { readonly seq: number; readonly type: "thinking"; readonly text: string; } /** * Token accounting. * INVARIANT: at least one `Usage` MUST precede each `Stop`. A turn that cannot * report its cost is a turn you cannot bill, cap, or trust. * * `known: false` means the provider reported NO usage — the token fields * are null, never faked as zero (Area 6). */ export interface Usage { readonly seq: number; readonly type: "usage"; readonly inputTokens: number | null; readonly outputTokens: number | null; readonly cacheRead: number | null; readonly cacheWrite: number | null; readonly known: boolean; /** * RSN-1: how many of `outputTokens` the model spent THINKING, when the * provider says. It is a SPLIT of the output, not a fifth quantity — * the bill has always counted it, because completion tokens include * reasoning ones (measured: 39 reasoning inside 76 completion, with the * remaining 37 accounting for the answer exactly). * * What was missing is the SPLIT, and the split is what the effort knob * controls. Without it, an arm that costs more cannot be told from an * arm that thought longer — and a cost regression traced to reasoning * length once already had to be found by other means. * * ABSENT means the provider did not report one: the field is missing * entirely from the vendor's response when thinking is off, and missing * on every route whose usage carries no breakdown at all. Absent is not * zero, and a zero here is a provider that measured no thinking. */ readonly reasoningTokens?: number; /** * TRACE-F1: the model id the SERVER says it served, when it says one. * * Every adapter until now spoke only of `options.model` — the id we * ASKED for. A vendor is free to serve something else: a retired name * that is now an alias, a migration id, a silently upgraded tier. The * bench read `deepseek-v4-flash` off its own config for four days while * the server served `deepseek-flash`, and the specified-vs-observed * reconciliation built exactly to catch that never fired, because * nothing produced the observed half. * * Optional because it is the SERVER's statement, not ours: a provider * that reports no model leaves this undefined, and undefined means * "not stated", never "same as requested" (Area 6 — unknown is not a * default). * * It rides the usage event because that is where the adapter holds the * response. NOT because usage arrives once: the contract is AT LEAST * once, and the openai-compat adapter has three exits that can emit it * — believing otherwise is what W22-R1 was, where a second report for * one call was added to the first and put a wrong number on screen. A * consumer of this field must therefore expect repeats, and they are * the same statement rather than two: the adapters capture the served * id ONCE per call and every exit reports that one value. */ readonly servedModel?: string; } /** MG-1 (ADR-0051 Amendment 5): the continuation envelope's scope — WHO * may replay it. Kernel-stamped at the Turn Commit append from the run's * configured binding; an adapter-supplied scope is always overwritten * (adapters are not trusted), and a run with no configured scope has * adapter-emitted continuation stripped at the same boundary. */ export interface ContinuationScope { readonly providerId: string; readonly apiId: string; readonly modelId: string; /** Origin only; REQUIRED when providerId === "custom". */ readonly endpoint?: string; } /** One opaque provider block. `data` is bytes to the kernel — serialized * verbatim by the emitting adapter, replayed verbatim by the scope-matched * one, never reconstructed from projected text. */ export interface ContinuationEntry { readonly kind: string; /** true = the next request is INVALID without it (never dropped; a * required set over the hard cap voids the turn before commit). * false = quality-degradable (droppable under the soft cap). */ readonly required: boolean; readonly data: string; } export interface Continuation { readonly scope: ContinuationScope; /** EMISSION ORDER, preserved end to end. */ readonly entries: readonly ContinuationEntry[]; /** Present only when OPTIONAL entries were dropped at the soft cap. */ readonly truncated?: true; } export interface Stop { readonly seq: number; readonly type: "stop"; readonly reason: StopReason; /** MG-1 (Amendment 5): absent on every pre-A5 log — rule 1's truly * optional field; old logs project byte-identically. */ readonly continuation?: Continuation; } /** * Structured failure classification for MODEL / TRANSPORT errors. * * Distinct from `ToolErrorKind` (which classifies a tool's own failure and * which the kernel never branches on): a `StructuredError` is an error the * LOOP itself may branch on — `retryable: true` means the loop may retry * (backoff, max attempts, state entirely inside the loop — see ADR-0005). * Adapters translate provider wire errors into this shape. No regex over * error strings anywhere: classification happens at the adapter boundary. */ export type ErrorCode = "rate_limit" | "overloaded" | "network" | "timeout" | "quota" | "api_5xx" | "context_overflow" | "invalid_request" | "unknown"; export interface StructuredError { readonly code: ErrorCode; readonly status?: number; readonly retryable: boolean; readonly message: string; /** CX-1 F8: the provider's `Retry-After`, normalized to milliseconds * (finite, >= 0) — the kernel owns retries and honors it; absent when * the header was missing, unparseable, or a date already past. */ readonly retryAfterMs?: number; } /** * Why the whole run ended. The ONE terminal shape every run converges to. * * Every consumer switches on `kind`; with `exactOptionalPropertyTypes` and * `strictNullChecks` on, a terminal that nobody handles is a compile error, * not a production mystery. The reference implementation's query() returns 11 different reasons that * every consumer discards — here the terminal is an event like any other, * so it cannot be lost. See ADR-0004. * * - `completed` — the loop ended on its own terms (no tool call, or the * mode's stop predicate fired). * - `max_tokens` — the provider stopped on its output budget; the model's * turn is truncated, NOT a clean completion (Phase B). * - `max_turns` — the round budget was consumed. * - `error` — a `StructuredError` the loop could not retry past. * - `aborted` — a human (user) or the parent agent stopped it. * - `hook_stopped` — a Stop-hook prevented continuation. */ export type Terminal = { kind: "completed"; } | { kind: "max_tokens"; } | { kind: "max_turns"; turns: number; } | { kind: "error"; error: StructuredError; } | { kind: "aborted"; by: "user" | "parent"; } | { kind: "hook_stopped"; hook: string; }; /** The kernel yields exactly one of these per run, as its last event. */ export interface TerminalEvent { readonly seq: number; readonly type: "terminal"; readonly outcome: Terminal; } /** * IDENTITY (the three-identity model, R-E 0.1.43 / ADR-0047): an invocation * carries THREE ids — `callId` (provider correlation only, may repeat across * runs), `invocationSeq` (the framework identity = the call's tool_call_end * .seq; OPTIONAL — absent on old logs, present on everything written from * 0.1.43), and `executionId` (a specific execution of that invocation, one * per started event). Old logs keep the callId + seq-proximity fallback. * * The union. Consume it with a `switch (event.type)`; with * `strictNullChecks` on, an unhandled variant is a compile error. */ export type Event = AssistantStart | AssistantEnd | TextStart | TextDelta | TextEnd | ToolCallStart | ToolCallInputDelta | ToolCallEnd | ToolResultEvent | Thinking | Usage | Stop | UserInputEvent | CompactedEvent | ToolExecutionStarted | ToolExecutionSucceeded | ToolExecutionFailed | ToolExecutionResolved | PermissionRequested | PermissionDecided | PermissionExpired | UncertainPending | UserInputReplaced | MicroCompactEvent | SummarizedEvent | ModelOutputAbandoned | TerminalEvent; /** * Runtime type guard for the union. The store validates every JSONL record * with it: valid JSON that is not a kiso event is corruption, not history. */ export declare function isKisoEvent(value: unknown): value is Event;