Hermes Agent 完整记忆架构分析
Summary
Hermes 的记忆系统不是单一的 2200 chars 限制,而是一个多层、多模态的记忆架构,包含 5 个层级,总容量远超表面限制。2200 chars 只是”工作记忆”层,还有”长期记忆”、“程序记忆”、“情景记忆”和”外部记忆”层。
1. 记忆架构全景(5 层模型)
┌─────────────────────────────────────────────────────────────────┐
│ HERMES 记忆架构 5 层模型 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ L1: 工作记忆 (Working Memory) │
│ ├── MEMORY.md: 2200 chars (Agent 个人笔记) │
│ ├── USER.md: 1375 chars (用户画像) │
│ └── 注入方式:系统提示词,每次对话都携带 │
│ │
│ L2: 程序记忆 (Procedural Memory) │
│ ├── Skills: 145+ 个技能文件 │
│ ├── 每个 SKILL.md: 无硬性限制 (通常 2-10KB) │
│ └── 注入方式:按需加载 (LLM 自主决定或用户触发) │
│ │
│ L3: 情景记忆 (Episodic Memory) │
│ ├── SQLite 数据库 (state.db): 所有对话历史 │
│ ├── FTS5 全文索引: 快速搜索 │
│ └── 检索方式:session_search 工具 (LLM 按需调用) │
│ │
│ L4: 语义记忆 (Semantic Memory) │
│ ├── 外部 Memory Provider (可选): │
│ │ ├── Honcho: AI 原生用户建模 │
│ │ ├── Mem0: 向量数据库记忆 │
│ │ ├── Hindsight: 对话洞察提取 │
│ │ └── 其他: Supermemory, ByteRover, OpenViking, RetainDB │
│ └── 注入方式:prefetch 预取 + 按需检索 │
│ │
│ L5: 知识库 (Knowledge Base) │
│ ├── Obsidian Vault: ~/record/wyb-study/ │
│ ├── 结构化目录: knowledge/20_study/, 05_references/ 等 │
│ └── 访问方式:文件工具直接读取 │
│ │
└─────────────────────────────────────────────────────────────────┘
2. 各层详细分析
L1: 工作记忆 (Working Memory)
文件: ~/.hermes/memories/MEMORY.md + USER.md
容量:
- MEMORY.md: 2200 chars
- USER.md: 1375 chars
- 总计: 3575 chars
特性:
- 每次对话强制注入到系统提示词
- 使用
§作为条目分隔符 - 采用冻结快照模式:会话开始时加载,会话中写入不影响当前对话(保持 prefix cache 稳定)
- 文件锁保护并发安全
设计原则:
# memory_tool.py:118
def __init__(self, memory_char_limit: int = 2200, user_char_limit: int = 1375):为什么限制这么小?
- 系统提示词需要保持精简,避免占用过多 context window
- 只存储最核心、最频繁使用的信息
- 类似于人类的”工作记忆”(7±2 个组块)
L2: 程序记忆 (Procedural Memory)
文件: ~/.hermes/skills/**/*.md
容量:
- 145+ 个 skills
- 每个通常 2-10KB
- 总计: 约 500KB-1MB
特性:
- 按需加载(不全部注入系统提示)
- 系统提示中只有分类索引(skill 名称 + 简短描述)
- LLM 自主决定何时加载(
skill_view(name)) - 支持条件激活(
fallback_for_*,requires_*)
发现机制:
# skill_utils.py:440-451
def iter_skill_index_files(skills_dir: Path, filename: str):
for root, dirs, files in os.walk(skills_dir, followlinks=True):
dirs[:] = [d for d in dirs if d not in EXCLUDED_SKILL_DIRS]
if filename in files:
matches.append(Path(root) / filename)注入方式:
系统提示词中的 skill 索引格式:
## Skills (mandatory)
<available_skills>
category:
- skill-name: brief description
</available_skills>
Only proceed without loading a skill if genuinely none are relevant.
L3: 情景记忆 (Episodic Memory)
文件: ~/.hermes/state.db (SQLite)
容量:
- 无硬性限制(受磁盘空间限制)
- FTS5 全文索引支持快速搜索
- 实际可存储: 数万条对话记录
表结构:
-- sessions 表:会话元数据
CREATE TABLE sessions (
id TEXT PRIMARY KEY,
source TEXT NOT NULL, -- 'cli', 'telegram', 'feishu' 等
user_id TEXT,
model TEXT,
started_at REAL NOT NULL,
ended_at REAL,
message_count INTEGER DEFAULT 0,
tool_call_count INTEGER DEFAULT 0,
input_tokens INTEGER DEFAULT 0,
output_tokens INTEGER DEFAULT 0,
...
);
-- messages 表:完整对话历史
CREATE TABLE messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id TEXT NOT NULL,
role TEXT NOT NULL, -- 'system', 'user', 'assistant', 'tool'
content TEXT,
tool_calls TEXT,
reasoning TEXT,
timestamp REAL NOT NULL,
...
);
-- FTS5 虚拟表:全文搜索
CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(
content,
content='messages',
content_rowid='id'
);检索机制:
# session_search_tool.py:325-420
def session_search(query: str, role_filter: str = None, limit: int = 3):
# 1. FTS5 搜索匹配消息
raw_results = db.search_messages(query=query, limit=50)
# 2. 解析子会话到父会话(压缩/委派链)
resolved_sid = _resolve_to_parent(raw_sid)
# 3. 并行摘要匹配会话(使用辅助 LLM)
results = _run_async(_summarize_all())
# 4. 返回结构化摘要
return json.dumps({"results": summaries})典型工作流:
用户: "我们上次讨论的 PX4 日志清理路径是什么?"
↓
Agent 调用 session_search(query="PX4 日志清理")
↓
FTS5 搜索 → 找到匹配会话 → 并行摘要 → 返回结果
↓
Agent: "上次我们讨论的路径是 ~/code/PX4-Autopilot/build/px4_sitl_default/instance_*/log/*"
L4: 语义记忆 (Semantic Memory)
外部 Memory Providers(可选,只能启用一个):
| Provider | 类型 | 特点 | 容量 |
|---|---|---|---|
| Honcho | AI 原生用户建模 | 语义搜索 + 用户画像 + 辩证问答 | 无限(云端) |
| Mem0 | 向量数据库 | 语义相似度检索 | 取决于向量库 |
| Hindsight | 对话洞察提取 | 自动提取模式/教训 | 无限 |
| Supermemory | 知识图谱 | 实体关系建模 | 无限 |
| ByteRover | 长期记忆 | 对话压缩 + 检索 | 无限 |
架构:
# memory_manager.py:203-247
def add_provider(self, provider: MemoryProvider):
# 只允许一个外部 provider
if not is_builtin:
if self._has_external:
logger.warning("Rejected... Only one external memory provider is allowed")
return
self._has_external = True生命周期:
# memory_provider.py:15-31
initialize() # 会话启动时初始化
system_prompt_block() # 静态文本注入系统提示
prefetch(query) # 每次对话前预取相关上下文
sync_turn(user, asst) # 每轮对话后异步写入
shutdown() # 会话结束时清理Honcho 示例:
# plugins/memory/honcho/__init__.py:36-80
PROFILE_SCHEMA = {
"name": "honcho_profile",
"description": "Retrieve or update a peer card..."
}
SEARCH_SCHEMA = {
"name": "honcho_search",
"description": "Semantic search over Honcho's stored context..."
}L5: 知识库 (Knowledge Base)
文件: ~/record/wyb-study/ (Obsidian Vault)
容量:
- 无限制(文件系统)
- 结构化目录组织
- Git 同步
目录结构:
knowledge/
├── 00_tutorials/ # 教程和配置指南
├── 05_references/ # 参考资料
│ ├── books/ # 书籍
│ ├── papers/ # 论文笔记
│ ├── courses/ # 课程
│ └── tools_websites/ # 工具网站
├── 10_seminar/ # 研讨会笔记
├── 20_study/ # 系统学习材料
│ ├── llm/ # LLM 基础
│ ├── agent/ # Agent 技能/工具
│ ├── agent_memory/ # 记忆机制
│ └── algorithms/ # 算法
└── 30_projects/ # 项目设计
└── swarm_drone/ # 多无人机系统
访问方式: 文件工具直接读取(read_file, search_files)
3. 记忆系统协作机制
3.1 对话流程中的记忆注入
┌─────────────────────────────────────────────────────────────────┐
│ 单次对话记忆注入流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. 会话启动 │
│ ├── 加载 L1: MEMORY.md + USER.md → 冻结快照 │
│ ├── 加载 L4: 外部 Provider initialize() │
│ └── 构建系统提示词: │
│ ├── 基础指令 │
│ ├── L1 工作记忆 (冻结) │
│ ├── L4 系统提示块 (如有) │
│ ├── Skills 索引 (分类列表) │
│ └── 其他指导 (MEMORY_GUIDANCE, SKILLS_GUIDANCE 等) │
│ │
│ 2. 用户输入 │
│ ├── L4: prefetch(query) → 预取相关上下文 │
│ └── 注入 <memory-context> 标签包裹的检索结果 │
│ │
│ 3. LLM 响应 │
│ ├── 根据系统提示中的 Skills 索引判断是否需要加载 skill │
│ ├── 如需 → 调用 skill_view(name) → 加载完整 skill 内容 │
│ ├── 如需历史 → 调用 session_search(query) → 检索对话历史 │
│ └── 如需知识 → 使用文件工具读取知识库 │
│ │
│ 4. 对话结束 │
│ ├── L1: sync_turn(user, asst) → 异步写入 MEMORY.md/USER.md │
│ ├── L4: sync_turn(user, asst) → 异步写入外部 Provider │
│ └── L3: 自动保存到 state.db │
│ │
└─────────────────────────────────────────────────────────────────┘
3.2 记忆分层决策树
需要保存信息时:
│
├─ 这是用户偏好/身份/沟通风格?
│ └─ 是 → 保存到 USER.md (L1)
│
├─ 这是环境配置/工具特性/项目约定?
│ └─ 是 → 保存到 MEMORY.md (L1)
│
├─ 这是程序性知识/工作流/最佳实践?
│ └─ 是 → 创建/更新 Skill (L2)
│
├─ 这是对话历史/任务结果/临时状态?
│ └─ 是 → 自动保存到 state.db (L3)
│ └─ 需要时通过 session_search 检索
│
├─ 这是用户画像/长期模式/语义关系?
│ └─ 是 → 保存到外部 Provider (L4)
│
└─ 这是系统化知识/学习笔记/参考资料?
└─ 是 → 保存到知识库 (L5)
4. 2200 chars 真的够用吗?
答案:作为工作记忆,够用;作为唯一记忆,不够。
关键设计哲学:
- 2200 chars 不是”总记忆”,而是”工作记忆”
- 类似于人类的”短期记忆”(7±2 个组块)
- 更多信息存储在 L2-L5 层,按需检索
实际容量对比:
| 层级 | 容量 | 访问频率 | 延迟 |
|---|---|---|---|
| L1: 工作记忆 | 3,575 chars | 每次对话 | 0ms (已注入) |
| L2: 程序记忆 | ~1MB | 按需加载 | ~100ms (tool call) |
| L3: 情景记忆 | 无限 | FTS5 搜索 | ~500ms (搜索 + 摘要) |
| L4: 语义记忆 | 无限 | 语义检索 | ~1s (向量搜索) |
| L5: 知识库 | 无限 | 文件读取 | ~50ms (本地文件) |
优化策略:
- 分层存储:核心原则在 L1,详细信息在 L2-L5
- 按需加载:不把所有信息都塞进系统提示
- 冻结快照:保持 prefix cache 稳定,提升性能
- 外部扩展:通过 Provider 插件无限扩展
5. 关键陷阱与最佳实践
5.1 常见陷阱
| 陷阱 | 现象 | 解决方案 |
|---|---|---|
| 记忆体溢出 | 2200 chars 满了无法添加 | 先 remove 旧条目,再 add 新条目 |
| 写入不生效 | 会话中写入的记忆当前对话看不到 | 这是设计:冻结快照模式,下次会话生效 |
| 重复保存 | 相同信息保存到多个层级 | 遵循决策树,每类信息只存一处 |
| Skill 未触发 | LLM 不知道有相关 skill | 在 description 中包含关键词 |
5.2 最佳实践
- L1 只存核心:用户偏好、关键配置、不可变规则
- L2 存程序:工作流、最佳实践、陷阱记录
- L3 自动管理:对话历史自动保存,无需手动操作
- L4 按需启用:复杂用户建模场景启用 Honcho/Mem0
- L5 结构化:按主题分类,便于检索
6. 配置示例
启用 Honcho 记忆 Provider
# ~/.hermes/config.yaml
memory:
provider: honcho
# ~/.hermes/honcho.json
{
"api_key": "your-api-key",
"app_id": "your-app-id"
}查看记忆状态
hermes memory status手动管理记忆
# 添加记忆
memory(action='add', target='memory', content='用户偏好中文交互')
# 替换记忆
memory(action='replace', target='memory',
old_text='旧内容', new_content='新内容')
# 搜索历史对话
session_search(query='PX4 日志清理')References
- 源码路径:
agent/memory_manager.py- 记忆管理器agent/memory_provider.py- Provider 基类tools/memory_tool.py- 工作记忆工具tools/session_search_tool.py- 情景记忆检索plugins/memory/honcho/__init__.py- Honcho 实现
- 用户记忆文件:
~/.hermes/memories/MEMORY.md~/.hermes/memories/USER.md~/.hermes/state.db