上一篇分析了 App Server 的整体架构:不同 Transport 被归一化成连接事件,Message Processor 负责协议分发,Request Processor 调用 Core,Outbound Router 再将 Response、Request 和 Notification 发送给客户端。
本篇继续向协议内部深入。
App Server 用 Thread、Turn、Item 三层对象描述一段 Agent 会话。它们看起来像普通的数据结构,但真正理解这套协议,需要同时回答四个问题:
- 每一层对象保存什么状态?
- Request Response 与流式 Notification 如何配合?
- 客户端如何从增量事件还原最终界面?
- 断线恢复时,持久化历史又如何变回相同的数据模型?
本篇将围绕这四个问题展开。
本篇目标
阅读完成后,你应该能够:
- 区分 Thread、Turn、ThreadItem 的职责和生命周期。
- 正确解释 ThreadStatus 与 TurnStatus。
- 理解
itemsView为什么是协议正确性的一部分。 - 描述
thread/start与turn/start的 Response/Notification 顺序。 - 使用 Item ID 处理 Started、Delta 和 Completed 事件。
- 避免用空的 Turn 快照覆盖客户端已收集的 Item。
- 理解 Rollout History 如何重建 Thread、Turn 和 Item。
1. 协议类型放在哪里
核心类型位于codex-app-server-protocol:
| 文件 | 职责 |
|---|---|
| thread_data.rs | Thread、Turn 聚合快照 |
| thread.rs | Thread API、状态和通知 |
| turn.rs | Turn API、输入和通知 |
| item.rs | ThreadItem 联合类型和 Item 通知 |
| common.rs | Request 与 Notification Method 注册 |
| event_mapping.rs | Core Event 到协议通知的映射 |
| thread_history.rs | Rollout History 重建 |
这些文件说明协议存在四个不同层面:
API Method -> Request / Response Type -> Thread / Turn / Item Snapshot -> Streaming Notification -> Persisted History Reconstruction不要只阅读ThreadStruct。完整语义分布在快照、通知和重建逻辑中。
2. 三层聚合关系
最简化的数据结构是:
Thread id status metadata turns[] | `-> Turn id status items[] | `-> ThreadItem id type-specific fields其关系是:
- 一个 Thread 可以包含多个 Turn。
- 一个 Turn 可以包含多个 Item。
- 每个 Item 只属于一个 Turn。
- Turn 通过 Thread ID 归属 Thread。
- Item Notification 同时携带 Thread ID、Turn ID 和 Item ID。
但这不意味着每个 Response 都携带完整嵌套树。为了控制传输和内存开销,很多快照只包含元数据。
3. Thread:可持续的会话资源
Thread 代表一段可以继续、恢复、分叉和归档的会话。
主要字段可以按职责分组。
3.1 身份关系
| 字段 | 含义 |
|---|---|
id | Thread ID,Codex 生成值为 UUIDv7 |
sessionId | 同一 Session Tree 共享的 Session ID |
forkedFromId | Fork 来源 Thread |
parentThreadId | Subagent 的直接父 Thread |
forkedFromId与parentThreadId不是同一个概念:
- Fork 表示复制历史形成分支。
- Parent 表示多智能体 Spawn 关系。
3.2 展示信息
| 字段 | 含义 |
|---|---|
preview | 通常取第一条用户消息 |
name | 用户可设置的 Thread 名称 |
agentNickname | Subagent 随机昵称 |
agentRole | Subagent 角色 |
3.3 运行环境
| 字段 | 含义 |
|---|---|
cwd | Thread 工作目录 |
modelProvider | 模型提供方 |
source | CLI、VS Code、Exec、App Server 等来源 |
threadSource | User、Subagent、Feature、Memory Consolidation |
gitInfo | 创建时捕获的 Git 信息 |
cliVersion | 创建 Thread 的 CLI 版本 |
3.4 持久化信息
| 字段 | 含义 |
|---|---|
ephemeral | 是否仅存在于内存 |
path | Rollout 文件路径,Ephemeral Thread 通常为null |
historyMode | Legacy 或 Paginated |
createdAt | 创建时间,秒 |
updatedAt | 更新时间,秒 |
recencyAt | 用于排序的最近活跃时间,秒 |
3.5 运行状态和历史
| 字段 | 含义 |
|---|---|
status | 当前 Thread Runtime 状态 |
turns | 当前 Response 选择携带的 Turn 快照 |
这里最容易误解的是turns。
4.Thread.turns通常不是完整历史
源码注释明确规定,turns只在以下场景填充:
thread/resumethread/rollbackthread/forkthread/read且includeTurns=true
其他返回 Thread 的 Response 与 Notification,turns通常为空。
特别是:
thread/start Response -> turns 通常为空 thread/started Notification -> 强制清空 turns thread/list -> 元数据列表,不承载完整历史thread_started_notification会在发送前主动执行:
thread.turns.clear()原因很直接:
- 创建或广播 Thread 时没有必要复制完整历史。
- 多连接广播大对象会放大内存和带宽。
- 历史应该通过显式 Read、Resume 或分页 API 获取。
客户端不能看到turns=[]就推断“这个 Thread 没有历史”。
5. ThreadStatus 描述的是 Runtime,不是最后一次任务
ThreadStatus 定义为:
notLoaded idle systemError active { activeFlags }5.1 NotLoaded
Thread 存在于持久化存储,但当前没有加载进 ThreadManager。
5.2 Idle
Thread 已加载,当前没有运行任务,也没有等待中的交互。
5.3 Active
Thread 正在运行,或正在等待某类交互。
activeFlags可能包含:
waitingOnApprovalwaitingOnUserInput
Active 的 Flag 也可能为空。这表示 Thread 确实正在执行,但暂时没有等待审批或用户输入。
5.4 SystemError
Thread 已加载,但 Runtime 记录了系统级错误,且当前不再运行。
5.5 状态优先级
实现位于:
thread_status.rs
简化优先级是:
未加载 -> NotLoaded 正在运行或等待交互 -> Active 存在系统错误 -> SystemError 其他已加载状态 -> IdleThreadStatus 不等于最后一个 Turn 的状态。
例如:
- 上一个 Turn Failed,但 Thread 仍可继续,当前可能是 Idle。
- 当前 Turn 正在运行,Thread 是 Active。
- Thread 未加载时,即使最后 Turn Completed,Thread 仍是 NotLoaded。
6. Turn:一次有边界的用户任务
Turn 表示 Thread 上的一次执行。
核心字段:
| 字段 | 含义 |
|---|---|
id | Turn ID,Codex 生成值为 UUIDv7 |
items | 当前 Payload 加载的 Item |
itemsView | items的完整度 |
status | Turn 生命周期状态 |
error | Failed 时的错误 |
startedAt | 开始时间,秒 |
completedAt | 完成时间,秒 |
durationMs | 持续时间,毫秒 |
Turn ID 是客户端关联 Turn Notification 的关键,不应使用数组位置代替。
7. TurnStatus 是一次任务的终态
TurnStatus 包含:
inProgress completed interrupted failedInProgress
Turn 已提交或已开始,但尚未进入终态。
Completed
Turn 正常完成。它表示 Agent 循环结束,不代表每个工具都一定成功。
例如某个命令可能失败,但 Agent 读取失败结果后仍生成最终回复,Turn 仍可 Completed。
Interrupted
Turn 被用户中断或通过turn/interrupt取消。
Failed
Turn 因不可恢复错误结束。此时error应包含:
message- 可选
codexErrorInfo - 可选
additionalDetails
Turn 的 Error 只在 Failed 状态下有意义。
8.itemsView不能被忽略
同一个 Turn 可以只携带不同详细程度的 Item。
TurnItemsView包含:
NotLoaded
items = []表示 Item 没有加载,而不是 Turn 没有 Item。
Summary
只保留适合列表展示的摘要:
- 第一条 UserMessage。
- 最后一条 AgentMessage。
如果二者之一不存在,则只返回存在的项。
Full
包含持久化历史中当前可用的完整 Item 列表。
客户端必须同时检查:
turn.items turn.itemsView不能只检查items.length。
9. 分页历史为什么需要itemsView
thread/turns/list默认按降序分页 Turn,并允许选择:
itemsView = notLoaded | summary | full这样不同界面可以选择不同成本:
- Thread 列表:不加载 Item。
- Resume Picker:加载摘要。
- 会话详情:加载完整 Item。
thread/items/list则直接按 Item 分页,还可以指定turnId。
两种 API 的区别:
| API | 分页对象 | 适用场景 |
|---|---|---|
thread/turns/list | Turn | 会话时间线、Turn 摘要 |
thread/items/list | Item | 大会话的详细内容 |
Cursor 是不透明字符串,客户端不应解析或自己计算。
10. Turn Start 的 Response 只是“已接受”
客户端发送:
{"method":"turn/start","id":10,"params":{"threadId":"THREAD_ID","input":[{"type":"text","text":"解释这个模块","textElements":[]}]}}TurnRequestProcessor将输入转换为 CoreOp::UserInput,提交给CodexThread,然后立即构造:
status = inProgress items = [] itemsView = notLoaded startedAt = null completedAt = nullResponse 表示:
- 请求参数有效。
- 用户输入已经提交给 Thread。
- Server 已分配 Turn ID。
它不表示模型已经开始采样。
11.turn/started才表示 Runtime 真正开始
Core 发出EventMsg::TurnStarted后,App Server 才发送:
turn/startedNotification 包含:
threadId- Turn Snapshot
此时:
status = inProgressstartedAt通常已经可用items=[]itemsView=notLoaded
所以一个 Turn 存在两个启动时刻:
turn/start Response -> 已提交并获得 Turn ID turn/started Notification -> Core Runtime 已开始执行客户端可以在 Response 后立即创建占位 Turn,在 Notification 到达后补充 Runtime 开始状态。
但客户端不应把网络到达顺序写成强前置条件。Response 处理与 Core Event Listener 属于不同异步路径,快速 Turn 可能让两条消息非常接近。无论哪条先到,都应根据同一个 Turn ID 执行 Upsert。
12. Turn Start 可以携带哪些输入
TurnStartParams.input是UserInput数组。
支持:
- Text。
- Image URL。
- Local Image。
- Skill。
- Mention。
Text 还可以携带textElements,用字节范围标记 UI 中的特殊元素。
Turn Start 还可以覆盖后续 Sticky Settings:
- CWD。
- Workspace Roots。
- Approval Policy。
- Approval Reviewer。
- Sandbox 或 Permission Profile。
- Model。
- Service Tier。
- Reasoning Effort。
- Reasoning Summary。
- Personality。
- Collaboration Mode。
- Output Schema。
这些 Override 不只是当前请求的临时参数,部分会成为 Thread 后续 Turn 的设置。
13.clientUserMessageId解决什么问题
客户端可以在turn/start或turn/steer中传入:
clientUserMessageId对应 UserMessage Item 会回显:
clientId它可以用于:
- 客户端乐观渲染后去重。
- 将本地消息与 Server 回显关联。
- 避免仅根据文本内容判断是否为同一消息。
文本相同不代表消息相同,稳定 Client ID 比文本去重更可靠。
14. ThreadItem 是可判别联合类型
ThreadItem使用type字段区分变体。
主要类型可以分成五组。
14.1 对话内容
userMessagehookPromptagentMessageplanreasoning
14.2 本地工具
commandExecutionfileChangeimageViewsleep
14.3 外部工具
mcpToolCalldynamicToolCallwebSearchimageGeneration
14.4 多智能体
collabAgentToolCallsubAgentActivity
14.5 生命周期标记
enteredReviewModeexitedReviewModecontextCompaction
所有变体都拥有稳定 Item ID,但不同类型拥有不同字段和状态。
15. Item 没有一个统一 Status
ThreadItem Enum 本身没有公共status字段。
只有需要生命周期的类型才定义自己的状态。
CommandExecution
inProgress completed failed declined还包含:
- Command。
- CWD。
- Process ID。
- Aggregated Output。
- Exit Code。
- Duration。
FileChange
inProgress completed failed declined并携带 Add、Delete、Update Diff。
McpToolCall
inProgress completed failed并携带 Arguments、Result、Error 和 Duration。
DynamicToolCall
inProgress completed failedCollabAgentToolCall
inProgress completed failedAgentMessage、Reasoning 或 UserMessage 不需要额外状态,它们通过 Item 生命周期事件表达完成。
16. Item 生命周期:Started、Delta、Completed
一个典型流式 Agent Message:
item/started item.type = agentMessage item.id = item-1 item/agentMessage/delta itemId = item-1 delta = "第一段" item/agentMessage/delta itemId = item-1 delta = "第二段" item/completed item.id = item-1 item.text = "第一段第二段"工具调用也采用相同思路:
item/started -> status=inProgress 类型专属进度通知 -> outputDelta / patchUpdated / progress item/completed -> final status + final payload并非每个 Item 都保证同时存在 Started 和 Completed。某些瞬时事件可以直接以 Completed Item 出现。
客户端应当按 Item ID Upsert,而不是假设事件严格成对。
17. Completed Item 是最终权威快照
Delta 用于低延迟显示,Completed Item 用于最终一致性。
正确策略是:
- Started 创建临时 Item。
- Delta 更新临时展示 Buffer。
- Completed 使用完整 Item 替换临时版本。
不要永久依赖 Delta 拼接结果。
Plan的协议注释尤其明确:
PlanDelta 拼接结果不保证等于 Completed Plan 文本原因可能包括:
- Server 端归一化。
- 重试或替换。
- 内容过滤。
- 最终结构转换。
AgentMessage 也应以 Completed Item 作为最终快照。
18. Item Notification 的时间单位
Item 生命周期 Notification 使用:
startedAtMscompletedAtMs
单位是毫秒。
而 Thread 和 Turn 的:
createdAtupdatedAtstartedAtcompletedAt
单位是秒。
这是客户端实现中很容易出现的错误:
Thread/Turn timestamp -> seconds Item lifecycle -> milliseconds Turn duration -> milliseconds不要直接把所有数字交给同一个 Date Constructor。
19. Core Event 如何变成 Item Notification
映射入口是:
event_mapping.rs
它处理可以一对一转换的 Core Event,例如:
EventMsg::ItemStarted -> item/started EventMsg::ItemCompleted -> item/completed EventMsg::AgentMessageContentDelta -> item/agentMessage/delta EventMsg::ExecCommandOutputDelta -> item/commandExecution/outputDelta一些需要状态或副作用的事件由:
bespoke_event_handling.rs
处理,例如:
- Turn Started/Completed。
- Pending Approval 清理。
- Interrupt Response。
- Thread Status 更新。
- 命令兼容事件去重。
这种拆分的原则是:
- 无状态一对一映射放在 Protocol Crate。
- 需要 Runtime State 的映射留在 App Server。
20.turn/completed不携带完整 Item
Turn 完成时,App Server 构造:
id = 当前 Turn ID items = [] itemsView = notLoaded status = completed | interrupted | failed error = Failed 时的错误 startedAt = 已知开始时间 completedAt = 完成时间 durationMs = 持续时间这是本篇最重要的协议细节之一。
turn/completed的职责是宣布 Turn 终态,不是重新发送完整 Turn History。
如果客户端执行:
state.turns[turnId] = notification.turn就会把之前通过 Item Notification 收集的所有 Item 覆盖为空。
正确做法是只合并终态字段:
status error startedAt completedAt durationMs除非itemsView明确表明 Payload 携带了所需历史,否则不要覆盖已构建的 Item 列表。
21. 一次完整 Turn 的典型事件序列
客户端可能观察到:
turn/start Response turn/started item/completed UserMessage item/started Reasoning item/reasoning/summaryTextDelta ... item/completed Reasoning item/started CommandExecution item/commandExecution/outputDelta ... item/completed CommandExecution item/started AgentMessage item/agentMessage/delta ... item/completed AgentMessage turn/completed实际序列会因工具、模型和 Feature 不同而变化:
- 可能没有 Reasoning。
- 可能有多个命令。
- 可能发生 MCP 调用。
- 可能发起审批 Request。
- 可能有子智能体。
- 可能 Turn Failed 或 Interrupted。
客户端应围绕 ID 和类型编写 Reducer,不应硬编码固定 Item 顺序。
22. ThreadStatus 与 TurnStatus 如何配合
典型状态变化:
Thread Idle | | turn/start v Thread Active, Turn InProgress | | approval request v Thread Active(waitingOnApproval), Turn InProgress | | approval response v Thread Active, Turn InProgress | | turn/completed v Thread Idle, Turn Completed如果 Turn Failed:
Thread Active -> Thread SystemError 或 Idle Turn InProgress -> Turn Failed具体 Thread 状态取决于 Runtime Error 是否仍被记录,不能只由 Turn Failed 机械推导。
23.turn/interrupt与turn/steer
23.1 Interrupt
请求必须同时提供:
- Thread ID。
- Turn ID。
这防止客户端误中断同一 Thread 上已经切换的新 Turn。
成功 Response 是空对象。真正终态通过:
turn/completed status = interrupted通知。
23.2 Steer
Steer 向正在运行的 Turn 追加用户输入。
它要求:
expectedTurnId如果当前活跃 Turn 已变化,请求会失败。
成功 Response 返回真正接受输入的 Turn ID。
Review 和手动 Compaction Turn 不接受 Steer。
24. Thread Start 的 Response 与 Notification
thread/start成功时,Server 先发送 Response:
- Thread 快照。
- 实际 Model。
- Model Provider。
- Service Tier。
- CWD。
- Workspace Roots。
- Instruction Sources。
- Approval Policy。
- Sandbox。
- Permission Profile。
- Reasoning Effort。
随后发送:
thread/startedNotification 中只保留 Thread 快照,并清空turns。
这种设计让请求发起方获得完整启动配置,其他订阅方只获得生命周期通知。
25. Resume 与 Fork 为什么更复杂
Resume
Resume 可能:
- 根据 Thread ID 加载。
- 根据 Path 加载。
- 使用实验性内存 History。
- 重新加入当前正在运行的 Thread。
Response 可以携带历史 Turn。
Fork
Fork 会:
- 复制源 Thread 历史。
- 分配新 Thread ID。
- 设置
forkedFromId。 - 可选择只复制到某个 Turn。
- 可创建 Ephemeral Fork。
Fork 的thread/startedNotification 同样不会广播完整历史。
运行中 Thread
Resume 一个正在运行的 Thread 时,Server 需要同时处理:
- 持久化历史。
- 当前内存中的 Active Turn。
- 已经发生但尚未完全落盘的 Item。
- Pending Approval。
- 新连接订阅。
这也是ThreadHistoryBuilder同时服务持久化重放和当前 Turn 追踪的原因。
26. ThreadHistoryBuilder:从事件日志重建快照
thread_history.rs 实现一个 Reducer。
输入包括:
RolloutItem::EventMsgRolloutItem::CompactedRolloutItem::ResponseItem- Turn Context 等元数据
Reducer 按顺序处理:
- User Message。
- Agent Message。
- Reasoning。
- Web Search。
- Command Begin/End。
- Patch Begin/End。
- MCP Begin/End。
- Turn Started/Complete/Aborted。
- Rollback。
- Compaction。
最终生成:
Vec<Turn>26.1 为什么不能直接反序列化成 Turn
Rollout 保存的是事实流,不是每一步完整快照。
例如命令执行可能分散为:
ExecCommandBegin Output Delta ExecCommandEndReducer 需要将它们合并成一个最终 CommandExecution Item。
26.2 Item Upsert
同一 Item ID 的后续状态会替换前一状态,而不是新增重复 Item。
26.3 Rollback
Rollback 会删除被回退 Turn,并清除对应的增量 Change Set。
26.4 Incremental Change Set
Builder 不仅能一次性生成全部历史,还能返回:
- Changed Items。
- Changed Turns。
- Removed Turn IDs。
这允许运行中的 Thread 只更新受影响快照。
27. 客户端 Reducer 应该如何设计
建议将客户端状态拆成:
threadsById turnsById itemOrderByTurnId itemsById itemDeltaBuffersThread Started
upsert thread metadata 不要用空 turns 删除已有历史Turn Start Response
upsert placeholder turn status = inProgressTurn Started
merge runtime start fields mark thread activeItem Started
if item id is new: append id to turn order upsert itemItem Delta
locate by threadId + turnId + itemId append or update type-specific bufferItem Completed
upsert final item snapshot clear matching delta bufferTurn Completed
merge status/error/time fields 保留本地已收集 itemsThread Status Changed
replace thread runtime status 不要修改 turn status这种 Normalized State 比嵌套数组更适合增量更新,也避免每个 Delta 复制整棵 Thread Tree。
28. 一个简化的 Reducer 伪代码
on ItemStarted(event): turn = turns[event.turnId] if event.item.id not in turn.itemIds: turn.itemIds.push(event.item.id) items[event.item.id] = event.item on ItemCompleted(event): ensureItemOrder(event.turnId, event.item.id) items[event.item.id] = event.item deltaBuffers.remove(event.item.id) on TurnCompleted(event): turn = turns[event.turn.id] turn.status = event.turn.status turn.error = event.turn.error turn.completedAt = event.turn.completedAt伪代码有意不执行:
turn.items = event.turn.items因为实时turn/completed的 Items 没有加载。
29. 如何处理重复与乱序风险
协议在单连接、单 Thread Listener 内尽量保持事件顺序,但客户端仍应具备基本幂等性。
建议:
- Thread、Turn、Item 全部按 ID Upsert。
- Completed 可以在没有 Started 时创建 Item。
- 重复 Started 不重复插入 Item Order。
- 重复 Completed 以最新完整快照覆盖。
- 未知 Delta 先进入临时 Buffer,等待 Started 或 Completed。
- Turn Completed 不删除仍在本地的 Item。
- Resume 后以 Server History 校正本地缓存。
对于多连接或断线重连场景,ID 比事件计数器更可靠。
30. 实时事件与持久化历史并不完全相同
实时协议强调低延迟:
- Started。
- Delta。
- Progress。
- Completed。
持久化历史强调可恢复事实:
- 完整 User/Agent Message。
- 工具开始与结束。
- Turn Context。
- Compaction。
- Rollback。
并非每个 UI 进度事件都必须永久保存。
因此,恢复后的界面应保证语义一致,但不一定逐字重放所有瞬时进度。
例如:
- 命令最终输出可以恢复。
- 曾经显示过的 Loading 文案不一定恢复。
- Delta 可以恢复为最终完整消息。
31. Experimental 字段如何进入协议
Thread、Turn 和 Item 中存在多个 Experimental 字段。
协议类型使用:
ExperimentalApi元数据标记:
- 某个 Method 是否实验性。
- 某个字段是否实验性。
- 嵌套类型是否包含实验字段。
客户端未在 Initialize 中启用 Experimental API 时:
- 实验性 Method 会被拒绝。
- 实验性 Notification 会被过滤。
- 某些出站 Payload 会移除实验字段。
这允许稳定客户端继续使用同一 Protocol Version,而不被未完成字段强制绑定。
32. Schema 生成为什么很重要
App Server Protocol 同时派生:
- Serde Serialize/Deserialize。
- JSON Schema。
- TypeScript Type。
可以通过:
codex app-server generate-ts--outDIR codex app-server generate-json-schema--outDIR生成与当前 Codex 二进制版本匹配的协议定义。
外部客户端应优先使用生成类型,而不是手写:
- Method Name。
- Camel Case 字段。
- Nullable 与 Optional。
- Experimental 字段。
- ThreadItem Union。
尤其是 ThreadItem 变体较多,手写类型很容易漏掉新增 Item。
33. 常见误区
误区一:ThreadStatus 就是最后一个 TurnStatus
ThreadStatus 描述 Runtime 是否加载、运行或等待交互;TurnStatus 描述一次任务结果。
误区二:items=[]表示没有内容
必须同时检查itemsView。NotLoaded 表示内容没有加载。
误区三:turn/startResponse 表示模型已开始
Response 表示提交成功;真正开始由turn/started通知。
误区四:turn/completed包含完整 Turn
实时完成通知中的 Items 明确为 NotLoaded。
误区五:所有 Item 都有统一 Status
只有命令、文件、MCP 等有执行生命周期的变体拥有类型专属状态。
误区六:拼接 Delta 就是最终内容
Completed Item 才是最终权威快照,Plan 尤其不保证 Delta 拼接等于最终文本。
误区七:每个 Item 都一定先 Started 再 Completed
部分瞬时 Item 可以直接 Completed,客户端必须按 ID Upsert。
34. 动手练习
练习一:绘制三层状态机
分别画出:
- ThreadStatus。
- TurnStatus。
- CommandExecutionStatus。
标记哪些状态可以并存。
练习二:跟踪一次 Agent Message
从 Core:
AgentMessageContentDelta跟踪到:
item/agentMessage/delta再找到 Completed AgentMessage 如何构造。
记录 Thread ID、Turn ID 和 Item ID 在每层的来源。
练习三:验证空 Items 语义
阅读以下三处:
TurnRequestProcessor创建 TurnStartResponse。TurnStartedEvent Handling。emit_turn_completed_with_status。
确认它们为什么都使用:
itemsView = notLoaded练习四:实现内存 Reducer
用任意语言实现以下事件:
- Thread Started。
- Turn Started。
- Item Started。
- Agent Message Delta。
- Item Completed。
- Turn Completed。
要求 Turn Completed 后仍能读取完整 Agent Message。
练习五:比较历史视图
对同一个 Thread 分别请求:
itemsView=notLoaded itemsView=summary itemsView=full比较 Payload 大小和 Item 内容。
35. 本篇小结
Thread、Turn、Item 不只是三层嵌套数据,而是一套“快照加事件”的协议模型。
核心规则可以概括为:
- Thread 表示可持续、可恢复和可分叉的会话。
- Turn 表示一次有终态的用户任务。
- ThreadItem 表示对话、工具和生命周期内容。
- ThreadStatus 与 TurnStatus 描述不同层次,不能互相替代。
itemsView决定空 Items 是“没有内容”还是“未加载”。turn/startResponse 表示提交成功,turn/started表示真正运行。- Started 和 Delta 用于实时展示,Completed Item 是最终权威快照。
turn/completed只携带终态元数据,不携带完整 Item。- 客户端应按 ID Upsert,并使用 Normalized State 避免重复复制。
- ThreadHistoryBuilder 将持久化事实流重建为相同的 Turn/Item 模型。
下一篇将进入 App Server 到 Core 的调用边界,跟踪一次 JSON-RPC Request 如何经过 MessageProcessor、ThreadRequestProcessor、ThreadManager,最终创建并返回一个可运行的 Core Thread。