Skip to main content
AI Agent Handoff: Don't Lose Context Between Agents

AI Agent Handoff: Don't Lose Context Between Agents

AI Integration
••5 min read•By Daily Miranda Pardo

80% of bugs in multi-agent pipelines aren't in the agents. They're in the handoff.

When an orchestrator passes control to a specialized sub-agent, something gets lost. Not because of the model, but because of how we design the transfer: what information travels, in what format, and with what guarantees it arrives intact.

It's the telephone game with a 200-token budget.

Why Handoffs Break What's Working

The common pattern: the orchestrator receives the task, reasons through it, and calls the sub-agent passing the full conversation history — or worse, a summary it generates on the fly.

The problem is three-fold:

The full history won't fit. Three rounds of tool use plus the first 50 context messages is 8,000 tokens of history when the sub-agent only needs to know what to do right now.

The summary loses critical information. The orchestrator doesn't know which parts are critical to the sub-agent. It summarizes what it thinks matters. The sub-agent needs something else. The error happens silently, with no trace to explain it.

No schema versioning. You update the orchestrator; the sub-agent inherits a different format without knowing it. The system works 90% of the time. The remaining 10% fails in ways that don't reproduce.

The HandoffPayload Pattern in TypeScript

The fix is treating the handoff as a typed contract, not free text that "the model will figure out."

import { z } from 'zod'

const HandoffPayloadSchema = z.object({
  version: z.literal('1.0'),
  goal: z.string().describe('Original objective unchanged'),
  constraints: z.array(z.string()).optional(),
  completedSteps: z.array(z.object({
    tool: z.string(),
    result: z.string(),
    timestamp: z.string()
  })),
  activeContext: z.record(z.string(), z.unknown()),
  returnTo: z.string().optional()
})

type HandoffPayload = z.infer<typeof HandoffPayloadSchema>

This schema does three concrete things:

  1. Separates the goal from history. The sub-agent receives the original objective uncontaminated by the orchestrator's intermediate reasoning noise.
  2. Serializes results, not reasoning. completedSteps carries each tool call output — not the chain of thought.
  3. Versions the contract. When the schema changes, the sub-agent detects the incompatibility before executing.

Building the Handoff in the Orchestrator

async function buildHandoff(
  originalGoal: string,
  toolResults: ToolResult[],
  context: Record<string, unknown>
): Promise<HandoffPayload> {
  const payload = {
    version: '1.0' as const,
    goal: originalGoal,
    completedSteps: toolResults.map(r => ({
      tool: r.toolName,
      result: r.output.slice(0, 500), // cap size per step
      timestamp: new Date().toISOString()
    })),
    activeContext: context,
    returnTo: process.env.ORCHESTRATOR_ID
  }

  return HandoffPayloadSchema.parse(payload) // validate before sending
}

The 500-character cap per step result is intentional. If a result is longer, the sub-agent fetches it with its own tool call to the source system. The handoff is not the data repository — it's the map.

Three Mistakes We See in Every Production Pipeline

Mistake 1: Passing the full messages array to the sub-agent

The orchestrator's chat history has {role, content}[] format. The sub-agent appends it to its own history. Context grows exponentially at each pipeline step. By the fourth agent in the chain, you're already in overflow.

Mistake 2: Missing returnTo

The sub-agent completes its work but has no destination to report to. A response is generated that no one collects. This shows up in logs as "completed successfully" when nothing actually arrived at its destination.

Mistake 3: Using activeContext as a junk drawer

Stuffing everything that "might be relevant" into activeContext creates a 30-field object where the sub-agent can't tell which fields are critical. The model weights them all equally. Define the minimum necessary context: not what could matter, but what must matter.

Handoff with Error Recovery

When a sub-agent fails, the orchestrator needs to know the handoff's final state to decide between retrying or escalating.

interface HandoffResult {
  status: 'completed' | 'failed' | 'partial'
  output?: string
  error?: {
    code: string
    step: string
    recoverable: boolean
  }
  finalContext?: Record<string, unknown>
}

async function executeWithHandoff(
  subAgent: SubAgent,
  payload: HandoffPayload
): Promise<HandoffResult> {
  try {
    const result = await subAgent.run(payload)
    return {
      status: 'completed',
      output: result.output,
      finalContext: result.context
    }
  } catch (error) {
    const isRecoverable =
      error instanceof ContextLossError ||
      error instanceof TimeoutError

    return {
      status: 'failed',
      error: {
        code: error.code,
        step: error.lastStep,
        recoverable: isRecoverable
      }
    }
  }
}

The recoverable flag lets the orchestrator distinguish between "retry with the same payload" and "escalate to a human because context is irrecoverably lost." Without it, all errors look the same and the system retries when it shouldn't.

What This Changes in Production

With this pattern in three-or-more agent pipelines:

  • Context errors drop 60-70%. The sub-agent always has the original goal, never a degraded version of it.
  • Timeouts are detected earlier. A version mismatch aborts the handoff at validation, before spending tokens.
  • Retries are safe. HandoffPayload is stateless and can be resent without duplication risk.

The pattern adds no measurable latency. Payload serialization is microseconds; Zod validation is milliseconds. What it does eliminate is the hours of debugging when a handoff fails silently in production with no trace.

If your orchestrator passes the full messages array to sub-agents, or you have no typed handoff schema, or a sub-agent failure leaves the orchestrator not knowing what state the system is in — you have this problem.

Designing this correctly from the start saves weeks. Our AI integration service builds multi-agent pipelines with typed handoffs, observability from day one, and versioned context contracts between agents. This is the kind of architecture you design before the problem costs you money — not after.

If you already have agents in production and suspect the issues are at the handoff points, let's look at it together:

Let's review your pipeline →

Share article

Repetitive processes in your business?

Download the free AI Automation Map — the 5 most time-consuming processes and how to fix them.

Controller: DailyMP (Daily Miranda Pardo). We use your email only to send you the PDF, unless you tick the box below. More info in the privacy policy.

Written by Daily Miranda Pardo

I help businesses automate processes, build AI agents and connect intelligent systems.