# Orchestrator

One agent in window 1 you talk to — it dispatches tasks to workspaces, tails the events log, and reports back. You are the priority-setter; it is the scheduler.

The 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**](/docs/concepts/agents) 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](/docs/concepts/workspaces) 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](/docs/guides/getting-started/workflows) 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](/docs/guides/getting-started/workflows#status-categories)
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](/docs/concepts/events-log#task-transitions)).

## 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](/docs/concepts/agents), 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:

1. Resolves the orchestrator agent's prompt: `_shared/preamble.md`
   prepended to `agents/orchestrator/instructions.md` (see
   [customizing the prompt](#customizing-the-prompt-per-project) below).
2. Writes the composed prompt to
   `.claude/agent-instructions.md` in the launch directory.
3. Launches the orchestrator runner in the right pane of the
   `dashboard` window, 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:

```yaml
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:

1. **Read the board.** `shelbi task list` for the column membership,
   priorities, `assigned_to`, and any `prefers_machine` hints on each
   card. Each status belongs to one of the five categories above; the
   orchestrator maps column → category from the active workflow.
2. **Read the workspace pool.** `shelbi workspace list` to see which
   workspaces are free (no `active`-category task assigned), which machine
   each lives on, and which agent each is running.
3. **Start tailing.** Launch `shelbi events tail --follow` in 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](/docs/concepts/review-workspaces), 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-check `active`-category tasks for
  stuck workspaces or missed markers. See
  [heartbeats](/docs/concepts/events-log#heartbeats) for the cadence
  config and rationale; tune it via the `heartbeat` key in
  `project.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](/docs/concepts/agents#customizing-an-agent),
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](/docs/concepts/config-modes) 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. On `shelbi reload`, Shelbi may append missing
  runner-critical sections from the shipped default, such as
  `Polling-only event drain`, so customized Codex orchestrators still
  receive required polling contracts without losing local changes. Or
  run `shelbi 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 four `instructions.md` files.
- **Shipped default:** the bundled orchestrator prompt lives in the
  `shelbi-orchestrator` crate. 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 reload`d).

`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 list` snapshot 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-based `events.log` fallback 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 renames
  `Todo` to `Ready` or splits `Review` into `Code Review` + `QA` will
  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 `handoff`
  category (`review` in the default workflow); you can authorize specific
  loops where it squash-merges and moves straight to `done` (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 `done` category.** 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](/docs/concepts/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](/docs/guides/getting-started/workflows#owners-and-agents).

## See also

- [Agents](/docs/concepts/agents) — the orchestrator is one; this is how
  its prompt, skills, and customization work.
- [Workspaces](/docs/concepts/workspaces) — what the orchestrator
  dispatches tasks to.
- [Review workspaces](/docs/concepts/review-workspaces) — the tag-routed slots
  the orchestrator loads a finished branch onto on handoff, and the queue it
  drains as they free.
- [Workflows](/docs/guides/getting-started/workflows) — the schema behind the board,
  including the full category set and the events-log annotations the
  orchestrator pattern-matches on.
- [The events log](/docs/concepts/events-log) — the orchestrator's
  live feed.
- [Zen Mode](/docs/concepts/zen-mode) — flipping the orchestrator
  from scheduler to lead.
