From claude-multi-teams
Spawn other AI agents (claude, codex) in tmux panes and drive them via tmux paste-buffer + jsonl tail. Three orchestration patterns ship as scripts — hub-and-spoke (ask.sh, 1-to-1), N-way P2P discussion (ask-group.sh, @mention routing + [CONSENSUS]/[DEADLOCK]), and work-then-review (review.sh, one impl + N reviewers + iterate-on-feedback loop with [APPROVE]/[CHANGES_REQUESTED]/[REJECT]/[GIVE_UP]). Includes ask-skill.sh for invoking slash commands (skills) in spawned agents — see "Invoking a skill" below before paraphrasing a skill's prompt by hand. Includes flow.log + viewer.sh for passive call-tree observability. Use when the user wants to delegate to another agent, get a second opinion, run code review by a different model, have multiple agents discuss a decision, or run an impl→review loop.
How this skill is triggered — by the user, by Claude, or both
Slash command
/claude-multi-teams:claude-multi-teamsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Drives sibling AI CLIs (`claude`, later `codex`/`gemini`) running in mux panes:
Drives sibling AI CLIs (claude, later codex/gemini) running in mux panes:
tmux paste-buffer -p (real tmux) or tmux send-keys -l with raw ESC[200~ markers (cmux claude-teams shim) — both deliver multi-line + special chars atomically.stop_reason, extract assistant textTriggered by user requests like:
$TMUX set) or cmux claude-teams
(which sets a fake $TMUX + $CMUX_SOCKET_PATH so claude-multi-teams rides on top —
see "Multiplexer support" below).claude (Phase 1), codex/gemini (Phase 3).jq available.claude-multi-teams works in two environments transparently:
| Environment | How |
|---|---|
| Real tmux session | Native: claude-multi-teams calls tmux paste-buffer -p, tmux split-window, etc. against the actual tmux server. |
| cmux terminal | Launch your primary Claude Code via cmux claude-teams (cmux ships a tmux shim that translates the tmux CLI into cmux's surface API). claude-multi-teams's mux_paste_bracketed detects $CMUX_SOCKET_PATH and uses send-keys -l with raw ESC[200~…ESC[201~ markers since the shim has no paste-buffer subsystem. |
For users with a separate Claude config (e.g. claude-spare alias =
CLAUDE_CONFIG_DIR=$HOME/.claude-spare claude), launch cmux claude-teams with
the env var set so claude-multi-teams-spawned sub-agents share the same config:
CLAUDE_CONFIG_DIR=$HOME/.claude-spare cmux claude-teams
claude-multi-teams's spawn.sh propagates CLAUDE_* / ANTHROPIC_* to every spawned
worker, so each sub-agent inherits the parent's config dir automatically.
Out of scope: plain claude invoked inside cmux without cmux claude-teams.
cmux's default pty env lacks $CMUX_PANE / $CMUX_WORKSPACE_ID, so claude-multi-teams
cannot identify its own pane to split from. Use cmux claude-teams.
SKILL=~/.claude/skills/claude-multi-teams/scripts
# Spawn an agent in a new tmux split. Optional --task sends an initial prompt.
bash $SKILL/spawn.sh <agent> <name> [--task "first prompt"]
[--cwd <dir>] [--permission-mode <mode>] [--model <m>]
# agent: claude | codex (gemini not yet implemented)
# Hub-and-spoke: send prompt, block until done, print response on stdout.
bash $SKILL/ask.sh [--timeout S] <name> "prompt"
bash $SKILL/ask.sh [--timeout S] <name> @/path/to/prompt-file.txt
cat prompt.txt | bash $SKILL/ask.sh [--timeout S] <name> -
# Default timeout: 600s wall-clock OR ALPHAFORK_IDLE_MAX (default 120s) of
# no jsonl growth (lets long thinking-heavy turns succeed). Per-call override
# via --timeout. Exit codes: 0 ok, 1 error, 1 + status=timeout for time limits.
# Invoke a slash command (skill) in a spawned agent. Use this whenever you
# want the agent to dispatch its own skill (/grill-me, /code-review, etc.) —
# see "Invoking a skill" below for why a hand-written prompt does NOT work.
bash $SKILL/ask-skill.sh <name> <skill> [args]
bash $SKILL/ask-skill.sh <name> <skill> @/path/to/args.txt
# Recover the last assistant reply from an agent's session jsonl. Useful when
# ask.sh aborted/timed out but the agent actually finished after.
bash $SKILL/last-reply.sh <name>
# P2P: drive a multi-agent discussion. Participants exchange messages via
# @<name>: until one of them emits [CONSENSUS: ...] or [DEADLOCK: ...]. Result
# (verdict + full transcript) printed on stdout.
bash $SKILL/ask-group.sh <name1,name2,...> "<topic>" [--max-turns N] [--per-ask-timeout S]
# Work-then-review: one impl agent produces; N reviewers review in parallel.
# If any reviewer asks for changes, impl iterates. Loops until all approve, any
# reject, impl gives up, or --max-rounds is hit.
bash $SKILL/review.sh <impl> <reviewer1,reviewer2,...> "<task>"
[--max-rounds N] [--per-ask-timeout S]
# Render the call tree from flow.log (passive observer). See "flow.log" below.
bash $SKILL/viewer.sh # tree with status badges
bash $SKILL/viewer.sh --excerpts # also show prompt/reply previews
# List currently-tracked agents.
bash $SKILL/list.sh
# Close a pane and forget the agent.
bash $SKILL/kill.sh <name>
bash $SKILL/kill.sh --all
READ THIS BEFORE WRITING A PROMPT THAT NAMES A SKILL.
Spawned claude / codex CLIs dispatch a skill only when the prompt literally starts with /<skill-name>. They do not pattern-match on natural language. So if you want the agent to actually run /grill-me, the prompt sent to its pane MUST be:
/grill-me <topic>
If you instead write:
You are running the /grill-me skill on topic X. Ask 10 sharp questions about...
→ the agent receives that as a long string of instructions and proceeds to imitate what it thinks the skill does. The actual skill never runs. You get your hand-written paraphrase, not the real procedure. This is a silent failure: output looks plausible but isn't the skill's output.
SKILL=~/.claude/skills/claude-multi-teams/scripts
bash $SKILL/spawn.sh claude grill-reviewer
bash $SKILL/spawn.sh claude code-reviewer
bash $SKILL/spawn.sh codex codex-reviewer
# These dispatch the actual skills inside each agent:
bash $SKILL/ask-skill.sh grill-reviewer grill-me "PRD for X — find every weak assumption"
bash $SKILL/ask-skill.sh code-reviewer code-review high
# codex has no slash commands, so it gets a natural-language prompt — that's
# fine FOR codex because there's no skill to dispatch in the first place:
bash $SKILL/ask.sh codex-reviewer @/tmp/codex-prompt.txt
# DON'T: writing a long prompt that names the skill but doesn't invoke it.
bash $SKILL/ask.sh grill-reviewer "You are running /grill-me. Walk each branch..."
# The agent receives instructions, NOT a skill dispatch.
ask-skill.sh exists to make the right shape impossible to get wrong:
/<skill> <args> and forwards to ask.sh./, rejects shell metachars).@file for long args, or stdin via -.--timeout S to ask.sh.If you find yourself writing more than one line of "this is what the skill does", stop and use ask-skill.sh instead.
/grill-me, /code-review, /security-review, etc.) where the skill's procedure is what makes the review valuable.SKILL=~/.claude/skills/claude-multi-teams/scripts
bash $SKILL/spawn.sh claude reviewer
git diff > /tmp/diff.txt
{
echo "Review this diff and report issues. Be concise."
echo "----"
cat /tmp/diff.txt
} > /tmp/prompt.txt
response=$(bash $SKILL/ask.sh reviewer @/tmp/prompt.txt)
echo "$response"
bash $SKILL/kill.sh reviewer
SKILL=~/.claude/skills/claude-multi-teams/scripts
bash $SKILL/spawn.sh claude alice
bash $SKILL/spawn.sh codex bob
verdict=$(bash $SKILL/ask-group.sh alice,bob \
"Discuss: should a function reading a file be named read_file, read_file_contents,
or load_file? Pick ONE and emit [CONSENSUS] within 2-3 short exchanges.")
echo "$verdict" # → [CONSENSUS] [CONSENSUS: Use read_file ...] (+ transcript)
bash $SKILL/kill.sh --all
SKILL=~/.claude/skills/claude-multi-teams/scripts
bash $SKILL/spawn.sh codex worker
bash $SKILL/spawn.sh claude reviewer
verdict=$(bash $SKILL/review.sh worker reviewer \
"Write a bash function `slug_of` that turns a string into a kebab-case slug:
lowercase, ASCII-only, runs of non-alnum collapse to single '-', strip
leading/trailing '-'. Print the function definition only.")
echo "$verdict" # → [APPROVED] all reviewers approved at round 1 (+ artifact + transcript)
bash $SKILL/kill.sh --all
Reviewer replies must contain ONE of:
[APPROVE] — work is acceptable[CHANGES_REQUESTED: <numbered list>] — concrete, actionable issues[REJECT: <reason>] — fundamentally wrong, not fixable by iterationImpl, on iteration, may emit [GIVE_UP: <reason>] instead of a revised artifact.
The harness terminates when: all reviewers [APPROVE], any reviewer [REJECT]s,
impl [GIVE_UP]s, or --max-rounds is hit.
You are participating as "<your-name>". Other participants: <list>.
- Address one participant: @<name>: <message> (one mention per line)
- Broadcast to everyone: @all: <message>
- Group agreement reached: [CONSENSUS: <one-line summary>]
- Cannot agree: [DEADLOCK: <reason>]
If a participant would only express agreement with no new concern, it must emit [CONSENSUS: ...] instead of natural-language agreement; any remaining concern or edge case should be voiced first.
The harness:
@<name>: mentions → batches per recipient → dispatches in parallel next turn--max-turns turns (default 8)~/.cache/claude-multi-teams/agents/<name>.json
{name, agent, pane_id, session_file, started_at}ALPHAFORK_STATE_DIR.ALPHAFORK_TIMEOUT (default 600s) or --timeout S flag.ALPHAFORK_IDLE_MAX (default 120s of no jsonl growth → 124).Every ask.sh invocation appends two jsonl events to ~/.cache/claude-multi-teams/flow.log (override with ALPHAFORK_LOG):
{"ts":"…","event":"pending","call_id":"c…","parent_call_id":"c…","depth":N,"from":"…","to":"…","prompt_excerpt":"…"}
{"ts":"…","event":"done","call_id":"c…","duration_ms":N,"status":"ok|error|aborted|timeout","reply_excerpt":"…"}
from is detected automatically from $TMUX_PANE → agents/*.json lookup. Agents call ask.sh <other> "..." exactly the same way; they don't need to know their own name. Calls from outside any tracked pane log from="parent" (in-tmux) or "external" (no tmux).parent_call_id is the most recent unmatched pending where to=<caller> — i.e., the call the caller is currently servicing. Top-level calls have parent_call_id=null.prompt_excerpt/reply_excerpt are whitespace-collapsed previews capped at ALPHAFORK_EXCERPT_MAX chars (default 200).viewer.sh (tree); children can cat $ALPHAFORK_LOG or run viewer.sh from their own Bash tool. Same source of truth, different views.: > ~/.cache/claude-multi-teams/flow.log when you want a fresh slate.ask.sh traps EXIT, so a killed/crashed process still logs done with status="aborted" — no phantom "in-flight" entries. Malformed lines are tolerated by find_my_active_parent / depth_of_parent / viewer.sh (skipped silently).ok (terminal stop_reason), timeout (adapter exit 124 = hard ceiling or ALPHAFORK_IDLE_MAX exhausted), aborted (process killed before done was logged), error (anything else).▶active (< 5s ago), ▶idleNs/m/h/d, or ⏰stale (pending older than --stale-after).ask.sh (hub-and-spoke), ask-group.sh (N-way P2P discussion), review.sh (work-then-review with N reviewers + iteration)claude (via --session-id), codex (snapshot-diff + trust override + long-paste file-handoff)flow.log + viewer.sh tree (passive — no enforcement)gemini adapter — not yet implemented (jsonl format varies across versions)@<other>: addressing); no automatic self-mention correction yetCreates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub namjug-kim/claude-multi-teams --plugin claude-multi-teams