Skip to content

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

Open
jmcclanahan wants to merge 9 commits into
mainfrom
jakemcclanahan/recording-session-summary
Open

jmcclanahan wants to merge 9 commits into
mainfrom
jakemcclanahan/recording-session-summary

Conversation

@jmcclanahan

@jmcclanahan jmcclanahan commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Description

Agent evals choose which sessions to score from a JSON "session summary" the SDK sends in the recording upload header (MetricsRecordingHeader.session_summary, livekit/protocol#1884). This PR makes agents-js send it. All summary fields are computed in sessionReportToJSON(), so the logic exists in one place; the upload only trims the report JSON.

Draft until @livekit/protocol 1.53.1+ is released with session_summary and CloseReason. Depends on livekit/protocol#1884 (field) and livekit/protocol#1887 (livekit.agent.CloseReason enum). This commit does not typecheck against the published @livekit/protocol@1.50.4.

Changes Made

  • sessionReportToJSON(report, options?) takes { includeChatHistory?, includeEvents? }, both default true; false omits the key. New exported type SessionReportJSONOptions.
  • sessionReportToJSON now always appends, after sdk_version: agent_name (string or null), duration_ms (rounded ms or null), user_turns (user message items in the chat history), errors (counts for llm, stt, tts, realtime, interruption, always all five), close_reason (the livekit.agent.CloseReason value name of the last close event, e.g. CR_PARTICIPANT_DISCONNECTED, or null without a close event; custom shutdown strings map to CR_UNKNOWN). The existing keys keep their key, value and order. errors and close_reason are computed from the events even when events is omitted.
  • SessionReport and SessionReportOptions gain agentName?; JobContext.makeSessionReport sets it from the job (API report updated).
  • The summary is only sessionReportToJSON(report, { includeChatHistory: false, includeEvents: false }). usage is the report's per-model list (each entry has its type, e.g. llm_usage), or null.
  • The close-reason names are typed as keys of the generated enum (type-only import), so report.ts does not need the enum at runtime. No tags (agents-js has no Tagger), no models (derivable from usage).
  • The header duration is now set from the report (ms), which removes the TODO.
  • Changeset: @livekit/agents patch.

Pre-Review Checklist

  • Build passes: lint passes; typecheck, build, API report and tests pass locally against @livekit/protocol patched with session_summary and CloseReason. Against the published 1.50.4, tsc fails only on the missing CloseReason and sessionSummary.
  • AI-generated code reviewed
  • Changes explained
  • Scope appropriate
  • Video demo: not applicable (no runtime behavior change beyond the upload header and the added report keys)

Testing

  • agents/src/voice/report.test.ts: default output keeps the old keys in order with the new keys appended; includeChatHistory / includeEvents omit their keys; summary fields (agent name, rounded duration, user turns, error counts, last close reason) and null for unknown values; every SDK CloseReason maps to a proto name other than CR_UNKNOWN that exists in the generated enum, and custom reasons map to CR_UNKNOWN.
  • agents/src/telemetry/traces.test.ts: the decoded header session_summary equals the trimmed report JSON, and duration is set.
  • agents/src/telemetry, agents/src/voice, agents/src/job pass 1864/1864 against the patched protocol package.
  • Bump @livekit/protocol to 1.53.1+ once released. That bump will also fail chat_item_proto_coverage on the new FunctionCall.extra field, which is unrelated to this PR.

Additional Notes

Related: livekit/protocol#1884 (field), livekit/protocol#1887 (close reason enum), livekit/cloud-observability#656 (stores it), livekit/agents-private#745 (reads it). The schema there must allow null for duration_ms, close_reason, usage and agent_name.

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 881dcea

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 41 packages
Name Type
@livekit/agents Patch
@livekit/agents-plugin-anam Patch
@livekit/agents-plugin-anthropic Patch
@livekit/agents-plugin-assemblyai Patch
@livekit/agents-plugin-azure Patch
@livekit/agents-plugin-baseten Patch
@livekit/agents-plugin-bey Patch
@livekit/agents-plugin-cartesia Patch
@livekit/agents-plugin-cerebras Patch
@livekit/agents-plugin-deepgram Patch
@livekit/agents-plugin-did Patch
@livekit/agents-plugin-elevenlabs Patch
@livekit/agents-plugin-fishaudio Patch
@livekit/agents-plugin-google Patch
@livekit/agents-plugin-hume Patch
@livekit/agents-plugin-inworld Patch
@livekit/agents-plugin-krisp Patch
@livekit/agents-plugin-lemonslice Patch
@livekit/agents-plugin-liveavatar Patch
@livekit/agents-plugin-livekit Patch
@livekit/agents-plugin-meta Patch
@livekit/agents-plugin-minimax Patch
@livekit/agents-plugin-mistral Patch
@livekit/agents-plugin-mistralai Patch
@livekit/agents-plugin-neuphonic Patch
@livekit/agents-plugin-openai Patch
@livekit/agents-plugin-perplexity Patch
@livekit/agents-plugin-phonic Patch
@livekit/agents-plugin-protoface Patch
@livekit/agents-plugin-resemble Patch
@livekit/agents-plugin-rime Patch
@livekit/agents-plugin-runway Patch
@livekit/agents-plugin-sarvam Patch
@livekit/agents-plugin-silero Patch
@livekit/agents-plugin-soniox Patch
@livekit/agents-plugin-speechify Patch
@livekit/agents-plugin-synthesia Patch
@livekit/agents-plugin-tavus Patch
@livekit/agents-plugins-test Patch
@livekit/agents-plugin-trugen Patch
@livekit/agents-plugin-xai Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

…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>
@JustinScitech

Copy link
Copy Markdown
Contributor

yeah as you stated in slack, once we merge livekit/protocol#1884, then the failing CI checks will pass.

@JustinScitech JustinScitech 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.

Seems like there's some failing builds

Comment thread agents/src/telemetry/traces.ts Outdated
Comment on lines +1061 to +1063
if (report.duration !== undefined) {
summary.duration_ms = Math.round(report.duration);
}

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.

Wondering the same thing in PR https://github.com/livekit/agents/pull/8069/changes about the report duration non-existing for audio transcripts

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.

jmcclanahan and others added 4 commits October 23, 2026 15:43
The recording header's session summary is now the session report without
chat history, events and the local recording path, plus agent_name,
duration_ms, user_turns, error counts and close_reason as a
livekit.agent.CloseReason value name. sessionReportToJSON takes options to
omit chat_history and events; its default output is unchanged.

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

sessionReportToJSON now always appends agent_name, duration_ms, user_turns,
errors and close_reason after sdk_version, with null for unknown values. The
recording header summary is only the trimmed report JSON without
audio_recording_path, so the logic lives in one place. SessionReport gains
agentName, filled by JobContext.makeSessionReport.

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
sessionReportToJSON(report, { includeChatHistory: false, includeEvents: 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:50
@jmcclanahan
jmcclanahan requested a review from a team as a code owner October 23, 2026 21:50
…ing-session-summary

# Conflicts:
#	agents/src/voice/report.test.ts

@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.

Note

Newer findings are available below. Devin Review posted a newer report on this PR, in addition to the findings presented here.

Devin Review found 2 potential issues.

Devin Review

Comment thread agents/src/voice/report.ts Outdated
sdk_version: version,
agent_name: report.agentName ?? null,
// The report has no duration when audio was not recorded.
duration_ms: report.duration !== undefined ? Math.round(report.duration) : null,

@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.

🟡 Recording setup inflates session duration

With audio recording enabled, duration_ms includes startup time before the session starts. RecorderIO.start() precedes the session's _startedAt, so duration-based eval selection uses an inflated length.

Learn more

The session report exposes a duration calculated from the audio recording start when audio is present createSessionReport. Audio recording starts during session startup RecorderIO.start, while the session start timestamp is assigned after startup work finishes AgentSession. The new summary field therefore includes setup time in sessions with audio, unlike sessions without audio. The header uses the same report duration uploadSessionReport.

Example: Recording begins at 12:00:00, session startup finishes at 12:00:05, and the session ends at 12:01:05. The summary reports 65 seconds instead of the session's 60 seconds.

Recommended fix: Derive the session summary duration from report.timestamp - report.startedAt for started sessions. Decide separately whether the recording header needs audio duration or session duration, and add a startup-delay test for both.

Devin Review


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

Comment thread agents/src/telemetry/traces.ts Outdated
CR_<REASON>, checked against livekit.agent.CloseReason; task_completed is the one exception
(CR_COMPLETED). Replaces the hand-written mapping, matching the Python SDK.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
devin-ai-integration[bot]

This comment was marked as resolved.

… fill agent_name from the upload

Addresses review: recording starts before the session, so duration_ms is timestamp - startedAt;
a custom 'completed' shutdown string is CR_UNKNOWN, not CR_COMPLETED; a report without
agentName takes the upload's agentName.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

2 participants