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).
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