Analysis
A tmux Dashboard for Multiple Coding Agents
A dashboard for multiple coding agents should answer three questions: which task needs attention, what is happening in its live pane, and how to enter that session. Extra permanent views become noise once tmux already owns navigation.

The current source and Linux releases are public in ttpears/tad. I maintain the project. It is a Rust terminal interface for tmux, released under the MIT license.
In this article
The session list stopped answering the question
tad began as the kind of shell function that grows in a dotfiles repository: attach to a named tmux session if it exists, create it if it does not. That works while the question is simply “which session do I want?”
It stops working once several sessions contain coding agents, background shells, remote hosts, and worktrees. A name such as api-fix says where the work started. It does not say whether the agent is still producing output, waiting at a prompt, or sitting untouched because I forgot about it.
The first full dashboard tried to solve that by making every kind of object a destination. Sessions, projects, groups, hosts, and agents each had a view. That was orderly in the code and tiring in use. The information I checked constantly was separated by the same navigation as reference data I touched occasionally.
In the v0.13 redesign, I deleted the Projects abstraction and removed more than a thousand lines net. A project had become another name layered over tmux state, not a distinct thing I needed to manage. The same change added a confirmation before killing a session or agent. In the reviewed version, d opens a y/N prompt, but y, Y, and Enter accept it. The label is misleading if you expect Enter to choose No. Use Escape or n to cancel; a second d also cancels. Check the selected target before confirming. The handler and its tests establish that behavior.
That commit was less about shrinking a feature list than admitting what the dashboard was for. It was not a database of everything I might connect to. It was a way to find live work and act on it quickly.
A label is not live state
The next problem was the preview. Metadata can tell me a working directory, process, window name, and last activity time. It still cannot tell me that an agent has stopped on a permission question, a failed test, or the last line of a stack trace.
The v0.14 preview overhaul changed the pane beside the list from a description into a capture of the actual tmux pane. The current implementation runs tmux capture-pane -p against the selected pane’s unique target, trims trailing blank rows, removes control characters, expands tabs, and shows a bounded tail. If the pane disappears between discovery and rendering, the preview falls back to a metadata card instead of turning into an empty box.
Using the pane itself matters when two agents share a working directory. Choosing the newest transcript by directory can make both rows show the same conversation. A tmux target such as session:window.pane is unique, so the preview stays attached to the row I selected.
The distinction is small on a feature checklist and obvious in use. I do not need a dashboard to repeat the label already visible in the sidebar. I need it to show the piece of information that determines my next action.
“Active” was still too vague
An updating pane is useful evidence, but modification time alone cannot answer whether an agent needs me. Recent transcript activity might mean the agent is working, or it might mean the agent just finished its answer and is waiting at the prompt.
The current agent scanner starts with every tmux pane, walks the descendant process tree under its shell through Linux /proc, and asks a provider implementation whether a process is a supported coding agent. For Claude Code, it then finds the most recent session transcript for that working directory and inspects a bounded 256 KiB tail.
That leaves an identity limitation: two agents using the same directory can receive status from the same newest transcript. The pane-specific preview fixes which terminal output I see; it does not fix how the status classifier selects its transcript. I check the live pane before acting on a blocked or working badge. The provider’s directory-based lookup is the relevant code.
The classifier works backward from the newest useful event. An assistant end_turn with no later user event means blocked: the next move belongs to me. A tool call, tool result, or new user message means working. An away_summary can downgrade a finished turn to away so an abandoned session does not keep shouting for attention. Unknown transcript shapes do not crash the dashboard; they fall back to recent file activity and eventually idle.
● blocked — needs my input
◐◓◑◒ working — actively producing output
○ idle — quiet for a while
◌ away — snoozed or no longer active
The scanner sorts recent work first, groups agents under their tmux sessions, and gives blocked work priority in the section count. The symbols are compact because they sit beside the thing I need to open; they are not pretending a heuristic is a detailed progress report.
I wanted to work beside the map
Opening a row used to solve one problem by recreating another. It took me to the pane, but the dashboard disappeared. Going back and forth was still navigation overhead.
In the first pane-pinning change, the o key began pulling the selected pane into a split beside tad. The current cockpit expands that to four pinned panes in a small grid. This is not a copied terminal view. It is the real pane, moved with tmux, so I can focus it and work while the sidebar continues to update.
Moving a real pane creates a recovery obligation. tad records the pane ID, original window ID, session, window name, and index before the move. Unpinning joins it back to the original window when that window still exists. If the window vanished, it breaks the pane into the original session, restores the name, and tries to restore the index. If the session vanished too, the pane becomes its own window instead of remaining stranded beside the dashboard.
Quitting the dashboard returns every pinned pane first and restores the window’s previous border setting and width. Refresh also drops a pin whose pane was killed elsewhere. The feature is useful because it is reversible, including when the surrounding tmux state changes while the pane is away.
The cockpit still had too much permanent furniture
The v0.16 sidebar cockpit put Sessions and Agents into one scrollable column with a preview beside it. It also added mouse support, collapsible sections, the four-pane grid, remembered sidebar state, and the semantic agent statuses.
Groups and Hosts were still permanent sections, though. I use them to open a known collection or connect to another machine. I do not need to stare at them while watching active work.
Version 0.17 moved Groups and Hosts into on-demand pickers. Pressing g or h opens a filterable overlay; choosing an item opens the group or connects to the host. The data remains available without taking vertical space from the two sections that change constantly.
Host discovery also stays local. tad reads concrete entries from SSH config, non-hashed known_hosts entries, and repeated SSH commands from shell history. It does not probe the network or perform DNS discovery. History-only names must appear more than once by default, which keeps a typo from becoming permanent interface clutter.
This is the design rule I kept rediscovering: frequency should determine prominence. A capability does not earn permanent screen space merely because it exists.
What I checked after removing the screens
This review is pinned to the 0.17.0 source at commit 7ff281a, the redesign commits above, and the release workflow. The CI run for that exact revision is recorded as successful. That is evidence for the reviewed revision, not a claim about whatever is currently on the main branch.
The CI job uses Rust 1.94.1 and runs formatting checks, a locked release build with warnings treated as errors, Clippy across all targets with warnings treated as errors, and the locked test suite. The release publishes a static Linux x86_64 binary, Debian and RPM packages, and shell completions.
I did not rerun the Rust suite or operate a live tmux session for this update. The linked CI run and reviewed source are the evidence here; they do not prove how an untested agent transcript format or a particular live workspace will behave.
What the dashboard is for now
tad still manages tmux sessions, opens host groups, provides shell completions, and can install a popup key binding. Those features are useful. They are not the reason the dashboard finally feels coherent.
The cockpit answers three questions in one place: what work exists, which agent needs me, and what is actually happening in that pane. Everything else can wait behind a key until I need it.
The biggest improvement was not adding the live preview or the pin grid by itself. It was removing the screens that made those signals harder to see.
Disclosure: I maintain tad.