Hermes Agent 自进化实现:从源码到架构的深度拆解

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)意味着:

  1. 会话开始时,系统提示词加载当前 MEMORY.mdUSER.md 的内容作为快照
  2. 会话进行中,即使后台更新了这些文件,当前会话看到的仍然是开始时的版本
  3. 下次会话,系统提示词才会反映最新的文件内容

为什么这样设计?因为系统提示词在会话中途被修改会导致:

  • 前缀缓存(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 是记忆系统的编排层,负责:

  1. 内置提供者(BuiltinMemoryProvider)--- 始终注册,提供 MEMORY.md/USER.md 的读写
  2. 外部提供者(External Provider)--- 最多一个,如 Honcho 的辩证式用户建模
  3. 统一接口 --- 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)架构:

  1. Tier 0 --- skills_categories():列出类别与描述
  2. Tier 1 --- skills_list():列出技能名称与简要描述
  3. 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 实现了两层缓存:

  1. 进程内 LRU 缓存 --- _SKILLS_PROMPT_CACHE(最多 8 条),按 (skills_dir, tools, toolsets, platform)
  2. 磁盘快照 --- .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()

核心要点:

  1. Fork 一个完整的 AIAgent --- 共享模型、工具和上下文
  2. 共享 MemoryStore --- 审查 Agent 的写入直接反映到主 Agent 的持久化文件
  3. 递归保护 --- 审查 Agent 的 nudge interval 设为 0,防止无限递归
  4. 迭代上限 --- 最多 8 次工具调用,控制审查成本
  5. 后台执行 --- 审查结果在主响应交付后才处理,不竞争用户注意力

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' 等

tools/session_search_tool.py 实现了跨会话的语义召回,流程为:

  1. FTS5 搜索 --- 找到匹配的历史消息
  2. 按会话分组 --- 取 Top N 相关会话(默认 3 个)
  3. 加载对话 --- 截取匹配点前后约 100k 字符
  4. LLM 摘要 --- 用廉价模型(如 Gemini Flash)生成聚焦摘要
  5. 返回结果 --- 按会话组织,附带元数据
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"。

这个微妙的措辞差异,体现为三个关键设计决策:

  1. 自驱 vs 他驱 --- Agent 自己决定学什么、怎么学,而非等待用户指令
  2. 后台 vs 前台 --- 学习过程在后台静默完成,不干扰用户的当前任务
  3. 积累 vs 即时 --- 价值在长期使用中体现,而非单次交互的即时能力
相关推荐
渐儿1 小时前
NestJS 生产级开发教程
前端
ZFSS1 小时前
PixVerse 视频生成 API 实战教程
人工智能·ai·ai作画·音视频·ai编程
前端毕业班1 小时前
uni-app onShareAppMessage hook 原理分析
前端·javascript
gogoing1 小时前
React 分包加载优化
前端·react.js
RxGc1 小时前
MCP生态爆发:Anthropic的协议野心与开发者的真实机会
人工智能·mcp
gogoing1 小时前
Babel 配置与工具
前端·javascript
亲亲小宝宝鸭1 小时前
重新install,项目就跑不起来了?!
前端·npm
agicall.com1 小时前
信电助 - 智能话务盒 UB-A-XC 型号功能列表
人工智能·语音识别·信创电话助手·固话录音转文字
Dovis(誓平步青云)1 小时前
《如何通过prometheus-webhook-dingtalk解决 Alertmanager 原生不支持钉钉 Webhook问题》
人工智能·生成对抗网络·钉钉·运维开发·prometheus