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