OpenClaw Skills 架构详解:从源码看 Skill 的加载与执行

OpenClaw Skills 架构详解:从源码看 Skill 的加载与执行

最近在研究 OpenClaw 的源码,发现它的 Skill 系统设计得非常精巧。今天来和大家聊聊 Skill 的底层原理,搞清楚我们平时用的 /skill-name 到底经历了什么。

一、Skill 是什么?

一句话概括:Skill 是 OpenClaw 中基于 Markdown 文件的”可调用能力单元”

关键设计点来了:Skill 本身不包含可执行代码,它只是一段指令文本。最终”执行”它的是 LLM 或者一个已注册的 Tool。

用户输入 /skill-name <args>

        ├─ Skill 带 tool dispatch ──→ 直接调用对应 Tool(不走 LLM)

        └─ Skill 无 tool dispatch ──→ 重写消息 + 注入清单 → LLM 读 SKILL.md → 按指令办事

也就是说,Skill 其实有两条执行路径,一条绕过 LLM,一条经过 LLM。这个设计非常灵活。

二、SKILL.md 文件结构

每个 Skill 的核心就是一个 SKILL.md 文件,它的 frontmatter 定义了 Skill 的元数据:

---
name: my-skill
description: Do something useful
command-dispatch: tool        # 可选:直接路由到工具(跳过 LLM)
command-tool: sessions_send   # 可选:配合 command-dispatch,指定工具名
command-arg-mode: raw         # 可选:参数传递模式
disable-model-invocation: false  # 可选:是否从系统 prompt 中隐藏
user-invocable: true          # 可选:是否允许用户通过 / 调用
metadata:                     # 可选:加载门控
  { "openclaw": { "requires": { "bins": ["uv"], "env": ["API_KEY"] } } }
---

正文部分就是 LLM 读取后要执行的指令内容。

三、扫描与加载流程

OpenClaw 启动时会按照优先级从低到高扫描多个目录:

extra skills          ← 插件自带的 skill 目录
bundled skills        ← 内置 skills(skills/ 目录)
managed skills        ← 托管目录
~/.agents/skills      ← 个人级
<workspace>/.agents/skills  ← 项目级
workspace skills      ← 工作区根目录(最高优先级)

同名 Skill 按优先级覆盖,高优先级的同名 skill 替换低优先级的。这个设计类似于 CSS 的层叠规则,非常直觉。

加载代码链路

loadSkillsFromDirSafe()                    ← local-loader.ts
  ├─ listCandidateSkillDirs()              ← 读目录,过滤隐藏目录
  └─ loadSingleSkillDirectory()            ← local-loader.ts
       ├─ readSkillFileSync()              ← 读 <skillDir>/SKILL.md
       ├─ parseFrontmatter()               ← 解析 YAML frontmatter
       └─ resolveSkillInvocationPolicy()   ← 读 invocation 配置

loadSkillEntries()                         ← workspace.ts
  └─ 按上述优先级合并所有源的 SkillEntry

四、执行路径 A:Tool Dispatch(绕过 LLM)

当 Skill 的 frontmatter 中声明了 command-dispatch: toolcommand-tool 时,走的是完全不经过 LLM 的路径:

// get-reply-inline-actions.ts

// 1. 读取 dispatch 配置
dispatch = skillInvocation.command.dispatch
// { kind: "tool", toolName: "sessions_send", argMode: "raw" }

// 2. 加载工具运行时
const tools = createOpenClawTools({ ... })
const tool = tools.find(c => c.name === dispatch.toolName)

// 3. 直接执行(不经过 LLM)
const result = await tool.execute(toolCallId, {
  command: rawArgs,
  commandName: skillName,
}, abortSignal)

// 4. 提取结果并直接回复
const text = extractTextFromToolResult(result) ?? "✅ Done."

关键:此路径下 SKILL.md 的正文内容完全不参与,用户消息也不进 LLM。执行确定性是 100%。

五、执行路径 B:LLM 驱动(两段式 Lazy Load)

这是更常见的路径,分为两个阶段:

阶段一:确定性代码(注入 prompt)

Step 1 - 消息体改写

// get-reply-inline-actions.ts
ctx.Body = `Use the "<skillName>" skill for this request.\n\nUser input:\n${args}`

Step 2 - 系统 prompt 装配

系统 prompt 中会注入一段 Skills 指令:

## Skills (mandatory)
Before replying: scan <available_skills> <description> entries.
- If exactly one skill clearly applies: read its SKILL.md at <location> with `Read`, then follow it.
- If multiple could apply: choose the most specific one...
- If none clearly apply: do not read any SKILL.md.

加上一个 <available_skills> 清单,格式如下:

<available_skills>
  <skill>
    <name>weather</name>
    <description>Get current weather data</description>
    <location>/path/to/weather/SKILL.md</location>
  </skill>
</available_skills>

只包含 name / description / location(文件绝对路径),SKILL.md 正文不进 prompt。

阶段二:模型驱动(读取并执行 SKILL.md)

LLM 看到系统 prompt 后:

  1. 扫描 <available_skills>,匹配到指定的 skill
  2. 调用 Read 工具,读取 <location> 给出的 SKILL.md 绝对路径
  3. 读到 SKILL.md 正文内容
  4. 按正文内容继续后续工具调用、产出回复

六、两条路径对比

维度路径 A(Tool Dispatch)路径 B(LLM 驱动)
触发条件command-dispatch: tool + command-tool无 tool dispatch
是否走 LLM❌ 完全不走✅ 必须走
SKILL.md 正文是否参与❌ 完全忽略✅ LLM 读取后按内容执行
执行确定性100% 确定取决于模型理解
延迟快(直接执行)慢(需多一轮模型交互)

七、实际应用中的 Skill 管理

在桌面端(如 WujieAI),Skill 的管理更加复杂,引入了 UI 隐藏 + Runtime 可见的二层模型

Skill 的 Source 类型

source 值来源说明UI 标签
openclaw-bundled启动时同步进来的内置 Agent 专属技能内置
openclaw-managed用户通过本地 zip 安装用户安装
aipc-market从技能市场(远程)安装市场安装
agents-skills-personal个人技能个人技能
openclaw-extra拓展技能拓展技能

版本驱动同步

内置 Agent 技能通过比对 skill.json.version 决定是否覆盖:版本一致跳过,不一致才删除旧目录、复制新文件。同步失败保留旧版本,状态 failed-keep-old,不阻塞启动。

八、总结

OpenClaw 的 Skill 系统设计有几个值得学习的点:

  1. Markdown 即代码:Skill 不包含可执行代码,而是通过自然语言指令让 LLM 执行,这是一种全新的编程范式
  2. 双路径执行:Tool Dispatch 保证确定性,LLM 驱动保证灵活性
  3. 优先级覆盖:多层目录扫描 + 同名覆盖,类似 CSS 的层叠规则
  4. UI/Runtime 分离:隐藏的 skill 仍然对 Runtime 可见,保证功能完整

这种设计理念对我们理解 AI Agent 的架构有很大启发。下次再用 /skill-name 的时候,你就知道背后发生了什么了。


参考资料:

  • OpenClaw 源码:src/agents/skills/ 目录
  • src/auto-reply/reply/get-reply-inline-actions.ts