Facts established by reading live data on camden, not from vendor documentation. Each one cost real work to establish and several contradict what a reasonable person would assume. Re-verify before depending on one; do not re-derive one that is still true.
The codex-lb join exists and works
Three resp_... ids taken from a current codex-personal session all resolved into the same-hour
/opt/codex-lb/data/codex-lb/conversation-archive/<hour>.jsonl.gz. The id in
token_usage_record.response_id is the same id space codexlb2otel keys Turn.ResponseID on.
It is still optional, for three reasons that are all independent of each other:
- codex-lb is being retired and has been turned off on camden.
model_provideris not stable within a namespace. Bothcodex-lbandopenaiappear across sessions incodex-personal. Never infer the provider for a namespace; read it per session.- The conversation archive retains only recent hours. Transcripts go back 90 days hot and forever cold, so most transcripts have no archive to join to even while codex-lb is running.
Both clients already export their own OTel, to the same collector
This was the assumption most likely to produce a duplicated metric set.
- Claude Code:
CLAUDE_CODE_ENABLE_TELEMETRY=1, metrics, logs and traces tocamden:4318. Personal additionally setsOTEL_LOG_USER_PROMPTS,OTEL_LOG_ASSISTANT_RESPONSES,OTEL_LOG_TOOL_DETAILSandOTEL_LOG_TOOL_CONTENT, so prompt and response bodies already ship. - Codex:
[otel]inconfig.toml, logs straight to the Grafana OTLP gateway and metrics tocamden:4318, withlog_user_prompt = true. - camden’s Alloy listens on 4317 and 4318. The receiver comes from Fleet Management remote config,
not from
/etc/alloy/config.alloy, so grepping the local file for a port finds nothing.
This service exports to the same localhost:4318 and therefore needs no Grafana credential.
What native telemetry does not cover
- Codex has no native cost anywhere. Claude Code’s
claude_code.cost.usageis live but covers Claude only. - Neither emits a latency histogram. Claude Code’s
claude_code.tool_resultlog event carriesduration_msas an attribute, which is not the same thing. - Neither covers history. Native telemetry begins when it was switched on; the transcripts are retroactive and already centralised on camden.
- Neither emits per-response token decomposition, the ephemeral cache TTL split, sub-agent topology, compaction accounting, rate-limit headroom, or shell exit codes.
The archive itself
49 GB, roughly 23,000 files, ten namespaces:
claude-personal 7571 codex-personal 6807 claude-work 6911 codex-work 1267
codex-mobile 373 claude-mobile 49 codex-security 11 codex-or 3
codex-standalone-gfmbp 153 codex-standalone-mbp16 128
agent-session-metrics.timer already publishes five-minute aggregates about the archive: file
counts, byte coverage, index freshness, parser errors. Do not re-emit its series. This service owns
the contents of the transcripts, not the state of the store.
/opt/agent-session-index/sessions.sqlite3 is a derived SQLite/FTS projection that deliberately
excludes tool calls, tool results and reasoning. That is precisely the most valuable operational
signal, which is why this service tails the JSONL rather than reading the index.
Version drift is real and silent
Record shapes vary by cli_version. Sessions exist in codex-personal with no
token_usage_record at all, and current sessions on 0.155.1 have them on every response. A
decoder that assumes a record type is present produces nothing rather than failing, which is
indistinguishable from a quiet day. An absent record type is a finding.
Measured distributions
From bounded random samples. They are here so an implementer can tell a plausible result from a broken one, not as inventory.
Codex, native: TTFT p50 6.5s / p95 17.8s. Turn duration p50 310s / p95 5018s. Item durations: Reasoning p50 1.3s, CommandExecution p95 22.7s, ContextCompaction p50 62s / p95 167s, CollabAgentToolCall p50 30s (which is inter-agent wait, not work, and will distort tool latency if counted as a tool).
Claude Code, derived by pairing tool_use to tool_result on tool_use_id:
Bash p50 1.8s / p95 18.7s, Read p50 0.2s, Edit p50 0.62s, Grep p50 0.03s, WebSearch p50 7.2s,
AskUserQuestion p50 238s (human think time, which falls out for free).
Assistant record inter-arrival p50 2.7s / p90 13.8s / p99 52.7s. That is not TTFT.
cost-state appeared in 62 of 250 sampled claude-personal files. Roughly a quarter of sessions,
and it looks version-gated.
Volumes, 30 days
codex-personal 3389 files 20.6 GB
codex-work 857 files 4.6 GB
claude-personal 1810 files 1.9 GB
codex-mobile 201 files 1.8 GB
claude-work 804 files 1.0 GB
Roughly 4M records per month, Codex-dominated. A content-free log tier is on the order of 1.5 to 3 GB per month; shipping bodies is most of the 30 GB.
Two traps worth stating plainly
find -newermt on the hot store selects by mtime, which reflects the archive sync rather than when
the session happened. Files named 2026-08-24 come back as recently modified. Select by the
filename date when you mean recency of the conversation.
event_msg:item_completed and the matching response_item:* describe the same item. Counting
both doubles every tool call and nothing about the totals looks wrong.