Claude Code Agent Loop 研究系列(05)—— QueryEngine 主循环:状态机全景
内容提要
Claude Code主循环实为状态机,非简单while循环。核心在src/query.ts的queryLoop函数,通过7种transition reason(如next_turn、reactive_compact_retry)决定路径,10余种Terminal状态结束循环。恢复机制采用并列transition而非嵌套try,支持链式恢复并隐藏中间错误。主代理与子代理复用同一代码,以agentId区分。
延伸解读
状态机设计:错误恢复的链式处理
Claude Code 的主循环并非简单的 while 循环,而是通过 state.transition.reason 驱动的状态机。这种设计将错误恢复(如 prompt_too_long、max_output_tokens)转化为状态转换,而非嵌套的 try-catch。好处是恢复可以链式进行,例如先压缩上下文,再提升 token 上限,最后注入 continue 消息,整个过程在同一循环内完成,避免了复杂的嵌套逻辑。
错误隐藏:对 SDK 调用方透明
主循环采用“withhold”策略,即遇到可恢复的错误时,不立即向 SDK 调用方报告,而是内部尝试恢复。只有恢复失败才抛出最终错误。这避免了 SDK 消费者(如桌面端)因看到中间错误而误判循环终止。设计哲学是:loop 是恢复引擎,而非错误处理器,错误是内部信号,不是外部输出。
主代理与子代理共享代码
主代理和子代理复用同一套 queryLoop 代码,通过 toolUseContext.agentId 区分身份。这种共享代码的方式减少了重复,但需要在多处添加 if (!toolUseContext.agentId) 判断来分流主线程特有的操作(如 MemoryPrefetch、Stop hook 锁)。收益是修复一处 bug 即可惠及所有代理,但代价是代码中散布条件分支。
Q&A
Claude Code 的主循环是简单的 while 循环吗?
不是。Claude Code 的主循环是一个显式的状态机,通过 state.transition.reason 决定每次迭代的路径,而不是简单的 while (has_tool_use) 循环。
Claude Code 主循环中 state.transition.reason 有哪几种?
共有 7 种:next_turn、collapse_drain_retry、reactive_compact_retry、max_output_tokens_escalate、max_output_tokens_recovery、stop_hook_blocking、token_budget_continuation。
Claude Code 主循环有哪些终止状态?
终止状态有 10 多种,包括 completed、max_turns、aborted_tools、aborted_streaming、hook_stopped、stop_hook_prevented、blocking_limit、image_error、model_error、prompt_too_long 等。
Claude Code 如何处理错误恢复?
Claude Code 不采用嵌套 try-catch 重试,而是将恢复操作转换为 state.transition.reason,在下一轮迭代中处理。这样支持链式恢复,并且便于测试。
Claude Code 主循环如何对 SDK 调用方隐藏中间错误?
通过 withhold 策略,loop 遇到错误时先不通知 SDK 调用方,而是内部尝试恢复;只有恢复失败才报告最终错误。这样 SDK 调用方不会因为中间错误而误判循环终止。
主代理和子代理的循环代码是分开的吗?
不是。主代理和子代理复用同一套 queryLoop 代码,各自独立运行,通过 toolUseContext.agentId 区分身份,并用 if (!toolUseContext.agentId) 判断执行主线程特有的操作。
Claude Code 主循环的一次迭代包含哪些步骤?
一次迭代包括:根据 transition reason 分派、构建请求、调用 LLM 并流式消费、判断停止原因、执行工具(权限、hooks、并行调度)、更新 transition。