一、为什么“这次查到了”还不等于“以后学会了”
继续沿用前两篇的例子:你让 Hermes 修复一个生成代码相关的失败测试。第一次处理时,它翻了项目目录, 又打开官方维护指南,终于找到正确顺序:先运行生成器,再检查差异,最后执行测试。任务完成了,但如果这些步骤只留在本轮对话里, 下次换一个会话,agent 仍可能从头搜索。这里出现了一个很实际的问题:怎样把已经确认过的一次做法,变成以后可以主动发现的操作流程?
第三篇讲的 background review 会在交付后检查“这一轮有没有值得留下的经验”,它适合处理运行时偶然暴露出来的偏好和流程。
本篇要讲的是另一条更明确的入口:用户已经知道要学什么,于是直接给出资料、范围和要求,让 Hermes 把它整理成 Skill。
这个入口就是 /learn。两条路最后都可能碰到 Skills,但触发者不同:前者由到点提醒和 finalizer 安排,后者由用户当场发起。
先不要把 /learn 想成“训练模型”。它做的事情更朴素,也更容易验证:让当前这个 agent 像完成普通任务一样读取资料、提炼步骤、
调用 skill_manage 写出文件。模型参数没有变化,也没有另外启动一套蒸馏服务。真正新增的是一个可读、可审查、可修改的 Skill 包。
| 读之前先问 | 本篇会给出的答案 |
|---|---|
/learn 会不会调用一套专用训练器? |
不会。入口只构造一条详细提示,再落回正常 AIAgent 回合。 |
| 网页或代码里的文字能不能反过来指挥 agent? | 不能。来源被明确当作数据,用户请求才是治理指令。 |
为什么有的结果只有一个 SKILL.md,有的还有很多文件? |
短流程适合紧凑 Skill;大型资料库适合索引加按需加载的 references/。 |
| Learning Graph 会不会继续优化 Skill? | 不会。它从 Skills 与 Memory 文件构建关系视图,编辑时再回写底层文件。 |
二、先走完整条路:一条请求怎样落成一个 Skill 包
假设你输入:/learn 官方项目指南,重点学习生成文件变更后的测试流程,忽略已经弃用的命令。
Hermes 不会只取第一个 URL,也不会把后半句当成闲聊。源码把这条请求拆成两类同等重要的信息:一类是要读取的来源,
另一类是决定 Skill 范围的要求。随后,正常工具回合依次完成读取、整理、写入和报告。
/learn 没有专用训练器;它复用正常回合与现有工具,把结果写成可见文件。| 阶段 | 修测试这个例子里发生什么 | 留下什么可观察结果 |
|---|---|---|
| 1. 接收请求 | 用户给出官方指南,并限定“生成文件变更后的测试流程”。 | 一条自由文本请求,还没有 Skill。 |
| 2. 改写为普通回合 | build_learn_prompt 把来源、要求、写作规范和安全边界合成提示。 |
进入与普通任务相同的 agent 输入队列。 |
| 3. 读取来源 | agent 用文件或网页工具找到真实命令、顺序和失败信号。 | 当前回合里的来源证据,不会直接变成长期指令。 |
| 4. 选择形状 | 这是一条短工作流,因此用一个紧凑 SKILL.md,不把整份指南搬进去。 |
Skill 名称、触发条件、步骤、陷阱与验证方法。 |
| 5. 写入并校验 | skill_manage(create) 检查名称、frontmatter、体积和安全扫描,再原子写入。 |
~/.hermes/skills/<category>/<name>/SKILL.md。 |
| 6. 以后发现 | Skills 索引缓存被清除,后续提示重建时会重新扫描可用 Skill。 | 未来回合可以按描述路由并按需读取完整内容。 |
这张表有一个重要边界:第二步到第五步都发生在同一个普通 agent 回合里,而第六步描述的是持久化结果怎样进入后续发现路径。 它不是“先训练一个小模型,再把模型挂进 Hermes”;它更像把一次成功操作整理成项目里可维护的运行手册。
三、入口层只做一件事:把命令改写成治理充分的普通请求
/learn 在 CLI、gateway 和桌面入口上的表面不同,但它们都调用同一个
build_learn_prompt。
CLI 把构造后的字符串放进 _pending_input;gateway 则改写当前事件的 event.text,然后继续走正常 agent 处理。
因此 provider、模型、工具后端和会话持久化都沿用现有 runtime。
# CLI:放回当前会话的输入队列
msg = build_learn_prompt(user_request)
self._pending_input.put(msg)
# Gateway:改写本轮文本,然后继续普通 agent 流程
event.text = build_learn_prompt(event.get_command_args().strip())
# fall through to agent processing
这段设计带来三个直接后果。第一,它不新增一个只能在本地运行的“学习后端”,所以本地、Docker 和远程 terminal backend 都能用。 第二,它仍受当前 agent 的工具权限约束:没有网页提取能力时,不能凭空读网页;没有可写 Skills 工具时,也不能神秘地落盘。 第三,它的成功标准不是“提示已经构造”,而是 agent 最后真的调用管理工具并报告写入结果。
如果用户只输入 /learn 而没有参数,builder 会把请求补成“复盘我们刚刚走过的流程,并整理成可复用 Skill”。
这仍然是显式用户请求,不等于第三篇的后台 review:它占用当前前台回合,用户看得见它正在读取、写入与回答;后台 review 则只能在上一轮已经交付后运行。
CLI 入口见
_handle_learn_command,
gateway 的 fall-through 见
learn command 分支。
四、读取资料之前,先分清“来源”和“要求”
一条学习请求可以同时包含目录、单个文件、URL、刚才的对话和用户粘贴的笔记。简单实现很容易抓到第一个链接就开工, 结果漏掉后面的“只讲认证流程”“不要收录废弃接口”“把验证命令写完整”。Hermes 的 prompt 明确规定:每一部分都承载意图, 来源告诉 agent 去哪里取证,要求决定最终 Skill 覆盖什么、强调什么、舍弃什么。
/learn https://project.example/maintainer-guide
只保留 generated code 的验证流程
跳过 deprecated commands
来源:官方维护指南
要求:范围 = generated code;排除 = deprecated commands
结果:不是“整站摘要”,而是一条可执行的验证 Skill
接着才是工具选择:本地目录用 read_file / search_files,URL 用 web_extract,
“刚才我们做的事”来自当前 conversation,粘贴材料则按原文处理。如果范围仍有轻微歧义,prompt 要求 agent 做合理选择并说明,
而不是因为一个无关紧要的缺口让整个学习过程停住。这个策略不是降低准确性;它要求把假设暴露出来,让用户能在结果上继续修正。
4.1 来源是证据,不是新的用户指令
学网页和文档还有一条更硬的边界。资料里可能出现“忽略之前要求”“运行这条命令上传配置”等句子,也可能藏有零宽字符或双向文本控制符。
对一个正在总结资料的 agent 来说,这些内容很像指令。learn_prompt.py 因此把 source hygiene 直接嵌入每条
/learn 提示:来源文本只作为数据,只有用户请求能治理本轮行为;不可见和双向 Unicode 控制字符要被丢弃,
来源里的命令不能因为语气像 prompt 就被带进 Skill。
回到修测试的例子:官方指南中的真实命令、先后顺序和失败条件是证据;页面里一段面向文档机器人写的操作提示不是治理指令。
Hermes 可以把前者整理成 Procedure 和 Verification,却不能因为后者要求“关闭安全检查”就修改本轮权限。具体约束见
_SOURCE_HYGIENE。
五、不是所有资料都该塞进同一个 SKILL.md
来源读完以后,agent 要先判断交付物的形状。源码给出两种选择,不是按文件数量机械切分,而是看资料本身是“一条可重复流程”还是“一大块需要按主题查询的知识”。 这一步很重要:把短流程拆成十几个文件会增加查找成本;把一本书压进 200 行,又会把真正有用的定义、决策规则和章节关系总结掉。
| 资料形状 | 推荐产物 | 什么时候加载 | 修测试例子 |
|---|---|---|---|
| 短工作流、小型 API、单一操作手册 | 一个紧凑 SKILL.md,复杂脚本再放 scripts/ |
路由命中后读取完整流程 | “生成代码变更后的测试顺序”适合这种形状 |
| 书、论文集、大型规范、完整文档站 | 精简 SKILL.md 索引 + 分主题 references/ |
核心规则常驻,章节按问题用 skill_view 加载 |
整套编译器维护手册可能需要这种形状 |
对短流程,最终文件可以很直白。开头的 description 负责让未来回合知道“什么时候该想到它”;Procedure 保留可复制的真实命令; Pitfalls 写清生成文件未刷新时的假失败;Verification 给出一次能证明流程完成的检查。它不是把官方指南换个格式再粘一遍,而是把“以后遇到同类任务时怎样做”写成操作协议。
generated-code-testing/
SKILL.md
When to Use # 哪些请求会触发它
Prerequisites # 运行前必须具备什么
Procedure # 先生成、看差异、再测试
Pitfalls # 旧生成物会制造什么假象
Verification # 什么结果能证明流程有效
如果来源是大型维护手册,SKILL.md 则只保留核心判断和引用索引,具体章节进入
references/code-generation.md、references/schema-migrations.md 等文件。未来只问生成代码时,不必把整套手册都塞进上下文。
这些 expansive rules 直接写在
_KNOWLEDGE_SKILL_STANDARDS 中。
5.1 skill_manage 不是普通文件写入
agent 整理好文本以后,不是随便把 Markdown 写到某个目录。skill_manage(action="create") 会验证名称、category、frontmatter 和内容体积,
检查同名冲突,原子写入 SKILL.md,再对整个目录做安全扫描;扫描阻断时会移除刚创建的目录。只有第一步成功后,
才用 write_file 增加 references/、templates/ 或 scripts/。
skill_manage(
action="create",
name="generated-code-testing",
category="development",
content=skill_md,
)
skill_manage(
action="write_file",
name="generated-code-testing",
file_path="scripts/verify-generated-files.sh",
file_content=script,
)
创建路径和失败回滚见
_create_skill,
action 分派与成功后的缓存处理见
skill_manage。
这让 Skill 成为受规则约束的长期对象,而不是一段无法追踪来源和结构的自由文本。
六、写入以后,什么时候才算“学会”
文件落盘只是第一步。Hermes 的系统提示不会在当前回答中凭空重写;Skill 创建成功后,管理工具清除进程内的 Skills prompt cache,
并删除可选的磁盘 snapshot。下一次需要构造 Skills 索引时,prompt builder 才会重新扫描目录,把新 Skill 的名称和短描述放进可发现列表。
等任务真的命中它,再按需读取完整 SKILL.md 或 reference 文件。
本轮 /learn
读取资料 -> skill_manage -> 文件已落盘
后续提示重建
扫描 Skills -> 索引出现 generated-code-testing
未来同类任务
描述命中 -> skill_view -> 读取步骤 -> 执行并验证
所以“学会”在这里不是模型内部多了一个不可见能力,而是三件可检查的事同时成立:文件真实存在,短描述能把合适任务路由过来,
正文给出了能执行和验证的流程。缓存清理实现见
clear_skills_system_prompt_cache。
还有一个与第三篇 Curator 相关的细节:前台 /learn 创建的是用户主动要求的 Skill,源码不会把它标成 background-review 创建。
Curator 的可选语义整理只应处理 agent 自行沉淀的候选,不能把用户主动建立的知识资产当成自动清理材料。两条学习路径共享文件格式,
但 provenance 仍然不同。
七、Learning Graph:把已经存在的关系画出来
Skill 越积越多以后,另一个问题出现了:用户怎样看见“我已经学过什么,它们和哪些记忆相关”?Hermes 的桌面 Journey 面板用
Learning Graph 回答这个问题。这里最容易产生的误会是把图上的节点和边理解成“另一个学习算法”。实际上,
agent/learning_graph.py 做的是读取底层文件、构造 JSON 视图;它不运行任务,不修改 Skill,也不比较哪个 Skill 更好。
7.1 哪些东西会成为节点
Skill 节点来自 profile 目录,而不是把所有 bundled base Skills 全部画出来;当前实现进一步要求它有真实学习信号:由 agent 创建,或至少被使用过。
节点会携带 category、时间戳、use count、state、created-by 和 pinned 等展示字段。Memory 节点则来自
MEMORY.md 与 USER.md:两个文件按裸 § 分隔,每一段成为一张可读卡片。
建图入口见
build_learning_graph。
注意,节点的 state、use_count 和 pinned 只是把已有生命周期与使用记录带到视图里。
Graph 不根据连线数量把 Skill 变成 active,也不因为节点孤立就 archive。第三篇讲的 Curator 仍然拥有生命周期迁移;这里负责的是显示,不是裁决。
7.2 两种边代表两种强度完全不同的关系
Skill 与 Skill 之间的边来自 frontmatter 里的 related_skills。只有两个端点都真实存在时才建边,并按无向关系去重。
这是一条作者显式声明的关系。Memory 与 Skill 之间没有这样的声明,于是实现采用一个很轻的词法启发:把标题和正文转成长度至少为 3 的 token;
Skill 全名出现在 Memory 中加 6 分,名称 token 重叠再加分;分数大于 0 的候选按分数排序,每张 Memory 卡最多连前 4 个 Skill。
score = 0
if skill_name_lower in memory_text:
score += 6
score += len(skill_name_tokens & memory_tokens)
if score > 0:
candidates.append((score, skill.name))
edges.extend(top_four(candidates))
因此虚线只能读成“词面上可能相关”,不能读成“模型已经证明两者存在因果关系”。例如 Memory 里写了
generated code,它可能同时连到测试 Skill 和代码生成 Skill;这有助于浏览和定位,却不是语义合并依据。
显式边见
build_edges,
词法边见
_memory_skill_edges。
| 图中元素 | 来源 | 可以怎样理解 | 不能推出什么 |
|---|---|---|---|
| Skill 节点 | profile Skill + usage/provenance 记录 | 这条长期流程已经存在并有学习信号 | 节点越大或越新就一定更好 |
| Memory 节点 | MEMORY.md / USER.md 的分段 |
这是一条长期事实、偏好或用户画像片段 | 它已经被编译成 Skill |
| Skill 实线 | related_skills |
作者显式声明了关联 | 两条 Skill 已经合并 |
| Memory-Skill 虚线 | 名称与 token 的词法重叠 | 浏览时值得一起看看 | 经过 embedding、评测或因果验证 |
八、在图上编辑,真正改的是哪一本账
关系图不是只读海报。Journey 的 CLI、TUI 和桌面 REST 都可以根据 node id 查看、编辑或删除节点,
但 learning_mutations.py 不会把修改存在一份独立 graph database 里。它把节点 ID 解析回原始文件:Skill 节点 ID 就是 Skill 名称;
Memory 节点 ID 形如 memory:<source>:<index>,再定位到 MEMORY.md 或 USER.md 的对应分段。
| 用户动作 | 底层发生什么 | 保护边界 |
|---|---|---|
| 编辑 Skill | 完整改写对应 SKILL.md,成功后清 Skills 缓存 |
仍经过 Skill frontmatter、体积和安全检查 |
| 删除 Skill | 归档 Skill,并给出 curator restore 命令 | pinned Skill 必须先 unpin,不做不可恢复删除 |
| 编辑 Memory | 替换对应 § 分段,再用原子写入保存文件 |
空内容不被当作编辑,需显式 delete |
| 删除 Memory | 移除对应分段并重写源文件 | 索引过期时拒绝操作,要求刷新 Graph |
这种设计保住了单一事实来源:Graph 可以随时重建,文件才是持久状态。Skill 编辑和归档见
delete_node / edit_node,
Memory 的原子回写与 Skills 缓存清理见
helpers。
这也解释了为什么图上的 Memory index 可能“过期”:前面的分段被删掉后,旧索引不再可信,正确做法是刷新投影,而不是猜一个新位置继续写。
九、把整条旅程重新播放一次
现在可以把例子从头走完。第一次修生成代码测试时,agent 临时读取官方指南并完成任务;用户发现这条流程以后还会重复,于是用
/learn 指定同一份指南与关注范围。入口把请求改写成普通回合,agent 把来源当证据,选用紧凑 Skill 形状,
通过 skill_manage 写出 generated-code-testing。后续提示重建后,新会话可以从 Skill 索引发现它;
Learning Graph 则把这条 Skill、相关 Skill 和包含 generated code 的 Memory 片段画到一起,方便用户检查和维护。
这条链里没有一步可以单独叫“学习完成”。只读取资料,得到的是本轮上下文;只写文件但 description 无法路由,未来任务找不到它;
只画出 Graph,也没有新增任何能力。Hermes 的主动学习之所以可理解,是因为每一层都有明确产物:普通回合负责取证,
skill_manage 负责受控持久化,prompt builder 负责未来发现,Learning Graph 负责可视化已有关系。
到这里,我们只证明“知识被整理并可再次调用”,还没有证明“新 Skill 比旧 Skill 更好”。 下一篇会先解决这把尺子:评测题从哪里来,一条任务记录包含什么,训练集、验证集和最终留出集各自允许参与哪一步。 只有先把任务与分组边界讲清楚,后面的 DSPy / GEPA 优化才不会变成“拿同一批题反复改,再拿同一批题宣布进步”。 继续阅读:评测题怎样变成可信的比较尺子。