Skip to content

Commit be9728a

Browse files
committed
fix(voice): propagate caller's OTel context through AgentSession
Honour otelContext.active() when starting the agent_session span and when building rootSpanContext, so spans the caller created before session.start() become parents of LiveKit spans. Stop calling tracerProvider.register() in setupCloudTracer to avoid replacing the global AsyncLocalStorageContextManager. Fixes #924
1 parent adf2651 commit be9728a

3 files changed

Lines changed: 55 additions & 5 deletions

File tree

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// SPDX-FileCopyrightText: 2026 LiveKit, Inc.
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
import { context as otelContext, trace } from '@opentelemetry/api';
5+
import { InMemorySpanExporter, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base';
6+
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
7+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
8+
import { setTracerProvider, tracer } from './traces.js';
9+
10+
describe('DynamicTracer', () => {
11+
let exporter: InMemorySpanExporter;
12+
let provider: NodeTracerProvider;
13+
14+
beforeEach(() => {
15+
exporter = new InMemorySpanExporter();
16+
provider = new NodeTracerProvider({
17+
spanProcessors: [new SimpleSpanProcessor(exporter)],
18+
});
19+
provider.register();
20+
setTracerProvider(provider);
21+
});
22+
23+
afterEach(async () => {
24+
await provider.shutdown();
25+
otelContext.disable();
26+
trace.disable();
27+
});
28+
29+
it('inherits the active OTel context as parent when no explicit context is passed', async () => {
30+
const outerTracer = provider.getTracer('test');
31+
32+
await outerTracer.startActiveSpan('outer', async (outer) => {
33+
const child = tracer.startSpan({ name: 'child' });
34+
child.end();
35+
outer.end();
36+
});
37+
38+
const spans = exporter.getFinishedSpans();
39+
const outerSpan = spans.find((s) => s.name === 'outer');
40+
const childSpan = spans.find((s) => s.name === 'child');
41+
42+
expect(outerSpan).toBeDefined();
43+
expect(childSpan).toBeDefined();
44+
45+
// parentSpanId in OTel SDK v1; parentSpanContext.spanId in v2.
46+
const parentId =
47+
(childSpan as { parentSpanId?: string }).parentSpanId ??
48+
(childSpan as { parentSpanContext?: { spanId: string } }).parentSpanContext?.spanId;
49+
50+
expect(parentId).toBe(outerSpan!.spanContext().spanId);
51+
});
52+
});

agents/src/telemetry/traces.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,8 +258,7 @@ export async function setupCloudTracer(options: {
258258
resource,
259259
spanProcessors: [new MetadataSpanProcessor(metadata), new BatchSpanProcessor(spanExporter)],
260260
});
261-
tracerProvider.register();
262-
261+
// Don't call tracerProvider.register() — it would replace the global context manager.
263262
setTracerProvider(tracerProvider);
264263

265264
// Initialize standalone Pino cloud exporter (no OTEL SDK dependency)

agents/src/voice/agent_session.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { Mutex } from '@livekit/mutex';
55
import type { AudioFrame, Room } from '@livekit/rtc-node';
66
import type { TypedEventEmitter as TypedEmitter } from '@livekit/typed-emitter';
77
import type { Context, Span } from '@opentelemetry/api';
8-
import { ROOT_CONTEXT, context as otelContext, trace } from '@opentelemetry/api';
8+
import { context as otelContext, trace } from '@opentelemetry/api';
99
import { EventEmitter } from 'node:events';
1010
import type { ReadableStream } from 'node:stream/web';
1111
import type { z } from 'zod';
@@ -537,10 +537,9 @@ export class AgentSession<
537537

538538
this.sessionSpan = tracer.startSpan({
539539
name: 'agent_session',
540-
context: ROOT_CONTEXT,
541540
});
542541

543-
this.rootSpanContext = trace.setSpan(ROOT_CONTEXT, this.sessionSpan);
542+
this.rootSpanContext = trace.setSpan(otelContext.active(), this.sessionSpan);
544543

545544
await this._startImpl({
546545
agent,

0 commit comments

Comments
 (0)