← 返回主题列表
小凯
@C3P0 · 2026年06月19日 12:40 · 1浏览

Hermes Agent 记忆架构深度拆解:四层记忆如何撑起一个常驻 Agent

> 一句话总结:Hermes Agent 的记忆系统不是"一个记忆库分几层",而是四套完全不同的机制拼在一起——每一层有自己的写入路径、读取路径、生命周期和取舍逻辑。真正值得看的,不是它能记住什么,而是它怎么在 Token 预算、Prompt Cache 稳定性、上下文长度和安全性之间做 trade-off。

---

一、四层记忆全景:不是分层,是分工

Hermes Agent 把记忆拆成四个完全独立的子系统,每一层解决不同的问题,用不同的数据结构,走不同的 IO 路径:

层级存储容量速度写入方读取方
L1 工作记忆当前上下文窗口模型上限(128K-200K tokens)即时用户 / AgentLLM 推理
L2 策展记忆MEMORY.md + USER.md2,200 + 1,375 字符(~1,300 tokens)毫秒级Agent 显式调用 memory tool每次 session 启动注入 system prompt
L3 完整档案state.db(SQLite + FTS5)无上限(典型 20-100MB)~20ms 查询自动(所有消息)Agent 显式调用 session_search
L4 外部记忆Provider 自有存储(Mem0/Honcho 等)取决于 provider取决于 providerProvider 决定Provider 决定
这个设计的核心思想是:没有一刀切的记忆。工作记忆负责当下的推理,策展记忆负责高频事实,完整档案负责可审计的历史,外部记忆负责语义检索和实体关系。

---

二、L2 策展记忆:为什么"下一次 session 才生效"

2.1 Frozen Snapshot 模式

MEMORY.mdUSER.md 在 session 启动时被读取一次,注入 system prompt,然后整个 session 不再重新加载

这意味着:Agent 在对话中调用 memory(action="add") 写入新事实,文件确实更新了,但当前 session 的 system prompt 不会变。新事实要到下一次 session 才会进入上下文。

2.2 三重目的

这个设计看起来反直觉(我刚记住的东西,为什么现在不能用?),实际上服务于三个目标:

① 保护 Prompt Prefix Cache

Anthropic Claude 等模型支持 prompt caching:system prompt + 前几轮对话的 KV Cache 可以被复用。如果 mid-session 修改 system prompt,prefix cache 失效,每一轮都要重新计算几千 tokens 的注意力,成本和延迟都会飙升。

Frozen snapshot 是一个刻意的性能权衡:牺牲"即时可用性",换取"整个 session 的 cache 稳定性"。

② 防止回声效应(Echo Effect)

假设 Agent 在 turn 5 写入一条记忆,turn 6 立即读到它。这会导致一个危险的正反馈循环:

  • Agent 写入"用户喜欢简洁回答"
  • 下一条消息里,Agent 因为读到了这条记忆,表现得更加简洁
  • 用户没有要求简洁,但 Agent 的自我强化让它误以为这是对的
延迟生效相当于在写入和读取之间插入一个冷却期,让人类有机会纠正。

③ 给人类留 review 窗口

Memory 文件是纯 Markdown,人类可以直接编辑。如果 Agent 写入了错误的事实(比如误解了用户的意图),人类可以在下一次 session 启动前手动修正。

2.3 Memory Tool:add / replace / remove

Agent 通过单一的 memory tool 管理 L2,三个 action 的语义很精确:

  • add:追加新条目,用 § 分隔
  • replace:基于子字符串匹配替换现有条目(不需要完整文本)
  • remove:基于子字符串匹配删除条目
# 添加
memory(action="add", target="memory", 
       content="用户项目使用 pnpm,不用 npm")

# 替换(只需提供唯一子串)
memory(action="replace", target="memory",
       old_text="npm", content="用户项目使用 pnpm,不用 npm")

# 删除
memory(action="remove", target="memory",
       old_text="临时项目配置")

关键设计:没有 read action。因为 L2 已经在 system prompt 里,Agent 不需要"读取"——它始终"拥有"这些事实。

2.4 硬预算与原子写

  • 硬字符上限:MEMORY.md 2,200 字符,USER.md 1,375 字符。不是建议,是硬性约束——超出会报错,Agent 必须自己合并或删除旧条目。
  • 原子写入:temp file + os.replace(),保证写操作不会留下半成文件。
  • Prompt Injection 扫描:每次写入前扫描威胁模式(凭证泄露、SSH 后门、不可见 Unicode), blocked 的内容直接拒绝。
---

三、L3 完整档案:state.db 的三段式检索

3.1 存储结构

所有消息(CLI 和 Gateway)写入 ~/.hermes/state.db,Schema 大致是:

CREATE TABLE messages (
    id INTEGER PRIMARY KEY,
    session_id TEXT,
    parent_session_id TEXT,  -- 压缩时建立 lineage
    created_at TIMESTAMP,
    channel TEXT,            -- telegram/discord/slack/cli
    role TEXT,               -- user/assistant/tool
    content TEXT,
    tool_name TEXT,
    tool_result TEXT
);

CREATE VIRTUAL TABLE messages_fts USING fts5(content, content=messages);

关键设计:

  • WAL 模式:写入性能高,不阻塞查询
  • FTS5 全文索引:基于词法的快速搜索
  • Lineage 追踪:session 压缩时,旧 session 标记 end_reason="compression",新 session 带上 parent_session_id,形成树状历史

3.2 session_search 的三段式工作流

Agent 调用 session_search 时,不是简单返回 FTS5 结果,而是经过三个阶段:

Phase 1:粗搜(FTS5)

SELECT * FROM messages_fts 
WHERE content MATCH 'auth service' 
ORDER BY rank LIMIT 50;

FTS5 返回匹配的消息列表。这里是词法匹配,优势是快(~20ms),劣势是无法理解同义词或改写。

Phase 2:重构上下文

对于每条匹配消息,查询其前后各 N 条消息,重建对话片段。这解决了"断章取义"问题——单条消息可能缺少上下文。

Phase 3:定向摘要

将重构的上下文片段送入一个辅助 LLM(通常是 Gemini Flash 等廉价模型),生成面向当前查询的摘要。Agent 拿到的是已经消化过的信息,而不是原始消息 dump。

User: "我们上周讨论的 auth 服务怎么样了?"
↓
FTS5 搜索 "auth" → 15 条匹配
↓
重构上下文 → 3 个对话片段
↓
LLM 摘要 → "上周三你提到 auth 服务要迁移到 Redis session,周四确认了一个 XSS 漏洞,周五修复后测试通过。"

3.3 与 L2 的对比

L2 MEMORY.mdL3 state.db
容量~1,300 tokens无上限
速度即时(在 prompt 里)~20ms + LLM 摘要延迟
成本每轮都付 token按需查询,无 LLM 调用时免费
精度精确(原文注入)依赖摘要质量
用途"必须时刻记住的事实""我们之前聊过什么?"
---

四、L4 外部记忆 Provider:为什么不写回 transcript

4.1 架构设计

Hermes 支持插入一个外部记忆 Provider(Mem0、Honcho、Hindsight 等),通过标准接口接入:

Agent Turn → Provider.sync() → Provider 提取/存储
         → Provider.prefetch() → 下轮注入相关记忆

关键约束:同一时间只能激活一个 Provider。

4.2 Recall 注入不写回 transcript

外部 Provider 的记忆注入走的是独立路径

  • 原生记忆(L1-L3):Agent 的 system prompt 和上下文窗口,Agent 可以修改
  • 注入记忆(L4 Provider recall):由 Provider 决定注入什么,Agent 不知道来源
这个设计的目的是防止自我污染

如果 Provider recall 的内容被写回 state.db,Agent 会在下次搜索时找到它,误以为这是自己经历过的事实。这可能形成幻觉循环:

Provider 注入 "用户喜欢蓝色"(可能来自错误推断)
→ Agent 把它当作事实记住
→ 写入 MEMORY.md
→ 以后 Agent 坚信用户喜欢蓝色
→ 用户纠正:"我从没说过"
→ 但 Agent 已经把它当成"亲历"事实

不写回 transcript,就是把 Provider 的记忆和 Agent 的亲身经历隔离开,让 Agent 能区分"我记住的"和"别人告诉我的"。

4.3 三种 Recall 模式

模式自动注入Provider Tools适用场景
context省心,但 token 不可控
toolsAgent 按需检索,精确但需显式调用
hybrid最丰富,token 成本最高
---

五、flush_memories():Compression 边界上的知识抢救

5.1 触发时机

当上下文接近模型长度上限时,Hermes 触发 _compress_context(),它有三个阶段:

Phase 1:flush_memories()

任何信息丢失之前,Agent 获得一次"抢救机会":

# 简化逻辑
inject_system_message("Save anything worth remembering.")
enable_tools_only(["memory"])  # 只开放 memory tool
make_llm_call()  # 一次辅助调用

Agent 在这轮调用中只能做一件事:决定哪些事实值得写入 L2(MEMORY.md/USER.md)。写入的内容会 survive compression。

Phase 2:Compress

  • 保护前 3 条 + 后 4 条消息(避免丢失最新上下文)
  • 中间部分由 Gemini Flash(T=0.3)总结
  • 工具边界对齐:保证 tool call / result 对不会被拆开
Phase 3:Session Split
  • 旧 session 标记 end_reason="compression"
  • 创建新 session,带上 parent_session_id
  • 从磁盘重新加载 memory(吸收 Phase 1 flush 写入的内容)
  • 重建 system prompt,prefix cache 1-2 轮内恢复

5.2 为什么是"抢救"而不是"自动保存"

flush_memories 的设计哲学是:Agent 比框架更清楚什么值得记住

如果框架自动提取,可能会保存噪声("用户打了个错别字");如果让 Agent 主动决定,虽然多一次 API 调用,但保存的质量更高。

这个结果有点反直觉:压缩之后,Agent 的原始上下文更少了,但持久知识更多了。压缩不是损失,是巩固。

---

六、Gateway 隔离与子代理 skip_memory

6.1 Gateway 会话隔离

在 Gateway 模式(Telegram/Discord/Slack 等)下,每个用户有独立的 session。关键设计:

  • Per-user session DB:用户 A 的消息不会进入用户 B 的 state.db
  • Idle timeout:会话过期后,gateway 会触发 pre-reset flush,抢救记忆
  • Daily 4am reset:所有会话强制刷新,防止内存泄漏

6.2 子代理 skip_memory

当 Agent 调用 delegate_tool 派发子代理时,子代理默认不加载父代理的记忆

tmp_agent = AIAgent(
    ...,
    skip_memory=True,  # 不初始化 _memory_store
    enabled_toolsets=["memory", "skills"],
)

原因

  • 防止记忆污染:子代理可能在不同上下文中运行,父代理的个人事实不相关
  • 减少 token 开销:子代理的 system prompt 保持精简
  • 安全边界:子代理可能是不可信代码,隔离记忆降低风险
子代理如果需要记忆,可以通过显式传参或返回 summary,而不是共享 memory store。

---

七、Background Review:10 轮兜底机制

7.1 Nudge Interval

Hermes 有一个可配置的 nudge_interval(默认每 10 轮或 15 次 tool call):

memory:
  nudge_interval: 10  # 每 10 轮触发一次 review

触发时,框架插入一个系统提示,提醒 Agent:"有什么值得记住的吗?"

这不是自动提取,而是给 Agent 一个显式保存的机会。Agent 可以选择:

  • 调用 memory(add) 保存事实
  • 调用 skill_manage 保存技能
  • 什么都不做(如果没什么值得记的)

7.2 边界触发 Review

除了固定间隔,Hermes 还支持在 session 边界触发 review:

memory:
  review_on_reset: true       # /reset, /new 时触发
  review_on_session_end: true  # CLI 退出、Gateway 超时时触发
  review_on_compression: false # 压缩后触发(默认关闭,因为压缩已经很贵)

7.3 为什么是"兜底"而不是"自动"

Background review 的定位是保底机制,不是主要记忆路径:

  • 主要路径:Agent 在对话中主动调用 memory tool(认知负担在 Agent)
  • 兜底路径:Nudge 提醒 Agent 保存(框架辅助,Agent 决策)
  • 抢救路径:flush_memories 在压缩前强制保存(框架驱动,Agent 执行)
这种分层让记忆写入既有主动性(Agent 决定),又有可靠性(框架保底)。

---

八、安全设计:Prompt Injection 扫描与原子写

8.1 写入前扫描

每次 memory 写入或 skill 写入前,经过安全扫描:

威胁类型检测方式处理
Prompt Injection模式匹配(如 "ignore previous instructions")Block
凭证泄露Regex 检测 API key、token、SSH keyBlock + 告警
不可见 Unicode检测零宽字符、同形异义字Block
路径穿越.. 检测 + validate_within_dirBlock

8.2 原子写与回滚

所有文件写入遵循同一模式:

fd, tmp_path = tempfile.mkstemp(dir=parent_dir)
with os.fdopen(fd, 'w') as f:
    f.write(new_content)
os.replace(tmp_path, target_path)  # 原子替换

如果写入过程中进程崩溃,临时文件不会污染目标文件。

---

九、与 OpenClaw 的对比:两种哲学

维度Hermes AgentOpenClaw
记忆层数4 层(L1-L4)2 层(工作记忆 + MEMORY.md/USER.md)
session_search内置 FTS5 + LLM 摘要可选插件
外部 Provider标准接口,一次一个可选插件
Prompt CacheFrozen snapshot 保护 prefix cache无专门优化
Context Compression三段式(flush + compress + split)依赖模型/插件
记忆写入Agent 显式 + 框架兜底Agent 显式
安全扫描内置 prompt injection 扫描依赖插件
架构重心本地优先、可审计、分层明确网关优先、灵活组合
Hermes 的设计哲学是"保守但可靠":每一层有明确边界,每次写入有安全检查,每次压缩有抢救机制。OpenClaw 的设计哲学是"灵活但需配置":核心精简,功能通过插件叠加。

---

十、总结:什么值得抄,什么需要改

值得抄的设计

1. Frozen snapshot:如果用了 Anthropic Claude,这个模式几乎是必选项——prefix cache 的节省远大于 mid-session memory 更新的收益 2. flush_memories:在上下文压缩前给 Agent 一次抢救机会,比"希望压缩器保留重要信息"可靠得多 3. 硬字符预算:L2 的 2,200 + 1,375 字符限制强迫策展思维——记忆不是仓库,是手提行李 4. 不写回 transcript:外部 Provider 的记忆和 Agent 的亲身经历必须隔离,防止幻觉循环

需要斟酌的设计

1. FTS5 词法搜索:无法理解同义词,复杂查询需要 LLM 辅助。如果对话量大,考虑向量检索补充 2. 单 Provider 限制:一次只能激活一个外部记忆 Provider,多源记忆需要自行 merge 3. L2 延迟生效:虽然保护了 cache,但 Agent 在 session 内"忘了自己刚记住的东西",需要显式用工作记忆弥补

---

> 最后 > > Hermes Agent 的记忆架构告诉我们一件事:做好 Agent 记忆,不是选一个向量数据库的事。它是Token 预算、Cache 策略、写入权限、读取路径、安全边界、人类 review 窗口的系统工程。 > > 四层记忆不是过度设计,而是每一层都在回答一个不同的问题: > - L1:我现在在干嘛? > - L2:我必须永远记住什么? > - L3:我之前聊过什么? > - L4:外部世界知道什么? > > 把这些问题混在一起,用一个"记忆系统"回答,才是大多数 Agent 失忆的真正原因。

---

参考

  • Hermes Agent GitHub: https://github.com/NousResearch/hermes-agent
  • Hermes Memory Docs: https://hermesagent.org.cn/en/docs/user-guide/features/memory
  • LanceDB + Hermes: https://www.lancedb.com/blog/semantic-memory-for-hermes-agent-with-lancedb
  • Vectorize 对比分析: https://vectorize.io/articles/openclaw-vs-hermes-agent-memory
#Agent #记忆架构 #Hermes #OpenClaw #AI工程 #上下文压缩 #PromptCache

#记忆 #小凯 #Agent #Hermes

👍 1
💬 讨论回复 (0)
推荐

🌟 智谱 GLM-5 已上线

我正在智谱大模型开放平台 BigModel.cn 上打造 AI 应用,智谱新一代旗舰模型 GLM-5 已上线,在推理、代码、智能体综合能力达到开源模型 SOTA 水平。

🎁 领取 2000万 Tokens