codex

Identity

  • Native format: codex-rollout. Session logs, called “rollouts”, are stored as JSONL under ~/.codex/sessions/YYYY/MM/DD/rollout-<timestamp>-<thread id>[_<rollout id>].jsonl. A thread can span several rollout files: a revert writes a new rollout (a segment) whose file name adds a rollout ID. Rollouts that are no longer active may be recompressed to .jsonl.zst or moved to archived_sessions/; the path is not identity (ASR-053). The SQLite stores beside them are derived indexes, except for titles, pins, sections, projects, pull request attachments, goals and the queue of input not yet delivered.
  • Session ID: the session_meta id (the thread ID), the same across segments. The spawn tree’s root session_id is kept in _meta. Resolve file aliases to their targets before identifying segments. A broken alias means the file is unavailable (unreadable or unknown in a manifest), never deleted_by_retention. When the header is only partly written (torn), recover the thread ID from the canonical native basename only when the native thread index corroborates it; keep com.openai.codex/identityRecovery, emit gaps and leave lineage unknown. Otherwise report an unidentified source. This native-source recovery rule does not relax ASR-020 for ASR bundles. A child rollout can contain its parent’s session_meta as well as its own; the child’s own metadata is the entry whose id identifies the child thread.
  • Entity keys: callId is call_id; inferenceId is token_usage_record.response_id. Legacy per-turn usage has no inference ID and uses usage_summary. turnId is the recorded turn_id. The native position a record’s identity comes from is (native segment ID, ordinal or line, sub-slot), kept at com.openai.codex/position; it is not an ASR seq tuple. Allocate increasing safe-integer seq in documented segment creation order and native item order, then sub-slot order. Revert segments reuse the ordinal numbering they inherit. Do not invent creation order when native evidence leaves it ambiguous; preserve the evidence and declare order.
  • Model: usage records carry no model. model.requested comes from the turn’s turn_context.model with requestedSource: turn_config (ASR-325); model.id stays absent.
  • Messages: each part’s author and origin come from content_item_kinds (aligned one to one with content): user.text and user.image → human, prompt; agents_md.instructions, environments.environment_context, host_skills.instructions, permissions.instructions, collaboration_mode.instructions and the other *.instructions kinds → harness, injected; hooks.additional_context → hook, injected; multi_agent.subagent_notification → agent, subagent_report; generic.developer_instructions → injected with author absent. Other kinds leave author absent. These are part-level values (ASR-224). Assistant phase commentary → commentary, final_answer → final. message.encrypted_content and encrypted parts of agent messages → opaque parts.
  • Messages between agents: agent_message items and inter_agent_communication → a message with author: agent, origin: inter_session, from: {session: <root thread>, address: <author path>}, recipients (recipient as primary, other_recipients as additional, each scoped by the root thread), and triggersTurn from trigger_turn.
  • Tools: function_call, custom_tool_call and local_shell_call → tool_call with requestedBy: model. Executions nested in a code-mode cell (CommandExecution, McpToolCall, FileChange items whose IDs are not model call IDs) → a tool_call with requestedBy: code and the item ID as callId, and its tool_result; parentCallId only from an explicit initiating model-call ID. cell_id identifies a host cell shared by execution and waiting operations. It does not identify a model call; keep it in com.openai.codex/cellId. Read raw execution fields as well as the typed dispatch data: deserializing data for replay can omit IDs and information about which executions occurred. CommandExecution.source: user_shell → requestedBy: user. McpToolCall server and tool → provider {kind: mcp, server, tool, identity: exact}; calls through an app connector (connectorId, appName) → provider.kind: connector with name. process_id → exec.processId, preserving its native string or safe-integer type. This is provenance, never a terminal reference. Approvals, hook runs and terminal lifecycle events are not written to rollouts: a converter writes no permission or hook records and no invented terminal records for them. A process ID alone does not establish a terminal lifecycle.
  • Questions: request_user_input calls and asynchronous questions (AgentMessage with delivery: async and questions) → input_request with mode: questions, callId, blocking: false for asynchronous ones, and question secret flags from the call; answers from the call’s output or the retained verified answers. Secret answers are never recorded (ASR-283).
  • Turns: task_started → state running; task_complete → idle (stopReason: end_turn, or error with error); turn_aborted → idle with stopReason: cancelled (unmapped reasons retained in com.openai.codex/abortReason). turn_attribution.turn_trigger → trigger.native; initiating_agent_path → trigger.source {session: <root thread>, address}. root_turn_id → state.rootTurn {session: <root thread>, turnId}. parent_turn_id → state.parentTurn with session set to the parent thread when the turn was started by the parent; when the source does not say which thread it is in, parentTurn is not written.
  • Configuration: each turn_context → a config_change snapshot (snapshot: true, turnId) with model, effort, permissionMode from approval_policy, sandbox from sandbox_policy (mode, network, writableRoots), and the other members in options. thread_settings_applied → a config_change that is a change; service_tier goes in options. Header: originator → client.name; thread_source → header trigger.native.
  • Compaction: compacted → compaction, with compactionId from window_id and replaces.throughId from the native checkpoint predecessor when available. replacement_history[].retained_source.id.message_id maps to retains only when it identifies an earlier local active-path record unambiguously; retained_source.complete: false sets capture: truncated. Keep the entire replacement_history, including shortened bytes, fresh instructions and order, at com.openai.codex/compaction. Declare _com.openai.codex/compaction_structure when ASR cannot express that replacement. External retained refs are not supported in this revision. message supplies the summary; encrypted checkpoint state uses kind: provider_native and opaque. CompactedItem records neither a trigger nor a link to the call that started it: a nearby new_context is not evidence. Set trigger only from an explicit native trigger or a qualified structural contract. compaction_response_id identifies the compaction inference when present. thread_rolled_back.num_turns is a rollback boundary only under the source’s native retained-turn contract; do not count UI messages as turns.
  • Instructions: AGENTS.md, skills and permission text reach the model as developer or user message parts; those parts are what the context view shows. instructions records come only from world_state records with full: true.
  • Memory: loaded memories reach the model as memories.instructions parts. Memory writes happen in a memory-consolidation thread (source internal_memory_consolidation, or subagent memory_consolidation): a file_change under the Codex memories directory (~/.codex/memories/) in such a thread also gets a memory_change with store: "codex_memories", scope: "user", op: "consolidate", target.path and cause.callId.
  • Other: update_plan → plan with planType: items; Plan items → planType: document; goals (thread_goal_updated) → planType: goal with status, budget.tokens from token_budget, and used from tokens_used and time_used_seconds. Pull request attachments → vcs pr_link with evidence: reported. The generated title → titleSource: model; the user-set name → titleSource: user; pins and archive → flags; projects → session_info.project. Realtime voice transcript segments → messages with text parts that carry derivation {method: transcription}. Reasoning items carry encrypted_content (visibility: encrypted, opaque.format: openai.encrypted_content) and a summary that is often empty (preserve []). In legacy history mode, command exit codes and output are not kept outside the model-facing output text: declare exec.exit_code.
  • Goal identity: prefer an authoritatively associated native goal_id if available. The protocol goal has no ID: derive planId from (threadId, createdAt), a goal type tag and derivation version, with idSource: derived, only when that tuple is stable and unique in the thread. If two goals produce the same derived ID, do not merge them. Preserve them as native goal evidence until a documented method of establishing identity distinguishes them. Do not match a database goal ID to a goal because the objective text or timestamps look alike.
  • Alternate representations: join response and UI items only by unique, same-scope IDs under their documented correspondence. Keep user UI items separate with displayContent only. UI {type: image, image_url} maps to resource_link with uri equal to image_url and a native display name when present, otherwise the URI as name; never claim inline image bytes from a URI. DynamicToolCallItem.error strings map to {message: <native string>}. Decode PathUri cwd under the source’s URI semantics (ASR-477).
  • Current state: titles, flags, projects, goals and read-state rows read at export time use snapshot assembly in the capture tail. Rows with a stable identity do not need idScope: capture.

Position basis

Use only positions established by the stated format and mapping evidence. Current-state rows without transcript positions form the snapshot tail. A protocol commit boundary takes precedence over physical EOF; uncommitted bytes are not a gap.

Destinations

Codex: native facts without a standard field

  • Lineage: subagent threads carry source.subagent.thread_spawn.parent_thread_id → spawnedBy (native_field), with role from agent_role. forked_from_id → forkedFrom; the fork boundary is forked_from_ordinal_exclusive: forkedFrom.ordinal is that value minus 1. history_base {thread_id, end_ordinal_exclusive} names a rollout (a segment), not a thread, despite its field name: resolve it to its thread before writing a session reference. When it names an earlier segment of the same thread, the new segment is a revert of that thread. Write the new segment’s records in the same stream after a rewind with reason: rollback. Set to to the last record that participates in context and has an ordinal below end_ordinal_exclusive in the identified segment’s active prefix. When it names another thread’s rollout, write inherits {mode: referenced, from, throughOrdinal: end_ordinal_exclusive − 1}. Legacy forks copy the parent’s items into the child: inherits {mode: copied, evidence: native_structure} only under the legacy native copied-prefix contract; subagent_history_start_ordinal marks where inherited records end (localThroughId). A subagent created from copied context is both spawnedBy and forkedFrom. root may be claimed when source is not a subagent source and there is no forked_from_id, and no history_base naming another thread.
  • Lineage ordinals after reverts: history_base.thread_id identifies a native segment, and end_ordinal_exclusive selects its retained prefix. Follow that segment’s own history-base chain and apply each recorded boundary. Then select the last item below the exclusive bound on that active prefix, using the item’s final context sub-slot. Prefer a record ref once mapped. A bare inclusive ordinal without an identified segment or prefix is ambiguous after a revert; leave the boundary unknown, retain native lineage metadata and declare lineage. Never select the most recent segment by timestamp or the last matching ordinal. Segment creation order comes only from native metadata or the recorded history-base relationship.
These contracts apply to source c587d6e; the observed 0.144.3 to 0.163.0 range does not imply that every field exists in every release.

Usage

  • Usage: token_usage_record → inference with input.total, includesCache: true, cacheRead, cacheWrite, output.total, output.reasoning and total; its turn and thread totals → usage_summary with cumulative: true. token_count repeats these totals (ASR-311). token_count.rate_limits → usage_summary.quota: plan from plan_type, and one window each for primary and secondary (usedPercent from used_percent, resetsAt from resets_at). No cost is recorded.

Lane and unassigned facts

Facts without established session ownership use manifest _meta["com.openai.codex/unassigned"] with the common shape. Selection may retain these facts with any session subset, including an empty subset, but must not imply session ownership. State the selection policy in the export report.

Losses

Declare every applicable loss under ASR-157. Missing model rendering, accounting joins, lineage or exact context boundaries remain losses when this entry does not qualify them. Unknown ownership is not itself lost data. Omitted unassigned facts use a documented vendor loss or report, not a new core category.

Evidence

Basis: source (openai/codex at c587d6e), and observed (local rollouts of versions 0.144.3 to 0.163.0). Evidence applies only to the named formats and explicit mappings above. A real capture covers only exercised behavior. A writer probe run without a model covers persistence, not model or tool execution. Authored tests do not qualify an agent version. Unlisted formats and link mappings remain unqualified.