The router doesn't guess which vendor should handle a request — it scores the message and compares the score against two configured thresholds. This guide is the actual scoring logic, not a plain-language gloss of it (for that, see the beginner post).
The scoring function
classifyComplexity(message) starts at 0 and adds or subtracts fixed weights based on
regex matches against the message text:
| Signal | Effect on score |
|---|---|
| Write-intent language ("create," "update," "delete," and similar) | +0.4 |
| Cross-project language (spans more than one project) | +0.3 |
| Code-related language | +0.5 |
| Reasoning-indicating language ("why," "compare," "explain," and similar) | +0.3 |
| Multi-step language ("first... then," numbered steps) | +0.2 |
| Simple-read language ("what is," "show me," a single lookup) | −0.3 |
| Single-project language | −0.2 |
The result is clamped to the 0–1 range. A message can trip several signals at once — "update this task, then tell me why the deadline moved" would add both the write-intent and reasoning weights.
The two thresholds
Two numbers, both configured per org in the Admin Panel (defaults: 0.6 low, 0.7 high):
- Score below the low threshold → routed to the simple vendor/model.
- Score at or above the low threshold, below the high threshold → routed to the primary vendor/model.
- Score at or above the high threshold → routed to primary with the tool-heavy path enabled.
Raising the low threshold sends more traffic to the cheap simple tier — good for cost, risky if genuinely complex requests start getting scored just under the line. Lowering the high threshold makes the tool-heavy path trigger more often, which costs more per request but reduces the chance a request that needed tools didn't get them.
Reading a routing decision from a real response
Every response from the router carries provider, model, and routingTier fields.
If you're debugging "why did this go to the wrong vendor," this is the ground truth —
not what you expected the threshold math to produce, but what the router actually
recorded. A response tagged routingTier: "simple" when you expected "primary"
usually means the message scored lower than you assumed; work backward through the
signal table above rather than assuming the router misbehaved.
Worked example
A message like "What's the status of the login bug?" — a simple read, single project —
scores roughly −0.3 + −0.2 = 0 after clamping to a minimum of 0. That's well under a
0.6 low threshold, so it routes to the simple vendor.
A message like "Update every overdue item across all my projects, then explain your
reasoning for each change" scores roughly 0.4 (write) + 0.3 (cross-project) + 0.3 (reasoning) + 0.2 (multi-step) = 1.2, clamped to 1.0. That's above any realistic high
threshold, so it routes to primary with tools enabled.
Tuning from real traffic, not guesses
Don't set thresholds from intuition alone. If you have request logs, get a rough sense of how often each tier fires before touching the defaults — a simple-tier vendor that's firing on requests that clearly needed real reasoning is a threshold problem, not a vendor-choice problem, and re-picking the simple vendor won't fix it. Adjust the low threshold first; only touch the high threshold once you're confident about where tool-heavy requests should trigger, since that's the more expensive path to get wrong in either direction.