面向 Agent 的 CLI / Skill 设计指南

从 6 个真实 skill(a1、dws、mw-cli、dms、pensieve、infra-config)里反推出来的方法论。它们是同一道题的不同成熟度答案——对照着看,就知道好在哪、坏在哪、该怎么写。

核心论点:给 Agent 用的 CLI,不是「给人的 CLI 加个 --json」,而是一套带自描述契约(schema)和强制门禁(gate)的 API——只是恰好长得像命令行。

目录

  1. 为什么 Agent CLI 是一类新东西
  2. 成熟度光谱:6 个样本横向对比
  3. 八条设计法则(含正反 case)
  4. 反面样本:没有 CLI 会怎样
  5. 落地顺序:从零写一个新 CLI
  6. SKILL.md 模板 + 检查清单
  7. 气味检测:一眼看出烂设计

01为什么 Agent CLI 是一类新东西

人和 Agent 用命令行的方式,根本不同。为人优化的东西,对 Agent 常常是坑。

维度人类用户Agent 用户
怎么知道有哪些参数翻文档、试错、Tab 补全、看报错必须一次拿到机器可读的完整契约,否则只能靠训练记忆猜——而记忆会过时
看到隐式状态(当前环境/绑定)记在脑子里,命令行提示符也会显示看不见。隐式状态 = 沉默的错误源
读输出表格、颜色、对齐,一眼扫要结构化 JSON;拼下一条命令时只想要那个 ID
等一个长任务盯着屏幕,看进度条每轮轮询 = 一次模型调用,烧 token 且可能死循环
犯错后看报错、改、重试,成本低错一步可能连环错,且不一定意识到错了
危险操作删库前会手抖、会二次确认只要文档没拦住,它就真敢执行
一句话:人靠「经验 + 实时反馈」补全 CLI 的不足;Agent 没有这两样,所以 CLI 必须把契约、状态、门禁显式地摆出来。文档里写的规则只是「建议」,能被工具拒绝的才是「规则」。

02成熟度光谱:6 个样本横向对比

同一道题的六种答卷。从右往左,是从「文档凑合」到「工程闭环」的演进。

能力 dws 最成熟 a1 成熟 dms mw-cli infra-config 反例
命令契约来源二进制生成的 schema,四层渐进查询手写 reference ~4000 行手写 reference手写 reference无,835 行单文件
安全机制放哪CLI 内:effect/risk/confirmation + --yes文档约定导流到平台审批工单纯文档「红线」
隐式上下文强制显式绑定态 + flag 可覆盖显式 + 本地缓存强制显式 --env --unit无概念
长任务原语40 个 scripts 封装--follow/--wait-until-settled/--offset工单状态查询
防契约漂移schema 随二进制发版 + 版本声明手工修(已留伤疤)手工手工(还躺着 .bak 文件)手工维护记录
输出契约schema 定义-f json / -q 取 ID本地 JSON 落盘-o json让 Agent 抄文档
关键读法:越靠右,越是「用提示词兜住工具的缺陷」;越靠左,越是「把规则做进工具、文档只做路由」。你要抄的是 dws 的架构,不是 infra-config 的做法。

03八条设计法则

每条都配一正一反的真实 case。看代码比看道理快。

法则 01 · 契约

--help 给人,schema 给机器,两者分开做

Agent 选参靠 schema,不靠训练记忆。手写文档一定会和真实 CLI 漂移。

dws 是唯一做到的:dws schema 分四层渐进查询(产品概览 → 产品 → 分组 → leaf),leaf 直接给出每个参数的 type / required / enum / constraints / examples。更关键的是它划定了事实源边界

✓ GOOD — dws 明确谁是权威
# 命令是否存在、当前接受哪些 flag
dws <path> --help
# Agent 选参 / required / 约束 / 风险
dws schema "calendar event create" --compact
# 三者冲突 = 「契约漂移」→ 上报,
# 不许拼接猜测、不许选更宽松的那个
✗ BAD — a1 只能靠警告兜底
# SKILL.md 开头被迫写:
⚠️ 构造命令前必须查阅完整参数
   不要凭记忆猜测 flag 名

# 这条警告存在本身,就是
# 「没有机器可读契约」的病征

→ 手写文档的代价,看 a1 留下的伤疤就够了:--run 现已必填run/retry 已废弃没有 --all(旧版 SKILL.md 拼写错误)。这些都是「文档跟不上代码」的化石。

法则 02 · 门禁

能被 CLI 拒绝的才是规则,写在 Markdown 里的只是建议

危险操作的拦截必须做进代码;文档拦不住一个「你确定要执行吗」都不问的 Agent。

✗ 弱 — mw-cli 用三条文档红线
# SKILL.md「安全红线」:
❌ 禁止 --env prod
❌ 禁止 mw config set ...

# 全靠 Agent 自觉。它完全
# 可以无视,CLI 层不拦。
✓ 强 — dws 做进 schema + 二段门禁
# schema 里每个工具都标:
"effect": "destructive",
"confirmation": "user_required"

# → 必须先让用户确认,再加 --yes
# CLI 层强制,不是靠提示词

还有第三种更聪明的解法(dms):不自己造门禁,而是把风险导流到已有的审批链。生产环境的 DML/DDL 一律走平台工单:

order init --order-type change → 编辑 → submit → submit-approval → execute # 审批通过后才执行

→ 设计顺序应该是:先把危险操作的门禁做进 CLI(或导流到审批系统),谈能力覆盖多少。

法则 03 · 状态

隐式上下文对人友好,对 Agent 是坑;要么不做,要么可查可覆盖

「有全局状态,但查不到、也盖不掉」是最差的形态。

这是样本里唯一的真分歧,两种可接受形态:

✓ 形态 A — mw-cli:干脆全显式
mw mq topic list \
   --env daily --unit ant-daily

# 且禁止 mw config set 改全局,
# 逼每条命令自带环境,意图零歧义
✓ 形态 B — a1:绑定态 + 可查可盖
a1 link status -f json # 执行前先确认绑定
a1 repo mr list          # 用绑定的仓库
a1 repo mr list --repo x # 单次覆盖

→ 核心不是「有没有隐式状态」,而是「Agent 能不能 看见它、并临时推翻它」。a1 的 link status -f json 就是把不可见的状态变可见。

法则 04 · 输出

输出必须有机器通道,并且 ID 能被直接取出

让 Agent 从人类表格里抠 ID,等于白送解析错误。

a1 的三档输出是范本:

档位命令面向
默认a1 app cr list人:表格/文本
结构化-f json| jq解析字段、链式取值
纯 ID-q只要 ID 去拼下一条命令

-q 这档最容易被忽略,但对 Agent 最关键:它拼下一条命令通常只需要一个 ID。list 类命令还应统一分页参数(--page-size / --offset),避免每个命令各造一套。

法则 05 · 长任务

凡「重复到某条件」的逻辑,下沉到 CLI 或脚本,别让 Agent 写循环

Agent 每轮循环 = 一次模型调用。轮询这种事必须由工具承担。

✓ a1 — 把「等」做成一个 flag
a1 app pipeline status --pipeline-id X \
   --wait-until-settled      # 一条命令等到结束
a1 build job log X --step Y --follow
a1 build job log X --step Y \
   --offset $NEXT --limit 500
✓ dws — 把复合流程封装成脚本
# scripts/ 下 40 个脚本,封装
# 翻页/轮询/批量导入/创建轮询
python scripts/aitable_import_via_task.py
# SKILL.md 明令:脚本优先于手写多步
a1 埋的关键细节:判断长任务是否结束,要用响应里的 nextOffset / finished / stepRunning 字段,不许用「返回空日志」当结束信号。日志 --limit 默认 500 行也是「输出预算」意识——防止一条命令灌爆上下文窗口。
法则 06 · 缓存

低频变更的元数据落盘 + 带时间戳,把「调 API」降级成「读文件」

表结构这类东西,读本地文件比调接口快一个量级。Agent 只需要一个能自己判断新鲜度的信号。

dms 把数据字典 sync 到本地,每个 JSON 带 synced_at

~/dms-alibaba/db-groups/<group>/databases/<db>/
├── database.json      # 连接信息 + synced_at
├── _index.json        # 表清单 + synced_at
└── tables/<t>.json     # 字段+索引 + synced_at
# 规则:本地优先;读前看 synced_at,过期再 sync

→ pensieve 是同一思想的极致版:整个知识库就是一个 Git 仓库,「编译一次、持续维护」,查询时读文件而非重新推导原始文档。

法则 07 · 自愈

故障自愈路径要压成「单命令可判定」,并授权 Agent 自行修复

别让 Agent 面对「先检查安装、再检查登录、再检查配置」的多步判断树——压成一条命令 + 一张处置表。

✓ dms — 一条命令定位问题
dms-alibaba auth  # 同时验证安装 + 凭证
输出处置
命令不存在跑安装命令
报错版本旧,更新
凭证缺失auth --force
就绪下一步
✓ a1 — 把「送回坑」也做成命令
a1 faq grep "关键词"   # 内置文档可搜
a1 faq search "..."    # 语义搜索
a1 feedback "标题" ...  # 统一反馈通道

# 承认 Agent 用 CLI 一定会踩坑,
# 就得留一条把坑送回团队的管道

→ dms 还写明:「Agent 有权直接执行安装和修复命令,无需展示给用户手动操作」,并给了无浏览器环境的旁路(直接写 credentials.json)。自愈要连「授权」一起给足。

法则 08 · 命名

消歧规则的条数,是你 CLI 领域模型混乱度的体温计

每多一条「⚠️ 不是 XXX」,就说明命令命名多欠一笔债。

a1 里至少 4 处强消歧,dws 有决策树 + 5 条「关键区分」+ 独立的 intent-guide.md:

这些不是文档问题,是命名问题。内部平台的词汇是组织架构的化石——不同团队各造一套动词。正确的修法是在 CLI 里收敛动词和别名,而不是在文档里不断追加消歧段落。文档消歧是止血,不是治病。

04反面样本:没有 CLI 会怎样

infra-config 是唯一没有 CLI 的 skill——835 行单文件说明书。它精确地演示了「配置说明书」冒充「能力入口」的三个结构性问题。

反例 · infra-config

把 MongoDB / Redis / AgentBay / DashScope / FAI / Tavily 的连接串、SDK 安装、代码示例、维护记录、待办清单全塞进一个 SKILL.md

问题表现对照正例
无分层任何一次触发都要吞 835 行进上下文a1 是 SKILL.md 618 行 : references 4000+ 行 ≈ 1:7,只有路由信息进上下文
占位符会被当真Redis 整节是 [待填充],Agent 读到照抄进代码命令不能留空:config get redis 要么返回值要么报错
没有可执行入口全靠 Agent 从文档复制连接串和示例应退化成一条命令 xxx config get mongodb -f json 或一个 .env
结论:「配置说明书」和「能力入口」是两件事。让 Agent 读文档抄连接串,正是 CLI 应该消灭的动作。如果你的 skill 本质是「一堆要 Agent 照抄的信息」,那它就还没成为一个 skill。

05落地顺序:从零写一个新 CLI

顺序很重要。先建契约和门禁,最后才写文档——反过来做,就会变成 a1 那样用文档追着代码补。

1
先建 schema,从代码 / Cobra tree 生成,别手写

每个命令、每个参数的 type / required / enum / 约束 / 示例 都从代码生成,随二进制发版。手写 4000 行 reference 的下场,看 a1 的 --bak 文件和「拼写错误」注释。做成分层查询(概览 → 领域 → leaf),别让 Agent 一次吞全量。

2
再上安全门禁

每个命令标 read / write / destructive。destructive 一律要 --yes,且默认要求先向用户确认。高危操作优先导流到已有审批系统(像 dms 走工单),而不是自己造一套确认逻辑。

3
再定输出契约

-f json(结构化)+ -q(纯 ID)两档必备。list 类命令统一分页参数。给每个可能刷屏的命令设默认输出上限(如日志默认 500 行)。

4
再补长任务原语

--follow--wait-until-*--offset/--limit 增量拉取。明确定义「结束信号」字段(finished 之类),别让 Agent 靠猜。复合流程封装成脚本。

5
最后写 skill 文档,且只写三类内容

意图 → 命令映射表 ② 消歧规则 ③ 故障处置表。全量参数交给 schema / --help。文档跟二进制一起发版,并声明版本约束(dws 的 cli_version: ">=1.0.15")。

06SKILL.md 模板 + 检查清单

一个能直接抄的骨架。它刻意只保留「路由 + 门禁 + 处置」,把重活留给 schema。

---
name: my-cli
version: 1.0.0                    # 每次发布必须递增(SemVer)
cli_version: ">=1.0.15"          # 声明依赖的二进制版本,防漂移
description: "一句话做什么 + 一串触发词(越具体越好,覆盖用户可能的口语说法)"
---

# My CLI 使用助手

## ⚠️ 核心规则:构造命令前查契约
下文只列常用 flag。涉及任何具体选项,先跑 `my-cli <cmd> --help`
或查 schema,不要凭记忆猜 flag 名。

## 🚦 安全门禁(不可协商)
| 操作类型 | 处置 |
|---|---|
| read      | 直接执行 |
| write     | 执行前告知用户影响范围 |
| destructive | 先展示操作摘要 → 用户确认 → 才加 --yes |
线上/生产:<写清楚是禁止,还是必须走审批工单>

## 前置检查(自愈)
跑 `my-cli auth`(一条命令同时验证安装 + 凭证):
| 输出 | 处置(Agent 有权直接执行) |
|---|---|
| 命令不存在 | <安装命令> |
| 凭证缺失   | my-cli auth --force |
| 就绪       | 下一步 |

## 意图 → 命令映射
| 用户意图 | 命令 |
|---|---|
| 查 X 列表 | `my-cli x list -f json` |
| 创建 X    | `my-cli x create --name ...` |
| ...       | ... |

## ⚠️ 消歧(只在命令易混时写)
- 用户说「A」→ 用 `cmd-a`,**不是** `cmd-b`(区别:...)

## 输出格式
`-f json` 解析字段;`-q` 只取 ID 拼下一条命令;默认表格给人看。

## 长任务
- 等结束:`--wait-until-settled`;跟日志:`--follow`
- 判断结束用响应的 `finished` 字段,不要用「空输出」

## 参考文档(按需读,不要全进上下文)
- references/x-commands.md — X 的全部 flag

SKILL.md 自检清单

07气味检测:一眼看出烂设计

不用读全文,看到这些信号就知道有问题。

闻到的气味真正的病根该怎么治
SKILL.md 里写「不要凭记忆猜 flag」没有机器可读契约做 schema(法则 01)
安全全靠「⚠️ 禁止…」文档段落门禁没做进代码标 effect + --yes 门禁(法则 02)
有全局配置,但没有 xxx status 能查隐式状态不可见加状态查询 + flag 覆盖(法则 03)
Agent 需要从表格输出里正则抠 ID没有机器输出通道-f json / -q(法则 04)
SKILL.md 教 Agent「循环调用直到…」长任务没做进 CLI--wait/--follow(法则 05)
references 目录里躺着 .bak 文件文档和代码在漂移schema 随二进制发版(法则 01)
消歧段落越写越多命令命名混乱收敛动词和别名(法则 08)
skill 本质是「一堆要 Agent 照抄的信息」它还不是能力入口退化成命令或 .env(第 04 节)
正文动辄几百行、无分层全量进上下文,挤占窗口SKILL.md 只留路由,细节进 references