1. 核心命题:不改模型权重,只改"怎么用模型"
Hermes Agent 的自进化能力,本质上不是模型层面的学习------不涉及微调、强化学习或参数更新------而是一次精妙的 Prompt Engineering + 文件持久化 的工程化实践。它的核心命题是:
不改变 LLM 本身,而是改变 LLM 被调用的方式。
具体而言,Hermes 的"进化"体现在三个维度的自动积累:
| 维度 | 载体 | 持久化位置 | 作用 |
|---|---|---|---|
| 声明性记忆 | MEMORY.md / USER.md | ~/.hermes/memories/ |
跨会话的事实与偏好 |
| 程序性技能 | SKILL.md | ~/.hermes/skills/ |
可复用的工作流程 |
| 跨会话检索 | SQLite FTS5 | ~/.hermes/state.db |
历史对话的语义召回 |
三者共同构成一个闭环:执行 → 审查 → 沉淀 → 注入 → 再执行。
2. 四维持久记忆系统
2.1 架构设计
Hermes 的记忆系统由 tools/memory_tool.py 中的 MemoryStore 类实现,维护两个平行的存储:
bash
~/.hermes/memories/
├── MEMORY.md # Agent 的个人笔记(环境事实、项目约定、工具特性、经验教训)
└── USER.md # 用户画像(偏好、沟通风格、期望、工作习惯)
两个文件使用 §(分节符)作为条目分隔符,每个条目是一段自然语言文本。例如:
用户偏好简洁的代码风格,不写冗余注释
§
项目使用 Python 3.11,包管理用 uv
§
该 API 端点容易超时,建议加重试逻辑
2.2 冻结快照机制
这是记忆系统最关键的设计决策。在 MemoryStore.load_from_disk() 中:
python:agent/memory_tool.py
def load_from_disk(self):
self.memory_entries = self._read_file(mem_dir / "MEMORY.md")
self.user_entries = self._read_file(mem_dir / "USER.md")
# 捕获冻结快照,用于系统提示词注入
self._system_prompt_snapshot = {
"memory": self._render_block("memory", self.memory_entries),
"user": self._render_block("user", self.user_entries),
}
冻结快照(frozen snapshot)意味着:
- 会话开始时,系统提示词加载当前 MEMORY.md 和 USER.md 的内容作为快照
- 会话进行中,即使后台更新了这些文件,当前会话看到的仍然是开始时的版本
- 下次会话,系统提示词才会反映最新的文件内容
为什么这样设计?因为系统提示词在会话中途被修改会导致:
- 前缀缓存(prefix caching)失效,成本剧增
- 模型上下文出现不一致------系统提示词与实际文件内容不同步
2.3 读写安全
写入操作(add/replace/remove)使用原子文件替换保证并发安全:
python:tools/memory_tool.py
@staticmethod
def _write_file(path: Path, entries: List[str]):
fd, tmp_path = tempfile.mkstemp(dir=str(path.parent), suffix=".tmp", prefix=".mem_")
with os.fdopen(fd, "w", encoding="utf-8") as f:
f.write(content)
f.flush()
os.fsync(f.fileno())
os.replace(tmp_path, str(path)) # 同一文件系统上的原子操作
读取端无需加锁------os.replace() 保证了读者要么看到旧的完整文件,要么看到新的完整文件,绝不会看到半写状态。
同时,所有写入内容经过安全扫描,阻止提示注入和数据泄露模式:
python:tools/memory_tool.py
_MEMORY_THREAT_PATTERNS = [
(r'ignore\s+(previous|all|above|prior)\s+instructions', "prompt_injection"),
(r'curl\s+[^\n]*\$\{?\w*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API)', "exfil_curl"),
(r'authorized_keys', "ssh_backdoor"),
# ... 更多模式
]
2.4 容量管理
记忆系统设置了字符上限(非 Token 数,因为字符计数与模型无关):
超出时拒绝新增,强制 Agent 执行替换或删除操作。这防止了记忆无限膨胀,也迫使 Agent 定期整理和精简自己的"笔记"。
2.5 记忆管理器(MemoryManager)
agent/memory_manager.py 中的 MemoryManager 是记忆系统的编排层,负责:
- 内置提供者(BuiltinMemoryProvider)--- 始终注册,提供 MEMORY.md/USER.md 的读写
- 外部提供者(External Provider)--- 最多一个,如 Honcho 的辩证式用户建模
- 统一接口 ---
build_system_prompt()、prefetch_all()、sync_all()、handle_tool_call()
python:agent/memory_manager.py
class MemoryManager:
def __init__(self):
self._providers: List[MemoryProvider] = []
self._has_external: bool = False # 最多一个外部提供者
def add_provider(self, provider: MemoryProvider):
is_builtin = provider.name == "builtin"
if not is_builtin and self._has_external:
logger.warning("Rejected --- only one external memory provider allowed")
return
# ...
3. 技能自动创造系统
3.1 技能的生命周期
在传统 Agent 中,Skill 的生命周期是:
用户编写 Skill → 安装到 Agent → Agent 执行 → 结束
Hermes Agent 将其改为:
Agent 执行任务 → 后台自动审查 → 判断是否有价值 → 自动创建/更新 Skill
3.2 SKILL.md 格式
每个技能本质上是一个包含 YAML Frontmatter 的 Markdown 文件:
markdown
---
name: web-scraping-with-retry
description: 网页爬取与重试策略
version: 1.0.0
platforms: [linux, macos]
prerequisites:
env_vars: [FIRECRAWL_API_KEY]
---
# 网页爬取与重试 Skill
## 触发条件
- 需要从网页提取结构化数据
- 目标网站有反爬机制
## 步骤
1. 先用 web_extract 尝试直接提取
2. 若返回空结果,切换到 browser_navigate + browser_snapshot
3. ...
## 常见陷阱
- Firecrawl 对 SPA 页面支持不佳,优先用浏览器工具
- ...
技能目录结构支持丰富的附件:
javascript
~/.hermes/skills/web-scraping-with-retry/
├── SKILL.md
├── references/
│ └── api-spec.md
├── templates/
│ └── output-format.json
└── scripts/
└── validate.py
3.3 skill_manage 工具
tools/skill_manager_tool.py 提供六个操作:
| 操作 | 说明 |
|---|---|
create |
创建新技能(含 SKILL.md + 目录结构) |
edit |
全量替换 SKILL.md(重大改版) |
patch |
精准查找替换(小修小补,推荐) |
delete |
删除技能 |
write_file |
添加/覆写附件文件 |
remove_file |
删除附件文件 |
其中 patch 操作使用模糊匹配引擎(tools/fuzzy_match.py),能处理缩进差异、空白差异等,大幅降低了 Agent 自修改时的失败率:
python:tools/skill_manager_tool.py
def _patch_skill(name, old_string, new_string, file_path=None, replace_all=False):
from tools.fuzzy_match import fuzzy_find_and_replace
new_content, match_count, match_error = fuzzy_find_and_replace(
content, old_string, new_string, replace_all
)
if match_error:
preview = content[:500] + ("..." if len(content) > 500 else "")
return {"success": False, "error": match_error, "file_preview": preview}
写入同样使用原子替换(_atomic_write_text),且写入后立即触发安全扫描------Agent 创建的技能与社区 Hub 安装的技能接受同等审查:
python:tools/skill_manager_tool.py
def _create_skill(name, content, category=None):
# ... 验证 + 创建目录 ...
_atomic_write_text(skill_md, content)
# 安全扫描 --- 若被阻止则回滚
scan_error = _security_scan_skill(skill_dir)
if scan_error:
shutil.rmtree(skill_dir, ignore_errors=True)
return {"success": False, "error": scan_error}
3.4 技能索引与渐进披露
系统提示词中的技能索引采用三层渐进披露(Progressive Disclosure)架构:
- Tier 0 ---
skills_categories():列出类别与描述 - Tier 1 ---
skills_list():列出技能名称与简要描述 - Tier 2/3 ---
skill_view():加载完整技能内容与附件
系统提示词中只注入 Tier 1 的精简索引:
yaml
## Skills (mandatory)
<available_skills>
web:
- web-scraping-with-retry: 网页爬取与重试策略
- api-testing: API 接口测试流程
devops:
- docker-deploy: Docker 容器化部署
</available_skills>
这遵循了 Anthropic 的推荐实践------名称 ≤64 字符、描述 ≤1,024 字符------在最小化 Token 消耗的同时提供足够的发现信号。
3.5 两层缓存
技能索引的构建非常昂贵(需要扫描所有 SKILL.md 文件、解析 Frontmatter),因此 prompt_builder.py 实现了两层缓存:
- 进程内 LRU 缓存 ---
_SKILLS_PROMPT_CACHE(最多 8 条),按(skills_dir, tools, toolsets, platform)键 - 磁盘快照 ---
.skills_prompt_snapshot.json,包含文件修改时间/大小清单(manifest),冷启动时若清单匹配则直接加载
python:agent/prompt_builder.py
def build_skills_system_prompt(available_tools=None, available_toolsets=None):
# Layer 1: LRU cache
cached = _SKILLS_PROMPT_CACHE.get(cache_key)
if cached is not None:
return cached
# Layer 2: disk snapshot
snapshot = _load_skills_snapshot(skills_dir)
if snapshot is not None:
# 快速路径:使用预解析的磁盘元数据
...
else:
# 冷路径:全文件系统扫描 + 写入快照
...
技能修改后,缓存会立即失效:
python:tools/skill_manager_tool.py
if result.get("success"):
from agent.prompt_builder import clear_skills_system_prompt_cache
clear_skills_system_prompt_cache(clear_snapshot=True)
4. KEPA:对"提示"做反向传播
4.1 类比:传统深度学习 vs Hermes
| 传统深度学习 | Hermes Agent | |
|---|---|---|
| 前向传播 | 输入 → 模型 → 输出 | 用户意图 → LLM + 工具 → 执行结果 |
| 反向传播 | 更新模型权重 | 更新"如何使用模型"的策略 |
| 更新对象 | 参数矩阵 | 提示模板、记忆条目、技能文档 |
| 更新方式 | 梯度下降 | 自然语言审查 + 文件写入 |
这不是训练模型,而是训练"模型的上下文"。社区称之为 KEPA(Knowledge-Enhanced Prompt Adaptation)。
4.2 源码实现
核心实现在 run_agent.py 的 _spawn_background_review 方法中,出奇地简洁:
python:run_agent.py
_MEMORY_REVIEW_PROMPT = (
"Review the conversation above and consider saving to memory if appropriate.\n\n"
"Focus on:\n"
"1. Has the user revealed things about themselves --- their persona, desires, "
"preferences, or personal details worth remembering?\n"
"2. Has the user expressed expectations about how you should behave, their work "
"style, or ways they want you to operate?\n\n"
"If something stands out, save it using the memory tool. "
"If nothing is worth saving, just say 'Nothing to save.' and stop."
)
_SKILL_REVIEW_PROMPT = (
"Review the conversation above and consider saving or updating a skill if appropriate.\n\n"
"Focus on: was a non-trivial approach used to complete a task that required trial "
"and error, or changing course due to experiential findings along the way, or did "
"the user expect or desire a different method or outcome?\n\n"
"If a relevant skill already exists, update it with what you learned. "
"Otherwise, create a new skill if the approach is reusable.\n"
"If nothing is worth saving, just say 'Nothing to save.' and stop."
)
4.3 触发机制
两种审查各有独立的触发条件:
记忆审查(Memory Review):基于用户轮次计数
python:run_agent.py
self._memory_nudge_interval = 10 # 默认每 10 轮触发一次
# 每次用户消息触发检查
self._turns_since_memory += 1
if self._turns_since_memory >= self._memory_nudge_interval:
_should_review_memory = True
self._turns_since_memory = 0
技能审查(Skill Review):基于工具调用迭代计数
python:run_agent.py
self._skill_nudge_interval = 10 # 默认累计 10 次工具调用后触发
# 每次工具调用迭代后计数
self._iters_since_skill += 1
# 当前轮次结束后检查
if self._iters_since_skill >= self._skill_nudge_interval:
_should_review_skills = True
self._iters_since_skill = 0
关键设计:两种触发互不干扰,可以同时触发(使用 _COMBINED_REVIEW_PROMPT),也可以独立触发。
4.4 后台审查线程
审查在后台线程中执行,不影响主会话的响应速度:
python:run_agent.py
def _spawn_background_review(self, messages_snapshot, review_memory=False, review_skills=False):
def _run_review():
review_agent = AIAgent(
model=self.model,
max_iterations=8, # 最多 8 次工具调用
quiet_mode=True, # 静默模式
platform=self.platform,
provider=self.provider,
)
review_agent._memory_store = self._memory_store # 共享记忆存储
review_agent._memory_enabled = self._memory_enabled
review_agent._user_profile_enabled = self._user_profile_enabled
review_agent._memory_nudge_interval = 0 # 审查 Agent 不再触发审查
review_agent._skill_nudge_interval = 0 # 防止递归
review_agent.run_conversation(
user_message=prompt,
conversation_history=messages_snapshot, # 主会话快照
)
thread = threading.Thread(target=_run_review, daemon=True)
thread.start()
核心要点:
- Fork 一个完整的 AIAgent --- 共享模型、工具和上下文
- 共享 MemoryStore --- 审查 Agent 的写入直接反映到主 Agent 的持久化文件
- 递归保护 --- 审查 Agent 的 nudge interval 设为 0,防止无限递归
- 迭代上限 --- 最多 8 次工具调用,控制审查成本
- 后台执行 --- 审查结果在主响应交付后才处理,不竞争用户注意力
4.5 审查结果反馈
审查完成后,系统扫描审查 Agent 的工具调用,向用户展示简要摘要:
python:run_agent.py
actions = []
for msg in getattr(review_agent, "_session_messages", []):
if msg.get("role") != "tool":
continue
data = json.loads(msg.get("content", "{}"))
if not data.get("success"):
continue
message = data.get("message", "")
# 提取操作摘要
actions.append(message)
如果审查 Agent 判断"没有值得保存的内容"(Nothing to save.),则不会执行任何写入操作。
5. 跨会话语义召回
5.1 会话存储
hermes_state.py 实现了基于 SQLite 的会话存储,使用 WAL 模式支持并发读取:
python
# 关键特性
# - WAL 模式:支持并发读取 + 单写入(Gateway 多平台场景)
# - FTS5 全文搜索:快速搜索所有会话消息
# - 压缩触发会话分裂:通过 parent_session_id 链式关联
# - 来源标记:'cli', 'telegram', 'discord' 等
5.2 session_search 工具
tools/session_search_tool.py 实现了跨会话的语义召回,流程为:
- FTS5 搜索 --- 找到匹配的历史消息
- 按会话分组 --- 取 Top N 相关会话(默认 3 个)
- 加载对话 --- 截取匹配点前后约 100k 字符
- LLM 摘要 --- 用廉价模型(如 Gemini Flash)生成聚焦摘要
- 返回结果 --- 按会话组织,附带元数据
python:tools/session_search_tool.py
"""
Flow:
1. FTS5 search finds matching messages ranked by relevance
2. Groups by session, takes the top N unique sessions (default 3)
3. Loads each session's conversation, truncates to ~100k chars centered on matches
4. Sends to Gemini Flash with a focused summarization prompt
5. Returns per-session summaries with metadata
"""
这确保了 Agent 不会简单地告诉用户"我之前做过这个但忘了细节",而是能主动召回并参考历史解决方案。
6. 闭环学习流程
将三个子系统串联起来,Hermes 的自进化形成了一个完整的闭环:
scss
┌──────────────────────────────────────────┐
│ 用户输入 │
└──────────────────┬───────────────────────┘
│
▼
┌──────────────────────────────────────────┐
│ 系统提示词组装(prompt_builder.py) │
│ ┌────────────────────────────────────┐ │
│ │ 1. Agent Identity │ │
│ │ 2. Memory Snapshot(冻结快照) │ │
│ │ 3. Skills Index(Tier 1) │ │
│ │ 4. Context Files(AGENTS.md 等) │ │
│ │ 5. Platform Hint │ │
│ │ 6. Memory/Skills Guidance │ │
│ └────────────────────────────────────┘ │
└──────────────────┬───────────────────────┘
│
▼
┌──────────────────────────────────────────┐
│ AIAgent 对话循环(run_agent.py) │
│ LLM + Tool Calling → 执行任务 │
└──────────────────┬───────────────────────┘
│
┌───────────┼───────────┐
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 记忆触发 │ │ 技能触发 │ │ 会话归档 │
│ (10轮) │ │ (10迭代) │ │ (实时) │
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │
▼ ▼ ▼
┌──────────────────────────────────────────┐
│ 后台审查线程(_spawn_background_review) │
│ Fork AIAgent → 审查 Prompt → 工具调用 │
└──────────────────┬───────────────────────┘
│
┌────────────┼────────────┐
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────────┐
│ MEMORY.md│ │ SKILL.md │ │ state.db │
│ USER.md │ │ (新/更新) │ │ (FTS5索引) │
└──────────┘ └──────────┘ └──────────────┘
│ │ │
└────────────┼─────────────┘
│
▼
┌──────────────────────────────────────────┐
│ 下次会话:系统提示词加载最新快照 │
│ (记忆/技能/历史 → 注入上下文) │
└──────────────────────────────────────────┘
7. 设计权衡与局限性
7.1 优势
零成本进化 --- 审查 Agent 的 Token 消耗极小(最多 8 次工具调用),Skill 文件就是普通的 Markdown 文本。无需数据标注、模型微调或向量数据库。
可解释、可编辑 --- Skill 和 Memory 都是自然语言 Markdown,用户可以随时打开查看 Agent 学到了什么,手动修正不当的"学习成果"。
跨模型迁移 --- Skill 不依赖特定模型。用 GPT-4o 积累的 Skill,换成 Claude 或 DeepSeek 一样可用,因为它们都是自然语言。
渐进式积累 --- 每次使用都在积累经验,Skill 库会越来越丰富。三个月后的 Hermes 和刚装好的,体验完全不同。
7.2 局限性
"自动"不等于"准确" --- 审查 Agent 的判断力上限就是底层 LLM 的能力上限。它可能保存噪声、遗漏重要经验、或写出质量参差不齐的 Skill。
只有复杂任务才会触发 --- 审查 Prompt 明确要求"涉及试错或中途改变策略"才值得保存。简单任务大概率被判定为 Nothing to save.。
更新有延迟 --- 冻结快照机制导致后台更新的 Skill 要到下次会话才生效。当前会话感知不到变化。
依赖系统提示词的稳定性 --- 记忆/技能通过系统提示词注入,任何中途修改都会破坏前缀缓存,导致成本剧增。这是一个根本性的约束。
8. 与传统 Agent 框架的本质差异
传统 Agent 框架(如 OpenClaw)遵循"做完即走"模式------每次任务独立执行,除非用户手动配置,否则不会记住任何偏好。Hermes Agent 的设计哲学则完全不同:
不是"为你工作的 Agent",而是"和你一起成长的 Agent"。
这个微妙的措辞差异,体现为三个关键设计决策:
- 自驱 vs 他驱 --- Agent 自己决定学什么、怎么学,而非等待用户指令
- 后台 vs 前台 --- 学习过程在后台静默完成,不干扰用户的当前任务
- 积累 vs 即时 --- 价值在长期使用中体现,而非单次交互的即时能力