跳到主要内容

有界 Agent 循环

工具循环(Tool Loop)让模型能够观察结果、选择下一步行动并持续迭代。这对调试和多步骤任务非常有用,但风险在于:一个错误的假设可能引发连锁反应,导致大量无效写入、反复重试以及上下文窗口的迅速膨胀。

核心原则很简单:模型只负责提议,Harness(执行框架)决定什么可以执行,以及结果是否算作成功。

下文用同一个任务把这条原则讲到底。假设你让 Agent 修好一个失败的单元测试 test_parse_date,Harness 给它 20 步、15 分钟,只允许改 src/ 和 tests/ 两个目录。

常见的循环形态​

模式适用场景典型失败模式
会话内工具循环交互式排查上下文被旧日志和重复猜测填满
新上下文任务循环跨越多个模型窗口的长任务任务台账与代码仓库状态脱节
实现-验证循环带有有效测试的修复工作Agent 只满足弱测试,而非真实业务契约
评估者-优化器循环重复起草或可量化调优执行者与评估者存在相同的盲点
基准测试循环优化单一标量指标将噪声误判为性能提升

修 test_parse_date 属于实现-验证循环。五种形态的差别在于什么时候停:同一个模型套哪种循环,都要先定好停止条件。

Harness 必须掌控的部分​

预算与停止机制​

在运行前设定硬性限制:步数、墙钟时间、模型调用次数、工具使用次数、花费,以及允许访问的文件或服务范围。自己付 API 费用时,花费上限最该先设:Claude Code 的无人值守模式可以用 --max-budget-usd 5.00 限定这次运行最多花 5 美元,用 --max-turns 限定回合数(命令行参考)。这个花费按客户端估算,可能略超上限,所以要留出余量。当达到限制、用户取消、关键证据缺失,或重复失败表明当前策略无效时,必须立即停止。

重复调用断路器(Breaker)就是为最后一种情况准备的:同一个调用连续失败 N 次,循环就自动停下。在修测试的例子里,假设 N 设为 3:模型第 1、2、3 步都原样运行 pytest tests/test_dates.py,三次都报同一个错误,断路器在第 3 步后停下循环,把任务交还给人,而不是把剩下的 17 步耗在同一条命令上。阈值要匹配操作类型:重试读取文件,和重试支付或部署,风险完全不同。

工具校验​

在模型外部验证工具名称、参数 Schema、路径、权限及副作用;参数数组、路径解析这些写法见工具契约。模型的输出如果因为长度上限被截断,其中的工具调用一律不执行:半截 JSON 可能碰巧通过校验,执行的却是错误的操作。Pi 的做法是把这些调用全部标为失败,让模型重新提交(agent-loop.ts)。

在例子里,如果模型提议修改 config/settings.py,路径检查发现它不在 src/ 和 tests/ 之内,就拒绝这次调用,把拒绝原因记进状态,模型下一步只能换个做法。

结果验证​

模型的最后一句话不是完成信号。验证器可以是测试套件、Schema 检查、产物精确比对、浏览器断言或人工审查。验证逻辑应针对预期行为,且具备足够的独立性,防止 Agent 通过删除或弱化检查来“通过”验证。

假设第 7 步模型回复“修好了”。验证器自己重新运行 test_parse_date,测试仍是红的,于是这次结束被打回,循环带着失败输出继续。如果模型改的是测试本身,比如删掉了那条断言,独立的验证器也应当拦下:它检查的是日期解析的预期行为,不是“测试有没有报错”。

持久化状态​

将完整对话记录和原始工具输出保存在活动 Prompt 之外,另外维护一份精简的任务记录;这份记录至少要有哪几项,见长任务 Agent。对修测试的任务来说,它可以写成:目标是让 test_parse_date 通过,只改 src/ 和 tests/;已改 src/dates.py;pytest 仍有 1 项失败;不要再试“换时区库”这条路;下一步检查两位数年份。新上下文接手时读这份记录就能继续。记录的更新依据是观察到的事实,比如测试输出,而不是模型说自己有多确定。

取消与清理​

长时间运行的工具需要超时设置、进程跟踪和清理机制。取消 UI 操作时,必须同时终止底层的子进程或后台任务。在例子里,用户点了取消,Harness 要连同正在跑的 pytest 子进程一起结束;否则看似已停止的 Agent,可能仍在后台向磁盘写入数据。

最小控制循环​

以下为伪代码;具体工具和模型接口因 Harness 实现而异。

for step in range(max_steps):
if cancelled() or budget.exhausted():
return STOPPED

context = select_working_context(state)
proposal = model.propose(context)

if proposal.is_final:
return verify_final(proposal, state)

checked = policy.validate(proposal.tool_call)
if not checked.allowed:
state.record_denial(checked.reason)
continue

result = tools.execute(checked.call, timeout=checked.timeout)
state.record(result)

if repeated_failure(result, state):
return NEEDS_REVIEW

return STOPPED # max_steps reached

实际实现还需处理异常、幂等性规则、并发控制及日志脱敏。这段伪代码标出了每项控制的位置:policy.validate 拦下越界的路径,repeated_failure 是断路器,verify_final 重新跑测试。这些控制逻辑包裹在模型调用之外,而不是写在 Prompt 里要求模型“小心一点”。

真实进展的标志​

失败集合缩小、出现新的区分性测试、合理的 Diff 变小或歧义得到解决,这些才是进展。重复表述相同的诊断、在没有证据的情况下增加抽象层,或仅仅运行更长时间,都不是进展。在例子里,失败从 3 项降到 1 项算进展;第三次说“可能是时区问题”却没有新的测试输出,不算。

最好的循环通常是能可靠完成任务的最小自主循环。只有在测量到的失败确实需要时,才引入并行工作者、规划层或自我批评机制。

探索关联打开关联网络