Programa does not meter agents itself. Claude Code and Codex each write a JSONL transcript per
session, and both put token counts in it. Their hooks pass the transcript's location to
Programa as transcript_path, next to session_id and cwd.
- Location:
~/.claude/projects/<encoded cwd>/<session_id>.jsonl. The file name is the session id. Subagent transcripts live next to it in<session_id>/subagents/**/agent-*.jsonl, with the same line format andisSidechain: true. They are a large share of a session's tokens. - Token lines: assistant lines (
type: "assistant") carrymessage.usagewith integerinput_tokens,output_tokens,cache_creation_input_tokensandcache_read_input_tokens.input_tokensexcludes both cache counts.message.idandmessage.modelsit beside it. - Dedupe: one API response is written as several lines, one per content block, all with the
same
message.id(and the same top-levelrequestId). Lines that repeat amessage.idcarry either the same usage values or all zeros, so the rule is: count eachmessage.idonce, using its line with the largest token total. A subagent transcript can repeat amessage.idfrom the main transcript, so the dedupe spans the main file and its subagent files. - Skipped lines:
message.model == "<synthetic>"marks locally generated messages with zero usage. - Advisor tokens:
message.usage.iterations[]lists each model call inside one response. Entries oftype: "advisor_message"carry their ownmodeland token counts, and those are not part of the top-levelusagefields. - Cost: no per-message cost exists. Some transcripts contain a
type: "cost-state"line withtotalCostUSDandmodelUsage.<model>.costUSD, but it is a point-in-time snapshot of the whole Claude Code process: it appears zero to two times per file, mid-file, and its token totals are several times the transcript's own totals, so it cannot be attributed to the session.
- Location:
~/.codex/sessions/YYYY/MM/DD/rollout-<timestamp>-<thread id>.jsonl. The thread id issession_meta.payload.idon the first line. - Token lines:
type: "token_usage_record"lines, one per model response, withpayload.response_idandpayload.usage.payload.turn_token_usageandpayload.thread_token_usagehold running totals.type: "event_msg"lines withpayload.type: "token_count"carrypayload.info.total_token_usage(running total for the thread) andpayload.info.last_token_usage.payload.infocan be null.- Each usage object has
input_tokens,cached_input_tokens,cache_write_input_tokens,output_tokens,reasoning_output_tokensandtotal_tokens.
- Field meaning:
input_tokensincludescached_input_tokens, andtotal_tokensequalsinput_tokens + output_tokens.output_tokensincludesreasoning_output_tokens.cache_write_input_tokensis 0 in every record observed, so whetherinput_tokensalso includes it is not visible in the data. - Dedupe: count each
token_usage_recordresponse_idonce.token_countevents repeat (the same running total is often emitted twice) and the running total can drop after a resume, so summinglast_token_usagedouble-counts and the lasttotal_token_usagecan undercount. The record sum matches the largesttotal_token_usagein most files; when a file has no records, the largesttotal_token_usageis the fallback. - Cost: none.
token_countalso carriespayload.rate_limits(plan usage percentages and a credit balance), which is account-wide, not per session. - Hook payload: every Codex hook input (
SessionStart,UserPromptSubmit,Stop,SessionEndand the tool hooks) declaressession_id,cwd,hook_event_nameand a nullabletranscript_path.
The claude-hook and codex-hook commands keep one record per live session in
~/.programa/claude-hook-sessions.json and ~/.programa/codex-hook-sessions.json, keyed by
session_id, with workspaceId, surfaceId, cwd and transcriptPath. Records expire 7 days
after their last update. session-end removes the live record and adds the session to
~/.programa/agent-last-sessions.json, which keeps at most one ended session per pane and per
agent, 50 entries, for 7 days. Ended sessions that a newer one replaced are no longer known.
OpenCode hooks pass no transcript, so OpenCode sessions have no token counts.
programa usage # every workspace
programa usage --workspace workspace:2 # one workspace
programa usage --jsonusage lists the Claude Code and Codex sessions in the hook stores above, grouped by workspace,
with four token counts per session and a total per workspace:
| Column | Claude Code | Codex |
|---|---|---|
| input | input_tokens |
input_tokens - cached_input_tokens |
| output | output_tokens |
output_tokens (includes reasoning) |
| cache read | cache_read_input_tokens |
cached_input_tokens |
| cache write | cache_creation_input_tokens |
cache_write_input_tokens |
- Counts are read from the transcripts each time the command runs, one line at a time. Nothing runs in the background, and the sidebar does not show them.
- Claude Code counts include the session's subagent transcripts and exclude advisor-model iterations.
- A session recorded without a transcript path is looked up by id under
$CLAUDE_CONFIG_DIR(default~/.claude) or$CODEX_HOME(default~/.codex). - Sessions whose transcript is missing or unreadable are skipped. The text output ends with how
many;
--jsonlists them underskipped. - A workspace that is no longer open is shown by its id, marked closed.
- There is no cost column: neither agent records a cost per session, and Programa does not keep a price table.
- Each session's state is
live(still in a live hook store) orended. - In
--json, each workspace hasid,ref,title,open,sessions[](provider,session_id,state,tokens) andtokens; the top level hastokensfor all workspaces andskipped.