AllDSH
中文

intermediate · 3 min read · updated 2026-09-06

Build a DSH plugin: from empty directory to loaded plugin

A practical walkthrough for building a DeepSeek Harness plugin, from package setup to the apply(ctx) entry point and local testing.

Written by AllDSH Editorial · Editorial team

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

Building a DSH plugin is closer to writing a small library than to writing an extension for a closed application. You need a package, an entry point, and a dshTarget declaration. This guide walks through all three, then shows how to test the result in a real session.

What a plugin actually is

A plugin is a package that exports an apply(ctx) function. The harness calls it during startup with a context object, and everything you register happens through that object: tools, commands, interface surfaces, event handlers. When the plugin is unloaded, the runtime disposes what you registered.

That design means you never reach into global state. If your plugin can only work by mutating something it does not own, it will break on the next release.

Step 1: create the package

mkdir my-dsh-plugin && cd my-dsh-plugin
npm init -y
npm pkg set type=module
npm pkg set name=dsh-my-plugin

Add the ecosystem topic so directories can discover it later, and declare the harness version you target in your README and package metadata:

{
  "name": "dsh-my-plugin",
  "type": "module",
  "keywords": ["dsh-plugin", "deepseek-harness"],
  "dsh": { "target": "rc.6" }
}

Step 2: write apply(ctx)

Start with the smallest useful plugin: one tool the model can call.

import type { Context } from '@deepseek-ai/dsh';

export function apply(ctx: Context) {
  ctx.tool({
    name: 'word_count',
    description: 'Count words in a piece of text.',
    parameters: {
      type: 'object',
      properties: { text: { type: 'string', description: 'Text to count' } },
      required: ['text'],
    },
    async execute({ text }: { text: string }) {
      const words = text.trim().split(/\s+/).filter(Boolean).length;
      return { words };
    },
  });
}

Three things matter in that snippet. The description is written for a model, not a human, because it is what the model sees when deciding whether to call the tool. The parameter schema is explicit, because vague schemas produce bad calls. The return value is structured data, not a formatted string, because the caller usually wants to reason over it.

Step 3: register more than tools

Tools are the most common surface, but not the only one. Name the surface you need and register through ctx:

  • Commands, for things you trigger yourself from the composer.
  • Events, for reacting to session lifecycle changes.
  • Interface surfaces, if you are building UI rather than capability.
  • Capability seams, when you are providing an implementation for something another plugin consumes, such as a memory provider.

Step 4: test it in a real session

Install your plugin into the web profile from the directory that contains it:

dsh plugin --profile web add /absolute/path/to/my-dsh-plugin

Then start the harness and check two things: that the plugin loaded without an error, and that the model actually calls your tool when the situation warrants it. A tool that exists but is never chosen usually has a description problem, not a code problem.

Step 5: handle failure honestly

Plugin failures should be legible. If a tool call cannot complete, return an error that names what was missing rather than an empty result. If your plugin needs configuration, fail at load time with a message that says which field is missing, instead of failing on first use three sessions later.

Step 6: document the contract

Before publishing, write down what you depend on: the capabilities you register, the permissions you need, and the harness version you target. That documentation is what a directory needs in order to list you accurately, and it is what a reviewer needs in order to trust the plugin.

FAQ

Do I need TypeScript?

No, but types for the context object prevent most of the mistakes people make in their first plugin.

Can a plugin depend on another plugin?

Indirectly, through capability seams. Direct private imports between plugins break on upgrade and create ordering problems.

How do I test without publishing to npm?

Install from a local path, as shown in step 4. The harness treats a local directory like any other package.

When should I bump dshTarget?

Whenever you adopt a new runtime capability. Declaring a newer target than you actually need shuts out users on older releases.

Next steps

Read the Cordis plugin lifecycle to understand disposal and ordering, then publish and submit your plugin to a directory.

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]@deepseek-ai/dsh on npm — npmPublished harness package, including the release tags our dshTarget field tracks.
  3. [3]About semantic versioning — npm DocsThe versioning convention behind the compatibility and dshTarget guidance.
  4. [4]Keep a Changelog 1.1.0 — Keep a ChangelogThe changelog convention recommended for plugins that want maintainable release notes.

All links last verified 2026-09-11

ESC

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