modelrig 0.9.0 → 0.11.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 (231) hide show
  1. package/CHANGELOG.md +227 -0
  2. package/README.md +51 -27
  3. package/data/PROVENANCE.md +14 -0
  4. package/data/pricing-map.json +26 -0
  5. package/dist/adapters/attempt-meta-map.d.ts +8 -1
  6. package/dist/adapters/attempt-meta-map.d.ts.map +1 -1
  7. package/dist/adapters/attempt-meta-map.js +4 -0
  8. package/dist/adapters/build-adapters.js +2 -2
  9. package/dist/adapters/gemini.d.ts +28 -1
  10. package/dist/adapters/gemini.d.ts.map +1 -1
  11. package/dist/adapters/gemini.js +38 -9
  12. package/dist/adapters/grok.d.ts.map +1 -1
  13. package/dist/adapters/grok.js +53 -4
  14. package/dist/adapters/openai-compat.d.ts +8 -2
  15. package/dist/adapters/openai-compat.d.ts.map +1 -1
  16. package/dist/adapters/openai-compat.js +8 -1
  17. package/dist/adapters/openai-stream.d.ts +10 -2
  18. package/dist/adapters/openai-stream.d.ts.map +1 -1
  19. package/dist/adapters/openai-stream.js +5 -1
  20. package/dist/adapters/openai.d.ts +10 -1
  21. package/dist/adapters/openai.d.ts.map +1 -1
  22. package/dist/adapters/openai.js +57 -7
  23. package/dist/adapters/responses-api.d.ts +61 -0
  24. package/dist/adapters/responses-api.d.ts.map +1 -0
  25. package/dist/adapters/responses-api.js +124 -0
  26. package/dist/artifacts/artifact.d.ts +20 -0
  27. package/dist/artifacts/artifact.d.ts.map +1 -1
  28. package/dist/artifacts/artifact.js +207 -171
  29. package/dist/artifacts/index.d.ts +3 -1
  30. package/dist/artifacts/index.d.ts.map +1 -1
  31. package/dist/artifacts/index.js +8 -1
  32. package/dist/bakeoff/groundedness.d.ts +58 -0
  33. package/dist/bakeoff/groundedness.d.ts.map +1 -0
  34. package/dist/bakeoff/groundedness.js +165 -0
  35. package/dist/bakeoff/quality.d.ts +14 -1
  36. package/dist/bakeoff/quality.d.ts.map +1 -1
  37. package/dist/bakeoff/quality.js +10 -0
  38. package/dist/bakeoff/replay.d.ts.map +1 -1
  39. package/dist/bakeoff/replay.js +4 -0
  40. package/dist/cli.d.ts.map +1 -1
  41. package/dist/cli.js +8 -0
  42. package/dist/config.d.ts +31 -0
  43. package/dist/config.d.ts.map +1 -1
  44. package/dist/config.js +23 -0
  45. package/dist/doctor-cli.d.ts +7 -0
  46. package/dist/doctor-cli.d.ts.map +1 -0
  47. package/dist/doctor-cli.js +68 -0
  48. package/dist/doctor-preflight.d.ts +64 -0
  49. package/dist/doctor-preflight.d.ts.map +1 -0
  50. package/dist/doctor-preflight.js +167 -0
  51. package/dist/feedback/coverage.d.ts.map +1 -1
  52. package/dist/feedback/coverage.js +9 -2
  53. package/dist/feedback/grade.d.ts +8 -1
  54. package/dist/feedback/grade.d.ts.map +1 -1
  55. package/dist/feedback/grade.js +4 -0
  56. package/dist/gateway/http-protocol.d.ts +19 -0
  57. package/dist/gateway/http-protocol.d.ts.map +1 -1
  58. package/dist/gateway/serve-client.d.ts +99 -0
  59. package/dist/gateway/serve-client.d.ts.map +1 -0
  60. package/dist/gateway/serve-client.js +360 -0
  61. package/dist/index.d.ts +31 -3
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +71 -8
  64. package/dist/prompts/manifest.d.ts +128 -0
  65. package/dist/prompts/manifest.d.ts.map +1 -0
  66. package/dist/prompts/manifest.js +214 -0
  67. package/dist/prompts/prompt-fetch.d.ts +84 -0
  68. package/dist/prompts/prompt-fetch.d.ts.map +1 -0
  69. package/dist/prompts/prompt-fetch.js +120 -0
  70. package/dist/prompts/prompt-resolve.d.ts +76 -0
  71. package/dist/prompts/prompt-resolve.d.ts.map +1 -0
  72. package/dist/prompts/prompt-resolve.js +93 -0
  73. package/dist/prompts/slot-resolver.d.ts +91 -0
  74. package/dist/prompts/slot-resolver.d.ts.map +1 -0
  75. package/dist/prompts/slot-resolver.js +135 -0
  76. package/dist/prompts/tools-register.d.ts +60 -0
  77. package/dist/prompts/tools-register.d.ts.map +1 -0
  78. package/dist/prompts/tools-register.js +48 -0
  79. package/dist/prompts/version-hash.d.ts +29 -0
  80. package/dist/prompts/version-hash.d.ts.map +1 -0
  81. package/dist/prompts/version-hash.js +54 -0
  82. package/dist/prompts-export-cli.d.ts +21 -0
  83. package/dist/prompts-export-cli.d.ts.map +1 -0
  84. package/dist/prompts-export-cli.js +130 -0
  85. package/dist/registry/cache-policy.d.ts.map +1 -1
  86. package/dist/registry/cache-policy.js +22 -0
  87. package/dist/registry/pricing-shape.d.ts +38 -8
  88. package/dist/registry/pricing-shape.d.ts.map +1 -1
  89. package/dist/registry/pricing-shape.js +40 -12
  90. package/dist/registry/pricing.d.ts +6 -5
  91. package/dist/registry/pricing.d.ts.map +1 -1
  92. package/dist/registry/pricing.js +9 -5
  93. package/dist/routes/destination-loader.d.ts +16 -0
  94. package/dist/routes/destination-loader.d.ts.map +1 -0
  95. package/dist/routes/destination-loader.js +29 -0
  96. package/dist/routes/draft.d.ts +7 -0
  97. package/dist/routes/draft.d.ts.map +1 -1
  98. package/dist/routes/draft.js +12 -0
  99. package/dist/routes/loader.d.ts.map +1 -1
  100. package/dist/routes/loader.js +27 -54
  101. package/dist/routes/output-loader.d.ts +42 -0
  102. package/dist/routes/output-loader.d.ts.map +1 -0
  103. package/dist/routes/output-loader.js +112 -0
  104. package/dist/routes/prompt-loader.d.ts +49 -0
  105. package/dist/routes/prompt-loader.d.ts.map +1 -0
  106. package/dist/routes/prompt-loader.js +113 -0
  107. package/dist/routes/resolved-bundle.d.ts +19 -0
  108. package/dist/routes/resolved-bundle.d.ts.map +1 -1
  109. package/dist/routes/resolved-bundle.js +24 -0
  110. package/dist/routes/screen-loader.d.ts +1 -1
  111. package/dist/routes/screen-loader.d.ts.map +1 -1
  112. package/dist/routes/screen-loader.js +8 -1
  113. package/dist/run/accept.d.ts +3 -0
  114. package/dist/run/accept.d.ts.map +1 -1
  115. package/dist/run/accept.js +1 -0
  116. package/dist/run/cost.d.ts +31 -2
  117. package/dist/run/cost.d.ts.map +1 -1
  118. package/dist/run/cost.js +57 -2
  119. package/dist/run/destination.d.ts +39 -0
  120. package/dist/run/destination.d.ts.map +1 -0
  121. package/dist/run/destination.js +45 -0
  122. package/dist/run/dispatch.d.ts +25 -3
  123. package/dist/run/dispatch.d.ts.map +1 -1
  124. package/dist/run/dispatch.js +26 -4
  125. package/dist/run/raw-defaults.d.ts.map +1 -1
  126. package/dist/run/raw-defaults.js +6 -1
  127. package/dist/run/raw-finalize.d.ts +96 -0
  128. package/dist/run/raw-finalize.d.ts.map +1 -0
  129. package/dist/run/raw-finalize.js +283 -0
  130. package/dist/run/raw-knobs.d.ts +36 -0
  131. package/dist/run/raw-knobs.d.ts.map +1 -0
  132. package/dist/run/raw-knobs.js +99 -0
  133. package/dist/run/raw-request.d.ts +24 -0
  134. package/dist/run/raw-request.d.ts.map +1 -0
  135. package/dist/run/raw-request.js +69 -0
  136. package/dist/run/raw-schema.d.ts +76 -0
  137. package/dist/run/raw-schema.d.ts.map +1 -0
  138. package/dist/run/raw-schema.js +166 -0
  139. package/dist/run/raw-search-cost.d.ts +49 -0
  140. package/dist/run/raw-search-cost.d.ts.map +1 -0
  141. package/dist/run/raw-search-cost.js +79 -0
  142. package/dist/run/raw-support.d.ts +64 -6
  143. package/dist/run/raw-support.d.ts.map +1 -1
  144. package/dist/run/raw-support.js +84 -11
  145. package/dist/run/raw.d.ts +25 -26
  146. package/dist/run/raw.d.ts.map +1 -1
  147. package/dist/run/raw.js +30 -351
  148. package/dist/run/record-attempt.d.ts +8 -0
  149. package/dist/run/record-attempt.d.ts.map +1 -1
  150. package/dist/run/record-attempt.js +26 -12
  151. package/dist/run/record-call.d.ts +104 -0
  152. package/dist/run/record-call.d.ts.map +1 -0
  153. package/dist/run/record-call.js +277 -0
  154. package/dist/run/repair-attempt.d.ts +68 -0
  155. package/dist/run/repair-attempt.d.ts.map +1 -0
  156. package/dist/run/repair-attempt.js +180 -0
  157. package/dist/run/resolve-destination.d.ts +49 -0
  158. package/dist/run/resolve-destination.d.ts.map +1 -0
  159. package/dist/run/resolve-destination.js +76 -0
  160. package/dist/run/routing-docs.d.ts.map +1 -1
  161. package/dist/run/routing-docs.js +9 -4
  162. package/dist/run/run.d.ts +30 -1
  163. package/dist/run/run.d.ts.map +1 -1
  164. package/dist/run/run.js +75 -148
  165. package/dist/run/telemetry-row.d.ts +92 -0
  166. package/dist/run/telemetry-row.d.ts.map +1 -0
  167. package/dist/run/telemetry-row.js +81 -0
  168. package/dist/run/validate.d.ts +12 -1
  169. package/dist/run/validate.d.ts.map +1 -1
  170. package/dist/run/validate.js +31 -0
  171. package/dist/run/web-search-pricing.d.ts +43 -0
  172. package/dist/run/web-search-pricing.d.ts.map +1 -0
  173. package/dist/run/web-search-pricing.js +56 -0
  174. package/dist/settings/grading-guide.d.ts +27 -1
  175. package/dist/settings/grading-guide.d.ts.map +1 -1
  176. package/dist/status.d.ts +6 -0
  177. package/dist/status.d.ts.map +1 -1
  178. package/dist/status.js +4 -1
  179. package/dist/telemetry/artifact-store.d.ts.map +1 -1
  180. package/dist/telemetry/artifact-store.js +11 -0
  181. package/dist/telemetry/attempt-meta.d.ts +66 -1
  182. package/dist/telemetry/attempt-meta.d.ts.map +1 -1
  183. package/dist/telemetry/attempt-meta.js +27 -1
  184. package/dist/telemetry/exporter-setup.d.ts +2 -0
  185. package/dist/telemetry/exporter-setup.d.ts.map +1 -1
  186. package/dist/telemetry/exporter-setup.js +6 -1
  187. package/dist/telemetry/grade-mappers.d.ts +5 -2
  188. package/dist/telemetry/grade-mappers.d.ts.map +1 -1
  189. package/dist/telemetry/grade-mappers.js +8 -2
  190. package/dist/telemetry/grades-sql.d.ts +4 -1
  191. package/dist/telemetry/grades-sql.d.ts.map +1 -1
  192. package/dist/telemetry/grades-sql.js +26 -3
  193. package/dist/telemetry/ingest-protocol.d.ts +26 -2
  194. package/dist/telemetry/ingest-protocol.d.ts.map +1 -1
  195. package/dist/telemetry/ingest-protocol.js +32 -29
  196. package/dist/telemetry/lanes.d.ts +31 -0
  197. package/dist/telemetry/lanes.d.ts.map +1 -0
  198. package/dist/telemetry/lanes.js +41 -0
  199. package/dist/telemetry/org-identity.d.ts +47 -0
  200. package/dist/telemetry/org-identity.d.ts.map +1 -0
  201. package/dist/telemetry/org-identity.js +100 -0
  202. package/dist/telemetry/route-registration.d.ts +48 -0
  203. package/dist/telemetry/route-registration.d.ts.map +1 -0
  204. package/dist/telemetry/route-registration.js +77 -0
  205. package/dist/telemetry/sinks.d.ts +10 -1
  206. package/dist/telemetry/sinks.d.ts.map +1 -1
  207. package/dist/telemetry/sinks.js +52 -6
  208. package/dist/telemetry/store-migrations.d.ts.map +1 -1
  209. package/dist/telemetry/store-migrations.js +13 -0
  210. package/dist/tenancy/api-key-format.d.ts +16 -1
  211. package/dist/tenancy/api-key-format.d.ts.map +1 -1
  212. package/dist/tenancy/api-key-format.js +17 -0
  213. package/dist/types/provider.d.ts +20 -3
  214. package/dist/types/provider.d.ts.map +1 -1
  215. package/dist/types/provider.js +1 -0
  216. package/dist/types/route.d.ts +76 -0
  217. package/dist/types/route.d.ts.map +1 -1
  218. package/dist/types/telemetry.d.ts +30 -2
  219. package/dist/types/telemetry.d.ts.map +1 -1
  220. package/docs/agent-skill/SKILL.md +75 -5
  221. package/docs/instrumentation.md +98 -36
  222. package/docs/llms.txt +96 -16
  223. package/docs/managed-serving.md +150 -0
  224. package/docs/mcp-oracle.md +75 -2
  225. package/docs/migration-playbook.md +5 -4
  226. package/docs/quickstart.md +169 -83
  227. package/package.json +3 -2
  228. package/registry/registry.json +82 -0
  229. package/dist/feedback/feedback.d.ts +0 -35
  230. package/dist/feedback/feedback.d.ts.map +0 -1
  231. package/dist/feedback/feedback.js +0 -39
@@ -1,5 +1,7 @@
1
1
  # Artifact instrumentation (runs, steps, artifacts)
2
2
 
3
+ **The question this page answers: how do I record what my pipeline actually did, run by run, step by step, without changing what it does?**
4
+
3
5
  ModelRig can record the **execution graph** of any pipeline your code runs — the
4
6
  run, its steps, and the artifacts each step produced (the prompt, the raw model
5
7
  text, the parsed output, the evidence it grounded on) — with lineage,
@@ -19,11 +21,13 @@ byte-for-byte as before.) `modelrig status` prints the current posture (on/off +
19
21
  second axis — the recommended, consent-gated [content custody](content-custody.md)
20
22
  path — never blurred with the telemetry axis here.
21
23
 
22
- > **Metadata + hashes only, this release.** The serialized value you save is
23
- > hashed (sha256) and the bytes are **discarded** — nothing but the metadata row
24
- > is persisted. Content custody (the blob path) ships in a later release; the
25
- > hash proves integrity today. A `zeroRetention` run refuses every artifact
26
- > fail-closed at the SDK gate.
24
+ > **What `artifact.save` keeps.** This page is the metadata axis: every saved
25
+ > artifact records a metadata row with a sha256 integrity hash, whatever your
26
+ > content setting. Whether the serialized *bytes* are kept as well is your
27
+ > organization's [content custody](content-custody.md) posture: new
28
+ > organizations are created `managed` (bytes kept in your managed store); posture
29
+ > `metadata` keeps hashes only and `off` captures nothing. A `zeroRetention` run
30
+ > refuses every artifact fail-closed at the SDK gate.
27
31
 
28
32
  ## The namespace toggle
29
33
 
@@ -57,11 +61,11 @@ const rig = createRig(loadConfigFromEnv());
57
61
 
58
62
  // 1. Open a run context around the pipeline. episodeKey bridges grades;
59
63
  // it is a grouping key, not a unique id — a natural choice is your own
60
- // per-run attempt id.
64
+ // per-run attempt id. environment fills the Env column on /runs.
61
65
  const run = rig.artifacts.run.start({
62
66
  pipeline: "my-pipeline",
63
67
  episodeKey: runAttemptId,
64
- environment: process.env.NODE_ENV,
68
+ environment: process.env.NODE_ENV, // e.g. "production", "staging", "test"
65
69
  });
66
70
  try {
67
71
  for (const step of steps) {
@@ -111,6 +115,23 @@ right for one run at a time on a request/async path.
111
115
  and honest. Never route a stage through the rig just to mint a step row; the
112
116
  console renders "cost n/a" for un-routed steps rather than inventing a number.
113
117
 
118
+ ### Record a call your code made itself (`rig.recordCall`)
119
+
120
+ _Needs `modelrig` 0.11.0 or newer._
121
+
122
+ If a stage's model call has to stay a direct provider call, report it instead:
123
+ `rig.recordCall({ provider, model, usage: { tokensIn, tokensOut, tokensCached }, servedTier, latencyMs, stepKey })`
124
+ right after the call returns. Inside a run context it records a step row for
125
+ that `stepKey` (stored as `ground-truth`, the only joined-inference value),
126
+ joined to the recorded inference, so the step gets a cost instead of "cost n/a".
127
+ The console shows that step and its call as **recorded · unverified**, never as
128
+ measured:
129
+ ModelRig neither dispatched nor validated the call, and the tokens are the ones
130
+ you reported. It is synchronous and never throws (bad input warns and returns
131
+ `inferenceId: null`); the returned `inferenceId` is gradeable with `rig.grade`.
132
+ Full contract, usage convention and cost notes:
133
+ [Not captured → record them](not-captured.md#calls-your-code-makes-itself-record-them).
134
+
114
135
  ## Tags, dimensions, and the graph
115
136
 
116
137
  ModelRig has **two** grouping mechanisms, and they compose:
@@ -137,22 +158,29 @@ process** (`required dimension "project" missing from run tags — costs … wil
137
158
  under-attributed on /costs`) — on the routed lane and the raw lane alike. It is a
138
159
  hygiene warning, never an error: the run proceeds.
139
160
 
140
- **Conventional tag keys** (documented, not enforced): `subject` (your
141
- end-customer — the tenant key), `feature`, `project` (flat grouping),
142
- `step` (names the step this call belongs to), and `run_id` (bridges a call to a
143
- run's `episodeKey`). `subject` is your end-customer's opaque id — it powers the
144
- per-customer tenant statements on `/tenants`; a tag named `client`/`tenant` is
145
- only a cost dimension. (Hash it yourself if it looks like PII — see
146
- [tenants.md](./tenants.md).) **Inside a `run.start` / `run.scope` context the SDK stamps
147
- both `run_id` and `step` for you** — `run_id` = the run's
148
- `episodeKey`, or its id when you gave none; `step` = the current step key (your
149
- explicit `stepKey`, else `tags.step`, else the auto `route#n` / `provider/model#n`
150
- key) — on every attempt row of both lanes, unless you passed your own value (yours
151
- always wins). Outside a run context nothing is stamped, so the row is
152
- byte-identical. This is why runs are the standard path: a `run.start` alone fills
153
- both `/runs` (the graph) and `/projects` (the tag tree), with no hand-written id.
154
- ModelRig owns `lane`, `zdr_enforced`, `rung`, and — on the hosted gateway —
155
- `key_ownership`; you cannot override those.
161
+ ### Reserved tag keys (the one list)
162
+
163
+ Any other key is yours. These are reserved:
164
+
165
+ | Key | Who sets it | What it means |
166
+ |---|---|---|
167
+ | `subject` | you | Your end-customer's **opaque** id. It powers the per-customer tenant statements on `/tenants`; hash it if it looks like PII ([tenants.md](./tenants.md)). A tag named `client` or `tenant` is only a cost dimension. |
168
+ | `feature` | you | The product feature the call serves. |
169
+ | `user` | you | The end user, as an opaque id. |
170
+ | `session` | you | The conversation or session the call belongs to. |
171
+ | `cost_center` | you | The team or budget to attribute spend to. |
172
+ | `run_id` | **the SDK**, inside a run | The run's `episodeKey`, or its id when you gave none. **Don't set it yourself.** A value you pass overrides the stamp, so the call no longer joins its run on `/runs` and the console's Run ID group-by disagrees with `/runs`. Put your own attempt id in a key of your own, such as `pipeline_attempt_id`. |
173
+ | `step` | **the SDK**, inside a run | The current step key: your explicit `stepKey`, else `tags.step`, else an auto `route#n` / `provider/model#n` key. Name steps with `stepKey`. |
174
+ | `lane`, `zdr_enforced`, `rung`, `key_ownership` | ModelRig | Set by the SDK or the hosted gateway. You can't override them. |
175
+ | `replay`, `replay_capture`, `variant`, `cutover` | ModelRig tooling | Set by bake-offs and replays. |
176
+
177
+ The five keys you set are first-class group-bys in the cost and coverage views, so use them verbatim; `modelrig validate` warns about near-misses such as `costcenter` or `team`. `project` and `environment` are common conventions, not reserved.
178
+
179
+ **Inside a `run.start` / `run.scope` context the SDK stamps `run_id` and `step`
180
+ on every attempt row of both lanes.** Outside a run context nothing is stamped,
181
+ so the row is byte-identical. This is why runs are the standard path: a
182
+ `run.start` alone fills both `/runs` (the graph) and `/projects` (the tag tree),
183
+ with no hand-written id.
156
184
 
157
185
  **How they compose.** Inside a `run.start` context, both `rig.run` and
158
186
  `rig.runRaw` record a **ground-truth step** and carry the run's `run_id` tag, so
@@ -234,8 +262,49 @@ Every entry point is fail-open: a telemetry-buffer write that fails is logged an
234
262
  dropped, never thrown into your code. Instrumentation that runs *after* an
235
263
  expensive model call must never be the thing that kills the run.
236
264
 
265
+ ## Which org am I exporting to?
266
+
267
+ A valid key minted in the wrong org fails silently: every ingest call succeeds,
268
+ and the runs land in someone else's console. So the first time a process flushes
269
+ telemetry, the SDK asks `GET /v1/whoami` once and logs where it is exporting:
270
+
271
+ ```text
272
+ [modelrig] exporting to org «Acme Analytics» (acme-analytics) as key «production exporter» rig_sk_…ab12
273
+ ```
274
+
275
+ The call runs in the background. It never delays or drops a batch, and if it
276
+ fails it says nothing (a rejected key already gets its own loud `AUTH FAILED`
277
+ line). To have the SDK check the org for you, pass the slug you expect in code:
278
+
279
+ | Config option | Type | Default | What it does |
280
+ |---|---|---|---|
281
+ | `expectOrg` | `string` (org slug) | absent | When the key resolves to a different org, logs `[modelrig] ORG MISMATCH: key resolves to «a», expectOrg is «b» — telemetry is landing in «a»` loudly on every flush once the org is known. It never throws and never drops telemetry: the rows still land in the key's org. |
282
+
283
+ ```ts
284
+ const rig = createRig({ ...loadConfigFromEnv(), expectOrg: "acme-analytics" });
285
+ ```
286
+
287
+ It is a code option, not an environment variable. To gate a deploy or CI step
288
+ on the same check, run `modelrig doctor --expect-org acme-analytics`. It exits 1 and
289
+ prints both slugs when the key belongs to a different org. More on keys,
290
+ orgs and the console picker: [Orgs and keys](https://modelrig.dev/orgs-and-keys.html).
291
+
237
292
  ## See it
238
293
 
294
+ Check first that the console is showing the org your key exports to:
295
+
296
+ <!-- BLOCK:CONFIRM_ORG -->
297
+ **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).
298
+
299
+ ```bash
300
+ npx modelrig doctor
301
+ # [modelrig] key rig_sk_…AbCd → org «Your Org» ✓ (ingest https://api.modelrig.ai, …)
302
+
303
+ # No CLI in that environment? Ask the API directly:
304
+ curl -H "Authorization: Bearer $MODELRIG_API_KEY" https://api.modelrig.ai/v1/whoami
305
+ ```
306
+ <!-- /BLOCK:CONFIRM_ORG -->
307
+
239
308
  The console's **Runs** tab lists every instrumented run; `/runs/[id]` is the
240
309
  semantic chain (artifacts grouped by step, with hashes, costs, and grade
241
310
  badges) and `/artifacts/[id]` shows one artifact's lineage, versions, and
@@ -252,15 +321,15 @@ that decides how much confidence a swap proposal carries.
252
321
 
253
322
  ```ts
254
323
  // One call — the inference id rides on every RunResult:
255
- const result = await rig.run("example.support_summarize", { input, tags: { run_id } });
324
+ const result = await rig.run("example.support_summarize", { input, tags: { subject } });
256
325
  rig.grade({ kind: "inference", id: result.meta.inferenceId }, { score: 1, kind: "human" });
257
326
 
258
- // A whole run (the subject id is your run_id tag):
259
- rig.grade({ kind: "run", id: run_id }, { score: 0.2, comment: "CAGR off by 10x", kind: "human" });
327
+ // A whole run: the subject id is the run's run_id (its episodeKey, or its id).
328
+ rig.grade({ kind: "run", id: runAttemptId }, { score: 0.2, comment: "CAGR off by 10x", kind: "human" });
260
329
  ```
261
330
 
262
331
  - **The subject** is `{ kind: "inference", id }` (`result.meta.inferenceId`),
263
- `{ kind: "run", id }` (the run's `run_id`/`episodeKey` tag), or
332
+ `{ kind: "run", id }` (the run's `run_id` — its `episodeKey`, or its id), or
264
333
  `{ kind: "artifact", id }`. **`opts`** is `{ score, scale?, comment?, kind,
265
334
  source? }` — the `score` in `[0, 1]` is the primitive (up = 1, down = 0), and
266
335
  the pass/fail verdict is derived from it against the route threshold. A subject
@@ -274,8 +343,8 @@ rig.grade({ kind: "run", id: run_id }, { score: 0.2, comment: "CAGR off by 10x",
274
343
  network call and never a failure your code can see; the cloud mirror rides the
275
344
  telemetry exporter's normal cadence.
276
345
  - **Why it matters.** A route with **≥10 attached grades** reaches **Rung 1** on
277
- `/coverage`; a route with no schema to conform to (a prose output) gains
278
- proposal confidence only this way. Grades are also what let `/insights`
346
+ `/coverage`; a route with no schema to conform to (an `output: text` prose
347
+ route, or a schema-less JSON route) gains proposal confidence only this way. Grades are also what let `/insights`
279
348
  surface a task that is degrading.
280
349
  - **From a non-SDK surface** (an end user clicking thumbs-down in your web app),
281
350
  post the same fields to `POST /v1/grade` with a `rig_sk_` key carrying the
@@ -340,10 +409,3 @@ function recordStep(
340
409
  Call `recordStep(step, { … })` once per stage inside the `run.start` / `run.end`
341
410
  context above, and every entry point stays fail-open — a save that fails is
342
411
  logged and dropped, never thrown into your pipeline.
343
-
344
- > **In-repo example (reconstructed via onboarding-test run-2).** ModelRig's own
345
- > customer-zero pipeline (the InferWealth v3 report generator) was stripped back
346
- > to a clean slate to be re-onboarded through this exact doc. Its live seam is
347
- > reconstructed as part of onboarding-test run-2; until then, the snippet above
348
- > is the reference. Do not resurrect a deleted seam from git history — writing a
349
- > fresh one is the point.
package/docs/llms.txt CHANGED
@@ -4,7 +4,7 @@
4
4
  > grounding, and caching measured empirically, with a capability-aware
5
5
  > gateway that routes by them. Registry data CC BY 4.0; code Apache-2.0.
6
6
 
7
- _This reference is as of modelrig v0.9.0 (the published npm package). Any surface below that a newer workspace adds is marked "unreleased — workspace only" until its release; nothing is so marked as of this version._
7
+ _This reference is as of modelrig v0.11.0 (the published npm package). Any surface below that a newer workspace adds is marked "unreleased — workspace only" until its release; nothing is so marked as of this version._
8
8
 
9
9
  ## Docs
10
10
 
@@ -36,6 +36,12 @@ _This reference is as of modelrig v0.9.0 (the published npm package). Any surfac
36
36
  - [Template: call disposition QA](https://modelrig.dev/template-call-disposition-qa.html): A starting point for checking a call's logged disposition and scoring it against your QA scorecard from the transcript: a starter schema with a TODO disposition slot and a placeholder scorecard object, a starter grading guide that checks every judgment is grounded in the transcript, a golden checklist for disputed and sensitive calls, and an escalation starter. Transcript in — you bring your own transcription; no accuracy claims.
37
37
  - [Template: financial classification](https://modelrig.dev/template-financial-classification.html): A starting point for classifying a financial record into your categories and risk flags: a starter schema with TODO enum slots, a starter grading guide that checks the classification is grounded and the risk flag follows your rules, a golden checklist for edge-of-rule and sensitive records, and an escalation starter. Regulated data — the fit note quotes the published custody terms verbatim; no other compliance claim, no accuracy claims.
38
38
  - [Template: medical classification](https://modelrig.dev/template-medical-classification.html): A starting point for classifying a clinical document into your categories: a starter schema with a TODO category slot, a starter grading guide that keeps the output a classification rather than a clinical judgment, a golden checklist for cross-category and clinically significant documents, and an escalation starter. Regulated data — the fit note quotes the published custody terms verbatim; no other compliance claim, no accuracy claims.
39
+ - [Managed serving](https://modelrig.dev/managed-serving.html): Serve a route's model on ModelRig's own credentials — destination: modelrig-served — paid from your prepaid balance at provider list cost plus the flat fee: declare the destination, enable managedServing in code, mint a serve-scoped key, opt the organization in, fund the balance; what each refusal teaches; the destination tag on the row; and the honest direct default that nothing here changes.
40
+ - [Orgs and keys](https://modelrig.dev/orgs-and-keys.html): One key = one org: how to see which organization a key exports to (`modelrig doctor`, `GET /v1/whoami`, the console badge), how the console picks the org it shows (the picker, `409 org_required`, `401 session_expired`), why a valid key in the wrong org looks like an empty console, and `doctor --expect-org` / `createRig({ expectOrg })`.
41
+ - [Choosing a lane](https://modelrig.dev/choosing-a-lane.html): Observe (wrap what you have in a run), the `rig.runRaw` seam (one adapter for a router you already own) or named routes (`rig.run`): when each fits, what you get, what you give up, what it costs — and why to start with observe.
42
+ - [Not captured](https://modelrig.dev/not-captured.html): What ModelRig does not see — direct provider SDK calls, streams read outside the SDK, fallbacks after a fail-closed schema, calls outside a run, embeddings — and the console tells: `cost n/a` steps, a missing `inferenceId`, a run total below your provider bill.
43
+ - [Deploying on hosted workers](https://modelrig.dev/deploying.html): Set MODELRIG_API_KEY on every process; Render's env-group vs service-override precedence; a change needs a finished redeploy and a new shell; rotate keys mint → set → redeploy → doctor → revoke; verify after every deploy with `doctor --expect-org`, then one run on /runs; notes for Vercel, Fly and Docker.
44
+ - [Troubleshooting an empty console](https://modelrig.dev/troubleshooting.html): The "console is empty" decision tree: is the process exporting (doctor: AUTH FAILED / no export sink / org ✓), is it the org you are viewing, are the calls inside a run, is the missing call on a ModelRig lane, and does your install match the docs.
39
45
  - [Leaderboard](https://modelrig.dev/leaderboard.html): Models ranked by effective cost per 1,000 schema-conformant outputs, with declared-vs-probed discrepancy flags.
40
46
 
41
47
  ## Source
@@ -43,9 +49,77 @@ _This reference is as of modelrig v0.9.0 (the published npm package). Any surfac
43
49
  - [Public repo](https://github.com/modelrig/modelrig): probe suite, registry data, contribution kits.
44
50
  - [Registry JSON](https://github.com/modelrig/modelrig/blob/main/registry/registry.json): three layers per model (declared / probed / observed aggregates) with as_of stamps.
45
51
 
52
+ ## Start here: from zero to your first run on /runs
53
+
54
+ _The default ingest host is https://api.modelrig.ai (override with `MODELRIG_INGEST_URL` only if you were given another). Choosing how your calls reach ModelRig: https://modelrig.dev/choosing-a-lane.html. Which org your data lands in: https://modelrig.dev/orgs-and-keys.html. Hosted workers: https://modelrig.dev/deploying.html. What is not captured: https://modelrig.dev/not-captured.html. Console empty: https://modelrig.dev/troubleshooting.html._
55
+
56
+ **Mint a key in the org you want the data in.** Sign in at https://app.modelrig.ai, check the organization name in the header badge, and create a key on the API keys page. The `rig_sk_` secret is shown once. One key = one org: every row the key exports lands in the organization it was minted in, whichever organization you happen to be viewing in the console. Set it on every service, worker and cron that creates a rig; your provider keys stay where they are.
57
+
58
+ ```bash
59
+ # Wherever your app reads configuration: .env.local, your platform's env vars, a secret manager. Never in code.
60
+ MODELRIG_API_KEY=rig_sk_…
61
+ ```
62
+
63
+ **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).
64
+
65
+ ```bash
66
+ npx modelrig doctor
67
+ # [modelrig] key rig_sk_…AbCd → org «Your Org» ✓ (ingest https://api.modelrig.ai, …)
68
+
69
+ # No CLI in that environment? Ask the API directly:
70
+ curl -H "Authorization: Bearer $MODELRIG_API_KEY" https://api.modelrig.ai/v1/whoami
71
+ ```
72
+
73
+ **Wrap one pipeline execution in a run.** Every pipeline execution is a run. Wrap each execution in `run.scope` (or `run.start` / `run.end`), name one step per model call with `stepKey`, `artifact.save` the work products, and pass `environment` so the Env column on /runs is filled. Inside the run the SDK stamps `run_id` and `step` on every call. `run_id` is reserved: don't set it yourself.
74
+
75
+ ```ts
76
+ import { createRig, loadConfigFromEnv } from "modelrig";
77
+
78
+ const rig = createRig(loadConfigFromEnv());
79
+
80
+ // Every pipeline execution is a run. Inside the scope the SDK records each
81
+ // rig.run / rig.runRaw call as a step and stamps run_id and step on every row.
82
+ // run_id is reserved: don't set it yourself (put your own attempt id in a tag of
83
+ // your own, such as pipeline_attempt_id).
84
+ await rig.artifacts.run.scope(
85
+ { pipeline: "support", episodeKey: ticketId, environment: process.env.NODE_ENV ?? "development" },
86
+ async () => {
87
+ const result = await rig.run("example.support_summarize", {
88
+ input: { ticket, product, priorContext },
89
+ tags: { subject: customer, feature: "summarize" },
90
+ stepKey: "summarize", // names this step on /runs
91
+ });
92
+ rig.artifacts.artifact.save(result.output, { name: "summary", type: "step_output", stepKey: "summarize" });
93
+ },
94
+ );
95
+
96
+ await rig.close(); // flushes the exporter — call once at shutdown
97
+ ```
98
+
99
+ **What ModelRig does not see unless you record it.** ModelRig records the calls your code makes through the SDK (`rig.run`, `rig.runBundle`, `rig.runRaw`, `rig.runRawStream`) or the gateway, plus any call your code makes itself and reports with `rig.recordCall`. Nothing else is seen. Five things stay invisible or unattached unless you record them: (1) direct provider SDK calls, with or without a JSON schema; (2) streams you read from a provider client yourself; (3) a fallback your code makes after the raw lane refuses a `responseSchema` it cannot enforce (deepseek, deepinfra, fireworks and zai fail closed with `invariant_violation`); (4) calls made outside `run.scope` / `run.start`, which land on /inferences but on no run; (5) embeddings. A recorded call shows as "recorded · unverified": ModelRig neither dispatched nor validated it, and its tokens are the numbers you reported. Details and the console tells: https://modelrig.dev/not-captured.html.
100
+
101
+ **The console is empty: five checks, in order.** If /runs (or /inferences) is empty for the org in the header badge, work these checks in order. The full tree is https://modelrig.dev/troubleshooting.html.
102
+
103
+ ```text
104
+ 1. npx modelrig doctor, from the environment your app runs in.
105
+ no export sink configured → this process does not see MODELRIG_API_KEY: set it, redeploy, open a new shell.
106
+ AUTH FAILED (401 / 403) → the key is mistyped, revoked or not accepted: mint a key, set it, redeploy, open a new shell.
107
+ 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.
108
+ 3. /inferences has rows but /runs is empty → the calls are not inside run.scope / run.start.
109
+ 4. A run is missing a step or a cost → that call is not on a ModelRig lane (see not-captured).
110
+ 5. A doc describes something your install lacks → compare `npm ls modelrig` with the version stamp in modelrig.dev/llms.txt.
111
+ ```
112
+
113
+ **After you change the key on a hosted worker.** A hosted process reads the environment it was started with. After you set or change MODELRIG_API_KEY, wait for the redeploy to finish, then open a new shell: a shell attached before the deploy still has the old environment. Confirm from inside it that the key resolves to the org you expect.
114
+
115
+ ```bash
116
+ npx modelrig doctor --expect-org your-org-slug # exits non-zero if the key belongs to another org
117
+ ```
118
+
46
119
  ## Capabilities (the full surface)
47
120
 
48
121
  - **Runs & projects** — the run → step → artifact graph at /runs, grouped by tag at /projects (the standard path above): https://modelrig.dev/instrumentation.html.
122
+ - **Recorded calls (`rig.recordCall`, modelrig ≥ 0.11.0)** — a provider call your code makes itself (no lane fits) is reported after it returns: one row, priced at list for the served tier from the tokens you report, joined to the run/step, gradeable, badged "recorded · unverified" (ModelRig neither dispatched nor validated it), never in routed/conformance math: https://modelrig.dev/not-captured.html.
49
123
  - **Tenants & statements** — a `subject` tag or project-scoped key becomes a per-customer tenant with a derived statement (attempted, verified, billable, true cost; never content or another org's data): https://modelrig.dev/tenants.html.
50
124
  - **Published receipts** — a signed, login-free receipt of a bake-off or route at /r/<id>, verifiable by anyone at /v1/verify/<id>; internal names aliased, content never leaves: https://modelrig.dev/receipts.html.
51
125
  - **Analytics query API** — a self-describing, read-only query surface over your own telemetry (`GET /v1/analytics/meta`, `POST /v1/analytics/query`): an allowlisted SELECT-only grammar that returns metadata only (enums, numbers, hashes), never content: https://modelrig.dev/optimization-loop.html.
@@ -124,7 +198,6 @@ _The runtime routing model — the CANDIDATE ladder of one `rig.run` call, disti
124
198
  | id | capability | what is true today | tracking |
125
199
  |---|---|---|---|
126
200
  | B1 | Route-declared judge (hosted lane) | The SDK-lane `qualityGate` predicate (it rejects a schema-valid output as `quality_rejected`) ships; a route-DECLARED judge that runs on the hosted lane — a `judge:` block in the bundle with a rubric — is not built, and the bake-off-scoped `bakeoff.judge` (offline scoring) (which only measures value accuracy during a bake-off) is a separate SDK feature that never serves the hosted lane. | Route-declared judge — customer request on file (2026-08-21); follows the SDK predicate |
127
- | B2 | Structure on the raw lane | `runRaw` never validates or repairs; `responseFormat: "json_object"` shapes MIME, not schema, so `meta.validated` stays false — structure on the raw lane is ruled out by design, and the path is to graduate that step to a route. | ruled schema-free on the raw lane; re-request on file |
128
201
  | B6 | Run-level idempotency / dedupe key | Two identical `rig.run` calls dispatch twice; the only idempotency is the exporter's watermark. | — |
129
202
 
130
203
  ### Deferred
@@ -144,6 +217,7 @@ _The runtime routing model — the CANDIDATE ladder of one `rig.run` call, disti
144
217
 
145
218
  | id | capability | what is true today | tracking |
146
219
  |---|---|---|---|
220
+ | B2 | Repair on the raw lane | `runRaw` validates a declared `responseSchema` (the provider's native structured-output directive plus a client-side Ajv check; a miss is a billed, terminal `content_invalid`) but never repairs, never retries, and never falls through — `meta.validated` stays false because no ladder stands behind the check, `responseFormat: "json_object"` shapes MIME only, and a schema miss you want recovered means graduating that step to a route. | [route-bundles](https://modelrig.dev/route-bundles.html) — raw-lane responseSchema (validate, never repair) |
147
221
  | B7 | Automatic model swap | No automated change to what serves a route happens without a bake-off and human approval (`modelrig swap execute <id> --approve`); you (or your agent) can change it directly at any time — instant, versioned, reversible. | [optimization-loop](https://modelrig.dev/optimization-loop.html) — human-approved swaps only |
148
222
  | B8 | Provider cache resource lifecycle | ModelRig passes your cache handle through and prices the hits; it never creates, refreshes, or deletes the resource — your heartbeat does. | [caching-lifecycle](https://modelrig.dev/caching-lifecycle.html) — who owns what |
149
223
  | B9 | Tool execution | ModelRig normalizes the tool-call protocol and returns `meta.toolCalls`; the caller runs the tool. | [route-bundles](https://modelrig.dev/route-bundles.html) — tools are normalized, never executed |
@@ -151,43 +225,48 @@ _The runtime routing model — the CANDIDATE ladder of one `rig.run` call, disti
151
225
 
152
226
  ## Build a route bundle correctly (for coding agents)
153
227
 
154
- A route bundle is one YAML file: `route`, `version`, `candidates`, `schema` (a JSON Schema file path or null), `prompt` (a system template path + declared `variables`), and `policy`. Five constraints decide whether it works — the first four fail loudly at load, the last fails SILENTLY:
228
+ A route bundle is one YAML file: `route`, `version`, `candidates`, `schema` (a JSON Schema file path or null), `prompt` (a system template path + declared `variables`), `policy`, and optionally `output` (`json`, the default, or `text` for prose) and a per-call `prompt.user` template. Seven constraints decide whether it works — the first five fail loudly at load, the sixth is a documented delivery rule, the last fails SILENTLY:
155
229
 
156
230
  - Schema and prompt files must EXIST at the paths the bundle names; paths resolve relative to the bundle file. A bundle referencing a missing schema fails at load, not at run.
231
+ - A route that returns PROSE or MARKDOWN (an editorial pass, a memo, a report) must declare `output: text` — the response is then returned verbatim as a string, never JSON-parsed, no repair rung. The default (`output: json`, even with `schema: null`) still requires parseable JSON and rejects prose as `content_invalid` with a fixHint naming this key. `output: text` cannot combine with `schema:`, `require: schema_conformant`, or `repair:` (load errors). The one declared prose check is `output: { kind: text, min_chars: N }`. See https://modelrig.dev/route-bundles.html#prose-and-markdown-output-output-text.
157
232
  - Every `{{variable}}` the prompt (or `grounding.query`) uses must be declared in `prompt.variables`. Undeclared references fail at load time.
233
+ - A route that will be called with a GEMINI CACHE HANDLE (`rig.run(…, { cache: { key, provider: "gemini" } })`) must put the per-call delta in a `prompt.user` template (the user turn) and the shared prefix in the cache resource: Gemini forbids a system instruction next to `cachedContent`, so with a handle everything the route rendered is delivered AS THE USER TURN (never dropped; `meta.prompt_delivery` = `user+cache`). `prompt.system` may be omitted when `prompt.user` is declared; both share `prompt.variables`. The loader warns on a `cache:` route with a Gemini candidate, a non-empty `prompt.system` and no `prompt.user`. See https://modelrig.dev/caching-lifecycle.html#a-cache-handle-changes-where-the-template-is-delivered.
158
234
  - Candidates come from the PROBED registry above, never from memory — declared capability and probed behaviour differ, which is ModelRig's entire thesis.
159
235
  - `json: native` is a CLAIM ABOUT THE MODEL, not a preference: set it only for a model whose registry entry shows probed native strict enforcement (`structured_native`). Otherwise omit it and let the emulation ladder handle structure — a wrong `json: native` loads fine and then behaves unlike the code it replaced.
160
236
  - `MODELRIG_API_KEY` is OPTIONAL for LOCAL route-running: everything runs locally without it, and it only decides where telemetry lands (the ModelRig console). But observing your runs IN that console requires a free account + key — for that it is not optional.
161
237
  - `MODELRIG_API_KEY` gates the exporter PER PROCESS — set it on EVERY service, worker, and cron that constructs a rig; a process without it buffers to its own local SQLite and ships nothing. A multi-service deployment that sets the key on only the web tier silently loses every row a worker or cron produced.
162
- - Every pipeline execution is a RUN — the standard path, not an appendix: wrap it in `rig.artifacts.run.start` / `run.scope`, name one `step()` per model-call family, `artifact.save` the work products, and tag `subject` / `feature`. Acceptance is the run appearing on /runs with its steps. Inside a run context, BOTH `rig.run` and `rig.runRaw` record a ground-truth STEP (the per-run tree at /runs/[id]); name it with `stepKey` on either lane (explicit > `tags.step` > an auto `route#n` / `provider/model#n` key). An `artifact.save` with no `stepKey` joins the ambient (most recent) step. TAGS are the flat filter/rollup (/costs); the GRAPH is the per-run tree — they compose, and inside a run context the SDK stamps `tags.run_id` (the run's episodeKey, else its id) on every row of both lanes unless you passed your own — and stamps `tags.step` (the current step key) the same way (a value you pass always wins). The raw lane records steps since modelrig 0.5.0 (before that, a run that wrapped raw calls got an empty tree); raw steps are metadata only (no artifact content on the raw lane).
238
+ - Every pipeline execution is a RUN — the standard path, not an appendix: wrap it in `rig.artifacts.run.start` / `run.scope`, name one `step()` per model-call family, `artifact.save` the work products, and tag `subject` / `feature`. Acceptance is the run appearing on /runs with its steps. Inside a run context, BOTH `rig.run` and `rig.runRaw` record a ground-truth STEP (the per-run tree at /runs/[id]); name it with `stepKey` on either lane (explicit > `tags.step` > an auto `route#n` / `provider/model#n` key). An `artifact.save` with no `stepKey` joins the ambient (most recent) step. TAGS are the flat filter/rollup (/costs); the GRAPH is the per-run tree — they compose, and inside a run context the SDK stamps `tags.run_id` (the run's episodeKey, else its id) and `tags.step` (the current step key) on every row of both lanes. `run_id` is RESERVED: don't set it yourself (a value you pass would override the stamp and split the run's rows from /runs) — put your own attempt id in a tag of your own, such as `pipeline_attempt_id`. Name the step with `stepKey`. The raw lane records steps since modelrig 0.5.0 (before that, a run that wrapped raw calls got an empty tree); raw steps are metadata only (no artifact content on the raw lane).
163
239
 
164
240
  ## Caching — pass your handle, own the TTL heartbeat
165
241
 
166
242
  - If the code you are migrating already caches with a provider (Gemini `cachedContents`, Anthropic `cache_control`, an OpenAI `prompt_cache_key`), carry it through the route — dropping it is a silent cost regression. A customer handle rides one optional field: `rig.run(task, { input, cache: { key, provider } })` (or `rig.runRaw({ …, apiKey, cache: { key } })` — runRaw is BYOK, pass your provider key); `provider` is required and the handle is applied only to that provider's candidates.
167
243
  - CRITICAL for long jobs: an explicit cache (Gemini `cachedContents`) has a TTL, and a multi-hour pipeline outlives it. ModelRig passes your handle through and prices the hits, but it NEVER creates, refreshes, or deletes the resource — your code must run the TTL heartbeat (refresh or recreate), by design. See [Caching lifecycle](https://modelrig.dev/caching-lifecycle.html) for the three regimes, what invalidates a cache, and prefix hygiene.
168
244
  - Route-level automatic caching is separate: set `cache: auto` on the route and ModelRig stamps the per-provider directive; a miss-streak detector exists but is UNWIRED (not yet connected to the monitor), so nothing yet alerts on a route that stopped caching. That handles Anthropic markers and prefix caching — the customer handle above is only for provider-native EXPLICIT resources you own.
169
- - The Lane-B `rig.runRaw` seam also forwards `grounding: { mode: "native" }` (native provider web grounding), `serviceTier: standard|flex|priority`, `reasoning: { level: minimal|low|medium|high }`, and `responseFormat: "json_object"`. Each is additive (absent ⇒ byte-identical); a directive a provider cannot honor FAILS CLOSED (`invariant_violation`, pre-dispatch), never a silent drop — which provider honors which knob is the matrix below. A grounded raw step that also passes `cache.key` must have the `googleSearch` tool baked into the cache resource at creation.
245
+ - The Lane-B `rig.runRaw` seam also forwards `grounding: { mode: "native" }` (native provider web grounding), `serviceTier: standard|flex|priority`, `reasoning: { level: minimal|low|medium|high }`, `responseFormat: "json_object"` (mime-shaping only), and `responseSchema: <JSON Schema object>` (the provider's NATIVE structured-output directive PLUS a client-side Ajv check — a miss is a billed, terminal `content_invalid`, never repaired or retried; `meta.schema_sha` on the row; declare one of responseFormat/responseSchema). Each is additive (absent ⇒ byte-identical); a directive a provider cannot honor FAILS CLOSED (`invariant_violation`, pre-dispatch), never a silent drop — which provider honors which knob is the matrix below. A grounded raw step that also passes `cache.key` must have the `googleSearch` tool baked into the cache resource at creation; a schema composes with the handle.
170
246
 
171
247
  ## Raw-lane provider × knob support (rig.runRaw)
172
248
 
173
- _Since 0.4.0: `reasoning` beyond gemini, `responseFormat`, and the DeepInfra/Fireworks `prompt_cache_key` mapping (grok native grounding is fail-closed: xAI retired Live Search; tracked as CE-9b). The openai/grok prompt-cache hints were already true on 0.3.0._
249
+ _Since 0.4.0: `reasoning` beyond gemini, `responseFormat`, and the DeepInfra/Fireworks `prompt_cache_key` mapping. **RELEASED:** native `web_search` grounding on openai AND grok via the Responses API — each cleared its own live recency+citations+price gate on 2026-09-09 (openai $0.01/call surcharge; grok $0.005 per CALL — $5/1k calls, a per-call model, retrieved content billed separately as input tokens; grok replaces the retired xAI Live Search surface, and its gate run scored 15/15 recency, 100% citation presence, avg 3.80 cites/response). The openai/grok prompt-cache hints were already true on 0.3.0._
174
250
 
175
251
  The raw lane forwards these provider knobs; a knob a provider cannot honor FAILS CLOSED pre-dispatch (`invariant_violation`) — discoverable here, never a silent drop:
176
252
 
177
- | Provider | grounding | reasoning | responseFormat | cache | serviceTier |
178
- |---|---|---|---|---|---|
179
- | `gemini` | native — Google Search (`googleSearch` tool) | `level` → `thinkingConfig.thinkingLevel`, thinking-capable models only (see `GEMINI_THINKING_CAPABLE_MODELS`) | `responseMimeType: application/json` (mime-shaping only — no schema, `validated` stays false) | `cache.key` = a `cachedContent` resource handle (customer-owned lifecycle) | `flex` / `standard` / `priority` honored |
180
- | `openai` | fail-closed | `level` → `reasoning_effort` (1:1); a non-reasoning model's rejection is classified teachably | `response_format: { type: json_object }` | `cache.key` → `prompt_cache_key`; `ttlSeconds ≥ 86400` → `prompt_cache_retention: 24h` | `flex` (allowlist) / `priority`; flex→standard is a customer-side degrade (§3.2) |
181
- | `deepseek` | fail-closed | no effort control — reasoning is model selection (`deepseek-reasoner`) | `response_format: { type: json_object }` — the prompt must contain the word "JSON" (caller's responsibility on the raw lane) | automatic prefix cache — the key is not used | no tiers — always `standard` |
182
- | `anthropic` | fail-closed | extended thinking is not wired on the raw lane (future work) | fail-closed — no json_object equivalent (structured output is a forced tool call) | prefix caching via `cache_control` markers — the key is not used | no tiers — always `standard` |
183
- | `grok` | fail-closed — xAI retired Live Search (HTTP 410, 2026-08-21); native `web_search` needs the Responses API, tracked as CE-9b | coarsened (disclosed): `minimal,low → "low"`; `medium,high → "high"` → `reasoning_effort` | `response_format: { type: json_object }` | `cache.key` → `prompt_cache_key` (routing hint) | no tiers — always `standard` |
184
- | `deepinfra` | fail-closed | `level` → `reasoning_effort` (1:1); a non-reasoning model's rejection is classified teachably | `response_format: { type: json_object }` | `cache.key` → `prompt_cache_key` (routing hint) | no tiers — always `standard` |
185
- | `fireworks` | fail-closed | `level` → `reasoning_effort` (1:1); a non-reasoning model's rejection is classified teachably | `response_format: { type: json_object }` | `cache.key` → `prompt_cache_key` (routing hint; caching is automatic host-side) | no tiers — always `standard` |
253
+ | Provider | grounding | reasoning | responseFormat | responseSchema | cache | serviceTier | destination |
254
+ |---|---|---|---|---|---|---|---|
255
+ | `gemini` | native — Google Search (`googleSearch` tool) | `level` → `thinkingConfig.thinkingLevel`, thinking-capable models only (see `GEMINI_THINKING_CAPABLE_MODELS`) | `responseMimeType: application/json` (mime-shaping only — no schema, `validated` stays false) | `responseJsonSchema` + `responseMimeType: application/json` (native); output Ajv-checked, a miss is `content_invalid` | `cache.key` = a `cachedContent` resource handle (customer-owned lifecycle) | `flex` / `standard` / `priority` honored | `direct` only — vendor key (BYOK / configured) |
256
+ | `openai` | native — `web_search` tool (Responses API) — released (live gate passed 2026-09-09) | `level` → `reasoning_effort` (1:1); a non-reasoning model's rejection is classified teachably | `response_format: { type: json_object }` | `response_format: { type: json_schema, strict: true }` (native; strict needs an object root — a non-object root is warned pre-dispatch); output Ajv-checked | `cache.key` → `prompt_cache_key`; `ttlSeconds ≥ 86400` → `prompt_cache_retention: 24h` | `flex` (allowlist) / `priority`; flex→standard is a customer-side degrade (§3.2) | `direct` only — vendor key (BYOK / configured) |
257
+ | `deepseek` | fail-closed | no effort control — reasoning is model selection (`deepseek-reasoner`) | `response_format: { type: json_object }` — the prompt must contain the word "JSON" (caller's responsibility on the raw lane) | fail-closed — json_object only; a schema would be silently dropped | automatic prefix cache — the key is not used | no tiers — always `standard` | `direct` only — vendor key (BYOK / configured) |
258
+ | `anthropic` | fail-closed | extended thinking is not wired on the raw lane (future work) | fail-closed — no json_object equivalent (structured output is a forced tool call) | forced tool call carrying the schema as `input_schema` (strict-first via the structured-outputs beta, advisory on a strict compile rejection); output Ajv-checked | prefix caching via `cache_control` markers — the key is not used | no tiers — always `standard` | `direct` only — vendor key (BYOK / configured) |
259
+ | `grok` | native — `web_search` tool (xAI Agent Tools / Responses API); replaces retired Live Search — released (live gate passed 2026-09-09) | coarsened (disclosed): `minimal,low → "low"`; `medium,high → "high"` → `reasoning_effort` | `response_format: { type: json_object }` | `response_format: { type: json_schema, strict: true }` (native); output Ajv-checked | `cache.key` → `prompt_cache_key` (routing hint) | no tiers — always `standard` | `direct` only — vendor key (BYOK / configured) |
260
+ | `deepinfra` | fail-closed | `level` → `reasoning_effort` (1:1); a non-reasoning model's rejection is classified teachably | `response_format: { type: json_object }` | fail-closed — the raw compat profile has no probed `structured_native`; the schema would degrade to json_object + a prompt guard | `cache.key` → `prompt_cache_key` (routing hint) | no tiers — always `standard` | `direct` · `modelrig-served` — ModelRig-owned key (managed serving RELEASED — GA, gated by `config.managedServing`, default off) |
261
+ | `fireworks` | fail-closed | `level` → `reasoning_effort` (1:1); a non-reasoning model's rejection is classified teachably | `response_format: { type: json_object }` | fail-closed — the raw compat profile has no probed `structured_native`; the schema would degrade to json_object + a prompt guard | `cache.key` → `prompt_cache_key` (routing hint; caching is automatic host-side) | no tiers — always `standard` | `direct` · `modelrig-served` — ModelRig-owned key (managed serving RELEASED — GA, gated by `config.managedServing`, default off) |
262
+ | `zai` | fail-closed — native GLM web_search wired in B4b | `level` → `reasoning_effort` (1:1); a non-reasoning model's rejection is classified teachably | response_format json_object | fail-closed — the raw compat profile has no probed `structured_native`; the schema would degrade to json_object + a prompt guard | `cache.key` → `prompt_cache_key` (routing hint) | no tiers — always `standard` | `direct` only — vendor key (BYOK / configured) |
186
263
 
187
264
  **Fail-closed is discoverable, not a surprise.** A knob a provider cannot honor throws `RigFailureError` `invariant_violation` PRE-DISPATCH (no meter, no provider call, no telemetry row) — read this table before you call, not after.
188
265
  **reasoning has NO per-model allowlist outside gemini.** openai/deepinfra/fireworks forward `level` verbatim and let the provider enforce; a rejection surfaces as a classified failure that names the fix. grok coarsens (disclosed above). deepseek reasoning is model selection (`deepseek-reasoner`); anthropic extended thinking is unwired.
189
266
  **responseFormat is mime-shaping, not schema enforcement.** `json_object` forces syntactic JSON only — the raw lane still never validates or repairs, and `meta.validated` stays `false` (F4).
267
+ **responseSchema validates, never repairs.** A JSON Schema on `rig.runRaw` is forwarded as the provider's NATIVE structured-output directive AND checked client-side by the same Ajv the routed lane uses: a miss is thrown as `content_invalid` (a billed row with that class, `fixHint` naming the first errors) — the raw lane never repairs, never retries, never falls through, and `meta.validated` stays `false` (no ladder stands behind it). The row's `meta.schema_sha` is the sha256 of the canonical (sorted-key) schema JSON, so declared-vs-served drift is queryable. Declare ONE of `responseFormat` / `responseSchema`, not both.
190
268
  **§3.2 flex degrade is customer-side.** ModelRig does not replicate the gemini direct-path infra-retry wrapper on the raw lane; under `flex` a capacity rejection surfaces classified (`capacity_shed`) for your own `→ standard` retry ladder — a NAMED difference for your migration record, not a silent one.
269
+ **`destination` (managed serving) is RELEASED (GA).** The serving-destination column names which providers *can* be `modelrig-served` (reached on ModelRig's own key) vs `direct` only. Managed serving is gated behind a config flag (`config.managedServing`, default off, set in code — not an env var); it defaults off, so until you enable it every call is `direct` (ABSENT ⇒ byte-identical).
191
270
 
192
271
  ## Raw-lane defaults (rig.runRaw)
193
272
 
@@ -196,13 +275,14 @@ What `rig.runRaw` sends when you omit a knob. `timeoutMs` is tier-aware since 0.
196
275
  | Knob | Default | Note |
197
276
  |---|---|---|
198
277
  | `timeoutMs` | 300 s (standard, priority) · 900 s (flex) | tier-aware wall-clock deadline; an explicit `timeoutMs` always wins, and a `timeout` failure carries a `fixHint` naming the tier + budget |
199
- | `serviceTier` | standard | forwarded to the adapter; `meta.servedTier` reports what actually served. On `flex`, set `timeoutMs` in minutes (default 900 s) |
278
+ | `serviceTier` | standard | forwarded to the adapter; `meta.servedTier` reports what actually served, and the cost row is priced at the SERVED tier (gemini flex = 0.5× input + output, cache read NOT discounted; priority = 1.8×). On `flex`, set `timeoutMs` in minutes (default 900 s) |
200
279
  | `temperature` | adapter default — not sent | absent ⇒ no `temperature` on the request (byte-identical dispatch) |
201
280
  | `topP` | adapter default — not sent | absent ⇒ no `topP` on the request (byte-identical dispatch) |
202
281
  | `maxOutputTokens` | adapter default — not sent | absent ⇒ no output-token cap on the request (byte-identical dispatch) |
203
282
  | `grounding` | absent ⇒ byte-identical | native provider grounding; provider support per the raw-lane knob matrix |
204
283
  | `reasoning` | absent ⇒ byte-identical | reasoning/thinking effort; provider support per the raw-lane knob matrix |
205
284
  | `responseFormat` | absent ⇒ byte-identical | syntactic-JSON forcing (mime-shaping only); provider support per the raw-lane knob matrix |
285
+ | `responseSchema` | absent ⇒ byte-identical (no schema sent, no validation) | JSON Schema forwarded natively AND Ajv-checked client-side — a miss is a billed, terminal `content_invalid` (never repaired); `meta.schema_sha` on the row; provider support per the raw-lane knob matrix |
206
286
  | `cache` | none | customer cache handle passed through verbatim when supplied; ModelRig never creates, refreshes, or deletes the resource |
207
287
  | `tags` | none | `lane=raw` and `rung=primary` are stamped by the SDK and cannot be overridden by a caller tag |
208
288
  | `tools / toolChoice` | none | absent ⇒ no tools declared; on the raw lane a grounding-originated `functionCall` part is NOT a tool-call turn |
@@ -0,0 +1,150 @@
1
+ # Managed serving — `destination: modelrig-served`
2
+
3
+ Serve a route's model on ModelRig's own credentials instead of yours. One less
4
+ provider key to hold; the same route bundle, the same validate → repair →
5
+ fall-through pipeline, the same per-call telemetry — served through the hosted
6
+ gateway and paid from your organization's prepaid balance.
7
+
8
+ **The honest default is `direct`.** A route or run that declares no
9
+ `destination` is served exactly as today: your own provider key, ModelRig not in
10
+ the credential path, byte-identical behaviour. Nothing in this page changes a
11
+ route you have not opted in.
12
+
13
+ Requires `modelrig` **0.10.0 or newer** — the first published version that
14
+ carries the hosted serving transport. On an older package a `modelrig-served`
15
+ run is served through the local seam on whichever key the adapter already holds,
16
+ so upgrade before you rely on the keyless path.
17
+
18
+ ## What "served" means
19
+
20
+ | | `destination: direct` (default) | `destination: modelrig-served` |
21
+ |---|---|---|
22
+ | Who calls the provider | your process, with your key | the hosted gateway, with ModelRig's key |
23
+ | What your process sends | the provider request | your `rig_sk_` key + the route slug + input — **never a provider key** |
24
+ | What you pay | your provider bill | provider list cost plus the flat fee, drawn from your prepaid balance at serve time |
25
+ | Telemetry row | written locally, exported if configured | written server-side (server-measured cost); `meta.inferenceId` points at it |
26
+ | Tag on the row | `destination: direct` | `destination: modelrig-served` |
27
+
28
+ Which providers *can* be served is a fact in the capability table — the
29
+ `destination` column in [Route bundles → provider × knob support](route-bundles.md#raw-lane-provider--knob-support-rigrunraw)
30
+ (and in `llms.txt`). A provider whose cell reads `direct` only cannot be
31
+ `modelrig-served`; declaring it fails closed before any call.
32
+
33
+ ## Five steps
34
+
35
+ ### 1. Declare the destination on the route (or the run)
36
+
37
+ ```yaml
38
+ # modelrig/routes/example.support_summarize.yaml
39
+ route: example.support_summarize
40
+ version: 3
41
+ destination: modelrig-served # absent ⇒ direct (byte-identical)
42
+ candidates:
43
+ - { provider: <a served-capable provider>, model: <model> }
44
+ # …schema, prompt, policy unchanged
45
+ ```
46
+
47
+ A per-run override wins over the route:
48
+
49
+ ```ts
50
+ await rig.run("example.support_summarize", {
51
+ input: { ticket },
52
+ destination: "modelrig-served", // this run only
53
+ });
54
+ ```
55
+
56
+ Precedence: `RunOptions.destination` → route `destination` → `direct`.
57
+
58
+ ### 2. Enable managed serving in code
59
+
60
+ ```ts
61
+ import { createRig, loadConfigFromEnv } from "modelrig";
62
+
63
+ const rig = createRig({ ...loadConfigFromEnv(), managedServing: true });
64
+ ```
65
+
66
+ `managedServing` is a **config field set in code, never an environment
67
+ variable**. It defaults to `false`: with it off, a `modelrig-served` route
68
+ throws a teaching `invariant_violation` *before* any provider call rather than
69
+ silently serving `direct`. This is the per-process switch; the per-organization
70
+ switch is step 4.
71
+
72
+ ### 3. Mint a key with the `serve` scope
73
+
74
+ Managed serving spends money, so it is a **deliberate** grant: in the console
75
+ (Keys → new key) tick `serve`, and set that key as `MODELRIG_API_KEY` in the
76
+ process that runs the rig. The SDK sends this `rig_sk_` key as the only
77
+ credential on a served call. A key without `serve` is refused with a 403 that
78
+ names the scope.
79
+
80
+ ### 4. Turn managed serving on for your organization
81
+
82
+ An **owner** flips it in the console — **Account settings → Managed serving**
83
+ (off for every new organization). Or from a coding agent / script, through the
84
+ settings API with an `org-settings`-scoped key:
85
+
86
+ ```http
87
+ PATCH /v1/orgs/<your-org-id>/settings
88
+ { "managed_serving_enabled": true }
89
+ ```
90
+
91
+ The server's owner-only gate decides; a member or admin is refused, and the
92
+ setting applies to the **next** served call — no redeploy.
93
+
94
+ ### 5. Fund the prepaid balance
95
+
96
+ Served calls are debited at serve time: the server-measured provider cost plus
97
+ the flat fee, as two ledger entries anchored to the inference id (idempotent —
98
+ a retry never charges twice). Before dispatch the gateway checks that a
99
+ conservative reservation is available; otherwise it refuses with a 402 and
100
+ nothing is served. Top up in the console (Billing). A `direct` route never
101
+ needs a balance.
102
+
103
+ ## What the refusals teach
104
+
105
+ Every refusal is a `RigFailureError` of class `invariant_violation` — terminal,
106
+ pre-dispatch where possible, and never a silent fall back to `direct`. The
107
+ `fixHint` on the error is the text below.
108
+
109
+ | Status | Means | `fixHint` says |
110
+ |---|---|---|
111
+ | (local, before any call) | `managedServing` is off | "managed serving is not enabled — set `managedServing: true` in your rig config … otherwise remove `destination` or set it to `direct`." |
112
+ | (local, before any call) | the resolved candidate cannot be served | "provider … cannot be modelrig-served; served providers: …. Declare a served-capable candidate, or set `destination: direct`." |
113
+ | **401** | the `rig_sk_` key was rejected | "the `rig_sk_` control-plane key (MODELRIG_API_KEY) was not accepted. Check the key and that it belongs to the org you expect, then retry." |
114
+ | **402** | no / insufficient prepaid balance | "your organization has no prepaid managed-serving balance. Top up your balance in the console, or serve this route with `destination: direct` (your own provider key)." Server side: "each managed call draws the server-measured provider cost + 2% fee. Add funds (at least … must be available to start a call), then retry." |
115
+ | **403** | the key lacks `serve`, **or** the organization has not turned managed serving on | "this organization/key is not entitled to managed serving. Enable managed serving for your org in the console, or serve this route with `destination: direct`." Server side the two cases are distinct: "this key does not carry the `serve` scope" vs "this organization is not provisioned for managed serving — an OWNER … turns it on (Account settings → Managed serving)". |
116
+ | **404** | the route slug is not mirrored for your organization | "deploy that route from your repo so its bundle syncs to the mirror, then retry — the hosted lane serves only your own organization's routes." |
117
+ | **502** | the entitlement or balance could not be read | "refused on purpose (fail-closed) rather than served on an unknown entitlement / without confirming funds; retry shortly, and check control-plane health if it persists." |
118
+ | **503** | the call ran but its charge could not be anchored, or the hosted lane is misconfigured | "refused on purpose (fail-closed) rather than billed against a shared idempotency key — retry shortly." |
119
+
120
+ A 404 here is a routing fact, not a permissions oracle: the hosted lane resolves
121
+ slugs **only inside the calling key's organization**, so another organization's
122
+ route is simply "not found".
123
+
124
+ ## What you see on the row
125
+
126
+ A served call's inference row carries `destination: modelrig-served`, the
127
+ provider and model that actually served it, the served tier, the
128
+ server-measured cost (which is what the ledger drew), and the inference id the
129
+ charge is anchored to. It is the same row shape as every other call — the
130
+ console, the analytics surface, exports and tenant statements all read it
131
+ unchanged — so "which calls did ModelRig serve for me, and what did they cost"
132
+ is one filter on the `destination` tag.
133
+
134
+ ## Data posture
135
+
136
+ On the served path your request reaches the serving provider on ModelRig's
137
+ credentials, which makes that provider ModelRig's subprocessor for the request
138
+ — the published security page states the posture, and the named list is part
139
+ of the security-questionnaire pack available on request. Regulated or PHI data
140
+ stays on `direct` (or managed custody) and is not sent through the served path
141
+ until the subprocessor agreement chain is confirmed. Content custody is a
142
+ separate axis with its own account setting (see
143
+ [Artifact content custody](content-custody.md)); the serving destination never
144
+ changes what is stored.
145
+
146
+ ## See also
147
+
148
+ - [Route bundles](route-bundles.md) — the `destination` field and the provider × knob table.
149
+ - [Routing & reliability](routing-reliability.md) — what fails closed and why.
150
+ - [Quickstart](quickstart.md) — the `direct` path everything above is additive to.