Solo is the voice agent pattern where one agent, with one prompt and every tool, handles the whole phone call. It is the pattern every voice agent starts as, and the one most of them should stay as for longer than their builders think. One agent picks up the phone, holds the entire conversation, and has every tool it needs in reach.
There is nothing between the caller and the model. No router, no handoff, no second agent waiting in the wings. That is why it is the fastest pattern in the catalogue.
Use it when
The call has one job and a handful of tools. Hours and directions, a simple booking, a FAQ line, a lead capture form read aloud. If you can describe the agent’s job in one sentence and its tools fit on one hand, Solo is the right answer.
For an agency, Solo is also the right first version for almost every client. It ships in a day, it is easy to explain on a sales call, and it gives you real transcripts to learn from before you design anything more elaborate.
Outgrow it when
The prompt keeps growing. Every new edge case becomes another paragraph, and different phases of the call start to need different rules. “Never quote a price during intake” sits next to “always quote the price at checkout”, and the model has to keep both in its head on every turn.
Accuracy slips quietly before it fails loudly. You will see it as small things first: a tool called at the wrong moment, a rule followed on Monday and ignored on Tuesday. When that happens, look at the next three patterns:
- Several intents in one call, or structured data to collect: Supervisor.
- Phases that need different voices, tools or permissions: Relay.
- Required fields in a fixed order: Guided Path.
Latency
The lowest per turn. The caller’s words go to one model, which answers or calls a tool. If a tool is slow, keep Solo and add a Background Worker rather than splitting the agent.
Build it on LiveKit
A Solo agent is one Agent class with function tools on it.
from livekit.agents import Agent, RunContext, function_tool
class Receptionist(Agent):
def __init__(self) -> None:
super().__init__(
instructions=(
"You answer the phone for Rosa's Trattoria. Be brief and warm. "
"Use the tools for hours, directions and bookings. "
"Never invent a time that the booking tool did not return."
),
)
@function_tool()
async def opening_hours(self, ctx: RunContext, day: str) -> str:
"""Return the opening hours for a day of the week."""
return HOURS.get(day.lower(), "closed")
@function_tool()
async def book_table(self, ctx: RunContext, name: str, party_size: int, time: str) -> str:
"""Book a table. Returns the confirmed time or the nearest free slot."""
return await reservations.book(name=name, party_size=party_size, time=time)
The receptionist template in ShipVoice Pro is a Solo agent, and it stays one until a client’s calls give it a reason not to be.
How it combines
Solo is the default inside every other pattern. A Supervisor’s tasks are small Solo agents. Each phase of a Relay is a Solo agent. Wrap any of them in Bookends and you have the shape most production agents ship with.
Common questions
How many tools can a Solo voice agent handle?
There is no fixed limit, but accuracy drops as tools and rules pile up. If the agent's job fits in one sentence and its tools fit on one hand, Solo is a good fit.
When should I split a single voice agent into several?
Split when you see concrete signals: a prompt that keeps growing, phases that need different tools or permissions, structured data to collect, or callers who need to fix earlier answers.
Is a slow tool a reason to leave Solo?
No. Keep the single agent and run the slow tool as a Background Worker so the caller is never left in silence.
Sources and further reading
Mahimai Raja
Mahimai builds production voice agents on LiveKit for small businesses and maintains the open-source livekit-starter. He is building ShipVoice, the voice AI platform for agencies. Find him on X or at [email protected].