对话分叉以后,原文按分支过滤很容易,摘要和事实却还记着被放弃的那条线。给 Mem0 只喂当前分支的原文,泄漏率从 60.8% 只降到 50.0%。
用户说「我下个月搬去成都」,过了一会儿回头把这条消息改成「搬去杭州」。聊天界面上成都那条已经看不到了,它变成了杭州的兄弟分支。可是那几分钟里,记忆系统已经从它提取出一条事实:用户要搬去成都。之后问「我要搬去哪」,模型很可能回答「杭州,你之前也提过成都」。
重新生成回复、编辑历史消息、Agent 从检查点分叉,都会产生这种情况。对话历史其实是一棵树,而大部分记忆系统按日志在存。
01过滤原文为什么不够
原始消息的问题好解决:从当前节点往根走,路径上的就是当前分支,其余的都不给模型看。难的是派生出来的东西——摘要、事实、画像、关系图的边。它们是在旧分支还是「当前」的时候写进库的,分支被放弃以后没有人回头去改。
一个常见的补救是给记忆打标签:branch_id、lineage_id,分支被放弃时标成 inactive。问题是标签会过时:一段共享前缀的摘要,会被打上写它那一刻的当前分支的标签,用户换到另一条分支以后,这段本该共享的摘要就看不到了。标签记录的是「写的时候在哪」,不是「它从哪来」。
02只看来源
后来定下的规则只有一条:一条派生记忆,只有它的全部来源事件都在当前锚点的祖先路径上时才可见。有一条来源在兄弟分支上,整条就隐藏。切回旧分支时只移动锚点,不标记、不删除、不改写,旧分支的记忆自然就回来了,因为它们从来没被动过。没有锚点的时候什么都不返回,不默认放行整棵树。
我把这部分从 Autarkia 里拆出来,整理成一个零依赖的 Python 库 branchmem,和原实现做了差分对照:400 棵随机打乱到达顺序的树,约 10.7 万次可见性判定结果完全一致。
03基准怎么搭
要说明这件事有多严重,得有一个能复现的基准。branchbench 是 30 个合成的中文分支对话、98 道题,五种分叉方式各 6 个场景:编辑用户消息、重新生成、从检查点分叉、切回旧分支、分支内改口。题分两类:leak 题检查回答里有没有兄弟分支的说法,retain 题检查该保留的有没有被误挡。
- 分开发集(10 个场景,可以拿来调提示词)和测试集(20 个场景、69 道题),测试集冻结后每个文件记 SHA-256,有一个对不上就拒绝开跑。
- 校验器检查每道题真的在测它声称要测的东西:禁止词只出现在锚点路径之外、且在提问之前到达,答案词在路径上,每个场景都真的有分叉。
- 评分只看关键词,不用 LLM 判官。数字粗一点,但每一格都能从保存的原始回答重新算出来。
- 每个系统都按到达顺序收到全部消息(包括被放弃的分支),再在锚点上用同一个模型、同一条提示词、温度 0 回答,跑 3 次。
04结果
中性提示词下,leak 题的泄漏率:整段摘要 40.2%,Mem0 + 原文按分支过滤 50.0%,全量历史 52.0%,Mem0 开源版 60.8%。branchmem 是 5.9%,和「只给当前分支原文」的上界一样,这也说明现有场景偏短,还没体现派生记忆的必要性。正确率上,基线在 70–80%,branchmem 95.7%。所有系统的 retain 题都是 100%,按分支过滤没有挡掉该留的东西。
泄漏大多不是答错,而是答对了顺带一句:「你现在预算 4000,之前也提过 3000 和 10000。」直接答成兄弟分支说法的只占 3–10%。
最难的是切回旧分支。四个基线在这里的泄漏率是 79–96%。模型不把两条分支当成冲突,而是并在一起:「你对花生和虾都过敏。」用户其实只在一条线上说过花生,在另一条线上说过虾。
给 Mem0 只喂当前分支的原文,泄漏率从 60.8% 降到 50.0%,只挡住 18%。同样的过滤放在全量历史上,挡住 89%。
这是整组实验里我最想让人看到的一行。差距来自 Mem0 自己的记忆条目:兄弟分支的事实在 add 的时候已经被写进去了,原文过滤管不到它们。对任何「先提取、后检索」的记忆系统都一样——只要派生记录不带来源,就没法在读的时候判断它属于哪条线。
05提示词能不能补救
一个自然的反驳是「让模型以最新说法为准就行」。我在跑测试集之前写定了一条冲突处理提示词,没有调过:「如果信息之间有冲突,以用户当前这段对话的最新情况为准,只给出一个答案。」它确实有用,基线泄漏率降到 21–42%,但没有消失,切回旧分支仍有 46–83%。而且挑错的变多了:全量历史「答成兄弟分支」从 2.9% 升到 13.7%。模型更愿意只给一个答案,但它不知道哪条才是当前分支——从到达顺序看,被放弃的那条可能更新。
06跑完才发现的问题
测试集跑完之后,我发现有 2 道 leak 题的禁止词就在问题里,比如「我需要上夜班吗?」禁止词是「夜班」。正确的否定回答也会重复这个词,被判成泄漏。这 2 道题在所有系统上几乎都被判泄漏,上界和 branchmem 的 5.9% 就是这么来的。
测试集已经冻结,我没有改题,主表保留这两道。另附一张去掉它们的敏感性分析:branchmem 和上界降到 0–1%,基线降 2–4 个百分点,结论不变。校验器当时只检查禁止词在不在兄弟分支上,没查它会不会出现在问题里,这是它漏掉的一类。
07边界
- 场景是合成的中文对话,leak 题只有 34 道,每种分叉方式只有 5–8 道,单个格子波动很大。
- 只用了一个回答模型(deepseek-v4-flash),评分只看关键词。
- 真实部署里的数字(约 2.3 万条事件、约 1 万条被丢弃分支的内容被挡下、兄弟线泄漏 0)只是聚合结果,数据不公开。
- 还没做的:记忆的作废与替换(supersede / retract)也要变成带闸门的派生记录。
代码、场景、校验器和结果表都在仓库里。