Orchestrator
View as markdownThe orchestrator is the agent you talk to. It lives in window 1 of the
project's tmux session (shelbi-<project>:dashboard), runs whatever
runner the project declares (typically claude), and treats the
shelbi CLI as its tool surface, the same CLI you use yourself.
It is a named agent like any other
(agents/orchestrator/), with an editable instructions.md for its system
prompt. What makes it special is only where it runs: window 1, talking to
you, rather than in a workspace picking up
tasks. The developer agent (and any reviewer roles you author) run inside
workspaces; the orchestrator runs the board.
The mental model is: you are the priority-setter and reviewer. It is the scheduler. It does not edit code, it does not accept reviews, it does not promote backlog items on its own. It turns natural-language requests into Kanban cards, picks free workspaces, and reports progress back.
The board it runs is the whole dashboard: the columns cards move through and the workspaces it dispatches them to:
You-are-the-scheduler
A useful contrast: a generic chatbot waits to be asked. The orchestrator
does not. As soon as a task lands in the ready category (todo in the
default workflow), that is the start signal. The orchestrator's job is
to find a free workspace, route the task to it, and tell you it did. When
a workspace hands off (its task moves into the handoff category,
review in the default workflow), the orchestrator's job is to give that
workspace the next ready task without waiting to be prompted.
This is what makes the loop feel continuous. You drop work into the backlog, triage what's ready, and review what comes back. Everything in between (assignment, branch setup, launch, completion detection, re-dispatch) is the orchestrator's responsibility.
Categories: the vocabulary the orchestrator reasons in
A workflow can rename statuses, split a category across multiple statuses, or drop columns the project doesn't need. To keep generic code (auto-dispatch, Zen Mode, the activity feed) working unchanged across every workflow, the orchestrator reasons in a fixed, closed set of status categories rather than literal status names:
| Category | What it means | Default workflow status |
|---|---|---|
backlog | Not yet ready for work (triage stage). | backlog |
ready | Queued for whoever owns it next (typically a workspace). | todo |
active | Owner is working on it now. | in_progress |
handoff | One owner finished their part; another's input is required next. | review |
done | Terminal; accepted by the user. | done |
The category set is fixed; status names are user-customizable per
workflow. A workflow that renames Review to QA, or splits handoff
into Code Review and QA, still triggers the same auto-dispatch and
auto-merge rules because the orchestrator matches on the category, not
the name. See the Workflows
concept page for the full schema.
The shelbi task move --to <status> CLI accepts the literal status name
from the active workflow. <status> is never a category. The category
is the semantic layer above; the orchestrator reads it from each task's
status definition (and, on the wire, from the from_category= /
to_category= tokens on every task event line; see the events
log).
How the prompt is wired
The shape of the orchestrator's responsibility (the bootstrap flow,
the category-keyed reaction rules, the dispatch contract) is encoded in
the orchestrator agent's instructions.md. Like every
agent, its rendered prompt is its
agents/_shared/preamble.md (project-wide context) followed by its own
agents/orchestrator/instructions.md. The orchestrator is the configured
runner with that prompt staged in its launch directory.
When you boot a project (shelbi orchestrate or via the TUI launcher),
ensure_dashboard() does the following:
- Resolves the orchestrator agent's prompt:
_shared/preamble.mdprepended toagents/orchestrator/instructions.md(see customizing the prompt below). - Writes the composed prompt to
.claude/agent-instructions.mdin the launch directory. - Launches the orchestrator runner in the right pane of the
dashboardwindow, with that path as its working directory.
For Claude, Shelbi adds Claude-specific launch wiring:
--append-system-prompt "$(cat .claude/agent-instructions.md)" and an
initial positional prompt that tells Claude to run the bootstrap sequence.
For Codex, Shelbi launches the configured command and flags, then adds
an initial positional prompt containing the project identity, worktree path,
rendered .claude/agent-instructions.md contents, reload handoff context,
and bootstrap request. Other non-Claude runners launch exactly as
configured, so they must support any prompt-loading flags you declare in
agent_runners.<name>.flags.
The split is: the source you edit is instructions.md;
.claude/agent-instructions.md is a rendered build artifact, not a file
you maintain by hand.
Orchestrator window setup lives in
crates/shelbi-orchestrator/src/lib.rs. ensure_dashboard(project)
resolves the agent prompt, renders it, and launches the runner in that
directory.
Choosing Codex
Declare Codex in agent_runners and select it for the orchestrator:
orchestrator:
runner: codex
agent_runners:
codex:
command: codex
flags: []orchestrator.runner must name a declared runner; otherwise project
validation fails before the dashboard launches. If your Codex CLI needs a
specific approval mode, sandbox mode, or model flag for unattended work,
put those arguments in flags. Shelbi does not translate Claude's
workspace_permissions_mode, --permission-mode, --continue, or
--append-system-prompt behavior to Codex.
Bootstrap flow on session start
The prompt instructs the orchestrator to do three things on the first
reply of a session (or right after shelbi reload), before answering
the user:
- Read the board.
shelbi task listfor the column membership, priorities,assigned_to, and anyprefers_machinehints on each card. Each status belongs to one of the five categories above; the orchestrator maps column → category from the active workflow. - Read the workspace pool.
shelbi workspace listto see which workspaces are free (noactive-category task assigned), which machine each lives on, and which agent each is running. - Start tailing. Launch
shelbi events tail --followin the background and watch it. Every emitted line is a trigger the orchestrator must consider.
After that, the in-memory snapshot is kept up to date from the event
stream. The orchestrator only re-runs workspace list / task list if
the tail process dies and it has to rebuild.
The reaction rules pattern-match the category tokens on each task
event line, not the literal <from> -> <to> status pair. That's what
keeps these rules correct under a workflow that renames or splits
statuses:
task=<id> ... to_category=ready reason=user:*→ find a free eligible workspace, dispatch.task=<id> ... to_category=handoff reason=workspace:ready-marker→ the finishing dev workspace has already closed its session, so it's already free. Find it the next ready task. If the handoff status requires review tags, also route the finished branch onto a matching workspace — firing the status's enter transition to boot a server (or leave it queued when they're all busy) so a human can run the change.worker=<name> working -> awaiting_input→ same idea, the workspace just freed up.worker=<name> pane_alive=false→ surface to the user; don't auto-restart.project=<name> heartbeat→ no state change to react to, but the watch is awake. A good time to re-checkactive-category tasks for stuck workspaces or missed markers. See heartbeats for the cadence config and rationale; tune it via theheartbeatkey inproject.yaml.
When dispatching, the orchestrator picks free eligible workspaces in the
order they're declared in the project YAML, honoring prefers_machine
hints. If a task names a machine and no workspace on that machine is free,
it stays in the ready category rather than getting routed to the wrong
host.
The full set of rules is in the default prompt, including when not to dispatch (failed retries, deduplicated misclicks, mid-conversation with the user). It's worth a read; it doubles as the spec for what the orchestrator is supposed to do.
Customizing the prompt per project
The orchestrator is a project-local agent with shipped defaults, so customizing it is the same as customizing any other agent. Edit a file:
- Per-project prompt: edit
<config-root>/agents/orchestrator/instructions.md, where<config-root>is~/.shelbi/projects/<name>/in global mode or<repo>/.shelbi/in in-repo mode. This is the orchestrator's system prompt; Shelbi seeds it with the shipped default on first load and never clobbers your edits on upgrade. Onshelbi reload, Shelbi may append missing runner-critical sections from the shipped default, such asPolling-only event drain, so customized Codex orchestrators still receive required polling contracts without losing local changes. Or runshelbi agent edit orchestrator. - Project-wide context: put anything that should apply to every agent
(repo layout, house style, the test command) in
<config-root>/agents/_shared/preamble.md. It's prepended to the orchestrator's prompt (and every other agent's), so you write it once instead of pasting it into fourinstructions.mdfiles. - Shipped default: the bundled orchestrator prompt lives in the
shelbi-orchestratorcrate. That's the right place for shipped-with-Shelbi changes; edit it and rebuild.
The composed output (preamble + instructions) is rewritten to the launch
directory's .claude/agent-instructions.md on every ensure_dashboard
call, so your edits take effect the next time the dashboard is
bootstrapped (or shelbi reloadd).
shelbi reload runs the agent self-heal before respawning the orchestrator
pane. If your customized agents/orchestrator/instructions.md is missing a
required shipped section, reload appends only that section and reports the
repair in its output; the rest of your prompt is preserved.
Prompt artifact
The source you edit is agents/orchestrator/instructions.md plus
agents/_shared/preamble.md; the generated
.claude/agent-instructions.md is a rendered artifact. Don't hand-edit
it; your changes are overwritten on the next render.
A few things worth keeping when you fork the default prompt:
- The bootstrap flow. The orchestrator needs the initial
task list/workspace listsnapshot and the tail process to do its job. - The polling-only event drain. Codex-backed and other polling-only runners
must drain pending project events before each user-facing reply, using
shelbi orchestrator events drain(the durable cursor is persisted in the project config dir and resumes automatically) or the documented cursor-basedevents.logfallback if that CLI primitive is unavailable. - The category-based reaction-rule matching. Patterns key off
to_category=<c>(ready,handoff) rather than the literal status pair, so the rules survive workflow customization. Hardcoding default- workflow status names here is a footgun. A project that later renamesTodotoReadyor splitsReviewintoCode Review+QAwill fall through the matches. - The reaction rules tied to specific reason strings (
user:*,workspace:ready-marker,orchestrator:auto-dispatch …). These are the contract the rest of Shelbi expects the orchestrator to honor. - The "you are the scheduler" framing. Without it, the agent reverts to chatbot mode and waits to be told what to do, which defeats the point.
What you might want to change per project:
- Routing rules: "always send infra tasks to charlie", "never use the hub for benchmarks".
- Merge policy: by default the orchestrator stops at the
handoffcategory (reviewin the default workflow); you can authorize specific loops where it squash-merges and moves straight todone(see the "Mark review done, merge, and push" section in the default prompt for the existing recipe). - Reporting style: the one-line activity summary is tunable.
What the orchestrator does not do
- It does not edit code. The
developer(and other workspace) agents do that. - It does not move tasks into the
donecategory. That's your accept signal. - It does not auto-promote
backlog→ready. Triage belongs to the user. - It does not restart a workspace whose pane died. It surfaces the death and waits for direction.
- It does not stop workspaces without asking.
These are guardrails encoded in the prompt itself. They're worth calling out because the temptation when watching a fast loop is to let the orchestrator do more. Don't. The boundaries are what keep the workflow legible.
Going further: Zen Mode
When you're ready to let the orchestrator initiate instead of just
scheduling, Zen Mode is the next-level
autonomy switch. It flips the agent from scheduler to lead: it
auto-promotes backlog-category tasks into ready when it judges the
work in scope, and it lands finished branches past a project-defined
confidence bar without waiting on a human reviewer. The policy that
decides "in scope" lives in the orchestrator agent's instructions.md
described above. You tune it by editing prose, not code. Whether a given
status is automated and which agent handles it is separate, and lives in
the workflow YAML.
See also
- Agents — the orchestrator is one; this is how its prompt, skills, and customization work.
- Workspaces — what the orchestrator dispatches tasks to.
- Review workspaces — the tag-routed slots the orchestrator loads a finished branch onto on handoff, and the queue it drains as they free.
- Workflows — the schema behind the board, including the full category set and the events-log annotations the orchestrator pattern-matches on.
- The events log — the orchestrator's live feed.
- Zen Mode — flipping the orchestrator from scheduler to lead.