做 Agent 开发的人,过去半年应该都有一个很明显的体感:Prompt 越来越长,长到你压根不想再去改它。我自己接手一个项目的时候,看到那份两万多字的系统提示词,第一反应是“这玩意儿谁维护谁知道”。更麻烦的是,每加一个新功能,就要往这坨巨型 Prompt 里塞一段指令,改一处往往连带破坏另外三处,排错排到怀疑人生。
后来我认真研究了 Agent Skills 这套架构,才算找到了一条比较“正经”的解法。简单说,就是把 Agent 的能力从“一大段提示词描述的逻辑”拆成“一个个独立、可插拔、可复用的技能包”,Agent 本体只负责调度和裁决。这当中不光是文件怎么放、指令怎么写的问题,更是一种从“单体应用”到“模块化”的架构思维转变。这篇文章我会从架构角度把这个方案彻底拆一遍,然后带大家从零做一个带 Skills 的前端开发 Agent,最后把我实际踩过的坑都整理出来,希望能帮到正在折腾 Agent 的各位。
1. Agent Skills 是什么:把“巨型 Prompt”拆成工具箱
1.1 从一个特别痛的场景说起
我不知道你有没有写过那种“全能型”Agent。一开始需求很简单,就是让它帮我整理资料、写代码、做表格,结果 Prompt 里什么都往里边塞:知识库路径、工具调用规则、输出格式、行业术语、禁止事项……写的时候觉得挺全,用起来问题立刻就来了。
第一个问题是上下文窗口再大也架不住这么造。每次调用都要把全量 Prompt 带着走,成本高、响应慢,模型反而容易抓不住重点。第二个问题是改一处常常破坏全局:今天加了一句“不要用 markdown 表格”,明天发现它连列表也不愿意用了。第三个问题最要命,调试极其痛苦,你根本不知道是哪个句子、哪个顺序导致它突然抽风,可能删掉一个不相关的词,整个行为就变了。
Agent Skills 架构解决的正是这些问题。它的核心思想非常简单:把能力拆开。一个 Skill 就是一个专注于单一任务的技能包,里面有一份说明文档(通常叫 SKILL.md),还可以配脚本、模板、样例数据。Agent 运行的时候,只有当它判断当前任务需要某项技能,才会去读取对应的 SKILL.md,加载对应的脚本。平时这些技能包就是躺在仓库里的普通文件,既不占上下文,也不会互相干扰。
1.2 Skills 架构的三个核心组成
一个标准的 Agent Skills 架构,通常可以拆成三层来看。这三层不是我发明的,而是我从几个主流 Agent 框架和社区项目里归纳出来的通用结构,落地时你可以直接按这个思路去设计:
技能包层(Skill Layer):这是能力的载体。每个技能包是一个独立文件夹,里面是 SKILL.md(给模型看的说明书)、scripts(给机器跑的执行代码)、references(参考资料和样例)。技能包之间默认互相隔离,没有隐式依赖,谁也不会偷偷引用谁的内部文件。
调度层(Orchestration Layer):这是 Agent 本体做的事。它读取用户请求,拆解任务,判断“当前任务需要调用哪些技能、按什么顺序调用”,并负责在技能之间传递中间结果。说白了,调度层是大脑,技能包是工具箱里一个个独立的工具。
运行时层(Runtime Layer):负责技能的实际执行。模型决定调用技能后,会通过函数调用或者 MCP 这类协议去执行技能包里的脚本,然后把执行结果返回给模型。这一层还负责权限控制、资源限制、日志记录这些基础设施的事。
我第一次看到这套设计时觉得有点“杀鸡用牛刀”。真要自己维护一个多功能 Agent 时才发现,这层隔离太重要了。没有隔离,变动是全局的;有了隔离,变动是局部的。
1.3 为什么这套架构现在成了主流
再往深了说,Skills 架构流行起来,是因为它把 Agent 开发从“写 Prompt”变成了“做产品”。在传统思路上,每加一个能力等于改一段巨大的文本;在 Skills 思路上,每加一个能力就是新增一个独立目录,完全可以像管理代码库一样管理和评审。多人协作的时候,一个人负责数据分析技能包,一个人负责前端开发技能包,互不冲突,合并代码时也不需要在几万字的 Prompt 里找冲突位置。这一点对团队项目来说非常友好。
另外,Skills 架构天然适配大模型当前的“长上下文”趋势。模型不需要把所有技能说明都背在脑子里,它只需要知道“系统里有什么技能可用”,用到的时候再去取说明书,这跟我们人脑的工作方式很像——你不会把螺丝刀的说明书背下来,用到时看一眼就行。这个类比虽然简单,但实际效果极其明显,我试过之后,上下文占用直接降了将近一半。
2. Skills 架构的设计思路:模块化、可复用、可组合
2.1 为什么“单一职责”是铁律
在设计 Skills 架构的时候,第一原则就是每个技能包必须单一职责。所谓单一职责,就是你不要试图去做一个“万能技能包”,而是让每个技能包只回答一个问题、只完成一类任务。前端开发这个领域尤其典型:做组件的技能和做样式规范的技能,最好拆开;写单元测试的技能和写端到端测试的技能,也最好拆开。
你可能觉得拆得太细会让技能包数量爆炸,实际运行下来反而更省心。因为模型在判断“该用哪个技能”的时候,是靠技能描述来做匹配的,描述越精准,匹配越准确。一个“前端开发”的大包描述写起来非常尴尬,你写“负责前端的一切”,模型根本不知道该什么时候触发它;但如果你拆成“create-react-component”“fix-css-layout”“generate-unit-tests”三个包,每个包的触发条件都非常明确,模型一看到相关任务就能准确命中。
我自己的经验是,一个技能包的职责范围最好控制在“描述不超过三句话就能说清楚”的粒度。如果三句话说不清,说明这个包还是太大了,继续拆。
2.2 技能包的标准目录结构长什么样
技能包的物理结构并不复杂,但规范必须统一。我目前项目里对每个技能包都强制规定这样一个目录结构:
skills/ └── frontend-helper/ ├── SKILL.md ├── scripts/ │ ├── scaffold.sh │ └── check-convention.py ├── templates/ │ ├── Component.tsx.tpl │ └── component.test.tsx.tpl └── references/ └── project-style-guide.md其中 SKILL.md 是核心,必须放在技能包根目录,文件名固定;scripts 放可执行脚本;templates 放模板文件;references 放参考资料。这个结构不是随便定的,它遵循的就是“渐进式披露”原则:模型先看到的是技能包的名称和一段简短描述,只有当它决定加载这个技能,才会看到完整的 SKILL.md 内容,再往下一层才会看到具体脚本和模板。每一层都比上一层披露更多细节,这样上下文开销最小。
关于目录命名,我建议一律用小写加连字符,比如 create-react-component、fix-css-layout。一方面是因为文件系统对大小写敏感容易出错,另一方面是模型对“连字符分隔的纯英文短语”识别稳定。我见过有人用中文目录名,不是不能用,但在函数调用和路径拼接时偶尔会出现编码问题,不建议在生产环境里这么搞。
2.3 技能之间如何协作:显式依赖与数据流
模块化之后,马上就会遇到一个新问题:技能之间需要协作怎么办?比如“生成 React 组件”之后紧接着“为组件写单元测试”,这是两个技能包,但第二个技能包需要知道第一个技能包创建了哪些文件。
我在实践中总结出的原则是:技能之间的数据传递必须走显式的中间产物,不允许技能 A 直接读取技能 B 的内部文件。正确的做法是让调度层做一次“状态转交”:技能 A 完成任务后,把产出文件路径和关键信息写进一个共享的工作区目录,然后调度层调度技能 B 时,把这个工作区路径传给技能 B。技能 B 只认路径和输入输出格式,不关心这个文件是谁生成的。
这样做的好处是技能包彻底解耦。你可以单独替换某一个技能包,而不需要动其他任何代码。反过来,如果技能之间直接耦合,那拆了半天等于没拆,又回到了单体 Prompt 的老路。这里我强调一句:Skills 架构的灵魂不是“文件分开放”,而是“运行时不互相依赖”,这一点很多新手容易理解偏。
3. 实操:从零打造一个带 Skills 的前端开发 Agent
3.1 工具选型:框架和平台的取舍
真正上手之前,先解决工具问题。目前主流支持 Agent Skills 的运行时有好几个,我这边实测下来比较顺手的是 Claude Code,社区里也有一批成熟的技能集合可以直接参考,比如 superpower-skills 这类开源仓库,本质上就是一堆写好的 SKILL.md 和配套脚本。如果你用的是 Codex 或者 OpenCode 这类工具,它们大多也支持类似的 skills 目录约定,只是读取位置和配置方式略有差异。
我的建议是:第一次做实验,直接选一个你日常就在用的工具,别为了“新技术”切换主战场。我用 Claude Code 做实验,是因为它的个人技能目录和项目技能目录分工清晰,个人目录放在~/.claude/skills/,项目目录放在.claude/skills/,个人技能所有项目都能用,项目技能只对本仓库生效,这个区分非常符合我的协作需求。
安装一个来自 GitHub 的第三方技能包,流程也简单:把对应的技能文件夹克隆或者复制到技能目录下,重启会话,然后通过对话确认模型能发现它。这里有一个容易被忽视的点:技能包更新后,当前会话不一定能立刻感知,需要新开一个会话再试。我在初期经常因为没重开会话,误以为技能没装上。
3.2 SKILL.md 怎么写,模型才“一眼就会用”
SKILL.md 是技能包的大脑。它的格式基本是 YAML frontmatter 加上 Markdown 正文,frontmatter 里最重要的两个字段是 name 和 description。注意,description 不是给你自己看的,是给模型看的。模型判断该不该加载这个技能,全靠 description 里的描述是否与当前任务匹配。
我总结了一个公式:description = 这个技能做什么 + 什么场景下触发 + 什么场景下不要触发。比如:
--- name: create-react-component description: Generate a new React component with tests and stories, following project conventions. Use when asked to create a React component. Do not use for utility functions or hooks. ---最后那句“Do not use for...”尤其重要,它就像一个反向过滤器,能显著减少模型误调用其他技能的概率。正文部分我建议按“何时使用 / 操作步骤 / 输出规范 / 常见陷阱”四段来组织,每段都要短。模型读 SKILL.md 也是要花 token 的,写得太长反而稀释重点,我见过有人把 SKILL.md 写成几千字的大报告,模型加载完依然不知道第一步该干嘛。
正确的节奏是让每个技能包“十分钟内能读完、三步内能执行”。如果步骤超过五步,就要考虑是不是应该把其中一部分下沉到 scripts 脚本里,让模型只负责“调用脚本”和“校验结果”,而不是亲力亲为地做每一步。
3.3 完整案例:做一个“生成 React 组件”的技能包
下面我直接给出一个我实际在用的前端开发 skills 案例,大家可以直接抄。这个技能包的目标是:当用户要求“创建一个按钮组件”时,Agent 自动加载该技能,调用脚手架脚本,输出符合项目规范的组件文件、测试文件和样式文件。
首先是 SKILL.md:
--- name: create-react-component description: Create a new React component with matching test file and CSS module. Use when the user asks to build a UI component like Button, Card, Modal. Do not use for pages or routes. --- ## When to use Use this skill any time the user requests a new UI component. If the component already exists, update the existing files instead. ## Steps 1. Read the project conventions in references/project-style-guide.md. 2. Run scripts/scaffold.sh --name <ComponentName>. 3. Verify the generated files compile and check naming conventions. ## Output - Component file in src/components/<ComponentName>/index.tsx - Test file in src/components/<ComponentName>/<ComponentName>.test.tsx - Styles in src/components/<ComponentName>/<ComponentName>.module.css ## Pitfalls - Component names must be PascalCase. - Do not create default exports unless the project style guide says otherwise. - If the user also asks for a page, tell them to use the create-page skill instead.然后是配套的脚手架脚本,我这里用最朴素的 bash 来实现,方便大家理解核心逻辑:
#!/bin/bash # scripts/scaffold.sh set -euo pipefail NAME="$1" DIR="src/components/${NAME}" mkdir -p "$DIR" cat > "$DIR/index.tsx" <<EOF import styles from './${NAME}.module.css'; export function ${NAME}() { return <div className={styles.root}>${NAME}</div>; } EOF cat > "$DIR/${NAME}.test.tsx" <<EOF import { render, screen } from '@testing-library/react'; import { ${NAME} } from './index'; test('renders ${NAME}', () => { render(<${NAME} />); expect(screen.getByText('${NAME}')).toBeTruthy(); }); EOF cat > "$DIR/${NAME}.module.css" <<EOF .root { padding: 0; } EOF echo "Component ${NAME} created."注意脚本里用的是${NAME},而不是直接写死组件名,这是为了让同一个技能包能复用于任意组件名。你在自己的项目里可以进一步扩展,比如读取项目里的组件命名规范、自动注册 Story 等,但核心逻辑就是这个思路:脚本负责机械劳动,模型负责判断和校验。
3.4 安装、测试与迭代:技能包也要有 CI 思维
技能包写完后,测试环节不能省。我目前的做法是给每个技能包准备一组测试任务,比如这个 create-react-component 技能包,测试用例就是“创建一个名为 Header 的组件”“创建一个名为 UserCard 的组件,并要求测试文件”。通过对话逐条执行这些用例,检查输出是否符合预期。
这里我强烈建议引入 evals 的思路,也就是评估集。第一次写完技能包,先记录它正确触发的次数和失败次数。之后每次修改 SKILL.md,都重新跑一遍同样的用例,看成功率是升还是降。你会发现,改一个 description 的措辞,成功率可能从 60% 跳到 90%,也可能从 90% 掉到 50%,这种波动不通过回归测试根本察觉不到。
我在一个项目里维护了 12 个技能包,每次升级模型版本或者更新技能包,都会花一个下午完整跑一遍评估用例。这看起来笨,但它是保证 Agent 行为稳定的唯一办法,比任何“精心设计的提示词”都靠谱。
4. Skill 和 Agent 的边界:常见误区与设计原则
4.1 Skill 不等于 Agent,Agent 也不等于 Skill 的集合
最近社区里关于 “skill 和 agent 的区别” 的讨论特别多,我看了不少项目,发现一个很普遍的误解:有的人把所有能力都做成技能包,然后宣称自己搭了一个 Agent。实际上,技能包只是“能力模块”,没有调度逻辑的话,它不过就是一堆文档和脚本,不可能自主完成多步任务。
在我看来,Skill 和 Agent 的关系更像函数库和程序的关系。函数库提供能力,程序负责用这些能力解决问题。Agent 至少要包含决策逻辑:在当前对话上下文里,它要能判断“用户到底想要什么”“哪个技能能帮上忙”“技能执行的中间结果是否符合预期”“失败了应该重试还是换一条路”。如果没有这层决策,那你做的只是一个“技能包浏览器”,不是 Agent。
反过来说,Agent 也不应该只依赖技能包。有些高频操作,比如“读取当前项目文件结构”“执行测试命令”,这些本来就在 Agent 的基础能力范围内,没必要硬包一层技能包。技能包的粒度应该介于“基础工具调用”和“完整业务功能”之间,太细了是重复造轮子,太粗了又回到单体 Prompt 的老路。
4.2 容易被误当成 Skills 的东西
我还在不少项目里见了一些奇怪的用法,这里统一提醒一下:
把知识库文档直接当技能包:知识库是用来被检索的参考资料,技能包是用来被执行的操作流程。如果你把一个 50 页的产品文档丢进技能包目录,模型每次加载它都要消耗大量上下文,而且它并不知道该怎么“执行”这份文档。正确的做法是,把文档放到 references 里,由技能包按需引用其中一小部分。
把工作流编排写成技能包:有些项目把 LangChain 或者 LangGraph 里的工作流逻辑硬编码进技能包脚本,这恰恰和 Skills 架构的初衷相反。工作流是调度层的职责,技能包应该尽量无状态、无流程。如果一个技能包内部偷偷写死了“先做 A 再做 B”,那下次遇到“先做 B 再做 A”的需求时,你就只能再写一个几乎一样的技能包。
把权限校验逻辑写进技能包:文件的读写权限、敏感操作确认这类事情,应该由运行时层统一控制,而不是让每个技能包自己判断。否则等于每个技能包都开了一个安全后门,审计的时候根本查不过来。
4.3 什么情况下不要硬上 Skills 架构
我也得说句公道话,Skills 架构不是银弹。如果你的 Agent 只需要完成一个非常固定的任务,比如“每天定时抓取天气数据并推送”,那用单体 Prompt 反而更简单,引入技能包架构纯属给自己加戏。我判断是否上 Skills 架构的标准很简单:看需求是否在稳定增长。如果你每隔两周就要给 Agent 加一个新能力,而且这些能力之间经常需要不同团队成员各自维护,那 Skills 架构值得上;如果你只是做一个一次性脚本,别折腾。
另外,如果你的团队里只有一个人短期维护,也不一定要一上来就搭完整的三层架构。可以先从“两个技能包 + 一个简单的规则判断”开始,跑通了再逐步加层次。架构是长出来的,不是一步到位的,这一点我在好几个项目里都反复验证过。
5. 常见问题与排查技巧实录
5.1 技能包“没被触发”的排查思路
这是群里问得最多的问题:技能包明明装好了,模型就是不调用它。我排查这类问题,基本按下面这张表来:
| 现象 | 可能原因 | 处理方法 |
|---|---|---|
| 模型完全不知道技能存在 | 技能目录不在扫描路径内 | 检查是个人目录还是项目目录,路径是否匹配工具约定 |
| 模型知道技能但从不使用 | description 与任务描述匹配度低 | 重写 description,加入触发词和反触发词 |
| 使用率忽高忽低 | 技能包有多个,描述互相重叠 | 收紧各技能包的边界,增加“Do not use when” |
| 同一会话内行为不一致 | 会话上下文过长,模型“忘了”技能列表 | 在关键节点重述技能清单,或者拆分会话 |
| 技能包更新后仍用旧行为 | 技能未重新加载 | 新开会话,确认文件路径和版本 |
我最常犯的错误就是 description 写得太抽象。早期我给一个“做图片处理”的技能包写 description 是“Handles all image-related tasks”,结果模型什么都往这里塞,又什么都不满意。改成“Convert, resize, and compress image files with ImageMagick. Use when the user provides an image file or asks to modify one. Do not use for generating images.”之后,准确率立刻上来了。
5.2 技能包加载多了,上下文还是会爆炸
有的场景下,用户一次对话会触发多个技能包,比如“创建一个组件,然后给它加测试,再跑一次全量测试”。如果每个技能包都全文加载,上下文还是会快速增长。我的解决办法是控制 SKILL.md 的篇幅,优先精简“背景说明”和“原理讲解”,保留“步骤”和“输出规范”。
记住一个原则:模型执行技能时,真正需要的是“下一步做什么”,而不是“为什么要这样做”。每个 SKILL.md 建议控制在 300-500 字以内。如果某些背景知识确实重要,放到 references 里,让模型按需读取具体文件,而不是把内容都铺在 SKILL.md 正文里。我实测下来,这个做法能把单次复杂任务的上下文峰值减少三成以上。
另外,合理利用“技能执行完毕后的状态压缩”。也就是让技能包脚本把中间结果浓缩成一份摘要,写回工作区,调度层只把摘要传给下一个技能包,而不是把完整的执行日志都带进下一轮。这相当于给 Agent 做了内存管理,非常实用。
5.3 技能包的安全边界问题
最后聊一个容易被忽略但很重要的话题:安全边界。技能包里的脚本是要在本地执行的,它有真实的文件系统访问权限,有的还会调用外部命令。这意味着一个来源不明的技能包,理论上可以读取你的配置、删除你的文件。我从一开始就给自己定了几条规矩:
- 技能包从 GitHub 安装后,必须人工 review scripts 目录里的所有脚本,再进入到项目技能目录。
- 技能包脚本不允许使用
sudo,运行前要有一层确认,关键时刻 Agent 会停下来问你“是否允许执行”。 - 每个技能包只授权它需要的那个工作区目录,不要默认放行整个文件系统。
实际上呢,我在某次测试一个第三方图片处理技能包时,发现它的脚本里居然有一段遍历我主目录的逻辑。虽然不一定是恶意代码,但这种行为绝不该出现在一个“正常”技能包里。从那以后,我对技能包的信任策略就变成了“默认不信任”,跟对待任何开源依赖一样。这不是劝退,而是想说:技能包是代码,不是提示词,它带来的安全影响完全不同。
最后再说几句实在话
这套 Skills 架构我前后实践了几个月,最大的感受是:它真正改变了我和 Agent 的协作方式。以前写 Prompt,每改一次都像是一次“玄学调参”,现在更像是在维护一个内部开源项目,每个能力都是独立的模块,可以单独测试、独立迭代,坏了就退回上一个版本。
关于技能包的维护,还有一个小技巧:我会在技能包里放一个CHANGELOG.md,记录每次修改前后评估集通过率的变化。群里经常有人问我“为什么同一个技能包,上周好用这周失灵”,十有八九是因为修改后忘了回归测试,或者换了模型版本没重新跑一遍。有了这个变更日志,排查起来真的能省一半时间。
最后再提醒一下想入坑的朋友:第一次不用追求大而全,挑一个你自己每天都在重复的工作流,做一个技能包出来,然后反复用它、改它,跑通了再去扩展。先让一个技能做到“让日常操作的效率肉眼可见地提升”,这比搭一个花架子架构有价值得多。