想象你在教一个 AI 助手做事。你可以给它一套工具(比如浏览器、文件读写、API 调用),但光有工具还不够——你还得告诉它什么时候用哪个工具、怎么组合使用、遇到什么情况该怎么做。在 OpenClaw 里,Extension 就是工具箱,Skill 就是操作手册。
一、先搞清楚三个概念
| Tool | |||
| Skill | |||
| Extension |
一句话总结:Extension 提供能力,Skill 指导使用,Agent 执行。
┌──────────────────────────────────────────┐│ AI Agent ││ ┌────────────┐ ┌──────────────────┐ ││ │ Skills │ │ Tools │ ││ │ (SKILL.md) │───→│ (function call) │ ││ │ 看说明书 │ │ 拿工具干活 │ ││ └────────────┘ └──────────────────┘ ││ ↑ ↑ ││ prompt注入 注册提供 ││ ┌─────────────────────────────────────┐ ││ │ Extensions(工具箱) │ ││ │ openclaw.plugin.json + 代码实现 │ ││ └─────────────────────────────────────┘ │└──────────────────────────────────────────┘二、Skill:用 Markdown 写的"操作手册"
2.1 一个 Skill 长什么样?
一个 Skill 就是一个目录,里面有一个 SKILL.md 文件:
skills/ weather/ SKILL.md ← 唯一的技能定义文件 helpers.sh ← 可选的辅助脚本 references/ ← 可选的详细文档SKILL.md 的结构很简单——上面是元数据(YAML),下面是正文(Markdown):
---name: weatherdescription: "Current weather and forecasts with web_fetch, falling back to wttr.in curl."homepage: https://wttr.in/:helpmetadata: { "openclaw": { "emoji": "☔", "install": [ { "id": "brew", "kind": "brew", "formula": "curl", "bins": ["curl"], "label": "Install curl (brew)", }, ], }, }---# WeatherUse for current weather, rain/temperature checks, forecasts, and travel planning. Need a city, region, airport code, or coordinates.## Preferred: web_fetchUse `web_fetch` first when the tool is available. Request JSON because wttr.inreturns browser-oriented HTML for many text formats when called with a browser-likeUser-Agent.```javascriptawait web_fetch({ url: "https://wttr.in/London?format=j2", extractMode: "text", maxChars: 12000,});For short answers, summarize `current_condition[0]`, `nearest_area[0]`, and thefirst entries in `weather[]`. Use `format=j2` for normal summaries because itomits bulky hourly data and fits the default `web_fetch` output cap....2.2 Frontmatter(元数据)是什么?
--- 之间的部分叫 frontmatter,用 YAML 格式写。它告诉 OpenClaw 这个技能的基本信息:
name:技能名称,Agent 通过这个名字识别技能description:技能描述,出现在系统 prompt 里,让 Agent 知道这个技能是干嘛的homepage:文档链接,用户想深入了解时可以点进去metadata.openclaw:OpenClaw 扩展字段,包含安装依赖、系统要求等
metadata.openclaw 不是给 Agent 看的,是给 OpenClaw 系统用的:
metadata:openclaw:emoji:"☔"# UI 展示用的图标install:# 自动安装依赖-kind:"brew"formula:"curl"bins:["curl"]requires:# 运行时依赖检查bins:["curl"]当用户执行 openclaw skills install weather:brew 时,OpenClaw 会根据 install 字段自动安装依赖。如果系统缺少 requires.bins 里的二进制,这个技能会被标记为不可用,不会注入到 Agent 的 prompt 里。
2.3 正文(Markdown body)是什么?
--- 下面的内容就是正文,用 Markdown 写。这部分会按需被 Agent 读取,告诉它具体怎么做事。
正文可以包含:
使用场景说明 工具调用示例 最佳实践 注意事项 辅助文件引用
比如 tmux 技能的正文教 Agent 如何操作 tmux 会话:
## Send inputLiteral text, then Enter:```bashtmux send-keys -t shared:0.0 -l -- "Please continue"tmux send-keys -t shared:0.0 EnterSpecial keys:```bashtmux send-keys -t shared:0.0 C-ctmux send-keys -t shared:0.0 C-d...2.4 辅助文件按需加载
一个 Skill 可以包含辅助文件,正文里可以引用它们。skill-creator 技能推荐这种结构:
skill-name/ SKILL.md ← 精简入口,只含触发条件和关键信息 scripts/ ← 辅助脚本 references/ ← 详细文档,按需加载 assets/ ← 模板/资源open-prose 插件的 SKILL.md 明确定义了每个文件的加载时机:
| File | Purpose | When to Load || --------------------- | ------------------- | ----------------------------------------------- || `prose.md` | VM / Interpreter | Always load to run programs || `state/filesystem.md` | File-based state | Load with VM (default) || `compiler.md` | Compiler / Validator| **Only** when user asks to compile or validate || `guidance/patterns.md`| Best practices | Load when **writing** new .prose files |这样设计的好处是:SKILL.md 保持精简,详细内容按需加载,节省 token。
2.5 OpenClaw 如何加载 Skill?
加载过程分几步:
第一步:扫描目录
// src/skills/loading/local-loader.tsfunctionloadSingleSkillDirectory(params) {const skillFilePath = path.join(params.skillDir, "SKILL.md");const raw = readSkillFileSync({ filePath: skillFilePath, ... });// 读取文件内容,检查 symlink 是否逃逸}第二步:解析 frontmatter
// src/skills/loading/frontmatter.tsexportfunctionparseFrontmatter(content: string): ParsedSkillFrontmatter{return parseFrontmatterBlock(content); // 提取 --- 之间的内容}parseFrontmatterBlock 会同时尝试 YAML 解析和行级解析,YAML 解析失败时降级为简单的 key: value 解析,提高容错性。
第三步:解析 metadata
// src/shared/frontmatter.tsexportfunctionresolveOpenClawManifestBlock(params) {const raw = getFrontmatterString(params.frontmatter, "metadata");const parsed = JSON5.parse(raw); // 注意:用 JSON5,不是 YAML// 查找 "openclaw" key}metadata 字段用 JSON5 解析而不是 YAML,因为 YAML 嵌套在不同解析器中行为不一致,JSON5 更明确。
第四步:组装 SkillEntry
// src/skills/loading/workspace.tsconst skillEntries = Array.from(merged.values()).map((record) => ({ skill: { name, description, filePath, baseDir, ... }, frontmatter, metadata: resolveSkillEntryMetadata({ frontmatter, skillDir }), invocation: resolveSkillInvocationPolicy(frontmatter), exposure: { includeInRuntimeRegistry: true, includeInAvailableSkillsPrompt: !invocation.disableModelInvocation, userInvocable: invocation.userInvocable ?? true, },}));SkillEntry 包含了技能的所有信息:基本信息、frontmatter、metadata、调用策略、暴露策略。
三、Extension:提供能力的代码插件
3.1 一个 Extension 长什么样?
一个 Extension 是一个 TypeScript 包,入口是 openclaw.plugin.json:
// extensions/browser/openclaw.plugin.json{"id": "browser","activation": { "onStartup": false },"contracts": {"tools": ["browser"] },"skills": ["./skills"]}关键字段:
id:插件唯一标识contracts.tools:声明提供哪些工具skills:声明附带哪些技能目录activation.onStartup:是否在启动时自动激活
3.2 如何注册一个工具?
插件通过 api.registerTool 将函数注册为 Agent 可调用的工具:
// extensions/browser/plugin-registration.ts(简化)api.registerTool({ name: "browser", description: "Control a browser: navigate, click, type, screenshot...", parameters: BrowserToolSchema, // JSON Schema 定义参数 execute: async (params, ctx) => {// 实际执行逻辑returnawait browserToolDeps.execute(params, ctx); },});name + description + parameters 会被传递给 LLM 作为 function calling 的 schema,execute 是 Agent 调用工具时实际执行的函数。
3.3 懒加载设计
OpenClaw 的插件加载采用懒加载模式,启动时只加载清单,运行时代码按需导入:
const loadBrowserRegistrationRuntimeModule = async () => { browserRegistrationRuntimeModulePromise ??= import("./register.runtime.js");returnawait browserRegistrationRuntimeModulePromise;};??= 确保模块只被导入一次。这种模式让启动速度不受插件数量影响。
3.4 边界规则
OpenClaw 对插件代码有严格的边界约束:
插件代码只能从 openclaw/plugin-sdk/*导入不能直接导入核心 src/**模块不能导入其他插件的内部代码 插件依赖属于插件自身包
插件代码 ──→ openclaw/plugin-sdk/* ──→ 核心能力 ✗ 不能直接导入 src/** ✗ 不能导入其他插件 src/**3.5 如何实现一个 Extension?
假设你想添加一个新工具,比如调用某个 API:
第一步:创建插件目录
extensions/my-api/ openclaw.plugin.json index.ts plugin-registration.ts my-api-tool.ts第二步:编写清单
{"id": "my-api","activation": { "onStartup": false },"contracts": { "tools": ["my_api"] }}第三步:实现工具
// my-api-tool.tsexportasyncfunctionexecuteMyApiTool(params, ctx) {const response = await fetch("https://api.example.com/endpoint", { method: "POST", body: JSON.stringify(params), });returnawait response.json();}第四步:注册工具
// plugin-registration.tsimport { definePluginEntry } from"openclaw/plugin-sdk";import { executeMyApiTool } from"./my-api-tool.js";exportdefault definePluginEntry({ id: "my-api", name: "My API", description: "My custom API tool", register: (api) => { api.registerTool({ name: "my_api", description: "Call my custom API", parameters: {type: "object", properties: { query: { type: "string" }, }, }, execute: executeMyApiTool, }); },});四、什么时候用 Skill,什么时候用 Extension?
4.1 用 Skill 的场景
场景 1:指导 Agent 使用现有工具
你想让 Agent 按特定流程使用已有的工具,比如:
用 browser工具自动化登录某个网站用 web_fetch工具抓取特定网站的数据用 file工具按特定模式修改配置文件
这时候写一个 Skill 就够了,不需要写代码。
场景 2:记录特定领域的最佳实践
比如你的项目有特定的代码审查标准,写一个 Skill 告诉 Agent 审查时要注意什么。
场景 3:工作流编排
比如发布流程、部署流程,涉及多个工具的组合使用,用 Skill 描述整个流程。
4.2 用 Extension 的场景
场景 1:需要新增工具
现有工具满足不了需求,需要调用新的 API、操作新的系统,这时候需要写 Extension。
场景 2:需要新增模型提供者
想接入新的 LLM(比如某个私有模型),需要写 Extension。
场景 3:需要新增能力
比如需要语音识别、图像生成、数据库访问等新能力,需要写 Extension。
4.3 简单判断法
需要写代码? → 用 Extension 只需要写文档? → 用 Skill 需要调用新 API? → 用 Extension 只是组合现有工具? → 用 Skill
五、Skill 和 Extension 的协作
5.1 Extension 可以附带 Skill
很多 Extension 在插件清单中声明附带 Skill:
// extensions/browser/openclaw.plugin.json{"contracts": { "tools": ["browser"] },"skills": ["./skills"]}这意味着 browser 插件不仅提供了 browser 工具,还提供了 browser-automation 技能指导 Agent 如何使用该工具。
5.2 Skill 引用 Extension 的工具
SKILL.md 的正文描述如何使用工具:
<!-- extensions/browser/skills/browser-automation/SKILL.md -->## Operating Loop1. Call `browser` tool with `action: "snapshot"` to see the page2. Identify the element to interact with3. Call `browser` tool with `action: "act"` and the element ref4. Repeat until task is completeAgent 读取 SKILL.md 后,按照指导调用 Extension 注册的 browser 工具。
5.3 metadata 中的插件关联
某些 skill 通过 skillKey 和 requires.config 与插件强关联:
metadata:openclaw:skillKey:"voice-call"requires:config:["plugins.entries.voice-call.enabled"]这表示该 skill 需要 voice-call 插件启用才能使用。
六、渐进式读取:用最少 Token 传递最多信息
这是 OpenClaw Skill 系统最精妙的设计。
6.1 Prompt 注入的三级降级
系统 prompt 中不会直接塞入所有 SKILL.md 的完整内容,而是采用三级降级策略:
// src/skills/loading/workspace.tsfunctionapplySkillsPromptLimits(params) {const byCount = params.skills.slice(0, limits.maxSkillsInPrompt);// 第一级:完整格式(name + description + location + version)if (!fitsFull(skillsForPrompt)) {// 第二级:紧凑格式(name + location only,去掉 description)if (fitsCompact(skillsForPrompt)) { compact = true; } else {// 第三级:二分搜索截断数量 compact = true;let lo = 0, hi = skillsForPrompt.length;while (lo < hi) {const mid = Math.ceil((lo + hi) / 2);if (fitsCompact(skillsForPrompt.slice(0, mid))) lo = mid;else hi = mid - 1; } skillsForPrompt = skillsForPrompt.slice(0, lo); } }}完整格式 (name + description + location + version) ↓ 超出字符预算紧凑格式 (name + location only) ↓ 仍然超出截断数量 (二分搜索最大可装下数量)6.2 按需读取 SKILL.md
Prompt 中只注入 skill 目录(name + location),不注入完整内容。紧凑格式明确告诉 Agent:
"Use the read tool to load a skill's file when the task matches its name."
系统 prompt:只列出 skill 目录 → 节省 token ↓Agent 匹配到 skill → read tool 读取完整 SKILL.md → 按指导执行6.3 Snapshot 缓存与增量刷新
OpenClaw 还通过版本号机制避免重复加载:
// src/skills/runtime/session-snapshot.tsconst shouldRefresh = promptFormatChanged || skillVersionChanged || filterChanged;const snapshot = !existingSnapshot || shouldRefresh ? buildSnapshot() // 重新构建 : hydrateResolvedSkills( // 复用缓存 existingSnapshot, cachedRebuild);文件变化时通过 chokidar 监听触发版本号更新,下次请求时才重建。
七、完整加载链路
启动 ↓ 扫描插件清单 openclaw.plugin.json ↓ 注册工具(api.registerTool) ↓ 扫描 skill 目录(限制候选数量) ↓ 逐个读取 SKILL.md frontmatter(限制加载数量) ↓ 检查 requires(bins/env/config)→ 过滤不合格的 ↓ 构建 snapshot → 缓存构建 prompt ↓ 完整格式能装下?→ 是 → 注入完整格式 ↓ 否 → 紧凑格式能装下?→ 是 → 注入紧凑格式 ↓ 否 → 二分搜索截断Agent 运行 ↓ 看到 skill 目录(name + location) ↓ 任务匹配 → read tool 读取完整 SKILL.md ↓ 需要辅助文件 → read references/xxx.md ↓ 按指导调用工具执行文件变化 ↓ chokidar 监听 → bump 版本号 ↓ 下次请求 → 版本对比 → 增量重建八、总结
Extension 是工具箱,Skill 是操作手册。
需要新能力 → 写 Extension(代码) 需要指导流程 → 写 Skill(Markdown) Extension 提供工具,Skill 指导使用 两者通过插件清单和 prompt 注入协同工作 渐进式读取确保用最少的 token 传递最多的信息
夜雨聆风