Reusable patterns for building .bot workflows. Each pattern includes a skeleton snippet and explanation.
The simplest pattern: nodes execute sequentially.
agent step_a:
model: "${MODEL}"
input: input_schema
output: step_a_output
agent step_b:
model: "${MODEL}"
input: step_b_input
output: step_b_output
workflow pipeline:
entry: step_a
step_a -> step_b with {
data: ""
}
step_b -> done
An agent produces work, a judge evaluates it. If rejected, loop back with feedback. Bounded to prevent infinite execution.
agent worker:
model: "${MODEL}"
input: task_input
output: task_output
session: fresh
judge evaluator:
model: "${MODEL}"
input: eval_input
output: eval_output
session: fresh
workflow review_loop:
entry: worker
worker -> evaluator with {
submission: ""
}
evaluator -> done when approved
evaluator -> worker when not approved as refine_loop(5) with {
feedback: "",
history: ""
}
Key points:
as refine_loop(5) — max 5 iterationswhen condition field (approved) must be bool in eval_outputA router sends work to multiple agents in parallel. A downstream node waits for all results.
router distribute:
mode: fan_out_all
agent analyzer_a:
model: "${MODEL}"
input: analysis_input
output: analysis_output
agent analyzer_b:
model: "${MODEL}"
input: analysis_input
output: analysis_output
judge synthesizer:
model: "${MODEL}"
await: wait_all
input: synthesis_input
output: synthesis_output
workflow parallel_analysis:
entry: distribute
budget:
max_parallel_branches: 4
distribute -> analyzer_a with { data: "" }
distribute -> analyzer_b with { data: "" }
analyzer_a -> synthesizer with {
result_a: ""
}
analyzer_b -> synthesizer with {
result_b: ""
}
synthesizer -> done
Key points:
fan_out_all sends to ALL outgoing edges simultaneouslyawait: wait_all on the synthesizer makes it wait for both branchesmax_parallel_branches in budget to control concurrencysession: inherit and session: fork are forbidden on nodes with await (use fresh or artifacts_only)A router forwards to different agents based on conditions from upstream output.
router dispatch:
mode: condition
workflow conditional:
entry: classifier
classifier -> dispatch with {
is_complex: ""
}
dispatch -> simple_handler when not is_complex
dispatch -> complex_handler when is_complex
simple_handler -> done
complex_handler -> done
Note: condition mode uses when clauses on edges. The condition field must exist as a bool in the source output schema.
An LLM decides which target to route to. No when conditions on edges.
prompt router_system:
Given the input, decide which specialist to route to.
- code_agent: for code-level issues
- design_agent: for architecture/design issues
router smart_router:
mode: llm
model: "${MODEL}"
system: router_system
workflow llm_routed:
entry: smart_router
smart_router -> code_agent
smart_router -> design_agent
code_agent -> done
design_agent -> done
Key points:
when conditions (C022)multi: true to allow the LLM to select multiple targets simultaneouslyPause execution for human approval before proceeding.
human approval_gate:
input: approval_input
output: approval_output
instructions: approval_instructions
interaction: human
workflow gated:
entry: worker
worker -> approval_gate with {
submission: ""
}
approval_gate -> done when approved
approval_gate -> fail when not approved
Key points:
interaction: human (default for human nodes) always pauses for inputinteraction: llm_or_human to let an LLM auto-answer when confidentiterion resume --run-id <id> --file f.bot --answers-file answers.jsonUse backend instead of model to run the node as an external CLI agent.
agent implementer:
backend: "claude_code"
input: task_input
output: task_output
system: impl_system
user: impl_user
session: fresh
tools: [Read, Edit, Write, Bash, Glob, Grep]
tool_max_steps: 25
Key points:
backend: "claude_code" (recommended) — bypasses LLM API, uses CLI subprocess. backend: "codex" is also accepted but discouraged (compiler emits a C030 warning); prefer claude_code for tool-using agents or claw + OpenAI (model: "openai/gpt-5.4-mini") for read-only judges/reviewersinteraction (forwarding human input to the subprocess)readonly: true marks the node as non-mutating for workspace safetyUse a tool node to run shell commands directly (no LLM), combined with a judge for feedback loops.
tool run_ci:
command: "${CI_COMMAND}"
output: ci_result
judge verify:
model: "${MODEL}"
input: verify_input
output: verify_output
agent fixer:
backend: "claude_code"
input: fix_input
output: fix_output
tools: [Read, Edit, Write, Bash]
workflow ci_fix:
entry: fixer
budget:
max_iterations: 25
fixer -> run_ci
run_ci -> verify with {
results: "",
changes: ""
}
verify -> done when passed
verify -> fixer when not passed as ci_loop(5) with {
feedback: "",
ci_logs: ""
}
Fork a session to let multiple readonly agents extract information without consuming the parent session.
agent worker:
backend: "claude_code"
session: fresh
tools: [Read, Edit, Write, Bash]
router extract_router:
mode: fan_out_all
agent summarizer:
backend: "claude_code"
session: fork
readonly: true
tools: [Read, Glob, Grep]
agent commit_namer:
backend: "claude_code"
session: fork
readonly: true
tools: [Read, Bash]
workflow fork_extract:
entry: worker
worker -> extract_router
extract_router -> summarizer with {
_session_id: ""
}
extract_router -> commit_namer with {
_session_id: ""
}
summarizer -> done
commit_namer -> done
Key points:
session: fork creates a non-consuming fork of the parent sessionreadonly: true allows multiple agents to read in parallel without workspace safety conflicts_session_id is passed via with to specify which session to fork fromCycle through agents one at a time, useful for dual-model approaches.
router alternator:
mode: round_robin
agent model_a:
model: "claude-sonnet-4-20250514"
input: task_input
output: task_output
agent model_b:
model: "gpt-4o"
input: task_input
output: task_output
workflow dual_model:
entry: alternator
alternator -> model_a
alternator -> model_b
model_a -> evaluator with { result: "" }
model_b -> evaluator with { result: "" }
evaluator -> done when accepted
evaluator -> alternator when not accepted as alternate_loop(6)
Key points:
round_robin sends to one target per iteration, cycling through them