拆解 Hermes Agent:一个自进化 AI Agent 的经验沉淀闭环

AI Agent 2026年4月12日 20 min read

从源码层面完整拆解 Hermes Agent 的三层记忆架构、后台 Review Agent 机制、Skill 模糊修补、增量式上下文压缩等自进化实现细节,并诚实分析其设计天花板。

"自进化"到底在进化什么?

看到"自进化 Agent"这个词,大多数人的第一反应是:这个 Agent 能修改自己的代码?能通过元学习自我优化?还是有某种遗传算法在跑?

都不是。

Hermes Agent(by Nous Research,MIT 协议,v0.8.0)所谓的"自进化",用他们自己的话说,叫 "A closed learning loop"——一个闭环学习循环。这个循环做的事情极其朴素:把成功经验存下来,下次复用;发现问题就修补,不让错误的经验积累。

但朴素不等于简陋。Hermes 的设计回答了一个被大多数 Agent 框架回避的问题:LLM 本身是无状态的,每次对话从零开始,怎么让它"越用越好"?

这个问题的难点不在于"存数据"——SQLite 谁都会写。难在于:存什么、什么时候存、存了之后怎么用、怎么保证存的东西不变成负担。 这四个问题任何一个答错了,"自进化"就退化成"自污染"。

Hermes 用一套三层记忆架构 + 后台 Review Agent 机制回答了这四个问题。本文逐层拆解其实现。

一句话判断: Hermes 的自进化本质是"使用即沉淀"——不修改模型权重,不修改自身代码,而是通过精心设计的 prompt 指令 + 持久化存储 + 异步后台 review,让 LLM 在使用过程中自主积累经验、修正知识、优化行为。进化上限不在架构,在底层 LLM 的指令遵循能力。


三层记忆:Agent 的知识架构

在进入具体实现之前,先建立一个抽象模型。理解了模型,代码就是验证。

Hermes 把 Agent 的"知识"分为三层,每一层解决不同的进化问题:

┌───────────────────────────────────────────────────────┐
│                 三层记忆架构                            │
│                                                       │
│  Layer 1: 程序性记忆 (Skills)                          │
│  "怎么做事" → 可复用的 SOP                             │
│  进化方式: 从成功经验中提取,使用中修正                  │
│  持久化: ~/.hermes/skills/ (SKILL.md + 目录结构)       │
│                                                       │
│  Layer 2: 声明性记忆 (Memory)                          │
│  "知道什么" → 用户偏好、环境事实、工具特性              │
│  进化方式: 对话中积累,定期 nudge 提醒                  │
│  持久化: ~/.hermes/MEMORY.md + USER.md                 │
│                                                       │
│  Layer 3: 压缩性记忆 (Context Compression)             │
│  "当前对话的关键上下文" → 结构化摘要                    │
│  进化方式: 上下文溢出时增量更新,跨压缩周期保留关键信息  │
│  持久化: 内存中,随会话结束消失                         │
│                                                       │
│  ─────────────────────────────────────────────────    │
│  外部增强: Memory Provider Plugins                     │
│  Hindsight (知识图谱 + 反思), Honcho (用户建模), 等     │
│  作为 Layer 2 的可插拔后端                              │
└───────────────────────────────────────────────────────┘

这三层对应一个类比:Skills 是你的工作方法论(遇到这类问题,按这个流程走);Memory 是你的个人笔记本(这个客户喜欢简洁的汇报格式);Context Compression 是你的工作记忆(当前任务做到哪一步了,关键决策是什么)。

三层记忆各有独立的"存什么"策略和"什么时候触发"机制,但共享同一个设计原则:不让进化过程打扰用户。 这引出了 Hermes 最关键的设计决策——后台 Review Agent。


后台 Review Agent:让进化不打扰用户

在深入三层记忆的实现之前,必须先理解一个贯穿全局的机制,因为它是所有"自进化"行为的执行引擎。

问题: 什么时候让 Agent "反思"并保存经验?如果在用户对话中插入"让我想想刚才做了什么值得记住的",用户体验灾难;如果不做,经验就丢了。

Hermes 的解法: 启动一个后台线程,fork 一个独立的 AIAgent 实例,用专门的 review prompt 审视刚刚完成的对话,然后直接写入共享的 Memory/Skill 存储。用户完全无感。

触发条件在 run_agent.py:9625-9631 中,基于两个计数器:

# 技能触发 — 基于工具调用次数
_should_review_skills = False
if (self._skill_nudge_interval > 0
        and self._iters_since_skill >= self._skill_nudge_interval
        and "skill_manage" in self.valid_tool_names):
    _should_review_skills = True
    self._iters_since_skill = 0

# 记忆触发 — 基于对话轮数
if (self._memory_nudge_interval > 0
        and "memory" in self.valid_tool_names
        and self._memory_store):
    self._turns_since_memory += 1
    if self._turns_since_memory >= self._memory_nudge_interval:
        _should_review_memory = True
        self._turns_since_memory = 0

两个计数器,一个按工具调用次数计(默认 10 次),一个按对话轮数计(默认 10 轮)。到达阈值时,不是在当前对话中插入提醒,而是设置一个标志位,等当前对话完全结束后,再在后台启动 review。

这个设计的关键实现在 _spawn_background_reviewrun_agent.py:1807-1906):

def _spawn_background_review(self, messages_snapshot, review_memory=False, review_skills=False):
    """Spawn a background thread to review the conversation for memory/skill saves.

    Creates a full AIAgent fork with the same model, tools, and context as the
    main session. The review prompt is appended as the next user turn in the
    forked conversation. Writes directly to the shared memory/skill stores.
    Never modifies the main conversation history or produces user-visible output.
    """

注意几个设计细节:

  1. fork 完整 AIAgent — review agent 拥有相同的模型、工具和上下文,不是简单的规则匹配,而是一个能理解对话语义的 Agent。
  2. 共享存储但隔离对话 — review agent 写入同一个 Memory/Skill 存储,但不会修改主对话历史。
  3. 静默执行 — stdout/stderr 重定向到 /dev/null,用户的终端不会被 review 输出污染。
  4. review agent 自身的 nudge 被禁用_memory_nudge_interval = 0, _skill_nudge_interval = 0,防止 review agent 自己再触发 review,形成无限递归。

Review prompt 的内容分三种(run_agent.py:1772-1805):

纯记忆 review:

"Review the conversation above and consider saving to memory if appropriate. Focus on: 1. Has the user revealed things about themselves — their persona, desires, preferences, or personal details worth remembering? 2. Has the user expressed expectations about how you should behave, their work style, or ways they want you to operate?"

纯技能 review:

"Review the conversation above and consider saving or updating a skill if appropriate. 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?"

混合 review: 两者的合并版。

这就是 Hermes 自进化的"心脏"。 不是什么花哨的元学习算法,而是一个后台线程 + 一个 fork 出来的 Agent + 一个精心设计的 prompt。

用一个类比:你开完一个艰难的客户会议后,有个助理在隔壁房间帮你整理会议纪要——哪些客户的偏好值得记录,哪些解决方案值得沉淀成 SOP。你不用管他,他也不会打扰你,下次开会前他会把整理好的笔记递给你。


Skills:把成功变成 SOP 的程序性进化

Skills 是 Hermes 自进化机制中最精巧的部分,也是最有意思的工程决策。

问题空间

Agent 完成一个复杂任务——比如调试一个跨三个微服务的认证 bug——过程中试了三种方案,最后一种有效。下次遇到类似问题,它要从零开始试吗?

理想状态是:Agent 能把这个"成功路径"提取成可复用的方法论,下次直接调用。 这就是程序性记忆——你不需要重新发明轮子,因为你已经把轮子的图纸存下来了。

存储结构

每个 Skill 是一个目录(~/.hermes/skills/<category>/<skill-name>/),核心是 SKILL.md 文件,附带可选的 references、templates、scripts、assets 子目录:

~/.hermes/skills/
├── debugging/
│   └── cross-service-auth/
│       ├── SKILL.md          # 核心文档
│       ├── references/       # 参考资料
│       ├── templates/        # 模板文件
│       ├── scripts/          # 脚本
│       └── assets/           # 其他资源
└── deployment/
    └── k8s-canary/
        └── SKILL.md

SKILL.md 使用 YAML frontmatter + Markdown body 的格式。frontmatter 包含 name、description、trigger(什么时候该用这个 skill)等元数据,body 是具体的步骤指南。

创建触发

Skills 的创建不是自动的——它需要 Agent "意识到"某个经验值得保存。触发来自两个路径:

路径 1:Agent 自主判断 — 系统 prompt 中的 SKILLS_GUIDANCEagent/prompt_builder.py:164-171)直接告诉 Agent:

"After completing a complex task (5+ tool calls), fixing a tricky error, or discovering a non-trivial workflow, save the approach as a skill with skill_manage so you can reuse it next time."

路径 2:后台 Review Agent — 前文描述的 _spawn_background_review 机制,当工具调用次数达到 _skill_nudge_interval 阈值后触发。

两条路径殊途同归:都是让 LLM 判断"这个经验值不值得存",然后调用 skill_manage 工具。

即时修补与模糊匹配

这是 Skill 系统最精巧的设计。

Agent 使用一个 Skill 时发现它过时或有错,怎么办?Hermes 的答案是:立刻 patch,不要等。 系统 prompt 明确指示:

"When using a skill and finding it outdated, incomplete, or wrong, patch it immediately with skill_manage(action='patch') — don't wait to be asked. Skills that aren't maintained become liabilities."

最后一句话值得注意——"Skills that aren't maintained become liabilities." 这不是技术要求,而是一种"哲学":过时的经验比没有经验更危险。

但 patch 操作有一个工程难题:LLM 生成的 old_string 不一定能精确匹配文件中的原文。 缩进差异、空白符、转义字符——任何微小的不匹配都会导致 find-and-replace 失败。

Hermes 的解法是 模糊匹配tools/fuzzy_match.py 中的 fuzzy_find_and_replace)。skill_manager_tool.py:422-439 在 patch 时调用这个引擎:

# Use the same fuzzy matching engine as the file patch tool.
# This handles whitespace normalization, indentation differences,
# escape sequences, and block-anchor matching — saving the agent
# from exact-match failures on minor formatting mismatches.
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
)

而且,当匹配失败时,系统不是直接报错,而是返回文件的前 500 字符预览,让 LLM 能自我修正

if match_error:
    # Show a short preview of the file so the model can self-correct
    preview = content[:500] + ("..." if len(content) > 500 else "")
    return {
        "success": False,
        "error": match_error,
        "file_preview": preview,
    }

这是一个精心设计的容错闭环:模糊匹配扩大了成功面,失败时给 LLM 足够的信息来纠正。

还有一个安全机制:patch 操作后会运行 _security_scan_skillskill_manager_tool.py:56-60),如果安全扫描不通过,会原子回滚到 patch 前的内容。这防止了 Skill 被恶意或错误地注入危险内容。

类比: Skills 的进化方式像一位资深工程师的"笔记系统"——每次解决完一个棘手问题,就往笔记里加一条 SOP;下次遇到类似问题先翻笔记;发现笔记里某条过时了,用涂改液覆盖掉旧内容写上新的(模糊匹配就是涂改液的容错能力),而不是把整页撕掉重写。


Memory + Nudge:对抗遗忘的声明性进化

如果说 Skills 是 Agent 的"方法论",Memory 就是它的"笔记本"——记录用户偏好、环境事实、工具的坑。

双文件模型

Hermes 内置两个 Markdown 文件(tools/memory_tool.py):

文件 存什么 类比
MEMORY.md 环境事实、工具特性、代码规范、约定 你的工作笔记
USER.md 用户偏好、沟通风格、习惯 客户档案

两个文件都是字符数限制的——不能无限增长。这迫使 Agent 做"信息筛选":只存真正重要的事实。

系统 prompt(agent/prompt_builder.py:144-156)对"什么值得存"给出了明确指引:

"Prioritize what reduces future user steering — the most valuable memory is one that prevents the user from having to correct or remind you again. User preferences and recurring corrections matter more than procedural task details."

这句指引精准地定义了 Memory 的价值函数:一条记忆的价值 = 它能减少多少次未来的用户纠偏。 用户反复纠正你"不要加注释"的记忆,比"项目使用 React 18"的记忆更有价值——因为后者你查一次 package.json 就知道了,前者你不记住就会反复犯错。

同时,prompt 明确了不存什么

"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."

任务进度、已完成的工作日志——这些是会话级信息,用 session_search(FTS5 全文搜索)去历史记录里找就行,不应该占用宝贵的 Memory 空间。

Nudge 机制:对抗"忘了存"

光有指引还不够。LLM 在处理复杂任务时,注意力全在解决问题上,很难"记得要存记忆"。这就像你在专注 debug 的时候很难记得要写会议纪要。

Hermes 的解法是 nudge(轻推)机制(run_agent.py:7244-7254):

# Track memory nudge trigger (turn-based, checked here).
_should_review_memory = False
if (self._memory_nudge_interval > 0
        and "memory" in self.valid_tool_names
        and self._memory_store):
    self._turns_since_memory += 1
    if self._turns_since_memory >= self._memory_nudge_interval:
        _should_review_memory = True
        self._turns_since_memory = 0

每轮对话 _turns_since_memory 加 1,到达 _memory_nudge_interval(默认 10 轮)时触发后台 review。计数器在 memory 工具实际被使用时重置——这意味着如果 Agent 主动在对话中存了记忆,nudge 计时器就不会触发。

这产生了一个有趣的行为模式:经常主动存记忆的 Agent 几乎不会被 nudge;从不存记忆的 Agent 最多 10 轮就会被强制 review 一次。 这是一种软约束——不强制你每轮都存,但不允许你永远不存。

Memory Manager:统一的记忆入口

所有记忆操作都通过 MemoryManageragent/memory_manager.py)路由。它的设计有一个硬约束:最多只有一个外部 Memory Provider,且内置 Provider 永远存在。

class MemoryManager:
    """Orchestrates the built-in provider plus at most one external provider.

    The builtin provider is always first. Only one non-builtin (external)
    provider is allowed.  Failures in one provider never block the other.
    """

为什么限制只有一个外部 Provider?注释里说得很清楚:"prevent tool schema bloat and conflicting memory backends." 每个 Provider 都要往 LLM 的工具列表里注入自己的 tool schema——两个 Provider 就是双倍的 schema,三个就是三倍。LLM 的上下文窗口是有限的,tool schema 膨胀会挤占实际对话的空间。

这个设计牺牲了灵活性(不能同时用知识图谱 + 向量检索),换来了简洁性和可靠性。在工程上这是一个正确的 trade-off:记忆系统的价值不在于后端多丰富,而在于"存取"链路的可靠性。


Context Compression:在不丢失关键信息的前提下"遗忘"

前面两层记忆(Skills 和 Memory)解决的是跨会话的知识沉淀问题。Context Compression 解决的是单次会话内的问题:当对话太长,快撑爆上下文窗口了怎么办?

最粗暴的做法是截断——砍掉最老的对话轮次。但这是"暴力遗忘",会丢失重要信息。比如砍掉了用户在第三轮提到的"项目必须兼容 Python 3.9"这个约束,后续代码就可能白写。

Hermes 的做法是 LLM 驱动的结构化摘要,而且有一个关键设计:增量更新,而非从零重写。

压缩流程

ContextCompressoragent/context_compressor.py)的压缩分三步:

  1. 廉价预清理 — 先剪掉旧的工具调用结果(不需要 LLM,省 token)
  2. 保护头部和尾部 — system prompt 和最近的对话轮次不动
  3. LLM 总结中间轮次 — 对中间的对话轮次生成结构化摘要

增量更新

这是最精巧的部分。当这不是第一次压缩时,prompt 不是"请总结以下对话",而是(context_compressor.py:292-336):

You are updating a context compaction summary. A previous compaction produced
the summary below. New conversation turns have occurred since then and need
to be incorporated.

PREVIOUS SUMMARY:
{self._previous_summary}

NEW TURNS TO INCORPORATE:
{content_to_summarize}

Update the summary using this exact structure. PRESERVE all existing
information that is still relevant. ADD new progress. Move items from
"In Progress" to "Done" when completed.

摘要结构是固定的:

## Goal
## Constraints & Preferences
## Progress
### Done
### In Progress
### Blocked
## Key Decisions
## Relevant Files
## Next Steps
## Critical Context
## Tools & Patterns

注意两个设计决策:

第一,结构化模板。 不是自由文本摘要,而是固定的 8 个 section。这确保了每次压缩产出的信息密度一致,不会因为某次压缩"忘了"写某个方面而丢失关键信息。

第二,迭代更新。 每次压缩保留上一次的摘要作为上下文,LLM 只需要"合并新信息"而不是"重新理解整个对话"。这大幅降低了压缩的 token 消耗,也降低了信息丢失的风险——因为 LLM 只需要做增量操作("Progress 从 'In Progress' 移到 'Done'"),而不是从零重建。

类比: 这像你在做一个长项目的周报。第一周你写了完整的进展。第二周你不是重写整份报告,而是在上一版的基础上更新——完成的移到 "Done",新任务加到 "In Progress",新的决策补充到 "Key Decisions"。如果某周你忘了写某个方面(比如"Relevant Files"),上一周的记录还在那里保底。


外部 Memory Provider:可插拔的记忆后端

Hermes 的内置 Memory(Markdown 文件)解决了 80% 的问题,但有其局限:纯文本、无语义检索、无法推理。为此,它设计了 MemoryProvider 抽象接口(agent/memory_provider.py),支持外部记忆后端作为增强层。

MemoryProvider 生命周期

class MemoryProvider(ABC):
    # 核心生命周期
    is_available()              # 检查配置和凭证
    initialize(session_id)      # 建立连接,预热
    system_prompt_block()       # 注入系统 prompt 的文本
    prefetch(query)             # 每轮对话前,后台召回相关记忆
    sync_turn(user, assistant)  # 每轮对话后,持久化
    get_tool_schemas()          # 暴露给 LLM 的工具 schema
    handle_tool_call()          # 处理工具调用

    # 可选钩子
    on_turn_start(turn, message)         # 每轮开始
    on_session_end(messages)             # 会话结束
    on_pre_compress(messages) -> str     # 上下文压缩前提取洞察
    on_memory_write(action, target, content)  # 镜像内置记忆写入
    on_delegation(task, result)          # 观察子 Agent 的结果

这个生命周期设计的精巧之处在于:每个钩子都有明确的触发时机和职责,Provider 不需要关心 Agent 的主循环,只需要在正确的时机做正确的事。

Hindsight:知识图谱 + 反思

8 个外部 Provider 中,Hindsight 最能体现"自进化"的理念。它提供了三个工具:

工具 作用
hindsight_retain 存储信息,自动提取结构化事实、解析实体、建立索引
hindsight_recall 多策略检索:语义搜索 + 关键词匹配 + 实体图遍历 + reranking
hindsight_reflect 跨所有存储记忆进行推理,合成一个有理据的回答

hindsight_reflect 是最值得关注的。它不像 recall 那样做检索,而是让 LLM 遍历所有相关记忆,进行综合推理。这本质上是 Agent 的"反思"能力——不只是"我记得什么",而是"我所有的记忆加在一起意味着什么"。

这是一个关键区分。类比:recall 是"搜索收件箱找那封邮件",reflect 是"坐下来想想,从过去所有的沟通中,我对这个客户的理解是什么"。


这个设计的天花板在哪里?

分析了这么多精巧的设计,必须回答一个诚实的问题:这个"自进化"的天花板在哪里?它什么时候会失效?

天花板 1:LLM 指令遵循能力

整个自进化系统的驱动力不是代码,是 prompt。从 SKILLS_GUIDANCE 到 MEMORY_GUIDANCE 到 review prompt——所有"什么时候存""存什么""怎么 patch"的决策都由 LLM 做出。

这意味着:

  • 弱模型 + 好 prompt = 不可靠的进化。 如果底层模型经常"忘记"遵循 SKILLS_GUIDANCE 的指令,就不会主动创建 Skill;如果它不能准确判断"这个经验值不值得存",Memory 就会要么空空如也,要么充满垃圾信息。
  • 模型的 attention 是有限的。 当 system prompt 很长(Hermes 的 system prompt 包含了记忆内容、skills 索引、工具 schema 等,动辄数千 token),模型对其中某条指引的遵循度就会下降。这叫做 "prompt 挤出效应"——prompt 越丰富,每条被认真对待的概率越低。

这是一个根本性的矛盾:你想让 prompt 包含更多指引以提高 Agent 的"智能"行为,但 prompt 越长,每条指引的有效性越低。

天花板 2:模糊匹配的上限

Skill patch 的模糊匹配解决了"LLM 输出与原文有微小差异"的问题。但它解决不了"LLM 理解错了要 patch 什么"的问题。

具体场景:Agent 使用一个 Skill,发现第 3 步有错。它生成一个 patch,old_string 匹配到了第 3 步的内容——但因为模糊匹配的容错性,它也可能匹配到第 5 步中相似但不完全相同的文本。这时候 patch 就改错了地方。

模糊匹配的容错性是一把双刃剑:它降低了 "找不到匹配" 的失败率,但提高了 "匹配到错误位置" 的风险。 后者比前者更危险——因为明确的失败可以被修复,静默的错误匹配会悄悄污染 Skill 的内容。

天花板 3:后台 Review 的"理解"深度

Review Agent 拿到的 prompt 是"考虑一下这个对话有没有什么值得存的经验"。但它对对话的"理解"深度受限于:

  1. max_iterations = 8 — review agent 最多做 8 次工具调用,防止它消耗太多资源。但 8 次调用可能不够完成一个复杂的 skill 创建(需要创建目录、写 SKILL.md、验证 frontmatter 等)。
  2. 静默执行 — review agent 的输出被重定向到 /dev/null。如果它做了错误的记忆存储(比如把用户的玩笑当真存进了 USER.md),用户完全不知道,也没有机会纠正。

天花板 4:Memory 的"污染"问题

MEMORY.md 和 USER.md 的字符数限制是好的——防止无限增长。但它引入了另一个问题:当空间不够时,删什么?

Agent 被迫做"信息淘汰"决策——删掉旧的、保留新的。但"旧"不等于"没用"。用户三个月前说的一句"我色盲,不要用红色标注错误"可能比昨天说的"项目用 TypeScript"更重要。LLM 做这个淘汰决策时,很难有足够的上下文来做出正确的判断。

这就是所谓**"自进化"变"自污染"**的风险路径:如果 Agent 存了太多垃圾信息(占用有限的 Memory 空间),又删掉了真正重要的事实(因为"看起来旧"),Memory 系统就从资产变成了负债。


工程启示

Hermes 的设计给了几个可迁移的工程教训:

1. "自进化"不需要元学习。 不修改模型权重、不修改自身代码,通过 prompt 指令 + 持久化存储 + 定期触发,就能实现相当程度的"使用即改进"。这个模式可以复制到任何 LLM-based Agent 框架中。

2. 后台 Review 是用户体验的关键。 如果"自进化"行为(存记忆、创建 skill)发生在用户对话中,会严重打断体验。fork 一个后台 Agent 来做这件事,是正确的工程选择——代价是多消耗一些 API token 和计算资源。

3. 模糊匹配是 LLM 自修改的必备能力。 LLM 生成的文本与原文有微小差异是常态而非异常。如果你设计任何"LLM 写 → 系统读 → 修改现有内容"的流程,必须用模糊匹配而非精确匹配。

4. 信息淘汰和信息积累同等重要。 无限增长的记忆是无用的。Hermes 通过字符数限制 + "什么值得存" 的指引来做淘汰,但这是最薄弱的环节。更好的做法可能是给每条记忆加时间戳和访问计数,用 LRU-like 策略淘汰——但这又增加了系统复杂度。

5. "自进化"的上限 = 底层 LLM 的指令遵循能力。 这是一个无法通过架构优化来突破的天花板。再精巧的 prompt,如果模型不能可靠地遵循,系统就无法正常运作。这意味着选择底层模型时,"指令遵循能力"应该是最重要的评估维度——比推理能力、比代码生成能力都重要。


全文基于 Hermes Agent v0.8.0 源码分析,关键文件路径和行号均指向实际代码位置。