@spandan_madan: https://x.com/spandan_madan/status/2067320100911493454
摘要
对Claude Code工具架构的详细技术分析,涵盖工具接口、注册表、调度管道和并发调度器,这些使AI能够执行43+种工具,如文件读取、shell命令和网络搜索。
查看缓存全文
缓存时间: 2026/06/18 12:14
Claude Code 的工具架构
深入解析 Claude Code 如何发现、调度和执行工具
引言
在之前的一篇文章中,我们根据 Anthropic Harness 的泄露源码对其记忆系统进行了逆向工程。结果发现,记忆系统出奇地简单——仅仅是 Markdown 文件、Frontmatter 和提示工程。而工具系统则恰恰相反。它是整个代码库中工程化程度最高的子系统:43 个以上的工具、一个流式执行管线、一个层级权限系统、一个钩子框架以及一个并发调度器——所有这些组件被连接在一起,将一个无状态的语言模型转变为能够读取文件、运行 Shell 命令、搜索网络并生成子代理的实体。
本文将从工具的生命周期出发,逐步讲解:工具如何定义、模型的工具调用如何被分发、以及结果如何流回对话。整体上,该系统由四个层级组成:一个每个工具都必须实现的工具接口,一个汇总工具池的注册表,一个负责验证、权限检查和执行每次调用的调度管线,以及一个决定哪些任务并行运行的并发调度器。
整体架构速览
工具接口
Claude Code 中的每个工具都实现了相同的接口,定义在 Tool.ts 中。该类型是泛型的,包含三个参数:Input(Zod 模式)、Output(结果类型)和 P(进度数据)。实际上,一个工具是一个包含大约 30 个方法的对象,但只有少数几个对理解系统至关重要。核心结构如下:
type Tool = {
name: string
inputSchema: ZodType // 用于输入验证的 Zod 模式
call(input, context, canUseTool, parentMessage, onProgress): Promise
// 行为声明
isConcurrencySafe(input): boolean // 能否并行运行?
isReadOnly(input): boolean // 是否只读操作?
isDestructive(input): boolean // 是否破坏性操作?
// 权限与验证
checkPermissions(input, context): Promise
validateInput(input, context): Promise
// API 集成
description(input, options): Promise
prompt(options): Promise // 该工具的系统提示文本
mapToolResultToToolResultBlockParam(result, toolUseId): ToolResultBlockParam
// UI 渲染(React)
renderToolUseMessage(input, options): ReactNode
renderToolResultMessage(content, ...): ReactNode
}
没有工具是从头开始实现上述所有方法的。一个名为 buildTool() 的工厂函数会填充安全的默认值:
默认值刻意保守。一个忘记声明并发安全性的工具作者,会得到串行执行的结果;一个忘记实现权限检查的工具作者,会使用默认的权限流程。系统默认保持封闭安全。
ToolResult 类型值得注意:
type ToolResult = {
data: T // 实际输出
newMessages?: Message[] // 可选的后续消息
contextModifier?: (ctx) => ToolUseContext // 为下一个工具修改上下文
mcpMeta?: { ... } // MCP 协议元数据
}
contextModifier 很重要——它允许一个工具更改同一轮次中后续工具的执行上下文。这就是像 EnterWorktree 这样的工具如何改变后续所有工具的工作目录。关键在于,上下文修改器只允许用于非并发安全的工具。如果一个工具并行运行,它不能修改共享状态。
工具注册表
所有工具都在一个单独的函数 getAllBaseTools()(位于 tools.ts)中注册。该函数返回一个扁平数组。有些工具始终存在,而另一些则受功能标志、环境变量或平台检查的限制。
始终可用的工具(16 个)
(列表内容省略,原文为列表)
受功能标志控制的工具(约 27 个)
其余工具按条件包含。部分受环境变量 USER_TYPE=ant(用于 Anthropic 内部工具,如 config 和 tungsten)限制。部分受 Statsig 功能标志(web_browser、sleep、monitor)限制。部分是平台特定(Windows 上的 powershell)。部分受复合条件限制——repl 工具需要同时满足 USER_TYPE=ant 和 REPL 功能标志。
受功能标志控制的工具完整列表
- 仅 Ant:config、tungsten、suggest_background_pr、repl(还需要 REPL 标志)
- 功能标志:web_browser、web_search、sleep、monitor、overflow_test、ctx_inspect、terminal_capture、list_peers、workflow、snip
- Agent 触发器:cron_create、cron_delete、cron_list、remote_trigger
- Kairos(主动型 agent):sleep、send_user_file、push_notification、subscribe_pr
- 多 agent 群组:team_create、team_delete、send_message
- Todo v2:task_create、task_get、task_update、task_list
- 环境:lsp(ENABLE_LSP_TOOL)、enter_worktree / exit_worktree(worktree 模式)、powershell(Windows)
- 工具发现:tool_search(当工具池较大时)
- 仅测试:testing_permission(NODE_ENV=test)
MCP 工具
除了内置工具,Claude Code 还支持 模型上下文协议(MCP) 服务器——这些外部进程通过标准化协议暴露自己的工具。MCP 工具在运行时从连接的服务器动态注册,并包装在相同的 Tool 接口中。从调度管线的角度来看,MCP 工具与内置工具没有区别。每个 MCP 工具都携带关于其原始服务器的元数据(mcpInfo: { serverName, toolName }),用于权限规则、错误处理和身份验证。当 MCP 工具发生身份验证错误时,系统会自动将服务器状态更新为 needs-auth,并向用户提示问题。
工具池组装
三个函数组装最终的工具集:
- getAllBaseTools()——返回 43 个以上内置工具的原始列表,已应用功能标志过滤
- getTools(permissionContext)——根据拒绝规则和 isEnabled() 进行过滤
- assembleToolPool(permissionContext, mcpTools)——合并内置工具和 MCP 工具
assembleToolPool() 中的合并策略是精密的:内置工具优先,因此命名冲突时内置工具胜出。每个分区内按字母排序,保持顺序跨会话稳定,这对提示缓存很重要——工具数组是 API 请求的一部分,重新排序会破坏缓存。
// 按字母排序每个分区,连接,去重
const byName = (a, b) => a.name.localeCompare(b.name)
return uniqBy(
[...builtInTools].sort(byName).concat(allowedMcpTools.sort(byName)),
'name',
)
API 序列化
在工具到达 Claude API 之前,toolToAPISchema() 会将每个工具的 Zod 模式转换为 Anthropic API 的 JSON Schema 格式。
调度管线
当 Claude 响应时,其消息可能包含 tool_use 块——对调用工具的结构化请求。调度管线通过七个阶段处理这些块。每个工具调用都会按顺序经过每个阶段。
第一阶段:提取
在主查询循环(query.ts)中,tool_use 块从助手的消息中被过滤出来:
const msgToolUseBlocks = message.message.content.filter(
content => content.type === 'tool_use',
) as ToolUseBlock[]
每个块包含 name、input 对象和唯一的 id。这个 id 至关重要——工具结果在返回给 API 时必须引用相同的 id,否则对话会中断。
第二阶段:输入验证
工具的 Zod 模式使用 safeParse() 验证原始输入——这是一种不抛出异常的方法,返回有效数据或结构化错误。如果验证失败,模型会收到一个格式化的错误消息(包含模式提示),并且该工具的执行会停止。无效输入不会运行任何代码。
const parsedInput = tool.inputSchema.safeParse(input)
if (!parsedInput.success) {
let errorContent = formatZodValidationError(tool.name, parsedInput.error)
// 向模型返回错误,跳过执行
}
Zod 验证之后,一些工具会运行第二层 validateInput() 检查,用于无法在模式中表达的语义验证——例如,验证文件路径是绝对路径而非相对路径。
第三阶段:预工具钩子
在权限检查之前,用户配置的钩子开始执行。这些是外部 Shell 命令或脚本,在工具调用时触发。预工具钩子可以:
- 允许工具调用,绕过交互式权限提示
- 拒绝工具调用,直接停止
- 修改输入后再执行
- 阻止执行并附带错误消息
- 提供额外上下文给用户
一个关键的不变量:钩子的 allow 并不绕过来自设置文件的拒绝规则。源码中有明确的注释说明:“钩子 ‘allow’ 并不会绕过 settings.json 中的 deny/ask 规则。”其意图是钩子可以开门,但不能覆盖锁。
第四阶段:权限检查
权限系统是管线中最复杂的部分。它按顺序通过多个层次解析:
- 拒绝规则——首先检查。如果任何拒绝规则匹配,执行立即停止。拒绝规则是最终的,不能被任何其他层覆盖。
- 询问规则——如果匹配,会提示用户审批(除非沙箱自动允许适用于 Bash)。
- 工具特定权限——工具自身的 checkPermissions() 方法运行。例如,BashTool 会解析命令以检查子命令级别的规则。
- 安全检查——针对敏感路径(.git/、.claude/、Shell 配置)的硬编码保护。这些是无法绕过的——即使在完全绕过模式下,也需要交互式审批。
- 权限模式——用户配置的模式决定了默认行为。
- 允许规则——最后检查。如果允许规则匹配且没有触发拒绝/询问规则,则工具继续执行。
权限模式
default——始终对“ask”决策提示用户。acceptEdits——自动允许安全的文件操作(读取、编辑),其他操作则提示。bypassPermissions——除拒绝规则和安全检查外,自动允许所有操作。plan——先批准一个计划,然后按之前的模式执行。auto——使用 AI 分类器决定是允许还是提示。dontAsk——将所有“ask”决策转换为“deny”——从不提示,直接拒绝。
权限规则来自多个来源,按优先级顺序解析:policySettings、localSettings、projectSettings、userSettings、flagSettings、cliArg、command、session。这使得组织策略可以覆盖用户偏好,CLI 参数可以覆盖两者。
第五阶段:执行
如果权限授予,工具会调用 call() 方法:
const result = await tool.call(
callInput,
{ ...toolUseContext, toolUseId: toolUseID },
canUseTool,
assistantMessage,
progress => onToolProgress({ toolUseID: progress.toolUseID, data: progress.data })
)
五个参数:验证后的输入、执行上下文(工作目录、中止控制器、应用状态)、权限回调(用于工具在运行过程中需要额外权限的情况)、父助手消息,以及用于实时更新的进度回调。执行时长会被全局跟踪。
一个细微之处:传递给 call() 的输入是模型的原始输入,而不是经过钩子和权限回填后的版本。这样可以保持对话记录的一致性——对话中记录的工具调用与模型生成的内容完全匹配。
第六阶段:后工具钩子
执行完成后,后工具钩子触发。它们可以修改 MCP 工具的输出、提供额外上下文,或阻止对话继续。还有一个独立的 PostToolUseFailure 钩子,仅在出错时触发,允许外部系统记录失败或建议补救措施。
第七阶段:结果映射
每个工具实现 mapToolResultToToolResultBlockParam(),将其输出转换为 Anthropic API 的 ToolResultBlockParam 格式——一个包含 tool_use_id 引用以及字符串或结构化内容的 tool_result 块。
如果结果超过大小阈值,它会被持久化到磁盘 sessionDir/tool-results/{toolUseId}.txt,并向 API 发送一个预览加文件引用,而不是直接发送完整内容。这样可以防止大型输出(如读取一万行文件、命令输出冗长)膨胀对话上下文。
并发调度器
当模型在一次消息中发出多个工具调用时,它们不会同时全部运行。调度器根据并发安全性将它们划分为批次。算法很简单:按顺序遍历工具调用,对每一个调用检查 isConcurrencySafe(input)。如果安全且前一批次也安全,则将其加入当前批次;否则,开始一个新批次。
// 简化自 toolOrchestration.ts
for (const toolUse of toolUseMessages) {
const isSafe = tool.isConcurrencySafe(parsedInput)
if (isSafe && lastBatch.isConcurrencySafe) {
lastBatch.blocks.push(toolUse) // 合并到并发批次
} else {
batches.push({ isConcurrencySafe: isSafe, blocks: [toolUse] })
}
}
安全的批次并发运行(上限为 10,可通过 CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY 配置)。不安全的批次串行运行,一次一个工具。上下文修改器仅在批次之间应用,而非批次内部。
在实际使用中,这意味着像“读取这 5 个文件”这样的消息会生成一个并发批次,而“读取这个文件,然后编辑它”会生成两个串行批次。模型甚至可以在一次轮次中同时触发两种模式——连续的只读调用会被批量处理,而第一个写操作会中断批次。
流式执行器
还有第二条执行路径:StreamingToolExecutor。当启用流式传输时,工具会在模型仍在生成响应时就开始执行。每当流中完成一个 tool_use 块,它立即被加入执行队列,而无需等待完整响应。
流式执行器使用相同的并发规则,但增加了一个行为:Bash 错误级联。如果 Bash 命令失败而同级工具仍在并行运行,执行器会中止所有同级工具。其理由是一个失败的 Bash 命令很可能使其他工具正在操作的上下文失效——继续运行会浪费时间并可能导致令人困惑的错误。
if (isErrorResult && tool.block.name === BASH_TOOL_NAME) {
this.hasErrored = true
this.siblingAbortController.abort('sibling_error')
}
一个实际示例
为了让这个过程更具体,让我们追踪当模型决定读取一个文件时会发生什么。
模型发出:
{
"type": "tool_use",
"id": "toolu_01XYZ",
"name": "read",
"input": {
"file_path": "/src/index.ts"
}
}
- 提取:query.ts 从助手消息内容中过滤出该块。
- 工具查找:findToolByName(tools, “read”) 找到 FileReadTool。
- 输入验证:Zod 根据 z.object({ file_path: z.string(), offset: z.number().optional(), limit: z.number().optional(), pages: z.string().optional() }) 解析 { file_path: “/src/index.ts” }。通过。
- 预工具钩子:任何用户配置的钩子触发。没有修改输入。
- 权限检查:FileReadTool 的 checkPermissions() 调用 checkReadPermissionForTool()。在大多数权限模式下,读取工具通常是允许的。
- 执行:FileReadTool.call() 读取文件,应用行号(cat -n 格式),并特殊处理 PDF/图像/笔记本。
- 结果映射:文件内容变成一个引用 “toolu_01XYZ” 的 tool_result 块。
- 返回:结果作为用户消息附加到对话中,并在下一次 API 调用时发送。
由于 FileReadTool 声明了 isConcurrencySafe: () => true 和 isReadOnly: () => true,如果模型在同一个消息中发出了五个读取调用,这五个调用会并行执行。
总结
工具系统是 Claude Code 的执行骨干。它将模型的意图——以结构化的 tool_use 块表示——转化为你机器上的真实操作,在每一步都进行验证、权限控制和并发管理。其设计是分层的:一个保守的 buildTool() 工厂确保安全默认值,一个受功能标志控制的注册表控制可用性,一个七阶段的调度管线对每次调用进行验证和权限检查。
相似文章
@Suryanshti777: https://x.com/Suryanshti777/status/2056764492093194257
九个强大插件和工具的全面概述,可将 Claude Code 转变为自主工程系统,实现更快的调试、代码库搜索、UI 生成、数据库查询和终端访问。
@akshay_pachaar:Claude Code 架构图谱。Claude Code 是现有最强大的智能体驱动框架之一,它不仅仅是……
详细解析 Claude Code 的六层架构,揭示其如何作为一个复杂的智能体驱动框架运作,包含输入、知识、执行、集成、多智能体和可观测性层,而不仅仅是 AI 模型本身。
一篇详细描述Anthropic自身工程师使用Claude Code时的确切配置和工作流程的文章,包括并行实例、CLAUDE.md模式、写作者/审阅者分离、技能文件夹、插件、钩子和批量操作。
一篇详细介绍Anthropic工程师在使用Claude Code时的具体配置和工作流程的文章,涵盖并行实例、CLAUDE.md模式、写作者/审阅者分离、技能文件夹、插件、钩子和批量操作。
@Tabbu_ai: https://x.com/Tabbu_ai/status/2059217417096843296
一篇深度解析文章,揭示Anthropic的Claude Code不仅仅是另一个AI编码助手,而是一个在终端中运行的自主软件工程师。
@akshay_pachaar: Claude Code 不是一款编码工具。(它是一个可编程的开发环境)工程师打开它,输入提示词,然后让它编…
Claude Code 被定位为可编程的开发环境,而非简单的编码工具,具备12项功能,包括持久记忆(CLAUDE.md)、行为规则、可复用技能、事件钩子、斜杠命令、插件、MCP 连接、计划模式、权限、子代理、语音模式和回滚检查点。