Skip to main content

What are Handoffs?

Handoffs allow agents to transfer control to other agents, enabling:
  • Task delegation to specialists
  • Multi-step workflows
  • Hierarchical agent systems
  • Dynamic routing based on expertise
When an agent has handoffs, it automatically receives transfer_to_<agent_name> tools.

Basic Handoff

import { agent, swarm } from '@deepagents/agent';
import { openai } from '@ai-sdk/openai';

const specialist = agent({
  name: 'specialist',
  model: openai('gpt-4o'),
  prompt: 'You handle specialized tasks.',
  handoffDescription: 'Handles complex analysis',
});

const coordinator = agent({
  name: 'coordinator',
  model: openai('gpt-4o'),
  prompt: 'You coordinate tasks and delegate to specialists.',
  handoffs: [specialist],
});

// Coordinator automatically gets transfer_to_specialist tool
const stream = swarm(coordinator, 'Analyze this data', {});
The LLM decides when to call transfer_to_specialist based on the task.

Handoff Configuration

handoffDescription

Describes when to use this agent:
const researcher = agent({
  name: 'researcher',
  model: openai('gpt-4o'),
  prompt: 'You research topics thoroughly using available sources.',
  handoffDescription: 'Use for fact-finding, research, and information gathering',
  tools: { search: searchTool },
});

const coder = agent({
  name: 'coder',
  model: openai('gpt-4o'),
  prompt: 'You write and review code.',
  handoffDescription: 'Use for code generation, debugging, and code review',
  tools: { executeCode: codeTool },
});

const coordinator = agent({
  name: 'coordinator',
  model: openai('gpt-4o'),
  prompt: 'Delegate tasks to the appropriate specialist.',
  handoffs: [researcher, coder],
});
The coordinator sees:
  • transfer_to_researcher: “Use for fact-finding, research, and information gathering”
  • transfer_to_coder: “Use for code generation, debugging, and code review”

prepareHandoff

Execute logic before transferring:
const specialist = agent({
  name: 'specialist',
  model: openai('gpt-4o'),
  prompt: 'You are a specialist.',
  prepareHandoff: async (messages) => {
    console.log('Preparing to receive handoff');
    // Modify messages, log, update metrics, etc.
  },
});

prepareEnd

Execute logic after agent completes:
interface TaskContext {
  completedBy: string[];
}

const specialist = agent<unknown, TaskContext>({
  name: 'specialist',
  model: openai('gpt-4o'),
  prompt: 'You are a specialist.',
  prepareEnd: async ({ contextVariables, responseMessage }) => {
    contextVariables.completedBy.push('specialist');
    console.log('Specialist completed task');
  },
});

Multi-Agent Patterns

Sequential Specialists

Each specialist has distinct responsibilities:
const researcher = agent({
  name: 'researcher',
  model: openai('gpt-4o'),
  prompt: 'Research topics and gather facts.',
  handoffDescription: 'Research and fact-finding',
  tools: { search: searchTool },
});

const analyst = agent({
  name: 'analyst',
  model: openai('gpt-4o'),
  prompt: 'Analyze data and draw insights.',
  handoffDescription: 'Data analysis and insights',
});

const writer = agent({
  name: 'writer',
  model: openai('gpt-4o'),
  prompt: 'Write clear, engaging content.',
  handoffDescription: 'Content creation and writing',
});

const coordinator = agent({
  name: 'coordinator',
  model: openai('gpt-4o'),
  prompt: instructions({
    purpose: ['Create comprehensive reports'],
    routine: [
      '1. Use researcher to gather facts',
      '2. Use analyst to analyze findings',
      '3. Use writer to create final report',
    ],
  }),
  handoffs: [researcher, analyst, writer],
});

Nested Coordinators

Coordinators can delegate to other coordinators:
const dataSpecialist = agent({
  name: 'data_specialist',
  model: openai('gpt-4o'),
  prompt: 'You handle data tasks.',
  handoffDescription: 'Data collection and processing',
});

const dataCoordinator = agent({
  name: 'data_coordinator',
  model: openai('gpt-4o'),
  prompt: 'Coordinate data operations.',
  handoffs: [dataSpecialist],
});

const contentSpecialist = agent({
  name: 'content_specialist',
  model: openai('gpt-4o'),
  prompt: 'You create content.',
  handoffDescription: 'Content creation',
});

const contentCoordinator = agent({
  name: 'content_coordinator',
  model: openai('gpt-4o'),
  prompt: 'Coordinate content creation.',
  handoffs: [contentSpecialist],
});

const mainCoordinator = agent({
  name: 'main_coordinator',
  model: openai('gpt-4o'),
  prompt: 'Route to data or content teams.',
  handoffs: [dataCoordinator, contentCoordinator],
});
Avoid deep nesting (>3 levels) as it increases latency and complexity.

Conditional Handoffs

Create handoffs dynamically based on context:
interface ProjectContext {
  projectType: 'web' | 'mobile' | 'data';
}

function createCoordinator(context: ProjectContext) {
  const handoffs = [];
  
  if (context.projectType === 'web') {
    handoffs.push(webSpecialist, frontendSpecialist);
  } else if (context.projectType === 'mobile') {
    handoffs.push(mobileSpecialist, iosSpecialist, androidSpecialist);
  } else {
    handoffs.push(dataSpecialist, mlSpecialist);
  }
  
  return agent({
    name: 'coordinator',
    model: openai('gpt-4o'),
    prompt: `Coordinate ${context.projectType} project tasks.`,
    handoffs,
  });
}

Execution Flow

1

User sends message to coordinator

swarm(coordinator, 'Write a blog post about AI', {});
2

Coordinator analyzes request

Determines that research is needed.
3

Coordinator calls transfer_to_researcher

Control transfers to the researcher agent.
4

Researcher executes

Uses search tool, gathers information, returns results.
5

Control returns to coordinator

Coordinator receives researcher’s output.
6

Coordinator calls transfer_to_writer

Control transfers to the writer agent.
7

Writer creates content

Writes blog post based on research.
8

Final response streams to user

User receives completed blog post.

Context Across Handoffs

Context variables persist across agent transitions:
interface WorkflowContext {
  userId: string;
  steps: string[];
  results: Record<string, any>;
}

const researcher = agent<unknown, WorkflowContext>({
  name: 'researcher',
  model: openai('gpt-4o'),
  prompt: (ctx) => `Research for user ${ctx?.userId}`,
  prepareEnd: async ({ contextVariables, responseMessage }) => {
    contextVariables.steps.push('research');
    contextVariables.results.research = 'Research findings...';
  },
});

const writer = agent<unknown, WorkflowContext>({
  name: 'writer',
  model: openai('gpt-4o'),
  prompt: (ctx) => {
    const research = ctx?.results.research || '';
    return `Write content based on: ${research}`;
  },
  prepareEnd: async ({ contextVariables }) => {
    contextVariables.steps.push('writing');
    contextVariables.results.content = 'Final content...';
  },
});

const coordinator = agent<unknown, WorkflowContext>({
  name: 'coordinator',
  model: openai('gpt-4o'),
  prompt: 'Coordinate workflow.',
  handoffs: [researcher, writer],
});

const ctx: WorkflowContext = {
  userId: 'user123',
  steps: [],
  results: {},
};

const stream = swarm(coordinator, 'Create a report', ctx);
await stream.text;

console.log(ctx.steps); // ['research', 'writing']
console.log(ctx.results); // { research: '...', content: '...' }

Swarm vs Execute

swarm()

Optimized for handoffs:
import { swarm } from '@deepagents/agent';

const stream = swarm(
  coordinator,
  'Complete this task',
  context,
  abortSignal
);
  • Automatically handles agent transfers
  • Maintains conversation history across handoffs
  • Returns unified text stream
  • Supports abort signals

execute()

Lower-level execution:
import { execute } from '@deepagents/agent';

const stream = execute(
  agent,
  messages,
  context,
  { maxSteps: 10 }
);
  • Full control over execution
  • Custom step limits
  • Access to tool calls and deltas
  • Use when you need fine-grained control
Use swarm() for multi-agent systems and execute() for single-agent or custom control flow.

Instructions Helper

The instructions() function creates structured prompts for coordination:
import { instructions } from '@deepagents/agent';

const coordinator = agent({
  name: 'coordinator',
  model: openai('gpt-4o'),
  prompt: instructions({
    purpose: [
      'You coordinate software development tasks',
      'Break down complex problems',
      'Delegate to specialized agents',
    ],
    routine: [
      '1. Analyze the user request',
      '2. Break into subtasks',
      '3. Identify required specialists',
      '4. Delegate using transfer_to_<agent> tools',
      '5. Synthesize results',
      '6. Present final output to user',
    ],
  }),
  handoffs: [/* specialists */],
});
For swarm-specific prompts:
const coordinator = agent({
  name: 'coordinator',
  model: openai('gpt-4o'),
  prompt: instructions.swarm({
    purpose: ['Coordinate specialist agents'],
    routine: ['Delegate appropriately', 'Synthesize results'],
  }),
  handoffs: [/* specialists */],
});

Best Practices

Clear Descriptions

Write specific handoffDescription for each agent

Single Coordinator

Use one coordinator per swarm level

Typed Context

Define TypeScript interfaces for context

Shallow Hierarchies

Avoid deep nesting (>3 levels)

Explicit Routing

Provide clear routing instructions

Context Cleanup

Clean up context in prepareEnd hooks

Anti-Patterns

Avoid these common mistakes:
  • Circular Handoffs - Agent A hands to B, B hands back to A
  • Too Many Handoffs - More than 5 specialists per coordinator
  • Vague Descriptions - Generic handoffDescription values
  • Missing Coordinator - Specialists handing off to each other
  • Deep Nesting - 4+ levels of coordinators

Next Steps

Context Management

Learn about context variables

Streaming

Handle streaming responses

Agent Package

Full API reference

Build docs developers (and LLMs) love