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 explanation fields

Look up every line emitted by policy explain, including selector scores, filtered candidates, random draws, overrides, and cascade attempts.

ReferenceLast updated

On this page

Every policy-language decision stores an explanation on its decision-ledger row. tokentriage policy explain renders that block as terminal text. This page defines the fields in the order they appear.

For the diagnostic workflow and prerequisite request id, use Explain a routing decision.

Summary fields

The first lines identify the row, its result, and the policy walk that produced it.

Field Meaning
req_id request id used to find the ledger row
policy_rev short fingerprint of the policy revision that decided this row
decision rule, tier, upstream, model rewrite, policy refusal, and fail-safe markers that apply to the result
path policy nodes visited in order, ending at the node that produced the result
trace one entry for each node that recorded an event during the walk
read values that were present and consumed by the node’s condition, in first-read order

policy_rev may be absent on a row written by an older or non-policy router. When it is present, compare it with the revision printed in the first comment from tokentriage policy migrate --config <path> before debugging against a config file.

The decision line reflects the mode that wrote the row. A matched ledger rule can show tier=— because ledger mode records the policy evaluation without applying a routing target.

If the row has no explanation block, the renderer prints (no explanation block — a v1/foreign policy decided this row) and stops. No path or trace follows because the row did not store them.

Rule traces and absent inputs

Rule entries use matched when their condition selected the rule and skipped when it did not. why=when=false means the condition evaluated to false.

Conditions stop as soon as their result is known. Later terms in an and expression are not read after an earlier term is false; later terms in an or expression are not read after an earlier term is true. Only the values consulted before that point appear under read.

An input with no value also makes its comparison false, but rule read lines contain present values only. The missing input is therefore absent from read. This differs from a selector’s filtered reason, which can name an absent candidate fact because it explains why that candidate was removed.

Select nodes

A select node separates candidates removed before scoring from candidates that reached the selector.

$ tokentriage policy explain --req-id tt_select --log ~/.tokentriage/decisions.jsonl
req_id:     tt_select
policy_rev: 8067f04
decision:   rule=default tier=mid upstream=openai
path:       root → select[tiers]
trace:
  • select[tiers]                selected
      selector=cheapest_meeting
      score  mid        0.0032  {input_cost=0.0009, output_cost=0.00225}
      score  frontier   0.0189  {input_cost=0.0054, output_cost=0.0135}
      filtered cheap      (candidate.latency.p95_ms=3400)

Read the select-specific lines as follows.

Line Meaning
selector selector used to rank the candidates that passed where
score candidate that reached the selector, its final score, and the named terms behind that score
filtered candidate removed by where, followed by the input or predicate that rejected it
ctx_ver version string for the runtime-state providers consulted by this node

In the sample, cheap was removed before scoring because its p95 latency was 3,400 ms. mid and frontier reached the selector, and mid won because its estimated input and output costs produced the lower total score.

filtered reasons preserve the condition’s read order. The final part is the predicate that rejected the candidate. A condition that finished early does not list later predicates.

ctx_ver appears when the selector depends on runtime state or its where clause reads calibrated quality. It can contain several provider versions joined with semicolons. Two rows with the same ctx_ver saw the same versions of those providers; rows with different values may have made their decisions from different state.

The complete score-term vocabulary is in Selectors and scorers. Two common terms are:

Term Meaning
unpriced the price catalog could not price the candidate
excluded a configured floor or required input removed the candidate from consideration

Random draws

Traffic splits and selectors that explore between candidates record each random draw.

path:       root → split → arm:canary
trace:
  • split                        split (arm:canary)
  roll split#1 scope=trace key=51bcc7b value=0.462589 → arm:canary

The draw line has five parts.

Part Meaning
split#1 stable identity of the node that made the draw
scope whether the draw used the trace id or request id as its key
key short hash of the key; the original id is not printed
value deterministic value produced from the policy seed, scope key, and node identity
outcome selected split arm or exploration result

scope=request under a policy configured with sticky: trace means the request had no trace id, so the draw fell back to request scope. The recorded inputs and the seed in the config are enough to recompute the assignment.

Overrides and fail-safes

Lines after the node trace record changes or terminal conditions that happened around the policy walk.

Line Meaning
overridden by=x-tt-tier A→B the request header replaced the policy’s tier after evaluation
overridden by=affinity_pin A→B cache-affinity state replaced the policy’s tier after evaluation
fail_safe_why reason the decision used its disclosed fail-safe result
REFUSED=<reason> policy produced a typed refusal instead of a route
FAIL_SAFE decision line marker showing that a fail-safe result was used

An override can suppress a staged plan. The JSON row keeps that fact even when the terminal text only needs to show the source and tier change.

Cascade attempts

A cascade records every provider attempt, including stages whose response was not accepted.

path:       root → rules[tools-cascade] → cascade
trace:
  • rules[tools-cascade]         matched
      read request.has_tools = true
  • cascade[try-cheap]           stage (to:cheap)
  • cascade[final]               stage (to:frontier)
cascade:    attempts=2 final=frontier
  stage try-cheap    tier=cheap model=gpt-4o-mini accepted=false cost=$0.000381
  stage final        tier=frontier model=gpt-4o accepted=true cost=$0.011902

The cascade summary and stage lines have these meanings.

Field Meaning
attempts number of provider calls made for this request
final tier whose response was ultimately returned
stage configured stage id
tier / model target used for that provider attempt
accepted whether this stage supplied the returned response
cost cost recorded for this individual attempt

In the sample, the request made two billable provider calls. The accepted stage’s cost is the cost on the main row. The rejected stage cost is additional cascade spend and is not included in that top-level row cost.

Storage and other surfaces

The explanation is stored inline on the decision-ledger row. These surfaces read that stored block, although they format it differently:

  • tokentriage policy explain --req-id <id>
  • the dashboard request drawer on the admin plane
  • GET /tt/api/v1/requests/{req_id}

There is no separate explain endpoint. POST /tt/api/v1/replay serves a different purpose: it re-evaluates a log under a draft policy, like policy preview.