DeepSeek Harness plugin examples: five copy-paste plugins

Plugin DevelopmentPublished 2026-09-12Author: DeepSeek Plugin Market
DSH pluginDeepSeek Harnessplugin examplessample codeCordis
Five DeepSeek Harness plugin examples you can copy: minimal plugin, model tool, event listener, configured plugin, and service provider.

DeepSeek Harness plugin examples get you further than a spec: five examples — minimal plugin, model tool, event listener, configured plugin, and service provider — cover every common plugin shape, and each one can be pasted straight into scratch-plugin/src/ and run behind a single --patch overlay. Any DSH plugin you copy from here follows the same run steps.

All five share one directory and one run command; only the registration code differs. Get the skeleton running in three steps (source):

  1. Create the directory — create scratch-plugin/src/ at the checkout root. Expect: this layout.
deepseek-harness/            # checkout root (run-from-source completed)
└── scratch-plugin/
    ├── src/my-plugin.ts     # all five examples go here
    └── cordis.yml           # overlay config
  1. Write the overlay config — create scratch-plugin/cordis.yml with name pointing at the plugin source by absolute path:
yaml
# scratch-plugin/cordis.yml
- insert:
    - id: demo
      name: '/absolute/path/to/deepseek-harness/scratch-plugin/src/my-plugin.ts'
  1. Start — run pnpm dsh web --patch ./scratch-plugin/cordis.yml. Expect: the Web UI comes up and mounts my-plugin.ts into the plugin tree.

The path must be absolute, and a patch contributes configuration without changing the profile directory used to resolve module paths. If your environment is not ready, see environment setup.

DSH plugin example one: the minimal plugin

Purpose: verify the environment and the load path. A single apply that logs:

ts
import type { Context } from '@deepseek-ai/cordis'

export const name = 'hello-plugin'

export function apply(ctx: Context) {
  console.log('[hello-plugin] plugin loaded!')
}

Expected result: the terminal prints [hello-plugin] plugin loaded! at startup. If that line never appears, stop and check the path and the build before going further.

DSH plugin example two: a model tool

Purpose: let the model call your capability. Note the inject plus defineTool trio:

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

export const name = 'greet-tool'
export const inject = ['tools']

export function apply(ctx: Context) {
  ctx.tools.register(defineTool({
    name: 'greet',
    description: 'Greet someone by name.',
    parameters: {
      name: { type: 'string', required: true, description: 'The name to greet' },
    },
    output: {
      schema: { type: 'string' },
      render: (_args, value) => [{ type: 'text', text: value }],
    },
    async execute(args) {
      return `Hello, ${args.name}!`
    },
  }))
}

Expected result: ask the model "Use the greet tool to greet Ada." and it calls greet, receiving Hello, Ada!. The full execute contract and card rendering rules are in how to write a tool plugin.

DSH plugin example three: event listener and cleanup

Purpose: hook a framework moment and release manual resources correctly. Register listeners with ctx.on and hand over a disposer with ctx.effect:

ts
import type { Context } from '@deepseek-ai/cordis'

export const name = 'heartbeat-plugin'

export function apply(ctx: Context) {
  ctx.effect(() => {
    const timer = setInterval(() => console.log('heartbeat'), 5000)
    // The returned function runs when the plugin unloads.
    return () => clearInterval(timer)
  })
}

Expected result: heartbeat prints every 5 seconds while the plugin runs, and stops after it unloads. Putting the timer directly in apply without ctx.effect leaves it running after unload — the most common source of resource leaks.

DSH plugin example four: a configured plugin

Purpose: let users change your behavior. Declare a Config type with a same-named schema, and take a second apply argument:

ts
import type { Context } from '@deepseek-ai/cordis'
import Schema from '@deepseek-ai/schemastery'

export interface Config {
  greeting: string
  maxRetries: number
}

export const Config: Schema<Config> = Schema.object({
  greeting: Schema.string().default('Hello'),
  maxRetries: Schema.number().default(3),
})

export const name = 'configured-plugin'

export function apply(ctx: Context, config: Config) {
  console.log(`${config.greeting} (retries: ${config.maxRetries})`)
}

Expected result: with no config it prints the defaults Hello (retries: 3); with partial config, missing fields are filled from the defaults. How config surfaces in the UI is covered in plugin configuration.

DSH plugin example five: providing a service

Purpose: let other plugins call you. This is when class form is warranted — register the service name in the constructor, and consumers declare it with inject:

ts
import { Service, type Context } from '@deepseek-ai/cordis'

export default class MetricsService extends Service {
  static inject = ['tools']

  constructor(ctx: Context) {
    super(ctx, 'metrics')
    // Perform synchronous initialization here.
  }

  count(name: string) {
    console.log(`[metrics] ${name}`)
  }
}

Expected result: once loaded, another plugin with export const inject = ['metrics'] can use ctx.metrics inside apply. Use class form only when providing a service; ordinary plugins are simpler in function form (source).

From DSH plugin examples to an installable package

These examples only run inside the checkout; they cannot be distributed yet. To make it installable, add three things to package.json: main (pointing at the entry), type: module, files (including the entry and cordis.patch.yml), and dsh.bundle.patch (pointing at that patch file) — inside a project directory that ships the patch.

Distribution path: first follow packaging into a bundle to produce an installable artifact, then publish to the plugin hub. After installing, check the plugin and its config in the installed list of DSH Plugin Hub. The hard self-checks for the code itself are in the development spec.

FAQ

Which DeepSeek Harness plugin example should I run first?

Start with the **minimal DSH plugin**: just a name and an apply that logs a line, mounted through a --patch overlay. Seeing that line prints means the environment and load path are both fine. **The official tutorial also puts the minimal plugin first**, because every later example is just registration code added to that skeleton (source: official "Your first plugin").

How should the directory structure look in a DeepSeek Harness plugin example?

**A DSH plugin example's directory structure has two stages**: while debugging locally the official path creates scratch-plugin/src/ at the repository root and mounts it by absolute path; to distribute, you move the project out with a package.json (main, type: module, files, dsh.bundle.patch) and a cordis.patch.yml. **The two are not either/or**: get it working in the checkout first, then extract it into an installable package (source: official "Your first plugin" and "Publish").

Is there one example that shows both a tool and configuration?

**To show a tool and configuration at once in a DSH plugin, stack the tool example on the configuration example**: declare inject = ['tools'] plus a Config schema, then use config inside apply(ctx, config) when building the tool's description or behavior. **The second apply argument is the framework-completed config object**, so it is safe to use directly (source: official "Build a tool" and "Plugin configuration").

How does the service example differ from an ordinary plugin?

**A DSH plugin service example uses class form**: export default class ... extends Service, with super(ctx, 'myService') in the constructor to register the service name on the context; other plugins consume it with inject: ['myService']. An ordinary plugin stops at registering capabilities, while a service plugin also exposes an instance — so use class form only when another plugin must call you (source: official "Services and dependencies").

Related Terms

scratch-plugin
scratch-plugin is the example directory the DSH plugin tutorial uses for local debugging. It sits at the checkout root and is mounted by absolute path through a --patch overlay; it is not part of distribution.https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/user/develop/basic/index.md
defineTool
defineTool is the DSH plugin helper that declares a model-callable tool. It takes name, description, parameters, output, and execute, and infers the execute arguments from parameters.https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/user/develop/basic/tool.md
Config schema
Config is how a DSH plugin declares its configuration types and defaults with Schemastery. The framework validates and completes the configuration on load, then passes the result as apply's second argument.https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/user/develop/basic/config.md
Service
Service is the base class for class-form DSH plugins. Calling super(ctx, name) in the constructor registers the service on the context, so other plugins reach it by declaring it in inject.https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/user/develop/framework/service.md

Sources