系列第四篇。本文深入解析 Hermes 记忆系统的三层架构:为什么拒绝"统一记忆系统"的诱惑,SQLite + FTS5 双表设计如何解决中文搜索的工程问题,以及 parent_session_id 这一个字段如何解锁了整个分支/回滚生态。


引子:为什么记忆问题没有统一答案

构建一个长期陪伴用户的 AI Agent,工程师面对的第一个诱惑是:把所有"需要记住的东西"放进一个统一的存储层。向量数据库、关系数据库,或者专门的记忆框架——选一个,然后把所有东西往里塞。

这个诱惑来自于一个不准确的前提:记忆是同质的。实际上,"AI 需要记忆"这个需求背后隐藏着至少三种截然不同的问题:

这三个问题的写入者不同(对话引擎、人工文档作者、多轮交互的积累),读取者不同(消息历史重放、即时的工具调用、每轮推理前的上下文注入),生命周期不同(单次会话、永久有效、随用户偏好演变)。

把这三类问题合并到同一个存储里会产生什么?最直接的后果是语义污染:如果向量搜索把"用户喜欢简短回答"这条偏好记忆和"上次部署失败的错误日志"混在一起,每次召回时都需要额外的过滤和排序逻辑,而这个过滤本身就是一个需要反复调试的问题。更严重的是,统一存储倾向于形成单一的访问模式——为了高效召回长期记忆而优化的向量索引,对于需要精确顺序重放的对话历史来说是错误的工具。

Hermes 的答案是拒绝合并:明确承认记忆问题的异质性,用三个独立的层次分别解决三个独立的问题。


三层模型的边界划分

第一层:会话历史(Session History)

这是对话连贯性的物理载体——按时间顺序排列的消息序列。它存储在 hermes_state.py 的 SessionDB 中,以 SQLite 作为后端。每轮对话结束后自动持久化,/resume 时完整重放到 messages 数组。

这一层的核心约束是顺序语义。LLM API 要求消息严格按 user/assistant/tool 交替出现,任何顺序错误都会导致 API 拒绝请求。关系数据库的行顺序(加上 timestamp 索引)天然满足这个约束,而向量数据库按相似度排序的召回机制从根本上就是错误的工具。

第二层:Skill(过程知识)

Skill 是"如何做某件事"的结构化知识——一个描述特定工作流程的 Markdown 文件。用户激活 Skill 后,它的内容被注入到对话流中。

这一层的核心约束是稳定性:Skill 内容是人工维护的、版本化的、确定性的。它不应该参与相似度搜索或动态召回,因为工作流程的正确性不是用距离来衡量的。把 Skill 放进向量数据库并按语义相似度召回,意味着在错误的 Skill 和正确的 Skill 之间做了一次不必要的模糊选择。

第三层:记忆提供者(Memory Provider)