文章探讨了智能体时代命令的本质——不应仅让现有CLI更易操作,还需重新思考命令本身应表达什么;提出了原子性、可组合性等设计原则。
我一直思考软件接口领域的一个转变。
CLI 是为人类设计的。API 是为程序设计的。现在我们正在为智能体设计接口。
这个框架是一种简化——CLI 始终有一部分是供机器操作的,因为 shell 脚本几十年来一直以非交互方式驱动它们,而这也正是 CLI 成为智能体天然载体的原因。但重点正在改变。我们不再只是容忍非人类调用者;而是开始有意为特定的调用者——智能体——进行设计。
前两类我们已经花了几十年时间去摸索。第三类仍然开放——而这正是本文不断围绕的问题:一个专为智能体设计的接口应该是什么样的?
目前已有大量有价值的工作致力于让 CLI 更安全、更可预测、更易于智能体操作。我在重新设计一个开源项目的 CLI 时应用了许多这类想法,而越是深入细节,我就越开始怀疑问题是否还有另一层。
也许我们不应该只是让现有 CLI 更易于智能体使用。也许我们还应该重新思考一个命令在本质上应该代表什么。
让 CLI 适合智能体并不等于为智能体而设计
近期关于智能体友好型 CLI 的工作涵盖了許多重要的领域。Zbigniew Sobiecki 的《构建智能体友好型 CLI》就是一个很好的例子,以下列表总结了该工作所倡导的那种纪律性:
清晰的 stdout/stderr 分离
非交互行为
渐进式发现
机器可读的帮助
(面向智能体的技能通常作为伴随层出现在这类工作旁边,而不是作为 CLI 功能本身——稍后详述。)
我在重新设计 loomloom 的 CLI 时应用了许多这类想法。loomloom 是一个编译器,将 AI 工作——作者的指令和工作流——转化为可执行系统。但越是深入细节,我就越发怀疑我们只解决了问题的一部分。
我一直回到这个区分上来:
智能体友好型设计问的是:智能体如何可靠地操作一个接口。 意图优先设计问的是:接口是否首先暴露了正确的抽象。
上述大部分工作关注的是第一个问题。本文关注的是第二个问题。
考虑一个传统的面向资源的 CLI。系统直接暴露其领域模型:
work create
execution get
execution retry
template publish
listing update
这是一个完全合理的模型。资源层级告诉你存在什么,命令则操作这些资源。
但智能体可能完全了解资源模型,却仍然不想从它开始。假设用户说:
"运行这个工作。"
总有什么必须弄清楚"这个工作"是什么——解析标识符,决定它是模板、私有规格还是市场项目,并创建正确的执行。真正的问题是:谁来做这件事,以及智能体何时需要思考它。在意图优先的设计中,智能体发出一个操作:
run <work>
系统在此之下执行该解析,作为翻译意图的一部分。智能体不需要为了开始而导航 Work → Execution → Artifact。
那些资源命令成为智能体在需要观察或恢复时才会使用的工具——获取结果、检查失败、慎重重试:
execution get
execution retry
artifact get
所以问题不在于智能体是否理解资源。它们经常需要理解。问题在于:做一件常规的事是否应该从资源模型开始。
了解资源模型与想要通过资源模型操作
这就是面向智能体的技能、MCP schema、文档和机器可读帮助发挥作用的地方。它们可以教智能体了解存在哪些资源、如何关联、哪些命令操作它们、需要什么输入、期望什么输出,以及哪些操作可以安全重试。对于复杂系统,这些知识可能是必不可少的。
但了解资源模型与想要通过资源模型操作之间存在一个重要区分。
考虑一个部署系统,其资源模型如下:
Application
└── Deployment
└── Revision
└── Instance
智能体可以理解整个层级。但当用户说:
"部署最新版本。"
智能体可能会推理出类似这样的步骤:
find application → find latest revision → create deployment → inspect deployment → verify instances
资源模型帮助智能体推理正在发生什么。意图提供了一种更直接的方式来表达应该发生什么,包括相关的参数和约束,而无需首先导航层级:
deploy <application> --version latest
系统在幕后解析涉及哪个应用、哪个版本、哪个部署。意图不仅仅是 deploy 这个动词——它以更高层级代表期望的操作,而资源模型描述了执行它所涉及的具体对象和状态。
这指向三个概念层:
Agent
│
▼
Knowledge layer what exists / how the system works
(skills, MCP, schema)
│
▼
Intent layer what the agent wants to accomplish
(run / deploy / verify)
│
▼
State layer what actually exists and what happened
(application / revision / deployment / instance)
意图通常追溯到人类目标,但智能体是在这个边界上表达它的——而且它添加了自己的操作意图(quote、watch、retry),这些是人类从未明确说过的。
有趣的设计问题是:这些层应该如何交互——尤其是,智能体应该从哪一层进入。
面向资源的接口仍然有价值
很容易将面向资源的设计 dismissal 为遗留包袱——它是智能体出现之前构建软件的产物。它确实比智能体更古老;这部分是真的。但古老不等于过时,有两件事让它保持相关性。
首先,资源暴露状态,而智能体需要状态。其次,这个模型经过了 API 和 CLI 设计的数代打磨,这让它足够灵活,可以带入智能体时代。
智能体从失败操作中恢复时,可能需要知道创建了什么、处于什么状态、哪个执行失败了、使用了哪些输入、存在哪些产物、实际运行的是哪个版本,以及重试是否安全。这些都是资源级别的问题。
像 retry 这样的高级命令很方便,但最终智能体需要准确了解正在重试什么以及为什么。在正常操作期间,智能体也是通过资源来看状态的——获取结果和检查状态也是资源读取,而不仅仅是紧急手段。这种组合——常规观察加上自动化出错时的低级控制面——就是我不想抛弃它们的原因。
为什么意图导向的接口有吸引力
真正的问题不是资源——而是泄漏的资源。直接暴露每个内部对象往往暴露的是系统的实现模型而不是其领域。面向资源的接口甚至可以提供高级操作——kubectl apply 部署应用时不需要你手动创建 ReplicaSet 再创建 Pod。所以敌人从来不是资源导向。当接口泄漏了调用者不应该解决的内部区分时,这才是问题所在。
这正是我在现有的 loomloom CLI 中遇到的问题。对构建二进制文件的遍历统计了 17 个顶级命令组和 67 个叶子命令——但数量并不是真正的问题。问题是语义碎片化:运行一个 AI 工作分散在六个不同的命令中,取决于资源——
template submit-file
run execute
template-spec submit-workbook
template-spec run
market run
market workbook run
——生命周期动词按组重命名(验证工作可以是 validate-file / validate / validate-workbook;估算其成本是 precheck-file / precheck / precheck-workbook / quote)。大型 CLI 不一定是坏事。让一个用户意图在多个内部执行路径中选择的 CLI 才是。这些是调用者不应该关心的内部区分。
对于人类来说,这是学习问题。对于智能体来说,这是决策问题。智能体不仅仅需要知道"如何执行这个?"—它还必须弄清楚"这些执行路径中哪个对应我想要的?"那是不必要的决策——每个额外的分支都是选错路径、阅读更多文档或从失败尝试中恢复的机会。
对于 loomloom 这样雄心勃勃的项目,这两种方式都不可接受。它的设计目标是运行大型并行批次,而非一次处理一个任务。在这种规模下,成本会叠加——每个新用户的学习成本,以及每次任务执行中的决策开销——小单位的任务成本乘以整个工作负载就变成了一笔大账。
loomloom 的 intent-first CLI RFC(RFC-0001)提议围绕意图而非内部资源来组织交互层,而 command-model RFC(RFC-0002)则将这一想法具体化:
loom run quote <work> ...
loom run start <work> ...
loom run watch <run-id>
loom run results <run-id>
不需要用户或智能体先去了解某样东西是 template、template-spec 还是 market object,CLI 在能安全判断时自动解析出底层的适当类型。用户或智能体说出他们想要做的事,CLI 负责更多的翻译工作。例如,loom run start <work> 会自行解析 work 的类型并验证输入,但它不会越过付费边界,除非有明确的 --yes。
这并不是要永远隐藏这些区别。有经验的用户或智能体最终会关心某样东西是模板、规范还是市场条目——在他们需要精确控制时会想要那种控制权。这是一种延迟区分的策略。第一次尝试 loomloom 的人应该能够在学会分类体系之前就运行一个 work 并看到结果。对于一个年轻的项目来说,这个gap——从"安装了它"到"让某件事发生了"——往往决定了用户是否会回来。
这听起来很有吸引力——而且在一定范围内确实如此。但如果把这种便利性推得太远,它本身就开始制造一个新的问题。
如果我们的设计目标是面向智能体,为什么不更进一步?为什么不让 loom run <work> 自动识别 work、自动选择版本、自动估算成本、自动开始执行、自动监控、自动重试失败、自动优化模型选择,并自动收敛到期望的结果?
到了那个程度,它看起来就不太像 CLI,而更像是一个意图接口。这里有一个有趣的先例:基于意图的网络(Intent-Based Networking)——表达一个期望的结果,将其翻译为具体操作,激活它,持续验证结果,并在系统偏离时进行修复。这个模型与智能体操作的软件有着惊人的相关性。
但有一个问题。在网络中,自主修复往往正是你想要的。而在 AI 智能体控制的 CLI 中,自主修复可能意味着花费比用户预期更多的钱——这是一种截然不同的失败模式,特别是在生产工作流中,许多任务并行运行:
100 个任务运行中
↓
20 个失败
↓
自动重试
↓
部分失败实际上是永久性的
↓
额外 20 次执行 / API 调用
对于单个任务看起来无害的重试策略,在乘以批次规模后会变得惊人的昂贵。这对 loomloom 尤为重要,因为它被优化为将 AI 工作作为潜在的大型并行工作负载来运行,而不是完成一个任务就停止。它通过提前将每个工作流编译成固定的执行图来实现这一点——购买结构确定性(可预测的执行形状,加上运行时可以调度的并行性和成本优化)而不是将控制流留给智能体即兴发挥。图是固定的;其中模型步骤在语义上仍然是随机的。这是一个有意的权衡——为了性能和可靠性而选择结构确定性,而不是假装输出也是确定性的——这正是为什么在批次规模下必须谨慎处理重试策略。
这个设计的有趣之处不在于"intent-first 获胜"。而在于弄清楚意图应该在何处停止,明确的控制应该从何处开始。
我正在尝试一个简单的原则:
辅助意图,费用处把关。
让系统在操作变得重大之前做尽可能多的安全推断,但将重大成本或变更边界明确化:
意图 → 检查 / 报价 → 明确批准 → 启动 → 监控 → 结果
系统可以安全地推断以下内容:某个 ID 指向哪种类型的 work、输入格式、正常解析链中的配置,以及逻辑操作的稳定标识,以便重试能被识别为同一请求。但它不应该静默推断涉及花钱或变更远程状态的事情。
值得诚实地说清楚这样做的代价。Intent-first 并未删除资源模型——而是迁移了责任。调用者不再需要导航类型;系统接管了解析——歧义、优先级、版本选择、默认值、授权。一个好的意图接口并不是隐藏资源模型,而是承担起解析它的责任,这就是为什么"安全解析或响亮失败"比"聪明解析"更重要。
所以 loom run quote ... 可以计算将要发生什么,而 loom run start ... --yes 是一个明确的激活边界。歧义的 work 类型应该失败而不是静默地选择一个。对于失败的付费操作,不应该自动重试并可能再次向用户收费。
这就是设计对我而言变得具体的时刻。对于长时间运行或并行的工作负载,智能体可能不知道前一个请求是否真的到达了后端——超时可能发生在服务器已经接受工作之后。如果智能体只是重新发送,就可能创建第二个执行。标准修复是幂等性:将请求与一个稳定标识符关联,这样后端可以识别同一逻辑操作的重复提交并一致地处理它们,而不是盲目地创建另一个。关键区别在于:
系统可以让重试变得安全,而不必决定重试应该发生。
智能体或用户仍然决定是否重试。这正是 intent-first 和 agent-native 设计开始拉向不同方向的地方:intent-first 倾向于为调用者做更多——推断、自动化、收敛——而 agent-native 倾向于确定性和明确控制,以便智能体能够看到发生了什么并自行决定后续步骤。自动重试正是前者所青睐的便利自动化,也是后者所抵制的。
最近关于 agent 友好 CLI 的许多思考集中在使操作可预测、可脚本化、安全可重试上。这是合理的——智能体需要确定性接口和清晰的变更边界,特别是当它们在没有人监督每个命令的情况下操作软件时。
但这引发了一个问题:如果我们正在让软件更容易被智能体操作,我们应该实际赋予智能体多少自主权?有一个诱人的方向——agent-native = 隐藏复杂性 + 让智能体做所有事——我不确定那是正确的终点。智能体需要抽象,但它也需要知道将要发生什么、发生了什么、什么失败了、什么可以安全重试、什么需要批准、以及如何恢复。
这就是为什么 RFC-0002 故意保持各个阶段可用:
run check
run quote
run start
run watch
run results
同时仍然使高层意图显而易见。注意,这是更多的命令,而不是更少——intent-first 不意味着命令最少,而是意味着决策最少:智能体每个目标面对一条明显的路径,而不是一堆内部执行路线的菜单。智能体可以在适当的时候走便捷路径,而不会失去对底层控制面的访问。换句话说:
隐藏实现模型,而不是隐藏操作状态。
需要对"确定性"做一个澄清,因为它容易被过度宣称:像 run start 这样的意图在时间上不是确定性的——--version latest 明天可能会解析出不同的结果。重要的是,在相同的输入和系统状态下,它是确定性的,而且智能体始终可以检查意图解析到了什么。而时间敏感性正是被尊重的意图,而不是 bug:--version latest 是请求追踪最新版本,所以每次运行都重新解析它正是正确的做法。将其固定到昨天的版本在时间上会更确定——但却是错的,因为它不再匹配所请求的内容。结果应该始终跟随意图,而不是意图的冻结快照。
综合来看:知识层告诉智能体存在什么以及系统如何工作,意图层表达智能体想要完成什么,状态层报告实际存在什么以及发生了什么。位于这两层之间——向下时的翻译,向上时的观察——的正是大部分真正设计工作所在的地方:
Agent
│
▼
Knowledge / skills
│
▼
Intent run / deploy / verify
│
▼
Translation resolve types, gate cost
│
▼
State execution / artifact / …
│
▼
Observation / recovery inspect, decide, retry
关键在于,AI 智能体不必从底层开始。对于常见操作,它可以从 intent 层开始;当需要推理状态、恢复、调试或精确控制时,再下沉到 state 层。
这与人们学习陌生系统的实际方式如出一辙:先定目标,再按需学习底层结构。AI 智能体亦然——它们不只是执行命令,而是 plan → act → observe → reason → recover → act again。接口必须同时支持目标表达和状态检查。要点不在于把系统藏起来不让 AI 智能体看见;而是让 AI 智能体从所需的抽象层级开始,在任务需要时再深入下去。这与让新手在学习分类体系之前先跑通第一个任务的原理相同——从现有位置出发,只在必要时才深入——只是现在服务的是任务中的 AI 智能体,而非入职第一天的人类。
这改变了"AI 智能体友好"的含义
一个 CLI 是否 AI 智能体友好,并不仅仅取决于 LLM 能否解析其输出。而在于接口是否在恰当的时刻提供了恰当的抽象:
Planning —— 高层操作:search、run、publish、install
Execution —— 可预测、明确的转换:quote、start、watch
Observation —— 结构化状态:get、results、status
Recovery —— 对已发生事情的精确控制:get run、inspect error、retry
这意味着"CLI 应该是资源导向还是意图导向?"是个错误的问题。更有意思的问题是:
在规划、执行、观察、恢复的每个阶段,AI 智能体应该使用哪种抽象?
协议不决定抽象
你可能在想:为什么不直接架设一个 MCP server 就完事了?但 MCP——如同 function calling、REST API 或 SDK——定义的是 AI 智能体如何发现和调用某个能力。它不决定那个能力应该是什么。你可以通过 MCP 暴露 template submit-file、run execute 和 market run,正如你可以暴露一个 run <work> 一样忠实。两者都是合法的 MCP。协议不决定抽象。
所以这一切实际上与 CLI 无关。CLI 只是我恰好在重新设计的具体表面。真正的问题——AI 智能体面向的接口应该给 AI 智能体资源模型,还是意图导向的操作面?——位于协议之上。无论传输层是什么,仍然需要有人来决定 AI 智能体操作的是什么。
我目前的结论——欢迎反驳
在开放问题之前,值得明确陈述这篇文章确实持有的立场——说得直白一些,便于争论。我对这些观点相当有信心,正因如此,我希望它们被狠狠检验:
如果其中任何一条明显是错的——或者明显已经被解决了——这将是最有价值的信息之一。
这仍然是一个开放问题
我并非在宣称这个问题已被解决。loomloom 的 RFC 是刻意作为讨论提案提出的。但在推演过程中,我意识到这些问题并不属于同一类。
有些我已经有了工作立场——至少对 loomloom 而言——即便我不确定答案是否具有普适性:
推理多少才算过多? 对 loomloom 来说,在安全范围内越多越好,在成本或变更边界处停止——"在钱包处设门控"。这是否是通用 правило,我不确信。
AI 智能体何时可以被允许在资源之间做选择? 我目前的答案是不静默选择:模糊的类型应该报错而不是猜测。但这对低风险系统可能过于严格。
意图层命令是否应该始终暴露底层资源操作以供精确控制? 我论证过是肯定的,但"始终"是个很强的词,我想知道它在哪些情况下会失效。
应该直接暴露多少状态? 我的经验法则是不错——操作状态是,模型则否;两者之间的界限仍然模糊。
CLI 能否在保持高级抽象的同时保持可预测性? 我认为可以——只要给定相同的输入和状态,它是确定性的,且 AI 智能体可以检查一个 intent 究竟解析成了什么。我对在漂移或对抗性使用下是否仍能保持这一点不太确定。
有些我确实没有答案:
以及对这一切的一个诚实警告:我相对来说是 CLI 设计的新手,而本文档中还有更多我不确定的地方。