It was a Tuesday morning. My OpenCode session had 378,755 tokens of context — weeks of trading-bot work. I hit /compact. The model choked: 3 output tokens, empty summary, finish: length. The compaction had failed, and the memory plugin I was using stored everything inside that same compaction call. Result: total amnesia. Zero memories. The session was effectively lobotomized.
That failure taught me the one design rule that matters for agent memory: never store memories only inside the compaction call. Save incrementally, while you work — so a failed compaction costs you a summary, not your past.
So I built opencode-mempalace-persistence (v2.1.0, MIT) on top of MemPalace: every conversation is auto-saved to a local vector palace — zero cloud — and injected back into future prompts, across compactions and across sessions.
How it works
-
Auto-inject recall — identity + relevant memories land in every prompt via
experimental.chat.messages.transform. Zero model discipline required. - AI checkpoints — every 15 messages the model files topics, decisions and quotes via MemPalace MCP tools (diary + knowledge graph). The hook decides when, the model decides what — same pattern as the official Claude Code hooks.
- Pre-compaction emergency save — before every compaction, everything essential is filed first, and core memory is re-attached to the summary so it survives.
- Exit save — quitting (even Ctrl+C) mines the last turns synchronously.
-
Per-project wings —
bot-apisessions never leak intomobile-appmemories. -
Backfill —
OPENCODE_MEMPALACE_BACKFILL=1mines your full history once (I backfilled 9,448 messages / 1,735 drawers from a dead session). - Bundled recall skill — question-driven search-before-answer protocol, adapted from the official MemPalace skill. Search results are quoted verbatim, never paraphrased.
Install (3 steps)
1. Install MemPalace:
uv tool install "mempalace>=3.3.5"
mempalace init ~/opencode-memory
2. Plugin + MCP server in ~/.config/opencode/opencode.json:
{
"plugin": ["opencode-mempalace-persistence"],
"mcp": {
"mempalace": {
"type": "local",
"command": ["mempalace-mcp"],
"enabled": true
}
}
}
3. Identity in ~/.mempalace/identity.txt (two lines about you), restart OpenCode. Done — from here it saves on its own: every 15 messages, on idle, on exit, before every compaction.
Why MemPalace and not summaries?
Summarizers distill and discard. MemPalace stores the verbatim transcript plus a knowledge graph with time-valid facts (kg_supersede/kg_invalidate when the world changes). When a new session asks "why does auth use Postgres instead of Redis?", it gets the original decision with its reasoning — including the part where the engineer who decided it is on holiday.
Links
- GitHub: https://github.com/geco/opencode-mempalace-persistence
- npm: https://www.npmjs.com/package/opencode-mempalace-persistence
- MemPalace: https://github.com/MemPalace/mempalace
If you use OpenCode and compaction anxiety keeps you up at night, give it a try — issues and PRs welcome. What memory setup are you running? I'm genuinely curious what survives in other people's stacks.

Top comments (3)
The design choice that stands out is saving incrementally instead of only inside the compaction call - that's the actual fix, not a bigger context window or a smarter summarizer. A much smaller version of the same idea: this desk's own action ledgers are plain append-only jsonl, so a crash mid-run can only ever truncate the tail, never corrupt what's already written. One question on kg_supersede - if a session dies mid-write, before the supersede resolves, does the stale fact just sit there until the next backfill catches it, or is there a check on read too?
Great question — supersede is atomic at the shared boundary (single SQLite transaction), so a mid-write crash rolls back to the old fact: stale but present, never half-written. Reads are validity-window only, no liveness check — the stale fact reads as current until the model revisits it. And you're right to suspect the backfill: it mines transcripts into drawers, KG facts only come from live MCP calls, so a crashed supersede waits for the next model touch, not the next backfill. I should document that gap — thanks for spotting it.
Small follow-up: I documented this properly in v2.7.0 — new "Durability notes (drawers vs KG)" section (github.com/geco/opencode-mempalace...) in the README. Your question is literally what prompted it.