Cosmo Realtime SDK
Concepts

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-chunk frames and is reassembled before your handler fires. You never see chunks. See Envelope chunking.
  • Unknown types aren't errors. SDKs surface unrecognized type values as an explicit unknown event and keep going. New server events never break old clients.

Client → server

Each client → server message and when to send it:

TypePurpose
session-configFirst frame after joining the room: SDK identity + agent config + session params. The server replies with ready.
muteToggle the server-side microphone gate.
send-textA text turn instead of audio — the agent answers it.
send-contextContext the agent should have without being asked anything: no turn, no speech, no transcript entry. For live application state.
send-imageOne base64 image frame (mime_type, stream_id). See Image input.
activity-endManual end-of-turn signal when you're running your own turn detection. See Turn-taking.
bind-inputBind the agent's audio input to this participant (the SDK sends it when you publish audio).
tool_job_resultDeferred result of a background client tool (job_id, status, result).
endGraceful goodbye; the server tears down the upstream session.
pingHeartbeat; server replies pong.
envelope-chunkFraming 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.

TypeFired whenPayload highlights
readyAgent is live and listeningsession_id, resolved agent summary, rejected_tools, max_session_seconds
reconnectingServer is rotating the upstream model session; a brief pause, not a failureseconds_remaining
session-ending-soonWall-clock cap approachingseconds_remaining, reason
session-endedServer ended the session deliberatelyreason (for example, max_session_duration)
errorSomething went wrongcode, message, fatal — non-fatal errors don't end the session
pongReply 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.

TypeFired whenPayload highlights
transcriptSpeech transcribed, either speakerrole, text, is_final — streaming events append; the final event replaces the accumulated text
model-textThe model emits text alongside (or instead of) audiotext, is_final
turn-completeA turn ended; finalize UI staterole

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:

TypeMeaning
user-started-speaking / user-stopped-speakingServer VAD detected user voice start/stop
bot-started-speaking / bot-stopped-speakingFirst/last audio frame of the assistant turn
bot-llm-started / bot-llm-stoppedModel began/finished generating
bot-tts-started / bot-tts-stoppedSpeech synthesis began/finished
user-speech-timeoutA 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:

TypeFired when
tool-callThe model decided to invoke a tool
tool-dispatch-startedThe server-side handler began running
tool-resultThe handler finished (ok, summary)
tool-invocationThe 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.*:

TypePurpose
cosmo.usageCumulative token usage, split by input/output and text/audio/image/cached
cosmo.session-stateThe durable session state after a cosmo.set_state write (state, updated_keys, stage) — see Session state

Ordering guarantees

  • ready always 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-complete follows 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.

On this page