Skip to content

Latest commit

 

History

History
85 lines (69 loc) · 3.94 KB

File metadata and controls

85 lines (69 loc) · 3.94 KB

Codex CLI rollout schema notes

Compiled 2026-06-11 from the openai/codex source (codex-rs/protocol), public parsers of the format (ccusage, tokscale, codex-trace, codex-wrapped, agent-sessions), and their documented gotchas. As with the Claude Code notes: defensive parsing only, nothing here is a contract.

Layout on disk

$CODEX_HOME (default ~/.codex)/
  sessions/YYYY/MM/DD/rollout-<timestamp>-<uuid>.jsonl   <- one per session
  archived_sessions/YYYY/MM/DD/rollout-*.jsonl           <- archived copies
  history.jsonl                                          <- prompt history only

A file can exist in both sessions/ and archived_sessions/; count it once (we key on the path relative to each base, active copy wins).

Line envelope (modern, >= ~0.44)

Every line: {"timestamp": ISO8601, "type": T, "payload": {...}} with T:

type what it is
session_meta first line: id, cwd, cli_version, git, and forked_from_id / parent_thread_id when forked
turn_context per-turn settings; canonical source of model and cwd
response_item a Responses-API item (see below)
event_msg UI events; only token_count and turn_aborted matter to us
compacted history compaction

response_item payload types:

  • message: role user/assistant/developer, content list of input_text/output_text blocks. User messages whose text starts with < (<user_instructions>, <environment_context>, <system-reminder>) are injected by the harness, not typed.
  • function_call: name (shell, apply_patch, MCP tools...), arguments (a JSON string), call_id. local_shell_call carries action.command instead. apply_patch arguments hold the patch text under input; file paths come from *** Add|Update|Delete File: lines.
  • function_call_output / custom_tool_call_output: call_id plus output, which is a plain string or a JSON string like {"output": "...", "metadata": {"exit_code": 1, ...}}. Calls and outputs are flat and joined by call_id, often lines apart.

event_msg user_message/agent_message duplicate the response_items; ignore them or you double-count prompts.

Token accounting

event_msg / token_count payload:

{"type":"token_count","info":{
  "last_token_usage":{"input_tokens":1000,"cached_input_tokens":250,
    "output_tokens":125,"reasoning_output_tokens":75,"total_tokens":1200},
  "total_token_usage":{...same shape, cumulative...},
  "model_context_window":272000}}
  • Prefer last_token_usage (per-turn). Older files only have the cumulative total_token_usage: credit the field-wise delta against the previous snapshot.
  • cached_input_tokens is a subset of input_tokens; bill input - cached at the input rate and cached at the cached rate. Very old entries spell it cache_read_input_tokens.
  • reasoning_output_tokens is already included in output_tokens.
  • info can be null; skip those lines.
  • Cumulative counters can regress (stale snapshot, compaction, fork reset); on regression reseed the baseline and credit nothing.

Version gotchas

  • No token telemetry at all before 2025-09-06; sessions parse, cost is 0.
  • Early-Sept-2025 files have token_count but no turn_context: no model name. We fall back to gpt-5 for pricing (and the info.model field on newer token_counts when present).
  • Oldest format (~2025-08): line one is a bare meta object ({"id","timestamp","cwd",...}), the rest are bare response items with no envelope and no timestamps.
  • Forked sessions (forked_from_id set) replay the parent's token_count rows at the top of the file; their timestamps predate the fork's session_meta, which is how we skip them (baseline only).
  • turn_aborted is the user pressing Esc: we count it as an interrupt.