# Handling Handoffs Between Agents in Swarm Group Chat: A Complete Guide

> Master agent handoffs in AutoGen Swarm group chat. Learn how `HandoffMessage` and `SwarmGroupChatManager` enable seamless control transfers. Get the complete guide.

- Repository: [Microsoft/autogen](https://github.com/microsoft/autogen)
- Tags: how-to-guide
- Published: 2026-03-07

---

**In AutoGen's Swarm group chat, agents transfer control exclusively through `HandoffMessage` objects, with the `SwarmGroupChatManager` selecting the next speaker by scanning the message thread backwards for the most recent handoff target.**

The `Swarm` team implementation in the **microsoft/autogen** repository provides a lightweight, deterministic approach to handling handoffs between agents in Swarm group chat. Unlike other group chat patterns that rely on LLM-based speaker selection, Swarm routes conversations exclusively via explicit `HandoffMessage` declarations, making it ideal for deterministic workflows and human-in-the-loop scenarios.

## Understanding the Swarm Handoff Architecture

### Core Components for Agent Handoffs

- **`Swarm`**: The public API class that validates the first participant can emit handoffs and wires the team to `SwarmGroupChatManager`. Located in [`python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_swarm_group_chat.py`](https://github.com/microsoft/autogen/blob/main/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_swarm_group_chat.py) (lines 26-45).

- **`SwarmGroupChatManager`**: The runtime engine that validates startup handoffs, selects speakers by scanning for `HandoffMessage` targets, and manages state persistence. Implementation spans lines 47-113 in the same file.

- **`HandoffMessage`**: The exclusive routing mechanism carrying a `target` field (next agent name) and optional `context`. Defined in [`python/packages/autogen-agentchat/src/autogen_agentchat/messages.py`](https://github.com/microsoft/autogen/blob/main/python/packages/autogen-agentchat/src/autogen_agentchat/messages.py) (lines 21-30).

- **`SwarmManagerState`**: Pydantic model for serializing the manager's internal state including `message_thread`, `current_turn`, and `current_speaker`. Found in [`python/packages/autogen-agentchat/src/autogen_agentchat/state/_states.py`](https://github.com/microsoft/autogen/blob/main/python/packages/autogen-agentchat/src/autogen_agentchat/state/_states.py) (lines 57-58).

### The Handoff Lifecycle in Swarm Group Chat

1. **Initialization**: The first agent in the `participants` list becomes the initial speaker. The `Swarm` constructor asserts this agent can emit `HandoffMessage` objects.

2. **Handoff Emission**: An agent emits a `HandoffMessage` with `target=<next_agent_name>`. The manager validates the target exists in `validate_group_state` (lines 47-73).

3. **Speaker Selection**: The manager scans the message thread backwards to find the latest `HandoffMessage` and sets `_current_speaker` accordingly in `select_speaker` (lines 82-98). If no handoff is present, the current speaker continues.

4. **State Persistence**: `save_state` (lines 100-107) serializes the thread and speaker, while `load_state` (lines 108-113) restores execution context.

## Implementing Agent Handoffs in Swarm Group Chat

### Basic Automatic Handoffs Between Two Agents

The following example demonstrates a two-agent Swarm where Alice automatically hands off to Bob:

```python
import asyncio
from autogen_ext.models.openai import OpenAIChatCompletionClient
from autogen_agentchat.agents import AssistantAgent
from autogen_agentchat.teams import Swarm
from autogen_agentchat.conditions import MaxMessageTermination

async def main() -> None:
    client = OpenAIChatCompletionClient(model="gpt-4o")

    alice = AssistantAgent(
        "Alice",
        model_client=client,
        handoffs=["Bob"],                     # Alice must be able to hand off to Bob

        system_message="You are Alice; answer only about yourself."
    )
    bob = AssistantAgent(
        "Bob",
        model_client=client,
        system_message="You are Bob; your birthday is 1 Jan."
    )

    termination = MaxMessageTermination(3)    # stop after three messages

    swarm = Swarm([alice, bob], termination_condition=termination)

    async for msg in swarm.run_stream(task="When is Bob's birthday?"):
        print(msg)

asyncio.run(main())

```

Key implementation details:
- `Swarm([alice, bob])` creates a hand-off-driven team.
- The first agent (`alice`) must have `handoffs` configured so it can emit a `HandoffMessage`.
- The conversation ends when `MaxMessageTermination` fires.

### Human-in-the-Loop Handoffs with HandoffTermination

For scenarios requiring human intervention, use `HandoffTermination` to pause execution when control transfers to a user:

```python
import asyncio
from autogen_ext.models.openai import OpenAIChatCompletionClient
from autogen_agentchat.agents import AssistantAgent
from autogen_agentchat.teams import Swarm
from autogen_agentchat.conditions import HandoffTermination, MaxMessageTermination
from autogen_agentchat.ui import Console
from autogen_agentchat.messages import HandoffMessage

async def main() -> None:
    client = OpenAIChatCompletionClient(model="gpt-4o")

    alice = AssistantAgent(
        "Alice",
        model_client=client,
        handoffs=["user"],                     # declares a human handoff target

        system_message="You are Alice; ask the user for help when needed."
    )
    termination = HandoffTermination(target="user") | MaxMessageTermination(3)
    swarm = Swarm([alice], termination_condition=termination)

    # 1️⃣ Alice asks the user a clarification question

    await Console(swarm.run_stream(task="Explain the concept of recursion."))

    # 2️⃣ Human replies via a hand‑off message

    await Console(
        swarm.run_stream(
            task=HandoffMessage(source="user", target="Alice",
                                 content="Recursion is a function calling itself.")
        )
    )

asyncio.run(main())

```

Key points:
- `HandoffTermination(target="user")` stops the AI loop when a `HandoffMessage` targeting `"user"` appears.
- The `Console` UI streams messages; the second call resumes the same `Swarm` instance with a handcrafted `HandoffMessage`.

### Persisting and Restoring Swarm State

Swarm supports full serialization of conversation state, enabling pause-and-resume workflows:

```python

# Assume `swarm` is a running Swarm instance

state = await swarm.save_state()          # serialises the whole chat manager

# …persist `state` to a DB or file…

# Later, recreate the same agents and load the saved state

new_swarm = Swarm([alice, bob], termination_condition=termination)
await new_swarm.load_state(state)         # resumes exactly where we left off

```

The saved JSON contains the message thread, the current turn index, and the name of the agent that will speak next (`_current_speaker`).

## Core Source Files and Implementation Details

| File | Purpose | Direct link |
|------|---------|-------------|
| [`python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_swarm_group_chat.py`](https://github.com/microsoft/autogen/blob/main/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_swarm_group_chat.py) | `Swarm` class, manager implementation, speaker selection, validation, state persistence | [Swarm implementation](https://github.com/microsoft/autogen/blob/main/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_swarm_group_chat.py) |
| [`python/packages/autogen-agentchat/src/autogen_agentchat/messages.py`](https://github.com/microsoft/autogen/blob/main/python/packages/autogen-agentchat/src/autogen_agentchat/messages.py) | Definition of `HandoffMessage` (target, optional context) | [HandoffMessage definition](https://github.com/microsoft/autogen/blob/main/python/packages/autogen-agentchat/src/autogen_agentchat/messages.py) |
| [`python/packages/autogen-agentchat/src/autogen_agentchat/state/_states.py`](https://github.com/microsoft/autogen/blob/main/python/packages/autogen-agentchat/src/autogen_agentchat/state/_states.py) | `SwarmManagerState` model used for checkpointing | [SwarmManagerState](https://github.com/microsoft/autogen/blob/main/python/packages/autogen-agentchat/src/autogen_agentchat/state/_states.py) |
| [`python/packages/autogen-agentchat/tests/test_group_chat.py`](https://github.com/microsoft/autogen/blob/main/python/packages/autogen-agentchat/tests/test_group_chat.py) | Unit tests that illustrate hand‑off flows, state round‑tripping and optional team‑event emission | [Swarm hand‑off tests](https://github.com/microsoft/autogen/blob/main/python/packages/autogen-agentchat/tests/test_group_chat.py) |
| [`python/packages/autogen-agentchat/src/autogen_agentchat/teams/__init__.py`](https://github.com/microsoft/autogen/blob/main/python/packages/autogen-agentchat/src/autogen_agentchat/teams/__init__.py) | Exposes `Swarm` in the public package namespace | [Team package init](https://github.com/microsoft/autogen/blob/main/python/packages/autogen-agentchat/src/autogen_agentchat/teams/__init__.py) |

These files together form the complete implementation of how **Swarm** orchestrates hand‑offs between agents, validates targets, and preserves conversational state.

## Summary

- **Swarm** routes conversations exclusively via `HandoffMessage` objects, not LLM-based selection.
- The `SwarmGroupChatManager` selects the next speaker by scanning backwards for the most recent handoff target in `select_speaker`.
- Agents must declare their handoff capabilities via the `handoffs` parameter in `AssistantAgent`.
- State persistence is handled through `SwarmManagerState`, enabling conversation checkpointing and resumption via `save_state` and `load_state`.
- Human-in-the-loop workflows are supported via `HandoffTermination` targeting a user agent.

## Frequently Asked Questions

### How does Swarm differ from other AutoGen group chat implementations?

Unlike `RoundRobinGroupChat` or `SelectorGroupChat`, Swarm does not use an LLM to choose the next speaker. Instead, it relies exclusively on explicit `HandoffMessage` declarations from agents, making it deterministic and ideal for structured workflows where predictable routing is required.

### Can an agent hand off to multiple targets in a single turn?

An agent emits a single `HandoffMessage` with one `target` field. However, the agent can be configured with multiple potential handoff targets in its `handoffs` list, and conditional logic within the agent can determine which specific target to specify for each turn based on conversation context.

### What happens if an agent emits a HandoffMessage targeting an unregistered agent?

The `SwarmGroupChatManager` validates all handoff targets in `validate_group_state`. If a `HandoffMessage` targets an agent not present in the participants list, the validation will fail and raise an appropriate error before the next speaker selection occurs, preventing invalid routing.

### How do I resume a Swarm conversation after a system restart?

Use `await swarm.save_state()` to serialize the current state (including the message thread and current speaker) to a JSON-compatible dictionary. Persist this state to a database or file. After recreating the agent instances, instantiate a new `Swarm` and call `await new_swarm.load_state(state)` to resume execution from the exact point of interruption.