给 Pi agent 的 /loop 加上停止条件:pi-loop 循环控制笔记
做 AI 编程 Agent 的自动重试/迭代,难的不是让它跑起来,而是让它在正确的地方停下来。pi-loop 是 Pi coding-agent 的一个扩展,注册了 /loop 命令,把同一个 prompt 按轮发送给 agent,默认每轮后要确认,也可用 --yes 真正无人值守。它的定位类似 Claude Code CLI 的 /loop,但收敛判断、外部命令停止条件、生命周期 hook、实时 TUI 面板和桌面通知都更密。以下按 pi-loop 当前 README 的功能写;装到本地的版本若有差异,以扩展自身的 flag 定义为准。
默认手动,autopilot 需要显式给
/loop 手动模式每轮结束问“是否继续”。确认后,扩展通过 pi.sendUserMessage 发送一条真实用户消息,所以 agent 看到的效果和你自己再敲一遍 prompt 没有区别。
--yes 或 --yolo 跳过确认并 auto-approve,进入完全无人值守。非交互环境没有确认入口,扩展不会挂住等待,会自动切 autopilot 并打 warning:在 CI/脚本里跑 /loop,最好显式把 autopilot 或停止条件写清楚,别依赖交互。
取消用 /loop stop,它不是硬杀,而是等当前迭代完成后结束整个循环。想要真正限制一轮的时长,得靠超时参数,而不是取消命令。
停止条件的层次
如果没有停止条件,/loop 只会把同一个错误重复 N 遍。最外层是轮数上限:
/loop "fix the failing tests" --max 5
--max 默认 10;--max 0 表示不限制。不限制时,必须有下面的 until 条件之一兜住。
按内容停:
--until "TEXT":回复中包含指定文本就停;flag 可重复,默认任一命中即停,--until-all要求全部命中。--until-regex R:按正则表达式匹配回复。
例子:
/loop --yes --until "all tests pass" "refactor the auth module"
/loop --until-regex "tests:\s+\d+ passing" --yes "run the suite"
按收敛停:
--until-stable N:最近 N 轮回复逐字完全相同。--until-stable-sim P:最近 N 轮回复按 token overlap 计算,相似度 ≥ P(0..1)即停。容忍措辞变化,但阈值要自己估准。--until-converged P:只看相邻两轮相似度 ≥ P% 即停,适合快速判断输出是否已稳定。
按时间停:
--timeout 5m给整个循环设墙钟上限。--iter-timeout限制每一轮;未设置时默认单轮 30 分钟上限,防止 agent 卡死在一个 turn 里。--delay MS与--delay-jitter P控制轮间节奏,给上游工具留恢复时间。
按外部命令停:
--until-cmd "cmd":每次迭代前和第 1 轮之后都运行一次 shell 命令,退出码为 0 就停。典型用途是“循环跑测试,直到通过”。--until-cmd-fail "cmd":方向相反,退出码非 0 时停,适合探测服务已停止。- 外部命令默认超时 120s,用
--until-cmd-timeout调整。
失败重试预算
--stop-on-error 遇到错误标记立刻终止;--max-failures N 允许失败累计到 N 次再终止。单轮失败还能用 --retry-on-error N 重试最多 N 次,--retry-delay 毫秒数按 base、2×base、4×base 指数增长,封顶 60s。连续失败时重试会拉长循环,所以要配合 --max-failures 和 --timeout。
三种收敛检测的粒度
--until-stable 逐字相等,适合输出应当确定的场景;回复里夹带时间戳、随机后缀时基本不会收敛。--until-stable-sim 按 token overlap 计算,能容忍换一种说法但意思不变。--until-converged 只看相邻两步,一个逐轮缓慢改写的 agent 可能始终相邻相似,十几轮后内容却完全不同,所以通常要配 --max,别单独作为无限循环的退出条件。
/loop --yes --until-stable 3 "tweak until output converges"
/loop --until-converged 95 --max 20 "iterate until replies stop changing"
模板:把上一轮的状态带回下一轮
模板 token 可以引用当前轮次和历史回复:{{n}} 迭代号,{{max}} 轮数上限,{{prev}} 上一轮回复(折叠空白、截断到 500 字符),{{prev:N}}、{{last:N}},{{all}} 所有先前回复用 --- 连接,{{count}} 已完成轮数,{{ts}} 时间戳。
/loop --yes --max 8 "Iteration {{n}} of {{max}} ({{count}} prior attempts). Prior work: {{all}} Now improve it."
{{all}} 会随轮数增长,prompt 越来越长;要控制长度就用 {{prev}} 或截断 token。
钩子、日志、并发
--on-start CMD:第 1 轮前执行一次--on-fail CMD:每轮失败后执行--on-stop CMD:循环无论以何种原因结束时执行一次--on-iter CMD:每轮完成后执行
钩子命令默认 30s 超时,超时被 kill 后只记 warning,不会中止循环。钩子可拿到 {{i}}、{{max}}、{{name}}、{{reason}}、{{reply}}、{{prev}}、{{iterations}}。结束信号还有 --notify(OS toast)和 --sound(终端响铃)。
运行时同一时间只允许一个 loop,新 loop 会被阻塞到当前 stop,避免两个自动任务叠在一起污染会话。每轮完整对话写成 JSON 日志到 ~/.pi/loops/,只保留最近 20 条,可用 logs、stats、show 查看。配置可存 preset,不带参数的 /loop 恢复上一次运行。
什么情况下别开自动循环
自动循环适合“判断标准能显式表达”的任务:测试通过、正则命中、输出稳定。目标本身模糊、只想“再跑几轮看看效果”时,--yolo 只会放大不确定性。默认手动确认的价值就在每轮之间留一道人工门禁。没有终止条件时不要用 --max 0;即使有收敛检测也建议保留最大轮数或总超时——收敛只说明 agent 不再改变主意,不代表结果可用。