# Meerdere AI-agents orkestreren [[TOC]] ## 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 title="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 | :::tip title="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. ```typescript 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 { 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 }); } } ``` :::warn title="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. ```typescript 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 { 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 { 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. ```typescript async function parallelOrchestrate(tasks: DelegatedTask[]): Promise> { const results = new Map(); 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. ```typescript async function executeWithRetry( agent: SubAgent, task: string, context: string, maxRetries = 2 ): Promise { 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 ""; } ``` :::tip title="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. :::faq ### 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. :::