Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 

Repository files navigation

tmon — Your AI agents, now with a leash

tmon agents

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.


What you'll see

Status bar

[@] ? 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.

Navigation (prefix a a)

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

Who's on the guest list

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.


Installation

With TPM (recommended)

# ~/.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'

Manual

git clone https://github.com/guillaumemeyer/tmon ~/.tmux/plugins/tmon
# ~/.tmux.conf
run-shell ~/.tmux/plugins/tmon/tmon.tmux

Reload: tmux source-file ~/.tmux.conf


Updates

When new versions are pushed to GitHub, updating depends on how you installed.

TPM

Hit prefix U (uppercase U) to pull the latest from all your plugins, then reload:

tmux source-file ~/.tmux.conf

The updated scripts take effect on the next poll cycle — no restart needed.

Manual (git clone)

cd ~/.tmux/plugins/tmon
git pull origin master
tmux source-file ~/.tmux.conf

Note: If tmon.tmux itself changed (new keybindings, renamed scripts, new config options), a full tmux restart is the safest bet. Otherwise source-file is enough.


Configuration

Set any of these in ~/.tmux.conf before the plugin line. All of them are optional — the defaults are sensible for most people.

@tmon-poll-interval

How often tmon scans for agents and samples their activity.

Default 3000
Unit milliseconds
Range 100060000 (1s to 60s)
set -g @tmon-poll-interval "5000"   # every 5 seconds

Lower 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.

@tmon-activity-threshold

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 1005000
set -g @tmon-activity-threshold "200"   # more sensitive

@tmon-io-threshold

The 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 102410485760
set -g @tmon-io-threshold "51200"  # more sensitive to IO

@tmon-status-position

Which side of your status bar gets the tmon indicator.

Default right
Options right or left
set -g @tmon-status-position "left"

@tmon-dashboard-key

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 instead

Advanced: environment variables

These 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

How it works (the juicy details)

Agent detection

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.

Activity tracking

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:

  1. running — First sight of an agent; shown immediately regardless of activity (agents often think remotely with near-zero local CPU).
  2. active — CPU delta ≥ @tmon-activity-threshold or IO delta ≥ @tmon-io-threshold since the last poll. This filters out scheduler noise and terminal cursor updates.
  3. 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.
  4. 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.

Blocked state detection

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.

Pane mapping

Matching an agent PID to a specific tmux pane uses three strategies, in order:

  1. Direct PID match — Check if the PID itself is a tmux pane_pid (O(1) via pre-built associative array).
  2. TTY matching — Read /proc/[pid]/stat field 7 (tty_nr), convert the major/minor to /dev/pts/N, then match against tmux list-panes -a -F (also O(1) via associative array).
  3. 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.

Architecture

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.


Keybindings

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.


Notifications

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 --notify

This 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.

Adding your own agent

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).


Troubleshooting

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 --once

Navigation 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.state

License

MIT — because the robots haven't taken over yet.

About

tmux plugin that detects running AI coding agents and shows their activity in your status bar

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages