04. 查询循环¶
查询循环(query loop)是 Claude Code 的主循环。它不是简单地“请求模型一次并返回”,而是在同一轮用户任务内反复执行四件事:准备本次采样上下文,调用模型并消费流式事件,执行模型请求的工具,把工具结果回填成下一次采样的输入。只要模型继续请求工具,循环就继续;当模型不再请求工具,并且停止钩子、预算和恢复逻辑都允许结束时,这一轮才完成。
开发任务需要连续观察、连续决策和连续验证。查询循环把模型的一次次短采样组织成用户视角的一次长任务:工具输出进入历史,下一次采样读取这些事实,权限拒绝和中断也以结构化结果回到同一条任务链路。
每次循环开始时,本地运行时会先整理即将发送给模型的消息。这里的“整理”不是单纯拼数组。它会从当前历史中找到压缩边界之后仍然有效的消息,处理过大的工具结果,必要时做微压缩(micro-compact,局部压缩过长内容)或上下文折叠,检查是否接近上下文窗口,准备动态工具、技能发现结果和记忆预取。这个阶段还会把系统上下文拼进完整系统提示,并把用户上下文作为特殊用户侧上下文插入请求。模型看到的不是转录记录原样,而是一次重新计算后的采样输入。
随后进入模型采样。本地运行时发出流式请求,携带消息、系统提示、推理配置、工具结构说明、MCP 状态、智能体定义、预算和备用模型信息。模型返回的流式事件会被转换成内部助手消息。文本块可以立即展示;推理内容和签名会按模型协议累积;工具调用的输入 JSON 会随着增量片段拼接,直到内容块结束才形成完整工具调用。流式输出先成为消息,消息中出现工具意图后才进入本地工具系统。
当一次采样产出工具调用,循环会把这一批工具调用交给工具运行时。并发安全的工具可以批量并行执行,不安全的工具会串行执行。执行前要做输入校验、钩子、权限检查和沙箱判断;执行后要把输出截断、持久化、渲染为工具结果,并更新已读文件状态、文件历史、任务状态或其他上下文修改。工具结果会被追加为新的用户消息,因为从模型视角看,这是“外部世界对它刚才动作的回应”。
下一次循环会带着这些工具结果再采样。模型可以根据命令输出继续读取文件、修改代码、运行测试,或给出最终回答。这个后续采样机制让模型在每次本地动作之后重新观察结果,而不是在第一轮里预判完整任务。
停止条件比“没有工具调用”更复杂。模型不再请求工具时,本地运行时还要检查停止钩子是否要求继续、是否存在 token 预算续写、是否需要恢复最大输出 token、是否要响应式压缩、是否有 API 错误可以通过备用模型或上下文压缩恢复。如果停止钩子返回阻塞结果,循环会把钩子结果作为新的上下文继续采样,而不是直接结束。只有这些检查都通过,查询引擎才把最后的助手输出视为成功结果。
中断也在循环内处理。用户中断或外部取消会触发中断控制器。若中断发生在模型流式返回期间,本地运行时会给未完成的工具调用补齐合成工具结果,避免下一次请求出现不成对的工具调用。若中断发生在工具执行期间,工具系统会尽量返回取消结果,并让循环以中断状态退出。这样可以保持转录记录可恢复、消息结构合法。
异常路径会沿同一条循环回流。工具参数无效时,工具系统生成错误工具结果;权限被拒时,拒绝原因作为工具结果进入下一次采样;上下文过长时,循环先压缩再继续;模型 API 可恢复失败时,循环尝试备用模型或重试。错误不是单独的旁路日志,而是下一次模型采样可以读取的事实。
读查询循环时,不要把它看成一个 while(true) 包住 API 请求。它真正维护的是“下一次模型应该看到什么”和“本地副作用是否已经被合法执行”。同一个用户任务里可能穿插模型流、工具流、权限拒绝、备用模型、压缩和中断。下一章会继续展开这些材料如何进入上下文与记忆系统。