PROJECT · 02

branchmem

树形对话里派生记忆的分支可见性

在 GitHub 上看 →开源 · 研究进行中
Fig. 1
BRANCH ABRANCH B编辑 →e1 养了只猫 Mochie2 记住了e3 搬去成都e4 看看高新区e5 搬去杭州e6 看看余杭e7 提醒订航空箱

path: e1 → e2 → e5 → e6 → e7

可见性判定锚点 = e7
  1. n1养猫,名叫 Mochi
    ← e1· ⊂ path→ 可见
  2. n2下月搬去成都
    ← e3· e3 ∉ path→ 隐藏
  3. n3摘要:养猫 Mochi,下月搬去成都
    ← e1,e2,e3· e3 ∉ path→ 连带隐藏
  4. n4下月搬去杭州
    ← e5· ⊂ path→ 可见
  5. n5待办:给 Mochi 订航空箱
    ← e7,e1· ⊂ path→ 可见
泄漏 0 · 误隐藏 0 · 连带隐藏 1
图 1. 同一棵树,三种读法。用户把 e3「搬去成都」编辑成 e5「搬去杭州」,两条分支各自是当前分支时,记忆系统已经提取了 n1–n5。三种读法给模型的原文都只有当前路径,区别在派生记忆。右侧的对错不看任何一种读法的规则,只看每条记忆的内容依赖哪些事件。(a) 只过滤原文:笔记全部保留,兄弟分支的事实照样进上下文。(b) 按分支标签:这里画的是最朴素的做法,记忆打上写入那一刻的当前分支。提取是异步的,n1 写入时用户还在 A 上,所以只来自共享前缀的 n1 被标成 A,到了 B 上就看不到。分叉时把共享前缀的标签复制给两条分支能修好这个例子,但修不了根上的问题:记忆写入时,当前分支可能已经变了。(c) 按来源:全部来源事件都在锚点的祖先路径上才可见。代价在粒度:n3 把共享前缀和 A 分支写进了同一条摘要,在 B 上整条隐藏,「养猫」这一半也一起看不到。点 e4 切回旧分支,只移动锚点,A 的记忆自然恢复。这张图只演示规则,不是证据。证据是基准里按回答评分的结果:测试集上 branchmem 泄漏率 5.9%,Mem0 加原文过滤 50.0%。

对话和 Agent 的历史不是日志,是树。用户重新生成一次回复、改一条之前的消息,或者 Agent 框架从检查点分叉,旧的那条线就变成了兄弟分支。只看原始消息的话,按当前分支过滤一下就够了;麻烦在长期记忆:摘要、事实、画像、关系图的边,都是在旧分支还是「当前」的时候生成的,分支被放弃以后它们还留在库里。用户把「搬去成都」改成了「搬去杭州」,记忆里还写着成都。

branchmem 的规则只有一条:派生记忆只有当它的全部来源事件都在当前锚点的祖先路径上时才可见。可见性不看 branch_id 或 inactive 这类标签——标签会过时,来源事件 ID 不会。只要有一条来源在兄弟分支上,整条记忆就隐藏。切回旧分支只移动锚点,不标记、不删除、不改写任何东西,旧分支的记忆自然回来;没有锚点时严格模式什么都不返回,绝不默认放行整棵树。

规则简单,难的是规则管不到的地方。一是全局失效和分支冲突:用户说「以后别再提这件事」,这条作废应该只在当前分支生效,还是对整棵树生效?作废和被作废的记忆落在不同分支上时,以谁为准?这部分还没做完。二是生产里的静默失效:来源事件缺了、旧数据没有拓扑、请求里没带锚点,任何一环没接上,规则都不会报错,只会安静地多放或多挡。所以严格模式下缺信息就一律不放行,兼容模式放行旧数据,但每一条都计数、单独报告。

配套的 branchbench 有 30 个合成中文对话、98 道题,覆盖编辑用户消息、重新生成、从检查点分叉、切回旧分支、分支内改口五种分叉方式。测试集 20 个场景、69 道题,冻结后用 SHA-256 锁住,正式结果只报测试集;评分只看关键词,不用 LLM 判官,可以复现。同一个回答模型、同一条提示词、温度 0、跑 3 次:全量历史泄漏 52.0%,整段摘要 40.2%,Mem0 开源版 60.8%,branchmem 5.9%,和「只给当前分支原文」的上界持平,也说明现有场景偏短,还没体现派生记忆的必要性;正确率 95.7%,基线在 70–80%。

最能说明问题的是 Mem0 那一组:把喂给它的原文换成只含当前分支的版本,泄漏率只从 60.8% 降到 50.0%,因为兄弟分支的事实早就写进了 Mem0 自己的记忆条目。同样的过滤放在全量历史上能挡住 89%。光过滤原文不够,派生出来的东西也得能追回到来源。

代码从 Autarkia 的记忆层整理出来,和原实现做过差分对照:400 棵随机打乱到达顺序的树上,约 10.7 万次可见性判定结果完全一致。局限也写在仓库里:场景是合成的,leak 题只有 34 道,只用了一个回答模型;有 2 道题的禁止词出现在问题本身,是跑完才发现的,测试集没有改,另附了去掉它们的敏感性分析。

01技术要点

可见性谓词派生记忆的全部来源事件都在当前锚点的祖先路径上才可见,不依赖任何分支标签
确定性拓扑同一组事件不论到达顺序、重复投递、子节点先到,都建出同一棵树、同一组分段和 ID
严格 / 兼容两种模式严格模式缺锚点即关闭;兼容模式放行缺拓扑的旧数据,但计数并单独报告
核心库零依赖、不调用模型;可选的笔记存储、带闸门的 BM25 / 向量检索和提取流水线
branchbench30 个场景 / 98 道题,五种分叉方式,校验器保证每道题测的是它声称要测的东西,测试集冻结
回答级对照全量历史、整段摘要、Mem0 开源版、Mem0 + 原文过滤、branchmem、上界,另有事先写定的冲突提示词稳健性检查

02技术栈

PythonpytestBM25Mem0LLM