把 Claude Code 的工作引擎拆开看,整套 agentic loop 由”收集上下文—采取行动—验证结果”三个阶段构成,并且会反复迭代直到任务完成或被用户打断。三个阶段并非线性流水线,而是一条”工具结果驱动下一轮决策”的反馈链,理解这条闭环结构是判断 Claude 在长任务里为什么会突然跑偏、又为什么能自我修正的关键。Anthropic 公开文档把这套机制列为 Claude Agent SDK 的核心设计模式。
(项目背景:在排查一个分布式链路里偶发的 502 时,一开始把任务写成”修一下 502″,模型只能给到泛泛的猜测。拆成”先读上游网关日志 → 改重试策略 → 跑压测验证”三步后,循环才真正被激活,几轮之内定位到根因。)
一、三阶段总览
“收集上下文—采取行动—验证结果”是 Claude Code 文档原话,三个阶段随时可被用户中断并重新引导。
| 阶段 | 模型在做什么 | 典型工具 | 输出 |
|---|---|---|---|
| 收集上下文 | 理解当前项目状态与任务边界 | Read、Grep、Glob、Bash | 文件树、符号表、运行日志 |
| 采取行动 | 决定下一步动作并执行 | Edit、Write、Bash | 文件修改、命令执行 |
| 验证结果 | 评估动作是否达成目标 | Bash、Read | 测试输出、类型检查结果 |
三个阶段并不按固定顺序切分,而是按工具结果在上下文窗口里实时拼接。一次”问代码库”任务可能只走第一个阶段;一次 bug 修复会在三个阶段之间反复横跳几十次;一次大规模重构则会把第三个阶段拉得很长。
二、收集上下文:循环的起点
Claude 拿到任务后并不立即行动,而是先调用工具建立”项目地图”。这一步会读 CLAUDE.md、auto memory、项目目录树、Git 状态,以及与任务直接相关的源文件。官方文档明确指出,工具返回的每一个结果都会进入上下文窗口,成为下一轮决策的依据。
// 收集上下文的典型工具组合
1. Bash -> git status -sb // 看未提交变更
2. Read -> src/services/auth.ts // 读目标文件
3. Grep -> 'TODO' src/ // 搜索遗留标记
4. Read -> package.json // 确认依赖与脚本
这一步决定了后续行动的方向。在小问题上 Claude 也许只读一两个文件就够;在跨服务改动中,它会把上下游几十个文件全读一遍,再决定哪一段需要改。
2.1 上下文窗口的可见反馈
每条工具调用的输入输出都会占用上下文窗口。窗口快满时,Claude Code 会自动 compact(压缩)历史消息,把靠前的内容替换成摘要。用户也可以手动 /compact 提前腾空间。理解这一机制能解释”为什么模型突然忘了开头交代的命名约定”——它们被压缩掉了。
上下文窗口组成(近似比例)
+----------------------------+
| 系统提示 + CLAUDE.md |
| 当前任务与最近工具结果 |
| 历史消息(可被压缩) |
+----------------------------+
三、采取行动:闭环里的”做”
在第二步里,Claude 根据上一阶段的信息选定一个工具,执行,再把结果放回上下文窗口。关键约束是:一次循环只发生一个动作,不存在”先把整个计划写完再依次执行”。
| 工具 | 类别 | 典型用途 |
|---|---|---|
| Read | 文件操作 | 读文件拿正文 |
| Edit / Write | 文件操作 | 改写、创建 |
| Bash | 执行 | 跑测试、构建、git 命令 |
| Grep / Glob | 搜索 | 全局文本或文件名匹配 |
| WebFetch | 网络 | 抓取外部文档 |
| Task | 编排 | 派生 subagent 并行处理 |
工具选择由模型根据上文推断,没有预编程的”先 A 后 B”流程。这种”自由选择工具”是 agent 与传统 chat 的分水岭——后者只能一次性输出文本,前者能反复操作真实环境。
3.1 行动的反馈粒度
工具结果以原始字节流的形式回到上下文,包括 Read 出来的整段文件、Bash 输出的全部 stdout/stderr、类型错误的完整堆栈。结果越详细,下一轮决策越准;反之,如果某个工具只回传”成功”两个字符,模型就缺少可修正的依据。
# 反例:把命令写成只输出状态
git pull && echo "ok"
# 正例:保留 stderr 便于诊断
git pull 2>&1
四、验证结果:闭环里的”测”
行动之后必须验证。验证的常见手段是跑测试、检查类型、读取命令输出,对比预期与实际。验证失败时,模型不会停下,而是把失败信息重新送进上下文,进入下一轮循环。
闭环反馈的一个完整例子
[用户] 修复 src/auth.ts 的 null 报错
[模型] Read src/auth.ts -> 上下文
[模型] Edit 增补 null 判断 -> 行动
[模型] Bash 跑 npm test -> 验证
[结果] 用例 #7 失败
[模型] Read 测试输出片段 -> 上下文
[模型] Edit 修正判断条件 -> 行动
[模型] Bash 重跑 npm test -> 验证
[结果] 全部通过,循环结束
4.1 验证失败的自动回退
验证不通过不会抛错退出,而是把”我刚才尝试过什么、错在哪里”沉淀到上下文,下一轮循环继续修正。这意味着只要给得出”完成信号”(比如”测试全部通过”),Claude 就能自己跑到结束;如果任务本身没有清晰的验收点,循环很可能在原地打转。
4.2 用户随时可以打断
循环再长,用户随时可介入。Esc 键停止当前动作,追加上下文或换方向即可;/rewind 可以回退到之前的检查点并尝试新方案。这种”人在回路”的设计把 agent 的自主性收敛在可控范围内。
五、闭环设计给使用者的启示
把这三个阶段想清楚,能直接优化提问方式。
- 任务要带可观察的完成信号(”测试全绿””构建无错误””接口 200″);
- 命令保留 stderr,工具结果越详细模型越准;
- 上下文窗口快满时主动 /compact 或 /clear;
- 长链路任务用 subagent 并行处理,避免主上下文被噪声淹没;
- 同一问题被纠正超过两次时,清空上下文用更具体的 prompt 重启。
把任务描述和工具结果都设计成”可被循环消化”的形态,Claude 才能在三个阶段之间稳定地穿梭到底。
常见问题(FAQ)
Q1:为什么 Claude 有时反复做同样的尝试?
通常是因为工具结果没回传到上下文,或完成信号太模糊,循环没有可收敛的判定点。
Q2:如何让循环更快结束?
给出明确的完成条件(”测试通过””文件存在””接口返回 X”),避免开放式指令。
Q3:subagent 在循环里扮演什么角色?
subagent 在独立上下文窗口里执行子任务,把摘要回传主循环,是减少主上下文噪声的常用手段。