Repository navigation
Proposal: Make local context-overflow recovery actually reachable #9
Description
Proposal
Why
The 413 auto-recovery for local llama-skein providers has never executed.
Diagnosed live on proxmox / qwopus3.6-27b-v2-mtp-q8-0 (2026-07-27) after the
model repeatedly appeared "stuck" in a Build session while z4 was unaffected.
-
The handler gates on the wrong error type for this failure mode.
adjustLocalContextOnOverflow(packages/opencode/src/provider/provider.ts:1379)
returns early unlesserror.type === "context_too_large". For a prompt
that overflows an already-loaded, correctly-configured model, llama-skein
actually emitstype: "exceed_context_size_error",code: "prompt_over_max_safe_ctx"(internal/server/promptguard.go:103-104).
Correction to an earlier draft of this proposal:context_too_large
is not dead code and does not "appear nowhere" — it is real and still
fires, from a different component for a different failure mode:
proxy/proxymanager.go:1095sends it when a model fails to load
because its configured ctx doesn't fit available memory, distinct from a
prompt being too large for an already-loaded model. Every prior task that
hardened this function (ctx-display-and-overflow-correctnesstasks 1–3,
all marked complete) refined code that was unreachable for the
prompt-overflow class specifically — the model-misconfigured class it
already handled correctly and continues to. -
The remedy is wrong for this error class.
prompt_over_max_safe_ctx
means the prompt is too large for the host, not the model's ctx-size is
misconfigured. The handler responds by PATCHing the backend to a larger
ctx_size. On the observed host that is impossible: the model already needs
32078 MB of a 32624 MB card (98.3%, 546 MB headroom). Growing ctx would
OOM, not recover. -
The ceiling it reads is legitimately absent. The handler takes
max_fit_ctxand bails on<= 0. For this model llama-skein omits
max_fit_ctxbecause its KV budget is negative
(32624 × 0.92 × 0.85 − 27701 × 1.05 = −3574 MB), so recovery would fail even
if reached. Meanwhilemax_safe_ctx: 74711is present and is the value
the backend actually enforces — andfetchLocalModelFitin the same file
already documentsmax_safe_ctxas "the authoritative prompt ceiling".
The two functions disagree about which field is authoritative. -
A single failed fit probe wedges the whole session. Discovery sets
limit.context = fit.max_safe_ctx ?? reported context_length. Fit and
/v1/modelsshare one 2s AbortController, and discovery runs once at
startup. When the fit probe loses that race, the model silently keeps
90112instead of74711— 21% over the enforced wall — for the entire
process lifetime. Compaction then sizes its budget from the wrong number,
never trims, and every request 413s with recovery dead. This is why the
symptom is intermittent ("stuck again") and why restarting clears it.
z4 is unaffected only because its gap is proportionally small and rarely
reached (max_safe_ctx 237076 vs reported 262144); proxmox sits at
74711 vs 90112, which a Build session crosses routinely.
What
- Match the real contract. Recovery MUST trigger on llama-skein's actual
overflow signal (type: "exceed_context_size_error"/code: "prompt_over_max_safe_ctx", HTTP 413), not oncontext_too_large. The
matcher MUST be driven by the published contract, not a hand-copied string. - Trim, do not grow. A prompt-overflow 413 MUST be handled by reducing the
outgoing prompt to the advertised ceiling (triggering compaction), never by
PATCHing the backend to a largerctx_size. Backend ctx-patching remains
valid only for a genuine model-misconfigured overflow. - Use the advertised ceiling. The limit MUST be read from the machine-
readableX-Skein-Max-Safe-Ctxresponse header (falling back to/api/fit
max_safe_ctx), never parsed out of the human-readable message, and never
gated onmax_fit_ctx, which is legitimately absent for a VRAM-tight model. - Self-heal the stale limit. On a prompt-overflow 413 opencode MUST correct
the in-memorylimit.contextfor that model from the authoritative value in
the response, so the session recovers without a restart. - Never silently over-estimate. When the
/api/fitprobe fails, discovery
MUST NOT fall back to acontext_lengththat exceeds the enforced budget
without marking the model as unverified; a too-large budget is worse than a
conservative one. Give fit its own timeout rather than sharing the models
fetch's 2s budget.
Non-goals
- No change to llama-skein's fit math or its VRAM safety fractions
(companion changereport-achievable-ctx-for-configured-models). - No re-litigation of the display denominator
(ctx-display-and-overflow-correctnessowns that and is complete). - Capacity remediation for the proxmox host (lowering
--ctx-sizeto ~74k or
relocating the model) is an ops action tracked separately, not a code change.
Impact
packages/opencode/src/provider/provider.ts:adjustLocalContextOnOverflow
trigger condition, remedy, and ceiling source; the 413 retry site (~:2126);
discoverOpenAICompatibleModelsfit timeout isolation and fallback policy.packages/opencode/src/provider/error.ts: overflow classification already
catches 413 — must stay consistent with the new handler.packages/opencode/src/session/compaction.ts: consumeslimit.context;
benefits from the corrected value, no signature change expected.- Regression risk is low and well-bounded: the code path being changed is
currently dead, so behaviour can only improve from "no recovery".
Disposition (2026-09-18)
Shipped. Shipped (provider.ts overflow discriminator + getModelFit). Open 7 (cross-repo drift guard) and 8 (live) are not blocking; reopen as a small change if the contract drifts.
Tasks
-
1. Add a failing regression test that feeds
adjustLocalContextOnOverflow
a real llama-skein 413 body
({"error":{"type":"exceed_context_size_error","code":"prompt_over_max_safe_ctx", "message":"prompt (~106692 tokens) exceeds the safe context ... (max_safe_ctx 74711)"}})
plus anX-Skein-Max-Safe-Ctx: 74711header, and asserts recovery fires.
It MUST fail againstmain— that failure is the proof the handler is
currently unreachable.- File:
packages/opencode/test/provider/provider.test.ts(3 new tests: header path,/api/fitfallback path, and a regression guard that the pre-existingcontext_too_largeclass still patches+retries) adjustLocalContextOnOverflowandStateexported fromprovider.tsto make this directly testable, matching the existingmergeDiscoveredModelexport convention.
- File:
-
2. Split the overflow classes. Introduce an explicit discriminator:
prompt_over_max_safe_ctx→ trim the prompt;
a model-ctx-misconfigured overflow → patch backend ctx (existing
behaviour). Only the latter may callpatchConfigModel.- File:
packages/opencode/src/provider/provider.ts(adjustLocalContextOnOverflow) - Note: see proposal.md correction —
context_too_largeis real (proxy/proxymanager.go:1095, model-failed-to-load class) and its existing patch-and-retry behavior is preserved unchanged, not replaced.
- File:
-
3. Prompt-overflow path: read the ceiling from
X-Skein-Max-Safe-Ctx,
falling back to/api/fitmax_safe_ctx. Do NOT consultmax_fit_ctx
and do NOT regex the message text. Drop themaxFit <= 0 → return false
bail from this path.- Implemented via
client.getModelFit({model})(single-model fit lookup, not the fleet-widegetFitReport) as the fallback when the header is absent or invalid.
- Implemented via
-
4. Self-heal: on a prompt-overflow 413, write the authoritative ceiling
back into the in-memorylimit.contextfor that model so the session
recovers without a restart, then signal compaction to re-run before the
retry.- Implemented as: mutate
s.providers[providerID].models[modelID].limit.contextdirectly (same live-object mutation pattern as the existingsetModelContextLimit, but touching onlycontext, notcontextMax— the hard n_ctx ceiling hasn't changed, only the safe prompt budget), then returnfalse(no blind inline retry of the same oversized body). "Signal compaction" is the existing reactiveneedsCompaction = truepath insession/processor.tson anyContextOverflowError(already fires for any 413 — error.ts already classifies by status code, confirmed unchanged) — it was already wired, just never got a correct budget to compact against. No compaction.ts signature change, matching the proposal's stated Impact scope.
- Implemented as: mutate
-
5. Give
fetchLocalModelFitits own AbortController instead of sharing
the/v1/models2s budget, so a slow fit probe cannot silently downgrade
limit.contextto the largercontext_length./v1/modelskeeps its 2s budget; fit gets its own 3s budget. Updated the existing "discovery completes when /api/fit hangs forever" test's stale comment (it previously asserted the two should share a budget); its assertions (limit.contextfallback value,elapsed < 5000) needed no change since 3s < 5s.
-
6. When fit is unavailable AND the reported
context_lengthexceeds any
previously-knownmax_safe_ctx, prefer the conservative known value and
log at WARN; never silently adopt the larger number.- File:
discoverOpenAICompatibleModelsinprovider.ts. Side effect: this also fixed a pre-existing, unrelated-looking failing test (discoverModels:true merges discovered models without overriding manual models, expected 4096 got 8192) — same root cause playing out via a manually-configured model's smaller context being overridden by a larger discovered value, not just a prior-discovery's fit value. Fullprovider.test.tssuite: 101 pass, 0 fail (was 1 fail before this task).
- File:
-
7. Contract-drift guard: derive the accepted error
type/codefrom the
llama-skein OpenAPI contract (or a single shared constant) and add a test
that fails if the two repos diverge again. This class of bug — a
hand-copied error string — is the actual root cause and MUST NOT recur.- Partial: added
LLAMA_SKEIN_PROMPT_OVERFLOW_TYPE/_CODE/_MAX_SAFE_CTX_HEADERas single-sourced constants inprovider.tswith comments citing the exact llama-skein source lines, so opencode-skein itself no longer has the string duplicated. Did NOT build the cross-repo CI drift guard (would need llama-skein's OpenAPI spec vendored or fetched into this repo's test/CI — real infra work, out of scope for a bug fix). Follow-up.
- Partial: added
-
8. Verify live against
proxmox/qwopus3.6-27b-v2-mtp-q8-0: a Build
session that crosses 74711 tokens compacts and continues instead of
wedging. Capture before/after in the change notes.- Not done — no access to the
proxmoxhost from this environment. Needs manual verification.
- Not done — no access to the
-
9.
bun run typecheckgreen inopencodeandtui;bun testgreen for
the provider package.bun run typecheck: only the pre-existingsrc/session/prompt.ts:1152error remains, caused by unrelated uncommitted WIP (auto-mode) in the working tree — confirmed via bisecting clean checkouts, not caused by this change.tuipackage not separately checked (no changes there).bun test test/provider/provider.test.ts: 101 pass, 0 fail.bun test test/session/compaction.test.ts: 55 pass, 1 skip, 0 fail.
Unchecked items above: see Disposition in proposal.md (2026-09-18).
Plan changes
7 done
Activity
androidand commentedon Sep 17, 2026
Archived 2026-09-18 — see the change's Disposition section.