
做 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 的前端开发 Agent3.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 DIRsrc/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 不等于 AgentAgent 也不等于 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记录每次修改前后评估集通过率的变化。群里经常有人问我“为什么同一个技能包上周好用这周失灵”十有八九是因为修改后忘了回归测试或者换了模型版本没重新跑一遍。有了这个变更日志排查起来真的能省一半时间。最后再提醒一下想入坑的朋友第一次不用追求大而全挑一个你自己每天都在重复的工作流做一个技能包出来然后反复用它、改它跑通了再去扩展。先让一个技能做到“让日常操作的效率肉眼可见地提升”这比搭一个花架子架构有价值得多。