Help us shape the future of Go! Take the 2026 Go Developer Survey

routes

package
v0.1.1450 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 24, 2026 License: MIT Imports: 6 Imported by: 0

Documentation

Overview

Package routes is the gateway's router strategy point (P3 of docs/subsystems/plugin-runtime.md): how one pool of routes — a provider's accounts or keys, or the heads of a group's members — is ordered for a request. The gateway lists the pool, rests, affinity and failover itself; a Strategy only says the order.

Smart, the default, is the core's. The others a provider or group can be routed by (order, rotate, usage, pace, weight) are here for a plugin to register: plugins/rotation does, and off, a pool routed by one of them is tried in its listed order, as an unknown routing always was.

Index

Constants

View Source
const (
	LowShare  = 90
	UsedShare = provider.SpentShare
)

Shares of an allowance past which an account is kept for when the others can't take a request: low, and all but used up — where the agent signed in to it is signed in to another, too (provider.SpentShare).

Variables

Builtin are the routings besides Smart and their strategies, as plugins/rotation registers them.

View Source
var FreeFirst = Strategy{ByFree: true, Order: func(_ string, seats []Seat) []int {
	free, paid := make([]int, 0, len(seats)), make([]int, 0, len(seats))
	for i, s := range seats {
		if s.Free {
			free = append(free, i)
		} else {
			paid = append(paid, i)
		}
	}
	return append(free, paid...)
}}

FreeFirst spends what costs nothing before what is paid for: the seats whose model its vendor serves at no cost to the subscription's allowance or credits go first, in their listed order, and the rest after them, also in their listed order. So a request whose model is free somewhere on the pool uses that first, and only when those can't answer does it go to one that spends credits or quota — nothing is left unused that cost nothing.

It orders one tier only, unlike Smart: a free seat is not pushed behind a paid one for running low, since the free one spends nothing by running out of. Among the paid, the listed order stands rather than Smart's renewal-weighted one, because what this routing is for is the free tier; set a provider's own routing (Smart, in order, weekly pace) if the paid seats should be spent smartly among themselves — a provider or group in this group keeps its own routing either way.

A seat whose model costs nothing nowhere is not free: it goes with the paid ones, so a routing set on a pool without free models behaves as In order.

View Source
var LeastUsed = Strategy{ByUse: true, Order: func(_ string, seats []Seat) []int {
	idx := identity(len(seats))
	sort.SliceStable(idx, func(a, b int) bool {
		sa, sb := seats[idx[a]], seats[idx[b]]
		if ta, tb := tier(sa.Used), tier(sb.Used); ta == 2 && tb == 2 && sa.Credits != sb.Credits {
			return sa.Credits
		}
		if sa.Used != sb.Used {
			return sa.Used < sb.Used
		}
		return sa.Tokens < sb.Tokens
	})
	return idx
}}

LeastUsed puts a subscription, or a key that reads its own windows, by the share of its allowance used, as the vendor says; then, and for other keys, by what magpie sent it lately. Of those all but used up, one that goes on on its credits goes first (spentBefore).

View Source
var Ordered = Strategy{Exact: true, Order: func(_ string, seats []Seat) []int { return identity(len(seats)) }}

Ordered tries a pool in its order: the first until it can't answer.

View Source
var Pace = Strategy{ByUse: true, Order: func(_ string, seats []Seat) []int {
	// the bands of pace alike within a tenth, the highest first: drawn
	// before sorting, from each band's highest down, so that sorting by
	// them is consistent — "within a tenth of each other" alone is not,
	// three paces a twelfth apart each going round in a circle. Drawn among
	// those the pace orders alone — one running low would otherwise set a
	// band's top and part two alike behind it
	var byPace []int
	for i, s := range seats {
		if tier(s.Used) == 0 && !s.Learns {
			byPace = append(byPace, i)
		}
	}
	sort.SliceStable(byPace, func(a, b int) bool { return seats[byPace[a]].Pace > seats[byPace[b]].Pace })
	band, top := make([]int, len(seats)), 0.0
	for k, i := range byPace {
		if p := seats[i].Pace; k == 0 || p < 0.9*top {
			top = p
			if k > 0 {
				band[i] = band[byPace[k-1]] + 1
				continue
			}
		} else {
			band[i] = band[byPace[k-1]]
		}
	}
	idx := identity(len(seats))
	sort.SliceStable(idx, func(a, b int) bool {
		sa, sb := seats[idx[a]], seats[idx[b]]
		if ta, tb := tier(sa.Used), tier(sb.Used); ta != tb {
			return ta < tb
		} else if ta == 2 && sa.Credits != sb.Credits {
			return sa.Credits
		} else if ta > 0 {
			if sa.Used != sb.Used {
				return sa.Used < sb.Used
			}
		} else if sa.Learns != sb.Learns {
			return sa.Learns
		} else if band[idx[a]] != band[idx[b]] {
			return band[idx[a]] < band[idx[b]]
		}
		return sa.Tokens < sb.Tokens
	})
	return idx
}}

Pace puts a subscription by what it has left of its week per hour until that renews, the most first: it has the most to lose at the reset, where the most left alone (least used) sends a fresh account ahead of one with 80% left and an hour to go, and the soonest reset alone (smart) sends one with 3% left and an hour to go ahead of one with 90% and two. The tiers are Smart's: one at 90% or more of any window that counts the model — the five hours too, a rate cap it would be sent into before its allowance was read again — waits until the others can't answer, one all but used up until nothing else can; each tier by the share used, one all but used up that goes on on its credits first (spentBefore). Among the rest, one not known that tells what it has left as it answers goes first while it isn't, as in Smart, else it would never be known; then the pace, those alike within a tenth by what magpie sent them lately, then in their order, keeping the vendor's prompt cache warm. An account not known counts as a whole week ahead of it; a key has no week, unless it reads its own windows (a sub2api key's limits). A Codex account that spends a reset about to run out by itself goes by the hours until then when that is sooner (Allowance.Pace, #717).

View Source
var Rotate = Strategy{Order: func(pool string, seats []Seat) []int {
	turns.Lock()
	n := turns.m[pool] % len(seats)
	turns.m[pool]++
	turns.Unlock()
	return append(identity(len(seats))[n:], identity(n)...)
}}

Rotate starts each request one further along the pool.

View Source
var Smart = Strategy{Order: func(_ string, seats []Seat) []int {
	var fine, low, spent []int
	for i, s := range seats {
		switch tier(s.Used) {
		case 2:
			spent = append(spent, i)
		case 1:
			low = append(low, i)
		default:
			fine = append(fine, i)
		}
	}
	sort.SliceStable(fine, func(i, j int) bool {
		a, b := seats[fine[i]], seats[fine[j]]
		if a.Learns != b.Learns {
			return a.Learns
		}
		ri, rj := a.Soon, b.Soon
		for k := 0; k < len(ri) || k < len(rj); k++ {
			var x, y time.Time // to the hour, so a few minutes don't reorder
			if k < len(ri) {
				x = ri[k].Truncate(time.Hour)
			}
			if k < len(rj) {
				y = rj[k].Truncate(time.Hour)
			}
			switch {
			case x.Equal(y):
				continue
			case x.IsZero() || y.IsZero():
				return y.IsZero()
			}
			return x.Before(y)
		}
		return false
	})
	sort.SliceStable(low, func(i, j int) bool { return seats[low[i]].Used < seats[low[j]].Used })
	sort.SliceStable(spent, func(i, j int) bool { return spentBefore(seats[spent[i]], seats[spent[j]]) })
	return append(append(fine, low...), spent...)
}}

Smart is the default: of those with quota to spare, the one whose allowance renews soonest, since what it has left is lost then, while one renewing later keeps: the biggest window decides — the week, not the five hours in it — and the next one only when that renews in the same hour. An account with no week (Claude Enterprise's five hours alone) goes by its five hours, so ahead of every week but one renewing sooner; a window not started renews its whole span from now (#576). A Codex account that spends a reset about to run out by itself (auto-use on, its windows used) renews when that is spent, if sooner: what it has left is lost there just the same (#718). Only holding one, or one that runs out after the week renews, changes nothing. Those alike stay in their order, keeping the vendor's prompt cache warm. Past that, whichever has the most left, and one all but used up only when nothing else can take it, one that goes on on its credits first of those (spentBefore). Only the windows that count the model do: Opus's own weekly allowance being used up leaves Sonnet alone.

One not known that tells what it has left as it answers goes first, once: else, kept after those known, it would never answer and never be known (Anthropic's usage endpoint can turn magpie away for hours).

View Source
var Weighted = Strategy{Order: func(pool string, seats []Seat) []int {
	keys, weights, rests := make([]string, len(seats)), make([]int, len(seats)), make([]bool, len(seats))
	for i, s := range seats {
		keys[i], weights[i], rests[i] = s.Key, max(s.Weight, 1), s.Resting
	}
	return First(Round(pool, keys, weights, rests), len(seats))
}}

Weighted spreads a pool's requests over its seats by each one's weight, smoothly (Round): the one the pool's round picks now first, the others after it in their order, for failover.

Functions

func Apply

func Apply(s Strategy, pool string, seats []Seat) (order []int)

Apply is the order s gives seats, checked: an order that isn't a permutation of them, or a strategy that panics, leaves them in their listed order, logged. A strategy only orders, so failing it fails open: every route is still tried.

func First

func First(best, n int) []int

First is the order of n with best first and the others after it in their order; best below 1 leaves it as it is.

func Forget

func Forget(pools ...string)

Forget drops what pool's round and turn hold, every pool's with none named: a test starts each anew.

func MoveRound

func MoveRound(pool, picked, kept string)

MoveRound charges pool's last pick to kept rather than picked, when affinity kept a conversation with kept: the round counts the requests each really took, so new conversations make up the share.

func Register

func Register(routing string, s Strategy) (remove func())

Register serves routing by s until remove is called. Registering one already served stacks: the last serves until it is removed.

func Round

func Round(pool string, keys []string, weights []int, rests []bool) int

Round is which of keys pool's round picks now, each weighing its weight, those resting left out of the round; -1 when every one rests. The pick is charged the round's total.

func Served

func Served(routing string) bool

Served says whether routing is served now.

Types

type Seat

type Seat struct {
	// Key is who it is: the account or key's rest key, the same for the
	// same route from request to request
	Key string
	// Account is a subscription's account, not an API key
	Account bool
	// Known: its allowance is known. Used, Soon and Pace are read from it
	Known bool
	// Learns: an account not known yet, of an agent that tells what it has
	// left as it answers (Claude Code's rate_limit_event)
	Learns bool
	// Used is the share of its allowance used that counts the model, 0–100
	Used float64
	// Soon is when its windows renew, the biggest first (Allowance.Renewal)
	Soon []time.Time
	// Pace is its week left per hour until that renews; an account not
	// known has a whole week (provider.FreshPace), a key none
	Pace float64
	// Weight is its key's weight, at least 1
	Weight int
	// Resting: it rests after a failure now
	Resting bool
	// Tokens is what magpie sent it lately, decayed by an hour's half-life
	Tokens float64
	// Free: the model this seat would be asked for costs nothing on it —
	// its vendor serves that model at no cost to the subscription's
	// allowance or credits (catalog.Model's Free). FreeFirst goes by it.
	Free bool
	// Credits: a Codex account that goes on past its allowance on its
	// credits, and is let spend them. All but used up, it goes before
	// those used up with nothing to go on with.
	Credits bool
}

Seat is one route of a pool, as a strategy weighs it.

type Strategy

type Strategy struct {
	// Order is the order to try seats in: a permutation of their indexes.
	// pool names the pool (a provider's id, or "group/<id>"), for a
	// strategy that keeps a round per pool. It is asked with two seats or
	// more.
	Order func(pool string, seats []Seat) []int
	// Exact: its order stands as it says. Otherwise, of a provider's API
	// keys, those that fit the request better go first, each kept in its
	// order among those alike.
	Exact bool
	// ByUse: it weighs by Tokens, which the routing trace then shows
	ByUse bool
	// ByFree: it weighs by Free, so the gateway works out which seat's
	// model costs nothing there, and the routing trace then shows it.
	// Only when set: working it out reads each provider's model list.
	ByFree bool
}

Strategy orders a pool.

func Find

func Find(routing string) (Strategy, bool)

Find is the strategy serving routing: Smart for "", else the last one registered; ok is false when none is.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL