Ask your agent what happened.
Watch the graph answer.

One command turns every agent session already on your disk into a graph: subagents, tool calls, retries and the moments you said no, laid out as nodes you can open, with the errors that never got fixed already flagged. Ask your agent what happened and the answer lands in your terminal while the graph lights up the exact nodes it means. Send the run to a reviewer and they see how the code came to be, not just the diff.

Star on GitHub
WORKS WITH
  • Claude Code
  • Codex CLI
  • Hermes Agent
  • opencode
  • Cursor
a real run, staged on a demo repo — the agent was fixing a token-refresh race when its edits started failing. the graph knows why r replay on/off · j/k walk · f fit · / find · esc clear

Two ends of
one loop.

The dashboard is for you; the MCP server is for your agent — two ends of one loop. You ask in your own terminal, and the answer arrives there: your model, your session, fully inspectable. Then the graph you have open lights up the exact nodes it's describing. No chatbot bolted onto a dashboard, no second model to pin, no conversation hidden somewhere you can't read it.

This exchange is real — captured from the MCP loop running against the run above, then tightened for length.

Send the run,
not a screenshot.

rungraph export writes a .rungraph bundle over whatever channel you already trust — Slack, a repo, AirDrop. On the other end, rungraph open serves the same interactive graph on their own local dashboard, provenance intact ("shared by Bilal") — and their agent can question your run, right alongside their own history. The inventory prints every time, and export blocks outright when it finds a high-confidence secret.


    

Attach the bundle to a PR instead of a wall of screenshots — or skip the file and hand over a link. copy link in the header (and focus_nodes, for your agent) captures the exact run, node, and focus as a URL that reopens right there.

Runs, not conversations.

Agent sessions stopped being conversations a while ago. They're runs: an orchestrator spawning subagents, tools failing and retrying, a human occasionally saying no. rungraph draws that structure, so a 4,000-line transcript becomes something you can actually read.

Subagents fan out and return. Parallel work gets side-by-side lanes, and every agent returns to the turn that collected its result.
Tool nodes say what ran. Not just which tool — and consecutive calls collapse into one node, so a test loop doesn't become a hairball.
The moment you said no is a node. Interventions are first-class, and the edge that follows carries the reason.

The graph has an opinion.

A graph that renders everything with equal weight points at nothing. rungraph derives signals from the run and puts them in a strip above the canvas — and on a clean run that strip costs zero height, because a marker you can't trust is worse than no marker.

hover or tap a signal — the run lights up. dimmed, never hidden.

Everything the UI does,
a prompt can do.

Everything the UI can do, an agent can do over the CLI — JSON on stdout, logs on stderr, exit codes 0/1/2, no prompts anywhere. Paste this into a prompt and the agent can self-serve.

npx rungraph list --json
# {"runs":[{"runId":"claude-code:…","kind":"session","title":"Fix flaky auth test",…}]}

npx rungraph graph <runId> --json
# the full Graph IR: nodes, edges, groups — and derived signals
# {"irVersion":1,"nodes":[…],"signals":[{"kind":"retry-storm","severity":"high",…}]}

npx rungraph find <runId> token.js --json
# narrow before you pull — a big graph is 20k+ tokens of context

npx rungraph serve --no-open
# {"url":"http://127.0.0.1:4321"} — same data over HTTP + SSE live tail

The same surface is available as MCP tools — rungraph mcp --install. The IR is versioned and vendor-neutral, with five adapters today: Claude Code, Codex CLI, Hermes Agent, opencode and Cursor. Schema in SCHEMA.md.

Everything else.

The honest version rather than the flattering one — including the parts that are deliberately missing. Longer walkthroughs live in the guide.

The basics

What does rungraph actually do?

It reads the transcripts your agent already writes and reconstructs the run as a directed agentic graph: the orchestrator, the subagents it spawned and the tools they called become nodes; spawn and return relationships become edges; the moments the run changed direction get marked on the path.

Nothing instruments your agent. The reconstruction happens after the fact, from files already sitting on your disk.

What do I have to set up?

npx rungraph. That is the entire quickstart. No hooks, no wrapper command, no config file, no account, no API key. It scans, starts a local server and opens your browser.

Which agents does it read?

Five, each from wherever it already writes: Claude Code (~/.claude/projects), Codex CLI (~/.codex/sessions), Hermes Agent (the SQLite database at ~/.hermes/state.db), opencode (one global SQLite database under ~/.local/share/opencode) and Cursor — both the IDE's agent conversations (its global state.vscdb) and cursor-agent chats (~/.cursor/chats). One scan picks up all five. When more than one of them has runs on the machine, a chip rail above the run list filters by agent. The three SQLite readers need Node ≥ 22.13; on older versions they self-disable with a warning and everything else still works.

Is one of those the real one and the others bolted on?

No. Each adapter implements the same interface and emits the same vendor-neutral IR, and every feature downstream — signals, coverage, find, focus, file attribution, export, resume, all seven MCP tools — consumes only that IR. A Codex run, a Hermes run and a Cursor run behave exactly like a Claude Code one, and no Claude-specific name or field exists outside its own adapter directory.

Three differences are real and worth knowing:

  • Hermes, opencode and Cursor runs need Node ≥ 22.13, because they use Node's built-in SQLite reader. On older versions they are skipped with a warning and everything else still works.
  • mcp --install registers with every agent it can see. The MCP server itself is plain stdio and identical for every agent — see below.
  • Each agent's own structure keeps its own name — Claude Code Workflow runs, Codex subagent rollouts, Hermes delegation lanes, Cursor's IDE and CLI surfaces — because those are genuinely different things, not one thing wearing five labels.
When is the graph computed — during the run or after it?

Neither, exactly: it is computed on demand, from files your agent already wrote. All five agents write a full transcript as they work — Claude Code and Codex as JSONL; Hermes, opencode and Cursor into SQLite. rungraph parses that transcript when you open the run, and the server keeps an mtime-keyed cache, so a run that has not changed is never parsed twice.

Live is the same code path rather than a second mode — fs.watch on the run's own files, debounced, re-parse, push. The graph does grow while the agent works, but nothing is instrumented inside the loop: liveness here just means re-reading a file that is being appended to.

It is fast enough not to be a cost you think about. A 795-record session parses end to end in under half a second.

Does it work on sessions I already ran?

Yes, and that is the main point. Every session still on disk is readable right now, including the ones from before you had heard of rungraph. Nothing had to be recording at the time.

Can I watch a run that's happening right now?

Yes. Liveness comes from watching the run's own files — still no hooks — and updates stream over SSE with stable node ids, so new nodes merge into the graph instead of redrawing it. A half-written final line (an agent mid-write) is tolerated and retried on the next tick.

Signals re-derive on every update, so you can go do something else and glance back only when the strip goes loud.

How many tokens does this add to a run?

Zero. Not 10%, not 1%. A run costs exactly the same whether rungraph is installed or not.

No hooks, no wrapper, no proxy, no injected prompt — your agent has no idea rungraph exists while it is running. That is the whole reason for reconstructing after the fact instead of instrumenting the loop. A tracing-style tool, where the model annotates spans as it goes, is exactly where a "+10% of the run" estimate would be right.

The one place tokens enter is entirely opt-in: the MCP server, when you ask your agent about a run. Measured on one real session of 795 records that spent 127k tokens of its own:

  • the 7 tool definitions plus server instructions — ~1,600 tokens, loaded once per session, not per question
  • find_nodes, the search you normally start with — ~440 tokens
  • get_graph, the compact projection of that entire run — ~4,900 tokens

So a typical question costs one to three thousand tokens of tool results, against a session that spent 127k on itself. And you pay it when you ask a question, never when you run.

What do I need installed?

Node ≥ 20. That's it — Hermes, opencode and Cursor runs want ≥ 22.13, as above. The frontend ships prebuilt inside the package, so there is no build step on your machine and nothing to resolve at install time.

Privacy & visibility

Does anything leave my machine?

No. The server binds 127.0.0.1 only, makes zero network requests and phones nothing home. There is no telemetry, no account and no anonymous usage data. This page keeps the same promise: no analytics, no CDNs, no cookies — nothing to consent to.

Two exceptions, both ones you ask for by name. rungraph export writes a file, and where that file goes afterwards is your channel, not rungraph's. And rungraph mcp answers your agent, so a tool result rides out in that agent's own API request.

Both run the same calibrated secrets scan on the way out: export blocks, MCP redacts — node labels included, not just the payloads — and every redacted result says how many values it replaced, so a scrubbed run is never mistaken for a clean one. The dashboard still shows you the real values, because those never leave 127.0.0.1 and reading a key is how you rotate it.

Could a web page I visit read my transcripts?

That is the attack worth naming, and it is guarded. Every request is Host-header-checked, so a hostile page cannot DNS-rebind its way into a local server that binds only to the loopback address.

The two endpoints that accept a write — focus and resume — sit behind the same guard plus a non-localhost Origin rejection, and neither one executes or persists a string from the request. Resume takes a run id and a boolean; the command is rebuilt from rungraph's own scan.

Does it send my transcripts to a model?

rungraph never runs a model and has no API key. Signals are deterministic rules over the parsed run, and find is a plain substring match that runs in your browser — no model, no network, no subprocess. Nothing here reaches a model on its own.

When your agent answers a question about a run, that is your agent, on your model, in your session, fully inspectable. rungraph contributes the graph, not the conversation.

What that does mean: if you ask your agent to read a run, the part it reads travels in its request, like any file it opens. So rungraph scans that path too — every MCP result is redacted before it leaves, using the same pattern list that blocks an export. Your agent is told a value was redacted rather than being handed it.

Does it change anything on disk?

It never writes to your transcripts. Adapters read the run's own files and nothing else; the Hermes, opencode and Cursor databases are opened strictly readonly.

rungraph writes exactly three things, each one something you asked for:

  • a .rungraph bundle, when you run export;
  • an entry in your agent's MCP config, when you run mcp --install;
  • a small file in your temp directory, so a second dashboard can find the first.

Reading a run

What am I actually looking at?

Time flows down. Your prompts are the backbone; parallel agents fan out into side-by-side lanes and return to the turn that collected their result. Tool nodes say what ran — Bash · npm test ×12, not just "Bash" — and consecutive calls of the same tool collapse into one node, so a test-fix loop doesn't become a hairball.

Click any node for the full story: prompt and response for turns, every call's inputs, outputs, errors and timing for tools, the whole transcript for a subagent — plus the agent's own narration from just before the call, which is usually the why.

What are signals?

rungraph's opinion about the run, derived server-side and shown in a strip above the canvas: retry storm (the same tool kept failing in one place), unresolved error (something failed and nothing came back to fix it), intervention (you denied a permission, interrupted, or answered a question), outlier (a step that cost far more than the rest of the run) and course change (the run's own recorded reason for changing direction).

Click one and the nodes it means light up. A graph that renders everything with equal weight points at nothing.

The strip is empty. Is it broken?

Almost certainly not — an empty strip is the design. On a clean run it costs zero height, because a marker you cannot trust is worse than no marker: once you stop believing the flags, you are back to reading the whole graph by hand.

So precision is favored over recall on purpose, the thresholds are calibrated against real sessions rather than reasoned into place, and a run that genuinely went fine is supposed to look like it.

What does "read 95% of this run" mean?

That is coverage, and it exists because an empty strip is a claim. Transcript formats are undocumented and unversioned: a vendor ships a release, adds a record type, and runs quietly start arriving with holes in them. Signals can only speak about records that parsed.

So every run also reports how many records rungraph examined and how many it could not interpret — and below 100% it names the record types it did not understand, because "one unknown metadata type" and "four hundred missing assistant turns" can be the same percentage and opposite emergencies. Your agent is handed the same numbers and told to say so before calling a run clean.

Without it, "nothing went wrong" and "I could not read part of this" render identically, which is the one failure the whole signal layer exists to prevent.

Why does focusing dim everything else instead of hiding it?

Because hiding collapses the layout, and the layout is what a graph view is uniquely good for. Once you have learned the shape of a run, every filter that reflows it costs you that spatial memory. Non-members drop to a quarter opacity and stay exactly where they were.

Signal clicks, file clicks, text find and your agent's answer are not four features — they all reduce to "light up this set of nodes, and say why", through the same mechanism.

How do I get back into a session I found?

The graph is where you find a session — the run where auth broke, the conversation from Tuesday you half-remember — and every local session carries the edge back to the terminal, from the run header or the run list.

rungraph shows the full command for whichever agent wrote it (claude --resume …, codex resume …, hermes --resume …) so it also teaches the incantation, and on macOS it can open a new terminal window with the session already loading. Where the CLI supports forking, resuming a live run offers it, so you branch a copy instead of interleaving with a session that is still going. Runs served from someone else's bundle are another machine's transcripts, so they offer no resume at all.

Your agent

What is the MCP server for?

The dashboard is for you; MCP is for your agent. They are two ends of one loop rather than two products: you ask in your own terminal, the answer arrives there, and the graph you have open lights up the exact nodes the answer is about — then hands back a link that restores the same highlight for anyone you paste it to.

Does this only work with Claude Code?

No. It is plain MCP over stdio, so any MCP-capable agent can wire it, and the seven tool names are identical everywhere — as is the loop.

npx rungraph mcp --install registers with every agent whose runs are already on this machine — Claude Code, Codex, Hermes, opencode and Cursor — by driving each vendor's own CLI, because each of them owns its config format and will keep owning it. rungraph never edits an agent's config file itself. (Cursor's CLI has no mcp add, so for Cursor it prints the one block to paste, plus the IDE's own one-click install link.)

npx rungraph mcp --install --client all

— installs into all five regardless of what was detected, and anything that cannot be delegated to prints the exact block to paste instead, so the command never dead-ends. Either way, npx rungraph mcp --check verifies the whole path end to end, one row per detected agent, and prints exactly what to fix.

Does the dashboard have to be open?

No. The read-only tools parse straight from disk, so asking a question never requires a server to be running.

focus_nodes is the one that wants a dashboard, and with none open it still succeeds and simply reports that the highlight was skipped. The highlight is a bonus, never the answer.

What can it actually answer?

The kinds of questions a transcript can genuinely settle:

  • Triage — what failed, did anything stay broken, where did I say no?
  • File archaeology — which steps touched this file, subagents included?
  • Structure — what did it spawn, and what came back?
  • Cost — what took longest, which agent was most expensive?
  • Verbatim — what was the actual error text, what prompt did that subagent get?
  • Self-audit — did it really run the tests, or just say it did?

You don't have to invent them either: the bottom of the inspector writes questions for you, from the run you are looking at, with a copy button.

What can't it answer?
  • What the agent is about to do, or why it is stuck this second. Transcripts are written after the fact. That is not observable, and it is not faked.
  • Whether the work was correct. It knows the edit landed, not that it was right.
  • File contents or diffs, beyond what a tool call's input captured.
  • Dollars. There is no pricing data.
  • Cross-run questions, not yet. An agent can loop over runs, but there is no index, so it is N calls and slow.
Why isn't there a chat box in the dashboard?

Because it was rejected on purpose. An embedded chatbot would mean owning model pinning, prompt maintenance and a chat UI — and, worse, it would hide the conversation somewhere you cannot inspect it.

Your terminal is already a good place to talk to your agent, with your model, your session and your history. rungraph adds the graph to that conversation instead of starting a second one next to it.

Sharing

How do I send someone a run?

rungraph export writes a .rungraph bundle — from the dashboard's share… panel or straight from the CLI. The file is the transfer: send it over whatever channel you already trust, because rungraph itself never touches a network.

On the other end, npx rungraph open team-work.rungraph serves the same interactive graph on their own dashboard, ephemerally. Nothing is copied anywhere; close the process and it is gone; keep the file to reopen it any time.

What's actually inside a bundle?

An inventory prints every single time — runs, nodes, how many of your prompts are included, files touched — because people underestimate how much lives in a transcript, and the moment to see it is before it leaves.

Want to share the shape without the words? --structure-only keeps graph structure, tool names, files and timings, and drops prompts and outputs entirely.

Am I going to leak a secret?

Export blocks when it finds a high-confidence one — AWS keys, GitHub, Slack and API tokens, private-key blocks; anchored patterns calibrated for near-zero false positives — and lists exactly where each one is.

You then resolve it deliberately: --redact-secrets swaps in placeholders and leaves everything else verbatim, --structure-only drops the text entirely, and --allow-secrets is there for when you have checked and they are fixture keys.

What does the person receiving it need?

npx rungraph open <file>, and nothing else — not even the agent that produced the run. A bundle carries only the vendor-neutral IR, so opening one needs no adapters at all and a Codex run opens perfectly on a machine that has never run Codex.

Signals derive on their rungraph, every run wears its provenance, and their agent can be pointed at your runs right alongside their own — "what went wrong in the bundle Bilal sent me?"

Is "shared by Bilal" verified?

No — and it should not be read as an identity. It is a display string the exporter chose. Trust a bundle the way you trust the channel it arrived on.

The project

What does it cost, and what's the licence?

Nothing, and MIT. No account, no paid tier, no hosted service to sign up for — there is nowhere for your runs to go.

Zero runtime dependencies — really?

Really: package.json has devDependencies only, and that is load-bearing for npx rungraph — nothing to resolve, nothing to audit, nothing that can rot. The backend is node:http, fs.watch and friends; the MCP server hand-rolls JSON-RPC over stdio for the same reason; the frontend (Preact + elkjs) is prebuilt at publish time and shipped inside the package.

What's deliberately not here?

Run comparison, filtering, cost estimates and cross-run querying are all out of scope today — decisions, not oversights. An embedded chatbot was rejected outright.

On the roadmap instead: annotations you can attach before exporting a bundle, cross-run questions, run comparison, and cost estimates once there is honest pricing data behind them.

My agent isn't one of the three. Can I add it?

Yes, and it is the most valuable contribution you can make. The adapter interface is small — detect, parse, and the run's own files in, IR out — and everything downstream comes free the moment your adapter emits that IR: signals, coverage, focus, find, export, MCP, the lot.

CONTRIBUTING.md has the repo map, the non-negotiables worth reading before you build, and the fixture workflow; SCHEMA.md documents the IR.

Something looks broken.

The guide has a symptom-to-fix table for the usual suspects — the port was busy (it auto-increments; the printed URL is authoritative), a banner about unrecognized records (your agent is newer than your rungraph — npx rungraph@latest), an empty graph (that session has no turns yet).

Still wrong? A bug report with a --structure-only bundle attached is the most useful kind, since it carries the run's shape without any of your prompts.