Skip to content

[PR] Refused compress exchanges become foldable like failed ones, no longer permanently stuck on the wire | 被拒绝的压缩调用像失败的一样可折叠,不再永久占着上下文 - #4310

Merged
ranxianglei merged 7 commits into
masterfrom
fix/refused-compress-unprotect
Oct 23, 2026
Merged

ranxianglei merged 7 commits into
masterfrom
fix/refused-compress-unprotect

Conversation

@ranxianglei

@ranxianglei ranxianglei commented Oct 22, 2026 •

Copy link
Copy Markdown
Owner

What

kernel-change: recognize [Compression refused receipts as failed so refused compress exchanges become foldable (fixes #4307 root cause)

Stacks on #4291 — rebased onto its final head 6be85cb5d: this branch carries ALL of #4291's commits (incl. the all-judgment-site failed-threading) plus the refused-prefix fix. Until #4291 merges, this PR's diff vs master shows both PRs' changes together; after #4291 merges, the diff shrinks to the prefix change only. Either merge order is safe (see Status).

Plain language: when the model tries to compress but the proxy refuses the attempt ("no net shrink" or "coverage regression"), the compress call + refusal receipt pair currently stays on the wire FOREVER — it can never be folded away. After #4291, receipts saying [Compression FAILED: …] became foldable; receipts saying [Compression refused: …] (a different prefix for two other refuse paths with the same "nothing was folded" semantics) were missed. This PR treats both prefixes as failure, so refused exchanges fold exactly like failed ones. Success and partial receipts stay protected (their arguments are the on-record summaries decompress restores from); calls with no result yet stay protected.

Why (evidence from issue #4307's session)

Empirical dump audit of live session 01a15993 (1639 request dumps, 239 compress calls tracked by tool_call_id):

Change

One kernel file + tests, stacked on #4291's final state:

Not renaming the proxy emitters (src/stream.ts gate:coverage-regression / gate:no-net-shrink): kernel-side classification fixes both historical receipts already persisted in client histories and future ones; a rename would only fix future.

Tests

  • Pre-rebase head: full-matrix CI — 17/19 test legs + static gates + all guards green, 2 windows shards still running at review time; kernel suite 1437/1437 per author.
  • Rebased head (90b95c2df): integration verified by delta comparison (the merged protected.ts equals the original fix byte-for-byte except the preserved dialect signature; zero stale references to the renamed constant; test-file seam checked). Full matrix re-running via push — verdict pending.

Status (2026-10-22, agent rebase)

Branch was cut from an intermediate #4291 state (missing #4291's final two commits: all-judgment-site threading + bump) and collided with #4291 at kernel 0.0.155. Now rebased onto #4291's final head and rebumped to 0.0.156. Recommended order: merge #4291 first (this PR's diff then shrinks to the prefix change); if this PR merges first instead, #4291 becomes a subset of it and can be closed as superseded.

Fixes #4307

ework-agent and others added 4 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).
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 ranxianglei added the ci:full Run the FULL CI matrix (all e2e lanes + 12-leg test matrix) on this PR label Oct 22, 2026
@ranxianglei ranxianglei changed the title Refused compress exchanges become foldable like failed ones, no longer permanently stuck on the wire | 被拒绝的压缩调用像失败的一样可折叠,不再永久占着上下文 [PR] Refused compress exchanges become foldable like failed ones, no longer permanently stuck on the wire | 被拒绝的压缩调用像失败的一样可折叠,不再永久占着上下文 Oct 22, 2026
@github-actions

github-actions Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

📦 Built Package Artifact

Branch: fix/refused-compress-unprotect (31f48f7)

Option A — Install this exact build (recommended)

npm install -g billion-context@0.0.5124-pr.4310.cc2e88d

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-pr4310.tgz
npm install -g package

Install instructions are refreshed on each push.

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

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

查重:不重复——本 PR 就是 #4307(被拒绝的压缩调用永久占着上下文)的修复,叠在 #4291/#4289(失败压缩调用可折叠)之上;检索 open+closed issue/PR 无其他条目覆盖「refused 回执可折叠」这一机制。两者是同一症状家族(零块落账的压缩交换不可折叠)的先后两半:#4291 修 [Compression FAILED 前缀,本 PR 补 [Compression refused 前缀,机制同源、按先来后到各自独立成 PR,无重复立项。

标签:bug + scope/kernel + p1(缓存泄露类——被拒压缩对只增不减地占住出站 wire,单会话实测 ~117K chars ≈ 14%;按 2026-10-19 owner 修订缓存泄露类一律 p1);客户端标签不涉及(kernel 层,全车道通用)。

正在审:代码 diff、与 #4291 未合入部分的交互、verif 守卫面、CI 结果。结论按八问体例随后跟上。

@ranxianglei ranxianglei added bug Something isn't working p1 scope/kernel labels Oct 22, 2026
ework-agent and others added 2 commits October 22, 2026 22:06
The two refuse gates whose draft parses but commits nothing
(gate:coverage-regression, gate:no-net-shrink) emit
'[Compression refused: ...' receipts with zero blocks applied — the
same dead-draft semantics as '[Compression FAILED'. But #4289's
isFailedCompressResult only matched the FAILED prefix, so refused
exchanges kept ALWAYS_PROTECTED_TOOLS protection forever: never
foldable, and exempt from the #3769 orphan sweep when their args
quote an active block's range (no-net-shrink refusals always do —
they re-quote the range they tried to fold).

Live evidence (issue #4307, sess 01a15993): 239 compress calls, all
with receipts; after #4289, 80/81 FAILED pairs left the wire while
0/47 refused pairs did — ~117KB of permanently stuck dead drafts
(14% of the outbound wire).

Refactor FAILED_COMPRESS_RESULT_PREFIX into
FAILED_COMPRESS_RESULT_PREFIXES = ['[Compression FAILED',
'[Compression refused']; refused pairs now un-protect and fold
exactly like failed ones. No consumer changes needed —
collectFailedCompressCallIds already feeds assignRefsNode
(compress.ts), filterProtectedToolMessages (unit-projection.ts) and
isMessageProtectedWithPairing. Success ('[Compressed') and partial
receipts stay protected; pending calls (no result yet) stay
protected.

(cherry picked from commit 85b7bdd)
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.
Cosmetic follow-up found while reviewing the stack: the internal-lane skip
note at src/preflight.ts strips the [Compression FAILED boilerplate before
recording the reason, but applyRanges can also return the #4307
[Compression refused receipts — those kept their full boilerplate in the
note. Same mechanism family (refused receipts first-class), one-line
regex extension.
@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

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

Review update — code-level review is complete; final verdict follows when the last CI leg lands. Three things happened on this branch during review, all resolved or in flight:

1. Stacking hazard → resolved by rebase. When I started, the branch was cut from #4291 before its last two commits landed (7b51e3e16 refactors isFailedCompressResult into a dialect-aware (msg, config?) signature; 6be85cb5d bumps 0.0.154→0.0.155) — merging as-is would have produced a semantic conflict in kernel/src/protected.ts plus a double version bump. At 14:07 UTC the branch was rebased onto current #4291 head (6be85cb5d): the union form is correct (isCompressToolName(msg.toolName, config) AND multi-prefix FAILED_COMPRESS_RESULT_PREFIXES.some(…)) and the bump was redone as 0.0.155→0.0.156. Full-matrix CI on that head (90b95c2df): all green — 12 test legs + 4 static-gates + every guard.

2. New third commit reviewed. 31f48f75a extends the skip-note boilerplate strip at src/preflight.ts:2147 to also remove the [Compression refused prefix. Verified accurate: preflight imports applyRanges from src/stream.ts (the host wrapper whose gates emit the refused receipts), so those notes really can carry the new prefix. Same mechanism family, one line, host-side (no further kernel bump needed).

3. CI transparency (my fault + infra hiccup). My Gitea-mirror labeling synced to GitHub and the labeled event cancelled the in-flight full-matrix run mid-flight; I re-triggered via workflow_dispatch(full=true) — noted for the record. On the current head, the triggered run's kernel-guard job stalled on its runner for ~35 min (trivial body-check; the kernel-change: declaration is present in the PR body). PATs can't rerun single jobs (403), so I dispatched a fresh full run (39801676692, queued 15:06 UTC) which supersedes the stalled one for mergeability.

Verdict + structured eight-question review coming as soon as that run finishes (~30 min).

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

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

Review 结论:代码通过,CI 实质全绿,可合 —— 唯一残留门禁是一个卡死在 runner 上的机械 job(见末尾)。

结构化八问

  1. 是什么问题:模型发起 compress 调用被代理拒绝(no-net-shrink / coverage-regression 两个 gate)时,调用+回执对永久钉在出站 wire 上、永远无法折叠。实测(Compression calls with lost ACKs are permanently stuck in context: client-side message loss / model-generation switch re | 回执丢失的压缩调用永久滞留上下文:客户端丢消息/切模型世代重置产生孤儿,且无任何回收路径 #4307,sess 01a15993):239 次 compress 调用中 47 个 refused 回执,[PR] Failed compression calls become collapsible regular messages, no longer permanently occupying the context. | 失败的压缩调用变成可折叠的普通消息,不再永久占着上下文 #4291 上线后 80/81 FAILED 对已离开 wire,而 0/47 refused 对——约 117K chars ≈ 14% 出站 wire 被死摘要草稿永久占用。
  2. 什么原因导致:kernel 零块落账分类点 isFailedCompressResult(kernel/src/protected.ts)只认 [Compression FAILED 前缀,漏掉另一前缀 [Compression refused——两者同为「零块落账」语义。refused 对因此保留 ALWAYS_PROTECTED_TOOLS 保护 → 被所有可折叠集排除(filterProtectedToolMessages),且被 [Bug] MCP failed compression record rotation prematurely rewrites the history prefix, triggering expensive cache recompu | [Bug] MCP 失败压缩记录轮换提前改写历史前缀,触发大额缓存重算 #3769 orphan sweep 豁免(no-net-shrink 回执参数引用活跃块范围,形似 summary carrier)。分层:根因 = 单点分类不全;表面现象 = wire 永久膨胀。修的是根因,不是症状。
  3. 影响范围与严重程度:全车道(kernel 层分类,plugin/proxy 两模式共用);触发条件 = 任何会话中模型反复尝试压缩被拒。严重度 p1 缓存泄露类(上下文只增不减、会话存续期内不可回收)。
  4. 怎么修复的:单一分类点从常量扩为前缀数组(FAILED_COMPRESS_RESULT_PREFIXES + some() 匹配),消费端零改动——collectFailedCompressCallIds 已喂给全部判定站点(assignRefsNode/compress.ts、filterProtectedToolMessages/unit-projection.ts、isMessageProtectedWithPairing)。rebase 后与 [PR] Failed compression calls become collapsible regular messages, no longer permanently occupying the context. | 失败的压缩调用变成可折叠的普通消息,不再永久占着上下文 #4291 的 dialect-aware 签名正交组合正确(先 isCompressToolName(msg.toolName, config) 门、再多前缀匹配)。附带一行 host 侧精修:src/preflight.ts:2147 skip-note 样板剥离 regex 扩到新前缀(已核实 preflight 的 applyRanges 即 src/stream.ts 里发出 refused 回执的那个包装器,commit message 属实)。
  5. 是否有回归问题:既有行为逐项审计——成功/部分回执([Compressed …)保持保护(有测试:同会话成功 exchange 仍 BLOCKED_REF、不进折叠集);pending 调用(尚无结果)保持保护(id 不进失败集,机制未动);非 compress 工具名负控(acp_rule)通过;MCP dialect 路径与多前缀正交组合不受损;golden/wire-contract 无漂移(CI 绿)。有意行为变更已披露:refused 交换「永不折叠」→「与 failed 相同可折叠」(旧→新+原因即第 1/2 问,正是本次修复目的)。无 fail-fast 变吞错、无阈值/时序/输出格式漂移。
  6. 是否破坏缓存:否——失效策略零改动;净正面(移除永久占位字节,前缀更稳)。
  7. 是否改了设计方向:否——既有分类机制的常量扩展,无架构/边界/数据流转向。
  8. 是否引入新配置:否。kernel 版本 0.0.155→0.0.156(叠在 [PR] Failed compression calls become collapsible regular messages, no longer permanently occupying the context. | 失败的压缩调用变成可折叠的普通消息,不再永久占着上下文 #4291 的 0.0.153→0.0.155 之上),lockfile 同步,bili 主包版本未动。

测试有效性

3 个新测试走真实入口(createCore/processTurn/applyCompression),断言有牙(数值 ref vs BLOCKED_REF、block.directMessageIds 成员关系、可见消息清单),带负控(acp_rule 拒认、success 同会话保持保护)——bug 复现时抓得住,不是恒真断言。

Kernel Boundary 合规

PR body 含 kernel-change: 声明 ✓;kernel/src 变更伴随版本 bump ✓;human-only review 门适用——合并需你操作。

CI 状态(含两条披露)

  • rebase 后 head 90b95c2df 的全矩阵 run:全部绿(12 test + 4 static-gates + 全部 guard)。
  • 最终 head 31f48f75a 的 pull_request run:12 test + 4 static-gates + 全部 guard 绿,唯 kernel-guard 卡在 runner 上 70+ 分钟未完成——它只是校验 PR body 声明 + 版本纪律的机械检查,两项均满足。PAT 无权 rerun 单个 job(403),需在 PR checks 页对该 job 点一次 Re-run(或等 runner 自行恢复)。
  • flake 披露:我在同 head 触发的 dispatch 全矩阵 run 里 test (windows-latest, 24, 1) 红了一次,而同 head 同 leg 在 pull_request run 中 30 分钟前是绿的——判为环境 flake 而非代码回归(diff 不触碰该 leg 相关面);若再复现我另开 issue 跟踪。
  • 我的失误(前一条已披露):打标操作同步到 GitHub 取消了当时在飞的全量 run,已用 dispatch 补触发。

合入顺序建议

先合 #4291 再合本 PR(本分支已包含 #4291 全部提交,#4291 合入后本 PR 无需 rebase,GitHub 直接显示干净可合);或者你直接合本 PR——#4291 的内容随之一起上线,#4291 关为 superseded 即可。

判定:可合(把卡死的 kernel-guard job 重跑一次即可)。

@ranxianglei
ranxianglei merged commit 5a04876 into master Oct 23, 2026
116 of 117 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

📦 Merged Build Published

This merge to master was published as 0.0.741-master.5a04876 (rolling master tag).

npm install -g billion-context@0.0.741-master.5a04876  # 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