The multi-agent scaffold
Three agents with three near-identical constructors is a smell. Shared behavior belongs in a base class. Shared state belongs in a typed dataclass the agents can all access. Get this scaffold right and every new specialist costs almost nothing to add.
Multi-agent architecture
Specialists inherit from a base agent and share state through a typed UserData dataclass.
from dataclasses import dataclass, field
from typing import Optional
from livekit.agents import JobContext
from livekit.agents.voice import Agent, RunContext
@dataclass
class UserData:
"""Shared state that every agent can read and write."""
personas: dict[str, Agent] = field(default_factory=dict)
prev_agent: Optional[Agent] = None
ctx: Optional[JobContext] = None
def summarize(self) -> str:
return "Medical office triage system with multiple specialized agents"
# Type alias so every tool signature reads cleanly
RunContext_T = RunContext[UserData]personas is a registry of all agents so any agent can transfer to any other by name. prev_agent tracks who just spoke so the next agent can copy their chat context. ctx holds the LiveKit room so agents can update room attributes.
from livekit.agents.voice import Agent
class BaseAgent(Agent):
"""Common behavior for every specialist."""
async def on_enter(self) -> None:
"""Lifecycle hook: called when this agent takes over the session."""
agent_name = self.__class__.__name__
userdata: UserData = self.session.userdata
# Track which agent is active so the UI can show it
if userdata.ctx and userdata.ctx.room:
await userdata.ctx.room.local_participant.set_attributes(
{"agent": agent_name}
)
# Start the conversation
self.session.generate_reply()Every agent inherits on_enter from BaseAgent. The hook fires the moment a handoff completes, updates room attributes, and triggers the first reply from the new specialist.
Flashcards: Flashcards
Loading practice…
Quiz: Quiz
Loading practice…