01 上下文源代数与统一组合 本节摘要:大多数 AI 应用给模型的「系统提示」就是一段写死的字符串。OpenCode 不这么干——它把喂给模型的初始指令做成一套结构化的事实集合(System Context),由许多个独立、可观测、有稳定标识的「上下文源(Context Source)」组合而成。本节讲清这个最小构建单元:一个上下文源长什么样(稳定 key、编解码、loader、纯渲染器)、如何用统一接口组合不同类型的源、有哪些内置源。理解了「上下文源」这个代数,你才能理解后续的纪元、快照、中途消息——它们全建立在这个最小单元之上。 一、为什么不是「一段字符串」 先想清楚传统做法的问题。如果把系统提示写成一段字符串: 改一处要全文重拼:想加一条「今天是周三」?得重新拼整段。
本节摘要:大多数 AI 应用给模型的「系统提示」就是一段写死的字符串。OpenCode 不这么干——它把喂给模型的初始指令做成一套结构化的事实集合(System Context),由许多个独立、可观测、有稳定标识的「上下文源(Context Source)」组合而成。本节讲清这个最小构建单元:一个上下文源长什么样(稳定 key、编解码、loader、纯渲染器)、如何用统一接口组合不同类型的源、有哪些内置源。理解了「上下文源」这个代数,你才能理解后续的纪元、快照、中途消息——它们全建立在这个最小单元之上。
先想清楚传统做法的问题。如果把系统提示写成一段字符串:
OpenCode 的解法是把系统提示拆成「上下文源(Context Source)」——每个源是一个独立的、可观测的事实单元,有自己的标识、自己的加载逻辑、自己的渲染逻辑。组合它们,就得到完整的 System Context。
每个上下文源有这几个必备部分:
| 部分 | 作用 |
|---|---|
| 稳定 key | 唯一标识,如 core/environment、core/date、core/instructions |
| 编解码(codec) | 把源的值序列化/反序列化 |
| loader(加载器) | 不可失败的加载逻辑(取这个源当前的事实) |
| 纯渲染器 | 把值渲染成给模型看的文本(baseline / update / removed 三种) |
上下文源 = { key: "core/environment", codec: ..., loader: () => 当前环境信息, // 不可失败 render: { baseline, update, removed } // 纯函数 }
几个关键点:
💡 「纯」的价值:loader 和渲染器都是纯的,意味着同一个源在同一个事实下永远渲染出同一段文本。这种「可重现性」是后续纪元、缓存的基础——只有纯,才能稳定。
不同上下文源的「值类型」可能不同(环境信息是对象、日期是字符串、指令是文本......),但 OpenCode 用一套统一接口把它们包起来,使不同类型的源能被一致地组合:
源A(环境,值类型=对象) ─┐ 源B(日期,值类型=字符串) ─┼─► SystemContext.combine([A, B, C]) ──► 统一的 System Context 源C(指令,值类型=文本) ─┘
这种「把不同类型的东西用统一接口包装、然后用代数运算组合」的设计,就是本节标题里的「代数」。它带来的好处:
OpenCode 自带一批上下文源,覆盖最常见的环境事实:
| 源(key) | 提供什么 |
|---|---|
core/environment |
工作目录、Git 状态、平台 |
core/date |
当前日期 |
core/instructions |
指令(Agent 角色说明、用户配置的指令) |
core/skill-guidance |
技能指引(第 11 章技能作为上下文源) |
每个都是独立、可观测、有稳定 key 的源。你可以把它们想象成「模型的环境感知器官」——模型通过这些源知道自己在哪、什么时候、该遵循什么规则、有哪些技能可用。
⚠️ 注意:这些源不是一成不变的——对话进行中它们可能变化(你切了 Git 分支,环境源就变了)。怎么安全地把变化告诉模型,是第 03 节「对话中途系统消息」的主题,也是整个 Epoch 机制要解决的核心问题。
把多个源组合起来,就得到完整的 System Context——也就是「这一回合模型看到的初始事实集合」。注意,它不是字符串,而是结构化的、由源组合而成的事实集合;只有在最后喂给模型时,才会被渲染成文本。
组合(System Context) │ ├─ 结构化事实集合(内部表示) │ ▼ 渲染 给模型的初始文本(含各源的 baseline)
这种「结构化优先、渲染在后」的设计,是后续所有高级机制(纪元、快照、中途消息、协调状态机)的地基。没有它,那些机制都无从谈起。
知道了源怎么组合,下一步问题来了:组合出来的 System Context 怎么保持稳定(让模型缓存不失效)?这就是「纪元三件套」——下一节。