0005 — Smooth weighted round robin, not modulo rotation¶
Status: accepted · Date: 2026-08-14
Context¶
Every phase rotates across engines. The set of eligible engines changes continuously as circuits open (credits exhausted, rate-limit window) and close (window reset, top-up). Engines are not equal: some are faster, cheaper, or capable of higher effort tiers.
Decision¶
nginx's Smooth Weighted Round Robin, over the eligible set, with effective weights derived from health, fidelity, cost, and affinity.
for each selection:
for e in eligible: e.current += e.effective_weight
winner = argmax(e.current) # ties broken by a stable `order`
winner.current -= sum(e.effective_weight for e in eligible)
Rationale¶
engines[i % len(engines)] fails three ways here:
- The eligible set resizes. Modulo over a shrinking list remaps every index, so the cursor jumps arbitrarily when a circuit opens — the opposite of fair.
- It ignores weight. A developer's stated preference has nowhere to live.
- Naive weighted RR clumps. Weights
{A:3, B:2, C:1}produceAAABBC— three consecutive items to A, which is exactly the burst that exhausts A's rate-limit window while B and C idle. SWRR producesABACAB.
Point 3 is not cosmetic. Vibey's whole reason for rotating is to spread load across independent capacity pools; a scheduler that bursts defeats the purpose.
Effective weight¶
effective = base × health × fidelity × cost × affinity
| Factor | Range | Meaning |
|---|---|---|
| health | 0.0–1.0 | 1.0 closed, 0.25 half-open, 0.0 open; decays on recent transient failures |
| fidelity | 0.5–1.0 | penalizes an engine that saturates below the requested effort |
| cost | 0.5–1.5 | relative $/Mtok, inverted; off by default |
| affinity | 1.0 or 2.0 | 2.0 when the engine holds a warm session and rotation is not forced |
affinity is what implements stickiness: ordinary retries stay put, and rotation
happens when something actually changed (ADR-0007).
Consequences¶
Good. Deterministic, so testable. Six properties are enforced by Hypothesis: no starvation, weight fidelity, smoothness, determinism, totality, exclusion honored. Rotation state is a single integer per engine, updated in the same transaction as the job lease, so a crash cannot advance the cursor without doing the work.
Bad. More state than modulo, and the four factors are tuning knobs that can be
set badly. Mitigated by exporting vibey_engine_selected_total so the empirical
distribution is checkable against the intended weights in production, not just in
unit tests.
Alternatives rejected¶
- Modulo. Above.
- Random / weighted random. Non-deterministic, so untestable by property, and it clumps by chance.
- Least-loaded. Requires a load signal vibey does not have — an engine's remaining quota is not observable until it is rejected.
- Cost-optimal routing. Would concentrate work on the cheapest engine until it runs dry, converting a fairness problem into a capacity problem. Cost is a factor here, deliberately not the objective.