Skip to content

feat(swe-router): route on a six-question rubric (0.2.0) - #33

Merged
shekharprateek merged 2 commits into
mainfrom
feat/swe-router-0.2.0
Oct 20, 2026
Merged

shekharprateek merged 2 commits into
mainfrom
feat/swe-router-0.2.0

Conversation

@shekharprateek

Copy link
Copy Markdown
Contributor

Why

The vended skill was still 0.1.x. It asked the assistant's model to judge a quality floor and a complexity tier in prose, then took the cheapest model clearing that floor at that tier.

Measuring that version against its own evidence showed the judgment, not the arithmetic, was the weak part:

  • Three identical passes agreed on the floor for only 14 of 21 tasks, and on the tier for 18 of 21.
  • The tier classifier matched the dataset's own complexity label 16 of 21 times.
  • Some floors were unreachable — the skill never checked that any model could clear the floor it had just set.

docs/swe-router-evaluation.md and -judged.md document that run, so they stay. This PR adds a note to each saying they score the previous router — the instability they document is what motivated the change.

What 0.2.0 does

Six scored questions replace the prose judgment. Four set a difficulty bucket (easy, moderate, hard, very_hard); two set the required quality. Each is 0–2 against written criteria in the new rubric.md, and routing-policy.json holds the thresholds, so an organisation can retune what a bucket demands without touching route.py.

The router then takes the cheapest permitted model whose benchmark score in that bucket clears the bar — and skips a model that failed a benchmark task in that bucket rather than trusting its mean.

Area Change
vend/swe-router/ route.py (accepts both schema majors; --rubric requires 2), SKILL.md, README.md, allowed-models.txt, model-aliases.json, models.json → schema 2.0 with score_by_difficulty / completion_by_difficulty
New files rubric.md (the questions) and routing-policy.json (the thresholds)
install.sh Seven files instead of five; preserves both policy files on upgrade; refuses to install without python3 3.8+
benchmarks/scripts/ swe_router_rubric.py (new — shared bucket/quality arithmetic), build_vended_models.py, run-swe-router-headless.py (--mode rubric, keeping --mode floor for one release), eval_swe_router.py
Tests test_swe_router_rubric.py (new) plus the route/generator/headless suites

swe_router_rubric.py exists so the skill, the generator and the evaluator share one implementation of the bucket arithmetic and cannot drift apart.

Three deviations from upstream's version of these files

Each avoids shipping something broken here:

  1. models.json is committed as a vended artifact, not regenerated. The generator reads docs/metrics/pareto-frontier-omp-swe3.json and the per-task run artifacts, neither committed in this repo, so it cannot run here. Same situation as the 0.1.x file.
  2. The skip guards this repo added in 56560da are re-applied to test_build_vended_models.py. Upstream commits the frontier data and so has no guards; without them CommittedArtifactTest and the real-data tier test fail here for want of input. They still run wherever the data is present.
  3. Five links to docs that don't exist here are dropped rather than left dangling — swe-router-how-it-works.md, swe-router-rubric-design.md, -evaluation.md, results-cli-cli-v1.md. Those pages carry un-abstracted model names and haven't been ported. Prose that stood on its own is kept, and a repo issue link became a bare issue #221 ref, matching the convention used in 28 places already.

Verification

Routing behaviour, exercised directly:

Rubric Bucket Floor Recommends
all zeros easy 60 claude-haiku-5-5 @ $0.14/task
all twos very_hard 80 claude-opus-5-5 @ $4.44/task
all twos, --confidence low very_hard 80 nothing — keeps the developer on their model

A 32x cost spread across the range, which is the point of routing. Low confidence declining to answer is deliberate: the policy keeps the developer put rather than guessing.

Installer, against this branch:

  • Installs all 7 files.
  • A re-run reports Kept your existing routing-policy.json allowed-models.txt and both edits survive.
  • Run against main (where rubric.md doesn't exist yet) it 404s, writes nothing, and says so — verified.
  • The installed copy routes standalone and honours the local allowed-models.txt.

Checks: 549 tests pass (19 more than before), ruff, ruff format, mypy, bandit (0 issues at every severity), no dangling relative links in any changed markdown.

test_judge_common.test_lowercase_preferred_on_collision fails — it fails on main too, assuming a case-sensitive filesystem. Untouched here.

Follow-ups

The vended skill was still 0.1.x: it asked the assistant's model to judge a
quality floor and a complexity tier in prose, then took the cheapest model
clearing that floor at that tier. Measuring that version against its own
evidence showed the judgment, not the arithmetic, was the weak part -- three
identical passes agreed on the floor for only 14 of 21 tasks. The two pages
that document that run are the motivation for this change, so they stay, with
a note saying they score the previous router.

0.2.0 replaces the prose judgment with six scored questions. Four set a
difficulty bucket (easy, moderate, hard, very_hard) and two set the required
quality, each 0-2 against written criteria in rubric.md. routing-policy.json
holds the thresholds, so an organisation can retune what a bucket demands
without touching route.py. The router then takes the cheapest permitted model
whose benchmark score IN THAT BUCKET clears the bar, and skips a model that
failed a benchmark task there rather than trusting its mean.

- vend/swe-router/: route.py (both schema majors; --rubric needs 2), SKILL.md,
  README.md, allowed-models.txt, model-aliases.json, models.json (schema 2.0,
  with score_by_difficulty/completion_by_difficulty columns), and two new
  files -- rubric.md (the questions) and routing-policy.json (the thresholds).
- install.sh: installs seven files instead of five, preserves BOTH policy
  files on upgrade (overwriting routing-policy.json would silently replace an
  organisation's quality bar), and refuses to install without python3 3.8+
  rather than leaving a skill that fails the first time it fires.
- benchmarks/scripts/: swe_router_rubric.py (new; the shared bucket/quality
  arithmetic, so the skill, the generator and the evaluator cannot drift),
  build_vended_models.py (builds the difficulty columns from a rubric pass),
  run-swe-router-headless.py (--mode rubric alongside the kept --mode floor),
  eval_swe_router.py.
- Tests: test_swe_router_rubric.py (new) plus the route/generator/headless
  suites. 549 tests pass, 19 more than before.

Three deviations from the upstream version of these files, all to avoid
shipping something broken here:

1. models.json is committed as a vended artifact rather than regenerated. The
   generator reads docs/metrics/pareto-frontier-omp-swe3.json and the per-task
   run artifacts, neither of which is committed in this repository, so it
   cannot run here. Same as the 0.1.x file.
2. The skip guards this repo added in 56560da are re-applied to
   test_build_vended_models.py. Upstream commits the frontier data and so has
   no guards; without them, CommittedArtifactTest and the real-data tier test
   fail here for want of input. They still run wherever the data is present.
3. Five links to docs that do not exist here are dropped rather than left
   dangling (swe-router-how-it-works, swe-router-rubric-design and
   -evaluation, results-cli-cli-v1). Those pages carry un-abstracted model
   names and have not been ported. The prose that stood on its own is kept,
   and the one-off repo issue link became a bare ref, matching convention.

Verified end to end: a rubric scoring all zeros buckets easy, floor 60, and
recommends claude-haiku-5-5 at $0.14/task; all twos buckets very_hard, floor
80, and recommends claude-opus-5-5 at $4.44. Low confidence keeps the
developer on their model instead of guessing. install.sh installs all seven
files from a ref, a re-run keeps an edited allowed-models.txt and
routing-policy.json, and the installed copy routes on its own and honours the
local allow-list. ruff, ruff format, mypy and bandit (0 issues) clean. The one
failing test, test_judge_common.test_lowercase_preferred_on_collision, fails
on main too: it assumes a case-sensitive filesystem.
All three describe shipped behaviour inaccurately, and all three were
introduced or missed by the two migration PRs, so they are cheaper to fix on
this branch than as a follow-up against main.

- README.md:121 still described the 0.1.x router -- a quality floor plus a tier
  table -- two paragraphs below the line 69 rewrite that describes the rubric.
  A reader got two incompatible accounts of the same skill. The row's link also
  pointed at .claude/skills/swe-router/SKILL.md, which is a symlink (mode
  120000) and so does not resolve on github.com; it now points at
  vend/swe-router/SKILL.md, the real file.
- docs/repository-structure.md:28 itemised the skill as the five-file 0.1.x
  layout. Added rubric.md and routing-policy.json, the two new files.
- docs/strands-setup.md promised derived Claude pricing: "the table covers the
  Claude models from Haiku 4.5 to the Claude 5 family". It does not, and that
  is deliberate -- an Anthropic model reports its cost through the agent, so
  the table carries only the GPT models. Strands is not a pass-through agent
  though: it reports usage and no cost, so a Claude model under --agent strands
  records total_cost_usd null, which a live Bedrock run confirmed. The
  paragraph now says that and points at the token counts instead. Prose only;
  the price table is intentional and untouched.

549 tests, no new failures, no dangling links in the changed files.
@shekharprateek
shekharprateek merged commit 813a8a2 into main Oct 20, 2026
8 checks passed
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.

1 participant