Introduction & Context

Welcome back! In the previous lesson, you mastered building agentic pipelines in which specialized agents work together in a fixed sequence. Today, we're taking a significant architectural leap forward by learning how to build agent orchestration systems, where a central planner agent can dynamically decide which specialist agents to call based on the specific task at hand.

The key insight is that complex agentic systems can be wrapped as simple tools for other agents. This creates a powerful hierarchy in which agents can leverage the full capabilities of other agents as easily as they use basic functions, enabling much more flexible and intelligent problem-solving than fixed pipeline sequences.

The Agent-as-Tool Concept

The fundamental difference between pipelines and orchestration lies in decision-making authority. In a pipeline, you as the developer decide the sequence: every problem goes through analysis, then calculation, then presentation. In orchestration, the central agent makes these decisions dynamically based on the nature of each specific request. The key insight is that we can encapsulate an agent call as a tool, allowing an orchestrator agent with access to that tool to seamlessly call another specialist agent.

Here's how the agent orchestration flow works:

  1. User submits a request to the orchestrator agent.
  2. Orchestrator analyzes the request and decides whether to handle it directly or delegate to a specialist.
  3. If delegation is needed, the orchestrator calls the specialist agent as a tool.
  4. Specialist processes the request and returns results to the orchestrator.
  5. Orchestrator provides the final response to the user.

Consider how this changes the system's behavior. If someone asks, "What is the capital of France?", a pipeline system would still run the question through all three agents unnecessarily. An orchestrated system, however, allows the central agent to recognize that this is a straightforward knowledge question requiring no mathematical tools and respond directly. But when someone asks about solving equations, the orchestrator can intelligently delegate this to a calculator specialist. This dynamic delegation creates systems that are both more efficient and more capable.

Supporting Asynchronous Agent Tools
Building Specialist Agents

Now that our Agent class can handle asynchronous tools, we can create the specialist agents that will serve as tools for our orchestrator. Let's build a calculator assistant that specializes in mathematical problem-solving, designed to work as a standalone tool that can handle mathematical questions from start to finish.

import fs from 'fs';
import { Agent } from './agent';
import { 
  sumNumbers,
  multiplyNumbers,
  subtractNumbers,
  divideNumbers,
  power,
  squareRoot
} from './functions';

// Load the schemas from JSON file
const schemasJson = fs.readFileSync('schemas.json', 'utf-8');
const toolSchemas = JSON.parse(schemasJson);

// Create a map of tool names to functions
const mathTools: Record<string, Function> = {
  "sum_numbers": sumNumbers,
  "multiply_numbers": multiplyNumbers,
  "subtract_numbers": subtractNumbers,
  "divide_numbers": divideNumbers,
  "power": power,
  "square_root": squareRoot
};

// Create a calculator assistant
const calculatorAssistant = new Agent({
  name: "calculator_assistant",
  systemPrompt: "You are a calculator assistant. You specialize in mathematical calculations and solving equations.",
  tools: mathTools,
  toolSchemas: toolSchemas,
  maxTurns: 15
});

This calculator assistant is designed as a complete mathematical problem-solver with access to all the mathematical tools. The system prompt establishes the agent's expertise in mathematics while allowing it the flexibility to handle various types of mathematical problems. The higher maxTurns: 15 setting gives the agent more room for multi-step math problems that require several sequential tool calls. This balance is important for specialist agents — they need clear domain focus while maintaining enough flexibility to be useful as tools for different orchestrators.

Creating Agent Tool Wrappers
Implementing the Orchestrator Agent

With our specialist agent ready and our wrapper function defined, we can now create the agent tool wrapper and build our orchestrator agent. The orchestrator will be a general-purpose assistant that can handle various types of questions, using the calculator assistant when mathematical expertise is needed.

// Create agent tool for calculator
const [calculatorToolFunction, calculatorToolSchema] = createAgentTool(
  calculatorAssistant,
  "Call the calculator assistant to solve mathematical problems and equations."
);

// Create a general assistant
const helpfulAssistant = new Agent({
  name: "helpful_assistant",
  systemPrompt: "You are a helpful assistant. You can assist with various tasks and use the calculator tool for math problems.",
  tools: { [calculatorToolSchema.name]: calculatorToolFunction },
  toolSchemas: [calculatorToolSchema]
});

The orchestrator agent (helpfulAssistant) has a deliberately general system prompt that doesn't limit it to any specific domain. Instead, it's designed to be helpful across various tasks while knowing it has access to mathematical expertise through the calculator tool. Notice how we pass the calculator tool to the orchestrator just like any other tool — from the orchestrator's perspective, calling another agent is no different from calling a mathematical function. We use computed property syntax { [calculatorToolSchema.name]: calculatorToolFunction } to dynamically set the tool name as the object key.

Testing General Knowledge Questions

Let's test our orchestrated system with a general knowledge question to see how the orchestrator handles tasks that don't require specialist agents.

// Create a message list with a general knowledge question
let messages: Anthropic.MessageParam[] = [
  {
    role: 'user', 
    content: 'What is the capital of France?'
  }
];

// Run the orchestrator agent
let [resultMessages, response] = await helpfulAssistant.run(messages);

// Display the orchestrator's response to the user
console.log(response);

When we run this code, the orchestrator agent will receive the question and analyze whether it needs specialist assistance. Since this is a straightforward factual question about geography, the agent should recognize that it can provide the answer directly from its general knowledge without calling any tools.

Here's what happens when we execute this request:

The capital of France is Paris. This is a straightforward geographical fact that doesn't require any calculations, so I can answer it directly without using the calculator tool.

The orchestrator correctly identified this as a question it could handle directly using its general knowledge without calling any tools. The response was immediate and appropriate, demonstrating the system's efficiency for simple tasks that don't require specialist assistance.

Testing Mathematical Problem Delegation

Now let's test the system with a mathematical question that requires the specialist agent's expertise.

// Create a message list with a math question
messages = [
  {
    role: 'user', 
    content: 'What is the solution to the equation x² - 5x + 6 = 0?'
  }
];

// Run the orchestrator agent
[resultMessages, response] = await helpfulAssistant.run(messages);

// Display the orchestrator's response to the user
console.log('\n=== Final Response ===\n');
console.log(response);

In this example, the orchestrator will recognize that solving a quadratic equation requires mathematical expertise beyond basic knowledge. The agent should automatically decide to delegate this task to the calculator assistant tool, which will then use its mathematical functions to solve the equation step by step.

Here's the complete execution trace showing the delegation and calculation process:

🔧 Tool called: calculator_assistant_agent({"message":"Solve the quadratic equation x² - 5x + 6 = 0"})
🦾 Agent tool called (calculator_assistant): Solve the quadratic equation x² - 5x + 6 = 0
🔧 Tool called: power({"base":-5,"exponent":2})
🔧 Tool called: multiply_numbers({"a":4,"b":1})
🔧 Tool called: multiply_numbers({"a":4,"b":6})
🔧 Tool called: subtract_numbers({"a":25,"b":24})
🔧 Tool called: square_root({"number":1})
🔧 Tool called: multiply_numbers({"a":-1,"b":-5})
🔧 Tool called: sum_numbers({"a":5,"b":1})
🔧 Tool called: multiply_numbers({"a":2,"b":1})
🔧 Tool called: divide_numbers({"a":6,"b":2})
🔧 Tool called: subtract_numbers({"a":5,"b":1})
🔧 Tool called: divide_numbers({"a":4,"b":2})
📊 Agent response (calculator_assistant): **Solution:**

The quadratic equation x² - 5x + 6 = 0 has two solutions:
- **x₁ = 3**
- **x₂ = 2**

**Verification by factoring:**
The equation can be factored as (x - 3)(x - 2) = 0, which gives us the same solutions: x = 3 and x = 2.

**Check:**
- For x = 3: 3² - 5(3) + 6 = 9 - 15 + 6 = 0 ✓
- For x = 2: 2² - 5(2) + 6 = 4 - 10 + 6 = 0 ✓

=== Final Response ===

The solution to the equation x² - 5x + 6 = 0 is:

**x = 3** and **x = 2**

This quadratic equation can be solved by factoring it as (x - 3)(x - 2) = 0. When a product equals zero, at least one of the factors must be zero, so either x - 3 = 0 (giving x = 3) or x - 2 = 0 (giving x = 2).

Both solutions can be verified by substituting back into the original equation:
- For x = 3: 3² - 5(3) + 6 = 9 - 15 + 6 = 0 ✓
- For x = 2: 2² - 5(2) + 6 = 4 - 10 + 6 = 0 ✓

For the mathematical equation, the orchestrator recognized that this required mathematical expertise and delegated the task to the calculator assistant. The debug output shows the agent tool being called with the original question, the calculator assistant processing the request and providing a complete mathematical solution with factoring, explanation, and verification. The orchestrator then received this detailed response from the specialist agent and presented the final answer to the user, demonstrating the full delegation and response cycle.

Summary & Exercises

You've now learned how to build agent orchestration systems in which a central planner can dynamically delegate tasks to specialist agents. This architectural pattern offers significant advantages over fixed pipelines: it's more efficient for simple tasks, more flexible for complex problems, and allows you to build increasingly sophisticated specialist agents without complicating the overall system design.

In your upcoming practice exercises, you'll build your own orchestrated agent systems with multiple specialists for different domains. This foundation will enable you to create much more sophisticated and flexible agentic systems that can adapt their approach based on the specific requirements of each task.

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal