Skip to content

fix(v2): stop compaction usage from becoming phantom system overhead (#421) - #430

Open
ranxianglei wants to merge 2 commits into
2026-09-17_v2-basefrom
2026-09-17_v2-compaction-phantom-system
Open

ranxianglei wants to merge 2 commits into
2026-09-17_v2-basefrom
2026-09-17_v2-compaction-phantom-system

Conversation

@ranxianglei

Copy link
Copy Markdown
Owner

Closes #421

Problem

V2 native compaction projects the checkpoint as an assistant carrying the compaction request's token usage verbatim (lib/v2/projection/shared.ts makeAssistantInfo). cacheSystemPromptTokens used that assistant as its calibration anchor, permanently storing the request size as "system + tool schemas" overhead. Repro from the issue: input 653137 → stored 653136 phantom; on a 400000-token model the hard-guard budget becomes −285904, producing negative-budget log spam and unnecessary tool clearing while real provider usage stays far smaller.

Fix

  • lib/token-utils.ts — new calibrateSystemOverhead(state, messages): never anchors on info.summary === true assistants or assistants created before state.lastCompaction; subtracts the full wire-visible pre-anchor prefix with BPE-exact counts (excluding host-ignored user parts and ACP-owned notices). estimateSystemPromptTokens(messages, lastCompaction = 0) delegates to it.
  • lib/ui/utils.ts — cacheSystemPromptTokens(state, messages, measuredSystemTokens?) merges the heuristic residual with a measured current-wire floor via max(), recording provenance in new transient field SessionState.systemPromptTokensSource (heuristic | measured). The [FIX estimateSystemPromptTokens inconsistent between nudge and acp_status after compression #255] stable-cache early return is preserved.
  • lib/v2/context.ts — measures the actual outgoing system parts (Σ countTokens(event.system[].text) + rendered ACP system prompt) each transaction and passes it as the floor. V1 callers omit it → pure heuristic, unchanged.
  • Invalidation — resetOnCompaction (lib/state/utils.ts) and the mid-session model-switch block (lib/messages/transform.ts) now clear the cached estimate, so first-request-after-compaction and after reload recalibrate cleanly instead of inheriting a stale/phantom value.
  • lib/compress/status.ts — /acp context breakdown labels the system segment [measured] / [estimated], distinguishing actual wire accounting from heuristics (issue acceptance criterion).

No persisted-state format change (systemPromptTokens* were already transient), no config change, all API extensions are optional trailing parameters.

Verification

  • New regression suite tests/v2-compaction-system-overhead.test.ts — 14/14 pass: the exact V2: compaction usage becomes phantom system overhead and negative context budgets #421 repro (fresh state + reload both store no phantom), post-compaction anchor calibrates to real wire overhead with nonnegative budget at the reported 400k limit, measured-floor/provenance behavior, invalidation paths, and estimateSystemPromptTokens inconsistent between nudge and acp_status after compression #255 compatibility (plain-text sessions calibrate byte-for-byte as before).
  • Bug-catch verified: with lib/ reverted to pre-fix code, the original cacheSystemPromptTokens on the repro input stores exactly 653136; fixed code stores undefined.
  • Full suite: 1424/1425 (sole failure = tests/soft-block.test.ts module-load EACCES /tmp — sandbox mounts /tmp read-only, pre-existing environmental issue, unrelated).
  • npm run typecheck clean, npm run build green, touched files prettier-clean.
  • Devlog: devlog/2026-09-17_v2-compaction-phantom-system/{REQ,DESIGN,WORKLOG}.md.

中文摘要:修复了 V2 原生压缩后校准锚点误用 compaction 请求用量、把 653136 tokens 当成永久 system 开销导致硬护栏预算为负(-285904)的问题——排除 summary/过期锚点、按当前实际出站 system 部件做测量下限、在压缩与模型切换时失效缓存,并在 /acp context 中标注 measured/estimated 来源。

…421)

V2 native compaction projects the checkpoint as an assistant carrying the
compaction REQUEST's token usage verbatim. cacheSystemPromptTokens used that
assistant as its calibration anchor, storing the request size (repro: 653136)
as permanent system overhead and driving hard-guard budgets negative
(-285904 on a 400k model) while actual provider usage stayed small.

- calibrateSystemOverhead: never anchor on summary assistants or on
  assistants created before state.lastCompaction; subtract the full
  wire-visible pre-anchor prefix (BPE-exact), excluding host-ignored user
  parts and ACP-owned notices. Plain-text sessions calibrate exactly as
  before (#255 suite unchanged).
- cacheSystemPromptTokens: merge heuristic residual with a measured
  current-wire floor (max), record provenance in new transient
  SessionState.systemPromptTokensSource (heuristic|measured).
- v2/context.ts: measure actual outgoing system parts + rendered ACP
  system prompt each transaction and pass as the floor.
- Invalidation: resetOnCompaction and mid-session model switch now clear
  the cached estimate so post-compaction/reload recalibrates cleanly.
- /acp context breakdown labels the system segment [measured]/[estimated].

Tests: tests/v2-compaction-system-overhead.test.ts (14 regression tests;
verified to fail against pre-fix code, which stores the exact 653136
phantom). Full suite 1424/1425 (soft-block /tmp EACCES is environmental).
Devlog: devlog/2026-09-17_v2-compaction-phantom-system/{REQ,DESIGN,WORKLOG}.md
@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

[bot] 🏷 收到,开始核验这个 PR(不重做,按 [PR] 流程:拉分支核对代码 + 确认 CI + 报告结论)。已确认 PR 基于 2026-09-17_v2-base(V2 迁移分支),head 为单提交 64f31509,GitHub 侧 mergeable/clean。正在本地跑新回归套件和全量测试,稍后给出完整结论。

…fallback (#421)

The /acp context fallback in lib/compress/status.ts already passed
state.lastCompaction to the live estimator, but the sibling fallback in
estimateContextComposition (lib/messages/inject/utils.ts) did not. After
resetOnCompaction invalidates the cached estimate, a pre-compaction
assistant could anchor calibration again and resurface the stale overhead
in nudge/context-usage math. Pass state?.lastCompaction ?? 0 and add a
regression test (fails against the unfixed code).
@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

核验完成(按 [PR] 流程:拉分支独立验证,未重做)。结论:修复正确、可合并,另补了一处漏网的小缺口(已直接修进 PR 分支)。

独立验证结果(全部通过)

  • npm run typecheck 干净;npm run build 成功;涉及文件 prettier 干净。
  • 新回归套件 tests/v2-compaction-system-overhead.test.ts:14/14 通过。
  • 全量测试:1424/1425 通过——唯一失败是 tests/soft-block.test.ts 模块加载时 EACCES mkdir '/tmp/...',本沙箱 /tmp 只读所致,属既有环境问题,该文件未被本 PR 触碰。
  • 独立 bug-catch:在纯修复前代码上用 V2: compaction usage becomes phantom system overhead and negative context budgets #421 的原始复现输入跑旧 cacheSystemPromptTokens,存入值恰为 653136(即幻影开销);修复后代码存 undefined。确认修复打中根因。
  • 逐行审过全部 diff:锚点排除规则(summary===true / 早于 lastCompaction 创建)、BPE 精确前缀扣除(排除 host-ignored user parts 与 ACP-owned notices)、测量下限 max() 合并 + provenance 字段、V2-only 测量接线(V1 调用方不变)、压缩/模型切换两处失效、/acp context 的 [measured]/[estimated] 标注——均正确一致。无持久化状态格式变更(两个新字段确认未进 persistence 层)、无配置变更、未动 version,devlog REQ/DESIGN/WORKLOG 齐全。

审查中发现并已直接修复的一处缺口(commit e833eff3,已推回 PR 分支)
estimateContextComposition 的 live fallback(lib/messages/inject/utils.ts)调用估算器时没传 lastCompaction——resetOnCompaction 失效缓存后,压缩前的 assistant 会重新成为校准锚点,把过期开销重新带进 nudge/context-usage 计算。PR 已在同类的 lib/compress/status.ts fallback 里加了这层防护,唯独漏了这条路径。已补传 state?.lastCompaction ?? 0 并新增回归测试 #15(对修复前代码验证过会失败:systemTokens 取到 STALE 而非 WIRE;修复后通过)。补测后:新套件 15/15,全量 1425/1426(唯一失败仍为上述环境性 soft-block),typecheck/prettier 干净。WORKLOG 已追加记录。

给 owner 的两点提示

  1. CI 不会在这个 PR 上跑:所有 workflow(ci.yml / pr-checks.yml / pr-artifact.yml / release.yml)都只触发 branches: [master],而本 PR base 是 2026-09-17_v2-base。因此该分支没有任何 CI 检查位,以上本地验证即为把关依据。若希望 V2 系列有 CI 覆盖,可考虑把 V2 base 分支加进 workflow triggers。
  2. soft-block 那条失败在 CI(/tmp 可写)里会通过。

合并请人工操作(我无权 merge)。

中文摘要:核验了 #421 幻影 system 开销修复的正确性(旧代码复现存入 653136、新代码不存),并直接修复了 live fallback 漏传 lastCompaction 的一处同类缺口(附回归测试,已推回 PR 分支),typecheck/build/全量测试均过(唯一失败为沙箱 /tmp 只读的环境问题);另注意此 PR base 非 master,CI 不会运行;可以合并。

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.

1 participant