
不是泛泛聊“个人技能提升”而是最近几个月在AI Agent应用里反复刷屏的那个工程概念Agent Skills。第一次看到这个词时我以为又是一句正确的废话。直到自己在项目里被“模型什么都会、一进真实流程就稀碎”的问题反复折腾才意识到Skills是一套很具体的工程范式把某一类任务的做法打包成模型可以直接读取、理解和执行的“技能包”。现在主流Agent框架基本都跟进了这套格式Anthropic提出后OpenAI的Agents SDK等也陆续兼容。这篇文章我会从原理、目录结构、和MCP的关系、实战编写、问题排查一直聊到技能库的长期维护。内容偏实操适合正在用Claude Code、Agents SDK这类工具做落地项目的人也适合想把自己团队的内部流程沉淀给模型的同学。我会把这一两个月里实际写的、改的、跑崩过的细节都摊开讲。1. Agent Skills是什么先搞清楚它解决的是哪个问题1.1 会写代码的模型为什么一进项目就“变笨”一个很典型的场景你让模型直接写一段Python函数它写得很漂亮逻辑清晰、注释到位。但你说“按我们团队规范评审一下当前分支的代码”它就有点顶不住了——它会给你一堆正确的废话比如“建议增强错误处理”“注意代码可读性”。这话没错但等于没说。没有具体的检查项、没有工具调用、没有行号定位、没有严重级别分级、没有团队特有的红线规则。模型本身拥有海量的“陈述性知识”也就是知道很多概念和原理。但它缺少“程序性知识”——在你们项目里到底按什么步骤、什么标准、用什么手段完成一件事。这就是为什么同一个模型在通用问答里像个专家进了具体项目上下文就像刚入职还没拿到SOP的实习生。有个比喻很贴切一个高材生掌握了车床的全部原理但没看过厂里的作业指导书不知道安全规程也不知道老师傅总结的操作手法直接让他上车床能不出问题吗1.2 一个Skill就是一个“技能包”Agent Skills就是来补这块短板的。一个标准的Agent Skill通常是一个目录目录里有一个SKILL.md作为主文件再按需放一些辅助内容比如脚本、模板、参考资料。模型在接到任务时会先通过技能描述判断“这件事我该用哪个技能”然后读取对应的SKILL.md把它当成一份作业指导书来执行。它的核心是三个要素的组合指令完成这项任务的具体步骤和顺序告诉模型“先做什么、再做什么”。知识判断标准、背景信息、团队规范、红线规则。让模型知道“做到什么样算合格”。工具执行过程中可以调用的外部能力比如脚本、命令行、MCP工具等。这部分是可选的。注意Skills不是提示词模板的换皮。提示词是一次性的文本输入而Skill是一个可以被检索、复用、版本管理、跨项目备份的文件资产它有统一的目录格式、命名规范和加载机制。就像快捷键和自动化脚本的区别前者只是少打几个字后者是完整的工作方法。举几个常见例子代码评审技能、发布前检查技能、日志异常分析技能、安全扫描技能、招聘JD分析技能。每个技能本质上都在回答同一个问题这类任务在我们这里是怎么被做好的1.3 一次Skill调用的完整执行链路理解Skills的运行机制是写好它的前提。整个调用过程可以拆成四步第一步是描述匹配。模型拿到用户请求后会根据每个Skill的description字段判断是否要启用。这里的description必须写得像“触发条件说明书”而不是一句干巴巴的介绍。第二步是上下文加载。一旦匹配命中框架会把SKILL.md的正文内容注入到当前模型上下文里。它不一定会加载该Skill目录下的所有文件通常只加载主文件其他文件由模型按需读取。第三步是按指令推理。模型把SKILL.md里的内容当成自己的临时行为准则按照里面的步骤逐步执行。它可以在执行到某一步时决定调用脚本、读取参考资料也可以根据实际情况微调执行方式。第四步是产出与检查。模型按Skill里定义的输出格式交付结果通常还会自查一遍是否遗漏了必做项。关键区别就在这里模型其实不是“运行”这个Skill而是“阅读”它然后把它当作指令来理解和执行。所以写Skill更像是在写一份给聪明但较真的实习生看的SOP而不是在写程序。这也解释了后面很多排坑思路——技能不生效往往不是代码逻辑出错而是指令没有被理解和遵从。2. 动手之前Skill的存放位置、命名规范和元信息设计2.1 skills/目录放哪用户级、项目级和内置级在Claude Code这套体系里Skills目录的存放位置分三层用户级目录、项目级目录和插件内置目录。用户级目录一般在~/.claude/skills/放你个人跨项目复用的技能比如代码风格检查、日志分析这种到哪都用得上的。项目级目录在项目根目录下的.claude/skills/只服务于当前项目里面可以放一些和这个项目强相关的流程。插件内置目录则是框架或插件自带的技能一般不直接改动。这里面有个优先级问题同名技能存在多个层级时项目级会覆盖用户级。有一个非常实用的细节团队协作时把项目的技能库直接提交到Git仓库里每个成员clone下来就能用不用额外安装任何东西。这比“每个人各自装一套”要省心太多。目录命名规范上也有些讲究。官方社区比较认可的做法是目录名全部小写多个单词用短横线连接比如code-review、log-analyzer不要用空格。每个技能一个目录目录内核心文件是SKILL.md其他辅助文件按职责分别放到scripts/、references/、templates/、assets/这些子目录里。还有一个官方建议值得记住目录嵌套层级控制在3层以内。太深的目录结构会让模型搜索和定位文件的成本变高反而拖慢响应速度。2.2 SKILL.md怎么写模型才愿意“照着做”SKILL.md是整个技能的灵魂。它的结构通常分两部分YAML格式的frontmatter和Markdown正文。frontmatter里最关键的是name和description两个字段。name要唯一description则是触发匹配的核心。我踩过的一个大坑是把description写得像功能简介比如“code review skill”结果模型经常在需要评审时想不起来调用它。后来改成场景描述效果立刻不一样了--- name: code-review description: Use when the user asks to review code changes, check a pull request, analyze a git diff, or look for bugs and risky changes before merging. Covers static issues, complexity, security risks and test gaps. ---这里有个很重要的行为规律模型是靠“语义匹配”来决定是否加载技能所以description里要写入足够多的触发词、任务动词和场景名词。“review”“pull request”“diff”“bug”“risky”“before merging”这些词都是给模型看的信号。不过实际测试下来YAML frontmatter里的description并不是所有模型都能稳定读取。同一个技能有些模型会忽略frontmatter直接看正文开头。我自己的做法是frontmatter里写精简版description正文第一段再用自然语言重复一遍触发条件。双保险命中率明显提升。正文部分的写作原则是字数尽量少规则尽量硬。单个SKILL.md控制在5000字以内是官方建议实际上超过2000字模型执行时就开始丢三落四了。正文只需要写清楚这几件事这个技能要求模型扮演什么角色、必须按什么顺序做什么、哪些红线不能碰、最终输出长什么样。至于更长的检查表、完整规范文档放到references/里按需读取。举个例子正文可以这样组织# Code Review Skill 当用户要求评审代码变更、检查PR或分析diff时你必须使用本技能。 ## 硬性步骤不允许跳过 1. 先执行 git diff 和 git status判断本次变更范围。 2. 运行扫描脚本 scripts/review-lints.mjs 获取静态问题。 3. 读取 references/review-checklist.md 并逐项核对。 4. 汇总输出问题清单标注严重级别。 ## 红线 - 必须标注每个问题的文件:行号。 - 禁止在没有依据的情况下给出“代码优秀”类结论。 ## 输出格式 按“严重问题/高/中/低”四级分组每组用列表逐条输出。注意我用的都是命令式短句“必须”“不允许跳过”“禁止”。这些强硬词汇在指令遵循方面的效果远好于“建议”“可以尝试”。2.3 辅助文件别乱放脚本、模板、参考资料的边界很多人写第一个Skill时喜欢把所有东西都塞进SKILL.md里结果文件越来越长模型执行时干脆把后半段忘了。正确做法是给辅助内容分门别类。scripts/目录放可执行的脚本工具。比如代码评审技能里的扫描脚本输出格式要稳定、可解析最好每行都是“文件:行号:严重级别:问题描述”这种机器可读格式这样模型可以直接拿结果组装报告。references/目录放知识型的长文档。比如评审标准、安全规范、团队代码约定。这些内容不需要一上来就加载进上下文而是让模型在需要时按路径读取。路径引用要写相对路径避免全路径被模型错误理解。templates/目录放输出模板。比如周报技能、PR描述技能输出格式可以直接套模板减少模型自由发挥的空间。这里有个判断标准如果一段内容是用来“判断做得对不对”的放references如果是用来“执行扫描或转换”的放scripts如果是用来“规定输出长什么样”的放templates。这个划分一开始可能不太准但跑几次就能调整得很顺。3. Skills和MCP、上下文缓存怎么配合3.1 MCP管“动作”Skills管“流程”别再混成一张饼很多人第一次接触Skills时会问那MCP工具和Skills有什么区别MCP的核心是定义“工具”它解决的是模型如何调用外部动作的问题比如读文件、执行命令、查数据库、调用某个API。它是一堆可供模型调用的原子操作。而Skills解决的是另一层问题一项任务该按什么流程、什么标准去组织这些原子操作。我习惯用一个类比MCP是工具箱里的扳手、螺丝刀、千斤顶Skill则是一份换胎作业指导书。指导书里会写“先用千斤顶把车顶起来”“按对角线顺序松螺丝”“扭矩打到多少牛米”。没有工具车没法修没有指导书新手会拆坏车。实际应用里两者也经常配合。比如代码评审Skill里要跑git diff这可能是通过MCP暴露的Git工具来做的要执行扫描脚本则可能是通过shell工具来做的。Skill里直接写明“在步骤2调用MCP工具xxx执行git diff”模型就会去调。3.2 推荐的三层结构系统提示薄一点技能按需加载我自己实测下来比较稳的架构是三层分离第一层是系统级提示只写模型通用的行为准则比如“遇到不确定的需求先追问”“禁止编造数据”。这一层要薄始终占着上下文写太厚会挤占后面的空间。第二层是MCP工具层提供基础的原子能力比如git、filesystem、database工具。工具列表本身也是上下文所以要节制只挂那些项目里真正会用的工具。第三层是Skills层按任务类型组织流程和标准。这一层默认不加载等模型匹配到要用某个Skill时再把这个Skill的SKILL.md读进来。这套结构的好处是上下文里永远只有“系统行为准则工具列表当前任务命中的技能”不会出现“所有技能全部挤在上下文里”的情况。很多Agent应用的上下文溢出问题其实不是模型能力不够而是设计时往系统提示里塞了太多不该常驻的内容。3.3 按需加载与防上下文爆炸的几个原则既然上下文窗口有限Skills的设计就要遵守“按需”两个字。我总结了三条原则。第一条技能正文要薄。SKILL.md只做索引和操作指令大段知识放外部文件。100个技能如果每个正文都3000字即便不全部加载模型匹配和检索的成本也会上升。第二条描述匹配要精准。两个技能的description如果覆盖了同一个场景模型就会随机选一个这是典型的上下文污染。要让每个技能的触发域尽量独立减少重叠。第三条引用外部文件时不要全文嵌套。比如技能A的SKILL.md里想引用技能B的部分内容正确做法是写一句“当遇到xx情况时读取skills/B/SKILL.md的相关章节”而不是把B的内容复制进A。否则每次加载AB的核心内容也跟着进上下文多套几层就爆了。缓存策略上有个额外细节框架通常会对系统提示和技能正文做缓存所以保持这部分内容的稳定性不要每次动态拼装对降低延迟有直接帮助。频繁变动的部分放在后段比如用户消息这样前段的缓存命中率才高。4. 实战记录从零写一个代码评审Skill4.1 先拆需求一份代码评审该管哪些检查点我先说结论写Skill第一步不是写文件而是拆需求。你要先想清楚“这个技能的输入是什么、输出是什么、必须覆盖哪些点”。以代码评审为例我结合自己团队的实际痛点拆出了四类检查点。第一类是静态违规最直接的一类。有没有遗留的console.log、TODO、FIXME标记有没有调试器断点。这类问题不需要模型推理直接脚本扫描最准。第二类是复杂度信号。函数行数是不是太长函数参数是不是过多diff是不是大得离谱。这类问题模型可以判断但有个阈值问题比如超过80行的函数建议拆分超过500行的diff建议拆成多次评审。第三类是安全风险。有没有eval、new Function这类动态执行有没有硬编码的密钥和token有没有把敏感信息打到了日志里。这类问题严重级别通常比较高需要模型结合上下文确认。第四类是测试覆盖。新增的函数有没有对应测试修bug的改动有没有加回归用例。模型要能对应新旧代码判断“这是新增还是旧逻辑”所以需要读取diff上下文。这个列表不要求一开始就面面俱到先覆盖最常被review打回的点后面根据团队反馈再迭代。我甚至建议在第一版里就写明“本技能当前覆盖范围限于上述四类其余问题按通用代码评审原则补充”给模型留出发挥空间也给自己留出改进余地。4.2 编写SKILL.md和配套扫描脚本需求拆完之后我先写了SKILL.md。frontmatter部分--- name: code-review description: Use when the user asks to review code, check a pull request, analyze git diff or look for risky bugs before merging. Triggers on review, PR, diff, code quality, security check keywords. ---正文第一段用自然语言重复触发条件然后列硬性步骤# Code Review Skill 当用户要求评审代码变更、检查PR或分析diff时必须使用本技能。 ## 必做步骤 1. 执行 git diff 和 git status确认改动范围。 2. 调用脚本 node scripts/review-lints.mjs 改动目录 获得静态扫描结果。 3. 读取 references/review-checklist.md按检查表逐项核对。 4. 如涉及可疑安全风险结合diff上下文判断是否误报。 5. 按分组格式输出问题清单每个问题附 文件:行号 和建议。 ## 红线 - 禁止给出“整体代码不错”这类无依据的结论。 - 对拿不准的高优问题必须如实标注“需人工确认”不能编造原因。配套的扫描脚本我写了一个Node版本逻辑不复杂#!/usr/bin/env node // scripts/review-lints.mjs import { readdirSync, statSync, readFileSync } from node:fs; import { join, extname } from node:path; const root process.argv[2] || .; const MAX_FUNC_LINES 80; const CODE_EXT new Set([.js, .ts, .jsx, .tsx, .py, .go, .java]); const issues []; const files []; function walk(dir) { for (const entry of readdirSync(dir)) { if (entry node_modules || entry .git) continue; const full join(dir, entry); const stat statSync(full); if (stat.isDirectory()) walk(full); else if (CODE_EXT.has(extname(entry))) files.push(full); } } walk(root); for (const file of files) { const lines readFileSync(file, utf8).split(\n); let funcStart -1; lines.forEach((line, idx) { if (/console\.(log|debug)\(/.test(line)) { issues.push(${file}:${idx 1}: [低] 残留调试输出); } if (/TODO|FIXME|HACK/.test(line)) { issues.push(${file}:${idx 1}: [中] 待办标记 ${line.trim().slice(0, 50)}); } if (/\beval\(|\bnew Function\(/.test(line)) { issues.push(${file}:${idx 1}: [高] 动态代码执行); } if (/API_KEY|SECRET|PASSWORD\s*[:]/.test(line) !file.includes(.env)) { issues.push(${file}:${idx 1}: [严重] 疑似硬编码密钥); } if (/^\s*(async\s)?function\s*\(|^\s*[\w]\s*\([^)]*\)\s*\{/.test(line)) { funcStart idx; } if (funcStart 0 idx - funcStart MAX_FUNC_LINES) { issues.push(${file}:${funcStart 1}: [中] 函数可能超过 ${MAX_FUNC_LINES} 行); funcStart -1; } }); } if (issues.length 0) { console.log(未发现明显静态问题); process.exit(0); } else { console.log(issues.join(\n)); process.exit(1); }这个脚本定位是“辅助扫描器”它不是银弹。像函数行数统计的正则就比较粗糙只覆盖常见写法。我在references/review-checklist.md里写了一句“以上脚本仅为初筛最终评审结论由模型结合人工判断给出”避免模型把脚本输出当成最终结论。4.3 在Agent调试环境里跑通和调参Skill写完后的第一件事是在Agent会话里验证它是否能被正确加载和执行。我通常这样测试先启动Agent输入/skill命令确认code-review出现在可见技能列表里。然后输入一句真实任务“帮我看看当前分支相对于main的改动按code-review技能做一次评审。”模型会按顺序执行步骤读diff、跑脚本、读checklist、输出问题清单。我见过最典型的失败方式是模型跳过了脚本扫描直接凭“直觉”给结论。原因不是模型笨而是SKILL.md里步骤描述不够强硬。我在“必做步骤”里加了一句话“步骤2是强制扫描即使你凭积累能发现大多数问题也必须让脚本先跑一遍。”这个改动之后稳定多了。调参方面有两个点值得单独讲。第一个是脚本输出的格式。如果脚本输出是“文件:行号:级别:描述”模型基本上能直接转述成评审报告如果脚本输出是一大段带花纹的文本模型解析时就容易丢字段甚至开始猜测含义。所以脚本设计时要刻意面向机器可读而不是面向人类阅读。第二个是exit code。脚本发现问题时返回1无问题时返回0。模型对非零退出状态非常敏感会认真对待问题列表如果总返回0模型会认为扫描通过了就不再看输出内容。这一点我一开始完全没注意后来看日志才发现模型根本没读扫描结果。5. 排坑实录Skills日常运行最常见的七个问题5.1 Skill根本没被加载九成是description写得不好症状是模型收到任务后完全无视你辛辛苦苦写的Skill直接靠通用能力回答。这时候别急着怪模型先查description。我整理了一张排查顺序表排查项具体操作description触发词看用户请求里的核心词是否和description中的场景词有重叠目录位置确认Skill在项目级目录且名称拼写正确技能可见性输入/skill看技能是否在列表中正文首段确认正文开头也用自然语言重写了触发条件文件大小确认SKILL.md没有被撑到几万字节最常见的就是第一种和第四种。description写得太抽象模型无法把用户请求和技能场景对应起来或者description写对了但模型忽略frontmatter只读正文首段。所以我现在的标配是双写触发条件实测下来效果最稳。5.2 指令被“选择性忽略”把“建议”改成“必须”第二个高频问题模型加载了Skill也读了正文但执行到一半开始自由发挥。比如漏了某个必做步骤、跳过了红线规则。原因很直接你的Skill正文里用了太多弱指令词。“可以考虑”“建议做”“尽量保持”“你可以试试”——这些词在模型眼里都是可选项不执行也说得过去。解决办法是全部换成命令式和禁止式表达弱指令“建议运行扫描脚本检查问题。” 强指令“必须运行扫描脚本脚本执行失败时中止评审并报告原因。”弱指令“输出问题时尽量标一下文件位置。” 强指令“每个问题都必须标注文件:行号无法定位的问题单独标记为待确认。”另外还有个技巧给步骤加编号。模型对编号列表的遵循率明显高于自然段。步骤越短越固定越好长段落很容易在读完之后丢失细节。5.3 技能互相污染、递归引用和上下文爆炸当你积累了多个Skill它们之间就会开始打架。互相污染的典型表现是请求是“分析日志”但日志分析技能和安全扫描技能的description都包含“分析日志”关键词模型随机挑了一个结果分析思路完全不对。解决办法是划定清晰的触发边界。比如日志分析技能的description里强调“应用运行时日志、错误堆栈”安全扫描技能里强调“依赖、Token、敏感信息”。两边触发场景错开自然就不冲突了。递归引用则是另一类坑。技能A的正文里写了“遇到xx情况参考技能B”技能B里又写了“遇到yy情况参考技能A”。模型读取时会陷入无限递归或者反复跳转浪费大量上下文。我的规则是一个Skill引用其他Skill时必须给出明确的触发条件并限定最多跳转一次。上下文爆炸通常是外部文件引用失控。比如代码评审Skill里如果不加控制地读取整个代码库或者读取了超大checklist全文模型很容易丢失前文指令。正确做法是让模型先读目录、再按问题类型精读相关片段而不是一上来就吞整个文件。5.4 技能改坏了怎么办回归测试不能省Skill的最大特点是可以迭代但这也意味着你随时可能改坏一个原本好用的技能。我严重建议每个重要技能都配一组“黄金样本”。简单说就是准备几个固定的测试请求每个请求对应一个预期结果。改技能之前先跑一遍旧样本改完再跑一遍对比输出是否退化。黄金样本不需要多三到五个就够。比如代码评审技能我会准备干净的改动期望是无问题或少问题不能误报。含安全风险的改动期望必须识别出硬编码密钥并标记为严重。复杂大改动期望提示“diff过大建议分次评审”而不是硬着头皮逐个文件评。这组样本可以直接放进Skill目录下比如prompts/golden/。虽然花几个晚上才能建好但如果你们的技能库是要长期给团队用的这点投入非常值得。6. 技能库长期维护的个人经验6.1 把Skills当成代码库来管Skill文件本质上就是一份代码资产应该有版本管理、变更记录和评审机制。我现在维护技能库的目录结构大概是这样skills/ code-review/ SKILL.md scripts/review-lints.mjs references/review-checklist.md prompts/golden/01_clean_diff.md log-analyzer/ SKILL.md scripts/parse-log.mjs每个技能的变更都走git提交commit message写明改了什么、为什么改。这个习惯的最大价值不是追溯而是逼你在改动前想清楚原因。很多技能改差的场景都是“顺手调整一下”导致的行为漂移。命名上技能名就是目录名也是profile里的name三者保持一致。技能名一旦发给同事用过就别轻易改。改名意味着所有依赖它的历史会话、笔记、文档全部失效。我吃过一次这个亏所以宁可在初期多花点时间想名字。6.2 用“黄金样本”给每个Skill做体检黄金样本不只是在改技能时用日常维护时也可以定期“体检”。我的做法是每个月抽一天把技能库里所有技能都跑一遍黄金样本记录两个指标一是是否出现明显行为退化二是调用成本是否有异常上涨。调用成本可以通过生成token数来观察如果同一个任务以前需要2000个token现在涨到5000多半是技能里塞了不该有的内容或者引用了过大的参考文件。还有一个指标是“人工返工率”。技能输出之后如果你每次都要花时间修改结果、补漏、纠正方向说明这个技能的输出标准有问题。这时候不是继续调description而是回到SKILL.md正文看输出格式和检查项是否足够具体。我见过最好的一个团队实践是每个技能页面顶部都写一段“上次更新/变更原因/已知限制”。这个看似很小的记录在几个月后回头看时非常有价值能帮你快速判断“这个技能为什么变成这样”。6.3 三个容易被忽略的实际习惯第一个习惯限制技能目录的执行权限。在SKILL.md的metadata里如果有底座支持可以把allowed-tools限制为只允许bash、read等少数工具。这能防止模型在会话过程中“灵机一动”去调用无关工具把表情跑歪。第二个习惯改动前先复制备份或者用一个分支。模型在复杂会话里有时会“帮忙”改技能文件如果技能库没有版本管理坏了一个文件冷却一整周。第三个习惯定期清理过期技能。我维护的库里曾有一个“weekly-report”技能团队流程整顿后已经没人用了但它还留在技能列表里导致模型偶尔在用户请求“写个总结”时加载这个老技能输出早已过时的模板。后来我养成了季度清理的习惯把超过三个月没被调用过的技能移到archive目录而不是直接删等确认没人需要再清掉。最后分享一个我个人的小习惯每次新建技能前我会先问自己一句“这个任务我一个月会做几次”。如果答案是不超过三次就先不建技能用普通提示词顶一顶等这个任务真的开始占用我的时间了再花半小时把它写成一个正式Skill。技能库的价值在于少而精不在多而全。