上两篇讲了 Rules 和 TTSR——它们解决的是”agent 必须遵守什么”。但约束只是底线。真正让 agent 效率飞升的,是另一面:把工作流固化成可复用知识。
这就是 Skill 系统。
从 prompt 到 Skill
你跟 agent 协作一段时间后,会发现某些任务每次都要重复同一段上下文:
- “部署这个博客:先 build,再 rsync,部署前检查 SSL”
- “写一篇专栏文章:frontmatter 必须有 series 字段,排序用 pubDatetime,文件名 NN-slug.md”
- “调试前端样式:先看 bridge.css 的变量映射,再查 Starlight override”
每次手动输入这些上下文,既慢又容易漏。Skill 的本质:把这段上下文写成文件,让系统按需自动加载。
跟 Rules 的区别一句话讲清:Rules 约束 agent 不能做什么,Skill 告诉 agent 怎么做什么。
Skill 长什么样
一个 Skill 就是一个目录,最少只需要一个文件:
.omp/skills/blog-ops/├── SKILL.md ← 必须:指令 + 元数据├── scripts/ ← 可选:可执行脚本├── references/ ← 可选:参考文档└── assets/ ← 可选:模板、图标等SKILL.md 是带 YAML frontmatter 的 Markdown。下面是一个真实 Skill 的头部(省略正文):
---name: blog-opsdescription: "deploy、部署、上线、发布、同步、rollback、回滚、SSL、nginx、服务器、运维、blog.sh"globs: ["scripts/**", "astro.config.*"]---
# 运维指南
## 标准流程### 1. 部署$ bash scripts/deploy.sh三个关键字段:
| 字段 | 作用 |
|---|---|
name | Skill 标识符,skill://<name> 的寻址依据 |
description | 触发机制的核心——agent 根据这段文字决定是否加载 |
globs | 文件路径模式——注意:解析了但不用于 Skill 触发(那是 Rules 的事,详见下文) |
description 不是给人看的文档——它是 agent 的决策依据。agent 在系统 prompt 里看到所有 Skill 的 name + description,根据当前任务决定是否用 skill://<name> 加载完整内容。
多源发现:Skills 从哪里来
跟 Rules 一样,omp 扫描多个位置发现 Skills——兼容主流 AI 编程工具的目录结构:
| 优先级 | 来源 | 扫描目录 |
|---|---|---|
| 100 | omp 原生 | .omp/skills/(向上遍历)、~/.omp/agent/skills/ |
| 90 | omp 插件 | <插件包>/skills/ |
| 80 | Claude Code | .claude/skills/(向上遍历)、~/.claude/skills/ |
| 70 | Codex / Agents | .codex/skills/、.agent/skills/ |
| 30 | GitHub | .github/skills/ |
| 5 | 托管 Skills | ~/.omp/agent/managed-skills/ |
同名去重规则:高优先级赢。 你在 .omp/skills/blog-ops/ 和 .claude/skills/blog-ops/ 同时放了一个 Skill,omp 只加载前者(priority 100 > 80)。从 Cursor 或 Claude Code 转过来的 Skills 直接能用,不用迁移。
这个设计跟 Rules 兼容 .cursor/rules/、.clinerules 是同一个思路:不强迫用户重写已有资产,用优先级解决冲突。
发现流程本身也有容错:Skill 目录扫描有 5 秒超时,超时则回退到空列表——不会因为一个慢文件系统卡住整个启动。
三级渐进加载
这是 Skill 系统最关键的设计:不要一次全加载。
| 级别 | 内容 | 加载时机 | 大小 |
|---|---|---|---|
| 1. 元数据 | name + description | 始终在上下文里 | ~100 词 |
| 2. SKILL.md 正文 | 完整指令 | agent 决定触发时 | <500 行 |
| 3. 捆绑资源 | scripts/、references/、assets/ | 按需读取 | 不限 |
为什么这样设计?因为 context window 是稀缺资源。项目可能有十几个 Skill,如果全部完整加载,光 Skill 就占满上下文。三级加载让 agent 先看到”目录”(description),只在需要时才读”正文”(SKILL.md),更深层的参考资料用到才读。
举个例子。这个博客项目有 6 个 Skill:
blog-author → 写文章、改 frontmatterblog-ops → 部署、运维、SSLblog-series → 专栏连载blog-component → 创建/修改 Astro 组件blog-debug → 前端调试blog-seo → SEO 优化agent 每轮看到的是 6 条 description(总共约 200 词)。用户说”部署一下”,agent 匹配到 blog-ops 的 description 里有”部署”,才加载完整 SKILL.md。用户说”写一篇新文章”,匹配到 blog-author。两个 Skill 不会同时加载——除非任务确实涉及两方面。
为什么三级加载不会”丢失”
一个关键问题:agent 读了 Skill 正文(第二级),后面对话变长触发 compaction 时,Skill 内容会不会被压缩掉?
答案是不会。omp 的 compaction 系统有工具保护机制:通过 skill:// 协议读取的内容被标记为不可压缩、不可裁剪。agent 在第 5 轮读了 blog-ops 的完整指令,到第 50 轮 compaction 压缩了早期对话,blog-ops 的内容仍然保留在上下文里。
这个设计是三级加载能工作的底层保障。没有它,agent 读完 Skill 后聊了 20 轮,Skill 就被”挤”出去了——等于没读。
skill:// 本身是一个协议处理器(SkillProtocolHandler),不只是文件读取——它支持嵌套路径(skill://blog-ops/scripts/check.sh)、路径遍历防护(防止 ../../ 逃逸),甚至能在 bash 命令里自动展开为绝对路径。
globs 的误会
前面字段表里特别标了:globs 解析了但不用于 Skill 触发。这是个容易踩的坑。
很多人(包括我一开始)以为在 SKILL.md 里写 globs: ["src/**/*.astro"] 就能让 Skill 只在编辑 Astro 文件时触发——类似 TTSR 规则的作用域。不是的。 globs 字段在 Skill 里被解析、存储,但触发判断完全靠 agent 读 description 后自行决定。
globs 真正的用途在 Rules 那边:TTSR 规则用它限定监控范围(“这条规则只看 .ts 文件”)。Skill 和 Rules 共享 schema 定义,但触发机制完全不同:
| Rules | Skills | |
|---|---|---|
globs | ✅ 用于 TTSR 匹配范围 | ❌ 解析了但不参与触发 |
| 触发依据 | condition + globs | 完全靠 description |
写 Skill 时别指望 globs 帮你控制触发——把触发意图写进 description 才是正道。
Description 是触发的灵魂
写 Skill 最容易犯的错是 description 写得太泛或太窄:
太泛:"处理博客相关任务"——什么都匹配,等于没匹配
太窄:"执行 bash scripts/deploy.sh 部署脚本"——用户说”上线”就不触发
好的 description 是关键词覆盖 + 场景列举:
description: "deploy、部署、上线、发布、同步、rollback、回滚、SSL、nginx、服务器、运维、blog.sh"这段 description 的设计思路:
- 同义词铺满——“部署”、“上线”、“发布”、“deploy”,用户怎么叫都能命中
- 关联操作列出——rollback、SSL、nginx,覆盖运维场景的常见关键词
- 工具名提及——blog.sh,用户直接提工具名时也能触发
实际效果:用户说”帮我上线”、“部署一下”、“SSL 快到期了”、“blog.sh rollback”——这些都会触发 blog-ops,不会误触 blog-author。
渐进式 Skill 结构
当指令超过 500 行时,SKILL.md 本身要分层:
blog-series/SKILL.md ← 主文件:工作流 + 判断逻辑blog-series/references/ ├── series-mechanism.md ← 排序机制详解 └── frontmatter-spec.md ← frontmatter 规范SKILL.md 里用清晰的指针告诉 agent 什么时候读哪个参考文件:
## 专栏机制速记排序实现在 `src/utils/getSeriesPosts.ts`...
## 创作流程### A. 新建专栏1. 先想清楚弧线...2. 参考 `references/frontmatter-spec.md` 写 frontmatter这样 agent 在”新建专栏”场景下只读 SKILL.md + frontmatter-spec.md,不会加载”调整专栏结构”那段——如果那段在参考文件里的话。
从错误里长出 Skill
这个博客项目的 Skill 不是一开始设计好的,是从反复犯错里提炼的:
blog-ops 的由来:第一次部署时 agent 不知道要检查 SSL,直接 rsync 了。我骂了一句,然后把部署流程写成了 Skill。从此每次部署,agent 自动加载”先检查 SSL → build → rsync”的完整流程。
blog-series 的由来:agent 写第一篇专栏文章时把 series 字段拼错了(写成 serie),发布后 SeriesBadge 不显示。排查了半天。于是有了 blog-series——把专栏的机制速记、frontmatter 必填项、发布前检查清单全写进去。
blog-debug 的由来:调前端样式时 agent 反复在 AstroPaper 和 Starlight 两层之间迷路,不知道该改哪个 CSS 文件。于是有了 blog-debug——把双层主题架构的排查路径固化。
规律:好 Skill 不是预设的,是从 agent 犯错里长出来的。 第一次犯错就写 Skill,第二次就不会再犯。
Skill vs Rules:互补而非替代
容易混淆的一点:既然 Rules 也能注入约束,为什么还需要 Skill?
| 维度 | Rules | Skills |
|---|---|---|
| 目的 | 约束:不能做什么 | 赋能:怎么做什么 |
| 触发 | 条件匹配(TTSR)或始终注入 | agent 按需加载 |
| 内容 | 禁令 + 替代方案 | 完整工作流 |
| 粒度 | 一条规则 = 一个约束 | 一个 Skill = 一个能力域 |
实际协作中两者叠加使用:
- Rules 说”禁止
fontProviders.google()”——agent 知道不能用 - Skill 说”字体配置用
fontProviders.fontsource(),详见 bridge.css”——agent 知道怎么配
一个管底线,一个给路径。缺一个都会出问题。
Skill 工程化的几个原则
跑了几个月,总结了几条写 Skill 的经验:
1. Description 要”贪婪”
宁可多列几个同义词,也不要让 agent 漏触发。用户说”帮我发一下”,description 里没有”发”或”发布”,Skill 就白写了。
2. 正文要”讲为什么”
不要只写步骤,要写原因。agent 有理论心智——告诉它”先检查 SSL 是因为证书过期会导致全站 404”,比单纯写”先检查 SSL”更有效。agent 理解了原因,遇到变体情况也能自己判断。
3. 禁止事项要具体
跟 Rules 一样,Skill 里的”不要做 X”必须具体到可验证。“写好代码”没用,“禁止在文章正文里手写第 N 篇,交给 SeriesBadge 自动算”有用。
4. 捆绑脚本省 token
如果某个流程需要 agent 每次都写一段脚本(比如部署前的检查),不如直接把脚本放进 scripts/,Skill 里写”执行 bash scripts/blog.sh status”。agent 不用每次重写,token 省了,行为也稳定。
5. Skill 可以引用 Skill
blog-series 的开头写了”单篇文章的 frontmatter 规范参见 blog-author”。这避免了重复——两个 Skill 各管各的领域,交叉时互相引用而不是内容复制。
下一步
Rules 约束 agent 的行为边界,Skill 赋能 agent 的工作能力。但到目前为止我们讨论的都是单 agent 场景——一个 agent 独立完成所有任务。
当任务变复杂时,单 agent 会遇到瓶颈:上下文太长、工具调用太杂、子任务之间互相干扰。下一篇我们讲 omp 的多 agent 协作——怎么用 swarm 扩展让多个 agent 并行干活,以及什么时候多 agent 反而比单 agent 慢。
本文是「omp 编程工具使用探讨」专栏第 5 篇。文中的 Skill 示例全部来自这个博客的 .omp/skills/ 目录——它本身就是 Skill 工程化的实践产物。