Skip to main content
kRouter
All posts
How kRouter works

The Zenith routing engine: how kRouter picks an account

Zenith scores every account on success rate, latency and remaining quota before each request. The exact formula, what it fixes and what it does not.

Kodelyth · The team behind kRouter
· Updated
5 min read

If you route AI traffic across several accounts on the same provider, something has to decide which account takes the next request. Before v0.5.75, kRouter's answer was mostly "the first one that still works": it used an account until it returned a 429, then moved to the next. That order ignores everything kRouter already knows about the accounts -- which ones have been failing, which are slow, which are nearly out of quota.

Zenith is the scoring step that replaced it. This post describes the formula as the code implements it.

What changed in this post

The first version of this post gave a weighted TTFB-and-quota formula, a squared penalty below 30% quota, an example table, and promised you would never hit a 429. None of that matched the code. The formula below does, and it does not prevent 429s; it makes them less likely and keeps them from cascading.

The score

Before each request, kRouter collects the accounts for the provider that are not locked for the requested model, then scores each one.

1. Health, from 0 to about 1,000. kRouter keeps an in-memory record per account of recent outcomes and request time:

health = success_rate × 1000 − (average_request_ms ÷ 100)

The average is an exponentially weighted moving average of total request time (each new request counts for 30%). Success and failure counts decay with a five-minute half-life, so an account that had a bad patch recovers on its own. An account kRouter has never used starts from a neutral record -- five successes and 3,000 ms -- so new accounts get tried rather than starved.

2. The quota penalty. If kRouter tracks quota for the provider and the account has less than 30% left for that model, the score is multiplied by the remaining percentage divided by 30, with a floor of 0.1:

if remaining < 30%:  score × max(0.1, remaining ÷ 30)

An account at 15% keeps half its score; one at 3% or less keeps a tenth. An account whose provider reports no quota is not penalised at all. (Before v0.5.164 it was scored as if it had none left, which cut every API-key account to a tenth of its score.)

3. List position. The account at the top of your list gets 40 points, the second 30, the third 20 and the fourth 10; the rest get none. On a score of about a thousand, that only settles near-ties. (Before v0.5.164 the bonus ran the other way, 10 points per position down the list, so together with the quota bug above the bottom account often won.)

The highest score wins.

For chat there is one exception. Once an account has answered a conversation, kRouter sends that conversation's next turns back to the same account until it has been idle for 15 minutes, so the provider's prompt cache stays warm. If that account fails, the retry goes through normal Zenith selection. Round robin switches this off, because spreading traffic is the point of choosing it.

A worked example

Two hypothetical accounts on the same provider, with numbers chosen to show the arithmetic:

Account AAccount B
Success rate100%90%
Average request time2,000 ms1,000 ms
Health1000 − 20 = 980900 − 10 = 890
Quota left15%60%
After quota penalty980 × 0.5 = 490890

B gets the request. Without the quota term, A would have won and been driven into its limit.

Notice how little latency moves the score: every 100 ms costs one point out of a thousand, so even a two-second difference is worth 20 points. Success rate and remaining quota do the real work.

Where the quota number comes from

The penalty needs a quota figure, and kRouter only has one for providers whose usage it can read: Claude Code, Antigravity, Kiro, Qoder, GitHub Copilot, Codex, Kimi Coding, Ollama, Gemini CLI, GLM, MiniMax and Vercel AI Gateway (plus the China-region GLM and MiniMax variants). You can see the same figures on the dashboard's Quota Tracker page.

What it does not do

It does not prevent 429s. A per-minute limit can trip on an account with plenty of daily quota left. When that happens, kRouter cools the account down for that model -- starting at 2 seconds and backing off up to 5 minutes, or, when the provider reports a real quota reset time, until then (capped at six hours) -- and retries the request on the next account.

It does not choose between models. Zenith picks an account for one provider. Choosing between models or providers is the job of a combo, which has its own strategies: fallback, round-robin and fusion.

It does not need the database on the hot path. The account list and its locks are kept in memory and re-read from SQLite every 10 seconds, so moving to the next account after a failure does not wait on a disk write. The RAM layer post covers that part.

Turning it on

You already have it. The account strategy lives in Settings under Routing Strategy. In v0.5.163 its default is still labelled Fill First, but since v0.5.77 that setting runs Zenith. (v0.5.75 added Zenith; v0.5.77 wired it into the default path.) The same selection applies to chat, embeddings, image, video, speech, transcription, search and fetch requests.

The other choices are:

  • Round Robin, which rotates accounts and stays on each one for 3 calls by default. Antigravity uses it by default, because its per-minute limits punish sending a burst to one account.
  • P2C (power of two choices), which picks two accounts at random and keeps the one with the higher Zenith score.
  • Random.
npm install -g @sifxprime/krouter@latest
krouter -v   # 0.5.77 or newer

Common questions

Does Zenith stop me hitting rate limits?

No. It makes the router prefer healthy accounts with quota left, which makes 429s rarer and keeps them from cascading across every account. When one happens, kRouter cools that account down and retries on another.

Why does the setting say Fill First if it runs Zenith?

The label predates Zenith. Since v0.5.77 the fill-first setting runs Zenith scoring, and as of v0.5.163 the label had not caught up.

Does latency matter much in the score?

Less than you might expect. Every 100 ms of average request time costs one point out of roughly a thousand, so success rate and remaining quota decide most choices.

Do I need to configure anything?

No. Zenith is the default. Moving an account higher in the list nudges it when scores are close, and you can switch to round robin, P2C or random in Settings.

Kodelyth · The team behind kRouter

Published by Kodelyth, the team that builds kRouter. Posts are drafted with AI assistance and reviewed by a person before they go out. kRouter is free and MIT licensed.

Install kRouter