DeepSeek Harness(DSH)插件开发保姆级教程:从 0 到 1 快速写出第一个插件
最近 DeepSeek 开源了自己的 Agent Harness ------ DeepSeek Harness,简称 DSH。
如果你用过 Claude Code、OpenClaw、MCP 或者各种 Agent Framework,会发现 DSH 一个非常有意思的地方:
Everything is a Plugin,一切皆插件。
模型、Tools、Hooks、Session、UI、Sandbox,甚至 Agent 本身的很多能力,都可以通过插件组合。
DSH 官方目前仍处于 Developer Preview 阶段,插件 API 还可能出现不兼容更新,所以现在非常适合学习它的插件机制,但生产环境最好锁定版本。
这篇文章不讲太多晦涩的架构理论,直接带你从 0 开始:
- 搭建 DSH
- 创建插件项目
- 注册一个 Tool
- 本地运行
- 安装到 DSH
- 调试插件
- 理解
apply / ctx / inject - 理解
cordis.patch.yml - 发布插件
- 最后再给一份可以直接交给 Codex / Claude Code 的插件开发 Prompt
如果你本身做过 Node.js / TypeScript,基本上十几分钟就可以把第一个插件跑起来。
一、DSH 插件到底是什么?
先看 DSH 官方给出的最核心定义。
一个最简单的 DSH 插件,本质上就是一个导出了 apply() 方法的 TypeScript 模块:
ts
import type { Context } from '@deepseek-ai/cordis'
export const name = 'my-plugin'
export function apply(ctx: Context) {
// 在这里注册插件能力
}
就这么简单。
DSH 加载插件时,会调用:
ts
apply(ctx)
然后把一个 Context 对象交给插件。
插件通过 ctx 获取或者注册各种能力,例如:
ts
ctx.tools
ctx.llm
ctx.on(...)
ctx.effect(...)
官方把这种设计建立在 Cordis 插件框架之上。
你可以粗暴地把它理解成:
text
DSH
│
├── Cordis
│ │
│ ├── Plugin A
│ ├── Plugin B
│ ├── Plugin C
│ └── Plugin D
│
└── Agent Runtime
插件并不是传统意义上的"往程序里塞一个脚本"。
它更像是:
向 DSH Runtime 动态注册一个能力模块。
二、DSH 插件可以做什么?
目前常见的插件大概可以分成几类。
| 类型 | 作用 |
|---|---|
| Tool Plugin | 给 Agent 增加工具 |
| Event / Hook Plugin | 监听或拦截 Agent 生命周期 |
| Service Plugin | 给其他插件提供服务 |
| LLM Plugin | 接入新的模型 Provider |
| Session Plugin | 处理会话、记忆、持久化 |
| Workflow Plugin | 编排多 Agent / 自动任务 |
| WebUI Plugin | 扩展 DSH Web UI |
| Policy Plugin | 权限、审批、安全控制 |
例如我们可以开发:
text
天气查询
Git 工具
股票数据查询
数据库查询
网页搜索
企业知识库
RAG
飞书机器人
任务通知
定时任务
代码扫描
Agent 协作
日志追踪
审批系统
这些都可以变成 DSH Plugin。
官方的 Tool Pipeline 甚至允许插件介入:
text
tools/pre-execute
↓
guards
↓
tools/execute
↓
tools/post-execute
↓
tools/result
所以像:
text
权限控制
日志记录
重试
超时
审计
指标监控
都可以通过插件完成。
三、准备开发环境
首先需要 Node.js。
DSH 可以直接通过 npm 运行:
bash
npx @deepseek-ai/dsh web
启动成功后默认访问:
text
http://127.0.0.1:3080
官方也支持直接从源码运行:
bash
git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness
pnpm install
pnpm run build
pnpm dsh web
如果后面要使用:
bash
dsh plugin
建议提前安装 pnpm。
例如:
bash
npm install -g pnpm
因为目前:
bash
dsh plugin --profile xxx add xxx
底层实际上会在 Profile 目录中调用 pnpm。
四、最快的方式:直接使用插件脚手架
如果只是想快速写插件,我不建议从零手写所有配置。
社区目前已经有:
text
create-dsh-plugin
可以快速生成 DSH Plugin 项目。
例如创建 Tool 插件:
bash
npx create-dsh-plugin@latest dsh-text-stats -t tool
如果希望创建之后顺便验证:
bash
npx create-dsh-plugin@latest dsh-text-stats -t tool --verify
目前脚手架主要提供:
text
tool
events
webui
等模板。
生成后进入目录:
bash
cd dsh-text-stats
安装依赖:
bash
pnpm install
五、看看 DSH 插件项目到底有哪些东西
一个典型的 DSH Tool Plugin,大致会长这样:
text
dsh-text-stats/
├── src/
│ └── index.ts
│
├── cordis.patch.yml
├── package.json
├── tsconfig.json
└── README.md
这里真正需要理解的只有三个东西:
text
src/index.ts
package.json
cordis.patch.yml
六、第一部分:src/index.ts
我们来写一个非常简单的工具:
text
text_stats
功能是:
让 Agent 可以统计一段文本的字符数、行数和单词数。
修改:
text
src/index.ts
代码如下:
ts
import type { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'
export const name = 'text-stats-tool'
export const inject = ['tools']
export function apply(ctx: Context) {
ctx.tools.register(
defineTool({
name: 'text_stats',
description:
'Calculate character count, line count and word count for a piece of text.',
parameters: {
text: {
type: 'string',
required: true,
description: 'The text to analyze',
},
},
output: {
schema: {
type: 'string',
},
render: (_args, value) => [
{
type: 'text',
text: value,
},
],
},
async execute(args) {
const text = args.text
const characters = [...text].length
const lines = text.length === 0
? 0
: text.split(/\r?\n/).length
const words = text.trim()
? text.trim().split(/\s+/).length
: 0
return JSON.stringify({
characters,
lines,
words,
})
},
}),
)
}
到这里,我们已经完成了一个真正可以被 Agent 调用的 DSH Tool。
官方推荐的 Tool 写法同样是:
ts
ctx.tools.register(
defineTool(...)
)
defineTool() 会负责参数定义、类型推导以及 Tool 输出约束。
七、理解最重要的四个概念
上面的代码看起来很多,真正需要掌握的其实只有四个东西。
1. name
ts
export const name = 'text-stats-tool'
表示:
text
插件名称
2. inject
这里非常关键:
ts
export const inject = ['tools']
意思是:
我的插件依赖
tools服务。
Cordis 会等:
text
ctx.tools
准备好之后,再执行这个插件。
如果以后需要其他能力,例如:
text
LLM
Session
Storage
Jobs
同样需要声明对应依赖。
官方文档明确说明,inject 用来保证依赖服务已经就绪。
3. apply
ts
export function apply(ctx: Context)
这是插件入口。
可以理解成:
text
main()
但是它不是程序入口,而是:
text
Plugin Lifecycle Entry
DSH 加载插件时调用它。
4. ctx
ctx 是整个 DSH 插件体系的核心。
例如:
ts
ctx.tools
代表 Tool Registry。
于是:
ts
ctx.tools.register(...)
就是:
往 DSH 的 Tool Registry 注册一个工具。
所以整个插件开发逻辑实际上非常清晰:
text
DSH
↓
Context
↓
Plugin
↓
Register Capability
八、defineTool 是什么?
我们再拆开:
ts
defineTool({
name,
description,
parameters,
output,
execute
})
这其实非常像 OpenAI Function Calling。
例如:
ts
parameters: {
text: {
type: 'string',
required: true
}
}
本质就是告诉模型:
text
这个 Tool 需要什么参数。
而:
ts
execute(args)
是真正执行 Tool 的地方。
模型可能产生:
json
{
"text": "hello world"
}
然后 DSH 调用:
ts
execute({
text: "hello world"
})
最终返回:
json
{
"characters": 11,
"lines": 1,
"words": 2
}
九、构建插件
代码写好以后执行:
bash
pnpm run build
正常情况下会生成:
text
dist/
或者脚手架配置的其他构建目录。
如果这里就报 TypeScript 类型错误,先不要急着安装插件。
原则是:
text
Build 成功
↓
再安装
↓
再启动 DSH
这样排查问题最快。
十、cordis.patch.yml 到底有什么用?
这是很多第一次写 DSH Plugin 的人最容易迷糊的地方。
例如:
yaml
- insert:
- id: text-stats
name: dsh-text-stats
它的意思其实就是:
把
dsh-text-stats插入当前 DSH Plugin Tree。
可以理解成:
text
DSH Plugin Tree
dsh-base
│
├── tools
├── llm
├── session
│
└── text-stats
DSH 的 Profile 本质上就是很多 Bundle Patch 按顺序叠加之后形成的一棵插件树。官方架构文档将运行配置描述为由多个 Bundle Layer 组合而成。
十一、package.json 为什么还有 dsh.bundle?
你会看到类似:
json
{
"dsh": {
"bundle": {
"patch": "./cordis.patch.yml"
}
}
}
这句话非常重要。
它是在告诉 DSH:
text
这个 npm package 不只是普通依赖。
它还是一个 DSH Bundle。
它对应的 Bundle Patch 就是:
text
cordis.patch.yml
如果没有:
json
"dsh": {
"bundle": {
"patch": "./cordis.patch.yml"
}
}
那么即使:
bash
dsh plugin add
安装成功,这个包也可能只是普通 npm dependency,而不会自动变成 DSH 的组合层。
官方打包规范也是通过 dsh.bundle.patch 来声明插件 Bundle。
十二、把插件安装到 DSH
现在开始真正安装。
例如安装到 Web Profile:
bash
npx @deepseek-ai/dsh plugin \
--profile web \
add ./dsh-text-stats
或者如果你已经有 dsh CLI:
bash
dsh plugin --profile web add ./dsh-text-stats
DSH 会把插件安装到:
text
Profile
中。
Profile 可以理解为:
一套 DSH 运行环境。
例如官方内置:
text
web
headless
不同 Profile 可以拥有不同插件组合。
十三、安装完不要急着启动,先做这个检查
推荐执行:
bash
dsh --profile web --dump-config
然后搜索:
text
text-stats
如果可以看到类似:
text
text-stats
dsh-text-stats
说明:
text
Bundle
↓
Patch
↓
Profile
已经成功组合。
官方也推荐使用 --dump-config 查看机器最终实际启动的插件树。
十四、启动 DSH
启动:
bash
npx @deepseek-ai/dsh web
打开:
text
http://127.0.0.1:3080
然后直接告诉模型:
text
请使用 text_stats 工具统计下面这段文本:
Hello DeepSeek Harness
This is my first DSH plugin.
如果一切正常,Agent 就会发起类似:
text
text_stats(...)
的 Tool Call。
到这里,你的第一个 DSH Plugin 就正式跑通了。
十五、不使用脚手架,官方是怎么开发插件的?
如果你想真正理解 DSH,也可以直接在官方源码里开发。
首先:
bash
git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness
pnpm install
pnpm run build
创建:
bash
mkdir -p scratch-plugin/src
然后:
text
scratch-plugin/src/my-plugin.ts
写:
ts
import type { Context } from '@deepseek-ai/cordis'
export const name = 'hello-plugin'
export function apply() {
console.log('Hello DSH Plugin')
}
创建:
text
scratch-plugin/cordis.yml
加入:
yaml
- insert:
- id: hello
name: '/绝对路径/deepseek-harness/scratch-plugin/src/my-plugin.ts'
注意:
本地 Patch 加载插件时官方要求这里使用绝对路径。
然后:
bash
pnpm dsh web --patch ./scratch-plugin/cordis.yml
就可以临时加载。
这种方式非常适合:
text
插件开发
快速调试
研究源码
测试 Hook
测试 Service
官方第一个插件教程就是采用这种方式。
十六、本地开发和正式安装有什么区别?
可以简单理解:
开发阶段
text
--patch
例如:
bash
pnpm dsh web --patch ./my-plugin/cordis.yml
优点:
text
修改快
调试快
不用反复安装
正式使用
使用:
bash
dsh plugin --profile web add xxx
插件进入:
text
Profile
并长期存在。
所以我的推荐工作流是:
text
写代码
↓
--patch 调试
↓
build
↓
plugin add
↓
dump-config
↓
正式运行
十七、DSH Hook 插件怎么写?
Tool 只是插件的一种。
DSH 还可以监听生命周期事件。
例如想记录所有 Tool 的执行结果:
ts
import type { Context } from '@deepseek-ai/cordis'
import type {} from '@deepseek-ai/dsh-tools'
export const name = 'tool-logger'
export const inject = ['tools']
export function apply(ctx: Context) {
ctx.on('tools/result', (exec) => {
console.log(
`[tool] ${exec.name} finished`
)
})
}
这样以后 Agent 调用:
text
bash
read_file
write_file
text_stats
都可以被观察。
DSH 官方提供的扩展点还包括:
text
tools/pre-execute
tools/execute
tools/post-execute
tools/result
例如:
text
pre-execute
可以做:
text
权限判断
危险命令阻止
参数校验
审批
而:
text
post-execute
可以做:
text
结果转换
数据脱敏
日志附加
十八、插件如何清理资源?
假设插件里面启动了:
text
Timer
Socket
数据库连接
Watcher
可以使用:
ts
ctx.effect(() => {
const timer = setInterval(() => {
console.log('running')
}, 5000)
return () => {
clearInterval(timer)
}
})
插件卸载时:
text
return function
会负责清理资源。
而通过 ctx 注册的 Tool、Event、Timer 等能力,Cordis 本身会跟踪生命周期。
这是 DSH 插件机制比普通 Node.js 脚本更舒服的地方之一。
十九、DSH Plugin、MCP、Skill 到底有什么区别?
这个地方非常值得理解。
很多人会把:
text
Plugin
MCP
Skill
混在一起。
其实它们不是一个层级。
可以粗略理解:
text
DSH
│
┌───────┴────────┐
│ │
Plugin Skill
│
│
├── Tool
│
├── Hook
│
├── UI
│
├── Workflow
│
├── Model
│
└── MCP Client
│
▼
MCP Server
Skill
更像:
text
告诉 Agent 怎么完成一类任务
核心是:
text
Prompt + Instructions + Workflow
MCP
解决的是:
text
Agent 如何通过统一协议调用外部能力。
例如:
text
GitHub
Postgres
Browser
Filesystem
Plugin
范围最大。
Plugin 可以:
text
注册 Tool
实现 MCP Client
修改 UI
监听 Hook
实现 Workflow
提供 Service
接入 LLM
所以:
MCP 可以成为 DSH Plugin 的一部分,但 Plugin 并不等于 MCP。
二十、最推荐的插件开发架构
对于稍微复杂一点的插件,我不建议所有代码全部放:
text
index.ts
推荐:
text
src/
├── index.ts
│
├── tools/
│ ├── search.ts
│ ├── analyze.ts
│ └── report.ts
│
├── services/
│ └── data-service.ts
│
├── schemas/
│ └── config.ts
│
├── clients/
│ └── api-client.ts
│
└── utils/
└── logger.ts
其中:
text
index.ts
只负责:
text
Plugin Registration
真正业务逻辑放:
text
Service
Client
Utils
例如:
text
Agent
↓
Tool
↓
Service
↓
API Client
↓
External System
这样以后你想:
text
增加第二个 Tool
增加 WebUI
增加 Workflow
都比较容易。
二十一、一个真正实用的 DSH 插件可以长什么样?
比如我们做一个:
text
dsh-stock-research
可以提供:
text
search_stock
get_kline
get_financials
get_news
analyze_stock
Agent 使用:
text
用户:
分析一下某只股票最近走势
DSH:
text
LLM
↓
search_stock
↓
get_kline
↓
get_financials
↓
get_news
↓
LLM Analyze
↓
Report
再比如:
text
dsh-devops
可以注册:
text
query_logs
query_metrics
restart_service
query_k8s
query_sentry
于是 Agent 就变成了一个:
text
DevOps Agent
这也是我认为 DSH 插件生态真正有价值的地方。
不是简单做几个:
text
Hello World Tool
而是:
把真实业务系统封装成 Agent 可以组合调用的能力。
二十二、插件开发最容易踩的几个坑
坑 1:DSH 现在变化比较快
目前官方仍然明确标记:
text
Developer Preview
并提醒:
text
可能出现 Breaking Changes
所以建议插件项目锁版本,不要所有依赖长期使用:
text
latest
线上插件升级前先进行兼容测试。
坑 2:忘记 inject
例如使用:
ts
ctx.tools
却没有:
ts
export const inject = ['tools']
这是非常典型的问题。
坑 3:package.json 没有 dsh.bundle
如果没有:
json
{
"dsh": {
"bundle": {
"patch": "./cordis.patch.yml"
}
}
}
插件可能只是:
text
npm dependency
而没有真正加入 Profile Bundle Stack。
坑 4:本地 --patch 使用相对路径
官方源码开发教程里:
yaml
name:
需要使用插件文件的:
text
绝对路径
因为插件解析基于 Profile 环境。
坑 5:装完插件忘记 dump-config
建议养成习惯:
bash
dsh --profile web --dump-config
确认:
text
插件真的进入 Plugin Tree
再启动。
坑 6:同一个插件加载两次
如果之前已经通过:
text
profile/cordis.patch.yml
手动 insert 插件,后来又:
bash
dsh plugin add
把这个插件作为 Bundle 安装,就可能出现:
text
duplicate loader entry id
因为同一个插件被加入了两遍。
社区已经有人遇到过这个问题。
所以原则是:
text
正式 Bundle 安装后
不要再手动 insert 同一插件。
二十三、插件怎么发布?
当插件完成以后,可以发布到:
text
npm
也可以直接维护:
text
GitHub Repository
正式 Bundle 至少需要包含:
text
package.json
入口 JS
cordis.patch.yml
并声明:
json
{
"dsh": {
"bundle": {
"patch": "./cordis.patch.yml"
}
}
}
例如:
yaml
- insert:
- id: my-plugin
name: my-dsh-plugin
用户就可以:
bash
dsh plugin --profile web add my-dsh-plugin
安装。
如果维护 GitHub Repo,也建议添加:
text
dsh-plugin
GitHub Topic。
官方 README 也推荐通过这个 Topic 提升插件的可发现性。
二十四、以后写 DSH 插件,我推荐这个开发流程
我自己更推荐:
text
1. 明确插件能力
↓
2. 判断 Tool / Hook / Service / UI
↓
3. create-dsh-plugin 创建项目
↓
4. 写最小 Tool
↓
5. pnpm build
↓
6. --patch 本地调试
↓
7. 编写单元测试
↓
8. plugin add
↓
9. dump-config
↓
10. Web / Headless 实测
↓
11. pnpm pack
↓
12. GitHub / npm 发布
不要一开始就搞:
text
10 个 Tool
5 个 Service
多 Agent
数据库
WebUI
最好先让一个能力跑通。
然后逐渐增加:
text
Tool
↓
Service
↓
Hook
↓
Workflow
↓
WebUI
二十五、让 Codex / Claude Code 直接帮你开发 DSH Plugin
最后给一个我比较推荐的提示词。
以后想开发插件,可以直接把下面内容交给 Codex、Claude Code 或其他 Coding Agent。
text
你是一名熟悉 DeepSeek Harness(DSH)、Cordis 和 TypeScript 的高级 Agent 插件开发工程师。
请帮我开发一个 DeepSeek Harness Plugin。
插件名称:
dsh-xxx
插件目标:
【填写插件功能】
技术要求:
1. 使用 TypeScript。
2. 遵循 DeepSeek Harness 当前 Plugin 架构。
3. 插件入口使用 apply(ctx: Context)。
4. 正确声明 inject 依赖。
5. Agent Tool 使用:
@deepseek-ai/dsh-tools
defineTool()
ctx.tools.register()
6. Tool 必须提供:
- name
- description
- parameters
- output.schema
- output.render
- execute
7. 业务逻辑不要全部堆在 index.ts。
8. 推荐目录:
src/
index.ts
tools/
services/
clients/
schemas/
utils/
9. package.json 必须包含:
"dsh": {
"bundle": {
"patch": "./cordis.patch.yml"
}
}
10. 提供正确的 cordis.patch.yml。
11. 插件必须支持:
pnpm install
pnpm build
12. 给出本地调试方法。
13. 给出安装方式:
dsh plugin --profile web add ./plugin
14. 给出:
dsh --profile web --dump-config
验证方式。
15. 如果涉及 Timer、Socket、Watcher、数据库连接等资源,
必须通过 Cordis 生命周期机制正确释放。
16. Tool 的业务实现与 DSH 注册逻辑分离。
17. 给插件增加:
README.md
错误处理
日志
单元测试
.gitignore
18. 不要修改 DeepSeek Harness 源码。
19. 优先使用 DSH 官方公开 Extension Point,
不要侵入 Agent Loop 内部实现。
20. 完成以后输出:
- 项目目录
- 每个文件作用
- 完整代码
- 安装步骤
- 调试步骤
- 测试步骤
- 发布步骤
- 常见问题
先分析插件应该属于 Tool、Hook、Service、Workflow 还是 WebUI,
然后再开始生成代码。
这套 Prompt 基本可以直接让 Coding Agent 帮你把 DSH Plugin 的骨架搭出来。
总结
如果只记住 DSH 插件开发的核心,我认为就记住下面这几个东西:
text
apply(ctx)
代表:
text
插件入口
text
inject
代表:
text
插件依赖
text
ctx.tools.register()
代表:
text
向 Agent 注册 Tool
text
cordis.patch.yml
代表:
text
把插件挂载到 DSH Plugin Tree
text
dsh.bundle
代表:
text
把 npm package 声明成可安装的 DSH Bundle
text
dsh plugin add
代表:
text
把插件安装到 Profile
整个流程就是:
text
TypeScript Plugin
↓
apply
↓
Cordis
↓
DSH Context
↓
Tool / Hook / Service
↓
Bundle
↓
Profile
↓
Agent
一旦理解这个模型,DSH 插件开发其实并不复杂。
真正值得做的,是把我们原来已有的:
text
Python 服务
Go 服务
数据库
内部 API
爬虫
RAG
搜索系统
数据平台
CI/CD
监控系统
逐步封装成:
text
Agent 可以理解
Agent 可以调用
Agent 可以组合
Agent 可以编排
的 DSH Plugin。
这才是 Everything is a Plugin 真正有意思的地方。