Skill 还是 Tool?——从 OpenClaw 源码看 Agent 能力扩展的两种范式

当我们给一个 Agent "加能力"时,到底应该写一份 Markdown,还是写一段 TypeScript?这个问题看似工程细节,实则关乎 Agent 系统的架构边界。本文以 OpenClaw 的实现为样本,剖析 Skill 与 Tool 两种抽象的本质差异,并给出可落地的决策框架。

引言:一个真实的架构困境

arduino 复制代码
用户:"帮我加一个部署能力。"
工程师 A:"写个 deploy.ts Tool。"
工程师 B:"写个 SKILL.md。"

两人都有道理。问题在于,这是一个 policy-mechanism separation 问题:部署涉及的"知识流程"(审批流程、回滚策略、哪个环境用什么策略)需要灵活表述,而"执行操作"(调 kubectl、docker push)需要安全校验------两边都要,但放错位置就会出问题。

OpenClaw 的设计选择是:Skill 承载知识层,Tool 承载执行层,二者通过 dispatch 正交桥接。本文从源码出发,拆开这个设计。


一、先看清两个东西"长什么样"

1.1 Tool:一段可被 LLM 调用的"函数"

OpenClaw 的核心工具清单定义在 src/agents/tool-catalog.ts:41-244

typescript 复制代码
const CORE_TOOL_DEFINITIONS: CoreToolDefinition[] = [
  { id: "read",    sectionId: "fs",     profiles: ["coding"] },
  { id: "write",   sectionId: "fs",     profiles: ["coding"] },
  { id: "exec",    sectionId: "runtime", profiles: ["coding"] },
  { id: "web_fetch", sectionId: "web",    profiles: ["coding"] },
  // ... 共 31 个核心工具,按 section 分组
];

每个 Tool 的实现形态是一段 TypeScript:namedescription、TypeBox 描述的 parameters schema ,以及一个 execute() 函数

转换层在 src/agents/pi-tool-definition-adapter.ts:16-58,把 AgentTool[] 打包成 ToolDefinition[],喂给底部 LLM 用于 function calling:

typescript 复制代码
export function toToolDefinitions(tools: AnyAgentTool[]): ToolDefinition[] {
  return tools.map((tool) => ({
    name, label, description, parameters,
    execute: async (...args: ToolExecuteArgs): Promise<AgentToolResult<unknown>> => {
      // 1. beforeToolCall 安全闸门
      // 2. 执行 tool.execute()
      // 3. 结果归一化
    },
  }));
}

Tool 的关键特征

  • 结构化输入输出:参数有强 schema(TypeBox),结果有规范化格式;
  • 安全闸门beforeToolCall 钩子 + 沙箱 + 结果截断;
  • 模型隐式调用:模型根据上下文判断"该不该用";
  • Profile 控制下发minimal / coding / messaging / full 决定工具列表长度。

1.2 Skill:一份可被 LLM 阅读的"操作手册"

Type 定义在 src/agents/skills/types.ts:51-71

typescript 复制代码
export type SkillCommandSpec = {
  name: string;
  skillName: string;
  description: string;
  dispatch?: SkillCommandDispatchSpec;  // { kind: "tool", toolName: string }
};

export type SkillEntry = {
  skill: Skill;
  frontmatter: ParsedSkillFrontmatter;
  metadata?: OpenClawSkillMetadata;
  invocation?: SkillInvocationPolicy;
};

物理形态是一个目录:SKILL.md + 可选的 scripts/references/assets/

加载逻辑在 src/agents/skills/workspace.ts按工作区动态扫描 ,按预算(默认 30,000 字符 [1]、最多 150 个 skill)拼成 SkillSnapshot.prompt 作为系统提示词片段。

提示词预算 来自 src/agents/skills/workspace.ts:27-29

typescript 复制代码
const DEFAULT_MAX_SKILLS_PROMPT_CHARS = 30_000;
const DEFAULT_MAX_SKILLS_IN_PROMPT = 150;
const DEFAULT_MAX_SKILL_FILE_BYTES = 256_000;

Skill 的关键特征

  • 以自然语言为主:给模型"读"的,不是给程序"调"的;
  • 三级渐进披露:metadata(始终在上下文)→ SKILL.md body(被触发时加载)→ bundled resources(按需请求);
  • 声明式依赖requires.binsrequires.envinstall[] 声明依赖环境;
  • 支持 /slash 命令显式触发 :见 src/auto-reply/skill-commands.ts:166-204

1.3 两者如何桥接?

关键字段在 src/agents/skills/types.ts:40-49

typescript 复制代码
export type SkillCommandDispatchSpec = {
  kind: "tool";
  toolName: string;      // 调用的目标 Tool
  argMode?: "raw";      // raw = 直接透传用户参数
};

skill-commands.ts:191-196 实现了 dispatch 逻辑:

typescript 复制代码
if (command.dispatch?.kind === "tool") {
  // 找到 toolName 对应的 Tool,执行它
  const tool = findToolByName(command.dispatch.toolName);
  return tool.execute(rawArgs);
}

这就是架构的精妙之处:Skill 提供语义层,Tool 提供执行层,二者通过 dispatch 正交桥接

perl 复制代码
┌─────────────────────────────────────────────────────────┐
│                    Agent 运行时                          │
├─────────────────────────────────────────────────────────┤
│  系统 Prompt                                           │
│  ┌─────────────────────────────────────────────────────┐ │
│  │ SkillSnapshot.prompt (30k budget)               │ │
│  │   ├─ always: metadata (~100 chars)           │ │
│  │   └─ on trigger: SKILL.md body            │ │
│  └─────────────────────────────────────────────────────┘ │
│                                                         │
│  ToolDefinitions[] (by Profile)                        │
│  ┌─────────────────────────────────────────────────────┐ │
│  │ read, write, exec, web_fetch, memory_search...    │ │
│  └─────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────┤
│                   dispatch 桥接                         │
│  Skill (描述层) ──dispatch──► Tool (执行层)            │
└─────────────────────────────────────────────────────────┘

二、本质差异:知识 vs 能力

维度 Skill Tool
抽象层级 知识 / 流程 / 约定 原子动作 / 副作用
载体 Markdown(给模型读) TypeScript(给程序执行)
触发方式 描述匹配 + /slash 模型 function call
状态 无状态、纯文本 有副作用、可写文件/网络
变更成本 改文件即生效,无需发版 需要代码评审、发版
安全模型 进入 prompt,被模型自由解读 beforeToolCall 闸门、沙箱、ACL
下发预算 共享 30k 字符上下文预算 Profile 控制工具数量
��观测性 出现在 prompt 即可见 每次调用有结构化日志

一句话:Skill 教模型"该怎么做",Tool 让模型"能够做"。


三、什么时候用 Skill?

3.1 典型案例:1Password Skill

skills/1password/SKILL.md 是 Skill 的最佳示范。核心知识是:

  • "op 之前必须 op signin"(操作顺序)
  • "OTP 字段路径是 op://Private/Npmjs/one-time password?attribute=otp"(领域细节)
  • "所有 op 命令必须在 tmux 里跑"(环境约束)

这些不是新功能,而是 "领域专家知道、但模型不知道"的操作手册 。底层执行靠已有的 exec Tool 调 op 命令------Skill 的价值是把这类知识注入上下文。

判据:能力是"知识 + 既有 CLI 的编排顺序" → Skill

3.2 典型案例:Skill Creator

skills/skill-creator/SKILL.md 定义了 skill 编写规范:

  • 三级渐进披露结构(metadata → body → bundled resources)
  • 建议的自由度分级(高/中/低)
  • 目录结构规范(SKILL.md + scripts/ + references/ + assets/

这是meta 能力的典型:不是给机器执行,而是 "教模型如何写一个新的 Skill"

判据:能力是"生成或理解其他能力的方法论" → Skill

3.3 何时判定用 Skill?

问题 答案 →
Q1: 是不是"先 A 再 B,注意 C 陷阱"的流程知识? 是 → Skill
Q2: 需要多种合法路径、让模型自己决策? 是 → Skill
Q3: 项目/用户会频繁定制/修改这个能力? 是 → Skill
Q4: 需要暴露 /xxx 这样可读的命令入口? 是 → Skill
Q5: 能力需要附带模板、示例、参考资料? 是 → Skill

四、什么时候用 Tool?

4.1 典型案例:Web Fetch Tool

src/agents/tools/web-fetch.ts 是复杂 Tool 的实现样本。关键特征:

  • TypeBox 参数校验url(必填)、extractMode?(枚举)、maxChars?(数值);
  • 执行安全保障:超时控制、响应截断、SSRF 黑名单;
  • 可观测性:每次调用产生结构化日志。

判据 :需要严格参数校验 + 安全闸门 + 审计 → Tool

4.2 典型案例:Core Tool Catalog

src/agents/tool-catalog.ts:41-244 定义了 31 个核心工具,分为 11 个 Section:

scss 复制代码
fs (read/write/edit)       runtime (exec/process)
web (web_search/fetch)    memory (search/get)
sessions (list/history/spawn)  ui (browser/canvas)
messaging (message)       automation (cron/gateway)
nodes (nodes)             agents (list)
media (image/tts)

这些是 平台级原子能力:所有 Agent 都需要、协议稳定、不随项目变化。

判据 :能力的本质是 I/O、进程、网络等副作用 → Tool

4.3 何时判定用 Tool?

问题 答案 →
Q1: 能力的本质是读/写/网络/进程等副作用? 是 → Tool
Q2: 参数错误会导致严重后果(删错文件、误付费)? 是 → Tool
Q3: 需要进入 beforeToolCall 安全闸门、ACL、可审计? 是 → Tool
Q4: 结果需要被程序结构化消费(后续 Tool / UI)? 是 → Tool
Q5: 能力是平台级、跨项目通用、契约需稳定? 是 → Tool

五、灰色地带:Skill 调 Tool 的复合模式

ini 复制代码
┌─────────────────────────────────────────────────────────────┐
│  复合能力示例:/deploy 命令                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  deploy Skill (知识层)                                     │
│  ┌───────────────────────────────────────────────────────┐ │
│  │ ## 部署流程                                          │ │
│  │ 1. 运行测试 ─► 2. 构建镜像 ─► 3. 推送到仓库         │ │
│  │ 4. 滚动更新 ─► 5. 验证 Health                       │ │
│  │                                                      │ │
│  │ ## 权限约束                                         │ │
│  │ - 生产环境只能 role=admin 的用户触发                 │ │
│  │ - 必须在 deploy 前完成 code review                  │ │
│  │                                                      │ │
│  │ ## 回滚策略                                         │ │
│  │ - 失败自动回滚到上一个 tag                         │ │
│  └───────────────────────────────────────────────────────┘ │
│          │ dispatch="tool"                                 │
│          ▼                                                │
│  ┌───────────────────────────────────────────────────────┐ │
│  │ deploy-orchestrator Tool (执行层)                  │ │
│  │   ├─ run-tests.sh                                  │ │
│  │   ├─ docker build && tag                           │ │
│  │   ├─ kubectl rolling-update                        │ │
│  │   └─ health-check loop                            │ │
│  └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

这种模式的架构价值:用 Skill 表达 policy(哪些人能在什么环境下做什么),用 Tool 实现 mechanism(底层执行细节)。Unix 的经久不衰的设计原则,在 Agent 系统里同样成立。


六、决策框架:四个问题

yaml 复制代码
                    ┌─────────────────┐
                    │ 加一个新能力     │
                    └────────┬────────┘
                             │
              ┌──────────────┼──────────────┐
              ▼              ▼              ▼
        ┌───────────┐  ┌───────────┐  ┌───────────┐
        │ Q1: 知识  │  │ Q2: 安全  │  │ Q3: 定制  │
        │ 还是     │  │ 需要      │  │ 权在      │
        │ 副作用? │  │ 闸门?   │  │ 项目?    │
        └─────┬─────┘  └─────┬─────┘  └─────┬─────┘
             │             │             │
      ┌──────┴──────┐     │      ┌──────┴──────┐
      ▼             ▼     ▼      ▼             ▼
   Skill         Tool   Tool   Skill          Tool
   (编排知识)    (I/O)  (安全)  (项目级)    (平台级)

Q1:能力本质是知识 + 现有工具编排,还是对外部世界的新副作用?

  • 知识/编排 → Skill
  • 新副作用 → Tool

Q2:需要严格的参数校验 + beforeToolCall 安全闸门 + ACL 吗?

  • 需要 → Tool(即使本质是知识,执行部分也要沉淀为 Tool)

Q3:能力需要项目/用户自由定制吗?

  • 需要 → Skill(即使背后调 Tool,对外暴露的入口是 Skill)

Q4:结果是否需要被程序结构化消费?

  • 是 → Tool
  • 否(只给人/模型读) → Skill

复合判定 :如果 Q1=Tool 且 Q3=需要定制 → 两层设计:底层 Tool 稳定提供 + 上层 Skill 灵活编排。


七、常见反模式

反模式 1:把领域知识硬编码成 Tool

typescript 复制代码
// ❌ 反例
const tool = {
  name: "write-good-commit",
  execute: async () => {
    // 强制要求 "[type]: description" 格式
    // 问题:模型失去灵活性,每次都被迫走固定路径
  }
}

问题:强制路径 = 丧失模型的决策空间。领域知识应该是 Skill。

反模式 2:把副作用,塞进 Skill 的 Markdown

markdown 复制代码
<!-- ❌ 反例 -->
## 执行部署
运行以下命令:
\`\`\`bash
kubectl apply -f deployment.yaml
\`\`\`

问题 :失去 beforeToolCall 保护,无法 ACL,无法审计。所有副作用必须经 Tool

反模式 3:Skill 数量爆炸,没有 filter

typescript 复制代码
// ❌ 反例:���做 skillFilter
const snapshot = buildWorkspaceSkillSnapshot(workspaceDir, {
  // 没有 skillFilter,全部加载
});
// 结果:30k 预算被稀释,核心能力被淹没

正确姿势 :在 agent-scope.ts 中为每个 Agent 配置 skillFilter,按 Agent 类型加载不同 skill 集合。

反模式 4:Tool 数量爆炸,没有 Profile

typescript 复制代码
// ❌ 反例:给 messaging Agent 加载 coding profile
const profile = "coding";  // 不该暴露给 messaging
// 结果:误调用风险 + 上下文浪费

正确姿势 :按 Agent 类型选 Profile:minimal(仅状态查询)、coding(有 fs/web)、messaging(仅 message)、full(全量)。

反模式 5:Skill 和 Tool 功能重复

ini 复制代码
Skill name="read-file"   ←─ dispatch ──► Tool name="read"
        ↑                            ↑
     重复!                      去重!

正确姿势SkillCommandSpec.dispatch 指向前置 Tool,或者直接砍掉 Skill 变体。dedupeBySkillName() 只是兜底,不该被正常路径依赖。


八、结论

Tool 是 Agent 的"硬件指令集",Skill 是 Agent 的"操作系统教程"。前者决定能做什么,后者决定该怎么做。

OpenClaw 的设计之所以健壮,正是因为两层正交

sql 复制代码
┌─────────────────────────────────────────────────────────┐
│                     Agent 能力体系                       │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  Skill 层(收敛的、Markdown 的、用户可扩展的)         │
│   - 位置:工作区 skills/ 目录                           │
│   - 变更:git add + commit,无发版                      │
│   - 触发:描述匹配 + /slash 命令                        │
│                                                         │
│   dispatch                                              │
│   ──────►                                               │
│                                                         │
│  Tool 层(收敛的、TypeScript 的、平台维护的)           │
│   - 位置:src/agents/tools/                             │
│   - 变更:code review + CI + npm publish                 │
│   - 触发:模型 function call                             │
│                                                         │
└─────────────────────────────────────────────────────────┘

工程上最容易犯的错,是把"加一个能力"当作一道单选题。真正的高级抽象,是知道 这个能力的哪部分属于知识层、哪部分属于执行层,然后把它们放到正确的位置上。

这,才是 Skill vs Tool 这道题的真正答案。


附录:源码索引

概念 文件路径 关键行号
Core Tool 定义 src/agents/tool-catalog.ts 41-244
Tool → ToolDefinition 转换 src/agents/pi-tool-definition-adapter.ts 16-58
beforeToolCall 闸门 同上 22-27
Skill 类型定义 src/agents/skills/types.ts 51-71
SkillCommandDispatchSpec src/agents/skills/types.ts 40-49
Skill 动态加载 src/agents/skills/workspace.ts 全文
提示词预算常量 src/agents/skills/workspace.ts 27-29
/slash 命令解析 src/auto-reply/skill-commands.ts 166-204
Profile 枚举 src/agents/tool-catalog.ts 1, 256-267
三级渐进披露 skills/skill-creator/SKILL.md 全文
1Password Skill 案例 skills/1password/SKILL.md 全文
Web Fetch Tool 实现 src/agents/tools/web-fetch.ts 全文
相关推荐
xingfujie1 小时前
第1章:整体架构与准备工作
linux·云原生·容器·架构·kubernetes·kubelet
Forrit1 小时前
AI多Agent 用户-会话-记忆 建表&架构总结
人工智能·架构
周杰伦fans1 小时前
禁止edge浏览器更新
前端·edge
user297525876121 小时前
使用SSE实现流式渲染实践
前端·javascript
LPieces1 小时前
【LPieces-UI】02-Icon组件的设计与实现
前端·vue.js
我本地是好的1 小时前
解决高德地图无外网访问难题:Vue项目代理转发全攻略
前端
wand codemonkey1 小时前
Maven Web 项目 + Tomcat 从零排错全流程(零遗漏版)
前端·tomcat·maven
2603_954708311 小时前
微电网分布式电源接入技术:光伏、风电的适配设计
人工智能·分布式·物联网·架构·系统架构·能源
豆苗学前端1 小时前
【前端内功】同为数据驱动,为什么只有 React 的"心智负担"这么重?(附实战优化指南)
前端·vue.js·面试