03 对话中途系统消息与安全回合边界 本节摘要:基准稳定能处理「缓慢变化」,但有个场景它搞不定——对话进行中某个上下文源突然变化(你切了 Git 分支、加了新指令),模型必须马上知道,但又不能破坏正在进行的回合。OpenCode 的解法是对话中途系统消息(Mid-Conversation System Message):不重写历史、不改基准,而是在一个安全回合边界插入一条按时间顺序的系统消息,既让模型知道事实变了,又不破坏厂商签名。这是全书最精巧的设计之一,本节把它讲透。 一、场景:对话进行中事实变了 设想这个场景: 问题来了:怎么让模型知道? 改基准? 不行——基准是纪元内冻结的,改了会废缓存,而且模型可能已经「记住」了旧基准。 重写历史?
本节摘要:基准稳定能处理「缓慢变化」,但有个场景它搞不定——对话进行中某个上下文源突然变化(你切了 Git 分支、加了新指令),模型必须马上知道,但又不能破坏正在进行的回合。OpenCode 的解法是对话中途系统消息(Mid-Conversation System Message):不重写历史、不改基准,而是在一个安全回合边界插入一条按时间顺序的系统消息,既让模型知道事实变了,又不破坏厂商签名。这是全书最精巧的设计之一,本节把它讲透。
设想这个场景:
回合 3 进行中(模型正在思考) │ ▼ 你突然切换了 Git 分支(环境源变了) │ ▼ 下一回合(回合 4),模型必须知道「分支变了」
问题来了:怎么让模型知道?
OpenCode 的做法是:不改基准、不改历史,而是在对话流里插一条新的系统消息。这条消息告诉模型「事实变了」,它出现在对话的「中途」,所以叫「对话中途系统消息」。
对话流(时间顺序): 用户消息1 助手消息1 用户消息2 助手消息2 ── 系统消息(中途):「环境变更:分支从 main 切到 dev」 ──◄ 插入 用户消息3 助手消息3(模型已知道分支变了)
关键特性:
这条消息不能随便插,必须等一个安全回合边界。什么是安全边界?它是:
提升输入(steer 等)与工具结算都完成、但下一次模型调用还没开始的那个时间点。
回合 N 内部: 组装 → 调模型 ──► 流式返回 ──► 工具结算 ──► [安全边界] ──► 回合 N+1 调模型 ▲ 中途系统消息在这里插入
为什么必须等安全边界?两个硬理由:
如果在模型调用中途插消息,等于「回合还没结束就塞了新东西」,破坏了 V2 的核心不变量(第 7 章)。安全边界是回合之间的「间隙」,在那里插才不破坏不变量。
如果工具还在执行(比如正在改文件),这时插「环境变了」的消息会乱套——工具结果可能基于旧环境,但消息说环境变了。必须等工具结算完,在新边界插,语义才一致。
⚠️ 记住:中途系统消息不是「立刻插」,而是「等到下一个安全边界插」。这个「等」不是延迟,而是保证语义正确的必要条件。
中途系统消息强调「按时间顺序」插入,意思是:如果多个源在差不多时间变了,它们的中途消息按变化发生的时间顺序排列。
为什么这个顺序重要?考虑:你先切了分支(环境源变),又加了新指令(指令源变)。如果消息顺序乱了(先说「指令变了」再说「分支变了」),模型可能误解因果。按时间顺序排列,模型看到的就是「真实发生顺序」,语义正确。
变化发生顺序:分支变 → 指令变 │ ▼ 中途消息按此顺序排列 系统消息1:「分支从 main 切到 dev」 系统消息2:「新增指令:遵循某某规范」
退一步看,这套设计解决了几个看似无解的矛盾:
| 矛盾 | 解法 |
|---|---|
| 要让模型知道变化,又不能改基准 | 插中途消息,基准不动 |
| 要让模型知道变化,又不能改历史 | 中途消息是「新消息」,不碰旧消息 |
| 要及时,又不能破坏回合 | 等安全边界再插 |
| 多个变化,语义要不乱 | 按时间顺序排列 |
它把「让模型知道事实变化」这件事,从「改写过去」变成了「追加现在」——这是一个非常干净的工程选择:不动历史,只追加。
最后澄清中途系统消息和纪元的关系。它们是协作关系,不是替代:
很多时候,一个变化会先走中途消息(立刻通知模型),如果变化足够大需要换基准,再开新纪元。两者各管一类演化,共同保证 System Context 既稳定又及时。
💡 类比:纪元像「换地图」(整张地图变了,发新地图),中途消息像「路上提示」(地图没换,但前面有个新情况,告诉你一声)。旅行中两种信息都需要。
中途消息解决了「何时插」,但还有一个问题:有些变化能不能立刻生效,得看状态——下一节讲「协调状态机」。