Repository navigation
Description
(ai: vllm-qwen/qwen3.8-27b)
按 Kernel Boundary 第 1 条与授权卡点政策(2026-10-26),本修复触碰 kernel/**,实现前需 owner 批准。批准后立即开 PR(PR 号回填本帖),带 kernel-change: 声明 + 回归测试 + kernel 版本 bump。
查重: 无(open/closed 均无同机制条目;在途的 5 个 kernel authorization request #5180/#5208/#5098/#4886/#4877 机制各不相同)。
一句话(白话)
长会话触发 EMERGENCY truncate 时,裁剪出来的文本片段会存进一个「以消息编号为键」的缓存表;但受保护消息的编号是共享哨兵 "BLOCKED"——于是这段无关文本从此被贴到每一个解析为 BLOCKED 的消息上。tool-call 的 text 就是 function.arguments,所以无辜工具调用的参数整体变成非 JSON,上游每轮 400,会话永久砖化、重启无效、无自愈路径(#5218,已线上复现于 v0.1.209/dsh/OpenAI wire)。
证据(master == v0.1.209:git diff --stat v0.1.209..HEAD -- kernel/src/compress.ts kernel/src/truncate-tools.ts kernel/src/sync.ts 为空)
- 存储 kernel/src/compress.ts:1434-1441:
truncatedIds → byRaw[id]无哨兵过滤,{ref:"BLOCKED", text:<clip>}直接入 latch 并经 kernel/src/sync.ts:90-91 持久化。 - 应用 compress.ts:1355-1367:latch 回放跑在水位检查之前,任何
byRaw[m.id]==="BLOCKED"的消息被替换成 clip 文本;wire 无关(Anthropic tool_use.input 同样坏)。 - 自我延续:latchLive 只按 liveRefs 淘汰(:1347-1352),只要视图里还有受保护消息,BLOCKED 永远在 liveRefs → 毒条目永不清除;唯一清除点是成功折叠(:812),而 400 风暴下模型不会再发 compress 调用 → 永久闭环。
- 为什么受害者是无辜消息:BLOCKED 是共享标记不是身份(Messages with duplicate IDs overwrite the rendered bytes of already-sent messages: protected messages are re-tagged by l | 重复 id 的消息会改写已发送消息的渲染字节:受保护消息被后来的同名 id 消息重新打标 #3945,kernel/src/refs.ts:114-125)——原裁剪消息解除保护后迁走数字 ref,哨兵键下的 clip 却跟着下一个 BLOCKED 走;成功折叠过的 compress 调用/结果长期驻留 BLOCKED(Failed compression invocations are never folded back for reclamation, and the context keeps ballooning under the weight | 失败的压缩调用永远不会被折叠回收,上下文被压缩残渣越撑越大 #4289)即成每轮受害者。
- truncate 不查保护:kernel/src/truncate-tools.ts:38-149 无任何 protection/BLOCKED 判断,且 emergencyTruncateNode 传
includeTextMessages: true(compress.ts:1394)→ 受保护大消息照样被裁。 - 本地复现(内核级脚本,三步全中):存储毒条目 ✓ / tool-call arguments 变非 JSON ✓ / 毒条目跨轮持久 ✓;clip 长度 4065 vs reporter 线上 dump 4064,形状一致。
拟提交 diff(已在树上验证后还原工作区,approve 后原样进 PR)
--- a/kernel/src/compress.ts
+++ b/kernel/src/compress.ts
@@ -1349,11 +1349,15 @@ const emergencyTruncateNode: PipelineNode = {
- const latchLive = latch.filter((e) => liveRefs.has(e.ref));
+ const latchLive = latch.filter(
+ (e) => e.ref !== BLOCKED_REF && liveRefs.has(e.ref),
+ );
if (latch.length > 0) {
- const byRef = new Map(latch.map((e) => [e.ref, e.text]));
+ const byRef = new Map(
+ latch.filter((e) => e.ref !== BLOCKED_REF).map((e) => [e.ref, e.text]),
+ );
@@ -1435,7 +1439,7 @@ const emergencyTruncateNode: PipelineNode = {
- if (ref !== undefined && clippedMsg?.text !== undefined) {
+ if (ref !== undefined && ref !== BLOCKED_REF && clippedMsg?.text !== undefined) {三处守卫分工:①持久化侧——把已落盘的毒条目在下一轮持久化时清掉;②回放侧——存量中毒会话升级后第一轮即不再被砸,无需手改 store;③存储侧——堵住生产者。另:kernel/package.json 版本 0.0.169 → 0.0.170(CI 强制);新增回归测试 kernel/tests/issue5218-blocked-latch-poison.test.ts(负向:不存 BLOCKED 键 / 种子毒状态不砸 tool-call JSON 且被清除;正向对照:数字 ref clip 仍逐字节回放)。
验证记录:补丁在树上实测——复现脚本三阶段全部转干净;模拟存量中毒 store 的 heal 检查通过(arguments 完整 JSON、毒条目被清除、合法数字条目保留);latch/truncate/BLOCKED 相关定向测试 44/44 绿(issue3939-latch ×4、issue3945-id-collision、terminal-escape、truncate-keep-filter、truncate-surrogates、issue3953-straddle);tests/verif-subset-guard 绿(compress 属 verif Tier A 守护文件,补丁语法在子语言内)。
为什么宿主侧(src/)修不了
生产者在内核存储路径。宿主侧只能在 hydration/出站前滤掉哨兵键条目——那是堵不是疏(mask):内核不变量「latch 条目以消息身份为键」依旧失守,acp-kernel 作为独立 npm 包的其他消费方继续暴露,且未来任何新的哨兵键状态都会再踩一遍。
行为变更披露(Deliverables 第 4 条)
旧:解析为 BLOCKED 的消息也会被 clip 锁存/回放/持久化(哨兵被当身份用)。新:哨兵永不做 latch 键。副作用(已知且有界):超大受保护消息失去稳定回放——用量在 95% 水位上下穿越的那一轮,该消息可能出现一次 clip↔全文翻转(#3691 振荡类,仅缓存成本,仅限此类消息)。其余行为零变化。
选项与代价
- A(推荐)批准:单 PR 落地三守卫 + 回归测试 + 版本 bump。影响面:仅 emergency-truncate latch 的键空间。回归风险:低——回放/持久化两条路径均有既有 44 项定向测试钉住,verif 子语言守卫通过,已验证不破坏 [Kernel Initiative] Transactional folding boundaries + wire byte fidelity (unified prefix cache fix #3938, kernel side) | [kernel 专项] 折叠边界事务化 + wire 字节保真(前缀缓存统一修复 #3938 内核侧) #3939 字节稳定契约(数字 ref 逐字节回放保持)。
- B 拒绝:I feel noticeably dumber after using it. | 感觉用了之后降智明显 #5218 维持无修复(p1:数据损坏 + 会话永久不可用);可选退路是宿主侧 mask(见上,劣于 A,且内核对其他消费方仍坏)。
- C(可选后续,不属于本次闭环):给受保护消息的 clip 改按 rawId 键控,保住超大受保护消息在水位附近的字节稳定回放;代价是 latch 条目形状变更(
{ref?,rawId?,text})+ 兼容读,更侵入。建议 A 落地后如 owner 在意该缓存成本再单列。
请拍板 A/B(/C)。
Activity