Skip to content

fix(session): drop oldest turns when compaction head exceeds model context - #51121

Closed
stweil wants to merge 1 commit into
anomalyco:devfrom
stweil:compact-oversized-heads
Closed

stweil wants to merge 1 commit into
anomalyco:devfrom
stweil:compact-oversized-heads

Conversation

@stweil

@stweil stweil commented Sep 24, 2026 •

Copy link
Copy Markdown

When the serialized conversation head no longer fits the model's usable context, compaction gave up with "Session too large to compact" and the session became unusable. Shrink the head from the oldest complete turns instead so the summarizer request fits the model context.

Assisted-by: OpenCode / qwen3.8-27b-thinking (Alibaba Cloud)

Issue for this PR

Fixes #29857, #48844 and maybe some more

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

When the serialized conversation head no longer fits the model's usable context, compaction gave up with "Session too large to compact" and the session became unusable. Shrink the head from the oldest complete turns instead so the summarizer request fits the model context.

How did you verify your code works?

Tested in local work.

Screenshots / recordings

No UI change.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

@github-actions github-actions Bot added needs:issue needs:compliance This means the issue will auto-close after 2 hours. labels Sep 24, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thanks for your contribution!

This PR doesn't have a linked issue. All PRs must reference an existing issue.

Please:

  1. Open an issue describing the bug/feature (if one doesn't exist)
  2. Add Fixes #<number> or Closes #<number> to this PR description

See CONTRIBUTING.md for details.

@github-actions

Copy link
Copy Markdown
Contributor

The following comment was made by an LLM, it may be inaccurate:

@github-actions github-actions Bot removed the needs:compliance This means the issue will auto-close after 2 hours. label Sep 24, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thanks for updating your PR! It now meets our contributing guidelines. 👍

@stweil

stweil commented Sep 24, 2026

Copy link
Copy Markdown
Author

Until this PR (or some other fix) is merged, a simple workaround can help: select a different model which has a larger context window than the current one. Then compression will work. After the successful compression, the original model can be used again to continue the work.

…ntext

When the serialized conversation head no longer fits the model's usable
context, compaction gave up with "Session too large to compact" and the
session became unusable. Shrink the head from the oldest complete turns
instead so the summarizer request fits the model context.

Assisted-by: OpenCode / qwen3.8-27b-thinking (Alibaba Cloud)
Signed-off-by: Stefan Weil <[email protected]>
@stweil
stweil force-pushed the compact-oversized-heads branch from 9102cbc to 36a9384 Compare September 25, 2026 07:17
@aitrawog

aitrawog commented Sep 25, 2026 •

Copy link
Copy Markdown

Reviewed against a live reproduction on opencode 1.18.32 (stable). Environment: provider ollama-cloud (https://ollama.com/v1), model deepseek-v4.1-flash (1,048,576 context / 393,216 max output).

Reproduction. Auto-compaction fired at a 666,763-token live context and the summarizer call was rejected: The prompt is too long: 1122556, model maximum context length: 1048576. The session then showed Session too large to compact and was unusable.

Mechanism (both steps verified).

  1. The provider admits a request on input tokens only; the output budget is not counted against the window. Probed directly: prompt 800,031 + max_tokens 393,216 → accepted; prompt 1,050,041 + max_tokens 1 → rejected (The prompt is too long: 1050041); max_tokens 2,000,000 → separately rejected (exceeds maximum output tokens). So compaction.reserved and max_tokens do not affect admission.
  2. The head is effectively the whole history. I reconstructed the full serialized head with the exact serialize() rules and measured it through the real provider: 1,128,448 input tokens, vs the logged crash prompt 1,122,556 (0.5% apart — the difference is the prompt template + prior summary). select() budgets only the retained tail; the default tail is tiny (min(15_000, …)), so with the newest turn (~43k) above it the head is everything older. That is the missing budget this PR adds.

The approach is right. Dropping oldest complete turns bounds the head. On my session the head was ~1.03M tokens while the newest single turn was only ~43k, so the head, not the tail, is where the budget was missing.

Three gaps worth closing before merge:

  1. Irreducible head. The loop stops at i < all.length - 1 and returns input.messages unchanged when start === 0. A single-turn head (or a newest turn larger than the budget) is returned oversized and the provider still rejects it — the same wedge survives. Suggest splitting the newest turn via splitTurn, or allowing an empty head (summary-only) with a clear log.
  2. Budget under-count. estimate() measures toModelMessagesEffect(messages), but the request is built from serialize() text (all reasoning included, tool output capped at 2,000 chars) plus previousSummary and plugin context. SUMMARIZER_PROMPT_RESERVE = 8_000 will not absorb a large previousSummary. Suggest estimating the actual built nextPrompt and iterating until estimate(builtPrompt) <= usable − summaryOutput.
  3. Cost. Each iteration re-estimates a fresh slice (O(turns²)); pre-computing per-turn sizes once makes it linear.

Style nit per CONTRIBUTING: prefer an immutable local over let.

Settings that mitigate until this lands (opencode.json): set limit.input so compaction.reserved is honored; keep reserved large (it fires compaction earlier, when the serialized head is smaller); keep compaction.prune: true; and raise compaction.preserve_recent_tokens so a larger tail is retained verbatim and the summarized head shrinks. On my session preserve_recent_tokens: 500000 brought the reconstructed head from ~1,128,448 down to 634,064 real tokens — comfortably under the input window. (Turning compaction onto a larger-context model, as @stweil noted, also works.) This is a stopgap: preserve_recent_tokens caps the tail, so as the session grows the head grows again — only dropping oldest turns (this PR) bounds it permanently.

Assisted-by: opencode ollama-cloud/deepseek-v4.1-flash (analysed on a live system)

github-actions Bot pushed a commit to opencharly/opencharly that referenced this pull request Sep 25, 2026
… params (#251)

The umbrella opencode config kept the models.dev generation default and the
stock compaction tail budget. On a long session that combination wedges:
select() budgets only the retained tail, whose default is min(15000, ...), so
once the newest turn exceeds it the summarizer head becomes the ENTIRE
serialized history (all reasoning text retained, tool output capped at 2k).

Measured on session ses_f31022a99ffe1gLJ1RdyDnhlAh (opencode 1.18.32,
ollama-cloud/deepseek-v4.1-flash, 1048576 context):
- auto-compaction fired at a 666,763-token live context;
- the summarizer prompt was 1,122,556 input tokens (logged) — reconstructing
  the head with the exact serialize() rules and measuring it through the real
  provider gives 1,128,448 (0.5% apart);
- the provider rejected it ("The prompt is too long"), the session was wedged.

The provider admits on INPUT tokens only (probed: 800k prompt + 393216
max_tokens accepted; 1.05M prompt rejected), so reserved/max_tokens do not
size the request — the head does.

Changes:
- compaction.preserve_recent_tokens: 500000 — keeps a large verbatim tail so
  the summarized head shrinks. Reconstructed head drops from 1,128,448 to
  634,064 real tokens. Stopgap until opencode drops oldest turns itself
  (anomalyco/opencode#48844, PR #51121).
- model options: max_tokens 262144, reasoningEffort high, and the sampling set
  (temperature 1.0, top_p 0.95, frequency_penalty 0.5, presence_penalty 1.0) —
  exact parity with the org PR-eval engine and the vendor-recommended sampling
  for deepseek-v4.1-flash.

Upstream: anomalyco/opencode#48844 (open) and anomalyco/opencode#51121 (open,
"drop oldest turns when compaction head exceeds model context").

Assisted-by: opencode ollama-cloud/deepseek-v4.1-flash (analysed on a live system)
@thdxr

thdxr commented Oct 10, 2026

Copy link
Copy Markdown
Member

This branch has been upgraded to V2. We're no longer taking PRs for V1. If you think this is still relevant, please port it over and open it for V2.

— from 𝕺𝖕𝖊𝖓𝕮𝖔𝖉𝖊

@thdxr thdxr closed this Oct 10, 2026
@stweil
stweil deleted the compact-oversized-heads branch October 24, 2026 15:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG]: /compact can exceed model context before summarization on large sessions

3 participants