V2 removed experimental.chat.system.transform and stops loading a plugin whose
configured path is a file (configured plugin path must be a directory). The old
file://…/index.js entry therefore loaded nothing at all, silently, from 2.x on.
This package now carries a V2 entrypoint (v2.js) and must be loaded by
directory:
{
"plugins": ["file:///home/nguyen/dev/opencode-consistent"]
}It registers ctx.session.hook("context") and rewrites event.system (the
assembled SystemPart[] for the outgoing call) so the head of the prompt is
byte-identical to the session's first turn. Snapshot is one JSON per session under
~/.local/state/opencode/consistent/, so a resumed session — even across an
OpenCode service restart — keeps the head it was stored under.
Options (plugin object form or env):
| Option | Default | Meaning |
|---|---|---|
freeze |
all |
all = env + AGENTS + skills + MCP prose frozen; date = old behaviour, only Today's date pinned |
freezeTools |
true |
drop tools that appear after the snapshot (a late MCP connect would otherwise move the tool block, which sits ahead of the conversation) |
mcpWaitMs |
5000 |
wait for MCP servers to settle before the first snapshot (only the first turn) |
Env fallbacks: OPENCODE_CONSISTENT_FREEZE, OPENCODE_CONSISTENT_MCP_WAIT_MS.
Trade-off, stated plainly: with freeze=all, editing AGENTS.md or installing a
skill mid-session does not reach that session; the next session gets it. That is
the price of a stable prefix, and it is the same choice upstream made in #48777
(they freeze env/instructions/MCP per session).
Every turn writes one line to ~/.local/state/opencode/opencode-consistent.log
with the system digest and a per-part diff (env, agents, skills, mcp,
other), plus tools_added / tools_dropped. When a cache miss happens, that
line names the component that moved instead of leaving it to forensics.
OpenCode rebuilds the system prompt on every turn. In 1.18.27 the <env> block is cwd, worktree, git yes/no, platform, and Today's date: ${new Date().toDateString()}. Prefix caches hash from token 0, so when the calendar rolls the whole session misses.
AGENTS.md, MCP instructions, and the skill list are re-read from disk. If you did not edit them, a restart already sends the same bytes. This plugin does not freeze those, so a mid-session edit still reaches the model (and correctly misses the old prefix).
The other restart miss is MCP tools, which are not in the system string. They sit after the built-in tool schemas. If those servers are still connecting, vLLM hashes builtins only (1,792 tokens = 7×256 blocks) and prefills the rest.
This plugin:
- Pins
Today's dateon the first real turn of asessionIDand rewrites just that field later, including after quit/reopen. - Waits in
chat.messageuntil the same MCP servers areconnected(or have finished failing) soSessionTools.resolveincludes the same tool block.
Upstream: #32622 (date in the cached prefix, closed not-planned), #29672, PR #29949 (move env to the tail, not default as of 1.18.27).
Put it last in the plugin list:
{
"plugin": [
"opencode-consistent"
]
}Optional wait budget (milliseconds). Default 15000. 0 polls once and does not block.
{
"plugin": [
["opencode-consistent", { "mcpWaitMs": 20000 }]
]
}Same value via OPENCODE_CONSISTENT_MCP_WAIT_MS. Restart OpenCode. Plugins load at process start.
Today's date per sessionID, on disk under ~/.local/state/opencode/consistent/.
The list of MCP server names that were connected when that date was first pinned. Later turns wait until those names are connected again.
Title-generation has no date line. Those calls are ignored.
AGENTS.md, cwd, skills, MCP instruction prose. Change them and the next turn is live — and a new prefix hash.
Tool JSON schemas. The plugin cannot inject a missing MCP tool. If a server is still down after mcpWaitMs, or it advertises different tools, the hash still misses after the built-in set.
/compact still rewrites history.
Logging is on by default. Writes ~/.local/state/opencode/opencode-consistent.log with wait ok|timeout and each snapshot / restore.
Each system-transform line has:
sha=current full system digest (12 hex)snap=first-turn digest for thissessionID(pinned on disk)prev=previous turnvs_snap=/vs_last=which buckets moved (env,agents,skills,mcp,other)- per-bucket digests:
env=agents=skills=mcp=other=(-if that bucket was empty)
sha is allowed to move when AGENTS.md, skills, or MCP prose change; the date field stays pinned. vs_snap=mcp on an overnight resume means the MCP instruction block changed, not the calendar.
OPENCODE_CONSISTENT_LOG=0 opencode # disableRestart OpenCode after a plugin edit. Plugins load at process start.
MIT