提供方切换与上下文交接
提供方(provider)切换是 V2 的一等功能。一条应用对话线程(thread)可以包含来自多个提供方的运行,同时保留每个提供方的原生对话句柄,以及应用的规范对话历史。
本文描述更广义的对话线程谱系与上下文转移模型在提供方切换上的特化。提供方切换是一种上下文转移,源应用对话线程与目标应用对话线程通常是同一条。分叉(fork)、合并回去(merge-back)和子智能体使用同一套源点与解析概念,但产品生命周期不同。
建议
回到先前用过的提供方(provider)时,默认恢复该提供方先前的提供方对话线程(thread),并注入一份增量摘要,覆盖该提供方处于非活动期间发生的那些运行。
只有在恢复先前的提供方对话线程失败、设置不兼容,或增量交接很可能得到很差的上下文时,才新建一条提供方对话线程,并附上完整的应用对话线程摘要。
这是最好的默认权衡:
- 保留特定于该提供方的既有工作的原生连续性,
- 避免反复把全部内容再摘要一遍,
- 让每个提供方保留自己的工具与历史能力,
- 让跨提供方的上下文明确且可审计,
- 在旧的提供方上下文过时时,留一条干净的回退路径。
示例
runs 1-5: Codex, ProviderThread C1
switch to Claude for run 6
-> ContextHandoff H1 covers runs 1-5
-> create ProviderThread L1
-> run 6 uses L1 with H1
runs 6-8: Claude, ProviderThread L1
switch back to Codex for run 9
-> ContextHandoff H2 covers runs 6-8
-> resume ProviderThread C1
-> run 9 uses C1 with H2不应指望 ProviderThread C1 会原生包含 Claude 的运行。它包含的是 Codex 的原生对话,加上用来概括外部进展的显式交接产物。
为什么不总是新建提供方对话线程?
总是新建提供方对话线程(thread)更简单,但会失去有用的提供方(provider)原生连续性:
- 只存在于该提供方对话线程中的先前隐藏推理与上下文,
- 提供方一侧的对话线程元数据,
- 在可得时的原生工具状态,
- 特定于该提供方的行为连续性。
这还会迫使每一次切换都依赖完整摘要的质量。完整摘要是有用的回退产物,但只要先前的提供方对话线程可以安全恢复,就不应当成为默认做法。
为什么不能不带摘要就恢复?
因为在该提供方(provider)处于非活动状态时,应用对话线程(thread)已经继续向前。恢复后的提供方不会知道另一个提供方做了什么。于是会出现错误的答案、过时的计划,以及对文件的不安全假设。
回到某个提供方时需要一份交接摘要,除非自该提供方上次参与以来没有发生过运行。
提供方对话线程的覆盖范围
提供方(provider)对话线程(thread)具有原生覆盖和交接覆盖。
type ProviderThreadCoverage = {
providerThreadId: ProviderThreadId;
nativeRunOrdinals: number[];
handoffIds: ContextHandoffId[];
};对于上面的示例:
C1 native runs: 1,2,3,4,5,9
C1 handoffs: H2 covering 6-8
L1 native runs: 6,7,8
L1 handoffs: H1 covering 1-5应用对话线程仍然是完整历史的事实来源。
交接产物
交接是显式的图数据。在更广义的模型中,ContextTransfer 记录源、目标、关系类型和解析状态。ContextHandoff 是已物化的可移植上下文产物,用在无法靠提供方(provider)原生连续性满足这次转移的时候。
type ContextHandoff = {
id: ContextHandoffId;
transferId: ContextTransferId;
threadId: ThreadId;
targetRunId: RunId;
fromProviderThreadIds: ProviderThreadId[];
toProviderThreadId: ProviderThreadId;
coveredRunOrdinals: { from: number; to: number };
strategy:
| "delta_since_target_last_seen"
| "full_thread_summary"
| "checkpoint_summary"
| "manual_context";
summaryText: string;
status: "pending" | "ready" | "failed" | "superseded";
createdByProvider: ProviderKind | null;
createdAt: string;
updatedAt: string;
};交接应当链接到消费它的那次运行。如果同一份摘要被重新生成,旧的交接会被取代,而不是被悄悄改写。
切换流程
start run with target provider P
-> find current app thread history
-> find best ProviderThread for P
-> decide handoff strategy
-> generate ContextHandoff if needed
-> ensure ProviderSession for P
-> ensure/resume/create target ProviderThread
-> send provider turn with handoff context + user message用户消息仍然是这次运行的实际输入。交接上下文是另外一段上下文;按提供方(provider)能力,它可以是前言、system、developer 或合成用户上下文。
策略选择
建议的策略顺序:
- 不交接:目标提供方(provider)对话线程(thread)已经覆盖当前的应用运行。
- 向已有提供方对话线程做增量交接:目标提供方对话线程能够恢复,只是错过了一段运行范围。
- 向已有提供方对话线程做完整线程交接:目标提供方能够恢复,但增量链过于复杂。
- 向新的提供方对话线程做完整线程交接:目标提供方恢复失败、设置不兼容,或旧上下文已经过时。
- 不支持:提供方无法接受交接上下文,而且不存在安全的重建路径。
过时策略
应用应当能够判定:再回到一条旧的提供方(provider)对话线程(thread)已经不再合适。
可能的过时信号:
- 自上次使用以来,发生在该提供方之外的运行过多,
- 串联起来的交接过多,
- 目标提供方的模型或运行时模式发生了不兼容的变更,
- 提供方对话线程恢复失败,
- 先前的提供方对话线程被回滚,或以互相冲突的方式分叉,
- 用户选择了「干净上下文」,
- 交接摘要会超出提供方的上下文策略。
一旦过时,就用 full_thread_summary 新建一条提供方对话线程。
摘要来源
摘要可以由以下来源生成:
- 正在离开的提供方(provider),
- 正在进入的提供方,
- 配置好的摘要提供方,
- 面向结构化产物的本地确定性摘要器。
生成摘要的不一定是目标提供方。交接会记录 createdByProvider,以便日后检查质量与出处。
摘要应当包含什么
摘要应当以应用对话线程(thread)为规范,而不是只依据提供方(provider)的对话记录。
应当包含:
- 用户的目标与约束,
- 已经做出的决定,
- 更改过的文件,
- 运行过的命令(command)与测试,
- 计划与待办状态,
- 尚未解决的审批与提问,
- 当前的检查点(checkpoint)与差异摘要,
- 已知的失败,
- 相关的子智能体结果。
不要包含:
- 无关的提供方协议噪声,
- 隐藏的思维链,
- 已被后续运行取代的过时工具输出,
- 在文件摘要已经足够时仍放入大段差异。
与检查点的交互
提供方(provider)切换并不改变检查点(checkpoint)的归属。检查点仍然附着在应用运行上。
不过,生成交接时应当引用最新的检查点和差异摘要,让下一个提供方看到正确的工作区状态。
run N completed
-> checkpoint N captured
switch provider for run N+1
-> handoff includes summary through checkpoint N与回滚的交互
回滚会使那些覆盖了已回滚运行的交接失效。
rollback to run 5
-> runs 6+ marked rolled_back
-> handoffs covering 6+ marked superseded
-> provider threads whose native/handoff coverage includes 6+ marked divergent or rolled back by capability如果当前提供方(provider)支持提供方回滚,就调用它。否则,在工作恢复时,根据保留下来的应用对话线程(thread)摘要新建一条提供方对话线程。
与分叉的交互
从混合了多个提供方(provider)的应用对话线程(thread)分叉时,应当记录应用对话线程的谱系和一个源点,分叉当时不必选择提供方。提供方上下文在该分叉上的第一次运行开始时惰性解析。
示例:
- 从 Codex 上的第 5 次运行分叉,且分叉后的第一次运行使用 Codex:若源引用足够强,优先使用 Codex 的原生分叉。
- 从 Codex 上的第 5 次运行分叉,且分叉后的第一次运行使用 Claude:在首次分发时惰性构建可移植上下文。
- 从没有提供方原生对话线程的应用检查点(checkpoint)分叉:用检查点与完整线程上下文创建目标提供方对话线程。
- 从子智能体的提供方对话线程分叉:若该线程稳定,就直接使用子智能体的提供方对话线程,否则回退到可移植上下文。
数据模型变更
提供方切换需要:
Run.providerRun.providerThreadIdRun.contextHandoffIdContextTransferProviderThread.firstRunOrdinalProviderThread.lastRunOrdinalProviderThread.handoffIdsContextHandoff- 支持交接的提供方(provider)能力
应用不必为了提供方切换而另建对话线程(thread)。它需要把多条提供方对话线程挂到同一条应用对话线程上。