opencode-era record. Superseded where moot by the pi migration (ADR-0055). Retained as historical context.
Date: 2026-07-09
Accepted
The session-bootstrap plugin (.opencode/plugins/session-bootstrap.ts) is the
structural enforcement mechanism for the anti-drift rationalization red-flags.
Its primary purpose is to inject the bootstrap text
(.opencode/docs/session-bootstrap.md) into the system prompt on every LLM
call, ensuring the model cannot "forget" to load skills or skip pipeline steps.
The plugin relies on two experimental hooks from @opencode-ai/plugin:
experimental.chat.system.transform— pushes text intooutput.systemon every LLM call (primary enforcement).experimental.session.compacting— pushes text intooutput.contextduring session compaction (persistence across context window resets).
As of SDK v1.17.15, experimental.chat.system.transform is defined in the
Hooks interface but was NOT documented in the upstream OpenCode docs
(plugins.mdx). This created a risk (issue #63) that the hook might be
"silently inert" — present in types but never dispatched at runtime, leaving
the anti-drift enforcement non-functional while tests stayed green.
Runtime dispatch verification: The hook IS dispatched at runtime. In
packages/opencode/src/session/llm/request.ts (anomalyco/opencode, dev
branch), the prepare() function triggers it via the generic plugin dispatch:
yield* input.plugin.trigger(
"experimental.chat.system.transform",
{ sessionID: input.sessionID, model: input.model },
{ system },
)This is called on every LLM request after the system prompt array is assembled
and before messages are sent to the model. The same generic plugin.trigger()
mechanism dispatches all experimental hooks, including the documented
experimental.session.compacting (in packages/opencode/src/session/compaction.ts).
Continue using experimental.chat.system.transform as the primary
system-prompt injection mechanism, rather than falling back to the
instructions array in opencode.json.
Rationale:
- The hook IS dispatched at runtime (confirmed via upstream source research).
- The hook is in the same
Hooksinterface and dispatched via the sameplugin.trigger()mechanism as the documented, workingexperimental.session.compactinghook. - The
instructionsarray only affects the initial system prompt and cannot replicate the compaction-context injection thatexperimental.session.compactingprovides. Migrating toinstructionswould be a functional regression for compaction persistence.
To mitigate the "silently inert" risk, two guards are in place:
- The plugin uses a typed intermediate variable
(
const hooks: Hooks = {...}) sotsc --noEmitfails on invalid hook names via excess property checking. - A type-level assertion test in
tests/Plugin/session-bootstrap.test.tsindependently validates that both hook names are validHookskeys — failingtscif the SDK removes or renames either hook.
- Positive: Both system-prompt injection and compaction-context injection are preserved. The anti-drift enforcement is structural (not model-chosen).
- Positive: The vendored
plugins.mdxnow documentsexperimental.chat.system.transformunder a "System prompt transform hooks" section, citing the SDK type definitions as the source of truth. - Negative: The plugin depends on an experimental hook that may be removed
or renamed in a future SDK version. If this happens,
tsc --noEmitwill fail, making the breakage loud rather than silent. - Fallback: If the hook is removed, migrate the system-prompt injection to
the
instructionsarray inopencode.json. The compaction-context injection would need a separate solution (e.g., a documented hook or a persistence mechanism). - Related documents: Updated
plugins.mdx(vendored docs), type assertion test insession-bootstrap.test.ts,package.json(added@opencode-ai/pluginto root devDependencies).
instructionsarray inopencode.json— Rejected. Only affects the initial system prompt; cannot inject into compaction context. Would lose theexperimental.session.compactingfunctionality.- Wait for the hook to become stable/documented — Rejected. The anti-drift enforcement is needed now; the experimental status is acceptable given the runtime dispatch confirmation and the type-level guard tests.
- Duplicate the bootstrap text in both
instructionsand the plugin — Rejected. DRY violation; the plugin is the single source of truth. Two injection paths would diverge.