线程谱系与上下文转移¶
Fork(派生)、provider 交接、merge-back(合并回源)和子代理是独立的产品功能,但它们应共享同一个编排模型。共同的原语不是“fork”,也不是“摘要”。它是:
应用应廉价地保存谱系(lineage),并在实际需要时惰性地解析代价高昂的 provider/上下文相关工作。
产品用例¶
为探索而 Fork¶
用户为探索一个分叉方向而 fork 现有应用线程,同时不污染源线程的上下文。
source AppThread
-> user forks from stable source point
-> new idle AppThread
-> no provider chosen yet
-> first message chooses provider/model
如果 fork 上的首次运行使用与源线程相同的 provider,且该 provider 支持原生 fork,则使用 provider 原生 fork 路径。如果使用不同的 provider,则在首次分发时物化可移植上下文。
使用不同 Agent 继续同一线程¶
用户使用另一个 provider 继续同一个应用线程。这并不是新的应用线程,但仍然是一次上下文转移。
AppThread runs 1-5 with Codex
user sends run 6 with Claude
-> resolve context transfer from Codex-backed source state to Claude
-> start Claude provider thread with handoff context + user message
应用线程仍然是规范对话。Provider 原生线程是带有明确覆盖范围的后备句柄。
将 Fork 合并回源线程¶
用户在 fork 中深入探索,然后将结果带回源线程。
source thread at source point S
fork explores runs F1-Fn
user sends next message in source thread with "bring this back"
-> build delta from S to fork latest stable point
-> inject delta context with the source-thread user message
这不是完整的源线程 provider 切换。这是从 fork 回到原始线程的定向上下文转移。
子代理¶
子代理与前面操作属于同一族系,只是创建者不同、生命周期契约不同。
原生子代理:
- provider 通过其原生能力生成子代理;
- 应用观察原生子代理的引用/事件;
- 子代理被建模为子执行节点;在有用时,它还会被建模为相关的应用子线程。
跨 provider 的 T3 子代理:
- 父代理调用应用拥有的工具;
- 应用创建与父代理具有关系的子应用线程/运行;
- 子代理结果作为“子代理结果上下文转移”传回父代理。
这个共享模型应让用户 fork 和代理创建的子线程呈现为相同的图结构,只是 createdBy 和生命周期策略不同。
核心概念¶
线程关系¶
线程关系描述目标线程或运行相对于另一个线程而存在的原因。
type ThreadRelationshipKind =
| "fork"
| "provider_handoff"
| "merge_back"
| "subagent_spawn"
| "subagent_result";
关系是产品层面的,并且与 provider 无关。Fork 并不等同于 provider 原生 fork。Provider 原生 fork 只是 fork 关系的一种可能的解析策略。
源点¶
源点描述被转移的确切状态。
type ContextSourcePoint = {
threadId: ThreadId;
runId?: RunId;
checkpointId?: CheckpointId;
turnItemId?: TurnItemId;
providerThreadRef?: NativeThreadRef;
providerTurnRef?: NativeTurnRef;
};
稳定的源点应优先选择已完成的运行/检查点。进行中的运行更棘手,因为 provider 的分块数据可能仍在到达,检查点也可能尚未最终确定。
上下文转移¶
上下文转移是连接源与目标的持久化操作记录。它创建成本低,并且可以保持未解析状态直到首次使用。
type ContextTransfer = {
id: ContextTransferId;
type: ThreadRelationshipKind;
sourceThreadId: ThreadId;
targetThreadId: ThreadId;
sourcePoint: ContextSourcePoint;
basePoint?: ContextSourcePoint;
sourceProvider?: ProviderKind;
targetProvider?: ProviderKind;
status:
| "pending"
| "resolved_native"
| "resolved_portable"
| "failed"
| "consumed"
| "superseded";
resolution?: ContextTransferResolution;
createdBy: "user" | "agent" | "system";
createdAt: string;
consumedAt?: string;
};
basePoint 用于增量转移,尤其是 merge-back。对于 fork 的 merge-back,basePoint 是原始 fork 源点,sourcePoint 是 fork 最新的稳定点。
上下文交接¶
上下文交接是代价高昂的物化上下文工件。它仅在原生转移不可用或不充分时,才会被惰性创建。
type ContextHandoff = {
id: ContextHandoffId;
transferId: ContextTransferId;
kind: "portable_context" | "delta_context" | "checkpoint_context";
payload: PortableContextPayload;
createdAt: string;
};
上下文交接应作为可审计的应用工件,而不是隐藏的 prompt 拼接。
解析策略¶
解析决定目标 provider/线程如何接收源上下文。
type ContextTransferResolution =
| {
strategy: "native_fork";
providerThreadRef: NativeThreadRef;
}
| {
strategy: "portable_context";
contextHandoffId: ContextHandoffId;
}
| {
strategy: "delta_context";
contextHandoffId: ContextHandoffId;
}
| {
strategy: "checkpoint_context";
contextHandoffId: ContextHandoffId;
};
在源原生引用稳定可靠且适配器支持的情况下,同 provider 的 fork 应优先选择 native_fork。跨 provider 的交接和 merge-back 通常需要一次上下文交接。
惰性解析¶
Fork 创建应保持低成本:
thread.fork
-> create target AppThread
-> record thread relationship / ContextTransfer(status=pending)
-> do not create provider session
-> do not create provider thread
-> do not build portable context
在待处理的 fork 上首次分发时,会解析该转移:
first run on fork chooses provider P
-> find pending fork ContextTransfer
-> if source provider == P and native fork is supported:
resolve native fork
else:
materialize portable context
-> create/resume provider thread
-> send handoff/native context + user message
这避免了在分叉时强制用户选择代理,也避免了生成可能永远不会被使用的摘要。
运行时入口点¶
运行启动应有一个与提供商无关的钩子:
此钩子检查针对线程/运行的待处理上下文转移,并选择一种策略:
- 无需转移:目标提供商已具备当前覆盖范围。
- 原生转移:同一提供商且适配器支持原生分叉/恢复。
- 可移植转移:构建上下文交接并将其注入到运行中。
- 增量转移:构建
basePoint与sourcePoint之间的变更。 - 不支持:在提供商工作开始前明确失败。
提供商适配器负责原生细节。编排器负责关系、源点、持久化转移记录和命令回执。
对于 Codex,原生 thread/fork 接受一个包含边界的 lastTurnId。当应用源点是已完成且具有原生回合引用的提供商回合时,Codex 适配器会传递该原生 ID,以便提供商直接在请求的位置创建分叉。如果没有可用的原生回合引用,适配器将回退到分叉最新的原生状态,并通过后续终止提供商回合的数量回滚分叉,在分页历史记录上使用 thread/revert,在旧版历史记录上报告明确失败,因为 Codex 0.156 无法回滚旧版历史记录。编排器仍然传递与提供商无关的源点和源提供商回合历史;它不对 Codex 边界或回滚策略进行编码。
直接检查点回滚也需要分页的 Codex 历史记录。V2 适配器在回滚前检查 historyMode,并在旧版历史记录上明确失败,因为 Codex 0.156 无法回滚。在分页历史记录上,它分页遍历 thread/turns/list 以找到第一个被移除的回合,然后发送带有 beforeTurnId 的 thread/revert。
数据所有权¶
AppThread 应存储轻量级的浏览谱系:
type AppThreadLineage = {
parentThreadId: ThreadId | null;
relationshipToParent: "fork" | "subagent" | null;
rootThreadId: ThreadId;
// A side chat: a fork shown only inside its parent. The fork planner sets it from `thread.fork`.
sideChat?: boolean;
};
操作转移细节应放在 ContextTransfer 中,而不是直接放在 AppThread 上,因为一个线程可以参与多次转移:
- 由分叉创建;
- 之后切换到另一个提供商;
- 之后合并回其源线程;
- 之后派生子代理。
检查点与活动运行策略¶
第一个实现应优先采用稳定的源点:
- 已完成的运行检查点;
- 显式检查点;
- 具有已知覆盖范围的空闲提供商线程。
从活动运行进行分叉或交接应:
- 使用最新的已完成检查点,或
- 在设计出活动运行语义之前被拒绝。
不要静默地从部分流式传输的提供商状态进行分叉。这会削弱关联性并使重放/恢复变得困难。
当前实现边界¶
第一个后端切片实现延迟的同提供商分叉解析:
thread.fork创建一个带有分叉谱系和待处理ContextTransfer的目标应用线程。thread.fork不创建提供商会话、提供商线程、提供商回合或可移植上下文工件。- 分叉上的首次调度解析待处理的转移。对于具有强原生引用和
thread/fork支持的 Codex 到 Codex 分叉,编排器使用提供商原生分叉路径并记录resolved_native然后consumed。 - 跨提供商或其他非原生分叉转移会在目标线程的首次调度时延迟物化一个可移植的
ContextHandoff。 - 对于显式运行/检查点分叉,活动源运行会被拒绝;
latest_stable解析为最新的已完成检查点运行。
这使运行时与架构保持一致,同时通过 ContextTransfer 和 ContextHandoff 记录使可移植上下文变得明确且可审计。
与提供商切换的关系¶
提供商切换是一种上下文转移,其中 sourceThreadId === targetThreadId 且目标提供商与当前活动提供商不同。
返回到先前的提供商也是一种上下文转移,通常是提供商最后覆盖的运行范围到当前应用线程点之间的增量。
提供商切换与上下文交接 文档描述了该特定功能的策略选择。本文档定义了提供商切换、分叉、合并回源和子代理所共享的更广泛模型。
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 t3/t3code