Repository navigation
Description
Describe the enhancement you want to request
Plugins can register tools (tool.transform), observe tool calls (tool.execute.before/after), and even take over how a running compaction produces its summary (session.compaction), but there is no hook where a plugin can decide that a compaction should happen before the next provider request streams.
Today the engine starts a compaction on engine-owned triggers only: manual (user), auto (threshold check before each step), and overflow (provider rejects the request as context overflow, packages/core/src/session/compaction.ts:218). If none of those fire, the turn proceeds; a plugin has no way to say "preempt this turn, compact first."
Why it belongs in OpenCode:
- It fills the last ungated spot in the loop. OpenCode already has decision-returning hooks of exactly this shape in three places:
session.promptgates admission,tool.execute.beforegates a call,session.retrygates continuation after failure. Between "request built" and "request sent" there is only a rewrite-only hook (session.model.request), no decision hook. - It enables a real use case while keeping policy out of core: plugin-proposed compaction (for example a tool the model calls to suggest compaction, evaluated at the next step boundary with the plugin's own cooldown). Core keeps only the generic trigger.
- The hook is reflect-only on the request — edits make no sense at that point, since the request is rebuilt after a compaction and
session.contextstays the place for rewrites. An approved decision simply entersSessionCompaction.compactlike the other triggers; the only schema change is widening the compactionreasonfrom three literals to an open string so plugins can report their own reason (e.g."plugin").
Proposed interface, in the style of the existing session hooks:
// fired once per step, after the request is built and before the provider streams
interface SessionStep {
sessionID: Session.ID
agent: Agent.ID
step: number // resets to 1 when a new inbox item delivers
model: Model.Ref
request: { // reflect-only snapshot
readonly system: readonly SystemPart[]
readonly messages: readonly Message[]
readonly tools: Readonly<Record<string, { description: string; input: JsonSchema }>>
}
entries: readonly SessionMessage.Info[] // projected history, oldest first
result?: { compact: boolean; reason?: string } // request compaction at this boundary
}I have this implemented and tested locally: the engine trigger in the session runner, the hook interface in packages/plugin, and a working suggest-compact plugin on top of it (tool registered via tool.transform, evaluated at the step boundary, requests compaction; verified end-to-end with requested → evaluated → compaction server logs plus the session-runner test suite).
Happy to push a branch with a small focused PR (the hook, and the plugin as a consumer) once there is a go-ahead on the design.
Activity
opencode-agent commentedon Oct 18, 2026
Thanks for the detailed proposal and for already prototyping it!
To restate it: you're asking for a per-step plugin hook that runs after the provider request is built and before it streams. It would give plugins a read-only view of the request and history, and let them ask for a compaction at that step boundary. The compaction
reasonwould become an open string so plugins can report their own reason. Right now only the engine starts a compaction (manual, auto threshold, overflow), and the existing hooks can only rewrite the request or shape a compaction that is already running. So a plugin can't build this today without a hook like the one you describe.Maintainers will look at the design and reply about whether to go ahead with a PR. These issues are related but ask for different things:
session.compactionhook can only return a summary stringexperimental.stoppingcontinuation hook, another decision hook in the session loop