附录 B Codex Session JSONL 存储格式

第十章从设计角度解释了 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": "测试已经通过。" }
    ]
  }
}

公共字段:

字段类型含义
timestampstring记录写入时间,UTC RFC 3339,当前 writer 精确到毫秒
ordinaluint64,可省略paginated history 的严格递增序号;legacy history 不写
typestring顶层记录类型,使用 snake_case
payloadobject该类型的具体载荷
metadataobject,可省略目前只用于 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 种:

typepayload解决的问题
session_metaSessionMetaLine这份 rollout 属于谁、从哪里来、采用哪种历史模式
response_itemResponseItem模型真正看到的对话、reasoning、工具调用与结果
inter_agent_communicationInterAgentCommunicationagent 之间的持久化消息
inter_agent_communication_metadata{ trigger_turn }在裁剪或迁移历史时保留消息的唤醒语义
compactedCompactedItem用 replacement history 建立新的模型历史基线
turn_contextTurnContextItem某个真实 user turn 实际使用的模型、目录与安全设置
world_stateWorldStateItem模型可见世界状态的 full snapshot 或 merge patch
security_risk_scoreSecurityRiskScorethread 级风险分类快照,不进入模型上下文或 UI 历史
event_msgEventMsg 的 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
timestampsession 创建时间;与外层每行写入时间不是同一个概念
cwdsession 建立时的工作目录
originator创建该 session 的产品或调用方标识
cli_versionwriter 版本,用于兼容诊断
sourcesession 从 CLI、IDE、exec、MCP、内部任务还是 sub-agent 创建
thread_source更偏分析用途的来源分类;自定义 feature 也编码为字符串
agent_*子 agent 的昵称、角色和 canonical path
model_provider初始 provider;可能为 null
base_instructionsthread 级基础指令与来源;旧记录可能为 null
dynamic_toolsthread 创建时冻结的前端动态工具 spec
selected_capability_roots前端选中的能力根
memory_mode记忆模式;当前常见特殊值为 "disabled"
history_mode决定后续 event_msg 的持久化形态
history_basepaginated 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_idhistory_base 说明来源;
  • revertid 不变,文件名中的 rollout_id 变化,history_base 指向被保留的旧前缀;
  • sub-agentsession_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字段
messageid?, role, content: ContentItem[], phase?: "commentary" | "final_answer", internal_chat_message_metadata_passthrough?
agent_messageid?, author, recipient, content: AgentMessageInputContent[], internal_chat_message_metadata_passthrough?
reasoningid?, summary: [{type:"summary_text",text}], content?: [{type:"reasoning_text"|"text",text}], `encrypted_content: string
local_shell_callid?, `call_id: string
function_callid?, name, namespace?, arguments: string, encrypted_function_args?, call_id, internal_chat_message_metadata_passthrough?
tool_search_callid?, `call_id: string
function_call_outputid?, call_id, output: FunctionCallOutput, internal_chat_message_metadata_passthrough?
custom_tool_callid?, status?, call_id, name, namespace?, input: string, internal_chat_message_metadata_passthrough?
custom_tool_call_outputid?, call_id, name?, output: FunctionCallOutput, internal_chat_message_metadata_passthrough?
tool_search_outputid?, `call_id: string
web_search_callid?, status?, action?, internal_chat_message_metadata_passthrough?
image_generation_callid?, status, revised_prompt?, result, internal_chat_message_metadata_passthrough?
compactionid?, encrypted_content, internal_chat_message_metadata_passthrough?
context_compactionid?, 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_idfunction_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

事件legacypaginated用途
task_startedturn 起点;读取时也接受别名 turn_started
task_completeturn 正常终点;读取时也接受别名 turn_complete
turn_abortedturn 中止终点
token_count恢复累计用量与窗口大小
thread_goal_updatedthread 长期目标状态
thread_rolled_backlegacy 逻辑 rollback marker
thread_settings_appliedthread 有效设置快照
item_completedPlan / clock.sleep全部paginated UI 历史的 canonical item
user_messagelegacy UI replay
agent_messagelegacy UI replay
agent_reasoninglegacy UI replay
agent_reasoning_raw_contentlegacy UI replay
entered_review_modelegacy review UI
exited_review_modelegacy review UI
patch_apply_endlegacy 文件改动 UI
context_compactedlegacy compaction UI
mcp_tool_call_endlegacy MCP UI
web_search_endlegacy 搜索 UI
image_generation_endlegacy图片生成 UI
sub_agent_activitylegacy 多 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_itemitem_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 IDsnapshot何时存在
modelmodel slug 字符串始终
personalitymodel 与 personalityPersonality feature 启用时
context_windowAgentPath 字符串Token Budget 启用且模型有 context window 时
context_window_guidanceguidance 字符串Token Budget 配置了非空 guidance 时
realtimerealtime 是否 active始终
agents_md当前生效的 AGENTS.md 目录与正文始终;无指令时保存空 object
permissions权限指令指纹与已批准命令前缀完整权限指令启用时
approved_command_prefixes已批准命令前缀集合不注入完整权限指令时;与 permissions 二选一
collaboration_modemode、model 与指令指纹collaboration mode 指令启用时
environments环境、日期、时区、网络、文件系统与 subagent 摘要environment context 启用时
environments_instructions是否启用环境使用说明始终
apps_instructions是否已有可用 Apps 使用说明始终
plugins_instructions是否已有可用 plugin 使用说明始终
toolsdeferred tool namespace 到描述的映射Deferred Tool World State 启用且映射非空时
multi_agent_usage_hintmulti-agent usage hint 的稳定指纹本轮存在 usage hint 时
multi_agent_modeeffective mode 与 usage hint 指纹始终
skillsselected-environment skills 的渲染状态Skills 扩展贡献时
orchestrator_skillsorchestrator skills 的渲染状态Skills 扩展贡献时
host_skills前端提供的 skills 渲染状态Skills 扩展贡献时
git_attribution是否启用 Git attributionGit 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 至少应遵守以下规则:

  1. 逐行解析。 空行忽略;单行 JSON 损坏时记录 parse error 并继续,避免一条坏记录拖垮整份历史。
  2. 第一条元数据定身份。 第一条有效 session_meta 是当前 rollout 的 canonical metadata;后续同类记录可能来自复制的父历史。
  3. history_mode 解释事件。 不要把 legacy 事件和 paginated item_completed 同时当成两份独立用户内容,否则 UI 会重复。
  4. 按关联键归并。 turn 用 turn_id,工具调用用 call_id,item 用 id;不要用行号猜关系。
  5. 按 ordinal 而非文件行号分页。 注释不存在于真实文件,但空行、损坏行和共享前缀都会让物理行号失去业务意义。
  6. 先应用 rollback,再找最新状态。 被回退 turn 中的 turn_context、world state patch 和 item 不能污染有效历史。
  7. Compacted 是替换基线。replacement_history 时,模型上下文从它继续;不是把它再追加到全部旧历史末尾。
  8. World state 先 full 后 patch。 patch 使用 merge semantics;缺少 baseline 时不能猜。
  9. Replay 不执行工具。 调用没有结果只代表 outcome unknown,不代表动作未发生。
  10. 保留未知字段。 做搬运、迁移或归档时不要重建成自己理解的最小对象,否则会丢掉新版本字段。

B.13.1 历史兼容形态

旧 rollout 可能出现:

  • session_meta 缺少 session_id,此时用 id 补齐;
  • turn_started / turn_complete,读取为当前的 task_started / task_complete
  • agent_type,读取为 agent_role
  • on-failure approval policy,读取为 on-request
  • guardian_subagent reviewer,读取为 auto_review
  • none 文件访问模式,读取为 deny
  • 数字 window_id,读取为 window_number
  • 旧式 permission profile、sandbox policy、review target、命令 cwd;
  • 已退休的 ghost_snapshotguardian_assessmentthread_name_updatedundo_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:改变有效历史"]

最值得记住的不是字段数量,而是三层分工:

  1. response_item 保存模型认知。 它回答“下一次采样应该看到什么”。
  2. event_msg 保存生命周期与展示事实。 它回答“一个 turn 如何开始、结束,前端应怎样重建历史”。
  3. checkpoint 与 metadata 保存解释这些事实所需的坐标。 它们回答“这是哪个 thread、哪条时间线、哪套设置、哪个上下文窗口”。

因此,Codex session JSONL 不是把 Session 对象序列化到磁盘,也不是简单的聊天导出。它是一份由多个 reducer 共同解释的 append-only protocol:同一组记录分别投影出模型记忆、用户界面、运行时基线和历史 lineage。

分类:Agent Harness标签:#agent #harness #codex