OpenCode技能文档精选排行榜
OpenCode Skills 完整指南
内容目录
- Skill 是什么
- SKILL.md 文件结构规范
- 发现路径与配置方式
- Skill、MCP、Subagent 三者对比
- 如何注入到 LLM 工作流
- 渐进式加载策略详解
- 自定义 Skill 实操步骤
- 远程 Skill 托管方案
在 AI 驱动的编程场景中,上下文窗口的管理始终是核心痛点。既要让 AI 掌握足够的项目规范与领域知识,又不能将宝贵的 token 浪费在无关内容上。OpenCode 引入了 Skills 机制,从根本上平衡信息完整性与上下文效率。这套机制虽不复杂,但设计精妙——尤其在信息与上下文的权衡上,值得深入拆解。
Skill 是什么
简单来说,Skill 是一个按需加载的 Markdown 指令文件,专门为 AI 注入专项知识或操作规范。
每个 Skill 本质上就是一个 SKILL.md 文件,包含两部分:YAML frontmatter 声明名称与用途,Markdown 正文承载具体指令、规则、示例和最佳实践。
AI 通过名为 skill 的工具按需加载这些内容,未加载的 Skill 不占用上下文。这意味着你可以将整份编码规范、框架指南全部纳入,AI 只在需要时才读取。
典型用途包括:
- 项目编码规范(命名规则、文件结构、禁止模式)
- 框架使用指南(如 Effect、React、Prisma 的正确用法)
- 工作流程规范(提交前检查清单、测试策略)
- 领域知识(业务术语定义、架构约束条件)
SKILL.md 文件格式
先看一个标准的 SKILL.md 文件长什么样:
---
name: my-skill
description: 简短描述这个 skill 的用途(AI 根据此决定是否加载)
---
# 正文内容
这里写具体的指令、规则、示例等。
## 章节一
...
## 章节二
...
Frontmatter 部分只有两个必填字段:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
name | string | 是 | skill 的唯一标识符,用于 skill 工具调用 |
description | string | 是 | 单行描述,显示在 AI 的工具描述和系统提示中 |
举一个真实项目中的例子。假设项目使用了 Effect-TS,那么在 .opencode/skills/effect/SKILL.md 里可能会写成这样:
---
name: effect
description: Guidelines for writing idiomatic Effect-TS code in this project
---
## Effect Coding Guidelines
- Always use `Effect.gen` for sequential async operations
- Prefer `pipe` over method chaining for readability
- Use `Schema` for all data validation
- Never use `Effect.runSync` in production code
## Error Handling
...
关键在于 description 要足够精准——AI 就是靠这一行来判断是否加载该 Skill。描述模糊可能导致 AI 错过,描述有误导性则可能在不该加载时将其塞入上下文。
发现路径与配置
OpenCode 按以下优先级顺序扫描 skill 文件:
内置路径(自动扫描)
| 优先级 | 路径 | 用途 |
|---|---|---|
| 1 | ~/.claude/skills/ | Claude 全局 skills |
| 2 | ~/.agents/skills/ | 通用 agent skills |
| 3 | <项目根>/.opencode/skills/ | 项目级 skills |
| 4 | opencode.json 中 skills.paths 指定的路径 | 自定义路径 |
| 5 | opencode.json 中 skills.urls 指定的远程 URL | 远程 skills |
扫描逻辑很直接:每个子目录下只要包含 SKILL.md 文件,就自动被视为一个 skill。目录结构大致如下:
.opencode/
└── skills/
├── effect/
│ └── SKILL.md ← skill: effect
├── testing/
│ ├── SKILL.md ← skill: testing
│ └── examples.md ← 伴随文件(可在 SKILL.md 中引用)
└── commit-style/
└── SKILL.md ← skill: commit-style
配置文件(opencode.json)
{
"skills": {
"paths": [
"~/my-shared-skills",
"/team/shared/opencode-skills"
],
"urls": [
"https://example.com/opencode-skills"
]
}
}
| 字段 | 类型 | 说明 |
|---|---|---|
skills.paths | string[] | 额外扫描的本地目录(支持 ~ 展开) |
skills.urls | string[] | 远程 skill 包的 URL(见远程托管) |
与 MCP、Subagent 的区别
这是很多人容易混淆的地方。Skill、MCP Tool、Subagent 虽然都用于扩展 AI 能力,但定位截然不同。一个表格能清晰对比:
| 维度 | Skill | MCP Tool | Subagent |
|---|---|---|---|
| 本质 | Markdown 指令文件 | 可执行工具(函数) | 独立 AI 进程 |
| 作用 | 注入知识/规范到 AI | 扩展 AI 的操作能力 | 将子任务委托给另一个 AI |
| 运行时 | 无副作用,仅读取文本 | 调用外部进程/API | 启动新的 AI 对话 |
| 上下文共享 | 在当前对话中内联 | 工具结果返回当前对话 | 独立上下文,结果汇报 |
| 定义方式 | SKILL.md 文件 | 服务器进程 + JSON Schema | 代码调用 AI API |
| 加载时机 | 按需(AI 主动调用 skill 工具) | 启动时注册到 LLM | 任务执行时动态创建 |
| 适合场景 | 编码规范、领域知识、操作指南 | 文件读写、代码执行、API 调用 | 并行任务、隔离复杂子任务 |
换个更直观的方式来理解:
- Skill = 给 AI 读的"操作手册",告诉它"应该怎么做"
- MCP Tool = 给 AI 用的"工具箱",让它能"做某件事"
- Subagent = 给 AI 雇的"助手",让它"帮你做某件事"
如何注入到 LLM
Skills 采用两阶段注入机制,核心目标是在信息可见性与上下文效率之间找到平衡。
阶段一:系统提示列表(每次请求都包含)
每次 LLM 调用时,系统提示中自动插入所有可用 skill 的概览。大致格式如下:
effect
Guidelines for writing idiomatic Effect-TS code in this project
testing
Testing strategy and patterns for this codebase
commit-style
Commit message format and branch naming conventions
AI 看到这个列表后,就知道哪些 skill 可用,然后根据当前任务判断是否需要加载。同时,skill 工具的描述中也会嵌入可用 skill 列表(简短格式),供 AI 调用时参考:
Load the content of a skill.
A vailable skills:
- effect: Guidelines for writing idiomatic Effect-TS code in this project
- testing: Testing strategy and patterns for this codebase
- commit-style: Commit message format and branch naming conventions
阶段二:按需加载(AI 主动调用 skill 工具)
当 AI 判断某个 skill 与当前任务相关时,会调用 skill 工具:
{
"tool": "skill",
"input": { "name": "effect" }
}
工具返回完整内容:
## Effect Coding Guidelines
- Always use `Effect.gen` for sequential async operations...
packages/core/src/effect-utils.ts
packages/core/src/schema.ts
返回内容包含两部分:skill 的完整 Markdown 指令正文,以及相关文件列表(最多 10 个)。文件列表怎么来的?OpenCode 用 ripgrep 在项目中搜索与 skill 同名的文件,帮助 AI 快速定位相关代码。
完整流程示意
整个流程走下来是这样的:
会话开始
│
▼
系统提示构建
├─ ...其他系统提示内容...
└─ 列表(所有已发现的 skill 名称+描述)
│
▼
LLM 收到请求
├─ 看到 列表,了解有哪些 skill
└─ 根据任务决定是否调用 skill 工具
│
(AI 决定加载某个 skill)
▼
AI 调用: skill("effect")
│
▼
OpenCode 读取 SKILL.md
├─ 解析 frontmatter
├─ 返回完整 Markdown 内容
└─ 附加相关文件列表(ripgrep 搜索)
│
▼
AI 将 skill 内容纳入上下文
└─ 按 skill 指令执行后续任务
渐进式加载策略
渐进式加载(Progressive Loading)是这套机制中最值得深究的部分。它描述的是一个从粗到细、按需展开的资源获取方式。不是一次性将所有相关信息塞入上下文,而是从极低代价的元信息开始,随着任务推进逐步加载更具体的内容。
三个层次
层次 1:描述(系统提示中的 skill 列表)
↓ AI 判断与当前任务相关
层次 2:规则(SKILL.md 正文 + 文件列表)
↓ AI 判断需要了解具体实现
层次 3:代码(SKILL.md 中列出的资源文件)
| 层次 | 内容 | 上下文代价 | 何时触发 |
|---|---|---|---|
| 描述 | skill 名称 + 一句话说明 | 极低(每个 skill ~20 tokens) | 每次请求自动包含 |
| 规则 | SKILL.md 完整正文 + 相关文件列表 | 中(几百到几千 tokens) | AI 调用 skill 工具时 |
| 代码 | 文件列表中的实际源码文件 | 高(按文件大小) | AI 主动读取文件时 |
核心思想很简单:只有真正需要某个层次的信息时,才付出对应的上下文代价。
复杂示例:实现一个新的业务 Service
假设项目背景是这样的——目录结构如下:
.opencode/skills/backend/
├── SKILL.md ← 规则:Service 编写规范
├── service-template.ts ← 资源:Service 模板
├── error-codes.md ← 资源:错误码定义表
└── existing-service-example.ts ← 资源:现有 Service 示例
现在有一个任务:"帮我实现 OrderService,支持创建订单和查询订单详情"
第 1 步:层次 1 — AI 看到描述,决定加载
系统提示中包含这样的内容:
backend
Service layer patterns, dependency injection rules, and error handling conventions
...
AI 识别到任务需要编写 Service,于是决定加载 backend skill。此时上下文只有一句话描述,代价极低。
第 2 步:层次 2 — AI 加载 SKILL.md,获得规则 + 文件列表
AI 调用:
{ "tool": "skill", "input": { "name": "backend" } }
返回的内容包含具体的规则和文件列表:
## Service 编写规范
### 结构要求
- 每个 Service 必须通过 `Effect.Service` 定义,不能用普通 class
- 依赖其他 Service 通过构造参数注入,禁止在方法内直接 import
- 所有公开方法返回 `Effect`,不允许 throw
### 错误处理
- 业务错误使用 `error-codes.md` 中定义的错误码
- 数据库错误统一包装为 `DatabaseError`,不能透传 Prisma 错误
### 命名约定
- 文件名:`.service.ts`
- 查询方法:`find*`(单个)、`list*`(列表)
- 写入方法:`create*`、`update*`、`delete*`
.opencode/skills/backend/service-template.ts
.opencode/skills/backend/error-codes.md
.opencode/skills/backend/existing-service-example.ts
packages/api/src/services/user.service.ts
packages/api/src/services/product.service.ts
现在 AI 已经知道了三件事:Service 的结构规范(Effect.Service、依赖注入、返回类型)、错误处理约定、以及有哪些资源文件可以参考。但此时还没有读取任何资源文件,代价只有 SKILL.md 正文的 tokens。
第 3 步:层次 3 — AI 按需读取资源文件
AI 根据任务复杂度决定读取哪些文件。通常先读模板文件,因为它直接给出了代码骨架:
// service-template.ts
import { Effect, Layer } from "effect"
import { PrismaService } from "./prisma.service"
export class TemplateService extends Effect.Service()("TemplateService", {
effect: Effect.gen(function* () {
const prisma = yield* PrismaService
return {
findById: (id: string) =>
Effect.tryPromise({
try: () => prisma.client.template.findUniqueOrThrow({ where: { id } }),
catch: (e) => new DatabaseError({ cause: e }),
}),
}
}),
}) {}
export const TemplateServiceLive = TemplateService.Default
有了这个骨架,AI 就知道如何套用到 OrderService 上了。接着,如果需要处理错误,可以去读错误码表:
| 错误码 | 类名 | 含义 |
|--------|------|------|
| ORDER_NOT_FOUND | OrderNotFoundError | 订单不存在 |
| ORDER_ALREADY_PAID | OrderAlreadyPaidError | 订单已支付 |
| INSUFFICIENT_STOCK | InsufficientStockError | 库存不足 |
如果模板已经足够清晰,AI 甚至可以跳过 existing-service-example.ts 和 user.service.ts,省下更多上下文空间。
最终上下文消耗对比:
全量预加载(假设):
5 个技能 × 平均 2000 tokens = 10,000 tokens(大量无关内容)
渐进式加载(实际):
层次 1:80 tokens(5 个 skill 的描述)
层次 2:800 tokens(backend SKILL.md 正文)
层次 3:600 tokens(service-template.ts + error-codes.md)
─────────────────────
合计:约 1,480 tokens(节省约 85%)
资源文件的两个来源
skill 工具返回的文件列表来自两处,AI 会根据相关性选择性读取:
| 来源 | 示例 | 特点 |
|---|---|---|
| skill 目录中的伴随文件 | service-template.ts、error-codes.md | 由 skill 作者精心准备,直接相关 |
| 项目中同名搜索结果 | user.service.ts、product.service.ts | ripgrep 搜索 skill 名称找到的真实代码 |
伴随文件是"教材"(规范示例),项目文件是"参考实现"(已有代码的惯用法)。两者结合,AI 既了解规范,又了解当前项目的实际写法。
如何在 SKILL.md 中引导渐进式加载
在 SKILL.md 正文中主动告诉 AI 什么时候该读哪个文件,可以让加载行为变得更可预测:
---
name: backend
description: Service layer patterns, dependency injection rules, and error handling conventions
---
## 使用指引
**新建 Service 时:** 先读 `service-template.ts` 获取骨架,再根据需要查阅 `error-codes.md`
**排查错误时:** 直接查阅 `error-codes.md` 中的错误码定义
**不确定惯用法时:** 参考 `existing-service-example.ts` 或项目中现有的 `*.service.ts`
## 规则
...
这样,AI 在读完 SKILL.md 后就能精准判断下一步应该读哪个文件,而不是盲目地读取所有列出的文件。
如何自定义 Skill
步骤一:创建目录结构
# 项目级 skill(推荐)
mkdir -p .opencode/skills/my-skill
# 或全局 skill(所有项目可用)
mkdir -p ~/.claude/skills/my-skill
步骤二:编写 SKILL.md
---
name: my-skill
description: 描述这个 skill 的用途(简洁,一句话)
---
# My Skill
## 规则
1. 规则一:...
2. 规则二:...
## 禁止事项
- 不要做 X
- 避免 Y 模式
## 示例
...
编写时注意几点:description 一定要精准,AI 靠它决定是否加载 skill;正文使用清晰的 Markdown 结构,便于 AI 理解;可以包含代码示例、对比示例(好/坏);保持专注,一个 skill 只解决一类问题。
步骤三:添加伴随文件(可选)
可以在同一目录放置辅助文件,比如模板、示例等。AI 调用 skill 时会在文件列表中看到它们:
.opencode/skills/my-skill/
├── SKILL.md ← 主文件(必须)
├── template.ts ← 模板文件
└── examples.md ← 详细示例
步骤四:验证
重启 OpenCode 后,在对话中测试:
请使用 my-skill skill 帮我...
或者直接询问 AI 有哪些可用的 skill。
远程 Skill 托管
可以将 skills 托管在 HTTP 服务器上,方便团队共享。
服务器目录结构
https://example.com/opencode-skills/
├── index.json ← 索引文件(必须)
├── effect/
│ └── SKILL.md
└── testing/
└── SKILL.md
index.json 格式
{
"skills": [
{
"name": "effect",
"files": ["effect/SKILL.md"]
},
{
"name": "testing",
"files": ["testing/SKILL.md", "testing/examples.md"]
}
]
}
| 字段 | 类型 | 说明 |
|---|---|---|
skills | array | skill 列表 |
skills[].name | string | skill 名称(对应目录名) |
skills[].files | string[] | 该 skill 包含的文件路径(相对于 URL 根) |
配置使用
{
"skills": {
"urls": [
"https://example.com/opencode-skills"
]
}
}
远程 skill 下载后会被缓存到 ~/.cache/opencode/skills/,避免每次重复下载,这一设计考虑周到。
源码位置参考
| 功能 | 文件 |
|---|---|
| Skill 服务(发现、加载、fmt) | packages/opencode/src/skill/index.ts |
| 远程 skill 下载 | packages/opencode/src/skill/discovery.ts |
skill 工具定义 | packages/opencode/src/tool/skill.ts |
| 工具描述(含 skill 列表) | packages/opencode/src/tool/skill.txt |
| 系统提示注入 | packages/opencode/src/session/system.ts |
| 配置 schema | packages/opencode/src/config/skills.ts |
| 工具注册(enriched description) | packages/opencode/src/tool/registry.ts |
