DeepSeek Harness 工具执行流水线:一次工具调用的守卫、执行与定格

DeepSeek Harness 工具执行流水线:一次工具调用的守卫、执行与定格

本文是 dsh 官方参考「工具执行流水线」的导读。上一篇文章《Agent 生命周期》里,工具调用只是时序伪代码里的一行 tool/call* -> tools/pre-execute -> tools/execute -> tools/post-execute -> tool/result*;这一篇把它完整展开------一次工具调用如何被三组 waterfall(瀑布式事件)层层改写,最终定格为一条 tool/result 事件


〇、先记住一句话

一次工具调用 = 三次 waterfall(pre-execute / execute / post-execute)+ 一次结果定格(快照 → finalizeContenttools/result)。

  • 前置瀑布tools/pre-execute)管"能不能跑":钩子、权限、沙箱、审批;
  • 执行瀑布tools/execute)管"怎么跑":超时、重试、指标包裹工具体;
  • 后置瀑布tools/post-execute)管"结果怎么用":接受、阻止、替换、添加上下文;
  • 结果定格 :注册表对结果做无损快照finalizeContent 执行最后的仅内容不变式tools/result 同步通知,最终冻结为唯一一份模型可见的权威事实。

理解这条主线,后面所有细节都是它的展开。


一、全景:一次工具调用的完整旅程

先看整体流程图,再逐段拆解。
#mermaid-svg-lzrC2tj4aA3NjQ5V{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-lzrC2tj4aA3NjQ5V .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-lzrC2tj4aA3NjQ5V .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-lzrC2tj4aA3NjQ5V .error-icon{fill:#552222;}#mermaid-svg-lzrC2tj4aA3NjQ5V .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-lzrC2tj4aA3NjQ5V .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-lzrC2tj4aA3NjQ5V .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-lzrC2tj4aA3NjQ5V .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-lzrC2tj4aA3NjQ5V .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-lzrC2tj4aA3NjQ5V .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-lzrC2tj4aA3NjQ5V .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-lzrC2tj4aA3NjQ5V .marker{fill:#333333;stroke:#333333;}#mermaid-svg-lzrC2tj4aA3NjQ5V .marker.cross{stroke:#333333;}#mermaid-svg-lzrC2tj4aA3NjQ5V svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-lzrC2tj4aA3NjQ5V p{margin:0;}#mermaid-svg-lzrC2tj4aA3NjQ5V .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-lzrC2tj4aA3NjQ5V .cluster-label text{fill:#333;}#mermaid-svg-lzrC2tj4aA3NjQ5V .cluster-label span{color:#333;}#mermaid-svg-lzrC2tj4aA3NjQ5V .cluster-label span p{background-color:transparent;}#mermaid-svg-lzrC2tj4aA3NjQ5V .label text,#mermaid-svg-lzrC2tj4aA3NjQ5V span{fill:#333;color:#333;}#mermaid-svg-lzrC2tj4aA3NjQ5V .node rect,#mermaid-svg-lzrC2tj4aA3NjQ5V .node circle,#mermaid-svg-lzrC2tj4aA3NjQ5V .node ellipse,#mermaid-svg-lzrC2tj4aA3NjQ5V .node polygon,#mermaid-svg-lzrC2tj4aA3NjQ5V .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-lzrC2tj4aA3NjQ5V .rough-node .label text,#mermaid-svg-lzrC2tj4aA3NjQ5V .node .label text,#mermaid-svg-lzrC2tj4aA3NjQ5V .image-shape .label,#mermaid-svg-lzrC2tj4aA3NjQ5V .icon-shape .label{text-anchor:middle;}#mermaid-svg-lzrC2tj4aA3NjQ5V .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-lzrC2tj4aA3NjQ5V .rough-node .label,#mermaid-svg-lzrC2tj4aA3NjQ5V .node .label,#mermaid-svg-lzrC2tj4aA3NjQ5V .image-shape .label,#mermaid-svg-lzrC2tj4aA3NjQ5V .icon-shape .label{text-align:center;}#mermaid-svg-lzrC2tj4aA3NjQ5V .node.clickable{cursor:pointer;}#mermaid-svg-lzrC2tj4aA3NjQ5V .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-lzrC2tj4aA3NjQ5V .arrowheadPath{fill:#333333;}#mermaid-svg-lzrC2tj4aA3NjQ5V .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-lzrC2tj4aA3NjQ5V .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-lzrC2tj4aA3NjQ5V .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lzrC2tj4aA3NjQ5V .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-lzrC2tj4aA3NjQ5V .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lzrC2tj4aA3NjQ5V .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-lzrC2tj4aA3NjQ5V .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-lzrC2tj4aA3NjQ5V .cluster text{fill:#333;}#mermaid-svg-lzrC2tj4aA3NjQ5V .cluster span{color:#333;}#mermaid-svg-lzrC2tj4aA3NjQ5V div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-lzrC2tj4aA3NjQ5V .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-lzrC2tj4aA3NjQ5V rect.text{fill:none;stroke-width:0;}#mermaid-svg-lzrC2tj4aA3NjQ5V .icon-shape,#mermaid-svg-lzrC2tj4aA3NjQ5V .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lzrC2tj4aA3NjQ5V .icon-shape p,#mermaid-svg-lzrC2tj4aA3NjQ5V .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-lzrC2tj4aA3NjQ5V .icon-shape .label rect,#mermaid-svg-lzrC2tj4aA3NjQ5V .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lzrC2tj4aA3NjQ5V .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-lzrC2tj4aA3NjQ5V .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-lzrC2tj4aA3NjQ5V :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} denied 或 审批被拒
allow
Assistant message 含 tool-call 块
Session event: tool/call

执行前记录
UI pending card

presentCall args
tools/pre-execute waterfall

hooks · permission · sandbox
单调守卫 deny 或 abstain

  • ctx.approval 一次性审批
    工具体被跳过

denied · rejected · cancelled
tools/execute waterfall

timeout · retry · metrics 环绕 dispatch
注册的工具 execute body
Tool-owned 事件

todo/write · fs/observed · hook/* · tool/code-dispatch
tools/post-execute waterfall

accept · block · replace · add context
Registry 外层规范化

无损快照 pipeline/result
ToolDefinition.finalizeContent

最后仅内容不变式
tools/result 同步通知

冻结的权威结果
Active-batch additionalContexts FIFO

结果之后注入 user/message
Session event: tool/result

单一 model-facing 结果
Tool batch settled 批次结算
UI completed card

presentResult args result

注意图里最关键的一句话:tools/pre-execute → 单调守卫 → tools/executetools/post-execute 这三个 waterfall 可以改写一次调用 ;而 finalizeContenttools/result 在它们之后运行,由工具定义自身控制,不再参与改写。


二、起点:模型发出 tool-call

流水线从模型输出一个工具调用块开始:

  1. Assistant message 包含 tool-call 块------模型决定调用某个工具;
  2. Session event:tool/call执行之前就被记录下来(持久化事实,可回放);
  3. UI pending card :界面立刻展示一张"进行中"卡片,调用 presentCall(args) 把参数呈现给用户。

从这一刻起,调用进入流水线。注意顺序:先落 tool/call 事件、再进守卫------即使后面被拒绝,这次"试图调用"的事实也已经留档。


三、第一道闸门:tools/pre-execute waterfall

这是调用能否放行的第一道(也是最主要的一道)闸门。它承载三类关注点:钩子(hooks)、权限(permission)、沙箱(sandbox)。各种插件挂在这里,对调用做出裁决。

3.1 可能的裁决

瀑布式事件允许每个监听者对调用做出自己的决定:

裁决 含义
allow 放行
deny 拒绝(本轮调用不执行)
throw 抛错(wrapper 抛错会向上冒泡)
ask 需要询问用户
allowed-once 一次性放行

3.2 单调守卫(monotonic guards)

在瀑布之外,注册表还维护着一组单调守卫

  • 每个守卫只能选择 denyabstain (弃权)------守卫不能放行,只能拦或不管;
  • 身份受保护:守卫的裁决不会被其他环节绕过或重排;
  • 所有"不得重新排序的所有者策略 "也以已注册守卫的形式存在------即使有 ctx.approval 之类的交互流程,它们仍会被执行。

3.3 ctx.approval:一次性审批

ctx.approval 提供一次性(one-shot)审批提示:需要用户拍板时,它发起一次询问。

  • 它在单调守卫之前处理"询问"环节;
  • 如果审批缺席或无法回答 ,结果一律按 deny 处理------拿不到明确许可,就不放行。

3.4 被拒的后果

一旦出现 denied 或 approval refused ,工具体(tool body)被完全跳过 :不执行、也不产生副作用,调用直接以拒绝/取消收场(rejectedcancelledunavailable 等状态)。

小结:tools/pre-execute 决定了"这次调用有没有资格跑"。


四、执行:tools/execute waterfall

通过守卫之后,进入执行阶段。这个 waterfall 把环绕分发(dispatch)的关注点包在真正工具体的外面:

4.1 环绕关注点

关注点 作用
timeout(超时) 限制一次调用最长执行时间,到期强杀
retry(重试) 允许对失败调用进行有限重试
metrics(指标) 采集调用耗时、成功率等观测数据

这些都由其他插件在 tools/execute 上包装实现,工具体本身不需要关心。

4.2 注册的工具 execute body

最内层是已注册工具的 execute() 主体------真正干活的代码。它执行过程中会产生两类事件:

① 文件系统意图事件(仅 tool-fs 的变更)

  • fs/write-intent(写入意图)
  • fs/edit-intent(编辑意图)

这些是"先读后写"策略的门禁点:文件系统的先读后编辑检查位于 tool-fs 之下,通过 fs/* 事件实现 。它由专门的策略插件(如 dsh-fs-observation-policy)挂接,不改变工具 schema

② Tool-owned 会话事件(工具自己发出的)

  • todo/write(任务清单更新)
  • fs/observed(文件已被观察)
  • hook/invokedhook/result(钩子被调用及其结果)
  • tool/code-dispatch(code 模式的代码分派)

若 wrapper 在执行中抛错(wrapper throws),异常沿 tools/execute 向上冒泡,按 throw 处理。


五、收尾:tools/post-execute waterfall

执行完成后,结果先经过后置瀑布------这是"结果级"的最后改写机会:

行为 含义
accept 接受当前结果
block 阻止该结果(视作失败/丢弃)
replace 用新内容替换结果
add context 向会话附加额外上下文

到这里,一次调用可以被改写的环节全部结束。接下来进入"定格"阶段------结果不再被 waterfall 改写。


六、结果定格:从快照到权威结果

6.1 Registry 外层规范化:无损快照

注册表对候选结果做外层规范化(outer normalization)

  • pipeline/result无损快照(snapshot)
  • 如果快照本身失败,会先把失败规范化throws 变成 isError 之类的结构),再继续走后面的不变式。

快照的意义:让后续回调看到的是同一份固定的结果,而不是可能被并发改动的活对象。

6.2 ToolDefinition.finalizeContent:最后的仅内容不变式

finalizeContent 由工具定义自身声明,是最后一个仅内容(content-only)的不变式

  • 同步执行,只允许调整内容;
  • 它使用的是已经随快照固定的结果;
  • 它不参与 waterfall 改写------这是定义自己收尾的最后一道关。

6.3 tools/result:同步通知冻结结果

tools/result 是一个同步通知 ,把冻结的、权威的结果分发给监听者。此刻结果已经定型,监听者只能观察,不能再改。

6.4 Active-batch additionalContexts FIFO

如果本次调用属于一个"活动批次",批次的 additionalContexts 会以 FIFO 顺序,在已记录的工具结果之后 注入 user/message。这样保证:注入的上下文总是排在本批结果后面,不会打乱时序。

6.5 Session event:tool/result

最后,流水线产出一条 tool/result 会话事件------这是唯一一份面向模型(model-facing)的结果。无论中间经过多少次改写,模型最终看到的只有这一份定格结果。


七、批次结算与 UI 呈现

  • Tool batch settled :当一批调用(例如模型一次输出中的多个工具调用)的所有 tool/result 事件都记录完成后,批次结算;
  • UI completed card :界面把"进行中"卡片翻转为"已完成"卡片,调用 presentResult(args, result) 同时呈现原始参数最终结果

至此,一次工具调用的完整旅程结束:从 tool/calltool/result,全程有据可查、可回放。


八、三个 waterfall 能力速查表

Waterfall 时机 承载能力 能改写调用吗
tools/pre-execute 执行前 钩子、权限、沙箱、审批(含单调守卫 + ctx.approval ✅ 可拒绝/放行
tools/execute 执行中 超时、重试、指标;工具体本体;fs/* 意图与 tool-owned 事件 ✅ 可抛错
tools/post-execute 执行后 accept / block / replace / add context ✅ 可改结果
finalizeContent + tools/result 定格后 仅内容不变式、同步通知 ❌ 只读、不改写

九、几个值得记住的设计点

  1. 三处 waterfall 是"一次调用可被改写的全部" :所有钩子、策略、审批都挂在这三个点上,过了 tools/post-execute 就再没人能动它;
  2. 守卫只能拦、不能放 :单调守卫 deny or abstain,且身份受保护------这保证了"安全策略不可被绕过";
  3. 拿不到许可 = 拒绝ctx.approval 一次性询问,缺席或无法回答一律 deny,工具体被跳过;
  4. 文件系统先读后写不碰 schema :通过 fs/* 意图事件实现,是 tool-fs 之下的门禁,而不是工具定义的改动;
  5. 结果只有一份 :中间再多的 replace / add context,最终都以单一 tool/result 事件呈现给模型------回放时只有一个"权威答案"。

一句话收尾:dsh 用"三道瀑布 + 一次定格"把一次工具调用变成了可审计、可拦截、可改写、且只留一份权威结果的过程。理解了这条流水线,就理解了为什么在 dsh 里加一个审批策略、换一个沙箱、或改造某个工具的行为,都是在"往流水线上挂插件",而不是改工具本身。

相关推荐
学无止步_穷其一生2 小时前
把 DeepSeek Harness 智能体嵌进 IntelliJ IDEA —— 类 Qoder 的 AI 编程插件
人工智能·intellij-idea·idea插件·deepseek
小马9269 小时前
插件化 Agent 架构:DeepSeek Harness 如何让智能体重构不再需要 Fork
架构·deepseek·agent架构
进军的码农13 小时前
DeepSeek-V4-Pro 正式版本地部署:联想 ThinkStation P4 硬件架构拆解与推理链路全验证
vllm·deepseek·ai推理·大模型本地部署·联想工作站·thinkstation p4
Zach_菠萝侠14 小时前
【DeepSeek Harness 研究】进化方向4:安全加固 思考、设计与实现
开发语言·安全·deepseek
DS随心转APP16 小时前
AI生成的word怎么下载?AI导出鸭技术架构深度测评
人工智能·ai·架构·word·deepseek·ai导出鸭
梦想的颜色18 小时前
DeepSeek‑Harness 插件系统源码级拆解:热插拔、自动装卸原理与自研 Web 插件架构落地指南
架构·deepseekharness·cordis内核·可插拔架构·web框架自研·后端架构设计·插件系统源码
我才是银古19 小时前
OpenCode × DeepSeek 配置优化实战:一次「费用 和 Token 效率优先」的深度重构
deepseek·opencode
潘正翔20 小时前
DeepSeek Harness从0到1部署
人工智能·开发·codex·deepseek·harness·deepseekharness·cludecode
AprChell20 小时前
DeepSeek Harness 开源了一套 Vibe Coding 工程流水线
ai编程·deepseek·vibecoding