diff --git a/docs/.nojekyll b/docs/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..230832d --- /dev/null +++ b/docs/README.md @@ -0,0 +1,26 @@ +# docs/ + +The GitHub Pages site for this repository: a landing page (`index.html`) and a results walkthrough +per recorded run. Plain HTML, CSS and JS with no build step, so Pages can serve the folder as is +(`.nojekyll` turns Jekyll off). + +To publish it: **Settings → Pages → Build and deployment → Deploy from a branch**, branch `main`, +folder `/docs`. + +| Path | What it is | +| --- | --- | +| `index.html` | Landing page: what System One models are, the four experiments, the protocol, the notebook log | +| `assets/` | Shared styles (`site.css`) and the theme toggle (`site.js`) | +| `model-router/` | The 2026-10-08 model-router run: `index.html`, `results.css`, `app.js`, and generated `data.js` | +| `tools/build-model-router-data.mjs` | Builds `model-router/data.js` | + +`model-router/data.js` is generated, not written by hand. The script reads the three recorded runs in +`model-router/eval/results-2026-10-08T*.json`, routes every answer with model-router's own policy +(`src/policy.mjs`) at each bar from 0.00 to 0.60, and refuses to write if its summaries differ from the +ones each run saved. After a change to those runs or to the policy, regenerate it: + +```bash +node docs/tools/build-model-router-data.mjs +``` + +To preview locally: `python3 -m http.server --directory docs`. diff --git a/docs/assets/site.css b/docs/assets/site.css new file mode 100644 index 0000000..78d8bc2 --- /dev/null +++ b/docs/assets/site.css @@ -0,0 +1,575 @@ +/* System One experiments. Shared by every page in docs/. + A neutral, Linear-style light theme: white surfaces, cool grays, hairline borders, one indigo accent. */ + +:root { + color-scheme: light; + --paper: #fbfbfc; + --surface: #ffffff; + --surface-2: #f4f5f7; + --ink: #16171b; + --ink-2: #5b5e69; + --muted: #8a8e99; + --rule: #e8e9ed; + --rule-strong: #d8dae0; + --grid: rgba(22, 23, 27, 0.04); + --highlight: rgba(94, 106, 210, 0.14); + --stamp: #5e6ad2; /* the accent */ + --accent-ink: #4b56bd; + --link: #4b56bd; + --shadow: 0 1px 1px rgba(16, 18, 27, 0.02), 0 2px 6px rgba(16, 18, 27, 0.04); + --shadow-lg: 0 1px 2px rgba(16, 18, 27, 0.04), 0 12px 32px -12px rgba(16, 18, 27, 0.14); + + /* Decision models: categorical slots 1-3 (validated all-pairs, both modes). */ + --m-typesafe: #2a78d6; + --m-flash: #eb6834; + --m-27b: #1baf7a; + + /* Routes: one ordinal gray ramp, cheapest (lightest) to most capable. */ + --r-0: #adb1bc; + --r-1: #777c8a; + --r-2: #363944; + --r-ink-0: #16171b; + --r-ink-1: #ffffff; + --r-ink-2: #ffffff; + + /* Status, always with an icon and a word. */ + --good: #0ca30c; + --good-text: #1a7f37; + --warning: #e5a000; + --warning-text: #9a6700; + --critical: #d03b3b; + + --serif: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif; /* display role: Inter Display via opsz */ + --sans: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif; + --mono: "JetBrains Mono", ui-monospace, "SFMono-Regular", Menlo, monospace; + + --measure: 66ch; + --wide: 1120px; + --gutter: 16px; + --radius: 10px; +} + +@media (prefers-color-scheme: dark) { + :root:not([data-theme="light"]) { + color-scheme: dark; + --paper: #0b0c0e; + --surface: #141517; + --surface-2: #1b1c1f; + --ink: #f4f5f6; + --ink-2: #b2b5bd; + --muted: #7f838d; + --rule: #25262a; + --rule-strong: #34363b; + --grid: rgba(255, 255, 255, 0.03); + --highlight: rgba(124, 134, 232, 0.2); + --stamp: #7c86e8; + --accent-ink: #a3abf2; + --link: #a3abf2; + --shadow: 0 1px 2px rgba(0, 0, 0, 0.3); + --shadow-lg: 0 16px 40px -12px rgba(0, 0, 0, 0.6); + --m-typesafe: #3987e5; + --m-flash: #d95926; + --m-27b: #199e70; + --r-0: #4b4e59; + --r-1: #8a8e9b; + --r-2: #d6d8df; + --r-ink-0: #ffffff; + --r-ink-1: #0b0c0e; + --r-ink-2: #0b0c0e; + --good-text: #3fb950; + --warning-text: #d29922; + } +} +:root[data-theme="dark"] { + color-scheme: dark; + --paper: #0b0c0e; + --surface: #141517; + --surface-2: #1b1c1f; + --ink: #f4f5f6; + --ink-2: #b2b5bd; + --muted: #7f838d; + --rule: #25262a; + --rule-strong: #34363b; + --grid: rgba(255, 255, 255, 0.03); + --highlight: rgba(124, 134, 232, 0.2); + --stamp: #7c86e8; + --accent-ink: #a3abf2; + --link: #a3abf2; + --shadow: 0 1px 2px rgba(0, 0, 0, 0.3); + --shadow-lg: 0 16px 40px -12px rgba(0, 0, 0, 0.6); + --m-typesafe: #3987e5; + --m-flash: #d95926; + --m-27b: #199e70; + --r-0: #4b4e59; + --r-1: #8a8e9b; + --r-2: #d6d8df; + --r-ink-0: #ffffff; + --r-ink-1: #0b0c0e; + --r-ink-2: #0b0c0e; + --good-text: #3fb950; + --warning-text: #d29922; +} + +*, +*::before, +*::after { + box-sizing: border-box; +} + +html { + -webkit-text-size-adjust: 100%; + scroll-behavior: smooth; + scroll-padding-top: 72px; +} +@media (prefers-reduced-motion: reduce) { + html { + scroll-behavior: auto; + } +} + +body { + margin: 0; + background: var(--paper); + color: var(--ink); + font-family: var(--sans); + font-size: 16px; + line-height: 1.6; + font-optical-sizing: auto; + font-feature-settings: "cv11", "ss01"; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + overflow-x: hidden; +} + +/* Hero backdrop: a faint grid fading out, with a soft accent glow. */ +.graph-paper { + position: relative; + background: + radial-gradient(60% 50% at 50% 0%, color-mix(in srgb, var(--stamp) 9%, transparent), transparent 70%), + var(--surface); +} +.graph-paper::before { + content: ""; + position: absolute; + inset: 0; + pointer-events: none; + background-image: + linear-gradient(var(--grid) 1px, transparent 1px), + linear-gradient(90deg, var(--grid) 1px, transparent 1px); + background-size: 32px 32px; + mask-image: radial-gradient(70% 80% at 50% 0%, #000 30%, transparent 75%); + -webkit-mask-image: radial-gradient(70% 80% at 50% 0%, #000 30%, transparent 75%); +} +.graph-paper > * { + position: relative; +} + +a { + color: var(--link); + text-decoration: none; +} +a:hover { + text-decoration: underline; + text-underline-offset: 3px; +} + +h1, +h2, +h3 { + font-weight: 600; + line-height: 1.12; + letter-spacing: -0.02em; + margin: 0 0 0.5em; + color: var(--ink); +} +h1 { + font-size: clamp(2.4rem, 5.6vw, 4rem); + letter-spacing: -0.035em; + line-height: 1.04; +} +h2 { + font-size: clamp(1.6rem, 3.2vw, 2.15rem); + letter-spacing: -0.028em; +} +h3 { + font-size: 1.12rem; + letter-spacing: -0.012em; +} +p { + margin: 0 0 1em; +} +em { + font-style: normal; + color: var(--ink); + font-weight: 500; +} +code, +kbd { + font-family: var(--mono); + font-size: 0.85em; + background: var(--surface-2); + border: 1px solid var(--rule); + border-radius: 5px; + padding: 0.08em 0.35em; +} +pre { + font-family: var(--mono); + font-size: 0.8rem; + line-height: 1.55; + background: var(--surface); + border: 1px solid var(--rule); + border-radius: var(--radius); + padding: 16px; + overflow-x: auto; + margin: 0 0 1.2em; +} +pre code { + background: none; + border: 0; + padding: 0; + font-size: inherit; +} +mark { + background: var(--highlight); + color: inherit; + border-radius: 4px; + padding: 0 3px; + box-decoration-break: clone; + -webkit-box-decoration-break: clone; +} + +.wrap { + max-width: var(--wide); + margin: 0 auto; + padding: 0 var(--gutter); +} +.prose { + max-width: var(--measure); +} + +/* Small quiet labels for metadata. */ +.label { + font-family: var(--sans); + font-size: 0.75rem; + font-weight: 500; + letter-spacing: 0; + color: var(--muted); +} +.eyebrow { + display: inline-flex; + gap: 8px; + align-items: center; + margin-bottom: 22px; + padding: 4px 12px 4px 8px; + border: 1px solid var(--rule); + border-radius: 999px; + background: var(--surface); + box-shadow: var(--shadow); + font-size: 0.8rem; + font-weight: 500; + color: var(--ink-2); +} +.eyebrow .dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--stamp); + box-shadow: 0 0 0 3px color-mix(in srgb, var(--stamp) 20%, transparent); +} + +/* Top bar */ +.topbar { + position: sticky; + top: 0; + z-index: 20; + background: color-mix(in srgb, var(--surface) 80%, transparent); + backdrop-filter: saturate(180%) blur(12px); + -webkit-backdrop-filter: saturate(180%) blur(12px); + border-bottom: 1px solid var(--rule); +} +.topbar .wrap { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + height: 52px; +} +.brand { + display: flex; + align-items: center; + gap: 10px; + color: var(--ink); + text-decoration: none; + font-family: var(--sans); + font-size: 0.875rem; + font-weight: 600; + letter-spacing: -0.01em; + white-space: nowrap; +} +.brand svg { + flex: none; +} +.brand .crumb { + color: var(--muted); + font-weight: 500; +} +.topbar nav { + display: flex; + align-items: center; + gap: 4px; + font-family: var(--sans); + font-size: 0.82rem; + font-weight: 500; +} +.topbar nav a { + color: var(--ink-2); + text-decoration: none; + padding: 5px 10px; + border-radius: 6px; +} +.topbar nav a:hover { + color: var(--ink); + background: var(--surface-2); +} +.theme-toggle { + appearance: none; + border: 1px solid var(--rule); + background: var(--surface); + color: var(--ink-2); + width: 30px; + height: 30px; + margin-left: 6px; + border-radius: 7px; + box-shadow: var(--shadow); + display: grid; + place-items: center; + cursor: pointer; +} +.theme-toggle:hover { + color: var(--ink); +} +@media (max-width: 640px) { + .topbar nav .hide-sm { + display: none; + } + .topbar nav a { + padding: 5px 6px; + } +} + +/* Section scaffolding */ +section { + padding: 88px 0; +} +section + section { + border-top: 1px solid var(--rule); +} +.sec-head { + display: block; + margin-bottom: 32px; +} +.sec-num { + display: inline-block; + font-family: var(--mono); + font-size: 0.75rem; + font-weight: 500; + color: var(--stamp); + margin-bottom: 12px; +} +.sec-head h2 { + margin-bottom: 0.4em; +} +.sec-head .lede { + color: var(--ink-2); + font-size: 1.05rem; + max-width: var(--measure); + margin: 0; +} +@media (max-width: 640px) { + section { + padding: 60px 0; + } +} + +/* Cards and panels */ +.card { + background: var(--surface); + border: 1px solid var(--rule); + border-radius: var(--radius); + box-shadow: var(--shadow); + padding: 20px; +} +.figure { + background: var(--surface); + border: 1px solid var(--rule); + border-radius: var(--radius); + box-shadow: var(--shadow); + padding: 20px; + margin: 0 0 12px; +} +.figcap { + font-family: var(--sans); + font-size: 0.8rem; + color: var(--muted); + line-height: 1.5; + margin: 8px 0 28px; + max-width: var(--measure); +} +.figcap b { + font-weight: 600; + color: var(--ink-2); + margin-right: 6px; +} + +/* Status badge: a dot and a word. */ +.stamp { + display: inline-flex; + align-items: center; + gap: 6px; + font-family: var(--sans); + font-size: 0.75rem; + font-weight: 500; + line-height: 1; + color: var(--ink-2); + background: var(--surface); + border: 1px solid var(--rule); + border-radius: 999px; + padding: 5px 9px; + white-space: nowrap; +} +.stamp::before { + content: ""; + width: 7px; + height: 7px; + border-radius: 50%; + background: var(--warning); +} +.stamp.live::before { + background: var(--good); +} +.stamp.idle::before { + background: var(--rule-strong); + box-shadow: inset 0 0 0 1.5px var(--muted); +} + +/* Tables */ +.table-wrap { + overflow-x: auto; + border: 1px solid var(--rule); + border-radius: var(--radius); + background: var(--surface); + box-shadow: var(--shadow); +} +table { + border-collapse: collapse; + width: 100%; + font-family: var(--sans); + font-size: 0.85rem; +} +th, +td { + text-align: left; + padding: 10px 12px; + border-bottom: 1px solid var(--rule); + vertical-align: top; +} +th { + font-weight: 500; + font-size: 0.75rem; + color: var(--muted); + background: var(--surface-2); +} +tr:last-child td { + border-bottom: 0; +} +td.num, +th.num { + text-align: right; + font-variant-numeric: tabular-nums; +} + +/* Buttons and controls */ +.btn { + appearance: none; + font-family: var(--sans); + font-size: 0.8rem; + font-weight: 500; + border: 1px solid var(--rule); + background: var(--surface); + color: var(--ink-2); + border-radius: 7px; + padding: 6px 11px; + cursor: pointer; + line-height: 1.25; + box-shadow: var(--shadow); + text-decoration: none; + display: inline-block; +} +.btn:hover { + color: var(--ink); + background: var(--surface-2); + text-decoration: none; +} +.btn[aria-pressed="true"] { + background: color-mix(in srgb, var(--stamp) 10%, var(--surface)); + color: var(--accent-ink); + border-color: color-mix(in srgb, var(--stamp) 40%, transparent); +} +.btn:focus-visible, +a:focus-visible, +input:focus-visible, +select:focus-visible, +.theme-toggle:focus-visible { + outline: 2px solid var(--link); + outline-offset: 2px; +} + +/* Footer */ +footer.site { + border-top: 1px solid var(--rule); + background: var(--surface); + padding: 32px 0 48px; + font-size: 0.8rem; + color: var(--muted); +} +footer.site .wrap { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + gap: 12px; +} +footer.site a { + color: var(--ink-2); +} + +/* Reveal on scroll */ +.reveal { + opacity: 0; + transform: translateY(14px); + transition: + opacity 0.6s ease, + transform 0.6s ease; +} +.reveal.in { + opacity: 1; + transform: none; +} +@media (prefers-reduced-motion: reduce) { + .reveal { + opacity: 1; + transform: none; + transition: none; + } +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} diff --git a/docs/assets/site.js b/docs/assets/site.js new file mode 100644 index 0000000..54a7ef3 --- /dev/null +++ b/docs/assets/site.js @@ -0,0 +1,56 @@ +/* Shared by every page: the theme toggle and reveal-on-scroll. */ + +(function () { + const root = document.documentElement; + const KEY = "s1x-theme"; + + function stored() { + try { + return localStorage.getItem(KEY); + } catch { + return null; + } + } + const saved = stored(); + if (saved === "light" || saved === "dark") root.dataset.theme = saved; + + function current() { + if (root.dataset.theme) return root.dataset.theme; + return matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; + } + + document.addEventListener("DOMContentLoaded", () => { + for (const btn of document.querySelectorAll(".theme-toggle")) { + const label = () => btn.setAttribute("aria-label", `Switch to ${current() === "dark" ? "light" : "dark"} theme`); + label(); + btn.addEventListener("click", () => { + root.dataset.theme = current() === "dark" ? "light" : "dark"; + try { + localStorage.setItem(KEY, root.dataset.theme); + } catch { + /* private window: the choice lasts for this page only */ + } + label(); + document.dispatchEvent(new CustomEvent("themechange")); + }); + } + + const els = document.querySelectorAll(".reveal"); + if (!("IntersectionObserver" in window)) { + els.forEach((el) => el.classList.add("in")); + return; + } + const io = new IntersectionObserver( + (entries) => { + for (const e of entries) { + if (e.isIntersecting) { + e.target.classList.add("in"); + io.unobserve(e.target); + } + } + }, + { rootMargin: "0px 0px -8% 0px" }, + ); + els.forEach((el) => io.observe(el)); + }); +})(); diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..014df74 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,950 @@ + + + + + + System One Experiments + + + + + + + + + + +
+
+ + + System One Experiments + + +
+
+ +
+
+
+
+
Lab notebook · AlteredCraft · opened 2026-09-26
+

What can a model do when it can't write?

+

+ System One models don't generate text. They answer a typed question about some state with a + probability for every answer, in milliseconds. These experiments test where that's enough to stand in + for a parser, a grader, a release manager, or the choice of which model to call. +

+ +
+ +
+
+ Score · route a sub-agent task + +
+
+
+
Which description fits the work this task asks for?
+
+
+ + +
+
+
+ Replaying real answers from TypeSafe Jev (jev-1.13.0), model-router run of 2026-10-08. +
+
+
+
+ +
+
+
+
01
+
+

Three kinds of question, no prose

+

+ Jev is TypeSafe's System + One model, and Open Jev serves the same + /v1/systemone API from open weights on your own machine. You send state and questions; you + get back distributions. The code around it decides what to do with them. +

+
+
+
+
+

Noul yes / no

+

The probability that a statement about the state is true.

+
+ attempt: "a genuine attempt?" → 0.97 + example from misconception-gates' design +
+
+
+

Choice one of ≤ 255

+

A probability for each option. It can only pick something that's on the list.

+
+ "grab the torch" → take__torch + godot-jev: options are the moves possible right now +
+
+
+

Score 2–10 levels

+

Where the input sits on an ordered scale, with a probability per level.

+
+ lookup 0.00 · standard 1.00 · open_ended 0.00 + model-router, task 11, TypeSafe Jev +
+
+
+
+
+ +
+
+
+
02
+
+

The experiments

+

+ Each is a standalone directory with its own stack, a hypothesis, the checks that would confirm it, and + tests that run without an API key. Skeleton means the design is in code and tested against + scripted answers. Live means runs against real models are recorded, with dates. +

+
+
+ +
+ + +
+
+ EXP-02 · godot-jev · Godot 4.7 + Live · 2 models +
+

The Sunken Crypt

+

+ A dungeon crawl whose parser understands whatever you type but can only choose moves the author wrote. + Each turn, the possible moves become one Choice, plus "none of these". +

+
+ Players type naturally with few dead ends, the game stays fully authored, and it's fast enough to feel + like a parser, not a chat. +
+
+
2026-09-30
+
TypeSafe Jev and Open Jev both landed 48 of 48 turns and ranked the same move first on all 48.
+
Speed
+
p95 502 ms hosted, 683 ms local. Neither met the 500 ms bar.
+
+ +
+ +
+
+ EXP-01 · misconception-gates · Node + Skeleton +
+

Grade the misunderstanding

+

+ Read a learner's free-text answer for which misconception it shows, and send them to the part + of the lesson written for it. +

+
+ With four to six authored misconceptions per gate, one Jev call diagnoses free-text answers accurately + enough to act on most of them automatically. +
+
+
Next
+
Accuracy at a useful coverage on ≥ 50 labelled answers per gate.
+
+ +
+ +
+
+ EXP-03 · semver-judge · Python + Skeleton +
+

The next version number

+

+ Decide the bump from the commits since the last tag. Code reads what messages declare, Jev judges what + they don't, and a human sees only commits that are uncertain and could change the answer. +

+
+ A single Jev call per commit agrees with the bump a maintainer would choose on commits without + Conventional prefixes. +
+
+
Next
+
Replay the tagged history of public repos and compare with the bumps maintainers made.
+
+ +
+
+
+
+ +
+
+
+
03
+
+

Protocol

+

+ Every experiment is built the same way. It keeps the model replaceable and the tests honest. +

+
+
+
    +
  1. +

    A decider port

    +

    Code asks decide(state, questions). One adapter calls Jev; a scripted one drives the tests. No unit test calls the network.

    +
  2. +
  3. +

    Policy in code

    +

    Thresholds, arithmetic, dates and counting stay in ordinary code. A policy change is a code change, not a prompt rewrite.

    +
  4. +
  5. +

    Ask only what's open

    +

    Deterministic rules go first. The model answers only what they leave undecided.

    +
  6. +
  7. +

    Confidence decides the path

    +

    Act, ask or escalate by how sure the model is, with the bar set by the cost of being wrong.

    +
  8. +
  9. +

    Fail safe

    +

    If the model is unreachable, fall back to what the system did before it had one.

    +
  10. +
+
+
+ +
+
+
+
04
+
+

Notebook

+

Dated entries, newest first. Each experiment's README records its evidence the same way.

+
+
+
    +
  1. + +
    + model-router's first live run + TypeSafe Jev, Open Jev 27B and Open Jev Flash 9B route 24 sub-agent tasks. None goes too low; the three differ in how sure they are and how fast. +
    +
  2. +
  3. + +
    + The Sunken Crypt on TypeSafe Jev and Open Jev + Same 16 lines, three rounds each. Both 48 of 48. p50 213 ms hosted, 600 ms local. +
    +
  4. +
  5. + +
    + A scripted 16-turn session through the crypt + Every turn 197–300 ms on jev-latest, once the HTTP request moved off the main thread. +
    +
  6. +
  7. + +
    + godot-jev becomes a dungeon crawl + Six rooms, five items, pixel-art rooms painted by code, and a status panel that shows what Jev heard. +
    +
  8. +
  9. + +
    + Three experiment skeletons + Picked from areas awesome-jev had no entries for. +
    +
  10. +
  11. + +
    + Proof of concept: pattern matching on meaning + The fuzzymatch library, now on the archive/fuzzymatch branch. +
    +
  12. +
+
+
+ +
+
+
+
+
05
+
+

Read before trusting

+

Limits that apply to every experiment here.

+
+
+
+
    +
  • Jev takes text only and reads instructions literally.
  • +
  • It can't count or compare dates. That's why arithmetic and thresholds live in code.
  • +
  • Text inside the state can argue for an answer: a learner, a commit author, a player or an orchestrating model can write "this is trivial".
  • +
  • Where the state comes from someone with a reason to game it, keep the stakes of any automatic action low, or keep a human in the loop.
  • +
+
+
+
+ + + + + + + diff --git a/docs/model-router/app.js b/docs/model-router/app.js new file mode 100644 index 0000000..285bb9c --- /dev/null +++ b/docs/model-router/app.js @@ -0,0 +1,742 @@ +/* model-router results page. Draws window.MR_DATA (data.js), which + docs/tools/build-model-router-data.mjs generates from the recorded runs + with model-router's own policy code. Nothing here re-decides a route: + routes at any bar come from rows[].routeAt. */ + +(function () { + "use strict"; + + const D = window.MR_DATA; + const runs = D.runs; + const routes = D.routes; + const ids = routes.map((r) => r.id); + const N = D.tasks.length; + const TOP = routes.length - 1; + const DEFAULT_IDX = Math.round(D.defaultBar * 100); + const byId = Object.fromEntries(runs.map((r) => [r.id, r])); + const ref = runs[0]; // TypeSafe Jev: the reference for deltas + const reduceMotion = matchMedia("(prefers-reduced-motion: reduce)").matches; + + const $ = (sel) => document.querySelector(sel); + const esc = (s) => String(s).replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]); + const f2 = (x) => x.toFixed(2); + const ms = (x) => `${Math.round(x)} ms`; + const mColor = (run) => `var(--m-${run.id})`; + const rColor = (l) => `var(--r-${l})`; + const rInk = (l) => `var(--r-ink-${l})`; + const pct = (n) => `${Math.round((n / N) * 100)}%`; + const swatch = (run) => ``; + const summaryAt = (run, bar) => run.summaries.find((s) => Math.abs(s.bar - bar) < 1e-9); + const atDefault = (run) => summaryAt(run, D.defaultBar); + + function outcome(route, label) { + if (route === label) return "agree"; + return route < label ? "low" : "high"; + } + const GLYPH = { agree: "✓", high: "▲", low: "▼" }; + const OUT_WORD = { agree: "as labelled", high: "too high", low: "too low" }; + + /* ---------- tooltip ---------- */ + const tip = $("#tip"); + const tipAttr = (html) => `data-tip="${esc(html)}" tabindex="0"`; + function placeTip(x, y) { + const pad = 14; + const w = tip.offsetWidth; + const h = tip.offsetHeight; + let left = x + pad; + let top = y + pad; + if (left + w > innerWidth - 8) left = Math.max(8, x - w - pad); + if (top + h > innerHeight - 8) top = Math.max(8, y - h - pad); + tip.style.left = `${left}px`; + tip.style.top = `${top}px`; + } + function showTip(el, x, y) { + tip.innerHTML = el.dataset.tip; + tip.hidden = false; + if (x == null) { + const r = el.getBoundingClientRect(); + x = r.left + r.width / 2; + y = r.bottom; + } + placeTip(x, y); + } + document.addEventListener("pointerover", (e) => { + const el = e.target.closest("[data-tip]"); + if (el) showTip(el, e.clientX, e.clientY); + }); + document.addEventListener("pointermove", (e) => { + if (!tip.hidden && e.target.closest("[data-tip]")) placeTip(e.clientX, e.clientY); + }); + document.addEventListener("pointerout", (e) => { + if (e.target.closest("[data-tip]") && !e.relatedTarget?.closest?.("[data-tip]")) tip.hidden = true; + }); + document.addEventListener("focusin", (e) => { + const el = e.target.closest("[data-tip]"); + if (el) showTip(el); + }); + document.addEventListener("focusout", () => (tip.hidden = true)); + addEventListener("scroll", () => (tip.hidden = true), { passive: true }); + + function taskTip(n, run) { + const row = run.rows[n - 1]; + const o = outcome(row.route, row.label); + return ( + `Task ${n} · ${esc(run.name)}
${esc(D.tasks[n - 1])}` + + `${ids.map((id, l) => `${id} ${f2(row.p[l])}`).join(" · ")}` + + `label ${ids[row.label]} · likeliest ${ids[row.likeliest]} · at ${D.defaultBar}: ${ids[row.route]} (${OUT_WORD[o]})${row.escalated ? ", moved up by the rule" : ""}` + ); + } + + /* ---------- small SVG helpers ---------- */ + const lin = (d0, d1, r0, r1) => (v) => r0 + ((v - d0) / (d1 - d0)) * (r1 - r0); + const widthOf = (el) => Math.max(280, el.clientWidth); + + /* ---------- hero ---------- */ + function hero() { + const under = runs.reduce((a, r) => a + atDefault(r).under, 0); + const misses = runs.reduce((a, r) => a + atDefault(r).under + atDefault(r).over, 0); + const offTop = runs.map((r) => N - atDefault(r).routes[ids[TOP]]); + const sameOff = offTop.every((v) => v === offTop[0]); + $("#hero-stats").innerHTML = ` +
${N} tasks
hand-written sub-agent tasks, each asked once of ${runs.length} decision models
+
${under}
tasks sent to a model too weak for them, by any of the three
+
${sameOff ? pct(offTop[0]) : offTop.map(pct).join(" / ")}
of tasks kept off the most capable model${sameOff ? ", by all three" : ""}. ${misses} miss in ${N * runs.length} decisions.
+
${runs + .map((r) => `${swatch(r)}${Math.round(r.latency.p50)}`) + .join("")}ms
median time per decision: TypeSafe, Flash 9B, 27B
`; + $("#legend-models").innerHTML = runs + .map((r) => `${swatch(r)}${esc(r.name)} · ${r.where === "Local" ? `local, ${esc(r.size)}` : "hosted"}`) + .join(""); + } + + /* ---------- checks ---------- */ + function checks() { + const s = runs.map(atDefault); + const speedPass = runs.map((r) => r.latency.p95 <= D.speedCheckMs); + const speedStatus = runs + .filter((r, i) => !speedPass[i]) + .map((r) => `${r.short} misses by ${Math.round(r.latency.p95 - D.speedCheckMs)} ms`); + const rowsHtml = [ + [ + "1 · Routing", + "On ≥ 100 real tasks, a bar where ≤ 5% go too low and ≥ 50% leave the top route", + s.map((x) => `${pct(x.under)} low · ${pct(N - x.routes[ids[TOP]])} off top`), + `Not yet evidence
24 starter tasks, one author`, + ], + [ + "2 · Speed", + `p95 ≤ ${D.speedCheckMs} ms per decision`, + runs.map((r, i) => `${ms(r.latency.p95)}`), + speedStatus.length ? `${runs.length - speedStatus.length} of ${runs.length} pass. ${speedStatus.join("; ")}.` : "All pass", + ], + [ + "3 · Nothing invented", + "Every decision names a model from the config", + runs.map(() => `by construction`), + `Pass
unit tests, whatever the model returns`, + ], + [ + "4 · Outcomes", + "Tasks sent below the top route succeed as often as on it", + runs.map(() => `—`), + `Later: needs the harness`, + ], + ]; + $("#checks-table tbody").innerHTML = rowsHtml + .map(([c, done, cells, status]) => `${c}${done}${cells.map((x) => `${x}`).join("")}${status}`) + .join(""); + } + + /* ---------- model cards ---------- */ + function modelCards() { + $("#model-cards").innerHTML = runs + .map( + (r) => ` +
+ ${esc(r.where)} +

${esc(r.name)}

+
+
Served model
${esc(r.servedModel)}
+
Size
${esc(r.size)}
+ ${r.temperature ? `
Calibration
T=${esc(r.temperature)}
` : ""} +
Median
${ms(r.latency.p50)} per decision
+
+

${esc(r.note)}

+
`, + ) + .join(""); + } + + /* ---------- the question ---------- */ + function levels() { + $("#levels-list").innerHTML = routes + .map( + (r, l) => ` +
  • + ${l} + ${esc(r.suits)} + route${esc(r.id)}runs${esc(r.model)} +
  • `, + ) + .join(""); + } + + /* ---------- stacked bar ---------- */ + function segments(p, { big = false, labels = false } = {}) { + return p + .map((v, l) => { + if (v < 0.005) return ""; + const show = labels && v >= 0.1; + return `
    ${show ? `${big && v >= 0.22 ? ids[l] + " " : ""}${f2(v)}` : ""}
    `; + }) + .join(""); + } + const stack = (p) => `
    ${segments(p)}
    `; + + /* ---------- §5 explorer ---------- */ + function explorer() { + const PRESETS = [ + { m: "27b", t: 5, bar: 25, txt: "Task 5 · 27B: the worked example" }, + { m: "27b", t: 9, bar: 25, txt: "Task 9 · 27B: sure" }, + { m: "flash", t: 15, bar: 25, txt: "Task 15 · Flash 9B: unsure" }, + { m: "typesafe", t: 3, bar: 5, txt: "Task 3 · TypeSafe: skips a tier" }, + { m: "27b", t: 7, bar: 25, txt: "Task 7 · 27B: the one miss" }, + ]; + const selM = $("#ex-model"); + const selT = $("#ex-task"); + const range = $("#ex-bar"); + selM.innerHTML = runs.map((r) => ``).join(""); + selT.innerHTML = D.tasks + .map((t, i) => ``) + .join(""); + $("#presets").innerHTML = PRESETS.map((p, i) => ``).join(""); + + const state = { m: "27b", t: 5, bar: 25 }; + function sync() { + selM.value = state.m; + selT.value = String(state.t); + range.value = String(state.bar); + document.querySelectorAll("#presets .btn").forEach((b) => { + const p = PRESETS[+b.dataset.i]; + b.setAttribute("aria-pressed", String(p.m === state.m && p.t === state.t && p.bar === state.bar)); + }); + render(); + } + function render() { + const run = byId[state.m]; + const row = run.rows[state.t - 1]; + const bar = D.slider[state.bar]; + const chosen = row.routeAt[state.bar]; + $("#ex-bar-out").textContent = f2(bar); + $("#ex-tasktext").innerHTML = `${state.t}. ${esc(D.tasks[state.t - 1])}· labelled ${ids[row.label]}`; + $("#ex-stack").innerHTML = segments(row.p, { big: true, labels: true }); + const maxP = Math.max(...row.p); + $("#ex-likeliest").innerHTML = + `${ids.map((id, l) => `${id} ${f2(row.p[l])}`).join(" · ")}
    ` + + `Biggest number: ${ids[row.likeliest]} at ${f2(maxP)}. ${run.name} ${maxP >= D.sureAt ? "was sure." : maxP < 0.7 ? "was unsure." : "leaned one way."}`; + $("#ex-ladder").innerHTML = routes + .map((r, l) => { + const risk = row.risk[l]; + const pass = risk <= bar + 1e-9; + const above = ids.slice(l + 1); + const why = above.length ? `P(${above.join(" or ")})` : "nothing above it"; + return ` +
    Send to ${r.id}?
    Risk it needed more = ${why} = ${f2(risk)}.
    ${pass ? "At or under" : "Over"} the bar of ${f2(bar)}.`)}> + ${r.id} + + ${f2(risk)} ${pass ? "✓ pass" : "✗"} +
    `; + }) + .join(""); + const o = outcome(chosen, row.label); + let note; + if (chosen > row.likeliest) { + const why = `The rule moved it up from ${ids[row.likeliest]}: there was a ${f2(row.risk[row.likeliest])} chance that ${ids[row.likeliest]} would be too weak, over the bar.`; + note = + outcome(row.likeliest, row.label) === "low" + ? `${why} Taking the biggest number would have sent it too low.` + : `${why} At a bar this strict it goes past its label; here the biggest number would have been right.`; + } else if (chosen < row.likeliest) { + note = `The bar is loose enough to settle for a cheaper route than the likeliest one.`; + } else if (maxP >= D.sureAt) { + note = `Jev was sure, so the bar makes little difference: the policy and the biggest number agree.`; + } else { + note = `The cheapest route that passes is also the likeliest.`; + } + $("#ex-verdict").innerHTML = + `→ Sent to ${ids[chosen]} (${esc(routes[chosen].model)}): ${GLYPH[o]} ${OUT_WORD[o]}` + + `${note}`; + } + $("#presets").addEventListener("click", (e) => { + const b = e.target.closest(".btn"); + if (!b) return; + const p = PRESETS[+b.dataset.i]; + Object.assign(state, { m: p.m, t: p.t, bar: p.bar }); + sync(); + }); + selM.addEventListener("change", () => ((state.m = selM.value), sync())); + selT.addEventListener("change", () => ((state.t = +selT.value), sync())); + range.addEventListener("input", () => ((state.bar = +range.value), sync())); + sync(); + } + + /* ---------- §6 scoreboard ---------- */ + function scoreboard() { + const metrics = [ + { k: "Routed as labelled", v: (r) => atDefault(r).agree, f: (v) => `${v} of ${N}`, better: 1, key: true }, + { k: "Routed too low", v: (r) => atDefault(r).under, f: String, better: -1, key: true }, + { k: "Routed too high", v: (r) => atDefault(r).over, f: String, better: -1 }, + { k: "Kept off the top model", v: (r) => N - atDefault(r).routes[ids[TOP]], f: (v) => `${v} (${pct(v)})`, better: 1 }, + { k: `Sure (≥ ${D.sureAt} on one route)`, v: (r) => r.sure, f: (v) => `${v} of ${N}`, better: 1 }, + { k: "Mean probability on the label", v: (r) => r.meanOnLabel, f: f2, better: 1, frac: true }, + { k: "Moved up by the rule", v: (r) => r.escalatedCount, f: String, better: 0 }, + { k: "Too low if it took the biggest number", v: (r) => r.likeliestWrongLow, f: String, better: -1 }, + { k: "Median per decision", v: (r) => r.latency.p50, f: ms, better: -1, ratio: true }, + { k: "p95 per decision", v: (r) => r.latency.p95, f: ms, better: -1, ratio: true }, + ]; + $("#scoreboard").innerHTML = runs + .map((r) => { + const rows = metrics + .map((m) => { + const v = m.v(r); + let d = ""; + let worse = false; + if (r !== ref) { + const rv = m.v(ref); + if (m.ratio) { + d = `×${(v / rv).toFixed(1)}`; + worse = v > rv; + } else { + const diff = v - rv; + const near = Math.abs(diff) < (m.frac ? 0.005 : 0.5); + d = near ? "=" : `${diff > 0 ? "+" : "−"}${m.frac ? f2(Math.abs(diff)) : Math.abs(diff)}`; + worse = !near && m.better !== 0 && Math.sign(diff) === -m.better; + } + } + return `
    ${m.k}${m.f(v)}${d}
    `; + }) + .join(""); + return `

    ${esc(r.name)}${r === ref ? 'reference' : ""}

    ${rows}
    `; + }) + .join(""); + + const s = runs.map(atDefault); + const missRuns = runs.filter((r, i) => s[i].over + s[i].under > 0); + const sureText = runs.map((r) => `${r.short} ${r.sure}`).join(", "); + const flash = byId.flash; + const fastest = [...runs].sort((a, b) => a.latency.p50 - b.latency.p50)[0]; + const local = runs.filter((r) => r.where === "Local").sort((a, b) => a.latency.p50 - b.latency.p50); + $("#findings").innerHTML = [ + `No model sent a task too low. ${missRuns + .map((r) => { + const misses = r.rows.map((row, i) => [row, i + 1]).filter(([row]) => row.route !== row.label); + return `The only miss, by the ${r.short}, was task ${misses.map(([, n]) => n).join(", ")}, one tier too high.`; + }) + .join(" ")}`, + `They differ in certainty. Tasks where a model put ≥ ${D.sureAt} on one route: ${sureText}, out of ${N}.`, + `Flash 9B's ${s[runs.indexOf(flash)].agree} of ${N} depends on the routing rule. Its biggest number would have sent ${flash.likeliestWrongLow} tasks to too weak a model; the rule moved them up. For the other two the rule changed nothing.`, + `${fastest.name} was fastest, network round trip included. Of the local models, ${local[0].short} answered ×${(local[1].latency.p50 / local[0].latency.p50).toFixed(1)} faster than the ${local[1].short}, at about a third of the download (${local[0].size} against ${local[1].size}).`, + ] + .map((t) => `
  • ${t}
  • `) + .join(""); + } + + /* ---------- §7a certainty curves ---------- */ + let sureKey = "top"; + function sureChart() { + const el = $("#sure-chart"); + const W = widthOf(el); + const narrow = W < 560; + const H = narrow ? 300 : 340; + const m = { l: 40, r: narrow ? 16 : 150, t: 30, b: 40 }; + const x = lin(1, N, m.l, W - m.r); + const y0 = sureKey === "top" ? 0.3 : 0; // a likeliest route has at least 1/3 of 3 routes + const y = lin(y0, 1, H - m.b, m.t); + const val = (run, row) => (sureKey === "top" ? Math.max(...row.p) : row.p[row.label]); + let g = ""; + for (const t of sureKey === "top" ? [0.3, 0.5, 0.7, 0.9, 1] : [0, 0.25, 0.5, 0.75, 1]) { + g += `${t.toFixed(2)}`; + } + const refLine = sureKey === "top" ? D.sureAt : 0.5; + g += ``; + g += ``; + g += `${sureKey === "top" ? `sure: ≥ ${D.sureAt}` : "0.50: below it, the label wasn't the biggest number"}`; + g += `tasks, each model's sorted from most to least sure →`; + let dots = ""; + let lines = ""; + let labels = ""; + const ends = []; + runs.forEach((run) => { + const pts = run.rows.map((row, i) => ({ n: i + 1, v: val(run, row), run })).sort((a, b) => b.v - a.v || a.n - b.n); + const d = pts.map((p, i) => `${i ? "L" : "M"}${x(i + 1).toFixed(1)},${y(p.v).toFixed(1)}`).join(""); + lines += ``; + pts.forEach((p, i) => { + dots += `${sureKey === "top" ? "on its likeliest route" : "on the labelled route"}: ${f2(p.v)}`)}/>`; + }); + const count = sureKey === "top" ? pts.filter((p) => p.v >= D.sureAt).length : null; + const mean = sureKey === "top" ? null : run.meanOnLabel; + ends.push({ run, y: y(pts[pts.length - 1].v), text: sureKey === "top" ? `${run.short}: ${count} sure` : `${run.short}: mean ${f2(mean)}` }); + }); + if (!narrow) { + ends.sort((a, b) => a.y - b.y); + for (let i = 1; i < ends.length; i++) ends[i].y = Math.max(ends[i].y, ends[i - 1].y + 16); + ends.forEach((e) => { + labels += `${esc(e.text)}`; + }); + } + el.innerHTML = `${g}${lines}${dots}${labels}${ + narrow ? `
    ${ends.map((e) => `${swatch(e.run)}${esc(e.text)}`).join("")}
    ` : "" + }`; + $("#sure-cap").innerHTML = + sureKey === "top" + ? `Fig. 3The probability each model put on its likeliest route, for each task, sorted. TypeSafe Jev and the 27B sit at the top for most tasks and drop off at the end; Flash 9B declines from the start. Hover or tap a dot for the task and the full answer.` + : `Fig. 3The probability each model put on the route the task was labelled with, sorted. Below 0.50 the label wasn't the biggest number, but the rule can still route the task correctly, as it did for Flash 9B's tasks 14–16. Hover or tap a dot for the task.`; + } + + /* ---------- §7b task grid ---------- */ + const differSet = new Set(); + const unsureSet = new Set(); + for (let i = 0; i < N; i++) { + const rs = new Set(runs.map((r) => r.rows[i].route)); + const ls = new Set(runs.map((r) => r.rows[i].likeliest)); + if (rs.size > 1 || ls.size > 1) differSet.add(i + 1); + if (runs.some((r) => Math.max(...r.rows[i].p) < 0.7)) unsureSet.add(i + 1); + } + let gridFilter = "differ"; + function taskGrid() { + const keep = (n) => gridFilter === "all" || (gridFilter === "differ" ? differSet.has(n) : unsureSet.has(n)); + const head = `
    #TaskLabel${runs + .map((r) => `${swatch(r)}${esc(r.short)}`) + .join("")}
    `; + const rows = D.tasks + .map((t, i) => { + const n = i + 1; + if (!keep(n)) return ""; + const label = runs[0].rows[i].label; + const cells = runs + .map((r) => { + const row = r.rows[i]; + const o = outcome(row.route, row.label); + return `
    ${esc(r.short)}${stack(row.p)}${GLYPH[o]}${row.escalated ? '↑' : ""}
    `; + }) + .join(""); + return `
    ${n}${esc(t)}${ids[label]}${cells}
    `; + }) + .join(""); + $("#taskgrid").innerHTML = head + rows; + } + + function deltaNotes() { + const mini = (n, list) => + list + .map( + (r) => + `
    ${swatch(r)}${esc(r.short)}${stack(r.rows[n - 1].p)}${f2(Math.max(...r.rows[n - 1].p))}
    `, + ) + .join(""); + const miss = byId["27b"].rows[6]; + const flash = byId.flash; + const ts16 = byId.typesafe.rows[15]; + $("#delta-notes").innerHTML = ` +
    + Task 7 · the only miss +

    "Rename MAX_RETRY … defined and used twice"

    +

    Labelled ${ids[miss.label]}. The 27B sent it to ${ids[miss.route]} (${miss.p.map(f2).join(" / ")}). The lookup description says "one small change whose exact content is given", and a rename across a definition and two uses is three edits, which may be why. TypeSafe Jev put ${f2(byId.typesafe.rows[6].p[0])} on lookup and Flash 9B ${f2(flash.rows[6].p[0])}.

    +
    ${mini(7, runs)}
    +
    +
    + Tasks 14, 15, 16 · Flash 9B split, the rule caught it +

    Small, well-specified changes labelled standard

    +

    Flash 9B split all three between lookup and standard, with lookup slightly ahead (${[13, 14, 15].map((i) => f2(flash.rows[i].p[0])).join(", ")}). The rule moved each up to standard. The 27B was sure of all three; TypeSafe Jev was sure of 14 and 15 and less sure of 16, the README task (${f2(ts16.p[1])} on standard).

    +
    ${[14, 15, 16].map((n) => `
    Task ${n}
    ${mini(n, runs)}`).join("")}
    +
    `; + } + + /* ---------- §7c pairs ---------- */ + function pairs() { + $("#pairs").innerHTML = D.pairs + .map((p) => { + const a = byId[p.a]; + const b = byId[p.b]; + const track = (field) => + a.rows.map((r, i) => ``).join(""); + return `
    +
    ${swatch(a)}${esc(a.short)} vs ${swatch(b)}${esc(b.short)}
    +
    Same route${track("route")}${p.sameRoute}/${N}
    +
    Same likeliest${track("likeliest")}${p.sameLikeliest}/${N}
    +
    `; + }) + .join(""); + } + + /* ---------- §8 move the bar ---------- */ + let barIdx = DEFAULT_IDX; + const countsAt = (run, idx) => { + const c = { agree: 0, low: 0, high: 0, offTop: 0 }; + for (const row of run.rows) { + const route = row.routeAt[idx]; + c[outcome(route, row.label)] += 1; + if (route !== TOP) c.offTop += 1; + } + return c; + }; + function barPanels() { + const OC = { low: "var(--critical)", high: "var(--warning)" }; + $("#bar-panels").innerHTML = runs + .map((run) => { + const lanes = routes + .map((r, l) => { + const cells = run.rows + .map((row, i) => [row, i + 1]) + .filter(([row]) => row.label === l) + .map(([row, n]) => { + const route = row.routeAt[barIdx]; + const o = outcome(route, row.label); + return `Task ${n} · ${esc(run.name)}
    ${esc(D.tasks[n - 1])}label ${ids[row.label]} → at ${f2(D.slider[barIdx])}: ${ids[route]} (${OUT_WORD[o]})`, + )}>${o === "agree" ? "" : GLYPH[o]}
    `; + }) + .join(""); + return `
    ${r.id}${cells}
    `; + }) + .join(""); + const c = countsAt(run, barIdx); + return `

    ${esc(run.name)}

    ${lanes} +
    +
    ${c.agree}as labelled
    +
    ${c.low}too low ▼
    +
    ${c.high}too high ▲
    +
    ${pct(c.offTop)}off top
    +
    `; + }) + .join(""); + $("#bar-out").textContent = f2(D.slider[barIdx]); + $("#bar-range").value = String(barIdx); + drawCountChart($("#low-chart"), "low"); + drawCountChart($("#high-chart"), "high"); + } + function drawCountChart(el, key) { + const W = widthOf(el); + const H = 210; + const m = { l: 34, r: 12, t: 12, b: 34 }; + const series = runs.map((run) => D.slider.map((_, i) => countsAt(run, i)[key])); + const maxV = Math.max(4, ...series.flat()); + const yMax = Math.ceil(maxV / 4) * 4; + const x = lin(0, D.slider[D.slider.length - 1], m.l, W - m.r); + const y = lin(0, yMax, H - m.b, m.t); + let g = ""; + for (let t = 0; t <= yMax; t += yMax / 4) { + g += `${t}`; + } + for (const b of [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6]) { + g += `${b.toFixed(1)}`; + } + g += `bar →`; + g += ``; + // Step lines, offset by a pixel or two so overlapping series stay visible. + const off = [-1.5, 0, 1.5]; + const paths = series + .map((s, k) => { + let d = `M${x(D.slider[0])},${y(s[0]) + off[k]}`; + for (let i = 1; i < s.length; i++) d += `H${x(D.slider[i])}V${y(s[i]) + off[k]}`; + return ``; + }) + .join(""); + const cx = x(D.slider[barIdx]); + const markers = series.map((s, k) => ``).join(""); + const hits = D.slider + .map((b, i) => { + const x0 = i === 0 ? m.l : (x(D.slider[i - 1]) + x(b)) / 2; + const x1 = i === D.slider.length - 1 ? W - m.r : (x(b) + x(D.slider[i + 1])) / 2; + return `Bar ${f2(b)} · routed ${key === "low" ? "too low" : "too high"}
    ${runs.map((r, k) => `${esc(r.short)}: ${series[k][i]}`).join("
    ")}`, + )}/>`; + }) + .join(""); + el.innerHTML = `${g}${paths}${markers}${hits}`; + } + function barNotes() { + const notes = runs.map((run) => { + const agree = D.slider.map((_, i) => countsAt(run, i).agree); + const best = Math.max(...agree); + const first = agree.indexOf(best); + let last = first; + while (last + 1 < agree.length && agree[last + 1] === best) last++; + const firstLow = D.slider.findIndex((_, i) => countsAt(run, i).low > 0); + const range = `${f2(D.slider[first])}${last > first ? `–${f2(D.slider[last])}` : ""}`; + const where = last === D.slider.length - 1 ? `at every bar from ${f2(D.slider[first])} to the end of the slider` : `at bars ${range}`; + return `${esc(run.name)}: ${best === N ? `all ${N}` : `at best ${best} of ${N}`} as labelled, ${where}. ${firstLow < 0 ? `No task goes too low anywhere up to ${f2(D.slider[D.slider.length - 1])}.` : `The first task goes too low at ${f2(D.slider[firstLow])}.`}`; + }); + notes.push( + `A move isn't always one tier. On task 3, TypeSafe Jev put nearly all its doubt on open_ended, not standard (${byId.typesafe.rows[2].p.map(f2).join(" / ")}), so at 0.05 the task jumps from the cheapest route straight to the most expensive.`, + ); + $("#bar-notes").innerHTML = notes.map((t) => `
  • ${t}
  • `).join(""); + } + function barControls() { + const range = $("#bar-range"); + const play = $("#bar-play"); + let timer = null; + const stop = () => { + clearInterval(timer); + timer = null; + play.textContent = "▶ Sweep"; + play.setAttribute("aria-pressed", "false"); + }; + range.addEventListener("input", () => { + stop(); + barIdx = +range.value; + barPanels(); + }); + $("#bar-reset").addEventListener("click", () => { + stop(); + barIdx = DEFAULT_IDX; + barPanels(); + }); + play.addEventListener("click", () => { + if (timer) return stop(); + if (barIdx >= D.slider.length - 1) barIdx = 0; + play.textContent = "❚❚ Pause"; + play.setAttribute("aria-pressed", "true"); + timer = setInterval( + () => { + barIdx += 1; + barPanels(); + if (barIdx >= D.slider.length - 1) stop(); + }, + reduceMotion ? 250 : 110, + ); + }); + for (const id of ["#low-chart", "#high-chart"]) { + $(id).addEventListener("click", (e) => { + const hit = e.target.closest(".hit"); + if (!hit) return; + stop(); + barIdx = +hit.dataset.idx; + barPanels(); + }); + } + } + + /* ---------- §9 latency ---------- */ + function latChart() { + const el = $("#lat-chart"); + const W = widthOf(el); + const narrow = W < 560; + const rowH = 84; + const m = { l: narrow ? 70 : 130, r: 16, t: 26, b: 36 }; + const H = m.t + rowH * runs.length + m.b; + const maxMs = Math.ceil(Math.max(...runs.map((r) => r.latency.max), D.speedCheckMs) / 100) * 100; + const x = lin(0, maxMs, m.l, W - m.r); + let g = ""; + for (let t = 0; t <= maxMs; t += 100) { + g += `${t}`; + } + g += `ms per decision →`; + g += `check 2: p95 ≤ ${D.speedCheckMs} ms`; + let body = ""; + runs.forEach((run, k) => { + const cy = m.t + rowH * k + rowH / 2; + body += `${esc(narrow ? run.short : run.name)}`; + body += ``; + run.rows.forEach((row, i) => { + const jitter = ((i * 7) % 5) - 2; + body += `Task ${i + 1} · ${esc(run.name)}
    ${ms(row.latencyMs)}${esc(D.tasks[i])}`, + )}/>`; + }); + // p50 labelled above its tick, p95 below, so close ticks don't collide. + for (const [k2, v, ty] of [ + ["p50", run.latency.p50, cy - 24], + ["p95", run.latency.p95, cy + 32], + ]) { + body += `${k2}`; + } + }); + el.innerHTML = `${g}${body}`; + } + function latTable() { + $("#lat-table tbody").innerHTML = runs + .map((r) => { + const L = r.latency; + const pass = L.p95 <= D.speedCheckMs; + return `${swatch(r)}${esc(r.name)}${[L.min, L.p50, L.mean, L.p95, L.max].map((v) => `${ms(v)}`).join("")}${pass ? "pass" : `misses by ${Math.round(L.p95 - D.speedCheckMs)} ms`}`; + }) + .join(""); + } + + /* ---------- misc ---------- */ + function sources() { + $("#sources").innerHTML = D.generatedFrom + .map((p, i) => `${esc(runs[i].short)}`) + .join(", "); + } + function routeKey() { + $("#route-key").innerHTML = routes.map((r, l) => `${r.id}`).join(""); + } + function toggles() { + $("#sure-toggle").addEventListener("click", (e) => { + const b = e.target.closest(".btn"); + if (!b) return; + sureKey = b.dataset.k; + document.querySelectorAll("#sure-toggle .btn").forEach((x) => x.setAttribute("aria-pressed", String(x === b))); + sureChart(); + }); + $("#grid-filter").addEventListener("click", (e) => { + const b = e.target.closest(".btn"); + if (!b) return; + gridFilter = b.dataset.f; + document.querySelectorAll("#grid-filter .btn").forEach((x) => x.setAttribute("aria-pressed", String(x === b))); + taskGrid(); + }); + } + function tocAndProgress() { + const links = [...document.querySelectorAll("#toc a")]; + const secs = links.map((a) => document.querySelector(a.getAttribute("href"))); + const bar = $("#progress"); + const onScroll = () => { + const h = document.documentElement; + bar.style.width = `${(h.scrollTop / Math.max(1, h.scrollHeight - h.clientHeight)) * 100}%`; + let active = -1; + secs.forEach((s, i) => { + if (s.getBoundingClientRect().top < innerHeight * 0.35) active = i; + }); + links.forEach((a, i) => a.classList.toggle("active", i === active)); + }; + addEventListener("scroll", onScroll, { passive: true }); + onScroll(); + } + + /* Charts sized to their container; redrawn when the width changes. */ + function drawCharts() { + sureChart(); + barPanels(); + latChart(); + } + + hero(); + checks(); + modelCards(); + levels(); + explorer(); + scoreboard(); + routeKey(); + taskGrid(); + deltaNotes(); + pairs(); + sources(); + latTable(); + barNotes(); + toggles(); + barControls(); + drawCharts(); + tocAndProgress(); + + let rt; + let lastW = innerWidth; + addEventListener("resize", () => { + if (innerWidth === lastW) return; + lastW = innerWidth; + clearTimeout(rt); + rt = setTimeout(drawCharts, 150); + }); +})(); diff --git a/docs/model-router/data.js b/docs/model-router/data.js new file mode 100644 index 0000000..56ce92e --- /dev/null +++ b/docs/model-router/data.js @@ -0,0 +1,2 @@ +/* Generated by docs/tools/build-model-router-data.mjs from model-router's eval runs. Don't edit. */ +window.MR_DATA = {"generatedFrom":["model-router/eval/results-2026-10-08T18-51-11-416Z.json","model-router/eval/results-2026-10-08T18-41-32-335Z.json","model-router/eval/results-2026-10-08T16-41-27-867Z.json"],"date":"2026-10-08","defaultBar":0.25,"bars":[0.05,0.1,0.15,0.25,0.35,0.5],"slider":[0,0.01,0.02,0.03,0.04,0.05,0.06,0.07,0.08,0.09,0.1,0.11,0.12,0.13,0.14,0.15,0.16,0.17,0.18,0.19,0.2,0.21,0.22,0.23,0.24,0.25,0.26,0.27,0.28,0.29,0.3,0.31,0.32,0.33,0.34,0.35,0.36,0.37,0.38,0.39,0.4,0.41,0.42,0.43,0.44,0.45,0.46,0.47,0.48,0.49,0.5,0.51,0.52,0.53,0.54,0.55,0.56,0.57,0.58,0.59,0.6],"sureAt":0.95,"speedCheckMs":500,"routes":[{"id":"lookup","model":"claude-haiku-5-5","suits":"Finding, reading, listing or summarising what already exists, or making one small change whose exact content is given. Every step is known before starting."},{"id":"standard","model":"claude-sonnet-5-5","suits":"Writing or changing code or text to a clear specification within a few files: a described feature, a bug with a known cause, tests for existing behaviour. The goal is settled, and how to reach it takes some judgment."},{"id":"open_ended","model":"claude-opus-5-5","suits":"Work where the approach itself has to be worked out: a failure with an unknown cause, a design or plan with trade-offs, a change that reaches across many parts of a system, or a review that has to find subtle faults."}],"tasks":["Find every file that imports parseConfig and list the paths with line numbers.","Read CHANGELOG.md and report the date and headline changes of the three most recent releases.","List the environment variables this service reads, with the file each is read in.","In package.json, change the version from 2.3.0 to 2.3.1. Change nothing else.","Run the test suite and report which tests fail, with the first line of each error. Do not fix anything.","Summarise what src/billing/invoice.ts does in five bullet points for someone new to the codebase.","Rename the constant MAX_RETRY to MAX_RETRIES in src/http/client.py, where it is defined and used twice.","Check whether the repo has a LICENSE file and say which licence it is.","Add a --dry-run flag to the sync command. With it set, print each file that would be uploaded and upload nothing. Add a test.","The date parser in utils/dates.py rejects ISO strings with a trailing Z. Accept them as UTC and add tests for both forms.","Write unit tests for the existing slugify function covering empty input, accents, repeated dashes and very long titles.","Add pagination to GET /api/orders: page and per_page query parameters, defaults 1 and 50, and a total count in the response.","Convert the callbacks in src/storage/upload.js to async/await without changing behaviour. The existing tests must still pass.","The settings form submits when Enter is pressed in the search box. Stop that, keeping Enter-to-submit in the other fields.","Add a retry with exponential backoff, at most three attempts, to the webhook sender when the response is a 5xx.","Write the README section for the new export command from its --help output and the two examples in docs/notes.md.","Checkout intermittently double-charges customers, a few times a week, with nothing in the logs. Find the cause.","We need to move the job queue off Redis. Compare the realistic options for our workload and recommend one, with a migration plan.","Memory use of the worker grows by about 50 MB an hour in production but not locally. Work out why.","Review the new authentication middleware for security problems. It handles session tokens, refresh and tenant isolation.","Design how offline editing should work in the mobile app, including conflict resolution when two devices edit the same note.","Split the monolith's billing module into its own service. Work out the boundary, the data it owns and the order to do it in.","Two tests pass alone and fail when run together, in either order. Find what state they share and fix it properly.","The search results page got three times slower after last week's release. Find which change did it and what to do about it."],"runs":[{"id":"typesafe","file":"results-2026-10-08T18-51-11-416Z.json","name":"TypeSafe Jev","short":"TypeSafe","where":"Hosted","size":"Hosted API","note":"Pinned to jev-1.13.0, what jev-latest served on the day. Latency includes the network round trip.","servedModel":"jev-1.13.0","temperature":null,"rows":[{"p":[1,0,0],"label":0,"likeliest":0,"route":0,"escalated":false,"risk":[0,0,0],"latencyMs":132,"routeAt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},{"p":[1,0,0],"label":0,"likeliest":0,"route":0,"escalated":false,"risk":[0,0,0],"latencyMs":80.8,"routeAt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},{"p":[0.9,0.02,0.08],"label":0,"likeliest":0,"route":0,"escalated":false,"risk":[0.1,0.08,0],"latencyMs":115.7,"routeAt":[2,2,2,2,2,2,2,2,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},{"p":[1,0,0],"label":0,"likeliest":0,"route":0,"escalated":false,"risk":[0,0,0],"latencyMs":75.2,"routeAt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},{"p":[0.9,0.01,0.09],"label":0,"likeliest":0,"route":0,"escalated":false,"risk":[0.09999999999999999,0.09,0],"latencyMs":81.8,"routeAt":[2,2,2,2,2,2,2,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},{"p":[0.99,0.01,0],"label":0,"likeliest":0,"route":0,"escalated":false,"risk":[0.01,0,0],"latencyMs":101,"routeAt":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},{"p":[0.98,0.02,0],"label":0,"likeliest":0,"route":0,"escalated":false,"risk":[0.02,0,0],"latencyMs":111.6,"routeAt":[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},{"p":[1,0,0],"label":0,"likeliest":0,"route":0,"escalated":false,"risk":[0,0,0],"latencyMs":107.3,"routeAt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},{"p":[0,1,0],"label":1,"likeliest":1,"route":1,"escalated":false,"risk":[1,0,0],"latencyMs":87.5,"routeAt":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]},{"p":[0.01,0.99,0],"label":1,"likeliest":1,"route":1,"escalated":false,"risk":[0.99,0,0],"latencyMs":85.2,"routeAt":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]},{"p":[0,1,0],"label":1,"likeliest":1,"route":1,"escalated":false,"risk":[1,0,0],"latencyMs":110.8,"routeAt":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]},{"p":[0.04,0.96,0],"label":1,"likeliest":1,"route":1,"escalated":false,"risk":[0.96,0,0],"latencyMs":96.5,"routeAt":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]},{"p":[0,1,0],"label":1,"likeliest":1,"route":1,"escalated":false,"risk":[1,0,0],"latencyMs":76,"routeAt":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]},{"p":[0.06,0.9400000000000001,0],"label":1,"likeliest":1,"route":1,"escalated":false,"risk":[0.9400000000000001,0,0],"latencyMs":91.9,"routeAt":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]},{"p":[0.01,0.99,0],"label":1,"likeliest":1,"route":1,"escalated":false,"risk":[0.99,0,0],"latencyMs":89.4,"routeAt":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]},{"p":[0.37,0.63,0],"label":1,"likeliest":1,"route":1,"escalated":false,"risk":[0.63,0,0],"latencyMs":134.6,"routeAt":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]},{"p":[0,0,1],"label":2,"likeliest":2,"route":2,"escalated":false,"risk":[1,1,0],"latencyMs":127.9,"routeAt":[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2]},{"p":[0,0,1],"label":2,"likeliest":2,"route":2,"escalated":false,"risk":[1,1,0],"latencyMs":108.1,"routeAt":[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2]},{"p":[0,0,1],"label":2,"likeliest":2,"route":2,"escalated":false,"risk":[1,1,0],"latencyMs":121.4,"routeAt":[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2]},{"p":[0,0,1],"label":2,"likeliest":2,"route":2,"escalated":false,"risk":[1,1,0],"latencyMs":93.4,"routeAt":[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2]},{"p":[0,0,1],"label":2,"likeliest":2,"route":2,"escalated":false,"risk":[1,1,0],"latencyMs":84.7,"routeAt":[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2]},{"p":[0,0,1],"label":2,"likeliest":2,"route":2,"escalated":false,"risk":[1,1,0],"latencyMs":96.2,"routeAt":[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2]},{"p":[0,0.04,0.96],"label":2,"likeliest":2,"route":2,"escalated":false,"risk":[1,0.96,0],"latencyMs":76,"routeAt":[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2]},{"p":[0,0,1],"label":2,"likeliest":2,"route":2,"escalated":false,"risk":[1,1,0],"latencyMs":90.6,"routeAt":[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2]}],"summaries":[{"bar":0.05,"n":24,"agree":22,"under":0,"over":2,"routes":{"lookup":6,"standard":8,"open_ended":10}},{"bar":0.1,"n":24,"agree":24,"under":0,"over":0,"routes":{"lookup":8,"standard":8,"open_ended":8}},{"bar":0.15,"n":24,"agree":24,"under":0,"over":0,"routes":{"lookup":8,"standard":8,"open_ended":8}},{"bar":0.25,"n":24,"agree":24,"under":0,"over":0,"routes":{"lookup":8,"standard":8,"open_ended":8}},{"bar":0.35,"n":24,"agree":24,"under":0,"over":0,"routes":{"lookup":8,"standard":8,"open_ended":8}},{"bar":0.5,"n":24,"agree":24,"under":0,"over":0,"routes":{"lookup":8,"standard":8,"open_ended":8}}],"latency":{"min":75.2,"p50":93.4,"mean":98.98333333333333,"p95":132,"max":134.6},"sure":20,"meanOnLabel":0.9683333333333334,"escalatedCount":0,"likeliestWrongLow":0},{"id":"flash","file":"results-2026-10-08T18-41-32-335Z.json","name":"Open Jev Flash 9B","short":"Flash 9B","where":"Local","size":"5 GB","note":"Released the day before the run. MLX 4-bit on an Apple M5 Pro.","servedModel":"OpenJev-Flash-9B-MLX-4bit","temperature":"1.07","rows":[{"p":[0.9312,0.0186,0.0502],"label":0,"likeliest":0,"route":0,"escalated":false,"risk":[0.0688,0.0502,0],"latencyMs":183.5,"routeAt":[2,2,2,2,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},{"p":[0.9774,0.0097,0.013],"label":0,"likeliest":0,"route":0,"escalated":false,"risk":[0.022699999999999998,0.013,0],"latencyMs":184.6,"routeAt":[2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},{"p":[0.9489,0.0226,0.0285],"label":0,"likeliest":0,"route":0,"escalated":false,"risk":[0.0511,0.0285,0],"latencyMs":185.7,"routeAt":[2,2,2,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},{"p":[0.9807,0.0155,0.0038],"label":0,"likeliest":0,"route":0,"escalated":false,"risk":[0.0193,0.0038,0],"latencyMs":181,"routeAt":[2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},{"p":[0.7743,0.0748,0.1509],"label":0,"likeliest":0,"route":0,"escalated":false,"risk":[0.2257,0.1509,0],"latencyMs":178.8,"routeAt":[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},{"p":[0.9674,0.0144,0.0182],"label":0,"likeliest":0,"route":0,"escalated":false,"risk":[0.032600000000000004,0.0182,0],"latencyMs":179.3,"routeAt":[2,2,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},{"p":[0.7898,0.1944,0.0158],"label":0,"likeliest":0,"route":0,"escalated":false,"risk":[0.2102,0.0158,0],"latencyMs":179.2,"routeAt":[2,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},{"p":[0.9619,0.0152,0.0229],"label":0,"likeliest":0,"route":0,"escalated":false,"risk":[0.0381,0.0229,0],"latencyMs":184.4,"routeAt":[2,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},{"p":[0.1571,0.8064,0.0365],"label":1,"likeliest":1,"route":1,"escalated":false,"risk":[0.8429,0.0365,0],"latencyMs":185.1,"routeAt":[2,2,2,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]},{"p":[0.1599,0.8206,0.0195],"label":1,"likeliest":1,"route":1,"escalated":false,"risk":[0.8401,0.0195,0],"latencyMs":182,"routeAt":[2,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]},{"p":[0.0626,0.92,0.0173],"label":1,"likeliest":1,"route":1,"escalated":false,"risk":[0.9373,0.0173,0],"latencyMs":180.6,"routeAt":[2,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]},{"p":[0.2916,0.6607,0.0477],"label":1,"likeliest":1,"route":1,"escalated":false,"risk":[0.7083999999999999,0.0477,0],"latencyMs":182.3,"routeAt":[2,2,2,2,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]},{"p":[0.4227,0.534,0.0433],"label":1,"likeliest":1,"route":1,"escalated":false,"risk":[0.5773,0.0433,0],"latencyMs":179.1,"routeAt":[2,2,2,2,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0]},{"p":[0.543,0.3825,0.0745],"label":1,"likeliest":0,"route":1,"escalated":true,"risk":[0.457,0.0745,0],"latencyMs":180.9,"routeAt":[2,2,2,2,2,2,2,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},{"p":[0.5377,0.4257,0.0366],"label":1,"likeliest":0,"route":1,"escalated":true,"risk":[0.46230000000000004,0.0366,0],"latencyMs":180.3,"routeAt":[2,2,2,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},{"p":[0.5377,0.4257,0.0366],"label":1,"likeliest":0,"route":1,"escalated":true,"risk":[0.46230000000000004,0.0366,0],"latencyMs":179.9,"routeAt":[2,2,2,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},{"p":[0.0191,0.0228,0.9581],"label":2,"likeliest":2,"route":2,"escalated":false,"risk":[0.9809,0.9581,0],"latencyMs":177.9,"routeAt":[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2]},{"p":[0.0553,0.0833,0.8614],"label":2,"likeliest":2,"route":2,"escalated":false,"risk":[0.9447000000000001,0.8614,0],"latencyMs":179,"routeAt":[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2]},{"p":[0.0414,0.0439,0.9147],"label":2,"likeliest":2,"route":2,"escalated":false,"risk":[0.9586,0.9147,0],"latencyMs":183.1,"routeAt":[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2]},{"p":[0.0652,0.0824,0.8524],"label":2,"likeliest":2,"route":2,"escalated":false,"risk":[0.9348000000000001,0.8524,0],"latencyMs":188.2,"routeAt":[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2]},{"p":[0.0377,0.1286,0.8337],"label":2,"likeliest":2,"route":2,"escalated":false,"risk":[0.9622999999999999,0.8337,0],"latencyMs":179.8,"routeAt":[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2]},{"p":[0.0727,0.0817,0.8455],"label":2,"likeliest":2,"route":2,"escalated":false,"risk":[0.9272,0.8455,0],"latencyMs":181.4,"routeAt":[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2]},{"p":[0.1074,0.2734,0.6193],"label":2,"likeliest":2,"route":2,"escalated":false,"risk":[0.8926999999999999,0.6193,0],"latencyMs":180.2,"routeAt":[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2]},{"p":[0.074,0.0658,0.8602],"label":2,"likeliest":2,"route":2,"escalated":false,"risk":[0.9259999999999999,0.8602,0],"latencyMs":179.8,"routeAt":[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2]}],"summaries":[{"bar":0.05,"n":24,"agree":19,"under":0,"over":5,"routes":{"lookup":4,"standard":9,"open_ended":11}},{"bar":0.1,"n":24,"agree":22,"under":0,"over":2,"routes":{"lookup":6,"standard":9,"open_ended":9}},{"bar":0.15,"n":24,"agree":22,"under":0,"over":2,"routes":{"lookup":6,"standard":9,"open_ended":9}},{"bar":0.25,"n":24,"agree":24,"under":0,"over":0,"routes":{"lookup":8,"standard":8,"open_ended":8}},{"bar":0.35,"n":24,"agree":24,"under":0,"over":0,"routes":{"lookup":8,"standard":8,"open_ended":8}},{"bar":0.5,"n":24,"agree":21,"under":3,"over":0,"routes":{"lookup":11,"standard":5,"open_ended":8}}],"latency":{"min":177.9,"p50":180.6,"mean":181.5041666666667,"p95":185.7,"max":188.2},"sure":5,"meanOnLabel":0.7938541666666667,"escalatedCount":3,"likeliestWrongLow":3},{"id":"27b","file":"results-2026-10-08T16-41-27-867Z.json","name":"Open Jev 27B","short":"27B","where":"Local","size":"14 GB","note":"MLX 4-bit on an Apple M5 Pro.","servedModel":"openjev-MLX-4bit","temperature":"0.85","rows":[{"p":[0.9956,0.0006,0.0037],"label":0,"likeliest":0,"route":0,"escalated":false,"risk":[0.0043,0.0037,0],"latencyMs":607.4,"routeAt":[2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},{"p":[0.9991,0.0004,0.0006],"label":0,"likeliest":0,"route":0,"escalated":false,"risk":[0.001,0.0006,0],"latencyMs":607.6,"routeAt":[2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},{"p":[0.9409,0.0016,0.0576],"label":0,"likeliest":0,"route":0,"escalated":false,"risk":[0.059199999999999996,0.0576,0],"latencyMs":605.4,"routeAt":[2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},{"p":[0.9962,0.0037,0.0001],"label":0,"likeliest":0,"route":0,"escalated":false,"risk":[0.0038,0.0001,0],"latencyMs":595.1,"routeAt":[2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},{"p":[0.8402,0.0799,0.0799],"label":0,"likeliest":0,"route":0,"escalated":false,"risk":[0.1598,0.0799,0],"latencyMs":594.5,"routeAt":[2,2,2,2,2,2,2,2,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},{"p":[0.9939,0.0037,0.0024],"label":0,"likeliest":0,"route":0,"escalated":false,"risk":[0.0060999999999999995,0.0024,0],"latencyMs":578.2,"routeAt":[2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},{"p":[0.1654,0.8337,0.001],"label":0,"likeliest":1,"route":1,"escalated":false,"risk":[0.8347,0.001,0],"latencyMs":563.6,"routeAt":[2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]},{"p":[0.9979,0.0006,0.0015],"label":0,"likeliest":0,"route":0,"escalated":false,"risk":[0.0021,0.0015,0],"latencyMs":572.5,"routeAt":[2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},{"p":[0.0018,0.9905,0.0077],"label":1,"likeliest":1,"route":1,"escalated":false,"risk":[0.9982000000000001,0.0077,0],"latencyMs":575.5,"routeAt":[2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]},{"p":[0.0037,0.9949,0.0013],"label":1,"likeliest":1,"route":1,"escalated":false,"risk":[0.9962,0.0013,0],"latencyMs":567.9,"routeAt":[2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]},{"p":[0.0028,0.9959,0.0013],"label":1,"likeliest":1,"route":1,"escalated":false,"risk":[0.9972,0.0013,0],"latencyMs":565.9,"routeAt":[2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]},{"p":[0.0032,0.9865,0.0103],"label":1,"likeliest":1,"route":1,"escalated":false,"risk":[0.9968,0.0103,0],"latencyMs":573.6,"routeAt":[2,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]},{"p":[0.0049,0.9767,0.0184],"label":1,"likeliest":1,"route":1,"escalated":false,"risk":[0.9951,0.0184,0],"latencyMs":564.4,"routeAt":[2,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]},{"p":[0.0434,0.9511,0.0055],"label":1,"likeliest":1,"route":1,"escalated":false,"risk":[0.9565999999999999,0.0055,0],"latencyMs":566.7,"routeAt":[2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]},{"p":[0.0024,0.9857,0.012],"label":1,"likeliest":1,"route":1,"escalated":false,"risk":[0.9977,0.012,0],"latencyMs":563.5,"routeAt":[2,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]},{"p":[0.0499,0.946,0.0041],"label":1,"likeliest":1,"route":1,"escalated":false,"risk":[0.9501,0.0041,0],"latencyMs":564.8,"routeAt":[2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]},{"p":[0.005,0.0008,0.9942],"label":2,"likeliest":2,"route":2,"escalated":false,"risk":[0.995,0.9942,0],"latencyMs":563.4,"routeAt":[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2]},{"p":[0.0028,0.0043,0.9929],"label":2,"likeliest":2,"route":2,"escalated":false,"risk":[0.9972,0.9929,0],"latencyMs":564.9,"routeAt":[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2]},{"p":[0.0037,0.0021,0.9942],"label":2,"likeliest":2,"route":2,"escalated":false,"risk":[0.9963,0.9942,0],"latencyMs":562.9,"routeAt":[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2]},{"p":[0.0018,0.0032,0.995],"label":2,"likeliest":2,"route":2,"escalated":false,"risk":[0.9982,0.995,0],"latencyMs":579.2,"routeAt":[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2]},{"p":[0.001,0.0067,0.9923],"label":2,"likeliest":2,"route":2,"escalated":false,"risk":[0.999,0.9923,0],"latencyMs":565.7,"routeAt":[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2]},{"p":[0.0089,0.005,0.9861],"label":2,"likeliest":2,"route":2,"escalated":false,"risk":[0.9911,0.9861,0],"latencyMs":566.7,"routeAt":[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2]},{"p":[0.0016,0.0103,0.988],"label":2,"likeliest":2,"route":2,"escalated":false,"risk":[0.9983,0.988,0],"latencyMs":564,"routeAt":[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2]},{"p":[0.0184,0.0049,0.9767],"label":2,"likeliest":2,"route":2,"escalated":false,"risk":[0.9816,0.9767,0],"latencyMs":564.9,"routeAt":[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2]}],"summaries":[{"bar":0.05,"n":24,"agree":21,"under":0,"over":3,"routes":{"lookup":5,"standard":9,"open_ended":10}},{"bar":0.1,"n":24,"agree":22,"under":0,"over":2,"routes":{"lookup":6,"standard":10,"open_ended":8}},{"bar":0.15,"n":24,"agree":22,"under":0,"over":2,"routes":{"lookup":6,"standard":10,"open_ended":8}},{"bar":0.25,"n":24,"agree":23,"under":0,"over":1,"routes":{"lookup":7,"standard":9,"open_ended":8}},{"bar":0.35,"n":24,"agree":23,"under":0,"over":1,"routes":{"lookup":7,"standard":9,"open_ended":8}},{"bar":0.5,"n":24,"agree":23,"under":0,"over":1,"routes":{"lookup":7,"standard":9,"open_ended":8}}],"latency":{"min":562.9,"p50":566.7,"mean":574.9291666666667,"p95":607.4,"max":607.6},"sure":20,"meanOnLabel":0.9448291666666666,"escalatedCount":0,"likeliestWrongLow":0}],"pairs":[{"a":"typesafe","b":"flash","sameRoute":24,"sameLikeliest":21},{"a":"typesafe","b":"27b","sameRoute":23,"sameLikeliest":23},{"a":"flash","b":"27b","sameRoute":23,"sameLikeliest":20}]}; diff --git a/docs/model-router/index.html b/docs/model-router/index.html new file mode 100644 index 0000000..14efec9 --- /dev/null +++ b/docs/model-router/index.html @@ -0,0 +1,531 @@ + + + + + + Routing With Jev + + + + + + + + + + +
    + + +
    + + + +
    +
    +
    +
    EXP-04 · model-router · first live run · 2026-10-08
    +

    Three Jevs pick the model for 24 sub-agent tasks.

    +

    + One request to a System One model decides whether a sub-agent runs on Haiku, Sonnet or Opus. We asked + three different Jevs, a hosted one and two running on a laptop, to make that call for the same tasks. On + the scoreboard they nearly tie. Underneath, they differ a lot in how sure they are and how fast they + answer. +

    +
    +
    +
    +
    + + +
    +
    +
    +
    01
    +
    +

    The problem: every sub-agent gets the same model

    +

    + An agent harness spawns sub-agents all the time: find the callers of a function, add a flag, chase down + a memory leak. Either the orchestrator spends its own tokens deciding which model each one gets, or + everything runs on the most capable model. model-router makes that choice with one call, just before + the spawn. +

    +
    +
    + + +

    + Fig. 1This run skipped steps 1 and 2 so that every task was judged by the model. If the decision + model fails or times out, the router returns the config's fallback route and never throws. +

    +
    +
    + + +
    +
    +
    +
    02
    +
    +

    Hypothesis, and what would confirm it

    +
    +
    +
    + Hypothesis + One Jev call over the text of a sub-agent task chooses a model tier well enough that a harness can move + a large share of sub-agent work off its most capable model, while + rarely sending a task to a model too weak for it, at a latency that is + small next to starting the sub-agent. +
    +
    + + + + + + + + + + + + +
    CheckDone whenTypeSafeFlash 9B27BStatus
    +
    +

    + Table 1Checks from the model-router README, with this run's numbers at the default bar of 0.25. + Check 1 needs ≥ 100 labelled tasks from real harness sessions. This run had 24 starter tasks, so its + routing numbers are not evidence for it yet. +

    +
    +
    + + +
    +
    +
    +
    03
    +
    +

    The three decision models

    +

    + Same API, same question, same 24 tasks, each asked once. One model is hosted by TypeSafe; two are + Open Jev models running 4-bit on an Apple M5 Pro laptop. + Flash 9B was released the day before this run. +

    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    04
    +
    +

    What Jev was asked

    +

    + Each request carries the task text and one Score question. The levels describe kinds of work, cheapest + first. Each maps to a model in the router's config, and Jev never sees a model name: + it can judge what a task asks for, but knows nothing about what claude-haiku-5-5 is good + at. +

    +
    +
    +
    +

    Which description fits the work that task asks a sub-agent to do?

    +
      +
      +
      +
      + + +
      +
      +
      +
      05
      +
      +

      How an answer becomes a route

      +

      + Jev returns three probabilities that add up to 1. The obvious policy takes the biggest. The router asks + something different of each route: if the task is sent here, how likely is it that it needed a more + capable model? That's the sum of everything above it. It takes the cheapest route where that risk + is at or under the bar. Sending a task too low costs a failed run and a retry; too high + only costs money. +

      +
      +
      + +
      +
      +
      +
      + + +
      +
      +

      +
      +
      + Jev's answer +
      +
      +
      +
      +
      + The bar + 0.25 +
      + +
      +
      +
      +
      +
      +

      + Fig. 2Pick an example, or any task and model. Risk for each route is the probability that the task + belonged higher. The most capable route always passes, since nothing is above it. Routes come from + model-router's own policy code, replayed at each bar; no new requests are made. +

      +
      +
      + + +
      +
      +
      +
      06
      +
      +

      Result: a near tie on the scoreboard

      +

      At the default bar of 0.25, with differences shown against TypeSafe Jev.

      +
      +
      +
      +
        +
        +
        + + +
        +
        +
        +
        07
        +
        +

        The deltas between the Jevs

        +

        + Same routes, different paths to them. The three differ in how much probability they put on an answer, + which tasks they hesitated over, and how much they depend on the routing rule to land. +

        +
        +
        + +

        7aHow sure each model was

        +
        +
        +
        + + +
        +
        +
        +
        +

        + Fig. 3Each dot is one task. Hover or tap a dot for the task and the full answer. +

        + +

        7bEvery task, side by side

        +
        +
        +
        + + + +
        +
        +
        +
        +
        +

        + Fig. 4Each bar is one model's answer, split lookup / standard / open_ended. A mark at the right + gives the outcome at the bar of 0.25: ✓ as labelled, + ▲ too high, ▼ too low, and + ↑ when the rule moved a task above its likeliest route. "Unsure" means some model + put less than 0.70 on its likeliest route. +

        + +
        + +

        7cHow often each pair agreed

        +
        +

        + Fig. 5Out of 24 tasks. "Same route" is after the policy at 0.25; "same likeliest" is what each + model ranked first before the policy. The rule closes Flash 9B's gap to the others. +

        +
        +
        + + +
        +
        +
        +
        08
        +
        +

        Move the bar

        +

        + The bar is arithmetic on answers already returned, so it can be tuned after the fact. Drag it and watch + where each model's tasks go. For a sure model it barely matters; for an unsure one it decides the + result. +

        +
        +
        +
        +
        + + + 0.25 + +
        +
        +
        +
        + Tasks routed too low +
        +
        +
        + Tasks routed too high +
        +
        +
        +
        +

        + Fig. 6Panels: the 24 tasks in rows by their label, each cell shaded by the route it was sent to at + this bar. Charts: how many tasks go too low or too high as the bar moves from 0 to 0.60, one line per + model; the vertical rule follows the slider. +

        +
          +
          +
          + + +
          +
          +
          +
          09
          +
          +

          Speed: one model per lane

          +

          + Check 2 asks for p95 ≤ 500 ms per routing decision. Measured around each request, one at a time, after + one untimed warm-up. +

          +
          +
          +
          +
          +
          +

          + Fig. 7Each dot is one request. Ticks mark p50 and p95. The hosted figure includes the network round + trip from the laptop; the local models ran on that laptop in ordinary use, and the three runs were spread + over two hours. A rough indication of what a routing decision costs, not a benchmark. +

          +
          + + + + + + + + + + + + + +
          Decision modelminp50meanp95maxCheck 2
          +
          +
          +
          + + +
          +
          +
          +
          10
          +
          +

          What this does and doesn't show

          +

          Read it as "the mechanism works", not as a measured accuracy.

          +
          +
          +
          +
          + It shows +
            +
          • The request works end to end on all three decision models.
          • +
          • A Score over route descriptions separates clear cases.
          • +
          • A model a third of the size can do the job in a third of the time, when the routing rule allows for its doubt.
          • +
          • The policy can be tuned after the fact without asking again.
          • +
          +
          +
          + It doesn't show +
            +
          • 24 tasks, asked once each. One task is 4 percentage points.
          • +
          • One author wrote everything. The tasks, their labels and the route descriptions came from one sitting, so tasks echo the wording they're judged against ("find the cause" against "a failure with an unknown cause").
          • +
          • Labels are opinions. Nobody ran these tasks on the three Claude models to see which was enough. That needs a harness (check 4).
          • +
          • Flash 9B's 24 of 24 has little margin. On tasks 13 to 16 it was close to an even split between two routes.
          • +
          • The tasks don't separate the models. All three are at or near 24 of 24, so this says nothing about which decides best.
          • +
          +
          +
          + +
          +
          + + +
          +
          +
          +
          11
          +
          +

          Run details and reproducing it

          +
          +
          +
          +
          + + + + + + + + + + + +
          Date2026-10-08
          TypeSafe Jevjev-1.13.0, pinned (what jev-latest served on the day), hosted API
          Open Jev 27Bopenjev/openjev-MLX-4bit (MLX, 4-bit, 14 GB), T=0.85
          Open Jev Flash 9Bopenjev/OpenJev-Flash-9B-MLX-4bit (MLX, 4-bit, 5 GB), T=1.07
          Open Jev helpershim.py 81a22f1b for both, with each model card's calibration settings
          HardwareApple M5 Pro, 64 GB, macOS 27.2 (local models)
          Taskseval/tasks.jsonl: 24, eight per route
          RequestsOne per task, sequential, plus one untimed warm-up. Rules and pinned models skipped.
          +
          +
          +
          cd model-router
          +TYPESAFE_API_KEY=sk-... TYPESAFE_DEFAULT_MODEL=jev-1.13.0 node eval/run.mjs
          +JEV_BACKEND=openjev node eval/run.mjs
          +JEV_BACKEND=openjev OPENJEV_URL=http://127.0.0.1:3003 node eval/run.mjs
          +node eval/compare.mjs eval/results-*.json
          +

          + Every number on this page is generated from the three recorded runs by + docs/tools/build-model-router-data.mjs, using model-router's own policy and summary code, + and checked against the summaries each run saved. Sources: + . The written report is + results-2026-10-08.md. +

          +
          +
          +
          +
          +
          + + + + + + + + + diff --git a/docs/model-router/results.css b/docs/model-router/results.css new file mode 100644 index 0000000..fa6aba2 --- /dev/null +++ b/docs/model-router/results.css @@ -0,0 +1,1290 @@ +/* model-router results page. Shared tokens are in ../assets/site.css. */ + +@media (max-width: 520px) { + .hide-xs { + display: none; + } +} + +.progress { + height: 2px; + background: transparent; +} +.progress span { + display: block; + height: 100%; + width: 0; + background: var(--stamp); +} + +/* Side table of contents, wide screens only. */ +.toc { + position: fixed; + top: 96px; + left: max(12px, calc((100vw - var(--wide)) / 2 - 190px)); + width: 170px; + display: none; + flex-direction: column; + gap: 2px; + font-size: 0.78rem; + font-weight: 500; + z-index: 5; +} +.toc a { + color: var(--muted); + text-decoration: none; + padding: 4px 8px; + border-left: 2px solid var(--rule); + display: flex; + gap: 8px; +} +.toc a span { + font-family: var(--mono); + font-size: 0.7rem; + color: var(--rule-strong); +} +.toc a.active { + color: var(--ink); + border-left-color: var(--stamp); +} +.toc a.active span { + color: var(--stamp); +} +@media (min-width: 1520px) { + .toc { + display: flex; + } +} + +/* Hero */ +.hero { + padding: 64px 0 48px; + border-bottom: 1px solid var(--rule); +} +.hero h1 { + max-width: 18ch; +} +.hero h1 em { + color: var(--muted); + font-weight: inherit; +} +.hero .sub { + font-size: 1.12rem; + color: var(--ink-2); + max-width: 62ch; + margin-bottom: 32px; +} +.hero-stats { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 0; + border: 1px solid var(--rule); + border-radius: var(--radius); + background: var(--surface); + box-shadow: var(--shadow); + overflow: hidden; +} +.hero-stats .stat { + padding: 18px 20px; + border-right: 1px solid var(--rule); + font-family: var(--sans); +} +.hero-stats .stat:last-child { + border-right: 0; +} +.hero-stats .v { + font-size: 2.1rem; + font-weight: 600; + line-height: 1; + letter-spacing: -0.035em; +} +.hero-stats .v small { + font-size: 0.45em; + color: var(--muted); + letter-spacing: 0; +} +.hero-stats .k { + font-size: 0.8rem; + color: var(--ink-2); + margin-top: 8px; + line-height: 1.4; +} +.hero-stats .lat { + display: flex; + align-items: baseline; + gap: 4px 10px; + flex-wrap: wrap; + font-size: 1.8rem; +} +.hero-stats .lat span { + display: inline-flex; + align-items: baseline; + gap: 4px; +} +@media (max-width: 860px) { + .hero-stats { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + .hero-stats .stat:nth-child(2) { + border-right: 0; + } + .hero-stats .stat:nth-child(-n + 2) { + border-bottom: 1px solid var(--rule); + } +} +@media (max-width: 440px) { + .hero-stats { + grid-template-columns: 1fr; + } + .hero-stats .stat { + border-right: 0; + border-bottom: 1px solid var(--rule); + } +} + +.legend-models { + display: flex; + flex-wrap: wrap; + gap: 8px 22px; + margin-top: 18px; + font-family: var(--sans); + font-size: 0.82rem; + color: var(--ink-2); +} +.legend-models span, +.sw-label { + display: inline-flex; + align-items: center; + gap: 8px; +} +.sw { + display: inline-block; + width: 10px; + height: 10px; + border-radius: 50%; + background: var(--c); + flex: none; + margin-right: 6px; + vertical-align: 0; +} +.legend-models .sw { + margin-right: 0; +} + +/* Fig. 1 pipeline */ +.pipeline { + display: flex; + align-items: stretch; + gap: 0; + overflow-x: auto; + padding-bottom: 4px; +} +.pipeline .stage { + flex: 1 1 0; + min-width: 140px; + background: var(--surface); + border: 1px solid var(--rule); + border-radius: var(--radius); + box-shadow: var(--shadow); + padding: 14px; + font-family: var(--sans); + font-size: 0.8rem; + color: var(--ink-2); + display: flex; + flex-direction: column; + gap: 6px; +} +.pipeline .stage b { + font-weight: 600; + font-size: 0.95rem; + color: var(--ink); + line-height: 1.25; +} +.pipeline .stage code { + font-size: 0.7rem; + line-height: 1.4; +} +.pipeline .stage.muted { + border-style: dashed; + border-color: var(--rule-strong); + background: transparent; + box-shadow: none; +} +.pipeline .stage.jev { + border-color: color-mix(in srgb, var(--stamp) 55%, transparent); + box-shadow: 0 0 0 4px color-mix(in srgb, var(--stamp) 12%, transparent), var(--shadow); +} +.pipeline .stage.jev .label { + color: var(--accent-ink); +} +.pipeline .arrow { + flex: 0 0 22px; + position: relative; +} +.pipeline .arrow::before { + content: ""; + position: absolute; + top: 50%; + left: 3px; + right: 6px; + height: 1px; + background: var(--ink-2); +} +.pipeline .arrow::after { + content: ""; + position: absolute; + top: calc(50% - 4px); + right: 3px; + border: 4px solid transparent; + border-left: 6px solid var(--ink-2); + border-right: 0; +} +.chips { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 4px 6px; + font-family: var(--mono); + font-size: 0.7rem; +} +.chips i { + width: 9px; + height: 9px; + border-radius: 2px; + background: var(--c); + display: inline-block; +} +@media (max-width: 860px) { + .pipeline { + flex-direction: column; + overflow: visible; + } + .pipeline .stage { + min-width: 0; + } + .pipeline .arrow { + flex: 0 0 20px; + } + .pipeline .arrow::before { + top: 3px; + bottom: 6px; + left: 24px; + right: auto; + width: 1px; + height: auto; + } + .pipeline .arrow::after { + top: auto; + bottom: 2px; + left: 20px; + right: auto; + border: 4px solid transparent; + border-top: 6px solid var(--ink-2); + border-bottom: 0; + } +} + +/* Hypothesis */ +.hypothesis { + margin: 0 0 28px; + padding: 28px 32px; + background: var(--surface); + border: 1px solid var(--rule); + border-left: 3px solid var(--stamp); + border-radius: var(--radius); + box-shadow: var(--shadow); + font-size: clamp(1.08rem, 2vw, 1.3rem); + font-weight: 500; + letter-spacing: -0.012em; + line-height: 1.55; + max-width: 900px; +} +.hypothesis .label { + display: block; + margin-bottom: 10px; +} +@media (max-width: 560px) { + .hypothesis { + padding: 20px; + } +} +.checks td:first-child { + font-weight: 500; + color: var(--ink); + white-space: nowrap; +} +.checks td { + color: var(--ink-2); +} +.checks .m-col { + white-space: nowrap; +} +.mark-pass, +.mark-fail, +.mark-na { + font-variant-numeric: tabular-nums; + white-space: nowrap; +} +.mark-pass::before { + content: "✓ "; + color: var(--good-text); + font-weight: 700; +} +.mark-fail::before { + content: "✗ "; + color: var(--critical); + font-weight: 700; +} +.mark-na { + color: var(--muted); +} + +/* Model cards */ +.model-cards { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 16px; +} +.model-card { + background: var(--surface); + border: 1px solid var(--rule); + border-top: 3px solid var(--c); + border-radius: var(--radius); + box-shadow: var(--shadow); + padding: 20px; + font-family: var(--sans); + font-size: 0.85rem; + color: var(--ink-2); +} +.model-card h3 { + font-size: 1.2rem; + margin-bottom: 4px; + color: var(--ink); +} +.model-card .where { + font-size: 0.75rem; + font-weight: 500; + color: var(--muted); +} +.model-card dl { + display: grid; + grid-template-columns: auto 1fr; + gap: 6px 14px; + margin: 14px 0; +} +.model-card dt { + color: var(--muted); +} +.model-card dd { + margin: 0; + color: var(--ink); + font-variant-numeric: tabular-nums; + word-break: break-word; +} +.model-card p { + margin: 0; + font-size: 0.8rem; +} +@media (max-width: 860px) { + .model-cards { + grid-template-columns: 1fr; + } +} + +/* The question */ +.question { + background: var(--surface); + box-shadow: var(--shadow); + border: 1px solid var(--rule); + border-radius: var(--radius); + padding: 24px; +} +.qtext { + font-size: 1.15rem; + font-weight: 500; + letter-spacing: -0.012em; + margin-bottom: 18px; +} +.levels-list { + list-style: none; + margin: 0; + padding: 0; + display: grid; + gap: 10px; +} +.levels-list li { + display: grid; + grid-template-columns: 34px minmax(0, 1fr) 210px; + gap: 16px; + align-items: start; + padding: 14px 0; + border-top: 1px dashed var(--rule); +} +.levels-list .lv { + width: 30px; + height: 30px; + border-radius: 4px; + display: grid; + place-items: center; + font-family: var(--mono); + font-size: 0.85rem; +} +.levels-list .desc { + font-size: 0.95rem; + color: var(--ink-2); +} +.levels-list .map { + font-family: var(--mono); + font-size: 0.75rem; + display: flex; + flex-direction: column; + gap: 4px; +} +.levels-list .map b { + font-weight: 500; + color: var(--ink); +} +.levels-list .map span { + color: var(--muted); +} +@media (max-width: 700px) { + .levels-list li { + grid-template-columns: 34px minmax(0, 1fr); + } + .levels-list .map { + grid-column: 2; + } +} + +/* Stacked probability bar */ +.stackbar { + display: flex; + height: 22px; + gap: 2px; + width: 100%; +} +.stackbar.big { + height: 44px; + margin: 8px 0 10px; +} +.stackbar .seg { + height: 100%; + background: var(--c); + min-width: 0; + position: relative; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + font-family: var(--sans); + font-size: 0.78rem; + font-variant-numeric: tabular-nums; + color: var(--tc); + transition: flex-grow 0.45s cubic-bezier(0.2, 0.8, 0.2, 1); +} +.stackbar .seg:first-child { + border-radius: 3px 0 0 3px; +} +.stackbar .seg:last-child { + border-radius: 0 3px 3px 0; +} +.stackbar.big .seg span { + white-space: nowrap; + padding: 0 6px; +} + +/* Explorer */ +.ex-controls { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + gap: 14px; + margin-bottom: 16px; +} +.presets { + display: flex; + flex-wrap: wrap; + gap: 8px; +} +.pickers { + display: flex; + flex-wrap: wrap; + gap: 12px; +} +.pickers label { + display: flex; + flex-direction: column; + gap: 4px; +} +select { + font-family: var(--sans); + font-size: 0.85rem; + background: var(--surface); + color: var(--ink); + border: 1px solid var(--rule-strong); + border-radius: 6px; + padding: 6px 8px; + max-width: 100%; +} +#ex-task { + max-width: 280px; +} +.ex-task { + font-family: var(--mono); + font-size: 0.85rem; + background: var(--surface-2); + border-radius: 6px; + padding: 12px 14px; + margin: 0 0 18px; + color: var(--ink); +} +.ex-task .lab { + color: var(--muted); + margin-left: 8px; +} +.ex-grid { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); + gap: 32px; +} +@media (max-width: 760px) { + .ex-grid { + grid-template-columns: 1fr; + } +} +.ex-likeliest { + font-family: var(--sans); + font-size: 0.85rem; + color: var(--ink-2); +} +.ex-bar-head { + display: flex; + justify-content: space-between; + align-items: baseline; +} +.ex-bar-head output, +.bar-out { + font-family: var(--mono); + font-size: 1.1rem; + font-variant-numeric: tabular-nums; +} +input[type="range"] { + width: 100%; + accent-color: var(--stamp); + margin: 8px 0 14px; +} +.ladder { + display: grid; + gap: 8px; + font-family: var(--sans); + font-size: 0.82rem; +} +.rung { + display: grid; + grid-template-columns: 96px minmax(0, 1fr) 70px; + gap: 10px; + align-items: center; + padding: 6px 8px; + border-radius: 6px; + border: 1px solid transparent; +} +.rung.chosen { + border-color: color-mix(in srgb, var(--stamp) 45%, transparent); + background: color-mix(in srgb, var(--stamp) 6%, var(--surface)); +} +.rung .rid { + font-family: var(--mono); + font-size: 0.75rem; + display: flex; + align-items: center; + gap: 6px; +} +.rung .rid i { + width: 10px; + height: 10px; + border-radius: 2px; + background: var(--c); +} +.rung .rtrack { + position: relative; + height: 12px; + background: var(--surface-2); + border-radius: 2px; +} +.rung.chosen .rtrack { + background: var(--surface); +} +.rung .rfill { + position: absolute; + inset: 0 auto 0 0; + background: var(--ink-2); + border-radius: 0 3px 3px 0; + transition: width 0.4s ease; +} +.rung .rbar { + position: absolute; + top: -4px; + bottom: -4px; + width: 2px; + background: var(--stamp); + transition: left 0.2s ease; +} +.rung .rres { + text-align: right; + font-variant-numeric: tabular-nums; + white-space: nowrap; +} +.rung .rres.pass { + color: var(--good-text); +} +.rung .rres.fail { + color: var(--muted); +} +.ex-verdict { + margin-top: 18px; + padding-top: 16px; + border-top: 1px solid var(--rule); + font-size: 1rem; + font-weight: 500; +} +.ex-verdict .note { + display: block; + color: var(--ink-2); + font-weight: 400; + font-size: 0.92rem; + margin-top: 4px; +} + +/* Outcome marks */ +.o-agree, +.o-high, +.o-low, +.o-up { + font-family: var(--sans); + font-weight: 700; +} +.o-agree { + color: var(--good-text); +} +.o-high { + color: var(--warning-text); +} +.o-low { + color: var(--critical); +} +.o-up { + color: var(--ink); +} + +/* Scoreboard */ +.scoreboard { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 16px; + margin-bottom: 28px; +} +.score { + background: var(--surface); + border: 1px solid var(--rule); + border-top: 3px solid var(--c); + border-radius: var(--radius); + box-shadow: var(--shadow); + padding: 18px 20px; + font-family: var(--sans); +} +.score h3 { + font-size: 1.1rem; + margin-bottom: 12px; +} +.score .ref { + font-size: 0.7rem; + font-weight: 500; + color: var(--muted); + border: 1px solid var(--rule); + background: var(--surface-2); + border-radius: 999px; + padding: 2px 8px; + margin-left: 6px; + vertical-align: 3px; +} +.score .rows { + display: grid; + gap: 0; +} +.score .r { + display: grid; + grid-template-columns: minmax(0, 1fr) auto auto; + gap: 10px; + align-items: baseline; + padding: 7px 0; + border-top: 1px solid var(--rule); + font-size: 0.85rem; +} +.score .r .k { + color: var(--ink-2); +} +.score .r .v { + font-variant-numeric: tabular-nums; + font-weight: 600; + color: var(--ink); + text-align: right; +} +.score .r .d { + font-family: var(--mono); + font-size: 0.7rem; + min-width: 64px; + text-align: right; + color: var(--muted); + font-variant-numeric: tabular-nums; +} +.score .r .d.worse { + color: var(--warning-text); +} +.score .r.key .v { + font-size: 1rem; +} +@media (max-width: 860px) { + .scoreboard { + grid-template-columns: 1fr; + } +} + +.findings { + margin: 0; + padding: 0; + list-style: none; + display: grid; + gap: 12px; + max-width: var(--measure); +} +.findings li { + padding-left: 22px; + position: relative; + color: var(--ink-2); +} +.findings li b { + color: var(--ink); + font-weight: 600; +} +.findings li::before { + content: ""; + position: absolute; + left: 2px; + top: 0.62em; + width: 8px; + height: 8px; + border: 1.5px solid var(--stamp); + transform: rotate(45deg); +} + +/* Sub-sections */ +.sub-h { + display: flex; + align-items: baseline; + gap: 10px; + margin-top: 44px; + font-size: 1.25rem; +} +.sub-h span { + font-family: var(--mono); + font-size: 0.75rem; + color: var(--stamp); +} +.fig-toolbar { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + align-items: center; + gap: 12px; + margin-bottom: 14px; +} +.fig-toolbar [role="group"] { + display: flex; + flex-wrap: wrap; + gap: 6px; +} +.route-key { + display: flex; + flex-wrap: wrap; + gap: 6px 14px; + font-family: var(--mono); + font-size: 0.7rem; + color: var(--ink-2); +} +.route-key span { + display: inline-flex; + align-items: center; + gap: 6px; +} +.route-key i { + width: 12px; + height: 12px; + border-radius: 2px; + background: var(--c); +} + +/* Charts */ +.chart { + width: 100%; + font-family: var(--sans); +} +.chart svg { + display: block; + width: 100%; + height: auto; + overflow: visible; +} +.chart text { + fill: var(--muted); + font-size: 11px; +} +.chart .tick-text { + font-variant-numeric: tabular-nums; +} +.chart .grid { + stroke: var(--rule); + stroke-width: 1; +} +.chart .axis { + stroke: var(--rule-strong); + stroke-width: 1; +} +.chart .ann { + fill: var(--ink-2); + font-size: 12px; +} +.chart .ann-strong { + fill: var(--ink); + font-size: 12px; + font-weight: 600; +} +.chart .dot { + stroke: var(--surface); + stroke-width: 2; + cursor: pointer; + transition: + cx 0.5s cubic-bezier(0.2, 0.8, 0.2, 1), + r 0.15s ease; +} +.chart .dot:hover, +.chart .dot.hl { + stroke: var(--ink); +} +.chart .hit { + fill: transparent; + cursor: pointer; +} +.chart .check-line { + stroke: var(--critical); + stroke-width: 1.5; + stroke-dasharray: 4 3; +} +.chart .cursor { + stroke: var(--ink); + stroke-width: 1.5; +} +.chart .default-line { + stroke: var(--rule-strong); + stroke-dasharray: 3 3; +} + +/* Task grid */ +.taskgrid { + display: grid; + gap: 0; + font-family: var(--sans); + font-size: 0.82rem; +} +.tg-head, +.tg-row { + display: grid; + grid-template-columns: 30px minmax(0, 1.5fr) 92px repeat(3, minmax(0, 1fr)); + gap: 12px; + align-items: center; + padding: 8px 0; + border-bottom: 1px solid var(--rule); +} +.tg-head { + font-size: 0.75rem; + font-weight: 500; + color: var(--muted); + border-bottom-color: var(--rule-strong); +} +.tg-head span:nth-child(n + 4) { + display: flex; + align-items: center; + gap: 6px; +} +.tg-row .n { + font-family: var(--mono); + color: var(--muted); + font-size: 0.75rem; +} +.tg-row .t { + color: var(--ink); + line-height: 1.35; +} +.tg-row .lab { + font-family: var(--mono); + font-size: 0.7rem; + display: inline-flex; + align-items: center; + gap: 6px; +} +.tg-row .lab i { + width: 10px; + height: 10px; + border-radius: 2px; + background: var(--c); +} +.tg-cell { + display: grid; + grid-template-columns: minmax(0, 1fr) 30px; + gap: 6px; + align-items: center; + cursor: default; +} +.tg-cell .o { + font-size: 0.8rem; + white-space: nowrap; +} +.tg-row.differs { + background: linear-gradient(90deg, var(--highlight), transparent 70%); +} +.tg-row.enter { + animation: rowin 0.35s ease both; +} +@keyframes rowin { + from { + opacity: 0; + transform: translateY(4px); + } +} +.tg-mlabel { + display: none; +} +@media (max-width: 760px) { + .tg-head { + display: none; + } + .tg-row { + grid-template-columns: 26px minmax(0, 1fr); + gap: 6px 10px; + padding: 12px 0; + } + .tg-row .lab { + grid-column: 2; + } + .tg-cell { + grid-column: 2; + grid-template-columns: 64px minmax(0, 1fr) 30px; + } + .tg-mlabel { + display: block; + font-family: var(--mono); + font-size: 0.66rem; + color: var(--ink-2); + } +} + +.delta-notes { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 16px; + margin: 8px 0 12px; +} +.delta-note { + background: var(--surface); + border: 1px solid var(--rule); + border-radius: var(--radius); + padding: 18px 20px; +} +.delta-note .label { + display: block; + margin-bottom: 6px; +} +.delta-note h4 { + font-weight: 600; + letter-spacing: -0.012em; + font-size: 1.02rem; + margin: 0 0 8px; +} +.delta-note p { + font-size: 0.9rem; + color: var(--ink-2); + margin: 0 0 12px; +} +.delta-note .mini { + display: grid; + gap: 6px; + font-family: var(--sans); + font-size: 0.75rem; +} +.delta-note .mini .mr { + display: grid; + grid-template-columns: 78px minmax(0, 1fr) 30px; + gap: 8px; + align-items: center; +} +@media (max-width: 760px) { + .delta-notes { + grid-template-columns: 1fr; + } +} + +/* Pairs */ +.pairs { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 16px; +} +.pair { + background: var(--surface); + border: 1px solid var(--rule); + border-radius: var(--radius); + padding: 16px 18px; + font-family: var(--sans); + font-size: 0.82rem; +} +.pair .who { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 4px 6px; + margin-bottom: 12px; + color: var(--ink); + font-weight: 600; +} +.pair .who .vs { + color: var(--muted); + font-weight: 400; +} +.pair .m { + display: grid; + grid-template-columns: 100px minmax(0, 1fr) 44px; + gap: 8px; + align-items: center; + margin-top: 6px; + color: var(--ink-2); +} +.pair .track { + display: flex; + gap: 1px; + height: 12px; +} +.pair .track i { + flex: 1; + background: var(--rule-strong); + border-radius: 1px; +} +.pair .track i.off { + background: var(--stamp); +} +.pair .m b { + text-align: right; + font-variant-numeric: tabular-nums; + color: var(--ink); +} +@media (max-width: 860px) { + .pairs { + grid-template-columns: 1fr; + } +} + +/* Move the bar */ +.bar-control { + display: grid; + grid-template-columns: auto minmax(0, 1fr) 54px auto; + gap: 14px; + align-items: center; + margin-bottom: 18px; +} +.bar-control input { + margin: 0; +} +@media (max-width: 560px) { + .bar-control { + grid-template-columns: auto minmax(0, 1fr) auto; + } + .bar-control #bar-reset { + grid-column: 1 / -1; + justify-self: start; + } +} +.bar-panels { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 16px; + margin-bottom: 24px; +} +.bp { + border: 1px solid var(--rule); + border-top: 3px solid var(--c); + border-radius: var(--radius); + padding: 14px; + font-family: var(--sans); + font-size: 0.8rem; +} +.bp h4 { + margin: 0 0 10px; + font-weight: 600; + font-size: 0.95rem; +} +.bp .lane { + display: grid; + grid-template-columns: 76px minmax(0, 1fr); + gap: 8px; + align-items: center; + margin-bottom: 6px; +} +.bp .lane .ln { + font-family: var(--mono); + font-size: 0.66rem; + color: var(--muted); +} +.bp .cells { + display: grid; + grid-template-columns: repeat(8, minmax(0, 1fr)); + gap: 3px; +} +.bp .cell { + aspect-ratio: 1; + border-radius: 3px; + background: var(--c); + display: grid; + place-items: center; + font-size: 0.7rem; + font-weight: 700; + transition: background-color 0.3s ease; + cursor: default; + color: var(--tc); +} +.bp .cell.low, +.bp .cell.high { + outline: 2px solid var(--oc); + outline-offset: 1px; +} +.bp .counts { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 6px; + margin-top: 12px; + padding-top: 10px; + border-top: 1px solid var(--rule); +} +.bp .counts div { + display: flex; + flex-direction: column; +} +.bp .counts b { + font-weight: 600; + letter-spacing: -0.02em; + font-size: 1.25rem; + font-variant-numeric: tabular-nums; + line-height: 1.1; +} +.bp .counts span { + font-size: 0.66rem; + color: var(--muted); + line-height: 1.3; +} +.bp .counts .bad b { + color: var(--critical); +} +.bp .counts .warn b { + color: var(--warning-text); +} +@media (max-width: 960px) { + .bar-panels { + grid-template-columns: 1fr; + } +} +.bar-charts { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 24px; +} +@media (max-width: 760px) { + .bar-charts { + grid-template-columns: 1fr; + } +} + +/* Caveats and details */ +.caveats { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr); + gap: 16px; +} +.caveats ul, +.next ol { + margin: 10px 0 0; + padding-left: 1.1em; + font-size: 0.92rem; + color: var(--ink-2); +} +.caveats li b { + color: var(--ink); + font-weight: 600; +} +.caveats li, +.next li { + margin-bottom: 0.55em; +} +.next { + margin-top: 16px; +} +@media (max-width: 760px) { + .caveats { + grid-template-columns: 1fr; + } +} +.details-grid { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); + gap: 24px; + align-items: start; +} +.details-grid > * { + min-width: 0; +} +.details-grid th { + width: 150px; + vertical-align: top; +} +.details-grid .small { + font-size: 0.9rem; + color: var(--ink-2); +} +@media (max-width: 860px) { + .details-grid { + grid-template-columns: 1fr; + } +} + +/* Tooltip */ +.tip { + position: fixed; + border: 1px solid color-mix(in srgb, var(--paper) 15%, transparent); + z-index: 50; + max-width: 320px; + background: var(--ink); + color: var(--paper); + font-family: var(--sans); + font-size: 0.78rem; + line-height: 1.45; + padding: 10px 12px; + border-radius: 6px; + pointer-events: none; + box-shadow: 0 8px 24px -8px rgba(0, 0, 0, 0.4); +} +.tip b { + font-weight: 600; +} +.tip .tm { + font-family: var(--mono); + font-size: 0.7rem; + opacity: 0.75; + display: block; + margin-top: 4px; +} diff --git a/docs/tools/build-model-router-data.mjs b/docs/tools/build-model-router-data.mjs new file mode 100644 index 0000000..ac1ecbc --- /dev/null +++ b/docs/tools/build-model-router-data.mjs @@ -0,0 +1,148 @@ +/* Builds docs/model-router/data.js from model-router's recorded eval runs. + + node docs/tools/build-model-router-data.mjs + + Every number the results page shows comes from here: the raw answers in + model-router/eval/results-*.json, routed by model-router's own policy + (src/policy.mjs) and summarised by its own eval code (eval/summary.mjs). + The page only draws them. Before writing, the summaries are checked + against the ones each run recorded, so the page can't drift from the run. */ + +import { mkdir, readFile, writeFile } from "node:fs/promises"; +import { fileURLToPath } from "node:url"; +import { deepStrictEqual } from "node:assert/strict"; +import { decisionFrom } from "../../model-router/src/policy.mjs"; +import { percentile, summarise, modelName } from "../../model-router/eval/summary.mjs"; + +const here = (p) => fileURLToPath(new URL(p, import.meta.url)); +const evalDir = here("../../model-router/eval/"); + +// The runs written up in model-router/eval/results-2026-10-08.md, in its order. +const RUNS = [ + { + id: "typesafe", + file: "results-2026-10-08T18-51-11-416Z.json", + name: "TypeSafe Jev", + short: "TypeSafe", + where: "Hosted", + size: "Hosted API", + note: "Pinned to jev-1.13.0, what jev-latest served on the day. Latency includes the network round trip.", + }, + { + id: "flash", + file: "results-2026-10-08T18-41-32-335Z.json", + name: "Open Jev Flash 9B", + short: "Flash 9B", + where: "Local", + size: "5 GB", + note: "Released the day before the run. MLX 4-bit on an Apple M5 Pro.", + }, + { + id: "27b", + file: "results-2026-10-08T16-41-27-867Z.json", + name: "Open Jev 27B", + short: "27B", + where: "Local", + size: "14 GB", + note: "MLX 4-bit on an Apple M5 Pro.", + }, +]; + +const BARS = [0.05, 0.1, 0.15, 0.25, 0.35, 0.5]; // the bars eval/run.mjs tries +const SLIDER = Array.from({ length: 61 }, (_, i) => i / 100); // 0.00 to 0.60 +const SURE = 0.95; // "put 0.95 or more on one route" + +const tasks = (await readFile(`${evalDir}tasks.jsonl`, "utf8")).split("\n").filter(Boolean).map((l) => JSON.parse(l)); + +const runs = []; +for (const meta of RUNS) { + const raw = JSON.parse(await readFile(`${evalDir}${meta.file}`, "utf8")); + const { config } = raw; + const ids = config.routes.map((r) => r.id); + const level = Object.fromEntries(ids.map((id, i) => [id, i])); + + raw.rows.forEach((row, i) => { + if (row.task !== tasks[i]?.task) throw new Error(`${meta.file} row ${i + 1} is not task ${i + 1} of tasks.jsonl`); + }); + + // The page must show exactly what the run recorded. + deepStrictEqual(BARS.map((bar) => summarise(config, raw.rows, bar)), raw.summaries, `${meta.file}: summaries differ`); + + const rows = raw.rows.map((row) => { + const p = ids.map((_, l) => row.answer.probabilities[l]); + const d = decisionFrom(config, row.answer); + return { + p, + label: level[row.label], + likeliest: level[d.likeliest], + route: level[d.route], + escalated: d.escalated, + risk: ids.map((_, l) => p.slice(l + 1).reduce((a, b) => a + b, 0)), + latencyMs: Math.round(row.latencyMs * 10) / 10, + // The route at each slider bar, from the experiment's own policy. + routeAt: SLIDER.map((bar) => level[decisionFrom({ ...config, maxUnderRouteRisk: bar }, row.answer).route]), + }; + }); + + const ms = rows.map((r) => r.latencyMs); + const top = rows.map((r) => Math.max(...r.p)); + runs.push({ + ...meta, + servedModel: modelName(raw.rows[0].model), + temperature: /\bT=([\d.]+)/.exec(raw.rows[0].model)?.[1] ?? null, + rows, + summaries: raw.summaries, + latency: { + min: Math.min(...ms), + p50: percentile(ms, 50), + mean: ms.reduce((a, b) => a + b, 0) / ms.length, + p95: percentile(ms, 95), + max: Math.max(...ms), + }, + sure: top.filter((t) => t >= SURE).length, + meanOnLabel: rows.reduce((a, r) => a + r.p[r.label], 0) / rows.length, + escalatedCount: rows.filter((r) => r.escalated).length, + likeliestWrongLow: rows.filter((r) => r.likeliest < r.label).length, + }); +} + +// How often each pair of decision models chose the same route, and ranked the same route first. +const pairs = []; +for (let a = 0; a < runs.length; a++) { + for (let b = a + 1; b < runs.length; b++) { + const same = (field) => runs[a].rows.filter((r, i) => r[field] === runs[b].rows[i][field]).length; + pairs.push({ a: runs[a].id, b: runs[b].id, sameRoute: same("route"), sameLikeliest: same("likeliest") }); + } +} + +const config = JSON.parse(await readFile(`${evalDir}${RUNS[0].file}`, "utf8")).config; +const data = { + generatedFrom: RUNS.map((r) => `model-router/eval/${r.file}`), + date: "2026-10-08", + defaultBar: config.maxUnderRouteRisk, + bars: BARS, + slider: SLIDER, + sureAt: SURE, + speedCheckMs: 500, + routes: config.routes, + tasks: tasks.map((t) => t.task), + runs, + pairs, +}; + +const out = here("../model-router/data.js"); +await mkdir(here("../model-router/"), { recursive: true }); +await writeFile( + out, + `/* Generated by docs/tools/build-model-router-data.mjs from model-router's eval runs. Don't edit. */\n` + + `window.MR_DATA = ${JSON.stringify(data)};\n`, +); +console.log(`wrote ${out}`); +for (const r of runs) { + const s = r.summaries.find((x) => x.bar === data.defaultBar); + console.log( + `${r.name.padEnd(18)} agree ${s.agree}/${s.n} low ${s.under} high ${s.over} sure ${r.sure} ` + + `escalated ${r.escalatedCount} p50 ${r.latency.p50.toFixed(0)} ms p95 ${r.latency.p95.toFixed(0)} ms`, + ); +} +for (const p of pairs) console.log(`${p.a} vs ${p.b}: same route ${p.sameRoute}/24, same likeliest ${p.sameLikeliest}/24`);