Host Services & Events
Host-side development: read optional services with ctx.get, declare hard dependencies with inject, listen to events and provide services
The Host is the Node.js process side where plugins run. Here you read and write services, listen to events, and provide services to other plugins.
What is a service?
A service is a capability one plugin exposes to others. tools, llm, and agents are named services mounted on ctx:
ctx.tools // the tool runtime service
ctx.llm // the LLM service
ctx.agents // the Agent serviceAny plugin can provide a service for other plugins to consume.
Reading services
Hard dependencies: inject
Declare inject for required services. When apply runs, those services are already ready; if a service is missing, the plugin waits instead of running:
export const inject = ['tools']
export function apply(ctx: Context) {
// ctx.tools is ready here.
ctx.tools.register(/* ... */)
}Optional dependencies: ctx.get
Leave optional services out of inject and query them at the use site with ctx.get(), handling undefined:
export function apply(ctx: Context) {
const metrics = ctx.get('metrics')
metrics?.record('plugin_loaded', 1)
}When a service disappears
If a required service disappears at runtime (for example its provider unloads), the dependent plugins dispose automatically and load again when the service returns. This prevents calling a service that no longer exists.
Providing a service
Extend the Service base class:
import { Service, type Context } from '@deepseek-ai/cordis'
export default class MetricsService extends Service {
static inject = ['llm']
constructor(ctx: Context) {
super(ctx, 'metrics')
}
record(event: string, value: number) {
// ...
}
}After loading, consumers access it as ctx.metrics. Type ctx.metrics via TypeScript declaration merging; the service names, public methods, and source locations live in each capability subsystem's generated documentation.
Listening to events
Listen with ctx.on() and emit with ctx.emit():
ctx.on('tools/result', handler) // removed automatically on unload
ctx.emit('my-plugin/ready', payload)Events have different dispatch modes:
emit— broadcast: every listener runs synchronously and return values are ignored.bail— short circuit: listeners run in order; the first result other thannull,false, orundefinedbecomes the final result.serial— ordered execution: listeners run in order and async results are awaited; the first non-empty result stops later listeners.waterfall— pipeline: each listener may wrap the downstream result; it must callnext()to delegate, otherwise it short-circuits.
Waterfall must call next()
A waterfall listener must call next(). Omitting it short-circuits the pipeline by design, enabling interception and gateway behavior.
Cordis events and session records
turn/*, step/*, tool/call, and tool/result 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; every registration belongs to the current fiber's lifecycle. For a resource that needs explicit cleanup, such as a connection, return a disposer from ctx.effect():
export function apply(ctx: Context) {
ctx.effect(() => {
const connection = createConnection()
return () => connection.close()
})
}Next steps
- To register UI in the browser, see Client UI & Slots.
- To register model-facing tools, see Registering Tools.