我有一个代码代理——Claude Code,每月与我的问题跟踪工具Linear交互大约800次:列出任务、创建问题、更改状态、发表评论。我检查了其中165次会话,并统计出了超过500个错误和超过370次重试。
这些错误没有一个是Linear的API故障引起的,全部都是接口错误:代理与命令行通信,但命令行无法理解。
保守估计,每月大约浪费了70万个token仅仅是与工具“斗争”:重试、读取错误信息、纠正再尝试。这种无形的成本不会出现在任何账单中,但却存在于每次会话中。
背景:代理现在是主要用户
命令行工具(CLI)——从历史上看,是为人类设计的。而人类用户是非常灵活的。如果一个命令失败了,他们会通过 --help 查看帮助。如果错误信息很难理解,他们会去查阅文档。如果该工具有某些特性,他们会记住并避免再犯同样的错误。
而AI代理基本不会这么做。它在不同会话之间不会积累经验,也不会投入太多精力去阅读文档。当某些操作失败时,它也不会深入分析问题所在,而是根据认为最有可能的方式尝试再次执行。
这改变了你的工具客户是谁的定义。如果代理每月调用该工具800次,而你手动使用它只有三次,那么这个接口的主要使用者就是代理。为了人类用户设计工具并期待代理能适应,这是在为次要用户优化。
为这个主要用户专门设计有一个名字:智能代理体验(agentic experience)。它之于代理,正如用户体验(UX)之于人类用户,开发者体验(DX)之于API的开发者。更重要的是,一种最有效的测量工具早已存在——代理的错误日志。
错误有其规律
我将所有以非零退出代码结束的CLI调用定义为错误。根据这一标准,这500多个错误并非随机发生:几乎全部集中在三个模式中。
| 错误模式 | 代理行为 | 设计暴露的问题 |
|---|---|---|
| 虚构的标志 | 输入 --status 而非 --state;--priority urgent 而非 --priority 1 | 真正的标志不是人们会优先猜到的名称 |
| 缺少的操作过程 | 尝试搜索文本、按项目筛选或在创建时分配项目:CLI不具备这些功能 | 工具未涉及实际的工作流程 |
| 忘记必要的标志 | 忽略 --sort,--no-pager,--no-interactive | 要求人工决策,而工具本可以自动处理 |
当代理输入 --status 时,它并非胡乱猜测,而是在推测出最可能的接口名称。--status 和 --state 都非常合理。而我设计的CLI并未与这种直觉一致。
还有一个问题没有反映在统计数据中,因为它不会导致命令失败:冗长的输出。CLI以JSON格式返回列表,每个问题大约50个token。如果命令成功返回(退出代码为零),这类问题就不会被统计为错误;但长列表和每月800次调用叠加起来,这就是那70万个token成本的另一半。这种成本往往被忽视,因为不会妨碍任务完成。
重新审视错误
容易的解释是直接了当的:代理在错误使用工具。这是错误的解释,应逐一反驳。
虚构的标志表明真正的标志名称不够直观。忘记的必要标志表明这些标志完全不应该是必要项:如果工具可以推测出合理的默认值,那么强制要求填写这些标志只会额外增加使用者的工作量。一段没有用的输出或者产生过多token意味着选错了应对的输出格式。
代理的错误日志不是错误列表,而是一个规范: 每个错误都“否定性”地描述了你原本该如何设计接口。而这个规范是你最真实的反馈:无需代价,无数的真实数据,且没有人为善意隐藏工具缺陷。一个人类用户遇到糟糕的CLI时,可能会保持沉默并自动适应它。但代理不会适应:它会一遍又一遍地犯同样的错误,并记录下每一次。
这与我在另一篇文章中提出的一个原则有关:不应允许错误路径存在,而非仅仅禁止其使用。禁止只是一种文档说明——“不要使用--status“——而文档取决于是否有人认真阅读。让错误变得不可能,是设计的职责。
因此,解决方案并非更好的文档,而是更好的工具。
重设计
我围绕这个原则重写了CLI,工具叫做lql。以下四个设计决策贯穿整个改进过程。
宽容优于拒绝。 如果代理输入了 --status,工具会将其接受为 --state 的别名并继续运行。如果输入了 --priority urgent,将其解析为 --priority 1 并说明假定的结果。最常见的“错误”路径直接被转变为正确路径。工具不会因为合理的猜测而惩罚用户,而是直接适应它。
通过错误信息指导正确方向。 当某个功能确实不存在时,错误信息不会简单地说 unknown flag,而是给出具体指导:--filter 不存在。要按状态筛选,请使用:--state <状态>。要搜索,请使用:lql search "文本"。 错误消息本身变成了文档,并且恰好呈现在用户最专注的时间点:他们刚刚失败时。
取消所有强制标志。 lql list 无需提供任何参数即可运行:默认按优先级排序,筛选出活跃状态,并根据工作目录自动检测团队。因为不存在 --sort 参数,自然也就没有遗漏它的可能性。一个代理无法忘记不存在的标志。
为实际用户定制化输出。 读取输出的是真正需要付费的LLM。该工具采用了 TOON(Token-Oriented Object Notation),一种紧凑的格式,使用头部编码一次性定义结构,随后以位置形式输出值。
| 格式 | 每条任务的tokens数 | 50条任务所需tokens |
|---|---|---|
| XML | ~70 | ~3,500 |
| JSON | ~50 | ~2,500 |
| TOON | ~25 | ~1,250 |
TOON并不是我自己发明的:这是一个公开的格式(toonformat.dev),我的工具只是选择采用它。--json 标志仍然可用,专为脚本和传统机器服务保留。
测试
最有说服力的测量结果并不是性能基准测试,而是以下这个结果。
为了让代理正常操作Linear,它需要一份操作指令文件(在Claude Code中被称为“技能”)。用旧的CLI时,这份文件有246行。其中150行是各种“权宜之计”:如果发生这种情况,就做另一种处理,“记得加上此标志”,“不要使用这种方式”。这些防御性的文档是为了弥补工具的不足。
重新设计后,文件减少到205行,并且没有任何权宜之计。一个宽容的工具无需任何辩解。这150行不是被我删除了,而是因为已经不再需要记录任何信息而自然消失。
本文的局限性
为了客观公正,定义范围很重要。这篇文章关注的是一个代理(Claude Code)和一个API(Linear)。三种错误模式的确切分布在其他代理或其他API中可能会有所不同。但问题的本质——代理在推测最可能的接口时与实际设计发生不一致——我相信不会改变,尽管这只是一个假设而非数据所证实的事实。
超过500个错误和370次重试的统计数据来源于对Claude Code 165次会话的JSON文件的解析。错误的定义已经给出——非零的退出演示代码;重试则是之前失败后重新执行同一命令。标准是机械且可重复的:退出代码无需解释,尽管计数来源于实际使用,并非受控实验。
尝试一下
lql 是一个基于 MIT 许可的开源软件。代码托管在 github.com/frr149/lql。
brew install frr149/tools/lql
lql list --team PROD --state Todo --priority urgent
如果你将代理连接到CLI该怎么做
如果你的AI代理需要调用一个命令行工具——不论是你开发的还是第三方的——那么你已经拥有改进它所需的数据:代理在使用它时犯下的错误。
不要将这些视为日志中的噪音或是代理的缺陷。提取这些错误,按模式归类,并将它们解读为它们本质上是:你的接口蓝图,用负面表现形式绘制出来。每个代理虚构的标志是对真实标志命名的建议;每个失败而不存在的操作则是对功能的请求。
本文原文为西班牙语,借助AI翻译。