1. 在WorkBuddy里写技能到底在写什么很多人第一次接触WorkBuddy社区时看到别人分享的Skill包第一反应是这不就是个文件夹加一篇说明文档吗。这个判断对了一半。Skill在WorkBuddy里确实表现为一个目录、几个脚本和一份Markdown说明书但真正决定它能不能被Agent正确调用、能不能被社区其他人装走就用靠的是那份说明书里的声明式格式——SKILL.md。你写的不是代码是一份让Agent理解什么时候该用你、怎么用你、用了之后输出什么的契约。我在社区维护过十几个Skill从简单的文件批量重命名到需要调第三方接口的查询类技能踩了不少坑。最大的体会是WorkBuddy的Agent本身已经具备很强的对话和推理能力它缺的不是能力而是触发条件和调用方式的精确描述。你把描述写得含糊Agent就会在用户问A事的时候错误调用你的B技能你把参数格式写得随意Agent传进来的数据就乱七八糟。所以社区里流传一句话Skill写得好不好一半看代码一半看说明书。这篇教程不打算讲太深的前端或算法而是沿着我实际开发Skill的完整流程走一遍先理解Skill在WorkBuddy里的定位然后拆解SKILL.md的文件结构再以批量重命名文件这个最经典的入门场景为例手把手写一个能跑的Skill最后聊调试、发布和维护。适合刚接触WorkBuddy社区、想把自己手上的小工具或者脚本打包成Skill分享出去的开发者也适合已经写过一两个Skill但总觉得Agent调用不够精准的人。2. Skill包的结构和SKILL.md格式规则2.1 一个标准的Skill包目录长什么样WorkBuddy社区目前对Skill包的结构约定比较统一遵循一个目录一个技能的原则。我习惯这样组织rename-files/ ├── SKILL.md ├── scripts/ │ ├── rename.py │ └── utils.py ├── assets/ │ └── example.png └── requirements.txt这里每个文件都有用处SKILL.md是Agent唯一会主动读取的入口文档它决定了Agent在什么场景下想起这个技能**scripts/**放实际执行的脚本Agent最终会调用这些脚本**assets/**放示例图片或模板文件社区详情页展示用requirements.txt声明Python依赖社区安装器会自动根据它配置环境。我见过有人把几百行代码全塞进SKILL.md的代码块里或者反过来把SKILL.md写得像产品PRD这两种极端都不对。SKILL.md的核心任务是描述意图和接口不是承载实现。Agent读取SKILL.md之后会把脚本路径和参数组装成系统命令来执行所以脚本必须是可被命令行调用的形态。2.2 SKILL.md的YAML头部元信息决定分发每个SKILL.md文件开头都有一个YAML格式的元信息块这是社区识别的关键。我贴一份当前推荐的最小示例--- name: rename-files description: 批量重命名文件。当用户需要按照规则批量修改文件名、为文件添加前缀或后缀、将文件名统一为某种格式时使用。 version: 1.0.0 author: community license: MIT ---几个容易忽略的点description是Agent判断是否调用Skill的第一依据。它要回答三个问题技能操作对象是什么文件、动作是什么重命名、典型场景是什么批量加前缀/后缀/统一格式。描述里不要出现这是一个非常有用的工具这类废话也不要只写重命名文件四个字因为Agent做意图匹配时是拿用户原话跟description做语义对比的。我用过一个反面例子某开发者把description写成for renaming stuff结果Agent在用户问修改文档标题时也能匹配上这个技能完全跑偏。name字段要稳。它会被用作调用时的标识社区里Skill的name规则是纯小写加连字符不要用下划线更不要带版本号。我一开始给技能起名file_rename_v2结果社区安装器解析时就报了格式警告因为连字符才是社区解析器认可的单词分隔符。version建议遵循语义化版本规则。社区详情页会把版本号展示出来维护者更新包时version是唯一让社区判断有新版的依据。我踩过的一个坑是更新脚本后忘了改version导致用户装到的还是旧缓存包。从那时候起我把更新代码后第一件事改version写进了自己的检查清单。2.3 正文里的三块关键内容变量、运行方式和注意事项YAML头部之下是Markdown正文。社区约定俗成推荐包含三个小节虽然不强校验但对Agent能否正确调用影响很大。第一块是输入变量Input Variables。WorkBuddy的Skill支持在SKILL.md里声明输入参数Agent会像填表单一样把从用户对话里抽取到的信息填进来。示例## Input Variables - directory: 要重命名的目标目录路径必填 - prefix: 要添加到文件名前的内容可选默认空 - suffix: 要添加到文件名后的内容可选默认空 - dry_run: 是否只输出预览结果不实际执行可选默认 false这里有一个重要的技巧每个变量都要写清楚是必填还是可选以及默认值。Agent做参数抽取时如果没有默认值概念遇到用户没提的参数就容易留空或者编造一个值。比如用户只说把下载目录里所有图片加个前缀project_那么suffix变量Agent会留空如果你的脚本没有默认值处理就直接崩了。把可选默认空写明白Agent就会在调用命令时忽略这个参数。第二块是运行方式Execution。你要告诉Agent脚本用什么解释器跑、参数怎么传。社区一般推荐写成一段简短的代码块python scripts/rename.py --directory $directory [--prefix $prefix] [--suffix $suffix] [--dry-run]这段描述看起来只是重复一下命令行但它其实是在给Agent规定动作。Agent在真实场景里会根据这段代码决定调用命令的长相。如果有人在这里写成一个含糊的run the script with the directoryAgent可能就不知道怎么组合参数。第三块是注意事项Important Notes。这里写给Agent看也写给使用Skill的人看。比如脚本只处理文件、不会递归子目录目标目录不存在时会先创建文件名冲突时自动添加序号避免覆盖这些边界行为写得越清楚Agent越不容易在用户提出奇怪需求时瞎猜。3. 手写一个批量重命名文件Skill从函数设计到Agent调用3.1 脚本设计先定义边界再写功能写Skill的脚本和普通脚本有个微妙区别普通脚本的默认假设是人在终端使用Skill脚本的默认假设是一个看不见的Agent在替你调用。所以参数处理要极其宽容容错要做足。我以rename脚本为例先列设计要点所有参数都支持命令行传参同时支持环境变量兜底目录不存在时自动创建而不是抛异常文件名冲突时自动加序号绝不覆盖已有文件提供dry-run模式先预览再执行每次执行打印结构化结果供Agent判断成功与否第一版脚本我直接用了os.rename代码很短但很快在社区反馈中发现一个典型问题用户让Agent把某目录下所有IMG_001.jpg改名为vacation_001.jpgAgent抽取参数时没问目录结果脚本抛了目录不存在。用户的预期是帮我整理相册但Agent拿到的目录是对话里从未出现的字段。这个问题的解决方案不是让脚本更聪明而是在SKILL.md的Input Variables里把directory写成必填并加一句如果用户没有提供目录询问用户具体路径不要猜测。这就是Skill开发的常态你一半的时间在写业务逻辑一半时间在写引导Agent正确提问的约束说明。下面是rename.py的完整实现我保留了核心功能去掉了一些平台相关装饰#!/usr/bin/env python3 import argparse import os import sys from pathlib import Path def safe_rename(src: Path, dst: Path) - Path: if not dst.exists(): dst dst else: stem dst.stem suffix dst.suffix counter 1 while dst.exists(): dst dst.with_name(f{stem}_{counter}{suffix}) counter 1 os.rename(src, dst) return dst def batch_rename(directory: str, prefix: str, suffix: str, dry_run: bool): folder Path(directory).expanduser().resolve() if not folder.exists(): folder.mkdir(parentsTrue, exist_okTrue) renamed [] for item in sorted(folder.iterdir()): if not item.is_file(): continue new_name f{prefix or }{item.stem}{suffix or }{item.suffix} new_path item.with_name(new_name) if new_path item: continue if dry_run: renamed.append((item.name, new_path.name, 预览)) else: final_path safe_rename(item, new_path) renamed.append((item.name, final_path.name, 完成)) return renamed def main(): parser argparse.ArgumentParser(descriptionBatch rename files in a directory) parser.add_argument(--directory, requiredTrue, helptarget directory path) parser.add_argument(--prefix, default, helpprefix to add) parser.add_argument(--suffix, default, helpsuffix to add before extension) parser.add_argument(--dry-run, actionstore_true, helppreview only) args parser.parse_args() results batch_rename(args.directory, args.prefix, args.suffix, args.dry_run) for old, new, status in results: print(f{status}: {old} - {new}) if not results: print(没有可重命名的文件只处理文件不递归子目录) sys.exit(0) if __name__ __main__: main()这段代码故意做得非常保守。resolve()和expanduser()处理~路径和相对路径Agent传参时经常带着~或者相对路径不处理就是各种文件找不到folder.mkdir(parentsTrue, exist_okTrue)让脚本面对不存在的目录时自动创建这是考虑到Agent可能在目录尚未建立时就调用技能。safe_rename里的序号冲突处理保证了脚本在文件重名时不会意外覆盖这在批量场景下几乎必然会遇到。3.2 写SKILL.md让Agent在合适的时机想起它脚本写好后动手写SKILL.md。前面YAML头部已经展示过这里重点说正文怎么组织。我完整的SKILL.md正文大概是这样的## Input Variables - directory: 要重命名的目标目录路径必填。如果用户未提供请先询问用户不要假设。 - prefix: 要添加到文件名前的内容可选默认为空。 - suffix: 要添加到文件名后、扩展名之前的内容可选默认为空。 - dry_run: 是否只预览不执行可选默认为 false。 ## Execution 使用以下命令执行 bash python scripts/rename.py --directory $directory [--prefix $prefix] [--suffix $suffix] [--dry-run]如果用户没有提供directory请向用户提问获取路径后再执行不要使用空字符串或当前目录代替。Important Notes脚本只重命名目录下的直接文件不递归处理子目录。目标目录不存在时会自动创建。当目标文件名已存在时会自动添加数字序号如report_1.txt不会覆盖原有文件。默认情况下脚本会真实执行重命名如需预览请使用dry_runtrue。命令输出格式为状态: 原文件名 - 新文件名其中状态可能是 完成 或 预览。注意我把如果用户没有提供directory请询问用户同时写在了变量说明和执行方式两处。这不是重复而是为了增强约束力。Agent读取上下文时有时候只读了变量定义没看后面的执行说明两处都写可以降低它乱猜的概率。 ### 3.3 为什么这个Skill适合做第一个练手项目 批量重命名文件几乎是所有Skill开发者的第一个练习因为它足够小、依赖少、需求场景清晰。完成它之后你会对Skill开发有个完整的体感脚本怎么写、SKILL.md怎么定义变量、Agent在什么场景下会调用、发布后别人怎么用。而且重命名这个动作天然适合交给Agent——用户在对话里描述规则Agent解析成参数脚本执行整个过程用户不需要打开终端也不需要手写命令。 更进一步这个Skill可以很自然地扩展出多个变体加一个--recursive支持递归子目录、加一个--pattern支持用正则匹配过滤文件、加一个--replace支持替换文件名中的特定关键词。每加一个参数你就多一次练习如何把参数语义写清楚的机会这些经验会反哺到你后面写复杂Skill的过程中。 ## 4. 调试和验证把Skill当成一个函数来测 ### 4.1 本地直接跑脚本最朴素的调试法 Skill本质是脚本描述文档所以最早期的调试完全不需要启动任何平台界面。我习惯先在本地把脚本当普通CLI工具测 bash mkdir -p /tmp/rename_test touch /tmp/rename_test/photo_{1,2,3}.jpg python scripts/rename.py --directory /tmp/rename_test --prefix holiday_ --suffix _2025这一步能验证脚本基本逻辑有没有错。参数传错、路径解析错误、文件冲突处理都在这里暴露。我把这套做法称为函数式调试输入一组已知数据断言输出结果和写单元测试的思路一样。本地测试通过后才是真正验证Skill能不能被Agent正确调用的环节。WorkBuddy社区提供了本地调试模式你不需要上传Skill包而是把本地目录挂载到运行环境中。启动时它会读取你当前的SKILL.md并渲染到对话上下文里然后你直接用自然语言提需求看Agent是否调用了技能、传的参数对不对。我在这个阶段最常发现的问题是描述写得太窄或者太宽。有一次我把一个查天气的Skill的description写成获取天气信息结果用户问今天需要带伞吗Agent调用了天气技能但传参时把带伞解析成了地点参数输出自然莫名其妙。后来我把description改成了获取指定城市当前天气和未来24小时降水概率。当用户询问天气、降雨、带伞、出行建议时使用Agent的匹配精度立刻上来了。4.2 关键测试用例覆盖Agent的九种奇怪习惯我总结了一份针对Skill的Agent习惯测试清单每个Skill在发布前我都会照着过一遍测试场景典型用户话术验证点完整参数把下载文件夹里的文件全部加上前缀project_参数抽取正确、脚本执行成功缺失必填参数帮我重命名一下文件Agent是否主动询问目录路径带波浪号处理~/Pictures目录expanduser是否生效相对路径重命名当前目录的文件resolve是否解析到绝对路径空目录指定一个空目录输出是否友好提示文件名冲突目标名已存在是否自动加序号预览模式下执行加了dry-run参数是否真的不修改文件多个同扩展名文件批量处理大量文件是否全部处理、顺序是否稳定目录不存在指定一个不存在的路径是否自动创建并静默执行第2个场景是最容易翻车的。很多Skill的SKILL.md里把参数写了默认空Agent就不会追问直接用一个空字符串调用脚本脚本执行后要么报错要么没有效果。我在重命名Skill的说明里专门加了一句如果用户未提供目录请先询问用户不要猜测Agent就基本不会再犯这种错了。写Skill的本质是你在教一个什么都不懂但非常听话的实习生怎么干活规则一旦没写清楚他就会一本正经地做错。4.3 调试日志脚本要把过程说出来调试Skill时另一个常用技巧是让脚本话多。普通CLI工具用户不介意你只在出错时打印一点信息但Agent需要通过标准输出来判断脚本执行情况。我在rename.py里故意每处理一个文件都打印一条记录即使只是dry-run模式也把预览: a.jpg - b.jpg逐条打出来。这样Agent在返回给用户时可以原样复述这些信息用户会感觉Agent真的看到了执行过程。反过来如果脚本只打印一个成功Agent回复用户时就只能干巴巴地说已完成用户如果再问具体改了什么名字Agent就完全无从回答。这也是社区里好Skill和普通Skill的一个隐性分水岭脚本输出是否结构清晰、信息完整直接决定了Agent的解释能力。5. 发布、安装机制和版本维护的一些细节5.1 打包检查发布前的最后一次自检Skill在WorkBuddy社区有两种分发形式一种是提交到社区仓库另一种是分享压缩包。无论哪种发布前都应该过一遍自检清单。我自己是这么做的检查目录结构是否完整有没有遗留的临时文件.DS_Store、__pycache__这些一定要清掉确认SKILL.md里的version已经比上一版递增在干净环境里重新安装一遍Skill跑一次端到端测试确认没有依赖缺失检查requirements.txt是不是最小依赖集避免把无关包写进去确认脚本权限是可执行的至少确保python能直接运行它第3步最容易出问题。我遇到过的情况是本地开发环境里装了某个包脚本运行正常但requirements.txt里漏掉了它用户装完Skill一跑就报ModuleNotFoundError。解决方案是每次发布前都新建一个干净虚拟环境安装依赖并跑通全部测试这个过程虽然多花五分钟但能避免社区里一堆装不上跑不了的反馈。5.2 社区安装器做了什么理解背后的机制社区平台的安装器读取Skill包时核心动作是解压文件、读取SKILL.md的YAML头部、把目录放到技能目录下、根据requirements.txt安装依赖。整个流程对用户是黑盒但对Skill作者来说理解它有助于规避问题。一个典型坑是文件夹名字和name字段不一致。社区安装器解压后以目录名为准建立技能目录但SKILL.md里的name字段用于Agent调用时的逻辑标识。如果两者不一致会出现包装上了但Agent找不到技能的诡异情况。另一个坑是requirements.txt里固定了过高的依赖版本和用户环境里已有包冲突导致Agent启动时报错。我现在的习惯是限制一个版本下限而不是上限比如requests2.25除非确有必要才用精确锁定。5.3 持续维护Skill不是写完就结束的Skill发布之后维护压力主要来自两头一是用户的真实使用反馈二是Agent平台本身对格式要求的变化。社区里的Skill格式已经迭代过好几版早期一些依赖旧字段的包现在已经检索不到。我的维护策略是给每个Skill建一个简单的小文档记录每个版本改了哪些内容、为什么改。这样下次收到用户反馈某个功能不好用的时候可以快速定位是脚本逻辑的问题还是描述说明的偏差。有一个经历让我印象很深某查询类Skill上线后一直表现稳定后来社区更新了一次Agent的语义匹配模型突然有用户在反馈区说明明问的是一个完全不相关的问题Agent却自动带了天气参数。排查后发现是description里某个词和天气模块的description有语义重叠改了一下描述措辞就恢复了。这种问题不写版本日志很难定位因为你不会记得两个月前那个description是怎么写的。6. 写Skill最容易翻车的四个细节我的经验笔记6.1 参数语义模糊AI会一本正经地猜错Skill参数不是给机器填的键值对而是给AI理解的自然语言描述。我见过很多新写的Skill把变量写成input、data、arg1这种名字SKILL.md里也只有一个孤零零的变量名没有任何解释。这种技能Agent调用时基本靠猜猜错是常态猜对是运气。正确做法是把变量命名得像自然语言里的槽位。比如你要设计一个查天气的技能变量不叫city叫city_name描述写清楚城市名如北京、上海、广州等具体地名用户可能使用我所在的城市此时需要结合对话上下文判断或澄清。这样Agent在抽取参数时才能从带伞吗这类模糊表达里提取出有效信息。6.2 脚本的退出码和错误处理用户反馈过一类问题技能执行时报错但Agent回复已完成。查下去根因是脚本里某个子流程发生异常但被一个宽泛的try-except吞掉了脚本照样exit(0)。这个教训让我此后非常重视退出码和异常信息的传递。WorkBuddy的Agent在脚本执行结束后会读取标准输出和退出码判断是否成功。如果脚本打印了明显的ERROR: xxx字样Agent通常能识别为失败并如实告知用户但如果脚本只是静默失败Agent大概率会把命令执行完成当作任务成功。所以现在我在所有Skill脚本里约定了一条规则任何捕获到的异常必须打印到标准错误并采用非零退出码绝不吞掉异常。6.3 参数里带空格和特殊字符的传递问题这是命令行类Skill的经典翻车点。Agent把用户话术里的目录路径直接塞给命令时如果路径里有空格比如Windows的C:\Users\My Documents裸传参数就会把路径拆成两段。我在rename脚本里之所以用--directory加号的风格就是为了规避一部分空格问题但对于Shell来说正确做法还是包装一层引号。社区的处理惯例是在Execution描述里明确写出命令模板并用引号包裹参数例如--directory $directory。同时脚本内部也应该对路径做一次标准化处理。经验是永远不要假设Agent传入的参数是干净的它在对话里拿到的原始值可能有空格、有中文、有括号、有~你的脚本必须全部容忍。6.4 演示文件和图片的尺寸社区详情页会给每个Skill渲染一组展示图片。很多人上传一张巨大无比的工作区截屏结果详情页加载很慢甚至图片边缘被裁掉。我踩过一次这个坑之后专门用一个脚本把assets里的示例图统一压缩到1200px宽、体积控制在500KB以内。这个细节看似和写Skill无关但真实影响体验一次我分享的图片加载超时好几个用户直接放弃了安装转去了另一个功能类似的包。7. 从能用到好用几个进阶思路如果一个Skill在社区里收获了一些关注值得花时间做一轮体验优化。我推荐三个方向第一个方向是丰富描述场景。参考社区热门的同类技能看它们的description怎么写的。如果一个场景自己没想到但用户反馈里反复出现就把它补进去。比如重命名Skill最初只写了批量重命名、加前缀后缀后来有用户拿来整理下载文件夹我就在description里加了整理下载目录、批量归类文件这些具体场景词之后这类请求的命中率明显上升。第二个方向是增加启发式行为。比如脚本在处理完文件后可以顺手生成一份变更清单写到目录下的.rename_log.txt这样用户事后能追溯。Agent读到这个行为后在回复里也会主动提及已生成变更日志体验完整度会高很多。第三个方向是保持兼容性迭代。每次社区平台更新Skill格式我会第一时间用老包跑一遍新机制发现警告就顺手修掉。这类维护看起来没有新功能上线那么光鲜但它决定了你的Skill在社区里是持续可搜索还是逐渐沉底。写Skill这件事越往后越会发现技术实现的门槛真的不高难的是让一个看不见推理细节的Agent准确理解你的设计意图。每一次发布后的反馈都是你重新审视契约写得好不好的机会。熟练之后定描述、写脚本、测场景这些流程大概半小时就能走完但这个契约思维的打磨是一个Skill作者在社区里真正积累下来的东西。