A multi-agent system is not a one-way assembly line.
The coordinator splits the goal, specialists work, results come back, and the coordinator decides whether to finish, retry, or send more work.
That repeating path is the coordination cycle.
One agent thinks, acts, observes, and remembers.
A team needs an extra layer around that:
| Step | What happens |
|---|---|
| 1. Decompose | The coordinator splits the goal into bounded tasks. |
| 2. Execute | Specialists run, in parallel where possible. |
| 3. Attribute | Each result is tagged with its agent and task id. |
| 4. Reconcile | The coordinator combines, checks, and decides. |
If something still conflicts, the cycle repeats.
The loop in code is small. The design lives in the stop conditions, not in extra frameworks.
while not goal_satisfied(state):
tasks = coordinator.delegate(state)
results = await run_agents(tasks)
state.merge(results)
if conflict(state):
state = coordinator.replan(state)
return coordinator.final_decision(state)
Three facts make this a loop rather than a script:
Without limits, two agents can undo each other forever: the flight agent picks a late arrival, the hotel agent rejects it, the flight agent picks it again.
MAX_ROUNDS, MAX_TOKENS = 3, 10_000
rounds = 0
while not goal_satisfied(state):
if rounds >= MAX_ROUNDS:
return escalate(state, "round limit")
if state.tokens > MAX_TOKENS:
return escalate(state, "over budget")
if repeats_conflict(state):
return escalate(state, "deadlock")
tasks = coordinator.delegate(state)
state.merge(await run_agents(tasks))
rounds += 1
return coordinator.final_decision(state)
| Limit | What it means here |
|---|---|
| Round limit | At most three delegation rounds |
| Cost budget | Stop before 10,000 tokens |
| Deadlock check | The same conflict twice means agents are undoing each other |
| Escalate | Hand the partial plan and the conflict to a person |
Escalation is a success of sorts. The system stopped honestly instead of booking a broken trip.
Use it when tasks do not need each other's output.
flight, hotel, policy = await gather(
flight_agent.run(f_task),
hotel_agent.run(h_task),
policy_agent.run(p_task),
)
Risk: wasted work if a later check fails. The hotel search may finish, then policy says the dates are invalid.
Use it when one agent needs another agent's result.
flight = await flight_agent.run(f_task)
hotel = await hotel_agent.run(h_task)
budget = await budget_agent.run(combine(flight, hotel))
Risk: waiting time adds up. Each agent sits idle until the previous one finishes.
A practical Paris design often mixes both: search flights and hotels together, then run budget and policy checks on the combined pair.
The coordinator decomposes, delegates, attributes, and reconciles in a bounded loop; parallelise independent searches and escalate when rounds, cost, or a repeated conflict say stop.