1. 从“agent-skills”说起:为什么我们需要给AI编码代理装上一套技能系统
第一次看到agent-skills这个项目名的时候,我脑子里冒出来的第一个念头是:这不就是把“提示词工程”从散装时代推进到“工程化时代”的那块拼图吗。过去大半年我一直在用 Claude Code 这类 AI coding agent 干活,从写脚本、重构老代码到补测试,几乎每天都要和它打交道。用得越多越发现一个问题:同一个模型,给不同的上下文和指令,产出质量能差出好几倍。你随口一句“帮我写个函数”,它给你一个能跑但边界条件全漏的版本;你换一套结构化的技能描述,它立刻能写出带类型标注、异常处理、单元测试的完整模块。
agent-skills要解决的就是这件事。它本质上是一套面向 AI 编码代理的技能定义与分发机制,配套一个skills CLI,让你可以把“怎么让代理干某类活”这件事沉淀成可复用、可版本管理、可共享的模块。你可以把它理解成给 Claude Code、Cursor、Copilot 这类工具装插件——只不过插件里装的不是代码逻辑,而是结构化的行为指令、工作流约束和领域知识。
这个项目适合谁?三类人最该关注。第一类是重度使用 AI coding agent 的独立开发者,你天天让代理帮你写代码,但每次都要重复交代同样的规矩,累不累?第二类是团队里的技术负责人,你想让团队所有人的 AI 助手都遵循同一套代码规范、测试标准、提交约定,靠嘴说靠文档传都不靠谱,得有个能落地的载体。第三类是对 test-driven-development 有执念的工程师,你想让代理先写测试再写实现,而不是反过来,这需要技能层面的强制约束。
我自己的使用场景很典型:手上维护着几个跨语言的老项目,Python、TypeScript、Go 都有,每个项目的测试框架、目录结构、命名习惯都不一样。以前每次开新会话都要花五分钟给 Claude Code “上课”,现在我把这些规矩写成 skill,skills命令一挂,代理立刻进入状态。这篇文章我就把这套东西从设计思路到实操细节完整拆一遍,包括我踩过的坑和实测有效的配置。
2. 核心设计思路拆解:技能为什么比提示词更靠谱
2.1 从“一次性提示”到“可复用技能”的演进逻辑
先说清楚一个概念区分。提示词是你对代理说的一句话,技能是你为代理准备的一整套工作手册。这两者的差别,类似于你临时给新员工口头交代任务,和给他一本写满流程规范、检查清单、常见错误案例的岗位手册。
为什么一次性提示不够用?我做过一个粗略统计,在我日常的编码任务里,有超过六成的工作是重复性的模式化操作:写一个符合项目规范的 React 组件、给现有函数补 pytest 测试、按约定格式写 commit message、把一段回调风格的代码改成 async/await。每次都用自然语言重新描述这些要求,不仅浪费时间,更致命的是描述本身会漂移——今天你说“测试要覆盖边界情况”,明天你说“测试要全面一点”,代理理解出来的东西就不一样,产出质量自然不稳定。
agent-skills的核心设计就是把这类重复性要求固化下来。一个 skill 通常包含几个部分:技能名称与触发条件、适用场景描述、具体的操作步骤或约束规则、示例输入输出、以及验证标准。当代理识别到当前任务匹配某个 skill 的触发条件时,就会加载对应的指令集来指导自己的行为。
这个机制背后有个很实在的工程考量:上下文窗口是稀缺资源。你不可能把所有规范都塞进系统提示里,那样既浪费 token 又会稀释重点。技能系统允许按需加载——写测试的时候加载测试技能,做代码审查的时候加载审查技能,各管各的,互不干扰。
2.2 技能与模型能力的关系:为什么换模型也能用
热词里有一堆关于“用 cc switch 接入 deepseek、qwen、glm 等模型”的搜索,这其实点出了一个关键问题:技能系统是不是绑定特定模型的?
我的实测结论是:技能定义本身是模型无关的,但效果会有差异。原因在于,技能本质上是一段结构化的自然语言指令加上可能的工具调用约定,任何具备指令遵循能力的模型都能读。但不同模型对复杂指令的解析精度、对多步骤工作流的执行稳定性、对“先写测试再写实现”这种顺序约束的遵守程度,确实参差不齐。
我拿同一个“TDD 技能”分别在几个模型上跑过。表现最好的是原生支持工具调用且指令遵循训练充分的模型,它能严格按“先输出测试文件、等我确认、再输出实现”的流程走。有些模型会偷懒,直接把测试和实现一起吐出来,虽然结果看着对,但破坏了 TDD 的节奏。这时候技能里就得加更硬的约束,比如明确写“在未收到测试通过确认前,禁止输出任何实现代码”。
所以技能设计的一个隐藏价值是:它把模型差异给暴露出来了。你写一个技能,在不同模型上跑,谁听话谁不听话一目了然。这比笼统地说“某模型代码能力强”要精确得多。
2.3 skills CLI 的定位:包管理器思维做技能分发
skills CLI这个工具的设计思路,我觉得最值得说的是它借鉴了包管理器的交互模型。你可以用类似skills install、skills list、skills remove这样的命令来管理技能,技能本身可以放在本地目录,也可以从远程仓库拉取。
这个设计选择很聪明。因为技能这东西天然具有可共享性——你团队里一个人写好了“符合公司代码规范的 PR 审查技能”,其他人直接装就行了,不需要每个人重新写一遍。而且技能可以版本化,今天规范改了,更新技能版本,所有人同步升级。
我自己的做法是在项目根目录建一个.skills文件夹,把项目相关的技能都放进去,然后通过 CLI 挂载。这样技能定义跟着代码仓库走,新人 clone 下来就能用,不需要额外配置。对于跨项目的通用技能,比如“写清晰的 commit message”,我放在全局目录,所有项目共享。
提示:技能目录的命名和结构最好一开始就规划好。我见过有人把所有技能平铺在一个文件夹里,几十个文件混在一起,找起来很痛苦。建议按领域分子目录,比如
testing/、review/、refactor/,CLI 加载时递归扫描即可。
3. 核心细节解析与实操要点:一个技能到底长什么样
3.1 技能文件的结构与关键字段
一个典型的技能定义文件,我用得最多的是 Markdown 加 YAML frontmatter 的格式。为什么选这个格式?因为 Markdown 对模型友好,frontmatter 里的结构化字段方便 CLI 解析和索引。
--- name: pytest-tdd description: 为 Python 函数生成符合 TDD 流程的 pytest 测试 trigger: 当任务涉及为 Python 代码编写测试时 version: 1.2.0 tags: [python, testing, tdd] --- ## 工作流程 1. 阅读目标函数的签名和文档字符串 2. 识别所有输入参数的类型和边界条件 3. 先输出测试文件,覆盖正常路径、边界值、异常路径 4. 等待用户确认测试文件 5. 运行测试,确认失败(红) 6. 输出实现代码 7. 再次运行测试,确认通过(绿) 8. 如有必要,重构实现,保持测试通过 ## 约束 - 禁止在测试确认前输出实现代码 - 每个测试函数必须有清晰的命名,描述被测行为 - 使用 pytest 的 parametrize 处理多组输入 - 异常测试必须使用 pytest.raises ## 示例 输入:一个计算折扣的函数 输出:先给出 test_discount.py,包含正常折扣、零折扣、负价格异常等用例这里有几个字段值得展开说。trigger字段是技能被激活的关键,它决定了代理在什么情况下会加载这个技能。写得太宽泛,技能会被频繁误触发;写得太窄,该用的时候用不上。我的经验是,trigger 里要包含领域关键词加动作关键词,比如“Python”加“测试”,两个条件同时满足才触发。
version字段看似不起眼,但在团队协作里很重要。技能是会迭代的,今天你觉得测试要覆盖边界值,明天你发现还得覆盖并发场景,技能就得更新。有版本号,你才能追踪“这个产出是用哪个版本的技能生成的”。
3.2 触发机制的设计:让代理在对的时候做对的事
触发机制这块我踩过不少坑。最开始我把 trigger 写得很简单,比如“写测试”,结果代理在讨论测试策略的时候也把技能加载进来了,浪费上下文。后来我改成更精确的匹配规则。
一个实用的技巧是用否定条件排除误触发。比如一个“生成 API 文档”的技能,trigger 可以写成“当任务涉及为公开接口生成文档时,排除内部工具函数”。这样代理在给内部函数写注释时就不会加载这个技能。
另一个技巧是技能优先级。当多个技能同时匹配时,得有个顺序。我的做法是在 frontmatter 里加一个priority字段,数值越小优先级越高。比如“安全审查”技能的优先级要高于“代码风格”技能,因为安全问题比格式问题严重。
注意:触发条件不要依赖过于模糊的语义判断。我试过写“当代码质量需要提升时”这种 trigger,结果代理几乎每个任务都触发它。后来改成具体的“当检测到函数超过 50 行或圈复杂度超过 10 时”,触发就精准多了。
3.3 技能之间的组合与依赖
实际工作中,一个任务往往需要多个技能协同。比如“重构一个函数并补测试”这个任务,既需要重构技能,又需要测试技能。agent-skills支持技能组合,你可以在一个技能里声明依赖其他技能。
dependencies: - refactor-extract-method - pytest-tdd加载主技能时,依赖的技能会被自动加载。这个机制的好处是避免重复定义。比如“写测试”这个能力被多个技能依赖,你只需要定义一次,到处复用。
但组合也带来一个问题:技能冲突。两个技能可能给出矛盾的指令,比如一个说“优先使用列表推导”,另一个说“避免列表推导以保持可读性”。这时候代理会困惑。我的处理方式是,在技能定义里明确声明冲突解决策略,或者在组合时手动指定哪个技能优先。
4. 实操过程与核心环节实现:从零搭一套可用的技能系统
4.1 环境准备与 skills CLI 安装
假设你已经在用 Claude Code 或者类似的 AI 编码代理,接下来就是装skills CLI。这东西通常是个 Node.js 包或者 Python 包,我用的是 Node 版本,因为和前端工具链集成方便。
# 全局安装 skills CLI npm install -g @agent-skills/cli # 验证安装 skills --version # 初始化一个技能目录 skills init my-skills初始化之后,你会得到一个目录结构,里面有个skills.config.json或者类似的配置文件,用来指定技能扫描路径、默认加载策略等。
{ "skillPaths": ["./skills", "~/.agent-skills/global"], "autoLoad": true, "conflictStrategy": "priority" }autoLoad设为 true 时,代理会在每次任务开始时自动扫描匹配的技能。如果你希望手动控制加载时机,可以设为 false,然后在需要的时候用skills load <name>显式加载。
4.2 编写第一个技能:以 TDD 为例
我拿最常用的 TDD 技能来演示完整流程。假设我要让代理在写 Python 代码时严格遵循测试驱动开发。
第一步,创建技能文件skills/testing/pytest-tdd.md,内容就是前面展示的那个结构。第二步,在项目里配置触发规则。第三步,实际跑一个任务验证。
我拿一个真实需求测试:写一个函数,计算购物车的总价,支持折扣码和税费。
代理加载了 TDD 技能后的行为是这样的:它先阅读需求,然后输出测试文件test_cart.py,里面包含正常价格计算、折扣码应用、税费叠加、空购物车、负价格异常等用例。输出完测试后,它停下来等我确认。我检查了一遍测试,发现漏了“折扣码过期”的场景,就补了一句“加上过期折扣码的测试”。代理更新测试文件,然后我让它继续。它运行测试,确认全部失败(因为实现还不存在),然后输出实现代码,再次运行测试,确认通过。
整个过程比我平时手动写测试再写实现要快,而且测试覆盖的完整性明显更好,因为技能里明确要求了边界值和异常路径。
4.3 参数计算与选择:技能粒度的权衡
技能粒度是个需要仔细权衡的问题。太粗,一个技能管太多事,指令会变得臃肿,代理执行时容易顾此失彼;太细,技能数量爆炸,管理和组合成本上升。
我的经验法则是:一个技能对应一类可独立验证的工作流。比如“写单元测试”是一个技能,“运行测试并分析失败原因”是另一个技能,“根据失败原因修复实现”又是另一个。这三个技能可以组合成完整的 TDD 循环,但每个都能单独使用。
具体到参数层面,我通常控制单个技能的指令在300 到 800 字之间。少于 300 字,约束不够具体,代理容易自由发挥;超过 800 字,代理可能抓不住重点,而且加载成本高。如果某个领域知识确实很多,我会拆成“基础技能”加“进阶技能”,按需加载。
还有一个参数是示例的数量。每个技能里放 1 到 2 个示例就够了,示例太多会占用大量上下文,而且代理可能过度拟合示例而忽略规则本身。示例的作用是锚定格式和风格,不是穷举所有情况。
4.4 与 Claude Code 的集成实操
Claude Code 本身支持通过配置文件加载自定义指令。把agent-skills和它集成,有两种方式。
一种是通过 CLAUDE.md 文件引用技能。在项目根目录的 CLAUDE.md 里写一句“本项目使用 agent-skills,技能定义在 ./skills 目录,请根据任务类型自动加载匹配的技能”。然后配合 skills CLI 的 hook 机制,在代理启动时把技能索引注入上下文。
另一种是通过 MCP 协议。如果 skills CLI 提供了 MCP server,可以直接在 Claude Code 的配置里注册这个 server,代理就能通过工具调用的方式查询和加载技能。这种方式更动态,技能更新后不需要重启代理。
我用的是第一种方式,因为配置简单,而且技能文件本身就在项目里,版本控制方便。实测下来,代理能正确识别技能目录并按要求加载。
# 在项目根目录初始化 skills init # 添加一个技能 skills add ./skills/testing/pytest-tdd.md # 列出已加载的技能 skills list # 查看某个技能的详情 skills show pytest-tdd提示:如果你在 Ubuntu 或者 Mac 上配置,注意 skills CLI 的路径解析。我遇到过
~展开问题,全局技能目录最好用绝对路径配置,避免不同 shell 环境下解析不一致。
5. 常见问题与排查技巧实录
5.1 技能不触发或者误触发怎么办
这是最高频的问题。技能该加载的时候没加载,或者不该加载的时候加载了,都会影响体验。
排查思路分三步。第一步,检查 trigger 的匹配逻辑。把 trigger 字段单独拿出来,用几个典型任务描述测试一下,看匹配结果是否符合预期。如果 trigger 里用了正则或者关键词列表,确认没有拼写错误或者转义问题。
第二步,检查技能加载顺序。如果多个技能竞争,优先级低的可能被覆盖。用skills list --verbose查看当前激活的技能和它们的优先级。
第三步,检查代理的上下文状态。有时候技能已经加载了,但代理因为上下文太长而“忘记”了技能里的约束。这时候需要精简技能内容,或者把关键约束放在技能开头。
我整理了一个速查表,覆盖最常见的几类问题:
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 技能完全不触发 | trigger 关键词不匹配 | 用典型任务描述测试 trigger | 放宽或调整关键词 |
| 技能频繁误触发 | trigger 过于宽泛 | 检查是否缺少否定条件 | 增加领域限定词 |
| 技能加载但代理不遵守 | 指令优先级低或被覆盖 | 查看技能加载顺序 | 提高 priority 或精简指令 |
| 多个技能冲突 | 指令矛盾 | 对比冲突技能的规则 | 声明冲突解决策略 |
| 技能更新后不生效 | 缓存未刷新 | 检查 CLI 缓存目录 | 清除缓存重新加载 |
5.2 技能与项目规范的冲突处理
团队项目里经常出现这种情况:技能里写的规范是“函数不超过 30 行”,但项目里有个历史遗留文件全是 200 行的巨函数。代理加载技能后,可能会试图重构这些巨函数,但这不在当前任务范围内。
我的处理方式是在技能里加作用域限定。比如写“本技能适用于新增代码和主动重构任务,不适用于未涉及的遗留代码”。这样代理就知道边界在哪。
另一个技巧是用项目级配置覆盖技能默认值。比如技能默认要求测试覆盖率 80%,但某个项目因为历史原因只能做到 60%,就在项目配置里覆盖这个参数。agent-skills通常支持这种层级覆盖:全局技能定义默认行为,项目配置可以调整具体参数。
5.3 跨模型使用时的兼容性坑
前面提到技能是模型无关的,但实际用起来还是有坑。我遇到最多的是工具调用格式差异。有的模型用 JSON 格式调用工具,有的用 XML 标签,技能里如果写死了某种格式,换个模型就失效。
解决办法是在技能里用抽象描述而不是具体格式。比如不要写“用 JSON 格式输出”,而是写“以结构化格式输出,包含以下字段”。让代理根据当前运行环境选择合适的序列化方式。
还有一个坑是指令遵循的严格程度。有些模型对“禁止”类指令特别敏感,你说“禁止输出实现代码”,它就真的一个字都不输出,连测试文件都不给了。这时候得把指令写得更精确:“在测试文件确认前,只输出测试相关代码,不输出实现代码”。
5.4 技能版本管理与团队协作
技能是要迭代的,团队里多人维护时,版本管理就很重要。我的做法是技能文件跟代码仓库一起走 Git,每次修改技能都走正常的 PR 流程。技能文件里保留version字段,重大变更升主版本号,小调整升次版本号。
另外,我会在技能文件顶部加一个变更日志区块,记录每次修改的原因和影响。这样新人看到技能时,能理解为什么某条规则存在,而不是盲目遵守。
## 变更日志 - 1.2.0: 增加异常路径测试要求,因为线上出过空指针问题 - 1.1.0: 增加 parametrize 使用规范,统一测试风格 - 1.0.0: 初始版本,覆盖正常路径和边界值这个习惯看起来麻烦,但实际省事。有次一个同事想删掉某条“多余”的规则,看到变更日志里写着“因为线上事故加的”,立刻就明白了。
6. 技能系统的扩展玩法与个人经验
6.1 把技能当成团队知识沉淀的载体
用了一段时间后我发现,agent-skills最大的价值可能不在“让代理干活更快”,而在于它强迫你把隐性知识显性化。以前团队里老工程师的很多经验是口口相传的:“这个模块的测试要特别注意并发”、“那个接口的错误码有历史包袱不能改”。这些知识散落在人脑里,新人来了得慢慢踩坑才能学会。
现在我把这些经验写成技能。比如一个“支付模块修改技能”,里面明确列出:修改支付相关代码时必须检查幂等性、必须验证金额精度、必须考虑并发下单场景。代理加载这个技能后,会自动在相关任务里应用这些检查。新人用这个技能干活,相当于站在老工程师的肩膀上。
6.2 技能与测试驱动开发的深度结合
热词里test-driven-development出现频率很高,这跟我的实践方向一致。TDD 和 AI 编码代理其实是绝配,因为 TDD 的核心是“先定义期望行为,再实现”,而代理最擅长的就是根据明确定义的期望生成代码。
我现在的标准工作流是:需求进来,先让代理加载 TDD 技能,输出测试用例。我审查测试用例,确保覆盖了所有业务规则和边界条件。测试确认后,代理生成实现。实现通过测试后,我再让代理加载重构技能,优化代码结构。
这个流程的关键在于测试用例的质量。测试写得好,实现基本不会跑偏。而技能的作用就是保证测试用例的质量标准一致——不管今天是谁在用代理,不管用的是哪个模型,产出的测试都遵循同一套规范。
6.3 我踩过的最大的一个坑
说一个真实的教训。有段时间我给一个技能加了太多约束,大概有二十多条规则,涵盖命名、格式、注释、异常处理、日志、性能等各个方面。结果代理执行任务时变得非常僵硬,生成出来的代码虽然每条规则都符合,但整体读起来很别扭,像是被规则绑架了。
后来我做了减法,把技能拆成“核心约束”和“建议实践”两部分。核心约束是必须遵守的,比如“测试必须先于实现”;建议实践是推荐但不强制的,比如“优先使用列表推导”。代理在核心约束内有一定的自由度,产出反而更自然。
这个经验让我明白:技能的目的是引导而不是束缚。好的技能应该像一位有经验的导师在旁边提醒,而不是一本死板的规则手册。
6.4 后续可以尝试的方向
我现在在试验的一个方向是技能的自适应调整。根据代理在任务中的表现,动态调整技能的严格程度。比如代理连续几次都正确遵循了某条规则,就可以适当放宽约束,给它更多自主空间;如果某条规则频繁被违反,就加强约束或者增加示例。
另一个方向是技能的市场化共享。社区里已经有人在分享自己写的技能,比如“React 最佳实践技能”、“Go 错误处理技能”。如果形成生态,新人可以直接安装经过验证的技能包,不用从零开始写。这对整个 AI 编码代理的使用体验提升会很大。
不过我得说,技能这东西最终还是要自己写才最合手。别人的技能可以参考,但每个团队、每个项目的规范都不一样,拿来主义往往水土不服。我的建议是先从一两个最痛的点开始写,用起来,再慢慢扩展。别一上来就搞几十个技能,那样管理成本会压垮你。
最后分享一个我每天都在用的小技巧:把技能文件放在编辑器的一个固定标签页里。每次代理产出不符合预期时,我第一反应不是改提示词,而是去看技能文件里哪条规则没写清楚或者写得太模糊。改技能比改提示词更一劳永逸,因为改一次,以后所有同类任务都受益。这个习惯坚持了几个月,我的技能库越来越精炼,代理的产出也越来越稳定。