Back to guides

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:

  1. Service — the interface definition;
  2. Provider — a concrete implementation;
  3. 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

  1. Push your plugin to a public GitHub repository.

  2. Write a README with a one-line description, install instructions, and (if you can) what it needs access to.

  3. Add the dsh-plugin topic to the repo — it's the official GitHub marker other tools and directories use for discovery.

  4. Open a PR to our awesome list — the single source of this directory:

  5. Optionally list it on an npm registry-style index so dsh tooling 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

DSH Plugins is an independent community directory of DeepSeek Harness plugins. Not affiliated with or endorsed by DeepSeek. Third-party plugins are not security-audited — review the source before installing.