OpenTelemetry Routing Semantics
On this page
Praxis AI can add AI routing decisions to the request trace created and exported by Praxis core. Build the proxy with:
cargo build --release -p praxis-ai-proxy --features opentelemetry
The feature is disabled by default. It does not install an exporter or parse OpenTelemetry environment variables. Configure exporting, propagation, sampling, and request lifecycle tracing through Praxis core.
After intelligent_route successfully selects a provider, the feature emits a
short routing.select child span. The span contains bounded routing identity,
admission, locality, rank, tier, and overlay revision attributes. It never
records request or response bodies, prompts, credentials, authorization
headers, cookies, or session keys.
After provider_route validates the edge-selected candidate and resolves it
to a provider-local backend cluster, the feature emits a short provider.route
child span. The span records the resolved backend cluster the request was
routed to; it is not proof that a downstream endpoint, pod, or model server
successfully served the request. It contains:
provider.id: the configured provider-boundary identifier for this listener. This is a configuration value, not necessarily the mTLS peer identity.provider.backend.cluster: the configured backend cluster the candidate resolved to.provider.route.model: the configured model accepted for the resolved route.provider.route.candidate_id: the edge-selected candidate ID that was validated and resolved.overlay.revision: present only when the edge supplied a serving overlay revision that passed syntax and trust-boundary validation. It is correlation evidence only, not a provider-local config revision and not an authorization decision.
Like routing.select, this span never records request or response bodies,
prompts, credentials, authorization headers, cookies, session keys, or raw
request identifiers.
The division of responsibility is intentional:
Edge Praxis core request span
|
+-- routing.select (Praxis AI, intelligent_route)
`-- upstream hop (Praxis core)
Provider Praxis core request span
|
+-- provider.route (Praxis AI, provider_route)
`-- upstream hop (Praxis core)
Either request span, when its pipeline runs token_rate_limit
|
+-- token_rate_limit (Praxis AI; admission until the response body ends)
`-- ...
The routing decision spans are short-lived siblings of the later transport
span within the same request. The token_rate_limit span is the exception:
it opens at admission and is closed when the response body ends, because its
actual_cost field is only known once provider usage has been read (see
below). When trace context is propagated between gateways, Praxis core
connects the edge provider-hop client span to the provider request span.
Praxis core owns the complete HTTP span lifetime and transport boundaries at both the edge and the provider-local listener. Praxis AI records only the semantic decisions it makes at each hop. This prevents duplicate request roots, conflicting trace propagation, and multiple exporter runtimes.
Agentic callouts follow the same ownership rule. When a listener enables the
core trace_context filter, Praxis AI projects only its typed TraceContext
into isolated web-search, OGX file-search, OGX file-resolution, and MCP child
requests. Praxis core then emits the same x-request-id and W3C trace ID with a
fresh span ID for every outbound hop. No ambient request extensions, provider
credentials, or MCP authorization assertions are copied into trace fields.
Token rate limiting
When the experimental token_rate_limit filter and the opentelemetry
feature are both enabled, every request that reaches a matching rule gets a
request-scoped token_rate_limit span. A hierarchy denial, authentication
miss, or backend error before the rule reservation uses the same span with
token_rate_limit.rule set to hierarchy:org, hierarchy:team, or
hierarchy:user. It records these bounded fields:
token_rate_limit.rule: configured rule name, orhierarchy:org,hierarchy:team, orhierarchy:userfor a hierarchy-level outcome.token_rate_limit.algorithm:sliding_windowortoken_bucket.token_rate_limit.estimated_cost: tokens reserved or considered at admission.token_rate_limit.decision:admitted(reservation made),denied(429, budget exhausted),soft_over_quota(soft mode forwarded over budget with annotation),unauthenticated(401, no trusted subject to key the budget on),error(503, the backend failed and the filter failed closed),bypassed(explicit fail-open before a mutation was dispatched), orunconfirmed(explicit fail-open after a mutation may have been dispatched). Soft decisions align withpraxis_trl_requests_total{result=…}.token_rate_limit.actual_cost: provider-reported weighted usage, recorded when the response body ends. Absent when the decision was notadmittedor no usage metadata was produced.
The token-rate-limit Prometheus metrics include
praxis_trl_fail_open_admissions_total{rule,backend,outcome} for explicit
backend.on_failure: open admissions. outcome is bypassed when no backend
mutation was dispatched and unconfirmed when a dispatched operation may have
committed. Fail-open admissions are not included in
praxis_trl_backend_errors_total, which is reserved for failed reservations
that fail closed with 503, plus reconciliation failures.
The bounded backend vocabulary is memory or shared_kv; Redis and Valkey
both use shared_kv.
The span is created at admission and dropped when the response body ends, so its lifetime covers the streamed response. It does not contain the authenticated subject, internal budget key, prompt, body, model, or other request-specific identity. Valkey reconciliation runs asynchronously after response processing; its success or failure is exposed through metrics and accounting logs rather than extending the span further.
Sentinel transport lifecycle is also metrics-only, not additional request-span
attributes. praxis_trl_sentinel_discovery_total{phase,result} records
bounded initial discovery and generation-fenced rediscovery outcomes;
praxis_trl_sentinel_failover_total{outcome} records proxy-side
recovery after invalidation. Their labels are fixed vocabularies and contain no
endpoint, service-name, credential, subject, or bucket-key values. A
recovered failover sample means that the proxy obtained a writable
connection; it is not evidence that Sentinel promoted a replica.