Claude Code 源码导读02|主循环如何工作
Digital Strategy Review | 2026
Claude Code 源码导读 02|从 `query.ts` 到 `toolOrchestration.ts`,看主循环如何真正跑起来
文 / 果叔 · 阅读时间 / 8 Min

写在前面
这一篇只回答一个问题:系统装好之后,到底是怎么持续推进任务的。把这一层看懂,你会更容易理解 Claude Code 为什么不像一个套着函数调用的聊天壳,而更像一个有调度能力的执行内核。
01
从 query.ts、tools.ts 到 toolOrchestration.ts:主循环是怎么工作的
如果第一篇讲的是“系统怎么被装起来”,这一篇讲的就是:装起来之后,它到底怎么跑。
Claude Code 的真正核心不是 UI,也不是命令系统,而是一个非常完整的 agentic execution loop。 这个 loop 的中心文件就是:
• src/query.ts
• src/tools.ts
• src/services/tools/toolOrchestration.ts
理解这三份文件之后,你就能看明白一件事:
Claude Code 不是“模型输出一段文字,偶尔调几个函数”,而是把模型采样、工具执行、上下文压缩、错误恢复、预算控制整合成了一条持续推进的工作流。
02
一、tools.ts 先定义了模型能看到的“行动世界”
在 Claude Code 里,工具不只是技术实现,更是模型的行动边界。
src/tools.ts 的核心职责有两个:
01 枚举系统拥有的工具总表。
02 根据模式、环境、feature gate、权限上下文裁剪出当前会话可见的工具集。
1. getAllBaseTools() 的意义
这一层列出了系统潜在的工具版图,包括:
• 文件读取/编辑/写入
• Bash / PowerShell
• AgentTool
• SkillTool
• Web / LSP / MCP 资源工具
• 任务工具
• Plan / Worktree 相关工具
• 各类 feature gate 下的可选工具
重点不是“工具很多”,而是:
Claude Code 把工具看成统一的执行协议,而不是各处散落的 helper function。
2. 为什么工具列表必须动态构造
工具不是固定暴露的。它会受这些因素影响:
• 当前 user type
• 是否启用某个 feature
• 当前是否 worktree mode
• 当前是否支持 swarms
• 当前是否开启 LSP / browser / workflow 等能力
• 当前 permission context 是否已 blanket deny 某些工具
于是模型看到的工具集合,实际上是一份会话时态的能力表面。
这件事非常关键。因为对大模型来说,“看不见”很多时候比“看得见但调用会被拒绝”更干净。
03
二、query.ts 才是 Claude Code 的真正心脏
很多人会下意识把聊天界面理解为核心,但在 Claude Code 里,真正的主循环在 query.ts。
export async function* query(...) 的含义很明确: 它不是一次请求,而是一个会不断产生流式事件、工具结果、消息、边界消息的异步生成器。
1. 这不是一轮问答,而是一轮工作
query.ts 管理的是一个完整 turn 的推进过程,大体上是:
01 拿到当前消息与上下文
02 构造 query config
03 发起模型调用
04 处理流式返回
05 识别 tool_use
06 执行工具
07 注入 tool_result
08 必要时继续下一轮
09 在上下文过长、输出受限、工具中断等情况下进行恢复
这意味着 Claude Code 的“一个回合”本质上更像一个小型工作流,而不是普通 chat completion。
2. 它显式维护 loop state
源码里把 mutable state 专门收拢成一个结构,里面包含:
• messages
• toolUseContext
• autoCompactTracking
• maxOutputTokensRecoveryCount
• hasAttemptedReactiveCompact
• pendingToolUseSummary
• stopHookActive
• turnCount
• transition
这背后的工程思想很成熟: 既然一个 turn 可能跨越多次继续、压缩、恢复、tool round,就不要让状态散落在几十个局部变量里。
3. 它从一开始就把“超长上下文”当成常态而不是异常
query.ts 里和 compact 相关的逻辑非常多:
• microcompact
• autocompact
• reactive compact
• snip compact
• post compact messages
• compact boundary messages
这说明 Claude Code 并不假设“任务会在一次上下文窗口内自然结束”。 它相反是在认真处理这样一种现实:
真正的 coding task 会跨很多轮推理、很多次工具执行、很多次上下文变形。
这是它和简单聊天循环最大的差别之一。
把模型当成一个会持续推进的执行体,而不是一次性文本生成器,整套系统的气质就完全变了。
04
三、为什么 query.ts 看起来这么复杂
因为它把四件本该分散在不同层的事合在一起了:
01 模型交互
02 工具驱动
03 上下文管理
04 恢复机制
很多系统把这四块分散实现,结果就是很难保证语义一致。Claude Code 选择把它们放在一条主循环里,于是复杂度上升,但一致性更强。
你在源码里能看到很多典型例子:
• tool_use 并不总是只靠 stop_reason 判断
• streaming tool executor 要避免 orphan tool_results
• 压缩之后还要维护 task budget 语义
• 遇到 max_output_tokens 需要决定是重试、压缩还是终止
这说明作者不是在写“happy path 主循环”,而是在写能长期跑复杂任务的主循环。
05
四、工具编排器:toolOrchestration.ts 解决了一个非常现实的问题
主循环一旦支持工具调用,就会遇到一个老问题:
• 工具全串行,慢
• 工具全并行,危险
Claude Code 的解法是 partitionToolCalls(...)。
1. 它怎么工作
大致策略是:
• 逐个看本轮 tool_use
• 用工具自己的 schema 去解析输入
• 调用该工具的 isConcurrencySafe(...)
• 把连续可并发的工具归成一批
• 其余工具单独串行执行
然后:
• 并发安全批次走 runToolsConcurrently(...)
• 非安全批次走 runToolsSerially(...)
2. 这件事为什么重要
因为在 coding 场景里,最常见的一批工具其实是:
• Glob
• Grep
• FileRead
• 某些 MCP read-only tool
这些操作天然应该并发。 如果模型一次要读 8 个文件、搜 5 个 pattern,还一条一条串行跑,交互体验会很差。
但与此同时:
• 文件编辑
• shell 写操作
• 依赖上下文更新的工具
又绝不能盲并发。
Claude Code 在这里做得非常工程化: 让运行时理解工具的并发语义,而不是把这个负担甩给模型。
真正决定速度和稳定性的,常常不是线程数,而是系统有没有理解不同工具调用的语义风险。
06
五、query.ts 和工具系统是怎么接起来的
主循环真正精彩的地方在于,工具不是外挂,而是 query loop 的内生步骤。
整体关系可以理解成这样:
01 模型输出 assistant message
02 其中包含若干 tool_use
03 query.ts 提取这些 blocks
04 交给 runTools(...)
05 编排器决定并发/串行策略
06 工具执行产出 message / contextModifier
07 结果回到 query.ts
08 新消息被并入 messages,再继续采样
这条链路的好处很大:
• tool progress 可以自然进入消息流
• tool result 可以被压缩、裁剪、持久化
• permission / hook / telemetry 可以统一接在执行路径上
• 出错时可以在主循环层做恢复
所以 Claude Code 不是“模型 + 工具 SDK”,而是模型驱动的状态机。
07
六、这里最值得注意的三个工程思想
1. 让工具语义进入运行时,而不是只停留在 prompt
很多系统会在 prompt 里告诉模型“可以并行做读操作”。 Claude Code 更进一步,直接让工具实现自己声明并发安全性,然后由 orchestration 层执行。
这是把“提示建议”升级成“系统保障”。
2. 让压缩成为主路径,而不是补救措施
query.ts 几乎把 compact 当作日常机制,而不是“上下文炸了以后再说”。 这说明系统一开始就是按长工作流在设计。
3. 让恢复路径和正常路径共用同一主循环
max output token、媒体错误、reactive compact、tool interruption,这些恢复路径都没有被写成独立旁路,而是努力回到同一套循环状态上。
这会增加实现复杂度,但长期维护更稳。
08
七、一张图看懂主循环与工具编排

09
八、这三份文件合起来定义了 Claude Code 的“执行内核”
如果你只看这三份文件,我会把它们的分工概括成这样:
• tools.ts:定义当前世界里有哪些动作是可能的
• toolOrchestration.ts:决定这些动作本轮怎么安全而高效地执行
• query.ts:把模型、工具、上下文、恢复机制串成一个持续推进的主循环
这正是后面 Agent runtime 可以建立起来的底座。 因为一个 agent 本质上无非就是:在某个专属上下文里,跑同一套 query loop,拿同一套工具系统做事。
下一篇就进入这个问题本身:AgentTool、runAgent、LocalAgentTask、swarm 到底如何把 agent 变成真正的 runtime 实体。
夜雨聆风