IronClaw 学习笔记 05:Skills 系统

SKILL.md 格式兼容、多目录发现、按需加载模式、三种设计流派对比

March 12, 2026·13 min read·Yimin
#Rust#AI#Agent#IronClaw#学习笔记#Skills#Cursor

上一篇我们给了 Agent 安全防护。但一个安全的 Agent 如果只会做固定的几件事,就像一个只会背课文的学生。

🎯 这一篇要解决什么问题?

Phase 04 之后,tiny-claw 的能力是硬编码的——所有工具在启动时注册,所有行为在编译时决定。想让 Agent 做新的事?改代码、重编译。

但在 Cursor 里,你只需要写一个 Markdown 文件扔到 skills/ 目录里,Agent 就能学会新技能——审查代码、操作数据库、合并 PR、搜索日志。这就是 Skills 系统的威力。

真实场景:

┌─────────────────────────────────────────────────────────────┐
│  skills/ 目录里放着多个技能:                                  │
│                                                             │
│  db-query/SKILL.md     → "Query databases using CLI tools"   │
│  ship/SKILL.md         → "One-click create and merge PR"    │
│  log-search/SKILL.md   → "Search application logs"          │
│  deploy-check/SKILL.md → "Check deployment status"          │
│  ...                                                        │
│                                                             │
│  用户说: "帮我查一下 users 表的数据"                           │
│  Agent 扫描所有技能的描述 →                                   │
│    db-query/SKILL.md 匹配 → 读取完整内容 →                    │
│    按照里面的步骤连接数据库、执行查询、输出结果                 │
│                                                             │
│  这一切不需要改代码,只需要写 Markdown。                       │
└─────────────────────────────────────────────────────────────┘

问题是:目前至少有三种 Agent Skills 实现——IronClaw、Cursor/Trae、OpenClaw。它们的 SKILL.md 格式和加载机制各有差异。tiny-claw 应该兼容哪个?

答案是:兼容 Cursor 格式作为基线,借鉴 OpenClaw 的门控机制,参考 IronClaw 的信任模型


🧠 三种实现流派对比

在写 tiny-claw 之前,先对比三种现有实现,理解它们的设计选择。

SKILL.md 格式

Cursor/TraeOpenClawIronClaw
Frontmattername + descriptionname + description + metadata.openclawname + description + activation
BodyMarkdown 提示词Markdown 提示词Markdown 提示词
激活关键词无(靠 description)无(靠 description)keywords + patterns + tags
门控requires.bins/env/configrequires.bins/env/config
安装规格install (brew/node/go)
必需字段name, descriptionname, descriptionname

Cursor 的格式最简单——只有 namedescription 两个字段。OpenClaw 在此基础上加了 metadata.openclaw 做门控和安装。IronClaw 加了 activation 做精确匹配。

技能选择机制

这是三种实现最大的分歧点:

┌─────────────────────────────────────────────────────────────┐
│                技能选择:两种根本不同的策略                     │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  策略 A:代码评分选择 (IronClaw)                              │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 用户消息                                             │   │
│  │    │                                                 │   │
│  │    ▼                                                 │   │
│  │ prefilter_skills()                                   │   │
│  │ • 关键词精确匹配 → +10 分                             │   │
│  │ • 关键词子串匹配 → +5 分                              │   │
│  │ • 标签匹配 → +3 分                                   │   │
│  │ • 正则匹配 → +20 分                                  │   │
│  │    │                                                 │   │
│  │    ▼                                                 │   │
│  │ 按分数排序 → 取 Top-N → 将 body 注入系统提示词         │   │
│  │                                                     │   │
│  │ 优点: 零 LLM 调用、确定性、可调试                     │   │
│  │ 缺点: 每个技能要精心配 keywords/patterns,维护成本高   │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  策略 B:列出 + 按需读取 (Cursor / OpenClaw)                 │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 启动时                                               │   │
│  │ • 扫描所有 SKILL.md → 提取 name + description        │   │
│  │ • 构建 <available_skills> 列表 → 放入系统提示词       │   │
│  │                                                     │   │
│  │ 每条消息                                              │   │
│  │ • LLM 扫描 <available_skills> 的 description          │   │
│  │ • 如果匹配 → 用 read 工具读取 SKILL.md 完整内容       │   │
│  │ • 按照技能指令执行                                    │   │
│  │                                                     │   │
│  │ 优点: 零配置、description 就够、LLM 自己判断最合适的  │   │
│  │ 缺点: 依赖 LLM 判断力、技能列表占用系统提示词空间     │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Cursor 和 OpenClaw 都选了策略 B——让 LLM 自己决定用哪个技能。这很合理:

  1. LLM 比关键词匹配更懂语义——"帮我看看这个 PR" 能匹配 "code-review" 技能,即使没配关键词
  2. 零维护成本——写好 description 就行,不需要穷举触发词
  3. 按需读取省 token——只有真正用到的技能才被读入上下文

IronClaw 选策略 A 有它的道理——在聊天机器人场景中 LLM 调用是收费的,确定性选择避免了额外开销。但对个人助手来说,策略 B 更实用。

发现目录

Cursor/TraeOpenClawIronClaw
项目级.cursor/skills/workspace/skills/workspace/skills/
用户级~/.cursor/skills/~/.openclaw/skills/~/.ironclaw/skills/
安装级~/.cursor/skills-cursor/多个插件目录~/.ironclaw/installed_skills/
优先级用户 > 项目后来源覆盖先来源Trusted > Installed

目录布局都是 <skill-name>/SKILL.md——这一点是统一的。

上下文注入方式

┌─────────────────────────────────────────────────────────────┐
│              上下文注入:Eager vs Lazy                         │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  IronClaw (Eager):                                          │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ system prompt:                                       │   │
│  │   ... 基础提示词 ...                                  │   │
│  │   <skill name="rust-reviewer" trust="trusted">       │   │
│  │     (完整的 SKILL.md body,可能数千字)                 │   │
│  │   </skill>                                           │   │
│  │   <skill name="git-helper" trust="trusted">          │   │
│  │     (又一个完整的 body)                                │   │
│  │   </skill>                                           │   │
│  │                                                     │   │
│  │ 每条消息都带着匹配到的技能全文 → 占用上下文窗口       │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  Cursor / OpenClaw (Lazy):                                  │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ system prompt:                                       │   │
│  │   ... 基础提示词 ...                                  │   │
│  │   ## Skills                                          │   │
│  │   <available_skills>                                 │   │
│  │     <skill>                                          │   │
│  │       <name>db</name>                                │   │
│  │       <description>Query PostgreSQL...</description> │   │
│  │       <location>skills/db/SKILL.md</location>        │   │
│  │     </skill>                                         │   │
│  │     <skill>                                          │   │
│  │       <name>ship</name>                              │   │
│  │       <description>One-click PR...</description>     │   │
│  │     </skill>                                         │   │
│  │     ... (只有 name + description,几十字)             │   │
│  │   </available_skills>                                │   │
│  │                                                     │   │
│  │ 模型判断需要哪个 → 用 read 工具读取对应 SKILL.md      │   │
│  │ → 只有真正用到的技能占用上下文                         │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Lazy 模式的优势在于可扩展性——Cursor 支持 150 个技能同时列出,因为每个只占一行名字 + 描述。IronClaw 的 Eager 模式在 3-5 个技能时可行,但到 150 个就不可能了。


📦 SKILL.md 格式:兼容设计

tiny-claw 的 SKILL.md 格式目标:一份文件,三个平台都能用

最小格式(Cursor 兼容)

---
name: my-skill
description: What this skill does. Use when user says X or wants to do Y.
---

# My Skill

## Instructions
Step-by-step guidance...

这是 Cursor 的标准格式,也是 tiny-claw 的基线。只需 name + description 两个字段。

扩展格式(OpenClaw 兼容门控)

---
name: github
description: "GitHub operations via gh CLI..."
metadata:
  {
    "openclaw": {
      "requires": { "bins": ["gh"] },
      "install": [{ "id": "brew", "kind": "brew", "formula": "gh" }]
    }
  }
---

tiny-claw 解析 metadata.openclaw.requires 做运行时门控(检查 bins/env),但不处理 install 规格。

字段说明

字段必需来源说明
nameCursor技能标识符,小写字母/数字/连字符,≤64 字符
descriptionCursor技能描述,包含 WHAT + WHEN,≤1024 字符
metadata.openclaw.requires.binsOpenClaw必须在 PATH 上的二进制
metadata.openclaw.requires.envOpenClaw必须存在的环境变量

为什么不用 IronClaw 的 activation 字段?

IronClaw 的 activation.keywords/patterns/tags 是为代码评分设计的。tiny-claw 采用 Cursor/OpenClaw 的策略——让 LLM 根据 description 自行判断。所以不需要这些字段。

但 tiny-claw 的解析器会忽略不认识的字段,不会报错。这意味着带 activation 字段的 IronClaw SKILL.md 也能在 tiny-claw 中加载——只是 activation 字段不会生效。

description 的写法

description 是技能选择的唯一依据——LLM 通过它判断技能是否适用。好的 description 需要:

原则好例子坏例子
包含 WHAT"Query databases using CLI tools""Database helper"
包含 WHEN"Use when user says /db"(缺少触发条件)
第三人称"Processes Excel files and generates reports""I can help you with Excel"
包含触发词"mentions PR merge, or says merge pr""handles PRs"

🔍 多目录发现

tiny-claw 扫描多个目录,兼容 Cursor 的路径约定:

┌─────────────────────────────────────────────────────────────┐
│                   技能发现路径                                 │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  优先级(后来源覆盖先来源,同名技能取后者):                    │
│                                                             │
│  1. 用户级技能                                                │
│     ~/.tiny-claw/skills/         (手动放置)                  │
│                                                             │
│  2. 项目级技能                                                │
│     <workspace>/skills/          (项目共享,最高优先级)       │
│                                                             │
│  复用外部技能:通过软链接植入                                  │
│     ln -s ~/.cursor/skills/db skills/db                     │
│     ln -s ~/.cursor/skills/ship ~/.tiny-claw/skills/ship    │
│                                                             │
│  布局:                                                       │
│  <dir>/                                                     │
│  ├── skill-a/                                               │
│  │   └── SKILL.md                                           │
│  ├── skill-b/                                               │
│  │   ├── SKILL.md                                           │
│  │   ├── reference.md    (可选,技能可以引用)                 │
│  │   └── scripts/        (可选,工具脚本)                    │
│  └── ...                                                    │
│                                                             │
│  同名技能: 后发现的覆盖先发现的。                              │
│  项目级 > 用户级                                              │
│                                                             │
└─────────────────────────────────────────────────────────────┘

为什么用软链接而不是直接扫描外部目录?

tiny-claw 只扫描自己的两个目录,不主动读取 ~/.cursor/skills/ 等外部路径。用户通过 ln -s 软链接按需引入,好处是:

  • 显式控制——你选择哪些技能植入,而不是全部加载
  • 不耦合——不依赖其他工具的目录约定
  • 零迁移成本——一条 ln -s 命令即可复用已有技能

门控检查

对于带 metadata.openclaw.requires 的技能,tiny-claw 在发现阶段做门控:

发现 github/SKILL.md
  → 解析 requires.bins: ["gh"]
  → which gh → 找到 /usr/local/bin/gh → ✅ 通过
  → 加入可用技能列表

发现 docker-ops/SKILL.md
  → 解析 requires.bins: ["docker"]
  → which docker → 未找到 → ❌ 跳过
  → 日志: "Skill 'docker-ops' skipped: binary 'docker' not found"

门控不满足的技能直接跳过,不出现在 <available_skills> 列表中。这比运行时报错"找不到命令"体验好得多。


🚀 按需加载:List → Read → Execute

这是整个 Skills 系统的核心模式,来自 Cursor 和 OpenClaw 的实践:

┌─────────────────────────────────────────────────────────────┐
│              Skills 工作流程                                   │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ═══ 启动时 ═══                                              │
│                                                             │
│  Step 1: 扫描目录 → 发现 SKILL.md 文件                       │
│  Step 2: 解析 frontmatter (name + description)               │
│  Step 3: 门控检查 (bins/env)                                 │
│  Step 4: 构建 skills prompt:                                 │
│                                                             │
│  <available_skills>                                         │
│    <skill>                                                  │
│      <name>db-query</name>                                   │
│      <description>Query databases using CLI tools.          │
│        Use when user says "/db".</description>              │
│      <location>/Users/me/project/skills/db/SKILL.md</loc>   │
│    </skill>                                                 │
│    <skill>                                                  │
│      <name>ship</name>                                      │
│      <description>One-click create and merge PR.            │
│        Use when user says "/ship".</description>            │
│      <location>/home/me/.tiny-claw/skills/ship/SKILL.md</l> │
│    </skill>                                                 │
│    ... (每个技能只占几行)                                     │
│  </available_skills>                                        │
│                                                             │
│  ═══ 每条消息 ═══                                            │
│                                                             │
│  Step 5: 系统提示词中包含:                                    │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ ## Skills                                            │   │
│  │ Scan <available_skills> descriptions.                │   │
│  │ If one matches: read its SKILL.md, then follow it.   │   │
│  │ If none match: do not read any SKILL.md.             │   │
│  │                                                     │   │
│  │ <available_skills>...</available_skills>              │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  Step 6: LLM 判断 → 调用 read_file 读取匹配的 SKILL.md       │
│  Step 7: LLM 按照 SKILL.md 中的指令执行任务                  │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 关键: SKILL.md 的完整内容只在被选中时才进入上下文。    │   │
│  │ 20 个技能的列表只占 ~2000 字符。                       │   │
│  │ 而一个技能的完整内容可能 5000+ 字符。                  │   │
│  │ Lazy 比 Eager 省了 10 倍上下文空间。                   │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

系统提示词中的 Skills 指令

OpenClaw 的实现非常精炼(参考 system-prompt.ts):

## Skills (mandatory)
Before replying: scan <available_skills> <description> entries.
- If exactly one skill clearly applies: read its SKILL.md at <location> with read_file, then follow it.
- If multiple could apply: choose the most specific one, then read/follow it.
- If none clearly apply: do not read any SKILL.md.
Constraints: never read more than one skill up front; only read after selecting.

<available_skills>
  ...
</available_skills>

关键设计点:

  1. "mandatory"——强制 LLM 在回复前先扫描技能列表
  2. "never read more than one skill up front"——防止 LLM 贪婪地读所有技能,浪费 token
  3. "only read after selecting"——先判断再读,不是先读再判断

read_file 工具的角色

在这种模式下,read_file 工具不仅用于读文件,还是 Skills 系统的核心组件——它是 LLM 获取技能完整内容的唯一通道。tiny-claw 已有 read_file 工具(Phase 01),所以这里零新增工具就能实现 Skills。


📝 tiny-claw Phase 05 的设计决策

维度IronClawCursor/OpenClawtiny-claw (Phase 05)
SKILL.md 格式activation 字段name + descriptionCursor 格式 + OpenClaw 门控 ✅
选择机制代码评分 (prefilter)LLM 自行判断LLM 自行判断 ✅
注入方式Eager(body 注入上下文)Lazy(列表 + 按需读取)Lazy ✅
发现目录3 个 ironclaw 目录Cursor/OpenClaw 目录兼容 Cursor 路径 ✅
门控检查bins/env/configbins/env/config + OSbins/env ✅
信任模型Trusted/Installed暂不实现
工具衰减只读白名单暂不实现
技能限制max_active + max_tokensmaxSkillsInPrompt (150)max_skills_in_prompt (100)

关键决策:

  1. 兼容 Cursor 格式——绝大多数用户已有 Cursor 技能文件,零迁移成本
  2. Lazy 加载模式——更省 token,支持更多技能,借助已有的 read_file 工具
  3. 门控从 OpenClaw 借鉴——metadata.openclaw.requires.bins/env 在加载时检查
  4. 安全模型推迟——先让技能能用起来,Trust/Attenuation 后续再加
  5. 不实现注册表——只支持本地文件,在线安装后续再做

🧪 动手试试

Phase 05 之后 tiny-claw 能加载和使用技能。试试这些场景:

场景操作观察点
软链接复用技能ln -s ~/.cursor/skills/db skills/db软链接的技能出现在列表中
创建项目技能skills/my-skill/SKILL.md 写一个技能/skills 命令看到新技能
触发技能发送匹配 description 的消息Agent 读取 SKILL.md 并按指令执行
门控检查在 SKILL.md 中设 requires.bins: ["nonexistent"]技能不出现在可用列表中
多技能共存准备 10+ 个技能列表正确显示,只有匹配的被读取
同名覆盖项目级和用户级有同名技能项目级覆盖用户级

📝 本篇总结

理解项描述
三种流派IronClaw(代码评分)、Cursor/OpenClaw(LLM 自选)
SKILL.md 格式name + description 两个必需字段(Cursor 兼容)
门控扩展metadata.openclaw.requires.bins/env(OpenClaw 兼容)
description 写法第三人称、包含 WHAT + WHEN、具体触发词
多目录发现tiny-claw 用户级 → Cursor 用户级 → 项目级(后覆盖前)
Lazy 加载列表只放 name + description + location,LLM 按需读
系统提示词<available_skills> XML 列表 + Skills 指令段落
read_file 复用不需新工具,现有 read_file 就是技能加载通道
门控检查bins/env 不满足 → 技能从列表移除,不会运行时报错
兼容性同一份 SKILL.md 在 Cursor、OpenClaw、tiny-claw 都能用

核心洞察

1. LLM 是最好的技能选择器

IronClaw 精心设计了关键词评分算法来选择技能。但 Cursor/OpenClaw 证明了一个更简单的方案——让 LLM 自己读 description 来决定。LLM 理解语义,关键词匹配只是文本比较。一个好的 description 比一堆 keywords 更有效。

2. Lazy > Eager

把匹配的技能全文塞进系统提示词(Eager)是直觉做法,但 Lazy 模式(只列出名字和描述,按需读取)在工程上更优——支持 100+ 技能、节省 token、利用已有工具。当 LLM 有 read_file 能力时,没必要提前把所有可能需要的内容都塞进上下文。

3. 生态兼容比创新更重要

tiny-claw 可以设计一个更"先进"的 SKILL.md 格式,但兼容 Cursor 的简单格式意味着用户能直接复用已有的 20+ 个技能。在 AI 工具生态中,兼容性就是功能


下一篇IronClaw 学习笔记 06:多渠道 + Web —— Channel trait 统一抽象、Web Gateway 设计、SSE/WebSocket 实时推送。