Claude Code 源码架构深度解析(三):工具多不等于 Agent 强,Claude Code 是怎么治理 42 个工具的?

张开发
2026/4/12 5:55:54 15 分钟阅读

分享文章

Claude Code 源码架构深度解析(三):工具多不等于 Agent 强,Claude Code 是怎么治理 42 个工具的?
模型说我要执行这个命令然后呢上一篇我们看了 Claude Code 的主循环引擎1729 行的状态机、流式工具执行、Prompt 缓存经济学。主循环解决了怎么跑。但 Agent 跑起来之后最核心的能力就是调用工具——读文件、写代码、执行 shell 命令、搜索代码库。现在的问题是模型说我要用 BashTool 执行rm -rf /系统就真的直接执行吗当然不是。在 Claude Code 里从模型决定调用一个工具到工具真正被执行中间有一条14 步的治理流水线。这条流水线不是摆设它是整个系统安全性和可靠性的基石。这篇文章的核心论点Agent 系统真正难的不是给模型多少工具而是工具调用怎么管控。一、Tool 接口不是 function call 那么简单大部分人对 Agent tool use 的理解停留在 OpenAI 的 function calling{name:read_file,parameters:{path:src/main.ts}}给模型一个函数签名模型输出参数你执行函数返回结果。搞定。Claude Code 的 Tool 接口远不止于此。Tool.ts有 792 行定义了一个工具需要实现的全部契约。看核心类型定义src/Tool.tsexporttypeToolInput,Output,P{// 核心能力call(args,context,canUseTool,parentMessage,onProgress?):PromiseToolResultOutputreadonlyinputSchema:Input validateInput?(input,context):PromiseValidationResultprompt(options):Promisestring// 安全标记checkPermissions(input,context):PromisePermissionResultisReadOnly(input):booleanisDestructive?(input):booleanisConcurrencySafe(input):boolean// 可观测性backfillObservableInput?(input):voidtoAutoClassifierInput(input):unknown// 6 个以上的 render 方法 ...}核心能力方法方法作用call()执行工具inputSchemaZod schema输入校验validateInput()更精细的自定义输入校验prompt()动态生成这个工具在 prompt 里的描述安全标记方法方法作用checkPermissions()工具级别的权限检查preparePermissionMatcher()为 Hook 模式匹配做预处理isReadOnly()是否只读isDestructive()是否有破坏性isConcurrencySafe()是否可以并发执行可观测性方法方法作用backfillObservableInput()在 SDK stream 和 transcript 里补充可观测字段toAutoClassifierInput()给安全分类器的紧凑表示UI 渲染方法还有6 个以上的 render 方法分别处理工具调用的展示、进度、结果、错误、拒绝、分组等 UI 场景。一个工具不只是能执行它还需要能被校验、被授权、被监控、被展示、被分类。坦率讲我第一次看到这个接口定义的时候觉得有点重——一个工具而已需要这么多方法吗但后来想了一下你让一个操作数据库的工具不做权限检查行不行让一个执行 shell 命令的工具不做风险分类行不行显然不行。如果你只把工具当 function call最终会在权限、安全、审计这些问题上踩坑到时候再来改代价大得多。二、Fail-closed忘了配置就走最严格路径buildTool()是创建工具的工厂函数。它提供了一组默认值这些默认值的选择非常有意思。直接看源码src/Tool.ts/** * Defaults (fail-closed where it matters): * - isEnabled → true * - isConcurrencySafe → false (assume not safe) * - isReadOnly → false (assume writes) * - isDestructive → false * - checkPermissions → { behavior: allow, updatedInput } (defer to general permission system) * - toAutoClassifierInput → (skip classifier — security-relevant tools must override) * - userFacingName → name */constTOOL_DEFAULTS{isEnabled:()true,isConcurrencySafe:(_input?:unknown)false,isReadOnly:(_input?:unknown)false,isDestructive:(_input?:unknown)false,checkPermissions:(input,_ctx?)Promise.resolve({behavior:allow,updatedInput:input}),toAutoClassifierInput:(_input?:unknown),userFacingName:(_input?:unknown),}exportfunctionbuildToolDextendsAnyToolDef(def:D):BuiltToolD{return{...TOOL_DEFAULTS,userFacingName:()def.name,...def}asBuiltToolD}注释里直接写了fail-closed where it matters——这不是我的总结而是开发者自己的设计意图。关键默认值关键默认值属性默认值含义isConcurrencySafefalse默认假设不能并发isReadOnlyfalse默认假设会写入checkPermissionsallow交给通用权限系统不做特殊权限检查toAutoClassifierInput跳过分类器安全相关工具必须自己覆写为什么这很重要假设你写了一个新工具忘了声明它是否并发安全。在 fail-open 的设计下系统会默认它安全允许并发执行——如果它实际上不安全就会出数据竞争。在 fail-closed 的设计下系统默认它不安全串行执行。你可能损失一些性能但不会出数据问题。同样忘了声明isReadOnly系统默认它会写入走更严格的权限检查。你可能多弹了一次权限确认框但不会在没有用户授权的情况下执行了写操作。忘了就严格的设计避免了一个很常见的灾难某个工具忘了配置结果在没有权限检查的情况下执行了危险操作。对比其他系统很多 Agent 框架的工具注册是 fail-open 的你注册一个工具默认就能被调用没有权限检查没有并发控制。这在 demo 阶段没问题但在生产环境里是定时炸弹。三、42 个工具背后的设计哲学源码里一共有 42 个工具目录。数量不是重点重点是这 42 个工具的设计分层。我把它们按风险等级来分比按功能分更能说明问题低风险层纯信息获取文件读取、代码搜索Glob/Grep、Web 搜索。这些工具只读不写调用几乎不需要权限检查。中风险层有副作用但可控文件编辑、文件创建、Notebook 编辑。这些工具会修改本地状态但影响范围有限且可以通过 git 回滚。高风险层副作用不可控BashTool 和 PowerShellTool。Shell 命令的能力边界几乎没有上限——可以删文件、改配置、执行网络请求、安装软件。这就是为什么 BashTool 有专门的风险分类器后面会讲。元操作层改变系统自身行为AgentTool调度其他 Agent、模式切换Plan/Worktree、任务管理Task CRUD。这类工具不直接操作用户代码而是改变 Agent 系统本身的运行方式。这个分层告诉我们一件事工具的权限策略不应该是统一的。读文件和执行 shell 命令不能用同一套审批逻辑。Claude Code 把文件的读和写拆成独立工具就是为了让它们走不同的权限路径。还有一个值得注意的细节每个工具都有独立的prompt.ts通过prompt()方法动态生成自己在 system prompt 里的描述。同一个工具在不同权限模式下给模型看的说明文字是不一样的。这意味着权限控制不只发生在执行阶段在模型决策阶段就已经开始了——通过 prompt 影响模型的调用倾向。四、14 步执行流水线从模型想调到真正执行这是这篇文章最核心的部分。toolExecution.ts有 1745 行实现了一条完整的工具执行流水线。当模型在响应中输出了一个 tool_use block不是直接调用对应工具的call()方法。而是要走完以下 14 步Step 1找工具通过名字或别名alias找到对应的 Tool 对象。找不到就直接返回错误。Step 2解析 MCP 元数据如果是 MCP 工具提取 server 信息。Step 3Zod schema 校验用工具的inputSchema做第一道校验。模型输出的参数不符合 schema直接挡回去。这一步拦住的是模型最常见的错误参数类型不对、缺少必需字段、多了不存在的字段。Step 4validateInput()工具自己的细粒度校验。比如 FileEdit 会检查路径是否合法、是否在允许的目录内。Step 5Speculative Classifier如果是 BashTool启动一个预测性分类器在权限决策之前就开始分析命令的风险等级。这个分类器可以在 Hook 执行的同时并行运行不阻塞主流程。等到需要做权限决策的时候分类结果可能已经出来了。为什么要这样设计因为分析一个 shell 命令的风险等级可能需要一定时间可能涉及模式匹配、危险命令数据库查询。如果串行执行用户要等更久。提前异步启动可以减少用户等待权限弹窗的时间。Step 6PreToolUse Hooks运行所有注册的 pre-hook。Hook 不只是记日志它能返回permissionBehaviorallow / ask / deny返回updatedInput修改工具输入返回blockingError直接阻断返回preventContinuation阻止后续流程返回additionalContexts补充上下文信息Step 7解析 Hook 权限结果Hook 可能返回 allow、ask、deny也可能修改输入。这一步解析所有 Hook 的结果。Step 8走权限决策综合 Hook 结果、规则配置、用户交互做出最终的允许/拒绝决策。Step 9修正输入如果权限决策或 Hook 修改了输入用修改后的版本。Step 10执行 tool.call()终于到了真正执行的时候。Step 11记录 analytics / tracing / OTel遥测和可观测性。每次工具调用都有完整记录。Step 12PostToolUse Hooks成功后的 post-hook。可以修改 MCP 工具的输出、追加消息、注入上下文。Step 13处理结果结构化输出、tool_result block 构建。Step 14PostToolUseFailure Hooks如果失败了运行失败 hook。看完这 14 步你应该能理解为什么toolExecution.ts有 1745 行了。前 9 步全部是在防止坏事发生只有第 10 步是真正干活后 4 步是记录和善后。真正的工具执行系统不是调函数而是一条治理流水线。五、Speculative ClassifierBashTool 的特殊待遇在所有 42 个工具里BashTool 得到了最特殊的对待。原因很简单shell 命令是最危险的能力。一个rm -rf /可以删掉整个文件系统。一个curl xxx | bash可以执行任意远程代码。一个git push --force可以覆盖远程分支的历史。Claude Code 给 BashTool 配了一个Speculative Classifier预测性分类器在正式权限检查之前系统就开始分析这条命令的风险等级这个分类器和 Pre-hook 是并行执行的等到权限决策需要分类结果的时候分类可能已经完成了分类结果只是辅助信息不能绕过 Hook 和权限系统这种提前开始异步计算的设计模式在高性能系统里很常见比如 CPU 的指令预取、数据库的 prefetch但在 Agent 系统里几乎没见过有人做。权限分类体系权限目录下有一整套分类器组件作用bashClassifierBash 命令风险分类yoloClassifierauto 模式下的分类器dangerousPatterns危险命令模式匹配库shellRuleMatchingShell 命令的规则匹配引擎pathValidation文件路径校验27 个文件组成了一套完整的权限模型。这不是一个简单的黑白名单而是一个支持多模式default/plan/auto、多规则allow/deny/ask、多层级的决策系统。六、与其他 Agent 系统的对比为了让你更直观地感受 Claude Code 工具系统的成熟度我拿几个常见的 Agent 框架做对比典型开源 Coding Agent 的工具系统工具注册 name description function 执行流程 模型输出 tool_use → 直接调用函数 → 返回结果 权限检查无 输入校验基础类型检查 并发控制无 失败处理try-catch 打日志 可观测性无Claude Code 的工具系统工具注册 name description inputSchema(Zod) validateInput checkPermissions isReadOnly isDestructive isConcurrencySafe prompt(动态) 6 render 方法 执行流程 14 步 pipeline (校验→分类→Hook→权限→执行→记录→后处理) 权限检查三层防护 (classifier hook permission) 输入校验schema 自定义校验 并发控制基于 isConcurrencySafe 标记 失败处理PostToolUseFailure hooks 错误恢复 可观测性analytics tracing OTel差距不在于工具多不多而在于治理深度。有一点我想特别强调能力和治理是成正比的。给模型一个只能读文件的工具几乎不需要治理。但给它一个能执行任意 shell 命令的工具你就需要输入校验、风险分类、权限检查、用户确认、执行记录、失败回滚这一整套。如果你只增加能力不增加治理你在构建的不是产品而是风险。Claude Code 的 14 步 pipeline 加上 OTel tracing 和 transcript recording给出了一个答案每次工具调用都是一个有完整记录的事件谁调的、什么参数、什么权限、什么结果全部可查。企业客户不会接受一个黑盒这套可审计性就是让它不再是黑盒。下一篇预告工具系统解决了模型怎么安全地执行操作的问题。但有一个更深层的问题你让模型自己验收自己的工作成果它会给自己打几分答案几乎总是还不错。这不是模型在撒谎而是同一个角色既做事又验收天然存在利益冲突。Claude Code 的做法是不相信任何单一 Agent 的自我判断。探索的 Agent 不能写文件验证的 Agent 专门找茬规划的 Agent 不能动手。再加上一套三层安全防护——互相配合但互不绕过。下一篇聊聊 Claude Code 为什么不让一个 Agent 干所有事。Claude Code 源码架构深度解析四从 Explore 到 VerificationClaude Code 为什么不让一个 Agent 干所有事本系列共 5 篇源码来自 Anthropic 泄露的 npm 包中的 source map 还原。内容为个人理解与工程分析不代表 Anthropic 官方观点。

更多文章