让四个 Agent 共用一套记忆:写归自己,读跨全库
昨天下午,我在 dsh 里丢了一句话:
codex 那边有个迁移脚本没跑通,你接着弄一下。
它没有反问"哪个 codex",也没有让我把背景重讲一遍。它去记忆库里把 codex 那次会话的结论捞了出来,接着往下改。
这件事放在半年前是不成立的。那时候我每换一个 Agent,就等于换了一个失忆的同事。
我机器上到底有几个 AI
先说清楚规模,不然"共享"这个词没有意义。
- Mac:Hermes、Codex、Claude Code、WorkBuddy、dsh
- NAS:Hermes、Codex CLI
- VPC:一个都没有,只跑服务
- 另一台服务器:Muse
一台机器上五个 Agent,跨三台机器。问题不是"AI 不够多",是它们各自记各自的东西。
Codex 知道我在改哪个项目、哪条提交没推;切到 dsh,它对这一切一无所知。我在 Codex 里花二十分钟讲清楚的约束,换个窗口要再讲一遍。最要命的是我自己也会记混——到底是在哪个窗口里说过这句话。
把记忆从 Agent 里抽出来
解法不新鲜:把记忆做成一个独立服务,所有 Agent 接同一个。
我用的是自建的 OpenViking,一个实例,插件和 MCP 两种接法。Hermes 走原生 provider,Codex 走插件加 hooks,WorkBuddy 走 MCP,dsh 走插件。
关键是想清楚一件事:共享和归属,不矛盾,但必须分开设计。
写归自己,读跨全库
最后落成的结构就两层:
viking://user/nnnnzs/
├── memories/ # 用户级:画像、偏好、项目状态、环境档案
│ └── entities/
└── peers/ # 每个 Agent 一个目录
├── codex/
├── hermes/
├── workbuddy/
└── dsh/
写入的时候带一个请求头 X-OpenViking-Actor-Peer,服务端就知道这段记忆是谁写的,落进对应的 peers/ 目录。读取的时候不带这个头,召回就横跨全部 peer,加上用户级记忆一起返回。
一句话:写的时候分家,读的时候不分。

这样"codex 上次那个方案"是可查的,同时你也能指着一堆记忆说"这条是 Codex 写的,那条是 Hermes 写的"。
踩坑一:以为加了身份就会自动隔离
我一开始把 actor_peer_id 理解成账号——填上 codex,它就该有自己的地盘了。
结果配完一测,什么都没有变:照样能读到别人的目录。
翻源码才看到这段:
js
// resolveMcpActorPeerId
if (recallPeerScope !== "actor") return "";
默认值是 all,函数直接返回空字符串。也就是说MCP 这条链路压根不发送身份头,你配了 peerId 也没用。
这不是 bug。设计者的意图很明确:默认就要跨 peer 广域召回,因为大多数人要的是"别忘事",不是"分权限"。
踩坑二:设成 actor,跨 Agent 读就断了
想通了上面那层,我的第一反应是"那就把 scope 设成 actor 呗"。
实测直接打脸。
同一句查询,找 Hermes 写过的一份调研笔记:
| 请求身份 | 结果 |
|---|---|
| 不带身份头 | 召回到 peers/hermes/ 里那条笔记 |
带 workbuddy 身份 |
那条笔记消失了 |
actor 不只是"只写自己",它连别人的历史一起挡在门外。
这正好把集中式记忆最想要的东西毁掉了。所以撤回,只保留 peerId,scope 保持默认。
记下来一条:归因和隔离是两个需求。我要的只有前者。
踩坑三:两个客户端共用了一个身份
排查过程中顺手发现的:WorkBuddy 的 Key 和我其他 Agent 的 Key 不是同一个账号——它的记忆一直写在另一个用户空间里,跟我这边的库完全不相通。
表面上看它"接入成功"了,实际上一直在隔壁房间说话。
这种问题不会报错,只会让你在某天觉得"怎么它什么都不记得"。现在给它单独一个身份,用 MCP 的自定义请求头指定,一行配置的事。
还有一层:机器不同,事实就不同
同一句"改一下 nginx 配置",在我这儿有三个答案:
- Mac:Homebrew 的
/opt/homebrew/etc/nginx - NAS:根本没有 nginx,服务由 VPC 反代
- VPC:宝塔的 vhost 目录
如果记忆里只写"nginx 配置在 xxx",跨机器查就会张冠李戴。
所以我给每台机器写了环境卡,又写了个小脚本 ov-env,让每个 Agent 在会话开头自报一次:主机、系统、工作区根目录、配置路径、生效的 peer 身份。生成的卡片固定落在 entities/运行环境/agents/<agent>-<host>.md。
配套四条硬约束:
- 路径类事实必须带
[mac]/[nas]/[vpc]前缀 - 不得把本机路径写成通用事实
- 复用别的环境卡里的命令前,先比对 host
- 不确定的字段写
unknown,禁止猜
写这段脚本的时候还撞到一个很典型的坑:NAS 上全局设了 HTTP_PROXY,Python 的 urllib 会把发往内网 ZeroTier 地址的请求也塞进代理,30 秒超时;而 curl 同一地址只要 57 毫秒。最后在脚本里强制禁用代理才对。
现在用起来是什么感觉
两个真实场景:
场景一:在 dsh 里问"Codex 刚才那个没做完的,你接着改"。它能读到 Codex 的结论和上下文,不用我复述。
场景二:在 Codex 里问"dsh 刚才卡在哪了"。反过来也一样成立。
这种感觉很难形容。不是"AI 变聪明了",而是它终于不再问我已经回答过三遍的问题。
它现在还不行的地方
写优点容易,写边界更重要。
- 会话要提交后才有记忆。正在聊的内容,别的 Agent 读不到。想实时接力,得手动写一条进度到用户级记忆里。
- 客户端守不守规矩,全靠自觉。服务端只验请求头,不验你是不是真的那个 Agent。想冒名顶替,改一行配置就行。
- 同一个 Agent 跨机器共用一个身份。Hermes 在 Mac 和 NAS 上是同一个
hermes,只能靠环境卡里的host字段区分。理论上仍会串味,只是概率低。 - peer 只是标签,不是权限。真正的权限隔离我最后没要,因为它和"集中式记忆"天然冲突。
值不值
如果只用一两个 Agent,这套东西是过度设计。
但只要你的工作流里出现"这个我在另一个窗口里说过",它就值。
我做这件事的动机很朴素:我不想再当那个在五个 AI 之间人肉转发上下文的中间人。
它没有让模型变强,只是让它别再忘事。而"不忘事"这件事,对日常使用的体感提升,比换一个更强的模型明显得多。