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



Claude Code 的 Agent Loop(智能体标准执行循环) 本质上是一个基于 ReAct(Reasoning & Acting)模式的
while循环 。只要大模型输出中包含tool_use(工具调用请求),循环就会持续迭代;如果模型输出纯文本(不再调工具),则循环终止。一、 Agent Loop 的 5 大核心阶段
上下文组装(Context Assembly)
- 职责 :把系统的 Prompt、
CLAUDE.md配置文件、历史对话记录以及工具描述信息完整拼装成输入模型的上下文,并顺带运行 5 级压缩器,确保总体积不超过 200K Token 的预算限制。模型推理(Model Inference)
- 职责 :流式调用 Claude API,实时产出文本内容与工具调用请求(
tool_use)。工具分发(Tool Dispatch)
- 职责 :解析模型响应中返回的
tool_use blocks(工具调用块),并根据工具的特性将其分类为并行执行 或独占串行执行。权限守门(Permission Gate)
- 职责 :每一个工具调用在真正运行前,都必须严格通过 deny-first(默认拒绝) 的权限安全管道。如果被拒绝,则直接返回错误并不予执行。
工具执行(Tool Execution)
- 职责:通过权限检查的工具会在本地被实际执行,执行产生的返回结果会被追加到对话历史中,准备进入下一轮循环。
二、 循环的终止条件
整个循环会不断重复上述步骤,直到大模型返回的
stop_reason(停止原因)是end_turn(而不是tool_use),此时代表任务已由模型自主判断完成,循环正式结束。三、 落地到代码实现的"9 步 Pipeline"
在实际的底层代码架构中,这 5 个宏观阶段被进一步细化和拆解为了 9 步 Pipeline:
准备阶段(对应"上下文组装"):
Settings resolution(配置解析)
State init(状态初始化)
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)"时看到的:
在每一次循环的起点,客户端都会把整个上下文"推倒重来"组装一遍:
拿出最新的系统提示词(System Prompt)。
读入层级
CLAUDE.md文件。动态加载当前可用的工具描述。
把内存中累积的整段
history(对话历史)全部拿出来,如果太长就跑 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 大关键设计点
流式输出(Streaming)与实时 UI 渲染
系统通过底层
AsyncGenerator的yield机制,把大模型吐出的内容实时、逐字地推给前端 UI。这保证了用户在终端里看到的是丝滑的"打字机效果",而不是对着屏幕干等半天突然蹦出一大段话。
Token 实时计数与防截断重试
- 系统会对 Token 的使用进行实时监控。如果因为字数过多导致模型的输出被
max_tokens强行截断,系统会自动提升限制并最多重试 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模块被完整解析出来,它就会立即加入执行队列。按类型智能分类:工具进入队列后,会被严格划分为两大阵营:
concurrent-safe(并发安全工具) :例如Read(读文件)、Glob(查目录)、Grep(搜代码)、WebSearch(搜网页)等只读工具 ,允许同时并行跑。
exclusive(独占/有副作用工具) :例如Bash(跑命令)、Write(写文件)、StrReplace(改文件)等会修改系统状态的工具,必须串行跑。经典运行示例 :如果大模型在某一轮同时要求执行 3 个
Read+ 1 个Write,系统会让 3 个Read并行执行 ,而Write则会耐心地等这 3 个读操作全部完成后才开始跑。二、 工具池本身的 5 步组装(Tool Pool Assembly)
在工具能够被分发之前,系统必须先动态组装出当前轮次"允许使用"的工具大盘。这个组装过程分为 5 个步骤:
Base enumeration(基础枚举):列出系统所有的内置工具,最多可达 54 个。
Mode filtering(模式过滤):根据当前的运行模式,筛掉那些不适用的工具。
Deny pre-filtering(黑名单预过滤) :根据安全配置中的deny规则,把被禁用的工具提前移除。
MCP integration(MCP 整合):将外部连接的 MCP Server 所提供的扩展工具合并进来。
Deduplication(去重):对最终的工具列表进行去重处理,确保没有重复项。

Claude Code 的 "阶段四:权限守门(Permission Gate)" 理解为整个 Agent Loop 中最核心的"安全保卫与合规审查卡口"。当第三阶段把工具分发并排好队之后,任何工具在真正被系统执行之前,都必须强行通过这个严密的权限管道。
这一阶段的核心逻辑可以拆解为"Deny-First 过滤机制"、 "7 种信任光谱模式"以及"钩子拦截与隔离机制":
一、 核心审判规则:
deny-first(默认拒绝优先)权限管道对每一个工具调用的检查顺序是极其严格的阶梯式过滤:
第 1 步:先查
deny(黑名单)列表
- 只要在黑名单里匹配到了,直接拒绝执行,且无法被覆盖。
第 2 步:再查
allow(白名单)列表
- 如果在白名单里匹配到了,直接放行。
第 3 步:如果两边都没匹配到
- 系统会根据当前所处的权限模式来决定下一步该怎么做(例如弹窗问用户或由 AI 自动判断)。
二、 7 种权限模式形成的"信任光谱"
Claude Code 提供了从极度安全到完全放开的 7 种权限模式,满足不同场景的需求:
plan → default → acceptedEdits → auto → dontAsk → bypassPermissions
plan模式 :最严格的模式,只允许执行只读操作(如查看文件、搜索),绝对禁止任何修改。
default模式 :标准安全模式,如果遇到未授权的操作,会弹窗询问用户是否批准。
auto模式 :引入了 ML 分类器(机器学习模型) 来自动判断当前操作的安全性。
bypassPermissions模式 :完全绕过权限检查,专为开发调试设计,严禁在生产环境中开启。三、 另外两个非常关键的设计细节
Pre-tool Hooks(前置生命周期钩子二次拦截)
- 即使工具顺利通过了上述权限守门,系统还会触发我们在前面学过的 Pre-tool hooks。外部注册的脚本可以对参数进行二次校验、修改参数,甚至直接取消这次调用。
故障隔离(不影响同伙)
- 如果某一轮中有多个工具并行执行,某一个工具因为权限不足被拒绝,完全不会影响同一轮中其他并行工具的正常执行。

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 种条件来决定何时退出:
模型输出纯文本、不调工具:代表任务正常完成。
达到
max_turns配置上限:防止死循环,触发强制停止。PNG
上下文溢出且压缩无效:对话太多导致 5 级压缩也塞不下,触发异常终止。
PNG
Hook 返回终止信号:受到外部钩子的干预而停止。
PNG
用户手动 abort:用户在前端主动中断(如按下快捷键)。

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



一、 ~/.claude/projects/ 下的 .jsonl 文件代表什么?
一个
.jsonl文件就完整代表了一个 Session(会话)的记录。
存储位置与结构 :每个会话在磁盘上通常对应一个
.jsonl文件(存放在~/.claude/projects/<id>/sessions/目录下)。追加式写入 :这个文件采用了 JSON Lines 格式。在会话进行时,用户的每一条 Prompt、大模型的回复、每个工具的调用结果、以及每次修改文件前的 Checkpoint 快照索引,都会作为一行独立的 JSON 对象实时追加到文件末尾。
二、 每次修改文件前的 Checkpoint 存在哪?是直接存在 .jsonl 里吗?
不是直接把整个文件的备份塞进
.jsonl里,而是"元数据索引"与"文件实体"相结合:
.jsonl记录的是"账本(索引)":文件中会记录某一个时间点、某一条指令执行前,哪些文件即将被修改,并生成对应的快照指针/索引。实际的文件副本存储在磁盘目录中 :每次用户发送 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 日常工作时的默认主路径 。它的核心特点是:"边走边看,边流边跑"------在模型的流式响应还没结束时,工具就已经开始并发执行了。
它的具体执行步骤如下:
流中解析 :在模型吐字(Streaming)的过程中,实时解析出完整的
tool_use块,并将其加入执行队列。智能分类:根据工具的特性进行分类:
concurrent-safe(并发安全) :如Read(读文件)、Glob(查目录)、Grep(搜代码)等纯只读工具。
exclusive(独占/串行) :如Bash(跑命令)、Write(写文件)等具有副作用、会修改系统状态的工具。混合调度 :
concurrent-safe的只读工具允许多个同时并行执行 ,而exclusive的有副作用工具则严格串行执行。汇总结果:所有工具跑完后统一汇总结果,塞回上下文。
二、 兜底路径:
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 个错误恢复状态(隐藏在主干道之下的"安全网")
这是这张图新补充的底层容错机制,它们在主路径发生异常时被触发:
Max tokens 恢复 :当模型输出被截断时,自动提升
max_output_tokens并重试(最多 3 次)。Reactive compaction(反应式压缩) :对应之前提到的
COMPACTING状态,当检测到上下文快要爆掉时,触发紧急压缩然后再重试。Streaming fallback(流式降级):当主模型的流式传输(Streaming)挂掉或网络异常时,自动切到我们前面学过的保守降级路径。
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 个核心阶段
Gather Context(收集上下文) → Think
做什么:在行动之前,先去摸清情况、搜集情报。
典型动作 :调用
Read读取文件内容,调用Grep搜索函数引用,或调用Glob查找目录结构。
Take Action(执行动作) → Act
做什么:基于收集到的信息进行推理分析,并做出实质性的改动或执行命令。
典型动作 :分析出 bug 原因后,调用
StrReplace修改代码,或调用Bash跑命令、写文件。
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 代码:
如果是全局安装的 CLI(如通过 npm / bun):
- 官方打包发布到 npm 的二进制或 Node 包通常是经过 Bundler(如 esbuild/rollup)打包打包压缩后的 JavaScript 文件 (如
dist/index.js或cli.js),原始的 TypeScript 源码文件(query.ts)在 npm 安装包中默认是不直接存在的。如果是安装了 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 个核心分析维度
工具集成(Tool Integration)
探讨重点 :流式输入模式如何支持"边解析边执行"的工具调用(如前面学过的
StreamingToolExecutor)。原理 :模型无需等待整句回复输出完毕,一旦生成完整的
tool_use结构,工具就可以立即入队并并发/并行跑起来,大大降低了等待延迟。实时反馈(Real-time Feedback)
探讨重点 :系统如何通过流式
yield机制,实现 UX 层面与控制流上的实时响应。原理 :不仅能在终端实现"逐字打字机"式的打字效果,还能在发生错误或触发权限询问(Permission Gate)时即时拦截并暂停,无需浪费冗余的 Token 和生成时间。
上下文持久化(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 种渐进式的技术方案:
CLAUDE.md声明验证命令(提示词约束层)
怎么做 :在
CLAUDE.md里写明规则,告诉 Agent 每次改完代码后必须运行npm test或pytest。特点:依赖大模型"自觉"去遵守提示词中的规则。
Skills封装验证循环(工作流/工具链层)
怎么做 :把"修改代码 → 跑测试 → 报错修复 → 再测试"的完整过程打包写成一个 Skill(如
/verify或/code-review)。特点:将验证流程标准化为可重复调用的链式动作。
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:
识别重试状态 :Hook 脚本可以通过检查
stop_hook_active变量,知道自己是不是因为上一次测试失败后的"第二次或第 N 次重试"被触发的。熔断放弃:如果发现已经重试了多次(比如连续失败了 3 次),Hook 脚本就可以选择不再返回错误 code,而是直接"放弃/放行",避免 Agent 陷入死循环浪费大量 Token。




