Orchestrator-Workers
An orchestrator agent dynamically breaks down a task and delegates sub-tasks to specialised worker agents via tools. The orchestrator decides what to do next based on intermediate results — it is not following a fixed script.
┌─► Worker A (via tool)
Input → Orchestrator├─► Worker B (via tool) → Final Output
└─► Worker C (via tool)When to use it
- The task requires dynamic planning that cannot be predetermined
- Different subtasks need different expertise (writing, analysis, code generation)
- The orchestrator needs to react to intermediate results before deciding the next step
Basic example
ts
import { agent, defineTool } from '@daedalus-ai-dev/ai-sdk';
// Worker tools — each wraps a specialised agent
const analyseRequirementsTool = defineTool({
name: 'analyse_requirements',
description: 'Analyse a feature request and break it into technical requirements.',
schema: (s) => ({
featureRequest: s.string().required(),
}),
handle: async (input) => {
const r = await agent({
instructions: 'You are a senior product engineer skilled at translating feature requests into technical specs.',
}).prompt(`Analyse and break down: ${input.featureRequest}`);
return r.text;
},
});
const writeCodeTool = defineTool({
name: 'write_code',
description: 'Write TypeScript code for a specific file or module.',
schema: (s) => ({
filePath: s.string().description('Path of the file to create').required(),
purpose: s.string().description('What this file should do').required(),
context: s.string().description('Relevant context or requirements').required(),
}),
handle: async (input) => {
const r = await agent({
instructions: 'You are an expert TypeScript developer. Write clean, idiomatic code with proper error handling.',
}).prompt(`Create ${input.filePath} to ${input.purpose}.\n\nContext:\n${input.context}`);
return r.text;
},
});
const writeTestsTool = defineTool({
name: 'write_tests',
description: 'Write unit tests for a given piece of code.',
schema: (s) => ({
code: s.string().description('The code to test').required(),
context: s.string().description('What the code does').required(),
}),
handle: async (input) => {
const r = await agent({
instructions: 'You are a testing expert. Write comprehensive unit tests using Vitest.',
}).prompt(`Write tests for:\n\n${input.code}\n\nContext: ${input.context}`);
return r.text;
},
});
// Orchestrator
const response = await agent({
instructions: `You are a software architect. When given a feature request:
1. Analyse the requirements first
2. Write the implementation code
3. Write tests for the implementation
Use the available tools and work through each step systematically.`,
tools: [analyseRequirementsTool, writeCodeTool, writeTestsTool],
maxIterations: 20,
}).prompt('Implement a rate limiter middleware for an Express.js API.');
console.log(response.text);Stateful orchestration
Pass context through the orchestrator using tool results. The model maintains state in its conversation history:
ts
const lookupOrderTool = defineTool({
name: 'lookup_order',
description: 'Look up an order by ID.',
schema: (s) => ({ orderId: s.string().required() }),
handle: async (input) => JSON.stringify(await db.orders.find(String(input.orderId))),
});
const issueRefundTool = defineTool({
name: 'issue_refund',
description: 'Issue a refund for an order. Only call after confirming the order exists and is eligible.',
schema: (s) => ({
orderId: s.string().required(),
reason: s.string().required(),
amount: s.number().min(0).required(),
}),
handle: async (input) => {
await refundService.issue(String(input.orderId), Number(input.amount), String(input.reason));
return `Refund of $${input.amount} issued for order ${input.orderId}.`;
},
});
// The orchestrator naturally looks up the order before issuing the refund
const response = await agent({
instructions: 'You are a customer service agent. Always verify orders before processing refunds.',
tools: [lookupOrderTool, issueRefundTool],
}).prompt('Please refund order 12345 — the customer received a damaged item.');Hierarchical orchestration
Orchestrators can themselves be used as tools in a higher-level orchestrator:
ts
const featureTeamTool = defineTool({
name: 'feature_team',
description: 'Delegate a complete feature implementation to the feature team orchestrator.',
schema: (s) => ({ feature: s.string().required() }),
handle: async (input) => {
const r = await agent({
instructions: 'You are a feature team orchestrator...',
tools: [writeCodeTool, writeTestsTool, writeDocsTool],
}).prompt(String(input.feature));
return r.text;
},
});
// CTO-level orchestrator
const response = await agent({
instructions: 'You are a CTO. Break large initiatives into features and delegate to teams.',
tools: [featureTeamTool, designReviewTool, securityAuditTool],
}).prompt('Build a complete OAuth2 authentication system.');Guarding against runaway loops
Always set maxIterations when the orchestrator might call many tools:
ts
const response = await agent({
instructions: '...',
tools: [tool1, tool2, tool3],
maxIterations: 15, // Prevent infinite loops
}).prompt('...');If the agent exceeds maxIterations, it throws an error. Catch it to handle gracefully:
ts
try {
const response = await agent({ ..., maxIterations: 15 }).prompt('...');
return response.text;
} catch (err) {
if (err instanceof Error && err.message.includes('maxIterations')) {
return 'The task took too many steps to complete. Please try a simpler request.';
}
throw err;
}Tips
- Write clear tool descriptions. The orchestrator's decisions depend entirely on tool names and descriptions.
- Return rich context from worker tools. The orchestrator needs enough information to make good next-step decisions.
- Limit scope per tool. Small, focused tools are easier for the orchestrator to use correctly than large, multipurpose ones.
- Log tool call sequences.
response.messagesshows exactly which tools were called and in what order — invaluable for debugging.