Agent Deck
Terminal session manager for AI coding agents. Built with Go + Bubble Tea.
Version: 0.8.98 | Repo: github.com/asheshgoplani/agent-deck | Discord: discord.gg/e4xSs6NBN8
Script Path Resolution (IMPORTANT)
This skill includes helper scripts in its scripts/ subdirectory. When Claude Code loads this skill, it shows a line like:
Base directory for this skill: /path/to/.../skills/agent-deckYou MUST use that base directory path to resolve all script references. Store it as SKILL_DIR:
# Set SKILL_DIR to the base directory shown when this skill was loaded
SKILL_DIR="/path/shown/in/base-directory-line"
# Then run scripts as:
$SKILL_DIR/scripts/launch-subagent.sh "Title" "Prompt" --waitCommon mistake: Do NOT use <project-root>/scripts/launch-subagent.sh. The scripts live inside the skill's own directory (plugin cache or project skills folder), NOT in the user's project root.
For plugin users, the path looks like: ~/.claude/plugins/cache/agent-deck/agent-deck/<hash>/skills/agent-deck/scripts/ For local development, the path looks like: <repo>/skills/agent-deck/scripts/
Quick Start
# Launch TUI
agent-deck
# Create and start a session
agent-deck add -t "Project" -c claude /path/to/project
agent-deck session start "Project"
# Send message and get output
agent-deck session send "Project" "Analyze this codebase"
agent-deck session output "Project"Essential Commands
| Command | Purpose | |
|---|---|---|
agent-deck | Launch interactive TUI | |
agent-deck add -t "Name" -c claude /path | Create session | |
agent-deck session start/stop/restart <name> | Control session | |
agent-deck session send <name> "message" | Send message | |
agent-deck session output <name> | Get last response | |
| `agent-deck session current [-q\ | --json]` | Auto-detect current session |
agent-deck session fork <name> | Fork Claude conversation | |
agent-deck mcp list | List available MCPs | |
agent-deck mcp attach <name> <mcp> | Attach MCP (then restart) | |
agent-deck status | Quick status summary | |
agent-deck add --worktree <branch> | Create session in git worktree | |
agent-deck worktree list | List worktrees with sessions | |
agent-deck worktree cleanup | Find orphaned worktrees/sessions | |
agent-deck feedback | Submit feedback (opens rating prompt + optional comment) |
Status: ● running | ◐ waiting | ○ idle | ✕ error
Sub-Agent Launch
Use when: User says "launch sub-agent", "create sub-agent", "spawn agent"
$SKILL_DIR/scripts/launch-subagent.sh "Title" "Prompt" [--mcp name] [--wait]The script auto-detects current session/profile and creates a child session.
Retrieval Modes
| Mode | Command | Use When |
|---|---|---|
| Fire & forget | (no --wait) | Default. Tell user: "Ask me to check when ready" |
| On-demand | agent-deck session output "Title" | User asks to check |
| Blocking | --wait flag | Need immediate result |
Recommended MCPs
| Task Type | MCPs |
|---|---|
| Web research | exa, firecrawl |
| Code documentation | context7 |
| Complex reasoning | sequential-thinking |
Consult Another Agent (Codex, Gemini)
Use when: User says "consult with codex", "ask gemini", "get codex's opinion", "what does codex think", "consult another agent", "brainstorm with codex/gemini", "get a second opinion"
IMPORTANT: You MUST use the --tool flag to specify which agent. Without it, the script defaults to Claude.
Quick Reference
# Consult Codex (MUST include --tool codex)
$SKILL_DIR/scripts/launch-subagent.sh "Consult Codex" "Your question here" --tool codex --wait --timeout 120
# Consult Gemini (MUST include --tool gemini)
$SKILL_DIR/scripts/launch-subagent.sh "Consult Gemini" "Your question here" --tool gemini --wait --timeout 120DO NOT try to create Codex/Gemini sessions manually with agent-deck add. Always use the script above. It handles tool-specific initialization, readiness detection, and output retrieval automatically.
Full Options
$SKILL_DIR/scripts/launch-subagent.sh "Title" "Prompt" \
--tool codex|gemini \ # REQUIRED for non-Claude agents
--path /project/dir \ # Working directory (auto-inherits parent path if omitted)
--wait \ # Block until response is ready
--timeout 180 \ # Seconds to wait (default: 300)
--mcp exa # Attach MCP servers (can repeat)Supported Tools
| Tool | Flag | Notes |
|---|---|---|
| Claude | --tool claude | Default, no flag needed |
| Codex | --tool codex | Requires codex CLI installed |
| Gemini | --tool gemini | Requires gemini CLI installed |
How It Works
- Script auto-detects current session and profile
- Creates a child session with the specified tool in the parent's project directory
- Waits for the tool to initialize (handles Codex approval prompts automatically)
- Sends the question/prompt
- With
--wait: polls until the agent responds, then returns the full output - Without
--wait: returns immediately, check output later withagent-deck session output "Title"
Examples
# Code review from Codex
$SKILL_DIR/scripts/launch-subagent.sh "Codex Review" "Read cmd/main.go and suggest improvements" --tool codex --wait --timeout 180
# Architecture feedback from Gemini
$SKILL_DIR/scripts/launch-subagent.sh "Gemini Arch" "Review the project structure and suggest better patterns" --tool gemini --wait --timeout 180
# Both in parallel (consult both, compare answers)
$SKILL_DIR/scripts/launch-subagent.sh "Ask Codex" "Best way to handle errors in Go?" --tool codex --wait --timeout 120 &
$SKILL_DIR/scripts/launch-subagent.sh "Ask Gemini" "Best way to handle errors in Go?" --tool gemini --wait --timeout 120 &
waitCleanup
After getting the response, remove the consultation session:
agent-deck remove "Consult Codex"
# Or remove multiple at once:
agent-deck remove "Codex Review" && agent-deck remove "Gemini Arch"Peer (Root) Sessions vs Sub-Agents
The default — sub-agent linkage: agent-deck launch and agent-deck add, when invoked from *inside* an existing agent-deck session, automatically link the new session as a child of the calling session (sets parent_session_id, inherits the parent's group when -g is omitted, and grants --add-dir to the parent's project path). This is usually what you want for short-lived work sessions (plan / verify / release / consult).
When the default is wrong — root-level peer sessions: if you are creating a session that should stand independently at the root — a peer conductor, a standalone project session, a session that should outlive the current one, or anything that semantically is NOT a child of the calling session — pass the -no-parent flag.
| Use case | Parent linkage | Flag |
|---|---|---|
| Plan / impl / verify worker for the current task | ✅ child | (default) |
| Consultation (codex / gemini / research) | ✅ child | (default) |
| Another conductor (root-level peer) | ❌ child | -no-parent |
| Project session unrelated to current work | ❌ child | -no-parent |
| Session intended to outlive the caller | ❌ child | -no-parent |
# Root-level peer conductor, no parent linkage:
agent-deck launch ~/projects/foo -t "conductor-foo" -g "conductor" -c claude -no-parent -m "..."
# Verify after spawn:
agent-deck list --json | jq '.[] | select(.title=="conductor-foo") | .parent_session_id'
# Must print: nullSymptoms you created a sub-agent when you wanted a peer:
parent_session_idis non-null inlist --jsonoutput- The new session's baked
pane_start_commandcontains--add-dir <caller's path>even though you gave it a different project path - Transition events for the new session's children flow to the caller instead of the new peer
- Event routing and heartbeat parent-linkage puts it under the caller's tree in the TUI
Fix for an already-created sub-agent: stop + remove the session, re-launch with -no-parent. There is no in-place un-parent flag.
Note on the launch-subagent.sh script: that script is specifically designed to create sub-agents (the name says so). It does NOT support -no-parent. For peer sessions, skip the script and invoke agent-deck launch -no-parent directly.
Conductors
A conductor is a persistent agent-deck session that orchestrates other sessions. It watches the rest of your sessions, auto-responds when confident, escalates to you when ambiguous, and optionally pairs with a remote channel (Telegram or Slack) so you can talk to it from your phone.
Use this section when the user says "conductor", "set up a conductor", "monitor sessions", "telegram bot for agent-deck", "slack bot for agent-deck", or "remote control my sessions".
# Create a conductor in the default profile
agent-deck conductor setup ops --description "Ops monitor"
# Create on a specific profile (work/personal/etc.)
agent-deck -p work conductor setup infra --description "Infra watcher"
# Use a non-Claude agent for the conductor itself
agent-deck conductor setup review --agent codex --description "Codex reviewer"
# Provide custom env (e.g., third-party Anthropic-compatible endpoint)
agent-deck conductor setup glm-bot \
-env ANTHROPIC_BASE_URL=https://api.z.ai/api/anthropic \
-env ANTHROPIC_AUTH_TOKEN=<token>
# Status across all conductors
agent-deck conductor status
# List configured conductors
agent-deck conductor listEach conductor lives at ~/.agent-deck/conductor/<name>/ with its own CLAUDE.md (or AGENTS.md for Codex), meta.json, state.json, and task-log.md. Multiple conductors per profile are supported and each can pair with its own bot.
Channels (Telegram / Slack)
Channels are how a conductor talks to you remotely. Each conductor pairs one-to-one with its own bot — bots are not shared. agent-deck conductor setup interactively walks you through Telegram or Slack pairing during creation.
Key constraints:
- The Telegram plugin must be installed under the conductor's Claude profile, but never globally enabled in
settings.json. Per-session activation happens via thechannelsfield on the conductor's session record. - Bot tokens live at
<channel-state-dir>/.env(chmod 600). Never committed to git. - Exactly one bot, one conductor, one chat — the routing is deterministic.
- Watch for the "many competing telegram pollers" gotcha (see Known Gotchas section below) — child sessions inherit
TELEGRAM_STATE_DIRand can leak duplicate pollers on the same bot token, causing 409 conflicts.
See: documentation/CONDUCTOR.md for the full ten-minute quickstart, telegram bot creation flow, multi-conductor setups, and lifecycle commands.
TUI Keyboard Shortcuts
Navigation
| Key | Action |
|---|---|
j/k or ↑/↓ | Move up/down |
h/l or ←/→ | Collapse/expand groups |
Enter | Attach to session |
Session Actions
| Key | Action |
|---|---|
n | New session |
r/R | Restart (reloads MCPs) |
m | MCP Manager |
s | Skills Manager |
f/F | Fork Claude session |
d | Delete |
M | Move to group |
Search & Filter
| Key | Action |
|---|---|
/ | Local search |
G | Global search (all Claude conversations) |
!@#$ | Filter by status (running/waiting/idle/error) |
Global
| Key | Action |
|---|---|
? | Help overlay |
Ctrl+Q | Detach (keep tmux running) |
Ctrl+E | Open feedback dialog |
q | Quit |
MCP Management
Default: Do NOT attach MCPs unless user explicitly requests.
# List available
agent-deck mcp list
# Attach and restart
agent-deck mcp attach <session> <mcp-name>
agent-deck session restart <session>
# Or attach on create
agent-deck add -t "Task" -c claude --mcp exa /pathScopes:
- LOCAL (default) -
.mcp.jsonin project, affects only that session - GLOBAL (
--global) - Claude config, affects all projects
Worktree Workflows
Create Session in Git Worktree
When working on a feature that needs isolation from main branch:
# Create session with new worktree and branch
agent-deck add /path/to/repo -t "Feature Work" -c claude --worktree feature/my-feature --new-branch
# Create session in existing branch's worktree
agent-deck add . --worktree develop -c claudeList and Manage Worktrees
# List all worktrees and their associated sessions
agent-deck worktree list
# Show detailed info for a session's worktree
agent-deck worktree info "My Session"
# Find orphaned worktrees/sessions (dry-run)
agent-deck worktree cleanup
# Actually clean up orphans
agent-deck worktree cleanup --forceWhen to Use Worktrees
| Use Case | Benefit |
|---|---|
| Parallel agent work | Multiple agents on same repo, different branches |
| Feature isolation | Keep main branch clean while agent experiments |
| Code review | Agent reviews PR in worktree while main work continues |
| Hotfix work | Quick branch off main without disrupting feature work |
Watchers
Watchers listen for inbound events (webhooks, push notifications, GitHub events, Slack messages) and route them into conductor sessions. Use them when the user says "set up a watcher", "listen for webhooks", "route GitHub events to my conductor", "forward ntfy notifications", or similar.
Four adapter types are supported:
| Type | Required flag | Typical use |
|---|---|---|
webhook | --port | Generic HTTP listener |
github | --secret | GitHub repo webhooks with HMAC verification |
ntfy | --topic | ntfy.sh push notifications |
slack | --topic | Slack (via Cloudflare Worker bridge) |
agent-deck watcher create <type> --name <name> <adapter-flags...>
agent-deck watcher start <name>
agent-deck watcher list # health + events/hour
agent-deck watcher test <name> # synthetic event (verify routing)Full conversational setup flow is available as a separate skill:
agent-deck watcher install-skill watcher-creatorAfter running the install command, read ~/.agent-deck/skills/pool/watcher-creator/SKILL.md to walk the user through adapter selection, required settings, and configuring ~/.agent-deck/watcher/<name>/clients.json routing.
See agent-deck watcher --help for the full command surface and per-adapter examples.
Configuration
File: ~/.agent-deck/config.toml
[claude]
config_dir = "~/.claude-work" # Custom Claude profile
dangerous_mode = true # --dangerously-skip-permissions
[logs]
max_size_mb = 10 # Max before truncation
max_lines = 10000 # Lines to keep
[mcps.exa]
command = "npx"
args = ["-y", "exa-mcp-server"]
env = { EXA_API_KEY = "key" }
description = "Web search"See config-reference.md for all options.
Troubleshooting
| Issue | Solution |
|---|---|
| Session shows error | agent-deck session start <name> |
| MCPs not loading | agent-deck session restart <name> |
| Flag not working | Put flags BEFORE arguments: -m "msg" name not name -m "msg" |
Get Help
- Discord: discord.gg/e4xSs6NBN8 for quick questions and community support
- GitHub Issues: For bug reports and feature requests
Report a Bug
If something isn't working, create a GitHub issue with context:
# Gather debug info
agent-deck version
agent-deck status --json
cat ~/.agent-deck/config.toml | grep -v "KEY\|TOKEN\|SECRET" # Sanitized config
# Create issue at:
# https://github.com/asheshgoplani/agent-deck/issues/newInclude:
- What you tried (command/action)
- What happened vs expected
- Output of commands above
- Relevant log:
tail -100 ~/.agent-deck/logs/agentdeck_<session>_*.log
See troubleshooting.md for detailed diagnostics.
Session Sharing
Share Claude sessions between developers for collaboration or handoff.
Use when: User says "share session", "export session", "send to colleague", "import session"
# Export current session to file (session-share is a sibling skill)
$SKILL_DIR/../session-share/scripts/export.sh
# Output: ~/session-shares/session-<date>-<title>.json
# Import received session
$SKILL_DIR/../session-share/scripts/import.sh ~/Downloads/session-file.jsonSee: session-share skill for full documentation.
Critical Rules
- Flags before arguments:
session start -m "Hello" name(notname -m "Hello") - Restart after MCP attach: Always run
session restartaftermcp attach - Never poll from other agents - can interfere with target session
Known Gotchas (v1.7.0+)
Friction points discovered during real usage. Work around them per the patterns below.
session send --no-wait can leave prompts typed-but-not-submitted
On a freshly-launched Claude session, agent-deck session send --no-wait <id> "..." may paste the message into the input buffer before Claude is fully ready, leaving it TYPED but not SUBMITTED. Classic race.
Workaround (always safe):
agent-deck -p <profile> session send <id> "..." --no-wait -q
sleep 3
# Get the tmux session name and send Enter to submit
TMUX=$(agent-deck -p <profile> session show --json <id> | jq -r .tmux_session)
tmux send-keys -t "$TMUX" EnterThe Enter is idempotent — if already submitted, it's just a no-op newline. Use this pattern every time you session send --no-wait to a freshly-launched session.
Alternative: omit --no-wait so the built-in 60s readiness wait kicks in before submitting.
Replacing the binary while agent-deck is running (text file busy)
If /usr/local/bin/agent-deck is a symlink to a build artifact and the binary is currently running (any tmux session, any daemon), a direct cp over it fails with Text file busy.
Workaround — move-then-copy (keeps running processes on the old inode):
INSTALL=$(which agent-deck)
TARGET=$(readlink -f "$INSTALL")
go build -ldflags "-X main.Version=X.Y.Z" -o /tmp/agent-deck-new ./cmd/agent-deck
mv "$TARGET" "$TARGET.old"
cp /tmp/agent-deck-new "$TARGET" && chmod +x "$TARGET"
agent-deck --version # verify
rm "$TARGET.old"Kernel tracks inodes, not names. Running processes keep a reference to the renamed inode; new invocations resolve through the original name to the new inode.
Cross-machine config drift (macOS ↔ Linux)
If ~/.agent-deck/skills/sources.toml (or other config files) were copied verbatim from a macOS machine, paths like /Users/<name>/ won't exist on Linux (should be /home/<user>/). The symptom: agent-deck skill list returns "No skills found" while the pool directory is clearly populated.
Check & fix:
grep -n "/Users/" ~/.agent-deck/skills/sources.toml
# If any matches, substitute the Linux home path:
sed -i "s|/Users/<mac-user>|$HOME|g" ~/.agent-deck/skills/sources.tomlChannel subscription for conductor/bot sessions (v1.7.0+)
For a session to receive Telegram/Discord/Slack messages as conversation turns (not just as MCP tool calls), it MUST be started with --channels <plugin-id>. Use the first-class field:
# At creation (preferred):
agent-deck -p personal add --channel plugin:telegram@claude-plugins-official -c claude -t my-bot /path
# Or after creation, then restart:
agent-deck -p personal session set my-bot channels plugin:telegram@claude-plugins-official
agent-deck -p personal session restart my-botThe channels field persists and every session start / session restart rebuilds the claude invocation with --channels. Do NOT rely on .mcp.json telegram entries — those load the plugin as a regular MCP (tools only), not a channel (inbound delivery).
Note — v1.7.0 display bug: agent-deck session show --json <id> currently omits the channels field (fix pending). agent-deck list --json | jq '.[] | select(.id==<id>)' shows it correctly. Data is persisted fine regardless.
Many competing telegram pollers after multiple session starts
Telegram's Bot API getUpdates is single-consumer per bot token. If N Claude sessions all load the telegram plugin, N bun pollers race for messages — deliveries land in whichever wins, not where you want them.
Correct topology: exactly ONE session loads the telegram channel plugin (normally the conductor, via --channels at start-time). All other sessions should NOT have telegram in their enabled plugins.
Disable globally: in ~/.claude/settings.json:
"enabledPlugins": {
"telegram@claude-plugins-official": false
}Enable per-session: via --channel on the specific session that should receive messages. See "Channel subscription" above.
Debug: pgrep -af "bun.*telegram" | wc -l should return 1. Anything higher means a race. Kill extras: pkill -f "bun.*telegram" then restart only the intended session.
Telegram conductor topology (v1.7.22+)
Supported topology — enforce this on every conductor host:
- Telegram is activated per-session via
--channels plugin:telegram@claude-plugins-official. This is the only supported activation path for a conductor bot. TELEGRAM_STATE_DIRis injected exclusively via[conductors.<name>.claude].env_filein~/.agent-deck/config.toml. The env file sources deterministically on both fresh-start and--resumespawns.- One bot token = one channel-owning session. Never share tokens between sessions.
enabledPlugins."telegram@claude-plugins-official"in the profilesettings.jsonmust be absent or false. Global enablement makes every claude subprocess (including child agents) load the plugin.
Codified anti-patterns — agent-deck v1.7.22 emits warnings for these:
| Anti-pattern | Code | Why it breaks |
|---|---|---|
enabledPlugins."telegram@claude-plugins-official" = true in profile settings | GLOBAL_ANTIPATTERN | Every claude process loads the plugin, including every child agent the conductor spawns. Each one starts a bun telegram poller. |
Global enablement AND --channels plugin:telegram@... on the same session | DOUBLE_LOAD | The plugin loads twice in one claude process. Two bun pollers race on one bot token and Telegram rejects with 409 Conflict. |
session set wrapper "TELEGRAM_STATE_DIR=... {command}" | WRAPPER_DEPRECATED | Works on the resume path; silently fails on fresh-start due to bash -c argv splitting. The env var never reaches claude, so the plugin falls back to the default state dir and two conductors collide. Use env_file instead. |
Relying on .mcp.json telegram entries for inbound delivery | — | .mcp.json loads the plugin as an MCP server (tool-use only). Inbound message → conversation-turn delivery requires --channels. |
| Using the same bot token for multiple concurrent sessions | — | getUpdates is single-consumer per token. |
Assuming an empty TELEGRAM_STATE_DIR is fine | — | The plugin falls back to ~/.claude/channels/telegram/; any DM approval there leaks across unrelated conductors. |
Verifying steady state (conductor host):
pgrep -af 'bun.*telegram' | grep -v grep | wc -l # expect: exactly one per conductor bot
for PID in $(pgrep -f 'bun.*telegram.*start'); do
echo "PID=$PID TSD=$(tr '\0' '\n' < /proc/$PID/environ | grep ^TELEGRAM_STATE_DIR= | cut -d= -f2-)"
done
# Each PID must show a distinct TELEGRAM_STATE_DIR; collisions indicate env_file is not being sourced.When agent-deck emits a ⚠ GLOBAL_ANTIPATTERN / DOUBLE_LOAD / WRAPPER_DEPRECATED warning, the problem is in your topology, not in agent-deck. Fix the profile settings or the conductor env_file; the warning is a leading indicator of the 409-Conflict symptom that follows minutes-to-hours later.
References
User guides (full how-to, in the repo):
- documentation/CONDUCTOR.md - Conductor quickstart, channel pairing, state files, multi-conductor setups
- documentation/WATCHERS.md - Event-forwarding framework: doorbell model, built-in adapters, custom watchers
- documentation/SKILLS.md - User-level vs pool skills, attach/detach, when to use which tier
- documentation/WATCHDOG.md - Optional Python daemon that auto-restarts critical sessions
Reference (shipped with this skill):
- cli-reference.md - Complete CLI command reference
- config-reference.md - All config.toml options
- tui-reference.md - TUI features and shortcuts
- troubleshooting.md - Common issues and bug reporting
- session-share skill - Export/import sessions for collaboration