Skip to content

p6: Orchestrator and delegation

In p5 one agent called plain tools. Here the tools are other agents. A concierge orchestrator hands the weather question to a weather analyst and the flight question to a flight advisor, each a full agent with its own brief, then combines their answers into a plan.

In a hurry? These three steps are the whole challenge. Everything below is the why and the how.

  1. Run npm run p6 and watch one [delegate] line fire: only the weather analyst is consulted, so any flight advice is guessed.
  2. Edit start/agent.ts: write the consultFlights body to run the flightAdvisor sub-agent and return its .text (TODO 2), then add consultFlights to the concierge’s tools map (TODO 2).
  3. Done when both [delegate] lines fire and the plan combines real answers from the weather and flight specialists.

The trick is small. A delegation tool’s execute just runs another agent and returns its answer. The orchestrator can’t tell the difference between that and a plain function, which is exactly what makes this compose. And because each sub-agent has its own instructions, the concierge stays focused on planning instead of trying to be a weather expert and a flight expert at once.

This is close to p3, where you split work across several agents too. The difference is who decides the split. In p3 you fixed the subtasks in code: always cost, weather, safety. Here the orchestrator decides at runtime which specialists to consult, based on the request. The subtasks aren’t hard-coded; the model chooses them.

orchestrator agent
   |-- tool "ask the weather agent"  ->  a whole sub-agent runs
   |-- tool "ask the flights agent"  ->  a whole sub-agent runs
   '-- composes their replies  ->  answer

A delegation tool’s execute just runs another agent and returns its answer, so the orchestrator’s tools are other agents.

Forget travel. Say a support concierge delegates the refund question to a refund specialist that is itself a full agent. The delegation tool is an ordinary tool({...}); the only twist is what its execute does:

const refundSpecialist = new ToolLoopAgent({ model, instructions: "You judge refund eligibility..." });

const consultRefunds = tool({
  description: "Ask the refund specialist whether a charge is refundable.",
  inputSchema: z.object({ charge: z.string() }),
  execute: async ({ charge }) => {
    const result = await refundSpecialist.generate({ prompt: charge });  // run a whole agent
    return result.text;                                                   // hand its answer back
  },
});

That is the whole pattern. The tool’s execute runs another agent and returns its answer, so the orchestrator’s “tools” are agents. The orchestrator cannot tell this from a plain function, which is exactly what makes it compose; and because the specialist carries its own brief, the concierge stays focused on planning instead of being a refund expert too. Below you write TripMate’s version for flights.

Open start/agent.ts. The two sub-agents (weatherAnalyst, flightAdvisor) and the consultWeather delegation tool are provided as your template. What is missing is consultFlights’s execute body, running flightAdvisor and returning its .text, plus wiring it into the concierge’s tools map. That is TODO 2.

npm run p6

The concierge consults the weather analyst, so you’ll see one [delegate] weatherAnalyst line. There’s no flight tool wired, so any flight detail in the plan is guessed. One delegation works; the other is missing, and you’re about to write it.

  1. Run it and see the one delegation. Run npm run p6 and watch for the [delegate] line. There’s one: weatherAnalyst. Read the plan and notice the flight advice has nothing behind it.

  2. Write the consultFlights delegation body (TODO, step 2). consultWeather above is your template: it is an ordinary tool({...}) whose execute runs a sub-agent (weatherAnalyst.generate(...)) and returns its .text instead of doing the work itself. In start/agent.ts the consultFlights skeleton already has its description and inputSchema, and the flightAdvisor sub-agent is provided; what’s missing is the execute body. Write it the way consultWeather does: run flightAdvisor on the route and return its .text. Build it from that template rather than copying it from here.

  3. Wire it into the concierge’s tools map (TODO, step 2). The orchestrator can only delegate through tools it holds, so add consultFlights alongside consultWeather in the concierge’s tools map. Run again. Now both specialists are consulted ([delegate] weatherAnalyst, then [delegate] flightAdvisor), and the plan combines real answers from both. The orchestrator chose to call each; you only supplied the option.

  4. Find the nesting in the trace. Scroll up to the console trace. Each sub-agent’s ai.generateText span sits inside the orchestrator’s ai.toolCall span, which sits inside the orchestrator’s own run. That nesting is delegation made visible: an agent run within a tool call within an agent run.

  5. Check you’ve got it. You should see two [delegate] lines, a plan that combines both answers, and the nested sub-agent spans in the trace. You should be able to say how delegation differs from the fixed fan-out in p3.

Stuck? finish/agent.ts is the canonical version. Read it after you’ve had a real go.

  • Recursion with no floor. A delegating agent can delegate to an agent that delegates again. Keep the depth shallow and bound each agent’s steps with stopWhen, or a run can fan out far more than you meant.

  • Naming an unwired tool in the instructions. If the orchestrator’s brief insists on a tool you haven’t added yet, a small model will try to call it and stall. Wire the tool and name it together. The starter softens the brief in step 1 for exactly that reason.

  • Delegating what a plain function should do. A sub-agent is the right call when the subtask needs judgement or language. For a lookup or a calculation, a plain tool is cheaper and more reliable. Don’t spend an LLM where a function will do.

Why does the orchestrator stay simpler than one mega-agent?

You could give one agent every instruction and every tool. It works until the brief grows long enough that the model loses the thread.

Delegation keeps each agent’s context small and its job clear: the concierge plans, the analysts analyse. Smaller context per agent means more reliable behaviour, and you can test and tune each specialist on its own.

When should a tool be a sub-agent rather than a plain function?

Reach for a sub-agent when the subtask needs judgement, language, or its own multi-step reasoning, like “advise how to fly this route within a budget.” Reach for a plain tool when the subtask is a lookup or a calculation with one right answer, like reading a row from a table.

The orchestrator treats both the same way, so the choice is yours to make on cost and reliability, not on what the model can call.

Next up is p7, where the agent remembers across turns and streams its replies, so it feels like a chat rather than a one-shot call.