@lilbunnyrabbit/agtc
v1.14.1
Published
Agent Traffic Control: air traffic control for your coding agents. Terminal dashboard for local Claude Code and Codex sessions.
Maintainers
Readme
agtc — Agent Traffic Control
Air traffic control for your coding agents.
A terminal dashboard for every Claude Code and Codex CLI session running on your Mac: which repo and worktree each one is in, whether it is busy, idle, waiting for input, or finished with output you have not looked at yet, and what it has changed so far. Run it inside tmux and it becomes a sidebar: enter puts the selected agent next to it, v opens the diff, o opens the checkout in Zed.
bunx @lilbunnyrabbit/agtcOr install once and get the agtc command:
bun add -g @lilbunnyrabbit/agtc
agtc update # later: pulls the newest versionRequires Bun and macOS (Terminal.app integration). Does not run under npx.
Setup
The dashboard alone needs nothing else. The rest of this README, agents in tmux and Zed, needs:
bun add -g @lilbunnyrabbit/agtc, soagtcis on the PATH for Zed tasks and the hub (~/.bun/bin, which Bun's installer adds to the shell).bunxworks for the plain dashboard only.brew install tmux. Every key that starts, shows or resumes an agent (enter,v,n,N,R) andagtc attach/agtc sendneed it.brew install umputun/apps/revdiffforv.- Optional, for Zed: the two tasks and two keys under Agents inside Zed, and
"terminal": {"option_as_meta": true}if you wantoption-athere.
Updating: agtc update, then quit agtc in the hub (q) and run agtc tmux again. Agents keep running through that. tmux and Zed configuration never change between versions unless the changelog says so.
What you see
agtc 1.5.0 ✳ 2 ⬡ 2 input 0 done 1 busy 1 idle 2 inactive 0
acme-platform ──────────────────────────────────────────────── 1 done 4 sessions
▌ 1 ✳ done Settings page safe padding 2m
2 ⎇ ✳ busy Sidebar header overflow on iOS 5m
3 ⬡ idle ╰ review 1m
4 ⬡ idle Can the chart legend be restyled 1h✳Claude Code,⬡Codex,⎇session lives in a git worktree,╰ reviewa reviewer started withV, hanging off the session it reviews; the digit left of a row that is done or needs input is its1…9key- needs input blocked on a permission or dialog
- done turn finished after your last prompt and you have not looked at it yet
- busy working, idle waiting for you, inactive not running (recent history)
Rows are one line each; s or --roomy makes them roomy: two lines each and a blank line between two of them, all of it clickable; the edge on the left starts and ends mid-line. Under the title sits what the agent waits for, when it waits, and at the right end uncommitted changes (+66 −5) and commits ahead; a thin edge on the left has the colour of the status. A block that is busy, done or needs input has the full colour of its status with dark text. An idle or inactive one has no background, only the edge. A subagent that still works gets a line of its own under the row, hanging off it by a dotted ╰┄ ◇ in magenta (a reviewer's ╰ is solid and cyan), with its type and task and how long it has been at it; it goes when the subagent is done. Compact rows hold the same on one line, where the title keeps its room and the branch is cut first. s flips to one line a row, --compact starts that way.
Selected session gets a detail pane: working directory, worktree and branch, uncommitted changes (3 files +120 −14 2 ahead of origin/main; v shows the files), the last prompt with its age and, when the terminal is tall enough, the two before it. A Claude session that created a worktree mid-way and moved into it is tracked by where it edits files, not where it started; the other checkouts it touched are listed as well.
Sessions that want you, input and done, get a filled badge and a title in the same colour, and their group line counts them, so nothing waiting hides in a long list. The order is fixed: repos alphabetically, live sessions in the order they started, finished ones below them newest first. A status change recolours a row, it never moves it, and the selection stays on the session it was on.
agtc graph, full screen in a terminal of its own, is a read-only overview of what runs: the same sessions drawn like a pipeline, a box per running session on the left, what it spawned in boxes to its right, arrows between them. The border takes the status colour. It redraws on every poll and takes no key but q.
agtc worktrees [DIR] lists the worktrees of DIR's repository (default cwd) with what keeps each one around: the agent running or last seen there, uncommitted files, commits the remote lacks. Sorted most alive first. A ✓ marks the ones safe to remove: fresh (never committed to, no changes), gone (branch deleted on the remote, so merged or closed there; a squash merge leaves no trace in the base's history, which is why the remote is asked and not git branch --merged) and missing (directory already deleted, git still lists it). One caveat: a commit made in a gone worktree after its last push is invisible once the remote branch is pruned, so prune deletes it with the branch; check dirty and unpushed are what you expect, gone is trusted. In a terminal the list is interactive: the removable ones start checked, space toggles any row that is not live or locked (a dirty or unpushed one goes with --force, the branch stays), a checks all removable, n none, enter asks once and removes the checked, q quits; piped or with --once it prints the table. agtc worktrees prune skips the list and removes the removable ones after a y, deleting the branch of a gone or fresh one with it; missing only loses its registration. agtc worktrees rm NAME takes one by directory name or branch, refuses dirty, unpushed, detached and locked unless --force, and never removes one an agent is running in. --force keeps the branch.
acme-platform ────────────────────────────────────────────────────────────── 4 sessions
┌─────────────────────────────────┐ ┌─────────────────────────────────┐
│ ✳ ⎇ Sidebar header overflow │─┬─▶│ ⬡ review │
│ idle · 5m · fix/header │ │ │ busy · 4m │
└─────────────────────────────────┘ │ └─────────────────────────────────┘
│ ┌─────────────────────────────────┐
└─▶│ ◇ Explore: callers of Header │
│ busy · 1m │
└─────────────────────────────────┘
┌─────────────────────────────────┐
│ ✳ Settings page safe padding │
│ busy · 2m · main │
└─────────────────────────────────┘Children are the session's reviewers (V) and ◇ its subagents: agents it runs inside its own process (Claude's Agent tool, Codex collaborators), busy or finished in the last ten minutes, with their type and what they were asked. A session with its children is a family; families flow across the width, as many per row as fit, so a full screen holds them all. A terminal too narrow for two boxes side by side hangs the children under their parent. What does not fit ends in a dim ….
Keys
| key | action |
| --- | --- |
| j k ↑↓ | move the selection up / down, inactive rows included |
| J K | the running session above / below, selected and jumped to in one key, wrapping around |
| 1…9 | the session with that digit, drawn left of its row, selected and jumped to; only a session that is done or needs input has one |
| enter | jump to that session: its Terminal.app tab, or its tmux pane (inside tmux it joins agtc's window, see below) |
| o | open the checkout in the editor (AGTC_EDITOR, default zed) at the most recently changed file |
| v | review what changed, in a tmux popup: uncommitted work, the whole branch since its base, or any commit. enter opens one in revdiff (brew install umputun/apps/revdiff): a comments on the line under the cursor, ] and [ jump between changes, q there hands the comments to the session with file and line and closes it all, leaving revdiff without comments goes back to the list: a Claude session gets them as a message and starts on them, a Codex one finds them in its input, unsent. Q in revdiff drops them |
| V | start a read-only reviewer agent for that session's work in a pane beside the session's (a window of its own when the session is not in tmux). Asks for the spec: tab walks the spec the session wrote (~/.cache/agtc/specs/<session>.md, when it exists), ask <tool> for a spec, its first and last prompt; or type text or a @path. Then which tool reviews (the other one by default). It shows as a row under the session. A Claude reviewer is named <checkout> review and told the author's session name, so its questions go to the author over Claude's session messaging, not to you; the author is told to ask you what it does not know. On a reviewer's row, V hands its report to the reviewed session: a Claude session gets it as a message and works through it, a Codex one finds it in its input, unsent |
| x | close a reviewer: its tmux pane and the agent in it. Refused while its report is unread (V hands it over, m drops it) |
| X | close an agent: every pane of its tmux window, a reviewer beside it included. Asks y/N first. In a worktree with nothing uncommitted and no other agent, asks once more whether to remove the worktree (git worktree remove, never --force; the branch goes only when it is gone from the remote or was never committed to). The session stays in the list as inactive, R resumes it |
| n | the new popup: claude, codex or terminal, where, and a first message. Starts from the selected agent's kind and checkout; tab walks the checkouts in the list, option-enter or ctrl-j makes a new line, enter starts it in a new tmux window |
| N | new worktree of that repository (asks for a branch name), then an agent in it |
| R | resume an inactive session in a new tmux window, so attach and send can reach it. A reviewer comes back read-only |
| S | restore the last hub: every agent window tmux held when agtc last looked, resumed in place, reviewers read-only |
| / | search across every prompt you ever typed in any session, plus worktree, branch, path, tool, status |
| m / M | mark selected / all as seen |
| a | show inactive sessions |
| d | toggle detail pane |
| s | roomy rows or compact ones |
| ? | the whole key reference, tmux keys and the review loop included, in a popup (q closes); outside tmux, the footer shows every key instead of the ones for the selected session |
| q | quit |
| mouse | click selects a row, a double click stages it like enter, the wheel moves the selection. Inside tmux this needs mouse on, which agtc sets for its session |
| prefix a, option-a | tmux keys agtc binds at start: back to agtc's pane from any window in the session. option-a needs the terminal to send option as meta |
| prefix space, option-space | tmux keys agtc binds at start: a popup over the pane you are in, naming the session there and giving every action below a key, see Keys from the agent's pane |
| option-n | tmux key agtc binds at start: the n popup over the pane you are in, any pane, a plain shell too; you land on what it started |
| option-j, option-k, option-1…option-9 | tmux keys agtc binds at start: J, K or the digit, typed into agtc from whatever pane you are in, so you loop between agents without leaving the one you are typing in |
Flows
Every flow assumes the hub is running: agtc tmux in any terminal, Zed's included. Pass --base origin/staging when new worktrees should branch from staging, and --jump zed when you look at agents from Zed rather than from the hub (see below).
Start a task in a fresh worktree. Select any session of that repository, N, type the branch name, enter. agtc adds the worktree under .claude/worktrees/, starts the agent there, shows it. Type the task.
Start an agent with a task, or a shell to run something else in. option-n from anywhere, or n in the hub. Pick claude, codex or terminal, the checkout, type the first message, enter. terminal opens a bare shell window there, for a launcher of your own.
Continue a finished session. a to show inactive ones, select it, R. It resumes in a tmux window with its whole transcript.
Move a session that runs in Terminal.app into tmux. /exit it in its tab, then the flow above. Sessions outside tmux are listed and can be jumped to, but attach, send, v and the stage cannot reach them.
Check on agents. The list: input needs you, done finished since you last looked, busy, idle. Detail pane: what changed, how far ahead of the base branch, last prompt. enter puts the agent next to agtc; M-a comes back. m or looking at it marks it seen.
Review what an agent did. Select it, v, pick uncommitted work, the branch or a commit, a on any line to comment. q and the comments go to the agent. For the diff in the editor, o.
Get a second opinion. Select the session, V. First time, take ask <tool> for a spec: the request reaches the session (a Claude session acts on it at once, a Codex one has it in its input, enter), and the agent writes what was asked, what the result must do and what is out of scope to ~/.cache/agtc/specs/<session>.md, in its own words but without its reasoning. One file per session: a resumed session finds it, a second session in the same checkout asks for its own. V again: the file is the default now (edit it in Zed first if you like), pick the reviewer. A fresh agent of the other tool starts in the same checkout, in a pane beside the session's, with the spec and the base branch, reads the diff, and reports findings, questions and a verdict. The verdict, ready or not ready, lands on the reviewer's row, in the detail pane with its first line, and in the banner when the reviewer finishes off screen. It cannot edit: Claude runs with every writing tool disallowed, Codex in its read-only sandbox. Its row hangs off the session's, so its status sits right under the work it judges. A Claude reviewer asks the author first, over Claude's session messaging, and the author is told to bring you what it cannot answer; a Codex reviewer asks you: enter on its row and answer. When it is done, V on its row: the report goes to the reviewed session and that session comes on stage. A Claude session takes it as a message and works through it, told to bring you any finding it disagrees with; a Codex session has it in its input, unsent, so read it, cut what you disagree with, enter. x on the reviewer closes it. The agent fixes, you V again on the session for a fresh pair of eyes, or v and commit.
Ship it. Tell the agent to commit, push and open the pull request; v first to check what goes in. agtc itself never sends anything off the machine.
Edit by hand or point the agent at a line. o opens the checkout in Zed as its own sidebar workspace, at the last changed file. In Zed, cmd-shift-a shows that agent in a terminal tab (agtc attach). Select code, cmd-shift-enter (agtc send): the agent's input gets file:row and the selection as a code block; type what should change, enter.
Zed mode. Start with --jump zed: enter opens the checkout in Zed instead of moving panes, so the Zed terminals attached to agents keep showing them. The hub is then just the list. Use this when you live in Zed; use the default when you live in the terminal.
Resume after a restart. Agents live in tmux, so closing Zed or the terminal loses nothing. agtc tmux attaches again; in Zed, cmd-shift-a again in each workspace. After a reboot or tmux kill-server the agents are gone too: agtc tmux, then S opens every window of the last hub again, same names, same directories, each running claude --resume (or codex resume) for its session. agtc notes the windows on every poll, and says on start when some are missing. Agents already running are skipped, so S twice does nothing extra.
Never archive or close a thread in Zed's sidebar for a worktree Zed itself created (paths under ../worktrees/): that deletes the worktree. Worktrees from N, Claude or git worktree add are safe.
Flags
agtc tmux open (or attach to) a tmux session with agtc in a window named hub
agtc graph read-only overview of what runs: a box per session, its reviewers and subagents to the right, live; q quits
agtc attach [DIR] show the agent running in DIR (default cwd) in this terminal, live
agtc send [DIR] --file F --row N
type "F:N" plus $AGTC_SELECTION as a code block into that agent's input
agtc worktrees [DIR] the worktrees of DIR's repository: what runs or last ran in each, uncommitted and unpushed work, ✓ on the removable; a checkbox list in a terminal, a table when piped
agtc worktrees prune [DIR]
remove the removable ones after a y/N, with their branches
agtc worktrees rm NAME [DIR] [--force]
remove one by directory name or branch; --force takes uncommitted or unpushed work with it
agtc --jump zed enter opens the checkout in the editor instead of pulling the pane next to agtc
agtc --days 7 list inactive sessions from the last 7 days (default 2)
agtc --interval 1000 poll every second (default 2000 ms)
agtc --stage 75 width of the agent pane next to agtc inside tmux, percent (default 70)
agtc --worktrees DIR where N creates worktrees, relative to the main checkout (default .claude/worktrees)
agtc --base staging branch N starts worktrees from (default origin's default branch)
agtc --inactive start with inactive sessions shown
agtc --roomy start with two lines a row (AGTC_ROOMY=1 does the same)
agtc --bell ring the terminal bell too when a session finishes or needs input while you are elsewhere
agtc --no-notify no macOS notification when a session finishes or needs input off screen
agtc --json print all sessions as JSON
agtc --once print one frame and exit
agtc update update this install to the newest version
agtc --help usageAGTC_WORKTREES, AGTC_BASE, AGTC_JUMP and AGTC_NOTIFY=0 are the environment forms of --worktrees, --base, --jump and --no-notify. AGTC_TMUX_SETUP=0 stops agtc from binding prefix a / option-a, turning the mouse on and setting CLAUDE_CODE_TMUX_TRUECOLOR in its tmux session. AGTC_EDITOR picks the editor for o. The default zed gets --existing: the checkout becomes its own workspace in the sidebar of the Zed window you already have open, or Zed switches to it if it is there already. This works regardless of the cli_default_open_behavior setting.
tmux hub
The intended setup: one tmux session holds every agent, agtc sits in a narrow pane on the left, and the agent you selected sits on the right. Zed stays a single window for editing and browsing; you only open a checkout there when you need it, so its language servers do not run for every worktree at once.
brew install tmux umputun/apps/revdiff # revdiff for v
agtc tmux # creates the session "agtc" with agtc in window "hub", then attachesRun that from any terminal you like, Zed's included. Inside the hub:
nstarts an agent, with its first message when you typed one, or a shell, in its own tmux window and shows it. The popup starts from the selected session's kind and checkout;tabcycles through every checkout in the list, or type a path. Windows are named after the checkout:agtc, oragtc/feat+xin a worktree.Nasks for a branch name, adds a worktree from the main checkout (git worktree add -b <name> <dir> <base>, or the existing branch when there is one, after fetching a remote base) and starts an agent there. Directory<main checkout>/.claude/worktrees/<name>with/turned into+, like Claude Code's own worktrees; change it with--worktrees.option-j/option-kfrom any pane stage the running agent above / below;option-1tooption-9stage the one with that digit in the list, an agent that is done or needs input. Each is a tmux root binding that selects agtc's window and typesJ,Kor the digit into agtc, so the list moves with you. Bound only when the key is free.entermoves the selected agent's pane into agtc's window as the stage; the agent that was there goes back to a window of its own. Windows keep their names. The first stage goes right of agtc,--stagepercent wide; after that the two panes swap places, so the hub layout stays as you left it. Rearranging is tmux's job:prefix space spaceflips to stacked, dragging the border resizes,prefix zzooms the stage to full screen. On a small screen skipenterand switch windows withprefix worprefix n, thenprefix aback to agtc.vopens the list of what changed as a popup, revdiff on what you pick;escon the list closes it and you are back in agtc.Vstarts a reviewer read-only, with a prompt written to~/.cache/agtc/prompts/, as a split beside the reviewed session's pane, so both are on screen; staging either one brings the other along, and whatever leaves the stage leaves together. A session outside tmux gets its reviewer in a window named<checkout> review. agtc remembers which session it reviews, so the pairing survives restarts andS.Von the reviewer hands its last message to the reviewed session the wayagtc senddoes: to a Claude session's inbox as a message, into a Codex session's pane bracketed, nothing submitted. A reviewed session that is not running, or a Codex one outside tmux, gets the report on the clipboard instead.oopens the checkout in Zed at its most recently changed file. Each checkout is its own workspace in Zed's sidebar, so the git panel and the project panel are that checkout's. Zed's sidebar needs the agent panel enabled (agent.enabled, the default); with it off, Zed opens a window per checkout instead.- Every agent pane gets a header band on its top border: worktree mark, tool, status, title and what it waits for on the left, worktree or branch and lines changed on the right. agtc's own pane gets one too. Like tabs, the band of the pane you type in is darker, and agtc's then reads
▶ agtc · your keys go to agtc, so a key meant for an agent is not pressed in the hub by mistake.AGTC_HEADER_STYLEandAGTC_HEADER_ACTIVE_STYLEtake other tmux styles,bg=#1a3a45andbg=#061419by default. A band is one line: tmux draws a border no taller. agtc sets the headers while it runs and takes them back when it quits.--no-chromeorAGTC_CHROME=0leaves the borders alone. - Agents started by hand also count: any tmux pane running
claudeorcodexis found, in any session.
Careful with worktrees Zed created itself (under its git.worktree_directory, by default ../worktrees/<repo>/<name>/<repo> next to the repo). Each belongs to a thread in Zed's sidebar, and archiving that thread, or closing its entry, runs git worktree remove on the directory after saving pending changes as WIP commits under refs/archived-worktrees/<n>. An agent working there from a terminal loses its working directory mid-task. agtc never removes anything, it only opens the checkout; but zed --existing does open that sidebar. Worktrees made with git worktree add, by Claude Code, or by agtc's N (all under .claude/worktrees/ by default) are not Zed-managed and are not affected. Prefer those for agent work.
Getting back to agtc from anywhere in the session is prefix a or option-a. agtc binds both itself every time it starts inside tmux, turns the mouse on for its session, and sets CLAUDE_CODE_TMUX_TRUECOLOR=1 in the session environment so agents started from the hub keep Claude Code's 24-bit colours (it falls back to 256 colours under tmux otherwise, which makes the logo and diff backgrounds look off). ~/.tmux.conf needs nothing. A key you already bound to something else is left alone; AGTC_TMUX_SETUP=0 skips the whole thing. option-a only reaches tmux when the terminal sends option as meta (Terminal.app: profile, Keyboard, "Use Option as Meta key"; Zed: "terminal": {"option_as_meta": true}), prefix a works everywhere. If you would rather own the lines:
set -g mouse on
bind a select-window -t hub \; select-pane -Z -t hub.0
bind -n M-a select-window -t hub \; select-pane -Z -t hub.0tmux in five keys
Everything below is stock tmux; the prefix is ctrl-b, pressed and released before the next key. Every agent is a window, listed in the status bar at the bottom; the hub is window hub, whose left pane is agtc and whose right pane is the staged agent.
- Switch windows:
prefix wopens a chooser,prefix n/prefix pgo next and previous,prefix 0..9by number,prefix lthe window you came from. Withmouse on, clicking a name in the status bar works too. From agtc,enteron a session does the same thing and also stages it. - Move between panes:
prefix ocycles,prefixplus an arrow key goes in that direction, or click the pane.prefix zzooms the current pane to full size and back. - Change the layout:
prefix space spacecycles side by side, stacked and more (agtc's popup tookprefix spaceand keeps tmux'snext-layouton itsspace),prefix {swaps the two panes,enterkeeps whatever you set. Resize by dragging the border, orprefix :andresize-pane -L 10(-R,-U,-D). tmux's ownprefix ctrl-arrownever arrives on macOS until you untick the Mission Control shortcuts under Keyboard Shortcuts. - Close an agent: quit it (
/exitin Claude Code,ctrl-ctwice orexitfor a shell), and its window closes with it. Nothing else is needed, agtc notices.prefix &kills the window with everything in it after a confirmation,prefix xkills just the pane; both end the agent, so prefer quitting it. - Leave:
prefix ddetaches, agents keep running,agtc tmuxbrings the session back. Closing the terminal window does the same. - Scroll:
prefix [enters copy mode, arrows or page up/down move,qleaves. Withmouse on, the wheel does it directly.
Sessions in Terminal.app tabs keep working as before; agtc uses whichever the session runs in. Agents survive closing the terminal that shows the hub, agtc tmux attaches again.
Keys from the agent's pane
prefix space, or option-space where the terminal sends option as meta, opens a popup over the pane you are typing in. On top the session's name, the tool, the start of the session id, the status, the branch and the checkout. Below that the keys, the same letters as in the hub, each one press:
| Key | Does | Where you end up |
| --- | --- | --- |
| V | starts a reviewer beside the agent; on a reviewer's pane it hands the report to the reviewed agent | on the reviewer, after a popup asked for the spec and the tool |
| X | closes the agent and its window; x on a reviewer's pane closes the reviewer | wherever tmux puts you, after a popup asked y/N |
| v | what changed, commit by commit, in revdiff; your comments go to the agent | on the agent, comments handed over |
| o | the checkout in the editor | where you were |
| a digit | the agent with that digit in the list under the keys | on that agent |
| space | tmux's next-layout, which prefix space was before | where you were |
Arrows mark an action and enter runs it; esc or q closes the popup. While it is open the pane's border takes the popup's colour. Every popup of agtc's darkens what is behind it until it closes: the panes and the status line turn to grey text on black, and agtc redraws its own list without colours. tmux has no opacity, so in an agent's pane text with a colour of its own keeps it. AGTC_BACKDROP takes another tmux style, 0 turns it off. A pane without an agent gets o and space for the directory it is in. option-a goes back to agtc, option-n starts something new, option-j / option-k the agent above / below.
Under the keys the popup lists the other agents that are done or need input, each with its digit from the hub, and that digit takes you there. option-1 to option-9 do the same without the popup.
A key from a pane never sends you to agtc for an answer. What it asks comes as a popup over the pane: type, tab or the arrows walk the choices, enter answers, esc drops the question. The same keys typed in agtc ask in its footer, as before.
Holding option alone cannot open it: a terminal sends nothing for a modifier until a key comes with it. The key types an escape sequence naming your pane into agtc, which holds the sessions and opens the popup with what it knows, so nothing is scanned. Every action but o reaches agtc the same way, so agtc selects that row and does what the key does there.
Agents inside Zed
The other way round: keep the agents in tmux, but look at them from Zed. Two commands, both meant to run from a Zed terminal inside a checkout, and both find the agent by directory (the checkout it works in, or one containing the terminal's cwd; several agents in one checkout: the one that needs you most).
agtc attachshows that agent in the terminal, live. It is a tmux session grouped with the agent's, so it shares the windows but keeps its own current window: the hub can show something else at the same time. No status bar; closing the terminal drops the view, the agent keeps running.agtc send --message TEXT --file F --row NsendsF:N,$AGTC_SELECTIONas a fenced code block when it is set, and TEXT. With--message, a Claude session gets all of it on its inbox and acts on it. Without one, or for a Codex session, it is typed into the input, bracketed paste, nothing submitted, and you finish the sentence there. A session that holds or refuses the message gets nothing typed: the text is copied andagtc sendexits 1.
agtc attach, and agtc send to a Codex session, need the agent to run inside tmux: started from the hub with n, N or R, or by hand in a tmux window. An agent in a Terminal.app tab is reported, not attached; quit it there and R brings it into tmux. Zed hands the environment of the zed call that opened a project to that project's terminals, so o from the hub launches Zed without tmux's variables, and agtc attach trusts TMUX_PANE only when its terminal really is that pane.
Wire them to Zed tasks (~/.config/zed/tasks.json) and keys (~/.config/zed/keymap.json):
[
{
"label": "Send selection to agent",
"command": "agtc",
"args": ["send", "--file", "$ZED_RELATIVE_FILE", "--row", "$ZED_ROW"],
"env": { "AGTC_SELECTION": "$ZED_SELECTED_TEXT" },
"reveal": "never",
"hide": "always"
},
{
"label": "Attach agent",
"command": "agtc",
"args": ["attach"],
"use_new_terminal": true,
"allow_concurrent_runs": true
}
][
{
"context": "Editor",
"bindings": {
"cmd-shift-enter": ["task::Spawn", { "task_name": "Send selection to agent" }],
"cmd-shift-a": ["task::Spawn", { "task_name": "Attach agent" }]
}
}
]In this mode start agtc with --jump zed (or AGTC_JUMP=zed): enter then opens the session's checkout in Zed like o, and n / N leave new agents in their own windows instead of pulling them next to agtc, so a Zed terminal attached to one keeps showing it. Zed restores terminal tabs after a restart but not what ran in them, so attach again.
agtc update runs bun add -g @lilbunnyrabbit/agtc@latest (or the npm equivalent) for you. Plain bun update -g will not do: bun add -g writes ^0.x.y to the global package.json, and a caret on a 0.x version never crosses a minor release. Under bunx or a git checkout the command only tells you what to do.
How it works
No hooks, no daemons, no config. Everything is read from what the tools already write:
- Claude Code:
~/.claude/sessions/<pid>.jsonfor live status and cwd,~/.claude/history.jsonlfor prompts,~/.claude/projects/<project>/<session>.jsonl, read incrementally, for the paths a live session's tool calls touch: any tool, so shell edits count like Edit calls. Subagents from~/.claude/projects/<project>/<session>/subagents/:agent-<id>.meta.jsonnames the type and task, the last entry ofagent-<id>.jsonlsays whether it is still working (a tool call or its result) or done (a plain message, or an interruption); the parent's tool result cannot tell, it is written when the agent launches. The checkout most of the recent calls hit is where the session works (that is how a session that moved into a worktree is placed, and why onecdelsewhere does not move it). Only checkouts listed bygit worktree listfor the repository the session started in count, so writes to memory files, dotfiles or other repos never move a session, and a session whose directory vanished (Claude then reports your home as cwd) stays under its repo. - Codex: the
thread-writer-locks/<id>.lockfile a runningcodexholds open identifies its thread;~/.codex/state_*.sqliteand the thread's rollout log give title, prompts and busy/idle. Collaborators a thread spawned come from itsthread_spawn_edgestable joined tothreads, named by nickname and role since their prompts are encrypted on disk, busy or done from their own rollout. - Worktrees:
git rev-parse --git-dirvs--git-common-dir. Changes:git status --porcelain,git diff HEAD --numstatandgit rev-list --count <base>..HEADagainstorigin/HEAD(elsemain/master), every 10 s per live checkout. - Terminal.app via AppleScript: tab titles, which tab you are looking at (that is how "done" turns into "idle"), and focusing a tab on
enter. - tmux:
list-clientsandlist-panesmap ttys to panes and tell which window is in front of an attached client. A client sitting in a Terminal.app tab counts as looking only while that tab is in front.
"Seen" marks persist in ~/.cache/agtc/state.json, and so do the list of agent windows S restores and the reviewer pairings from V (by session id; a Codex reviewer is matched by its tmux pane until its thread id exists).
Security
agtc reads what the tools write and is offline. It types into an agent, or messages one, only on your key. The full footprint:
- Reads
~/.claude/sessions/*.jsonand, to message a Claude session, the*.keybeside its entry,~/.claude/history.jsonl, the last 256 KB of~/.claude/projects/*/<session>.jsonlfor live sessions, their<session>/subagents/agent-*.meta.jsonand the tail ofagent-*.jsonl,~/.codex/state_*.sqlite(opened read-only) and Codex rollout.jsonllogs. - Writes
~/.cache/agtc/state.json(session ids and timestamps of when you looked at them, the session last opened per checkout, the agent windows tmux held, forS, and which reviewer reviews which session) and, onV, the reviewer's prompt under~/.cache/agtc/prompts/. Specs under~/.cache/agtc/specs/are written by the agent you asked, agtc only creates the directory and reads them. Inside tmux it also sets a@agtc_windowpane option on panes it moves, and unless--no-chromea@agtc_headerpane option,pane-border-statusandpane-border-formaton windows that hold an agent,status-rightandstatus-right-lengthon its session; those are unset when it quits. - Spawns while polling:
ps,lsof,git(rev-parse,worktree list,status,diff,rev-list,symbolic-ref),osascriptandtmux list-*, always as argv arrays, never through a shell. The tty passed to AppleScript is validated againstttys<digits>first. When a poll finds a session that just turned done or needs input while its terminal is off screen,osascript -e 'display notification …'shows a banner with the session's title and status (--no-notifystops that). - Spawns once at start inside tmux:
tmux set-option mouse onandtmux set-environment CLAUDE_CODE_TMUX_TRUECOLOR=1for its own session, andtmux bind-keyforprefix a,option-a,prefix space,option-space,option-n,option-j,option-kandoption-1tooption-9, aftertmux list-keysshowed them free or already agtc's.prefix spaceis the one stock tmux binding it takes. - Spawns on a key press only:
tmuxpane commands (enter,n,N), the editor fromAGTC_EDITOR(o), forva tmux popup that runsgit logand thenrevdiffin the checkout, whose comments are read from a file under the temp directory and pasted into the agent's input, and forNgit fetchof the base branch plusgit worktree addin the main checkout.nandNtype the bare commandclaudeorcodexinto a fresh shell in the checkout, with the first message you typed inn's popup as its one quoted argument, nothing else;nwithterminaltypes nothing;RandStypeclaude --resume <id>orcodex resume <id>the same way, with the read-only flags below added back for a reviewer.Vtypesclaude "$(cat <prompt file>)" --session-id <uuid> --disallowedTools 'Edit,Write,NotebookEdit,Read(~/.claude/**),Read(~/.codex/**)' --allowedTools 'Read,Grep,Glob,Bash(git diff:*),…'orcodex --sandbox read-only --ask-for-approval never "$(cat <prompt file>)".agtc send,vandVon a reviewer hand text to an agent: a Claude session gets it over the unix socket its ownSendMessagetool uses (~/.claude/sessions/<pid>.jsonnames it, the token beside it authenticates), as a message it acts on, nothing else ever goes through that socket; a Codex session gets it pasted into its input without enter (Vreads the reviewer's last message from its transcript or rollout log first, and copies it withpbcopywhen the pane is out of reach);agtc attachcreates a grouped tmux session.xrunstmux kill-paneon a reviewer's pane, and only after its report was handed over or dropped;Xruns it on every pane of an agent's window but agtc's own, after ay. These are the only processes agtc ends. The only things agtc deletes are worktrees and branches, and only throughagtc worktrees pruneafter ay,agtc worktrees rm, orXafter its secondy:git worktree remove(with--forceonly when you passed it), thengit branch -dfor a never-used branch andgit branch -Dfor one whose remote branch is gone,git worktree prunefor a directory that is already gone. Nothing runs in a worktree an agent is active in. - Network: none.
bun run check:offlinefails CI if anything undersrc/references fetch, http, sockets or Bun's server APIs. The one thing that reaches the registry isagtc update, and it does so by runningbun add -g(ornpm install -g), never from agtc's own code. - Dependencies: zero at runtime.
bun-typesfor development only. No install scripts. - macOS asks for Automation permission (control Terminal.app) the first time. Denying it only disables tab titles, the seen detection and
enterfor sessions in Terminal.app tabs. --jsonincludes each session's title, first and last prompt, and working directory. The full prompt list stays in memory only.
To run exactly what you reviewed, pin a version. Every release is published from GitHub Actions with npm provenance, so the tarball can be traced to a commit:
bunx @lilbunnyrabbit/[email protected]
npm view @lilbunnyrabbit/[email protected] dist.attestationsGitHub Actions in the workflows are pinned to commit SHAs.
Development
git clone https://github.com/lilBunnyRabbit/agtc
cd agtc
bun install
bun start # or: bun src/main.ts
bun src/main.ts tmux # the hub, running this checkout
bun run check # typecheck
bun test
bun run check:offline
bun link # makes `agtc` on your PATH point at this checkout
ln -s "$PWD/bin/agtc" ~/.local/bin/agtc-dev # or keep the release and run the checkout as agtc-devLayout:
src/main.ts entry: dispatches on the parsed mode
src/cli.ts flag parsing and usage text
src/paths.ts every file agtc reads or writes under ~
src/model/ Session type and status order, tool commands (resume, read-only flags), collect from every
source + git changes + "done" + sort, `/` search, the state file (~/.cache/agtc/state.json),
the agent running in a directory (for attach and send)
src/sources/ read-only: claude/ (registry, history, transcript, subagents), codex/ (sqlite, lsof, rollout),
git, processes, Terminal.app tabs, tmux panes
src/tmux/ acting on tmux: own pane detection, stage (enter), windows/panes/popups, key setup, the hub
session, restoring the last hub
src/review/ the V loop: spec request, reviewer prompt and command, reading a report and its verdict
src/worktrees/ `agtc worktrees`: read state per worktree, state rules, table, picker, remove
src/commands/ one file per subcommand: attach, send, update, graph loop, worktrees
src/desktop/ the editor (`o`) and macOS notifications
src/tui/ ansi, theme, row layout, frame rendering, key parsing, the shared raw-mode screen, help text,
graph rendering; tui/app/ is the interactive loop with its actions split by concern
(stage, agents, review, input) over one AppContext
src/lib/ shell, files, text, time, ttl-cache, map-limit
test/ bun test: pure parts (parsing, layout, state rules, rendering invariants)Releasing
Publishing is automated from tags via npm trusted publishing (OIDC), no tokens stored anywhere:
npm version patch # or minor / major — bumps package.json, commits, tags vX.Y.Z
git push --follow-tagsGitHub Actions runs a smoke test, publishes to npm with provenance, and creates a GitHub release.
One-time setup: the first version is published by hand (npm publish --access public --provenance=false), then on npmjs.com under the package's Settings → Trusted Publisher, register GitHub Actions with owner lilBunnyRabbit, repository agtc, workflow release.yml.
License
MIT
