feat(tracing): propagate OTel trace context across Temporal boundaries - #485
Open
NiteshDhanpal wants to merge 1 commit into
Open
feat(tracing): propagate OTel trace context across Temporal boundaries#485NiteshDhanpal wants to merge 1 commit into
NiteshDhanpal wants to merge 1 commit into
Conversation
Temporal serializes start_workflow / execute_activity across (potentially cross-process) boundaries and does not carry the active W3C traceparent, so spans created inside a workflow or activity become detached roots -- the trace shatters at every Temporal hop. This bites agentex directly: adk.tracing.span creates the business span as a Temporal activity when in_temporal_workflow(), so without propagation those spans detach from the turn's obs trace. Wire temporalio's first-party TracingInterceptor onto both Temporal client factories (worker client + the ACP's workflow-starting TemporalClient) and the AgentexWorker, so client -> workflow -> activity is one trace. The interceptor injects context on outbound calls and extracts + roots execution spans under it, using the global OpenTelemetry propagator. - ENABLED BY DEFAULT. Opt out with AGENTEX_TEMPORAL_TRACE_INTERCEPTOR_ENABLED=false (also 0/no/off). Safe no-op (never raises) if temporalio's OTel contrib isn't importable, so default-on can't break a worker. - Tracing interceptor is placed OUTERMOST on the worker so existing business interceptors (and their spans) nest under the propagated span. Tests: tests/lib/core/tracing/test_temporal_interceptor.py -- default-on returns a TracingInterceptor, env opt-out returns [], contrib-missing returns []. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
NiteshDhanpal
force-pushed
the
feat/temporal-otel-trace-interceptor
branch
from
August 3, 2026 21:49
65c1c5f to
7b34987
Compare
stephen-wang24
approved these changes
Aug 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Temporal breaks OpenTelemetry context propagation. A
start_workflow/execute_activitycall is serialized and dispatched to a (potentially different) worker process, and the active W3Ctraceparentis not carried across that boundary. So any span created inside a workflow or activity becomes a new detached root — the trace shatters at every Temporal hop.This bites agentex directly:
adk.tracing.spanbranches onin_temporal_workflow()and, when true, creates the business span as a Temporal activity (TracingActivityName.START_SPAN). Without propagation, those business spans (and any downstream spans) detach from the turn's obs trace.The existing
ContextInterceptorthreads business context (task_id) across the boundary — but nothing threads the obs-trace (W3C) context.Change
Wire temporalio's first-party
temporalio.contrib.opentelemetry.TracingInterceptoronto our Temporal client factories and worker, soclient → workflow → activityis one trace. It injects the active span context into Temporal headers on outbound calls, and extracts + roots the workflow/activity execution spans under it, using the global OpenTelemetry propagator.Wired at three points (both client factories + the worker):
core/temporal/workers/worker.py—get_temporal_client(worker's client) and theWorker(interceptors=...)(inbound execution, tracing outermost so business interceptors nest under it).core/clients/temporal/utils.py—get_temporal_clientused byTemporalClient(the ACP's workflow-starting client — the critical outboundstart_workflowpropagation point).core/tracing/temporal.py—temporal_tracing_interceptors()helper so every client/worker wires it identically.Default-on with an opt-out
Enabled by default. Opt out with
AGENTEX_TEMPORAL_TRACE_INTERCEPTOR_ENABLED=false(also accepts0/no/off). It also degrades to a no-op and never raises if temporalio's OpenTelemetry contrib isn't importable — so default-on can't break a worker. (adk already depends onopentelemetry-api/sdk, so the contrib is available in practice.)Ordering (important)
On the worker the tracing interceptor is outermost:
If reversed, business spans would be created before the trace context is active → back to detached roots.
Tests
tests/lib/core/tracing/test_temporal_interceptor.py— default-on returns aTracingInterceptor, env opt-out returns[], and the contrib-missing fallback returns[](verified against the real temporalio contrib).Risk
Low — additive, config-gated, no-op when disabled or when the contrib is absent; no workflow-determinism concern (
TracingInterceptoris designed for the workflow sandbox). Independent of PR #484 (correlation edge) — branched offmain.Relation to platform tracing
This is the Temporal-boundary piece of "W3C propagation everywhere": once it lands, an agent turn (or any workflow) that fans out through Temporal activities stays one trace end-to-end, and the business/anchor spans created inside activities attach to the turn.
🤖 Generated with Claude Code
Greptile Summary
The PR enables OpenTelemetry trace-context propagation across Temporal boundaries.
Confidence Score: 5/5
The PR appears safe to merge.
No blocking failure remains; the constructor now executes inside the guarded try block, so ordinary constructor failures follow the documented no-op fallback instead of aborting Temporal startup.
Important Files Changed
Sequence Diagram
sequenceDiagram participant C as Temporal Client participant TW as Temporal Workflow Worker participant A as Temporal Activity C->>TW: start_workflow + injected OTel context activate TW TW->>A: execute_activity + propagated OTel context activate A A-->>TW: activity result deactivate A TW-->>C: workflow result deactivate TWReviews (2): Last reviewed commit: "feat(tracing): propagate OTel trace cont..." | Re-trigger Greptile