02 V2 的核心分离:持久化录入 vs 模型执行 本节摘要:V1 把所有事塞在一个循环里,简单但难 durable、难并发。V2 做了一次刻意的架构演进,核心动作就一个——把「用户提示如何进入系统」(录入)与「模型如何执行这一回合」(执行)彻底分开。听起来只是拆分,但它换来的是质变:录入变成 durable(不会丢)、可恢复、可并发、可重放。本节讲清这次分离:什么是 durable 录入(收件箱模型)、为什么分开换来这些好处、以及「事件溯源」这个关键词到底意味着什么。读懂本节,你就读懂了 V2 的灵魂——也是 OpenCode 内核正在长成的样子。 一、先看清 V1 的耦合在哪 要理解 V2,先精确指出 V1 的问题在哪。
本节摘要:V1 把所有事塞在一个循环里,简单但难 durable、难并发。V2 做了一次刻意的架构演进,核心动作就一个——把「用户提示如何进入系统」(录入)与「模型如何执行这一回合」(执行)彻底分开。听起来只是拆分,但它换来的是质变:录入变成 durable(不会丢)、可恢复、可并发、可重放。本节讲清这次分离:什么是 durable 录入(收件箱模型)、为什么分开换来这些好处、以及「事件溯源」这个关键词到底意味着什么。读懂本节,你就读懂了 V2 的灵魂——也是 OpenCode 内核正在长成的样子。
要理解 V2,先精确指出 V1 的问题在哪。在 V1 里,「用户输入消息」和「跑这一回合」是同一件事——你输入消息,循环立刻开始跑,输入和执行绑在一起:
V1:输入即执行 用户输入 ──► [循环立刻开始:组装→调模型→工具→续跑]
这种绑定带来三个麻烦:
V2 的解法是引入一个持久化的收件箱(inbox)。用户输入不再立刻触发执行,而是先被「录入」到这个收件箱(一条持久化记录),执行引擎再从收件箱取出来跑:
V2:录入与执行分离 用户输入 ──► [录入:durable 收件箱,一条持久化记录] │ ▼ [执行引擎:从收件箱取,跑回合]
这两步现在是独立的——录入只负责「把输入安全地记下来」,执行只负责「拿输入跑模型」。它们之间通过收件箱解耦。
💡 关键认知:在 V2 里,「用户输入了一条消息」和「这条消息被执行了」是两件独立的事,各有各的记录。这正是「事件溯源(event sourcing)」的味道——系统的状态由一系列事件推导出来,而不是被就地修改。
把录入独立出来、做成 durable,换来四样实在的东西:
录入第一步就是落盘(写一条持久化记录)。即使执行引擎还没开始跑就崩了,输入也已经在盘上了,重启后能继续。这对长任务、不稳定环境是刚需。
因为录入和执行都基于持久化记录,「从某个状态恢复」就是「把记录重放到那个点」。V1 里进程崩了状态可能丢失,V2 里状态永远能从事件重建。
录入和执行解耦后,并发变得自然:多条输入可以先进收件箱排队,执行引擎按规则协调(下一节的协调器)。同一会话的并发、不同会话的并发,都能被妥善处理。
想「如果当时换个模型会怎样」?V2 里你可以拿同一份录入记录,换条执行路径重跑——因为录入是「发生了什么」的客观记录,执行只是「怎么响应它」的一种可能。
V2 的设计带有明显的「事件溯源」特征。简单说,事件溯源的核心是:
系统不存「当前状态」,而是存「发生过的事件序列」;当前状态由事件序列推导出来。
对照 V2:
| 事件溯源概念 | V2 对应 |
|---|---|
| 事件(event) | 用户提示被录入、回合开始、工具被调用、工具返回、回合结束...... |
| 事件存储 | durable 收件箱 + 会话事件流 |
| 状态推导 | 会话当前状态由事件序列重建 |
这不是说 V2 是教科书式的事件溯源,而是它借用了这套思想来解决「durable + 可恢复 + 可重放」的问题。第 8 章的 Context Epoch 也依赖这种思路——纪元的边界由事件决定。
「录入先落盘,再执行,多一步,不会慢吗?」——这是一个合理的疑问。答案是:多了一次落盘,但换来的是 durability,这笔账划算。
⚠️ 别误读:V2 不是「所有事都变慢了」,而是「录入多了一步落盘,执行可以更自由地调度」。整体上,V2 在 durability 和并发上的收益远大于那一部落盘开销。
最后强调一点:V2 不是把 V1 推倒重来,而是把 V1 的循环拆开、把录入独立成 durable。V1 里那些「组装系统提示、调模型、处理工具、续跑」的逻辑,在 V2 里依然存在,只是它们现在跑在一个更健壮的骨架上(执行引擎),而录入不再和它们绑死。
这种「保留核心逻辑、升级骨架」的演进方式,是大型项目重构的常态——也是为什么 V1 和 V2 能并存一段时间(迁移期)的原因。
录入和执行分开后,一个新问题来了:多条输入并发进来、执行引擎要不要排队?谁来协调?这正是下一节的「回合协调器」。