English | 中文
Cordis is the vendored plugin framework underneath DeepSeek Harness. This primer teaches the Cordis ideas a harness plugin author needs before reading the generated service/event reference on the subsystem pages; the Cordis tutorial walks the same ideas hands-on. The vendored source and sync procedure live in vendor/README.md.
inject and apply(ctx) fields, or a Service subclass whose lifecycle Cordis mounts into the current context.ctx.<key> such as ctx.tools, ctx.llm, or ctx.sessions from a context; other plugins find services via key instead of importing a concrete implementation.inject. A plugin that names required services waits until those services exist, so load order is expressed through service requirements rather than manual boot sequencing.emit, waterfall, parallel, serial, or bail depending on whether listeners observe, wrap, fan out, run in order, or stop at the first bail value.ctx.effect() or ctx.on() so reload and teardown unwind them predictably.Every event can have one of the following dispatch mode and can only be dispatched by these methods accordingly.
| Mode | Awaited? | Dispatch Order | Has Return Value? |
|---|---|---|---|
emit |
No | listeners observe in registration order | No |
waterfall |
No | listeners observe in registration order | Yes |
parallel |
Yes | all listeners observe the event in parallel | No |
serial |
Yes | listeners observe in registration order | Yes |
bail |
No | listeners observe in registration order until one bails | Yes |
The dispatch mode is part of the event's public contract. New harness events document it with an @mode tag so the generated catalog can check declarations against dispatch sites.
ctx.waterfall is around-middleware. A listener receives (...args, next). Call next() to delegate the possibly wrapped result to the next service; return without next() to short-circuit. Values propagate through next()'s return value.
Cooperative listeners usually mutate a shared request or decision object and then delegate. A listener can also choose to replace the result entirely and downstream listeners will only see the result after replacement. Use prepend: true only when the listener must run before ordinary registrations.
For single-decision events, short-circuiting is the design. A policy listener can return without next() when it owns the decision, while a listener that only annotates or observes must delegate.
@deepseek-ai/cordis-plugin-include parses !!js into expression nodes. Loader interpolates an entry's config (after declared injections activate, against that plugin context — ctx.serviceName) and its disabled field (at every mount decision, against the loader context); Include preserves nested row expressions until target activation. Other entry metadata stays literal. Use overlays when the environment selects plugins.
Encapsulate behavior into plugins: a tool pipeline event belongs to ctx.tools, model streaming belongs to ctx.llm, and live agent coordination belongs to ctx.agents. Prefer events for interception and policy; prefer service methods for direct capability calls.
Every registration should have a disposer, either by returning one from ctx.effect() or using a Cordis helper that does it for you. If teardown order matters, keep the related work in one effect so disposal unwinds in the intended sequence.