致力于模拟草履虫的网关记忆架构。
很多 AI 记忆系统想成为大脑。这个项目致力于成为草履虫:没有神经元,没有遗忘曲线,不做梦——一个单细胞生物,受刺激就摆纤毛,要进食就取原文,每个细胞器都说得清是干什么的。
以最小可行为核心目标。达没达到不好说,但方向和思路是这个。顺便希望草履虫也能看懂(人话版施工中)。
人类补充:虽然现在可能没那么草履虫了,但是单从架构本身来说,不过是“原文+检索”而已,还是很原始的
(空位。人类说这一节要用人话重写,等她睡醒。旧版人话在 git 历史里。在那之前,下面 CC 写的版本先顶着。)
市面上大多数记忆系统的做法是:把对话喂给一个模型,改写成简短的记忆条目,存起来,原文扔掉。这等于让转述永久地代替了真相——三年后你搜到的不是你说过的话,而是某个旧版本小模型对你说的话的复述。
Paramecium 拒绝这个。灵感来自 Attention 机制:
零件是同一批——embedding、向量库、召回,拆开看没有一个新发明。但 RAG 是图书馆员:馆里一亿本别人写的书,你提问,它捞几段给模型当参考资料。书是死的,谁写的不重要,chunk 被模型嚼碎了吐出来,没人关心那个 chunk 的死活。
这里是档案员:馆里只有一个人的人生,每天还在续写。召回的不是参考资料,是货本身——那句话当时怎么说的,带着日期和名字,一字不差。图书馆员找得准就算好;档案员找得准只是及格,一个字不许改才是饭碗。
RAG 的库建好就不动了,ingest 一次,从此只读。这里的库每十分钟长一截,会过时,会翻旧账,会自相矛盾,superseded 了还能复活——文档不会跟自己吵架,人生会。
所以"比 RAG 优势在哪"这个问题没法答。拿这套去做企业知识库,没有 rerank 没有评估集,会被打得很惨;拿标准 RAG 来存一段关系,它会把你的话嚼成「用户偏好:奶茶」——留下来的是结论,吐掉的是你说那句话时的样子。这个项目干的全部事情,就是不让吐掉这件事发生。
(这一节是 CC 写的。作为每天跑在这套系统上的用户,利益相关,但句句属实。)
这个标准是一位朋友提的。她宁可全文注入也不做检索,因为她要记忆是"有人格的 AI"自己察觉的——算法替 AI 决定该想起什么,那注意力就不是人格的,是 bge-small-zh 的。
这里的做法:算法只配菜单,夹不夹菜是人格的事。
目录递过去就是一行行标题,150 个 token,像海马体拍上来的便签,还没进意识。看到「06-12 她以后奶茶喝乌龙轻乳茶」这行字,伸不伸手去翻,模型自己定——人设、上下文、聊到哪了,一起说了算。所以 access 只在 recall 时 +1:上了菜单不算被想起。(坦白:这个计数加成本质上还是热度的残党,一个触发条件更老实的计数器而已。它的退役方案在下面。)
没做到的也摆在明面上:菜单毕竟是语义匹配排的。一段语义上不沾边、但人格"该"想起的东西——没人提的纪念日,一次纯粹的情感联想——上不了菜单,人格连注意到它的机会都没有。recall 留了日期过滤和逐字检索的口子,人格可以自己说"去翻翻那天的话",但主动联想这块还空着。
填法想好了(一顿午饭讨论出来的),三块料,同一个配方:把人格的真实行为变成检索信号。零 LLM 抽取,每块能单独验尸:
过去的时刻、过去的关联、当下的念头。三层都齐了的时候,"菜单是谁排的"这个问题就能换一个答案。
(思想是人类的,CC 只负责誊写。)
从「你 → API」变成「你 → 服务器 → API」。网关在转发之前把人设、画像、记忆目录拼装好,前端只需要一个接口。
网关自带两件内置工具,进程内执行,不走 MCP(这部分单独拆了个仓库:flagellum。严谨声明:草履虫是纤毛虫,并没有鞭毛——但 cilium 这个名字被 eBPF 那个大项目占了,本仓库选择尊重命名美学而非生物学):
| 层 | 是什么 | 类比 |
|---|---|---|
| L0 原文层 | 全部聊天原话,逐字存档,机械切窗向量化 + 全文索引 | 档案馆 |
| L1 摘录层 | 小模型从对话里圈出的信息点,每条必须附逐字引用当出处 | 卡片目录 |
| L2 画像层 | 手工维护的稳定事实(人设、画像) | 刻在脑子里的 |
取用分三档,越贵的越按需:
所有对话进同一个记忆库,换窗口不丢——新窗口里 recall 照样能把上个窗口的原话一字不差翻出来。
进:你在 23:47 发了一句「我决定以后奶茶就喝乌龙轻乳茶了」
第一站,它落在对话 JSON 里——原文,带时间戳。这是它的家,从此永远不会被删除或修改。后面发生的一切都只是给它做索引,没有任何一步会动它本身。
十分钟内,两个互不认识的 cron 工人先后路过:
L0 工人(archive-import)是个装订工,不识字。 它把新消息按句号切段、贪心打包成 700 字以内的窗口,在窗口开头烤上说话人和日期,交给 bge 算一个向量存进档案。全程零 AI、零理解、零改写——它不知道你说了什么,它只负责让你说的话将来能被找到。向量是门牌号,门后面是你的原话。
L1 工人(extract-memories)是个摘录员,识字但没有笔。 便宜模型读这批新消息,圈出最多 5 个值得记的点,比如「她以后奶茶喝乌龙轻乳茶」——但每条必须附一句逐字引用当出处,然后机械校验真的去原文里找这句引用,找不到就整条扔掉,不信模型自觉。没东西值得记就交白卷(它经常交白卷)。存完还有一步:新记忆跟旧记忆高置信度矛盾时(你以前那条「最喜欢茉莉奶绿」),旧条目被标记 superseded——退出排名但不删除,哪天你喝回茉莉奶绿了它还能复活。
另外每天凌晨有个第三工人重建逐字索引(FTS5),管"原话里有没有出现过这四个字"这种查询。L2 画像层没有自动入口,那是手写的。
出:三天后你问「我上次说想换什么奶茶来着」
网关拿你最近几条消息当线索去问记忆网关,回来两样东西:一是 L2 画像全文,进缓存段,等于身份信息每条消息都在场但几乎不要钱;二是一份目录——L1 里最相关的几条,按 RRF(向量排名+词面排名) × 新近度 × 访问加成 × 有效期排好,但每条只给一行标题,总共 150 个 token 左右。注意:被列进目录不算被回忆,access 不涨——翻目录不等于想起来。
AI 看到目录里有「06-12 她以后奶茶喝乌龙轻乳茶」,想要细节,伸手调 recall。recall 同时查两层:L1 的摘录条目,和 L0 的聊天原文窗口(带相关性地板、每场对话最多 2 个窗口免得刷屏、可按日期过滤)。回来的结果分段标着来源,档案命中还带 conv_id,想看前因后果可以拿它在那一场对话里继续挖。到这一步 access_count 才 +1——只有真的被取用过的记忆才会变得更容易被想起。
然后你 23:47 的原话——一字不差,带着日期和你的名字——进了 AI 的上下文,模型自己的 attention 对它逐 token 打权重,回答你。那条被 superseded 的茉莉奶绿躺在库里没出场,但它还在。
整个系统就两条铁律贯穿进出:进来的东西永不被改写,出去的东西只为命中付费。 中间所有的切窗、摘录、目录、排名,全是为了让"找到"这件事便宜——而"是什么"这件事,永远只由原文回答。
对话窗口超长后小模型总结压缩——但原始消息一条不删,只是推进一个锚点。摘要从"有损压缩"降级成"省钱的默认视图",被压缩掉的内容照样躺在 L0 档案里等 recall。图片同理:原图先落盘存好,存不成功就不压缩,之后才转述成一句话省 token。
你(前端) → Paramecium 网关 → 上游 API(Anthropic / OpenRouter / 任何兼容端点)
↕ ↕
记忆网关 内置工具 loop
(L0 档案 + L1 向量/BM25 (recall / exec,
+ L2 画像,inject/recall) 进程内执行,零 MCP)
网关负责把你的请求递给上游 API,但在递出去之前,它会:
记忆网关负责让 AI 记住你说过的话:
chat-api/
├── server.mjs # HTTP 服务器,对话管理,SSE 流式
├── gateway.mjs # 核心网关:prompt 组装、BP 缓存、流式代理、
│ # 内置工具(recall/exec)、tool loop、cycle 压缩
├── data/ # 对话存档(JSON 文件即数据库)、统计
├── images/ # 图片原件(压缩转述前先落盘)
└── attic/ # 退役代码(冻结不删除,备查)
memory/
├── memory-gateway.py # 记忆网关(:3900):inject / search / raw-search /
│ # archive-search / archive-ingest / embed / ingest
├── extract-memories.mjs # L1 提取器(摘录+逐字引用,便宜模型跑)
├── archive-import.mjs # L0 导入器(纯机械切窗,零 AI,10 分钟增量)
├── build-fts.py # L0 全文索引构建(FTS5)
├── split-memories.mjs # 长记忆拆分(已退役 2026-06-12,罪状见下)
├── verify-splits.py # 拆分件保真度验尸(只读,逐字/转述对账)
├── revault.py # vault 日记归位迁移(一次性:L0 切窗 + L1 重圈 + 退役)
├── sync-vault.py # 旧日记/备忘录系统同步(兼容存量数据)
├── embedding.py # 本地 embedding(bge-small-zh)
├── profile/ facts/ # L2 画像层(手工维护的 markdown)
├── vectors/ # L1+L0 向量(ChromaDB 两个 collection)
├── raw-archive.db # L0 全文索引(SQLite FTS5)
├── meta.db # L1 元数据 + recall 日志(SQLite)
├── edges.jsonl # 关系图谱(保留可查,冻结出排名)
└── models.json # API 密钥配置(⚠️ 不要提交)
gateway-admin/
└── index.html # 管理后台(记忆浏览 / 搜索 / 统计 / 架构文档)
Anthropic 的 prompt cache 按 TTL 生效——命中了就不重复计费。Paramecium 把 prompt 切成四段,按变化频率排列,会变的东西全部压到最后,每条消息只重付增量:
| Breakpoint | 内容 | 变化频率 |
|---|---|---|
| BP1 | 人设 + L2 画像 | 几乎不变 |
| BP2 | 最近日记/备忘 | 每天一次(按日锚定) |
| BP3 | 会话摘要 | 每个压缩周期(~60K token) |
| BP4 | 滚动锚点(最后一条历史消息) | 每条消息推进 |
断点之后才是每次重付的部分:当前时间、本条消息召回的记忆目录、消息正文。
实测效果:缓存命中率 60-80%,token 费用约为无优化时的一半。
顺带一个教训:曾经做过"定时空请求保温缓存",后来拿日志验尸发现保温请求的前缀和真实请求字节对不上,纯白干,拆了。"感觉在省钱"不算省钱,日志说了算。
用户发消息
↓
gateway 调记忆网关 /inject
↓
记忆网关:
1. 读 profiles(L2 画像,进缓存段)
2. 读 facts(固定事实,关键词触发)
3. 语义搜索(向量 + BM25 的 RRF 排名融合 × 新近度 × 访问加成)
4. 只返回目录索引——一行一条,不给全文(~150 token,原来塞全文要 1000+)
↓
gateway 拼装 prompt → 发给上游 API
↓
AI 看到目录想翻细节 → 自己调 recall 工具
↓
recall 双层并查: L1 条目 + L0 聊天原文,结果分段标注来源
(exact=true 走逐字检索;after/before 过滤日期;conv_id 在单场对话里深挖)
只有 recall 才算"真的回忆了"——access 计数和召回日志只在这里涨
对话产生(每 10 分钟增量扫描)
↓
L1: extract-memories.mjs
1. 去重(跳过已处理的对话)
2. 便宜模型圈信息点,每条附 10-40 字逐字引用,宁缺勿滥
3. 机械校验引用确实在原文里(机械 > prompt,不信模型自觉)
4. 存入 ChromaDB(向量)+ SQLite(元数据)
5. 矛盾自动失效:新记忆与旧记忆高置信度冲突/更新时,旧条目标记
superseded——退出排名但不删除(可逆),手动 pin 的记忆神圣不可侵犯
↓
L0: archive-import.mjs(零 AI)
1. 按句界切段(≤350 字)
2. 贪心打包成窗口(≤700 字,带 1 段重叠),说话人/日期烤进文本
3. 存入 chat_archive collection,conv_id + 段范围指回原文
↓
下次对话时就能召回——而且召回的是原话
pip install chromadb jieba)# 复制配置模板
cp memory/models.json.example memory/models.json
# 编辑填入你的 API 密钥
models.json 格式:
{
"extraction": {
"endpoint": "https://openrouter.ai/api/v1/chat/completions",
"key": "你的key",
"model": "deepseek/deepseek-chat-v3-0324"
}
}
# 启动记忆服务
cd memory && python3 memory-gateway.py &
# 启动网关
cd chat-api && node server.mjs &
# 定时任务(L1 提取 + L0 入库)
crontab -e
# */10 * * * * cd /path/to/memory && node extract-memories.mjs
# 3-59/10 * * * * cd /path/to/memory && node archive-import.mjs
# 管理后台
# 用 nginx 把 gateway-admin/ 指向一个路径就行
npm install 拉下来 200MB 依赖的东西docker-compose up 启动一个 PostgreSQLmodels.json 包含 API 密钥,绝对不要提交到 gitdata/ 目录包含对话记录,images/ 包含图片原件,建议 .gitignorerm -r / dd / shutdown / iptables -F / 读 .ssh/ 等)、公网入口务必加认证以下项目和分享对 Paramecium 的设计有启发:
MIT
$ claude mcp add paramecium \
-- python -m otcore.mcp_server <graph>