MemPalace 源码级技术方案分析报告

项目定位与总体判断

MemPalace 本质上不是“神奇记忆模型”,而是一套极度克制的本地记忆基础设施

MemPalace 的源码给人的第一印象,并不是“模型创新”,而是工程取舍非常明确。它没有构造新的训练范式,也没有自研向量数据库,更没有把复杂的 agent orchestration 硬塞进一个巨型框架里。相反,它把问题压缩成一个非常朴素但现实的命题:用户与 AI 的长期交互之所以无法形成“记忆”,核心并不是模型不会推理,而是历史信息在会话结束后无法被稳定归档、低成本召回与结构化复用。 因此,MemPalace 的真实产品定义更接近一个“离线可部署的长期记忆操作系统”,而不是一个单点算法。

README 对这一点说得很直白:系统强调的不是“让 AI 判断什么值得记”,而是“把所有内容保留下来,再让系统变得可检索”。这种设计路线,直接反对了大量记忆系统依赖摘要、抽取或 LLM 过滤的常见思路。 从源码实现看,这不是营销措辞,而是被贯穿到底的工程原则:无论是 miner.py 的项目文件入库、convo_miner.py 的会话导入、searcher.py 的原文检索,还是 layers.py 的分层唤醒接口,都尽量把摘要和解释延后,把原文保存和元数据路由前置

这里最值得注意的反常识点是:MemPalace 的强项不是“懂得多”,而是“忘得少”;不是“压缩得狠”,而是“保存得真”。 README 甚至公开承认,Headline benchmark 的高分来自 raw mode,而不是更“炫”的 AAAK 压缩模式。这在开源项目里并不常见,因为多数项目会把实验性功能包装成核心卖点,但 MemPalace 在源码与文档里反而把最有效的部分落在最朴素的路径上:把原文扔进 ChromaDB,然后用语义检索与轻量元数据过滤做召回。

判断维度 结论 源码依据
产品本质 本地长期记忆基础设施,而非新模型 README.mdcli.pylayers.py
核心哲学 Store everything, then make it findable README.mdminer.pysearcher.py
主能力边界 导入、归档、检索、唤醒、MCP 工具暴露 cli.pymcp_server.py
真实性能来源 Raw verbatim storage + Chroma semantic search README.mdbenchmarks/longmemeval_bench.py
实验性能力 AAAK 压缩、图遍历、知识图谱、通用记忆抽取 dialect.pypalace_graph.pyknowledge_graph.pygeneral_extractor.py

为了从架构视角把项目看清楚,可以先把它抽象成四条主链路:初始化链路、导入链路、检索链路、对外能力暴露链路。初始化链路负责生成翼区与房间定义;导入链路负责把项目文件或对话记录变成 drawers;检索链路负责通过 ChromaDB 与 metadata filter 完成查找;对外暴露链路负责把这些能力包装成 CLI、MCP 和 wake-up 文本。

从这个图可以看出,MemPalace 并不复杂,但它的复杂度被有意识地固定在数据进入系统之后的“第二层”:即统一的 collection、统一的 metadata schema、统一的查询接口。一旦这一层稳定下来,前端可以接项目文件、聊天日志、MCP、CLI;后端可以接唤醒层、语义搜索、知识图谱甚至图遍历。这是一个典型的以数据面为中心、以接口面为延展点的架构,而不是“每个功能自己维护一套状态”的拼装式系统。

“Other memory systems try to fix this by letting AI decide what's worth remembering… MemPalace takes a different approach: store everything, then make it findable.” —— 项目 README

这种设计有明显优点。第一,它极大减少了记忆系统最常见的“摘要失真”问题,因为原文仍在;第二,它把系统效果更多地交给检索质量,而不是交给一个不可解释的“记忆提取 prompt”;第三,它天然适合离线环境,因为从导入到检索都不要求远程 API。但它也有代价:一是存储会更大,二是早期结构设计一旦糟糕,后续检索体验会迅速恶化,三是很多“聪明功能”实际上只是建立在已有 metadata 上的轻量操作,而不是真正强语义的知识推理。README 在更新说明里对这些边界也有相当坦诚的说明。

技术栈与运行时骨架

一个依赖极简、接口层清晰、数据层统一的 Python CLI 项目

pyproject.toml 可以看出,MemPalace 是一个标准的 Python CLI 包,要求 Python 版本为 3.9 及以上,构建系统使用 hatchling,暴露出的脚本入口为 mempalace = "mempalace:main"。这意味着整个仓库首先是“命令行应用”,其次才是“可嵌入的库”。这一点在 __init__.py__main__.py 的设计里也能得到印证:包级入口几乎只是把 main 转发给 CLI,而大部分核心功能都由独立模块承载。

更值得关注的是它的依赖策略。项目硬依赖只声明了 chromadbpyyaml 两项。这在 AI 基础设施项目里非常少见,因为大多数项目会引入 embedding SDK、HTTP 客户端、prompt orchestration 框架甚至任务图引擎。MemPalace 则把架构重心刻意压在本地数据库与文件系统上:pyyaml 用于读 mempalace.yamlchromadb 负责持久化向量存储与查询,其余行为尽量依赖 Python 标准库完成,例如 ospathlibhashlibjsondatetimeargparsecollections

维度 选型 作用 工程含义
语言 Python 3.9+ 主实现语言 CLI 与本地数据处理友好
包管理/构建 Hatchling 打包与发布 轻量标准化
配置格式 JSON + YAML 全局配置、项目房间配置 兼顾可编程与可读性
核心存储 ChromaDB PersistentClient 向量持久化与检索 单机场景部署门槛低
对外接口 CLI + MCP 用户操作与 Agent 集成 兼容工具型 AI 工作流
图数据 基于 metadata 派生,不引入单独图库 图遍历与 tunnel 发现 保持数据面统一
知识图谱 本地 SQLite 实体关系与时序事实 与向量库解耦

这种“依赖极简”的技术栈,一方面有非常大的工程优势。它让仓库的安装、阅读和扩展门槛都很低,用户可以较快建立心理模型。cli.py 里所有子命令都直连具体模块,调用关系几乎是平铺的:init 对应实体检测和房间检测,mine 分流到项目挖掘或对话挖掘,search 直接走 searcher.searchwake-up 通过 MemoryStack 聚合四层记忆,statusrepair 分别负责数据巡检和索引修复。这种入口设计极其利于源码级理解,因为主流程没有被框架魔法隐藏。

另一方面,依赖极简也意味着能力上限受到约束。例如,系统默认依赖 ChromaDB 自带 embedding 路径,benchmark 脚本里虽然支持 fastembed 切换为 bge-basebge-largenomicmxbai 等模型,但这部分并未被提升为主产品面的可配置核心,而更像评测与试验通道。这说明项目在“工程稳定性”与“可调优空间”之间,明显更偏前者。对于希望快速落地本地记忆系统的用户,这是优点;对于想把它当成企业级记忆平台底座的人,这又是一种约束。

config.py 则揭示了它的全局运行时骨架。系统默认把 palace 放在 ~/.mempalace/palace,collection 名称默认为 mempalace_drawers,同时保留 people_map、topic wings、hall keywords 等全局配置项。 它的配置优先级是环境变量 > 用户配置文件 > 默认值,这是非常标准且合理的设计。更重要的是,这个全局配置类被多个模块共享,从而在 CLI、Layer、Graph、MCP 等子系统之间提供了一致的路径与集合名来源。

总体而言,MemPalace 的技术栈传递出一个清晰信号:作者并不试图用复杂依赖打造“AI 框架”,而是用最少依赖拼出一个本地长期记忆闭环。 这种选择并不华丽,却与其产品承诺高度一致:免费、本地、低门槛、可验证。

核心原理与数据模型

“宫殿”并不是数据库替代品,而是向量检索之上的语义命名系统

如果只读 README,容易误以为 MemPalace 的“wing / hall / room / drawer”是一种新型数据库结构。但从源码层面看,它并没有造新数据库,而是在 ChromaDB 之上定义了一套命名明确的语义组织协议。换句话说,宫殿模型不是 storage engine,而是 metadata ontology。真正的存储仍然是 Chroma collection;真正决定语义过滤与可导航性的,是每条 drawer metadata 中持续存在的 wingroomsource_filechunk_indexadded_byfiled_at 等字段。

miner.pyadd_drawer() 很能说明这个问题。每个 chunk 被写入 collection 时,文档内容直接作为 documents 存储,drawer id 由 wing + room + source_file + chunk_index 的哈希派生,而 metadata 则写入 wingroomsource_filechunk_indexadded_byfiled_atconvo_miner.py 走的是相同数据面,只是会额外记录 ingest_modeextract_mode,并在 general 模式下把 memory_type 映射为 room。 这说明项目真正稳定的“公共协议”不是类接口,而是collection 内部的 metadata schema

抽象概念 在源码中的真实实现 语义作用
Wing metadata 字段 wing 表示项目、主体或大域
Room metadata 字段 room 表示具体议题、模块或记忆类别
Drawer collection 中的一条文档记录 最小可检索原文单元
Hall 部分模块中 metadata 字段 hall 更粗粒度的走廊/类别层
Palace ChromaDB 持久化目录 + collection 统一数据面

这套设计的关键价值,在于它把“结构化”限制在足够轻的层面。项目没有要求每一条历史必须经过复杂的知识抽取,也没有强制建立实体关系图之后才能查询。它接受一个现实:大多数长期记忆需求,首先是“把过去的话找回来”,其次才是“对过去的话做高阶推理”。因此,MemPalace 的一切上层能力——Layer 召回、图遍历、重复检测、MCP 工具——都建立在这个轻量 metadata schema 之上。

需要特别指出的是,这套宫殿结构不等于真正的层级树数据库。 在源码里,wing 与 room 更像标签,而不是严格父子目录。searcher.py 的过滤逻辑也说明了这一点:查询时只是根据 wingroom 生成一个简单的 where 条件,组合方式也只是 {"$and": [{"wing": wing}, {"room": room}]} 或单字段过滤。 这意味着“宫殿”更多是帮助人和模型建立稳定的认知地图,而非数据库层面复杂的层级索引。

这种“命名系统而非新存储”的设计还有一个额外好处:它让系统能同时兼容三种不同复杂度的工作方式。最轻量的用户只需要 raw ingest + search;中等复杂度用户可以利用 wing / room 做精细过滤;更高阶用户则可以叠加图遍历、KG、AAAK 和 general extractor。也就是说,MemPalace 的扩展策略不是替换底座,而是在统一底座上叠加不同的解释层。 这一点,是整个项目最成熟的架构判断之一。

从理论上说,这是一种向量检索 + 轻量语义标签的折中路径。它没有知识图谱系统那样强的结构表达力,也没有全文搜索系统那样对关键词的极致可控性,但在“低配置、离线、可快速起效”的目标下,这种折中非常有效。也正因为如此,README 才会明确承认“metadata filtering is a standard ChromaDB feature, not a novel retrieval mechanism”,这说明作者知道自己的创新点更偏系统组合与协议设计,而不是底层算法发明。

项目文件导入链路

从目录扫描、忽略规则、房间路由到 Drawer 落库的完整实现

miner.py 是 MemPalace 的第一条核心数据入口,也是最适合看出其工程风格的模块。它并不试图做 AST 级语义理解,也不做 LLM 摘要,而是先定义一个足够务实的文件处理流水线:扫描项目目录、应用跳过规则、读取文本、切块、路由到 room、写入 ChromaDB

扫描阶段首先由 scan_project() 驱动。可读扩展名白名单覆盖了代码、文档、配置与常见数据文件,例如 .py.ts.json.yaml.sql.toml.csv 等;同时硬编码跳过 .gitnode_modules.venvdistcoverage.mypy_cache 等生成目录。这种白名单 + skip dirs 的组合,是一种非常稳健的本地文件挖掘策略,因为它把“默认安全”放在第一位,先避免系统把大量噪音文件写进记忆库。

进一步看,miner.py 还自己实现了一套轻量 .gitignore 匹配器 GitignoreMatcher。它支持注释、否定规则、锚定路径、目录规则和 `** 级路径匹配,并通过 load_gitignore_matcher()is_gitignored() 在目录遍历过程中逐层生效。这段代码非常说明问题:作者不愿为了一个局部功能引入额外的第三方解析器,而是用足够小但功能相对完整的实现来覆盖 80% 以上实际场景。技术上看它不算复杂,但产品上看非常关键,因为真实项目目录里 .gitignore 代表着用户最自然的“别碰这些东西”的意图。

导入步骤 关键函数 作用 设计判断
文件扫描 scan_project() 遍历目录、筛选可读文件 以白名单保证稳定性
忽略处理 GitignoreMatcher / is_gitignored() 遵守 .gitignore 语义 与开发者工作流对齐
读文件 process_file() 读取文本内容 只做文本级处理
路由 detect_room() 把文件映射到 room 轻量启发式而非语义模型
分块 chunk_text() 生成 drawers 优先保留边界可读性
入库 add_drawer() 写入 ChromaDB 原文 + metadata 双写

在 room 路由方面,detect_room() 的实现非常值得分析。它的优先级是:先看目录路径是否匹配 room 名或关键词,再看文件名是否匹配,最后才是统计内容关键词出现频次,若仍无结果则回退到 general 这是一种明显“重目录结构、轻深语义”的策略。对于真实软件项目而言,这其实相当合理,因为大多数项目的文件夹命名已经天然携带模块信息,如 authbillinginfradocs。如果强行上更重的语义分类,未必能比目录结构更可靠。

分块策略由 chunk_text() 实现,默认 CHUNK_SIZE=800CHUNK_OVERLAP=100MIN_CHUNK_SIZE=50。这说明作者倾向于让每个 drawer 保持在一个适中的字符级尺度,既能让向量检索捕捉语义中心,又不至于因为过长而混入太多无关上下文。算法上它会优先尝试在段落边界或换行边界截断,若找不到合适边界再按长度切断。这种“尽量尊重自然边界”的 chunking,对代码和文档都比纯固定窗口更友好。

入库阶段 add_drawer()source_file + chunk_index 做 MD5 哈希,得到稳定的 drawer id。 这意味着同一个文件同一个 chunk 的 ID 基本稳定,从而为幂等处理提供基础。与之配套的 file_already_mined() 则通过 source_file 做快速存在性判断,如果文件已经被处理过,就可以跳过重新导入。这并不完美,因为它更像“按文件级幂等”而不是“按内容级增量更新”;但对本地工作流来说,这是一种很划算的简化。用户通常会在重大变更后重新执行 mine,而不是要求系统做复杂的 patch-level 更新。

从架构角度讲,miner.py 的最大优点是链路足够短、调试成本足够低、行为几乎全可解释。但它的短板也很明确:第一,room 路由是关键词启发式,对命名混乱或跨域文件不够稳;第二,chunking 是字符级,不具备 AST、函数边界或 Markdown section 感知;第三,已导入文件跳过机制偏粗糙,缺乏基于哈希的增量重建。这些并不是 bug,而是成本换来的边界。对一个主打“本地可用”的系统来说,这种边界是可以理解的,但如果未来要支持大型工程仓库,就需要把这些地方逐步系统化。

对话导入链路

格式归一化、交换对分块、通用记忆抽取与会话记忆落库

与项目文件链路相比,MemPalace 的第二条入口——对话导入链路——更能体现其“把异构历史统一成可检索记忆”的能力。这里涉及三个关键模块:normalize.py 负责多格式标准化,convo_miner.py 负责分块与入库,general_extractor.py 则负责在 general 模式下从对话中抽出更偏“记忆类型”的片段。

normalize.py 的角色是把不同平台的导出结构,统一转换为一种近似 transcript 的内部格式:用户发言以前缀 > 标记,后接 assistant 内容。该模块支持 Claude Code JSONL、OpenAI Codex CLI JSONL、Claude.ai JSON、ChatGPT conversations.json、Slack JSON 以及已经带 > 标记的纯文本。其思路不是构建统一消息对象层,而是先把各平台 schema 拉平为 (role, text) 列表,再通过 _messages_to_transcript() 写回统一文本格式。

这一步的工程价值非常高,因为后续 convo_miner.py 就无需再关心来源平台。chunk_exchanges() 只需要对“> 用户消息 + 后续 assistant 响应”做局部解析即可。如果能检测到足够多的 > 行,就走 _chunk_by_exchange(),以一组 Q+A 作为一个 drawer;否则就退回 _chunk_by_paragraph(),按段落或固定行数组合切块。从产品角度,这种设计显然优先服务“对话是以轮次发生的”这一认知模型,因此非常适合记住决策讨论、debug 过程、架构争论等交互式内容。

模块 核心函数 主要责任 关键启发
normalize.py normalize() 多平台聊天导出标准化 先统一 transcript,再做下游处理
convo_miner.py chunk_exchanges() 把对话变成可入库片段 Q+A 对是天然记忆单元
convo_miner.py detect_convo_room() 为整段对话分配 room 基于主题关键词轻量路由
general_extractor.py extract_memories() 提取 decisions / problems 等 不依赖 LLM 的规则抽取

convo_miner.py 有两种模式。默认的 exchange 模式把每个用户回合与紧随其后的 AI 回复拼成一个 chunk;而 general 模式则把内容交给 general_extractor.extract_memories(),从中抽出 decision、preference、milestone、problem、emotional 五类记忆片段。这一分叉非常重要,因为它体现出项目在“保真存档”和“语义抽取”之间的双轨策略:默认路径保原文结构,增强路径才做抽取分类。

深入看 general_extractor.py,会发现它完全依赖本地启发式规则工作。文件里定义了多个 marker 集,例如 decision marker 会捕捉 “we decided”“because”“trade-off”“framework”“infrastructure”等模式;problem marker 会捕捉 “bug”“error”“root cause”“workaround”等;milestone marker 会捕捉 “it works”“breakthrough”“shipped”“deployed”等。然后它通过 _score_markers() 计算分数,再用 _disambiguate() 结合正负面词汇与“问题是否已解决”做再分类,例如已解决的问题会升级为 milestone,正面情绪显著时也可能转为 emotional。

这种规则系统并不聪明,但它有两个现实优点。其一,可解释且离线可跑;其二,对“开发者与 AI 的技术协作语料”其实非常有效,因为这类语料高度模板化,存在大量稳定措辞,例如 “we should”“the fix was”“it works now”“because”。这解释了为什么 MemPalace 没有在通用记忆抽取上引入 LLM:在目标语域足够窄时,规则系统的收益/成本比反而更高。

在入库数据面上,convo_miner.py 复用了与项目文件同一套 collection,只是在 metadata 中增加了 ingest_modeextract_mode,并在 general 模式下将 memory_type 直接视作 room。这再一次说明,MemPalace 的系统优势来自于数据面的统一:项目文件与对话记录,最终都变成相同的 drawer 对象,只是来源和路由逻辑不同。

不过,对话链路也有明显局限。第一,_chunk_by_exchange() 默认只截取 AI 回复前 8 行左右的内容,极长回答会被强烈裁切;第二,Slack 多人场景通过交替 user/assistant 角色来保持结构,这对复杂多人讨论并不严格准确;第三,通用抽取模块没有上下文窗口概念,跨段落决策可能会被切断。但如果把目标限定在“为 AI 形成可查询的中长期记忆”,这套简化依旧是可以成立的,因为它强调的是让大量历史先被纳入统一检索面,而不是一开始就做到完美语义重建。

检索与四层记忆栈

系统真正的主战场不是导入,而是如何在极低上下文成本下把历史拿回来

如果说导入链路解决的是“记忆能否留下来”,那么 layers.pysearcher.py 解决的就是“记忆能否以低成本被重新激活”。从源码看,MemPalace 的核心设计并不是把所有历史塞回 prompt,而是通过四层记忆栈构建一个分层召回系统:Layer0 负责身份,Layer1 负责 essential story,Layer2 负责按 wing/room 的局部召回,Layer3 负责无限深度的语义搜索。

Layer0 很简单:读取 ~/.mempalace/identity.txt,若不存在则返回默认提示文本。它实际上承担的是“人格与角色定义”的静态层,不依赖 ChromaDB。这个设计很合理,因为身份信息通常不是从历史检索里推出来的,而是一个相对稳定的系统前提。

Layer1 则更有意思。Layer1.generate() 会从 collection 中批量取出文档和 metadata,尝试读取 importanceemotional_weightweight 字段作为权重,若没有则默认 3 分;然后按权重排序,最多取 15 条,再按 room 分组,裁成总长不超过 3200 字符的紧凑唤醒文本。注意,这并不是高级 summarization,而是一种加权摘录拼接。换句话说,Layer1 的本质仍然是原文片段的精选组合,而不是新的语义重写。

记忆层 典型长度 数据来源 主要用途
L0 Layer0 ~100 tokens identity.txt 角色身份与稳定设定
L1 Layer1 ~500-800 tokens Chroma drawers 每次启动时的“核心故事”
L2 Layer2 ~200-500 tokens Chroma drawers 按 wing/room 的定向回忆
L3 Layer3 不限 Chroma semantic search 精确问题查询与深度回溯

Layer2.retrieve()searcher.search() 在哲学上也很不一样。Layer2 并不是 semantic query,而更像一个带过滤条件的抽样读取:通过 col.get() 直接按 wing/room 取若干 drawers,主要服务于“话题已知”的场景。Layer3 才是真正的深度语义搜索层:它使用 col.query(),同时可叠加 wing/room 的 metadata filter,再把距离转成 1 - dist 的 similarity 输出。因此,MemPalace 的四层模型本质上是在区分已知话题的回忆未知位置的搜索

MemoryStack 则提供统一 façade。它在初始化时装配 Layer0Layer3 四个对象,对外只暴露 wake_up()recall()search()status() 等统一接口。这是一个典型的外观模式设计:内部层次明确,但外部只需面向一个聚合对象。CLI 的 cmd_wakeup() 正是通过它来生成唤醒文本。

从检索实现上看,searcher.py 非常克制。search() 主要负责 CLI 输出格式化,而 search_memories() 返回程序可消费的 JSON 结构,供 MCP 服务端复用。它们都采用相同的 where filter 逻辑与 Chroma query 流程。这种拆分很好地遵守了“表现层与数据层分离”的原则:同样一次检索,CLI 需要打印,MCP 需要结构化返回,因此把格式化放在边缘层,而把检索逻辑留在共享模块,是正确的工程选择。

MemPalace 的性能故事,本质上也来自这里。README 宣称 96.6% 的 LongMemEval R@5 来自 raw mode,而 benchmark 脚本证实 raw 模式只是把会话内容直接写入临时 collection,再用 benchmark 问题作为 query 做 Chroma semantic search。也就是说,它的惊艳之处并不在“魔法重排”,而在于:一方面会话内容保留了足够多的原文信号,另一方面 benchmark 的任务形态与向量检索天然适配。这种解释或许不如营销叙事浪漫,但在源码上是成立的。

实验性与扩展子系统

AAAK、Benchmark、Palace Graph、Knowledge Graph 与 MCP Server 的实现关系

若只看主流程,MemPalace 已经成立;但仓库真正显示其野心的,是那些扩展子系统:AAAK 压缩、benchmark 多模式、基于 metadata 的 palace graph、独立 knowledge graph,以及 MCP server。它们共同说明项目并不满足于“做个本地检索工具”,而是试图把检索、记忆、事实与 agent 交互统一到一个长期演进的平台里。

先看 AAAK。dialect.py 开宗明义写明,AAAK 是一种lossy summarization format,它会抽取实体、主题、关键句、情绪与 flags,把原始文本变成一个更紧凑的结构化表示,而且明确声明“96.6% benchmark score is from raw mode, not AAAK mode”。这很重要,因为它说明 AAAK 在当前版本里并不是主存储格式,而是一层可选压缩层。cli.pycmd_compress() 也遵循这一点:它从已有 drawers 中批量读取文档,调用 Dialect.compress() 得到压缩结果与统计信息,而不是把 AAAK 嵌入默认 mine 流程。

再看 benchmark 脚本 longmemeval_bench.py。这个文件几乎就是 README 里性能声明的“可执行解释器”。它分别实现了 raw、aaak、rooms、hybrid、full-turn 等多种检索管线。raw 模式只做原文入库与语义检索;aaak 模式先调用 Dialect.compress() 再入库;rooms 模式为文档与问题打 room 标签,然后对匹配 room 的结果做 20% 距离折减;hybrid 模式则在语义检索结果上按关键词重叠进行再排序。这段代码非常诚实地显示:所谓“架构创新”,大部分都还停留在启发式增强与实验组合层面,而不是形成完全新的检索理论。

子系统 关键模块 当前成熟度 技术意义
AAAK 压缩 dialect.py 实验性 为大规模 token 压缩做预研
Benchmark 管线 longmemeval_bench.py 把性能叙事落到可执行脚本
Palace Graph palace_graph.py 基于 metadata 的轻量图遍历
Knowledge Graph knowledge_graph.py 时序实体事实层
MCP Server mcp_server.py 面向 Agent 的操作系统接口

palace_graph.py 是一个很聪明的设计。它没有引入 Neo4j 或 NetworkX 之类的新依赖,而是直接从 collection metadata 扫描 room / wing / hall / date,动态构建房间图。build_graph() 会统计每个 room 关联到哪些 wings、出现过哪些 halls、出现次数和日期;若一个 room 横跨多个 wing,就形成 tunnel edge。traverse() 通过广度优先搜索从起始 room 出发,查找所有共享 wing 的相邻 room;find_tunnels() 则专门找连接多个 wing 的主题桥梁。这不是复杂图算法,但非常符合“宫殿”比喻,也证明 wing/room 命名体系在架构上确实能支持更多高层能力。

Knowledge Graph 则走了另一条路。虽然当前上下文中没有完整重读它的前半段源码,但从已读部分以及 MCP 接口可以确认,它通过本地 SQLite 存储实体与三元组,并支持 query_entityinvalidatetimelinestatsseed_from_entity_facts 等操作,用 valid_from / valid_to 表示事实时效性。这说明 MemPalace 明确认识到:向量检索适合找语料片段,但不适合维护“当前仍然为真”的结构化事实。 因此它将 KG 与 Chroma 解耦,分别承担检索与事实两类职责。

mcp_server.py 则是整个项目通向 agent 生态的桥梁。它把状态查询、taxonomy、semantic search、重复检测、drawer 增删、KG 查询、KG 写入、graph traversal、diary 读写等能力统一注册到 TOOLS 字典中。 换言之,MCP server 让 MemPalace 从“人手执行 CLI”升级为“AI 可直接调用的记忆操作系统”。值得强调的是,MCP 工具接口本身并不重新实现业务逻辑,而是大量复用 search_memories()KnowledgeGraphpalace_graph 等已有模块。这说明其架构是健康的:接口层薄,能力层厚。

从平台化视角看,这些扩展子系统共同证明了一件事:MemPalace 的底层不是为“一个 demo”写的,而是有明显的产品分层意识。只是它目前的很多高级能力仍属于半成品但方向正确的状态。README 里关于 AAAK、contradiction detection 与 rerank 暴露度的修正,也恰好证明了这一点。

源码级优缺点评估、可扩展性判断与后续演进建议

如果站在架构评审的角度,我会把 MemPalace 评价为一个产品判断强于算法噱头、数据协议强于功能堆砌、可用性强于完备性的开源项目。它最大的优点不是每个模块都做得极深,而是主线非常清楚:统一导入、统一元数据、统一 collection、统一检索入口、统一 agent 接口。这使得仓库即便功能在增加,整体仍然没有失控。

第一大优势是系统边界清晰。项目文件导入、对话导入、检索、分层唤醒、MCP 暴露、图遍历、KG,各自都有独立模块和合理职责。尤其是 MemoryStackmcp_server.py 这种 façade 层,让外部使用方式简单,但内部保留了可拆分性。 这意味着后续想替换底层存储、增强路由逻辑或接入不同检索模型,都不必推翻整体架构。

第二大优势是数据面统一。无论来源是项目文件还是聊天记录,最终都变成 drawers;无论上层调用来自 CLI、Layer 还是 MCP,最终都回到同一个 collection 或 KG。统一数据面是长期演化最关键的前提,因为它降低了跨功能协作的复杂度。许多开源 agent 项目失败的原因,正是每个新功能都偷偷引入一套自己的状态结构;MemPalace 目前基本避免了这个问题。

第三大优势是坦诚地把 benchmark 与实验能力分开。源码与 README 都明确把 raw mode 的成功与 AAAK 的实验性区分开来,并通过 benchmark 脚本把不同模式展开给读者看。这在技术社区里非常重要,因为它减少了“论文式叙述”和“代码实际能力”之间的落差。

但与此同时,它的局限也很鲜明。

第一,room 路由与通用抽取仍偏启发式。不论 detect_room() 还是 detect_convo_room(),都主要依赖目录、文件名与关键词命中,这决定了它在结构清晰的技术语料上表现较好,在跨域内容上则容易失真。

第二,增量更新机制不够精细。项目文件是否重复主要由 source_file 判断,而不是基于内容摘要、mtime 或块级 diff,因此大仓库长期使用时容易出现“需要整仓重挖”的粗放体验。

第三,高阶能力尚未形成闭环。KG、Graph、AAAK 已经存在,但它们和主检索路径之间更多是并列关系,而不是深度协同关系。

评估维度 优势 风险/短板 建议
架构 模块职责清晰,主线短 高级能力仍偏分散 增加统一服务层或 domain model
导入 本地、稳健、依赖少 chunking 与路由较粗 引入 AST / Markdown section 感知
检索 原文保真,高解释性 过度依赖 embedding 质量 增加 BM25 / rerank 插件化接口
对话记忆 多格式兼容强 多人语境与长回复切分有限 引入 conversation graph / speaker model
可演化性 数据面统一 metadata schema 仍相对隐式 正式化 schema 与迁移机制
平台能力 MCP 接口完整 KG/Graph 尚未深度联动 做跨系统 query planner

从更长期的技术演进看,我认为 MemPalace 最值得投资的方向有四个。

第一,把 chunking 升级为结构感知型分块,例如代码按函数/类、Markdown 按标题层级、对话按主题漂移点切换。

第二,把检索层做成可插拔融合检索,不仅有 semantic,也有 keyword、time-aware、metadata prior 与可选 reranker。

第三,把 KG 与 vector search 的关系从并列提升为双层查询协同,例如先从 KG 校正实体,再回到原文 drawer 找证据。

第四,给 metadata schema 增加正式版本与迁移工具,避免未来高级功能越来越多时出现“字段大家都在写,但无人定义”的问题。

最后需要辩证看待“MemPalace 是否真的代表 AI memory 的方向”。如果把“AI memory”理解为模型内部参数级长期记忆,那么答案显然不是;它做的是外部记忆系统。如果把“AI memory”理解为在用户真实工作流中,让模型能够低成本访问过去的事实、决策与上下文,那么 MemPalace 恰恰命中了最现实也最重要的一层。源码给出的结论并不神秘:它不是通过更复杂的模型让 AI 记住,而是通过更清晰的数据协议让系统不再轻易忘记。这或许不浪漫,但很工程,也因此很有生命力。

附录一:关键模块调用关系总览

附录二:适合二次开发者的阅读顺序建议

对于希望二次开发 MemPalace 的工程师,最佳阅读顺序不是从 README 到处,而是先从 cli.py 建立入口视图,再看 miner.pyconvo_miner.py 理解数据进入方式,然后阅读 searcher.pylayers.py 掌握召回方式,最后再补 mcp_server.pypalace_graph.pyknowledge_graph.pydialect.py 理解扩展能力。这种顺序的好处在于,能先抓住系统主干,再看实验性枝叶,避免把图、KG、AAAK 误以为是当前版本的主路径。

参考资料

https://github.com/milla-jovovich/mempalace/