Hermes 三层记忆系统(上):会话记忆------让 AI 记住刚才聊了什么
第 19 篇讲了 Hermes 的核心理念是"会记住你"。第 20 篇配好了 config.yml,里面有个 memory 块,分了三层:session / persistent / skill。从这一篇开始,我们一层一层拆开看。先从最底层的会话记忆开始------它是整个记忆系统的基础,也是最容易被忽视的一层。
会话记忆是什么
当你和 Hermes 对话时,每一轮你说的话、它的回复、它读过的文件、它执行的命令......所有这些信息按时间顺序组成一个数组,这就是会话记忆。
┌──────────────────────────────────────────────────────────┐
│ 会话记忆(Session Memory) │
├──────────────────────────────────────────────────────────┤
│ │
│ Turn 1: 👤 "帮我写一个 FastAPI 的用户认证接口" │
│ 🤖 "[生成代码...]" │
│ │
│ Turn 2: 👤 "密码哈希记得加 salt" │
│ 🤖 "已修正:[更新后的代码...]" │
│ │
│ Turn 3: 👤 "再加一个 JWT token 刷新接口" │
│ 🤖 "好的,基于刚才的认证模块,我加上 refresh..." │ ← 它知道"刚才的认证模块"
│ │
│ ...以此类推... │
│ │
└──────────────────────────────────────────────────────────┘
会话记忆和普通"聊天记录"的区别:聊天记录只是文本。会话记忆是结构化的------每条消息包含角色(user/assistant/system/tool)、时间戳、Token 数、关联的文件路径、被引用的记忆条目 ID 等元信息。
会话记忆 vs 上下文窗口
这两个概念经常被混淆,但它们的区别很关键:
会话记忆(Session Memory)
= AI 可以访问的对话历史数据(存储层)
→ 存在 SQLite 的 sessions 表中
→ 理论上可以无限存储
上下文窗口(Context Window)
= AI 模型一次能"看到"的最大 Token 数(推理层)
→ Claude Sonnet 4: 200K tokens
→ GPT-5: 128K tokens
→ 是模型架构决定的硬限制
关系:
会话记忆 > 上下文窗口时 → 最旧的对话被裁剪掉
会话记忆 < 上下文窗口时 → 全部对话都在 AI 的"视野"内
一个更直观的类比:
会话记忆 = 你桌上的所有文件(一直在积累)
上下文窗口 = 你能同时摊开看的桌面面积(固定的)
当文件太多 → 桌面放不下 → 最早的文件被收到抽屉里(裁剪)
你需要这些文件时 → 只能从抽屉里找(持久记忆 / 摘要)
裁剪发生时会发生什么
假设上下文窗口是 200K,当前会话记忆是 250K:
┌──────────────────────────────────────────────────────────┐
│ 被裁剪的区域(50K) │ 上下文窗口内的区域(200K) │
│ ──────────────────────── │ ──────────────────────────── │
│ Turn 1-15: │ Turn 16-30: │
│ "帮我建 FastAPI 项目" │ "JWT 刷新逻辑有个 bug..." │
│ "用 SQLAlchemy" │ "这里的 expire time..." │
│ "密码加 salt 和 bcrypt" │ "不对,应该是 refresh..." │
│ │ │
│ ❌ AI 看不到这些了 │ ✅ AI 能看到 │
└──────────────────────────────────────────────────────────┘
影响:如果你在第 25 轮说"之前那个密码加密方案改一下",AI 已经看不到第 1-15 轮的对话了,它不知道"之前那个"指的是什么------这就是为什么超长对话中 AI 会变"笨"。
Hermes 的会话记忆存储
在 ~/.hermes/data/memory.db 中,会话记忆存储在两张核心表中:
sessions 表
sql
CREATE TABLE sessions (
session_id TEXT PRIMARY KEY, -- UUID
user_id TEXT, -- 关联用户
title TEXT, -- 自动生成的会话标题
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
total_tokens INTEGER DEFAULT 0, -- 会话累计 Token
message_count INTEGER DEFAULT 0, -- 消息轮数
status TEXT DEFAULT 'active', -- active | archived | deleted
summary TEXT -- 超过上下文的早期内容摘要
);
messages 表
sql
CREATE TABLE messages (
message_id TEXT PRIMARY KEY,
session_id TEXT REFERENCES sessions(session_id),
role TEXT NOT NULL, -- user | assistant | system | tool
content TEXT NOT NULL,
token_count INTEGER,
tool_calls JSON, -- 如果这轮调用了工具,记录参数和结果
file_refs JSON, -- 引用的文件路径列表
memory_refs JSON, -- 引用的持久记忆条目 ID
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_messages_session ON messages(session_id, created_at);
会话恢复机制
当你退出 Hermes 再重新进入时:
bash
# 查看所有会话
hermes session list
# 输出示例:
# session_id title updated messages
# a1b2c3d4... FastAPI 用户认证模块开发 2026-06-04 15:30 12
# e5f6g7h8... 数据库连接池优化讨论 2026-06-03 10:15 8
# i9j0k1l2... 上周的代码风格讨论 2026-05-28 18:00 25
# 恢复上次会话
hermes session resume
# 或恢复指定的会话
hermes session resume a1b2c3d4
恢复时 Hermes 会做什么:
- 从
messages表加载该会话的所有消息 - 根据
total_tokens判断是否超过当前模型的上下文窗口 - 如果没超过 → 全部注入上下文,无缝衔接
- 如果超过了 → 加载最近的消息 + 将早期的消息用
summary替代
让会话记忆更高效:四个实践
实践一:一件事一个会话
❌ 一个会话做所有事:
Turn 1-5: 写用户认证模块(FastAPI → JWT → bcrypt → 测试)
Turn 6-10: 审查前端的登录页面(React → 表单验证 → CSS)
Turn 11-15: 讨论 Kubernetes 部署方案(Docker → K8s → CI/CD)
问题:三个不相关的话题挤在一个上下文里
→ 每个话题能用的 Token 变少
→ 讨论 K8s 时上下文里还留着 bcrypt 的细节(噪音)
✅ 一个会话一件事:
Session A: 写用户认证模块 → 做完 → /clear
Session B: 审查前端登录页 → 做完 → /clear
Session C: 讨论 K8s 部署 → 做完 → /clear
每个会话的上下文都聚焦在一个任务上
→ AI 的注意力不会被无关信息稀释
实践二:主动清除不用的上下文
bash
# 1. 清除当前会话(最常用)
/clear
# 效果:清空消息数组,但保留持久记忆中记录的关键信息
# 2. 忘记当前会话中最近 N 轮
/forget-session 5
# 效果:删除最近 5 轮对话(适用于"刚才那段讨论走错方向了")
# 3. 忘记当前会话中特定的内容
/forget-session --keyword "MySQL"
# 效果:删除当前会话中包含"MySQL"的所有消息
# 4. 归档会话(不删除,但不再加载到上下文)
hermes session archive <session_id>
什么时候该 /clear:当你感觉 Hermes 的回复开始出现这些信号:
信号 1:它开始混淆这个任务和上一个任务的信息
信号 2:它的代码开始偏离你的要求(说明 System Prompt 的约束被对话历史稀释了)
信号 3:回复明显变慢(上下文太大,推理耗时增加)
信号 4:`hermes session info` 显示上下文使用率 > 80%
实践三:用摘要替代完整对话
Hermes 支持预压缩------把早期的完整对话用一段摘要替代,而不是直接丢弃:
yaml
# config.yml
memory:
session:
backend: "memory"
max_messages: 50
ttl: 3600
# 新增:上下文压缩策略
compression:
enabled: true
strategy: "hybrid" # rolling_window | summary | hybrid
keep_recent: 10 # 最近 10 轮保持完整
summarize_older: true # 更早的对话用 LLM 摘要替代
三种策略的对比:
策略 最近的消息 早期的消息 效果
─────────────────────────────────────────────────────────
rolling_window 全部保留 直接丢弃 简单,但丢失历史信息
summary 全部摘要 全部摘要 省 Token,但细节丢失
hybrid 完整保留 自动摘要 推荐:兼顾连贯性和省 Token
hybrid 的工作原理:
原始会话(25 轮,150K Token):
Turn 1 → ... → Turn 15 → Turn 16 → ... → Turn 25
压缩后(~80K Token):
[摘要:Turn 1-15 讨论了 FastAPI 的 JWT 认证方案,
关键决策:access token 30min/refresh token 7d,
密码用 bcrypt + salt,数据库用 PostgreSQL]
+ Turn 16(完整)→ ... → Turn 25(完整)
实践四:关键信息及时转入持久记忆
会话记忆的局限是它会随着会话结束或裁剪而消失。所以------
当前会话中的重要信息 → 应该尽快写入持久记忆
方式一:Hermes 自动提取(默认开启)
Hermes 在后台分析对话 → 发现"用户偏好函数式风格"
→ 自动写入持久记忆
方式二:你主动标记
👤 > /remember "JWT access token 过期时间设为 30 分钟"
🤖 > 已存入持久记忆。
方式三:对话中自然触发
👤 > 记住,我们这个项目用 PostgreSQL,不用 MySQL
🤖 > 已记录项目数据库选型。
会话记忆的常见问题
问题一:接近上下文天花板时 AI 变笨
这是最常见的问题。现象:
前半段对话:Hermes 的代码质量很高,逻辑严密
后半段对话:同一个话题,Hermes 开始犯低级错误
→ 变量名拼错
→ 忽略了前面自己写的函数
→ 重复问了已经回答过的问题
原因:上下文太满时,模型的注意力被稀释。它在"扫描"海量历史消息时,分配给每条消息的注意力远低于前半段。
解决:
bash
# 1. 先检查
hermes session info
# Token usage: 178K / 200K (89%) ← 危险
# 2. 果断 /clear
/clear
# 3. 如果需要保留关键上下文,先 /remember
/remember "当前任务:重构用户模块的认证逻辑。已完成的:JWT 签发和验证。待处理:refresh token 轮换、token 黑名单。"
/clear
# 这样持久记忆会保留下关键信息,新会话自动加载
问题二:会话切换时上下文断裂
Session A: 讨论了 20 轮 FastAPI 的项目结构
Session B: 全新的对话 → Hermes 不知道"这个项目"指的是什么
👤 > "在刚才的项目里加一个接口"
🤖 > "你指的是哪个项目?我目前没有上下文。"
解决:重要信息不放"刚才的对话"里,放持久记忆里:
bash
# Session A 结束时
/remember "项目 my-api 使用 FastAPI + SQLAlchemy + PostgreSQL,目录结构 src/models → src/services → src/routers → src/schemas"
# Session B 开始时
Hermes 自动从持久记忆中检索到"项目 my-api"的信息 → 不需要你重新介绍
问题三:工具调用结果占用大量 Token
👤 > "帮我查一下用户表的结构"
🤖 > [调用工具:读取 src/models/user.py]
[工具返回:整个文件 300 行代码,8000 Token]
问题:这 8000 Token 的模型定义会留在会话记忆中
→ 后面 10 轮讨论"要不要加个字段"时,这些字段定义一直在占用上下文
解决:Hermes 会自动将工具返回的大文件在对话历史中替换为摘要:
yaml
# config.yml 中可以配置
advanced:
context_management:
tool_result_max_tokens: 2000 # 工具返回结果在对话中最多保留 2000 Token
tool_result_summarize: true # 超过上限的部分用 AI 摘要替代
会话记忆的配置参考
yaml
# config.yml 中 session 块的完整参数
memory:
session:
backend: "memory" # memory(进程内,重启丢失) | redis(跨重启)
max_messages: 50 # 保留最近 N 条消息
ttl: 3600 # 无活动后过期时间(秒),0 = 不过期
compression:
enabled: true
strategy: "hybrid" # rolling_window | summary | hybrid
keep_recent: 10 # hybrid 模式下保留最近 N 轮完整
summarize_older: true
summary_model: "default" # 用于生成摘要的模型,default = 当前主模型
auto_title: true # 自动为会话生成标题
save_tool_results: true # 是否保存工具调用结果
tool_result_max_tokens: 2000 # 工具结果在对话中最多保留 Token 数
persistent:
# ... 下一篇讲
skill:
# ... 下下篇讲
和 ChatGPT / Claude Code 的会话记忆对比
ChatGPT Claude Code Hermes
──────────────────────────────────────────────────────────────────
会话恢复 自动(30天) 无(每次独立) 手动或自动 resume
上下文裁剪 透明(用户无感) 透明 可配置裁剪策略
预压缩 不支持 不支持 支持 hybrid 摘要
跨会话传递 靠持久记忆 靠 CLAUDE.md 靠持久记忆 + resume
存储位置 云端 本地(.claude/) 本地 SQLite
可见性 用户看不到会话数据 可查看 JSON 文件 hermes session list
可操作性 很低(只能改名/删) 低 高(归档/恢复/搜索)
关键差异:ChatGPT 和 Claude Code 的会话记忆对用户来说更像"黑箱"------你感受到它们记住了,但你控制不了怎么记、记多少、什么时候清。Hermes 把会话记忆当作一个可管理、可调优的系统暴露出来。
这篇文章的要点
1. 会话记忆 ≠ 上下文窗口
→ 会话记忆是存储层(可以无限存)
→ 上下文窗口是推理层(模型架构决定的硬限制 200K)
→ 会话记忆 > 上下文窗口时,最旧的对话被裁剪
2. 会话记忆存储在 SQLite 的 sessions + messages 表中
→ 支持会话恢复(hermes session resume)
→ 支持会话列表和归档
3. 四个让会话记忆更高效的实践:
→ 一件事一个会话,做完就 /clear
→ 主动清除不用的上下文(/forget-session)
→ 用 hybrid 摘要替代完整历史(省 40-50% Token)
→ 关键信息及时转入持久记忆
4. 上下文使用率 > 80% 时 AI 开始变笨------这是我的经验阈值