Skip to content

学习循环与记忆

源码版本v2026.7.20

职责

这是 Hermes 区别于普通 agent 的核心:一个「越用越好用」的闭环。它把每轮对话沉淀成:① 技能演化——从经验创建/改写技能(agent/learning_mutations.py);② 记忆——FTS5 全文检索 + LLM 摘要的过往会话检索(memory_manager),配合 Honcho 辩证式用户建模;③ 学习图——把技能与记忆组织成节点/边(agent/learning_graph.py);④ 后台审视——background_review 在线程里消化历史、产出可执行的改写动作。技能改写最终回灌到技能系统,供后续轮次复用。

关键文件

技能节点与学习图

技能/记忆改写

记忆管理

后台审视

技能节点长什么样

SkillNode 是学习图里技能的统一表达(agent/learning_graph.py:28-41):dataclass 字段有 name/category/source(base 内置 vs profile 用户/agent 创建)/use_count(从 .usage.json 读)/created_by('agent' 表示后台审视创建的)/pinned/relatedbuild_skill_nodes 扫描 skills/ 下的 SKILL.md 文件并叠加使用统计。source != "base" 加上 created_by == "agent"use_count > 0 是后面 build_learning_graph 判断「这是学到的,不是内置的」的过滤条件——只把真正学到的节点放到桌面上给用户看。

学习图的产出

build_learning_graph(agent/learning_graph.py:248-328)把节点、边、记忆卡、聚类统计拼成一个完整 payload,供桌面端学习面板渲染。它先调 build_skill_nodes(_skill_roots()) 扫两个根——base(repo/skills/,内置)和 profile(~/.hermes/skills/,用户/agent 创建);然后过滤出 source != "base"created_by == "agent"use_count > 0 的技能当「学到的」,再叠加 build_edges(技能间关联)、_memory_cards(记忆卡)、_memory_skill_edges(记忆与技能的关联)。base 永远在,profile 才是「学到的」。

改写后必须清缓存

技能被 edit_node 改写后,如果不清缓存,下一轮 prompt_builder 还会用旧的 SKILL.md 快照,改写就白做了。_clear_skill_cache(agent/learning_mutations.py:200)调 clear_skills_system_prompt_cache(clear_snapshot=True),clear_snapshot=True 连工具快照一起清,因为 MCP 工具列表也可能在审视中被刷新。这个函数是 edit_node / delete_node 的最后一环——改写→落盘→清缓存→下轮生效,四步缺一不可。

记忆注入:从 MemoryManager 到提示词

MemoryManager(agent/memory_manager.py:354)协调一个内置 provider(FTS5 + LLM 摘要)加至多一个外部 provider(Honcho/Hindsight/Mem0 之类)。外部 provider 只允许一个,多了会被拒——避免工具 schema 膨胀和记忆后端打架;两个 provider 失败互不阻塞。检索到的原始记忆会被 build_memory_context_block(agent/memory_manager.py:337-354)包成 <memory-context> 围栏 + 系统注的上下文块,系统注明确告诉模型「这是记忆不是新指令」;sanitize_context 在索引前先剥掉围栏和旧的系统注——防止 provider 把已经包过的内容再包一层。StreamingContextScrubber 在流式输出端做同样的事,避免模型把记忆块原文吐回用户。

后台审视:摘要与候选动作

spawn_background_review_thread(agent/background_review.py:956)不直接起线程,而是构造 (target, prompt) 元组交给 AIAgent._spawn_background_review_thread,这样测试层 patch threading.Thread 还能工作。函数签名(review_memory / review_skills 两个 bool)决定挑哪个 prompt——_MEMORY_REVIEW_PROMPT / _SKILL_REVIEW_PROMPT / _COMBINED_REVIEW_PROMPT:

python
def spawn_background_review_thread(agent, messages_snapshot,
                                    review_memory=False, review_skills=False):
    """Build the review thread target and prompt. Returns a ``(target, prompt)`` tuple."""
    if review_memory and review_skills:
        prompt = getattr(agent, "_COMBINED_REVIEW_PROMPT", _COMBINED_REVIEW_PROMPT)
    elif review_memory:
        prompt = getattr(agent, "_MEMORY_REVIEW_PROMPT", _MEMORY_REVIEW_PROMPT)
    else:
        prompt = getattr(agent, "_SKILL_REVIEW_PROMPT", _SKILL_REVIEW_PROMPT)
    def _target() -> None:
        _run_review_in_thread(agent, messages_snapshot, prompt)
    return _target, prompt

线程内 _run_review_in_thread(agent/background_review.py:617)fork 一个继承父 agent runtime 的子 agent(避免重新解析凭证——OAuth-only provider 在子 agent 里根本重建不出来),并装 _bg_review_auto_deny 审批回调,让审视期间触碰危险命令一律 deny 而不是 fallback 到 input()(会和父 agent 的 prompt_toolkit TUI 死锁)。_digest_history(agent/background_review.py:122)把历史压成「近 24 条 verbatim + 更早的合成摘要」,只在审视被路由到不同模型时用(冷缓存少写 token 划算);主模型路径跑全量 replay(缓存还热)。

数据流

  1. 每轮对话结束,主循环把消息快照交给后台审视线程(agent/background_review.py:956)。
  2. _digest_history(agent/background_review.py:122)摘要近期历史,产出候选动作(新建/改写技能、写记忆)。
  3. 技能改写经 edit_node:157 / 删除经 delete_node:124 落盘,并 _clear_skill_cache:200 清缓存。
  4. 记忆写入经 MemoryManager:354,脱敏(agent/memory_manager.py:164)后建 FTS5 索引,LLM 摘要压缩。
  5. 下轮开始时,build_memory_context_block(agent/memory_manager.py:337)把相关记忆拼进 build_turn_context:268;技能经 agent/skill_bundles.py 注入。
  6. 学习图由 build_learning_graph:254 汇总,供 UI/调试展示。

设计动机

为什么把学习闭环拆成四个独立模块?因为它们的代价结构不同。background_review 跑在子 agent 上,要 fork runtime、要花 LLM 调用,得异步、得能失败回退——它和主循环必须解耦。learning_graph 是只读汇总,便宜,可以随时拉来给 UI 用,不能和改写耦合。learning_mutations 是写路径,改完必须清缓存,否则下轮吃到旧快照。memory_manager 是检索+脱敏+注入,放在一起是因为「从外部 provider 拿记忆 → 脱敏 → 包围栏 → 塞提示」必须在一个边界内完成,否则脱敏漏一处就把记忆原文漏到模型输出里。

边界与失败

  • 后台审视不是同步的_run_review_in_thread 跑在 daemon 线程里,主循环不等它完成就返回用户。审视刚跑完那一轮,这轮的技能改写一般要等到下一轮才生效。
  • 外部记忆 provider 只能挂一个。Honcho 和 Hindsight 不能同时跑,add_provider 会拒绝第二个外部 provider。
  • _digest_history 只在审视被路由到不同模型时生效。主模型路径跑全量 replay(缓存还热);改这个行为要先看 auxiliary.background_review 配置。
  • 记忆注入的脱敏边界在「围栏和系统注」sanitize_context 只剥 <memory-context> 围栏和系统注,不改写记忆正文——记忆原文含敏感信息得在写入前就处理好。

小结

学习闭环把「对话」变成「资产」:后台审视无感地把经验沉淀为技能与记忆,下轮自动注入。关键四件套——learning_graph(图谱)、learning_mutations(改写)、memory_manager(检索+脱敏+摘要)、background_review(异步消化)。这是 Hermes 「与用户共同成长」的机制底座,也是它和「无状态 LLM 调用」的根本区别。

非官方社区学习站,内容以 MIT 许可的 NousResearch/hermes-agent 源码为依据。