Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,15 +157,15 @@ Core compression behavior.

#### `compress.maxContextLimit`
- **Type:** `number | \`${number}%\``
- **Default:** `"55%"`
- **Default:** `"80%"`
- **Status:** ACTIVE
- **Description:** Upper context usage threshold (as % of model context window or absolute tokens). When exceeded, ACP nudges the model to compress. Example: `"55%"` or `100000`.
- **Description:** Upper context usage threshold (as % of model context window or absolute tokens). When exceeded, ACP nudges the model to compress. Example: `"80%"` or `100000`.

#### `compress.minContextLimit`
- **Type:** `number | \`${number}%\``
- **Default:** `"45%"`
- **Default:** `"80%"`
- **Status:** ACTIVE
- **Description:** Lower context usage threshold. ACP stops nudging when usage drops below this level.
- **Description:** Lower context usage threshold for turn/iteration reminder nudges. ACP stops injecting those reminders when usage drops below this level (or when the limit cannot be resolved to a concrete value, e.g. a `"X%"` limit with an unknown model context window). Growth nudges are governed separately by `minNudgeContextPercent`.

#### `compress.modelMaxLimits`
- **Type:** `Record<string, number | \`${number}%\`>`
Expand All @@ -187,9 +187,15 @@ Core compression behavior.

#### `compress.minNudgeContextPercent`
- **Type:** `number`
- **Default:** `15`
- **Default:** `5`
- **Status:** ACTIVE
- **Description:** Floor for growth-triggered nudges, as a percentage of the model context window: a growth nudge requires context usage at or above this percentage (in addition to the growth threshold). Over-max (`maxContextLimit`) and the 98% emergency-override nudges bypass the floor. If the model context window is unknown, the floor is unresolvable and growth nudges fall back to growth-only behavior. Turn/iteration reminder nudges are governed by `minContextLimit`, not this field. The default is deliberately low: with the default `nudgeGrowthTokens` (50K), a 5% floor stays inert for typical working cycles and only binds on very large (≥2M-class) windows — a higher default (e.g. 15%) would bind on ≥400K windows and shift every compress cycle's working range upward on large-window models. Set `0` to disable the floor entirely, or raise it (e.g. 15–30%) to keep growth nudges waiting until a larger share of the window is in use. Override it per model with `modelMinNudgeLimits`.

#### `compress.modelMinNudgeLimits`
- **Type:** `Record<string, number | \`${number}%\`>`
- **Default:** `undefined`
- **Status:** ACTIVE
- **Description:** Minimum context usage percentage before any nudges are shown. Below this, no nudges are injected.
- **Description:** Per-model override for the growth-nudge floor. Keyed by `provider/model` (same keying as `modelMaxLimits` / `modelMinLimits`). Values are absolute tokens or `"X%"` of that model's context window. Precedence: `modelMinNudgeLimits[provider/model]` → `minNudgeContextPercent` × model context → growth-only behavior when the model context is unknown. An absolute value applies even when the model context window is unknown; a `"X%"` value with an unknown model context falls through to the global percent. Example: `{"openai/gpt-5.6": 150000, "openrouter/z-ai/glm-5.3": "20%"}`

#### `compress.nudgeGrowthTokens`
- **Type:** `number`
Expand Down Expand Up @@ -442,6 +448,10 @@ Post-compression quality evaluation. Runs after each compression to verify summa
"modelMinLimits": {
"gpt-4o": 60000,
"claude-3.5-sonnet": "50%"
},
"modelMinNudgeLimits": {
"openai/gpt-5.6": 150000,
"openrouter/z-ai/glm-5.3": "20%"
}
}
}
Expand Down
16 changes: 13 additions & 3 deletions CONFIGURATION.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ ACP 从最多三层配置文件中读取(后加载的覆盖先加载的):
- **类型:** `number | \`${number}%\``
- **默认值:** `"45%"`
- **状态:** ACTIVE
- **说明:** 上下文使用率下限。使用率降至此值以下时ACP 停止 nudge。
- **说明:** turn/iteration 提醒 nudge 的上下文使用率下限。使用率降至此值以下时(或该限制无法解析为具体值时,例如模型上下文窗口未知时的 `"X%"` 限制),ACP 停止注入这些提醒。Growth nudge 由 `minNudgeContextPercent` 单独控制

#### `compress.modelMaxLimits`
- **类型:** `Record<string, number | \`${number}%\`>`
Expand All @@ -187,9 +187,15 @@ ACP 从最多三层配置文件中读取(后加载的覆盖先加载的):

#### `compress.minNudgeContextPercent`
- **类型:** `number`
- **默认值:** `15`
- **默认值:** `5`
- **状态:** ACTIVE
- **说明:** 触发任何 nudge 的最低上下文使用率百分比。低于此值时不注入 nudge。
- **说明:** growth 触发 nudge 的下限,以模型上下文窗口的百分比表示:growth nudge 要求上下文使用率达到或超过该百分比(此外还需满足增长阈值)。超过上限(`maxContextLimit`)和 98% 紧急覆盖 nudge 不受此下限约束。若模型上下文窗口未知,则下限无法解析,growth nudge 回退到仅增长行为。turn/iteration 提醒 nudge 由 `minContextLimit` 控制,而非此字段。默认值刻意设低:在默认 `nudgeGrowthTokens`(50K)下,5% 下限在典型工作循环中不生效,仅在超大(≥2M 级)窗口上才会约束——更高的默认值(如 15%)会在 ≥400K 窗口上生效,并使大窗口模型每个压缩循环的工作区间上移。设为 `0` 可完全禁用下限;调高(如 15–30%)可让 growth nudge 等待更大的窗口占用比例。可用 `modelMinNudgeLimits` 按模型覆盖。

#### `compress.modelMinNudgeLimits`
- **类型:** `Record<string, number | \`${number}%\`>`
- **默认值:** `undefined`
- **状态:** ACTIVE
- **说明:** 按模型覆盖 growth-nudge 下限。以 `provider/model` 为键(与 `modelMaxLimits` / `modelMinLimits` 相同)。值为绝对 token 数或该模型上下文窗口的 `"X%"`。优先级:`modelMinNudgeLimits[provider/model]` → `minNudgeContextPercent` × 模型上下文 → 模型上下文未知时回退到仅增长行为。绝对值在模型上下文窗口未知时同样生效;`"X%"` 值在模型上下文未知时回退到全局百分比。示例:`{"openai/gpt-5.6": 150000, "openrouter/z-ai/glm-5.3": "20%"}`

#### `compress.nudgeGrowthTokens`
- **类型:** `number`
Expand Down Expand Up @@ -442,6 +448,10 @@ ACP 从最多三层配置文件中读取(后加载的覆盖先加载的):
"modelMinLimits": {
"gpt-4o": 60000,
"claude-3.5-sonnet": "50%"
},
"modelMinNudgeLimits": {
"openai/gpt-5.6": 150000,
"openrouter/z-ai/glm-5.3": "20%"
}
}
}
Expand Down
19 changes: 14 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -324,11 +324,12 @@ Each level overrides the previous, so project settings take priority over global
// Soft upper threshold: above this, ACP keeps injecting strong
// compression nudges (based on nudgeFrequency), so compression is
// much more likely. Accepts: number or "X%" of model context window.
"maxContextLimit": "55%",
// Soft lower threshold for reminder nudges: below this, turn/iteration
// reminders are off (compression less likely). At/above this, reminders
// are on. Accepts: number or "X%" of model context window.
"minContextLimit": "45%",
"maxContextLimit": "80%",
// Soft lower threshold for turn/iteration reminder nudges: below this,
// those reminders are off (compression less likely). At/above this, they
// are on. Growth nudges have their own floor: minNudgeContextPercent.
// Accepts: number or "X%" of model context window.
"minContextLimit": "80%",
// Optional per-model override for maxContextLimit by providerID/modelID.
// If present, this wins over the global maxContextLimit.
// Accepts: number or "X%".
Expand All @@ -343,6 +344,14 @@ Each level overrides the previous, so project settings take priority over global
// "openai/gpt-5.3-codex": 50000,
// "anthropic/claude-sonnet-4.6": "25%"
// },
// Optional per-model override for the growth-nudge floor
// (minNudgeContextPercent). Keyed by providerID/modelID; accepts a
// token count or "X%" of that model's context window. If present,
// this wins over the global minNudgeContextPercent for that model.
// "modelMinNudgeLimits": {
// "openai/gpt-5.6": 150000,
// "openrouter/z-ai/glm-5.3": "20%"
// },
// How often the context-limit nudge fires (1 = every fetch, 5 = every 5th)
"nudgeFrequency": 5,
// Start adding compression reminders after this many
Expand Down
15 changes: 12 additions & 3 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,9 +279,10 @@ ACP 使用自己的配置文件,按以下顺序搜索:
// compression nudges (based on nudgeFrequency), so compression is
// much more likely. Accepts: number or "X%" of model context window.
"maxContextLimit": "55%",
// Soft lower threshold for reminder nudges: below this, turn/iteration
// reminders are off (compression less likely). At/above this, reminders
// are on. Accepts: number or "X%" of model context window.
// Soft lower threshold for turn/iteration reminder nudges: below this,
// those reminders are off (compression less likely). At/above this, they
// are on. Growth nudges have their own floor: minNudgeContextPercent.
// Accepts: number or "X%" of model context window.
"minContextLimit": "45%",
// Optional per-model override for maxContextLimit by providerID/modelID.
// If present, this wins over the global maxContextLimit.
Expand All @@ -297,6 +298,14 @@ ACP 使用自己的配置文件,按以下顺序搜索:
// "openai/gpt-5.3-codex": 50000,
// "anthropic/claude-sonnet-4.6": "25%"
// },
// Optional per-model override for the growth-nudge floor
// (minNudgeContextPercent). Keyed by providerID/modelID; accepts a
// token count or "X%" of that model's context window. If present,
// this wins over the global minNudgeContextPercent for that model.
// "modelMinNudgeLimits": {
// "openai/gpt-5.6": 150000,
// "openrouter/z-ai/glm-5.3": "20%"
// },
// How often the context-limit nudge fires (1 = every fetch, 5 = every 5th)
"nudgeFrequency": 5,
// Start adding compression reminders after this many
Expand Down
21 changes: 18 additions & 3 deletions dcp.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,21 @@
]
}
},
"modelMinNudgeLimits": {
"description": "Per-model override for the growth-nudge floor (minNudgeContextPercent) by exact provider/model key. Values are absolute tokens or \"X%\" of that model's context window. If set, this takes priority over the global minNudgeContextPercent for that model.",
"type": "object",
"additionalProperties": {
"oneOf": [
{
"type": "number"
},
{
"type": "string",
"pattern": "^\\d+(?:\\.\\d+)?%$"
}
]
}
},
"nudgeFrequency": {
"type": "number",
"default": 5,
Expand Down Expand Up @@ -229,9 +244,9 @@
},
"minNudgeContextPercent": {
"type": "number",
"default": 15,
"default": 5,
"minimum": 0,
"description": "Minimum context usage percent to show per-message nudges"
"description": "Minimum context usage percent for growth-triggered nudges; growth nudges below this floor are suppressed (0 disables the floor)"
},
"maxSummaryLengthHard": {
"type": "number",
Expand Down Expand Up @@ -312,7 +327,7 @@
"protectedTools": ["skill", "compress"],
"protectTags": false,
"protectUserMessages": false,
"minNudgeContextPercent": 15,
"minNudgeContextPercent": 5,
"maxSummaryLengthHard": 20000,
"maxVisibleSegments": 50,
"minCompressRange": 5000,
Expand Down
75 changes: 75 additions & 0 deletions devlog/2026-08-28_min-gate-growth-nudges/REQ.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# REQ - Gate T1 growth nudges on the minNudgeContextPercent floor

- Task ID: `2026-08-28_min-gate-growth-nudges`
- Home Repo: `opencode-acp`
- Created: 2026-08-28
- Status: Done
- Priority: P1
- Owner: ework-daemon (qwen3.8-27b)
- References: issue ranxianglei/opencode-acp#342

## 1. Background & Problem Statement

- **Context**: ACP's T1 (message/range) compression nudges should not fire when the working context is too small to be worth compressing. The intended knob for that is `minNudgeContextPercent` (default **15**, a percent of the model context) — the "don't nudge below this" floor. It was plumbed into `computeShouldNudge` but **ignored** by the external trigger policy, so it was a no-op and growth nudges fired at any context size. (`minContextLimit` / `maxContextLimit` are a separate concern — the soft limit-reminder thresholds, both defaulting to **80%**.)
- **Current behavior (symptom)**: T1 *growth* nudges fire well below any configured floor. In the reporter's setup (400K window, `modelMinLimits=150000`, `modelMaxLimits=215000`, `nudgeGrowthTokens=50000`), ten `trigger=growth` nudges fired at 67K–152K tokens. Raising `nudgeGrowthTokens` is not a clean workaround because the same knob drives T2/T3 promotion.
- **Expected behavior**:
- T1 efficiency (growth) nudge = `currentTokens >= minNudgeContextPercent%×modelContext` AND `growth >= nudgeGrowthTokens` (AND `growth >= growthFloor`).
- T1 maximum nudge = `currentTokens >= maxContextLimit` (unchanged; bypasses the floor).
- Emergency (≥ `emergencyThresholdPercent`, default 98%) override unchanged.
- T2/T3 tier-promotion nudges remain independent of the floor (useful even when working context is small).
- **Impact**: users who tune the floor to defer compression get no such deferral for growth nudges; the model compresses early instead of at the configured floor, wasting compression cycles.

## 2. Reproduction (if applicable)

- **Environment**: opencode-acp 1.14.25, Node 22/24, 400K-context model.
- **Minimal reproduction steps**:
1. Set `compress.minNudgeContextPercent` above the session's starting context (e.g. 37.5 on a 400K model = 150K floor).
2. Let context grow by ≥ `nudgeGrowthTokens` while still below the floor.
3. A `trigger=growth` nudge is injected despite `currentTokens < floor`.
- **Relevant configuration**: `compress.minNudgeContextPercent`, `compress.nudgeGrowthTokens`.

## 3. Root cause

- `computeShouldNudge` (external `context-compress-algorithms/trigger`) computes `shouldNudge = growthSinceLastNudge >= nudgeGrowthTokens || overMaxLimit`. `minNudgeContextPercent` is passed in but **ignored** (deprecated in the policy's param type).
- In `lib/messages/inject/inject.ts`, `nudgeAllowed = emergencyOverride || (decision.shouldNudge && growthSinceBaseline >= growthFloor)` — no context-size floor at all. So growth nudges fire at any context size once the growth threshold is met.
- **Why not `minContextLimit`**: the first revision gated on `overMinLimit`, but `minContextLimit` defaults to **80%** (`lib/config.ts:200`) and is documented as the "soft lower threshold for turn/iteration reminders" (README.md:328-331). Gating growth nudges on it would suppress ALL growth nudges below 80% for default users — effectively disabling compression for most of a session (flagged by @dog in issue #342). The correct floor is the low-default `minNudgeContextPercent` (15%).

## 4. Constraints & Non-Goals

- **Constraints**:
- The floor must default to a LOW value (`minNudgeContextPercent` = 15%) so default users still get growth nudges throughout a session.
- When the model context limit is unknown, the floor is unresolvable and growth nudges keep pre-#342 growth-only behavior (no accidental suppression).
- `overMaxLimit` and the emergency path must be unaffected (they bypass/override the floor).
- T2/T3 tier-promotion nudges must remain independent of the floor.
- No new dependencies; no change to the external `context-compress-algorithms` package (fix lives in ACP's `inject.ts`).
- No change to persisted state format or internal `dcp` tags.
- **Non-Goals** (out of scope):
- Percentage-based `nudgeGrowthTokens` (issue #300).
- The broader T1/T2/T3 decision-chain refactor (issue #300).
- A per-model `modelMinNudgeContextPercent` variant (the floor is a global percent for now; per-model is a follow-up enhancement).
- Making the max-limit path bypass the `growthFloor` cadence gate (pre-existing, documented anti-thrashing behavior — left as-is per issue #342 discussion).

## 5. Acceptance Criteria (must be testable)

- **Correctness**:
- [x] A T1 growth nudge does NOT fire when `currentTokens < minNudgeContextPercent%×modelContext` (even if `growth >= nudgeGrowthTokens` and `>= growthFloor`).
- [x] A T1 growth nudge DOES fire when `currentTokens >= floor` and `growth >= nudgeGrowthTokens` and `>= growthFloor`.
- [x] A T1 maximum nudge still fires when `currentTokens >= maxContextLimit` (bypasses the floor).
- [x] The emergency (≥98%) override still fires regardless of the floor.
- [x] When the model context limit is unknown, growth nudges keep pre-#342 behavior (floor unresolvable → no suppression).
- [x] T2/T3 tier-promotion nudges are unaffected by the floor.
- **Performance / Stability**: one floor check in the existing decision path — no measurable cost.
- **Regression**:
- [x] New/modified test cases added to test suite and passing (1035 tests, 0 failures).
- [x] Pre-existing test #27 (context 100K on a 1M model) updated into the [floor, max) range since the dormant 15% floor now suppresses below 150K.
- [x] Stale README/CONFIGURATION default docs corrected (45%/55% → 80%/80%).

## 6. Proposed Approach

- **Affected modules & entry files**:
- `lib/messages/inject/inject.ts` — compute `overMinNudgeFloor` from `minNudgeContextPercent` and add `(overMaxLimit || overMinNudgeFloor)` to the `nudgeAllowed` growth path.
- `lib/messages/inject/utils.ts` — revert the `minLimitResolved` addition (dead code in the floor approach).
- `tests/inject.test.ts` — 4 floor tests + 2 new (unresolvable model limit, T2 independence); fix pre-existing test #27.
- `README.md`, `CONFIGURATION.md` — default corrections (from the review commit, preserved).
- **Risks**: activating a previously-dormant field changes default behavior (growth nudges below 15% are now suppressed). Verified against the full suite; the only pre-existing test affected was #27 (100K context on a 1M model).
- **Rollback strategy**: revert the commits; no schema/config/data migrations.
Loading
Loading