1. SKILL.md 不是魔法咒语,而是 Agent 的“岗位说明书”
你有没有在某个技术群看到这样的截图:有人贴出一个不到20行的SKILL.md文件,然后说“把这个丢进项目里,Agent 就会自动帮我跑测试用例、生成报告、甚至给开发提 Bug”?底下一片惊叹:“真·一行代码改变命运?”——我第一次看到时也愣了三秒,赶紧翻出自己压箱底的自动化脚本对比了一下,发现不是人家代码写得玄,而是我们根本没搞懂:SKILL.md 的本质,从来就不是让 Agent “变聪明”,而是帮它“守规矩”。
这东西压根儿不是 AI 模型的训练配置,也不是什么神秘 prompt 工程黑盒。它更像一份给新入职的测试工程师看的《岗位说明书》:里面不写“你要多聪明”,只写“你每天要干哪几件事”“遇到 XX 情况必须走 XX 流程”“输出结果必须长成这个样子”。而 Agent,就是那个被严格按说明书执行任务的“数字员工”。
为什么测试人尤其该盯住这个?因为测试工作的天然属性就是强流程、弱创造、高复现。写一个登录接口的自动化用例,和写一首十四行诗,对“智能”的要求天差地别。前者需要的是精准识别字段、严格校验状态码、按固定模板生成日志;后者才需要天马行空。而SKILL.md正好卡在这个黄金切口上:它不挑战模型上限,只压缩执行偏差。
我拿某次实际项目举例。团队用一个开源 Agent 框架做回归测试调度,最初大家直接扔自然语言指令:“去把昨天失败的用例重跑一遍,把结果发到钉钉”。结果 Agent 有时只重跑前5个,有时把成功用例也塞进失败报告里,甚至有次把日志里的堆栈信息当成了“失败原因摘要”直接发出去——不是模型不行,是它根本不知道“失败用例”在你们团队的定义是“status 字段为 failed 且 error_code 不为 0”,也不知道“钉钉消息”必须带 emoji 和超链接。后来我们补了一份SKILL.md,只写了三条规则:
- 所有“失败用例”指:数据库表
test_result中status = 'failed' AND error_code != '0'的记录; - 每次重跑必须调用
retry_test_case(case_id)接口,且并发数 ≤ 3; - 钉钉消息格式为:
[⚠️ 重跑完成] 共执行 {n} 个用例,{m} 个通过,{k} 个仍失败。详情:{url}。
当天下午,问题归零。这不是模型升级了,是我们终于给它发了工牌和考勤表。
提示:别被“md”后缀迷惑。
.md只是便于人类阅读和 Git 管理,Agent 解析时实际读的是结构化语义。它真正依赖的,是你在文件里埋下的可提取、可验证、可执行的约束条件,而不是 Markdown 的渲染效果。
2. 剖开 SKILL.md 的骨架:四个不可省略的“器官”
很多人以为SKILL.md就是随便列几条命令,其实它有明确的生理结构。我拆解过二十多个真实项目中的SKILL.md文件,发现所有稳定运行的版本,都包含且仅包含以下四个核心模块。少一个,Agent 就会开始“自由发挥”;多一个,反而增加解析负担。它们不是并列关系,而是有严格依赖顺序的“执行流水线”。
2.1 能力边界声明(The Scope Clause)
这是整份文件的“宪法序言”,必须放在最开头,且只能用一句话定义。常见错误是写成“支持测试用例管理、环境监控、报告生成”,这等于没说——太宽泛,无法校验。正确写法必须满足三个条件:主体明确、动作具体、对象可枚举。
比如:
- ❌ 错误示范:“能处理测试相关事务”
- ✅ 正确示范:“仅响应以
#test开头的指令,且指令中必须包含case_id或suite_name字段”
这个声明直接决定了 Agent 的“听觉范围”。没有它,Agent 会把“今天天气怎么样”也当成测试指令去解析;有了它,连#test run suite=login_v2和#test run suite=login-v2都会被判定为非法输入——因为-不在允许字符集里。我在某次压测中就靠这条规则拦住了 73% 的无效请求,避免了资源浪费。
2.2 输入协议规范(The Input Contract)
这里解决的是“你怎么跟我说话,我才听得懂”。很多测试人卡在这一步,以为只要自己懂就行,但 Agent 不是人,它没有上下文推理能力。必须把输入格式掰碎了喂给它。
关键要定义三样东西:
- 必填字段:如
case_id: string, timeout: number (unit: seconds) - 可选字段及默认值:如
env: string = 'staging', retry_times: number = 2 - 字段校验规则:如
case_id 必须匹配正则 ^TC\d{4,6}$,timeout 范围 10–300
我见过最典型的翻车案例,是某团队把env字段设为可选,默认值prod。结果运维同事随手敲了句#test run case_id=TC1001,Agent 直接冲进生产环境跑了压力测试……后来我们强制改成env: required,并在文档里加了一行红字:> 注意:未指定 env 时,Agent 将拒绝执行,绝不降级为默认值。
2.3 执行逻辑图谱(The Execution Graph)
这才是SKILL.md的心脏。它不用写代码,但必须用清晰的节点+箭头描述“接到指令后,每一步做什么、依据什么判断、走向哪里”。我坚持用 Mermaid 语法(虽然你不能用,但原理一样)来设计,因为它的 DAG(有向无环图)结构天然契合测试流程。
比如一个“失败用例分析”技能,它的逻辑图谱应该是:
[接收指令] → [查数据库获取原始日志] → [调用日志解析服务] → 判断 [error_code 是否在已知异常库中] ├─ 是 → [匹配解决方案模板] → [生成修复建议] └─ 否 → [触发人工审核流程] → [创建 Jira 任务]注意:每个节点必须标注触发条件(如“当 error_code 匹配ERR_NETWORK_TIMEOUT”)和失败兜底动作(如“若日志解析超时,则返回原始日志片段”)。我在某金融项目里发现,90% 的 Agent 异常中断,都源于某个节点没写兜底动作——比如解析服务挂了,Agent 就卡死在那里,既不报错也不跳过。
2.4 输出契约模板(The Output Covenant)
最后一步,也是最容易被忽视的“交付标准”。测试人最怕什么?不是 Agent 干不了,而是它干完了,你却看不懂它干了啥。所以输出必须像合同条款一样刚性。
必须规定:
- 结构格式:JSON / Markdown 表格 / 纯文本?
- 必含字段:
case_id,start_time,end_time,status,error_summary - 字段约束:
status只能是passed/failed/blocked;error_summary长度 ≤ 200 字符,且必须包含error_code
有一次,我们要求 Agent 输出失败用例的“根因分析”,结果它每次生成一段 300 字的散文式总结。后来在SKILL.md里加了一条硬约束:“root_cause字段必须为 JSON 对象,含category(string)、evidence_line(number)、suggested_fix(string)三个键”,问题立刻解决。不是模型不会写,是它之前根本不知道什么叫“必须为 JSON 对象”。
3. 测试人的第一份 SKILL.md:从零搭建“用例执行员”
现在,我们动手写一份真正能跑起来的SKILL.md。目标很实在:让 Agent 接收一条类似#test execute case_id=TC2024 login_staging的指令,自动在 staging 环境执行登录用例,并返回结构化结果。不炫技,只求稳。我会把每一步背后的“为什么”摊开讲,因为这才是你以后能自己写的底层逻辑。
3.1 第一步:定义能力边界(Scope)
# SKILL: Test Case Executor ## Scope Only responds to commands starting with `#test execute`, and the command must contain exactly one `case_id` parameter and one `env` parameter.为什么这么写?
- 用
#test execute而不是#test,是为了和后续的#test report、#test analyze等技能区分开,避免指令冲突; - 强制
exactly one,是因为测试用例 ID 在系统里是唯一主键,出现两个case_id只能是用户输错了,Agent 必须拒绝而非猜测; - 把
env也列为必填,彻底杜绝误入生产环境的风险——这是测试人最不能碰的红线。
3.2 第二步:输入协议(Input Contract)
## Input Contract | Parameter | Type | Required | Default | Validation Rule | |-----------|--------|----------|---------|---------------------------------------| | case_id | string | Yes | — | Must match regex `^TC\d{4,6}$` | | env | string | Yes | — | Must be one of: `staging`, `dev` | | timeout | number | No | 120 | Range: 30–600 |重点解释case_id的正则:^TC\d{4,6}$。
^和$锁定首尾,防止用户输abcTC1234def这种混杂字符串;\d{4,6}限定数字位数为 4 到 6 位,因为我们团队的用例编号规则就是 TC+4~6 位数字(如 TC1001、TC202401),超出范围一定是编错了;- 我们曾用这个规则,在 CI 流水线里提前拦截了 17 个拼写错误的用例 ID,避免了无效执行。
3.3 第三步:执行逻辑(Execution Graph)
## Execution Graph 1. Parse input parameters and validate against Input Contract. 2. If validation fails → return error message in JSON: `{ "error": "validation_failed", "details": "..." }`. 3. If valid → call test execution API: `POST /api/v1/test/run` with body: ```json { "case_id": "TC2024", "env": "staging", "timeout": 120 }- Wait for API response (max 2x timeout).
- If API returns HTTP 200 → parse response and go to Step 6.
- If API returns HTTP 4xx/5xx → return error in JSON:
{ "error": "api_failed", "http_code": 500, "message": "..." }. - Parse result: extract
status,duration_ms,error_code,log_url. - If
status == "failed"anderror_codeexists → query knowledge base for known fixes. - Return final output using Output Covenant.
这里藏着一个关键经验:**所有外部依赖(API、数据库、知识库)都必须有明确的超时和错误分支**。我见过太多 Agent 因为调用一个慢查询卡死 10 分钟,最后被 Kubernetes 杀掉。所以 Step 4 的 “max 2x timeout” 是硬性保护——如果用户设了 `timeout=120`,Agent 最多等 240 秒,超时就报错,绝不死等。 ### 3.4 第四步:输出契约(Output Covenant) ```markdown ## Output Covenant All outputs must be valid JSON with the following structure: ```json { "case_id": "string", "env": "string", "status": "string (one of: passed, failed, blocked)", "duration_ms": "number", "error_code": "string (optional)", "error_summary": "string (optional, max 200 chars)", "log_url": "string (URL to raw log)", "suggested_fix": "string (optional, only if error_code is known)" }注意:
status字段必须小写且全英文,禁止使用PASS/Fail/BLOCKED等变体。这是为了下游系统(如 Jenkins 插件)能用统一逻辑解析。
为什么强调大小写?因为在某次跨团队协作中,A 团队的 Agent 输出Status: "Passed",B 团队的监控系统用status == "passed"判断,结果永远为 false。后来我们全量统一为小写,问题消失。这种细节,SKILL.md就是干这个的。
4. 实战避坑指南:那些让 SKILL.md 失效的“温柔陷阱”
写完SKILL.md不等于万事大吉。我在三个不同项目里,都经历过“文件明明没错,Agent 就是不干活”的诡异阶段。排查下来,90% 的问题不在文件本身,而在它运行的“土壤”。以下是血泪总结的四大陷阱,每一个我都踩过不止一次。
4.1 陷阱一:环境变量污染——Agent 的“耳垢”
SKILL.md里写的env: staging,和 Agent 运行时的系统环境变量ENV=prod,是两回事。但很多框架会默认把系统环境变量注入到所有指令执行上下文中。结果就是:你明令env=staging,Agent 却偷偷把ENV=prod塞进了 API 请求头里。
如何验证?
在SKILL.md的 Execution Graph 里,加一条调试步骤:“在调用 API 前,打印所有环境变量”。实测发现,某次故障就是因为NODE_ENV=production被自动注入,导致测试框架加载了生产配置。
解决方案:
- 在 Agent 启动脚本里,显式清空所有非必要环境变量:
env -i NODE_ENV=staging ./agent-start.sh; - 或者在
SKILL.md的 Input Contract 里,加一条硬约束:“禁止使用ENV环境变量,所有环境标识必须通过env参数传入”。
提示:永远假设 Agent 运行环境是“脏”的。你的
SKILL.md不是理想国宣言,而是战地生存手册。
4.2 陷阱二:时间戳漂移——Agent 的“生物钟错乱”
测试用例执行时间、日志生成时间、Agent 记录时间,三者必须对齐。但我们发现,Agent 容器的系统时间比宿主机快了 47 秒——因为容器启动时没挂载宿主机的/etc/localtime,又没配置 NTP 同步。
后果是什么?
- Agent 记录的
start_time比日志里第一条记录早 47 秒; - 当你用
start_time去查日志时,会漏掉前 47 秒的关键初始化日志; - 最终
error_summary里写的“连接超时”,其实是 Agent 自己的时间错乱导致的误判。
怎么发现?
在SKILL.md的 Output Covenant 里,强制要求输出system_time和log_first_timestamp两个字段,并在 CI 流水线里加一道检查:abs(system_time - log_first_timestamp) < 5000(5 秒容差)。超过就标红告警。
4.3 陷阱三:权限最小化缺失——Agent 的“越权冲动”
SKILL.md写得很干净:“调用/api/v1/test/run”。但它没说,这个 API 调用需要什么权限。结果 Agent 用默认 service account 跑,而这个账号有删除数据库的权限——虽然SKILL.md没让它删,但万一哪天逻辑出 bug,或者被人恶意构造指令呢?
真实案例:
某次安全审计,扫描出 Agent 容器的 service account 绑定了cluster-admin角色。我们自查发现,SKILL.md里只写了“调用 API”,但没写“调用 API 时使用的 token 必须由test-runnerRole 绑定”。后来我们补了一条规则:“所有 API 调用必须使用test-runner-token,该 token 仅具备get/createtestresult资源的权限”。
操作清单:
- 在
SKILL.md的 Scope 里,加一句:“本技能所有外部操作,均使用test-runner权限上下文”; - 在部署文档里,同步更新 RBAC 配置,确保
test-runnerRole 真的只给了最小权限。
4.4 陷阱四:版本漂移——SKILL.md 的“过期身份证”
SKILL.md不是写完就封存的文物。它必须和你的测试框架、API 接口、日志格式一起演进。但我们发现,有 3 个项目长期没更新SKILL.md,直到某次框架升级,API 返回字段从error_msg改成了error_detail,Agent 还在按老字段解析,结果所有失败用例都显示error_summary: null。
防漂移机制:
- 在 CI 流水线里,加一道
skill-md-lint步骤:自动解析SKILL.md里的 API 调用路径,然后调用curl -I检查对应接口的 OpenAPI Spec 是否匹配; - 每次 PR 合并
SKILL.md时,强制要求关联一个 Jira 任务号,说明本次修改对应的系统变更点; - 在
SKILL.md文件头加一行元数据:# Last verified against test-framework v2.4.1 on 2024-05-20。
5. 进阶实战:用 SKILL.md 构建“测试流水线指挥官”
单个SKILL.md只能干一件事,但测试人的终极目标,是让 Agent 成为整个质量保障体系的“神经中枢”。这就需要组合技——不是写一个大文件,而是用多个小文件协同工作。我以某电商项目的“发布前全链路冒烟测试”为例,展示如何用 4 个SKILL.md文件,织成一张自动化的质量防护网。
5.1 四层技能分工:各司其职,严丝合缝
| 文件名 | 核心职责 | 关键设计点 |
|---|---|---|
smoke-trigger.md | 接收发布指令,触发冒烟流程 | Scope 里强制要求release_version和git_commit_hash,且需校验 commit 是否在 release 分支上 |
env-provisioner.md | 自动准备测试环境(拉起 staging DB、注入 mock 数据) | Input Contract 中db_snapshot_id必须匹配预生成的快照列表,杜绝随意拉库 |
api-smoke.md | 并行执行核心 API 冒烟用例 | Execution Graph 里内置熔断机制:连续 3 个用例失败,自动暂停并通知负责人 |
report-compiler.md | 汇总所有结果,生成带趋势图的 HTML 报告 | Output Covenant 强制要求trend_data字段,包含最近 7 天同版本成功率,供 DevOps 看板消费 |
这四个文件不是孤立的。smoke-trigger.md的 Execution Graph 最后一步,是调用env-provisioner.md的指令;而env-provisioner.md执行成功后,会自动生成一个provision_id,作为参数传递给api-smoke.md。整个链条像齿轮咬合,一环扣一环。
5.2 如何让多个 SKILL.md “认出彼此”?
关键在于指令路由协议。我们没用任何中心化注册中心,而是靠一套轻量级约定:
- 所有
SKILL.md文件必须放在./skills/目录下; - 文件名即技能 ID(如
smoke-trigger.md→ skill id =smoke-trigger); - Agent 解析指令时,先按
#<skill-id>前缀匹配,再按SKILL.md里的 Scope 规则二次校验; - 技能间调用,用特殊语法:
@smoke-trigger execute version=2.3.0 commit=abc123。
这样做的好处是:新增一个技能,只需放文件、写文档、重启 Agent,无需改任何代码。我们在两周内,就用这套机制上线了 12 个新技能,包括“iOS 真机兼容性检测”、“支付链路资金对账”、“CDN 缓存命中率巡检”。
5.3 真实收益:从“救火队员”到“防火墙设计师”
上线这套体系后,我们团队的角色发生了根本转变:
- 以前:发布前 2 小时,5 个测试人盯着 Jenkins 控制台,手动点“重试”,一边刷新日志一边喊“这个超时是不是网络问题?”,平均每次发布耗时 47 分钟;
- 现在:开发提交 release tag 后,Agent 自动触发全流程,12 分钟内返回带根因分析的 HTML 报告。测试人只做两件事:看报告里的红色区块,以及每周优化一个
SKILL.md的失败兜底逻辑。
最大的价值不是省时间,而是把人的经验沉淀为可执行、可验证、可传承的机器指令。那个曾经靠老师傅记忆的“登录用例在 iOS 16 上要加 500ms 等待”的知识点,现在就写在api-smoke.md的known_issues注释里,还附带了自动跳过逻辑。新人第一天入职,就能跑通全链路。
最后分享一个小技巧:在每个
SKILL.md文件末尾,加一个## Audit Trail章节,记录“谁在什么时候,因为什么原因修改了这条规则”。不是为了追责,而是为了让下次看到status: blocked时,能快速回溯到三个月前那次数据库迁移——那才是SKILL.md从工具升维为资产的临界点。