Repository navigation
feat(audio): Add AudioSessionRecorder for audio schema v3 sessions - #2725
Merged
Luca Forstner (lforst) merged 1 commit intoNov 3, 2026
Merged
Conversation
…scription AudioSessionRecorder describes one session's audio in the audio schema v3 metadata shape and never logs: session() returns its schema version, sources and timeline; participant() and speaker() return a stable, session-local ID (participant-N, speaker-N) for an opaque native key, plus the entry to publish the first time only; onFile hands off each recorded segment with its audio.segments entry, or an omitted entry when storing or encoding fails. It records through an internal AudioRecorder when audio is configured, and otherwise describes the session without audio. audioSelections turns played or spoken intervals into a span's audio.selections: rounded, sorted, and merged where they overlap or touch. AudioRecorder now waits for an asynchronous onError, so an omitted segment is handed off before the recorder finishes.
David Elner (delner)
force-pushed
the
delner/audio-session-recorder-v3
branch
from
November 3, 2026 02:07
4955cc0 to
c28a239
Compare
Luca Forstner (lforst)
approved these changes
Nov 3, 2026
Luca Forstner (lforst)
pushed a commit
that referenced
this pull request
Nov 3, 2026
Based on #2725 ## Why LiveKit voice traces now follow audio schema v3. Some of the major changes from this: - Speech and user turns carry `audio.selections` in reference to the session's timeline, so a span points at its exact place in the recording. This is recorded even when audio capture is off. - Attribution for sources, participants (e.g. a caller), and speakers (individual voices) including support for diarization (identifying which text belonged to what speaker.) - Links between when operations, when possible. e.g. A model response links to the transcripts in its context, a tool to the response that requested it, and played speech to the synthesis and model response that generated it. ## Example A two-caller order call with a diarizing STT (from the e2e fixture): ``` session audio.schema.version: 3.0.0 · sources · speakers · segments · timeline ├── user_turn input: "Hi. I'm calling with my partner, … Where is order demo one zero four two? …" │ ├── user_speech ×4 audio.selections │ ├── stt "Hi. I'm calling with my partner," speaker-1 │ ├── stt "who has a question about our order." speaker-1 │ ├── stt "Where is order demo one zero four two?" speaker-2 │ └── … └── agent_turn input: (the transcript above) · turn.reply_to → user_turn ├── llm_response uses_context → each stt ├── lookup_order requested_by → llm_response ├── llm_response uses_context → each stt · consumes_tool_result → lookup_order ├── tts generated_by → llm_response └── agent_speech "Your order … is scheduled for delivery on Friday." audio.selections · generated_by → tts ``` Session metadata, illustrated for a call with a room participant: ```json { "audio.schema.version": "3.0.0", "audio.sources": { "session-input": { "label": "Agent input audio" }, "agent-output": { "label": "Agent audio" } }, "audio.speakers": { "speaker-1": { "type": "user", "attributes": { "contrib.livekit.speaker_id": "0" } } }, "audio.participants": { "participant-1": { "type": "user", "attributes": { "contrib.livekit.participant_id": "PA_x7Kq", "contrib.livekit.participant_kind": "SIP" } } }, "audio.segments": { "part-0": { "state": "ready", "start_offset_ms": 0, "duration_ms": 30000, "attachment": { "ref": "/input/audio/part-0" } } } } ``` ## Design notes - Prefer keys and IDs native to the framework rather than heuristics. Replies match turns by LiveKit's identities, not timestamps. An STT span runs from the first result to the final, under the turn it began in. Heard text comes from the sink's synchronized transcript. - Lean on more reliable mechanics for timing. Agent playout is timed by the sink, even after an interruption. User speech is selected only with a local VAD, since provider speech events arrive late. - Identities can only be added when LiveKit knows them. Without rooms, diarization, or PII permitted, some forms of identification are not always possible. - New fields follow #2724's PII gate.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Based on #2724
Why
Every voice integration has to describe its session the same way under audio schema v3: sources, a shared timeline, participants, speakers, recorded segments, and each span's place on that timeline.
AudioSessionRecorderowns that shape once, so integrations only translate their framework's events into calls.participant-N,speaker-N) derived from native values.In sum, this enables LiveKit's move to schema v3 (next PR), and future voice integrations.
Example
How an integration uses it:
Design notes
AudioSessionRecorderis responsible for managing the session, resolving references, etc but it is not responsible for instrumentation or trace data itself... it merely returns metadata that instrumentation can log.participantandspeaker, and helps spans map to a common, PII-less ID. (e.g.participant-1)