上一节我们给 Hermes 写了 SOUL.md,让它说话的腔调整个变了。那是风格文件,决定了 Hermes 是什么样的人。
这一节我们打开 ~/.hermes/memories/ 目录看看,里面还有两个文件。它们不负责腔调,负责的是另一件事:Hermes 关于用户和项目都知道些什么。
读完会知道:持久记忆的几份文件各管什么、Hermes 会自觉记住什么又刻意跳过什么、容量满了之后它怎么给自己腾空间。
2.1 从灵魂到记忆
之前我们打开 ~/.hermes/SOUL.md 的时候,在里面写了鲁迅和王小波。从此以后 Hermes 说话就带了一点冷幽默和刻薄,像换了一个人。那是风格文件,它定义了 Hermes 是什么样的人。
但 Hermes 还需要知道另一类信息:关于这个用户、这个项目、这台机器的各种情况。它干活的时候需要了解这些东西。
那么这些情况写在哪呢?
打开 ~/.hermes/memories/ 目录看看。里面有两个文件:MEMORY.md 和 USER.md。SOUL.md 不在这,它在上一级目录 ~/.hermes/ 下面。三种文件,三种职责,分开存放。
2.2 MEMORY.md 里到底有什么
官方文档对 MEMORY.md 的定义只有一句话:
Agent's personal notes — environment facts, conventions, things learned
翻译过来就是:Hermes 自己的备忘录,记录的是环境信息、项目约定和学到的教训。它的作用是存储 Hermes 需要知道的信息,而不是待办清单,不是备忘录数据,也不是对话记录。
Hermes 有一套自己的判断标准,什么该记、什么不该记,分得很清楚。
它会主动记这些
下面这些信息是我的 MEMORY.md 里记录的真实信息:
- 环境信息,比如「gingery-wiki 项目位置:/Users/Desktop/gingery-wiki/,包含 blog、coze 等子项目,skills/ 下有软链接被多个子项目引用」
- 项目约定,比如「macOS 上用 sips 命令压缩图片:iPhone 原图 4032×3024 压缩为 JPG,宽度 1920px,质量 75%,保留原图和压缩版两份」
- 被纠正过的事,比如「DeepSeek 模型不支持图片识别,需要分析图片时切到 Claude 或 GPT」
- 已完成任务的摘要,比如「skills-collection 目录已重命名为 skills,46 个软链接已完成同步更新」
- 工作流技巧,比如「文件输出位置:项目相关的放项目目录内,一次性交付物放 ~/Desktop/,临时文件放 /tmp/ 用完清理」
- 明确要求记住的事情,比如「只有一张截图/图片时,自动用 open 命令打开,不要只提路径」
这些都是 Hermes 自己判断并写入的。你不需要特意说「请记住……」,它会在干活的过程中自己判断什么东西值得留一份摘要。
它不会记这些
反过来,下面这些它不会浪费容量去记:
- 琐碎的信息,比如你问了一句「Python 是什么」,这不值得占一个条目
- 搜索引擎就能回答的,比如「Python 3.12 支持 f-string 嵌套」,搜一下就有
- 原始数据,比如大段代码、日志文件、数据表格。容量太宝贵了,不该塞这些
- 会话级的临时信息,比如一次性的文件路径、临时调试用的上下文
- 已经在 SOUL.md 里写过的内容
判断原则其实很简单:这条信息如果忘掉了,下次干活会不会出问题?如果会,就记下来;如果不会,就跳过。
USER.md 也是一样的格式
MEMORY.md 旁边还有一个 USER.md,格式完全一样,也是按 § 符号分隔的一条条信息。区别只在于内容的方向:MEMORY.md 记的是项目和环境的事,USER.md 记的是用户本人的事。
打开看几条 USER.md 条目就明白了:
Full-stack developer, works remote (UTC+8). Uses VS Code + Neovim,
prefers TypeScript for backend and React for frontend. Has Docker
Desktop and PostgreSQL 16 installed locally.
这一条登记了用户的基本画像:技术栈、工作方式、时区。知道这些之后,Hermes 给建议时就会优先推荐用户熟悉的工具和语言。
用户喜欢先了解整体方案再动手。遇到不确定的操作时,先问清楚再做,
不要直接执行修改。项目命名偏好简洁,用下划线不用驼峰。
这一条是关于沟通和协作习惯的。Hermes 知道之后,做事之前会先出方案让用户确认,不会闷头就改。
截图只有一张时,直接 open 打开图片让用户看,不要只给路径。
这一条是具体的交互偏好。简短、明确,一条就把一个交互习惯说清楚了。
代码改完先跑测试再提 PR,不要在本地只编译通过就推送。
测试覆盖率不能低于 80%,新功能必须补对应测试。
这一条是关于工作流程规范的。用户有明确的代码提交流程要求,Hermes 知道之后,写完代码会自动补上测试,不用等用户来提醒。
对比 MEMORY.md 和 USER.md 的条目,有一个明显的差异:MEMORY.md 里记的是客观事实(项目路径、命令参数、依赖关系),USER.md 里记的是主观偏好(喜欢什么做法、讨厌什么步骤、什么沟通方式有效)。两类信息分开存放,各自有各自的容量上限,互不干扰。
2.3 三份文件,三种职责
这三个文件分布在两个目录里:SOUL.md 在 ~/.hermes/ 根目录下,MEMORY.md 和 USER.md 在 ~/.hermes/memories/ 子目录里。分工如下:
SOUL.md 定义 Hermes 是什么样的人:说话风格、行事准则、价值观。这是亲手写的,几乎不变。
MEMORY.md 是 Hermes 关于环境和任务的笔记。一条条信息摘要,按 § 符号分隔排列。
USER.md 是 Hermes 关于用户的笔记。名字、时区、沟通偏好、忌讳事项,都记在这里。
SOUL.md 和 MEMORY.md、USER.md 的区别在于:前者是风格,后面两个是记忆。风格文件没有硬性字符上限,但同样会注入到提示词里,写太长一样占 Token。记忆文件则有严格的字符限制,由系统强制控制上限,不会超。
可以用一张表对比一下:
| 文件 | 职责 | 谁来写 | 容量限制 | 典型条目数 |
|---|---|---|---|---|
| SOUL.md | 人格与风格 | 手动编辑 | 无 | 不适用 |
| MEMORY.md | 环境信息、约定、经验、已完成任务 | Hermes 自动 | 2,200 字符 | 8-15 条 |
| USER.md | 姓名、时区、偏好、忌讳 | Hermes 自动 | 1,375 字符 | 5-10 条 |
SOUL.md 就像角色设定卡,亲手写一次就够了。MEMORY.md 和 USER.md 像是便签条,Hermes 在工作过程中,觉得什么东西值得记住,就往上写一条。
2.4 容量满了怎么办
两份记忆文件各自有严格的字符上限,由 Hermes 系统强制控制:
| 文件 | 字符上限 | 等价 Token | 典型条目数 |
|---|---|---|---|
| MEMORY.md | 2,200 字符 | ~800 tokens | 8-15 条 |
| USER.md | 1,375 字符 | ~500 tokens | 5-10 条 |
这些限制写在 ~/.hermes/config.yaml 里,可以改,但一般不需要动。
会话开始时,系统提示词里会显示当前使用情况:
MEMORY (your personal notes) [67% — 1,474/2,200 chars]
Hermes 看到这个百分比,就知道还有多少空间。
满了之后会发生什么
当 Hermes 试图写入一条新条目,但加上之后会超出限制时,memory tool 会返回一个错误:
{
"success": false,
"error": "Memory at 2,100/2,200 chars. Adding this entry
(250 chars) would exceed the limit. Replace or
remove existing entries first.",
"current_entries": ["..."],
"usage": "2,100/2,200"
}
Hermes 收到这个错误之后,会做四件事:
- 读取当前所有条目(错误响应里已经列出来了)
- 找出可以删除或合并的条目
- 用 replace 把相关的几条合并成更紧凑的描述
- 然后再把新条目写进去
最佳实践是:当使用率超过 80%(系统提示词里能看到),就提前做合并,不要等到满了报错再处理。
但如果不喜欢这种自动写入的感觉,Hermes 也提供了一个开关:
hermes config set memory.auto_write false
关掉之后,Hermes 不再主动写记忆文件。当它觉得值得记住什么的时候,会先问意见:「我想记住以下内容,你看行不行」。同意了才写。
不过一开始使用的时候建议开着这个功能。用一段时间之后再打开 MEMORY.md 和 USER.md 看看里面长什么样,一条条摘要忠实地记录了这段时间和 Hermes 一起做的各种事情。
2.5 冻结与安全
MEMORY.md 和 USER.md 在每次对话开始时会被读入内存,冻结成一份快照,然后注入到系统提示词里。
注意是冻结。会话开始之后,Hermes 可以通过它的 memory tool 追加或修改记忆。修改会立即写入磁盘文件,但不会更新当前会话的系统提示词。为什么呢?因为系统提示词一旦生成就固定了,中途改它会破坏 LLM 的前缀缓存,导致性能下降。所以会话内的修改,要到下一次新会话才生效。
这个冻结设计有一个直接后果:如果想在当前会话里删除一条已经生效的记忆,是做不到的。改完文件之后必须新开一个会话,新的提示词才会拿到更新后的版本。
另外还有一个可能被忽略的点:记忆文件是有安全检查的。因为记忆内容要注入到系统提示词里,Hermes 会在写入之前扫描是否存在注入攻击或敏感信息泄露的迹象,比如隐藏的 Unicode 控制字符、prompt injection 模式、凭据泄露等。匹配到威胁模式的内容会被直接拦截。
2.6 不只是 MEMORY.md
MEMORY.md 覆盖的是一个有限集。2,200 个字符只能存最重要的东西。但如果想知道「三周前跟 Hermes 讨论过的那个数据库迁移方案」,光靠它是不够的。
这时候就要用到 session_search 了。
Hermes 会把所有的对话记录存在 ~/.hermes/state.db 里。这是一个 SQLite 数据库,用 FTS5 加了全文索引。当提到「之前是不是提过 BrightCart?」,Hermes 会调用 session_search 工具,直接在数据库里搜索,返回匹配的对话片段。
MEMORY.md 和 session_search 的分工很清晰:
| 对比 | MEMORY.md | session_search |
|---|---|---|
| 容量 | ~2,200 字符 | 无限(所有历史会话) |
| 速度 | 即时(在系统提示里) | ~20ms FTS5 查询 |
| Token 成本 | 每次对话固定开销 | 按需查询,不占提示词 |
| 用途 | 关键信息始终在手 | 查找具体的历史讨论 |
| 管理方式 | Hermes 自动维护 | 自动记录 |
MEMORY.md 存的是应该永远知道的事,session_search 查的是曾经发生过的事。
也可以直接在终端里翻看历史会话:
hermes sessions list
它会列出所有历史会话,找到之后可以前后翻阅上下文。
2.7 三层记忆体系
到现在为止我们涉及了三层不同的记忆,每层的生存期、精度和用途都不一样。
第一层叫持久记忆层,就是 SOUL.md、MEMORY.md 和 USER.md 这三份文件。每次对话开始时被读入并冻结,注入到系统提示词里。这是最重要的信息,永远在手边。
第二层叫技能层,在 ~/.hermes/skills/ 目录下。每个技能是一个文件夹,里面有一份 SKILL.md 描述什么时候调用、怎么做。它不跟记忆一样每次对话都加载,只在被触发时才进入上下文。
第三层叫会话检索层,就是前面说的 session_search。state.db 里的 FTS5 全文索引,按需搜索,搜到了才占 Token。
这三层的关系可以用一个金字塔来表示:
┌──────────
│ 持久记忆 每次对话必加载
│ SOUL.md 固定开销
│ MEMORY
│ USER
┌┴──────────
│ 技能层 条件触发
│ SKILL.md 触发时才加载
┌┴────────────
│ 会话检索层 按需搜索
│ state.db 免费查询
└──────────────
这三层不是互斥的,而是协同工作的。持久记忆确保重要信息永远在手边;技能层确保可以复用的流程能被自动捕捉;会话检索层确保过去说过的话可以随时翻出来。它们合在一起,才是 Hermes 完整的记忆体系。
2.8 ■ 原来如此
回过头来看,MEMORY.md 本身只是一个 Markdown 文件。但有意思的是它背后的这套分层设计。
Hermes 把记忆分成了三层:持久记忆(SOUL.md + MEMORY.md + USER.md)保证最重要的信息每次对话都在手边;技能层把重复出现的流程自动封装成可复用的技能,不占提示词空间;会话检索层用 SQLite 全文索引存储所有历史,随查随用。
这个设计有什么值得借鉴的?三个关键词:分级、自动、按需。
持久记忆是分级:事实和偏好分开存,风格和记忆分开管,各自的容量上限独立控制。技能层是自动:不需要手动创建,反复做同一件事时 Hermes 自己就会把它打包成技能。会话检索是按需:不提前加载,查到了才消耗 Token。
如果你将来要设计自己的 Agent,也可以用这个思路:把最重要的信息放在提示词里(持久层),把可复用的流程做成插件或工具(技能层),把海量的历史数据放到外部数据库里按需搜索(检索层)。三层分开管,比把所有东西都塞进系统提示词要灵活得多。
2.9 ■ 学点英语
| 中文 | English | 音标 | 说明 |
|---|---|---|---|
| 持久记忆 | Persistent Memory | /pərˈsɪstənt ˈmeməri/ | 跨会话保存并持续影响 Agent 行为的记忆 |
| 记忆条目 | Memory Entry | /ˈmeməri ˈentri/ | 一条可被读取和复用的记忆记录 |
| 记忆检索 | Memory Retrieval | /ˈmeməri rɪˈtriːvəl/ | 从保存的记忆中找回相关信息 |
| 记忆更新 | Memory Update | /ˈmeməri ˈʌpdeɪt/ | 新增、修改或删除长期记忆的过程 |