本节摘要:外循环的收束节,打通"经验→创建→使用→反馈→改进→沉淀"全闭环。改进的主引擎是
agent/background_review.py(1663 行)的后台评审 fork:每轮对话结束后,主 agent fork 一个继承运行时(同 provider/同模型/同凭证/同缓存前缀)的影子 AIAgent,回放会话快照并自问"should any skill/memory be saved or updated?"——影子 agent 被工具白名单锁死在 memory/skill 工具上,其他工具运行期直接拒绝。改进触发条件在评审提示词里写得很直白:用户纠正了风格/工作流、出现了值得沉淀的技巧、本次加载的技能被发现是错的/缺步骤/过时——立刻 patch。反向约束同样严格:环境依赖型失败、"X 工具坏了"类否定断言、未解决的失败序列都禁止写入技能(否则会硬化成长期自我设限)。最后,agent/learning_graph.py把学到的技能节点与 MEMORY.md/USER.md 记忆卡片连成图谱("learning made visible"),供桌面端可视化与用户编辑。这是 Hermes 区别于所有普通 agent 的核心:同一个 agent 既是技能的使用者,也是作者与维护者。
内容来源:原项目源码
agent/background_review.py(评审 fork/白名单/三段评审提示词)、agent/learning_graph.py(图构建)、agent/learning_mutations.py(节点编辑)、tools/skill_usage.py(bump_patch/patch_generation)。
⚠️ 注意:后台评审是非关键路径的自我改进——它跑在守护线程里,必须在下一轮用户消息到来时让路(2 秒取消窗口),失败绝不影响主会话;它的 token 用量记回父会话。评审 fork 只能改 curator 管辖的技能:捆绑/hub/外部目录/pinned/用户手写技能一律拒绝写入——"being in play does not make one yours to edit"。
阅读完本节,你应当能够:
background_review.py:1-17 的模块 docstring 给出全景:"After every turn, AIAgent.run_conversation may call spawn_background_review to fire off a daemon thread that replays the conversation snapshot in a forked AIAgent and asks itself 'should any skill/memory be saved or updated?'. Writes go straight to the memory + skill stores. Main conversation and prompt cache are never touched."。fork 的隔离与继承(background_review.py:1425-1465,节选):
1425 review_toolsets = ["skills"] 1426 if review_agent._memory_enabled or review_agent._user_profile_enabled: 1427 review_toolsets.insert(0, "memory") 1428 review_whitelist = { 1429 t["function"]["name"] 1430 for t in get_tool_definitions( 1431 enabled_toolsets=review_toolsets, 1432 quiet_mode=True, 1433 ) 1444 set_thread_tool_whitelist( 1445 review_whitelist, 1446 deny_msg_fmt=( 1447 "Background review denied non-whitelisted tool: " 1448 "{tool_name}. Only memory/skill tools are allowed." 1449 ), 1450 ) ... 1465 review_agent.run_conversation( 1466 user_message=( 1467 prompt 1468 + "\n\nYou can only call memory and skill " 1469 "management tools. Other tools will be denied " 1470 "at runtime — do not attempt them." 1471 ), 1472 conversation_history=_review_history, 1473 )
白名单从 toolset 展开(skills + 按需 memory),运行期线程级拒绝;提示词尾部再软性声明一次。注意 1425-1427 行的注释:白名单不能硬编码 ["memory","skills"]——那会在 profile 设置 memory_enabled: false 时仍把 MEMORY.md 读写工具发给评审 LLM,污染禁用记忆的 profile(#54937)。fork "inherits the parent's live runtime (provider, model, base_url, credentials, cached system prompt) so it hits the same prefix cache and uses the same auth"——评审复用主模型的缓存前缀,冷读成本最低;若被路由到别的模型则改喂摘要(_digest_history 只取尾部 24 条)。评审预算:最多 16 轮迭代(_REVIEW_MAX_ITERATIONS = 16)、默认 60 万输入 token 上限(_REVIEW_MAX_INPUT_TOKENS_DEFAULT = 600_000,可配)。取消机制(_BackgroundReviewRun)保证新用户消息到来时 2 秒内让路:"The review is non-critical self-improvement work and it must never" 挡住前台;finally 块里把评审 fork 的 token 用量记回父会话(#87250:fork 消耗了 token 然后抛异常也要记账)。
评审提示词有三份,按配置选用:_MEMORY_REVIEW_PROMPT(只看记忆:用户暴露了什么人格/偏好/期望,"If nothing is worth saving, just say 'Nothing to save.'")、_SKILL_REVIEW_PROMPT(只看技能,下一小节展开)、_COMBINED_REVIEW_PROMPT(两者合并,开篇即分工:"Memory: who the user is ... Skills: how to do this class of task")。这个分工句是第 4-01 节"技能与记忆分野"在提示词层的回声:记忆回答"用户是谁、当前处境",技能回答"这类活怎么干"。
_SKILL_REVIEW_PROMPT(453 行起)开篇定调:"Be ACTIVE — most sessions produce at least one skill update, even if small. A pass that does nothing is a missed learning opportunity, not a neutral outcome"(无所作为不是中性结果,是错失的学习机会)。四类触发信号,任一即动:
"Signals to look for (any one of these warrants action): • User corrected your style, tone, format, legibility, or verbosity. Frustration signals like 'stop doing X', 'this is too verbose', ... are FIRST-CLASS skill signals, not just memory signals. Update the relevant skill(s) to embed the preference so the next session starts already knowing. • User corrected your workflow, approach, or sequence of steps. ... • Non-trivial technique, fix, workaround, debugging path, or tool-usage pattern emerged that a future session would benefit from. Capture it. • A skill that got loaded or consulted this session turned out to be wrong, missing a step, or outdated. Patch it NOW.
注意第一类的措辞:用户的挫败感是一等技能信号——"下次会话开局就知道"是所有改进的验收标准。定位到信号后按四级优先动作落笔(与 curator 的伞形理念一脉相承):①优先 patch 本次已加载的技能(它正在场上,是最该吸收教训的那个,但仅限 curator 管辖);②patch 现有类级伞;③在伞下加支撑文件(references/ 会话细节/知识库、templates/ 模板、scripts/ 可重跑脚本),并在 SKILL.md 加一行指针让未来 agent 找得到;④都不合适才 create 新伞——名字必须在类级别,"If the proposed name only makes sense for today's task, it's wrong"。四级动作的判定树:
信号触发(四类之一) ├─ 本次加载/查阅过的技能覆盖该领域? │ ├─ 是,且 curator 管辖 ──────────── ① skill_manage(patch) 吸收教训 │ └─ 是,但受保护/用户所有 ────────── 降级到 ②,并在回复中建议 adopt ├─ 存在类级伞技能覆盖该类任务? ──────── ② patch 伞(加小节/pitfall/触发词) ├─ 内容窄但值钱的会话细节? ──────────── ③ write_file 到伞的 │ references/<topic>.md | templates/<x> | scripts/<x> └─ 无任何现有技能覆盖该类? ──────────── ④ create 类级伞(名字过"类级"测试)
评审提示词用整节列出禁止捕获的内容,理由是"these become persistent self-imposed constraints that bite you later when the environment changes":
工具因配置问题失败时要捕获修复法(安装命令/配置步骤/环境变量)归入既有排障技能——"never 'this tool does not work' as a standalone constraint"。这段负向清单是整个外循环里最深刻的产品判断:自我改进系统最大的风险不是学得太慢,而是把噪声学成教条。
用户偏好归属也有一条明确的裁定(提示词原话):"when the user expressed a style/format/workflow preference, the update belongs in the SKILL.md body, not just in memory"——用户抱怨"你怎么做的这件事"时,教训要落进管辖该类任务的技能,只写记忆不够;两者在相关时都该携带。评审的收尾由 summarize_background_review_actions(681 行)完成:扫 fork 会话里的工具调用结果,把实际发生的记忆/技能动作提炼成给用户看的自我改进摘要,并按 _classify_review_result 归类(acted/nothing/error)记入日志——每轮自我改进对用户可见,不是黑箱。
patch 落盘时 skill_manage 调 tools/skill_usage.py:911 的 bump_patch:
911 def bump_patch(skill_name, *, action="patch", task_id=None, session_id=None): 916 def _apply(rec): 917 rec["patch_count"] = _non_negative_int(rec.get("patch_count")) + 1 918 rec["patch_generation"] = _non_negative_int(rec.get("patch_generation")) + 1 919 rec["last_patched_at"] = _now_iso()
patch_generation 是技能的"代数":每 patch 一次 +1。下一次该技能被加载进 prompt 时 bump_use 检查 patch_generation > last_reused_patch_generation——成立则记一次 reuse_after_patch(第 4-02 节读过),并发射 skill_lifecycle 遥测事件(created/loaded/patched/...)。于是"改了之后有没有人用"不再是感觉而是数据:一个 patch_count 高但 reuse_after_patch 恒为零的技能,改进是在空转;反之则证明外循环真的在把经验转化为复用价值。配合 .usage.json 里的 use_count/last_used_at,curator 的失活判定与 learning_graph 的"已用"过滤都有了事实基础。
agent/learning_graph.py(328 行)的定位写在第一行:"Assemble the 'learning made visible' graph for desktop"。它把两类东西连成图:学到的技能(非 base 安装、且 agent 创建或 use_count>0)与记忆卡片(MEMORY.md/USER.md 按 § 分隔切出的每一段)。节点构建(build_skill_nodes,125 行)读每个 SKILL.md 的 frontmatter(name/category/related_skills)并 join usage 侧车:
142 nodes[name] = SkillNode( 143 name=name, 144 category=_category(fm, skill_md), 145 source=source, # base / profile 146 timestamp=last_activity or file_ts, 147 use_count=int(rec.get("use_count", 0) or 0), 148 state=str(rec.get("state", "active") or "active"), 149 created_by=rec.get("created_by"), 150 pinned=bool(rec.get("pinned", False)), 151 related=_related(fm), 152 )
边有两类:技能-技能边来自 frontmatter 声明的 related_skills(两端都存在才连,去重);记忆-技能边由词汇重叠派生(_memory_skill_edges,227 行):把记忆卡片标题+正文与技能名做 token 交集计分,技能名整串命中 +6 分,每取前 4 名连边——让图能回答"我记住的东西和我会的技能之间有什么关系"。density_stats(171 行)输出节点数/边密度/孤立节点占比/agent 创建数/已用数等统计。build_learning_graph 最终产出的 payload 形如:
{ "nodes": [ {"id": "deploy-checklist", "label": "deploy-checklist", "kind": "skill", "category": "devops", "useCount": 7, "state": "active", "createdBy": "agent", "pinned": false, "timestamp": 1760000000}, {"id": "memory:profile:2", "label": "部署偏好蓝绿不发全量…", "kind": "memory", "memorySource": "profile", "category": "memory"} ], "edges": [{"source": "memory:profile:2", "target": "deploy-checklist"}], "clusters": [{"category": "devops", "count": 1}, {"category": "memory", "count": 1}], "memory": [ ...卡片全文... ], "stats": {"nodes": 1, "related_edges": 0, "isolated_pct": 0.0, "agent_created": 1, "used": 1, "memory_nodes": 1} }
agent/learning_mutations.py 再给每个节点稳定 id(技能=名字,记忆=memory:<source>:<index>),支撑 CLI/TUI/桌面三端的 journey edit|delete——**图谱不只可看,还可编辑**;删技能走归档(hermes curator restore 可恢复),删记忆重写文件。渲染层 agent/learning_graph_render.py(658 行)把同一份 payload 转成 TUI 可画的字符图;learning_graph.py 还能 python -m agent.learning_graph 直接对真实数据打印边密度统计——图谱数据结构(节点/边/簇/统计)与展示解耦,桌面端、TUI、CLI 共用一源。

安全边界逐段可查:评审段有白名单+取消让路+保护名单;技能段有 curator 的"永不硬删";账本段有 fail-closed 回滚;图谱段有用户编辑权。整条回路的净效应就是总纲的那句承诺——用得越多越强,而且每一分"变强"都可审计、可撤销、可干预。
💡 循环要点:外循环的心脏不是某个算法,而是一个角色转换:同一个 AIAgent 白天当技能的使用者,入夜(每轮结束的空闲里)当自己技能库的作者与维护者。触发靠显式信号清单,落笔有四级优先动作与五类负向禁忌兜底,成效由 patch_generation 的复用率度量,轨迹由 learning_graph 显影。普通 agent 的能力随会话结束归零;Hermes 的能力以 SKILL.md 为单位跨会话复利——这就是"自我改进"四个字在源码里的全部含义。
技能与学习图谱已就位,下一章转向外循环的另一沉淀端——记忆器官:有界策展的 MEMORY.md/USER.md,以及 8 种可插拔记忆 provider 与 FTS5 中文分词搜索。