Skip to content

fix: guard goal continuation across compaction epochs - #61

Closed
harryzhou2000 wants to merge 1 commit into
willytop8:mainfrom
harryzhou2000:fix/compaction-epoch-guard
Closed

fix: guard goal continuation across compaction epochs#61
harryzhou2000 wants to merge 1 commit into
willytop8:mainfrom
harryzhou2000:fix/compaction-epoch-guard

Conversation

@harryzhou2000

@harryzhou2000 harryzhou2000 commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

What changed

  • scope continuation claims to a monotonically increasing compaction epoch
  • resume exactly once after a successful compaction even when the retained assistant ID is unchanged
  • coalesce identifiable duplicate session.compacted events and invalidate stale idle handlers before promptAsync
  • ignore compaction-summary assistants when selecting work progress or continuation sources
  • pause and best-effort abort after two compactions without a productive non-compaction assistant/tool turn
  • preserve native experimental.compaction.autocontinue suppression for active goals
  • add regressions for retained sources, duplicate delivery, stalled cycles, productive resets, summaries, and the claim-persistence race

Why

OpenCode can repeatedly compact a session whose retained context does not make progress. The existing goal-plugin fix correctly cleared the pre-compaction continuation claim, but it had no epoch identity or bounded failure path. Repeated compaction delivery could reopen continuation, while retaining the same assistant could also be re-scored by existing stall gates before the required post-compaction continuation.

This change keeps good compactions autonomous and makes active-goal stalled compactions terminate safely. It does not replace the scheduler-level OpenCode guard needed for sessions without an active goal.

Checks

Passed:

  • /usr/bin/node test/goal-plugin.test.js (313 tests)
  • compaction-focused regression run (13 tests)
  • npm run type:check
  • npm run smoke
  • npm run smoke:packed-host
  • npm run smoke:packed-tools
  • npm run benchmark:behavior (100/100)
  • npm run verify (7/7)
  • npm run pack:check
  • git diff --check

Environment baseline limitation:

  • npm test, npm run test:coverage, npm run check, and the mutation baseline reach the unchanged cross-process tests, where persistence-lease.test.js and session-concurrency.test.js helper children exit with code 0 before emitting READY.
  • The same two failures reproduce from a pristine archive of upstream/main on this Node 22.22.3 host; the changed goal-plugin.test.js passes completely.

GitHub Actions:

  • CodeQL, Node 18/20/22/24, Linux/macOS filesystem tests, coverage, type, mutation, behavior, smoke, audit, and package checks passed.
  • The initial Windows filesystem job failed in unchanged persistence-lease.test.js: simultaneous stale observers cannot both acquire the replacement lease expected one winner and observed zero. The integration lacks permission to rerun that failed job.

Manual OpenCode smoke testing was not performed; source and packed-host/tool contracts were run instead.

@harryzhou2000
harryzhou2000 marked this pull request as ready for review August 20, 2026 04:38

Copy link
Copy Markdown
Contributor Author

@willytop8 this is ready for review. It preserves automatic goal continuation after a good compaction while adding epoch-scoped deduplication and a bounded stalled-compaction circuit breaker, with regression coverage for the retained-assistant and race cases.

All PR-relevant CI jobs passed. The remaining red Windows filesystem job is an unchanged persistence-lease concurrency test flake documented in the PR body; the integration could not rerun it.

@willytop8

Copy link
Copy Markdown
Owner

Thanks for this @harryzhou2000 — the epoch-guard framing is the right model and
I've kept your commit as the base of the follow-up in #62.

While verifying against the vendored @opencode-ai/sdk types I found the guard
needed two corrections to match the events OpenCode actually emits:

  • session.compacted carries only sessionID (no id/compactionID/summaryID and
    no sync variant), so the identity-based dedup can't fire against a live host —
    a single duplicate delivery reached the two-strike breaker and aborted the
    session. fix: harden post-compaction continuation guard (supersedes #61) #62 recognizes re-deliveries by the absence of message activity
    instead.
  • message.updated never carries parts (they come on message.part.updated,
    which the plugin doesn't observe), so the messageHasToolCall reset branch was
    inert; the breaker now resets on output-token progress, which a tool turn also
    produces.
  • A [goal:complete] on the turn a compaction retains as its source was being
    suppressed; fix: harden post-compaction continuation guard (supersedes #61) #62 honors it.

Superseding here rather than pushing to your branch so the changelog credit and
the SDK-shape context land together. Closing in favor of #62.

@willytop8 willytop8 closed this Aug 22, 2026
willytop8 added a commit that referenced this pull request Aug 22, 2026
fix: harden post-compaction continuation guard (supersedes #61)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants