从五分钟跑通,到写出你自己的插件
把 DeepSeek 开源的 Agent Harness(dsh)讲透——上手、原理、组合、开发、自动化、生态
全书地图:三部怎么读
全书是一条完整的成长路径——先能跑(基础篇)→ 用成系统(进阶篇)→ 参与共建(生态篇)。按你所处的阶段进入即可:
| 部 | 包含篇章 | 读完你能做到 |
|---|---|---|
| 一、基础篇 先跑起来 |
1.1 上手篇(六步跑通) 1.2 认知篇(第 1–3 章) |
一条命令启动 Web UI、配好模型、跑通第一个任务、会用 headless 和 profile、能把自己的插件树打印出来看;并且理解「一切皆插件」到底解决什么问题。 |
| 二、进阶篇 把 Harness 用成系统 |
2.1 组合篇(第 4–6 章) 2.2 开发篇(第 7–10 章) 2.3 机制篇(第 11–14 章) 2.4 自动化篇(第 15–17 章) |
用 patch 分层定制自己的 dsh 而不 fork 源码;写出并发布第一个插件;看懂轮次/事件/服务这套引擎机制;用 headless、Python SDK 把 dsh 接进自己的工作流。 |
| 三、生态篇 从使用者到共建者 |
3.1 生态与社区 3.2 两层世界 |
知道去哪找现成插件(附两张配套生态图谱页)、怎么参与社区,以及应用层产品与 harness 层底座的关系——什么时候用哪一层。 |
deepseek-ai/deepseek-harness 的最新文档为准——本书教你的「怎么理解它」不会过时,具体参数可能会。dsh 是什么:Agent Harness 与「一切皆插件」
在敲第一条命令之前,先用三分钟搞清楚你装的是什么。这决定了你后面每一步的预期。
DeepSeek Harness(命令名 dsh)是 DeepSeek AI 开源的 agent harness——直译是「智能体挽具」,实际角色是智能体的运行环境:模型只负责思考,harness 负责把思考变成安全落地的行动——调工具、读写文件、跑命令、管会话、管权限、管持久化,再把整个过程呈现给你审批和观察。
它有三个身份,对应你使用它的三种深度:
- 一个开箱即用的产品:一条命令启动 Web UI,配上 DeepSeek API 密钥就能让 agent 在你的项目目录里干活;
- 一套可组装的底座:通过配置分层(profile / 组合包 / patch),你可以替换其中任何一块——包括换模型、换工具、换界面,甚至换掉 agent 主循环;
- 一个插件生态的起点:官方鼓励社区写插件,用 GitHub 话题
dsh-plugin互相发现。
一句话记住它的架构立场
一切皆插件(Everything is a Plugin):产品的每一部分都是插件——模型适配器、工具注册表、会话日志、乃至 agent loop 本身——因此每一部分都可以从配置替换。不存在需要打补丁的特权内核。
这句话来自官方架构文档,是全书反复回到的锚点。它的底层框架叫 Cordis,其设计对应一篇正式论文《A Programming Paradigm for Spatiotemporal Composability》——1.2.3 章会用大白话讲清楚这篇论文在说什么,以及它为什么重要。
| 你可能熟悉的东西 | dsh 对应物 | 差别 |
|---|---|---|
| 聊天机器人网页 | dsh Web UI | dsh 的对话对象是一个能动手的 agent:读写工作区文件、跑命令、委派子任务,高危操作先请求审批 |
| Claude Code / Codex 这类编码 agent | dsh 全体 | 同类产品,但 dsh 把「可组装」提到第一优先级:整个系统是一棵可以逐行替换的插件树,且开源(MIT) |
| VS Code 插件系统 | Cordis 插件系统 | VS Code 卸载一个扩展要重启宿主;Cordis 插件卸载时副作用自动完全回收,热插拔是数学保证(见 1.2.3) |
五分钟启动 Web UI
只需要 Node.js 和一条命令。这一章跑完,你的浏览器里就有一个本地 agent 驾驶舱。
方式一:npx 直接跑(推荐首次体验)
安装 Node.js 后,在你想让 agent 干活的项目目录里执行:
npx @deepseek-ai/dsh web
命令会启动 Web UI,默认地址 http://127.0.0.1:3080(终端会打印实际地址)。注意一个细节:你在哪个目录启动 dsh,哪个目录就是默认的工作区根——所以别在 C 盘根目录随手一敲,先 cd 到一个演练项目里。
方式二:从源码跑(想改代码 / 想装本地插件)
git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness
pnpm install
pnpm run build
pnpm dsh web
源码方式是后面开发篇(2.2)的前置条件:本地插件教程需要在仓库检出里进行。只是使用的话,npx 就够。
启动后你会看到什么
- 一个会话列表 + 输入框的界面——但输入框暂时不可用,因为还没选工作区(下一章解决);
- 设置入口——先去配模型密钥(下一章第一件事)。
dsh-playground 文件夹)里启动,观察它的审批请求都长什么样,再决定放到真实项目里用。这和你第一次给任何 agent 授权的原则一致。配置模型:DeepSeek 密钥与更多提供方
dsh 的模型路由是热生效的:改完下一次请求就用新配置,不用重启服务器。
最短路径:填一个 DeepSeek API 密钥
- 打开 设置 → 模型;
- 在 DeepSeek 卡片里输入 API 密钥,保存。
就这两步。有个值得点赞的设计:密钥是只写的——保存后页面只显示脱敏描述符,永远不会回显明文。密钥落盘在 $DSH_HOME/.credentials.yaml(dsh 的用户目录),设置文件里只保留一个凭据引用。
接入其他提供方:三个层次
| 层次 | 操作 | 适用 |
|---|---|---|
| 目录提供方 | 「添加提供方」→ 选 Anthropic、OpenAI 等 → 填密钥 | 主流厂商。端点、协议、模型列表都由内置目录提供,填个密钥就完事 |
| 原生认证提供方 | Bedrock / Vertex / Azure / Codex 各自用 AWS 凭据与区域、ADC 项目、api-version、OAuth | 云厂商托管模型。只填 API 密钥字段是配不通的,要按各家原生方式来 |
| 自定义提供方 | 「添加自定义提供方」→ 填小写 Provider ID、基础 URL、API 协议、凭据、至少一个模型 | 公司网关、自建 vLLM/代理、任何 OpenAI 兼容端点。「获取可用模型」按钮可直接拉取端点的模型列表 |
进阶:视觉模型要自己声明
手动录入的模型默认按纯文本对待。如果你的自定义端点上有支持图片的模型,需要在 $DSH_HOME/settings.yaml 里给它加一行 input:
llm-pi-ai:
providers:
my-gateway:
apiKeyEnv: GATEWAY_API_KEY
api: openai-completions
baseURL: https://gateway.example/v1
models:
- id: legacy-chat
- id: vision-preview
input: [text, image]
整条路由都是视觉模型的话,用 defaultInput: [text, image] 设一次回退值即可。记住这些字段是「你对端点的断言」而不是「dsh 替你做的检查」——声明了端点没有的能力,请求会被提供方拒绝。
跑通第一个任务:工作区 · 会话 · 审批
模型配好了,现在让 agent 干第一件实事。三个概念一次搞懂:工作区决定它能碰哪里,会话承载一次协作,审批是你手里的闸。
第一步:选择工作区
点击选择工作区,把你启动 dsh 时所在的项目目录添加进来并选中。选中工作区之前,会话输入框是禁用的——这是有意设计:agent 必须先有明确的活动范围,才能开始接活。
第二步:发出第一个任务
官方指南给的第一个任务就很好,直接抄:
Summarize this repository and identify its main packages.
(总结这个仓库,识别它的主要模块。)
你会看到 agent 开始读取工作区文件、运行命令、组织回答。它能做的事包括:读取和编辑工作区文件、运行命令、委派工作(子代理)、维护计划。
第三步:理解审批
当某个操作在当前权限策略下需要审批时,Web UI 会先弹窗询问你。第一次使用时,建议每个审批都点开看清楚它要干什么、在哪个目录干——这是你建立信任边界的过程。dsh 底层有完整的沙箱与审批策略体系(进程限制支持 bwrap / Landlock / Seatbelt 等后端),权限策略本身也是插件树里的可配置行。
中文任务照样可以
"总结这个仓库的结构和主要模块,输出一份 docs/overview.md,中文。"
"扫描 input 目录里的文件,按类型分类,先给我一份分类方案清单,我确认后再执行移动。"
"给 README 增加一节'快速开始',包含安装和启动命令,改完把 diff 摘要发我。"
CLI 三种姿势:web · headless · profile
dsh 命令本质是一个「profile 启动器」。搞懂这一点,你就同时学会了它的三种用法。
入口模式一览
| 命令 | 作用 |
|---|---|
dsh web | 启动 Web UI(它其实是 dsh --profile web 的别名) |
dsh --profile headless "任务描述" | 无头模式:新起一个持久化会话跑完这一个任务,把最终回答打印到终端,然后退出。适合脚本和 CI |
dsh --profile <name> | 启动 $DSH_HOME/profiles/<name> 下的具名 profile(你自己组装的版本) |
dsh plugin --profile <name> <pnpm 参数> | 管理某个 profile 的插件——本质是把参数转发给该 profile 目录里的 pnpm |
web 和 headless 两个 profile 首次使用时会从内置模板自动初始化;其他 profile 要用 dsh plugin 创建。
参数的分界线
启动器只解析自己认识的 flag,第一个它不认识的 token 开始,后面全部交给被启动的应用去解析:
dsh --profile web --port 8080 # --port 是 web 应用的参数
dsh --profile headless "run the tests"
dsh --profile web --help # 打印 web 应用的帮助,不是启动器的
dsh --help # 启动器自己的帮助
dsh --profile headless "检查本次提交的文档链接是否有效,输出坏链清单"。会话照样持久化,回头可以在 Web UI 里翻记录。看清你的插件树:--dump-config
「一切皆插件」不是口号,是可以打印出来的事实。这一章教你把它打印出来。
运行中的 dsh 是一棵插件树,由启动时按序叠加的配置层组合而成。想看你机器上实际会启动什么:
dsh --profile web --dump-config # 打印最终组合出的配置树
dsh --profile web --dump-default-config # 只打印内置组合包那几层
你会看到一长串条目:模型适配器、工具注册表、会话持久化、沙箱策略、Web 服务器……每一行都是一个插件条目,带着自己的 id 和 config。关键在官方那句话:
它打印出的任何条目,都可以由你自己的 patch 替换。
这就是 dsh 和「有插件机制的软件」的本质区别——后者是核心 + 插件挂载点,前者是整棵树都由同一种机制组成。怎么替换,是进阶篇 2.1 的主题;现在你只需要建立这个视觉印象:你的 agent 是一份可以逐行审查、逐行修改的清单。
--dump-config,看看是哪一层把配置改了。它相当于 dsh 世界的 git diff。越用越好的 10 个技巧
前六步是「能跑」。这十条是把 dsh 用顺手的经验清单,每条都有出处。
- 永远从演练目录开始。dsh 以启动目录为默认工作区根——第一次试新玩法,先 cd 进沙盒目录再启动。
- 模型配置是热生效的。换密钥、加提供方、改模型,下一次请求即生效,别浪费时间重启服务器。
- 把
--dump-config当体检。装了新插件、改了 patch 之后先 dump 一遍,确认改动落在了你以为的那一层。 - headless 是最好的「第二入口」。重复性任务写成一条
dsh --profile headless "...",扔进脚本或定时任务。 - 独立任务用独立会话。官方基准测试的纪律同样适用于日常:不相关的任务分开跑,需要延续上下文(包括持久 shell 状态)时才复用会话。
- 密钥只进凭据文件。API 密钥存
$DSH_HOME/.credentials.yaml,不要贴进对话里;对话是会持久化的。 - 会话日志是可审计资产。每个会话是一份 JSONL 日志,含组装后的模型请求与工具调用——出问题时它就是「黑匣子」。
- 用
--patch做实验。想试一个改动,用启动参数挂一个 patch 文件(2.1.2 详解),不满意删掉文件就还原,profile 本体不动。 - 找现成插件先看
dsh-plugin话题。GitHub 上搜这个 topic,或直接翻本书配套的生态图谱页(3.1.1)。 - 想深入就跟官方教程走。仓库里有一套七课的 Cordis 教程(
docs/cordis-tutorial/),从第一个插件到组合与热更新,全部动手向。
为什么 Agent 需要 Harness
模型能力每几个月上一个台阶,但「模型」和「能替你干活的系统」之间隔着一整层工程。这一层,就是 harness。
把一个大模型变成能安全干活的 agent,至少要补齐这些事:
- 工具执行:模型说「我要读这个文件」,谁去真的读?读之前谁检查它有没有权限?
- 会话与记忆:几十轮对话、几百次工具调用,怎么持久化、怎么回放、怎么在上下文超限时压缩?
- 权限与沙箱:agent 要跑命令,怎么保证它只能碰工作区、碰不到你的系统盘?
- 人机协作:什么操作先斩后奏、什么操作先请示?审批流放在哪一步?
- 可观察性:任务跑了十分钟,中间发生了什么?哪一步花了多少 token?
- 编排:子代理、后台任务、工作流、计划模式——多股工作怎么协调?
这些事和模型无关,但决定了 agent 能不能用、敢不敢用。业内把承担这层的系统叫 agent harness。你熟悉的 Claude Code、Codex 都是 harness;dsh 是 DeepSeek 给出的开源答案。
dsh 的差异化立场
同类 harness 大多是「产品优先」:功能围绕官方设定的形态生长,扩展靠预留的挂载点。dsh 反过来,把「可组装」放在第一位——先造一套让任何能力都能插拔的机制(Cordis),再用这套机制把产品拼出来。于是:
- 官方拼出来的形态(Web UI、headless)只是参考组合,不是唯一形态;
- 社区可以替换任何一层,做出终端 UI 版、企业定制版、教学精简版;
- 连「agent 自己修改自己」都成为一等公民能力(2.3.4 章)。
一切皆插件:没有特权内核
大多数软件的插件系统是「核心恩赐给外围的接口」。dsh 把这个关系拆了:核心自己也是插件。
这个立场换来三件实事
① 扩展 = 挂插件,不是打补丁
官方原话:「扩展 dsh 的方式是把插件挂载到其他插件旁边,而各项注册都是副作用,会在其插件卸载时撤销。」你想改它的行为,不需要 fork 源码,把你的插件挂上去、或者用 patch 把某一行换掉即可。
② 自举:官方和你用的是同一套机制
官方组装 Web UI 用的组合文件格式,和你定制专属 agent 用的是同一种。系统对自己和对外部暴露同一个机制——这叫 dogfooding,也是「没有特权」的最硬证明。
③ 元能力:系统可以审视并修改自己
因为一切都是插件条目,agent 可以查看自己由哪些插件组成,甚至(在审批之下)动态挂载新插件、修改自己的组合。这是「自进化 agent」的机制基础,2.3.4 章专门讲。
Cordis 与时空组合性:一篇论文打的地基
「一切皆插件」谁都会喊,难的是插件卸得干净、装得上瘾。dsh 底层的 Cordis 框架为此配了一篇正式论文,把这件事从工程直觉变成了数学保证。
论文叫《A Programming Paradigm for Spatiotemporal Composability》(时空可组合性的编程范式),作者来自北京大学与 DeepSeek-AI。用大白话讲,它回答了动态插拔的两个根本难题:
时间维度:卸载一个插件,怎么保证擦干净?
想想 VS Code:禁用一个扩展要重启整个扩展宿主,因为运行中的代码留下的副作用(监听器、定时器、状态)没法安全撤销。论文给的答案叫可逆效应(revertible effects):每个副作用在产生时就带上自己的「逆操作」,运行时自动追踪;卸载插件 = 按序执行所有逆操作。关键定理证明了:组合效应的逆,可以由各原子逆自动推导——插件作者只需为最小操作提供撤销方式,整个插件的完整清理是框架结构性保证的,不靠作者自觉。
落到代码里就是你会在 2.2 章见到的 ctx.effect():注册时顺手返回清理函数,剩下的框架全包。
空间维度:插件之间的依赖,怎么随插拔自动重连?
论文的第二个机制叫响应式余效应(reactive coeffects):插件声明自己依赖哪些服务(dsh 里的 inject),运行时监控这些依赖的可用性——依赖到齐才激活,依赖被拔走就自动停用,提供方换了就自动重连。更讲究的是顺序保证:依赖者会先于提供方完成停用,且在自己收尾的全程还能用到那个依赖(比如关闭连接池时还能把连接还回去)。
合流性:折腾一万次,等于从头装一次
论文压轴的定理(合流性)翻译过来是:不管你运行中装了拆了换了多少次插件,系统最终静止的状态,和「按最终配置从零启动一次」完全一致——动态历史不留痕迹。这就是为什么 dsh 敢做配置热重载、敢让 agent 改自己的插件树:怎么折腾都不会「越用越脏」。
| 论文概念 | dsh 里的样子 | 你得到的保证 |
|---|---|---|
| 可逆效应 | ctx.effect() / ctx.on() 自动回收 | 插件卸载不留垃圾,不用重启 |
| 响应式余效应 | inject 声明依赖,就绪才启动 | 加载顺序不用手排,热替换自动重连 |
| 合流性 | 配置热重载 / HMR / patch 分层 | 最终状态只由配置决定,与折腾路径无关 |
还有一个信心来源:Cordis 不是为 dsh 新造的实验品。它已经在开源聊天机器人框架 Koishi 里跑了四年,支撑着 4000 多个社区插件的生态——热插拔、依赖联动这套机制是被真实生态验证过的。dsh 相当于把一套久经考验的插件底盘,装到了 agent harness 这个新赛道上。
github.com/cordiverse/paper,88 页硬核数学,本章是它的五分钟版本。Profile 与组合包:一棵插件树的分层
你已经在 1.1.6 打印过自己的插件树。这一章讲清楚这棵树是怎么一层层叠出来的——这是定制 dsh 的全部原理。
两个核心名词:
- 组合包(bundle):插件配置及其挂载代码的分发格式。官方内置三个:
dsh-base——每个 profile 的第一层:模型适配器、工具、持久化、沙箱与审批策略、设置、凭据、遥测;dsh-web-app——在 base 之上增加浏览器应用;dsh-headless——增加一次性运行器,完全不带服务器。
- Profile:存放在
$DSH_HOME/profiles/<name>的具名组装。目录里有package.json(声明dsh.profile.bundles有序组合包列表 + 树外插件依赖)和cordis.patch.yml(你自己的覆盖层)。
这个分层设计的聪明之处:组合包插入的内容,始终可以被它上面的任何一层 patch 掉。官方升级组合包不会覆盖你的定制,你的定制也不用碰官方的文件——两边永远在不同的层里。
Patch 覆盖:不 fork 就能改一切
上一章讲了层,这一章动手写 patch。三种典型操作:插入一个插件、替换一行配置、临时做实验。
操作一:插入你的插件(--patch 实验层)
写一个最小的 patch 文件(YAML),用 insert 插入新条目:
# my-overlay.yml
- insert:
- id: hello
name: '/absolute/path/to/your-plugin.ts'
挂着它启动:
dsh web --patch ./my-overlay.yml
不满意?去掉 --patch 参数就完全还原。这是最安全的实验方式——profile 本体一个字节都没动。
操作二:持久化定制(profile / home 层)
实验满意后,把同样的条目并入 $DSH_HOME/profiles/web/cordis.patch.yml(只影响 web profile)或 $DSH_HOME/cordis.patch.yml(影响这台机器所有 profile)。patch 的语义:按 id 定位某个条目并替换其整个 config,或插入新条目。也就是说你可以精确地把官方树里任何一行换成自己的实现——比如换掉某个工具、某个模型路由策略。
操作三:条目里的动态表达式
配置文件支持 !!js 表达式节点:条目的 config 会在其声明的注入激活后、基于插件上下文插值,disabled 字段则在每次挂载决策时基于 loader 上下文插值。这让「按环境开关插件」「引用运行时值」成为配置层的能力。官方建议:由环境选择插件时,优先用 overlay 而不是复杂表达式。
dsh --profile web --dump-config 验证结果。分层系统的调试铁律是「相信打印出来的最终态,不要相信自己对层叠顺序的记忆」。Agent 预设:组装你的专属 agent
前两章定制的是「整个 dsh 进程」。这一章的颗粒度更细:按会话组装 agent——同一个 dsh,不同会话可以跑完全不同配置的 agent。
dsh 的 preset 包提供了这个能力:由 preset 的 cordis.yml 按会话组装 agent。也就是说,一个 agent 预设本质上就是一份组合文件——和官方组装整个宿主用的是同一种机制。这正是 1.2.2 讲的「自举」:
- 宿主组合持有跨会话共享的能力:注册表、持久化、模型路由、子代理后端;
- Agent 预设持有单个会话的贡献:这个 agent 有哪些工具、什么人设、哪些提示词片段。
「某个能力应该放宿主层还是预设层」是用 dsh 做定制时的一等架构决策。判断标准很朴素:要跨会话共享、要持久化的,放宿主;只属于某类任务角色的,放预设。
官方给的参考样本
仓库 examples/ 里有多个可以直接研究的组合:例如 jsonrpc-agent 的 minimal 组合——一个只带持久 bash 和 str_replace_editor 两个工具、关闭上下文压缩的极简编码 agent(2.4.2 章会跑它)。读懂一份 minimal 组合,你就有了搭建自己预设的起点:从最小集合开始,一行一行往上加。
Cordis 五个核心概念
写插件之前,五个概念先立住。这五条来自官方 Cordis 入门文档,是全部插件开发的世界观。
- 插件是实现 Service 的对象。可以是一个带可选
inject和apply(ctx)的函数,也可以是一个Service子类,生命周期由 Cordis 挂到当前上下文。 - 上下文是服务的容器。一个服务占据一个稳定的
ctx.<key>(如ctx.tools、ctx.llm、ctx.sessions);其他插件通过 key 查找服务,而非导入具体实现。 - 通过
inject声明服务依赖。插件声明所需服务后,会等这些服务就绪才启动——加载顺序通过依赖表达,不用手动编排。 - 类型化事件用于通信。事件按四种模式分发(下表),分别对应观察、包装、并行扇出、按序执行。
- 注册是可逆的副作用。提示词片段、工具 schema、适配器、监听器都通过
ctx.effect()或ctx.on()安装,reload 和 teardown 时按预期撤销。
四种事件分发模式
| 模式 | 是否 await | 分发顺序 | 有无返回值 | 典型用途 |
|---|---|---|---|---|
emit | 否 | 按注册顺序观察 | 无 | 广播通知 |
waterfall | 否 | 按注册顺序包装 | 有 | 拦截 / 改写(中间件) |
parallel | 是 | 并行 | 无 | 并发扇出 |
serial | 是 | 按注册顺序执行 | 有 | 顺序检查点 |
其中 waterfall 最重要(agent 的关键扩展点都是它):监听器拿到 (...args, next),调用 next() 才委托给下游;不调用直接返回就是短路。协作式监听器改共享对象后委托,策略式监听器在拥有决策权时直接短路——这是设计意图,不是坑。
ctx.tools,模型流式输出归 ctx.llm;拦截和策略优先用事件,直接能力调用优先用服务方法;每个注册都要有对应的清理(disposer),teardown 有顺序要求的资源放同一个 effect 里。第一个插件:apply(ctx) 十行起步
跟官方教程走一遍最小插件,从建文件到在 Web UI 里生效,全程五分钟。前置条件:完成 1.1.2 的「从源码运行」。
① 建目录和插件文件
mkdir -p scratch-plugin/src
创建 scratch-plugin/src/my-plugin.ts:
import type { Context } from '@deepseek-ai/cordis'
export const name = 'hello-plugin'
export function apply(ctx: Context) {
console.log('[hello-plugin] plugin loaded!')
}
一个插件就是「导出 apply 函数的 TypeScript 模块」——框架加载时调用 apply 并传入 ctx,你通过 ctx 注册一切能力。这就是完整结构,没有隐藏的样板代码。
② 写一个 overlay 把它插进去
创建 scratch-plugin/cordis.yml(插件路径必须是绝对路径):
- insert:
- id: hello
name: '/absolute/path/to/deepseek-harness/scratch-plugin/src/my-plugin.ts'
③ 挂着 overlay 启动
pnpm dsh web --patch ./scratch-plugin/cordis.yml
打开 http://127.0.0.1:3080,启动期间终端会打印 [hello-plugin] plugin loaded!。恭喜,你的代码已经和官方插件运行在同一棵树里、享受同样的待遇。
docs/cordis-tutorial/ 有完整七课——第一个插件 → 生命周期与效应 → 服务 → 事件 → 配置 → 组合与 HMR → 进入 harness。本书接下来两章覆盖其中最常用的部分。依赖、副作用与配置:插件的三块肌肉
Hello world 之后,真实插件靠三样东西干活:inject 拿到别人的服务,effect 管好自己的资源,config 接受用户的定制。
① inject:声明依赖,就绪才启动
import type { Context } from '@deepseek-ai/cordis'
export const name = 'my-tool-plugin'
export const inject = ['tools']
export function apply(ctx: Context) {
// 运行到这里时,ctx.tools 一定已就绪
ctx.tools.register(/* ... */)
}
这一行 inject 背后就是 1.2.3 讲的「响应式余效应」:框架保证依赖到齐才调用你的 apply,依赖被卸载时你的插件也会被正确停用——顺序问题从此不归你管。
② effect:有借有还的副作用
通过 ctx 注册的东西(事件监听、工具、定时器)卸载时自动清理。自己管理的资源,用 ctx.effect() 告诉框架怎么还:
export function apply(ctx: Context) {
ctx.effect(() => {
const timer = setInterval(() => {
console.log('heartbeat')
}, 5000)
// 返回的函数会在插件卸载时执行
return () => clearInterval(timer)
})
}
这就是「可逆效应」的代码形态。养成习惯:凡是打开的,注册时就写好怎么关。做到这一点,你的插件天生支持热插拔和 HMR。
③ config:把可调参数交给条目
插件条目里的 config 字段会传给插件,用户在 patch 里就能定制你的行为:
- insert:
- id: hello
name: '/absolute/path/to/my-plugin.ts'
config:
greeting: '你好'
官方约定是「无硬编码的可调参数」——任何值得调的东西都应该走 config 暴露出去,配置错误则响亮失败。具体的 schema 声明方式见官方 docs/user/develop/basic/config 文档。
发布与被发现:dsh-plugin
插件写完,走完最后一公里:发出去、让别人装得上、找得到。
发布三件事
- 发包:把插件发布为 npm 包(官方包用
@deepseek-ai/dsh-*命名空间;社区包用你自己的名字即可)。 - 打标:给插件的 GitHub 仓库加上
dsh-plugin话题——这是官方指定的被发现渠道。 - 写清楚 README:官方对自家包的要求值得抄——用途、API、扩展点、已知限制,一个不少。
用户怎么装你的插件
dsh 的插件安装走 profile 机制:dsh plugin --profile <name> ... 会把参数转发给该 profile 目录里的 pnpm,包会装进 profile 自己的 node_modules;然后在 profile 的 cordis.patch.yml 里插入条目启用它。树外插件与 dsh 本体完全隔离——卸载 = 删条目 + 删依赖,干干净净。
能写什么类型的插件
官方 cookbook(docs/cookbook/)给了几条现成路线:
- 加一个工具(adding-a-tool):给 agent 一个新动作,比如调用你公司的内部 API;
- 加一个 LLM 适配器(adding-an-llm-adapter):接入一个新模型提供方;
- 加一个会话节点(adding-a-conversation-node):在 Web UI 对话流里渲染新的卡片类型;
- 加一个包 / vendor 包:向 dsh 本体贡献代码的规范路径。
轮次与步骤:一次对话的完整旅程
你在 Web UI 里发一句话,到 agent 回你一个结果,中间发生了什么?看懂这条流水线,你就能在任何一个环节上挂自己的逻辑。
dsh 的执行单位有两级:步骤(step)= 一次模型请求加上它调用的工具;轮次(turn)= 零个或多个步骤——在领取首条输入前打开,在不再欠任何工作时关闭。
几个关键节点的含义:
| 节点 | 类型 | 你能在这做什么 |
|---|---|---|
agent/pre-step | waterfall | 步骤的守门员:可以拒绝这一步、改写进入的消息。上下文压缩就在这里处理压力 |
agent/request / llm/stream | waterfall | 模型请求的组装与流式过程:改请求参数、拦截或改写流 |
tools/pre-execute → execute → post-execute | waterfall | 工具执行三段:前置检查(审批就在这类位置生效)、执行、后处理 |
agent/turn-stopping | serial | 轮次收尾检查点:最后的机会决定要不要续 |
注意流水线上的事件分两类:turn/*、step/*、user/message、assistant/*、tool/* 是持久会话事件——写进日志、可回放;其余是实时扩展点——挂逻辑用的。waterfall 事件的监听器必须调 next() 才委托下去,这是 2.2 学过的语义在真实战场上的应用。
事件三域:会话 · Agent · 能力
「事件就是扩展点,而选对事件域是大多数改动的第一个决定。」——官方架构文档的这句话,是插件设计的第一课。
| 事件域 | 是什么 | 什么时候用 |
|---|---|---|
| 会话事件 | 追加到日志并通过 session/event 广播的持久事实 | 某个事实必须在重新加载后仍然存在时(审计、回放、SDK 消费 transcript) |
| Agent 事件 agent/* | 携带活跃 Agent 的实时协调:inbox、步骤、状态、请求、续跑 | 要观察或拦截进行中的工作时(steering、请求改写、错误处理) |
| 能力事件 fs/* tools/* telemetry/* | 挂在某个能力接缝上的策略与适配,无需进入主循环 | 给文件系统加策略、给工具执行加把关、接遥测 |
判断口诀:要留痕 → 会话事件;要干预 → agent 事件;要给某个能力加规则 → 能力事件。官方还维护了一份「事件生产方/消费方映射表」(docs/event-producer-consumer.md),查某个事件谁发谁听,比读源码快得多。
给 SDK 用户的一条铁律
需要可回放 transcript 数据的,消费 session/event;agent/* 只用于实时协调。把实时事件当持久数据用,重启后你的数据就没了。
核心服务地图:ctx 上都挂了什么
写插件时你会不断问一个问题:「这件事该找哪个服务?」这一章是答案的速查版。
主干服务(core)
| 包 | 职责 | ctx 键 |
|---|---|---|
core/session | 仅追加的 SessionEvent 日志和存储 | ctx.sessions |
core/system-prompt | 提示词片段与工具 schema 的组装 | ctx.systemPrompt |
core/tools | 作用域化的工具注册表和带把关的执行流水线 | ctx.tools |
core/agent | Agent 接口、活跃 agent 注册表和 agent/* 事件 | ctx.agents |
core/agent-loop | 实现该接口的默认驱动器(可替换!) | ctx.agentLoop |
llm/llm | 消息与流式词汇表 + 适配器接缝 | ctx.llm |
能力系列(按「接缝 + 实现 + 面向模型的工具」组织)
dsh 的能力包大多遵循同一个三件套模式:Service Definition(接缝定义)+ Service Provider(具体实现)+ Consumer(面向模型的工具),三个角色可独立演进——这是「换实现不换接口」的关键。主要的能力系列:
| 系列 | 提供什么 |
|---|---|
fs / shell / terminal | 文件系统、一次性 Bash、持久 PTY——agent 的手 |
web | 搜索 / 网页获取——agent 的眼 |
lsp | 语言服务器接入,代码智能 |
sandbox | 进程限制接缝:bwrap / Landlock / Seatbelt 后端 |
interaction | 人机协作平面:审批接缝、权限预设、询问用户的工具 |
subagent / jobs / workflow | 子代理委托、后台任务(job_* 工具)、工作流引擎(workflow / ralph 工具) |
skill / todo / plan / goal | 技能目录与加载、todo_write、计划模式、会话目标 |
compaction / spill / guard | 上下文压缩、超大结果外溢存储、循环卫生守卫(重复调用提醒、执行超时) |
session-query | 会话检索:全文搜索、血缘、语义过滤 |
完整清单见附录。官方有一条依赖纪律值得所有插件作者遵守:扩展插件依赖 Service Definition,绝不依赖具体提供方——这样当用户把本地实现换成远程实现时,你的插件不用改一行。
自我修改:agent 给自己装插件
这是 dsh 最有未来感的一章:因为一切皆插件,「agent 修改自己」不再是科幻,而是一个有审批、有回滚的工程流程。
dsh 的 extensions 包提供「agent 运行时自修改」能力,官方描述是两句话:实时插件/服务检查 + 模型所写插件的挂载/卸载。拆开看:
- 自省:agent 可以查询自己当前由哪些插件组成、有哪些服务和事件——「我是谁、我有什么」对模型可见;
- 自改:agent 可以起草一个新插件(它自己写代码),经过审批后挂载进自己的运行时;不满意可以卸载回滚。整个过程有完整的生命周期管理。
为什么这在 dsh 上是安全的
回到 1.2.3 的三个保证:可逆效应保证挂载的插件卸得干净;响应式依赖保证新插件与现有插件的连接自动理顺;合流性保证折腾多少次系统状态都等价于从头组装。Cordis 论文的结语其实就点了这个方向:自进化 agent harness 是这套数学最重要的应用场景——每一次自我修改都是一次动态组合,没有时空组合性的保证,频繁自改会把系统改成废墟。
Headless:不开浏览器跑任务
Web UI 适合协作和观察,自动化场景要的是「一条命令进,一个结果出」。headless profile 就是为此存在的。
dsh --profile headless "Inspect the repository and fix the failing tests."
行为语义:新起一个持久化会话,跑完这一个任务,把最终回答打印到终端,然后退出。它由 dsh-headless 组合包组装——在 dsh-base 之上加一个一次性运行器,完全不带服务器。
三个典型接法
crontab / 计划任务里跑:dsh --profile headless "检查 docs 目录所有链接有效性,输出坏链清单到 report.md"
流水线步骤:dsh --profile headless "审查本次变更的 SQL 迁移脚本是否有破坏性操作,有则非零退出"
shell 循环喂任务列表,每个任务独立会话,事后在 Web UI 里统一翻记录复盘。
headless 和 web 共享同一个底座(dsh-base),所以模型配置、凭据、权限策略都是通的——配一次,两个入口都能用。会话照样落盘,自动化跑的活也留完整档案。
Python SDK:把 harness 当库用
如果你的自动化生活在 Python 世界(数据管线、评测脚本、内部平台),官方 Python SDK 让你直接 import 一个 harness。
安装
python -m venv .venv
. .venv/bin/activate
python -m pip install deepseek-harness-sdk
要求 Python 3.10+;支持 Linux x64 / arm64 与 macOS 14+ (arm64)。SDK 自带同版本的内置运行时,不需要系统装 Node.js。凭据走环境变量:
export DEEPSEEK_API_KEY=sk-your-key-here
# 走 OpenAI 兼容代理时:export DEEPSEEK_BASE_URL=http://127.0.0.1:8000/v1
核心 API:一个上下文管理器
from pathlib import Path
from deepseek_harness import DeepSeekHarness
config = Path("examples/jsonrpc-agent/minimal.cordis.yml").resolve()
with DeepSeekHarness(
provider="deepseek-official",
model="deepseek-v4-flash",
max_tokens=49_152,
cwd="/absolute/path/to/workspace", # agent 的工作区
session_root="/absolute/path/to/sessions", # 会话日志目录
cordis=str(config), # 用哪份组合
) as harness:
result = harness.run(
"Inspect the repository and fix the failing tests.",
session_id="example-001",
)
print(result.final_response)
注意 cordis= 参数——SDK 调用方直接指定 agent 组合,2.1 学的组合能力在这里变成了 Python 的一个参数。官方示例用的 minimal 组合值得记住它的形状:
| 属性 | minimal 组合的取值 |
|---|---|
| 面向模型的工具 | 仅持久 bash 与 str_replace_editor 两个 |
| Bash 超时 / 编辑器输出上限 | 300 秒 / 16,000 字符 |
| 上下文压缩 | 关闭 |
| 会话持久化 | session_root 下未压缩 JSONL |
| 权限 | danger-full-access——只能在可丢弃的检出或容器里跑! |
复用同一个 harness 与 session id 会保留该会话的 Bash 进程(工作目录、导出变量、shell 函数都在);独立任务用新 session id。官方基准测试(BENCHMARK.md)跑的就是这套 minimal 变体。
协议与桥:RPC · ACP · MCP · hooks
除了 Web UI 和 Python SDK,dsh 还准备了一组「接口面」,让它能被程序驱动、与其他 agent 工具互通。挑你需要的用。
| 接口面 | 包 / 示例 | 干什么用 |
|---|---|---|
| JSON-RPC SDK | packages/sdk · examples/jsonrpc-agent | 进程外运行时 SDK:JSON-RPC 协议 + TypeScript 客户端 + 服务器插件。Python SDK 底下走的就是它;其他语言可按协议自接 |
| ACP 服务器 | packages/acp · examples/acp-agent | 面向自动化的 Agent Client Protocol 服务器——用支持 ACP 的客户端 / 编辑器驱动 dsh |
| MCP | packages/mcp · examples/mcp-memory | Model Context Protocol 接入:示例演示把 MCP 记忆服务器接进 harness,让 agent 获得外部记忆能力 |
| hooks 桥 | packages/hooks | 钩子桥接 + 共享的 Claude Code / Codex 线协议库——与既有 agent 工具链的互操作层 |
| 无头运行器 | examples/headless-agent | 非交互 agent 的参考实现,配自动化剧本用 |
这张表传递的信号比每一行本身更重要:dsh 不打算做孤岛。它把「被集成」当成一等需求——你可以把它塞进任何一个已有的自动化体系,也可以让它和 Claude Code / Codex 系的工作流并存互通。
插件生态地图
「一切皆插件」的最终兑现,不在架构图里,在生态里。这一章告诉你现在的生态长什么样、怎么逛、空位在哪。
官方指定的发现机制:一个 GitHub 话题
dsh 生态的目录不在某个中心化市场里,而在 GitHub 话题 dsh-plugin 上——官方 README 明确请插件作者给仓库打这个标。这个选择很「dsh」:去中心化、零审核门槛、天然带 star/issue/README 等信任信号。逛生态就三步:
- GitHub 搜索
topic:dsh-plugin,按 star 或更新时间排序; - 读 README 看它注册了什么(工具?适配器?UI?预设?)、依赖哪些服务;
- 用 2.2.4 的方式装进一个演练 profile 先试。
本书配套:两张生态图
为了看得更直观,本书配了两张基于 topic:dsh-plugin 数据的交互页(与本书同目录,可离线打开):
读生态的三个视角
- 用户视角:先看「工具类」和「预设类」插件——它们决定你的 agent 能干多少活;
- 开发者视角:看「适配器类」(模型、沙箱、存储的替代实现)——它们标记了接缝的成熟度;
- 投资视角:数一数垂直行业插件的数量——现在还很少,这正是 2.2.4 章说的机会窗口。
社区、贡献与预览期注意
开源项目的可持续性看社区。这一章讲清楚参与渠道,以及在开发者预览期使用它的正确姿势。
参与渠道
| 渠道 | 用途 |
|---|---|
| GitHub Discussions | 官方指定的反馈与 bug 报告入口 |
dsh-plugin 话题 | 发布插件、被生态发现 |
| 企微群 / 微信公众号 | 中文社区:仓库 README 有企微小助手与入群问卷二维码 |
| Discord | 国际社区(英文 README 提供邀请链接) |
| CONTRIBUTING.md | 代码贡献规范;docs/development.md 与 docs/architecture.md 是改代码前的必读 |
一个值得注意的细节:仓库根目录有 AGENTS.md(还有面向 Claude 的 CLAUDE.md)——这个代码库从第一天起就是「为 agent 协作而组织的」,官方开发指南甚至建议用 agent 来探索代码库。用 dsh 开发 dsh,是官方自己的日常。
预览期使用姿势
- License 放心:MIT,第三方依赖许可证在 THIRD_PARTY_NOTICES.md 里完整披露;
- 兼容性谨慎:官方明说会有破坏性变更——你的 patch、插件、SDK 集成要做好跟版本走的准备;
- 生产环境克制:预览期适合评估、内部工具、实验性集成;对外服务再等等;
- 升级纪律:升级后先
--dump-config对比插件树,再跑你的回归任务清单。
extensions 自修改能力的开放程度(决定自进化叙事的兑现速度);③ 生态图谱里适配器类插件的增长(决定「可替换」从理论变现实的速度)。应用层与 Harness 层:什么时候用哪个
读过《WorkBuddy 实战绿皮书》的朋友一定会问:我已经在用 WorkBuddy 这类 AI 工作台了,dsh 和它是什么关系?答案是:不是竞争关系,是上下层关系。
| 应用层(WorkBuddy 这类 AI 工作台) | Harness 层(dsh 这类底座) | |
|---|---|---|
| 面向谁 | 业务人员、不写代码的实干者 | 开发者、技术团队、产品构建者 |
| 交付什么 | 开箱即用的任务、专家团、自动化 | 可组装的运行时:插件树、接缝、协议 |
| 定制方式 | 装 Skill、配专家、写自动化规则 | 写插件、叠 patch、组预设 |
| 类比 | 整车:上车就开 | 底盘 + 发动机:造你自己的车 |
三种人的行动建议
你的主战场还是 WorkBuddy 绿皮书那套:任务五要素、专家团、自动化闭环。读这本书的价值是「知其所以然」——理解了 harness 层,你对应用层产品的能力边界和选型判断会准得多。
如果你在给公司搭内部 agent 平台,dsh 给了你一个「不用从零造、又处处可改」的起点:接内部模型网关(2.1)、加内网工具(2.2)、配权限策略、用 SDK 嵌进现有系统(2.4)。
应用层产品的护城河正在从「会调模型」转向「场景 + 数据 + 信任」。用 dsh 这类开源底座省下运行时的成本,把力气全部押在你的场景上——这是这一波开源 harness 给创业者的真实红利。
最后回到本书反复出现的那条线:模型在快速变强,harness 在快速开源,真正稀缺的是「把它们落进真实业务的人」。上一本绿皮书讲怎么落地应用层,这一本讲怎么理解和驾驭底座层——两层都通的人,会是接下来几年最值钱的角色。
命令速查 · 目录速查 · 包清单 · 名词表
A. 命令速查
| 命令 | 作用 |
|---|---|
npx @deepseek-ai/dsh web | 启动 Web UI(默认 http://127.0.0.1:3080) |
dsh --profile headless "任务" | 无头跑一个任务,打印最终回答后退出 |
dsh --profile <name> | 启动具名 profile |
dsh plugin --profile <name> <pnpm 参数> | 管理 profile 的插件(转发给 pnpm) |
dsh --profile web --dump-config | 打印最终组合的插件树(排障第一招) |
dsh --profile web --dump-default-config | 只打印内置组合包层 |
dsh web --patch ./overlay.yml | 挂临时 overlay 启动(实验用) |
dsh --profile web --port 8080 | 启动器后的参数交给 web 应用解析 |
pnpm install && pnpm run build && pnpm dsh web | 源码方式构建并运行 |
pip install deepseek-harness-sdk | 安装 Python SDK(自带运行时,免 Node) |
B. 目录与文件速查
| 路径 | 是什么 |
|---|---|
$DSH_HOME/profiles/<name>/ | 具名 profile 目录(web、headless 首次使用自动初始化) |
…profiles/<name>/package.json | profile 清单:dsh.profile.bundles 组合包列表 + 树外插件依赖 |
…profiles/<name>/cordis.patch.yml | 该 profile 的用户覆盖层 |
$DSH_HOME/cordis.patch.yml | 机器级覆盖层(跨 profile 生效) |
$DSH_HOME/.credentials.yaml | API 密钥等凭据(只写,UI 不回显明文) |
$DSH_HOME/settings.yaml | 用户设置(如自定义提供方的模型模态声明) |
会话目录(如 SDK 的 session_root) | 每会话一份 JSONL 日志:组装后的模型请求与工具调用,可回放可审计 |
| 启动 dsh 时所在目录 | 默认工作区根 |
C. 包分组清单(精选)
npm scope 统一为 @deepseek-ai/dsh-*;完整表见仓库 packages/README.md。
| 组 | 职责 |
|---|---|
core/ | 产品 API 主干:会话、提示词、工具、agent 服务与默认循环 |
llm/ | LLM 能力系列:抽象服务 + 提供方适配器 |
fs/ shell/ terminal/ | 文件系统、Bash、持久 PTY 能力系列 |
web/ lsp/ skill/ | 搜索与网页获取、语言服务器、技能系统 |
sandbox/ interaction/ | 进程限制(bwrap/Landlock/Seatbelt)、审批与权限预设 |
subagent/ jobs/ workflow/ | 子代理委托、后台任务、工作流引擎 |
session/ session-query/ | 会话持久化(JSONL/SQLite)、检索与全文搜索 |
compaction/ spill/ guard/ | 上下文压缩、超大结果外溢、循环卫生守卫 |
todo/ plan/ goal/ schedule/ | 待办工具、计划模式、会话目标、会话内定时 |
preset/ bundle/ boot/ | 按会话组装 agent、可安装补丁层、启动粘合 |
settings/ credentials/ storage/ workspace/ | 设置、凭据、非会话存储、工作区实体 |
host/ client/ api/ typert/ | Web GUI 宿主半侧与浏览器半侧、RPC 网关、类型图 |
sdk/ acp/ mcp/ hooks/ | JSON-RPC SDK、ACP 服务器、MCP 接入、Claude Code/Codex 桥 |
extensions/ | agent 运行时自修改:插件自省 + 模型所写插件挂载/卸载 |
subprocess/ code-runtime/ e2b/ | 子进程能力、代码执行(Code Mode)、E2B 沙箱(POC) |
attachment/ feedback/ identity/ context/ | 附件存储、人类反馈、匿名身份、模型可见请求上下文 |
D. 名词表
- Agent Harness:智能体运行环境——工具执行、会话、权限、编排、观测这一整层工程。
- 一切皆插件:dsh 的架构立场——产品每一部分(含 agent 主循环)都是可替换的插件,没有特权内核。
- Cordis:dsh 底层的插件框架,设计对应论文《A Programming Paradigm for Spatiotemporal Composability》。
- 时空组合性:时间维度 = 插件卸载时副作用可完全回收(可逆效应);空间维度 = 插件间依赖随插拔自动重连(响应式余效应)。
- 合流性:无论运行中怎样装拆插件,最终静止状态等价于按最终配置从零启动——「动态历史不留痕迹」。
- ctx(上下文):服务的容器;插件经由 ctx 查找服务、注册能力。
- inject:插件对所需服务的声明;服务就绪才启动。
- effect:可逆副作用的注册方式;返回清理函数,卸载时自动执行。
- waterfall:环绕中间件式的事件分发;监听器调用 next() 委托,不调用即短路。
- Profile:$DSH_HOME 下的具名组装(组合包列表 + 用户 patch)。
- 组合包(bundle):插件配置及挂载代码的分发格式(dsh-base / dsh-web-app / dsh-headless)。
- Patch / Overlay:按 id 替换或插入插件条目的配置层;--patch 为启动时临时层。
- Agent 预设(preset):按会话组装 agent 的 cordis.yml——一份「专属 agent」的完整描述。
- 轮次 / 步骤(turn / step):执行单位——步骤 = 一次模型请求 + 其工具调用;轮次 = 零到多个步骤。
- 会话事件:追加进日志的持久事实,可回放;与实时的 agent/*、能力事件相区分。
- headless:无服务器的一次性运行模式——一条命令,一个会话,一个结果。
- danger-full-access:全放开权限档位;仅限可丢弃环境使用。
- dsh-plugin:GitHub 话题,插件生态的官方发现机制。
找到雷神
读完有想法想聊、想交流 dsh 与 AI 落地实战、或想第一时间收到本书和生态图谱的更新——扫下面两个码。