diff --git a/instrumentation/opentelemetry-instrumentation-genai-openai-agents/src/opentelemetry/instrumentation/genai/openai_agents/span_processor.py b/instrumentation/opentelemetry-instrumentation-genai-openai-agents/src/opentelemetry/instrumentation/genai/openai_agents/span_processor.py index f78552f1..95fdff87 100644 --- a/instrumentation/opentelemetry-instrumentation-genai-openai-agents/src/opentelemetry/instrumentation/genai/openai_agents/span_processor.py +++ b/instrumentation/opentelemetry-instrumentation-genai-openai-agents/src/opentelemetry/instrumentation/genai/openai_agents/span_processor.py @@ -1312,7 +1312,7 @@ def on_trace_start(self, trace: Trace) -> None: otel_span = self._tracer.start_span( name=trace.name, attributes=attributes, - kind=SpanKind.SERVER, # Root span is typically server + kind=SpanKind.INTERNAL, # Root span represents local workflow; not a remote server span ) self._root_spans[trace.trace_id] = otel_span diff --git a/instrumentation/opentelemetry-instrumentation-genai-openai-agents/tests/test_z_span_processor_unit.py b/instrumentation/opentelemetry-instrumentation-genai-openai-agents/tests/test_z_span_processor_unit.py index 0a2d2cda..f1febb5f 100644 --- a/instrumentation/opentelemetry-instrumentation-genai-openai-agents/tests/test_z_span_processor_unit.py +++ b/instrumentation/opentelemetry-instrumentation-genai-openai-agents/tests/test_z_span_processor_unit.py @@ -503,6 +503,22 @@ def test_span_lifecycle_and_shutdown(processor_setup): == sp.GenAIOperationName.INVOKE_AGENT ) +def test_root_span_kind_is_internal(processor_setup): + """Regression test for #154: root span must be INTERNAL, not SERVER. + + invoke_agent root spans don't represent the server side of a + synchronous remote call, so SpanKind.SERVER is incorrect per the + OTel semantic conventions for span kinds. + """ + processor, exporter = processor_setup + + trace = FakeTrace(name="workflow", trace_id="trace-root-kind") + processor.on_trace_start(trace) + processor.on_trace_end(trace) + + finished = exporter.get_finished_spans() + assert len(finished) == 1 + assert finished[0].kind is SpanKind.INTERNAL def test_chat_span_renamed_with_model(processor_setup): processor, exporter = processor_setup