Naar inhoud
lightbulb Welkom op de nieuwe kennisbank | We hebben de docs volledig vernieuwd met meer dan 160 features. Bekijk wat nieuw isarrow_forward

Meerdere AI-agents orkestreren

Bouw een orchestrator-agent die complexe taken opsplitst, delegeert aan gespecialiseerde sub-agents en de resultaten combineert tot een coherent eindantwoord.

Waarom orkestratie?

Een enkele agent met veel tools wordt snel inefficient: het model moet kiezen uit tientallen opties, de context groeit snel en gespecialiseerde kennis verdunt. Orkestratie splitst een complex systeem op in gespecialiseerde agents die elk uitblinken in hun eigen domein.

info

Analogie: de manager en de specialisten

Denk aan een bedrijf. De manager (de orchestrator) verdeelt het werk over specialisten (de sub-agents). De manager hoeft niet zelf alles te kunnen, maar moet wel weten wie wat kan en hoe de stukken samenkomen.

Architectuurpatronen

Er zijn drie patronen die je in de praktijk het vaakst tegenkomt. Kies op basis van hoe de taken zich tot elkaar verhouden.

Patroon Hoe het werkt Geschikt voor
Hub-and-spoke Een centrale orchestrator delegeert direct aan sub-agents De meeste use cases, eenvoudig te bouwen en te monitoren
Pipeline Agents werken sequentieel: de output van A is de input van B Dataverwerkings- en transformatieworkflows
Hierarchisch De orchestrator stuurt sub-orchestrators aan die op hun beurt agents aansturen Grote, organisatorisch complexe taken
lightbulb

Begin simpel

Start vrijwel altijd met hub-and-spoke. Voeg pas een hierarchische laag toe als een enkele orchestrator echt te veel sub-agents moet coordineren. Elke extra laag maakt debuggen en kostenbewaking moeilijker.

Orchestrator implementeren

De orchestrator krijgt voor elke sub-agent een delegate_to_*-tool. Het model kiest zelf welke agent het inzet en geeft een taak plus context mee.

const subAgents = {
  research: new ResearchAgent(),
  writer: new WriterAgent(),
  reviewer: new ReviewerAgent(),
};

const orchestratorTools: Anthropic.Tool[] = Object.entries(subAgents).map(([name, agent]) => ({
  name: `delegate_to_${name}`,
  description: agent.description,
  input_schema: {
    type: "object" as const,
    properties: {
      task: { type: "string", description: "De taak die de sub-agent moet uitvoeren" },
      context: { type: "string", description: "Relevante context voor de sub-agent" },
    },
    required: ["task"],
  },
}));

async function orchestrate(userTask: string): Promise<string> {
  const messages: Anthropic.MessageParam[] = [
    { role: "user", content: userTask },
  ];

  const ORCHESTRATOR_SYSTEM = `Je bent een orchestrator die complexe taken verdeelt over gespecialiseerde agents.
Analyseer de taak, splits hem op in subtaken en delegeer elke subtaak aan de meest geschikte agent.
Combineer de resultaten tot een coherent eindantwoord.`;

  while (true) {
    const response = await anthropic.messages.create({
      model: "claude-opus-4-8",
      max_tokens: 4096,
      system: ORCHESTRATOR_SYSTEM,
      tools: orchestratorTools,
      messages,
    });

    if (response.stop_reason === "end_turn") {
      return extractText(response);
    }

    const toolResults: Anthropic.ToolResultBlockParam[] = [];
    for (const block of response.content) {
      if (block.type !== "tool_use") continue;
      const agentName = block.name.replace("delegate_to_", "");
      const agent = subAgents[agentName as keyof typeof subAgents];
      const result = await agent.execute(block.input.task as string, block.input.context as string);
      toolResults.push({ type: "tool_result", tool_use_id: block.id, content: result });
    }

    messages.push({ role: "assistant", content: response.content });
    messages.push({ role: "user", content: toolResults });
  }
}
warning

Bewaak het aantal rondes

De while (true)-lus hierboven heeft geen rem. Voeg in productie een teller toe en stop na een vast maximum aan rondes, zodat een agent die blijft delegeren je niet onbedoeld een hoge rekening en een eindeloze run bezorgt.

Sub-agents met gespecialiseerde tools

Elke sub-agent krijgt zijn eigen system-prompt en eigen set tools. De description is belangrijk: de orchestrator gebruikt die tekst om te beslissen welke agent past, dus maak hem concreet.

class ResearchAgent {
  description = "Verzamelt informatie via web-zoekopdrachten en document-analyse. Gebruik voor feitenverzameling, marktonderzoek en technische documentatie.";

  async execute(task: string, context?: string): Promise<string> {
    return reactAgent(task, researchTools, RESEARCH_SYSTEM_PROMPT, context);
  }
}

class WriterAgent {
  description = "Schrijft en formatteert content: rapporten, e-mails, samenvattingen en presentaties. Gebruik nadat onderzoek is afgerond.";

  async execute(task: string, context?: string): Promise<string> {
    return reactAgent(task, writingTools, WRITER_SYSTEM_PROMPT, context);
  }
}

Parallelle uitvoering met afhankelijkheden

Zijn subtaken onafhankelijk, voer ze dan parallel uit. Hebben ze afhankelijkheden, modelleer die dan als een gerichte acyclische graaf (DAG): elke taak draait pas als alle taken waarvan hij afhangt klaar zijn.

async function parallelOrchestrate(tasks: DelegatedTask[]): Promise<Map<string, string>> {
  const results = new Map<string, string>();
  let remaining = [...tasks];

  while (remaining.length > 0) {
    const ready = remaining.filter(t => t.depends_on.every(dep => results.has(dep)));
    if (ready.length === 0) throw new Error("Circulaire afhankelijkheid gedetecteerd");

    const batch = await Promise.all(
      ready.map(async (t) => {
        const context = t.depends_on.map(dep => results.get(dep)).join("

");
        const result = await subAgents[t.agent].execute(t.task, context);
        return { id: t.id, result };
      })
    );
    batch.forEach(({ id, result }) => results.set(id, result));

    remaining = remaining.filter(t => !ready.includes(t));
  }

  return results;
}

In elke ronde draaien alle taken die klaarstaan tegelijk via Promise.all. Daarna schuift de lus door naar de volgende laag van de graaf. Raakt er een ronde waarin niets klaarstaat, dan zit er een cyclus in je afhankelijkheden en gooit de code een fout.

Foutafhandeling en retry

Sub-agents falen soms: een tool geeft een time-out, een API geeft een tijdelijke fout. Vang dat op met een retry met exponentiele backoff, zodat een tijdelijke hapering de hele orchestratie niet onderuit haalt.

async function executeWithRetry(
  agent: SubAgent,
  task: string,
  context: string,
  maxRetries = 2
): Promise<string> {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await agent.execute(task, context);
    } catch (err) {
      if (attempt === maxRetries) {
        return `Sub-agent mislukt na ${maxRetries + 1} pogingen: ${err instanceof Error ? err.message : String(err)}`;
      }
      await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, attempt)));
    }
  }
  return "";
}
lightbulb

Laat een fout de hele run niet stoppen

Geef bij definitief falen een nette foutmelding terug als resultaat in plaats van een exception door te gooien. Zo kan de orchestrator zelf beslissen of hij verder kan met een gedeeltelijk resultaat of de gebruiker om verduidelijking vraagt.

Kosten en monitoring

Orkestratie vermenigvuldigt het aantal model-aanroepen, dus hou je verbruik in de gaten. Log per sub-agent-aanroep het aantal input- en output-tokens en aggregeer dat per orchestrator-run. Stel per agent-type een budget in en breek af zodra dat overschreden wordt. Zo zie je meteen welke specialist de meeste kosten veroorzaakt.

Hoe diep mag de agent-hierarchie zijn?

Houd het op maximaal twee tot drie niveaus. Dieper maakt debuggen en monitoren exponentieel lastiger, omdat een fout door meerdere lagen heen gevolgd moet worden. Heb je echt meer diepte nodig, herzie dan eerst de taakstructuur.

Hoe voorkom ik dat agents in een loop blijven hangen?

Gebruik een globale stap-teller die over alle agents heen loopt, niet per agent apart. Stel een totaalmaximum in, bijvoorbeeld 50 stappen, en gooi een fout zodra dat bereikt wordt.

Kan ik dezelfde agent meerdere keren aanroepen?

Ja. Houd er wel rekening mee dat agents idealiter stateless zijn: elke aanroep staat op zichzelf. Geef alles wat de agent moet weten mee via de context-parameter in plaats van te leunen op gedeelde toestand.

Welk Claude-model gebruik ik voor de orchestrator?

Voor de orchestrator loont een sterk redeneermodel, omdat die de taak moet ontleden en de stukken moet samenvoegen. In juni 2026 is claude-opus-4-8 het meest geschikte model voor dit soort werk. Voor eenvoudige, afgebakende sub-agents kun je een sneller en goedkoper model zoals claude-sonnet-4-6 of claude-haiku-4-5 inzetten.

Wanneer kies ik parallel in plaats van sequentieel?

Voer subtaken parallel uit zodra ze geen elkaars output nodig hebben, dat scheelt veel wachttijd. Heeft taak B de uitkomst van taak A nodig, modelleer dat dan als afhankelijkheid in de DAG, zodat B pas start als A klaar is.

Hoe geef ik resultaten van de ene agent door aan de volgende?

Geef de uitvoer van afgeronde taken mee als context aan de afhankelijke taak. In het DAG-voorbeeld gebeurt dat door de resultaten van alle taken in depends_on samen te voegen en mee te sturen in de context-parameter.