# Event system English | [中文](events.zh.md) Events are the core communication mechanism between Cordis plugins. Harness uses them extensively for loosely coupled extension points. ## Basic use ### Listen for an event ```ts ignore-check ctx.on('event-name', (payload) => { // Handle the event. }) ``` ### Emit an event ```ts ignore-check ctx.emit('event-name', payload) ``` ## Event modes Cordis provides several event modes for different interaction contracts. ### emit — broadcast Every listener runs synchronously and return values are ignored: ```ts ignore-check // Emit ctx.emit('my-plugin/ready', { id: 'worker-1' }) // Listen ctx.on('my-plugin/ready', ({ id }) => { console.log(`${id} is ready`) }) ``` ### bail — short circuit Listeners run in order; the first non-`undefined` result becomes the final result: ```ts ignore-check // Dispatch const result = ctx.bail('some-check', input) // Listen: a returned value stops later listeners. ctx.on('some-check', (input) => { if (shouldBlock(input)) return 'blocked' // Return undefined to continue to the next listener. }) ``` ### serial — ordered execution Listeners run in registration order and asynchronous results are awaited. The first listener to return a non-empty value stops further execution: ```ts ignore-check await ctx.serial('setup-phase', context) ``` ### waterfall — pipeline Each listener may wrap the downstream result to form a processing chain. A listener **must call `next()` to delegate downstream**; omitting the call short-circuits the pipeline: ```ts ignore-check // Dispatch const output = await ctx.waterfall('my-plugin/transform', input, async () => input) // Listen: next() is mandatory. ctx.on('my-plugin/transform', async (_input, next) => { const downstream = await next() return downstream.trim() }) ``` ::: warning A waterfall listener **must call `next()`**. Omitting it short-circuits the pipeline by design, enabling interception and gateway behavior. ::: ## Typed events Harness uses TypeScript declaration merging for type-safe events: ```ts import 'cordis' declare module 'cordis' { interface Events { 'my-plugin/ready': (payload: { id: string }) => void 'my-plugin/check': (input: string) => boolean | undefined 'my-plugin/transform': (input: string, next: () => Promise) => Promise } } // ctx.on('my-plugin/ready', ...) and ctx.emit('my-plugin/ready', ...) // are now inferred correctly. ``` ## Cordis events and session records Harness Cordis events use `namespace/action` names, including `agent/step`, `agent/request`, `agent/request-error`, `tools/result`, and `session/event`. The generated `cordis-surface` regions on the [subsystem pages](../../../subsystems/core.md) record complete signatures and modes. `turn/*`, `step/*`, `tool/call`, `tool/result`, and `compact/*` are durable session-event types, not same-named Cordis events. To observe them, listen to `session/event` and inspect `event.type`. ## Event listeners are effects A listener registered with `ctx.on()` is removed automatically when its plugin unloads: ```ts ignore-check export function apply(ctx: Context) { // This listener is removed when the plugin disposes. ctx.on('tools/result', handler) } ``` ## Example: logging plugin This plugin logs tool calls and results: ```ts import type { Context } from 'cordis' import '@deepseek-ai/dsh-tools' export const name = 'tool-logger' export function apply(ctx: Context) { ctx.on('tools/result', (exec, result) => { console.log(`[tool] ${exec.name}(${JSON.stringify(exec.arguments)})`) const text = result.content .map(block => block.type === 'text' ? block.text : '') .join('') console.log(`[tool result] ${text.slice(0, 100)}`) }) } ``` ## Next steps - [Capability layering](../practice/) — understand events within capability interfaces - [LLM adapters](../practice/llm-adapter.md) — implement a complete LLM backend