首页 › tutorials › 循环工程:造自驱到交付的Claude Agent

概述

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

前置条件

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

很多人对自动化的第一反应是 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 会开始遗忘早期约束、重复犯错。每轮从干净的窗口出发,只读取"上一轮的产物 + 进度记录",反而更稳。

#!/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:

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

.claude/hooks/quality-gate.sh:

#!/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 管"总成本"。

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

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

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

把质量门同时接到两个地方: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,供事后审计和回滚。
// .loop/state.json — 任务记忆
{
  "goal": "让 npm test 全绿",
  "remaining_failures": ["auth.test.ts", "cache.test.ts"],
  "last_action": "重构了 auth.ts 的 mock",
  "iteration": 3
}

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

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,要求明确且可验证:

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

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

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

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 只动了业务代码没动测试期望。

常见问题与解决方案

延伸阅读

参考来源

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

训练营适用性评估

[!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学通信知识星球海报-扫码加入