TLDR: Don't save memory aggressively. Capture it cheaply and commit immediately — flush expensively on demand.
The Setup
Apollo (my personal AI agent, running locally via the Claude CLI) remembers things between sessions through a file-based memory system — markdown files, a routing table, and a RAG (semantic search) index.
The problem: if a session dies mid-work, any learnings from that session die with it.
So I asked Apollo to save memory at the end of every turn.
That was the wrong call.
What I Tried First
Running the full apollo-save skill every turn seemed obvious. Capture everything, always.
Except the full pipeline does a lot: re-audits the whole conversation, deduplicates against existing memory files, writes to disk, and re-runs the RAG reindex. That's expensive. And worse — it floods memory with slop.
Half-formed conclusions from turn 3 get baked in before turn 7 revises them. Every quick status check earns a permanent memory entry. The 80-line MEMORY.md routing cap fills up with noise inside a week.
Then I discovered something I hadn't expected.
I run two Apollo instances simultaneously — one in the terminal, one as an iMessage bridge. They share the same memory files. A full-file save from one clobbers the other mid-write. The concurrency race is REAL, and I had no idea it was coming.
So "save aggressively" failed on three counts: too slow, too noisy, and not safe under concurrent writers.
The Fix That Worked
Separate the capture step from the consolidation step — and make them radically different in cost.
Capture: At the end of any turn that produced something durable, Apollo appends a single rich line to memory/_inbox.md (a cheap staging buffer). One append. No dedup, no reindex, no routing logic. Milliseconds.
Then — this is the part that actually makes it durable — Apollo commits the append immediately.
chore(memory): stage learning to inbox
Writing to disk survives a dead session. But an uncommitted edit dies to any git checkout or reset. In a git-managed repo, the commit IS the durability boundary. Not the file write.
Append-only buffers are also naturally conflict-safe. Both Apollo instances can append to _inbox.md in the same session without clobbering each other. The flush sorts it out later.
Flush: apollo-save (my on-demand or end-of-session skill) reads _inbox.md, promotes each staged line into a proper memory file with full dedup + MEMORY.md routing + RAG reindex, then clears only the promoted lines — only after the writes and reindex succeed. A crash mid-flush cannot lose what was staged. The promotion either completes cleanly or leaves the inbox intact.
Why This Matters to Me
I build systems that run while I sleep. If they forget what they learned, I'm starting over every morning.
The deeper lesson isn't really about memory files. It's about cheap, durable capture vs. expensive, correct consolidation. Those are two different jobs. Conflate them into one heavy per-turn operation and you'll either skip it (too slow) or do it wrong (captured too early).
Keep the ingestion path cheap and safe. Make the promotion path careful and on-demand.
That's the pattern I'll reach for every time now...