Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
20 changes: 20 additions & 0 deletions docs/compaction.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,25 @@ Prompt selection:
- short UI summary: `compaction-short-summary.md`
- handoff document: `handoff-document.md` (used by `generateHandoff(...)`, not serialized compaction)

LLM summary model selection:

1. `currentModel.compactionModel`, if the active model metadata defines one.
2. `modelRoles.compaction`, if configured.
3. A provider-local fast compaction model such as `gpt-5.3-codex-spark`, when `compaction.preferFastModel !== false`, the prepared compaction input fits the fast model's context and modalities, and rerouting cannot regress prompt-cache economics (see below).
4. The active session model.
5. Built-in fallback roles (`default`, `smol`, `slow`, `vision`, `plan`, `designer`, `commit`, `tiny`, `task`, `advisor`).
6. The remaining available model with the largest context window.

Candidates without usable credentials are skipped. The inferred fast compaction candidate is skipped before any request when the prepared input is too large for its usable context window, so the chain falls through to the active model or later fallback. `/compact remote` keeps the same order but filters out provider-native non-remote-capable candidates when no `compaction.remoteEndpoint` is configured. Snapcompact does not use this chain because it archives history locally and requires the active model to support image input.

The inferred candidate is additionally gated on prompt-cache economics. Provider-native remote compaction (V1 `/responses/compact` and V2 streaming) replays the session's exact native history — the prefix the provider has already cached for the live conversation — so on the session model most of that input bills at the roughly 10x cheaper `cacheRead` rate. Prefix caches do not transfer across models, so rerouting the replay to a fast model whose list price matches the session model's would silently turn a warm cached read into a full-price cold prefill. The inferred fast candidate is therefore only used when at least one of these holds:

- the compaction wire is `openai-codex-responses` (the flat-rate ChatGPT subscription backend, where tokens are not metered),
- the session model would not use provider-native replay anyway (remote compaction disabled or unsupported; the local summary prompt never shared a prefix with the session cache), or
- the fast model's catalog input price does not exceed the session model's `cacheRead` price (no billing regression; also covers zero-cost catalogs).

Explicitly configured targets (`compactionModel`, `modelRoles.compaction`) bypass this gate — a user who routes compaction to a specific model accepts the trade-off.

Remote summarization modes:

- If `compaction.remoteEndpoint` is set and remote compaction is enabled, local summary generation POSTs:
Expand Down Expand Up @@ -411,6 +430,7 @@ From `settings-schema.ts`:
- `compaction.keepRecentTokens` = `20000`
- `compaction.autoContinue` = `true`
- `compaction.midTurnEnabled` = `true`
- `compaction.preferFastModel` = `true`
- `compaction.remoteEnabled` = `true`
- `compaction.remoteEndpoint` = `undefined`
- `compaction.thresholdPercent` = `-1` and `compaction.thresholdTokens` = `-1`; when no positive override is set, the threshold is `contextWindow - max(15% of contextWindow, reserveTokens)`
Expand Down
2 changes: 1 addition & 1 deletion docs/models.md
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,7 @@ Resolution precedence for exact selectors:

Supported model roles:

- `default`, `smol`, `slow`, `vision`, `plan`, `designer`, `commit`, `tiny`, `task`, `advisor`
- `default`, `smol`, `slow`, `vision`, `plan`, `designer`, `commit`, `tiny`, `task`, `compaction`, `advisor`

The `tiny` role overrides the online model used for lightweight background tasks (session titles, memory, `auto`-thinking difficulty classification, unexpected-stop detection); when unset, these fall back to `pi/smol`. Pick one in `/models`.

Expand Down
4 changes: 3 additions & 1 deletion docs/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ enabledModels:

| Key | Type | Default | Notes |
|---|---|---|---|
| `modelRoles` | record | `{}` | Map of role name -> model id. Built-in roles: `default`, `smol`, `slow`, `vision`, `plan`, `designer`, `commit`, `tiny`, `task`, `advisor`. The `tiny` role overrides the online model for lightweight background tasks (titles, memory, auto-thinking, unexpected-stop), else `pi/smol`. Per-role env/flags exist only for `--model`/`--smol`/`--slow`/`--plan`; configure the advisor with `modelRoles.advisor`. |
| `modelRoles` | record | `{}` | Map of role name -> model id. Built-in roles: `default`, `smol`, `slow`, `vision`, `plan`, `designer`, `commit`, `tiny`, `task`, `compaction`, `advisor`. The `tiny` role overrides the online model for lightweight background tasks (titles, memory, auto-thinking, unexpected-stop), else `pi/smol`; the `compaction` role overrides the model used for LLM summary compaction when the active model does not define `compactionModel`. Per-role env/flags exist only for `--model`/`--smol`/`--slow`/`--plan`; configure the advisor with `modelRoles.advisor`. |
| `modelTags` | record | `{}` | Custom role/tag metadata; can introduce additional roles. |
| `modelProviderOrder` | array | `[]` | Preferred provider order when a model id is ambiguous. |
| `cycleOrder` | array | `["smol","default","slow"]` | Roles cycled by the model switcher. |
Expand Down Expand Up @@ -508,6 +508,7 @@ compaction:
enabled: true
strategy: snapcompact # context-full, handoff, shake, snapcompact, off
midTurnEnabled: true # check thresholds between tool-loop provider requests
preferFastModel: true # try provider-local fast summary model when it fits
thresholdPercent: -1 # -1 = default reserve-based behavior
thresholdTokens: -1 # fixed token limit when > 0
remoteEnabled: true
Expand All @@ -521,6 +522,7 @@ memory:
| `contextPromotion.enabled` | boolean | `true` | Promote relevant earlier context. |
| `compaction.enabled` | boolean | `true` | Automatic conversation compaction. |
| `compaction.midTurnEnabled` | boolean | `true` | Check thresholds at safe mid-turn tool-loop boundaries before the next provider request. |
| `compaction.preferFastModel` | boolean | `true` | Try a provider-local fast model such as `gpt-5.3-codex-spark` for LLM summary compaction when no explicit compaction model is configured; skip it when the prepared input does not fit or when rerouting would forfeit cached input pricing on a metered provider (see [compaction](compaction.md)). |
| `compaction.strategy` | enum | `snapcompact` | `context-full`, `handoff`, `shake`, `snapcompact`, `off`. |
| `compaction.thresholdPercent` | number | `-1` | Percent-of-context trigger; `-1` = reserve-based default. |
| `compaction.thresholdTokens` | number | `-1` | Fixed token trigger when `> 0`. |
Expand Down
2 changes: 2 additions & 0 deletions packages/agent/src/compaction/compaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ export interface CompactionSettings {
*/
reserveTokens?: number;
keepRecentTokens: number;
preferFastModel?: boolean;
autoContinue?: boolean;
remoteEnabled?: boolean;
remoteEndpoint?: string;
Expand All @@ -191,6 +192,7 @@ export const DEFAULT_COMPACTION_SETTINGS: CompactionSettings = {
thresholdTokens: -1,
midTurnEnabled: true,
keepRecentTokens: 20000,
preferFastModel: true,
autoContinue: true,
remoteEnabled: true,
remoteStreamingV2Enabled: true,
Expand Down
5 changes: 5 additions & 0 deletions packages/coding-agent/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## [Unreleased]

### Added

- Added a `modelRoles.compaction` role for routing LLM summary compaction to a dedicated model when the active model does not define `compactionModel`.
- Added `compaction.preferFastModel` to try a provider-local fast compaction model by default and fall back when the prepared input does not fit. The inferred fast candidate is only used when it cannot regress prompt-cache economics: on the flat-rate `openai-codex-responses` wire, when the session model would not use provider-native history replay anyway, or when the fast model's input price does not exceed the session model's cached-input price. Explicit `compactionModel`/`modelRoles.compaction` targets bypass the gate.

## [16.3.6] - 2026-07-04

### Changed
Expand Down
3 changes: 3 additions & 0 deletions packages/coding-agent/src/config/model-roles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export type ModelRole =
| "commit"
| "tiny"
| "task"
| "compaction"
| "advisor";

export interface ModelRoleInfo {
Expand All @@ -35,6 +36,7 @@ export const MODEL_ROLES: Record<ModelRole, ModelRoleInfo> = {
commit: { tag: "COMMIT", name: "Commit", color: "dim" },
tiny: { tag: "TINY", name: "Tiny", color: "dim" },
task: { tag: "TASK", name: "Subtask", color: "muted" },
compaction: { tag: "COMPACT", name: "Compaction", color: "muted" },
advisor: { tag: "ADVISOR", name: "Advisor", color: "accent" },
};

Expand All @@ -48,6 +50,7 @@ export const MODEL_ROLE_IDS: ModelRole[] = [
"commit",
"tiny",
"task",
"compaction",
"advisor",
];

Expand Down
13 changes: 13 additions & 0 deletions packages/coding-agent/src/config/settings-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1894,6 +1894,18 @@ export const SETTINGS_SCHEMA = {
},
},

"compaction.preferFastModel": {
type: "boolean",
default: true,
Comment on lines +1897 to +1899

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should-fix / maintainer-call: default: true changes default compaction routing from the active session model to an inferred fast model in subscription, remote-disabled, and zero-cost cases. Repo review guidance treats default-behaviour changes as requiring explicit maintainer sign-off; if this is intentional, the maintainer should confirm the default flip rather than shipping it only as an opt-out setting.

ui: {
tab: "context",
group: "Compaction",
label: "Prefer Fast Compaction Model",
description:
"Use a provider-local fast model for LLM summary compaction when no explicit compaction model is configured; falls back when the prepared compaction input does not fit or when rerouting would forfeit cached input pricing on a metered provider",
},
},

"compaction.strategy": {
type: "enum",
values: ["context-full", "handoff", "shake", "snapcompact", "off"] as const,
Expand Down Expand Up @@ -5056,6 +5068,7 @@ export interface CompactionSettings {
reserveTokens: number | undefined;
keepRecentTokens: number;
midTurnEnabled: boolean;
preferFastModel: boolean;
handoffSaveToDisk: boolean;
autoContinue: boolean;
remoteEnabled: boolean;
Expand Down
Loading