Repository navigation
Description
"Session too large to compact" — the summarization request itself has no size budget
Summary
Follow-up to #48827 (auto-compaction ping-pong). Even with the loop guard in place, compaction of a genuinely large session fails outright with:
Session too large to compact - context exceeds model limit even after stripping media
Three compounding causes in session/compaction.ts:
-
The head has no budget.
select()caps only the retained tail (preserveRecentBudget, 25% of the usable window). Everything older — the head — is serialized and sent to the summarizer as a single request. A 60k-token session on a 64k-context model, or a 900k-token session on a 1M-context model, produces a summarization request that doesn't fit the model — so compaction (the mechanism whose entire job is shrinking oversized sessions) fails precisely when it is most needed. -
The tail budget derives from the wrong model. When a dedicated compaction model is configured (
agent.compaction.model, e.g. a cheap 1M model),select()sizes the retained tail from the compaction model's window. The compacted session must fit the session model's window — with a 1M compactor and a 64k session model, the retained tail alone (15k tokens) plus system prompt permanently overflows the session model. -
No escalation. If the post-compaction context still overflows, the next auto-compaction retains the same-sized tail and fails identically, until the loop guard from Auto-compaction loops forever when a session's irreducible context exceeds the model's usable window #48827 stops the session.
Proposed fix
PR linked below:
- Chunked (map-reduce) summarization. The serialized head is packed into chunks capped at half the compaction model's usable window; each chunk is summarized; partial summaries that still exceed the budget are packed and re-summarized until the final request fits. Sessions whose head fits take the previous single-request path unchanged, byte for byte.
- Tail budget from the session model, escalating full → half → none on each consecutive failed auto-compaction (tracked by the Auto-compaction loops forever when a session's irreducible context exceeds the model's usable window #48827 streak machinery), so a session whose floor exceeds the window sheds retained context before the loop guard stops it. The guard's limit becomes the ladder length (3).
With both, compaction succeeds by construction for any session size on any (working) model pair, and the session loop still stops cleanly when even a summary-only context fits no better.
Environment
- OpenCode desktop v1.18.30 (macOS), session model
cerebras/qwen-3.8-27b(64k context), compaction modeldeepseek/deepseek-v4-flash(1M context), ~60k-token session.
Activity
github-actions commentedon Sep 13, 2026
This issue might be a duplicate of existing issues. Please check:
If this is intentionally a follow-up to #48827 with a distinct root cause and proposed fix, it may be worth clarifying that relationship and whether a linked PR has been opened.
stweil commentedon Sep 24, 2026
Does PR #51121 work for you?
aitrawog commentedon Sep 25, 2026
Confirmed live, and the mechanism is now exactly pinned: the summarizer's head is the entire history, because
select()budgets only the retained tail and the default tail is tiny.Environment: opencode 1.18.32 (stable), provider
ollama-cloud(https://ollama.com/v1), modeldeepseek-v4.1-flash(1,048,576 context / 393,216 max output).Auto-compaction fired at a 666,763-token live context and failed with
The prompt is too long: 1122556, model maximum context length: 1048576; the session was permanently wedged.Two verified facts:
max_tokens393,216 → accepted; prompt 1,050,041 +max_tokens1 → rejected (The prompt is too long: 1050041).serialize()rules (all reasoning text retained, tool output capped at 2,000 chars) and measured it through the real provider: 1,128,448 input tokens, against the logged crash prompt of 1,122,556 (0.5% apart). The live context was only 666,763 — so the serialized request was ~1.7× the live context.The
select()trigger:preserveRecentBudgetdefaults tomin(15_000, max(2_000, 25% of usable)). With the newest turn (~43k tokens here) above that budget, the retained tail is empty andheadbecomes all messages — precisely the request that overflowed.Mitigation for an existing session (
opencode.json):limit.inputsocompaction.reservedis honored, e.g.limit: {context: 1048576, input: 1048576, output: 384000};reservedlarge — it makesisOverflowfire earlier, when the serialized head is smaller;compaction.prune: true;compaction.preserve_recent_tokens: 500000— on this session that cut the reconstructed head from ~1,128,448 to 634,064 real tokens, under the input window.This is explicitly a stopgap:
preserve_recent_tokenscaps the tail, so the head keeps growing as the session grows. Only dropping oldest turns when the head exceeds budget (PR #51121) bounds it permanently.Assisted-by: opencode ollama-cloud/deepseek-v4.1-flash (analysed on a live system)
fix(opencode): bound the compaction head and mirror the PR-eval model…
thdxr commentedon Oct 11, 2026
Thanks for the report! OpenCode 2.0 is now out and development is focused on V2, so we're closing issues filed against OpenCode 1. Please upgrade (
opencode upgrade --major, details: https://anoma.ly/notes/opencode-2-0), and if this is still relevant on 2.0, reopen this issue or open a new one with your 2.0 version.— from 𝕺𝖕𝖊𝖓𝕮𝖔𝖉𝖊
thymikee commentedon Oct 12, 2026
This is still happening on v2
stweil commentedon Oct 12, 2026
@thdxr, could you please reopen this issue? I don't have the required permission.