This is the heart of multi-agent orchestration. The Schedule
Coordinator is an agent whose tools are the other agents.
Step 44: Create the coordinator agent
Create lib/agents/coordinator-agent.ts:
// lib/agents/coordinator-agent.ts
import { ToolLoopAgent, InferAgentUIMessage, tool } from 'ai'
import { z } from 'zod'
import { availabilityAgent } from './availability-agent'
import { fairnessAgent } from './fairness-agent'
import { coverageAgent } from './coverage-agent'
import { updateSchedule } from '@/lib/db'
// Wrap each specialist as a tool the coordinator can call
const checkAvailabilitySubagent = tool({
description: 'Ask the Availability Agent about employee availability.',
inputSchema: z.object({
question: z.string().describe('The availability question to answer'),
}),
execute: async ({ question }) => {
const result = await availabilityAgent.generate({
messages: [{ role: 'user', content: question }],
})
return { answer: result.text }
},
})
const checkFairnessSubagent = tool({
description: 'Ask the Fairness Agent to evaluate shift distribution equity.',
inputSchema: z.object({
question: z.string().describe('The fairness question to evaluate'),
}),
execute: async ({ question }) => {
const result = await fairnessAgent.generate({
messages: [{ role: 'user', content: question }],
})
return { answer: result.text }
},
})
const validateCoverageSubagent = tool({
description: 'Ask the Coverage Agent to validate staffing.',
inputSchema: z.object({
question: z.string().describe('The coverage question to validate'),
}),
execute: async ({ question }) => {
const result = await coverageAgent.generate({
messages: [{ role: 'user', content: question }],
})
return { answer: result.text }
},
})
const assignShiftTool = tool({
description: 'Assign an employee to a specific shift.',
inputSchema: z.object({
weekStart: z.string(),
dayOfWeek: z.number().min(0).max(6),
shiftType: z.enum(['opening', 'mid', 'closing']),
employeeName: z.string(),
}),
execute: async ({ weekStart, dayOfWeek, shiftType, employeeName }) => {
await updateSchedule(weekStart, dayOfWeek, shiftType, employeeName)
return { success: true, assigned: `${employeeName} → ${shiftType} on day ${dayOfWeek}` }
},
})
export const coordinatorAgent = new ToolLoopAgent({
model: 'anthropic/claude-sonnet-4.5',
instructions: `You are the Schedule Coordinator for Fabulosa Books.
You coordinate scheduling by delegating to three specialist agents:
1. **Availability Agent** — checks who is free and when
2. **Fairness Agent** — ensures equitable shift distribution
3. **Coverage Agent** — validates store staffing requirements
When handling scheduling requests:
1. First, check availability to understand the current state
2. Then, consult the Fairness Agent to understand distribution patterns
3. Make assignment decisions that balance availability, fairness, and coverage
4. After making assignments, validate coverage to ensure no gaps
5. Report the final result clearly
You can also assign shifts directly using the assignShift tool.
Important guidelines:
- Always consult at least the Availability Agent before making suggestions
- For full schedule creation, consult all three specialists
- Prefer regular staff over on-call staff
- Be transparent about trade-offs
- Present proposed schedules in a table format when possible`,
tools: {
checkAvailability: checkAvailabilitySubagent,
checkFairness: checkFairnessSubagent,
validateCoverage: validateCoverageSubagent,
assignShift: assignShiftTool,
},
})
This is the key pattern. The coordinator's tools
are the other agents. When it calls
checkAvailability, it's actually running the
Availability Agent as a subagent—a full agent with its own
tools and reasoning loop. The coordinator receives a text summary
back, reasons about it, and decides what to do next.