本文档不描述任何具体业务流程,只讲「方法论」:把一套重复发生、涉及多工具协作的人工流程,如何拆解、封装、编排成一份 AI 可稳定复用的 Skill。
一份好的 Skill,本质是把你脑子里的「工作流」显式化,并让 AI 能以最少的询问、最少的状态维护、最少的错误把它跑完。
在动手写任何一份 Skill 之前,你必须接受一个核心认知:
Skill 不是"让 AI 变聪明"的魔法,而是把你脑子里本来就清晰的流程,写下来让 AI 照着跑。 如果流程本身是模糊的、靠经验拍脑袋的、每次做法都不一样的——那不是 AI 能解决的问题,而是你自己还没想明白。
AI 在执行指令时有一个很明显的特征:
模糊指令的典型长相:
这些在人类工程师看来"大家都懂"的话,对 AI 来说都是灾难级的不确定性。AI 会在这些模糊点上开始猜测,而每一次猜测都是一次出错的机会。
一条合格的流程,哪怕完全不用 AI、全部交给人来做,也应该能满足:
一个简单的自测方法:假设你明天离职,让一个新入职的同事只看你写的文档就能把这个流程完整跑一遍——如果能,这流程才值得 Skill 化;如果不能,先把文档补齐,再谈自动化。
在你打开编辑器准备写 SKILL.md 的第一行之前,先拿一张纸,老老实实回答清楚下面三个问题:
如果第 1 题答不上:你还没想清楚要做什么,不要写。 如果第 2 题答不出清晰的步骤:先人工跑几次把路径摸出来,不要写。 如果第 3 题答不上:可能这件事根本不值得 Skill 化,不要写。
一个很常见的误区是:跳过前两步,直接让 AI 来救场。
正确的演进顺序应该是:
[人工操作] ——原始状态,完全靠经验
↓ 显式化
[文档化的 SOP] ——换个人能看懂、能跑
↓ 自动化
[脚本化的 SOP] ——关键步骤有命令封装,不用记忆细节
↓ 智能化
[AI 可执行的 Skill] ——AI 串联脚本、推断参数、处理异常
每一层都是上一层的加速器,而不是替代品。 如果你跳过中间两步,直接从「模糊的人工操作」跨到「让 AI 来搞定」,那 AI 拿到的就是一堆前后矛盾的模糊指令,准确度必然大幅下降。
一句话总结:AI 不是在"帮你想",AI 是在"帮你跑"。想清楚是你的事,跑得快才是 AI 的事。
同时满足下列多数条件的流程,收益最大:
反例(不适合):一次性任务、需要大量人工判断、没有稳定工具入口的流程。
在开始写 Skill 之前,先把下面 6 条原则想清楚。这比任何格式模板都重要。
不要让 AI 在流程开头问一堆「请告诉我 xxx」。凡是能从对话上下文、工作区路径、已有配置文件里推断出的信息,都让 AI 自己推断。
举例:你要 AI「调试某个服务的某个接口」,服务名在你自然语言里已经说了,工作区路径 AI 也能看到,这些都不该作为参数询问。
落实方式:在 SKILL.md 的开头列一张「信息来源表」,明确告诉 AI 每个关键变量从哪里来、以及什么条件下需要主动获取。
| 信息 | 来源 | 何时主动获取 |
|---|---|---|
| 变量A | 用户自然语言 | 永不主动问 |
| 变量B | 工作区路径 | 永不主动问 |
| 变量C | 本地配置文件 | 文件不存在或字段为空时,执行 <发现命令> |
多层状态(session 缓存 + 磁盘缓存 + 环境变量 + .env ...)是 Skill 腐烂的根源。
规则:
$HOME/.<your-skill>/env)。chmod 600)。reset 子命令用于彻底清空。反模式:把任何「可以当场 docker ps 查出来」「可以当场从 git 推断出来」的值写进缓存。每次查永远比维护缓存更稳。
不要设计「必须先跑 A,再跑 B,再跑 C」的死板流程,那样 AI 和人都会出错。
正确做法:每个子命令自己检查并补齐它需要的前置依赖。
子命令 X 执行时:
if 需要变量 V 且 V 不存在:
自动执行获取 V 的逻辑
if 凭证过期(如 HTTP 401):
自动刷新一次后重试
执行真正的业务逻辑
这让 AI 能直接「说干就干」,不需要在脑子里维护一张依赖顺序表。
把你能封装的所有「细碎命令」,都收敛到一个脚本的若干子命令下。
<entry> discover # 发现环境
<entry> login # 登录
<entry> ensure-xxx # 懒加载式准备某资源
<entry> call <...> # 核心业务调用
<entry> env # 导出可被 eval 的环境变量
<entry> env-reset # 重置
把零散命令整合成统一入口,带来的收益是叠加的,不是线性的:
curl,明天换 SDK,后天接内部 RPC,SKILL.md 和 AI 的使用姿势都不用改。这是长期维护性最大的来源。一句话:统一入口 = 把流程的复杂度从"AI 对话层"下沉到"代码层"。代码是稳定的,对话是概率的。
第一步:把所有人工操作列出来
把你在这个流程里会用到的所有命令,不分大小全部列出来。比如:
curl -X POST https://<host>/oauth/login ...
curl -X GET https://<host>/api/v1/xxx -H "Cookie: ..." -H "Accept: ..."
docker exec -it <ctr> sh -c "..."
<cli-tool> resource list --format json | jq '.items[].id'
cat ~/.something/token
openssl rand -hex 16
...
第二步:按"领域"而不是"工具"分组
不要按「curl / docker / cli」分组——那是工具视角。要按「这个命令解决什么问题」分组——这是业务视角。
【发现类】:找出运行环境信息(host / client_id / user_id)
【鉴权类】:登录、拿 token、刷 cookie
【业务类】:调用具体接口、查询具体资源
【环境类】:导出环境变量、重置状态
第三步:每个分组收敛成一个子命令
每个子命令应该:
discover / login / call / ensure-token / reset,而不是名词。ensure-* 系列)。第四步:子命令之间用"懒加载"串联,而不是"硬编码顺序"
错误做法:
# 要求用户必须按这个顺序跑,错一步全盘皆输
<entry> discover
<entry> login
<entry> ensure-token
<entry> call /api/xxx
正确做法:每个子命令自己检查依赖,缺啥补啥:
def cmd_call(path):
ensure_host() # 没发现过?自己 discover
ensure_cookie() # 没 cookie?自己 login
resp = http_get(path)
if resp.status == 401:
refresh_cookie() # 过期了?刷新一次再来
resp = http_get(path)
return resp
这样 AI 只要知道「我要调某个接口」,直接 <entry> call /xxx 就行,前置步骤不用它操心。
假设原本要调一个需要登录态的接口,散装命令是这样的:
# ❌ 封装前:AI 要管 5 件事,每件都可能出错
HOST=$(curl -s -I http://localhost/oauth/login | grep -i location | sed ...)
COOKIE=$(curl -s -X POST "https://$HOST/oauth/login" -d '...' -c - | grep SESSION | awk '...')
if [ -z "$COOKIE" ]; then echo "login failed"; exit 1; fi
RESP=$(curl -s "https://$HOST/api/v1/xxx" -H "Cookie: $COOKIE" -H "Accept: application/json")
echo "$RESP" | jq .
问题:
封装后:
# ✅ 封装后:AI 只要一行
<entry> call /api/v1/xxx
AI 看到的 SKILL.md 里只需要写:
调接口:
<entry> call <path> [METHOD] [body],会自动处理登录、401 刷新、Accept 头。
信息量从 5 个命令 × 若干参数,压缩到了 1 个命令 + 1 行说明。这就是封装带来的确定性红利。
| 该封装 | 不该封装 |
|---|---|
| 多步、有状态、鉴权相关的操作 | 单条、无状态、纯查询的命令(如 ls、pwd) |
| 涉及 JSON 解析、错误重试的逻辑 | 标准工具已经做得很好的操作(如 git status) |
| 跨工具串联(curl + jq + docker) | 只调一次且未来不会再调的一次性命令 |
| 业务概念("创建一个测试环境") | 物理动作("执行 docker run")—— 后者应是封装内部实现 |
判断标准:如果一个操作需要 AI 「想一下怎么组合」,那就该封装;如果 AI 一条命令就能搞定,不需要封装。
让 SKILL.md 里出现的每一条命令,都是"业务语言"而不是"工具语言"。
<entry> call /api/v1/xxx、./ops.sh start <service>、<entry> ensure-tokencurl -X POST ... -H ... -d ...、docker exec -it ... sh -c '...'、jq '.items[].id'AI 读 SKILL.md 的时候,看到的应该是一串串语义清晰的动作,而不是一堆 flag 的排列组合。
subprocess 去做容器操作。Skill 最怕「跑到一半不知道发生了什么」。每一步都要保证:
stop / cleanup 命令,执行后回到干净状态。your-skill/
├── README.md # 给人看:这个 Skill 是什么、怎么用、FAQ
├── SKILL.md # 给 AI 看:完整 SOP(这是 Skill 的"主文件")
└── scripts/
├── <entry>.py # 统一入口(推荐 Python,标准库)
├── <ops>.sh # 系统级操作(容器/进程/文件)
└── 00-ensure-<dep>.sh # 前置依赖自动安装
| 文件 | 读者 | 内容侧重 |
|---|---|---|
| README.md | 人类开发者 | 「它能干什么、我为什么要用它、快速上手」 |
| SKILL.md | AI | 「在什么情况下触发、完整的 Step 1~N、每一步用哪个命令、边界条件、速查表」 |
二者有少量重复是正常的,但不要互相复制粘贴——写给人和写给 AI 的结构不同。
下面是一个与业务无关的通用骨架。你的 SKILL.md 照着填就行。
---
name: your-skill
description: 一句话说明「做什么」+「什么场景触发」。AI 靠这段文字决定是否加载,写得越精准命中率越高。
---
写好 description 的要点:动词 + 领域 + 触发条件。避免「这是一个工具」这类空话。
在所有步骤之前,集中声明贯穿全文的约定:
一张表把所有子命令列完,AI 不用翻详细步骤也能大致找到需要的命令。
每一步都按下面的结构写:
显式写出「改代码 → 重启 → 重试」之类的最短循环,让 AI 在多轮对话里能稳定复用。
强制要求 AI 在任务结束时执行清理命令。否则你会留下一堆"测试残留"。
任何 Skill 的统一入口,至少应该提供:
| 类别 | 子命令 | 用途 |
|---|---|---|
| 发现 | discover | 探测环境、填充持久化变量 |
| 鉴权 | login / token-ensure | 懒加载式获取/刷新凭证 |
| 核心动作 | call / run / do | 实际业务调用,失败自动重试一次 |
| 环境注入 | env | 打印 export 语句,供 eval "$(... env)" |
| 重置 | env-reset | 一键回到初始状态 |
start 若容器已在运行:要么复用,要么先清理再启动,不要报错停住。stop 若容器不存在:正常退出,不要报错。restart = stop + start,不要写成两个独立的子命令组合。如果你的流程会在容器里启动服务,不要依赖 docker logs——它只抓 PID 1 的输出,容器内用 exec 启动的进程日志进不去。
规范做法:
$CODE_ROOT/.<skill>-output-<ctx>.log。tail。| 反模式 | 为什么不行 | 正确做法 |
|---|---|---|
| SKILL.md 里写「请先执行 A,再 B,再 C」 | AI 很容易跳步或搞错顺序 | 让每个子命令懒加载自己的依赖 |
| 关键变量从多个地方读取 | 状态不一致时难排查 | 单一持久化文件 + 单一加载函数 |
| 在 Bash 里解析复杂 JSON | 脆弱、难调试 | 换 Python 标准库 json |
| 把 Token 写进代码仓 | 安全事故 | 只写入 $HOME/.xxx/env,权限 600 |
| 每次让用户确认一堆参数 | AI 体验差,用户还不如自己点鼠标 | AI 从上下文推断,只在真歧义时才问 |
| 缓存「当场查很快」的值(如容器名) | 容易过时 | 每次 docker ps 现查 |
| 没有 cleanup 子命令 | 测试资源越积越多 | 提供幂等的 stop / env-reset |
| description 写成「这是一个 xx 工具」 | AI 加载不准 | description 包含动词 + 场景 + 触发词 |
如果你打算从头构建一份 Skill,推荐按这个顺序:
discover 和核心 call,其他子命令逐步加。一份好的 Skill,核心不是「文档写得多详细」,而是:
让 AI 能像一个熟练的工程师一样,以最少的提问、最少的错误、最短的路径,跑完你最擅长的那套流程。
做到这一点,只需要坚持三件事:单一入口、懒加载、最小持久化。其他所有细节都是从这三条原则自然长出来的。