Air traffic control for parallel Claude Code subagents. A live terminal and browser monitor that spots stuck, blocked and drifting agents, and the worktree file conflicts headed for your merge.

Left: a Claude Code session orchestrating seven worktree agents. Right: tracon in a split next to it.

A made-up run from tracon --demo, sped up.

uvx tracon --demo # look around a made-up run; no Claude Code session needed

uvx tracon # watch your newest Claude Code session that has subagentsYou dispatched six agents an hour ago. One has been waiting 40 minutes on a permission prompt

nobody can see. Two are editing shop/cart.py at the same time. One is restyling the admin

pages its brief said to leave alone, and another has run the same failing command six times.

Claude Code's status line shows none of it.

Like a TRACON

controller watching every aircraft on one scope, tracon shows every agent at once: what it

is doing, whether it is holding, whether it has drifted off its brief, and whether two of them

are on a collision course in the same file. Run it in a split next to your session.

- Read-only and local. It reads the transcripts Claude Code already writes to disk and

never writes to your repository. It sends something to a session only when you press n. See What it touches.

- No setup. No hooks, no config, no account. Point it at a project, or let it find the newest session.

- Optional LLM judge. A small model checks each busy agent against its brief, for about

a tenth of a cent a check, with a hard per-run budget. --no-judgeturns it off.

tracon is an independent project. It is not affiliated with, endorsed by or sponsored by Anthropic. "Claude" and "Claude Code" are trademarks of Anthropic.

Agents waiting on a permission prompt nobody can answer, agents that went quiet, hung test runs, processes left behind, loops, edits to the wrong checkout or on a stale base, two agents changing the same file, and (with the optional judge) agents drifting from their brief. Red needs you now; amber is worth a look.

enter opens an agent: what it is doing, the judge's concern and how the verdict changed,

every flag explained, the files it touched outside its brief, and a live tail of what it said,

in full. The tool calls between two messages fold into one line (▸ 23 tool calls Read 5 · Edit 8 …); click it to open that run, or press e to open them all.

x shows which agent edited which file. ! marks a file two agents were editing at the same

time.

n nudges your orchestrating session about the selected agent: tracon drafts a note with

the agent's state, flags, the judge's concern and what to do, and sends it straight away (see

Nudging the lead).

t is the timeline. [ and ] rewind and forward, space plays it back, and

--at "07:20" opens any past session as it stood at that time.

Reading the screen lists every state and flag, what it means, and what to do about it.

With --notify, a desktop notification fires when an agent needs you. The terminal title is

set to the watched session's name (◎ <name>, mirroring Claude Code's ✳ <name>), so the

split reads as part of that session.

Requires Python 3.12+, on macOS or Linux.

uv tool install tracon # or: pipx install tracon

uv tool install 'tracon[api]' # optional: judge through the Anthropic API instead of `claude -p`Or run it without installing: uvx tracon. From source:

git clone https://github.com/phalladar/tracon && cd tracon

uv tool install --editable .cd your-project && tracon # newest session with subagents in this project (or anywhere)

tracon --demo # a made-up run, to look around

tracon web # browser view only

tracon --list # recent sessions that spawned subagents

tracon -s 4f2a # pick a session by id prefix

tracon --at "2026-10-11 07:20" # replay a session as it stood at that time

tracon --once # print one snapshot and exitA good layout: your orchestrating session on one side, tracon in a split next to it

(in Ghostty, cmd+d).

w in the terminal, or tracon web, serves a page on 127.0.0.1:

- Timeline: zoom with the scroll wheel, drag to pan, double-click to fit. Hover any bar for the calls it covers. Silent stretches past the idle threshold are drawn in orange.

- Files: heatmap of edits per file per agent, shared files first.

- Replay scrubber: drag through the run, or press ▶ to play it back.

- Click an agent for its detail drawer and live activity feed.

Each agent row has a state (what it is doing now), a JUDGE column (the LLM's read of

whether it is on brief), and any number of flags (things worth a look). Press enter on a

row for the detail, which shows the judge's one-line concern and every flag's explanation.

You don't control agents from tracon; it is read-only. When something needs action, you act in your orchestrating session: answer the prompt there, or ask the lead to message, stop or re-dispatch the agent.

Once an agent has finished, its flags turn grey: they are history, not action. Only OUTSIDE and ORPHAN stay loud, because what they describe outlives the agent.

A pinned ◆ lead row shows the orchestrating session itself (running, working, or waiting on its agents or on you), so its activity is never mistaken for a subagent's.

The JUDGE column shows the latest verdict from the optional LLM check.

It is a small model's opinion of the brief against the last 40 actions, so treat it as a

prompt to look, not a fact. The detail view (enter) shows what the agent is doing, the

judge's concern, and how the verdict has changed over time.

Verdicts refresh at most every 3 min per agent, after 5 more tool calls. Press j to re-check

the selected agent now, for example after the lead has nudged it. Finished agents are never

judged; their last verdict stays, greyed out, and DRIFT and STUCK are dropped.

Dismissing. d on a drifting or stuck agent marks that verdict as dismissed: it shows

greyed out as "· dismissed", DRIFT or STUCK goes away, the nudge leaves the concern out, and

the next check lists it to the judge as a concern you've cleared, so it shouldn't be raised

again on the same actions. A new concern flags as usual. d again restores the verdict.

Rules catch mechanical failures. For drift, tracon periodically asks a small model

(Claude Haiku 5.5) to read an agent's brief, the lead's messages to it, and its last 40

actions, and answer on_track, drifting, stuck or unclear with a one-line summary.

The lead's messages amend the brief, so work the lead approved isn't drifting. Failed calls

reach it with their whole message, so it can tell an agent doing what a refusal told it (split

the command, use another tool) from one rewording its way around it.

See Judge verdicts for what each one means.

- When: only active agents, at most every 3 min each, and only after 5 new tool calls. One exception: after a drifting or stuck verdict, a message from the lead to that agent gets one early re-check as soon as the agent acts on it. Finished and idle agents are never judged.

- Cost: about $0.001 per check. On one day of real runs, 85 checks across 15 agents cost $0.09 in all, and the busiest agent under 2 cents. A per-run budget (default $1.00) stops judging when reached, and the spend is shown in the header.

- Backends: with ANTHROPIC_API_KEYset and the[api]extra installed, it uses the Anthropic SDK. Otherwise it usesclaude -pon your Claude Code login, counting against your plan.

- Privacy: checks send the brief and summaries of recent tool calls (commands, file

paths, error text) to Anthropic, as your sessions already do. --no-judgeturns it off.

Judgments are cached in ~/.cache/tracon/judgments.json and dismissals in

dismissed.json beside it, so replays and restarts show them. Each verdict records the

version of the prompt that made it; when an update changes the prompt, each active agent

gets one re-check instead of keeping a verdict the old prompt made.

Most of the "What to do" advice above is "have the lead…". Press n on an agent (in the table

or its detail) and tracon nudges the lead with that note: the agent's id, state, flags, the

judge's concern, and the matching advice, phrased for the lead's own tools (SendMessage,

TaskStop). When the note is about refusals, drift or a stuck agent and the agent has been

refused anything, it quotes the last refusal word for word, so the lead can check the claim

without opening the transcript. It is sent as soon as you press the key; a notification confirms it.

It is delivered through Claude Code's cross-session inbox: tracon finds the lead's process in

~/.claude/sessions/ and writes one message to the Unix socket listed there. The lead sees it

as a message from another session, between tool calls or as a new turn if it is idle, and it

can't approve permission prompts. If the lead's crossSessionInbound setting is hold (or

its permission mode makes Claude Code review peer messages), the message waits for you to

approve it in that session. If the lead isn't running, n copies the note to the

clipboard instead. tracon never reads the inbox auth keys; the socket accepts unauthenticated

messages from your own user on macOS and Linux.

tracon is read-only. This is everything it does outside its own process:

No telemetry, no account, no config files.

Claude Code writes, per session:

~/.claude/projects/<encoded project path>/

<session>.jsonl the orchestrating ("lead") session

<session>/subagents/agent-<id>.jsonl each subagent's transcript

<session>/subagents/agent-<id>.meta.json description, model, worktree, branch

tracon tails these files incrementally. Every few seconds it also checks, per worktree,

which processes are running there (lsof and ps, keeping only what Claude launched, plus

orphans) and the git state (branch, commits ahead, dirty files). States and flags are derived

from both.

tracon/

transcript.py JSONL -> compact events (all knowledge of the transcript format lives here)

session.py find sessions, track the lead and its agents

probe.py live processes and git state per worktree

signals.py one agent -> state + flags (pure, testable)

lane.py whole-run view: file collisions, judge results, assess_all()

judge.py the optional LLM check, its scheduling, budget and cache

advice.py the nudge `n` sends the lead

demo.py the made-up run behind `--demo`

inbox.py find a live session's inbox socket and send to it

app.py Textual TUI

web.py local HTTP server + JSON API; static/index.html is the page

cli.py entry point

- Transcript files are Claude Code internals, not a public API. A format change may break

parsing; the fix belongs in transcript.py. The same goes for the session registry and inbox socket used byn, ininbox.py.

- Developed on macOS; tests run on macOS and Linux. On Linux, processes are read from /proc(nolsofneeded), notifications usenotify-send, copy useswl-copy,xcliporxsel(else the terminal's OSC 52), andoopens Ghostty orx-terminal-emulator. Windows isn't supported; WSL should behave like Linux.

- In replay there is no process data, so a long-running shell call shows as PENDING rather than RUNNING or BLOCKED.

uv sync --group dev

uv run pytest # synthetic transcripts; no Claude Code or network needed

uv run tracon --once --no-judge # quick check against your latest session

uv run --with pillow python scripts/screenshots.py # regenerate docs/screenshots from the demo (macOS, Chrome)

uv run --with pillow python scripts/hero.py # regenerate the README's split-terminal hero.gifSee CLAUDE.md for architecture notes and conventions.