Skip to content

第 4 章:工具、权限与中断 — 为什么代理不能直接碰世界 ​

出处

来源:原网页 · 状态:已读 · PDF:book1-claude-code.pdf p.33–41

一句话 ​

工具是受管执行接口;权限是代理系统的基本器官。 工具不是意见,是动作。模型提建议不等于拥有授权。没有停下语义的执行系统,最终只能靠用户外部打断来补完设计。

核心概念 ​

只输出文本时,出错主要增加沟通成本。一旦调工具,问题性质就变了:跑了一条不该跑的命令,文件会被删、进程会被停、Git 历史会乱。能力增强伴随后果增强。工具系统最重要的问题只有一个:谁来约束这些工具。Claude Code 的回答是把工具变成受管执行接口,避免让模型直接伸手碰世界。

调度属于行为宪法 ​

runTools() 不直接执行一串 tool_use,先按并发安全性分批。partitionToolCalls() 读 inputSchema,调 isConcurrencySafe():能并发的归一批,不能的拆成串行单元。并发路径里不让最先完成的工具抢先改上下文,而是先缓存 contextModifier,再按原始 block 顺序回放——执行可以并发,语义上的上下文演化仍保持确定顺序。

并发可以提高吞吐,但不能破坏因果秩序。工具只会跑得更快却不能保证上下文一致性,就会替系统制造另一种随机性。成熟代理系统不迷信并发:并发是需要证明自身无害的例外,不是默认自由。

真正执行前已经发生了很多事 ​

tool_use 一旦出现,下一步不是自然执行。runToolUse() 周围已经接上 permission、hooks、telemetry、synthetic error materialization。工具执行是完整流程:前置校验 → 执行中事件 → 执行后修正 → 失败补偿。

库函数默认属于程序内部,调用者自己承担后果;工具属于模型与外部世界之间的接口,系统不能假设调用者具备稳定判断。工具不该被建模为「模型能力的延长线」,而该被建模为「需要运行时代为管理风险的外部能力」。一旦接受这一点,permission、hooks、interrupt、synthetic result 就更像常识,而不是负担。

权限先于能力:三态,不允许布尔偷懒 ​

CanUseToolFn 的存在说明:工具是否允许执行,不由模型自己说了算。hasPermissionsToUseTool(...) 返回 allow / deny / ask。成熟权限系统除了「能」和「不能」,还要承认第三种状态:系统自己也不该替用户做决定。

  • deny:直接拒绝(对这个 tool_use_id sticky,不得重试成 allow)
  • ask:进入协调器、swarm worker、classifier 或交互式审批;ask 永不自动升级成 allow
  • allow:才真正放行

理解意图 ≠ 拥有授权 ≠ 拥有持续授权。系统必须把「会做」和「可以做」分开。模型提出动作建议;是否放行,由运行时、规则和用户决定。

PermissionResult.ts 给 allow / deny / ask 准备了专门描述函数——权限不只是内部布尔值,而是有独立语义的运行时对象。当代理说「我需要确认」时,系统是在声明责任边界。责任边界说清楚,后续的拒绝、放行、缓存规则、临时授权、永久授权才有地方安放。连「这一步是我能做、不能做,还是需要问」都说不清,就不该碰终端。终端不会替系统补完语义,终端只会执行。

中断是一等语义 ​

工具一旦并发和流式执行,面对的是 queued / executing / completed / yielded 的队列,不再是单一动作。StreamingToolExecutor 独立处理中断和丢弃:

  • streaming fallback 时可整体 discard 当前工具集合
  • 按原因生成 synthetic error:sibling error、user interrupted、streaming fallback
  • 区分:因为别的并行工具出错而取消 / 因为用户 interrupt 而取消 / 因为 fallback 而放弃当前批次
  • 工具还有 interruptBehavior:用户插话时究竟 cancel 还是 block

中断不是「执行失败的一种特殊情况」,而是和执行本身同样重要的语义。系统不仅要知道工具能不能开始,还要知道它被打断时如何收场、如何补齐结果、是否允许新消息插入。不仅设计开始,也设计停下。

事件下一步
并发批内单个工具失败其它工具照常;按 block 顺序回放 contextModifier
用户插话 + interruptBehavior=cancel取消执行,生成 user interrupted synthetic result
用户插话 + interruptBehavior=block继续工具至自然终点,阻塞新消息
streaming fallback 时有 pending tool_usediscard 整批,按顺序 yield fallback synthetic result
abort signal补齐 synthetic tool_result,账本闭环
classifier 拒绝deny,不进入 ask 路径

Bash 永远比别的工具更可疑 ​

Bash 过于通用:file read 至少不会顺手杀进程,grep 至少不会偷偷 push,Bash 几乎什么都能做。两层高压治理:

  1. prompt:对 git、PR、危险命令、hook、force push、interactive flags 写大量明确规则。凡是后果大的地方,系统就不怕啰嗦。
  2. 权限与安全判定(bashPermissions.ts):shell 语义、命令前缀、重定向、wrapper、安全环境变量、classifier 与规则匹配;复合命令给 subcommand 数量设上限,防止检查失控。

高风险能力不应该享受通用能力的待遇。能力越通用,越要特殊看管。把 Bash 当成普通工具,往往只是设计上的偷懒。

工具系统保护的不只是用户,还包括系统自己 ​

不完整的 tool_result、失序的上下文修改、无边界的并发副作用、说不清楚的中断语义——最先崩掉的往往是系统的一致性。query loop 在中断时补齐 synthetic tool_result;StreamingToolExecutor 预留 discarded、hasErrored、siblingAbortController、interruptBehavior。两边一起作用,让系统在「执行过什么、没执行完什么、为什么停了」上还能保持一条可追溯的因果链。很多约束表面上防止误操作,更深一层是防止系统自己变成一堆无法解释的状态残片。

关键洞察 ​

提炼成工程原则:模型提建议不等于拥有授权;并发要保持因果秩序;中断必须有一等语义;Bash 这类高风险工具按特例治理;工具系统保护的既是用户,也是运行时本身。

源码证据:toolOrchestration.ts 先分批再执行;toolExecution.ts 把 hooks / permission / telemetry / synthetic error 包在周围;useCanUseTool.tsx 三态授权;StreamingToolExecutor.ts 为中断、fallback、并发出错预留专门语义;BashTool/prompt.ts 与 bashPermissions.ts 对 Bash 特殊高压治理。

个人思考 ​

日常里最常见的「Ask / Allow once / Allow always」弹窗,就是三态权限被做成了产品。Esc 之后工具结果仍出现在 transcript 里,是账本闭环。下一章拆另一种常见错觉:上下文越多越好——真正有经验的系统把上下文当资源,不当仓库。

相关 ​

Copyright © 2026 openalon. All rights reserved.