Analysis Guide

End-to-end workflow for analyzing AI agent sessions with go-minitrace — from raw transcripts to structured insights

Sections

Terminology & Glossary
📖 Documentation
Navigation
20 sectionsv0.1
📄 Analysis Guide — glaze help analysis-guide
analysis-guide

Analysis Guide

End-to-end workflow for analyzing AI agent sessions with go-minitrace — from raw transcripts to structured insights

Tutorialminitraceanalysisduckdbqueryannotationworkflowdiscoverconvertqueryquery duckdbquery commandsannotatearchive-globdb-pathsource-diroutput-dir+3

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.

Start with the embedded help tree

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.

Discover: know what you have before converting

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.

Convert: turn native sessions into minitrace archives

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

Filtering a subset before converting

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

Conversion output structure

output/
├── manifest.json                  # all sessions, counts, quality grades
└── active/
    └── 2026-04/
        ├── <session-id>.minitrace.json
        └── <session-id>.minitrace.json

Query: analyze the converted archive

Built-in presets

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:

PresetShows
session-listOne row per session: framework, model, turns, tools, duration
framework-summaryAggregate stats grouped by framework
tool-operation-breakdownTool call counts by framework and operation type
timing-analysisDuration, active time, TTFA, and idle ratio by framework
read-ratio-distributionRead/write/execute breakdown per session
annotationsAll annotations across sessions

Custom SQL

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

Structured query commands

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

JavaScript command handlers run in a Goja-powered JS runtime and have access to require("minitrace") for builder-composed normalized SQLite databases through mt.db(), 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:

  • Multi-query joins: run several SQL queries and combine or post-process their results in JS
  • JS-side scoring and classification (e.g. computing a focus_score from ratios, categorizing sessions as tool-orchestrator vs balanced-builder)
  • Async logic with require("timer")
  • Reusable helper modules shared across multiple commands
  • Output shapes richer than flat SQL rows (cards, summaries, synthesized rows)

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");
  const db = mt.db().RuntimeArchives().QueryCommandDefaults().Build();
  try {
    return db.query(`
      SELECT session_id, title, agent_framework AS framework
      FROM sessions
      WHERE 1=1
      ${filters.framework?.length
        ? `AND agent_framework IN (${mt.sql.stringIn(filters.framework)})`
        : ""}
      ORDER BY started_at DESC
      LIMIT ${filters.limit}
    `);
  } finally {
    db.close();
  }
}

__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:

ExportDescription
mt.db()Create a fluent normalized SQLite builder. In query commands, start with mt.db().RuntimeArchives().QueryCommandDefaults().Build().
db.query(sql)Execute read-only SQL against normalized tables such as sessions, turns, tool_calls, files, metrics, and events; return Array<Record>.
db.queryOne(sql)Same but return only the first row or null/undefined.
db.queryResult(sql)Return { columns, rows, count, truncated, error } instead of throwing on query validation errors.
db.schema() / db.tables()Discover the normalized SQLite schema from inside JS.
db.close()Release or close the DB handle; call it in finally.
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.runtimeRead-only context object (archiveGlob, commandName, plus compatibility fields).

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.db().RuntimeArchives().QueryCommandDefaults().Build(), db.queryOne(), multi-query joins in JS, JS-side scoring, tool co-occurrence
  • testdata/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 directly

Smoke 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:

  1. Create a repository directory: mkdir -p ./my-commands/overview
  2. Write the .js file (copy from the showcase and adapt)
  3. Verify the command surface: go-minitrace query commands <group...> <leaf> --help
  4. Run it: go-minitrace query commands <group...> <leaf> --archive-glob ...
  5. Open go-minitrace serve --archive-glob ... and use the Commands sidebar and debug panels in /query
  6. Add an alias (.alias.yaml) only after the base command is solid

For JS files, remember the path rule precisely:

  • multi-verb or differently named JS files keep the extra file-stem level, e.g. overview session-tools session-list
  • self-named single-verb JS files collapse the redundant extra level, e.g. hardware-research/research-summary.js with a single research-summary verb runs as hardware-research research-summary

Multi-framework analysis

Load 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"

Output formats

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")'

Annotate: add human review notes

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.

Add a session-level annotation

go-minitrace annotate add \
  --output-dir ./output \
  --session <SESSION_ID> \
  --category observation \
  --title "Interesting parallel tool use pattern"

Add a turn-level annotation

go-minitrace annotate add \
  --output-dir ./output \
  --session <SESSION_ID> \
  --turn 5 \
  --category question \
  --title "Why was this tool chosen over alternatives?"

Query annotations

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.

Validate: check archive integrity

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.

Serve: web UI and API

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 editor

Common analysis patterns

Session counts by framework over time

SELECT
  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;

Tool use frequency

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.

Sessions with highest tool-call density (tools per turn)

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;

Failed tool calls

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.

See also

  • go-minitrace help getting-started — shorter step-by-step tutorial from install through first query
  • go-minitrace help overview — architecture, format, and supported sources
  • go-minitrace help js-api-reference — complete JS runtime API for .js command handlers
  • go-minitrace help structured-query-commands — authoring guide for .sql and .js command files, repository layout, aliases
  • go-minitrace help writing-duckdb-queries — SQL patterns: JSON access, UNNEST, casting, annotation joins
  • go-minitrace help query-duckdb — preset list, --sql-file, and archive loading flags
  • go-minitrace help duckdb-query-recipes — ready-to-use SQL examples for common analysis patterns
  • go-minitrace help annotation-playbook — annotation workflow in depth
  • go-minitrace help minitrace-schema — every field in a minitrace session document
  • go-minitrace help convert-commands — detailed reference for each conversion subcommand