CLI
The convert, sessions, detect, stats, and drift commands.
agentminutes convert
To parse a transcript into a normalized session record, use convert
(harness auto-detected):
agentminutes convert ~/.claude/projects/my-project/some-session.jsonl
The default output is one JSON document: session metadata, the ordered
events, token totals, and a report accounting for everything that did not
become an event. totals is omitted entirely when the transcript records
no usage (Antigravity), so a measured zero is never conflated with “not
recorded”.
This is the output for a minimal two-message session, with the middle of the event list trimmed:
{
"agentminutes_schema": "0.1.0",
"meta": {
"harness": "claude-code",
"harness_version": "2.1.204",
"session_id": "739cfcc1-2863-4b77-bb48-cd030e55969d",
"cwd": "/Users/me/my-project",
"git_branch": "main"
},
"events": [
{
"kind": "session_meta",
"timestamp": "2026-07-16T14:45:41.182Z",
"provenance": { "line": 3, "end_line": 3 },
"session_meta": {
"harness": "claude-code",
"harness_version": "2.1.204",
"session_id": "739cfcc1-2863-4b77-bb48-cd030e55969d",
"cwd": "/Users/me/my-project",
"git_branch": "main"
}
},
{
"kind": "user_message",
"timestamp": "2026-07-16T14:45:41.182Z",
"id": "3885928d-e53c-46c8-a89c-d61e196924ae",
"provenance": { "line": 3, "end_line": 3 },
"user_message": {
"origin": "human",
"content": [
{ "kind": "text", "text": "Reply with exactly one word: pong" }
]
}
}
],
"totals": {
"input_tokens": 3159,
"output_tokens": 4,
"cache_read_input_tokens": 15021,
"cache_creation_input_tokens": 3764
},
"report": {
"skipped_records": {
"ai-title": 1,
"last-prompt": 1,
"queue-operation": 2
}
}
}
Note the report: the four skipped records are Claude Code UI
bookkeeping with no model-visible content, and they’re counted rather
than dropped. Between events, skips, and errors, every line of the
transcript is accounted for.
To stream events as JSONL instead (one event per line on stdout, with the same per-type skip accounting summarized on stderr):
agentminutes convert --format jsonl session.jsonl | jq -r 'select(.kind == "tool_call") | .tool_call.name'
For example, listing the same session’s event kinds:
$ agentminutes convert --format jsonl session.jsonl | jq -r .kind
session_meta
user_message
system
system
system
assistant_message
agentminutes: 6 events, 4 skipped records (ai-title 1, last-prompt 1, queue-operation 2)
The last line is the stderr summary; it stays out of your pipe.
Useful flags
--harness claude-codeskips auto-detection (also onstats).--harness-version 1.1.1(also onstats) records the harness version for formats that don’t record one themselves (Antigravity). Metadata only; it never changes how the transcript is parsed, and a version the transcript records wins.--permissive(also onstats) preserves unclassifiable records asunknownevents instead of failing the parse.--keep-rawretains the verbatim native records in each event’s provenance.--max-payload-bytes Nreplaces tool-result payloads larger than N with size-and-digest placeholders.--promote codex:web-search/--promote codex:patch-apply(also onstats) opt into a telemetry promotion; see Telemetry promotions below.-o out.json(also onstats) writes to a file; passing-as the transcript reads stdin.
agentminutes sessions
To find transcripts without parsing everything, use sessions:
# Every session that ran in a given working directory, ready to pipe:
agentminutes sessions --cwd ~/runs/exp-42 | xargs -n1 agentminutes convert
# One known session, resolved directly (Claude Code subagent transcripts
# are included in the output; forgetting them is the classic archiving bug):
agentminutes sessions --harness claude-code --session-id 0199c9a3-...
# Full metadata instead of paths, bounded by time:
agentminutes sessions --since 2026-07-19 --format jsonl
The default output is one transcript path per line on stdout, with an accounting summary on stderr:
$ agentminutes sessions --harness claude-code --session-id 739cfcc1-2863-4b77-bb48-cd030e55969d
~/.claude/projects/my-project/739cfcc1-2863-4b77-bb48-cd030e55969d.jsonl
agentminutes: 1 session, 0 filtered out, 0 skipped files, 0 errors
Filters: --harness, --root (explicit root, requires --harness),
--cwd, --session-id, --since/--until (RFC 3339 or bare dates). A
stderr summary accounts for what didn’t match and why; sessions with no
recorded cwd (Antigravity records none) never match --cwd and are
counted in the summary rather than left unexplained. Per-file scan errors go to stderr
and set exit status 1 without aborting the scan. See
Session Discovery for the discovery model.
agentminutes detect
To identify a transcript’s harness, use detect:
$ agentminutes detect session.jsonl
claude-code certain
agentminutes stats
To summarize a session’s behavior (tool mix, bytes retrieved, latency,
tokens, observed models, final answer), use stats:
agentminutes stats session.jsonl
This is the complete summary for the same minimal session shown under
convert:
{
"events": 6,
"event_counts": {
"assistant_message": 1,
"session_meta": 1,
"system": 3,
"user_message": 1
},
"user_messages": 1,
"tool_calls": 0,
"result_bytes": 0,
"system_by_subtype": {
"attachment/agent_listing_delta": 1,
"attachment/deferred_tools_delta": 1,
"attachment/skill_listing": 1
},
"models": [
"claude-fable-5"
],
"totals": {
"input_tokens": 3159,
"output_tokens": 4,
"cache_read_input_tokens": 15021,
"cache_creation_input_tokens": 3764
},
"start_time": "2026-07-16T14:45:41.182Z",
"end_time": "2026-07-16T14:45:46.5Z",
"wall_time_ms": 5318,
"final_answer": "pong"
}
Even this trivial session shows the shape of the analysis: the harness
injected three system attachments (skill listings and tool deltas)
before the model said a word, and cache reads dwarf fresh input tokens.
models lists the models observed on assistant messages in
first-observed order, so more than one entry means the serving model
changed mid-session.
Telemetry promotions
The summary’s system_by_subtype counts surface actions a harness
records only as telemetry. Codex 0.144 logs URL fetches solely as
web_search_end events, and 0.144.6 records file edits solely as
patch_apply_end, so they appear there rather than as tool calls.
This matters whenever the question you’re asking is about tool behavior.
Take a cross-harness comparison like “how many file edits did each
harness make on this task?” (see
Use Cases):
Claude Code records edits as tool calls, so its edits land in
tool_calls_by_name, but a Codex session whose edits live in telemetry
reports zero. The comparison skews on a logging difference: both
harnesses edited files, and only one recorded the edits as tool calls. The same applies to
retrieval metrics built on fetch counts and bytes, and to
Session.ToolInteractions(), which only joins tool_call/tool_result
pairs and can’t see actions that never became either.
The rule of thumb: promote when your analysis counts, times, or compares tool calls; leave telemetry as telemetry when you’re studying what the harness records natively. To opt in:
agentminutes stats --promote codex:web-search --promote codex:patch-apply rollout.jsonl
Here is the difference on a Codex session whose transcript records one
file edit only as telemetry. Without promotion, the edit hides in
system_by_subtype and tool metrics see one call:
{
"tool_calls": 1,
"tool_calls_by_name": { "exec": 1 },
"tool_calls_by_kind": { "execute": 1 },
"system_by_subtype": {
"patch_apply_end": 1,
"task_complete": 1,
"task_started": 1
}
}
With --promote codex:patch-apply, the same transcript reports the edit
as a tool call, classified edit, and patch_apply_end leaves the
system counts:
{
"tool_calls": 2,
"tool_calls_by_name": { "apply_patch": 1, "exec": 1 },
"tool_calls_by_kind": { "edit": 1, "execute": 1 },
"system_by_subtype": {
"task_complete": 1,
"task_started": 1
}
}
The synthesized events are visible in convert output, pointing back at
the telemetry record they came from:
{
"kind": "tool_call",
"timestamp": "2026-07-19T22:00:04Z",
"provenance": { "line": 11, "end_line": 11 },
"tool_call": {
"tool_call_id": "exec-x1",
"name": "apply_patch",
"kind": "edit",
"input": { "/tmp/exp/hello.txt": { "type": "add", "content": "hello\n" } },
"promoted_from": "event_msg/patch_apply_end"
}
}
A matching tool_result is synthesized alongside it, carrying the
telemetry record’s stdout and success status in its enrichment.
Promotions are never on by default, because harness versions that also
record the action natively would double-count. Synthesized events carry
promoted_from so they stay auditable.
agentminutes drift
If a parse fails or the output looks wrong, check whether the transcript’s format has drifted past what this build was validated against (free; nothing is invoked):
$ agentminutes drift scan session.jsonl
session.jsonl: claude-code
drift: record type "assistant" has new key "responseMeta"
A clean scan reports what it checked, including baseline record types the transcript happened not to contain:
$ agentminutes drift scan session.jsonl
session.jsonl: claude-code
info: baseline record types not exercised: [file-history-delta file-history-snapshot mode permission-mode system]
clean: matches the claude-code baseline
A drift finding usually means the harness updated its log format; check for an agentminutes update or file an issue with the scan output. Exit codes: 0 clean, 1 drift.