Skip to content

Codex parser: populate TurnRecord.fidelity #84

@willwashburn

Description

@willwashburn

Context

PR #76 (#41 first cut) introduced TurnRecord.fidelity (granularity + per-field coverage + derived class) and the helpers EMPTY_COVERAGE, classifyFidelity, makeFidelity in @relayburn/reader. The Claude parser populates fidelity on every turn (packages/reader/src/claude.ts:255,1049 via buildClaudeFidelity at :604).

The Codex parser does not. Quoting PR #76's deferred-work paragraph:

Codex and OpenCode parsers (they still emit no fidelity; consumers treat absence as best-effort full for backward compat).

packages/reader/src/codex.ts constructs TurnRecord objects at :513-543 without any fidelity field. As a result, summarizeFidelity (packages/analyze/src/fidelity.ts) buckets every Codex turn under unknown, and hasMinimumFidelity(undefined, …) passes silently — the exact "0 vs unknown" ambiguity #41 was filed to fix.

Proposal

Populate TurnRecord.fidelity in parseCodexSessionIncremental (packages/reader/src/codex.ts) on every emitted turn, mirroring the Claude approach in buildClaudeFidelity.

Coverage for Codex rollouts (event_msg.token_count payload at :283-294):

  • hasInputTokens: truetotal_token_usage.input_tokens is the source of truth.
  • hasOutputTokens: truetotal_token_usage.output_tokens.
  • hasReasoningTokens: truereasoning_output_tokens is surfaced; this is one of the few sources where it is.
  • hasCacheReadTokens: truecached_input_tokens.
  • hasCacheCreateTokens: false — Codex rollouts have no ephemeral cache-create concept.
  • hasToolCalls: truefunction_call / custom_tool_call items are captured.
  • hasToolResultEvents: truefunction_call_output / custom_tool_call_output items are captured (:430-449).
  • hasSessionRelationships: false for now — Codex rollouts have no parent-tracking path (per Parity approach: Codex and OpenCode spawn-tagging + incremental ingest without native hooks #63); flip when the spawn-tagging substrate lands.
  • hasRawContent: true — full content capture path exists when contentMode === 'full'.

Granularity: 'per-turn'. The cumulative-delta arithmetic in finalizeTurn (:597) gives true per-turn token counts.

If a token-count event is absent for a turn (no task_complete-bound total_token_usage observed before the turn closes), set hasInputTokens / hasOutputTokens / hasReasoningTokens / hasCacheReadTokens to false for that turn so the resulting class is partial rather than a silent zero.

Also follow Claude's pattern of treating hasToolCalls / hasToolResultEvents / hasRawContent as capability flags (true even when a particular turn has no tools), not presence.

Acceptance criteria

  • Every TurnRecord emitted by parseCodexSession / parseCodexSessionIncremental carries a fidelity field.
  • granularity === 'per-turn'.
  • Coverage flags follow the matrix above; hasReasoningTokens is true when a Codex token_count was observed for the turn.
  • A turn whose source omitted total_token_usage reports class === 'partial', not 'full', and the missing usage flags are falseusage numeric fields can still be 0 (the existing post-hoc default) but the coverage flag is the honest signal.
  • New tests in packages/reader/src/codex.test.ts cover at minimum: a full-fidelity turn (all usage fields present), a turn missing token_count (partial), and a turn with both function_call and function_call_output (tool-result coverage stays true).
  • summarizeFidelity over a real Codex session yields unknown === 0 for the produced turns.

Out of scope

Refs

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions