我扒了 Hermes Agent 的源码,终于搞懂了它怎么"自己教自己"

市面上号称”自学习”的 AI Agent 我见过不少,大部分就是套了个 RAG 外壳,存点向量就敢叫”记忆”。但当我真的去翻了 Hermes 的源码之后,才发现这玩意儿的自进化机制设计得确实有点东西——它不是花架子,是三层嵌套的渐进式学习系统。
今天就把我看源码的心得整理出来,主要是三件事:即时学习、后台审视、会话持久化。搞明白这三层,你就知道 Hermes 为什么越用越好用了。
先看全貌:三层自进化架构
Hermes 的自进化不是一个单独的功能,而是三层机制叠加在一起:
┌─────────────────────────────────────────────────┐│ 用户可见的主对话流程 ││ (run_conversation → agent loop → tool dispatch) │└──────────────┬──────────────────────┬────────────┘ │ │ ┌──────────▼──────────┐ ┌────────▼─────────┐ │ 层1: 即时学习 │ │ 层2: 后台审视 │ │ SKILLS_GUIDANCE │ │ _spawn_background │ │ (system prompt引导) │ │ _review │ │ LLM自主判断何时保存 │ │ (计数器触发) │ └─────────────────────┘ └────────┬──────────┘ │ ┌──────────▼──────────┐ │ 层3: 会话级持久化 │ │ commit_memory_session│ │ on_session_end │ │ (会话轮转/退出时) │ └─────────────────────┘
-
层 1 – 即时学习:通过 system prompt 里的 SKILLS_GUIDANCE,让 LLM 在对话中自己判断要不要保存经验 -
层 2 – 后台审视:用计数器每隔 N 轮自动 fork 一个后台 LLM,审查对话里有没有值得存的东西 -
层 3 – 会话持久化:会话 ID 轮转的时候(比如 /new、上下文压缩),把 memory provider 的数据刷写到磁盘
下面一个个拆开讲。
层 1:即时学习—— LLM 自己决定什么时候存
这一层的核心思路很简洁:与其让用户手动保存,不如在 system prompt 里直接告诉 LLM “遇到这些情况你就自己存”。
提示词原文长这样:
After completing a complex task (5+ tool calls), fixing a tricky error,or discovering a non-trivial workflow, save the approach as askill with skill_manage so you can reuse it next time.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.
划重点——三种触发场景:
-
完成复杂任务(5+ 次工具调用) -
修复了一个棘手的错误 -
发现了非平凡的工作流
这里面有个设计我觉得特别巧妙:“Skills that aren’t maintained become liabilities.” 这句话不是废话,它在引导 LLM 主动维护已有的 skill,而不是只管创建不管更新。
Skill 的加载引导也嵌入在 system prompt 里:
Skills also encode the user's preferred approach, conventions, and quality standardsfor tasks like code review, planning, and testing — load them even for tasks youalready know how to do, because the skill defines how it should be done here.If a skill has issues, fix it with skill_manage(action='patch').After difficult/iterative tasks, offer to save as a skill.If a skill you loaded was missing steps, had wrong commands, or neededpitfalls you discovered, update it before finishing.
说白了就是:哪怕你(LLM)觉得自己已经会做这件事了,也要把 skill 加载出来看看——因为 skill 里编码的是用户在这个项目里的偏好和规范,不是通用知识。
层 2:后台审视——源码里最精彩的部分
如果说层 1 是”教 LLM 主动存”,那层 2 就是”即使 LLM 忘了存,也有人帮它补上”。
这是整个自进化机制里工程实现最复杂的一层,也是我觉得最值得学的地方。
两个独立计数器
Hermes 在 run_agent.py 的 __init__ 里初始化了两个计数器:
# Memory 计数器:按对话轮数计数self._memory_nudge_interval = 10# 默认每 10 轮对话触发一次self._turns_since_memory = 0# Skill 计数器:按工具迭代次数计数self._skill_nudge_interval = 10# 默认每 10 次工具迭代触发一次self._iters_since_skill = 0
配置来源是 config.yaml:
memory:nudge_interval:10# 每 N 轮对话触发 memory reviewflush_min_turns:6# 最少累积轮数memory_char_limit:2200# memory 文件字符上限skills:creation_nudge_interval:10# 每 N 次工具迭代触发 skill review
注意这两个计数器是独立的:Memory 按对话轮数计数,Skill 按工具迭代次数计数。为什么要分开?因为一次对话可能触发多次工具调用,两个维度的节奏不一样。
计数逻辑
Memory 计数——每次 run_conversation 开始时 +1:
# run_agent.py:8940-8947_should_review_memory = Falseif (self._memory_nudge_interval > 0and"memory"inself.valid_tool_namesandself._memory_store):self._turns_since_memory += 1ifself._turns_since_memory >= self._memory_nudge_interval: _should_review_memory = Trueself._turns_since_memory = 0
Skill 计数——在 agent loop 每次迭代(LLM 返回 tool call)时 +1:
# run_agent.py:9221-9223if (self._skill_nudge_interval > 0and"skill_manage"inself.valid_tool_names):self._iters_since_skill += 1
在 run_conversation 结束时检查是否达到阈值:
# run_agent.py:11933-11939_should_review_skills = Falseif (self._skill_nudge_interval > 0andself._iters_since_skill >= self._skill_nudge_intervaland"skill_manage"inself.valid_tool_names): _should_review_skills = Trueself._iters_since_skill = 0
归零机制——这个设计很聪明
当用户在对话中主动使用了 memory 或 skill_manage 工具,对应的计数器直接归零:
# run_agent.py:7977-7980if function_name == "memory":self._turns_since_memory = 0elif function_name == "skill_manage":self._iters_since_skill = 0
为什么要归零?因为用户既然已经手动管理了,就没必要紧接着再触发一次自动 review。避免重复操作,也减少不必要的 token 消耗。
后台 Review 的执行
当计数器达到阈值后,Hermes 会 fork 一个全新的 AIAgent 来做审查:
# run_agent.py:2542-2565def_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 再触发 nudge review_agent._skill_nudge_interval = 0# 防止无限递归
几个关键设计决策:
-
quiet_mode=True:用户完全无感,不打断主对话 -
共享同一个 memory_store 和 skill_store:子 agent 写入的东西,主 agent 下一轮就能用 -
子 agent 的 nudge_interval 设为 0:防止子 agent 再 fork 子子 agent,无限套娃 -
max_iterations=8:防止 review 跑飞,8 轮足够了
触发入口在这里:
# run_agent.py:11951-11961if final_response andnot interrupted and (_should_review_memory or _should_review_skills):try:self._spawn_background_review( messages_snapshot=list(messages), review_memory=_should_review_memory, review_skills=_should_review_skills, )except Exception:pass# Background review is best-effort
注意那个 except Exception: pass——后台 review 是 best-effort 的,失败了不影响主流程。这个设计很务实。
层 3:会话级持久化——兜底机制
这一层比较简单,就是在会话 ID 轮转的时候,把 memory provider 里积攒的数据刷写一遍:
# run_agent.py:3536-3546defcommit_memory_session(self, messages: list = None) -> None:ifnotself._memory_manager:returntry:self._memory_manager.on_session_end(messages or [])except Exception:pass
触发时机包括 /new 命令、上下文压缩、CLI 退出等。说白了就是个兜底——前面两层万一有遗漏,这层保证数据不丢。
背后的提示词设计
光看工程实现还不够,Hermes 给后台 agent 的审查提示词也值得研究。这是决定”存什么、不存什么”的关键。
Memory 审查提示词
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 workstyle, or ways they want you to operate?If something stands out, save it using the memory tool.If nothing is worth saving, just say 'Nothing to save.' and stop.
重点:只关注用户个人信息和行为偏好,不关注任务细节。
Skill 审查提示词
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 trialand error, or changing course due to experiential findings along the way, or didthe user expect or desire a different method or outcome?If a relevant skill already exists, update it with what you learned.Otherwise, create a new skill if the approach is reusable.If nothing is worth saving, just say 'Nothing to save.' and stop.
重点:只关注经过试错的工作流,一次性完成的简单任务不存。
合并审查(Memory + Skill 同时触发)
Review the conversation above and consider two things:**Memory**: Has the user revealed things about themselves ...**Skills**: Was a non-trivial approach used ...Only act if there's something genuinely worth saving.If nothing stands out, just say 'Nothing to save.' and stop.
最关键的一句话是最后那句——“Only act if there’s something genuinely worth saving.” 这个约束防止了 LLM 为了”表现积极”而存一堆没用的东西。
MEMORY_GUIDANCE(嵌入 system prompt)
这段比较长,但里面有个设计理念我觉得值得单独拎出来说:
Write memories as declarative facts, not instructions to yourself.'User prefers concise responses' ✓ — 'Always respond concisely' ✗.'Project uses pytest with xdist' ✓ — 'Run tests with pytest -n 4' ✗.
声明式 vs 指令式。Memory 存的是”事实”,不是”指令”。指令式的写法会在后续会话中被 LLM 重新当作命令执行,导致重复劳动或覆盖用户的当前需求。
另外还有个区分:
-
Memory = 声明性知识(用户偏好、项目事实) -
Skill = 程序性知识(工作流步骤、操作方法)
这个分类思路很清晰,避免了两种知识混在一起互相污染。
Skill 怎么存的
Skills 存在 ~/.hermes/skills/ 下面,每个 skill 一个目录:
~/.hermes/skills/├── skill-name/│ ├── SKILL.md # 主指令文件(YAML frontmatter + Markdown)│ ├── references/ # 参考文档│ ├── templates/ # 输出模板│ ├── scripts/ # 辅助脚本│ └── assets/ # 其他资源文件
SKILL.md 的格式:
---name:skill-namedescription:Briefdescriptionformatchingversion:1.0.0platforms: [macos]metadata:hermes:tags: [tag1, tag2]---# Skill TitleFullinstructionsandproceduresinMarkdown...
注意那个 platforms 字段——不同平台的 skill 不会互相干扰。tags 则用于匹配,Hermes 加载 skill 时会根据 tags 和当前任务做匹配。
完整流程一张图看懂
用户发送消息 │ ▼run_conversation() │ ├── _turns_since_memory += 1 │ ├── [Agent Loop] ── 每次迭代 _iters_since_skill += 1 │ │ │ ├── LLM 返回 → 用户看到回复 │ └── LLM 调用 skill_manage → _iters_since_skill = 0 (归零) │ ├── 检查 _should_review_memory ? │ └── _turns_since_memory >= 10 ? → True │ ├── 检查 _should_review_skills ? │ └── _iters_since_skill >= 10 ? → True │ └── 触发后台 Review (if any) │ ▼ _spawn_background_review() │ ├── fork AIAgent(quiet=True, max_iter=8) ├── 传入完整对话历史 + Review Prompt │ ├── LLM 判断: │ ├── "Nothing to save." → 结束 │ ├── 调用 memory 工具 → 写入 MEMORY.md / USER.md │ └── 调用 skill_manage → 写入 ~/.hermes/skills/ │ └── 打印摘要:💾 Skill created · Memory updated
聊聊我的看法
看完这套机制,我觉得有几个设计决策是真正经过思考的:
|
|
|
|
|---|---|---|
|
|
全自动
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
其中最让我印象深刻的是 Memory 用声明式、Skill 用程序式 的区分。很多 Agent 框架把记忆和工作流混在一起,结果是记忆里塞满了步骤说明,工作流里又掺杂了用户偏好,两边都不好用。Hermes 这个分类方式干净利落。
不过说实话,让 LLM 自己判断”值不值得存”这件事,我觉得还是有点冒险。毕竟 LLM 的判断不是百分百靠谱——可能会存一些鸡毛蒜皮的东西,也可能漏掉真正重要的偏好。但目前来看,这可能是工程上最务实的方案了,总不能每次都弹窗问用户”要不要存这个?”吧。
写在最后
Hermes 的自进化机制,本质上是在回答一个问题:怎么让 AI Agent 在不被用户打扰的情况下,越用越懂你?
三层机制各有分工:即时学习管”当下”,后台审视管”回顾”,会话持久化管”兜底”。加在一起,构成了一个还算完整的自我进化闭环。
如果你也在做 Agent 相关的开发,这套机制有不少可以借鉴的地方。特别是计数器驱动的后台审查和 Memory/Skill 的分类存储,这两个设计我觉得可以直接拿来用。
有问题欢迎评论区交流,下一篇打算聊聊 Hermes 的 tool dispatch 机制,感兴趣的可以关注一下。
夜雨聆风