Skip to content

tests: trace-shape schema and checker - #2501

Merged
davidzhao merged 2 commits into
dz/telemetry-agent-turnfrom
dz/telemetry-trace-schema
Sep 26, 2026
Merged

davidzhao merged 2 commits into
dz/telemetry-agent-turnfrom
dz/telemetry-trace-schema

Conversation

@davidzhao

@davidzhao davidzhao commented Sep 15, 2026 •

Copy link
Copy Markdown
Member

Port of livekit/agents#7148. Stacked on #2500.

Description

The nesting of spans is an emergent property of many call sites. A refactor can move a span under the wrong parent while every existing test passes, because each test asserts the one edge it was written for. This layer writes the rules down once and checks every span in every full-session test against them, and applies the same rules to a real run's export.

agents/src/telemetry/testing/trace_schema.ts (test support, not exported from the package):

  • SPAN_PARENTS: for every span name the JS framework emits, the parents it may have (ROOT for none, ANY for spans that follow their caller: rpc_call, event_loop_blocked). An unknown name is a violation, so a new span must be registered.
  • MAY_OUTLIVE_PARENT: the child/parent edges where the child may end after its parent, each with its reason (the startup spans session.start() does not wait for, on_enter, and stalls whose end is one heartbeat late). Everything else must sit inside its parent, with 2 ms of slack.
  • checkTrace(): one trace id, every parent present, parent allowed by the schema, bounds, and the per-turn invariants: one agent_turn per lk.speech_id, lk.generation_count equal to the number of generation events, every eou_wait with an outcome.
  • Two span sources, same rules: fromReadableSpans for an in-memory exporter, fromOtlpJson for an export downloaded from LiveKit Cloud. As a CLI, pnpm exec tsx agents/src/telemetry/testing/trace_schema.ts traces.json prints the span summary and the violations, tolerating orphans in a partial export.

assertTraceWellFormed(exporter.getFinishedSpans()) now ends the full-session tests of the earlier layers: agent_turn_span, coverage_spans, eou_wait_span, session_lifecycle_span.

Schema differences from Python

All from what the JS code emits: answering_machine_detection is JS's name for amd; JS has no llm_fallback_adapter / tts_fallback_adapter / tts_stream_adapter spans (its adapters emit the plain request spans, which nest under the attempt's *_request_run), no wait_for_video_track, no judge_evaluation; rpc_handler may be a root, since without a session the SDK dispatches on a context carrying no span. MAY_OUTLIVE_PARENT is identical to Python's.

Testing

  • New telemetry/testing/trace_schema.test.ts (9 tests): schema self-consistency; a sound trace passes; a misparented llm_request, an eou_detection outside its wait, an unknown span, a missing parent, a child outside its parent, a duplicated speech turn, a generation-count mismatch and a second trace id are each reported; the deliberate overruns pass; OTLP and in-memory sources agree; a full fake session is well-formed.
  • Test hardening: the eou_wait full-session helper decided the turn 20 ms after the fake STT final was due, which under load opened a second user turn; the endpointing delay now leaves a 170 ms margin.
  • No shipped code changes. Full agents suite green.

Review follow-ups

  • lk.generation_count is checked against every generation event on the turn, matching feat(telemetry): one agent_turn span per speech handle #2500 where the count is the turn's and includes a discarded preemptive attempt's generation. The handoff itself is still validated separately: generations after the last preemptive_generation_discarded event must carry the finishing speech's id (a realtime tool reply continues the tool call's id, so it passes).
  • An empty export is a violation (0 traces), not a sound trace.
  • A generation event without lk.generation_id is a violation wherever it sits on the turn, a discarded attempt's before the handoff included: the framework always stamps one. Only the ownership check (the id is the finishing speech's) is limited to generations after the last handoff.

🤖 Generated with Claude Code

@davidzhao
davidzhao added this pull request to stack #2502 September 15, 2026 06:32
@changeset-bot

changeset-bot Bot commented Sep 15, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 474081a

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

@davidzhao
davidzhao force-pushed the dz/telemetry-trace-schema branch 2 times, most recently from 0a979d8 to 473c53d Compare September 16, 2026 04:28
@davidzhao
davidzhao force-pushed the dz/telemetry-trace-schema branch from 473c53d to 70effd6 Compare September 16, 2026 04:34
@davidzhao
davidzhao force-pushed the dz/telemetry-trace-schema branch from 70effd6 to 1bb26cb Compare September 20, 2026 05:02
@davidzhao
davidzhao force-pushed the dz/telemetry-trace-schema branch from 1bb26cb to 378dc23 Compare September 20, 2026 05:58
@davidzhao
davidzhao force-pushed the dz/telemetry-trace-schema branch from 378dc23 to 434f47e Compare September 20, 2026 06:11
@davidzhao
davidzhao force-pushed the dz/telemetry-trace-schema branch from 434f47e to 0d1517e Compare September 20, 2026 07:14
@davidzhao
davidzhao force-pushed the dz/telemetry-trace-schema branch from 0d1517e to b2c5e8d Compare September 20, 2026 07:15
@davidzhao
davidzhao force-pushed the dz/telemetry-trace-schema branch 2 times, most recently from 2268d2d to f064b33 Compare September 20, 2026 17:17
@davidzhao
davidzhao force-pushed the dz/telemetry-trace-schema branch 2 times, most recently from 0daa94a to 92a97ea Compare September 20, 2026 18:02
@davidzhao
davidzhao force-pushed the dz/telemetry-trace-schema branch 2 times, most recently from b6eebad to 289b916 Compare September 21, 2026 03:28
@davidzhao
davidzhao force-pushed the dz/telemetry-trace-schema branch from 81f697d to f0c186a Compare September 21, 2026 05:18
devin-ai-integration[bot]

This comment was marked as resolved.

@davidzhao
davidzhao force-pushed the dz/telemetry-trace-schema branch from f0c186a to aa7c911 Compare September 21, 2026 05:37
devin-ai-integration[bot]

This comment was marked as resolved.

@davidzhao
davidzhao force-pushed the dz/telemetry-trace-schema branch from aa7c911 to cf82e5b Compare September 21, 2026 05:42
devin-ai-integration[bot]

This comment was marked as resolved.

@davidzhao
davidzhao force-pushed the dz/telemetry-trace-schema branch 2 times, most recently from fe43d72 to c776963 Compare September 21, 2026 15:35
@davidzhao
davidzhao force-pushed the dz/telemetry-trace-schema branch from c776963 to 472c7d6 Compare September 21, 2026 15:37
* downloaded from LiveKit Cloud, so the same rules check a unit test and a real run:
*
* ```
* pnpm exec tsx agents/src/telemetry/testing/trace_schema.ts path/to/traces.json

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't think we install tsx automatically.

@davidzhao
davidzhao force-pushed the dz/telemetry-trace-schema branch from 472c7d6 to dd49427 Compare September 23, 2026 15:10
@davidzhao
davidzhao force-pushed the dz/telemetry-trace-schema branch from dd49427 to 5e57bdb Compare September 26, 2026 01:56

@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 new potential issues.

1 flag not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)

Devin Review

Comment thread agents/src/telemetry/testing/trace_schema.ts
Comment thread agents/src/telemetry/testing/trace_schema.ts Outdated
@davidzhao
davidzhao force-pushed the dz/telemetry-trace-schema branch from 5e57bdb to 59471a7 Compare September 26, 2026 05:02
@davidzhao
davidzhao force-pushed the dz/telemetry-trace-schema branch from 59471a7 to c47581e Compare September 26, 2026 05:26
@davidzhao
davidzhao force-pushed the dz/telemetry-trace-schema branch from c47581e to 8d1afc6 Compare September 26, 2026 05:40
davidzhao and others added 2 commits September 25, 2026 22:52
Port of livekit/agents#7148. The nesting of spans is an emergent
property of many call sites; a refactor can move a span under the wrong
parent while every existing test passes, because each asserts one edge.
agents/src/telemetry/testing/trace_schema.ts writes the rules down once:

- SPAN_PARENTS: for every span the JS framework emits, the parents it
  may have (ROOT for none, ANY for spans that follow their caller).
  Unknown names are violations, so a new span must be registered.
- MAY_OUTLIVE_PARENT: the child/parent edges where the child may end
  after its parent, each with its reason. Everything else must sit
  inside its parent, with 2 ms of slack.
- checkTrace(): one trace id, every parent present and allowed, bounds,
  one agent_turn per lk.speech_id, lk.generation_count equal to the
  speech's own generation events, every eou_wait with an outcome.
- fromReadableSpans / fromOtlpJson: an in-memory exporter or an export
  downloaded from LiveKit Cloud, same rules. As a CLI
  (`pnpm exec tsx agents/src/telemetry/testing/trace_schema.ts x.json`)
  it prints the span summary and the violations, tolerating orphans in
  a partial export.

assertTraceWellFormed() now ends the full-session tests: the tool call
and plain reply in agent_turn_span, the barge-in and handoff in
coverage_spans, the hook and redaction sessions in eou_wait_span, the
lifecycle and SIP sessions in session_lifecycle_span. The module is
test support: imported by tests only, not exported from the package,
and free of framework imports so the CLI runs on the source file.

Schema differences from Python, all from what the JS code emits:
answering_machine_detection is JS's name for `amd`; JS has no
llm_fallback_adapter / tts_fallback_adapter / tts_stream_adapter spans
(its adapters emit the plain request spans, which nest under the
attempt's *_request_run), no wait_for_video_track, no judge_evaluation;
rpc_handler may be a root, since without a session the SDK dispatches
on a context carrying no span.

Test hardening: the eou_wait full-session helper decided the turn 20 ms
after the fake STT final was due, so a loaded host could open a second
user turn (the intermittent failure seen in full-suite runs); the
endpointing delay now leaves a 170 ms margin.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…etrics

- EXTERNAL allowed parent: a session started inside an integrator's own
  span (which the session inherits) passes, whether that span is in the
  export or not; unknown spans with nothing of ours above them are the
  application's, unknown spans under ours are still reported
- realtime_metrics may end after realtime_inference: the provider reports
  usage after the response

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@davidzhao
davidzhao force-pushed the dz/telemetry-trace-schema branch from 8d1afc6 to 474081a Compare September 26, 2026 05:54
@davidzhao
davidzhao merged commit fae4ab7 into main Sep 26, 2026
11 of 12 checks passed
@davidzhao
davidzhao deleted the dz/telemetry-trace-schema branch September 26, 2026 06:04
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