Docs Sessions
Sessions
Connection lifecycle, reactive session state, transcripts, typed turns, feedback, and the cleanup contract.
Lifecycle
A session moves through three states: disconnected, connecting, and connected. startConversation() does all of the preparation in a fixed order,
then resolves after the server sends its metadata or after a start failure.
Validate the target and read the microphone and signed URL settings. Obvious mistakes fail before a permission prompt appears.
Create the audio graph inside the user gesture. This includes the microphone track, the input context, and the playback context. Text only sessions skip this step.
Resolve the WebSocket URL. A public id is converted to a ConvAI URL, an existing
wss://target is used directly, and signed mode calls your server route.Open the socket, send the initiation frame, and wait for
conversation_initiation_metadata. The controller has a 15 second handshake timeout.
Connection and audio failures populate error and errorCode instead of
rejecting the start promise. Read them after it settles, or pass onError when the controller
is created. Calling start while a session is active is a no-op.
You can also end a session with the alias endSession().
Session state
| Field | Type | Notes |
|---|---|---|
status | ConversationStatus | Connection lifecycle. |
mode | ConversationMode | Single label: disconnected, connecting, ready, listening, or speaking. |
canStart | boolean | True when the controller is disconnected. |
isSpeaking | boolean | Agent playback is scheduled or audible. |
isListening | boolean | Microphone is live and not muted. |
isUserSpeaking | boolean | Local voice activity detection hears speech. |
isMuted | boolean | Outgoing audio is stopped. |
isTextOnly | boolean (getter) | Current session or next session runs without audio devices. |
volume | number | Loudest level from 0 to 1. |
inputVolume | number | Normalized microphone level. |
outputVolume | number | Normalized agent playback level. |
transcripts | TranscriptMessage[] | Streaming transcript entries. |
messageCount | number | Derived from the transcript length. |
message | TranscriptMessage | null | Most recently created or updated entry. |
error | string | null | Safe to render. |
errorCode | ConversationErrorCode | null | Stable category for UI branches. |
latency | number | null | Latest time to first audio, in milliseconds. |
latencyHistory | number[] | Recent measurements, oldest first. Keeps 12. |
conversationId | string | null | ElevenLabs conversation id after negotiation. |
inputAudioFormat | AudioFormatId | null | Negotiated microphone format. |
outputAudioFormat | AudioFormatId | null | Negotiated agent format. |
queueStatus | QueueStatus | null | Concurrency queue state. |
interruptions | number | Barge-ins, deduplicated within one second. |
canSendFeedback | boolean | Feedback is available for the current response. |
vadScore | number | Latest server voice activity score. |
contextUsage | ContextUsageServerEvent['context_usage_event'] | null | LLM context window usage. |
outputGain | number | Playback gain from 0 to 1. |
inputDeviceId | string | null | Current microphone device id. |
outputDeviceId | string | null | Current output device id. |
Transcripts
Entries have a stable id. Partial agent text updates the same entry as it streams,
and corrections replace the text in place. Check isFinal when you want to style streaming
content differently.
{#each conversation.transcripts as message (message.id)} <article data-role={message.role}> <strong>{message.role === 'user' ? 'You' : 'Agent'}</strong> <p>{message.text}</p> {#if !message.isFinal} <span aria-label="Still streaming">...</span> {/if} </article>{/each}The transcript is capped by maxTranscripts, which defaults to 250 and is clamped to
at least 20. Starting a new session clears the transcript unless resetTranscriptsOnStart is set to false. clearTranscripts() clears it without touching the server conversation.
Agent entries can include attachments and a responseId. Files are
plain objects with url, name, and an optional mime_type.
Text turns
Text only sessions use the same socket and protocol without opening a microphone. You can also send typed turns during a voice session.
await conversation.startConversation('agent_your_public_id', { textOnly: true});conversation.sendUserMessage('Show my last order.');conversation.sendContextualUpdate('The customer is on the checkout page.', 'checkout');conversation.sendUserActivity();sendUserMessage(text)adds a typed user turn and returnsfalsewhen the socket is not open or the text is empty.sendContextualUpdate(text, contextId?)injects context for the next turn without interrupting speech. Reusing a context id replaces older context.sendUserActivity()signals that the user is present. Calls are throttled to one frame per second.
Set textOnly on the controller when the whole page is a chat, or pass it per start call
when the same controller needs both modes. A per-start value wins for that session.
Mute and playback
conversation.toggleMute();conversation.setMuted(true);conversation.setVolume(0.8);// Object form is accepted for API familiarity.conversation.setVolume({ volume: 0.65 });Muting stops outgoing chunks but keeps the microphone track and audio context alive, so unmuting
is instant. setVolume clamps the value to the 0 to 1 range and updates outputGain.
Barge-in is local and server assisted. When local voice activity crosses vadThreshold and interruptOnUserSpeech is enabled, queued playback is flushed immediately. A
server interruption event does the same. The controller counts one interruption when both signals
arrive together.
Feedback
Feedback becomes available after an agent response supplies an event id. Render controls only
when canSendFeedback is true.
{#if conversation.canSendFeedback} <button onclick={() => conversation.sendFeedback(true)}>Helpful</button> <button onclick={() => conversation.sendFeedback(false)}>Not helpful</button>{/if}sendFeedback(like, eventId?) defaults to the latest agent response. It returns false when feedback is not available or the socket is closed.
Cleanup
useConversation registers an $effect teardown. On unmount it closes
the socket, stops media tracks, disconnects the audio graph, aborts signed URL requests, clears
timers, and closes both audio contexts. Set autoCleanup: false only when you intend
to call destroy() yourself.
import { createConversation } from 'svelte-elevenlabs';const conversation = createConversation({ agentId: 'agent_your_public_id'});await conversation.startConversation();// Later, or from a store teardown:conversation.destroy();stopConversation() ends the call but keeps the transcript for a post-call view. destroy() is idempotent. The controller also stops itself on pagehide so a navigation does not leave a socket running.