Skip to content

telemetry: send a session summary in the recording header - #8069

Merged
jmcclanahan merged 10 commits into
mainfrom
jakemcclanahan/recording-session-summary
Oct 23, 2026
Merged

jmcclanahan merged 10 commits into
mainfrom
jakemcclanahan/recording-session-summary

Conversation

@jmcclanahan

@jmcclanahan jmcclanahan commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

What

  • _upload_session_report sets MetricsRecordingHeader.session_summary, a compact JSON object: SessionReport.to_dict(include_chat_history=False, include_events=False). The upload adds no other logic.
  • SessionReport.to_dict gets keyword-only include_chat_history and include_events flags (default True). False omits the key.
  • SessionReport gains agent_name (default "") and tags (default []). JobContext.make_session_report fills them from the job and the job's Tagger (sorted).
  • to_dict changes are additive. Every existing key keeps its value and order. These keys are appended after sdk_version:
    • agent_name, tags
    • duration_ms: int ms, null when the report has no duration
    • user_turns: user ChatMessage items in the chat history
    • errors: error event counts by llm, stt, tts, realtime, interruption (always all five keys)
    • close_reason: the livekit.agent.CloseReason proto enum name for the last close event (e.g. CR_PARTICIPANT_DISCONNECTED), null without one. Unknown reasons map to CR_UNKNOWN.
  • Each usage entry in to_dict now includes its type (llm_usage, tts_usage, ...). The other keys are unchanged (model_dump(exclude_defaults=True)).
  • The header duration is now set from the report (ms).

Why

Agent evals choose which sessions to score from facts the SDK knows at upload time. The collector stores the summary (livekit/cloud-observability#656) and agent-evals reads it (livekit/agents-private#745). Key contract: evalschema/session-summary.schema.json in livekit/cloud-protocol (livekit/cloud-protocol#1415). null means "not sent". The computed keys live in to_dict so the logic exists in one place, and local session reports get them too.

How

  • voice/report.py holds the error-key mapping (_ERROR_KEYS) and the close-reason mapping (_CLOSE_REASON_NAMES, keyed by CloseReason members).
  • _session_summary(report) in telemetry/traces.py is only the trimmed to_dict call.
  • Events are not uploaded, so errors and close_reason reach agent evals only through the header.

Tests

  • tests/test_session_options_report.py: to_dict default keys (old keys in order, then the new keys); each flag omits only its key; computed keys for a full session and an empty session; drift test: every CloseReason member maps to a name in the generated proto enum livekit.protocol.agent_pb.agent_session.CloseReason.
  • tests/test_recording.py: the header carries to_dict(include_chat_history=False, include_events=False).
  • tests/test_realtime/test_openai_realtime_model.py: the usage entry expectation includes type.
  • uv run pytest --unit --audio_eot passes on livekit-protocol 1.1.28 (5780 passed; the only errors are tests/test_room.py, which needs a local livekit-server). make type-check passes.

Depends on

🤖 Generated with Claude Code

jmcclanahan and others added 2 commits October 22, 2026 14:15
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ssion summary

The report's events are not uploaded, so the header is the only way agent evals can see the
close reason and error counts. Drop the unused "version" key: every key is optional and new
keys are additive.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread livekit-agents/livekit/agents/telemetry/traces.py Outdated
jmcclanahan and others added 4 commits October 23, 2026 15:42
The summary is now the report's to_dict without chat history, events and the
local recording path, plus agent_name, tags, duration_ms, user_turns, errors
and close_reason as the livekit.agent.CloseReason proto enum name. Usage is
the report's list with each entry's type added back. SessionReport.to_dict
gets include_chat_history and include_events flags; defaults are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
to_dict appends agent_name, tags, duration_ms, user_turns, errors and
close_reason after sdk_version, and each usage entry gains its type. The
recording header's session summary is now to_dict without chat history,
events and the local recording path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ol#1887)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…helper

The local audio_recording_path is harmless, so the summary is exactly
to_dict(include_chat_history=False, include_events=False).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jmcclanahan
jmcclanahan marked this pull request as ready for review October 23, 2026 21:49
@jmcclanahan
jmcclanahan requested a review from a team as a code owner October 23, 2026 21:49
Comment thread livekit-agents/livekit/agents/voice/report.py Outdated

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 potential issues.

Devin Review

Comment thread livekit-agents/livekit/agents/telemetry/traces.py
Comment on lines +116 to +120
for event in self.events:
if event.type == "error":
key = _ERROR_KEYS.get(getattr(event.error, "type", ""))
if key is not None:
errors[key] += 1

@devin-ai-integration devin-ai-integration Bot Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Interruption failures always count as zero

When interruption detection fails, errors.interruption remains zero. AgentActivity._on_error returns without emitting an ErrorEvent, leaving the summary without a failure to count.

Learn more

AgentActivity._on_error emits ErrorEvent for LLM, STT, TTS and realtime failures. For InterruptionDetectionError, it instead falls back to VAD if needed and returns without emitting anything. _error_counts sees only recorded ErrorEvent objects, so the advertised interruption category never reflects detector failures in normal sessions.

Example: An adaptive interruption detector fails twice during a call and the activity falls back to VAD. The uploaded session summary reports "interruption": 0, hiding both failures.

Recommended fix: Emit a corresponding ErrorEvent from the interruption-error branch in AgentActivity._on_error, including its source, before any fallback or return. Verify that the new event does not alter existing shutdown handling.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

jmcclanahan and others added 4 commits October 23, 2026 17:05
…eason

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CR_<member name>, checked against livekit.agent.CloseReason; TASK_COMPLETED is the one
exception (CR_COMPLETED). Replaces the hand-written mapping.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s its tags

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jmcclanahan
jmcclanahan merged commit c700522 into main Oct 23, 2026
23 checks passed
@jmcclanahan
jmcclanahan deleted the jakemcclanahan/recording-session-summary branch October 23, 2026 22:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants