ClaudeCode有关Agent Loop知识点大全

1.Agent Loop标准执行循环包括哪些关键阶段?

Claude Code 的 Agent Loop(智能体标准执行循环) 本质上是一个基于 ReAct(Reasoning & Acting)模式的 while 循环 。只要大模型输出中包含 tool_use(工具调用请求),循环就会持续迭代;如果模型输出纯文本(不再调工具),则循环终止。

一、 Agent Loop 的 5 大核心阶段

  1. 上下文组装(Context Assembly)

    • 职责 :把系统的 Prompt、CLAUDE.md 配置文件、历史对话记录以及工具描述信息完整拼装成输入模型的上下文,并顺带运行 5 级压缩器,确保总体积不超过 200K Token 的预算限制。
  2. 模型推理(Model Inference)

    • 职责 :流式调用 Claude API,实时产出文本内容与工具调用请求(tool_use)。
  3. 工具分发(Tool Dispatch)

    • 职责 :解析模型响应中返回的 tool_use blocks(工具调用块),并根据工具的特性将其分类为并行执行 或独占串行执行。
  4. 权限守门(Permission Gate)

    • 职责 :每一个工具调用在真正运行前,都必须严格通过 deny-first(默认拒绝) 的权限安全管道。如果被拒绝,则直接返回错误并不予执行。
  5. 工具执行(Tool Execution)

    • 职责:通过权限检查的工具会在本地被实际执行,执行产生的返回结果会被追加到对话历史中,准备进入下一轮循环。

二、 循环的终止条件

整个循环会不断重复上述步骤,直到大模型返回的 stop_reason(停止原因)是 end_turn (而不是 tool_use),此时代表任务已由模型自主判断完成,循环正式结束。

三、 落地到代码实现的"9 步 Pipeline"

在实际的底层代码架构中,这 5 个宏观阶段被进一步细化和拆解为了 9 步 Pipeline:

  • 准备阶段(对应"上下文组装"):

    1. Settings resolution(配置解析)

    2. State init(状态初始化)

    3. Context assembly(上下文组装)

  • 推理阶段(对应"模型推理") : 4. 5 pre-model shapers(5 个预处理塑形器) 5. Model call(模型调用)

  • 执行阶段(对应"分发、守门、执行") : 6. Tool dispatch(工具分发) 7. Permission gate(权限守门) 8. Tool execution(工具执行)

  • 收尾阶段 : 9. Stop condition(终止条件判定)

阶段一:上下文组装

在一个Session中,ClaudeCode在一直在维护着这个上下文,LLM每轮问答是无状态的,也就意味着用户与LLM的每一次问答过程中其实都是发送的这个带着完整上下文Prompt。

这触及了所有大模型应用开发(包括 Claude Code)一个极其核心底层机制:大模型 API 本身是完全无状态的(Stateless),而"有状态"的对话体验是由本地客户端/SDK 包装出来的。

我们可以把这个过程拆解为以下几个关键点:

1. LLM 底层 API 的"失忆症"(无状态)

当你调用 Anthropic 的 Messages API 时,大模型服务器端不会在内存里记住你刚才聊了什么。对它而言,每一次接收到的 HTTP 请求都是一个全新的、独立的孤立事件。它既不知道你上一秒说了啥,也不知道你们上一步执行了什么工具。

2. 谁在维护状态?(Claude Code 客户端)

真正帮你"记住"上下文的,是运行在你本地机器上的 Claude Code 客户端(或 Agent SDK) 。 在它的内部运行内存里,一直维护着一个不断生长的 history(历史消息数组)。里面按时间顺序存着:

  • 用户的提问 (user)

  • 模型的回答与工具调用请求 (assistant)

  • 本地工具执行返回的结果 (tool_result)

3. 每一轮问答的真相:全量"打包发送"

正如我们前面探讨"阶段一:上下文组装(Context Assembly)"时看到的:

在每一次循环的起点,客户端都会把整个上下文"推倒重来"组装一遍:

  1. 拿出最新的系统提示词(System Prompt)。

  2. 读入层级 CLAUDE.md 文件。

  3. 动态加载当前可用的工具描述。

  4. 把内存中累积的整段 history(对话历史)全部拿出来,如果太长就跑 5 级压缩器瘦身。

  5. 把以上所有东西(系统提示词 + 规则 + 工具 + 完整的历史对话 )重新打包成一个巨大的、全新的 Prompt,一股脑儿发送给 Claude API。

4. 为什么要这么设计?带来的工程挑战是什么?

这种"每轮都发送全量历史"的设计虽然逻辑简单、确保了模型能看到全貌,但它带来了一个巨大的痛点------Token 消耗爆炸与延迟变高。

如果聊到第 50 轮,难道每次都要把前面 49 轮几万个 Token 的对话重新发一遍吗?

  • 这就是为什么 Claude Code 要设计"5级压缩器(Compactor Pipeline)"和依赖 Anthropic 的 Prompt Caching(提示词缓存)机制。

  • 通过缓存前面不变的大块内容(如系统提示词和早期的历史摘要),只有新增的对话内容需要实时付费和传输,从而完美解决了"每次都发全量 Prompt"带来的性能和成本问题。

我们可以把 Claude Code 的 "阶段二:模型推理(Model Inference)" 理解为整个 Agent Loop 中的"大脑思考与交互枢纽"。

它的核心职责非常纯粹:拿着第一阶段组装好的全量上下文去调用 Claude API,并把大模型的流式响应接回来。

这一阶段包含了三个关键设计点 以及三种至关重要的停止原因(stop_reason):

一、 3 大关键设计点

  1. 流式输出(Streaming)与实时 UI 渲染

    • 系统通过底层 AsyncGenerator 的 yield 机制,把大模型吐出的内容实时、逐字地推给前端 UI。

    • 这保证了用户在终端里看到的是丝滑的"打字机效果",而不是对着屏幕干等半天突然蹦出一大段话。

  2. Token 实时计数与防截断重试

    • 系统会对 Token 的使用进行实时监控。如果因为字数过多导致模型的输出被 max_tokens 强行截断,系统会自动提升限制并最多重试 3 次,确保大模型的回答不会说断就断。
  3. 多模型自动降级(Fallback)

    • 如果在调用时遇到主模型服务器不可用或网络异常,系统具备容灾能力,会自动切换到 fallback model(降级模型)继续处理,保证高可用性。

二、 3 种 stop_reason(决定下一步去哪)

大模型推理结束后,返回的数据包里一定会带有一个 stop_reason 字段,系统根据这个字段来决定流程的走向:

  • tool_use:

    • 含义:模型在思考后,认为需要借助外部工具来完成任务(比如读文件、查代码)。

    • 流向:流程正式进入下一阶段(工具分发与权限守门)。

  • end_turn:

    • 含义:模型认为任务已经圆满完成,给出了最终的文本总结。

    • 流向:循环正常终止(Terminal)。

  • max_tokens:

    • 含义:输出内容因超出单次最大 Token 限制而被截断。

    • 流向:触发前文提到的自动恢复重试逻辑。

我们可以把 "阶段三:工具分发(Tool Dispatch)" 理解为整个循环中的"调度与并发指挥中心"。

当大模型在一轮回复中同时申请调用多个工具(即包含多个 tool_use blocks)时,分发阶段负责决定它们怎么跑、谁先跑、谁同时跑。

这一阶段主要包含两个核心板块:执行调度机制 与工具池组装逻辑。

一、 工具的执行调度(StreamingToolExecutor)

Claude Code 并不会死板地等模型把话说完才去干活,而是采用"边解析边入队"的流式调度策略:

  • 实时入队 :在流式响应还没结束时,只要某个 tool_use 模块被完整解析出来,它就会立即加入执行队列。

  • 按类型智能分类:工具进入队列后,会被严格划分为两大阵营:

    1. concurrent-safe(并发安全工具) :例如 Read(读文件)、Glob(查目录)、Grep(搜代码)、WebSearch(搜网页)等只读工具 ,允许同时并行跑。

    2. exclusive(独占/有副作用工具) :例如 Bash(跑命令)、Write(写文件)、StrReplace(改文件)等会修改系统状态的工具,必须串行跑。

  • 经典运行示例 :如果大模型在某一轮同时要求执行 3 个 Read + 1 个 Write ,系统会让 3 个 Read 并行执行 ,而 Write 则会耐心地等这 3 个读操作全部完成后才开始跑。

二、 工具池本身的 5 步组装(Tool Pool Assembly)

在工具能够被分发之前,系统必须先动态组装出当前轮次"允许使用"的工具大盘。这个组装过程分为 5 个步骤:

  1. Base enumeration(基础枚举):列出系统所有的内置工具,最多可达 54 个。

  2. Mode filtering(模式过滤):根据当前的运行模式,筛掉那些不适用的工具。

  3. Deny pre-filtering(黑名单预过滤) :根据安全配置中的 deny 规则,把被禁用的工具提前移除。

  4. MCP integration(MCP 整合):将外部连接的 MCP Server 所提供的扩展工具合并进来。

  5. Deduplication(去重):对最终的工具列表进行去重处理,确保没有重复项。

Claude Code 的 "阶段四:权限守门(Permission Gate)" 理解为整个 Agent Loop 中最核心的"安全保卫与合规审查卡口"。当第三阶段把工具分发并排好队之后,任何工具在真正被系统执行之前,都必须强行通过这个严密的权限管道。

这一阶段的核心逻辑可以拆解为"Deny-First 过滤机制"、 "7 种信任光谱模式"以及"钩子拦截与隔离机制":

一、 核心审判规则:deny-first(默认拒绝优先)

权限管道对每一个工具调用的检查顺序是极其严格的阶梯式过滤:

  1. 第 1 步:先查 deny(黑名单)列表

    • 只要在黑名单里匹配到了,直接拒绝执行,且无法被覆盖。
  2. 第 2 步:再查 allow(白名单)列表

    • 如果在白名单里匹配到了,直接放行。
  3. 第 3 步:如果两边都没匹配到

    • 系统会根据当前所处的权限模式来决定下一步该怎么做(例如弹窗问用户或由 AI 自动判断)。

二、 7 种权限模式形成的"信任光谱"

Claude Code 提供了从极度安全到完全放开的 7 种权限模式,满足不同场景的需求:

plan → default → acceptedEdits → auto → dontAsk → bypassPermissions

  • plan 模式 :最严格的模式,只允许执行只读操作(如查看文件、搜索),绝对禁止任何修改。

  • default 模式 :标准安全模式,如果遇到未授权的操作,会弹窗询问用户是否批准。

  • auto 模式 :引入了 ML 分类器(机器学习模型) 来自动判断当前操作的安全性。

  • bypassPermissions 模式 :完全绕过权限检查,专为开发调试设计,严禁在生产环境中开启。

三、 另外两个非常关键的设计细节

  1. Pre-tool Hooks(前置生命周期钩子二次拦截)

    • 即使工具顺利通过了上述权限守门,系统还会触发我们在前面学过的 Pre-tool hooks。外部注册的脚本可以对参数进行二次校验、修改参数,甚至直接取消这次调用。
  2. 故障隔离(不影响同伙)

    • 如果某一轮中有多个工具并行执行,某一个工具因为权限不足被拒绝,完全不会影响同一轮中其他并行工具的正常执行。

Claude Code 的 "阶段五:工具执行(Tool Execution)" 理解为整个 Agent Loop 中的"落地执行与闭环收尾阶段"。

当工具顺利通过第四阶段的权限守门之后,就会在这个阶段真正跑起来。这一阶段包含了 4 大核心运行机制,并最终驱动整个循环走向闭环或终止:

一、 4 大核心运行机制

  • Shell 沙箱(Shell sandbox) :如果 Bash 工具触发了沙箱规则,它会在一个受限制的环境中运行,文件系统和网络访问都会受到操作系统级别的严格限制。

  • 超时控制(Timeout control) :每个工具都有明确的执行时间上限,一旦超时系统会自动将其强行 kill(终止进程)。

  • 结果格式化(Result formatting) :工具执行完产生的输出,会被包装成标准的 tool_result 消息格式。

  • 后置钩子(Post-tool hooks):工具执行完毕后会触发生命周期钩子,通常用于记录审计日志或对输出结果进行过滤。

二、 形成完整闭环

工具执行完毕后,其结果会连同大模型先前的 assistant 响应一起追加到对话历史中,作为下一轮循环中"上下文组装"的输入。

这便构成了一个完美的自适应闭环:模型看到工具结果 → 评估并决定下一步 → 再次调用新工具或结束任务。

三、 循环的 5 种终止方式

整个 while 循环并不依赖死板的计时器,而是根据以下 5 种条件来决定何时退出:

  1. 模型输出纯文本、不调工具:代表任务正常完成。

  2. 达到 max_turns 配置上限:防止死循环,触发强制停止。

    PNG

  3. 上下文溢出且压缩无效:对话太多导致 5 级压缩也塞不下,触发异常终止。

    PNG

  4. Hook 返回终止信号:受到外部钩子的干预而停止。

    PNG

  5. 用户手动 abort:用户在前端主动中断(如按下快捷键)。

2.CC对checkpoint数量和生命周期的约束,体现了其在Agent Loop中哪两类资源的显式权衡?

一、 ~/.claude/projects/ 下的 .jsonl 文件代表什么?

一个 .jsonl 文件就完整代表了一个 Session(会话)的记录。

  • 存储位置与结构 :每个会话在磁盘上通常对应一个 .jsonl 文件(存放在 ~/.claude/projects/<id>/sessions/ 目录下)。

  • 追加式写入 :这个文件采用了 JSON Lines 格式。在会话进行时,用户的每一条 Prompt、大模型的回复、每个工具的调用结果、以及每次修改文件前的 Checkpoint 快照索引,都会作为一行独立的 JSON 对象实时追加到文件末尾。

二、 每次修改文件前的 Checkpoint 存在哪?是直接存在 .jsonl 里吗?

不是直接把整个文件的备份塞进 .jsonl 里,而是"元数据索引"与"文件实体"相结合:

  1. .jsonl 记录的是"账本(索引)":文件中会记录某一个时间点、某一条指令执行前,哪些文件即将被修改,并生成对应的快照指针/索引。

  2. 实际的文件副本存储在磁盘目录中 :每次用户发送 Prompt 触发修改前,系统会对即将被修改的文件做全量快照,将文件副本存放在磁盘的对应存储区中(由 .jsonl 中的索引进行关联管理)。

三、 能否根据这个 .jsonl 文件恢复之前的任一 Checkpoint 快照?

从底层原理和技术上来说:完全可以。

  • 因为 .jsonl 就像一本不可篡改的"历史账本",它按时间顺序忠实记录了每一个 Checkpoint 的生成节点、对应的文件路径以及快照标识。

  • 如果你需要手动或通过脚本恢复,理论上可以通过解析该 .jsonl 文件中记录的快照索引,找到对应的文件物理副本,然后覆盖回原路径。

但在实际开发和使用中需要注意:

  • 生命周期限制 :由于 Claude Code 设有 30 天的自动清理机制 和 100 个 Checkpoint 的空间上限(超出后最老的快照会被丢弃),如果超出了这个时间窗口或步数限制,对应的快照实体可能已经被系统自动回收删除了。

  • 推荐做法 :官方通常会通过内置的交互命令或界面来管理这些回滚操作,直接去手动解析和拼接 .jsonl 相对繁琐且容易出错,但它确实是整个时间机器(Time Travel)和版本回退功能的底层数据支撑。

在 Claude Code 的底层架构中,会话的文字记录与文件的修改快照(Checkpoint Snapshots)是分开存放在不同目录的:

  • 会话记录(Transcript) :存放在 ~/.claude/projects/<project-hash>/ 目录下的 .jsonl 文件中,记录的是对话、工具调用、结果及时间线索引。

  • 文件修改快照(Checkpoint Snapshots) :实际的文件内容备份(即每次 Claude 修改文件前的 Pre-edit 快照)则统一存放在全局的 ~/.claude/file-history/<project-hash>/ 目录下。

3.CC的Agent Loop是如何设计与运行的?每个阶段调用时机是啥?

python 复制代码
async function* query(params: QueryParams) {
  while (true) {
    // 1)上下文组装 + 压缩
    const messages = assemble(history);
    await compact(messages);  // 5 级压缩器依次跑

    // 2)调用模型(流式)
    const stream = await callClaude(messages);
    for await (const chunk of stream) {
      yield chunk;  // 实时推给 UI
      if (chunk.type === 'tool_use') {
        executor.queue(chunk);  // 边流边排队
      }
    }

    // 3)工具执行
    const results = await executor.run();
    history.push(assistantMsg, ...results);

    // 4)终止判断
    if (!hasToolUse(response)) return Terminal;
  }
}

这段代码是 Claude Code 核心引擎中最精髓的 Agent Loop(智能体循环) 实现。从架构设计上讲,Claude Code 的 Agent Loop 本质上是一个基于 AsyncGenerator(异步生成器)实现的 ReAct(Reasoning & Acting)状态机。

核心代码详解:

代码中的 async function* query 是一个异步生成器函数,它通过 while (true) 死循环驱动整个对话和工具执行过程,直到任务完成才退出:

1. 第一步:上下文组装与压缩 (assemble + compact)

复制代码
const messages = assemble(history);
await compact(messages); // 5 级压缩器依次跑
  • 做什么:在每一轮(Turn)开始前,系统先把历史对话组装起来,并通过多级压缩器(Compactor)进行清理和摘要,确保上下文不会爆掉,同时保护 Prompt Cache。

2. 第二步:调用模型并实时流式处理 (callClaude + 边流边排队)

复制代码
const stream = await callClaude(messages);
for await (const chunk of stream) {
  yield chunk;  // 实时推给 UI
  if (chunk.type === 'tool_use') {
    executor.queue(chunk);  // 边流边排队
  }
}
  • 做什么 :调用大模型并开启流式传输(Streaming)。

    • yield chunk:大模型每吐出一个字或一个事件,立刻通过生成器实时吐给前端 UI(实现打字机效果)。

    • executor.queue(chunk):如果在流式传输中发现模型想要调用工具(tool_use),系统不等整段话说完,就边流边把工具放进执行队列里排队,极大提升了响应速度。

3. 第三步:批量执行工具并更新历史 (executor.run + history.push)

复制代码
const results = await executor.run();
history.push(assistantMsg, ...results);
  • 做什么 :大模型生成完这一轮的回复后,排好队的工具开始集中执行(期间会经过我们前面学过的 Pre-tool Hook 和权限检查)。执行完毕后,系统把大模型的助手消息和工具返回的 tool_result 一起打包,追加到 history(历史记录)中。

4. 第四步:终止判断 (hasToolUse)

复制代码
if (!hasToolUse(response)) return Terminal;
  • 做什么 :检查这一轮模型输出里还需不需要调工具。

    • 如果模型只是输出了普通的文本回答(不需要再调工具了),说明任务结束,直接跳出循环返回 Terminal 状态。

    • 如果模型还需要调用新工具,while (true) 就会带着最新的工具结果回到开头,开启下一轮(Turn)循环。

这段代码最巧妙的地方在于使用了 async function* 和 yield 关键字。

  • 传统同步函数的痛点:如果写成普通函数,程序必须等大模型说完、再等工具执行完、再等第二轮模型说完,才能把最终结果一次性吐给前端。用户在屏幕前就会看到长达十几秒的"死等"状态。

  • AsyncGenerator 的优势 :通过 yield chunk,底层循环每产生一个事件(无论是文字流、工具调用请求、还是执行结果),都可以实时、像流水一样源源不断地吐给前端 UI 渲染,让用户在终端里看到丝滑的动态交互过程。

为了兼顾极致的响应速度 与系统的健壮性 ,巧妙地设计了两条不同的执行路径 。分别是主路径(流式并发执行器)和兜底路径(保守降级路径)

一、 主路径:StreamingToolExecutor(流式并发执行器)

这是 Claude Code 日常工作时的默认主路径 。它的核心特点是:"边走边看,边流边跑"------在模型的流式响应还没结束时,工具就已经开始并发执行了。

它的具体执行步骤如下:

  1. 流中解析 :在模型吐字(Streaming)的过程中,实时解析出完整的 tool_use 块,并将其加入执行队列。

  2. 智能分类:根据工具的特性进行分类:

    • concurrent-safe(并发安全) :如 Read(读文件)、Glob(查目录)、Grep(搜代码)等纯只读工具。

    • exclusive(独占/串行) :如 Bash(跑命令)、Write(写文件)等具有副作用、会修改系统状态的工具。

  3. 混合调度 :concurrent-safe 的只读工具允许多个同时并行执行 ,而 exclusive 的有副作用工具则严格串行执行。

  4. 汇总结果:所有工具跑完后统一汇总结果,塞回上下文。

二、 兜底路径:runTools(保守降级路径)

这是作为备用方案(Fallback)存在的路径。它的核心特点是:"牺牲延迟,换取稳定性"。

  • 触发时机:当主路径的流式执行器(StreamingToolExecutor)遇到异常或报错时,系统会自动降级切到这条路径。

  • 运行方式 :它不再搞"边流边跑",而是等大模型的响应完全结束后,再把工具拿出来逐个、串行地运行。

  • 设计取舍:虽然多花了一点时间(牺牲了延迟),但极大地保证了在复杂或出错环境下的代码稳定性。

这里讲的 7 个状态 和 5 种终止条件 ,与我们之前讨论的 5 个主要状态节点 (INIT、WAITING_FOR_MODEL、EXECUTING_TOOLS、COMPACTING、TERMINATED)并不矛盾。实际上,这张图是对之前状态机工作流的"细化、微调与落地补充"------它把大循环内部的正常路径进行了更精准的命名,并把之前一笔带过的 错误恢复机制和终止判定条件做了极其详尽的拆解。

一、 7 个状态(3 个正常路径 + 4 个错误恢复)vs 之前 5 个状态节点

图中的 7 个状态把整个运转流程分成了主干道 和异常救援支线:

1. 3 个正常路径状态(对应核心主干道)

  • BuildConfig 对应之前的 INIT 状态:负责初始化、组装配置和系统提示词。

  • CallModel 对应之前的 WAITING_FOR_MODEL 状态:向大模型发起流式调用并等待返回。

  • ExecuteTools 对应之前的 EXECUTING_TOOLS 状态:在本地安全并发地执行模型要求的工具。

2. 4 个错误恢复状态(隐藏在主干道之下的"安全网")

这是这张图新补充的底层容错机制,它们在主路径发生异常时被触发:

  1. Max tokens 恢复 :当模型输出被截断时,自动提升 max_output_tokens 并重试(最多 3 次)。

  2. Reactive compaction(反应式压缩) :对应之前提到的 COMPACTING 状态,当检测到上下文快要爆掉时,触发紧急压缩然后再重试。

  3. Streaming fallback(流式降级):当主模型的流式传输(Streaming)挂掉或网络异常时,自动切到我们前面学过的保守降级路径。

  4. Loop divergence detection(死循环发散检测):监控 Agent 是不是陷入了"无限兜圈子"的僵局,一旦发现异常立刻强行终止。

二、 5 种终止条件 vs 之前进入 TERMINATED 的契机

我们之前讲 TERMINATED 是 Loop 的终点,而图中所列的 5 种终止条件 ,正是触发状态机跳转到 TERMINATED(终止状态)的具体裁判标准:

终止条件类型 触发场景 对应之前的概念
1. 模型输出纯文本(无 tool_use) 模型认为任务大功告成,只吐出文字总结,不再申请调工具。 正常退出 (stop_reason == "end_turn")
2. 达到 max_turns 上限 交互轮数超过了安全限制,防止死循环无限消耗。 熔断保护
3. 上下文溢出且压缩后依然塞不下 对话太长,即使经过紧急压缩也无法容纳下一轮交互,被迫中止。 异常终止
4. Hook 干预 外部的 Hook 脚本(如安全拦截器)检查出风险,返回拒绝信号。 外部强行终止
5. 用户显式 abort 用户在终端手动按了 Ctrl+C 或终止按钮。 中断终止

💡强调了一个极其精妙的设计哲学:"Agent 自己决定什么时候停"。

4.CC的AgentLoop包含哪三个核心阶段?如何体现闭环反馈?

Claude Code 的 Agent Loop 高度概括为三个核心阶段 ,这本质上就是经典的 ReAct(Think → Act → Observe) 模式在工程上的精炼映射。

一、 Agent Loop 的 3 个核心阶段

  1. Gather Context(收集上下文) → Think

    • 做什么:在行动之前,先去摸清情况、搜集情报。

    • 典型动作 :调用 Read 读取文件内容,调用 Grep 搜索函数引用,或调用 Glob 查找目录结构。

  2. Take Action(执行动作) → Act

    • 做什么:基于收集到的信息进行推理分析,并做出实质性的改动或执行命令。

    • 典型动作 :分析出 bug 原因后,调用 StrReplace 修改代码,或调用 Bash 跑命令、写文件。

  3. Verify Results(验证结果) → Observe

    • 做什么:观察并获取动作执行后的真实环境反馈。

    • 典型动作:检查工具返回的执行成功/失败状态、运行测试或 lint 检查的输出结果。

二、 如何体现"闭环反馈"?

闭环反馈的核心逻辑可以总结为两句话:"输出变成输入,错误驱动修正"。

具体体现如下:

  • 拒绝盲跑 :Agent 不是预先规划好 10 步然后闭着眼睛一路跑到底,而是每走一步,都必须拿到当前环境的"地面实况(Ground Truth)"。

  • 结果重新注入 :第三阶段(Verify Results)拿到的真实输出(无论是修改成功、命令报错还是 lint 警报),都会被作为 tool_result 实时追加并注入回对话历史。

  • 下一轮决策的起点 :当循环重新回到第一阶段(Gather Context)时,模型在新的上下文里一眼就能看到上一步的报错信息。这会直接驱动模型在下一轮中自动发起自我纠错和修复,从而形成不断自我完善的自适应闭环。

query.ts 是 Claude Code 核心源码中最核心、最关键的文件之一(全篇大约 1729 行),它本质上是整个 Agent Loop(智能体循环)的底层实现载体。

具体来说,它主要承担以下核心职责:

  • 驱动 ReAct 状态机循环 :它内部实现了一个 async function* query 异步生成器函数,通过 while (true) 死循环和 yield 关键字,精准驱动着"组装上下文 → 调用模型 → 流式处理 → 分发/执行工具 → 检查权限 → 回环更新历史"的完整生命周期。

  • 承载 Gather Context(收集上下文)等核心阶段 :正如图片中所指出的,query.ts 负责上下文的组装以及模型推理的前半段。它让模型在推理时动态判断需要哪些信息,并指挥模型去调用 Read、Grep、Glob 等工具去探索和收集项目数据。

  • 实现丝滑的流式交互(Streaming) :它利用 yield 实时向前端 UI 逐字推送大模型的响应和工具调用请求,做到"边流式传输、边工具排队",彻底告别了让用户死等的尴尬体验。

简单来说,query.ts 就是 Claude Code 的"大脑中枢与神经脉络",所有的智能思考、工具调度和闭环迭代都在这个文件里协同运转。

如何获取到query.ts文件?

query.ts 文件是 Claude Code 的源码核心文件 (位于源码目录的 src/query.ts 或其 SDK 包内部)。

如果你想在自己的本地环境找到它或者阅读它的代码,有以下几种主要途径:

一、 你能在本地机器上直接找到它吗?

通常不能直接看到原始 .ts 文件,但能找到编译后的 JavaScript 代码:

  1. 如果是全局安装的 CLI(如通过 npm / bun):

    • 官方打包发布到 npm 的二进制或 Node 包通常是经过 Bundler(如 esbuild/rollup)打包打包压缩后的 JavaScript 文件 (如 dist/index.js 或 cli.js),原始的 TypeScript 源码文件(query.ts)在 npm 安装包中默认是不直接存在的。
  2. 如果是安装了 SDK(@anthropic-ai/claude-agent-sdk):

    • 在你的项目的 node_modules/@anthropic-ai/claude-agent-sdk/ 目录下,你可以找到编译后的 query.js 或包含的逻辑模块,有时会随附 .d.ts 类型声明文件。

二、 如何获取并阅读 query.ts 的原始源码?

由于开源社区和开发者对 Claude Code 底层架构的深度研究,目前你可以通过以下几种非常方便的渠道获取和阅读 query.ts 的完整源码:

1. 社区开源镜像与逆向/解包仓库(GitHub)

在 GitHub 上搜索 claude-code query.ts,有许多社区开发者(如安全研究员、源码解析项目)整理的源码与解读仓库:

  • tornikeo/claude-code 或 lowcortisolprogrammer/claude-code :包含了完整的 src/query.ts(文件长达 1700+ 行,大小约 700KB+),里面清晰记录了 async function* query() 的全量实现。

  • chauncygu/collection-claude-code-source-code:收录了 Claude Code 各个版本的源码解析与架构映射。

2. 学术与架构分析文档仓库

  • GitHub 上的 Windy3f3f3f3f/how-claude-code-works 仓库对 query.ts 的底层设计(如 AsyncGenerator 机制、流水线拦截器、状态机转换等)做了极其详尽的中文架构图解和逐行剖析。

3. 从本地安装包提取(Sourcemap 还原)

如果你本地已经通过 npm 安装了 Claude Code,且包中带有 SourceMap,你可以借助工具(如 shsourcemap 或 unminify)对本地 dist/ 目录下的 .js 文件进行反编译还原,即可提取出原始的 src/query.ts 文件。

5.在Agent Loop执行过程中,tun的定义是啥?max_turns指的是啥?

6.CC中为什么流式输入模式被官方明确标注为首选和推荐?流式输入模式与Agent Loop(智能体循环)有啥关系?

复制代码
// Streaming Input Mode 典型用法
const client = new ClaudeSDKClient();
const session = client.connect();

// 持续喂入消息,session 不断开
async function* messageStream() {
  yield { type: "user", content: "分析 src/ 下所有 TypeScript 文件的依赖关系" };
  // 等第一轮工具调用完成后继续追问
  yield { type: "user", content: "把循环依赖的文件列出来" };
}

for await (const event of session.streamInput(messageStream())) {
  // 实时拿到 partial response、tool_use 事件、最终结果
  console.log(event);
}

具体来说,这个问题要求从以下 3 个关键技术维度,解释为什么流式输入模式被官方明确标注为"首选(preferred)"和"推荐(recommended)":

一、 题目的 3 个核心分析维度

  1. 工具集成(Tool Integration)

    • 探讨重点 :流式输入模式如何支持"边解析边执行"的工具调用(如前面学过的 StreamingToolExecutor)。

    • 原理 :模型无需等待整句回复输出完毕,一旦生成完整的 tool_use 结构,工具就可以立即入队并并发/并行跑起来,大大降低了等待延迟。

  2. 实时反馈(Real-time Feedback)

    • 探讨重点 :系统如何通过流式 yield 机制,实现 UX 层面与控制流上的实时响应。

    • 原理 :不仅能在终端实现"逐字打字机"式的打字效果,还能在发生错误或触发权限询问(Permission Gate)时即时拦截并暂停,无需浪费冗余的 Token 和生成时间。

  3. 上下文持久化(Context Persistence)

    • 探讨重点:流式传输如何与底层状态管理及对话历史(Transcript/Checkpoint)保持同步。

    • 原理 :流式块在生成的同时,可以增量地拼接并写进本地 .jsonl 持久化账本和内存历史,确保每一步的状态变更都是即时、可靠且可追踪的。

二、 与 Agent Loop 设计目标的对齐性

题目的落脚点在于:说明这种设计如何完美契合 Agent Loop 的终极目标。

Agent Loop 的核心理念是 ReAct 闭环(Think → Act → Observe):

  • 传统非流式:Agent 需要预先规划一大串动作,生成完再串行执行,不仅反馈滞后,一旦中间有错就会造成连锁崩溃。

  • 流式输入模式 :使得 Agent Loop 能以极低延迟拿到"地面实况(Ground Truth)",实现真正的"边走、边看、边调整"的渐进式闭环决策。

问题一:interrupt() 告知调用方"哪些排队消息活了下来",有啥好处?

当用户在 Agent 还在"疯狂想/疯狂跑工具"的中途突然发了一条新指令,触发了 interrupt() 中断。此时,系统通过 SDKControlInterruptResponse 明确告知调用方保留了哪些消息,核心好处有三点:

1. 彻底解决"消息吞掉/丢失"的并发盲区(可靠性)

  • 痛点:在 v2.1.205 之前,如果用户发消息的瞬间正好赶上 Turn 的最后一次迭代,旧机制可能会误以为"旧任务退出了,队列清空",导致用户的输入直接消失。

  • 好处 :显式返回"幸存消息列表",调用方(如 UI 或 上层服务)能够明确感知到:"哦!我刚才强行打断 Agent 时,用户发的那条'不用修了,改做 B 方案'的指令成功留在队列里了!"

2. 精准的 UI 渲染与状态同步(一致性)

  • 前端 CLI 或 IDE 界面可以根据这个响应,精确知道哪些 Prompt 会被继续送入下一轮 Agent Loop,哪些需要被丢弃或重新渲染,避免前端显示"已发送"但后端实际没收到的"幽灵消息"尴尬。

3. 支持上层应用的"消息重组/二次撤回"

  • 调用方拿到这个幸存列表后,如果发现某个排队消息因为上下文改变已经不再适用,调用方有机会在它进入下一个 Turn 前再次对其进行撤回、合并或改写,给予了开发者极高控制权。

问题二:中断后排队消息启动"独立 Turn,带独立 max_turns 限制",有啥好处?

在之前的实现中,如果排队消息直接追加到当前被打断的 Turn 里,它会共享并消耗 当前 Turn 剩余的 max_turns 配额。

改变后,排队消息启动自己全新的独立 Turn 并享有独立的 max_turns 限制,带来的好处极其显著:

复制代码
【旧机制(共享 Turn)】
[Turn 1] 用户 Prompt A ➔ 已耗费 9 次迭代 ➔ 中断 ➔ 插入 Prompt B ➔ 仅剩 1 次迭代 ➔ 报错 (Exceed max_turns) ❌

【新机制(独立 Turn)】
[Turn 1] 用户 Prompt A ➔ 已耗费 9 次迭代 ➔ 中断 (安全结算 Turn 1)
                                ⬇
[Turn 2] 用户 Prompt B ➔ 开启全新生命周期 ➔ 拥有完整的 10 次迭代配额 ➔ 顺利完成 ✅

1. 避免新任务因旧任务的"额度耗尽"而被无辜冤杀(隔离性)

  • 场景 :假设上一个任务是个极为复杂的重构,已经跑了 19 轮(max_turns=20),此时你打断它并发了一条新指令:"不用重构了,只帮我在文件末尾加个注释"。

  • 好处 :如果共享 Turn,新指令只剩 1 轮配额,极易直接报错死掉。而给予独立 Turn 和独立 max_turns,能确保新指令拥有完整、充足的步数预算去独立完成任务。

2. 上下文与归因清晰(可维护性)

  • 账本与日志对齐 :每个 Turn 在 .jsonl 记录中代表一个明确的逻辑任务单元。打断前是一个独立的 Turn,打断后新触发的动作是另一个 Turn。

  • 审计方便:你可以清晰定位"任务 A 耗费了 15 轮被中断,后续任务 B 耗费了 2 轮完成",避免多个逻辑不相干的操作混在同一个 Turn 的步数统计里,方便做精确的 Token 与步数成本审计。

7.在不需要人类盯着(无人值守/unattended run)的情况下,如何确保 Agent 真的把代码改对了,而不是自己"盲目自信"地觉得搞定了?

为了在 Agent Loop 中集成自动化验证,文档推荐了从低到高 3 种渐进式的技术方案:

  1. CLAUDE.md 声明验证命令(提示词约束层)

    • 怎么做 :在 CLAUDE.md 里写明规则,告诉 Agent 每次改完代码后必须运行 npm test 或 pytest。

    • 特点:依赖大模型"自觉"去遵守提示词中的规则。

  2. Skills 封装验证循环(工作流/工具链层)

    • 怎么做 :把"修改代码 → 跑测试 → 报错修复 → 再测试"的完整过程打包写成一个 Skill(如 /verify 或 /code-review)。

    • 特点:将验证流程标准化为可重复调用的链式动作。

  3. Hooks 自动触发验证(系统底层强制层)

    • 怎么做 :配置 Post-tool hooks(后置生命周期钩子,例如 PostToolUse),只要 Agent 修改了文件,系统在底层强行触发 ESLint 或测试脚本。

    • 特点:完全绕过大模型的意志,由系统做硬性死把关,改完立刻强制检查。

这三种方案在"设置成本(配置复杂度)"与 "人工干预需求(需要人类插手的程度)"上呈现反向对齐的权衡关系:

方案 设置成本 (配置复杂度) 人工干预需求 逻辑控制力
1. CLAUDE.md 最低(写几行文字) 最高(模型可能偶尔漏掉,需人类提醒) 模型自觉(软约束)
2. Skills 中等(编写 Skill 脚本与工作流) 中等(流程规范,失败时人类介入) 流程引导(半硬约束)
3. Hooks 最高(需配置底层生命周期钩子) 最低(接近零干预,实现真正的无人值守) 系统强制(硬约束)

"跑 npm test 返回 exit code 0 是客观事实,模型说'我觉得我改对了'只是凭空幻觉。"

无人值守(Unattended Run)的本质,就是通过确定性的外部系统信号(如 exit code、Lint 报错),来代替大模型的主观幻觉,让 Agent 在 ReAct 的循环中不断靠硬指标自我纠错。

1. Standalone(独立模式)

  • 原理 :最基础、最直观的模式。Skill 作为独立的指令工具存在,完全由人类开发者根据当前需要手动调用。

    PNG

  • 典型场景:

    • 当你写完一段前端代码,想单独跑一下检查时,手动输入 /verify-frontend。

      PNG

    • 这种模式非常适合通用的、跨工作流的辅助检查 (如 /format-code、/security-scan),不需要绑定在任何固定流程后,想用时随时触发。

      PNG

2. Embedded(内嵌模式)

  • 原理 :把验证或收尾动作直接内嵌编写在主要生产 Skill 的逻辑末尾 。只要主任务跑完,末尾内嵌的逻辑就会被隐式自动执行。

    PNG+ 1

  • 典型场景:

    • 假设你写了一个重构代码的 Skill /refactor-api,你在该 Skill 脚本的最下方加入了一段打包/编译校验逻辑。

      PNG

    • 适用于只属于特定工作流的绑定校验 。比如重构完了必须顺手校验 API Schema,其他场景不需要独立跑这个校验。

      PNG

3. Chained(链式模式)

  • 原理 :利用拓扑链条(DAG)将多个独立的 Skill 串联起来。前一个 Skill 执行成功后,其输出作为输入直接自动触发下一个 Skill,形成流水线。

  • Anthropic 内部团队的典型 Chain :

    /code-review⟶/simplify⟶/verify⟶/design (若修改了UI)

    • 步骤 1 (/code-review) :先对改动的代码进行深度 Code Review。

      PNG

    • 步骤 2 (/simplify) :Review 通过后,自动精简冗余代码、重构复杂逻辑。

      PNG

    • 步骤 3 (/verify) :自动跑测试用例和类型检查。

      PNG

    • 步骤 4 (/design) :如果检测到改动涉及 UI 组件,自动发起设计规范对齐检查。

      PNG

  • 优势 :这一整条链能够自动跑完整个开发周期,人类开发者完全不需要在中间干预,只有在链条某一环失败并触发错误升级时人才介入 。

    PNG

4. PR-bound(PR 绑定/CI 门禁模式)

  • 原理:将 Skill 接入系统的 CI/CD 流程(如 GitHub Actions、GitLab CI)。当开发者提交 Pull Request(PR)或 Push 代码时,由 GitHub Actions 自动化环境拉起 Claude Code 并运行特定的 Skill。

  • 典型场景:

    • 团队级强制门禁 :不再依赖某个程序员在自己本地"自觉"跑 Skill,而是在 GitHub Actions 里配置门禁。如果 /verify 或 /security-audit 没有跑过,直接阻止 PR 合并。

这段 JSON 配置定义了两种生命周期钩子(Hooks):

1. PostToolUse(改完文件立马查------增量细查)

  • matcher: "Edit|Write|MultiEdit" :当 Agent 刚执行完编辑、写入或批量修改文件的工具时。

  • command : 系统自动且强制 在后台执行 npx eslint ...。

  • 作用:只要 Agent 一改代码,系统立马检查被修改的文件有没有语法错误或格式代码问题(即时抓住低级错误)。

2. Stop(准备结束时做全量校验------终极把关)

  • command : "npm test -- --watchAll=false 2>&1 | tail -20"。

  • 作用 :当 Agent 觉得自己大功告成、准备结束本轮任务(Stop)时,系统在后台自动跑全量单元测试 。只有测试通过(exit code 0),才允许它正式退出;如果测试报错,系统会把错误信息扔回给 Agent,逼它继续修改。

如果 Stop 钩子跑测试失败了,Agent 会尝试修代码;修完后又准备退出,再次触发 Stop 钩子跑测试;如果还是失败,就会陷入 "修改 → 失败 → 再修 → 再失败" 的死循环。

为了解决这个问题,系统设计了一个关键环境变量 stop_hook_active:

  1. 识别重试状态 :Hook 脚本可以通过检查 stop_hook_active 变量,知道自己是不是因为上一次测试失败后的"第二次或第 N 次重试"被触发的。

  2. 熔断放弃:如果发现已经重试了多次(比如连续失败了 3 次),Hook 脚本就可以选择不再返回错误 code,而是直接"放弃/放行",避免 Agent 陷入死循环浪费大量 Token。

相关推荐
汤米粥7 小时前
2026年主流前端技术框架
vue·react·elementplus·tailwind css·angular‌·svelte‌·bootstrap‌
2601_967987371 天前
Next.js 文档
react·开发·全栈·文档·next.js
前进的李工2 天前
Agent 学习笔记 01:从工具调用到 ReAct 循环
笔记·学习·llm·agent·react·工具调用
Oiiouui2 天前
关于 Logto 登录系统的体验以及建议
javascript·安全·react
ZoeLandia2 天前
React11:数据请求与 TanStack Query
前端·react.js·typescript·react
漫步的猪4 天前
# 告别混乱与付费墙!Linkit:完全免费、本地优先的开源 AI 智能书签与收藏夹管理神器
人工智能·ai·开源·react·免费·书签·收藏夹
ZoeLandia7 天前
React03:useState 与不可变更新
前端·react.js·typescript·react
莫逸风8 天前
【毕设源码二期01】校园失物招领系统:从发布线索到核验交接
spring boot·毕业设计·课程设计·react
Shockang11 天前
TanStack Router 实战:URL 校验、Loader 与路由权限
typescript·react·路由·前端开发·tanstack router