第十章从设计角度解释了 rollout 为什么是 canonical replay log,以及 resume、fork、rollback、revert 如何建立在 replay 之上。 本附录进一步落到磁盘格式:一条 JSONL 记录长什么样、有哪些
type、每种payload保存什么、不同运行场景会写出怎样的记录序列。这里讨论的是 Codex 内部的 session rollout,不是附录 A 的 app-server JSON-RPC。两者虽然都可能采用“一行一个 JSON”,但用途、信封和兼容承诺不同。
版本基线:本文按 2026-09-07 所在代码版本整理。Rollout 会持续演进,排查其他版本时应先读取该文件自己的
session_meta.cli_version。
B.1 先分清三种 JSONL
Codex 周围至少有三类容易被叫作“JSONL”的数据:
| 数据 | 一行代表什么 | 主要消费者 | 是否是 session 的事实源 |
|---|---|---|---|
| rollout JSONL | 一条持久化记录 | resume、fork、历史投影、搜索与诊断 | 是 |
| app-server stdio JSONL | 一条请求、响应或通知 | IDE、TUI、自动化客户端 | 否 |
| rollout-trace | 一条更细的诊断证据 | 调试与离线分析 | 否 |
本附录只描述第一种。
Rollout 的默认位置是:
$CODEX_HOME/sessions/YYYY/MM/DD/
rollout-YYYY-MM-DDThh-mm-ss-<thread_id>.jsonl
目录和文件名使用创建机器的本地时间,精确到秒;session_meta.timestamp 与每行 timestamp 则转换成 UTC。不要从文件名推导精确事件时间。
归档后位于:
$CODEX_HOME/archived_sessions/
普通 rollout 中,thread_id 同时也是 rollout_id。执行新版 revert 时,逻辑 thread ID 保持不变,但会创建新的物理 rollout:
rollout-YYYY-MM-DDThh-mm-ss-<thread_id>_<rollout_id>.jsonl
冷历史还可能被压缩成 .jsonl.zst。压缩只改变物理表示,不改变逐行解码后的逻辑 schema。
[!important] Rollout 是内部持久化协议 它需要长期向后兼容,但不是建议第三方直接生产的公共 API。读取工具应容忍新增字段和不认识的非关键记录,不应依赖字段顺序。
B.2 每一行的公共信封
每行都是一个独立 JSON object:
{
"timestamp": "2026-09-07T08:30:12.345Z",
"ordinal": 42,
"type": "response_item",
"payload": {
"type": "message",
"role": "assistant",
"content": [
{ "type": "output_text", "text": "测试已经通过。" }
]
}
}
公共字段:
| 字段 | 类型 | 含义 |
|---|---|---|
timestamp | string | 记录写入时间,UTC RFC 3339,当前 writer 精确到毫秒 |
ordinal | uint64,可省略 | paginated history 的严格递增序号;legacy history 不写 |
type | string | 顶层记录类型,使用 snake_case |
payload | object | 该类型的具体载荷 |
metadata | object,可省略 | 目前只用于 response_item,位于顶层而非 payload 内 |
用一个类型表达式表示:
RolloutLine =
{
timestamp: string,
ordinal?: uint64,
type: RolloutItemType,
payload: object,
metadata?: CodexHarnessMetadata
}
ordinal 的语义不是“当前文件第几行”,而是逻辑历史位置:
- legacy 模式没有
ordinal; - paginated 模式从
0开始递增; - 若当前 rollout 引用另一个 rollout 的前缀,新文件的首个
ordinal从被引用前缀的end_ordinal_exclusive继续; HistoryPosition同时保存 ordinal 和 byte offset,使共享前缀既有逻辑边界,也有物理读取边界。
第一条非空记录必须是当前 rollout 自己的 session_meta。Fork 复制历史时可能在后面再次出现父线程的 session_meta,reader 只把第一条视为当前文件的 canonical identity。
B.3 顶层记录总表
当前 canonical RolloutItem 有 9 种:
type | payload | 解决的问题 |
|---|---|---|
session_meta | SessionMetaLine | 这份 rollout 属于谁、从哪里来、采用哪种历史模式 |
response_item | ResponseItem | 模型真正看到的对话、reasoning、工具调用与结果 |
inter_agent_communication | InterAgentCommunication | agent 之间的持久化消息 |
inter_agent_communication_metadata | { trigger_turn } | 在裁剪或迁移历史时保留消息的唤醒语义 |
compacted | CompactedItem | 用 replacement history 建立新的模型历史基线 |
turn_context | TurnContextItem | 某个真实 user turn 实际使用的模型、目录与安全设置 |
world_state | WorldStateItem | 模型可见世界状态的 full snapshot 或 merge patch |
security_risk_score | SecurityRiskScore | thread 级风险分类快照,不进入模型上下文或 UI 历史 |
event_msg | EventMsg 的 durable 子集 | turn 生命周期、token、UI item 与兼容事件 |
这 9 类记录不是平级地服务同一个消费者:
flowchart LR
J["RolloutLine"] --> M["模型历史 reducer"]
J --> U["UI 历史 reducer"]
J --> S["Session 状态 reducer"]
J --> Q["查询 / 索引 projection"]
M --> R1["response_item"]
M --> R2["compacted"]
M --> R3["world_state"]
M --> R4["inter_agent_communication"]
U --> E1["event_msg"]
U --> E2["response_item"]
S --> S1["session_meta"]
S --> S2["turn_context"]
S --> S3["security_risk_score"]
同一行可能被某个 reducer 使用、被另一个 reducer 忽略。Rollout 是多种 projection 的共同输入,不是一份可以直接展示的 transcript。
B.4 session_meta:文件的身份页
形状:
type SessionMetaLine = {
session_id: UUID,
id: UUID,
forked_from_id?: UUID,
parent_thread_id?: UUID,
timestamp: string,
cwd: string,
originator: string,
cli_version: string,
source: SessionSource,
thread_source?: string,
agent_nickname?: string,
agent_role?: string,
agent_path?: string,
model_provider: string | null,
base_instructions: BaseInstructions | null,
dynamic_tools?: DynamicToolSpec[],
selected_capability_roots?: SelectedCapabilityRoot[],
memory_mode?: string,
history_mode: "legacy" | "paginated",
history_base?: HistoryPosition,
subagent_history_start_ordinal?: uint64,
multi_agent_version?: "disabled" | "v1" | "v2",
context_window?: { window_id: string },
git?: GitInfo
}
字段说明:
| 字段 | 作用 |
|---|---|
session_id | 一棵 agent 树共享的 session 身份;旧记录缺失时回退为 id |
id | 用户看到的稳定 thread ID |
forked_from_id | 普通 fork 的来源 thread |
parent_thread_id | 子 agent 的父 thread |
timestamp | session 创建时间;与外层每行写入时间不是同一个概念 |
cwd | session 建立时的工作目录 |
originator | 创建该 session 的产品或调用方标识 |
cli_version | writer 版本,用于兼容诊断 |
source | session 从 CLI、IDE、exec、MCP、内部任务还是 sub-agent 创建 |
thread_source | 更偏分析用途的来源分类;自定义 feature 也编码为字符串 |
agent_* | 子 agent 的昵称、角色和 canonical path |
model_provider | 初始 provider;可能为 null |
base_instructions | thread 级基础指令与来源;旧记录可能为 null |
dynamic_tools | thread 创建时冻结的前端动态工具 spec |
selected_capability_roots | 前端选中的能力根 |
memory_mode | 记忆模式;当前常见特殊值为 "disabled" |
history_mode | 决定后续 event_msg 的持久化形态 |
history_base | paginated fork/revert 引用的 immutable prefix |
subagent_history_start_ordinal | 子 agent 自有历史开始位置;此前内容只是继承上下文 |
multi_agent_version | 多 agent 协议版本 |
context_window | 初始上下文窗口 ID |
git | 创建时的 Git commit、branch 与 remote URL |
嵌套 schema:
BaseInstructions = {
text: string,
provenance?: { type: "custom" }
| { type: "model", model: string }
}
HistoryPosition = {
thread_id: UUID, // 名字是历史遗留,语义上指 rollout_id
end_ordinal_exclusive: uint64,
end_byte_offset: uint64
}
GitInfo = {
commit_hash?: string,
branch?: string,
repository_url?: string
}
DynamicToolSpec =
{ type: "function", name: string, description: string,
inputSchema: JSON, deferLoading: boolean }
| { type: "namespace", name: string, description: string,
tools: Array<{
type: "function", name: string, description: string,
inputSchema: JSON, deferLoading: boolean
}> }
SelectedCapabilityRoot = {
id: string,
location: {
type: "environment",
environmentId: string,
path: string
}
}
source 是一个兼容性较强的联合类型:
SessionSource =
"cli" | "vscode" | "exec" | "mcp" | "unknown"
| { "custom": string }
| { "internal": "memory_consolidation" }
| { "subagent":
"review"
| "compact"
| "memory_consolidation"
| { "thread_spawn": {
parent_thread_id: UUID,
depth: int32,
agent_path: string | null,
agent_nickname: string | null,
agent_role: string | null
}}
| { "other": string }
}
典型首行:
{"timestamp":"2026-09-07T08:30:00.000Z","ordinal":0,"type":"session_meta","payload":{"session_id":"0199...","id":"0199...","timestamp":"2026-09-07T08:30:00.000Z","cwd":"/repo","originator":"codex_cli_rs","cli_version":"0.x.y","source":"cli","thread_source":"user","model_provider":"openai","base_instructions":{"text":"...","provenance":{"type":"model","model":"gpt-5"}},"history_mode":"paginated","context_window":{"window_id":"0199..."}}}
场景:
- 新建:作为第一行写入;
- resume:读取第一份
session_meta恢复身份和历史模式,继续追加原文件; - fork:新文件有自己的
session_meta,并通过forked_from_id或history_base说明来源; - revert:
id不变,文件名中的rollout_id变化,history_base指向被保留的旧前缀; - sub-agent:
session_id与根 agent 相同,id不同,并记录父 thread 与 agent path。
B.5 response_item:模型历史的原子
基本形状:
{
"timestamp": "...",
"ordinal": 7,
"type": "response_item",
"payload": { "type": "...", "...": "..." },
"metadata": { "client_authored": true }
}
metadata 可省略:
CodexHarnessMetadata = {
client_authored: boolean // 默认 false
}
它说明某条 developer message 是由 app-server 客户端提供,而不是 harness 自己生成。这个信息不能塞进模型原生 item,因而作为并列 sidecar 保存。
B.5.1 公共嵌套类型
ContentItem =
{ type: "input_text", text: string }
| { type: "input_image", image_url: string,
detail?: "auto" | "low" | "high" | "original" }
| { type: "input_audio", audio_url: string }
| { type: "output_text", text: string }
AgentMessageInputContent =
{ type: "input_text", text: string }
| { type: "encrypted_content", encrypted_content: string }
InternalChatMessageMetadataPassthrough = {
turn_id?: string,
create_time?: number,
executed_tool_calls?: ExecutedToolCall[]
}
MemoryCitation = {
entries: [{
path: string,
lineStart: uint32,
lineEnd: uint32,
note: string
}],
rolloutIds: string[]
}
internal_chat_message_metadata_passthrough 是 provider 侧 metadata 的保真通道。最常用的是 turn_id,用于把 item 重新关联到 turn;create_time 是带小数的 Unix 秒。消费者应保留未知字段,不应把这个对象当成稳定 UI schema。
reasoning.content 还有一条特殊的写入规则:当其中包含 reasoning_text 时,该字段不会再次序列化到 rollout;加密的 encrypted_content 才是跨轮继续传递 raw reasoning 的主要载体。
工具输出允许两种 wire shape:
FunctionCallOutput =
string
| Array<
{ type: "input_text", text: string }
| { type: "input_image", image_url: string, detail?: ImageDetail }
| { type: "input_audio", audio_url: string }
| { type: "encrypted_content", encrypted_content: string }
>
B.5.2 全部 durable ResponseItem
下表中的字段名就是 JSON wire name。标 ? 的字段可以省略;没有 ? 的 T | null 字段会以 null 表达未知值。
payload.type | 字段 |
|---|---|
message | id?, role, content: ContentItem[], phase?: "commentary" | "final_answer", internal_chat_message_metadata_passthrough? |
agent_message | id?, author, recipient, content: AgentMessageInputContent[], internal_chat_message_metadata_passthrough? |
reasoning | id?, summary: [{type:"summary_text",text}], content?: [{type:"reasoning_text"|"text",text}], `encrypted_content: string |
local_shell_call | id?, `call_id: string |
function_call | id?, name, namespace?, arguments: string, encrypted_function_args?, call_id, internal_chat_message_metadata_passthrough? |
tool_search_call | id?, `call_id: string |
function_call_output | id?, call_id, output: FunctionCallOutput, internal_chat_message_metadata_passthrough? |
custom_tool_call | id?, status?, call_id, name, namespace?, input: string, internal_chat_message_metadata_passthrough? |
custom_tool_call_output | id?, call_id, name?, output: FunctionCallOutput, internal_chat_message_metadata_passthrough? |
tool_search_output | id?, `call_id: string |
web_search_call | id?, status?, action?, internal_chat_message_metadata_passthrough? |
image_generation_call | id?, status, revised_prompt?, result, internal_chat_message_metadata_passthrough? |
compaction | id?, encrypted_content, internal_chat_message_metadata_passthrough? |
context_compaction | id?, encrypted_content?, internal_chat_message_metadata_passthrough? |
不会写入 rollout 的 ResponseItem:
payload.type | 原因 |
|---|---|
additional_tools | 请求期控制数据,不是历史事实 |
compaction_trigger | 请求期控制信号 |
未知类型(reader 映射为 Other) | 当前版本不知道怎样安全 replay |
local_shell_call.action:
{
type: "exec",
command: string[],
timeout_ms: uint64 | null,
working_directory: string | null,
env: object<string,string> | null,
user: string | null
}
web_search_call.action 使用 Responses API 的 snake_case 形态:
ResponseWebSearchAction =
{ type: "search", query?: string, queries?: string[] }
| { type: "open_page", url?: string }
| { type: "find_in_page", url?: string, pattern?: string }
一次普通工具 LOOP 的核心记录:
{"timestamp":"...","ordinal":4,"type":"response_item","payload":{"type":"message","id":"msg_user","role":"user","content":[{"type":"input_text","text":"运行测试"}]}}
{"timestamp":"...","ordinal":5,"type":"response_item","payload":{"type":"reasoning","id":"rs_1","summary":[{"type":"summary_text","text":"需要先运行测试。"}],"encrypted_content":"..."}}
{"timestamp":"...","ordinal":6,"type":"response_item","payload":{"type":"function_call","id":"fc_1","name":"exec_command","arguments":"{\"cmd\":\"just test\"}","call_id":"call_1"}}
{"timestamp":"...","ordinal":7,"type":"response_item","payload":{"type":"function_call_output","id":"fco_1","call_id":"call_1","output":"42 passed"}}
{"timestamp":"...","ordinal":8,"type":"response_item","payload":{"type":"message","id":"msg_final","role":"assistant","content":[{"type":"output_text","text":"测试通过。"}],"phase":"final_answer"}}
这里有三条关键关联:
function_call.call_id与function_call_output.call_id成对;id是 item 自身身份,call_id是调用与结果的关联键,两者不能混用;internal_chat_message_metadata_passthrough.turn_id在需要时把 item 归到具体 turn。
B.6 event_msg:持久化的是事件子集
EventMsg 的类型远多于会落盘的类型。是否持久化取决于 history_mode。
B.6.1 两种 history mode
| 事件 | legacy | paginated | 用途 |
|---|---|---|---|
task_started | 是 | 是 | turn 起点;读取时也接受别名 turn_started |
task_complete | 是 | 是 | turn 正常终点;读取时也接受别名 turn_complete |
turn_aborted | 是 | 是 | turn 中止终点 |
token_count | 是 | 是 | 恢复累计用量与窗口大小 |
thread_goal_updated | 是 | 是 | thread 长期目标状态 |
thread_rolled_back | 是 | 是 | legacy 逻辑 rollback marker |
thread_settings_applied | 是 | 是 | thread 有效设置快照 |
item_completed | 仅 Plan / clock.sleep | 全部 | paginated UI 历史的 canonical item |
user_message | 是 | 否 | legacy UI replay |
agent_message | 是 | 否 | legacy UI replay |
agent_reasoning | 是 | 否 | legacy UI replay |
agent_reasoning_raw_content | 是 | 否 | legacy UI replay |
entered_review_mode | 是 | 否 | legacy review UI |
exited_review_mode | 是 | 否 | legacy review UI |
patch_apply_end | 是 | 否 | legacy 文件改动 UI |
context_compacted | 是 | 否 | legacy compaction UI |
mcp_tool_call_end | 是 | 否 | legacy MCP UI |
web_search_end | 是 | 否 | legacy 搜索 UI |
image_generation_end | 是 | 否 | legacy图片生成 UI |
sub_agent_activity | 是 | 否 | legacy 多 agent UI |
下面这些实时 Event 不属于当前 durable set:
- 所有 delta:assistant、reasoning、plan、命令输出、patch preview;
- 所有 started/begin 进度事件,
task_started除外; - 审批、提问、权限申请、MCP elicitation 等 pending request;
- warning、stream error、模型 reroute、安全 buffering、环境连接状态;
- raw response 透传、hook 进度、MCP 启动进度;
- guardian 实时评估、动态工具 request/response;
- shutdown 通知。
这正是第二章“item 是权威,delta 是加速带”的磁盘版本。
B.6.2 始终持久化的事件 schema
task_started = {
type: "task_started",
turn_id: string,
trace_id?: string,
started_at?: int64, // Unix 秒
model_context_window: int64 | null,
collaboration_mode_kind: "default" | "plan"
}
task_complete = {
type: "task_complete",
turn_id: string,
last_agent_message: string | null,
error?: { message: string, codex_error_info: CodexErrorInfo | null },
started_at?: int64,
completed_at?: int64,
duration_ms?: int64,
time_to_first_token_ms?: int64
}
CodexErrorInfo =
"context_window_exceeded"
| "session_budget_exceeded"
| "usage_limit_exceeded"
| "server_overloaded"
| "cyber_policy"
| "misalignment_policy_violation"
| "internal_server_error"
| "unauthorized"
| "bad_request"
| "sandbox_error"
| "thread_rollback_failed"
| "other"
| { "http_connection_failed": { http_status_code: uint16|null } }
| { "response_stream_connection_failed": { http_status_code: uint16|null } }
| { "response_stream_disconnected": { http_status_code: uint16|null } }
| { "response_too_many_failed_attempts": { http_status_code: uint16|null } }
| { "active_turn_not_steerable": { turn_kind: "review"|"compact" } }
turn_aborted = {
type: "turn_aborted",
turn_id: string | null,
reason: "interrupted" | "replaced" | "review_ended" | "budget_limited",
started_at?: int64,
completed_at?: int64,
duration_ms?: int64
}
thread_rolled_back = {
type: "thread_rolled_back",
num_turns: uint32
}
thread_settings_applied:
{
type: "thread_settings_applied",
thread_settings: {
model: string,
model_provider_id: string,
service_tier?: string,
approval_policy: AskForApproval,
approvals_reviewer: "user" | "auto_review",
permission_profile: PermissionProfile,
active_permission_profile?: { id: string, extends?: string },
cwd: string,
reasoning_effort?: ReasoningEffort,
reasoning_summary?: "auto" | "concise" | "detailed" | "none",
personality?: "none" | "friendly" | "pragmatic",
collaboration_mode: CollaborationMode
}
}
token_count:
{
type: "token_count",
info: {
total_token_usage: TokenUsage,
last_token_usage: TokenUsage,
model_context_window: int64 | null
} | null,
rate_limits: RateLimitSnapshot | null
}
TokenUsage = {
input_tokens: int64,
cached_input_tokens: int64,
cache_write_input_tokens: int64,
output_tokens: int64,
reasoning_output_tokens: int64,
total_tokens: int64
}
RateLimitSnapshot = {
limit_id: string | null,
limit_name: string | null,
primary: { used_percent: number, window_minutes: int64|null, resets_at: int64|null } | null,
secondary: { used_percent: number, window_minutes: int64|null, resets_at: int64|null } | null,
credits: { has_credits: boolean, unlimited: boolean, balance: string|null } | null,
individual_limit: { limit: string, used: string,
remaining_percent: int32, resets_at: int64 } | null,
spend_control_reached: boolean | null,
plan_type: string | null,
rate_limit_reached_type: string | null
}
thread_goal_updated 使用 camelCase:
{
type: "thread_goal_updated",
threadId: UUID,
turnId?: string,
goal: {
threadId: UUID,
objective: string,
status: "active" | "paused" | "blocked" | "usageLimited"
| "budgetLimited" | "complete",
tokenBudget?: int64,
tokensUsed: int64,
timeUsedSeconds: int64,
createdAt: int64,
updatedAt: int64
}
}
B.6.3 legacy 专属事件 schema
user_message = {
type: "user_message",
client_id?: string,
message: string,
images?: string[],
image_details?: (ImageDetail|null)[],
local_images: string[],
local_image_details?: (ImageDetail|null)[],
audio?: string[],
local_audio: string[],
text_elements: TextElement[]
}
agent_message = {
type: "agent_message",
message: string,
phase: "commentary" | "final_answer" | null,
memory_citation: MemoryCitation | null,
delivery?: "async"
}
agent_reasoning = {
type: "agent_reasoning",
text: string
}
agent_reasoning_raw_content = {
type: "agent_reasoning_raw_content",
text: string
}
context_compacted = {
type: "context_compacted"
}
Review:
entered_review_mode = {
type: "entered_review_mode",
target:
{ type: "uncommittedChanges" }
| { type: "baseBranch", branch: string }
| { type: "commit", sha: string, title: string|null }
| { type: "custom", instructions: string },
user_facing_hint?: string,
turn_id?: string,
item_id?: string
}
exited_review_mode = {
type: "exited_review_mode",
turn_id?: string,
item_id?: string,
review_output: {
findings: [{
title: string,
body: string,
confidence_score: number,
priority: int32,
code_location: {
absolute_file_path: string,
line_range: { start: uint32, end: uint32 }
}
}],
overall_correctness: string,
overall_explanation: string,
overall_confidence_score: number
} | null
}
工具与多 agent:
patch_apply_end = {
type: "patch_apply_end",
call_id: string,
turn_id: string,
stdout: string,
stderr: string,
success: boolean,
changes: object<path, FileChange>,
status: "completed" | "failed" | "declined"
}
mcp_tool_call_end = {
type: "mcp_tool_call_end",
call_id: string,
invocation: { server: string, tool: string, arguments: JSON|null },
connector_id?: string,
mcp_app_resource_uri?: string,
link_id?: string,
app_name?: string,
action_name?: string,
plugin_id?: string,
read_only_hint?: boolean,
duration: { secs: uint64, nanos: uint32 },
result: { "Ok": CallToolResult } | { "Err": string }
}
web_search_end = {
type: "web_search_end",
call_id: string,
query: string,
action: ResponseWebSearchAction,
results?: JSON[]
}
image_generation_end = {
type: "image_generation_end",
call_id: string,
status: string,
revised_prompt?: string,
result: string,
transparent_background?: boolean,
failure?: ImageGenerationFailure,
saved_path?: string
}
sub_agent_activity = {
type: "sub_agent_activity",
event_id: string,
occurred_at_ms: int64,
agent_thread_id: UUID,
agent_path: string,
kind: "started" | "interacted" | "interrupted"
}
公共子类型:
FileChange =
{ type: "add", content: string }
| { type: "delete", content: string }
| { type: "update", unified_diff: string, move_path: string|null }
CallToolResult = {
content: JSON[],
structuredContent?: JSON,
isError?: boolean,
_meta?: JSON
}
B.7 item_completed:paginated history 的 UI 事实
Paginated 模式不再为每类 UI 内容分别保存一组 legacy 事件,而是统一写:
{
type: "item_completed",
thread_id: UUID,
turn_id: string,
item: TurnItem,
started_at_ms?: int64,
completed_at_ms: int64
}
completed_at_ms 是 Unix 毫秒。旧的 plan 记录可能没有该字段,读取时按 0 处理。
一个容易踩坑的细节:TurnItem.type 当前使用 PascalCase,不是外层记录常见的 snake_case。例如:
{"timestamp":"...","ordinal":9,"type":"event_msg","payload":{"type":"item_completed","thread_id":"0199...","turn_id":"0199...","item":{"type":"AgentMessage","id":"msg_1","content":[{"type":"Text","text":"完成。"}],"phase":"final_answer"},"completed_at_ms":1788770000123}}
B.7.1 全部 TurnItem
UserMessage = {
type: "UserMessage",
id: string,
client_id?: string,
content: UserInput[]
}
HookPrompt = {
type: "HookPrompt",
id: string,
fragments: [{ text: string, hookRunId: string }]
}
AgentMessage = {
type: "AgentMessage",
id: string,
content: [{ type: "Text", text: string }],
phase?: "commentary" | "final_answer",
memory_citation?: MemoryCitation,
delivery?: "async"
}
Plan = {
type: "Plan",
id: string,
text: string
}
Reasoning = {
type: "Reasoning",
id: string,
summary_text: string[],
raw_content: string[]
}
UserInput:
{ type: "text", text: string, text_elements: TextElement[] }
| { type: "image", image_url: string, detail?: ImageDetail }
| { type: "local_image", path: string, detail?: ImageDetail }
| { type: "audio", audio_url: string }
| { type: "local_audio", path: string }
| { type: "skill", name: string, path: string }
| { type: "mention", name: string, path: string }
TextElement = {
byte_range: { start: uint, end: uint },
placeholder: string | null
}
命令执行:
CommandExecution = {
type: "CommandExecution",
id: string,
plugin_id?: string,
script_path?: string,
process_id?: string,
command: string[],
cwd: string, // Path URI
parsed_cmd: ParsedCommand[],
source: "agent" | "user_shell"
| "unified_exec_startup" | "unified_exec_interaction",
interaction_input?: string,
status: "in_progress" | "completed" | "failed" | "declined",
stdout?: string,
stderr?: string,
aggregated_output?: string,
exit_code?: int32,
duration?: { secs: uint64, nanos: uint32 },
formatted_output?: string
}
ParsedCommand =
{ type: "read", cmd: string, name: string, path: string }
| { type: "list_files", cmd: string, path: string|null }
| { type: "search", cmd: string, query: string|null, path: string|null }
| { type: "unknown", cmd: string }
动态工具与多 agent:
DynamicToolCall = {
type: "DynamicToolCall",
id: string,
namespace?: string,
tool: string,
arguments: JSON,
status: "in_progress" | "completed" | "failed",
content_items?: DynamicToolOutput[],
success?: boolean,
error?: string,
duration?: { secs: uint64, nanos: uint32 }
}
DynamicToolOutput =
{ type: "inputText", text: string }
| { type: "inputImage", imageUrl: string }
| { type: "inputAudio", audioUrl: string }
CollabAgentToolCall = {
type: "CollabAgentToolCall",
id: string,
tool: "spawn_agent" | "send_input" | "resume_agent" | "wait" | "close_agent",
status: "in_progress" | "completed" | "failed",
sender_thread_id: UUID,
receiver_thread_ids: UUID[],
receiver_agents: CollabAgentRef[],
prompt?: string,
model?: string,
reasoning_effort?: ReasoningEffort,
agents_states: object<UUID, AgentStatus>
}
CollabAgentRef = {
thread_id: UUID,
agent_nickname?: string,
agent_role?: string
}
AgentStatus =
"pending_init" | "running" | "interrupted" | "shutdown" | "not_found"
| { "completed": string|null }
| { "errored": string }
SubAgentActivity = {
type: "SubAgentActivity",
id: string,
kind: "started" | "interacted" | "interrupted",
agent_thread_id: UUID,
agent_path: string
}
搜索、图片与扩展:
WebSearch = {
type: "WebSearch",
id: string,
query: string,
action: ResponseWebSearchAction,
results?: JSON[]
}
ImageView = {
type: "ImageView",
id: string,
path: string // Path URI
}
ImageGeneration = {
type: "ImageGeneration",
id: string,
status: string,
revised_prompt?: string,
result: string,
saved_path?: string
}
Extension =
{ type: "Extension", kind: "clock.sleep",
id: string, durationMs: uint64 }
| { type: "Extension", kind: "web.search",
id: string, query: string,
action: ExtensionWebSearchAction|null, results: JSON[]|null }
| { type: "Extension", kind: "image_gen.generation",
id: string, status: string, revisedPrompt: string|null,
result: string, transparentBackground?: boolean,
failure: ImageGenerationFailure|null, savedPath?: string }
ImageGenerationFailure = {
type: "usageLimitExceeded",
limitId: string,
resetsAt: int64 | null
}
ExtensionWebSearchAction =
{ type: "search", query: string|null, queries: string[]|null }
| { type: "openPage", url: string|null }
| { type: "findInPage", url: string|null, pattern: string|null }
| { type: "other" }
文件、MCP、review 与压缩:
FileChange = {
type: "FileChange",
id: string,
changes: object<path, FileChange>,
status?: "completed" | "failed" | "declined",
auto_approved?: boolean,
stdout?: string,
stderr?: string
}
McpToolCall = {
type: "McpToolCall",
id: string,
server: string,
tool: string,
arguments: JSON,
connectorId?: string,
mcpAppResourceUri?: string,
linkId?: string,
appName?: string,
actionName?: string,
pluginId?: string,
readOnlyHint?: boolean,
status: "inProgress" | "completed" | "failed",
result?: CallToolResult,
error?: { message: string },
duration?: { secs: uint64, nanos: uint32 }
}
EnteredReviewMode = {
type: "EnteredReviewMode",
id: string,
target: ReviewTarget,
user_facing_hint: string
}
ExitedReviewMode = {
type: "ExitedReviewMode",
id: string,
review_output: ReviewOutput | null
}
ContextCompaction = {
type: "ContextCompaction",
id: string
}
为什么同时保存 response_item 与 item_completed?
response_item服务于模型上下文,尽量忠实于 Responses API;item_completed服务于前端历史,把命令、文件改动、MCP、review 等整理成可直接展示的领域对象;- 两者可以指向同一次行为,但不是同一个 schema,也不能假设一一对应;
- replay 会按消费目标选择 projection,而不是把两份都塞给模型。
B.8 compacted:模型历史 checkpoint
CompactedItem = {
message: string,
replacement_history?: ResponseItem[],
replacement_history_metadata?: CodexHarnessMetadata[],
mcp_resource_origins?: {
origins: [{
call_id: string,
turn_id?: string,
tool: string,
connector_id: string,
link_id?: string,
uri: string,
ambiguous_account?: boolean
}],
turns: string[],
current_turn_id?: string
},
window_number?: uint64,
first_window_id?: string,
previous_window_id?: string,
window_id?: string
}
约束:
replacement_history是 checkpoint 后模型应该使用的完整替代历史;replacement_history_metadata与它按下标一一对应,长度必须相等;- 只有至少一条 replacement item 带 metadata 时,writer 才写 metadata 数组;没有 metadata 的位置写默认对象;
- 单独出现
replacement_history_metadata是非法记录; - 旧记录可能没有 replacement history,此时
message仍可转换为一条 assistant 摘要,但恢复能力更弱; - 更老的记录曾把数字窗口号写进
window_id,reader 会把它兼容为window_number。
典型场景:
flowchart LR
H["旧模型历史"] --> C["compacted
replacement_history"]
C --> W["world_state full"]
W --> T["turn_context"]
T --> N["后续 response_item"]
compacted 不删除前面的行。它只告诉模型历史 reducer:“从这里起,使用 replacement history 作为新基线。”
B.9 turn_context:一轮实际用了什么设置
TurnContextItem = {
turn_id?: string,
cwd: string,
workspace_roots?: string[],
current_date?: string,
timezone?: string,
approval_policy: AskForApproval,
approvals_reviewer?: "user" | "auto_review",
sandbox_policy: SandboxPolicy,
permission_profile?: PermissionProfile,
active_permission_profile?: { id: string, extends?: string },
network?: {
allowed_domains: string[],
denied_domains: string[]
},
file_system_sandbox_policy?: RawFileSystemSandboxPolicy,
model: string,
comp_hash?: string,
personality?: "none" | "friendly" | "pragmatic",
collaboration_mode?: CollaborationMode,
multi_agent_version?: "disabled" | "v1" | "v2",
multi_agent_mode?: MultiAgentMode,
realtime_active?: boolean,
effort?: ReasoningEffort,
summary: "auto" | "concise" | "detailed" | "none"
}
summary 是兼容字段,当前仍然写出,但恢复逻辑不再依赖它。multi_agent_mode 也是读取旧 rollout 的 legacy 字段。
主要嵌套类型:
AskForApproval =
"untrusted" | "on-request" | "never"
| { "granular": {
sandbox_approval: boolean,
rules: boolean,
skill_approval: boolean,
request_permissions: boolean,
mcp_elicitations: boolean
}}
SandboxPolicy =
{ type: "danger-full-access" }
| { type: "read-only", network_access?: boolean }
| { type: "external-sandbox", network_access: "restricted"|"enabled" }
| { type: "workspace-write",
writable_roots?: string[],
network_access: boolean,
exclude_tmpdir_env_var: boolean,
exclude_slash_tmp: boolean }
PermissionProfile =
{ type: "managed",
file_system:
{ type: "restricted", entries: FileSystemEntry[],
glob_scan_max_depth?: uint }
| { type: "unrestricted" },
network: "restricted" | "enabled" }
| { type: "disabled" }
| { type: "external", network: "restricted" | "enabled" }
RawFileSystemSandboxPolicy = {
kind: "restricted" | "unrestricted" | "external-sandbox",
glob_scan_max_depth?: uint,
entries?: FileSystemEntry[]
}
FileSystemEntry = {
path:
{ type: "path", path: string }
| { type: "glob_pattern", pattern: string }
| { type: "special", value:
{ kind: "root" | "minimal" | "tmpdir" | "slash_tmp" }
| { kind: "project_roots", subpath?: string }
| { kind: "unknown", path: string, subpath?: string }
},
access: "read" | "write" | "deny",
missing_path_behavior?: "skip"
}
CollaborationMode = {
mode: "default" | "plan",
settings: {
model: string,
reasoning_effort: ReasoningEffort | null,
developer_instructions: string | null
}
}
MultiAgentMode =
"explicitRequestOnly" | "proactive" | { "custom": string }
ReasoningEffort 的具体可选值随模型协议演进,读取方应把它当作字符串枚举处理,不应在 rollout 分析器里写死模型能力。
场景:
- 每个真实 user turn 在计算完本轮模型可见更新后保存一次;
- mid-turn compaction 重建完整上下文后再保存一次;
- resume 用最后一个仍然有效的 user turn context 恢复模型、cwd 和安全基线;
- rollback 必须同时排除被回退 turn 内的
turn_context,否则旧 turn 的模型或权限会泄漏到新时间线。
B.10 world_state:full snapshot 与 merge patch
WorldStateItem = {
full: boolean,
state: object<string, JSON>
}
state 故意是开放 map,而不是封闭字段表。每个 key 对应一个世界状态 section,例如环境、指令、权限、工具可见性或扩展贡献的状态。section 自己拥有内部 schema 和版本演进责任。
B.10.1 当前 section 目录
下面列出当前版本可能写入的核心 section,以及随首方扩展启用后可能出现的 section。它不是对未来 key 的封闭枚举:extension 可以注册新的稳定 ID,并以任意非 null JSON 值作为 snapshot。
| section ID | snapshot | 何时存在 |
|---|---|---|
model | model slug 字符串 | 始终 |
personality | model 与 personality | Personality feature 启用时 |
context_window | AgentPath 字符串 | Token Budget 启用且模型有 context window 时 |
context_window_guidance | guidance 字符串 | Token Budget 配置了非空 guidance 时 |
realtime | realtime 是否 active | 始终 |
agents_md | 当前生效的 AGENTS.md 目录与正文 | 始终;无指令时保存空 object |
permissions | 权限指令指纹与已批准命令前缀 | 完整权限指令启用时 |
approved_command_prefixes | 已批准命令前缀集合 | 不注入完整权限指令时;与 permissions 二选一 |
collaboration_mode | mode、model 与指令指纹 | collaboration mode 指令启用时 |
environments | 环境、日期、时区、网络、文件系统与 subagent 摘要 | environment context 启用时 |
environments_instructions | 是否启用环境使用说明 | 始终 |
apps_instructions | 是否已有可用 Apps 使用说明 | 始终 |
plugins_instructions | 是否已有可用 plugin 使用说明 | 始终 |
tools | deferred tool namespace 到描述的映射 | Deferred Tool World State 启用且映射非空时 |
multi_agent_usage_hint | multi-agent usage hint 的稳定指纹 | 本轮存在 usage hint 时 |
multi_agent_mode | effective mode 与 usage hint 指纹 | 始终 |
skills | selected-environment skills 的渲染状态 | Skills 扩展贡献时 |
orchestrator_skills | orchestrator skills 的渲染状态 | Skills 扩展贡献时 |
host_skills | 前端提供的 skills 渲染状态 | Skills 扩展贡献时 |
git_attribution | 是否启用 Git attribution | Git attribution 扩展贡献时 |
这些 section 的 snapshot schema 如下:
WorldStateHash = string // 对模型可见 fragment 的稳定 SHA-1 指纹
AgentPath = string // 例如 "/root"、"/root/reviewer"
state.model = string
state.personality = {
model: string,
personality?: "none" | "friendly" | "pragmatic"
}
state.context_window = AgentPath
state.context_window_guidance = string
state.realtime = { active: boolean }
state.agents_md = {
directory?: string,
text?: string
}
state.permissions =
| WorldStateHash // legacy
| {
instructions: WorldStateHash,
approved_command_prefixes: string[][]
}
state.approved_command_prefixes = string[][]
state.collaboration_mode =
| "plan" | "default" // legacy
| {
mode: "plan" | "default",
model: string,
instructions?: WorldStateHash
}
state.environments = {
environments: object<string, {
cwd: string,
status: "starting" | "available",
shell?: string,
is_primary?: boolean
}>,
current_date?: string,
timezone?: string,
network?: string,
filesystem?: string,
subagents?: string
}
state.environments_instructions = boolean
state.apps_instructions = boolean
state.plugins_instructions = boolean
state.tools = object<string, string>
state.multi_agent_usage_hint = WorldStateHash
state.multi_agent_mode = {
mode?: "explicitRequestOnly"
| "proactive"
| { custom: string },
usage_hint_hash?: WorldStateHash
}
SkillSectionSnapshot = {
body?: string,
includeInstructions: boolean,
enabled?: boolean
}
state.skills = SkillSectionSnapshot
state.orchestrator_skills = SkillSectionSnapshot
state.host_skills = SkillSectionSnapshot
state.git_attribution = boolean
state.<extension_owned_id> = non-null JSON
保存 snapshot 时会递归移除 object 中值为 null 的字段,因此上面标有 ? 的字段会直接缺席。数组里的 null 不会被这一规则删除。
WorldStateHash 只用于比较两次模型可见 fragment 是否相同。它不能还原原始指令正文,也不应被当作安全哈希或内容寻址 ID。
两种语义:
// 建立新基线
{"type":"world_state","payload":{"full":true,"state":{"environments":{"environments":{"local":{"cwd":"/repo","status":"available"}}},"permissions":{"instructions":"70e11c...","approved_command_prefixes":[]}}}}
// RFC 7386 风格 merge patch
{"type":"world_state","payload":{"full":false,"state":{"environments":{"environments":{"local":{"cwd":"/repo/web"}}},"tools":null}}}
full: true:丢弃旧基线,以state建立完整基线;full: false:把state作为 JSON merge patch 应用到现有基线;- patch 中字段为
null表示删除; - 没有 full baseline 时不能可靠解释 patch,应走保守恢复;
- compaction 后通常重新写 full snapshot,使新窗口可以独立恢复。
这类记录保存的不是“机器当前真实状态”,而是模型最后被告知的世界状态。文件系统可能已继续变化,resume 后仍要重新观察现实。
B.11 多 agent 与安全记录
B.11.1 inter_agent_communication
InterAgentCommunication = {
id?: string,
author: string, // AgentPath
recipient: string, // AgentPath
other_recipients: string[],
content: string,
encrypted_content?: string,
internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough,
trigger_turn: boolean
}
它既是跨 agent 的通信事实,也可以成为接收方模型历史。trigger_turn 区分:
true:消息应该唤醒接收方并触发工作;false:只进入邮箱/历史,等接收方自然运行时消费。
当前 live 接收路径通常会把通信正文转换成 response_item.agent_message,并在它前面追加:
inter_agent_communication_metadata = {
trigger_turn: boolean
}
它只保留“紧随其后的 agent message 会不会触发 turn”的控制语义,不重复正文。完整的 inter_agent_communication 仍是可读取、可持久化的 canonical variant,主要用于旧记录、fork 输入和迁移路径。
B.11.2 security_risk_score
SecurityRiskScore = {
scores: object<string, number>,
sampled_at?: string
}
scores是分类器名称到分数的 map;sampled_at是 RFC 3339 时间;- 它属于 thread-owned 风险状态;
- 它不会进入模型可见 conversation,也不会投影成用户可见 item;
- fork 子 agent 时不会作为普通历史继承。
这体现了安全数据的一个边界:可以持久化用于恢复决策,但不应因此自动暴露给模型或 UI。
B.12 典型写入序列
下面只列记录顺序,省略大部分字段。
B.12.1 新建空 thread
新建时 recorder 可以处于 deferred 状态,文件尚不存在。第一次跨过有意义的持久化边界后才 materialize:
session_meta
因此“创建过内存 Session”不等于“一定存在 rollout 文件”。
B.12.2 普通对话
Legacy:
session_meta
event_msg(task_started)
event_msg(user_message)
turn_context
world_state(full 或 patch)
response_item(message:user)
response_item(reasoning)
event_msg(agent_reasoning)
response_item(message:assistant)
event_msg(agent_message)
event_msg(token_count)
event_msg(task_complete)
Paginated:
session_meta
event_msg(task_started)
turn_context
world_state(full 或 patch)
response_item(message:user)
event_msg(item_completed:UserMessage)
response_item(reasoning)
event_msg(item_completed:Reasoning)
response_item(message:assistant)
event_msg(item_completed:AgentMessage)
event_msg(token_count)
event_msg(task_complete)
两份记录看起来有重复,是因为它们分别服务模型历史与 UI projection。
B.12.3 工具调用
response_item(function_call 或 custom_tool_call)
event_msg(item_completed:CommandExecution / FileChange / McpToolCall ...)
response_item(function_call_output 或 custom_tool_call_output)
关键时序是先调用、后结果。并行工具可以并发执行,但结果按调用顺序回灌 response_item,保持模型历史确定。
审批请求本身通常不持久化。若进程在“用户已批准、外部动作已发生、结果尚未落盘”之间崩溃,rollout 只能表达“调用存在、结果未知”,不能据此自动重试。
B.12.4 Steer
Steer 不创建新 turn:
... 当前 turn 的已有记录
response_item(message:user, metadata.turn_id = 当前 turn)
... 下一次 step 继续
它在步骤边界进入历史,而不是插入已经发出的模型请求中。Paginated UI 还会有对应 UserMessage completed item。
B.12.5 Interrupt 与 recover
正常中断:
event_msg(task_started, turn_id=T)
... 已完成的 response_item / item_completed
event_msg(turn_aborted, turn_id=T, reason="interrupted")
同进程 recover 沿用 T:
... 新的 response_item / item_completed
event_msg(task_complete, turn_id=T)
如果进程崩溃,只看到 task_started 而没有 terminal event,reader 应把它理解为 stale in-progress turn,而不是仍在后台运行的任务。
B.12.6 Compaction
... 旧历史
compacted(replacement_history, window_id ...)
world_state(full)
turn_context
... 新窗口的增量历史
Legacy 还可能写 event_msg(context_compacted) 供 UI replay;paginated 用 item_completed:ContextCompaction。
B.12.7 Resume
Resume 不复制文件:
读取原 rollout
→ replay 有效历史与设置
→ 创建新的运行时连接和 channel
→ 继续向原 rollout 尾部追加
恢复过程会分别寻找:
- 第一条 canonical
session_meta; - 最新有效
compacted; - 最新有效
turn_context; world_state的最近 full baseline 与后续 patch;- 最新
token_count; - rollback 后仍然有效的 turn;
- 未闭合的最后 turn。
B.12.8 Fork
Copy 型 fork:
子 rollout.session_meta(forked_from_id=父 thread)
复制选定的父历史前缀
追加子线程自己的记录
Reference 型 fork:
子 rollout.session_meta(
history_base={
thread_id: 父 rollout_id,
end_ordinal_exclusive: N,
end_byte_offset: B
}
)
只追加子线程后缀
引用边界是 exclusive。读取子线程完整历史时,先读父 rollout 的 [0, N),再读子 rollout。
B.12.9 Rollback
Legacy rollback 只追加 marker:
... turn A
... turn B
event_msg(thread_rolled_back, num_turns=1)
物理文件仍保留 turn B,模型历史 reducer 与 UI reducer 在 replay 时把它从有效视图中排除。连续 marker 可以累计。
B.12.10 Revert
Paginated history 不写 thread_rolled_back 来完成 revert,而是:
旧 rollout:保持不变
新 rollout:session_meta(id=同一 thread, history_base=目标前缀)
状态库:把 thread 当前指针原子切到新 rollout
因此:
- thread ID 不变;
- rollout ID 改变;
- 旧时间线仍可审计;
- 并发切换必须检测指针冲突;
- 外部文件与网络副作用不会被撤销。
B.12.11 多 agent
父线程:
response_item(function_call: spawn_agent)
event_msg(item_completed:CollabAgentToolCall)
response_item(function_call_output)
子线程:
session_meta(
session_id=父树 session_id,
id=新的 thread_id,
parent_thread_id=父 thread_id,
source={subagent:{thread_spawn:{...}}}
)
... 继承的上下文或 history_base
inter_agent_communication_metadata(trigger_turn=...)
response_item(agent_message, author=..., recipient=...)
... 子线程自己的 turn
每个 agent 独立记账。跨 thread 消息不是一个跨文件原子事务,因此通信必须依赖稳定 ID、sender/recipient 与幂等处理,而不能假设 exactly-once。
B.13 读取、容错与兼容规则
一个可靠 reader 至少应遵守以下规则:
- 逐行解析。 空行忽略;单行 JSON 损坏时记录 parse error 并继续,避免一条坏记录拖垮整份历史。
- 第一条元数据定身份。 第一条有效
session_meta是当前 rollout 的 canonical metadata;后续同类记录可能来自复制的父历史。 - 按
history_mode解释事件。 不要把 legacy 事件和 paginateditem_completed同时当成两份独立用户内容,否则 UI 会重复。 - 按关联键归并。 turn 用
turn_id,工具调用用call_id,item 用id;不要用行号猜关系。 - 按 ordinal 而非文件行号分页。 注释不存在于真实文件,但空行、损坏行和共享前缀都会让物理行号失去业务意义。
- 先应用 rollback,再找最新状态。 被回退 turn 中的
turn_context、world state patch 和 item 不能污染有效历史。 - Compacted 是替换基线。 有
replacement_history时,模型上下文从它继续;不是把它再追加到全部旧历史末尾。 - World state 先 full 后 patch。 patch 使用 merge semantics;缺少 baseline 时不能猜。
- Replay 不执行工具。 调用没有结果只代表 outcome unknown,不代表动作未发生。
- 保留未知字段。 做搬运、迁移或归档时不要重建成自己理解的最小对象,否则会丢掉新版本字段。
B.13.1 历史兼容形态
旧 rollout 可能出现:
session_meta缺少session_id,此时用id补齐;turn_started/turn_complete,读取为当前的task_started/task_complete;agent_type,读取为agent_role;on-failureapproval policy,读取为on-request;guardian_subagentreviewer,读取为auto_review;none文件访问模式,读取为deny;- 数字
window_id,读取为window_number; - 旧式 permission profile、sandbox policy、review target、命令 cwd;
- 已退休的
ghost_snapshot、guardian_assessment、thread_name_updated、undo_completed等记录; - 来自相邻版本或实验 writer、但当前 reader 不认识的顶层记录。
兼容策略不是“所有旧字段永远参与当前语义”。有些记录会被归一化,有些只为 UI 迁移读取,有些明确跳过;无法解析的行进入 parse error 计数并被隔离。
这也说明为什么不能用一份静态 JSON Schema 粗暴验证整份历史:读取协议是“schema + alias + normalization + replay policy”的组合。
B.14 常用检查命令
查看每种顶层记录数量:
jq -r '.type' rollout.jsonl | sort | uniq -c
查看 response_item 子类型:
jq -r 'select(.type == "response_item") | .payload.type' rollout.jsonl \
| sort | uniq -c
查看 durable event 子类型:
jq -r 'select(.type == "event_msg") | .payload.type' rollout.jsonl \
| sort | uniq -c
只看 turn 边界:
jq -c '
select(
.type == "event_msg"
and (.payload.type == "task_started"
or .payload.type == "task_complete"
or .payload.type == "turn_aborted")
)
| {ordinal, timestamp, event: .payload.type, turn_id: .payload.turn_id}
' rollout.jsonl
检查 paginated ordinal 是否连续:
jq -r 'select(.ordinal != null) | .ordinal' rollout.jsonl \
| awk 'NR == 1 { expected = $1 } $1 != expected { print "gap:", expected, "->", $1 } { expected = $1 + 1 }'
查看工具调用是否有结果:
jq -r '
select(.type == "response_item")
| .payload
| select(.type == "function_call" or .type == "function_call_output"
or .type == "custom_tool_call" or .type == "custom_tool_call_output")
| [.type, .call_id] | @tsv
' rollout.jsonl
这些命令适合检查结构,不应把包含 reasoning、工具输出或凭证片段的完整 rollout 上传到第三方服务。
B.15 一张总图:一份文件如何支撑多种恢复
flowchart TD
META["session_meta
身份 / lineage / history mode"] --> LOAD["加载 rollout"]
RESP["response_item
模型历史"] --> LOAD
EVT["event_msg
turn / UI / token"] --> LOAD
CP["compacted
replacement history"] --> LOAD
WS["world_state
full + patch"] --> LOAD
TC["turn_context
有效设置"] --> LOAD
MAIL["inter_agent_communication
跨 agent 消息"] --> LOAD
RISK["security_risk_score
安全状态"] --> LOAD
LOAD --> MH["模型上下文 projection"]
LOAD --> UI["thread / turn / item projection"]
LOAD --> ST["Session 设置与状态"]
LOAD --> IX["SQLite 查询索引"]
MH --> RESUME["Resume:同 thread 继续追加"]
MH --> FORK["Fork:新 thread 继承前缀"]
MH --> ROLLBACK["Rollback / Revert:改变有效历史"]
最值得记住的不是字段数量,而是三层分工:
response_item保存模型认知。 它回答“下一次采样应该看到什么”。event_msg保存生命周期与展示事实。 它回答“一个 turn 如何开始、结束,前端应怎样重建历史”。- checkpoint 与 metadata 保存解释这些事实所需的坐标。 它们回答“这是哪个 thread、哪条时间线、哪套设置、哪个上下文窗口”。
因此,Codex session JSONL 不是把 Session 对象序列化到磁盘,也不是简单的聊天导出。它是一份由多个 reducer 共同解释的 append-only protocol:同一组记录分别投影出模型记忆、用户界面、运行时基线和历史 lineage。