Skip to content

2.5 Supervision

Routing (2.4) sends a task to the right agent. But some tasks aren’t one job — they’re several independent jobs that a single agent would do one after another, slowly. That’s failure mode #5 — sub-tasks run serially — with a nasty twin: if one of those sub-tasks throws, the whole run dies with it.

Think “research this company”: check pricing, summarize reviews, look up inventory. None depends on the others. Doing them in sequence wastes time; letting one failure sink the other two wastes everything.

A supervisor is an agent whose job is coordinating other agents:

  1. Plan — break the task into independent sub-tasks.
  2. Fan out — launch a sub-agent for each, in parallel, each isolated from the others (isolation we already have from 2.2).
  3. Fan in — collect their results as typed contracts so they can be merged.
  4. Synthesize — combine them into one answer, degrading gracefully if some failed.

Press dispatch. Three sub-agents run at the same time — and one of them fails on purpose. Watch what the supervisor does with the two that succeed.

Supervisorplans → fans out 3 sub-agents in parallel → merges results
Sub-agent Aidle
Research competitor pricing
Sub-agent Bidle
Summarize customer reviews
Sub-agent Cidle
Check current inventory

Run serially, these three would finish one after another. In parallel they finish in about the time of the slowest one — and because each runs in isolation, one crashing doesn't take the others down. The supervisor's job is to fan out, collect whatever comes back, and make something of it.

Two payoffs. Speed: in parallel, the batch finishes in about the time of the slowest sub-agent, not the sum of all three. Resilience: sub-agent B failing doesn’t crash the run — the supervisor merges the results it did get and returns a degraded-but-useful answer. Partial failure is not total failure.

async function supervise(task) {
const subTasks = plan(task); // 1. break into independent pieces
// 2 + 3. run them all at once; settle so one failure doesn't reject the batch.
const results = await Promise.allSettled(
subTasks.map((st) => runSubAgent(st)), // each isolated, in parallel
);
const ok = results.filter((r) => r.status === 'fulfilled').map((r) => r.value);
const failed = results.length - ok.length;
return synthesize(ok, { degraded: failed > 0 }); // 4. merge what we got
}

Promise.allSettled is the whole trick: it waits for every sub-agent and never rejects just because one did. You keep the successes, note the failures, and synthesize from what you have.

  • Orchestration graphs (LangGraph, Mastra) and map-reduce-over-agents patterns give you supervisors, parallel fan-out, and typed result contracts.

You built the fan-out/fan-in-with-partial-failure core yourself.

© 2026 Clifford Bernard · Content CC BY 4.0 · Code MIT ·Source