Search

↑↓ to move↵ to openesc to closetop 6 documentation results

Book a demoTry TokenTriage

In development · Explore the product preview. Join the launch list or book a walkthrough.

Browse docs

Routing inputs

Look up every routing input, where you can use it, its type, and when it may be absent.

ReferenceLast updated

On this page

Use this page when you are writing or debugging a routing condition. It lists each supported input, the clauses where you can use it, and the cases where TokenTriage has no value for it.

An input with no value is absent. Absence is different from 0 and false, which are both present values. Most comparisons with an absent input evaluate to false, but != and not are important exceptions. How a routing decision is made shows the complete behavior.

If tiers, upstreams, or routing clauses are new to you, start with the Routing overview. A tier is a named model-and-upstream target; an upstream is the configured provider connection that serves it.

Scope decides what exists

Routing conditions run at different stages of a request. The stage determines which inputs are available.

Each condition scope names facts that exist at that momentReading left to right along one request's life. At the first moment the request has arrived and nothing has been chosen; request-scope inputs exist here — the request shape, the trace labels, the business dimensions, the classifier signals and the context providers — and they are readable in a rule's when clause and in a named predicate. Slightly later the router is comparing candidate tiers against each other; candidate-scope inputs exist only in that window and only inside a select node's where clause. Later still an attempt has come back with an answer; outcome-scope inputs exist only then and only inside a cascade stage's accept_when. Request-scope inputs stay readable in all three windows because a request does not stop existing. Naming an input outside its window is a config error that names the path, not a value that quietly reads as absent.one request, over timerequest scopethe request has arrived;nothing has been chosenrequest. trace. dim.signal. ctx.readable in when · predicatescandidate scopethe router is comparingone tier against anothercandidate.price.candidate.healthy …readable in select … whereoutcome scopean attempt has come backwith a real answeroutcome.statusoutcome.judge. …readable in accept_whenRequest-scope inputs stay readable in all three windows — a request does not stop existing.Naming an input outside its window is a config error that names the path, not a value that quietly reads as absent.

Read left to right along one request. Request-scope inputs exist as soon as the request arrives and stay readable everywhere afterwards. Candidate-scope inputs exist only while the router is comparing tiers, so they are readable only inside a select … where. Outcome-scope inputs exist only after an attempt has returned, so they are readable only inside a cascade … accept_when.

Use the clause you are writing to choose the matching scope:

Clause Inputs you can read
rule when or predicate request.*, trace.*, dim.*, signal.*, ctx.*
select … where all request-scope inputs, plus candidate.*
cascade … accept_when all request-scope inputs, plus outcome.*

Using an input outside its scope is different from reading an absent value. An out-of-scope input makes validation fail and identifies the invalid path. An absent input is valid, but it changes how the condition evaluates.

For example, this rule condition reads one request-scope input:

when: 'dim.tenant exists and dim.tenant = "acme"'

If business dimensions are disabled or the request has no x-tt-tenant header, dim.tenant exists is false. Evaluation stops there and the rule does not match.

How absent values affect conditions

X exists is true only when X has a value. X = false is true only when X is present and false. If X is absent, both X != false and not X are true. Test exists first when absence must behave differently from a present false.

Request scope

Request-scope inputs describe the incoming request and the runtime state available before a tier is chosen. You can read them in a rule’s when, in a predicate body, and inside where and accept_when.

request.* — what the client asked for

These inputs describe the request body after TokenTriage has parsed it.

Input Type What it holds Absent when
request.model string model name from the request the request names no model
request.dialect string parsed request format not expected; the parser normally sets it
request.endpoint string classified API endpoint the endpoint cannot be classified
request.stream bool whether the client requested streaming never; false is present
request.prompt_bytes number combined prompt length in bytes never
request.messages number message count never
request.system_bytes number system-prompt length in bytes never
request.has_tools bool whether the request declares tools never
request.tool_count number declared tool count never
request.tool_result_count number tool-result message count never
request.max_tokens number requested output cap; 0 when unset never
request.reasoning_effort string requested reasoning level the request sets none

Numbers and booleans here are present even at zero or false. Only the string fields go absent, and only when they are empty.

trace.* — the agent and the step

Trace inputs connect a request to an agent, conversation, and step. Each value comes from an x-tt-* header unless the table says otherwise.

Input Type What it holds Absent when
trace.agent string x-tt-agent value the header was not sent
trace.step_class string user_turn, tool_loop, or provider_state never; derived when the header is missing
trace.step_class_source string header or derived never
trace.trace_id string conversation ID the header was not sent
trace.step_id string current step ID the header was not sent
trace.parent_id string parent step ID the header was not sent
trace.w3c string raw traceparent header the header was not sent

dim.* — your business dimensions

Business dimensions are optional labels supplied by your application. They are disabled by default. Set ledger.dimensions.enabled to true before sending their headers; otherwise every dim.* input is absent.

Input Type What it holds Absent when
dim.user string x-tt-user value dimensions are disabled or the header is missing
dim.tenant string x-tt-tenant value dimensions are disabled or the header is missing
dim.feature string x-tt-feature value dimensions are disabled or the header is missing
dim.tag.<key> string matching x-tt-tag-<key> value dimensions are disabled or the header is missing

dim.tag.<key> is a family: any single-segment key is accepted, so dim.tag.env works without being declared anywhere. A key containing a dot is not accepted.

signal.* — the classifier and your scorers

The built-in classifier runs on every request. It always emits these numeric inputs:

Input Type What it holds Absent when
signal.complexity number combined complexity score in [0,1] never
signal.token_est number estimated prompt tokens, calculated as characters divided by four never
signal.code number code-vocabulary score never
signal.reasoning_markers number reasoning-phrase score never
signal.reasoning_marker_count number phrase count behind the reasoning score never
signal.technical number technical-vocabulary score never
signal.simple number simple-phrase score, which lowers complexity never
signal.multi_step number multi-step-structure score never
signal.question number question-density score never
signal.has_tools number 1 when tools are declared; otherwise 0 never
signal.tool_count number declared tool count never
signal.tool_result_count number tool-result message count never
signal.message_count number message count never

Declaring a scorer adds more: semantic emits signal.semantic.<topic> per topic, and est_cost emits signal.est_cost. Both are described in selectors and scorers.

ctx.* — clock and governance state

The clock is always available. Budget, limit, and principal inputs expose governance state to a routing condition. They steer a request but do not decide whether it is admitted.

Input Type Available when Absent when
ctx.time.hour_utc number always never; 0–23, UTC
ctx.time.weekday number always never; 0 is Sunday
ctx.time.unix number always never; request arrival, epoch seconds
ctx.budget.<id>.remaining_frac number that budget is configured budget state is unavailable or suspended
ctx.budget.<id>.remaining_usd number that budget is configured budget state is unavailable or suspended
ctx.budget.<id>.exhausted bool that budget is configured budget state is unavailable or suspended
ctx.limit.<id>.remaining_frac number a global or node-pinned limit with that ID is configured limit state is unavailable or that limit is suspended
ctx.limit.<id>.exhausted bool a global or node-pinned limit with that ID is configured limit state is unavailable or that limit is suspended
ctx.limit.<id>.suspended bool a global or node-pinned limit with that ID is configured limit state is unavailable
ctx.principal.budget_remaining_frac number governance is enabled anonymous or unresolved traffic
ctx.principal.rpm_remaining_frac number governance is enabled anonymous or unresolved traffic; otherwise present as 1
ctx.principal.exhausted bool governance is enabled anonymous or unresolved traffic

Naming an unconfigured budget or limit ID is a validation error, not an absent read. Admission is a separate governance gate that runs in every mode regardless of the routing policy.

Candidate scope

Candidate-scope inputs describe one tier while a select node filters its choices. They are readable only inside select … where.

Identity and eligibility

Use these inputs to identify a candidate and check whether it can serve the request.

Input Type What it holds Absent when
candidate.tier string tier name never
candidate.upstream string configured upstream name never
candidate.model string model assigned to the tier never
candidate.rank number position in the declared tiers: list never
candidate.pool_weight number configured pool weight the candidate set came from a tier list rather than a pool
candidate.healthy bool whether cooldown and request-format checks both pass never

Cost, latency, and rewards

These inputs support cost, performance, and learned-outcome comparisons.

Input Type What it holds Absent when
candidate.price.input_per_tok number catalog input price in dollars per token the catalog cannot price that tier
candidate.price.output_per_tok number catalog output price in dollars per token the catalog cannot price that tier
candidate.latency.p50_ms number median observed latency in milliseconds that upstream has no observations
candidate.latency.p95_ms number 95th-percentile observed latency in milliseconds that upstream has no observations
candidate.reward_mean number graded pass rate in [0,1] that tier has no graded outcomes
candidate.reward_count number number of graded outcomes behind the pass rate that tier has no graded outcomes

Calibrated quality

These six inputs come from the same calibration result. If no result is available for the candidate, all six are absent.

Input Type What it holds Absent when
candidate.quality.lcb number lower confidence bound used for quality floors no calibration result is available
candidate.quality.n number sample count stored with the bound no calibration result is available
candidate.quality.calibrated number 1, marking the value as calibrated no calibration result is available
candidate.quality.confidence number nominal confidence stored with the result no calibration result is available
candidate.quality.method string calibration method that produced the bound no calibration result is available
candidate.quality.estimand string type of guarantee represented by the bound no calibration result is available

Inputs that are not available

Two candidate names pass validation but currently have no value. A positive comparison against either one evaluates to false. The != and not exceptions described earlier still apply.

Input Type What happens Use instead
candidate.provider string always absent candidate.upstream
candidate.max_context number always absent request.prompt_bytes with a threshold you set for each tier

candidate.quality.mean is different: it is not a readable routing input. Using it in a when, where, accept_when, predicate body, or selector dependency produces a validation error. Route on the calibrated lower confidence bound, candidate.quality.lcb, instead.

Outcome scope

Outcome-scope inputs describe one completed cascade attempt. They are readable only inside that stage’s accept_when. The optional judge is a model-based quality check requested by the cascade stage.

Input Type What it holds Absent when
outcome.status number HTTP response status never
outcome.response_bytes number response size in bytes never
outcome.latency_ms number attempt latency in milliseconds never
outcome.output_tokens number measured output-token count usage cannot be measured for that attempt
outcome.finish_reason string provider finish reason the provider sends none or the value is unrecognized
outcome.judge.available bool whether the optional judge returned a result the stage did not set judge: true
outcome.judge.ok bool optional judge’s pass-or-fail result the stage did not set judge: true
outcome.refusal_markers number distinct decline patterns in the first 8 KiB of the response never

What this page does not cover

This page lists values; it does not teach the syntax that reads them or the components that produce them. See the policy language for operators and grammar, selectors and scorers for the producers and consumers of routing inputs, and ledger fields for the recorded result.