一个 skill 跑得好,靠的是三件事
最近刚好需要自己实现一套完整的 Skills 运行框架,所以把官方协议、源码、实现方案都啃了一遍。写这篇文章,就是想把自己踩的坑、悟出来的门道记录下来。对做 Agent 开发、做 skill 开发,或者单纯想搞懂 "我的 skill 为什么效果不稳定" 的人,应该都有帮助。
道理很简单,一个 skill 的强大,从来不是单点优势决定的。它靠三件事:
- skill 本身写得好不好
- 模型理解得对不对
- 框架支撑得稳不稳
三者缺一不可,尤其是框架对于 skill 能力的支撑,常常容易被忽略。
你见过这样的场景吗?同一个技能,在 A 框架上跑得很顺,换到 B 框架上就崩了;甚至同一个模型,换到不同 Agent 里,能力表现也天差地别。
这玩意儿不是玄学,是 Runtime Architecture 的锅。
skill 是什么?它长什么样
先说 skill 本身的结构。一个完整的 skill,通常由两部分组成:SKILL.md 和 配套文件夹。
SKILL.md 是技能的 "说明书"。它告诉模型:
- "我能干什么"
- “什么时候该用我”
- “具体怎么干”
配套文件夹是技能的 "工具箱",它存放:
- 脚本
- 模板
- API 文档
- 配置文件
- 数据文件
- 静态资源
这些配套文件,才是模型执行任务时真正会依赖的东西。
SKILL.md 是必须的,配套的文件夹可以没有。有些 Skill 只是行为引导,不提供任何脚本和资源文件,这类 Skill 本质上更接近 “高级 Prompt 模块”。
SKILL.md 的角色定义
你可以把 SKILL.md 理解成三个东西的合集:
- 身份证
- 操作手册
- 路由提示器
文件的开头是一段 YAML 格式的Front Matter,负责定义技能的身份信息:
---
name: hermes-agent
description: Configure, extend, or contribute to Hermes Agent.
category: devops
---
Front Matter 部分的字段,根据 Agent Skills 开放标准,有以下几个。
| 字段 | 必填 | 说明 |
|---|---|---|
| name | 是 | 技能名称,只能用小写字母、数字、连字符,不能以连字符开头或结尾 |
| description | 是 | 技能描述,告诉模型"这玩意儿能干嘛,什么时候用" |
| license | 否 | 许可证信息,可以放许可证名称或引用 |
| compatibility | 否 | 环境要求,比如"需要 macOS"或"需要 Docker" |
| metadata | 否 | 自定义元数据,随便填 |
| allowed-tools | 否 | 技能能用的工具列表,比如 ["terminal", "file"] |
其中 description 的作用,不单单只是 “技能介绍”,同时也是 Semantic Routing(语义路由)。
从某种意义上来说,description 更像是一个 “隐式分类器”,所以 description 写得差,Skill 甚至可能永远不会被调用。
Front Matter 下面是正文部分,也就是技能的具体执行内容。这部分没有固定格式,Markdown、纯文本、甚至伪代码都行,只要模型能读懂。
比如一个 "写周报" 的技能,完整的结构应该是:
---
name: weekly-report-generator
description: 自动生成标准规范的每周工作周报,涵盖本周工作总结、工作遇到的问题、下周工作计划、需要协调支持的事项四大核心模块,输出格式统一整洁,无需人工整理排版,可直接复用或微调。
---
# 写周报技能
## 功能
帮助模型生成每周工作周报,包含:
- 本周工作总结
- 遇到的问题
- 下周计划
- 需要支持的事项
## 输入
无(模型自行收集信息)
## 输出
Markdown 格式的周报
## 示例
### 本周工作总结
1. 完成了 XXX 功能开发
2. 修复了 YYY 问题
这段内容就是模型调用技能时看到的 "操作指南"。模型根据这个指南,决定怎么组织周报、怎么写措辞、要不要调用其他工具。
这里还有一个容易忽略的问题, 模型并不会像人一样 “认真阅读文档”。
所以真正工业级 Skill,往往会大量使用:
- checklist
- step-by-step
- XML tags
- hard constraints
- examples
- retry instruction
- structured output
配套文件夹:标准化 + 开放度
协议规定了三个标准文件夹:
scripts/ — 可执行脚本库
放脚本文件,比如analyze.py、run.sh、process.js。语言不限,由框架实现决定支持什么脚本语言。这个目录的设计非常灵活,用 Python、Bash、Node.js 都行。
references/ — 补充文档库
放额外的说明文档。官方不强制 .md 后缀,也不强制有哪些文件。你可以放 API 文档、流程图、表格、FAQ,一切靠约定。
assets/ — 静态资源库
用于存放资源文件:模板、图片、数据文件等。比如:
assets/demo.html —— 样式模板
assets/logo.png —— Logo
assets/sample.csv —— 示例数据
重点:允许自定义扩展
除了这三个标准文件夹,框架允许你自由添加。这也是协议真正聪明的地方,只规定 “最低公共结构”,不限制扩展。
比如很多 Agent 框架会添加:
templates/ —— 专门放模板文件
cache/ —— 缓存中间结果
logs/ —— 运行日志
这也是为什么不同 Agent 虽然都有 Skill System,但运行方式差异会非常大。
框架层:让 skill 真正跑起来的引擎
光有 skill 是不行的,还必须有 Runtime Framework。
框架对 skill 的处理,分三步:启动加载、动态召回、动态执行。
第一步:启动加载
Agent 启动时,框架会扫描所有 skill 目录,提取 Front Matter 中的 name 和 description,拼成一个 "技能菜单",塞进 system prompt。
<available_skills>
name: hermes-agent
description: Configure, extend, or contribute to Hermes Agent.
category: devops
---
name: youtube-content
description: YouTube transcripts to summaries, threads, blogs.
category: media
---
name: article-writing-style
description: 写作规范:篇幅、结构、语气、禁忌
category: productivity
---
</available_skills>
这玩意儿就是模型的 "能力菜单"。模型启动时就看到 "我手里有哪些家伙"。
为什么要只加载 Front Matter?
因为技能内容太多。假设你有 30 个技能,每个技能正文平均 3k token,那就是 9k token,不仅浪费,也容易导致模型注意力不集中。
所以只加载 name 和 description,大概每个技能 100-200 字符,可以大大减少 token 消耗,同时保证模型运行效果。
环境过滤:有些技能有环境限制,比如 "只在 macOS 运行" 或 "需要 Docker"。框架会读取 Front Matter 的 compatibility 字段,在启动时排除不可用的技能。比如 Agent 部署在 Windows 下,所有 compatibility: macOS 的技能直接跳过。
这样保证模型看到的菜单,都是"当前环境真正能用的"。
第二步:动态召回
模型启动后,如果需要用某个技能,但它的具体内容不在 system prompt 里,这时候就要 动态召回。
skill 框架一般提供两个工具 skills_list 和 skill_view。
tool 1: skills_list(category?)
这个工具返回某个目录下的所有技能,只有 name 和 description:
{
"skills": [
{"name": "hermes-agent", "description": "Configure, extend, or contribute to Hermes Agent."},
{"name": "youtube-content", "description": "YouTube transcripts to summaries, threads, blogs."}
]
}
这个工具更多是一个兜底策略。假设 model 的上下文被其他任务挤占,导致 system prompt 里的技能菜单丢失,它可以通过这个工具 "重新查一遍" 自己有哪些能力。
但实际上,这个工具很少被调用。正常情况,模型直接看 system prompt 里的菜单就够了。它更多是 健壮性设计,防止极端情况下的信息丢失。
tool 2: skill_view(skill_name, file_path?)
这个工具用于查看 skill 详情,分两种情况,是否传 file_path:
第一种:只传 skill_name,不传 file_path
skill_view(skill_name='hermes-agent')
返回:
{
"success": true,
"name": "hermes-agent",
"description": "Configure, extend, or contribute to Hermes Agent.",
"content": "正文内容(Markdown)...",
"path": "/home/laowang/.hermes/skills/hermes-agent/SKILL.md",
"skill_dir": "/home/laowang/.hermes/skills/hermes-agent",
"linked_files": ["scripts/config.py", "scripts/validate.sh", "references/api.md"]
}
此时工具返回的 skill 信息会略多,其中三个信息最关键:
- content —— 技能的正文,也就是具体执行内容
- skill_dir —— 技能的根目录,告诉模型 "如果需要执行脚本,从这里开始找"
- linked_files —— 关联文件列表,告诉模型 "技能目录下有哪些配套文件"
第二种:skill_name + file_path
skill_view(skill_name='hermes-agent', file_path='scripts/config.py')
返回:
{
"success": true,
"name": "hermes-agent",
"file": "scripts/config.py",
"content": "#!/usr/bin/env python3\n...",
"file_type": ".py"
}
返回内容变简单了,只剩下了 content 一个关键信息,包含了文件的完整代码或文本。
skill 的动态召回体现了一个非常关键的 设计哲学:先概览、再细节。
模型先知道 "这个技能有哪些文件",再决定 "要不要看某个文件的细节",最后决定 "看了之后要不要修改或执行"。
这种渐进式加载,既节省上下文,又保持灵活性。也是整个 Agent Runtime 最重要的 Context Management 技术之一。
第三步:动态执行
SKILL.md 和文件夹里的文件,光有 "说明书" 和 "素材" 还不行,还得有执行工具。
因为 SKILL.md 本身不会执行任何东西。
比如技能里有 scripts/analyze.py,模型要执行它,就得有一个 python_execute() 工具;如果技能里是scripts/run.sh,那得有一个bash_execute() 工具。
执行工具的作用:
- 提供执行环境:隔离的沙箱,防止模型乱来
- 安全校验:比如检查脚本路径是否在允许范围内
- 权限控制:比如限制脚本能访问哪些文件、能调哪些 API
其他执行工具:
除了脚本执行工具外,如果 skill system 支持自定义文件夹(例如 figures/图片.png,video/视频.mp4),框架还得提供相应的文件访问工具,比如 read_image()、download_file() 等。
如果光有对应的文件,没有配套的工具,那模型就只能假装用自然语言模拟代码逻辑。这时候 Skill 就退化成了 “纸上谈兵”。
Skill 系统的真正价值
Skills 系统不是 "多几个 Function Call 那么简单"。
它的本质是 知识层和执行层的分离。SKILL.md 负责知识层(告诉模型"干什么"),文件夹负责执行层(提供"怎么干"的材料),框架负责把两者串联起来。
它的核心价值是 渐进式披露。模型启动时只看到 name+description,用的时候再按需查看详情,避免上下文爆炸。
它的 开放度 也是关键。协议规定了三个标准文件夹,但允许自定义扩展。skill 框架的实现者可以按自己的场景添加响应的支持工具。
回到开头那个问题:技能强大 = 技能本身 × 模型能力 × 框架能力。
很多时候不是 Skill 写的有问题,也不是模型变蠢了。
而是 Agent 的 Skill 框架根本没把 Skill 真正跑起来。