<!-- Generated by scripts/gen-cordis-catalog.ts — do not edit by hand.
     Run `pnpm run gen-cordis-catalog` to regenerate. -->

# Cordis Events Catalog

Every cordis event a plugin can listen to: exact signature, dispatch mode, and the declaration's JSDoc. This is one axis of the **wiring** reference a plugin author works against — the callable `ctx.<key>` surface is the sibling [services catalog](services.md), and [core-data-structures/](../core-data-structures/core.md) catalogs the *data structures* these signatures move around.

This file is GENERATED from source (`scripts/gen-cordis-catalog.ts`) and verified fresh by `pnpm run verify-cordis-catalog` (part of `doc-sync`) — do not edit it by hand. Signature blocks use a `ts cordis-catalog` fence (skipped by doc-typecheck, since a bare signature is not standalone-compilable). Type names in a signature link to the page that documents them.

The **harness tier** below (the `@deepseek-ai/dsh-*` packages) is the vocabulary this repo owns, grouped by scope. The **inherited tier** at the end is the cordis-core + loader/hmr/timer event surface a plugin also sees — pinned vendor source, summarized tersely.

Dispatch modes: **emit** (fire-and-forget), **waterfall** (each listener gets `next()` and may transform or veto — see [waterfall semantics](../cordis-primer.md#cordis-waterfall-semantics)), **parallel** (awaited fan-out; all listeners run), **serial** (awaited in registration order until one returns a bail value — anything other than `null`, `false`, or `undefined`).

## `agent/*`

### `agent/created` — emit

An agent was registered in the AgentRegistry and is ready to receive messages.

```ts cordis-catalog
'agent/created'(agent: Agent): void
```

Types: [Agent](../core-data-structures/core.md)

Source: [`packages/core/agent/src/types.ts:264`](../../packages/core/agent/src/types.ts)

### `agent/disposed` — emit

An agent was disposed and removed from the registry; its fiber and any in-flight turn have been torn down.

```ts cordis-catalog
'agent/disposed'(agent: Agent): void
```

Types: [Agent](../core-data-structures/core.md)

Source: [`packages/core/agent/src/types.ts:271`](../../packages/core/agent/src/types.ts)

### `agent/error` — emit

A step or turn errored. The loop reports a failure here (plus the logger) even when the error has no in-turn position for a session `error` event.

```ts cordis-catalog
'agent/error'(agent: Agent, turn: number, step: number, error: Error): void
```

Types: [Agent](../core-data-structures/core.md)

Source: [`packages/core/agent/src/types.ts:420`](../../packages/core/agent/src/types.ts)

### `agent/pre-step` — serial

Awaited pre-step surface-mutation checkpoint, fired once per step AFTER `turn/start` (and after the prior step closed) but BEFORE this step's `step/start` — so anything a listener appends lands OUTSIDE the step, between `turn/start`/`step/end` and the upcoming `step/start`. `step` is the number of the step about to start. The loop awaits `ctx.serial('agent/pre-step', …)` after assembling the system prompt, then opens the step and derives the request history ONCE from whatever the surface now holds. This is where compaction belongs: it mutates the session surface in place (shadowing an older range with a summary node) with its log-only `compact/*` records cleanly outside any step, and the single subsequent derive reflects the mutation — so there is no double-derive and no listener can see (or be expected to act on) an assembled `messages` array that does not exist yet.

Serial (awaited in registration order), not a waterfall: a listener mutates the surface as a side effect; there is nothing to transform, but the loop must wait for the mutation to complete before opening the step and deriving. Cordis `serial` bails early if a listener returns a bail value; this event is typed and documented as `void`, so listeners must not return a semantic veto value. `fullSystemPrompt` is the assembled prompt a listener needs to measure pressure (the system prompt counts toward the budget). `signal` cancels any in-flight work a listener starts (e.g. a summarization model call).

```ts cordis-catalog
'agent/pre-step'(agent: Agent, turn: number, step: number, fullSystemPrompt: string, signal: AbortSignal): Promise<void> | void
```

Types: [Agent](../core-data-structures/core.md)

Source: [`packages/core/agent/src/types.ts:349`](../../packages/core/agent/src/types.ts)

### `agent/prompt-submit` — waterfall

Waterfall: decide what happens to ONE drained queued message before it becomes a `user/message` — allow (optionally rewriting the prompt bytes or attaching `additionalContext`) or block it. Fires inside the already-open turn, per drained message. Maps onto Claude Code's `UserPromptSubmit` hook. Call `next()` to delegate to the default (allow unchanged), or return a PromptDecision without calling `next()` to short-circuit.

```ts cordis-catalog
'agent/prompt-submit'(agent: Agent, content: ContentBlock[], source: MessageSource, next: () => Promise<PromptDecision>): Promise<PromptDecision>
```

Types: [Agent](../core-data-structures/core.md) · [ContentBlock](../core-data-structures/core.md) · [MessageSource](../core-data-structures/core.md)

Source: [`packages/core/agent/src/types.ts:362`](../../packages/core/agent/src/types.ts)

### `agent/queued` — emit

A message entered the agent's inbox (queued or steering). `source` is the resolved source (defaults applied), not the caller's raw options.

```ts cordis-catalog
'agent/queued'(agent: Agent, content: ContentBlock[], info: { source: MessageSource; steering: boolean }): void
```

Types: [Agent](../core-data-structures/core.md) · [ContentBlock](../core-data-structures/core.md) · [MessageSource](../core-data-structures/core.md)

Source: [`packages/core/agent/src/types.ts:289`](../../packages/core/agent/src/types.ts)

### `agent/request` — waterfall

Waterfall: shape the step's call configuration — model switching, sampling overrides — by returning a replacement LlmCallConfig (the frozen seed is the config the loop would otherwise use). Config is ALL a listener shapes here: every request is a pure function of the session log (the reconstructability RFC), so model-visible content flows through the log channels — `inject()`, steering, prompt-submit `additionalContext`, prompt sections via `system-prompt/assemble` — never through request mutation, and the loop records whatever config the request actually uses as a `request/header*` event before dispatch. The step's messages are already snapshotted when this fires (the `step/start` boundary): an `inject()` from a listener here lands in the log but joins the NEXT request. For surface mutation that must precede the snapshot (compaction), use agent/pre-step. Call `next()` to delegate, or return an LlmCallConfig without it to short-circuit.

```ts cordis-catalog
'agent/request'(agent: Agent, turn: number, step: number, config: LlmCallConfig, next: () => Promise<LlmCallConfig>): Promise<LlmCallConfig>
```

Types: [Agent](../core-data-structures/core.md) · [LlmCallConfig](../core-data-structures/core.md)

Source: [`packages/core/agent/src/types.ts:385`](../../packages/core/agent/src/types.ts)

### `agent/session-start` — emit

The agent's session lifecycle began, fired once before its first turn. `source` says why (SessionStartSource: fresh startup, a resumed persisted session, …). A pure NOTIFICATION (emit, not waterfall): it carries no veto — a session-start listener that wants to seed context does so via `agent.inject()` (a `context/message` the first request sees), not by returning a decision. Cannot block the session from starting; that gap is deliberate (a bridge logs/injects, it does not gate startup).

```ts cordis-catalog
'agent/session-start'(agent: Agent, source: SessionStartSource): void
```

Types: [Agent](../core-data-structures/core.md)

Source: [`packages/core/agent/src/types.ts:304`](../../packages/core/agent/src/types.ts)

### `agent/status` — emit

Agent status changed (`idle` ⇄ `running`, or → `disposed`). Drive lifecycle off this transition, never off a status you just requested — `send()` does not flip status to `running` before it returns.

```ts cordis-catalog
'agent/status'(agent: Agent, status: AgentStatus): void
```

Types: [Agent](../core-data-structures/core.md)

Source: [`packages/core/agent/src/types.ts:280`](../../packages/core/agent/src/types.ts)

### `agent/step-result` — waterfall

Waterfall: post-process the assembled assistant Message before tool dispatch (validation, content rewriting, …).

```ts cordis-catalog
'agent/step-result'(agent: Agent, turn: number, step: number, message: Message, next: () => Promise<Message>): Promise<Message>
```

Types: [Agent](../core-data-structures/core.md) · [Message](../core-data-structures/core.md)

Source: [`packages/core/agent/src/types.ts:395`](../../packages/core/agent/src/types.ts)

### `agent/turn-continuation` — waterfall

Waterfall: override the turn-continuation decision via a typed ContinuationDecision. The loop's `defaultDecision` is `continue` when the step had tool calls or steering was injected, else `stop`. Listeners force-continue (`/goal`, `/loop` — optionally attaching a `reason` recorded as next-step steering) or force-stop (budget guards). Call `next()` to delegate to the default, or return a decision to override.

```ts cordis-catalog
'agent/turn-continuation'(agent: Agent, turn: number, defaultDecision: ContinuationDecision, next: () => Promise<ContinuationDecision>): Promise<ContinuationDecision>
```

Types: [Agent](../core-data-structures/core.md)

Source: [`packages/core/agent/src/types.ts:408`](../../packages/core/agent/src/types.ts)

## `fs/*`

### `fs/edit-intent` — waterfall

Single-slot decision: produce the optional version guard for the next FileSystem.editText. The tool dispatches this as an unbound waterfall and supplies a default thunk returning `undefined` (unconditional edit of the current content — the bare provider; no `stat`). The `@deepseek-ai/dsh-fs-policy` policy listener returns `{ version: vObserved }`, or throws `FS_NOT_OBSERVED` if the actor is unset or has not observed the target. Does NOT call `next()`: one decision, first-wins (see Events.'fs/write-intent').

```ts cordis-catalog
'fs/edit-intent'(target: FsTarget, actor: object | undefined, next: () => { version: FsVersion } | undefined | Promise<{ version: FsVersion } | undefined>): Promise<{ version: FsVersion } | undefined>
```

Types: [FsTarget](../core-data-structures/filesystem.md) · [FsVersion](../core-data-structures/filesystem.md)

Source: [`packages/fs/fs/src/index.ts:123`](../../packages/fs/fs/src/index.ts)

### `fs/observed` — emit

Record that an actor observed a target at a version, after a successful read/write/edit. Fire-and-forget (plain `emit`). A listener MUST be a synchronous, side-effect-only recorder (`@deepseek-ai/dsh-fs-policy`'s is a `WeakMap.set`): the tool does not guard the emit, so a listener that throws surfaces as the tool's `isError` result, and cordis `emit` does not await listener promises — async or fallible audit/telemetry does not belong here. No listener ⇒ nothing recorded. `actor` is the opaque tool-execution context.

```ts cordis-catalog
'fs/observed'(target: FsTarget, version: FsVersion, actor: object | undefined): void
```

Types: [FsTarget](../core-data-structures/filesystem.md) · [FsVersion](../core-data-structures/filesystem.md)

Source: [`packages/fs/fs/src/index.ts:138`](../../packages/fs/fs/src/index.ts)

### `fs/write-intent` — waterfall

Single-slot decision: produce the write intent for the next FileSystem.writeText. The tool dispatches this as an unbound waterfall (no `this`) and supplies a default thunk returning `undefined` (unconditional create-or-overwrite — the bare provider). The `@deepseek-ai/dsh-fs-policy` policy listener returns `createIfAbsent` (unobserved actor) or `{ kind: 'replaceIfVersion', version: vObserved }` (observed) and does NOT call `next()` — one decision, not a composable chain. The slot is first-wins: the first non-`next()` decider (registration order, or `prepend`) occupies it; a second decider is a misconfiguration, not layering. `actor` is the opaque tool-execution context, never read here.

```ts cordis-catalog
'fs/write-intent'(target: FsTarget, actor: object | undefined, next: () => FsWriteIntent | undefined | Promise<FsWriteIntent | undefined>): Promise<FsWriteIntent | undefined>
```

Types: [FsTarget](../core-data-structures/filesystem.md) · [FsWriteIntent](../core-data-structures/filesystem.md)

Source: [`packages/fs/fs/src/index.ts:109`](../../packages/fs/fs/src/index.ts)

## `llm/*`

### `llm/stream` — waterfall

Waterfall around every streaming model call (retry, replay, routing). Bound to the LlmService; call `next()` to reach the resolved adapter's stream, or yield your own chunks to short-circuit.

```ts cordis-catalog
'llm/stream'(this: LlmService, options: GenerateOptions, next: () => AsyncIterable<StreamChunk>): AsyncIterable<StreamChunk>
```

Types: [GenerateOptions](../core-data-structures/core.md) · [StreamChunk](../core-data-structures/llm-streaming.md)

Source: [`packages/llm/llm/src/index.ts:39`](../../packages/llm/llm/src/index.ts)

## `session/*`

### `session/created` — emit

A session was created in the store.

```ts cordis-catalog
'session/created'(session: Session): void
```

Source: [`packages/core/session/src/index.ts:39`](../../packages/core/session/src/index.ts)

### `session/event` — emit

An event was appended to a session log (sync, fire-and-forget). This is the per-append feed a UI or invariant plugin tails.

```ts cordis-catalog
'session/event'(session: Session, event: SessionEvent): void
```

Types: [SessionEvent](../core-data-structures/core.md)

Source: [`packages/core/session/src/index.ts:47`](../../packages/core/session/src/index.ts)

### `session/flush` — parallel

Awaited durability checkpoint. The agent loop awaits `ctx.parallel('session/flush', session)` at every turn end; persistence plugins (JSONL, SQLite) drain their write-behind buffers here and on fiber dispose. Awaited (parallel), not a waterfall: every listener runs and the loop waits for all of them, but none can veto.

```ts cordis-catalog
'session/flush'(session: Session): Promise<void> | void
```

Source: [`packages/core/session/src/index.ts:57`](../../packages/core/session/src/index.ts)

## `subagent/*`

### `subagent/end` — emit

A subagent run settled — emitted when SubagentRun.result resolves (any stop reason). Paired with Events['subagent/start'].

```ts cordis-catalog
'subagent/end'(info: SubagentRunEndInfo): void
```

Source: [`packages/subagent/subagent/src/index.ts:98`](../../packages/subagent/subagent/src/index.ts)

### `subagent/provider-added` — emit

A provider became resolvable in the SubagentService registry. Consumers that derive state from a named provider (e.g. the model-facing tool wording in `dsh-tool-subagent`) react HERE instead of assuming load order — the cordis Loader starts sibling plugins concurrently, so "listed earlier in cordis.yml" does not mean "registered earlier".

```ts cordis-catalog
'subagent/provider-added'(provider: SubagentProvider): void
```

Source: [`packages/subagent/subagent/src/index.ts:72`](../../packages/subagent/subagent/src/index.ts)

### `subagent/provider-removed` — emit

A provider left the registry (its plugin's fiber was disposed — an unload or an HMR reload). Consumers holding provider-derived state drop it here; a reload re-fires `subagent/provider-added` with the fresh provider. Delivered with per-listener containment: a throwing subscriber is logged, never starves later subscribers, and never disrupts the provider's teardown.

```ts cordis-catalog
'subagent/provider-removed'(name: string): void
```

Source: [`packages/subagent/subagent/src/index.ts:83`](../../packages/subagent/subagent/src/index.ts)

### `subagent/start` — emit

A subagent run started — emitted after the provider is resolved and its capabilities validated, as the child run begins. Paired with Events['subagent/end'].

```ts cordis-catalog
'subagent/start'(info: SubagentRunInfo): void
```

Source: [`packages/subagent/subagent/src/index.ts:91`](../../packages/subagent/subagent/src/index.ts)

## `system-prompt/*`

### `system-prompt/assemble` — waterfall

Waterfall around prompt assembly — mutate or extend the PromptAssembly (sections + tools + variables) before it is rendered. Bound to the SystemPrompt service; call `next()` to delegate.

```ts cordis-catalog
'system-prompt/assemble'(this: SystemPrompt, assembly: PromptAssembly, context: AssembleContext, next: () => Promise<PromptAssembly>): Promise<PromptAssembly>
```

Source: [`packages/core/system-prompt/src/index.ts:38`](../../packages/core/system-prompt/src/index.ts)

### `system-prompt/change` — emit

A section, tool provider, or variable provider was registered or unregistered (the assembly inputs changed).

```ts cordis-catalog
'system-prompt/change'(): void
```

Source: [`packages/core/system-prompt/src/index.ts:44`](../../packages/core/system-prompt/src/index.ts)

## `tools/*`

### `tools/change` — emit

A tool was registered or unregistered (the available tool set changed).

```ts cordis-catalog
'tools/change'(): void
```

Source: [`packages/core/tools/src/index.ts:111`](../../packages/core/tools/src/index.ts)

### `tools/post-execute` — waterfall

Waterfall AFTER a tool runs — where hook plugins inspect the result and accept it (optionally REPLACING the model-facing content, and/or attaching `additionalContext` for the next request) or block it with corrective `feedback` (Claude Code's `PostToolUse`). Listeners receive `(exec, result, next)`: call `next()` to delegate to the default (accept unchanged), or return a PostToolDecision to override. The core tool dispatch sits between the two waterfalls as plain code, all inside `execute`'s outer try/catch (and the tool body keeps its own inner try/catch, so a thrown tool still reaches `post-execute` as an `isError` result).

```ts cordis-catalog
'tools/post-execute'(this: ToolRegistry, exec: ToolExecution, result: ToolExecutionResult, next: () => Promise<PostToolDecision>): Promise<PostToolDecision>
```

Types: [ToolExecution](../core-data-structures/tools.md) · [ToolExecutionResult](../core-data-structures/tools.md)

Source: [`packages/core/tools/src/index.ts:106`](../../packages/core/tools/src/index.ts)

### `tools/pre-execute` — waterfall

Waterfall BEFORE a tool runs — the gate where sandbox, permission, and hook plugins allow or deny a call (Claude Code's `PreToolUse`). Listeners receive `(exec, next)`: call `next()` to delegate to the default (allow), or return a PreToolDecision without calling `next()` to short-circuit. A `deny` skips dispatch and yields an `isError` result; the tool body never runs. Input rewrite is deliberately NOT offered here (see PreToolDecision); `ask` degrades to deny until the permission system lands (`FIXME(permissions)`).

```ts cordis-catalog
'tools/pre-execute'(this: ToolRegistry, exec: ToolExecution, next: () => Promise<PreToolDecision>): Promise<PreToolDecision>
```

Types: [ToolExecution](../core-data-structures/tools.md)

Source: [`packages/core/tools/src/index.ts:90`](../../packages/core/tools/src/index.ts)

## Inherited events (cordis core + loader/hmr/timer)

The framework events every plugin also sees, beyond the harness vocabulary above. This is pinned vendor source ([vendoring policy](../../vendor/README.md)); it is summarized here so the page is a complete picture of the event bus, without elevating framework internals to the harness tier's prominence.

- `internal/plugin` — A plugin fiber was created. ([`vendor/cordis/src/events.ts:197`](../../vendor/cordis/src/events.ts))
- `internal/status` — A fiber changed lifecycle state. ([`vendor/cordis/src/events.ts:198`](../../vendor/cordis/src/events.ts))
- `internal/service` — Interception hook for a service binding (no core producer). ([`vendor/cordis/src/events.ts:199`](../../vendor/cordis/src/events.ts))
- `internal/update` — Waterfall: a fiber config update is being applied. ([`vendor/cordis/src/events.ts:200`](../../vendor/cordis/src/events.ts))
- `internal/get` — Waterfall: a service is being read from the store. ([`vendor/cordis/src/events.ts:201`](../../vendor/cordis/src/events.ts))
- `internal/set` — Waterfall: a service is being written to the store. ([`vendor/cordis/src/events.ts:202`](../../vendor/cordis/src/events.ts))
- `internal/listener` — A listener was registered. ([`vendor/cordis/src/events.ts:203`](../../vendor/cordis/src/events.ts))
- `internal/dispatch` — An event is being dispatched to listeners. ([`vendor/cordis/src/events.ts:204`](../../vendor/cordis/src/events.ts))
- `hmr/change` — A watched source file changed on disk. ([`vendor/hmr/src/index.ts:20`](../../vendor/hmr/src/index.ts))
- `hmr/reload` — Plugins are being reloaded after a change. ([`vendor/hmr/src/index.ts:21`](../../vendor/hmr/src/index.ts))
- `exit` — The process is exiting on a signal. ([`vendor/loader/src/index.ts:23`](../../vendor/loader/src/index.ts))
- `loader/config-update` — The loader config tree changed. ([`vendor/loader/src/index.ts:24`](../../vendor/loader/src/index.ts))
- `loader/entry-init` — A config entry is being initialized. ([`vendor/loader/src/index.ts:25`](../../vendor/loader/src/index.ts))
- `loader/partial-dispose` — An entry is being partially disposed on reload. ([`vendor/loader/src/index.ts:26`](../../vendor/loader/src/index.ts))
- `loader/patch-context` — A context is being patched during a reload. ([`vendor/loader/src/index.ts:27`](../../vendor/loader/src/index.ts))
