Adding an Agent
Step-by-step guide to adding a new specialized agent to the swarm.
Overview
Adding an agent requires touching 3 files:
lib/ai/agents/registry.ts— register the agentlib/ai/agents/your-agent.ts— implement the agentlib/ai/orchestrator.ts— add the dispatch case
Optionally: lib/ai/utils.ts if the agent produces assets.
Steps
Register the agent in registry.ts
Add an entry to AGENT_REGISTRY. The description field is critical — it's the only thing the orchestrator's routing LLM reads:
"invoice-parser": {
id: "invoice-parser",
name: "Invoice Parser",
description:
"Extracts structured data from invoice PDFs: vendor, line items, totals, due dates.",
icon: "FileText",
},Use kebab-case for the ID. snake_case and camelCase IDs will break routing. The ID must exactly match the key in AGENT_REGISTRY.
Create the agent file
Create lib/ai/agents/invoice-parser-agent.ts:
import { resolveModel } from "@/lib/ai/models";
import { saveAgentResponse } from "@/lib/ai/utils";
import { convertToModelMessages, ToolLoopAgent, UIMessage } from "ai";
export const maxDuration = 60;
export async function streamInvoiceParserAgent(
messages: UIMessage[],
team: any,
conversationId: string,
modelId?: string,
) {
const agent = new ToolLoopAgent({
model: resolveModel(modelId),
tools: {
read_document: readDocumentTool,
extract_invoice_data: buildExtractInvoiceTool(team.id),
},
instructions: `
You are an invoice parsing specialist.
When given a PDF URL, use read_document to fetch it,
then use extract_invoice_data to extract structured data.
`,
onFinish: async ({ response }) => {
await saveAgentResponse(response, { conversationId, teamId: team.id });
},
});
const modelMessages = await convertToModelMessages(messages);
return agent.stream({ prompt: modelMessages });
}Key points:
- Export
maxDurationto control the Vercel function timeout - Always call
saveAgentResponseinonFinish - Use
resolveModel(modelId)to support per-request model switching
Add the dispatch case in orchestrator.ts
// lib/ai/orchestrator.ts
import { streamInvoiceParserAgent } from "@/lib/ai/agents/invoice-parser-agent";
// Inside executeAgent switch:
case "invoice-parser":
return await streamInvoiceParserAgent(messages, team, conversationId, modelId);Register assets (optional)
If your agent produces outputs that should be saved as assets, add an entry to ASSET_REGISTRY in lib/ai/utils.ts:
extract_invoice_data: ({ input, output }) => {
const value = (output as any)?.value ?? output;
if (!value?.vendor) return null;
return {
type: "invoice",
title: String(value.vendor ?? "Invoice").slice(0, 100),
content: `**Vendor:** ${value.vendor}\n**Total:** ${value.total}`,
metadata: { total: value.total, currency: value.currency },
};
},Writing Effective Agent Descriptions
The routing description must be distinct from all other agents:
| Bad (ambiguous) | Good (specific) |
|---|---|
| "Processes documents" | "Extracts structured data from invoice PDFs: vendor, line items, totals, due dates" |
| "Helps with content" | "Writes long-form scripts for YouTube videos, podcasts, and webinars" |
| "Analyzes data" | "Scans receipt images and extracts structured line-item data including totals and payment info" |
If two agents have overlapping descriptions, the orchestrator will mis-route messages unpredictably.