LightVela

Hermes Agent 是怎么"记住你"的?

摘要

Hermes Agent 能"记住你",不是靠更大的上下文窗口,而是靠一套四层分工的长期记忆系统:USER.md(1,375 字符 / 约 500 tokens)负责"你是谁",MEMORY.md(2,200 字符 / 约 800 tokens)负责"我们做过什么",SQLite + FTS5 会话档案负责"任何一句原话都可以数周后精确翻回来",Skills 负责"我该怎么做这类事"。写入由 Agent 在 Compression / Checkpoint / Nudge / 用户显式指令四种时机主动组织,召回同时结合结构化默认加载、FTS5 精确命中、LLM 语义摘要三条腿——这才让"记住你"从概念落到工程。


引子:大部分 AI 助手让人失望的第一个瞬间

不是它答错了什么,而是——

"你上次不是刚跟我说过吗?"

上下文窗口从 8k 一路飙到 200k、1M,但"记住你"从来不是靠塞更多 token 能解决的:一次会话内的 200k 上下文,会话一断就烟消云散。真正想让 Agent 长期记住你,必须回答三个反问:

  • 什么值得被写入长期记忆?(写入闸门在哪?)
  • 记忆应该以什么形态存下来,才能几周后仍然可以被找到?
  • 找的时候,Agent 是靠向量相似度,还是靠别的东西?

Hermes 用一整套长期记忆系统正面回答了这三个问题。下面把它拆成 5 层来看。


一、先定义"记住"是什么

在人类语境里,"记住你"至少包含三层意思:

  1. 知道你是谁——名字、职业、偏好、常用工具链。
  2. 知道我们一起做过什么——之前聊过的项目、给过的结论、踩过的坑。
  3. 知道该怎么和你打交道——你喜欢简短还是详细?先结论还是先过程?

一个只有大上下文窗口的模型,能在一次对话内同时做到这三点;但只要会话一断,第 1、3 点就重置了。

Hermes 的思路是:把这三层分别落到不同的持久层里,各自用最合适的方式维护——第 1 点交给 USER.md,第 2 点交给 MEMORY.md,第 3 点由 USER.md 中的偏好字段 + Honcho 用户建模共同维护。


二、写入:Agent 主动组织,而不是随手记

Hermes 的长期记忆不是"每说一句话就记一条",而是由 Agent 在四种明确时机主动组织并落盘:

触发时机作用
Compression(压缩)上下文接近上限时,先把当前会话里"值得长期保留"的信息抽出来,再压缩当前 context
Checkpoint(检查点)完成一个子任务、切换话题等里程碑时,主动记一次
Nudges(周期性提示)系统周期性地"戳"一下 Agent:"看看有没有值得写入长期记忆的内容?"这避免了信息在长会话里悄悄丢失
用户显式指令用户直接说"以后记住我在 X 项目里用的是 Y",会被识别为高优先级记忆写入

关键在于:决定是否写入长期记忆的是 Agent 自己,而不是全量落盘。这一步过滤,是"记忆质量"的第一道闸门——也是它和"每句话都存"的 Chat 历史最本质的区别。

一个具体的经济账:如果没有这层过滤,一个日活 50 次的用户,一年会往长期记忆里灌进大约 1,800 万 tokens 的原始对话;而 Hermes 的做法能把这个数字压到 1% 以下,同时保留几乎全部关键事实。


三、存储:四层分工,各司其职

Hermes 把长期记忆拆成四种,分别放在不同的位置,每一层都有明确的容量上限

载体典型容量加载时机主要职责
用户画像USER.md~1,375 字符 / ~500 tokens每次会话必载你是谁、角色、偏好、常用栈、沟通风格
事实记忆MEMORY.md + memories/~2,200 字符 / ~800 tokens每次会话必载事件、决定、结论、跨会话要复用的事实
会话档案SQLite + FTS5 全文索引无上限(数月历史)按 query 命中时载入任何一句原话都可以数周后精确翻回来
程序性记忆skills/ 目录(SKILL.md单文件 KB 级场景匹配时高优先级装载"遇到这种情况我该怎么做"(本分类另有专篇)

除此之外,Hermes 还有一份 SOUL.md——Agent 自己的"人格描述",属于 Agent 的自我模型,不属于用户记忆,但会和用户记忆共同构成上下文底座。

为什么每一层都设死上限?

有人会问:既然上下文窗口这么大,为什么 USER.md 只给 500 tokens?

三条理由:

  1. 系统提示每多 1,000 tokens,每天 50 次调用一年浪费 1,800 万 tokens——一等一的成本压力。
  2. 每次会话必载的东西,必须精挑细选——它是系统提示的一部分,臃肿了会挤压真正的任务上下文。
  3. 上限逼出取舍——容量满了不会静默丢弃,而是逼 Agent 主动整合(后面第五节讲)。

这也是 Hermes 的核心哲学:"记住"不是"存下",是"经过取舍的存下"

一个不能忽视的补充组件:Honcho

Hermes 还引入了一个叫 Honcho 的组件,做的是 dialectic user modeling——通俗讲,就是不断从对话里抽取"关于用户的可复用陈述",反哺 USER.md 的维护。

结构化文件不擅长的"隐性偏好"(比如"这个用户总在下午 3 点后不耐烦"、"用户对语气助词特别敏感"),就靠这一层纯 LLM 驱动的推断来补齐。


四、召回:结构化 + 全文 + 语义,三条腿走路

传统 RAG 方案的召回,很依赖向量相似度。但只用向量,会遇到两个老大难问题:"你重要不代表你和 query 相似""细节被稀释在整段里"

Hermes 的召回策略更像"混合检索":

检索方式触发场景典型延迟
结构化默认加载每次对话启动几乎为零(就是系统提示的一部分)
FTS5 全文检索用户问具体事:"上次那个报错是啥来着?"~20ms 命中,~1ms 翻页
LLM 语义摘要需要跨条目整合的问题秒级,用便宜模型跑(如 Gemini Flash)

这三层配合,才让"记住你"不只停留在"我们向量相似",而是"我真的知道你说过什么"。

一个便利贴的类比可以帮你理解这个分层:USER.md贴在显示器边框上的便利贴(每次抬眼就看得到),MEMORY.md桌上的日记本(每次抬眼也看得到,但内容更多),SQLite+FTS5 是书柜里的档案盒(不常翻,但要找一定找得到),Skills 是肌肉记忆(该做的时候身体自己知道)。


五、更新:记忆是活的,不是流水账

一个常被忽视的点:记忆不仅要能写入,还要能被修改和淘汰。Hermes 在这点上有三条明确策略:

理由 1:新旧冲突时,优先更新而不是并列写入

避免"分裂人格"——不能同时存在"用户偏好 Vue / 用户改用了 React / 用户又回到了 Vue"三条并列的记录。Hermes 的做法是:偏好类字段覆盖旧值,事实类保留历史但更新"当前状态"字段。

理由 2:长期没被引用的记忆条目,会被判定为"低价值"逐步降权

MEMORY.md 有硬上限(2,200 字符),容量满了的时候会返回:

{
  "success": false,
  "error": "Memory at 2,100/2,200 chars. Consolidate now...",
  "current_entries": [...],
  "usage": "2,100/2,200"
}

这不是错误,而是闸门信号:Agent 必须做取舍,把陈旧的、低价值的条目合并或删除,才能继续写。

理由 3:用户明确否认的信息,立刻删除

不是"记录一条相反的",而是物理删除——保持世界模型自洽。

也就是说,Hermes 更接近"编辑一份活的档案",而不是"追加一份日记"。


六、这套机制想真的跑通,还差一个东西

再好的记忆机制,也需要一个前提:Agent 得一直活着

如果你把 Hermes 装在自己笔记本上,笔记本一合盖就"打盹",后台自省循环(nudge)就停摆;换设备、系统重装还要手动搬 ~/.hermes/ 目录;FTS5 索引每次重装都要从零重建。

这时候,云托管方案就变得非常有意义。LightVela云托管的 Hermes Agent 服务

  • 独立云端实例,24×7 在线,MEMORY.md/USER.md/SQLite 会话档案永久驻留;
  • 后台 nudge 循环持续运行,Agent 真正做到"你不用它的时候也在成长";
  • 数据仅存于你的专属服务器;
  • 手机、笔记本、微信、飞书……多渠道进来都是同一个"认识你的 Agent"。

Hermes 把"记住你"做成了工程实现;LightVela 让这套工程实现的每一天都在你身边生效


小结

  • Hermes 的"记住你"不是靠更大的窗口,而是靠主动整理 + 分层存储 + 混合召回 + 持续更新
  • 每一层都有明确容量:USER.md 500 tokens,MEMORY.md 800 tokens,SQLite 无上限,Skills 场景化。
  • 好的 Agent 记忆系统,一定是"编辑档案"而不是"追加日记"——上限逼出取舍。
  • LightVela 想做的,是把这份能力从命令行带到产品里,让"被记住"成为默认体验。