
一、先说个场景
你在群里给 OpenClaw 发了一句 /status v1.2.0,希望它立刻返回当前系统的部署状态。结果模型开始”思考”:”用户是不是在问状态?我应该调用哪个工具?参数是什么?”三秒钟后,它才慢悠悠地执行。对于一个已经语义明确的固定命令,这种绕弯子是浪费。
更麻烦的是,这类命令往往出现在运维群、告警群这样的高压场景里。值班同学要的是”秒回”,而不是”思考”。一旦模型理解偏了,可能调到错误的工具、传错参数,反而引入新的事故。把语义固定的命令交给模型去”猜”,既慢又不可控。
command-dispatch 就是解决这个问题的:让某些斜杠命令绕过模型,直接派发给已注册的工具。延迟更低、结果更确定,也更好审计。在需要”说了就马上做”的场合,它比默认路径靠谱得多。
二、为什么是这样
默认情况下,OpenClaw 收到 /my-skill args 后,会把它交给模型:模型读系统提示里的 skill 描述,再决定是否调用、如何调用。这个流程灵活,但有两个代价:
- 延迟:每多一轮模型推理,就多几百毫秒到几秒。
- 不确定性:同样的命令,模型可能做出不同选择,尤其在提示复杂时。
有些命令语义非常固定,比如 /release_notes v1.2.0、”/deploy staging”、”/status”。这种时候不需要模型”理解”,直接映射到工具更快更稳。
command-dispatch: tool 就是做这个映射的声明。
这里有一个容易忽略的点:一旦 command-dispatch 生效,模型就不再读取这个 skill 的指令正文(Markdown 部分)。它只负责把用户输入作为原始字符串转交给 command-tool。所以正文里写再多”请先这样做、再那样做”的指令也不会被执行——真正决定行为的,是工具端的代码。这也意味着,你在维护这类 skill 时,重点应该放在工具的参数解析和校验上,而不是在正文里堆指令。
三、跟着做:分步操作
1. 声明 command-dispatch
---
name: release-notes
description: Print the latest release notes.
command-dispatch: tool
command-tool: print_release_notes
command-arg-mode: raw
---
# Release Notes
Print release notes.
三个字段一起用:
command-dispatch: tool:绕过模型。command-tool:指定目标工具名。command-arg-mode: raw:把用户输入的原始字符串传给工具。
description 虽然在 dispatch 时不参与执行,但它在 openclaw skills list 的展示、以及在 /skill <name> 的帮助信息里仍然有用,所以还是要写得清楚、且不超过 160 字。
2. 工具收到什么
当用户输入 /release_notes v1.2.0 --details 时,工具会收到一个对象:
{
"command": "v1.2.0 --details",
"commandName": "release_notes",
"skillName": "release-notes"
}
工具自己解析 command,决定下一步做什么。
这里有个实践细节:command 里是用户敲的原始字符串,可能带空格、引号、不规则顺序。工具端的解析要足够健壮,遇到多余空格、缺失参数、未知子命令时,应当给出明确报错,而不是静默忽略。因为命令不再经过模型”兜底”,所有边界情况都得工具自己兜。
3. 工具必须已注册
command-tool 指向的工具必须是 OpenClaw 环境里已经注册的工具。否则命令会失败。
如果工具依赖某个外部二进制,记得通过 metadata.openclaw.requires.bins 声明,例如 requires: { bins: ["git"] }。这样在缺少 git 的环境里,技能在加载阶段就会被拦下,而不是等到用户运行时才报”命令找不到”。
4. 放在哪个目录
技能加载有优先级:workspace 的 /skills 最高,其次是 ~/.openclaw/skills,再是 bundled 内置技能,extraDirs 排最低。把走 command-dispatch 的运维 skill 放进 workspace 的 /skills,既能保证它优先于同名的内置 skill,也方便团队用版本管理一起维护。
四、可以复制的指令
---
name: deploy
description: Deploy to a target environment.
command-dispatch: tool
command-tool: deploy_tool
command-arg-mode: raw
---
# Deploy
Dispatch to the deploy tool with the raw arguments.
// 把某个 skill 设为只对用户 slash 命令可见,模型不主动调用
{
skills: {
entries: {
"deploy": {
enabled: true,
},
},
},
}
# 测试直接派发
openclaw agent --message "/deploy staging --dry-run"
除了 openclaw agent --message,你在聊天里也可以直接用 /skill deploy staging --dry-run 触发同样的派发路径。两者最终都落到 command-tool,区别只是入口不同。
一个容易混淆的点:走 dispatch 的技能依然会出现在 openclaw skills list 里,它的 description 也照常展示——只是模型不再读取正文去决定怎么执行。如果工具还需要某个配置项,用 metadata.openclaw.requires.config 声明,加载阶段缺配置就会被提示,而不是等用户运行命令时才报错。
五、实战案例
你有一个内部运维工具叫 ops-tool,支持子命令:
ops-tool statusops-tool restart <service>ops-tool logs <service> --tail 50
你给它写了三个 OpenClaw skill:
---
name: ops-status
description: Show system status.
command-dispatch: tool
command-tool: ops_tool
command-arg-mode: raw
---
---
name: ops-restart
description: Restart a service.
command-dispatch: tool
command-tool: ops_tool
command-arg-mode: raw
---
---
name: ops-logs
description: Fetch service logs.
command-dispatch: tool
command-tool: ops_tool
command-arg-mode: raw
---
用户输入:
/ops-status→ 工具收到command: ""。/ops-restart nginx→ 工具收到command: "nginx"。/ops-logs nginx --tail 50→ 工具收到command: "nginx --tail 50"。
工具内部根据 commandName 区分是 status、restart 还是 logs,再调用 ops-tool 的对应子命令。
这样做的好处:
- 三个命令都不需要模型参与,毫秒级响应。
- 工具内部可以做严格的参数校验和权限检查。
- 审计时直接看工具日志,不需要回溯模型推理过程。
一个常见疑问是:”三个命令能不能合成一个 skill?”可以,但前提是工具端能根据 commandName 之外的信息区分意图;否则拆成多个 skill、各自映射一个子命令,反而更清晰、也更不容易误触。命令越少、语义越单一,越适合走 dispatch。
六、常见误区
- 误区 1:
command-dispatch: tool可以不写command-tool。不行,必须同时指定目标工具名。 - 误区 2:绕过模型就一定更快。确实更快,但你也失去了模型的灵活判断能力。语义不固定的命令不要绕过。
- 误区 3:
command-arg-mode: raw是唯一模式。当前默认且推荐的就是 raw,把原始字符串交给工具。 - 误区 4:用户 slash 命令和模型调用不能共存。如果
disable-model-invocation没设,模型仍可能主动调用 skill;但 slash 命令走 dispatch 路径。是否让模型主动调用取决于你的字段组合。 - 误区 5:绕过模型就不需要校验输入。恰恰相反,因为不经过模型,所有参数校验、权限检查都要在工具里做。
- 误区 6:正文里写指令能影响 dispatch 行为。不能。dispatch 模式下模型不读正文,真正的逻辑都在工具里。
七、进阶玩法
组合 disable-model-invocation:
---
name: ops-restart
description: Restart a service. User invocation only.
user-invocable: true
disable-model-invocation: true
command-dispatch: tool
command-tool: ops_tool
command-arg-mode: raw
---
user-invocable: true:用户能 slash 调用。disable-model-invocation: true:模型不会自己调用。command-dispatch: tool:slash 直接走工具。
这就是”只有用户明确授权才执行”的典型配置。
user-invocable 控制”用户能不能主动调用”,disable-model-invocation 控制”模型能不能主动调用”,两者是正交的两个开关。当危险操作需要强约束时,常见组合是:user-invocable: true + disable-model-invocation: true + command-dispatch: tool,把执行权完全收回到用户的显式输入上。
和 gating 结合:
---
name: deploy-prod
description: Deploy to production.
command-dispatch: tool
command-tool: deploy_tool
metadata: { "openclaw": { "requires": { "env": ["PROD_DEPLOY_TOKEN"] } } }
---
只有配置了生产部署密钥的人才能加载这个 skill,进一步降低误操作风险。
注意:这里的 PROD_DEPLOY_TOKEN 来自宿主进程的环境变量,注入到宿主进程而非沙箱。它不会出现在 prompt 或日志里,但工具代码能读到。这也提醒你:dispatch 工具一旦能读宿主环境变量,就要像对待任何本地脚本一样审慎对待它。
八、小结
| 场景 | 是否用 command-dispatch |
|---|---|
| 语义固定、需要快速响应 | 适合 |
| 危险操作,用户必须显式授权 | 适合 + disable-model-invocation |
| 语义模糊,需要模型理解上下文 | 不适合 |
command-dispatch: tool 让斜杠命令像快捷指令一样直接命中工具,省掉模型推理的延迟和不确定性。但它把判断责任交给了工具,所以参数校验、权限控制必须在工具层做扎实。把它用在”命令明确、动作固定、需要快速”的场景,才是正确姿势。
