编写与测试 agent skill
一份把「写好一个 skill」拆成五步的可执行方法论:分类 → 编写 → 触发设计 → 脚本分工 → 测试评估。适用于为任何 agent(Claude Code、Codex、Gemini CLI 等)编写、重写、审查或评估 skill。
核心心智模型
- Skill 不是一个 markdown 文件,是一个文件夹:
SKILL.md清单 + 可选的scripts/、references/、assets/。把整个文件系统当作**渐进式披露(progressive disclosure)**工具。 - 加载顺序是分层的:启动时只加载
name+description;被选中后才加载SKILL.md正文;正文里指向的引用/脚本只在需要时才读或跑。写作时要顺着这个顺序分配信息。 - 一个好 skill 有三个特征:窄契约、明确触发、具体产出。让人困惑的 skill 往往横跨多个类别、或触发条件含糊。
第一步:分类(决定要不要做、做哪一类)
先判断这个 skill 属于哪一类,横跨多类是重构信号。常见类别:
- 库/API 参考 — 帮 agent 正确使用某个库/CLI/SDK,核心是「踩坑点列表」+ 参考片段。
- 产品验证 — 描述如何测试/验证代码是否工作,常配 Playwright、tmux、无头浏览器。
- 数据获取与分析 — 连接数据与监控,含凭证、仪表盘 ID、查询工作流。
- 业务流程自动化 — 把重复工作流收敛成一条命令(站会汇报、周报、建工单)。
- 代码脚手架/模板 — 生成符合本仓库注解与约定的样板代码。
- 代码质量/审查 — 执行代码风格、adversarial review,可挂在 hook 或 CI 里。
- CI/CD 与部署 — 拉取、推送、部署、监控 PR、回滚。
- 运维手册 — 从一个现象(告警/错误)走完排查流程,输出结构化报告。
- 基础设施运维 — 日常维护与破坏性操作,需要安全护栏。
同时分清两种性质(影响测试策略):
- 能力型(capability):补模型稳定做不到的事。模型变强后可能过时,靠 eval 检测退役。
- 偏好型(preference):记录你的特定工作流。持久,但价值取决于对真实工作流的忠实度,靠 eval 验证。
第二步:编写正文
- 别说显而易见的事。 Claude 本身很会编程、也了解代码库。正文重点放在能打破模型默认思路的信息上(例如某设计系统避免 Inter 字体和紫色渐变),而不是复述常识。
- 用指令,不用信息。 模型执行指令好于推断含义。写「Always use
interactions.create()」而不是「Interactions API 是推荐做法」;写「运行完 X 才能标记完成」而不是「建议验证」。 - 建一个踩坑点(gotchas)章节。 这是信息量最大的部分。它应随着 agent 使用中暴露的失败点持续增量积累——每修一次真实失败就补一条。
- 别把 Claude 限制得太死。 Skill 复用性强,指令过于具体会削弱泛化。给它需要的信息,但留出适应具体情况的灵活性。
- 渐进式披露正文。 详细函数签名、用法示例拆到
references/api.md;最终产物模板放assets/供复制。在正文里告诉 Claude 有哪些文件、何时去读。 - 初始设置。 若 skill 需要用户上下文(如发到哪个 Slack 频道),把配置存进 skill 目录下的
config.json,未配置时让 agent 主动询问;需要结构化多选时用 AskUserQuestion。 - 记忆与数据。 需要跨次记忆时,用 append-only 日志或 JSON/SQLite。数据存稳定目录(如
${CLAUDE_PLUGIN_DATA}),别放会随升级被清空的 skill 目录内。
第三步:触发设计(description 是路由契约)
description 不是摘要,是路由元数据——启动时它是模型判断「这个请求要不要用这个 skill」的主要依据。这是结构性问题,不是文风问题。
- 写成 when / if-then 条件,而非功能说明。要同时讲清三件事:何时适用、哪类变更触发、是否可选。
- 反例(太模糊):
Run the mandatory verification stack in the monorepo. - 正例:
Run the mandatory verification stack when changes affect runtime code, tests, or build/test behavior in the monorepo. - 描述里要嵌入触发时机与产出形态。例:
Create a PR title and draft description after substantive code changes are finished. Trigger when wrapping up a moderate-or-larger change ... you need the PR-ready summary block.—— 它告诉模型:这是任务结束型、仅用于实质变更、产出是 PR 块。 - 匹配用户意图,不用 API 术语。 用用户真正会说的话描述场景。实测中,仅重写 description 就能修掉大部分「该触发却没触发」的失败。
- 经验法则:如果路由不可靠,先修元数据,再加正文/代码。
第四步:脚本分工(确定性下沉,判断留给模型)
可靠的拆分原则:
- 交给模型: 解释、比较、报告、权衡取舍——依赖上下文和判断力的部分。例如读源码推断预期行为、对比日志与预期、判断 release diff 是否有真实兼容性风险。
- 下沉到
scripts/: 确定性、可重复的 shell 工作。例如按固定顺序跑验证命令、收集日志、取上一个 release tag。判据:如果模型每次都要重新发现同一套 shell 配方,它就该变成脚本。 - 把脚本设计成小型 CLI:命令行可跑、打印确定性 stdout、失败时大声报错(附 usage)、需要时把产物写到已知路径;暴露
start/stop/status/logs/rerun之类的辅助子命令便于反复运行。
脚本能挡掉 LLM 的固有缺陷(重要踩坑):
- 无语义文本(hash、随机串)LLM 会抄错。 别让模型手抄 SHA256 之类字符串——用脚本生成好带该字段的文件,再让模型填内容。
- LLM 对时间流逝无感知,会「刻舟求剑」记住旧时间戳。别让它自行输出当前时间,让它调脚本取,例如
node -e 'console.log(new Date().toISOString())'。 - 质量门槛用代码级判断,别靠 prompt 恳求。 「不要输出空 insight」写进 prompt 弱模型会忽略;
if zero_insights: return None永不退化。凡是「必须成立」的约束,尽量落到确定性代码里。 - 约束不足时模型会自相矛盾/自作聪明;加更多约束能压制,但代价是灵活性下降——优先想能否用脚本或结构消除歧义,而不是无限堆 prompt。
第五步:测试评估(没有 eval 就别发 skill)
写代码不会不写测试,发 skill 也不该不做 eval。流程:
- 先定义成功标准(写 skill 之前就写)。 三个维度,评结果不评路径(agent 会找创造性解法,别惩罚意外但正确的路线):
- Outcome:是否产出可用结果(能编译/能渲染/API 返回有效)。这是基线。
- Style & Instructions:是否遵循约定与 skill 指令(对的 SDK、正确的 model ID、命名规范、指定格式)。
- Efficiency:耗费的时间/token/重试。最被低估的维度,回归是真实成本。
- 先手动触发几次。 用显式调用(
Use the {skill name} to do X或/skill、$skill-name)跑几遍,观察在哪出错——暴露隐藏假设(假设了不存在的依赖?跳过了该做的步骤?)。手动阶段修的每一个问题都变成一条可自动化的检查。 - 建提示集(prompt set)。 单个 skill 起步 10–20 条,取自真实用法。每条声明自己的
expected_checks。覆盖核心能力、护栏(如禁用已弃用 API)、扩展功能,以及负面测试。 - 务必包含负面测试。 加上 skill 不该触发的提示——描述过宽的 skill 会在每个请求上乱触发。
- 跑 agent 并捕获输出。 用 agent 真实体验它的方式测(通常经 CLI),捕获 response、stats、exit code。
- 写确定性检查。 每个检查是一个返回 bool 的小函数(多用 regex 针对抽取出的代码),注册进 registry,按
check_id分发,遍历每个用例。 - 对定性维度用 LLM-as-judge。 代码结构、命名、设计品味等 regex 抓不住的,用第二个模型辅助评分,并用**结构化输出(typed schema)**约束结果使其可解析可追踪。选择性使用——确定性检查快,LLM 评分有成本和延迟。
- 多次试验 + 隔离环境。 agent 非确定,每个用例跑 3–5 次看分布,不看单次;每个用例用干净环境,防止上下文串味掩盖真实失败。
- 跨 harness 测试。 同一 skill 在不同 agent 框架下行为可能不同;若跨工具使用,每个都要 eval。
- 升级与退役。 能力评估从低通过率爬到 ~100% 后,转为回归评估防倒退。定期在卸载该 skill的情况下跑评估——若仍全过,说明模型已内化其价值,可以退役。
分发与强制
- 小团队协作少数仓库:直接把 skill 提交到仓库
./.claude/skills/(或.agents/skills/)即可。注意每个提交进去的 skill 都会给上下文增加负担。 - 规模变大:做成插件 + 内部插件市场,让成员自选安装;让最有用的 skill 自然涌现,发布前要有审核机制,避免低质/重复 skill。
- 让工作流在正确时机成为强制项。 在仓库级指令文件(
AGENTS.md/CLAUDE.md)里用简短 if-then 规则声明强制 skill,高价值的放最前:例如「改动影响运行时代码/测试/构建行为时,调用$code-change-verification,通过前不得标记完成」「准备移交时调用$pr-draft-summary」。skill 编码「什么叫已验证/已就绪」,指令文件让这个定义可强制执行。 - 门控输出要可操作。 release/审查类 skill 从「安全」出发,只有 diff 有具体证据才切到「阻塞」,且每个阻塞必须附带明确的解除清单。
- 本地稳定后,再用 GitHub Action 等把同一工作流搬进 CI(手动使用阶段才是调指令、修脚本、发现边缘情况的地方)。
快速清单
- 归入单一类别,契约窄、产出具体
- description 写成 when/if-then,讲清何时适用+触发条件+是否可选,用用户意图措辞
- 正文用指令不用信息,不说显而易见的事,有踩坑点章节,不过度限制
- 确定性/重复的活下沉到 scripts(小型 CLI);hash/时间戳/硬质量门用代码而非 prompt
- 详细引用与模板拆到 references/、assets/,渐进式披露
- 有 10–20 条 prompt 的 eval harness,含负面测试,评结果不评路径,多次试验
- 需要时用 AGENTS.md/CLAUDE.md 的 if-then 规则强制触发