Skip to content

Trace context (traceparent) not propagated from controller to agent pods #1295

@syn-zhu

Description

@syn-zhu

Summary

W3C TraceContext headers (traceparent, tracestate) are not propagated from the kagent controller to agent pods, breaking distributed tracing across the controller-agent boundary.

When an upstream service (e.g., AgentGateway) sends a request with a traceparent header to the kagent controller, the trace context is lost when the controller forwards the request to the agent pod via the A2A client. This results in disconnected traces — the upstream service's spans appear under one trace ID while the agent's spans appear under a completely different trace ID.

Root Cause

There are two independent issues:

1. Go Controller: A2A client drops incoming HTTP headers

The A2ARequestHandler in go/internal/httpserver/auth/authn.go (line 82-117) only injects X-User-Id and Authorization headers into the outgoing request to agent pods. The trpc-a2a-go A2A client constructs a new HTTP request from the JSON-RPC payload, so the original traceparent/tracestate headers from the incoming request are not carried over.

Flow where headers are lost:

Incoming HTTP request (with traceparent)
  → A2A Server (trpc-a2a-go) receives request with headers ✅
  → PassthroughManager.OnSendMessage() — only passes protocol.SendMessageParams (no HTTP headers)
  → A2AClient.SendMessage() — constructs NEW HTTP request
  → A2ARequestHandler — only adds auth headers, NOT trace headers ❌
  → Agent pod receives request WITHOUT traceparent

2. Python Agent: No W3C TraceContext propagator configured

In python/packages/kagent-core/src/kagent/core/tracing/_utils.py, the configure() function sets up FastAPIInstrumentor but does not call set_global_textmap_propagator(TraceContextTextMapPropagator()). Even if the traceparent header reached the agent pod, the Python OTEL SDK would not extract it.

Note: The OTEL_PROPAGATORS environment variable does not automatically configure the propagator in the Python OTEL SDK — explicit code is required.

Suggested Fix

Go Controller (authn.go)

Store trace headers from the incoming request in context, then inject them in A2ARequestHandler:

// In middleware.go or similar — capture trace headers into context
type traceHeadersKey struct{}

func traceHeaderMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        ctx := r.Context()
        if tp := r.Header.Get("traceparent"); tp != "" {
            ctx = context.WithValue(ctx, traceHeadersKey{}, map[string]string{
                "traceparent": tp,
                "tracestate":  r.Header.Get("tracestate"),
            })
        }
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

// In A2ARequestHandler — inject trace headers into outgoing request
if headers, ok := ctx.Value(traceHeadersKey{}).(map[string]string); ok {
    if tp := headers["traceparent"]; tp != "" {
        req.Header.Set("traceparent", tp)
    }
    if ts := headers["tracestate"]; ts != "" {
        req.Header.Set("tracestate", ts)
    }
}

Python Agent (_utils.py)

Add propagator setup after TracerProvider configuration:

from opentelemetry.propagate import set_global_textmap_propagator
from opentelemetry.propagators.composite import CompositePropagator
from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator

# Inside configure(), after trace.set_tracer_provider():
set_global_textmap_propagator(
    CompositePropagator([TraceContextTextMapPropagator()])
)

Reproduction

  1. Deploy kagent with tracing enabled (OTEL_TRACING_ENABLED=true)
  2. Send a request with a known traceparent header:
    curl -H "traceparent: 00-aaaabbbbccccddddeeee111122223344-1234567890abcdef-01" \
      -H "Content-Type: application/json" \
      -X POST http://<gateway>/api/a2a/<namespace>/<agent>/ \
      -d '{"jsonrpc":"2.0","method":"message/send","params":{"message":{"role":"user","parts":[{"kind":"text","text":"hello"}]}},"id":"test"}'
  3. Check traces in your OTEL backend — the agent spans will have a different trace ID than aaaabbbbccccddddeeee111122223344

Environment

  • kagent version: latest main branch (as of 2026-02-13)
  • Upstream: AgentGateway (sends traceparent headers correctly)
  • OTEL backend: Langfuse (via OTEL Collector)
  • Python OTEL SDK: opentelemetry-sdk
  • trpc-a2a-go: used for A2A client/server in Go controller

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status

    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions