要把 Agent Loop 推向真正的 unattended run,关键不是让它跑得更久,而是把验证检查变成”不通过就不能停”的硬关卡。三类方案在工程上各有侧重:shell 测试闸、判官式复审、目标驱动循环。它们在设置成本、人工介入频率与适用规模上形成清晰梯度,按团队规模与任务可验证性选取即可。
一、为什么验证必须内置进循环
放任 Agent Loop 自由停下的最大风险是”模型自报完成”。模型对自己产出的信心与实际正确率没有强相关,特别是在测试稀疏、需求模糊的代码区。即便加了步数上限,没有外部验证的循环也只是”撞墙即停”,不是”达标才停”。
把验证放进循环等于把”停下”这件事外包给一个独立于生成器的判断者。即便生成器乐观,验证仍按客观命令(例如 pytest、go test、curl 业务接口)执行,失败就把堆栈与失败用例作为反馈塞回下一轮。停下来的条件第一次变成可观测的客观信号,而不是生成器的主观陈述。
二、方案一:Shell 命令闸(verify-loop.sh)
起步阶段的方案是给循环配一个”闸门脚本”,每次生成器完成后跑一次。脚本的退出码就是通过与否,循环据此决定继续或停止。设置要点:
- 选定真正能反映任务完成的命令(测试套件、构建、smoke run);
- 把命令包装成 verify.sh,写成可被任意子代理或主代理复用的标准入口;
- 给循环加最大步数与可选的成本上限(verify-loop.sh –max-cost USD);
- 默认先跑一次”红起步”校验,确认闸门在未改代码上确实失败。
#!/usr/bin/env bash
set -euo pipefail
pytest -q --maxfail=1 || exit 1
go vet ./... || exit 1
curl -fsS http://localhost:8080/healthz >/dev/null || exit 1
echo "GATE PASS"
优势是设置成本极低,几乎任何 CI 都能用。劣势是只验证脚本里写明的内容,未写明的一律不管。在多角度需求或主观产物上容易”绿灯但其实是错的”。
三、方案二:判官式复审(scripts/judge-check.sh)
对正确性、安全性或主观产物,仅靠 shell 闸不够,需要一个独立审稿环节。判官式复审的做法是再起一个只读权限的子代理,让它把生成器产出的 diff 逐行读一遍,按 rubric.md 打分,失败时给出具体意见反馈给主循环。
- 编写 rubric.md,把”必须满足的检查点”逐条列成可枚举的项;
- 把 judge-check.sh 与 verify.sh 串接,让判官只在 shell 闸通过后再跑(一次成功一审);
- 判官必须运行在独立的上下文窗口,不能复用生成器的会话;
- 主观产物可让判官读取渲染后的图片,对照 rubric 给出意见。
#!/usr/bin/env bash
set -euo pipefail
# 仅在 shell 闸通过后才调用
claude --print --model claude-opus-4-5 \
--system-prompt "你是独立判官,按 rubric.md 逐条核对下方 diff" \
<(git --no-pager diff HEAD) || exit 1
这一方案设置成本比 shell 闸高一档:需要写 rubric、调试判官提示词、设计失败反馈的格式。回报是抓到了 shell 闸完全看不见的”测试通过了但漏了某条业务需求”。
四、方案三:目标驱动循环(goal-based loop)
对自主跑要求高的场景,把”何时停”这件事整体交给一个评估器。Agent Loop 在每轮结束后,把当前产出传给评估器;评估器根据目标陈述(例如”所有 API 鉴权检查在路由层都生效”)返回结构化结论,包含通过/失败、证据、下一轮应改的方向。
- 把目标陈述写成可被独立验证的形式,避免”把代码改好”这种含糊措辞;
- 写一个 evaluator 函数:输入是工作目录状态,输出是结构化 JSON;
- 循环的停止条件改成”evaluator.pass == true”或”达到步数/成本上限”;
- 把 evaluator 的调用结果落盘,便于事后审计与改进循环本身。
def evaluate_auth(state):
routes = list_routes(state)
missing = [r for r in routes if not has_auth_check(r)]
return {
"pass": len(missing) == 0,
"missing": missing,
"evidence": [r.path for r in missing],
}
这种方案在设置成本上明显更高:评估器本身需要写代码或配置逻辑、需要测试其正确性。优势是给”自主跑”提供了最严密的护栏,适合无人值守过夜运行。步数上限与成本上限仍然是必须的硬护栏,缺一不可。
五、三种方案的横向比较
| 维度 | Shell 闸 | 判官复审 | 目标驱动循环 |
|---|---|---|---|
| 设置成本 | 低(一段 shell) | 中(rubric + 判官提示) | 高(evaluator + 单元测试) |
| 抓错范围 | 写明的命令 | 主观/语义/需求覆盖 | 任意可观测目标 |
| 人工介入 | 闸门失灵时介入 | rubric 改动时介入 | 评估器改写时介入 |
| 适用任务 | 测试覆盖完善的代码改动 | 涉及业务语义、UI、安全 | 无人值守、大规模并行 |
| 风险 | 绿灯即放过 | 判官与生成器同模型时可靠性下降 | evaluator 自身缺陷导致误判 |
六、混合配置与升级路径
实际工程里三类很少单独使用,更多是”先闸再判”或”先评再判”的组合。最稳的升级路径是:先用 Shell 闸让循环能跑起来,观察哪些任务”绿灯了但不该绿”,针对这些任务加判官;等判官稳定的覆盖面够广,再把停止条件整体迁到目标驱动循环。
- 起步阶段只配 Shell 闸,验证循环框架本身跑得通;
- 跑出 10–20 个任务后统计误判,把反复误判的场景拆出判官;
- 等判官覆盖 80% 以上场景,把停止条件改为”evaluator.pass == true”;
- 长期运行时把步数/成本上限、暂停恢复、trace 日志一起加上。
升级的判断标准不是”用了哪个最复杂的方案”,而是”误判率是否低于团队可接受阈值”。每升一档都要重新跑回归任务集,避免评估器自身的变化引入新偏差。
七、避免”自欺式绿灯”
自主循环最隐蔽的失败是验证自身出问题。常见三类:
- 测试本身写错:测试在错误条件下也通过,需要在加循环之前先红起步一次;
- 判官与生成器同模型:模型对自己的产出有偏好,判官应该用独立会话甚至独立模型;
- 评估器目标过宽:写”代码质量好”等于没写,要写”所有路由都覆盖了鉴权检查”。
把这三类写进循环的事前 checklist,能把”看起来通过实则无效”的事故压在最低水平。
常见问题(FAQ)
Q1:Shell 闸够用吗?
对测试覆盖完善的代码改动够用;对业务语义、UI 效果或安全要求,必须叠加判官或目标驱动。
Q2:判官用与生成器不同的模型有必要吗?
有必要。判官独立上下文是底线,独立模型能进一步削弱”自我偏好”,提升判官的稳定性。
Q3:目标驱动循环的 evaluator 怎么写测试?
准备一组已知结果的回归集,跑 evaluator 比对结论与已知结果,覆盖率与单元测试同等重要。