前天写 一切皆插件:DeepSeek Harness 的野心与收敛鸿沟 时,我说 DeepSeek 开源的不是工具,是运行时。当时主要看的是官方页面和仓库门面。这两天陆续有人把整个仓库拉下来逐包分析,翻出来的东西比我想的更硬。
其中有一份源码深扒(作者 @Ai 学习的老章,文末有出处)把 200 多个包从启动配置翻到 Agent Loop、事件管线、上下文压缩,再加上我自己读了一遍官方架构文档,发现「一切皆插件」这句话真正站得住,靠的不是插件数量,而是三个很容易被忽略的细节。
这三个细节,恰好是「运行时」和「工具」的分界线。
一、没有不可替换的核心
先交代背景。官方架构文档里有一句话,值得单独拎出来:
There is no privileged core to patch: you extend dsh by mounting a plugin beside the others.
翻译一下:不存在一个需要去打补丁的特权核心——你扩展 dsh 的方式,是把插件挂在其他插件旁边。
这句话的分量在于它的覆盖面。dsh 里是插件的不只是工具和 UI,还包括 模型适配器、工具注册表、会话日志,以及 Agent Loop 本身。模型怎么请求、一步何时开始、工具怎么调度、任务什么时候停,全在插件树上,都可以被观察和改写。仓库里甚至备了一套第三方的 LLM 适配器,证明「换模型」真的只是换一个 Provider 的事。
这个设计我在 #347 里讲过它的野心:争 Agent 的运行时标准。但野心要靠机制兑现,接下来三个细节就是兑现方式。
二、细节一:注册即副作用,卸载即回滚
第一个细节藏在插件系统最基础的地方。
插件运行起来要注册很多东西:注册一个工具、监听一条事件、挂一条网页路由。常规做法是注册完就完事了,卸载时能清理多少算多少——结果是热插拔几次之后,旧工具还挂着,监听器越叠越多,系统行为开始漂移。做过插件化架构的人都见过这个病。
dsh 的做法是把注册本身当成副作用来管理:插件每注册一个能力,同时登记一个对应的清理动作;插件卸载或重载时,这些注册被一起撤销。官方文档的原话是 registrations are effects that unwind when their plugin unloads——注册即副作用,随插件卸载而展开回滚。
这个机制单独看不惊艳,但它回答了一个大问题:为什么敢把 UI、循环、存储全都开放成插件? 因为最危险的能力(循环和存储)也可以安全地换掉——换掉之后不会留下半个旧实现继续污染系统。
可替换是口号,可回滚才是能力。
三、细节二:模型看见的一切,必须能在日志里找到
第二个细节在会话系统。
dsh 的 Session 不是聊天记录,是一份 仅追加的事件日志:模型可见的所有消息都从这份日志推导出来,工具调用、工具结果、模型输出(包括流式的每个 chunk)全部写回同一处。一轮任务的事件序列大致是:
turn/start → step/start → user/message → request header
→ assistant/chunk → assistant/message
→ tool/call → tool/result → step/end → turn/end
# generated by hugo AI
注意 request header——模型供应商、模型名、思考强度、系统提示词、工具目录,会作为快照写进日志。也就是说,恢复一个会话时,系统知道当时到底给模型看了什么。
这份日志一次解决了五件事:
| 能力 | 含义 |
|---|---|
| 恢复 | 进程重启后继续同一个任务 |
| 分叉 | 从历史节点另开一条路线 |
| 回放 | 重建模型与工具当时看到的内容 |
| 检索 | 从事件流里找任务和消息 |
| 审计 | 说明某个文件修改从哪里来 |
我在 #347 里说 Trajectory 日志是「回归的数据基础」,源码分析把这件事说得更全了——它不只是回归的基础,它是整套系统的脊柱。而且源码里有一条很硬的原则:
模型看见的东西,必须能回到日志里找到。 否则恢复后的 Agent,会生活在另一份历史里。
这句话值得写进每一个做 Agent 持久化的团队的评审清单里。上下文压缩、截断、摘要,任何对历史的加工,如果不可回溯,恢复出来的 Agent 就不再是原来那个 Agent。
四、细节三:工具可以乱序完成,写回必须按原序
第三个细节最小,但最能看出工程品味。
模型一次可能吐出多个工具调用。dsh 会先判断哪些工具允许并行(默认并行池上限 10 个),哪些必须独占执行。并行本身不稀奇,稀奇的是后面这句:工具实际完成的顺序可以不同,但写回模型的结果,仍按模型最初的调用顺序排列。
为什么?因为同一轮上下文是模型的推理基础。如果网络快的搜索结果跑到前面、慢的文件修改跑到后面,上下文里的信息顺序就会抖动,模型看到的因果链就和它预期的不一致——这种不一致不会报错,只会让后续推理悄悄变味。
同理,任务被取消时,已经启动的动作尽量收尾,来不及启动的调用会写入合成结果——日志里永远不会凭空少半截工具链。
这三个细节放在一起看,指向同一个结论:一个 Agent 运行时的可靠性,不体现在它跑得起来,而体现在它被打断、被替换、被取消之后,状态依然自洽。
五、还有一件事:插件是宿主代码
说完设计,说一个必须提醒的风险。
dsh 的权限模型分三档:只读、工作区可写、完全访问。模型调用 Shell 和文件工具时,会经过沙箱与审批——这一层大家容易理解。容易忽略的是另一层:
装进 Profile 的第三方插件,是宿主代码。 它不经过沙箱,权限接近 dsh 进程本身。插件仓库里的安装脚本、依赖和 Host 代码,要按「一段即将在你机器上以你权限运行的程序」来审查,而不是按「一个扩展功能」来审查。
这不是 DeepSeek 一家的设计缺陷,而是所有插件化 Agent 运行时的共性边界。我在 企业级 Agent Runtime 的第一道防线:安全沙箱 里讲过沙箱这一层,当时的对象是模型行为;现在要补上的是插件行为——模型在笼子里,不代表给 Agent 装插件的手也在笼子里。
源码里两个默认值能看出官方对这件事的警惕:web_fetch 默认关闭(防内网探测类风险);创造模式允许动态加载和修改插件,官方文档对它的信任等级描述,接近给 Agent Shell 权限。
对用户的实用建议:工作区可写加逐次审批,是日常更稳的起点;装第三方插件之前,先让一个可信的模型把它的代码读一遍。
六、可以吹,但不必急着用
最后说使用建议。源码深扒作者的结论我基本认同,补充一点:
这份代码现阶段最大的价值是学习,不是日常使用。 它把「Agent 运行时应该长什么样」用 200 多个包具体地回答了一遍——可逆副作用怎么设计、事件日志怎么做脊柱、上下文三道控制线(稳定前缀保缓存、工具结果裁剪、80% 水位自动压缩)怎么省 token,这些问题的答案在源码里都能抄到。实测数据也给了参考:一轮 14 步的任务,缓存命中率 93%。
至于要不要拿它当日常工具:如果你已经在用成熟的编码 Agent,不必折腾——它还是预览版,破坏性变更写在官方文档里。但如果你在为公司构建 Agent 基础设施,这份代码值得逐包读一遍。运行时标准之争,最后比的不是谁先喊出口号,是谁先把这些细节做对。
写在最后
回到「一切皆插件」。这句话第一眼看是架构口号,拆开看是三个工程承诺:
- 可替换——靠没有特权核心
- 可回滚——靠注册即副作用
- 可追溯——靠模型所见皆可回到日志
口号人人会说,细节才是护城河。你在做 Agent 基础设施时,踩过哪个「注册了收不回来」的坑?欢迎留言聊聊。
本文源码分析部分参考了 @Ai 学习的老章 的《我扒了 DeepSeek Harness 219 个包,真有价值的是插件内核》,以及 DeepSeek Harness 官方架构文档。