Keep track of every coding agent you have running, from one floating panel.
Run enough Claude Code and Codex agents and they scatter across panes, tabs, and whole Zellij sessions. This panel shows all of them at once: who is working, who is blocked waiting on you, and who finished while you were elsewhere. Press Enter to jump straight to any of them, even in another session.
A blocked agent is invisible until you happen to cycle past its pane. Rows sort by urgency, so whatever needs you most sits at the top.
- Every agent at once, across sessions, with live status and the task each is on.
- Jump to any pane with Enter, across tabs and sessions.
- Fuzzy find with /: a few characters of a task, worktree, or session narrows the list to it.
- Pin the ones you are watching with p: they sit in a block at the top with stable row numbers, in every session's panel.
- Told when you are away: a desktop notification the moment an agent blocks or fails.
- Answer in place: a / r for permission prompts, A to always allow a tool, y / m to reply.
- Kill a runaway with x, two-step so you never do it by accident.
- No daemon and no socket. Hooks pipe straight to the plugin and drop a disposable status file for other sessions to read.
Inspired by herdr, without adopting an entire new multiplexer.
Docs: setup · how it works · development · troubleshooting
| Requirement | Why |
|---|---|
| Zellij 0.44+ | Plugin API (LaunchOrFocusPlugin, pipes, RunCommandResult) |
| Nerd Font | The status icons. Set icons "unicode" for the old glyph set |
Rust + wasm32-wasip1 target |
Only to build from source; not needed if you download a release |
Three steps: install, bind a key, restart your agents.
No clone and no Rust toolchain required:
curl -fsSL https://github.com/mohseenrm/zj-agent-mob/releases/download/v1.0.0/init.sh | shThis downloads the plugin and hook script for that release, wires up whichever of Claude Code and Codex you have, and leaves an installer at ~/.config/zj-agent-mob/install.sh so the in-panel install screen works from then on.
Prefer to read before running? Same thing in two steps:
curl -fsSL -O https://github.com/mohseenrm/zj-agent-mob/releases/download/v1.0.0/init.sh
less init.sh && sh init.shOr build it from source
rustup target add wasm32-wasip1
cargo build --release --target wasm32-wasip1
./init.shFrom a clone, ./init.sh uses your local build and downloads nothing.
Reinstalling over an existing install, or syncing a checkout across machines? Use
./scripts/reinstall-local.sh, which also clears Zellij's plugin cache and any stale
status records - both of which survive a plain init.sh and keep old behaviour live.
--check reports whether what is installed matches your checkout.
Add to ~/.config/zellij/config.kdl:
keybinds {
session {
bind "c" {
LaunchOrFocusPlugin "file:~/.config/zellij/plugins/zj-agent-mob.wasm" {
floating true
move_to_focused_tab true
}
SwitchToMode "Normal"
}
}
}Press Ctrl+s then c to open the panel.
Important
Restart any running claude / codex sessions after installing. Hooks are read at session start, so existing sessions won't report status.
Then start an agent in any pane and open the panel: it shows up within a turn. If the panel stays empty, troubleshooting walks through it in order.
See docs/setup.md for per-target install, the in-panel install screen, layout registration, and every config knob.
|
The agent list. One row per agent: status, elapsed time, where it runs
( |
First run. With no hooks installed nothing can report, so the panel offers to install rather than sitting empty. |
|
Killing an agent. x interrupts and arms the row; again closes the pane. |
The install screen (i). Each target toggles independently, so running only one agent's hooks is supported. |
| Key | Action |
|---|---|
| j / k, ↓ / ↑ | Move selection |
| Enter | Jump to that agent's pane (across tabs and sessions) and hide the panel |
| 1–9 | Jump straight to agent N |
| g N Enter | Jump to any row by number, including past 9. g opens a count, Enter or G closes it: g25Enter, or g25G for the vim spelling |
| gg / G | First row / last row |
| / | Fuzzy find: type to narrow the list (task, worktree, path, session, tool, status), Ctrl+j/k or ↓/↑ to pick a match, Enter jumps to it, Esc cancels. Smartcase, like vim |
| p | Pin the selected row to the top; press again to unpin. Pinned rows form a block above everything else, keep stable numbers for 1-9 and g, and are shared by every panel |
| s | Cycle the ordering: urgency (default) -> grouped by project (the git repo, so worktrees group together) -> grouped by session |
| x | Send SIGINT to the agent; press again to close the pane (any session) |
| a / r | Approve / reject a parked permission prompt |
| A | Approve, and stop that tool asking again |
| f | Queue a follow-up, delivered when the turn ends |
| y | Answer a blocked agent with y (only shown while it is waiting) |
| m | Type a one-line reply to a blocked agent; Enter sends, Esc cancels |
| o | Expand the selected row's subagents into one line each; press again to collapse |
| d | Dismiss a done badge (works, but not in the footer - its slot went to U) |
| D | Dismiss every done badge at once |
| n | Open a new agent in a floating pane, in the selected row's directory (advertised in the empty state rather than the footer) |
| i | Open the install screen |
| U | Update the plugin to the latest release. See Updating |
| q / Esc | Hide the panel (works, but not in the footer - its slot went to p) |
| Key | Action |
|---|---|
| c / x / p | Toggle Claude hooks / Codex hooks / the plugin wasm |
| j / k, ↓ / ↑ | Move selection |
| Enter | Toggle the selected row |
| r | Re-read install state |
| U | Update the plugin to the latest release |
| i / q / Esc | Back to the agent list |
U updates the plugin. One press: it asks GitHub for the latest
release, downloads it, installs it, and reloads the panel in place. Nothing to
clone, no init.sh to re-run, no restart.
It is in the footer at all times, because it works at all times - there is no state where pressing it does nothing. The version you are running is shown in the top right of the panel.
The panel also checks quietly in the background - once per open, answered from a six-hour cache so it is not hammering the network - and adds a line when there is something newer:
update available: v0.14.0 (press U)
That line is a nudge, not a prerequisite. With a release already known U goes straight to installing; with nothing known it does the check itself, ignoring the cache, and installs whatever it finds. If you are already current it says so rather than sitting silent.
Turn the background check off and the key still works:
zj-agent-mob location="file:~/.config/zellij/plugins/zj-agent-mob.wasm" {
check_updates "false"
}| Status | Meaning | Hook event |
|---|---|---|
failed |
Stopped by an error (rate limit, billing, auth) | StopFailure (Claude only) |
waiting |
Needs you now (permission prompt / question) | Notification (Claude), PermissionRequest (both) |
idle-wait |
Has been waiting on you for a while | Notification / idle_prompt (Claude only) |
done |
Finished while you were elsewhere | Stop |
compact |
Compacting context (looks like a hang otherwise) | PreCompact, cleared by PostCompact |
working |
Processing a turn | UserPromptSubmit, refreshed by PreToolUse/PostToolUse |
idle |
Session open, nothing new | SessionStart, or done after you visit the pane |
found |
Spotted by the process scan, but it has never fired a hook | - |
gone |
Its Zellij session has exited; the detail line keeps what it was last doing | - |
Rows sort in that order, so whatever needs you most is at the top. A found row is normal rather than broken: the agent was already running when hooks were installed, and it fills in the moment it next does anything.
A row from another session that nothing has refreshed in a minute keeps its status but is dimmed, with last seen 2m ago on its detail line: the panel shows the last thing it knew rather than replacing it with a label that says nothing (more).
s cycles the ordering: urgency (default), grouped by project, then grouped by session. Each group is ranked by its most urgent member, so grouping never buries a blocked agent under a quiet project. The active mode shows in the header; the default needs no announcing.
zj-agent-mob 1 waiting · 2 working [project groups · s]
────────────────────────────────────────────────────────────
api (2)
▶ 1 ● claude waiting 12s api Fix the failing auth test
└ wants: permission · needs approval: git push --force · pane:6
2 ⠙ claude working 41s api Port the hook suite to Rust
web (1)
3 ⠙ codex working 2m10s web Update the checkout flow
A waiting row says what kind of answer it wants, so you can triage without
visiting a pane. Only a permission is a yes/no the panel can answer:
wants: |
You can |
|---|---|
permission |
Answer with a / r, or A to always allow that tool |
plan |
Read it - Enter to the pane |
question |
Reply with m, or jump to the pane |
idle |
Nothing is blocked on a decision |
On by default; ZJ_AGENT_APPROVE=0 in the agent's environment opts out. A permission prompt
parks in the panel and a / r approve or reject it without leaving the panel:
▶ 1 ● codex waiting 2s web Fix flaky checkout test
└ needs approval: rm -rf node_modules · pane:5
┌──────────────────────────────────────────┐
│ Bash │
│ rm -rf node_modules │
│ a approve r reject A always ↵ pane │
└──────────────────────────────────────────┘
It waits ZJ_AGENT_APPROVE_TIMEOUT seconds (default 30) and then falls through to the agent's own
prompt, so the worst case is the normal interactive experience. Reject is r, not
d, so a mis-keyed dismiss can never answer a prompt.
A approves and stops that tool asking again, by appending a line to
~/.config/zj-agent-mob/approve.rules:
allow Read
allow Bash git
A matching rule is answered immediately, with no prompt and no wait, so a fleet only interrupts you for something new. Allow-only: a wrong auto-deny wedges a turn, where a wrong auto-allow is still bounded by whatever the agent's own sandbox permits.
f composes a follow-up for the selected agent. It is delivered when the current turn ends, so the agent picks it up and keeps going instead of stopping:
▶ 1 ⠙ claude working 1m4s api Add retry to webhook client
└ follow-up: now run the tests · 3 turns · pane:5
Unlike a reply this needs no prompt to be open - a working agent is exactly what it is for - and
it reaches agents in other sessions. ZJ_AGENT_FOLLOWUP=0 opts out.
On by default. When an agent starts waiting or failed, you get a system notification even
when Zellij is not the focused window - which is exactly when a blocked agent is invisible.
A burst becomes one banner rather than five, each agent is rate-limited to one notification a minute, and nothing fires while the panel is already on screen. To change what it notifies about:
LaunchOrFocusPlugin "file:~/.config/zellij/plugins/zj-agent-mob.wasm" {
floating true
notify "waiting,failed,done" // add done; "" turns notifications off
notify_sound "true"
}Set summary_file and the panel publishes the fleet's state on every change, so
you know whether anyone needs you without opening anything:
summary_file "/tmp/zj-agent-mob.summary"Two files, both written atomically: the prose line (2 waiting · 3 working,
empty when nothing needs you) and <path>.kv for consumers that would rather
not parse prose:
failed=0 waiting=2 working=1 done=0 found=0 total=3
The prose line is also piped as zj-agent-mob-summary, so
zjstatus can pick it up with its pipe
widget. Both formats are a stated contract - see
the fleet summary for
worked starship, tmux and shell examples.
- Agents started before
init.shran aren't tracked (no hooks installed yet). Restart them. - Agents in other sessions report through a status file in
$TMPDIR. The states that need you (waiting,failed,done) pipe across immediately; quieter ones wait for the next scan (5s). - Claude has no "permission granted" event, so
waitingtoworkingrelies on the next tool-event heartbeat. WithZJ_AGENT_HEARTBEAT=0,waitingpersists until the turn ends. - Notifications need a notifier on
PATH(terminal-notifier/osascripton macOS,notify-sendon Linux). Without one, everything else still works.
Hitting something not listed here? See docs/troubleshooting.md.
Prebuilt zj-agent-mob.wasm binaries and changelogs are on the releases page. Pushing a v* tag builds the wasm, verifies it exports what Zellij needs, and publishes it automatically.




