@tyanyin/pi-loop-guard
v0.1.0
Published
Anti-death-loop guard extension for pi-coding-agent: detects repeated identical tool calls with unchanged results, warns, blocks, and aborts.
Maintainers
Readme
pi-loop-guard
为什么需要
当小模型陷入困境时,它有时会用完全相同的参数反复调用同一个工具 —— 重复读取同一个文件、重复执行同一条命令、重复更新同一个 todo。每一次重复都在消耗 token、污染对话,而结果从未变化。Pi 本身没有内置的循环检测(#6158,closed no-action):agent 循环会一直持续下去。
pi-loop-guard 精确识别这种特征 —— 结果无变化的重复相同工具调用 —— 并以分级方式打断:
- 警告 — 注入一条 steer 消息,提示模型更换策略或收尾
- 拦截 — 拒绝该工具调用;拦截原因成为喂回模型的 error 消息
- 终止 — 通过
ctx.abort()终止整个用户请求
合法行为完全不受影响:参数不同的调用,或参数相同但结果持续变化的调用(轮询),都不是循环。
安装
pi install git:github.com/Tyan66666/pi-loop-guard完成。扩展在下次 Pi 启动时自动加载。无需配置 —— 默认值(3 次警告、5 次拦截、3 次拦截后终止)开箱即用。
锁定版本:
pi install git:github.com/Tyan66666/[email protected]工作原理
pi-loop-guard 监听 tool_call / tool_result 事件并维护一个 streak 计数器。只有三者全部相同才判定为循环:
- 工具名相同
- 参数指纹相同 — 递归 key 排序后 JSON 序列化,字段顺序无关
- 结果指纹相同 — 文本内容流式 SHA-256(图片只算数量,不比内容)
分级干预(默认值):
| 阶段 | 触发 | 动作 |
|------|------|------|
| 警告 | 连续 3 次相同 | 注入 steer 消息:"请停止重复,换一种策略或给出最终回答" |
| 拦截 | 连续 5 次相同(允许 4 次,拦第 5 次) | 返回 { block: true, reason };reason 成为模型可见的 error |
| 终止 | 同一用户请求内被拦 3 次 | ctx.abort() 终止请求 |
关键细节:
- 跨参数调用永不计数 — 读取不同文件永不触发,即使内容相同。结果对比只作用于重复的同参数调用。
- 合法轮询安全 — 参数相同但结果变化的调用不推进 streak。
- 并行批不推进 streak — 单次 LLM 响应内的并行调用不计数,只允许批内最后一个结果与上次串行结果比较。
- 拦截后同参数重试会被连续拦截(streak 不重置),直到请求被终止或模型更换参数。
terminate需要 pi ≥ 0.84.1(PR #7715),默认关闭(LOOP_GUARD_USE_TERMINATE);旧版本下插件主体可安全运行。
配置
全部通过环境变量配置,无需配置文件。
| 变量 | 默认值 | 说明 |
|------|--------|------|
| LOOP_GUARD_ENABLED | true | 总开关 |
| LOOP_GUARD_WARN_THRESHOLD | 3 | 连续多少次相同后警告 |
| LOOP_GUARD_BLOCK_THRESHOLD | 5 | 连续多少次相同后拦截(必须 > warn) |
| LOOP_GUARD_ABORT_THRESHOLD | 3 | 同一用户请求内拦截多少次后 ctx.abort() |
| LOOP_GUARD_IGNORE_TOOLS | (空) | 白名单工具(逗号分隔,如 todo,bash) |
| LOOP_GUARD_IGNORE_ERRORS | false | 跳过 isError=true 的结果(错误重试不计数) |
| LOOP_GUARD_USE_TERMINATE | false | 拦截时附带 terminate: true(需 pi ≥ 0.84.1) |
示例:
LOOP_GUARD_WARN_THRESHOLD=5 LOOP_GUARD_BLOCK_THRESHOLD=8 pi若 warnThreshold >= blockThreshold,插件会自我禁用并记录错误,而不是异常运行。
插件兼容性
pi-loop-guard 只拦截 tool_call(用于拦截)和 tool_result(用于结果指纹)。它不触碰 context 事件,因此可以与上下文压缩插件(如 billion-context-pi)及任何其他扩展共存。
一点须知:当多个扩展同时 block 同一个 tool_call 时,pi 按加载顺序采用第一个 { block: true } 结果。如果你运行多个拦截类扩展,先加载的优先。这是 Pi 扩展模型的固有限制,并非 pi-loop-guard 特有。
开发
npm install # typescript / @types/node / tsx(--ignore-scripts)
npm run check # tsc 类型检查
npm test # 单元测试(mock ExtensionAPI 驱动,无需真实 pi)测试覆盖:警告触发、拦截时机(允许 4 次拦第 5 次)、合法轮询不误伤、跨参数重置、abortThreshold 终止、before_agent_start 重置、ignoreErrors、并行批、useTerminate、ignoreTools 白名单、配置不变量。
已知局限
- 换参数循环(如轮换读取一组文件)检测不到 —— 通常有进展,危害有限。
- 交错循环(
read(A) → bash(cmd) → read(A) → bash(cmd))不在目标范围。 - 图片只算数量,不比内容 — 两个不同图片 + 相同文本可能误报。
许可证
MIT。
