Skip to content

tests: trace-shape schema and checker - #2501

Draft
davidzhao wants to merge 1 commit into
dz/telemetry-agent-turnfrom
dz/telemetry-trace-schema
Draft

davidzhao wants to merge 1 commit 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.

🤖 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: 70effd6

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
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>
@davidzhao
davidzhao force-pushed the dz/telemetry-trace-schema branch from 473c53d to 70effd6 Compare September 16, 2026 04:34
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.

1 participant