From 9d473ced62a700e399237e73fde2cda4cbf6d2bb Mon Sep 17 00:00:00 2001 From: David Hyde Date: Thu, 17 Sep 2026 11:34:30 -0500 Subject: [PATCH 1/2] Remove TemporalIdGenerator span/trace ID seeding The OpenAI Agents OTel interceptor, the only caller of seed_span_id and seed_trace_id, now propagates the caller's span as a remote parent instead of re-minting it under the caller's IDs (temporalio/ai-integrations#26), and #1868 removes it from this repository. Drop the two methods, their context-local seed queues, the seed-consumption branches in the generate_* methods, and the test that only exercised the seeding. --- CHANGELOG.md | 6 +++ .../contrib/opentelemetry/_id_generator.py | 39 ------------------ .../test_opentelemetry_plugin.py | 41 ------------------- 3 files changed, 6 insertions(+), 80 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bf669826..c9a7039fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,12 @@ to include examples, links to docs, or any other relevant information. package, and compatibility modules preserve the old public `temporalio.contrib.openai_agents` imports. New code should depend on `temporalio-openai-agents` directly and import `temporalio.openai_agents`. +- `temporalio.contrib.opentelemetry`: removed `TemporalIdGenerator.seed_span_id()` and + `seed_trace_id()`. They existed for the OpenAI Agents OTel interceptor, which now propagates + the caller's span as a remote parent instead of re-minting it under the caller's IDs; nothing + in the SDK or in `temporalio-openai-agents` calls them. To connect a trace across a boundary, + set a remote-parent `SpanContext`; to control the ID sequence, pass your own `IdGenerator` to + `create_tracer_provider(id_generator=...)`. ### Fixed diff --git a/temporalio/contrib/opentelemetry/_id_generator.py b/temporalio/contrib/opentelemetry/_id_generator.py index f553cd273..9aaa00ef1 100644 --- a/temporalio/contrib/opentelemetry/_id_generator.py +++ b/temporalio/contrib/opentelemetry/_id_generator.py @@ -1,5 +1,4 @@ import random -from contextvars import ContextVar from opentelemetry.sdk.trace.id_generator import IdGenerator from opentelemetry.trace import ( @@ -40,39 +39,11 @@ class TemporalIdGenerator(IdGenerator): inside a workflow execution, ensuring deterministic span and trace IDs across workflow replays. Falls back to standard random generation outside of workflows. - - Can be seeded with OpenTelemetry span IDs from client context to maintain - proper span parenting across the client-workflow boundary. """ def __init__(self, id_generator: IdGenerator): """Initialize a TemporalIdGenerator.""" self._id_generator = id_generator - self._traces: ContextVar[tuple[int, ...]] = ContextVar( - "temporalio_otel_trace_id_seeds", default=() - ) - self._spans: ContextVar[tuple[int, ...]] = ContextVar( - "temporalio_otel_span_id_seeds", default=() - ) - - def seed_span_id(self, span_id: int) -> None: - """Seed the generator with a span ID to use as the first result. - - This is typically used to maintain OpenTelemetry span parenting - when crossing the client-workflow boundary. - - Args: - span_id: The span ID to use as the first generated span ID. - """ - self._spans.set((*self._spans.get(), span_id)) - - def seed_trace_id(self, trace_id: int) -> None: - """Seed the generator with a trace ID to use as the first result. - - Args: - trace_id: The trace ID to use as the first generated trace ID. - """ - self._traces.set((*self._traces.get(), trace_id)) def generate_span_id(self) -> int: """Generate a span ID using Temporal's deterministic random when in workflow. @@ -80,11 +51,6 @@ def generate_span_id(self) -> int: Returns: A 64-bit span ID. """ - spans = self._spans.get() - if spans: - self._spans.set(spans[:-1]) - return spans[-1] - if workflow_random := _get_workflow_random(): span_id = workflow_random.getrandbits(64) while span_id == INVALID_SPAN_ID: @@ -98,11 +64,6 @@ def generate_trace_id(self) -> int: Returns: A 128-bit trace ID. """ - traces = self._traces.get() - if traces: - self._traces.set(traces[:-1]) - return traces[-1] - if workflow_random := _get_workflow_random(): trace_id = workflow_random.getrandbits(128) while trace_id == INVALID_TRACE_ID: diff --git a/tests/contrib/opentelemetry/test_opentelemetry_plugin.py b/tests/contrib/opentelemetry/test_opentelemetry_plugin.py index 022645fee..37c461231 100644 --- a/tests/contrib/opentelemetry/test_opentelemetry_plugin.py +++ b/tests/contrib/opentelemetry/test_opentelemetry_plugin.py @@ -1,7 +1,5 @@ import logging -import threading import uuid -from concurrent.futures import ThreadPoolExecutor from datetime import timedelta from typing import Any @@ -12,7 +10,6 @@ from opentelemetry.sdk.trace.export.in_memory_span_exporter import ( InMemorySpanExporter, ) -from opentelemetry.sdk.trace.id_generator import RandomIdGenerator from opentelemetry.trace import ( get_tracer, ) @@ -21,7 +18,6 @@ from temporalio import activity, nexus, workflow from temporalio.client import Client, WorkflowFailureError from temporalio.contrib.opentelemetry import OpenTelemetryPlugin, create_tracer_provider -from temporalio.contrib.opentelemetry._id_generator import TemporalIdGenerator from temporalio.exceptions import ApplicationError from temporalio.testing import WorkflowEnvironment @@ -33,43 +29,6 @@ logger = logging.getLogger(__name__) -@pytest.mark.parametrize( - ("seed_method", "generate_method"), - [ - ("seed_span_id", "generate_span_id"), - ("seed_trace_id", "generate_trace_id"), - ], -) -def test_temporal_id_generator_seeds_are_context_local( - seed_method: str, generate_method: str -) -> None: - generator = TemporalIdGenerator(RandomIdGenerator()) - first_seeded = threading.Event() - second_seeded = threading.Event() - first_generated = threading.Event() - seed_values = (123, 456) - - def generate_first() -> int: - getattr(generator, seed_method)(seed_values[0]) - first_seeded.set() - assert second_seeded.wait(timeout=5) - generated = getattr(generator, generate_method)() - first_generated.set() - return generated - - def generate_second() -> int: - assert first_seeded.wait(timeout=5) - getattr(generator, seed_method)(seed_values[1]) - second_seeded.set() - assert first_generated.wait(timeout=5) - return getattr(generator, generate_method)() - - with ThreadPoolExecutor(max_workers=2) as executor: - first = executor.submit(generate_first) - second = executor.submit(generate_second) - assert (first.result(), second.result()) == seed_values - - @activity.defn async def simple_no_context_activity() -> str: with get_tracer(__name__).start_as_current_span("Activity"): From d144a98e2d4816126b3364b30ee4689e87aa0bb0 Mon Sep 17 00:00:00 2001 From: David Hyde Date: Tue, 22 Sep 2026 18:06:26 -0500 Subject: [PATCH 2/2] condense changelog --- CHANGELOG.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9a7039fe..e7153279c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,11 +34,7 @@ to include examples, links to docs, or any other relevant information. `temporalio.contrib.openai_agents` imports. New code should depend on `temporalio-openai-agents` directly and import `temporalio.openai_agents`. - `temporalio.contrib.opentelemetry`: removed `TemporalIdGenerator.seed_span_id()` and - `seed_trace_id()`. They existed for the OpenAI Agents OTel interceptor, which now propagates - the caller's span as a remote parent instead of re-minting it under the caller's IDs; nothing - in the SDK or in `temporalio-openai-agents` calls them. To connect a trace across a boundary, - set a remote-parent `SpanContext`; to control the ID sequence, pass your own `IdGenerator` to - `create_tracer_provider(id_generator=...)`. + `seed_trace_id()`. ### Fixed