kind-sunset-1403
02/23/2026, 10:47 AMtall-airline-16756
02/23/2026, 10:47 AMtall-airline-16756
02/23/2026, 10:47 AMuser_input_transcribed event on an Agent session. That event is emitted whenever user speech is transcribed (it includes interim and final results), so your agent can react in real time. A common pattern is to wait for the final transcription (is_final) and then process or respond to it — for example, echoing it back or triggering downstream logic.
Example (Python-style):
@session.on("user_input_transcribed")
def on_transcript(transcript):
if transcript.is_final:
session.say(transcript.transcript)
If you’d like, tell me which language/SDK you’re using (Python, TypeScript, etc.) or whether you need to handle interim vs final transcripts, and I’ll provide a concrete code snippet or integration tips.
Sources: Agent session | LiveKit Documentation | Repeater | LiveKit Documentation | Pi Zero Transcriber | LiveKit Documentationkind-sunset-1403
02/23/2026, 10:49 AMRoomEvent.TranscriptionReceived . is that equivalent of this?tall-airline-16756
02/23/2026, 10:49 AMRoomEvent.TranscriptionReceived on the frontend corresponds to the same underlying transcription data that an Agent receives via the user_input_transcribed event. The main differences are about where the event is delivered and how you consume it:
• Scope:
◦ RoomEvent.TranscriptionReceived is a client-side (frontend) room event you subscribe to in your app.
◦ user_input_transcribed is the Agent-level event delivered to Agents/sessions running server-side logic.
• Payload & timing:
◦ Both provide the transcription text and include interim and final results. You’ll typically see the same transcription text and is_final-style flag in both places.
◦ Use interim events for live UI updates and final results (is_final) for committing actions (e.g., triggering an intent or agent response).
• Typical usage patterns:
◦ Frontend: show live transcript, captions, or highlight words as the user speaks.
◦ Agent/server: process final transcriptions to run NLP, decide actions, or speak a response (for example the Repeater pattern that echoes final transcripts).
If you want, tell me which SDK/language you’re using on the frontend (web/TypeScript, React, etc.), and I can provide a short concrete code snippet showing how to subscribe to RoomEvent.TranscriptionReceived and how to map/use the is_final flag the same way an Agent would.kind-sunset-1403
02/23/2026, 10:51 AMtall-airline-16756
02/23/2026, 10:51 AMconversation_item_added event on the Agent session. It contains completed messages with role assistant. This gives you the final text the agent produced (what you’d normally store as the agent’s reply).
• Example (Python):
@session.on("conversation_item_added")
def on_conversation_item_added(item):
if item.message.role == "assistant":
agent_text = item.message.content
save_to_db(agent_text)
• Example (Node.js/TS):
session.on('conversation_item_added', (item) => {
if (item.message.role === 'assistant') {
const agentText = item.message.content;
saveToDb(agentText);
}
});
2) Capture LLM streaming output (before TTS)
• If you need the raw LLM output in real time (partial chunks), override llm_node in your Agent subclass and collect chunks as they stream. This is useful for logging, moderation, or showing typing-like progress.
• You can assemble the chunks into a final string and store it when the stream completes.
3) Capture text when you explicitly call session.say() or generate text yourself
• If your code calls session.say(response_text) after generating an LLM reply, simply store response_text at that point (no event needed).
4) Frontend / text-stream capture
• Agent responses are also available as text streams on the lk.transcription topic (with no lk.transcribed_track_id when audio is disabled). You can register a text stream handler on the room to receive agent text and store it.
• Example (frontend JS):
room.registerTextStreamHandler('lk.transcription', async (reader, participantInfo) => {
const message = await reader.readAll();
const isAgentResponse = !reader.info.attributes['lk.transcribed_track_id'];
if (isAgentResponse) {
saveToDb(message);
}
});
5) End-of-session report (full conversation export)
• Generate a session report or conversation history at session end and persist any assistant messages found there (useful for audits or bulk exports).
Which approach should you use?
• For most cases where you want the canonical final agent reply to store: use conversation_item_added.
• If you need partial streaming text or want to intercept text before TTS: override llm_node.
• If you control the text directly with `session.say()`: store it where you emit it.
If you tell me which SDK/language you’re using (Python, Node/TS, or frontend framework) and whether you need interim streaming or only final replies, I’ll provide a concrete example tailored to your setup and include best-practices for redaction/storage.
Sources: Redacting PII from agent logs and transcripts | LiveKitkind-sunset-1403
02/23/2026, 10:53 AMtall-airline-16756
02/23/2026, 10:53 AMconversation_item_added event.
• Detect speech start with agent_state_changed when the agent transitions to speaking. For an earlier signal (speech synthesized/queued) use speech_created.
How to use them together (recommended flow)
1. Listen for conversation_item_added to persist the assistant message (final text).
2. Listen for agent_state_changed and check for new_state == "speaking" to mark the moment audio playback begins.
3. Optionally listen for speech_created if you want a notification immediately after TTS is ready (this occurs slightly before speaking).
Concrete examples
• Python (agent/server side)
@session.on("conversation_item_added")
def on_conversation_item_added(item):
if item.message.role == "assistant":
agent_text = item.message.content
save_to_db(agent_text) # store the LLM-generated text
@session.on("agent_state_changed")
def on_agent_state_changed(ev):
if ev.new_state == "speaking":
# Agent audio playback has started
mark_speech_started(timestamp_now(), ev)
• If you want the earliest signal (synthesis complete / audio queued):
@session.on("speech_created")
def on_speech_created(speech_handle):
# TTS has been created and queued; speaking will follow soon
note_speech_queued(speech_handle.id)
Frontend / room-level option
• If you capture agent output via room text streams (lk.transcription) or the frontend RoomEvent.TranscriptionReceived, you can still correlate the stored text with the agent speaking state by subscribing to the agent-state events (server-side) or by listening for audio-playback indicators in the client. The canonical source for “speech started” remains the agent state transition to speaking.
Notes and best practices
• Use conversation_item_added as your canonical place to store the final LLM reply — it contains the final assistant message content.
• Use agent_state_changed (speaking) for accurate playback start timing (good for analytics, VUIs, avatar lip-sync).
• Use speech_created if you need the earliest possible notification (it fires when audio is synthesized/queued, just before speaking).
• If you also need to detect when speech ends, listen for playback completion / playback_finished on the output audio handle.
Would you like examples tailored to a specific SDK/language (TypeScript, React frontend, or Node/Python backend)? If you tell me which one you’re using, I’ll give a ready-to-use snippet and show how to correlate the stored text with the speaking timestamp.
Sources: Agent session | LiveKit Documentation | Turns overview | LiveKit Documentation | Agent speech and audio | LiveKit Documentation | How to detect when an agent has finished speaking