code-yeongyu / code-yeongyu/senpi

Feature request: configurable compaction model (`compaction.model`) — summarization always runs on the active session model

Open
#830 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
429
Forks
98
Avg merge
5h 3m
Merged PRs (30d)
526

Description

## Summary

`CompactionSettings` has no way to select the model that performs summarization. Compaction always runs on the **active session model**, so a session on an expensive model (e.g. `claude-sdk-oauth/claude-opus-5`) also pays Opus rates — and Opus OAuth quota — for every summarization, including speculative and idle compactions the user never asked for.

opencode already exposes this as `agents..compaction.model`. Requesting the equivalent in senpi.

## Evidence: no such knob exists today

`dist/core/settings-manager.d.ts` — every key of `CompactionSettings`, none of them a model:

```ts
export interface CompactionSettings {
enabled?: boolean;
reserveTokens?: number;
keepRecentTokens?: number;
speculativeEnabled?: boolean;
speculativeFraction?: number;
speculativeCooldownMs?: number;
restorationEnabled?: boolean;
restorationMaxItems?: number;
restorationMaxTokensPerItem?: number;
restorationMaxTotalTokens?: number;
restorationContextRatio?: number;
idleCompactionEnabled?: boolean;
}
```

`dist/core/extensions/builtin/compaction/speculative.js:247` — the snapshot hard-binds the active model:

```js
export function createSpeculativeCompactionSnapshot(context, options) {
const model = context.model;
if (!model) return undefined;
...
return { generation, expectedRevision, model, contextWindow, preparation, ... };
}
```

`dist/core/extensions/builtin/compaction/speculative.js:284` — summarization authenticates and generates against that same model:

```js
export async function runExtensionCompaction(context, snapshot, signal, onProgress) {
const auth = await context.modelRegistry?.getApiKeyAndHeaders(snapshot.model);
...
response = await generateSummaryMessage({ context, messages, prompt, snapshot, auth, ... });
}
```

There is no seam between "active model" and "summarizer model" anywhere on this path. A repo-wide grep for `compactionModel` / `summarizationModel` / `compaction.model` returns nothing in `@code-yeongyu/senpi` or `@earendil-works/pi-agent-core`.

## Why this matters (concrete case)

Main session on `claude-sdk-oauth/claude-opus-5`:

- With the default `resumeMode: "auto"`, `lane-policy.js` makes senpi stand down entirely (`SDK_NATIVE_LANE_REJECTION_REASON`), so the session can sit above the compaction threshold with `Compaction rejected: the Claude Agent SDK owns compaction for this session` and `Context remains above the compaction threshold because compaction did not complete`.
- The documented escape hatch is `claudeSdkOauthProvider.resumeMode: "off"`, which restores senpi compaction but flattens and re-sends the full history each turn (worse caching, more tokens).
- Once senpi compaction is back on, every summarization — proactive, speculative, idle — is billed to Opus, on the exact credential that is already the scarce resource.

A `compaction.model` would make that trade-off survivable: keep Opus for reasoning, run summarization on a cheap model.

Note that `omo.jsonc` users can already write `["opencode"].agents.build.compaction.model` and it is silently a no-op for senpi itself, which is an easy trap.

## Proposed behavior

Add an optional model override to `CompactionSettings`:

```json
"compaction": {
"enabled": true,
"model": "zai/glm-5.2"
}
```

Semantics:

- Unset (default) keeps today's behavior exactly: summarize on the active model.
- When set, `createSpeculativeCompactionSnapshot` resolves the override through the model registry and puts it in `snapshot.model`; `runExtensionCompaction` then authenticates and generates against it with no other change.
- `snapshot.contextWindow` should stay derived from the **session** model's usage (it describes the history being summarized), while the summarizer's own limits bound `boundSummarizationInput`. Worth deciding explicitly if the override has a smaller window than the session model.
- If the override cannot be resolved or has no credentials, fall back to the active model rather than failing compaction — a misconfigured summarizer must never wedge a session above the threshold.
- Optionally accept the same override per-agent/per-category, mirroring opencode's `agents..compaction.model`.

## Version

- `@code-yeongyu/senpi` 2026.8.11-4 (via `omo-ai` 5.0.0-0.beta.6)
- macOS arm64
- Provider/model: `claude-sdk-oauth` / `claude-opus-5`

## Checked for duplicates

Searched open + closed: `compaction model`, `summarization model`, `cheaper model`, `compact config`, `compaction settings`, `opencode compaction`, plus a full pass over all 28 open issues. Related but distinct: #765 (OpenAI remote compaction 15s timeout), #650 (unbounded overflow-retry loop), #527/#531 (caps and circuit breaker), #543 (runtime-registered provider auth), #296/#415 (codex-responses specific), #723 (retry re-bills full conversation). None of them request a configurable compaction model.

Contributor guide

Open the contributing guide

Research direction

Start with dist/core/settings-manager.d.ts and the snapshot and execution paths in dist/core/extensions/builtin/compaction/speculative.js. Trace model resolution, authentication, and context-window handling, then verify that an unset or unavailable override preserves the active-model behavior while a configured override is used for summarization without wedging compaction.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
ai
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
58/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.