omp-llm-role 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +179 -11
  2. package/agents/designer.md +4 -3
  3. package/docs/dev/README.md +44 -7
  4. package/docs/dev/agent-authoring.md +59 -1
  5. package/docs/dev/architecture.md +84 -27
  6. package/docs/dev/ci.md +152 -0
  7. package/docs/dev/current-state.md +327 -24
  8. package/docs/dev/data-sources.md +202 -38
  9. package/docs/dev/explorer.md +157 -46
  10. package/docs/dev/quirks.md +45 -4
  11. package/docs/dev/releasing.md +129 -17
  12. package/docs/dev/scoring.md +789 -28
  13. package/docs/dev/testing.md +206 -0
  14. package/docs/dev/writes-and-state.md +176 -28
  15. package/docs/llm-role-rankings.md +141 -140
  16. package/package.json +754 -80
  17. package/skills/omp-llm-role-create-agent/SKILL.md +7 -1
  18. package/src/agent-architect.ts +148 -19
  19. package/src/agent-create.ts +800 -40
  20. package/src/agent-file.ts +2 -5
  21. package/src/architect-provenance.ts +16 -0
  22. package/src/availability.ts +104 -6
  23. package/src/benchmark-author.ts +9 -4
  24. package/src/benchmark-sources.ts +283 -18
  25. package/src/cli/create-role.ts +29 -1
  26. package/src/cli/llm-role-rank.ts +40 -1
  27. package/src/config-edit.ts +207 -80
  28. package/src/engine.ts +409 -46
  29. package/src/error-report.ts +562 -0
  30. package/src/explorer/boot.ts +36 -24
  31. package/src/explorer/explain.ts +110 -13
  32. package/src/explorer/scopes.ts +110 -0
  33. package/src/explorer/server.ts +112 -32
  34. package/src/extension.ts +407 -34
  35. package/src/features.ts +103 -0
  36. package/src/project-registry.ts +65 -0
  37. package/src/project-setup.ts +382 -0
  38. package/src/prompts/agent-creation-architect.md +2 -0
  39. package/src/prompts/project-profile-architect.md +33 -0
  40. package/src/prompts/project-profile-user.md +8 -0
  41. package/src/role-archetypes.ts +12 -2
  42. package/src/role-settings.ts +22 -0
  43. package/src/settings.ts +205 -24
  44. package/src/state.ts +1 -1
  45. package/src/updater.ts +150 -31
  46. package/web/app.js +411 -12
  47. package/web/index.html +1 -0
  48. package/web/style.css +44 -1
  49. package/docs/dev/spec.md +0 -633
@@ -1,9 +1,8 @@
1
1
  # Scoring
2
2
 
3
- Maintainer reference for the ranking math. The normative decisions live in
4
- [`spec.md`](spec.md) §4.1, §6 and §7; this page is the implementation-level
5
- companion: the exact transforms, the invariants a weight change must preserve,
6
- and the measured findings behind the shipped weights. All math is in
3
+ Maintainer reference for the ranking math: the exact transforms, the invariants
4
+ a weight change must preserve, and the measured findings behind the shipped
5
+ weights. All math is in
7
6
  `src/engine.ts`; the shipped role defs are `DEFAULT_ROLES` in `src/settings.ts`;
8
7
  the archetype sets are `src/role-archetypes.ts`.
9
8
 
@@ -16,9 +15,9 @@ rank compression (real magnitude gaps now count) and field-dependent scales.
16
15
 
17
16
  | Class | Metrics | Transform | Anchors |
18
17
  |---|---|---|---|
19
- | index | `general`, `reasoning`, `math`, `code`, `agents`, `search`, `vision`, `tool_calling`, `long_context` | `(v+20)/80` | −20→0, +60→1; extrapolates outside (no clamp) |
18
+ | index | `general`, `reasoning`, `math`, `code`, `agents`, `search`, `vision`, `tool_calling`, `long_context`, `index_communication`, `index_finance`, `index_healthcare`, `index_legal` | `(v+20)/80` | −20→0, +60→1; extrapolates outside (no clamp) |
20
19
  | benchmark, chance-anchored | `gpqa` | `(v−0.25)/0.75` | 4-way multiple choice, chance 0.25 |
21
- | benchmark, raw | `mrcr`, `aime`, `swe_bench`, `arc_agi`, `terminal_bench`, `tau_bench` | identity | pass rate 0–1, chance ≈ 0 |
20
+ | benchmark, raw | `mrcr`, `aime`, `swe_bench`, `arc_agi`, `terminal_bench`, `tau_bench`, `simpleqa_score`, `hle_score`, `mmmu_score`, `mmmu_pro_score`, `mmmlu_score`, `browsecomp_score`, `swe_bench_pro_score`, `mcp_atlas_score`, `apex_agents_score`, `osworld_score`, `scicode_score`, `screenspot_pro_score`, `charxiv_r_score`, `frontiermath_score`, `toolathlon_score` | identity | pass rate 0–1, chance ≈ 0 |
22
21
  | throughput | `throughput` | `ln(v/10)/ln(30)` | 10→0, 300→1; clamped to [0,1] |
23
22
  | percentile / score | `website`, `writing` | identity | already 0–1 |
24
23
  | external benchmark | `bench:<id>`, `<ns>:<local>` | identity | normalized to 0–1 at parse time (`score / scoreMax`), so no new transform class |
@@ -35,13 +34,14 @@ rank compression (real magnitude gaps now count) and field-dependent scales.
35
34
  **Invariant — forward and inverse must agree.** The explorer's "what would it
36
35
  take to move up" targets come from `inverseCardinal(metric, t)`
37
36
  (`src/explorer/explain.ts`), which must invert `cardinalMetric` for every
38
- weightable metric. It handles `index` (`t·80−20`) and `throughput` (`10·30^t`,
39
- null outside [0,1]) and returns identity for everything else. That identity is
40
- correct for the raw benchmarks and the percentiles but **wrong for `gpqa`**,
41
- whose forward transform is chance-anchored: `t = 0.5` should map to raw 0.625,
42
- not 0.5. Before adding a metric to `KNOWN_METRICS`, check that both
43
- `cardinalMetric` and `inverseCardinal` handle its transform; a chance-anchored
44
- metric needs a matching inverse (read `BENCHMARK_CHANCE`).
37
+ weightable metric. It handles `index` (`t·80−20`), `throughput` (`10·30^t`,
38
+ null outside [0,1]), and a chance-anchored metric (`t·(1−chance)+chance`, reading
39
+ `BENCHMARK_CHANCE` from the engine so the two can never drift); everything else
40
+ is identity. Forward and inverse therefore agree for every weightable metric —
41
+ `gpqa`'s `t = 0.5` maps to raw 0.625, not 0.5. Before adding a metric to
42
+ `KNOWN_METRICS`, check that both `cardinalMetric` and `inverseCardinal` handle
43
+ its transform; a chance-anchored metric needs a matching inverse (read
44
+ `BENCHMARK_CHANCE`).
45
45
 
46
46
  ## Quality composite `q`
47
47
 
@@ -78,7 +78,139 @@ value = q − λ · priceEff
78
78
  model will actually run the role's level (see below); otherwise it is the bare
79
79
  billed price.
80
80
  - `value` is the sort key; ties break score desc → blended $/M asc → id asc
81
- (determinism, spec §6.1).
81
+ (determinism).
82
+
83
+ ### Cache-read pricing (cache-hit rate)
84
+
85
+ A role may declare an assumed **cache-hit rate** for its input tokens
86
+ (`roles.<role>.cacheHitRate`, 0–1; `0`/absent = off). Agent loops resend the
87
+ same system prompt every turn, so a cache-heavy role's invoice is dominated by
88
+ cache reads; the knob prices that, opt-in, without touching any shipped role.
89
+
90
+ - the route's billed price is `price = (3·input + output)/4` on the **listed**
91
+ input price; a hit rate `h` blends the endpoint's cache-read price into the
92
+ input term, `effInput = h·cacheRead + (1−h)·input`, giving
93
+ `billed = price + 3·(effInput − input)/4` (the output term is unchanged);
94
+ - `blendRoutePool(routes, cacheHitRate)` weights by **`1/listedInput²`** — the
95
+ router's sort key — so the hit rate changes the per-route billed price, not the
96
+ routing probability: a cheap cache on a lightly-weighted route does not
97
+ dominate (user story 9);
98
+ - a route with **no cache-read price** keeps its full input price
99
+ (`cacheReadPrice = null` → `effInput = input`), so missing data is not a silent
100
+ discount;
101
+ - `endpointFilteredModel(model, filters, cacheHitRate)` recomputes the blend when
102
+ the role declares endpoint filters **or** a non-zero hit rate; the hit rate
103
+ composes with the filters (filter the pool first, then cache-price it);
104
+ - `roleEffectivePrice`/`routeEffectivePrice` is the single per-route accessor the
105
+ blend, the pinned-route path and `roleMetricValue` share; a pinned role prices
106
+ its route's cache-adjusted billed price, and `maxPriceUsdPerM` caps the
107
+ thinking-adjusted `priceEff` (the same cap as the blend path);
108
+ - `explainModel` mirrors all of it, carries `role.cacheHitRate`, and reports the
109
+ cache-adjusted `cost.billedPrice`; the SPA cost line appends `· cache <rate>`
110
+ when the rate is set;
111
+ - with `cacheHitRate = 0` (absent) every path is **byte-identical** to the
112
+ uncached ranking. `resolveSettings` validates the rate to `[0, 1]` (a typo
113
+ cannot silently halve or double the price).
114
+
115
+ ### Route-aware pricing (provider pin)
116
+
117
+ A role may pin its requests to one OpenRouter provider route
118
+ (`roles.<role>.providerPin`). When it does, the role is priced by that
119
+ route instead of the `1/price²` blend:
120
+
121
+ - `pinnedRoute(model, pin)` (`src/engine.ts`) resolves the model's route by
122
+ **exact, tiered-verbatim** `providerSlug` — `deepinfra` and `deepinfra/fp8`
123
+ are different routes, with no normalization — and returns `null` on no match.
124
+ - `priceEff` is the route's billed 3:1 price (cache-adjusted by the role's
125
+ `cacheHitRate` when set, see *Cache-read pricing*), scaled by the same
126
+ thinking factor and gated by the same `runsAtLevel` rule as an unpinned role:
127
+ the pin changes the base price, not the thinking math.
128
+ - the `throughput` weight reads the route's p50, falling back to the model's
129
+ blended throughput when the route carries no p50 — a sparse route must not
130
+ zero the metric (an absent p50 is unknown, not slow).
131
+ - `maxPriceUsdPerM` caps the pinned route price (the thinking-adjusted price the
132
+ role actually pays), like the blend cap.
133
+ - `roleMetricValue(model, metric, route, cacheHitRate)` is the accessor `rankRole`
134
+ and `explainModel` share: for a pinned role it returns the route's cache-adjusted
135
+ billed price (`routeEffectivePrice`) and `throughput`, and the model's own value
136
+ for every other metric.
137
+ - a model with **no matching route** is ineligible for that role
138
+ (`providerPinDrops` → `Decision.pinBlocked`, rendered `; pin-blocked: …`);
139
+ `explainModel` reports "no route matches the role's provider pin".
140
+ - an **unpinned** role ignores `routes` entirely — the blend path is unchanged.
141
+
142
+ The pin is a cost-and-throughput posture, not a quality edit: it moves the price
143
+ axis to the route's billed price and the throughput axis to the route's p50,
144
+ while every other weighted metric stays the model's own.
145
+
146
+ ## Capability presets (feature flags)
147
+
148
+ One named flag per opt-in capability (`features.<id>` global,
149
+ `roles.<role>.features.<id>` per role) applies the plugin's recommended settings
150
+ to a role. The registry (`FEATURES` in `src/features.ts`) is the single source of
151
+ truth; `expandFeatures(authored, globalFlags)` is the single implementation of
152
+ the precedence rule, called by `resolveSettings` (per role) and by the explorer's
153
+ `/api/rank`/`/api/explain` on the posted def. The rule:
154
+
155
+ ```
156
+ effective def = shipped default ⊕ preset(enabled flags) ⊕ explicit user keys
157
+ ```
158
+
159
+ A flag is on when `roles.<role>.features.<id>` is present (it wins either way)
160
+ and otherwise when the global `features.<id>` is `true`; the preset fills only
161
+ knobs the authored def does not already set, and an explicit key is merged last,
162
+ so it always wins. Every flag ships **off** and no shipped role sets one, so with
163
+ no flags the resolved roles are byte-identical to `DEFAULT_ROLES`. The effective
164
+ def is what `rankRole`/`explainModel` see; the authored def (flags + explicit
165
+ keys) is what the explorer edits and exports.
166
+
167
+ What each recommendation does to the ranking math (grounded on the 2026-10-07
168
+ cache, `default` role, 142 eligible):
169
+
170
+ - **`endpointCeilings`** (`filters.tools: true`, `filters.minOutputTokens:
171
+ 16384`) drops tool-incapable routes and routes whose output ceiling is below
172
+ 16 384 tokens from the priced pool (see *Endpoint filters*): 142 → 132
173
+ eligible, leader unchanged (`deepseek-v4.1-flash`, 0.579 $/M). 16 384 is the
174
+ video's own threshold (19 endpoints cap output at or below it).
175
+ - **`cachePricing`** (`cacheHitRate: 0.5`) reprices the role on a cache-heavy
176
+ loop's invoice (see *Cache-read pricing*): 142 eligible, leader unchanged, its
177
+ `priceEff` 0.579 → 0.508 (−12 %). 0.5 is the low end of the measured
178
+ third-party range (51–69 %), so the preset never over-credits a cache.
179
+ - **`providerPinning`** (`preferOwnProvider: true`) prices a model on its own
180
+ lab's route where one exists, falling back to the default `1/price²` blend
181
+ otherwise. It is a **per-model policy, not a scalar** — the sensible pin
182
+ differs per model, so the bundle carries a policy field evaluated against the
183
+ model's own routes at rank time, not a slug baked into the lock file. The lab
184
+ match is a **slug prefix on the model's `orgId`** (exact, or `orgId` + `/`),
185
+ with the display `org` as a second try; there is no alias map in v1. Measured
186
+ coverage: 51 of the 149 routed models resolve to one of their own lab's routes
187
+ (33 on an exact slug match, 17 on an `orgId` prefix, 1 on the display-`org`
188
+ fallback), and 10 of 25 orgs match for no model (`zai-org` —
189
+ its lab route slug is `z-ai` — `google`, `qwen`, `inclusionai`, `nvidia`,
190
+ `microsoft`, `bytedance`, `thinking-machines`, `ibm`,
191
+ `gryphe`). An unmatched model keeps the blend, so the capability
192
+ **under-delivers rather than mis-pins**, and it **never changes eligibility**
193
+ (unlike the hard `providerPin`, which drops a model with no matching route).
194
+ The resolved route is the same basis a hard pin uses, so the preference moves
195
+ the **throughput** axis as well as the billed price — a role that routes to the
196
+ lab's endpoint gets that endpoint's p50 tok/s, not the blend's. Measured on the
197
+ 2026-10-07 cache, the `default` leader's `priceEff` rises 0.579 → 0.975 while
198
+ its `value` *rises* 0.8132 → 0.8172, because its own lab route's p50 throughput
199
+ is higher than the blend's, so `q` moves too. That is deliberate (the pin path
200
+ has always priced and measured on the pinned route); a role that wants the
201
+ blend's throughput should leave the flag off.
202
+ - **`costCap`** (`filters.maxPriceUsdPerM: 10`) drops the priciest tail of the
203
+ eligible pool: 142 → 135 (the top ≈5 %), leader unchanged. The pool's p90 is
204
+ 7.8 and p95 8.9 $/M, and every role leader sits at 0.58–0.85 $/M, so the
205
+ preset never excludes today's leader.
206
+
207
+ **Interaction with the switch hysteresis.** `switchMargin`/`priceSwitchFraction`
208
+ compare a challenger against the incumbent on `value` and `priceEff`. A flag that
209
+ reprices the **whole field** (`cachePricing`, `providerPinning`) moves **both
210
+ sides** of that comparison, so it can hold a switch that would otherwise happen
211
+ (or vice versa). Flip such a flag **between runs**, not mid-day: the day gate
212
+ stamps one run per UTC day, and a mid-day flag change would compare a
213
+ half-repriced field against a stale incumbent.
82
214
 
83
215
  ## Eligibility
84
216
 
@@ -88,13 +220,40 @@ A model ranks for a role only when:
88
220
  **independent of weights** — the shipped defaults require `throughput`
89
221
  without weighting it, so validation checks `required ⊆ KNOWN_METRICS`, not
90
222
  `required ⊆ weights`);
91
- - it has a billed price (`m.price != null`);
92
- - `filters.image` (if set) is satisfied (`m.multimodal`).
223
+ - it has a billed price (`m.price != null`; a pinned role uses its matching
224
+ route's price — see *Route-aware pricing*);
225
+ - `filters.image` (if set) is satisfied (`m.multimodal`);
226
+ - the role's **endpoint filters** (if any) are satisfied (below);
227
+ - for a **pinned** role, the model has a route matching `providerPin`
228
+ (`pinnedRoute != null`) — a route-less model is ineligible.
93
229
 
94
- `filters.image` is the only filter the engine implements; spec §6.3 also names
95
- `filters.maxPriceUsdPerM` / `filters.minContextTokens` as schema capability, but
96
- `rankRole` does not enforce them and `resolveSettings` does not validate them.
97
- Filters apply before ranking eligibility so percentile norms stay on the
230
+ ### Endpoint filters
231
+
232
+ `RoleDef.filters` carries four endpoint filters beyond `image`:
233
+ `tools` (boolean), `minContextTokens`, `minOutputTokens` and `maxPriceUsdPerM`
234
+ (numbers; `0`/`false` = off). They gate the **eligible standard-tier route pool**
235
+ — the same pool the `1/price²` blend uses — before the blend:
236
+
237
+ - `routePassesEndpointFilters` drops a route failing any declared filter; a
238
+ `null` capability field is **kept** (missing data is not a capability failure);
239
+ - `modelPassesEndpointFilters` requires at least one surviving route — a model
240
+ whose every standard-tier route fails (or that has no route data) is
241
+ ineligible, and the single-find-route fallback does not resurrect it;
242
+ - `minContextTokens` filters the **endpoint** `context_length`, not the
243
+ model-level `context`;
244
+ - `maxPriceUsdPerM` caps the thinking-adjusted `priceEff` (the price the role
245
+ actually pays), not the bare billed blend;
246
+ - a role that declares `tools`/`minContextTokens`/`minOutputTokens` is **priced**
247
+ on the surviving pool: `endpointFilteredModel` recomputes `price`/`throughput`
248
+ as the `1/price²` blend over the routes that clear the filters
249
+ (`blendRoutePool`), so the role is priced on the routes the router would
250
+ actually choose from — not the model's full-route blend. No endpoint filter
251
+ leaves the model's own blend unchanged.
252
+
253
+ `endpointFilterDrops(def, models)` lists the ranking ids the gate removed (they
254
+ pass every other eligibility gate but have no capable route) for the decision
255
+ log. `resolveSettings` validates all four; no shipped role sets an endpoint
256
+ filter. Filters apply before ranking eligibility so percentile norms stay on the
98
257
  unfiltered pool.
99
258
 
100
259
  ## Pareto frontier (★)
@@ -134,6 +293,20 @@ instead of 0, so absence is not a coverage penalty. None of the three may be a
134
293
  fill. Making it per-metric is a one-line change (`CAPABILITY_FILL`).
135
294
  - The report marks a filled value `~` in the model column.
136
295
 
296
+ ### The 0-fill posture
297
+
298
+ `code`, `agents`, `tool_calling`, `math` and `vision` are deliberately
299
+ **0-filled** — a missing value scores 0, not the capability fill. This is a
300
+ cost-conservative choice, not an oversight: capability-filling them at 0.195
301
+ promotes expensive models, because the fill lifts every uncovered model's `q`
302
+ and the expensive models are the ones the source tends to cover. Measured on the
303
+ 2026-10-03 caches, filling these metrics flips `slow` #1 muse-spark-1.3 →
304
+ gpt-6-astra and `plan` #1 hy4-preview → gpt-5.6-sol. The 0-fill keeps the
305
+ ranking on the models the source actually measured; the cost is that a model
306
+ missing a weighted metric is penalized for a coverage gap (see rule 4), which is
307
+ why the defaults weight these metrics at reduced share and never add them to
308
+ `required`.
309
+
137
310
  ## Thinking price factor
138
311
 
139
312
  Roles with a `thinking` level rank on the thinking-adjusted price.
@@ -195,6 +368,136 @@ Naming the archetype's own specialist set is a no-op (the focus share equals its
195
368
  archetype share). A named metric already in the weights is reported as a
196
369
  duplicate; a name outside the known-metric set is unknown.
197
370
 
371
+ **The nine-axis focus-metric gate.** A focus metric that is fill-0 and covers too
372
+ little of the field turns `q` into a coverage score (a missing weighted metric
373
+ contributes 0 while occupying its denominator share). `assessFocusMetric(models,
374
+ metric, declared, source?)` (`src/agent-create.ts`) is the one rule, returning a
375
+ `FocusMetricAssessment` with nine axes, each `{ …, status: "ok" | "below-bar" |
376
+ "unknown" }`:
377
+
378
+ - **coverage** (`FocusCoverageEntry`) — the share of the pool carrying the metric
379
+ (not the imputed fill). Safe when the source is capability-filled (`fill > 0`,
380
+ so absence is a known non-penalty) **or** the share clears
381
+ `FOCUS_COVERAGE_FLOOR` (0.35). This is `focusCoverageOk`, the coverage branch.
382
+ - **dispersion** — IQR/median of the cardinal-normalized covered values (the
383
+ engine's `cardinalMetric` first, so index and 0–1 metrics are comparable),
384
+ below-bar under `FOCUS_DISPERSION_FLOOR` (0.05). A saturated metric cannot
385
+ separate models. Fewer than two covered values, or a zero median, is `unknown`.
386
+ - **composition** — an org whose pool share is ≥ `FOCUS_ORG_MIN_SHARE` (0.1) but
387
+ which carries no covered model is flagged (`omittedOrgs`); a smaller provider's
388
+ absence is not a false positive.
389
+ - **freshness** — the newest covered model's `releaseDate` against the pool's
390
+ newest, below-bar beyond `FOCUS_STALENESS_MONTHS` (1). A missing date on either
391
+ side degrades to `unknown`.
392
+ - **trust** — the self-reported share of the payload's covered entries (the
393
+ per-entry `self_reported` flag, read by `parseBenchmarkPayloadMeta`), below-bar
394
+ above `FOCUS_SELF_REPORTED_MAX_SHARE` (0.5). A mostly vendor-submitted source
395
+ cannot be trusted to rank on. The `source` argument carries the cached trust
396
+ inputs (`cachedSourceInfo`: the source's scores cache for the payload meta, the
397
+ catalog cache for the row); a payload with no entry flags (a declared or
398
+ writing source) or no covered entries is `unknown` — never a silent `ok`.
399
+ `verified` is uniformly `false` upstream today, so it is carried but not gated.
400
+ The axis also carries the catalog row's `is_community` flag as `community` — a
401
+ community-submitted benchmark is visible even when the payload half is absent
402
+ (no per-entry flags); it is an annotation, never a gate (it never changes
403
+ `status` and `belowBarReason` has no community branch).
404
+ - **modality** — the catalog row's `modality` (e.g. `text`/`image`/`audio`/
405
+ `video`/`multimodal`) and the payload's per-entry `multimodal` share, read by
406
+ `parseBenchmarkPayloadMeta` and carried on `BenchmarkCatalogEntry`. This axis
407
+ is an **annotation, never a gate**: there is no role modality field, so it is
408
+ never `below-bar` and never a drop reason (`belowBarReason` has no modality
409
+ branch). It is `unknown` only when neither the catalog row nor the payload
410
+ carries a modality signal.
411
+ - **maintenance** — the catalog row's `updated_at` age in months against
412
+ `FOCUS_DATASET_STALENESS_MONTHS` (12), with `version_count`/`star_count`
413
+ carried as supporting detail. This is the **dataset-date** axis, distinct from
414
+ `freshness` (the **model-date** axis): freshness asks whether the benchmark
415
+ tracks the model field, maintenance asks whether the benchmark *project* is
416
+ alive. A benchmark whose dataset was last touched years ago but whose rows
417
+ happen to include one recent model passes freshness and is caught here. Like
418
+ modality it is an **annotation, never a gate**: `belowBarReason` has no
419
+ maintenance branch, so a stale dataset is warned but never drops a discovered
420
+ candidate. It is `unknown` when the catalog row is absent or its `updated_at`
421
+ is missing/unparseable.
422
+ - **provenance** — the benchmark's owner (the catalog row's `dataset_org_id`,
423
+ falling back to `dataset_slug`) and the source's own per-entry org mix (the
424
+ dominant lab and its share, read by `parseBenchmarkPayloadMeta` from each row's
425
+ `organization_id`, falling back to `provider_id`), below-bar above
426
+ `FOCUS_PROVENANCE_DOMINANT_SHARE` (0.5 — a **chosen, tunable heuristic**, not a
427
+ measured value). This is the **source's own claim** about who publishes the
428
+ benchmark and whose scores it carries: an independent cross-check on the
429
+ pool-derived `composition` axis, **not a replacement for it** (the composition
430
+ rule is unchanged). `compositionAgreement` records whether the two concur:
431
+ `"disagree"` when exactly one of the two flags `below-bar`, `"agree"` when both
432
+ or neither do, `"unknown"` when the payload carries no org mix. Like modality
433
+ and maintenance it is an **annotation, never a gate**: `belowBarReason` has no
434
+ provenance branch, so a vendor-populated source is warned but never drops a
435
+ discovered candidate. It is `unknown` when there is neither an owner nor a
436
+ payload org mix.
437
+ - **cross-source** — the zeroeval per-entry price/throughput/context
438
+ (`BenchmarkPayloadMeta.crossSource`, keyed by the entry's `model_id`) compared
439
+ against the plugin's own `price`/`throughput`/`context`. The join is **direct
440
+ on the bare id** (the generic llm-stats benchmark's `entries[].model_id` is the
441
+ llm-stats id, the same space as `Model.id`); only models present in the map AND
442
+ carrying the metric count toward `compared`. price compares zeroeval's 3:1
443
+ blend `(3·input + output)/4` against `m.price` ($/M), context compares
444
+ `row.context` against `m.context`, and the relative divergence is
445
+ `|a − b| / max(a, b, 1e-9)`; `priceDivergence`/`contextDivergence` are the
446
+ median over the models compared on that axis (`null` when none). An axis is
447
+ `unknown` when nothing was compared on it, else `ok` within
448
+ `PRICE_AGREEMENT_TOLERANCE` (0.5 — a **chosen, tunable heuristic**), else
449
+ `below-bar`. `speedAgreement` is **informational only** and excluded from
450
+ `status`: the units differ (zeroeval `speed_rps` is requests/s, the plugin's
451
+ `throughput` is output tok/s) and re-deriving throughput from `speed_rps` is
452
+ explicitly out of scope. `status` is `unknown` when `compared === 0`, else
453
+ `below-bar` when `priceAgreement` or `contextAgreement` is `below-bar`, else
454
+ `ok`. Like modality, maintenance and provenance it is an **annotation, never a
455
+ gate**: `belowBarReason` has no cross-source branch, and the axis never feeds
456
+ `price`/`weightPrice` or any ranking input — the `1/price²` blend is unchanged.
457
+ (A future option, recorded here only: use zeroeval's list price as a fallback
458
+ fill for a model with no OpenRouter route.)
459
+
460
+ An empty pool (unknown field size) or `covered === 0` (the metric's scores were
461
+ not loaded) reports **all nine axes `unknown`**, coverage included — never a
462
+ silent `ok`. `belowBarReason(assessment)` names the failed axis.
463
+
464
+ **Enforcement.** The pre-fetch catalog gate is count-only (`modelCount >= 3`); the
465
+ create-agent gate then probe-fetches each selected candidate's source
466
+ (`loadBenchmarkScores`), joins it onto the ranking universe, and assesses the
467
+ joined pool. A **discovered** candidate below-bar on any gating axis is dropped
468
+ non-fatally with a reason naming the axis; a **user-named** metric is never
469
+ dropped — it is annotated and warned. The report prints the nine signals and
470
+ warns per below-bar axis; the explorer's `/api/bootstrap` carries
471
+ `focusAssessments` and the SPA renders a `table.focus` (below-bar warned,
472
+ `unknown` muted, never green); the CLI report annotates a role's focus metrics
473
+ (`focusLine`). The genuine hazard is the six raw pass-rate metrics (`gpqa`,
474
+ `aime`, `swe_bench`, `arc_agi`, `terminal_bench`, `tau_bench`), which are fill-0
475
+ and reachable from discovery via the catalog→shipped mapping;
476
+ `writing`/`website`/`bench:<id>` are capability-filled at 0.195 and stay
477
+ weightable at any coverage.
478
+
479
+ **Focus cap.** The focus set is capped at `FOCUS_METRIC_CAP` (3) in priority
480
+ order (named/linked first, then discovery order), so one benchmark keeps a
481
+ decisive share instead of five sharing the budget; the metrics beyond the cap are
482
+ reported as dropped. The cap is applied before the share math, so the kept set
483
+ still receives `clamp(specialistShare, 0.25, 0.40)` split evenly and both
484
+ invariants hold exactly.
485
+
486
+ ### Zeroeval catalog benchmarks — none added
487
+
488
+ No zeroeval catalog benchmark adds an independent axis at usable coverage, so
489
+ none is weighted in a shipped role. Measured over the 400-model ranking field
490
+ (2026-10-03): `mmlu-pro` is the only catalog benchmark that clears the 35%
491
+ `FOCUS_COVERAGE_FLOOR` (142/400 = 35.5%), but it correlates r 0.90–0.92 with
492
+ `general`/`reasoning`; `HLE` is the next best at 26% and does not clear the bar.
493
+ The role-relevant benchmarks are 17–30% coverage and collinear with an existing
494
+ index (`toolathlon` 0.89 with `tool_calling`, `swe-bench-pro` 0.91 with `code`,
495
+ `terminal-bench-2.1` 0.94 with `code`, `mcp-atlas` 0.80 with `agents`,
496
+ `browsecomp` 0.97 with `search`, `deepswe-1.1` 0.80 with `agents`, `lvbench`
497
+ 0.95 with `vision`). The six shipped-but-unweighted raw benchmarks (`gpqa`,
498
+ `aime`, `swe_bench`, `arc_agi`, `terminal_bench`, `tau_bench`) are all redundant
499
+ with an index or 5–6% coverage — do not weight them.
500
+
198
501
  ## Weight design rules
199
502
 
200
503
  The shipped weights (`DEFAULT_ROLES`) and the archetype sets
@@ -217,19 +520,48 @@ The shipped weights (`DEFAULT_ROLES`) and the archetype sets
217
520
  100%-coverage backbone plus the partial-coverage trio at reduced share;
218
521
  `mrcr`/`search` are unweighted and `long_context` is capped at 0.14.
219
522
  `website`/`long_context`/`writing` are capability-filled rather than
220
- 0-filled.
523
+ 0-filled. The nine-axis focus-metric gate (above) is the automated check for
524
+ this rule: it drops a discovered candidate that is below-bar on coverage,
525
+ dispersion, composition, freshness or trust (modality, maintenance,
526
+ provenance and cross-source are annotations, never a drop).
221
527
  5. **Non-collinear differentiation.** The capability indices are one latent
222
- factor (Pearson r over the pool: general↔reasoning 0.99, code↔agents 0.95,
528
+ factor (Pearson r over the pool: general↔reasoning 0.984, code↔agents 0.95,
223
529
  general↔code 0.94), so re-weighting them barely separates roles.
224
530
  Differentiate on the independent axes instead — throughput (r 0.13 with
225
- general), price (r ≈ 0), and the specialist metrics.
531
+ general), price (r ≈ 0), and the specialist metrics. The `general`+`reasoning`
532
+ pair is the extreme case: at r 0.984 they are ~one latent factor, so
533
+ `default`/`slow`/`plan`/`advisor` merge them into a single `general` weight —
534
+ a pure relabel (the combined capability share is unchanged, so no share is
535
+ freed and none is reallocated; the ranking is ≈ unchanged, near-tie
536
+ reorderings only).
226
537
  6. **λ from the intended posture.** `λ = (w_price/(1−w_price))/20` is the
227
538
  quality-per-dollar exchange rate; a price weight whose leader-flip threshold
228
- is 10–30× away is decoration. `plan`/`advisor` carry price 0.12/0.20
229
- (λ 0.00682/0.0125); `tiny` stays at 0.40.
230
- 7. **`website` vs `code` collinearity.** They correlate at r = 0.876, so
231
- `designer` weights `website` 0.18 with `code` trimmed to 0.10 (the old
232
- 0.10/0.18 pair double-counted one capability axis).
539
+ is 10–30× away is decoration. `advisor` carries price 0.20 (λ 0.0125),
540
+ `plan` 0.25 (λ 0.01667); `tiny` stays at 0.40.
541
+ - **Measure the leader-flip threshold on the *reachable* pool** — the pool
542
+ the selector is actually chosen from (candidates the probe walk marks `ok`
543
+ or `unknown`). The full-pool threshold is a stability check only. Measured
544
+ 2026-10-03 (issue #16): `plan`'s reachable threshold was w_price 0.2175
545
+ (Hy4 preview → DeepSeek-V4.1-Flash) against a full-pool 0.311 (Muse Spark
546
+ 1.3 → Qwen3.8 Flash), so price 0.25 flips the actionable pick while the
547
+ full-pool #1 is unchanged.
548
+ - **Degenerate case.** When the value leader is also the quality leader the
549
+ formula `(q_QL − q_VL)/(p_QL − p_VL)` is 0/0 — *undefined*, not zero, and
550
+ it does **not** mean "no price weight dethrones the leader". The leader
551
+ still flips at `min` over the cheaper challengers X of
552
+ `(q_leader − q_X)/(pEff_leader − pEff_X)`.
553
+ - **Coverage caveat.** A reachable flip can land on a model whose weighted
554
+ metric is *imputed*, not measured. `long_context` covers 39% of the `plan`
555
+ pool (56/142), and DeepSeek-V4.1-Flash's `index_long_context` is null —
556
+ llm-stats reads that as insufficient evidence, not zero — so it scores the
557
+ 0.195 capability fill against Hy4 preview's measured 18.9. Read the flip as
558
+ partly a data-coverage artefact.
559
+ 7. **`website` vs `code` collinearity.** They correlate at r = 0.890 over the
560
+ covered pool, so `designer` weights `website` 0.18 with `code` trimmed to
561
+ 0.06 (the old 0.10/0.18 pair double-counted one capability axis). The freed
562
+ 0.04 goes to the independent axes (`price` +0.02, `throughput` +0.02) per
563
+ rule 5, giving `general` 0.26, `vision` 0.18, `throughput` 0.15, `price`
564
+ 0.17, `website` 0.18, `code` 0.06.
233
565
 
234
566
  ## Value-review findings (2026-09-30)
235
567
 
@@ -250,6 +582,431 @@ one." Three findings, each measured on that day's caches:
250
582
  GLM-5.3 → DeepSeek −0.004 q for 4.6× less money; `vision` −0.041 for 18×;
251
583
  `designer` −0.010 for 26×; `task` +0.007 for 4.6×).
252
584
 
585
+ ## Value-review findings (2026-10-03)
586
+
587
+ Three measured defects in the shipped weights, each fixed in issue #13 (all
588
+ numbers from the 2026-10-03 caches: 400 models, 151 throughput-matched, 150
589
+ priced):
590
+
591
+ - **`math` is collinear and sparse in `plan`/`slow`.** Over the eligible pool
592
+ `math` correlates with `reasoning` at r 0.919 and is missing for 18% of the
593
+ pool, so a model without `math` data loses up to 0.090 `q` for `plan` (share
594
+ 0.156) and 0.049 for `slow` (share 0.084) — a coverage penalty, not a quality
595
+ signal. `advisor` had already dropped `math` for this reason (2026-09-30);
596
+ `plan`/`slow` were left behind. Fix: drop `math` from both and move its share
597
+ to the consolidated `general` axis (capability-preserving, 100% coverage) —
598
+ not `throughput`, which would contradict `slow`'s "speed as tiebreakers"
599
+ description and flip both leaders to a cheap/fast model. `plan`'s description
600
+ drops `math` in the same change.
601
+ - **`smol`/`commit` price weight does not bind.** Both are described as
602
+ "cheap", but the value leader is also the quality leader, so the price term is
603
+ not what picks #1. The leader-flip threshold is λ* 0.0367 (`smol`, w_price*
604
+ 0.423) and 0.0402 (`commit`, w_price* 0.446) against the current λ 0.0214
605
+ (w_price 0.30) and 0.0286 (w_price 0.35). Fix: raise both `w_price` to 0.45
606
+ (the measured binding threshold), rescaling the non-price weights
607
+ proportionally — a posture change, not a capability change.
608
+ - **The capability weights are ~one latent factor.** `general`~`reasoning` r
609
+ 0.984 over the eligible pool; `default`/`slow`/`plan`/`advisor` each spend
610
+ 55–74% of their non-price budget on that pair. The split buys almost nothing —
611
+ merging leaves `default`'s top-3 identical and only swaps #4/#5. Fix: merge
612
+ `general`+`reasoning` into `general` in those four roles as a pure relabel (no
613
+ reallocation).
614
+
615
+ Measured effect: `slow` #1 glm-5.3 → muse-spark-1.3, `plan` #1 hy4-preview →
616
+ muse-spark-1.3, `smol`/`commit` #1 muse-spark-1.1 → deepseek-v4.1-flash;
617
+ `default`/`advisor` keep their #1 (near-tie reorderings only).
618
+
619
+ ## Value-review findings (2026-10-03, issue #16 — `plan` cost posture)
620
+
621
+ `plan` did not honour the stated "cheapest model that can do the task" stance:
622
+ its price weight (0.12, λ 0.00682) was decoration on the pool the selector is
623
+ actually chosen from.
624
+
625
+ - **The reachable pool, not the full pool, is the decision pool.** The shipped
626
+ `plan` pick was Hy4 preview ($2.32/M); DeepSeek-V4.1-Flash ($0.43/M, 5.4×
627
+ cheaper) is reachable on this account and lost by 0.013 value. Hy4's whole
628
+ edge is `long_context` (measured 18.9 vs DeepSeek's null → capability fill
629
+ 0.195); DeepSeek leads the measured axes (`general` 51.2 vs 50.4, 81 vs 36
630
+ tok/s). Fix: raise `plan`'s `price` 0.12 → 0.25 (λ 0.01667), rescaling the
631
+ non-price weights by 0.852273 — `general` 0.6, `long_context` 0.116676,
632
+ `throughput` 0.033324 (Σ(non-price) = 0.75 = 1 − price; `q` unchanged).
633
+ Measured reachable flip 0.2175, full-pool 0.311, so 0.25 flips the actionable
634
+ pick (Hy4 → DeepSeek-V4.1-Flash) and leaves the full-pool #1 (Muse Spark 1.3)
635
+ in place. `long_context` stays weighted — it is the only non-collinear
636
+ capability axis in `plan`; dropping it would collapse the role onto `general`.
637
+ - **The flip rides on imputed data.** 5 of the 7 reachable `plan` models are
638
+ `long_context`-imputed; the metric covers 39% of the pool. If the account's
639
+ provider whitelist changes, `qwen3.8-flash` (same $0.43/M, higher q) strictly
640
+ dominates DeepSeek and takes the reachable #1 with no weight edit.
641
+
642
+ ## Domain-knowledge axis: measured, not landed (2026-10-05, issue #23)
643
+
644
+ The llm-stats `index_finance`/`index_legal`/`index_healthcare` scores were
645
+ evaluated as a domain-knowledge axis and **not landed** — no role weights them.
646
+ Measured over the 2026-10-05 400-model field (clean HEAD `a201a3b`, throwaway
647
+ worktree):
648
+
649
+ - **Coverage**: `index_finance` 228/400 (57.0%), `index_legal` 213/400 (53.3%),
650
+ `index_healthcare` 248/400 (62.0%), `general` 377/400 (94.3%).
651
+ - **Correlation with `general`** (full covered pool): finance 0.6981, legal
652
+ 0.6632, healthcare 0.8449. Among the domains: finance↔legal 0.9529,
653
+ finance↔healthcare 0.9417, legal↔healthcare 0.9464 — one latent factor.
654
+ - **On the eligible covered pool** (domain + `general` + price + throughput):
655
+ finance 0.408, legal 0.388, healthcare 0.797.
656
+ - **0-fill leader flip** (`default`, share taken from `code`+`agents`): full pool
657
+ flips at 0.03–0.06 (DeepSeek-V4.1-Flash $0.24 → Muse Spark 1.3 $2.00 /
658
+ GPT-5.6 Sol $6.13); reachable pool (73 models) at 0.04–0.17; covered pool at
659
+ 0.17–0.20.
660
+ - **Capability-fill measurement** (real fill branch): no leader flip on the full
661
+ pool at shares 0.05–0.20; on the reachable pool it flips at 0.05
662
+ (finance/legal) and 0.20 (healthcare), always to the **cheaper** model.
663
+
664
+ **Verdict: do not land.** r with `general` is 0.66–0.84, the 0-fill is a coverage
665
+ lottery (the cheap leader has no domain data), and the capability fill still
666
+ flips the leader on the reachable pool — neither posture is stable.
667
+ Counter-evidence for the record: on the eligible covered pool finance/legal
668
+ correlate only 0.39–0.41 with `general`, so the metric does carry independent
669
+ signal mid-ranking; it just does not move the leader at a plausible share.
670
+
671
+ ## Truthfulness axis: measured, not landed (2026-10-06, issue #24)
672
+
673
+ The llm-stats `simpleqa_score` was evaluated as a truthfulness axis and **not
674
+ landed** — no role weights it. Measured over the 2026-10-06 400-model field:
675
+
676
+ - **Coverage**: `simpleqa_score` 47/400 (11.8%); default-eligible 18/142 (12.7%);
677
+ catalog-resolvable 16/137 (11.7%); allowed-keyed 7/65 (10.8%); probe-walk
678
+ reachable **0/3**.
679
+ - **Correlation with `general`**: full pool 0.68, eligible 0.75, catalog 0.70,
680
+ allowed 0.89. `hle_score` correlates 0.88 with `general` (collinear), and
681
+ `simpleqa_score` vs `hle_score` is 0.35 — the two truthfulness metrics are not
682
+ interchangeable.
683
+ - **0-fill leader flip** (`default`): the full pool flips at share 0.21 to
684
+ `gemini-3-flash-preview` ($1.125/M vs the leader's $0.598/M — 1.9× more
685
+ expensive); the capability fill flips to the pricier model too. The
686
+ allowed-pool top-10 **all** have `simpleqa_score = null`.
687
+
688
+ **Verdict: do not land.** The 0-fill is a pure coverage lottery for exactly the
689
+ models the selector chooses from (the reachable pool has no covered model at
690
+ all), and the capability fill reproduces the cost-promotion failure mode the
691
+ 0-fill posture exists to avoid.
692
+
693
+ ## Guardrail/alignment axis: measured, not landed (2026-10-06, issue #31)
694
+
695
+ The catalog's guardrail/alignment entries were evaluated as a "completes the
696
+ objective without tripping guardrails" axis and **not landed** — no role weights
697
+ one. Measured over the 2026-10-06 400-model field (clean HEAD `01fd7f2`,
698
+ throwaway worktree, `loadRankData({ roles: {} })` + `loadBenchmarkCatalog(false)`):
699
+ the catalog carries 745 entries; 57 match a guardrail/alignment keyword
700
+ (`guardrail`, `alignment`, `safety`, `attack`, `jailbreak`, `harm`, `refusal`,
701
+ `automationbench`, `agentdojo`, `siren`, `trust`, `toxicity`, `bias`) in
702
+ id/name/description/categories; 30 of those clear the pre-fetch count gate
703
+ (`modelCount >= 3`).
704
+
705
+ - **The semantically-relevant entries are at one model.** The pair the issue
706
+ names — `siren-agentdojo-attack-success` and `siren-agentdojo-utility` — and
707
+ `automationbench-aa` each carry `modelCount = 1`, below the count gate, so none
708
+ is even probe-fetchable. The rest of the `safety` category is the same:
709
+ `air-bench`, `wmdp`, `wmdp-bio`, `wmdp-chem`, `wmdp-cyber`, `cyberseceval-4`,
710
+ `cve-bench`, `cwe-bench`, `cathedralbench`, `gray-swan-ipi`, `mask`,
711
+ `miabench`, `biolp-bench`, `cloningscenarios`, `protocolqa`,
712
+ `protocolqa-open-ended`, `google-real-world-vulnerability-discovery`,
713
+ `ci-memories-coverage`, `ci-memories-violation` are all 1 model; `cybench`,
714
+ `mimo-cyber-bench`, `vct` are 2. Nothing in the catalog measures
715
+ guardrail-compliance at usable coverage.
716
+ - **Coverage**: the best-covered candidate is 20/400 = **5.0%**
717
+ (`automationbench`, `cybergym`, `winogrande`; the llm-stats per-benchmark
718
+ endpoint caps `entries` at 20, so `automationbench` 22 and `winogrande` 23 load
719
+ only 20). Every other candidate is 0.7–4.0%. The 35% `FOCUS_COVERAGE_FLOOR` is
720
+ 7× away from the best candidate and ~50× from the median. `general` covers
721
+ 377/400 (94.3%) for scale. (The generic `bench:<id>` source is
722
+ capability-filled at 0.195, so `assessFocusMetric`'s coverage axis reads `ok`
723
+ for all of them — the raw share is the binding number here.)
724
+ - **Correlation with `general`** (covered pool): the safety/security entries are
725
+ collinear — `capture-the-flag-challenges` 1.00, `figqa` 1.00, `exploitbench`
726
+ 0.97, `sec-bench-pro` 0.97, `cybersecurity-ctfs` 0.97, `alignbench` 0.97,
727
+ `attaq` 0.93, `internal-research-debugging-evaluation` 0.92, `xstest` 0.91,
728
+ `exploitgym` 0.89. The only low-correlation candidate is `automationbench`
729
+ (r 0.24 with `general`, 0.30 with `agents`, 0.61 with `tool_calling`) — but it
730
+ is an agentic-automation benchmark (categories `reasoning|agents|tool_calling`),
731
+ not a guardrail axis, and it covers 5.0%.
732
+ - **0-fill leader flip** (`default`, share taken from the collinear `code`+`agents`
733
+ block = 0.2842; baseline leader `deepseek-v4.1-flash` $0.599/M): most of the
734
+ flips that happen go to **pricier** models — `capture-the-flag-challenges` 0.04 →
735
+ `gpt-5.6-sol` ($6.13/M, 10.2×), `exploitbench` 0.04 → `glm-5.3` ($2.47/M,
736
+ 4.1×), `internal-research-debugging-evaluation` 0.06 → `gpt-5.6-sol` (10.2×),
737
+ `cybersecurity-ctfs` 0.21 → `gpt-5.3-codex` ($4.81/M, 8.0×), `exploitgym` 0.22
738
+ → `gpt-5.6-sol` (10.2×), `cybergym` 0.30 → `mimo-v2.6-pro` ($0.543/M, cheaper),
739
+ `alignbench` 0.32 → `qwen3-14b` ($0.165/M, cheaper), `voicebench-avg` 0.14 →
740
+ `inkling-small` ($0.638/M, ≈same), `winogrande` 0.20 → `mimo-v2.5-pro`
741
+ ($0.622/M, ≈same). The capability-fill posture (the real posture for
742
+ `bench:<id>`) moves the thresholds only slightly (`exploitgym` 0.16,
743
+ `cybersecurity-ctfs` 0.26, `alignbench` 0.37) and never changes the direction.
744
+ `automationbench`, `attaq`, `xstest`, `pope`, `figqa`, `sec-bench-pro`,
745
+ `crag`, `big-bench`, `vqav2` do not flip the leader at any share up to 0.40 —
746
+ they are the least-covered entries.
747
+
748
+ **Verdict: do not land.** No candidate clears the coverage bar (best 5.0% vs the
749
+ 35% floor), the semantically-relevant guardrail entries are at one model, and the
750
+ safety/security entries that do carry data are collinear with `general`
751
+ (r 0.89–1.00). The one low-correlation candidate (`automationbench`, r 0.24) is
752
+ an agentic-automation benchmark, not a guardrail axis, and still covers only 5.0%.
753
+ The 0-fill is a cost-promotion lottery: most of the flips that happen land on
754
+ models 3–10× more expensive than the leader. No declarative source is authored —
755
+ the catalog is the only fetchable guardrail-adjacent data and it is too sparse.
756
+
757
+ ### Candidate table (modelCount ≥ 3, 2026-10-06 field)
758
+
759
+ `flip0` = 0-fill leader-flip share; `flipCap` = capability-fill (0.195) share;
760
+ `none` = no flip up to 0.40. `rAgents` is `n/a` when fewer than 3 covered models
761
+ carry `agents`.
762
+
763
+ | catalog id | metric | modelCount | covered/400 | r(general) | r(agents) | dispersion | flip0 | flipCap |
764
+ |---|---|---|---|---|---|---|---|---|
765
+ | automationbench | bench:automationbench | 22 (20 loaded) | 20 (5.0%) | 0.24 | 0.30 | 0.62 | none | none |
766
+ | cybergym | bench:cybergym | 20 | 20 (5.0%) | 0.48 | 0.75 | 0.14 | 0.30 → mimo-v2.6-pro | 0.30 → mimo-v2.6-pro |
767
+ | winogrande | bench:winogrande | 23 (20 loaded) | 20 (5.0%) | 0.60 | n/a | 0.13 | 0.20 → mimo-v2.5-pro | 0.24 → mimo-v2.5-pro |
768
+ | global-mmlu-lite | bench:global-mmlu-lite | 16 | 16 (4.0%) | 0.88 | n/a | 0.33 | 0.15 → inkling-small | 0.18 → inkling-small |
769
+ | healthbench-professional | bench:healthbench-professional | 14 | 14 (3.5%) | 0.95 | 0.93 | 0.14 | 0.06 → gpt-5.6-sol | 0.09 → gpt-5.6-sol |
770
+ | healthbench | bench:healthbench | 13 | 13 (3.3%) | 0.70 | 0.84 | 0.08 | 0.07 → gpt-5.6-sol | 0.10 → gpt-5.6-sol |
771
+ | healthbench-hard | bench:healthbench-hard | 11 | 11 (2.8%) | 0.66 | 0.61 | 0.46 | 0.12 → gpt-5.6-sol | 0.30 → gpt-6.1-sol |
772
+ | exploitbench | bench:exploitbench | 8 | 8 (2.0%) | 0.97 | 0.95 | 0.57 | 0.04 → glm-5.3 | 0.06 → glm-5.3 |
773
+ | exploitgym | bench:exploitgym | 8 | 8 (2.0%) | 0.89 | 0.84 | 0.69 | 0.22 → gpt-5.6-sol | 0.16 → muse-spark-1.3 |
774
+ | sec-bench-pro | bench:sec-bench-pro | 7 | 7 (1.8%) | 0.97 | 0.96 | 0.25 | none | none |
775
+ | global-mmlu | bench:global-mmlu | 7 | 7 (1.8%) | 0.98 | 1.00 | 0.50 | 0.20 → mimo-v2.5-pro | 0.25 → mimo-v2.5-pro |
776
+ | alignbench | bench:alignbench | 5 | 5 (1.3%) | 0.97 | n/a | 0.10 | 0.32 → qwen3-14b | 0.37 → qwen3-14b |
777
+ | bfcl-v2 | bench:bfcl-v2 | 5 | 5 (1.3%) | 0.74 | n/a | 0.10 | 0.34 → llama-3.3-70b-instruct | 0.40 → llama-3.3-70b-instruct |
778
+ | healthbench-consensus | bench:healthbench-consensus | 5 | 5 (1.3%) | 0.79 | 0.55 | 0.004 | 0.04 → gpt-5.6-sol | 0.05 → gpt-5.6-sol |
779
+ | xstest | bench:xstest | 4 | 4 (1.0%) | 0.91 | n/a | 0.035 | none | none |
780
+ | tempcompass | bench:tempcompass | 4 | 4 (1.0%) | 1.00 | 0.74 | 0.13 | 0.28 → seed-2.0-mini | 0.34 → seed-2.0-mini |
781
+ | vlmsarebiased | bench:vlmsarebiased | 4 | 4 (1.0%) | 0.89 | 0.84 | 0.17 | 0.36 → seed-2.0-mini | none |
782
+ | attaq | bench:attaq | 3 | 3 (0.7%) | 0.93 | n/a | 0.014 | none | none |
783
+ | pope | bench:pope | 3 | 3 (0.7%) | 0.42 | n/a | 0.018 | none | none |
784
+ | capture-the-flag-challenges | bench:capture-the-flag-challenges | 3 | 3 (0.7%) | 1.00 | 1.00 | 0.06 | 0.04 → gpt-5.6-sol | 0.05 → gpt-5.6-sol |
785
+ | cybersecurity-ctfs | bench:cybersecurity-ctfs | 3 | 3 (0.7%) | 0.97 | n/a | 0.52 | 0.21 → gpt-5.3-codex | 0.26 → gpt-5.3-codex |
786
+ | figqa | bench:figqa | 3 | 3 (0.7%) | 1.00 | n/a | 0.35 | none | none |
787
+ | internal-research-debugging-evaluation | bench:internal-research-debugging-evaluation | 3 | 3 (0.7%) | 0.92 | 0.95 | 0.13 | 0.06 → gpt-5.6-sol | 0.08 → gpt-5.6-sol |
788
+ | voicebench-avg | bench:voicebench-avg | 3 | 3 (0.7%) | 1.00 | n/a | 0.10 | 0.14 → inkling-small | 0.18 → inkling-small |
789
+ | vqav2 | bench:vqav2 | 3 | 3 (0.7%) | 0.76 | n/a | 0.018 | none | none |
790
+ | big-bench | bench:big-bench | 3 | 3 (0.7%) | 0.39 | n/a | 0.045 | none | none |
791
+ | crag | bench:crag | 3 | 3 (0.7%) | 0.90 | n/a | 0.08 | none | none |
792
+
793
+ Not loadable (404 on the per-benchmark endpoint): `alpacaeval-2.0` (4),
794
+ `automationbench-1.0.6` (5), `vqav2-(val)` (3). Below the count gate (1–2 models):
795
+ the `siren-agentdojo-*` pair, `automationbench-aa`, and the rest of the `safety`
796
+ category listed above.
797
+
798
+ ## Per-task cost composite: measured, not landed (2026-10-06, issue #32)
799
+
800
+ Issue #32 asked to rank on a per-task cost/throughput composite **when a per-task
801
+ token profile is available**, keeping the current $/M price as the fallback, and
802
+ to quantify the gap and defer when no profile source exists. **Verdict: defer.**
803
+ No per-task (or per-role) token profile exists, so the $/M price stays the price
804
+ axis. The closest available source — OpenRouter's per-model analytics, which the
805
+ plugin already downloads and ignores — is a *platform-wide, per-request* profile,
806
+ not the role's workload; using it would price OpenRouter's traffic mix, not the
807
+ plugin's.
808
+
809
+ ### What the $/M proxy assumes
810
+
811
+ `priceEff` is the billed 3:1 blend `(3·p_in + p_out)/4` scaled by the role's
812
+ thinking factor. A per-task price is `tokens_per_task × priceEff`, so the proxy
813
+ makes two assumptions the composite would relax:
814
+
815
+ 1. **A fixed 3:1 input:output token mix** (output = 25% of the mix). Measured on
816
+ 6,813 real assistant turns in this repo's sessions: **5.74:1** (output =
817
+ 14.8%). The blend over-weights the output price by ~1.7×. Per-model
818
+ `p_out/p_in` spans 0.8–800 (median 4.19) over the 448 OpenRouter models with
819
+ both prices, so the correction is a per-model price rescale of up to 1.72×.
820
+ 2. **A model-independent token count per task.** If `tokens_per_task` is constant
821
+ across models, the per-task price is a constant × `priceEff` — a pure λ
822
+ rescale with no ranking change. The composite only bites when
823
+ `tokens_per_task` varies by model (a verbose/retrying model costs more per
824
+ task).
825
+
826
+ ### Source 1 — a published per-task metric: none
827
+
828
+ - **llm-stats leaderboard** (`LlmStatsRow`, 56 fields in the 2026-10-06 cache):
829
+ `input_price`/`output_price` ($/M), `throughput` (output tok/s), `latency`
830
+ (TTFT), the index/benchmark scores, `params`, `training_tokens`. No
831
+ token-usage, cost-per-task or output-tokens-per-task field. `training_tokens`
832
+ is a pretraining count, not per-task usage.
833
+ - **Benchmark catalog** (`loadBenchmarkCatalog`, 745 entries): every entry is
834
+ `{id, name, description, categories, modelCount}`. No token/cost field; the
835
+ descriptions mention "token" only as context length.
836
+
837
+ ### Source 2 — omp's recorded per-role usage: auxiliary-only
838
+
839
+ omp's session jsonl carries a dedicated `model_usage` event with a `role` tag
840
+ (`{type:"model_usage", purpose, role, api, provider, model, usage:{input, output,
841
+ cacheRead, cacheWrite, totalTokens, cost}}`). Across all 433 session files it
842
+ holds **2,833 records, 2 roles, 2 models** — `typesafe` (2,832; the judge role)
843
+ and `tiny` (1) — and every record is an *auxiliary* call: `judge_batch` 2,235,
844
+ `unexpected-stop` 255, `auto-thinking` 218, `find` 122, `judge` 3. The main
845
+ assistant turn's usage sits on the `message` record with `model` but **no role
846
+ tag** (6,813 turns, 7 models, 36.9M input / 6.4M output). So omp records per-role
847
+ usage only for harness-internal calls, never for the role's own work, and never
848
+ per task. The plugin's own `llm-role-history.jsonl` records per-role *decisions*
849
+ (`role, from, to, reason, score, bestScore, currentScore`) with no token fields.
850
+
851
+ ### Source 3 — OpenRouter analytics: a per-model profile, but not per-task/per-role
852
+
853
+ The OpenRouter find payload the plugin already fetches
854
+ (`openrouter.ai/api/frontend/v1/models/find`, cached as
855
+ `openrouter-fetched-data.json`) carries `data.analytics`: per permaslug+variant,
856
+ `count` (requests), `total_prompt_tokens`, `total_completion_tokens`,
857
+ `total_native_tokens_reasoning`, `total_native_tokens_cached`, `total_usage` ($),
858
+ `total_tool_calls`, `date`. `src/` reads none of it (`grep analytics src/` → no
859
+ hits). It is a real per-model token+cost profile, but it does not satisfy the
860
+ issue's requirement:
861
+
862
+ - **Platform-wide, not the role's workload.** The plugin's own measured
863
+ input/req is **~10–14× lower** than the platform's prompt/req for the same
864
+ models (deepseek-v4.1-flash 4,451 vs 57,018; glm-5.3 4,533 vs 63,434;
865
+ mimo-v2.6-pro 7,556 vs 80,780; hy4-preview 9,302 vs 113,793). The platform's
866
+ prompt/req reflects long-context agentic traffic, not omp's turns. Output/req
867
+ is closer (deepseek 955 vs 926; glm-5.3-flash 906 vs 931), so the *verbosity*
868
+ signal is usable but the *workload* signal is not.
869
+ - **Per-request, not per-task.** `count` is API calls; an agent task spans many.
870
+ - **Single-day snapshot** (576/625 entries dated 2026-09-29), so it is
871
+ stale-prone.
872
+ - **152/400 coverage** (standard variant, `count > 0`); the other 248 models
873
+ would fall back to $/M, giving a mixed axis.
874
+ - **Not per-role.** The issue allows "per role, or per model × role"; this is
875
+ per-model only.
876
+
877
+ ### Measured effect (if the analytics were used as the profile)
878
+
879
+ Re-ranking the 2026-10-06 field with `priceEff × (tokens_per_task / median)`,
880
+ uncovered models at the $/M fallback (t = 1):
881
+
882
+ | role | current #1 | composite #1 | change |
883
+ |---|---|---|---|
884
+ | `default` | deepseek-v4.1-flash | deepseek-v4.1-flash | same |
885
+ | `plan` | muse-spark-1.3 | qwen3.8-flash | changed |
886
+ | `slow` | muse-spark-1.3 | deepseek-v4.1-flash | changed |
887
+ | `smol` | muse-spark-1.1 | mercury-2 | changed |
888
+
889
+ The output-only variant (completion+reasoning per request, the model-controlled
890
+ part) changes the same three roles. So the composite is not inert — but the
891
+ change is driven by the platform's workload mix (tokens/req spans 437–114,810,
892
+ 262×), not by the role's task. The token-mix correction alone (3:1 → 5.74:1) does
893
+ **not** change any leader (only a #5 swap in `default`).
894
+
895
+ ### Verdict
896
+
897
+ **Do not land.** No per-task token profile exists: the published sources carry no
898
+ per-task metric, omp's per-role usage is auxiliary-only (2 roles, 2 models), and
899
+ the one per-model profile the plugin already has (OpenRouter analytics) is
900
+ platform-wide aggregate traffic whose prompt/req is ~10–14× the plugin's own
901
+ measured input/req. Landing it would price OpenRouter's workload mix, not the
902
+ role's, and would change 3 of 4 leaders on that basis. The $/M proxy remains the
903
+ price axis.
904
+
905
+ **What would change the verdict:** a per-role (or per-model × role) token count
906
+ from the plugin's own recorded usage. The plugin already sees the main-turn usage
907
+ per model (session jsonl) and the role→model mapping (config.yml); recording the
908
+ role alongside the turn's usage would give a per-role profile the composite could
909
+ use without the platform-workload confound. Until then this is a measurement, not
910
+ a weight change.
911
+
912
+ ## Agentic multi-agent axes: measured, not landed (2026-10-06, issue #33)
913
+
914
+ The named multi-agent behaviours — delegation, small agent teams (SATs), agent
915
+ handoffs, agent swarms, failure recovery / self-healing, agent-to-agent
916
+ communication — were searched for in the llm-stats benchmark catalog and
917
+ **not landed** — no role weights an axis for them, because no source measures
918
+ them.
919
+
920
+ **Sweep.** The catalog (745 entries) was swept on `id`/`name`/`description`/
921
+ `categories` for `delegat`, `swarm`, `handoff`, `recover`, `self-heal`, `a2a`,
922
+ `agent-to-agent`, `negotiat`, `multi-agent`, `team`, `coordination`,
923
+ `orchestrat`, `collaborat`, `subagent`. **Zero hits** for delegat / swarm /
924
+ handoff / recover / self-heal / a2a / agent-to-agent / negotiat / subagent. The
925
+ 17 hits that did match are:
926
+
927
+ | catalog id | models | matched | what it actually measures |
928
+ |---|---|---|---|
929
+ | `supergpqa` | 37 | collaborat | graduate QA; "Human-LLM **collaborative** filtering" is annotation methodology |
930
+ | `mcp-atlas` | 36 | coordinat | **single-agent** tool use ("coordinate and utilize multiple tools") |
931
+ | `tau2-telecom` | 36 | coordination | dual-control **human↔agent** conversation (Dec-POMDP) |
932
+ | `hmmt-2025` | 33 | team | math competition **team rounds** |
933
+ | `multichallenge` | 32 | collaborat | multi-turn conversation (instruction retention, self-coherence) |
934
+ | `hmmt25` | 28 | team | math competition team rounds |
935
+ | `tau2-airline` | 24 | coordination | dual-control human↔agent conversation |
936
+ | `automationbench` | 22 | orchestrat | **single-agent** tool orchestration |
937
+ | `big-bench` | 3 | collaborat | "**collaborative** benchmark" = many contributors |
938
+ | `phibench` | 3 | team | Microsoft internal math/coding benchmark |
939
+ | `acebench` | 2 | multi-agent | the **only** multi-agent eval (an "Agent" sub-type); 2 models |
940
+ | `mcp-universe` | 1 | orchestrat | single-agent MCP tool orchestration |
941
+ | `cathedralbench` | 1 | team | red-team cyber |
942
+ | `groundui-1k` | 2 | coordinat | UI grounding |
943
+ | `swe-bench-verified-(agentic-coding)` | 2 | coordinat | single-agent coding |
944
+ | `swe-bench-verified-(agentless)` | 2 | coordinat | single-agent coding |
945
+ | `swe-bench-verified-(multiple-attempts)` | 1 | coordination | single-agent coding |
946
+
947
+ A broader sweep of the agentic vocabulary (`agent`, `tool`, `plan`, `memory`,
948
+ `workflow`, `long-horizon`, `computer-use`, `mcp`, `environment`, `simulat`,
949
+ `theory of mind`, `cooperat`, `adversar`, …) returns ~40 agentic benchmarks
950
+ (`toolathlon` 42, `terminal-bench-2` 53, `agents-last-exam` 21, `t2-bench` 23,
951
+ `nl2repo` 23, `osworld-2.0` 15, `claw-eval` 14, `tau3-banking` 12, `mcp-mark` 9,
952
+ `coworkbench` 6, `vending-bench-2` 4, …). **Every one is single-agent**
953
+ long-horizon / tool-use / computer-use; none scores delegation, teams, handoffs,
954
+ swarms, recovery or a2a. The nearest thing to a multi-agent axis in the whole
955
+ catalog is `acebench`'s "Agent (multi-agent interactions)" sub-type, at 2 models.
956
+
957
+ **Four-axis gate on the candidates with ≥ 3 models** (metric = `bench:<id>`,
958
+ generic llm-stats source, joined direct on `model_id`):
959
+
960
+ | candidate | catalog | loaded | covered | dispersion | composition | freshness | r(general) | r(agents) | gate |
961
+ |---|---|---|---|---|---|---|---|---|---|
962
+ | `supergpqa` | 37 | 20 | 20/400 (5.0%) | 0.062 | below-bar (Google, OpenAI) | 3.2 mo | 0.867 | 0.765 | reject |
963
+ | `mcp-atlas` | 36 | 20 | 20/400 (5.0%) | 0.092 | ok | 1.1 mo | 0.607 | 0.695 | reject |
964
+ | `tau2-telecom` | 36 | 20 | 20/400 (5.0%) | 0.061 | below-bar (Qwen, Google) | 4.3 mo | 0.735 | 0.429 | reject |
965
+ | `hmmt-2025` | 33 | 20 | 20/400 (5.0%) | 0.048 | below-bar (Google) | 3.4 mo | 0.615 | 0.199 | reject (dispersion) |
966
+ | `multichallenge` | 32 | 20 | 20/400 (5.0%) | 0.194 | below-bar (Google) | 3.8 mo | 0.355 | 0.326 | reject |
967
+ | `hmmt25` | 28 | 20 | 20/400 (5.0%) | 0.147 | below-bar (Google, OpenAI) | 1.1 mo | **0.909** | 0.712 | reject (collinear) |
968
+ | `tau2-airline` | 24 | 20 | 20/400 (5.0%) | 0.149 | below-bar (Google) | 5.7 mo | 0.694 | 0.439 | reject |
969
+ | `automationbench` | 22 | 20 | 20/400 (5.0%) | 0.622 | ok | 0.0 mo | 0.242 | 0.299 | **clears all axes** |
970
+ | `big-bench` | 3 | 3 | 3/400 (0.8%) | 0.045 | below-bar | 27.1 mo | 0.389 | — | reject |
971
+ | `phibench` | 3 | 3 | 3/400 (0.8%) | 0.128 | below-bar | 17.0 mo | 0.993 | — | reject |
972
+ | `acebench` | 2 | 2 | 2/400 (0.5%) | 0.000 | below-bar | 12.8 mo | — | — | reject (below 3-model floor) |
973
+ | `mcp-universe` | 1 | 1 | 1/400 (0.3%) | — | below-bar | 9.9 mo | — | — | reject (below 3-model floor) |
974
+
975
+ `automationbench` is the only candidate that clears the implemented nine-axis
976
+ gate (dispersion 0.62, composition ok, freshness ok, r(general) 0.24,
977
+ r(agents) 0.30) — but it measures **single-agent tool orchestration**, not
978
+ delegation / teams / handoffs / swarms / recovery / a2a, and it covers 5% of the
979
+ field. Landing it under an agentic-axis label would be mislabeling, and the
980
+ issue forbids a stub or synthetic axis.
981
+
982
+ **Coverage ceiling (structural).** The per-benchmark endpoint caps `entries` at
983
+ `BENCHMARK_ENTRY_CAP = 20` regardless of `limit`/`offset`/`page`/`per_page`, so
984
+ a generic `bench:<id>` metric can never cover more than 20 of the 400 models —
985
+ 5.0%, against a `FOCUS_COVERAGE_FLOOR` of 0.35 (≥ 140 models). The best-covered
986
+ agentic candidate in the catalog (`toolathlon`, 42) would still load ≤ 20. The
987
+ top of the catalog by `model_count` is general-knowledge / coding (`gpqa` 250,
988
+ `mmlu-pro` 142, `aime-2025` 122, `swe-bench-verified` 116, `humanity's-last-exam`
989
+ 104, `mmlu` 103); the highest-modelCount **agentic** entry is `toolathlon` at 42,
990
+ and the field already carries `toolathlon_score` (42 covered) from the index
991
+ payload.
992
+
993
+ **The general agentic axis is already covered; the multi-agent sub-axes are
994
+ not.** The field already carries agentic proxies, all in `KNOWN_METRICS`:
995
+ `agents` (index) 192/400 (48.0%, r(general) 0.918), `tool_calling` 202/400
996
+ (50.5%, r(general) 0.799, r(agents) 0.879), `mcp_atlas_score` 36 (9.0%),
997
+ `toolathlon_score` 42 (10.5%), `osworld_score` 21 (5.3%), `apex_agents_score` 10
998
+ (2.5%). The agentic-adjacent candidates that do have data are collinear with
999
+ them (`osworld` r(general) 0.927 / r(agents) 0.921; `browsecomp` 0.897 / 0.916;
1000
+ `toolathlon` 0.828 / 0.858; `mcp_atlas` 0.772 / 0.805), so even a landed
1001
+ tool-orchestration axis would add little independent signal.
1002
+
1003
+ **Verdict: do not land.** No catalog benchmark measures the named axes; the one
1004
+ multi-agent eval is below the 3-model floor; the endpoint cap makes the coverage
1005
+ bar unreachable for any generic benchmark; and the agentic-adjacent candidates
1006
+ are either collinear with the shipped `agents`/`tool_calling` indices or measure
1007
+ single-agent tool use. The honest outcome is the recorded evidence, mirroring
1008
+ the domain-knowledge (#23) and truthfulness (#24) verdicts.
1009
+
253
1010
  ## Changing a weight — checklist
254
1011
 
255
1012
  1. Keep Σ = 1 and Σ(non-price) = 1 − w_price (rescale the others; do not touch
@@ -264,3 +1021,7 @@ one." Three findings, each measured on that day's caches:
264
1021
  4. Re-run the report (`node src/cli/llm-role-rank.ts --top 5`) and check the stderr
265
1022
  match/eligible counts; the explorer's `Δ` column measures against the role's
266
1023
  effective def.
1024
+ 5. A price-weight change is a posture change: measure the leader-flip threshold
1025
+ on the **reachable** pool (rule 6), not the full pool, and record both
1026
+ numbers (reachable and full-pool) in the review. A threshold measured on the
1027
+ wrong pool reads as decoration when it is binding, or vice versa.