Skip to content

compress 伪成功死循环:重复压缩已压缩区间报告 success,模型上下文自复位无限重试(33 次,真实会话复现) #199

Description

@ranxianglei

现象

真实会话陷入压缩死循环:无任何新输入的情况下,模型逐字节相同地重发同一个 compress 调用 33 次(约 50 秒一次,13:26:14 → 13:54:41,持续约 28 分钟)。每次工具都报告"成功",但上下文实际毫无变化,且模型看不到自己之前的重试,循环无法自终止,最终靠用户打断恢复。

根因链(v0.0.32 = 会话实际运行版本)

13:24:28 模型发起 3 区间批量压缩,成功创建 b15/b16/b17(回收 ~70K)。此后每轮迭代:

  1. prune 注入合成消息 acp_summary_b15;模型(参考了过期的 nudge/状态,仍认为区间未压缩)再次发送 m00361..m00436
  2. 这两个 ref 映射到的是夹住 summary 的两条幸存普通消息(创建 b15 时处于保护区被排除、仍可见),区间解析成功,覆盖内容 = 2 条小消息 + acp_summary_b15
  3. v0.0.32 applySingleRange 不过滤合成 summary id(过滤在 compress cannot promote an active block after pruning replaces its raw messages #195 修复 8a4dc72 / v0.0.38 才加入)→ 产出 directMessageIds=["acp_summary_b15"]空壳包装块,报告 blocksCreated=1、回收 ~2.1K → 伪成功
  4. 扩展层 isCompressSuccessText() 只看 blocksCreated>0 → 判定成功,重试预算不消耗。
  5. 下一轮 syncBlocks 对比原始日志:合成 id 不存在 → 壳块被静默反激活;hide-consumed 又把该 compress 调用/结果对从模型视野隐藏。
  6. 模型看到的上下文与上一轮完全相同 → 相同 thinking → 相同调用。×33。

状态文件佐证:.acp.json 中 b18..b50(33 个)全部是 directMessageIds=["acp_summary_b15"]compressedTokens 2075active:false 的一次性壳块,与 33 次迭代一一对应。

v0.0.39(kernel master)仍会循环 —— 只是换了形态

用同一份真实数据(会话 jsonl 前 568 行 + 真实 .acp.json 裁剪到 b1..b17 + jsonl 第 569 行的原始 compress 参数)在 acp-kernel v0.0.39 上回放:

applyCompression →
{ blocksCreated: 1, tokensCompressed: 811, errors: [],
  warnings: [
    "Skipped range (m00509..m00542) — already compressed (messages consumed by existing block(s)); nothing to compress.",
    "Skipped range (m00549..m00576) — already compressed (messages consumed by existing block(s)); nothing to compress." ] }
新建块: { id: "b54", directMessageIds: [], consumedBlockIds: ["b15"], tier: 1, active: true }
下一轮 processTurn: 壳块存活(active), b15 被反激活, 视图大小不变(120→120),
模型自己的 compress 工具结果仍不可见

#195 的两个修复(8a4dc72、95f7530)让 summary id 被过滤,但 nested-discovery 转而把 b15 本身识别为嵌套块,产出 directMessageIds=[]空 tier-1 重写块并消费 b15 —— 依旧 errors:[]blocksCreated=1,对扩展层仍是"成功";视图结构不变(summary 文本换壳)、模型自身调用仍被隐藏,第 3 轮起按同构机制无限继续。livelock 条件没有关闭。

为什么现有防护都拦不住

防护 为什么不适用
bcp tier3OnlyRewrite guard(PR #181) 只拦 tier-3 重写;此处是 tier-1
bcp no-op 计失败 + retry cap(PR #182) 只对 0-block no-op 生效;此处 blocksCreated=1 恒为成功,计数器永不增长
PR #194(OPEN,terminal 拒绝不再重推 nudge) 处理的是被拒绝(0-block)后的 nudge 重推;此处工具"成功",nudge 逻辑不介入
kernel 空块守卫 compress.ts directMessageIds.length === 0 && consumedBlockCount === 0 只拦 direct 与 consumed 同时为空;consumed=["b15"] 时放行

修复建议

  1. kernel(核心):message-ref 区间解析后,若实际覆盖内容只剩活跃块的渲染 summary(无 block-boundary ref、非显式 T2+ 提升路径),应按已压缩区间同样方式拒绝:terminal 报错 + blocksCreated=0
  2. kernel:directMessageIds=[] 的 tier-1 块只应产生于显式提升路径(block-boundary refs 如 b2..b2,或 T2/T3 压缩),否则拒绝创建。
  3. bcp:成功判定不应只看 blocksCreated>0,结合 warnings(本例每次都有 2 条 "Skipped range — already compressed")与空 direct 块。

复现

回放脚本基于真实会话数据,可在任意 kernel 版本验证(tmp/repro-iter2.mjs):用扩展自身的 entriesToCoreMessages 载入 jsonl → processTurnresolveBoundariesapplyCompression → 再 processTurn,输出如上。

关联 #195:其修复解决了 promote-after-prune,但未关闭本循环(反而是过滤引入后的新路径)。

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