Skip to content

feat: per-model growth-nudge floor modelMinNudgeLimits (issue #344) - #345

Closed
ranxianglei wants to merge 17 commits into
masterfrom
2026-08-28_model-min-nudge-limits
Closed

feat: per-model growth-nudge floor modelMinNudgeLimits (issue #344)#345
ranxianglei wants to merge 17 commits into
masterfrom
2026-08-28_model-min-nudge-limits

Conversation

@ranxianglei

Copy link
Copy Markdown
Owner

Summary

Follow-up to PR #343 (issue #342): adds an optional per-model growth-nudge floor for mixed-model installations, where no single global minNudgeContextPercent can express the intended floors.

compress: {
  // "provider/model" -> absolute tokens or "X%" of that model's window
  modelMinNudgeLimits: {
    "openai/gpt-5.6": 150000,
    "openrouter/z-ai/glm-5.3": "20%"
  }
}

Precedence (per the issue):

  1. modelMinNudgeLimits[provider/model]
  2. minNudgeContextPercent × model context (PR fix: gate T1 growth nudges on minContextLimit (issue #342) #343 behavior, unchanged when the new field is unset)
  3. growth-only behavior when the model context is unknown

Absolute per-model floors apply even when the model context window is unknown (mirrors modelMaxLimits/modelMinLimits); a per-model percent with unknown context falls through to the global percent (also unresolvable → growth-only). modelMinLimits keeps its turn/iteration-reminder meaning — untouched.

Changes

  • lib/messages/inject/utils.ts — new resolveMinNudgeFloorTokens(config, modelContextLimit, providerId, modelId)
  • lib/messages/inject/inject.ts — floor resolved via the helper (provider/model from existing getModelInfo)
  • lib/config.tsCompressConfig.modelMinNudgeLimits + merge (?? replace-inherited, same as modelMinLimits) + deepClone
  • lib/config-validation.ts — valid key, no dynamic-key recursion, validateModelLimits reuse
  • dcp.schema.json, CONFIGURATION.md, README.md — documented
  • tests/inject.test.tsuserMsgWithModel() helper + 9 issue #344: tests
  • tests/config-validation.test.ts — 4 new tests
  • devlog/2026-08-28_model-min-nudge-limits/ — REQ.md + WORKLOG.md

Tests

  • 1048 pass / 0 fail (npm run test), typecheck + build green, scripts/ci/check-pr.sh green
  • New inject tests cover: per-model absolute wins over global percent (multi-turn with lastPerMessageNudgeTokens/lastNudgeShownTokens side-effect assertions); per-model percent resolves against the per-model window (20% of 1,048,576 → 209,715); unlisted model keeps the global floor; unknown-context fallbacks (absolute applies, percent falls through); production config (preserveRecentMessages: 2); full growth cycle baseline → nudge → compress → new baseline → nudge; over-max bypass; modelMinLimits independence
  • Mutation check (AGENTS.md §5.7): with the per-model branch disabled, the 4 per-model-specific tests fail and the fallback/bypass/independence tests still pass — expected split; fix re-applied, all green

Note on base

This branch is stacked on PR #343 (2026-08-28_min-gate-growth-nudges) because the floor it extends (minNudgeFloorTokens/overMinNudgeFloor) only exists there. The diff against master therefore includes #343's commits until #343 merges; after that this PR's diff shrinks to the changes above. Merging #343 first is required (this PR is the follow-up, not a replacement).

Compatibility

No behavior change when modelMinNudgeLimits is unset (global computation is PR #343's verbatim). No persisted-state or dcp tag changes. No version bump (release handled separately).

ework-agent added 8 commits August 28, 2026 20:35
Growth-triggered compression nudges fired well below a configured
minContextLimit because computeShouldNudge() only uses overMinLimit to
pick the tips variant, not to gate the decision. Add an explicit
(overMaxLimit || overMinLimit) gate to the T1 growth path in
injectCompressNudges so a configured minimum acts as a lower bound.

- overMaxLimit and the emergency override bypass the gate
- T2/T3 tier-promotion nudges are unaffected (independent cadence)
- 4 new tests; 2 existing growth-mechanism tests had minContextLimit
  lowered so they still isolate the growth mechanism

devlog: devlog/2026-08-28_min-gate-growth-nudges/
- Gate only applies when minContextLimit resolves (minLimitResolved):
  percent limits with an unknown model context window keep pre-#342
  growth-only behavior instead of suppressing all growth nudges
- Unmask 2 existing tests whose minContextLimit sat above the test
  context (post-compress small growth, baseline init)
- 3 new tests: unresolvable-limit fallback, emergency override bypass,
  T2 tier-promotion independence from the min gate
- Correct stale min/max defaults in README.md and CONFIGURATION.md
  (45%/55% -> 80%/80%, matching lib/config.ts since v1.14.16)
- Devlog: REQ constraints/acceptance, WORKLOG results (1036 tests)
…minContextLimit

Per @Dog's feedback in issue #342: minContextLimit defaults to 80%
(lib/config.ts:200) and is documented as the soft lower threshold for
turn/iteration reminders, so gating growth nudges on it suppresses ALL
growth nudges below 80% for default users — effectively disabling
compression for most of a session. The dual-agent review's minLimitResolved
guard did not address this (a default user with a known context still gets
growth nudges suppressed below 80%).

Use minNudgeContextPercent (default 15, a percent of model context) as the
growth-nudge floor instead. It is the intended 'don't nudge below this'
knob and was previously a no-op (plumbed into computeShouldNudge but
ignored). When the model context limit is unknown the floor is unresolvable
and growth nudges keep pre-#342 behavior. overMaxLimit and the emergency
override bypass the floor; T2/T3 tier-promotion nudges are unaffected.

- Reverts the review commit's minLimitResolved mechanism (dead code here)
- Preserves the review commit's README/CONFIGURATION default fixes (80%/80%)
- Tests: 4 floor tests + 2 new (unresolvable model limit, T2 independence)
  re-pointed at the floor; pre-existing test #27 raised into the [floor, max)
  range. 1035 tests, 0 failures.
…th-floor redesign

Re-point stale references left by the floor redesign (ecfe7f0):
- README.md: minContextLimit governs turn/iteration reminders only;
  growth nudges have their own floor (minNudgeContextPercent)
- CONFIGURATION.md: minContextLimit description (turn/iteration only);
  minNudgeContextPercent description (growth-nudge floor, over-max and
  emergency bypasses, unknown-window fallback)
- lib/messages/inject/utils.ts: fix the @deprecated comment on
  minNudgeContextPercent (field is active; floor computed in inject.ts)
- tests/inject.test.ts: section header re-pointed to the floor
- WORKLOG: record ecfe7f0 hash + this commit's row
Add optional compress.modelMinNudgeLimits (Record<provider/model,
number | "X%">) as a per-model override for the T1 growth-nudge floor
introduced in PR #343. Precedence: per-model entry -> global
minNudgeContextPercent x model context -> growth-only when the model
context is unknown. Absolute per-model floors apply even with unknown
model context (mirrors modelMaxLimits/modelMinLimits); per-model
percents fall through to the global percent when unresolvable.

- lib/messages/inject/utils.ts: new resolveMinNudgeFloorTokens()
- lib/messages/inject/inject.ts: floor resolved via the helper
- lib/config.ts: field + merge (?? semantics) + deepClone
- lib/config-validation.ts: VALID_CONFIG_KEYS, no recursion,
  validateModelLimits reuse
- dcp.schema.json, CONFIGURATION.md, README.md: documented
- tests/inject.test.ts: userMsgWithModel helper + 9 issue #344 tests
  (multi-turn, side-effect assertions, production config, growth cycle,
  over-max bypass, modelMinLimits independence; mutation-verified)
- tests/config-validation.test.ts: 4 new tests
- devlog/2026-08-28_model-min-nudge-limits/: REQ.md + WORKLOG.md
@github-actions

github-actions Bot commented Aug 28, 2026

Copy link
Copy Markdown

📦 Built Plugin Artifact

Branch: 2026-08-28_model-min-nudge-limits (7ceb97b)

Option A — Install from npm PR tag (recommended)

opencode plugin opencode-acp@pr-345 --global

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

Option B — Install from GitHub

opencode plugin "github:ranxianglei/opencode-acp#2026-08-28_model-min-nudge-limits" --global

Option C — Download artifact

  1. Download the artifact from the Actions run
  2. Extract the tarball and install:
tar xzf opencode-acp-pr345.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] On it — reviewing PR #345 (2026-08-28_model-min-nudge-limits). Since it's stacked on #343, I'll review the diff against 2026-08-28_min-gate-growth-nudges rather than master, run the full test suite + typecheck + build, and check the §5.7 nudge-testing requirements (multi-turn, side-effect assertions, production config, growth cycle, mutation check). Will report findings here.

@ranxianglei

Copy link
Copy Markdown
Owner Author

[bot] Dual-agent review complete — no blockers.

Two independent agents reviewed ecfe7f0..50a117e per AGENTS.md §5.3/§5.6:

Code review — verified the full precedence contract (per-model entry → global minNudgeContextPercent → growth-only fallback) and edge cases: "" rejected by validation, "0%" → floor 0, "150%" clamped to 100, undefined provider/model guarded, mid-session model switching consistent, deepCloneConfig safe on an unset record. Independently reproduced the §5.7 mutation check (per-model branch disabled → exactly tests 1/2/4/6 fail, fallback/bypass/cycle/independence still pass). modelMinLimits semantics, max/emergency bypasses, and T2/T3 paths confirmed untouched.

Test review — §5.6 all pass (real imports, name fidelity, buildConfig() completeness, token math); §5.7 all pass (multi-turn tests, side-effect assertions on lastPerMessageNudgeTokens/lastNudgeShownTokens every turn, production config with preserveRecentMessages: 2, full growth cycle, mutation discrimination).

Should-fix applied: missing zh-CN doc parity → CONFIGURATION.zh-CN.md + README.zh-CN.md now document modelMinNudgeLimits (commit 4c9d772).

Nits noted, not taken: negative-number validation gap is pre-existing and shared with modelMaxLimits/modelMinLimits; userMsgWithModel duplicates userMsg's body (cosmetic — the existing helper is shared with older tests).

ework-agent added 2 commits August 28, 2026 23:04
The branch was forked from #343 before its floor-redesign + doc-alignment
commits (ecfe7f0, b71e10b); the stacked commit carried stale pre-redesign
wording that silently reverted #343's intentional doc fixes:

- CONFIGURATION.md / README.md / README.zh-CN.md / CONFIGURATION.zh-CN.md:
  minContextLimit is the turn/iteration-reminder threshold, not a global
  nudge gate; growth nudges are floored by minNudgeContextPercent (with
  over-max / 98% emergency bypass). Restored #343 wording; the new
  modelMinNudgeLimits sections keep their per-model cross-reference.
- tests/inject.test.ts: restored the issue #342 section header
  (minNudgeContextPercent floor, not minContextLimit).
- lib/messages/inject/utils.ts: restored the minNudgeContextPercent
  param comment (floor is computed in inject.ts, not deprecated).
- devlog/2026-08-28_min-gate-growth-nudges/WORKLOG.md: restored from the
  #343 branch (this PR had rewritten another PR's devlog, replacing real
  commit hashes with <pending>).
- CONFIGURATION.zh-CN.md: fixed leading-space indentation defect from the
  zh-CN parity commit and aligned the minNudgeContextPercent description
  with the (corrected) English floor semantics.
@ranxianglei

Copy link
Copy Markdown
Owner Author

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

[bot] PR #345 review complete — fixes pushed, ready for merge (after #343).

Dual-agent review (per the report above): no blockers — precedence contract, edge cases, §5.7 mutation split, and §5.6/§5.7 test requirements all verified; the zh-CN parity should-fix was already applied (4c9d772).

Additional findings from my independent review — the dual-agent review missed a doc-regression: this branch was forked from #343 before its floor-redesign + doc-alignment commits (ecfe7f0, b71e10b), so the stacked commit carried stale pre-redesign wording that silently reverted #343's intentional doc fixes:

  1. CONFIGURATION.md / README.md / README.zh-CN.md / CONFIGURATION.zh-CN.mdminContextLimit had been re-described as a global nudge gate ("stops nudging… growth, turn, and iteration reminders" / "触发任何 nudge"), which is wrong: it gates only turn/iteration reminders; growth nudges are floored by minNudgeContextPercent (with over-max / 98% emergency bypass). Restored fix: gate T1 growth nudges on minContextLimit (issue #342) #343's wording; the new modelMinNudgeLimits sections keep their per-model cross-references.
  2. tests/inject.test.ts — the issue [Bug]: Growth nudges ignore minContextLimit and fire below it #342 section header had been rewritten from "must respect the minNudgeContextPercent floor" to "must respect minContextLimit" — the opposite direction. Restored.
  3. lib/messages/inject/utils.ts — the minNudgeContextPercent param comment had been changed to a misleading "@deprecated… Cadence is growth-only now." Restored the accurate comment (floor is computed in inject.ts).
  4. devlog/2026-08-28_min-gate-growth-nudges/WORKLOG.md — this PR had rewritten another PR's devlog (real commit hashes ecfe7f0/b71e10b replaced with <pending>). Restored from the fix: gate T1 growth nudges on minContextLimit (issue #342) #343 branch.
  5. CONFIGURATION.zh-CN.md — fixed a leading-space indentation defect from the zh-CN parity commit and aligned the minNudgeContextPercent description with the corrected English floor semantics.

Commits pushed (3276533..6f0d9cd):

Verification (re-run after the fixes): npm run test 1048/1048 ✓ · typecheck ✓ · build ✓ · scripts/ci/check-pr.sh ✓ (version unchanged). §5.7 mutation check independently reproduced: per-model branch disabled → exactly the 4 per-model-specific tests fail, fallback/bypass/cycle/independence still pass.

Reminder: this PR is stacked on #343 (2026-08-28_min-gate-growth-nudges) — merge #343 first; afterwards this PR's diff shrinks to the modelMinNudgeLimits changes above. Per AGENTS.md §5.1.1.2 I won't merge either PR — please merge yourself when ready.

@ranxianglei

Copy link
Copy Markdown
Owner Author

继续

@ranxianglei

Copy link
Copy Markdown
Owner Author

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

[bot] PR #345 的工作已全部完成,当前状态汇总:

已完成(代码侧无剩余工作):

  1. 验证了双 agent 审查结论(no blockers;zh-CN 文档 parity 的 should-fix 已在 4c9d772 落地)。
  2. 发现并修复了审查漏掉的问题——本分支在 fix: gate T1 growth nudges on minContextLimit (issue #342) #343 的 floor-redesign + 文档对齐提交(ecfe7f0b71e10b)之前分叉,stacked commit 携带了旧措辞,静默回滚了 fix: gate T1 growth nudges on minContextLimit (issue #342) #343 有意的文档修正
    • 4 份文档把 minContextLimit 错写成全局 nudge 闸门(实际只控制 turn/iteration 提醒;growth nudge 由 minNudgeContextPercent 控制,over-max / 98% 紧急覆盖不受限)
    • tests/inject.test.ts 的 issue [Bug]: Growth nudges ignore minContextLimit and fire below it #342 段落标题方向写反(minNudgeContextPercent → 误写为 minContextLimit
    • lib/messages/inject/utils.ts 误导性 @deprecated 注释
    • 本 PR 改写了 fix: gate T1 growth nudges on minContextLimit (issue #342) #343 自己的 devlog(真实 commit hash 被替换成 <pending>
    • CONFIGURATION.zh-CN.md 缩进缺陷 + minNudgeContextPercent 描述与英文对齐
  3. 已推送:940f4ca(修复)+ 6f0d9cd(WORKLOG 记录 hash),分支 tip 6f0d9cd
  4. 修复后复验全绿:npm run test 1048/1048 ✓ · typecheck ✓ · build ✓ · scripts/ci/check-pr.sh ✓;§5.7 mutation check 独立复现(per-model 分支禁用 → 恰好 4 个 per-model 测试失败,fallback/bypass/cycle/independence 仍通过)。

刚刚核实的仓库状态:

接下来需要人工操作(按 AGENTS.md §5.1.1.2 我不能合并 PR):

  1. 先合并 PR fix: gate T1 growth nudges on minContextLimit (issue #342) #3432026-08-28_min-gate-growth-nudges)——本 PR 是 stack 在其上的 follow-up
  2. 再合并 PR feat: per-model growth-nudge floor modelMinNudgeLimits (issue #344) #345——fix: gate T1 growth nudges on minContextLimit (issue #342) #343 合并后,本 PR 对 master 的 diff 会自动收缩为纯 modelMinNudgeLimits 变更

A 15% default floor binds on windows >= ~400K (P% x W > baseline + 50K
growth threshold): on 1M-window models every compress cycle is
suppressed until 150K context usage, shifting the steady-state working
range ~2x upward. For the dominant large-window user profile
(baseline 10-50K, cycle ceiling ~110K) this is a silent, bug-level
behavior change.

5% only binds on >= ~2M-class windows, keeping the floor inert for
typical working cycles. Users who want a higher floor set it
explicitly (0 disables).

- lib/config.ts default 15 -> 5
- lib/messages/inject/inject.ts policy passthrough + floor fallback
  15 -> 5, with rationale comment
- dcp.schema.json default + stale description fixed
- CONFIGURATION.md / CONFIGURATION.zh-CN.md defaults + rationale
- tests/inject.test.ts: default-lock test (mutation-sensitive: fails
  if the fallback reverts to 15)
Merges 2026-08-28_min-gate-growth-nudges (f3a3fc8+2419871: default
minNudgeContextPercent 15% -> 5%) into the per-model floor stack and
aligns it:

- lib/messages/inject/utils.ts: resolveMinNudgeFloorTokens global-percent
  fallback 15 -> 5 (+ rationale comment; JSDoc updated)
- lib/messages/inject/inject.ts: kept resolveMinNudgeFloorTokens call,
  folded in the low-default rationale comment
- tests/inject.test.ts: kept all #344 per-model tests; moved the
  #342-follow-up default-lock test (from the base branch) before the
  #344 section
- CONFIGURATION.md: merged descriptions (deliberately-low default
  rationale + per-model override pointer)

Verified: typecheck, 1049/1049 tests, build.
@ranxianglei

Copy link
Copy Markdown
Owner Author

Merged the 5% default-floor fix from the #343 base branch — commits 1566c23+19efbc5 (merge + docs).

This stack's own copy of the global-percent fallback (resolveMinNudgeFloorTokens() in lib/messages/inject/utils.ts) was still ?? 15 and is now aligned to ?? 5 with the rationale comment. The base branch's default-lock test initially failed against this branch (150K floor suppressed the 100K-context case), catching exactly that stale-fallback inconsistency — kept and now passing. Suite: 1049/1049, typecheck + build clean. Per-model tests unaffected (they pin explicit values).

Model: Claude Sonnet 4.6

…ment note

- CONFIGURATION.zh-CN.md: the minNudgeContextPercent description now
  carries the deliberately-low-default rationale added to the English
  description by the 5% merge (5% inert for typical cycles, binds only
  on >=2M-class windows; 0 disables; raise to 15-30% to wait longer).
- WORKLOG: header amendment note pointing to §8, mirroring the base
  branch's WORKLOG treatment of the same 15% -> 5% change.
@ranxianglei

Copy link
Copy Markdown
Owner Author

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

[bot] Verified the 5% default-floor merge locally — plus 2 residual doc-parity gaps fixed (7ceb97b, pushed).

Merge verification (1566c23+19efbc5): all fallback copies are now consistent at ?? 5lib/config.ts default, lib/messages/inject/inject.ts:298 policy passthrough, resolveMinNudgeFloorTokens() global-percent fallback (lib/messages/inject/utils.ts:211, rationale comment + JSDoc), and dcp.schema.json (default + example + stale description fixed). The new default-lock test (issue #342 follow-up: unset minNudgeContextPercent falls back to the low 5% default floor) is correctly mutation-sensitive: deleting the field forces the fallback path (100K ≥ 50K floor + 50K growth → fires; ?? 15 would suppress at 150K).

Independently re-ran: npm run test 1049/1049 ✓ · typecheck ✓ · build ✓ · scripts/ci/check-pr.sh ✓.

Residual gaps I fixed (7ceb97b, docs-only):

  1. CONFIGURATION.zh-CN.md — the English minNudgeContextPercent description gained the deliberately-low-default rationale in the merge, but the zh-CN description didn't (same parity class as the earlier 4c9d772 finding). Added the translated rationale (5% inert for typical cycles, binds only on ≥2M-class windows; 0 disables; raise to 15–30% to wait longer).
  2. devlog/2026-08-28_model-min-nudge-limits/WORKLOG.md — added the header amendment note pointing to §8, mirroring how the base branch's WORKLOG treats the same 15% → 5% change. (The §3 code listing still shows ?? 15 — deliberately left as the historical snapshot of the original commit, with §8 recording the amendment, same pattern as the base branch.)

Branch tip is now 7ceb97b; still stacked on #343 (which now carries f3a3fc8+2419871), so the merge order stands: #343 first, then this PR.

@ranxianglei

Copy link
Copy Markdown
Owner Author

Model: Claude Sonnet 4.6

Closing in favor of #351, which replaces this PR's flat compress.modelMinNudgeLimits map ("provider/model" string keys) with a nested compress.providers.{provider}.models.{model} structure (model > provider > global field-by-field cascade, billion-context-pi design) — per the maintainer's direction. The config surface never shipped, so there is zero migration cost. #351 is stacked on #343 the same way this PR was; please merge #343#351.

drexb-ops pushed a commit to drexb-ops/opencode-acp that referenced this pull request Sep 3, 2026
…roviders (issue ranxianglei#344)

Replaces the flat modelMinNudgeLimits surface (PR ranxianglei#345, superseded) with
compress.providers.{provider}.models.{model}.minNudgeContextPercent,
cascading field-by-field: model > provider > global. 0 disables explicitly;
unknown ids fall back to global; percent resolves against the active model
window. Deep-merged across the three config file layers per provider/model
key. Validated + schema + CONFIGURATION (en/zh) + devlog + 17 new tests
(mutation-verified M1/M2/M3). 1053/1053 green.
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