Skip to content

Feature: Support percentage-based nudgeGrowthTokens for cross-model portability #300

Description

@yangyaofei

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 / 配置用法:

{
  "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 from nudgeGrowthTokens, so they benefit automatically. Backward-compatible: existing numeric configs keep working unchanged.

这是一处单点行为新增。所有下游消费者(growthFlooreffectiveThreshold、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:

在调查上述问题时,触发决策链显示出明显的补丁堆叠。从 currentTokensshouldInjectNudge 的完整路径:

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions