Skip to content

Exporting a run

Terminal window
graphmind record <runId> # NDJSON, for machines
graphmind record <runId> --out bug-4471.ndjson
graphmind record <runId> --html # one page, for people

Two formats, same run. NDJSON is one wire envelope per line — the shape WS /ingest accepts, so it round-trips. --html is the viewer itself, inlined. Default output is graphmind-run-<runId>.ndjson / .html. Pass a run id you do not have and the command lists recent run ids instead of failing blankly.

Terminal window
graphmind record run_01H8… --html
# exported run run_01H8… (214 events) → /Users/you/graphmind-run-run_01H8….html (1204 KB)
# Open it in any browser, or send it to someone — it needs no server.

The viewer’s own JS and CSS are inlined and the run’s envelopes are embedded in the page, so the file is the debugger UI reading a frozen run: no server, no network, no account, no install. Open it on a plane. Attach it to a GitHub issue. Drop it in Slack and the person who opens it sees the graph, every node’s input and output, the errors and the timings — exactly what you were looking at.

What is different from a live run: controls that would steer execution are hidden, because there is nothing to steer. Everything else — the canvas, the inspector, the timeline, filtering — works.

It needs the built viewer. The published package ships it; from a monorepo checkout run pnpm build:viewer first, or the command tells you where it looked.

Each line is a complete envelope, exactly as it went over the socket:

{"gm":1,"seq":0,"ts":1756254187512,"runId":"run_01H8…","type":"run.started","payload":{"app":"support-agent","sdk":{"name":"ai","version":"7.0.79"},"meta":{"name":"handle-ticket"}}}
{"gm":1,"seq":1,"ts":1756254187533,"runId":"run_01H8…","type":"node.started","payload":{"nodeId":"tool:searchOrders","kind":"tool","name":"searchOrders","instanceId":"call_a1","input":{"email":"alex@example.com"}}}
{"gm":1,"seq":2,"ts":1756254187904,"runId":"run_01H8…","type":"node.finished","payload":{"nodeId":"tool:searchOrders","instanceId":"call_a1","output":[],"durationMs":371,"status":"ok"}}

This is the same shape graphmind demo replays and WS /ingest accepts, which makes an export a portable run: it can be re-ingested, diffed, scripted over, or attached to an issue.

Bug reports. A trace narrative plus “it broke somewhere in the middle” is not reproducible. An NDJSON file is the actual run.

Diffing two runs. Export both and use ordinary tools:

Terminal window
jq -r 'select(.type=="node.started") | "\(.payload.nodeId)\t\(.payload.input|tojson)"' bad.ndjson

See comparing runs.

Fixtures. A recorded run replays deterministically with no API key and no cost — which is exactly how graphmind demo works. If you are building on GraphMind, record a run once and use it as a test fixture.

Analysis. Every line is JSON, so jq, DuckDB, pandas and friends all apply:

Terminal window
# total token usage for the run
jq -s '[.[] | select(.type=="node.finished") | .payload.usage // empty]
| { input: map(.inputTokens) | add, output: map(.outputTokens) | add }' run.ndjson
# every error, in order
jq -r 'select(.type=="node.error") | "\(.payload.nodeId)\t\(.payload.error.name): \(.payload.error.message)"' run.ndjson

From the viewer’s run picker, or from the API:

Terminal window
curl -s http://127.0.0.1:4747/api/runs | jq -r '.runs[] | "\(.id)\t\(.app)\t\(.status)"'

Or from MCP — ask your coding agent for the last failing run and it will hand you the id and a deep link. See the MCP reference.

Runs are also available over HTTP while the server runs:

Terminal window
curl -s 'http://127.0.0.1:4747/api/runs/<runId>/events?afterSeq=0&limit=1000'

Returns { runId, total, events, nextAfterSeq }events are full wire envelopes in ascending seq order, nextAfterSeq is the cursor for the next page (or null on the last one). limit defaults to 1000, max 5000.

Because the format is the wire format, an exported run can be pushed back into a server over WS /ingest — envelopes keep their original seq, and storage inserts are keyed (run_id, seq) with INSERT OR IGNORE, so re-ingesting the same run is idempotent rather than duplicating it.