The 13 Multi-Agent Orchestration Patterns, Implemented via the Reactive Reducer
These are the thirteen multi-agent orchestration patterns that keep showing up in real systems, and what each one looks like when it’s implemented as a reactive reducer instead of a pile of agents messaging each other. Each section below: the pattern, its shape as a diagram, what it’s good for, and its failure points - with a short note on what the reducer does to each one. The full treatment (FSM YAML, a walk-through, the complete failure-point analysis) lives in the article for each pattern.
Start with the series intro - why the reactive reducer, and the two ways to run it
Why the reactive reducer?
Most agent orchestration is non-deterministic by construction: the agents decide the control flow - who acts next, what gets handed off, when the task is done. That produces outcomes you can’t reproduce, user experiences that feel unreliable, and token bills that grow with the length of the conversation.
The reactive reducer removes the model from the control flow. Three rules:
contextis the single source of truth - every artifact and decision is a typed field in a shared store. The handoff is a context write, not a message.- Agents advance the machine by writing context, never by requesting a transition. An actor’s only lever is to contribute a value.
- A microstep loop fires the first eligible, guard-passing edge until the machine parks - durable, journaled, restart-safe.
The machine moves itself. Actors only ever contribute values. The payoff: the path is the declared edge set (replayable, debuggable), the user watches a machine advance through declared states (not agents guessing), and token cost is bounded by the design (a verdict enum, maxIterations, a retry budget read off the data itself) rather than by how well the models behaved. The same FSM runs in a small interactive runner or compiled to Mutiny / Flink for high-volume orchestration at scale - the coordination logic is separate from the execution substrate.
The full argument, with the two execution targets, is in the series intro.
1. Sequential Pipeline
Sequential Pipeline. Each agent owns a distinct stage of the workflow and passes its output forward as the input to the next stage. Control moves in a predictable, usually linear direction, making the pattern easy to reason about and observe. Agents can have different roles, tools, models, or context windows, but each is expected to complete its responsibility before the workflow advances.
Good for: work with a natural ordering; research → design → implementation → review; ETL-like processing; document generation; multi-stage transformation pipelines.
Failure points - and what the reducer does to them:
- Early mistakes compound downstream. The pipeline makes it structural: every stage’s output is a named context field, so a bad value is traceable to the exact stage that wrote it. The review back-edge is the containment mechanism - a
changesverdict routes work back with the notes preserved, so the rework agent gets the actual feedback, not a paraphrase of a paraphrase. - Context degrades during handoffs. There is no handoff message. The next state’s objective interpolates the full value of the field the previous stage wrote, and every turn the runtime re-injects the whole set context store. The handoff is the artifact itself, not a lossy summary.
- Slowest agent determines latency. True - and now it’s visible. Each stage is a park; the machine is literally at the slow stage, and the parked record is journaled, so a restart resumes the same stage with the same context.
- Recovery may require replaying earlier stages. A parked machine is a replay point. The
(state, context)pair is the complete run, so recovery is “re-summon the agent at the current state.” And a bad stage doesn’t replay the whole pipeline - the back-edge consumes the trigger and re-parks at exactly the stage that needs to redo its work.
2. Parallel Fan-Out / Fan-In
Parallel Fan-Out / Fan-In. A coordinator decomposes a larger task into independent or mostly independent subtasks and dispatches them to several agents at the same time. Once enough workers finish, their outputs are collected by an aggregator, reducer, or synthesizer that combines them into a single result. The pattern trades coordination complexity for lower latency and broader coverage.
Good for: research from multiple sources; independent analysis; generating alternatives; broad search or comparison tasks; large decomposable workloads.
Failure points - and what the reducer does to them:
- Bad decomposition creates overlapping or missing work. The decomposition is a staged, inspectable context field produced by a named state - you can validate it, gate the fan-out on a review of it, or route it back to
decomposewith a back-edge. Overlap and gaps are properties of a document you can read, not emergent behavior. - Outputs can be inconsistent or hard to combine. Every worker writes into the same typed field - a
mapkeyed by whoever wrote it - so the outputs are coerced to one shape at the write instead of described alike in N prompts. The synthesizer’s state gets the whole map side by side, so “the docs and the API disagree” is a comparison of two entries, not an act of reassembly. - Aggregator becomes the bottleneck. True - and now it’s one state you can see.
mergeis where the machine rests while synthesis runs; the parked record shows exactly where the wall-clock is going. If synthesis is parallelizable, that’s just another fan-out state. The pattern composes with itself. - One slow worker can hold up the join. The machine doesn’t time out or busy-wait - it parks, and parking is durable. And if you don’t want to wait for all of them, you don’t: the join is a guard, so you write the policy you actually want -
size(context.findings) >= 2for two-of-three, an||fast-path, a filtered count for a conditional quorum. Three workers or thirty, the expression is the same shape and only the number changes.
3. Supervisor / Router
Supervisor / Router. A central supervisor retains control of the workflow and decides which specialist agent should act next based on the current task and state. Specialists typically return their results to the supervisor rather than directly transferring control to one another. The supervisor may route multiple times, request revisions, call several agents, and ultimately synthesize the final answer.
Good for: tool-rich assistants; specialist agent teams; customer support systems; coding agents; systems where centralized control is desirable.
Failure points - and what the reducer does to them:
- Supervisor becomes a bottleneck and single point of failure. The bottleneck is now visible - every routing decision is a park, journaled, so a crash resumes at the hub with the full store intact. And the supervisor’s only lever is a typed enum, so it can’t do the work itself or route out of order. Its failure mode is “picks the wrong value” - a routing-quality problem you can log and fix, not a control-flow corruption.
- Bad routing poisons the whole task. A bad route is bounded and legible.
routeis an enum, so a malformed or out-of-vocabulary write fails coercion - it can’t silently misroute. And because the hub re-parks after every specialist, a bad first route is recoverable: the supervisor sees the note and re-routes. - Supervisor can consume excessive context. This is where the design pays off most. The supervisor’s per-turn injection is the capped CONTEXT block plus its own objective; the full specialist notes live in the store and are templated in full only into the state that needs them. Context cost is roughly constant in the number of specialists, not the sum of their outputs.
- Specialists may become overly dependent on the supervisor’s interpretation. Each specialist’s objective is templated from the staged fields - the request gist, the running record - not from the supervisor’s free-form paraphrase. What the specialist writes is what the next state reads.
- Routing to the same specialist twice does nothing. Not on the original list, and the trap you’ll actually hit. A requirement is satisfied by presence, so if a specialist’s report field still holds its last report, re-entering that state satisfies it instantly and bounces back to the hub with no work done - a hop in the trace and nothing in it. The discipline: the report is a shared scalar consumed on the way out, while the record accumulates separately in a
listanexitaction appends to. Accumulate what you want to keep; consume what re-arms the loop.
4. Hierarchical Manager / Worker
Hierarchical Manager / Worker. Coordination is split across multiple levels rather than handled by one global supervisor. A top-level manager decomposes broad objectives into workstreams and delegates them to lower-level managers, which in turn coordinate their own specialist agents. This mirrors organizational hierarchies and allows orchestration responsibilities, context, and decision-making to be scoped to smaller subproblems.
Good for: large tasks; software-delivery organizations; multi-domain problems; systems with many specialized agents; large agent organizations with scoped responsibility.
Failure points - and what the reducer does to them:
- Too many management layers. The hierarchy is declared, so depth is a design decision you make and can count, not an emergent property of agents deciding to delegate. Each level is a separate YAML definition; adding a layer is a code review, not a runtime surprise. And you can inline a level into the parent when the workstream is small.
- Context gets distorted as it moves through the hierarchy. This is the failure the design attacks hardest. The handoff between levels is a typed context field - the brief goes down as a write addressed to the child’s alias, and the child’s whole context comes up into a parent
mapfield when it finishes (spawn: X, alias: Y, into: Z). No level re-summarizes another’s working context; the parent reads the child’s output as data, not the child’s transcript. Distortion is bounded to two explicit boundaries: brief in, context out. - Coordination becomes expensive. The director’s coordination cost is constant in the size of a workstream. It parks at
awaitand evaluates two map lookups; it does not poll or track the managers’ internal progress, and it is woken only by a child completing, because publishing the result is what re-evaluates the join. The expensive work happens inside the child, in the child’s context, at the child’s cost. - Recursive delegation can explode agent count. Two structural brakes: a child is a registered definition (delegation is to a known machine, not “spawn whatever”), and the same policy tools apply at every level -
maxIterationsbounds any loop inside a child, and a stuck child just keeps the parent parked, visibly, atawait.
5. Peer Handoff / Swarm
Peer Handoff / Swarm. Control moves dynamically between peer agents through explicit handoffs. The active agent determines which peer is best suited to continue based on the current conversation, state, or emerging requirements, then transfers responsibility directly. Unlike a supervisor model, no single agent necessarily sees or controls the entire execution path, so the workflow can evolve organically as the task unfolds.
Good for: dynamic workflows; conversational systems; loosely coupled specialists; tasks where the correct path is not known ahead of time; adaptive workflows that change as information emerges.
Failure points - and what the reducer does to them:
- Infinite handoff loops. The reducer converts this from a trace-forensics problem into a declared one. Two real backstops: a
stuckstate is a declared escape valve any peer can route into, andpolicy.maxIterationscaps the total exchange count. Worth being precise about a third that is often over-credited - the reducer’s no-progress guard stops oscillation within a singleadvance()call, which keeps the microstep loop hazard-free but does not span turns, so it won’t catch a swarm bouncing across separate agent turns. The loop still can’t happen silently: if it’s about to, the machine is visibly atstuckor has tripped the cap. (No hop counter in context - the handoff record is alist, sosize(context.trail)is the hop count, derived rather than maintained.) - Unclear ownership. At any instant the machine is in exactly one state, and that state’s
canTransitionnames the acting agent. The parked record answers “who’s driving right now,” and the moment of ownership transfer is a journaled event - the edge that fired, the context that triggered it, the state that received it. - Agents bounce work around instead of completing it. The bounce is still possible - the pattern wants dynamic routing - but it’s bounded and legible. The swarm either converges to
finish, escalates tostuck, or trips the policy cap. “Bounce forever” isn’t a legal run. - Execution paths become hard to predict. They’re still not predictable - that’s the point - but they’re enumerable and replayable. The legal topology is the declared edge set, and a finished run is a sequence of
(state, context-write, edge-fired)records: a complete replay of the path, without reconstructing it from model outputs. - Debugging and replay can be difficult. The run is the debug artifact, and the answer is one field. Each state’s
exitappends its hop and the peer’s stated reason to atraillist before blanking the handoff trigger, so “why did the swarm go triage → billing → technical → stuck?” is a read, not a reconstruction - and it doesn’t depend on whichever peer happened to write last, because nothing is overwritten.
6. Debate / Consensus
Debate / Consensus. Several agents examine the same problem from independent perspectives rather than dividing it into separate tasks. They may produce competing answers, critique one another’s reasoning, revise their positions over several rounds, and expose assumptions or weaknesses. A judge, voting rule, scoring mechanism, or synthesis agent then determines the final outcome.
Good for: ambiguous reasoning; architecture decisions; hypothesis evaluation; adversarial review; reducing individual-agent blind spots.
Failure points - and what the reducer does to them:
- Agents may share the same blind spot. No orchestration pattern fixes this - three models from the same family share the same blind spots. What the design does is make the blind spot visible and attributable: positions and critiques live in
mapfields keyed by their author, so “all three missed X” is a comparison across entries rather than a hunt through a transcript, and it can name the debater that has agreed with everyone for four rounds. The fix is model diversity in the roster, not a different loop shape. - Apparent consensus can reinforce a common error. The machine can’t tell you the consensus is right, but it can tell you how it formed. The full position/critique/judge-note trail is in the store, so a consensus that formed without any critique landing looks structurally different from one where the judge ran two rounds to resolve a real objection. The
judge_notefield is the tell. - Debates can run indefinitely. Structurally bounded here: the judge’s
verdictis an enum (consensusends,continuecosts a round), andpolicy.maxIterationscaps the total exchange count. The debate either converges or trips a bound you can read in the YAML. The inverse failure is the sneakier one - a loop that doesn’t run, because the round’s critiques were never cleared and the second round’s requirements were satisfied by the first round’s entries. Emptyingcritiqueson the loop-back is what re-arms the asks; keepingpositionsis what stops round 3 arguing with a paraphrase. - Judge quality becomes critical. The judge is a named, scoped role with a typed output - it can only write
verdict(an enum) andjudge_note(a string). It can’t declare victory by calling a tool or route the debate anywhere other thanfinalizeorcritique. A weak judge produces a weakjudge_note, which is legible in the store, and you can swap the judge agent without touching the machine. - Token cost grows rapidly. The cost is exactly (debaters × rounds × positions+critiques) + judge calls. Rounds are bounded by the enum and
maxIterations, and because each debater’s objective is templated from staged fields rather than a growing chat transcript, each round’s context cost is roughly constant. A group-chat debate’s prompt grows with every message; this one grows with the number of fields.
7. Blackboard / Shared-State
Blackboard / Shared-State. Agents coordinate through a shared workspace rather than through tightly controlled point-to-point handoffs. Each agent can inspect relevant portions of the shared state, contribute new facts or artifacts, update hypotheses, claim work, or react to changes made by others. The blackboard becomes the durable coordination surface, allowing agents to operate asynchronously and with relatively loose coupling.
Good for: long-running tasks; asynchronous agents; complex problem solving; shared memory; multi-agent planning; systems where agents may join or leave dynamically.
Failure points - and what the reducer does to them:
- A bad write can contaminate every agent. The board is typed, so contamination is bounded. Each field has a declared type (an enum rejects out-of-vocabulary values at coercion); a fact field can carry a
validatepredicate; and big artifacts get declaredbloband claim-checked. A bad write is still a bad write, but it’s a typed, validated, provenanced bad write, visible in the store where you can find and correct it. - Concurrent updates can conflict. The reducer serializes them. Each contribution is a discrete write, and the machine processes writes one at a time: a write merges, the reducer re-runs to a stable configuration, and the next write is applied on top. There’s no lost-update or read-modify-write race, because the store has a single writer at a time - the reducer. The analysts run concurrently, but their writes are ordered.
- Stale reads cause incorrect decisions. An agent never reads a stale board. Every turn the runtime injects the current CONTEXT block into the agent’s guidance, and because a write re-runs the reducer before the next agent is re-engaged, the ordering is write → re-run → next agent sees the updated store. There’s no window where an agent acts on a board that’s already changed.
- Provenance becomes essential. Provenance is structural, and taken from the actual writer rather than from what the author declared. Facts accumulate in a
list; positions live in amapwhose entries are keyed by whoever wrote them, and the runtime forces that key - so a requirement namingfrom: { agent: db_expert }is satisfied only by that agent’s entry.hypotheses['agent:db_expert']is a lookup, not a log grep, and it’s also what makes the join trustworthy. - Shared state can become an uncontrolled context dump. The board is schema’d. The
context:block declares every field the board can hold, with its type - andof:types a collection’s elements too. An agent can’t write an undeclared field. The CONTEXT block is capped per value, big values get claim-checked, and_-prefixed fields are internal (hidden from the agents’ view). The board is a data model, not a scratchpad - one level deep, deliberately.
8. Planner / Executor
Planner / Executor. One agent is responsible for turning an objective into a plan, sequence of steps, or task graph, while one or more executor agents perform the actual work. Results from execution can be returned to the planner so that the remaining plan can be revised as new information is discovered. This separates strategic decomposition from tactical execution.
Good for: open-ended objectives; coding and implementation tasks; research workflows; tasks whose solution path cannot be predetermined; long-running work that benefits from re-planning.
Failure points - and what the reducer does to them:
- Bad plans cascade into bad execution. A bad plan still cascades - but the cascade is now interruptible. The executor’s
outcomeenum is the tripwire: the moment execution reveals the plan was wrong, the executor writesreplan, and the machine routes back to the planner with the actual result in hand. The cascade stops at the first step where reality disagreed with the assumption - and because each cycle’s directive and finding are appended to ahistorylist on the way out, the planner revises against everything that’s happened, not just the last thing said. - Planner over-decomposes simple work. Over-decomposition is now visible and cheap to fix. The plan is a staged, inspectable field - you can read the roadmap before the executor walks a single step. And the fix is structural: the planner can issue a single directive that walks the whole thing and report
donein one execute. - Execution reveals assumptions that invalidate the plan. This is the pattern’s core case, and it’s the
replanedge doing exactly what it was drawn for. The invalidation is a first-class event - a context write that fires a declared edge - not a side effect of the executor mentioning it in a comment. The plan and the reality are reconciled by the machine, at a specific state, with the evidence in the store. - Re-planning can become an endless loop. Bounded two ways: the
outcomeenum has three values (doneandabortexit, onlyreplancontinues), andpolicy.maxIterationscaps the total exchange count. The no-progress guard is often over-credited as a third - it stops oscillation within a singleadvance()call, not across turns.size(context.history)is the cycle count if you want a harder in-machine cap, which is a guard rather than a new field. - The loop that runs and does nothing. Not on the original list, and the one that actually bites. The
replanedge has to consume all three triggers -directive,outcomeandresult- and each survivor breaks it differently: a survivingoutcomere-fires the edge the instantexecuteis re-entered (bouncing back without ever summoning the executor), a survivingresultsatisfies the executor’s requirement with last cycle’s finding, and a survivingdirectivemeans the planner’s new instruction is never waited for. The evidence survives elsewhere, in an accumulated list. Consume what re-arms; accumulate what you need to keep. - Planner and executor can disagree about task completion. The executor is the one who did the work, and the executor is the one who writes
outcome. The planner can’t declare the task done - it can only react to the executor’soutcome. If they disagree, the planner’s move is to issue another directive (a verification step), not to override the executor’s report. Completion is the executor’s typed claim.
9. Generator / Critic / Refiner
Generator / Critic / Refiner. One agent produces an artifact or proposed answer, another explicitly evaluates it against criteria, and the original agent or a separate refiner improves it using the critique. The cycle can repeat until a quality threshold, acceptance condition, or iteration budget is reached. Roles are intentionally asymmetric: one produces, one evaluates, and one improves.
Good for: code generation; writing and document creation; architecture reviews; compliance checks; structured-output quality; tasks with explicit acceptance criteria.
Note what isn’t in that diagram: a separate refine state. The generator and the refiner are the same agent doing the same thing, and the only difference between pass 1 and pass 3 is the history the objective hands them - so it’s draft, entered again. Which works because nothing is cleared: drafts and critiques accumulate in list fields, and freshness becomes a size comparison (size(drafts) > size(verdicts)) rather than a presence test. That’s the one thing to internalise if you take a loop shape from this series - when you stop destroying state to re-arm a loop, every test becomes a comparison, and it has to be on both sides of the loop.
Failure points - and what the reducer does to them:
- Critic invents problems that are not material. The machine can’t stop a nitpicking critic - that’s a quality-of-the-critic problem. But it can make the nitpicks legible and bounded: every critique the critic has ever written is in the store, not just the last one, so a post-hoc read shows whether the findings were grounded and whether they kept moving. And the loop is bounded regardless of how much the critic finds.
- Refinement loops can continue indefinitely. Bounded, and the bound lives in the data:
size(context.drafts) < 4is the retry budget, read straight off the accumulated history - noattemptscounter to increment, and so no way for a counter to disagree with what it’s counting. On top of that the verdict is an enum whereacceptexits, andpolicy.maxIterationscaps the total exchange count. - Quality can plateau despite additional iterations. This is the one the pattern names explicitly, and the design gives it a first-class exit: a declared
stalledstate the budget edge routes into, which ships the best draft with a note on what’s unresolved rather than burning the remaining cycles. The critic can also see the plateau - its objective carries its own prior critiques, so “I said this two rounds ago and it still isn’t fixed” is a fact on the page rather than something it’s trusted to remember across turns it doesn’t share. - Generator may optimize for the critic rather than the real objective. The generator’s objective is templated on the spec, not just on the critiques. The critiques are the how; the spec is the what. The spec is in the objective on every pass, and the critic grades against it too, so both are anchored to the same criteria.
- Weak criteria produce weak criticism. The spec is a staged, inspectable field - the acceptance criteria are a document you can read before the loop starts. Weak criteria are visible in the first state’s context, not discovered after a loop of weak criticism. And the fix is structural: strengthen the spec field, and both the critic and the generator are re-anchored.
10. Auction / Contract-Net
Auction / Contract-Net. A task is advertised to a pool of candidate agents, which compete or bid for the work based on factors such as capability, confidence, cost, latency, availability, model quality, or tool access. An orchestrator evaluates the bids and awards the task to the most appropriate candidate. This makes assignment itself a dynamic optimization problem rather than a static routing rule.
Good for: large heterogeneous agent pools; cost optimization; dynamic resource allocation; model routing; systems where agents have different capabilities or availability.
Failure points - and what the reducer does to them:
- Agents may misrepresent or poorly calibrate confidence. The machine can’t stop over-bidding - that’s a calibration problem. But it makes the bids inspectable, attributable, and comparable: bids land in one
mapfield keyed by whoever wrote them, confidence is amapof an enum so every candidate draws from the same vocabulary, andrationalerecords why the orchestrator picked what it picked. A mis-calibrated bid that won is visible in the store, pinned to its author. - Bidding introduces coordination overhead. True - the auction is a fan-out with a join, and that costs a round of concurrency. But the overhead is bounded and visible: the
biddingstate parks until all bids are in. And if the overhead isn’t worth it for a given task, you don’t run the auction - you route statically (the supervisor pattern). - Poor scoring functions select the wrong agent. The scoring function is declared, not emergent. The
awardstate’s objective says score the bids against the task’s constraints - and the constraints are a staged field the orchestrator reads. You can read the scoring policy before the run, and a wrong award is legible: you can see which constraints the orchestrator weighed and how. How much of the weighing lives in the guard versus the prompt is a dial: with confidence typed as an enum,confidence['agent:premium'] == 'high'is a perfectly good pure predicate. - Cheapest or fastest agent is not necessarily the best. The orchestrator scores all bid dimensions against the task’s stated constraints, not just cost. A cheap bid on a quality-weighted task loses to a high-confidence bid, because the scoring is against the constraints, not the price tag. The cheapest-fastest-not-best failure is a scoring failure, and the scoring is declared and inspectable.
- Bids can become difficult to compare across heterogeneous agents. This is the one the design answers most directly. The bids land in one field with one element type - not in differently-named fields that merely happen to be described alike in a prompt - so every contribution is coerced to the same shape at the write and the orchestrator reads a single comparable table instead of assembling one. The contract-net idea made literal: the contract is the field’s type, and every candidate signs the same one.
- Assignment is dynamic, so the topology shouldn’t be static. The award is the routing: one
executestate whose delegate resolves from{{ context.winner }}at entry, rather than arun_*state and an edge per candidate. Adding a fourth bidder adds a value to an enum, not three lines of graph.
11. Event-Driven / Publish-Subscribe
Event-Driven / Publish-Subscribe. Agents subscribe to event types and react independently when relevant events are emitted. Instead of one coordinator explicitly directing every transition, work is triggered by changes in the environment or by events produced by other agents. The overall workflow emerges from subscriptions, event contracts, and reactions, making the pattern naturally asynchronous and loosely coupled.
Good for: long-running systems; asynchronous workflows; distributed agent systems; monitoring and reactive automation; enterprise event architectures; systems where agents should act only when relevant changes occur.
One caveat before the failure points, because it’s the pattern itself and not a detail: the reducer is a real event system internally - every claim below is about that and holds. Externally it isn’t one yet. Nothing taps a stream (the “watcher” is an agent turn), and emit/send, while real DSL the reducer executes, currently only write a log line - no bus, no webhook, on either the interactive runtime or the headless targets. Read the emits as a declared publish contract with the transport unbuilt.
Failure points - and what the reducer does to them:
- Event storms. The reducer settles - it runs to a stable configuration after every event, and the microstep cap bounds the reactions per event. A storm of external events is bounded by
policy.maxIterationson the watch/react cycle. The system can’t storm internally - the loop is bounded - and external storms are a watcher-throughput problem you can see in the parked record. - Duplicate processing. The consume-the-event discipline is the dedup, and it has two halves. The loop-back edge clears the event scalars, re-arming the routing; it also empties the writer-keyed
reactionsmap, re-arming the asks. Skip the second and the next cycle advances without summoning anyone, silently re-using the last response as if it were new - nothing that gates a cycle should survive the cycle. Beyond that, a write re-runs the reducer to a stable configuration before the next write applies, so there’s no window where one event is processed twice. - Race conditions. The reducer serializes them. Every event is a discrete context write, and the machine processes writes one at a time. The reactive agents run concurrently, but their writes are ordered by the machine. There’s no lost-update or read-modify-write race - “race conditions” is a concurrency problem; here it’s a queue.
- Unclear causality. Causality is structural. Every reaction is a fired edge with a named trigger, and each reactor’s contribution is keyed by the runtime to whoever actually wrote it. To answer “why did the scaler scale?” you read the edge that fired, the context that triggered it, and the entry under the scaler’s own key. The causal chain is the edge sequence plus the writer-keyed map - journaled, replayable, and attributed by the runtime rather than by convention.
- Difficult global reasoning. The global state is the store, and it’s typed and inspectable. At any moment the machine is at one state, the store holds the current event and the reactions so far, and the parked record shows what’s pending. You don’t correlate a distributed trace to understand the global state - you read the store.
- Accidental feedback loops. The reducer makes loops declared, not accidental. If a reaction is supposed to feed back into the watcher, it’s an edge you drew, with a consume-the-event
onTransitionand amaxIterationsbound. An undeclared feedback loop can’t happen, because the reducer only fires declared edges. (Note the absent external legs are currently an accidental safety property here: anemitthat reached a bus that fed a watcher would be exactly the cross-process cycle nothing in the machine can see.)
12. Quorum / Voting
Quorum / Voting. Multiple agents independently evaluate the same question, classification, or decision, and their outputs are aggregated using a voting or quorum rule. The agents may never communicate with one another; independence is often desirable because the goal is to reduce reliance on any single model or reasoning path. Votes can be simple majority decisions or weighted by confidence, expertise, model quality, or historical performance.
Good for: high-confidence decisions; classification; validation; reducing single-model variance; safety or quality gates; ensemble-style reasoning.
Failure points - and what the reducer does to them:
- Correlated agents produce correlated errors. The reducer can’t fix correlation - if all three voters are the same model with the same blind spot, the majority will be confidently wrong. But it makes the correlation visible and countable: the votes sit in one
mapkeyed by voter, so identical votes at identical confidence are one lookup away, and a guard can act on it. The fan-out is heterogeneity-ready - the voters can be three different models or prompts, all summoned into the samevotingstate. Independence is structural; diversity is your design choice. - Majority agreement does not guarantee correctness. A 2/3 majority is a confidence signal, not a correctness proof - the reducer doesn’t claim otherwise. What it does is make the majority exact and inspectable: the quorum guard counts (
size(votes.filter(k, votes[k] == 'approve')) >= 2) rather than enumerating winning combinations, so the rule reads as the rule and a five-voter panel is a different number, not ten more disjuncts. A wrong majority is legible in the store. - Duplicate inference increases cost. Quorum is duplicated inference, by design - the price of reduced reliance on a single model. The cost is bounded and visible: the
votingstate parks until all votes are in, and because the voters run concurrently, the wall-clock cost is the slowest voter, not the sum. If the cost isn’t worth it, you run a single reviewer. - Confidence scores may be poorly calibrated. Confidence is a
mapofintegerkeyed by voter, so an inflated claim is attributable to whoever made it - the precondition for recalibrating anything. And because the values really are integers, a weighted quorum is a guard rather than a prompt. Mind the typing: there is nonumbertype, and a field declared with one lands as a string, whereupon the arithmetic guard errors and an errored guard simply doesn’t fire. - Weak or homogeneous agent diversity limits the value of voting. The value of a quorum is only as good as the independence of the voters, and the reducer’s contribution is that the independence is structural: the voters are summoned concurrently on the same snapshot, with no access to each other’s votes. Homogeneous voters are a design choice you can see in the YAML - three requirements naming three agents that are the same model is a correlated quorum, written down.
- Voting twice, or on someone else’s behalf. Not on the original list, but it’s what a single shared ballot field would invite. Map entries are keyed by the runtime from the actual writer, and an explicit key is rejected on a field a named requirement is gathering - so the join and the attribution are the same mechanism, and neither is a convention the voters are trusted to follow.
13. Dynamic Team Formation
Dynamic Team Formation. Instead of assigning work to a fixed group of agents, the system assembles a temporary team based on the needs of the current task. Agents can be discovered from a registry, selected by capability, recruited for specific phases, replaced when they underperform, and released when their expertise is no longer required. Team composition can therefore evolve alongside the problem itself.
Good for: large agent registries; heterogeneous capabilities; organizations with many specialists; tasks whose requirements emerge dynamically; mission-oriented agent systems; workloads that need different skills at different stages.
Failure points - and what the reducer does to them:
- Poor team selection. The builder’s selection is a judgment call, and what the machine can do is constrain its space and make its result inspectable. The roster is a
listwhose element type is anenumover the registry, so recruiting someone who doesn’t exist is rejected at the write with the legal values in the error. The roster is staged before any work begins, so you can read the team in the parked record. Beyond that the selection is a judgment; the routing is exact. - Excessive discovery and negotiation overhead. The discovery is the builder’s turn: it reads the mission, recruits, and stages the roster. There’s no multi-round negotiation - the specialists are summoned after the roster is set. The overhead is one builder turn plus the specialists’ work, bounded and visible. If it isn’t worth it, you run a fixed team (any of the other twelve patterns).
- Redundant specialists.
size(context.roster) > 4is a real edge into a real state: over-recruit and the mission doesn’t start, the roster is cleared, and the builder recruits again - costing one builder turn instead of four specialist turns. The cap is declared where you can read it. What it can’t judge is fit: a within-budget roster containing someone the mission didn’t need is legible but not preventable, because “needed” isn’t a predicate. - Loss of context when team composition changes. This is the one the design answers most directly. Every recruit writes into the same
outputsmap, keyed by author, and reads the whole of it. The context doesn’t live in the specialists; it lives in the store. A recruit that’s finished and gone hasn’t taken anything with it - the members are interchangeable and the store is not. - Team churn can undermine ownership and continuity. Continuity holds: it’s owned by the phase and the store, so a substitute reads exactly what its predecessor would have. Churn itself is where this shape gives something up - the roster is consumed positionally (
roster[size(outputs)]), so appending mid-mission extends the run but replacing a delivered slot doesn’t work, because its index is spent. Expressing replacement means expressing it as data (a substitutions map the pick reads through), which is a real design and a couple more fields - not something you get for free.
The structural note worth carrying out of this one: there is a single phase state for the whole team. from: { agent: "{{ context.current }}" } resolves against live context when the state is entered, so the same state summons a different principal each pass. A mission of two and a mission of four traverse the same graph - the topology stops scaling with the registry.
A useful mental model
The thirteen patterns group by the primary orchestration problem they solve:
| Category | Patterns |
|---|---|
| Flow | Sequential Pipeline, Parallel Fan-Out / Fan-In |
| Authority | Supervisor / Router, Hierarchical Manager / Worker |
| Delegation | Peer Handoff / Swarm, Auction / Contract-Net, Dynamic Team Formation |
| Reasoning | Debate / Consensus, Quorum / Voting, Generator / Critic / Refiner |
| Planning | Planner / Executor |
| Coordination | Blackboard / Shared-State, Event-Driven / Publish-Subscribe |
The categories aren’t mutually exclusive - real systems compose several at once. But in every one of them, the same idea holds: the agents do the judgment, the context store holds the state, the guards apply the rules, and the reducer is the only thing that moves the machine. The pattern is the shape of the coordination; the reactive reducer is the thing that makes the shape exact.
Three idioms recur across almost all thirteen, and they’re worth carrying out of the series on their own - they’re what the individual patterns keep reducing to.
Many contributors, one field. When N actors contribute the same kind of thing - votes, bids, findings, critiques, positions - that’s one map field, not N scalar fields. Entries are keyed by whoever actually wrote them (the runtime forces the key), which collapses three separate concerns into one mechanism: the join is the requirement set, the attribution is the key, and “how many have answered” is size(). The version with a field per contributor makes the panel’s size part of the graph, so changing it means editing guards. The map version makes it a number.
Accumulate what you keep; consume what re-arms. A requirement is satisfied by presence, so anything that gates a loop has to be destroyed for the loop to re-arm - and the thing you’d naturally destroy is usually the record you wanted. Split them: a scalar trigger blanked on the edge, and a list or map that only grows, appended in an exit action (which runs after the edge is chosen and before it fires, so the trigger is still readable). Most of the “loop that runs and does nothing” bugs in this series are one half of that split missing.
When nothing is destroyed, every test becomes a comparison. The moment a field accumulates, has(x) stops being a signal - it’s true from the first write forever. Freshness becomes size(drafts) > size(verdicts), and the retry budget becomes size(drafts) < 4 rather than a counter beside the data that can disagree with it. Both sides of the loop have to compare; getting only one is how a machine silently reuses a stale judgement.
None of these is a feature you turn on. They’re what typed context plus pure guards make natural once the scalar-shaped workarounds are gone.
Series intro - why the reactive reducer, and the two ways to run it