PROJECT · 02
branchmem
Branch-aware visibility for derived memory in tree-shaped histories
path: e1 → e2 → e5 → e6 → e7
- n1has a cat, Mochi← e1· ⊂ path→ visible
- n2moving to Chengdu← e3· e3 ∉ path→ hidden
- n3summary: cat Mochi, moving to Chengdu← e1,e2,e3· e3 ∉ path→ COLLATERAL
- n4moving to Hangzhou← e5· ⊂ path→ visible
- n5todo: carrier for Mochi← e7,e1· ⊂ path→ visible
Chat and agent histories are trees, not logs. A user regenerates a reply, edits an earlier message, or an agent framework forks from a checkpoint, and the old continuation becomes a sibling branch. For raw messages, filtering to the current branch is enough. Long-term memory is the problem: summaries, facts, profile entries, and graph edges were derived while the old branch was still current, and they stay in the store after it is abandoned. The user edited “moving to Chengdu” into “moving to Hangzhou”; memory still says Chengdu.
branchmem has one rule: a derived memory is visible only when every one of its source events lies on the ancestor path of the current anchor. Visibility never depends on labels like branch_id or inactive — labels go stale, source event IDs do not. One source on a sibling hides the whole note. Switching back only moves the anchor; nothing is marked, deleted, or rewritten, so the old branch’s memories simply come back. Without an anchor, strict mode returns nothing rather than defaulting to the whole tree.
The rule is simple; the hard parts are where it doesn’t reach. First, global invalidation and branch conflicts: when the user says “don’t bring this up again,” should that retraction apply to the current branch only, or to the whole tree? When a retraction and the memory it retracts sit on different branches, which one wins? This part is not done yet. Second, silent failure in production: a missing source event, legacy data without topology, a request without an anchor — if any link breaks, the rule raises no error; it just quietly lets too much through or hides too much. So strict mode fails closed whenever information is missing, and compat mode lets legacy data through but counts and reports every case separately.
The companion branchbench has 30 synthetic Chinese dialogues and 98 probes across five branch operations: editing a user message, regenerating, forking from a checkpoint, switching back, and correcting within a branch. The test split (20 scenarios, 69 probes) is frozen with SHA-256 hashes, and reported results use it only; scoring is keyword-based with no LLM judge, so it is reproducible. Same answer model, same prompt, temperature 0, three runs: full history leaks on 52.0% of leak probes, a rolling summary 40.2%, Mem0 OSS 60.8%, and branchmem 5.9% — equal to the “current-branch raw only” upper bound — which also shows the current scenarios are short and do not yet demonstrate why derived memory is needed. Its correct rate is 95.7%, against 70–80% for the baselines.
The Mem0 pair makes the point. Feeding it only the current branch’s raw messages lowers its leak rate from 60.8% to just 50.0%, because the sibling-branch facts are already in Mem0’s own memories. The same filter on full history blocks 89%. Filtering raw messages is not enough; whatever is derived from them has to trace back to its sources.
The code was extracted from Autarkia’s memory layer and checked differentially against the original: on 400 trees with shuffled arrival, about 107k visibility decisions matched exactly. The limits are stated in the repository: the scenarios are synthetic, there are only 34 leak probes, and a single answer model was used. Two probes had the forbidden word in the question itself, found after the run; the frozen set was left unchanged and a sensitivity analysis without them is reported alongside.