老周的工具笔记

老周的工具笔记

已发布 24 篇文章

门控 Gating:让技能只在条件满足时才出现

门控 Gating:让技能只在条件满足时才出现
门控 Gating:让技能只在条件满足时才出现

一、先说个场景

你写了一个调用 gemini CLI 的技能,兴冲冲地发给同事。结果同事装上后一聊,OpenClaw 报错了:”找不到 gemini 命令”。原来他机器上根本没装 Gemini CLI。如果能在技能加载前就判断”依赖是否满足”,这种尴尬就不会发生。

OpenClaw 的 Gating(门控) 就是做这件事的:让某个 skill 只在条件满足时才被加载。条件可以是平台、二进制、环境变量、配置文件,甚至是一个”总是加载”的强制开关。

再设想一个场景:你维护一份内部工具技能,只在公司内网、且配了 INTERNAL_API_TOKEN 的机器上才能用。如果强行让所有人加载,一半同事会看到一堆用不了的灰按钮,体验极差。有了 gating,这份技能会”智能隐身”——不满足条件的人根本不会在列表里看到它,自然也不会去点、去报错。这就是 gating 存在的价值:把”能不能用”的判断前移到加载期,而不是等到运行时才爆炸。

二、为什么是这样

不同用户的机器环境不一样。有的装了 Docker,有的没有;有的配了 OPENAI_API_KEY,有的没配;有的在 macOS 上跑,有的在 Windows 上跑。如果让同一个 skill 在所有环境下都加载,不是功能残缺就是报错连连。

Gating 让 skill 作者声明”我的技能需要这些前提”。OpenClaw 在加载阶段就做过滤,不满足条件的 skill 根本不会出现在 agent 的视野里。这样模型不会调用一个注定失败的技能,用户也不会看到一堆灰掉的按钮。

把过滤放在”加载期”而不是”运行时”,还有一个好处:agent 的上下文更干净。如果所有技能无差别加载,模型要在几十个用不了的技能里挑挑拣拣,既浪费 token,也容易被”看起来相关但其实跑不起来”的技能误导。gating 相当于在门口先筛掉不合格者,让进门的每一个技能都是”当前环境真能用”的。对技能作者来说,这也是一种责任:你声明了依赖,框架就帮你兜住”环境不对”的底线,你不必在正文里写一堆”请先确认你装了某某”。

三、跟着做:分步操作

1. 官方示例拆解

---
name: gemini-search
description: Search using Gemini CLI.
metadata: { "openclaw": { "requires": { "bins": ["gemini"] }, "primaryEnv": "GEMINI_API_KEY" } }
---

这行 frontmatter 告诉 OpenClaw:

  • 只有在 PATH 里能找到 gemini 这个二进制时,才加载这个 skill。
  • 同时声明了 primaryEnv 是 GEMINI_API_KEY,方便 openclaw.json 里做 apiKey 绑定。

注意 requires.bins 是一个数组,写多个就表示”全部都要存在”;它只负责”检查”,不负责”安装”。这一点后面误区里还会强调。另外 primaryEnv 和 requires 是并列的两件事:前者是给配置绑定用的”主变量名提示”,后者是加载门槛,不要混为一谈。

2. Gating 字段全览

键 含义
requires.bins 列出的二进制全部必须存在于 PATH
requires.anyBins 至少有一个列出的二进制存在于 PATH
requires.env 每个环境变量必须存在于进程或配置中
requires.config 每个 openclaw.json 路径必须为 truthy
os 平台过滤:["darwin"] / ["linux"] / ["win32"]
always 只要 OS 兼容就加载,绕过 requires.* 检查
emoji macOS Skills UI 显示的表情
primaryEnv 与 skills.entries.<name>.apiKey 关联的变量名
install 给 macOS Skills UI 用的安装器规格

旧键 metadata.clawdbot 在 metadata.openclaw 缺失时仍被接受。

这张表里最容易混淆的是 requires.bins 和 requires.anyBins 的”与/或”关系:前者是”全部都要”,后者是”至少一个”。比如你写的技能既支持 rg 也支持 grep,就别用 bins(那样会要求两个都有),而该用 anyBins: ["rg", "grep"],让装了任意一个的人都能用。

3. 常用配方

只在 macOS 加载:

---
name: mac-helper
description: macOS-specific helper.
metadata: { "openclaw": { "os": ["darwin"] } }
---

要求某个 CLI 存在:

---
name: docker-cleanup
description: Clean up Docker containers.
metadata: { "openclaw": { "requires": { "bins": ["docker"] } } }
---

二选一,兼容两个同类工具:

---
name: web-search
description: Search the web via available CLI.
metadata: { "openclaw": { "requires": { "anyBins": ["gemini", "ddgr"] } } }
---

要求环境变量存在:

---
name: internal-api
description: Call internal API.
metadata: { "openclaw": { "requires": { "env": ["INTERNAL_API_TOKEN"] } } }
---

强制加载,忽略依赖检查:

---
name: always-on
description: Always load on compatible OS.
metadata: { "openclaw": { "always": true, "os": ["linux", "darwin"] } }
---

关于 always: true,要记住它跳过的是 requires.* 的检查,却不跳过 os 的过滤。所以上面这个例子在 Windows 上依然不会加载——always 不等于”全平台”,它只是说”只要平台对,就别再卡我依赖了”。

四、可以复制的指令

---
name: python-format
description: Format Python files with black or ruff.
metadata: { "openclaw": { "requires": { "anyBins": ["black", "ruff"] } } }
---
---
name: company-deploy
description: Deploy to company server.
metadata: { "openclaw": { "requires": { "env": ["DEPLOY_TOKEN"], "bins": ["kubectl"] }, "primaryEnv": "DEPLOY_TOKEN" } }
---
// openclaw.json 中绑定 primaryEnv
{
  skills: {
    entries: {
      "company-deploy": {
        enabled: true,
        apiKey: { source: "env", provider: "default", id: "DEPLOY_TOKEN" },
      },
    },
  },
}

这一段给出了”声明依赖 + 绑定密钥”的最小闭环:metadata.openclaw.requires 负责加载门槛,primaryEnv 告诉框架主变量叫什么,openclaw.json 里的 apiKey 再把它真正注入。三者配合,技能既能在缺依赖时自动隐身,又能在环境齐全时顺畅拿到密钥。建议你每写一个依赖外部命令或密钥的技能,都先照这个模板把 gating 补齐,再写正文逻辑。

五、实战案例

你有一个技能叫 cloud-backup,功能是把本地笔记同步到某对象存储。它依赖:

  • rclone 命令存在;
  • RCLONE_CONFIG 环境变量已配置;
  • 只在 macOS 和 Linux 上跑,因为 Windows 路径逻辑还没测试。

SKILL.md frontmatter:

---
name: cloud-backup
description: Sync notes to object storage via rclone.
metadata: {
  "openclaw": {
    "os": ["darwin", "linux"],
    "requires": {
      "bins": ["rclone"],
      "env": ["RCLONE_CONFIG"]
    },
    "primaryEnv": "RCLONE_CONFIG"
  }
}
---

openclaw.json:

{
  skills: {
    entries: {
      "cloud-backup": {
        enabled: true,
        apiKey: { source: "env", provider: "default", id: "RCLONE_CONFIG" },
      },
    },
  },
}

这样,Windows 用户不会加载这个 skill,没装 rclone 的用户也不会看到它,只有环境配齐的人才能用。出了问题也容易定位:先检查 rclone 在不在 PATH,再检查 RCLONE_CONFIG 有没有设。

补充一个排查顺序:当同事说”我怎么看不到 cloud-backup“时,别先怀疑 SKILL.md 写错。先问他是 Windows 还是 Mac/Linux(对应 os 过滤);再让他跑 which rclone(对应 bins);最后让他 echo $RCLONE_CONFIG(对应 env)。三步走完基本就能定位是哪一道门没过。把 gating 当成”自解释的安装说明书”,比在文档里写一大段”使用前请确认”要可靠得多。

六、常见误区

  • 误区 1:requires.bins 会帮你安装二进制。不会。它只是检查,缺失就跳过加载。安装依赖要另外处理。
  • 误区 2:always: true 可以覆盖所有平台。不能。always 不覆盖 os 过滤,平台不匹配仍然不会加载。
  • 误区 3:gating 是运行时权限控制。它只是加载期过滤,不是运行时授权。技能加载后依然可能做危险操作,要结合沙箱和白名单。
  • 误区 4:requires.config 写的是 shell 路径。它写的是 openclaw.json 的 JSON 路径,比如 agents.defaults.sandbox.docker.enabled。
  • 误区 5:把密钥存在 requires.env 里就安全了。requires.env 只是检查存在性,密钥管理另有 skills.entries.<key>.apiKey,且只注入宿主进程、不进沙箱。

七、进阶玩法

组合多个条件:

---
name: ai-image-gen
description: Generate images using local ComfyUI.
metadata: {
  "openclaw": {
    "requires": {
      "bins": ["python3"],
      "env": ["COMFYUI_URL"],
      "config": ["agents.defaults.mediaModels.image"]
    },
    "primaryEnv": "COMFYUI_URL"
  }
}
---

这里同时要求二进制、环境变量、配置文件三者都满足。

用 install 字段给 macOS Skills UI 提供安装器:

当你的 skill 在 macOS UI 里被发现但依赖缺失时,可以指定安装步骤,让用户一键补齐。

组合条件时有个实用心法:把”硬依赖”和”软依赖”分开想。硬依赖(缺了就完全跑不起来,比如核心 CLI)放进 requires.bins;软依赖(没有就降级功能,而不是不能用)则不该写进 gating,而应在技能正文里做分支判断。否则你会把一大批”其实勉强能用”的用户挡在门外。install 字段的意义也正在于此:它给 macOS UI 一个”缺口补丁”,让用户在看到技能的同时就能一键补齐依赖,而不是只能对着隐身的技能干瞪眼。

八、小结

Gating 是 skill 的”入场券”。通过 metadata.openclaw.requires 里的 bins、anyBins、env、config,再加上 os 和 always,你可以精确控制一个 skill 在什么环境下才加载。它不是运行时权限,只是加载期过滤,但已经能避免大量”环境不满足导致报错”的尴尬。用好 gating,你的 skill 在别人机器上才不会一装就炸。

一句话总结:bins/anyBins 管命令在不在,env/config 管配置齐不齐,os 管平台对不对,always 是”平台对就别卡依赖”的快捷通道。把这几样想清楚,再配合 primaryEnv 做密钥绑定,你写出的技能就能做到”该现身的现身、该隐身的隐身”,分发体验会好上不止一个档次。