工具是 agent 的"手脚"。但手脚怎么被管住,才是架构题。这一章看 dsh 的工具系统:一次工具调用要经过哪五道关卡、工具向模型呈现的两种形态(原生 Function Calling 与 Code Mode)有什么区别、以及本机实测的 27 个工具是怎么来的。
核心结论先行:dsh 把「工具执行」拆成了注册表 + 事件流水线,工具本体只是执行器,所有横切策略(超时、溢出、权限、重复提醒)都是插在流水线上的监听器。
在讲执行之前,先讲呈现 —— 因为这是 dsh 工具系统最特别的决策。
模型调用工具有两种范式:
| 范式 | 机制 | 优点 | 缺点 |
|---|---|---|---|
| 原生 Function Calling | 把每个工具描述成 JSON Schema,模型返回结构化调用 | 标准、可靠 | 工具多时 schema 占大量上下文 |
| Code Mode | 只给模型一个 run_code 工具,让它写代码来调其他工具 | 上下文极小 | 模型要会写代码 |
dsh-tools 的 mode 配置可以三选一 [官方文档]:
注册表还决定以何种方式向模型呈现工具:mode 配置可以选择原生 Function Calling、Code Mode,或同时选择两者;单个 agent 可用 presentAs 为自己遮蔽该默认值。 —— dsh-tools/README.zh.md
presentAs 是关键 —— 呈现方式不是全局的,可以按 agent 遮蔽。你可以在 preset 里给某个子 agent 配成 Code Mode(省上下文),给主 agent 配 Function Calling(可靠)。
Code Mode 下模型看到的不是一个工具清单,而是一个 run_code 工具 —— 模型用代码(比如调用一个函数库)来间接使用其他工具。dsh-code-runtime-worker-thread 是它的执行后端。
理解:这类似 Claude Code 的 Bash 全能模式 —— 牺牲一点可靠性,换一个干净得多的上下文。第 1 章说过 dsh 的系统提示词实测 9927 字符,如果全用 Function Calling 塞 27 个工具的 schema,会大得多。Code Mode 是 dsh 控制上下文的杠杆之一。

配图说明:pre-execute(允许/拒绝门禁,可以否决)→ 单调守卫(只能更严不能更松)→ execute(环绕分发包装层,超时/重试/指标挂这)→ post-execute(检查/替换结果,spill-policy 挂这)→ finalizeContent(工具自定义收尾)→ tools/result(只读通知)。底部是两种呈现:原生 Function Calling(每工具一个 schema,可靠但占上下文)vs Code Mode(一个 run_code 工具,上下文极小)。
dsh-tools 的 README 第一段就给出了完整流水线 [官方文档]:
工具插件注册各自的 schema 和执行器;agent loop 依次让每次调用经过 tools/pre-execute(可扩展的允许/拒绝门禁)→ 已注册的单调守卫 → tools/execute(供超时/重试/指标插件使用的环绕分发包装层)→ tools/post-execute(检查/替换结果、附加上下文)→ 由工具定义持有的 finalizeContent 边界 → 仅观测的 tools/result 通知。 —— dsh-tools/README.zh.md
画成图:
逐段解释:
① tools/pre-execute —— 允许/拒绝门禁 可扩展的决策点。监听器可以 block(拒绝)或 accept(放行)。拒绝时工具不执行。
② 单调守卫(monotonic guard) "单调"的意思是:守卫只能让结果更严格,不能让结果更宽松。这是安全关键 —— 如果某个守卫能放开另一个守卫的拒绝,防线就破了。守卫注册后对每次调用生效。
③ tools/execute —— 环绕分发包装层 这是"横切策略的挂点":超时策略、重试策略、指标收集都包装在这一层。它们包住真正的工具执行器,而不需要改工具本身。
④ tools/post-execute —— 检查/替换结果 可以改写结果。第 9 章会看到 spill-policy 就挂在这里 —— 超长结果被替换成预览 + 文件路径。
⑤ finalizeContent —— 工具定义的收尾 工具自己持有的边界,处理"结果怎么变成最终内容"。
⑥ tools/result —— 只读通知 仅供观测(日志、UI、统计)。不能改结果。
从会话日志的 request/header 事件提取 [实测],27 个工具按功能分组:
| 分组 | 工具 |
|---|---|
| 文件系统 | read · write · edit · glob · grep |
| 执行 | pwsh(Windows)/ bash(Linux,本机未启用) |
| 任务管理 | todo_write |
| 目标 | create_goal · get_goal · update_goal |
| 后台任务 | job_output · job_list · job_kill |
| 子 agent | subagent · subagent_fork · send_message · interrupt_agent · list_agents |
| 编排 | workflow · ralph |
| 搜索 | web_search |
| 提问 | ask_user_question |
| 技能 | skill |
| 图像 | generate_image_gpt · generate_image_gemini |
| 计划 | exit_plan_mode |
| 图像读取 | read_image |
工具清单为什么是 27 个? 回看第 3 章:工具属于 agent 平面,由 agent preset 决定。本机的 dsh-toolbelt preset 装了什么,模型就有什么工具。官方基础工具(pwsh/fs/glob/grep/todo/web…)加上 toolbelt 的图像生成、跨 agent 记忆等,凑成了 27 个。
这也解释了为什么两个 dsh 实例的工具清单可能完全不同 —— 工具集是配置,不是代码。
第 4 章说过:横切关注点不占 seam,做成事件监听器。工具系统里就有三个活例子:
工具调用超时策略:一个 tools/execute 包装器,在 exec.signal 上武装每工具截止时间,超时则返回 TOOL_TIMEOUT。 —— dsh-tool-call-timeout-policy/README.zh.md
工具自己的 schema 可以声明 timeoutMs(比如 web 工具的 fetchTimeoutMs / searchTimeoutMs 配置),策略在 tools/execute 层强制。
理解:超时不在工具内部实现,而在流水线上统一实现。任何工具想有超时,声明一个字段即可。
工具结果 spill 策略:一个 tools/post-execute 转换器,防止过大的纯文本工具结果进入模型上下文。当最终结果超过 maxInlineBytes 时,通过 ctx.spillStore 保存完整文本,并将面向模型的结果替换为有界的首尾预览、后端定位信息与取回指引。 —— dsh-spill-policy/README.zh.md
这正是本教程写作过程中反复出现的机制 —— 超长工具输出会变成:
两个刻意的跳过项:嵌套执行跳过(复用外层结果)、read 工具跳过(避免 read → spill → read again 循环)。
重复工具调用守卫插件:当 agent 在完全相同的工具调用上循环时给出建议性提醒。 —— dsh-repeat-tool-reminder/README.zh.md
注意"建议性"(advisory)—— 它只提醒,不拦截。真正的拦截属于 pre-execute 门禁。
以文件系统工具为例,看"工具"和"策略"怎么分工。
dsh-tool-fs 是三个面向模型的工具的执行器:
| 工具 | 行为 |
|---|---|
| read | 读取文件,返回编号行 + 渲染 footer |
| write | 原子写入 + 创建目录 |
| edit | 字面量替换 |
而"编辑前先读"、"版本防护"、"写前检查"这些策略不在工具里,在 dsh-fs-observation-policy —— 第 4 章解剖过,它是通过 fs/* 事件门禁工作的,没有服务。
dsh-tool-fs 的 README 用一句话点破:
工具就是执行器;策略是事件门禁。 —— dsh-tool-fs/README.zh.md
翻译:dsh-tool-fs 只负责"面向模型的 schema + 调用 ctx.fs + 渲染结果",所有"该不该做"的判断都在流水线的其他环节。工具层换成别的实现,策略一行不动。
两个补充设计:
Schema 子集:dsh-tools 强制执行一个"原始 JSON Schema 子集"(官方文档有专门小节),工具定义用受限的 schema 描述参数。这保证所有工具的 schema 都能被可靠地验证和呈现。
工具自带 UI:每个工具定义可以声明 presentCall / presentResult,把工具调用在 UI 里渲染成卡片而不是裸 JSON。第 12 章会看到 Web 层的 dsh-client-ui-tool 怎么消费它。
工具系统是 dsh 流水线思想的典型体现:
下一章是这一切落盘的地方:会话 —— 事件溯源与 surface 投影。