go-minitrace
A transcript analysis toolkit that converts AI agent sessions into queryable archives and lets you build reusable analysis tooling with SQL and JavaScript.
The core problem it solves: when something breaks in a codebase you've worked on for months, the question is rarely "what changed?" — it's "what did this look like when it worked, and which sessions show the transition?" go-minitrace treats transcript analysis as a reduction pipeline, not a reading task.
What it does
go-minitrace converts raw session stores into structured .minitrace.json archives, then provides three query surfaces over a normalized SQLite engine — query run (presets and ad hoc SQL), query commands (reusable structured commands), and a JavaScript runtime (mt.db()) — plus a web UI with a transcript viewer and annotation editor.
Supported sources: Claude Code, Codex, Pi, GitHub Copilot CLI, claude.ai, ChatGPT, Geppetto/Pinocchio.
Installation
# Homebrew (recommended — includes the embedded web UI)
brew tap go-go-golems/go-go-go
brew install go-minitrace
# Go install (NOTE: the embedded web UI will be missing;
# use Homebrew or build from source for the full experience)
go install github.com/go-go-golems/go-minitrace/cmd/go-minitrace@latest
# From source (includes the embedded web UI)
git clone https://github.com/go-go-golems/go-minitrace.git
cd go-minitrace
go generate ./... # builds the frontend with Dagger (requires Docker)
go build ./...
Quick start
# See sessions created since a date (narrow by project)
go-minitrace discover pi --cwd-contains my-repo --since 2026-06-01
# Find long-lived Pi, persisted Codex, or Claude Code JSONL sessions with activity since a date
# (this scans candidate transcripts and is more expensive than --since; Codex exec JSONL has no native timestamps)
go-minitrace discover pi --cwd-contains my-repo --active-since 2026-06-01
# Convert sessions into queryable archives
go-minitrace convert claude-code --output-dir ./output
go-minitrace convert pi --output-dir ./output
# ... or convert exactly the sessions discover found
go-minitrace convert pi --source-session /path/to/session.jsonl --output-dir ./output
# Query with built-in presets (no SQL required)
go-minitrace query run \
--archive-glob './output/active/*/*.minitrace.json' \
--preset session-list
# Run a structured command
go-minitrace query commands overview session-list \
--archive-glob './output/active/*/*.minitrace.json'
# Start the web UI
go-minitrace serve --archive-glob './output/active/*/*.minitrace.json'
Why this exists
A common mistake when debugging with historical sessions is treating every transcript as equally important. That leads to two failures:
- Token waste — too much raw material, too little structure.
- Reasoning waste — the investigator spends time rediscovering the same filters, archive globs, and path patterns instead of asking progressively better questions.
go-minitrace is most effective when treated as a small analysis framework, not a one-off SQL runner. The recommended workflow is a three-layer reduction funnel:
Native session stores → .minitrace.json archives → SQLite database
(queryable) (normalized)
│
┌─────────────────┐ │
│ SQL leaves │◀───────────────┘
│ (filter/search)│
└────────┬────────┘
│
┌────────▼────────┐
│ JS summarizers │
│ (explain to │
│ humans) │
└────────┬────────┘
│
┌────────▼────────┐
│ Compact │
│ evidence / │
│ next hypothesis│
└─────────────────┘
- Session inventory — find the 4 sessions that matter, not the 400 that don't.
- Evidence extraction — pull only the tool calls that carry actual signal (bash output, file touches, git history).
- Summarization — turn raw rows into a chronological narrative a human can act on.
Every stage reduces entropy. The end result is not a dump — it's a report.
The three query layers
1. Built-in presets (no SQL required)
Nine presets ship with go-minitrace for common questions:
| Preset |
Description |
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 |
tool-failures |
Failed tool calls with target and error detail |
timing-analysis |
Duration, active time, TTFA, and idle ratio by framework |
read-ratio-distribution |
Read/write/execute breakdown per session |
file-operations |
Every file touch in session order |
file-timeline |
Chronological operations on files |
annotations |
All annotations across sessions |
go-minitrace query run \
--archive-glob './output/active/*/*.minitrace.json' \
--preset framework-summary
2. Custom SQL
When a preset doesn't fit, write SQL directly. The normalized schema exposes the full session structure as real tables — sessions, turns, tool_calls, files, annotations, metrics, events, attachments — so most queries need no JSON extraction:
-- Sessions with highest tool-call density (tools per turn)
SELECT session_id, title,
tool_call_count AS tools,
turn_count AS turns,
CAST(tool_call_count AS REAL) / turn_count AS density
FROM sessions
WHERE turn_count > 0
ORDER BY density DESC
LIMIT 10;
-- Tool failures across all sessions
SELECT tc.session_id, s.title, tc.tool_name, tc.error
FROM tool_calls tc
JOIN sessions s USING (session_id)
WHERE tc.success = 0;
-- Bash output containing a specific keyword (e.g. a runtime error)
SELECT tc.session_id, s.title, s.started_at,
tc.tool_call_id, tc.command, substr(tc.result, 1, 200) AS bash_output
FROM tool_calls tc
JOIN sessions s USING (session_id)
WHERE tc.tool_name = 'bash'
AND tc.result LIKE '%Connect successful%'
ORDER BY s.started_at, tc.session_id
LIMIT 50;
go-minitrace query run \
--archive-glob './output/active/*/*.minitrace.json' \
--sql "SELECT session_id, title, tool_call_count AS tools
FROM sessions
WHERE agent_framework = 'pi'
ORDER BY tools DESC
LIMIT 20"
Long-tail fields stay reachable via json_extract on raw_json/framework_metadata_json, and session-level SQL written for the old DuckDB engine keeps working against the sessions_base compatibility view (go-minitrace help query-duckdb has the migration table).
Migration checklist for pre-single-engine users:
- replace
go-minitrace query duckdb ... with go-minitrace query run ...;
- remove
--db-path, --table-name, and --persist-loaded from SQL command invocations;
- rewrite
UNNEST(turns/tool_calls) queries against the turns and tool_calls tables;
- in JS commands, build a handle with
mt.db().RuntimeArchives().QueryCommandDefaults().Build() and call db.query(sql).
Output formats: --output table (default), --output json, --output csv.
When an analysis task becomes part of your repeatable workflow, promote it to a structured command. Structured commands give you named, typed parameters, alias support, and work identically in CLI, web UI, and API.
A command file is a .sql or .js file with metadata:
/* sqleton
name: session-list
short: List minitrace sessions
flags:
- name: framework
type: stringList
help: Filter by agent framework
- name: limit
type: int
default: 100
help: Limit the number of rows returned
*/
SELECT
session_id AS id,
agent_framework AS framework,
title,
turn_count AS turns,
tool_call_count AS tools
FROM sessions
WHERE 1=1
{{ if .framework -}}
AND agent_framework IN ({{ .framework | sqlStringIn }})
{{ end -}}
ORDER BY started_at DESC
LIMIT {{ .limit }};
Repository layout maps directly to CLI structure:
query-commands/
└── overview/
├── session-list.sql → go-minitrace query commands overview session-list
└── framework-summary.sql → go-minitrace query commands overview framework-summary
Run with typed parameters:
go-minitrace query commands overview session-list \
--archive-glob './output/active/*/*.minitrace.json' \
--framework codex,pi \
--limit 50
Load external repositories for team or investigation-specific commands:
go-minitrace query commands overview session-list \
--query-repository ./my-commands \
--archive-glob './output/active/*/*.minitrace.json'
Configure repositories via GO_MINITRACE_QUERY_REPOSITORIES or in the app config:
queryRepositories:
- ./query-commands/team
- ~/.config/go-minitrace/query-commands
JavaScript command handlers
For analysis tasks that go beyond what SQL can express conveniently — multi-query joins, JS-side scoring, classification logic, async control — write a JS handler.
JS handlers run in a Goja-powered runtime with access to:
require("minitrace") — normalized SQLite databases through mt.db() and db.query()
require("timer") — setTimeout, setInterval for async commands
require("fs"), require("exec"), require("path") — file system and shell access
require("console") — debug output to server logs
// overview/session-tools.js
__section__("filters", {
title: "Filters",
fields: {
framework: {
type: "stringList",
help: "Filter by agent framework",
},
limit: {
type: "int",
default: 25,
help: "Maximum number of rows",
},
},
});
function sessionList(filters) {
const mt = require("minitrace");
const db = mt.db().RuntimeArchives().QueryCommandDefaults().Build();
try {
// Run SQL against normalized SQLite tables and post-process in JS.
const rows = db.query(`
SELECT
session_id,
title,
agent_framework AS framework,
tool_call_count AS tools,
turn_count AS turns
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}
`);
return rows.map(row => ({
...row,
density: row.turns > 0 ? (row.tools / row.turns).toFixed(2) : 0,
type: row.tools / row.turns > 2 ? "tool-heavy" : "balanced",
}));
} finally {
db.close();
}
}
__verb__("sessionList", {
name: "session-list",
short: "List minitrace sessions",
fields: { filters: { bind: "filters" } },
});
This becomes:
go-minitrace query commands overview session-tools session-list \
--archive-glob './output/active/*/*.minitrace.json' \
--framework codex \
--limit 25 \
--output json
When to use JS instead of SQL:
| SQL is the right tool |
JS is the right tool |
| Single-query filtering and grouping |
Multi-query joins: run several queries and combine in JS |
Aggregations (COUNT, AVG, GROUP BY) |
JS-side scoring and classification |
| Simple predicates |
Async logic with require("timer") |
| Row-oriented questions |
Reusable helper modules shared across commands |
| Single-column transformations |
Output shapes richer than flat SQL rows |
See go-minitrace help js-api-reference for the complete require("minitrace") API.
Embedding require("minitrace") in a hand-built Goja host
You can embed the JavaScript API in your own Go binary without shelling out to the go-minitrace CLI or generating an xgoja application. Link the module package for its side effect, build a plain go-go-goja runtime, and then use require("minitrace") from JavaScript.
package main
import (
"context"
"github.com/dop251/goja"
"github.com/go-go-golems/go-go-goja/pkg/engine"
// Side-effect imports register these packages in modules.DefaultRegistry.
_ "github.com/go-go-golems/go-minitrace/pkg/minitracejs"
_ "github.com/go-go-golems/goja-text/pkg/template"
)
func main() {
factory, err := engine.NewRuntimeFactoryBuilder().Build()
if err != nil {
panic(err)
}
rt, err := factory.NewRuntime(
engine.WithStartupContext(context.Background()),
engine.WithLifetimeContext(context.Background()),
)
if err != nil {
panic(err)
}
defer func() { _ = rt.Close(context.Background()) }()
_, err = rt.Owner.Call(context.Background(), "render-report", func(_ context.Context, vm *goja.Runtime) (any, error) {
return vm.RunString(`
const mt = require("minitrace");
const template = require("template");
const fs = require("fs");
const session = mt.session()
.File("./output/active/example/session.minitrace.json")
.InteractiveCache("./.cache/minitrace")
.Open();
try {
const markdown = template.renderText("# {{ .Title }}\n\nSession: {{ .SessionID }}\n", {
Title: "Embedded minitrace report",
SessionID: session.id(),
}).Text;
fs.writeFileSync("./dist/minitrace-report.md", markdown);
} finally {
session.close();
}
`)
})
if err != nil {
panic(err)
}
}
Important details:
- Go does not dynamically discover modules by JavaScript name. The blank import of
pkg/minitracejs links the package into the binary and runs its init() registration.
require("fs") comes from go-go-goja's default registry. require("template") comes from the optional goja-text side-effect import shown above.
- The default minitrace module has empty runtime settings. In embedded hosts, prefer explicit sources such as
.File(...), .Files(...), .Glob(...), .Dir(...), or .Content(...).
- Generated query-command runtimes still use a command-scoped loader so
.RuntimeArchives() can read --archive-glob and related runtime settings.
- If you call
WithModules(...) directly, you are choosing an explicit module set and must register every module that JavaScript should be able to require.
The web UI
go-minitrace serve starts an HTTP server on the same normalized SQLite engine (the live annotation store is ATTACHed as anno.annotations) and exposes:
- Sessions (
/sessions) — sortable session list with framework, model, turns, tools, duration, active %, and annotation badges
- Transcript viewer — per-session turn/tool-call timeline with inline annotation creation and editing
- Query (
/query) — interactive query editor with saved queries, presets, structured command forms, SQL rendering, and result tables
- REST API — sessions, queries, annotations, and structured command execution under
/api/v2/*
Flags: --archive-glob, --port, --max-rows, --query-timeout-ms, plus --query-dir/--preset-dir/--query-repository for SQL libraries.



Annotations
Add human review notes to sessions and turns. Annotations live in output/annotations.db and sync back into .minitrace.json files.
go-minitrace annotate add \
--output-dir ./output \
--session <session-id> \
--category observation \
--title "Session worth revisiting for context"
go-minitrace annotate sync --output-dir ./output
Categories: observation, ai-failure, user-error, environment-issue, success, question, to-discuss, to-improve
Full workflow: go-minitrace help annotation-playbook.
Supported sources
| Source |
Format |
Command |
| Claude Code |
JSON (dir-v1, subagent transcripts) |
convert claude-code |
| Codex |
JSONL (sessions + exec) |
convert codex |
| Pi |
JSONL |
convert pi |
| GitHub Copilot CLI |
Session-state directories |
convert copilot |
| claude.ai |
ZIP export |
convert claude-ai |
| ChatGPT |
ZIP export / JSON |
convert chatgpt, convert chatgpt-json |
| Geppetto / Pinocchio |
turns.db |
convert turnsdb |
Development
make lint
make test
make build
Pre-commit hooks:
lefthook install
Release:
make goreleaser
Security
CLI output, logs, and exported data may contain sensitive information from your session history. Review tool output before sharing it.
License
MIT