Skip to content

[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

Merged
ranxianglei merged 6 commits into
masterfrom
2026-10-31_compress-noop-receipt
Oct 31, 2026

Conversation

@ranxianglei

@ranxianglei ranxianglei commented Oct 30, 2026 •

Copy link
Copy Markdown
Owner

什么东西坏了

长会话里压缩「总是 FAILED,重试很多次才成功」。实测会话 01a18a4d 在 20:09 连续 4 次 FAILED 才成功一次。

根本原因

模型发出的压缩请求里,有大量「其实无事可做」的合法请求:

  • endId 猜到尚未发号的 ref 之上(紧急催促下模型外推编号)→ unknown ref → 整批拒绝
  • 重报已被活动块覆盖的区间(成功压缩后工作记忆里还当它是待办)→ 重复报账 → 整批拒绝
  • 整段落在保护区(最新消息不可压)→ 保护区 fatal → 整批中止

这三类全被判 FAILED:原子批整体拒绝、断路器计数、失败提示催模型再试 → 模型再猜再报 → 循环。同时成功横幅之间的「armed-but-silent」窗口里模型看不到任何当前 ref,只能凭记忆猜号,进一步制造失败请求。

怎么解决的(5 项,一个 PR)

  1. endId 越界自动钳制:钳到最高已发 ref,警告说明,不再整批拒。
  2. 全覆盖 = 幂等 no-op 成功收据:区间全部被活动块覆盖时返回 result.noop: true 的成功形收据(warnings 指引用 bN..bM 改写,勿重发原文),不失败、不进断路器、可安全重发。
  3. 纯保护区区间 = 同样 no-op:警告明说「勿重发该区间」。
  4. 保护区 fatal 降级:不再中止整批,只跳过该区间。
  5. armed-but-silent 状态行:tier 级 nudge 已备好但本轮不投横幅时(退避/节拍/模型定时),四条 lane(anthropic/openai/google/responses)以临时 user 消息刷新「最高已发 ref + 当前可压区间」,模型不再猜号。全新会话的低于阈值计划保持完全静默(refactor(persist): delegate SessionStore mechanism to acp-kernel/persist #196 域隔离、Test backfill: No test pins the protocol wiring for Anthropic lane nudge (residual gap from #3903) | 补测:Anthropic lane nudge 的 protocol 接线没有任何测试钉住(#3903 遗留缺口) #3910 基线不破坏)。

kernel-change: 压缩结果契约新增 noop+warnings 语义(无新配置面);processTurn 新增 statusLine 刷新;kernel 版本 0.0.171 → 0.0.172。

验证

  • kernel 单测 1493/1493 绿(新增 kernel/tests/issue4521-retry-livelock.test.ts 5 例:zone no-op / covered no-op / endId 钳制 / statusLine 窗口 / pureResubmit)
  • host 侧 227 例绿(compress/nudge/prepare/stream/advisory/fork/issue3910/google/responses/claude-bridge;17 个旧契约断言翻到 no-op 契约)
  • 根 tsc --noEmit 绿
  • e2e-anthropic 11/11 绿;e2e-codex-fake 在本机环境 main 上也挂(codex 首跑/环境问题,非本 PR 回归,已验证 stashed main 同样失败)

…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.
@ranxianglei ranxianglei changed the title 压缩总是失败要重试很多次?已折叠/保护区区间改为成功 no-op 收据,并刷新 armed-but-silent 窗口的 ref 指针 (#4521) [PR] 压缩总是失败要重试很多次?已折叠/保护区区间改为成功 no-op 收据,并刷新 armed-but-silent 窗口的 ref 指针 (#4521) Oct 30, 2026
@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

📦 Built Package Artifact

Branch: 2026-10-31_compress-noop-receipt (3197f10)

Latest build — install this exact version

npm install -g billion-context@0.0.7327-pr.5253.6c9ab8b

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

Recent builds (commit → version)

commit version build
3197f10 billion-context@0.0.7327-pr.5253.6c9ab8b build
85ed1f6 billion-context@0.0.7292-pr.5253.150de5c build
5096940 billion-context@0.0.7285-pr.5253.a1a4992 build
ad0ce65 billion-context@0.0.7254-pr.5253.9b86625 build
bc18783 billion-context@0.0.7192-pr.5253.3f6601c build

Download artifact

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

Install 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.
ci-check and others added 2 commits October 31, 2026 06:44
…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 ranxianglei left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

评审结论:功能代码通过,合并前有一项 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 为空),必然被拦。连带两件事:

  1. PR 描述的版本行是错的:写的是 "kernel 版本 0.0.170 → 0.0.171",但 master 经 #5182 已经在 0.0.171,且分支里根本没有 bump。请按实际改后版本更正描述。
  2. 与 #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.
@ranxianglei

Copy link
Copy Markdown
Owner Author

评审中的唯一阻塞项已直接修复并推送(85ed1f6):

  1. kernel 版本补 bump:kernel/package.json 0.0.171 → 0.0.172——此前分支改了 5 个 kernel/src 文件但版本未动,ci.yml kernel-guard Rule 2(src 有改动而 version 未变 → exit 1)会拦下。
  2. 锁文件同步:根 package-lock.json 与 kernel/package-lock.json 用 npm install --package-lock-only 重新生成,diff 仅 3 行 version。
  3. PR 描述更正:原写 "kernel 版本 0.0.170 → 0.0.171" 有误(master 经 [PR] compression: protect the newest HUMAN request, not the host reminders injected after it #5182 已在 0.0.171,原分支无 bump),已改为 "0.0.171 → 0.0.172"。

⚠️ 顺序耦合:#5236(未合并)在其工作分支上占用了 0.0.172。若 #5236 先落 master,本 PR 需同步到 0.0.173(一个号+两个锁文件,一分钟的事);若本 PR 先合,#5236 让路即可。

本地确认性测试已在推送前跑完:kernel 全套 1497/1497 绿;host 侧定向 74 套件(compress/nudge/advisory/stream/fork/issue3910/google/responses/claude-bridge)505 pass/0 fail/2 skip;根 tsc --noEmit 绿。正式验证留给本次新触发的 CI。

ranxianglei pushed a commit that referenced this pull request Oct 31, 2026
…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.
@ranxianglei

Copy link
Copy Markdown
Owner Author

主 CI 矩阵红了(8 个 shard、去重后 10 个用例),已逐一定因并修复(3197f10)。分三类:

A. 本 PR 的契约遗漏 —— 已在 3197f10 修掉

新收据把「无事可做」从失败(errors)改成了成功+警告(warnings),有两条下游没跟着翻:

  1. [Bug][preflight] range selector re-nominates a fully-folded span every turn — permanent "no new compressible messages" rejection loop + nudge churn #2638 死区记忆失效(真防线缺口,非断言问题):preflight 的"结构死区"记忆(src/dead-ranges.ts)靠解析 error 文案识别"这个区间压不动",只读 result.errors。契约一改,空区间判定走了 warnings 通道 → 记忆永远不生效,死跨度每轮重新提名、白烧上游 summarize 调用。修法:错误+警告合并输入;接受两种新措辞(covered by active block(s)… / protected or filtered…);zone 措辞刻意不收进记忆——recent zone 会随新消息漂移,应保持可重试。原措辞正则保留兼容旧 kernel 降级路径。
  2. 五处旧时代断言漏翻转(语义都在测试注释里写明了新契约依据):dsh内部问题 出现这个错误后,重启dsh可以发消息 但陷入无限压缩失败 #2146 混合区间现在是「钳制到 m00008 + 应用」而非整批失败;bili 插件模式下 allowDshCompaction 在 responses 线路上完全失效 —— DSH 原生压缩落地、折叠基底被摧毁、compress 连片失败 #2360 覆盖重发现是 [Compression no-op: 收据;[compress] Still present after #156: the LLM also gets stuck on an invalid end ref; #370's same-parameter short-circuit fails to prevent it. | [compress] #156 之后仍存在:大模型同样卡在无效 end ref,#370 的同参数短路挡不住 #3302 Ask 3 的 fallback 触发改用一个真·陈旧 ref 轮次;never-preserve-recent-tools ×3 的判定从 errors[0] 改读 result.warnings。
  3. codex 命中率问题分析 #2929 探针锚点重校准(7/8 → 6/7):armed-but-silent 窗口的状态行本身是设计行为,但它的"已显示"标记会回喂 nudge 门控,注入排期从 master 的 {7,8,9} 位移到 {4,6,8,9}——探针钉死的字节稳定性不变量不受影响(实测仍通过),只是锚点下标要随新排期重校准。master/分支两侧同脚本对照序列已核对。

B. 与本 PR 无关(不在本 PR 里动)

  1. #4982 e2e draining same-lane predecessor:在 master(c0fa5b2)本地同样挂(6/7,~3.7s)⇒ master 既有问题,建议另开 issue 单独追。
  2. claude-native hook e2e(另一端口 proxy 永不 attach):master 与分支本地都过 ⇒ CI runner 环境/时序因素,主矩阵绿了之后复看这一例即可。

验证

  • 6 个受影响文件定向回归:34/34 绿;
  • 扩展回归(compress/nudge/advisory/stream/fork/preflight/dead-range/responses/google/bridge 等 74 套件口径):715 pass / 0 fail / 2 skip;
  • 根 tsc --noEmit 绿;本轮未动 kernel(此前 1497/1497 结论继续有效);
  • 临时诊断脚本已全部清理,工作树仅含上述 8 个文件。

3197f10 已触发新一轮 CI;按惯例合并动作留人工(Kernel Boundary)。

@ranxianglei
ranxianglei merged commit 69be0b7 into master Oct 31, 2026
24 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

📦 Merged Build Published

This merge to master was published as 0.0.936-master.69be0b7 (rolling master tag).

npm install -g billion-context@0.0.936-master.69be0b7  # 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.

@ranxianglei

Copy link
Copy Markdown
Owner Author

更正一个 issue 关联错误(合并后说明,不改动代码):本 PR 标题、commit message 和测试文件名里写的 #4521 是误引。#4521 实际是「codex 本地自动压缩在 OpenAI 线路漏检测」(它的修复 PR 是 #4542,仍 OPEN,与本 PR 无关)。

本 PR 实际修复的问题对应 #4240(已关闭,「已覆盖区间整批拒、好区间陪葬」)以及 #5185 家族单里的以下形态:

  • Shape 2:整段落入保护区的区间 → 原整批拒绝,现降级为警告,好区间照常折叠
  • Shape 1/3/4 的 endId 越界 → 原直接失败,现自动钳到最高已发 ref
  • 已覆盖区间的重报 → 幂等 no-op 成功收据,收据点名块号并引导用 bN..bM 改写

#5185 的残留部分(存量摘要引用死 ref、split-guard 拒绝)未包含在本 PR,继续在该单跟踪。kernel/tests/issue4521-retry-livelock.test.ts 的文件名属于同一误引,行为本身是正确回归钉;若在意命名,后续小 PR 重命名即可。

@ranxianglei ranxianglei changed the title [PR] 压缩总是失败要重试很多次?已折叠/保护区区间改为成功 no-op 收据,并刷新 armed-but-silent 窗口的 ref 指针 (#4521) [PR] Compression always failing and requiring many retries? Changed collapsed/protected region intervals to successful no-op | 压缩总是失败要重试很多次?已折叠/保护区区间改为成功 no-op 收据,并刷新 armed-but-silent 窗口的 ref 指针 (#4521) Oct 31, 2026
ranxianglei added a commit that referenced this pull request Oct 31, 2026
@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

(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,且 git diff origin/master -- kernel/ 为空(PR 的 kernel 侧改动已全部在 master),对 master 的净 diff 只剩 host 侧 7 个文件(#5211 面)。若 #5236 工作分支还停在 0.0.172,现在轮到它落地前重新对齐了(方向反转)。

本地验证:typecheck + build 绿(build-info commit=2e29733ed),定向测试 issue5211 + issue-393 共 50/50 pass;全量交给本次新触发的 CI。

另:共享工作区里发现一批与本 PR 无关的未提交改动(内容是对 #5109/#5095 特性的本地回退,mtime 早于本次唤醒、来源不明),已备份为 patch 并 stash(wake6 pre-sync dirt…),未丢弃也未提交,不影响本分支。

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

(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 了 —— 3197f10a(CI 修复)+ 85ed1f6b(kernel bump)所在的 2026-10-31_compress-noop-receipt 分支经 #5231 已整体合入 master(git 核实:该分支所有 commit 均为 origin/master 祖先,git diff 为空)。即 no-op 收据工作(#4240/#5185 家族)已随 #5231 落地,#5258 对 master 的净 diff 现在只剩 #5211 foreign-origin-form-path 拒绝面(src/acp-status/server/admin/handle/observability + 2 测试,+508/-11,kernel diff 为空)。你第二条更正(#4521 误引)作为历史记录成立,无需再动。

B.4(#4982 e2e draining)复核:我在含最新 master 的合并树本地重跑 tests/issue4982-draining-twin-ladder.test.ts → 7/7 全绿(失败的那个子测试耗时 4.8s 通过)。结合你「master c0fa5b2 挂 6/7」的观察,判定为间歇性时序 flake 而非确定性 master 回归(且本 PR diff 不碰 port-zone/launcher 路径,可排除诱因)。按 CI 协调政策未开新 issue,已在 #4599 留结构化记录(证据链 + 「断言失败非超时、复红时抓日志」建议方向),待其稳定复现再立项。

@github-actions github-actions Bot mentioned this pull request Oct 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant