第八话结尾留了一个问题:工具能即插即用了,方法怎么复用?
那一篇我们用 MCP 把「工具怎么被发现、怎么被调用」做成了协议,Server 和 Client 各自解耦。查天气、读数据库、调别人家的工具,都不必再为每一项能力重写适配代码。
但一个任务从来不只是「缺一个工具」。
让 Agent 检查一篇文章,它找得到文件检查工具,却不知道先查 frontmatter 还是先查标题;它发现了错误,却不知道能不能直接改原文;它做完几步,却没有一条「什么算做完」的验收线。工具是有了,但「应该按什么方法把事情做完整」这套东西,还散落在每次手写的 prompt 里、散落在循环代码里、散落在写这个 Agent 的人的脑子里。
工具能被协议接入了,方法却还没有自己的载体。这就是第八话留下的那个问题。
MCP 拆的是「能力」,Skill 拆的是「方法」。两者是同一个动作,作用在不同的对象上。
Skill 不是把 prompt 写得更长,也不是给 Agent 再加一个配置文件。它把一套经过验证的做法,拆成能被发现、按需加载、逐条执行、可被脚本验收的资源。
一、方法没有载体,是真正的复用缺口
先看一个已经打通工具的场景:Agent 要检查一篇 Markdown 文章的结构。
它有工具,MCP 也能把工具发现、调用都串起来。可真正动手时,每一步都卡在「工具给不了答案」的地方:
| 任务里的问题 | 工具能不能回答 |
|---|---|
| 检查哪个文件 | 能,传路径即可 |
| 先查 frontmatter 还是先查 H1 | 不能,这是顺序 |
| 失败后继续还是停下 | 不能,这是分支 |
| 发现了错误能不能直接改 | 不能,这是权限边界 |
| 什么结果算「检查完成」 | 不能,这是验收标准 |
顺序、分支、权限、验收——这四样东西,工具一样都给不了。它们不属于「能力」,属于「方法」。
现在这个方法是存在哪里的?大概率是每次写一段很长的 prompt,让模型「认真、全面地检查文章,先看 frontmatter,再数 H1,最后确认有没有总结」。这段 prompt 每次都要重新写一遍,写的人不同、措辞就不同,改的时候也找不到一份权威版本。
这和第八话开头那个「N 个模型 × M 个工具」的集成问题,是同一类问题:能力可以被协议统一,方法却一直是私有的一次性产物。 你写的「检查文章的方法」,绑死在这一段 prompt 里、绑死在这一次任务里,换个人、换个 Agent、换一天,就没了。
要复用的不是「检查」这个动作,而是「怎么检查」。方法得有自己的载体。
二、Skill 只做一件事:把方法从执行者身上拆出来
MCP 做的事,是把「工具怎么实现」从「调用方的代码」里拆出来,变成 Server 暴露的协议。Skill 做的事,是把「方法」从「执行者的代码」里拆出来,变成文件。
拆完之后,职责是这样分开的:
| 谁 | 知道什么 | 不知道什么 |
|---|---|---|
| Agent 循环 / Host | 怎么发现 Skill、怎么匹配、怎么按步骤执行 | 这个方法具体要检查什么 |
SKILL.md | 这个 Skill 叫什么、解决什么、步骤顺序 | 规则怎么用代码实现 |
references/ | 要检查哪些规则 | 规则怎么逐条校验 |
scripts/ | 怎么用代码逐条校验 | 什么时候该用、结果怎么上报 |
注意这张表里的「不知道」那一列。一个拆得干净的 Skill,每一层都只管自己那一件事,既不替上层做决定,也不把下层的实现细节背到自己身上。
一个过于抽象的 Skill 往往没有可执行性:
# Markdown 质量检查
请认真、全面地检查文章。
这句话没有任何可以执行、可以验收的东西。「认真」「全面」不是步骤,是愿望。
拆出来之后长这样:
---
name: markdown-quality
description: Check Markdown articles for required frontmatter, one H1 title, and a closing summary.
steps:
- ref: references/checklist.md
- run: python3 scripts/validate.py {file}
---
差别不在后者更长,而在它把「方法」落成了三样能分别落盘、分别替换的东西:
description是方法的名字和适用场景——发现阶段只靠它;steps是方法的顺序——先读规则,再跑校验,Host 逐条执行;scripts/是方法里那些「结果只有对错」的确定性部分,交给代码而不是交给模型。
name + description 回答「什么时候该用我」,steps 回答「用我时按什么顺序做」,scripts 回答「哪一步不用模型猜、直接算」。三样东西拆开,才能各自独立演进。
三、方法拆出来了,怎么被找到、怎么被加载
一个 Agent 手里可能挂着几十个 Skill。如果每个任务一开始就把所有 Skill 的正文、参考资料、脚本全塞进上下文,上下文先被撑爆,匹配也变成大海捞针。
所以加载是分层的:发现靠一句话,加载靠匹配,执行才碰细节。
第一层决定一切。description 写得太泛,匹配阶段就没了线索;把无关资料全塞进入口,每次执行又背上一堆用不上的上下文。一个能打的 description 更像服务注册中心里的服务名片——它不执行业务,但它决定调用方能不能先找到正确的入口。
这和 MCP 的 tools/list 是同一套思路:先只给「叫什么、能干什么」的元数据,等调用方真正需要了,再给细节。
四、动手:一个把步骤真正拆出来的最小 Host
职责拆得对不对,用一个能跑的最小实现来看:Host 自己不知道什么叫「Markdown 质量」,它只做发现、匹配、加载、按步骤执行这四件通用的事;「检查什么、怎么检查」全部写在 Skill 自己的文件里。
代码在 GYA 的 c09-skill-system,Java 21 等价实现在 GYA-Java 的 c09-skill-system。目录长这样:
c09-skill-system/
├── main.py # Host:只做发现/匹配/加载/执行
├── skills/
│ └── markdown-quality/
│ ├── SKILL.md # 声明 name/description/steps
│ ├── references/
│ │ └── checklist.md # 要检查哪些规则
│ └── scripts/
│ └── validate.py # 怎么逐条校验
└── samples/
├── good.md # 合格样例
└── bad.md # 故意损坏的样例
Host 的关键代码只有一段——它把 SKILL.md 里声明的 steps 读出来,逐条执行:
# main.py —— Host 不知道"Markdown 质量"是什么,它只知道按 steps 执行
for step in metadata["steps"]:
if "ref" in step:
print(f"[load] {step['ref']}") # 加载资源
elif "run" in step:
validator_cmd = step["run"] # 拿到 Skill 自己声明的命令
for sample_name in ("good.md", "bad.md"):
sample = SAMPLES / sample_name
argv = validator_cmd.replace("{file}", str(sample)).split()
print(f"[run] {' '.join(argv)}") # 把 {file} 填成真实路径再执行
subprocess.run(argv, ...) # 校验结果由脚本给出
注意 validate.py 是 Skill 目录里自带的,{file} 是目标文件的占位符。Host 从头到尾不知道「要检查 frontmatter、要数 H1、要确认总结」——这些规则在 references/checklist.md 和 scripts/validate.py 里。Host 只是把 Skill 声明的步骤串起来跑。
运行环境只有 Python 3.9+,无第三方依赖,也不需要 API Key:
git clone https://github.com/renxin2024/GYA.git
cd GYA/c09-skill-system
python3 main.py
实际输出(只留关键行,完整输出见仓库该目录的 README):
[discover] markdown-quality → [match] → [load] → [run]
[validate] good.md -> PASS: frontmatter=ok h1=1 summary=ok
[validate] bad.md -> FAIL: frontmatter must be delimited by ---; missing closing section: ## 总结
[result] status=PASS
最后一行 status=PASS 不表示两份样例都合格。它表示 Host 的验收条件被满足:合格样例通过、故意损坏的样例被拦截。「验证器工作正常」和「被验证对象合格」是两回事。 把这两层分开,是写 Skill 时最基本的工程习惯——否则一个只会返回 PASS 的验证器,也会被当成「任务成功了」。
如果运行结果不同,按顺序排查:
- 确认当前目录是
GYA/c09-skill-system,否则 Host 找不到相对路径下的 Skill 和样例。 - 确认 Python 3.9+。
- 检查
SKILL.md的steps里是否还保留着ref: references/checklist.md和run: ... {file}两条;Host 只认这两条声明,缺了谁都会判定为无效 Skill。
五、方法里,哪些交给脚本、哪些交给模型、哪些交给权限
一个方法里,脚本和模型的分工要先定清楚。定反了,会得到两种同样坏的方法:让模型去数 H1 的个数,是把该确定性的事交给概率;让脚本去判断文章观点清不清楚,是把该语义的事交给规则。
| 工作 | 该交给谁 | 原因 |
|---|---|---|
| 判断文章观点是否清楚 | 模型 | 需要语义理解和上下文判断 |
| 判断语气是否自然 | 模型 | 需要综合语言和读者语境 |
| 统计 H1 数量 | 脚本 | 结果是确定的,不该让模型猜 |
| 检查 JSON 能否解析 | 脚本 | 成功或失败有明确判定 |
| 检查文件是否存在 | 脚本 | 不需要模型猜测 |
| 删除文件、发消息、部署 | 权限系统 + 人工审批 | 正确性不等于被授权 |
这条边界,就是 scripts/validate.py 和「让模型读文章给意见」之间的那条线:模型处理语义,脚本锁住不该漂移的事实,权限系统控制不可逆的影响。 Skill 的价值正是在任务层把这三类职责串成一条可检查的方法,而不是把其中任何一类神化成万能方案。
六、Prompt、Memory、MCP、Skill,各自回答什么
这四个词经常同时出现在 Agent 架构里,但回答的是四个不同的问题:
| 能力 | 保存或提供什么 | 回答什么问题 | 不能替代什么 |
|---|---|---|---|
| Prompt | 当前任务的即时指令与上下文 | 这次希望我怎样回答 | 可复用工作流与长期事实 |
| Memory | 过去的事实、偏好或经验 | 上次我们决定了什么 | 工具接入和任务方法 |
| MCP | 可发现、可调用的工具与资源 | 现在能做什么 | 步骤、验收和权限策略 |
| Skill | 完成一类任务的方法与边界 | 这件事应该怎样做 | 模型推理、外部工具、授权 |
在一个真实任务里,Memory 补充用户偏好,MCP 提供读写外部系统的工具,Skill 规定检查流程,Agent 循环推进步骤,脚本验证局部结果。它们协作,但不互相冒充。
把这四行连起来看,结论和开头一致:MCP 把「能力」做成了协议,Skill 把「方法」做成了文件。一个是「能做什么」的标准化,一个是「怎么做对」的标准化。
七、从 demo 到生产,方法还要补什么
把 SKILL.md 写出来只是起点。一个写错的方法,会让 Agent 更稳定地执行一套错误的流程,所以生产环境至少还要补:
- 用真实样例验证它在该触发时会被发现(而不是靠
description撞运气); - 用故意失败的样例验证验证器真的能拦住错误(
bad.md就是); - 为规则变化保留版本和变更原因,方法不是写完就不动的;
- 把写文件、发消息、发布、部署这类高影响动作,放在明确的权限边界之后;
- 记录每次执行轨迹,把反复失败的点沉淀为下一版规则。
这些是从这个几十行的 demo 推导出的工程实践,不代表 demo 已经具备生产能力。demo 只证明一件事:Host 不写「检查什么」,也能靠读 SKILL.md 里的步骤把检查跑完,「方法在文件里」这条边界是能落地的。
八、能力有了协议,方法也该有自己的载体
工具接入这件事,第八话用 MCP 收了口。方法复用这件事,靠的是把它拆成文件:经过验证的做法保存下来,下一次任务不必从零猜测。
当 Agent 既能调用工具、又能复用方法时,下一个问题自然浮现:这些「推理」和「行动」的方式本身,又是怎么一步步演化出来的?下一篇,我们沿着 CoT、ReAct、Toolformer、Reflexion 到 SWE-Agent 的线索,看 Agent 的推理范式是怎么被发明出来的。
参考资料
- Agent Skills Specification:Skill 的最小目录、frontmatter、可选资源与渐进加载约定。
- Agent Skills Quickstart:发现、激活和执行的最小示例。
- GYA 第九话 demo / GYA-Java 第九话 demo:本文配套的最小 Skill Host。
演示代码:Python 版在 GYA 仓库
c09-skill-system,Java 21 等价实现在 GYA-Java 仓库c09-skill-system。按各自 README 的命令跑,不要复制这篇文章里零散的代码块。

留言
欢迎分享你的想法。评论提交后会在审核通过后显示。