Agent Skill 设计

Skill可以理解为提供给Agent的可复用工作流程和领域知识,它不是一份越长越好的Prompt。

Agent Skills目前已经形成了比较统一的目录和加载方式。Agent平时只需要看到Skill的namedescription,确认任务匹配后才读取SKILL.md,其它资料继续按需加载。

flowchart LR
    A[User Task] --> B{name + description}
    B -->|No Match| C[Normal Handling]
    B -->|Match| D[Load SKILL.md]
    D --> E[Execute Workflow]
    E --> F{Need Extra Resources?}
    F -->|Knowledge| G[references]
    F -->|Deterministic Tasks| H[scripts]
    F -->|Output Resources| I[assets]

这种设计的核心就是减少上下文污染,让Agent只在需要的时候读取需要的信息。

基本结构

一个标准Skill最少只需要一个SKILL.md

skill-name/
├── SKILL.md
├── scripts/
├── references/
└── assets/

其中只有SKILL.md是必须的:

  • SKILL.md:核心工作流和规则
  • scripts/:稳定、重复、适合程序执行的操作
  • references/:API、Schema、规范、领域知识
  • assets/:模板、图片、代码骨架等输出资源

如果是在OpenAI Codex中使用,还推荐增加:

skill-name/
├── SKILL.md
├── agents/
│   └── openai.yaml
├── scripts/
├── references/
└── assets/

agents/openai.yaml是OpenAI自己的扩展,主要用于UI信息和工具依赖,不属于Agent Skills开放规范的核心部分。

如果希望Skill尽量跨Agent兼容,真正的执行逻辑应该留在SKILL.md,平台相关配置单独处理。

SKILL.md

SKILL.md由YAML Front Matter和Markdown正文组成:

---
name: code-review
description: Review code changes for correctness and maintainability. Use when the user asks to review a diff, pull request, patch, or modified source files.
---
 
# Code Review

开放规范要求namedescription必须存在。

name

name需要满足:

  • 最长64字符
  • 只能使用小写字母、数字和-
  • 不能以-开头或结尾
  • 不能连续出现--
  • 应与Skill目录名称一致

一般直接使用kebab-case

code-review
testing-code
processing-pdfs
managing-databases

不要使用过于宽泛的名称:

helper
utils
tools
files

名称本身最好就能说明Skill负责什么能力。

description

description实际上承担了Skill的触发器作用,因为Agent主要通过它判断是否需要读取这个Skill。

例如下面这种写法太宽泛:

description: Helps with code.

更合适的写法:

description: Review code changes for correctness, security, maintainability, and project conventions. Use when the user asks to review a diff, pull request, patch, or modified source files.

基本可以按照:

做什么 + 处理什么对象 + 什么情况下使用

来编写。

如果Skill容易和其它Skill产生冲突,还可以明确写出排除条件。OpenAI自己的security-best-practices就明确说明只在用户要求安全审查时触发,而普通Code Review和Debug不应该触发。

触发条件必须尽量写在description里,而不是只写在正文中;因为正文只有Skill已经触发以后才会被读取。

如果Skill经常没有被调用,首先应该检查的通常就是description

渐进加载

Skill最重要的设计原则之一是Progressive Disclosure,也就是渐进加载。

flowchart TD
    A["name + description<br/>Always Visible"]
    B["SKILL.md<br/>Loaded After Trigger"]
    C["references / scripts / assets<br/>Loaded or Executed On Demand"]
    A --> B --> C

Agent Skills规范建议SKILL.md控制在500行以内,并推荐正文保持在约5000 tokens以内。

所以SKILL.md应该更接近操作手册和导航,而不是完整知识库。

比如一个数据库Skill同时支持MySQL、PostgreSQL和SQLite,可以这样组织:

database/
├── SKILL.md
└── references/
    ├── mysql.md
    ├── postgresql.md
    └── sqlite.md

然后在SKILL.md中只负责选择:

根据当前数据库读取对应资料:
 
- MySQL:读取`references/mysql.md`
- PostgreSQL:读取`references/postgresql.md`
- SQLite:读取`references/sqlite.md`

这样处理SQLite时就不会把其它数据库资料一起放入上下文。

引用层级也不要太深,最好都由SKILL.md直接引用:

SKILL.md
├── references/a.md
├── references/b.md
└── references/c.md

而不是让a.md再引用b.md,然后继续向下套。

正文设计

Agent本身已经具有大量通用知识,所以Skill正文最值得写的是Agent无法可靠推断出来的东西,例如:

  • 项目自己的约定
  • 固定工作流程
  • 特殊边界条件
  • 容易犯错的地方
  • 必须执行的验证步骤
  • 某个工具应该在什么情况下使用

类似“什么是JSON”“为什么测试很重要”这种通用知识通常没有必要展开。

OpenAI当前的skill-creator还建议正文尽量使用直接的命令式表达,例如:

1. Read the project configuration.
2. Inspect the modified files.
3. Run the validator.
4. Fix reported errors.
5. Re-run validation before finishing.

相比“Ensure quality”“Follow best practices”这种抽象要求,明确步骤会稳定很多。

条件流程

如果不同输入应该走不同路径,应直接写明判断条件:

1. 判断任务类型:
   - 创建新文件 -> 使用创建流程
   - 修改已有文件 -> 使用编辑流程
 
2. 根据对应流程继续执行。

如果分支越来越复杂,就把具体流程拆到references/,让SKILL.md只负责选择。

默认路径

Skill最好提供一个明确默认方案,而不是给Agent很多平级选择。

推荐:

默认使用A。
只有输入为扫描件时使用B。
只有A无法保留格式时才使用C。

不推荐:

可以选择A、B或者C,根据情况决定。

前者能够减少不必要的选择,也让多次执行更加稳定。

自由度

Skill应该根据任务本身的风险决定约束程度。

flowchart LR
    A[High Freedom] --> B[Medium Freedom] --> C[Low Freedom]
    A --- A1[Analysis / Review / Writing]
    B --- B1[Fixed Workflow + Tunable Parameters]
    C --- C1[Migration / Release / Deletion / Conversion]

高自由度

任务允许很多正确方案时,只需要规定目标、原则和检查点。

例如Code Review只需要说明:

  1. 检查代码结构。
  2. 检查Bug和边界条件。
  3. 检查可维护性。
  4. 检查项目约定。

中自由度

如果已经存在推荐处理方式,但参数需要根据任务变化,可以规定:

固定工作流 + 默认工具 + 参数选择规则

低自由度

对于迁移、发布、批处理、文件格式修改等容易出错的操作,最好提供确定性的脚本和明确顺序:

python scripts/migrate.py --verify --backup

然后直接规定:

不要跳过验证。
验证失败时先修复问题,再继续后续步骤。

这类任务越稳定越好,没有必要让Agent每次重新设计执行过程。

scripts

如果Agent每次执行Skill都需要重新写一段几乎相同的代码,就应该考虑放入scripts/

比较典型的情况:

  • 文件转换
  • 数据校验
  • Schema验证
  • 固定格式生成
  • 批量处理
  • 对参数和顺序敏感的操作

例如:

skill-name/
├── SKILL.md
└── scripts/
    ├── validate.py
    └── convert.py

然后在SKILL.md中直接调用:

python scripts/validate.py output/

脚本除了节省Token,更重要的是把不应该由LLM自由发挥的部分变成确定性的程序。

能稳定用程序解决的问题,没有必要让Agent每次重新推理一次。

references

references/比较适合存放“需要理解,但不是每次都需要理解”的内容:

references/
├── api.md
├── schema.md
├── conventions.md
└── error-codes.md

关键在于SKILL.md需要明确说明什么时候读取:

- 涉及数据库查询时读取`references/schema.md`
- 涉及API调用时读取`references/api.md`
- 修改业务逻辑前读取`references/conventions.md`

不要只写:

更多资料位于references目录。

因为Agent并不知道应该什么时候读哪个文件。

对于比较长的reference,也应该尽量保持一个文件只负责一个主题。

assets

assets/中的内容一般不是为了让Agent理解规则,而是用于最终产物。

例如:

assets/
├── logo.png
├── report.docx
├── slides.pptx
└── project-template/

如果一个文件主要用于让Agent理解知识,应该放references/;如果主要用于复制、修改或嵌入最终结果,则更适合放assets/

验证闭环

优秀Skill通常不会停在“操作完成”,而是会要求Agent验证结果。

flowchart LR
    A[Execute] --> B[Validate]
    B -->|Failed| C[Fix]
    C --> B
    B -->|Passed| D[Deliver]

例如:

1. 修改文件。
2. 运行validator。
3. 如果失败,读取错误并修复。
4. 再次运行validator。
5. 只有验证通过后才能完成任务。

如果没有验证脚本,也可以根据references/style-guide.md之类的规则文档进行检查。

验证最好紧跟在容易出错的操作后面,而不是最后再一次性检查全部内容。

防错规则

Skill真正有价值的内容,很多时候不是通用最佳实践,而是Agent“看起来做得合理,实际上却会做错”的地方。

例如:

## 注意
 
- 用户表使用软删除,普通查询必须过滤`deleted_at IS NULL`
- `/health`只表示Web服务启动,完整健康检查使用`/ready`
- 数据库中的`user_id`和认证服务中的`uid`表示同一个用户。

实际使用Skill后,如果Agent出现一个可能重复发生的问题,就应该考虑把它沉淀成:

规则
工作流步骤
reference
validator
script

而不是继续增加大段解释。

优秀Skill的设计范式

从OpenAI和Anthropic现有Skill中,大致可以看到几种比较稳定的设计方式。

精确触发

OpenAI的security-best-practices会在description中同时说明:

什么时候使用
什么时候不要使用

这种方式特别适合多个Skill能力相近的情况,可以减少误触发。

工具优先

OpenAI的jupyter-notebook会优先要求使用已经准备好的模板和辅助脚本,而不是让Agent手写.ipynb JSON。

这种模式适合文件格式复杂、手工生成容易出错的任务。

Gate Skill

OpenAI的openai-platform-api-key更像一个前置检查,它不是主要业务Skill,而是负责在后续API工作开始前处理凭据相关流程。

因此Skill并不一定需要直接产生最终结果,也可以负责:

前置检查
安全门
环境检测
路由

核心流程 + 按需资料

复杂Skill不会把所有领域知识写进正文,而是:

SKILL.md -> 判断当前任务 -> 读取对应reference -> 执行

这也是最适合大型Skill长期维护的方式。

Skill设计流程

实际设计一个Skill时,可以按照下面的顺序:

  1. 先用真实任务跑Agent。
  2. 找出它容易失败、重复推理或者浪费工具调用的地方。
  3. 确定Skill负责什么,以及明确不负责什么。
  4. 先写好namedescription
  5. 将稳定主流程写进SKILL.md
  6. 将详细知识拆到references/
  7. 将重复且确定的操作做成scripts/
  8. 给关键步骤增加验证。
  9. 再使用真实任务测试。
  10. 根据实际失败继续修改。
flowchart LR
    A[Real Tasks] --> B[Observe Problems]
    B --> C[Minimal Skill]
    C --> D[Use in Practice]
    D --> E{Stable?}
    E -->|No| F[Add Rules / References / Scripts]
    F --> D
    E -->|Yes| G[Keep It Simple]

Anthropic官方也推荐先建立真实评测场景,再增加最少的指令解决这些问题,而不是一开始预测所有可能情况。

触发测试

除了测试Skill执行结果,还需要单独测试它是否会正确触发。

准备两组Prompt即可:

应该触发
不应该触发

例如一个安全审查Skill:

应该触发:
- 做一次安全审查
- 检查这段Go代码有没有安全问题
 
不应该触发:
- Review一下这段代码
- 这个NullPointerException怎么修

如果经常漏触发,一般说明description太窄;如果几乎什么任务都触发,则说明范围太宽。

这里最好重点测试与其它Skill比较接近的边界场景,而不是只测试最明显的Prompt。

Pi Agent

Pi实现了Agent Skills标准,整体设计仍然遵循前面的通用原则:启动时只收集Skill的namedescription,匹配任务后再读取完整SKILL.md

Pi本身强调最小核心和可组合扩展,因此设计Skill时,需要先判断需求到底属于Skill,还是应该放到其它机制中。

flowchart TD
    A[New Requirement] --> B{Always Needed?}
    B -->|Yes| C[AGENTS.md]
    B -->|No| D{User Invoked Prompt?}
    D -->|Yes| E[Prompt Template]
    D -->|No| F{Needs Runtime Capability?}
    F -->|Yes| G[Extension]
    F -->|No| H[Skill]
    C --> I[Pi Package]
    E --> I
    G --> I
    H --> I

Skill、AGENTS.md、Prompt Template和Extension

Pi中的几个机制解决的是不同层级的问题:

需求更适合的机制
始终需要生效的项目规范、命令和约定AGENTS.md
Agent根据任务按需加载的流程和知识Skill
用户主动执行的一段可复用PromptPrompt Template
新工具、事件拦截、UI、MCP、权限控制等运行时能力Extension
将多个资源一起安装和分发Pi Package

例如:

始终使用Java 21
提交前运行./gradlew test
项目使用Google Java Format

这类内容应该放AGENTS.md,因为每次任务都可能需要。

而:

发布新版本时按照固定流程检查版本号、生成Changelog、运行测试并创建Tag

更适合做成Skill,因为只有发布任务才需要加载。

如果只是需要一个用户主动调用的Review Prompt,例如:

/review src/main/java

使用Prompt Template会比Skill更简单。

需要注册一个新的http_request工具、连接MCP Server、拦截危险命令或者提供交互UI时,则应该使用Extension,而不是试图通过SKILL.md实现运行时能力。

Skill负责告诉模型“如何使用能力”,Extension负责真正“提供能力”。

Skill加载位置

Pi可以从多个位置发现Skill:

Global:
~/.pi/agent/skills/
~/.agents/skills/
 
Project:
.pi/skills/
.agents/skills/
 
Package:
skills/
package.json -> pi.skills
 
Other:
settings.json -> skills
--skill <path>

其中.agents/skills/更适合作为跨Agent共享位置;.pi/skills/则适合Pi专用Skill。

项目级Skill只有在项目被信任后才会加载,因此涉及第三方仓库时,不应该假设项目Skill一定已经启用。

如果Skill需要同时在Pi、Codex、Claude Code等环境使用,优先遵循Agent Skills开放标准,不依赖Pi专有行为。

Pi中的加载过程

Pi启动时会扫描Skill,只把名称和描述加入系统上下文;模型认为任务匹配后,再通过read读取完整的SKILL.md

flowchart LR
    A[Startup] --> B[Scan Skills]
    B --> C[Inject Metadata]
    C --> D{Task Matches?}
    D -->|No| E[Continue Normally]
    D -->|Yes| F[Read SKILL.md]
    F --> G[Follow Workflow]

这种方式意味着大量Skill同时安装时,虽然不会一次加载全部正文,但所有可自动触发Skill的元数据仍然会占用一部分系统上下文。

因此更适合:

  • 全局只保留高复用Skill
  • 项目专用Skill放.pi/skills/
  • 避免安装大量功能重叠的Skill
  • description保持准确,不写无关背景
  • 不需要自动触发的Skill改为显式调用

显式调用

Pi会将Skill注册为:

/skill:<name>

例如:

/skill:pdf-tools extract
/skill:release

这可以绕过模型是否主动选择Skill的问题。

Pi还支持:

---
name: release
description: Release the current project using the repository release workflow.
disable-model-invocation: true
---

设置disable-model-invocation: true后,Skill不会出现在模型自动选择的Skill列表中,只能通过/skill:release显式调用。

这比较适合:

  • 发布
  • 部署
  • 删除
  • 数据迁移
  • 高成本API调用
  • 需要用户明确表达意图的操作

对于这类Skill,与其不断调整description防止误触发,不如直接关闭模型自动调用。

触发可靠性

Pi官方文档明确说明模型并不一定总会主动读取匹配的Skill,因此不能把Skill发现机制理解成严格路由器。

关键流程需要更高确定性时,可以按照优先级处理:

  1. 优化description
  2. AGENTS.md中说明特定场景应使用哪个Skill
  3. 使用/skill:name显式调用
  4. 对必须自动执行的行为使用Extension,而不是依赖模型自行选择

例如项目中固定存在数据库迁移Skill,可以在AGENTS.md中加入:

数据库Schema发生变化时,使用`database-migration`Skill,不要手工执行迁移流程。

这里仍然保持Skill按需加载,只是在始终存在的项目上下文中增加一个轻量路由规则。

disable-model-invocation

这个字段属于Pi支持的Skill Front Matter扩展:

disable-model-invocation: true

它解决的是“Skill可以存在,但不希望模型自动调用”的需求。

可以按照下面的方式分类:

Skill类型自动调用
文档查询、代码分析、格式转换通常开启
测试、Lint、普通构建根据项目决定
发布、部署、迁移、删除通常关闭
产生费用或修改外部系统通常关闭

对于跨Agent Skill,不应该依赖这个字段保证安全,因为其它Agent可能不支持它。

真正危险的操作仍然应该由脚本、权限、Extension确认机制或外部系统进行限制。

Skill + Extension

Pi中特别适合使用的一种设计是:

Extension = Capability
Skill     = Workflow

例如MCP:

flowchart LR
    A[Extension] -->|Expose Tools| B[Agent]
    C[Skill] -->|Usage Rules| B
    B --> D[MCP Server]

Extension负责:

  • MCP连接
  • 工具注册
  • 参数Schema
  • 生命周期
  • 错误处理
  • 权限和确认

Skill负责:

  • 什么情况下使用这些工具
  • 调用顺序
  • 默认策略
  • 多工具组合方式
  • 失败后的处理方式
  • 结果如何验证

这样可以避免把工具实现和使用策略耦合在一起。

例如一个IntelliJ MCP集成,可以由Extension暴露IDE工具,而Skill只描述:

修改代码前先读取文件问题。
能够使用IDE Refactoring时优先使用Refactoring。
修改完成后执行项目Build。
Build失败时读取Problems并继续修复。

如果后续更换MCP实现,只要工具语义保持一致,Skill通常不需要整体重写。

Pi Package

当Skill需要依赖Extension时,最好一起打包成Pi Package。

my-pi-package/
├── package.json
├── extensions/
│   └── index.ts
└── skills/
    └── my-skill/
        ├── SKILL.md
        └── references/
            └── usage.md

package.json可以声明:

{
  "name": "my-pi-package",
  "keywords": ["pi-package"],
  "pi": {
    "extensions": ["./extensions"],
    "skills": ["./skills"]
  }
}

Pi也支持约定目录自动发现,因此简单Package不一定需要显式填写pi字段;但公开发布时,显式声明会更容易看出Package包含什么资源。

这种组合适合:

Extension提供工具
Skill提供工具使用方法
Package负责安装和版本管理

如果Skill只是纯Markdown工作流,没有运行时依赖,则没有必要为了Skill单独创建Extension。

名称冲突

Pi允许从全局、项目、Package和自定义路径同时加载Skill,因此更容易出现同名Skill。

发现同名Skill时Pi会给出警告,并保留先发现的Skill。

因此名称最好不要过于通用:

不推荐:
review
deploy
database
 
更合适:
java-code-review
docker-image-release
sqlite-schema-migration

即使Pi当前对Skill名称和父目录名称的匹配比开放规范更宽松,为了跨Agent兼容,仍建议保持:

skills/sqlite-schema-migration/SKILL.md
name: sqlite-schema-migration

Pi专用Skill的设计原则

在通用Skill原则之外,Pi环境还可以增加下面几条:

  1. 始终生效的规则放AGENTS.md,不要塞进Skill。
  2. 用户主动调用的简单Prompt优先使用Prompt Template。
  3. 需要新增能力时使用Extension,Skill只描述如何使用能力。
  4. 高风险Skill优先使用disable-model-invocation和显式/skill:name
  5. 项目专用Skill优先放项目目录,减少全局Skill数量。
  6. Skill与Extension强相关时,通过Pi Package一起分发。
  7. 仍然遵循Agent Skills标准,Pi专有字段只作为附加能力。
  8. 不把模型自动选择Skill当作严格可靠的路由机制。

对Pi而言,比较理想的结构不是“所有东西都做成Skill”,而是让AGENTS.md负责常驻规则、Skill负责按需工作流、Extension负责运行时能力,再由Pi Package负责组合和分发。

其它注意事项

开放规范允许在Front Matter中增加:

license:
compatibility:
metadata:
allowed-tools:

其中allowed-tools目前仍属于实验字段,不同Agent实现的支持程度可能不同。

OpenAI当前的skill-creator则更倾向于让SKILL.md只保留:

---
name:
description:
---

OpenAI自己的UI信息、图标以及MCP依赖则放进agents/openai.yaml

另外还有几个比较实用的原则:

  1. 文件引用尽量使用相对路径,并保持一层引用。
  2. 不要在Skill中增加无关的README.mdCHANGELOG.md等说明文件。
  3. 不要写大量容易过时的版本和时间判断,过时方案需要保留时单独放到Legacy部分。
  4. 同一个概念尽量保持同一种名称,不要在一份Skill里来回更换术语。
  5. 示例应该展示真正需要固定的格式或行为,不需要为了“看起来完整”而堆很多例子。

最终可以把Skill设计归纳成一句话:

description负责让Agent在正确的时候找到Skill,SKILL.md负责告诉Agent怎么做,references补充需要的知识,scripts把不应该自由发挥的部分固定下来。

参考资料