Trace Context & request correlation
Control Zero is a W3C Trace Context participant. Every request that flows agent → SDK → backend → gateway → LLM provider carries one stable trace-id, so you can follow a single operation end to end across logs, the audit trail, and your own observability stack.
This is the same traceparent header OpenTelemetry uses by default, so if your
application is already instrumented, Control Zero continues your trace rather
than starting a new one.
Headers we honor on ingest
On every inbound request the backend and the gateway resolve one correlation id, using this precedence:
| Priority | Inbound header | Behavior |
|---|---|---|
| 1 | traceparent (valid) | Continue the trace: keep the trace-id, mint a new child span for this hop. |
| 2 | X-Correlation-ID | Adopt that value as the correlation id and synthesize a valid traceparent. |
| 3 | X-Request-ID | Same as X-Correlation-ID. |
| 4 | (none / malformed) | Generate a fresh W3C trace. |
A malformed traceparent is never forwarded — Control Zero starts a new trace
instead of propagating a bad value.
traceparent format
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
^^ ^------------------------------^ ^--------------^ ^^
| trace-id (32 hex, not all-zero) parent/span-id trace-flags
version (00) (16 hex) (01 = sampled)
Validation is strict: lowercase hex only, the trace-id and parent-id must not be
all-zero, version ff is rejected, and a header longer than 512 characters is
rejected. A version higher than 00 is parsed using the 00 layout (its first
55 characters) per the spec's forward-compatibility rule.
What we emit
On the response, Control Zero echoes all three headers so a caller can quote any of them:
traceparent— carries this hop's child span, so the next reader's parent is Control Zero.X-Request-IDandX-Correlation-ID— carry the trace-id (the stable correlation value).
On every provider-bound request (Anthropic, OpenAI, Google, Azure OpenAI,
Bedrock, Vertex), the gateway injects traceparent with the same trace-id and a
fresh child span, plus the correlation headers — so the trace continues into the
upstream provider. For AWS Bedrock (SigV4) the correlation headers are part of
the signed header set, so the request signature still verifies.
Where the trace-id shows up
- Logs. The request-scoped logger carries
trace_id(andcorrelation_idfor back-compat) on every line in the request's lifecycle. - Audit. The trace-id is a first-class, indexed column on the audit stores.
SELECT ... WHERE trace_id = '<id>'returns every audit row for that request, rather than scanning JSON. Rows produced outside a correlated request context store an empty /NULLtrace-id — never a fabricated one. - Error bodies. Structured error responses include the correlation id so you can quote it in a support request and an operator can grep straight to the log line.
Propagation diagram
agent / host app
│ (TRACEPARENT env, optional)
▼
ControlZero SDK ── originates or continues the trace
│ traceparent: 00-<trace-id>-<spanA>-01
▼
backend / gateway ── continues the trace, new child span
│ traceparent: 00-<trace-id>-<spanB>-01
▼
LLM provider ── receives the same trace-id
The trace-id (<trace-id> above) is constant across every hop; only the span-id
changes per hop.
Originating a trace from the SDK
The Python, Node, and Go SDKs originate the trace on outbound calls. If your
host application already set a TRACEPARENT environment variable (the
convention many runtimes and OpenTelemetry auto-instrumentation use), the SDK
continues it; otherwise it generates a new trace. Either way the trace-id the
SDK chose appears in the gateway response, the logs, and the audit trail.
You do not need to configure anything — propagation is automatic. To correlate a
Control Zero request with a trace your application already owns, set
TRACEPARENT in the SDK's environment before the call.
Trust note
The inbound trace-id is attacker-influenceable: any client can set the header.
Control Zero treats it strictly as a correlation value — never as a security
identity, and never for authorization, rate-limit keying, or cache keying. A
client may therefore choose its own trace-id; this is inherent to (and required
by) the W3C model, and matches the long-standing behavior where an inbound
X-Request-ID is adopted verbatim.
Limitations
Control Zero implements the traceparent propagation half of Trace Context. It
does not author tracestate (an inbound tracestate is forwarded unmodified),
and it does not emit OpenTelemetry spans or run a sampler — it is a propagation
participant, not a tracing backend. Point your own OpenTelemetry collector at
the trace-id to assemble spans.