Skip to content

fix(opencode): bound the compaction head and mirror the PR-eval model params - #251

Merged
github-actions[bot] merged 1 commit into
mainfrom
fix/opencode-deepseek-flash-parity
Sep 25, 2026
Merged

github-actions[bot] merged 1 commit into
mainfrom
fix/opencode-deepseek-flash-parity

Conversation

@aitrawog

Copy link
Copy Markdown
Contributor

Summary

The umbrella opencode.json used the models.dev generation default plus the stock compaction tail budget. On a long session that combination wedges: select() budgets only the retained tail, whose default is min(15000, 25% of usable), so once the newest turn exceeds that budget the summarizer head becomes the entire serialized history (all reasoning text retained; tool output capped at 2k). The provider rejects the summary request for input length and the session is left unusable.

This change bounds the summarized head and mirrors the org PR-eval engine's model parameters.

Session: ses_f31022a99ffe1gLJ1RdyDnhlAh (opencode 1.18.32, provider ollama-cloud at https://ollama.com/v1, model deepseek-v4.1-flash, 1,048,576 context / 393,216 max output).

Changes to opencode.json:

Key Before After
compaction.preserve_recent_tokens (unset → ~15,000) 500000
options.max_tokens 384000 262144
options.temperature (unset) 1.0
options.top_p (unset) 0.95
options.frequency_penalty (unset) 0.5
options.presence_penalty (unset) 1.0
  • preserve_recent_tokens keeps a large verbatim tail so the summarized head shrinks.
  • The model options are exact parity with the org PR-eval engine (AI_REVIEW_* defaults / vars) and the vendor-recommended sampling for deepseek-v4.1-flash.

How tested

1. Root cause measured on the real provider. The crash was a summarizer prompt of 1,122,556 input tokens (logged). Reconstructing the head with opencode's exact serialize() rules and measuring it through the live provider gives 1,128,448 (0.5% apart; difference = prompt template + prior summary):

$ curl -sS https://ollama.com/v1/chat/completions -d @headtext.json | jq -r .error.message   # max_tokens=1
The prompt is too long: 1128448, model maximum context length: 1048576

2. Provider admits on input tokens only (so reserved/max_tokens do not size the request):

prompt 800,031 + max_tokens 393,216 -> accepted        (usage.prompt_tokens=800031)
prompt 1,050,041 + max_tokens 1     -> rejected        (The prompt is too long: 1050041)
max_tokens 2,000,000                -> rejected        (exceeds maximum output tokens (393216))

3. The committed config's head fits. With preserve_recent_tokens: 500000, the reconstructed head measured through the live provider drops to 634,064 tokens:

$ curl -sS https://ollama.com/v1/chat/completions -d @head500k.json | jq .usage.prompt_tokens
634064

4. Wire parity with the PR-eval. A local echo endpoint captured the request body the committed config actually emits:

$ opencode run --model ollama-cloud/deepseek-v4.1-flash hi   # baseURL -> local echo
model=deepseek-v4.1-flash max_tokens=262144 reasoning_effort=high
temperature=1 top_p=0.95 frequency_penalty=0.5 presence_penalty=1

matching the org PR-eval engine defaults (config.go) and org vars: AI_REVIEW_MODEL=deepseek-v4.1-flash, AI_REVIEW_TEMPERATURE=1.0, AI_REVIEW_TOP_P=0.95, AI_REVIEW_FREQUENCY_PENALTY=0.5, AI_REVIEW_PRESENCE_PENALTY=1.0, DefaultMaxTokens=262144.

5. Live recovery. The wedged session was resumed under this config and completed the compaction, then continued executing its PR #57 work:

$ OPENCODE_CONFIG_CONTENT='{"compaction":{"auto":true,"prune":true,"reserved":384000,"preserve_recent_tokens":500000}}' \
    opencode run -s ses_f31022a99ffe1gLJ1RdyDnhlAh "continue"

(log continued past compaction into charly box validate / mutation-test steps; no ContextOverflowError)

6. Runnable gates on the committed tree:

$ opencode debug config      # parses; compaction/options as above
$ node scripts/check-opencode-plugin.mjs     # 5 PASS, 1 SKIP, OK
$ bash scripts/sync-pin-evidence.sh --self-test   # PASS
$ bash scripts/sync-pr-body.sh --self-test        # PASS
$ bash scripts/sync-dispatcher.sh --self-test     # PASS

charly task verify could not be run in this environment: no charly binary available here implements the new task surface (/usr/bin/charly is 2026.256). This change touches no pins or scripts, so task verify's assertions (policy B, submodule cleanliness, nested gitlinks) are unaffected.

Upstream tracking

This is a stopgap; the permanent fix belongs in opencode:

I reproduced the failure on opencode 1.18.32 with provider ollama-cloud / model deepseek-v4.1-flash and documented it on both threads. When #51121 (or an equivalent) lands, compaction.preserve_recent_tokens can be dropped; it is a tail cap, so the head still grows as a session does. The model-option parity stands regardless.

Rulebook compliance

  • R0 — loaded root-cause-analyzer, agent, strict-policy, git-workflow.
  • R1 — full RCA before remediation: expected vs actual, mechanism (head = entire history; input-only admission), missed control, blast radius (every long session under this config), root fix.
  • R2 — no deferral: the upstream defect is tracked, the stopgap lands now.
  • R4 — no workarounds: the mechanism is bounded by a documented opencode knob, not a retry/sleep.
  • R4a — the upstream product defect is referenced, not routed around in prose.
  • R8/R9 — the emitted request body was captured and verified; the changed path ran live.
  • R3 — parity is one config expression of the PR-eval's own model parameters.
  • Umbrella rules 1–2: change is at the umbrella root only; no submodule edited.
  • Rule 9: made in an isolated clone (no/opencode), not the session-shared checkout.

Change classification

  • Change class: behavioural configuration (opencode.json) — no pins, no scripts, no submodules.
  • Verification gate: changed path executed live (crash measured, fix measured, wire captured, session recovered); charly task verify unavailable in this environment (no new-task-surface binary).
  • Attribution tier: analysed on a live system.

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

… params

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)
@github-actions

Copy link
Copy Markdown
Contributor

Review — PASS

Head SHA: 93d815d501ed7745efdc49b4a72bb51572795a6a (branch: fix/opencode-deepseek-flash-parity)

Security screen: PASS — diff touches only opencode.json config values; no secrets/egress, no guardrail weakening, no supply-chain/pin/script/submodule change, no self-modifying-security surface.
Checklist: All applicable rules PASS or NA. Change class = umbrella behavioural config (one file), so bed/rebuild requirements are NA. Body accounts for every key in the diff (preserve_recent_tokens, max_tokens, temperature, top_p, frequency_penalty, presence_penalty), ends with a correct AI-form Assisted-by: line, sets a conservative non-inflated tier, and routes the un-fixable-in-tree part to named upstream issues (not a vague deferral). Tool-limited disposition: I cannot re-run the live provider probes or charly task verify; the pasted numbers are internally consistent (1,122,556≈1,128,448; head 634,064 with 500k preserve; input-only admission), the wire capture matches the diff, and the diff itself confirms no pins/scripts/submodules were touched, so task verify's assertions are genuinely unaffected. No comments to reconcile.

Verdict: PASS


ollama-cloud/deepseek-v4.1-flash — action-review.

View action run

@github-actions
github-actions Bot enabled auto-merge (squash) September 25, 2026 11:59
@github-actions
github-actions Bot merged commit 2689383 into main Sep 25, 2026
1 check passed
charly-auto-merge Bot added a commit that referenced this pull request Sep 25, 2026
@aitrawog
aitrawog deleted the fix/opencode-deepseek-flash-parity branch September 25, 2026 18:28
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.

1 participant