Choreography
Choreography
Section titled “Choreography”Choreography is BMO’s state-machine layer for session behavior. Use it when you need runtime state, tool gates, prompt shaping, model overrides, or hooks to change together as a session moves between planning, review, execution, or custom phases.
Quick start
Section titled “Quick start”The examples below use JSON because the nested DSL is easier to scan that way,
but bmo.toml is the canonical config format for BMO.
{ "choreography": "plan-execute"}The agent starts in the plan state (read-only, mutation tools disabled) and advances to execute once you approve the plan.
Visual: state, triggers, and applied behavior
Section titled “Visual: state, triggers, and applied behavior”Choreography is easiest to read as a state machine. Each state can apply prompt text, tool gates, model overrides, hooks, and turn limits. Transitions are triggered by approval, slash commands, or other configured events.
This diagram is conceptual: your exact graph comes from the selected preset and any overrides in merged config.
Config shapes
Section titled “Config shapes”Choose one of three config shapes:
1. Preset name (string)
Section titled “1. Preset name (string)”{ "choreography": "modal"}2. Preset with overrides
Section titled “2. Preset with overrides”Starts from a preset and merges your changes on top:
{ "choreography": { "preset": "plan-execute", "override": { "states": { "execute": { "max_turns": 20 } } } }}3. Full inline spec
Section titled “3. Full inline spec”Define the complete state machine yourself:
{ "choreography": { "initial": "research", "directives": { "read_only": { "prompt": "Read and analyse only. Do not modify files.", "tools": { "deny": ["write", "edit", "multiedit", "bash_mutation"] } } }, "states": { "research": { "use": ["read_only"], "transitions": [ { "to": "implement", "trigger": "approval", "label": "approve plan" } ] }, "implement": { "transitions": [ { "to": "research", "trigger": "command:plan", "label": "return to planning" } ] } } }}Built-in presets
Section titled “Built-in presets”| Preset | Initial state | Description |
|---|---|---|
none | default | Passthrough — no gating or prompt injection |
plan-execute | plan | Read-only planning → full execution on approval |
plan-auto | plan | Like plan-execute but auto-advances after the first inference turn |
modal | coordinator | Seven modes: code, chat, coordinator, debug, review, plan, sre; switch with /mode <name> |
plan-modal | plan | Three modes: plan, review, execute; approval gates the execute transition |
Directives
Section titled “Directives”A Directive is the bundle of behavior that applies when the agent is in a given state.
| Field | Type | Description |
|---|---|---|
prompt | string | Text prepended to the system prompt while in this state |
tools | ToolGate | Controls which tools are available (see below) |
model | string | Override the default model for this state |
hooks | Hook[] | Post-execution hooks (reflection, retry, command) |
max_turns | int | Maximum inference turns allowed in this state (0 = unlimited) |
Directives can be defined once in the top-level directives map and referenced from any state via use:
"directives": { "strict_read_only": { "prompt": "Do not modify any files.", "tools": { "deny": ["write", "edit", "multiedit", "bash_mutation"] } }},"states": { "plan": { "use": ["strict_read_only"] }}A state can reference multiple directives — they are merged left-to-right. Inline fields on the state are merged last and take highest priority.
Tool gate
Section titled “Tool gate”The tools field on a directive controls tool availability:
"tools": { "allow": ["view", "glob", "grep"], "deny": ["write", "edit", "multiedit", "bash_mutation"], "require_approval": ["bash"]}| Field | Behaviour |
|---|---|
allow | Only listed tools are available (allowlist mode) |
deny | Listed tools are blocked entirely |
require_approval | Listed tools show a permission prompt before running |
If both allow and deny are specified, deny takes precedence within the allow set.
Selectors may be exact tool names or category selectors such as
category:write and category:command. When a category selector triggers
approval, the permission prompt shows the matched selector in its Gate row.
Blocked tools are real boundaries. If a capability is denied in the current
state, the recovery path is to transition to the smallest suitable state and
retry, not to route around the gate with a broader tool. For the agent, that
recovery path is the session_mode tool; /mode is the operator-facing TUI
alias.
Tool names match the built-in tool names documented in Tools Reference.
States
Section titled “States”Each entry in states is a node in the state machine:
"states": { "plan": { "use": ["no_mutations"], "prompt": "Extra note appended after directive prompts.", "max_turns": 5, "transitions": [ { "to": "execute", "trigger": "approval", "label": "approve plan" }, { "to": "review", "trigger": "command:mode", "label": "review" } ] }}| Field | Description |
|---|---|
use | Names of shared directives to merge in (in order) |
prompt | Additional prompt prefix (merged after use directives) |
tools | Inline tool gate (merged after use directives) |
model | Inline model override |
hooks | Inline hooks |
max_turns | Inline turn limit |
transitions | Outgoing edges (see below) |
Transitions
Section titled “Transitions”A transition moves the engine from the current state to a target state when its trigger fires.
{ "to": "execute", "trigger": "approval", "label": "approve plan" }| Field | Description |
|---|---|
to | Target state name |
trigger | Trigger that fires this edge (see trigger reference below) |
label | Human-readable description shown in the TUI and /choreography output |
Trigger reference
Section titled “Trigger reference”| Trigger | Fires when |
|---|---|
approval | User approves an agent action in the permission dialog |
command:plan | User runs /plan or the TUI “Return to Plan” action |
command:mode | User runs /mode <state> where the target label matches a state name |
turn:end | An inference turn completes (used for auto-advance) |
error | A tool execution returns an unrecoverable error |
Hooks run synchronously after each tool execution in a state.
"hooks": [ { "type": "reflection", "on_failure": "warn" }, { "type": "retry", "max_retries": 3, "on_failure": "abort" }, { "type": "command", "command": "go test ./...", "on_failure": "reinject" }]| Hook type | Description |
|---|---|
reflection | Triggers the reflection runner to evaluate the tool result |
retry | Retries the failed operation up to max_retries times |
command | Runs a shell command; result is injected back as context |
on_failure values
Section titled “on_failure values”| Value | Behaviour |
|---|---|
reinject | Feed the failure output back to the agent as context |
abort | Stop the current turn and surface the error to the user |
warn | Log the failure and continue |
Examples
Section titled “Examples”Read-only research then implement
Section titled “Read-only research then implement”{ "choreography": { "initial": "research", "directives": { "read_only": { "prompt": "Analyse the codebase only. Do not make changes.", "tools": { "deny": ["write", "edit", "multiedit", "bash_mutation"] } } }, "states": { "research": { "use": ["read_only"], "max_turns": 10, "transitions": [ { "to": "implement", "trigger": "approval", "label": "start implementation" } ] }, "implement": { "transitions": [ { "to": "research", "trigger": "command:plan", "label": "back to research" } ] } } }}Plan-execute with a post-tool test hook
Section titled “Plan-execute with a post-tool test hook”{ "choreography": { "preset": "plan-execute", "override": { "states": { "execute": { "hooks": [ { "type": "command", "command": "go test ./...", "on_failure": "reinject" } ] } } } }}Modal with a custom model for review
Section titled “Modal with a custom model for review”{ "choreography": { "preset": "modal", "override": { "states": { "review": { "model": "anthropic/claude-opus-4" } } } }}Slash commands
Section titled “Slash commands”When choreography is active, two commands are available in the TUI:
| Command | Action |
|---|---|
/plan | Fire the command:plan trigger — returns to the plan state if a transition exists |
/mode <name> | Fire the command:mode trigger toward <name> — switches to that state if a transition exists |
The current state is visible in the TUI header. The mode picker only shows
states reachable from command:mode transitions, plus the current state. For
new sessions, that list is derived from the choreography’s initial state. Use
/choreography (when available) to inspect available transitions.
State persistence
Section titled “State persistence”Choreography state is persisted to the session SQLite database. If the process restarts mid-session the agent resumes in the same state it was in when it stopped.
Relationship to session modes
Section titled “Relationship to session modes”Session modes (/mode code, /mode chat, etc.) are the user-facing surface of
the modal preset. When modal or plan-modal is active, /mode <name>
fires the command:mode trigger and transitions to that state. Custom
choreographies can expose a narrower mode surface, and the UI follows that
graph instead of always advertising the built-in modal states.
Native runs honor choreography prompt, tool-gate, hook, and model overrides.
Optional agent_sdk_go and adk_go backends receive the same prepared prompt
and session-mode context, but model selection remains backend-owned until
those runtimes expose per-run override hooks.