Back to DSH guides

DSH Plugin Development Guide

How to Build a DSH Plugin for DeepSeek Harness

Build a real DeepSeek Harness plugin from the minimal Cordis module shape, register a model-facing capability, package it for profile installation, test the load path, and publish it for the DSH ecosystem.

DeepSeek HarnessDSH PluginsDeveloper Preview

Everything is a plugin

Model adapters, tools, sessions, UI behavior, services, policies, and workflows all attach through the same composable architecture.

Effects are reversible

Registrations belong to the plugin lifecycle and unwind when the plugin unloads.

Packages stay patchable

A bundle distributes Cordis config rows through a patch file declared in package metadata.

01

Understand the DSH Plugin Model First

DeepSeek Harness is powered by Cordis and treats product behavior as composable plugins rather than a privileged core plus optional add-ons. A plugin receives a shared Context and contributes services, events, tools, UI behavior, policy, storage, or other capabilities through that context.

The minimal form is deliberately small: export a plugin name and an apply function. If your plugin depends on another service, declare it through inject so Cordis waits for that service before your plugin activates.

Minimal function plugin
import type { Context } from '@deepseek-ai/cordis'

export const name = 'my-dsh-plugin'
export const inject = ['tools']

export function apply(ctx: Context) {
  // Register capabilities through ctx here.
}
02

Choose the Capability Your Plugin Owns

The architecture documentation maps new behavior to extension points. A plugin can be much more than a model-facing tool, so choose the seam that matches the behavior instead of forcing every feature through one API.

Model tool

Register a typed tool on ctx.tools.

Model provider

Register an adapter on ctx.llm.

Background work

Use ctx.jobs for durable or scheduled work.

Policy / interception

Listen on tools/*, agent/*, fs/*, or another documented event seam.

UI integration

Render session events or register Web client presentation behavior.

Service provider

Expose a replaceable service that other plugins consume through Context.

03

Build a Small Model-Facing Tool

A tool is a good first plugin because the contract is visible: define the input schema, return a canonical value, and render that value into model-facing content. DSH validates tool arguments before execute runs and removes the registration when the owning plugin unloads.

Small example tool
import type { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'

export const name = 'project-info'
export const inject = ['tools']

export function apply(ctx: Context) {
  ctx.tools.register(defineTool({
    name: 'project_info',
    description: 'Return a small project summary.',
    parameters: {
      name: { type: 'string', required: true },
    },
    output: {
      schema: { type: 'string' },
      render: (_args, value) => [{ type: 'text', text: value }],
    },
    async execute(args) {
      return 'Project: ' + args.name
    },
  }))
}

For production tools, also design cancellation, structured output, policy hooks, replay-safe UI presentation, and tests around the real execution contract.

04

Package the Plugin as a DSH Bundle

For profile installation, a package can declare bundle metadata in package.json. The dsh.bundle declaration points to a Cordis patch file, and that patch contributes rows to the profile composition. This keeps the plugin installable while still allowing later profile and user patch layers to override it.

  • Ship the patch file in the package files list.
  • Expose the runtime entry that the Cordis Loader resolves.
  • Declare peer dependencies that match the DSH services your plugin consumes.
  • Keep the bundle focused: configuration rows should describe what the package actually mounts.
package.json bundle declaration
{
  "name": "@example/my-dsh-plugin",
  "type": "module",
  "dsh": {
    "bundle": {
      "patch": "./cordis.patch.yml"
    }
  }
}
cordis.patch.yml
- insert:
    - id: my-dsh-plugin
      name: '@example/my-dsh-plugin'
05

Test the Load Path, Not Only the Function

Unit tests can validate your own function, but a plugin is only useful if Harness can resolve it, mount it, satisfy its injected services, and unwind its effects. Test the assembled path in a real DSH composition before publishing.

  • Verify the plugin appears in the composed tree.
  • Exercise the capability through the surface that will actually use it.
  • Test failure and unload paths as well as the happy path.
  • Keep your Node and peer dependency ranges aligned with the current Harness developer preview.
Load a development overlay from a Harness checkout
pnpm dsh web --patch ./scratch-plugin/cordis.yml
Inspect the composed profile
dsh --profile web --dump-config
06

Publish and Make the Plugin Discoverable

A community plugin can live outside the official monorepo and depend on the published @deepseek-ai packages it needs. When the package and installation path are stable, publish the repository and package, document the exact profile and install command, and add the dsh-plugin GitHub topic so users and registries can discover it.

  • README: what the plugin does, target profile, installation, configuration, limitations, and verification steps.
  • package.json: package identity, exports, version, license, engines, dependencies, and dsh.bundle metadata when applicable.
  • Repository: dsh-plugin topic, releases or tags, changelog, and issue/support path.
  • Verification: a reproducible command that proves the capability is loaded and working.

Primary references

This guide is grounded primarily in the current DeepSeek AI Harness repository and development documentation. Harness is still in developer preview, so exact commands and contracts can change with later releases.