乐于分享
好东西不私藏

源码读到 query.ts 后,Claude Code 的运行逻辑突然清晰了

源码读到 query.ts 后,Claude Code 的运行逻辑突然清晰了

看 Claude Code 的模型执行引擎时,我最大的感受是:
它不是“调用一次模型”这么简单。
这一块可以理解成 Claude Code 真正“跑起来”的地方。
它要做的不是简单把用户问题丢给模型,而是把:
当前状态
消息历史
工具能力
一起接上,推进成一轮又一轮可以持续执行的 agent 流程。
放到整套 Claude Code 架构里,它的位置大概是这样:
入口与运行模式:程序怎么被拉起来
状态与数据模型:系统怎么记住当前世界
模型执行引擎:系统怎么真正开始干活
所以我觉得,这一块基本就是 Claude Code 的运行核心。
这部分我会按 4 条线来看。
主循环入口
重点看:query.ts line 219
一开始我也容易把 query(…) 理解成“发起一次模型请求”的函数。
但它其实不是普通函数,而是一个异步生成器。
它会一边执行,一边不断往外产出:
消息
事件
工具结果
真正负责循环推进的,是 queryLoop(…)。
也就是说,这里已经不是普通问答逻辑了,而是 agent 主循环的入口。
API 装配层
重点看:claude.ts line 1017
这一层也不是简单包一层 API。
它其实在做一件很重要的事:
把 Claude Code 内部的一轮查询,整理成模型 API 能接受的合法请求。
里面包括:
工具 schema 的选择和裁剪
消息正规化
tool_use / tool_result 的配对修复
模型能力适配
流式请求
非流式回退
所以 claude.ts 不是薄封装,而是请求装配层。
它负责把内部复杂状态,翻译成模型真正能理解、能处理的一轮请求。
模型阶段和工具阶段怎么切换
关键连接点在:
query.ts line 833
toolOrchestration.ts line 19
这里最重要的是记住一句话:
tool_use 就是分界线。
只要助手消息里出现 tool_use,主循环就不再只是“等模型回答”。
它会从模型采样阶段,切到工具编排阶段。
工具执行完之后,结果也不会停在工具层。
它会被整理成新的消息历史,再回到下一轮模型请求里。
所以 Claude Code 不是单轮问答,而是一个不断循环的闭环:
模型
工具
模型
这也是它更像 agent 系统,而不是普通聊天壳的原因。
停止、重试、压缩和恢复
这一块也很关键。
Claude Code 的主循环不是一条直线,而是一个带恢复能力的状态机。
在 query.ts line 1062 之后,可以看到几类典型控制逻辑:
上下文太长,会先尝试 collapse / reactive compact
输出被截断,会尝试提高上限,或者注入续写消息
stop hooks 和 token budget 也可能触发系统继续下一轮
真的恢复不了,才会停止
所以它的策略不是“出错就结束”。
更准确地说,它是在尽量把系统推进到还能继续执行的下一个状态。
最后,可以把这一块压缩成一条主链路:
用户消息
query.ts 判断这一轮怎么跑
claude.ts 组装并发起模型请求
流式接收助手消息
如果出现 tool_use,切到工具编排层
工具结果回流成新的消息历史
主循环决定:继续、恢复、压缩,还是停止
这就是 Claude Code 的 agent 主循环。
如果只记 6 件事,我会记这 6 条:
query.ts line 219 是主执行入口,不是普通模型调用函数。
claude.ts line 1017 是请求装配层,不是薄薄包一层 API。
tool_use 是从“回答模式”切到“agent 执行模式”的分界线。
toolOrchestration.ts line 19 说明工具执行是编排过程,不是随手调用。
工具结果必须回到消息历史里,系统才有下一轮可继续的上下文。
整个执行引擎本质上是:能继续就继续,能恢复就恢复,修不好才停止。
如果你也在读 Claude Code 源码,我建议先抓住这条线:
query.ts 管主循环,
claude.ts 管请求装配,
toolOrchestration.ts 管工具执行。
这条线理清楚之后,Claude Code 为什么能从“聊天”变成“agent 执行系统”,就会清楚很多。
你觉得 Claude Code 里最难理解的是主循环,还是工具调用这一层?