Orchestrator

View as markdown
Instructions for

The orchestrator is the agent you talk to. It runs as the project's orchestrator session (the Orchestrator view in the sidebar, ⌘O), 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: in its own session, talking to you, rather than in a workspace picking up issues. The developer, review, qa, security, and adversarial agents (and any 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:

my-project
Orchestrator⌘O
Issues⌘I
Changelog⌘L
Workspaces
hub
alphaDeveloper⌘1
bravoDeveloper⌘2
charlieIdle
deltaIdle
devbox
echoDeveloper⌘3
foxtrotDeveloper⌘4
golfIdle
Ready for review3
Cold-start cache⌘5
you/t-011
CSV import fix⌘6
you/t-018
Nightly report⌘7
you/t-024
Queued for review2
Validate webhook payloads⌘8
you/t-023
Harden token refresh⌘9
you/t-025
Zen mode off
Filter:WorkflowsWorkspacesboard 2sSearchNew issue
BACKLOG10
Rework onboarding UX
task
Audit OSS licenses
task
Draft Q3 roadmap
task
Migrate CI to arm64
task
Sunset legacy v1 API
task
Add SSO for admins
task
Prune stale flags
task
Dedupe error reports
task
Archive S3 buckets
task
Refresh brand assets
task
TO DO9
Add API ratelimit
task
Fix mobile nav
task
Wire webhook retries
task
Split OTel spans
task
Sync i18n strings
task
Paginate search API
task
Cache user sessions
task
Add health probes
task
Debounce autosave
task
IN PROGRESS4
Deploy staging env
taskyou/t-009
Wire up OAuth flow
taskyou/t-010
Backfill order index
taskyou/t-017
Trim vendor bundle
taskyou/t-022
REVIEW5
Cold-start cache
taskyou/t-011
CSV import fix
taskyou/t-018
Nightly report
taskyou/t-024
Validate webhook payloads
taskyou/t-023
Harden token refresh
taskyou/t-025
DONE6
Migrate to PG 16
task
Ship dark mode
task
Retry dead-letters
task
Redis cache /profile
task
Add audit logging
task
Fix flaky CI tests
task
CANCELED0
h/l column k/j card ↵ open ⇧h/⇧l move ⇧k/⇧j reorder drag to move or reorder s start a assign e edit E editor n new

You-are-the-scheduler

A useful contrast: a generic chatbot waits to be asked. The orchestrator does not. As soon as an issue 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 issue to it, and tell you it did. When a workspace hands off (its issue moves into the handoff category, review in the default workflow), the orchestrator's job is to give that workspace the next ready issue 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
archivedTerminal; closed without acceptance.canceled

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 issue 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 issue'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 by opening it in the TUI or the desktop app), 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. Starts the orchestrator runner as the project's shelbi-<name> session, with the project state directory (~/.shelbi/projects/<name>/) 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. Codex, by default, routes through Shelbi's native app-server bridge (shelbi __codex-orchestrator <project>): the bridge owns the Codex thread and attaches the visible TUI to it, so board events are never pasted into the session. A compatibility fallback (codex_standalone_launch, for Codex binaries without app-server mode) instead launches the configured command and flags with 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 session 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 orchestrator 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 these things on the first reply of a session (or right after shelbi reload), before answering the user:

  1. Read the board. shelbi issue list for the column membership, priorities, assigned_to, and any prefers_machine hints on each card. Each status belongs to one of the six 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 issue assigned), which machine each lives on, and which agent each is running.
  3. Start a passive watch. Run shelbi events tail --follow --project <name> in the background. It mirrors the log without claiming events, so it only tells the orchestrator when to look.
  4. Self-drain on every turn. Before each reply, on every heartbeat, and whenever the watch shows a line, run shelbi orchestrator events next --follow --max-lifetime 2s, react to the batch, then acknowledge it with shelbi orchestrator events ack <delivery-id>. This claim/ack drain is the source of truth; every event it returns is a trigger the orchestrator must consider. (The prompt forbids a background events next --follow: two consuming readers would race.)
  5. Read config-upgrade findings. shelbi config upgrade --needs-judgment --format json lists config drift that needs the orchestrator's judgment.

In practice the shipped prompt folds the first two steps into a single shelbi status --full --handoff snapshot (plus shelbi zen status and a crash-recovery scan); the commands above read the same content piecewise. After that, the in-memory snapshot is kept up to date from the event stream. The orchestrator only re-reads the board if its watch 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 issue. If the handoff status requires review tags, Shelbi's hub loads the finished branch onto a free matching review workspace on its own (or queues it until one frees; you can also load it from the sidebar). The orchestrator doesn't drive review loads.
  • workspace=<name> working -> awaiting_input → same idea, the workspace just freed up.
  • workspace=<name> pane_alive=false reason=<short> → an ordinary crash is auto-restarted by the supervisor; the orchestrator at most mentions it. Only supervision=gave-up (crash-loop) is surfaced for direction.
  • project=<name> heartbeat → no state change to react to, but the watch is awake. A good time to re-check active-category issues 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 an issue 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 every agent's instructions.md.
  • 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 orchestrator starts (or on shelbi reload).

shelbi reload runs the agent self-heal before respawning the orchestrator session. 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 board and workspace snapshot and the background watch to do its job.
  • The event drain (the Polling-only event drain section). Every orchestrator drains pending events before each user-facing reply with shelbi orchestrator events next --follow --max-lifetime 2s and acks each batch with shelbi orchestrator events ack <delivery-id> (the durable cursor lives in ~/.shelbi/projects/<name>/event-cursor and resumes automatically). The legacy shelbi orchestrator events drain and a cursor-based events.log read remain as fallbacks.
  • 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 issues 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 issues into the done category. That's your accept signal.
  • It does not auto-promote backlog → ready. Triage belongs to the user.
  • It does not itself restart a workspace whose session died. The supervisor auto-restarts an ordinary crash; the orchestrator only surfaces the case the supervisor gives up on (a crash loop) and waits for direction there.
  • 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 issues 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", and the merge bar, live in the project's zenmode.md (<config-root>/zenmode.md). You tune it by editing prose, not code. Issues routed to a review workspace always wait for a human. 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 issues to.
  • Review workspaces: the tag-routed slots the hub loads a finished branch onto on handoff.
  • 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.
  • Changelog: the record of what shipped, which the orchestrator adds to after every merge.