工具不是能力延伸,是受管执行接口

内容纲要

想象这样一个场景:你让 AI 去删除一个文件夹。三秒钟之后,一个包含了两年数据的项目目录消失了。没有恢复按钮,没有回收站,就是没了。

这一刻,问题的性质从"AI 说错了什么"变成了"AI 做错了什么"。说错话,充其量尴尬一下。但如果 AI 能调用工具、执行指令、碰到真实世界,那它的每一个动作都会带上后果。能力增强了,代价也增强了。

很多人还在把工具理解成 Agent 的"能力延伸"——好像就是把模型的触手拉长了一点,让它能动动文件、调调 API。但如果你真的看过一个生产级 Agent 系统怎么处理工具调用,你就会明白:工具不是能力延伸,是受管执行接口。系统从那一刻起,就不再是一个"执行机器",而变成了一个"调度机构"。

并发安全性是第一个检查站

在 Claude Code 的 <code>runTools()</code> 函数里,有一个细节很能说明问题。工具列表到手之后,系统做的第一件事不是执行,而是分批

通过 <code>partitionToolCalls()</code> 和 <code>isConcurrencySafe()</code> 判断哪些工具调用可以并发执行,哪些必须排队。为什么?因为并发执行可能导致顺序混乱。比如你同时删除一个文件,又同时读取它,结果就不确定了。

但这还不是最有意思的部分。即使工具要并发执行,系统也会先把所有的 context modifier 缓存起来,然后按原始顺序回放。翻译一下:在执行层面允许并发,但在语义层面保持确定顺序。换句话说,系统承诺:无论你怎么并发,最终的上下文演化路径都是确定的、可重现的。

这是什么意思?意思是系统优先级很清楚——一致性压倒性能

执行前已经发生了太多事

当系统真正执行一个工具的时候,看起来就像:调用工具函数 → 获得结果 → 继续。但实际上呢?

在 <code>runToolUse()</code> 里,系统已经接进来了一整套防护机制:

  • Permission Check —— 权限判定,决定这个工具这次是否被允许执行
  • Pre-execution Hooks —— 执行前钩子,可以在最后时刻发出警告或拒绝
  • Telemetry & Logging —— 完整的追踪和日志,记录每一个工具调用的时间、参数、结果
  • Synthetic Error Materialization —— 把执行失败转换成结构化的错误消息,补齐缺失的结果
  • Post-execution Hooks —— 执行后钩子,可以对结果进行二次验证或修改
  • Failure Compensation —— 失败补偿,如果出了问题,系统自动清理现场

工具执行在这套系统里,不是"裸调用"。它被包裹在一个完整的前-中-后流程里。中断、失败、权限问题,系统都见过,都有套路。

权限系统:第三种决策

有一个很关键的函数叫 <code>CanUseToolFn</code>。它决定了一个工具在某一刻是否允许被执行。返回值分成三种:

  • Allow —— 允许,继续执行
  • Deny —— 拒绝,停止执行,返回错误
  • Ask —— 询问,系统自己也不确定,把决定权交给人

看到这个"Ask"了吗?这就是系统的深刻洞察:某些决定不应该由模型单方面做,也不应该由系统单方面做,而应该由人来做

这意味着什么?意味着系统承认自己的局限。它没有那么聪明,足以替用户决定"你真的想删除这个文件吗"。所以干脆让人来决定。

这就是为什么很多生产级 Agent 系统都有一个"需要人工批准"的流程。不是因为系统不信任自己,而是因为系统知道:权限不是技术问题,是治理问题

中断语义:StreamingToolExecutor 的设计

想象一个场景:系统发出了五个并行的工具调用。其中两个已经在执行了,还有三个排队中。这时用户突然按了 Ctrl+C。

系统怎么办?简单粗暴地中止?不对。系统会:

  1. 消费 StreamingToolExecutor 的剩余缓冲(确保已经发出的部分数据不丢失)
  2. 对那两个正在执行的工具调用生成合成的 tool_result(告诉模型"这个没完成,被打断了")
  3. 对那三个排队的调用,根据它们的 <code>interruptBehavior</code> 设置,决定是直接取消还是让它们继续等待

代码里有一个概念叫 <code>synthetic error message</code>。它用来区分三种中断原因:

  • Sibling error —— 同时执行的另一个工具失败了,连带影响
  • User interrupted —— 用户手动中止了
  • Streaming fallback —— 流式获取失败,系统降级处理

每一种原因对应不同的恢复策略。系统不会笼统地说"都失败了",而会说"第二个工具因为第一个工具出错而没执行"或"用户在第三秒按了中止"。

这就叫中断是一等语义。不是"出了问题所以中止",而是"中断本身是一种系统状态,需要被妥善处理"。

Bash 为什么最可疑

在 Claude Code 里,Bash 这个工具有特别待遇——专门针对它写了一大段操作规约。为什么?

因为 Bash 几乎不受任何领域边界的约束。它可以:

  • 直接读写文件系统的任何地方
  • 启动、中止、监控任意进程
  • 发起网络请求、下载、上传
  • 修改 Git 仓库的历史
  • 执行管道、重定向、后台运行

还有复杂的 shell 语义(<code>&&</code>、<code>||</code>、<code>|</code>、重定向符等),一个命令就能做到别的工具需要五步的事。

正因为这样,Claude Code 对 Bash 有一套专门的防御:

  • 明确列出了危险命令的黑名单(比如 <code>rm -rf /</code>、<code>git push --force</code>)
  • 限制 subcommand 的数量,防止复合命令导致检查失控
  • 对 Bash 的 hooks 做了特殊处理,确保每一步都能被追踪
  • 对 Git 操作做了完整的审计,防止无可挽回的改动

这不是说 Bash 不能用。而是说,权力越大、风险越高,约束就越细。系统对待 Bash 的态度就像对待一个精神不太稳定的天才——很想用它的智力,但必须给它套上足够的防护装置。

工具系统保护的,不只是用户

这里有个很有意思的反转。很多人以为工具的权限管理是为了"保护用户"。但实际上,系统也在保护自己

为什么?因为工具的执行结果会影响系统的状态。如果一个工具调用留下了不完整的 tool_result,系统就无法继续工作。如果执行顺序出了问题,上下文演化就失序了。如果中断处理不当,就会留下孤立的工具调用,导致系统的逻辑图断裂。

权限系统、调度机制、中断处理,这些看起来像是"防御措施"的东西,其实是系统自己的器官。没有它们,系统根本活不了。

核心观点

一旦模型开始调用工具,Agent 就不再是一个"执行机器"了。

它变成了一个调度机构——需要在工具之间协调、在权限和能力之间平衡、在安全性和效率之间找到平衡点。工具的权限管理不是锦上添花,而是基础设施。

这就是为什么真正的 Agent 系统,从来不会让模型"想用什么工具就用什么工具"。系统会说:"你想用什么?让我看看,权限检查一下,调度安排好了,再准许你用。执行过程中如果出问题,我来处理。"

权限不是限制,是秩序。秩序让 AI 和人类能在同一个系统里共存。

<!-- 改动说明:核心选题为工具执行的权限管理和调度机制。重点强调并发安全性、权限决策的三态(Allow/Deny/Ask)、中断处理的一等语义,以及 Bash 工具的特殊风险。通过具体代码流程说明工具执行不是裸调用,而是被多层防护包裹的有管制过程。最后反转视角,说明工具管制同时也在保护系统本身的一致性。 -->

滚动至顶部