Repository navigation
Description
Feature hasn't been suggested before.
- I have verified this feature I'm about to request hasn't been suggested before. Related requests [FEATURE]: Plugin API: hook before the host stores synthetic text #57214 (pre-storage synthetic text filtering), [FEATURE]: Expose message-free session resume to V2 plugins #58390 (plugin-initiated resume) and server: add option to disable session auto-resume on service start #56032 (disabling auto-resume) cover adjacent parts of this surface; none provides an initialization point before context assembly on the host-initiated resume path.
Describe the enhancement you want to request
Problem
When the OpenCode service restarts and automatically resumes a suspended session, the resumed session's first model request bypasses every plugin hook that runs on the normal prompt path. Plugins that build per-agent state at admission time start the resumed turn with empty state and no supported way to rebuild it.
The path, on current dev (24b5ed8bcc):
prepareResumeinpackages/core/src/session/execution/restart.tspublishesSessionEvent.Synthetic("The server restarted while you were working…") and gatesexecution.resume. This path never reachesSessionPrompt.prepare, sosession.prompt— which only fires inpackages/core/src/session/prompt.ts— does not run.- The resumed turn's first model request still triggers
session.context, but by then the plugin has had no initialization point.
There is a second, structural gap: session.context receives a mutable array of system parts with no provenance. SessionModelRequest.baseTranscript assembles the system array as [agent.system ?? host default prompt, initial], and earlier hooks may already have modified or reordered parts. Given [GUARD, DEFAULT, INITIAL], a plugin cannot distinguish "GUARD was added as an independent part" from "the agent's system text was rewritten to GUARD and DEFAULT is separate". A plugin that owns the agent's system text therefore cannot replace exactly its own region while preserving independent parts added by other plugins. Copying the host default template or guessing indices does not solve this.
What this breaks in practice
A plugin that takes over an agent's system prompt via a marker-delimited owned region seeds its state at admission (the session.prompt path). After a service restart:
- The plugin process restarts with empty in-memory state.
- The resumed session's first request arrives via the synthetic/resume path — no
session.prompt, no seed. - At
session.context, the system parts contain no marker, and the plugin cannot prove which part is the native system text it owns. It must fail closed and block the request. - The next ordinary user message re-seeds via admission and everything works — so the observable behavior is "the first request after every restart fails, then the session self-heals."
Fail-closed is correct behavior for a plugin that cannot prove ownership. The missing capability is on the host side.
Proposed API
An awaited primary-request preflight hook: after plugin activation completes and before the per-request agent snapshot is selected, run an awaited hook that exposes sessionID. Plugins establish their owned state there; the existing session.context hook then renders the final text with this request's model and tools.
A candidate wiring point is packages/core/src/session/runner/llm.ts: inside prepareContext, before context.select. Public types would live in packages/plugin/src/{promise,effect}/session.ts.
The hook contract:
- Awaited: the request does not proceed until the hook settles, so asynchronous initialization (config loads, snapshot capture) completes deterministically.
- Runs on every primary-request path: ordinary prompts, restart resume, and any future synthetic-driven execution. Uniformity is the point.
- Read-only beyond
sessionID: the hook establishes plugin-side state; it does not mutate the request. - Idempotency belongs to the plugin: the hook may fire more than once per session; plugins key state by session/agent and make initialization idempotent.
Independently, the provenance gap could be addressed by tagging system parts with their origin (agent config, host default, or the plugin/hook that added them). That would let ownership-aware plugins do precise replacement even without a preflight hook, but the preflight hook is the more general fix.
Related issues
- [FEATURE]: Plugin API: hook before the host stores synthetic text #57214 requests a hook before the host stores synthetic text. It shares the root cause (synthetic/resume paths bypass
session.prompt) but targets pre-storage filtering of message text. Asession.synthetichook does not give plugins a point to rebuild system-prompt ownership before context rendering. - [FEATURE]: Expose message-free session resume to V2 plugins #58390 asks to expose
Session.resumeso plugins can initiate a message-free resume. This issue concerns the host-initiated resume path that already exists. - server: add option to disable session auto-resume on service start #56032 asks for an option to disable auto-resume on service start — the same path, from the user-experience side.
Reproduction evidence
Observed on OpenCode v2.0.26 and v2.1.2 (Linux and Windows) with a plugin implementing marker-based system-prompt ownership. The plugin's live regression harness drives a real opencode serve process with a mock provider: two sessions share one managed agent, the service is SIGKILLed mid-conversation and restarted. The resume request records zero admissions and fails at context with no marker present; a subsequent ordinary user message re-seeds and succeeds. With a non-empty unique agents.<id>.system configured, the plugin can reseed from its pre-takeover snapshot and the resume succeeds — confirming the gap is exactly the missing initialization point, not the resume mechanics.
Activity
opencode-agent commentedon Oct 24, 2026
Thanks for the detailed write-up and the regression evidence!
To restate the request: you're asking for an awaited plugin hook that runs before context assembly on every primary request path, including the restart resume that skips
session.prompt. That would let plugins rebuild their state beforesession.context. Separately, you suggest tagging system parts insession.contextwith where they came from (agent config, host default, or the plugin that added them), so plugins can safely replace only their own part.I checked the current code, and the restart resume path (
prepareResume→SessionEvent.Synthetic) does bypassSessionPrompt.prepareas you describe. A plugin can't work around this today, so it needs a change to the plugin API. Maintainers will consider it.Related issues:
session.prompt)WhiteGiverMa commentedon Oct 24, 2026
This report is generated by AI, but the request was discovered from a strange production bug in my plugin. Doc included, and AI too, contact me immediately if any problem.