Customize the orchestrator prompt

View as markdown

The orchestrator is your runner (claude, codex, …) booted with a system prompt that explains the board, the workspaces, and the loop. That prompt is the contract for who initiates what, how dispatch is decided, and how the agent reports back. Shelbi ships a sensible default and seeds it into every project as an editable file. (This page is about that prompt. The status pipeline issues move through is a separate thing, the YAML workflow.)

This page customizes the orchestrator's prompt, encodes three concrete tweaks, and applies them. The underlying machinery lives in the orchestrator concept page; this is the practical version.

Where the prompt lives

The orchestrator is a project-local agent with shipped defaults. Its prompt is composed from two files, both under the project's agents/ directory (~/.shelbi/projects/<name>/agents/ in the default global mode, or <repo>/.shelbi/agents/ in in-repo mode):

  • agents/orchestrator/instructions.md: the orchestrator-specific system prompt. Seeded with the shipped default by shelbi init; your edits stay put on upgrade. (Self-heal does merge a few runner-critical orchestrator sections back in if your copy is missing them, and re-adds the ## Changelog section if you delete its heading.)
  • agents/_shared/preamble.md: project-wide context prepended to every agent (developer, review, qa, orchestrator…). Put the repo layout, house style, and test command here so you write it once instead of pasting it into every agent's prompt.

Every time the orchestrator launches (at session start and on every shelbi reload), Shelbi composes preamble.md + instructions.md and writes the rendered output to ~/.shelbi/projects/<name>/.claude/agent-instructions.md, which is state, so it lives under ~/.shelbi/ in both modes. Claude receives it through --append-system-prompt; Codex gets it as part of its startup prompt.

Don't hand-edit the rendered prompt

.claude/agent-instructions.md is a rendered artifact, rewritten on every orchestrator launch, so hand-edits are silently lost. Put project-wide bits in _shared/preamble.md and orchestrator-specific bits in agents/orchestrator/instructions.md. Older versions rendered the prompt to ~/.shelbi/projects/<name>/CLAUDE.md; that file is no longer read, so move anything you added there into those two files and delete it.

Start from the default

Don't rewrite from a blank file. The default prompt encodes load-bearing contracts (bootstrap, reaction rules, reason strings) that the rest of Shelbi expects the orchestrator to honor. Open the seeded default and edit in place:

shelbi agent edit orchestrator

That opens agents/orchestrator/instructions.md (seeded by shelbi init) in $EDITOR. The file is plain markdown. Read it top to bottom before changing anything. The comments explain why each section exists.

What to keep when you fork

A handful of pieces are the contract between the orchestrator and the rest of the system. Edit them carefully, but don't delete:

  • The bootstrap flow. The first reply of every session (and after every shelbi reload):

    1. runs shelbi status --full --handoff for one snapshot of the board, workspaces, Zen mode, and any handoff note;
    2. starts a non-consuming shelbi events tail --follow --project <you> as a latency accelerator;
    3. runs the periodic self-drain, shelbi orchestrator events next --follow --max-lifetime 2s followed by shelbi orchestrator events ack <id>. This drain is the delivery guarantee: drop it and events go unprocessed;
    4. reads shelbi config upgrade --needs-judgment --format json for config findings that need a decision.

    Drop any of these and the orchestrator boots without state.

  • The reaction rules tied to specific reason strings. The orchestrator dispatches on user:* promotions, rolls workspaces on workspace:ready-marker, and wakes on heartbeat lines. The rest of Shelbi emits those strings. Your prompt has to recognize them.

  • The "you are the scheduler" framing. Without it, the agent reverts to chatbot mode and waits to be told what to do. The whole loop falls apart.

  • The ## Zen Mode section (if you intend to use Zen). It points the orchestrator at zenmode.md, which holds the judgment categories and merge conditions. Tune Zen by editing zenmode.md (tweak 2 below), not this section.

Everything else is fair game.

Three example tweaks

A prompt override pays for itself the first time the agent stops asking a question you've answered the same way a dozen times. Three patterns worth stealing.

1. Pin certain task shapes to specific workspaces

The default scheduler picks the first free workspace in YAML declaration order, skipping review-tagged workspaces and any slot you have open as a shell, and honoring each issue's prefers_machine: hint. If your project has natural routing (infra issues always to a beefy box, UI issues always somewhere with the design system pre-cached), bake it into the prompt:

## Routing rules
 
In addition to each issue's `prefers_machine:` hint, apply
these project-specific rules when picking a workspace for a `todo`
card:
 
- **Infra issues**: titles containing `infra:`, `terraform`,
  `helm`, `kubectl`, or paths under `infra/`. Always route to
  `devbox` (the beefier remote). If no `devbox` workspace is free,
  leave the card in `todo` rather than routing to the hub.
- **Docs and content**: titles containing `docs:` or paths under
  `site/content/**`. Route to the first free hub workspace; these
  are short, IO-light issues that don't need the remote.
- **Migration touch-ups**: anything matching `db/migrate/**` or
  `crates/shelbi-state/src/migrations/**` always goes to
  `charlie` (so the same agent context accumulates across the
  series). If charlie is busy, hold in `todo`.
 
These rules take precedence over default declaration-order
selection. An issue's `prefers_machine:` hint still overrides
everything if it is set.

The orchestrator now applies your routing on top of the default free-workspace scan. No code change.

2. Tune Zen's judgment categories for this project's reality

Zen's policy doesn't live in the orchestrator prompt. The judgment categories and merge conditions are in a separate user-owned file, zenmode.md (~/.shelbi/projects/<name>/zenmode.md in global mode, or <repo>/.shelbi/zenmode.md in in-repo mode). The orchestrator reads it as the source of truth whenever Zen is on, so edit that file, not instructions.md. See the zenmode.md file for its full shape.

The bundled default's judgment categories are deliberately conservative: promote if you've routinely accepted this shape, or the user just raised the issue, or it's part of a larger body of work they kicked off. That's a sensible starting bar, but every project has its own "always safe" pattern. Replace the ## Auto-promote judgment categories section of zenmode.md to encode it:

## Auto-promote judgment categories
 
Promote a backlog candidate to `todo` only if **at least one** is
true:
 
1. **Tagged `automation:` in the title.** Always in scope: these
   are bot-filed PR-bumps and lint sweeps we have years of
   acceptance history on.
2. **Touches only `docs/**` or `site/content/**`.** Content
   edits are reversible and never reach prod. If the issue body
   names a file outside those globs, fall through.
3. **The user has accepted ≥3 issues of the same shape (same
   verb-prefix, same target directory) without changes in the
   last week.** Track this against the `done` status via
   `shelbi issue list --status done`.
 
If none of the above, leave the card in `backlog`, emit
`reason=orchestrator:zen-decline reason-text=<short>`, and surface
it in the next reply ("I considered promoting `<issue>` but wasn't
sure, want me to?").

Leave the ## Merge conditions section as shipped unless you mean to change it. Keep the file's first line a one-line summary of the policy and update it when you change the rules: the hub's heartbeat re-injects that line into the orchestrator every few ticks while Zen is on.

The Rust side (shelbi zen scan) never inspects this prose. It just emits the mechanically-eligible candidates. Your policy decides which of those actually get promoted.

3. Change the reporting style

The default prompt's ## Reporting rule leads each reply with a terse one-line summary of what moved, naming issues by branch without the user prefix (palette-icons started on alpha, fetch-loop -> handoff, no ready issues for charlie). If you'd rather get a richer rundown (or a quieter one), say so:

## Reporting style
 
When dispatching, reply with:
 
- The card id and chosen workspace (always).
- The full first line of the issue body (so the user sees what
  you're about to make the workspace work on).
- An estimated complexity tag (`xs` / `s` / `m` / `l`) based on
  the body and any `depends_on`. Don't overthink it: a one-token
  guess is better than nothing.
 
When reporting a finished review:
 
- The card id, the workspace, and the branch.
- A two-bullet summary of what the diff changed (read it via
  `git diff main..<branch> --stat`).
- Whether a relevant `done`-column precedent exists for this
  shape of change.
 
Background sweeps (heartbeat-triggered re-checks) report nothing
unless they take action. Silence is fine when the board is quiet.

This is the easiest place to start customizing. The behavior the orchestrator gives you is downstream of the prose you write here. Ask for the report you actually want.

Apply the edits

1

Edit the orchestrator's instructions

shelbi agent edit orchestrator

Opens agents/orchestrator/instructions.md in $EDITOR.

2

Re-render and restart the orchestrator

shelbi reload

Restarts the orchestrator session so it relaunches with the prompt composed from your edits.

3

Verify what landed

shelbi agent show orchestrator

Prints instructions.md followed by the agent's skills/.

The composed prompt (preamble + instructions) is written to ~/.shelbi/projects/<name>/.claude/agent-instructions.md every time the orchestrator launches. Read that file to see exactly what the runner loads.

shelbi reload reloads the whole project: it restarts the daemon, reattaches open clients, and replaces the orchestrator session. Workspaces keep running. Before the old orchestrator goes away it writes a handoff note (agents/orchestrator/handoff.md) that its successor ingests and then deletes, so in-flight context carries over. The raw conversation transcript does not.

Iterate from prose, not code

The whole point of putting the orchestrator's policy in markdown is that you can keep tightening it. Two practical habits:

  • Treat surprises as prompt bugs. When the orchestrator does something you didn't expect (promotes a card you'd have held, reports in a style you find noisy, picks the wrong workspace), open agents/orchestrator/instructions.md and add the rule that would have produced the outcome you wanted. Reload. Try again.
  • Version-control the prompt. Check agents/orchestrator/ and agents/_shared/ into the repo (or a dotfiles repo) and symlink them into place. These prompts are the most opinionated artifacts in your Shelbi setup, and the ones most worth preserving across machine moves and reinstalls.

What you should not do is try to encode policy in code. The mechanical layer (the Rust CLI) is intentionally generous. shelbi zen scan will hand the orchestrator everything that's mechanically safe, and your zenmode.md picks which of those to actually run. That separation is what lets you raise or lower the bar by editing prose.

See also

  • Workflow config: the YAML field reference for the status pipeline the orchestrator runs work through.
  • Orchestrator: how the prompt template is loaded, the bootstrap flow it triggers, and the reaction rules tied to specific event reason strings.
  • Zen Mode: the high-confidence bar your custom judgment categories sit on top of.
  • The events log: the full reason string vocabulary the orchestrator emits and your override may want to recognize or extend.
  • shelbi zen: the single-purpose primitives the orchestrator sequences during auto-merge, available to call by hand any time.