Orchestrator

View as markdown

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

jlong@hub — shelbi
Tasks · my-project 34 total Workflow: All ▾ Workspace: All ▾ BACKLOG (10) TO DO (9) IN PROGRESS (4) REVIEW (5) DONE (6) Rework onboarding UX Add API ratelimit Deploy staging env Cold-start cache Migrate to PG 16 app app app ⎇ shelbi/deploy… app ⎇ shelbi/cold-s… app Audit OSS licenses Fix mobile nav Wire up OAuth flow CSV import fix Ship dark mode app app app ⎇ shelbi/wire-u… app ⎇ shelbi/csv-im… app Draft Q3 roadmap Wire webhook retries Backfill order index Nightly report Retry dead-letters app app app ⎇ shelbi/backfi… app ⎇ shelbi/nightl… app Migrate CI to arm64 Split OTel spans Trim vendor bundle Validate webhook Redis cache /profile app app app ⎇ shelbi/trim-v…payloads app app ⎇ shelbi/valida… Sunset legacy v1 API Sync i18n strings Add audit logging app app Harden token refresh app app ⎇ shelbi/harden… Add SSO for admins Paginate search API Fix flaky CI tests app app app Prune stale flags Cache user sessions app app Dedupe error reports Add health probes app app Archive S3 buckets Debounce autosave app app Refresh brand assets app
h/l col j/k row open n new f filter r refresh

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:

CategoryWhat it meansDefault workflow status
backlogNot yet ready for work (triage stage).backlog
readyQueued for whoever owns it next (typically a workspace).todo
activeOwner is working on it now.in_progress
handoffOne owner finished their part; another's input is required next.review
doneTerminal; 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:

  1. Resolves the orchestrator agent's prompt: _shared/preamble.md prepended to agents/orchestrator/instructions.md (see customizing the prompt 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:

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, 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 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, 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. 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 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 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 backlogready. 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.