Skip to content

Successful compression can end the turn when its summary contains FAILED #5383

Open

Description

@LkxPro

Environment

  • billion-context 0.1.209
  • Reproduced on master e209109bfc3c628c655f09e729784c9f1b95835a
  • Observed with Hermes using proxy-side ACP compression
  • Trigger requires compress.visibilityMarkers: false for the no-continuation exit

Actual behavior

A successful compress call whose model-authored summary contains the uppercase word FAILED is classified as a failed compression. With visibility markers disabled, the loop takes the failed-only exit and finishes with stop_reason: "max_tokens" without making the next model request, so the client's task stops on the compression round.

Expected behavior

Success or failure must be determined from the tool result's status line, not from arbitrary words inside the stored summary. A successful compression must refresh the folded view and continue the model loop even when its summary quotes an earlier failure.

Mechanism

The successful receipt has a [Compressed …] status line followed by fingerprint lines containing the model-authored summary. src/loop/core.ts uses pr.result.includes("FAILED") in five places. If the summary mentions an earlier FAILED tool result, those checks misclassify the new successful compression as failed. This affects continuation, fold refresh, failed-only recovery, and committed-result accounting.

A normal successful compression with markers disabled continues correctly; marker visibility alone is not the trigger. Genuine failures whose first status line contains FAILED must retain their existing recovery behavior.

Minimal reproduction

Run a real proxy-server compression with an eligible range and a valid summary containing FAILED, with visibilityMarkers: false.

  • Before: one upstream request, no continued answer, final stop_reason: "max_tokens".
  • Expected: a second upstream request containing the successful tool result, followed by the task's final answer.

Proposed fix

Classify only the first non-empty status line with proxyToolResultFailed() and use it consistently at all five call sites. Add both the failing summary-keyword regression and the ordinary-success negative control, then validate through a fresh real Hermes session.

Related: #4856 covers plugin identity and proxy execution, not this result-classification bug.

Suggested labels: bug, hermes, scope/wiring, p2.

Activity

  1. LkxPro commented on Oct 31, 2026

    @LkxPro
    Author

    (ai: custom/kimi-k3.1-pro)

    Implemented in #5384: #5384

  2. ranxianglei commented on Oct 31, 2026

    @ranxianglei
    Owner

    🤖 Powered by ework · qwen3.8-27b

    (ai: vllm-qwen/qwen3.8-27b)

    查重:不重复。 #5386 是独立缺陷。相关条目核对:#5383 是 owner 的 v0.1.210「回归占位」(ranxianglei 开,只收 210 才有的新坏),不是本 bug——#5384 之前误写 Fixes #5383,我已改成 #5386;#4856 是插件身份/proxy 执行的另一条线,机制不同。全库检索无同类在途修复。

    标签: bug · hermes · scope/wiring · p2(功能性缺陷,特定触发,可重发绕行)。

    审核(先审后做)

    • 查证: 缺陷属实。成功压缩回执首行是 [Compressed …](src/stream.ts:1356),其后逐块追加指纹行(src/stream.ts:1387–1398)内嵌模型自撰摘要摘录。若摘要引用了更早的 FAILED 工具结果,src/loop/core.ts 五处用整串 pr.result.includes("FAILED") 判定(1394/1415/1493/1574/1581)就会把这次成功误判成失败。
    • 层次: 根因是「拿整段回执文本做成功/失败分类」;修在分类源头(只认首个非空状态行),不是在途中途改 payload 掩盖症状。
    • 方案: [PR] fix(loop): don't treat FAILED inside compression summaries as tool failure #5384 方案正确且最小——新增 proxyToolResultFailed() 只取首个非空状态行判 FAILED,五处统一替换;真失败回执([Compression FAILED:/[decompress FAILED:/[absorb FAILED:)FAILED 都在首行,恢复行为不变。

    结构化八问

    1. 是什么问题: 一次成功的压缩,只要它存下的摘要里有大写 FAILED,就被当成失败压缩处理,本轮直接结束、不再发起下一次模型请求,客户端任务卡在压缩轮。
    2. 什么原因导致: 循环判「这次工具调用是否失败」用的是整段回执 includes("FAILED");成功回执的指纹行带模型摘要,摘要引用旧 FAILED 就命中 → 成功被误判为失败。
    3. 影响范围/严重程度: 所有走「proxy 侧执行压缩」的车道都共享 runCompressLoop(dsh/opencode/pi/hermes/codex…),Hermes 上复现;触发=该轮唯一动作是一次成功压缩且其摘要含大写 FAILED(报告配置 visibilityMarkers:false)。特定条件下任务停摆、需重发,非数据丢失/缓存泄露 → p2。
    4. 怎么修复: 五处判定统一换成「只看首个非空状态行是否含 FAILED」。成功/部分折叠首行是 [Compressed …] 不含 FAILED → 正常续跑 + 刷新折叠视图;真失败首行含 FAILED → 维持原恢复路径。
    5. 是否回归: 审过五处既有行为(hideConsumed / 折叠刷新 / failed-only 提前退出 / 传输失败的 committed·absorbed 统计)。唯一行为变化:部分折叠(partial)即便错误行含 FAILED 也不再当整体失败(首行仍是成功形态)→ 会续跑——这是同机制的正确修正,非回归。真失败 FAILED 全在首行,无一条被改判成功。
    6. 是否破坏缓存: 不涉及。不动缓存/重试/游标/去重键,只改一次性成功/失败布尔。
    7. 是否改设计方向: 不涉及。在既有「状态行为准」契约内收敛,无新数据流/边界。
    8. 是否新配置: 不涉及。无新 env/config/默认值。

    交付

    • 实现 PR:[PR] fix(loop): don't treat FAILED inside compression summaries as tool failure #5384(外部贡献者 LkxPro)。我逐行核对了 diff(core +12/-5、tests +57/-4),确认两条新测试真正打到 runCompressLoop 的 proxy 压缩路径(x-bili-plugin-compression: proxy → pluginMode=false,src/server/handle.ts:1817)、visibilityMarkers:false 真实生效(src/config.ts:410/3032 → ctx.visibilityMarkers);第二条是带牙回归测试(修复前 1 次上游请求→断言挂,修复后 2 次→过)。
    • 我的修正: PR 原写 Fixes #5383(那是 v210 回归占位,合并会误关占位、漏关本 bug),已改为 Fixes #5386;其余正文(含 Related: #4856)未动。
    • CI: 读镜像缓存——static-gates / config-surface-guard / lock-sync / pi-subagents-guard / mysql-contract 已过且绿,11 个 test 分片仍在跑、暂无失败;最终判定以推送 head 的远程 CI 为准。

    非阻塞小观察(不塞进本 PR):buildVisibilityMarker(src/loop/core.ts:54–79)选图标时仍逐行 includes("FAILED");标记可见时,成功折叠若摘要含 FAILED 会显示 ❌ 而非 📦。纯外观、同机制不同症状,建议后续单开小修。

    收工判定: 方向正确、diff 干净、测试有效,issue 引用已修正;等远程 CI 绿即可合并。

  3. added
    bugSomething isn't working
    hermes改动范围:内核之外的主机侧接线(src/agent 适配器、server 路由、注入拦截)/范围兜底/客户端标签
    on Oct 31, 2026
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

    bugSomething isn't workinghermes改动范围:内核之外的主机侧接线(src/agent 适配器、server 路由、注入拦截)/范围兜底/客户端标签p2scope/wiring

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions