Skip to content

fan out, barrier, synthesise

An orchestrator splits one research brief across three worker agents, waits for the slowest of them, and writes the answer itself. Fan-out is the easy half; the barrier — sitting still while two workers are already done — is the half that decides how long the whole thing takes.

Edit in the playground

The source — 05-ai-systems/02-multi-agent-fanout.dgm
%% An orchestrator splits one research brief across three worker agents, waits
%% for the slowest of them, and writes the answer itself. Fan-out is the easy
%% half; the barrier — sitting still while two workers are already done — is
%% the half that decides how long the whole thing takes.
%% ---
%% This example is here for one timing rule: **all actions in a step start
%% together, and steps run one after another.** The fan-out is therefore not
%% three steps, it is three `flow` actions inside a single step. Written that
%% way the reader sees what the system actually does — three requests leaving
%% at once — instead of a staircase the code never performs. Reach for `seq`
%% only when one action genuinely causes the next.
%% ---
%% The barrier step is the same rule read backwards. Two workers answer early
%% and are dimmed; the step keeps running because the third has not replied,
%% and the picture of a mostly-idle system is the honest one.
flowchart TB
  brief[Research Brief]
  orch[Orchestrator]

  subgraph workers[Worker Agents]
    w1[Sources Agent]
    w2[Filings Agent]
    w3[Risks Agent]
  end

  report[Synthesised Answer]

  brief --> orch
  orch --> w1
  orch --> w2
  orch --> w3
  orch --> report

scenario "fan out, barrier, synthesise" { speed: 1.0 }

  step brief "One brief, three questions inside it" {
    desc: "The orchestrator's first job is decomposition: turning a request into sub-tasks that do not need each other's answers. Anything that does need another's answer cannot be fanned out, and belongs in a later round."
    flow brief -> orch { label: "assess the acquisition", dur: 700ms }
    set orch { badge: "planning", state: busy }
  }

  step fanout "All three workers start at the same instant" {
    desc: "Three flows in one step, because that is what dispatch is: the requests leave together and no worker waits on another. Splitting them across three steps would draw a staircase the system never climbs."
    flow orch -> w1 { label: "find primary sources", dur: 600ms }
    flow orch -> w2 { label: "read the last four filings", dur: 600ms }
    flow orch -> w3 { label: "list the deal risks", dur: 600ms }
    set w1 { badge: "searching", state: busy }
    set w2 { badge: "reading", state: busy }
    set w3 { badge: "reasoning", state: busy }
  }

  step barrier "Two finish early and the orchestrator waits" {
    desc: "This step is the barrier. The sources and risks agents are done and dimmed, the filings agent is still reading, and the elapsed time of the round is now entirely that one worker's problem."
    flow w1 -> orch { label: "11 sources", dur: 700ms, style: response }
    flow w3 -> orch { label: "6 risks", dur: 700ms, style: response }
    %% The orchestrator's badge changes because its job has: it stopped
    %% planning the moment the requests left, and it is now only waiting.
    set orch { badge: "waiting", state: busy }
    set w1 { badge: "done", state: ok, color: "#16a34a" }
    set w3 { badge: "done", state: ok, color: "#16a34a" }
    dim w1
    dim w3
    highlight w2 { style: busy }
  }

  step straggler "The slowest worker returns" {
    desc: "A fan-out costs the maximum of its branches, never the average. Every budget written against the average is wrong the first time one worker hits a long document."
    flow w2 -> orch { label: "4 filings summarised", dur: 900ms, style: response }
    set w2 { badge: "done", state: ok, color: "#16a34a" }
    dim w1
    dim w3
  }

  step synthesise "The orchestrator reads all three answers together" {
    desc: "Focus moves to the orchestrator because the work has: the workers are idle and their partial answers now have to be reconciled into one that does not contradict itself. This is the step no worker could have done."
    focus orch
    set orch { badge: "synthesising", state: busy }
    note orch "3 partial answers\n1 contradiction to resolve"
  }

  step deliver "One answer leaves, with the disagreement noted" {
    desc: "The filings disagreed with two of the sources about the closing date, and the answer says so rather than picking a winner. A fan-out that hides its disagreements is just a slower single agent."
    unset orch
    flow orch -> report { label: "one answer, 1 caveat", dur: 800ms, style: response }
  }

one question, four turns
rag pipeline