跳转至

深度拆解 Hermes Agent 记忆系统

Ch06.004 深度拆解 Hermes Agent 记忆系统

📊 Level ⭐⭐ | 29.5KB | entities/hermes-agent-memory-system-openclaw-comparison.md

原文存档

一句话结论

Hermes 没有做"更强大的记忆",而是把记忆的成本账算得更细:让系统提示词前缀尽量稳定,把必须常驻的事实压到很小,把历史、流程和深层用户建模放到各自成本完全不同的位置。

先把"记忆"这个词拆开

聊 Agent 记忆时,首先要停一下的问题是:不同类型的信息根本不是同一类东西,混在一起存储和召回必然导致系统越用越乱。

一说记忆,很多人脑子里会同时想到:用户偏好、历史会话、文件笔记、向量检索、长期画像、自动总结、workflow、skills。最后所有东西都进了一个大口袋,名字都叫 memory。工程上这样做,后面很容易乱,因为这些东西的更新频率、召回方式、风险边界都不一样。

要记住的问题 Hermes 里更接近的位置
每轮都应该知道的事实和偏好 MEMORY.md / USER.md
以前聊过什么、做过什么 session_search
这类任务下次怎么做 Skills
更深的用户画像和跨平台连续性 Honcho 等外部 provider

Garry Tan 开源 GBrain 时,用的是一个很有吸引力的说法:让 OpenClaw 或 Hermes Agent 能对 10,000 多个 Markdown 文件做 total recall。这个需求很真实。但这也把问题推到了另一面:能回忆一切,不等于每轮都应该携带一切。

Hermes 四层记忆体系

Hermes 没有一套单一记忆系统。更准确地说,它有一组分层的连续性机制:

层级 存储位置 默认容量 定位
热记忆 MEMORY.md + USER.md 2,200 + 1,375 字符 每轮都该知道的事实和偏好
会话检索 session_search (SQLite + FTS5) 无硬上限 档案室,"上次那个问题"
程序性记忆 Skills (~/.hermes/skills/) 无硬上限 SOP,"这类任务下次怎么做"
深层用户建模 Honcho(外部 provider) 可选 跨平台/跨设备长周期画像

容量设计细节

MEMORY.md 和 USER.md 用字符限制而非 token 限制,这让它不需要依赖某个模型的 tokenizer,就能控制记忆大小。实现看上去朴素,但很符合运行时系统的取舍:稳定、可预测、少耦合。

文件格式也很简单,条目之间用 § 分隔。没有上来就做复杂向量库,也没有把记忆写成一个难以人工审查的内部格式。

核心设计:保护稳定前缀

理解 Hermes 的记忆系统,先要看它到底把什么发给模型。原文里整理的系统提示词结构大致是这样:

默认 Agent 身份
工具使用行为 guidance
可选 Honcho 集成块
可选系统消息
冻结的 MEMORY.md 快照
冻结的 USER.md 快照
Skills index
上下文文件,比如 AGENTS.md、SOUL.md、.cursorrules
日期、时间和平台提示
对话历史
当前用户消息

这个顺序很关键。 如果前面的系统提示词部分频繁变化,模型供应商侧的 prompt caching 就很难命中。每一轮都把新的记忆、检索结果、用户画像、历史摘要塞进 system prompt,看起来信息更足,实际会把成本和延迟一起抬上去。

Hermes 的方向很清楚:稳定的东西放前面,动态的东西放后面;每轮都要看的信息尽量短,偶尔才用的信息走工具。

热记忆边界:只放高价值事实

Hermes 鼓励保存到 MEMORY.md/USER.md 的内容:

  • 用户偏好
  • 环境事实
  • 反复出现的修正
  • 稳定约定
  • 以后每次都可能影响行为的高价值信息

它明确不鼓励保存:

  • 当前任务进度
  • 本次会话结果
  • 临时 TODO
  • 一次性排查路径
  • 只是为了证明"我做完了"的日志

这条边界看着细,其实很关键。 很多 Agent 系统的记忆之所以越用越乱,就是因为它把"应该长期影响行为的事实"和"当时发生过的流水账"混在一起。时间一长,模型每次启动都背着一堆已经过期、低密度、上下文不完整的信息。

Frozen Snapshot 机制

会话开始时,MEMORY.md 和 USER.md 被加载进系统提示词,并冻结成快照。会话中途如果通过 memory 工具写入新内容,新内容会立刻落盘,但不会立刻改掉当前会话已经构建好的 system prompt。

用户刚刚纠正了偏好,为什么不马上进入提示词?答案还是缓存和稳定性。Hermes 选择让当前会话继续使用稳定前缀,新写入的记忆等下一次会话,或者压缩触发 prompt rebuild 时再进入系统提示词。它牺牲了一点即时性,换来更稳定的缓存命中和更可控的提示词结构。

memory 工具:安全边界设计

Hermes 管理记忆靠一个 memory 工具,动作很少:addreplaceremove。这里没有复杂的"读"动作,因为当前记忆已经在会话开始时注入过了。

replaceremove 的交互很实用:它们用子字符串匹配。模型不需要记住一个内部 ID,只要拿现有条目里一段唯一文本,就能替换或删除。

记忆是提示词供应链

Hermes 会拒绝重复条目,也会在写入记忆前检查危险内容,包括提示词注入、凭证泄露、SSH 后门暗示、不可见 Unicode 字符等模式。

原因很直接:写进 memory 的内容,未来可能进入 system prompt。 普通日志里混进一句恶意文本,影响范围通常有限。长期记忆里混进一句"忽略之前的所有指令",它就可能在后续很多会话里反复污染系统状态。

很多自建 Agent 记忆系统,容易低估这一点。记忆不是普通数据库字段。只要它会被模型读到,并且会影响模型后续行为,它就属于提示词供应链的一部分。

如果 MEMORY.md 和 USER.md 是热记忆,session_search 就更像档案室。

Hermes 会把过去的会话存到 ~/.hermes/state.db。里面有 sessions、messages、FTS5 全文索引,还通过 parent_session_id 保留会话之间的 lineage。

当模型需要回忆以前聊过什么时,更稳的路径是:

  1. 用 FTS5 在历史消息里搜索
  2. 按 session 聚合结果
  3. 解析父子会话关系
  4. 加载最相关的会话
  5. 在匹配点附近截断 transcript
  6. 用便宜的辅助模型做 focused summary
  7. 把压缩后的回顾交还给主模型

这条链路比"直接把所有历史都存进 memory"麻烦很多,但边界也清楚很多。 MEMORY.md 负责"我每次都要知道什么"。session_search 负责"用户说上次那个问题时,我怎么找回来"。这两类问题放在同一个存储和召回策略里,迟早会互相干扰。

档案室很重要,但没人会把档案室背在身上。

压缩前的 memory flush

长会话一定会遇到压缩。压缩本身不稀奇。真正难的是:压完以后,Agent 还能不能继续干活。

Hermes 的做法里,有一个很值得注意的动作:压缩前先做 memory flush。当会话太长、系统准备压缩中间历史时,它会先给模型一个专门指令,大意是:

会话即将被压缩。
请先保存任何值得长期记住的内容。
优先保存用户偏好、修正和重复模式,不要保存一次性的任务细节。

然后它运行一次额外模型调用,而且只开放 memory 工具。 [这一步的价值不在于"又多总结了一次",它更像一次 checkpoint:趁历史还没被压薄,先把未来可能还会用到的稳定事实挪到更可靠的位置。]

压缩完成后,Hermes 会让缓存的系统提示词失效并重建。这样,压缩前刚写入的 durable memory 就能进入新的稳定提示词快照。

整个流程:

长会话
-> 压缩前保存稳定事实
-> 压缩旧历史
-> 重建提示词
-> 带着更新后的热记忆继续

会话压缩不能只理解成把历史变短,它更应该是把任务状态迁移到更稳定的位置。

这也和 Agent Harness 上下文管理的判断完全一致:窗口里留下来的,不应该是发生过的一切,而应该是下一轮推理真的要用的工作集。

Skills:程序性记忆

Hermes 的记忆故事不止事实和历史。它还有 skills,放在 ~/.hermes/skills/,原文把它称为 procedural memory(程序性记忆)。

这个词用得挺准:

  • 事实记忆回答"环境是什么、用户偏好是什么"
  • 会话检索回答"以前发生过什么"
  • Skills 回答的是"下次遇到类似任务,应该怎么做"

比如:

  • 复杂 PR review 应该先看哪些文件
  • 某类部署失败先查哪些日志
  • 某个团队的数据导出流程怎么跑
  • 一个反复出现的问题,哪些修复路径已经验证过,哪些不要再试

这类知识如果只留在聊天记录里,下次很难稳定复用。如果塞进 MEMORY.md,又会挤占本来就很小的热记忆空间。更合理的做法,是把它沉淀成一个可维护的 skill。

Hermes 也没有把所有 skill 内容都塞进提示词。它注入的是紧凑的 skills index,真正需要时再加载完整 skill。

Skills 是 Agent Runtime 里的 SOP。它的价值不在"越来越有灵性"这种叙事上,而在于把团队和系统已经验证过的做事方法,变成可检索、可更新、可审查的运行时资产。

Skills 风险:错误经验固化

这里还有一个风险:错误经验也可能被固化。

一个写坏的 skill,比一段普通错误回复更麻烦。普通错误回复过去就过去了,skill 会在未来反复触发。所以 skill 必须有生命周期:能创建,能修补,能删除,能标注适用范围,最好还能附带验证步骤和失败模式。这是"Agent 会学习"真正难的地方。学习不是只会多写几段总结。学习意味着系统有能力区分哪些经验可复用,并且愿意在经验过时后把它改掉。

Honcho:守住缓存边界

Hermes 还有一个可选层:Honcho。如果说本地 memory 是一张短卡片,session_search 是档案室,skills 是流程库,那 Honcho 更像外部用户模型。它可以做跨会话用户建模、跨机器和跨平台连续性、语义搜索,以及对用户或 AI peer 的更深层推断。

这里最值得看的,是 Hermes 把 Honcho 接进来的方式:

  • 第一轮会话里,预取到的 Honcho 上下文可以被织入系统提示词
  • 后续轮次里,Hermes 尽量不改稳定 system prompt,而是把 Honcho recall 附加到当前用户消息附近,在 API 调用时动态提供

这样做的好处:稳定前缀继续稳定、prompt cache 仍然能发挥作用、后台预取到的新上下文可以服务下一轮、深层用户建模不会每轮都改写系统提示词。

Honcho 适合被看成增强层,不适合一开始就当成所有 Agent 的默认记忆底座。

深层用户建模带来更重的治理问题:用户是否知道哪些信息被保存,哪些结论被推断,怎么删除,跨平台同步时权限怎么处理,外部 provider 出错时如何回滚。多数团队先把热记忆、历史检索、压缩迁移和 skill 生命周期做好,收益可能更直接。

它修正的 OpenClaw 记忆观

Manthan 用了一个很强的说法:Hermes 修正了 OpenClaw 做错的地方。放到工程里,我觉得应该把这句话收得更稳妥一点。

Hermes 和 OpenClaw 的差别,不是简单的谁有记忆、谁没有记忆。OpenClaw 有 Markdown 记忆、工作区文件、记忆搜索、压缩前的静默刷写、Honcho 相关能力,也在往更完整的 memory plane 演进。

真正需要被修正的,是一种很容易出现的记忆观:

只要把更多东西存下来、搜回来、塞给模型,Agent 就会越来越好用。

这个想法有一半是对的。长期 Agent 确实需要记忆。GBrain 这类工具受关注,也说明"总回忆"是一个真实需求。 [但另一半问题也绕不开:更多记忆会带来更多成本。]

  • 每次都带进提示词,会破坏缓存和注意力
  • 全部交给历史搜索,召回质量和摘要质量就成了瓶颈
  • 流程经验如果只留在 transcript 里,下次很难稳定复用
  • 如果把错误经验沉淀成 skill,又会在未来反复误导 Agent

OpenClaw vs Hermes 系统重心差异

OpenClaw Hermes
系统重心 Gateway、workspace、入口治理、多 Agent、工作区隔离、可控执行、可审计 cache-aware 执行型 runtime
记忆厚在 Memory plane + workspace 热记忆 + 会话检索 + Skills
哲学 把长期状态放进工作区和记忆平面里管理 先保护提示词稳定性,再把长期资产拆到各层

OpenClaw 更像把长期状态放进工作区和记忆平面里管理;Hermes 更像先保护提示词稳定性,再把长期资产拆到热记忆、档案室、流程库和外部模型里。

这不是谁绝对更好。换成一张图,大概是这样的关系:

  • 如果目标是做一个多入口、长期在线、强治理的 Agent Gateway,OpenClaw 那套控制面和工作区边界很有价值
  • 如果目标是做一个本地执行型 Agent,强调缓存成本、长任务连续性、过程经验沉淀,Hermes 的分层就很值得研究

更值得带走的,是它们都在往同一个方向收敛:Agent 不能再只靠一个越来越长的聊天历史活着。它需要窗口外的状态层,也需要能把状态分门别类地放好。

给自研 Agent 的六个问题

第一,什么信息配得上进入热记忆?

热记忆要小。用户偏好、环境事实、稳定约定可以进。任务进度、完成日志、一次性 TODO 最好留在别的层。一旦它会进入 system prompt,就要按提示词供应链来做输入校验、安全扫描和可删除能力。

第二,历史会话有没有档案层?

历史没必要都压进 memory。更稳的是保存完整事件或消息,再提供关键词搜索、按 session 聚合、局部截断和摘要召回。用户问"上次那个问题"时,系统应该能查,而不是让模型凭印象猜。

第三,压缩前有没有状态迁移动作?

长任务压缩之前,最好有一轮明确的 durable state extraction。哪些偏好、修正、稳定事实要留下来,哪些只是本轮任务细节,最好在历史被压薄之前处理。

第四,流程经验有没有自己的位置?

如果一个问题反复出现,或者一次任务沉淀出可复用方法,光写进总结不太够。更好的位置是 skill、runbook、项目规则、测试脚本或 CI。流程经验要能版本化、能修补、能删除。

第五,外部用户建模是否真的需要?

Honcho、Mem0 这类外部记忆层不是没价值,但它们会引入隐私、权限、同步和解释问题。先确认热记忆、历史检索和 skill 层已经清楚,再考虑是否需要更深的用户模型。

第六,系统有没有观测记忆怎么被使用?

至少要知道:哪些条目进了 prompt,哪些内容来自历史检索,哪些 skill 被触发,压缩前写了什么,外部 provider 返回了什么。记忆如果不可观测,最后很容易变成一团没人敢删的旧状态。

深度分析

记忆的本质:不是存储,是成本分配

Hermes 对记忆系统的重构,本质上回答了一个更底层的问题:当上下文窗口不再是无限资源时,Agent 应该把不同信息放在哪里?

传统思路是"记忆越强大越好"——更多存入、更多召回、更多塞给模型。但 Hermes 的思路是按成本分类

  • 热记忆成本最高:直接影响每次 token 消耗
  • 档案层成本中等:召回时才有开销
  • Skills 层成本最低:按需加载,不占主上下文

这个分层的背后是 token 经济的精确计算。字符限制 vs token 限制的选择也值得注意:用字符而不是 tokenizer 模型的 token 数量做配额,意味着容量上限与模型解耦——换模型不会导致记忆容量突然变化。这是一种刻意为之的稳定性选择,用可预测性换取精确性。

Frozen Snapshot 的缓存友好设计

系统提示词在会话开始时被"冻结"成 snapshot,之后的热记忆写入只落盘、不改 prompt cache。这个设计解决了 LLM 应用中一个容易被忽视的问题:每次 system prompt 变化都会使供应商侧的 prompt cache 失效。

对于日均处理大量会话的 Agent runtime,prompt cache 命中率直接影响响应延迟和成本。Frozen snapshot 的代价是记忆的即时性略有牺牲——新写入的内容不会立刻影响当前会话的推理。但这个代价是有意的:等到会话结束或压缩节点再做合并,缓存命中更稳定。

Memory Flush:状态迁移而非历史截断

压缩前的 memory flush 机制,是 Hermes 最值得单独拎出来讨论的设计。大多数 Agent 在长会话压缩时只是把历史摘要变短,但 Hermes 明确在这个节点插入了一个状态迁移动作

压缩前 → 模型专门做一轮 memory extraction → 稳定事实写入 durable memory → 历史压缩 → 重建 prompt cache → 带着新热记忆继续

这个流程的核心洞察是:长会话中真正有价值的信息,往往不是"历史上说了什么",而是"环境/偏好/约定发生了什么变化"。 前者是事件记录,后者是状态更新。前者可以被安全压缩,后者必须迁移到稳定层。

如果跳过这一步,压缩后的 Agent 会丢失那些只在历史里出现过的偏好和事实修正——这些信息在摘要中很难被保留,却在下一会话中至关重要。

session_search 的技术选型

用 SQLite + FTS5 而不是向量搜索做会话检索,这个选择值得琢磨。向量检索适合语义相似性搜索,但会话检索的核心需求是精确召回:用户说"上次那个关于 SSH 配置的问题",需要的不是语义相近的片段,而是那次会话的完整上下文和父子关系。

FTS5 的关键词搜索 + SQLite 的 session 聚合 + parent_session_id 的关系追踪,构成了一个针对时序事件的检索管道。辅助模型做 focused summary 则把最终的信息压缩工作交给了便宜的小模型,主模型只消费处理好的结果。这种分离职责的做法在延迟和成本上都有收益。

Skills 的边界:SOP 不是知识库

Skills 作为程序性记忆,与常见的"知识库检索"有本质区别。知识库回答的是"X 是什么",Skills 回答的是"遇到 Y 类型的任务,应该按什么步骤做"。前者是信息查询,后者是流程复用。

Hermes 的 skills 注入策略(index 按需加载完整 skill)也是一种成本控制:主上下文只保留推理直接需要的高密度信息,流程细节在需要时才展开。这与 memory 的分层逻辑一脉相承——任何信息都有它该在的位置,不该全部堆在主上下文里。

OpenClaw 对比:两种 Agent 范式

OpenClaw 和 Hermes 代表了两种 Agent 架构思路:

OpenClaw 走的是 Gateway/Control Plane 路线——强调入口治理、多 Agent 协作、工作区隔离、可控执行。它的记忆厚在 memory plane 和 workspace,核心是把 Agent 状态纳入企业级治理框架。这套体系在多入口、长期在线、需要强审计的场景下很有价值。

Hermes 走的是 Cache-Aware Runtime 路线——强调每次调用的 token 经济、prompt cache 命中稳定、长任务连续性。它的记忆厚在热记忆 + 会话检索 + Skills,核心是把记忆系统当作提示词供应链的一部分来设计。

两者并不互斥。一个结合了 OpenClaw 的控制面和 Hermes 的 cache-aware runtime 的混合架构,可能是更完整的方案。但对于专注于本地执行型 Agent的团队,Hermes 的分层记忆体系提供了更直接的参考实现。

实践启示

1. 先算成本,再设计记忆

在设计 Agent 记忆系统之前,先回答:热记忆每次推理都会被注入,它的 token 成本是多少?向量检索的延迟能否接受?Skills 加载的 I/O 开销在不在容许范围内?不同信息的召回频率和召回成本差异巨大,先算清楚成本账,再决定放哪一层。

2. 用字符限制作热记忆边界,而非 token

如果热记忆用 token 配额,换模型时容量会变,容量设计就和模型选择耦合了。用字符限制可以保持容量边界稳定,实现更可预测的记忆管理。这个细节虽小,但在长期维护中省去很多意外的容量调整。

3. 会话中途记忆落盘不修修改 prompt

任何 memory write 先落盘,会话结束或明确触发点再统一重建 system prompt。保护 prompt cache 的收益远大于即时更新的体验收益。真正的 cache 友好设计是让稳定前缀尽可能稳定,但同时为动态信息预留清晰的注入路径。

4. 压缩节点是记忆设计的试金石

一个记忆系统设计得好不好,看它在长会话压缩时会不会丢失关键信息。在压缩逻辑里加入显式的 state extraction 步骤,确保用户偏好、环境变化、修正记录在历史压缩前迁移到 durable memory。这个动作做和不做,长期来看差异巨大。

会话检索的核心需求是事件召回而不是语义相似。FTS5 + SQLite 的组合对这类场景更合适:按 session 聚合父子关系,截断局部 transcript,用辅助模型做 focused summary。如果直接上向量检索,可能召回的是语义相似但不属于同一次会话的内容,干扰反而更大。

6. Skills 是流程资产,不是知识资产

建 Skills 时想清楚它回答的是"怎么做"还是"是什么"。前者是程序性记忆,后者是知识库。两者的更新频率、格式要求、注入策略都不同。混在一起会导致 Skills 越来越难维护,最终失去"可检索、可更新、可审查"的初衷。

7. 深层用户画像要在治理框架完备后再上

Honcho 这类外部 provider 引入深层用户建模,但带来了额外的治理复杂度:用户知情权、数据删除权、跨设备同步的权限处理、provider 出错时的回滚机制。如果这些治理问题没有想清楚,深层画像宁可慢一点上,也不要留下用户信任和合规风险。

8. 记忆必须可观测

至少要能回答:哪些条目进了 prompt,哪些内容来自历史检索,哪些 skill 被触发,压缩前写了什么,外部 provider 返回了什么。如果记忆系统不可观测,最后很容易变成一团没人敢删的旧状态——这比没有记忆更麻烦。

架构图

C4 架构图

相关实体