Skip to content

通用工具重复熔断缺失:acp_status 30× 字节级相同调用死循环(贪心模型,任何工具可成环;compress 熔断器覆盖不到) #308

Description

@ranxianglei

通用工具重复熔断缺失:acp_status 30× 字节级相同调用死循环 —— 贪心模型下任何工具都可成环,compress 熔断器覆盖不到

#250(compress 同参失败空转,已修)互补的形态:这次是永远成功的工具的重复循环。#250 的熔断器只统计 compress 失败,对此零计数。#269(模型无视 nudge)的另一变种。

环境

  • 插件:billion-context-pi 0.1.54(~/.pi/agent/npm/node_modules/billion-context-pi)
  • 宿主:pi coding agent,Linux
  • 模型:qwen3.8-27b-w8a16,SGLang 0.5.17(:8199),贪心解码(模型目录 generation_config.json do_sample:false, temperature:0),thinking off
  • 会话:01a071dc-e334-76ea-98a9-115e48061ac9(2026-09-05 13:58 → 09-06 11:23 UTC,21.5h,1066 条消息,61 次压缩,累计压缩 414K tokens)
  • 证据:~/.pi/agent/sessions/--home-dog-projects-billion-context-pi--/2026-09-05T13-58-16-628Z_01a071dc-e334-76ea-98a9-115e48061ac9.jsonl + 同名 .acp.json sidecar + ~/.pi/acp.log

现象

2026-09-06 11:03:08–11:23:08 UTC,模型进入死循环,30 轮字节级完全相同:

  • thinking md5 恒定、正文 md5 恒定(30 次全部一致)
  • 每轮唯一工具调用:acp_status {"scope":"uncompressed","view":"ranges"},每次输出恰好 2635 tokens,~30s/轮
  • 上下文每轮净增 ~2980 tokens,57K → 130K(有效上限 131072 = 262144 − maxOutput)
  • 触发时序:11:01:07 nudge 注入一次 → 11:02:22 模型 compress 成功(27.8K→6.6K,blocks b58+b59)→ 11:03:08(下一轮)立即进入循环,thinking 声称 The user says "合并了 下一个" —— 该消息在全部 1069 条原始消息和所有压缩块中均不存在(幻觉,是两句真实用户口头禅的混合;turnKey 全程未变,证明无新用户输入)
  • 用户打断两次(11:16:59、11:21:18):模型道歉 + compress 一次 → 下一轮立刻复发同 think hash
  • 11:15:01 emergency nudge(96%)注入也未能打破吸引子

为什么现有防线全部失效

  1. compress-retry-capped 熔断器只统计 compress 失败(src/index.ts:321,小模型(本地 27B 量化)compress 循环空转:同参数重复失败 + 幻觉 ref(0.1.52 完整日志证据) #250 的修复):本循环调的是 acp_status,永远成功 → 熔断器零计数、永不触发
  2. nudge 去重按 turnKey:循环期间没有新用户消息,turnKey 恒定 → 76%→96% 全程 nudge-suppressed,emergency(≥96%)才注入,太晚
  3. src/tool-guardrails.ts 只有 bash 输出上限 + timeout 检测,没有任何"连续相同调用"检测
  4. 保护带保护了循环自己:循环产生的 ~60 条消息全在 recent/last-user zone,打断后的 compress 显示 Excluded 42 protected message(s),回收寥寥

复发性(本机 80 个近期会话扫描,3 个中招)

会话 模型 形态
01a071dc qwen3.8-27b 24× acp_status + 打断后 2 次复发
01a02d90 同上 27B 7× compress(#250 形态)+ 17× 相同 bash(gitlab 命令)
01a06110 qwen3.8-flash W4A16 21× 相同 bash(sleep 轮询,17 分钟)

任何工具都可成为吸引子;两代 qwen3.8(27B 与 flash)都有;贪心解码是共同条件。 循环是序列级的(上下文尾部 N 个相同 (assistant,toolResult) 对自我强化),token 级 penalties(presence/frequency/repetition)对跨轮重复完全无效。

修复建议(按优先级)

1. 通用重复指纹熔断(核心,挂 src/tool-guardrails.ts)

  • 指纹 = sha1(toolName + 稳定序列化(args 键排序后的 JSON))
  • 会话级滑动窗口记录最近指纹
  • 连续 ≥3 次相同 → 在该次 toolResult 尾部注入强警告:你已连续 N 次发出完全相同的工具调用(name+参数逐字节一致)。禁止再次发出相同调用;必须改变策略、换参数,或停止并等待用户。
  • 连续 ≥5 次相同 → 拒绝执行该调用(返回错误 toolResult)+ abort 当前 turn + 终端 echo(参照现有 bash timeout 的处理路径)
  • 注意:只比对参数,不看结果 —— acp_status 结果里可见消息数会微变(24→26→28),但参数相同就算命中
  • 阈值可配:acp.json { "repetitionGuard": { "warn": 3, "abort": 5 } },默认开启
  • tests/ 增加:连续同参调用触发 warn(3 次)、触发 abort(5 次)、参数变化即重置计数的用例

2. 熔断器泛化(src/index.ts:321 一带)

per-turn cap 除 compress 失败外,同时计入"无进展工具调用"(acp_status/decompress/search_context 反复调用且 tokenCount 无下降),达到上限同样 no-progress-capped 告警。

3. nudge 去重放宽

同一 turnKey 内,若距上次注入后上下文相对增长 >10%,允许再注入一次(仍设每档位上限,防止重蹈 #223 无限追加)。

4. 循环消息可压缩

检测到重复指纹时,将循环产生的 assistant/toolResult 消息移出保护带(或显式标记为可压缩),让用户打断后的 compress 能真正回收它们(本例 60 条 × ~3K tokens 全被保护)。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions