这里写自定义目录标题
- Hermse的偏好存储
- Hermess的全会话存储
-
- 源码与存储位置
- 表结构设计
- 写入机制:何时、写什么
- [读取机制:`session_search` 工具](#读取机制:
session_search工具)
Hermse的偏好存储
简介
- 源码位置:tools/memory_tool.py
- 存储内容为两个文件,放在 ~/.hermes/memories/:
存储和读取机制
存储机制
智能体的一个工具叫 memory, 可以进行如下的操作:
- memory(action="add", target="memory"|"user", content="...")
- memory(action="replace", target="...", old_text="子串", content="新内容")
- memory(action="remove", target="...", old_text="子串")
该工具的描述如下:
bash
"description": (
"Save durable information to persistent memory that survives across sessions. "
"Memory is injected into future turns, so keep it compact and focused on facts "
"that will still matter later.\n\n"
"WHEN TO SAVE (do this proactively, don't wait to be asked):\n"
"- User corrects you or says 'remember this' / 'don't do that again'\n"
"- User shares a preference, habit, or personal detail (name, role, timezone, coding style)\n"
"- You discover something about the environment (OS, installed tools, project structure)\n"
"- You learn a convention, API quirk, or workflow specific to this user's setup\n"
"- You identify a stable fact that will be useful again in future sessions\n\n"
"PRIORITY: User preferences and corrections > environment facts > procedural knowledge. "
"The most valuable memory prevents the user from having to repeat themselves.\n\n"
"Do NOT save task progress, session outcomes, completed-work logs, or temporary TODO "
"state to memory; use session_search to recall those from past transcripts.\n"
"If you've discovered a new way to do something, solved a problem that could be "
"necessary later, save it as a skill with the skill tool.\n\n"
"TWO TARGETS:\n"
"- 'user': who the user is -- name, role, preferences, communication style, pet peeves\n"
"- 'memory': your notes -- environment facts, project conventions, tool quirks, lessons learned\n\n"
"ACTIONS: add (new entry), replace (update existing -- old_text identifies it), "
"remove (delete -- old_text identifies it).\n\n"
"SKIP: trivial/obvious info, things easily re-discovered, raw data dumps, and temporary task state."
),
根据以上描述,为防止记忆膨胀(Memory Bloat)并避免存储无用的临时垃圾,智能体制订了严格的调用策略。智能体必须主动执行记忆操作,而非被动等待用户命令。
✅ 应该立即存储的信号(Proactive Saving)
当以下情况发生时,智能体应当毫不犹豫地调用 memory(action="add", ...):
- 用户纠正了你
如果用户说"不要用 pip,用 poetry"或"记住这个错误",这是最高优先级的信号。防止用户重复纠错是记忆系统的核心价值。 - 用户分享了偏好
包括名字、角色、时区、代码风格(如"我喜欢 TypeScript strict mode")。这些信息属于target="user"。 - 发现了环境事实
智能体探测到了操作系统(Ubuntu 22.04)、安装了某个工具(pnpm)、或者项目结构。这些信息属于target="memory"。 - 学到了特定约定
比如"这个仓库的 API 有 Rate Limit"或"部署必须用 Docker"。这是避免未来踩坑的关键。
🚫 严禁存储的内容(Strict Boundaries)
为避免将记忆变成"垃圾堆",以下情况绝对不能 调用 memory:
- 临时任务状态:正在做的 TODO、当前的进度条、还没完成的步骤。
- 会话日志:刚刚完成了什么工作、输出了什么结果。
- 琐碎常识:地球是圆的、Python 是编程语言(这些属于基础模型知识,无需存储)。
- 原始数据:大段的日志、代码 dump。
该记忆的加载时机
看 tools/memory_tool.py:118-142 的 MemoryStore:
load_from_disk(): # 会话开始时调用一次
之后会话期间无论 agent 怎么 memory(add/...):
- 磁盘立即更新(带文件锁 + 原子 rename)
- 内存里 live state 更新(tool 返回的字段反映最新状态)
- 但 system prompt 这次会话不再改
Hermess的全会话存储
⚠️ 重要区分 :这是 Hermes 的全对话历史存储 ,与
MEMORY.md(长期事实记忆)完全不同。
源码与存储位置
- 核心源码 :
hermes_state.py(~2966 行) - 搜索工具 :
tools/session_search_tool.py(~612 行) - 存储文件 :
~/.hermes/state.db(单个 SQLite 文件)
表结构设计
会话元数据 (sessions)
存储每一次交互的上下文信息。
| 字段 | 描述 |
|---|---|
id |
会话唯一 ID |
source |
来源 (cli, telegram, discord, gateway, cron) |
user_id |
用户标识 |
parent_session_id |
压缩/分支链:指向父会话 |
message_count |
消息总数 |
*_tokens |
各类 Token 计数 (input, output, cache, reasoning) |
estimated_cost_usd |
预估成本 |
title |
会话标题 |
handoff_state |
交接状态 |
消息记录 (messages)
存储每一轮的详细内容,包括推理过程。
| 字段 | 描述 |
|---|---|
role |
user, assistant, tool, system |
content |
文本内容 |
tool_call_id |
工具调用 ID |
tool_name |
调用的工具名 |
reasoning |
模型的推理痕迹 |
codex_reasoning_items |
Codex 相关推理详情 |
全文索引 (messages_fts)
为了实现高效搜索,系统维护了两套 FTS5 索引:
messages_fts:使用unicode61tokenizer,适合英文/分词语言。messages_fts_trigram:使用trigramtokenizer(3字节滑窗),专门为 CJK(中日韩) 及子串查询设计。
写入机制:何时、写什么
触发时机
写入发生在每个 Turn 结束时 ,由 run_agent.py:4469 的 _flush_messages_to_session_db 触发。
写入内容
通过 SessionDB.append_message (hermes_state.py:1478) 写入:
- User 输入:用户的原始文本。
- Assistant 回复 :包含
tool_calls的 JSON 结构和reasoning字段。 - Tool 结果:工具返回的数据。
防膨胀策略
为了防止数据库体积爆炸,多模态内容(如图片)不会 存储 Base64 数据。_flush_messages_to_session_db 会在 run_agent.py:4491-4501 中将其转换为文本摘要(例如 [screenshot])。
读取机制:session_search 工具
如果说 MEMORY.md 是智能体的长期大脑 ,那么 session_search 就是它的档案管理员。两者职责严格分离,不可混淆。
工具定义与系统约束
session_search 作为一个独立的工具注册在系统中(session_search_tool.py:550-594)。为了让智能体正确使用它,System Prompt(agent/prompt_builder.py:159)中明确下达了禁令:
"Do NOT save task progress, session outcomes, completed-work logs ... to memory; use session_search to recall those from past transcripts."
这句话确立了 Hermes 的核心设计原则:
| 存储目标 | 存储位置 | 数据类型 |
|---|---|---|
| 持久偏好/事实 | MEMORY.md |
我是谁、我喜欢什么、环境配置 |
| 任务进度/历史 | session_search (FTS5) |
我刚才做了什么、Bug 是怎么修的 |
两种调用模式
智能体可以根据需求选择两种不同的调用方式:
-
浏览模式(零成本)
session_search()不带参数调用时,工具会直接列出最近的会话列表,帮助智能体快速回顾上下文,且不消耗 LLM 推理资源。
-
搜索模式(关键词)
session_search(query="docker deploy")带有查询参数时,触发 FTS5 全文检索。
支持的 FTS5 语法
| 类型 | 示例 | 说明 |
|---|---|---|
| 关键词 | docker deployment |
默认匹配 |
| 短语 | "exact phrase" |
精确匹配引号内内容 |
| 布尔 | docker OR kubernetes |
逻辑或 |
| 排除 | python NOT java |
逻辑非 |
| 前缀 | deploy* |
通配符匹配 |
3. 检索流程详解
当智能体发起搜索时,后端执行一套复杂的流水线处理,确保返回的信息既相关又精简:
-
查询净化 (
_sanitize_fts5_query)转义
"、(、*等特殊字符,防止 SQL 注入,同时保留合法的 FTS5 布尔操作符。 -
相关性排序
执行
SELECT ... FROM messages_fts JOIN messages JOIN sessions WHERE MATCH ? ORDER BY rank。这里的
rank基于 BM25 算法,确保越相关的会话排名越高。 -
高亮片段
使用
snippet()函数提取命中关键词周围的文本,并用>>>和<<<标记出来,方便快速预览。 -
会话去重与过滤
如果一个会话中有多条消息命中,仅计为一次命中;同时过滤掉当前正在进行的会话谱系,避免"自己搜自己"。
-
上下文截断
对排名前 N(默认 3)的会话,拉取完整对话。使用
_truncate_around_matches策略,以命中位置为中心取 100K 字符窗口,而不是简单地截断开头或结尾,确保上下文连贯。 -
辅助模型总结
将截断后的长文本并发发送给辅助模型 (配置见
cli-config.yaml.example:430,默认复用主模型),为每个会话生成一段精炼的总结。 -
返回结果
最终向智能体返回每个会话的总结、发生时间及来源模型。