Repository navigation
fix: gate stale estimate baselines out of nudge/preflight sizing (#1492) - #1503
Conversation
After an upstream failure without a usage report, armFailureShrink raises stats.lastInputTokens (source=estimate) to chars/4 of the body just sent. On kernel-transform-failure fallbacks that body is the UNFOLDED raw client history, so the poisoned baseline survives across turns while upstream keeps failing and three source-blind consumers apply it cross-turn: - effectiveTokenCount fed it to decideNudge -> false emergency bands - preflight trigger max()'d it against the post-fold payload estimate - the preflight loop floor pinned currentTokens at the stale value, burning summarization rounds over a payload whose own estimate fits the window Rule now: an estimate-sourced baseline may floor sizing decisions only while the current payload is unmeasurable (processedMessages empty). Usage-sourced baselines keep flooring everywhere. armFailureShrink also measures with defaultCountTokens (CJK-aware lower bound) instead of estimateTokensFast (chars/4); ASCII output is byte-identical.
📦 Built Package ArtifactBranch: Option A — Install this exact build (recommended)npm install -g billion-context@0.0.1440-pr.1503.c2e2d44This exact version is immutable and reproducible. The rolling Option B — Download artifact
tar xzf billion-context-pr1503.tgz
npm install -g packageInstall instructions are refreshed on each push. |
[bot] 🏷 Reviewing PR #1503 (Closes #1492). Pulled the branch: single commit One thing to verify: master has since moved 26 commits past the PR base (web UI rewrite #1426, chain-checkpoint enforcement #1421, etc.), including hunks inside |
[bot] 🏷 Review complete — PR #1503 (fixes #1492): 可以合并 (recommend one rebase onto current master first) ① 原 issue 方向 — 打到了根因。 #1492: upstream failure with no usage report → ② 回归审查 — touched-path 既有行为逐项:
③ 代码审查 — one rule, identically shaped at all three consumers; thorough issue-cited comments; ④ 测试有效性 — 真测试,断言有牙。 Real end-to-end proxy test (actual HTTP servers,
⑤ diff 整洁度 & CI — single commit, 4 files, every line in scope, no whitespace/lockfile churn. Stale base only: head's base Verdict: mergeable. Suggest one rebase onto 中文摘要:修复了 #1492——上游失败且无 usage 报告时,未折叠原始历史的估算基线(~1.27M)跨回合污染 nudge/preflight 定容、对已折叠的 ~16 万 token 负载空烧数十秒压缩的问题;方案是在三个消费点按 #857 的来源标记门控估算基线,并换用 CJK 感知的默认估算器。已独立复现红/绿证明、本地模拟合并到当前 master 后全量测试通过(2963 项 0 失败)、CI 13 项全绿——可以合并(建议先 rebase 到当前 master)。 |
backstop Follow-ups from review of #1503: - F1/HIGH: estimateInputTokens floors on lastInputTokens only when lastInputTokensSource === "usage". An estimate-grade baseline (raw-view poison from a transform-failure fallback arm, #1492/#1493) would otherwise mask the payload's own estimate through the max() and let the #453 clamp fail open while the poison persists. clampOutgoingOutput now feeds the source through (#1498 F5's clamp hole, closed here instead). - F3: the loop-floor and emergency-band gates share this exact clamping mechanism, so they inherit the fix; pinned via the updated backstop test. - tests/issue453-backstop.test.ts: the real-count test now also pins the gate (estimate-grade and unknown-provenance baselines never floor).
|
Second-round review landed on top (head Core rule verified sound — usage-grade floor, unmeasurable-gated entry, #604 relay rescue semantics intact, CJK/ASCII estimates byte-equivalent under the F8 simplification. Kept as-is. Follow-up fixes on the branch (from review):
Gates: typecheck ✅ · targeted 13/13 ✅ · full suite 2923 pass / 8 fail, all 8 = the known main-repo env drift ( Merge order with #1498: this PR first — #1498 needs a rework on top (its F1 breaks the #604 relay rescue deterministically, its F2 leaves the transform-failure fallback unarmed; see its review comment). |
…arm on the text char bound Second-round rework after review (on top of #1503): - F2 (HIGH): outboundPayloadBreakdown now measures originalMessages when processedMessages is empty — every protocol's kernel-transform-failure catch leaves it empty while the outbound IS the raw client body, so the estimator previously returned wire overhead only and the rescue never armed in the very scene #1493 names. Regression test drives the real fallback (broken state seam) and pins the arm at >= the raw char bound. - F1 (HIGH): the arm is not only a preflight floor — the kernel's truncation loop consumes it as its remaining-depth input, and the optimistic chars/4 text estimate undercounts dense payloads by up to ~4x (#553), stopping the loop one candidate early (pre-rebase this deterministically re-broke the #604 relay rescue: armed 31101 vs 31691, retry 116.5KiB vs 108.7KiB, 500 vs 200). armEstimate floors the arming quantity on the text char bound (never undershoots, deliberately skips image base64 so #1493's pixel-billed fix stands); preflight keeps the billing-accurate payloadEstimate so a payload that fits does not falsely cross the trigger. - Rebased onto #1503 (ee12524): its consumer-side provenance gates are the root-cause half; this PR now supplies the producer-side fallback and depth insurance. All shared suites green together: issue1493 3/3, issue1492 2/2, relay-5xx selfheal 3/3, overflow selfheal, #453 backstop.
What
After an upstream failure with NO usage report,
armFailureShrinkraisessession.stats.lastInputTokens(source=estimate) to ~chars/4 of the body just sent. On the kernel-transform-failure fallback paths that body is the unfolded raw client history — so on a long session the poisoned baseline (1.27M in the issue) survives across turns, because while upstream keeps failing no real usage report ever overwrites it. Three source-blind consumers then applied it cross-turn:effectiveTokenCount(src/server.ts) fed it straight intodecideNudge→ false emergency bands every turn (nudge injection + tool-result truncation churn).max()'d it against the post-fold payload estimate → preflight fired every turn despite the folded payload fitting the window.src/preflight.ts) pinnedcurrentTokensat the stale value → summarization rounds burned over a payload whose own estimate fits (the 42–203 s waste), ending in the "payload fits with no compressible ranges" log.The #857 provenance field (
lastInputTokensSource: "usage"|"estimate") already existed; consumers at server.ts:4288 (#496) and :2769 already checked it — these three missed it.Fix
One rule: an estimate-sourced baseline may floor a sizing decision only while the current payload is unmeasurable (
processedMessages.length === 0, i.e. kernel transform failed and the outbound IS the raw body). Usage-sourced baselines keep flooring everywhere — real overflow evidence is untouched.effectiveTokenCount: returnslastInputTokensonly when source is"usage"; otherwise falls through to the existinglocalInputEstimate/ raw-upper chain.baselineFloor = measured ? (usage ? lastInputTokens : 0) : lastInputTokens.armFailureShrink(+ the overflow-arm twin): measure withdefaultCountTokens(CJK-aware lower bound) instead ofestimateTokensFast(chars/4). ASCII output is byte-identical; CJK bodies now get the lower bound the doc comment always claimed.Behavior changes (old → new)
localInputEstimate(post-fold view) decidesmax(staleBaseline, payloadEstimate)fires preflightpayloadEstimatealone decidescurrentTokensTest update (disclosed)
tests/issue847-compress-failure-guards.test.ts: the "honest exhaustion" case setlastInputTokens = 6000with no provenance (legacy shape). Under the new contract that baseline no longer floors the walk against a measured 1.2K-token payload, so the loop correctly exits without running. AddedlastInputTokensSource = "usage"so the case exercises the SAME exhaustion-report path (sub-gate ranges → honestexhausted, zero upstream calls) with a legitimate baseline. All original assertions unchanged.Regression test
New
tests/issue1492-stale-estimate-view.test.ts(real proxy + mock relay, Anthropic wire):lastInputTokens=1.2Mwith source=estimateon a folded 37%-window session → asserts NO preflight call, NO nudge, history forwarded unfolded, self-heal to real usage on the recovered turn. Pre-fix this FAILS (preflight fires + emergency nudge injected); post-fix passes.usage→ asserts preflight STILL fires (asymmetry guard against over-correction).Red proof:
git stash push -- src/→ A fails, B passes; pop → both pass.Pre-flight (rebased head c741120 on master 0f1a0df)
npm run typecheckcleannpm test: 2942 tests, 0 fail, 12 skipped (e2e-gated)npm run buildsuccessCloses #1492