Blog

How AgentBar reconstructs multi-agent traces from local logs

Three coding agents, one request, one calling the next. AgentBar rebuilds the call graph and state from files the agents already write, and labels every edge it infers.

By tsuvic · Published Aug 1, 2026 · 8 min read

You ask Claude Code to refactor a module. Halfway through it decides the job needs another agent and runs codex exec in a shell. That Codex, in turn, calls claude -p to check a diff. One request from you, three agents, two process boundaries crossed without your involvement.

Later you want the whole chain. Who called whom, with what command, and what each one is doing right now. In a service backend this is what distributed tracing is for. A trace context propagates across each hop, a collector assembles the spans, and a UI draws the waterfall. The catch is the instrumentation. Somebody has to inject the context and run the collector.

You installed none of that. No hooks in the agents, no tracer library, no daemon watching process creation. So the fair question is how a trace can exist at all.

The short answer is that it does not exist, in the tracing sense. Nothing was captured at the boundary. AgentBar reconstructs the graph afterwards, from records the agents wrote for their own reasons, and it marks every connection that required a guess.

The records were already there

Coding agents keep transcripts. Claude Code writes ~/.claude/projects/**/*.jsonl. Codex writes dated session JSONL under ~/.codex/sessions/. Qwen and Gemini keep their own JSONL. Cursor stores state in a SQLite database, which AgentBar reads directly through the sqlite3 C API rather than asking Cursor anything.

These files exist so the agent can resume a session and show you history. They are a side effect of the agent working, not of the agent being watched. AgentBar reads them and nothing else. It spawns no CLI, installs no hook, injects no tracer. The companion piece on reading files instead of spawning agents covers why that constraint matters. Here the point is narrower: the raw material for a trace was on disk the whole time.

A trace built this way is forensic. It works on what already happened, from artifacts left behind. Live telemetry reports truth as it crosses a boundary. Reconstruction reads the receipts and infers what most likely happened.

Finding the moment one agent calls another

The first job is to notice a crossing at all. AgentBar's EdgeDetector scans the parent run's conversation turns, looking only at tool and assistant turns, since user and system turns do not execute commands. Within those turns it splits each line at the shell separators (&&, ;, |, a lone &) and asks what each segment actually runs.

The tokenizer is deliberately shallow, and the source says so. It skips leading environment assignments (FOO=bar) and command wrappers (nohup, sudo, env, time, and friends), then takes the first real token and strips any path. nohup codex exec "…" reads as codex. /usr/local/bin/claude -p "…" reads as claude. Agents launched detached or under sudo are exactly the ones worth tracking, so the wrappers matter.

This is no shell parser, and the job does not require one. The task is to tell a launch command apart from prose. In codex exec "fix the tests", the agent name is the command token. In a sentence like "I asked claude to review the diff", it never is, so the sentence produces nothing. That single distinction removes most false positives before correlation even runs.

Each detection becomes an invocation: the command that crossed the boundary, the target agent, the timestamp of the turn that contained it, the parent's working directory, and the parent tool-call id when the command came from a tool result.

Turning an invocation into an edge

An invocation says the parent announced another agent. On its own it does not say which session that agent started. The binary could have run and exited inside the same turn, or the matching session might be one of several. AgentBar correlates.

For each invocation it filters the candidate sessions. A session qualifies when its provider matches the target agent, it started after the invocation and within a 60-second window, and, when both sides carry a directory, the project equals the invocation's working directory. A missing directory on either side is treated as unknown rather than as a mismatch. If several sessions qualify, the earliest start wins.

The result is an edge, and the data model is explicit about what kind. Every edge this path produces carries an .inferred confidence and renders as a dashed line. The model also defines a confirmed confidence, for a linkage passed across the boundary by an environment variable or session id. The file-only path never emits one. There is no cross-process signal to confirm against, only timing and directory coincidence, so AgentBar does not claim certainty it does not have.

This is the honesty the format allows. A solid line would mean the linkage was observed. A dashed line means AgentBar saw a launch command, saw a matching session appear shortly after in the same place, and concluded one caused the other. Usually that conclusion is right. The label exists for the times it is not.

What the graph draws

The Graph window opens from the Active tab in the menu bar and lays one human request out left to right, in the style of an X-Ray or Datadog trace map. Shape carries identity and color carries state, so the two never compete.

Circles are actor points: a human prompt, an agent run, a terminal marker. Squares are tool calls. Triangles are model thought, system notes, and approval waits. An agent run draws as a circle with a provider-specific border and a 6-point status dot, colored by one of five states: running, waiting for approval, completed, failed, or idle.

The node is a run, not a session. A session can contain many runs, each --resume or re-run of a thread, and parent-child edges are keyed to the run. Resuming a conversation does not orphan its children.

A child agent expands inline at the point where it launched. When bash runs codex exec …, that Codex draws as a child lane rather than a plain tool call. The parent lane descends into the child flow and merges back once it completes, and deeper nesting means deeper lanes. Consecutive thoughts of the same kind collapse into a single ×N node; clicking it opens every merged message, and clicking any shape opens an inspector with the thought content, the tool command and output, or, for an agent run, the model, tokens, cache hit rate, changed files, and every event. Large graphs stay navigable through two-direction scrolling, zoom from 35 to 200 percent, and a fit button.

State is a function of the log tail

Knowing who called whom is half the question. The other half is what each agent is doing now. AgentBar answers it the same way it builds the graph, by reading files and computing.

Each provider reduces its log tail to a small signal: the timestamp of the last record, the kind of the last event, whether a tool proposal sits unanswered, whether a terminal success or failure appeared. A pure classifier turns that signal into a state with a fixed priority. Failure wins first, regardless of recency. A fresh log with an unanswered tool proposal reads as waiting for approval. A fresh log with an open question reads as waiting for input. Fresh and still producing reads as running. A terminal stop that has gone cold reads as completed. Anything else is idle.

The freshness window matters. An approval wait only makes sense while the log is recent, so once a log goes stale the classifier stops claiming the user owes the agent anything.

Precision varies by provider, and the UI says so. For Claude the approval wait is read from the raw JSONL tail: an unfinished tool_use with no matching result record. For Codex, Qwen, Gemini, and Cursor it is a best effort inferred from conversation turns, and the state note carries the hedge. The menu bar's Active tab polls the session directories on a 10-second throttle, limits the live graph to the last three days and 25 runs per provider, reuses unchanged summaries and conversation tails from an in-memory cache, and pins anything waiting for approval to the top.

What reconstruction gives up

Reading receipts has a cost, and naming it is what separates a monitor from a demo.

Correlation can misfire. Two Codex sessions started in the same directory within a minute of the same invocation both look like candidates, and the earliest wins even if the other was the real child. The 60-second window and the directory check keep this rare rather than impossible. A crashed agent and one waiting for approval can leave similar log tails, which is why the state carries a note instead of a guarantee. Gemini is detected as a launch command but never correlates to a session, because AgentBar has no Gemini provider to match against yet. The invocation is recorded; the edge is not.

And the whole picture is only as current as the files that feed it. AgentBar sees what the agents have flushed to disk, not live process truth. A run that finished a second ago but has not written its terminal record still looks active.

None of this is hidden in the rendering. Inferred edges are dashed. Estimated states carry a note. The Analysis pane, a set of pure functions over the graph, reports per-provider tokens, estimated cost, cache hit rate, and run count, alongside graph health: maximum depth with a warning for chains stacked too deep, orphan runs, and edge count, with the longest run named as the bottleneck. It describes what it sees and flags where it guessed.

The trace was never collected. It was left behind, one JSONL line at a time, by agents that had no idea anyone would assemble their receipts into a graph. AgentBar's job is to read those receipts carefully, connect them only where the evidence supports a connection, and say plainly where it had to guess. The causal chain you wanted is on your disk. So is the honesty about how much of it is inference.

Back to the blog

Get AgentBar

See how lightly it runs on your own Mac.

AgentBar runs entirely on files your agents already write to disk. It spawns no CLI, MCP server, browser, or agent to get usage or history. The Diag tab reports AgentBar's own CPU, memory, and spawned process count — always 0. macOS 14+ · Swift 6 / SwiftUI · source on GitHub.

git clone https://github.com/tsuvic/agentbar