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
@@ -5,7 +5,6 @@ llm-stats id, and where it is cached. All fetching lives in `src/engine.ts`
5
5
  (plus `src/availability.ts` for the key/catalog/probe surfaces).
6
6
 
7
7
  - Module map and pipeline: [`architecture.md`](architecture.md).
8
- - Normative behavior contract: [`spec.md`](spec.md).
9
8
 
10
9
  ## llm-stats.com — quality
11
10
 
@@ -22,12 +21,27 @@ payload; there is no public JSON API.
22
21
  `context`, `release_date`, `multimodal`, `license`, `input_price`/`output_price`
23
22
  (unused), `throughput`/`latency` (unused), the `index_*` scores (0–100:
24
23
  general, reasoning, math, code, agents, search, vision, tool_calling,
25
- long_context) and the benchmark scores (0–1: `gpqa_score`,
26
- `aime_2025_score`, `swe_bench_verified_score`, `arc_agi_v2_score`,
27
- `mrcr_v2_score`, `terminal_bench_score`, `tau_bench_retail_score`).
24
+ long_context, communication, finance, healthcare, legal) and the benchmark
25
+ scores (0–1: `gpqa_score`, `aime_2025_score`, `swe_bench_verified_score`,
26
+ `arc_agi_v2_score`, `mrcr_v2_score`, `terminal_bench_score`,
27
+ `tau_bench_retail_score`, plus `hle_score`, `simpleqa_score`, `mmmu_score`,
28
+ `mmmu_pro_score`, `mmmlu_score`, `browsecomp_score`, `swe_bench_pro_score`,
29
+ `mcp_atlas_score`, `apex_agents_score`, `osworld_score`, `scicode_score`,
30
+ `screenspot_pro_score`, `charxiv_r_score`, `frontiermath_score`,
31
+ `toolathlon_score`, `coding_arena_score`). `buildModels` maps the nine
32
+ short-key indices plus the four new `index_*` fields (communication, finance,
33
+ healthcare, legal) and the seven short-key benchmark scores plus the fifteen
34
+ new `*_score` benchmarks — 13 indices and 22 benchmarks in all. The new
35
+ metrics keep the **raw leaderboard field name as the metric key**
36
+ (`index_communication`, `simpleqa_score`, …), unlike the original nine/seven
37
+ short keys. `coding_arena_score`, `latency` and `context` are fetched but
38
+ discarded (see the research note on the agentic-engineering benchmarks for the
39
+ coverage/collinearity of the unmapped ones).
28
40
  - **Build**: `buildModels(rows)` maps each row to a `Model`; `metrics` carries
29
41
  the index/benchmark values, with `price`/`throughput`/`website`/`writing`
30
- left `null` for later enrichment.
42
+ left `null` for later enrichment. The row's `release_date` is carried onto
43
+ `Model.releaseDate` (null when absent) and used by the focus-metric freshness
44
+ axis (scoring.md, the nine-axis gate).
31
45
  - **Cache**: `cache/llm-stats-fetched-rankings.json` —
32
46
  `{ fetchedAt, source, modelCount, rankings[] }` (pretty-printed; each row
33
47
  gains a `rank` = array position). Fresh while `fetchedAt` is the current UTC
@@ -48,14 +62,16 @@ Two payloads, both joined to llm-stats by slug suffix: the llm-stats `model_id`
48
62
  - **Shape**: `data.endpoint_perf[endpointId]` → `{ p50_throughput (output tok/s),
49
63
  p50_latency (ms) }` over the last **30 minutes** of routed traffic;
50
64
  `data.models[]` rows link a slug to one endpoint id — the route currently
51
- getting the traffic — each carrying `pricing.prompt`/`pricing.completion`
52
- (USD/token strings, discounts already applied). The payload also carries
53
- `analytics`, `benchmarks`, `benchmark_ranges`, `categories`,
65
+ getting the traffic — each carrying `pricing.prompt`/`pricing.completion` and
66
+ `pricing.input_cache_read` (USD/token strings, discounts already applied). The
67
+ payload also carries `analytics`, `benchmarks`, `benchmark_ranges`, `categories`,
54
68
  `modality_counts`.
55
69
  - **Parse**: `parseFindData(v)` narrows `find` (models, `endpoint_perf`,
56
- `endpoint_price` blended to $/M at 3:1 in:out) and keeps the full `data`
57
- object verbatim for the cache; it also extracts the Design Arena mirror
58
- (`extractDesignElo`, see below).
70
+ `endpoint_price` blended to $/M at 3:1 in:out, the parallel
71
+ `endpoint_weight_price` input-only $/M map — the router's sort key — and the
72
+ `endpoint_cache_read_price` cache-read input-only $/M map; see "The
73
+ 1/price² blend") and keeps the full `data` object verbatim for the cache; it
74
+ also extracts the Design Arena mirror (`extractDesignElo`, see below).
59
75
  - **Cache**: `cache/openrouter-fetched-data.json` —
60
76
  `{ fetchedAt, source, modelCount, data }` where `data` is the verbatim
61
77
  `find?fmt=cards` response data (sorted keys). Throughput/price maps are
@@ -65,35 +81,75 @@ Two payloads, both joined to llm-stats by slug suffix: the llm-stats `model_id`
65
81
 
66
82
  - **URL**: `https://openrouter.ai/<slug>` (RSC flight payload), fetched only for
67
83
  slugs that match a leaderboard model (~150 of ~540).
68
- - **Shape**: every provider route of a model — pricing, service tier, status and
69
- routed-traffic p50 stats — dehydrated as React-Query state (sometimes twice,
70
- one copy without stats; records merge by endpoint id, the stats-carrying copy
71
- winning). `parseModelPage(html)` narrows each row to
72
- `OpenRouterEndpointRecord` (`id`, `providerSlug`, `serviceTier`, `status`,
73
- `free`, `variant`, `price`, `tput`, `latency`).
84
+ - **Shape**: every provider route of a model — pricing, service tier, status,
85
+ routed-traffic p50 stats and the per-endpoint capability ceilings — dehydrated
86
+ as React-Query state (sometimes twice, one copy without stats; records merge by
87
+ endpoint id, the stats-carrying copy winning). `parseModelPage(html)` narrows
88
+ each row to `OpenRouterEndpointRecord` (`id`, `providerSlug`, `serviceTier`,
89
+ `status`, `free`, `variant`, `price`, `weightPrice`, `cacheReadPrice`
90
+ (endpoint `pricing.input_cache_read`; `null` when absent), `tput`, `latency`,
91
+ `contextLength` (endpoint `context_length`), `maxCompletionTokens` (endpoint
92
+ `max_completion_tokens`) and `supportsTools` (`supported_parameters` contains
93
+ `tools`)). The three capability fields are `null` when the field is absent —
94
+ the role endpoint filters keep a `null` (missing data is not a capability
95
+ failure).
74
96
  - **Fetch**: `fetchEndpointPages(slugs)` with an 8-worker pool
75
97
  (`OPENROUTER_PAGE_CONCURRENCY`), one retry per page, permanent 404/410 gives
76
98
  up. A page that fails is absent and that model keeps the single-route
77
99
  fallback.
78
100
  - **Cache**: `cache/openrouter-endpoints-fetched-data.json` —
79
101
  `{ fetchedAt, source, slugCount, slugs }` where `slugs` maps each fetched
80
- model slug to its narrowed per-provider route records.
102
+ model slug to its narrowed per-provider route records. `readEndpointsCache`
103
+ rejects a cache whose records lack `weightPrice` (the pre-#21 shape),
104
+ `contextLength` (the pre-#18 shape) **or** `cacheReadPrice` (the pre-#30
105
+ shape) and refetches, rather than silently dropping every route from the
106
+ blend, treating every ceiling as unstated, or leaving a role's `cacheHitRate`
107
+ inert (a missing cache-read price reads as the full input price).
108
+ - **Per-model routes**: `buildOpenRouterEnrichment` carries the eligible
109
+ standard-tier pool on `OrEnrichment.routes`, and `applyOpenRouterData` sets it
110
+ on `Model.routes` — the pool the role endpoint filters gate. The
111
+ find-route fallback carries the same field (its capability fields are `null`,
112
+ since the find payload has no ceilings).
81
113
 
82
114
  ### The 1/price² blend
83
115
 
84
116
  `buildOpenRouterEnrichment(data, pages)` computes the expected price/throughput
85
117
  of one request under OpenRouter's default price-based load balancing (a request
86
118
  goes to ONE provider, picked among the stable standard-tier routes with
87
- probability proportional to `1/price²`):
119
+ probability proportional to `1/price²`). The weight basis is the **input
120
+ (prompt) price** — the router's sort key — while the reported price is the
121
+ **billed 3:1 in:out blend** under that distribution.
88
122
 
89
123
  - **Pool**: the page's per-provider routes, plus any find-row endpoint the page
90
124
  does not list (the page is normally a superset).
91
125
  - **Eligible**: standard tier (`serviceTier === null`), `status === 0`, not
92
- `:free`, `price > 0`, not `:batch`. flex/priority tiers are excluded (only the
93
- `:floor`/`:nitro` variants make them eligible); degraded routes (`status ≠ 0`)
94
- are fallbacks and drop out.
95
- - **Blend**: `price = Σ(1/p²)·p / Σ(1/p²)`; `throughput = Σ(1/p²)·t / Σ(1/p²)`
96
- renormalized over the routes that have p50 data; latency likewise.
126
+ `:free`, `price > 0`, `weightPrice > 0`, not `:batch`. flex/priority tiers are
127
+ excluded (only the `:floor`/`:nitro` variants make them eligible); degraded
128
+ routes (`status ≠ 0`) are fallbacks and drop out.
129
+ - **Weight basis**: the input-only $/M (`pricing.prompt * 1e6`, carried as
130
+ `weightPrice` on each route and `endpoint_weight_price` in the find payload).
131
+ OpenRouter's docs say only "weighted by inverse square of the price" and never
132
+ define the basis; the input price is the measured one. Tarun Chitra (Robot
133
+ Ventures), *Caching Cheaters on OpenRouter* (2026-08-14,
134
+ https://robvc.com/research/caching-cheaters): 64/64 informative provider
135
+ splits picked the cheapest-**input** provider (including menus where that
136
+ provider quoted the most expensive output price), output price carries ≲6
137
+ cents per dollar of input, and the inverse-square exponent recovered from
138
+ 1,648 choices is r = 1.968 (95% CI [1.856, 2.098]); cached-token price adds no
139
+ measurable predictive power. The authors' caveat: behaviour-measured, not
140
+ source-read.
141
+ - **Blend**: `price = Σ(1/p_in²)·p_billed / Σ(1/p_in²)` — the w-weighted mean of
142
+ the billed 3:1 blend (what a caller pays), weighted by the input price;
143
+ `throughput = Σ(1/p_in²)·t / Σ(1/p_in²)` renormalized over the routes that
144
+ have p50 data; latency likewise. A route with no input price drops from the
145
+ pool (`1/p²` blows up at 0, and the router cannot score a route without one).
146
+ - **Cache-read pricing**: a role may declare `cacheHitRate` (0–1, opt-in), and
147
+ `blendRoutePool(routes, cacheHitRate)` then blends each route's
148
+ `cacheReadPrice` (the endpoint's `pricing.input_cache_read`) into its billed
149
+ price, `billed = price + 3·(h·cacheRead + (1−h)·input − input)/4`, **without**
150
+ changing the `1/p_in²` weight basis (the router still sorts on the listed input
151
+ price). A route with no cache-read price keeps its full input price; `h = 0`
152
+ is byte-identical to the uncached blend. See scoring.md, *Cache-read pricing*.
97
153
  - **Fallback** (no eligible route carries throughput): throughput = highest-p50
98
154
  variant (`:free` included — it rescues otherwise-unranked models), price =
99
155
  cheapest billed route (a $0 free tier never sets the price).
@@ -101,6 +157,80 @@ probability proportional to `1/price²`):
101
157
  `m.throughput`, `m.metrics.price`/`throughput`, and `m.thinking`
102
158
  (`supports_reasoning`).
103
159
 
160
+ A role with a `providerPin` does **not** use this blend: it prices the
161
+ model's matching route directly — the route's billed 3:1 price and its p50
162
+ throughput (scoring.md, *Route-aware pricing*). A role with the soft
163
+ `preferOwnProvider` policy (the `providerPinning` capability) likewise prices
164
+ each model on its **best route** rather than the blend, falling back to the blend
165
+ only when the model has no candidate route (scoring.md, *Capability presets*).
166
+ The blend is the default for every other model.
167
+
168
+ ## OpenRouter — key metadata (tier gate)
169
+
170
+ Bearer-authenticated GETs with the omp-resolved key decide which variant tier the
171
+ run may use:
172
+
173
+ - `GET https://openrouter.ai/api/v1/key` → `is_free_tier`, `limit_remaining`,
174
+ `limit`, `free_model_daily_requests.remaining`.
175
+ - `GET https://openrouter.ai/api/v1/credits` → `total_credits`, `total_usage`.
176
+
177
+ ```
178
+ billedUsable = !is_free_tier && limit_remaining > 0 && (total_credits - total_usage) > 0
179
+ freeUsable = free_model_daily_requests.remaining > 0
180
+ ```
181
+
182
+ `tierGate` (`src/availability.ts`) maps these to `"billed" | "free" | "none"`:
183
+ billed → billed variants only (exclude `:free`, `:batch`); else free → `:free`
184
+ variants only (exclude `:batch`); else `"none"` aborts the run. The key endpoint
185
+ carries **no** model/provider restriction field — the account's allowed-providers
186
+ whitelist is invisible to it (see the allowed-providers whitelist below and the
187
+ probe in [`architecture.md`](architecture.md)).
188
+
189
+ ## OpenRouter — keyed catalog (availability)
190
+
191
+ The account setting **"Filter the model catalog for API keys"** (openrouter.ai/settings)
192
+ makes the Bearer-authenticated catalog a per-key allowlist.
193
+
194
+ - **URLs**: `GET https://openrouter.ai/api/v1/models` twice — once unauthenticated
195
+ (the public catalog) and once with `Authorization: Bearer <key>` (the keyed catalog).
196
+ `fetchKeyAvailability` (`src/availability.ts`) issues both in parallel.
197
+ - **Signal**: set membership only — the response carries no per-model access flag. Both
198
+ id lists are normalized with `rankingIdOf` (suffix after the last `/`, minus `:free`,
199
+ minus a trailing `-latest`), the same identity space as the engine's `Model.id`, so
200
+ aliases never produce a false "blocked". The keyed list being a non-empty **proper
201
+ subset** of the public one means the setting is on (`active`); equality is `no-filter`;
202
+ an empty list or any transport/status/shape failure is `unavailable`.
203
+ - **Consumers**: the updater fetches it **once per run** and shares it across roles; the
204
+ explorer fetches it on boot and on `POST /api/refresh`. It is **not cached to disk** —
205
+ every consumer re-fetches, and `fetchKeyAvailability` **never throws** (a failure
206
+ degrades to `unavailable` with empty sets, so the probe walk / `unknown` marks stand).
207
+
208
+ ## OpenRouter — allowed-providers whitelist (availability)
209
+
210
+ The account's **allowed-providers privacy whitelist**
211
+ (openrouter.ai/settings/privacy) restricts which provider routes a request may
212
+ use. It is invisible to `/api/v1/key` and to the catalog endpoints; the only
213
+ place it appears is the 404 body of a request that fails because of it.
214
+
215
+ - **URL**: `POST https://openrouter.ai/api/v1/chat/completions` with
216
+ `provider.only: ["__omp-llm-role-canary__"]` (a nonexistent provider slug) on a
217
+ valid catalog model. `fetchAllowedProviders(token, modelId, fetchImpl?)`
218
+ (`src/availability.ts`) issues it.
219
+ - **Signal**: the 404 body names the whitelist after "permits only:" — the
220
+ message is parsed into a `Set<string>` of bare lab names (`coreweave`,
221
+ `baseten`, …). A non-404 response, a body without the clause (no whitelist
222
+ set), or any transport/shape failure returns `null`. The canary 404s, so it
223
+ bills no tokens.
224
+ - **Match rule**: a route's `providerSlug` is allowed when it equals a whitelist
225
+ entry or starts with `<entry>/` — the whitelist carries bare lab names while
226
+ route slugs are tiered (`coreweave/fp8`, `baseten/fp8`).
227
+ - **Consumer**: the updater harvests it **once per run** (with a valid catalog
228
+ model id) and prunes every model's route pool to allowed providers before
229
+ ranking (architecture.md, *Data flow*). It is **not cached to disk** — every
230
+ run re-learns it, so a privacy-setting change takes effect on the next refresh
231
+ — and `fetchAllowedProviders` **never throws** (a failure degrades to `null`,
232
+ which leaves every route pool untouched).
233
+
104
234
  ## Design Arena — design quality
105
235
 
106
236
  Two routes, never averaged (same Elo family; mean diff −1.0 website … −5.1 svg,
@@ -191,25 +321,88 @@ mapping to a `KNOWN_METRICS` key. A source whose data the engine already fetches
191
321
  ### The generic llm-stats benchmark
192
322
 
193
323
  `GET https://api.zeroeval.com/leaderboard/benchmarks/<id>` returns
194
- `{ benchmark_id, benchmark_name, max_score, entries[] }`; each entry carries
195
- `model_id` (the bare llm-stats id, so the join is **direct**) and
324
+ `{ benchmark_id, benchmark_name, max_score, total_models, entries[] }`; each
325
+ entry carries `model_id` (the bare llm-stats id, so the join is **direct**) and
196
326
  `normalized_score` (0-1). Any `llm-stats.com/benchmarks/<id>` link (or a bare
197
327
  benchmark id) resolves to a source whose metric is `bench:<normalized-id>`, cache
198
328
  file `bench-<normalized-id>-fetched-data.json`, transform `identity`, and a
199
329
  capability fill of 0.195. The raw id is preserved for the fetch URL (the real id
200
330
  `alpacaeval-2.0` normalizes to the metric `bench:alpacaeval-2_0`), so the source
201
331
  is persisted as a declaration (below) — the metric key alone cannot reconstruct
202
- the raw id.
332
+ the raw id. `catalogBenchmarkDeclaration` builds that declaration for a dotted
333
+ catalog id at discovery/authoring time, carrying the raw id in `fetch.url`.
334
+
335
+ Each entry also carries the trust flags `self_reported` and `verified` (both
336
+ booleans), the `multimodal` flag, the org fields `organization_id`/
337
+ `provider_id`, and a **second, independent** set of the price/throughput/context
338
+ quantities: `input_cost_per_million`, `output_cost_per_million`, `speed_rps`
339
+ (requests/s) and `context_window` (tokens). `parseBenchmarkPayloadMeta(payload)`
340
+ reads them independently of `source.parse` (a declared/writing source reads no
341
+ entry flags) and returns
342
+ `{ trust: { selfReported, verified, covered }, modality: { multimodal, covered },
343
+ provenance: { dominantOrg, dominantShare, orgs, covered }, crossSource }` — the
344
+ trust
345
+ `covered` is the count of entries carrying a boolean `self_reported` (the share
346
+ denominator), `selfReported` the count of those whose `self_reported` is `true`,
347
+ and `verified` the count of those whose `verified` is `true`; the modality
348
+ `covered` is the count of entries carrying a boolean `multimodal` and `multimodal`
349
+ the count of those that are `true`; the provenance `covered` is the count of
350
+ entries carrying a non-empty org string (each row's `organization_id`, falling
351
+ back to `provider_id`), `orgs` the distinct org count, and `dominantOrg`/
352
+ `dominantShare` the most frequent org and its count / `covered`; `crossSource` is
353
+ a map keyed by the entry's `model_id` of `{ input, output, speed, context }`
354
+ (each a finite number, else `null`), `null` when no entry carried any of the four
355
+ (the four summaries have independent denominators)
356
+ (measured 2026-10-07: `verified` is uniformly `false`; `self_reported` varies —
357
+ gpqa 19/20, deepswe-1.1 14/20, terminal-bench-4.0 10/20, automationbench-aa 0/1).
358
+ `trust`/`modality`/`provenance`/`crossSource` are each `null` when no entry
359
+ carried the respective field, and the whole meta is `null` when the payload has
360
+ no llm-stats entries shape.
361
+ `BenchmarkPayload` carries it as `meta`, `BenchmarkScores` as `meta`, and the
362
+ scores cache persists it. The scores-cache reader carries the same shape guard as
363
+ the catalog reader: a cache lacking the `meta` key (written before the
364
+ payload-meta fields landed) is rejected and refetched, so the four
365
+ payload-derived focus axes never go silently inert; `meta: null` (a
366
+ declared/writing payload) is still accepted. The
367
+ focus-metric trust, modality, provenance and cross-source axes (scoring.md)
368
+ consume it via `cachedSourceInfo`.
369
+
370
+ The endpoint caps `entries` at `BENCHMARK_ENTRY_CAP` (20) regardless of
371
+ `limit`/`offset`/`page`/`per_page`, so a generic metric can never load more and
372
+ no pagination loop helps. `total_models` still reports the full set, so
373
+ `loadBenchmarkScores` returns `loaded` (the entries actually read) alongside
374
+ `total`; `loaded < total` marks a capped load (annotated to stderr) and the
375
+ post-fetch coverage count is the loadable `loaded`, never the catalog's
376
+ `model_count`.
203
377
 
204
378
  ### The benchmark catalog (discovery)
205
379
 
206
380
  `GET https://api.zeroeval.com/leaderboard/benchmarks` (the same backend, no
207
381
  `/<id>`) returns the full catalog as a top-level JSON array of ~745 rows:
208
382
  `{ benchmark_id, name, description, categories[], modality, max_score, verified,
209
- model_count, is_community }`. `parseBenchmarkCatalog` narrows it to
210
- `{ id, name, description, categories, modelCount }`; `loadBenchmarkCatalog`
211
- follows the same daily cache chain (cache file
212
- `benchmark-catalog-fetched-data.json`). `/create-agent` uses it to discover the
383
+ model_count, is_community, updated_at, version_count, latest_version_row_count,
384
+ star_count, dataset_id, dataset_org_id, dataset_slug }`. `parseBenchmarkCatalog`
385
+ narrows it to
386
+ `{ id, name, description, categories, modelCount, isCommunity, modality,
387
+ updatedAt, versionCount, latestVersionRowCount, starCount, datasetId,
388
+ datasetOrgId, datasetSlug }` (the
389
+ row's `is_community`, defaulting `false` when absent/non-boolean — consumed by
390
+ the trust axis as `community`, 20/745 true — the row's `modality` as an open
391
+ string, `null` when absent/non-string, so an unknown value survives, the
392
+ maintenance fields `updated_at`/`version_count`/`latest_version_row_count`/
393
+ `star_count`, each `null` when absent, non-string (for `updated_at`) or
394
+ non-finite (for the three counts), and the provenance fields `dataset_id`/
395
+ `dataset_org_id`/`dataset_slug`, each `null` when absent, non-string or empty —
396
+ `dataset_org_id` is the benchmark's owning organisation, the provenance axis's
397
+ owner, with `dataset_slug` as its fallback);
398
+ `loadBenchmarkCatalog` follows the same
399
+ daily cache chain (cache file `benchmark-catalog-fetched-data.json`). The catalog
400
+ cache reader carries a shape guard mirroring `readEndpointsCache`: a cache whose
401
+ sampled entry lacks `isCommunity`, `modality`, any of the four maintenance
402
+ fields or any of the three provenance fields (written before the field landed) is
403
+ rejected and refetched, so a stale
404
+ cache never reports `undefined`; an empty
405
+ catalog is still `null`. `/create-agent` uses it to discover the
213
406
  benchmarks relevant to a purpose: `discoverBenchmarks` filters by coverage
214
407
  (`modelCount >= 3`), ranks the survivors by IDF-weighted lexical overlap with the
215
408
  purpose (a category that names the purpose's skill is the strongest signal), caps
@@ -282,10 +475,10 @@ fresh checkout needs no setup.
282
475
  | `cache/openrouter-fetched-data.json` | `{ fetchedAt, source, modelCount, data }` | non-fatal (affected models unranked) |
283
476
  | `cache/openrouter-endpoints-fetched-data.json` | `{ fetchedAt, source, slugCount, slugs }` | non-fatal (single-route fallback) |
284
477
  | `cache/designarena-fetched-data.json` | `{ fetchedAt, source, categories }` | non-fatal (OR mirror alone) |
285
- | `cache/writing-fetched-data.json` | `{ fetchedAt, source, scores }` | non-fatal (`writing` unfilled) |
286
- | `cache/bench-<id>-fetched-data.json` | `{ fetchedAt, source, scores }` | non-fatal (the external metric is unfilled) |
287
- | `cache/benchmark-catalog-fetched-data.json` | `{ fetchedAt, source, entries }` | non-fatal (discovery skipped) |
288
- | `cache/<declared-id>-fetched-data.json` | `{ fetchedAt, source, scores }` | non-fatal (the external metric is unfilled) |
478
+ | `cache/writing-fetched-data.json` | `{ fetchedAt, source, scores, total, meta }` | non-fatal (`writing` unfilled) |
479
+ | `cache/bench-<id>-fetched-data.json` | `{ fetchedAt, source, scores, total, meta }` | non-fatal (the external metric is unfilled) |
480
+ | `cache/benchmark-catalog-fetched-data.json` | `{ fetchedAt, source, entries }` (each entry carries `isCommunity`, `modality`, the maintenance fields `updatedAt`/`versionCount`/`latestVersionRowCount`/`starCount` and the provenance fields `datasetId`/`datasetOrgId`/`datasetSlug`) | non-fatal (discovery skipped) |
481
+ | `cache/<declared-id>-fetched-data.json` | `{ fetchedAt, source, scores, total, meta }` | non-fatal (the external metric is unfilled) |
289
482
 
290
483
  The last two are the registry's generic scores cache (`loadBenchmarkScores`),
291
484
  shared by the generic llm-stats benchmark and every declared source.
@@ -324,9 +517,9 @@ the source does not cover.
324
517
  the routes that have p50 data, so a stable route with no recent requests
325
518
  contributes price weight but no throughput. A degraded cheapest route
326
519
  (`status ≠ 0`) drops out entirely until it recovers, and the price can jump.
327
- The blend weights by the documented `1/p²` formula, not by the page's observed
328
- request counts (which aggregate ALL OpenRouter traffic, `:nitro`/`:floor` and
329
- `sort` users included).
520
+ The blend weights by the `1/p_in²` formula (input price, see "The 1/price²
521
+ blend"), not by the page's observed request counts (which aggregate ALL
522
+ OpenRouter traffic, `:nitro`/`:floor` and `sort` users included).
330
523
  - **The find route and the page record for the same endpoint id can disagree**
331
524
  by a few percent (price revisions, status flips, p50 windows); the page copy
332
525
  wins the pool merge, the find row joins only when the page does not list its