事故复盘:2026-08-23 会话 01a02d90 死循环(五层失效链 + 验尸数据)
会话在 sglang(127.0.0.1:8199,max_model_len=262144,模型 qwen3.8-27b,contextWindow=262144 / maxTokens=131072)上运行,最终对每个请求永久返回 400 status code (no body),模型一次都拿不到成功回合,compress 永远无法被调用,上下文永不收缩。
直接死因
一条 50,358 字符的单行压缩 JS bash toolResult(ACP 自己的计数 ≈ 31,475 tokens,≈ 有效输入窗口的 24%)整体进入上下文。有效输入上限 = 262,144 − 131,072 (maxTokens 预留) = 131,072。真实输入达到 134,569 tokens(见下),超限 → 400。
验尸数据(会话 jsonl + acp.log)
- 953/953 条 assistant 消息全部带真实 provider usage(pi 确实请求了
stream_options.include_usage,sglang 确实返回了,pi 确实落盘了——传输层没有问题)。
- 崩溃前最后一条 usage:
{input: 169, cacheRead: 134400} → 真实上下文 134,569 tokens,已超有效上限;而同轮仲裁用的估算值是 tokens=57463 pct=51.8 —— 4.5× 低估。
acp.log 中 density=2.5(钳制上限)出现 831 次:密度学习机制一直在工作,只是真比值 ~4.5 永远追不上,所有阈值(75% nudge / 95% emergency)从未触发。
五层失效链
| # |
层 |
为什么没拦住 |
| L1 |
字节级截断(pi 51,200B / ACP 200KB) |
毒消息是一行 minified JS:50,358 字符 < 所有字节上限,行数=1 |
| L2 |
token 估算(chars/4 × 密度) |
45% 系统性低估:原始估算 ~29.7K,density 顶死 2.5 钳制,校准后仍只有真实值的 ~55% |
| L3 |
95% 紧急截断 |
门槛永远没到(51.8% < 95%);且 protectRecentMessages 会跳过最新消息——毒消息恰是最新消息 |
| L4 |
overflow 自愈 |
正则只认带错误体的标记(maximum context length is N 等);bodyless 400 一个都不匹配,armed 永远不置位 |
| L5 |
billion-context-pi 对 pi 自身 session_before_compact 恢复 |
无条件 cancel,pi 原生自救通道被关 |
根因(L2 的上游):仲裁用的是估算值,而 provider 锚定的真实值(getContextUsage().tokens,含 cacheRead)就在同一轮上下文事件里、且当轮已 >100%——手里有真数,仲裁却用估算。
修复
| PR |
层 |
内容 |
| #214 |
根因 |
仲裁改用 max(校准估算, provider 锚定值),带三重防护(pi 无法锚定→跳过;压缩后瞬态轮→跳过;provider 从不报 usage 的整树求和 regime→跳过,即 omp #18) |
| #215 |
L4 |
no-body 4xx 双守卫武装自愈(用量 ≥50% 或连续第 2 次)——循环发生后也能恢复 |
| ranxianglei/acp-kernel#159(叠在 #133 上) |
L1/L3 |
单条 toolResult 硬上限 min(10%×limit, 16384),每轮无条件跑、无视新旧保护,head+tail 截断 |
三者互补:#159 按条拦截(不看总量)、#214 按真实总量掐灭、#215 兜底恢复。任一层单独存在都挡不住这次事故的全部变种。
未修(记录在案)
会话数据
- 会话文件:
~/.pi/agent/sessions/--home-dog-projects-billion-context--/2026-08-23T07-40-13-110Z_01a02d90-17b6-7ca7-b22a-a5170c2f9098.jsonl(已手工修复后可用)
- 日志:
~/.pi/acp.log(sid=01a02d90,事发时段 ~13:45 UTC 前后)
事故复盘:2026-08-23 会话 01a02d90 死循环(五层失效链 + 验尸数据)
会话在 sglang(127.0.0.1:8199,
max_model_len=262144,模型qwen3.8-27b,contextWindow=262144 / maxTokens=131072)上运行,最终对每个请求永久返回400 status code (no body),模型一次都拿不到成功回合,compress永远无法被调用,上下文永不收缩。直接死因
一条 50,358 字符的单行压缩 JS bash
toolResult(ACP 自己的计数 ≈ 31,475 tokens,≈ 有效输入窗口的 24%)整体进入上下文。有效输入上限 =262,144 − 131,072 (maxTokens 预留) = 131,072。真实输入达到 134,569 tokens(见下),超限 → 400。验尸数据(会话 jsonl + acp.log)
stream_options.include_usage,sglang 确实返回了,pi 确实落盘了——传输层没有问题)。{input: 169, cacheRead: 134400}→ 真实上下文 134,569 tokens,已超有效上限;而同轮仲裁用的估算值是tokens=57463 pct=51.8—— 4.5× 低估。acp.log中density=2.5(钳制上限)出现 831 次:密度学习机制一直在工作,只是真比值 ~4.5 永远追不上,所有阈值(75% nudge / 95% emergency)从未触发。五层失效链
protectRecentMessages会跳过最新消息——毒消息恰是最新消息maximum context length is N等);bodyless 400 一个都不匹配,armed 永远不置位session_before_compact恢复根因(L2 的上游):仲裁用的是估算值,而 provider 锚定的真实值(
getContextUsage().tokens,含cacheRead)就在同一轮上下文事件里、且当轮已 >100%——手里有真数,仲裁却用估算。修复
max(校准估算, provider 锚定值),带三重防护(pi 无法锚定→跳过;压缩后瞬态轮→跳过;provider 从不报 usage 的整树求和 regime→跳过,即 omp #18)min(10%×limit, 16384),每轮无条件跑、无视新旧保护,head+tail 截断三者互补:#159 按条拦截(不看总量)、#214 按真实总量掐灭、#215 兜底恢复。任一层单独存在都挡不住这次事故的全部变种。
未修(记录在案)
session_before_compact无条件 cancel):改成条件放行需要 ACP 记账重建,风险中等,单独立项。[0.5, 2.5]上限偏低(fix(arbitration): prefer provider-anchored usage over calibrated estimate #214 落地后仲裁不再依赖它,优先级降低)。会话数据
~/.pi/agent/sessions/--home-dog-projects-billion-context--/2026-08-23T07-40-13-110Z_01a02d90-17b6-7ca7-b22a-a5170c2f9098.jsonl(已手工修复后可用)~/.pi/acp.log(sid=01a02d90,事发时段 ~13:45 UTC 前后)