Skip to content

Lifecycle Hooks

BMO exposes hook planes:

  • runloop hooks run inside the BMO engine around inference and tool use
  • lifecycle hooks run around session and agent-turn envelope events
  • outcomes hooks run after arena accept, stitch, verified file merge, arena workflow step completion, and orchestrate_workflow / execute_dag terminals (advisory only)
  • options.automation.rules is the canonical in-process follow-on layer for structured automation. Use reaction-style actions (run_recipe, notify, enqueue_job) for lightweight glue and activation-style actions for typed orchestration.

Hooks are external shell callbacks. Structured automation rules are separate from hooks and stay in-process.

For the broader orchestration map, see Workflow map and Staged Workflow.

For contributors: Deeper event-graph semantics live in the repository topics reactions.md and activation-graph.md.

Hooks are shell commands. Blocking behavior depends on the specific hook event; it is not uniform across the whole surface.

[options.hooks.runloop]
[[options.hooks.runloop.pre_tool_use]]
type = "command"
command = "check-tool-policy"
timeout = 30
[[options.hooks.runloop.post_tool_use]]
type = "command"
command = "audit-log-tool"
[[options.hooks.runloop.pre_inference]]
type = "command"
command = "check-budget"
[[options.hooks.runloop.post_inference]]
type = "command"
command = "log-tokens"
[options.hooks.lifecycle]
[[options.hooks.lifecycle.session_start]]
type = "command"
command = "notify-start"
[[options.hooks.lifecycle.session_end]]
type = "command"
command = "notify-end"
[[options.hooks.lifecycle.before_agent]]
type = "command"
command = "validate-context"
[[options.hooks.lifecycle.after_agent]]
type = "command"
command = "summarize-turn"
[options.hooks.outcomes]
[[options.hooks.outcomes.arena_accepted]]
type = "command"
command = "jq . >&2"
timeout = 30
[[options.hooks.outcomes.arena_stitched]]
type = "command"
command = "jq . >&2"
[[options.hooks.outcomes.arena_merged]]
type = "command"
command = "jq . >&2"
[[options.hooks.outcomes.arena_workflow_step_completed]]
type = "command"
command = "jq . >&2"
[[options.hooks.outcomes.workflow_completed]]
type = "command"
command = "jq . >&2"
[[options.hooks.outcomes.workflow_failed]]
type = "command"
command = "jq . >&2"

Legacy flat keys such as [[options.hooks.pre_tool_use]] and [[options.hooks.before_agent]] are still accepted as compatibility aliases, but the nested runloop and lifecycle sections are canonical.

These hooks are converted into the BMO engine runloop hook chain.

HookBlockingDescription
pre_tool_useYesRuns before each tool call; exit 2 blocks the call and stderr becomes the tool result
post_tool_useNoRuns after each tool call; best-effort
pre_inferenceNoRuns before each LLM inference call; command failures are advisory
post_inferenceNoRuns after each LLM inference call; command failures are advisory

These hooks stay outside the BMO engine and wrap session or turn lifecycle.

HookBlockingDescription
session_startNoRuns when a session is created; advisory, non-blocking
session_endNoRuns when a session is deleted; best-effort
before_agentYesRuns at the start of each agent turn; exit 2 blocks the turn
after_agentNoRuns at the end of each agent turn; best-effort

Outcome hooks (arena, arena workflow, DAG tools)

Section titled “Outcome hooks (arena, arena workflow, DAG tools)”

These run after the corresponding coordinator success paths, in a background goroutine (they do not delay accept/stitch/merge in the UI). Exit code 2 does not block BMO; it is logged like any other non-zero exit.

HookBlockingDescription
arena_acceptedNoAfter a successful arena candidate accept
arena_stitchedNoAfter a successful arena prose stitch
arena_mergedNoAfter a successful verified arena file merge (arena_merge / HTTP / MCP)
arena_workflow_step_completedNoAfter an arena workflow step is marked completed
workflow_completedNoAfter orchestrate_workflow or execute_dag finishes with all nodes completed
workflow_failedNoAfter those tools finish with failures, skipped nodes, or an executor error

Stdin JSON includes schema_version (currently 1), event (e.g. arena_accepted, arena_merged, workflow_completed), session_id, and event-specific fields (user_message_id, model_key, candidate_id, assistant_message_id, candidate_ids, merge_signature, merged_paths, workflow_id, step_id, phase, workflow_run_id, workflow_tool, workflow_source, etc.). See the generated configuration reference for the full field list.

FieldRequiredDescription
typeYesAlways "command"
commandYesShell command to execute
timeoutNoTimeout in seconds (default: 30)

Hooks inherit the normal BMO process environment. BMO-specific hook context is passed on stdin as JSON.

Runloop hook input includes event-specific fields such as:

  • inference hooks: hook_event, model
  • tool hooks: hook_event, tool_name, tool_id, arguments

Lifecycle hook input includes event-specific fields such as:

  • session_start: hook_event, session_id, cwd, title
  • session_end: hook_event, session_id, cwd
  • before_agent: hook_event, session_id, cwd, prompt_preview
  • after_agent: hook_event, session_id, cwd, completed, error_preview

For team quality gates, use team_hooks instead:

[[options.team_hooks.teammate_idle]]
type = "command"
command = "validate-output"
[[options.team_hooks.task_completed]]
type = "command"
command = "run-acceptance-tests"
HookDescription
teammate_idleRuns when a teammate finishes its turn; exit 2 blocks idle and re-injects stderr
task_completedRuns when a task is marked completed; exit 2 blocks completion
  • Audit logging — record every tool call to a file or external system
  • Budget checkspre_inference hook records or warns on budget state before inference
  • Policy gatespre_tool_use hook blocks destructive operations in production environments
  • Notificationssession_start/session_end hooks send Slack or webhook messages