modelrig 0.4.0 → 0.6.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 (189) hide show
  1. package/CHANGELOG.md +238 -0
  2. package/README.md +65 -3
  3. package/dist/adapters/anthropic.d.ts.map +1 -1
  4. package/dist/adapters/anthropic.js +36 -7
  5. package/dist/adapters/attempt-meta-map.d.ts +63 -0
  6. package/dist/adapters/attempt-meta-map.d.ts.map +1 -0
  7. package/dist/adapters/attempt-meta-map.js +154 -0
  8. package/dist/adapters/deepseek.d.ts.map +1 -1
  9. package/dist/adapters/deepseek.js +25 -2
  10. package/dist/adapters/gemini.d.ts +39 -0
  11. package/dist/adapters/gemini.d.ts.map +1 -1
  12. package/dist/adapters/gemini.js +82 -4
  13. package/dist/adapters/grok.d.ts.map +1 -1
  14. package/dist/adapters/grok.js +25 -2
  15. package/dist/adapters/openai-compat.d.ts.map +1 -1
  16. package/dist/adapters/openai-compat.js +25 -2
  17. package/dist/adapters/openai-stream.d.ts +2 -0
  18. package/dist/adapters/openai-stream.d.ts.map +1 -1
  19. package/dist/adapters/openai-stream.js +23 -3
  20. package/dist/adapters/openai.d.ts.map +1 -1
  21. package/dist/adapters/openai.js +36 -6
  22. package/dist/artifacts/artifact.d.ts +3 -2
  23. package/dist/artifacts/artifact.d.ts.map +1 -1
  24. package/dist/artifacts/artifact.js +35 -9
  25. package/dist/artifacts/classification.d.ts +27 -0
  26. package/dist/artifacts/classification.d.ts.map +1 -0
  27. package/dist/artifacts/classification.js +38 -0
  28. package/dist/artifacts/index.d.ts +3 -1
  29. package/dist/artifacts/index.d.ts.map +1 -1
  30. package/dist/artifacts/index.js +10 -1
  31. package/dist/artifacts/run-context.d.ts +33 -2
  32. package/dist/artifacts/run-context.d.ts.map +1 -1
  33. package/dist/artifacts/run-context.js +95 -1
  34. package/dist/artifacts/scrub.d.ts +26 -0
  35. package/dist/artifacts/scrub.d.ts.map +1 -1
  36. package/dist/artifacts/scrub.js +53 -0
  37. package/dist/artifacts/step.d.ts +9 -0
  38. package/dist/artifacts/step.d.ts.map +1 -1
  39. package/dist/artifacts/step.js +9 -0
  40. package/dist/bakeoff/engine.d.ts +46 -4
  41. package/dist/bakeoff/engine.d.ts.map +1 -1
  42. package/dist/bakeoff/engine.js +269 -19
  43. package/dist/bakeoff/eval-cases.d.ts +5 -0
  44. package/dist/bakeoff/eval-cases.d.ts.map +1 -1
  45. package/dist/bakeoff/eval-cases.js +3 -0
  46. package/dist/bakeoff/judge.d.ts +39 -0
  47. package/dist/bakeoff/judge.d.ts.map +1 -0
  48. package/dist/bakeoff/judge.js +102 -0
  49. package/dist/bakeoff/quality.d.ts +44 -0
  50. package/dist/bakeoff/quality.d.ts.map +1 -0
  51. package/dist/bakeoff/quality.js +126 -0
  52. package/dist/bakeoff/reference.d.ts +34 -0
  53. package/dist/bakeoff/reference.d.ts.map +1 -0
  54. package/dist/bakeoff/reference.js +68 -0
  55. package/dist/bakeoff/replay.d.ts +25 -0
  56. package/dist/bakeoff/replay.d.ts.map +1 -1
  57. package/dist/bakeoff/replay.js +65 -2
  58. package/dist/bakeoff/report.d.ts +8 -1
  59. package/dist/bakeoff/report.d.ts.map +1 -1
  60. package/dist/bakeoff/report.js +83 -12
  61. package/dist/bakeoff/samples.d.ts +35 -2
  62. package/dist/bakeoff/samples.d.ts.map +1 -1
  63. package/dist/bakeoff/samples.js +67 -6
  64. package/dist/bakeoff/stats.d.ts +17 -1
  65. package/dist/bakeoff/stats.d.ts.map +1 -1
  66. package/dist/bakeoff/stats.js +67 -0
  67. package/dist/bakeoff/types.d.ts +76 -5
  68. package/dist/bakeoff/types.d.ts.map +1 -1
  69. package/dist/bakeoff/types.js +4 -3
  70. package/dist/cli-query.d.ts +14 -0
  71. package/dist/cli-query.d.ts.map +1 -0
  72. package/dist/cli-query.js +59 -0
  73. package/dist/cli.d.ts +2 -1
  74. package/dist/cli.d.ts.map +1 -1
  75. package/dist/cli.js +47 -24
  76. package/dist/config.d.ts +6 -0
  77. package/dist/config.d.ts.map +1 -1
  78. package/dist/config.js +14 -0
  79. package/dist/detect/detector.d.ts +82 -0
  80. package/dist/detect/detector.d.ts.map +1 -0
  81. package/dist/detect/detector.js +23 -0
  82. package/dist/index.d.ts +34 -23
  83. package/dist/index.d.ts.map +1 -1
  84. package/dist/index.js +49 -12
  85. package/dist/query/analytics.d.ts +84 -0
  86. package/dist/query/analytics.d.ts.map +1 -0
  87. package/dist/query/analytics.js +280 -0
  88. package/dist/query/columns.d.ts +59 -0
  89. package/dist/query/columns.d.ts.map +1 -0
  90. package/dist/query/columns.js +89 -0
  91. package/dist/query/grammar.d.ts +74 -7
  92. package/dist/query/grammar.d.ts.map +1 -1
  93. package/dist/query/grammar.js +179 -71
  94. package/dist/query/saved.d.ts +26 -0
  95. package/dist/query/saved.d.ts.map +1 -0
  96. package/dist/query/saved.js +51 -0
  97. package/dist/registry/select.d.ts +59 -0
  98. package/dist/registry/select.d.ts.map +1 -1
  99. package/dist/registry/select.js +66 -0
  100. package/dist/routes/loader-warnings.d.ts +28 -0
  101. package/dist/routes/loader-warnings.d.ts.map +1 -0
  102. package/dist/routes/loader-warnings.js +56 -0
  103. package/dist/routes/loader.d.ts +1 -11
  104. package/dist/routes/loader.d.ts.map +1 -1
  105. package/dist/routes/loader.js +54 -31
  106. package/dist/routes/objective-loader.d.ts +35 -0
  107. package/dist/routes/objective-loader.d.ts.map +1 -0
  108. package/dist/routes/objective-loader.js +286 -0
  109. package/dist/run/accept.d.ts +43 -0
  110. package/dist/run/accept.d.ts.map +1 -0
  111. package/dist/run/accept.js +65 -0
  112. package/dist/run/cost.d.ts +13 -0
  113. package/dist/run/cost.d.ts.map +1 -1
  114. package/dist/run/cost.js +27 -0
  115. package/dist/run/dispatch.d.ts +24 -1
  116. package/dist/run/dispatch.d.ts.map +1 -1
  117. package/dist/run/dispatch.js +19 -0
  118. package/dist/run/ladder-key.d.ts +4 -0
  119. package/dist/run/ladder-key.d.ts.map +1 -1
  120. package/dist/run/ladder-key.js +3 -0
  121. package/dist/run/quality-gate.d.ts +39 -0
  122. package/dist/run/quality-gate.d.ts.map +1 -0
  123. package/dist/run/quality-gate.js +70 -0
  124. package/dist/run/raw-defaults.d.ts +52 -0
  125. package/dist/run/raw-defaults.d.ts.map +1 -0
  126. package/dist/run/raw-defaults.js +123 -0
  127. package/dist/run/raw.d.ts +12 -0
  128. package/dist/run/raw.d.ts.map +1 -1
  129. package/dist/run/raw.js +107 -8
  130. package/dist/run/record-attempt.d.ts.map +1 -1
  131. package/dist/run/record-attempt.js +22 -1
  132. package/dist/run/resolve.d.ts +17 -1
  133. package/dist/run/resolve.d.ts.map +1 -1
  134. package/dist/run/resolve.js +20 -4
  135. package/dist/run/routing-docs.d.ts.map +1 -1
  136. package/dist/run/routing-docs.js +12 -5
  137. package/dist/run/run.d.ts +7 -1
  138. package/dist/run/run.d.ts.map +1 -1
  139. package/dist/run/run.js +54 -69
  140. package/dist/run/variant.d.ts +43 -0
  141. package/dist/run/variant.d.ts.map +1 -0
  142. package/dist/run/variant.js +86 -0
  143. package/dist/savings/emit.d.ts +33 -0
  144. package/dist/savings/emit.d.ts.map +1 -1
  145. package/dist/savings/emit.js +75 -3
  146. package/dist/settings/index.d.ts +1 -1
  147. package/dist/settings/index.d.ts.map +1 -1
  148. package/dist/settings/index.js +2 -1
  149. package/dist/settings/task-settings.d.ts +10 -0
  150. package/dist/settings/task-settings.d.ts.map +1 -1
  151. package/dist/settings/task-settings.js +17 -0
  152. package/dist/telemetry/attempt-meta.d.ts +178 -0
  153. package/dist/telemetry/attempt-meta.d.ts.map +1 -0
  154. package/dist/telemetry/attempt-meta.js +319 -0
  155. package/dist/telemetry/exporter.d.ts.map +1 -1
  156. package/dist/telemetry/exporter.js +6 -0
  157. package/dist/telemetry/ingest-protocol.d.ts +11 -0
  158. package/dist/telemetry/ingest-protocol.d.ts.map +1 -1
  159. package/dist/telemetry/ingest-protocol.js +3 -0
  160. package/dist/telemetry/otel-map.d.ts +10 -0
  161. package/dist/telemetry/otel-map.d.ts.map +1 -1
  162. package/dist/telemetry/otel-map.js +64 -0
  163. package/dist/telemetry/store-migrations.d.ts.map +1 -1
  164. package/dist/telemetry/store-migrations.js +15 -0
  165. package/dist/telemetry/store.d.ts.map +1 -1
  166. package/dist/telemetry/store.js +25 -7
  167. package/dist/types/failures-teaching.d.ts.map +1 -1
  168. package/dist/types/failures-teaching.js +20 -0
  169. package/dist/types/failures.d.ts +1 -1
  170. package/dist/types/failures.d.ts.map +1 -1
  171. package/dist/types/failures.js +1 -0
  172. package/dist/types/provider.d.ts +9 -0
  173. package/dist/types/provider.d.ts.map +1 -1
  174. package/dist/types/quality.d.ts +41 -0
  175. package/dist/types/quality.d.ts.map +1 -0
  176. package/dist/types/quality.js +12 -0
  177. package/dist/types/route.d.ts +120 -0
  178. package/dist/types/route.d.ts.map +1 -1
  179. package/dist/types/telemetry.d.ts +13 -0
  180. package/dist/types/telemetry.d.ts.map +1 -1
  181. package/dist/validate-cli.d.ts.map +1 -1
  182. package/dist/validate-cli.js +10 -0
  183. package/dist/watch/evaluate.d.ts +27 -1
  184. package/dist/watch/evaluate.d.ts.map +1 -1
  185. package/dist/watch/evaluate.js +13 -0
  186. package/dist/watch/proposals.d.ts +28 -0
  187. package/dist/watch/proposals.d.ts.map +1 -1
  188. package/dist/watch/proposals.js +51 -0
  189. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -10,6 +10,244 @@ published package diverge.
10
10
  This project adheres to [Semantic Versioning](https://semver.org/). Dates are
11
11
  UTC.
12
12
 
13
+ ## [0.6.0] — 2026-08-23
14
+
15
+ Released with the docs/registry mirror in one R-A ceremony. Headline for router
16
+ codebases: the candidate ladder can now fall through on **quality** (`qualityGate`
17
+ → `quality_rejected`), bake-offs measure **regret against a reference arm** under a
18
+ declared objective (legacy routes stay cost-first, byte-identical), the console
19
+ gains the **project tree** (`/projects`) and the **analytics API**
20
+ (`/v1/analytics/meta` + `/query`, metadata only — never content), and the
21
+ OpenAI-compatible proxy accepts the `developer` role. ZERO DDL across the line.
22
+
23
+ Regret-bounded bake-offs (RB-1, near-optimal) — the bake-off objective flips from
24
+ "cheapest that passes" to **near-optimal at a reasonable price**. New additive
25
+ route YAML: `objective` (`mode: quality-first | balanced | cost-first`,
26
+ `max_regret`, `price_ceiling`, `lambda`), `bakeoff` (`reference: auto | provider/model`,
27
+ `reference_envelope_usd`, `judge: { model, max_samples }`), and `value_checks`
28
+ (JSON-pointer assertions). Every bake-off can run a **reference (ceiling) arm**
29
+ and report each arm's **regret vs the reference with a seeded-bootstrap CI and n**.
30
+ Quality per sample resolves eval-case golden → `value_checks` → an opt-in capped,
31
+ metered judge (judge model ∈ declared candidates) → 1/0 conformance; it composes
32
+ with Q1's `qualityGate` (deterministic checks first, then the gate/judge). The
33
+ proposal gate is objective-aware: `cost-first` keeps Q1's gate byte-identical
34
+ (golden); a declared `quality-first`/`balanced` objective with no bound reports
35
+ regret but proposes nothing (no silent actuation). `modelrig bakeoff --objective
36
+ <mode>` overrides per run. The report/PR body state regret, cost and the ceiling
37
+ on one line. ZERO DDL. See `docs/bakeoffs.md` §5.
38
+
39
+ Analytics surface (WI-1b) — the read side of the attempt envelope. `GET
40
+ /v1/analytics/meta` describes every dimension and metric with its classification
41
+ and `since` version; `POST /v1/analytics/query` runs the SELECT-only grammar over
42
+ an org's inferences (metadata only — enums, numbers, hashes, identifiers; never
43
+ content; bake-off replay rows excluded). `modelrig query "<grammar>"` /
44
+ `--saved <id>` run the same grammar locally. Console: saved questions on
45
+ `/costs`, `/coverage`, `/routes/[route]`. OTLP-out gains `modelrig.meta.*` span
46
+ attributes for the metadata-safe envelope fields. Behaviour change: a free-text
47
+ `actor` is dropped with one warning (metadata-safe rule). New root exports:
48
+ `parseQuery`, `compileQuery`, `evaluateAnalyticsQuery`, `describeAnalyticsSchema`,
49
+ `planAnalyticsRead`, `runTelemetryQuery`, `SAVED_QUERIES`, `savedQueryById`, the
50
+ column registries and their types.
51
+ ZERO DDL.
52
+
53
+ Quality gate (#223, Q1) — the candidate ladder can now fall through on *quality*,
54
+ not just shape. Pass `rig.run(route, { qualityGate })` and a schema-valid output
55
+ your predicate rejects is recorded as a new `quality_rejected` failure (its own
56
+ retry budget, never the repair rung) and the ladder advances. The same predicate
57
+ scores bake-offs (`rig.bakeoff({ qualityGate })`) — a quality pass rate and an
58
+ `effectiveUsdPer1kPassing` metric beside the conformant one. SDK lane only: a
59
+ function cannot cross the hosted boundary; the route-declared hosted judge is Q2.
60
+
61
+ ### Added — quality gate (#223, Q1)
62
+
63
+ - **`RunOptions.qualityGate?`** — a builder predicate
64
+ `(output, meta) => { ok, score?, reason? }` (sync or async) evaluated on a
65
+ schema-VALID output before it is accepted. `ok:false` records a
66
+ `quality_rejected` attempt (message carries the reason + score), captures the
67
+ rejected text for replay, and falls the ladder through on the class's OWN retry
68
+ budget (`policy.retries.quality_rejected`, default 0) — the repair rung is NEVER
69
+ entered from this path. A throwing predicate rejects fail-closed (`reason:
70
+ "qualityGate threw: …"`, one warning per process). Tool-call turns and
71
+ `schema: null` routes are handled per spec §4.1. `runStream` inherits it.
72
+ - **`FailureClass "quality_rejected"`** — retryable (own non-fungible budget),
73
+ never terminal, with an errors-that-teach entry (`docsUrl`
74
+ → routing-reliability.html#quality_rejected) and a suggested `policy.retries`
75
+ edit. `policy.retries.quality_rejected` is accepted by the loader.
76
+ - **`RunMeta.quality?`** `{ score, verdict, judgedBy }` — present when a gate ran
77
+ (`verdict: "pass"` on a returned run; `judgedBy: null` for an SDK predicate).
78
+ SDK-owned `tags.quality` / `tags.quality_score` are stamped on the attempt row.
79
+ - **`rig.bakeoff` / `rig.bakeoffFromEvalCases` gain `qualityGate?`** — scored on
80
+ each replayed sample into `ReplaySampleResult.valueAccuracy` + `qualityPassed`;
81
+ `VariantMetrics` gains `qualityPassCi95` and `effectiveUsdPer1kPassing` (beside,
82
+ never redefining, `effectiveUsdPer1kConformant`), and `computeProposal` gains a
83
+ third clause (challenger quality-pass CI-lower ≥ incumbent's). The `modelrig
84
+ bakeoff` CLI cannot carry a function — use programmatic `rig.bakeoff` until Q2.
85
+ - **Exports:** `QualityGate`, `QualityVerdict`, `QualityGateMeta` from the root.
86
+ - **Boundary B1 rewritten** to "Route-declared judge (hosted lane)" (`not_built`,
87
+ tripwire `/\bjudge\??\s*:/`) now the SDK predicate ships; the routing-reliability
88
+ page, llms.txt (C11), README, `/definitions`, and the v4 setup prompt are swept
89
+ to match.
90
+
91
+ Project tree (G2b) — the console gains `/projects` (picker) and `/projects/[value]`
92
+ (project → run → step → call → attempts, cost + quality + lane on every node) over
93
+ one new read, `GET /v1/console/tree`. Server + console only; the SDK change below
94
+ is documentation. ZERO DDL.
95
+
96
+ - **`@step` / `step()` sets the ambient step key.** Documented in `step.ts` that
97
+ entering a step scope mints `label#n` and makes it the run context's ambient
98
+ step: an `artifact.save` with no explicit `stepKey` attaches to it — for saves
99
+ inside the scope AND for saves after it, until the next step mints a new key
100
+ (the ambient key is a running "most recent step", not a lexical bracket).
101
+ `run-context.ts` clarifies that within one run's parallel step waves only an
102
+ explicit `stepKey` prevents cross-attribution (`run.scope()` separates distinct
103
+ runs, not overlapping steps in one run).
104
+
105
+ Screening (B0 + B1) — two bug-class fixes and a shared detector contract.
106
+ - **Bake-off review samples are scrubbed before they persist.** A `--keep-outputs`
107
+ bake-off now runs each sample's input and output through the rig's content-custody
108
+ scrub mode (`detect` counts onto `arm_meta.scrub`, `redact` masks, `block` keeps a
109
+ metadata-only sample). Default `off` ⇒ rows byte-identical to before.
110
+ - **Proxy role enum.** `messages[].role` must be `system|developer|user|assistant|tool`
111
+ (`developer` normalises to the system slot); anything else is rejected with a
112
+ teaching message before dispatch (HTTP 422). Previously an arbitrary role was cast
113
+ through.
114
+ - **Raw-lane multi-turn encoding (behaviour change).** A genuine multi-turn transcript
115
+ is now flattened into the adapter's user prompt as one JSON object per line instead
116
+ of `role: content` text, so a crafted turn can never read as a synthetic system
117
+ line. Single-user-turn (and system + single user) requests are byte-identical.
118
+ - **Shared `Detector` interface.** `Detector`, `DetectionSpan`, `detectSpans`, and
119
+ `scrubDetectors` are exported from the package root; `scrubContent` is unchanged.
120
+ `buildBakeoffSamples` gains a scrub-resolver argument.
121
+
122
+ ## [0.5.0] — 2026-08-22
123
+
124
+ Released with the docs/registry mirror in one R-A ceremony. Headline for router
125
+ codebases: `rig.runRaw` inside a run context now records a step (the `/runs`
126
+ tree fills in), raw-lane timeouts are tier-aware and teach, the `trial:`
127
+ candidate flag + console picker ship (deploy the server before any YAML uses
128
+ `trial:`), and the console rollups show cost and quality together by route or
129
+ tag. `modelrig-oracle` 0.2.0 ships alongside (`propose_candidates`).
130
+
131
+ Raw-lane defaults (#245) — the raw lane teaches its own timeouts. The `timeoutMs`
132
+ default becomes tier-aware, a `timeout` failure carries a teaching `fixHint`, an
133
+ empty no-tools gemini response carries a hint naming grounding, and the raw-lane
134
+ defaults are published as a generated table that cannot drift from the runtime.
135
+
136
+ Declare-first experiment ergonomics (routing-console R3, #239) — a `trial:`
137
+ candidate you declare but never serve live, a console challenger picker, and a
138
+ one-command bake-off that measures it on real captured traffic before you
139
+ promote it.
140
+
141
+ ### Changed
142
+
143
+ - **Raw-lane `timeoutMs` default is now tier-aware** (was a flat 120 s):
144
+ `standard`/`priority` default to 300 s and `flex` to 900 s (the 15-minute budget
145
+ ModelRig advertises) — a passthrough should not be shorter-fused than the tier it
146
+ advertises, and grounded/large prompts routinely exceed 120 s even at standard.
147
+ An explicit `timeoutMs` **always wins** (no pre-dispatch rejection — a short
148
+ deadline is a choice), and a `timeout` failure on the raw lane now carries a
149
+ `fixHint` naming the elapsed time, the tier, that tier's budget, and the two
150
+ fixes (raise `timeoutMs`, or drop to `serviceTier: "standard"`). The routed
151
+ lane's required `policy.timeout_ms` is unchanged. Callers who relied on a fast
152
+ 120 s failure should set `timeoutMs` explicitly.
153
+
154
+ ### Added — declare-first experiment (routing-console R3)
155
+
156
+ - **`candidates[].trial: true`** (route bundle) — a DECLARED candidate that is
157
+ NEVER served live. The loader parses and validates it, marks an all-trial
158
+ variant `trialOnly`, and warns on a trial candidate no variant references;
159
+ `resolveCandidates` drops trial candidates on every live path (`rig.run`,
160
+ `runStream`, `runBundle`, the hosted gateway slug lane) and keeps them only in
161
+ bake-off replay. Selecting a trial variant on a live call is a
162
+ `RouteConfigError` that names the bake-off. This lets a customer measure a new
163
+ model on real captured traffic before promoting it. Additive: a route with no
164
+ trial candidate is byte-identical to before.
165
+ - CAVEAT — **deploy the server before any customer YAML carries `trial:`.** An
166
+ SDK/server older than 0.5.0 IGNORES `trial:` (the loader has always been
167
+ tolerant of unknown candidate keys) and its resolver has no trial-mode filter,
168
+ so it would serve the candidate LIVE — including an older hosted-gateway build
169
+ reading the flag through the mirror. Roll the server first, then let routes
170
+ start declaring `trial:`. The console's declare-YAML says so inline.
171
+ - **`requirementPredicates(route)` / `evaluateChallengerEligibility(...)`** (new
172
+ exports) — decompose a route's `require` + json rung into capability-flag
173
+ requirements and answer "can this model be a trial challenger?", pinned in
174
+ agreement with the live resolver's `satisfiesRequirements`.
175
+ - **`buildDeclareTrialChange(bundle, challenger)` / `trialVariantName(...)`** and
176
+ a trial-winner branch of **`buildSwapChange`** (new exports) — the paste-ready
177
+ declare-YAML (challenger appended `trial: true`, a `trial-<provider>-<model>`
178
+ variant, `version: n+1`) and the promote-YAML that drops the winner's trial
179
+ flag, moves it to position 0, and removes the trial variant. No filesystem, no
180
+ git — the human applies the PR (F6).
181
+ - **`ManualChallengerTrigger` + `manualChallengerTriggerHash`**, and
182
+ **`proposalsClient.createManualChallenger`** — a console/oracle-initiated
183
+ declare-first proposal (`kind:"bakeoff"`, idempotent per `(route, challenger)`).
184
+ - **`RunMeta`/`Rig` additions:** `rig.capabilities` and `rig.searchAvailable` are
185
+ exposed so the challenger picker answers eligibility with the exact live flags;
186
+ `modelrig bakeoff --from-proposal <id>` reads the trial variant from a
187
+ manual_challenger proposal (default vs that one variant).
188
+ - **`resolveRouteBundle` / `hashResolvedBundle`** (new exports) — the canonical
189
+ `bundle_sha` helpers, used by the hosted-lane bake-off to prove the caller org's
190
+ mirrored bundle is the SAME bundle the deployment holds locally (a route-name
191
+ collision on a multi-tenant mirror otherwise baked off a foreign org's route).
192
+
193
+ ### Added
194
+
195
+ - **A grounding-naming `fixHint` on an empty no-tools gemini response.** When a
196
+ call that declared no tools comes back with no text parts (a grounding turn
197
+ that produced only `functionCall` parts, for example), the `content_invalid`
198
+ "empty response" failure now names grounding as the likely cause. Note: the
199
+ text extraction itself is unchanged in effect — the SDK already concatenated
200
+ mixed `functionCall` + text parts — so the customer-zero empty-response report
201
+ (#245 secondary) remains an OPEN investigation pending a captured response.
202
+
203
+ ### Docs
204
+
205
+ - **Raw-lane defaults table** — a generated table (`RAW_DEFAULTS`, rendered at the
206
+ `<!-- RAW_DEFAULTS -->` marker in `route-bundles.md` and in `llms.txt`) lists
207
+ every raw-lane knob and its default, with the tier-aware `timeoutMs` read from
208
+ the same constant the runtime resolves from — so the published defaults cannot
209
+ drift from what `rig.runRaw` sends. README / quickstart / migration-playbook
210
+ gain the "flex ⇒ set `timeoutMs` in minutes" one-liner.
211
+
212
+ Raw-lane artifact graph + tags parity (G1). A `rig.runRaw` / `rig.runRawStream`
213
+ call inside a `rig.artifacts.run.start` context now records a ground-truth step,
214
+ so a router or report pipeline that wraps raw (BYOK) calls in a run context gets
215
+ a real per-run tree at `/runs/[id]` instead of an empty one. ZERO DDL.
216
+
217
+ ### Added — raw-lane graph
218
+
219
+ - **Raw calls record a ground-truth step.** Inside a run context,
220
+ `runRaw`/`runRawStream` emit a step (on success AND failure, buffered AND
221
+ streaming) whose `inference_id` joins the metered `lane=raw` row — parity with
222
+ the routed lane's auto-step. Fail-open: a buffer error there never affects the
223
+ returned result. Outside a run context the dispatch is byte-identical to 0.4.0
224
+ (no step row). Raw steps are **metadata only** — no artifact content is written
225
+ on the raw lane.
226
+ - **`stepKey` on BOTH lanes.** `RunOptions.stepKey` (routed) and
227
+ `RawRunInput.stepKey` (raw) name the step. Resolution is `stepKey ?? tags.step
228
+ ?? auto` (`route#n` / `provider/model#n`), validated `[A-Za-z0-9_.:#-]{1,128}`;
229
+ an invalid value is warned once and dropped to the auto key (never thrown). A
230
+ raw row's step **task** is `tags.step ?? provider/model`.
231
+ - **Ambient artifact step.** `artifact.save` without a `stepKey` now attaches to
232
+ the ambient step (the most recent step in the run context), so the two grouping
233
+ mechanisms compose — a raw/routed call and the artifacts saved around it land in
234
+ one group.
235
+ - **Required-dimension hygiene on the raw lane.** `runRaw`/`runRawStream` run the
236
+ same once-per-process "required dimension missing from tags" warning the routed
237
+ lane does.
238
+
239
+ ### Added — docs
240
+
241
+ - **instrumentation.md** gains "Tags, dimensions, and the graph": tags = flat
242
+ filter/rollup (`/costs`), the graph = the per-run tree (`/runs/[id]`), how
243
+ `stepKey` + ambient artifacts compose, and the raw-lane behavior.
244
+ - **The per-service key rule** is now stated in the quickstart, the migration
245
+ playbook (F-6), and llms.txt: `MODELRIG_API_KEY` gates the exporter *per
246
+ process* — set it on EVERY service, worker, and cron, or a keyless process
247
+ buffers to its own local SQLite and ships nothing.
248
+ - **route-bundles.md** points at where `rig.yaml` `dimensions` are declared; the
249
+ **README** documents `stepKey` on the raw lane.
250
+
13
251
  ## [0.4.0] — 2026-08-22
14
252
 
15
253
  Released with the docs/registry mirror in one R-A ceremony. NOT in this tag:
package/README.md CHANGED
@@ -144,6 +144,19 @@ never stores it. It records one metered `lane=raw` telemetry row at the
144
144
  provider's list price (zero ModelRig margin) and throws a typed `RigFailureError`
145
145
  on failure, exactly like `rig.run`.
146
146
 
147
+ Called inside a `rig.artifacts.run.start` context, `runRaw`/`runRawStream` also
148
+ records a ground-truth **step** in the run's graph (`/runs/[id]`) — since
149
+ modelrig 0.5.0, on success and on failure. Name it with `stepKey` (the same field
150
+ `RunOptions` exposes on the routed lane; precedence `stepKey` > `tags.step` > an
151
+ auto `provider/model#n` key), so a raw call and the artifacts you `save` around it
152
+ group under one step.
153
+
154
+ The raw-lane `timeoutMs` default is **tier-aware** since 0.5.0 — `standard`/
155
+ `priority` 300 s, `flex` 900 s; an explicit `timeoutMs` always wins, and a
156
+ `timeout` failure teaches the tier + budget in its `fixHint`. **flex ⇒ set
157
+ `timeoutMs` in minutes** (default 900 s since 0.5.0). See the generated
158
+ [raw-lane defaults table](https://modelrig.dev/route-bundles.html#raw-lane-defaults-rigrunraw).
159
+
147
160
  **runRaw-only construction (`routesDir: null`) — 0.4.0+.** A
148
161
  Lane-B deployment that ONLY calls `runRaw`/`runRawStream` has no routes. Build it
149
162
  with `routesDir: null` and `createRig` skips route loading, the `rig.yaml`
@@ -166,9 +179,55 @@ Every attempt is recorded in a local SQLite file (`.modelrig/telemetry.db`)
166
179
  tagged with your `tags`. Failures are typed —
167
180
  `RigFailureError.failure.class` is one of `content_invalid`, `capacity_shed`,
168
181
  `network`, `refusal`, `cache_invalid`, `timeout`, `config_auth`, `budget_exhausted`,
169
- `invariant_violation` — with a separate retry budget per class, so a flaky
182
+ `invariant_violation`, `quality_rejected` — with a separate retry budget per class, so a flaky
170
183
  network never spends your content-validation retries. What each class means, whether it retries, and how it backs off is on [Routing & reliability](https://modelrig.dev/routing-reliability.html#5-failure-classes).
171
184
 
185
+ ## Metadata capture ("capture now, analyze later")
186
+
187
+ Every inference records a versioned, **metadata-safe** attempt envelope
188
+ (`AttemptMeta@v1`) on `inferences.meta` — in both the routed and the raw lane,
189
+ from the same producer. It is the metadata the adapters already see and used to
190
+ discard: finish reason, refusal, reasoning tokens, the provider-reported model
191
+ snapshot and request id, coarse HTTP status / error code, sampling echo, tool
192
+ names (declared kept, undeclared hashed) with counts, per-component cost, request
193
+ and output **hashes** (never the content), request/response byte sizes, response
194
+ char count, and a coarse context bucket.
195
+
196
+ **It never contains free text.** Every field is an enum, a number, a boolean, a
197
+ hash, or an identifier. The producer drops any string that is over 128 chars or
198
+ contains whitespace, and the hosted ingest endpoint re-applies the same rule as a
199
+ backstop — so analysis later is possible without prompts or outputs ever being
200
+ stored here. Building the envelope never blocks or changes a run: on any failure
201
+ it is simply absent (`meta` is null).
202
+
203
+ Runs carry a companion `RunMeta@v1` on `runs.meta`, set at `run.start`:
204
+
205
+ ```ts
206
+ const h = rig.run.start({
207
+ pipeline: "nightly-report",
208
+ meta: { v: 1, trigger: "cron", actor: "scheduler", config_hash: "…" },
209
+ // pipeline_sha auto-fills from git when you don't set it (never fails the run)
210
+ });
211
+ ```
212
+
213
+ ### Reserved dimensions
214
+
215
+ Business dimensions live in `tags`. Five keys are **well-known** and become
216
+ first-class group-bys in cost and coverage views — use them verbatim so your data
217
+ lines up with the console's built-ins:
218
+
219
+ | key | meaning |
220
+ |---|---|
221
+ | `subject` | the customer/tenant the work is for — an **opaque** id, hashed if it looks like PII |
222
+ | `feature` | the product feature the call serves |
223
+ | `user` | the end user (an opaque id) |
224
+ | `session` | the conversation/session the call belongs to |
225
+ | `cost_center` | the team/budget to attribute spend to |
226
+
227
+ `modelrig validate` warns on near-misses — a declared `rig.yaml` dimension named
228
+ `customer`, `tenant`, `user_id`, `team`, … is nudged toward the reserved key it
229
+ resembles. They stay ordinary tags; nothing is required.
230
+
172
231
  ## CLI
173
232
 
174
233
  The package ships a `modelrig` binary:
@@ -204,8 +263,11 @@ on the quickstart:
204
263
  - **`modelrig serve` needs the `modelrig-server` package** — it is not bundled
205
264
  here.
206
265
  - **What the ladder does and does not do.** Fall-through is structural and
207
- infrastructural, never a quality judgment; the full boundary (quality gate,
208
- raw-lane structure, latency ordering…) is generated and tripwire-tested on
266
+ infrastructural, plus an OPTIONAL SDK-lane `qualityGate` predicate you supply
267
+ (a schema-valid output it rejects becomes
268
+ `quality_rejected`); the route-declared
269
+ hosted judge is not built yet, and the full boundary (raw-lane structure, latency
270
+ ordering…) is generated and tripwire-tested on
209
271
  [Routing & reliability](https://modelrig.dev/routing-reliability.html#10-not-yet-implemented-the-boundary).
210
272
 
211
273
  ## License
@@ -1 +1 @@
1
- {"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../src/adapters/anthropic.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../src/adapters/anthropic.ts"],"names":[],"mappings":"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"}
@@ -22,6 +22,7 @@ exports.normalizeAnthropicInputUsage = normalizeAnthropicInputUsage;
22
22
  exports.createAnthropicAdapter = createAnthropicAdapter;
23
23
  const sdk_1 = __importDefault(require("@anthropic-ai/sdk"));
24
24
  const tool_mapping_1 = require("./tool-mapping");
25
+ const attempt_meta_map_1 = require("./attempt-meta-map");
25
26
  const sampling_1 = require("./sampling");
26
27
  const DEFAULT_MAX_TOKENS = 32000;
27
28
  const OUTPUT_TOOL_NAME = "route_output";
@@ -159,6 +160,8 @@ function createAnthropicAdapter(config) {
159
160
  supportsTools: () => true,
160
161
  async call(req) {
161
162
  const model = req.candidate.model;
163
+ // WI-1: native attempt envelope for this call (see openai.ts for the pattern).
164
+ let attemptMeta;
162
165
  // F1: post-stream failures still billed tokens — attach usage.
163
166
  const fail = (cls, message, fixHint, usage) => ({
164
167
  ok: false,
@@ -170,6 +173,7 @@ function createAnthropicAdapter(config) {
170
173
  ...(fixHint !== undefined ? { fixHint } : {}),
171
174
  },
172
175
  ...(usage !== undefined ? { usage } : {}),
176
+ ...(attemptMeta !== undefined ? { meta: attemptMeta } : {}),
173
177
  });
174
178
  const wantJson = req.outputSchema !== null;
175
179
  try {
@@ -210,9 +214,20 @@ function createAnthropicAdapter(config) {
210
214
  };
211
215
  let completionTokens = 0;
212
216
  let stopReason = null;
217
+ // WI-1 native fields: message.id + message.model arrive at message_start.
218
+ let responseModel = "";
219
+ let responseId = "";
213
220
  for await (const event of events) {
214
- if (event.type === "message_start" && event.message?.usage !== undefined) {
215
- inputUsage = normalizeAnthropicInputUsage(event.message.usage);
221
+ if (event.type === "message_start") {
222
+ if (event.message?.usage !== undefined) {
223
+ inputUsage = normalizeAnthropicInputUsage(event.message.usage);
224
+ }
225
+ if (responseModel === "" && typeof event.message?.model === "string") {
226
+ responseModel = event.message.model;
227
+ }
228
+ if (responseId === "" && typeof event.message?.id === "string") {
229
+ responseId = event.message.id;
230
+ }
216
231
  }
217
232
  else if (event.type === "content_block_start" &&
218
233
  wantTools &&
@@ -257,13 +272,25 @@ function createAnthropicAdapter(config) {
257
272
  };
258
273
  // Tool-call turn (spec §4.4): `arguments` is the raw JSON string the
259
274
  // model streamed (OpenAI parity) — text may be empty, skip the guards.
260
- if (wantTools && toolBlocks.size > 0) {
261
- const toolCalls = [...toolBlocks.values()].map((b, i) => ({
275
+ const toolCalls = wantTools && toolBlocks.size > 0
276
+ ? [...toolBlocks.values()].map((b, i) => ({
262
277
  id: b.id !== "" ? b.id : `call_${i}`,
263
278
  type: "function",
264
279
  function: { name: b.name, arguments: b.json === "" ? "{}" : b.json },
265
- }));
266
- return { ok: true, text, usage, toolCalls };
280
+ }))
281
+ : undefined;
282
+ // WI-1: native attempt envelope. refusal derives from stop_reason=refusal.
283
+ attemptMeta = (0, attempt_meta_map_1.buildAdapterMeta)("anthropic", {
284
+ req,
285
+ finishReasonRaw: stopReason,
286
+ refusal: stopReason === "refusal",
287
+ modelSnapshot: responseModel,
288
+ providerRequestId: responseId,
289
+ toolCalls,
290
+ streaming: false,
291
+ });
292
+ if (toolCalls !== undefined) {
293
+ return { ok: true, text, usage, toolCalls, meta: attemptMeta };
267
294
  }
268
295
  if (stopReason === "refusal") {
269
296
  return fail("refusal", "Anthropic refused this request (stop_reason=refusal)", undefined, usage);
@@ -274,11 +301,12 @@ function createAnthropicAdapter(config) {
274
301
  if (stopReason === "max_tokens" && wantJson) {
275
302
  return fail("content_invalid", "Anthropic structured output truncated (stop_reason=max_tokens)", "raise maxOutputTokens for this route", usage);
276
303
  }
277
- return { ok: true, text, usage };
304
+ return { ok: true, text, usage, meta: attemptMeta };
278
305
  }
279
306
  catch (error) {
280
307
  const message = error instanceof Error ? error.message : String(error);
281
308
  const { cls, fixHint } = classifyAnthropicFailure(message);
309
+ attemptMeta = (0, attempt_meta_map_1.transportErrorMeta)(error);
282
310
  return {
283
311
  ok: false,
284
312
  failure: {
@@ -289,6 +317,7 @@ function createAnthropicAdapter(config) {
289
317
  cause: error,
290
318
  ...(fixHint !== undefined ? { fixHint } : {}),
291
319
  },
320
+ meta: attemptMeta,
292
321
  };
293
322
  }
294
323
  },
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Per-adapter NATIVE metadata mapping (WI-1, spec §2). ONE place that turns a
3
+ * provider's native finish/refusal/usage/id signals into the additive
4
+ * `AdapterResult.meta` (`Partial<AttemptMetaV1>`). Adapters map ONLY what the
5
+ * provider reports; the producer (`telemetry/attempt-meta.ts`) adds
6
+ * hashes/sizes/cost and enforces the classification rule. Absent = unknown;
7
+ * nothing here is inferred.
8
+ *
9
+ * `refusal` derivation, per adapter:
10
+ * - openai / grok / deepseek / deepinfra / fireworks (OpenAI dialect): the
11
+ * stream carried a non-empty `choices[].delta.refusal` string, OR
12
+ * `finish_reason === "content_filter"`.
13
+ * - anthropic: `stop_reason === "refusal"`.
14
+ * - gemini: `finishReason` is `SAFETY` / `PROHIBITED_CONTENT` / `RECITATION`.
15
+ * The refusal TEXT itself is never carried (content) — only the boolean.
16
+ */
17
+ import type { AttemptMetaV1, FinishReason } from "../telemetry/attempt-meta";
18
+ import type { AdapterRequest, ProviderId, ToolCall } from "../types/provider";
19
+ /** Map a provider's native finish/stop reason to the `FinishReason` enum
20
+ * (spec §2). Unknown/absent ⇒ undefined (never inferred); an unrecognised
21
+ * non-empty value ⇒ `"other"`. */
22
+ export declare function mapFinishReason(provider: ProviderId, raw: string | null | undefined): FinishReason | undefined;
23
+ /** The declared sampling echo (spec §2 `sampling`) — the values ModelRig sent,
24
+ * read back from the request. topP → top_p, maxOutputTokens → max_output_tokens
25
+ * (the raw lane's top-level or the routed lane's `sampling` block). Numbers
26
+ * only; undefined when the caller declared none. */
27
+ export declare function samplingEcho(req: AdapterRequest): {
28
+ temperature?: number;
29
+ top_p?: number;
30
+ max_output_tokens?: number;
31
+ } | undefined;
32
+ /** Aggregate normalized tool calls into `{ name, count }` (RAW names — the
33
+ * producer hashes undeclared ones). Undefined when the turn made none. */
34
+ export declare function aggregateToolCalls(toolCalls: readonly ToolCall[] | undefined): {
35
+ name: string;
36
+ count: number;
37
+ }[] | undefined;
38
+ /** The signals an adapter collects on ONE completed call. All optional. */
39
+ export interface AdapterMetaSignals {
40
+ readonly req: AdapterRequest;
41
+ readonly finishReasonRaw?: string | null;
42
+ readonly refusal?: boolean;
43
+ readonly reasoningTokens?: number;
44
+ readonly modelSnapshot?: string;
45
+ readonly providerRequestId?: string;
46
+ readonly region?: string;
47
+ readonly toolCalls?: readonly ToolCall[];
48
+ readonly grounding?: {
49
+ citations?: number;
50
+ searchProvider?: string;
51
+ };
52
+ /** true = the run loop streamed deltas (stream()); false/absent = buffered. */
53
+ readonly streaming?: boolean;
54
+ }
55
+ /** Build one adapter's NATIVE `meta` (spec §2). Only defined signals appear —
56
+ * everything else is unknown and omitted. The producer takes it from here. */
57
+ export declare function buildAdapterMeta(provider: ProviderId, s: AdapterMetaSignals): Partial<AttemptMetaV1>;
58
+ /** A transport/SDK error's coarse envelope (spec §2 `http_status`/`error_code`).
59
+ * Reads `error.status` (number) and a short `error.code` / `error.error.code`
60
+ * slug — NEVER the message (that is free text). The producer's classification
61
+ * rule drops a code that turns out to be free text. */
62
+ export declare function transportErrorMeta(error: unknown): Partial<AttemptMetaV1>;
63
+ //# sourceMappingURL=attempt-meta-map.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attempt-meta-map.d.ts","sourceRoot":"","sources":["../../src/adapters/attempt-meta-map.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAC7E,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE9E;;kCAEkC;AAClC,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,UAAU,EACpB,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC7B,YAAY,GAAG,SAAS,CA6C1B;AAED;;;oDAGoD;AACpD,wBAAgB,YAAY,CAC1B,GAAG,EAAE,cAAc,GAClB;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAOlF;AAED;0EAC0E;AAC1E,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,SAAS,QAAQ,EAAE,GAAG,SAAS,GACzC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAAE,GAAG,SAAS,CAQ/C;AAED,2EAA2E;AAC3E,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,GAAG,EAAE,cAAc,CAAC;IAC7B,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,QAAQ,EAAE,CAAC;IACzC,QAAQ,CAAC,SAAS,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACrE,+EAA+E;IAC/E,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED;8EAC8E;AAC9E,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,UAAU,EACpB,CAAC,EAAE,kBAAkB,GACpB,OAAO,CAAC,aAAa,CAAC,CAyBxB;AAED;;;uDAGuD;AACvD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC,CASzE"}