Skip to main content

Agent Handoffs & Shared State

Introduction

Last lesson wired agents through a central supervisor (L144) — one orchestrator in control. But what if there is no hub? In the decentralized world — the network topology we deferred — agents coordinate directly with each other. That takes two distinct primitives, and this lesson is about both:

Handoffs transfer control"you take it from here." Shared state shares information — a common workspace every agent reads and writes. A handoff answers "who acts next?"; shared state answers "what does everyone know?"

They're the coordination fabric of the swarm model (and they quietly show up inside supervisor systems too). And both hide the same trap — the one that sinks most multi-agent systems: context. Move control without moving context, and the next agent makes decisions that conflict with what the last one already figured out.

In this lesson:

  • Handoffs — transferring control to a specialist that owns the conversation (the swarm model)
  • The hard part of a handoff — context: what travels with control, and why partial handoffs fail (hands-on)
  • Shared state — the blackboard: agents coordinating through a shared object, with reducers and a schema-contract
  • Why context-sharing is non-negotiable (Cognition's two principles) and the consistency problems shared state brings

Scope: this lesson is coordination within one system. The open A2A protocol for agents across different systems is next (L146 — The A2A (Agent-to-Agent) Protocol); the deep coordination & cost trade-offs are L147 (Coordination & Cost); and designing a full system is L148 (Designing a Multi-Agent System). Builds on the supervisor topology (L144 — Supervisor / Hierarchical Architectures) and memory (L132–L133 — Why Agents Need Memory → Short-Term vs Long-Term Memory).

An infographic titled 'Agent Handoffs and Shared State' showing how agents coordinate without a central supervisor, using two complementary primitives. On the left, HANDOFFS transfer control: a triage agent passes the conversation to a billing specialist, drawn as an arrow labelled 'you take it from here', with the specialist now owning the conversation directly with the user. A callout warns that a handoff moves control but not automatically context, and that the payload matters: passing nothing or only the last message makes the specialist fail or make a conflicting decision, passing the full transcript is noisy and expensive, and a structured summary of goal, what was tried, account state, and the open question is the best handoff. On the right, SHARED STATE shares information: a central blackboard or shared state object that every agent reads from and writes to, instead of sending messages directly, so a researcher posts findings and a writer reads them, coordinating indirectly. A callout notes the state schema is a contract all agents must honor, that a reducer or merge strategy is needed so two concurrent writes do not clobber each other, and that ignoring this causes race conditions, stale reads, and lost updates. The center ties them together with Cognition's two principles: share context, and share full agent traces not just messages; and actions carry implicit decisions, so when agents cannot see each other's work they make conflicting assumptions and produce misaligned results. Cards summarize the rules: a handoff transfers control, so pass a structured summary, not a raw transcript and not nothing; shared state shares information through a blackboard with a schema and a merge strategy; and the failure to avoid is lost context, which causes conflicting implicit decisions. The takeaway: in a decentralized multi-agent system, handoffs answer who acts next and shared state answers what everyone knows, and both must carry context deliberately or the agents drift into conflicting decisions.

Handoffs — Transferring Control

A handoff is one agent transferring responsibility to another: when an agent hits something outside its scope, it passes the conversation to a specialist — and from that point, the specialist owns the conversation, responding to the user directly.

That's the sharp difference from the supervisor (L144): a supervisor calls a worker as a tool and keeps control (the user only ever talks to the orchestrator); a handoff gives control away — there's no central hub and no reporting back. The canonical picture is a triage star: a front-desk agent classifies a request and hands off to billing, tech, or returns; the chosen specialist then drives. (Many frameworks let a specialist return to triage when the user's intent shifts — billing → security → re-route.)

Mechanically, a handoff is just a tool call. You give each agent a transfer_to_X tool; the model calls it when the task belongs elsewhere. OpenAI's Agents SDK (the production successor to the experimental Swarm) makes the handoff its central abstraction; the model emits a handoff tool-call and the runtime swaps in the new agent. Crucially, that tool call can carry a payload — and the payload is where handoffs live or die:

# A HANDOFF transfers control to a specialist — and the PAYLOAD makes or breaks it.
# The triage agent decides to hand off AND writes a structured summary (not the raw chat):
from anthropic import Anthropic
client = Anthropic()

HANDOFF_TOOL = {"name": "handoff_to_billing",
    "description": "Transfer the conversation to the billing specialist.",
    "input_schema": {"type": "object", "properties": {
        "goal":    {"type": "string"},   # what the customer wants
        "tried":   {"type": "string"},   # what's already been attempted
        "account": {"type": "string"},   # the relevant state
        "open":    {"type": "string"},   # the unresolved question
    }, "required": ["goal", "tried", "open"]}}

# Triage runs with the handoff tool; calling it passes CONTROL + a STRUCTURED summary on:
m = client.messages.create(model="claude-opus-4-8", max_tokens=1024,
        system="You are a triage agent. If billing is needed, call handoff_to_billing "
               "with a tight summary — never dump the whole transcript.",
        messages=conversation, tools=[HANDOFF_TOOL])

for b in m.content:
    if b.type == "tool_use" and b.name == "handoff_to_billing":
        brief = b.input                          # {goal, tried, account, open}
        run_specialist("billing", brief=brief)   # Billing now OWNS the conversation
# OpenAI's Agents SDK does this natively — handoff(billing_agent, input_filter=summarize) —
# where input_filter controls exactly which history the specialist sees.

This is the swarm model: specialized agents passing control peer-to-peer, the system tracking who's active. It shines for triage and escalation — customer service, where each specialist should own its part of the conversation. But notice what the code is careful about: it doesn't just transfer control, it hands over a structured brief. Skip that, and you hit the central problem of handoffs.

The Hard Part of a Handoff — Context

Here's the trap: a handoff moves control, but it does not automatically move context. The new agent only knows what you give it — and there's a spectrum of bad-to-good choices:

  • Nothing → the specialist cold-starts and re-asks everything. Worst handoff; the user repeats themselves.
  • The last message only → looks fine, fails subtly: the specialist misses what the first agent already discovered and makes a conflicting decision.
  • The full transcript → nothing's lost, but it's noisy and expensive, and the key fact can get buried (lost-in-the-middle, L132 — Why Agents Need Memory).
  • A structured summary → goal, what was tried, state, the open question. Signal, no noise — the best handoff.

Frameworks expose exactly this knob: the OpenAI Agents SDK's input_filter decides which history the receiving agent sees (full, filtered, or collapsed to a summary), and on_handoff can fire side-effects (fetch the account record) the moment a handoff is triggered. Context loss on handoff is a leading cause of multi-agent breakdowns — feel it: choose the payload and watch the specialist succeed or fail:

A triage agent has gathered a customer's goal, what they already tried (the cancel button is hidden because they're on legacy billing), and their account state — then hands off to a billing specialist. Choose what travels with the handoff: nothing (Billing re-asks everything), the last message only (Billing runs the normal cancel flow and FAILS because it never learned about legacy billing — a conflicting implicit decision), the full transcript (works but noisy and expensive, key fact buried), a structured summary of goal/tried/state/open-question (resolved in one turn — the best handoff), or a shared scratchpad both agents read and write (context was never transferred — it lived on shared state, so nothing is lost). Teaches that control is not context: pass a structured summary or keep context on shared state.

The lesson generalizes past customer service: whatever the new agent can't see, it will re-decide — possibly differently. Which is the perfect motivation for the second primitive. What if the context never had to be handed over at all, because it lived somewhere both agents could always see?

Shared State — The Blackboard

Shared state flips the model: instead of agents passing information to each other, they read and write a common workspace — a blackboard. A researcher posts its findings; a writer reads them and posts a draft; a critic reads the draft and posts feedback. Nobody messages anybody directly — they coordinate indirectly through the shared store. (It's a classic AI architecture, going back to the Hearsay-II speech system in the 1970s.)

In modern frameworks this is concrete. In LangGraph, the graph's state object is the blackboard, defined by a schema, with every agent a node that reads the state and returns a partial update. The key detail is the reducer — an annotation that says how updates merge:

# SHARED STATE — a "blackboard" every agent reads & writes, instead of messaging directly.
# In LangGraph the graph STATE is the blackboard; a reducer says HOW updates merge:
from typing import Annotated, TypedDict
from operator import add

class TeamState(TypedDict):
    messages: Annotated[list, add]    # reducer = APPEND (not overwrite) → no lost updates
    findings: Annotated[list, add]    # each agent appends what it discovered
    decision: str                     # a single-writer field — who sets it is a contract

# Each agent is a node that READS the shared state and WRITES a partial update:
def researcher(state: TeamState):
    facts = search(state["messages"][-1].content)
    return {"findings": [facts]}              # merged via `add` → appended to the shared list

def writer(state: TeamState):
    draft = compose(state["findings"])        # reads what the researcher already posted
    return {"decision": draft}
# The state SCHEMA is a contract every agent must honor; the reducer stops two concurrent
# writes from clobbering each other — the #1 shared-state bug. (LangGraph also bundles a
# handoff + state update into one Command(goto="writer", update={...}).)

Two design rules fall out of that snippet. First, the state schema is a contract: every agent must agree on the shape — findings is a list you append to, decision is a single field. Second, you choose a merge strategy per field: an add-reducer appends (so two agents' findings both survive), while a plain field overwrites (so you must decide who's allowed to write it). Get the contract right and agents compose cleanly; get it wrong and they silently corrupt each other's work. Often you keep both kinds of memory: a shared blackboard for what the team needs, plus each agent's private scratchpad for its own working notes (shared whiteboard vs. personal notebook).

Share Context, or Pay for It

Why does any of this matter so much? Because of a principle the Cognition team (builders of Devin) put bluntly in "Don't Build Multi-Agents" — the same essay behind L143 (Why Multiple Agents?)'s skepticism. Two rules:

  1. Share context — and share full agent traces, not just messages. An agent's context is everything it did: the files it read, the questions it asked, the answers it got. Passing a one-line message between agents isn't enough; ideally they share the full decision trace.
  2. Actions carry implicit decisionsand conflicting decisions carry bad results. When an agent acts, it makes implicit choices (a naming style, an edge-case assumption). If a second agent can't see those choices, it makes different ones, and the outputs don't line up.

This is the deep reason handoffs and shared state are the same lesson: both exist to make sure every agent sees what the others decided. A lossy handoff or an un-shared scratchpad reintroduces exactly the failure L143 (Why Multiple Agents?) warned about — dispersed, conflicting decisions. Whether you pass context (handoff) or pool it (shared state), the imperative is identical: no agent should have to guess what another already settled.

The Catch in Shared State — Consistency

Shared state solves context, but it imports a classic problem from distributed systems: consistency. The moment two agents can write the same place — especially if they run in parallel — you risk:

  • Lost updates — two agents read, both write, the second clobbers the first.
  • Stale reads — an agent acts on a value another agent already changed.
  • Race conditions — the result depends on who happens to finish first.

The fixes are the same ones databases use, scaled down:

  • Reducers / merge strategies — make writes commutative (append to a list, union a set) so order doesn't matter — LangGraph's add_messages is exactly this.
  • Single-writer fields — designate one agent as the owner of each field (only the planner writes plan); everyone else reads.
  • A serialized event log / message bus — agents publish events; one processor applies them in order, so there's a single source of truth.

The rule of thumb: shared, mutable state across parallel agents needs a merge plan — never assume last-write-wins is fine. If you can make every write append-only and commutative, most of the hazard disappears. (This is also why supervisor systems, L144 — Supervisor / Hierarchical Architectures, are simpler: a single thread of control means far fewer concurrent writes to reconcile.)

How They Fit Together

Step back and the picture is clean. Coordination has two axes, and you mix them freely:

  • Controlwho acts next? A handoff transfers it peer-to-peer; a supervisor (L144) keeps it central.
  • Informationwhat does everyone know? Shared state pools it on a blackboard; message-passing ships it point-to-point.

A pure swarm is handoffs + shared state: agents hand control around and coordinate through a common workspace, no boss. A supervisor system is central control + (often) shared state for the team's working context. Most real systems are a blend — e.g., a supervisor that delegates, but whose workers all read a shared scratchpad so none of them re-decides what another already settled.

The takeaway isn't "handoffs vs. shared state" — it's that they're orthogonal tools for the two things coordination requires. Pick how control moves; pick how information is shared; and in both dimensions, carry context deliberately. Next, L146 (The A2A (Agent-to-Agent) Protocol) standardizes all of this across organizations with the open A2A protocol — handoffs and shared context between agents that don't even share a codebase.

🧪 Try It Yourself

Reason these through, then check with the handoff lab:

  1. A handoff and a supervisor delegation both involve a "specialist." In one sentence, what's the core difference in who's in control afterward?
  2. Your triage agent hands off to billing passing only the customer's last message. It usually works but sometimes the specialist does the wrong thing. Why — and name the Cognition principle.
  3. You switch to passing the entire transcript on every handoff. What two problems did you just buy?
  4. Two agents run in parallel and both write state["summary"]. One overwrites the other. What's this called, and give two fixes.
  5. When would you reach for shared state instead of richer handoff payloads?

(1) After a handoff the specialist owns the conversation (control transfers, no hub); after a supervisor delegation the orchestrator stays in control and the specialist is just a tool it called. (2) The last message lacks what triage already discovered (e.g. legacy billing), so billing makes a conflicting implicit decision — Cognition's "actions carry implicit decisions"; the fix is a structured summary, not one message. (3) Noise (the key fact gets buried — lost-in-the-middle) and cost (you pay for every token, every handoff). (4) A lost update (race condition); fix with a reducer/merge (append instead of overwrite, e.g. add_messages) or a single-writer field (only one agent owns summary), or serialize writes through an event log. (5) When many agents need the same evolving context (so repeatedly summarizing-and-passing is wasteful or lossy), or when work is parallel and you want one shared source of truth — a blackboard means context can't be dropped on a handoff at all.

Mental-Model Corrections

  • "A handoff is the same as calling a sub-agent." No — calling a worker (supervisor/agents-as-tools, L144 — Supervisor / Hierarchical Architectures) keeps control and gets a result back; a handoff gives control away — the specialist now owns the conversation.
  • "Handing off transfers the context automatically." It transfers control; the context is whatever you put in the payload. Pass nothing or one message and the specialist is flying blind.
  • "More context in the handoff is always better." The full transcript is noisy and expensive and buries the signal. A structured summary (goal · tried · state · open question) beats a raw dump.
  • "Shared state is just a global variable." It's a schema-defined contract with merge rules (reducers). Treating it as a free-for-all global gives you race conditions and lost updates.
  • "Agents on a blackboard talk to each other." They don't — they coordinate indirectly, reading and writing the shared store. No direct messages; that's the point.
  • "Handoffs and shared state are competing choices." They're orthogonal: handoffs move control, shared state pools information. A swarm uses both.

Key Takeaways

  • Decentralized coordination = two primitives. Handoffs transfer control (a specialist takes over, no hub — the swarm model); shared state shares information (a blackboard every agent reads/writes). Control vs. information are orthogonal axes.
  • A handoff is a tool call that gives control away. Unlike a supervisor (keeps control, L144 — Supervisor / Hierarchical Architectures), the specialist now owns the conversation. Built into the OpenAI Agents SDK (successor to Swarm); a triage star with optional return-to-triage.
  • Control is not context. A handoff moves control but not knowledge — pass a structured summary (goal · tried · state · open question), not nothing, not one message, not the noisy full transcript (input_filter tunes this).
  • Shared state = a blackboard with a contract. Agents coordinate indirectly through a schema-defined state object; reducers (e.g. add_messages) define how writes merge. Keep a private scratchpad per agent too.
  • Share context or pay (Cognition): share full traces, not just messages, because actions carry implicit decisions — unshared context → conflicting decisions (the L143 — Why Multiple Agents? failure, again).
  • Shared, mutable, parallel state needs a merge plan — beware lost updates, stale reads, races; fix with commutative reducers, single-writer fields, or a serialized event log.
  • Next — L146: The A2A (Agent-to-Agent) Protocol — standardizing handoffs and shared context across organizations, for agents that don't share a codebase.