Skip to content

Compaction: post-summarize "Continue" runs on the compaction model; summarize context-window mismatch; races with native compaction (3 bugs + feature gap) #69

Description

@Aurelian-Shuttleworth

Environment

  • opencode-supermemory (current main)
  • OpenCode 1.18.8 on a non-Claude provider (Z.ai GLM-5.2 1M-ctx main agent / GLM-4.7 200K-ctx compaction agent)
  • supermemory.jsonc with compactionThreshold set

Summary

Three related bugs in createCompactionHook (dist/index.js) plus a feature gap. On a non-Claude provider whose compaction model has a smaller context window than the main model, the plugin's custom compaction path (a) overflows the compaction model, (b) leaves the session "stuck" on the compaction model after summarizing, and (c) races with OpenCode's native compaction.auto.

Bug 1 — Post-summarize "Continue" runs on the compaction model, not the session model

After session.summarize, the hook fires a synthetic continuation:

// dist/index.js ~line 14945
await ctx.client.session.promptAsync({
  path: { id: sessionID },
  body: {
    agent: storedMessage2?.agent,           // ← agent resolved from the NEAREST message
    parts: [{ type: "text", text: "Continue" }]
  },
  query: { directory: ctx.directory }
})

storedMessage2 comes from findNearestMessageWithFields(messageDir), which picks up the summary message just produced by session.summarize — whose agent is the compaction agent. The session therefore continues on the compaction model (e.g. GLM-4.7) instead of the session's main model (GLM-5.2), and stays there until the user manually switches. On a 1M-ctx main / 200K-ctx compaction split this is both a quality regression and a context-window trap.

Suggested fix: capture the pre-compaction agent/model before calling session.summarize and restore them for the "Continue".

Bug 2 — Compaction context-window mismatch

The trigger fires at threshold × getModelLimit(mainModel) (e.g. 0.8 × 1M ≈ 800K tokens):

// dist/index.js ~line 15171-15174
const compactionHook = isConfigured() && ctx.client ? createCompactionHook(ctx, tags, {
  threshold: CONFIG.compactionThreshold,
  getModelLimit
}) : null;

…but session.summarize runs on the compaction agent (200K ctx):

// dist/index.js ~line 14927
await ctx.client.session.summarize({
  path: { id: sessionID },
  body: { providerID, modelID },     // ← compaction agent's model
  query: { directory: ctx.directory }
});

So the summarize request can be 4× the compaction model's context window → it fails or yields a truncated/garbage summary. In a 13-day window I see 47 sessions >1M tokens and 104 >200K in ~/.local/share/opencode/session.db. Compaction needs a model whose context window ≥ the main model's, or chunked summarization.

Bug 3 — validateCompactionThreshold makes 0 un-disablable

There is no documented way to disable the plugin's custom compaction. Setting compactionThreshold: 0 does not disable it — it silently falls back to the default:

// dist/index.js ~line 13771
function validateCompactionThreshold(value) {
  if (value === undefined || typeof value !== "number" || isNaN(value)) {
    return DEFAULTS.compactionThreshold;     // 0.8
  }
  if (value <= 0 || value > 1)
    return DEFAULTS.compactionThreshold;     // ← 0 lands here → silently re-enabled at 0.8
  return value;
}

The only effective disable is 1 (triggers at 100% of the model limit, which native compaction always beats). A 0 (or false) value should disable, not fall back.

Bug 4 — Races with OpenCode native compaction

The plugin's token-ratio → summarize → Continue path duplicates OpenCode's native compaction.auto; the two can double-trigger on the same session. OpenCode now exposes an experimental.session.compacting hook (fires before the continuation summary; lets plugins inject into output.context or replace output.prompt). The plugin should defer to that hook instead of reimplementing the trigger + continuation.

Feature gap — memory capture is post-hoc only

The only memory captured is the lossy post-compaction summary (saveSummaryAsMemory, ~line 14988). There is no pre-compaction extraction step. A review-and-extract pass (read the about-to-be-discarded context → curate durable facts → store, then lean summarize) would align the plugin with retrieval-augmented compaction (Anthropic Effective Context Engineering; LangChain write/isolate/compress) and substantially reduce context loss across the compaction boundary.

Workaround I'm using

  • agent.compaction.model set to a model with a context window matching the main agent.
  • compactionThreshold: 1 (the only value that effectively disables the plugin's compaction).
  • Native compaction.auto enabled.
  • A local experimental.session.compacting plugin that does pre-compaction extraction + dedup + store.

Happy to PR any of the fixes if there's interest. Tagging this against the version above; happy to provide the exact dist/index.js commit hash on request.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions