我这半年被多个 AI 客户端反复折磨到没脾气,每天要在 ChatGPT、Claude、Kimi 和手机上的各类 App 之间来回切换,同一句话至少重复讲四遍。后来实在忍不了,才动手做了 MemTether——一个让多个 AI 客户端共享同一份记忆的开源工具。它的思路很简单:记忆不跟着客户端走,而是跟着人走。这篇文章会把我的完整动机、架构设计、接入步骤、实测结果和踩过的坑一次讲清楚,适合正在被"AI 记忆碎片化"困扰、想自己搭一套记忆层的开发者或重度 AI 用户。
1. 当十几个 AI 助手互不相识,我终于决定自己动手
1.1 同一个问题,我每天要复述四遍
先描述一下我的日常工作流:白天写项目方案,主力是 ChatGPT;写代码调试时切到 Claude 或 Cline;查资料、读论文用 Kimi 更方便;出门在外还得靠手机上的第三方 AI 客户端临时应付。听起来挺丰富对吧?但问题也随之而来——这些客户端之间的记忆完全隔离。
每打开一个对话框,我都要重新介绍自己:"我是做 IoT 方向的,目前在做一个智慧农场监控系统,技术栈以 Python 为主,代码风格偏好类型注解,之前讨论过传感器数据延迟问题,结论是用边缘节点做初步清洗……" 这段话我一天至少要讲四遍。时间一长,体感上就是一种纯粹的浪费。我大概估算过,一天花在"重复交代背景"上的时间超过三十分钟。一个月就是十几个小时,比做一次深度复盘的时间还多。
1.2 官方记忆、Prompt 模板和知识库为什么都不够用
我不信邪,挨个试过市面上的方案。第一个尝试的是各家官方的"记忆功能"。ChatGPT 有自定义指令和记忆,Claude 有 Projects,Kimi 也能设置人设。但它们有一个共同的硬伤:记忆只在自己的产品里生效。换一个客户端,一切归零。我不能说"把我在 ChatGPT 里的记忆同步到 Claude 里",产品层面根本不互通。
第二个尝试是写一套冗长的 Prompt 模板,每次开头粘贴进去。这个方法确实有效,但问题是它只有"输入"没有"沉淀":AI 在对话中了解到的新信息,不会自动灌回模板里。我晚上发现项目改用了 Rust 做网关,第二天早上又得手动把这条新信息加进模板。维护成本高到离谱。
第三个尝试是知识库方案,把一个像样的 RAG 服务搭起来后,上传文档、做向量化、配置检索。但这也是只解决了"AI 能查到资料"的问题,并没有解决"AI 能记住我们上次聊到哪、边界条件是什么"的问题。知识库是静态的,而对话记忆是动态的。我需要的是一个能读、能写、能共享的记忆层,而不是一堆等着被检索的文档。
1.3 为什么不自研一个跨客户端的记忆层
最后,我意识到真正缺的是一个人本位记忆单元。用户产生的背景信息、项目上下文、偏好结论、命名规范、约定俗成,这些应该存在一个用户能完全控制的地方,任何 AI 客户端都以标准方式接入读写。市面没有现成方案,我就自己造了个轮子。
在动手之前我给自己定了三条底线。第一,本地优先。记忆是最敏感的私人数据,默认不离开我的局域网。第二,协议开放。不能被绑定到某一个客户端或生态,未来新出的 AI 客户端只要支持标准协议就能接入。第三,要轻量。我不需要分布式、不需要高并发,一个普通开发者在一台树莓派上也能跑起来。基于这三点,MemTether 在一天内完成了技术选型,并在一周后跑通了第一个版本。
2. 记忆网关的架构拆解:既做存储,也做标准接口
2.1 三个核心组件,职责分离
MemTether 的整体架构可以拆成三块:记忆仓库、网关服务、客户端适配器。
| 组件 | 职责 | 具体实现 |
|---|---|---|
| 记忆仓库 | 存储结构化记忆,支持分类、查询、状态管理 | 目录 + JSONL 文件 |
| 网关服务 | 提供统一 HTTP API,控制读写权限记录日志 | Python FastAPI,默认端口 8513 |
| 适配器 | 让各类 AI 客户端以各自生态的方式接入同一位记忆仓库 | MCP Server、OpenAI 兼容端点、REST API |
记忆仓库是地基。我选择 JSONL 作为存储格式(每一行一条 JSON),因为它够简单,能直接被文本编辑器打开,备份时直接复制目录就行。后续等数据量上来,可以无缝迁移到 SQLite,接口不变,只换存储层。
网关服务是枢纽,它对外暴露POST /v1/memories写入和GET /v1/memories?tag=xxx查询等标准 REST 接口,所有客户端适配器都是围绕这个接口工作的。这样做的好处是:记忆的存取逻辑与客户端种类彻底解耦,将来想接一个全新的 AI 客户端,只需要写一个适配器,不需要动存储层。
适配器这块我会放在第三节重点讲,这里先把定位摆明:MemTether 不做深度魔改,不逆向任何客户端协议,只做"所有人都愿意接的接口"。
2.2 为什么把 MCP 作为主力接入协议
很多客户端接入第三方数据,都需要单独写插件。给 A 客户端写一个插件,可能在 B 客户端完全失效。我不想陷入这种无底洞式的适配工作,因此在选型时优先考虑了 MCP(Model Context Protocol)。
MCP 这两年已经成了 AI 客户端接入外部工具的事实标准。Claude Desktop、Cherry Studio、Cline 这些客户端原生支持 MCP,这意味着 MemTether 只需实现一次 MCP Server,就能被这些客户端直接识别为"记忆工具"。我做了一个比较粗糙但准确的类比:MCP 就是 AI 客户端的 USB-C 口,各种功能模块都做成标准外设,而 MemTether 相当于一个"记忆外设",插上就能用。这个思路替代了为每个客户端单独写插件的维护地狱。
2.3 手机端和主流客户端的接入逻辑
手机客户端的情况更复杂一些,很多 App 不支持 MCP,但普遍支持自定义服务地址,也就是 OpenAI 兼容接口。顺着这个逻辑,MemTether 额外暴露了一个 OpenAI 兼容的/v1/models和/v1/chat/completions端点。用户只要在支持自定义 API 地址的 App 里,把地址填成 MemTether 网关的地址,就相当于给手机上的 AI 也接上了同一份记忆。
这里补充说明一句:MemTether 并不是要去模拟完整的模型接入,它只实现了一个"记忆注入"端点。手机 AI 客户端会把 MemTether 返回的记忆内容拼接到输入上下文中,从而实现记忆共享。简单来说,就是当 AI 在对话前先读取记忆目录,再开始回答。这种方式可以复用到任何支持自定义服务地址的客户端,也是 MemTether 能在手机端场景下快速落地的关键原因。
2.4 记忆条目的状态设计
记忆不能全盘照收,否则会越存越乱。MemTether 里每条记忆有三种状态:pending、confirmed、archived。AI 在对话中自动总结出的新记忆,默认落到pending;用户通过memtether review确认后,才升级为confirmed;过期或者被新结论覆盖的记忆,标记为archived。
这个状态机在后续帮了大忙,它让我不用时刻盯着 AI 在写什么,也不会让没经确认的脏记忆污染长期上下文。关于这一点,第五节踩坑部分会专门展开。
3. 三分钟跑通主流程:从克隆仓库到双端共享记忆
3.1 环境准备和初始化
按 Debian/Ubuntu 或 macOS 上最顺的路径,需要准备一个 Python 3.10+ 的环境。MemTether 没有用数据库,依赖很少,安装非常省事:
git clone https://github.com/yourname/memtether.git cd memtether pip install -r requirements.txt memtether initmemtether init会在~/.memtether下生成配置和目录结构,默认长这样:
~/.memtether/ ├── config.yaml # 端口、鉴权 token 等配置 ├── memories.jsonl # 全部记忆,一行一条 JSON └── index/ └── by_tag/ # 按标签生成的索引文件3.2 启动服务,写入第一条记忆
memtether serve --port 8513服务起来后,验证写入和读取最直观的方式是直接调 REST API:
curl -X POST http://127.0.0.1:8513/v1/memories \ -H "Content-Type: application/json" \ -d '{ "content": "当前项目是智慧农场监控系统,技术栈以 Python 为主,网关模块已经改用 Rust。", "tags": ["project", "techstack"], "confidence": "confirmed" }' curl "http://127.0.0.1:8513/v1/memories?tag=project"MemTether 还内置了一个简单的命令行验证命令,适合不需要完整客户端的快速测试:
memtether ask "我们当前项目的技术栈是什么?"它会先读取相关记忆,再以记忆为上下文调用底层模型给出回答。这一步主要是帮你确认"记忆读取链路"是否通了,不依赖外部客户端。
3.3 在 Claude Desktop 里挂载 MCP,让 AI 自动获得记忆工具
打开 Claude Desktop 的 MCP 配置文件(claude_desktop_config.json),加一段:
{ "mcpServers": { "memtether": { "command": "python", "args": ["-m", "memtether.mcp"], "env": { "MEMTETHER_HOME": "/Users/you/.memtether" } } } }重启 Claude Desktop,AI 的工具列表里就会出现memtether_search和memtether_store。前者用于检索记忆,后者用于把对话中出现的新信息写入记忆仓库。到这一步,Claude Desktop 已经接上了记忆层。
3.4 在手机端配置自定义服务地址
手机 App 上的配置思路完全不一样,因为大多数 App 不支持 MCP 工具调用。我亲测可用的方案是:找到设置里的"自定义 API 地址"或"服务端点"入口,填上 MemTether 网关的 OpenAI 兼容地址:
http://192.168.1.100:8513/v1这里192.168.1.100是运行 MemTether 那台机器的局域网 IP。填完后,手机上的 AI 在发起对话时会先向这个端点请求记忆片段,再拼入对话上下文。实测下来,同一个项目背景,手机端无需任何额外设置就能直接引用。
提示:手机端接入的关键是让 MemTether 服务监听在局域网可达的地址上,同时开启 token 鉴权,避免同一网段的其他人任意读写你的记忆。
4. 实测见真章:一份"项目档案"被多个客户端同时引用
4.1 测试场景搭建
架构和安装都跑通以后,我专门设计了一个真实场景来验收:在~/.memtether里建立一份完整的项目档案,包括我的身份背景、当前项目简介、技术栈偏好、代码规范、常用的数据接口约定。我用 REST API 一次性写入,标签设为project和personal,然后分别在 Claude Desktop 和手机端 AI 客户端测试同一个问题。
4.2 从"一问三不知"到"秒懂上下文"的对比
接入前,我在 Claude Desktop 里问一句"我们对传感器数据延迟的处理方案是什么",它表示从来没聊过这个话题。手机端同样如此。接入后,同样的问题,Claude 会先调用memtether_search检索相关记忆,然后回答:“根据你的项目档案,之前的结论是采用边缘节点做初步清洗,网关已经改用 Rust。”
手机端的表现也稳定,因为它走的 OpenAI 兼容端点同样能读到project标签下的记忆。最有意思的是,我在 Claude Desktop 里新增了一条结论:"后续所有时序数据存储统一改用 TDengine",然后切到手机端 App 重新问"时序数据存储用哪个数据库",它能正确给出 TDengine。这个体感上的跃迁,其实就是跨客户端记忆共享的核心价值。
4.3 多客户端并发读写,数据没丢
为了验证多客户端同时读写会不会相互踩踏,我写了个简短的并发脚本,模拟 4 个线程同时写入记忆:
from concurrent.futures import ThreadPoolExecutor import requests def write(i): requests.post( "http://127.0.0.1:8513/v1/memories", json={"content": f"并发测试记忆-{i}", "tags": ["test"]}, timeout=5, ) with ThreadPoolExecutor(max_workers=4) as pool: pool.map(write, range(4))跑完以后读取tag=test,四条记录全部存在,没有任何一条覆盖或丢失。能做到这一点,全靠第五节要讲的追加式写入和文件锁机制。读多写少的场景下,这个方案完全够用。
5. 开源迭代中踩过的三个坑,每一个都值得记下来
5.1 并发写入冲突:最朴素的解法反而最好用
一开始我采用的存储方式是"读取整个 JSON → 在内存中追加 → 写回文件"。这个方案很快暴露问题:两个客户端同时写入时,后写的那一方盖掉了先写的那一方。第一次丢数据,就是因为 Claude 和手机端 App 同时写入了两条不同的记录,结果文件里只剩最后一条。
我一度考虑引入 SQLite 或者 PostgreSQL,后来冷静下来评估了项目量级:一个个人项目,不可能为了几个并发的读写请求就去搞数据库运维。最合理的解法是JSONL 追加写 + 文件锁。JSONL 天然只追加不覆盖:任何新记忆都是写到文件末尾,不存在读改写整个文件的步骤;文件锁保证同一时刻只有一个客户端能执行写操作。我把这个逻辑放在网关层,用一个跨进程的filelock锁住写路径,然后把高频请求的内存索引和落盘文件分开维护。这个方案跑了两周,没有丢过任何一条记忆。
5.2 记忆漂移:AI 自动总结出来的记忆,不能全信
第二个坑来自"让 AI 自动把对话内容沉淀成记忆"这个功能。我原本期望的是,AI 每聊完一轮就自动生成一份结构化摘要写入仓库。结果发现 AI 会脑补。比如我们只是讨论了一下传感器的选型范围,它给我总结出"用户决定采用低功耗 LoRa 方案"。这个结论如果进入长期记忆,往后所有客户端都会把它当成既定事实,越传越歪。
我把这种问题叫作记忆漂移。后来我加了一个硬性规定:AI 写入的记忆必须标记为pending,不能直接进入confirmed状态。只有我通过memtether review查看并确认后,它才会进入长期记忆。可能有人觉得每次确认很烦,但实际用下来,每天只需要花两三分钟扫一遍新记忆,比起让漂移结论污染所有客户端的代价,这点成本完全可以接受。
5.3 隐私红线:本地优先,不上云,还要有访问控制
做这个工具的时候我反复提醒自己,记忆数据非常敏感。我记录在里面的不只是项目信息,还有个人习惯、联系方式、工作路径,一旦泄露危害极大。所以 MemTether 的默认哲学是绝不主动上传任何数据,所有逻辑都在本地执行。
不过在接入局域网设备时,安全意识还是不能放松。如果直接让网关监听在0.0.0.0且不设任何鉴权,同一 WiFi 下的任何人都有可能读写记忆。我在代码里默认生成了一个 token,写入请求必须带着这个 token 才能成功。手机端填自定义地址时,也要把 token 一并填到对应的鉴权字段里。等后续使用场景扩展到跨公网时,我会再加一层更细粒度的权限控制,目前阶段"本地优先 + 局域网 endpoint + token 鉴权"已经足够稳妥。
6. 从"共享记忆"到"记忆治理":后续规划与几句实在话
6.1 记忆不是越多越好,治理才是关键
第一次跑通双客户端共享记忆的时候,我确实兴奋了好几天。但用上一周之后,我意识到一个新的问题:记忆仓库不是被塞得越多越好,而是需要治理。
记忆多了以后,读取顺序和权重会直接影响 AI 的回答质量。比如我存了 30 条"项目相关"的记忆,如果每次调用都全部丢给 AI,上下文可能被撑爆;如果只取前 5 条,又可能丢掉刚更新的关键结论。MemTether 当前的做法是按标签召回,并让最近更新且有confirmed状态的记忆排在前面。后续我计划加一个简单的权重体系:被 AI 成功引用次数多的记忆自动提高权重,长期没有被引用的记忆降权乃至自动归档。
另外我还在规划一个更轻量的记忆版本机制。每次确认修改时记一个快照,给用户保留一处可回滚的入口。因为记忆里有很多隐含依赖,比如某个结论可能建立在一个旧前提上,一旦前提变了,结论就失效。这种演化过程如果完全靠人工管理,很难坚持,我打算让它能按时间维度展示"这条记忆的演变过程"。
6.2 给想造类似轮子的朋友几句实在话
工程做完了,重新复盘这个过程,我有些真心话想分享。
先解决自己的问题,再考虑开源。我最初做 MemTether 真的就是为了自己每天少重复四遍背景,后来发现很多朋友也有同样的痛点,才整理成开源项目。如果一开始就奔着"做一个装机量很大的开源框架"去,压力会大到第三天就放弃。
协议优先,别绑死单一客户端。如果当初我给每个客户端写插件,现在大概率已经维护不过来了。正是因为先落定 MCP 和 OpenAI 兼容端点这两个标准协议,MemTether 才能在很小的代码量下覆盖多种不同生态的 AI 客户端。
必须保留手动兜底入口。任何自动机制都会有判断失准的时候,所以 MemTether 一直保留命令行直接查看、修改、归档记忆的老路。这也是我在这大半年里最认可自己的设计之一:自动化不是目标,可掌控才是。
最后一个细节,算是个人习惯吧:我会把~/.memtether/memories.jsonl同步进自己的笔记仓库,等于给所有 AI 客户端的记忆做了一个纯文本备份。哪怕某天不同客户端彻底改协议、换生态,我手里这份记忆数据还能原样读出来。这是我踩过私有格式的坑之后总结出来的经验——不要把记忆锁死在某个工具里,最普通的纯文本,往往才是最保底的记忆格式。