晓风博客

一片荒芜的地方

OpenCode 的精妙,不在模型,而在 Harness

如果你第一次接触 OpenCode 这类 AI Coding Agent,很容易先被表象带偏:它能读代码、改文件、跑命令、调用大模型,看上去像是“一个更强的聊天机器人”或者“命令行里的 Cursor”。但如果只看到这里,我们就错过了真正值得研究的部分。

OpenCode 真正精妙的地方,不是它接了哪个模型,也不是它把终端界面做得多顺手,而是它把“如何驾驭一个会写代码的模型”做成了一套工程系统。换句话说,模型只是发动机,真正决定这台机器是否能稳定跑起来的,是外面那层 harness:环境、工具、权限、状态、反馈回路、停止条件,以及错误处理。

这也是我读完《从写代码到驭代码:深度解读 OpenAI 的 Harness Engineering》之后,再回头看 OpenCode 源码时最强烈的感受:优秀的 agent 工具,本质上不是 prompt 的集合,而是一套围绕模型构建的控制系统。

先讲清楚:Agent 工具到底和聊天机器人有什么不同

普通聊天机器人做的事情很简单:你给它一段输入,它返回一段输出,最多再记一点上下文。整个交互是“问一句,答一句”的结构。

Agent 工具不是这样。Agent 的最小工作单元更像是一个小型执行循环:

  1. 接收目标
  2. 读取上下文
  3. 决定下一步动作
  4. 调用工具
  5. 观察结果
  6. 再决定下一步动作
  7. 满足停止条件后结束

这和“补全文本”已经不是一回事了。它需要的不只是模型推理能力,还需要一层外部系统来回答下面这些问题:

所以真正的 agent,不是“会说话的模型”,而是“被组织起来执行任务的模型”。

OpenCode 的整体架构:它不是一个终端程序,而是一套运行时

很多人第一次看到 OpenCode,会把它理解成一个终端里的 AI 助手。这个理解不算错,但太浅了。

从官方文档和当前源码看,OpenCode 的核心并不是某个单独界面,而是一个本地运行时。终端界面只是它的一种客户端形态。README 里明确强调了两件事:它是 provider-agnostic 的,同时它采用 client/server 架构。这两点其实已经把它和很多“单体式 AI 工具”区分开了。

如果把 OpenCode 拆开看,大致可以分成四层:

1. Client 层

包括 TUI、桌面端、Web 端、IDE extension。它们的职责主要是展示状态、接收输入、把用户意图送进运行时,再把运行时的事件流展示出来。

2. Runtime / Server 层

这是 OpenCode 的中枢。它负责启动服务、管理项目实例、暴露 HTTP API、通过 SSE 向前端推送事件。

也就是说,OpenCode 的界面不是直接“绑死在模型调用上”的。真正工作的,是背后这个运行时。终端只是一个驾驶舱,不是发动机本体。

3. Agent / Session 层

这里是最关键的循环:组织 prompt、组装消息历史、注入可用工具、接收模型流式输出、执行工具调用、记录补丁、决定是否继续、重试还是停止。

4. Capability 层

也就是各种能力模块:文件读取、编辑、写入、Shell、Web、LSP、权限系统、Provider 适配、数据库、插件、MCP 等。

这套分层看似普通,但它的精妙之处在于:OpenCode 没有把“AI”写死在某个地方,而是把它嵌进了一套运行时结构里。模型只是其中一个组件,真正的产品能力来自这些组件之间的协作。

Session Loop 才是 Agent 的心脏

如果只挑一个模块来看 OpenCode,我会先看 session processor。

因为 agent 工具最难的地方,从来不是“把 prompt 发给模型”,而是“拿到模型输出之后,系统要怎么接住它”。这就是 harness 的核心。

OpenCode 的执行循环大致是这样的:模型开始流式返回内容;返回的内容可能是普通文本,也可能是 reasoning,也可能是某个工具调用;一旦出现工具调用,系统就会把这次调用登记为一个独立 part,执行工具,把输出写回会话,再让模型继续往下推理;每个步骤结束时,系统还会记录 token、cost、finish reason,以及当前文件快照和 patch。

注意这里的关键词:不是一次请求一次响应,而是多步、流式、可中断、可观察的状态机。

这正是很多 agent 工具分水岭所在。一个玩具 agent 往往只能“看起来像在自动工作”,真正一进复杂项目就开始失控;而 OpenCode 这种系统之所以值得研究,是因为它没有假设模型总会聪明地停下来,而是在外层明确设计了整个执行回路。

组件职责:每一块都不是装饰

Agent:不是人设,而是权限包

OpenCode 内置了 build、plan、general 等 agent。

这件事表面看像模式切换,实际上代表了一个非常好的设计:agent 不只是 prompt 风格不同,而是权限、能力范围、默认行为都不同。

比如 plan 模式本质上是一个“只读规划代理”。它不是简单告诉模型“不要改文件”,而是在系统层真的限制了编辑能力和某些执行权限。这个设计非常关键,因为在 agent 场景里,光靠提示词说“请小心一点”是很脆弱的,真正稳的做法是把边界写成规则。

Provider:模型兼容层

OpenCode 支持 OpenAI、Anthropic、Google、OpenRouter、Bedrock、GitHub Copilot 等大量 provider。

这层的意义不只是“多接几个 API”,而是把模型差异收敛到统一接口上。不同 provider 的鉴权、header、stream 行为、tool call 能力、URL 规则都不一样,如果不做这一层抽象,整个系统会很快被供应商细节污染。

这也是为什么我说它的价值不在模型本身。OpenCode 的真正目标,是让 agent runtime 尽量不依赖某个单一模型的特性。模型会变,价格会变,能力会变,但 harness 应该尽量稳定。

Tool Registry:Agent 的行动器官

OpenCode 的工具系统里有 read、edit、write、bash、webfetch、websearch、codesearch、todo,甚至还有 LSP 工具和插件扩展入口。

重要的不是“工具多”,而是它把工具当成了一等公民:工具有注册表,有参数 schema,有执行结果结构,也有针对不同模型的工具策略切换。比如对某些模型优先启用 patch 风格编辑,对另一些模型则提供 edit/write,这其实是一种非常实际的模型调优:不是去想“怎样让模型更聪明”,而是去想“怎样给这个模型一个它更稳定用得好的控制杆”。

LSP:把文件级理解升级成语义级理解

这是 OpenCode 很容易被低估的一层。

很多 AI Coding Tool 的“理解代码”其实停留在 grep 和 read file。能用,但粗糙。OpenCode 直接把 LSP 接进运行时,让 agent 可以拿到 definition、references、hover、document symbol、workspace symbol、call hierarchy 这些语义能力。

这相当于把 agent 从“会在文本里找东西”,升级成“能顺着语言服务理解结构”。对大型项目来说,这种差别是本质性的。

Permission:给 agent 装刹车,不是事后念经

OpenCode 另一个非常精妙的地方,是权限系统不是 UI 层的礼貌提醒,而是 agent runtime 的一部分。

哪些工具允许用,哪些路径允许读写,plan 模式能不能改文件,外部目录是否允许访问,这些都可以通过规则集控制。换句话说,OpenCode 不是默认相信模型“会听话”,而是默认模型需要在轨道里运行。

这和 harness engineering 的思想非常一致:不要把关键控制逻辑寄托在模型自觉上。

Database / Session:状态是 agent 的记忆骨架

OpenCode 本地用 SQLite 持久化 session、message、workspace、project 等状态。很多人觉得“会话记录”只是附属功能,但对 agent 来说,持久化状态其实是运行时骨架的一部分。

因为一旦你希望系统支持撤销、重做、分享、回看工具调用、跟踪 patch、统计 token 和 cost,你就需要一套明确的状态模型,而不是在界面里临时拼接几段对话。

从 Harness 的角度看,OpenCode 做对了什么

读那篇 Harness Engineering 文章时,我最认同的一点是:真正的瓶颈已经不再是生成代码,而是如何让系统稳定地产生“可以接受的结果”。

用这个视角回看 OpenCode,会发现它做对了不少关键设计。

第一,它把“停止”当成了正式问题

很多人做 agent 时,默认模型会知道什么时候该停。但现实不是这样。模型非常擅长继续生成,却不天然擅长在正确的时机结束。

OpenCode 的 session loop 里,结束不是一个模糊感觉,而是一种明确状态:

这就是典型 harness 思维:停止条件不是 prompt 的附属说明,而是状态机的一部分。

第二,它认真处理错误,而不是把错误当作意外

在源码里能看到,它会区分:

对不同错误,系统策略也不同。有些要直接停,有些要退回用户,有些可以指数退避后重试,有些则应该触发上下文压缩。

这点非常重要。Agent 不怕犯错,怕的是系统把所有错误都等价化。只要错误处理不分层,最终结果一定是:要么过早中断,要么无意义重试,要么带着错误状态继续乱跑。

第三,它防止 agent 掉进 doom loop

这是我很喜欢的一个设计。

当系统发现最近连续几次调用的是同一个工具、同样的输入,它会认为可能陷入了循环,并触发额外确认。这个设计非常朴素,但极有价值。因为 agent 失控最常见的方式,不是突然做出惊天错误,而是开始在一个错误局部最优里稳定打转。

一个好的 harness,不是让系统永不犯错,而是让它在犯错时尽早暴露出“我卡住了”。

第四,它有上下文压缩能力

上下文窗口再大也不是无限的。复杂会话里,真正难的不是“存更多历史”,而是“在有限上下文里保留真正重要的信息”。

OpenCode 没有把这个问题完全甩给模型,而是把 compaction 做成专门机制,必要时甚至用专门 agent 处理。这很像工程里常见的思路:不要让主流程在超载时直接崩,而是提供一个降维整理的旁路。

第五,它把 patch 和 snapshot 纳入主流程

代码 agent 的一个现实问题是:你得知道它到底改了什么。

OpenCode 在 step 前后跟踪快照,必要时生成 patch part。这个设计带来的价值非常直接:可追踪、可回看、可撤销,也更容易做更高层的 review 和审计。

如果说模型负责“想怎么改”,那 snapshot/patch 系统负责“证明它改了什么”。这也是 harness 的组成部分。

普通团队真正该学的,不是把 OpenCode 原样抄回去

如果只把 OpenCode 看成一个开源的 Claude Code 替代品,那还是低估它了。它真正值得普通团队学习的,不是 UI、不是命令集,甚至也不是它支持多少 provider,而是这几种工程直觉:

1. 把知识写进系统,而不是留在资深工程师脑子里

agent 看不到的知识,就等于不存在。架构约定、目录分工、命名偏好、危险路径、执行边界,这些都应该是系统可读的。

2. 把审美变成规则,而不是 review 时临场发挥

“文件不要太大”“依赖方向要清晰”“日志要结构化”这些看似主观的品味,一旦能转成规则、权限、lint、schema 或工具限制,agent 的稳定性会立刻上一个台阶。

3. 把反馈做成回路

没有反馈的 agent 只能猜。文件系统、LSP、命令执行结果、错误信息、状态持久化、事件流,这些东西共同组成了 agent 的感官系统。

4. 把失控当成系统设计问题

一个成熟的 agent harness,不会假设模型永远正确。它会假设:模型会重复、会误判、会上下文溢出、会拿错工具、会在半正确状态下继续前进。系统的价值,恰恰在于如何把这些问题变成可控制、可恢复、可观察的行为。

最后:OpenCode 让我们看到的,不只是一个工具,而是一种工程范式

那篇讲 Harness Engineering 的文章有一句我很认同:软件工程的控制面正在上移。

OpenCode 正好提供了一个很好的开源样本,让我们看到这种“上移”在具体系统里会长成什么样:不是工程师突然不写代码了,而是工程师越来越多地在设计环境、规则、感官、回路和刹车系统,让模型在这套结构里稳定地产生代码。

从这个意义上说,OpenCode 最有价值的地方,并不是“它会不会比别的工具多写几行代码”,而是它提醒我们:在 agent 时代,真正稀缺的能力不是继续手搓每一行实现,而是把判断力、边界感和工程品味铸造成系统能力。

模型是会越来越强的。

但真正拉开差距的,往往不是谁拿到了更强的模型,而是谁先学会了给模型装上方向盘、仪表盘、刹车和保险丝。

阅读更多