Supervisor and workers: route subtasks to specialists
A supervisor decomposes a request into typed subtasks, assigns each to a named specialist with its own instruction and its own slice of context, and a synthesiser merges the results while preserving disagreement.
Fatti chiave
- Livello
- advanced
- Runtime
- Python • API OpenAI
- Pattern
- Flusso ispezionabile con confini di sistema visibili
- Interazione
- Sandbox live • Script
- Aggiornato
- 27 luglio 2026
Naviga questo esempio
Pattern
Relazioni con la doctrineQuali principi questo pattern esprime nel codice, e quali la sua forma predefinita infrange.Libreria
Sfoglia gli esempiRiapri la libreria completa per confrontare pattern vicini e percorsi collegati.Interazione
Esegui ora nel sandboxProva l'interazione direttamente nella superficie guidata di questo esempio.Sorgente
Apri codice completoLeggi l'implementazione reale, i punti evidenziati e i requisiti runtime.MCP
Chiama via MCPUsa la stessa risorsa dentro agenti, export deterministici e setup MCP.
Principi collegati
Relazioni con la doctrine
Queste sono forme di ragionamento: come un agente decide, quando agisce, cosa ricorda. Per comporre l'interfaccia intorno a esso, vedi l'AI Pattern Blueprint. Per trigger, pianificazione, caricamento del contesto e ripristino, vedi Agent runtime architecture.
Questo pattern esprime questi principi direttamente nel codice.
P9 · Rappresentare il lavoro delegato come un sistema, non solo come una conversazione
This is the pattern that makes P9 concrete. The work exists as a typed Plan of Subtasks with named owners, not as a message stream: which specialist was engaged, on what question, and what each returned are all separate addressable objects. A supervisor that passes a growing chat transcript between agents has the shape without the property.
supervisor_workers.py:Plan
La forma classica di questo pattern li viola per impostazione predefinita. Ognuno riporta la correzione.
P2 · Assicurarsi che il lavoro in background rimanga percepibile
Specialists run one after another with nothing emitted between the plan and the synthesis unless the caller prints it. A multi-specialist run is the longest-latency shape in this corpus and the easiest to mistake for a hang. Fix, applied here: emit which specialist is running, and its question, before each call rather than after.
supervisor_workers.py:runP6 · Esporre uno stato operativo significativo, non la complessità interna
Every specialist's raw output is surfaced by default, so the operator sees internal chatter rather than the state of the work. At three specialists it is readable; at ten it is noise that hides which lens actually mattered. Fix: report progress as the count of specialists complete plus any disagreement detected, and keep the per-specialist text behind an inspection step.
supervisor_workers.py:runP8 · Rendere espliciti i passaggi, le approvazioni e i blocchi
The supervisor's plan executes as soon as it is produced. Nothing pauses between deciding to engage four specialists and spending four model calls doing it. Fix: treat the plan as a proposal with its cost stated, and gate execution on approval when the fan-out or its cost crosses a threshold you set.
supervisor_workers.py:run
Le relazioni con la doctrine sono analisi di prima parte, non il risultato di una validazione. Esegui il validatore sul tuo codice per ottenere un verdetto con punteggio.
supervisor_workers.py
"""
Supervisor and workers: route subtasks to specialists
A supervisor decomposes a request into subtasks, assigns each to a named
specialist, and a synthesiser merges the results. Each specialist runs with
its own instruction and its own slice of context.
Request -> Plan (typed subtasks) -> Specialists -> Synthesis
The real reason to reach for this is context isolation, not intelligence:
each worker sees only what its subtask needs, so one long transcript does not
drag every step. It is not a general upgrade. Most requests are better served
by one loop, and a team of agents that does not need to be a team is just a
larger bill and more places to fail.
Reach for it when the subtasks are genuinely independent, or when they need
materially different instructions. Not when they merely sound different.
Run: python supervisor_workers.py
"""
from typing import Literal
from dotenv import load_dotenv
from openai import OpenAI
from pydantic import BaseModel, Field
load_dotenv()
client = OpenAI()
MODEL = "gpt-5.4-mini"
MAX_SUBTASKS = 4
#: Each specialist is an instruction, not a model. Adding one is a line here.
SPECIALISTS: dict[str, str] = {
"technical": (
"You assess technical feasibility and implementation risk. Name the "
"specific mechanism that would break, not general caution."
),
"financial": (
"You assess cost and commercial impact. Give ranges and state what "
"drives them. Never invent a figure that was not provided."
),
"operational": (
"You assess who has to run this once it exists, and what they need. "
"Focus on the ongoing burden rather than the build."
),
}
class Subtask(BaseModel):
specialist: Literal["technical", "financial", "operational"]
question: str = Field(
description="The single question this specialist should answer."
)
class Plan(BaseModel):
subtasks: list[Subtask] = Field(
description=(
"One subtask per specialist that is genuinely needed. Do not "
"include a specialist whose lens adds nothing to this request."
)
)
reasoning: str = Field(description="Why these specialists and not the others.")
def plan(request: str) -> Plan:
completion = client.beta.chat.completions.parse(
model=MODEL,
messages=[
{
"role": "system",
"content": (
"Decompose the request into subtasks for the specialists "
"listed. Use only the specialists that genuinely add "
"something. Fewer is better: a specialist with nothing "
"useful to say costs a call and dilutes the synthesis.\n\n"
"Specialists:\n"
+ "\n".join(f"- {name}: {role}" for name, role in SPECIALISTS.items())
),
},
{"role": "user", "content": request},
],
response_format=Plan,
)
return completion.choices[0].message.parsed
def run_specialist(name: str, question: str) -> str:
"""One worker. Sees its own question, not the whole transcript."""
completion = client.chat.completions.create(
model=MODEL,
messages=[
{"role": "system", "content": SPECIALISTS[name]},
{"role": "user", "content": question},
],
)
return completion.choices[0].message.content or ""
def synthesise(request: str, findings: list[tuple[str, str]]) -> str:
body = "\n\n".join(f"[{name}]\n{text}" for name, text in findings)
completion = client.chat.completions.create(
model=MODEL,
messages=[
{
"role": "system",
"content": (
"Merge the specialist findings into one answer. Attribute "
"each point to the specialist it came from. Where two "
"specialists disagree, say so rather than averaging them."
),
},
{"role": "user", "content": f"Request: {request}\n\n{body}"},
],
)
return completion.choices[0].message.content or ""
def run(request: str) -> str:
print(" planning...")
proposal = plan(request)
print(f" rationale: {proposal.reasoning}")
subtasks = proposal.subtasks[:MAX_SUBTASKS]
if not subtasks:
# A plan with no subtasks is a real answer: this request did not need
# a team. Say so rather than inventing work for the specialists.
message = "No specialist lens applied to this request. Answer it directly."
print(f" {message}")
return message
findings: list[tuple[str, str]] = []
for index, subtask in enumerate(subtasks, start=1):
# Printed before the call, so a slow specialist reads as working.
print(f" [{index}/{len(subtasks)}] {subtask.specialist}: {subtask.question}")
answer = run_specialist(subtask.specialist, subtask.question)
print(f" -> {answer[:80]}...")
findings.append((subtask.specialist, answer))
print(" synthesising...")
result = synthesise(request, findings)
print(f" {result}")
return result
if __name__ == "__main__":
request = (
"We are considering moving our nightly batch reconciliation to a "
"streaming pipeline. Should we?"
)
print(f"\n> {request}")
run(request)
Principi correlati
- P2visibilityAssicurarsi che il lavoro in background rimanga percepibileQuando il sistema opera in modo asincrono o al di fuori del focus immediato dell'utente, dovrebbe fornire segnali persistenti e proporzionati che il lavoro sta continuando.Apri il principio →
- P6visibilityEsporre uno stato operativo significativo, non la complessità internaPresentare lo stato del sistema in linguaggio e strutture rilevanti per l'utente, piuttosto che esporre dettagli interni di basso livello che non supportano l'azione o la comprensione.Apri il principio →
- P8trustRendere espliciti i passaggi, le approvazioni e i blocchiQuando il sistema non può procedere, la ragione dovrebbe essere immediatamente visibile, insieme a qualsiasi azione richiesta dall'utente o da un'altra dipendenza.Apri il principio →
- P9orchestrationRappresentare il lavoro delegato come un sistema, non solo come una conversazioneDove il lavoro coinvolge più passaggi, agenti, dipendenze o attività concorrenti, dovrebbe essere rappresentato come un sistema strutturato piuttosto che solo come un flusso di messaggi.Apri il principio →