记忆系统:跨会话持久化的工程实现
Hermes 如何用记忆快照 + 外部 Provider 让 Agent 拥有「长期记忆」
通过 Hermes 探秘 Agent 工程 | 第 8 篇 · 查看全部
[系列文章导航]
| # | 文章 | 定位 |
|---|---|---|
| 1 | Agent Loop:Agent 的核心执行循环 | 入口 |
| 2 | System Prompt:身份、上下文与策略的三层架构 | 认知层 |
| 3 | 工具系统:从注册到调度 | 工具层 |
| 4 | 工具调度系统:从注册到执行的完整生命周期 | 调度层 |
| 5 | 安全防护体系:当 Agent 拥有终端时如何防止「做出格」的事 | 安全层 |
| 6 | 沙箱与代码执行:让 Agent 安全跑代码的 RPC 架构 | 执行层 |
| 7 | 上下文压缩:让 Agent 在有限窗口里「记得住」 | 记忆层 |
| 8 | 记忆系统:跨会话持久化的工程实现 | 记忆层 |
| 9 | 技能系统:Agent 如何把经验变成可复用的程序化记忆 | 记忆层 |
| 10 | Provider 抽象层:让 Hermes 同时驾驭 30+ 个 LLM 提供商 | 模型层 |
| 11 | Gateway 网关:连接 20+ 平台的统一消息路由 | 接入层 |
| 12 | 多 Agent 协作:委托、调度与看板 | 协作层 |
为什么 Agent 需要「记忆」
Agent 有一个根本性缺陷:每次会话都是全新开始。系统提示词的 Volatile 层包含了记忆快照,但那只是「这个会话内」的快照。一旦会话结束,这些内容就完全没了。
Hermes 的记忆系统解决的问题就是:把跨会话的信息持久化下来,并在每次对话开始时注入到系统提示词里。
内置记忆:两个文件,没有固定 key
~/.hermes/memories/MEMORY.md ← Agent 的个人笔记
~/.hermes/memories/USER.md ← 关于用户的信息
每个文件的内容就是一段由 § 分隔符分割的自由文本列表,没有任何硬编码的 key。
§ 就是分隔符。没有 schema、没有 key 约束、没有枚举——Agent 写什么就是什么。
记忆工具 memory 只有两个 target:memory(Agent 自己的笔记)和 user(关于用户的信息)。
存储规则:
- ❌ 任务进度
- ❌ 临时 TODO
- ❌ 7 天后会过时的信息
只保存"能让未来少问用户一次"的事实。
外部记忆 Provider
Hermes 支持接入外部记忆 Provider(Honcho、Hindsight、Mem0、SuperMemory 等),但Provider 不是 LLM——它们是独立的外部服务:
- Honcho:基于嵌入向量的语义匹配
- Hindsight:基于会话的观察者模式
- Mem0:向量化记忆存储
Hermes 只允许同时注册一个外部 Provider(通过 MemoryManager.add_provider() 注册)。
记忆怎么加载到对话里
| 来源 | 加载时机 | 注入位置 | 生命周期 |
|---|---|---|---|
| 内置 MEMORY.md / USER.md | 会话启动一次 | Volatile 层(system prompt) | 整个会话不变 |
| 外部 Provider prefetch | 每轮 API 调用前 | 当前轮 user message | 每轮重新查询 |
关键设计:外部记忆注入用户消息而不是系统提示词——因为用户消息每次都重新发送,不会破坏前缀缓存。
sync_all 不是"写入记忆"
sync_all() 每一轮后都被触发,但它不改变内置存储。它只是通知外部 Provider「这轮对话发生了什么」。唯一改变内置存储的是 memory 工具。
与上下文压缩的关系
每次触发压缩时,Hermes 会在压缩流程开始之前调用:
compression_insights = agent._memory_manager.on_pre_compress(messages)
外部 Provider 用这个钩子告诉摘要模型"这部分信息要保留"。
压缩完成后,新的摘要通过 _invalidate_system_prompt() 标记为需要重建。下次 API 调用时,Volatile 层的记忆会被重新注入。
工程启示
1. 记忆不是"采集一切"
记忆越多≠越好。膨胀的记忆会导致模型更难定位真正有用的信息。
2. 内置存储负责高频写入,外部 Provider 负责语义检索
3. 记忆注入要做「安全围栏」
Hermes 对外部 Provider 返回的内容用 <memory-context> 标签包裹,防止被误认为用户指令。
4. 记忆是分层注入的
- 内置静态记忆(MEMORY.md)→ 会话级固定
- 内置动态记忆(state.db KV)→ 会话级固定
- 外部 Provider prefetch → 每轮动态变化
- 压缩摘要(第 7 篇的产物)→ 若干轮后逐渐失效
下一篇:技能系统:Agent 如何把经验变成可复用的程序化记忆 — 从声明式 SKILL.md 到自动化经验积累