<!-- title: 循环工程:造自驱到交付的Claude Agent -->

## 概述

本教程聚焦一个被反复问到的难题:**如何让 Claude Code Agent 不在你眨眼时停下,而是自己跑到任务真正完成**。你将掌握 2026 年社区称作"循环工程(Loop Engineering)"的那套设计模式——把 `-p` 无头模式、`Stop` 钩子反馈回路、任务预算(Task Budgets)与跨迭代记忆拼成一个收敛、有界、可审计的自驱循环。适合已写过几个 `-p` 脚本、想让 Claude 真正"值夜班"的开发者,预计 90–120 分钟读完并可复现。

## 前置条件

- Claude Code CLI 可运行,版本支持 `--print`/`-p`、`--resume`、`--output-format`、`--max-turns`(建议 2026 春季之后的版本)
- 一个有测试套件的小项目(Node/Python/Go 皆可,本文用 Node `npm test` 演示)
- 基础的 Bash 与 `jq` 知识,能看懂 NDJSON 流
- 已读过 Claude Code 官方 hooks 文档(`code.claude.com/docs/en/hooks`)最佳,没读过也能跟上

## 一、为什么"跑一次"远远不够:三种循环的区别

很多人对自动化的第一反应是 `claude -p "修好这个 bug"`。这只跑**一轮 agent loop**:Claude 收到提示 → 评估 → 调用工具 → 观察结果 → 决定停下。问题在于,Claude 决定"停下"的瞬间,任务往往**并没有真正完成**——测试还红着、lint 还有 3 个警告、它只是"觉得做完了"。

循环工程的核心洞察是:**"完成"是一个可验证的条件,而不是 Claude 的主观判断**。围绕这个判断,衍生出三种性质完全不同的循环,必须先分清:

| 循环类型 | 驱动者 | 上下文 | 典型用途 |
|---|---|---|---|
| **单轮 agent loop** | Claude 内部 | 单次会话 | 一次性问答、小改动 |
| **Stop-hook 反馈循环** | 钩子拒绝停止 | 同一会话内累积 | 强制 Claude 做完质检再交付 |
| **外部 fresh-context 循环(Ralph)** | Bash `while` | 每轮全新进程 | 长周期迭代,规避上下文衰减 |

90% 的"它怎么自己停了"的困惑,源于把这三种混为一谈。本教程的目标,是把三者组合成一个**自驱到交付**的系统。

## 二、自驱循环的五个构建块

### 块 1:循环驱动器——fresh-context 还是续跑?

外部循环有两种范式,选错会导致任务永远不收敛。

**范式 A:fresh-context 循环(社区称 Ralph Loop)。** 一个 Bash `while`,每轮 spawn 一个全新的 `claude -p` 进程,把目标文件喂进去。核心思想是**对抗上下文衰减**:一个跑了 30 轮的会话,上下文里堆满历史 diff,Claude 会开始遗忘早期约束、重复犯错。每轮从干净的窗口出发,只读取"上一轮的产物 + 进度记录",反而更稳。

```bash
#!/usr/bin/env bash
# loop-driver.sh — fresh-context self-driving loop
set -euo pipefail

GOAL_FILE="$1"               # e.g. goals/fix-tests.md
MAX_ITERS="${MAX_ITERS:-12}"  # hard ceiling, see 块3

for i in $(seq 1 "$MAX_ITERS"); do
  echo "=== iteration $i / $MAX_ITERS ==="

  # Each call = a fresh context window, same goal, reads its own prior work
  claude -p \
    --permission-mode acceptEdits \
    --max-turns 40 \
    "$(cat "$GOAL_FILE")" \
    > "logs/iter-$i.json" 2>&1 || true

  # Quality gate (see 块4) decides convergence
  if bash .claude/hooks/quality-gate.sh --check; then
    echo "✅ convergence reached at iteration $i"
    break
  fi
done
```

**范式 B:`--resume` 续跑循环。** 同一 session 反复 `claude -p -r <session_id>`,保留全部历史。适合**累积式探索**(比如逐步重构,每一步依赖前一步的现场),但扛不住长任务——上下文会膨胀到拖慢、甚至触发压缩。判断口诀:**任务每步互相独立、可重放 → fresh-context;任务强依赖同一现场、不可重放 → resume**。

### 块 2:收敛条件——用 Stop 钩子拒绝"提前交卷"

`Stop` 钩子在 Claude 每轮结束、准备停下时触发。它能做一件关键的事:**用 `decision: "block"` 拒绝停止,并把原因注入回上下文**,逼 Claude 继续。

`.claude/settings.json`:

```json
{
  "hooks": {
    "Stop": [
      {
        "matcher": "",
        "hooks": [
          { "type": "command", "command": "bash .claude/hooks/quality-gate.sh" }
        ]
      }
    ]
  }
}
```

`.claude/hooks/quality-gate.sh`:

```bash
#!/usr/bin/env bash
# Read JSON from stdin, decide whether Claude may stop
input="$(cat)"
stop_active=$(echo "$input" | jq -r '.stop_hook_active // false')

# Run the real check: are tests green?
if npm test --silent >/dev/null 2>&1; then
  echo '{"decision":"approve"}'   # allow stop
  exit 0
fi

# Block + inject a concrete, verifiable reason back into context
# Claude will see this and continue working
echo '{"decision":"block","reason":"测试套件仍有失败。运行 npm test,逐个修复,直到全绿才能停下。"}'
exit 0
```

**两个必须知道的护栏(踩过坑的人都懂):**

1. **官方的 8 次熔断**:Claude Code 检测到 Stop 钩子连续 8 次 block 却没有实质进展,会**自动 override 钩子强制停止**——防止你写出一个永动机。所以你的收敛条件必须是**真正可达成**的,不能是"达到 100% 覆盖率"这种无法收敛的目标。
2. **`stop_hook_active` 字段防死循环**:当 Claude 因 block 而再次尝试停止时,输入里会带上 `stop_hook_active: true`。成熟的脚本会检查它,在连续 N 次相同失败后主动放行,避免被 GitHub issue #2956 那样的卡死困住。

### 块 3:预算护栏——让循环有"天花板"

没有预算的自驱循环,等于一张空白支票。2026 年有三层预算控制,建议全部配上:

**层 1:单轮轮数上限 `--max-turns`。** 限制一次 `claude -p` 内 agent loop 最多转多少圈,防止某一轮卡在工具调用里空转。超限时的动作由 `--max-turns-action`(继续/停止)控制,默认安全停止。

**层 2:会话级 token 上限。** 通过环境变量(如 `CLAUDE_CODE_MAX_OUTPUT_TOKENS`)或配置项给单次会话封顶。

**层 3:任务预算(Task Budgets)。** 这是 Opus 4.7 之后引入的**建议性(advisory)整会话 token 预算**。一个反直觉但官方明确警告的陷阱:**预算太小会让 Claude 产生"拒绝式"行为**——它看到一个紧巴巴的预算,会倾向于说"这个任务我做不完"而直接放弃。所以预算要给足,再用外部循环的 `MAX_ITERS` 做硬封顶,二者分工:Task Budget 管"单轮质量",`MAX_ITERS` 管"总成本"。

```bash
# 组合预算:每轮质量预算 + 总迭代硬封顶
export MAX_ITERS=12           # 硬天花板,块1已用
export CLAUDE_CODE_MAX_OUTPUT_TOKENS=60000   # 单会话上限
```

### 块 4:质量门——"完成"必须是机器可判定的

收敛条件的本质是一道**质量门(Quality Gate)**。最好的质量门是**项目已有的 CI 命令**,因为它是客观、可复现的:

- 测试套件全绿 → `npm test && npm run test:e2e`
- 类型零错误 → `tsc --noEmit`
- Lint 零警告 → `eslint . --max-warnings 0`
- 构建产物可产出 → `npm run build`

把质量门同时接到两个地方:**Stop 钩子(块2)**,让 Claude 每次想停都被拦;**外部循环(块1)** 的 `--check`,让循环整体知道何时可以 break。注意区分:`PostToolUse` 钩子适合"每次改文件后跑单测做快速反馈",而 Stop 钩子适合"整轮交付前的终检"。两者配合,反馈更密。

### 块 5:跨迭代状态持久化——让它"记得自己做到哪了"

fresh-context 每轮都是失忆的新生,必须靠**外部记忆**接力。三类持久化要分开用:

1. **`CLAUDE.md`(项目记忆)**:写稳定不变的全局约束——"测试必须用 `pnpm test`""禁止动 `legacy/` 目录"。每轮自动加载。
2. **进度 JSON(任务记忆)**:`.loop/state.json`,记录已完成/待办项,脚本在每轮 prompt 里拼上 `$(cat .loop/state.json)`。
3. **产物 + 日志(证据链)**:`logs/iter-N.json`(每轮的 stream-json 输出)、diff,供事后审计和回滚。

```json
// .loop/state.json — 任务记忆
{
  "goal": "让 npm test 全绿",
  "remaining_failures": ["auth.test.ts", "cache.test.ts"],
  "last_action": "重构了 auth.ts 的 mock",
  "iteration": 3
}
```

## 三、整体架构:自驱循环数据流

```mermaid
flowchart TD
    Start([循环启动]) --> Read[读取 GOAL + CLAUDE.md + state.json]
    Read --> Spawn["claude -p --max-turns 40<br/>fresh context 每轮"]
    Spawn --> AgentLoop[Claude 内部 agent loop<br/>评估→工具→观察]
    AgentLoop --> WantStop{Claude 想停下?}
    WantStop -->|是| StopHook["Stop 钩子<br/>quality-gate.sh"]
    StopHook --> Gate{质量门通过?<br/>npm test 全绿}
    Gate -->|否| Block["decision: block<br/>原因注入回上下文"]
    Block -->|8次熔断保护| AgentLoop
    Gate -->|是| Approve["decision: approve<br/>允许停止"]
    Approve --> Persist[写 state.json + 产物 + 日志]
    Persist --> BudgetCheck{预算/迭代数耗尽?}
    BudgetCheck -->|否| Read
    BudgetCheck -->|是| Done([循环结束 交付])
    WantStop -->|否| AgentLoop
```

## 四、实战案例:一个"值夜班修测试"的自驱机器人

目标:给你一个有 4 个失败测试的项目,机器人自己循环修到全绿或耗尽预算为止。

**步骤 1** —— 写目标文件 `goals/fix-tests.md`,要求明确且可验证:

```markdown
# 目标
让 `npm test` 全绿。逐个修复失败用例,不要跳过、不要改测试期望值。
完成标准:`npm test` 退出码为 0。
当前进度见 .loop/state.json。每修一个,更新它。
```

**步骤 2** —— 落地块2 的 Stop 钩子和块4 的质量门(代码见上文,记得 `chmod +x .claude/hooks/quality-gate.sh`)。

**步骤 3** —— 启动外部循环:

```bash
mkdir -p logs .loop
echo '{"goal":"fix-tests","remaining_failures":["unknown"],"iteration":0}' > .loop/state.json
MAX_ITERS=10 bash loop-driver.sh goals/fix-tests.md
```

**预期结果**:终端逐行打印 `iteration N`,每轮 Claude 攻克 1–2 个用例,`state.json` 的 `remaining_failures` 递减。约 3–6 轮后出现 `✅ convergence reached`,或 10 轮后因预算停手。`logs/` 下留下每轮完整决策日志可审计。

**步骤 4** —— 验证交付:`npm test` 退出码为 0,`git diff` 只动了业务代码没动测试期望。

### 常见问题与解决方案

- **循环转了几轮后开始原地打转**:典型的上下文衰减或目标不可收敛。把 fresh-context 范式 A 打开,并在 `state.json` 里记录"已尝试且失败的方法",prompt 里显式告诉 Claude"别重复这些"。
- **Stop 钩子被官方 8 次熔断 override,循环提前停**:说明你的质量门 Claude 达不到。要么放宽目标、要么换更聪明的策略(如先让它只修一个最简单的用例),别和熔断硬刚。
- **预算设太小,Claude 直接"摆烂"说做不完**:这是 Task Budgets 的 refusal-like 行为。把单轮预算调大,改用 `MAX_ITERS` 控总成本。
- **`stop_hook_active` 死循环(卡在 issue #2956)**:脚本里加计数器,连续 3 次相同失败就 `decision: approve` 放行,让外部循环接手下一轮。
- **改了测试期望值来"骗"过质量门**:在 `CLAUDE.md` 明令禁止,并在质量门里加 `git diff --stat 'tests/*'` 检测,若测试文件被改则判失败。

## 延伸阅读

- Claude Code 官方 hooks 参考:`code.claude.com/docs/en/hooks` 与 hooks 指南 `code.claude.com/docs/en/hooks-guide`(后者含 8 次熔断的官方说明)
- agent loop 工作原理:`code.claude.com/docs/en/agent-sdk/agent-loop`
- Ralph Loop 官方插件:`claude.com/plugins/ralph-loop`
- Task Budgets 官方文档:`platform.claude.com/docs/en/build-with-claude/task-budgets`

## 参考来源

1. [Loop Engineering: How to Design Coding Agent Loops That Run While You Sleep (2026 Guide)](https://explainx.ai/blog/loop-engineering-coding-agents-claude-code-guide-2026) — 循环工程的五构建块与 2026 社区共识,本文结构主干来源
2. [How the agent loop works — Claude Code Docs](https://code.claude.com/docs/en/agent-sdk/agent-loop) — 官方对"收提示→评估→工具→观察→停/续"单轮循环的权威定义
3. [Automate actions with hooks — Claude Code Docs](https://code.claude.com/docs/en/hooks-guide) — 官方 Stop 钩子 `decision:block` 反馈回路与"连续 8 次无进展自动 override"熔断机制
4. [Task budgets — Claude API Docs](https://platform.claude.com/docs/en/build-with-claude/task-budgets) — 任务预算(advisory token ceiling)语义,及"预算过小触发 refusal-like 行为"的官方警告
5. [Fresh Context Pattern (Ralph Loop) — DeepWiki](https://deepwiki.com/FlorianBruniaux/claude-code-ultimate-guide/7.6-fresh-context-pattern-(ralph-loop)) — fresh-context 外部循环对抗上下文衰减的实现细节,块1 范式 A 依据

## 训练营适用性评估

- **最适合**:中阶开发者训练营、DevOps/平台工程方向、AI 工程化实战营。学员需先掌握基础 `-p` 用法与一个真实带测试的项目。
- **教学时长**:理论讲授 30 分钟 + 五构建块动手 60 分钟 + 完整案例复现 30–60 分钟,合计约 2.5 小时(含调试踩坑时间)。
- **不适合**:零基础入门营(需先理解 hooks 与无头模式);纯演示型短期分享(本主题价值在亲手让循环收敛,只看不练收益有限)。
- **延伸作业建议**:让学员把质量门从"测试全绿"换成"安全扫描零高危",体会收敛条件设计对循环成败的决定性影响。

> [!tip] 51学通信知识星球
> **星主：爱卫生**（微信：gprshome201101）——公众号「51学通信」创办者，19 年通信行业运维与讲师经验，著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书（各大电商平台有售）。
> **星球定位**：深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域，主打运维岗位与信令分析。
> **当前规模**：5200 篇主题 / 990 个回答，198 元/年。
> **加入福利**：加入星球后请加爱卫生微信，拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题，另有群内分享。
> **AI 加持**：51学通信知识星球已作为 skill 接入 WorkBuddy，使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例，做总结、找文档和 PPT 等场景全覆盖。

![51学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
