Skip to content

[PR] Failed compression calls become collapsible regular messages, no longer permanently occupying the context. | 失败的压缩调用变成可折叠的普通消息,不再永久占着上下文 - #4291

Merged
ranxianglei merged 4 commits into
masterfrom
fix/unprotect-failed-compress
Oct 23, 2026
Merged

ranxianglei merged 4 commits into
masterfrom
fix/unprotect-failed-compress

Conversation

@ranxianglei

@ranxianglei ranxianglei commented Oct 22, 2026 •

Copy link
Copy Markdown
Owner

kernel-change: #4289 — failed compress exchanges (receipt prefix "[Compression FAILED", zero ranges applied) are dead drafts, not compression metadata: no block covers their arguments and decompress never consults them. Un-exempt both halves so they fold like ordinary messages, or a failing compress loop bloats context forever. The protection judgment lives in acp-kernel (shared by every lane) and cannot be solved host-side.

Fixes #4289

一句话

压缩失败的调用(结果以 [Compression FAILED 开头 = 一个 range 都没折成)不再享受"永久不可折叠"的元数据豁免 —— 它和普通消息一样可以被下一次压缩回收;成功/部分成功/还没出结果的调用保持豁免不变。

为什么安全

改动

  • kernel/src/protected.ts — FAILED_COMPRESS_RESULT_PREFIX / isFailedCompressResult() / collectFailedCompressCallIds();Failed compression invocations are never folded back for reclamation, and the context keeps ballooning under the weight | 失败的压缩调用永远不会被折叠回收,上下文被压缩残渣越撑越大 #4289 豁免提升到 isMessageProtected 顶部(宿主 isMessageProtected hook 之后、一切内置保护源之前:ALWAYS 表、protectedTools pattern、宿主 tool-hook),使所有判定站点一致解除失败交换的豁免;识别经既有 Config.isCompressTool 方言感知(bare compress 恒计数,mcp__bili__compress 等 MCP 方言由宿主注入的 recognizer 认领);isMessageProtectedWithPairing 新增可选失败集合参数;collectProtectedToolCallIds 跳过失败调用(配对保护不会复活它们的结果)
  • kernel/src/compress.ts — assignRefs 节点把失败集合传进保护判定(调用端 + 配对端;path-pattern 形状同样覆盖)
  • kernel/src/recommend.ts — buildCompressibleRanges 接收同一失败集合:失败调用不再被归类为 protected、不再把 advertised range 切碎(KDD#12 单一投影一致性)
  • kernel/src/ccr.ts / kernel/src/absorb.ts — CCR 存储与 absorb 候选判定同样感知失败交换
  • kernel/src/unit-projection.ts — filterProtectedToolMessages 同样感知失败交换
  • kernel/tests/issue4289-failed-compress-fold.test.ts — 11 个测试:数字 ref / 可折叠 / 成功与部分成功仍 BLOCKED / 无结果在途仍 BLOCKED / 存量 BLOCKED 自愈迁移 / 单元形状,外加 5 个新回归:advertised range 覆盖 / path-pattern 形状 / MCP 方言可折叠且 advertised / 未知方言保守钉住(负控)/ 方言识别单元
  • 宿主侧零 diff:src/config.ts 本就同时注入两个 hook(isToolProtected + isCompressTool)
  • acp-kernel 0.0.153 → 0.0.155(Kernel Boundary rule 2)

行为变更披露

  • 失败的 compress 交换(call + result 两端)从「永久不可折叠」变为「可被下一次压缩折叠」——全部车道(proxy/plugin/MCP 方言);成功/部分成功/在途不受影响
  • 显式 protectedTools pattern 与宿主 tool-hook 从此无法钉住失败交换(它们本就不钉成功的)
  • 未知方言(无 isCompressTool recognizer 的宿主)下的失败交换保持保守钉住 —— 行为不变
  • protectedLatestTools 刻意不豁免(opt-in 的近因语义,默认空)

验证

  • 本地定向(新 head 6be85cb5d):issue4289 测试文件 11/11;22 个相关 kernel 测试文件(recommend/protected-/message-protection/absorb/ccr/crush/wire-tool-result/orphan-sweep/ranges-/carrier/dead-ref/media/block-map/issue3236/protect-recent/tool-pairs)307/307 绿;verif-subset-guard 2/2(compress.ts 属 Tier-A 守护清单);typecheck + build 绿
  • 全量回归(kernel 全量 + 代理侧 + e2e)以远程 CI 在新 head 上的结论为准;旧 head 4e2c22fef 上全仓已全绿(kernel 1434/1434)

ework-agent added 2 commits October 22, 2026 18:20
A compress tool-result rendered "[Compression FAILED…" applied ZERO
ranges: no block carries its arguments, decompress never consults it,
and its summary draft is dead weight. ALWAYS_PROTECTED_TOOLS protected
these exchanges by tool name alone, so a failing compress loop
permanently bloated context (live incident: 70 failed calls = 116KB of
unreclaimable residue, ~75% of a 227K context was compress residue).

- protected.ts: FAILED_COMPRESS_RESULT_PREFIX + isFailedCompressResult +
  collectFailedCompressCallIds; isMessageProtected gains optional
  failedToolCallIds — failed exchanges lose ALWAYS protection (both
  halves), successful/partial/pending keep it
- collectProtectedToolCallIds: skips failed compress calls (pairing can
  never re-protect their results)
- compress.ts assignRefsNode + unit-projection filterProtectedToolMessages:
  thread the failed-call set
- refs.ts BLOCKED→numeric migration (#2607) self-heals legacy sessions:
  the next assignRefs re-refs old failed exchanges as foldable
Kernel Boundary rule 2 (CI kernel-guard): kernel/src changes require a
version bump in kernel/package.json so accumulated changes can ship as
an independent acp-kernel release later. Regenerates both lockfiles
(version lines only).
@ranxianglei ranxianglei added the ci:full Run the FULL CI matrix (all e2e lanes + 12-leg test matrix) on this PR label Oct 22, 2026
@github-actions

github-actions Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

📦 Built Package Artifact

Branch: fix/unprotect-failed-compress (6be85cb)

Option A — Install this exact build (recommended)

npm install -g billion-context@0.0.5105-pr.4291.d4453d1

This exact version is immutable and reproducible. The rolling pr tag tracks the newest test build across all PRs.

Option B — Download artifact

  1. Download the artifact from the Actions run
  2. Extract the tarball and install:
tar xzf billion-context-pr4291.tgz
npm install -g package

Install instructions are refreshed on each push.

@ranxianglei ranxianglei changed the title 失败的压缩调用变成可折叠的普通消息,不再永久占着上下文 [PR] Failed compression calls become collapsible regular messages, no longer permanently occupying the context. | 失败的压缩调用变成可折叠的普通消息,不再永久占着上下文 Oct 22, 2026
@ranxianglei ranxianglei reopened this Oct 22, 2026
@ranxianglei ranxianglei added bug Something isn't working p1 scope/kernel labels Oct 22, 2026
@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

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

查重: 不重复。 本 PR 是 #4289 的实现 PR(自身线程);检索 open+closed 的 issue/PR,无其他在途工作做「失败压缩交换可折叠」——最近的邻居 #4217(failed-only 轮次的 ephemeral 失败帧不进上游)机制不同:它管的是失败帧的出站可见性,不管已持久化残渣的可回收性。

标签: bug / scope/kernel / p1(缓存泄露类——上下文只增不减地失控增长,按 10-19 规则定 p1;客户端面跨所有走代理的车道,不猜单个客户端标签)。

初审结论:方向正确、回执契约成立,但豁免没有覆盖全部判定站点和全部方言——实测三处缺口,正在 PR 分支上补齐。

方向与契约核验(通过):

实测缺口(用 PR head 代码构造会话跑 processTurn + buildCompressibleRanges 复现):

  1. recommend 从未收到失败集合 —— kernel/src/recommend.ts:358 恒走 isMessageProtectedWithPairing(msg, config, protectedCallIds),其第一行调 isMessageProtected(msg, config) 不带集合。后果:即使 refs 已是数字(可折叠),acp_status/recommend 推荐的 compressible ranges 里失败的调用半仍被标 protected,range 在它这里断开——模型根本看不到这段可折。默认配置下就中招,这是主路径。
  2. 路径模式配置下调用半仍 BLOCKED —— kernel/src/compress.ts assignRefsNode 里,只要 protectedTools 含 /(如 skill/*)就走 pairing 分支,同样不带失败集合。实测:结果半拿到数字 ref,调用半(9–21KB 摘要草稿,残渣大头)永久 BLOCKED。
  3. MCP 车道完全未覆盖 —— Kimi Code/Claude Code/zcode 的 mcp__bili__compress 走宿主注入的 isToolProtected hook(src/config.ts [Bug] MCP 模式下 acp_rule 的工具名带有前缀,可能导致硬保护失效 #2578),而失败识别只认裸名 "compress"。实测:两端全 BLOCKED,一个字节都救不回。Failed compression invocations are never folded back for reclamation, and the context keeps ballooning under the weight | 失败的压缩调用永远不会被折叠回收,上下文被压缩残渣越撑越大 #4289 讨论区(floor 3)明确点过「两条保护路径要同时开口子」,实现漏了这条。

修法(已在做,kernel-only,不动宿主):把 #4289 豁免上移到 isMessageProtected 顶部(宿主 isMessageProtected 声明之后、一切内建来源之前),识别器改用宿主已注入的 config.isCompressTool(裸名永远兜底)→ MCP 方言自动覆盖;失败集合贯穿到 WithPairing 及 recommend/ccr/absorb/unit-projection 各判定站(KDD#12 一致性)。补三个缺口的回归测试 + kernel 0.0.155 bump。另外 PR body 缺 kernel-change: 标记导致 CI kernel-guard 红,一并修。

完成后按八问体例给最终结论。

awork added 2 commits October 22, 2026 18:51
The first pass left three shapes permanently pinned:

- path-pattern protectedTools routed assignRefs through isMessageProtectedWithPairing without the failed set, keeping the call half (the 9-21KB drafts) at BLOCKED

- buildCompressibleRanges never received the failed set, so the failed call was classified protected and fragmented the advertised ranges

- MCP dialect names (mcp__bili__compress) were outcome-blind: recognition keyed on the bare name "compress" while host tool-hooks stay unconditional

Move the #4289 exemption to the top of isMessageProtected (after the host isMessageProtected hook, before every built-in source), make failure recognition dialect-aware through the existing Config.isCompressTool union (bare "compress" always counts), and thread the failed set through isMessageProtectedWithPairing plus the remaining judgment sites (recommend, ccr, absorb, unit-projection). Exchanges under an unknown dialect without a recognizer stay conservatively pinned. Host side unchanged: src/config.ts already injects both hooks. Five new regression tests pin all four facets including both negative controls.
@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

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

Review 结论:PR 方向正确;我独立审核 + probe 实锤后发现初版留了三个判定站点未覆盖,已在分支上直接补齐(新 head 6be85cb5d,两个 commit)。待 owner 合并(Kernel Boundary:kernel 变更 human-only)。

八问

  1. 是什么问题:压缩失败的调用(回执以 [Compression FAILED 开头 = 一个 range 都没折成)被当作"压缩元数据"永久豁免、不可折叠。失败风暴场景(compress calls rejected when the summary text rides in content next to top-level startId/endId — plus U+2212 dash silently folds the wrong range #4252)下每次失败调用 9–21KB 的死草稿参数永久累积,单会话实测占上下文 ~75%(227K 里 187K 才是真实工作)。
  2. 什么原因导致的:ALWAYS_PROTECTED_TOOLS 保护是 outcome-blind —— 不区分 compress 调用成功还是失败。失败调用的参数不覆盖任何 block、decompress 永远不会用到,它根本不是"摘要载体",却和成功调用一样拿到永久豁免。因果链:保护表按工具名判 → assignRefs 给 BLOCKED ref → 后续折叠永远跳过。
  3. 影响范围与严重程度:全部车道(proxy/plugin/MCP 方言);任何发生过重复压缩失败的会话都触发。严重度:p1 类缓存泄露/上下文膨胀 —— 能用,但残渣单调占位,成本与质量随时间劣化,且无自愈出口。
  4. 怎么修复的:以回执契约为判定信号(零应用回执恒以 [Compression FAILED 开头;成功/部分成功恒以 [Compressed 开头),把豁免从"compress 工具整体"收窄到"失败的 compress 交换两端",之后按普通消息回收;存量会话走既有 BLOCKED→数字 ref 迁移(建议:compress.stripImages 默认开启——否则带图消息不可压缩会形成只升不降的输入地板撑爆上下文 #2607)自动自愈,无迁移脚本。
    审核中补齐的三个缺口(probe 逐一实锤后才动手):
    • ① path-pattern protectedTools(如 skill/*)形状走配对端分支,失败调用端仍 BLOCKED —— 正是 9–21KB 草稿那一半;
    • ② buildCompressibleRanges 从未接收失败集合,失败调用被归类 protected,把 advertised range 切碎,模型永远看不到这段可折叠区间;
    • ③ MCP 方言(mcp__bili__compress)完全 outcome-blind —— 宿主 hook 无条件保护,内核识别只认 bare 名。
      修法:豁免提升到 isMessageProtected 顶部(宿主 isMessageProtected hook 之后、一切内置保护源之前),识别经既有 Config.isCompressTool 做方言感知(bare compress 恒计数,未认领方言保守钉住),失败集合贯穿 isMessageProtectedWithPairing 与全部剩余站点(recommend/ccr/absorb/unit-projection)。宿主侧零 diff:src/config.ts 本就同时注入两个 hook。
  5. 是否有回归问题:逐项审过——成功/部分成功回执仍 BLOCKED(测试钉住);在途无结果仍 BLOCKED(测试钉住);显式 protectedTools pattern 与宿主 tool-hook 从此无法钉住失败交换(它们本就不钉成功的,语义未变只是扩展到 MCP);无 recognizer 的未知方言保持钉住(负控测试);protectedLatestTools 刻意不豁免(opt-in 近因语义,默认空,已在 PR 描述披露)。verif-subset-guard 过(compress.ts 属 Tier-A 守护清单);golden pin 无漂移。
  6. 是否破坏缓存:不影响前缀缓存接缝——折叠走正常 applyCompression 路径(锚重渲/尾巴保留不变),变的只是"哪些消息拿数字 ref 还是 BLOCKED"。存量会话首次 assignRefs 的 BLOCKED→数字重编号只改 ref 号不改消息字节(建议:compress.stripImages 默认开启——否则带图消息不可压缩会形成只升不降的输入地板撑爆上下文 #2607 机制,测试 5 钉住自愈)。
  7. 是否改了设计方向:方向未变——正是把原修复方向的 KDD#12 单一投影一致性要求做完:资格判定(recommend)与执行(assignRefs/apply)共用同一失败交换集合。acp-kernel 0.0.153 → 0.0.155(Kernel Boundary rule 2,独立 bump commit)。
  8. 是否引入新配置:无。复用既有 Config.isCompressTool 注入点,无新 env/config 字段。

验证状态

  • 本地定向(新 head):issue4289 文件 11/11(原 6 + 新增 5 回归,含 2 个负控);22 个相关 kernel 测试文件 307/307;verif-subset-guard 2/2;typecheck + build 绿。
  • 全量回归以远程 CI 在新 head 上的结论为准(运行中);旧 head 4e2c22fef 此前全仓已全绿(kernel 1434/1434,12 个 test shard 全 success)。
  • PR 描述已更新:三个补齐点 + 行为变更披露 + 验证边界。

收工判定:代码侧闭环(方向 + 三缺口 + 负控 + 自愈全部钉住),剩 CI 全绿确认 + owner 合并。kernel 变更走 human-only review,请 @ranxianglei 审后合并。

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

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

CI 全绿确认:新 head 6be85cb5d 上全部 57 个 job success/skipped、0 失败 —— kernel-guard / version-guard / lock-sync / config-surface-guard 等门禁全过,12 个 test shard(ubuntu+windows × node22/24)全绿,registry e2e 与全部真实客户端车道(dsh/opencode/pi/codex/OpenCode V2/real-image billing)全绿。PR 状态 open、mergeable clean。

代码与 CI 均已闭环,kernel 变更按 human-only review 待你合并:#4291

ranxianglei pushed a commit that referenced this pull request Oct 22, 2026
Rebased onto the #4291 head (fix/unprotect-failed-compress @ 6be85cb):
the refused-prefix fix now stacks on top of the all-judgment-site failed
threading instead of an intermediate #4291 state. Version rebumped past
#4291's 0.0.155 so the two PRs can merge in either order without a
kernel-guard collision.
@ranxianglei
ranxianglei merged commit fcf3e82 into master Oct 23, 2026
57 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

📦 Merged Build Published

This merge to master was published as 0.0.740-master.fcf3e82 (rolling master tag).

npm install -g billion-context@0.0.740-master.fcf3e82  # this exact build
npm install -g billion-context@master  # always the newest merged build

npm view billion-context@master prints the current version at any time.

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

Labels

bug Something isn't working ci:full Run the FULL CI matrix (all e2e lanes + 12-leg test matrix) on this PR p1 scope/kernel

Projects

None yet

1 participant