# 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.` 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 A fully configured agent and live session were published. Setup is composition-only; `agent/session-start` is the first startup-driving seam. Synchronous listener failure vetoes publication, while returned-promise rejection is reported. Detach requested during dispatch waits until every creation listener has observed the stable entry. ```ts cordis-catalog 'agent/created'(this: Scoped, agent: Agent): void ``` Types: [Agent](../core-data-structures/core.md) Source: [`packages/core/agent/src/types.ts:141`](../../packages/core/agent/src/types.ts) ### `agent/disposed` — emit An agent left the registry; AgentLoop emits this after driver quiescence but before session detachment and scoped-registration unwind. Custom registry users own their driver-ordering contract. ```ts cordis-catalog 'agent/disposed'(this: Scoped, agent: Agent): void ``` Types: [Agent](../core-data-structures/core.md) Source: [`packages/core/agent/src/types.ts:150`](../../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'(this: Scoped, agent: Agent, turn: number, step: number, error: Error): void ``` Types: [Agent](../core-data-structures/core.md) Source: [`packages/core/agent/src/types.ts:285`](../../packages/core/agent/src/types.ts) ### `agent/pre-step` — serial Awaited serial checkpoint for session-surface mutation after prompt assembly and before `step/start`; appends land outside the pending step. The loop derives history once afterward, so compaction records and replacements are included without rewriting an assembled request. The prompt and prefix are the exact pressure inputs for that request, and `signal` cancels listener work. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. ```ts cordis-catalog 'agent/pre-step'(this: Scoped, agent: Agent, turn: number, step: number, fullSystemPrompt: string, sessionPrefix: readonly Message[], signal: AbortSignal): Promise | void ``` Types: [Agent](../core-data-structures/core.md) · [Message](../core-data-structures/core.md) Source: [`packages/core/agent/src/types.ts:204`](../../packages/core/agent/src/types.ts) ### `agent/prompt-submit` — waterfall Allow, rewrite, or block one drained prompt before it becomes a user message. Call `next()` for the unchanged default. ```ts cordis-catalog 'agent/prompt-submit'(this: Scoped, agent: Agent, content: ContentBlock[], source: MessageSource, next: () => Promise): Promise ``` 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:214`](../../packages/core/agent/src/types.ts) ### `agent/queued` — emit Detached, frozen content entered the agent's inbox. Source defaults have already been applied, so these are the exact values retained for the log. ```ts cordis-catalog 'agent/queued'(this: Scoped, 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:169`](../../packages/core/agent/src/types.ts) ### `agent/request` — waterfall Replace the frozen call configuration. Model-visible content must use logged channels; this seam cannot mutate messages. Injection here joins the next request because the current step boundary is already fixed. ```ts cordis-catalog 'agent/request'(this: Scoped, agent: Agent, turn: number, step: number, config: LlmCallConfig, next: () => Promise): Promise ``` Types: [Agent](../core-data-structures/core.md) · [LlmCallConfig](../core-data-structures/core.md) Source: [`packages/core/agent/src/types.ts:226`](../../packages/core/agent/src/types.ts) ### `agent/session-prefix` — waterfall Compose request-only messages placed before derived history. The frozen result is computed once per loop instance, logged on its anchoring request header, and reused so the provider prefix remains stable. Interrupted composition is discarded. Composition precedes the first `agent/pre-step` and request boundary, so listener appends join the current request and pressure accounting sees the composed prefix. Changing context belongs in history; contributors should prepend to `await next()` to preserve registration order. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. ```ts cordis-catalog 'agent/session-prefix'(this: Scoped, agent: Agent, prefix: Message[], signal: AbortSignal, next: () => Promise): Promise ``` Types: [Agent](../core-data-structures/core.md) · [Message](../core-data-structures/core.md) Source: [`packages/core/agent/src/types.ts:241`](../../packages/core/agent/src/types.ts) ### `agent/session-start` — emit The session lifecycle began, once before the first turn. Use `agent.inject()` to seed model-facing context. This is a notification, not a veto; disposal requested by a lifecycle owner is rechecked before the driver starts. ```ts cordis-catalog 'agent/session-start'(this: Scoped, agent: Agent, source: SessionStartSource): void ``` Types: [Agent](../core-data-structures/core.md) · [SessionStartSource](../core-data-structures/core.md) Source: [`packages/core/agent/src/types.ts:182`](../../packages/core/agent/src/types.ts) ### `agent/status` — emit Agent status changed (`idle` ⇄ `running`, or → `disposed`). `send()` does not enter `running` synchronously; drive lifecycle from this event. ```ts cordis-catalog 'agent/status'(this: Scoped, agent: Agent, status: AgentStatus): void ``` Types: [Agent](../core-data-structures/core.md) Source: [`packages/core/agent/src/types.ts:159`](../../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'(this: Scoped, agent: Agent, turn: number, step: number, message: Message, next: () => Promise): Promise ``` Types: [Agent](../core-data-structures/core.md) · [Message](../core-data-structures/core.md) Source: [`packages/core/agent/src/types.ts:252`](../../packages/core/agent/src/types.ts) ### `agent/turn-continuation` — waterfall Override whether the turn continues. The default continues after tool calls or steering and stops otherwise; a continue reason becomes steering. ```ts cordis-catalog 'agent/turn-continuation'(this: Scoped, agent: Agent, turn: number, defaultDecision: ContinuationDecision, next: () => Promise): Promise ``` Types: [Agent](../core-data-structures/core.md) Source: [`packages/core/agent/src/types.ts:262`](../../packages/core/agent/src/types.ts) ### `agent/turn-stop` — serial Monotonic terminal-stop checkpoint after continuation and steering are folded; a stop remains authoritative through turn close and flush: steering queued in that window is discarded, while ordinary sends survive. ```ts cordis-catalog 'agent/turn-stop'(this: Scoped, agent: Agent, turn: number): ContinuationStop | undefined ``` Types: [Agent](../core-data-structures/core.md) Source: [`packages/core/agent/src/types.ts:272`](../../packages/core/agent/src/types.ts) ## `agent-loop/*` ### `agent-loop/config-start-failed` — emit A declarative agent entry failed before it could publish a live agent. Consumers that buffer work for the configured identity use this transient signal to reject that work instead of waiting forever. Normal factory teardown suppresses failures from the cancelled startup attempt. ```ts cordis-catalog 'agent-loop/config-start-failed'(sessionId: SessionId, error: unknown): void ``` Source: [`packages/core/agent-loop/src/index.ts:362`](../../packages/core/agent-loop/src/index.ts) ## `approval/*` ### `approval/request` — waterfall Ask composed answerers for one decision. Return an outcome to claim the request or call `next()`; failure yields the fail-closed default. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. ```ts cordis-catalog 'approval/request'(this: Scoped, req: ApprovalRequest, next: () => Promise): Promise ``` Types: [ApprovalOutcome](../core-data-structures/approval.md) · [ApprovalRequest](../core-data-structures/approval.md) Source: [`packages/ui/user-approval/src/index.ts:31`](../../packages/ui/user-approval/src/index.ts) ## `fs/*` ### `fs/edit-intent` — waterfall Single-slot decision for the next FileSystem.editText. Calling `next()` yields an unconditional edit; the first returned guard wins. ```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:61`](../../packages/fs/fs/src/index.ts) ### `fs/observed` — emit Record a successful observation. Listeners must be synchronous recorders: throws fail the tool call and returned promises are not awaited. ```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:70`](../../packages/fs/fs/src/index.ts) ### `fs/write-intent` — waterfall Single-slot decision for the next FileSystem.writeText. Calling `next()` yields the bare provider's unconditional write; the first listener that returns an intent owns the decision rather than composing with peers. ```ts cordis-catalog 'fs/write-intent'(target: FsTarget, actor: object | undefined, next: () => FsWriteIntent | undefined | Promise): Promise ``` Types: [FsTarget](../core-data-structures/filesystem.md) · [FsWriteIntent](../core-data-structures/filesystem.md) Source: [`packages/fs/fs/src/index.ts:53`](../../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): AsyncIterable ``` Types: [GenerateOptions](../core-data-structures/core.md) · [StreamChunk](../core-data-structures/llm-streaming.md) Source: [`packages/llm/llm/src/index.ts:40`](../../packages/llm/llm/src/index.ts) ## `session/*` ### `session/created` — emit Creation announcement during session publication. A synchronous throw vetoes and rolls back with a paired disposal; detach requested during dispatch is deferred. A returned-promise rejection is logged but cannot retroactively veto this synchronous boundary. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only sessions entered through that agent's context. ```ts cordis-catalog 'session/created'(this: Scoped, session: Session): void ``` Source: [`packages/core/session/src/index.ts:47`](../../packages/core/session/src/index.ts) ### `session/disposed` — emit Emitted once when an announced session leaves the store, including publication rollback, but never for an entry whose creation announcement did not begin. Listener failures are logged and contained. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`) reuses the owner scope. ```ts cordis-catalog 'session/disposed'(this: Scoped, session: Session): void ``` Source: [`packages/core/session/src/index.ts:57`](../../packages/core/session/src/index.ts) ### `session/event` — emit Post-commit, fire-and-forget append feed. The listener snapshot resolves before the log push, but callbacks run after it; observer failures are logged and contained without making the committed append fail. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only events from sessions entered through that agent's context. ```ts cordis-catalog 'session/event'(this: Scoped, session: Session, event: SessionEvent): void ``` Types: [SessionEvent](../core-data-structures/core.md) Source: [`packages/core/session/src/index.ts:69`](../../packages/core/session/src/index.ts) ### `session/flush` — parallel Awaited parallel durability checkpoint: every listener runs and the caller awaits all of them, with no waterfall veto. Dispatch through SessionStore.flush. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`) reuses the session's owner scope. ```ts cordis-catalog 'session/flush'(this: Scoped, session: Session): Promise | void ``` Source: [`packages/core/session/src/index.ts:79`](../../packages/core/session/src/index.ts) ## `subagent/*` ### `subagent/end` — emit A ready child settled. Scope-filtered dispatch uses the same delegating parent carrier as `subagent/start`, so the lifecycle pair reaches the same scoped audience. ```ts cordis-catalog 'subagent/end'(this: Scoped, info: SubagentRunEndInfo): void ``` Source: [`packages/subagent/subagent/src/index.ts:112`](../../packages/subagent/subagent/src/index.ts) ### `subagent/provider-added` — emit A provider became resolvable in the registry. ```ts cordis-catalog 'subagent/provider-added'(provider: SubagentProvider): void ``` Source: [`packages/subagent/subagent/src/index.ts:86`](../../packages/subagent/subagent/src/index.ts) ### `subagent/provider-removed` — emit A provider left the registry. Accepted runs remain holder-owned. ```ts cordis-catalog 'subagent/provider-removed'(name: string): void ``` Source: [`packages/subagent/subagent/src/index.ts:92`](../../packages/subagent/subagent/src/index.ts) ### `subagent/start` — emit A provider established a ready child. For in-process providers, `ctx.agents.get(info.id)` resolves during this notification. Scope-filtered dispatch keys the carrier by the delegating parent, so a parent-scoped listener observes only its own delegations. Paired with `subagent/end`. ```ts cordis-catalog 'subagent/start'(this: Scoped, info: SubagentRunInfo): void ``` Source: [`packages/subagent/subagent/src/index.ts:103`](../../packages/subagent/subagent/src/index.ts) ## `system-prompt/*` ### `system-prompt/assemble` — waterfall Expert waterfall over the assembled sections, tools, and variables. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): scoped listeners receive only that scope's assemblies. The returned value is authoritative. ```ts cordis-catalog 'system-prompt/assemble'(this: Scoped, assembly: PromptAssembly, context: AssembleContext, next: () => Promise): Promise ``` Source: [`packages/core/system-prompt/src/index.ts:27`](../../packages/core/system-prompt/src/index.ts) ### `system-prompt/change` — emit Emitted when any prompt provider changes. This registry notification is unfiltered because a global change affects every scope. ```ts cordis-catalog 'system-prompt/change'(): void ``` Source: [`packages/core/system-prompt/src/index.ts:33`](../../packages/core/system-prompt/src/index.ts) ## `tools/*` ### `tools/change` — emit A tool was registered or unregistered, or a scoped restriction changed (the available tool set changed — possibly for one scope only). An UNFILTERED registry-subject notification, deliberately not scope-filtered dispatch: a global change concerns every agent's next assembly, so a scoped listener subscribing here sees every change, not just its own scope's. ```ts cordis-catalog 'tools/change'(): void ``` Source: [`packages/core/tools/src/index.ts:116`](../../packages/core/tools/src/index.ts) ### `tools/execute` — waterfall Around-dispatch waterfall for timeout, retry, or metrics. `next()` returns a normalized result; wrappers may change only `exec.signal`, while call identity remains immutable. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent's calls. ```ts cordis-catalog 'tools/execute'(this: Scoped, exec: ToolExecution, next: () => Promise): Promise ``` Types: [ToolExecution](../core-data-structures/tools.md) · [ToolExecutionResult](../core-data-structures/tools.md) Source: [`packages/core/tools/src/index.ts:89`](../../packages/core/tools/src/index.ts) ### `tools/post-execute` — waterfall Accept, replace, enrich, or block a normalized dispatch result. `next()` accepts it unchanged; thrown tools still reach this seam as errors. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent's calls. ```ts cordis-catalog 'tools/post-execute'(this: Scoped, exec: ToolExecution, result: Readonly, next: () => Promise): Promise ``` Types: [ToolExecution](../core-data-structures/tools.md) · [ToolExecutionResult](../core-data-structures/tools.md) Source: [`packages/core/tools/src/index.ts:98`](../../packages/core/tools/src/index.ts) ### `tools/pre-execute` — waterfall Allow, deny, or ask before dispatch. `next()` delegates to allow; missing approval support turns `ask` into denial. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent's calls. ```ts cordis-catalog 'tools/pre-execute'(this: Scoped, exec: ToolExecution, next: () => Promise): Promise ``` Types: [ToolExecution](../core-data-structures/tools.md) Source: [`packages/core/tools/src/index.ts:80`](../../packages/core/tools/src/index.ts) ### `tools/result` — emit Observe the frozen, lossless-JSON final outcome. Listener failures are contained. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): keyed by `exec.agent`. ```ts cordis-catalog 'tools/result'(this: Scoped, exec: Readonly, result: Readonly): undefined ``` 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) ## `workflow/*` ### `workflow/agent-end` — emit One `agent()` call settled (clean result, child failure, or run cancellation). Paired with Events['workflow/agent-start'] by `agent.seq`, exactly once per started call on every stop path — on an engine termination path (a worker killed past its grace) the end is engine-synthesized with outcome `'cancelled'`. ```ts cordis-catalog 'workflow/agent-end'(info: WorkflowRunInfo, agent: WorkflowAgentEndInfo): void ``` Source: [`packages/workflow/workflow/src/index.ts:81`](../../packages/workflow/workflow/src/index.ts) ### `workflow/agent-start` — emit One `agent()` call established a ready child run. Paired with Events['workflow/agent-end'] by `agent.seq`. A call that never receives a ready run from the provider emits neither event in this pair. ```ts cordis-catalog 'workflow/agent-start'(info: WorkflowRunInfo, agent: WorkflowAgentInfo): void ``` Source: [`packages/workflow/workflow/src/index.ts:70`](../../packages/workflow/workflow/src/index.ts) ### `workflow/end` — emit A workflow run settled (any stop reason). Fired when WorkflowRun.result resolves. Paired with Events['workflow/start']. ```ts cordis-catalog 'workflow/end'(info: WorkflowRunInfo, result: WorkflowResultInfo): void ``` Source: [`packages/workflow/workflow/src/index.ts:91`](../../packages/workflow/workflow/src/index.ts) ### `workflow/log` — emit The script emitted a narration line (a `log(message)` call). ```ts cordis-catalog 'workflow/log'(info: WorkflowRunInfo, message: string): void ``` Source: [`packages/workflow/workflow/src/index.ts:60`](../../packages/workflow/workflow/src/index.ts) ### `workflow/phase` — emit The script entered a phase (a `phase(title)` call) — progress grouping for observers; no execution semantics. ```ts cordis-catalog 'workflow/phase'(info: WorkflowRunInfo, title: string): void ``` Source: [`packages/workflow/workflow/src/index.ts:53`](../../packages/workflow/workflow/src/index.ts) ### `workflow/start` — emit A workflow run started — the script's meta block validated, the body about to execute. Paired with Events['workflow/end']. ```ts cordis-catalog 'workflow/start'(info: WorkflowRunInfo): void ``` Source: [`packages/workflow/workflow/src/index.ts:45`](../../packages/workflow/workflow/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:328`](../../vendor/cordis/src/events.ts)) - `internal/status` — A fiber changed lifecycle state. ([`vendor/cordis/src/events.ts:330`](../../vendor/cordis/src/events.ts)) - `internal/service` — Interception hook for a service binding (no core producer). ([`vendor/cordis/src/events.ts:332`](../../vendor/cordis/src/events.ts)) - `internal/update` — Waterfall: a fiber config update is being applied. ([`vendor/cordis/src/events.ts:334`](../../vendor/cordis/src/events.ts)) - `internal/get` — Waterfall: a service is being read from the store. ([`vendor/cordis/src/events.ts:336`](../../vendor/cordis/src/events.ts)) - `internal/set` — Waterfall: a service is being written to the store. ([`vendor/cordis/src/events.ts:338`](../../vendor/cordis/src/events.ts)) - `internal/listener` — A listener was registered. ([`vendor/cordis/src/events.ts:340`](../../vendor/cordis/src/events.ts)) - `internal/dispatch` — An event is being dispatched to listeners. ([`vendor/cordis/src/events.ts:342`](../../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))