Cordis is the plugin runtime underneath DeepSeek Harness. Understanding its lifecycle is what separates a plugin that survives upgrades from one that leaks handlers and breaks other people’s sessions. The good news is that the model is small: a plugin is loaded, it registers, and it is disposed.
The three phases
| Phase | What happens | What you should do |
|---|---|---|
| Load | The runtime resolves the package and calls apply(ctx) | Register only what you need, validate configuration |
| Active | Events fire, tools are called, interfaces render | React through handlers, keep state in the profile directory |
| Dispose | The runtime unloads the plugin and runs cleanups | Release resources, remove anything you added |
The middle phase gets all the attention, and the third phase is where most plugin bugs come from.
What the context object gives you
apply(ctx) receives a context that acts as your handle on the harness. Three properties of that object matter most.
Scoped registration. Anything you register through the context is owned by your plugin. When your plugin is unloaded, the runtime knows what to remove. If you instead attach a listener to a global object or start a background loop you never stop, that work outlives your plugin and mutates a harness you no longer have a contract with.
Ordering. Plugins load in a defined order and can depend on capabilities that other plugins provide. A memory provider should register its capability before a consumer asks for it. If your plugin is a provider, register early and make readiness observable rather than assuming you started first.
Events. Event handlers are how most plugins react to the session: a turn starting, a tool completing, a session ending. Keep handlers fast. A slow handler in a synchronous path delays everything behind it, and a handler that throws can interrupt a turn the user did not expect to lose.
Disposal is not optional
A plugin that registers without disposing leaves residue with three symptoms: duplicated output after a reload, handlers firing twice, and slow growth in memory over a long session. All three come from the same mistake, which is treating apply as a one-way street.
The pattern that avoids it is to register through the context and let the runtime own the cleanup. The following sketch shows a handler that is explicitly removed during disposal:
import type { Context } from '@deepseek-ai/dsh';
export function apply(ctx: Context) {
const onTurnEnd = (sessionId: string) => {
ctx.log.debug(`turn finished for ${sessionId}`);
};
ctx.on('session:turn-end', onTurnEnd);
ctx.onDispose(() => {
ctx.off('session:turn-end', onTurnEnd);
});
}
If you start a timer, open a socket or spawn a child process, stop it in the disposal path. Unstopped background work is the most common cause of a plugin that “works fine” but makes the harness feel slower after a few hours.
State belongs in the profile
Plugins that keep data should write it inside the profile directory rather than a global cache path. Two reasons: uninstalling the plugin can then remove its data cleanly, and a user running several profiles does not get their state mixed between them.
Keep the format forward compatible. A schema version field in your stored JSON costs nothing now and saves you from a migration problem in the release after next.
Failure modes worth designing for
A missing dependency. If your plugin needs a capability that is not registered, fail at load time with a message naming the capability and the plugin you expected to provide it.
A changed event payload. Payloads evolve. Validate the fields you read and degrade gracefully rather than throwing inside a handler.
A slow external service. Never block the session on a network call in a synchronous path. Do the work asynchronously and report the result when it arrives.
FAQ
Do I have to call onDispose manually?
If you registered everything through the context, the runtime handles teardown. You still need it for resources the context does not own, such as timers, sockets and child processes.
Can a plugin reload without restarting the harness?
Yes, which is exactly why disposal correctness matters. Reloading a plugin that never disposes its handlers makes the damage visible immediately.
How do I debug load order problems?
Check whether your plugin registered its capability before another plugin consumed it. Provider plugins should register early and expose readiness.
Is the lifecycle stable across releases?
The concept is stable. Specific event names and payload fields can change between preview releases, so validate what you read.
Next steps
Build a plugin if you have not yet, then read the plugin security guide before you publish anything that touches files or the network.