实体 ID 与关联¶
原则¶
Provider ID 是证据。App ID 是身份。
每个持久化实体都使用应用自有 ID 作为主键。Provider 原生 ID 存储为可选引用,仅用于关联、调试、重放和命令路由。
ID 系列¶
ThreadId app-visible conversation thread
RunId counted app turn
NodeId execution graph node
ProviderSessionId live/resumable provider runtime session
ProviderThreadId app handle for provider-native conversation
ProviderTurnId app handle for provider-native turn
RuntimeRequestId app handle for provider callback/request
RawEventId optional raw provider diagnostic frame id
CheckpointId app checkpoint id
PlanId app plan/todo/question artifact id
Provider ID 永远不会取代这些 ID。
Provider 引用¶
Provider 引用是运行时实体和事件上的元数据。
type ProviderRefs = {
provider: ProviderKind;
nativeSessionRef?: string;
nativeThreadRef?: string;
nativeTurnRef?: string;
nativeItemRef?: string;
nativeRequestRef?: string;
rawEventId?: RawEventId;
method?: string;
};
Codex 可以填充这些字段中的大部分。其他 Provider 可能只会填充其中的一部分。
关联范围¶
关联始终限定在作用域内。切勿在没有 provider/session/thread 作用域的情况下全局匹配原生 ID。
首选匹配键:
ProviderThread:
provider + providerSessionId + nativeThreadRef
ProviderTurn:
providerThreadId + nativeTurnRef
or providerThreadId + turnOrdinal
Node / TurnItem:
providerTurnId + nativeItemRef
or providerTurnId + itemOrdinal
RuntimeRequest:
providerSessionId + nativeRequestRef
or providerTurnId + requestOrdinal
如果 Provider 回收利用 ID,Provider 适配器必须扩大作用域,直到映射不再有歧义。
映射存储¶
V2 需要一个持久化关联表或等效的事件溯源绑定流。
type IdentityBinding = {
id: IdentityBindingId;
appEntityKind: "provider_thread" | "provider_turn" | "node" | "item" | "request" | "message";
appEntityId: string;
provider: ProviderKind;
providerSessionId: ProviderSessionId;
nativeKind: string | null;
nativeRef: string | null;
scope: {
threadId: ThreadId | null;
runId: RunId | null;
parentNodeId: NodeId | null;
providerThreadId: ProviderThreadId | null;
providerTurnId: ProviderTurnId | null;
};
correlation: "native_exact" | "native_scoped" | "ordinal" | "fingerprint" | "synthetic";
firstRawEventId?: RawEventId;
lastRawEventId?: RawEventId;
createdAt: string;
updatedAt: string;
};
这并不意味着要构建一个大型身份子系统。它是最小的持久化位置,用来表明“这个原生事物对应这个应用事物”。原始事件引用是可选的诊断指针;绑定本身不得要求生产环境的 SQLite 永远保留原始 Provider 帧。
ID 分配规则¶
- 如果 Provider 提供稳定的原生 ID,则复用现有绑定,或分配新的应用 ID 并绑定它。
- 如果 Provider 未提供稳定 ID,则按作用域序数分配。
- 如果序数不足,则在作用域内添加指纹。
- 一旦分配,绝不为该实体更改应用 ID。
- 切勿通过字符串匹配 ID 来推断父级/根级完成状态。
作用域序数¶
弱 Provider 需要确定性序数。
runOrdinalWithinThread
providerTurnOrdinalWithinProviderThread
nodeOrdinalWithinParent
itemOrdinalWithinProviderTurn
requestOrdinalWithinProviderTurn
messageOrdinalWithinNode
序数由规范化器在处理 Provider 帧时分配。对于给定的 Provider 记录,它们必须是可重放确定性的。
指纹¶
指纹只是关联的回退工具。它们应包含作用域和稳定结构,而不是大量可变文本。
示例:
ProviderTurn fingerprint:
providerThreadId + runId + providerTurnOrdinalWithinProviderThread
Node / TurnItem fingerprint:
providerTurnId + itemKind + itemOrdinalWithinProviderTurn + toolName?
RuntimeRequest fingerprint:
providerTurnId + requestKind + nativeItemRef? + requestOrdinalWithinProviderTurn
不要将完整的助手文本用作主指纹。文本是可变的、可能分块流式传输,并且可能重复。
运行时事件¶
规范化后的运行时事件应同时携带应用 ID 和 Provider 引用。
type RuntimeEvent = {
id: RuntimeEventId;
type: RuntimeEventType;
threadId: ThreadId;
runId: RunId | null;
nodeId: NodeId | null;
parentNodeId: NodeId | null;
providerSessionId: ProviderSessionId | null;
providerThreadId: ProviderThreadId | null;
providerTurnId: ProviderTurnId | null;
nativeItemRef: string | null;
requestId: RuntimeRequestId | null;
providerRefs: ProviderRefs;
payload: unknown;
createdAt: string;
};
下游系统应使用应用 ID。Provider 引用被保留用于检查与适配器路由。
命令路由¶
UI 和编排命令以应用 ID 为目标。
approval.respond(RuntimeRequestId)
interrupt.run(RunId)
interrupt.node(NodeId)
fork.fromNode(NodeId)
rollback.toRun(ThreadId, runOrdinal)
Provider 命令层在边缘将应用 ID 解析为 Provider 引用。如果 Provider 引用缺失或不再处于活动状态,命令将失败并返回类型化的能力/状态错误。
示例:
RuntimeRequestId -> nativeRequestRef + providerSessionId
RunId -> root NodeId -> ProviderTurnId -> nativeTurnRef
NodeId -> ProviderThreadId -> nativeThreadRef
重启后的待处理请求¶
待处理请求记录可能在重启后仍然存在,但 Provider 回调状态通常不会。
V2 应区分历史中看似待处理的请求与可响应的请求:
type ResponseCapability =
| { type: "live"; providerSessionId: ProviderSessionId }
| { type: "not_resumable"; reason: string };
UI 可以显示请求已过期,并且用户必须重启或重新运行该轮次。
子代理关联¶
子代理通过父子执行节点来表示。
对于 Codex:
collabAgentToolCall item
-> ExecutionNode(kind="subagent" or "tool_call")
receiverThreadIds[]
-> ProviderThread records
child turn/started and turn/completed
-> child ProviderTurn and child ExecutionNode
子 ProviderTurn 保留自己的 provider refs。它通过 parentNodeId 链接到父节点,而不是用父节点的 turn id 替换自己的 turn id。
对于弱 provider,适配器可以在当前父节点下按 ordinal 创建子节点。
重放确定性¶
给定相同的 provider transcript 和相同的初始应用状态,规范化必须产生相同的 id。
要求:
- 身份绑定以事件或持久化行的形式持久保存。
- 生成的 id 可以是随机的,只要该绑定在后续投影之前已持久化。
- Fixture 重放可以使用确定性的 id 生成,以便更容易进行断言。
- 重新处理同一个 provider frame 时应找到现有绑定,而不是分配新的实体。
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 t3/t3code