Skip to content

About

Agent monitoring like herdr, without the multiplexer

Resources

Stars

10 stars

Watchers

0 watching

Forks

Latest commit

 

History

67 Commits

Folders and files

Repository files navigation

Zellij Agent Mob (zj-agent-mob)

CI Release License: Apache-2.0 Zellij 0.44+

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.

Seven agents across three Zellij sessions in one list; a permission prompt is approved from the panel and another is always-allowed, the list is narrowed by typing, a question is answered and a follow-up queued, statuses move through compact, done and failed, then the kill confirm and the install screen; an agent two sessions away blocks and sorts to the top, and Enter jumps straight into its pane

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

Requirements

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

Quick start

Three steps: install, bind a key, restart your agents.

1. Install

No clone and no Rust toolchain required:

curl -fsSL https://github.com/mohseenrm/zj-agent-mob/releases/download/v1.0.0/init.sh | sh

This 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.sh
Or build it from source
rustup target add wasm32-wasip1
cargo build --release --target wasm32-wasip1
./init.sh

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

2. Bind a key

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.

3. Restart your agents

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.

Screens

The agent list. One row per agent: status, elapsed time, where it runs (repo/worktree once the hooks report git identity), task, and an indented detail line. A renamed pane wins as the task label, and a ⑂N badge counts running subagents.

The agent list: one row per agent with status, elapsed time, project, and task

First run. With no hooks installed nothing can report, so the panel offers to install rather than sitting empty.

The setup screen listing four quick actions: install for Claude Code, for Codex, for both, or quit

Killing an agent. x interrupts and arms the row; again closes the pane.

The agent list with the selected row showing "press x again to close pane" in red

The install screen (i). Each target toggles independently, so running only one agent's hooks is supported.

The install screen showing Claude Code hooks, Codex hooks, and Plugin wasm all installed

Keys

Agent list

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)

Install screen

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

Updating

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"
}

Statuses

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

Ordering and grouping

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

Why an agent is blocked

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

Answering permission prompts from the panel

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.

Queueing the next instruction

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.

Desktop notifications

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"
}

Fleet status in your status bar

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.

Known limitations

  • Agents started before init.sh ran 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 waiting to working relies on the next tool-event heartbeat. With ZJ_AGENT_HEARTBEAT=0, waiting persists until the turn ends.
  • Notifications need a notifier on PATH (terminal-notifier / osascript on macOS, notify-send on Linux). Without one, everything else still works.

Hitting something not listed here? See docs/troubleshooting.md.

Releases

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.

License

Apache-2.0

About

Agent monitoring like herdr, without the multiplexer

Resources

Stars

10 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages