Cordis: Five Concepts for Reading a Plugin Tree
Build the smallest useful mental model of Cordis with Plugin, Context, inject, Events, and Effect.
The short answer: Cordis can be reduced to five ideas: plugins contribute capabilities, Context connects them by name, inject waits for dependencies, events coordinate without direct imports, and Effects reverse side effects.
Put the API names aside for a moment. Cordis answers one runtime question: how does a capability enter a running application, then leave safely when dependencies or code change?
01 / Five concepts, one runtime path
These are not five unrelated features. They form a path from declaration to teardown:
| Concept | Question it answers | Observable result |
|---|---|---|
| Plugin | What does this capability contribute? | A function, object, or Service class is mounted |
| Context | Where can a plugin find capabilities? | Services are resolved by stable names on ctx |
| inject | When may the plugin start? | It stays PENDING until dependencies exist |
| Events | How can unknown consumers cooperate? | Dispatch follows a declared mode |
| Effect | What happens during unload? | Disposers reverse registrations and external resources |
02 / A Plugin describes its own contribution
A minimal plugin does not boot the entire application. It exports its contribution:
import type { Context } from '@deepseek-ai/cordis'
export const name = 'hello'
export function apply(ctx: Context) {
console.log('hello from my first plugin')
}Loader finds the module and calls apply(ctx). The plugin file can focus on behavior while the composition file decides which behavior enters this run.
Cordis accepts functions, objects with an apply method, and Service subclasses commonly used to expose named capabilities. Each becomes a plugin instance owned by a Fiber.
03 / Context connects named services
A provider registers a capability under a name. Consumers use entries such as ctx.tools, ctx.llm, or ctx.agents instead of importing a provider class. The composition can therefore replace the implementation.
Context is not an invitation to write arbitrary global state. Cordis Context is a proxy and scope container: property reads resolve services, while extend() and isolate() create child contexts without mutating the parent.
04 / inject turns startup order into dependencies
export const inject = ['greeter']
export function apply(ctx: Context) {
console.log(ctx.greeter.greet('world'))
}With inject, the plugin enters apply only after the greeter service exists. Swapping two rows in cordis.yml therefore does not change the result; service readiness determines when the plugin starts.
If the feature can work without a service, do not pretend it is a hard dependency. Probe an optional service at the use site with ctx.get('greeter').
05 / Events hide the listener set
Events fit the case where something happened but the sender should not know every consumer. TypeScript declaration merging adds the event name and payload to Events, then the caller chooses emit, parallel, serial, bail, or waterfall.
A stats service can emit stats/report while a logger listens independently. The dispatch mode is part of the public agreement: it determines waiting, return values, and short-circuit behavior.
06 / Effect makes registration reversible
Cordis does not treat registrations as permanent mutations scattered through the application. ctx.on(), service registration, child-plugin mounting, and explicit ctx.effect() attach cleanup to the current Fiber.
ctx.effect(() => {
const timer = setInterval(() => console.log('tick'), 200)
return () => clearInterval(timer)
})When the plugin unloads, the disposer runs and the timer stops. HMR’s “old code out, new code in” behavior is built on this reversible relationship.
07 / There is no privileged kernel
Together, these concepts make model adapters, tool registries, sessions, agent management, and even the agent loop replaceable plugins. Adding behavior normally means defining a service, providing an implementation, registering a consumer, or attaching to an existing event—not patching a privileged kernel.
In DeepSeek Harness, a profile chooses composition, Context connects capabilities, Fiber owns instances, and Effects reverse them. “Everything is a plugin” means replacement points and resource ownership stay visible.
Where to go next
Implementation references: the Cordis primer, tutorial index, Context, and Events.
Author
Categories
More Posts
Composition and HMR: Import New Code, Dispose the Old Fiber, Then Activate
Understand stable ids, configuration groups, and Cordis HMR’s unload-reload cycle, including how to diagnose plugins stuck in PENDING.
Configuration: Give Plugins Validated Options and Defaults
Define Cordis plugin configuration with Schemastery and see how defaults, path-aware errors, and FAILED Fibers prevent a sick start.
waterfall: Intercept, Wrap, and Short-Circuit with next()
Follow a Cordis waterfall chain to see listeners delegate downstream, wrap results, or short-circuit when they own the decision.
Newsletter
Join the community
Subscribe to our newsletter for the latest news and updates