Summary / 概述
compress.nudgeGrowthTokens currently accepts only an absolute token count. This makes it impossible to configure a portable "trigger at X% of the context window" that works across models with different window sizes. This issue proposes supporting percentage strings (e.g. "20%") for cross-model portability, and also raises a broader discussion about the complexity of the nudge decision chain.
compress.nudgeGrowthTokens 目前只接受绝对 token 数。这使得无法配置一个跨模型的"在窗口 X% 时触发"的设置。本 issue 建议支持百分比字符串(如 "20%")以实现跨模型可移植性,并附带讨论 nudge 决策链的复杂度问题。
The Problem / 问题
1. Fixed token count is not portable / 固定值不可移植
The nudge decision is:
shouldNudge = growthSinceLastNudge >= nudgeGrowthTokens || overMaxLimit
- Default
nudgeGrowthTokens = 50000 (since v1.14.15). On a 1M-token window, that's only 5% — efficiency nudges fire too eagerly, causing over-compression before context reaches useful utilization.
- A user can manually set
nudgeGrowthTokens: 200000 to target ~20% on a 1M window. But on a 200K-token window, 200000 > 200000... the growth branch can never fire, effectively disabling efficiency nudges entirely.
There is no single absolute value that works correctly across all window sizes.
默认 nudgeGrowthTokens = 50000(v1.14.15 起)。在 1M 窗口下只占 5%——efficiency nudge 触发过于频繁,在 context 达到有效利用率之前就过度压缩。用户可以手动设 200000 来适配 1M 窗口的 ~20%,但在 200K 窗口下,200000 > 200K,growth 分支永远无法触发,等于完全禁用了 efficiency nudge。不存在一个能正确适配所有窗口大小的绝对值。
2. The adaptive fallback is clamped too tightly / adaptive 回退被 clamp 卡死
resolveAdaptiveNudgeGrowth is ratio-based (5% × window), which is portable in principle. But it's clamped:
NUDGE_GROWTH_FLOOR = 6000
NUDGE_GROWTH_CAP = 50000 // ← caps 1M window at 50K (5%), defeating the ratio
NUDGE_GROWTH_RATIO = 0.05 // ← 5% is too small for users wanting later nudges
For a 1M window: min(50000, max(6000, 1M × 5%)) = 50000 (capped, not 5% of 1M). The adaptive mechanism exists but its CAP and RATIO prevent it from serving large-window models or users who want nudges at higher utilization.
resolveAdaptiveNudgeGrowth 本身是按比例的(5% × 窗口),原则上可移植。但被 clamp 限制:CAP=50000 导致 1M 窗口只得到 50K(5%,被截断),RATIO=0.05 对希望在更高利用率才触发 nudge 的用户来说太小。
3. Asymmetry: maxContextLimit is portable, nudgeGrowthTokens is not / 不对称:占比参数可移植,增长量参数不可移植
maxContextLimit: "40%" works identically on any model — it's a ratio. But nudgeGrowthTokens (which controls the efficiency nudge cadence) has no percentage equivalent. Users who want a portable efficiency-nudge threshold have no clean option.
maxContextLimit: "40%" 对任何模型都一致工作(占比)。但控制 efficiency nudge 频率的 nudgeGrowthTokens 没有对应的百分比语法。想要可移植 efficiency 阈值的用户没有干净的选项。
Proposed Solution / 建议方案
Allow nudgeGrowthTokens to accept percentage strings, resolved against modelContextLimit. Minimal change at lib/messages/inject/inject.ts:250-251:
让 nudgeGrowthTokens 支持百分比字符串,按 modelContextLimit 解析。最小改动在 lib/messages/inject/inject.ts:250-251:
// Current / 现状
const nudgeGrowthTokens =
config.compress?.nudgeGrowthTokens ?? resolveAdaptiveNudgeGrowth(modelContextLimit)
// Proposed / 建议
const raw = config.compress?.nudgeGrowthTokens
const nudgeGrowthTokens =
typeof raw === "string" && raw.endsWith("%")
? Math.floor((modelContextLimit ?? 1_000_000) * parseFloat(raw) / 100)
: typeof raw === "number"
? raw
: resolveAdaptiveNudgeGrowth(modelContextLimit)
Config usage / 配置用法:
This is a single-point behavioral addition. All downstream consumers (growthFloor, effectiveThreshold, tier-trigger gating) derive from nudgeGrowthTokens, so they benefit automatically. Backward-compatible: existing numeric configs keep working unchanged.
这是一处单点行为新增。所有下游消费者(growthFloor、effectiveThreshold、tier trigger 门控)都基于 nudgeGrowthTokens 派生,自动受益。向后兼容:现有的数字配置不受影响。
Broader Discussion: Decision Chain Complexity / 附带讨论:决策链复杂度
While investigating the above, the trigger decision chain shows significant patch-layering. The full path from currentTokens to shouldInjectNudge:
在调查上述问题时,触发决策链显示出明显的补丁堆叠。从 currentTokens 到 shouldInjectNudge 的完整路径:
currentTokens
├─ overMaxLimit? (ratio > maxLimit%) → tipsVariant
├─ overMinLimit? (ratio > minLimit%) → tipsVariant
├─ growthReference = lastNudgeShown ?? lastPerMessageNudge ← which baseline?
├─ effectiveThreshold = pending ? nudgeGrowthTokens/2 : nudgeGrowthTokens ← halving
├─ shouldNudge = growth >= effectiveThreshold || overMaxLimit
├─ growthFloor = max(minNudgeGrowthFloor, minNudgeGrowthRatio × nudgeGrowthTokens) ← anti-thrash
├─ growthSinceBaseline >= growthFloor? ← floor gate
├─ emergencyOverride = ratio >= emergencyThresholdPercent(98%) ← backstop
├─ nudgeAllowed = emergency || (shouldNudge && floorGate)
├─ nothingToCompress?
└─ shouldInject = nudgeAllowed && (!nothing || emergency)
[+ tier 2/3 independent check, also gated by nudgeGrowthTokens]
This is 8–9 decision nodes with cross-dependent intermediates. The root cause: two measurement systems coexist — growth-based (absolute token growth) and limit-based (ratio) — and they don't compose cleanly because baseline shifts after each compression.
这是 8–9 个判断节点,带有交叉依赖的中间变量。根因:两套度量共存——**growth-based(绝对增长量)**和 limit-based(占比)——它们无法干净组合,因为 baseline 在每次压缩后会漂移。
Observable patch layering / 可观察的补丁堆叠:
| Patch |
What it fixes |
补丁 |
修复什么 |
growthFloor anti-thrash |
Growth too frequent |
growthFloor 防抖 |
growth 过于频繁 |
| baseline correction |
Baseline drift after compress |
baseline 纠正 |
压缩后 baseline 漂移 |
effectiveThreshold halving |
Nudge not acted on |
threshold 减半 |
nudge 未被响应 |
| pin fixed 50K (v1.14.15) |
Adaptive too small for small windows |
固定 50K |
adaptive 对小窗口太小 |
| user hand-tunes |
Fixed 50K too small for large windows |
用户手动调 |
固定值对大窗口太小 |
Bug20 suppress overMaxLimit |
cacheRead stale after compress |
Bug20 抑制 |
压缩后 cacheRead 未更新 |
A ratio-first design could collapse this to / 一个占比优先的设计可将此简化为:
if ratio >= maxLimit% → overflow (hard compress)
if ratio >= minLimit% && anti-thrash → efficiency (soft nudge)
Two checks instead of nine, naturally portable, no baseline/halving/adaptive machinery. Not asking for a rewrite — just flagging the architectural debt for discussion. The percentage-string proposal above is a pragmatic first step that doesn't require touching the chain structure.
两个判断代替九个,天然可移植,不需要 baseline/减半/adaptive 那套机制。不要求重写——只是标记这个架构债务供讨论。上面的百分比字符串建议是一个务实的、不需要改动链结构的第一步。
Environment / 环境
- opencode-acp:
v1.14.16
- context-compress-algorithms:
1.3.0
- Model window: 1M tokens (large-window model)
- Config:
"compress.nudgeGrowthTokens": 200000 (manual workaround for 1M window)
TL;DR / 总结: nudgeGrowthTokens should accept "20%" percentage strings (like maxContextLimit already does) for cross-model portability. One-line change at inject.ts:250-251. Separately, the 9-node decision chain mixing absolute-growth and ratio thresholds has accumulated significant patch debt worth discussing.
Summary / 概述
compress.nudgeGrowthTokenscurrently accepts only an absolute token count. This makes it impossible to configure a portable "trigger at X% of the context window" that works across models with different window sizes. This issue proposes supporting percentage strings (e.g."20%") for cross-model portability, and also raises a broader discussion about the complexity of the nudge decision chain.compress.nudgeGrowthTokens目前只接受绝对 token 数。这使得无法配置一个跨模型的"在窗口 X% 时触发"的设置。本 issue 建议支持百分比字符串(如"20%")以实现跨模型可移植性,并附带讨论 nudge 决策链的复杂度问题。The Problem / 问题
1. Fixed token count is not portable / 固定值不可移植
The nudge decision is:
nudgeGrowthTokens = 50000(since v1.14.15). On a 1M-token window, that's only 5% — efficiency nudges fire too eagerly, causing over-compression before context reaches useful utilization.nudgeGrowthTokens: 200000to target ~20% on a 1M window. But on a 200K-token window,200000 > 200000... the growth branch can never fire, effectively disabling efficiency nudges entirely.There is no single absolute value that works correctly across all window sizes.
默认
nudgeGrowthTokens = 50000(v1.14.15 起)。在 1M 窗口下只占 5%——efficiency nudge 触发过于频繁,在 context 达到有效利用率之前就过度压缩。用户可以手动设200000来适配 1M 窗口的 ~20%,但在 200K 窗口下,200000 > 200K,growth 分支永远无法触发,等于完全禁用了 efficiency nudge。不存在一个能正确适配所有窗口大小的绝对值。2. The adaptive fallback is clamped too tightly / adaptive 回退被 clamp 卡死
resolveAdaptiveNudgeGrowthis ratio-based (5% × window), which is portable in principle. But it's clamped:For a 1M window:
min(50000, max(6000, 1M × 5%))= 50000 (capped, not 5% of 1M). The adaptive mechanism exists but itsCAPandRATIOprevent it from serving large-window models or users who want nudges at higher utilization.resolveAdaptiveNudgeGrowth本身是按比例的(5% × 窗口),原则上可移植。但被 clamp 限制:CAP=50000导致 1M 窗口只得到 50K(5%,被截断),RATIO=0.05对希望在更高利用率才触发 nudge 的用户来说太小。3. Asymmetry:
maxContextLimitis portable,nudgeGrowthTokensis not / 不对称:占比参数可移植,增长量参数不可移植maxContextLimit: "40%"works identically on any model — it's a ratio. ButnudgeGrowthTokens(which controls the efficiency nudge cadence) has no percentage equivalent. Users who want a portable efficiency-nudge threshold have no clean option.maxContextLimit: "40%"对任何模型都一致工作(占比)。但控制 efficiency nudge 频率的nudgeGrowthTokens没有对应的百分比语法。想要可移植 efficiency 阈值的用户没有干净的选项。Proposed Solution / 建议方案
Allow
nudgeGrowthTokensto accept percentage strings, resolved againstmodelContextLimit. Minimal change atlib/messages/inject/inject.ts:250-251:让
nudgeGrowthTokens支持百分比字符串,按modelContextLimit解析。最小改动在lib/messages/inject/inject.ts:250-251:Config usage / 配置用法:
{ "compress.nudgeGrowthTokens": "20%" // 1M→200K, 200K→40K — always 20% of window }This is a single-point behavioral addition. All downstream consumers (
growthFloor,effectiveThreshold, tier-trigger gating) derive fromnudgeGrowthTokens, so they benefit automatically. Backward-compatible: existing numeric configs keep working unchanged.这是一处单点行为新增。所有下游消费者(
growthFloor、effectiveThreshold、tier trigger 门控)都基于nudgeGrowthTokens派生,自动受益。向后兼容:现有的数字配置不受影响。Broader Discussion: Decision Chain Complexity / 附带讨论:决策链复杂度
While investigating the above, the trigger decision chain shows significant patch-layering. The full path from
currentTokenstoshouldInjectNudge:在调查上述问题时,触发决策链显示出明显的补丁堆叠。从
currentTokens到shouldInjectNudge的完整路径:This is 8–9 decision nodes with cross-dependent intermediates. The root cause: two measurement systems coexist — growth-based (absolute token growth) and limit-based (ratio) — and they don't compose cleanly because
baselineshifts after each compression.这是 8–9 个判断节点,带有交叉依赖的中间变量。根因:两套度量共存——**growth-based(绝对增长量)**和 limit-based(占比)——它们无法干净组合,因为
baseline在每次压缩后会漂移。Observable patch layering / 可观察的补丁堆叠:
growthFlooranti-thrasheffectiveThresholdhalvingoverMaxLimitcacheReadstale after compressA ratio-first design could collapse this to / 一个占比优先的设计可将此简化为:
Two checks instead of nine, naturally portable, no baseline/halving/adaptive machinery. Not asking for a rewrite — just flagging the architectural debt for discussion. The percentage-string proposal above is a pragmatic first step that doesn't require touching the chain structure.
两个判断代替九个,天然可移植,不需要 baseline/减半/adaptive 那套机制。不要求重写——只是标记这个架构债务供讨论。上面的百分比字符串建议是一个务实的、不需要改动链结构的第一步。
Environment / 环境
v1.14.161.3.0"compress.nudgeGrowthTokens": 200000(manual workaround for 1M window)TL;DR / 总结:
nudgeGrowthTokensshould accept"20%"percentage strings (likemaxContextLimitalready does) for cross-model portability. One-line change atinject.ts:250-251. Separately, the 9-node decision chain mixing absolute-growth and ratio thresholds has accumulated significant patch debt worth discussing.