_ContextPropagationWorkflowOutboundInterceptor.start_activity (likewise start_child_workflow and start_local_activity) starts the temporal:startActivity span with mark_as_current=True so that set_header_from_context captures it, and finishes it in the activity handle's done callback without restoring the previous current span. The span therefore stays current in the Agents SDK scope after it finishes, and every span created later within the same enclosing span is parented to it. In an agent loop, tool calls, handoffs, and guardrails that follow a model call within a turn nest under that model call's temporal:startActivity instead of under the turn:
turn
└─ temporal:startActivity (model call)
├─ temporal:executeActivity → response
└─ lookup_account (tool) <- should be a sibling of the model call under "turn"
└─ temporal:startActivity → temporal:executeActivity
test_openai_tracing.py asserts turn → temporal:startActivity → temporal:executeActivity, which is the intended shape, but its workflow has no tool calls, so this is not covered. The interceptor is shared by both tracing modes, so the OpenAI platform dashboard shows the same nesting.
Fix: keep the span current only while the header is captured. Record the current span before span.start(mark_as_current=True) and restore it right after set_header_from_context (Scope.set_current_span(previous)), leaving the span open until the done callback finishes it. Resetting inside the done callback is not an option, because it may run in a different contextvars context than the one that started the span.
Related but separate: #1852 (OTel bridge replica registration). Found while validating temporalio/samples-python#365.
_ContextPropagationWorkflowOutboundInterceptor.start_activity(likewisestart_child_workflowandstart_local_activity) starts thetemporal:startActivityspan withmark_as_current=Trueso thatset_header_from_contextcaptures it, and finishes it in the activity handle's done callback without restoring the previous current span. The span therefore stays current in the Agents SDK scope after it finishes, and every span created later within the same enclosing span is parented to it. In an agent loop, tool calls, handoffs, and guardrails that follow a model call within a turn nest under that model call'stemporal:startActivityinstead of under the turn:test_openai_tracing.pyassertsturn → temporal:startActivity → temporal:executeActivity, which is the intended shape, but its workflow has no tool calls, so this is not covered. The interceptor is shared by both tracing modes, so the OpenAI platform dashboard shows the same nesting.Fix: keep the span current only while the header is captured. Record the current span before
span.start(mark_as_current=True)and restore it right afterset_header_from_context(Scope.set_current_span(previous)), leaving the span open until the done callback finishes it. Resetting inside the done callback is not an option, because it may run in a differentcontextvarscontext than the one that started the span.Related but separate: #1852 (OTel bridge replica registration). Found while validating temporalio/samples-python#365.