All blogs
Essay

When Two Branches Remember Differently

Why Memoir's merge story matters, what we have today, and what we know we still have to build.

The question I keep getting

Every AI memory system has to solve a handful of hard problems — drift, retrieval quality, privacy, scale. This post is about one of the sharpest, and the one that came up frequently in this discussion: "updatable memory is dangerous unless you're being very careful." The questioner wasn't wrong — they'd built two or three of these systems themselves and knew exactly where the cliffs are.

That concern is the cleanest justification for why Memoir borrows from Git in the first place. Memory should be branchable, blame-able, and revertible. But the moment you say "memory works like Git," people start projecting Git's actual merge semantics onto it — and that's where the analogy strains.

This post is my attempt to write down, in one place, how merging works in Memoir today, where the design is honestly thin, and where we're going next. I'll redirect future questions here.

Core value, restated

Memoir treats AI memory as a versioned store with semantic paths. The Prolly tree underneath is content-addressed and append-only — every "update" is a commit, never an overwrite. That gets you three things ordinary memory stores don't have:

  • Time travel. memoir checkout <commit> restores an old view of the entire memory state.
  • Audit. memoir blame <path> traces every value back to the session that wrote it.
  • Isolation. Branches let an experimental session's memories live separately from your "primary" until you say so.

None of this matters if merge is a black box. So merge has to be a first-class design surface, not an afterthought.

Where the Git analogy holds

Memoir mirrors Git on the parts that translate cleanly:

  • Storage is append-only. No silent destruction, ever.
  • Branches are cheap. Spin one up for a feature attempt, throw it away if it goes sideways.
  • Commits are atomic. A turn either lands as a commit or doesn't.
  • primary (typically main) holds canonical state. Non-primary branches stay provisional until you explicitly merge.

If you've used Git, the mental model carries.

Where the Git analogy breaks

Git merges text. Conflicts are line-based and structural. The merge algorithm is a three-way diff; if line 42 was edited differently in two branches, you get a <<<<<<< block and the human picks. The thing being merged — source code — is mechanical, and the conflict surface is well-defined: same file, same line, different content.

Memoir merges meaning. And that's the core of the problem.

The "same path" conflict (mechanical, easy)

branch A:  preferences.coding.style = "tabs, 2-space indents"
branch B:  preferences.coding.style = "spaces, 4-space indents"

This is the Git equivalent of "two edits to line 42." Memoir detects it, surfaces it in the merge UI, and you pick one — or edit a third value before committing. Solved.

The "semantically contradictory" conflict (hard, unsolved)

branch A:  preferences.editor.python = "vim"
branch B:  preferences.tools.editor   = "emacs"

Two writes at different paths. Git would happily merge both — no line conflict, no overlap. But the memories contradict each other in meaning. An agent loading the merged "primary" sees both values, and downstream behavior becomes nondeterministic depending on retrieval order.

Git has no language for this case because text doesn't have intent. Semantic paths do — and that's exactly the thing that makes Memoir more useful than a pile of vector chunks. But it's also the thing that makes "merge" much harder than three-way diff. Right now, Memoir doesn't catch these. It's the single biggest gap in the merge story, and pretending otherwise would be dishonest.

The fix isn't an algorithm — it's an LLM-mediated check at merge time, surfacing semantic conflicts as suggestions, never auto-resolving. That's a roadmap item, not a shipped feature.

Bulk triage doesn't scale

The current UI lets you cherry-pick, edit, or ignore individual memories during merge. That's great at five memories. At two hundred — a feature branch that ran for a week, with auto-capture firing on every turn — the developer becomes the bottleneck. We need grouped diffs, presets like "auto-accept additions, manual on overwrites," and stale-memory hints. None of that ships today.

The "primary = main" assumption

Memoir's merge story assumes a single canonical branch. That fits coding agents perfectly, since they piggyback on the repo's own main. It does not generalize cleanly to research assistants, planners, or longitudinal personal memory, where there isn't an obvious anchor branch. We'll need a richer model for non-coding use cases. Better to scope it openly today than let people extrapolate and bounce off.

The honest admission

The merge design needs work. Specifically:

  • No cross-path semantic conflict detection. Same-path-different-value is detected; semantically contradictory writes at different paths are not.
  • No bulk-triage UX. The picker is a great primitive at small scale and a wall at large scale.
  • No automated stale-memory hints. A memory captured eight days ago on a feature branch may already be irrelevant, and the merge UI shouldn't make you read it to find that out.
  • No formal definition of "conflict" beyond same-path-different-value. People will project Git's textual semantics onto Memoir until we publish ours.

These aren't theoretical complaints — they're the things people hit when they actually run Memoir for a few weeks.

What's next: an LLM-mediated, clarification-first merge

The near-term improvement to merging isn't a bigger algorithm — it's borrowing a pattern that's already working well in coding agents: when in doubt, ask.

Claude Code's auto-mode is the reference. The agent runs autonomously on clear, low-risk work, but the moment it hits ambiguity or an intent decision — Should I commit this? Which directory does this file belong in? Which of these two interpretations did you mean? — it stops and asks. That single pattern, "act on the clear cases, clarify on the murky ones," is what makes auto-mode safe in practice.

Memoir's merge surface has exactly the same shape:

  • Clear cases — additive memories, well-separated paths, no semantic overlap — should auto-merge. No prompts. No friction.
  • Murky cases — same-path-different-value, semantically contradictory writes at different paths, near-duplicates, stale-looking entries — should never be auto-resolved. They should be batched into a short review queue and surfaced with concrete options: keep A, keep B, edit, ignore, defer until next session.
  • User feedback closes the loop. Every clarification ("on this kind of conflict, prefer the more recent value," "always merge additions to preferences.*," "always ask on profile.*") becomes a policy hint that carries forward. Over time: fewer prompts, more confident auto-merges, less friction.

What this looks like end-to-end:

  1. Auto-capture writes to a feature branch as you work, just like today.
  2. At merge time, an LLM-driven pass classifies each pending memory as trivial, ambiguous, or contradictory.
  3. Trivial entries auto-merge silently.
  4. Ambiguous and contradictory entries surface in the merge UI with proposed resolutions; where intent isn't recoverable from context, the LLM proactively reaches out to the user before doing anything — same instinct as Claude Code stopping mid-task to ask which interpretation you meant.
  5. Your decisions feed a per-store policy that quietly raises the auto-merge confidence threshold for similar cases later.

Crucially, this is not a black-box autopilot. The LLM never decides for you on contested ground — it routes the easy stuff through, routes the hard stuff to you with enough context to decide quickly, and learns the shape of your judgment as it goes. Same primitive Claude Code's auto-mode uses, applied to memory merging.

Longer-term, more ambitious work — what I've been sketching as dream mode: background consolidation, dedupe, and taxonomy reorganization on a dedicated branch — builds on top of this. But merge clarity has to come first; without it, every later automation inherits the same ambiguity problem.


Where this leaves us

Memoir's bet is that memory operations should look like source-control operations — not because Git is sacred, but because Git solved the audit, isolation, and revert problems for code thirty years ago, and memory has the same shape of need.

The thesis holds up on the easy cases. It needs more work on the hard ones — semantic conflicts, scale, and reorganization — and we're being public about that rather than papering over it.

If you've built memory systems and have war stories about merges gone wrong, please drop them in the discussion or open an issue. Concrete failure patterns are more useful than principles right now, and the design will be better for it.

Try Memoir's branched memory

Merge is where a memory system stops being a notebook and starts being infrastructure.