Chapter 08

Skills:程序性记忆

从 Voyager 在 Minecraft 里建立 skill library, 到 Hermes 用 SKILL.md 让 Agent 越用越聪明。这一章讲"程序性记忆"是什么、为什么这么设计。

本章约 5,900 字 阅读 ~25 分钟 关键词:Voyager · SKILL.md · procedural memory · cache-preserving injection

从这一章开始我们进入 Hermes 最有意思的部分——学习闭环。一个好 Agent 不应该每次 都从零开始,它应该把过去的成功解法存下来下次用。 这就是"程序性记忆"——区别于"情景记忆"("我们上次聊过什么"),程序性记忆是 "做某类事情的方法"

8.1认知科学的两种记忆

这个区分来自认知科学。人脑有两种长期记忆:

陈述性记忆 (Declarative)程序性记忆 (Procedural)
例子 "巴黎是法国首都" "骑自行车的肌肉记忆"
能用语言描述? 很难——"做了就会"
提取方式 主动回忆 遇到情境自动启动
形成方式 看到 + 编码 重复练习

对 LLM Agent 来说,这两种都很重要,但实现机制完全不同

8.2Voyager:让 Skill Library 成为研究范式

程序性记忆在 Agent 上的开山之作是 Voyager。我们之前提过一次,这里详细看:

Foundational Paper
Voyager: An Open-Ended Embodied Agent with Large Language Models
Wang et al. · arXiv:2305.16291 · NeurIPS 2023

NVIDIA 团队在 Minecraft 里跑 GPT-4 Agent。三个核心组件:

  1. Automatic Curriculum:根据当前进度提议下一个"刚好够难"的任务。
  2. Skill Library:每次完成新任务时,把解法(一段 JavaScript 代码) 存进 library。Library 里的代码索引向量化,下次任务来时按相似度检索。
  3. Iterative Prompting:执行报错时把错误信息送回 GPT-4 改代码,直到通过。

成绩:unique items 拿到 3.3 倍、移动距离 2.3 倍、tech tree milestone 解锁速度 15.3 倍。 更重要的是:在一个全新的 Minecraft 世界里,Voyager 能用学来的 skill解决新任务, 而对照组从零开始。

Voyager 教会业界的事

  1. Skill 是"可执行的",不是描述性 prompt。Voyager 的 skill 是真的 JavaScript 函数。
  2. Skill Library 是向量化检索。任务描述 → 找最相似 skill → 直接调用或参考。
  3. 每个 skill 在使用中 iterative refine。错了不要扔,要改。
  4. Skill 抵抗 catastrophic forgetting:因为 skill 是 explicit storage,不是模型权重。

Hermes 的 Skill 系统继承了这些思想,但选择了一个更"人类友好"的表达:

8.3Hermes 的 Skill 是什么

Hermes 的 Skill 不是可执行代码,而是一份 Markdown 文档—— SKILL.md。它告诉 LLM "在什么情况下、按什么步骤、用什么工具"。

看一个真实的例子。skills/apple/apple-reminders/SKILL.md

skills/apple/apple-reminders/SKILL.md
---
name: apple-reminders
description: "Apple Reminders via remindctl: add, list, complete."
version: 1.0.0
author: Hermes Agent
license: MIT
platforms: [macos]
metadata:
  hermes:
    tags: [Reminders, tasks, todo, macOS, Apple]
prerequisites:
  commands: [remindctl]
---

# Apple Reminders

Use `remindctl` to manage Apple Reminders directly from the terminal.
Tasks sync across all Apple devices via iCloud.

## Prerequisites

- **macOS** with Reminders.app
- Install: `brew install steipete/tap/remindctl`
- Grant Reminders permission when prompted
- Check: `remindctl status` / Request: `remindctl authorize`

## When to Use

- User mentions "reminder" or "Reminders app"
- Creating personal to-dos with due dates that sync to iOS
- Managing Apple Reminders lists
- User wants tasks to appear on their iPhone/iPad

## When NOT to Use

- Scheduling agent alerts → use the cronjob tool instead
- Calendar events → use Apple Calendar or Google Calendar
- Project task management → use GitHub Issues, Notion, etc.

## Quick Reference

\`\`\`bash
remindctl                    # Today's reminders
remindctl today
remindctl tomorrow
remindctl week
remindctl overdue
remindctl all
remindctl 2026-01-04         # Specific date
remindctl add "Buy milk" --due tomorrow
remindctl done <id>
\`\`\`

解剖这份 skill:

区块作用
YAML frontmatter元数据:name、description、platforms 限制、依赖命令
简介段落告诉模型这个 skill 大致干啥
Prerequisites系统要求 / 安装指令 / 权限要求
When to Use触发条件,这是最关键的一块
When NOT to Use反触发条件,防误用
Quick Reference具体命令例子

When to Use / When NOT to Use 是 Skill 的灵魂。LLM 看到这两块,就知道 在用户说"提醒我明天 9 点开会"时调用,在用户说"5 分钟后通知我"时不调用(那个应该走 cronjob)。

8.4Skill 被发现:scan_skill_commands

用户敲 /apple-reminders 启动 skill 之前,Hermes 必须先发现它。 扫描器在 agent/skill_commands.py:263-326

agent/skill_commands.py:263-326 (节选)
def scan_skill_commands() -> Dict[str, Dict[str, Any]]:
    """Scan ~/.hermes/skills/ and return /command → skill info."""
    global _skill_commands, _skill_commands_platform
    _skill_commands_platform = _resolve_skill_commands_platform()
    _skill_commands = {}

    from tools.skills_tool import (
        SKILLS_DIR, _parse_frontmatter,
        skill_matches_platform, _get_disabled_skill_names,
    )
    from agent.skill_utils import (
        get_external_skills_dirs, iter_skill_index_files,
    )

    disabled = _get_disabled_skill_names()
    seen_names = set()

    # 扫多个目录:本地 + 外部
    dirs_to_scan = []
    if SKILLS_DIR.exists():
        dirs_to_scan.append(SKILLS_DIR)
    dirs_to_scan.extend(get_external_skills_dirs())

    for scan_dir in dirs_to_scan:
        for skill_md in iter_skill_index_files(scan_dir, "SKILL.md"):
            # 跳过 .git / .archive 等
            if any(p in {'.git', '.archive'} for p in skill_md.parts):
                continue

            content = skill_md.read_text(encoding='utf-8')
            frontmatter, body = _parse_frontmatter(content)

            # 平台不匹配跳过(macOS-only skill 在 Linux 上不显示)
            if not skill_matches_platform(frontmatter):
                continue

            name = frontmatter.get('name', skill_md.parent.name)
            if name in seen_names or name in disabled:
                continue

            # 把 "Apple Reminders" → /apple-reminders
            cmd_name = name.lower().replace(' ', '-').replace('_', '-')
            cmd_name = _SKILL_INVALID_CHARS.sub('', cmd_name)

            _skill_commands[f"/{cmd_name}"] = {
                "name": name,
                "description": frontmatter.get('description', ''),
                "skill_md_path": str(skill_md),
                "skill_dir": str(skill_md.parent),
            }
            seen_names.add(name)

    return _skill_commands

设计要点:

  1. 多目录扫描:内置 skills/、用户 ~/.hermes/skills/、外部 ~/.hermes/external_skills/
  2. 平台过滤platforms: [macos] 的 skill 只在 macOS 上显示。
  3. 禁用列表:用户可以在 config 里 skills.disabled: [foo] 显式禁用。
  4. 命名空间:名字 → kebab-case slash command。"Apple Reminders"/apple-reminders
  5. 同名优先:先扫到的赢。本地覆盖外部,外部覆盖内置。

结果是个全局 dict _skill_commands,TUI 的 tab 补全直接读它。

8.5Skill 被加载:作为 User Message

这是 Hermes Skill 系统最聪明的设计。回顾第 5 章: system prompt 不能改,会破 cache。但我们想动态启用 skill 怎么办?

答案:把 skill 作为 user message 追加,不动 system promptagent/skill_commands.py:160-183

agent/skill_commands.py:160-183
def _build_skill_message(
    loaded_skill, skill_dir, activation_note,
    user_instruction="", runtime_note="", session_id=None,
) -> str:
    """Format a loaded skill into a user/system message payload."""
    from tools.skills_tool import SKILLS_DIR

    content = str(loaded_skill.get("content") or "")

    # 模板变量替换({{config.foo}} 等)
    skills_cfg = _load_skills_config()
    if skills_cfg.get("template_vars", True):
        content = _substitute_template_vars(content, skill_dir, session_id)

    # Inline shell 展开 — 实验功能,默认关
    if skills_cfg.get("inline_shell", False):
        timeout = int(skills_cfg.get("inline_shell_timeout", 10) or 10)
        content = _expand_inline_shell(content, skill_dir, timeout)

    parts = [activation_note, "", content.strip()]
    # 拼好后作为 user 消息追加
    return "\n".join(parts)

activation_note 长这样:

[IMPORTANT: The user has invoked the "apple-reminders" skill, indicating they want
you to follow its instructions for the upcoming request. Read the skill content
below carefully and apply it to your next response.]

# Apple Reminders

Use `remindctl` to manage Apple Reminders directly from the terminal.
...

这条 user message 被追加到 messages 末尾:

messages = [
    {"role": "system", "content": "...(stable + context + volatile)"},
    {"role": "user", "content": "你好"},
    {"role": "assistant", "content": "嗨..."},
    {"role": "user", "content": "[Skill activated: apple-reminders]\n# Apple Reminders\n..."},
    # ↑ 新加的 — 不动前面任何一条
]

cache 命中分析:

这就是 Hermes 工程的核心智慧 把"上下文相关的能力"放进 user message 而不是 system prompt。 System prompt 像操作系统镜像,user message 像应用程序。装应用不该重启系统。 这一招让 Hermes 能动态加载 skill 而不付 cache miss 代价。

对比:错误做法

如果你天真地用"system prompt 加 skill":

# BAD
def load_skill(skill_name):
    agent.system_prompt += f"\n\n--- Skill: {skill_name} ---\n{SKILL_MD_CONTENT}"
    # 立刻破 cache

下次 API call,前缀完全变了。整个 cache 报废。一个简单 turn 可能成本 ×10。 这就是为什么 Hermes 选择"以 user message 注入"——不要碰前缀

8.6Skill 的高级特性

模板变量

SKILL.md 里可以写 {{config.something}},加载时被替换:

# Send Email via SMTP

Use SMTP server at {{config.smtp.host}}:{{config.smtp.port}}.
Auth user: {{config.smtp.user}}

变量来源是 config.yamlskills.config.<key>

Inline Shell 展开(实验功能)

SKILL.md 里的 $(command) 块在加载时被替换成 shell 输出:

# Project Context

Current branch: $(git branch --show-current)
Recent commits:
$(git log --oneline -5)

默认关(inline_shell: false),有安全顾虑。打开后注意: 这相当于让 SKILL.md 在加载时执行任意命令。只用于自己写的 skill。

Skill 附带 scripts

真正复杂的逻辑不该让 LLM 每次重新生成。把它存成脚本:

~/.hermes/skills/research/web_scrape/
├── SKILL.md                # 决策手册
├── scripts/
│   ├── extract_main.py    # 提取主内容的复杂 BeautifulSoup 逻辑
│   └── dedupe_urls.py
└── references/
    └── selectors.json     # 常见网站的 CSS 选择器

SKILL.md 里只说 "用 scripts/extract_main.py 提取主内容",LLM 会用 execute_codeterminal 工具执行这个脚本。Hermes 在 execute_code 工具的 working directory 里把 scripts/ 加入 PATH。

为什么用脚本而不是让模型每次写 稳定性——同样的逻辑每次生成可能有细微差异,调试困难。 token 成本——一段 200 行的 BeautifulSoup 代码每次都生成是浪费。 测试——脚本可以单独跑 unit test,工具调用没法。 用脚本是程序性记忆的"实弹",SKILL.md 是"使用手册"

8.7Skill 的来源与生态

Hermes 的 skills 有三个来源:

① 内置 (skills/)

仓库自带的,~30 个分类:

skills/
├── apple/                  apple-reminders, imessage
├── research/               web-deep-research, fact-check
├── devops/                 ci-cd-status, k8s-debug
├── data-science/           pandas-eda, sklearn-baseline
├── software-development/   conventional-commit, code-review
├── mcp/                    mcp-server-init
├── mlops/                  mlflow-experiment, training-monitor
└── ...

② 用户自创 (~/.hermes/skills/)

你随时可以扔一个 SKILL.md 在这个目录下,Hermes 重启自动发现。 或者让 Agent 自己用 skill_manage(action="create") 工具创建——这是核心 self-improving 机制。

③ Skills Hub (远程仓库)

Hermes 维护一个公开 skill 索引在 agentskills.io。 用户用 hermes skills install <name> 装。

这个标准被多个 Agent 框架支持(Claude Desktop、Aider、Cursor)—— 写一份 SKILL.md 可以被所有兼容工具用上。这是"skill 互操作性"方向,Hermes 团队主推。

8.8Agent 自己创建 Skill

最有趣的部分:Agent 自己学新本事并把它存下来。 Hermes 提供 skill_manage 工具:

# LLM 决定创建一个新 skill
skill_manage(
    action="create",
    name="data-processing-pipeline",
    skill_md="""---
name: data-processing-pipeline
description: 'Generic CSV-to-Parquet ETL with pandas/polars.'
version: 1.0.0
author: Hermes Agent
metadata:
  hermes:
    created_by: agent
    tags: [data, etl, pandas]
---

# Data Processing Pipeline

When the user asks to clean a CSV and produce Parquet output:

## Steps

1. Use read_file to inspect the CSV schema.
2. Use execute_code to run polars (faster than pandas for > 1GB):
   ```python
   import polars as pl
   df = pl.read_csv(input_path, ignore_errors=True)
   df.write_parquet(output_path, compression='zstd')
   ```
3. Verify with df.describe() ...
""",
)

触发条件由 LLM 自己判断——通常在 system prompt 里有这样一段建议:

After completing a non-trivial task that required figuring out a workflow, consider whether the pattern is general enough to be saved as a skill. Use skill_manage(action="create", ...) when you think future you would benefit from the recipe.
system_prompt 里的 skill creation hint

关键元数据:created_by: agent——标记这是 Agent 自创, 为下章要讲的 Curator 提供归属信息。Curator 只管理 agent-created skill。

8.9Hermes Skill vs Voyager Skill

VoyagerHermes
Skill 是什么 可执行 JavaScript 函数 Markdown 决策手册(可附脚本)
检索方式 向量相似度 显式 /skill-name + LLM 自己 skill_view
iterative refine 报错回灌 → LLM 改代码 Curator 定期审查 + LLM skill_manage(action="patch")
使用环境 Minecraft(封闭世界) 用户自己的电脑(开放世界)
可读性 代码——只机器懂 Markdown——人和机器都懂、能编辑
互操作性 专属 Voyager 实现 跨 Agent 框架(Claude/Cursor/Hermes)

Hermes 这个设计的取舍:放弃 Voyager 的全自动化,换来用户可审计、可编辑、可分享。 对生产用户来说,"Agent 自己写了什么 skill"是必须可看的;不可读的代码会让人不敢信。

8.102025.12 — Skill 成为跨厂家开放标准

本章前面把 SKILL.md 当作"Hermes 内部约定"讲。2025 年 12 月一个事件改写了它的地位

Industry Standard
Anthropic Agent Skills Specification (Open Standard)
Anthropic · 2025-12 · github.com/anthropics/skills

Anthropic 把 SKILL.md 格式作为开放标准发布并开源 reference 实现。 格式核心:YAML frontmatter + Markdown body + 可选 scripts/ 目录。 OpenAI 在两周内宣布 Codex CLI 和 ChatGPT 采用同一格式。 这是继 MCP 之后第二个跨厂家 Agent 标准。

标准化之后的生态形态

2026 年 5 月时,Agent Skills 生态已经稳定下来,主要节点:

角色例子定位
标准 owner Anthropic anthropics/skills spec + reference skills(PDF / PPTX / DOCX 等)
免费 marketplace agentskills.io Hermes 早期支持的、社区免费 skill 索引
商业 marketplace agensi.io · skillsmp.com 付费 skill;作者拿 80% 分成
Host(支持加载) Claude Desktop / Claude Code / Codex CLI / ChatGPT / Cursor / Zed / Hermes 同一份 SKILL.md 在每个 host 都能用
分发协议 Claude Plugin Marketplace(/plugin 三合一:MCP server + Skill + plugin

Hermes 的 SKILL.md 是否兼容标准?

几乎完全兼容——因为 Hermes 的格式就是参考标准(之一)。 Hermes 维护者参与了 standard 早期讨论。差异点不到 5%:

实战建议 写新 skill 时避开 Hermes 私有字段(metadata.hermes.config 等)。 这样这份 SKILL.md 可以扔给 Claude Code 或 ChatGPT 直接用,不光在 Hermes 里跑。 Skill 互操作性是这个领域接下来最值钱的资产。

Agent Skills 与 MCP 的分工

同期 Anthropic 还推 MCP。两者经常被混淆,区别看一眼就清楚:

MCP ServerAgent Skill
形态 外部进程(JSON-RPC) SKILL.md + scripts 文件
提供 Tools / Resources / Prompts 决策手册 + 可选脚本
调用方式 JSON-RPC tools/call 把 SKILL.md 注入到 LLM context
典型例子 GitHub MCP(封装 GitHub API) conventional-commit 风格手册
状态保持 进程持续运行,可有状态 无状态,文件只读
语言 任意(Python / TS / Rust / Go) Markdown + 任意脚本语言
分发 NPM / Pip / Binary Git repo / marketplace tarball

记忆口诀MCP 是"接什么外部系统",Skill 是"怎么用这些系统"。 一个 Excel skill 配 Excel MCP server——前者教 LLM "什么时候要做透视表", 后者提供 "如何调 Excel API 真的去做"。

Voyager → 标准化:六年的路

把整条路线压成时间线:

timeline
  title Procedural Memory 在 Agent 中的演进
  section 概念期
    2023.05 : Voyager : 可执行代码 + 自动课程
    2023.10 : MemGPT : OS 类分层记忆
  section 工程化
    2024.06 : Hermes 原型 : SKILL.md 内部约定
    2025.01 : agentskills.io : 第一个跨工具免费 marketplace
    2025.06 : Anthropic Context Engineering : 把 skill 正名为"程序性记忆"工程实践
  section 标准化
    2025.12 : Anthropic Agent Skills spec : 开放标准发布
    2026.01 : OpenAI Codex CLI + ChatGPT : 采用同一格式
    2026.02 : agensi.io / skillsmp.com : 付费 marketplace 上线 (80/20 分账)
    2026.04 : Pre-built skills : Anthropic 自发 PowerPoint / Excel / Word / PDF skill

下一节我们看在标准化之后,怎么写一份跨工具兼容的 SKILL.md。

8.11SKILL.md 的写作规范

Hermes 的 AGENTS.md 里有八条 skill 写作硬规则。这些规则是 PR 评审标准:

  1. description ≤ 60 字符,一句话,句号结尾。 避免营销词。
  2. 引用工具时用 backtick,且必须是 Hermes 原生工具或 MCP server 名。 不要写 "use grep",写 "use search_files"
  3. platforms gating 要和实际脚本 import 一致。 POSIX-only 的代码必须声明 platforms。
  4. author 写真人,不写 "Hermes Agent"。
  5. section 顺序固定:标题 → 简介 → When to Use → Prerequisites → How to Run → Quick Reference → Procedure → Pitfalls → Verification。
  6. 复杂逻辑放 scripts/,不要让 LLM inline 写。
  7. 测试在 tests/skills/test_<skill>_skill.py,只用 stdlib + pytest + unittest.mock。
  8. .env.example 添加要清晰隔离,不污染周围内容。
学到了写作的方法学 上面 8 条本质都是"让 LLM 容易理解、让人容易审计、让 PR 容易合并"。 你自己写 skill 时也照做。写 skill 是给 LLM 写说明书, 跟写代码注释类似——细节越具体越好。

8.12本章带走的

章末练习

  1. Easy 为什么 Skill 必须作为 user message 注入,而不是 system prompt?用 50 字回答。
  2. Easy 程序性记忆和陈述性记忆的核心区别是什么?分别给一个 Agent 应用场景。
  3. Medium 给一个具体场景:你天天用 kubectl logs 看某个特定 namespace 的 pod 日志, 要按时间、grep 关键字、过滤掉 healthcheck。写一份完整的 SKILL.md,遵守八条写作规则。
  4. Medium Voyager 的 "iterative prompting" 在 Hermes 里对应什么机制? (提示:考虑 Curator 和 skill_manage(action="patch")
  5. Hard Hermes 的 Skill 没有"自动检索"——LLM 不会"在 100 个 skill 里挑相关的"。 为什么这种设计权衡值得?设想一个 Agent 有 500 个 skill 时会怎样。 然后想想:如何加一个"显式向量检索"机制而不破 cache?
  6. Hardtools/skill_usage.py(如果你下载了 Hermes 源码)。它存 ~/.hermes/skills/.usage.json 里。 解释 created_by: agent 标记从哪里写入、被谁读取。这条数据链路对下章的 Curator 极重要。