1. 项目概述:一次对AI Agent记忆系统的深度“解剖”
最近在折腾自己的AI助手项目,发现一个挺有意思的现象:市面上几乎每个AI Agent产品都会宣传自己有“记忆”能力,但当你真想搞清楚这记忆到底是怎么存、怎么取、怎么管理的,能找到的要么是些高屋建瓴的概念图,要么就是语焉不详的“我们用了向量数据库”。这感觉就像买了一台宣称“智能烹饪”的烤箱,结果说明书只告诉你“把食物放进去,按开始键”。
作为一个喜欢刨根问底的技术人,这种黑盒状态让我浑身难受。记忆,作为Agent从“一问一答”的聊天机器人,进化成能持续协作的“智能伙伴”的核心能力,它的实现细节不该是个谜。于是,我决定自己动手,把几个在GitHub上口碑不错、且明确开源了记忆模块的AI Bot项目,从源码层面彻底“解剖”一遍。
我选了四个风格迥异的项目:用Python写的极简主义nanobot,用Zig打造、追求极致性能的NullClaw,基于TypeScript、插件化设计出色的OpenClaw,以及用Rust构建、号称“Agent操作系统”的OpenFang。我的目标很简单:像读工程日志一样,追踪从用户发送一条消息,到几周后Agent还能基于这条消息的上下文与你对话,这中间每一行代码、每一个设计决策。我把这个过程和发现,整理成了这篇超过30篇文档的深度分析。
这不是另一篇泛泛而谈的“AI Agent综述”。这里没有浮夸的愿景,只有实实在在的代码、数据流图和设计取舍。无论你是想快速给自己的小项目加个记忆功能,还是正在设计一个企业级、高可用的记忆系统,我相信这里的“解剖报告”都能给你带来最直接的启发和可复用的方案。
2. 四大记忆系统架构的源码级拆解
2.1 nanobot:极简主义的“文件即记忆”哲学
核心洞察:LLM自己就是最好的记忆策展人。
nanobot的记忆系统,简单到让人怀疑其有效性,但细看之下又觉得精妙。它完全摒弃了数据库,就用两个Markdown文件:MEMORY.md和HISTORY.md。
1.1 数据模型与存储MEMORY.md存储的是经过提炼的“长期事实”。它的格式就是纯文本,每一条记忆可能是一句话,比如“用户张三喜欢喝黑咖啡,不加糖”。这个文件会在每次对话开始时,全量注入到LLM的system prompt中。这意味着,LLM在生成任何回复前,已经“知道”了所有这些事实。这种做法牺牲了一点上下文长度(因为要占用token),但换来了记忆的“零延迟”访问和极强的确定性——LLM绝不会“忘记”写在system prompt里的东西。
HISTORY.md则是一个按时间顺序排列的对话事件日志。每一条用户消息和AI回复都会以时间戳的形式追加进去。当需要回忆“上次我们聊了什么”时,nanobot不会用向量搜索,而是直接用grep命令在HISTORY.md里进行关键词搜索。虽然粗糙,但对于文本匹配特定关键词的场景,速度快且没有外部依赖。
1.2 记忆的整合与更新这是nanobot最有趣的部分。记忆不是自动写入的,而是由LLM自己决定何时、如何更新。系统提供了一个save_memory()工具函数供LLM调用。当对话进行到一定长度,或者LLM认为产生了需要长期记住的新信息时,它会主动调用这个工具。此时,系统会将当前的对话上下文(包括HISTORY.md中的相关条目和MEMORY.md的当前内容)再次喂给LLM,并指令它:“请基于最新的对话,更新MEMORY.md文件,提炼出需要长期记住的核心事实。”
这个过程本质上是一次由LLM驱动的记忆压缩与去重。LLM会合并重复信息,修正过时内容,并以更凝练的格式重写整个MEMORY.md文件。这种设计的巧妙之处在于,它将“什么值得记忆”和“如何组织记忆”这两个最困难的问题,完全交给了LLM本身,开发者只需设计好触发和执行的流程。
1.3 实操心得与避坑指南
- 文件锁是关键:由于读写都是针对单个文件,在高并发或异步操作下,必须实现文件锁机制,避免多个进程同时写入导致文件损坏。nanobot本身是单用户设计,但如果你要扩展,这是首要考虑点。
- 控制记忆体积:
MEMORY.md会不断增长。需要在save_memory()的指令中明确要求LLM进行总结和删减,或者设定一个事实条目的上限,让LLM只保留最重要的。 - 搜索的局限性:
grep搜索只能做关键词匹配,无法处理“语义相似但用词不同”的查询。例如,用户问“我之前说的饮品偏好”,可能无法匹配到“喜欢喝黑咖啡”这条记录。这是为简洁性付出的代价。 - 最佳适用场景:个人使用的CLI工具、快速原型验证、以及对外部依赖(数据库、向量服务)零容忍的环境。它的美就在于那种“一个脚本,两个文件,记忆自成”的优雅。
2.2 NullClaw:企业级记忆系统的“重炮”蓝图
核心洞察:记忆检索是一个标准的搜索引擎问题,必须用搜索引擎的工程化思路来解决。
如果说nanobot是瑞士军刀,那NullClaw就是一套完整的机床。它的记忆系统被清晰地划分为四层(D-B-C-A),每一层都致力于解决规模化应用中的特定问题。
2.1 分层架构详解
- D层(编排层):这是大脑。负责高层策略,如响应缓存、语义缓存(缓存相似的查询结果)、定期执行记忆清理任务、生成系统状态快照,以及调用LLM对长记忆进行摘要。
- B层(检索引擎层):这是核心。实现了多达9个阶段的检索管线,堪比专业搜索引擎:
- 查询扩展:将用户原始查询,利用LLM扩展出多个相关或同义的查询词。
- 多路召回:用扩展后的查询词,同时查询多个存储后端(如全文检索、向量库)。
- 倒数排名融合(RRF):将不同后端返回的结果列表,根据排名进行加权融合,得到一个初步的混合列表。
- 时间衰减:对融合后的结果,根据其创建时间施加衰减权重,新的记忆排名更高。
- MMR(最大边界相关)重排:在结果中兼顾相关性与多样性,避免返回一堆高度重复的记忆。
- LLM重排:将Top N的结果交给LLM,让它根据当前对话的完整上下文,重新判断并排序哪个记忆最相关。
- C层(向量平面):专门处理向量相关操作。包括Embedding模型的路由(支持多种提供商)、向向量数据库的写入与查询、以及至关重要的熔断器机制——当某个Embedding服务超时或失败时,自动降级或切换,保证系统整体可用性。
- A层(存储层):提供了惊人的10种可插拔存储后端,包括SQLite(带FTS5全文搜索)、PostgreSQL、Redis、LanceDB以及各种HTTP API接口。这意味着你可以根据数据特性(结构化、文本、向量)和运维环境,灵活选择存储方案。
2.2 工程化特性
- 灰度发布:新的检索策略或Embedding模型可以以“Shadow”模式运行,即同时处理请求但不影响实际返回结果,用来对比效果。效果稳定后再通过“Canary”模式小流量放量。
- 完备的运维支持:所有检索步骤都有详细的指标和日志,便于性能分析和问题排查。生命周期管理模块能自动处理记忆的归档、清理和迁移。
2.3 实操心得与避坑指南
- 复杂度与收益的权衡:9阶段管线不是每个项目都需要。对于大多数应用,查询扩展+向量检索+简单的时间衰减已经能解决80%的问题。NullClaw的方案是天花板,展示了可能性,但落地时需要做大量裁剪。
- 资源消耗:LLM重排虽然效果最好,但成本也最高(延迟和token消耗)。需要谨慎设置触发该阶段的条件(如当其他方法返回的结果置信度都不高时)。
- 熔断与降级是生命线:依赖外部Embedding服务(如OpenAI)是主要风险点。熔断器的配置(失败次数、超时时间、半开状态逻辑)必须经过充分测试。同时,必须有一个本地的、轻量的备选方案(如用SentenceTransformers模型)作为最后兜底。
- 最佳适用场景:高并发、高可用的生产级AI应用,对记忆检索的准确性、相关性和性能有极致要求,且拥有足够的工程资源进行开发和维护。
2.3 OpenClaw:插件化与优雅降级的大师
核心洞察:记忆系统应该是松耦合、可插拔的组件,并且必须为所有可能得故障准备好退路。
OpenClaw的记忆系统深深植根于其整体的插件化架构哲学中。它不追求单一方案的极致,而是构建了一个健壮的、有多种后备方案的系统。
3.1 双引擎与降级策略OpenClaw记忆检索的核心是一个“搜索管理器路由器”。当Agent需要回忆时,请求首先到达这里。路由器的策略是:
- 首选QMD引擎:QMD是一个独立的外部进程,专门负责高性能的混合搜索(结合关键词和向量)。这是主力部队。
- 自动降级:如果QMD进程无响应或返回错误,路由器会立刻、无缝地将请求降级到内置的SQLite引擎。这个SQLite数据库使用了FTS5扩展进行全文搜索,并集成了
sqlite-vec扩展以支持向量相似度搜索。 - 文件作为唯一真相源:无论是QMD还是SQLite,它们索引的数据都来源于同一个地方——本地的Markdown文件。数据库在这里仅仅是“索引”,而Markdown文件才是原始记忆的存储规范。这意味着即使数据库完全损坏,也可以从Markdown文件重建整个索引。这种设计实现了数据持久化和检索性能的分离。
3.2 插件化的Embedding服务OpenClaw将Embedding生成也抽象成了插件。它内置支持了多达6种提供商,包括OpenAI、Gemini、Cohere等云端服务,以及Ollama这样的本地模型部署。系统可以根据配置的优先级、成本或当前可用性自动选择提供商。同样,这里也内置了降级逻辑,当首选提供商失败时,会自动尝试列表中的下一个。
3.3 记忆的“刷写”机制与nanobot类似,OpenClaw也采用LLM来整合记忆。它定义了一个“刷写”周期或触发条件。当触发时,系统会将近期零散的、可能存储在临时会话中的记忆片段,交给LLM进行处理。LLM的任务是判断哪些信息值得转化为长期记忆,并以结构化的格式(最终写入Markdown文件)输出。这个过程确保了长期记忆库的质量和简洁性。
3.4 实操心得与避坑指南
- 降级路径的测试:降级逻辑不能停留在设计上,必须进行完整的故障注入测试。模拟QMD崩溃、网络分区、Embedding API超时等情况,确保系统能如预期般回退,且回退后的用户体验下降在可接受范围内。
- 插件接口的设计:设计一个清晰、稳定的插件接口(API)至关重要。这个接口需要定义好输入(查询文本、参数)、输出(记忆列表、错误码)以及生命周期钩子(初始化、销毁)。OpenClaw的实践表明,基于事件总线的松耦合设计能让插件集成变得非常干净。
- Markdown格式的规范:既然文件是真相源,就必须严格定义Markdown中存储记忆的格式(如特定的YAML front-matter,或固定的标题层级)。这关系到索引构建和重建的准确性。
- 最佳适用场景:需要支持多种部署环境(云端、本地)、希望灵活切换不同AI服务提供商、并且非常重视系统鲁棒性和可维护性的项目。它的插件化架构为未来的扩展留下了巨大空间。
2.4 OpenFang:Rust构建的统一存储底座
核心洞察:对于复杂的Agent系统,记忆不是单一功能,而是一组相互关联的子系统的集合,最好用一个统一、可靠的基础来支撑。
OpenFang将自己定位为“Agent操作系统”,它的记忆系统MemorySubstrate体现了这种系统软件的设计思想:统一、高效、自包含。
4.1 统一门面下的六合一存储OpenFang没有为不同类型的记忆使用多个数据库或服务,而是通过一个统一的MemorySubstrate门面,暴露了六种逻辑存储能力,但所有数据都持久化在同一个SQLite数据库文件中:
StructuredStore:键值存储,用于配置、状态等结构化数据。SemanticStore:向量存储,用于基于Embedding的语义搜索。KnowledgeStore:知识图谱存储,以(主体,关系,客体)三元组形式存储事实,支持图遍历查询。SessionStore:管理“规范会话”,能将来自不同渠道(如Slack、Discord、Web)的同一用户的对话串联起来,形成统一的上下文历史。ConsolidationEngine:记忆衰减与合并引擎,基于时间、访问频率和LLM评估的置信度,自动降低旧记忆的权重或合并相似记忆。UsageStore:用量遥测数据存储,用于分析和优化。
这种“单一SQLite”架构极大简化了部署、备份和事务一致性管理。通过Arc<Mutex<Connection>>安全地共享数据库连接,确保了线程安全。
4.2 知识图谱与记忆衰减这是OpenFang区别于其他系统的两个亮点。
- 知识图谱:不仅仅是存储“用户喜欢咖啡”,而是可以存储“用户(主体)-喜欢(关系)-黑咖啡(客体)”。这使得Agent可以进行推理,例如,如果它知道“黑咖啡是咖啡的一种”,那么它可能推断出“用户喜欢咖啡”。这为更复杂的记忆关联和推理奠定了基础。
- 置信度衰减:记忆不是简单的“过期删除”。
ConsolidationEngine会为每条记忆维护一个置信度分数。这个分数会随着时间推移而衰减,但如果该记忆被频繁访问或与其他强置信度的记忆关联,其分数又会被强化。当需要为上下文腾出空间时,低置信度的记忆会被优先排除。这是一种更精细、更拟人化的记忆管理策略。
4.3 强悍的工程实践
- Schema迁移:数据库Schema版本化,并支持在应用启动时自动执行迁移脚本(共7次)。这保证了数据模型可以平滑演进。
- 零外部依赖:向量搜索直接使用SQLite的BLOB存储Embedding,并用Rust在内存中计算余弦相似度。知识图谱也完全在SQLite中通过关系表实现。这带来了极致的可移植性和部署简便性。
4.4 实操心得与避坑指南
- SQLite并发性能:虽然SQLite的WAL模式大大改善了并发读性能,但在极高并发的写入场景下(比如多个Agent同时大量写入记忆),它仍然可能成为瓶颈。需要合理设计写入队列或分库策略。
- 知识图谱的维护成本:手动构建和维护高质量的三元组数据是非常困难的。在实践中,往往需要借助LLM来自动化地从文本中抽取结构化关系,这个过程本身就有准确率的问题。启动阶段,可以从简单的键值对和向量搜索开始。
- Rust的学习曲线:整个系统用Rust实现,性能和安全性的好处显而易见,但对于团队的技术栈有较高要求。复刻其设计思想时,可以用其他语言,但需特别注意其内存安全和并发模型带来的设计约束。
- 最佳适用场景:对性能、资源占用和部署简便性有极高要求的嵌入式或边缘AI应用,以及那些希望探索记忆关联与推理等更高级功能的项目。它的“一体化”设计哲学非常有吸引力。
3. 横向对比与选型指南
将这四种架构放在一起对比,能更清晰地看到它们的设计哲学和适用边界。
| 特性维度 | 🐱 nanobot (极简派) | 🦞 OpenClaw (插件派) | 🐍 OpenFang (统一底座派) | ⚡ NullClaw (企业级派) |
|---|---|---|---|---|
| 核心存储介质 | Markdown 文件 | Markdown文件 + SQLite/LanceDB索引 | 单一SQLite数据库 | 10种可插拔后端(SQLite, PG, Redis...) |
| 向量搜索能力 | ❌ 仅关键词grep | ✅ 6种提供商,支持降级 | ✅ 内置(余弦相似度) | ✅ 多提供商路由 + 熔断保护 |
| 知识图谱 | ❌ | ❌ | ✅ 三元组存储 | ❌ |
| 记忆衰减/整合 | ✅ LLM驱动整合 | ✅ LLM驱动刷写 | ✅ 基于置信度的衰减引擎 | ✅ 时间衰减 + LLM摘要 |
| 跨通道会话 | 工作区共享 | 工作区共享 | ✅ 规范会话(CanonicalSession) | 会话隔离 |
| 检索复杂度 | 线性文件搜索 | 混合搜索(关键词+向量) | 向量搜索 + 图谱查询 | 9阶段检索管线 |
| 外部依赖 | 极少(仅LLM API) | 中等(数据库,可选向量服务) | 极少(仅SQLite) | 多(多种数据库和云服务) |
| 部署复杂度 | ⭐ | ⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ |
| 适合场景 | 个人工具、原型验证 | 需要灵活性与鲁棒性的产品 | 嵌入式、资源受限环境、需记忆关联 | 大规模、高可用生产系统 |
如何选择?
- “我就想快速验证一个想法”:毫不犹豫选nanobot的思路。用两个Markdown文件,几小时就能搭出可用的记忆原型,把精力集中在你的核心Agent逻辑上。
- “我要做一个面向用户的产品,必须稳定可靠”:OpenClaw的插件化和降级设计是你的最佳参考。它教会你如何构建一个能应对各种故障、且易于扩展的记忆模块。
- “我的应用要跑在树莓派上,或者需要极简部署”:深入研究OpenFang的单一SQLite底座设计。它的零外部依赖和高效的内存管理,是资源受限环境的典范。
- “我在构建一个日活百万级的AI助理,检索必须又快又准”:NullClaw的蓝图就是为你准备的。它的分层架构、多阶段检索管线和工程化特性(熔断、灰度),是应对大规模复杂场景的必备品。
4. 从零开始:构建你自己的Agent记忆系统
看了这么多,你可能已经摩拳擦掌想自己动手了。无论你选择哪种路径,以下是从这些优秀项目中提炼出的、通用的构建步骤和核心决策点。
4.1 第一步:定义记忆的数据模型
这是所有工作的基础。你需要决定记忆到底长什么样。
- 基础字段:至少包含
id(唯一标识)、content(记忆内容文本)、embedding(向量表示)、timestamp(创建时间)、source(来源,如用户ID、会话ID)。 - 元数据:考虑添加
type(事实、对话片段、用户偏好等)、importance(手动或LLM评估的重要性分数)、access_count(访问次数)等,用于高级检索和生命周期管理。 - 关联关系:是否支持记忆之间的链接(如“回复了哪条消息”、“属于哪个主题”)?这决定了你后续能否实现简单的图谱功能。
实操建议:初期从最简单的模型开始。可以借鉴nanobot,先只用content和timestamp。随着需求复杂再逐步扩展。用JSON或Protocol Buffers来定义这个模型,方便序列化和版本管理。
4.2 第二步:设计存储与索引层
这是“记”的部分。
- 存储选择:
- 简单快速:像nanobot一样用文件(JSONL, Markdown)。适合原型。
- 功能全面:使用SQLite。它轻量、单文件、支持ACID,通过
sqlite-vec等扩展还能支持向量搜索。OpenFang证明了其强大。 - 生产级:使用专业的向量数据库(如Chroma, Weaviate, Qdrant)或支持向量的云数据库(如PostgreSQL with pgvector)。它们为大规模向量检索做了优化。
- 索引策略:
- 向量索引:如果你用向量搜索,这是必须的。大多数向量数据库会自动创建。如果自己管理,了解HNSW(近似最近邻搜索)这类索引算法。
- 全文索引:对
content字段建立全文索引(如SQLite的FTS5,Elasticsearch),支持高效的关键词检索。OpenClaw和NullClaw都采用了混合搜索(关键词+向量)。 - 时间索引:在
timestamp上建立索引,方便按时间范围查询或进行时间衰减计算。
4.3 第三步:实现检索管线
这是“忆”的部分,也是最复杂的部分。
- 查询处理:接收用户查询。可以进行查询扩展(用LLM生成同义词或相关问题),这能显著提升召回率。
- 多路召回:
- 向量路:将查询文本转换为Embedding,在向量索引中进行相似度搜索(如余弦相似度)。
- 关键词路:在全文索引中搜索查询词。
- 时间路:检索最近一段时间内的记忆。
- 元数据路:根据
type,importance等过滤。
- 结果融合与重排:
- 初步融合:使用像RRF这样的算法,将不同路召回的结果合并成一个列表。
- 相关性重排:应用时间衰减(越新的记忆权重越高)、MMR(保证结果多样性)。
- 上下文重排(可选但强大):将Top K的候选记忆和当前完整对话上下文一起交给LLM,让它判断哪个最相关并重新排序。这是效果最好但成本最高的步骤。
- 返回与注入:将最终排序后的记忆,格式化成文本,注入到LLM的提示词(Prompt)中,通常放在
system或user消息里。
避坑指南:不要一开始就实现完整的9阶段管线。先从“向量检索+简单时间排序”开始,测量效果。然后逐步加入关键词检索、查询扩展,最后再考虑LLM重排。每加一步,都要用真实的测试集评估效果提升是否对得起复杂度增加。
4.4 第四步:设计记忆生命周期管理
记忆不能只进不出。
- 整合:定期(或基于长度触发)将零碎的对话片段,通过LLM总结、提炼成结构化的长期记忆。参考nanobot和OpenClaw的“刷写”机制。
- 衰减:记忆的重要性会随时间或不再被访问而降低。实现一个后台任务,定期扫描记忆,降低其
importance分数,或在检索时施加衰减权重。OpenFang的置信度衰减模型很值得借鉴。 - 清理/归档:对于分数低于某个阈值的记忆,可以将其移动到归档表,或直接删除。务必提供手动恢复或查看归档的入口。
4.5 第五步:保障可靠性
这是生产系统必须考虑的。
- 降级策略:向量服务挂了怎么办?像OpenClaw一样,准备一个降级到关键词搜索的路径。数据库连接失败时,是否有只读缓存或本地文件备份?
- 熔断与重试:对依赖的外部服务(Embedding API、向量DB)实现熔断器。失败超过阈值后快速失败,并定期探测是否恢复。
- 监控与指标:记录检索延迟、召回数量、各阶段耗时、缓存命中率、LLM调用次数和token消耗。这些指标是优化和排查问题的黄金标准。
5. 逆向工程中的核心收获与未来展望
拆解完这数万行代码,我得到的远不止四种技术方案。一些更深层的、关于如何设计AI系统“心智”的规律浮现出来。
收获一:记忆的本质是连接“短期工作记忆”与“长期知识库”的桥梁。所有系统都在解决同一个问题:如何把流动的对话(短期记忆),筛选、固化、索引到可持久查询的知识库(长期记忆)中,并在需要时精准提取回来。区别只在于这座桥的材质(文件、SQLite、分布式DB)和交通规则(简单检索、复杂管线)。
收获二:让LLM管理LLM的记忆,是最高效的范式。试图用硬编码的规则(如“包含关键词‘喜欢’的句子存为偏好”)来提取记忆,在复杂多变的自然语言面前注定脆弱。这四个系统不约而同地将“记忆该记什么”、“如何组织记忆”的任务交还给了LLM本身。我们只需设计好触发这个过程的机制(定时、定量、事件驱动),并提供清晰的指令(“请总结对话中的新事实”)。
收获三:检索的相关性比存储的容量重要一百倍。存下海量记忆并不难,难的是在需要的时候,瞬间找到最相关的那几条。NullClaw的9阶段管线像一个缩影,揭示了工业级应用对“精准回忆”的极致追求。未来,更智能的检索策略(如基于对话状态的动态检索、多跳推理检索)将是竞争焦点。
收获四:没有完美的架构,只有适合场景的权衡。nanobot的极简在个人场景是美,在生产环境可能就是灾难。NullClaw的重型管线在大公司是必要,在小创业公司就是过度设计。理解每种方案背后的权衡(复杂度 vs 能力、依赖 vs 自包含、实时性 vs 一致性),比照搬代码更重要。
个人体会与展望:在我自己的项目中,我最初直接模仿了NullClaw的复杂设计,结果陷入运维泥潭。后来我回归本质,采用了“OpenFang的统一SQLite底座 + OpenClaw的插件化降级思想”的混合架构。用SQLite作为核心存储保证简单可靠,但将向量检索、关键词检索等能力抽象成插件接口。初期只实现一个本地向量插件(用all-MiniLM-L6-v2模型),并预留了接入云端服务的插件槽。这样,项目既能快速跑起来,又为未来扩展留足了空间。
AI Agent的记忆系统还在快速演进。我目前最关注两个方向:一是成本与效果的平衡,如何用更小的模型、更少的token实现媲美GPT-4的记忆管理;二是记忆的主动性与关联性,让Agent不仅能被动响应查询,还能主动联想相关记忆、甚至基于记忆进行简单的规划和推理。这或许需要更深度地融合知识图谱与向量检索的技术。
最后,一个非常实用的小技巧:在给你的记忆系统添加任何新特性(比如复杂的重排算法)之前,先建立一个简单的评估基准。准备一组标准问题,记录在现有系统下的回答质量。添加新特性后,用同一组问题测试,看效果提升是否显著。这能帮你避免陷入“为了复杂而复杂”的陷阱,始终让工程决策服务于真实的用户体验。