omp-llm-role 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +179 -11
  2. package/agents/designer.md +4 -3
  3. package/docs/dev/README.md +44 -7
  4. package/docs/dev/agent-authoring.md +59 -1
  5. package/docs/dev/architecture.md +84 -27
  6. package/docs/dev/ci.md +152 -0
  7. package/docs/dev/current-state.md +327 -24
  8. package/docs/dev/data-sources.md +202 -38
  9. package/docs/dev/explorer.md +157 -46
  10. package/docs/dev/quirks.md +45 -4
  11. package/docs/dev/releasing.md +129 -17
  12. package/docs/dev/scoring.md +789 -28
  13. package/docs/dev/testing.md +206 -0
  14. package/docs/dev/writes-and-state.md +176 -28
  15. package/docs/llm-role-rankings.md +141 -140
  16. package/package.json +754 -80
  17. package/skills/omp-llm-role-create-agent/SKILL.md +7 -1
  18. package/src/agent-architect.ts +148 -19
  19. package/src/agent-create.ts +800 -40
  20. package/src/agent-file.ts +2 -5
  21. package/src/architect-provenance.ts +16 -0
  22. package/src/availability.ts +104 -6
  23. package/src/benchmark-author.ts +9 -4
  24. package/src/benchmark-sources.ts +283 -18
  25. package/src/cli/create-role.ts +29 -1
  26. package/src/cli/llm-role-rank.ts +40 -1
  27. package/src/config-edit.ts +207 -80
  28. package/src/engine.ts +409 -46
  29. package/src/error-report.ts +562 -0
  30. package/src/explorer/boot.ts +36 -24
  31. package/src/explorer/explain.ts +110 -13
  32. package/src/explorer/scopes.ts +110 -0
  33. package/src/explorer/server.ts +112 -32
  34. package/src/extension.ts +407 -34
  35. package/src/features.ts +103 -0
  36. package/src/project-registry.ts +65 -0
  37. package/src/project-setup.ts +382 -0
  38. package/src/prompts/agent-creation-architect.md +2 -0
  39. package/src/prompts/project-profile-architect.md +33 -0
  40. package/src/prompts/project-profile-user.md +8 -0
  41. package/src/role-archetypes.ts +12 -2
  42. package/src/role-settings.ts +22 -0
  43. package/src/settings.ts +205 -24
  44. package/src/state.ts +1 -1
  45. package/src/updater.ts +150 -31
  46. package/web/app.js +411 -12
  47. package/web/index.html +1 -0
  48. package/web/style.css +44 -1
  49. package/docs/dev/spec.md +0 -633
@@ -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,51 @@ 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*). The blend is the unpinned
163
+ default.
164
+
165
+ ## OpenRouter — key metadata (tier gate)
166
+
167
+ Bearer-authenticated GETs with the omp-resolved key decide which variant tier the
168
+ run may use:
169
+
170
+ - `GET https://openrouter.ai/api/v1/key` → `is_free_tier`, `limit_remaining`,
171
+ `limit`, `free_model_daily_requests.remaining`.
172
+ - `GET https://openrouter.ai/api/v1/credits` → `total_credits`, `total_usage`.
173
+
174
+ ```
175
+ billedUsable = !is_free_tier && limit_remaining > 0 && (total_credits - total_usage) > 0
176
+ freeUsable = free_model_daily_requests.remaining > 0
177
+ ```
178
+
179
+ `tierGate` (`src/availability.ts`) maps these to `"billed" | "free" | "none"`:
180
+ billed → billed variants only (exclude `:free`, `:batch`); else free → `:free`
181
+ variants only (exclude `:batch`); else `"none"` aborts the run. The key endpoint
182
+ carries **no** model/provider restriction field — the account's allowed-providers
183
+ whitelist is invisible to it (see the keyed catalog below and the probe in
184
+ [`architecture.md`](architecture.md)).
185
+
186
+ ## OpenRouter — keyed catalog (availability)
187
+
188
+ The account setting **"Filter the model catalog for API keys"** (openrouter.ai/settings)
189
+ makes the Bearer-authenticated catalog a per-key allowlist.
190
+
191
+ - **URLs**: `GET https://openrouter.ai/api/v1/models` twice — once unauthenticated
192
+ (the public catalog) and once with `Authorization: Bearer <key>` (the keyed catalog).
193
+ `fetchKeyAvailability` (`src/availability.ts`) issues both in parallel.
194
+ - **Signal**: set membership only — the response carries no per-model access flag. Both
195
+ id lists are normalized with `rankingIdOf` (suffix after the last `/`, minus `:free`,
196
+ minus a trailing `-latest`), the same identity space as the engine's `Model.id`, so
197
+ aliases never produce a false "blocked". The keyed list being a non-empty **proper
198
+ subset** of the public one means the setting is on (`active`); equality is `no-filter`;
199
+ an empty list or any transport/status/shape failure is `unavailable`.
200
+ - **Consumers**: the updater fetches it **once per run** and shares it across roles; the
201
+ explorer fetches it on boot and on `POST /api/refresh`. It is **not cached to disk** —
202
+ every consumer re-fetches, and `fetchKeyAvailability` **never throws** (a failure
203
+ degrades to `unavailable` with empty sets, so the probe walk / `unknown` marks stand).
204
+
104
205
  ## Design Arena — design quality
105
206
 
106
207
  Two routes, never averaged (same Elo family; mean diff −1.0 website … −5.1 svg,
@@ -191,25 +292,88 @@ mapping to a `KNOWN_METRICS` key. A source whose data the engine already fetches
191
292
  ### The generic llm-stats benchmark
192
293
 
193
294
  `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
295
+ `{ benchmark_id, benchmark_name, max_score, total_models, entries[] }`; each
296
+ entry carries `model_id` (the bare llm-stats id, so the join is **direct**) and
196
297
  `normalized_score` (0-1). Any `llm-stats.com/benchmarks/<id>` link (or a bare
197
298
  benchmark id) resolves to a source whose metric is `bench:<normalized-id>`, cache
198
299
  file `bench-<normalized-id>-fetched-data.json`, transform `identity`, and a
199
300
  capability fill of 0.195. The raw id is preserved for the fetch URL (the real id
200
301
  `alpacaeval-2.0` normalizes to the metric `bench:alpacaeval-2_0`), so the source
201
302
  is persisted as a declaration (below) — the metric key alone cannot reconstruct
202
- the raw id.
303
+ the raw id. `catalogBenchmarkDeclaration` builds that declaration for a dotted
304
+ catalog id at discovery/authoring time, carrying the raw id in `fetch.url`.
305
+
306
+ Each entry also carries the trust flags `self_reported` and `verified` (both
307
+ booleans), the `multimodal` flag, the org fields `organization_id`/
308
+ `provider_id`, and a **second, independent** set of the price/throughput/context
309
+ quantities: `input_cost_per_million`, `output_cost_per_million`, `speed_rps`
310
+ (requests/s) and `context_window` (tokens). `parseBenchmarkPayloadMeta(payload)`
311
+ reads them independently of `source.parse` (a declared/writing source reads no
312
+ entry flags) and returns
313
+ `{ trust: { selfReported, verified, covered }, modality: { multimodal, covered },
314
+ provenance: { dominantOrg, dominantShare, orgs, covered }, crossSource }` — the
315
+ trust
316
+ `covered` is the count of entries carrying a boolean `self_reported` (the share
317
+ denominator), `selfReported` the count of those whose `self_reported` is `true`,
318
+ and `verified` the count of those whose `verified` is `true`; the modality
319
+ `covered` is the count of entries carrying a boolean `multimodal` and `multimodal`
320
+ the count of those that are `true`; the provenance `covered` is the count of
321
+ entries carrying a non-empty org string (each row's `organization_id`, falling
322
+ back to `provider_id`), `orgs` the distinct org count, and `dominantOrg`/
323
+ `dominantShare` the most frequent org and its count / `covered`; `crossSource` is
324
+ a map keyed by the entry's `model_id` of `{ input, output, speed, context }`
325
+ (each a finite number, else `null`), `null` when no entry carried any of the four
326
+ (the four summaries have independent denominators)
327
+ (measured 2026-10-07: `verified` is uniformly `false`; `self_reported` varies —
328
+ gpqa 19/20, deepswe-1.1 14/20, terminal-bench-4.0 10/20, automationbench-aa 0/1).
329
+ `trust`/`modality`/`provenance`/`crossSource` are each `null` when no entry
330
+ carried the respective field, and the whole meta is `null` when the payload has
331
+ no llm-stats entries shape.
332
+ `BenchmarkPayload` carries it as `meta`, `BenchmarkScores` as `meta`, and the
333
+ scores cache persists it. The scores-cache reader carries the same shape guard as
334
+ the catalog reader: a cache lacking the `meta` key (written before the
335
+ payload-meta fields landed) is rejected and refetched, so the four
336
+ payload-derived focus axes never go silently inert; `meta: null` (a
337
+ declared/writing payload) is still accepted. The
338
+ focus-metric trust, modality, provenance and cross-source axes (scoring.md)
339
+ consume it via `cachedSourceInfo`.
340
+
341
+ The endpoint caps `entries` at `BENCHMARK_ENTRY_CAP` (20) regardless of
342
+ `limit`/`offset`/`page`/`per_page`, so a generic metric can never load more and
343
+ no pagination loop helps. `total_models` still reports the full set, so
344
+ `loadBenchmarkScores` returns `loaded` (the entries actually read) alongside
345
+ `total`; `loaded < total` marks a capped load (annotated to stderr) and the
346
+ post-fetch coverage count is the loadable `loaded`, never the catalog's
347
+ `model_count`.
203
348
 
204
349
  ### The benchmark catalog (discovery)
205
350
 
206
351
  `GET https://api.zeroeval.com/leaderboard/benchmarks` (the same backend, no
207
352
  `/<id>`) returns the full catalog as a top-level JSON array of ~745 rows:
208
353
  `{ 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
354
+ model_count, is_community, updated_at, version_count, latest_version_row_count,
355
+ star_count, dataset_id, dataset_org_id, dataset_slug }`. `parseBenchmarkCatalog`
356
+ narrows it to
357
+ `{ id, name, description, categories, modelCount, isCommunity, modality,
358
+ updatedAt, versionCount, latestVersionRowCount, starCount, datasetId,
359
+ datasetOrgId, datasetSlug }` (the
360
+ row's `is_community`, defaulting `false` when absent/non-boolean — consumed by
361
+ the trust axis as `community`, 20/745 true — the row's `modality` as an open
362
+ string, `null` when absent/non-string, so an unknown value survives, the
363
+ maintenance fields `updated_at`/`version_count`/`latest_version_row_count`/
364
+ `star_count`, each `null` when absent, non-string (for `updated_at`) or
365
+ non-finite (for the three counts), and the provenance fields `dataset_id`/
366
+ `dataset_org_id`/`dataset_slug`, each `null` when absent, non-string or empty —
367
+ `dataset_org_id` is the benchmark's owning organisation, the provenance axis's
368
+ owner, with `dataset_slug` as its fallback);
369
+ `loadBenchmarkCatalog` follows the same
370
+ daily cache chain (cache file `benchmark-catalog-fetched-data.json`). The catalog
371
+ cache reader carries a shape guard mirroring `readEndpointsCache`: a cache whose
372
+ sampled entry lacks `isCommunity`, `modality`, any of the four maintenance
373
+ fields or any of the three provenance fields (written before the field landed) is
374
+ rejected and refetched, so a stale
375
+ cache never reports `undefined`; an empty
376
+ catalog is still `null`. `/create-agent` uses it to discover the
213
377
  benchmarks relevant to a purpose: `discoverBenchmarks` filters by coverage
214
378
  (`modelCount >= 3`), ranks the survivors by IDF-weighted lexical overlap with the
215
379
  purpose (a category that names the purpose's skill is the strongest signal), caps
@@ -282,10 +446,10 @@ fresh checkout needs no setup.
282
446
  | `cache/openrouter-fetched-data.json` | `{ fetchedAt, source, modelCount, data }` | non-fatal (affected models unranked) |
283
447
  | `cache/openrouter-endpoints-fetched-data.json` | `{ fetchedAt, source, slugCount, slugs }` | non-fatal (single-route fallback) |
284
448
  | `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) |
449
+ | `cache/writing-fetched-data.json` | `{ fetchedAt, source, scores, total, meta }` | non-fatal (`writing` unfilled) |
450
+ | `cache/bench-<id>-fetched-data.json` | `{ fetchedAt, source, scores, total, meta }` | non-fatal (the external metric is unfilled) |
451
+ | `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) |
452
+ | `cache/<declared-id>-fetched-data.json` | `{ fetchedAt, source, scores, total, meta }` | non-fatal (the external metric is unfilled) |
289
453
 
290
454
  The last two are the registry's generic scores cache (`loadBenchmarkScores`),
291
455
  shared by the generic llm-stats benchmark and every declared source.
@@ -324,9 +488,9 @@ the source does not cover.
324
488
  the routes that have p50 data, so a stable route with no recent requests
325
489
  contributes price weight but no throughput. A degraded cheapest route
326
490
  (`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).
491
+ The blend weights by the `1/p_in²` formula (input price, see "The 1/price²
492
+ blend"), not by the page's observed request counts (which aggregate ALL
493
+ OpenRouter traffic, `:nitro`/`:floor` and `sort` users included).
330
494
  - **The find route and the page record for the same endpoint id can disagree**
331
495
  by a few percent (price revisions, status flips, p50 windows); the page copy
332
496
  wins the pool merge, the find row joins only when the page does not list its