Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 138 additions & 0 deletions proposals/00654_trace-context-propagation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
---
issue: https://github.com/praxis-proxy/praxis/issues/1051
discussion: https://github.com/praxis-proxy/praxis/issues/1051
status: proposed
repos:
- praxis
- ai
authors:
- VaishnaviHire
- cdoern
graduation_criteria:
- How? section with requirements and design
- Request-scoped trace context model reviewed by stakeholders
- Forwarded upstream requests propagate x-request-id and W3C traceparent when enabled
- Subrequests propagate x-request-id and W3C traceparent transparently through the default subrequest path when enabled
- Default/easy subrequest API cannot accidentally bypass trace propagation
- AI delegated callouts that use subrequests are covered without AI-specific trace header stitching
- Unit and integration tests cover inbound, generated, malformed, and subrequest propagation cases
stakeholders:
- shaneutt
- alexsnaps
- aslakknutsen
- leseb
related:
- https://github.com/praxis-proxy/ai/pull/654
---

# Trace Context Propagation

## What?

Add core support for request correlation and W3C trace context
propagation across Praxis request execution paths.

Praxis already has a `request_id` observability filter that can
forward or generate `X-Request-ID`, and it has OpenTelemetry/span
export plumbing. What is missing is a shared, request-scoped trace
context that can be used consistently by both the normal forwarded
upstream request and by subrequests created during request processing.

This proposal introduces that missing correlation model in core. When
trace propagation is enabled, Praxis should resolve or generate the
correlation values for a request once, store them in request-scoped
context, and propagate them through the framework-owned request and
subrequest paths.

The propagated headers are:

- `x-request-id` — request correlation ID used primarily for logs and
support/debugging.
- `traceparent` — W3C Trace Context header used to continue a
distributed trace across services.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Large] The W3C Trace Context specification (Level 1, Section 3.3) defines two headers that conforming implementations must propagate together: traceparent and tracestate. The spec states: "If a tracing system receives a W3C Trace Context with a traceparent header, it MUST propagate the tracestate header if received."

The proposal lists only traceparent (line 52). The tracestate header carries vendor-specific trace context that must not be dropped when forwarding or creating subrequests.

Either:

  1. Add tracestate to the propagated headers list (lines 47-52) and Goals (lines 65-71), or
  2. Explicitly list tracestate as a Non-Goal with rationale for why partial W3C conformance is acceptable at this stage (e.g., "v1 propagates traceparent only; tracestate deferred to post-v1 after OTel integration is validated").


The default subrequest path should be context-aware. Filters and
call sites should not need to know whether tracing is enabled, and they
should not manually add regular tracing headers. If a filter creates a
subrequest through the supported subrequest subsystem, propagation
should happen transparently when enabled.

### Goals

- Add request-scoped trace context handling in core.
- Reuse an existing request ID when present, including values produced
by the existing `request_id` filter, or generate one when absent.
- Accept and continue valid inbound W3C `traceparent` values.
- Generate a valid W3C trace context when inbound trace context is
absent or malformed.
- Propagate `x-request-id` and `traceparent` to the forwarded upstream
request when enabled.
- Propagate `x-request-id` and `traceparent` to subrequests
transparently through the default subrequest path when enabled.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Medium] The W3C Trace Context spec requires each participating system to update the parent-id field in traceparent when creating child spans to produce a hierarchical trace graph. If subrequests copy traceparent verbatim (without updating parent-id), all trace legs will appear as siblings rather than children of the originating request in tools like Jaeger or Tempo.

Line 70-71 states "Propagate x-request-id and traceparent to subrequests" but does not clarify whether propagation is verbatim or involves parent-id generation.

This distinction fundamentally affects trace topology and should be stated in the What/Why phase. Add a note or Goal clarifying the intended span relationship:

  • "Subrequests generate a fresh parent-id to create proper parent-child hierarchy in distributed traces", or
  • "v1 propagates traceparent unchanged for flat correlation; hierarchical span relationships deferred to future OTel span integration."

This is not an implementation detail — it's a core design choice that affects whether the feature delivers hierarchical tracing or just correlation.

- Keep tracing/correlation propagation in core plumbing rather than in
individual filters.
- Make the context-aware subrequest path the default API so new
callouts do not accidentally bypass propagation.
- Allow AI request flows such as file resolve and file search to rely
on the core mechanism rather than manually stitching correlation
headers into delegated callouts.
- Validate propagation behavior with unit and integration coverage.

### Non-Goals

- Defining an opt-out path before a concrete use case exists.
- Adding AI-specific trace parsing or header construction.
- Implementing OGX-to-vector-backend propagation as part of the core
Praxis change.

## Why?

### Motivation

A single downstream request can fan out into multiple upstream or
subrequest operations. Without consistent correlation propagation,
the services involved see unrelated HTTP requests. That makes it difficult
to answer operational questions such as:

- Which backend or delegated callout belonged to this user request?
- Was latency caused by the forwarded model request or by retrieval?
- Which logs across Praxis, AI callouts, OGX, or another backend belong
to the same request?
- Did a subrequest bypass the normal tracing path?

The current AI-side prototype in
[praxis-proxy/ai#654](https://github.com/praxis-proxy/ai/pull/654)
proves the need for propagating `x-request-id` and W3C `traceparent`
across AI request legs. Review feedback on that PR pointed out that the
mechanism is not AI-specific. Request ID resolution, trace context
validation, request-scoped context storage, and propagation through
subrequests are framework concerns.

Keeping this logic in individual filters can cause difficulty in debugging.
Praxis should avoid that by making propagation as core as possible.

The subrequest abstraction already implies a child relationship to the
request that created it. Trace propagation should reflect that
relationship. If a subrequest is made through the supported subrequest
subsystem, it should inherit the parent request's correlation context by
default when propagation is enabled.

### User Stories

- As a platform operator, I want all forwarded requests and subrequests for one
downstream request to share correlation identifiers so that I can
debug failures and latency without manually stitching unrelated logs
together.
- As a platform operator, I want Praxis to propagate W3C trace context
through framework-owned paths so that distributed traces can connect
client requests, forwarded upstream requests, and delegated
subrequests.
- As a filter author, I want the default subrequest API to handle
tracing headers for me so that I do not need to know about or
manually add `x-request-id` and `traceparent`.
- As a maintainer, I want the easy subrequest path to be context-aware
so that new filters and callout paths do not accidentally bypass
observability plumbing.
- As a support engineer, I want `x-request-id` to remain available for
log search while `traceparent` carries the distributed trace identity
across services.
Loading