Orchestration Agent Fanout
Quick Answer
Prefer agent-internal fanout when one coordinator agent should decide during
its own reasoning which specialist agents to run in parallel.
The preferred shape is:
- Define reusable specialists in top-level
:agents. - Give the coordinator agent
:tools {:fanout {:agents [...]}}. - Let the coordinator call the generated
fanout_agentstool with a bounded
item list. - Let the coordinator synthesize the collected successes and failures into
the final answer.
Flow-level flow/step :fanout with {:type :agent ...} items is still valid.
Use it when :flow already knows the sibling agent calls deterministically.
Do not put full agent definitions inside fanout items. Both patterns reference
top-level agents by id.
Choose The Pattern
| Situation | Use |
|---|---|
| A coordinator agent should decide whether and how to fan out while reasoning | Agent-internal fanout tool |
The parent :flow already has a deterministic item list | flow/step :fanout |
| A specialist needs an adaptive one-at-a-time conversation with the coordinator | :tools {:agents [...]} direct delegation |
| You want parallel raw tool calls rather than specialist agents | Wrap those tool capabilities in specialist agents or packaged steps, then fan out to those agents |
| Each branch is a larger workflow or installed app boundary | flow/call-flow or :fanout :call-flow items |
Preferred: Agent-Internal Fanout
This example exposes a bounded fanout_agents tool to the coordinator. The
model chooses which allowlisted specialists to call, but the runtime enforces
the configured agent allowlist, item limit, concurrency cap, and error policy.
{:slug :repo-multi-agent-review
:name "Repo Multi-Agent Review"
:agents [{:id :orchestrator/review
:description "Coordinate specialist reviews."
:objective "Review the request and repo tree. When parallel specialist review is useful, call fanout_agents with the smallest useful item list. Then synthesize the collected successes and failures into a concise Markdown action plan."
:input-schema [:map
[:request :string]
[:repo-tree :any]]
:tools {:fanout {:agents [:review/security
:review/performance]
:max-items 5
:max-concurrency 3
:on-error :collect}}}
{:id :review/security
:description "Security reviewer."
:objective "Review the selected area for security risks."
:input-schema [:map
[:request :string]
[:repo-tree :any]
[:area :string]
[:focus {:optional true} :string]]
:output {:format :json}}
{:id :review/performance
:description "Performance reviewer."
:objective "Review the selected area for performance risks."
:input-schema [:map
[:request :string]
[:repo-tree :any]
[:area :string]
[:focus {:optional true} :string]]
:output {:format :json}}]
:flow
'(let [input (flow/input)]
(flow/step :orchestrator/review :coordinate-review
{:request (:request input)
:repo-tree (:repo-tree input)}))}
The coordinator sees a tool shaped like:
{
"items": [
{
"agent": "review/security",
"input": {
"area": "auth",
"focus": "OAuth callback and token storage"
}
},
{
"agent": "review/performance",
"input": {
"area": "database queries"
}
}
]
}
By default, each item inherits the coordinator agent's current :inputs, so
the specialist calls above also receive request and repo-tree. Set
:inherit-inputs? false when each fanout item should be fully self-contained.
The fanout tool intentionally delegates only to the configured top-level
agents; it does not expose arbitrary tool execution to the model. When a branch
needs tool access, put those tools on the specialist agent definition.
Flow-Level Named-Agent Fanout
Use flow-level fanout when deterministic orchestration should build and run
the whole batch without asking a coordinator model to choose it.
'(let [input (flow/input)
fanout (flow/step :fanout :parallel-review
{:items [{:type :agent
:agent :review/security
:input {:request (:request input)
:repo-tree (:repo-tree input)
:area "auth"}}
{:type :agent
:agent :review/performance
:input {:request (:request input)
:repo-tree (:repo-tree input)
:area "database queries"}}]
:max-concurrency 2
:on-error :collect})]
fanout)
This is also the right pattern when a deterministic :function step has
already shaped and allowlisted the item vector for the parent flow.
Guardrails
Use these defaults unless you have a specific reason not to:
- Put reusable specialist behavior in top-level
:agents. - Prefer the agent-internal fanout tool when the coordinator should choose the
branches dynamically. - Use flow-level
:fanoutwhen the branch list is deterministic. - Keep the fanout tool's
:agentsallowlist small and explicit. - Use
:on-error :collectwhen partial results are useful. - Keep
:max-itemsand:max-concurrencybelow provider and workspace limits. - Pass compact inputs or resource refs, not huge inline payloads.
- Put
:input-schemaon every specialist. - Use
:output-schemaor:output {:format :json}when downstream synthesis
depends on structured specialist output.
Result Handling
The fanout tool returns the same collected result shape as flow/step :fanout:
{:successes [{:item ... :result ...}]
:failures [{:item ... :error ...}]
:total 2
:success-count 1
:failure-count 1}
With :on-error :collect, the coordinator can decide how to handle partial
failures in its final answer.
Typical synthesis behavior:
- summarize successful findings
- list failed specialist ids or areas
- say whether the result is complete or partial
- recommend reruns only for failed units
Direct Named-Agent Calls
When you do not need parallelism, call a named agent directly by using its
qualified id as the step type:
'(flow/step :review/security :scan-auth
{:area "auth"
:repo-tree repo-tree})
Inside an agent, publish the specialist directly with :tools {:agents [...]}:
{:id :orchestrator/review
:objective "Delegate to the security specialist when needed."
:tools {:agents [:review/security]}}
Use :tools {:fanout ...} only when the coordinator may need to launch several
sibling specialists and collect them as one tool result.
Common Mistakes
| Mistake | Use Instead |
|---|---|
| Put a full agent config inside fanout items | Define it once in top-level :agents and reference it by id |
| Give the model a broad or open-ended fanout surface | Configure :tools {:fanout {:agents [...] :max-items N :max-concurrency N}} |
| Let the planner invent arbitrary agent ids | Use the configured fanout :agents allowlist |
Use flow-level :fanout when the coordinator should decide branches dynamically | Use the agent-internal fanout tool |
| Use the fanout tool for one sequential specialist conversation | Use direct :tools {:agents [...]} delegation |
| Fail the whole batch on one specialist failure | Use :on-error :collect and synthesize partial results |