T3 Code 非官方中文翻译不是官方产品,也不是 T3 Tools 官方文档。源提交 d2c25950b4d9英文原文GitHub 源文件
Skip to content

实体 ID 与关联 ​

原则 ​

提供方(provider)id 是证据。应用 id 是身份。

每个持久实体都以应用自己的 id 作为主键。提供方原生 id 作为可选引用保存,只用于关联、调试、重放和命令(command)路由。

ID 族 ​

text
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。

提供方引用 ​

提供方引用是运行时实体和事件(event)上的元数据。

ts
type ProviderRefs = {
  provider: ProviderKind;
  nativeSessionRef?: string;
  nativeThreadRef?: string;
  nativeTurnRef?: string;
  nativeItemRef?: string;
  nativeRequestRef?: string;
  rawEventId?: RawEventId;
  method?: string;
};

Codex 可以填上其中大部分字段。其他提供方(provider)可能只填一个子集。

关联范围 ​

关联始终带有范围。没有提供方(provider)、提供方会话(session)和对话线程(thread)的范围时,绝不能对原生 id 做全局匹配。

首选的匹配键:

text
ProviderThread:
  provider + providerSessionId + nativeThreadRef

ProviderTurn:
  providerThreadId + nativeTurnRef
  or providerThreadId + turnOrdinal

Node / TurnItem:
  providerTurnId + nativeItemRef
  or providerTurnId + itemOrdinal

RuntimeRequest:
  providerSessionId + nativeRequestRef
  or providerTurnId + requestOrdinal

如果提供方会回收 id,提供方适配器(adapter)必须放宽范围,直到映射不再有歧义。

映射存储 ​

V2 需要一张持久的关联表,或等价的事件溯源绑定流。

ts
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 永远保留原始提供方帧。

ID 分配规则 ​

  1. 如果提供方(provider)给出了稳定的原生 id,就复用已有绑定,或者分配一个新的应用 id 并把它绑定上。
  2. 如果提供方没有给出稳定 id,就按范围序号分配。
  3. 如果序号不够,就在该范围之内再加一个指纹。
  4. 一旦分配,就绝不再更改该实体的应用 id。
  5. 绝不能靠字符串匹配 id,来推断父级或根级已经完成。

范围序号 ​

弱提供方(provider)需要确定性的序号。

text
runOrdinalWithinThread
providerTurnOrdinalWithinProviderThread
nodeOrdinalWithinParent
itemOrdinalWithinProviderTurn
requestOrdinalWithinProviderTurn
messageOrdinalWithinNode

序号由规范化器在处理提供方帧时分配。对给定的一份提供方记录稿,重放结果必须是确定的。

指纹 ​

指纹只是兜底的关联手段。它们应当包含范围和稳定结构,而不是大段可变文本。

示例:

text
ProviderTurn fingerprint:
  providerThreadId + runId + providerTurnOrdinalWithinProviderThread

Node / TurnItem fingerprint:
  providerTurnId + itemKind + itemOrdinalWithinProviderTurn + toolName?

RuntimeRequest fingerprint:
  providerTurnId + requestKind + nativeItemRef? + requestOrdinalWithinProviderTurn

不要把完整的助手文本用作主指纹。文本是可变的,可能分块流入,也可能重复。

运行时事件 ​

规范化后的运行时事件(event)应当同时带上应用 id 和提供方引用。

ts
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。提供方引用保留下来,供检查和适配器(adapter)路由使用。

命令路由 ​

界面和编排命令(command)以应用 id 为目标。

text
approval.respond(RuntimeRequestId)
interrupt.run(RunId)
interrupt.node(NodeId)
fork.fromNode(NodeId)
rollback.toRun(ThreadId, runOrdinal)

提供方命令层在边界把应用 id 解析为提供方引用。如果提供方引用缺失,或已经不再有效,该命令会以带类型的能力/状态错误失败。

示例:

text
RuntimeRequestId -> nativeRequestRef + providerSessionId
RunId -> root NodeId -> ProviderTurnId -> nativeTurnRef
NodeId -> ProviderThreadId -> nativeThreadRef

重启之后的待处理请求 ​

待处理请求的记录可以在重启后保留,但提供方的回调状态通常不会。

V2 应当区分历史上看似仍待处理的请求,和现在仍可响应的请求:

ts
type ResponseCapability =
  | { type: "live"; providerSessionId: ProviderSessionId }
  | { type: "not_resumable"; reason: string };

界面可以表明某个请求已经过期,用户必须重新开始,或重新运行这一回合(turn)。

子智能体关联 ​

子智能体通过父子执行节点来表示。

对于 Codex:

text
collabAgentToolCall item
  -> ExecutionNode(kind="subagent" or "tool_call")
receiverThreadIds[]
  -> ProviderThread records
child turn/started and turn/completed
  -> child ProviderTurn and child ExecutionNode

子提供方回合(turn)保留自己的提供方引用。它通过 parentNodeId 链接到父级,而不是用父回合 id 替换自己的回合 id。

对于弱提供方,适配器(adapter)可以在当前活动的父节点下,按序号创建一个子节点。

重放确定性 ​

给定相同的提供方记录稿和相同的初始应用状态,规范化必须产生相同的 id。

要求:

  • 身份绑定作为事件(event)或持久行保存。
  • 只要绑定在下游投影(projection)之前已经持久化,生成的 id 就可以是随机的。
  • 夹具重放可以使用确定性的 id 生成,以便断言更容易。
  • 重新处理同一条提供方帧时,应当找到已有绑定,而不是再分配一个实体。

非官方译文 · 源提交 d2c25950b4d9 · 不代表 T3 Tools