Auto-persistence hook for opencode — forces the agent to write memory to both MCP Serena and a project-local
MEMORY.mdat the end of every substantive turn and before every context compaction.
opencode agents, like any LLM-backed assistant, forget everything between
sessions unless memory is explicitly persisted. Manual write_memory calls
are easy to skip, especially on long sessions where context compaction
silently drops earlier reasoning.
This plugin enforces a dual-write protocol:
- MCP Serena (semantic memory, shared across projects via Serena itself)
MEMORY.mdlocal to the project (plain-text, git-trackable audit log)
Neither channel is optional. The hook makes it impossible for the agent to wind down a substantive turn without persisting to both.
This is a functional port of the memory-guardian.sh Stop-hook pattern from
Claude Code. Claude Code can return decision:"block" from a hook to
re-prompt the model; opencode has no equivalent block mechanism, but the
plugin SDK exposes client.session.prompt() inside the session.idle event,
which can inject a follow-up prompt into the same session. This plugin reuses
that technique, adapted for auto-persistence instead of iteration.
-
session.idle— fires whenever the agent finishes responding. If the turn had substantive work (write/edit/apply_patch tool calls, emitted patch parts, or response ≥ 1500 characters), the plugin injects an obligatory dual-write prompt viaclient.session.prompt(). The agent then executeswrite_memoryon Serena and editsMEMORY.md, and must finish the response with the literal tag<memory-persisted/>. -
experimental.session.compacting— fires before the session compacts its context. The plugin appends a short reminder tooutput.contextwarning that any unpersisted memory is about to be lost and dual-write must happen now. Non-destructive: preserves the default compaction prompt.
Per-project state in .opencode-auto-memory.state.json:
{
"persistedSessions": {
"<sessionID>": { "messageID": "<messageID>", "at": "2026-04-14T19:00:00Z" }
}
}When the agent's last message contains <memory-persisted/>, the plugin does
not trust the marker alone. It first verifies concrete evidence that the same
turn:
- executed a Serena memory tool (
write_memoryoredit_memory), and - actually touched
MEMORY.mdvia a file-edit tool or emittedpatchpart.
Only then does it mark the session as persisted and stop re-injecting.
Subsequent session.idle events in the same session become no-ops.
The plugin re-prompts only when either condition holds:
- The session history contains any tool call whose name (lowercased) is in
{write, edit, apply_patch, patch, multiedit, notebookedit}. - The session history contains any emitted
patchpart. - The last assistant response is ≥ 1500 characters.
Short, read-only turns are ignored — no noise, no unnecessary re-prompts.
The plugin now rejects <memory-persisted/> if it cannot prove both channels
were updated in the same persistence turn.
Evidence accepted for Serena:
- tool part named
write_memoryoredit_memory - MCP-style tool name variants such as
serena__write_memory
Evidence accepted for local memory:
patchpart referencingMEMORY.md- write/edit/apply_patch-like tool input that mentions
MEMORY.md
If one side is missing, the plugin re-prompts with a specific error telling the agent which channel was not detected.
Requires opencode ≥ 1.4.3. MCP Serena is expected to be configured in your
opencode.json if you want the Serena channel to work.
Keeps the plugin versioned in one place; git pull updates your live
install automatically.
git clone https://github.com/daniloaguiarbr/opencode-auto-memory.git \
~/.local/share/opencode-auto-memory
mkdir -p ~/.config/opencode/plugin
ln -s ~/.local/share/opencode-auto-memory/plugin/auto-memory.ts \
~/.config/opencode/plugin/auto-memory.tsgit clone https://github.com/daniloaguiarbr/opencode-auto-memory.git
cp opencode-auto-memory/plugin/auto-memory.ts ~/.config/opencode/plugin/The plugin imports @opencode-ai/plugin. opencode installs this SDK
automatically in ~/.config/opencode/node_modules/ the first time you
enable any plugin, so you normally don't need to install it manually.
If it is missing:
cd ~/.config/opencode && npm install @opencode-ai/pluginAdd .opencode-auto-memory.state.json to every project's .gitignore:
echo '.opencode-auto-memory.state.json' >> .gitignoreIn your ~/.config/opencode/opencode.json:
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"serena": {
"type": "local",
"command": [
"uvx", "--from", "git+https://github.com/oraios/serena",
"serena", "start-mcp-server",
"--context=claude-code",
"--enable-web-dashboard", "False"
],
"enabled": true
}
}
}Once installed, the plugin runs automatically. There is nothing to invoke.
Start a session, make some edits, finish a turn — you'll see the agent
respond with memory persistence instructions, execute write_memory and
edit MEMORY.md, and close with <memory-persisted/>. From that point on,
the session is marked persisted and the plugin stays quiet unless you do
more substantive work.
The plugin logs to opencode's log service under service: "opencode-auto-memory".
Tail the opencode log file to watch it work.
Edit plugin/auto-memory.ts:
const MIN_RESPONSE_CHARS = 1500 // bump up to reduce noise
const WRITE_TOOLS = new Set([...]) // add/remove tools hereThe prompt injected into the session is defined in
DUAL_WRITE_INSTRUCTIONS near the top of plugin/auto-memory.ts. It is
in Brazilian Portuguese by default (matching the maintainer's agent
workflow). Translate to your working language — the structure
(4 categories, checklist, completion tag) is what matters.
Just delete the symlink and copy the file:
rm ~/.config/opencode/plugin/auto-memory.ts
cp ~/.local/share/opencode-auto-memory/plugin/auto-memory.ts \
~/.config/opencode/plugin/| Concern | Claude Code memory-guardian.sh |
opencode-auto-memory |
|---|---|---|
| Language | Bash + Python | TypeScript (Bun runtime) |
| Trigger | Stop hook, stdin JSON |
session.idle + experimental.session.compacting |
| Re-prompt mechanism | {decision:"block", reason:...} via stdout |
client.session.prompt() from plugin hook |
| Anti-loop guard | stop_hook_active flag in payload |
.state.json + <memory-persisted/> tag |
| Substantive check | ≥ 3000-byte transcript | ≥ 1500-char last response OR write-tool call |
| Dual-write channels | Serena + MEMORY.md | Serena + MEMORY.md |
| Language of prompt | Portuguese (pt-BR) | Portuguese (pt-BR) by default, trivially translatable |
Plugin doesn't seem to fire. Verify the symlink exists and the target
file is readable: ls -la ~/.config/opencode/plugin/auto-memory.ts. Restart
opencode after (re)installing.
Plugin loads but never re-prompts. Ensure your installed version uses the
current SDK request shape: client.session.messages({ path: { id } }),
client.session.prompt({ path: { id }, body: { parts } }), and
client.app.log({ body: ... }). Older variants that called session.get(),
session.send(), or app.log() without body can silently no-op against
newer SDK builds.
Agent ignores the re-prompt. Check that Serena MCP is actually running (inspect your opencode logs). Also check the state file — if the session was already marked persisted, delete the entry to force a re-run.
Too many re-prompts on trivial turns. Raise MIN_RESPONSE_CHARS or
trim WRITE_TOOLS to only the tools that matter to you.
Loops forever. The model isn't emitting <memory-persisted/>. Check
your system prompt/context — some models drop the literal tag. You can
adapt the marker to any tag your model reliably emits.
Issues and PRs welcome. Keep the plugin single-file and dependency-light — the whole appeal is drop-in simplicity.
MIT © 2026 Danilo Aguiar
opencode-ralph— sameclient.session.send()technique for iterative Ralph Loop workflows.- serena — the MCP memory server this plugin persists into.
- opencode — the terminal AI coding agent this plugin extends.