Ri0n72Y
dsh-plugin-practice
learn how to write cordis dsh plugin
- Stars
- 0
- Language
- TypeScript
- Created
- Aug 14, 2026
- Updated
- Aug 14, 2026
Introduction
dsh-plugin-practice
A minimal practice repository for learning DeepSeek Harness plugin development one concept at a time.
Lesson 1: plugin lifecycle
This first example keeps the surface area deliberately small. It demonstrates only:
- a Cordis function plugin;
- the
apply(ctx)entry point; - lifecycle-owned resources with
ctx.effect(); - loading the plugin into DSH with a patch file.
Files
dsh-plugin-practice/
├── README.md
├── cordis.patch.yml
└── src/
└── plugin.ts
Plugin
src/plugin.ts contains a small heartbeat resource:
import type { Context } from '@deepseek-ai/cordis'
export const name = 'practice-lifecycle'
export function apply(ctx: Context): void {
console.log('[practice-lifecycle] loaded')
ctx.effect(() => {
const timer = setInterval(() => {
console.log('[practice-lifecycle] heartbeat')
}, 5_000)
return () => {
clearInterval(timer)
console.log('[practice-lifecycle] disposed')
}
}, 'practice-lifecycle.heartbeat()')
}
The important relationship is:
flowchart LR
Loader["Cordis loader"] -->|"apply(ctx)"| Plugin["practice-lifecycle plugin"]
Plugin --> Effect["ctx.effect()"]
Effect --> Timer["setInterval"]
Plugin -. unload .-> Cleanup["effect disposer"]
Cleanup --> Timer
The timer is created by the plugin effect and its cleanup function is owned by the same lifecycle. When the plugin is unloaded or replaced, Cordis runs the disposer.
Load it from a DSH source checkout
The patch file contains a placeholder because the local TypeScript module must be referenced by an absolute path when it is loaded as an external patch during this exercise.
- Clone this repository somewhere on your machine.
- Edit
cordis.patch.ymland replace/ABSOLUTE/PATH/TO/dsh-plugin-practicewith the repository's absolute path. - From the
deepseek-harnesssource checkout, run:
pnpm dsh web --patch /ABSOLUTE/PATH/TO/dsh-plugin-practice/cordis.patch.yml
- Watch the terminal. You should see:
[practice-lifecycle] loaded
[practice-lifecycle] heartbeat
When the plugin is unloaded or replaced, you should also see:
[practice-lifecycle] disposed
What to understand before Lesson 2
For now, focus on four questions:
- Who calls
apply(ctx)? - What does the
ctxobject represent? - Why is the timer created inside
ctx.effect()? - What event causes the disposer returned from
ctx.effect()to run?
Once these are clear, the next lesson can add inject = ['tools'] and register a real DSH Tool without treating the framework as a black box.