Event system
Basic use
Section titled “Basic use”Listen for an event
Section titled “Listen for an event”ctx.on('event-name', (payload) => { // Handle the event.})Emit an event
Section titled “Emit an event”ctx.emit('event-name', payload)Event modes
Section titled “Event modes”Cordis provides several event modes for different interaction contracts.
emit — broadcast
Section titled “emit — broadcast”Every listener runs synchronously and return values are ignored:
// Emitctx.emit('my-plugin/ready', { id: 'worker-1' })
// Listenctx.on('my-plugin/ready', ({ id }) => { console.log(`${id} is ready`)})bail — short circuit
Section titled “bail — short circuit”Listeners run in order; the first result other than null, false, or undefined becomes the final result:
// Dispatchconst result = ctx.bail('some-check', input)
// Listen: a returned value stops later listeners.ctx.on('some-check', (input) => { if (shouldBlock(input)) return 'blocked' // Return null, false, or undefined to continue to the next listener.})serial — ordered execution
Section titled “serial — ordered execution”Listeners run in registration order and asynchronous results are awaited. The first result other than null, false, or undefined stops further execution:
await ctx.serial('setup-phase', context)waterfall — pipeline
Section titled “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:
// Dispatchconst 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
Section titled “Typed events”Harness uses TypeScript declaration merging for type-safe events:
import '@deepseek-ai/cordis'
declare module '@deepseek-ai/cordis' { interface Events { 'my-plugin/ready': (payload: { id: string }) => void 'my-plugin/check': (input: string) => boolean | undefined 'my-plugin/transform': (input: string, next: () => Promise<string>) => Promise<string> }}
// ctx.on('my-plugin/ready', ...) and ctx.emit('my-plugin/ready', ...)// are now inferred correctly.Cordis events and session records
Section titled “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 record complete signatures and modes.
turn/*, step/*, tool/call, tool/result, and compaction/* 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
Section titled “Event listeners are effects”A listener registered with ctx.on() is removed automatically when its plugin unloads:
export function apply(ctx: Context) { // This listener is removed when the plugin disposes. ctx.on('tools/result', handler)}Example: logging plugin
Section titled “Example: logging plugin”This plugin logs tool calls and results:
import type { Context } from '@deepseek-ai/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
Section titled “Next steps”- Capability layering — understand events within capability interfaces
- LLM adapters — implement a complete LLM backend