Repository navigation
[PR] Refused compress exchanges become foldable like failed ones, no longer permanently stuck on the wire | 被拒绝的压缩调用像失败的一样可折叠,不再永久占着上下文 - #4310
Conversation
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.
📦 Built Package ArtifactBranch: Option A — Install this exact build (recommended)npm install -g billion-context@0.0.5124-pr.4310.cc2e88dThis exact version is immutable and reproducible. The rolling Option B — Download artifact
tar xzf billion-context-pr4310.tgz
npm install -g packageInstall instructions are refreshed on each push. |
(ai: vllm-qwen/qwen3.8-27b) 查重:不重复——本 PR 就是 #4307(被拒绝的压缩调用永久占着上下文)的修复,叠在 #4291/#4289(失败压缩调用可折叠)之上;检索 open+closed issue/PR 无其他条目覆盖「refused 回执可折叠」这一机制。两者是同一症状家族(零块落账的压缩交换不可折叠)的先后两半:#4291 修 标签:bug + scope/kernel + p1(缓存泄露类——被拒压缩对只增不减地占住出站 wire,单会话实测 ~117K chars ≈ 14%;按 2026-10-19 owner 修订缓存泄露类一律 p1);客户端标签不涉及(kernel 层,全车道通用)。 正在审:代码 diff、与 #4291 未合入部分的交互、verif 守卫面、CI 结果。结论按八问体例随后跟上。 |
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.
e841fb8 to
90b95c2
Compare
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.
(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 ( 2. New third commit reviewed. 3. CI transparency (my fault + infra hiccup). My Gitea-mirror labeling synced to GitHub and the Verdict + structured eight-question review coming as soon as that run finishes (~30 min). |
(ai: vllm-qwen/qwen3.8-27b) Review 结论:代码通过,CI 实质全绿,可合 —— 唯一残留门禁是一个卡死在 runner 上的机械 job(见末尾)。 结构化八问
测试有效性3 个新测试走真实入口(createCore/processTurn/applyCompression),断言有牙(数值 ref vs BLOCKED_REF、block.directMessageIds 成员关系、可见消息清单),带负控(acp_rule 拒认、success 同会话保持保护)——bug 复现时抓得住,不是恒真断言。 Kernel Boundary 合规PR body 含 CI 状态(含两条披露)
合入顺序建议先合 #4291 再合本 PR(本分支已包含 #4291 全部提交,#4291 合入后本 PR 无需 rebase,GitHub 直接显示干净可合);或者你直接合本 PR——#4291 的内容随之一起上线,#4291 关为 superseded 即可。 判定:可合(把卡死的 kernel-guard job 重跑一次即可)。 |
📦 Merged Build PublishedThis merge to master was published as npm install -g billion-context@0.0.741-master.5a04876 # this exact build
npm install -g billion-context@master # always the newest merged build
|
What
kernel-change: recognize
[Compression refusedreceipts 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):
[Compression refused: no net shrink — …]. On the final outbound wire, after [PR] Failed compression calls become collapsible regular messages, no longer permanently occupying the context. | 失败的压缩调用变成可折叠的普通消息,不再永久占着上下文 #4291 shipped: 80/81 FAILED pairs had left the wire, but 0/47 refused pairs did — ~117K chars (≈14% of the outbound wire) of permanently stuck dead summary drafts.isFailedCompressResult(kernel/src/protected.ts) only matched[Compression FAILED, so refused pairs keptALWAYS_PROTECTED_TOOLSprotection → excluded from every compressible set (filterProtectedToolMessages), and the [Bug] MCP failed compression record rotation prematurely rewrites the history prefix, triggering expensive cache recompu | [Bug] MCP 失败压缩记录轮换提前改写历史前缀,触发大额缓存重算 #3769 orphan sweep also exempts them because a no-net-shrink refusal's arguments quote an active block's range (they look like a summary carrier).Change
One kernel file + tests, stacked on #4291's final state:
kernel/src/protected.ts:FAILED_COMPRESS_RESULT_PREFIX→FAILED_COMPRESS_RESULT_PREFIXES = ["[Compression FAILED", "[Compression refused"];isFailedCompressResultmatches any of them, keeping [PR] Failed compression calls become collapsible regular messages, no longer permanently occupying the context. | 失败的压缩调用变成可折叠的普通消息,不再永久占着上下文 #4291's dialect-aware signature (config.isCompressTool). No call-site changes — [PR] Failed compression calls become collapsible regular messages, no longer permanently occupying the context. | 失败的压缩调用变成可折叠的普通消息,不再永久占着上下文 #4291'scollectFailedCompressCallIdsalready feeds every judgment site (compress.ts assignRefsNode, unit-projection.ts filterProtectedToolMessages, recommend.ts buildCompressibleRanges, ccr.ts, absorb.ts, isMessageProtectedWithPairing).kernel/tests/issue4289-failed-compress-fold.test.ts: union of Failed compression invocations are never folded back for reclamation, and the context keeps ballooning under the weight | 失败的压缩调用永远不会被折叠回收,上下文被压缩残渣越撑越大 #4289's suite and 3 new Compression calls with lost ACKs are permanently stuck in context: client-side message loss / model-generation switch re | 回执丢失的压缩调用永久滞留上下文:客户端丢消息/切模型世代重置产生孤儿,且无任何回收路径 #4307 refused-receipt tests (refused pairs get numeric refs / fold into blocks; both prefixes recognized in unit shapes; success stays protected in the same session). 14 tests total.Not renaming the proxy emitters (
src/stream.tsgate: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
testlegs + static gates + all guards green, 2 windows shards still running at review time; kernel suite 1437/1437 per author.90b95c2df): integration verified by delta comparison (the mergedprotected.tsequals 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