Skip to content

feat(routes): round-robin weights as priority tiers — rotate within a tier, fall through when it is exhausted #1122

Closed

Description

@dezren39

Ruling

Drew (2026-10-31 ~20:19 CDT), on route pool selection:

allow round robin to accept weight but instead of all of them even or all of them by weight, evenly go through each priority so lowest weight even until all fail then second lowest evenly and so on

Semantics to implement

In a round-robin pool, weights stop being proportional shares and become priority tiers:

  • Targets with equal effective weight form one tier. Within the active tier, selection rotates evenly across dispatches (the existing per-session draw + stickiness, restricted to the tier).
  • Selection falls through to the next tier only when every member of the current tier has failed or is unavailable for that dispatch's candidate walk (not ready, already tried, gate-closed, cooling, quota-gated — the existing eligibility rules decide membership as today).
  • All-equal weights (or no weights; unset weight still defaults to 1) collapse to a single tier, which is exactly today's plain even round-robin. Unweighted pools do not change behavior.
  • ordered mode is untouched.

Tier order: higher weight = earlier tier (interpretation, flagged)

Drew's wording says "lowest weight" first, but the fleet's pools already use weight as preference, larger = preferred:

  • the GPT pools put alternate at weight 2 over main at 1 precisely so alternate burns first;
  • luna-plan puts the Go leaves at 3/2 above the rest;
  • the DeepSeek plan pool uses a weight-100 guard to keep plan doors ahead of PAYG.

A literal lowest-first reading would invert every one of those pools. This issue therefore implements descending order (highest weight is the first tier). If Drew reverses the call, the change is the tier sort comparator and nothing else.

The tier key is the target's effective pre-demotion weight: configured pool weight after scheduled rules, user preferences, and learned adjustments — the same layers tracedWeightOf already computes, stopping before the demotion layer.

Interaction with quota demotion

Demotion today scales the effective weight by 0.01, which under tiering would smear a demoted heavyweight into the middle tiers (100 x 0.01 = 1 ties it with nominal 1s). Rule instead:

  • While demoted (>= quota.demoteAfter quota errors inside quota.windowMs, per target or per serving credential as today), a leaf sits in a demoted tier below every non-demoted tier, whatever its configured weight. Demoted leaves still order among themselves by weight, and they still serve as last resort when nothing else is left.
  • A nested group counts as demoted only when every currently eligible leaf under it is demoted; a mixed group stays in its nominal tier and its own walk applies the same rule one level down.
  • The 0.01 factor itself is unchanged for ordered and weighted modes, whose ranking keeps using the final effective weight exactly as today.

weighted mode: left as-is

weighted remains the proportional mode (random draw by ratio) for pools that genuinely want shares. The ruling redefines round-robin only; removing the only proportional mode is not part of it.

Acceptance criteria

Tests in the model-route suite cover at least:

  1. Single-tier pools rotate evenly, as today.
  2. Two-tier strict fall-through: the second tier is never drawn while any first-tier member is ready; it engages once every first-tier member has failed / left the candidate set, within a single dispatch walk and across dispatches.
  3. Within-tier rotation persists across dispatches (successive sessions cycle through the tier, not the pool).
  4. An unset weight lands in the same tier as weight 1.
  5. A quota-demoted leaf sorts below all non-demoted tiers regardless of nominal weight, and serves again as last resort.
  6. A nested route reference participates as a tier member with its group weight.

No config changes; selection logic and its tests only.

Activity

  1. dezren39 commented on Nov 1, 2026

    @dezren39
    Author

    Feeder triage evidence (issue-sweep-rotating, enqueue-only — this run does not fix):

    Checked on current main c4ad56aaea3542e561e481918f59091b38f2134b (gh api):

    • packages/core/src/model-route.ts:39 — selection modes are ordered / round-robin / weighted.
    • selectSessionTarget (:745) — the round-robin branch (:757ff) is smooth weighted round robin: scores accumulate each candidate's weight and the highest score wins, i.e. weights act as proportional ratios today. That is exactly the behavior this ruling replaces — there is no tier grouping, no within-tier even rotation, and no strict fall-through anywhere in selection.
    • tracedWeightOf (:1197-1223) — layers are base → scheduled → prefer → adjustment → demotion, where demotion is weight * DEMOTED_FACTOR with DEMOTED_FACTOR = 0.01 (:1132). The issue's tier key (effective pre-demotion weight) is the trace's adjustment layer, which already exists and is recorded per selection (feat(core): per-layer weight attribution in the route decision log (design #587, phase 2) #589) — the tiering change can key off it without new weight plumbing. The demoted-tier rule (below all non-demoted tiers, regardless of nominal weight) is not representable in the current 0.01 scaling, as the issue notes (100 × 0.01 = 1 ties with nominal 1s).
    • packages/schema/src/config/model-routes.ts:285-287 — the config annotation describes round-robin as "rotates per new session" and does not mention weights; no config-shape change is needed for tiers.

    Verdict: the gap is present on current main; round-robin weights are proportional shares today, not priority tiers.

    What "done" looks like (for the triage lane): confirm this diagnosis, pin the tier semantics against the dispatch candidate walk (tier key = pre-demotion traced weight; demoted tier last; nested group demoted only when every eligible leaf is demoted; session stickiness preserved within tier), and promote to a fix packet covering the issue's six acceptance tests. ordered and weighted stay untouched. The descending-order interpretation is flagged in the issue itself — surfacing it in the triage note suffices; it is a one-comparator change if Drew reverses it.

    Enqueueing a triage packet from this evidence (task id in the follow-up agent note).

  2. dezren39 commented on Nov 1, 2026

    @dezren39
    Author

    Queued by the triage feeder (issue-sweep-rotating): triage packet enqueued as task 4820 in queue repo-health-opencode-pilot (dedupe repo-health:v1:triage:developing-today/opencode:issue:1122). Evidence comment: #1122 (comment)

  3. removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 1, 2026
  4. dezren39 commented on Nov 1, 2026

    @dezren39
    Author

    agent-note:v1
    state: done
    Agent: repo-worker-opencode (openchamber scheduled lane)
    Session/ID: task 4820
    Outcome: Already done on pickup: issue closed; PR #1124 merged as 6aae7cc. No competing implementation or follow-up task created.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions