Observability & tracing
The x-tt-* header contract, traces and agents, the Live feed, business dimensions, and OTel export — attribution comes only from explicit identity on the wire, never a guess.
On this page
TokenTriage attributes cost by trace identity, and identity comes exclusively from
explicit x-tt-* request headers. There is no heuristic stitching — traceparent is logged
for correlation but is never used to infer identity, because a wrong ledger is worse than a
narrow one.
The header contract
| Header | What it does |
|---|---|
x-tt-trace-id |
Groups requests into one agent trace. The one header that turns rows into a trace. |
x-tt-step-id |
Identifies the step within the trace — enables the per-step cost tree (top --trace ID). |
x-tt-parent-id |
The parent step id — draws the sub-agent fan-out edges. |
x-tt-agent |
Logical agent / sub-agent name — drives top --by agent. |
x-tt-step-class |
Explicit step class: user_turn | tool_loop | provider_state. When absent, the proxy derives a guess and records step_class_source: derived — the guess feeds routing rules but never trace identity. |
x-tt-tier |
Explicit tier override by name; always honored in route mode, logged in all modes. |
traceparent |
W3C context, logged as trace.w3c — never used to stitch identity. |
All x-tt-* headers are stripped before the request is forwarded upstream (the provider
never sees them); response rows echo the resolved identity into the decision log. The
complete recipe is one line:
x-tt-trace-idper run,x-tt-agentper role,x-tt-step-idper call,x-tt-parent-idon delegation.
Setting the headers
Any framework where you control the HTTP client sets these on its outbound requests (set
base_url to the proxy and pass the headers). The Python and
TypeScript SDKs mint and propagate them for you across async
boundaries. For Claude Code, tokentriage wrap injects a static, per-session
x-tt-trace-id (plus x-tt-agent):
Business dimensions
With ledger.dimensions.enabled, four business dims come from headers — x-tt-user,
x-tt-tenant, x-tt-feature, and bounded x-tt-tag-<k> (key ≤ 32 bytes, value ≤ 128
bytes, up to max_tags, default 8). They land in dims on the row, are stripped before
upstream, and spend with no value renders as a first-class “unattributed” slice, never
silently dropped. Promotion to Prometheus labels is opt-in and cardinality-guarded via
ledger.dimensions.promote_to_metrics.
The Live feed
The dashboard’s Live surface is an off-path SSE tail of costed decision-log rows as they arrive — with a byte-fidelity indicator and a running session cost ticker. Being off-path, it can never backpressure your traffic.


OTel export
TokenTriage ships an OTLP exporter today (export.otlp.enabled, one span per ledger row)
that emits cost under two namespaces on every priced row. Its own extension —
tokentriage.cost.usd (omitted when unpriced), tokentriage.cost.unpriced,
tokentriage.cost.pricing_snapshot, tokentriage.cost.pricing_key,
tokentriage.cost.estimated, and tokentriage.cost.would_usd.<tier> — and, alongside it,
the proposed GenAI convention gen_ai.cost.amount, gen_ai.cost.currency,
gen_ai.cost.pricing_source, and gen_ai.cost.estimated, carrying the identical dollar. The
endpoint and headers are references (export.otlp.endpoint, headers_env).
Related: SDKs (which automate the header contract) · The trust dashboard.