modelrig 0.11.0 → 0.12.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 (199) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/dist/adapters/anthropic.d.ts +4 -4
  3. package/dist/adapters/anthropic.d.ts.map +1 -1
  4. package/dist/adapters/anthropic.js +8 -1
  5. package/dist/adapters/deepseek.d.ts +2 -4
  6. package/dist/adapters/deepseek.d.ts.map +1 -1
  7. package/dist/adapters/deepseek.js +9 -5
  8. package/dist/adapters/gemini.d.ts +4 -1
  9. package/dist/adapters/gemini.d.ts.map +1 -1
  10. package/dist/adapters/gemini.js +16 -1
  11. package/dist/adapters/grok.d.ts +2 -4
  12. package/dist/adapters/grok.d.ts.map +1 -1
  13. package/dist/adapters/grok.js +8 -4
  14. package/dist/adapters/history-mapping.d.ts +126 -0
  15. package/dist/adapters/history-mapping.d.ts.map +1 -0
  16. package/dist/adapters/history-mapping.js +230 -0
  17. package/dist/adapters/openai-compat.d.ts +2 -4
  18. package/dist/adapters/openai-compat.d.ts.map +1 -1
  19. package/dist/adapters/openai-compat.js +9 -5
  20. package/dist/adapters/openai.d.ts +2 -4
  21. package/dist/adapters/openai.d.ts.map +1 -1
  22. package/dist/adapters/openai.js +8 -4
  23. package/dist/adapters/responses-api.d.ts.map +1 -1
  24. package/dist/adapters/responses-api.js +9 -4
  25. package/dist/artifacts/artifact.d.ts.map +1 -1
  26. package/dist/artifacts/artifact.js +79 -0
  27. package/dist/artifacts/conversation.d.ts +128 -0
  28. package/dist/artifacts/conversation.d.ts.map +1 -0
  29. package/dist/artifacts/conversation.js +172 -0
  30. package/dist/artifacts/index.d.ts +1 -1
  31. package/dist/artifacts/index.d.ts.map +1 -1
  32. package/dist/artifacts/media.d.ts +46 -0
  33. package/dist/artifacts/media.d.ts.map +1 -0
  34. package/dist/artifacts/media.js +88 -0
  35. package/dist/artifacts/run-context.d.ts +61 -2
  36. package/dist/artifacts/run-context.d.ts.map +1 -1
  37. package/dist/artifacts/run-context.js +183 -12
  38. package/dist/artifacts/scrub.d.ts +3 -0
  39. package/dist/artifacts/scrub.d.ts.map +1 -1
  40. package/dist/bakeoff/engine.d.ts.map +1 -1
  41. package/dist/bakeoff/engine.js +34 -7
  42. package/dist/bakeoff/eval-cases.d.ts +5 -0
  43. package/dist/bakeoff/eval-cases.d.ts.map +1 -1
  44. package/dist/bakeoff/eval-cases.js +10 -1
  45. package/dist/bakeoff/replay.d.ts +15 -0
  46. package/dist/bakeoff/replay.d.ts.map +1 -1
  47. package/dist/bakeoff/replay.js +19 -1
  48. package/dist/bakeoff/report.d.ts.map +1 -1
  49. package/dist/bakeoff/report.js +9 -2
  50. package/dist/bakeoff/turn-replay.d.ts +93 -0
  51. package/dist/bakeoff/turn-replay.d.ts.map +1 -0
  52. package/dist/bakeoff/turn-replay.js +150 -0
  53. package/dist/bakeoff/types.d.ts +12 -0
  54. package/dist/bakeoff/types.d.ts.map +1 -1
  55. package/dist/cli.d.ts.map +1 -1
  56. package/dist/cli.js +1 -0
  57. package/dist/config.d.ts +18 -0
  58. package/dist/config.d.ts.map +1 -1
  59. package/dist/config.js +23 -3
  60. package/dist/doctor-preflight.d.ts.map +1 -1
  61. package/dist/doctor-preflight.js +6 -1
  62. package/dist/feedback/grade.d.ts +7 -2
  63. package/dist/feedback/grade.d.ts.map +1 -1
  64. package/dist/feedback/grade.js +14 -0
  65. package/dist/feedback/outcome.d.ts +103 -0
  66. package/dist/feedback/outcome.d.ts.map +1 -0
  67. package/dist/feedback/outcome.js +175 -0
  68. package/dist/gateway/history-bridge.d.ts +32 -0
  69. package/dist/gateway/history-bridge.d.ts.map +1 -0
  70. package/dist/gateway/history-bridge.js +64 -0
  71. package/dist/gateway/serve-client.d.ts.map +1 -1
  72. package/dist/gateway/serve-client.js +6 -0
  73. package/dist/index.d.ts +44 -1
  74. package/dist/index.d.ts.map +1 -1
  75. package/dist/index.js +110 -9
  76. package/dist/init/init-cli.d.ts.map +1 -1
  77. package/dist/init/init-cli.js +21 -4
  78. package/dist/init/observe-cli.js +1 -1
  79. package/dist/init/observe.d.ts +7 -1
  80. package/dist/init/observe.d.ts.map +1 -1
  81. package/dist/init/observe.js +74 -22
  82. package/dist/init/starter.d.ts +36 -0
  83. package/dist/init/starter.d.ts.map +1 -0
  84. package/dist/init/starter.js +85 -0
  85. package/dist/registry/build.d.ts +4 -0
  86. package/dist/registry/build.d.ts.map +1 -1
  87. package/dist/registry/generation-models.d.ts +68 -0
  88. package/dist/registry/generation-models.d.ts.map +1 -0
  89. package/dist/registry/generation-models.js +168 -0
  90. package/dist/registry/model-kind.d.ts +34 -0
  91. package/dist/registry/model-kind.d.ts.map +1 -0
  92. package/dist/registry/model-kind.js +40 -0
  93. package/dist/registry/pricing-shape.d.ts +29 -1
  94. package/dist/registry/pricing-shape.d.ts.map +1 -1
  95. package/dist/registry/pricing-shape.js +9 -0
  96. package/dist/registry/pricing.d.ts +26 -0
  97. package/dist/registry/pricing.d.ts.map +1 -1
  98. package/dist/registry/pricing.js +29 -0
  99. package/dist/registry/rows.d.ts.map +1 -1
  100. package/dist/registry/rows.js +3 -1
  101. package/dist/registry/unit-pricing.d.ts +32 -0
  102. package/dist/registry/unit-pricing.d.ts.map +1 -0
  103. package/dist/registry/unit-pricing.js +62 -0
  104. package/dist/routes/input-loader.d.ts +38 -0
  105. package/dist/routes/input-loader.d.ts.map +1 -0
  106. package/dist/routes/input-loader.js +80 -0
  107. package/dist/routes/loader.d.ts.map +1 -1
  108. package/dist/routes/loader.js +3 -0
  109. package/dist/routes/resolved-bundle.d.ts +2 -0
  110. package/dist/routes/resolved-bundle.d.ts.map +1 -1
  111. package/dist/routes/resolved-bundle.js +3 -0
  112. package/dist/run/cost.d.ts +28 -0
  113. package/dist/run/cost.d.ts.map +1 -1
  114. package/dist/run/cost.js +74 -0
  115. package/dist/run/dispatch.d.ts +4 -1
  116. package/dist/run/dispatch.d.ts.map +1 -1
  117. package/dist/run/dispatch.js +3 -0
  118. package/dist/run/limits.d.ts +88 -0
  119. package/dist/run/limits.d.ts.map +1 -0
  120. package/dist/run/limits.js +155 -0
  121. package/dist/run/messages.d.ts +199 -0
  122. package/dist/run/messages.d.ts.map +1 -0
  123. package/dist/run/messages.js +430 -0
  124. package/dist/run/raw-finalize.d.ts +5 -2
  125. package/dist/run/raw-finalize.d.ts.map +1 -1
  126. package/dist/run/raw-finalize.js +8 -5
  127. package/dist/run/raw-request.d.ts.map +1 -1
  128. package/dist/run/raw-request.js +2 -1
  129. package/dist/run/raw.d.ts +10 -4
  130. package/dist/run/raw.d.ts.map +1 -1
  131. package/dist/run/raw.js +11 -3
  132. package/dist/run/rawonly.d.ts +29 -5
  133. package/dist/run/rawonly.d.ts.map +1 -1
  134. package/dist/run/rawonly.js +54 -19
  135. package/dist/run/record-attempt.d.ts.map +1 -1
  136. package/dist/run/record-attempt.js +16 -6
  137. package/dist/run/record-call.d.ts +13 -2
  138. package/dist/run/record-call.d.ts.map +1 -1
  139. package/dist/run/record-call.js +50 -7
  140. package/dist/run/run.d.ts.map +1 -1
  141. package/dist/run/run.js +12 -8
  142. package/dist/telemetry/attempt-meta.d.ts +49 -1
  143. package/dist/telemetry/attempt-meta.d.ts.map +1 -1
  144. package/dist/telemetry/attempt-meta.js +45 -1
  145. package/dist/telemetry/exporter.d.ts.map +1 -1
  146. package/dist/telemetry/exporter.js +2 -1
  147. package/dist/telemetry/grade-mappers.d.ts +1 -1
  148. package/dist/telemetry/grade-mappers.d.ts.map +1 -1
  149. package/dist/telemetry/grade-mappers.js +7 -1
  150. package/dist/telemetry/grade-task.d.ts +49 -0
  151. package/dist/telemetry/grade-task.d.ts.map +1 -0
  152. package/dist/telemetry/grade-task.js +85 -0
  153. package/dist/telemetry/grades-sql.d.ts +16 -1
  154. package/dist/telemetry/grades-sql.d.ts.map +1 -1
  155. package/dist/telemetry/grades-sql.js +64 -4
  156. package/dist/telemetry/ingest-protocol.d.ts +24 -1
  157. package/dist/telemetry/ingest-protocol.d.ts.map +1 -1
  158. package/dist/telemetry/ingest-protocol.js +72 -43
  159. package/dist/telemetry/otel-content.d.ts +31 -0
  160. package/dist/telemetry/otel-content.d.ts.map +1 -0
  161. package/dist/telemetry/otel-content.js +106 -0
  162. package/dist/telemetry/otel-hierarchy.d.ts +57 -0
  163. package/dist/telemetry/otel-hierarchy.d.ts.map +1 -0
  164. package/dist/telemetry/otel-hierarchy.js +155 -0
  165. package/dist/telemetry/otel-normalize.d.ts +15 -1
  166. package/dist/telemetry/otel-normalize.d.ts.map +1 -1
  167. package/dist/telemetry/otel-normalize.js +24 -39
  168. package/dist/telemetry/sinks.d.ts +4 -1
  169. package/dist/telemetry/sinks.d.ts.map +1 -1
  170. package/dist/telemetry/sinks.js +53 -34
  171. package/dist/telemetry/store-migrations.d.ts.map +1 -1
  172. package/dist/telemetry/store-migrations.js +8 -0
  173. package/dist/telemetry/store.d.ts +9 -0
  174. package/dist/telemetry/store.d.ts.map +1 -1
  175. package/dist/telemetry/store.js +10 -0
  176. package/dist/telemetry/uniform-rows.d.ts +43 -0
  177. package/dist/telemetry/uniform-rows.d.ts.map +1 -0
  178. package/dist/telemetry/uniform-rows.js +139 -0
  179. package/dist/types/failures-teaching.d.ts.map +1 -1
  180. package/dist/types/failures-teaching.js +11 -0
  181. package/dist/types/failures.d.ts +1 -1
  182. package/dist/types/failures.d.ts.map +1 -1
  183. package/dist/types/failures.js +2 -0
  184. package/dist/types/provider.d.ts +12 -0
  185. package/dist/types/provider.d.ts.map +1 -1
  186. package/dist/types/route.d.ts +13 -0
  187. package/dist/types/route.d.ts.map +1 -1
  188. package/dist/types/telemetry.d.ts +14 -1
  189. package/dist/types/telemetry.d.ts.map +1 -1
  190. package/docs/agent-skill/SKILL.md +122 -12
  191. package/docs/how-it-fits.md +4 -4
  192. package/docs/instrumentation.md +148 -44
  193. package/docs/llms.txt +143 -13
  194. package/docs/mcp-oracle.md +15 -4
  195. package/docs/quickstart.md +129 -16
  196. package/package.json +4 -2
  197. package/registry/registry.json +456 -0
  198. package/templates/creative-approval.json +81 -0
  199. package/templates/support-conversation.json +82 -0
@@ -1,8 +1,17 @@
1
1
  # The MCP oracle
2
2
 
3
- > `modelrig-oracle` is published on npm (0.1.0, 2026-08-21) — the
4
- > `npx modelrig-oracle` commands on this page work everywhere. Nothing else on
3
+ > `modelrig-oracle` is published on npm (0.4.0, 2026-10-25) — the
4
+ > `npx modelrig-oracle` commands on this page work everywhere. The write tools
5
+ > below (grading guides, `record_grade`, `get_call_config`, the read resources)
6
+ > need **0.3.0 or later**; `record_outcome`, `query_conversations` and
7
+ > `query_registry`'s `kind` filter need **0.4.0 or later**. If you ran an earlier
8
+ > version, refresh once with `npx -y modelrig-oracle@latest`. Nothing else on
5
9
  > this site depends on it: llms.txt carries the same probed facts as plain text.
10
+ >
11
+ > **Grading guides on an older oracle.** 0.2.0 (2026-08-22) does not know
12
+ > `grading_guide` and refuses a guide-only `set_task_settings` call — there,
13
+ > write the guide with `PATCH /v1/tasks/<task>/settings` instead
14
+ > ([Set up a grader](set-up-a-grader.md)).
6
15
 
7
16
  **What it is:** an MCP server that lets a coding agent ask what models can
8
17
  actually do — measured, dated, and priced by what conformance really costs —
@@ -31,7 +40,7 @@ anonymous record of the question's *shape* (see [Demand signal](#demand-signal))
31
40
 
32
41
  | Tool | Answers |
33
42
  |---|---|
34
- | `query_registry` | "Which models can serve strict JSON under $1/M?" — capability predicates plus a price bound, ranked by effective cost of conformance. |
43
+ | `query_registry` | "Which models can serve strict JSON under $1/M?" — capability predicates plus a price bound, ranked by effective cost of conformance. `kind: image_gen` / `video_gen` / `tts` / `stt` lists the declared-only generation models instead — unprobed, never ranked, with the unit price ModelRig could source. |
35
44
  | `get_leaderboard` | Standings, optionally for one capability. Cheap-but-unreliable ranks below pricier-but-correct, because the ranking is cost per *conformant* output. |
36
45
  | `explain_pricing` | List price for one model, the effective cost of conformance beside it, and why they differ. |
37
46
  | `get_call_notes` | Per-model call-shaping quirks: parameters it rejects, shapes it needs, defaults that surprise. Plus any measured disagreement with the provider's claims. |
@@ -65,7 +74,9 @@ on purpose.
65
74
  | `decide_proposal` | Records a human's approve/reject on a proposal. Needs the `decision` scope. | Execute or apply the swap. |
66
75
  | `execute_swap` | Records an approved swap and **returns** the route-YAML diff to apply via a PR. Needs the `decision` scope. | Write any file; commit to your repo. |
67
76
  | `record_grade` | Attaches a 0–1 grade (with an optional comment) to one inference or a whole run — the signal the next cycle learns from. Needs the `grade` scope on the HTTP path. | Spend a billed call; guess attribution for an unknown subject. |
68
- | `get_task_settings` / `set_task_settings` | Reads / writes a task's governance settings and its **grading guide** (the rubric the task's grader is taught from). `set` needs the `task-settings` scope. | Change route config (the pass threshold lives in the route, and is refused here). |
77
+ | `record_outcome` | Posts a delayed business outcome (resolved, booked, approved, CTR) as a `kind: outcome` grade for a conversation id, run, inference or artifact (`POST /v1/outcomes`); `idempotency_key` makes a retry a no-op. Needs the `ingest` scope. | Calibrate a grader on its own — outcome agreement is a receipt. |
78
+ | `query_conversations` | Lists your agent's conversations (turns, cost, latest grade, outcomes, run-limit stops), or one conversation's turns with `run_id`. Metadata only. | Return message content. |
79
+ | `get_task_settings` / `set_task_settings` | Reads / writes a task's governance settings and its **grading guide** (the rubric the task's grader is taught from). `set` needs the `task-settings` scope. The task must exist: a route (registered on route sync), a `runRaw` / `recordCall` step whose call set `tags.step`, or one created with `POST /v1/tasks` (HTTP only — there is no MCP tool to list or create tasks yet; `GET /v1/tasks` lists them). | Change route config (the pass threshold lives in the route, and is refused here). |
69
80
  | `get_call_config` | Reads a route's **current live** call-config — its version, mode, the model(s) it serves, the `effective` model + why (pin / YAML default), and any `drift`. Read-only. | Change anything. |
70
81
  | `set_call_config` | **Sets what serves a route right now** — pins a model or starts a live A/B — on the human's behalf. A real, versioned control-plane change. | Bypass the eligibility gate or the `route-config` scope; imply it is a draft. |
71
82
  | `rollback_call_config` | Restores a prior call-config version (re-appended as a new version — history is never deleted). | Delete history; escape the scope check. |
@@ -1,4 +1,4 @@
1
- # ModelRig quickstart — see, prove, and save on every model call
1
+ # ModelRig quickstart — See. Grade. Improve.
2
2
 
3
3
  **The question this page answers: how do I get from nothing to my first run on `/runs`, with its steps and artifacts, in about ten minutes?**
4
4
 
@@ -11,11 +11,13 @@ and three things happen at once:
11
11
  artifacts, lineaged and integrity-hashed; see the
12
12
  [instrumentation guide](instrumentation.md)), with cost/tokens/tier and a
13
13
  grade recorded against your tags.
14
- - **Prove** — you get schema-validated output, served only by a model you
15
- authorized, and you can replay that traffic against cheaper candidates in a
14
+ - **Grade** — you get schema-validated output, served only by a model you
15
+ authorized, you attach grades from signals you already have, and you can replay that traffic against cheaper candidates in a
16
16
  bake-off before you change anything.
17
- - **Save** — a winning candidate is a change to your YAML you review; nothing
18
- swaps a model on its own, and your routes are always YAML in your git.
17
+ - **Improve** — a winning candidate is a proposal you review. Never a silent
18
+ swap: automated changes (opt-in) are proven before they apply, and a change
19
+ you promote in ModelRig (console or MCP) is versioned and reversible — or
20
+ merge it as a PR if you keep routes in git.
19
21
 
20
22
  The path is the same whoever does the work: **mint a key → install → confirm the
21
23
  org → define a route → wrap one pipeline execution in a run → see it on `/runs`.**
@@ -102,9 +104,16 @@ import { createRig, loadConfigFromEnv } from "modelrig";
102
104
  Environment (read once, in ModelRig's config layer only):
103
105
 
104
106
  ```bash
105
- GEMINI_API_KEY=… # only providers you configure get adapters
107
+ # Provider keys: only the providers you configure get adapters. Set the ones
108
+ # your routes (or runRaw calls) use; these are the names the SDK reads.
109
+ GEMINI_API_KEY=…
106
110
  OPENAI_API_KEY=…
107
111
  DEEPSEEK_API_KEY=…
112
+ ANTHROPIC_API_KEY=…
113
+ XAI_API_KEY=… # grok
114
+ ZAI_API_KEY=… # Z.ai / Zhipu
115
+ MODELRIG_DEEPINFRA_API_KEY=…
116
+ MODELRIG_FIREWORKS_API_KEY=…
108
117
 
109
118
  # Where your telemetry goes — pick ONE lane:
110
119
  MODELRIG_API_KEY=rig_sk_… # hosted: rows go to ModelRig,
@@ -119,14 +128,14 @@ MODELRIG_ENVELOPE_BUDGET_USD=25 # default envelope budget
119
128
  ### 2. Confirm the org
120
129
 
121
130
  <!-- BLOCK:CONFIRM_ORG -->
122
- **Confirm the org before you trust an empty console.** Run `modelrig doctor` from the same environment your app runs in. It asks GET /v1/whoami which organization the key belongs to, and the name it prints must match the org badge in the console header. If it names a different organization, your data is landing there: switch the console to that organization, or mint a key in the one you meant (https://modelrig.dev/orgs-and-keys.html).
131
+ **Confirm the org before you trust an empty console.** Run `modelrig doctor` from the same environment your app runs in, once your app has started with the key. It asks GET /v1/whoami which organization the key belongs to and prints the org's name and, in parentheses, its slug; the name must match the org badge in the console header. If it names a different organization, your data is landing there: switch the console to that organization, or mint a key in the one you meant (https://modelrig.dev/orgs-and-keys.html).
123
132
 
124
133
  ```bash
125
134
  npx modelrig doctor
126
- # [modelrig] key rig_sk_…AbCd → org «Your Org» ✓ (ingest https://api.modelrig.ai, …)
135
+ # [modelrig] key rig_sk_…AbCd → org «Your Org» (your-org) ✓ (ingest https://api.modelrig.ai, …)
127
136
 
128
- # No CLI in that environment? Ask the API directly:
129
- curl -H "Authorization: Bearer $MODELRIG_API_KEY" https://api.modelrig.ai/v1/whoami
137
+ # No CLI in that environment? Ask the API directly (same host the SDK uses):
138
+ curl -H "Authorization: Bearer $MODELRIG_API_KEY" "${MODELRIG_INGEST_URL:-https://api.modelrig.ai}/v1/whoami"
130
139
  ```
131
140
  <!-- /BLOCK:CONFIRM_ORG -->
132
141
 
@@ -176,6 +185,14 @@ CRITICAL: respond with a single JSON object, no markdown fences.
176
185
  Variables must be declared in `prompt.variables`; referencing an undeclared
177
186
  variable fails at load time, not at run time.
178
187
 
188
+ The route references two files next to it, and both must exist or the route
189
+ fails to load (`schema file not found` / `prompt.system file not found`).
190
+ `npx modelrig init` writes this starter route's two files,
191
+ `modelrig/routes/schemas/support_summarize.schema.json` and
192
+ `modelrig/routes/prompts/support_summarize.system.md`, without overwriting
193
+ files you already have. Then `npx modelrig validate` checks the bundle with no
194
+ provider key.
195
+
179
196
  The `schema` file is a JSON Schema. ModelRig validates output under **JSON
180
197
  Schema 2020-12** as the primary dialect and **also accepts draft-07** (a schema
181
198
  declaring either `$schema` compiles). Validation runs non-strict with union
@@ -185,6 +202,23 @@ unless you have a draft-07 schema already; both work.
185
202
 
186
203
  ### 4. Call it
187
204
 
205
+ No route yet, because you are observing first or calling providers through a
206
+ `rig.runRaw` seam? You don't need one:
207
+
208
+ <!-- BLOCK:RAW_RIG -->
209
+ **No routes yet? Build the rig raw-only.** Observing a pipeline, or recording calls your code makes itself, needs no route files. Construct the rig with `routesDir: null`: `rig.runRaw`, `rig.runRawStream` and `rig.recordCall` work at once, and `rig.run` / `rig.runBundle` refuse with a teaching error until you add routes. Without it, createRig loads ./modelrig/routes: a directory you configured that is missing throws, and a missing default directory falls back to raw-only with a one-line warning.
210
+
211
+ ```ts
212
+ import { createRig, loadConfigFromEnv } from "modelrig";
213
+
214
+ // Raw-only on purpose: no route files needed. rig.runRaw, rig.runRawStream and
215
+ // rig.recordCall work; rig.run needs routes (add them later, drop routesDir: null).
216
+ const rig = createRig({ ...loadConfigFromEnv(), routesDir: null });
217
+ ```
218
+ <!-- /BLOCK:RAW_RIG -->
219
+
220
+ With the route above in place:
221
+
188
222
  ```ts
189
223
  const rig = createRig(loadConfigFromEnv());
190
224
 
@@ -200,7 +234,7 @@ result.meta.costEstimateUsd; // priced from the pinned LiteLLM snapshot
200
234
  result.meta.servedTier; // vs meta.requestedTier — downgrades visible
201
235
  result.meta.attemptsByClass; // retries consumed, per failure class
202
236
 
203
- rig.close();
237
+ await rig.close(); // flushes telemetry; await it once at shutdown (R6)
204
238
  ```
205
239
 
206
240
  ### 5. Wrap it in a run — the standard path
@@ -240,6 +274,37 @@ await rig.close(); // flushes the exporter — call once at shutdown
240
274
 
241
275
  - **Raw lane:** a `rig.runRaw` call inside a run context records a ground-truth
242
276
  step too (since 0.5.0), so grounded/cached pipelines fill the same graph.
277
+ - **A call your code makes itself** (a direct provider client, a stream, an
278
+ embedding) is invisible until you record it:
279
+
280
+ <!-- BLOCK:RECORD_CALL -->
281
+ **Record a call your code makes itself.** A provider call your code makes directly (a client ModelRig has no lane for, a stream you read yourself, an embedding, a fallback) is invisible until you report it. Call `rig.recordCall` right after it returns, inside the run, with the tokens the provider reported: the call shows on /inferences as "Reported by your code", joins its run and step, and counts toward cost. It is synchronous and never throws; invalid input warns once and records nothing (`inferenceId: null`). Spread optional fields only when the provider reported them, so the call type-checks under `exactOptionalPropertyTypes`.
282
+
283
+ ```ts
284
+ const started = Date.now();
285
+ const resp = await genai.models.generateContent({ model: "gemini-3.1-pro-preview", contents });
286
+ const u = resp.usageMetadata;
287
+ const finishReason = resp.candidates?.[0]?.finishReason;
288
+
289
+ const rec = rig.recordCall({
290
+ provider: "gemini",
291
+ model: "gemini-3.1-pro-preview",
292
+ usage: {
293
+ tokensIn: u?.promptTokenCount ?? 0, // total input, cached included
294
+ tokensOut: (u?.candidatesTokenCount ?? 0) + (u?.thoughtsTokenCount ?? 0),
295
+ tokensCached: u?.cachedContentTokenCount ?? 0,
296
+ // optional fields: spread only when the provider reported them
297
+ ...(u?.thoughtsTokenCount !== undefined ? { reasoningTokens: u.thoughtsTokenCount } : {}),
298
+ },
299
+ servedTier: "standard", // the tier that SERVED the call: pass what the provider reports (flex, priority); it is priced at that tier
300
+ latencyMs: Date.now() - started,
301
+ stepKey: "summarize", // the step on /runs (inside run.scope / run.start)
302
+ ...(finishReason !== undefined ? { finishReason } : {}),
303
+ });
304
+ if (rec.inferenceId !== null) rig.grade({ kind: "inference", id: rec.inferenceId }, { score: 1, kind: "deterministic" });
305
+ ```
306
+ <!-- /BLOCK:RECORD_CALL -->
307
+
243
308
  - **Reserved tags:** `run_id` and `step` are stamped by the SDK; the full list of
244
309
  reserved keys is in the
245
310
  [instrumentation guide](instrumentation.md#reserved-tag-keys-the-one-list).
@@ -260,7 +325,9 @@ warns if a rig records calls but never starts a run. If the page is empty:
260
325
  ```text
261
326
  1. npx modelrig doctor, from the environment your app runs in.
262
327
  no export sink configured → this process does not see MODELRIG_API_KEY: set it, redeploy, open a new shell.
328
+ malformed key → the value is truncated, quoted or padded: paste the whole rig_sk_… secret again.
263
329
  AUTH FAILED (401 / 403) → the key is mistyped, revoked or not accepted: mint a key, set it, redeploy, open a new shell.
330
+ whoami unreachable → no network path to the ingest host (firewall, proxy, wrong MODELRIG_INGEST_URL): fix the route out, retry.
264
331
  2. The org doctor prints is not the org in the badge → wrong org: switch the console, or mint a key in the org you meant.
265
332
  3. /inferences has rows but /runs is empty → the calls are not inside run.scope / run.start.
266
333
  4. A run is missing a step or a cost → that call is not on a ModelRig lane (see not-captured).
@@ -268,6 +335,48 @@ warns if a rig records calls but never starts a run. If the page is empty:
268
335
  ```
269
336
  <!-- /BLOCK:EMPTY_CONSOLE -->
270
337
 
338
+ ### 7. Grade it, then prove a cheaper model
339
+
340
+ <!-- BLOCK:GRADE_RUN -->
341
+ **Attach a grade to a call or a whole run.** A grade is your judgment: a 0–1 score with an optional comment, from a validator, a downstream accept/reject, a human or a model. Grade one call by its inference id (`result.meta.inferenceId`, or the `inferenceId` rig.recordCall returns). Grade a whole run by its run id, `run.runId` on the handle `run.start` returns: that grade shows on the run page and on /projects. A run grade whose id is the run's `episodeKey` also shows, on the run page only. `run_id` is a reserved tag the SDK stamps, not a variable your code has. A grade whose id matches no call or run attaches to nothing. The write is synchronous and local and never throws into your pipeline.
342
+
343
+ ```ts
344
+ const run = rig.artifacts.run.start({ pipeline: "support", episodeKey: ticketId, environment: "production" });
345
+ const result = await rig.run("example.support_summarize", {
346
+ input: { ticket, product, priorContext },
347
+ tags: { subject: customer },
348
+ stepKey: "summarize",
349
+ });
350
+ run.end("succeeded");
351
+
352
+ // one call: the inference id rides on every result
353
+ rig.grade({ kind: "inference", id: result.meta.inferenceId }, { score: 1, kind: "deterministic", grader: "schema-check" });
354
+ // the whole run: its id is run.runId from run.start (shows on the run page and on /projects)
355
+ rig.grade({ kind: "run", id: run.runId }, { score: 0, kind: "human", comment: "missed the refund request" });
356
+ ```
357
+ <!-- /BLOCK:GRADE_RUN -->
358
+
359
+ <!-- BLOCK:BAKEOFF -->
360
+ **Prove a cheaper model on your own traffic.** A bake-off replays your own inputs through the variants a route declares and reports the effective cost of conformance for each. `--variants` names entries in the route's `variants:` block (`default` is the route as declared), never model ids, and a variant can only narrow the route's candidates. Set `capture: true` first: replay works on traffic captured after that, not before. With no captured traffic (a run-based pipeline), promote a real /runs output to a golden (the console's make golden, or `promoteToEval`) and use `--from-eval-cases <task>`; its variants come from the route bundle named after the task. A bake-off measures and never switches anything.
361
+
362
+ ```bash
363
+ # 1. in modelrig/routes/example.support_summarize.yaml: capture traffic, and declare
364
+ # a cheaper variant (narrowing only: its candidates must be in the route's list)
365
+ capture: true
366
+ variants:
367
+ - name: cheap
368
+ candidates:
369
+ - provider: gemini
370
+ model: gemini-3.1-flash-lite
371
+
372
+ # 2. after traffic accumulates, compare the route as declared (default) with cheap
373
+ npx modelrig bakeoff --route example.support_summarize --variants default,cheap --replay-last 50
374
+
375
+ # no captured route traffic? promote a real /runs output to a golden, then
376
+ npx modelrig bakeoff --from-eval-cases example.support_summarize --variants default,cheap
377
+ ```
378
+ <!-- /BLOCK:BAKEOFF -->
379
+
271
380
  ## Change your route's model live (no redeploy)
272
381
 
273
382
  A route's **call-config** — which model/provider serves it, and its sampling — can be
@@ -375,7 +484,9 @@ const { bundle: runnable, arm } = resolveCallConfigForRun(bundle, resolved, {
375
484
  input, // stickyKey (if set) reads its unit from here…
376
485
  // unitKey: userId, // …or pass the unit key explicitly (sticky assignment)
377
486
  });
378
- const result = await rig.runBundle(runnable, { input, tags, arm });
487
+ // arm is string | null and RunOptions.arm is optional: pass it only when one was
488
+ // drawn (a bare `arm` fails to compile under exactOptionalPropertyTypes).
489
+ const result = await rig.runBundle(runnable, { input, tags, ...(arm !== null ? { arm } : {}) });
379
490
  // arm is null for a pin / no-override / non-experiment call ⇒ no meta.arm (byte-identical).
380
491
  ```
381
492
 
@@ -419,7 +530,7 @@ key never burns backoff time. Failures that still billed tokens (refusals,
419
530
  truncations) carry their token usage, so envelopes and telemetry account the
420
531
  real spend.
421
532
 
422
- Each class — what it means, whether it retries, and how it backs off — is on [Routing & reliability](routing-reliability.md#5-failure-classes); how the whole candidate ladder runs (ordering, validate → repair → fall-through, and what is not built) is on that page too.
533
+ Each class — what it means, whether it retries, and how it backs off — is on [Routing & reliability](routing-reliability.md#5-failure-classes); that page also covers how the whole candidate ladder runs: the ordering, the validate → repair → fall-through sequence, and the parts that are not built yet.
423
534
 
424
535
  ## Where the telemetry goes
425
536
 
@@ -427,7 +538,9 @@ Every attempt (not just every run) writes one row to package-owned SQLite,
427
538
  tagged with your `tags` — always, with no key. Setting `MODELRIG_API_KEY` turns
428
539
  on **api export mode**: a background exporter ships batches to the ingest API
429
540
  (`api.modelrig.ai`, override with `MODELRIG_INGEST_URL`), which the console
430
- reads — fire-and-forget, queue-on-failure; the inference path never blocks on it.
541
+ reads — fire-and-forget: a transient failure (network, 5xx) is retried, a
542
+ rejected key (401/403) drops the rows and logs `AUTH FAILED`; the inference path
543
+ never blocks on it.
431
544
  Inspect live rows in your local SQLite file, or in the console once the key is
432
545
  set — never a database URL of your own.
433
546
 
@@ -438,7 +551,7 @@ only the web tier silently loses every row a worker or cron produced.
438
551
 
439
552
  Turning **Optimization on** for a route (`capture: true`) is the **recommended
440
553
  default** — your traffic is what makes the routing smarter and the bill smaller,
441
- and everything it keeps you can see, export and delete in the console. Keeping the
554
+ and everything it keeps you can see and export in the console. Keeping the
442
555
  prompts, outputs and evidence too is [content custody](content-custody.md), which
443
556
  is **on by default** — every new org is created managed, having accepted the
444
557
  published terms at signup — and captures into your managed store, scrubbed for the
@@ -510,4 +623,4 @@ loader's own words, is in [route-bundles.md](./route-bundles.md#when-it-doesnt-w
510
623
  registry entry shows probed `structured_native`. Check the probed registry,
511
624
  not memory.
512
625
 
513
- Still stuck? Open an issue at <https://github.com/modelrig/modelrig/issues>.
626
+ Still stuck? See the [Troubleshooting guide](https://modelrig.dev/troubleshooting.html).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "modelrig",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "private": false,
5
5
  "description": "ModelRig — capability-aware AI gateway: routes-as-code, provider adapters, typed failures, budget envelopes, and local-first telemetry.",
6
6
  "license": "Apache-2.0",
@@ -63,7 +63,9 @@
63
63
  "templates/catalog-cleansing.json",
64
64
  "templates/call-disposition-qa.json",
65
65
  "templates/financial-classification.json",
66
- "templates/medical-classification.json"
66
+ "templates/medical-classification.json",
67
+ "templates/support-conversation.json",
68
+ "templates/creative-approval.json"
67
69
  ],
68
70
  "scripts": {
69
71
  "build": "tsc",