A conductor is one persistent AI session whose job is to supervise all your other agent-deck sessions. Think of it as the foreman on a jobsite: you send instructions to the foreman, the foreman assigns them to workers (child sessions), and you never need to stand next to each worker — you just need to know the foreman is reachable.
No Telegram, Slack, or Discord required. The conductor runs as a normal agent-deck session you can interact with from the TUI or CLI.
agent-deck conductor setup work --description "Work fleet"This creates:
~/.local/share/agent-deck/conductor/work/
├── CLAUDE.md # per-conductor startup instructions (auto-generated)
├── POLICY.md # auto-response rules vs escalation rules
├── LEARNINGS.md # patterns that worked or failed (starts empty)
├── state.json # persistent state across compactions (conductor writes this)
└── task-log.md # append-only log of what the conductor did
It also registers a session called conductor-work in agent-deck's registry.
XDG note: The conductor directory defaults to
$XDG_DATA_HOME/agent-deck/conductor/<name>/. On most systems this resolves to~/.local/share/agent-deck/conductor/<name>/.
agent-deck session start conductor-workThe tmux session spins up, Claude boots inside it, reads CLAUDE.md on its first turn, and is now alive and waiting for instructions.
From the TUI, select conductor-work and press Enter to attach — you are now talking directly to your conductor.
From the CLI:
agent-deck session send conductor-work "status?"
agent-deck session output conductor-workThat is it.
Every other agent-deck session you run is now supervisable by this conductor.
It watches for sessions in waiting or error, auto-responds when its policy matches, and asks you when it cannot decide.
Under the hood, a conductor is:
- A
claude(orcodex) process pinned inside a named tmux session managed by agent-deck. - A directory at
~/.local/share/agent-deck/conductor/<name>/that holds its instructions, policy, learnings, state, and task log. - An agent-deck session record (
agent-deck listwill show it) withis_conductor: true. - Optionally, one or more remote channels attached (Telegram, Slack, Discord) so you can talk to it from your phone.
- Optionally, a heartbeat daemon that pings the conductor on a schedule.
The conductor reads its CLAUDE.md on every session start or resume.
It consults POLICY.md to decide what to auto-respond to and what to escalate.
It accumulates patterns in LEARNINGS.md.
Every action it takes gets appended to task-log.md.
Everything lives under ~/.local/share/agent-deck/conductor/<name>/.
The startup checklist Claude reads on every session start or resume.
Generated by conductor setup from a template, customizable with --instructions-md.
Tells the conductor what its scope is, what agents live in its group, and the reading order of the other files.
The auto-response contract.
Reads something like: "Messages that match pattern X -> auto-respond with Y.
Messages that ask for Z -> escalate to the user with NEED: ...."
The shared default is installed into the conductor base dir; --policy-md on conductor setup overrides it per conductor.
You own this file; edit it directly when you want the conductor to behave differently.
Append-only notes on what worked and what did not. The conductor is instructed to consult this at session start and write a new entry whenever a pattern stabilizes: either "this approach works reliably, keep doing it" or "this approach failed for reason X, avoid it". Over time this becomes your conductor's institutional memory.
The conductor's own working memory across Claude compactions.
Small JSON file holding summaries of each child session's state, the current top-level goal, and any handoff notes.
The conductor reads it at the start of every interaction and updates it after every action.
You almost never need to read or edit it yourself; when you do, jq over it is fine.
Append-only timeline of what the conductor did and why. Useful for audits, for catching drift, and for writing post-incident reports. Format is freeform markdown; each entry is timestamped.
# Create
agent-deck conductor setup <name> [--description "..."] [--agent claude|codex] \
[--heartbeat|--no-heartbeat] [--instructions-md path] [--policy-md path]
# Observe
agent-deck conductor list
agent-deck conductor status # all conductors
agent-deck conductor status <name> # one conductor
# Operate
agent-deck session start <name>
agent-deck session stop <name>
agent-deck session restart <name>
agent-deck session send <name> "message"
agent-deck session output <name>
# Tear down
agent-deck conductor teardown <name> # stop, keep files
agent-deck conductor teardown <name> --remove # stop and delete dir
agent-deck conductor teardown --all --remove # nuke every conductor on this hostThe --remove flag is destructive: it deletes ~/.local/share/agent-deck/conductor/<name>/.
Your LEARNINGS.md and task-log.md go with it.
Snapshot them first if they matter.
Two files in the conductor directory are worth editing regularly:
POLICY.md— rules for what the conductor should auto-answer vs escalate. The starting template includes "if a worker asks whether to run tests, say yes". Edit freely; the conductor reads it every turn.LEARNINGS.md— append-only patterns the conductor accumulates over time. After every correction you make ("no, don't do X again"), the conductor should add a learning. You can also hand-edit.
A typical setup: one conductor per logical scope. Work conductor, personal conductor, each customer, each repo, whatever axis you cut on.
| Conductor | Profile | Channel |
|---|---|---|
personal |
~/.claude |
(local only) |
work |
~/.claude-work |
@my_work_bot |
oncall |
~/.claude-work |
@my_oncall_bot |
Rules for multi-conductor hosts:
- Independent state. Each conductor gets its own directory, never shares state with another.
- One bot per conductor (if using remote channels). Share-a-bot leaks messages between scopes.
- One state directory per bot. Every
TELEGRAM_STATE_DIRis distinct. Two conductors pointing at the same state dir race each other for every inbound message. - Check for poller collisions. After starting all conductors,
pgrep -af 'bun.*telegram' | wc -lshould equal the number of conductors with a telegram channel attached.
Every agent-deck launch child receives an ephemeral scratch CLAUDE_CONFIG_DIR so the telegram plugin is never re-activated inside child sessions.
You can let your conductor spawn children liberally; the pollers stay pinned to the conductor itself.
The bridge daemon handles all conductors — one bridge process per machine multiplexes across N bots.
Remote channels let you talk to the conductor from your phone or a chat platform. They are optional — the conductor works fully without them.
- Telegram — easiest to set up, one bot per conductor, message from your phone
- Slack — channel-based control with slash commands and threaded replies
- Discord — server-based control with permission scopes
Each conductor pairs one-to-one with its own bot.
Bots cannot be shared between conductors.
The agent-deck conductor setup wizard walks you through channel pairing during creation.
Heartbeat is enabled by default for all conductors.
Pass --no-heartbeat at setup to disable it.
When enabled, agent-deck installs a systemd --user timer (Linux) or launchd agent (macOS) that pings the conductor on a schedule.
Each ping is a simple message:
[HEARTBEAT]
The first action on every heartbeat is to drain the conductor's inbox:
agent-deck inbox drain selfThis pulls any child completions that landed in the conductor's durable outbox (~/.local/share/agent-deck/inboxes/<id>.jsonl) while it was busy.
Delivery is pull, not push (#1225 / #1226): a child that finishes mid-turn commits its completion to disk rather than typing into the conductor's pane.
The conductor's synchronous Stop hook drains the same queue at each turn boundary (the busy-conductor path); the heartbeat drain is the idle-conductor fallback.
Together they guarantee no completion is missed.
The conductor then responds with either:
[STATUS] {one-line summary of current state}
when everything is on track, or:
NEED: {question or decision point}
when it is blocked and wants to escalate.
If a remote channel is attached, NEED: lines are forwarded to your chat; [STATUS] lines log to the task log and are only surfaced if you ask.
The heartbeat prevents a failure mode where a conductor appears healthy (tmux alive, Claude alive) but has silently drifted: it thinks it is waiting on you, you think it is waiting on a worker, nothing moves for hours.
To disable heartbeat for a conductor:
agent-deck conductor setup my-conductor --no-heartbeatSetting enabledPlugins."telegram@claude-plugins-official" = true in a profile's settings.json leaks a poller to every Claude session under that profile.
agent-deck warns about it (GLOBAL_ANTIPATTERN, DOUBLE_LOAD).
The plugin code needs to be installed but disabled at the profile level; per-session activation happens through the session's channels field.
setup auto-disables this flag.
Verify exactly one poller is running per active conductor:
pgrep -af "bun.*telegram" | wc -lThe token is in <conductor-dir>/.env.
agent-deck loads it via env_file in ~/.config/agent-deck/config.toml:
[conductors.work.claude]
config_dir = "~/.claude"
env_file = "~/.local/share/agent-deck/conductor/work/.env"If you renamed or moved the conductor dir, this block points at the wrong path and the bot silently never gets its token.
Do not use session set wrapper "env FOO=bar {command}" — it does not survive claude --resume.
Always use env_file.
Some upgrade paths drop the channels field on the conductor session.
Fix:
agent-deck session set conductor-work \
channels plugin:telegram@claude-plugins-official
agent-deck session restart conductor-workIf you ran conductor setup under the wrong profile, the conductor's config_dir resolution may later fail.
Pin the profile explicitly:
[conductors.work.claude]
config_dir = "~/.claude-work"Lookup precedence (highest wins):
CLAUDE_CONFIG_DIR env var -> [conductors.<name>.claude] -> [groups."conductor".claude] -> [profiles.<profile>.claude] -> [claude] global -> ~/.claude default.
Managed claude sessions share one OAuth token via symlinks.
Never run /login inside a managed session — it replaces the symlink with a copy that goes stale on the next token rotation.
agent-deck self-heals this on the next session start/restart by re-asserting the symlink. The clean habit: log in once in the canonical profile, every session inherits it.
When a conductor launches a child session, the parent linkage carries status events back.
Pass -parent "$AGENTDECK_INSTANCE_ID" explicitly on every agent-deck launch / add to make it belt-plus-suspenders.
Existing orphans can be relinked with agent-deck session set-parent <child> "$AGENTDECK_INSTANCE_ID".
Children inherit the conductor's group at creation time.
The conductor group is reserved for conductor sessions themselves; children belong in a project group.
Either pass -g <my-project-group> on the launch command, or run agent-deck group move <child> <my-project-group> after creation.
Once a conductor's claude session crosses ~250k tokens, claude --resume shows an interactive picker.
Auto-handled as of v1.7.73: agent-deck auto-confirms the default option within a few seconds.
To opt out, set [claude].auto_resume_summary = false in config.toml.
Do not pkill -f 'bun.*telegram' or tmux kill-server on a host with a live conductor.
The first destroys all pollers across every conductor; the second destroys every agent-deck tmux session.
Prefer agent-deck session restart <conductor> to restart one poller.
Never commit bot tokens. They live in the channel state directory only, chmod 600. If a token lands in git, revoke it via BotFather immediately.
- Watcher Setup — add "doorbells" so external events wake the conductor
- Watchdog — optional daemon that auto-restarts a flaky conductor
- Skills — conductors lean on skills heavily