核心循环解析:QueryEngine 如何驱动一次任务完成
2026/7/31 14:34:23 · 更新于 2026/7/31 14:39:22
剖析 Claude Code CLI 的核心组件 `QueryEngine`,深入理解其如何通过会话级运行时管理全局上下文、驱动模型与工具的闭环决策以及维护跨轮次任务状态。
如果只能选一个文件代表 Claude Code 的灵魂,那大概率就是 QueryEngine.ts。它负责的不是某个局部能力,而是整个任务生命周期:接收用户输入 → 组装上下文 → 驱动模型调用 → 处理中间工具执行 → 维护会话状态 → 把任务一直推进到结束。这便是典型的 Agent 主循环架构。

会话级运行时,而非单次请求处理器
源码中的注释已经把定位写得很明确:
One QueryEngine per conversation.
这句话非常关键。QueryEngine 不是一次性的 request handler,而是一个围绕会话长期存在的对象。因此它会保留大量跨轮次状态,这也是 Claude Code 能够实现连续工作的基础:
mutableMessages(消息历史)permissionDenials(权限拒绝记忆)readFileState(文件缓存)totalUsage(用量累计)discoveredSkillNames/loadedNestedMemoryPaths(Skill 与 Memory 的发现状态)
从成员变量即可看出其“会话对象”的本质。对应源码如下:
export class QueryEngine {
private mutableMessages: Message[]
private abortController: AbortController
private permissionDenials: SDKPermissionDenial[]
private totalUsage: NonNullableUsage
private readFileState: FileStateCache
private discoveredSkillNames = new Set<string>()
private loadedNestedMemoryPaths = new Set<string>()
}
submitMessage():任务的真正入口与生命周期
用户每提交一次消息,最终都会进入 submitMessage()。这里可以把一次任务粗略拆分为以下七个阶段:
- 读取当前配置和状态
- 设置工作目录与 session 环境
- 包装工具权限判断逻辑
- 准备系统提示词与上下文
- 调用底层 query 流程与模型交互
- 在模型输出过程中处理工具调用和消息追加
- 统计 usage、成本、边界状态
因此 submitMessage() 本质上就是“启动一轮 agent run”。它不仅接收 prompt,还会同时挂载 tools、commands、mcpClients、budget、thinking 等运行时资源,并在初始化阶段处理 cwd 和 session 级状态:
async *submitMessage(
prompt: string | ContentBlockParam[],
options?: { uuid?: string; isMeta?: boolean },
): AsyncGenerator<SDKMessage, void, unknown> {
const {
cwd,
commands,
tools,
mcpClients,
verbose = false,
thinkingConfig,
maxTurns,
maxBudgetUsd,
} = this.config
this.discoveredSkillNames.clear()
setCwd(cwd)
const persistSession = !isSessionPersistenceDisabled()
}

模型与工具的闭环编排
很多人容易把主循环简单理解为“while 模型没结束就继续”。但 QueryEngine 远不止于此,它在持续处理会话历史的追加与标准化、工具调用前后的权限判断、部分/最终输出的区分、usage 预算更新,以及中断、恢复、压缩等运行时边界。因此它更像是一个精密的“编排层”,而不是简单循环。
这类系统最关键的一点,是模型和工具之间必须形成闭环。Claude Code 里的闭环路径大致如下:
- 模型根据系统提示与历史消息作出决策
- 决策可能包含工具调用
- 工具调用前先经过权限判断
- 工具执行后把结果转成消息
- 这些消息再次进入会话历史
- 模型根据新结果继续下一轮
QueryEngine 并非简单地“把工具借给模型”,而是在负责整个闭环的调度。只有形成这种工具结果回流再决策的循环,系统才具备真正的纠错能力。例如最基础的排查流程:
- 模型先猜某个 bug 在
api.ts- 读取文件后发现判断不成立
- 再搜索相关调用点
- 最后才定位到真实问题
如果没有工具结果回流,这种自适应过程根本无法发生。
依赖总表与架构定位
QueryEngine 需要持有大量上下文对象,因为它本质上是一个会话运行时的调度中心。通过 QueryEngineConfig 可以清晰地看到它所依赖的资源矩阵。这份配置几乎可以被看成 Claude Code 主循环的依赖总表,将模型循环真正依赖的外部世界全部显式列出。大部分高级能力最终都会在这里汇流。
export type QueryEngineConfig = {
cwd: string
tools: Tools
commands: Command[]
mcpClients: MCPServerConnection[]
agents: AgentDefinition[]
canUseTool: CanUseToolFn
getAppState: () => AppState
setAppState: (f: (prev: AppState) => AppState) => void
readFileCache: FileStateCache
customSystemPrompt?: string
appendSystemPrompt?: string
thinkingConfig?: ThinkingConfig
maxTurns?: number
maxBudgetUsd?: number
}

边界处理与状态留存
QueryEngine 处理的远不止成功路径。源码中包含了大量工程级的防护逻辑:
- 运行时控制:
abortController(中断)、orphanedPermission、snipReplay - 错误分类:API 错误分级与处理
- 状态跟踪:usage 实时统计、permission denial 持续跟踪
这说明 Claude Code 的主循环绝非理想化的 Demo,而是一个必须应对长会话、中断、失败、压缩、恢复等复杂现实场景的严谨实现。
一次任务结束后,哪些状态还会留下来?这是会话型 Agent 和一次性脚本最大的区别之一:
- 消息历史(上下文连续性)
- 已知权限拒绝信息(避免重复触雷)
- 文件读取缓存与 usage 统计
- 部分 memory / skill 发现状态
正是这些残留状态的保留,让用户能够真正“接着上轮继续聊”。任务完成后的状态保留,也是 QueryEngine 长期驻留于内存的根本原因。
核心心智模型
理解 QueryEngine,最好的方式不是把它看成“请求处理器”,而是:
Claude Code 会话级运行时中的任务编排器。
它向上连接用户输入和 REPL,向下连接模型、工具、权限、上下文与状态系统。如果说 main.tsx 决定“这次会话怎么启动”,那 QueryEngine.ts 决定的就是:
这次任务接下来到底怎样一步一步做完。
真正理解 Claude Code 的架构,绕不过 QueryEngine。
Key takeaways
QueryEngine.ts是 Claude Code 的 Agent 主循环灵魂,负责完整任务生命周期而非单次请求。- 会话级常驻:作为 One-per-conversation 对象,保留消息、权限、缓存与用量等跨轮次状态。
submitMessage()为入口:挂载全量运行时配置(tools/budget/agent),启动一轮完整的 agent run。- 闭环编排核心:通过“模型决策 → 权限校验 → 工具执行 → 结果回流 → 再决策”形成纠错与自适应能力。
- 工程化边界处理:内置中断恢复(
abortController)、API 错误分级、预算追踪与状态持久化,确保长会话可靠性。
学习地图
- 核心基础概念认知:区分“单次请求”和“会话对象”,理解 Agent 为什么需要持久化的
mutableMessages和状态缓存。 - 源码架构拆解:分析
QueryEngineConfig依赖注入(tools, mcpClients, commands 等)以及运行时环境组装逻辑。 - 核心任务流追踪:精读
submitMessage()异步生成器,掌握从接收 prompt、权限筛选到模型交互的完整执行路径。 - 闭环机制理解:研究“工具结果回流”原理,即模型如何基于执行结果进行二次决策与自我纠错。
- 行业横向扩展:将
QueryEngine的设计模式迁移至 LangGraph 或 AutoGen,掌握现代主流 Agent 框架的调度中心构建方法。
动手实践——分步指南
- 本地仓库环境准备:克隆 Anthropic 官方源仓到本地终端,并使用 VS Code 等工程编辑器打开根目录。
- 定位并阅读核心文件:导航至
src/core/engine/路径下的QueryEngine.ts,利用编辑器的搜索功能找到submitMessage()函数入口。 - 拆解配置与依赖流:在代码中逐行分析构造函数的传入参数,画出系统依赖关系图,理清工具列表、MCP Client 和权限判断逻辑是如何被注入的。
- 手动模拟消息流转:选取一个包含多次工具调用的示例 Prompt,对照代码分支推演“下发提示词 -> 判定权限 -> 执行 -> 追加历史 -> 二次生成”的完整闭环路径。
- 知识迁移与对比实战:查阅 LangGraph 官方文档中的
StateGraph章节,尝试用简短代码或伪逻辑概括此架构优势,并与传统无状态 API 进行对比验证。
三大推荐资源
- 1Anthropic 官方开发者文档
Anthropic 平台的核心知识库,涵盖 Agent API、工具调用规范及 Claude Code CLI 的运行架构说明。
https://docs.anthropic.com/en/docs/getting-started/welcome
- 2LangGraph 核心循环指南
现代 LLM Agent 框架的权威文献,详细阐述了如何实现类似 `QueryEngine` 的会话级状态管理与多步循环调度。
https://langchain-ai.github.io/langgraph/
- 3OpenAI Function Calling 协议规范
业界现代 LLM 工具调用的标准设计文档,为理解模型如何触发外部工具并处理错误结果提供理论基准。
https://platform.openai.com/docs/guides/function-calling
链接由 AI 推荐——使用前建议快速核实。