fix(#651): drop oversized reasoning from closed compress turns - #667
Conversation
Models that keep reasoning/thinking on the wire build a permanent uncompressible floor: the fold anchor is a compress call, and any reasoning sitting before that anchor survives every fold as protected prefix. Storm sessions hit ~50% visible context lost this way. Proxy-side twin of billion-context-pi #339 / opencode-acp #377: - new src/reasoning-drop.ts: dropCompressReasoning() strips the contiguous reasoning run immediately preceding a CLOSED compress call (result present + genuine user message after) when the summed run exceeds a char threshold (default 2048). Active rounds, other tools' reasoning, and non-contiguous runs are never touched. - resolveCompress() now merges a third nested-object field (reasoning) sub-field-wise like absorb/prompts. - all three wire prepares (anthropic/openai/responses) apply the drop after stripKernelSummaries, before wire rebuild. - new config: compress.reasoning = { drop = true, threshold = 2048 }. - docs: CONFIGURATION.md + zh-CN; 17 new tests (1263 total green).
📦 Built Package ArtifactBranch: Option A — Install from npm PR tag (recommended)npm install -g billion-context@pr-667Each push to this PR publishes a new version under the Option B — Download artifact
tar xzf billion-context-pr667.tgz
npm install -g packageThis comment is automatically updated on each push. |
[bot] 🏷 Review of PR #667 — verified, one docs fix pushed, mergeable after the e2e gate Duplicate screen: no duplicates. Source issue is #651 (this PR fixes it). Closest neighbor is #539/#540 (ACP-loop re-request dropping Root-cause verification (triage step 1–2): confirmed in kernel 0.0.61 — What I verified on the branch (checked out
Provider round-trip analysis (the main risk of default-on):
Non-blocking finding (kernel-side, not for this PR): Before merge: per the repo spec, request-pipeline changes should run the 4-phase e2e core ( Verdict: mergeable once the e2e core passes. 中文摘要:验证了 #651 根因(保护是消息粒度,compress 强制受保护,附着 reasoning 每轮原样重发)与修复层次正确(剥离请求时视图而非动保护机制),typecheck 干净、1263/1263 全过、两种压缩模式与三家 provider 往返安全性均核过;已直接在 PR 分支补了 kill-switch 适用场景的文档(commit bd8fd8a),另发现 kernel 把 Responses 不透明工具项映射成 reasoning 类型的小问题(有界、不阻塞,建议 kernel 侧后续处理);跑完 e2e 四阶段核心后即可合并。 |
twin) The 'genuine user message after the compress call' closure gate is unreachable in long agentic sessions — no user messages exist after the opening prompt, so every compress round stayed 'active' forever and its thinking survived as a permanent uncompressible floor (observed on the pi side: 0 drops, 20.6K/8.4K/10.6K chars retained). A round now closes when the compress call's tool-result (contentType 'tool-result', matching toolCallId) exists at a later index AND at least one message follows it. In-flight rounds (result missing, or result still the last message) are never touched; the per-provider compress.providers.<name>.reasoning.drop=false escape hatch is preserved for reasoning-replay providers (GLM). Mirrors billion-context-pi #348 / PR #349. Tests: 1266 pass, gate suite rewritten with round-evidence scenarios (no-user-message agentic chains, pending result, result-before-call, mismatched id, distinct ids).
Fixes #651
Problem
Reasoning/thinking traces left on the wire before a
compresscall are a permanent uncompressible floor. The anchor of every fold is the compress call itself, so reasoning immediately before it survives every subsequent fold as protected prefix — it can never be re-summarized, only stripped. In the storm sessions this floor reached ~50% of visible context (75.8K tokens of a 151K view in one session).This is the proxy-side twin of:
reasoning-dropon the pi extension path)Change
New
src/reasoning-drop.ts—dropCompressReasoning(messages, cfg)strips the contiguous reasoning run immediately preceding a closed compress call when its summed length exceedsthresholdchars. A turn is closed when the compress call already has its tool result AND a genuine user message follows it. Safety gates:read,bash, …) reasoning is preservedConfig — new nested field merged sub-field-wise across the three levels exactly like
absorb/prompts:drop: falserestores the old wire verbatim. Invalid thresholds fall back to the default instead of throwing.Wiring —
resolveCompress()gains the third nested-object merge; all three wire prepares (anthropic / openai / responses) apply the drop afterstripKernelSummaries, before the wire rebuild, with an ops log line when anything is dropped.Tests
17 new tests in
tests/reasoning-drop.test.ts(default gate, strictly-greater boundary, active-round protection, non-compress tools, summed runs, non-contiguous runs, kill-switch, purity, idempotence, resolver validation, three-level merge). Full suite: 1263/1263 pass;npm run typecheckclean.Docs
CONFIGURATION.md+CONFIGURATION.zh-CN.md: newreasoningsection with rationale, defaults, and safety gates.