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 history-compact checkpoint created by the standalone compaction path (Desktop "Compact" action / RuntimeKernel.compactSession) is never used by the next turn whenever the session has any durable model-projection transition (stale Tool Result archive — enabled by default) inside the covered prefix. The next turn's replay fails open with source_hash_mismatch and sends the full uncompacted history to the provider, so the user sees "Context compacted." while the context usage stays pinned at ~100% and every request keeps paying the full-history cost.
The failure is silent in the UI until the turn settles (the context_compaction_failed_open note is only appended together with token_usage at settlement), so from the user's perspective compaction "succeeded" but did nothing.
Root cause
The checkpoint's coverage.sourceDigest is pinned over raw RuntimeEvents at creation, but the pre-turn replay matches it against folded events (after reduceEffectiveModelProjections):
Creation (AiSdkCompaction.compactHistory, and the mid-turn state's priorContentEvents from buildMidTurnCapacityCompactState) reads begin.runtimeContext / input.runtimeContext — the raw ledger, no transition fold. For an archived function_response, effectiveDigestEvent therefore hashes the raw result field.
Pre-turn replay (buildPriorMessages → prepareContextBudgetPolicy) folds the durable transitions first, so the same events carry modelProjection placeholders and effectiveDigestEvent hashes those instead → matchHistoryCompactCheckpointPrefix returns source_hash_mismatch → applyRuntimeEventHistoryCompact fails open → full replay.
Verified against a live session's durable ledger (session 58da2f70-…, checkpoint hcheckpoint-eb286323…, 863 covered events):
digest over the raw prefix → matches the checkpoint exactly
digest over the folded prefix (6 pre-existing stale-result transitions, 6 covered events rewritten) → source_hash_mismatch
the turn's own model_stream_started diagnostic recorded compactionDecisions: [{ stage: 'priorReplay', decision: 'failedOpen', failOpenReason: 'source_hash_mismatch' }] with droppedEvents: 0, and the provider then reported 268,212 tokens used against a 262,144 window.
The same mismatch also kills mid-turn checkpoints at the next turn boundary: the mid-turn create/match pair is self-consistent over raw events, but the next turn's pre-turn replay matches over folded events.
How to reproduce
In any session, produce a tool result larger than 2,048 estimated tokens and let at least two more turns pass, so the default staleToolResultPrune commits a durable archive transition for it.
Run manual context compaction (Desktop compact action). It succeeds and records a checkpoint ("Context compacted.").
Send another message.
Observe: the request still carries the full history (ctx indicator unchanged), and once the turn settles the transcript carries context_compaction_failed_open. The token_usage.contextBudget.compactionDecisions shows decision: 'failedOpen', failOpenReason: 'source_hash_mismatch'.
Make the two views agree. The least invasive, fleet-compatible option: match the checkpoint against the raw durable prefix at pre-turn replay, then fold the projected [block, tail] through reduceEffectiveModelProjections before building messages (mirroring what the mid-turn path already does at ai-sdk-turn.ts for the current-turn projection). Rationale:
every existing checkpoint in the field was hashed over raw events (both creation paths), so raw-side matching revives them instead of orphaning them;
raw ledger rows are immutable, so the pinned digest can no longer drift when a later transition commits;
Alternative (heavier): fold at creation in both compactHistory and buildMidTurnCapacityCompactState and keep folded matching — this matches the v2 policy comment's wording but leaves every existing raw-hashed checkpoint dead and requires the sync mid-turn state builder to become async.
I can take this. Related but distinct: #4634 (fail-open from summarizer route), #3029/#3041 (summary content validation).
What happened
A history-compact checkpoint created by the standalone compaction path (Desktop "Compact" action /
RuntimeKernel.compactSession) is never used by the next turn whenever the session has any durable model-projection transition (stale Tool Result archive — enabled by default) inside the covered prefix. The next turn's replay fails open withsource_hash_mismatchand sends the full uncompacted history to the provider, so the user sees "Context compacted." while the context usage stays pinned at ~100% and every request keeps paying the full-history cost.The failure is silent in the UI until the turn settles (the
context_compaction_failed_opennote is only appended together withtoken_usageat settlement), so from the user's perspective compaction "succeeded" but did nothing.Root cause
The checkpoint's
coverage.sourceDigestis pinned over raw RuntimeEvents at creation, but the pre-turn replay matches it against folded events (afterreduceEffectiveModelProjections):AiSdkCompaction.compactHistory, and the mid-turn state'spriorContentEventsfrombuildMidTurnCapacityCompactState) readsbegin.runtimeContext/input.runtimeContext— the raw ledger, no transition fold. For an archivedfunction_response,effectiveDigestEventtherefore hashes the rawresultfield.buildPriorMessages→prepareContextBudgetPolicy) folds the durable transitions first, so the same events carrymodelProjectionplaceholders andeffectiveDigestEventhashes those instead →matchHistoryCompactCheckpointPrefixreturnssource_hash_mismatch→applyRuntimeEventHistoryCompactfails open → full replay.Verified against a live session's durable ledger (session
58da2f70-…, checkpointhcheckpoint-eb286323…, 863 covered events):source_hash_mismatchmodel_stream_starteddiagnostic recordedcompactionDecisions: [{ stage: 'priorReplay', decision: 'failedOpen', failOpenReason: 'source_hash_mismatch' }]withdroppedEvents: 0, and the provider then reported 268,212 tokens used against a 262,144 window.The same mismatch also kills mid-turn checkpoints at the next turn boundary: the mid-turn create/match pair is self-consistent over raw events, but the next turn's pre-turn replay matches over folded events.
How to reproduce
staleToolResultPrunecommits a durable archive transition for it.context_compaction_failed_open. Thetoken_usage.contextBudget.compactionDecisionsshowsdecision: 'failedOpen', failOpenReason: 'source_hash_mismatch'.Environment
Proposed fix
Make the two views agree. The least invasive, fleet-compatible option: match the checkpoint against the raw durable prefix at pre-turn replay, then fold the projected
[block, tail]throughreduceEffectiveModelProjectionsbefore building messages (mirroring what the mid-turn path already does atai-sdk-turn.tsfor the current-turn projection). Rationale:Alternative (heavier): fold at creation in both
compactHistoryandbuildMidTurnCapacityCompactStateand keep folded matching — this matches the v2 policy comment's wording but leaves every existing raw-hashed checkpoint dead and requires the sync mid-turn state builder to become async.I can take this. Related but distinct: #4634 (fail-open from summarizer route), #3029/#3041 (summary content validation).