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, three 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
/choreographyInspect the current choreography posture: active spec, persist health, hashed session rows, and recent bounded lifecycle telemetry

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 to inspect the live posture for the active session.

Choreography ships one shared local inspect family:

SurfaceWhat it returns
bmo config show-choreographyDetached local snapshot using the shared text renderer or canonical JSON envelope (--format=json); reports live recent-ring unavailability explicitly
/choreographyLive TUI posture for the active session
get_choreography_statusAgent-native JSON posture for the current or requested session
bmo_get_choreography_statusMCP twin of the same JSON posture

There is intentionally no dedicated choreography HTTP route. For remote, authenticated session posture export, use /v1/sessions/{id}/posture/status.


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.