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: tool 和 command-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 后:
- 扫描
<available_skills>,匹配到指定的 skill - 调用
Read工具,读取<location>给出的 SKILL.md 绝对路径 - 读到 SKILL.md 正文内容
- 按正文内容继续后续工具调用、产出回复
六、两条路径对比
| 维度 | 路径 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 系统设计有几个值得学习的点:
- Markdown 即代码:Skill 不包含可执行代码,而是通过自然语言指令让 LLM 执行,这是一种全新的编程范式
- 双路径执行:Tool Dispatch 保证确定性,LLM 驱动保证灵活性
- 优先级覆盖:多层目录扫描 + 同名覆盖,类似 CSS 的层叠规则
- UI/Runtime 分离:隐藏的 skill 仍然对 Runtime 可见,保证功能完整
这种设计理念对我们理解 AI Agent 的架构有很大启发。下次再用 /skill-name 的时候,你就知道背后发生了什么了。
参考资料:
- OpenClaw 源码:
src/agents/skills/目录 src/auto-reply/reply/get-reply-inline-actions.ts