Skip to main content

LiveKit

The @mastra/livekit package connects Mastra agents to the LiveKit Agents framework. LiveKit runs the audio pipeline (voice activity detection, speech-to-text, turn detection, text-to-speech, barge-in) and the package bridges reply generation to a Mastra agent's stream() call.

See Using LiveKit with Mastra for setup and concepts.

The package has two entry points, one per process:

createLiveKitWorker()
Direct link to createlivekitworker

Builds a LiveKit agent definition that answers voice sessions with Mastra agents. Use it as the default export of your worker entry file.

src/mastra/voice-worker.ts
import { fileURLToPath } from 'node:url'
import { createLiveKitWorker, runLiveKitWorker } from '@mastra/livekit/worker'
import { mastra } from './index'

export default createLiveKitWorker({
mastra,
agent: 'support',
stt: 'deepgram/nova-3',
tts: 'cartesia/sonic-3',
turnDetection: 'multilingual',
})

if (process.argv[1] === fileURLToPath(import.meta.url)) {
runLiveKitWorker({ entry: import.meta.url, agentName: 'mastra-voice' })
}

Options
Direct link to Options

mastra:

Mastra
The Mastra instance whose agents handle voice sessions.

agent?:

string | (args) => string | Agent | Promise<string | Agent>
Which Mastra agent answers each session: a fixed agent key or id, or a resolver called per session with the dispatch metadata and job context. Defaults to the agentId from the dispatch metadata.

workflow?:

string | Workflow | (args) => string | Promise<string>
Generate each turn's reply with a Mastra workflow instead of an agent: a Workflow instance, a fixed workflow key or id, or a resolver that returns a workflow id per session. The workflow runs once to completion per turn (no suspend or resume). Mutually exclusive with agent; requires workflowInput.

workflowInput?:

(args: VoiceTurnContext & { metadata }) => unknown | Promise<unknown>
Maps a turn into the workflow inputData. Required when workflow is set. A stateless mapping that passes the full transcript each turn avoids carrying conversation state in the workflow.

replyStep?:

string
Only stream text from this workflow step id. Defaults to every step that writes to its writer.

resultText?:

(result: unknown) => string | undefined
Fallback when the workflow streams no text via writer: derive the spoken reply from the final run result.

generate?:

VoiceReplyGenerator
Lowest-level escape hatch: supply any reply generator directly (a custom workflow, remote bridge, and so on).

stt?:

STT | string
Speech-to-text: a LiveKit plugin instance or an inference model string such as 'deepgram/nova-3'.

tts?:

TTS | string
Text-to-speech: a LiveKit plugin instance or an inference model string such as 'cartesia/sonic-3'.

vad?:

VAD | 'silero' | false
= 'silero'
Voice activity detection. 'silero' loads the Silero VAD from @livekit/agents-plugin-silero during prewarm. Pass an instance to bring your own, or false to disable.

turnDetection?:

'multilingual' | 'english' | TurnDetectionMode
End-of-turn detection. 'multilingual' and 'english' load LiveKit's semantic turn detector from @livekit/agents-plugin-livekit. Other values such as 'vad', 'stt', or 'manual' pass through.

turnHandling?:

Partial<TurnHandlingOptions>
Turn handling tuning: endpointing delays, interruption sensitivity, preemptive generation. The worker disables preemptiveGeneration unless set here — each preemptive attempt re-runs the Mastra agent and persists a duplicate user message.

sessionOptions?:

Partial<AgentSessionOptions>
Extra LiveKit AgentSession options merged over what this helper builds.

memory?:

false | ((args) => { thread, resource } | false)
Memory mapping. Defaults to { thread: metadata.threadId ?? room name, resource: metadata.resourceId ?? thread } when the resolved agent has memory configured. Pass false to disable, or a function to customize.

toolFeedback?:

(toolCall) => string | undefined
Called when the Mastra agent starts a tool call mid-reply. Return a short phrase to speak while the tool runs.

greeting?:

string
Static greeting spoken when the session starts.

persistGreeting?:

boolean
= true
Save the spoken greeting to the memory thread as an assistant message, making the saved thread a faithful call transcript. Only applies when a greeting is set and memory is enabled.

observability?:

boolean
= true
Trace each call when the Mastra instance has observability configured. Opens a voice call span per session: every turn's agent run nests under it, LiveKit's STT, TTS, end-of-utterance, VAD, and LLM latency metrics become child spans, and the span closes with a per-model usage roll-up. Pass false to disable.

inputOptions?:

Partial<RoomInputOptions>
LiveKit room input options passed to session.start().

outputOptions?:

Partial<RoomOutputOptions>
LiveKit room output options passed to session.start().

onSessionStart?:

(args: { session, ctx, agent, metadata }) => void | Promise<void>
Called after the session starts. Attach event listeners or trigger replies here.

runLiveKitWorker()
Direct link to runlivekitworker

Starts the LiveKit worker CLI (dev, start, and connect subcommands) for a worker entry file. Call it from the file that default-exports the worker definition, guarded so it only runs when executed directly (the worker spawns a child process per session that re-imports the same file). Using this helper instead of cli.runApp from @livekit/agents guarantees the worker runtime and the bridge share one copy of the LiveKit SDK.

Options
Direct link to Options

entry:

string | URL
The worker entry module whose default export is the agent definition. Pass import.meta.url.

agentName?:

string
= 'mastra-voice'
LiveKit agent name for explicit dispatch.

serverOptions?:

Partial<ServerOptions>
Extra LiveKit ServerOptions merged over what this helper builds.

pipeAgentReplyToWriter()
Direct link to pipeagentreplytowriter

Streams a Mastra agent's reply into a workflow step's writer on the workflow reply path. It forwards the agent's text deltas, so text-to-speech starts before the full reply is ready, and its tool-call chunks, so toolFeedback fires and onTurnComplete sees the tool list. Piping only stream.textStream silently drops tool calls. Pass the step's abortSignal to agent.stream() so barge-in stops generation promptly.

src/mastra/workflows/phone-conversation.ts
import { pipeAgentReplyToWriter } from '@mastra/livekit'

const generateResponse = createStep({
id: 'generateResponse',
// input and output schemas omitted
execute: async ({ inputData, mastra, writer, abortSignal }) => {
const stream = await mastra.getAgent('support').stream(inputData.turn, { abortSignal })
const reply = await pipeAgentReplyToWriter(stream, writer)
return { reply }
},
})

Returns: Promise<string>, the accumulated reply text.

Parameters
Direct link to Parameters

agentStream:

AgentReplyStreamLike
The stream returned by agent.stream() — anything exposing a fullStream async iterable.

writer:

WritableStream<unknown>
The workflow step's writer.

chatContextToMessages()
Direct link to chatcontexttomessages

Converts a LiveKit chat context into plain messages accepted by agent.stream(), excluding instructions and function calls. Use it in workflowInput to pass the full transcript into a stateless workflow.

src/mastra/voice-worker.ts
import { createLiveKitWorker, chatContextToMessages } from '@mastra/livekit/worker'

export default createLiveKitWorker({
mastra,
workflow: 'phoneConversation',
workflowInput: ({ chatCtx }) => ({ history: chatContextToMessages(chatCtx) }),
})

Returns: VoiceTurnMessage[], where each entry is { role: 'system' | 'user' | 'assistant'; content: string }.

liveKitConnectionRoute()
Direct link to livekitconnectionroute

Returns an API route that mints a LiveKit access token with the voice agent dispatched into the room. Frontends call it to join a session.

src/mastra/index.ts
import { Mastra } from '@mastra/core/mastra'
import { liveKitConnectionRoute } from '@mastra/livekit'

export const mastra = new Mastra({
server: {
apiRoutes: [liveKitConnectionRoute({ agentName: 'mastra-voice' })],
},
})

The route accepts a JSON body with optional agentId, threadId, and resourceId fields and responds with { serverUrl, roomName, participantName, participantToken }. The threadId defaults to the generated room name.

Options
Direct link to Options

path?:

string
= '/voice/livekit/connection-details'
Route path.

serverUrl?:

string
= process.env.LIVEKIT_URL
LiveKit server URL.

apiKey?:

string
= process.env.LIVEKIT_API_KEY
LiveKit API key.

apiSecret?:

string
= process.env.LIVEKIT_API_SECRET
LiveKit API secret.

agentName?:

string
= 'mastra-voice'
LiveKit agent name for explicit dispatch. Must match the worker's agentName.

ttl?:

string | number
= '15m'
Token time-to-live.

requiresAuth?:

boolean
= true
Whether the route requires authentication.

roomName?:

string | (args) => string
Room name or a function that derives one from the request.

participantIdentity?:

string | (args) => string
Participant identity or a function that derives one from the request.

metadata?:

(args) => LiveKitSessionMetadata | Promise<LiveKitSessionMetadata>
Builds the session metadata delivered to the worker. Defaults to passing through agentId, threadId, and resourceId from the request body.

dispatchVoiceSession()
Direct link to dispatchvoicesession

Dispatches a Mastra voice agent into a LiveKit room programmatically — for server-initiated sessions such as outbound calls.

import { dispatchVoiceSession } from '@mastra/livekit'

await dispatchVoiceSession({
roomName: 'support-call-42',
agentName: 'mastra-voice',
metadata: { agentId: 'support', threadId: 'thread-42' },
})

Options
Direct link to Options

roomName:

string
Room to dispatch the agent into. Created on demand.

agentName?:

string
= 'mastra-voice'
Must match the worker's agentName.

metadata?:

LiveKitSessionMetadata
Session metadata: agentId, threadId, resourceId, requestContext.

serverUrl?:

string
= process.env.LIVEKIT_URL
LiveKit server URL.

apiKey?:

string
= process.env.LIVEKIT_API_KEY
LiveKit API key.

apiSecret?:

string
= process.env.LIVEKIT_API_SECRET
LiveKit API secret.

LiveKitSessionMetadata
Direct link to livekitsessionmetadata

The metadata passed from the Mastra server to the worker through LiveKit job dispatch.

agentId?:

string
Mastra agent to run, by registered key or agent id.

threadId?:

string
Memory thread id. Defaults to the LiveKit room name.

resourceId?:

string
Memory resource id, typically the end user id.

requestContext?:

Record<string, unknown>
Plain-object entries restored into a RequestContext for agent execution.

The metadata travels as a JSON string. liveKitConnectionRoute() and dispatchVoiceSession() serialize it for you; use serializeSessionMetadata(metadata) when dispatching through your own code, or write the JSON directly in LiveKit-side configuration such as a SIP dispatch rule. Entries in requestContext reach the agent's dynamic instructions, tools, and input processors on every turn of the call.