End-to-end workflow for analyzing AI agent sessions with go-minitrace — from raw transcripts to structured insights
This guide walks through the complete analysis workflow: discovering sessions, converting them into the minitrace format, running queries to extract insights, and optionally enriching them with annotations for later review.
It assumes go-minitrace is installed and go-minitrace --version prints a version. If not, start with go-minitrace help getting-started.
Before writing any custom SQL, inspect what is already built in:
go-minitrace help # top-level help
go-minitrace help query-commands # query modes and flags
go-minitrace help query-duckdb # presets, SQL-file mode, loading flags
go-minitrace help structured-query-commands # reusable command authoring
go-minitrace help duckdb-query-recipes # ready-to-use query patterns
go-minitrace help writing-duckdb-queries # JSON access, UNNEST, casting
Run a command with --help to see its full flag surface:
go-minitrace query commands --help
go-minitrace query commands overview session-list --help
The embedded catalog already includes commands for session listing, framework summaries, timing analysis, and nightly reviews. Use the built-ins first; only fall back to custom SQL when you need something new.
Use discover to scan a native session store and report what sessions exist — without writing anything.
go-minitrace discover claude-code
go-minitrace discover codex --source-dir ~/.codex
go-minitrace discover pi
Each output shows session ID, format hint, and source path. Get a raw count:
go-minitrace discover pi --output json | jq length
If a source does not have a discover command (claude.ai, ChatGPT, turnsdb), check the source paths in go-minitrace help convert-commands and verify they contain readable session files.
Pick the right subcommand and point it at the session store:
go-minitrace convert claude-code --output-dir ./output
go-minitrace convert codex --source-dir ~/.codex --output-dir ./output
go-minitrace convert pi --output-dir ./output
Each session becomes one .minitrace.json file in output/active/YYYY-MM/<id>.minitrace.json. A manifest.json tracks all sessions with metadata and statistics.
To preview what would be converted without writing files:
go-minitrace convert claude-code --dry-run
Convert multiple source formats into the same output directory — sessions from different frameworks are fully interoperable in the minitrace schema:
go-minitrace convert pi --output-dir ./output
go-minitrace convert codex --source-dir ~/.codex --output-dir ./output
For Codex, discover does not expose the working directory. To convert only sessions for one repository, stage matching JSONL files into a temporary tree first:
# find sessions for a specific cwd
rg -l '"cwd":"/path/to/repo"' ~/.codex/sessions/ \
| xargs -I{} cp {} /tmp/staged-codex/sessions/
# convert the staged subset
go-minitrace convert codex --source-dir /tmp/staged-codex --output-dir ./output
For Pi, the --source-session flag converts one file at a time:
go-minitrace convert pi \
--source-session ~/.pi/agent/sessions/--slugged-cwd--/<session-id>.jsonl \
--output-dir ./output
output/
├── manifest.json # all sessions, counts, quality grades
└── active/
└── 2026-04/
├── <session-id>.minitrace.json
└── <session-id>.minitrace.json
The fastest analysis path — no SQL required:
go-minitrace query duckdb \
--archive-glob './output/active/*/*.minitrace.json' \
--preset session-list
Six presets ship with go-minitrace:
| Preset | Shows |
|---|---|
session-list | One row per session: framework, model, turns, tools, duration |
framework-summary | Aggregate stats grouped by framework |
tool-operation-breakdown | Tool call counts by framework and operation type |
timing-analysis | Duration, active time, TTFA, and idle ratio by framework |
read-ratio-distribution | Read/write/execute breakdown per session |
annotations | All annotations across sessions |
Start with a preset, then extend it with custom SQL for specific questions:
go-minitrace query duckdb \
--archive-glob './output/active/*/*.minitrace.json' \
--sql "SELECT id, title,
CAST(metrics->>'tool_call_count' AS INT) AS tools,
CAST(metrics->>'turn_count' AS INT) AS turns,
CAST(metrics->>'total_input_tokens' AS INT) AS input_tokens
FROM sessions_base
WHERE (environment->>'agent_framework') = 'pi'
ORDER BY tools DESC
LIMIT 20"
For queries you reuse, save them as .sql files and run them with --sql-file:
go-minitrace query duckdb \
--archive-glob './output/active/*/*.minitrace.json' \
--sql-file ./queries/top-sessions.sql
When an analysis task becomes part of your repeatable workflow, promote it to a structured query command. Structured commands give you named, typed parameters and work identically in the CLI, the web UI, and the API.
go-minitrace query commands overview session-list \
--archive-glob './output/active/*/*.minitrace.json' \
--framework codex,pi \
--limit 50
See go-minitrace help structured-query-commands for how to author your own .sql and .js command files, and go-minitrace help js-api-reference for the full JavaScript runtime API.
JavaScript command handlers run in a Goja-powered JS runtime and have access to require("minitrace") for DuckDB queries, plus the full Goja NodeJS stdlib (timer, fs, exec, path, console, etc.). Use JS when SQL alone is not enough.
When to reach for JS instead of SQL:
focus_score from ratios, categorizing sessions as tool-orchestrator vs balanced-builder)require("timer")A minimal JS command file (my-commands/overview/session-tools.js):
__section__("filters", {
fields: {
framework: { type: "stringList", help: "Filter by framework" },
limit: { type: "int", default: 25, help: "Row limit" },
},
});
function sessionList(filters) {
const mt = require("minitrace");
return mt.query(`
SELECT id, title,
environment->>'agent_framework' AS framework
FROM ${mt.tableName}
WHERE 1=1
${filters.framework?.length
? `AND (environment->>'agent_framework') IN (${mt.sql.stringIn(filters.framework)})`
: ""}
ORDER BY timing->>'started_at' DESC
LIMIT ${filters.limit}
`);
}
__verb__("sessionList", {
name: "session-list",
short: "List minitrace sessions",
fields: { filters: { bind: "filters" } },
});
The filename stem (overview/session-tools) usually becomes a CLI group, and each __verb__ name (session-list) becomes a leaf command:
go-minitrace query commands overview session-tools session-list \
--query-repository ./my-commands \
--archive-glob './output/active/*/*.minitrace.json' \
--framework codex
Key require("minitrace") API:
| Export | Description |
|---|---|
mt.query(sql) | Execute read-only SQL, return Array<Record> |
mt.queryOne(sql) | Same but return only the first row or null |
mt.tableName | The loaded DuckDB table name (use in SQL FROM) |
mt.sql.string(val) | Single-quoted, escaped SQL string literal |
mt.sql.stringIn(arr) | Comma-separated quoted list for IN (...) clauses |
mt.sql.like(val) | LIKE pattern with % wildcards on both sides |
mt.runtime | Read-only context object (tableName, dbPath, archiveGlob, persistLoaded, commandName) |
See go-minitrace help js-api-reference for the complete API, all built-in modules, scanner markers, and the supported field type set.
Start from working examples. The testdata showcase directories demonstrate every practical pattern:
testdata/query-repositories/js-showcase/ — pure JS commands: multi-verb files, aliases targeting JS, relative helpers, async via require("timer"), mt.queryOne(), multi-query joins in JS, JS-side scoring, tool co-occurrencetestdata/query-repositories/mixed-sql-js-showcase/ — the same commands side-by-side as .sql and .js equivalents so you can compare SQL vs JS approaches directlySmoke the showcases against your own local archive:
go-minitrace query commands \
--query-repository ./testdata/query-repositories/js-showcase \
analysis workspace-lab workspace-scoreboard \
--archive-glob './output/active/*/*.minitrace.json' \
--output json
Authoring workflow:
mkdir -p ./my-commands/overview.js file (copy from the showcase and adapt)go-minitrace query commands <group...> <leaf> --helpgo-minitrace query commands <group...> <leaf> --archive-glob ...go-minitrace serve --archive-glob ... and use the Commands sidebar and debug panels in /query.alias.yaml) only after the base command is solidFor JS files, remember the path rule precisely:
overview session-tools session-listhardware-research/research-summary.js with a single research-summary verb runs as hardware-research research-summaryLoad all frameworks at once for cross-framework comparisons:
go-minitrace query duckdb \
--archive-glob './output/active/*/*.minitrace.json' \
--sql "SELECT environment->>'agent_framework' AS framework,
COUNT(*) AS sessions,
AVG(CAST(metrics->>'total_duration_ms' AS DOUBLE) / 1000) AS avg_duration_s,
AVG(CAST(metrics->>'tool_call_count' AS INT)) AS avg_tools
FROM sessions_base
GROUP BY framework
ORDER BY sessions DESC"
All query results flow through Glazed's processor pipeline, so you get multiple output formats for free:
--output table # default, human-readable terminal table
--output json # newline-delimited JSON
--output csv # comma-separated values
--fields col1,col2,col3 # select specific columns
Pipe JSON output to jq for further shell-based analysis:
go-minitrace query duckdb \
--archive-glob './output/active/*/*.minitrace.json' \
--preset framework-summary \
--output json | jq '.[] | select(.framework == "pi")'
After querying, mark sessions or individual turns for follow-up. Annotations are stored in output/annotations.db and synced back into .minitrace.json when you call annotate sync.
go-minitrace annotate add \
--output-dir ./output \
--session <SESSION_ID> \
--category observation \
--title "Interesting parallel tool use pattern"
go-minitrace annotate add \
--output-dir ./output \
--session <SESSION_ID> \
--turn 5 \
--category question \
--title "Why was this tool chosen over alternatives?"
go-minitrace query duckdb \
--archive-glob './output/active/*/*.minitrace.json' \
--preset annotations
Or with custom SQL:
go-minitrace query duckdb \
--archive-glob './output/active/*/*.minitrace.json' \
--sql "SELECT s.id, s.title, a.category, a.title AS annotation_title
FROM sessions_base s
JOIN annotations a ON a.session_id = s.id
WHERE a.category = 'observation'
ORDER BY s.timing->>'started_at' DESC"
For the full annotation workflow — categories, syncing, browsing, and the web UI — see go-minitrace help annotation-playbook.
After conversion or manual edits, verify the JSON files are well-formed:
go-minitrace validate --path ./output --recursive
This reports any parse errors and stops on the first malformed file.
Run the HTTP server for interactive querying and an annotation browser:
go-minitrace serve --archive-glob './output/active/*/*.minitrace.json'
The server exposes:
/query — interactive query editor with structured command forms, SQL rendering, and result tables/api/v2/query-commands — REST endpoint for structured command execution/annotate — annotation browser and editorSELECT
SUBSTR(timing->>'started_at', 1, 7) AS month,
environment->>'agent_framework' AS framework,
COUNT(*) AS sessions
FROM sessions_base
GROUP BY month, framework
ORDER BY month, framework;
SELECT
tc->>'tool_name' AS tool,
tc->>'operation_type' AS operation,
COUNT(*) AS calls,
COUNT(DISTINCT id) AS sessions
FROM sessions_base,
UNNEST(tool_calls) AS t(tc)
GROUP BY tool, operation
ORDER BY calls DESC, tool ASC
LIMIT 20;
If you want to inspect file-oriented tool calls, prefer a path fallback like:
COALESCE(tc->'input'->>'file_path', tc->'input'->'arguments'->>'path')
See go-minitrace help writing-duckdb-queries for the JSON[] caveats, 1-based list indexing, and additional nested-field examples.
SELECT
id,
title,
CAST(metrics->>'tool_call_count' AS INT) AS tools,
CAST(metrics->>'turn_count' AS INT) AS turns,
CAST(metrics->>'tool_call_count' AS DOUBLE) /
CAST(metrics->>'turn_count' AS DOUBLE) AS density
FROM sessions_base
WHERE CAST(metrics->>'turn_count' AS INT) > 0
ORDER BY density DESC
LIMIT 10;
SELECT
id,
title,
tc->>'tool_name' AS tool,
tc->>'operation_type' AS operation,
tc->'output'->>'error' AS error
FROM sessions_base,
UNNEST(tool_calls) AS t(tc)
WHERE (COALESCE(tc->'output'->>'success', 'true')) = 'false'
ORDER BY timing->>'started_at' DESC;
See go-minitrace help duckdb-query-recipes for more ready-to-use patterns and go-minitrace help writing-duckdb-queries for the JSON access, casting, and UNNEST mechanics in detail.
go-minitrace help getting-started — shorter step-by-step tutorial from install through first querygo-minitrace help overview — architecture, format, and supported sourcesgo-minitrace help js-api-reference — complete JS runtime API for .js command handlersgo-minitrace help structured-query-commands — authoring guide for .sql and .js command files, repository layout, aliasesgo-minitrace help writing-duckdb-queries — SQL patterns: JSON access, UNNEST, casting, annotation joinsgo-minitrace help query-duckdb — preset list, --sql-file, and archive loading flagsgo-minitrace help duckdb-query-recipes — ready-to-use SQL examples for common analysis patternsgo-minitrace help annotation-playbook — annotation workflow in depthgo-minitrace help minitrace-schema — every field in a minitrace session documentgo-minitrace help convert-commands — detailed reference for each conversion subcommand