fix(contextview): never trim a compaction summary before newer raw rows - #1116
Merged
Merged
Conversation
Selection treated conversation summaries as ordinary droppable history, so under history-budget pressure the oldest fragment - the summary - was the first eviction: a successful compaction pass rendered zero value exactly when the budget needed it (observed live: the persisted summary fragment left selection as decision=dropped while newer raw rows survived). Summary fragments are now must-keep, so trimming funds itself from raw rows and the compacted span stays represented. Where the history budget cannot hold even the summary, selection now fails closed (protected overflow) instead of silently re-losing the compacted span; that regime previously produced an unusable context either way. Claude-Session: https://claude.ai/code/session_01BQNmA3AKE3nh6AnArrG54g
…e selection The must-keep tier meant a summary larger than the history slot turned into a protected overflow: the whole selection failed closed exactly in the small-window regime compaction exists to serve. Under protected pressure the selector now shortens must-keep conversation summaries — UTF-8 safe, head-preserving, with a model-visible truncation notice and the </summary> wrapper re-closed — and fails closed only when even the minimum viable summary cannot fit. Allocation is feasibility-preserving: the trim-notice cost is charged against the summary ceiling only when droppable rows actually force spatial drops (a summary-only selection keeps the full budget, and a summary that fits raw budget but would squeeze the notice out yields the difference instead of failing); under-target summaries settle at their actual cost so their unused share funds oversized ones; and the geometric descent probes exactly the floor before giving up, since mixed-density (CJK-heavy) text can otherwise overshoot past a floor size that fit. Applied shrinks surface as summary_budget_truncate edits.
chen-ran
approved these changes
Sep 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Selection treats a compaction summary as ordinary droppable history. The summary sits at the oldest anchor position, so under history-budget pressure it is the first fragment evicted — a successful compaction pass renders zero value at exactly the moment the budget needs it, and the compacted span is silently re-lost.
Live evidence (500-turn LongMemEval haystack, window 32000, budget plan
history_budget≈6.9k): after a successful automatic pass (438 rows covered, 12k-char summary), every subsequent turn'sselection_decisionsshowed the summary fragment asdecision=dropped, retention_tier=Nonewhile newer raw rows survived; the rendered breakdown was byte-identical to a no-compaction run. ~90s and ~125k summarizer tokens spent for zero rendered value.Fix
KindConversationSummaryfragments are must-keep in selection. Trimming funds itself from raw rows; the compacted span stays represented. The new test reproduces the production shape (summary dropped first among 12 drops) and pins the fixed behavior (summary kept, oldest raw rows fund the trim, newest raw row kept).Trade-off, disclosed: where the history budget cannot hold even the summary, selection now fails closed (protected overflow) instead of silently re-losing the compacted span; that regime previously produced an unusable context either way.
Verification
go testover contextview, application, agent/context/…, native: green;go test -racecontextview + application: green;golangci-lint run(2.10.1): clean.conversation_summary(est 1,577) alongside the trimmed raw tail on every turn, and the cross-turn prompt-cache classification stays hit/stable across the compacted render.