这是概念篇,讲"为什么这样设计"。代码与命令细节请看同目录的《dsh-plugin-guide.md》(技术参考篇)。
阅读本文不需要编程背景,但读完后你应该能明白:一个插件在 dsh 里是什么、靠什么活下去、如何被别人使用。
大语言模型很聪明,但它像一颗泡在营养液里的大脑——没有手,摸不到文件;没有腿,跑不了命令;没有本子,记不住昨天做了什么。
Agent Harness(智能体运行框架)就是给这颗大脑配上的"身体和工位":一个能读文件、执行命令、调用 API、记录对话历史的工作环境。模型负责思考,harness 负责让思考落地。
DeepSeek Harness(简称 dsh)就是 DeepSeek 开源的这样一套"工位"。你在终端或网页里和它对话、让它改代码查资料时,背后支撑这一切的就是它。
想象两种商场:
传统商场:有一栋不可动摇的主楼(内核),商户(插件)只能在旁边的铺位营业。你想改造主楼的大堂?对不起,那是开发商的地盘,得动整栋楼的图纸。
dsh 商场:没有主楼。保安室、配电房、广播站、连"商场管理办公室"本身,全都是一间间普通的铺位。每一间都可以退租、可以换成隔壁竞品、可以在隔壁再开一家。
这就是 dsh 的口号——一切皆插件:
为什么这么执着?因为"每一部分都能被换掉"意味着每一部分都能被定制。举例:把"文件系统"插件换成指向远程沙箱的版本,那么所有依赖文件系统的东西——Bash 命令、终端、代码分析——会自动跟着搬进沙箱,一行都不用重写。能力像插头一样换了个插座,电器们毫无感知。
剥掉所有术语,一个插件就是一份三句话的自我介绍:
export const name = 'hello-plugin' // 我叫什么
export const inject = ['tools'] // 我需要什么(可以不填)
export function apply(ctx) { ... } // 入驻后我要做什么
把它想象成入驻商场的租户:
name);inject)——商场保证这些就位之前,不会让你进场装修;apply)——挂招牌、摆货架,也就是把你的能力"注册"出去;插件可以简单到一个函数,也可以复杂到一个类。形态不重要,重要的是这份"自我介绍"的契约。
插件之间怎么合作?不是互相抄微信,而是通过服务。
dsh 里每个能力都占一个公开的、有名字的位置——像一个标准的墙面插座:
ctx.tools —— 工具登记处ctx.llm —— 模型接线总机ctx.fs —— 文件系统接口ctx.sessions —— 会话档案室想用工具?你不需要认识"工具部"的任何一个人,只要找到 tools 这个插座。依赖名字,而非依赖具体的人——这是解耦的第一课:插座后面接的是谁,随时可以换。
每个插件入驻时,商场发给它一块钥匙牌(ctx)。用这块牌子能做的一切操作——注册工具、监听事件、占用资源——都被记账。为什么要记账?为了下一节的主角。
传统插件系统最常见的烂尾方式:插件装了又卸,留下几个再也杀不掉的定时器、几个没人认领的监听器,系统越跑越脏,最后只能重启。
dsh 的解法朴素而彻底:你在钥匙牌(ctx)上做的每一个注册,都会被记账;退租时按账单逐条撤销。
ctx.effect(() => 返回清理函数) 告诉商场怎么关掉它。带来的直接后果是:插件可以随时加载、随时卸载、随时热替换——改一行配置,旧的退场、新的进场,不留残渣。上一节说"一切皆可替换",而"可逆的副作用"就是让"替换"敢真的发生而不留后患的地基。
插座(服务)解决"找到对方",但很多场景不适合一对一喊话:
dsh 用事件覆盖这些场景,几种模式各像一种生活场景:
| 模式 | 生活类比 | 特点 |
|---|---|---|
emit |
小区广播 | 谁想听谁听,说完就完 |
bail |
举手认领 | 排队问"谁能处理?",第一个举手的定案 |
waterfall |
流水线 | 每站加工一下必须传给下一站;不传,货就被扣下(= 拦截) |
serial |
依次签收 | 按顺序过手,每步等上一步 |
其中 waterfall 值得多看一眼,因为"拦截"就是这么实现的:想做一个权限网关?在流水线上占一站,检查到危险操作就不把货传下去(不调用 next()),整个请求到此为止。想做一个观察者?加工完原样传下去即可。一套机制,两种角色。
另一个容易混淆的点:dsh 里有两类"事件"。实时事件(如 agent/*、tools/*)像广播,听过就过了;会话事件(如每轮对话、每次工具调用的记录)像台账,一笔笔记进日志,回放、恢复、摘要全都从这本台账重建。原则一句话:凡是模型看过的东西,必须能从台账里查回来。
模型终究要通过某种方式操作世界,这个界面就是工具。一个工具是一张标准格式的"操作卡":
这里藏着 dsh 一个很克制的设计:回执必须是规范的数据,而不是一段话。比如"搜索文件"工具返回的是结构化的匹配列表,而不是一段"我帮你找到了三个文件,它们分别是……"的散文。原因:散文是给人看的,数据是给程序用的;从数据生成散文容易,反过来就要靠猜。人类可读的展示(UI 卡片)是独立的另一层,不混进模型的结果里。
还有两个理念值得一提:
单个插件好理解了,那一个跑起来的 dsh 是怎么拼出来的?答案是分层穿搭:
dsh-base(基础款:模型对接、工具、存储……)和各个组合包一层层穿上,再套上用户自己的 cordis.patch.yml(个人偏好层)。叠加规则简单粗暴:后穿的覆盖先穿的,而且是整件替换、不做缝缝补补。这带来一个舒服的生态关系:插件作者提供合理的默认值,用户永远可以在自己的层里整行覆盖,谁也不用求谁改代码。
想看自己身上到底穿了什么?一条 --dump-config 命令展开整套配置树,每一层、每一行都清清楚楚。
三种主要途径,本质区别在"用户拿到的是成品还是原料":
这个授权设计不是流程刁难,而是一次诚实的风险告知:允许构建脚本,等于允许陌生代码在你机器上、且在任何沙箱之外执行。所以官方建议:只授权给看过源码的包,并锁定到具体 commit。信任应该是一个显式的动作,而不是默认发生的事。
最后用一段话把所有概念连成线:
模型是大脑,harness 是工位。工位上的一切设备都是插件——它们靠服务(标准插座)找到彼此,靠依赖声明决定开工顺序,靠事件(广播与流水线)协作与拦截,靠可逆的副作用做到即插即拔不留渣。模型通过工具(操作卡)驱动这些设备,操作卡的说明书自动汇入模型的视野。一个个插件打成组合包分发,用户按层叠穿成自己的 profile——于是,一套完全属于你自己的智能体工位就成型了。
如果这张地图让你产生了"我想试试"的念头,接下来:
docs/user/develop/basic/index.zh.md(半小时教程,一个文件跑起来);