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.send() inside the session.idle event,
which is exactly what the Ralph Loop
plugin uses to re-feed a prompt. This plugin reuses the same re-prompt
technique, adapted for auto-persistence instead of iteration.
-
session.idle— fires whenever the agent finishes responding. If the turn had substantive work (write/edit tool calls, or response ≥ 1500 characters), the plugin injects an obligatory dual-write prompt viaclient.session.send(). 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
marks the session as persisted and stops 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, patch, multiedit, notebookedit}. - The last assistant response is ≥ 1500 characters.
Short, read-only turns are ignored — no noise, no unnecessary re-prompts.
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.send() 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.
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.