乐于分享
好东西不私藏

重读 OpenClaw:Skill 系统是如何工作的

重读 OpenClaw:Skill 系统是如何工作的

深入浅出 OpenClaw Skill 系统:AI 助手网关的”说明书”是如何工作的

这是「重读 OpenClaw」系列的第二篇,通过 12 个问题,从零揭开 OpenClaw 中 Skill 的定位、加载、执行与协作全貌。


前言

最近在深入研究 OpenClaw——一个个人 AI 助手网关项目。它最让我着迷的设计是 Skill 系统。一开始我以为 Skill 就是”插件”,后来发现完全不是。它更像是一套”说明书体系“——Skill 负责告诉 AI 做什么、怎么做,而真正干活的是另一套 Tool 体系

这篇文章以 Q&A 的形式,从最基础的概念开始,逐步深入到加载机制、执行路径、循环检测、插件协作,最后用一个完整场景把一切串起来。


一、入门:Skill 到底是什么?

Q1:一个 Skill 长什么样?

最简单的方式:打开一个真实的 Skill 文件看看。

skills/skill-creator/SKILL.md

---
name:
 skill-creator
description:
 "Create, edit, audit, tidy, validate, or restructure AgentSkills and SKILL.md files."
---

# Skill Creator


Skills
 are compact triggerable workflows...

## Shape


skill-name/

  SKILL.md
          ← 入口文件
  scripts/
          ← 可选脚本
  references/
       ← 可选参考文档
  assets/
           ← 可选资源文件

一个 Skill = 一个文件夹 + 一个 SKILL.md 文件。

  • • 文件头部的 --- 包裹的是 YAML 元数据(name、description、是否可被用户调用等)
  • • 下面是 指令正文(告诉 AI 遇到什么情况该怎么做)

核心认知:Skill 是纯文本的说明书,不是可执行代码。它不”运行”,而是被 AI “阅读后遵循”。


Q2:Skill 和 Tool 有什么区别?

这是最容易混淆的概念。用一个表格说清楚:

Skill
Tool
本质
说明书(Markdown 文本)
可执行代码(TypeScript 函数)
作用
告诉 AI 做什么、怎么做
实际执行操作
被谁使用
AI 模型阅读并遵循
AI 模型通过函数调用执行
例子 browser-automation/SKILL.md browser({ action: "open", url: "..." })

举个例子,browser-automation 这个 Skill 里写的是:

1. Check browser state: action="status"
2.
 Open tab: action="open", label="search"
3.
 Snapshot the page: action="snapshot"
4.
 Act: action="act" with a ref

这些指令教 AI 如何使用 browser 这个 Tool——按什么顺序调用、传什么参数、出错怎么恢复。Skill 本身不包含 browser 的实现代码。

一句话:Skill 是”菜谱”,Tool 是”锅和食材”。菜谱告诉你步骤,锅和食材真正把菜做出来。


Q3:那 Plugin 又是什么?三者什么关系?

Plugin 是集装箱,它可以同时打包 Skill 和 Tool,还可以打包更多东西。

看一个真实的 Plugin 声明——extensions/browser/openclaw.plugin.json

{
  "id"
: "browser",
  "contracts"
: {
    "tools"
: ["browser"]
  }
,
  "skills"
: ["./skills"],
  "commandAliases"
: [{ "name": "browser" }]
}

这一个 Plugin 同时提供了:

  • • Toolbrowser —— 真正可执行的浏览器自动化代码
  • • Skillbrowser-automation/SKILL.md —— 教 AI 怎么用 browser 的说明书

而 Plugin 还能承载更多:Provider(模型提供商)、Channel(消息渠道)、Hook(生命周期钩子)、MCP Server……多达 20+ 种能力类型。

┌────────────────────────────────────────────┐
│              Plugin(集装箱)                │
│                                            │
│  ┌──────────┐ ┌──────────┐ ┌────────────┐ │
│  │  Skills  │ │  Tools   │ │  其他能力   │ │
│  │ 说明书   │ │ 可执行   │ │  Provider  │ │
│  │ 纯文本   │ │ 代码     │ │  Channel   │ │
│  │          │ │          │ │  Hook      │ │
│  └──────────┘ └──────────┘ └────────────┘ │
└────────────────────────────────────────────┘

一句话:Plugin 是容器,Skill 是指令,Tool 是执行。三者不在一个维度上。


二、加载:Skill 怎么被发现和注入?

Q4:OpenClaw 从哪里找到 Skill?

这是整个系统最核心的问题。OpenClaw 从 7 个来源 扫描 Skill,按优先级从低到高:

📦 插件技能   →  ~/.openclaw/plugin-skills/      (最低优先级)
📦 额外目录   →  config 里配的 extraDirs
📦 内置技能   →  openclaw 自带的 skills/ 目录
📦 托管技能   →  ~/.openclaw/skills/              (从 ClawHub/Git 安装的)
📦 个人技能   →  ~/.agents/skills/                (你自己写的)
📦 项目技能   →  项目根/.agents/skills/           (团队共享的)
📦 工作区技能 →  项目根/skills/                   (最高优先级)

合并方式极其简单:用一个 Map<名字, 技能>,后面的覆盖前面的。

比如内置 skills/ 里有个 browser-automation,你的项目 <workspace>/skills/ 里也有一个同名但改过的版本。加载顺序是:

  1. 1. 先加载内置的 → Map 里有 browser-automation = 内置版
  2. 2. 再加载工作区的 → Map 里 browser-automation被覆盖成你的版本

结果:你的版本胜出。这就是”工作区覆盖内置”的全部秘密——不是复杂的优先级算法,就是 Map.set 的自然行为。


Q5:外部 Skill(插件、ClawHub、Git)是怎么进来的?

插件技能:插件在 openclaw.plugin.json 里声明 skills: ["./skills"],OpenClaw 启动时扫描插件目录,验证路径安全性,然后创建符号链接到 ~/.openclaw/plugin-skills/,之后走正常加载流程。

ClawHub 安装openclaw skills install <name> → HTTP 下载解压 → 放到 ~/.openclaw/skills/

Git 克隆安装openclaw skills install <git-url> → git clone → 放到 ~/.openclaw/skills/

关键点:不管技能从哪来,最终都是文件系统上的一个 SKILL.md 目录。进入文件系统后,加载管道一视同仁——不关心你是谁、从哪来的,只关心你有没有合法的 SKILL.md


Q6:加载完后怎么让 AI 知道有哪些 Skill?

Skill 列表被渲染成 XML 块,注入到系统提示词中:

<available_skills>
  <skill>

    <name>
browser-automation</name>
    <description>
Use when controlling web pages with the OpenClaw browser tool...</description>
    <location>
/home/user/.openclaw/plugin-skills/browser-automation/SKILL.md</location>
  </skill>

  <skill>

    <name>
gh-issues</name>
    <description>
Fetch GitHub issues, select candidates, spawn background fix agents...</description>
    <location>
/app/skills/gh-issues/SKILL.md</location>
  </skill>

</available_skills>

关键指令就一句话:

“Use the read tool to load a skill’s file when the task matches its description.”

翻译:“当你觉得某个技能的描述和当前任务有关时,自己去读那个文件。”

这就是**延迟加载(lazy loading)**的精髓——菜单只放”菜名+简介”(一行),AI 需要时才去读”完整菜谱”(整个文件),避免 token 爆炸。


三、执行:Skill 被选中后怎么”运行”?

Q7:AI 选中 Skill 后,具体怎么执行?

有三种执行路径:

用户说话
   │
   ├─ 普通聊天 → 【路径1:模型自动匹配】
   │              AI 看到 <available_skills>
   │              → 判断任务和某个 skill 匹配
   │              → Read(SKILL.md)
   │              → 按指令调用 Tool
   │
   └─ /skill名  → 【路径2:斜杠命令】
                   ├─ 普通模式:改写用户消息,让 AI 处理
                   └─ Tool Dispatch:绕过 AI,直接执行工具

路径1:模型自动匹配(最常用)

用户说”帮我打开浏览器搜天气”,AI 的推理过程:

“这个任务涉及浏览器操作,而且是多步骤流程。browser-automation 的描述是 ‘Use when controlling web pages…’,完全吻合。我先读它的 SKILL.md。”

然后 AI 在一个推理回合里:

→ Read("browser-automation/SKILL.md")          // 读到操作手册
→ browser({ action: "status" })                // 检查浏览器状态
→ browser({ action: "open", url: "https://baidu.com", label: "search" })  // 打开百度
→ browser({ action: "snapshot", targetId: "search" })  // 获取页面结构
→ browser({ action: "act", ... })              // 输入"天气"并搜索

Skill 不”运行”任何代码。 它改变的是 AI 的行为方式——读完后 AI 知道了正确的操作顺序、参数格式、错误恢复策略。

路径2A:普通斜杠命令

用户输入 /gh-issues --limit 5,消息在到达 AI 之前被拦截。OpenClaw 把用户消息改写为:

Use the "gh-issues" skill for this request.

User input:
--limit 5

然后这条改写后的消息 + 系统提示词(含 <available_skills>)一起发给 AI。AI 从改写消息中知道要用 gh-issues,从系统提示词中找到它的文件位置,然后 Read → 按指令执行。

路径2B:Tool Dispatch(绕过 AI)

如果 Skill 声明了 command-dispatch: tool 和 command-tool: message,用户输入 /my-status 时,OpenClaw 会:

  1. 1. 拦截消息
  2. 2. 识别 dispatch 配置
  3. 3. 直接调用 tool.execute()——AI 完全不知情

这个模式的特点:速度极快(不需要模型推理)、成本为零(不消耗 token),适合确定性操作。


Q8:Skill 能包含脚本吗?脚本怎么执行?

可以! Skill 的标准目录结构支持 scripts/ 文件夹。

例如 model-usage 这个 Skill,它的 SKILL.md 里写:

python {baseDir}/scripts/model_usage.py --provider codex --mode current

AI 读到后,做的事是:

AI: "需要运行 scripts/model_usage.py"
  → 调用 exec 工具:
    { "command": "python /path/to/skills/model-usage/scripts/model_usage.py --provider codex --mode current" }
  → exec 工具执行命令,返回 stdout/stderr
  → AI 看到结果,格式化展示给用户

关键点:脚本不是被 Skill 系统”自动执行”的。是 AI 读完 SKILL.md → 理解要做什么 → 主动调用 exec 工具 → 把脚本路径和参数传进去。脚本对框架来说完全是透明的——框架只知道 AI 调用了 exec 工具。


Q9:如果 Tool 调用名称写错或参数写错,怎么办?

错误被捕获后返回给模型,模型自我纠正。这是 ReAct 模式的核心优势。

但如果不加限制,模型可能陷入”犯错→重试→再犯错”的死循环。所以 OpenClaw 有5 层循环检测器

检测器
触发条件
警告阈值
阻断阈值
例子
unknown_tool_repeat
反复调用不存在的工具
10次
幻觉出 magic_fix 反复调
generic_repeat
相同工具+相同参数+相同结果
10次
20次
反复 grep 空结果
known_poll_no_progress
process poll 无进展
10次
20次
反复 poll 已结束的进程
ping_pong
两个模式交替往复无进展
10次
20次
A→B→A→B→A→B…
global_circuit_breaker
任何无进展重复
30次
最后的保险丝

检测原理:每个 tool call 被哈希为 工具名:参数哈希,执行结果也被哈希。循环检测器追踪最近 30 条历史,检查相同的 (工具名, 参数) 组合出现了多少次,结果是否一直相同(=没有进展)。

警告:tool call 仍然执行,但模型在返回结果里看到警告消息。阻断:tool call 被直接拦截,返回 { status: "blocked" },模型被迫放弃这条路。


Q10:ReAct 循环里会多次读取 Skill 吗?

可以,但通常不需要。系统提示词里写了:

“If a skill’s version differs from a previous turn, re-read its SKILL.md before using it.”

多次读取的场景:

  • • 版本变化:skill 文件被修改,版本号变了
  • • 上下文截断:长对话中上下文被压缩,模型忘记了 skill 内容
  • • 多 skill 协作:一个任务需要多个 skill

通常只读一次——读完内容就在上下文窗口里了。


Q11:我可以注册自定义 Tool 吗?

完全可以。 通过插件 API 的 registerTool 方法:

// 在插件中注册
api.registerTool({
  name
: "get_weather",
  description
: "Get current weather for a city",
  inputSchema
: {
    type
: "object",
    properties
: {
      city
: { type: "string", description: "City name" }
    },
    required
: ["city"]
  },
  async
 execute(toolCallId, params) {
    const
 { city } = params;
    const
 weather = await fetch(`https://api.weather.com/${city}`).then(r => r.json());
    return
 {
      content
: [{ type: "text", text: `${city}: ${weather.temp}°C` }]
    };
  }
});

Tool 需要满足的接口:

  • • name — 模型用这个名字来调用
  • • description — 告诉模型这个工具做什么
  • • inputSchema — 参数的 JSON Schema
  • • execute — 实际执行函数

注册后,Tool 经过完整的策略管道(profile → provider → global → agent → group → sender → sandbox → subagent),最终出现在模型的工具列表中。


四、全景:Skill + Tool = ReAct 模式

Q12:所以整个体系就是 ReAct?

完全正确。 Skill 体系负责”说明书”(Reasoning),Tool 体系负责”执行”(Acting),两者通过 ReAct 循环联动。

┌──────────────────────────────────────────────────────┐
│                  System Prompt                        │
│  ┌───────────────────┐  ┌─────────────────────────┐  │
│  │ <available_skills> │  │  Available Tools:        │  │
│  │ 说明书目录          │  │  - read, write, edit     │  │
│  │ name + description │  │  - exec, bash           │  │
│  │ + location         │  │  - browser, message     │  │
│  └───────────────────┘  └─────────────────────────┘  │
└──────────────────────────────────────────────────────┘
                        │
                        ▼
┌──────────────────────────────────────────────────────┐
│                  ReAct Loop                           │
│                                                      │
│  ┌──────────┐     ┌──────────┐     ┌──────────┐     │
│  │ REASON   │ ──→ │   ACT    │ ──→ │ OBSERVE  │ ──→ │
│  │          │     │          │     │          │     │
│  │ 读Skill  │     │ 调Tool   │     │ 看结果   │     │
│  │ 选Tool   │     │ 填参数   │     │ 判成败   │     │
│  │ 定参数   │     │          │     │          │     │
│  └──────────┘     └──────────┘     └──────────┘     │
│       ↑                                    │         │
│       └────────────────────────────────────┘         │
│                  循环直到任务完成                      │
└──────────────────────────────────────────────────────┘

两个体系的职责边界

Skill 体系
Tool 体系
本质
说明书(提示词工程)
可执行代码(函数)
谁提供
SKILL.md 文件
TypeScript 模块 + 插件
加载时机
系统提示词注入 + 按需 Read
Agent 启动时注册
AI 的角色
决定选哪个 skill、怎么遵循
决定选哪个 tool、传什么参数

五、完整场景:从头到尾跑一遍

最后,用一个完整场景把所有概念串起来。

场景:用户在聊天里说 “帮我搜一下今天 AI 领域的新闻,整理成摘要发到 Discord”


第1步:消息到达,检查是否是斜杠命令

OpenClaw 收到消息 → 检查是否以 / 开头 → 不是 → 不是斜杠命令 → 正常发送给 AI。


第2步:AI 收到系统提示词(含 Skill 菜单)

AI 看到的 system prompt 里包含:

<available_skills>
  <skill>

    <name>
tavily</name>
    <description>
Web search via Tavily API; search, extract...</description>
    <location>
/home/user/.openclaw/plugin-skills/tavily/SKILL.md</location>
  </skill>

  <skill>

    <name>
summarize</name>
    <description>
Summarize text, articles, or transcripts...</description>
    <location>
/app/skills/summarize/SKILL.md</location>
  </skill>

  <skill>

    <name>
discord</name>
    <description>
Discord message-tool ops: send/read/edit/delete...</description>
    <location>
/app/skills/discord/SKILL.md</location>
  </skill>

</available_skills>

以及可用的 Tool 列表:readexecmessagetavily_searchtavily_extract, …


第3步:AI 判断需要哪些 Skill(ReAct – Reason)

AI 分析用户请求:

“搜索新闻 → 匹配 tavily。整理摘要 → 匹配 summarize。发到 Discord → 匹配 discord。”

AI 在同一个推理回合输出多个 tool call:

→ Read("tavily/SKILL.md")
→ Read("summarize/SKILL.md")
→ Read("discord/SKILL.md")

第4步:OpenClaw 记录 Skill 使用

每次 Read 执行前,before-tool-call 钩子检查读取的文件路径是否命中某个 Skill 的 SKILL.md。命中了 → 发出 skill.used 诊断事件,记录技能名、来源、触发方式。


第5步:AI 读取 Skill 内容,按指令执行(ReAct – Act)

tavily 技能告诉 AI:

Use tavily_search with query parameter for web search.
Example: tavily_
search({ query: "AI news today", max_results: 5 })

summarize 技能告诉 AI:

Use summarize.sh CLI tool to condense text.
Example: exec: summarize.sh --input /tmp/news.txt --format bullet

discord 技能告诉 AI:

Use message tool with channel: "discord".
Example: message({ action: "send", channel: "discord", to: "channel:123", message: "..." })
注意:这个技能声明了 allowed-tools: ["message"],只能用 message 工具

AI 按指令执行:

→ tavily_search({ query: "AI news 2026-06-11", max_results: 5 })
  ← 返回 5 条新闻结果

→ exec({ command: "summarize.sh --input /tmp/news.txt --format bullet" })
  ← 返回格式化的摘要

→ message({
    action: "send",
    channel: "discord",
    to: "channel:123",
    message: "📰 Today's AI News Summary:\n\n• ...\n• ...\n• ..."
  })
  ← 发送成功

第6步:AI 观察结果,判断任务完成(ReAct – Observe)

所有 Tool 返回成功 → AI 判断任务完成 → 输出最终回复给用户:

“已为你搜索今天 AI 领域的最新新闻,摘要已发送到 Discord #general 频道!”


第7步:如果中途出错?

假设 AI 把 tavily_search 拼错成 tavily_seach

→ tavily_seach({ query: "AI news" })
  ← Error: unknown tool: tavily_seach

AI 看到错误:"啊,拼错了,应该是 tavily_search"
→ tavily_search({ query: "AI news" })  // 纠正
  ← 正常返回结果

如果 AI 死不悔改,连续调用不存在的工具 10 次 → 循环检测器触发 unknown_tool_repeat → 硬阻断 → 模型被迫放弃。


这个场景涉及的全部组件:

步骤
组件
类型
来源
Skill 发现
tavily, summarize, discord
Skill
Plugin + Bundled
技能读取
Read 工具
Tool
内置
技能使用记录
before-tool-call 钩子
Hook
框架
网页搜索
tavily_search
Tool
Plugin “tavily”
文本摘要
exec + summarize.sh
Tool + 脚本
内置 + Skill 脚本
Discord 发送
message
Tool
内置
工具限制
allowed-tools: [“message”]
安全策略
Skill frontmatter
错误恢复
ReAct 循环
模式
框架
循环保护
循环检测器
安全机制
框架

总结

OpenClaw 的 Skill 系统本质上是一套基于提示词工程的行为指导体系

  1. 1. Skill 是说明书——纯文本的 SKILL.md,告诉 AI 遇到什么情况该怎么做
  2. 2. Tool 是执行器——TypeScript 函数,真正干活
  3. 3. Plugin 是集装箱——打包 Skill、Tool、Provider、Channel 等一切
  4. 4. 加载靠文件系统——7 个来源,Map 合并,后加载覆盖先加载
  5. 5. 注入靠提示词——<available_skills> 块注入系统提示词,AI 按需 Read
  6. 6. 执行靠 ReAct——Reason(读 Skill 选 Tool)→ Act(调 Tool)→ Observe(看结果)→ 循环
  7. 7. 安全靠多层防护——allowed-tools 限制、5 层循环检测、工具策略管道

最精妙的设计是:Skill 从来不是”运行”的代码,它始终是一份被 AI 阅读后遵循的说明书。 这让 Skill 极其轻量(就是一个 Markdown 文件)、极其安全(没有代码执行风险)、极其灵活(任何人都可以写)。同时 Tool 体系提供了真正的执行能力,两者通过 ReAct 模式无缝协作。