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,11 +1,348 @@
1
- # Current state (2026-10-02)
1
+ # Current state (2026-10-11)
2
2
 
3
- Dated snapshot of the ranking as of the date in the heading — matched/priced/
4
- eligible counts, per-role leaders, and the open defects. It is refreshed by
3
+ Dated snapshot of the ranking and the landed work as of the date in the heading —
4
+ matched/priced/eligible counts, per-role leaders, the open defects, and the
5
+ per-issue work log below. It is refreshed by
5
6
  regenerating the report (`node src/cli/llm-role-rank.ts --out docs/llm-role-rankings.md`) and
6
7
  copying the new numbers here; release prose belongs in
7
- [`../../CHANGELOG.md`](../../CHANGELOG.md), not in this file. The numbers below
8
- are the 2026-10-02 measurement and are not re-derived on read.
8
+ [`../../CHANGELOG.md`](../../CHANGELOG.md), not in this file. The model-level
9
+ ranking numbers below are the 2026-10-09 measurement (the input-price blend basis
10
+ #21, the #17/#18/#19/#20 wave and the #34–#40 focus-axis/capability-flag work);
11
+ the provider-pin numbers are the 2026-10-11 measurement (spec #71; see the top
12
+ bullet). Neither is re-derived on read.
13
+
14
+ - Provider ordering: value-for-money route score (2026-10-11, spec #71): the
15
+ auto-pin route basis changed from the route's own value (`q_route −
16
+ λ·priceEff_route`) to a min/max-normalized **value-for-money score** `vfm =
17
+ wT·tNorm − wP·pNorm` (`wT = wTput/(wPrice+wTput)`, `wP =
18
+ wPrice/(wPrice+wTput)`; `tNorm`/`pNorm` min/max over the candidate set), with a
19
+ new **soft** `filters.preferredMinThroughput` floor (routes at or above sort
20
+ first; below-floor routes are kept and sorted last) that the `providerPinning`
21
+ preset sets to 50. `routeValue` is deleted. The model-level ranking is
22
+ unchanged — the report is not regenerated (the ranking CLI does not apply
23
+ capability features and no shipped role sets `preferOwnProvider`). Measured
24
+ 2026-10-11 on the current endpoints cache with the live lock's expanded defs
25
+ (all four capabilities on, `cacheHitRate 0.9`; `deepseek/deepseek-v4.1-flash`,
26
+ 11 allowed providers, 12 allowed routes):
27
+ `morph/fp8` ($0.0840,
28
+ 103 tok/s) wins `default`, `smol`, `commit`, `task`, `advisor`; `coreweave/fp8`
29
+ ($0.1978, 185.5 tok/s) wins `slow`, `tiny`, `designer`; `decart/fp4` ($0.0639,
30
+ 72 tok/s) wins `vision`, `plan`.
31
+
32
+ - Auto-pin chain expansion (2026-10-10, issue #69): with `providerPinning` on,
33
+ the fallback chain no longer stops at three providers per model — the primary
34
+ model contributes every gate-passing route except the pinned best, then each
35
+ `fallbackChainDepth` fallback model contributes every gate-passing route, in
36
+ score order (the `vfm` route score — spec #71 replaced the original
37
+ route-value order) and deduped by provider slug, so a down provider fails over
38
+ to another provider of the same model before the chain switches models. Chain
39
+ length is data-driven (`(R_primary − 1) + Σ R_fallback`) instead of the fixed
40
+ `2 + 3·fallbackChainDepth`; the manual-`providerPin` and no-pin paths are
41
+ unchanged. The ranking numbers are unchanged — the chain is a write-side
42
+ shape, not a ranking input.
43
+ - Opt-in error reporting (2026-10-08, issue #48): a new `errorReporting` setting
44
+ (`off` — the default — `ask`, `auto`) turns a genuinely unexpected failure — a
45
+ command handler throwing, or a `runUpdater` abort whose `RunResult.defect` is
46
+ set (a `ConfigEditError`/self-check mismatch or the catch-all throw; the
47
+ enumerated environment aborts stay notify-only) — into one structured report
48
+ (plugin version, runtime, platform, the failing command, error
49
+ name/message/stack, timestamp) filed on `Gott50/omp-llm-role`. `ask` surfaces
50
+ the exact payload and files on confirmation, degrading to a notified prefilled
51
+ `…/issues/new` URL in a session with no UI; `auto` files without asking; `off`
52
+ is byte-identical to the pre-#48 behaviour. A token (`GITHUB_TOKEN` → `GH_TOKEN`
53
+ → `gh auth token`, never stored) uses the API; no token, or a 401/403/404 (a
54
+ fine-grained PAT without Issues:write answers 404), falls through to the
55
+ prefilled URL. The payload is redacted (home dir → `~`, token-shaped strings →
56
+ `<redacted>`) and carries no config contents, model selectors, prompts or
57
+ history rows. A fingerprint marker dedupes a repeat onto the existing open
58
+ issue, and the path is capped (one open issue per fingerprint, one creation per
59
+ fingerprint per UTC day, 3 per UTC day). The ledger of what was filed, offered
60
+ or suppressed is `llm-role-error-reports.json` under the agent dir. The ranking
61
+ numbers are unchanged — reporting is a side channel, not a ranking input.
62
+
63
+ - Empty-collection spellings accepted (2026-10-08, issues #54 + #55): a config
64
+ whose `task.disabledAgents` is spelled `null` — or whose `retry.fallbackChains`
65
+ / `modelRoles` body is the serializer's own indented `{}` / `[]` line — used to
66
+ throw inside `parseConfig`, so **every session start aborted with `config edit
67
+ refused: disabledAgents: inline value is not supported (block sequence
68
+ expected)` and no role was ever refreshed**. omp re-serializes `config.yml`
69
+ through Bun's `YAML.stringify` (`stringifyYamlConfig`), which spells an empty
70
+ collection exactly that way, so those spellings now read as the empty
71
+ collection and the patch path rewrites the placeholder line into the block
72
+ form (`disabledAgents:` + `- "designer"`); any other inline value on a managed
73
+ key line still throws. Verified against a copy of the real
74
+ `~/.omp/agent/config.yml` (which carries `disabledAgents: null`): the run wrote
75
+ and reported 8 decisions instead of aborting — and the real file carries its own
76
+ production confirmation. Its 2026-10-08T16:34Z `session-start` run wrote the
77
+ config while `disabledAgents: null` was present (config, `llm-role-state.json`
78
+ and `llm-role-history.jsonl` are all stamped 18:34:17 local), which pre-fix was
79
+ impossible: the read threw before any write. The write is recognizably the
80
+ plugin's surgical one, not omp's serializer: a *changed* role line is re-quoted
81
+ (`tiny: "…deepseek-v4.1-flash:off"`) while an unchanged one stays byte-identical
82
+ (`smol: …deepseek-v4.1-flash:off`), the previously plugin-written
83
+ `openrouter/inclusionai/ling-3.0-flash` chain key is pruned (a dumper never
84
+ prunes), and the chain keys the plugin does not own (`web:`,
85
+ `openrouter/~deepseek/…`) keep their unquoted items. The placeholder is left in
86
+ place because the agent sync had nothing to add or remove — `patchDisabledAgents`
87
+ early-returns then, by design. The same probe turned up two more defects, one of
88
+ them tracked as its own issue (#55 — a different error-report fingerprint): the
89
+ parent-block variant of this class — an empty `task:` + ` {}` / `retry:` + ` {}`
90
+ used to take the new child key *after* the `{}` line, YAML no parser accepts,
91
+ with the read-back self-check still passing, i.e. **silent corruption** — now
92
+ has the child key replace the `{}` line, and a prune-only patch creates nothing;
93
+ and #55's block-bound bug (`chainEntries` scanned to the end of `retry:`
94
+ instead of to the end of `fallbackChains:`, so any sibling `retry.*` key
95
+ aborted every run with `unexpected line in fallbackChains block`) is fixed by
96
+ bounding the scan at the key's own indent. Ranking numbers unchanged.
97
+
98
+ - Capability presets (2026-10-07, issue #40): one opt-in flag per capability
99
+ (`features.<id>` global, `roles.<role>.features.<id>` per role) applies the
100
+ plugin's recommended settings to a role; every flag ships **off** and no
101
+ shipped role sets one, so the resolved roles are byte-identical to
102
+ `DEFAULT_ROLES` until a user opts in. The registry (`FEATURES` in
103
+ `src/features.ts`) is the single source of truth and `expandFeatures` the
104
+ single precedence implementation (`effective def = shipped default ⊕
105
+ preset(enabled flags) ⊕ explicit user keys`; a per-role `false` beats a global
106
+ `true`; an explicit knob always wins). Grounding on the 2026-10-09 cache
107
+ (`default` role, 146 eligible; the `providerPinning` row is re-measured on the
108
+ 2026-10-11 cache):
109
+
110
+ | Flag | Recommended settings | Measured effect |
111
+ |---|---|---|
112
+ | `endpointCeilings` | `filters.tools: true`, `filters.minOutputTokens: 16384` | 146 → 135 eligible, leader unchanged (`deepseek-v4.1-flash`, 0.37 $/M) |
113
+ | `cachePricing` | `cacheHitRate: 0.5` | 146 eligible, leader unchanged, its `priceEff` 0.3732 → 0.3522 (−5.6 %) |
114
+ | `providerPinning` | `preferOwnProvider: true`, `filters.preferredMinThroughput: 50` | per-model policy, never changes eligibility; prices each model on its **best route** (max `vfm` among the filter-passing, non-degraded, priced routes, cap-respecting; the soft 50 tok/s floor sorts below-floor routes last; ties by slug then id) — 144 of 144 routed models have a candidate route (2026-10-11), so the soft basis covers the whole routed field |
115
+ | `costCap` | `filters.maxPriceUsdPerM: 10` | 146 → 137 eligible (the top ≈6 %), leader unchanged; pool p90 7.8, p95 11.1 $/M, every leader 0.20–3.71 $/M |
116
+
117
+ `providerPinning`'s recommended setting is a **soft per-model policy**
118
+ (`preferOwnProvider`), not a hard `providerPin`: a hard pin would make a model
119
+ with no matching route ineligible and empty most of a role's pool, so the
120
+ policy prices a model on its **best route** — the max-`vfm` route (the
121
+ value-for-money score, with the soft 50 tok/s floor sorting below-floor routes
122
+ last) among the filter-passing, non-degraded, priced routes (thinking-adjusted
123
+ price ≤ `maxPriceUsdPerM`; ties by slug then id) — and falls back to the
124
+ `1/price²` blend only when the model has no candidate route. The basis is soft:
125
+ it never changes eligibility, and on the 2026-10-11 cache every one of the 144
126
+ routed models has a candidate route, so the capability covers the whole routed
127
+ field (the old own-lab slug-prefix rule matched only 51 of 149). `minContextTokens`
128
+ is deliberately not in the `endpointCeilings` bundle (an absolute floor cannot
129
+ express the video's fraction-of-the-model's-own-window complaint). The flags
130
+ are settable from `create-role.ts`, `/create-agent`, `/project-roles`
131
+ (`--feature`/`--list-features`) and the explorer's Features panel; the ranking
132
+ numbers are unchanged until a flag is turned on.
133
+
134
+ - Cross-source axis (2026-10-07, issue #38): the focus-metric gate gained the
135
+ cross-source axis — the zeroeval per-benchmark payload's **second, independent**
136
+ set of the price/throughput/context quantities (`input_cost_per_million`/
137
+ `output_cost_per_million`/`speed_rps`/`context_window`, read by
138
+ `parseBenchmarkPayloadMeta` onto `BenchmarkPayloadMeta.crossSource`, keyed by
139
+ the entry's `model_id`) compared against the plugin's own `price`/`context`.
140
+ The join is **direct on the bare id** (the generic llm-stats benchmark's
141
+ `entries[].model_id` is the llm-stats id, the same space as `Model.id`); the
142
+ axis reports the median relative divergence per axis against
143
+ `PRICE_AGREEMENT_TOLERANCE` (0.5, a **chosen, tunable heuristic**).
144
+ `speedAgreement` is **informational only** and excluded from `status` (the
145
+ units differ — rps vs tok/s — and re-deriving throughput from `speed_rps` is
146
+ out of scope). It is an **annotation, never a gate** (`belowBarReason` has no
147
+ cross-source branch) and it never feeds `price`/`weightPrice` or any ranking
148
+ input — the `1/price²` blend is unchanged (a test pins `rankRole`
149
+ byte-identical with the annotation on). It is `unknown` when no model joined or
150
+ the payload carried none of the four fields. The ranking numbers are unchanged
151
+ — the axis is a create-agent/explorer annotation, not a weight.
152
+
153
+ - Provenance axis (2026-10-07, issue #37): the focus-metric gate gained the
154
+ provenance axis — the benchmark's owner (the catalog row's `dataset_org_id`,
155
+ falling back to `dataset_slug`) and the source's own per-entry org mix (the
156
+ dominant lab and its share, read from each row's `organization_id`, falling
157
+ back to `provider_id`), below-bar above `FOCUS_PROVENANCE_DOMINANT_SHARE` (0.5,
158
+ a **chosen, tunable heuristic** — the per-entry org mix is not measurable from
159
+ the repo's caches at implementation time). It is the **source's own claim**
160
+ about who publishes the benchmark and whose scores it carries: an independent
161
+ cross-check on the pool-derived `composition` axis (whose rule is unchanged),
162
+ with `compositionAgreement` recording whether the two concur (`disagree` when
163
+ exactly one flags `below-bar`, `agree` when both or neither, `unknown` when the
164
+ payload carries no org mix). It is an **annotation, never a gate**
165
+ (`belowBarReason` has no provenance branch), so a vendor-populated source is
166
+ warned but never drops a discovered candidate; it is `unknown` when there is
167
+ neither an owner nor a payload org mix. The three catalog fields (`datasetId`/
168
+ `datasetOrgId`/`datasetSlug`) ride `BenchmarkCatalogEntry` behind the cache
169
+ shape guard (a pre-#37 cache is refetched); the per-entry org mix rides
170
+ `BenchmarkPayloadMeta.provenance`. The ranking numbers are unchanged — the axis
171
+ is a create-agent/explorer annotation, not a weight.
172
+
173
+ - Maintenance axis (2026-10-07, issue #36): the focus-metric gate gained the
174
+ maintenance axis — the catalog row's `updated_at` age in months against
175
+ `FOCUS_DATASET_STALENESS_MONTHS` (12), with `version_count`/`star_count`
176
+ carried as supporting detail. This is the **dataset-date** axis, distinct from
177
+ the model-date `freshness` axis: a benchmark whose dataset was last touched
178
+ years ago but whose rows include one recent model passes freshness and is
179
+ caught here. It is an **annotation, never a gate** (`belowBarReason` has no
180
+ maintenance branch), so a stale dataset is warned but never drops a discovered
181
+ candidate; it is `unknown` when the catalog row is absent or its `updated_at`
182
+ is missing/unparseable. The four fields (`updatedAt`/`versionCount`/
183
+ `latestVersionRowCount`/`starCount`) ride `BenchmarkCatalogEntry` behind the
184
+ cache shape guard (a pre-#36 cache is refetched). The ranking numbers are
185
+ unchanged — the axis is a create-agent/explorer annotation, not a weight.
186
+
187
+ - Modality axis (2026-10-07, issue #35): the focus-metric gate gained the
188
+ modality axis — the catalog row's `modality` (text 514, multimodal 194, image 19,
189
+ audio 9, video 2, null 7 of 745) and the payload's per-entry `multimodal`
190
+ share. It is an **annotation, never a gate**: there is no role modality field,
191
+ so the axis is never `below-bar` and never a drop reason; it is `unknown` only
192
+ when neither the catalog row nor the payload carries a modality signal. The
193
+ catalog row's `modality` rides `BenchmarkCatalogEntry` behind the cache shape
194
+ guard (a pre-#35 cache is refetched); the per-entry `multimodal` rides
195
+ `BenchmarkPayloadMeta.modality`. The ranking numbers are unchanged — the axis
196
+ is a create-agent/explorer annotation, not a weight.
197
+
198
+ - Trust axis (2026-10-07, issue #34): the focus-metric gate gained a fifth axis —
199
+ the per-entry `self_reported` share of a benchmark payload, below-bar above
200
+ `FOCUS_SELF_REPORTED_MAX_SHARE` (0.5). Measured live: `verified` is uniformly
201
+ `false`; `self_reported` varies (gpqa 19/20, deepswe-1.1 14/20,
202
+ terminal-bench-4.0 10/20, aa-omniscience-index 2/3, automationbench-aa 0/1, the
203
+ general benchmarks ~20/20), so a strict-majority bar flags the mostly
204
+ vendor-submitted sources while passing an independently measured one. The
205
+ catalog row's `is_community` (20/745 true) rides `BenchmarkCatalogEntry` behind
206
+ a cache shape guard and is surfaced on the trust axis as `community` (issue
207
+ #43), so a community-submitted benchmark is visible even before its scores are
208
+ loaded. The ranking numbers are unchanged — the axis is a
209
+ create-agent discovery gate, not a weight.
210
+
211
+ - Explorer scope switcher (2026-10-06, issue #28): the explorer is no longer
212
+ user-level only. A **scope** is a role-config source — the user-level lock
213
+ file, or one project where `/project-roles` was used. `startExplorer` resolves
214
+ the scope list (the user-level scope, the projects in the new global registry
215
+ `llm-role-projects.json`, and the session's project when its cwd has a project
216
+ role config) and opens on the session's project when present, else user-level.
217
+ A project scope reads the project lock merged over the user-level lock — the
218
+ updater's own read — and Export writes that scope's lock file through the same
219
+ validate → backup → atomic path. The ranking dataset is loaded once as the
220
+ union of every known scope's resolved roles, so a metric only one scope
221
+ weights (a project-only declared `bench:<id>`) is fetched and switching is
222
+ free. The registry is written by the project-aware `runUpdater` immediately
223
+ after `resolveProjectDir()` and before any early return (guarded only by
224
+ `dryRun`), so a day-stamped no-op session start still registers; entries whose
225
+ project lock file is gone are pruned on the next write. New `POST /api/scope`;
226
+ `GET /api/bootstrap` gains `scopes`/`activeScope`. Verified live (headless
227
+ Chromium): the header lists both scopes, the project is selected by default,
228
+ switching to user drops the project role and repoints the lock path, and a
229
+ registry entry with no lock file renders disabled with a reason tooltip.
230
+
231
+ - Measurement wave (2026-10-06, issues #31–#33): three candidate axes were
232
+ measured and **not** landed — guardrail/alignment (#31: the semantically
233
+ relevant catalog entries sit at one model, the best-covered candidate covers
234
+ 5.0% of the field vs the 35% floor, and the safety/security entries that carry
235
+ data are collinear with `general`, r 0.89–1.00), per-task cost (#32: no
236
+ per-task or per-role token profile exists — the only per-model profile the
237
+ plugin already has is OpenRouter's platform-wide traffic, ~10–14× the plugin's
238
+ own input/req, so the $/M price stays the price axis), and agentic multi-agent
239
+ axes (#33: no catalog benchmark measures delegation/teams/handoffs/swarms/
240
+ recovery/a2a, the one multi-agent eval is below the 3-model floor, and the
241
+ 20-entry endpoint cap makes the coverage bar unreachable). Numbers and verdicts
242
+ in `scoring.md`.
243
+
244
+ - Endpoints cache shape guard (2026-10-06, issue #30): `readEndpointsCache` now
245
+ also rejects a cache whose records lack `cacheReadPrice` (alongside
246
+ `weightPrice`/`contextLength`), so a cache written before the cache-read
247
+ pricing landed is refetched instead of leaving every route's cache-read price
248
+ `undefined` and a role's `cacheHitRate` silently inert. The fix forces a
249
+ refetch on the first run after the upgrade; the refreshed measurement is
250
+ 400 models, throughput-matched 152/400, priced 151.
251
+
252
+ - Wave (2026-10-06): the `1/price²` blend now weights routes by the **input
253
+ (prompt) price** — the router's sort key — while reporting the billed 3:1 blend
254
+ under that distribution (issue #21), so prices moved; endpoint capability
255
+ filters (#18), the four-axis benchmark-quality gate (#17), provider pinning
256
+ (#19) and route-aware pricing (#20) landed. The domain-knowledge (#23) and
257
+ truthfulness (#24) axes were measured and **not** landed (`scoring.md`). Fresh
258
+ measurement: 400 models, throughput-matched 152/400, priced 151. Eligible:
259
+ `default`/`smol`/`slow`/`plan`/`commit`/`tiny`/`task`/`advisor` 143, `vision`
260
+ 75, `designer` 88. Leaders: `default`/`task`/`designer` DeepSeek-V4.1-Flash,
261
+ `smol`/`commit` Muse Spark 1.1 (was DeepSeek-V4.1-Flash), `slow`/`plan`/
262
+ `advisor` Muse Spark 1.3, `vision` Qwen3.8 Flash, `tiny` Muse Spark 1.1 (was
263
+ Gemini 3.1 Flash-Lite).
264
+
265
+ - `plan` cost posture (2026-10-03, issue #16): `plan`'s `price` raised 0.12 → 0.25
266
+ (λ 0.00682 → 0.01667) so the posture binds on the **reachable** pool — the pool
267
+ the selector is chosen from. Measured reachable leader-flip w_price 0.2175 (Hy4
268
+ preview → DeepSeek-V4.1-Flash) vs full-pool 0.311 (Muse Spark 1.3 → Qwen3.8
269
+ Flash); 0.25 flips the actionable pick while the full-pool #1 is unchanged.
270
+ Non-price weights rescaled by 0.852273: `general` 0.6, `long_context` 0.116676,
271
+ `throughput` 0.033324 (Σ(non-price) = 0.75 = 1 − price; `q` unchanged). The
272
+ reachable flip rides on `long_context` imputation (39% coverage; DeepSeek's
273
+ index is null → capability fill 0.195, not a measured weakness). Full-pool
274
+ `plan` #1 stays Muse Spark 1.3 (value 0.837 → 0.801); reachable #1 Hy4 preview →
275
+ DeepSeek-V4.1-Flash. The other nine roles are untouched.
276
+
277
+ - Keyed-catalog availability fast path (2026-10-03, issue #15): the updater now consumes
278
+ the shared `KeyAvailability` primitive — when the account's OpenRouter "Filter the
279
+ model catalog for API keys" setting is on, allowed candidates skip the probe and
280
+ key-blocked candidates are pruned from the walk order (the budget counts candidates
281
+ examined, so pre-seeding a blocked verdict would not save it); candidates in neither
282
+ catalog are still probed, and `no-filter`/`unavailable` degrade to the probe walk with
283
+ no abort path. Measured on this account (setting on): keyed `GET /api/v1/models`
284
+ returned 108 rows vs 466 public; after `rankingIdOf` normalization 107 keyed ranking
285
+ ids ⊆ 454 public, 347 blocked. A 6-blocked/6-allowed probe cross-check agreed 6/6
286
+ blocked and 5/6 allowed (the sixth probed `unknown`, which counts as usable).
287
+
288
+ - Role-weight rebalance (2026-10-03, issue #13): `math` dropped from `plan` and
289
+ `slow` (collinear with `reasoning`, r 0.919, and missing for 18% of the pool —
290
+ a coverage penalty, not a quality signal) and its share moved to the
291
+ consolidated `general` axis (capability-preserving, 100% coverage), not
292
+ `throughput` (which would have tripled `slow`'s speed weight and flipped both
293
+ leaders to a cheap/fast model). `general`+`reasoning` merged into `general`
294
+ in `default`/`slow`/`plan`/`advisor` (a pure relabel: `general`~`reasoning`
295
+ r 0.984, so the combined capability share is unchanged). `smol`/`commit`
296
+ `price` raised 0.30/0.35 → 0.45 (the measured binding threshold; non-price
297
+ weights rescaled proportionally, so it is a posture change, not a capability
298
+ change). `plan`'s description drops the now-unweighted `math`. New weights:
299
+ `default` general 0.4926/code 0.1705/agents 0.1137/tool_calling 0.0947/
300
+ throughput 0.0285/price 0.10; `slow` general 0.60/code 0.18/agents 0.13/
301
+ throughput 0.04/price 0.05; `plan` general 0.704/long_context 0.1369/
302
+ throughput 0.0391/price 0.12 (superseded by issue #16, above); `advisor`
303
+ general 0.5947/long_context 0.1004/
304
+ price 0.20/throughput 0.1049; `smol` price 0.45/throughput 0.22/general
305
+ 0.1571/code 0.0943/tool_calling 0.0786; `commit` price 0.45/throughput
306
+ 0.2285/general 0.2369/code 0.0846. Leaders: `default` DeepSeek-V4.1-Flash
307
+ (unchanged), `slow`/`plan` → Muse Spark 1.3, `advisor` Muse Spark 1.3
308
+ (unchanged), `smol`/`commit` → DeepSeek-V4.1-Flash. `required`, `thinking`,
309
+ `filters`, `enabled` and the `vision`/`tiny`/`task`/`designer` weights are
310
+ unchanged.
311
+
312
+ - Designer weight reconciliation (2026-10-03, issue #12): `designer` `code`
313
+ 0.10 → 0.06 (collinear with `website`, r 0.890, so it double-counted one
314
+ capability axis) and the freed 0.04 moved to the independent axes (`price`
315
+ +0.02, `throughput` +0.02), per Scoring rule 5. New weights: general 0.26/
316
+ code 0.06/vision 0.18/throughput 0.15/price 0.17/website 0.18 (Σ = 1.00,
317
+ Σ(non-price) = 0.83 = 1 − 0.17); λ 0.00882 → 0.010241. The `design` archetype
318
+ now derives its `weights`/`required` from `DEFAULT_ROLES.designer` by
319
+ reference (the `general` archetype's pattern), so the two design weightings
320
+ cannot diverge. `required`, `thinking`, `filters.image`, `enabled` and the
321
+ description are unchanged; the leader is unchanged (a refinement, not a
322
+ re-ranking).
323
+
324
+ - Coverage-safe benchmark discovery + agent-write hardening in `/create-agent`
325
+ (2026-10-03, issue #11): discovery now refuses a benchmark that would turn `q`
326
+ into a coverage score — a fill-0 metric whose catalog coverage is below 35% of
327
+ the ranking field is dropped (non-fatal, with a reason), while a capability-
328
+ filled metric (`writing`/`website`/`bench:<id>`, fill 0.195) stays weightable at
329
+ any coverage. The focus set is capped at 3 (priority order preserved) so one
330
+ benchmark keeps a decisive share. The handler loads the ranking universe once
331
+ (the same daily-cached chain the updater/explorer use) for the field size,
332
+ per-metric coverage, and the differentiation comparison; a failed load skips
333
+ the share rule (fill rule still applies, coverage annotated `unknown`). The
334
+ report prints each focus metric's coverage and warns below the bar; `--dry-run`
335
+ and `--json` carry the same signal. Free-text extraction no longer folds
336
+ `general`/`price`/`throughput` (every archetype weights them) but keeps
337
+ `reasoning`. The agent file is written **before** the role, and a failed role
338
+ write rolls it back (restoring the prior file on a `--force` re-create, else
339
+ removing the new one), so a partial failure never leaves a dangling role, a
340
+ retry needs no `--force`, and the user's prior agent is never destroyed. The
341
+ step-3 benchmark prompt previews the request's resolved weights (`resolveRole`),
342
+ so the "already in this role's weights" marker appears live. The vendored
343
+ architect prompt carries a source-version marker (`ARCHITECT_PROMPT_VERSION`)
344
+ asserted by a test. Unit-tested in `tests/create-agent.test.ts` and
345
+ `tests/architect-provenance.test.ts`.
9
346
 
10
347
  - Benchmark discovery in `/create-agent` (2026-10-02, issue #10): when a purpose
11
348
  is given, the command fetches the llm-stats benchmark catalog (745 rows,
@@ -44,13 +381,15 @@ are the 2026-10-02 measurement and are not re-derived on read.
44
381
  explorer's `/api/bootstrap` listed the external metric with its derived
45
382
  metadata. Unit-tested in `tests/benchmark-sources.test.ts`.
46
383
 
47
- - Release (2026-10-02): `Gott50/omp-llm-role` is public and `main` is pushed
48
- (`cc120bb`). The git and marketplace install routes were re-verified against
49
- the real URLs in throwaway HOMEs (omp 18.4.10): both installed 1.0.0, and each
50
- post-install session run wrote `modelRoles` + `retry.fallbackChains` into that
51
- HOME's `config.yml` and landed the three daily caches in the installed copy.
52
- npm is not published yet (name unclaimed, no credential on the workstation) —
53
- see Releasing.
384
+ - Release (2026-10-03): `Gott50/omp-llm-role` is public, `main` is pushed, and
385
+ `omp-llm-role@1.0.0` is published to npm (`npm view omp-llm-role version` →
386
+ `1.0.0`). All three install routes were verified against the real URLs in
387
+ throwaway HOMEs (omp 18.4.12): npm (`omp plugin install omp-llm-role`), git
388
+ (`github:Gott50/omp-llm-role`) and marketplace
389
+ (`omp-llm-role@gott50-plugins`) each installed 1.0.0, and each post-install
390
+ session run wrote `modelRoles` + `retry.fallbackChains` into that HOME's
391
+ `config.yml` and landed the three daily caches in the installed copy. See
392
+ Releasing.
54
393
 
55
394
  - `/create-agent` free-text form (2026-10-02): the command now accepts a
56
395
  plain-language request as well as flags. `parseCreateAgentInput` splits the
@@ -314,18 +653,18 @@ are the 2026-10-02 measurement and are not re-derived on read.
314
653
  0.663, margin 0.039 > `switchMargin`), because the incumbent has no Design
315
654
  Arena data and takes the neutral fill while MiMo carries a measured 0.97.
316
655
  The other nine roles are untouched (only `designer` weights `website`).
317
- - 400 llm-stats models; OpenRouter matched 152/400 (throughput), 151 priced.
318
- - Eligible per role: 143 (vision 75, designer 88, image-input filter).
656
+ - 408 llm-stats models; OpenRouter matched 155/408 (throughput), 153 priced (the
657
+ 2026-10-08 snapshot read 155/407 — the daily cache moved, not the code; the
658
+ #34–#40 axes and flags leave every ranking byte-identical until a flag is
659
+ turned on).
660
+ - Eligible per role: 146 (vision 75, designer 89, image-input filter).
319
661
  - Value-ranking leaders (this report, thinking-adjusted prices; the *ranking*
320
662
  leaders, before the account's provider whitelist drops the blocked ones):
321
- `default` DeepSeek-V4.1-Flash (0.810), `smol` Muse Spark 1.1 (0.740, blocked
322
- → DeepSeek), `slow` GLM-5.3 (0.816), `vision` Qwen3.8 Flash (0.731, blocked
323
- → DeepSeek), `plan` Qwen3.8 Flash (0.751), `commit` Muse Spark 1.1 (0.768,
324
- blocked → DeepSeek), `tiny` Muse Spark 1.1 (0.779, blocked → Ling Fin),
325
- `task` DeepSeek-V4.1-Flash (0.759), `advisor` Muse Spark 1.3 (0.788,
326
- blocked → Hy4), `designer` DeepSeek-V4.1-Flash (0.787). The suffix pass moved
327
- `default` and `advisor` onto their reachable leaders (Muse Spark 1.3 and
328
- GPT-5.6 Sol were blocked).
663
+ `default` DeepSeek-V4.1-Flash (0.810), `smol` DeepSeek-V4.1-Flash (0.729),
664
+ `slow` DeepSeek-V4.1-Flash (0.832), `vision` DeepSeek-V4.1-Flash (0.730),
665
+ `plan` Muse Spark 1.3 (0.793), `commit` DeepSeek-V4.1-Flash (0.762), `tiny`
666
+ Muse Spark 1.1 (0.791), `task` DeepSeek-V4.1-Flash (0.754), `advisor`
667
+ Muse Spark 1.3 (0.787), `designer` DeepSeek-V4.1-Flash (0.787).
329
668
  - Thinking-adjusted pricing landed (2026-09-27): the suffix table moved into
330
669
  `DEFAULT_ROLES` as a per-role `thinking` field, and the price axis scales by
331
670
  the level's factor for thinking-capable models — `slow` (`:max`, ×7.86)
@@ -373,8 +712,9 @@ are the 2026-10-02 measurement and are not re-derived on read.
373
712
  cannot re-add keys outside `settings.roles` `patchModelRoles` selects. The
374
713
  owner-written `retry.fallbackChains` key
375
714
  `openrouter/~deepseek/deepseek-v4-flash-latest` is now unreferenced by any
376
- role; it is left in place deliberately (SPEC §6.4: unwritten, unreferenced
377
- keys are never pruned), so removing it is a manual call.
715
+ role; it is left in place deliberately (unwritten, unreferenced keys are never
716
+ pruned — see [`writes-and-state.md`](writes-and-state.md)), so removing it is a
717
+ manual call.
378
718
  - Plugin verified live (2026-09-23) with the provider-allowlist probe: the
379
719
  account's allowed-providers whitelist excludes first-party openai/azure/
380
720
  anthropic endpoints, so the probe gate rewrote `slow` → GLM-5.3 (`:max`),