Skip to main content

Routing Attributes

Routing evidence travels as span attributes, which is what OpenBox Core hashes into the session's Merkle tree. That is not a display choice: attributes are the only channel this record has, and they are what puts it inside the session's attestation rather than beside it.

This page is the reference for those attributes. Policies read them, the Routing Integrity panel aggregates them, and a run receipt projects them into readable claims.

The Two Records

The same vocabulary covers both ends of a call, so one rule can read either.

Recordhook_typeWhenWhat it is
Routing decisionllm_routing_requestBefore the request is builtWhere this prompt may go. A rule can refuse or narrow it.
Routing recordllm_provenanceAfter the answerWhat the gateway did. Evidence, sealed and stored.

A third type, llm_routing, is the routing step that appears only when a policy actually redirects a call.

What the Gateway Reported

Read back from GET /api/v1/generation?id=<gen-id> for every governed model call.

AttributeMeaning
gen_ai.upstream.providerThe provider that actually served the call
gen_ai.upstream.data_regionWhere the data was processed
gen_ai.upstream.is_byokWhether your own provider key was used
gen_ai.upstream.latency_msLatency of the attempt that served it
gen_ai.usage.total_costWhat the call cost
gen_ai.usage.upstream_costUpstream inference cost — meaningful for BYOK traffic only
gen_ai.usage.tokens_prompt · …tokens_completionToken counts
gen_ai.response.modelThe model that actually ran
gen_ai.response.finish_reasonHow the completion ended
gen_ai.generation.idOpenRouter's receipt number, so any claim can be re-checked at source

upstream_cost is confined to BYOK on purpose. A non-BYOK call reports 0 alongside a non-zero total, because for that traffic the gateway charge is the cost — so a fleet-wide margin figure computed from it would read as though the gateway kept everything.

The Failover Trail

AttributeMeaning
gen_ai.routing.providers_triedEvery provider on the chain, in order
gen_ai.routing.fallback_attemptsHow many attempts preceded the one that served it
gen_ai.routing.attemptsPer-attempt detail: provider, status and latency for each
gen_ai.routing.allow_fallbacksWhether the request permitted failover

gen_ai.routing.attempts is what makes the provider reliability scorecard honest. Scoring only the attempt that won would flatter every provider whose failures were quietly absorbed by failover.

What Was Asked For

AttributeMeaning
openbox.routing.declaredWhether this call named an allowlist at all
openbox.routing.requested_onlyThe providers the request will accept
openbox.routing.requested_orderPreference order — a preference, not a promise
openbox.routing.requested_modelsThe model fallback chain
openbox.routing.allow_fallbacksWhether the call fails closed
gen_ai.request.modelThe model asked for

The Comparisons

These are the attributes that turn a report into a check.

AttributeValuesMeaning
openbox.routing.honoredtrue / false / absentDid an allowed provider serve it? Absent when the request named no allowlist — such a call is unconstrained, not a pass.
openbox.model.requestedmodel idThe model the request named
openbox.model.honoredtrue / false / absentDid the model that ran match? Absent for openrouter/auto, where choosing the model is the point.
openbox.residency.approved_regionslistThe regions the policy approved
openbox.residency.declaredtrue / falseWhether this call ran under an approved-region list
openbox.residency.region_honoredtrue / false / absentDid it stay inside them? Absent when there is no list, or no region to compare.
openbox.residency.require_own_keytrue / falseWhether a customer-supplied key was required
openbox.residency.own_key_honoredtrue / false / absentWhether one was used

Absent is not false. Every comparison here is three-valued, and writing a rule against == false is what keeps an unconstrained or unchecked call from being reported as a violation. Writing one against != true would sweep them in.

When a Policy Redirected

Present only on calls a policy re-routed.

AttributeMeaning
openbox.routing.redirected_fromWhat the call originally asked for
openbox.routing.resolutionWhere it went instead
openbox.routing.retried_fromThe refused attempt this call replaces
openbox.routing.stageWhich half of the routing step this span is
openbox.routing.attemptWhich attempt this is

retried_from is what links a refused attempt to the corrected call that replaced it, so a reader can see that one call was re-routed rather than that two calls happened.

Reading Them in a Policy

provenance := span if {
some span in input.spans
span.hook_type == "llm_provenance"
}

# A dishonored call: an allowlist was named, someone else served it.
dishonored if provenance.attributes["openbox.routing.honored"] == false

# NOT `!= true` — that would also match an unconstrained call, which
# promised nothing and therefore broke nothing.

Identifying a Routing Span in Storage

Routing records are identified by the presence of gen_ai.generation.id in the attributes rather than by their span type. Core classifies these spans as llm_provenance now, but rows written before that classifier shipped carry whatever it computed at the time, and reclassification is not retroactive. The attribute is the one thing true of every such row, past and future.