Mechanism (proven from plugin daily logs)
Two production sessions (headless opencode, resumed per-message by an orchestrator) grew until every request failed. The plugin's own log shows the root cause:
2026-08-27T15:59:26Z Chat transform complete | session=ses_7fb333d7… messages=145 prePruneTokens=229479 postTokens=229479 nudged=false
2026-08-28T14:28:31Z Chat transform complete | session=ses_7fb2e8b9… messages=99 prePruneTokens=229535 postTokens=229535 nudged=false
postTokens == prePruneTokens on every transform — even at 229k tokens. The in-flight prune never removes anything, nudged=false throughout, and the persisted state shows lastCompaction: 0 (proactive compaction also never fired; stats.totalPruneTokens 37k/230k respectively, all content parked in prune.messages).
Failure chain
- Conversation reaches ~229k tokens (plus ~15-20k system+tool schema +
max_tokens: 16384)
- Total exceeds the serving backend's context window (sglang qwen 27B,
max_model_len: 262144) → immediate request rejection
- opencode run ends with exit 0 and zero output (no error surfaced)
- Orchestrator retries → identical ~4.6s failure, forever (3× consecutive observed per session)
Both sessions froze at almost exactly the same size (229,479 / 229,535) — the length-rejection wall.
Environment
- opencode 1.14.46 +
opencode-acp@latest (plugin log says v=1.14.25), direct-to-sglang (no bili in this path)
- Headless per-message spawn+resume (ACP path), daemon-driven
acp.jsonc contains only the $schema ref (all defaults)
Expected
The plugin's core contract is bounding the conversation. At minimum:
- In-flight prune must actually reduce tokens before the request when near the model context limit (or prune thresholds are misconfigured/never fire in this mode)
- Proactive compaction (
lastCompaction) should trigger long before 229k
- On unrecoverable context overflow, surface an error instead of a silent empty run with exit 0 (the orchestrator can't distinguish this from success)
Workaround in use
Orchestrator detects the empty-response loop and starts a fresh session (loses in-session continuity; conversation history only survives via the issue thread).
Cross-ref: initially misfiled as ranxianglei/billion-context#318 (environment there corrected — no bili proxy in this path; the state model investigation may still be relevant to acp-kernel).
State files and full logs available on request.
Mechanism (proven from plugin daily logs)
Two production sessions (headless opencode, resumed per-message by an orchestrator) grew until every request failed. The plugin's own log shows the root cause:
postTokens == prePruneTokenson every transform — even at 229k tokens. The in-flight prune never removes anything,nudged=falsethroughout, and the persisted state showslastCompaction: 0(proactive compaction also never fired;stats.totalPruneTokens37k/230k respectively, all content parked inprune.messages).Failure chain
max_tokens: 16384)max_model_len: 262144) → immediate request rejectionBoth sessions froze at almost exactly the same size (229,479 / 229,535) — the length-rejection wall.
Environment
opencode-acp@latest(plugin log says v=1.14.25), direct-to-sglang (no bili in this path)acp.jsonccontains only the$schemaref (all defaults)Expected
The plugin's core contract is bounding the conversation. At minimum:
lastCompaction) should trigger long before 229kWorkaround in use
Orchestrator detects the empty-response loop and starts a fresh session (loses in-session continuity; conversation history only survives via the issue thread).
Cross-ref: initially misfiled as ranxianglei/billion-context#318 (environment there corrected — no bili proxy in this path; the state model investigation may still be relevant to acp-kernel).
State files and full logs available on request.