Skip to content

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.

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.

stateDiagram-v2 [*] --> Plan: initial Plan: prompt guard Plan: mutation tools denied Plan --> Execute: approval Execute: implementation prompt Execute: write/edit/bash allowed by policy Execute --> Review: command:mode(review) Review: evidence-first output Review --> Plan: command:plan Execute --> Debug: failure hook Debug: retry and inspect failure Debug --> Execute: retry approved Execute --> [*]: task complete

This diagram is conceptual: your exact graph comes from the selected preset and any overrides in merged config.


Choose one of three config shapes:

{
"choreography": "modal"
}

Starts from a preset and merges your changes on top:

{
"choreography": {
"preset": "plan-execute",
"override": {
"states": {
"execute": {
"max_turns": 20
}
}
}
}
}

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" }
]
}
}
}
}

PresetInitial stateDescription
nonedefaultPassthrough — no gating or prompt injection
plan-executeplanRead-only planning → full execution on approval
plan-autoplanLike plan-execute but auto-advances after the first inference turn
modalcoordinatorSeven modes: code, chat, coordinator, debug, review, plan, sre; switch with /mode <name>
plan-modalplanThree modes: plan, review, execute; approval gates the execute transition

A Directive is the bundle of behavior that applies when the agent is in a given state.

FieldTypeDescription
promptstringText prepended to the system prompt while in this state
toolsToolGateControls which tools are available (see below)
modelstringOverride the default model for this state
hooksHook[]Post-execution hooks (reflection, retry, command)
max_turnsintMaximum 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.


The tools field on a directive controls tool availability:

"tools": {
"allow": ["view", "glob", "grep"],
"deny": ["write", "edit", "multiedit", "bash_mutation"],
"require_approval": ["bash"]
}
FieldBehaviour
allowOnly listed tools are available (allowlist mode)
denyListed tools are blocked entirely
require_approvalListed 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.


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" }
]
}
}
FieldDescription
useNames of shared directives to merge in (in order)
promptAdditional prompt prefix (merged after use directives)
toolsInline tool gate (merged after use directives)
modelInline model override
hooksInline hooks
max_turnsInline turn limit
transitionsOutgoing edges (see below)

A transition moves the engine from the current state to a target state when its trigger fires.

{ "to": "execute", "trigger": "approval", "label": "approve plan" }
FieldDescription
toTarget state name
triggerTrigger that fires this edge (see trigger reference below)
labelHuman-readable description shown in the TUI and /choreography output
TriggerFires when
approvalUser approves an agent action in the permission dialog
command:planUser runs /plan or the TUI “Return to Plan” action
command:modeUser runs /mode <state> where the target label matches a state name
turn:endAn inference turn completes (used for auto-advance)
errorA 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 typeDescription
reflectionTriggers the reflection runner to evaluate the tool result
retryRetries the failed operation up to max_retries times
commandRuns a shell command; result is injected back as context
ValueBehaviour
reinjectFeed the failure output back to the agent as context
abortStop the current turn and surface the error to the user
warnLog the failure and continue

{
"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" }
]
}
}
}
}
{
"choreography": {
"preset": "plan-execute",
"override": {
"states": {
"execute": {
"hooks": [
{ "type": "command", "command": "go test ./...", "on_failure": "reinject" }
]
}
}
}
}
}
{
"choreography": {
"preset": "modal",
"override": {
"states": {
"review": {
"model": "anthropic/claude-opus-4"
}
}
}
}
}

When choreography is active, two commands are available in the TUI:

CommandAction
/planFire 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.


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.


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.