资讯动态

OpenClaw 3.13 Skill编写实战:Docker环境自定义Agent技能

发布时间:2026/10/16 3:51:59 来源:尧图企业网站定制
做Agent开发这段时间我一直在折腾OpenClaw这个框架前几天终于把3.13版本在Docker环境里跑起来了顺手研究了它的Skill编写机制。说实话这个版本把Skill的抽象做得比之前清晰了不少但官方文档写得比较简略对我这种习惯边看边试的人来说光是搞清楚一个Skill到底由哪些文件组成、注册流程走哪条链路就花了不少时间。这篇文章就把我初探OpenClaw 3.13 Skill编写的过程完整记录下来重点围绕Docker部署环境下如何使用自定义Skill里面包含了我实际验证过的代码、目录结构和排坑记录希望对正在研究或准备上手OpenClaw的人有点帮助。先说明一下这篇文章适合谁看如果你已经对Agent框架有一定了解想快速在本地或服务器上通过Docker搭建OpenClaw环境并且需要扩展自定义能力那这篇内容基本就是照着走一遍的事。如果你是完全零基础也没关系我会把从环境准备到Skill编写再到调试的每一步都拆开讲清楚。1. 先把Docker环境跑起来从零到能开箱即用OpenClaw 3.13的部署方式有好几种官方推荐的是直接用Docker这也是我这次选择的方式。Docker部署最大的好处是环境隔离依赖不会污染宿主机而且版本切换非常方便想回滚就换镜像标签重跑一次比在裸机上一通操作稳妥得多。1.1 基础镜像选择与启动我第一次跑的时候其实卡在镜像选择上。OpenClaw 3.13的官方镜像有几种不同变体有纯运行时的有带调试工具的。我最终用的是稳定版加调试组件的组合因为初学阶段需要频繁查看容器内日志和状态带调试组件的镜像能省不少事。镜像拉取命令很简单但有几个细节要注意如果你在国内网络环境建议先配置好镜像加速源不然这个镜像拉取过程会比较煎熬我实测下来不配置加速源可能要等好几分钟。拉取完成之后启动容器时需要预留好两个关键目录的挂载一个是数据持久化目录一个是后续要放Skill的配置目录。具体启动参数我放在后面结合完整案例一起说明因为不同使用场景对端口的映射要求不太一样。1.2 与宿主机目录的挂载策略这里重点说一下挂载策略。OpenClaw容器内部有一套固定的目录结构但Skill文件的存放路径在不同版本之间有过调整3.13版本默认的Skill目录是在一个工作目录下层的skills文件夹里。我这里采用的策略是把整个工作目录挂载出来这样做有一个好处之后无论是写Skill还是调整配置都可以直接在宿主机上操作不用反复进容器改文件、再重启容器。挂载还有一层考虑是权限问题。容器内部运行的用户和宿主机用户不是同一个UID如果挂载目录权限设置不当经常会出现容器内无法写入文件的情况。我在调通一次之后索性把挂载目录的属主调整成了当前用户这样既不影响容器读取也能保证宿主机上的编辑操作顺手无阻。1.3 确认版本与基础连通性容器跑起来之后第一件事就是确认版本号和基础服务是否正常。OpenClaw 3.13本身带有版本查询命令也有健康检查接口直接在宿主机上curl对应的端口就能看到响应结果。这一步能帮你快速判断环境是否真的通了而不是光看容器状态是Up就以为万事大吉。我在这个过程里踩过一个比较隐蔽的问题容器起来了健康检查的端口也能通但后续连Agent的时候一直报认证失败。查了一圈才发现是令牌配置项没有同步到环境变量里容器读取的是默认值而我在宿主机上配置的是自定义值。所以如果你也遇到类似“环境好像没问题但连接总失败”的情况优先检查配置文件和环境变量的对应关系。2. Skill到底是什么一个最小Skill的完整解剖在动手写Skill之前我觉得有必要先把Skill的本质讲清楚。OpenClaw里的Skill简单理解就是给Agent预置的一套“专业技能包”。它不是一个单纯的功能函数而是一组结构化的文件集合包含对这个能力如何调用、如何描述、如何执行的完整定义。框架通过这套定义在合适的时机自动加载并调用对应的逻辑。2.1 Skill的核心文件结构一个最简单但结构完整的Skill通常包含这几个文件我用一个叫“时间查询”的Skill举例time-check-skill/ ├── SKILL.md ├── scripts/ │ └── check_time.py ├── resources/ │ └── timezones.json └── reference/ └── notes.mdSKILL.md这个是Skill的“身份证”里面写明了这个技能的名字、描述、适用场景、参数定义、如何调用等元信息。Agent调用Skill之前首先读取的就是这个文件它决定了一个Skill能不能被正确识别和触发。scripts/这里面放的是真正的可执行脚本。框架在执行Skill时会根据SKILL.md里声明的调用方式来运行脚本并传递参数。resources/可选目录存放Skill运行过程中需要读取的静态资源比如时区表、字典、数据文件等。reference/可选目录存放给Agent参考的扩展文档Agent在做决策时可能会读取这里面的内容来增强对场景的理解。我第一次看到这个结构的时候觉得有点过于工程化但实际跑通之后才发现这样的分层恰恰是各种基础模型能够稳定调用外部能力的关键。没有这种标准化结构Agent就没法在运行时动态判断“该用哪个脚本”“脚本要接什么参数”。2.2 SKILL.md元信息的关键字段解析如果说整个Skill结构里最重要的是哪个文件那一定是SKILL.md。它的内容质量直接决定了Agent能不能准确理解并使用这个技能。我自己写的时候摸索出几个至关重要的字段name技能名要求简短且语义明确Agent判断是否使用这个技能时会参考它。description技能描述这里一定不能敷衍要写清楚“什么场景下用”“解决了什么问题”“限制条件是什么”。描述写得不到位Agent很容易在需要的时候想不到调用它。parameter参数定义明确这个技能需要接收哪些参数类型是什么是否必填。output输出格式说明告诉Agent执行完技能之后会拿到什么结构的数据。我举个实际对比。同样一个天气查询Skill描述如果只写“查询天气”Agent在多技能场景下可能根本不会调用它。但如果写成“查询某个城市未来几天的天气情况适用于出行前准备、户外活动规划等场景需要提供城市名称和日期”Agent就很容易在合适的场景中做出正确选择。所以SKILL.md写得好不好直接决定了你的Skill是“能用”还是“好用”。2.3 Skill的加载与触发链路OpenClaw 3.13加载Skill的流程大概是这样框架启动时扫描配置好的Skill目录逐个解析SKILL.md把技能元信息注册到技能表中当用户输入到来时Agent根据对话上下文和技能元信息做匹配选出最合适的Skill然后按声明的调用方式执行对应脚本拿到结果后再组织语言回复给用户。理解这个链路很重要因为很多问题都出在链路中的某个环节。比如“Skill明明写在目录里了但Agent就是不调用”多半是注册阶段出了问题最常见的原因就是SKILL.md格式没被正确解析。再比如“Skill被调用了但结果不对”那就要往脚本执行阶段排查了可能是参数传递格式不对也可能是脚本本身的逻辑有边界条件没处理好。3. 真正动手写一个时间查询Skill一步步拆开揉碎纸上谈兵没意思直接动手写一个能跑通的Skill才是正道。我这次选择“时间查询”作为第一个实操案例因为这个功能逻辑简单、边界清晰但它涵盖了编写Skill所需的所有重点环节包括参数定义、脚本编写、格式化输出、异常处理。一个简单的Skill跑通了后面复杂的自然水到渠成。3.1 设计这个Skill的完整思路我设计这个时间查询Skill时先明确它要解决什么问题用户想知道“某个时区的当前时间”。输入是时区名称可选如果没有指定就默认使用UTC。输出是当前时间、时区、日期以及一个简单的格式化时间字符串。为什么选这个案例因为时区转换涉及到一个关键参数时区标识的传递和校验又涉及格式化输出还涉及对错误时区的处理。一个Skill该有的要素它基本都覆盖了。3.2 编写SKILL.md到底怎么写才不会被Agent误解这一步我特别想多说几句。SKILL.md写得太简单Agent会误解写得太复杂Agent解析起来又容易出错。关键是在“简洁”和“充分”之间找平衡。我的SKILL.md最终长这样--- name: time_check description: 查询指定时区的当前时间和日期适用于跨时区沟通、会议安排、时间换算等场景。如果不指定时区默认返回 UTC 时间。 parameter: - name: timezone type: string required: false description: 时区标识遵循 IANA 时区数据库格式例如 Asia/Shanghai、America/New_York output: - name: current_time type: string description: 格式化的当地时间字符串 - name: timezone type: string description: 实际使用的时区标识 - name: utc_time type: string description: 对应的 UTC 时间 --- # 时间查询 Skill 当用户询问“现在几点”“某个时区的时间”时使用此技能。 执行方式运行 python3 scripts/check_time.py --timezone timezone有几个细节说明一下。一是description里我写了几个典型的应用场景这是为了让Agent在匹配时能有更丰富的依据。二是parameter这里我专门标注了时区格式要遵循IANA数据库这样Agent在从前端拿到用户输入时如果有缺失或不规范它会尝试补全或提示用户。三是“执行方式”里我写清的是具体命令脚本如果调不通Agent至少能在日志里看到是执行环境的问题。3.3 编写执行脚本这里的坑比你想的多脚本本身逻辑不复杂难点反而在于与框架的交互方式。OpenClaw 3.13执行Skill脚本时默认会把参数以命令行参数形式传入并期望脚本将结果以JSON格式打印到标准输出。框架会解析这个JSON再交给Agent组织语言。这是我最开始没弄明白的地方。我第一版脚本直接打印了一行字符串“当前时间是xxxx”结果Agent拿到后虽然能看懂但框架层面其实并没有把结构化数据传递回去导致后续逻辑无法引用具体的时间字段。后来我改成JSON输出问题就解决了。我用的脚本逻辑如下import argparse import json from datetime import datetime from zoneinfo import ZoneInfo def get_current_time(timezone: str) - dict: try: tz ZoneInfo(timezone) if timezone else ZoneInfo(UTC) except Exception: return {error: f无效的时区标识: {timezone}} now datetime.now(tz) return { current_time: now.strftime(%Y-%m-%d %H:%M:%S %Z), timezone: str(tz), utc_time: datetime.now(ZoneInfo(UTC)).strftime(%Y-%m-%d %H:%M:%S UTC) } if __name__ __main__: parser argparse.ArgumentParser() parser.add_argument(--timezone, defaultNone) args parser.parse_args() result get_current_time(args.timezone) print(json.dumps(result, ensure_asciiFalse))这里面有个非常关键的细节脚本返回值如果包含错误不要直接抛异常而是要返回一个包含error字段的JSON对象。这个设计是为了让Agent能理解发生了什么而不是执行到一半就中断。Agent拿到带error的返回结果后会尝试调整参数再调用或者直接告诉用户出错原因。3.4 Django风格的前后端分离Skill结构带来的思路启发这里插一段题外话但我觉得对理解Skill的价值有帮助。我写的时候注意到OpenClaw Skill的“SKILL.md scripts”结构有点像Web开发里前后端分离的思路。SKILL.md相当于前端给用户在这里是Agent看的“能力说明书”scripts相当于后端真正干活的逻辑。Agent充当了中间调度的角色它在两个层面之间做语义翻译把用户意图翻译成对某个“接口”的调用再把“接口”的返回翻译成用户能听懂的话。理解这个类比有什么好处呢它帮我建立了一种写Skill的正确姿势不要把逻辑都堆在SKILL.md里描述归描述实现归实现把参数设计和返回值设计当作定义API来做先想清楚这个“接口”有多少入参、每个入参什么类型、返回什么结构再动手写代码。按这个思路下来的Skill往往结构清晰、Agent调用成功率也高。4. 热更新、日志定位、权限控制调试Skill的现场记录Skill写好了接下来就是调试环节。说实话刚开始调Skill是我感觉最吃力的时候因为问题可能出现在任何一个环节框架没加载到我的Skill解析SKILL.md失败了脚本运行报错了参数传得不对输出格式不被认这些问题本身不难解决难的是快速定位到具体是哪一环出了问题。4.1 让Skill生效的两种方式与热加载实测在Docker环境下让新写的Skill生效有两种方式。第一种是重启容器简单粗暴但每次改代码都要重启毕竟麻烦。第二种是依赖OpenClaw 3.13提供的Skill热加载能力理论上只刷新配置就可以让新Skill生效。我在测试中发现热加载对新增的Skill文件生效比较及时但对已有的Skill修改偶尔会有缓存旧描述的情况。这种情况我建议直接在宿主机上改完文件后通过管理接口触发一次配置刷新不要干等自动加载。我实测下来刷新接口调用之后几秒钟内新Skill就能被识别到。4.2 排查问题的几个有用命令调试过程中我主要依赖两个手段容器日志和框架自带的调试接口。日志这块如果容器是前台启动的直接看输出就行如果是后台启动的要用日志命令去拉取。日志级别建议调试期直接调到最详细的档位这样能看到Agent决策时到底看了哪些Skill描述、为什么选了这个Skill、脚本执行结果是什么。框架自带的调试接口则能直接返回当前已注册的技能列表这个用来确认“我的Skill到底加载进来没有”特别管用。我一般排查流程是第一步调这个接口确认Skill在不在列表里如果不在检查目录挂载和SKILL.md格式如果在就去调一次技能执行接口看返回结果是否符合预期。4.3 Docker容器的权限与安全配置权限问题我在前面提到过一次但这值得展开说。容器里跑脚本和本地跑脚本有个很大的区别容器是有用户隔离的如果只以普通用户身份启动那容器内运行的进程可能没有权限写某些目录、读某些配置。好在OpenClaw对这块的处理已经比较成熟常见操作基本不会遇到权限卡点。真正要留心的是另一件事不要让Skill脚本拥有过高的权限。Skill本质上是一个可执行逻辑的入口如果脚本本身的权限过大那任何一个能通过Agent触发Skill的人都可能间接获得容器内的操作权限。我的做法是容器内运行用户使用普通权限挂载目录只开放必要的读写范围Skill脚本一律不需要root就能跑。安全配置这部分内容不复杂但涉及生产环境最好提前规划好别等出了事再补救。5. Docker环境里那些绕不开的细节问题在Docker里跑OpenClaw和本地跑体验差异最明显的就是各种环境相关的问题。这些问题单独看都不算大但凑在一起很消耗耐心。我把这几天实际踩过的几个问题整理出来再配上我的解决思路希望对你有参考价值。5.1 容器重启后Skill丢失的问题这个问题出现的场景是我手动改了宿主机上挂载的Skill文件但容器重启后Agent就是不认新文件。查来查去最后发现并非文件丢失而是容器启动时挂载的路径和容器实际读取的路径不是同一个。这里就体现出目录结构版本差异的坑了。3.13版本的Skill目录路径设计合理但如果你是从旧版本升上来的一定要先确认一下新版本的默认路径是不是变了再看看自己的挂载参数是否指向了正确的目录。不然就会出现“文件明明在但框架就是读不到”的尴尬。5.2 时区与日志时间错乱的困惑默认时区问题在Docker容器里尤其突出。容器启动时如果没指定时区内部时间默认是UTC和宿主机时间可能差了好几个小时。这不是OpenClaw的bug但会直接导致日志里的时间让你误判问题发生的时间点。我的操作很简单启动容器时通过环境变量把时区设为Asia/Shanghai这样容器内部日志、脚本执行的时间都会走本地时区。在时间查询这类Skill的调试中这个设置特别重要不然你看到“当前时间”和实际时间对不上还得在脑子里做一遍时区换算很容易把自己绕进去。5.3 日志输出的编码问题中文环境下脚本输出中文到标准输出时偶尔会遇到编码问题表现是Agent拿到的结果里出现乱码。这个问题在框架层面不一定能察觉因为日志看起来是正常的中文但通过脚本执行接口拿原始输出就会发现编码已经不对了。我的解决办法在脚本里就处理掉统一用UTF-8作为输出编码同时在JSON序列化时设置ensure_asciiFalse这样中文在输出中就是真实的中文字符而不是一串转义后的Unicode序列。这个问题虽然小但如果你要做的Skill涉及大量中文内容输出最好还是提前处理一下免得后面排查问题的时候多一层复杂度。6. 我这几天踩出来的几个实用技巧最后这部分我根据这几天的实际体验把零散的经验系统化整理一下里面有几条是官方文档不会主动告诉你但实操中很有用的内容。6.1 Skill命名与描述要“场景化”这是我认为写好Skill最重要的一点。命名和描述不要写“查询时间”这种干巴巴的表述而要写“当用户需要知道某个时区的当前时间、安排跨时区会议或换算时间时使用”。Agent的决策依据主要就是描述信息描述里的场景词越多匹配准确率越高。我后面写一个新Skill的时候会在描述里刻意埋入几个高频场景词实测下来被正确调用的概率提升明显。6.2 参数设计要遵循“最小必要”原则Skill的参数不是越多越好。每增加一个参数Agent就要从对话中抽取一次信息抽取失败概率就会上升最终表现为“Skill被触发但参数不全执行报错”。所以参数设计一定要遵循最小必要原则能默认的默认能推断的推断只有真正无法省略的信息才设为必填参数。我的时间查询Skill里时区就是可选参数默认用UTC这样即使用户不提供时区也能返回结果。6.3 容器的生命周期管理建议我目前的使用习惯是Skill开发和调试阶段频繁改代码容器起了又停等Skill代码稳定之后再把它固定在一个长期运行的容器实例里。这样既保证了开发阶段的灵活性也避免了生产环境下因为频繁重启带来的不必要风险。另外一个建议是用好容器命名和标签。我在本地同时跑了好几个实验容器的实例如果没有清晰的命名规范最后很容易分不清哪个容器对应哪个Skill版本特别耽误事。我现在统一用“项目名-用途-序号”的方式命名找起来一目了然。6.4 从模仿开始再逐步建立自己的Skill库关于Skill编写本身我还有一个体会不要一开始就尝试写复杂的Skill。如果你是新手最好的办法是先把官方提供的几个示例Skill完整地读一遍、跑一遍看看它们是怎么写SKILL.md的、怎么设计参数的、怎么组织脚本输出的。然后在此基础上替换成自己的业务逻辑跑通了再逐步加复杂度。OpenClaw 3.13的Skill体系设计得足够灵活但越灵活的东西上手阶段越容易走弯路。我的建议是先从20行以内脚本能搞定的小需求开始把流程跑通再慢慢扩展。我自己就是在连续写完几个不同场景的小Skill之后才对整条链路有了比较完整的感觉。这篇文章基本把我从零开始接触OpenClaw 3.13 Skill编写的过程完整记录下来了从Docker环境搭建、Skill结构理解、第一个实操案例到调试方法和避坑经验每一步都是我实际走过的路。如果你也正在研究这个版本希望我的这些经验能帮你少绕几个弯子。Skill编写这块其实没有什么高深莫测的东西关键还是多上手试试得多了很多看起来复杂的机制自然就理顺了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑