Agent Skill 工程化指南(二):设计与封装 — 构建高质量的能力单元

0 阅读32分钟

一句话定位:读完这篇,你将掌握从零编写一个生产级 SKILL.md 的完整方法论,知道每个设计决策背后的依据和权衡。


1. 引言:封装差的 Skill 会怎样

上一篇我们建立了 Skill 的统一认知,知道了 SKILL.md 是 Skill 的核心文件。现在假设你要为团队写一个"流水线故障诊断 Skill",你打开编辑器,写下:

---
name: pipeline-diagnosis
description: Helps with CI/CD issues.
---

When CI/CD fails, check the logs and fix it.

这是一个格式正确的 SKILL.md 吗?从语法上看,头部元数据(front matter)和正文都有了。但从工程实践看,它有三个致命问题。

问题一:description 太模糊,Agent 不会召回它。

当用户说"master 的 CI 挂了",Agent 平台会将这句话与所有 Skill 的 description 做语义匹配[1]。"Helps with CI/CD issues" 这个描述太泛——它没有说明什么场景触发做什么动作处理什么类型的问题。Agent 很可能匹配不到它,或者匹配到另一个描述更精确的 Skill。

问题二:正文没有具体步骤,Agent 不知道怎么做。

"When CI/CD fails, check the logs and fix it"——怎么检查?用什么 API?日志怎么分析?什么算"fix"?Agent 要么自行发挥(行为不可控),要么追问用户每一步怎么做(体验极差)。

问题三:没有任何安全约束,Agent 可能做出危险操作。

"Fix it"——Agent 可能理解为直接回滚生产环境。没有 STOP and ask for confirmation 这样的约束,高风险操作会在无人知晓的情况下执行。

这三个问题的根因是同一个:封装质量差。SKILL.md 的格式你写对了,但内容没有经过设计。

本篇就是要解决这个问题:怎么写好一个 SKILL.md? 我们会从设计决策、头部元数据编写、执行指令组织、推理策略、容错设计到安全约束,逐层展开。


2. 封装前的四个设计决策

在写第一行 SKILL.md 之前,你需要先回答四个问题。这四个问题的答案会直接决定 Skill 的边界、结构和质量。

2.1 这个 Skill 解决什么问题?

用一句话写清楚:谁在什么场景下需要什么结果。

✗ "帮我处理 CI/CD 的问题"               → 太模糊
✗ "流水线故障诊断、修复和优化"           → 包含太多能力
✓ "当流水线构建失败时,诊断故障类型,定位根因,尝试修复"  → 一个完整闭环

这个一句话就是你 description 的基础。如果这句话写不清楚,说明你对这个 Skill 要解决的问题还没有想清楚,不应该开始写 SKILL.md。

2.2 粒度切到多细?

粒度是 Skill 设计中最关键也最难的决策。切太粗和切太细都会带来问题:

切太粗:"DevOps 全能 Skill"
  → 包含了故障诊断、数据库迁移、代码审查、安全扫描……
  → description 难以精确描述所有能力,匹配时容易误召回
  → 正文会变成几千行的巨型文档,维护困难
  → 某个子能力更新时,整个 Skill 都要重新测试和发布

切太细:"拉取构建日志 Skill"、"解析日志 Skill"、"分类故障 Skill"
  → 每个 Skill 只做一小步,组合爆炸
  → 需要一个编排层来串联它们,增加复杂度
  → 单独的"解析日志 Skill"几乎没有独立复用价值

刚好:"流水线故障诊断 Skill"
  → 一个用户意图的完整闭环
  → description 能精确描述
  → 正文控制在可维护的长度
  → 可以被多个 Agent 复用

粒度判断的经验法则[2]

  • 一个 Skill 应该对应一个用户的完整意图。用户说"帮我看看 CI 为什么挂了"——这是一个意图,对应一个 Skill。
  • 如果 Skill 的 description 里出现"和"、"以及",可能需要拆分。"诊断流水线故障执行安全扫描"是两个不同的意图,应该拆成两个 Skill。
  • 如果 Skill 只做一件事但永远需要被另一个 Skill 调用,可能需要合并。"解析构建日志"几乎不会被单独触发,它应该作为"流水线故障诊断"内部的一步。

2.3 是否幂等?

幂等性意味着同一个操作执行一次和执行多次的效果完全相同[3][4]。这不是理论概念,而是 Agent 系统中的现实需求:当 Skill 执行超时后,Agent 平台可能会自动重试。如果你的 Skill 不是幂等的,重试会导致重复操作。

天然幂等(读操作):
  查询构建日志(查多少次结果都一样)
  分析日志内容(纯计算,不产生副作用)

需要设计幂等(写操作):
  触发重新构建 → 用幂等键去重,避免重复触发
  创建故障工单 → 用 pipeline_id + 时间窗口去重,避免创建多张工单
  发送通知消息 → 用消息 ID 去重,避免重复通知

无天然幂等性的不可逆操作(需业务层拦截):
  发送短信 → 一旦发出去就无法撤回
  → 对这类操作,必须在 SKILL.md 中增加确认步骤,
    而不是依赖自动重试

在写 SKILL.md 之前评估每个步骤的幂等性,决定哪些步骤可以安全重试,哪些需要额外的去重或确认机制。第八节会给出具体的幂等性设计方法。

2.4 谁来用?怎么触发?

同一个"流水线故障诊断"能力,不同的使用者和触发方式会影响 SKILL.md 的写法:

使用者触发方式对 SKILL.md 的影响
研发同学在 Slack 问 Agent主动调用,有对话上下文Step 1 可以从上下文中提取 pipeline_id
CI/CD Webhook 自动推送事件驱动,无对话上下文Step 1 的 pipeline_id 必须从事件 payload 中提取
值班机器人自动触发事件驱动,可能凌晨无人响应Step 4 不能依赖人工确认,需要全自动降级策略

如果你的 Skill 需要支持多种触发方式,SKILL.md 中需要为每种方式定义参数获取策略。


3. 写好头部元数据

头部元数据(front matter)只有两个必填字段:namedescription[1]。但这两个字段的质量直接决定了 Skill 的生死——如果 description 写得不好,Agent 根本不会召回你的 Skill,后续写得再好的执行指令都没有意义。

3.1 name 的命名规范

name 是 Skill 的唯一标识符。推荐使用 kebab-case(全小写,单词之间用连字符连接)[1]

✓ pipeline-diagnosis
✓ database-migration
✓ code-review

✗ pipelineDiagnosis     → 驼峰命名在路径和命令行中可能产生问题
✗ Pipeline_Diagnosis    → 大写和下划线混合,风格不统一
✗ pipeline diagnosis    → 包含空格,在文件系统中会有转义问题

命名原则:

  • 可读:从名字就能大致猜出功能
  • 唯一:在同一 Skill 注册表中不与其他 Skill 重名
  • 稳定:一旦发布,不要轻易改名(改名会导致已有 Agent 的引用失效)

3.2 description 的写法

description 是整个 SKILL.md 中投入产出比最高的字段。Agent 平台通过对 description 的语义匹配来决定是否调用你的 Skill[1]

根据 OpenAI 的函数调用文档[5],函数描述的质量直接影响模型选择工具的准确率。Anthropic 的工具使用文档[6]同样强调:"工具描述应清晰且具体地说明工具的功能和使用场景。"以下是编写 description 的具体方法:

要素一:说明触发场景(什么时候该用这个 Skill)

✗ "Handles CI/CD pipelines."
   → 没有说清楚什么场景。用户说"帮我配一条新流水线"也会匹配到它,
     但这个 Skill 根本不处理创建流水线。

✓ "When a CI/CD pipeline build fails..."
   → 明确了触发条件:构建失败时。

要素二:说明具体动作(这个 Skill 做什么)

✗ "Helps with CI/CD issues."
   → "Helps" 是什么?查看?分析?修复?告警?

✓ "...fetch the build logs, diagnose the failure type, locate the root cause,
   and attempt auto-fix or provide fix suggestions."
   → 四个具体动作:拉取、诊断、定位、修复。

要素三:说明覆盖范围(处理什么类型的问题)

✓ "...diagnose the failure type (dependency conflict, compile error, test failure,
   or deploy timeout)..."
   → 括号中列出了具体的故障类型,帮助 Agent 判断这个 Skill 是否覆盖
     用户遇到的具体问题。

把三个要素组合起来,就是一条高质量的 description

When a CI/CD pipeline build fails, fetch the build logs, diagnose the
failure type (dependency conflict, compile error, test failure, or deploy
timeout), locate the root cause, and attempt auto-fix or provide fix
suggestions.

反面案例对比:

description问题
"Helps with CI/CD issues."缺少触发场景、缺少具体动作、缺少覆盖范围
"Diagnoses pipeline failures."有动作但缺少触发场景和覆盖范围
"When a pipeline fails, looks at the logs."有触发场景和动作,但"looks at"太模糊
上面的完整版本三个要素齐全

一句话检验标准:如果把 description 给一个不了解你系统的同事看,他能否判断"这个 Skill 在什么情况下会被用到、用了之后会做什么"?如果能,说明 description 写得够清楚。

语言选择说明description 推荐使用英文编写。原因有二:一是主流大模型(GPT-4、Claude 等)在英文指令上的理解准确率通常高于中文[7];二是当 Skill 需要跨团队或跨组织共享时,英文是更通用的选择。如果你的 Agent 仅面向中文用户且使用的模型对中文理解效果好,使用中文 description 也是可行的。


4. 编写执行指令

头部元数据决定了 Skill 会不会被召回,正文决定了 Skill 被召回后执行得好不好。

SKILL.md 的正文是给 Agent 执行的操作指令。它不是代码,不是伪代码,也不是给人看的文档。它是用自然语言写给 LLM Agent 的操作规程——Agent 会按照这些指令一步步执行[1]

4.1 步骤结构设计原则

以第一篇中的"流水线故障诊断 Skill"为例,它的正文采用了五个 Step 的结构。这不是随意的选择,步骤结构的设计遵循以下原则:

原则一:每个步骤有且仅有一个明确目标

✗ 模糊的步骤:
  "Step 2: Get the logs and figure out what went wrong and try to fix it."
  → 一个步骤里塞了三件事(获取、分析、修复),Agent 容易跳过中间环节。

✓ 清晰的步骤:
  Step 2: Fetch build logs(只做一件事:获取日志)
  Step 3: Diagnose failure type(只做一件事:分类故障)
  Step 4: Execute fix(只做一件事:执行修复)

原则二:步骤之间有明确的输入输出关系

Step 2 的输出(构建日志)= Step 3 的输入
Step 3 的输出(故障分类 + 根因)= Step 4 的输入
Step 4 的输出(修复结果)= Step 5 的输入

这种链式结构让 Agent 在每一步都知道"我需要什么信息才能开始"和"我需要产出什么信息给下一步"。

原则三:先获取信息,再分析,最后行动

Step 1: 获取参数          ← 信息获取
Step 2: 获取日志          ← 信息获取
Step 3: 诊断分类          ← 信息分析
Step 4: 执行修复          ← 行动
Step 5: 生成报告          ← 结果输出

这个顺序不是偶然的。在 AI Agent 系统设计中,"先感知再思考再行动"的模式被广泛采用。ReAct 框架[8]提出了推理(Thought)与行动(Action)交替进行的模式;Wang 等人提出的 Plan-and-Solve 方法[9]也强调先制定计划再逐步执行。我们的步骤结构本质上体现了同样的思想:先获取所有必要信息(Steps 1-2),再分析判断(Step 3),最后执行(Steps 4-5)。

4.2 参数获取与追问(Step 1 模式)

几乎每个 Skill 的第一步都是"获取参数并校验"。这个步骤的质量直接决定了后续所有步骤能否顺利执行。

以"流水线故障诊断 Skill"的 Step 1 为例:

## Step 1: Obtain pipeline information

Confirm you have the following parameters. If any required parameter is
missing, ask the user before proceeding.

- `pipeline_id` (required): the pipeline run ID, e.g. `pl-20260706-a3f7`
- `service_name` (optional): the microservice name (useful for monorepos)
- `branch` (optional, defaults to `master`): the git branch

If `pipeline_id` is missing, ask: "What is the pipeline ID or build link?"

If the user cannot provide it, fall back to querying the CI/CD platform for
the most recent failed build matching `service_name` and `branch`.

Maximum 2 rounds of clarification. After that, use the fallback strategy.

这个步骤中嵌入了三个关键设计:

参数声明:明确列出每个参数的名称、是否必填、示例值。这相当于输入契约(Input Contract)[10]的自然语言版本,让 Agent 在执行时知道"我需要哪些信息"。

追问策略:当必填参数缺失时,Agent 不是直接报错,而是向用户询问。追问话术是写死在 SKILL.md 中的固定文本,确保 Agent 的追问方式一致且友好。

追问上限与降级Maximum 2 rounds of clarification. After that, use the fallback strategy. 这一行至关重要。没有追问上限,Agent 可能陷入"反复追问 → 用户反复答不上来 → 无限循环"的死循环。2 轮是经验值——超过 2 轮还获取不到信息,说明追问不是正确策略,应该降级。追问轮次限制是对话式 AI 设计中的通用原则[11]

4.3 条件分支(Step 4 模式)

当 Skill 的执行路径需要根据不同条件走不同分支时,在 SKILL.md 中用 Markdown 标题层级表达分支结构:

## Step 4: Execute fix based on failure type

### If dependency:

1. Identify the conflicting package and versions from the log.
2. Roll back to the last stable version.
3. Trigger a rebuild.
**STOP and ask for user confirmation before executing the rollback.**

### If compile:

1. Locate the error file and line number from the log.
2. Provide a fix suggestion.
Do NOT modify code automatically. Only provide suggestions.

### If test:

1. Check if the failed test is a flaky test.
2. If flaky: rerun skipping those tests.
3. If not flaky: provide failure analysis.

### If deploy:

1. Check the target environment status.
2. Prepare a rollback command.
**STOP and ask for on-call engineer confirmation before executing.**

### If unknown:

Output the last 50 lines of error logs and recommend manual investigation.

这种"大标题下用子标题做分支"的结构,Agent 能够清晰地理解为条件路由。每个分支内部用编号列表表达顺序步骤。

需要注意的是,条件分支的判断逻辑可能需要 LLM 的推理能力。在这个例子中,Step 3 已经通过 LLM 分析日志完成了故障分类(输出 failure_type),Step 4 只是根据分类结果做确定性的路由——这是"先推理再行动"模式的体现[8]

4.4 错误处理的嵌入方式

SKILL.md 中的错误处理不是用 try-catch 代码块表达的,而是用自然语言的条件指令

If the CI/CD API times out, retry up to 2 times with increasing wait
intervals (wait 1 second, then 2 seconds). If still failing after retries,
use the fallback strategy.
If the log exceeds 500KB, stop and return an error asking the user to
download the log manually.

这种方式的好处是 Agent 能理解并执行,不需要编写代码。但它有一个隐含要求:错误处理指令必须足够具体

✗ "Handle errors gracefully."
   → Agent 不知道"gracefully"具体指什么。是重试?是降级?是报错?

✓ "If the CI/CD API times out, retry up to 2 times. If still failing,
   fall back to locally cached logs from the last successful run."
   → 明确了:什么错误(超时)→ 第一反应(重试 2 次)→ 降级方案(用本地缓存的日志)

每一处错误处理都应该包含三要素:什么错误第一反应降级方案。这三个要素来源于分布式系统的容错设计原则[12][13]


5. Prompt 策略

SKILL.md 的正文是给 Agent 的操作指令,但其中某些步骤需要 Agent 调用 LLM 进行推理——比如 Step 3 的"分析日志,判断故障类型"。这种推理需要 Prompt 来引导。

5.1 何时引入 LLM 推理

不是每个步骤都需要 LLM。判断标准是:这个步骤的逻辑是否确定性可编码?

确定性步骤(不需要 LLM):
  - 调用 API 获取日志         → 代码直接执行
  - 根据 failure_type 选分支  → 条件路由
  - 输出报告模板              → 模板填充

需要 LLM 推理的步骤:
  - 分析日志文本,判断故障类型    → 需要理解自然语言日志
  - 判断测试是否为 flaky         → 需要综合多维信息做判断
  - 生成自然语言的诊断报告       → 需要语言生成能力

在 SKILL.md 中,需要 LLM 推理的步骤应该明确指向一个外部 Prompt 模板,而不是把推理规则直接写在正文中。这是因为 Prompt 通常需要独立管理和迭代[14]

## Step 3: Diagnose failure type

Analyze the build log and classify the failure into one of these categories.
Use the diagnostic prompt at `prompts/system.md` for structured analysis.

- **dependency**: version conflicts, missing packages, resolution failures
- **compile**: syntax errors, type errors, missing symbols
- **test**: assertion failures, flaky tests, timeout on test suites
- **deploy**: health check failures, image pull errors, resource exhaustion
- **unknown**: none of the above patterns match

SKILL.md 中给出的是分类体系和判断标准(Agent 用来验证 LLM 的输出是否合理),而 prompts/system.md 中是具体的推理指令(LLM 用来做分析)。两者分工明确。

5.2 推理策略选择

当 Skill 内部需要 LLM 推理时,选择什么推理策略?以下是三种主要策略及其适用场景:

直接推理(Direct Prompting)

LLM 直接根据输入输出结果,不展示中间推理过程。适用于简单的分类和提取任务[14]

适用场景:Step 3 的故障分类(给定日志文本,输出分类标签)
不适用场景:需要多步推理的复杂判断

思维链(Chain-of-Thought,简称 CoT)

引导 LLM 先展示推理过程,再给出最终结论。Wei 等人在 2022 年的研究[15]证明,CoT 能显著提升 LLM 在复杂推理任务上的表现。

适用场景:需要多步推理的判断,如
  "这条日志显示了依赖冲突,但同时也出现了测试超时。
   需要判断哪个是根因、哪个是连锁反应。
   → 先检查依赖冲突是否阻塞了编译……
   → 如果编译通过了,那测试超时可能是独立问题……
   → 结论:根因是依赖冲突。"

ReAct(Reasoning + Acting)

Yao 等人在 2022 年提出的框架[8],让 LLM 在推理和行动之间交替进行:先思考(Thought),再执行动作(Action),观察结果(Observation),再思考……如此循环。

适用场景:需要与外部环境交互后才能做判断的场景,如
  Thought: 日志中出现了 "ERESOLVE" 错误,可能是依赖冲突。
  Action: 调用 dependency-cli 查询包版本。
  Observation: react@18.3.1 与某个内部库要求的 react@17 不兼容。
  Thought: 确认是版本冲突,需要回滚 react 到 17.x。
  Action: 执行回滚。

在"流水线故障诊断 Skill"中,Step 3 使用 CoT(分析日志需要多步推理),Step 4 使用直接推理(根据已确定的 failure_type 做路由)或 ReAct(依赖问题需要查询外部系统后再决策)。选择哪种策略取决于步骤的复杂度和是否需要与外部环境交互[8][15]

5.3 引用外部 Prompt 的方式

SKILL.md 中引用外部 Prompt 文件时,使用相对路径:

Use the diagnostic prompt at `prompts/system.md` for structured analysis.

Agent 平台在执行到这一步时,会加载 prompts/system.md 的内容注入到 LLM 的上下文中[1]

prompts/system.md 的内容示例:

---
name: pipeline-diagnosis-system
description: System prompt for analyzing CI/CD build logs and classifying failures.
---

You are a senior DevOps engineer. Analyze the following CI/CD build log and
classify the failure.

Output a JSON object with these fields:
- failure_type: one of "dependency", "compile", "test", "deploy", "unknown"
- root_cause: a one-paragraph description of the root cause
- affected_files: list of file paths involved
- failed_tests: list of test names (if applicable)
- likely_commit: the commit hash that likely introduced the failure

Base your analysis on the log content. Do not speculate beyond what the
evidence supports.

这种分离的好处是:Prompt 可以独立迭代。当你发现故障分类的准确率不够时,只需要修改 prompts/system.md,不需要动 SKILL.md 的正文。这符合关注点分离的原则[14]

5.4 少样本示例(Few-shot)管理

当 LLM 推理的效果不稳定时,在 Prompt 中提供几个输入→输出的范例(少样本示例,Few-shot examples)是提升准确率的有效手段。Brown 等人在 GPT-3 的研究[16]中证明了 Few-shot prompting 的有效性。

在 Skill 的工程目录中,少样本示例存放在 prompts/few_shots/ 下,按场景分类:

prompts/
├── system.md                    # System Prompt
└── few_shots/
    ├── dependency_conflict.md   # 依赖冲突的示例
    ├── compile_error.md         # 编译错误的示例
    ├── test_failure.md          # 测试失败的示例
    └── deploy_timeout.md        # 部署超时的示例

SKILL.md 中可以指示 Agent 根据初步判断选择相关的少样本示例:

If the log pattern suggests a dependency issue, include the examples from
`prompts/few_shots/dependency_conflict.md` in your analysis prompt.

这种动态选择策略比把所有少样本示例都塞进 Prompt 更高效——它节省了上下文窗口的 Token 消耗,同时提高了示例与当前问题的相关性[16]

这需要 Agent 框架层的配合。例如:“这通常需要 Agent 框架在执行该步骤前,通过 RAG 机制从 **<font style="color:rgb(13, 13, 13);background-color:rgb(236, 236, 236);">few_shots/</font>** 目录中检索相关示例并动态注入到 Prompt 中,或者赋予 Agent 读取文件的 Tool。”


6. 工具调用的容错设计

SKILL.md 的步骤中会调用外部工具和 API(如 cicd-cligitkubectl)。这些调用可能失败——网络超时、服务不可用、权限不足。如果 SKILL.md 不处理这些失败,Agent 会在遇到错误时卡住或做出不可预测的行为。

根据分布式系统的容错设计原则[12][13],每个工具调用都应该考虑三层防线:超时、重试、降级。

6.1 超时

每个外部调用都应该有明确的超时时间。在 SKILL.md 中以自然语言表达:

Call the CI/CD platform API to retrieve the full build log. If the request
takes longer than 10 seconds, treat it as a timeout.

超时时间的选择参考:内部服务通常 1-5 秒,外部 API 通常 5-15 秒,涉及大量数据传输的操作(如拉取完整构建日志)可以适当放宽到 10-30 秒[12]

6.2 重试与指数退避

超时后立即重试通常没有意义(如果服务端还在忙,立即重试大概率还是失败)。正确的做法是指数退避:每次重试之间的等待时间翻倍[12][13]

第 1 次重试:等 1 秒
第 2 次重试:等 2 秒
第 3 次重试:等 4 秒

AWS 的架构最佳实践[13]详细分析了指数退避策略,并建议在指数退避的基础上加入随机抖动(jitter),避免多个客户端同时重试导致"惊群效应"。在 SKILL.md 的自然语言指令中,可以用"increasing wait intervals"来表达这一策略:

If the CI/CD API times out, retry up to 2 times with increasing wait
intervals (wait 1 second, then 2 seconds). If still failing after retries,
use the fallback strategy.

重试次数的经验值:关键操作 2-3 次,非关键操作 1 次[12]

6.3 降级

当重试仍然失败时,需要一个降级策略——用一个不完美但可用的方案替代原来的方案。

降级策略的一个常见陷阱是:降级方案指向了同一个不可用的数据源。 例如,如果 CI/CD 平台 API 因服务故障而超时,那么"获取上一次成功构建的日志"大概率也需要调用同一个 API,同样会失败。有效的降级方案应该指向不同的数据源[12]

✗ 无效降级:
  主方案:调 CI/CD API 获取当前构建日志
  降级方案:调 CI/CD API 获取上一次成功构建的日志
  → 同一个 API 挂了,降级方案也用不了

✓ 有效降级:
  主方案:调 CI/CD API 获取当前构建日志
  降级方案一:使用本地缓存的上一次成功构建日志
  降级方案二:提示用户提供日志文本,直接分析
  → 指向了不同的数据源(本地缓存、用户输入)

在 SKILL.md 中:

If the CI/CD API times out after retries, fall back to querying locally
cached logs from the last successful run.

If no cached logs are available, ask the user to paste the build log
directly and proceed with the analysis based on user-provided content.

If no log content is available from any source, return an error:
"Unable to access build logs. Please check the CI/CD platform directly
and provide the log content."

注意最后一行:当连降级方案都不可用时,应该明确报错,而不是让 Agent 默默跳过或猜测。降级策略的设计原则是:降级方案应该尽可能保留 Skill 的核心价值,但当所有路径都走不通时,诚实的失败好过不确定的行为[12]

三层防线的完整示例

Call the CI/CD platform API to retrieve the full build log.

If the request takes longer than 10 seconds, treat it as a timeout and
retry up to 2 times with increasing wait intervals.

If still failing after retries, fall back to locally cached logs from the
last successful build run.

If no cached logs are available, ask the user to paste the build log
directly.

If the user cannot provide the log, return an error: "Unable to fetch
build logs. Please check the CI/CD platform directly."

7. 安全约束的嵌入

上一篇的 Guardrails 要素提到了权限、限流、熔断等安全机制。在 SKILL.md 中,这些安全约束以嵌入式指令的形式出现在相关的步骤中,而不是集中声明。这样做的原因是:安全约束需要在具体的操作上下文中才有意义[17]

7.1 人机协同

对于高风险操作,在执行前要求人工确认。在 SKILL.md 中用 STOP 关键词标注:

**STOP and ask for user confirmation before executing the rollback.**
**STOP and ask for on-call engineer confirmation before executing deploy
rollback.**

STOP 在这里的角色需要正确理解。它有两个层面:

LLM 层面STOP 是给 LLM 的强指令提示,使用全大写和粗体格式,让 LLM 理解"此处必须暂停,不要自行继续执行后续步骤"[17]

框架层面:仅靠 LLM 的"理解"是不够的——LLM 有可能忽略指令或自行生成后续内容[18]。因此,一个工程上可靠的 Agent 系统还需要在框架层(如 LangGraph 的断点机制、OpenAI Assistants 的 requires_action 状态)实现硬性拦截:当 Agent 的输出包含 STOP 信号时,调度器强制暂停执行流程,等待人工审批后才恢复。STOP 是指令层面的提示,框架拦截器是工程层面的保障,两者配合才能实现可靠的人机协同。

补充说明框架层面的拦截通常是通过结构化信号(如 LLM 返回特定的 JSON 结构 {"action": "pause", "reason": "..."} )或框架预设的断点(如 LangGraph 中在特定 Step 前设置 interrupt_before)来实现的。纯文本的 STOP 只是给 LLM 的提示,真正的阻断权在框架的调度器逻辑

不同操作的风险等级不同,确认的对象也不同:

操作风险等级确认对象SKILL.md 中的标注
查询日志不需要确认无标注
触发重试用户STOP and ask for user confirmation
回滚依赖用户STOP and ask for user confirmation
回滚部署on-call 工程师STOP and ask for on-call engineer confirmation

7.2 操作边界

明确告诉 Agent 哪些事情绝对不能做

Do NOT modify code automatically. Only provide suggestions.

这类否定指令需要语气足够强(使用全大写的 NOT),因为 LLM 天然倾向于"帮忙做事"[18]——如果你只说"provide suggestions",Agent 可能会自作主张地修改代码。明确的否定指令划定了 Agent 的行为空间。

7.3 资源保护

防止 Agent 因异常输入消耗过多资源:

If the log exceeds 500KB, stop and return an error asking the user to
download the log manually.
Maximum 2 rounds of clarification. After that, use the fallback strategy.

这两条分别保护了两种资源:Token 消耗(超大日志会撑爆上下文窗口)和交互次数(无限追问会浪费用户时间)[11]


8. 幂等性设计

第 2.3 节中我们评估了哪些操作需要幂等。在实际编写 SKILL.md 时,幂等性的保障方式取决于操作的类型。

读操作:天然幂等,无需额外处理

cicd-cli logs --pipeline-id <pipeline_id> --format json

查多少次日志,结果都一样(假设日志没有被篡改)。

写操作:需要设计去重机制

幂等性在 API 设计中是一个经典问题。Stripe 的 API 文档[3]详细规范了幂等键(Idempotency Key)机制:客户端为每次操作生成一个唯一标识并携带在请求中,服务端用它去重——如果同一个幂等键的请求已经处理过,直接返回之前的结果,不重复执行。Microsoft 的 REST API 指南[4]也有类似的要求。

幂等键的核心原则是:同一个逻辑操作,无论重试多少次,幂等键必须相同。 这意味着幂等键不能包含时间戳(每次重试时间戳都不同,服务端会将其视为全新请求),而应该由确定性参数组合生成。

在 SKILL.md 中:

Trigger a rebuild with an idempotency key derived from deterministic
parameters to prevent duplicate triggers:

​```bash
cicd-cli trigger --pipeline-id <pipeline_id> --branch <branch> \
  --idempotency-key "rebuild-<pipeline_id>-<failed_run_id>"

If a rebuild with the same idempotency key was already triggered in the last 10 minutes, the CI/CD platform will return the existing build instead of creating a new one.


这里的幂等键 `"rebuild-<pipeline_id>-<failed_run_id>"` 由确定性参数组成:同一个 pipeline 的同一个分支,无论重试多少次,幂等键都相同。服务端收到重复的幂等键后,返回已有的构建结果而不是创建新的<sup>[3][4]</sup>。

### 不可逆操作:用确认步骤替代幂等性
有些操作一旦执行就无法撤回(如发送通知、发送短信)。对于这类操作,幂等性设计的价值有限(即使不重复发送,第一次发送本身已经不可撤回),正确的做法是在 SKILL.md 中增加确认步骤:

​```markdown
Before sending the diagnosis report to the team channel, present the
report content to the user and confirm:
"Ready to send this report to #devops-alerts. Proceed?"

9. 封装自检清单

在提交一个 Skill 之前,用以下清单逐项检查。每一项都对应本篇中讨论的设计原则:

头部元数据检查:

□  name 是否使用 kebab-case,且在 Skill 注册表中唯一?
□  description 是否包含三个要素:触发场景 + 具体动作 + 覆盖范围?
□  description 是否足够具体,能让 Agent 区分"这个 Skill"和"其他相似 Skill"?

执行指令检查:

□  每个步骤是否有且仅有一个明确目标?
□  步骤之间是否有清晰的输入输出关系?
□  是否遵循了"先获取信息 → 再分析 → 最后行动"的顺序?

参数与追问检查:

□  必填参数是否全部列出,且标注了 (required)?
□  每个必填参数缺失时,是否有明确的追问话术?
□  是否设置了追问上限(如 Maximum 2 rounds)?
□  追问上限后是否有降级策略?

推理策略检查:

□  需要 LLM 推理的步骤是否引用了外部 Prompt 模板(而不是把推理规则写在正文里)?
□  推理策略选择是否合理(简单分类用直接推理,复杂判断用 CoT,需要外部交互用 ReAct)?
□  是否提供了足够的少样本示例?

容错检查:

□  每个外部 API / 工具调用是否都有超时设定?
□  超时后是否有重试策略(含指数退避)?
□  重试失败后是否有降级方案?
□  降级方案是否指向了不同的数据源(而不是同一个不可用的 API)?
□  降级方案不可用时,是否明确报错(而不是静默跳过)?

安全检查:

□  高风险操作前是否有 STOP 标注?
□  STOP 标注是否同时考虑了 LLM 层面的指令提示和框架层面的拦截机制?
□  不允许的操作是否有 Do NOT 明确否定?
□  是否有资源保护措施(日志大小限制、追问轮次限制)?

幂等性检查:

□  写操作是否设计了幂等键(由确定性参数生成,不包含时间戳)?
□  不可逆操作是否增加了确认步骤?
□  Agent 平台自动重试时,是否会产生副作用?

10. 小结

本篇围绕一个核心问题展开:怎么写好一个 SKILL.md?

答案可以归纳为三层:

第一层:想清楚再写。 回答四个设计决策——解决什么问题、粒度切多细、是否幂等、谁来触发。这些问题的答案决定了 Skill 的边界和结构。

第二层:写好两个部分。 头部元数据中的 description 是 Skill 的"简历",决定了 Agent 会不会选中它——它需要包含触发场景、具体动作和覆盖范围三个要素。正文中的步骤结构是 Skill 的"操作手册",决定了 Agent 选中它之后执行得好不好——它需要遵循"先获取 → 再分析 → 后行动"的顺序,每个步骤目标单一,步骤之间有清晰的输入输出链。

第三层:处理好边界情况。 容错三层防线(超时 → 重试 → 降级)保证了 Skill 在外部系统异常时不会崩溃,但降级方案必须指向可用的数据源[12][13];安全约束的嵌入(人机协同、操作边界、资源保护)保证了 Skill 不会做出超出预期的危险操作,但 STOP 指令需要框架层面的拦截机制配合才能可靠生效[17];幂等性设计保证了重试不会产生副作用,但幂等键必须由确定性参数生成[3][4]

写好一个 SKILL.md 的本质,是用自然语言为 LLM Agent 定义一套完整的、有边界的、可容错的操作规程。它不是代码,但需要和代码一样严谨。

下一篇,我们进入运行时的世界:当用户说了一句话,Agent 是怎么从众多 Skill 中找到你写的这个 SKILL.md 的?找到了之后又是怎么加载、校验和安全执行的?


下一篇预告:《Agent Skill 工程化指南(三):运行与生效 — 运行时的全链路机制》


参考来源

  1. SKILL.md 格式规范 — YAML 头部元数据(name + description)+ 正文操作指令是 AI Agent 开发实践中广泛采用的 Skill 定义模式。description 作为语义检索依据决定 Skill 是否被召回。该模式在多个 Agent 开发框架和平台中被采用,如 GitHub Copilot Custom Instructions(docs.github.com/copilot)、Cursor Rules 等。
  2. Skill 粒度设计 — "一个 Skill 对应一个用户意图"的粒度原则是 Agent Skill 设计中的通用经验。在实际工程中,粒度的最终判断需要结合具体业务场景和团队维护能力。
  3. 幂等性设计 — Stripe API 文档中对 Idempotency Key 的详细规范:每个 POST 请求携带唯一的 Idempotency-Key header,服务端用它去重。参见 Stripe API Docs: Idempotent Requests
  4. Microsoft REST API Guidelines — Microsoft 对幂等性的要求:PUT 和 DELETE 操作必须幂等,POST 操作应支持客户端提供的幂等键。参见 Microsoft API Guidelines
  5. OpenAI Function Calling — OpenAI 官方文档强调函数描述的质量直接影响模型选择工具的准确率:"The description of the function is important as it helps the model decide when and how to call it." 参见 OpenAI Docs: Function Calling
  6. Anthropic Tool Use — Anthropic 官方文档对工具描述的要求。参见 Anthropic Docs: Tool Use
  7. 多语言模型表现差异 — 主流大模型(GPT-4、Claude 等)在英文指令上的推理准确率通常高于中文,这是由于训练数据中英文占比更高导致的。具体表现因模型版本和任务类型而异。
  8. ReAct: Synergizing Reasoning and Acting in Language Models — Yao, S. et al. (2022). 提出了 LLM 中推理(Thought)与行动(Action)交替进行的框架。参见 arXiv:2210.03629
  9. Plan-and-Solve Prompting — Wang, L. et al. (2023). 提出了"先制定计划再逐步执行"的 Prompting 策略。需要注意的是,该论文提出的是 Prompting 层面的技巧(Plan-and-Solve),Agent 领域中更广义的"先规划后执行"模式在多个系统设计中被采用。参见 arXiv:2305.04091
  10. JSON Schema Specification — IETF 提出的 JSON Schema 标准,用于定义 JSON 数据的结构、类型和约束。参见 json-schema.org
  11. 对话式 AI 追问轮次限制 — 在对话系统设计中,限制澄清追问的轮次以避免用户体验恶化,是通用的设计原则。当追问无法获取信息时应提供降级路径(如使用默认值或转人工)。
  12. Martin Fowler: Circuit Breaker — 熔断器设计模式,用于防止级联故障。当失败率超过阈值时"熔断",快速失败而非继续重试。参见 martinfowler.com/bliki/Circu…
  13. AWS Architecture Blog: Exponential Backoff and Jitter — 对指数退避和随机抖动策略的详细分析,包括不同退避策略在高并发场景下的性能对比。参见 AWS Architecture Blog
  14. OpenAI Prompt Engineering Guide — OpenAI 官方的 Prompt 工程指南,包含策略选择、系统消息设计、输出格式控制等最佳实践。参见 OpenAI Docs: Prompt Engineering
  15. Chain-of-Thought Prompting Elicits Reasoning in Large Language Models — Wei, J. et al. (2022). Google Research 的研究证明,在 Prompt 中引导 LLM 展示中间推理步骤能显著提升推理准确率。参见 arXiv:2201.11903
  16. Language Models are Few-Shot Learners — Brown, T. et al. (2020). OpenAI 的 GPT-3 论文,证明了大型语言模型可以通过少量示例快速适应新任务。参见 arXiv:2005.14165
  17. Anthropic: Constitutional AI — Anthropic 提出的 Constitutional AI 方法,通过自然语言规则约束 LLM 的行为,使模型在执行中遵循明确的安全边界。参见 arXiv:2212.08073
  18. OpenAI: GPT-4 System Card — OpenAI 的 GPT-4 技术报告中讨论了模型的"sycophancy"倾向(倾向于迎合用户期望而非严格遵循指令),说明了仅靠 LLM 自觉执行 STOP 指令的不可靠性,以及需要框架层面拦截机制配合的必要性。参见 OpenAI: GPT-4 System Card