# Persistence Log Event Catalog Every event type that can appear in a session's durable event log: each member of the merge-extensible `SessionEventMap` — the owning vocabulary in `@deepseek-ai/dsh-session` plus every plugin declaration merge in this repo — with the payload it carries, its surface badge, and the declaration it comes from. It complements [session.md](core-data-structures/session.md) (the `SessionEvent` envelope, surface list, and `deriveMessages()` projection), [persistence.md](core-data-structures/persistence.md) (how the log is made durable), and the [cordis events catalog](cordis-catalog/events.md) (the live bus wiring — a log event is NOT a cordis event; it reaches listeners via the single `session/event` emit). This file is GENERATED from source (`scripts/gen-persistence-catalog.ts`) and verified fresh by `pnpm run verify-persistence-catalog` (part of `doc-sync`) — do not edit it by hand. Payload blocks use a `ts persistence-catalog` fence (skipped by doc-typecheck, since a bare payload fragment is not standalone-compilable). Type names in a payload link to the page that documents them. See [the persistence-log-catalog RFC](rfc/implemented/process/2026-07-04-persistence-log-catalog.md). The on-disk envelope around every payload is `SessionEvent` — `type`, monotonic `seq`, epoch-ms `time`, the `data` documented here, plus `surfaceOp`/`sourceEventSeqs` on **surface** events only ([envelope](core-data-structures/session.md#sessioneventt--one-log-entry)). **surface** marks a `SurfaceEventType` member: it produces an LLM message and declares how it joins the surface list. **log-only** marks everything else: durable, replayable record with no derived-history contribution. Every payload is JSON-serializable (enforced at `Session.append`), and the whole format is pinned at `SESSION_FORMAT_VERSION = 0` — pre-release, no compatibility implied ([the version stance](core-data-structures/persistence.md)). Scope: the packages in this repo; a downstream plugin can merge further event types, which are outside this catalog by construction. ## Events ### `approval/*` #### `approval/asked` — log-only An approval question was put to the answerer chain — log-only audit (like `hook/*`; NOT a surface event, carries no `surfaceOp`). `id` pairs it with the `approval/decided` that always follows; `toolName` is the tool the question is about, `callId` the exact tool call when the asker had one, `reason` the asker's human-readable explanation (e.g. a hook's permission-decision reason). ```ts persistence-catalog 'approval/asked': { id: ApprovalRequestId; toolName: string; callId?: CallId; reason?: string } ``` Types: [CallId](core-data-structures/core.md) Source: [`packages/ui/user-approval/src/index.ts:45`](../packages/ui/user-approval/src/index.ts) #### `approval/decided` — log-only The outcome of a prior `approval/asked` (same `id`) — log-only audit. Exactly one per ask, appended when the outcome is known: a decision, a cancellation, or the fail-closed `'unavailable'`. ```ts persistence-catalog 'approval/decided': { id: ApprovalRequestId; outcome: ApprovalOutcome } ``` Source: [`packages/ui/user-approval/src/index.ts:56`](../packages/ui/user-approval/src/index.ts) #### `approval/policy` — log-only The session's approval policy was switched — log-only, durable, replayable, never in the model transcript (the model learns the policy from the prompt section and the narrator's notices). The LAST such event is the session's override (effectiveApprovalPolicy); who asked for it is derivable from position (an event after the log's last `request/header*` was a runtime switch by the user). ```ts persistence-catalog 'approval/policy': { policy: ApprovalPolicy } ``` Source: [`packages/ui/user-approval/src/index.ts:68`](../packages/ui/user-approval/src/index.ts) ### `assistant/*` #### `assistant/chunk` — log-only Raw stream chunk — token-level replay fidelity. ```ts persistence-catalog 'assistant/chunk': { turn: number; step: number; chunk: StreamChunk } ``` Types: [StreamChunk](core-data-structures/llm-streaming.md) Source: [`packages/core/session/src/types.ts:242`](../packages/core/session/src/types.ts) #### `assistant/message` — surface Assembled assistant message for one step (derived history uses this). Carries the step's `usage` when the adapter reported token accounting, so the model output and its accounting travel together (there is no separate usage record). `usage` is absent when the adapter reported none. ```ts persistence-catalog 'assistant/message': { turn: number; step: number; content: ContentBlock[]; usage?: TokenUsage } ``` Types: [ContentBlock](core-data-structures/core.md) · [TokenUsage](core-data-structures/llm-streaming.md) Source: [`packages/core/session/src/types.ts:249`](../packages/core/session/src/types.ts) ### `compact/*` #### `compact/end` — log-only Marks the end of a compaction — log-only, releases the lock. `error` set if summarization failed. ```ts persistence-catalog 'compact/end': { turn: number; error?: string } ``` Source: [`packages/compact/compact/src/types.ts:38`](../packages/compact/compact/src/types.ts) #### `compact/start` — log-only Marks the start of a compaction — log-only, holds the lock until `compact/end`. ```ts persistence-catalog 'compact/start': { turn: number } ``` Source: [`packages/compact/compact/src/types.ts:15`](../packages/compact/compact/src/types.ts) #### `compact/summary` — log-only Provenance record of a completed summarization — log-only, no surfaceOp. The summary content is in `data.summary`; the actual surface replacement is performed by a subsequent `user/message` event that shadows the compacted range. ```ts persistence-catalog 'compact/summary': { summary: ContentBlock[]; shadowedRange: { start: number; end: number }; shadowedSeqs: number[]; shadowedTokenCount: number; model: string; maxTokens?: number } ``` Types: [ContentBlock](core-data-structures/core.md) Source: [`packages/compact/compact/src/types.ts:22`](../packages/compact/compact/src/types.ts) ### `context/*` #### `context/message` — surface In-session context injection (file-change notices, subdir AGENTS.md, skill content, cron notifications, …). Rendered into the derived history as tagged synthetic context — NOT a user prompt. ```ts persistence-catalog 'context/message': { content: ContentBlock[]; source: MessageSource } ``` Types: [ContentBlock](core-data-structures/core.md) · [MessageSource](core-data-structures/core.md) Source: [`packages/core/session/src/types.ts:240`](../packages/core/session/src/types.ts) ### `hook/*` #### `hook/invoked` — log-only A hook command was invoked at a hook point — log-only provenance (like `compact/*`; NOT a SurfaceEventType, carries no `surfaceOp`). `dialect` is the bridge that ran it (`claude`/`codex`), `point` the hook point (`PreToolUse`, `Stop`, …), `matcher` the matcher-group pattern that selected it (absent for match-all), `handlerId` a stable id for the command (so an invoked/result pair correlates). `turn` is the open turn the invocation lives inside. ```ts persistence-catalog 'hook/invoked': { turn: number; point: string; dialect: HookDialect; matcher?: string; handlerId: string } ``` Source: [`packages/hooks/hook-protocol/src/types.ts:19`](../packages/hooks/hook-protocol/src/types.ts) #### `hook/result` — log-only Log-only outcome paired to `hook/invoked` by `handlerId`. Decision is the parsed permission result, `stop` for `continue:false`, or `pass`; exit code may be absent, stderr is bounded, and duration is wall-clock runtime. ```ts persistence-catalog 'hook/result': { turn: number; point: string; handlerId: string; decision: string; exitCode?: number; stderrSummary?: string; durationMs: number } ``` Source: [`packages/hooks/hook-protocol/src/types.ts:31`](../packages/hooks/hook-protocol/src/types.ts) ### `permission/*` #### `permission/preset` — log-only Records the selected preset as durable, log-only user intent. The knob events follow in the same turn and control execution; this event stays out of the model transcript and lets effectivePermissionPreset preserve a selection when bundles match. ```ts persistence-catalog 'permission/preset': { preset: string } ``` Source: [`packages/ui/permission/src/index.ts:36`](../packages/ui/permission/src/index.ts) ### `prompt/*` #### `prompt/blocked` — log-only Durable record of a prompt veto and its reason. It is log-only: the blocked prompt never enters the model-visible surface, including in a mixed batch. ```ts persistence-catalog 'prompt/blocked': { content: ContentBlock[]; source: MessageSource; reason: string } ``` Types: [ContentBlock](core-data-structures/core.md) · [MessageSource](core-data-structures/core.md) Source: [`packages/core/session/src/types.ts:234`](../packages/core/session/src/types.ts) ### `request/*` #### `request/header` — log-only Full EpochHeader for the next request, appended inside its step before dispatch. It is log-only and anchors subsequent deltas. ```ts persistence-catalog 'request/header': { header: EpochHeader; reason: RequestHeaderReason } ``` Source: [`packages/core/session/src/types.ts:277`](../packages/core/session/src/types.ts) #### `request/header-delta` — log-only Log-only amendment to the folded EpochHeader. System and tools use their delta codecs; config and prefix replace whole, with an empty prefix encoding removal. Writers verify round-trip equality or log a fallback snapshot. ```ts persistence-catalog 'request/header-delta': { system?: SystemDelta; tools?: ToolsDelta; config?: LlmCallConfig; messagePrefix?: Message[] } ``` Source: [`packages/core/session/src/types.ts:283`](../packages/core/session/src/types.ts) ### `sandbox/*` #### `sandbox/mode` — log-only The session's sandbox mode was switched — log-only (like `approval/*`; NOT a surface event, carries no `surfaceOp`): durable and replayable, never in the model transcript. The LAST such event is the session's override (effectiveSandboxMode); who asked for it is derivable from position (an event after the log's last `request/header*` was a runtime switch by the user; see the tool layer's narrator). ```ts persistence-catalog 'sandbox/mode': { mode: SandboxMode } ``` Source: [`packages/sandbox/sandbox-policy/src/session-mode.ts:34`](../packages/sandbox/sandbox-policy/src/session-mode.ts) ### `steering/*` #### `steering/message` — surface Steering content injected between steps of a running turn. ```ts persistence-catalog 'steering/message': { turn: number; content: ContentBlock[]; source: MessageSource } ``` Types: [ContentBlock](core-data-structures/core.md) · [MessageSource](core-data-structures/core.md) Source: [`packages/core/session/src/types.ts:267`](../packages/core/session/src/types.ts) ### `step/*` #### `step/end` — log-only Closes step `step` of turn `turn`. ```ts persistence-catalog 'step/end': { turn: number; step: number } ``` Source: [`packages/core/session/src/types.ts:227`](../packages/core/session/src/types.ts) #### `step/start` — log-only Opens step `step` of turn `turn` — one model call plus the tool executions it requested. ```ts persistence-catalog 'step/start': { turn: number; step: number } ``` Source: [`packages/core/session/src/types.ts:225`](../packages/core/session/src/types.ts) ### `todo/*` #### `todo/write` — log-only Whole-list snapshot; the latest write wins on replay. It is log-only UI state and never enters derived model history. ```ts persistence-catalog 'todo/write': { todos: TodoItem[] } ``` Types: [TodoItem](core-data-structures/session.md) Source: [`packages/core/session/src/types.ts:272`](../packages/core/session/src/types.ts) ### `tool/*` #### `tool/call` — log-only The model requested one tool invocation: `name` with the raw `arguments` JSON string exactly as the model produced it (unparsed). `callId` pairs the call with its `tool/result`. ```ts persistence-catalog 'tool/call': { turn: number; step: number; callId: CallId; name: string; arguments: string } ``` Types: [CallId](core-data-structures/core.md) Source: [`packages/core/session/src/types.ts:255`](../packages/core/session/src/types.ts) #### `tool/code-dispatch` — log-only One bridged sub-dispatch from a `run_code` program: the parent `run_code` call id, the deterministic sub-call id (`:code:`), the tool `name` with its JSON-normalized `arguments` — the exact value dispatched, normalized before dispatch, so this append can never fail on payload shape — whether the sub-call errored, and a bounded `resultSummary` of its model-facing text. ```ts persistence-catalog 'tool/code-dispatch': { parentCallId: CallId; subCallId: CallId; name: string; arguments: unknown; isError: boolean; resultSummary: string } ``` Types: [CallId](core-data-structures/core.md) Source: [`packages/core/tools/src/code-mode.ts:25`](../packages/core/tools/src/code-mode.ts) #### `tool/result` — surface A completed tool call's model-facing result, plus an optional tool-private `meta` presentation payload. `meta` is opaque to the core (`unknown` — the producing tool owns its shape and reads it back in `presentResult`) but MUST be JSON-serializable: `Session.append` runtime-validates all event data with `isJsonValue`, so a non-serializable `meta` is rejected at the source, and the durable log reproduces the identical card on replay. Absent unless the tool attaches one (e.g. `dsh-tool-fs` carries its result-time contextual diff here). ```ts persistence-catalog 'tool/result': { turn: number; step: number; callId: CallId; content: ContentBlock[]; isError: boolean; error?: { name: string; code: string }; meta?: unknown } ``` Types: [CallId](core-data-structures/core.md) · [ContentBlock](core-data-structures/core.md) Source: [`packages/core/session/src/types.ts:265`](../packages/core/session/src/types.ts) ### `turn/*` #### `turn/end` — log-only Closes turn `turn` with the TurnEndReason that ended it. The loop fires the awaited `session/flush` checkpoint at every turn end, so the turn boundary is also the durable-commit boundary. ```ts persistence-catalog 'turn/end': { turn: number; reason: TurnEndReason } ``` Types: [TurnEndReason](core-data-structures/session.md) Source: [`packages/core/session/src/types.ts:223`](../packages/core/session/src/types.ts) #### `turn/start` — log-only Opens turn `turn`. `trigger` records what started it — a drained message batch or an idle-time injection. The turn is the durability/replay boundary: every event sits between a `turn/start` and its matching `turn/end` (the turn-enclosure invariant). ```ts persistence-catalog 'turn/start': { turn: number; trigger: TurnTrigger } ``` Types: [TurnTrigger](core-data-structures/session.md) Source: [`packages/core/session/src/types.ts:217`](../packages/core/session/src/types.ts) ### `user/*` #### `user/message` — surface A user-visible prompt (queued message drained at turn start). ```ts persistence-catalog 'user/message': { content: ContentBlock[]; source: MessageSource } ``` Types: [ContentBlock](core-data-structures/core.md) · [MessageSource](core-data-structures/core.md) Source: [`packages/core/session/src/types.ts:229`](../packages/core/session/src/types.ts)