Skip to content

fix: size preflight summary output to window headroom + name empty JSON completions (#987) - #988

Merged
ranxianglei merged 1 commit into
masterfrom
2026-09-19_preflight-window-clamp
Sep 20, 2026
Merged

ranxianglei merged 1 commit into
masterfrom
2026-09-19_preflight-window-clamp

Conversation

@ranxianglei

@ranxianglei ranxianglei commented Sep 19, 2026 •

Copy link
Copy Markdown
Owner

What

Two preflight fixes for #987 ("context window problem continues", 9router + opencode v2):

  1. Summary calls now fit inside the model's window. Preflight always requested max_tokens=32768 regardless of the model's window. Upstreams that enforce input+output ≤ window (muse-spark behind 9router) answer such calls with an empty completion (content:"") instead of an error, so every summary attempt is unusable → "no range could be compressed" → client retry loop. Now clamped to window − input estimate (floor 64, 256-token scaffolding margin); unchanged when headroom is ample or unknown.
  2. Precise diagnostics for that failure mode. diagnoseEmptySummary no longer reports "non-SSE body" for plain-JSON empty completions; it names the shape with finish_reason/stop_reason/status and the model id the upstream answered as — which exposes alias mismatches (request said subagent, upstream answered muse-spark-1.3-contributor).

Relation to PR #992 (owner's removal work)

#992 removes the runtime window-learning mechanism (the "window keeps changing" symptom) — that half is superseded by it and no longer in this PR. This fix is orthogonal: zero file overlap with #992, independently mergeable in either order, and still needed after it — once the window is purely declarative, a correctly-declared small window (e.g. models.subagent.context) would otherwise still dead-end preflight via the 32k max_tokens request above. The clamp reads the effective declared window (deps.config.modelContextLimit).

Tests

Pre-flight (head df840a6 on master ea2b33f)

typecheck ✅ · full suite 2000 pass / 0 fail / 2 skip (e2e gate) ✅ · build ✅

Addresses #987.

@github-actions

github-actions Bot commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

📦 Built Package Artifact

Branch: 2026-09-19_preflight-window-clamp (064b3ad)

Option A — Install from npm PR tag (recommended)

npm install -g billion-context@pr-988

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

Option B — Download artifact

  1. Download the artifact from the Actions run
  2. Extract the tarball and install:
tar xzf billion-context-pr988.tgz
npm install -g package

Install instructions are refreshed on each push.

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

[bot] 🏷 Received. This is a [PR] wake-up — I will NOT redo the work. Plan: locate the PR, pull its branch, review the diff against current master, re-run typecheck / full test suite / build on the rebased head, check CI status, then report conclusions here.

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

Reviewed PR #988 (2026-09-19_preflight-window-clamp, head 0e3ff5a) — verified against current master 519634b: single commit, 0 behind / 1 ahead (fully rebased), mergeable. I did not modify anything; this is a review + re-verification report.

Code review — both changes hold up:

  1. Window clamp (src/preflight.ts:287-293): the window source is correct — deps.config.modelContextLimit is the per-request reqConfig (server.ts:1689), i.e. after learned-limit application (model window problem(I'm using opencode2 and 9router) #969/fix: only an upstream-stated window is ever learned (#969) #976), codex alignment, and output-headroom reservation, so the clamp sees the same effective window the rest of the pipeline uses. Behavior boundaries verified: unknown/≤0 window → unchanged; ample headroom → byte-identical 32768; headroom ≤ 0 → unchanged (clamping can't save a call whose input alone doesn't fit) and the failure now gets the precise diagnosis from change 2 instead of looping on "non-SSE body". Single call site (preflight.ts:694) — absorb/acp-status/side-request paths issue no summary LLM calls of their own, so nothing else needs clamping. Mode-neutral: only the proxy's internal summarization payload (max_tokens) changes; client-facing wire shape is untouched in both plugin and proxy modes (§6).
  2. Diagnostics (src/preflight.ts:563-591): branch ordering inside diagnoseEmptySummary is safe — error-signal extraction and the SSE scan run first, so no existing diagnosis is shadowed; the shape guard returns null for unrecognized JSON and keeps the old message exactly. The answered as model= field is the right clue for alias mismatches (subagent → muse-spark-1.3-contributor).

Tests: new tests/preflight-window-clamp.test.ts asserts the invariant per-call (max_tokens >= 64 AND input + max_tokens <= window) plus proves the clamp engages at 8k, with a 100k control asserting 32768 retention — deterministic (ephemeral ports, estimator-consistent bounds). Diagnostics tests cover all three wire shapes + the shape guard. The fixture updates (#568 hold, wire-exit-gap cells, #781, #663, #853) swap brittle literal max_tokens === 32768 detection for shape-based detection / relaxed bounds — justified, since legitimate clamping breaks that literal; #853 opts out via a large window so it isolates the registry-ceiling clamp.

Verification (all on rebased head 0e3ff5a):

  • Local: typecheck ✅ · full suite 1997 pass / 0 fail / 2 skip (e2e gate) ✅ · build ✅
  • GitHub CI on 0e3ff5a: all green — CI matrix (ubuntu/windows × node 22/24), build-artifact, version-guard, e2e-fake-codex (zero tokens), codex overlay.
  • Real-upstream codex e2e is manual-dispatch-only (needs a live upstream); the zero-token fake-codex e2e that passed covers the pipeline mechanics. Not blocking.

Non-blocking observations:

  • When 0 < headroom < 64 the floor still requests 64 tokens, which can exceed the residual headroom — deliberate (a degenerate 1–60-token summary is useless), strictly better than before, and such a call now fails with the precise empty-completion diagnosis. Fine as-is.
  • The input estimate uses heuristic defaultCountTokens (chars/4, CJK per-char); dense code/JSON could in theory undercount by enough that input+output still slightly exceeds the window after clamping. Best-effort by nature, mitigated by the 256-token margin, and covered by the new diagnosis if it happens. No change needed.

Conclusion: mergeable — clean single-purpose diff (8 files, +244/-25), regression tests for the exact reported failure mode, green everywhere. Per repo rules the merge itself stays with you: #988

中文摘要:核查了预检压缩在小型窗口下请求 32k 输出导致上游返回空补全、压缩死锁的修复(按窗口余量收紧 max_tokens + 精确的空补全诊断,含 finish_reason 与上游实际应答模型名)——代码审查、本地三件套与 GitHub CI 全绿,测试覆盖了报告的失败模式,可以合并。

@ranxianglei ranxianglei changed the title fix: size preflight summary output to window headroom + name empty JSON completions (#987) fix: declarative context window — remove runtime learning/self-heal; preflight summary output sized to window headroom (#987) Sep 19, 2026
@ranxianglei
ranxianglei force-pushed the 2026-09-19_preflight-window-clamp branch from 932ffc6 to df840a6 Compare September 19, 2026 11:01
@ranxianglei ranxianglei changed the title fix: declarative context window — remove runtime learning/self-heal; preflight summary output sized to window headroom (#987) fix: size preflight summary output to window headroom + name empty JSON completions (#987) Sep 19, 2026
@ranxianglei
ranxianglei force-pushed the 2026-09-19_preflight-window-clamp branch from df840a6 to 3b75695 Compare September 19, 2026 16:18
…N completions (#987)

Preflight summarization always asked for max_tokens=32768 regardless of the
model window. Upstreams that enforce input+output <= window answer such calls
with an EMPTY completion instead of an error, so on small (often overflow-
learned) windows every summary attempt was unusable and preflight dead-ended
with 'no range could be compressed' — exactly the loop reported in #987
(model=subagent, learned window 16898, muse-spark answering content:'').

- clamp the summary call's max_tokens to window - input estimate (floor 64,
  256-token scaffolding margin); untouched when headroom is ample or unknown
- diagnoseEmptySummary: a plain-JSON completion with empty content is now
  named as such, with finish_reason/stop_reason/status and the model id the
  upstream answered as (the alias-vs-real-model mismatch is the clue),
  instead of the misleading 'non-SSE body' message
- tests: new preflight-window-clamp regression (clamp engages + invariant
  input+output <= window on every call; ample-headroom control); existing
  fixtures that identified summary calls by the literal 32768 cap value are
  switched to shape detection / relaxed bounds
@ranxianglei
ranxianglei force-pushed the 2026-09-19_preflight-window-clamp branch 2 times, most recently from 60a396b to 064b3ad Compare September 19, 2026 16:30
@ranxianglei
ranxianglei merged commit 92285cb into master Sep 20, 2026
8 checks passed
@ranxianglei
ranxianglei deleted the 2026-09-19_preflight-window-clamp branch October 15, 2026 05:32
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