Supervisor
Supervisor puts a manager agent in charge of a team of worker agents. The manager breaks the task down, delegates pieces to workers (in parallel when it can), and combines their results. Use it for tasks that split into parts needing different specialists, such as research then writing then review. It is the multi-agent pattern most often worth its cost in production: Anthropic’s write-up of its multi-agent research system reports a 90.2% improvement over a single-agent baseline on its internal research eval.
Pattern
The supervisor itself runs an architecture (default ReAct). Its tool host is augmented with two extra tools:
delegate(worker, instructions). The named workerAgentruns to completion with the given instructions and returns its final answer as the tool result.forward_message(worker). Hands the named worker’s latest output to the user verbatim, with no synthesis pass: it ends the supervisor’s turn, so the model isn’t called again. It fails (and the model can delegate first) when that worker has no output yet.
Because ReAct’s tool dispatch is already parallel
(anyio.create_task_group over all tool calls in a turn), the
supervisor gets parallel delegation for free. Emit two delegate
calls in one turn and both workers run concurrently.
Usage
from loomflow import Agent
from loomflow.team import Team
researcher = Agent("Find sources, summarise key claims.",
model="claude-opus-4-7", tools=[search])
writer = Agent("Draft the article.",
model="claude-opus-4-7")
reviewer = Agent("Critique the draft for clarity and factual accuracy.",
model="gpt-4o")
team = Team.supervisor(
workers={"researcher": researcher, "writer": writer, "reviewer": reviewer},
instructions=(
"Manage the article pipeline. Delegate research first, then "
"writing, then a review pass. Synthesize the final answer."
),
model="claude-opus-4-7",
)
result = await team.run("Write an article about agent harnesses.")Or via the explicit Architecture form:
from loomflow import Agent
from loomflow.architecture import Supervisor
agent = Agent(
"Manage the article pipeline.",
model="claude-opus-4-7",
architecture=Supervisor(workers={
"researcher": researcher,
"writer": writer,
"reviewer": reviewer,
}),
)The two forms are interchangeable, Team.supervisor(...) is exactly
Agent(architecture=Supervisor(...)) under the hood. Use the Team
facade for single-level teams; use the explicit form for recursive
composition.
Dynamic spawning — allow_spawn=True
By default the roster is fixed at construction. With
allow_spawn=True the coordinator model can create new specialist
workers mid-run via an injected spawn_worker(role, instructions)
tool:
team = Team.supervisor(
workers={"researcher": researcher, "writer": writer},
instructions="Coordinate the report. Spawn extra specialists if needed.",
model="claude-opus-4-7",
allow_spawn=True,
max_spawned=3, # hard cap per run (default 5)
spawn_template=worker_template # model + tools spawned workers inherit
)How spawn_worker behaves:
rolemust be a valid Python identifier not already in the roster;instructionsbecome the new worker’s system prompt — that is all the worker knows about its job.- The spawned worker is immediately delegable: the
delegate/forward_messagetool definitions are rebuilt after every spawn, so the next model turn sees the new role in the enum. - The model may not choose the worker’s tools or model. Those
are inherited from
spawn_template(anAgentwhose model + tool host are cloned), along with the template’s guards: its permissions, hooks, approval handler, guardrails, rate limiter, audit log and limits. (Before 0.17 a spawned worker got the template’s tools but none of its guards.) With no template, spawned workers use the coordinator’s own model and get no tools — the v1 surface is deliberately minimal. - Guardrails return error strings to the model, not exceptions:
exceeding
max_spawned, invalid role names, duplicate roles, and empty instructions all come back as correctable tool results. - Each spawn emits a
supervisor.worker_spawnedarchitecture event (role, worker_id, running count) on the stream.
Ephemerality + tenant pinning
Spawned workers are ephemeral by design. They live in a per-run
overlay merged with the fixed roster for delegate lookup and die when
the run ends — never persisted into the agent-level worker registry,
so nothing spawned leaks across runs or across users. (Mid-run they
are mirrored into the shared registry so
send_message(to=<worker_id>) works; the run’s teardown pops those
entries.)
Tenant isolation holds from birth: a spawned worker is pinned to the
spawning run’s user_id at creation, and delegation to it goes
through the same acquire_worker_session discipline as persistent
workers — a different user’s run attempting to reach it is rejected,
and so is an anonymous one.
Spawning is off by default (allow_spawn=False — zero behavior
change), and each spawned worker is a full agent whose spend rolls up
into the parent run’s usage and budget. Keep max_spawned small and
prefer a well-designed fixed roster; the built-in prompt already
tells the model to spawn only when no current worker fits.
Replay correctness
Each delegate call is a tool call of the supervisor’s loop, so it
is journaled like any other tool call, and the worker’s own run
journals under the supervisor’s run. With a SqliteRuntime or
PostgresRuntime, a crash mid-supervisor-turn replays cleanly:
completed worker runs return cached results; only the un-completed
work re-executes.
When Supervisor pays off
- Specialist workers with distinct skills (research vs write vs review).
- Tasks decompose into chunks the supervisor can hand off without hand-holding.
- Cost is acceptable at 2–4× ReAct: Typical for important research, multi-source content generation, complex coding tasks.
Workers are real Agent instances. Each has its own model,
memory, tools, and architecture. Workers can themselves use Reflexion,
ActorCritic, or even another Supervisor. See
Recursive composition.