A CLI tool to manage multiple AI coding agents (Claude Code and Codex) working in parallel on isolated git worktrees.
Inspired by Conductor for Mac.
curl -fsSL https://raw.githubusercontent.com/admud/conductor-linux/main/install.sh | bash- Run multiple AI coding agents simultaneously (Claude Code & Codex)
- Each agent gets an isolated git worktree (no conflicts)
- Monitor all agents from a single dashboard
- TUI Dashboard - Visual interface with clickable buttons
- JSON output - Scriptable with
jq - Shell completions - bash, zsh, fish
- fzf integration - Interactive agent picker
- Config file - Customize defaults
- View diffs and changes across all agents
- Push agent branches back when ready
- Archive and restore workspaces
- MCP Server Support - Connect agents to external tools via Model Context Protocol
- Agent Completion Detection - Automatically detect when agents finish tasks
- Desktop Notifications - Get notified when agents complete, error, or need input
- Agent Health Check - Monitor CPU/memory usage and detect stuck agents
- Batch Operations - Send commands to multiple agents at once
- Summary Command - Aggregated statistics and overview of all agents
- Watch Mode - Real-time monitoring with status change callbacks
- Agent Templates - Reusable configurations for common workflows
- Session Save/Restore - Save and restore agent sessions for handoff
- Python 3.10+
- git
- tmux
textual(required forcdl-uionly)- Claude Code CLI (
claude) and/or Codex CLI (codex) - Optional: GitHub CLI (
gh) for PR workflow - Optional:
fzffor interactive picker
Both Claude Code and Codex require authentication before use. Credentials are stored locally on your machine.
# Login (opens browser for OAuth or prompts for API key)
claude login
# Or set API key directly
export ANTHROPIC_API_KEY=sk-ant-...
# Verify
claude whoamiCredentials stored in: ~/.config/.claude/
# Login (opens browser for ChatGPT auth or prompts for API key)
codex login
# Or set API key directly
export OPENAI_API_KEY=sk-...
# Verify
codex --versionCredentials stored in: ~/.codex/
Note: CDL does not store or manage API keys. Each agent CLI handles its own authentication locally.
git clone https://github.com/admud/conductor-linux.git ~/.cdl
cd ~/.cdl
pip install -r requirements.txt
# If you only need the CLI, you can skip `textual`, but `cdl-ui` requires it.
# Add to PATH (add to your .bashrc or .zshrc)
export PATH="$HOME/.cdl/bin:$PATH"
# Enable shell completions (optional)
eval "$(cdl completions bash)" # for bash
eval "$(cdl completions zsh)" # for zsh# 1. Add a repository
cdl add https://github.com/user/repo.git
# 2. Spawn agents on different branches
cdl spawn myrepo feature-auth --task "Implement OAuth login"
cdl spawn myrepo feature-tests --task "Add unit tests"
cdl spawn --from-pr 123 --task "Review and fix PR"
cdl spawn --from-branch hotfix/login --task "Hotfix login bug"
# 3. Monitor all agents
cdl status # or: cdl s
cdl status --json # JSON output for scripting
# 4. Launch TUI dashboard
cdl-ui
# 5. View an agent's terminal
cdl attach 1 # or: cdl a 1
cdl attach # fzf picker if no arg
# 6. Follow logs live
cdl logs -f 1 # like tail -f
# 7. Review changes
cdl diff # or: cdl d
cdl diff --tool delta # use external diff tool
# 8. Push changes when ready
cdl merge 1
# 8b. Create a PR
cdl pr create 1 --fill
# 9. Cleanup
cdl kill 1 --cleanup # or: cdl k 1 -c
# 10. Archive and restore
cdl archive 1
cdl archives
cdl restore| Command | Alias | Description |
|---|---|---|
cdl add <url> |
Clone and register a repository | |
cdl list |
List all repos and active agents | |
cdl spawn <repo> <branch> |
Start a new Claude Code agent | |
cdl status |
s |
Show detailed status of all agents |
cdl attach [n] |
a |
Attach to agent's tmux session |
cdl diff [n] |
d |
Show git diff for agent(s) |
cdl logs [n] |
l |
View agent's terminal output |
cdl merge <n> |
Push agent's branch to origin | |
cdl kill [n] |
k |
Stop an agent |
cdl killall |
Stop all agents | |
cdl pick |
Interactive agent picker (for scripting) | |
cdl completions <shell> |
Generate shell completions | |
| `cdl pr <create | view | merge>` |
cdl archives |
List archived workspaces | |
cdl archive [n] |
Archive a workspace | |
cdl restore [name] |
Restore an archived workspace | |
cdl open [n] |
Open a worktree in an editor | |
cdl add-dir [n] <path> |
Attach extra repo/dir into a worktree | |
cdl mcp <list|info> |
Manage MCP server presets | |
cdl-ui |
Launch TUI dashboard |
Note: Commands without
[n]argument will show an interactive picker (requiresfzfor falls back to numbered menu).
--json, -j # Output in JSON format (for scripting)
--follow, -f # Follow logs (like tail -f)
--cleanup, -c # Remove worktree when killing agent
--tool <name> # Use external diff tool (delta, difftastic, etc.)# Create a PR for an agent branch (auto-fill from commits)
cdl pr create 1 --fill
# Create as draft, target a base branch, and open in browser
cdl pr create 1 --base main --draft --web
# View PR details or open in browser
cdl pr view 1
cdl pr view 1 --web
# Merge a PR (squash by default if you pass --squash)
cdl pr merge 1 --squash --delete-branch# Open a worktree in your editor (defaults to $EDITOR/$VISUAL or 'code')
cdl open 1
cdl open 1 --editor nvim
# Attach extra repos/dirs into a worktree
cdl add-dir 1 /path/to/other-repo
cdl add-dir 1 /path/to/dir --name vendorCreate .cdl.json in the repo root:
{
"setup": [
"npm install",
"pip install -r requirements.txt"
]
}Then run:
cdl spawn myrepo feature-branch --run-setupcdl archive 1
cdl archives
cdl restoreNote:
cdl archiveremoves the worktree by default. Use--keep-worktreeto keep it on disk.
cdl spawn <repo> <branch> [options]
Options:
-t, --task "..." Task/prompt for the agent
-a, --agent <type> Agent type: claude (default) or codex
-y, --auto-accept Enable auto-accept mode (no permission prompts)
-n, --no-auto-accept Run in interactive/print mode (default)
-l, --label <name> Label for grouping agents
--from-pr <id|url> Create workspace from a GitHub PR
--from-branch <name> Create workspace from a branch name
--link-node-modules Symlink node_modules from the base repo
--link-venv Symlink .venv from the base repo
--copy-env Copy .env from the base repo if present
--run-setup Run setup scripts from .cdl.json
--mcp <spec> Add MCP server (name:cmd:args or http:name:url)
--mcp-preset <name> Add built-in MCP preset (filesystem, github, etc.)
--no-user-mcp Don't load MCP servers from user configNote:
--from-prrequires the GitHub CLI (gh) to be installed and authenticated.
Tip: If you omit
<repo>or<branch>, CDL will prompt you to select a repo and enter a branch name (defaults to the current branch).
# Spawn a Claude Code agent (default)
cdl spawn myrepo feature-auth --task "Implement OAuth login"
# Spawn a Codex agent
cdl spawn myrepo feature-api --agent codex --task "Create REST API"
# Mix both agents on the same repo
cdl spawn myrepo auth-claude --task "Add authentication" --agent claude
cdl spawn myrepo api-codex --task "Build API endpoints" --agent codexCDL supports Model Context Protocol (MCP) to give Claude Code agents access to external tools and data sources.
# Use built-in presets
cdl spawn myrepo feature --mcp-preset filesystem --task "Analyze project files"
cdl spawn myrepo feature --mcp-preset github --task "Review open PRs"
# Combine multiple MCP servers
cdl spawn myrepo feature --mcp-preset filesystem --mcp-preset memory --task "Remember context"
# Custom MCP servers (colon-separated format)
cdl spawn myrepo feature --mcp "myserver:npx:-y:my-mcp-server" --task "Use custom tool"
# HTTP MCP servers
cdl spawn myrepo feature --mcp "http:notion:https://mcp.notion.com/mcp" --task "Sync with Notion"
# JSON format for complex configs
cdl spawn myrepo feature --mcp '{"name":"custom","command":"node","args":["server.js"]}' --task "Task"
# Skip user config MCP servers
cdl spawn myrepo feature --no-user-mcp --mcp-preset filesystem --task "Only use filesystem"| Preset | Description |
|---|---|
filesystem |
File system access |
github |
GitHub API access |
memory |
Persistent memory for agents |
fetch |
HTTP fetch capabilities |
puppeteer |
Browser automation |
postgres |
PostgreSQL database access |
sqlite |
SQLite database access |
# List all presets
cdl mcp list
# Show preset details
cdl mcp info filesystemConfigure default MCP servers in ~/.conductor/config.toml:
[mcp_servers.filesystem]
command = "npx"
args = ["-y", "@anthropic-ai/mcp-server-filesystem", "/home/user/projects"]
[mcp_servers.custom-api]
transport = "http"
url = "https://my-mcp-server.example.com/mcp"Enable tab completion for commands, repos, and agents:
# Bash (add to ~/.bashrc)
eval "$(cdl completions bash)"
# Zsh (add to ~/.zshrc)
eval "$(cdl completions zsh)"
# Fish
cdl completions fish > ~/.config/fish/completions/cdl.fishAll list/status commands support JSON output for scripting:
# Get list of agents
cdl status --json | jq '.agents'
# Get repo names
cdl list --json | jq -r '.repos | keys[]'
# Use with fzf
cdl status --json | jq -r '.agents[] | "\(.number): \(.repo)/\(.branch)"' | fzfCreate ~/.conductor/config.toml to customize defaults:
[defaults]
auto_accept = false # Default auto-accept mode
diff_tool = "delta" # Default diff tool
notify = false # Desktop notifications
[hooks]
post_spawn = "notify-send 'CDL' 'Agent spawned'"
post_complete = "~/.local/bin/on-agent-done.sh"CDL provides a Python API for automation and integration:
| Module | Description |
|---|---|
cdl.core.mcp |
MCP server configuration and management |
cdl.core.agent_status |
Detect agent completion, waiting, error states |
cdl.core.notifications |
Desktop notifications (Linux, macOS, Termux) |
cdl.core.health_check |
Monitor CPU/memory, detect stuck agents |
cdl.core.batch |
Batch operations on multiple agents |
cdl.core.summary |
Aggregated statistics and formatting |
cdl.core.watch |
Real-time status monitoring with callbacks |
cdl.core.templates |
Reusable agent configurations |
cdl.core.session |
Save/restore agent sessions |
- Repositories are cloned to
~/.conductor/repos/ - Worktrees are created in
~/.conductor/worktrees/for isolation - Agents run Claude Code inside tmux sessions
- Config is stored in
~/.conductor/config.json
~/.conductor/
├── config.json # Tracks repos and agents
├── config.toml # User preferences (optional)
├── repos/
│ └── myrepo/ # Cloned repositories
└── worktrees/
├── myrepo-feature-1-143022/ # Isolated worktree
└── myrepo-feature-2-143045/ # Another worktree
Launch the visual dashboard to monitor all agents in real-time:
cdl-ui- Clickable agent cards - Click to select
- Action buttons - Attach, Logs, Diff, Kill, Refresh
- Keyboard shortcuts - Full keyboard navigation
- Live updates - Auto-refresh every second
- Confirmation dialogs - Safe kill with confirmation
| Key | Action |
|---|---|
1-5 |
Select agent by number |
r |
Refresh |
a |
Attach to selected agent |
l |
Show logs |
d |
Show diff |
k |
Kill agent (with confirmation) |
q / Esc |
Quit |
┌─────────────────────────────────┐┌────────────────────────────────────────────┐
│ AGENTS ││ myrepo/feature-1 | LOGS | Add authentication│
│ │├────────────────────────────────────────────┤
│ ┌─────────────────────────────┐ ││ [Attach] [Logs] [Diff] [Kill] [Refresh] │
│ │ 1 myrepo/feature-1 (clean) │ │├────────────────────────────────────────────┤
│ │ Add authentication... │ ││ │
│ └─────────────────────────────┘ ││ I'll help you implement authentication... │
│ ┌─────────────────────────────┐ ││ │
│ │ 2 myrepo/feature-2 (+3) │ ││ Let me check the existing code first... │
│ │ Create REST API... │ ││ │
│ └─────────────────────────────┘ ││ > Reading src/auth/... │
│ ││ │
└─────────────────────────────────┘└────────────────────────────────────────────┘
q Quit | r Refresh | a Attach | l Logs | d Diff | k Kill
CDL automatically detects agent states:
- RUNNING - Agent is actively working
- COMPLETED - Agent has finished its task
- WAITING_FOR_INPUT - Agent needs user input (permission prompts, questions)
- ERROR - Agent encountered an error
- IDLE - Agent is idle at shell prompt
Get notified when agents need attention:
# Enable notifications in config.toml
[notifications]
enabled = trueSupported backends:
notify-send(Linux/libnotify)osascript(macOS)termux-notification(Android/Termux)
Monitor agent resource usage and detect stuck agents:
from cdl.core.health_check import get_agent_health, HealthStatus
health = get_agent_health("conductor-agent1")
if health["status"] == HealthStatus.UNHEALTHY:
print(f"Agent stuck! CPU: {health['cpu_percent']}%")Configure thresholds in config.toml:
[health]
memory_threshold_mb = 1024 # Warn above 1GB
cpu_threshold_percent = 90 # Warn above 90%
stuck_timeout = 300 # Seconds without outputSend commands to multiple agents at once:
from cdl.core.batch import batch_send_keys, batch_approve, batch_kill_filtered
# Approve all waiting agents
batch_approve_filtered(status=AgentStatus.WAITING_FOR_INPUT)
# Kill all agents with a specific label
batch_kill_filtered(label="test")
# Send input to multiple agents
agents = [{"session": "s1"}, {"session": "s2"}]
batch_send_keys(agents, "yes\n")Monitor status changes in real-time:
from cdl.core.watch import Watcher
watcher = Watcher()
@watcher.on_completed
def handle_complete(change):
print(f"{change.session} completed!")
@watcher.on_error
def handle_error(change):
print(f"{change.session} errored!")
watcher.watch(interval=5.0) # Poll every 5 secondsUse templates for common workflows:
# Built-in templates
cdl spawn myrepo feature --template claude # Default Claude agent
cdl spawn myrepo review --template review # Code review workflow
cdl spawn myrepo tests --template test # Test writing workflow
cdl spawn myrepo docs --template docs # Documentation workflowDefine custom templates in config.toml:
[templates.my-template]
command = "claude --model opus"
agent_type = "claude"
branch_prefix = "feature/"
default_task = "Implement the requested feature"
env = { CLAUDE_MODEL = "opus" }Save and restore agent sessions for team handoff:
from cdl.core.session import capture_session, save_session, restore_session
# Save current session
state = capture_session("conductor-agent1")
save_session(state, Path("~/handoff/agent1.json"))
# Restore on another machine
state = load_session(Path("~/handoff/agent1.json"))
restore_session(state)Get aggregated statistics:
from cdl.core.summary import get_agent_stats, format_summary
stats = get_agent_stats()
print(f"Total: {stats['total']} agents")
print(f"By status: {stats['by_status']}")
print(f"By label: {stats['by_label']}")
# Formatted output
print(format_summary())When attached to an agent, press Ctrl+B then D to detach without killing it.
# Spawn 3 agents working on different features
cdl spawn myrepo auth --task "Add authentication" -n
cdl spawn myrepo api --task "Create REST API" -n
cdl spawn myrepo tests --task "Write tests" -n
# Watch them all
cdl-uicdl logs -f 1 # Like tail -f, updates every 0.5scdl diff --tool delta # Use delta for syntax highlighting
cdl diff --tool difftastic # Use difftastic for structural diff# Kill all agents with changes
cdl status --json | jq -r '.agents[] | select(.changes > 0) | .number' | xargs -I{} cdl kill {}
# Get agent count
cdl status --json | jq '.count'MIT