头图

前言

Hermes Agent 是 Nous Research 开源的个人 AI Agent 项目(GitHub:NousResearch/hermes-agent),主打的卖点是"自我进化"——它会从经验中创建 skill、在使用中改进 skill、定期提醒自己沉淀知识,并跨会话构建对用户的理解。这套能力的地基,就是它的记忆与上下文管理系统。

这篇文章不谈营销话术,只拆官方文档和源码里写明的机制:Hermes 到底用几层结构来"记住"东西,上下文快爆的时候它怎么处理,这套设计又在哪些地方做了取舍。

一、为什么 Agent 记忆是个工程问题,不是一句"加个数据库"

很多人对 AI 记忆的想象停留在"存进数据库,需要时取出来"。但一个长时间运行的 Agent 面对的是两个互相冲突的约束:

  • 上下文窗口是有限且昂贵的:塞进系统提示的每一条记忆,都要在每一次请求里反复付费、反复占用模型的注意力。
  • 有价值的信息是无限增长的:一个人用了 Hermes 三个月,攒下的项目背景、偏好、踩过的坑,远超任何上下文窗口能装下的量。

Hermes 的解法不是"一个记忆模块",而是按照访问频率和重要性分层——常用的、关键的信息放进系统提示常驻;海量但低频访问的历史对话,做成可检索而不常驻的索引;当前会话内部快速膨胀的工具输出,则用规则和摘要双重压缩。

二、第一层:常驻记忆——MEMORY.md 与 USER.md

Hermes 的核心持久记忆只有两个文件,存放在 ~/.hermes/memories/ 下:

文件作用字符上限
MEMORY.mdAgent 自己的笔记——环境信息、项目约定、踩坑记录2,200 字符(约 800 token)
USER.md用户画像——偏好、沟通风格、技能水平1,375 字符(约 500 token)

这两个文件在每次会话开始时被读入,渲染成系统提示里的一个固定区块,大致是这样的格式:

══════════════════════════════════════════════
MEMORY (your personal notes) [67% — 1,474/2,200 chars]
══════════════════════════════════════════════
User's project is a Rust web service at ~/code/myapi using Axum + SQLx
§
This machine runs Ubuntu 22.04, has Docker and Podman installed
§
User prefers concise responses, dislikes verbose explanations

几个细节值得注意:

1. 严格的字符上限,且不自动压缩。 这是个很关键的工程决策——当写入会超出上限时,memory 工具直接返回报错,而不是悄悄帮你截断或丢弃旧条目。Agent 必须在同一轮里自己想办法:先合并几条相关记录,或删掉过时的,腾出空间后再重试写入。这种"报错而非静默丢失"的设计,避免了记忆内容在用户毫无感知的情况下悄悄消失。

2. 冻结快照模式(Frozen Snapshot)。 系统提示里的记忆区块只在会话开始时生成一次,整个会话期间不会变化——即便 Agent 在会话中途新增或修改了记忆条目,改动会立刻写入磁盘,但要等到下一次会话开始才会反映到系统提示里。这个设计是为了保住 LLM 的前缀缓存(prefix cache):如果系统提示在会话中途变化,前缀缓存会失效,每一轮对话的成本和延迟都会上升。

3. 子串匹配的增删改。 replaceremove 操作不需要你提供完整的原文,只需要一段能唯一定位某条记忆的子串即可。如果这段子串同时匹配多条记忆,系统会报错要求更精确的匹配——避免误删或误改。

4. 两个目标分得很清楚。 memory(环境事实、项目约定、踩坑记录、已完成工作)和 user(身份、偏好、沟通风格)是两个独立的写入目标,职责不重叠。官方文档也明确给了"该存什么、不该存什么"的判断标准:值得存的是用户偏好、环境事实、纠错记录、项目约定;不值得存的是琐碎信息、可以重新搜索到的常识、大段原始数据、一次性的会话细节。

5. 重复检测与安全扫描。 系统会自动拒绝完全重复的写入请求;同时因为记忆内容会被注入系统提示,所有写入在落地前都会做注入和泄露模式扫描——包含提示注入特征、凭证泄露特征、SSH 后门特征,或带有不可见 Unicode 字符的内容会被直接拦截。这是因为系统提示一旦被污染,影响的是整个会话的行为,风险级别比普通用户输入高得多。

三、第二层:会话检索——session_search 与 FTS5

MEMORY.md 和 USER.md 加起来满打满算也就 1,300 个 token 左右,显然装不下一个人用了几个月积累的全部对话历史。Hermes 给了第二条路:session_search

所有 CLI 和消息平台的会话都存在本地 SQLite(~/.hermes/state.db)里,并建立了 FTS5 全文索引。这条路径和"记忆"的设计哲学完全不同:

维度持久记忆(Memory)会话检索(Session Search)
容量约 1,300 token不限(所有历史会话)
速度即时(已在系统提示里)FTS5 查询约 20ms,翻页约 1ms
成本每次对话都要付 token 费用免费,不调用 LLM
用途始终需要在场的关键事实"我们上周是不是聊过 X"
维护方式Agent 手动精选全自动,所有会话都存

换句话说:记忆是"必须随身携带的少量行李",会话检索是"放在仓库里、需要时再去取的库存"。这个分层背后的判断标准很朴素——访问频率高、决定每一次回复质量的信息放进记忆;访问频率低、只在特定查询下才需要的信息放进可检索的存储。这正是经典的"热数据 vs 冷数据"分层思路在 Agent 记忆系统里的体现。

查询返回的是数据库里的原始消息,没有 LLM 摘要、没有截断,这点和很多"先摘要再返回"的检索式记忆方案不同,胜在保真度高,代价是查询结果可能比较冗长,需要 Agent 自己判断哪些片段真正有用。

四、第三层:当前会话内部的"减负"——上下文压缩

前两层解决的是跨会话的记忆问题。但同一个会话内部,上下文也会因为反复的工具调用迅速膨胀——读一次文件、跑一次命令、搜一次关键词,每次工具输出都在往上下文里堆字节。这是 Hermes 上下文压缩系统要解决的问题。

触发条件是动态计算的,不是写死的数字:

threshold_tokens = 主模型上下文窗口 × 压缩阈值(默认 0.50)
tail_token_budget = threshold_tokens × target_ratio(默认 0.20)
max_summary_tokens = min(主模型上下文窗口 × 0.05, 12000)

比如一个 200K 上下文窗口的模型,默认配置下会在用到 100K token 时触发压缩,压缩后保留约 20K token 的最近对话原文,摘要本身的长度上限是 10K token。

压缩本身分两步,而不是一上来就丢给 LLM 去总结:

第一步是规则处理(不调用 LLM,成本几乎为零)。系统会从后往前检查旧的工具调用结果,发现内容重复的就只保留最新一份,旧的替换成占位说明——这是因为 Agent 经常会重复读同一个文件、重复跑同一条命令,如果不去重,上下文里会堆着大量完全相同的内容。同时,超长的工具输出(比如几百行的终端日志)会被改写成轻量摘要,而不是整段保留。

第二步才是把中间这段对话交给一个独立的摘要模型做结构化总结。这里有几个工程细节:

  • 头尾保护:系统提示和最初的用户交互(protect_first_n,默认 3 条)、以及最近的对话(protect_last_n,默认 20 条)始终保持原文不压缩,只压缩中间这一段。
  • 多模态处理:如果中间段里有图片,压缩器会按每张图约 1,600 token 来估算其对总 token 数的影响。
  • 摘要模型可以和主模型不同:可以单独配置一个更便宜更快的模型(比如用 Gemini Flash)专门做压缩摘要,前提是这个摘要模型的上下文窗口不能小于主模型——如果摘要模型装不下要总结的内容,调用会报错,压缩器会捕获这个错误、记录警告,然后在没有摘要的情况下直接丢弃中间段。这是目前压缩质量下降最常见的原因,值得在生产环境里特别注意。
  • 兜底路径:如果摘要模型调用失败(比如供应商临时故障),系统会用一个确定性的兜底方案,至少保留工具名、文件路径这类本地可恢复的信息,避免上下文彻底归零。
  • 交接前缀:所有摘要前面都会加一个固定前缀,明确告诉模型"这是压缩后的背景参考,不是完整对话",避免模型把摘要内容误当作逐字记录。

缓存友好性也被纳入了设计:如果接的是 Anthropic 模型,Hermes 会用"system_and_3"策略管理缓存断点——系统提示固定占用一个断点,再加上倒数第三、第二、第一条非系统消息构成一个滚动窗口,总共四个断点(这是 Anthropic API 允许的上限)。消息顺序的稳定性在这里很重要:缓存命中要求前缀完全匹配,中间插入或删除消息会让后面所有内容的缓存失效。

五、设计取舍背后的思路

把这三层放在一起看,能看出几个一致的工程原则:

1. 不做静默丢失。 记忆写满了报错而不是自动截断;摘要模型失败了走兜底方案而不是直接清空——所有"信息可能丢失"的路径都做了显式处理,而不是依赖隐式的优雅降级。

2. 性能和正确性的权衡是显式的,而不是隐藏在黑盒里。 冻结快照保前缀缓存、规则去重在调用 LLM 之前先省一遍 token、压缩阈值和保留比例全部可配置——这些都是把"系统应该多激进地压缩/保留上下文"这个权衡,交还给使用者去调,而不是内置一个万能默认值。

3. 记忆分层对应的是访问模式分层,不是"重要性"的单一维度。 很容易把记忆系统设计成"重要的存、不重要的丢",但 Hermes 的分层逻辑其实是"高频访问、决定每轮回复质量的放系统提示;低频访问、按需查询的放可检索存储"——这两者并不完全等同于"重要"和"不重要",一条只在特定任务里才用得上的信息可能很重要,但完全不需要常驻。

六、几个能直接拿来参考的设计点

如果你在设计自己的 Agent 记忆系统,这几个细节是可以直接借鉴的工程模式,而不只是 Hermes 的专属实现:

  • 给记忆设硬性容量上限,并且写满时报错而非自动淘汰,把"如何取舍"的决策权交给 Agent 自己在当前上下文里完成,而不是用一个写死的 LRU 策略。
  • 把"常驻记忆"和"可检索历史"拆成两个独立系统,分别优化容量、速度和成本,而不是用一套存储应付所有场景。
  • 上下文压缩前先做无 LLM 调用的规则去重,再做 LLM 摘要——便宜的手段先上,贵的手段后顶。
  • 摘要要带"这是压缩后的背景,不是原文"的明确标记,避免模型混淆摘要内容和真实对话记录的可信度。

本文内容基于 Hermes Agent 官方文档(hermes-agent.nousresearch.com/docs)及 GitHub 开源仓库(NousResearch/hermes-agent)整理,截至 2026 年 6 月的版本(v0.16.0)。具体参数和接口可能随版本更新发生变化,建议结合官方文档核实最新细节。


Smoothcloud润云
1 声望5 粉丝

哈喽~很高兴来到思否社区