智能编程 CLI 拆解:循环、上下文与权限
编程智能体 CLI 为何好用?拆解工具调用循环、上下文管理、权限模型与客户端/服务端设计。

模型才是简单的那部分。至少这是我的观点。过去几年我看着十几款智能编程 CLI 崛起、分叉、又栽跟头,现在我觉得这话是对的:智能编程 CLI的成败取决于它的 harness(外围运行框架),而不是模型本身。OpenCode 2.0 本周发布,这是一款开源、基于终端的编程智能体,最近热度很高,正好可以借此把这个观点直接说出来。这次发布里真正有意思的,并不是接入了哪个新模型,而是客户端/服务端分离、支持热重载的插件、可后台运行的任务,以及重新设计的智能体循环。说白了都是底层基础设施。而基础设施本身就是产品。
我以前是做机器学习工程的,所以这个结论让我有点不爽。我本来希望模型质量才是决定性因素。但当我思考自己为什么留下或放弃某个工具时,原因从来不是“模型变聪明了”,而是“工具不再要求我批准每一条命令”,或者“它终于不用重启就能重新加载配置”,又或者“我可以把 GUI 挂到远程机器上运行的会话里”。下面我们来拆解真正决定编程智能体是否好用的三个子系统:工具调用循环、上下文管理和权限模型。最后,我也会说明这个反直觉的观点在哪些地方站不住脚。
工具调用循环是一个分布式系统
撇开营销话术,每个编程智能体本质上都是同一个程序:带着提示词和工具列表调用模型,接收文本或工具调用,执行工具,把结果追加进去,然后重复。这个循环听起来很简单,其实一点都不简单。那些把它当成简单东西的工具,在真实使用五分钟后就会显得很卡顿。
想想这个循环实际上必须处理什么。工具执行会失败:编译器报错,测试套件卡住,或者因为模型凭空捏造了路径,文件根本不存在。模型生成的工具参数可能是格式错误的 JSON。一次工具调用耗时四十秒,用户在运行中途按下 Escape。一轮里来了两个工具调用,其中一个在另一个失败时绝不能执行。任务执行到一半,上下文窗口塞满了,总得有什么东西要让位。这些问题本质上都是并发与故障处理的问题,而不是智能问题。我之前写过LLM 智能体本质上就是分布式系统,而编程 CLI 恰恰是最纯粹的例子:一个不可靠、非确定性的远程组件(模型)需要和本地有副作用的组件(shell、文件系统、LSP)协同工作,而这个循环就是它们之间的共识协议。
这也是为什么 OpenCode 2.0 据报道用 Effect 重构了它的循环,这一点很值得注意。Effect 是一个 TypeScript 框架,提供结构化并发和带类型的错误处理。这个选择说明团队不再把循环看成“里面塞了 fetch 的 while 循环”,而是把它当作它本来的样子:一个由一个个可中断、可组合、可重试的 effect 组成的系统,并且有明确的错误通道。当你的智能体循环具备真正的取消语义时,“用户按下 Escape”就成了一等事件,而不是把状态搞坏的意外。工具结果有了类型,你就能在执行前校验模型的输出。这和受限解码这类技术正好配合:通过限制模型的输出空间,让格式错误的工具调用根本不可能发生,而不只是事后被捕获。
客户端/服务端架构是没人宣传的功能
编程 CLI 做出的最重要的设计决定之一,就是智能体究竟是一个进程还是一项服务。最朴素的设计是:一个进程、一个终端,终端关了会话也就没了。大多数此类工具一开始都是这么做的,但这是天花板。OpenCode 的客户端/服务端模型(由一个长期运行的服务端持有会话,任意数量的客户端,包括 TUI、桌面应用和 Web UI,都可以连接上来)打破了这个天花板。实际意义是:终端关闭后会话依然保留;你可以在远程开发机上运行智能体,再用笔记本上的原生应用来操控它;多个界面还可以同时观察同一个会话。
如果你曾经通过 SSH 在虚拟机上跑过智能体,结果一次重连就把会话冲掉了,你就知道这不是锦上添花的功能,而是玩具和基础设施之间的分水岭。长时间运行的智能体应该和构建、测试套件归为一类:你启动它,离开去做别的,过一会儿再回来查看。OpenCode 2.0 的可后台运行任务也是同样的思路,让智能体不再是同步对话,而变成一个带聊天界面的任务执行器。
不过要说明一下,因为我的观点一向如此:服务端架构并不等于永久守护进程。有几位评论者怀疑地指出,每个 harness 突然都想在你机器上常驻一个后台进程。这种警惕是对的。我之前写过CLI 工具不该默认安装后台守护进程,我至今仍然这么认为。其实有一个很合理的折中方案:按需启动服务端,范围限定在项目目录,最后一个客户端断开时就关掉它。这样既保留了会话持久性和多客户端连接,又不用让一个常开的进程在你机器上“回家打电话”。OpenCode 2.0 中改为从会话所在目录而非服务端工作目录加载配置,这一点也说明他们明白:应该是服务端跟着项目走,而不是反过来。
上下文管理才是质量的所在
问任何一个实际用过的人,为什么智能体工作一小时后就“变笨”了,答案几乎从来不是模型退化,而是“上下文腐烂”。窗口里塞满了过时的工具输出、死胡同,还有四十轮之前相关、现在早已无用的文件全文。模型现在是在一大堆干草里做决策,而大海捞针式的召回能力,早在你触及名义上的 token 上限之前就已经下降了。
好的 harness 会把上下文当作一种带分配策略的预算,而不是一根管道。具体来说,需要几件事协同工作:
- 工具输出截断,并保留检索的逃生通道。一份 5000 行的测试日志,应该只把失败的那四十行放进上下文,再附上一条说明,告诉模型完整日志存放在哪个路径,需要时可以重新读取。由 harness 决定什么是重要的,由模型决定何时需要更多。
- 子会话与子智能体。“去看看这个仓库里认证是怎么实现的,然后汇报结果”这类任务,应该在隔离的上下文中运行,只返回一份摘要,而不是整个探索过程的记录。OpenCode 2.0 改进的子会话通信,正是这个思路。
- 保留意图的压缩。窗口填满时,总要有人把之前的对话总结一下。如果总结得很粗暴,用户的约束(“别动计费模块”)会和噪声一起被丢掉。好的压缩会原文保留决策和约束,只压缩叙述性的部分。
- 把配置和技能视为上下文,而非代码。项目级指令、插件定义的工具以及技能文件,都会占用窗口。热重载它们(又一个 2.0 的修复)很重要,因为在不重启会话的情况下修改智能体的上下文,正是你迭代智能体行为本身的方式。
这些都不是模型的智能,但它们决定了模型看起来有多聪明。在任何超过二十分钟的任务上,一个上下文策略粗糙的前沿模型,都会输给背后有严谨策略的便宜模型。

权限模型:决定采用与否的无聊功能
HN 讨论中有个问题,我认为是这个领域里最重要的产品问题:在“每条命令都要批准”和“yolo 模式”之间,有没有别的选择?全自动批准确实很危险:一个拥有 shell 权限的编程智能体,一轮之内就可能执行 rm -rf、git push --force,还会把你的环境变量外泄出去。全手动批准则确实不可用:每小时批准八十条命令,你不是在监督智能体,而是在当一个缓慢的人肉键盘中断。能做好中间地带的工具,才能赢得务实的用户。
中间地带是一个分级的权限模型,它的设计空间比大家想象的要成熟得多:
- 只读、写入与执行。读取几乎没有风险;写入应限定在项目目录树内;而执行才是策略最关键的地方。
- 带参数感知的白名单。
git status自动批准,git push则弹出确认。朴素的工具只匹配二进制名称,而好的工具会匹配动词和参数,因为npm test和npm publish用的是同一个二进制,但影响范围天差地别。 - 会话级授权。“在本次会话中始终允许这条命令”是现有权限功能中价值最高的一项。它能把七十次提示变成一次。
- 对不可逆操作的隔离。网络访问、项目目录之外的任何操作,以及涉及凭据的操作,都应该在结构上受到限制,而不只是弹出提示询问。
最后这一条正是大多数 harness 投入不足的部分,因为弹提示很容易,隔离却很难。但提示只是一个问题,模型希望你在周五下午六点能正确回答。对智能体的进程树做沙箱隔离,限制网络、限定文件系统挂载范围,无论模型或用户做了什么,整类故障都变得不可能发生。我在AI 智能体的隔离与监控一文中详细分析过这个权衡;简短地说,提示属于监控,沙箱属于隔离,而对于任何不可逆的操作,你需要的是隔离。CLI 生态正在慢慢学会这一点。学得最快的那些,将赢得用户的信任,让用户真正敢为那枯燥的 90% 开启自动批准。
可扩展性与自托管模型问题
2.0 的讨论还带来两个较小的教训,值得关注。第一:“一切皆插件”正在胜出。用户正在这些 harness 之上构建技能、配置编辑器和工作流钩子。那种让插件成为一等公民的 harness(支持热重载、能观察会话、能修改行为),会把社区的投入累积起来,而不是与之对抗。OpenCode 认真对待这一点的标志是:他们自己的 GitHub 应用通过复现问题并在评论中给出发现来分拣 bug 报告,本质上是用智能体来维护智能体。用户称赞的那种“自我意识”,正是插件架构带来的结果,而不是模型本身的结果。
第二:模型无关性比看起来要难,而且它是个上下文格式问题。有一位用户尝试一个很优秀的本地模型,它经过训练,习惯用 bash 发出命令,而那个 harness 却固执地期待类 JavaScript 风格的工具调用,两者根本无法互通。这就是每个智能体 harness 中隐藏的耦合:工具调用格式、系统提示词中的假设,以及模型的训练分布,必须彼此对齐。一个硬编码了某个模型家族约定的 harness,算不上模型无关,充其量只能算是容忍模型。如果本地模型要在编程智能体中发挥作用(出于成本和隐私的考虑,它们理应如此),harness 就需要可插拔的工具调用“方言”,而不仅仅是可插拔的 API 端点。
反直觉观点站不住脚的地方
我说模型是简单的那部分。现在我得承认它的边界。这个说法对 harness 的设计成立,也就是循环、上下文和权限,但在某些地方,模型确实是真正的瓶颈。工具使用的可靠性本身是在模型训练中形成的;一个无法稳定输出有效函数调用的模型,会让每个 harness 看起来都像坏的,而再多基于 Effect 的结构化并发也修不好一个和自己的 schema 对着干的模型。长时程的多步规划,依然能在单次会话中让你明显感觉到不同模型档次之间的差距。而且 harness 与模型的划分有保质期:随着模型把智能体行为吸收进训练中(原生工具使用、原生长时程规划),能力会从 harness 转移到权重里,今天巧妙的 harness 功能,明天就可能变成白费的功夫。构建 harness 的团队应该假设自己在搭的是迟早会被模型淘汰的脚手架。老实说,这恰恰是开源 harness 加插件生态的理由,因为它们靠的是社区,而不是一时的能力差距。
还有一个 UX 底线是任何架构都无法开脱的。有些用户试用了 OpenCode 2.0,仍然觉得它笨拙;另一些人抱怨启动慢,以及聊天历史数据库越来越臃肿。这些都不是深层次的问题,但正是它们让人在前十分钟就弃用一个工具,根本等不到那个优雅的智能体循环展示实力。打磨也是一种功能,而开源项目恰恰最常在这方面投入不足。
模型负责写代码,但决定你是否放心让它去写的,是 harness。
所以,作为一个看着这个领域反复折腾的人,我的务实建议是:选编程智能体,要像选数据库一样,而不是像挑选模型基准测试的冠军。看看循环的故障处理、上下文策略、权限的粒度,以及会话能否在终端关闭后保留。这些才是在下一次模型发布、让所有模型再次变得同样聪明之后,依然重要的部分。


