让 Agent 记住该记住的:记忆系统的设计与演化
记忆是 agent 系统的地基,不是附加功能。一个没有记忆的 agent 每次会话都从零开始,不知道用户偏好、不记得环境约定、不积累踩过的坑——它只是个会说话的搜索引擎。
过去一个月,我在 Agent Commander 项目里折腾记忆系统。从最初一个朴素的 Markdown 文件,到调研业界七个方案,再到落地一套三层分离架构,过程里踩了一些坑,也得到一些判断。这篇文章记录这段演化。
起点:单文件从够用到膨胀
项目最初的设计简单直接:所有 agent 共享一个 .project/memory.md,用 § 符号分隔条目,纯文本,每次会话全量注入 prompt。每条经验的格式是「§ 日期 — 角色 — 描述」,写事实不写指令。
这套方案在项目头两个月完全够用。agent 跨会话记住了 SSH 凭证、CI 配置、工具怪癖、命名约定。直到积累到 92 条经验、文件膨胀到 52KB——全部塞进每次会话的 prompt——信噪比开始下降。"沟通交流使用中文"和"tc3 磁盘巡检 17%"在检索时同等权重,每条经验无差别占用 token。
更棘手的是用户偏好和环境事实混在一起。"用户喜欢倒金字塔式汇报"和"开发机器是 nuc11"根本是两种性质的东西,却挤在同一个文件的同一个层级。agent 写入时也分不清该往哪条经验后面追加。
问题不在文件格式,在分层。
调研:业界怎么做的
带着这个困惑,我派了三个调研员并行扫了七个主流方案。调研覆盖 Letta/MemGPT、Mem0、LangGraph、Mnemosyne BEAM、CrewAI Memory、AutoGPT 和 Hermes Agent。
第一个核心发现:"三层记忆"不是业界共识,每个项目有自己的一套分类法。 Letta 用热/温/冷(system/ 目录 / 文件树 / git 历史),Mem0 用维度分组(User/Session/Agent/App),LangGraph 引用 CoALA 心理学分类(语义/情景/程序性),Mnemosyne 用 BEAM 三层(Working/Episodic/TripleStore)。七个方案七种分层法,说明没有银弹,只有取舍。
第二个核心发现:SQLite 或文件系统优先,不要引入独立向量数据库。 这是三个调研员独立得出的共识。Hermes 用 SQLite FTS5,Mnemosyne 用 SQLite + sqlite-vec,Letta 用 git-backed Markdown,MemGPT v1 用 SQLite。四个不同来源的方案不约而同选了文件或 SQLite,而不是 Qdrant、Pinecone、Chroma 这些独立向量数据库。对单机部署的 agent 系统,这是明确的信号。
第三个惊喜来自 Mnemosyne BEAM。这个只有 1.4k star 的社区项目,用纯 SQLite 单文件跑出了 10M 消息 35ms 检索、LongMemEval 98.9% Recall 的成绩。它的 MIB 二进制向量压缩把 384 维 float32 压到 48 字节,32 倍压缩。架构简洁性和性能达到了极好的平衡。
横向对比的关键维度:
| 方案 | 分层模型 | 存储后端 | 遗忘机制 | 容量管理 |
|---|---|---|---|---|
| Letta | 热/温/冷三级 | Git-backed Markdown | defrag 收敛 | 15-25 文件 |
| Mem0 | 维度分组 | Qdrant 向量 DB | ADD-only 软覆盖 | 无硬上限 |
| LangGraph | 短期+长期 | SQLite/Postgres | 四种策略 | 开发者定义 |
| Mnemosyne | BEAM 三层 | SQLite 单文件 | TTL + 三级退化 | 10K 上限 |
| Hermes | 4 子系统 | SQLite FTS5 | 手动 replace/remove | 硬容量 2200 字符 |
调研结束后,判断清晰了:从热/温两层开始,用文件系统加 Markdown,不引入外部向量数据库。
落地第一步:Hermes 式两文件方案
第一个实装方案借鉴 Hermes Agent。把单文件拆成两个:
memory.md— 环境事实、经验、约定,硬上限 96Kuser.md— 用户偏好、沟通风格、工作习惯,硬上限 8K
(Hermes 原版的容量仅 2200/1375 字符,我们在其思路上放大了容量,适配自身需求)
两个文件都用 § 分隔,格式不变。所有 16 个 agent 的 prompt 加上加载指令,会话启动时分别读取注入。划分原则是「记忆跟随项目,偏好跟随用户」——换一个项目,user.md 可以带走,memory.md 留在原项目。
这个方案的核心洞察来自 Hermes 的设计:把"是什么"和"怎么沟通"分开存储。 环境事实是客观的(机器 IP、CI 配置、工具版本),用户偏好是主观的(汇报风格、语言习惯)。混在一起会导致 agent 检索时无法区分优先级。
拆分当天的效果立竿见影:user.md 从 52KB 的混合文件里剥离出不到 1KB 的纯偏好,写入一个容量上限为 8K 的独立文件。agent 在决定"怎么回复"时只看这 8K,token 消耗降了一个量级。
落地第二步:USER + SOUL 战斗伙伴系统
两文件拆分解决了事实与偏好的混淆,但另一个老问题留了下来——agent 的「人格」(语调、风格、沟通原则)仍然散落在 16 个 agent prompt 文件里。想统一调整所有 agent 的风格,得改 16 个文件。
这次调研对象是 Hermes 的 Personality 功能。Hermes 用一个 SOUL.md 文件定义 agent 的人格基线,放在 system prompt 的 slot #1,是身份的基础层。它的设计哲学很清晰:人格跟随用户,不跟随项目。
借鉴这个思路,落地了三层分离:
| 层 | 文件 | 容量 | 内容 | 变化频率 |
|---|---|---|---|---|
| SOUL | .project/SOUL.md | 8K | agent 是谁:语调、风格、原则 | 低频 |
| USER | .project/user.md | 8K | 用户偏好:沟通方式、汇报习惯 | 中频 |
| MEMORY | .project/memory.md | 96K | 环境记忆:事实、经验、约定 | 高频 |
注入顺序是 SOUL → USER → 规则。先定义「我是谁」,再加载「用户喜欢什么」,最后才是具体的项目规则和环境事实。SOUL 和 USER 容量相同,职责互补——人格定义行为基线,用户配置使用偏好。
SOUL 和 USER 的关系是「相互影响、共同进化」。SOUL 定义 agent 的行为基线,USER 定义用户的期望,两者冲突时 USER 优先——用户明确表达的偏好压过 agent 的默认风格。
关键设计决策
纯文本,不引入向量数据库。 调研时三个不同模型驱动的调研员并行扫描了七个方案后独立确认了这条共识,落地时验证了它的正确性。纯文本的好处是可 git diff、人类可读、出问题能直接打开文件检查。Letta 从虚拟文件系统 API 演化到真实 git-backed 文件系统,正是因为文件是「人类和 agent 都能用熟悉工具操作的通用原语」。换 SQLite 或向量数据库,就没法用 git diff 看到 agent 改了什么。
硬容量加满写报错,不静默丢数据。 借鉴 Hermes 的 Bounded curated memory:96K 硬上限触发时报错,返回完整条目列表,让 agent 自行决策合并或删除。80K 是软警告线,提醒但允许继续写入。这套机制的核心是把「容量管理」的决策权交给 agent,框架只提供信息和边界。实践证明这对中等能力的国产模型也有效——不需要 GPT-4 级别的推理能力,只需要在报错时触发一次合并操作。
Frozen Snapshot 模式。 这是 Hermes 的一个值得注意的设计:记忆在会话开始时冻结并注入 prompt,中途的修改写到磁盘但不更新当前上下文,下次新会话才生效。好处是保护 LLM 的 prefix cache——如果每次 turn 都重新拼接记忆,cache 命中率归零。会话中途 agent 写入的新经验,会在下一轮会话启动时被加载。本质是「读时快照,写时穿透」。
安全扫描。 记忆条目写入前检查 prompt injection 攻击模式、凭证窃取(API key、password、token)、不可见 Unicode 字符(Bidi override、零宽字符)。这在多 agent 系统里尤其重要——一个被污染的记忆条目可能影响整个 agent 链。
未完待续
当前状态有几个明显的缺口。
没有版本控制。 16 个 agent 都能写 memory.md,一旦某个 agent 错误覆盖了重要经验,无法回滚。调研时发现 Letta 的 git-backed Context Repositories 是最完整的方案——每次 agent 编辑记忆都自动 commit,通过 git revert 回滚任意版本。我们调研了最简实现方案(对 memory.md 做定时 git auto-commit),但还没落地。业界所有 AI 编码工具的 checkpoint 都只针对代码状态,没有一个覆盖 agent 的记忆文件。这是真实的空白。
内存分层还在热/温两层。 调研建议的三步走路径(热/温分层 → sessions 隔离 + git 版本化 → SQLite FTS5 索引 + 后台整理子 agent),目前只完成了第一步。sessions/ 短期记忆隔离和后台整理子 agent 还在计划中。
后台自我改进审查未实施。 Hermes 有一个很优雅的机制:会话结束后,后台异步 LLM 重放完整对话,蒸馏出记忆条目和技能,可配置到更便宜的模型上运行。这能解决「agent 太忙顾不上写记忆」的问题,但需要额外的调度基础设施。
下一步的方向已经清晰:先补 git 版本化(最高优先级,成本最低),再拆 memory.md 为目录结构,最后按需加 SQLite FTS5 索引。记忆系统的演化不会停在这。
这是 Agent Commander 系列的第一篇。后续会写 agent 编排(指挥官 → 队长 → 队员的三级调度)、IM 桥接(Mattermost 到 Commander 的消息链路)、CI 集成(Woodpecker 流水线里的 agent 协作)。这些子系统都建立在记忆系统之上——没有可靠的记忆,编排就是无状态的反复启动,协作就是每次重新认识。