Skip to main content

The activation model

In a multi-agent system, only one agent is active at a time (per bridge). The active agent receives frames from the bus. Inactive agents exist but don’t process frames. Every agent has an active property that defaults to True. In handoff scenarios, voice agents should start with active=False so only one is active at a time — the parent then activates the first one explicitly. You control which agent is active using three methods: handoff_to() is the most common — it’s a single call that transfers control from the current agent to another.

Building a handoff system

Let’s walk through how the two-agent handoff works. You need three pieces:

1. A main agent with a bus bridge

The main agent owns the transport (audio I/O) and places a BusBridgeProcessor in its pipeline instead of an LLM. The bridge routes frames to whichever LLM agent is active:

2. LLM agents with bridged=()

LLM agents set bridged=() to receive frames from the bus. Each one runs its own LLM and defines its own system prompt:
bridged=() means the agent receives frames from all bridges. You can filter by bridge name with bridged=("voice",) if you have multiple bridges.

3. Handoff via tools

The LLM decides when to transfer by calling a tool. The tool calls handoff_to():
For LLMAgent, handoff_to() also accepts a messages parameter. These messages are injected and spoken by the current agent before the transfer happens — useful for announcing the handoff:
When the greeter calls handoff_to("support", ...):
  1. The greeter is deactivated — it stops receiving frames from the bus
  2. The support agent is activated with the provided arguments
  3. The support agent’s on_activated() fires, injecting the reason message into its LLM context
  4. The support agent starts responding to the user
The transition is seamless — the user experiences it as a natural conversation flow.

Activation arguments

When activating an LLMAgent, you can pass LLMAgentActivationArgs to give the target agent context about why it was activated:
The target agent receives these messages in its LLM context and immediately runs the LLM to respond.

Waiting for agents to be ready

Before you can activate an agent, it needs to be ready (its pipeline must be started). Use the @agent_ready decorator to declare a handler that fires when a specific agent registers:
The framework automatically watches for the named agent when the main agent starts. If the agent is already registered, the handler fires immediately. This is the typical pattern: the main agent waits for the first LLM agent, then activates it to start the conversation.

Putting it all together

Here’s the full flow:
1

Runner starts

AgentRunner creates the bus and starts the main agent’s pipeline.
2

Client connects

The main agent creates child LLM agents and adds them via add_agent().
3

Agent becomes ready

The @agent_ready handler fires on the parent. The main agent activates the greeter.
4

User speaks

Audio flows: transport -> STT -> BusBridge -> bus -> greeter’s LLM -> bus -> BusBridge -> TTS -> transport.
5

LLM decides to transfer

The greeter’s LLM calls transfer_to_agent. The tool calls handoff_to("support", ...).
6

Handoff completes

Greeter deactivates, support activates with context. Audio now flows through the support agent.

What’s next

Handoff transfers a conversation between agents. But sometimes you need agents to do work in parallel. Next, let’s look at task coordination.

Task Coordination

Dispatch work to multiple agents in parallel