Your First Cordis Plugin

What is Cordis

Cordis is the plugin framework vendored underneath DeepSeek Harness. Five core concepts:

  • A plugin is an object that implements a capability: a function with optional inject and apply(ctx) fields, or a Service subclass;
  • A context is a container of services: each service lives at a stable ctx.<key> (e.g. ctx.tools, ctx.llm) and is looked up by key rather than imported;
  • inject declares dependencies: a plugin starts only when its required services are ready; load order follows dependencies, not manual sequencing;
  • Typed events are for communication: dispatched via emit, waterfall, parallel, serial and friends;
  • Registrations are reversible side effects: anything installed via ctx.effect() or ctx.on() is torn down automatically on unload.

The minimal plugin shape

A plugin is a module exporting an apply function; the framework calls it with ctx on load:

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

export const name = 'my-plugin'

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

name is optional display metadata used in diagnostics. The function form is the most common; object form ({ name, inject, apply }) and class form (extending Service) are also supported. Function form is enough in most cases.

Register a tool

This complete example registers a greet tool on the harness tools service, callable by the model:

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}!`
    },
  }))
}

How it works:

  • inject: ['tools'] makes Cordis wait for the tool registry, so ctx.tools is safe to use in apply;
  • defineTool converts parameters into the JSON Schema shown to the model, and derives and validates args;
  • execute returns a canonical value declared by output.schema; output.render turns it into model-facing content;
  • the ctx.tools.register(...) registration is an effect, so the tool is deregistered automatically when the plugin unloads.

Load it in DSH

During development, mount the plugin into the Web UI with a --patch overlay. Create scratch-plugin/cordis.yml:

- insert:
    - id: greet
      name: '/absolute/path/to/your-plugin/src/greet-tool.ts'

The plugin path must be absolute. Then start with the overlay (pnpm dsh web from a source checkout, dsh web when installed):

pnpm dsh web --patch ./scratch-plugin/cordis.yml

Open http://127.0.0.1:3080 and send Use the greet tool to greet Ada. — the model can call greet and receives Hello, Ada!. The terminal logs the tool call and its result event.

To publish for others, package the plugin as a bundle declaring dsh.bundle and follow the dsh plugin --profile web add flow in Installing DSH Plugins.