fix(acp): forward W3C trace-context headers to downstream agents - #396
Conversation
filter_request_headers only allowed x-* headers through the forward to the downstream ACP server, silently stripping traceparent/tracestate/baggage. That detached the agent's observability trace (and the Temporal workflow/activity it signals) from the ingress trace, so a request couldn't be followed from the API call to the agent's work. Allow the three W3C trace-context headers (not sensitive, not hop-by-hop) through the forward. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Greptile P1: the allowlist change only helped callers that thread request_headers through get_headers() — i.e. event/send. task/create, message, streaming and cancel call get_headers(agent) with no request_headers, so filter_request_headers received nothing and traceparent was still dropped, leaving those downstream traces detached. get_headers now always extracts inbound W3C trace-context from self._request (which it already reads for delegation headers) and forwards it regardless of request_headers, so every operation continues the ingress trace. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Addressed the Greptile P1 (trace propagation incomplete) in The allowlist change alone only helped Fix: |
|
what is DD_TRACE_ENABLED in the cluster where this was verified, and in prod? If it differs, this fix silently won't apply where it matters most. -- My bad just saw the description |
Good point — the environment I'm testing on doesn't have ddtrace enabled.. For envs where it is enabled, this will just be a no-op. Still working through the end-to-end testing for trace propagation and trace correlation for both sync and async paths; the goal is to provide a paved migration path from ddtrace to LGTM for such services |
MeeSo-Scale
left a comment
There was a problem hiding this comment.
LGTM -- nit - forward_task_to_acp passes no headers, so filter_request_headers never runs on the create path
|
Re: the create-path nit (
|
| } | ||
|
|
||
|
|
||
| class TestExtractTraceContextHeaders: |
There was a problem hiding this comment.
nit: The new tests cover the two helpers, but the behavior this PR exists for is that get_headers forwards trace context even when the caller passes no request_headers (task/create, message, streaming, cancel). Consider one test that sets mock_request.headers = {"traceparent": "...", "baggage": "..."} and asserts await agent_acp_service.get_headers(sample_agent) includes them, similar to test_get_headers_server_request_id_wins_over_passthrough. That pins the actual wiring rather than just the helper.
| # message, streaming and cancel (which call get_headers(agent) with no | ||
| # request_headers) would drop traceparent and the downstream agent would | ||
| # start a detached trace. The inbound headers are on self._request. | ||
| inbound_headers = dict(self._request.headers) if getattr(self, "_request", None) is not None else {} |
There was a problem hiding this comment.
nit: _request is always assigned in __init__, and get_delegation_headers() two lines below dereferences self._request.state unconditionally, so this getattr guard never actually protects anything. Simpler to write dict(self._request.headers) directly.
Problem
filter_request_headers(agent_acp_service.py) only forwardsx-*headers to the downstream ACP server, so it silently strips W3Ctraceparent/tracestate/baggage. That detaches the downstream agent's observability trace — and the Temporal workflow/activity it signals — from the ingress trace, so a request can't be followed from the API call (task/create/event/send) to the agent's work. The agent's activity shows up in Tempo as a fresh root with no link back to the gateway.Verification (infra-staging, rocket-mock-async)
workflow → activity → egp/dbinto one trace.traceparent, so the agent starts a fresh trace.Fix
Allow the three W3C trace-context headers (
traceparent,tracestate,baggage) through the forward allowlist. They are neither hop-by-hop nor sensitive. With this,event/send(gateway) → agent ACP → workflow → activity → egp/db becomes one continuous trace.Tests
test_forwards_w3c_trace_context_headerspinning the new behavior.ruffclean.🤖 Generated with Claude Code
Greptile Summary
The PR completes W3C trace-context propagation from ingress requests to downstream ACP operations.
traceparent,tracestate, andbaggagethrough the safe-header filter.Confidence Score: 5/5
The PR appears safe to merge.
The previously reported trace-propagation gap is resolved because every downstream ACP operation obtains headers through get_headers, which now extracts W3C context from the request-bound inbound headers.
Important Files Changed
Sequence Diagram
sequenceDiagram participant Client participant AgentEx as AgentEx ingress participant ACP as Downstream ACP agent participant Temporal Client->>AgentEx: Request with traceparent/tracestate/baggage AgentEx->>AgentEx: Extract and filter trace context AgentEx->>ACP: JSON-RPC with trace-context headers ACP->>Temporal: Start workflow/activity in continued traceReviews (3): Last reviewed commit: "Merge branch 'main' into agentex-obs" | Re-trigger Greptile