
字节笔记本
2026年10月6日 · 约 5 分钟读完
DeepSeek Harness 工具执行管线拆解
DeepSeek 在 GitHub 上开源的 agent 框架 DeepSeek Harness(命令行工具名 dsh)信奉“一切皆插件”,底层运行在 Cordis 插件框架之上,代码以 MIT 协议公开,目前处于快速迭代的开发者预览阶段。对这类运行时来说,最值得看懂的一条链路是:模型发出一次工具调用之后,这次调用究竟经历了什么。官方仓库 docs 目录里有一张工具执行管线图(tool-execution-pipeline),把策略、钩子、沙箱、文件系统守卫、结果改写、最终结果观察和 UI 渲染各自的位置标得清清楚楚,而且这些环节全部挂在主循环之外,不改变循环本身。本文沿这张图把整条管线走一遍。

起点:从模型输出到会话事件
一切从助手消息里的工具调用块开始。会话层先记下一条 tool/call 事件,日志在执行之前就落盘;UI 同时弹出一张 pending 卡片,用 presentCall(args) 把调用参数呈现给用户。随后调用依次穿过三道事件瀑布(waterfall):tools/pre-execute、tools/execute、tools/post-execute。三道瀑布都有改写这次调用的能力,分工却各不相同。
第一道瀑布:前置检查与单调守卫
tools/pre-execute 承载钩子、权限策略与沙箱决策,最先运行。紧随其后的是已注册的单调守卫(monotonic guard):守卫只有拒绝(deny)和弃权(abstain)两种选择,身份相关信息受保护,策略只能收紧、不能放松。任何一环说“拒绝”,工具体直接跳过。需要用户拍板时,管线发起 ctx.approval 一次性提示:询问缺席或无法回答时一律按拒绝处理;放行只对当前这一次生效(allowed-once);用户拒绝、取消或询问通道不可用,同样汇入拒绝分支。

第二道瀑布:环绕分发与文件闸门
过了守卫,调用进入 tools/execute 瀑布。它以环绕分发(around dispatch)的方式包住工具本体,超时、重试、指标统计都挂在这一层。真正执行的是注册工具的 execute() 函数;凡涉及 tool-fs 的文件变更,还要过 fs/write-intent 或 fs/edit-intent 闸门,先读后编辑的检查则沉在 fs/* 事件之下。工具执行期间产生的 todo/write、fs/observed、hook/invoked、hook/result、tool/code-dispatch 等会话事件,都由工具自己发出。
第三道瀑布与收尾链
tools/post-execute 是最后一次改写机会,能做四件事:接受、拦截、替换、追加上下文。值得注意的是,被拒绝的调用也会流经这道瀑布,拒绝同样是一种有记录、可观察的结果。之后进入收尾链:注册表先对候选结果做无损快照并做外层规范化,快照过程中抛出的异常会转成 isError 结果,不会击穿管线;接着可见工具定义上的 finalizeContent 回调执行最后一道仅内容不变式校验;随后 tools/result 以同步通知方式观察这个不可变、可被 JSON 无损表示的权威结果;会话层写下唯一的 tool/result 事件,作为面向模型的单一结论。整批调用结算完毕后,活动批次攒下的 additionalContexts 按 FIFO 顺序以 user/message 注入,位置排在已记录的工具结果之后;UI 的 pending 卡片也换成完成卡片。
这套设计换来了什么
钩子得以横跨不同工具族复用,工具本身不必与任何策略服务耦合;必须保证顺序、不得重排的所有者策略则注册成守卫,位置天然固定。快照加规范化的组合让“管线自身出错”也能变成一条合法的错误结果交回模型,主循环不至于中断。
Code Mode 走同一条路
Code Mode 下,保留的 run_code 传输和它派生的序列化子调用统统送进同一条管线:子调用携带父级 token,记录 tool/code-dispatch 事件,被拒绝时呈现为具有约束力的驳回(binding rejection),并省略 additionalContexts,以保持调用与结果相邻。
想亲手跑一遍,装好 Node.js 后执行 npx @deepseek-ai/dsh web 即可启动本地 Web UI。另外官方说明这份文档处于维护模式:图中的 Mermaid 流程由人工维护、生成器写出,确切的工具 schema 与事件签名以自动生成的目录为准,动手前建议对照仓库里的最新目录。



