pi-smart-router 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/.pi/extensions/smart-router/command-formatters.ts +2 -1
  2. package/.pi/extensions/smart-router/commands.ts +2 -1
  3. package/.pi/extensions/smart-router/types.ts +7 -1
  4. package/README.md +96 -6
  5. package/bin/pi-smart-router.mjs +6 -4
  6. package/config/benchmark-profiles.json +2 -2
  7. package/config/operator-config.json.example +7 -0
  8. package/config/p-success-weights.json +19 -12
  9. package/config/routing-calibration.json +157 -24
  10. package/data/contrib/example.json +5 -2
  11. package/dist/cli/smart-router-cli.d.ts +21 -3
  12. package/dist/cli/smart-router-cli.d.ts.map +1 -1
  13. package/dist/cli/smart-router-cli.js +56 -11
  14. package/dist/cli/smart-router-cli.js.map +1 -1
  15. package/dist/config/defaults.d.ts.map +1 -1
  16. package/dist/config/defaults.js +3 -1
  17. package/dist/config/defaults.js.map +1 -1
  18. package/dist/domain/matching/embedding-provider.d.ts +63 -1
  19. package/dist/domain/matching/embedding-provider.d.ts.map +1 -1
  20. package/dist/domain/matching/embedding-provider.js +117 -0
  21. package/dist/domain/matching/embedding-provider.js.map +1 -1
  22. package/dist/domain/matching/encoder-gate.d.ts +73 -0
  23. package/dist/domain/matching/encoder-gate.d.ts.map +1 -0
  24. package/dist/domain/matching/encoder-gate.js +66 -0
  25. package/dist/domain/matching/encoder-gate.js.map +1 -0
  26. package/dist/domain/matching/hydra-matcher.d.ts +45 -3
  27. package/dist/domain/matching/hydra-matcher.d.ts.map +1 -1
  28. package/dist/domain/matching/hydra-matcher.js +48 -3
  29. package/dist/domain/matching/hydra-matcher.js.map +1 -1
  30. package/dist/domain/pipeline/low-intensity-stage.d.ts.map +1 -1
  31. package/dist/domain/pipeline/low-intensity-stage.js +20 -2
  32. package/dist/domain/pipeline/low-intensity-stage.js.map +1 -1
  33. package/dist/domain/pipeline/router-pipeline.d.ts +5 -0
  34. package/dist/domain/pipeline/router-pipeline.d.ts.map +1 -1
  35. package/dist/domain/pipeline/router-pipeline.js +16 -0
  36. package/dist/domain/pipeline/router-pipeline.js.map +1 -1
  37. package/dist/domain/pipeline/triage-stage.d.ts +1 -0
  38. package/dist/domain/pipeline/triage-stage.d.ts.map +1 -1
  39. package/dist/domain/pipeline/triage-stage.js +21 -1
  40. package/dist/domain/pipeline/triage-stage.js.map +1 -1
  41. package/dist/domain/routing/tier-features.d.ts +3 -1
  42. package/dist/domain/routing/tier-features.d.ts.map +1 -1
  43. package/dist/domain/routing/tier-features.js +3 -2
  44. package/dist/domain/routing/tier-features.js.map +1 -1
  45. package/dist/domain/triage/triage-engine.d.ts +10 -1
  46. package/dist/domain/triage/triage-engine.d.ts.map +1 -1
  47. package/dist/domain/triage/triage-engine.js +19 -2
  48. package/dist/domain/triage/triage-engine.js.map +1 -1
  49. package/dist/domain/triage/triage-thresholds.d.ts +45 -0
  50. package/dist/domain/triage/triage-thresholds.d.ts.map +1 -0
  51. package/dist/domain/triage/triage-thresholds.js +107 -0
  52. package/dist/domain/triage/triage-thresholds.js.map +1 -0
  53. package/dist/domain/types/entities.d.ts +29 -0
  54. package/dist/domain/types/entities.d.ts.map +1 -1
  55. package/dist/domain/types/schemas.d.ts +65 -7
  56. package/dist/domain/types/schemas.d.ts.map +1 -1
  57. package/dist/domain/types/schemas.js +47 -3
  58. package/dist/domain/types/schemas.js.map +1 -1
  59. package/dist/infra/telemetry.d.ts +8 -0
  60. package/dist/infra/telemetry.d.ts.map +1 -1
  61. package/dist/infra/telemetry.js +10 -0
  62. package/dist/infra/telemetry.js.map +1 -1
  63. package/dist/infrastructure/persistence/sqlite-store.d.ts.map +1 -1
  64. package/dist/infrastructure/persistence/sqlite-store.js +48 -3
  65. package/dist/infrastructure/persistence/sqlite-store.js.map +1 -1
  66. package/dist/infrastructure/telemetry/dataset-recorder.d.ts.map +1 -1
  67. package/dist/infrastructure/telemetry/dataset-recorder.js +3 -0
  68. package/dist/infrastructure/telemetry/dataset-recorder.js.map +1 -1
  69. package/dist/infrastructure/telemetry/routing-telemetry.d.ts +1 -1
  70. package/dist/infrastructure/telemetry/routing-telemetry.d.ts.map +1 -1
  71. package/dist/infrastructure/telemetry/routing-telemetry.js +1 -1
  72. package/dist/infrastructure/telemetry/routing-telemetry.js.map +1 -1
  73. package/dist/infrastructure/telemetry/telemetry-scalar-fields.d.ts +4 -0
  74. package/dist/infrastructure/telemetry/telemetry-scalar-fields.d.ts.map +1 -1
  75. package/dist/infrastructure/telemetry/telemetry-scalar-fields.js +4 -0
  76. package/dist/infrastructure/telemetry/telemetry-scalar-fields.js.map +1 -1
  77. package/package.json +1 -1
  78. package/specs/001-build-smart-router/contracts/telemetry-contrib.schema.json +23 -1
  79. package/src/cli/smart-router-cli.ts +68 -9
  80. package/src/config/defaults.ts +3 -0
  81. package/src/domain/matching/embedding-provider.ts +205 -1
  82. package/src/domain/matching/encoder-gate.ts +121 -0
  83. package/src/domain/matching/hydra-matcher.ts +106 -6
  84. package/src/domain/pipeline/low-intensity-stage.ts +21 -2
  85. package/src/domain/pipeline/router-pipeline.ts +22 -0
  86. package/src/domain/pipeline/triage-stage.ts +23 -1
  87. package/src/domain/routing/tier-features.ts +3 -1
  88. package/src/domain/triage/triage-engine.ts +28 -2
  89. package/src/domain/triage/triage-thresholds.ts +153 -0
  90. package/src/domain/types/entities.ts +29 -0
  91. package/src/domain/types/schemas.ts +57 -7
  92. package/src/infra/telemetry.ts +14 -0
  93. package/src/infrastructure/persistence/sqlite-store.ts +61 -3
  94. package/src/infrastructure/telemetry/dataset-recorder.ts +3 -0
  95. package/src/infrastructure/telemetry/routing-telemetry.ts +1 -0
  96. package/src/infrastructure/telemetry/telemetry-scalar-fields.ts +5 -0
@@ -184,11 +184,12 @@ export function parseSmartRouterArgs(args: string): ParsedSmartRouterCommand {
184
184
  }
185
185
 
186
186
  if (tokens[0] === 'export' && tokens[1] === 'telemetry-contrib') {
187
- const { limit } = parseExportTelemetryContribArgs(tokens.join(' '));
187
+ const { limit, includeEmbeddings } = parseExportTelemetryContribArgs(tokens.join(' '));
188
188
  return {
189
189
  command: 'export',
190
190
  subcommand: 'telemetry-contrib',
191
191
  limit: Math.min(limit, DEFAULT_TELEMETRY_CONTRIB_EXPORT_LIMIT),
192
+ includeEmbeddings,
192
193
  };
193
194
  }
194
195
 
@@ -21,7 +21,7 @@ import { FLEET_MODE_ENTRY_TYPE } from './session-lifecycle.js';
21
21
  import type { SmartRouterRuntime } from './types.js';
22
22
 
23
23
  export const SMART_ROUTER_USAGE =
24
- '/smart-router [status] | history [limit] | stats [limit] | mode scoped|all | pricing refresh | export dataset [--limit N] | export telemetry-contrib [--limit N] | feedback good|bad | unpin | plan [--json] | doctor';
24
+ '/smart-router [status] | history [limit] | stats [limit] | mode scoped|all | pricing refresh | export dataset [--limit N] | export telemetry-contrib [--limit N] [--embeddings] | feedback good|bad | unpin | plan [--json] | doctor';
25
25
 
26
26
  type CompletionItem = { value: string; label: string };
27
27
 
@@ -237,6 +237,7 @@ export function registerSmartRouterCommand(
237
237
  store: runtime.store,
238
238
  cwd: ctx.cwd,
239
239
  limit: parsed.limit,
240
+ includeEmbeddings: parsed.includeEmbeddings,
240
241
  });
241
242
  if (!result.path) {
242
243
  ctx.ui.notify(
@@ -39,7 +39,13 @@ export type SmartRouterCommand =
39
39
  | { command: 'mode'; mode: FleetMode }
40
40
  | { command: 'pricing'; subcommand: 'refresh' }
41
41
  | { command: 'export'; subcommand: 'dataset'; limit: number }
42
- | { command: 'export'; subcommand: 'telemetry-contrib'; limit: number }
42
+ | {
43
+ command: 'export';
44
+ subcommand: 'telemetry-contrib';
45
+ limit: number;
46
+ /** Include captured 384-dim embeddings in contrib rows (SP-285, #170). */
47
+ includeEmbeddings?: boolean;
48
+ }
43
49
  | { command: 'feedback'; rating: 'good' | 'bad' }
44
50
  | { command: 'unpin' };
45
51
 
package/README.md CHANGED
@@ -225,7 +225,7 @@ Cursor models bill against your **Cursor Pro subscription quota**, not per-token
225
225
  | `/smart-router mode all` | Route among **all authenticated models** in the registry |
226
226
  | `/smart-router pricing refresh` | Manually fetch LiteLLM pricing from `LITELLM_PRICING_URL`, persist to SQLite, and rebuild the fleet with updated rates |
227
227
  | `/smart-router export dataset [--limit N]` | Export opt-in routing dataset as JSONL (requires `SMART_ROUTER_DATASET=1`) |
228
- | `/smart-router export telemetry-contrib [--limit N]` | Export privacy-safe community telemetry JSON for calibration contributions |
228
+ | `/smart-router export telemetry-contrib [--limit N] [--embeddings]` | Export privacy-safe community telemetry JSON for calibration contributions (`--embeddings` opts in to captured 384-dim embedding rows) |
229
229
  | `/smart-router feedback good\|bad` | Label the last auto-routed request outcome (requires `SMART_ROUTER_DATASET=1`) |
230
230
  | `/smart-router unpin` | Clear the current session pin (in-memory and SQLite) so the next request runs the full routing pipeline |
231
231
  | `/smart-router plan [--json]` | **Read-only** local placement report: encoder resident status, local model warm/cold, RAM/disk constraints, cold vs warm TPS, and bottleneck guess. `--json` prints the schema-stable report for automation |
@@ -440,6 +440,7 @@ Cluster IDs are stable reason-code prefixes (`cluster_low_stakes_general`, `clus
440
440
  | `SMART_ROUTER_LOG_ROUTING` | (unset) | Set to `1` to log each routing decision to stderr as JSON (debugging dogfood sessions). Canonical payload builder (`buildRoutingDecisionLogPayload`) includes top-level `stage`, `reason_code`, `low_intensity_score`, `tier_hint`, `local_eligible_reason`, and `cluster_id` (plus nested `cluster_summary` / `features`). The pi extension’s live stderr logger is still a slim subset — see [LOG_ROUTING field checklist](#log_routing-field-checklist) |
441
441
  | `SMART_ROUTER_DATASET` | (unset) | Set to `1` to opt in to privacy-safe routing dataset capture (metadata and feature fields only; 30-day / 10k-row retention). Prompt text, messages, and tool arguments are never stored. Required for outcome labels and P(success) training export. See [#8](https://github.com/beettlle/pi-smart-router/issues/8). |
442
442
  | `SMART_ROUTER_DATASET_FINGERPRINT` | (unset) | Set to `1` (requires `SMART_ROUTER_DATASET=1`) to store an install-local HMAC-SHA256 fingerprint of each normalized prompt for duplicate detection within this install. The install pepper lives in `.pi-smart-router/.dataset-key` (gitignored) and is never exported. **Warning:** short or common prompts are vulnerable to offline rainbow-table guessing; use only when you accept that tradeoff. See [#10](https://github.com/beettlle/pi-smart-router/issues/10). |
443
+ | `SMART_ROUTER_DATASET_EMBEDDINGS` | (unset) | Set to `1` (requires `SMART_ROUTER_DATASET=1`) to capture the raw 384-dim HyDRA encoder embedding on each dataset row (derived dense vector of the metadata-prefixed routing input — never prompt text). Embeddings stay local until you additionally export with `--embeddings`; they exist so the hydra_projection ≥100-row training floor can ever be met. See [#170](https://github.com/beettlle/pi-smart-router/issues/170). |
443
444
  | `MODELS_YAML_PATH` | `./config/models.yaml` | Fleet catalog path (library API only) |
444
445
  | `SMART_ROUTER_PLANNING_TURN_BUFFER` | `2` | SAAR planning buffer: frontier planning turns allowed before hard-lock ([v0.2.0 Continuity](https://github.com/beettlle/pi-smart-router/issues/72)) |
445
446
  | `SMART_ROUTER_PLANNING_DELEGATE_ENABLED` | `true` | Enable cache-preserving planning delegate ([#71](https://github.com/beettlle/pi-smart-router/issues/71)) |
@@ -720,9 +721,13 @@ Optional `feedback_good` / `feedback_bad` only refine labels when the operator c
720
721
 
721
722
  **Sample floor:** collect at least **≥30** labeled **economical-tier** rows (`minimum_training_samples.p_success_weights` / `isotonic_calibrator` in [`config/routing-calibration.json.example`](config/routing-calibration.json.example)) before relying on non-neutral `P(success)` or isotonic. Below that floor the classifier returns neutral `P_success_cheap = 0.5`.
722
723
 
723
- **Provenance today (honest defaults, v1.0.0):** checked-in `config/p-success-weights.json` and `config/routing-calibration.json` ship **neutralized** (`trained_sample_count: 0` for P(success), isotonic, and triage; HyDRA remains 0/≥100). Serve-time routing uses **structural tier hints** and `P_success_cheap = 0.5` until a verifier-graded retrain clears hard ECE gates (`holdout_ece_calibrated holdout_ece_raw`, absolute ECE 0.10, non-degenerate `y_knots`). Scripted `scripts/qa/dogfood-gather.sh` labels are **quarantined** (`scripted_intent`) and must not feed ship trains. Bootstrap routing centroids remain in the bundle. TwinRouterBench CI corpus soft-fail (`mean_over_routing_rate 0.87` vs 0.15) is a **harness adapter artifact** ([#112](https://github.com/beettlle/pi-smart-router/issues/112) / [`over-routing-analysis.md`](spine-tasks/_authoring/release-v0.11.0/over-routing-analysis.md)), not a live-pipeline measurement absolute release gates stay fixture-backed; frugality stays. [#95](https://github.com/beettlle/pi-smart-router/issues/95) / [#110](https://github.com/beettlle/pi-smart-router/issues/110) stay open for human dogfood + verifier-graded recalibration.
724
+ **Label provenance (SP-281 / [#168](https://github.com/beettlle/pi-smart-router/issues/168)):** every labeled export/contrib row may carry `label_provenance` `human_feedback` (human-rated dogfood turn, [#95](https://github.com/beettlle/pi-smart-router/issues/95)), `llm_judge` (adversarial harness graders, [#169](https://github.com/beettlle/pi-smart-router/issues/169)), or `scripted_intent` (scripted pack intent / exit heuristics). **Ship-claim sample floors count only `human_feedback` | `llm_judge` rows** `scripted_intent` and untagged (legacy) rows never count toward ship floors, and trainers skip `scripted_intent` rows entirely. `scripts/qa/dogfood-gather.sh` auto-tags all of its export output `scripted_intent`; `routing:calibration-aggregate` prints the provenance breakdown, rejects unknown grades, and `--ship-grade-only` emits just the verifier-grade rows for ship trains. Provenance is never inventedabsent stays absent (untagged).
724
725
 
725
- **SP-206 / hybrid interim:** July dogfood and a Sept hybrid were explored during the 1.0 train; Sept isotonic collapsed (same-x PAV overwrite) and scripted gather labels failed #110 honesty. Code now pools same-x scores and skips null labels; shipped artifacts stay honest-untrained until post-1.0 verifier-graded trains land.
726
+ **Provenance today (v1.1.0 trained ship):** checked-in `config/p-success-weights.json` and `config/routing-calibration.json` ship `provenance.source: verifier_grade_train_2026-09-12` with `trained_sample_count: 243` for P(success)/isotonic, triage threshold **5** (57 samples, serve-active via #171 loader), HyDRA still 0/≥100. Serve-time routing uses trained expected-cost tier hints (no longer neutral `P=0.5`). Scripted `scripts/qa/dogfood-gather.sh` labels stay **quarantined** (`scripted_intent`) and must not feed ship trains. TwinRouterBench CI corpus soft-fail remains a **harness adapter artifact** ([#112](https://github.com/beettlle/pi-smart-router/issues/112)). [#95](https://github.com/beettlle/pi-smart-router/issues/95) / [#110](https://github.com/beettlle/pi-smart-router/issues/110) stay open for human dogfood volume and HyDRA embeddings.
727
+
728
+ **v1.1.0 update:** Sep 12 hard gates **PASSED** (ECE cal 0.0645) on 147 `human_feedback` + 96 live `llm_judge` packs — decision record [`hard-gate-pass-2026-09-12.md`](spine-tasks/_authoring/release-v1.1.0/hard-gate-pass-2026-09-12.md) and [migration guide](docs/migration-v1.md#behavioral-calibration-artifacts-110--168). Session-holdout ECE is implemented but not yet ship-cleared ([`session-holdout-retrain-2026-09-12.md`](spine-tasks/_authoring/release-v1.1.0/session-holdout-retrain-2026-09-12.md)). Issue bookkeeping: [`operator-notes-issues.md`](spine-tasks/_authoring/release-v1.1.0/operator-notes-issues.md) — no encoder/frugality default flips; `modernbert_k4` (#96) stays deferred.
729
+
730
+ **SP-206 / hybrid interim:** July dogfood and a Sept hybrid were explored during the 1.0 train; Sept isotonic collapsed (same-x PAV overwrite) and scripted gather labels failed #110 honesty. Code now pools same-x scores and skips null labels; v1.1 ships verifier-graded weights after the Sep 12 PASS.
726
731
 
727
732
  **Zero-manual-label path (aggregate → train → verify):**
728
733
 
@@ -735,6 +740,7 @@ SMART_ROUTER_DATASET=1
735
740
 
736
741
  # 2) Aggregate privacy-safe contrib / exports (reject tainted payloads)
737
742
  npm run routing:calibration-aggregate -- --contrib-dir data/contrib
743
+ # Verifier-grade trains: add --ship-grade-only (human_feedback | llm_judge rows only)
738
744
 
739
745
  # 3) Train when ≥30 economical-tier labeled rows exist
740
746
  npm run routing:train-p-success -- --input path/to/export.jsonl --output config/p-success-weights.json
@@ -771,7 +777,7 @@ npm run routing:train-calibration -- --input path/to/aggregated.jsonl
771
777
 
772
778
  Reload is file-based: replace `config/p-success-weights.json` (and optionally `config/routing-calibration.json` for isotonic) and restart the host agent — no prompt text is ever written into training artifacts.
773
779
 
774
- **Isotonic calibration (shipped since v1.0.0):** serve-time isotonic calibration loads from `config/routing-calibration.json` (`isotonic_calibrator`). The checked-in bundle ships an **honest-untrained** calibrator (`trained_sample_count: 0` identity knots), so serve-time `p_success_calibrated` / `p_success_cheap` stay at neutral `0.5` with structural tier hints until a verifier-graded retrain clears hard ECE gates (calibrated ECE ≤ raw, absolute ECE ≤ 0.10, non-degenerate `y_knots`). Operators who retrain should pass `--calibration-output` or run `routing:train-calibration` with ≥30 labeled samples (not `dogfood-gather.sh` scripted_intent). Explain and telemetry still expose `p_success_raw` vs `p_success_calibrated` / `p_success_cheap`.
780
+ **Isotonic calibration (shipped trained since v1.1.0):** serve-time isotonic calibration loads from `config/routing-calibration.json` (`isotonic_calibrator`). The checked-in bundle ships a **trained** calibrator (`trained_sample_count: 243`, provenance `verifier_grade_train_2026-09-12`). Below the ≥30 floor or when the bundle is missing, serve-time falls back to identity / neutral `0.5`. Operators who retrain should run `routing:train-calibration` with verifier-grade labels only (not `dogfood-gather.sh` scripted_intent) and `--require-hard-gates`. Explain and telemetry still expose `p_success_raw` vs `p_success_calibrated` / `p_success_cheap`.
775
781
 
776
782
  Library helpers (see `src/domain/routing/p-success-classifier.ts`):
777
783
 
@@ -785,13 +791,19 @@ Library helpers (see `src/domain/routing/p-success-classifier.ts`):
785
791
  When `SMART_ROUTER_DATASET=1`, you can export privacy-safe scalar routing features (plus outcome labels) for community calibration training. The export never includes prompt text, messages, raw session identifiers, or install-local pepper fields.
786
792
 
787
793
  ```bash
788
- /smart-router export telemetry-contrib [--limit N]
794
+ /smart-router export telemetry-contrib [--limit N] [--embeddings]
789
795
  # or from shell (cwd must contain .pi-smart-router/state.db):
790
- npx pi-smart-router export telemetry-contrib [--limit N]
796
+ npx pi-smart-router export telemetry-contrib [--limit N] [--embeddings]
791
797
  ```
792
798
 
793
799
  This writes schema-valid JSON to `.pi-smart-router/exports/telemetry-contrib-<timestamp>.json`. Each row conforms to [`telemetry-contrib.schema.json`](specs/001-build-smart-router/contracts/telemetry-contrib.schema.json).
794
800
 
801
+ **Feature-complete rows (v1.1.0, [#170](https://github.com/beettlle/pi-smart-router/issues/170)).** Contrib rows now carry additive optional fields within format v2:
802
+
803
+ - `row_id` — stable per-install HMAC-SHA256 of `request_id` (domain-separated `row:` prefix; raw request ids are never exported). Aggregation dedupes overlapping exports by `row_id`, and training splits stay reproducible as files are added or removed.
804
+ - `prompt_length_chars` / `message_count` — count-only envelope fields that feed the P(success) prompt-length feature and edit-distance proxies (previously dropped on this path, starving the feature to zero).
805
+ - `embedding` — **double opt-in**: captured only when `SMART_ROUTER_DATASET_EMBEDDINGS=1` was set at routing time, and exported only when you pass `--embeddings`. This is the raw 384-dim encoder vector HyDRA projection training needs to reach its ≥100-row floor; it is a derived dense vector of the metadata-prefixed routing input, never prompt text. `calibration-aggregate` reports how many aggregated rows carry embeddings and warns while the count is below the hydra_projection floor (honest-untrained defaults remain until the floor is met).
806
+
795
807
  **Export schema v2 — session-hash migration (v0.21.0, [#146](https://github.com/beettlle/pi-smart-router/issues/146)).** The contrib export schema was bumped **v1 → v2** (`TELEMETRY_CONTRIB_VERSION = 2` in `src/cli/smart-router-cli.ts`). In v1, `session_id_hash` was an **unsalted SHA-256** of the raw session id; in v2 it is an **HMAC-SHA256 keyed with an install-local pepper** (`hashSessionIdForTelemetryExport` in `src/infra/telemetry.ts`). The pepper is generated once per install at `.pi-smart-router/.dataset-key` (mode 0600) and is **never** included in export payloads — ingest strips pepper fields before aggregation. Consequences for operators comparing exports:
796
808
 
797
809
  - Hashes are **stable per install** (same session → same hash within one install/cwd) but **not correlatable across installs** — two machines routing the same prompt produce different hashes.
@@ -979,6 +991,28 @@ Set the encoder in operator config:
979
991
 
980
992
  MiniLM remains the default fallback when `encoder` is omitted. Both encoders produce 384-dim vectors compatible with the SP-115 learned projection head.
981
993
 
994
+ **Opt-in per-prompt encoder cascade ([#173](https://github.com/beettlle/pi-smart-router/issues/173), v1.2.0 — default OFF).** Instead of a process-wide single encoder, an opt-in gate routes prompts whose estimated token count reaches a threshold to the long-context encoder, avoiding MiniLM's 512-token truncation on long turns:
995
+
996
+ ```json
997
+ {
998
+ "hydra": {
999
+ "artifact_cache_path": ".pi-smart-router/models/",
1000
+ "encoder": "minilm",
1001
+ "encoder_cascade": {
1002
+ "enabled": true,
1003
+ "long_context_encoder": "granite",
1004
+ "token_threshold": 512
1005
+ }
1006
+ }
1007
+ }
1008
+ ```
1009
+
1010
+ - **Default off** — existing single-encoder installs are unaffected and pay zero added cost (the gate is pre-embedding arithmetic, ~sub-microsecond p50 in replay).
1011
+ - **Lazy memory** — the Granite ONNX session loads only on the first over-threshold prompt; short prompts keep using the same MiniLM session, so their routing decisions are identical to the MiniLM-only baseline.
1012
+ - **Degrade, never mix** — encoders embed into different vector spaces. If Granite is unavailable at request time, routing continues on MiniLM with `granite_fallback` telemetry; the cascade never compares cross-encoder vectors. Per-encoder artifacts: bootstrap Granite centroids with `npm run routing:bootstrap-centroids -- --encoder granite` (namespaced `config/routing-centroids.granite.json`); `npm run routing:verify-calibration` rejects mixed-encoder bundles, and Granite-side projection ships honest-untrained.
1013
+ - **Telemetry** — cascade-enabled decisions carry `encoder_selected`, `token_estimate`, `cascade_threshold`, and `cascade_fallback_reason` on the feature sidecar.
1014
+ - **Evidence, not a default flip** — see the cascade replay report [`docs/qa/encoder-cascade-replay-v1.2.0.md`](docs/qa/encoder-cascade-replay-v1.2.0.md). Promoting the cascade (or Granite) to a default is a future ticket gated on the #173 eval table.
1015
+
982
1016
  **Latency budget:** the HyDRA embedding stage targets ~80–120 ms per turn. Compare MiniLM vs Granite on held-out agent turn samples:
983
1017
 
984
1018
  ```bash
@@ -988,6 +1022,62 @@ npm run benchmark:encoder
988
1022
 
989
1023
  The script reports p50/p95 latency for each encoder and asserts Granite p50/p95 stay within the 120 ms budget ceiling. Requires `@huggingface/transformers` and a one-time ONNX artifact download.
990
1024
 
1025
+ #### Granite opt-in dogfood runbook ([#167](https://github.com/beettlle/pi-smart-router/issues/167))
1026
+
1027
+ Operator enablement for the Granite 97M long-context encoder on dogfood installs. **MiniLM remains the shipped default** — this runbook never flips `encoder` defaults; promotion is decided only through [#96](https://github.com/beettlle/pi-smart-router/issues/96) with dogfood evidence. The code path already shipped in [#80](https://github.com/beettlle/pi-smart-router/issues/80); do not reimplement it.
1028
+
1029
+ **1. Fetch the model.** No manual download step is required — the ONNX artifact fetches on demand:
1030
+
1031
+ | Item | Value |
1032
+ |------|-------|
1033
+ | Runtime HF ONNX id | `onnx-community/granite-embedding-97m-multilingual-r2-ONNX` (`GRANITE_ONNX_MODEL` in `src/domain/matching/embedding-provider.ts`) |
1034
+ | Upstream weights | `ibm-granite/granite-embedding-97m-multilingual-r2` (384-dim, long context) |
1035
+ | Cache dir | `hydra.artifact_cache_path` (default `.pi-smart-router/models/` — gitignored) |
1036
+ | Fetch trigger | First HyDRA embed with `encoder: granite`, **or** `npm run benchmark:encoder` (downloads on demand) |
1037
+ | Offline | One-time Hugging Face download; fully offline after cache warm (see Supply-chain below for pin modes) |
1038
+
1039
+ Expected cache layout after fetch:
1040
+
1041
+ ```text
1042
+ .pi-smart-router/models/onnx-community/granite-embedding-97m-multilingual-r2-ONNX/
1043
+ ```
1044
+
1045
+ **2. Switch config (operator).** Edit `config/operator-config.json` on the dogfood host:
1046
+
1047
+ ```json
1048
+ {
1049
+ "hydra": {
1050
+ "artifact_cache_path": ".pi-smart-router/models/",
1051
+ "encoder": "granite",
1052
+ "hydra_heads": "learned_projection"
1053
+ }
1054
+ }
1055
+ ```
1056
+
1057
+ Keep `hydra_heads: learned_projection` — Granite stays 384-dim compatible with the SP-115 projection (`config/hydra-projection-weights.json`). Do **not** enable `modernbert_k4` (still blocked on trained `config/modernbert-k4-heads.json`; tracked by #96).
1058
+
1059
+ **3. Verify resident.** After a routed request, run `/smart-router plan` or `/smart-router doctor` to confirm the Granite encoder model and cache path are loaded. Smoke-check that routing still selects tiers and that there is **no silent MiniLM fallback** — if the encoder errors or exceeds budget, the neural stage fails open by design (#119 / #148) and telemetry/`reason_code` surfaces it; a crash or silent downgrade is a bug, not expected behavior.
1060
+
1061
+ **4. Measure and archive.** Run the encoder benchmark and archive output for the issue trail:
1062
+
1063
+ ```bash
1064
+ mkdir -p .pi-smart-router/measurements/
1065
+ npm run benchmark:encoder | tee .pi-smart-router/measurements/benchmark-encoder-$(date +%Y%m%d).txt
1066
+ # optional: --fixtures path --cache .pi-smart-router/models/
1067
+ ```
1068
+
1069
+ Expectation: Granite p50/p95 within the ≤120 ms HyDRA embedding-stage budget (SP-204 go/no-go measured ~17 ms p50 for both encoders; [artifact](spine-tasks/_authoring/release-v0.11.0/encoder-gonogo-artifact.md)). `.pi-smart-router/measurements/` is gitignored — post a short summary comment on #167 with the numbers.
1070
+
1071
+ **5. Post-switch follow-ups (feed #96 — evidence only, no default flip).** Track after Granite is live on the dogfood host:
1072
+
1073
+ - **Latency regression watch** — HyDRA stage stays within ~80–120 ms on dogfood hardware vs SP-204 baselines; re-run `npm run benchmark:encoder` after fleet/catalog changes.
1074
+ - **Centroid / cluster space** — centroids were bootstrapped with MiniLM (`npm run routing:bootstrap-centroids`). Same 384-dim space, but embedding geometry differs; dry-run compare cluster match quality under Granite and document whether `config/routing-centroids.json` / the calibration bundle need refresh **before** any default flip.
1075
+ - **Projection / calibration** — SP-115 weights were trained on MiniLM embeddings. Run `npm run routing:calibration-dry-run` under Granite; if holdout ECE worsens, retrain the projection **or** keep MiniLM as the shipped default.
1076
+ - **Truncation / quality signal** — confirm long agent turns no longer hit MiniLM's 512-token wall (the primary motivation); note any residual encoder truncation surfaced in explain/telemetry.
1077
+ - **Degraded / fail-open path** — with Granite ONNX missing or slow, confirm neural failover still fails open (#119 / #148) rather than crashing the host agent.
1078
+ - **Feed #96** — after dogfood evidence, comment on #96 with *promote Granite as default?* yes/no + evidence. Only then open a separate issue/PR to flip defaults — never from dogfood alone (see `docs/qa/shadow-dogfood-protocol.md`).
1079
+ - **Do not conflate with ModernBERT** — K=4 stays blocked on trained `config/modernbert-k4-heads.json` (SP-218/219); out of scope here.
1080
+
991
1081
  #### Supply-chain: artifact pins, offline cache, and audit posture
992
1082
 
993
1083
  **Digest pinning (SP-259, [#147](https://github.com/beettlle/pi-smart-router/issues/147)).** The embedder verifies cached ONNX artifacts against SHA-256 pins before they are used. Pins live in [`config/onnx-artifact-pins.json`](config/onnx-artifact-pins.json) (`pins[modelId][cacheRelativePath] = sha256`; digests are the HuggingFace LFS oids for the default quantized artifacts). Pin mode is controlled by `SMART_ROUTER_ONNX_PIN_MODE`:
@@ -33,9 +33,11 @@ function getRouterStateDbPath(cwd) {
33
33
 
34
34
  function printUsage() {
35
35
  console.error(`Usage:
36
- pi-smart-router export telemetry-contrib [--limit N]
36
+ pi-smart-router export telemetry-contrib [--limit N] [--embeddings]
37
37
 
38
- Run from a directory with pi routing state (default: ./.pi-smart-router/state.db).`);
38
+ Run from a directory with pi routing state (default: ./.pi-smart-router/state.db).
39
+ --embeddings includes captured 384-dim encoder embeddings (opt-in; rows only carry
40
+ embeddings when SMART_ROUTER_DATASET_EMBEDDINGS=1 was set at routing time).`);
39
41
  }
40
42
 
41
43
  async function main() {
@@ -46,14 +48,14 @@ async function main() {
46
48
  const sqlite = await loadDist('dist/infrastructure/persistence/sqlite-store.js');
47
49
 
48
50
  if (cli.isExportTelemetryContribInvocation(args)) {
49
- const { limit } = cli.parseExportTelemetryContribArgs(args);
51
+ const { limit, includeEmbeddings } = cli.parseExportTelemetryContribArgs(args);
50
52
  const cwd = process.cwd();
51
53
  const { store } = sqlite.createResilientStore({
52
54
  dbPath: getRouterStateDbPath(cwd),
53
55
  models: [],
54
56
  });
55
57
 
56
- const result = await cli.exportTelemetryContrib({ store, cwd, limit });
58
+ const result = await cli.exportTelemetryContrib({ store, cwd, limit, includeEmbeddings });
57
59
  if (result.path) {
58
60
  console.log(`Exported ${result.recordCount} telemetry-contrib record(s) to ${result.path}`);
59
61
  return;
@@ -7,8 +7,8 @@
7
7
  "livecodebench": "https://livecodebench.github.io/leaderboard.html",
8
8
  "bfcl": "https://gorilla.cs.berkeley.edu/leaderboard.html"
9
9
  },
10
- "scrape_date": "2026-09-10",
11
- "catalog_freeze_date": "2026-09-10"
10
+ "scrape_date": "2026-09-13",
11
+ "catalog_freeze_date": "2026-09-13"
12
12
  },
13
13
  "aliases": {
14
14
  "anthropic/claude-opus-4": "claude-opus-4-5",
@@ -21,6 +21,13 @@
21
21
  "hydra": {
22
22
  "artifact_cache_path": ".pi-smart-router/models/",
23
23
  "encoder": "minilm",
24
+ "_encoder_documentation": "Encoder switch — minilm (shipped default) | granite (opt-in trial, #80/#167). Granite: ibm-granite/granite-embedding-97m-multilingual-r2 ONNX, 384-dim long-context drop-in; fetches on first use into artifact_cache_path. See README 'HyDRA model cache → Granite opt-in dogfood runbook (#167)' for fetch/switch/measure/follow-ups. Do NOT flip the shipped default here — promotion is decided only via #96.",
25
+ "encoder_cascade": {
26
+ "enabled": false,
27
+ "long_context_encoder": "granite",
28
+ "token_threshold": 512
29
+ },
30
+ "_encoder_cascade_documentation": "SP-291 (#173) — opt-in per-prompt encoder cascade. Default OFF: every prompt uses hydra.encoder (MiniLM) and existing installs are unaffected. When enabled, prompts whose estimated token count reaches token_threshold are embedded with long_context_encoder (Granite 97M, long-context) instead of MiniLM (512-token window), avoiding truncation on long prompts. The gate is pure pre-embedding arithmetic (~zero overhead) and reuses the turn-envelope token estimator (estimated_input_tokens ?? prompt_text.length). Hard constraint: encoders embed into different vector spaces — never mix per-encoder artifacts (routing centroids, learned projection); per-encoder artifact sets and the cascading embedder land with SP-292.",
24
31
  "hydra_heads": "learned_projection",
25
32
  "_onnx_artifact_pins_documentation": "SP-259 (#147) — ONNX artifact supply-chain pinning. Digests live in config/onnx-artifact-pins.json (SHA-256 per cached artifact). Enable with env SMART_ROUTER_ONNX_PIN_MODE=verify (verify configured pins; unpinned models still download) or =enforce (CI/prod: pins required for the loaded model — missing pin file, missing pins, missing cached artifact, or any digest mismatch fails closed). Default off preserves first-run unpinned dogfood downloads. Override pin file path with SMART_ROUTER_ONNX_PIN_FILE. See README 'HyDRA model cache → Supply-chain' for offline cache warm and audit posture.",
26
33
  "_hydra_heads_documentation": {
@@ -13,18 +13,25 @@
13
13
  "routing_latency_norm",
14
14
  "economical_tier"
15
15
  ],
16
- "intercept": 0,
16
+ "intercept": -0.5222003136513049,
17
17
  "coefficients": [
18
- 0,
19
- 0,
20
- 0,
21
- 0,
22
- 0,
23
- 0,
24
- 0,
25
- 0,
26
- 0,
27
- 0
18
+ -0.2956545771757284,
19
+ 7.784841874649057,
20
+ 3.5433520874221505,
21
+ 4.8383314206088155,
22
+ 0.38566078028982864,
23
+ 7.26315059735526,
24
+ -4.790294638202887,
25
+ 0,
26
+ -1.6830999767157413,
27
+ -1.3270549117026749
28
28
  ],
29
- "trained_sample_count": 0
29
+ "trained_sample_count": 243,
30
+ "provenance": {
31
+ "source": "verifier_grade_train_2026-09-12",
32
+ "note": "v1.1.0 verifier-grade ship (2026-09-12): train on 147 human_feedback contrib + 96 llm_judge live packs (SP-282 campaign + panel adjudication). All isotonic hard gates PASS (ece_cal=0.0645, ece_raw=0.1142). See spine-tasks/_authoring/release-v1.1.0/hard-gate-pass-2026-09-12.md. Closes #168 ship precondition.",
33
+ "trained_at": "2026-09-12",
34
+ "hard_gates_passed": true,
35
+ "trained_sample_count": 243
36
+ }
30
37
  }
@@ -1175,8 +1175,8 @@
1175
1175
  },
1176
1176
  "triage_thresholds": {
1177
1177
  "version": 1,
1178
- "cyclomatic_threshold": 15,
1179
- "trained_sample_count": 0
1178
+ "cyclomatic_threshold": 5,
1179
+ "trained_sample_count": 57
1180
1180
  },
1181
1181
  "p_success_weights": {
1182
1182
  "version": 1,
@@ -1193,35 +1193,163 @@
1193
1193
  "routing_latency_norm",
1194
1194
  "economical_tier"
1195
1195
  ],
1196
- "intercept": 0,
1196
+ "intercept": -0.5222003136513049,
1197
1197
  "coefficients": [
1198
- 0,
1199
- 0,
1200
- 0,
1201
- 0,
1202
- 0,
1203
- 0,
1204
- 0,
1205
- 0,
1206
- 0,
1207
- 0
1198
+ -0.2956545771757284,
1199
+ 7.784841874649057,
1200
+ 3.5433520874221505,
1201
+ 4.8383314206088155,
1202
+ 0.38566078028982864,
1203
+ 7.26315059735526,
1204
+ -4.790294638202887,
1205
+ 0,
1206
+ -1.6830999767157413,
1207
+ -1.3270549117026749
1208
1208
  ],
1209
- "trained_sample_count": 0
1209
+ "trained_sample_count": 243
1210
1210
  },
1211
1211
  "isotonic_calibrator": {
1212
1212
  "version": 1,
1213
1213
  "min_training_samples": 30,
1214
1214
  "x_knots": [
1215
1215
  0,
1216
+ 0.4931564466862494,
1217
+ 0.49429102457530366,
1218
+ 0.4949099107727396,
1219
+ 0.4973855925869458,
1220
+ 0.5533629738338711,
1221
+ 0.5534649562964067,
1222
+ 0.5580493828890756,
1223
+ 0.5757897994300778,
1224
+ 0.5808212286637637,
1225
+ 0.6050649262180884,
1226
+ 0.8246138426581088,
1227
+ 0.825447772179378,
1228
+ 0.9687181641984473,
1229
+ 0.970743664095184,
1230
+ 0.9723800899966535,
1231
+ 0.9829037979927008,
1232
+ 0.9899734425367577,
1233
+ 0.9900120052118988,
1234
+ 0.9900504210635419,
1235
+ 0.9900886906392573,
1236
+ 0.9901268144846618,
1237
+ 0.9901647931434236,
1238
+ 0.9902403170659917,
1239
+ 0.9902778634074517,
1240
+ 0.990932091551219,
1241
+ 0.9923781893493379,
1242
+ 0.9931065498824946,
1243
+ 0.9944060334221272,
1244
+ 0.9957179579486877,
1245
+ 0.9988841927456904,
1246
+ 0.9988885226774248,
1247
+ 0.9988928358253212,
1248
+ 0.9989014120290056,
1249
+ 0.9989056752138725,
1250
+ 0.9989099218730609,
1251
+ 0.9989183658698353,
1252
+ 0.9989225633345237,
1253
+ 0.9989267445277392,
1254
+ 0.9992992382276508,
1255
+ 0.9997094887571614,
1256
+ 0.999710617024703,
1257
+ 0.9997117409116203,
1258
+ 0.9997128604349119,
1259
+ 0.9997139756115103,
1260
+ 0.9999977750941793,
1261
+ 0.9999977756250997,
1262
+ 0.9999977788459334,
1263
+ 0.9999977797978844,
1264
+ 0.9999977799872095,
1265
+ 0.9999977835743561,
1266
+ 0.9999977870462493,
1267
+ 0.9999977879784937,
1268
+ 0.9999977895779203,
1269
+ 0.9999977897416097,
1270
+ 0.9999977928146635,
1271
+ 0.9999977939558492,
1272
+ 0.9999977953947778,
1273
+ 0.9999978029719648,
1274
+ 0.9999978049176854,
1275
+ 0.9999978073738002,
1276
+ 0.999997809369306,
1277
+ 0.9999978098952513,
1278
+ 0.9999978806395697,
1279
+ 0.9999978879421653,
1216
1280
  1
1217
1281
  ],
1218
1282
  "y_knots": [
1219
1283
  0,
1284
+ 0,
1285
+ 0.25,
1286
+ 0.25,
1287
+ 0.5,
1288
+ 0.5,
1289
+ 0.625,
1290
+ 0.625,
1291
+ 0.6666666666666666,
1292
+ 0.6666666666666666,
1293
+ 0.6976744186046512,
1294
+ 0.6976744186046512,
1295
+ 0.95,
1296
+ 0.95,
1297
+ 1,
1298
+ 1,
1299
+ 1,
1300
+ 1,
1301
+ 1,
1302
+ 1,
1303
+ 1,
1304
+ 1,
1305
+ 1,
1306
+ 1,
1307
+ 1,
1308
+ 1,
1309
+ 1,
1310
+ 1,
1311
+ 1,
1312
+ 1,
1313
+ 1,
1314
+ 1,
1315
+ 1,
1316
+ 1,
1317
+ 1,
1318
+ 1,
1319
+ 1,
1320
+ 1,
1321
+ 1,
1322
+ 1,
1323
+ 1,
1324
+ 1,
1325
+ 1,
1326
+ 1,
1327
+ 1,
1328
+ 1,
1329
+ 1,
1330
+ 1,
1331
+ 1,
1332
+ 1,
1333
+ 1,
1334
+ 1,
1335
+ 1,
1336
+ 1,
1337
+ 1,
1338
+ 1,
1339
+ 1,
1340
+ 1,
1341
+ 1,
1342
+ 1,
1343
+ 1,
1344
+ 1,
1345
+ 1,
1346
+ 1,
1347
+ 1,
1220
1348
  1
1221
1349
  ],
1222
- "trained_sample_count": 0,
1223
- "holdout_ece_raw": null,
1224
- "holdout_ece_calibrated": null
1350
+ "trained_sample_count": 243,
1351
+ "holdout_ece_raw": 0.11415615868987086,
1352
+ "holdout_ece_calibrated": 0.06453883879133059
1225
1353
  },
1226
1354
  "routing_centroids": {
1227
1355
  "version": 1,
@@ -2794,13 +2922,18 @@
2794
2922
  ]
2795
2923
  },
2796
2924
  "provenance": {
2797
- "source": "neutralized_for_v1_honesty",
2798
- "labeled_samples_p_success": 0,
2799
- "labeled_samples_triage": 0,
2800
- "labeled_samples_isotonic": 0,
2925
+ "source": "verifier_grade_train_2026-09-12",
2926
+ "labeled_samples_p_success": 243,
2927
+ "labeled_samples_triage": 57,
2928
+ "labeled_samples_isotonic": 243,
2801
2929
  "hydra_trained_sample_count": 0,
2802
- "note": "v1.0 honesty neutralize (2026-09-10): hybrid Sept P(success)/July isotonic rejected for ship. Scripted dogfood-gather labels are not #110 evidence. Serve-time p_success/isotonic/triage floors unmet so structural tier hints resume. PAV same-x pooling + null-label skip landed in code. Verifier-graded recalibration tracked post-1.0 (spine SP-280+). TwinRouterBench corpus over-routing ~0.87 is harness adapter artifact (#112), not live pipeline.",
2803
- "trained_at": "2026-09-10",
2804
- "neutralize_reason": "hybrid_harms_sept_distribution_and_scripted_labels"
2930
+ "note": "v1.1.0 verifier-grade ship (2026-09-12): train on 147 human_feedback contrib + 96 llm_judge live packs (SP-282 campaign + panel adjudication). All isotonic hard gates PASS (ece_cal=0.0645, ece_raw=0.1142). See spine-tasks/_authoring/release-v1.1.0/hard-gate-pass-2026-09-12.md. Closes #168 ship precondition.",
2931
+ "trained_at": "2026-09-12",
2932
+ "gate_report": "data/calibration/verifier-grade-gate-report.json",
2933
+ "hard_gates_passed": true,
2934
+ "holdout_ece_raw": 0.11415615868987086,
2935
+ "holdout_ece_calibrated": 0.06453883879133059,
2936
+ "contrib_ship_eligible": 147,
2937
+ "pack_rows": 96
2805
2938
  }
2806
2939
  }
@@ -1,7 +1,8 @@
1
1
  [
2
2
  {
3
- "version": 1,
4
- "timestamp": "2026-07-07T12:00:00.000Z",
3
+ "version": 2,
4
+ "timestamp": "2026-09-11T12:00:00.000Z",
5
+ "row_id": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
5
6
  "session_id_hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
6
7
  "turn_type": "main_loop",
7
8
  "stage": "hydra_match",
@@ -11,6 +12,8 @@
11
12
  "routing_latency_ms": 12,
12
13
  "estimated_cost_usd": 0.001,
13
14
  "estimated_input_tokens": 60,
15
+ "prompt_length_chars": 240,
16
+ "message_count": 1,
14
17
  "has_tool_context": false,
15
18
  "compaction_flag": false,
16
19
  "triage_verdict": "ambiguous",
@@ -24,6 +24,13 @@ export declare const TELEMETRY_CONTRIB_STRIP_KEYS: readonly ["request_id", "sess
24
24
  export declare const TELEMETRY_CONTRIB_REJECT_KEYS: readonly ["session_id", "prompt_text", "messages", "prompt", "prompt_fingerprint"];
25
25
  /** Mirrors SP-116 tainted key pattern for ingest-safe export. */
26
26
  export declare const TELEMETRY_CONTRIB_TAINTED_KEY_PATTERN: RegExp;
27
+ /**
28
+ * Privacy-safe count-only fields that the tainted pattern would otherwise
29
+ * reject by name (SP-285, #170). These carry integer counts — never content —
30
+ * so they are explicitly allowed: `prompt_length_chars` feeds the P(success)
31
+ * prompt_length feature; `message_count` feeds envelope-shape features.
32
+ */
33
+ export declare const TELEMETRY_CONTRIB_TAINTED_KEY_ALLOWLIST: readonly ["prompt_length_chars", "message_count"];
27
34
  export declare class TelemetryContribValidationError extends Error {
28
35
  readonly name = "TelemetryContribValidationError";
29
36
  constructor(message: string, options?: ErrorOptions);
@@ -47,6 +54,8 @@ export interface ExportTelemetryContribOptions {
47
54
  readonly limit?: number;
48
55
  readonly cwd?: string;
49
56
  readonly writeFile?: boolean;
57
+ /** Include captured 384-dim embeddings in contrib rows (SP-285, #170). */
58
+ readonly includeEmbeddings?: boolean;
50
59
  }
51
60
  export interface ExportTelemetryContribResult {
52
61
  readonly path: string | null;
@@ -57,6 +66,8 @@ export interface TelemetryContribExportContext {
57
66
  readonly store: StorePort;
58
67
  readonly cwd: string;
59
68
  readonly limit: number;
69
+ /** Include captured embeddings (SP-285, #170 --embeddings flag). */
70
+ readonly includeEmbeddings?: boolean | undefined;
60
71
  }
61
72
  /** Returns true when args invoke `/smart-router unpin`. */
62
73
  export declare function isUnpinInvocation(args: string): boolean;
@@ -64,6 +75,7 @@ export declare function isUnpinInvocation(args: string): boolean;
64
75
  export declare function isExportTelemetryContribInvocation(args: string): boolean;
65
76
  export declare function parseExportTelemetryContribArgs(args: string): {
66
77
  readonly limit: number;
78
+ readonly includeEmbeddings: boolean;
67
79
  };
68
80
  /**
69
81
  * Clear the current session pin via SessionPinner.breakPin().
@@ -71,17 +83,23 @@ export declare function parseExportTelemetryContribArgs(args: string): {
71
83
  */
72
84
  export declare function executeUnpinCommand(ctx: UnpinCommandContext): UnpinCommandResult;
73
85
  /** Map a dataset row plus linked outcomes to a privacy-safe telemetry contrib record. */
74
- export declare function toTelemetryContribRecord(record: RoutingDatasetRecord, outcomes?: readonly RoutingOutcomeRecord[], pepper?: Buffer): Record<string, unknown>;
86
+ export declare function toTelemetryContribRecord(record: RoutingDatasetRecord, outcomes?: readonly RoutingOutcomeRecord[], pepper?: Buffer, options?: {
87
+ readonly includeEmbeddings?: boolean | undefined;
88
+ }): Record<string, unknown>;
75
89
  export declare function formatTelemetryContribJson(records: readonly Record<string, unknown>[]): string;
76
90
  export declare function formatTelemetryContribJsonl(records: readonly Record<string, unknown>[]): string;
77
91
  export declare function formatTelemetryContribExportTimestamp(date?: Date): string;
78
92
  export declare function getTelemetryContribExportPath(cwd: string, timestamp: string): string;
79
93
  /** Defense-in-depth validation using SP-116 contrib safety checks. */
80
94
  export declare function validateTelemetryContribRecord(record: unknown, context?: string): Record<string, unknown>;
81
- export declare function buildTelemetryContribRecords(datasetRecords: readonly RoutingDatasetRecord[], outcomeRecords?: readonly RoutingOutcomeRecord[], pepper?: Buffer): Record<string, unknown>[];
95
+ export declare function buildTelemetryContribRecords(datasetRecords: readonly RoutingDatasetRecord[], outcomeRecords?: readonly RoutingOutcomeRecord[], pepper?: Buffer, options?: {
96
+ readonly includeEmbeddings?: boolean | undefined;
97
+ }): Record<string, unknown>[];
82
98
  export declare function exportTelemetryContrib(ctx: TelemetryContribExportContext, options?: Pick<ExportTelemetryContribOptions, 'writeFile'>): Promise<ExportTelemetryContribResult>;
83
99
  /** Join dataset export shape with outcome labels, then strip to contrib-safe fields. */
84
- export declare function datasetExportRowToTelemetryContrib(exportRecord: Record<string, unknown>, outcomes: readonly RoutingOutcomeRecord[], pepper?: Buffer): Record<string, unknown>;
100
+ export declare function datasetExportRowToTelemetryContrib(exportRecord: Record<string, unknown>, outcomes: readonly RoutingOutcomeRecord[], pepper?: Buffer, options?: {
101
+ readonly includeEmbeddings?: boolean | undefined;
102
+ }): Record<string, unknown>;
85
103
  export declare const smartRouterCliSubcommands: {
86
104
  readonly unpin: typeof executeUnpinCommand;
87
105
  readonly exportTelemetryContrib: typeof exportTelemetryContrib;