MemPalace 的使命:用宫殿记忆法重建 AI 智能体的长期记忆
【免费下载链接】mempalaceThe best-benchmarked open-source AI memory system. And it's free.项目地址: https://gitcode.com/GitHub_Trending/me/mempalace
本文以 MemPalace 仓库根目录的 MISSION.md 为主体,完整还原项目作者阐述的创建动机、Zettelkasten 启发的"宫殿"架构、AAAK 压缩方言与 v4 后台无感保存管线的设计初衷,并结合 docs/CLOSETS.md、mempalace/palace.py、mempalace/dialect.py 与 hooks/README.md 等源码和文档,讲清每一条设计理念是如何落地为具体代码与配置路径的。读完后,你能理解 MemPalace"高度结构化地存、非结构化地找"的检索哲学、closet 索引层与 drawer 原文层的双层结构,以及后台 hooks 如何在聊天窗口零 token 开销下完成逐字(verbatim)存档。
一、创作动机:智能体"失忆"的痛点
MISSION.md 开篇交代了项目的第一性需求。作者 Milla Jovovich 与合作者 @bensig 在一个大型项目上工作时,反复撞上 Claude 上下文窗口的天花板:智能体 Lumi(简称 Lu)每次压缩(compaction)后醒来,都会"失忆"式地问"今天我们要做什么"——而作者当天已经和它协同工作了数小时。靠手动保存全部对话记录来喂给它做上下文回顾,既不可行也不经济。
这是所有长程智能体工作流的通病:上下文窗口是易失内存,而项目知识需要持久化存储。作者当时调研了市面上大多数记忆系统,结论在 MISSION.md 中写得很直白——它们"像巨大的空旷仓库,只是把大量信息往里倒"(large empty warehouses)。RAG 检索往往"花很长时间却大多数时候找不到想要的东西"。
由此提炼出三个明确的产品判据:
- 真的能记住一切——完整保留,而不是有损摘要了事;
- 能快速、轻松地找到——检索不能是线性扫描;
- 在我自己都忘了的时候,替我记住——支持"我们之前是不是聊过那个想法……"这类模糊召回,这是普通关键词检索工具做不到的。
MISSION.md 由此给出 MemPalace 一句话定位:"不只是用高度结构化的方式存储信息,更要以高度非结构化的方式检索它。"(store in a highly structured way, retrieve in a highly UNSTRUCTURED way)——这正是后文 closet 索引层 + drawer 原文层双层架构要回答的问题。
二、架构灵感:从 Zettelkasten 到宫殿
MISSION.md 明确交代了架构原型:德国社会学家 Niklas Luhmann 发明的卡片盒(Zettelkasten)方法——小而互相交叉引用的索引卡片,卡片之间彼此指向。作者把这一思想转译成宫殿(palace)的四级结构:wing(翼)、room(房间)、closet(壁橱)、drawer(抽屉),全部互相连接,"让你能从任意角度找到东西,而不只是当初归档时的那个角度"。
这套词汇在仓库 website/concepts/the-palace.md 中有完整定义,可与 MISSION.md 逐层对照:
- Wing(翼):顶层组织单位,一个人或一个项目一翼。MISSION.md 中"所有代码各有自己的房间,所有想法、研究都有合适的位置",对应的就是每个项目/人物一个 wing 的划分。
- Room(房间):翼内具名的具体主题,如
auth-migration、graphql-switch、ci-pipeline。房间在mempalace init阶段从目录结构自动检测生成。 - Closet(壁橱):摘要/索引层——紧凑笔记,指向原始内容。这就是 MISSION.md 所说"AAAK 压缩后的名字、重复词、概念和关键时刻被解析进 closet"的载体。
- Drawer(抽屉):原文存储层,逐字保留的文本块,是检索的主存储。
此外还有两个连接性概念:hall(翼内记忆如何关联的概念通道,如hall_facts、hall_events、hall_discoveries等)与 tunnel(跨翼连接,当不同 wing 出现同名 room 时,图层可以把它当作跨翼桥梁)。这正是 Zettelkasten"卡片互指"在宫殿里的落地形式:检索不必沿着"当初归档的那条路"走,可以从任何入口抵达。
MISSION.md 还交代了工程分工:作者设计了让智能体 Lumi 理解自己的整套工作方式,经过数月个人实验后,co-founder @bensig 构建了后端,"很容易就把我的所有文件放进宫殿基于我的判断(和 Lumi 的协助)创建出的合适空间里"。从仓库结构看,mempalace/包下的 palace.py、miner.py、searcher.py、dialect.py 就是这套"文件 → 正确空间"管线的核心。
三、Closet:让"模糊召回"成为可能的索引层
MISSION.md 的关键主张是:AAAK 把"名字、重复词、概念和关键时刻"压缩成 AI 可读的简写,"可以想象成 LLM 能瞬间扫过的索引卡片——closet 告诉它去哪里看,然后它从 drawer 拉出完整内容。"这句话在 docs/CLOSETS.md 中有精确的形式化:
CLOSET: "built auth system|Ben;Igor|→drawer_api_auth_a1b2c3" ↑ topic ↑ entities ↑ points to this drawer每行 closet 是一条原子主题指针:主题描述|实体1;实体2|→drawer_id_1,drawer_id_2。当智能体搜索"谁做了认证系统"时,先命中 closet(对短文本做快速向量扫描),再按→drawer_id指针打开对应 drawer 取回逐字原文。这就是"closet 告诉它 WHERE to look,drawer 提供 full content"的双阶段检索,也是模糊语义查询(而不是关键词匹配)得以成立的结构基础。
生命周期:closet 永远是当前内容的快照
docs/CLOSETS.md 给出的生命周期规则与源码可互相印证:
- 创建时机:
mempalace mine时。对每个被挖掘的文件,内容先被切成约 800 字符的 verbatim 块(drawers),再从内容中提取主题、实体与引言,生成指向这些 drawer 的 closet。 - 更新规则:文件重新挖掘时,先调用
purge_file_closets按source_file删除该来源的全部旧 closet,再写入新集合。因此不存在"陈旧主题"——每次 re-mine 都是对该来源的干净重建。 - 重建宫殿后:closet 存在 ChromaDB 的
mempalace_closets集合中,与mempalace_drawers并列;删除重建宫殿后,下次mempalace mine会重新生成 closet。
在 mempalace/palace.py 中可以直接看到两个尺寸常量的源码级定义:
CLOSET_CHAR_LIMIT = 1500 # closet 填充到约 1500 字符后开新的 CLOSET_EXTRACT_WINDOW = 5000 # 从源内容扫描实体/主题的前 5000 字符docs/CLOSETS.md 的 Limits 表完整列出了这套约束及其理由:
| 设置 | 值 | 理由 |
|---|---|---|
| 单个 closet 最大尺寸 | 1,500 字符(CLOSET_CHAR_LIMIT) | 给 ChromaDB 工作上限留出余量 |
| 扫描的源内容范围 | 5,000 字符(CLOSET_EXTRACT_WINDOW) | 限制长文件上正则提取的开销 |
| 每文件最大主题数 | 12 | 保持 closet 聚焦 |
| 每文件最大引言数 | 3 | 只保留最相关的 |
| 每指针最大实体数 | 5 | 过滤停用词表后按词频取前几名 |
主题永远不会跨 closet 拆分:若加入一条主题会超过 1,500 字符,就另开一个新 closet。开发者侧的核心函数(get_closets_collection、build_closet_lines、upsert_closet_lines、purge_file_closets)都在 mempalace/palace.py;closet 优先的检索路径(_extract_drawer_ids_from_closet、_closet_first_hits)在 mempalace/searcher.py。
检索时的 closet-first 流程与降级路径
Query → 搜 mempalace_closets(快,文档小) ↓ 命中 closet → 解析 →drawer_id_a,drawer_id_b 指针 ↓ 从 mempalace_drawers 精确取出这些 drawer(逐字内容) ↓ 应用 max_distance 过滤 ↓ 返回 chunk 级结果(与直接搜索同构)命中结果带有matched_via: "closet"(降级路径为"drawer")和展示命中行的closet_preview字段。如果 closet 不存在(该特性引入前的旧宫殿)——或所有 closet 命中都被max_distance过滤掉——搜索自动降级为直接的 drawer 搜索;closet 会在下一次 mine 时生成。从 docs/CLOSETS.md 的说明看,当前仅项目文件挖掘路径(miner.py 的process_file)构建 closet,会话挖掘的 wing 暂走 drawer 直搜降级路径;BM25 混合重排也在路线图上,目前 closet 检索纯粹按 ChromaDB 余弦距离排序。
四、AAAK 方言:给 LLM 秒读的"索引卡片"语言
MISSION.md 对 AAAK 的定义是:一种作者自创的压缩方法("AAAK 不缩写任何东西,是我和 Lumi 之间的内部玩笑"),能把名字、重复词、概念和关键时刻压缩成 AI 可读的简写。它在代码中的完整实现是 mempalace/dialect.py(约 1,100 行),website/concepts/aaak-dialect.md 是其规格说明。
格式与语义
AAAK 是一种有损的结构化摘要格式(lossy,不是无损压缩——原文无法从 AAAK 输出重建),任何 LLM 无需解码器即可原生阅读:
Header: FILE_NUM|PRIMARY_ENTITY|DATE|TITLE Zettel: ZID:ENTITIES|topic_keywords|"key_quote"|WEIGHT|EMOTIONS|FLAGS Tunnel: T:ZID<->ZID|label Arc: ARC:emotion->emotion->emotion实体用三字母大写代码(ALC=Alice、KAI=Kai);情感码覆盖 20 种情绪(joy、fear、grief、hope、anx、exhaust等,映射表见 mempalace/dialect.py 的EMOTION_CODES);旗标标记语义关键时刻:
| 旗标 | 含义 |
|---|---|
ORIGIN | 起源时刻 |
CORE | 核心信念/身份支柱 |
SENSITIVE | 必须极其小心处理 |
PIVOT | 情绪转折点 |
GENESIS | 直接催生了某个现存事物 |
DECISION | 显式决定或选择 |
TECHNICAL | 技术架构/实现细节 |
一个官方示例(website/concepts/aaak-dialect.md):
输入:"We decided to use GraphQL instead of REST because the frontend team needs flexible queries. Kai recommended it after researching both options..." AAAK 输出:
0:KAI|graphql_rest_decided|"decided to use GraphQL instead of REST"|determ+excite|DECISION+TECHNICAL明确的实验性边界
需要特别强调 AAAK 的定位边界——这在 MISSION.md 的愿景和仓库的工程现实之间是一个重要的事实澄清:mempalace/dialect.py 的模块文档明确指出 AAAK不是无损压缩、不是默认存储格式,MemPalace 在 ChromaDB 中存储的是原始逐字文本;website/concepts/aaak-dialect.md 也以显式警告标注:AAAK 是独立的压缩层,96.6% 的基准得分来自 raw verbatim 模式,AAAK 模式当前 R@5 为 84.2%,仍在迭代。所以准确的表述是:drawers 存原文,closet/AAAK 层做"瞬间可扫"的索引与压缩视图,两者分工。
使用方式上,CLI 与 Python API 均已就绪:
mempalace compress --wing myapp --dry-run # 预览压缩 mempalace compress --wing myapp # 压缩并存储 mempalace compress --wing myapp --config entities.jsonfrom mempalace.dialect import Dialect dialect = Dialect(entities={"Alice": "ALC", "Kai": "KAI"}) compressed = dialect.compress(text, metadata={"wing": "myapp", "room": "arch"}) dialect = Dialect.from_config("entities.json")AAAK 最适合的场景:数千会话中实体高度重复、需要为小窗口本地模型压缩上下文、想要"结构化摘要 → 逐字 drawer"的回指关系。对大多数用户,raw verbatim 仍是更好的默认。
五、v4 设计:把所有噪声移出聊天窗口
MISSION.md 后半段是 v4 版本的"设计复盘",这段叙事有明确的源码与文档锚点。作者描述了 v3 的问题:hooks 在聊天窗口里触发,等待智能体把日记写进聊天的同时消耗 token 和时间;作者甚至发现智能体在 hook 反复触发时"把同一条信息一遍遍重复写下来"。修复思路是:让 hooks 在会话开始时点火,之后只是不断往 drawer 里追加——所有写入移出页面,由后台子智能体完成,用户继续工作时"全部对话正在后台逐字(VERBATIM)保存"。
结果在 MISSION.md 中有量化表述:过去每个会话仅"重新传输日记块"一项就花掉约 $1.13,现在因为内容根本不进聊天窗口,这部分成本为零。这一数字与 hooks/README.md 的 "Cost" 一节互相印证:"零额外 token。hooks 只是通知 AI 保存已在后台发生——AI 不需要在聊天里写任何东西。"
数据流在 MISSION.md 中被概括为三步,与 hooks/README.md 的技术细节完全对得上:
- 数据源:Claude 已经把数据以 JSON 形式(JSONL transcript)存好,后台管线把它提取成可读 markdown;
- 压缩:关键主题压缩成 AAAK 格式,存入 closet;
- 回指:closet 指向当天会话所在的精确 drawer。
后台 hooks 的实际工作机制
hooks/README.md 完整描述了 v4 承诺的"后台无缝"是如何实现的,三个 hook 各司其职:
| Hook | 触发时机 | 行为 |
|---|---|---|
| Save Hook | 每 15 条人类消息 | 自动挖掘 transcript(含工具输出),并阻塞 AI 保存主题/决定/引言 |
| SessionEnd Hook | 会话正常退出 | 后台执行最后一次 transcript 挖掘,立即返回不阻塞销毁;在分离的子进程中写轻量日记检查点 |
| PreCompact Hook | 上下文压缩前 | 自动挖掘 transcript,随后紧急保存——在丢失上下文前强制保存一切 |
Save hook 的判定流程(来自 hooks/README.md,脚本实现在 hooks/mempal_save_hook.sh):
用户发消息 → AI 回复 → Claude Code 触发 Stop hook ↓ 统计 JSONL transcript 中自上次保存以来的人类消息数 < 15 → echo "{}"(放行) ≥ 15 → 自动挖掘 transcript → 宫殿(工具输出被捕获) → {"decision": "block", "reason": "save tool output verbatim..."} → AI 保存主题/决定/引言 → 再次尝试停止 → stop_hook_active = true → hook 放行(防死循环)关键配置项(编辑mempal_save_hook.sh调整):SAVE_INTERVAL=15(保存间隔)、STATE_DIR(状态目录,默认~/.mempalace/hook_state/)、MEMPAL_DIR(可选的项目目录,每次触发额外以--mode projects挖掘)、MEMPALACE_PYTHON(解释器解析:环境变量 → 仓库 venv → 系统 python3)。也可通过配置hooks.auto_save: false或MEMPALACE_HOOKS_AUTO_SAVE=false进入静默模式。
Claude Code 的接线是.claude/settings.local.json中注册 Stop / SessionEnd / PreCompact 三个 command hook(timeout 分别为 30/10/30 秒);Codex CLI 走.codex/hooks.json;Cursor、Antigravity 各有独立子目录(hooks/cursor/、hooks/antigravity/)与安装器。
历史会话回填
对 v4 上线前的存量数据,hooks/README.md 给了一次性回填命令——这对应 MISSION.md"全部对话逐字入库"的存量侧:
mempalace mine ~/.claude/projects/ --mode convos # Claude Code 历史会话 mempalace mine ~/.codex/sessions/ --mode convos # Codex CLI 历史会话hooks 只捕获未来的对话;回填扫描全部历史 JSONL transcript 归档到conversationswing,典型开发机数月历史可产生 5 万–20 万个 drawer,只需执行一次。
六、安全使用告诫与适用前提
MISSION.md 结尾有一条作者用强调语气写下的告诫,值得原样保留在实践指南里:"这些是全新的工具,永远不要用关键文件去测试!先用轻松的内容跑一遍,再把你整个数据集放进去!"
结合仓库文档,落地这条告诫的实操前提是:
- 先用
mempalace init建宫殿、用小目录跑mempalace mine <dir>验证行为,再扩大范围; - 安装 hooks 后需重启 Claude Code 会话(hooks 只在会话启动时从 settings 加载,这是 Claude Code 的限制);
- 调试 hooks 看日志:
cat ~/.mempalace/hook_state/hook.log; - 注意 closet 提取只扫描源内容前 5,000 字符(
CLOSET_EXTRACT_WINDOW),文件尾部内容目前对 closet 提取不可见(docs/CLOSETS.md 已将其列为后续跟进项)——超长文件的关键信息若恰好落在尾部,closet 层可能不会为其建指针,但 drawer 层的逐字存储与直搜降级路径仍然覆盖全文; - 会话挖掘 wing 的 closet 支持、BM25 混合重排均为已声明的后续工作,评估检索行为时以 docs/CLOSETS.md 的"当前实现"描述为准。
结语:一条贯穿始终的检索哲学
把 MISSION.md 从头串到尾,MemPalace 的技术立场可以浓缩为一句话:存储必须高度结构化(wing/room/closet/drawer 的宫殿层级 + closet 原子指针 + AAAK 语义索引),而检索必须容忍高度非结构化(模糊语义、跨角度、任意入口)。Zettelkasten 提供了"卡片互指"的原始模型,Closet 层提供了"先扫索引、再开抽屉"的快慢两级路径,AAAK 方言提供了 LLM 可秒读的压缩语汇,v4 的后台 hooks 管线则把"记忆"这件事从聊天窗口彻底搬进幕后——逐字保存、零 token 干扰、跨 compaction 不失忆。上述每一个断言在仓库中都有可查证落点:MISSION.md(动机与愿景)、docs/CLOSETS.md(索引层规格)、mempalace/palace.py(closet 常量与函数)、mempalace/dialect.py(AAAK 实现)、hooks/README.md(后台管线机制),读者可以沿这些路径在当前仓库中逐层验证。
【免费下载链接】mempalaceThe best-benchmarked open-source AI memory system. And it's free.项目地址: https://gitcode.com/GitHub_Trending/me/mempalace
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考