Repository navigation
Description
Description
On a 1M context model, the default compaction buffer is 15% of the window:
157,286 tokens. That is large in absolute terms (about 10× the 16k minimum),
but smaller than the 256,000 output reservation v2 uses per request. The two
ceilings are therefore inverted:
- compaction ceiling:
1 048 576 − 157 286 = 891 290(85%) - request ceiling:
1 048 576 − 256 000 = 792 576(75.6%)
The request overflows 98,714 tokens before compaction can run. On a 1M model
the percentage buffer is both large (157k below the limit) and insufficient
(below the 256k reservation).
flowchart LR
A["0"] --> B["792,576 · 75.6%<br/>request ceiling"]
B --> C["891,290 · 85%<br/>compaction ceiling"]
C --> D["1,048,576 · 100%<br/>context limit"]
B -. "98,714<br/>reservation exhausted" .-> C
Sessions fail at ~75.6% of the window, or compact mid-turn. Observed on
OpenCode 2.1.4.
Model: DeepSeek V4.1 Flash (deepseek-flash), context_window: 1048576,
max_output_tokens: 393216. No limit override.
Error:
This model's maximum context length is 1048576 tokens. However, you requested
1048582 tokens (799027 in the messages, 249555 in the completion).
799 027 + 249 555 = 1 048 582. The 249 555 completion is the room left:
1 048 576 − 249 555 = 799 021. That 799 021 is the engine's estimate; the
provider counted 799 027, so the request exceeded the window by 6 tokens.
compaction.auto: true: the overflow recovery compacts at ~75.6% mid-turn.compaction.auto: false: the turn fails.
v1 capped every request at min(limit.output, 32_000), so the request ceiling
was 96.9% and compaction (85%) ran first.
Expected: the request ceiling should not be lower than the compaction ceiling
(compute the buffer from the output reservation, or cap maxTokens below the
compaction ceiling).
Code references, dev (v2) as of 2026-10-23:
packages/core/src/session/model-request.ts:48-54 (OUTPUT_TOKEN_MAX = 256_000),
:85-90 (outputLimit), packages/core/src/session/compaction.ts:1104-1111
(calculateCeiling).
Workaround, no per-model limit.output: a v2 model transform caps
limit.output at 32,000 for every model.
~/.config/opencode/opencode.jsonc:
{
"compaction": { "keep": { "tokens": 24000 } }
}auto keeps its default (true); buffer is unset (default 15%, min. 16k →
compaction at 85%). keep.tokens: 24000 is a compaction-quality setting
(recent context kept verbatim), not part of the ordering fix.
~/.config/opencode/plugin/output-cap.ts:
export default {
id: "output-cap",
effect: (ctx) =>
ctx.model.transform((models) => {
for (const m of models.list())
if (m.limit.output > 32_000)
models.update(m.providerID, m.id, (item) => {
item.limit.output = 32_000
})
}),
}Restart opencode, then verify (redirect to a file; the CLI truncates large
payloads on a pipe):
opencode api get /api/model > /tmp/model.json
jq '.data[] | select(.modelID=="deepseek-flash") | .limit' /tmp/model.jsonExpected: {"context":1048576,"output":32000}. The request ceiling is back to
1,016,576 (96.9%), above the 85% compaction ceiling.
Related issues (all closed, none fixed for this ordering)
- Bug:
limit.outputin config is silently capped at 32k;OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAXis a poor workaround #29363: closed "fixed in latest v2 release"; v2 removed the 32k request clamp and set the reservation to 256k, but did not couple the two ceilings. - DeepSeek V4 output limit is capped at 32K #38236: duplicate of Bug:
limit.outputin config is silently capped at 32k;OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAXis a poor workaround #29363. - Feature Request: Configurable Context Compaction Threshold #11314, Feature: Configurable compaction threshold and model (global + per-model) #11930, Feature Request: Configurable context limit and auto-compaction threshold #8140: closed by the 60-day inactivity bot; v2 has
buffer(tokens), not the requested percentage threshold. - Bug: compaction.reserved config is ignored for models without explicit 'input' limit #13980: closed by the inactivity bot;
reservedwas ignored withoutlimit.input, andlimit.inputstill selectswindowincalculateCeiling(a commit was referenced, then stale-closed). - compaction.auto: false is ignored when provider throws a context overflow error #16882: fixed by fix(opencode): respect disabled auto compaction on overflow #30749 for
auto:falseon provider overflow, not for this ordering. - [Bug] Compaction ignores "auto: false" config and OPENCODE_DISABLE_AUTOCOMPACT env vars #32385: closed completed; last comment says the issue persists.
- compaction.auto=false ignored: isOverflow called without cfg parameter #20249: self-closed, redirected to compaction.auto: false is ignored when provider throws a context overflow error #16882.
- Auto-compaction triggers at ~200k tokens instead of model's actual context window (1M) #15871: closed "cites pi mono not opencode"; misattributed.
- [FEATURE]: Improve compaction prompt to preserve critical context details #16512: closed by inactivity bot; compaction prompt quality.
- Custom provider (LM Studio) ignores limit.output config, hardcodes max_tokens to 32000 #20078: closed by inactivity bot; custom provider ignores
limit.output. - GitHub Copilot plugin ignores user-configured model limit overrides #21564: closed by inactivity bot; Copilot ignores model limits.
- max_tokens defaults to 32000 when using a custom provider #1735: closed by inactivity bot; "Bug still exists" (Sept 2026).
Plugins
none for the reproduction. Workaround: local plugin output-cap.ts (v2 model transform, caps limit.output at 32000 for every model).
OpenCode version
2.1.4
Steps to reproduce
- Use a model with context >= 1M and max_output_tokens >= 256000 (DeepSeek V4.1 Flash: 1048576 / 393216).
- Leave
limitunset in opencode.jsonc. Setcompaction.auto: false(or true). - Run a session until the prompt reaches ~792k tokens.
- The next request fails with the context-length error (auto:false) or triggers a compaction at ~75.6% (auto:true).
Screenshot
Operating System
Linux
Terminal
N/A (opencode web)
Activity
opencode-agent commentedon Oct 23, 2026
Thanks for the detailed analysis, @oopen. I reproduced this on 2.1.4, and it still happens on the latest
dev(24b5ed8).Setup: a mock OpenAI-compatible provider with
limit: { context: 1048576, output: 393216 }, nocompaction.buffer. The mock reports an ~800k prompt after the first turn, then counts a few tokens more than OpenCode on later requests and enforces the 1,048,576 window the way DeepSeek does.max_tokens: 248568, exactly the room OpenCode estimates is left, and the provider rejects it:you requested 1048583 tokens (800015 in the messages, 248568 in the completion).compaction.auto: falsethe turn fails. Withcompaction.auto: trueoverflow recovery compacts mid-turn at ~76% instead of at the preflight 85%.The script below runs the reproduction (with
OC_DEV=<checkout>it runs from source). I've passed this to the maintainers with pointers tooutputLimitinmodel-request.tsandcalculateCeilingincompaction.ts. Yourlimit.outputcap plugin looks like a reasonable workaround for now.Related: #50574 (1M-context sessions not compacting before a provider 400), #29363 (the change that removed the 32k output clamp).
Reproduction script (repro.sh)
Run it:
bash repro.shoopen commentedon Oct 23, 2026
Reproduced here too, same mechanism.
The overshoot size depends on the count drift between the engine estimate and
the provider (1 token in your mock, 6 on DeepSeek). The cause is that
maxTokensis set to the remaining room with no headroom, while the compactionceiling is computed above the request ceiling.
#50574 is the same class on 1M models. Its ceiling formula is from 2.0.12
(
context − max(min(output, 32k), buffer)= 968k); on 2.1.4 the default iscontext − 15%= 891k, still above the 792k request ceiling.The workaround is a v2 model transform that caps
limit.outputat 32k forevery model (not a per-model
providers.<id>.models.<id>.limitoverride).compaction.keep.tokens: 24000is a separate quality setting. It restores thev1 ordering: request ceiling 96.9% > compaction 85%.
mark-kojo commentedon Oct 23, 2026
Same mechanism on a much smaller prompt, with Kimi K2.6 through OpenRouter (2.1.3 and dev). The catalog lists
context: 262144, output: 235929, so the output request fills almost the whole window and the drift only has to be a few hundred tokens:So the session compacts at about 27k tokens, roughly 10% of the window. The drift here is bigger than in the DeepSeek case because the estimate seems to leave out part of the tool definitions (an MCP server with ~45 tools) and pictures.
One side effect worth knowing: after the mid-turn compaction the model only had the summary and answered a request nobody made.
Capping the output with a provider override (
limit: { output: 32000 }) fixes it for us, which matches the v1 behaviour you mention.oopen commentedon Oct 23, 2026
The Kimi case is the same ratio problem at a different scale: 235,929/262,144 =
90% of the window reserved for output, so the compaction ceiling collapses to
26,215 (10%). The 79-token overflow is the estimator missing part of the tool
definitions/images, which
maxTokens = roomcannot absorb.Two separate defects:
maxTokens = roomleaves no headroom. The request must leave the compactionbuffer free:
maxTokens = min(output, context − buffer − prompt). On Kimithat gives ~195k output at 27k input instead of 235k, and the session can
reach 222k (85%) before compaction instead of 26k.
miss is enough at the boundary.
A 32k cap fixes both by brute force but throws away 200k of output on this model.
Dopetaiga commentedon Oct 23, 2026
I'd like to take this. Plan: make the compaction ceiling never exceed the request ceiling (context minus the output-token reservation) in
calculateCeiling/ the model-request ceiling math, so compaction always triggers before the request-side overflow on ~1M-context models. Will verify against the numbers from the issue and open a PR.oopen commentedon Oct 23, 2026
Proposition — derive the output budget and reserve from the model's output limit
Verified against
anomalyco/opencode@e3e83be(dev), 2026-10-23.TL;DR
Two budgets are computed independently and neither reserves anything for the
other. Replace the percentage reserve with a reserve derived from the output
budget, and make the compaction request truncate its prompt to the ceiling:
O = min(limit.output, OUTPUT_TOKEN_MAX)is the engine's usable output, andoverheadis the summary prompt plus its nudge — about 1k tokens.At
P = Tthe compaction request sendsTinput +Foutput =window - Sand fits with
Sof margin. If the prompt overshotT— a single step grew byup to ~22k tokens at p99, per the comment on
RESERVE_SHARE— the prompt istruncated back to
Tinstead of the output being squeezed.On 1M the reserve becomes 40k (3.8%) instead of 15% (157k). On 32k a derived
reserve of 12k replaces a 16k floor that today pushes the ceiling down to 51%.
On 128k the ceiling drops by 15.5 points — the cost of a 32k summary budget,
which is 24% of that window.
The default keeps
Fat the allowance the code already grants a shrinkingsummary (
SUMMARY_OUTPUT_FLOOR = 32 000). An under-sizedFdoes not degradethe summary, it fails the compaction and re-fails on every following turn, so
the default is the safe end of the trade and measurement lowers it if warranted.
What is broken
The two budgets, quoted from
e3e83be:The request budget is
min(O, C - P): it ends exactly onCwith no margin, soany drift between the estimate and the provider's count overflows. The ceiling
is unrelated to it, and
limit.outputnever enters it.COBoth were primary requests issued below a ceiling of 85%, and both asked for the
entire remaining window.
That is the reported failure:
P + Mlands onC, so six tokens of drift rejectthe request. A separate risk is a reply that consumes its whole budget — it
leaves the next prompt at
C - S, past the trigger and already 235k above whereit started on Kimi, with no room for the tool results that follow. Neither
reported request got that far. The second risk is why the provider-rejection path
and the
SHRINK_STEPSladder must stay — no arithmetic bound can anticipate tooloutput. What the arithmetic removes is the zero-margin request, and the waste of
asking for 235k of output on a 27k prompt.
The change
P + M(P) <= C - Sfor every request withP <= C - S; atP = C - Sthebudget is 0 and the request must not be sent.
T - overhead, so the fullrequest — conversation plus summary prompt and nudge — is
TandT + F = C - S. This replacesfit = max(budget, summaryRoom), which lets thecompaction prompt run up to
C - 16_000to keep the prefix cache: a deliberatetrade this proposal reverses in favour of a guaranteed
F.buffer),P = TimpliesM = F. Under abufferoverride the compaction budget is
reserve - S.M(P)depends onSonly, so it is identical in both variants; the variantmoves the ceiling, not the per-turn output.
Ceilings
TThe 64k row is 65 536, above the point where
Sstops tracking the window, soits reserve is 24 384 (37.2%) and the ceiling reads 62.8% and not 62.5%. The 32k
and 16k rows follow
T = 5C/8exactly. The three large windows are all at orabove 128 000, where
SandFare both capped andT = C - 40 000.One decision needed
F_policy: 32k (output-first default, matchesSUMMARY_OUTPUT_FLOOR, 128k at69.5%) or 16k (window-first, 128k at 81.7%)? The failure modes are asymmetric:
too large an
Fcosts ceiling, too small anFfails the compaction outrightand re-fails every turn. The default should stay at 32k until the p99 of
usage.output_tokensover compaction responses is measured; 16k is only safeonce that tail is known to sit well below it.
Full derivation, implementation sketch, tests and release note
Why not 15%
15% is doing two unrelated jobs: a summary budget on large windows, and a
step-absorption margin everywhere. The summary needs an absolute amount; a step
is better handled by truncating the prompt at compaction time.
Net effect on the reserve: 157k → 40k on 1M, 39.3k → 40k on 262k, 19.7k → 40k on
128k, 16k → 24.4k on 64k, 16k → 12.3k on 32k, 2.5k → 6.1k on 16k. This is not a
uniform shrink. It is a fixed budget where a proxy used to be, so it falls where
the proxy was too large and rises where it was too small.
The 16k floor is one defect with two symptoms. The
window >= 2 * RESERVE_MINguard makes the reserve discontinuous at 32 000 — the ceiling drops from 85% to
50% across a single token. And between 32 000 and 106 667 the floor dominates the
15% share, so 64k and 32k windows lose 9.4 and 33.8 points of ceiling to a floor
meant for larger windows.
A derived reserve has neither problem: it is continuous across all six rows, with
kinks only where
SandFstop tracking the window (64 000 and 128 000).The 128k row is the honest cost of the default: a 32k summary budget is 24% of a
128k window, so the ceiling drops to 69.5%. The window-first variant gives back
12 points there at the price of a 16k summary budget. That trade is the one open
product decision.
Proposal
Let:
C= context window (limit.inputwhen set, ascalculateCeilingalready does)P= prompt tokens,measured + ceil(estimated * (1 + ESTIMATE_ERROR)), onevalue for both the trigger and the request (see "one estimate, one inflation")
O=min(limit.output, OUTPUT_TOKEN_MAX)F= compaction output budget,S= request safety reservereserve= tokens kept free belowC,T=C - reserveoverhead= summary prompt + nudge, about 1k tokensDefault (output-first):
Window-first variant:
F_policy = 16 000,S_policy = 8 000. OnlyF_policychanges:
Scovers estimate drift and does not depend on the summary budget.window / 2cap only binds abufferoverride: the derived default peaksat
3C/8.compaction.auto: false,Mreaches 0 atC - Sand the request mustfail with a typed error rather than be sent with a token budget of 0.
Cbetween two requests;Scovers estimate drift, not tool output.Numbers
Output at a given prompt — the per-turn budgets,
M(P), identical in bothvariants because they depend on
Sonly.Tis in the table above.Small windows, output-first default. All three rows sit below 128 000, so
F = C/4; the 64k row is 65 536, above the point whereSis capped at 8 000:FSTT + FCompaction request at
T, full request includingoverhead. The total isC - Sin both variants — the variant moves the trigger point, not the total:T + FWhy this is the best option
Against a 32k cap on
limit.outputA cap truncates output on every turn regardless of the window: it hits long
reasoning (as reported in #29363:
reasoning: 32000, output: 0, finish_reason: length) and large writes, and on Kimi it throws away 204k ofoutput the model is allowed to produce. It does not move the compaction
ceiling —
limit.outputdoes not appear incalculateCeiling— so it buysnothing on the ceiling it is usually proposed to protect.
Against 15%
It scales with the window while the need does not: 157k on 1M for a summary that
is structured and terse, 2 457 on 16k for a prompt plus a summary, and a cliff
at 32 000. The margin's real job — absorbing a step past the trigger, p99 ≈ 22k —
is done deterministically by truncation instead of by a window-proportional
number that is wrong in both directions.
Against tuning
compaction.bufferbufferis already the exact reserve:T = C - buffer, ignored whenbuffer >= window. So it cannot fix the request path —outputLimitneverreads it — and
buffer: 0today sets the ceiling toC, i.e. no reserve at all.The proposal keeps
bufferas an override but clamps it to[1, C/2]and makesthe request path honour the same
S.Who decides what
F >= p99(summary output),S >= p99(drift)F_policy = 32kvs16kcompaction.buffer: keep, clamp, rename toreserveImplementation sketch
packages/core/src/session/limits.ts(new, shared). It also owns the outputconstants, so that
model-request.tscan importlimitsForwithout a cycle:model-request.ts:The call site must not send a zero budget: when
outputLimit(...) < 1, compactbefore sending (auto) or fail with a typed session error (auto: false). Never put
max_tokens: 0on the wire.compaction.ts:Notes:
outputLimitmust switch fromlimit.contexttolimit.input || limit.context.calculateCeilingalready does this; the two currently disagree whenlimit.input < limit.context.usable, not rawlimit.output, intolimitsFor:Fmust never exceedwhat the engine can actually ask for.
preparemust keep receiving the ceiling as its budget:prepare({ ..., inputTokens: { measured: T, estimated: 0 } })givesmaxTokens = min(O, C - S - T) = F.deliverthen truncates toT - overheadand
sendappendsoverhead, so the full request isTandT + F = C - S. Neither the truncation nor the overhead is counted twice.bufferhas to reachmodel-request.ts, which has no access tocompaction.Settings. Add abufferfield toInput, filled bypreparefrom the compaction settings, and thread it through the two
fitOutputcalls aswell — otherwise
Sdiffers between the main path and the shrink path.fitOutputbecomes inert under the truncation rule.preparesizes thecompaction request at
F,delivertruncates toT - overhead, somin(current, …)can only lower a budget that is already guaranteed. Keep itas a defensive assertion or drop it, but do not rely on it. The
deliverladderis still needed: it is what absorbs a provider that counts more than the
estimator, and the
payload-too-largepath, which no arithmetic can predict.OUTPUT_TOKEN_MINmust leaveoutputLimit. It is documented as reachableonly with
auto: false, and at that pointroom = C - S - P < 1: sending1 024 tokens overflows the window by 1 024. Remove the clamp and let the caller
fail with a typed session error — the honest outcome when automatic compaction
is off. The constant then becomes unused and should be deleted.
only. Adding it to every normal request costs its size on each turn and moves
the trigger earlier for no reason.
Ffails the compaction, it does not degrade it.finish_reason: "length"on the summary maps tocompaction.failed("Compaction summary reached the output token limit"), and
deliverneverretries a
Failurefromsend.due()stays true, so the next turn attemptsthe same failing compaction. This is why
F_policystarts atSUMMARY_OUTPUT_FLOORrather than below it.precomputestarts atPRECOMPUTE_RATIO * T, so a higherTdelays it. ItstailLimitalready caps the kept tail at a quarter ofT, so the checkpointfits by construction once
Tis above a few thousand tokens.One estimate, one inflation
due()comparesmeasured + estimatedto the ceiling;outputLimitallocatesagainst
measured + ceil(estimated * 1.15). The gap is 15% of the estimatedpart — negligible on a fully measured prompt, ~120k on an 800k prompt that is
entirely estimated.
Both sides must use the inflated value,
Pas defined above. The consequence isthat the trigger fires earlier exactly when the estimate is least reliable, which
is the intended direction: the ceiling then bounds the request rather than the
estimate. Landing this change without unifying
Pleaves the budget and thetrigger disagreeing by up to 15% of the conversation.
estimateContextbecomesP, soprecomputemoves with it: the backgroundsummary now starts at
PRECOMPUTE_RATIOof the ceiling measured on the inflatedprompt, and
PRECOMPUTE_MIN_TOKENS/tailLimitmust be re-read against the newTrather than the old 15% ceiling.How to set
F_policyandS_policy: measurementLog per request:
inputTokens.measured,inputTokens.estimated, the provider'susage.prompt_tokens(cache included), the tool-schema and image token counts,T,M,reserve, and the branch (policy/window4/buffer).Three distributions matter, and they are not the same:
With the current reports, drift is 6 tokens on DeepSeek and 79 on Kimi. Step is
~22k at p99 per the comment on
RESERVE_SHARE— two orders of magnitude larger.Sizing
Son drift alone is correct only because truncation absorbs thestep; the proposal should not be read as making
Scover both.For
F, the relevant statistic is not the mean but the tail that producesfinish_reason: "length": any summary longer thanFbecomes a failedcompaction, not a shorter summary.
Migration
ceilings: 1M 85.0% → 96.2%, 262k 85.0% → 84.7%, 128k 85.0% → 69.5%,
64k 75.6% → 62.8%, 32k 51.2% → 62.5%, 16k 85.0% → 62.5%.
F = 32 000is 24% of the window.The window-first variant gives back 12 points there with a 16k budget.
3C/8reserve: it rises from 2 457 to 6 144, and thesummary budget from 2 457 to 4 096 — a deliberate increase, not a by-product of
F_policy.the
Smargin on the request, not the ceiling.bufferkeeps its meaning (T = C - buffer) but is clamped to[1, C/2]instead of being ignored at or above the window. A user reproducing the old 1M
ceiling sets
compaction.buffer: 157286; the output at 800k is then 240 576,not 248 576, because
Sis reserved.Sat every prompt size: 248 576 → 240 576 at 800kon 1M, 235 080 → 227 080 on Kimi, 51 072 → 43 072 on 128k.
1M gets
F = 4k,S = 2k,T = 99.4%. That is intended —Fis bounded bywhat the summary can actually emit — but it is a visible change from 85%.
bufferbelowsafety + Fshrinks the summary budget accordingly; log awarning when it does.
max_tokensis never below 1; a request with no room is compacted or failsexplicitly.
Edge cases
T: the conversation is truncated toT - overheadbeforesummarising. At most
Fis dropped when the overshoot comes from the previousresponse, and at most
reserveif tool results pushed the prompt to the windowedge. The dropped history is not summarised: it is degraded by level (tool
outputs clipped, then removed, then user text clipped) before any exchange is
omitted.
C:M = 0; compaction must run with a truncatedprompt or fail with a typed error.
3C/8holds while bothFandStrack the window, i.e.C <= 64 000. From 64 000Sis capped at 8 000; from 128 000Fis cappedat 32 000.
O < F_policy:F = O,S = O/2; the summary may use the model's wholeoutput budget, which is the correct ceiling for that model.
auto: false: output shrinks to 0 atC - S, i.e. 8k earlier than todayon 1M, and the request is refused rather than degraded. Document it as a
deliberate trade-off, not as a stall.
keep: after compaction the checkpoint issummary + keep, and it muststay below
Tor the next turn re-triggers, on any conversation longer thankeep.keepdefaults to 15 000, so any window below ~29 000 re-compacts onevery turn;
keep = 24 000, the reported configuration, raises that thresholdto ~43 000. Validate
keep + summary + overhead < Tand clamp with a warningrather than reject — a hard failure on startup would strand the session.
max_tokensstays a per-request reservation; a providercounting more input than the engine is covered by
S.Tests
limitsFor: six windows × two variants, plusbufferunset,0,null,NaN,< safety,>= C/2.P + M(P) <= C - SforPin[0, C - S];M(T) = Fon thedefault branch;
T + F = C - S;output + safety <= reserve.overheadincluded, estimates atTafter truncation; the conversation target is
T - overhead, notT.room < 1is never sent:outputLimitreturns 0 and the callerraises a typed error.
limit.outputat0,undefined,NaN;windowat0,undefined,NaN(calculateCeilingmust stayInfinity, as today).P > T⇒ the compaction request's prompt is<= Tand its budgetis
F; the dropped amount never exceedsreserve.fitOutputdoes not change the budget once truncation has run.keepclamp: withkeep = 15 000the raw condition is violated on the 16kwindow only; with
keep = 24 000on 16k and 32k. The test asserts the clampbrings those windows under
Tand leaves the other four unchanged.produce
P + M <= C - Sand must not be rejected.buffer: 0must not produce a ceiling ofC.fitOutputand the main path must agree onSwhenbufferis set.Release note
Open questions for maintainers
compaction.buffer: keep the name and clamp it, or addcompaction.reservewith the new semantics and deprecate
buffer?M(P)andTbe exposed in session metadata / the TUI, since theychange per turn?
outputLimituseslimit.contextwhilecalculateCeilinguseslimit.input || limit.context. Is aligning them part of this change or aseparate fix?
OUTPUT_TOKEN_MAX(currently 256 000) for models with 350k+ outputa separate issue?
fix(core): keep a user buffer authoritative and offset the reservatio…