query.ts:真正的 AgentLoop
query.ts 是 Claude Code 的核心循环。它负责把一轮用户请求推进到模型完成回答,或者推进到下一轮工具调用。
简化后,它的逻辑是:
text
while true:
准备 messagesForQuery
处理上下文压缩
调用模型
收集 assistant messages
如果没有 tool_use:
返回最终回答
执行 tools
把 tool_result 追加到 messages
continue1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
模型调用前发生什么
Claude Code 在真正调用模型前,会对上下文做很多处理:
- 应用 tool result budget。
- 执行 snip compact。
- 执行 microcompact。
- 执行 context collapse。
- 判断是否需要 autocompact。
- 拼接 system prompt、user context、system context。
这些机制都服务于同一个目标:让发送给模型的上下文既包含必要信息,又不会超过窗口。
Streaming 响应如何进入循环
query() 通过 deps.callModel(...) 获取一个 async stream。stream 中可能出现:
- 文本内容。
- thinking block。
tool_useblock。- API 错误消息。
- request start event。
当 assistant message 里出现 tool_use,循环不会结束,而是进入工具执行阶段。
源码里有一个重要细节:它并不只依赖 stop_reason === "tool_use"。注释中明确提到这个字段并不总可靠,所以循环会在 streaming 过程中收集实际出现的 tool_use block,并用它决定是否需要 follow-up。
可以重点看这些锚点:
query():对外暴露的 async generator。queryLoop():真正的循环体。yieldMissingToolResultBlocks():当中断或异常导致工具结果缺失时,补齐错误型 tool result。runTools():执行本轮 assistant message 中收集到的工具调用。
工具结果如何触发下一轮
模型请求工具后,Claude Code 会执行工具并生成 tool_result。这个结果会被包装为用户消息,再放回 messages:
text
assistant: tool_use(Read)
user: tool_result(Read output)
assistant: 根据读取结果继续回答1
2
3
2
3
这是 Agent 能“行动后观察结果”的关键。
query.ts 复杂在哪里
Claude Code 是成熟产品,所以 query.ts 还处理:
- fallback model。
- prompt too long。
- max output tokens。
- 用户中断。
- missing tool result。
- stop hooks。
- task budget。
- compact boundary。
- tool use summary。
这些都不是第一版 Agent 必须实现的,但它们展示了真实 Agent 产品会遇到的边界问题。
小结
query.ts 的学习重点不是复刻每个分支,而是理解主循环不变量:
text
模型输出 tool_use,Agent 必须返回配对的 tool_result。
只要还有 tool_use,就继续下一轮。
没有 tool_use,才算本轮完成。1
2
3
2
3