Six coding agents were running. I could not tell you what any of them had done.
Not roughly. Not approximately. The output was there, the files had changed, and the honest answer to "which one did that" was a shrug. Three questions in particular had no answer: which run burned the tokens, whether they genuinely ran at the same time or merely started together, and whether two of them had quietly edited the same file.
That last one is the expensive question. An agent working on the wrong file looks exactly like an agent working on the right one, right up until you read the diff.
The thing that was already true
Every one of those runners writes a transcript to disk while it works. Claude Code does. So do Cursor, Codex, Gemini CLI, Copilot CLI and Kiro. The record of what happened was sitting in my home directory the entire time, in six different formats, none of which I had ever looked at.
So runlanes does not wrap anything. There is no SDK, no instrumentation step, no account, and nothing to start before the run starts. It reads what the runner already wrote.
The consequence is the part I did not expect to matter as much as it does: it works on runs that already finished. Most tools in this space need you to have decided, in advance, that this particular run was worth watching. This one can answer a question you only thought to ask afterwards.
npx runlanes
That opens a console on 127.0.0.1:4180 for whatever project you are standing in. There is no configuration file to write first.
What it actually shows
Now is every live session, across every runner it found, with what the main conversation spent against what it handed to subagents. On the session that motivated the whole thing, that split was 8.3 million tokens of conversation against 2.1 million delegated, which was not the ratio I would have guessed.
The parallelism figure is the one I keep coming back to. Peak concurrency was four agents. The share of elapsed time where anything genuinely overlapped was 9%. Four agents were running, and for 91% of the wall clock they were politely taking turns. Nobody reports that number, and it changes how you plan a fan-out.
Graph links runs to the files that more than one of them touched.
The distinction that makes this worth having: an edge exists because a run opened that file. Not because a prompt said it would. Those are different claims, and only one of them is evidence.
History is every session found for the project, with spend and agent time. Docs is the plans, skills and agent instructions sitting beside the project, which turn out to be the thing you most want to read when a run has gone sideways.
Refusing to make numbers up
Kiro bills in credits, not tokens. Its transcripts record which files were touched and which documents were read, and no usage at all.
The console shows a dash.
It would be trivially easy to show 0 there. It would also be a lie of the most ordinary kind, the sort that makes a dashboard feel authoritative and quietly poisons every total on the page. An unmeasured run is not a free run. It is left out of the sums rather than counted as nothing.
The same instinct runs through the token arithmetic. Tokens counted are input plus output plus cache creation. Cache reads are deliberately excluded, because they re-report the entire prompt on every single turn, and summing them across a session counts the same context dozens of times and produces a number several times larger than anything that happened. They are still included in cost, because they are still billed. Those are two different questions and the tool answers them separately.
The claims are tests, not sentences
runlanes reads local files, makes no outbound network calls, and binds to 127.0.0.1. Every tool in this category says something like that in its README.
The difference here is that CI fails the build if any of it stops being true. There is a step that greps the source for fetch, axios and https.request and fails if it finds one. A step that asserts 127.0.0.1 still appears in the server. A step that fails if a runtime dependency ever appears, which is how the dependency count stays at zero rather than aspirationally low. And one that fails if a vendor name leaks outside the adapter directory, which is what keeps six runners from turning into six special cases scattered through the app.
A README claim is a sentence somebody wrote once. A CI step is a claim that has to survive every commit. I would rather ship the second kind.
Where the package came from
Published from GitHub Actions through OIDC trusted publishing, so npm records the commit, the workflow and the runner that built the tarball. npm audit signatures reports a verified attestation, and anyone can check that what is on npm matches what is on GitHub. Sigstore signs it with a certificate that lives about ten minutes and files the record in a public transparency log, so there is no long-lived signing key to steal.
For a tool whose entire argument is "these numbers survive checking", it seemed inconsistent to ask anyone to take the tarball on faith.
The demo is the product
runlanes --export writes the whole console to a single self-contained HTML file. The live demo is that export, rebuilt by CI whenever the source changes. It is not a screenshot and not a mock, which means a change that breaks the console breaks the demo in CI before it reaches anybody.
The demo also says in its first line that its runs are staged. A tool arguing that its numbers can be checked should not open with invented data presented as real.
It was called something else
This shipped as "agenttrace" for about a fortnight. Then I looked properly, and five published CLIs already install a binary by that name, one of them with a near-copy of my own opening line in its README.
The registry entry was still free. Taking it would have meant winning the package name while losing the command name, the search result and the repository name, which is losing three arguments to win one.
"runlanes" is also just a better description. The timeline draws one lane per run, and whether those lanes overlap is the entire question.
Try it
npx runlanes
MIT, Node 18 and up, zero runtime dependencies, 95 tests running on Node 18, 20 and 22. The source is on GitHub and the package is on npm.
If you run more than one agent at a time, the data is already on your disk. This just reads it.





Top comments (4)
I wrote off a reviewer subagent as unresponsive last month, and its verdict had been sitting in its own file under the session directory the whole time. I only thought to go looking afterwards, which is the case you built for. And I would have printed the 0 for Kiro without pausing on it.
That first case is the one I keep hearing, and it is worse than a missing answer: you conclude the agent failed, when it succeeded and nobody read it. The delegation split exists for the same reason. On the run that motivated all this, 8.3M tokens were the conversation and 2.1M were handed to subagents, and I had no idea the ratio was that lopsided until it was on a screen.
The Kiro zero is the one I nearly got wrong too. Writing 0 is easier, it lines up in the column, and it quietly poisons every total on the page. An unmeasured run is not a free run.
"Works on runs that already finished" is the property most observability tools miss, and it's the one that matters. The questions you actually need answered - which run burned the tokens, who touched this file - are questions you only think of after something looks wrong. Requiring instrumentation decided in advance means the interesting runs are exactly the ones with no data.
The same-file question is really a provenance question. "Which agent edited this" is unanswerable from the diff alone, because a wrong-file edit and a right-file edit produce identical diffs. You need an origin record per change - which run, which step, what it thought it was doing - captured at write time, not reconstructed later. Reading what the runner already wrote is the right instinct; the runner knew, it just never had to say.
You're right that a diff can't answer it, and right that the record has to be captured at write time rather than reconstructed. The part I'd push back on is that it already is. The runner logs each tool call as it makes it, so "run X wrote file Y" is observed, not inferred from the diff afterwards. The premise holds.
What you found is narrower and real: it was captured and then thrown away. The timestamp sits on every transcript line, and the parser was reading it for a run's start and end while dropping it for the calls in between. So two runs that touched the same file came back as an unordered pair. The console could say they collided and not who landed on top.
That's fixed in 0.5.0. Runs now carry the file events in the order the runner reported them, and a collision names the run that wrote last and when. Four of the six runners stamp their tool calls. Cursor stamps user turns instead and Kiro times an execution rather than the actions inside it, so for those it says nothing rather than borrowing a nearby timestamp and passing it off as a measurement.
Your other two points I've written into the README as limits rather than roadmap. Hunk-level attribution and recorded intent need the runner to emit a per-change record with reasoning attached, and no amount of reading transcripts recovers what was never written down. Better to say that plainly than imply a path that doesn't exist.
Thanks for this. It was a better critique than the feature request it turned into.