Skip to content

[PR] fix: pause compression nudges when folds stop shrinking the session - #5092

Open
ranxianglei wants to merge 19 commits into
masterfrom
2026-10-29_net-progress-guard
Open

ranxianglei wants to merge 19 commits into
masterfrom
2026-10-29_net-progress-guard

Conversation

@ranxianglei

@ranxianglei ranxianglei commented Oct 29, 2026 •

Copy link
Copy Markdown
Owner

Closes #5058

What this fixes

Companion-style long sessions (hundreds of turns, days) hit a pure positive feedback loop in plugin mode: every nudge round ends in a successful compress whose own action cost (tool-call args incl. summary text, receipts, turn reasoning — all persisted by the host and re-sent next turn) outweighs what the fold removed. The kernel's baseline reset after each successful commit makes the next nudge due another flat 50K step later, and the action re-trips the growth gate — no convergence. In the reported session (0.1.208, dsh-native, deepseek-flash 750K window) that walked the host's local store to 30 MB / 24,051 events until dsh's pre-send overflow guard refused everything locally: no provider error, so no automatic recovery anywhere could fire, and the session was permanently bricked.

Every standing guard misses this shape by design:

How it works

New src/net-progress-guard.ts adds the trajectory view:

  • Baseline at each successful fold commit (recordNetProgressBaseline, src/stream.ts): live-view token total + rendered post-fold estimate, both from the same kernel counter over the same request (postCtx≈0 的退化折叠会摧毁缓存前缀(shrink 287%/129%,cache ceiling ≥0%) #1911 same-space discipline). A new fold opens a fresh evidence window; strikes reset.
  • Round check once per nudge-carrying request (evaluateNetProgressRound, src/server/handle.ts, inside the existing !selfHealRoundDone && prepared.nudge !== undefined block): the client's raw resend (countMessageTokens over prepared.originalMessages) below pre-fold-level-minus-margin (NET_PROGRESS_MARGIN_TOKENS = 25_000, half the flat step — absorbs estimator noise and the one-round materialization lag where the post-compress re-request still carries the unfolded history, fix: net post-compress usage credit + hand launcher context windows to the proxy (#252) #252) counts as progress and resets; otherwise a strike. NET_PROGRESS_STRIKE_LIMIT = 2 consecutive non-progress rounds arms.
  • Remedy: nudge suppression through nudgeInjectionBlocked(session) (the 会话卡死自愈机制:卡死模式检测与代理侧主动补救 #2155 predicate OR'd with this latch) at all four lane gates — the decide-ladder fallback punch-through is preserved (same dsh上下文700K不触发压缩 #3004 deadlock guard as D1). Latch state { baseline, expectedFloor, strikes, armedAt } kept PROCESS-LOCAL in a WeakMap keyed by the session object — never persisted: persist serializes session.metadata into the state file, and this guard fires on every successful fold commit, so a persisted latch would drift the byte-for-byte storage goldens on every long session ([PR] Warn when the outbound bili tool set flips mid-session (#4789) #4790 precedent, same mechanism family). Wall expiry reuses SELF_HEAL_SUPPRESS_CAP_MS (10 min); any real reduction lifts it early; an evicted-and-reloaded session re-seeds silently (a fresh arm needs one more successful fold plus at most 2 non-progress rounds). Evaluate runs while suppressed so recovery can be observed.
  • Observability: armed state on /__bili/sessions (netProgress) + Web UI badge ("swelling"); arm/recovery/expiry log [net-progress] lines with the observed-vs-baseline numbers and rotate-to-fresh-session advice.

Docs: CLIENTS.md (en+zh) dsh section documents the overflow-wall failure mode, the known refusal wording for host-side detection regexes, and the mitigations (rotation / compress.nudgeGrowthTokens / file-size watch / #5052 pointer); TECHNICAL-NOTES.md records the constants beside the #2155 section.

Behavior change (old → new)

Sessions whose folds stop shrinking them now get their compression nudges PAUSED after two consecutive non-progress rounds (old: nudges continued forever, each compress enlarging the session further). The pause lifts on the first round that demonstrably reduces the context below pre-fold-level-minus-margin, or after 10 minutes wall-clock. Healthy sessions are unaffected: any fold buying back more than ~25K tokens of growth counts as progress immediately. This is a suppression (the decide ladder's fallback escape still punches through one legacy advisory), not a removal of the compression surface.

Config surface

None. Two named module constants by design (#2155 precedent: guard thresholds are internals, not operator knobs). Operators who want a lazier cadence for companion sessions already have compress.nudgeGrowthTokens.

Verification

  • Local targeted: new suite tests/issue5058-net-progress-guard.test.ts (strike state machine, boundary, wall cap, fresh-window reset, corrupt-state tolerance, OR semantics vs the 会话卡死自愈机制:卡死模式检测与代理侧主动补救 #2155 latch) + tests/issue2155-self-heal.test.ts + tests/lane-decisions-matrix.test.ts + tests/web-i18n.test.ts — all green (123 tests); typecheck clean; build clean (dist-external OK). Full-suite verdict from CI on this head.
  • Version-first triage: v0.1.208 carries the identical standing-guard set; nothing in v0.1.208..HEAD changed nudge gating — the spiral was reachable in the reporter's build exactly as analyzed.

Out of scope (tracked separately)

CI fix after first push (head b88abf4 was red)

The first commit stored the latch in session.metadata, which persist serializes into the state file — every long-session storage golden drifted its persisted-state digest deterministically on all three CI platforms (six red test shards: storage-engine-proofs full-mode restart x4 wires + golden-storage-render x4 wires; master itself green). Follow-up commit 3f4b7efe3 keeps the state process-local (WeakMap keyed by the session object, #4790 precedent) and adds a regression pin asserting session.metadata stays untouched across every mutation path. Behavior delta vs the first commit: the latch no longer survives a proxy restart (it self-expires within 10 min anyway), and the /__bili/sessions netProgress badge shows live sessions only.

ci-check added 2 commits October 29, 2026 10:29
…5058)

Companion-style long sessions hit a pure positive feedback loop: every nudge round ends in a successful compress whose own action cost (tool-call args, receipts, reasoning — all persisted by the host and re-sent next turn) outweighs what the fold removed, so the flat-50K growth gate re-arms forever and the session walks past its window while every standing guard stays silent (the #2748/#3322 gate judges one commit in isolation, the #2146 breaker counts failures, #2155 D1 needs reductions to STOP landing, #3461 covers host emergency injections only).

Add the cross-round trajectory view: recordNetProgressBaseline at each successful fold commit (live-view tokens + post-fold estimate, same kernel counter space per #1911); evaluateNetProgressRound once per nudge-carrying request compares the client's raw resend against pre-fold-level-minus-margin (25_000 = half the flat step). Two consecutive non-progress rounds latch a per-session nudge suppression through nudgeInjectionBlocked at all four lane gates (decide-ladder fallback punch-through preserved, #3004 deadlock guard shared with D1); any demonstrable reduction lifts it early and the SELF_HEAL_SUPPRESS_CAP_MS wall cap bounds silence. Armed state surfaces on /__bili/sessions (netProgress) and as a Web UI badge; arm/recovery/expiry log [net-progress] lines.

No config surface — constants by design (#2155 precedent). Tests: tests/issue5058-net-progress-guard.test.ts (strike state machine, boundary, wall cap, fresh-window reset, corrupt-state tolerance, OR semantics against the #2155 latch).
)

CLIENTS.md (en+zh): the pre-send local refusal failure mode for long-lived plugin-mode sessions (dsh's overflow guard measures its own unpruned local store, so once it exceeds the window no traffic reaches bili and no automatic recovery can fire), the known refusal wording for host-side detection regexes, and the rotation / nudgeGrowthTokens / file-size-watch mitigations plus a pointer at #5052.

TECHNICAL-NOTES.md: net-progress guard constants and semantics (baseline/strike/margin/wall-cap), alongside the #2155 self-heal section.
@github-actions

github-actions Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

📦 Built Package Artifact

Branch: 2026-10-29_net-progress-guard (c539dd1)

Latest build — install this exact version

npm install -g billion-context@0.0.7163-pr.5092.7e06930

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
c539dd1 billion-context@0.0.7163-pr.5092.7e06930 build
d17689b billion-context@0.0.6938-pr.5092.c90e582 build
a18a925 billion-context@0.0.6925-pr.5092.a90b116 build
f5d3410 billion-context@0.0.6915-pr.5092.fa6aaa2 build
4fc74f1 billion-context@0.0.6827-pr.5092.1d88376 build
98dc0ba billion-context@0.0.6733-pr.5092.e626301 build
ddf42e4 billion-context@0.0.6729-pr.5092.7b56925 build
3f4b7ef billion-context@0.0.6728-pr.5092.1ae24a3 build
b88abf4 billion-context@0.0.6704-pr.5092.7f0e2fa build

Download artifact

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

Install instructions are refreshed on each push.

@ranxianglei ranxianglei changed the title fix: pause compression nudges when folds stop shrinking the session [PR] fix: pause compression nudges when folds stop shrinking the session Oct 29, 2026
@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

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

查重:不重复。 本 PR 是 #5058(陪伴类长会话 nudge 自我放大直到超窗永久报废)的修复单;open+closed issue/PR 全量检索无同机制的并行工作(跨轮净进展守卫 / nudge 暂停)。相关条目:#5033(同死锁模式不同机制,不并案)、#5052(关联功能请求)。

这是已提交的修复 PR(#5058 线程 floor 27),按规则不重做 —— 现在做独立审核:重新通读全量 diff、审既有行为回归、本地定向测试、核对 pushed head 的 CI 状态。完成后按结构化八问回复结论。

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

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

进度汇报(进程曾被重启,已恢复继续):独立审核进行中。已完成:通读新模块 src/net-progress-guard.ts(140 行)与全部接线 diff(handle.ts / stream.ts / lane-decisions.ts / prepare-*×4 / web 三件)。正在核实一个关键前提 —— 「基线与轮次测量同 token 空间」:stream.ts 的基线记在 ctx.messages(按 #3307 注释是压缩后出站视图),而 handle.ts 的轮次测量用的是 prepared.originalMessages(客户端原始重发);正在追主循环 ctx.messages 的实际填充来源,确认两者是否可比。之后:测试有效性审查、本地定向测试、CI 状态核对,最后按八问给结论。

ci-check added 2 commits October 29, 2026 14:13
recordNetProgressBaseline ran on EVERY successful fold commit and wrote
session.metadata["netProgressGuard"]; persist serializes session.metadata
into the state file, so every long-session storage golden drifted its
persisted-state digest deterministically on all three CI platforms (the six
red test shards at b88abf4: storage-engine-proofs full-mode restart x4
wires + golden-storage-render x4 wires).

The latch now lives process-local in a WeakMap keyed by the session object
(#4790 precedent - same mechanism family, same fix shape): entries die with
their session object, an evicted-and-reloaded session re-seeds silently,
which is harmless because the latch self-expires within
SELF_HEAL_SUPPRESS_CAP_MS (10 min) anyway, and a fresh arm needs one more
successful fold commit plus at most NET_PROGRESS_STRIKE_LIMIT non-progress
rounds. /__bili/sessions netProgress reads the live store (badge visible
while the session object is live; disk rows do not badge). Adds a regression
pin asserting session.metadata stays untouched across every mutation path.
@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

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

CI 红已修复,全车道绿。 最终 head ddf42e422(修复 3f4b7efe3 + merge master 的 README 同步),30 checks / 0 失败:11 个 test shard(ubuntu-22/24 × windows-22)+ static-gates + e2e 车道全过。

八问:

  1. 是什么问题:PR 首推(b88abf4be)CI 六个 test shard 红——不是继承性失败(master 6b298c3 全绿、ci-fixes c24c303 ⊂ master),是 PR 自己把新 guard 的状态写进了会被持久化的 session.metadata,导致两套字节级存储 golden 确定性漂移。
  2. 什么原因导致的:persist 把整个 session.metadata 序列化进 state file;而 recordNetProgressBaseline 在每次成功 fold commit 无条件写 netProgressGuard → 每个长会话 golden 的 persisted-state digest 与 committed goldens 对不上(storage-engine-proofs full-mode restart ×4 wire + golden-storage-render ×4 wire,正好 6 个红 shard)。层次判断:用户可见症状是 CI 红,直接原因是 golden digest 漂移,根因是「易失运行时状态被写进持久化文档」——与 [PR] Warn when the outbound bili tool set flips mid-session (#4789) #4790(tripwire 把 toolsetFp 写进 metadata,改进程内 WeakMap 修掉)同一机制族。本地复现拿到与 CI 完全一致的漂移值后动手。
  3. 影响范围与严重程度:仅影响本 PR 分支的 CI;未合入、不影响任何已发布版本或运行中的用户。修复对运行行为零影响(状态机逻辑一字未动,只换存储位置)。
  4. 怎么修复的:guard 状态从 session.metadata["netProgressGuard"] 改为按 session 对象键控的进程内 WeakMap([PR] Warn when the outbound bili tool set flips mid-session (#4789) #4790 同款修法):条目随 session 对象消亡,evict 后重载的会话静默重播种;补了回归钉断言所有 mutation 路径下 session.metadata 保持不被触碰;/__bili/sessions 的 netProgress 改读 live store;TECHNICAL-NOTES.md 同步更正。
  5. 是否有回归问题:无。审过的既有行为:nudge 抑制状态机(strike/arm/reset/wall-expiry)不变——PR 自带套件 10/10;会话卡死自愈机制:卡死模式检测与代理侧主动补救 #2155 self-heal、lane-decisions(矩阵 + B 类)、web-sessions 套件全绿(定向共 163 pass / 0 fail);typecheck 干净;原先红的两套存储 golden 本地与 CI 均转绿。无阈值/时序/输出格式漂移。
  6. 是否破坏缓存:不涉及前缀缓存/重试/去重键;恰恰相反,是把一个新增的易失字段从持久化文档里移除,恢复「full-mode restart 不可见」的既有不变量。
  7. 是否改了设计方向:否。guard 机制(fold 基线 → 连续 2 轮无净进展 → 抑制 nudge → 10 分钟 wall cap / 真实缩减提前解除)完全不变,只改状态存放位置。
  8. 是否引入新配置:无(仍是 constants by design,同 会话卡死自愈机制:卡死模式检测与代理侧主动补救 #2155)。

行为变更披露(本 PR 两个 commit 之间,旧值→新值+原因):latch 状态原设计随 session.metadata 持久化、跨代理重启存活 → 现改为进程内、重启即失。原因:≤10 分钟的易失 latch 不应进入持久化文档(否则每个 fold 都破坏存储 golden 契约);丢失的最坏代价是一次重启后需再一次成功 fold + 至多 2 个非进展轮重新武装,而 latch 本身 10 分钟墙钟到期自动解除。连带:/__bili/sessions 的 swelling 徽章只对 live 会话显示(磁盘行不再显示)。PR 描述已同步更新该段。

验证链:本地复现红(digest 漂移值与 CI 一致)→ 修复后定向套件绿 → push → 远端 CI 全绿(判定以 CI 为准,本地未跑全量)。结论:可合并(请人工合并 #5092 );#4599 已留一行结论。

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

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

这条失败通知是过期重复转发,无需再修。 它指向的 head 仍是 b88abf4be(run 40807262654,即修复前的旧 head)——该失败已在本轮 attempt 1 修复:

  • 根因:recordNetProgressBaseline 每次成功 fold commit 写 session.metadata["netProgressGuard"],persist 把整个 metadata 序列化进 state file → 两套存储 golden(storage-engine-proofs full-mode restart ×4 wire + golden-storage-render ×4 wire)persisted-state digest 确定性漂移,正好 6 个红 shard。与 [PR] Warn when the outbound bili tool set flips mid-session (#4789) #4790 同机制族。
  • 修复:3f4b7efe3 把 latch 状态改为按 session 对象键控的进程内 WeakMap(零持久化副作用)+ metadata 不被触碰的回归钉;ddf42e422 另含 master 同步(README-only)。
  • 当前状态(刚核对):分支 head = ddf42e422,镜像缓存显示 30 checks / 0 failed / 0 pending(11 test shards + static-gates + e2e 全绿),分支已包含全部 master 提交(master 未再前进)。

详细八问见上一条回复。结论不变:可合并(请人工合并 #5092 )。不重复开 issue、不做重复修复。

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

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

[bot] 进程被重启前的进度汇报:本 issue 的工作已全部完成,无阻塞。

当前无待办:等人工合并 #5092 。若合并后有新事件(如 owner 评论或新一轮 CI),再按新指令处理。

…in client-resend space

Review corrections for the #5092 guard (two facets of one measurement
integrity defect):

1. Lane scope — plugin lanes only (session.metadata.pluginAgent bound).
Only plugin hosts persist and re-send the compression action itself, so
only there does the resent payload shrink when a fold works. Proxy-mode
clients keep their full local history and re-send it verbatim every
turn; once folded coverage exceeds the 25K margin the raw resend
structurally never drops below pre-fold-minus-margin, so an unscoped
latch would arm on every mature proxy session and silence the flat-50K
nudge cadence (KDD #8) without defending anything. The #5058 swelling
domain is plugin-mode only.

2. Same-space baseline — recordNetProgressBaseline now takes the inbound
RESEND total (stream.ts rawViewTokens — same array class as the round
check counts over prepared.originalMessages) instead of the rendered
live view. Mixing spaces would distort the bar by the reasoning-drop
delta on heavy-reasoning hosts. postCtx stays the rendered post-fold
estimate (diagnostic only).

Tests: proxy-mode negative row (guard stays inert, cadence untouched);
persistence pin updated. TECHNICAL-NOTES.md: lane-scope +
baseline-space bullets.
@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

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

独立审核结论:方向正确,发现并当场修复两处测量缺陷——其中一处是原实现引入的未披露回归(proxy 模式平 50K nudge 节奏被整体掐灭),已把守卫收窄到插件车道。

diff 全部 15 个文件逐行读过(未沿用 issue 侧结论),四个既有防御机制的漏法逐一验证。

① 方向关 ✅:#5058 根因成立——压缩动作本身(tool-call 参数含摘要全文、回执、回合推理)被插件宿主持久化进历史、每轮重发,fold 省得比动作花得少时纯正反馈不收敛;kernel 每次成功 compress 后重置基线,下一次 nudge 永远再隔一个平 50K 步到来。既有四道防御(#2748/#3322 单轮净收益、#2146 失败断路器、#2155 自愈 D1、#3461 紧急退避)各按设计恰好漏掉这个形状。加跨轮轨迹视图方向对;补救用抑制不用删除,#3004 死锁逃生口保留。

② 回归审计——发现原实现一处未披露回归(已修):baseline 记的是渲染活视图(stream.ts viewTokens),轮次检查量的是客户端原始重发(handle.ts prepared.originalMessages)——两个不同数组类。插件车道宿主重发的就是折叠后历史,两者近似相等,行为正确;但 proxy 模式客户端每轮重发完整本地历史,bili 的 fold 从不缩小它(fold 只缩小发给模型的渲染)→ folded 覆盖一旦超过 25K margin,raw 重发结构性永远落不到 pre-fold-minus-margin 以下 → latch 两轮即 arm → 所有成熟 proxy 会话失去平 50K 主动 nudge 节奏(KDD #8 明确设计决策),只剩 preflight 硬限被动压缩。PR 正文写「all sessions」+「healthy sessions unaffected」,对 proxy 模式不成立;且 10 个单测没有一个钉住调用点的空间/车道接线,CI 全绿也抓不到。修复:守卫收窄到插件车道(session.metadata.pluginAgent 绑定),proxy 恢复原节奏,补负例测试(proxy 会话永不 arm)。

②b 第二处测量空间缺陷(顺手修):即便插件车道内,baseline 也应与观测同一空间(客户端重发量),否则重推理宿主会被 reasoning-drop delta 扭曲门槛。stream.ts 改传 rawViewTokens(与 handle.ts 观测同数组类),postCtx 保持渲染诊断值。TECHNICAL-NOTES.md 同步。

③ 代码关 ✅:状态机逻辑、常量(margin 25K=半步,吸收估算噪声+一轮物化滞后;2 strikes)、wall cap 复用 SELF_HEAL_SUPPRESS_CAP_MS(10min)、与 #2155 的 OR 语义、Web 徽章/i18n 均无问题。作者自推的 3f4b7ef(latch 从 session.metadata 移入进程本地 WeakMap,#4790 先例,修 storage golden drift 红)方向正确,两套 storage 套件本地绿。

④ 测试有效性:单测有牙(严格 < 边界、wall 过期、fresh-window 重置、脏状态容忍),但原本没有覆盖调用点空间/车道接线的测试——②的回归正是从这个缺口漏掉的,已补车道负例。e2e-codex 真客户端套件在 ACP_TEST_E2E=1 之后、CI 默认不跑,此类问题仍依赖人审。

八问:

  1. 是什么问题:插件模式长会话里,当压缩动作自身成本超过 fold 移除量,bili 仍每 50K 催一次压缩,越压越大,直到宿主侧 overflow 硬拒、会话永久不可用([Bug] 陪伴类长会话:compress nudge 自我放大,直到会话超过模型窗口且永久不可恢复 #5058)。
  2. 什么原因导致的:kernel 每次成功 compress 后重置基线,nudge 永远隔一个平步再次到期;既有净收益门只看单轮不看跨轮轨迹,跨轮视角缺失。
  3. 影响范围与严重程度:p1(原 bug):插件长会话完全不可用且无恢复路径。原实现引入的回归(已修):成熟 proxy 会话失去主动 nudge——体验退化,非数据丢失。
  4. 怎么修复的:每次成功 fold commit 记基线;nudge 轮测客户端重发量,连续两轮没低于 pre-fold-minus-margin(25K)则抑制 nudge(非删除,保留 dsh上下文700K不触发压缩 #3004 decide-fallback 逃生);出现真实降幅立即解除,或 10 分钟墙钟自动释放;面板 swelling 徽章可观测。本次追加:作用域收窄到插件车道 + 测量统一为客户端重发空间。
  5. 是否有回归问题:审过的既有行为——平 50K 节奏(KDD feat: release CI (npm auto-publish on release branch) #8):收窄后 proxy 恢复、健康插件会话不变(fold 买回 >25K 增长=立即 progress 重置);会话卡死自愈机制:卡死模式检测与代理侧主动补救 #2155 自愈通道 OR 语义不变(13 测试绿);dsh上下文700K不触发压缩 #3004 decide-ladder 穿透保留;四个 prepare 车道门仅谓词替换(matrix 90/90 绿);存储格式:作者 WeakMap 化后无新字段落盘(golden 两套件绿)。本地定向全绿:issue5058 11/11、issue2155 13/13、issue3004 7/7、lane-decisions-b-class 5/5、matrix 90/90、web-i18n 10/10、golden-storage-render 3/3、storage-engine-proofs 19/19;typecheck clean。
  6. 是否破坏缓存:不影响前缀缓存策略——nudge 注入/抑制不改消息字节;latch 状态进程本地,不落持久化。
  7. 是否改了设计方向:无。纯增量守卫,与既有防御层正交;唯一方向性决定 = 作用域限插件车道(机制真正成立的领域)。
  8. 是否引入新配置:无配置面变更(两个模块常量,跟随 会话卡死自愈机制:卡死模式检测与代理侧主动补救 #2155 先例——守卫阈值是实现内部件,不是操作者旋钮)。面板只读 netProgress 字段为观测输出。

行为披露(old→new):PR 正文称守卫作用于「all sessions」、baseline 为「live-view token total」——修正后实际交付 = 仅插件车道 + baseline = 客户端重发量。原因:proxy 客户端每轮重发完整历史且 fold 不缩小它,套用会纯伤害(KDD #8 节奏被掐);异空间测量在重推理宿主上扭曲门槛。

当前状态与合并判定:修正 commit 本地就绪(定向测试+typecheck 全绿),但推送暂被本环境到 github.com 的网络故障阻塞(Connection reset by peer,含只读 ls-remote 在内多次重试约 10 分钟失败,判断为瞬时出口故障而非凭据问题)。注意:修正第一版 98dc0ba 已推上去,其中新增的 proxy 负例有一个 JS 默认参数坑(undefined 触发默认值)会在 CI 显示 1 红;修正版 6a57f3db4 网络恢复后立即推上并补说明,届时以 CI 为准。CI 绿后建议合并。超范围项 A/B/C(summaryMaxChars clamp / DSH 上游诉求 / compaction 归属策略)已在 PR 正文列出待你拍板,不阻塞本 PR 合并。

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

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

补充状态:到 github.com 的出口网络故障仍在持续(约 25 分钟,只读探测同样失败),修正版 commit 6a57f3db4 仍在我本地工作区就绪,尚未推上分支。当前分支头 98dc0bae2 上那个会红的 proxy 负例测试是 JS 默认参数坑(undefined 触发默认值),6a57f3db4 已修好(本地定向套件全绿 + typecheck clean)。网络恢复后我会立即推上并盯 CI;在此之前请不要以 98dc0bae2 的 CI 结果为准。若故障持续较久,也可由你侧从我的工作区日志跟进——审核结论以上一条八问回复为准。

…inel for unbound fakes

CI (3 shards, same subtest): 'proxy-mode sessions are out of scope' expected no evidence window but got {baseline: 600000, expectedFloor: 120000, strikes: 0}. Root cause is the test helper, not the guard: a JS default argument fires when the passed value IS undefined, so mkSession(undefined) hits the '= "dsh"' default and produces metadata {pluginAgent: "dsh"} — exactly the binding that arms the guard. The implementation already gates on session.metadata.pluginAgent per spec (plugin lanes only), so the proxy-mode row needs a sentinel that survives the signature: null.
@ranxianglei

Copy link
Copy Markdown
Owner Author

(ai: sglang-gpu0/qwen3.8-27b)

CI 三个 shard 全红的是同一条子测试(tests/issue5058-net-progress-guard.test.ts:176「proxy-mode sessions are out of scope and never arm」):期望守卫状态为 undefined,实际拿到 {baseline: 600000, expectedFloor: 120000, strikes: 0}。三个 shard 只是并行跑同一文件,不是三个独立 bug。

根因不在守卫实现,在测试的假会话构造器。 最新提交已经把守卫门控在 session.metadata.pluginAgent 上(只给插件绑定会话 arm,proxy 模式零影响——这个行为本身是对的)。但测试里的 mkSession(pluginAgent = "dsh") 用了默认参数:JS 的默认参数在传进来的值就是 undefined时触发,所以测试想造「无绑定」会话写的 mkSession(undefined),实际拿到的是 {pluginAgent: "dsh"} ——一个带 dsh 绑定的会话。带绑定的会话被守卫记录证据窗是完全正确的行为,于是断言必挂。运行时直接打印了该函数的真实求值结果验证了这一点(mkSession(undefined) 返回 {"pluginAgent":"dsh"})。

修法:测试 helper 改用 null 哨兵(默认参数不会吃 null),两行改动:签名 string | null = "dsh" + 判定 pluginAgent === null ? {} : …,proxy-mode 子测试改调 mkSession(null)。src 零改动,因为插件门控本来就在。

本地验证:node --import tsx --test tests/issue5058-net-progress-guard.test.ts 子测试 11/11 全过(含原本就绿的全部行);tsc --noEmit -p tsconfig.json 零输出干净。

old→new:此前这条本应证明「proxy 模式不 arm」的 CI 行其实一直在 arm 一个 dsh 绑定的假会话;现在它真的构造无绑定会话并证实守卫不碰、nudge flat-50K 节奏不受影响。插件车道行为不变。新提交 4fc74f1,请对新提交看 CI。

ci-check and others added 7 commits October 30, 2026 08:22
…uires (#4599)

The #5140 recovery test proves forkSnapshot reconstructs a non-retained
parent's raw tail from the UNIFIED index — but it never pinned its own
storage mode. Under the CI storage baseline (preload-storage-baseline.mjs
sets BILI_STORAGE_UNIFIED=legacy for the broad corpus) no unified rows
exist, so the reconstruction has no source and forkSnapshot fails closed
with 409 SNAPSHOT_UNAVAILABLE. Deterministic, not a flake: reproduces 100%
on master and any merged branch with BILI_STORAGE_UNIFIED=legacy, passes
without it (default shadow). Pin full like the sibling persist tests and
restore the ambient value afterwards.
@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

(ai: vllm-qwen/qwen3.8-27b) No new merge needed for this trigger: master has not advanced past the previous sync — live master is still 44067a5bc, which head c539dd161 already fully contains (verified by ancestry), so nothing to push. GitHub now reports mergeable_state: blocked (behind cleared, no conflicts, only pending CI) — this "behind" event was a stale/racy detection from the instant master reached 44067a5bc, before the prior push landed.

ework-agent and others added 5 commits October 31, 2026 01:22
ci-fixes at 67f8e89 still leaves four Windows shards red, and all four
deltas already exist on PR #5178. This ports the missing hunks so the
convergence branch actually converges.

1. `test (windows-latest, 2x, 3)` — #5103. `UnifiedShadow` builds its db
   path as `${dir}/index.db`, so the open-failure warning reads
   `C:\...\sessions/index.db`. `issue5103` asserts the warning contains
   `path.join(dir, "index.db")` — `...\index.db` on win32 — so a mixed
   separator never matches. `path` is already imported at :30 and the db is
   opened through the same join, so POSIX output is byte-identical.

2. `test (windows-latest, 22, 4)` + the 4788 leg — #4739/#4788 teardown.
   The suites arm a real `SessionStorage` under
   `BILI_STORAGE_UNIFIED=full`, so the sidecar is still open at teardown.
   `cancelAll()` clears timers and the build map but never closes the
   shadow; Windows refuses to unlink a directory holding an open file
   (`EBUSY`/`EPERM` on `index.db`). Use `closeQuietly()` — cancel, then
   close — in the short-lived-store teardown.

3. `test (ubuntu-latest, 24, 3)` + `(windows-latest, 22, 2)` — #5155.
   Both cold-rebuild cases waited for the debounced durable flush with a
   fixed `setTimeout(20)`. A millisecond budget is a deadline a loaded
   runner can miss, and the miss is an honest `409 !== 200` from a
   one-turn-stale table. Await the settle itself: `afterNextFlush("conv-ptrs")` resolves once the armed save lands.

4. `test (ubuntu-latest, 22, 3)` — #4788 verdict deadlock.
   `afterNextFlush` parks a resolver in `flushWaiters`, and the only thing
   that drains it is the debounce timer — which is armed `unref?.()`.
   Once that timer is the last pending handle the loop drains, the promise
   never settles, and node reports
   `Promise resolution is still pending but the event loop has already
   resolved`. Keep the timer referenced exactly while a waiter exists
   (`ref()` on waiter registration; the host-upkeep default stays unref'd).

Both view hunks and the two test-teardown hunks are byte-identical to the
ones already on PR #5178, so this converges the branches instead of
inventing a third variant.

Verified locally: `npx tsc --noEmit` clean; issue4788 0/4 -> 4/4;
issue4739 4/4; issue5103 5/5; inc-canonical-pointers 3/3.

The `cancelAll` -> `closeQuietly` hunk has no local negative control:
reverting it keeps Linux green because `EBUSY`/`EPERM` only fire where an
open handle blocks unlink. That control can only run on a Windows lane —
stated rather than papered over.

The #4788 arm's own reader is confirmed instead: `cancelAll()` leaves
`index.db`, `index.db-shm`, `index.db-wal` in `/proc/self/fd` while
`closeQuietly()` leaves none (probed directly).
…e settle (#4599)

The turn() helper arms scheduleSave whose debounce timer is unref by design
(persist.ts: a pending save must not hold the process open at shutdown) and
then awaits afterNextFlush. Nothing else in that await is refd, so in a
node:test child whose only surviving handles are unref sockets the event
loop drains before the 50 ms debounce fires and the settle promise never
resolves: all four subtests fail with "Promise resolution is still pending
but the event loop has already resolved" / cancelledByParent. Reproduced
deterministically on master 44067a5 AND on pre-merge ci-fixes 67f8e89
(standalone run, exact CI preload chain), while the same file has passed
some CI legs by ambient-liveness luck - a born-flake per section 7.2
(no environmental luck). The pin at 67f8e89 does not touch this path.

Fix: arm a refd 25 ms keep-alive interval around the afterNextFlush await
(cleared in finally) so the debounce fires deterministically on every host.
Test-only diff; production settle semantics untouched.
…ts ref-tracking fix (#4599)

PR #5262 fixes #4788 at the source: scheduleSave now leaves the debounce
timer refd while flush waiters exist (and afterNextFlush refs a pending
unrefd timer when a waiter registers), so the settle is deterministic for
EVERY caller on every host. The 25 ms keep-alive interval added in
a3019b0 only patched this one test file and is redundant now; removing it
leaves one mechanism instead of two. The comment stays: without it a future
reader re-adds the interval or deletes the persist.ts tracking as dead weight.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] 陪伴类长会话:compress nudge 自我放大,直到会话超过模型窗口且永久不可恢复

2 participants