跳到主要内容

skill_md 型 · 文档型(含 API 型)

什么时候读:不写代码,让隔离子代理照 SKILL.md 的步骤操作;或要把一组第三方 HTTP API 包成技能。 适合:多步操作流程、需要判断的工作流、调现有工具的组合。不适合:需要确定性结果的计算。 执行面按 roles_chat_skill_runtime.py / roles_chat_api_skill_executor.py 核对(2026-10)。

工作机制:三条出口​

trigger-type: explicit 才进模型的工具清单。模型选中后,平台按文档形态走三条路之一:

出口条件执行者
纯指令型平台判定无需执行文档直接交还主模型,主模型自己照做,不起子代理
API 型见下文「API 型」_ApiToolFace:每个端点一个工具,httpx 确定性发请求,工具面里没有 shell
执行型(默认)其余_ShellToolFace:子代理 + read_skill_file + container_shell(无容器时退为 run_code)

子代理没有调用方的对话历史,文档必须自足:输入从哪来、用什么工具、产出什么、什么情况下停。 链 / 定时任务(allow_skill_md=False)直接拒绝这类技能。

执行型子代理的实际约束​

项值
轮数 / 墙钟12 轮 / 420 秒;判定为「样例脚本型」(有 scripts/ 但脚本不吃参数)时放宽到 20 轮 / 900 秒
卡死判定同一 (工具, 参数) 3 次
工具read_skill_file(file=相对路径)(自动定位当前技能,单次上限 40000 字符;脚本文件 120000)+ container_shell 或 run_code
run_code 模式宿主 Python 沙箱:无 shell、不能装依赖、没有密钥注入通道——要 $密钥 的步骤在该模式下不可用
平台追加的系统提示「你唯一的任务是完成【用户指令】那一句;SKILL.md 的简介/功能列表不是任务清单」「不要用 shell 测密钥是否存在」「拿不到数据如实说」
{baseDir}改写成容器内技能目录路径;资源文件清单自动附在提示里
依赖声明了 pip / npm 依赖则已预置;否则提示子代理 pip install -r <技能目录>/requirements.txt
文档导航存在 references/recipes/_index.md 时按「配方」导航;否则列出技能真实包含的文档、预读相关的几份

frontmatter 里执行面读的是 metadata.openclaw:

metadata:
openclaw:
primaryEnv: XXX_API_KEY # 子代理被告知用 -H "Authorization: Bearer $XXX_API_KEY"
requires: {env: [XXX_API_KEY]} # 没写 primaryEnv 时取第一个
requiresContainer: true # 或 sandbox: container / docker
outputType: file # 产出契约(可选)

写法上的推论:

  • 不要写「先 echo $KEY 确认一下」「[ -n "$KEY" ] 判断有没有配」——平台明令子代理别做,写了只会冲突。
  • 「功能列表」写成能力说明,别写成待办清单;子代理只做用户这一句要的事。
  • 要被预读,reference 文件名和标题就要和任务词对得上。

硬限制:8000 字符​

SKILL.md 超过 8000 字符会被截头部注入,尾部丢失——丢的恰好是 checklist 和收尾步骤(规范 §2.2)。

对策:

  • 主文档只放:触发场景、总流程、分支判断、何时读哪份 reference。
  • 细节放 references/*.md,正文写明「执行 X 前先读 references/xxx.md」。
  • 单个 reference 别超过 40000 字符(read_skill_file 单次上限),超了拆。

写法​

写给执行它的子代理看,不是写给选工具的模型看:

  • 祈使句。「先调 list_tables 取表 id,再……」而不是「本技能可以……」。
  • 每步写清用什么工具、传什么参数、期望返回什么形状、返回不符时怎么办。
  • 分支用表格:左列是判断条件,右列是动作。
  • 明确「停下来问用户」的条件,否则子代理会自己编参数往下走。
  • 动作型(以 action 分发)的文档明写「action 必须传中文原词」+ 至少三个调用示例。
  • 帮助表每行左列是触发场景(「连得上数据库吗」),右列是它回答什么。不要左列写动作名。

给用户看的「下一步」​

「下一步:说拉数据」会被子代理当成自己的指令直接执行。改为「可继续的操作:……」并标注 for_user。

frontmatter​

---
name: xxx
description: 一句话说清什么时候该调它
skill_type: skill_md # 没有 handler.py 时建议显式写;有 handler.py 一律判执行型
skill_md: SKILL.md # 可选,主文档不叫 SKILL.md 时指定
trigger-type: explicit
category: general # 只认 13 个分类值,见 templates/SKILL.md.tmpl
---

API 型(包一组第三方 HTTP API)​

怎么被判成 API 型(任一命中):

  1. metadata.openclaw.skillClass 以 api-skill 结尾;
  2. metadata.openclaw.authType: bearer_env;
  3. 技能目录里有规格文件:openapi.yaml|yml|json(根目录、references/、api/、spec/),或 endpoints_whitelist.yaml|yml(references/ 或根目录)——找不到还会递归全目录搜同名文件。

推论:普通文档型技能里只要任何位置放了一份名为 openapi.yaml|yml|json / endpoints_whitelist.yaml|yml 的文件 (哪怕只是参考资料),就会被当成 API 型执行、失去 shell。参考用的 OpenAPI 改个名(如 api-reference.yaml)。

规格怎么变成工具:

来源规则
OpenAPI端点 = paths 下 get/post/put/delete/patch;工具名 = operationId(没有则 method_path);参数 = parameters + requestBody 第一个 content 的 properties;base_url = servers[0].url;域名白名单 = 全部 servers 的 host
whitelistbase_url + endpoints: [{id, method, path, required: [..] 或 {oneOf: [..]}, optional: [..]}];参数一律按 string;白名单 = base_url 的 host
# references/endpoints_whitelist.yaml
base_url: https://api.example.com/v1
endpoints:
- {id: search_notes, method: GET, path: /notes/search, required: [keyword], optional: [page]}
- {id: note_detail, method: GET, path: /notes/detail, required: {oneOf: [note_id, url]}}

执行规则(_http_call):

  • URL = base_url + path,路径模板 {id} 不做替换——端点设计成参数全走 query / body; GET 参数进 query string,其它方法参数整体作 JSON body。
  • 鉴权只有一种:配了密钥时加 Authorization: Bearer <密钥>(没配则不带头)。其它鉴权方式(签名、query key、自定义头)用执行型或 handler 型。
  • 目标 host 不在白名单 → 拒绝(防 SSRF)。超时 25 秒;429 按 Retry-After 退避最多 2 次(单次 ≤5 秒);5xx 重试 1 次。
  • 401 / 402 / 403 / 404 → 立即停(提示检查密钥 / 端点过期)。
  • 子代理最多 6 轮 / 90 秒,同参重复 2 次判卡死;端点多时先按用户指令预筛一批再给模型。
  • 模型没给最终文本时,平台用最后一次返回(截 12000 字符)自动整理成中文结果。

调别的技能​

文档型拿不到 call_skill,子代理靠自己的工具清单调。 需要「先取数再落库」且落库要 db 的,把落库写成进程内 Python 技能,文档里写明调它。