Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

技能系统:从经验中学习的闭环

本章核心源码agent/skill_utils.py(442 行)、tools/skills_tool.py(1376 行)、tools/skill_manager_tool.py(742 行)

定位:本章分析 Hermes “self-improving” 理念的核心实现——技能系统。技能不是静态文档,而是 agent 从工作中提炼、在使用中改进、按条件加载的过程性知识。 前置依赖:第 5 章(提示词系统)、第 6 章(工具系统)。适用场景:想理解“self-improving agent“如何在工程上实现。

为什么技能系统是 Hermes 的核心差异

大多数 agent 框架有工具系统(“agent 能做什么”)和 prompt 系统(“agent 知道什么”)。Hermes 多了第三个系统——技能系统(“agent 学到了什么”)。

三者的关系:

系统内容类型变化频率来源
工具可执行的函数低(开发者添加)代码
Prompt身份和规则低(配置/硬编码)配置文件
技能过程性知识高(agent 自主创建/改进)工作经验

技能填补了一个空白:agent 在解决一个复杂问题后,如果没有技能系统,下次遇到同类问题需要从头探索。有了技能系统,agent 可以把解决过程提炼为可复用的技能——下次直接检索使用。

这就是 Learning Loop 赌注的工程实现。

技能的数据模型

每个技能是一个 Markdown 文件,带有 YAML frontmatter:

---
metadata:
  hermes:
    fallback_for_toolsets: [web]
    tags: [automation, workflow]
platforms: [macos, linux]
---

# 使用 Exa API 搜索学术论文

## 问题
标准 web_search 对学术论文的检索效果不佳...

## 方案
使用 Exa 的 neural search 模式,配合 category=research...

## 示例
```python
result = web_search(query="transformer attention mechanism", ...)

### Frontmatter 解析

`parse_frontmatter()`(`agent/skill_utils.py:52`)负责解析 YAML frontmatter:

```python
# agent/skill_utils.py:52-86
def parse_frontmatter(content: str) -> Tuple[Dict[str, Any], str]:
    if not content.startswith("---"):
        return {}, content
    end_match = re.search(r"\n---\s*\n", content[3:])
    yaml_content = content[3 : end_match.start() + 3]
    try:
        parsed = yaml_load(yaml_content)  # CSafeLoader(快速)+ fallback
    except Exception:
        # Fallback: simple key:value parsing for malformed YAML
        for line in yaml_content.strip().split("\n"):
            key, value = line.split(":", 1)
            frontmatter[key.strip()] = value.strip()
    return frontmatter, body

设计选择:优先使用 CSafeLoader(C 实现,快);如果 YAML 格式有问题,fallback 到逐行 key: value 解析。这让系统对格式不完美的技能文件保持鲁棒。

条件加载

Frontmatter 支持多种条件字段:

平台过滤agent/skill_utils.py:92):

platforms: [macos, linux]  # 只在 macOS 和 Linux 上加载

skill_matches_platform()platforms 列表与 sys.platform 比较。缺失或空表示兼容所有平台。

工具依赖agent/skill_utils.py:240):

metadata:
  hermes:
    fallback_for_toolsets: [web]     # 当 web toolset 不可用时激活
    requires_toolsets: [terminal]     # 需要 terminal toolset 才加载
    requires_tools: [web_search]     # 需要特定工具才加载

extract_skill_conditions() 提取这些条件,由 build_skills_system_prompt() 在构建技能索引时评估。

禁用列表agent/skill_utils.py:121):

# config.yaml
skills:
  disabled: [deprecated-skill]
  platform_disabled:
    telegram: [desktop-only-skill]

get_disabled_skill_names() 支持全局禁用和按平台禁用。Gateway 的 Telegram 平台可以禁用只在桌面端有意义的技能。

技能的生命周期

graph LR
    A["发现<br/>扫描 skills/ 目录"] --> B["索引<br/>提取 name + description"]
    B --> C["注入<br/>写入 system prompt"]
    C --> D["检索<br/>skill_view 按需加载"]
    D --> E["使用<br/>模型根据内容执行"]
    E --> F["创建<br/>skill_manage create"]
    F --> G["改进<br/>skill_manage patch"]
    G --> D

阶段 1:发现

get_all_skills_dirs()agent/skill_utils.py:226)返回技能搜索路径:

# agent/skill_utils.py:226-234
def get_all_skills_dirs() -> List[Path]:
    dirs = [get_hermes_home() / "skills"]   # 当前 HERMES_HOME/skills/(总是第一位)
    dirs.extend(get_external_skills_dirs())  # config.yaml 中的 external_dirs
    return dirs

阶段 2:索引

build_skills_system_prompt()agent/prompt_builder.py:529)扫描所有技能目录,为每个技能提取名称和一句话描述,生成索引列表注入 system prompt。

索引只包含标题和描述——完整内容在模型需要时通过工具按需加载。这个设计控制了 system prompt 的 token 开销:26 个技能类别的索引只占几百 token,但完整内容可能超过 50K token。

阶段 3-4:检索与使用

模型通过 skill_view 工具按名称检索技能的完整内容:

# tools/skills_tool.py:787
def skill_view(name: str, file_path: str = None, task_id: str = None) -> str:
    # 在所有 skills 目录中搜索匹配的技能文件
    # 返回完整的 Markdown 内容

阶段 5-6:创建与改进

这是 Learning Loop 的核心。模型通过 skill_manage 工具创建和改进技能:

# tools/skill_manager_tool.py:569
def skill_manage(action: str, name: str = None, content: str = None,
                 patch: str = None, ...):
    # action = "create": 创建新技能
    # action = "patch": 改进现有技能
    # action = "delete": 删除技能

SKILLS_GUIDANCEagent/prompt_builder.py:164)引导模型的行为:

“After completing a complex task (5+ tool calls), fixing a tricky error, or discovering a non-trivial workflow, save the approach as a skill… 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.”

两个关键指令:

  1. 自主创建:完成复杂任务后主动保存为技能
  2. 使用时改进:发现技能过时或不准确时立即 patch

这让技能系统形成了一个正反馈循环:使用越多 → 发现越多问题 → 改进越多 → 质量越高 → 使用越有效。

Nudge 机制:主动触发学习

agent 不会“忘记“创建技能——AIAgent 中的 nudge 机制会定期提醒:

# run_agent.py:1072-1076
self._skill_nudge_interval = 10  # 每 10 个 tool iteration 检查
self._iters_since_skill = 0

当模型在一次对话中使用了 10+ 个工具调用但没有触碰 skill_manage,编排器会在 _spawn_background_review() 中启动一个后台 agent 审查对话内容,判断是否值得创建技能(详见第 4 章)。

记忆与技能的协调:Learning Loop 的完整闭环

技能系统不是独立演化的——它和记忆系统(第 11 章)通过一套共享机制紧密耦合,共同构成了 Hermes 的 Learning Loop。理解这个耦合对理解“self-improving agent“的完整实现至关重要。

前面三章分别讲了三个要素:第 4 章介绍了 _spawn_background_review,第 11 章介绍了记忆系统,本章介绍了技能系统。但这三者之间的关系——它们如何共享触发、共享上下文、又如何保持语义分离——才是完整的 Learning Loop 故事。

三个耦合点

graph TD
    subgraph "主对话循环"
        CONV["run_conversation()<br/>用户对话"]
        TC1["_turns_since_memory<br/>按 user turn 递增"]
        TC2["_iters_since_skill<br/>按 tool iteration 递增"]
    end

    subgraph "触发判断"
        M_CHECK{"turns ≥ 10?"}
        S_CHECK{"iters ≥ 10?"}
    end

    subgraph "_spawn_background_review"
        ROUTE{"哪些触发?"}
        P1["MEMORY_REVIEW_PROMPT<br/>关注用户偏好/身份"]
        P2["SKILL_REVIEW_PROMPT<br/>关注复杂工作流"]
        P3["COMBINED_REVIEW_PROMPT<br/>同时检查两者"]
        RA["review agent fork<br/>(max_iterations=8)"]
    end

    subgraph "写入(共享 store)"
        MEM["MEMORY.md / USER.md"]
        SKILL["skills/*.md"]
    end

    CONV --> TC1
    CONV --> TC2
    TC1 --> M_CHECK
    TC2 --> S_CHECK
    M_CHECK -->|是| ROUTE
    S_CHECK -->|是| ROUTE
    ROUTE -->|only memory| P1
    ROUTE -->|only skill| P2
    ROUTE -->|both| P3
    P1 --> RA
    P2 --> RA
    P3 --> RA
    RA --> MEM
    RA --> SKILL

    style P3 fill:#f96,stroke:#333,stroke-width:2px
    style RA fill:#9cf,stroke:#333,stroke-width:2px

耦合点 1:共享的后台审查机制

两个系统共用同一个 _spawn_background_reviewrun_agent.py:1954)。当记忆和技能的触发条件同时满足时,Hermes 不会 spawn 两个 review agent——而是用 _COMBINED_REVIEW_PROMPTrun_agent.py:1940-1952)在一次后台审查中同时检查两者:

# run_agent.py:1970-1975
if review_memory and review_skills:
    prompt = self._COMBINED_REVIEW_PROMPT   # 一次检查两者
elif review_memory:
    prompt = self._MEMORY_REVIEW_PROMPT
else:
    prompt = self._SKILL_REVIEW_PROMPT

这节省了一次 LLM 调用,也让 review agent 可以在一次扫描中判断“这段对话该保存记忆还是技能“——而不是两个独立 agent 各自做判断、可能把同一条信息保存两次或都漏掉。

耦合点 2:不同计数器,不同语义

虽然共享触发机制,但两个计数器语义完全不同run_agent.py:1111-1112):

self._turns_since_memory = 0     # 按用户 turn 递增
self._iters_since_skill = 0      # 按工具迭代 递增

记忆的触发发生在主循环前run_agent.py:7481-7487),按 user turn 计数:

if self._turns_since_memory >= self._memory_nudge_interval:
    _should_review_memory = True

技能的触发发生在主循环后run_agent.py:9952-9958),按工具迭代计数:

if self._iters_since_skill >= self._skill_nudge_interval:
    _should_review_skills = True

这反映了两个系统要捕获的内容性质不同:

系统触发粒度捕获目标直觉
Memory用户 turn用户偏好、身份、环境事实用户说得越多,越可能透露值得记的信息
SkillTool iteration复杂工作流、解决方法agent 做得越多,越可能形成可复用的套路

用户连续聊 10 轮但每轮 agent 只做 1 次工具调用——这时 memory review 该触发(用户透露了很多),skill review 不该触发(没有复杂工作流)。反之,用户一句话触发了 agent 20 次工具调用完成一个复杂任务——skill review 该触发,memory review 不该。不同计数器保证两个系统按各自的语义独立触发

耦合点 3:Prompt 层的相互路由

两个系统的 guidance 文本明确相互指引边界。MEMORY_GUIDANCEagent/prompt_builder.py:144-156)的最后一句话是一个显式的路由指令:

“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. If you’ve discovered a new way to do something, solved a problem that could be necessary later, save it as a skill with the skill tool.

翻译过来:“任务进度和完成日志不要塞 memory,那是技能系统的地盘”。反过来 SKILLS_GUIDANCEprompt_builder.py:164-171)严格聚焦在“复杂任务后提炼可复用方法“,不越界到记忆领地。

这个 prompt 层的路由非常关键。没有它,模型可能把用户偏好(“我喜欢 Vim 键位”)写进技能、把任务历史(“上次我们部署到 AWS 的过程”)塞进 memory——两者都会污染。有了路由指令,模型在每次保存前都有一个清晰的决策框架:

保存什么保存到哪为什么
用户偏好、环境事实、工具怪癖memory 工具 → MEMORY.md / USER.md持久,注入每次 system prompt,影响每轮对话
完整对话历史SessionDB(自动)按需通过 session_search FTS5 检索
可复用方法、工作流、踩过的坑skill_manage 工具 → skills/*.md持久索引,按需通过 skill_view 加载

耦合点 4:共享的写入上下文

最细节但最关键的耦合——review agent 被 spawn 时同时继承了主 agent 的 memory store 和 skill 工具run_agent.py:1984-1998):

review_agent = AIAgent(model=self.model, max_iterations=8, quiet_mode=True, ...)
review_agent._memory_store = self._memory_store       # 共享 memory store
review_agent._memory_enabled = self._memory_enabled
review_agent._user_profile_enabled = self._user_profile_enabled
review_agent._memory_nudge_interval = 0               # 防递归触发
review_agent._skill_nudge_interval = 0                # 防递归触发
review_agent.run_conversation(user_message=prompt, conversation_history=messages_snapshot)

这意味着如果用户在一段对话中既透露了偏好又解决了复杂问题,同一个后台 review agent 在一次运行中会同时写入 memory 和 skill——两个系统看到的是完全一致的对话上下文。这避免了“memory review 看到了一个版本的对话、skill review 看到了另一个“的数据分裂问题。

注意 nudge interval 都被设为 0——防止 review agent 自己做工作时又触发另一次 review,造成无限递归。

设计启示:协调的自进化循环

三个耦合点共同实现了一个叫做**“coordinated self-improving loop”**的模式:

  • 语义分离:由 prompt guidance 强制路由,两个系统各管各的数据类型
  • 触发分离:两个独立计数器,按各自的领域指标独立递增
  • 机制共享:一个后台 review agent、一份消息快照、一次 LLM 调用
  • 写入共享:同一份 memory store 和同一套 skill 工具

这种“分离语义 + 共享机制“的设计避免了三种常见失败模式:

  1. 双重 LLM 成本:如果是两个完全独立的系统,触发条件同时满足时要跑两次 review agent,LLM 成本翻倍
  2. 数据分裂:两次独立 review 可能基于略微不同的 messages snapshot(时间差),导致两个系统的“记忆“不一致
  3. 语义越界:没有 prompt 层路由,模型会把任务历史塞进 memory、把偏好塞进 skill,两者都被污染

Learning Loop 的完整故事不是“有一个记忆系统 + 有一个技能系统“,而是“两个系统通过共享的后台审查机制协调工作,在各自语义边界内独立演化“。这才是“self-improving agent“的工程实现。

与 agentskills.io 的对接

Hermes 的技能格式兼容 agentskills.io 开放标准。这意味着:

  • 社区创建的技能可以直接放入当前 HERMES_HOME/skills/(默认 profile 下表现为 ~/.hermes/skills/
  • Hermes 创建的技能可以分享到 Skills Hub
  • 不同 agent 框架之间的技能可以互通

设计哲学:基于来源的信任分级(Source-based credibility)

并非所有技能享有同等信任。Hermes 实现了一套信任分级机制(tools/skills_guard.py:41-49),根据技能的来源决定如何解读其安全性发现:builtin 技能(Hermes 团队编写)即使触发“dangerous“级发现也被允许执行;trusted-source 技能允许“caution“但拦截“dangerous“;community-sourced 技能连“caution“级发现也会被拦截。同一个安全发现,因为编写者不同,处理策略完全不同——这是来源即信誉的设计原则。

技能 vs 工具 vs Prompt:三种知识的区分

维度工具Prompt技能
形式Python 函数文本块Markdown 文件
变更频率低(版本发布)低(配置修改)高(agent 自主创建)
作者开发者开发者/用户Agent 自身
加载方式模块导入构建时注入 system prompt索引注入 + 按需检索
可改进否(需改代码)手动Agent 自动 patch

这个区分不是学术分类,而是有工程后果的:如果技能被实现为工具,每次添加技能就需要修改代码;如果技能被实现为 prompt,所有技能内容都会占用 system prompt token。Markdown + 按需检索的方案既让 agent 自主管理,又控制了 token 开销。

设计启示

技能系统的设计展示了 “self-improving” 的工程实现路径:

  1. 低成本创建:Markdown 文件 + YAML frontmatter,没有 schema 验证、没有编译、没有注册步骤
  2. 渐进式加载:索引在 system prompt,完整内容按需检索——控制基础 token 开销
  3. 正反馈循环:使用时发现问题 → 立即 patch → 下次使用时更好
  4. Nudge 而非强制:通过 background review 提醒,不强制每次都创建技能

第 9 章将分析子代理与委托机制——另一种扩展 agent 能力的方式。


设计赌注回扣:本章是 Learning Loop 赌注的核心实现。技能系统让 Hermes 从“能用工具做事的 agent“升级为“能从经验中学习的 agent“。SKILLS_GUIDANCE 的“使用时立即 patch“指令和 _spawn_background_review 的自动审查构成了完整的学习闭环。


版本演化说明

本章核心分析基于 Hermes Agent v0.8.0(2026 年 4 月)。 技能文件发现、skill_manage 和基础 skills 目录迁移,在 v0.3.0 发布窗口之前就已出现;v0.4.0 发布窗口又加入了 background review 驱动的自动审查。之后 v0.5.0-v0.8.0 之间继续补足条件加载、展示层和 agentskills.io 兼容细节。