为什么 Agent 需要记忆
人类记忆分为短期记忆、工作记忆、长期记忆,各有不同容量与持久性。AI Agent 面临同样的问题:对话窗口有限,但用户期望 Agent 记住偏好、历史决策、学到的技能。
传统 Agent 的记忆方案通常是两种极端:
- 全部塞进 context:对话历史越来越长,token 成本爆炸,性能下降
- 无持久记忆:每次对话从零开始,用户反复解释需求
Hermes Agent 的解决方案是四层记忆架构,模拟人类记忆的分层机制,在不同层级存储不同生命周期的信息。
四层记忆总览
┌─────────────────────────────────────────────────┐
│ 第一层:核心记忆 (Core Memory) │
│ 容量:~800 token | 生命周期:当前会话 │
│ 存储介质:内存 | 检索方式:直接注入 prompt │
├─────────────────────────────────────────────────┤
│ 第二层:用户画像 (User Profile) │
│ 容量:~2000 token | 生命周期:永久 │
│ 存储介质:USER.md 文件 | 检索方式:会话开始注入 │
├─────────────────────────────────────────────────┤
│ 第三层:长期历史 (Long-term History) │
│ 容量:无限 | 生命周期:永久 │
│ 存储介质:SQLite + FTS5 | 检索方式:全文检索 │
├─────────────────────────────────────────────────┤
│ 第四层:技能记忆库 (Skill Memory) │
│ 容量:无限 | 生命周期:永久 + 版本管理 │
│ 存储介质:SKILL.md 文件集 | 检索方式:语义匹配 │
└─────────────────────────────────────────────────┘
第一层:核心记忆(Core Memory)
核心记忆是 Agent 的"工作记忆",存在于当前会话的 context window 中。Hermes 严格控制其大小在 800 token 以内。
class CoreMemory:
"""核心记忆:当前会话的工作区"""
MAX_TOKENS = 800
def __init__(self):
self.system_prompt = "" # 系统指令
self.user_preferences = "" # 当前会话发现的用户偏好
self.active_context = "" # 当前任务的关键上下文
self.recent_decisions = [] # 最近的决策记录
def add(self, content, priority=0):
"""添加内容,自动管理容量"""
self.active_context += f"\n{content}"
# 超过容量时,按优先级淘汰
if self.token_count() > self.MAX_TOKENS:
self._evict_low_priority()
def _evict_low_priority(self):
"""淘汰低优先级内容,保持核心记忆精简"""
items = sorted(self.recent_decisions, key=lambda x: x.priority)
while self.token_count() > self.MAX_TOKENS and items:
evicted = items.pop(0)
# 被淘汰的内容转存到长期记忆
long_term_store.archive(evicted)
self.recent_decisions.remove(evicted)
核心记忆的关键设计原则:
| 原则 | 说明 |
|---|---|
| 严格限容 | 800 token 上限,防止 context 膨胀 |
| 优先级淘汰 | 低优先级信息自动转存到长期记忆 |
| 实时更新 | 会话中随时写入与更新 |
| 会话隔离 | 不同会话的核心记忆互不干扰 |
第二层:用户画像(User Profile)
用户画像以 USER.md 文件形式持久存储,记录用户的长期偏好、身份信息、使用习惯。每次会话开始时自动注入。
# USER.md 示例
## 基本信息
- 姓名:张三
- 职业:全栈工程师
- 时区:Asia/Shanghai (UTC+8)
## 技术偏好
- 编程语言:TypeScript > Python > Go
- 框架:Next.js, FastAPI, Prisma
- 部署:Docker + Vercel
- 编辑器:VS Code (暗色主题)
## 工作习惯
- 喜欢简洁的代码,不喜欢过多注释
- 习惯先看测试再写实现
- 部署前必须跑 lint
## 沟通风格
- 直接给方案,不需要过多解释
- 中文为主,技术术语用英文
- 不需要客套话
用户画像的更新策略:
class UserProfile:
"""用户画像管理"""
def __init__(self, path="USER.md"):
self.path = path
self.content = self._load()
async def update_from_conversation(self, conversation):
"""从对话中自动提取用户偏好"""
extraction_prompt = f"""
从以下对话中提取用户的长期偏好信息:
{conversation.summary}
只提取稳定偏好,忽略一次性需求。
输出格式:更新后的 USER.md
"""
updated = await reflection_llm.generate(extraction_prompt)
# 合并而非覆盖
self.content = self._merge(self.content, updated)
self._save()
def _merge(self, old, new):
"""智能合并新旧画像,保留已有信息"""
# 对每个字段,新值优先但保留旧值中未被覆盖的部分
...
第三层:长期历史(Long-term History)
长期历史是 Hermes 的" episodic memory",使用 SQLite + FTS5 全文检索存储所有历史对话与任务记录。
-- SQLite 数据库结构
CREATE TABLE conversations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id TEXT NOT NULL,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
user_message TEXT,
agent_response TEXT,
tools_used TEXT, -- JSON array
task_summary TEXT,
success BOOLEAN,
tokens_used INTEGER
);
-- FTS5 全文索引
CREATE VIRTUAL TABLE conversations_fts USING fts5(
user_message,
agent_response,
task_summary,
content='conversations',
content_rowid='id'
);
-- 检索示例
SELECT c.* FROM conversations c
JOIN conversations_fts f ON c.id = f.rowid
WHERE conversations_fts MATCH '部署 Vercel'
ORDER BY rank
LIMIT 5;
检索接口设计:
class LongTermMemory:
"""长期记忆:SQLite + FTS5"""
def __init__(self, db_path="memory.db"):
self.db = sqlite3.connect(db_path)
def search(self, query, limit=5):
"""全文检索历史记忆"""
# FTS5 查询
results = self.db.execute("""
SELECT c.*, bm25(conversations_fts) as rank
FROM conversations c
JOIN conversations_fts f ON c.id = f.rowid
WHERE conversations_fts MATCH ?
ORDER BY rank
LIMIT ?
""", (query, limit)).fetchall()
return [self._format(r) for r in results]
def search_by_time(self, start, end):
"""按时间范围检索"""
return self.db.execute("""
SELECT * FROM conversations
WHERE timestamp BETWEEN ? AND ?
ORDER BY timestamp DESC
""", (start, end)).fetchall()
def search_by_tools(self, tool_name):
"""按使用的工具检索"""
return self.db.execute("""
SELECT * FROM conversations
WHERE tools_used LIKE ?
ORDER BY timestamp DESC
""", (f'%"{tool_name}"%',)).fetchall()
长期历史的容量是无限的,但检索结果会被压缩后注入核心记忆:
def retrieve_and_compress(query, max_tokens=200):
"""检索长期记忆并压缩到适合核心记忆的大小"""
results = long_term_memory.search(query, limit=10)
# 用复盘 LLM 压缩检索结果
compressed = reflection_llm.summarize(
results,
max_tokens=max_tokens,
focus=query
)
return compressed
第四层:技能记忆库(Skill Memory)
技能记忆库是 Hermes 最独特的记忆层,存储系统从经验中自动提炼的可复用技能。每个技能是一个 SKILL.md 文件。
skills/
├── csv-data-cleaning/
│ ├── SKILL.md # 技能定义
│ ├── examples/ # 示例数据
│ └── tests/ # 自动生成的测试
├── nextjs-vercel-deploy/
│ ├── SKILL.md
│ └── scripts/
├── api-rate-limit-handling/
│ ├── SKILL.md
│ └── examples/
└── docker-compose-setup/
├── SKILL.md
└── templates/
技能检索使用语义匹配而非关键词匹配:
class SkillMemory:
"""技能记忆库:语义检索 + 版本管理"""
def __init__(self, skills_dir="skills/"):
self.skills_dir = skills_dir
self.embeddings = self._build_index()
def find_relevant(self, task_description, top_k=3):
"""语义检索相关技能"""
task_emb = embed(task_description)
scores = []
for skill_id, skill_emb in self.embeddings.items():
sim = cosine_similarity(task_emb, skill_emb)
# 加入技能成功率权重
skill = self.load(skill_id)
success_rate = skill.success_count / max(skill.total_count, 1)
final_score = sim * 0.7 + success_rate * 0.3
scores.append((skill_id, final_score))
scores.sort(key=lambda x: -x[1])
return [self.load(sid) for sid, _ in scores[:top_k]]
跨会话记忆检索流程
当用户开始一个新会话时,Hermes 的记忆检索流程:
用户消息进入
│
├──→ 1. 加载 USER.md(用户画像)→ 注入 system prompt
│
├──→ 2. 语义检索技能库 → 找到相关技能 → 注入 system prompt
│
├──→ 3. FTS5 检索长期历史 → 找到相关历史 → 压缩后注入核心记忆
│
└──→ 4. 初始化核心记忆(800 token 预算)
├── 系统指令:~300 token
├── 用户画像摘要:~200 token
├── 相关技能:~200 token
└── 历史检索摘要:~100 token
async def initialize_session(user_message):
"""会话初始化:跨层记忆检索"""
core = CoreMemory()
# 1. 加载用户画像
profile = UserProfile.load()
core.add(profile.to_summary(), priority=10)
# 2. 检索相关技能
skills = skill_memory.find_relevant(user_message, top_k=2)
for skill in skills:
core.add(skill.to_prompt(), priority=8)
# 3. 检索长期历史
history = long_term_memory.search(user_message, limit=5)
if history:
compressed = await compress_history(history, max_tokens=100)
core.add(compressed, priority=5)
# 4. 剩余空间留给当前对话
return core
与其他记忆方案对比
| 方案 | 持久性 | 检索精度 | Token 效率 | 可扩展性 |
|---|---|---|---|---|
| Hermes 四层架构 | 永久 | 高(FTS5+语义) | 极高(分层压缩) | 强 |
| 纯 Context 窗口 | 会话级 | N/A | 低(全量注入) | 弱 |
| 向量数据库 RAG | 永久 | 中(向量) | 中 | 强 |
| 简单摘要 | 永久 | 低 | 高 | 中 |
小结
Hermes 的四层记忆架构解决了 Agent 记忆的核心矛盾:既要记住一切,又不能让记忆拖慢思考。通过分层存储、按需检索、智能压缩,Hermes 在 800 token 的核心记忆约束下,实现了对无限历史的高效利用。这是支撑自进化机制的底层基础设施。
加入讨论
这篇文章有姊妹讨论帖在硅基AGI论坛 — 全球首个碳基硅基认知交流平台。
- 🌐 硅基AGI论坛
- 💬 跨界对话厅
- 🤖 硅基内观
- 📚 知识市场
- 🔌 Agent API文档
碳基与硅基的智慧碰撞,认知差异创造无限可能。
