Skip to content

fix(compaction): keep the post-compaction autocontinue on the session working model - #8397

Open
mradwankhalil wants to merge 15 commits into
code-yeongyu:devfrom
mradwankhalil:fix/compaction-autocontinue-working-model
Open

mradwankhalil wants to merge 15 commits into
code-yeongyu:devfrom
mradwankhalil:fix/compaction-autocontinue-working-model

Conversation

@mradwankhalil

@mradwankhalil mradwankhalil commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Keeps the post-compaction autocontinue on the session's working model when the agent's compaction.model differs from it.
  • OpenCode's synthetic continue turn inherits the summarize request's model (session/compaction.ts: model: userMessage.model), so with a per-agent compaction.model configured the compaction model silently ran the continuation turns. The existing guard only matches agent === "compaction" and can never catch a model-only mismatch.
  • On mismatch the handler now disables OpenCode's synthetic continue and re-issues the same continue prompt through session.promptAsync with the working model, the working agent, and the compaction_continue metadata marker.
  • New helper module plugin/compaction-autocontinue-reroute.ts + three regression tests.

Problem

With agents.<agent>.compaction.model configured (preemptive compaction enabled), a live session was observed continuing on the compaction model after auto-compaction. Message rows from that session's opencode.db:

time (UTC) role model notes
17:23:18 user zai-coding-plan/glm-5.3-flash synthetic compaction trigger (carries the compaction part, auto: true)
17:24:20 assistant zai-coding-plan/glm-5.3-flash summary (agent: "compaction") — expected
17:31:38 user zai-coding-plan/glm-5.3-flash synthetic continue: "Continue if you have next steps…"
17:31:38 → 17:33:25 assistant ×5 zai-coding-plan/glm-5.3-flash task continuation ran on the compaction model
17:36:11 user opencode-go/deepseek-v4.1-flash next real prompt returns the session to its working model

Root cause chain

  1. Preemptive compaction calls client.session.summarize({ providerID, modelID: <compaction.model>, auto: true }) (hooks/preemptive-compaction-trigger.ts).
  2. OpenCode stores the compaction trigger user message with that model (SessionCompaction.create).
  3. OpenCode's autocompaction creates the synthetic continue message with model: userMessage.model — i.e. the summarize model — and gates it only on the experimental.compaction.autocontinue plugin hook (packages/opencode/src/session/compaction.ts, around lines 497–548 in v1.18.31).
  4. createCompactionAutocontinueHandler disables the continue only when isCompactionAgent(input.agent). When the trigger's agent is the working agent (normal case) and only the model differs, the guard cannot match, so the continue runs on the compaction model.

Fix

plugin/compaction-autocontinue-reroute.ts

  • Resolves the trigger model (newest message carrying a compaction part) and the working model (nearest non-compaction message, via the existing findNearestMessageWithFieldsFromMessages, which already skips compaction messages).
  • createCompactionAutocontinueHandler now, after the existing checkpoint restores:
    • sets output.enabled = false to drop OpenCode's poisoned synthetic continue, then
    • re-issues the identical continue prompt with session.promptAsync({ body: { agent, model: <working>, parts: [{ type: "text", text: <same continue text>, metadata: { compaction_continue: true } }] } }).

Behavior matrix:

condition behavior
models equal unchanged
message lookup fails / no working model resolvable unchanged (fail-open, never breaks the flow)
models differ rerouted to the working model; if the re-issue call itself fails, no continue is issued (preferable to continuing on the wrong model) and the failure is logged

Wiring: testing/create-plugin-module.ts passes client and directory into the handler options. Both are optional — without a client the handler keeps the previous behavior, so existing unit tests and non-client call sites are unaffected.

Notes / limitations

  • The SDK session.promptAsync body has no variant field, so the re-issued continue runs at the model's default effort; model selection itself is exact.
  • Scope is limited to the post-compaction autocontinue; summaries still run on the configured compaction.model.
  • Applies to every agent with a compaction.model, not just one.

Verification

  • bun test src/index.compacting.test.ts src/index.compaction-model-agnostic.static.test.ts → 16 pass / 0 fail, 52 expect() calls (3 new cases: reroute on mismatch; unchanged on equal models; unchanged on lookup failure)
  • bunx tsgo --noEmit -p packages/omo-opencode/tsconfig.json → passed
  • bun run build → passed
  • Live reproduction recorded before the fix (table above); the guard logic is additionally covered by unit tests.

Only the intended files are included (4 files, +308/−1); unrelated local worktree edits were not staged.


Summary by cubic

Fixes post-compaction autocontinue running on the configured compaction.model instead of the session's working model.

OpenCode's synthetic continue inherits the summarize request's model, so with a per-agent compaction.model configured, the compaction model silently ran continuation turns after auto-compaction. The handler now compares the compaction trigger's model with the working model, resolved from the newest real assistant turn while skipping compaction summaries and internal continue prompts so the reroute doesn't cascade onto itself. On mismatch it disables the synthetic continue and reissues the same prompt on the working model and agent through the shared prompt gate (dispatchInternalPrompt, queued when busy); a sent or queued dispatch counts as reissued, while equal models, lookup failures, and rejected dispatches keep the previous flow.

  • New plugin/compaction-autocontinue-reroute.ts module plus client/directory wiring in testing/create-plugin-module.ts.
  • Three regression tests cover rerouting, equal models, and lookup failure.
  • Changelog entry added.

Written for commit 7de402d. Summary will update on new commits.

View guided diff


Rebased onto v5.0.0-beta.72 (2026-09-18)

The fix has been ported forward from beta.62 to the current release so it can merge cleanly:

  • 8 local fix commits cherry-picked onto v5.0.0-beta.72; the only conflict was changes.md (changelog) and the base version was kept there.
  • The generated-artifacts commit (lockfile + extension bundles) was dropped and regenerated instead (bun install + bun run build) — replaying it collides with extension files this release removed.
  • packages/omo-opencode/src/index.compacting.test.ts: 14 pass / 0 fail; typecheck clean on omo-opencode, omo-senpi, omo-codex.
  • Verified this fix is not present upstream at v5.0.0-beta.72 (compaction-autocontinue-reroute absent from the tree), so it is still required.

Reproduction this addresses: after auto-compaction, the synthetic continuation ran on the compaction model instead of the session working model (session/compaction.ts stamps the trigger message with the per-agent compaction.model, and the continuation inherits userMessage.model). The added compaction-autocontinue-reroute module disables core's synthetic continue and re-issues the prompt via session.promptAsync with the working model.

@mradwankhalil

Copy link
Copy Markdown
Contributor Author

Root cause (the synthetic continue inheriting the summarize model) is now tracked upstream at anomalyco/opencode#49386 — note the repo moved from sst/opencode to anomalyco/opencode. This PR is the plugin-side mitigation until core can resolve the continue model from the session instead.

@mradwankhalil
mradwankhalil force-pushed the fix/compaction-autocontinue-working-model branch from b53ea27 to 0d10267 Compare September 18, 2026 15:02
@github-actions github-actions Bot added lazycodex Codex (LazyCodex) edition: packages/omo-codex omo-senpi Changes under packages/omo-senpi labels Sep 18, 2026
@mradwankhalil

Copy link
Copy Markdown
Contributor Author

Follow-up commit pushed: a98b94dd8 — expands the working-model resolution, because the current one is still reachable by two message classes that make the reroute sticky:

  1. Compaction summaries (agent: "compaction") when they carry no compaction part — they become the "nearest message with fields".
  2. The internal compaction_continue prompts themselves — synthetic user turns are stored with agent+model, so once a continue is issued on a non-session model, every subsequent reroute re-resolves to that same model. Self-sustaining loop.

Observed live: continuations on zai-coding-plan/glm-5.3-flash long after the session was back on its own model; the same message signature exists in several older sessions.

The follow-up adds isCompactionArtifact / isInternalContinuationMessage filters and resolves the working model/agent from the newest non-internal assistant turn instead.

Verified: package tsgo typecheck clean, full bun run build clean, behavior reconstructed from opencode.db message forensics.

OpenCode's synthetic post-compaction continue inherits the summarize request's
model (session/compaction.ts stamps model: userMessage.model). When an agent's
compaction.model differs from the model the session is actually running, the
compaction model ended up continuing the task.

The experimental.compaction.autocontinue handler now compares the compaction
trigger message's model with the nearest non-compaction message's model. On a
mismatch it disables OpenCode's synthetic continue and re-issues the continue
prompt via session.promptAsync with the session's working model, agent, and the
compaction_continue metadata marker. Lookup failures and equal models keep the
previous behavior.

Adds plugin/compaction-autocontinue-reroute.ts and three regression tests.
…l assistant turn

Follow-up to the autocontinue reroute: the resolver previously used the newest message carrying agent+model fields, which includes compaction summaries and the internal compaction_continue prompts themselves. Once a continue was issued on a non-session model it kept re-resolving to that model, silently switching live sessions to the compaction model (observed: zai-coding-plan/glm-5.3-flash continuations after the session had returned to its own model; same pattern across Sep 14/15/18 sessions).

Now the working model/agent are resolved only from non-compaction, non-internal messages, preferring the newest assistant turn. Added isCompactionArtifact / isInternalContinuationMessage / pickActiveSessionMessage helpers.

Verified: package typecheck (tsgo) clean, full build clean, live-session message forensics.
@code-yeongyu

Copy link
Copy Markdown
Owner

Thanks for this, @mradwankhalil, the working-model fix is a good catch.

One real CI failure to sort out: the prompt-route audit (packages/omo-opencode/src/shared/prompt-async-route-audit.test.ts:424) flags plugin/compaction-autocontinue-reroute.ts, because it calls client.session.promptAsync directly. Only the shared prompt gate may call the raw OpenCode prompt APIs. If you send the reroute through dispatchInternalPrompt from packages/omo-opencode/src/shared/prompt-async-gate.ts (features/monitor/output-injector.ts is a small example, checking the result with isInternalPromptDispatchAccepted), the audit should go green.

The other two reds need nothing from you: the Windows "memory pressure dream wiring" failure is a known flake (#10154), and the changelog check is failing on several PRs since the v5.1.35 cut moved ## [Unreleased]. Your entry just needs to sit back under ## [Unreleased] once you're next on the branch.

@mradwankhalil

Copy link
Copy Markdown
Contributor Author

Thanks — both points noted, and that's the shape we'll push on the branch:

  1. The reroute goes through dispatchInternalPrompt (packages/omo-opencode/src/shared/prompt-async-gate.ts), with the result checked via isInternalPromptDispatchAccepted — no direct client.session.promptAsync left in plugin/compaction-autocontinue-reroute.ts, so the prompt-route audit should stop flagging it.
  2. The CHANGELOG entry moves back under ## [Unreleased] (right below the heading, above ## [5.1.35]) on the next pass on the branch.

Also, thanks for the flake pointer on the Windows "memory pressure dream wiring" failure (#10154) — good to know it and the changelog gate are not this PR's to fix beyond the move.

…ompt gate

The prompt-route audit flags plugin/compaction-autocontinue-reroute.ts for calling client.session.promptAsync directly; only the shared prompt gate may call the raw OpenCode prompt APIs. The reroute now goes through dispatchInternalPrompt with an explicit queueBehavior, and the result is checked with isInternalPromptDispatchAccepted (sent or queued counts as reissued); anything else is logged and reported as not reissued.
mradwankhalil added a commit to mradwankhalil/oh-my-openagent that referenced this pull request Oct 17, 2026
@mradwankhalil

Copy link
Copy Markdown
Contributor Author

Both points are pushed on the branch, new head f2f8981086af4d0899d492ef787b3807a21396eb:

  • The reroute now goes through dispatchInternalPrompt with an explicit queueBehavior: "enqueue", and the result is checked with isInternalPromptDispatchAccepted (sent or queued counts as reissued; anything else is logged and reported as not reissued). No direct client.session.promptAsync left in plugin/compaction-autocontinue-reroute.ts — the prompt-route audit passes (10/0).
  • The CHANGELOG entry sits under ## [Unreleased] (right below the heading), and the branch is synced with dev 90eaba6e93.

Checks on the branch: index.compacting.test.ts 14/0, prompt-async-route-audit.test.ts 10/0, packages/omo-opencode typecheck exit 0, changelog gate PASS.

@mradwankhalil
mradwankhalil force-pushed the fix/compaction-autocontinue-working-model branch from a0cbc6b to 23dad8c Compare October 19, 2026 14:15
@code-yeongyu

Copy link
Copy Markdown
Owner

Thanks for the updates, @mradwankhalil! A heads-up on the red CI here: most of it isn't from your change. A dev-wide lint/typecheck break landed in dev and is fixed there now. Could you merge the current dev into this branch? That should clear the inherited typecheck, ubuntu test and senpi-compatibility failures. A couple of Windows-only failures may still show up after that; those are known and being fixed on our side, so you can ignore them. Anything else still red is worth a look.

@mradwankhalil

Copy link
Copy Markdown
Contributor Author

Merged dev at f65dbc3aed38a29a64d97d7f72c64bbc09d1e62d into this branch; the published head is 7f30b7dfac04fb92b9c1d603f3414a998ff2b244. The merge preserves the PR changes and changelog entry without rewriting history.

I have also noted your later clarification on #9787/#10828 that the remaining Senpi Ubuntu freshness failure is an upstream generated-artifact issue. I am leaving those generated bundles untouched rather than adding unrelated regeneration to this PR. This confirms the requested merge, not an all-green CI result; the known Windows failures remain separate as you advised. Thank you for the guidance.

@code-yeongyu

Copy link
Copy Markdown
Owner

Changelog entries moved to per-PR fragments (#10477). Pull requests no longer edit CHANGELOG.md, the root changes.md or packages/<name>/changes.md; every merge used to leave the other open pull requests conflicting on those first lines. This pull request still edits one of them, so the Changelog gate check fails until it is converted:

git fetch origin dev
bun script/changelog-fragments-cli.ts from-diff --base origin/dev --pr <this PR's number>
git add -A && git commit -m "chore(changelog): move the entry into changes/unreleased"

That moves your Unreleased paragraphs and log entries into changes/unreleased/<number>.md and puts the shared files back to the merge base, so the branch also stops conflicting on them. The format is in docs/reference/changelog-fragments.md. No other change is needed; what counts as a shipped change is the same as before.

@mradwankhalil

Copy link
Copy Markdown
Contributor Author

Thanks for clarifying the fragment format. Noted: this PR's entry belongs in changes/unreleased/8397.md, rather than the shared changelog files. No branch change to report.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lazycodex Codex (LazyCodex) edition: packages/omo-codex omo-senpi Changes under packages/omo-senpi opencode OpenCode edition: packages/omo-opencode

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants