你有没有遇到过这种情况:给 AI Agent 写了一个 Skill,第一次跑得挺顺,第二次换个场景就开始飘,隔几天再用还冒出一个莫名其妙的 bug。
这类问题很像写脚本。一个脚本在自己电脑上跑通,和一个工具能交给团队稳定使用,中间隔着的不是语法,而是工程化。Skill 也是一样。它不是把提示词写长一点,也不是把 API 文档塞进去就完事了。
专业的 Skill,本质上是在给 Agent 写一份可执行、可判断、可回退的工作手册。它要解决的不是“Agent 知不知道做什么”,而是“Agent 在不同上下文里,能不能稳定做对”。
一、Skill 的边界先要想清楚
很多 Skill 写不稳,问题一开始就埋下了:作者把它当成 prompt 写。
Skill 更接近 Agent 的操作手册。它通常要回答四个问题:
- 什么时候应该触发
- 具体按什么步骤执行
- 出错时怎么判断和回退
- 做完后怎么验证结果
一个典型 Skill 的目录结构大概是这样:
my-skill/
├── SKILL.md # 核心指令,必需
├── scripts/ # 可执行脚本
│ └── helper.py
├── references/ # 参考文档
│ ├── api.md
│ └── best-practices.md
└── templates/ # 模板文件
└── output.md
SKILL.md 是唯一必需文件,但专业 Skill 和随手写的 Skill,差距往往就在这些辅助目录里。
原因也简单:真实任务里,Agent 很少只靠一段自然语言就稳定完成复杂流程。它需要明确的输入输出、约束条件、异常分支,以及可以直接执行的脚本。
如果一个 Skill 只是“一段描述 一条命令”,它可以解决一次性任务。但如果这个 Skill 会被反复调用、给别人用,或者承载关键流程,就需要按工程资产来维护。
二、能跑和专业差在哪里
可以用两个层级来对比。
一个入门级 Skill,比如查询 AI 资讯,核心目标很明确:拉取最近 24 小时的数据。它的 frontmatter 可能长这样:
name: aihot
核心命令也很直接:
since=$(date -u -v-24H %Y-%m-%dT%H:%M:%SZ)
curl -s "https://aihot.virxact.com/api/public/items?mode=selected&since=$since"
这个 Skill 能不能用?当然能用。
它结构清晰,代码量小,维护成本低。如果任务边界足够窄,这种写法没有问题。工程上最怕的不是简单,而是把简单任务硬做复杂。
但换成一个公众号全流程助手,比如 WeWrite 这类写作流水线 Skill,情况会变得完全不同。它不仅要生成内容,还要处理选题、风格、素材、质量检查、禁用词、历史记录、重复规避等问题。
两者差异大概是这样:
| 维度 | 入门级 Skill | 专业级 Skill |
|---|---|---|
| Frontmatter | name description | 完整元数据、tags、related_skills、version |
| 触发条件 | 一句话描述 | 多个触发关键词 反向触发条件 |
| 工作流 | 一个主路径 | 多步骤流水线,每步有输入输出 |
| 错误处理 | 基本没有 | 每个关键步骤有 fallback 和坑位说明 |
| 辅助文件 | 很少或没有 | references、scripts、templates、personas 等 |
| 验证方式 | 人工看结果 | 自动检查、质量评分、规则扫描 |
| 迭代机制 | 临时修改 | 记录历史元数据,避免重复和回归 |
文件大小可能从十几 KB 增长到上百 KB。关键不是“写得长”,而是每一部分都在降低 Agent 的自由发挥空间。
这是专业 Skill 的核心工程取舍: 用更多前置设计,换更少运行时不确定性。
当然,这也有成本。一个一次性查询任务没必要搞 7 个 references 和 5 个 scripts。复杂度应该服务于任务稳定性,而不是服务于作者的仪式感。
三、Frontmatter 决定能不能被正确加载
Frontmatter 是很多人最容易糊弄的地方,但它其实非常关键。
Agent 在决定是否加载一个 Skill 时,最先看到的就是 name 和 description。如果这部分写得含糊,后面的工作流再专业也可能没有机会执行。
入门级写法通常是这样:
name: my-skill
问题在于太泛。
“帮我做 X 事”可能匹配大量请求,容易过度触发;也可能因为缺少关键词,在真正该触发时没有被选中。触发不准的 Skill,用起来会非常别扭:该出现时不出现,不该出现时抢活干。
更稳妥的写法应该把适用场景、触发词和排除场景说清楚:
name: commit-message-generator Use when the user wants to generate conventional commit messages based on staged git changes.
Covers feat/fix/docs/refactor/test/chore types.
Triggers on: "commit message", "生成提交信息", "git commit", "提交说明".
Do NOT use for: general text writing, PR descriptions, or changelogs.
version: 1.2.0
author: your-name
license: MIT
metadata:
hermes:
tags: [git, commit, conventional-commits, automation]
related_skills: [github-pr-workflow, github-code-review]
这里有几个细节值得注意:
description要控制长度,比如 Hermes 场景下有 1024 字符限制- 触发词要覆盖用户真实会说的话,而不是只写系统术语
- 反向触发条件很重要,它能减少误加载
version不是装饰,后续排查问题时能定位行为变化tags和related_skills有助于分类检索和关联推荐
很多团队做到这里会卡住,因为他们习惯把 Skill 当“说明文档”写,而不是当“路由规则”写。Frontmatter 的核心价值不是介绍自己,而是帮助 Agent 做选择。
四、工作流要能按图施工
入门级 Skill 常见写法是段落式 这类描述对人类还算友好,对 Agent 就有点危险。“处理一下”这种表达,会把大量判断空间交给模型。一次结果不错,不代表下次还稳定。
专业 Skill 更适合用步骤、表格、代码块组合表达。
比如一个 API 查询步骤,可以先定义参数:
| 参数 | 值 | 说明 |
|---|---|---|
| endpoint | /api/public/items |
主数据接口 |
| mode | selected |
精选模式,默认值 |
| since | 动态计算 | 最近 24 小时 |
| take | 50 |
最大返回数量 |
然后给出可执行命令:
#!/usr/bin/env bash
set -euo pipefail
UA="Mozilla/5.0 SkillRunner/1.0"
# 计算时间窗口:兼容 macOS 和 Linux
if [[ "${OSTYPE:-}" == "darwin"* ]]; then
since=$(date -u -v-24H %Y-%m-%dT%H:%M:%SZ)
else
since=$(date -u -d '24 hours ago' %Y-%m-%dT%H:%M:%SZ)
fi
curl -sH "User-Agent: $UA" \
"https://example.com/api/public/items?mode=selected&since=$since&take=50"
再明确输出格式:
[
{
"id": "item-001",
"title": "Example title",
"category": "AI",
"url": "https://example.com/item-001",
"published_at": "2026-01-01T08:00:00Z"
}
]
这比一句“调用接口获取数据”稳定得多。
这里有个很现实的工程细节:macOS 和 Linux 的 date 参数不一样。很多脚本在本地跑没问题,到了容器或 CI 里就挂了。Skill 如果面向 Agent 执行,就必须提前写清楚这些差异。
专业工作流通常还会给每一步定义输入输出:
| Step | 输入 | 输出 | 失败处理 |
|---|---|---|---|
| 拉取数据 | 时间窗口、API 参数 | JSON 数组 | 空结果时扩大时间窗口 |
| 过滤数据 | 原始 JSON | 候选列表 | 字段缺失时跳过该条 |
| 生成内容 | 候选列表、模板 | Markdown | 内容为空时回退摘要模式 |
| 验证结果 | Markdown | 检查报告 | 不通过则重新生成或提示人工确认 |
这样写的好处不是“更规范”,而是降低上下文误解。Agent 不需要猜上一步给下一步什么,也不需要猜失败时怎么办。
五、错误处理才是分水岭
入门级 Skill 通常默认一切顺利。专业 Skill 默认一定会出错。
这个判断有点残酷,但生产环境基本就是这样。接口会返回空数组,认证会过期,编码会乱码,依赖版本会变化,用户输入会缺字段。你不写,Agent 不一定能自己补对。
一个好的 pitfalls 章节,建议按“现象 → 原因 → 处理”组织。
API 返回空结果
现象:items 数组为空,但 HTTP 状态码是 200。
可能原因:
- 时间窗口内确实没有数据
since参数格式错误- 接口使用 UTC 时间,而本地时间被误用
处理方式可以写成可执行逻辑:
#!/usr/bin/env bash
set -euo pipefail
response=$(curl -sH "User-Agent: Mozilla/5.0 SkillRunner/1.0" "https://example.com/api/public/items?mode=selected&since=$since")
count=$(echo "$response" | python3 -c '
import sys, json
data = json.load(sys.stdin)
print(len(data) if isinstance(data, list) else len(data.get("items", [])))
')
if [ "$count" -eq 0 ]; then
echo "⚠️ 无数据,尝试扩大时间窗至 7 天" >&2
if [[ "${OSTYPE:-}" == "darwin"* ]]; then
since=$(date -u -v-7d %Y-%m-%dT%H:%M:%SZ)
else
since=$(date -u -d '7 days ago' %Y-%m-%dT%H:%M:%SZ)
fi
curl -sH "User-Agent: Mozilla/5.0 SkillRunner/1.0" \
"https://example.com/api/public/items?mode=selected&since=$since"
else
echo "$response"
fi
User-Agent 导致 403
现象:直接 curl 返回 403 Forbidden。
原因:部分接口会拦截默认 curl UA,或者对未知客户端做限流。
处理:
- 所有请求统一带浏览器 UA 或约定 UA
- 不要在不同步骤里临时拼 curl 参数
- 需要时把 UA 抽成变量,避免漏写
中文编码乱码
现象:终端输出中文乱码,或者写入文件后格式异常。
原因:
- 远程终端 locale 不是 UTF-8
- shell 命令中硬编码中文
- 管道处理时混用了不同编码
处理:
- 传递结构化数据时优先使用 JSON 文件
- 避免把中文直接塞进复杂 shell 参数
- 检查环境变量:
LANG、LC_ALL
专业 Skill 的 pitfalls 不应该等到最后“补一章”。更好的方式是边用边记。每次踩坑就加一条,长期看,这部分就是 Skill 的护城河。
六、别把 SKILL.md 写成垃圾桶
Skill 复杂以后,很多人会自然地把所有东西都塞进 SKILL.md。这很快会出问题。
一方面,SKILL.md 通常有大小限制。例如某些系统会限制在 100,000 字符左右,超过后可能截断。另一方面,内容太多会稀释关键路径,Agent 加载后反而抓不住重点。
更合理的拆法是:
professional-skill/
├── SKILL.md # 核心路径,控制在 8-15K 字符
├── references/
│ ├── api-reference.md # API 完整文档
│ ├── error-codes.md # 错误码对照表
│ └── best-practices.md # 最佳实践
├── scripts/
│ ├── validate.sh # 自动验证脚本
│ └── deploy.sh # 部署脚本
└── templates/
└── output-template.md # 输出模板
SKILL.md 里只保留关键路径和必要引用:
## API Reference
详见 `references/api-reference.md`,覆盖以下端点:
| 端点 | 用途 | 认证 |
|---|---|---|
| `/api/data` | 数据采集 | 无 |
| `/api/publish` | 发布内容 | OAuth |
| `/api/media` | 上传图片 | OAuth |
比较稳的分工是:
| 文件位置 | 放什么 | 不适合放什么 |
|---|---|---|
SKILL.md |
触发条件、关键流程、决策逻辑、验证入口 | 大段 API 文档、长模板、历史记录 |
references/ |
API、错误码、规范、背景资料 | 必须立即执行的核心步骤 |
scripts/ |
可运行脚本、检查工具、自动化任务 | 需要模型理解的策略描述 |
templates/ |
输出模板、固定格式 | 动态判断逻辑 |
这里的取舍很明确:
SKILL.md 要短到 Agent 能抓住主线,辅助文件要细到复杂任务有据可查。
七、验证机制要自动化
靠人眼检查 Skill,早期可以,长期一定会漏。
最基础的是发布前 checklist:
- [ ] Frontmatter 以 --- 开头,无前导空白行
- [ ] name ≤ 64 字符,仅使用小写字母和连字符
- [ ] description ≤ 1024 字符
- [ ] description 包含触发词和反向触发词
- [ ] 所有代码块都能直接复制运行
- [ ] curl 命令都带 User-Agent
- [ ] 时间窗口计算兼容 macOS 和 Linux
- [ ] pitfalls 至少覆盖 3 个已知错误场景
- [ ] 总文件大小 ≤ 100,000 字符
- [ ] related_skills 引用的 Skill 确实存在
更进一步,可以写一个最小验证脚本。
下面是一个示意性的质量检查工具,适合放在 scripts/validate_skill.py:
#!/usr/bin/env python3
"""
Skill 质量检查工具
用于检查 SKILL.md 的 frontmatter、长度、代码块和 pitfalls 覆盖情况。
"""
import json
import pathlib
import re
import sys
import yaml
def validate_skill(skill_path: str) -> dict:
path = pathlib.Path(skill_path)
content = path.read_text(encoding="utf-8")
issues = []
# 检查 1:frontmatter 必须在文件开头
if not content.startswith("---"):
issues.append("❌ 必须以 --- 开头,前面不能有空白行")
# 检查 2:解析 frontmatter
fm = {}
try:
parts = content.split("---", 2)
if len(parts) >= 3:
fm = yaml.safe_load(parts[1]) or {}
else:
issues.append("❌ frontmatter 未正确闭合")
except yaml.YAMLError as exc:
issues.append(f"❌ frontmatter YAML 解析失败: {exc}")
# 检查 3:name 格式
name = fm.get("name", "")
if not name:
issues.append("❌ 缺少 name")
elif len(name) > 64:
issues.append(f"❌ name 长度 {len(name)},超过 64 字符")
elif not re.match(r"^[a-z0-9-] $", name):
issues.append("❌ name 只能包含小写字母、数字和连字符")
# 检查 4:description 长度
desc = fm.get("description", "")
desc_len = len(desc)
if not elif desc_len > 1024:
issues.append(f"❌ description {desc_len} 字符,超过 1024 限制")
# 检查 5:代码块数量
code_blocks = re.findall(r"```[\s\S]*?```", content)
if len(code_blocks) 100_000:
issues.append(f"❌ 文件大小 {len(content)} 字符,超过 100,000 限制")
return {
"skill": skill_path,
"valid": not any(item.startswith("❌") for item in issues),
"issues": issues,
"stats": {
"size_chars": len(content),
"code_blocks": len(code_blocks),
"has_pitfalls": has_pitfalls,
},
}
if __name__ == "__main__":
target = sys.argv[1] if len(sys.argv) > 1 else "SKILL.md"
result = validate_skill(target)
print(json.dumps(result, indent=2, ensure_ascii=False))
运行方式:
python3 scripts/validate_skill.py SKILL.md
输出示例:
{
"skill": "SKILL.md",
"valid": true,
"issues": [],
"stats": {
"size_chars": 12458,
"code_blocks": 7,
"has_pitfalls": true
}
}
这个脚本不复杂,但价值很高。它把一些低级错误挡在提交前,比如 frontmatter 前多了空行、description 超长、缺少 pitfalls。真正麻烦的线上问题,很多就是这些小地方滚出来的。
八、写一个提交信息 Skill
用 Git 提交信息生成器做例子,能看清一个专业 Skill 的基本骨架。
先创建目录:
mkdir -p commit-gen/{scripts,references,templates}
touch commit-gen/SKILL.md
SKILL.md 的 frontmatter 可以这样写:
name: commit-message-generator Use when user wants to generate conventional commit messages from staged git changes.
Supports feat/fix/docs/refactor/test/chore/style/ci/perf/revert types.
Triggers on: "commit message", "提交信息", "git commit", "生成提交", "commit msg".
Do NOT use for: PR descriptions, changelogs, release notes.
version: 1.0.0
author: your-name
license: MIT
metadata:
hermes:
tags: [git, commit, conventional-commits, automation]
related_skills: [github-pr-workflow]
核心工作流从读取 staged diff 开始:
git diff --cached --no-color
如果没有 staged 变化,不要继续生成。直接提示用户先执行:
git add
接着根据变更内容判断 commit type:
| 变更特征 | type | 示例 |
|---|---|---|
| 新功能、新文件 | feat |
feat: add user login endpoint |
| Bug 修复 | fix |
fix: handle null auth token |
| 文档变更 | docs |
docs: update API reference |
| 重构且不改变行为 | refactor |
refactor: extract validation logic |
| 测试相关 | test |
test: add coverage for auth flow |
| 构建或 CI | ci |
ci: upgrade node version to 20 |
| 性能优化 | perf |
perf: reduce redundant DB queries |
| 格式调整 | style |
style: format lint warnings |
| 回滚提交 | revert |
revert: revert OAuth login change |
输出遵循 Conventional Commits:
type(scope): subject
body
footer
要求可以写得更具体:
- 第一行
type(scope): subject scope可选,优先取变化文件最多的目录subject控制在 50 字符以内- 正文说明 why,而不是重复 diff 里的 what
- footer 用于
BREAKING CHANGE或Closes #123
示例输出:
feat(auth): add OAuth2 Google login
- Integrate with Google Identity Platform
- Support token refresh flow
- Add unit tests for token validation
Closes #42
最后由用户确认后执行:
git commit -e -F /tmp/commit-msg.txt
这里建议保留 -e,让用户在提交前编辑。完全自动 commit 看起来很爽,但风险也高。提交信息属于项目历史的一部分,给人类最后确认权更稳。
这个 Skill 还应该有 pitfalls:
| 场景 | 现象 | 处理 |
|---|---|---|
| 空 diff | 用户没执行 git add |
提示 staged 为空,不生成提交 |
| subject 过长 | 第一行超过 50 字符 | 压缩措辞或把细节移到 body |
| scope 歧义 | 多个模块都有修改 | 取变更最多的目录,或省略 scope |
| diff 太大 | 上下文过长 | 先按文件摘要,再生成提交信息 |
| 混合变更 | 同时有 feat 和 fix | 建议拆分提交,或选择主要变更类型 |
对应 checklist:
- [ ] 支持 feat/fix/docs/refactor/test/chore/style/ci/perf/revert
- [ ] staged diff 为空时优雅降级
- [ ] subject 长度 ≤ 50 字符
- [ ] scope 可以省略,不强行编造
- [ ] diff 过大时先摘要再生成
- [ ] 使用 git commit -e -F 保留人工确认
- [ ] 兼容 Git 2.0
到这里,一个可维护的 Skill scaffold 就成型了。它不一定复杂,但关键部件齐全:触发、流程、异常、验证都有了。
九、从能跑到稳定的检查表
写 Skill 时,可以用这张表做最后检查。
| 层次 | 检查项 | 入门级 | 专业级 |
|---|---|---|---|
| Frontmatter | name / description 清晰 | ✅ | ✅ |
| Frontmatter | 包含触发词和反向触发词 | ❌ | ✅ |
| Frontmatter | 有 version / author / metadata | ❌ | ✅ |
| 结构 | 分步骤工作流 | ✅ | ✅ |
| 结构 | 表格定义参数 | ❌ | ✅ |
| 结构 | 代码块可直接运行 | ⚠️ | ✅ |
| 结构 | 明确输入输出格式 | ❌ | ✅ |
| 鲁棒性 | 有 pitfalls 章节 | ❌ | ✅ |
| 鲁棒性 | 覆盖至少 3 个错误场景 | ❌ | ✅ |
| 鲁棒性 | 处理 OS / 版本兼容 | ❌ | ✅ |
| 工程化 | 拆分 references | ❌ | ✅ |
| 工程化 | 有验证脚本 | ❌ | ✅ |
| 工程化 | 有历史记录机制 | ❌ | ✅ |
| 成本 | 总工作量 | 约 30 分钟 | 约 2-4 小时 |
这里没有绝对答案。
如果 Skill 只用一次,入门级完全够了。多写一堆结构化内容,反而浪费时间。工程上要警惕“为了专业而专业”。
但如果一个 Skill 会被频繁调用、多人复用,或者挂在关键业务流程上,就值得把它做成专业级。多花的那几个小时,通常会在后续调试、返工、误触发排查里赚回来。
专业 Skill 解决的不是“能不能做”,而是“能不能稳定做 100 次”。这也是 AI Agent 工程化里最容易被低估的一层:模型能力很重要,但把模型能力约束进可靠流程里,才是系统真正可用的开始。[DONE]



























































