第二套记忆 / LINGXI × OPENVIKING
技术长文 · Agent Memory

第二套
记忆

灵犀自带的记忆是为「把活干完」设计的:条目化、常驻、精简。但聊天里那些犹豫、偏好、当时的判断,也值得被留下。 这篇讲我怎么用 MCP + OpenViking 在本机搭第二套记忆,以及为了让手机遥控、跨端会话都能被记住,中间那层桥到底做了什么。

2026.11.07 约 26 分钟 7 张架构图 生产运行 72 天
01

工具箱记得住任务,记不住那天下午

我用灵犀做很多事:改脚本、跑数据、写文档、查资料。它自带的记忆系统很好用——知道我的减脂方案是哪一版,知道某个仓库必须用完整路径推送,知道我讨厌被反复追问同一件事。但它记住的,几乎都是「以后做事要用到的结论」。

这套记忆的形态是条目:一条偏好、一条规则、一页项目状态。写入要显式发生——判断「这件事值得记」,然后落一条。读取是常驻的:核心记忆每轮全文注入,Wiki 目录每轮给索引,需要时再展开。

这种设计是对的。它让 Agent 在工具这条路上更稳、更省、更可控。系统不会因为昨天聊了三个小时的电影,今天写代码时上下文里就多出三千字影评。

但它也意味着另一件事:生活化的对话默认不会被留下。不是记不住,是不在它的职责里。你当时为什么犹豫、哪句话让你改了主意、那次失败之后你怎么想——这些不构成「操作指令」,也就没有入口。

工具记忆回答的是「我该怎么做」;生活记忆回答的是「我当时为什么这么选」。 前者可以精简到一条规则,后者一旦压缩成结论,就失去了大部分价值。

所以我做的不是改造灵犀的记忆,而是在它旁边加了第二套:一套会话级、可检索、按需加载的记忆库。它不参与每一轮推理,只在需要的时候被叫出来。

02

两套记忆,两种职责

先把边界说清楚:它们不是主备关系,也不是新旧替代。一套管「做事」,一套管「发生过什么」。混在一起,两边都会变差。

SYSTEM A

工具记忆

灵犀自带 · 核心记忆 + Wiki

写入
显式。判断值得记,才落一条。
形态
结构化条目:偏好、规则、项目状态。
加载
常驻。核心记忆全文注入,Wiki 给索引。
目标
精简、确定、可执行。宁少勿滥。
擅长
「这个仓库要用完整路径」「方案已经到 V5」。
盲区
闲聊、情绪、过程中的犹豫与取舍。
SYSTEM B

会话记忆

OpenViking · 本地长期记忆库

写入
自动。桥接器持续捕获对话,提交后蒸馏。
形态
事件、偏好、人物、案例、经验、轨迹。
加载
按需。MCP 工具调用时才召回几条。
目标
丰沛、可追溯、带上下文。宁全勿断。
擅长
「那次为什么改方案」「她当时怎么说的」。
代价
需要存储、索引、提取算力,还要防脏数据。
SYSTEM A / 常驻 工具记忆 条目化 · 每轮注入 · 追求精简 核心记忆(全文) Wiki 索引(目录) 按需展开正文 SYSTEM B / 按需 会话记忆 连续捕获 · 语义索引 · 需要时才来 原始会话(可回溯) 蒸馏记忆(事件 / 偏好 / 人物) 检索返回 3–5 条 同一个 Agent ONE CONTEXT · TWO SOURCES 常驻的归常驻,检索的归检索。混在一起,两边都会变差。
图 1两套记忆的职责划分。工具记忆每轮进入上下文;会话记忆只在工具调用时被召回,不占常驻预算。

我把这件事讲给朋友听,他问:那不就是「聊天记录」吗?不是。聊天记录是线性的、按时间排的、需要人自己翻。记忆库要的是另一件事:能在对的时刻,把对的那几条自动端上来。

这要求它在写入时就做结构化——把一次对话拆成事件、偏好、人物关系、经验教训;在读取时做语义匹配,而不是关键词搜索。这部分工作我交给 OpenViking,它本来就是干这个的。

03

为什么是 MCP,而不是再写一段提示词

最容易想到的做法,是把记忆摘要塞进系统提示词。简单,直接,但很快会撞墙:预算是固定的,而生活是无限的。

MCP 解决的不是「能不能读到」,而是「什么时候读、读多少」。它是工具调用协议:模型先看到一份能力清单,判断需要,才发起调用,服务端返回几条结果。没调用的时候,记忆库对上下文几乎零占用。

CONTEXT WINDOW 系统提示 核心记忆 · 每轮全文注入 当前任务与对话 工具返回的记忆片段 只在调用发生后出现 · 3–5 条 固定预算 · 每一轮都要付 越塞越多 → 挤压任务本身 MEMORY STORE · 本地 events · 发生过什么 preferences · 喜欢什么 entities · 谁与谁 cases · 做成过什么 patterns · 反复出现的规律 trajectories · 一条路怎么走的 TOOL CALL 要的时候才拿 不调用 = 不占上下文 可以很大,可以一直长
图 2MCP 的本质是把记忆从「常驻预算」挪到「按需 I/O」。上下文里只有被召回的那几条。

除了预算,还有三个实际好处:

  • 可共享。服务跑在本地,任何支持 MCP 的客户端都能接同一份记忆。灵犀、其他桌面 Agent、命令行工具看到的不是各自为政的几份摘要。
  • 可撤销。记错了,改服务端的文件就行,不用去翻提示词、改配置、重启会话。
  • 可审计。记忆是磁盘上的文件,有路径、有版本、有修改时间。比起「模型脑子里大概记得」,这是能拿出来核对的。

代价也很实在:多一个服务要维护,多一条链路会坏。这篇后面的篇幅,基本都在讲怎么让它别坏。

04

先搞清楚:灵犀的对话到底存在哪

要搬运,先找到货。这一步花的时间比写代码多得多——因为「对话在本地」这个直觉,在灵犀身上是错的。

2026 年 8 月,我在安卓开发版上导了一份日志。结论很干脆:对话 100% 在云端处理,客户端只负责渲染快照。传输走 WebSocket 为主、SSE 备用,断线后从云端拉历史。本地日志里只有 sessionId 和统计数字,没有正文。

但桌面端不是完全没痕迹。灵犀的聊天界面是一个 H5 页面,跑在 Electron 的独立分区里,它的 IndexedDB 缓存了完整事件流——包括正文。路径是:

本地路径
~/Library/Application Support/WPS 灵犀/Partitions/lingxi.wps.cn/
  IndexedDB/https_lingxi.wps.cn_0.indexeddb.leveldb

这个库不是普通 JSON。它是 LevelDB 的 WAL,值是 V8 的序列化格式。要还原,得先按 WAL 的 record 格式切分,再把 V8 的标签流解成对象。我写了两个小模块专门干这件事:wal.py 和 v8_codec.py。跑通那天确实很爽——但它只解决了历史。

因为后来主对话切到了云端 agent 通道,IndexedDB 里只剩旧通道的遗留。正文在云端,本地只剩线索。于是架构必须改:发现和获取分开——本地找 sessionId,云端拉正文。

本机日志 sessionId / cowork-<id> 只给线索,不给正文 DISCOVERY 本机 IndexedDB WAL + V8 序列化 旧通道遗留 · 可完整还原 LEGACY SOURCE 云端 REST /sessions/{id}/messages 当前主对话 · 唯一正文来源 PRIMARY SOURCE Canonical Event PLATFORM-AGNOSTIC · FINGERPRINTED 脱敏 → SQLite 账本 → OpenViking 到这里,来源已经不重要了 发现与获取分离:本地只负责「有哪些会话」,正文一律回到云端取。
图 3三条来路最后都收敛成同一种事件格式。旧通道不是 fallback——它兜不住当前会话,只用于历史迁移与回归对照。

拉取正文需要鉴权。灵犀把 wps_sid 存在 Cookies 的 SQLite 里,有效期很长。桥接器用只读 URI 打开它,读不到才退化成 0600 的临时副本,用完即删。这条凭据不落盘、不打印、不进 git,这是底线。

一个反直觉的结论:「本地优先」在灵犀身上不成立。本地只有索引和线索,真正的资产在云端。所以桥接器必须联网,且必须能识别「会话已被云端清理」这种 404——它和「空会话」是两回事。
05

中间层:一条只负责搬运的流水线

我给这层定的第一原则,是克制:它不做摘要、不做判断、不做「什么值得记」。它只保证一件事——云端发生的对话,一条不丢、一条不重地送到记忆库门口。

为什么不在这里做 LLM 蒸馏?三个原因。第一,蒸馏会引入第二次判断,错了无法回溯;第二,记忆库自己就有提取链路,重复做一遍是浪费;第三,一旦中间层开始「理解」内容,它就必须跟着模型和提示词一起演化,维护成本会失控。

所以它的定位是事件同步器:原始事件先落本地账本,再投递。记忆怎么长,是记忆库的事。

COLLECT 01 发现 扫本机日志 · 拉云端任务列表 4 条正则 + 7 天窗口 → 注册进 Registry 02 取数 按会话状态轮询云端 新发现 0s · 活跃 45s · 静默 30min TRUST 03 规范化 统一事件格式 角色 / run / 序号 / 指纹 04 脱敏 进账本之前就抹掉 凭据 / 卡号 / 身份证 / 私钥 05 账本 SQLite · append-first 指纹幂等 · 崩溃可重放 DELIVER 06 批量写入 每批不超过 100 条 超限整批拒绝,不会部分成功 07 归属校验 确认写进的是自己的空间 created_by_user_id 08 提交与提取 冷却 15min / 150 条 / 6 万字 由记忆库侧蒸馏成记忆文件 SIDECAR / 旁路保障 三方核账 源 == 账本 == 库 失败归档巡检 去重切块 → replay 看门狗四条件 心跳 / 进程 / 错误 / 金丝雀 告警 连续异常才发 主链路只负责搬运;核账、巡检、告警都在旁路,失败不阻塞同步。
图 4九段流水线。01–02 采集,03–05 建立可信副本,06–08 投递;最下面一层是旁路保障,不参与主链路串行。

五条不肯让步的设计

  • 先落账,再投递。事件进 SQLite 才算「收到」。后面发不出去、进程被杀、云端限流,重启都能从账本续上。
  • 事件级指纹去重。不用「第几条消息」当身份,用 sha256(来源 | 会话 | run | run 内序号 | 角色 | 正文)。同一段内容无论拉多少次,只会入账一次。
  • 脱敏前置。敏感内容在进账本之前就替换成 [REDACTED:规则],只留原文哈希用于对账。事后脱敏等于没脱敏——副本已经散出去了。
  • 只同步正文。用户的输入 + 助手的最终回复。推理过程、工具调用中间态、推荐问题全部不写。这条规则救过一次千万 token 级的事故。
  • 人格层只读。记忆库里的身份、性格文件禁止自动改写。记忆可以长,人格不能被对话漂移带跑。
06

一轮六十秒里发生了什么

桥接器现在每 60 秒跑一轮。看起来只是「拉一下、发一下」,但真正决定它稳不稳的,是几个不起眼的判断:什么时候提交、一次发多少、失败了算谁的。

T+00s · 发现 扫日志,注册新会话 只扫最近 7 天修改过的日志文件。命中 sessionId 就写进 Registry,状态标为 DISCOVERED。
T+01s · 取数 按状态决定这一轮拉谁 新会话立刻拉;活跃会话 45 秒一次;静默会话降到 30 分钟。归档的不再轮询。
T+02s · 规范化 拆成平台无关的事件 补上 session、run、run 内序号、角色,算出指纹。到这里,数据已经不认识灵犀了。
T+03s · 脱敏 在入库前抹掉敏感值 五类规则:已知凭据、Bearer/sk- 令牌、银行卡(Luhn 校验)、身份证(校验位)、私钥块。
T+04s · 入账 append-first,不覆盖 指纹做主键,重复事件静默跳过。状态先记 pending,投递成功才转 verified。
T+05s · 闸门 这一轮只取最早的 N 条 先按条数封顶 150,再按累计正文 6 万字封顶。取两者先到者,剩下的留到下一轮。
T+06s · 写入 每 100 条一批 超限整批拒绝,不会部分成功。所以必须自己分批,不能指望服务端兜底。
T+07s · 校验 确认归属 读回会话的创建者。多用户共用一台机器时,这一步防的是「写进别人的空间」。
T+08s · 提交 冷却到了才提交 同一会话最少间隔 15 分钟,或积压到 150 条强制提交。提交触发提取,提取很贵。
T+20s ~ 数分钟 · 提取 记忆库把会话蒸馏成文件 这一步不在桥接器里。它可能成功、可能产出 0 条、也可能因为输出预算被截断而整批作废。

最初不是这样的。第一版是「每见到 run.end 就提交」——听起来很合理,实际把上游打爆了:一天 86 次提交里 61 次被限流,一个早饭会话提交了 52 次、六千多条消息、烧掉七十多万 token,把同账号其他会话的提取一起挤死。

所以后来加了两层节流:时间上同会话 15 分钟冷却,体量上积压到 150 条强制提交。再往后发现 150 条长回复照样能把提取的输出预算撞穿,又补了 6 万字的字符闸门。

这里的教训是:「提交」不是免费的写操作,它是「触发一次模型提取」。频率、单次体量、失败代价,三个都要单独设计。
07

三个入口:本机、手机遥控、跨端

做到这一步,本机的对话能进来了。但真实使用里,会话会从三个地方冒出来,它们的物理位置完全不同。这一节是整套东西里最容易被忽略、也最容易「以为记住了其实没记」的部分。

ENTRY 01

本机

在 Mac 客户端或网页里聊。日志里有 sessionId,正文回云端拉。

~/Library/Logs/WPS 灵犀/
ENTRY 02

手机遥控

手机当遥控器,任务实际在 Mac 上执行。会话是在本机形成的,日志照样有。

remoteBridge · app.*.log
ENTRY 03

跨端创建

在另一台电脑上创建、只在那台机器聊过。本机日志永远不会有它。

GET /session_list

7.1 手机遥控:会话没跑在手机上

灵犀的手机遥控不是把对话搬到手机执行,而是手机发指令、桌面端跑任务,中间走一条 remoteBridge 中继。所以只要任务是在这台 Mac 上完成的,sessionId 一定会落在这台 Mac 的 app 日志里,和本地发起没有区别。

这一点很关键:不需要为手机单独做一套采集,也不需要手机端开放什么接口。发现器扫的是本机日志,天然覆盖。我在日志里能看到大量 remoteBridge 记录,会话注册、拉取、落库的路径与本地会话完全一致。

7.2 跨端:本机日志永远找不到它

反过来,在另一台电脑上创建、只在那台机器聊过的会话,本机日志里一个字都没有。默认发现逻辑扫不到,也不会报错——它只是安静地不存在。

解决办法是不猜,直接问云端要清单。账号下的全部任务可以通过 session_list 拿到,关键是 session_type 要传对:

云端任务列表
# 只读:拿账号下全部桌面端任务
GET https://lingxi.wps.cn/api/public/v1/session_list
    ?limit=100
    &exclude_agent=true
    &session_types=cowork_client_project
Cookie: wps_sid=<从本机 Cookies 只读>

拿到 id 之后不需要重新实现一套拉取——把 id 注册进 Registry,标上来源 cloud-session-list,下一轮它就会被当成普通会话,走同一条云端取数、脱敏、入账、投递的路径。实测补五个历史会话,拿到三千多条事件,其中两百多条正文入库。

注意两个坑:一是 session_type 写错(比如只写 cowork)会返回 0 条,看起来像「没有数据」;二是日志里那份会话列表响应会被截断到一千多字符,靠日志永远补不全清单,必须直接调接口。

7.3 为什么不做「全自动扫全账号」

技术上可以每天把账号下所有任务都拉一遍,但我没有这么做。一是历史会话量很大,全量重扫会把云端和提取预算一起打满;二是大部分旧会话的边际信息量很低。现在的做法是:默认只跟本机活跃会话,跨端按需补拉,补哪些由人决定。

这也是整套系统的性格:自动负责「不漏」,人工负责「取舍」。

Mac 本机 客户端 / 网页对话 日志有 sessionId 手机遥控 任务在 Mac 上执行 日志同样有 sessionId 另一台电脑 本机日志完全没有 只能问云端要清单 Session Registry ONE TABLE · THREE ORIGINS 同一条流水线:取数 → 规范化 → 脱敏 → 账本 → 投递 入口不同,但「怎么记」只有一套。
图 5三个入口的差别只在「怎么发现」,发现之后完全同构。这是把复杂度关在入口层的关键。
08

跑起来之后,账本长什么样

下面的数字来自这台机器今天的账本,不是估算。我一直觉得,讲架构如果不给运行数据,读者没法判断它到底有没有真的跑起来。

418,473累计事件入账
65,627通过校验的正文事件
167注册会话
1,027记忆库归档次数
1,091蒸馏出的记忆文件
164记忆库内会话
72连续运行天数
1.0 GB记忆库磁盘占用

有个数字对比很有意思:账本里四十多万条事件,最后只有六万五千条被判定为「可同步正文」。剩下的三十五万条是结构事件、流式分片、推理中间态、工具调用记录——它们在同步前就被过滤掉了。

这不是丢数据,是刻意的:如果把这些全部写进记忆库,token 会被中间产物淹没,提取质量反而下降。第一版就吃过这个亏,一次事故烧掉近千万 token。

口径数量说明
累计入账事件418,473所有从云端拉回、经脱敏后落账的事件
已过滤352,846结构事件与中间产物,不同步到记忆库
已核验正文65,627真正写入记忆库的消息
— 助手最终回复52,353只取最终呈现文本,不含推理过程
— 用户输入10,357提问、指令、口述内容
— 结构事件2,917run 边界等,用于对账锚点

记忆库那边的分布也符合预期:事件最多,因为每天都有新的事;实体次之,人物、工具、项目会不断被提到;偏好和轨迹增长最慢,但价值密度最高——它们是「关于你的稳定结论」,不是流水账。

记忆分类文件数典型内容
events746某天发生了什么、做了什么决定
entities134人物、项目、工具、服务
preferences80明确表达过的偏好与纠正
trajectories56一件事从头到尾怎么推进的
experiences32踩过的坑与正确做法
cases27可复用的完整案例
patterns9反复出现的行为规律
projects / relationship2长期项目状态、关系约定
关于「提取产出 0 条」:不是每次都一定能提取出东西。一次提交可能因为内容重复、没有新事实,返回零新增;也可能因为输出预算被截断而整批作废,且服务端不会自动重试。所以「提交成功」和「记忆成功」是两件事,必须分开核。
09

九个坑,每一个都静默过

这条链路上最危险的故障,不是报错,是「看起来一切正常」。下面九条,全都曾经在没有任何告警的情况下持续运行过一段时间。

SIDECAR / 失败归档巡检 主链路看不到提取结果,所以必须有一条旁路,专门替失败的那一批收尾。 01 扫描 找 .failed.json 按失败时间过滤 02 过滤 已恢复 / 不可重试 超出 7 天窗口的跳过 03 去重 跨归档内容去重 重发会制造大量重复 04 切块 150 条一组 不超过单次提取预算 05 重放 独立 replay 会话 提交后轮询终态 成功: 整组补齐,才给每个归档写 .recovered.json —— 部分成功不算恢复,下次还能重跑。 失败: 不写标记,保留原状。宁可下次再来一次,也不要制造「已恢复」的假象。 RATE LIMITS 四道闸门,防止兜底本身变成新的雪崩 30 分钟冷却 每天最多 20 块 单轮最多 2 块 只碰最近 7 天
图 6失败归档的自动收尾。它只在主链路成功之后触发,跑在守护线程里,不阻塞同步;补不上的历史积压,宁可不动。
P1日志格式一改,发现器全哑了八天

现象:进程在跑、心跳正常、没有 error,但新会话一个都没进来。

根因:客户端升级后日志格式变了,旧正则匹配不上。而看门狗只看「进程活着 + 心跳新鲜 + 没有报错」,这三个条件全绿——它根本不知道「发现数应该增长」。

修法:①正则改成宽匹配,同时认三种历史形态;②加一条金丝雀:用独立正则干扫日志,和已注册列表比对,有差距就告警;③看门狗加第四个条件,连续两轮发现缺口未消才报。

教训:监控要盯「产出」,不能只盯「存活」。

P2批量写入有硬上限,超一条就整批拒绝

现象:回补一个大 session 时一直失败,日志只说 HTTP 400。

根因:接口单批最多 100 条,101 条直接拒绝,而且不会部分写入。

修法:写入层自己按 100 条分批,每批独立「写入 → 校验 → 提交」。失败只影响那一批。

教训:凡是「批量」接口,先去实测边界,别假设它和单条一样宽容。

P3一天 86 次提交,61 次被限流

现象:记忆时有时无,提取任务大面积失败,别的会话也跟着饿死。

根因:每见到 run 结束就提交,而提交会触发一次模型提取,非常贵。一个早饭会话提交 52 次、六千多条消息、烧掉七十多万 token,把共享配额吃干。

修法:加提交冷却:同会话最短 15 分钟;积压到 150 条强制提交;静默会话到期补一次归档。

教训:「写消息」和「触发提取」要分开算账,前者廉价,后者昂贵。

P4提取被输出预算截断,整批作废且不重试

现象:提交返回成功,但归档里多了一个失败标记,一条记忆都没产生。

根因:单次提交的消息太多,模型把输出预算耗在推理上,正式结果一个字没产出。服务端判定「截断」,整批放弃,且没有重试端点。

修法:①单轮处理量加双闸门(150 条 / 6 万字);②失败归档自动巡检:扫描失败标记、跨归档去重、切块、用独立会话重放、成功才落「已恢复」。

教训:任何「一次处理一大批」的设计,都要有单批体量上限和失败兜底。

P5指纹用了全局序号,滑窗一裁就重传

现象:同一个会话一天内重传了八百多条历史消息,连发九个归档。

根因:指纹里含「全局第几条」。云端对超长会话有滑动窗口,新增消息会裁掉最老的几条,所有序号整体前移,历史消息的指纹全变了,被当成新事件。

修法:序号改成「run 内相对序号」,头部裁剪不影响它。旧指纹做迁移判重,命中后原地升级。

教训:幂等键不能依赖会随外部状态漂移的坐标。

P6看门狗读错了字段,永远不告警

现象:桥接器真的进入故障退避了,看门狗一次都没报。

根因:健康状态文件把错误写在 stats.error,看门狗读的是顶层 error,条件恒假。

修法:两种形状都识别,并用真实的写盘函数做回归测试,锁住三种状态。

教训:告警器自己也需要测试,否则它只是「看起来很安心」。

P7试运行污染了状态,真跑时跳过 290 条

现象:演练一切正常,正式跑却少了一大段。

根因:试运行用的假写入层返回成功,编排层照样把事件标成「已核验」。等真跑时,这些事件被当成已完成跳过。

修法:试运行改成显式参数,走真实入库(指纹本来就幂等),但绝不标记完成状态。

教训:「dry-run」必须是编排层的语义,不能只是替换一个假客户端。

P8每次重启都触发全量重扫

现象:一重启就要跑三到五分钟,心跳短暂陈旧,看门狗差点误报。

根因:「上次对账日期」存在进程内存里,重启归零,立刻触发一次全量对账并重置上百个会话的游标。

修法:把日期写进账本的 meta 表——状态跟它描述的数据放在一起,备份和回滚不会错位。

教训:凡是「每天一次」的开关,都要问一句:进程重启后它还准吗。

P9部署直接指向工作区,改完没提交就上了生产

现象:修复在生产上生效了,但仓库里没有记录。一次 git checkout 就能让修复凭空消失。

根因:定时任务的启动参数直接指向工作区脚本,工作区即生产。

修法:立规矩:部署目录的任何改动必须当场提交,不留未提交状态过夜;自检 git status。

教训:「本地开发」和「生产运行」共用一个目录时,版本管理不是好习惯,是安全措施。

10

开源计划:只放中间层出去

OpenViking 是第三方项目,灵犀本身闭源,这两层都不在开源范围内。计划放出去的是我自己写的那一层:桥接器、账本、核账、巡检、部署脚本。许可证倾向 MIT 或 Apache-2.0,仓库只包含这一层,不夹带任何个人数据。

会开源

  • 发现器、云端拉取、规范化、脱敏
  • SQLite 账本与指纹幂等
  • 写入层:分批、归属校验、提交冷却
  • 三方核账、失败归档巡检
  • 看门狗四条件与告警适配
  • launchd 部署、依赖自愈、运维脚本
  • 全部测试与事故复盘文档

不会包含

  • OpenViking 本体(第三方项目)
  • 个人记忆数据、账本、归档
  • Cookie、User Key、token
  • 告警接收人、内网地址、端口映射
  • 与个人账号绑定的 session 清单
  • 带真实对话的测试金样本

抽象工作不多,但要一次做干净:绝对路径换成配置、身份信息换成占位符、灵犀专属的常量收进 adapter。核心层不认识灵犀,它只认识「事件源」和「记忆库」两个协议。这是当初分层留下的红利,也是它能被搬到别的 Agent 上的原因。

计划中的仓库结构
memory-bridge/
├── adapters/
│   └── lingxi/          # 只此一处认识灵犀:日志、接口、字段映射
├── core/
│   ├── events.py        # 规范事件 + 指纹
│   ├── filter.py        # 脱敏规则
│   ├── ledger.py        # SQLite 账本
│   └── pipeline.py      # 编排与闸门
├── sinks/
│   └── openviking/      # 分批、归属校验、提交冷却
├── ops/
│   ├── reconcile.py     # 三方核账
│   ├── recovery.py      # 失败归档巡检
│   └── watchdog.py      # 四条件 + 告警适配
├── deploy/              # launchd / systemd 模板
├── tests/               # 含崩溃重放与金丝雀回归
└── docs/
    └── incidents/       # 事故复盘,比 README 更有价值

发布前会过一遍这张清单:

  1. 全仓搜一遍真实用户名、主机名、内网 IP、端口映射,全部替换。
  2. 确认 .gitignore 挡住账本、日志、归档、备份、.env。
  3. 测试金样本换成构造数据,不含任何真实对话。
  4. 把「灵犀专属」的常量集中在 adapter,core 不出现平台名词。
  5. README 只讲怎么跑起来;架构取舍和事故写进 docs/。

这类基础设施的价值不在代码本身,在于有人愿意照着搭一遍,再把它改得更好。所以文档会比代码写得更细,尤其是那九个坑。

11

复刻路线

如果你也想给自己的 Agent 搭一套,顺序大概是这样的。不要一上来就写同步器——先把「记忆库能不能被按需调用」跑通,那是整件事的地基。

先跑通记忆库,不写代码

本地起 OpenViking,签发一个用户级 Key,用 MCP 接进客户端。手动写一条记忆、检索一次、确认归属正确。这一步不通,后面全是白搭。

把「写入三件套」跑通

建会话 → 批量写消息 → 提交 → 轮询提取结果。用 curl 就行。记住两个边界:单批 100 条,提交会触发一次昂贵的提取。

找到你的 Agent 把对话存在哪

先看本地有没有明文(数据库、缓存、日志),再看云端有没有可用接口。两者都没有,这事做不成——不要假设「应该能抓到」。

写最小的采集器:只做发现

先不加投递,只把 sessionId 扫出来、注册进表、打日志。跑一天,看数量对不对。发现错了,后面全错。

补上账本与幂等,再接通投递

事件先落 SQLite,指纹去重,状态机 pending → verified。然后才是写入层。这一步做完,就可以强杀进程测试重放了。

最后才是「让它别坏」

核账、失败巡检、看门狗、告警。顺序不能反:没有产出指标,监控只会告诉你「进程还活着」,而那往往是最没用的信息。

一个实用建议:先只同步「用户输入 + 助手最终回复」,其余全部过滤。等你确认提取质量稳定了,再考虑要不要放宽。反过来做,几乎一定会被中间产物淹掉。
12

Agent Home:多 Agent 共用一套记忆

这套桥现在只服务一个 Agent。但它要解决的问题,很快会变成多个 Agent 的共同问题。

我平时不止用一个 Agent:有的负责写作,有的跑代码,有的查资料、管日程。它们各有各的入口、工具链和上下文窗口,互相之间并不知道对方做过什么。

如果每个 Agent 各记各的,一个人会被记成几份:同一个偏好出现三个版本,同一个项目的结论互相打架,昨天在 A 那里定下的事,今天在 B 那里完全不成立。记忆越勤奋,分裂得越快。

所以下一步不是再写一座桥,而是把记忆做成底座——我们把它叫 Agent Home。一套记忆,多个住户,按归属和权限访问。

  • 公共层:身份、偏好、人物、项目。谁都可以读,写入按权限收紧。
  • 工作层:事件、决策、轨迹。按 Agent 与会话归属,同一个人可以跨 Agent 看到。
  • 私有层:各个 Agent 的工作上下文,默认隔离,不互相污染。

OpenViking 的结构刚好能承载这件事:账户、用户、peer 之间的隔离,session 的归属校验,memories 的分类目录。桥这一层要做的,是把「哪个 Agent、哪个会话、写进哪一层」标清楚。

桥是第一个接入者,也是验证场。它已经证明三件事:会话能可靠落库、提取能产出可用记忆、失败能自己收尾。这三件事是 Agent Home 的地基,剩下的部分还在做。

这一节只讲概念,不展开路线图和排期。等底座真的能跑起来,再单独写一篇。

AGENT HOME / 概念示意 多个住户,一套记忆,按归属与权限访问。 Agent A 独立会话与上下文 Agent B 独立会话与上下文 Agent C 独立会话与上下文 …… 还会有更多 MCP 接入层 各自连接,不互相直连;按需召回,不常驻上下文 SHARED MEMORY / 分层 公共层 · 身份 / 偏好 / 人物 / 项目 工作层 · 事件 / 决策 / 轨迹(按 Agent 与会话归属) 私有层 · 各 Agent 的工作上下文(默认隔离) 概念示意,不代表最终架构与排期。
图 7Agent Home 的分层设想:Agent 之间不直接共享上下文,只共享记忆底座;公共、工作、私有三层用归属与权限隔开。
13

工具记忆求简,生活记忆求真

做完这套东西,我对「Agent 该记住什么」的看法反而更保守了:不是记得越多越好,而是该简的地方极简,该全的地方别删。

工具记忆应该继续精简。它每轮都进上下文,多一条就多一分干扰,所以必须有人为的判断:什么值得留下。这部分不该自动化,也不该膨胀。

生活记忆相反。它的价值恰恰在细节里——那句没说完的话、当时改主意的理由、一次失败之后的情绪。把它压缩成「用户偏好 X」当然更省,但也就没什么可召回的了。

两套记忆并行,各司其职:一套让 Agent 更像一个靠谱的同事,一套让它记得你是个具体的人。

真正难的不是让它记住,是让它在该想起的时候想起,在不该打扰的时候安静。 前者靠索引,后者靠按需加载。这也是为什么我最后选了 MCP,而不是把一切塞进提示词。

这套中间层还在长。失败巡检是最近才补上的,跨端补拉也只做了按需的一小部分。但骨架已经稳了:三个入口、一条流水线、两层记忆。

如果把它开源,我希望被拿走的是那几条规则——先落账再投递、脱敏前置、只同步正文、监控盯产出。代码可以重写,这些是踩出来的。

关于数据:文中所有运行数字来自本文写作当天这台机器的账本与记忆库统计,仅代表个人使用规模,不是性能基准。

关于边界:OpenViking 是第三方本地记忆服务;本文开源讨论仅覆盖我自建的桥接层,不包含其代码与数据。

关于隐私:文中不含任何真实会话内容、凭据、内网地址或账号标识;示例路径与接口均为公开形态。