Skip to content

feat(core): round-robin weights are priority tiers, not shares - #1124

Merged
dezren39 merged 1 commit into
mainfrom
feat/route-round-robin-tiers
Nov 1, 2026
Merged

dezren39 merged 1 commit into
mainfrom
feat/route-round-robin-tiers

Conversation

@dezren39

@dezren39 dezren39 commented Nov 1, 2026 •

Copy link
Copy Markdown

Closes #1122.

What changes

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

  • Equal effective weights form one tier; selection rotates evenly inside the active tier (the existing per-session draw + stickiness, restricted to the tier).
  • The next tier is touched only when every member above it has failed or is unavailable for that dispatch's walk — not ready, already tried, gate-closed, cooling, quota-gated; eligibility is judged exactly as before.
  • All-equal or unset weights (unset still defaults to 1) collapse to a single tier = today's plain even round-robin. Unweighted pools do not change behavior.

Tier order interpretation (flagged in #1122)

The ruling said "lowest weight" first, but the fleet's pools already use weight as preference — alternate at 2 over main at 1 so alternate burns first, Go leaves at 3/2 atop luna-plan, the DeepSeek plan pool's weight-100 guard ahead of PAYG. This PR implements higher weight = earlier tier; a literal ascending reading would invert all of those pools. Reversing is the tier comparator and nothing else.

Demotion rule

The tier key is the pre-demotion effective weight (the tracedWeightOf layers through adjustment). Quota demotion's 0.01 factor would smear a demoted heavyweight into the middle tiers, so instead:

  • a demoted leaf sorts below every non-demoted tier for the length of its demotion window, keeps weight order among demoted leaves, and still serves as last resort;
  • 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 everywhere else: ordered and weighted ranking keeps using the final effective weight exactly as today.

Per-mode delta

  • round-robin: tiered, as above. The session draw and sticky honor are scoped to the active tier, and the failover tail is tier-ordered, so one dispatch exhausts a tier before touching the next. Route decisions record each candidate's tiers entry (pre-demotion weight + demoted flag) next to the existing weights/layers.
  • ordered: no change.
  • weighted: no change — it remains the proportional mode (random draw by ratio) for pools that genuinely want shares; the ruling redefines round-robin only.

Seam

Tiering lives in nextTarget's walk, the one place that sees each child's full weight trace and demotion state. selectSessionTarget is unchanged — the walk passes it only the active tier's candidates with the (uniform) tier weight. factor() learned to average the adjustment layer for a group's tier key; its default layer is the existing behavior.

Tests

New ModelRoute round-robin tiers (#1122) block in packages/core/test/model-route.test.ts (9 tests): single-tier even rotation; two-tier strict fall-through (never drawn while a top member is ready; engages when all have failed/left, within a walk and across dispatches); sticky return to a recovered tier; rotation persisting across dispatches; unset weight tiering with weight 1; a demoted weight-100 leaf sorting below live 2/1 tiers and serving last; a nested route reference as a tier member at both weight directions; a fully-demoted group dropping as one demoted member.

Typecheck clean; the route family — 11 files (model-route, -credential, -autonomy, -limits, -schedule, -tuning, -tuning-schedule, model-routes-expand, model-route-stats-tool, model-resolver, location-layer) — 310 pass, 0 fail.

Docs

routing-config.md and routing-review.md now describe weights as ratios in weighted pools and priority tiers in round-robin pools.

Known follow-up (not in this PR)

The route review's expectedShare (§3.4 "underused members") still models round-robin expectation as proportional, so a lower tier's members will read as underused while a higher tier is healthy. The tiers data now recorded on decisions is what a tier-aware review needs; flagging here rather than widening this change.

Not deployed

This PR does not update the laptop's running server; deploy is a separate step after merge.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

In a round-robin node a target's weight now names its priority tier:
selection rotates evenly inside the top tier and falls through to the
next tier only once every member above it has failed or is unavailable
for the dispatch. The tier key is the pre-demotion weight (configured
weight after schedules, preferences and learned adjustments). A
quota-demoted target sorts below every live tier instead of being
smeared into the middle by the 0.01 factor — ordered and weighted
modes keep that factor and their ranking unchanged. A nested group
tiers by its own weight scaled by its leaves' average pre-demotion
weight, and counts as demoted only when every door under it is
demoted. Sessions stick within the active tier: a pick made in a
lower tier while the top tier was down no longer strands the session
there once the tier recovers.

Decisions now record each candidate's tier alongside its weights so a
tiered choice can be explained after the fact. The routing skill docs
describe the new reading of weights.

Weighted mode is untouched: it remains the proportional mode for
pools that want shares.
@coderabbitai

coderabbitai Bot commented Nov 1, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 19 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 4ffe0a99-2b28-423e-a5d8-c543e5decccc
Commits

Reviewing files that changed from the base of the PR and between 1fd0233 and fcd4b09.

Files selected for processing: 4
  • packages/core/src/model-route.ts
  • packages/core/src/plugin/skill/routing-config.md
  • packages/core/src/plugin/skill/routing-review.md
  • packages/core/test/model-route.test.ts
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-01T01:32:53.537027Z fcd4b09 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fcd4b09f3d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// so a recovered tier takes the session back instead of stranding it on the fallback. The
// draw's weights are the tier keys: equal inside a tier, so the rotation there stays even.
// Weighted draws keep the whole candidate set and the effective weights, exactly as before.
const pool = node.selection === "round-robin" ? topTier(positions) : positions

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Update expected-share reporting for tier-scoped draws

For a healthy round-robin pool with weights [2, 1], draws now always select the first target, but memberOf in packages/core/src/model-route-review.ts still calculates expectedShare from proportional weights across all candidates, reporting 33% for the fallback instead of 0%. This reaches users through route_stats as a share the weight “promised”; moreover, §3.4 of routing-review.md explicitly instructs the reviewing agent to raise weights for these supposedly underused members after 30 draws. Consequently, correctly idle fallback tiers generate misleading diagnostics and recommendations that can undermine the configured priorities. Make the expectation calculation and underuse guidance tier-aware alongside this selection change.

Useful? React with 👍 / 👎.

const head = tiers[byTier(from)[0]]
return from.filter((position) => {
const tier = tiers[position]
return tier.demoted === head.demoted && tier.weight === head.weight

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Normalize computed weights before comparing priority tiers

When mathematically equal effective weights reach this comparison through different computations, floating-point rounding can split them into separate tiers and permanently starve one member. For example, an equal-weight round-robin parent containing a three-leaf nested group and a direct leaf, with a global schedule factor of 0.1, gives the group a weight of (0.1 + 0.1 + 0.1) / 3 = 0.10000000000000002 through factor(), versus 0.1 for the direct leaf. The group therefore becomes the sole top-tier candidate, and the direct leaf receives no new sessions while the group is available. Previously that rounding difference had only a negligible effect on proportional rotation. Normalize tier keys or consistently account for rounding in both tier ordering and equality so equivalent computed weights still rotate together.

Useful? React with 👍 / 👎.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

1 participant