摘要把脚本交给 Agent 跑崩的往往不是代码是接口。这篇文章拆开一条命令的六个契约——非交互、输出分流、退出码、机器可读、错误可自修、危险操作可预演也讲清改造成本与不该改的场合。周一早上你把跑了几年的日报脚本丢给 Agent让它重新生成上周那份。它先不带参数跑了一遍报错。接着开始猜./report.sh --date 上周。脚本不认识--date但这个位置本来就是个透传参数于是上周两个字被当成日期塞进了查询条件。数据库没报错只是查不到东西。最后屏幕上是这么一行生成完成共 0 条。Agent 在总结里写日报已生成。你打开文件空的。脚本错了吗你自己天天跑从来没出过事。人看见0 条会停下来查Agent 不会——它看的是退出码和输出内容两个都在告诉它成功了。变的不是脚本是读脚本的人。你那条命令现在有两个读者一个坐在屏幕前一个在上下文里。我第一反应是包一层然后被官方文档按住给机器用的接口直觉答案是把命令包成 MCP server或者干脆写个 HTTP 接口入参出参都定死多干净。这个想法不算错但它忽略了一件事接口本身也要占地方。Anthropic 的成本文档里写得很直接来源Prefer CLI tools when available: Tools likegh,aws,gcloud, andsentry-cliare still more context-efficient than MCP servers because they don’t add any per-tool listing.MCP 的每个工具都要在上下文里挂一份 schema。你有十个工具就是十份清单每一轮请求都跟着走。命令行不用没调用的时候它不存在调用了才留下那一段输出。那 CLI 就白嫖了也没有。Anthropic 那篇讲会话成本的博客里有一句更扎心的话来源Add quiet flags to noisy commands, or run them in a subagent. Command output is added to the conversation just like a file, and stays there for the rest of the session.命令的输出会像文件一样进上下文而且一直待着直到这个会话结束。你那条命令吵一次后面每一轮都在替它付账。那到底该改哪儿答案不在包不包在给谁看。同一条命令人看的是报表Agent 要的是数据。这两样东西挤在同一个 stdout 里出事是迟早的。输出是给人读的还是给上下文读的官方文档里有个对比很说明问题。与其让 Agent 去读一万行日志自己找错误不如让 hook 先 grep 出ERROR那几行——上下文从数万 token 掉到数百。你的脚本是同一个道理。一张带边框、带颜色、带温馨提示数据仅供参考的表格人扫一眼就懂Agent 得从头读到尾还得猜哪一列是主键。粗算一下四十行表格、每行三十来个字符一千二百 token 起步同一个结果只给六个字段的 JSON两百 token 以内。这个数字是估算不是实测你就当量级看。真正要记住的是它的复利这段输出不是付一次是每一轮都付。也别把 JSON 当万能。字段名本身也花 token嵌套三层之后一份 JSON 可能比那张表格还长。省下来的不是格式这两个字是只给需要的字段。六个开关签的是同一份契约把一条命令改造成两个读者都能用落到手上是六件事。它们不是六条最佳实践是一份契约的六个面。契约人怎么用Agent 怎么用不做会怎样非交互缺参数时弹提示问一句没有 stdin只会挂住或乱猜任务卡死或者参数被猜错输出分流表格、进度、彩色日志都在屏幕上stdout 只认数据日志进 stderr把正在查询…当成结果解析退出码基本不看靠它决定继续、重试还是停失败了也当成功机器可读--json时能看平时不用默认就要结构化还要能选字段靠正则抠表格一改版就崩错误可自修看中文提示自己判断需要错在哪、怎么改的明确指引同一个错反复试危险操作可预演手抖之前还有一次机会需要--dry-run而不是--yes一条命令删完没有 undo六条里最容易搞反顺序的是前两条。分流该先讲——因为它决定后面所有输出长什么样。判据很短把 stdout 直接扔进jq能通就是数据不能通就是日志。进度条、彩色提示、正在导出…这些东西一旦混进 stdout调用方只能靠正则去猜哪一行才是结果——你改一次文案它就得改一次正则。剩下最容易被漏掉的是退出码。为什么偏偏是它因为人对退出码几乎无感而 Agent 只看它。我在本机试了个很常见的写法if[!-f$CONF];thenecho错误找不到配置文件$CONF请先运行 initexit0fi跑一下输出是那句中文错误提示echo $?是0。写脚本的人想着我都提示得这么清楚了但调用方拿到的信号是一切正常。反过来看gh的做法。它的 man page 里有一张自己的退出码表0成功、1出错、2命令被取消、4需要认证。留意那个2。在 shell 的惯例里2通常是用法错误127是命令找不到126是找到了但不能执行。gh把2定义成命令被取消等于告诉你一件事退出码不是你随手的返回值是给别人分支用的接口定下来就得一直守。再看--json。字段该由谁定gh issue list --json number,title,labels这种写法给了答案调用方点名你不点就不给再配一个--jq让调用方自己筛。这比一股脑输出全部 JSON省得多因为决定输出什么的是调用方不是脚本作者。还有一处细节容易滑过去gh issue list的--limit默认是 30。不写参数它就只给你三十条。这不是小气是给输出设了个上限——一次调用把上下文灌满后面几十轮都得背着它。所以机器可读不只是换成 JSON还得有界。宁可让调用方多调一次带--limit 200也别默认全量倾倒。一个 Java 后端会怎么改Java 里判断是不是人在看用的是System.console()。它跟 shell 里的[ -t 1 ]是一回事终端里为真管道和重定向里为假。booleanhumanSystem.console()!null!false.equals(System.getenv(REPORT_HUMAN));if(!hasDate(args)){System.err.println(missing --date, e.g. report --date 2026-10-19);System.exit(2);}if(human)printTable(rows);elseprintJson(rows);留那个环境变量是有原因的。System.console()在某些 IDE 的运行窗口里照样不为 null你本地跑得好好的一到 CI 就变了样。这种坑不值得争论谁对留个开关最省事。至于错误提示别写参数错误四个字就完事。Agent 需要的是一句能直接拿去改命令的话缺什么参数、正确格式长什么样、下一步跑哪条。--date不接受上周这种自然语言就把可接受的格式写进 stderr——它下一轮就不用再猜了。别把--yes当便利非交互这条最容易做歪。Agent 没有 stdin问它一句它就卡住所以缺参数直接报错退出是对的。但很多人顺手把--yes也加上了觉得这样才叫顺畅。没有人盯着的时候多给点权限是不是更省事短期看是长期看是在攒事故。--yes一开删库和生成报表走的是同一条路。十月初 Cursor 那批事故里有几起就是终端删除造成的——据一家安全厂商整理的论坛记录三月到十月至少有六位 Windows 用户丢了盘rmdir /s /q的引号一破路径塌到盘根。更麻烦的是从终端发起的删除不进 checkpoint没有撤销。所以非交互不等于无条件放行。我的做法是分两档幂等操作给--yes破坏性操作只给--dry-run先出一份将要删除什么的清单人点头了再执行。多一步但这一步是所有自动化里最便宜的一步。不是每条脚本都值得改说到这儿得泼一盆水。这六个契约全做齐成本不低。是不是每条脚本都得照这个改一遍不是。改造的钱花在兼容性上只有会被反复调用的命令才还得起。你要维护两套输出测试也得写两份错误码一旦发出去就不能改改一次就得升大版本--json的字段集合同样是兼容性包袱。一个只有你自己跑、输出从来不超过五行的清理脚本为它做这些属于过度设计。判据其实只有一条这条命令会不会被别的东西调用第二次。会——哪怕是半年后你写个定时任务来调那就值得改。不会就让它保持原样人看得懂比机器看得懂重要。换个接口还是同一件事这套契约不只属于命令行。你给 Agent 暴露的任何东西都在走同一条路只是换了张脸。HTTP 接口的版本最直白状态码就是退出码。200 里套一个code: 500的业务错误等于把exit 0那套又演了一遍调用方还是只能去猜 body 里的字段。换到 SDK异常信息就是那句错误提示。抛一个NullPointerException给调用方等于说你自己猜哪儿空了补一句哪个字段没初始化、该在哪一步赋值才是那份契约里的错误可自修。报表视图更隐蔽问题出在列名。col1、col2这种宽表人对着表头能推Agent 只能瞎猜把列名写成人能念出来的业务词同一份数据就同时供了两种读者。所以判据不用重记谁读它它就得说谁的语言。人读的那层可以留着只是别让它挡在机器前面。回顾总结你的命令现在有两个读者这是所有改动的起点。CLI 比 MCP 省上下文是因为它不挂常驻清单但命令的输出会一直留在会话里吵一次就付一路。六个契约里最容易被忽略的是退出码——报错却exit 0等于告诉 Agent 一切正常。--json让调用方点名字段比全量输出更省再给它一个默认上限别一次灌满。非交互要配--dry-run不能只配--yes否则顺畅的代价是不可撤销。换到 HTTP 接口、SDK、报表视图账是同一笔只是换了个地方还。判据只有一条这条命令会不会被调用第二次。回到那份空报表。它没坏它只是还在用只有人能听懂的方式说话。参考资料Anthropic, Manage costs effectivelyCLI 工具与 MCP 的上下文开销对比Anthropic, Maximizing the value of your Claude Code sessions命令输出进入会话的说明GitHub CLI, gh issue list 手册--json、--jq与退出码定义CybeDefend, Cursor YOLO Mode and Auto-Run in 2026终端删除与 checkpoint 的边界作者唐悦玮 | 从后端出发用 AI 拓展到全栈的工程师。