AllDSH
中文

intermediate · 4 min read · updated 2026-09-07

The Cordis plugin lifecycle in DSH: apply, events and disposal

How the Cordis runtime loads DSH plugins, what apply(ctx) can register, how event ordering works and why disposal is the part that keeps plugins safe.

Written by AllDSH Editorial · Editorial team

Maintains the verification levels, categories and install specs in this directory. ·

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

PhaseWhat happensWhat you should do
LoadThe runtime resolves the package and calls apply(ctx)Register only what you need, validate configuration
ActiveEvents fire, tools are called, interfaces renderReact through handlers, keep state in the profile directory
DisposeThe runtime unloads the plugin and runs cleanupsRelease 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.

Sources & references

The technical claims on this page rest on the primary documents below. Each link is followed so it can be checked directly.

  1. [1]deepseek-ai/deepseek-harness — GitHubUpstream repository for DeepSeek Harness, the Cordis runtime and the plugin loading model.
  2. [2]Model Context Protocol — AnthropicSpecification for the MCP bridges several listed plugins implement.
  3. [3]About semantic versioning — npm DocsThe versioning convention behind the compatibility and dshTarget guidance.

All links last verified 2026-09-11

ESC

Type to search the index. Built at deploy time by Pagefind.