Realtime events
Every message on the data channel, grouped by direction and family — the protocol at a glance.
All control traffic is JSON on a reliable LiveKit data channel, discriminated by a type field. Use the map below; each SDK exposes the same events with language-native names (TypeScript session.on('transcript', …), Python TranscriptDeltaEvent, Swift case .transcript). Subscribing is one stream (or emitter) per session:
session.on('transcript', (event) => {
if (event.isFinal) console.log(event.role, event.text);
});from cosmo_ai import TranscriptDeltaEvent
async for event in session:
if isinstance(event, TranscriptDeltaEvent) and event.is_final:
print(event.role.value, event.text)for try await event in session.events {
if case .transcript(let delta) = event, delta.isFinal {
print(delta.role, delta.text)
}
}Two framing rules apply to everything below:
- Oversized messages are chunked. Anything above 12,000 bytes travels as
envelope-chunk/server-envelope-chunkframes and is reassembled before your handler fires. You never see chunks. See Envelope chunking. - Unknown types aren't errors. SDKs surface unrecognized
typevalues as an explicitunknownevent and keep going. New server events never break old clients.
Client → server
Each client → server message and when to send it:
| Type | Purpose |
|---|---|
session-config | First frame after joining the room: SDK identity + agent config + session params. The server replies with ready. |
mute | Toggle the server-side microphone gate. |
send-text | A text turn instead of audio — the agent answers it. |
send-context | Context the agent should have without being asked anything: no turn, no speech, no transcript entry. For live application state. |
send-image | One base64 image frame (mime_type, stream_id). See Image input. |
activity-end | Manual end-of-turn signal when you're running your own turn detection. See Turn-taking. |
bind-input | Bind the agent's audio input to this participant (the SDK sends it when you publish audio). |
tool_job_result | Deferred result of a background client tool (job_id, status, result). |
end | Graceful goodbye; the server tears down the upstream session. |
ping | Heartbeat; server replies pong. |
envelope-chunk | Framing carrier for oversized client messages. |
Server → client
Server messages fall into three families: lifecycle, transcripts, and tool traffic.
Session lifecycle
The following table lists the lifecycle events and what each one carries.
| Type | Fired when | Payload highlights |
|---|---|---|
ready | Agent is live and listening | session_id, resolved agent summary, rejected_tools, max_session_seconds |
reconnecting | Server is rotating the upstream model session; a brief pause, not a failure | seconds_remaining |
session-ending-soon | Wall-clock cap approaching | seconds_remaining, reason |
session-ended | Server ended the session deliberately | reason (for example, max_session_duration) |
error | Something went wrong | code, message, fatal — non-fatal errors don't end the session |
pong | Reply to ping | — |
Every SDK guarantees a terminal session-ended item as the last event on the stream, synthesized locally if the transport died before the server said goodbye.
Subscribing to session-ending-soon per language: TypeScript — session.on('session_ending_soon', …). Python — match SessionEndingSoonEvent on the event stream. Swift — match .sessionEndingSoon. Every form carries seconds_remaining and reason.
Transcripts and model text
The following table lists the transcript and model-text events.
| Type | Fired when | Payload highlights |
|---|---|---|
transcript | Speech transcribed, either speaker | role, text, is_final — streaming events append; the final event replaces the accumulated text |
model-text | The model emits text alongside (or instead of) audio | text, is_final |
turn-complete | A turn ended; finalize UI state | role |
Which one to display for which UI is covered in Transcripts.
Speech activity and model processing
These are informational, type-only markers — ideal for driving avatars, level meters, and "thinking…" indicators:
| Type | Meaning |
|---|---|
user-started-speaking / user-stopped-speaking | Server VAD detected user voice start/stop |
bot-started-speaking / bot-stopped-speaking | First/last audio frame of the assistant turn |
bot-llm-started / bot-llm-stopped | Model began/finished generating |
bot-tts-started / bot-tts-stopped | Speech synthesis began/finished |
user-speech-timeout | A server silence-timeout hook fired: silence_ms, trigger_count, max_count, and the action that the server already took |
Tools
Four events cover both dispatch directions — see Tools:
| Type | Fired when |
|---|---|
tool-call | The model decided to invoke a tool |
tool-dispatch-started | The server-side handler began running |
tool-result | The handler finished (ok, summary) |
tool-invocation | The server asks this client to run a local tool (request_id, name, args). Two informational fields ride along: origin says which producer authored the invocation ("realtime" — the voice model, the default — or "server" for a server tool runtime reaching back through the same bridge), and executable is false for an informational mirror whose execution runs out of band — true (and a missing field) means this client runs the tool |
The three observability events share a tool_call_id so you can render a timeline per invocation.
First-party extensions
Cosmo-specific events are namespaced cosmo.*:
| Type | Purpose |
|---|---|
cosmo.usage | Cumulative token usage, split by input/output and text/audio/image/cached |
cosmo.session-state | The durable session state after a cosmo.set_state write (state, updated_keys, stage) — see Session state |
Ordering guarantees
readyalways precedes transcripts, tool events, and speech markers.- The three tool observability events arrive in order for a single
tool_call_id, but events from different tool calls interleave. - Transcript deltas for one turn arrive in order;
turn-completefollows the final transcript of that turn. - The terminal session-ended item is always last; nothing follows it.
Wire reference
Exact schemas for every message live in the wire protocol reference. The protocol evolves additively and session-config identifies the sending SDK; see Protocol compatibility for how changes ship without breaking deployed apps.