[转][译] 从零开始构建 OpenClaw — 第一部分(智能体核心)
[转][译] 从零开始构建 OpenClaw — 第二部分(技能插件系统)
[转][译] 从零开始构建 OpenClaw — 第三部分(元技能)
原文:Building Openclaw from Scratch — Part 2 (Skill Plugin System)
在这个部分,我们将为 openclaw-mini 添加一个可扩展的插件架构,大约需要 200 行 TypeScript 代码
在第一部分,我们构建了 openclaw-mini — 一个轻量级、仅通过终端运行的 AI 编码智能体,由 PI SDK 驱动。它配备了 REPL、工具调用循环、网络搜索、文件编辑和上下文感知系统提示词。
在本篇帖子中,我们将添加一些内容,将其从一个功能强大的智能体转变为一个可扩展的平台:一个技能插件系统。技能允许任何人(你、你的团队或社区)仅使用 Markdown 文件来教会智能体新的工作流程。
无需代码。无需 API。无需插件运行时。只需在正确的文件夹中放置一个 .md 文件。
我们在第一部分构建了什么:基础
在深入技能之前,让我们回顾一下 openclaw-mini 已经实现了什么。如果你跟上了第一部分,你构建了一个终端智能体,包括:
一个工具调用式 REPL 循环——任何 AI 智能体的核心。用户输入一个提示词,LLM 用工具调用响应( read , write , edit , bash , web_search , web_fetch ),工具执行,结果反馈给模型,循环继续直到任务完成。
多提供者 LLM 支持——Anthropic、OpenAI、Google、Groq、XAI、Mistral、OpenRouter、Cerebras 和 Ollama。一个代码库,九个提供者。
自定义网络工具 — web_fetch (通过 Mozilla Readability 从 URL 中提取可读内容)和 web_search (Brave 搜索或 Perplexity 集成)叠加在 SDK 内置工具之上。
上下文感知系统提示词 — 自动加载项目文件如 CONTEXT.md 、 CLAUDE.md 、 SOUL.md 和 .github/copilot-instructions.md ,将其注入系统提示词中,以便智能体理解您项目的规范。
会话持久化 — 对话状态存储在 ~/.openclaw-mini/state/sessions/ 中,以便您可以继续未完成的对话。
斜杠命令 — /new 、 /think 、 /model 、 /status 、 /quit 用于从终端控制会话。
这是第一部分之后启动横幅的样子:
┌ openclaw-mini
│ model: anthropic/claude-sonnet-4-20250514
│ workspace: /Users/you/project
│ session: mini-1719432000000
│ context: CLAUDE.md
│ tools: read, bash, edit, write, web_fetch, web_search
└ /new /think /model /quit
这是一个可靠的智能体。但每个提示词都从零开始——模型有工具,但没有工作流。如果你希望它创建常规的提交记录、使用特定的清单审查代码,或以标准格式总结项目,你每次都必须解释工作流。
这就是技能发挥作用的地方。
技能是什么?
一个技能是一个用 Markdown 编写的可重用工作流定义。它告诉智能体如何一步步使用它已有的工具来执行特定任务。
可以这样理解:
- 工具是智能体的手(读取文件、运行命令、搜索网络)
- 技能是智能体的行动手册(如何将工具组合成工作流)
这里是一个完整的技能—— git-commit/SKILL.md :
---
name: git-commit
description: Create well-structured git commits with conventional commit messages
---
When asked to commit changes, follow this workflow:
1. Run `git status` to see modified, added, and deleted files
2. Run `git diff --staged` to review what's staged; if nothing is staged,
ask the user what to stage or suggest staging relevant files
3. Analyze the changes and determine the conventional commit type:
- `feat`: New feature
- `fix`: Bug fix
- `docs`: Documentation changes
- `refactor`: Code restructuring without behavior change
- `test`: Adding or updating tests
- `chore`: Maintenance tasks, dependency updates
- `style`: Formatting, whitespace changes
4. Write a commit message:
type(scope): short summary in imperative mood (<72 chars)
Optional body explaining WHY the change was made.
5. Run `git commit -m "<message>"` to create the commit
6. Show `git log --oneline -1` to confirm
Keep the summary under 72 characters. Use imperative mood ("add feature" not "added feature").
就这样。没有 TypeScript。没有构建步骤。没有 API 注册。只需要 YAML 前置文本(名称 + 描述)和 Markdown 说明。
设计:渐进式披露
技能系统的关键架构决策是渐进式披露。以下是问题所在:如果你有 20 个技能,每个技能都有 30 多行指令,将它们全部注入系统提示词中会在每个请求上浪费数千个令牌——即使用户只是想问一个简单的问题。
我们的解决方案有两个层次:
- 系统提示词中的目录——仅注入技能名称和简短描述。每个技能大约消耗 10 个 token。模型知道有哪些技能存在,而无需承担它们的工作原理。
- 按需提供完整指令 — 当用户调用
/skill:git-commit时,SDK 会读取完整的SKILL.md文件,并将其中所有指令注入到该特定提示词中。模型在需要时就能获得详细的逐步指导。
这意味着 50 项技能的成本大致相当于系统提示词中的 5 项。代币预算随使用量而扩展,而非注册。
实现
整个技能系统涉及两个文件,约 200 行更改。让我们逐一了解每个部分。
1. 技能发现
技能可以存在于三个地方,按检查优先级顺序排列:
这个层级意味着:
- 用户技能会覆盖所有设置(您的个人偏好优先)
- 项目技能覆盖捆绑技能(团队约定优于默认设置)
- 打包技能提供开箱即用的合理默认值
发现功能很简单——遍历目录,加载技能,按名称去重(先匹配胜出):
function discoverSkills(workspaceDir: string): {
skills: Skill[];
diagnostics: string[];
} {
const skillMap = new Map<string, Skill>();
const diagnostics: string[] = [];
const sources = [
{ dir: USER_SKILLS_DIR, source: "user" },
{ dir: path.join(workspaceDir, ".openclaw-mini", "skills"), source: "project" },
{ dir: BUNDLED_SKILLS_DIR, source: "bundled" },
];
for (const { dir, source } of sources) {
try {
const result = loadSkillsFromDir({ dir, source });
for (const diag of result.diagnostics) {
diagnostics.push(`[${source}] ${diag.message}`);
}
for (const skill of result.skills) {
if (!skillMap.has(skill.name)) {
skillMap.set(skill.name, skill);
}
}
} catch {
// Directory doesn't exist - skip silently
}
}
return { skills: Array.from(skillMap.values()), diagnostics };
}
loadSkillsFromDir 是一个 PI SDK 工具,用于读取目录,查找包含 SKILL.md 文件的子目录,解析 YAML 前置内容,并返回类型的 Skill 对象。SDK 还处理格式错误的技能的诊断(缺少名称、YAML 错误等)。
2. 系统提示词集成
接下来,我们将技能目录注入到系统提示词中。SDK 的 formatSkillsForPrompt() 将技能数组转换为 XML 目录:
<skills>
<skill name="git-commit"
description="Create well-structured git commits with conventional commit messages" />
<skill name="code-review"
description="Review code changes for bugs, style issues, and improvement opportunities" />
<skill name="summarize"
description="Summarize files, directories, or project structure into concise overviews" />
<skill name="weather"
description="Fetch current weather and forecasts for any location" />
</skills>
这只需三行代码即可添加到系统提示词构建器中:
// In buildSystemPrompt()
if (skillsPrompt) {
lines.push("## Skills", skillsPrompt, "");
}
函数签名增加了一个新的可选参数:
export function buildSystemPrompt(params: {
workspaceDir: string;
runtime: RuntimeInfo;
toolNames: string[];
contextFiles: ContextFile[];
thinkingLevel?: string;
skillsPrompt?: string; // ← new
}): string {
这就是整个系统提示词的变更——包括参数添加在内,共 8 行代码。
3. 资源加载器用于按需扩展
目录告诉模型存在哪些技能。但当用户输入 /skill:git-commit 时,SDK 需要知道在哪里找到完整的 SKILL.md 文件。这由 DefaultResourceLoader 处理:
const resourceLoader = new DefaultResourceLoader({
cwd: workspaceDir,
agentDir: AGENT_DIR,
settingsManager,
additionalSkillPaths: [
USER_SKILLS_DIR,
path.join(workspaceDir, ".openclaw-mini", "skills"),
BUNDLED_SKILLS_DIR,
],
noExtensions: true,
noPromptTemplates: true,
noThemes: true,
});
await resourceLoader.reload();
资源加载器被传递给 createAgentSession() ,它连接了 SDK 内部的 _expandSkillCommand() 机制。当模型在提示词中看到 /skill:weather 时,SDK:
- 在
additionalSkillPaths中搜索weather/SKILL.md - 读取文件内容
- 将它们包裹在 XML 标签中
- 将完整的指令注入到该特定提示词中
模型随后看到完整的工作流程,并使用其可用工具逐步执行它。
4. /skills 命令
最后,我们添加一个斜杠命令,以便用户可以查看已加载的内容:
if (trimmed === "/skills") {
if (skills.length === 0) {
console.log("No skills loaded.");
} else {
console.log(`Loaded skills (${skills.length}):`);
for (const skill of skills) {
const src = `[${skill.source}]`.padEnd(10);
console.log(` ${src} ${skill.name} — ${skill.description}`);
}
}
continue;
}
输出:
Loaded skills (4):
[bundled] git-commit — Create well-structured git commits with conventional commit messages
[bundled] code-review — Review code changes for bugs, style issues, and improvement opportunities
[bundled] summarize — Summarize files, directories, or project structure into concise overviews
[bundled] weather — Fetch current weather and forecasts for any location
启动横幅也会更新:
┌ openclaw-mini
│ model: anthropic/claude-sonnet-4-20250514
│ workspace: /Users/you/project
│ session: mini-1719432000000
│ context: CLAUDE.md
│ tools: read, bash, edit, write, web_fetch, web_search
│ skills: git-commit, code-review, summarize, weather ← new
└ /new /think /model /skills /quit ← /skills added
捆绑的四种技能
openclaw-mini 随附四种技能,展示了不同的模式:
git-commit — 工具编排
仅使用 bash 。链式 git status → git diff --staged → 分析 → git commit → git log 。展示技能如何将多个顺序工具调用编排成一个连贯的工作流程。
code-review — 结构化输出
使用 bash 和 read 。定义特定的审查清单(正确性、安全性、性能、可读性、错误处理)和输出格式(关键问题 / 建议 / 细节问题 / 积极反馈)。展示技能如何强制执行一致的输出结构。
summarize — 多模式行为
使用 bash 和 read 。根据输入(单个文件与目录与完整项目)表现不同。展示技能如何定义基于不同上下文的条件工作流。
weather — 自定义工具集成
专用于 web_search 。展示了技能与自定义工具无缝协作,而不仅限于 SDK 内置工具。还展示了非编码用例——技能不仅限于工程工作流程。
创建自己的技能
创建一个技能只需 60 秒:
# 1. Create the skill directory
mkdir -p ~/.openclaw-mini/agents/main/agent/skills/my-skill
# 2. Write the SKILL.md
cat > ~/.openclaw-mini/agents/main/agent/skills/my-skill/SKILL.md << 'EOF'
---
name: my-skill
description: One-line description of what this skill does
---
Step-by-step instructions for the agent...
1. First, do X using `tool_name`
2. Then, do Y
3. Finally, present the result in this format:
Output template here
EOF
# 3. Restart openclaw-mini — your skill is automatically discovered
可以尝试的技能想法:
pr-review— 获取一个 GitHub PR 并使用团队检查清单进行评审test-writer— 读取源文件并生成单元测试explain— 生成复杂函数的逐步解释migrate— 指导数据库迁移工作流deploy-check— 部署前验证清单
如何协同工作
以下是从启动到技能执行的完整流程:
Startup
│
├─ discoverSkills()
│ ├─ Scan user skills dir (~/.openclaw-mini/.../skills/)
│ ├─ Scan project skills dir ({cwd}/.openclaw-mini/skills/)
│ └─ Scan bundled skills dir ({repo}/skills/)
│ └─ Deduplicate by name (first-match-wins)
│
├─ formatSkillsForPrompt(skills)
│ └─ Generate XML catalog (names + descriptions only)
│
├─ buildSystemPrompt({ ..., skillsPrompt })
│ └─ Inject "## Skills" section with catalog
│
└─ Print startup banner with skill names
User prompt: "/skill:git-commit stage and commit my changes"
│
├─ SDK's _expandSkillCommand("git-commit")
│ ├─ Search additionalSkillPaths for git-commit/SKILL.md
│ ├─ Read file contents
│ └─ Inject full instructions into prompt
│
├─ Model sees: system prompt + skill catalog + full git-commit instructions + user message
│
└─ Model executes:
├─ bash("git status")
├─ bash("git diff --staged")
├─ [analyzes changes, picks commit type]
├─ bash("git commit -m 'feat(auth): add OAuth2 login flow'")
└─ bash("git log --oneline -1")
变更内容:差异对比
整个技能系统都包含在这些更改中:
没有新的依赖项。没有新的构建步骤。未使用的技能没有运行时开销。
为什么这种架构有效
- 零成本抽象 — 技能是 Markdown 文件。没有插件运行时,没有沙箱,没有 IPC。所谓的“执行引擎”就是 LLM 本身,它遵循指令并调用自己已有的工具。
- 渐进式披露 — 系统提示词始终保持简洁,无论安装了多少技能。完整说明仅在调用时加载。
- 覆盖层级 — 用户 > 项目 > 打包。团队可以通过项目技能标准化工作流程。个人可以自定义而不需要分叉。
- 模型原生 — 技能不会与模型冲突;它们会引导模型。一个技能本质上是一个精心设计的提示词,在正确的时间注入。模型的推理和工具调用能力来完成剩余工作。
- 可组合 — 技能使用与自由形式提示词相同的工具。模型可以自然地组合技能工作流与临时指令。
下一步是什么
技能系统开辟了几个方向:
- 技能参数 — 向技能传递参数(例如,
/skill:review --scope=staged) - 技能链式调用 — 一个技能调用另一个技能
- 社区技能注册表 — 通过 npm 或中央仓库共享技能
- 条件工具需求 — 技能声明所需的工具
- 技能模板 — 使用 openclaw-mini skill:create
构建新技能
但是基础已经在这里:一个 200 行的插件系统,可以将 Markdown 文件转换为可重用的智能体工作流。无需编写代码。
openclaw-mini 的完整源代码(包括技能系统)可在 GitHub 上获取。第一部分涵盖了构建基础智能体;本文则介绍了如何添加技能插件系统。
如果你构建了一个酷炫的技能,我很乐意听到相关信息。