Skip to content

Latest commit

 

History

History
 
 

README.md

Conductor

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.

Conductor overview

Quickstart (local, two minutes)

No Telegram, Slack, or Discord required. The conductor runs as a normal agent-deck session you can interact with from the TUI or CLI.

1. Create the conductor

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>/.

2. Start it

agent-deck session start conductor-work

The tmux session spins up, Claude boots inside it, reads CLAUDE.md on its first turn, and is now alive and waiting for instructions.

3. Interact

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-work

That 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.

How it works

Under the hood, a conductor is:

  • A claude (or codex) 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 list will show it) with is_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.

Fleet topology: user phone or TUI, conductor, child sessions, with watchers on the side

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.

State files

Everything lives under ~/.local/share/agent-deck/conductor/<name>/.

CLAUDE.md

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.

POLICY.md

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.

LEARNINGS.md

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.

state.json

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.

task-log.md

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.

Lifecycle commands

# 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 host

The --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.

Making the conductor smarter

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.

Running multiple conductors

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:

  1. Independent state. Each conductor gets its own directory, never shares state with another.
  2. One bot per conductor (if using remote channels). Share-a-bot leaks messages between scopes.
  3. One state directory per bot. Every TELEGRAM_STATE_DIR is distinct. Two conductors pointing at the same state dir race each other for every inbound message.
  4. Check for poller collisions. After starting all conductors, pgrep -af 'bun.*telegram' | wc -l should equal the number of conductors with a telegram channel attached.

Worker-child poller isolation (v1.7.40+)

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.

Adding remote channels

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

Channels topology — one bot per conductor

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

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 self

This 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-heartbeat

Common gotchas

1. Plugin globally enabled by accident

Setting 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 -l

2. Token disappears across session restarts

The 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.

3. Channels field is empty in agent-deck list --json

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-work

4. Conductor profile and Claude profile don't match

If 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.

5. Sessions keep dropping into /login (401 loop)

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.

6. Children silently orphaned

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".

7. Children land in the wrong group

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.

8. Resume-from-summary picker stalls long-running conductors

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.

9. Blanket kills during debug

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.

10. Tokens in git

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.

See also

  • 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