Skip to content

fix: context budget guard + no-window warning (#347) - #350

Open
ranxianglei wants to merge 3 commits into
masterfrom
2026-08-28_context-budget-guard
Open

fix: context budget guard + no-window warning (#347)#350
ranxianglei wants to merge 3 commits into
masterfrom
2026-08-28_context-budget-guard

Conversation

@ranxianglei

Copy link
Copy Markdown
Owner

What

Fixes #347 (billion-context#317): sessions whose input grew past the backend's real context window were rejected with HTTP 400, which opencode swallows as an empty exit-0 response — permanently stuck session, no error surface.

Root cause chain (full analysis in #317): a custom model with no limit entry → modelContextLimit stays undefined → every percentage threshold (min/max/emergency, GC) is disabled → only advisory nudges remain → input grows unbounded until the backend 400s (production case: 230,527 input + 32,000 completion > 262,144 window).

Changes

  1. Deterministic prune-to-fit guard (lib/messages/enforce-budget.ts, new): in messages.transform, after the existing GC truncation, if the estimated request exceeds modelContextLimit - completionReserveTokens, old compressible tool outputs are truncated (prefix+suffix, same marker as truncateLargeToolOutputs for idempotency) and, if still over, cleared to the standard placeholder. Protections: first user message, last 3 messages, protectedTools, compress-tool outputs (summaries).

    The guard enforces ONLY the model-reported window. An absolute compress.maxContextLimit is deliberately not a fallback — it is a soft nudge threshold, not the backend's real limit; pruning to it would destroy context the backend would accept (this regressed e2e-blocks-nudges during development: the guard pruned the one large tool output below the recommendation floor → nothingToCompress → no nudge suffix).

  2. Loud one-time WARN (lib/hooks.ts): when the model reports no context window and the catalog has no entry, log actionable guidance once per session (set the model limit in opencode.json — this both enables the guard and fixes the 32,000 max_tokens fallback — or set an absolute compress.maxContextLimit for proactive nudges).

  3. New config compress.completionReserveTokens (default 32768, schema + EN/CN docs): reserved for the completion; covers opencode's 32,000 max_tokens fallback when limit.output is 0/unknown.

  4. Transient state noContextLimitWarned (not persisted; default/reset in createSessionState/resetSessionState).

Pre-flight

  • npm run typecheck — clean
  • npm test — 1043/1043 pass (14 new tests in tests/enforce-budget.test.ts)
  • npm run build — success
  • Regression check: tests/e2e-blocks-nudges.test.ts 10/10

Notes

A model with no declared context window (limit.context=0) disabled every
percentage threshold in the plugin, so requests could grow past the
backend's real window and be rejected with HTTP 400 - which opencode
swallows as an empty exit-0 response (billion-context#317).

- enforceContextBudget: deterministic prune-to-fit guard in
  messages.transform (truncate largest old tool outputs, then clear
  oldest) using the model window or an absolute maxContextLimit,
  reserving completionReserveTokens (default 32768) for completion
- one-time WARN when the model reports no context window and the
  catalog has no entry, with actionable config guidance
- new config: compress.completionReserveTokens (schema + docs)
- 14 new unit tests
@github-actions

github-actions Bot commented Aug 28, 2026

Copy link
Copy Markdown

📦 Built Plugin Artifact

Branch: 2026-08-28_context-budget-guard (18debdb)

Option A — Install from npm PR tag (recommended)

opencode plugin opencode-acp@pr-350 --global

Each push to this PR publishes a new version under the pr-350 npm tag.

Option B — Install from GitHub

opencode plugin "github:ranxianglei/opencode-acp#2026-08-28_context-budget-guard" --global

Option C — Download artifact

  1. Download the artifact from the Actions run
  2. Extract the tarball and install:
tar xzf opencode-acp-pr350.tgz
cp -r package/dist ~/.cache/opencode/packages/opencode-acp@latest/node_modules/opencode-acp/dist
  1. Restart opencode to pick up changes.

This comment is automatically updated on each push.

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 ework agent · vllm-qwen/qwen3.8-27b

[bot] Picked up PR #347 (2026-08-28_context-budget-guard, tip 0fa4e24) for review. Initial local verification so far:

  • npm run typecheck — clean
  • npm test1043/1043 pass (incl. the 14 new tests in tests/enforce-budget.test.ts)
  • npm run build — success (dist/index.js 419.61 KB)
  • tests/e2e-blocks-nudges.test.ts — 10/10 (the regression the PR calls out)
  • ./scripts/ci/check-pr.sh 2026-08-28_context-budget-guard origin/master — all checks pass (branch name, devlog REQ+WORKLOG, no version bump)

Now running the mandatory dual-agent review (source per AGENTS.md §5.3, tests per §5.6) plus a few consistency checks (devlog commit hash vs branch tip, PR/CI state on GitHub). Full review verdict will follow as a second comment.

ework-agent added 2 commits August 29, 2026 03:09
…ard, docs

Dual-agent review of the context budget guard found:
- BLOCKER: compress.completionReserveTokens missing from VALID_CONFIG_KEYS
  → spurious "Unknown keys" toast for users setting the documented key
- NaN reserve hazard: non-numeric config value made budget NaN, disabling
  every early-exit (guard would truncate all candidates)
- estimateWireTokens undercount: anchored on last assistant by role while
  getCurrentTokenUsage anchors on last assistant WITH token data; also
  dropped the system prompt estimate when getCurrentTokenUsage fell back
  to content estimation
- truncation could GROW content just over the 4000-char threshold
  (prefix/suffix overlap + marker line)
- docs (schema, EN/CN, warning text) claimed an absolute
  compress.maxContextLimit enables the guard; it deliberately does not

Tests: +9 (23 in enforce-budget.test.ts, 9 new in config-validation.test.ts);
full suite 1052/1052.
@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 ework agent · vllm-qwen/qwen3.8-27b

[bot] Review verdict: REQUEST-CHANGES → fixed & pushed. Two independent agent reviews (source + tests) of the PR branch found 1 blocker and several real bugs. All fixed, pushed as two commits on 2026-08-28_context-budget-guard (318c94d, 18debdb).

BLOCKER (fixed): compress.completionReserveTokens was documented in the schema/EN/CN docs but missing from VALID_CONFIG_KEYS (lib/config-validation.ts) — anyone who set the new key would get a spurious "Unknown keys" toast. Added to the whitelist + validateConfigTypes (number, >= 0).

Other bugs found & fixed:

  1. NaN reserve hazard — config validation is advisory-only (warns, doesn't block load), so a non-numeric completionReserveTokens made budget NaN, which made every <= budget early-exit false → the guard would truncate all candidates with no stop. Now falls back to DEFAULT_COMPLETION_RESERVE_TOKENS when the value isn't a finite number >= 0.
  2. estimateWireTokens undercount (two parts) — it anchored additions on the last assistant by role, while getCurrentTokenUsage anchors on the last assistant with token data (it skips tokenless aborted requests); the gap between the two was undercounted. Additionally, when getCurrentTokenUsage itself fell back to content estimation, the system-prompt estimate was silently dropped. Both fixed.
  3. Truncation could GROW content — for output just over the 4000-char threshold, prefix + suffix overlap plus the marker line made the "truncated" form longer than the original. Now skipped (phase 2 may still clear it).
  4. Docs contradicted codedcp.schema.json, CONFIGURATION.md, CONFIGURATION.zh-CN.md, and the warn-once text all claimed an absolute compress.maxContextLimit enables the guard. It deliberately does not (soft nudge threshold, not the backend's real limit — the e2e regression you noted). All four corrected.

Left as documented known limitations (low impact, noted in WORKLOG): the one-time no-window warning can fire one turn early for a catalog-unknown model that declares its limit (transform ordering); first-user-message protection is index-0-only (matches truncateLargeToolOutputs); the "still over budget" WARN repeats while the condition persists (deliberate — it's a genuinely alarming state).

Tests: +9 new (23 total in tests/enforce-budget.test.ts; 4 in tests/config-validation.test.ts) covering budget<=0 no-op, <3 messages, non-numeric reserve fallback, the tokenless-aborted-request estimate gap, and the non-shrinking truncation skip. The fragile clear-phase test premise (a ~10% BPE margin) was rebuilt so phase 1 provably saves 0 by char-count fact; the weak fallback-estimate assertion was tightened to near-exact equality.

Verification (all on the pushed branch):

  • npm run typecheck — clean
  • npm test1052/1052 pass
  • npm run build — success (dist/index.js 420.69 KB)
  • tests/e2e-blocks-nudges.test.ts — 10/10
  • ./scripts/ci/check-pr.sh 2026-08-28_context-budget-guard origin/master — all checks pass

No version bump (content branch). PR is ready — merge is a human-only operation per project rules, so I'll leave that to you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant