Repository navigation
[PR] Compression always failing and requiring many retries? Changed collapsed/protected region intervals to successful no-op | 压缩总是失败要重试很多次?已折叠/保护区区间改为成功 no-op 收据,并刷新 armed-but-silent 窗口的 ref 指针 (#4521) - #5253
Conversation
…op, not FAILED (#4521) Long sessions hit "compression FAILED, retry" loops: the model re-reported ranges already covered by live blocks, guessed endIds beyond the highest allocated ref, or aimed entirely inside the protected zone. Every one of those "nothing left to fold" rounds was classified FAILED — the atomic batch was refused whole, the breaker counted strikes, and the failure text taught the model to guess again (live session 01a18a4d: 4 consecutive FAILED at 20:09 before one succeeded). Five fixes: - endId beyond the highest allocated ref clamps down to it (warning, not a batch refusal) - a range fully covering live blocks is an idempotent no-op success receipt (result.noop: true, warning points at bN..bM rewrite instead of resending originals) — no failure, no breaker arm - pure protected-zone ranges return the same no-op shape ("do not resend") - protected-zone fatals downgrade that range instead of aborting the batch - new armed-but-silent status line: when a tiered nudge arm is deferred (cadence/backoff/model-decided timing) or a banner was already shown, an ephemeral trailing user message refreshes "highest allocated ref + compressible ranges" so the model stops guessing refs; fresh below-threshold sessions stay fully silent (#196 regime scoping, #3910 lane baseline) kernel-change: compress result contract gains noop + warnings semantics (no new config surface); processTurn gains the statusLine refresh. Hosts render the no-op receipt and append the status line on the anthropic/openai/google/responses lanes.
📦 Built Package ArtifactBranch: Latest build — install this exact versionnpm install -g billion-context@0.0.7327-pr.5253.6c9ab8bThis exact version is immutable and reproducible. The rolling Recent builds (commit → version)
Download artifact
tar xzf billion-context-pr5253.tgz
npm install -g packageInstall instructions are refreshed on each push. |
The kernel bump regen dropped the root package entry's declared mysql2/zod dependencies block and the trailing newline, so the lock-sync gate (npm install --package-lock-only + diff) fails on any regen. Regenerated with the exact CI command set; kernel/package-lock.json was already in sync.
…bset guard) The #4521 empty-range relocation left two unused parameters silenced with 'void' statements, which the verifiable-subset translator does not accept (VOID_EXPR) — tests/verif-subset-guard.test.ts reds on the PR head. The function body only ever used 'input', so the parameters are removed with the single call site updated.
ranxianglei
left a comment
There was a problem hiding this comment.
评审结论:功能代码通过,合并前有一项 CI 必红要修 —— kernel 版本没 bump
这是啥问题 / 怎么修的
#4521「压缩总是失败、要重试很多次」的活锁。模型在 armed-but-silent 窗口里看不到当前 ref,凭记忆猜号;猜到的 ref 越界、区间已被折叠覆盖、或整段落在保护区——这三类「其实无事可做」的合法请求过去一律整批 FAILED,断路器计数 + 失败提示催重试 → 再猜再报 → 循环(实测会话 01a18a4d,20:09 连挂 4 次)。本 PR 五处修正:endId 越界钳制、covered 区间幂等 no-op 收据、纯保护区 no-op、zone fatal 降级为跳过、armed-but-silent 窗口刷新状态行。修法对症,且三类 no-op 都有明确的「勿重发该区间 + 指向可用目标」的警告文本,不是单纯吞错。
代码审查(通过的部分)
- 契约面干净(kernel/src/types.ts):
ApplyCompressionResult.noop?: true、ProcessTurnResult.statusLine?: string,都是可选加法,无破坏性改动;validateCompressionRange空区间判定移到调用方改为 no-op outcome,唯一调用点已更新。 - no-op 三分类边界正确(kernel/src/compress.ts):pureResubmit gate 的条件(counted=0 ∧ consumed>0 ∧ 无 dangling/unknown)与「混合批保持拒绝」的取舍有注释说明且合理;zone fatal 只降
kind === "zone",其他 fatal 照旧抛;covered-span 与 filter-空区间两个 no-op 都在applySingleRange内返回,不污染批级状态。 - 误伤面核查过:start 锚点越界仍保持硬拒(真 targeting error),只有 endId 端钳制——
snapToNeighborVisible用 visible-ref 序,语义正确;新会话低于阈值计划(tier===null 且从未发过横幅)statusLine 完全静默,#196/#3910 基线不破;[Compression no-op:前缀登记进 FAILED_COMPRESS_RESULT_PREFIXES(kernel/src/protected.ts),收据会被折叠而非残留堆积。 - host 侧四条 lane(prepare-anthropic/google/openai/responses.ts):statusLine 走 ephemeral trailing user message 载体,位置正确(在 retrieve-note 之前追加不重排缓存消息);responses lane 有 Array.isArray 防御。src/stream.ts no-op 分支复用 compressionRecovery hint——no-op 告诉模型「做什么」而不只是「别重发」,这是对闭环的关键一环。
- 测试:新增 issue4521-retry-livelock.test.ts 含负向例(start 硬拒不出 clamped 字样、横幅同轮送达时 statusLine=undefined);17 处旧契约断言翻转到 no-op 契约而非删除。
本地确认性测试(正式留给 CI)
- kernel 全套:1497/1497 绿
- host 侧定向 74 套件(compress/nudge/advisory/stream/fork/issue3910/google/responses/claude-bridge 等):505 pass / 0 fail / 2 skip(首轮 9 个失败是本地 kernel/dist 未重建的环境问题,
npm run build后全绿,非 PR 缺陷) tsc --noEmit绿、root build 绿
⛔ 合并前必须修(CI kernel-guard 会红)
.github/workflows/ci.yml kernel-guard Rule 2 明文:kernel/src 有改动而 kernel/package.json version 未变 → exit 1。本 PR 改了 5 个 kernel/src 文件但 package.json 没动版本(diff 为空),必然被拦。连带两件事:
- PR 描述的版本行是错的:写的是 "kernel 版本 0.0.170 → 0.0.171",但 master 经 #5182 已经在 0.0.171,且分支里根本没有 bump。请按实际改后版本更正描述。
- 与 #5236 的 bump 顺序耦合:#5236(尚未合并)占用了 0.0.172。若 #5236 先落 master,本 PR bump 到 0.0.172 会撞车需要再改;若本 PR 先合,#5236 那边得让路。建议按最终合并顺序定号,并在 bump 时同步根
package-lock.json(acp-kernel file: 依赖的 resolved version,参考 #4978 的 lock-sync 先例)。
修完 bump + 描述后即可人工合并(kernel 变更按 Boundary 规则留人工)。
…tatusLine (kernel-guard Rule 2) kernel/src changed five files; the PR body's "0.0.170 → 0.0.171" line was stale — master is already at 0.0.171 (#5182) and this branch carried no bump, which trips ci.yml kernel-guard Rule 2 (src changed without version bump → exit 1). Bump to 0.0.172 and re-sync both lockfiles. If #5236 lands first with 0.0.172, re-sync to 0.0.173 on merge.
|
评审中的唯一阻塞项已直接修复并推送(85ed1f6):
本地确认性测试已在推送前跑完:kernel 全套 1497/1497 绿;host 侧定向 74 套件(compress/nudge/advisory/stream/fork/issue3910/google/responses/claude-bridge)505 pass/0 fail/2 skip;根 tsc --noEmit 绿。正式验证留给本次新触发的 CI。 |
…efore they 400 upstream (#5253) Gemini thinking models reject a whole request when one replayed functionCall/thought part lacks its thoughtSignature. bili forwards client parts verbatim (proven by audit + repro in #5253), so an unsigned part on the outbound was unsigned on the inbound — but until now the lane just died with an upstream 400 every turn and no bili-side trace. prepareGoogle now audits both sides of the rebuild at egress: a sticky per-session latch records that the model signs its calls, and the first affected turn logs a WARN naming counts, content positions, tool names, and the origin — client-resend loss (host/plugin signature replay) vs a bili-side drop (re-report with ACP_DUMP_BODY captures). Re-warns every tenth affected turn only. Pure observability: no outbound bytes, defaults, timing or config surface change.
…+ flip superseded assertions - src/dead-ranges.ts / src/preflight.ts: structural-dead spans now arrive as no-op SUCCESS warnings instead of failures under the new receipt contract, so parseStructuralEmptyVerdict must read errors+warnings together and accept the 'covered by active block(s)' / 'protected or filtered' wordings (the zone wording stays deliberately unrecorded — the recent zone drifts back into life). Restores the #2638 preview-rejection memory without extra upstream calls. - Flip failure-era assertions the contract supersedes: #2146 mixed range now clamps+applies with a warning instead of failing; #2360 covered resubmit is a '[Compression no-op:' receipt; #3302 Ask 3 re-triggers its fallback via a stale-ref FAILED round; never-preserve-recent-tools x3 read their verdict from result.warnings. - Recalibrate the #2929 probe anchor 7/8 -> 6/7: the armed-but-silent status line feeds the nudge gate shown-markers, shifting the injection schedule from {7,8,9} to {4,6,8,9}; the fold/proxy-tool-round byte-stability invariant under test is unchanged.
|
主 CI 矩阵红了(8 个 shard、去重后 10 个用例),已逐一定因并修复(3197f10)。分三类: A. 本 PR 的契约遗漏 —— 已在 3197f10 修掉新收据把「无事可做」从失败(errors)改成了成功+警告(warnings),有两条下游没跟着翻:
B. 与本 PR 无关(不在本 PR 里动)
验证
3197f10 已触发新一轮 CI;按惯例合并动作留人工(Kernel Boundary)。 |
📦 Merged Build PublishedThis merge to master was published as npm install -g billion-context@0.0.936-master.69be0b7 # this exact build
npm install -g billion-context@master # always the newest merged build
|
|
更正一个 issue 关联错误(合并后说明,不改动代码):本 PR 标题、commit message 和测试文件名里写的 #4521 是误引。#4521 实际是「codex 本地自动压缩在 OpenAI 线路漏检测」(它的修复 PR 是 #4542,仍 OPEN,与本 PR 无关)。 本 PR 实际修复的问题对应 #4240(已关闭,「已覆盖区间整批拒、好区间陪葬」)以及 #5185 家族单里的以下形态:
#5185 的残留部分(存量摘要引用死 ref、split-guard 拒绝)未包含在本 PR,继续在该单跟踪。kernel/tests/issue4521-retry-livelock.test.ts 的文件名属于同一误引,行为本身是正确回归钉;若在意命名,后续小 PR 重命名即可。 |
… 0.0.172 (order coupling noted in PR 5253)
(ai: vllm-qwen/qwen3.8-27b) merge-guard 同步完成:master 已自行推进到 e193861(kernel 经 #5253 合入后由 1fa1f11 升到 0.0.173),已干净合入本分支(零冲突),推送为 2e29733。推送后状态:open / mergeable / 仅 CI 重跑中,不再 behind。 关于顺序耦合的重要更新:你推的 85ed1f6(0.0.172 bump)已经在 master 里了——它作为 #5253 链路的一部分进了主干,随后 master 自己又 bump 到 0.0.173。所以耦合已由上游消解,本 PR 无需再做任何版本动作:合并后本分支 kernel = 0.0.173 == master,且 本地验证:typecheck + build 绿(build-info commit=2e29733ed),定向测试 issue5211 + issue-393 共 50/50 pass;全量交给本次新触发的 CI。 另:共享工作区里发现一批与本 PR 无关的未提交改动(内容是对 #5109/#5095 特性的本地回退,mtime 早于本次唤醒、来源不明),已备份为 patch 并 stash( |
(ai: vllm-qwen/qwen3.8-27b) merge-guard 同步完成(本轮 master 走了两步):→ 71d2494(含 #5231、#5303)干净合入并推送 fcb1c4d,验证后 master 又进 3 个 commit(#5307,纯 docs/benchmark 图片,0 代码文件变化),再合入推送 75d728b。推送后状态:open / mergeable / 仅 CI 重跑中,behind=0。验证:typecheck+build 绿(fcb1c4d);定向测试 56/56 pass(issue5211 + issue-393 + #5220 failed-compress-receipt,确认本 PR 与 master 新收据契约在 src/server.ts 上无语义冲突)。 给线程的一个重要事实核对:你两条更新里的工作已经在 master 了 —— B.4(#4982 e2e draining)复核:我在含最新 master 的合并树本地重跑 |
什么东西坏了
长会话里压缩「总是 FAILED,重试很多次才成功」。实测会话 01a18a4d 在 20:09 连续 4 次 FAILED 才成功一次。
根本原因
模型发出的压缩请求里,有大量「其实无事可做」的合法请求:
这三类全被判 FAILED:原子批整体拒绝、断路器计数、失败提示催模型再试 → 模型再猜再报 → 循环。同时成功横幅之间的「armed-but-silent」窗口里模型看不到任何当前 ref,只能凭记忆猜号,进一步制造失败请求。
怎么解决的(5 项,一个 PR)
result.noop: true的成功形收据(warnings 指引用 bN..bM 改写,勿重发原文),不失败、不进断路器、可安全重发。kernel-change: 压缩结果契约新增 noop+warnings 语义(无新配置面);processTurn 新增 statusLine 刷新;kernel 版本 0.0.171 → 0.0.172。
验证
kernel/tests/issue4521-retry-livelock.test.ts5 例:zone no-op / covered no-op / endId 钳制 / statusLine 窗口 / pureResubmit)tsc --noEmit绿e2e-codex-fake在本机环境 main 上也挂(codex 首跑/环境问题,非本 PR 回归,已验证 stashed main 同样失败)