Turning this repo into a TUI for AI-assisted development: a tmux-native dashboard to organize projects, monitor and manage multiple AI coding agents (Claude Code, OpenCode, Codex), review file changes, manage compute resources, and auto-close sessions.
| Topic | Decision |
|---|---|
| Stack | Rust + Ratatui (+ crossterm + tokio + axum/SSE) |
| Base tool | Adopt/adapt showrunner (Bendzae/showrunner) — Rust/Ratatui, tmux-backed, Projects/Tasks/Sessions with worktrees, diff review, send/ask CLI, mobile web UI |
| Bash role | Becomes a lightweight bootstrap plugin: TPM entry (sessionizer.tmux) binds Alt+s (open TUI), Alt+a (daemon on/off), Alt+n (new session), auto-starts the daemon. All heavy logic moves to Rust. |
| Event transport | Daemon + HTTP/SSE on localhost (ccmux-style). Daemon owns state; TUI and web UI are SSE clients. |
| Milestone 1 | Projects + Agents + fast switch |
| Diff viewer | Keep hunk (showrunner's default) |
| Resource scope | All tmux sessions + GPU (nvidia-smi) |
| Auto-close policy | Idle + agent-finished, with safeguards; both configurable and disable-able |
| Rendering | Main dashboard in Ratatui; diff and resource panel open via tmux display-popup (external viewers) |
tmux (sessions hold agent panes + run sessions)
▲ hooks (Claude/OpenCode/Codex) + process scan + tmux -C
│ write state events
DAEMON showrunnerd (Rust/tokio)
• discover agents; correlate pane ↔ session ↔ project
• collect CPU/mem/GPU per session
• auto-close policy
• HTTP + SSE on localhost
▲ SSE
TUI showrunner (Ratatui) — and web UI
views: Projects · Agents · Diff · Resources · Notifications
▲
bash bootstrap (sessionizer.tmux): Alt+s / Alt+a / Alt+n
- Project — a git repo added by path.
- Task — a unit of work tied to a git branch. Main session works on the task branch; extra sessions get per-session worktree branches.
- Session — an agent instance (claude / codex / pi; add opencode) running in a tmux session, by default in its own git worktree.
- Adhoc session — project-scoped, no task/worktree.
- Run session — per-project command (
npm run dev, etc.) in a dedicated tmux session.
State lives in ~/.showrunner/.
- Owns the authoritative session/agent state.
- Exposes
GET /events(SSE) and a small JSON API. showrunner already has an HTTP server (serve) for the web UI — extend it for SSE and make the TUI subscribe. - Correlates a pane to a session/project via
pane_current_path+ hook markers + process name.
- Hooks (authoritative): wire each agent to emit events to the daemon.
- Claude Code:
Notification(matcherpermission_prompt/idle_prompt/agent_needs_input/agent_completed),SessionStart/End,Stopin~/.claude/settings.json. (Yourhelpers/claude-hook.shis the prototype.) - OpenCode: plugin subscribing to event bus (
session.idle,session.error,session.diff, …). (Your repo already readsopencode-hook.state.) - Codex:
hooks.json/[hooks]—SessionStart,SessionEnd,PermissionRequest,Stop; plusnotifyforagent-turn-complete.
- Claude Code:
- Fallback detection: scan panes by process name + parse visible prompt (bosun's prompt-box approach) when hooks are absent.
- States:
idle·working·waiting(permission/question) ·error·done.
- Per-session CPU/mem:
tmux list-panes -F '#{pane_pid}'→ walk process tree → sum usage. (Yourui.shalready does the tree-walk pattern.) - GPU per process:
nvidia-smi(pattern from tmux-cpu). - Applies to all tmux sessions, not just agents.
- Rendered via tmux
display-popup(external viewer) per decision.
- Policy: close when idle (no input + low CPU) OR agent finished (
Stop/SessionEndhook). - Safeguard: never auto-close a worktree with uncommitted changes without confirmation.
- Both triggers configurable and disable-able.
- showrunner already has
send/ask(CLI). Expose quick-reply in the TUI that sends viatmux send-keys -t pane "text" Enter.
- Unify hooks from all three agents → daemon → TUI badge + OS notify (
notify-send/terminal-notifier) + bell. Youralerts.sh/alert_status.shalready cover the indicator + status-right.
- showrunner adds projects by path manually. Add discovery by scanning configured dirs (tmux-sessionizer pattern) + zoxide/ghq.
- Fast switch via fuzzy palette (
/already exists in showrunner).
- Organized project view (Projects→Tasks→Sessions, worktrees) — inherited
- Active agent sessions view (claude/opencode/codex) with status — extend
- Diff visualization via hunk (kept) — inherited
- Fast project switching — extend
- Open/close/manage sessions — inherited
- Compute resource management (CPU/mem/GPU, all sessions) — new
- Auto-close sessions (idle + finished, safeguarded) — new (opt-in; off by default)
- Send messages via tmux send-keys — adapt
- Notifications — extend (tmux status-bar badge + OS notify; the "bell" was dropped as non-functional)
Fork showrunner, build from source, understand the codebase. Set up the Rust workspace
(crates: daemon, tui, tmux, agents, resources). Wire bash bootstrap
(sessionizer.tmux) to launch the binary + daemon. Verify build/test.
- Daemon with SSE (
/events) + JSON API; TUI subscribes instead of polling. - Projects view (inherited) + Agents view with status (hooks + scan fallback).
- Add OpenCode harness.
- Fast project/session switching.
- Definition of done: dashboard shows projects and live agent states; switching works; daemon runs detached.
- Quick-reply in TUI (
send-keys). - Approval flows (y/n, numbered choices).
- Spawn sessions by agent type; kill/restart from TUI.
- Per-session CPU/mem (+ GPU) collection.
- Resource panel in tmux popup.
- Auto-close policy engine with safeguards + config.
- Keep hunk diff (already integrated); wire into project/task/session contexts.
- Unify notifications (badge + OS notify + bell).
- Themes, full config, keybindings, docs, TPM install docs.
Phases 0–4 are complete. It all ships as a single Rust
binary (showrunner) — daemon + TUI + web UI + CLI in one crate (not the
multi-crate workspace Phase 0 sketched). cargo build + cargo test (180 pass)
are the gates; the legacy bash TUI was dropped in favour of the Rust one.
Actuals vs. the locked decisions above:
- Rendering: the diff and resource panels are in-TUI overlays (Ratatui),
not
tmux display-popupexternal viewers — the popup decision was superseded. - Notifications: unified to the tmux status-bar badge (daemon-written
status.cache) + OS notify. The "bell" was never functional and was removed. - Authoritative hooks: wired for Claude, OpenCode, and Codex — Claude via the
plugin
hooks.json, OpenCode via an auto-loaded.opencode/plugins/file, Codex via the legacynotify(turn-complete). Each POSTs to/api/hook; the worker correlates bycwdand applies the status. Pi uses the pane-scrape fallback.
Remaining / deferred:
- display-popup diff/resource viewers — superseded by the in-TUI overlays (better UX); not reimplementing.
- Tighter idle/finished policy — already satisfied: hook events apply as a one-shot status override on the next tick (zero pane-scrape latency), and the 3-tick stability + finished guards cover the hook-less cases.
The agent-workspace work (status, naming, categories, restore, suspend, MCP,
sessions view) is tracked in PLAN.md. This section is what comes after it,
drawn from a survey of similar tools (see Landscape below).
Guiding constraint: showrunner lives inside the user's own tmux. Features that need an owned multiplexer, a replaceable UI or native Windows are out of scope (see Not planned).
| Tool | Stars | Backend | What to learn from it |
|---|---|---|---|
| Herdr | 44.8k | own multiplexer | ~24 agents; "add Herdr support" protocol for agent authors; layout snapshots; plugins; mobile PWA (Collie) |
| Vibe Kanban | 28.4k | web app | kanban issues → agent workspaces; inline diff comments sent to the agent; PR + merge |
| Claude Squad | 8.6k | tmux | -y auto-accept; pause/checkout |
| workmux | 2.9k | user's tmux | closest in spirit: status in window names, merge cleans up worktree + window + branch, sandbox |
| CCManager | 1.3k | own PTY | .worktreeinclude; AI-verified auto-approval; devcontainer |
| Agent Deck | 1.1k | tmux | message queue; fork + parent link; cost dashboard; Telegram/Slack conductor; MCP/skills manager; Docker sandbox |
| thurbox | 95 | own tmux server | multi-repo sessions; fork + lead/worker tree; agent mailbox; scheduled automations; agents as TOML; Lua UI |
What only showrunner has: a native MCP server with guards (own-pane
protection, read_only), six statuses including question vs permission with the
pending options, per-session CPU/mem/GPU, and suspend of idle agents.
A kanban board over a pluggable task source. Cards moved to Ready are spawned as agent sessions automatically, up to a concurrency cap, and the card follows the session through review to done.
Canonical columns. Every provider maps its own states onto these:
| Column | Meaning | Set by |
|---|---|---|
| Backlog | not for agents yet | human |
| Ready | eligible for dispatch | human |
| Running | a session is working on it | dispatcher |
| Needs you | session is question, permission or error |
dispatcher (from session status) |
| Review | agent finished; PR open | dispatcher |
| Done | PR merged / card closed | dispatcher or human |
Needs you is derived from session status. Providers without a matching state show it as a label or as a badge on the card.
Providers. Each implements one trait (src/tasks/), so the board, the
dispatcher and the UIs never know which one is behind a board:
trait TaskProvider {
fn list(&self) -> Result<Vec<Card>>; // cards + their column
fn move_to(&self, card: &CardId, col: Column) -> Result<()>;
fn comment(&self, card: &CardId, body: &str) -> Result<()>;
fn link(&self, card: &CardId, kind: Link, url: &str) -> Result<()>; // branch, PR
}
// Card { id, key ("SR-42"), title, body, column, labels, priority, url }| Provider | API | Auth | Notes |
|---|---|---|---|
native |
~/.showrunner |
none | extends Task with column + body; built first, defines the trait |
plane |
REST /api/v1/workspaces/{slug}/projects/{id}/… (work items, states, comments) |
X-API-Key |
self-hosted on the LAN; check endpoint names against the instance's version |
github |
Projects v2 GraphQL (updateProjectV2ItemFieldValue on the Status field) |
reuses gh auth |
items are issues; PR link is native |
jira |
REST v3 /rest/api/3/search/jql, /issue/{key}/transitions |
API token | comments are ADF |
Config (~/.showrunner/config.toml). One board per block, each bound
to one showrunner project (repo):
[[boards]]
name = "sessionizer"
provider = "plane" # native | plane | github | jira
project = "tmux-sessionizer" # showrunner project the cards run in
agent = "claude"
max_running = 2 # concurrency cap for this board
prompt_template = "~/.showrunner/prompts/task.md"
[boards.plane]
url = "http://plane.lan"
workspace = "joserdf"
project_id = "…"
api_key_env = "PLANE_API_KEY" # secrets come from env, never the file
[boards.columns] # provider state -> canonical column
"Backlog" = "backlog"
"Ready for agent" = "ready"
"In Progress" = "running"
"In Review" = "review"
"Done" = "done"Dispatcher (daemon loop, poll every 60 s; webhooks later):
- Per board, count Running cards whose session is alive. While below
max_running(and below a global[dispatch] max_running), take the next Ready card by provider rank/priority. - Create the task: branch
sr/<key>-<slug>, a worktree (existingcopy_patterns/setup_commands), and a session with the board's agent. The first message is the prompt template rendered with the card's title, body and URL. - Move the card to Running and comment the branch and session name.
- Map session status onto the card:
question/permission/error→ Needs you; back toworking→ Running. - On
done: if the branch has commits, push and open the PR (existing ops), link it and move to Review. If not, move to Needs you with the agent's last output as a comment. - On PR merged → Done: archive-and-close the session (existing snapshot path).
Rules:
- Idempotent. The card id is stored on the
SessionRecord. A card with a live or archived task is never dispatched twice, including after a daemon restart. - Pausable.
showrunner board pause|resume, and daemon off (Alt+a) stops dispatching. - No silent escalation. Dispatched agents run in their normal permission mode. A board must opt in explicitly to any bypass flag.
--dry-runprints what would be dispatched.
UI (both):
- Web (
/boardon the daemon): one column per canonical state, drag between columns (callsmove_to), live session status on each card via the existing SSE, click a card for output and diff, plus a "dispatch now" button. - TUI:
showrunner boardand a key in the sessions view. Compact columns with counts;Enteron a card switches to its session;mmoves the card. - MCP:
list_cards(board, column?),move_card,dispatch(card)(lays the groundwork for a lead agent, Phase 6). - CLI:
showrunner board list|dispatch|pause|resume.
Order: native provider + trait → dispatcher → TUI board → web board → Plane → GitHub Projects → Jira.
Definition of done: a card moved to Ready in Plane becomes a running session within one poll, shows Needs you when the agent asks something, opens a PR when the agent finishes, and the cap holds with N Ready cards.
Status (2026-10-20): native, Plane, dispatcher (full status mapping,
auto push + PR, merged → Done + archive), TUI board, web board (status badge,
output/diff panel), CLI and MCP tools (list_cards, move_card, dispatch)
are in. The Plane provider passed a live round trip (list, move, comment)
against a private test project, and a live daemon run (2026-10-20) met the
definition of done: Ready → session within one poll, question → Needs you,
commits → push + PR → Review, PR merged → Done + session archived. Found on
that run: Claude's folder-trust dialog (fresh repo) left the card Running with
no signal (now an attention marker); a card in Needs you stopped counting
against max_running (now counted); and an agent could cd out of its
worktree and commit on the main checkout, leaving its card with no commits
(the default prompt now pins it to the worktree). GitHub Projects and Jira are
not started.
Open questions:
Plane URL and version on the LAN— Plane 1.4.2 serves bothissues/andwork-items/; the provider useswork-items/.- Webhooks need the daemon reachable from the LAN (it binds
127.0.0.1:7878today). Keep polling as the default; add webhooks with a secret as an opt-in. - Boards spanning several repos: map card → repo by a label (
repo:<name>) or keep one board per showrunner project.
- Message queue.
send_messageto aworkingagent queues instead of refusing, and is delivered on the nextidle/done(built onwait_for_status).forcekeeps today's interrupting behaviour. (Agent Deck) - Fork a session (Claude
--fork-session, Codex fork) with a parent link on theSessionRecord. The sessions view nests children under their parent. (thurbox, Agent Deck) - Orchestration MCP tools:
spawn_worker(prompt, project?),children(session)andcollect(session), so a lead agent can fan out work and gather results. This shares the spawn path with the Phase 5 dispatcher. (thurbox Fleet, Herdr agent-automation)
Status (2026-10-20): all three are in. The message queue landed earlier.
Fork: showrunner fork, the sessions view F key and the MCP fork tool; a
fork shares the source's worktree, and Pi has no fork. Orchestration takes
the parent task session instead of a project: spawn_worker(parent, prompt, agent?) starts a session of the parent's task in its own worktree, so workers
merge back like any other session of the task.
- Token cost per session in the
REScolumn and the resource panel, read from the Claude/Codex transcripts that search already parses. (Agent Deck) - Phone notifications via ntfy or Telegram on
question/permission/ board Needs you, sent from the daemon next to the OS notifier. (Agent Deck, Herdr plugin)
Status (2026-10-20): token cost is in. Phone notifications go through
carcara-duo (Telegram) rather than the daemon: the daemon serves MCP over
streamable HTTP at /mcp with a bearer token (~/.showrunner/mcp_token),
and duo polls status and turns permission and question prompts into Telegram
cards that call approve / answer. The duo side lives in the carcara-duo
repo.
- Agents as data: a TOML file with command, resume command, hook/scrape
status strategy and window-name rules, so new agents need no recompile. Add
Gemini, Copilot CLI, Cursor Agent and Amp. (thurbox
agents.toml, Herdr) - Diff comments to the agent: comment lines during hunk review, then send them to the session as one message. (Vibe Kanban)
.worktreeinclude: also honour a repo-level.worktreeinclude(gitignore syntax) in addition to the per-projectcopy_patterns. (CCManager, Agent Deck)
Status (2026-10-20): diff comments and .worktreeinclude are in. The TUI
forwards hunk/difit comments, and the web diff view takes per-line notes sent
through POST /api/sessions/{name}/review. Agents as data is hybrid: the four
built-ins stay in code (hooks, transcripts, usage), and Gemini, Copilot CLI,
Cursor Agent and Amp are agents.toml entries (bundled, overridable from
~/.showrunner/agents.toml) with pane-scraped status. Cursor and Amp are
unverified (not installed).
- Own multiplexer / own tmux server (Herdr, thurbox): would give up living in the user's tmux.
- Replaceable Lua UI and a plugin marketplace (thurbox, Herdr).
- Native Windows; SSH hosts in one list (revisit if remote agents become a need).
- AI auto-approval of permission prompts (CCManager): risky. The board's Needs you column plus phone notifications cover the same pain.