2. Sub-Agent Layer

PreviousNext

Learn how to design sub-agents that own domains, run in parallel, and use small, reliable toolsets.

Sub-Agent Layer

Once the router decides which domains to activate, it hands work off to sub-agents — specialized agents that each manage a single area of responsibility.

Each sub-agent is built with the Agent class, giving it its own tools, system prompt, and loop control.

Think of sub-agents as mini-experts:
The Orders Agent knows sales data.
The Inventory Agent knows stock.
The Analytics Agent knows KPIs.


Why Use the Agent Class for Sub-Agents

The Agent class lets you define domain-specific logic once and reuse it anywhere.
This keeps your system consistent and easy to scale.

Benefits:

  • Encapsulation – each domain’s model, tools, and prompts stay self-contained
  • Consistency – shared behavior across environments (API, dashboard, chat)
  • Type safety – tool inputs and outputs are fully typed with Zod
  • Modularity – you can add or remove domains without touching the router

Creating a Sub-Agent

Define each sub-agent with the Agent class, giving it:

  • a clear system role,
  • a small toolset (3–7 tools), and
  • loop control to manage reasoning.

Example: Orders Agent

orders-agent.ts
import { Experimental_Agent as Agent, stepCountIs, tool } from "ai"
import { z } from "zod"
 
// Orders Agent — handles sales and revenue queries
export const ordersAgent = new Agent({
  model: "openai/gpt-4o",
  system: `You are an ecommerce orders specialist.
  You can fetch, summarize, and analyze order data.`,
 
  tools: {
    getSalesData: tool({
      description: "Fetch total sales and revenue for a date range",
      parameters: z.object({
        startDate: z.string(),
        endDate: z.string(),
      }),
      execute: async ({ startDate, endDate }) => {
        const revenue = 124_000 // fetched from API or DB
        const totalOrders = 3800
        return { startDate, endDate, revenue, totalOrders }
      },
    }),
 
    summarizePerformance: tool({
      description: "Summarize performance given sales data",
      parameters: z.object({
        revenue: z.number(),
        totalOrders: z.number(),
      }),
      execute: async ({ revenue, totalOrders }) => ({
        summary: `Revenue: $${revenue}, Orders: ${totalOrders}`,
        avgOrderValue: Math.round(revenue / totalOrders),
      }),
    }),
  },
 
  stopWhen: stepCountIs(10), // allow up to 10 reasoning steps
})

The agent runs its own loop: it can call getSalesData, then summarizePerformance, then produce text — all automatically.


Example: Inventory Agent

Another sub-agent owns the Inventory domain.

inventory-agent.ts
import { Experimental_Agent as Agent, tool } from "ai"
import { z } from "zod"
 
export const inventoryAgent = new Agent({
  model: "openai/gpt-4o",
  system: `You manage stock data for an ecommerce store.
  You know how to check product quantities and restock needs.`,
 
  tools: {
    getInventoryLevels: tool({
      description: "Get current stock levels for products",
      parameters: z.object({
        category: z.string().optional(),
      }),
      execute: async ({ category }) => {
        const data = [
          { product: "Wireless Mouse", stock: 83 },
          { product: "Laptop Stand", stock: 45 },
        ]
        return { category, data }
      },
    }),
  },
})

Parallel Execution

When the router detects multiple intents, it can run sub-agents in parallel.

run-sub-agents.ts
import { inventoryAgent } from "./inventory-agent"
import { ordersAgent } from "./orders-agent"
 
async function handleMultiIntent() {
  const [orders, inventory] = await Promise.all([
    ordersAgent.generate({ prompt: "Get sales this week" }),
    inventoryAgent.generate({ prompt: "Get stock for top sellers" }),
  ])
 
  return {
    orders: orders.text,
    inventory: inventory.text,
  }
}

Parallel runs make responses fast and independent — perfect for queries like:

“Show me this week’s revenue and current stock.”


Sequential Execution

Sometimes agents must work in order — for example:

“Find products with low stock, then estimate lost revenue.”

Here the router triggers agents sequentially:

ordered-sub-agents.ts
const inventory = await inventoryAgent.generate({
  prompt: "Find products with low stock",
})
 
const orders = await ordersAgent.generate({
  prompt: `Estimate revenue impact from low-stock products: ${inventory.text}`,
})

Design Principles

Keep sub-agents tight and deterministic:

  • 3–7 tools max — small, static catalogs are predictable
  • Validated inputs (Zod schemas) — guardrail for safety
  • Stable schemas — keep data structures consistent across runs
  • Small system prompts — clarity beats cleverness

Scaling with Ease

Adding a new domain is simple:

  1. Create a new sub-agent (e.g., analyticsAgent.ts)
  2. Register it in the router’s domain map
  3. Done — your router now supports a new capability

The whole system stays modular and reliable.