本文档基于上游仓库
deepseek-ai/deepseek-harness(克隆于本目录deepseek-harness/,已加入.gitignore)的官方文档整理,作为本仓库(dsh-plugins)开发 dsh 插件的参考。上游处于 developer preview 阶段,存在破坏性变更风险。本文所有路径引用均相对于
deepseek-harness/。
DeepSeek Harness(dsh)是 DeepSeek AI 开发的开源 Agent Harness(智能体运行框架),底层基于 Cordis 插件框架(以 vendor 方式引入为 @deepseek-ai/cordis)。
核心架构理念:一切皆插件。模型适配器、工具注册表、会话日志、甚至 agent loop 本身都是插件,因此每一部分都可以从配置替换。不存在需要打补丁的特权内核——扩展 dsh 的方式是把插件挂载到其他插件旁边。
关键目录:
| 路径 | 内容 |
|---|---|
docs/user/develop/ |
面向插件作者的教程(入门、工具、配置、发布、框架) |
docs/cookbook/ |
扩展实操手册(添加包/工具/LLM 适配器/Chat 节点/设置卡片) |
docs/architecture.zh.md |
整体架构与扩展点归属映射 |
docs/cordis-primer.zh.md |
Cordis 框架入门 |
packages/ |
全部核心包(monorepo,pnpm workspace) |
examples/ |
可运行示例(mcp-memory、headless-agent、web-schedule 等) |
理解插件机制的基础(详见 docs/cordis-primer.zh.md):
inject 和 apply(ctx) 字段的函数,也可以是 Service 子类,生命周期由 Cordis 管理。ctx.<key>(如 ctx.tools、ctx.llm、ctx.sessions);其他插件通过 key 查找服务,而非导入具体实现。inject 声明服务依赖——插件等待所需服务就绪后才启动;加载顺序由服务依赖表达,无需手动编排。emit、waterfall、parallel、serial(另有 bail)分发。ctx.effect() 或 ctx.on() 安装,reload/teardown 时自动撤销。插件是导出 apply 函数的 TypeScript/JavaScript 模块(详见 docs/user/develop/basic/index.zh.md):
import type { Context } from '@deepseek-ai/cordis'
export const name = 'hello-plugin'
export const inject = ['tools'] // 可选:声明依赖的服务,就绪后才执行 apply
export function apply(ctx: Context) {
// ctx.tools 已就绪,在此注册能力
}
三种等价形态:
export default { name, inject, apply(ctx) {} }。export default class MyService extends Service,构造函数中 super(ctx, 'myService') 占据 ctx.myService。PENDING → LOADING → ACTIVE
↘ FAILED
ACTIVE → UNLOADING → DISPOSED
inject 的插件在依赖服务就绪后加载;依赖消失时自动卸载,恢复后重新加载。ctx.on()、ctx.tools.register()、ctx.llm.registerAdapter() 等注册随插件卸载自动撤销;自定义资源用 ctx.effect(() => disposer) 声明清理逻辑。cordis.yml 配置或(通过 cordis-plugin-hmr)源文件会卸载旧实例、加载新实例。导出 Config(Schemastery schema)即可让用户在 cordis.yml 中传配置(详见 docs/user/develop/basic/config.zh.md):
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 function apply(ctx: Context, config: Config) {
console.log(config.greeting)
}
设计约定:
cordis.yml 就改变该值?Config,必须满足 Standard Schema 接口。面向模型的工具通过 defineTool + ctx.tools.register() 注册(详见 docs/user/develop/basic/tool.zh.md 与 docs/cookbook/adding-a-tool.zh.md):
import type { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'
export const name = 'my-tool'
export const inject = ['tools']
export function apply(ctx: Context) {
ctx.tools.register(defineTool({
name: 'read_file',
description: 'Read a file from disk.', // 模型可见描述
parameters: {
path: { type: 'string', required: true, description: 'Absolute path' },
limit: { type: 'number' }, // 默认可选
},
output: {
schema: { type: 'string' }, // 规范 JSON 值的 schema
render: (_args, value) => [{ type: 'text', text: value }], // 模型可见内容
},
async execute(args, exec) {
// args 已按 schema 自动校验并推导类型
// exec.signal 用于取消;exec.agent 可发送异步通知
return readFile(args.path, { encoding: 'utf8', signal: exec.signal })
},
}))
}
核心约定:
defineTool 在 execute 前按 parameters 校验模型生成的参数;schema DSL 表达不了的约束(非空、正数、跨字段)需手动检查。execute 只返回 output.schema 声明的值;不要返回内容块或迫使调用方解析自然语言。抛异常或返回无效值即 isError。exec.signal:信号触发时取消进行中的工作。await tools.<name>(args) 调用。presentCall(args) / presentResult(args, result) 返回渲染意图(generic / terminal / diff / search / web 卡片)。必须是纯函数(实时流式与日志回放都会执行,禁止 I/O、时钟、随机数)。run_in_background,用 ctx.jobs.start({ kind, label, owner: exec.agent, run }) 注册;返回 { kind: 'background', jobId } 类型的规范句柄。tools/pre-execute(允许/拒绝/询问)、tools/execute(截止时间/重试/指标)、tools/post-execute(改写展示或结果)、tools/result(观测不可变结果)等事件实现。生产级参考实现:packages/shell/tool-bash(三包结构)、packages/core/fs 中的工具。
事件是插件间松耦合通信的核心机制(详见 docs/user/develop/framework/events.zh.md):
| 模式 | 语义 |
|---|---|
emit |
广播;监听器按注册顺序同步观察,无返回值 |
bail |
短路;第一个非空返回值成为最终结果 |
serial |
顺序执行,await 异步结果,非空返回值终止后续 |
waterfall |
中间件链;监听器收到 (...args, next),必须调用 next() 才能委托下游,不调用即短路(用于拦截/网关) |
parallel |
所有监听器并行观察 |
namespace/action 约定(如 agent/pre-step、tools/result、session/event)。通过声明合并获得类型安全:
declare module '@deepseek-ai/cordis' {
interface Events {
'my-plugin/ready': (payload: { id: string }) => void
}
}
注意区分:turn/*、step/*、tool/call、tool/result 等是持久化会话事件类型(日志事实),观察它们要监听 session/event 并检查 event.type;agent/*、tools/* 才是实时 Cordis 事件。
单决策事件(如 agent/pre-step、agent/request、llm/stream、tools/* 三事件)是 waterfall;agent/turn-stopping 是 serial。
两个核心概念(详见 docs/user/develop/basic/publish.zh.md):
package.json 中 dsh.bundle 指向其 patch 文件。$DSH_HOME/profiles/<name> 的可启动组装目录,dsh.profile.bundles 按序列出组合包。由 dsh plugin 命令维护,无需手写。hello-plugin/
├── package.json # 声明 dsh.bundle
├── cordis.patch.yml # 被列入 profile 时应用的配置层
└── index.js # 插件入口
{
"name": "dsh-hello-plugin",
"version": "0.1.0",
"type": "module",
"main": "index.js",
"files": ["index.js", "cordis.patch.yml"],
"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }
}
cordis.patch.yml(插件行按包名引用,让 Node 模块解析找到已安装代码):
- insert:
- id: hello
name: dsh-hello-plugin
无需打包,直接以绝对路径插入本地源码:
- insert:
- id: hello
name: '/absolute/path/to/my-plugin/src/my-plugin.ts'
dsh web --patch ./cordis.yml # 仓库源码内用 pnpm dsh web
dsh plugin --profile demo add ./hello-plugin # 本地路径
dsh plugin --profile demo add github:you/hello-plugin # git(需 prepare 脚本 + allowBuilds 授权)
dsh plugin --profile demo add your-package # npm(预构建,无需授权)
dsh plugin --profile demo remove dsh-hello-plugin
dsh --profile demo --dump-config # 查看实际生效的配置树
生效配置按以下顺序逐层叠加(后应用的层按行胜出,patch 替换目标行整个 config,不做深度合并):
dsh.profile.bundles 所列各组合包 patch(按列表顺序,首个为 @deepseek-ai/dsh-base)cordis.patch.yml$DSH_HOME/cordis.patch.yml--patch <path> overlay(按 argv 顺序)推论:你的 patch 可按 id 覆盖前面层的行(须重述该行全部键);用户可在自己 profile 的 patch 中覆盖你的行(因此优先给出合理默认值)。
git 安装拉取源码而非构建产物,作者需提供自包含的 prepare 脚本(参考 turtle-ui);用户需在 profile 的 pnpm-workspace.yaml 中授权(allowBuilds: { dsh-hello-plugin: true })——这等于允许该包代码在安装时于本机执行,只对可信源码授权并锁定 commit。免授权的替代:发布到 npm(预构建 lib/)或交付 tarball(pnpm pack)。
新行为附加到已有扩展点(完整版见 docs/architecture.zh.md § 新行为的归属位置):
| 目标 | 机制 |
|---|---|
| 添加模型提供方 | 在 ctx.llm 上注册适配器 |
| 添加面向模型的能力(工具) | ctx.tools.register();schema 自动加入提示词组装 |
| 添加用户命令(无需模型轮次) | ctx.commands |
| 添加后台工作 | ctx.jobs;job_* 工具收集/停止 |
| 添加 shell / 终端执行 | 注册 ctx.shell 后端 / ctx.terminals 后端 |
| 添加文件系统访问或策略 | 注册 ctx.fs 提供方,或监听 fs/* 事件 |
| 限制所启动的进程 | ctx.sandbox 后端 |
| 拦截请求、工具或轮次 | agent/* 或 tools/* 事件;agent/turn-stopping 停止轮次 |
| 添加模型可见上下文 | agent.inject()(落入下一次获准的请求) |
| 添加 UI / Web Chat 节点 | 驱动 ctx.agents 并从 session/event 渲染;注册 ConversationNodeDefinition |
| 添加持久会话状态 | 扩展 SessionEventMap,从日志渲染和回放 |
| 生成会话标题 | 注册唯一的 ctx.sessionTitle 提供方 |
| fork 活跃会话 | ctx.sessions.fork(source, boundary?) |
| 将注册项限定到单个 agent | 使用该 agent 的 agent.ctx |
一个 seam 包含三种角色,可按需拆分为不同包:
fs/进程指向远程沙箱)ctx key;registry 用复数。docs/cookbook/adding-a-package.zh.md(本仓库开发树外插件无需遵循其 workspace 不变式,但角色命名规范值得借鉴)。# 克隆并从源码运行(探索/调试上游)
git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness && pnpm install && pnpm run build
pnpm dsh web --patch ./my-overlay/cordis.yml
# 快速验证插件层
dsh --profile <name> --dump-config
# 发布插件
pnpm build && pnpm publish # 预构建 lib/ 后发布 npm
| 主题 | 路径(相对 deepseek-harness/) |
|---|---|
| 第一个插件 | docs/user/develop/basic/index.zh.md |
| 开发一个工具(教程) | docs/user/develop/basic/tool.zh.md |
| 插件配置 | docs/user/develop/basic/config.zh.md |
| 打包与安装插件 | docs/user/develop/basic/publish.zh.md |
| 插件与生命周期 | docs/user/develop/framework/index.zh.md |
| 事件系统 | docs/user/develop/framework/events.zh.md |
| 服务与依赖 | docs/user/develop/framework/service.zh.md |
| 工具编写参考(权威) | docs/cookbook/adding-a-tool.zh.md |
| LLM 适配器 | docs/user/develop/practice/llm-adapter.zh.md、docs/cookbook/adding-an-llm-adapter.zh.md |
| 架构总览 | docs/architecture.zh.md |
| Cordis 入门 / 教程 | docs/cordis-primer.zh.md、docs/cordis-tutorial/index.zh.md |
| 扩展实操手册 | docs/cookbook/extension-cookbook.zh.md |
| 工具目录 / 配置目录(生成) | docs/tool-catalog.zh.md、docs/config-catalog.zh.md |
| 示例集 | examples/README.zh.md |
社区:GitHub Discussions;插件仓库可打 dsh-plugin topic 提升可发现性;Discord。