跳转至

Provider 切换与上下文交接

Provider 切换是 V2 的一等公民功能。应用线程可以包含来自多个 Provider 的运行,同时保留每个 Provider 的原生对话句柄以及应用的规范对话历史。

本文档描述更广泛的 线程谱系与上下文转移 模型中 Provider 切换的特化。Provider 切换是一种上下文转移,其源应用线程和目标应用线程通常是同一个线程。Fork、merge-back 和子代理使用相同的源点/解析概念,但具有不同的产品生命周期。

推荐方案

当切换回之前使用过的 Provider 时,默认恢复该 Provider 之前的 Provider 线程,并注入一份增量摘要,覆盖该 Provider 非活动期间发生的运行。

仅在以下情况下创建带有完整应用线程摘要的全新 Provider 线程:恢复之前的 Provider 线程失败、设置不兼容,或增量交接可能产生较差的上下文。

这提供了最佳默认权衡:

  • 保留 Provider 特定先前工作的原生连续性,
  • 避免反复对所有内容进行摘要,
  • 让每个 Provider 保持自己的工具/历史能力,
  • 使跨 Provider 的上下文显式且可审计,
  • 在旧的 Provider 上下文过期时保留清晰的回退方案。

示例

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 的原生对话,以及总结外部进展的显式交接产物。

为什么不一律创建全新的 Provider 线程?

始终创建全新的 Provider 线程更简单,但会丢失有用的 Provider 原生连续性:

  • 仅存在于 Provider 线程中的先前隐藏推理/上下文,
  • Provider 端的线程元数据,
  • 可用的原生工具状态,
  • Provider 特定行为的连续性。

它还迫使每次切换都依赖于完整摘要的质量。完整摘要是有用的回退产物,但当之前的 Provider 线程可以安全恢复时,不应将其作为默认方案。

为什么不能在没有摘要的情况下恢复?

因为应用线程在 Provider 非活动期间已经继续推进。恢复的 Provider 不会知道其他 Provider 做了什么。这会产生错误的答案、过时的计划和不安全的文件假设。

除非自该 Provider 上次参与以来没有发生任何运行,否则切换回 Provider 需要一份交接摘要。

Provider 线程覆盖

Provider 线程具有原生覆盖和交接覆盖。

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

用户消息仍然是实际的运行输入。交接上下文是一个独立的 preamble/system/developer/synthetic-user 上下文,具体取决于 Provider 的能力。

策略选择

推荐的策略顺序:

  1. 无交接:目标 Provider 线程已覆盖当前应用运行。
  2. 增量交接至现有 Provider 线程:目标 Provider 线程可以恢复,且仅遗漏了一段运行范围。
  3. 完整线程交接至现有 Provider 线程:目标 Provider 可以恢复,但增量链过于复杂。
  4. 完整线程交接至新的 Provider 线程:目标 Provider 恢复失败、设置不兼容,或旧上下文已过期。
  5. 不支持:Provider 无法接受交接上下文,且不存在安全的重建路径。

过期策略

应用应能够判断切换回旧的 Provider 线程是否已不再合适。

可能的过期信号:

  • 自上次使用以来,非本 Provider 的运行过多,
  • 链式交接过多,
  • 目标 Provider 模型/运行时模式发生了不兼容的更改,
  • Provider 线程恢复失败,
  • 之前的 Provider 线程以冲突的方式被回滚或 fork,
  • 用户选择了“干净上下文(clean context)”,
  • 交接摘要将超出 Provider 的上下文策略。

当判断为过期时,使用 full_thread_summary 创建新的 Provider 线程。

摘要生成来源

摘要可以由以下方式生成:

  • 正在离开的 Provider,
  • 正在进入的 Provider,
  • 已配置的摘要 Provider,
  • 用于结构化产物的本地确定性摘要器。

摘要生成器不一定是目标提供方。交接会记录 createdByProvider,以便日后检查其质量和来源。

摘要应包含的内容

摘要应以应用线程为规范,而不是只记录提供方转录。

包含:

  • 用户目标和约束,
  • 已做出的决策,
  • 已更改的文件,
  • 运行过的命令/测试,
  • 计划/待办状态,
  • 未解决的审批/问题,
  • 当前检查点/diff 摘要,
  • 已知失败,
  • 相关的子代理结果。

不要包含:

  • 无关的提供方协议噪音,
  • 隐藏的思维链,
  • 已被后续运行取代的过时工具输出,
  • 当文件摘要足够时的大段 diff。

检查点交互

提供方切换不会改变检查点的归属。检查点仍然附加到应用运行。

但交接生成应引用最新的检查点和 diff 摘要,以便下一个提供方获得准确的工作区状态图景。

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

如果当前提供方支持提供方回滚,请调用它。否则,在恢复工作时,从保留的应用线程摘要创建新的提供方线程。

Fork 交互

从混合提供方的应用线程进行 Fork 时,应记录应用线程的谱系和源点,而无须在 Fork 时选择提供方。提供方上下文会在 fork 上首次运行开始时被延迟解析。

示例:

  • 从 Codex 上的运行 5 进行 Fork,fork 的首次运行使用 Codex:如果源引用足够强,则优先使用 Codex 原生 fork。
  • 从 Codex 上的运行 5 进行 Fork,fork 的首次运行使用 Claude:在首次调度时延迟构建可移植上下文。
  • 从没有提供方原生线程的应用检查点进行 Fork:使用检查点/完整线程上下文创建目标提供方线程。
  • 从子代理提供方线程进行 Fork:如果稳定,则直接使用子代理的提供方线程;否则回退到可移植上下文。

数据模型变更

提供方切换需要:

  • Run.provider
  • Run.providerThreadId
  • Run.contextHandoffId
  • ContextTransfer
  • ProviderThread.firstRunOrdinal
  • ProviderThread.lastRunOrdinal
  • ProviderThread.handoffIds
  • ContextHandoff
  • 用于交接支持的提供方能力

应用不需要为提供方切换单独维护会话线程。它需要将多个提供方线程附加到同一个应用线程上。

本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 t3/t3code