You've got Grok Build crunching through a refactor in one pane, Claude Code negotiating a design doc in another, and Hermes Agent off doing… whatever Hermes Agent does. Wouldn't it be nice to know who's actually working, who's stuck waiting for your approval, and who's just daydreaming?
tmon sits quietly in your tmux status bar and tells you exactly that.
It sniffs out running AI coding agents from /proc, tracks their activity
via CPU ticks and IO bytes, and renders it all as a dead-simple count
indicator with animated status characters. Need details? Hit prefix a a
for an interactive dashboard that shows every agent and lets you jump
straight to their pane. Or just click the status bar indicator — that
works too.
[@] ? 2 - ● 3 - ‖ 1
↑ ↑ ↑ ↑
icon blocked active idle
- ● cyan
[@]prefix — your personal fleet of AI agents - ? orange — agent is blocked, waiting for you (permission prompt, plan approval, y/n question). Toggles to ! every other poll as a visual nudge.
- ● green — agent is cooking (CPU or IO activity detected, or just booted up). Toggles to ! every other poll as a visual pulse.
- ‖ blue — agent is idle (no meaningful activity for several polls)
- [@] ? 0 - ● 0 - ‖ 0 — no agents detected (peace and quiet)
Every segment always renders at a fixed width, so your status bar won't dance around when counts change.
Note: The green ● segment includes both "active" agents (actively using CPU/IO above threshold) and "running" agents (just detected, haven't yet shown activity). Both are doing real work. The distinction matters mainly for the dashboard where you can see the exact status label.
An 80%×80% popup that lists every running agent with its emoji icon, status, and exact tmux location — all on one compact line per agent:
┌──────────────────────────────────────────────────┐
│ [@] TMON [ESC] to quit │
│ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
│ │
│ ● Grok Build: $1:main / 0:code / 0 │
│ ● Claude Code: $1:main / 1:chat / 0 │
│ ‖ Hermes Agent: $0:main / 2:tmon / 3 │
│ │
│ ▌ type to filter ↑↓ nav enter/→… │
└──────────────────────────────────────────────────┘
Each agent gets one compact line with an emoji icon, full display name,
and its tmux path ([session_id]:session / [window_index]:window / [pane_index]).
The selected row is highlighted with a dim background.
Status is always accurate — the dashboard reads from the same state file that drives the status bar, so the two never disagree. "Blocked" detection runs live against each pane's visible content on every refresh.
Hit Enter or → on any agent and you're teleported directly to its
pane (session, window, and pane all switch at once).
Type to filter — The popup is always in filter mode. Just start typing
to narrow the list by agent name, session, or window name. The list updates
in real time, and a match count appears in the footer. Press Esc to clear
the filter. Press Esc again (with no filter) to close the popup.
Auto-refresh: The dashboard refreshes every 1.5 seconds, with a full data reload every ~6 seconds.
| Key | Action |
|---|---|
↑ ↓ |
Navigate the list |
Enter / → |
Jump to the selected agent's pane |
| Type | Filter the list (agent name, session, window) |
Backspace |
Remove last character from filter |
Esc |
Clear filter; if already clear, close popup |
tmon recognizes 11 agents out of the box:
| Agent | What it watches for |
|---|---|
| Grok Build | grok, grok-build, grok agent |
| Claude Code | claude, claude-code, node @anthropic/claude |
| Codex CLI | codex, codex-cli |
| Cursor | cursor agent, cursor-agent |
| Cline | cline, cline agent |
| Aider | aider, python aider |
| GitHub Copilot | copilot agent |
| CodeBuddy | codebuddy, codebuddy agent |
| Windsurf | windsurf, windsurf agent |
| Hermes Agent | hermes agent, hermes run |
| OpenClaw | openclaw agent, openclaw chat |
Detection works purely by scanning /proc/[pid]/cmdline — no agent APIs,
no daemons, no external dependencies beyond bash and tmux itself.
# ~/.tmux.conf
set -g @plugin 'guillaumemeyer/tmon'Then prefix I to install.
Note: If you're setting up TPM for the first time, make sure the TPM initializer runs at the very bottom of your
~/.tmux.conf:# Initialize TMUX plugin manager (keep this line at the very bottom of tmux.conf) run '~/.tmux/plugins/tpm/tpm'
git clone https://github.com/guillaumemeyer/tmon ~/.tmux/plugins/tmon# ~/.tmux.conf
run-shell ~/.tmux/plugins/tmon/tmon.tmuxReload: tmux source-file ~/.tmux.conf
When new versions are pushed to GitHub, updating depends on how you installed.
Hit prefix U (uppercase U) to pull the latest from all your plugins,
then reload:
tmux source-file ~/.tmux.confThe updated scripts take effect on the next poll cycle — no restart needed.
cd ~/.tmux/plugins/tmon
git pull origin master
tmux source-file ~/.tmux.confNote: If
tmon.tmuxitself changed (new keybindings, renamed scripts, new config options), a full tmux restart is the safest bet. Otherwisesource-fileis enough.
Set any of these in ~/.tmux.conf before the plugin line. All of them
are optional — the defaults are sensible for most people.
How often tmon scans for agents and samples their activity.
| Default | 3000 |
| Unit | milliseconds |
| Range | 1000–60000 (1s to 60s) |
set -g @tmon-poll-interval "5000" # every 5 secondsLower values give snappier status updates but burn slightly more CPU.
The default of 3000ms (3 seconds) hits a sweet spot — agents don't change
state faster than that anyway, and /proc scans are cheap.
The CPU floor for calling an agent "active." Agents doing remote API work (thinking, streaming) often use very little CPU — tmon treats any activity above zero as "active" on first detection. This threshold kicks in on subsequent polls to distinguish genuine work from background noise.
| Default | 500 |
| Unit | CPU milliseconds per second |
| Range | 100–5000 |
set -g @tmon-activity-threshold "200" # more sensitiveThe minimum IO throughput for calling an agent "active." Agents streaming responses or writing files generate IO — this threshold distinguishes real work from background filesystem noise (logs, terminal echo).
| Default | 102400 |
| Unit | bytes per poll interval |
| Range | 1024–10485760 |
set -g @tmon-io-threshold "51200" # more sensitive to IOWhich side of your status bar gets the tmon indicator.
| Default | right |
| Options | right or left |
set -g @tmon-status-position "left"The chord leader key for opening the agent navigation popup. Opens with
prefix <key> <key>.
| Default | a |
| Example binding | prefix a a → navigation |
set -g @tmon-dashboard-key "b" # use prefix b b insteadThese are set by tmon.tmux from the tmux options above but can also be
overridden directly for custom setups:
| Variable | From option | Description |
|---|---|---|
TMON_POLL_INTERVAL_MS |
@tmon-poll-interval |
Poll interval in ms |
TMON_ACTIVITY_THRESHOLD_MS |
@tmon-activity-threshold |
CPU threshold in ms/s |
TMON_IO_ACTIVITY_THRESHOLD |
@tmon-io-threshold |
IO threshold in bytes |
TMON_IDLE_DECAY_POLLS |
(no tmux option) | Consecutive idle polls before "idle" (default: 3) |
TMON_STATE_DIR |
(always ~/.cache/tmon) |
Directory for state files |
Every poll, tmon walks /proc/[0-9]*/cmdline and greps against a combined
regex of all 11 agent signatures. It's fast — even with hundreds of processes,
the scan completes in single-digit milliseconds.
tmon reads two counters from /proc:
- CPU ticks from
/proc/[pid]/stat(fields 14+15+16+17: user + system + child user + child system) - IO bytes from
/proc/[pid]/io(rchar + wchar)
Agents transition through a 4-state machine:
- running — First sight of an agent; shown immediately regardless of activity (agents often think remotely with near-zero local CPU).
- active — CPU delta ≥
@tmon-activity-thresholdor IO delta ≥@tmon-io-thresholdsince the last poll. This filters out scheduler noise and terminal cursor updates. - blocked — Overrides everything. If the pane content matches any blocked-state pattern (permission prompts, y/n questions, approval wait), the agent is stuck waiting for you.
- idle — No meaningful CPU or IO activity for 3 consecutive polls (9 seconds at default 3s interval). The grace period prevents flickering for agents between API calls.
If an agent is running inside a tmux pane, tmon uses tmux capture-pane to
grab the visible terminal content and looks for telltale signs of a stuck agent:
- Selectors:
❯ 1.,[y/N],[Y/n] - Permission prompts: "Do you want to proceed?", "Press any key"
- Plan approval:
[approve],[confirm], "plan approval required" - Chat questions: "Should I", "Waiting for your input"
Blocked detection overrides everything — if tmon thinks an agent is waiting for you, it won't call it "active" no matter how much CPU it's burning.
Matching an agent PID to a specific tmux pane uses three strategies, in order:
- Direct PID match — Check if the PID itself is a tmux
pane_pid(O(1) via pre-built associative array). - TTY matching — Read
/proc/[pid]/statfield 7 (tty_nr), convert the major/minor to/dev/pts/N, then match againsttmux list-panes -a -F(also O(1) via associative array). - Process tree fallback — If both above fail (e.g., the agent is a child
process), walk up the parent chain looking for a PID that matches a tmux
pane_pid.
All pane maps are built once per poll using pure bash associative arrays,
so lookups are O(1) with no grep or cut forks.
This means tmon works even when the agent binary is launched deep inside a shell pipeline or subshell.
tmon.tmux ← Plugin entrypoint (sourced by tmux)
├── scripts/monitor.sh ← Process scanner + activity evaluator + blocked detection
│ ├── --once ← Called by tmux #() interpolation for status bar
│ └── --notify ← Daemon mode with tmux display-message notifications
├── scripts/pane-map.sh ← Standalone PID-to-pane resolver
├── scripts/dashboard.sh ← Interactive navigation popup (self-contained)
└── scripts/notify.sh ← Notification dispatcher (stdin or arg)
No npm, no pip, no cargo. Just bash and /proc.
| Binding | Action |
|---|---|
prefix a a |
Open the agent navigation popup |
| Click status bar indicator | Open the agent navigation popup |
Tip: The status bar indicator is a clickable
#[range=user|tmon]range. Clicking anywhere on it opens the dashboard without needing a keyboard shortcut.
tmon can send tmux display-message popups on state transitions (agent
started, agent became active). Run the monitor in notify mode:
bash ~/.tmux/plugins/tmon/scripts/monitor.sh --notifyThis runs continuously in the background, emitting transient popups like "Grok Build started in code/tmon" or "Claude Code is now active in src/api" whenever an agent changes state. Notifications are opt-in and off by default to avoid noise.
Edit scripts/monitor.sh and scripts/dashboard.sh. Add an entry to the
AGENT_SIGNATURES array in both files:
"YourAgent:^your-tool( |$)"
"YourAgent:your-tool( |-)(agent|chat|run)"Then add its full display name in dashboard.sh's agent_full_name() function,
and optionally an emoji icon in the agent_icon() function. The regex is
matched against the process command line (null-separated args joined with
spaces, no trailing newline).
Status bar is empty — tmon only renders when agents are detected. Fire up Grok Build or Claude Code and it should appear. Still nothing? Run manually:
bash ~/.tmux/plugins/tmon/scripts/monitor.sh --onceNavigation won't open — Check your keybinding: tmux list-keys -T a-table.
If a conflicts with another plugin, change @tmon-dashboard-key.
Agent shows as "?" instead of a pane path — The PID-to-pane mapping couldn't resolve. This happens with headless agents or processes outside tmux. Normal and harmless — the status bar still tracks them.
High CPU from tmon — Increase @tmon-poll-interval. The default 3000ms
is already conservative; bumping to 10000ms (10s) makes the scan cost
essentially invisible.
Stale agent counts after crash — tmon persists agent state to
~/.cache/tmon/agents.state. If counts seem wrong after an abnormal tmux
exit, delete this file and let it rebuild:
rm ~/.cache/tmon/agents.stateMIT — because the robots haven't taken over yet.