You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A single mid-turn compaction attempt that finds nothing safe to fold disables all compaction for the rest of the turn — including the reactive path that is supposed to recover from a real provider context_overflow. The turn can then die on input capacity even though a later fold would have succeeded.
Two different outcomes currently share one flag, state.summarizerFailure:
No summarizer call was ever made — selectSafeCompactionPrefix found no completed span that preserves tool call/result pairs, honors the head anchor, and leaves the reserved tail (no_safe_completed_span).
Outcome 2 is a property of the event pool at that step, not a failure. As the turn proceeds, tool results keep landing and a safe prefix can appear — but the flag short-circuits every later attempt before the ledger is even re-read:
packages/runtime/src/ai-sdk-compaction.ts:977-983 — entry short-circuit on state.summarizerFailure.
packages/runtime/src/ai-sdk-compaction.ts:1111-1125 — all fail-open reasons, including no_safe_completed_span, are written into the flag.
packages/runtime/src/ai-sdk-compaction.ts:1205-1287 — recoverFromOverflowError routes through the same function, so a real provider rejection after the flag is set cannot be rescued either.
Expected: "nothing foldable right now" is re-evaluated as the ledger grows; only outcomes that actually invoked the summarizer suppress further attempts.
How to reproduce
In a live session this needs one unlucky timing: the proactive trigger fires on a step where the uncovered frontier cannot yield a safe span — e.g. fewer foldable events than the headAnchor + reserveTailEvents: 1 headroom allows, or an open tool pair at the tail. Verified deterministically by stubbing the ledger, summarizer, and projection seams (no provider involved):
Event pool = user anchor + one completed tool call/result pair. Under the mid-turn spec this leaves nothing foldable.
Append a second completed tool pair — a safe prefix now exists — and call again: returns early on the flag; listRuntimeEvents is not even re-read; summarizer count stays 0.
Control: clear state.summarizerFailure, call again with the same pool — fold succeeds, summarizer count = 1.
Environment
Repo HEAD: f58f30203 (upstream/main)
Surface: runtime (@maka/runtime), all model routes
Suggested fix
Mark only summarizer-attributable failures; let structural outcomes lapse:
no_safe_completed_span (and equivalent insufficient-headroom outcomes) must not write state.summarizerFailure — zero summarizer calls were made, and the outcome becomes obsolete as the pool grows.
Ledger/durability read failures are a third category if a different retry policy is wanted; they are not summarizer failures either.
Acceptance tests (seams exist in mid-turn-capacity-backend.test.ts / overflow-reactive-recovery.test.ts):
First attempt yields no_safe_completed_span with zero summarizer calls; after the pool gains a completed pair, the next attempt folds successfully.
Same setup, then a real provider context_overflow: reactive recovery folds instead of being blocked.
provider_error / malformed-summary outcomes still mark the turn as failed; summarizer call counts stay capped as existing tests assert.
For reference, DeepSeek-Reasonix keys its compaction "stuck" state on an input hash and resets it when new input arrives (internal/agent/context_manager.go) — the same principle: a grown pool may present a new foldable boundary.
What happened
A single mid-turn compaction attempt that finds nothing safe to fold disables all compaction for the rest of the turn — including the reactive path that is supposed to recover from a real provider
context_overflow. The turn can then die on input capacity even though a later fold would have succeeded.Two different outcomes currently share one flag,
state.summarizerFailure:selectSafeCompactionPrefixfound no completed span that preserves tool call/result pairs, honors the head anchor, and leaves the reserved tail (no_safe_completed_span).Outcome 2 is a property of the event pool at that step, not a failure. As the turn proceeds, tool results keep landing and a safe prefix can appear — but the flag short-circuits every later attempt before the ledger is even re-read:
packages/runtime/src/ai-sdk-compaction.ts:977-983— entry short-circuit onstate.summarizerFailure.packages/runtime/src/ai-sdk-compaction.ts:1111-1125— all fail-open reasons, includingno_safe_completed_span, are written into the flag.packages/runtime/src/ai-sdk-compaction.ts:1205-1287—recoverFromOverflowErrorroutes through the same function, so a real provider rejection after the flag is set cannot be rescued either.Expected: "nothing foldable right now" is re-evaluated as the ledger grows; only outcomes that actually invoked the summarizer suppress further attempts.
How to reproduce
In a live session this needs one unlucky timing: the proactive trigger fires on a step where the uncovered frontier cannot yield a safe span — e.g. fewer foldable events than the
headAnchor+reserveTailEvents: 1headroom allows, or an open tool pair at the tail. Verified deterministically by stubbing the ledger, summarizer, and projection seams (no provider involved):compactActiveRequestHistory: returnsno_safe_completed_span; summarizer call count = 0;state.summarizerFailureis set.listRuntimeEventsis not even re-read; summarizer count stays 0.state.summarizerFailure, call again with the same pool — fold succeeds, summarizer count = 1.Environment
f58f30203(upstream/main)@maka/runtime), all model routesSuggested fix
Mark only summarizer-attributable failures; let structural outcomes lapse:
no_safe_completed_span(and equivalent insufficient-headroom outcomes) must not writestate.summarizerFailure— zero summarizer calls were made, and the outcome becomes obsolete as the pool grows.provider_error,malformed_*,output_length,empty_summary,input_too_largestay marked as today — the bounded-retry behavior from History compaction always fails open on kimi-coding-plan/k3-256k: summarizer instruction as system prompt is ignored and breaks prefix cache #4634 is unchanged.Acceptance tests (seams exist in
mid-turn-capacity-backend.test.ts/overflow-reactive-recovery.test.ts):no_safe_completed_spanwith zero summarizer calls; after the pool gains a completed pair, the next attempt folds successfully.context_overflow: reactive recovery folds instead of being blocked.provider_error/ malformed-summary outcomes still mark the turn as failed; summarizer call counts stay capped as existing tests assert.For reference, DeepSeek-Reasonix keys its compaction "stuck" state on an input hash and resets it when new input arrives (
internal/agent/context_manager.go) — the same principle: a grown pool may present a new foldable boundary.中文版本
发生了什么
只要有一次轮中压缩尝试发现当前没有可安全折叠的区间,本轮剩余的所有压缩都会被禁用——包括本应处理 provider 真实
context_overflow的反应式恢复路径。即使之后折叠本来能够成功,这个 turn 仍会因输入超限而终止。当前两类不同的结果共用同一个失败标记
state.summarizerFailure:selectSafeCompactionPrefix找不到既完整、又保住工具调用/结果对、又满足 head anchor 和尾部保留的跨度(no_safe_completed_span)。第 2 类结果是当前这一步事件池的属性,不是失败。随着 turn 推进、工具结果持续落账,安全前缀可能出现——但失败标记让之后的每次尝试在重读账本之前就被短路:
packages/runtime/src/ai-sdk-compaction.ts:977-983——state.summarizerFailure置位后入口直接短路;packages/runtime/src/ai-sdk-compaction.ts:1111-1125—— 所有 fail-open 原因(含no_safe_completed_span)都写入该标记;packages/runtime/src/ai-sdk-compaction.ts:1205-1287——recoverFromOverflowError走同一个函数,标记置位后真实的 provider 拒绝也无法被抢救。预期行为:"此刻没有可折叠内容"应随账本增长被重新评估;只有真正调用过摘要器的结果才应抑制后续尝试。
如何复现
真实会话中需要一个不巧的时机:主动触发器在某一步 firing,而当时的未覆盖头部恰好选不出安全跨度——例如可折叠事件少于
headAnchor+reserveTailEvents: 1所需余量,或尾部卡在未闭合的工具调用对中间。已通过注入账本/摘要器/投影桩做了确定性复现(不经过真实 provider):compactActiveRequestHistory:返回no_safe_completed_span;摘要调用次数 = 0;state.summarizerFailure被置位。listRuntimeEvents都未重读;摘要调用次数仍为 0。state.summarizerFailure后用同一事件池再调——折叠成功,摘要调用次数 = 1。环境
f58f30203(upstream/main)@maka/runtime),与模型路由无关建议修复
只对可归因于摘要器的失败置标记,结构性结果不置:
no_safe_completed_span(及等价的头部空间不足类结果)不得写入state.summarizerFailure——它没有发起任何摘要调用,且随事件池增长会失效;provider_error、malformed_*、output_length、empty_summary、input_too_large维持现状——History compaction always fails open on kimi-coding-plan/k3-256k: summarizer instruction as system prompt is ignored and breaks prefix cache #4634 引入的有界重试行为不变;验收测试(
mid-turn-capacity-backend.test.ts/overflow-reactive-recovery.test.ts已有可用桩点):no_safe_completed_span且摘要调用为零;事件池新增一对完整工具调用后,下次尝试折叠成功;context_overflow能被反应式恢复折叠,而不是被失败标记挡下;provider_error/ 畸形摘要结果仍在本轮置标记,摘要调用次数上限与现有测试断言一致。参考:DeepSeek-Reasonix 将压缩 stuck 状态按输入哈希记录,新输入到来即重置(
internal/agent/context_manager.go)——同一原则:增长后的事件池可能出现新的可折叠边界。