BrainBank
AI 课堂/最佳实践Claude Code Deep Dive

核心循环解析:QueryEngine 如何驱动一次任务完成

2026/7/31 14:34:23 · 更新于 2026/7/31 14:39:22

#agent-architecture#best-practices#context-management#claude-code-cli#core-loop

剖析 Claude Code CLI 的核心组件 `QueryEngine`,深入理解其如何通过会话级运行时管理全局上下文、驱动模型与工具的闭环决策以及维护跨轮次任务状态。

如果只能选一个文件代表 Claude Code 的灵魂,那大概率就是 QueryEngine.ts。它负责的不是某个局部能力,而是整个任务生命周期:接收用户输入 → 组装上下文 → 驱动模型调用 → 处理中间工具执行 → 维护会话状态 → 把任务一直推进到结束。这便是典型的 Agent 主循环架构。

image.png

会话级运行时,而非单次请求处理器

源码中的注释已经把定位写得很明确:

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()。这里可以把一次任务粗略拆分为以下七个阶段:

  1. 读取当前配置和状态
  2. 设置工作目录与 session 环境
  3. 包装工具权限判断逻辑
  4. 准备系统提示词与上下文
  5. 调用底层 query 流程与模型交互
  6. 在模型输出过程中处理工具调用和消息追加
  7. 统计 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()
}

image.png

模型与工具的闭环编排

很多人容易把主循环简单理解为“while 模型没结束就继续”。但 QueryEngine 远不止于此,它在持续处理会话历史的追加与标准化、工具调用前后的权限判断、部分/最终输出的区分、usage 预算更新,以及中断、恢复、压缩等运行时边界。因此它更像是一个精密的“编排层”,而不是简单循环。

这类系统最关键的一点,是模型和工具之间必须形成闭环。Claude Code 里的闭环路径大致如下:

  1. 模型根据系统提示与历史消息作出决策
  2. 决策可能包含工具调用
  3. 工具调用前先经过权限判断
  4. 工具执行后把结果转成消息
  5. 这些消息再次进入会话历史
  6. 模型根据新结果继续下一轮

QueryEngine 并非简单地“把工具借给模型”,而是在负责整个闭环的调度。只有形成这种工具结果回流再决策的循环,系统才具备真正的纠错能力。例如最基础的排查流程:

  1. 模型先猜某个 bug 在 api.ts
  2. 读取文件后发现判断不成立
  3. 再搜索相关调用点
  4. 最后才定位到真实问题

如果没有工具结果回流,这种自适应过程根本无法发生。

依赖总表与架构定位

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
}

image.png

边界处理与状态留存

QueryEngine 处理的远不止成功路径。源码中包含了大量工程级的防护逻辑:

  • 运行时控制abortController(中断)、orphanedPermissionsnipReplay
  • 错误分类: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 错误分级、预算追踪与状态持久化,确保长会话可靠性。

学习地图

  1. 核心基础概念认知:区分“单次请求”和“会话对象”,理解 Agent 为什么需要持久化的 mutableMessages 和状态缓存。
  2. 源码架构拆解:分析 QueryEngineConfig 依赖注入(tools, mcpClients, commands 等)以及运行时环境组装逻辑。
  3. 核心任务流追踪:精读 submitMessage() 异步生成器,掌握从接收 prompt、权限筛选到模型交互的完整执行路径。
  4. 闭环机制理解:研究“工具结果回流”原理,即模型如何基于执行结果进行二次决策与自我纠错。
  5. 行业横向扩展:将 QueryEngine 的设计模式迁移至 LangGraph 或 AutoGen,掌握现代主流 Agent 框架的调度中心构建方法。

动手实践——分步指南

  1. 本地仓库环境准备:克隆 Anthropic 官方源仓到本地终端,并使用 VS Code 等工程编辑器打开根目录。
  2. 定位并阅读核心文件:导航至 src/core/engine/ 路径下的 QueryEngine.ts,利用编辑器的搜索功能找到 submitMessage() 函数入口。
  3. 拆解配置与依赖流:在代码中逐行分析构造函数的传入参数,画出系统依赖关系图,理清工具列表、MCP Client 和权限判断逻辑是如何被注入的。
  4. 手动模拟消息流转:选取一个包含多次工具调用的示例 Prompt,对照代码分支推演“下发提示词 -> 判定权限 -> 执行 -> 追加历史 -> 二次生成”的完整闭环路径。
  5. 知识迁移与对比实战:查阅 LangGraph 官方文档中的 StateGraph 章节,尝试用简短代码或伪逻辑概括此架构优势,并与传统无状态 API 进行对比验证。

三大推荐资源

  1. 1
    Anthropic 官方开发者文档

    Anthropic 平台的核心知识库,涵盖 Agent API、工具调用规范及 Claude Code CLI 的运行架构说明。

    https://docs.anthropic.com/en/docs/getting-started/welcome

  2. 2
    LangGraph 核心循环指南

    现代 LLM Agent 框架的权威文献,详细阐述了如何实现类似 `QueryEngine` 的会话级状态管理与多步循环调度。

    https://langchain-ai.github.io/langgraph/

  3. 3
    OpenAI Function Calling 协议规范

    业界现代 LLM 工具调用的标准设计文档,为理解模型如何触发外部工具并处理错误结果提供理论基准。

    https://platform.openai.com/docs/guides/function-calling

链接由 AI 推荐——使用前建议快速核实。