docs/persistence-catalog/log-events.md enumerates every SessionEventMap member — the owning dsh-session vocabulary plus the dsh-compact and dsh-hook-protocol declaration merges — with payload, surface/log-only badge, JSDoc prose, and declaration site. scripts/gen-persistence-catalog.ts is a pure AST pass in the gen-cordis-catalog mold: verify-persistence-catalog (--check) joins doc-sync, so a stale committed catalog fails pre-push and CI. The walk enforces JSDoc completeness (every member needs description prose; @mode is rejected as a category error — log events do not dispatch on the cordis bus), derives the surface badge from the SurfaceEventType union with a stale-member cross-check, and hard-errors on duplicate declarations. Payloads render through the TypeScript printer so newline-separated multi-line type literals still emit valid one-line fragments. Documented the five previously JSDoc-less core events (turn/step boundaries, tool/call), removed the two stray @mode tags on the hook/* merges, and replaced the hand-restated event enumerations (session.md hook/* table, compact README table, hook-protocol README bullets, session README name-list — whose merge note had already drifted) with links to the catalog. RFC: docs/rfc/implemented/process/2026-07-04-persistence-log-catalog.md.
12 KiB
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 (the SessionEvent envelope, surface list, and deriveMessages() projection), persistence.md (how the log is made durable), and the cordis catalog (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.
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). 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). Scope: the packages in this repo; a downstream plugin can merge further event types, which are outside this catalog by construction.
Events
assistant/*
assistant/chunk — log-only
Raw stream chunk — token-level replay fidelity.
'assistant/chunk': { turn: number; step: number; chunk: StreamChunk }
Types: StreamChunk
Source: packages/core/session/src/types.ts:238
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.
'assistant/message': { turn: number; step: number; content: ContentBlock[]; usage?: TokenUsage }
Types: ContentBlock · TokenUsage
Source: packages/core/session/src/types.ts:245
compact/*
compact/end — log-only
Marks the end of a compaction — log-only, releases the lock. error set if summarization failed.
'compact/end': { turn: number; error?: string }
Source: packages/compact/compact/src/types.ts:37
compact/start — log-only
Marks the start of a compaction — log-only, holds the lock until compact/end.
'compact/start': { turn: number }
Source: packages/compact/compact/src/types.ts:23
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.
'compact/summary': { summary: ContentBlock[]; shadowedRange: { start: number; end: number }; shadowedSeqs: number[]; shadowedTokenCount: number }
Types: ContentBlock
Source: packages/compact/compact/src/types.ts:30
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.
'context/message': { content: ContentBlock[]; source: MessageSource }
Types: ContentBlock · MessageSource
Source: packages/core/session/src/types.ts:236
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/native), 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.
'hook/invoked': { turn: number; point: string; dialect: HookDialect; matcher?: string; handlerId: string }
Source: packages/hooks/hook-protocol/src/types.ts:27
hook/result — log-only
A hook command's outcome — log-only, paired with a prior hook/invoked (same handlerId). decision is the resolved dialect-neutral outcome the bridge mapped it to (allow/deny/ask/block/continue/stop/pass), exitCode the process exit (absent if it never ran), stderrSummary a truncated stderr (the block reason source on exit 2), durationMs the wall time. turn matches the hook/invoked.
'hook/result': { turn: number; point: string; handlerId: string; decision: string; exitCode?: number; stderrSummary?: string; durationMs: number }
Source: packages/hooks/hook-protocol/src/types.ts:42
prompt/*
prompt/blocked — log-only
A queued prompt an agent/prompt-submit listener VETOED — the durable record of a blocked prompt and why. Appended in place of the user/message the prompt would have become, so the block survives replay even in a MIXED batch where another queued prompt is allowed (there the turn does not end rejected, so the boundary reason alone would not preserve it). content is the original prompt the listener rejected; reason is the veto text (PromptDecision block.reason). NOT a SurfaceEventType: a blocked prompt produces no LLM message and never reaches deriveMessages().
'prompt/blocked': { content: ContentBlock[]; source: MessageSource; reason: string }
Types: ContentBlock · MessageSource
Source: packages/core/session/src/types.ts:230
steering/*
steering/message — surface
Steering content injected between steps of a running turn.
'steering/message': { turn: number; content: ContentBlock[]; source: MessageSource }
Types: ContentBlock · MessageSource
Source: packages/core/session/src/types.ts:263
step/*
step/end — log-only
Closes step step of turn turn.
'step/end': { turn: number; step: number }
Source: packages/core/session/src/types.ts:217
step/start — log-only
Opens step step of turn turn — one model call plus the tool executions it requested.
'step/start': { turn: number; step: number }
Source: packages/core/session/src/types.ts:215
todo/*
todo/write — log-only
The agent's whole todo list, carried as a full snapshot and replaced wholesale on each write — the current list is the most recent todo/write (last-write-wins on replay, no fold). Appended by an owning agent via session.append('todo/write', { todos }).
NOT a SurfaceEventType: it produces no LLM message and never reaches deriveMessages(), so it carries no surfaceOp and stays off the surface — it is durable, replayable UI state, distinct from the conversation history. It is a SessionEventMap member riding the existing session/event emit, not a first-class Cordis interface Events notification, so it has no cordis-catalog row.
'todo/write': { todos: TodoItem[] }
Types: TodoItem
Source: packages/core/session/src/types.ts:277
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.
'tool/call': { turn: number; step: number; callId: CallId; name: string; arguments: string }
Types: CallId
Source: packages/core/session/src/types.ts:251
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).
'tool/result': { turn: number; step: number; callId: CallId; content: ContentBlock[]; isError: boolean; error?: { name: string; code: string }; meta?: unknown }
Types: CallId · ContentBlock
Source: packages/core/session/src/types.ts:261
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.
'turn/end': { turn: number; reason: TurnEndReason }
Types: TurnEndReason
Source: packages/core/session/src/types.ts:213
turn/start — log-only
Opens turn turn. trigger records what started it — a drained message batch, a continuation, 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).
'turn/start': { turn: number; trigger: TurnTrigger }
Types: TurnTrigger
Source: packages/core/session/src/types.ts:207
user/*
user/message — surface
A user-visible prompt (queued message drained at turn start).
'user/message': { content: ContentBlock[]; source: MessageSource }
Types: ContentBlock · MessageSource