DeepSeek Harness
How to Build a DeepSeek Harness Plugin with Cordis
Create a minimal TypeScript plugin, insert it with a patch file, and understand cleanup and dependency injection from the official tutorial.

What you will learn
- A minimal plugin exports apply(ctx) and can be inserted through cordis.yml.
- Absolute module paths make the patch loader resolution explicit.
- Use inject for services and ctx.effect for explicit cleanup.
Before you start
- Basic HTTP and API knowledge
Leave with a concrete implementation checklist and a testable starting point.
Key takeaways
- A minimal plugin exports apply(ctx) and can be inserted through cordis.yml.
- Absolute module paths make the patch loader resolution explicit.
- Use inject for services and ctx.effect for explicit cleanup.
The minimal plugin contract
The official first-plugin tutorial defines a plugin as a TypeScript module exporting an apply function. The function receives a Cordis Context and registers capabilities through that context; the tutorial's minimal example only logs when it loads.
Function form is enough for many plugins. Object and class forms are available when you need declared dependencies or a service class, but choose the smallest form that matches the capability you are adding.
Load it through a patch
Create a scratch project, use an absolute path to the plugin module, and insert it in a cordis.yml overlay. Then launch `pnpm dsh web --patch ./scratch-plugin/cordis.yml`; the tutorial expects the plugin's load message during startup.
The absolute path requirement is intentional: a patch contributes configuration but does not change the profile directory from which the loader resolves module paths. Verify the path with `pwd` before starting the UI.
Dependencies and cleanup
If a plugin consumes a service such as tools or llm, declare it in inject. Cordis waits for required services before loading the plugin, so apply can use the declared context contract.
Registrations made through ctx are cleaned up when the plugin unloads. For timers or network connections that need explicit disposal, return a disposer from ctx.effect(). This makes reload and failure paths observable rather than leaky.
Decision guide
| Criterion | Option A | Option B |
|---|---|---|
| Best when | You need predictable behavior and easy auditing | You need adaptive optimization and have reliable telemetry |
| Main risk | May leave performance on the table | Can become difficult to explain or debug |
Implementation steps
- 1
Create a scratch TypeScript module with an apply function.
- 2
Insert its absolute path in a Web patch file.
- 3
Launch with `--patch` and confirm the load message.
- 4
Add inject and effect only when the plugin needs them.
Copy-ready example
import type { Context } from '@deepseek-ai/cordis'
export const name = 'hello-plugin'
export function apply(ctx: Context) {
console.log('[hello-plugin] plugin loaded!')
}Frequently asked questions
When should a plugin use a class?
Use class form when the plugin provides a service to other plugins; function form is sufficient for many registrations.
Sources
- DeepSeek Harness first plugin tutorialSource checked 2026-08-27