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
[Bug] Compaction selects its own previous checkpoint as the whole span, so a long session never converges (59/79 attempts rejected, 44.6 min burned)
#7212
Automatic compaction can select the checkpoint written by the previous compaction as its entire shadowed span. Re-summarizing a summary produces a same-size summary, the shrink guard rejects it, and the agent/pre-step listener simply tries again on the next step. One session produced 59 rejected attempts in 44.6 minutes of summarization (48 of them inside a single turn), 5 failed manual /compact runs, and a session that never got below its threshold.
Environment
dsh 0.1.6-alpha.2, commit ddefc45fbc7f8e46dd73185e68295696d1297887, --profile web
routed model openrouter/deepseek/deepseek-v4.1-flash
Run any session long enough to compact once. The first compaction lands normally (in the log: shadowedRange 9–246, 38,883 tokens).
The replacement checkpoint now sits at the head of the surface: [system head, checkpoint, …retained tail].
Let compaction trigger again — by agent/pre-step pressure or by overflow recovery. selectCompactableRange always starts at the first non-system surface node, so the only eligible span is the checkpoint itself, and shadowedRange comes back as a one-node span ({start: 709, end: 709}).
The summarizer rewrites that checkpoint into another checkpoint of the same size. The guard in summarizeCompaction rejects it, compactIfNeeded throws, the agent/pre-step listener logs step compaction failed: …; continuing the turn, and the next step starts the cycle again.
Current behavior
Every rejected attempt is this guard, from packages/compaction/compaction-basic/src/region.ts:
summary is not smaller than the shadowed content (N estimated framed tokens >= M)
Metric
Value
compaction/start
79
failed compaction/end (with error)
59
succeeded
20
wall time in failed attempts
2,678 s = 44.6 min (median 38.9 s, max 97.8 s)
wall time in successful attempts
887 s
failures where summary tokens equal shadowed tokens
39 of 59
attempts in turn 20 alone
48
user /compact commands
5, all 5 failed
19 of the 20 successful compactions are single-node spans on the previous checkpoint — the last one shadows 2,238 tokens and produces a 2,237-token framed checkpoint, which is exactly the equality the guard then rejects:
summary seq
selected span
span is prior checkpoint
shadowed tokens
708
9–246
no
38,883
873
709–709
yes
2,453
877
874–874
yes
2,442
922
878–878
yes
2,432
…
…
yes
…
1338
1256–1256
yes
2,238
Expected behavior
A compaction candidate that cannot shrink should not be retried in a loop. Concretely:
Do not offer a compaction checkpoint (a user/message whose data.source.plugin == "compact") as a summary span, or treat such a span as non-compactable when it is the only candidate.
When the shrink guard rejects a summary, record that the selected region is non-shrinking for this surface generation and stop retrying it — do not re-run the same doomed summarization on the next agent/pre-step.
Bound the total work: 48 full summarization calls inside one turn is not a bounded retry policy, and each call costs ~38k input tokens on top of the wall time.
Relation to existing reports
Proposal: suppress repeated pressure summaries after non-converging compaction #5669 ("suppress repeated pressure summaries after non-converging compaction") describes the successful-but-still-above-threshold case. This is a different failure mode: here the compaction throws before commit, so nothing is landed and there is no result to suppress — the loop is driven by the throw.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Summary
Automatic compaction can select the checkpoint written by the previous compaction as its entire shadowed span. Re-summarizing a summary produces a same-size summary, the shrink guard rejects it, and the
agent/pre-steplistener simply tries again on the next step. One session produced 59 rejected attempts in 44.6 minutes of summarization (48 of them inside a single turn), 5 failed manual/compactruns, and a session that never got below its threshold.Environment
0.1.6-alpha.2, commitddefc45fbc7f8e46dd73185e68295696d1297887,--profile webopenrouter/deepseek/deepseek-v4.1-flashthresholdRatio 0.8,retainRatio 0.16,compactionRetries 1,maxOverflowRetries 1session-2496881f-8d36-4ce6-93d9-1954a31930c7— 1,423 events, 5.28 h, exported log attachedReproduction
shadowedRange 9–246, 38,883 tokens).[system head, checkpoint, …retained tail].agent/pre-steppressure or by overflow recovery.selectCompactableRangealways starts at the first non-system surface node, so the only eligible span is the checkpoint itself, andshadowedRangecomes back as a one-node span ({start: 709, end: 709}).summarizeCompactionrejects it,compactIfNeededthrows, theagent/pre-steplistener logsstep compaction failed: …; continuing the turn, and the next step starts the cycle again.Current behavior
Every rejected attempt is this guard, from
packages/compaction/compaction-basic/src/region.ts:compaction/startcompaction/end(witherror)/compactcommands19 of the 20 successful compactions are single-node spans on the previous checkpoint — the last one shadows 2,238 tokens and produces a 2,237-token framed checkpoint, which is exactly the equality the guard then rejects:
Expected behavior
A compaction candidate that cannot shrink should not be retried in a loop. Concretely:
user/messagewhosedata.source.plugin == "compact") as a summary span, or treat such a span as non-compactable when it is the only candidate.agent/pre-step.Relation to existing reports
Evidence bundle
https://gist.github.com/gorban/4725c87390cb391047a2f14b56b98d28EVIDENCE.md— how to recompute every number above from the logsession-2496881f-sanitized.jsonl— full session log (credential-redacted)session-2496881f-checkpoint-chain.tsv— the span tablesession-2496881f-stats.json— counts, wall time, failure messagesRecompute the chain directly:
All reactions