Skip to main content

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:

PriorityInbound headerBehavior
1traceparent (valid)Continue the trace: keep the trace-id, mint a new child span for this hop.
2X-Correlation-IDAdopt that value as the correlation id and synthesize a valid traceparent.
3X-Request-IDSame 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-ID and X-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 (and correlation_id for 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 / NULL trace-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.