Skip to content

fix: re-anchor nudge cadence when count drops below the shown reference - #479

Merged
ranxianglei merged 1 commit into
masterfrom
2026-09-28_nudge-downward-reset-shown-ref
Oct 2, 2026
Merged

ranxianglei merged 1 commit into
masterfrom
2026-09-28_nudge-downward-reset-shown-ref

Conversation

@ranxianglei

@ranxianglei ranxianglei commented Sep 28, 2026 •

Copy link
Copy Markdown
Owner

Root cause

nudgeNode's downward reset compared the incoming tokenCount against the baseline only:

if (baseline > 0 && ctx.tokenCount < baseline - nudgeGrowthTokens) { ... }

but decideNudge keys growth to lastNudgeShownTokens whenever it is non-zero (src/compress.ts:1689). When the two diverge — an estimate-grade reading stamped at inject time that later settles far lower (network-failure local estimate), a model/provider switch, or compaction elsewhere — real counts settling near the low baseline produce permanently negative growthSinceReference. Every growth-gated (non-pressure) tier inject then requires growthSinceReference >= growthFloor forever: the cadence dead-zones in [baseline - interval, shown + floor) until context regrows past the phantom level or a compression success zeroes the refs.

Production incident: billion-context#1595 (0.1.166/0.1.167) — inject stamped shown=500733 (estimate-grade, 184% of a 272k window); real usage settled at 121k–196k vs baseline ~135k; idle turns showed growth=-379112/27000 (ref=500733) indefinitely.

Fix

Extend the reset condition to the reference the growth decision actually uses — keep the existing baseline clause, add the symmetric shown clause:

if (
  (baseline > 0 && ctx.tokenCount < baseline - nudgeGrowthTokens) ||
  (shownAtDecision > 0 &&
    ctx.tokenCount < shownAtDecision - nudgeGrowthTokens)
) {
  stamped.lastPerMessageNudgeTokens = ctx.tokenCount;
  stamped.lastNudgeShownTokens = 0;
  stamped.lastShownByTier = {};
}

A sustained real drop below the stale shown reference now re-anchors the cadence trio at the settled level instead of blocking it. Note the issue's alternative variant (min(baseline, shown)) does not cover the production repro: min(135298, 500733) − 27000 = 108298 < 121621, so that clause would never have fired in the incident.

⚠️ Behavior change (old → new)

  • Old: downward reset fired only when tokenCount < lastPerMessageNudgeTokens − nudgeGrowthTokens. With lastNudgeShownTokens pinned above the baseline, gentle-nudge cadence stayed dead until usage regrew past the phantom shown level or compression succeeded.
  • New: the reset also fires when tokenCount < lastNudgeShownTokens − nudgeGrowthTokens, even if still above baseline − interval. Effect: after an overshoot-grade reading, provider switch, or scale change, once real usage settles below the stale reference minus one growth interval, baseline/shown/per-tier stamps re-anchor immediately and growth-gated nudges can fire again after normal regrowth.
  • Why: the reset must be keyed to the same reference the growth decision uses; otherwise the two mechanisms disagree by construction and the lower one wins forever.
  • Unaffected: pressure/emergency band (bypasses the growth gate), compression paths, state schema shape, wire format, ref ids, prompt rules, node ordering. No new dependencies.

Evidence (rebased head a587c0c on current master 5d7b538, v0.0.98)

  • New regression test tests/nudge.test.ts — "downward reset re-anchors when count drops below the shown reference even if still above baseline (nudge: downward reset compares baseline only, never lastNudgeShownTokens — a high shown-reference is never re-anchored #478)": turn @10000 → inject @55000 (shown=55000) → settle @9000 (above baseline−interval=4000, so old code never resets) → assert reset trio + cadence re-fires at @15000. Fails on pre-fix code (10000 !== 9000), passes with the fix.
  • All four CI gates green locally on the rebased head: npm run format:check clean, npm run typecheck clean, npm test 1203/1203, npm run build clean.
  • Existing scale-drop test (both refs equal, 366k→40k) and baseline-correction test unchanged and green — the added clause only widens the trigger, never narrows it.

Notes

  • First push landed red only because this branch was cut from a stale local master (57 commits behind): current master gained the format:check CI gate (8da8374) plus the prettier config/mass-format (1f6c77a, 965eb0d). Rebased onto current master cleanly (no conflicts), reformatted the new test block to the repo style, force-pushed. The side issue I initially filed about a "missing prettier config" is obsolete and has been closed as such.
  • Interim host-side mitigation already landed: billion-context#1599. This makes all kernel consumers robust.

Fixes #478

@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

📦 Built Package Artifact

Branch: 2026-09-28_nudge-downward-reset-shown-ref (a587c0c)

Option A — Install from npm PR tag (recommended)

In your adapter project:

npm install acp-kernel@pr-479

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

Option B — Download artifact

  1. Download the artifact from the Actions run
  2. Extract the tarball and install:
tar xzf acp-kernel-pr479.tgz
npm install ./package

This comment is automatically updated on each push.

…ce (#478)

nudgeNode's downward reset only compared tokenCount against the baseline,
while decideNudge keys growth to lastNudgeShownTokens whenever it is
non-zero. An estimate-grade overshoot pinned far above the baseline
dead-zoned every growth-gated inject in [baseline - interval, shown + floor)
until context regrew past the phantom level or a compression success zeroed
the refs (production incident: billion-context#1595).

Extend the reset condition to also fire when tokenCount drops below
lastNudgeShownTokens - nudgeGrowthTokens, re-anchoring baseline/shown/
per-tier stamps at the settled level. Pure fix: no state-shape, wire-format,
or node-ordering change; pressure/emergency bands bypass the growth gate
and are untouched.
@ranxianglei
ranxianglei force-pushed the 2026-09-28_nudge-downward-reset-shown-ref branch from b463942 to a587c0c Compare September 28, 2026 14:53
@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

[bot] 🏷 Review 完成(按 PR Review 规范逐关过了一遍)。结论:代码修复正确、测试有牙、diff 干净,CI 已全绿,可合并。合并操作留给 owner。

① 原 issue(#478)方向关

根因成立且已在代码级复核:nudgeNode 的下行重置只比 baseline(src/compress.ts:992),而 decideNudge 的 growth reference 在 lastNudgeShownTokens > 0 时优先取 shown——两机制参照不一致,count ∈ [baseline−interval, shown+floor) 区间内既不重置也不满足 growth,growth-gated 注入永久死锁。生产事故数字代入验证一致(121621 落在 [108298, 527733))。修的是根因(把重置 key 到决策实际使用的参照),不是给 growth 计算打补丁或加超时逃逸。变体选择也正确:对称子句(A)在事故数字下触发(121621 < 500733−27000);issue 里的 min(baseline, shown) 变体(B)阈值 108298 < 121621,事故现场永不触发——PR 弃 B 取 A 是对的。

② 回归审查(既有行为清单)

  • 下行重置触发面:新子句是旧条件的严格超集(union),且只在 shown > baseline(即背离态)时才有额外触发区域;shown ≤ baseline 时新子句阈值不高于旧子句,行为不变。有意扩大,已在 PR 披露 old→new+原因,非回归。
  • 压力/emergency 带:绕过 growth gate,不受影响。
  • compression 成功重置路径(applyCompression):独立通道,未触碰。
  • state schema 形状:同一组字段(lastPerMessageNudgeTokens / lastNudgeShownTokens / lastShownByTier),无新增字段。wire format、ref id、prompt rules、节点顺序均未触碰。
  • §7.3 契约:全部未触碰(nudge 不变量中 growth-gating/tier-trigger gating/baseline-reset-on-compress 语义保持,仅扩展了既有的向下重锚定分支)。

③ 代码审查

shownAtDecision 在 stamped 拷贝前捕获,条件用的是决策时刻值而非被重置后的值 ✓;条件为对称扩展、无短路副作用 ✓;新增注释解释了第二子句为何 load-bearing ✓。单节点改动,+12/−1(src/compress.ts)。

④ 测试有效性

我独立验证了「先红后绿」:把新回归测试单独放到未打补丁的 master(5d7b538)上跑 → 失败,断言 expected: 9000, actual: 10000(baseline 未重锚定),同文件其余 39 项全过;打上修复后通过。测试断言覆盖完整重置三元组 + @15000 cadence 恢复,bug 回归时抓得住,不是恒真测试。

⑤ diff 整洁度 + CI

当前 head a587c0c(已 rebase 到最新 master 5d7b538)对 master 的净 diff = 恰好 2 文件,+76/−1,无无关改动。

关于首轮 CI 失败(test (24) failure)的说明:PR 开出来之后 master 落了全库 prettier 化(.prettierrc width 80 + ~150 文件格式化 + ci.yml 加入 format:check gate,即 #480 的落地),PR 描述里「CI 不跑 format:check、仓库无 prettier 配置」的 Notes 已过时。唯一违例就是本 PR 新增的测试块(旧式行宽),src/compress.ts 功能 hunk 本身是 prettier-clean 的。我在本地完整复现并修复(rebase + 仅对该测试块做行宽格式化,无语义变化),全套门本地验证:format:check 全仓干净、tsc --noEmit 干净、1203/1203 测试通过(master 套件已从 1125 增长)、build 干净。推送前发现远端分支已被另一个会话推入了与我的验证结果**逐字节相同(tree-identical)**的修复提交 a587c0c,我的推送按 force-with-lease 保护自动中止,未重复提交。现 CI 状态(a587c0c):pr-validation / test (22) / test (24) 全部 success(每个 test job 含 format:check+typecheck+test+build),PR mergeable=clean。

自动合并门槛(§7.4)评估

七条全满足:单节点 scope、纯回归测试先红后绿、rebased head 四门全绿、不触碰 §7.3 契约与结构约束、纯 fix:、diff 干净、正文带 Fixes #478。符合 auto-merge 条件;但按仓库规则合并仍由人执行:**https://github.com/ranxianglei/acp-kernel/pull/479**(合并后 #478 自动关闭)。

中文摘要:审核了 nudge 下行重置重锚定修复(#478)——方向正确(修根因而非治标)、既有行为逐项清点无无意回归(新子句严格扩宽触发面且已披露)、回归测试经我独立验证在未打补丁 master 上确实报红(9000≠10000)修复后转绿、CI 首因是 master 后落地的 prettier gate 与 PR 旧式测试块冲突,现已 rebase+格式化并在 a587c0c 上全绿,可以合并。

@ranxianglei
ranxianglei merged commit e020acf into master Oct 2, 2026
4 checks passed
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.

nudge: downward reset compares baseline only, never lastNudgeShownTokens — a high shown-reference is never re-anchored

1 participant