DeepSeek Harness Plugin Development: Build Your First dsh Plugin
How to write DeepSeek Harness (dsh) plugins: Cordis services and events, capability seams, quality gates, and how to get your plugin listed in the directory.
Last updated: 2026-08-13
What a dsh plugin actually is
A DeepSeek Harness plugin is a Cordis plugin. Cordis is the meta-framework under the hood: it only handles loading, unloading and dependency resolution. Every concrete component of the harness — tools, storage, policies, the agent loop, UI panels — is a Cordis plugin, and plugins cooperate through services and events.
That's the mental model shift: a plugin is not a bolt-on. It can replace or extend any part of the agent:
- Add a tool the model can call (a title generator, a database connector…)
- Swap the system prompt or the whole agent loop
- Intercept tool executions to approve, log or rewrite them
- Register a new UI panel, status bar or theme
- Add a sandbox backend or a model provider
The official anchors to know:
- npm package:
@deepseek-ai/dsh - CLI command:
dsh(install:npm install -g @deepseek-ai/dsh, run:dsh --profile web) - GitHub topic for community plugins:
dsh-plugin - Framework: Cordis
Capability seams: service, provider, consumer
Every capability (running commands, reading files, network access, model calls, sandbox isolation) is split into three independent roles:
- Service — the interface definition;
- Provider — a concrete implementation;
- Consumer — the code that uses the capability.
The three are decoupled, so the model always sees a stable interface no matter which provider sits behind it. Want to swap the search engine? Register a different provider — the consumer and the model see nothing change. This is exactly how community plugins swap out so much of the harness.
The plugin tree
A running dsh is a tree of plugins assembled from layered config: bundles (official sets), profiles (your named assemblies) and patches (your overrides, addressable down to a single plugin entry). Upper layers win. When a plugin is removed, everything it registered is revoked — registration must be a reversible side effect.
A minimal plugin, conceptually
Every plugin is a function (or object with lifecycle hooks) that receives a Cordis context and uses it to declare what it provides and what it consumes. A skeleton looks like:
import { Context, Service } from '@cordisjs/core';
export const name = 'my-plugin';
// 1. A service the plugin provides
export class Greeting extends Service {
constructor(ctx: Context) {
super(ctx, 'greeting');
}
hello() {
return 'hello from my-plugin';
}
}
// 2. The plugin itself
export function apply(ctx: Context) {
ctx.provide('greeting', new Greeting(ctx));
// 3. React to an event — e.g. run when the agent starts
ctx.on('agent/start', () => {
ctx.logger.info('my-plugin loaded');
});
}
The exact APIs evolve quickly (the project is at v0.1 with rapid iteration), so treat any snippet as a shape, not a contract — and always check the official docs and the plugin-template repo for the current conventions.
Engineering conventions the repo enforces
The official repository's automated gates codify a set of rules worth copying into your own plugin:
- Registration must be reversible — uninstalling leaves no residue;
- Config errors must fail loudly at load time, never silently skip;
- Cross-boundary identifiers use branded types — no raw strings leaking between layers;
- Deployment-varying parameters live in config, not hardcoded.
Quality gates: 100% unit-test coverage per source file, snapshot-replay tests that run without API keys, real-API end-to-end tests, cross-file code-clone detection, and docs-sync checks that block CI when documentation is stale.
Where plugin code plugs in
Depending on what you're building, the seams differ:
- Tool — register a tool the model can call; it runs through the same pipeline as built-in tools (approvals, guards, logging);
- Agent event — hooks around
turn/start,step/start, tool execution, model requests; - UI — the Web UI is itself a second plugin tree; pages declare mount points (sidebar, chat area, input, settings) and plugins register components into them;
- Storage / session — replace how sessions and logs are persisted;
- Sandbox / model — provide your own backend or model adapter (any OpenAI-compatible endpoint works).
Publishing and getting listed
-
Push your plugin to a public GitHub repository.
-
Write a README with a one-line description, install instructions, and (if you can) what it needs access to.
-
Add the
dsh-plugintopic to the repo — it's the official GitHub marker other tools and directories use for discovery. -
Open a PR to our awesome list — the single source of this directory:
- cccakeee/awesome-dsh-plugins — add one line under the matching category heading (English or Chinese).
-
Optionally list it on an npm registry-style index so
dshtooling can find it.
Once merged, this directory picks your plugin up on its next sync (see the ecosystem guide for how that works).
One last warning
Plugins are code that runs with the agent's permissions. Before installing anyone else's plugin, read its source, check its license and permissions, and try it in an isolated workspace. Your own plugin should be just as honest about what it reads, writes and sends over the network.
Keep exploring
- Installing plugins you can trust — the other side of the install line
- Profiles, patches & presets — where your plugin lands in the tree
- Sandboxes, approvals & secrets — the safety model your plugin plugs into
- Submit a plugin — the full listing checklist
