先说一个我自己的真实场景。上个月我用Claude做一次前端重构,目标是把老项目里散落的各种按钮统一成设计系统里的三个变体。前两三天一切正常,但一到“生成新组件并保证它符合设计规范”这种细节环节,模型就开始自由发挥:有时候class命名不符合规范,有时候忘了写loading态和禁用态,我必须反复在对话里粘贴规则、强调约束。直到我按Agent Skills的规范写了一个“design-system-enforcer”技能包,把这些规则固化进去,Claude才真正表现得像团队里的老手,而不是一个记性不好的实习生。
这篇东西写给所有正在做AI Agent开发、或者天天用Claude、Codex这类工具干活的人。围绕“agent-skills”这件事,我想聊清楚几个问题:Agent Skills到底解决了什么、它的工作原理是什么、怎么自己动手写一个、怎么安装分发,以及它和Tools、MCP、Harness、子Agent这些东西的分工边界。全文不做理论空谈,都是我实际踩过、验证过、正在生产环境里跑的经验。
如果你还不确定Skills和普通Prompt的区别,可以先记一个定义:Skills是“给Agent办的岗位说明书加操作手册加工具箱”,而不是一段一次性消费的提示词。这个定义会贯穿整篇文章。
1. Agent再聪明,也逃不过“碎片化失败”——Skills出现的真实背景
1.1 从一次改版需求说起:Agent反复“差一步”的原因
可能你也遇到过这个现象:同一个Agent,让它写通用的Python脚本,表现惊艳;让它按照你团队的代码规范产出代码,就开始各种“差不多先生”。原因并不复杂——大模型的知识是广谱的,什么领域都懂个大概,但没有任何一个领域能精通到“肌肉记忆”的层面。这导致Agent在处理长尾、高频、有明确约束的任务时,成功率和稳定性都会明显下降。
我把这种表现叫作“碎片化失败”。它和通用任务上偶尔犯错的随机性不一样,碎片化失败是可预测的、固定场景下的连环出错:同一类细节反复丢失,同一个规范反复违反,而且每次都要你手动纠正。做过生产级Agent应用的都知道,这种“差一步”比“完全不会”更让人崩溃,因为你无法预测它哪一步会掉链子。
举一个具体例子。我们希望Agent在生成前端代码时自动遵守组件规范:颜色只能从design-tokens中取,圆角分三档,禁用态必须有opacity。用纯Prompt的方式,你必须在每条指令里都粘贴一遍规范,Token消耗大,而且规范一旦变长,模型根本记不住。用Tool的方式,你需要把它变成一个函数调用,但“组件规范”是半结构化知识,不是一个API边界清晰的操作。这两种方式都解决不了“让Agent在恰当时机自己想起规范并用起来”的问题。
1.2 Skills解决的不是“知道什么”,而是“会做什么”
Agent Skills这个概念(Anthropic在2025年推出,Codex生态里也有对应实现)本质上是在Prompt和Tool中间补了一层东西:行为知识包。它把“该做什么事”“按什么顺序做”“用什么脚本辅助”“输出格式是什么”全部打包进一个独立目录,Agent在遇到匹配场景时按需加载,而不是每次把全部知识塞进上下文。
想明白这一点,你就会发现Skills真正解决的不是让模型“知道”更多,而是让模型“会做”更多。知道一个东西只需要“读过”,会做一个东西需要“读加练加判断”。Skills里的SKILL.md是指南,脚本是把判断变成可执行动作,资源文件是参考材料,三者合在一起才称得上一个技能,而不是一段记忆。
我在实际项目里还观察到,Skills对“稳定性的提升”远比“上限的提升”更明显。它不会让Agent突然学会完全不懂的东西,但能让Agent在80%的场景下稳定地不犯低级错误。对于要上生产的Agent系统,稳定性比惊艳值钱得多。
2. Skills的第一性原理:SKILL.md、渐进式披露、执行器如何协同
2.1 SKILL.md:一份给Agent读的使用说明,同时也是触发开关
一个标准的Agent Skill,目录里最关键的文件是SKILL.md。它用Markdown写成,头部是YAML frontmatter,包含name和description,正文写清这个技能什么时候用、操作步骤是什么、有什么注意事项。
关键在于Agent不会把每个Skills全文都读一遍再干活。它采用的是渐进式披露的机制:先只读每个技能包的description,判断当前任务和哪个技能匹配,匹配了才加载对应SKILL.md全文,再按需调用脚本和资源。这就好比你的工具箱里挂了20个标签,Agent先扫一眼标签,需要哪把就打开哪把,而不是把所有工具一股脑摊在地上。
这个机制设计的原因很实际:如果把所有Skills都塞进上下文,Token消耗会爆炸,模型的注意力也会被稀释,结果是“啥都看了,啥都没记住”。渐进式披露保证了“知识不进上下文,直到真正需要它”这一点,对执行质量和成本控制都是决定性的。
补充一个踩坑经验:description里一定要写清楚“什么时候不要用这个技能”。我发现很多Agent会把技能当万金油,场景不匹配也强行调用。负面描述往往比正面描述更能提高检索准确率。比如我会在技能描述里加一句:“当用户只是在讨论设计方案、并不需要生成代码时,禁止调用此技能。”
2.2 知识、动作、经验三层打包的工程化实现
SKILL.md可以引用同目录下的脚本、模板、参考文档、JSON配置。也就是说,一个技能包是一个有内部结构的目录,我习惯分成三层:
- 知识层:规范文档、设计原则、常见反例,解决“Agent需要了解什么”。
- 动作层:可执行的Python或Node脚本,处理批量操作、文件生成、数据校验,解决“Agent需要做什么”。
- 经验层:输出模板、检查清单、常见错误清单,解决“Agent怎么保证做对”。
这三层结构一旦落到实际场景,效果是碾压式的。拿“生成符合团队规范的React组件”这个技能举例:知识层放组件规范,动作层放一个脚手架脚本,经验层放组件自检清单。Agent接到任务后不会凭记忆硬编,而是读规范、跑脚本生成骨架、按模板填充内容、最后用检查清单自检。整个过程是一条可复现的流水线。
动手之前我也纠结过一个问题:这种三层打包,和“把规范文档直接拖进项目目录让Agent读”有什么本质区别?区别在触发时机。普通文档是靠用户主动提示或模型偶然想起才可能被读取,而Skills的description会被Agent在每轮任务开始时主动扫描,触发逻辑是自动且优先的。“自动”和“优先”这两个词,才是Skills比“塞文档”高级的根本原因。
3. 实战:从零实现一个真正的Agent Skill
3.1 需求拆解:选一个“高频、长尾、可固化”的场景
先交代需求背景。我团队的项目用一套自定义设计系统,我不希望每次生成代码时都口头强调规范。这个场景有三个特点,非常适合做一个技能包:高频(几乎每个页面都在用组件)、长尾(规范细节很多,模型不可能完全背上)、可固化(规范已经写成文档,打包就能带走)。
我总结过“选场景的三问”:这个任务是不是每周都会出现?模型是不是在没有提示的情况下反复出错?我手上是不是已经有一份可落地的规范文档?三个回答都是“是”,才值得做。技能包的开发本身有成本,千万别为了炫技去做。
3.2 建立技能目录与SKILL.md:怎么写才能被正确触发
先看目录结构。我把它放在Claude Code的skills目录下:
design-system-enforcer/ ├── SKILL.md ├── scripts/ │ └── scaffold_component.py ├── references/ │ ├── design-tokens.md │ └── component-guidelines.md └── templates/ └── component-checklist.mdSKILL.md的YAML头和正文可以这样写(简化版):
--- name: design-system-enforcer description: 在生成React/TypeScript组件时使用。当用户要求新增按钮、输入框、卡片等UI组件,或需要让组件符合团队设计系统规范时,自动加载此技能。当用户仅讨论设计想法、不需要产出代码时,禁止调用。 ---正文部分我会写三段。第一段“执行步骤”,告诉Agent先读references里的两个文档,再跑脚手架脚本,最后用templates里的清单做检查;第二段“输出约定”,规定组件文件、样式文件、测试文件的命名和导出方式;第三段“常见错误”,列出模型最容易犯的五个规范问题,要求生成后逐项对照。
这段写完,要让Agent真的“动手”,而不是“想了想就过”。所以我特意在SKILL.md里加了一句“必须运行scripts/scaffold_component.py来生成文件骨架”,用强指令锁死执行链。这个细节非常重要,因为模型在模糊指令下倾向于“直接生成文件内容”,而不是“先跑工具再写内容”。
3.3 辅以脚本和资源文件:技能从“会读”变成“会做”
脚本部分我写了一个轻量的Python脚手架。核心代码是这样的:
#!/usr/bin/env python3 import argparse import os from pathlib import Path def _tsx_template(name: str, comp_type: str) -> str: return f"""import React from 'react'; import styles from './{name}.module.css'; export interface {name}Props {{ label: string; disabled?: boolean; loading?: boolean; }} export function {name}({{ label, disabled = false, loading = false }}: {name}Props) {{ return ( <button className={styles.base} >