Repository navigation
fix(core): start background compaction a fixed lead before the threshold - #60049
Conversation
There was a problem hiding this comment.
I found no problems that should block this change. The new compaction tests fail on dev and pass here, bun test test/session in packages/core passes (712 pass, 0 fail), and typecheck:tsc is clean. The new start point, the half-the-room cap on small windows, the cache-age check and the 40% tail limit all match the description. The cache age is measured from when the latest response was created, which matches when the provider last used its cache. The new tests use TestClock rather than real time, so they shouldn't be flaky. I didn't run it end to end: the simulated model can't recreate real token counts, latencies or cache expiry.
Public API: unchanged. It only reads the existing cache.ttl model setting; no config, schema or SDK changes.
Related: #50777
e760f96 to
7edd932
Compare
There was a problem hiding this comment.
The new commits only rebase this onto the current dev; your changes to compaction, the tests and the docs are unchanged, and I have no new concerns. On the rebased head, bun test test/session in packages/core passes (755 pass, 0 fail) and typecheck:tsc is clean.
Public API: unchanged. It only reads the existing cache.ttl model setting.
There was a problem hiding this comment.
The new commit only merges dev in; your changes to compaction, the tests and the docs are unchanged, and I have no new concerns. On the merged head, bun test test/session in packages/core passes (755 pass, 0 fail) and typecheck:tsc is clean.
Public API: unchanged. It only reads the existing cache.ttl model setting.
Issue for this PR
Closes #58818
Type of change
What does this PR do?
Changes when
experimental.background_compactionstarts a summary and when it drops one. Three rules change.1. Start a fixed 60k tokens before the threshold, not at 85% of it, and drop the 64k minimum. A background summary takes 30–60 s to write, and the session only waits if the conversation reaches the threshold first. How much it grows in that time doesn't depend on the window: a few thousand tokens at the median, and 25k or more about one time in twenty, when large file reads arrive together.
Today's rule is right only by coincidence:
On small windows, the lead is capped at half the room since the cycle's first response, so a new summary still covers more than the previous one.
2. Don't start from an expired prompt cache. After a break longer than the cache lifetime, the next step started a background summary from the same cold prefix the next request was about to write again, so the whole prompt was written twice. I hit this locally: an Opus session at 823k tokens, an 85-minute break, and a background summary that wrote 825,304 tokens to the cache instead of reading them (about $4.13 instead of $0.16). It now waits for a response whose cache is still warm (the model's
cache.ttl, 5 minutes by default), the way it already waits for the first response after a model switch.3. Allow a verbatim tail of up to 40% of the budget instead of 30k. Once 30k tokens (twice
keep) arrived after the held summary, it was replaced, or at the threshold rejected in favor of a foreground compaction. That happened at the same 30k on every window, and on 1M windows most held summaries were replaced before they were used. Typical tails are 10–20k, so the limit only matters for bursts of large tool inputs or pasted text. At 40%, a swapped-in context still stays well under the threshold.To check these numbers, I replayed about 41k steps from my local sessions through a simulation of this code. It uses only token counts and timings, no content, and draws summary latencies from measured requests.
On top of the summaries per compaction above, sessions that end before the threshold leave summaries that are never used: about 1.5 per compaction on 1M windows today, 0.6 with this PR.
The trade-off is on windows from 128k to 262k, where a summary now starts earlier:
On 1M windows the cost per step barely moves (+0.1% on Opus, +0.6% on GPT), and summary spend drops from 91¢ to 37¢ per compaction on Opus.
Charts
Both sides run the same simulated sessions; only the background compaction rules differ. Everything that changes, better in green and worse in red:
Where a summary starts. Today it starts 128k before the threshold on a 1M window and 13k before it on GPT 128k:
When compaction runs in one session on a 200k window. Dev waits for the summary at 4 of 16 compactions, this PR at none. Each compaction keeps more verbatim, so in the same time the PR compacts 17 times:
The same on a 1M window. Compaction runs at the same moments; dev starts its summary 128k early and throws one away:
How long the session waits when compaction is due:
What the summaries paid for turn into. On 1M windows dev pays for 3.3 summaries per compaction and uses one. On smaller windows this PR pays for more that are never used, because more sessions end within the longer lead:
How much of the context one compaction summarizes and how much it keeps:
Not in this PR:
How did you verify your code works?
session-compaction.test.ts. They fail ondevand pass here:cache.ttl: "1h";bun teston the session test files inpackages/core(713 pass) andbun run checkfrom the root.compaction.mdxto describe the new start point, the cache wait and the tail limit.Visual
Checklist
Added by the opencode bot (AI-generated).
Related: #50777