New: Voice AI Orchestration Benchmarks — Retell, Vapi, Pipecat, LiveKit & more

LiveKit Agents: What They Are and How to Build One (2026)

Shashij Gupta
Written byJUL 28, 202617 MIN READ
Shashij GuptainExpert verified
Co-founder & CTO, Cekura

Has stress-tested 5M+ voice agent minutes at Cekura.

Why Trust Cekura on Voice AI Evals

  • Built by engineers from Google, Apple, Microsoft. Backed by Y Combinator.
  • 60K+ voice AI calls evaluated daily.
  • Native integration for every major voice AI stack: LiveKit, Pipecat, Vapi, Retell, ElevenLabs.

We run simulations against LiveKit agents every day at Cekura. Check your entry point before you write anything, because the WorkerOptions sample still published on PyPI sits a version behind the AgentServer pattern in LiveKit's own docs.

Here is what LiveKit Agents is in 2026, how to build one in six steps, and what to test before real callers arrive.

TL;DR

  • LiveKit Agents is an open-source Python and Node.js framework that drops your program into a LiveKit room as a realtime participant. Apache-2.0, 11k+ GitHub stars, 360+ releases across the monorepo.
  • The current entrypoint is AgentServer plus a @server.rtc_session() handler. The WorkerOptions snippet still sitting on PyPI is behind the docs.
  • Six steps take you from lk agent init to a deployed agent that answers a phone call.
  • LiveKit's built-in test helpers run text-only by design. Audio, accents, telephony, and noise need a layer on top.

What Is LiveKit Agents?

LiveKit Agents is an open-source framework that adds any Python or Node.js program to a LiveKit room as a full realtime participant. Your code sits between the caller and the AI models, feeding realtime media through a pipeline and publishing the results back to the room.

The whole ecosystem is Apache-2.0. The Python repo carries more than 3,600 commits and over 3.4k forks, and the package declares optional extras for every supported provider, from Deepgram and Cartesia to Silero and OpenAI, plus utility extras for codecs, images, and MCP.

The Four Objects You Actually Write

Four pieces carry the framework. When you learn what each one owns, the code stops looking arbitrary.

ObjectWhat it ownsHow you use it
AgentInstructions, tools, and chat context for one roleSubclass it
AgentSessionThe runtime wiring STT, LLM, TTS, and turn handling into a conversationConstruct one per call
AgentServerThe long-lived process that registers with LiveKit and schedules jobsOne per program
JobContextThe handle to a single call, including the roomArrives as an argument to your handler

The session handler is the closest thing to a request handler in a web server. One call in, one conversation out.

How an Agent Joins a Call

Your agent registers before it ever hears a caller. The flow runs in four moves, all described in LiveKit's framework overview.

  1. Your code starts and registers with a LiveKit server, self-hosted or Cloud, as an agent server process.
  2. The agent server idles until a dispatch request arrives.
  3. It boots a job subprocess that joins the room. By default, agent servers get dispatched to every new room in the project.
  4. Agent and user talk over LiveKit WebRTC. Your agent talks to your own backend over HTTP and WebSockets.

Callers can also arrive by phone through SIP, in which case there is no frontend at all.

LiveKit Agents vs. LiveKit Cloud vs. Agent Builder vs. Agents UI

Four products answer to roughly the same search, and picking the wrong one can cost you a week. Here's what each name refers to.

NameWhat it isReach for it when
🧩 LiveKit AgentsThe open-source Python and Node.js SDKYou want agent logic in code
☁️ LiveKit CloudManaged dispatch, deployment, inference, and observabilityYou would rather not run media servers
🖱️ Agent BuilderA browser tool that builds an agent with no codeYou are prototyping an idea
🎨 Agents UIReact and shadcn components for the caller-facing frontendYou need a visualizer and a transcript view

LiveKit is direct about the tradeoff on Agent Builder. It gets you moving in the browser, and it carries fewer features than the full SDK.

Agents UI installs through the shadcn registry with npx shadcn@latest registry add @agents-ui, and ships five audio visualizer styles with states for connecting, speaking, listening, and thinking.

What You'll Need Before Starting

Prerequisites:

  • Python 3.10 or newer for the Python SDK. The published package caps at Python 3.14. Node.js users need version 24.0.0 or newer and pnpm 10 or newer.
  • A package manager the docs assume. uv for Python, pnpm for Node.js.
  • A LiveKit Cloud project, or a self-hosted LiveKit server. The free Build plan includes 1,000 agent session minutes, Inference credits, and one US local number for inbound calls.
  • The LiveKit CLI, installed and linked to your project.
  • Model access. LiveKit Inference covers STT, LLM, and TTS with your LiveKit key. Bringing your own provider keys works too.

Time required: LiveKit's quickstart promises a talking agent in under 10 minutes, and that number holds if you use the starter template. Budget two to three hours to add real tools, tune turn-taking, and deploy.

How to Build a LiveKit Agent: Step-by-Step

Step 1: Scaffold the Project With lk agent init

Install the CLI, link your project, then let the template do the boilerplate. The CLI clones a starter repo, writes an .env.local with your credentials, and prints the commands to run next.

Each starter ships a working agent, a test suite, and an AGENTS.md tuned for coding assistants.

# macOS
brew install livekit-cli

# Linux
curl -sSL https://get.livekit.io/cli | bash

# Windows
winget install LiveKit.LiveKitCLI

lk cloud auth
lk agent init my-agent --template agent-starter-python
cd my-agent
uv sync

Pro tip: The name you pass to lk agent init becomes your dispatch name, and deployment preserves it. Pick something you can live with, because explicit dispatch will target that exact string later.

Step 2: Choose Your Pipeline

Two architectures run a LiveKit agent, and the choice changes your latency, your cost, and your turn detection. A chained pipeline strings three models together, while a real-time model takes audio in and puts audio out in one pass.

Swappability is what you gain from chaining, and speed is what you gain from a single model.

🔗 STT-LLM-TTS⚡ Realtime model
ShapeThree models in a rowOne model, audio to audio
Swap one componentYes, per providerNo
LatencyA tax at every handoffLower, single pass
Turn detectionYou supply VAD or a turn detectorBuilt into the model
Quickstart defaultDeepgram Nova-3, Gemma 4 31B, Inworld TTS-2OpenAI Realtime, coral voice

Pro tip: Test both against your own audio before you commit. A chained pipeline lets you replace one weak component, but a realtime model makes you replace the whole thing.

Step 3: Write the Agent

Subclass Agent for the persona, construct an AgentSession for the runtime, and hang a session handler off an AgentServer. The decorator on the handler is what routes a dispatched job into your code.

Instructions carry more weight in voice than in chat. Punctuation, asterisks, and markdown all get read aloud by the TTS, so ask for plain, speakable sentences.

from dotenv import load_dotenv
from livekit import agents
from livekit.agents import Agent, AgentServer, AgentSession, TurnHandlingOptions, inference

load_dotenv(".env.local")


class Assistant(Agent):
    def __init__(self) -> None:
        super().__init__(
            instructions=(
                "You are a scheduling assistant for a dental office. "
                "Keep answers short and speakable. No lists, no symbols, no emojis."
            ),
        )


server = AgentServer()


@server.rtc_session(agent_name="my-agent")
async def my_agent(ctx: agents.JobContext):
    session = AgentSession(
        stt=inference.STT(model="deepgram/nova-3", language="multi"),
        llm=inference.LLM(model="google/gemma-4-31b-it"),
        tts=inference.TTS(model="inworld/inworld-tts-2", voice="Ashley"),
        turn_handling=TurnHandlingOptions(turn_detection=inference.TurnDetector()),
    )
    await session.start(room=ctx.room, agent=Assistant())
    await session.generate_reply(
        instructions="Greet the caller and offer to book an appointment."
    )


if __name__ == "__main__":
    agents.cli.run_app(server)

Pro tip: If a tutorial hands you agents.cli.run_app(agents.WorkerOptions(entrypoint_fnc=entrypoint)), it predates this API. The PyPI project page still contains that older sample even though the docs and the GitHub README moved on.

Step 4: Give It Tools

Decorate a method with @function_tool() and the LLM can call it. The function name becomes the tool name, the type hints become the schema, and the docstring becomes the description the model reads when deciding whether to call it.

Vague docstrings produce wrong tool calls. Say what the tool does, when to use it, and what comes back.

from typing import Any
from livekit.agents import Agent, RunContext, function_tool
from livekit.agents.llm import ToolError


class Assistant(Agent):
    @function_tool()
    async def check_availability(
        self,
        context: RunContext,
        day: str,
    ) -> dict[str, Any]:
        """Check open appointment slots for a given day.

        Args:
            day: The day to check, for example Tuesday.
        """
        slots = await lookup_slots(day)
        if not slots:
            raise ToolError(f"No schedule for {day}. Ask the caller to pick another day.")
        return {"day": day, "slots": slots}

ToolError hands a readable message back to the model so it can recover in conversation. Any other exception returns a generic internal error to the LLM, and LiveKit logs the real one for you.

Pro tip: Call context.disallow_interruptions() at the top of any tool that writes data. By default, a caller talking over the agent discards the tool result while the code keeps running, which can leave an order half-placed with nothing to roll back.

Step 5: Tune Turn Detection and Interruptions

Turn handling decides whether your agent feels like a person or a walkie-talkie. Everything lives in a TurnHandlingOptions object passed to AgentSession.

Leave turn_detection off, and the session picks the best available mode in priority order, falling back through realtime_llm, vad, stt, and manual as models are available.

from livekit.agents import AgentSession, TurnHandlingOptions

session = AgentSession(
    turn_handling=TurnHandlingOptions(
        turn_detection="vad",
        endpointing={"mode": "fixed", "min_delay": 0.5, "max_delay": 3.0},
        interruption={
            "mode": "adaptive",
            "min_duration": 0.5,
            "min_words": 0,
            "resume_false_interruption": True,
        },
    ),
)

If you change nothing, you get these values.

SettingDefaultWhat it controls
endpointing.min_delay0.5sSilence before the agent calls your turn finished
endpointing.max_delay3.0sCeiling, so the agent never waits forever
interruption.min_duration0.5sSpeech length that counts as a barge-in
interruption.min_words0Word count that counts as a barge-in
false_interruption_timeout2.0sSilence after a barge-in before it is ruled a false alarm
resume_false_interruptionTrueAgent picks its sentence back up after a false alarm

Two gotchas hide in min_delay. In VAD mode it behaves like max(VAD silence, min_delay). In STT mode it stacks on top of your STT provider's own endpointing, so the caller waits for both.

Pro tip: Switching to the audio turn detector changes the defaults underneath you. Any min_delay and max_delay you leave unset become 0.3 and 2.5 seconds instead of 0.5 and 3.0, because the model gives a confident end-of-turn signal sooner. Node.js takes all of these in milliseconds.

Step 6: Run It

Four modes cover the lifecycle, and each one behaves differently. Start in console, iterate in dev, and ship with start.

ModeCommandWhat it does
consolelk agent consoleTerminal session on your machine, no LiveKit connection. Python only
devlk agent devAuto-reload, debug logging, reachable from the internet
startuv run src/agent.py startProduction logging, load balancing, graceful drain on SIGTERM
connectuv run src/agent.py connect --room my-test-roomOne direct connection to a named room

Console mode still needs your credentials in the environment if you use LiveKit Inference, and it cannot take them as flags. Run lk app env -w to write them into .env.local first.

Pro tip: lk agent console --record saves the session to console-recordings/session-<timestamp>/ as JSON. Recording your own bad calls beats trying to remember what the agent said wrong. Add --text to skip audio when you are only checking logic.

How to Test a LiveKit Agent Before You Ship

LiveKit ships behavioral test helpers that work with pytest in Python and Vitest in Node.js. Feed a user turn to session.run(), then assert on the events that come back, including an LLM judge for open-ended replies.

from livekit.agents import AgentSession, inference
from agent import Assistant


@pytest.mark.asyncio
async def test_assistant_greeting() -> None:
    async with (
        inference.LLM(model="google/gemma-4-31b-it") as llm,
        AgentSession(llm=llm) as session,
    ):
        await session.start(Assistant())
        result = await session.run(user_input="Hello")
        await result.expect.next_event().is_message(role="assistant").judge(
            llm, intent="Makes a friendly introduction and offers assistance."
        )
        result.expect.no_more_events()

Set LIVEKIT_EVALS_VERBOSE=1 and pytest's -s flag to see every event and judgment as it runs. In CI, the helpers hit your live LLM provider, so LIVEKIT_API_KEY and LIVEKIT_API_SECRET need to exist as secrets. No room connection is made.

The helpers are designed for text input and output, and LiveKit says so on the page itself. Accents, packet loss, background noise, barge-in timing, and the telephony path are all outside what they cover.

That same page sends readers to third-party services for end-to-end audio testing, and Cekura is one of the tools listed there.

Common Mistakes to Avoid

Every error below survives code review. They surface on a real call, on a real phone, weeks later.

  • Copying the PyPI code sample. The project README on PyPI still shows WorkerOptions(entrypoint_fnc=...), while the docs and the GitHub README use AgentServer with @server.rtc_session(). Read the docs first, then the package page.
  • Running unpinned. The Python package went from 1.5.0 on March 19, 2026 to 1.6.7 on July 25, 2026. That is 26 releases in about 18 weeks, including a minor version bump to 1.6. Pin an exact version and upgrade deliberately.
  • Assuming every published version is safe. Four releases have been pulled, including 1.2.7 for a regression that left agents stuck mid-tool-call, and 1.5.18 on June 5, 2026, tagged simply "not ready for release." (The others were 1.1.2 and 1.3.4). Read the yank reasons on PyPI before you chase a version bump.
  • Treating text evals as audio coverage. A test suite that passes on typed input says nothing about how the agent handles a caller on a cell connection in a parking lot.
  • Calling get_job_context() in a test. It raises a RuntimeError outside a job. Avoid the code path or mock it with unittest.mock.
  • Leaving barge-in on defaults for phone calls. A 0.5 second interruption window is generous on a clean mic and jumpy on a noisy line. Set min_words above zero if coughs are cutting your agent off.
  • Writing instructions like a chat prompt. Markdown, bullets, and asterisks get spoken. Ask for plain sentences.

Taking It Further: Handoffs and Production Deploys

Two problems arrive next. One prompt trying to cover every role and an agent that only runs locally.

Multi-Agent Handoffs

One Agent per role scales better than one giant prompt. Return a new agent instance from a tool, and the framework performs the handoff after the current reply finishes.

@function_tool()
async def transfer_to_billing(self, context: RunContext):
    """Transfer the caller to billing for payment questions."""
    return BillingAgent(chat_ctx=self.chat_ctx), "Transferring you to billing"

Pass chat_ctx along, or the new agent will start with an empty history and ask the caller to repeat themselves. Shared state travels through session.userdata, reachable inside any tool through RunContext.

Deploying to LiveKit Cloud

Deployment is one command from your project directory. lk agent create registers the agent, writes a livekit.toml, generates a Dockerfile if you lack one, uploads the code to LiveKit's build service, and rolls it out.

lk cloud auth
lk agent create
lk agent status
lk agent logs

Agents run in three regions today, and sessions are billed by the minute your agent is actively serving a caller. We covered what that adds up to in our LiveKit pricing breakdown.

Self-hosting stays open. The framework, the media server, and the SIP stack are all Apache-2.0, and you can run the whole thing on your own boxes. You then own the autoscaling, the session placement, and the 3 am pager.

If that math stops working, our LiveKit alternatives roundup covers where else the same workload can live, and our Pipecat comparison covers the other open-source route.

Cekura Makes LiveKit Agent Testing Easier

Cekura runs the calls you cannot place by hand, then watches the ones your callers actually place. It connects to a LiveKit agent two ways. Dial the agent's telephony endpoint, or hand over your LiveKit credentials and let Cekura create rooms and join over WebRTC.

The LiveKit integration docs cover both paths, and there is a tracing SDK that captures full transcripts and tool calls from every session.

Pre-production: Thousands of simulated multi-turn calls before go-live, across off-script callers, adversarial inputs, and the scenarios your text evals never reach. Single-turn checks miss the context and recovery that real conversations demand.

Infrastructure: Interruptions, background noise, latency, and voice activity detection tested against the same agent, so your endpointing settings get proven on messy audio instead of a clean laptop mic.

Observability: Live scoring on every production call for latency, interruptions, sentiment, and instruction following, with alerts when a number moves.

Native integrations work out of the box for Retell, VAPI, ElevenLabs, LiveKit, Pipecat, Bland, and more. You don't rebuild anything. You add a testing and monitoring layer on top of what you already have.

Cekura supports SOC 2, HIPAA, and GDPR compliance, covering transcript redaction, role-based access, and audit trails.

Book a demo to see what your LiveKit agent does on a call you did not script.

Frequently Asked Questions

What is LiveKit Agents?

LiveKit Agents is an open-source framework for building realtime voice AI agents in Python or Node.js. It adds your program to a LiveKit room as a participant, handles the audio pipeline and turn detection, and connects to STT, LLM, and TTS providers through plugins or LiveKit Inference.

Is LiveKit Agents free?

Yes, LiveKit Agents is free and open source under the Apache 2.0 license, and you can self-host the framework and the media server. LiveKit Cloud is the paid option, billed per agent session minute, with a free Build plan that includes 1,000 agent session minutes per month.

What language is LiveKit Agents written in?

LiveKit Agents is written in Python, which makes up 99.3% of the main repository. A separate Node.js and TypeScript SDK lives in the livekit/agents-js repo and tracks the same concepts.

What is the difference between LiveKit Agents and LiveKit Cloud?

The main difference between LiveKit Agents and LiveKit Cloud is what each one does for you. LiveKit Agents is the open-source SDK you write your agent in. LiveKit Cloud is the managed platform that hosts, dispatches, scales, and observes that agent once it is written.

Can you test a LiveKit agent automatically?

Yes, LiveKit Agents includes test helpers for pytest and Vitest that assert on agent behavior and use an LLM judge for open-ended replies. Those helpers run text-only, so audio-layer testing across accents, noise, and telephony requires a dedicated platform such as Cekura.

How do you deploy a LiveKit agent to production?

You deploy a LiveKit agent by running lk agent create from your project directory after authenticating with lk cloud auth. The CLI registers the agent, builds a container image from your Dockerfile, and deploys it to LiveKit Cloud. Self-hosted deployment to your own infrastructure is also supported.

Ready to ship voice
agents fast? 

Book a demo