Skip to content

fix: rotate count-triggered T2/T3 nudges with ready T1 on the growth path (#509) - #510

Open
ranxianglei wants to merge 3 commits into
masterfrom
2026-10-07_nudge-tier-rotation
Open

ranxianglei wants to merge 3 commits into
masterfrom
2026-10-07_nudge-tier-rotation

Conversation

@ranxianglei

Copy link
Copy Markdown
Owner

Closes #509

Root cause

decideNudge()'s growth path checked t1Eff >= nudgeGrowthTokens FIRST and unconditionally injected T1. In any session whose raw compressible mass stayed above the threshold, the count-triggered branches (t2CountReady / t3CountReady) were therefore structurally unreachable — even when tier2Trigger/tier3Trigger were explicitly lowered and satisfied. Production instance: billion-context-pi#628 (256K window, 22 active T1 blocks, t1Eff 100K-400K) never received a single T2 nudge across the whole session.

Fix (issue option A: decouple distillation readiness from t1Eff < threshold)

  • Count-ready tiers now join a SLOT ROTATION on the growth path: T1 keeps first pick until it has gone (fresh state, or the previous slot was a distill); then the slot hands to the count-ready tier with the oldest cadence stamp (never-shown = oldest; tie -> lower tier), and back to T1 the next cycle. Strict alternation — neither side starves, even if the model keeps ignoring one side.
  • Rotation memory lives in nudge.lastInjectedTier (new OPTIONAL state field, defaults null), set by the nudge node on every injection. Deliberately NOT cleared by the compression-success reset: without that, a compliant model draining raw ranges would restart every tie at T1 after each compression and the explicit trigger would stay dead in exactly the #628 shape. (First-pass design keyed rotation on lastShownByTier alone; the compress->re-nudge loop test exposed that its memory is wiped on success.)
  • With the default triggers (1000/2000 since Count-triggered tier distillation distills without need (cache-loss root cause); #238 usage gate is itself a bug — default-off the count path, delete the gate #379) the candidate pool stays empty -> BYTE-IDENTICAL default behavior. Token-mass paths and the pressure path are untouched.

Behavior change disclosure (old -> new)

Path Old New
growth path, t1Eff >= threshold, explicit low trigger met T1 every cycle; T2/T3 unreachable (explicit trigger = dead config) strict T1 <-> distill alternation; explicit trigger works
growth path, fresh state, both ready T1 T1 (first pick preserved)
default triggers (1000/2000) T1-first cascade identical (pool empty)
pressure path / token-mass paths unchanged unchanged
state schema - additive optional nudge.lastInjectedTier; absent = null = legacy behavior, no migration

Issue face 2 (mass condition t2Pen > t1Eff) deliberately left as-is: it is the DEFAULT-ON mass-dominance signal designed by #379; loosening it changes default behavior for everyone (product decision) and would not fix face 1 anyway. Happy to follow up separately if the owner wants it relaxed.

Tests

  • New regression tests (tests/nudge.test.ts): repro under sustained high t1Eff (T2 heard on the second ready cycle), strict [1,2,1,2] alternation, T3 rotation, default-trigger non-drift, and rotation memory surviving a successful T1 compression (the #628 production shape).
  • Updated the pre-existing 'count-triggered T2 does not preempt ready T1' test, which encoded the old invariant, to pin first-pick + rotation across three turns.
  • Local targeted run (nudge/compress/pipeline/pressure-benefit/persist/packs): 158/158 green; typecheck clean. Full suite left to CI.

Second commit (docs, labeled): fixes the doc drift flagged in the issue — TierConfig JSDoc said 'Default 5'/'Default 10', actual defaults have been 1000/2000 since #379; plus README note on the rotation semantics.

Touches §7.3 contract 8 (nudge invariants) + additive state field -> human-gated merge.

ework-agent added 3 commits October 7, 2026 19:43
…path (#509)

The growth path short-circuited to T1 whenever t1Eff >= nudgeGrowthTokens, so an explicitly lowered tier2Trigger/tier3Trigger could never fire in a session whose raw compressible mass stayed high (billion-context-pi#628: 22 T1 blocks, t1Eff 100K-400K, zero T2 nudges).

Count-ready tiers now join the slot rotation: T1 keeps first pick until it has gone (nudge.lastInjectedTier), then the count-ready tier with the oldest cadence stamp wins, and back again next cycle. lastInjectedTier is deliberately not cleared by the compression-success reset, so a compliant model draining raw ranges cannot restart every tie at T1. Default triggers (1000/2000) leave the pool empty -> byte-identical default behavior; token-mass paths and the pressure path are unchanged.
…000 since #379)

The TierConfig JSDoc still said 'Default 5' / 'Default 10'; actual defaults have been 1000/2000 since #379 (count is not a need signal, path defaults OFF). Doc-only, flagged in issue #509's附带发现. Also documents the #509 slot-rotation semantics in the JSDoc and README nudge section.
@ranxianglei
ranxianglei force-pushed the 2026-10-07_nudge-tier-rotation branch from 74e31ac to bafbdfc Compare October 7, 2026 11:46
@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

📦 Built Package Artifact

Branch: 2026-10-07_nudge-tier-rotation (bafbdfc)

Option A — Install from npm PR tag (recommended)

In your adapter project:

npm install acp-kernel@pr-510

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

Option B — Download artifact

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

This comment is automatically updated on each push.

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

冲突已解决,分支已 rebase 到当前 master(v0.0.100,633b8c8)并推送:ranxianglei:2026-10-07_nudge-tier-rotation → bafbdfc(force-with-lease,钉在旧头 74e31ac)。PR merge state 已从 dirty 转回可合并。

冲突成因:PR 建在旧基线(b3f7246,v0.0.94 时代)上,期间 master 落了 ~30 个 PR,其中两处与本 PR 交叠——#478(a587c0c,nudge cadence 下移重锚,动了 src/compress.ts 的 nudgeNode + tests/nudge.test.ts)和 #438 的 prettier 全树格式化基线。唯一内容冲突在 tests/nudge.test.ts:正是本 PR 改写的那个测试(旧版单轮断言编码了 pre-#509 的「T1 永远优先」不变量,master 侧还保留旧形)——按本 PR 的意图取了新版三轮轮换断言(首轮 T1 → 轮换 T2 → 还回 T1),旧形必须被替换而非并存。src/compress.ts / types.ts / state.ts 自动合并,已逐行核对:#478 的下移重置在 nudgeNode、本 PR 的轮换在 decideNudge,互不重叠,两侧语义完整保留;README/types JSDoc(docs commit)干净落上。另补一个独立标注 style commit:PR 代码写于 prettier 基线之前,rebase 后需按 CI format gate 重排(纯换行,无语义改动)。

结构化七问(本次 rebase):

  1. 是什么问题:PR 分支落后 master 产生内容冲突,merge state dirty,阻塞合并。
  2. 什么原因导致的:旧基线 + 期间 master 演进(nudge: downward reset compares baseline only, never lastNudgeShownTokens — a high shown-reference is never re-anchored #478 触碰同一测试文件区域、chore: prettier baseline — config, CI format gate, tree reformat #438 prettier 基线重排全树格式),非代码逻辑分歧。
  3. 怎么修复的:rebase 到 master tip;唯一冲突取 PR 侧新测试(该测试的存在意义就是替换旧不变量);自动合并结果逐行复核确认与 master 侧变更语义正交;style commit 过 format gate。
  4. 是否回归问题:无。逐项核过:① nudge: downward reset compares baseline only, never lastNudgeShownTokens — a high shown-reference is never re-anchored #478 下移重锚逻辑原样保留且与本 PR 状态字段交互安全(nudge: downward reset compares baseline only, never lastNudgeShownTokens — a high shown-reference is never re-anchored #478 重置 lastShownByTier 但不清 lastInjectedTier——re-anchor 后若 T1+distill 双就绪,仍按 lastInjectedTier 正常续走轮换,交替性不被打断);② 压力路径/token-mass 路径未触碰;③ 默认触发器(1000/2000)候选池恒空 → 默认行为仍逐字节不变;④ 首轮优先语义保持。
  5. 是否破坏缓存:不涉及——wire 格式、渲染标签、快照机制均未动;state 字段仍是增量可选。
  6. 是否改了设计方向:不涉及——方向与原 PR 一致,未引入任何新语义。
  7. 是否引入新的配置:不涉及——无新 env/config/默认值(lastInjectedTier 为原 PR 已披露的内部可选状态字段)。

验证:typecheck 干净;定向测试(nudge/compress/pipeline/pressure-benefit/persist/packs)162/162 绿;build 成功;format:check 稳定通过。全量回归交 CI(已随 push 触发)。

判定:冲突清除,PR 恢复可合并,等 CI 绿 + owner 人工合并(§7.3 契约 8 + 状态字段增量,按规约留人)。

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.

decideNudge(): growth-path T1-first ordering structurally starves T2/T3 distillation nudges when t1Eff >= nudgeGrowthTokens

1 participant