pi-smart-router 0.1.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 (310) hide show
  1. package/.pi/extensions/smart-router/command-formatters.ts +167 -0
  2. package/.pi/extensions/smart-router/commands.ts +242 -0
  3. package/.pi/extensions/smart-router/dataset-export.ts +142 -0
  4. package/.pi/extensions/smart-router/delegate-stream.ts +105 -0
  5. package/.pi/extensions/smart-router/delegation-runtime.ts +355 -0
  6. package/.pi/extensions/smart-router/extension-setup.ts +115 -0
  7. package/.pi/extensions/smart-router/fleet-bootstrap.ts +244 -0
  8. package/.pi/extensions/smart-router/index.ts +115 -0
  9. package/.pi/extensions/smart-router/package.json +3 -0
  10. package/.pi/extensions/smart-router/pi-model-scope.ts +55 -0
  11. package/.pi/extensions/smart-router/pricing-lifecycle.ts +43 -0
  12. package/.pi/extensions/smart-router/route-and-delegate.ts +562 -0
  13. package/.pi/extensions/smart-router/routing-context.ts +171 -0
  14. package/.pi/extensions/smart-router/routing-outcomes.ts +44 -0
  15. package/.pi/extensions/smart-router/session-lifecycle.ts +140 -0
  16. package/.pi/extensions/smart-router/stream-delegation.ts +75 -0
  17. package/.pi/extensions/smart-router/types.ts +90 -0
  18. package/.pi/extensions/smart-router/utils.ts +58 -0
  19. package/README.md +593 -0
  20. package/bin/pi-smart-router.mjs +79 -0
  21. package/config/.gitkeep +0 -0
  22. package/config/hydra-projection-weights.json.example +1169 -0
  23. package/config/models.yaml.example +58 -0
  24. package/config/p-success-weights.json.example +19 -0
  25. package/config/routing-calibration.json.example +1605 -0
  26. package/config/routing-centroids.json.example +1570 -0
  27. package/config/routing-clusters.yaml.example +41 -0
  28. package/data/contrib/example.json +25 -0
  29. package/dist/api/explain/router-explain.d.ts +47 -0
  30. package/dist/api/explain/router-explain.d.ts.map +1 -0
  31. package/dist/api/explain/router-explain.js +101 -0
  32. package/dist/api/explain/router-explain.js.map +1 -0
  33. package/dist/api/middleware/pi-router-middleware.d.ts +86 -0
  34. package/dist/api/middleware/pi-router-middleware.d.ts.map +1 -0
  35. package/dist/api/middleware/pi-router-middleware.js +83 -0
  36. package/dist/api/middleware/pi-router-middleware.js.map +1 -0
  37. package/dist/cli/smart-router-cli.d.ts +85 -0
  38. package/dist/cli/smart-router-cli.d.ts.map +1 -0
  39. package/dist/cli/smart-router-cli.js +293 -0
  40. package/dist/cli/smart-router-cli.js.map +1 -0
  41. package/dist/config/defaults.d.ts +7 -0
  42. package/dist/config/defaults.d.ts.map +1 -0
  43. package/dist/config/defaults.js +33 -0
  44. package/dist/config/defaults.js.map +1 -0
  45. package/dist/config/models-loader.d.ts +22 -0
  46. package/dist/config/models-loader.d.ts.map +1 -0
  47. package/dist/config/models-loader.js +51 -0
  48. package/dist/config/models-loader.js.map +1 -0
  49. package/dist/config/pi-model-mapper.d.ts +40 -0
  50. package/dist/config/pi-model-mapper.d.ts.map +1 -0
  51. package/dist/config/pi-model-mapper.js +216 -0
  52. package/dist/config/pi-model-mapper.js.map +1 -0
  53. package/dist/config/routing-clusters-loader.d.ts +35 -0
  54. package/dist/config/routing-clusters-loader.d.ts.map +1 -0
  55. package/dist/config/routing-clusters-loader.js +100 -0
  56. package/dist/config/routing-clusters-loader.js.map +1 -0
  57. package/dist/domain/delegation/delegation-context.d.ts +23 -0
  58. package/dist/domain/delegation/delegation-context.d.ts.map +1 -0
  59. package/dist/domain/delegation/delegation-context.js +73 -0
  60. package/dist/domain/delegation/delegation-context.js.map +1 -0
  61. package/dist/domain/delegation/execution-ledger.d.ts +19 -0
  62. package/dist/domain/delegation/execution-ledger.d.ts.map +1 -0
  63. package/dist/domain/delegation/execution-ledger.js +23 -0
  64. package/dist/domain/delegation/execution-ledger.js.map +1 -0
  65. package/dist/domain/delegation/output-headroom.d.ts +39 -0
  66. package/dist/domain/delegation/output-headroom.d.ts.map +1 -0
  67. package/dist/domain/delegation/output-headroom.js +78 -0
  68. package/dist/domain/delegation/output-headroom.js.map +1 -0
  69. package/dist/domain/matching/cluster-matcher.d.ts +69 -0
  70. package/dist/domain/matching/cluster-matcher.d.ts.map +1 -0
  71. package/dist/domain/matching/cluster-matcher.js +294 -0
  72. package/dist/domain/matching/cluster-matcher.js.map +1 -0
  73. package/dist/domain/matching/embedding-provider.d.ts +21 -0
  74. package/dist/domain/matching/embedding-provider.d.ts.map +1 -0
  75. package/dist/domain/matching/embedding-provider.js +42 -0
  76. package/dist/domain/matching/embedding-provider.js.map +1 -0
  77. package/dist/domain/matching/hydra-input.d.ts +18 -0
  78. package/dist/domain/matching/hydra-input.d.ts.map +1 -0
  79. package/dist/domain/matching/hydra-input.js +40 -0
  80. package/dist/domain/matching/hydra-input.js.map +1 -0
  81. package/dist/domain/matching/hydra-matcher.d.ts +103 -0
  82. package/dist/domain/matching/hydra-matcher.d.ts.map +1 -0
  83. package/dist/domain/matching/hydra-matcher.js +275 -0
  84. package/dist/domain/matching/hydra-matcher.js.map +1 -0
  85. package/dist/domain/pinning/cache-economics.d.ts +53 -0
  86. package/dist/domain/pinning/cache-economics.d.ts.map +1 -0
  87. package/dist/domain/pinning/cache-economics.js +72 -0
  88. package/dist/domain/pinning/cache-economics.js.map +1 -0
  89. package/dist/domain/pinning/loop-escalation.d.ts +41 -0
  90. package/dist/domain/pinning/loop-escalation.d.ts.map +1 -0
  91. package/dist/domain/pinning/loop-escalation.js +137 -0
  92. package/dist/domain/pinning/loop-escalation.js.map +1 -0
  93. package/dist/domain/pinning/session-pinner.d.ts +81 -0
  94. package/dist/domain/pinning/session-pinner.d.ts.map +1 -0
  95. package/dist/domain/pinning/session-pinner.js +237 -0
  96. package/dist/domain/pinning/session-pinner.js.map +1 -0
  97. package/dist/domain/pinning/sub-route-policy.d.ts +41 -0
  98. package/dist/domain/pinning/sub-route-policy.d.ts.map +1 -0
  99. package/dist/domain/pinning/sub-route-policy.js +72 -0
  100. package/dist/domain/pinning/sub-route-policy.js.map +1 -0
  101. package/dist/domain/pipeline/router-pipeline.d.ts +180 -0
  102. package/dist/domain/pipeline/router-pipeline.d.ts.map +1 -0
  103. package/dist/domain/pipeline/router-pipeline.js +807 -0
  104. package/dist/domain/pipeline/router-pipeline.js.map +1 -0
  105. package/dist/domain/pipeline/safe-default.d.ts +25 -0
  106. package/dist/domain/pipeline/safe-default.d.ts.map +1 -0
  107. package/dist/domain/pipeline/safe-default.js +41 -0
  108. package/dist/domain/pipeline/safe-default.js.map +1 -0
  109. package/dist/domain/refactor-guardrails.d.ts +48 -0
  110. package/dist/domain/refactor-guardrails.d.ts.map +1 -0
  111. package/dist/domain/refactor-guardrails.js +129 -0
  112. package/dist/domain/refactor-guardrails.js.map +1 -0
  113. package/dist/domain/routing/context-fit.d.ts +53 -0
  114. package/dist/domain/routing/context-fit.d.ts.map +1 -0
  115. package/dist/domain/routing/context-fit.js +142 -0
  116. package/dist/domain/routing/context-fit.js.map +1 -0
  117. package/dist/domain/routing/expected-cost.d.ts +63 -0
  118. package/dist/domain/routing/expected-cost.d.ts.map +1 -0
  119. package/dist/domain/routing/expected-cost.js +177 -0
  120. package/dist/domain/routing/expected-cost.js.map +1 -0
  121. package/dist/domain/routing/p-success-classifier.d.ts +130 -0
  122. package/dist/domain/routing/p-success-classifier.d.ts.map +1 -0
  123. package/dist/domain/routing/p-success-classifier.js +350 -0
  124. package/dist/domain/routing/p-success-classifier.js.map +1 -0
  125. package/dist/domain/routing/tier-features.d.ts +70 -0
  126. package/dist/domain/routing/tier-features.d.ts.map +1 -0
  127. package/dist/domain/routing/tier-features.js +174 -0
  128. package/dist/domain/routing/tier-features.js.map +1 -0
  129. package/dist/domain/routing/tool-history-guard.d.ts +32 -0
  130. package/dist/domain/routing/tool-history-guard.d.ts.map +1 -0
  131. package/dist/domain/routing/tool-history-guard.js +110 -0
  132. package/dist/domain/routing/tool-history-guard.js.map +1 -0
  133. package/dist/domain/scoring/multi-objective.d.ts +48 -0
  134. package/dist/domain/scoring/multi-objective.d.ts.map +1 -0
  135. package/dist/domain/scoring/multi-objective.js +116 -0
  136. package/dist/domain/scoring/multi-objective.js.map +1 -0
  137. package/dist/domain/triage/triage-engine.d.ts +43 -0
  138. package/dist/domain/triage/triage-engine.d.ts.map +1 -0
  139. package/dist/domain/triage/triage-engine.js +317 -0
  140. package/dist/domain/triage/triage-engine.js.map +1 -0
  141. package/dist/domain/triage/turn-envelope.d.ts +17 -0
  142. package/dist/domain/triage/turn-envelope.d.ts.map +1 -0
  143. package/dist/domain/triage/turn-envelope.js +81 -0
  144. package/dist/domain/triage/turn-envelope.js.map +1 -0
  145. package/dist/domain/types/entities.d.ts +303 -0
  146. package/dist/domain/types/entities.d.ts.map +1 -0
  147. package/dist/domain/types/entities.js +6 -0
  148. package/dist/domain/types/entities.js.map +1 -0
  149. package/dist/domain/types/index.d.ts +4 -0
  150. package/dist/domain/types/index.d.ts.map +1 -0
  151. package/dist/domain/types/index.js +2 -0
  152. package/dist/domain/types/index.js.map +1 -0
  153. package/dist/domain/types/schemas.d.ts +346 -0
  154. package/dist/domain/types/schemas.d.ts.map +1 -0
  155. package/dist/domain/types/schemas.js +241 -0
  156. package/dist/domain/types/schemas.js.map +1 -0
  157. package/dist/domain/types/store-port.d.ts +44 -0
  158. package/dist/domain/types/store-port.d.ts.map +1 -0
  159. package/dist/domain/types/store-port.js +6 -0
  160. package/dist/domain/types/store-port.js.map +1 -0
  161. package/dist/index.d.ts +39 -0
  162. package/dist/index.d.ts.map +1 -0
  163. package/dist/index.js +36 -0
  164. package/dist/index.js.map +1 -0
  165. package/dist/infra/gemini-provider.d.ts +28 -0
  166. package/dist/infra/gemini-provider.d.ts.map +1 -0
  167. package/dist/infra/gemini-provider.js +112 -0
  168. package/dist/infra/gemini-provider.js.map +1 -0
  169. package/dist/infra/telemetry.d.ts +51 -0
  170. package/dist/infra/telemetry.d.ts.map +1 -0
  171. package/dist/infra/telemetry.js +100 -0
  172. package/dist/infra/telemetry.js.map +1 -0
  173. package/dist/infrastructure/delegation/provider-error.d.ts +44 -0
  174. package/dist/infrastructure/delegation/provider-error.d.ts.map +1 -0
  175. package/dist/infrastructure/delegation/provider-error.js +179 -0
  176. package/dist/infrastructure/delegation/provider-error.js.map +1 -0
  177. package/dist/infrastructure/gateway/circuit-breaker.d.ts +68 -0
  178. package/dist/infrastructure/gateway/circuit-breaker.d.ts.map +1 -0
  179. package/dist/infrastructure/gateway/circuit-breaker.js +150 -0
  180. package/dist/infrastructure/gateway/circuit-breaker.js.map +1 -0
  181. package/dist/infrastructure/gateway/gateway-dispatch.d.ts +138 -0
  182. package/dist/infrastructure/gateway/gateway-dispatch.d.ts.map +1 -0
  183. package/dist/infrastructure/gateway/gateway-dispatch.js +308 -0
  184. package/dist/infrastructure/gateway/gateway-dispatch.js.map +1 -0
  185. package/dist/infrastructure/hardware/hardware-probe.d.ts +32 -0
  186. package/dist/infrastructure/hardware/hardware-probe.d.ts.map +1 -0
  187. package/dist/infrastructure/hardware/hardware-probe.js +199 -0
  188. package/dist/infrastructure/hardware/hardware-probe.js.map +1 -0
  189. package/dist/infrastructure/local/local-zero-tier.d.ts +47 -0
  190. package/dist/infrastructure/local/local-zero-tier.d.ts.map +1 -0
  191. package/dist/infrastructure/local/local-zero-tier.js +92 -0
  192. package/dist/infrastructure/local/local-zero-tier.js.map +1 -0
  193. package/dist/infrastructure/persistence/memory-store.d.ts +31 -0
  194. package/dist/infrastructure/persistence/memory-store.d.ts.map +1 -0
  195. package/dist/infrastructure/persistence/memory-store.js +87 -0
  196. package/dist/infrastructure/persistence/memory-store.js.map +1 -0
  197. package/dist/infrastructure/persistence/sqlite-store.d.ts +81 -0
  198. package/dist/infrastructure/persistence/sqlite-store.d.ts.map +1 -0
  199. package/dist/infrastructure/persistence/sqlite-store.js +658 -0
  200. package/dist/infrastructure/persistence/sqlite-store.js.map +1 -0
  201. package/dist/infrastructure/pricing/litellm-fetch.d.ts +46 -0
  202. package/dist/infrastructure/pricing/litellm-fetch.d.ts.map +1 -0
  203. package/dist/infrastructure/pricing/litellm-fetch.js +145 -0
  204. package/dist/infrastructure/pricing/litellm-fetch.js.map +1 -0
  205. package/dist/infrastructure/pricing/price-broker.d.ts +56 -0
  206. package/dist/infrastructure/pricing/price-broker.d.ts.map +1 -0
  207. package/dist/infrastructure/pricing/price-broker.js +133 -0
  208. package/dist/infrastructure/pricing/price-broker.js.map +1 -0
  209. package/dist/infrastructure/pricing/pricing-monitor.d.ts +27 -0
  210. package/dist/infrastructure/pricing/pricing-monitor.d.ts.map +1 -0
  211. package/dist/infrastructure/pricing/pricing-monitor.js +46 -0
  212. package/dist/infrastructure/pricing/pricing-monitor.js.map +1 -0
  213. package/dist/infrastructure/telemetry/dataset-limits.d.ts +13 -0
  214. package/dist/infrastructure/telemetry/dataset-limits.d.ts.map +1 -0
  215. package/dist/infrastructure/telemetry/dataset-limits.js +28 -0
  216. package/dist/infrastructure/telemetry/dataset-limits.js.map +1 -0
  217. package/dist/infrastructure/telemetry/dataset-recorder.d.ts +48 -0
  218. package/dist/infrastructure/telemetry/dataset-recorder.d.ts.map +1 -0
  219. package/dist/infrastructure/telemetry/dataset-recorder.js +154 -0
  220. package/dist/infrastructure/telemetry/dataset-recorder.js.map +1 -0
  221. package/dist/infrastructure/telemetry/outcome-limits.d.ts +12 -0
  222. package/dist/infrastructure/telemetry/outcome-limits.d.ts.map +1 -0
  223. package/dist/infrastructure/telemetry/outcome-limits.js +28 -0
  224. package/dist/infrastructure/telemetry/outcome-limits.js.map +1 -0
  225. package/dist/infrastructure/telemetry/outcome-recorder.d.ts +29 -0
  226. package/dist/infrastructure/telemetry/outcome-recorder.d.ts.map +1 -0
  227. package/dist/infrastructure/telemetry/outcome-recorder.js +49 -0
  228. package/dist/infrastructure/telemetry/outcome-recorder.js.map +1 -0
  229. package/dist/infrastructure/telemetry/routing-telemetry.d.ts +115 -0
  230. package/dist/infrastructure/telemetry/routing-telemetry.d.ts.map +1 -0
  231. package/dist/infrastructure/telemetry/routing-telemetry.js +529 -0
  232. package/dist/infrastructure/telemetry/routing-telemetry.js.map +1 -0
  233. package/dist/infrastructure/telemetry/telemetry-limits.d.ts +16 -0
  234. package/dist/infrastructure/telemetry/telemetry-limits.d.ts.map +1 -0
  235. package/dist/infrastructure/telemetry/telemetry-limits.js +35 -0
  236. package/dist/infrastructure/telemetry/telemetry-limits.js.map +1 -0
  237. package/package.json +110 -0
  238. package/skills/router-backlog-orchestrator/SKILL.md +183 -0
  239. package/skills/router-backlog-orchestrator/references/github-router-issue-template.md +78 -0
  240. package/skills/router-backlog-orchestrator/references/packet-from-issue.md +99 -0
  241. package/skills/router-backlog-orchestrator/references/prioritization-rubric.md +65 -0
  242. package/skills/router-backlog-orchestrator/scripts/collect-backlog.sh +44 -0
  243. package/specs/001-build-smart-router/contracts/telemetry-contrib.schema.json +151 -0
  244. package/src/api/explain/.gitkeep +0 -0
  245. package/src/api/explain/router-explain.ts +146 -0
  246. package/src/api/middleware/.gitkeep +0 -0
  247. package/src/api/middleware/pi-router-middleware.ts +169 -0
  248. package/src/cli/smart-router-cli.ts +436 -0
  249. package/src/config/.gitkeep +0 -0
  250. package/src/config/defaults.ts +35 -0
  251. package/src/config/models-loader.ts +68 -0
  252. package/src/config/pi-model-mapper.ts +285 -0
  253. package/src/config/routing-clusters-loader.ts +148 -0
  254. package/src/domain/delegation/delegation-context.ts +111 -0
  255. package/src/domain/delegation/execution-ledger.ts +34 -0
  256. package/src/domain/delegation/output-headroom.ts +131 -0
  257. package/src/domain/matching/.gitkeep +0 -0
  258. package/src/domain/matching/cluster-matcher.ts +474 -0
  259. package/src/domain/matching/embedding-provider.ts +79 -0
  260. package/src/domain/matching/hydra-input.ts +53 -0
  261. package/src/domain/matching/hydra-matcher.ts +454 -0
  262. package/src/domain/pinning/.gitkeep +0 -0
  263. package/src/domain/pinning/cache-economics.ts +128 -0
  264. package/src/domain/pinning/loop-escalation.ts +181 -0
  265. package/src/domain/pinning/session-pinner.ts +388 -0
  266. package/src/domain/pinning/sub-route-policy.ts +119 -0
  267. package/src/domain/pipeline/.gitkeep +0 -0
  268. package/src/domain/pipeline/router-pipeline.ts +1139 -0
  269. package/src/domain/pipeline/safe-default.ts +70 -0
  270. package/src/domain/refactor-guardrails.ts +202 -0
  271. package/src/domain/routing/context-fit.ts +221 -0
  272. package/src/domain/routing/expected-cost.ts +320 -0
  273. package/src/domain/routing/p-success-classifier.ts +511 -0
  274. package/src/domain/routing/tier-features.ts +290 -0
  275. package/src/domain/routing/tool-history-guard.ts +159 -0
  276. package/src/domain/scoring/.gitkeep +0 -0
  277. package/src/domain/scoring/multi-objective.ts +175 -0
  278. package/src/domain/triage/.gitkeep +0 -0
  279. package/src/domain/triage/triage-engine.ts +373 -0
  280. package/src/domain/triage/turn-envelope.ts +104 -0
  281. package/src/domain/types/.gitkeep +0 -0
  282. package/src/domain/types/entities.ts +384 -0
  283. package/src/domain/types/index.ts +41 -0
  284. package/src/domain/types/schemas.ts +289 -0
  285. package/src/domain/types/store-port.ts +59 -0
  286. package/src/index.ts +103 -0
  287. package/src/infra/gemini-provider.ts +142 -0
  288. package/src/infra/telemetry.ts +169 -0
  289. package/src/infrastructure/delegation/provider-error.ts +251 -0
  290. package/src/infrastructure/gateway/.gitkeep +0 -0
  291. package/src/infrastructure/gateway/circuit-breaker.ts +196 -0
  292. package/src/infrastructure/gateway/gateway-dispatch.ts +444 -0
  293. package/src/infrastructure/hardware/.gitkeep +0 -0
  294. package/src/infrastructure/hardware/hardware-probe.ts +276 -0
  295. package/src/infrastructure/local/.gitkeep +0 -0
  296. package/src/infrastructure/local/local-zero-tier.ts +151 -0
  297. package/src/infrastructure/persistence/.gitkeep +0 -0
  298. package/src/infrastructure/persistence/memory-store.ts +122 -0
  299. package/src/infrastructure/persistence/sqlite-store.ts +900 -0
  300. package/src/infrastructure/pricing/.gitkeep +0 -0
  301. package/src/infrastructure/pricing/litellm-fetch.ts +215 -0
  302. package/src/infrastructure/pricing/price-broker.ts +183 -0
  303. package/src/infrastructure/pricing/pricing-monitor.ts +65 -0
  304. package/src/infrastructure/telemetry/.gitkeep +0 -0
  305. package/src/infrastructure/telemetry/dataset-limits.ts +39 -0
  306. package/src/infrastructure/telemetry/dataset-recorder.ts +215 -0
  307. package/src/infrastructure/telemetry/outcome-limits.ts +42 -0
  308. package/src/infrastructure/telemetry/outcome-recorder.ts +118 -0
  309. package/src/infrastructure/telemetry/routing-telemetry.ts +824 -0
  310. package/src/infrastructure/telemetry/telemetry-limits.ts +50 -0
@@ -0,0 +1,275 @@
1
+ /**
2
+ * HyDRA embedding matcher — T048, FR-005.
3
+ *
4
+ * Embeds prompt text via ONNX (Xenova/all-MiniLM-L6-v2 384-dim),
5
+ * projects to requirement dimensions [reasoning, code_gen, tool_use],
6
+ * and scores fleet candidates with a shortfall gate.
7
+ *
8
+ * Shortfall gate (data-model §Validation): candidate excluded when
9
+ * any capability dimension shortfall > 0 (quality parity).
10
+ *
11
+ * Budget: 80–120 ms (configurable, default 100 ms).
12
+ */
13
+ import { existsSync, readFileSync } from 'node:fs';
14
+ import { resolve } from 'node:path';
15
+ import { z } from 'zod';
16
+ import { DEFAULT_OPERATOR_CONFIG } from '../../config/defaults.js';
17
+ import { buildHydraInput } from './hydra-input.js';
18
+ import { createOnnxTextEmbedder, EMBEDDING_DIM, } from './embedding-provider.js';
19
+ import { scoreMultiObjective, } from '../scoring/multi-objective.js';
20
+ const DEFAULT_BUDGET_MS = 100;
21
+ const MIN_BUDGET_MS = 80;
22
+ const MAX_BUDGET_MS = 120;
23
+ export const DEFAULT_HYDRA_PROJECTION_WEIGHTS_PATH = resolve('config', 'hydra-projection-weights.json');
24
+ export const HYDRA_PROJECTION_OUTPUT_DIM = 3;
25
+ export const HydraProjectionWeightsSchema = z.object({
26
+ version: z.literal(1),
27
+ embedding_dim: z.literal(EMBEDDING_DIM),
28
+ weights: z
29
+ .array(z.array(z.number().finite()).length(EMBEDDING_DIM))
30
+ .length(HYDRA_PROJECTION_OUTPUT_DIM),
31
+ bias: z.array(z.number().finite()).length(HYDRA_PROJECTION_OUTPUT_DIM),
32
+ });
33
+ export class HydraProjectionWeightsLoaderError extends Error {
34
+ name = 'HydraProjectionWeightsLoaderError';
35
+ constructor(message, options) {
36
+ super(message, options);
37
+ }
38
+ }
39
+ function formatZodIssues(error) {
40
+ return error.issues.map((issue) => `${issue.path.join('.')}: ${issue.message}`).join('\n');
41
+ }
42
+ export function parseHydraProjectionWeightsJson(raw) {
43
+ let parsed;
44
+ try {
45
+ parsed = JSON.parse(raw);
46
+ }
47
+ catch (err) {
48
+ const message = err instanceof Error ? err.message : String(err);
49
+ throw new HydraProjectionWeightsLoaderError(`Failed to parse JSON: ${message}`, {
50
+ cause: err,
51
+ });
52
+ }
53
+ const result = HydraProjectionWeightsSchema.safeParse(parsed);
54
+ if (!result.success) {
55
+ throw new HydraProjectionWeightsLoaderError(`Invalid HyDRA projection weights artifact:\n${formatZodIssues(result.error)}`, { cause: result.error });
56
+ }
57
+ return result.data;
58
+ }
59
+ /**
60
+ * Load HyDRA projection weights from disk. Returns null when the artifact is
61
+ * missing; throws only when the file exists but is invalid.
62
+ */
63
+ export function loadHydraProjectionWeights(options) {
64
+ const filePath = options?.filePath ?? DEFAULT_HYDRA_PROJECTION_WEIGHTS_PATH;
65
+ if (!existsSync(filePath)) {
66
+ return null;
67
+ }
68
+ let raw;
69
+ try {
70
+ raw = readFileSync(filePath, 'utf8');
71
+ }
72
+ catch (err) {
73
+ const message = err instanceof Error ? err.message : String(err);
74
+ throw new HydraProjectionWeightsLoaderError(`Failed to read weights file: ${message}`, {
75
+ cause: err,
76
+ });
77
+ }
78
+ return parseHydraProjectionWeightsJson(raw);
79
+ }
80
+ /** Resolve projection weights — missing artifacts fall back to placeholder projection. */
81
+ export function resolveHydraProjectionWeights(options) {
82
+ try {
83
+ return loadHydraProjectionWeights(options);
84
+ }
85
+ catch (err) {
86
+ console.warn('HyDRA projection weights artifact invalid; using placeholder projection', {
87
+ error: err instanceof Error ? err.message : String(err),
88
+ });
89
+ return null;
90
+ }
91
+ }
92
+ // ─── Scoring helpers ─────────────────────────────────────────────────────────
93
+ function dimensionShortfall(requirement, capability) {
94
+ return Math.max(0, requirement - capability);
95
+ }
96
+ function computeShortfall(requirement, capabilities) {
97
+ const reasoning = dimensionShortfall(requirement.reasoning, capabilities.reasoning);
98
+ const codeGen = dimensionShortfall(requirement.code_gen, capabilities.code_gen);
99
+ const toolUse = dimensionShortfall(requirement.tool_use, capabilities.tool_use);
100
+ return {
101
+ perDimension: { reasoning, code_gen: codeGen, tool_use: toolUse },
102
+ max: Math.max(reasoning, codeGen, toolUse),
103
+ total: reasoning + codeGen + toolUse,
104
+ };
105
+ }
106
+ function cosineSimilarity(a, b) {
107
+ const dot = a.reasoning * b.reasoning + a.code_gen * b.code_gen + a.tool_use * b.tool_use;
108
+ const magA = Math.sqrt(a.reasoning ** 2 + a.code_gen ** 2 + a.tool_use ** 2);
109
+ const magB = Math.sqrt(b.reasoning ** 2 + b.code_gen ** 2 + b.tool_use ** 2);
110
+ if (magA === 0 || magB === 0)
111
+ return 0;
112
+ return dot / (magA * magB);
113
+ }
114
+ // ─── Projection helpers (384-dim → 3 requirement dimensions) ─────────────────
115
+ function sigmoid(x) {
116
+ return 1 / (1 + Math.exp(-x));
117
+ }
118
+ function meanSlice(embedding, start, end) {
119
+ let sum = 0;
120
+ for (let i = start; i < end; i++) {
121
+ sum += embedding[i] ?? 0;
122
+ }
123
+ return sum / (end - start);
124
+ }
125
+ function projectToRequirementsPlaceholder(embedding) {
126
+ const third = Math.floor(EMBEDDING_DIM / 3);
127
+ return {
128
+ reasoning: sigmoid(meanSlice(embedding, 0, third)),
129
+ code_gen: sigmoid(meanSlice(embedding, third, 2 * third)),
130
+ tool_use: sigmoid(meanSlice(embedding, 2 * third, EMBEDDING_DIM)),
131
+ };
132
+ }
133
+ function linearProjection(embedding, weights) {
134
+ const logits = [];
135
+ for (let dim = 0; dim < HYDRA_PROJECTION_OUTPUT_DIM; dim++) {
136
+ const row = weights.weights[dim];
137
+ let sum = weights.bias[dim] ?? 0;
138
+ for (let i = 0; i < EMBEDDING_DIM; i++) {
139
+ sum += (embedding[i] ?? 0) * (row[i] ?? 0);
140
+ }
141
+ logits.push(sum);
142
+ }
143
+ return [logits[0], logits[1], logits[2]];
144
+ }
145
+ function projectToRequirementsLearned(embedding, weights) {
146
+ const [reasoningLogit, codeGenLogit, toolUseLogit] = linearProjection(embedding, weights);
147
+ return {
148
+ reasoning: sigmoid(reasoningLogit),
149
+ code_gen: sigmoid(codeGenLogit),
150
+ tool_use: sigmoid(toolUseLogit),
151
+ };
152
+ }
153
+ /**
154
+ * Project a 384-dim embedding to 3 requirement dimensions.
155
+ *
156
+ * Uses learned linear projection (sigmoid(embedding @ W + b)) when weights are
157
+ * provided; otherwise falls back to deterministic mean-pooled-thirds placeholder.
158
+ */
159
+ export function projectToRequirements(embedding, weights) {
160
+ if (embedding.length !== EMBEDDING_DIM) {
161
+ throw new Error(`Embedding shape mismatch: expected ${EMBEDDING_DIM}, got ${embedding.length}`);
162
+ }
163
+ if (weights) {
164
+ return projectToRequirementsLearned(embedding, weights);
165
+ }
166
+ return projectToRequirementsPlaceholder(embedding);
167
+ }
168
+ // ─── HyDRA embedding adapter ─────────────────────────────────────────────────
169
+ /**
170
+ * Adapts a shared TextEmbedder for HyDRA requirement extraction.
171
+ * dispose() delegates to the underlying embedder for shared lifecycle.
172
+ */
173
+ export function wrapHydraEmbeddingProvider(embedder, projectionWeights) {
174
+ return {
175
+ async extractRequirements(text) {
176
+ const embedding = await embedder.embed(text);
177
+ return projectToRequirements(embedding, projectionWeights);
178
+ },
179
+ async dispose() {
180
+ await embedder.dispose();
181
+ },
182
+ };
183
+ }
184
+ // ─── HydraMatcher ────────────────────────────────────────────────────────────
185
+ export class HydraMatcher {
186
+ provider;
187
+ budgetMs;
188
+ frugality;
189
+ projectionWeights;
190
+ constructor(provider, config) {
191
+ const budget = config.budgetMs ?? DEFAULT_BUDGET_MS;
192
+ if (budget < MIN_BUDGET_MS || budget > MAX_BUDGET_MS) {
193
+ throw new Error(`HyDRA budget must be ${MIN_BUDGET_MS}–${MAX_BUDGET_MS}ms, got ${budget}ms`);
194
+ }
195
+ this.provider = provider;
196
+ this.budgetMs = budget;
197
+ this.frugality = config.frugality ?? DEFAULT_OPERATOR_CONFIG.frugality;
198
+ this.projectionWeights = resolveHydraProjectionWeights(config.projectionWeightsPath ? { filePath: config.projectionWeightsPath } : undefined);
199
+ }
200
+ /** Whether learned projection weights were loaded at init. */
201
+ usesLearnedProjection() {
202
+ return this.projectionWeights !== null;
203
+ }
204
+ async match(request, fleet) {
205
+ const start = performance.now();
206
+ const requirements = await this.provider.extractRequirements(buildHydraInput(request));
207
+ this.validateRequirements(requirements);
208
+ const healthyFleet = fleet.filter((m) => m.healthy !== false);
209
+ const candidates = [];
210
+ for (const model of healthyFleet) {
211
+ const elapsed = performance.now() - start;
212
+ if (elapsed > this.budgetMs) {
213
+ break;
214
+ }
215
+ const shortfall = computeShortfall(requirements, model.capabilities);
216
+ const hasShortfall = shortfall.max > 0;
217
+ const score = hasShortfall
218
+ ? 0
219
+ : cosineSimilarity(requirements, model.capabilities);
220
+ candidates.push({
221
+ model_id: model.id,
222
+ score,
223
+ shortfall: shortfall.max,
224
+ rejected_reason: hasShortfall ? 'shortfall_gate' : null,
225
+ });
226
+ }
227
+ const shortfallByModel = new Map(candidates.map((c) => [c.model_id, c.shortfall]));
228
+ const multiObjective = scoreMultiObjective(candidates, healthyFleet, this.frugality);
229
+ const toCandidateScore = (scored) => ({
230
+ model_id: scored.model_id,
231
+ score: scored.composite_score,
232
+ shortfall: shortfallByModel.get(scored.model_id) ?? 0,
233
+ rejected_reason: scored.rejected_reason,
234
+ });
235
+ const rankedCandidates = multiObjective.candidates.map(toCandidateScore);
236
+ const selected = multiObjective.selected
237
+ ? toCandidateScore(multiObjective.selected)
238
+ : null;
239
+ const elapsedMs = performance.now() - start;
240
+ return {
241
+ selected,
242
+ candidates: rankedCandidates,
243
+ requirements,
244
+ elapsedMs,
245
+ budgetExceeded: elapsedMs > this.budgetMs,
246
+ };
247
+ }
248
+ async dispose() {
249
+ await this.provider.dispose();
250
+ }
251
+ validateRequirements(req) {
252
+ const dims = [
253
+ 'reasoning',
254
+ 'code_gen',
255
+ 'tool_use',
256
+ ];
257
+ for (const key of dims) {
258
+ const value = req[key];
259
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
260
+ throw new Error(`Invalid requirement dimension '${key}': ${String(value)}`);
261
+ }
262
+ }
263
+ }
264
+ }
265
+ /**
266
+ * Creates an EmbeddingProvider backed by the shared ONNX text embedder.
267
+ * For multi-matcher setups, create one TextEmbedder via createOnnxTextEmbedder
268
+ * and pass wrapHydraEmbeddingProvider(embedder) to HyDRA while sharing embed().
269
+ */
270
+ export async function createOnnxEmbeddingProvider(artifactCachePath, options) {
271
+ const embedder = await createOnnxTextEmbedder(artifactCachePath);
272
+ const projectionWeights = resolveHydraProjectionWeights(options?.projectionWeightsPath ? { filePath: options.projectionWeightsPath } : undefined);
273
+ return wrapHydraEmbeddingProvider(embedder, projectionWeights);
274
+ }
275
+ //# sourceMappingURL=hydra-matcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hydra-matcher.js","sourceRoot":"","sources":["../../../src/domain/matching/hydra-matcher.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Cache-warmup economics — FR-008 rule #4, SC-006.
3
+ *
4
+ * Decides whether switching providers justifies the cache-warmup penalty.
5
+ * A provider switch invalidates upstream KV-cache, so the router must
6
+ * compare the warmup cost against projected savings from the candidate
7
+ * model before breaking a pin.
8
+ *
9
+ * When warmup cost exceeds projected savings the pin holds (User Story 4,
10
+ * scenario 4).
11
+ */
12
+ import type { ModelProfile, SessionPin } from '../types/index.js';
13
+ export interface CacheEconomicsConfig {
14
+ /**
15
+ * Multiplier applied to estimated_input_tokens to approximate
16
+ * the token cost of re-prompting a cold provider cache.
17
+ * Default: 1.15 (15 % warmup overhead).
18
+ */
19
+ readonly warmupCostMultiplier?: number;
20
+ /**
21
+ * Minimum per-1M-token savings required to justify a provider switch.
22
+ * Prevents switching for negligible price differences.
23
+ * Default: 0.10 (USD).
24
+ */
25
+ readonly minSavingsThreshold?: number;
26
+ /**
27
+ * Number of remaining turns to amortize savings over.
28
+ * Higher values favor switching; lower values favor keeping the pin.
29
+ * Default: 5.
30
+ */
31
+ readonly projectedRemainingTurns?: number;
32
+ }
33
+ export interface CacheEconomicsResult {
34
+ readonly shouldSwitch: boolean;
35
+ readonly warmupCostUsd: number;
36
+ readonly projectedSavingsUsd: number;
37
+ readonly reason: string;
38
+ }
39
+ /**
40
+ * Evaluate whether switching from the currently-pinned model to a candidate
41
+ * on a different provider is justified by projected cost savings.
42
+ *
43
+ * Returns `shouldSwitch: false` when:
44
+ * - Candidate is on the same provider (no cache penalty).
45
+ * Caller should handle same-provider switches without this check.
46
+ * - Warmup cost exceeds projected savings.
47
+ * - Savings fall below the minimum threshold.
48
+ *
49
+ * Returns `shouldSwitch: true` only when projected savings meaningfully
50
+ * exceed the cache-warmup penalty.
51
+ */
52
+ export declare function evaluateCacheEconomics(pin: SessionPin, pinnedModel: ModelProfile, candidate: ModelProfile, estimatedInputTokens: number, config?: CacheEconomicsConfig): CacheEconomicsResult;
53
+ //# sourceMappingURL=cache-economics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache-economics.d.ts","sourceRoot":"","sources":["../../../src/domain/pinning/cache-economics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAIlE,MAAM,WAAW,oBAAoB;IACnC;;;;OAIG;IACH,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAEvC;;;;OAIG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAEtC;;;;OAIG;IACH,QAAQ,CAAC,uBAAuB,CAAC,EAAE,MAAM,CAAC;CAC3C;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAUD;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,UAAU,EACf,WAAW,EAAE,YAAY,EACzB,SAAS,EAAE,YAAY,EACvB,oBAAoB,EAAE,MAAM,EAC5B,MAAM,CAAC,EAAE,oBAAoB,GAC5B,oBAAoB,CAsDtB"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Cache-warmup economics — FR-008 rule #4, SC-006.
3
+ *
4
+ * Decides whether switching providers justifies the cache-warmup penalty.
5
+ * A provider switch invalidates upstream KV-cache, so the router must
6
+ * compare the warmup cost against projected savings from the candidate
7
+ * model before breaking a pin.
8
+ *
9
+ * When warmup cost exceeds projected savings the pin holds (User Story 4,
10
+ * scenario 4).
11
+ */
12
+ // ─── Defaults ─────────────────────────────────────────────────────────────────
13
+ const DEFAULT_WARMUP_COST_MULTIPLIER = 1.15;
14
+ const DEFAULT_MIN_SAVINGS_THRESHOLD = 0.10;
15
+ const DEFAULT_PROJECTED_REMAINING_TURNS = 5;
16
+ // ─── Evaluation ───────────────────────────────────────────────────────────────
17
+ /**
18
+ * Evaluate whether switching from the currently-pinned model to a candidate
19
+ * on a different provider is justified by projected cost savings.
20
+ *
21
+ * Returns `shouldSwitch: false` when:
22
+ * - Candidate is on the same provider (no cache penalty).
23
+ * Caller should handle same-provider switches without this check.
24
+ * - Warmup cost exceeds projected savings.
25
+ * - Savings fall below the minimum threshold.
26
+ *
27
+ * Returns `shouldSwitch: true` only when projected savings meaningfully
28
+ * exceed the cache-warmup penalty.
29
+ */
30
+ export function evaluateCacheEconomics(pin, pinnedModel, candidate, estimatedInputTokens, config) {
31
+ const warmupMultiplier = config?.warmupCostMultiplier ?? DEFAULT_WARMUP_COST_MULTIPLIER;
32
+ const minSavings = config?.minSavingsThreshold ?? DEFAULT_MIN_SAVINGS_THRESHOLD;
33
+ const remainingTurns = config?.projectedRemainingTurns ?? DEFAULT_PROJECTED_REMAINING_TURNS;
34
+ if (pinnedModel.provider === candidate.provider) {
35
+ return {
36
+ shouldSwitch: false,
37
+ warmupCostUsd: 0,
38
+ projectedSavingsUsd: 0,
39
+ reason: 'same_provider_no_cache_penalty',
40
+ };
41
+ }
42
+ const tokenCountM = estimatedInputTokens / 1_000_000;
43
+ const warmupTokens = estimatedInputTokens * (warmupMultiplier - 1);
44
+ const warmupCostUsd = (warmupTokens / 1_000_000) * candidate.pricing.fallback_cost_per_1m;
45
+ const perTurnSavings = (pinnedModel.pricing.fallback_cost_per_1m -
46
+ candidate.pricing.fallback_cost_per_1m) *
47
+ tokenCountM;
48
+ const projectedSavingsUsd = perTurnSavings * remainingTurns;
49
+ if (projectedSavingsUsd < minSavings) {
50
+ return {
51
+ shouldSwitch: false,
52
+ warmupCostUsd,
53
+ projectedSavingsUsd,
54
+ reason: 'savings_below_threshold',
55
+ };
56
+ }
57
+ if (warmupCostUsd >= projectedSavingsUsd) {
58
+ return {
59
+ shouldSwitch: false,
60
+ warmupCostUsd,
61
+ projectedSavingsUsd,
62
+ reason: 'warmup_exceeds_savings',
63
+ };
64
+ }
65
+ return {
66
+ shouldSwitch: true,
67
+ warmupCostUsd,
68
+ projectedSavingsUsd,
69
+ reason: 'switch_justified',
70
+ };
71
+ }
72
+ //# sourceMappingURL=cache-economics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache-economics.js","sourceRoot":"","sources":["../../../src/domain/pinning/cache-economics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAoCH,iFAAiF;AAEjF,MAAM,8BAA8B,GAAG,IAAI,CAAC;AAC5C,MAAM,6BAA6B,GAAG,IAAI,CAAC;AAC3C,MAAM,iCAAiC,GAAG,CAAC,CAAC;AAE5C,iFAAiF;AAEjF;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,sBAAsB,CACpC,GAAe,EACf,WAAyB,EACzB,SAAuB,EACvB,oBAA4B,EAC5B,MAA6B;IAE7B,MAAM,gBAAgB,GACpB,MAAM,EAAE,oBAAoB,IAAI,8BAA8B,CAAC;IACjE,MAAM,UAAU,GACd,MAAM,EAAE,mBAAmB,IAAI,6BAA6B,CAAC;IAC/D,MAAM,cAAc,GAClB,MAAM,EAAE,uBAAuB,IAAI,iCAAiC,CAAC;IAEvE,IAAI,WAAW,CAAC,QAAQ,KAAK,SAAS,CAAC,QAAQ,EAAE,CAAC;QAChD,OAAO;YACL,YAAY,EAAE,KAAK;YACnB,aAAa,EAAE,CAAC;YAChB,mBAAmB,EAAE,CAAC;YACtB,MAAM,EAAE,gCAAgC;SACzC,CAAC;IACJ,CAAC;IAED,MAAM,WAAW,GAAG,oBAAoB,GAAG,SAAS,CAAC;IAErD,MAAM,YAAY,GAAG,oBAAoB,GAAG,CAAC,gBAAgB,GAAG,CAAC,CAAC,CAAC;IACnE,MAAM,aAAa,GACjB,CAAC,YAAY,GAAG,SAAS,CAAC,GAAG,SAAS,CAAC,OAAO,CAAC,oBAAoB,CAAC;IAEtE,MAAM,cAAc,GAClB,CAAC,WAAW,CAAC,OAAO,CAAC,oBAAoB;QACvC,SAAS,CAAC,OAAO,CAAC,oBAAoB,CAAC;QACzC,WAAW,CAAC;IAEd,MAAM,mBAAmB,GAAG,cAAc,GAAG,cAAc,CAAC;IAE5D,IAAI,mBAAmB,GAAG,UAAU,EAAE,CAAC;QACrC,OAAO;YACL,YAAY,EAAE,KAAK;YACnB,aAAa;YACb,mBAAmB;YACnB,MAAM,EAAE,yBAAyB;SAClC,CAAC;IACJ,CAAC;IAED,IAAI,aAAa,IAAI,mBAAmB,EAAE,CAAC;QACzC,OAAO;YACL,YAAY,EAAE,KAAK;YACnB,aAAa;YACb,mBAAmB;YACnB,MAAM,EAAE,wBAAwB;SACjC,CAAC;IACJ,CAAC;IAED,OAAO;QACL,YAAY,EAAE,IAAI;QAClB,aAAa;QACb,mBAAmB;QACnB,MAAM,EAAE,kBAAkB;KAC3B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Loop escalation — FR-014, FR-008 rule #3.
3
+ *
4
+ * Detects bounded repeated identical tool failures and signals
5
+ * session pin escalation to a frontier-capable tier.
6
+ *
7
+ * Escalation fires once per session; no tier oscillation (FR-008).
8
+ * Threshold defaults to 3 identical tool failures (operator configurable).
9
+ *
10
+ * Design: pure evaluation function — caller (pipeline stage) is
11
+ * responsible for applying pin state updates via SessionPinner.
12
+ */
13
+ import type { ModelProfile, RoutingRequest, SessionPin } from '../types/index.js';
14
+ export interface LoopEscalationConfig {
15
+ readonly threshold: number;
16
+ }
17
+ export interface LoopEscalationResult {
18
+ readonly shouldEscalate: boolean;
19
+ readonly updatedPin: SessionPin | null;
20
+ readonly escalationTarget: ModelProfile | null;
21
+ readonly reason: string;
22
+ }
23
+ /**
24
+ * Extract a tool-failure signature from the request's messages.
25
+ * Returns null when the request does not carry a tool failure.
26
+ *
27
+ * Only inspects tool_result turns — other turn types cannot
28
+ * carry observational failure signals (FR-014: no post-generation judging).
29
+ */
30
+ export declare function extractToolFailureSignature(request: RoutingRequest): string | null;
31
+ /**
32
+ * Evaluate whether the session should escalate to a higher tier.
33
+ *
34
+ * Pure function — does not mutate pin state. Returns `updatedPin`
35
+ * when the caller should persist new failure-tracking state.
36
+ *
37
+ * Caller applies updates via `SessionPinner.loadPin()`, and on
38
+ * escalation via `breakPin()` + `recordPin()`.
39
+ */
40
+ export declare function evaluateLoopEscalation(pin: SessionPin | null, request: RoutingRequest, fleet: readonly ModelProfile[], config: LoopEscalationConfig): LoopEscalationResult;
41
+ //# sourceMappingURL=loop-escalation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loop-escalation.d.ts","sourceRoot":"","sources":["../../../src/domain/pinning/loop-escalation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAIlF,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,gBAAgB,EAAE,YAAY,GAAG,IAAI,CAAC;IAC/C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAgBD;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,cAAc,GAAG,MAAM,GAAG,IAAI,CAclF;AAmBD;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,UAAU,GAAG,IAAI,EACtB,OAAO,EAAE,cAAc,EACvB,KAAK,EAAE,SAAS,YAAY,EAAE,EAC9B,MAAM,EAAE,oBAAoB,GAC3B,oBAAoB,CAkEtB"}
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Loop escalation — FR-014, FR-008 rule #3.
3
+ *
4
+ * Detects bounded repeated identical tool failures and signals
5
+ * session pin escalation to a frontier-capable tier.
6
+ *
7
+ * Escalation fires once per session; no tier oscillation (FR-008).
8
+ * Threshold defaults to 3 identical tool failures (operator configurable).
9
+ *
10
+ * Design: pure evaluation function — caller (pipeline stage) is
11
+ * responsible for applying pin state updates via SessionPinner.
12
+ */
13
+ // ─── Failure signature extraction ─────────────────────────────────────────────
14
+ const FAILURE_PATTERNS = [
15
+ 'error',
16
+ 'fail',
17
+ 'exception',
18
+ 'timed out',
19
+ 'timeout',
20
+ 'econnrefused',
21
+ 'enotfound',
22
+ 'econnreset',
23
+ 'epipe',
24
+ ];
25
+ /**
26
+ * Extract a tool-failure signature from the request's messages.
27
+ * Returns null when the request does not carry a tool failure.
28
+ *
29
+ * Only inspects tool_result turns — other turn types cannot
30
+ * carry observational failure signals (FR-014: no post-generation judging).
31
+ */
32
+ export function extractToolFailureSignature(request) {
33
+ const msgs = request.messages;
34
+ if (!msgs || msgs.length === 0)
35
+ return null;
36
+ for (let i = msgs.length - 1; i >= 0; i--) {
37
+ const msg = msgs[i];
38
+ if (msg.role === 'tool') {
39
+ if (looksLikeFailure(msg.content)) {
40
+ return computeSignature(msg.content);
41
+ }
42
+ return null;
43
+ }
44
+ }
45
+ return null;
46
+ }
47
+ function looksLikeFailure(content) {
48
+ const lower = content.toLowerCase();
49
+ return FAILURE_PATTERNS.some((p) => lower.includes(p));
50
+ }
51
+ /** Deterministic djb2 hash of normalised error content. */
52
+ function computeSignature(content) {
53
+ const normalized = content.trim().slice(0, 256).toLowerCase();
54
+ let h = 5381;
55
+ for (let i = 0; i < normalized.length; i++) {
56
+ h = ((h << 5) + h + normalized.charCodeAt(i)) | 0;
57
+ }
58
+ return `tf:${(h >>> 0).toString(16)}`;
59
+ }
60
+ // ─── Evaluation ───────────────────────────────────────────────────────────────
61
+ /**
62
+ * Evaluate whether the session should escalate to a higher tier.
63
+ *
64
+ * Pure function — does not mutate pin state. Returns `updatedPin`
65
+ * when the caller should persist new failure-tracking state.
66
+ *
67
+ * Caller applies updates via `SessionPinner.loadPin()`, and on
68
+ * escalation via `breakPin()` + `recordPin()`.
69
+ */
70
+ export function evaluateLoopEscalation(pin, request, fleet, config) {
71
+ if (!pin) {
72
+ return noEscalation('no_pin');
73
+ }
74
+ if (pin.pin_reason === 'loop_escalation') {
75
+ return noEscalation('already_escalated');
76
+ }
77
+ if (request.turn_type !== 'tool_result') {
78
+ return noEscalation('not_tool_result');
79
+ }
80
+ const signature = extractToolFailureSignature(request);
81
+ if (!signature) {
82
+ if (pin.consecutive_tool_failures > 0) {
83
+ return {
84
+ shouldEscalate: false,
85
+ updatedPin: {
86
+ ...pin,
87
+ consecutive_tool_failures: 0,
88
+ last_tool_failure_signature: null,
89
+ updated_at: new Date().toISOString(),
90
+ },
91
+ escalationTarget: null,
92
+ reason: 'success_reset',
93
+ };
94
+ }
95
+ return noEscalation('no_failure');
96
+ }
97
+ const isIdentical = pin.last_tool_failure_signature === signature;
98
+ const newCount = isIdentical ? pin.consecutive_tool_failures + 1 : 1;
99
+ const updated = {
100
+ ...pin,
101
+ consecutive_tool_failures: newCount,
102
+ last_tool_failure_signature: signature,
103
+ updated_at: new Date().toISOString(),
104
+ };
105
+ if (newCount >= config.threshold) {
106
+ const target = selectEscalationTarget(fleet, pin.pinned_model_id);
107
+ if (target) {
108
+ return {
109
+ shouldEscalate: true,
110
+ updatedPin: updated,
111
+ escalationTarget: target,
112
+ reason: 'threshold_exceeded',
113
+ };
114
+ }
115
+ return {
116
+ shouldEscalate: false,
117
+ updatedPin: updated,
118
+ escalationTarget: null,
119
+ reason: 'no_frontier_available',
120
+ };
121
+ }
122
+ return {
123
+ shouldEscalate: false,
124
+ updatedPin: updated,
125
+ escalationTarget: null,
126
+ reason: 'below_threshold',
127
+ };
128
+ }
129
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
130
+ function noEscalation(reason) {
131
+ return { shouldEscalate: false, updatedPin: null, escalationTarget: null, reason };
132
+ }
133
+ /** Select the best healthy frontier-cloud model that differs from the current pin. */
134
+ function selectEscalationTarget(fleet, currentModelId) {
135
+ return (fleet.find((m) => m.tier === 'frontier-cloud' && m.id !== currentModelId && m.healthy !== false) ?? null);
136
+ }
137
+ //# sourceMappingURL=loop-escalation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loop-escalation.js","sourceRoot":"","sources":["../../../src/domain/pinning/loop-escalation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAiBH,iFAAiF;AAEjF,MAAM,gBAAgB,GAAG;IACvB,OAAO;IACP,MAAM;IACN,WAAW;IACX,WAAW;IACX,SAAS;IACT,cAAc;IACd,WAAW;IACX,YAAY;IACZ,OAAO;CACC,CAAC;AAEX;;;;;;GAMG;AACH,MAAM,UAAU,2BAA2B,CAAC,OAAuB;IACjE,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC;IAC9B,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAE5C,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC1C,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAE,CAAC;QACrB,IAAI,GAAG,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YACxB,IAAI,gBAAgB,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;gBAClC,OAAO,gBAAgB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACvC,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,gBAAgB,CAAC,OAAe;IACvC,MAAM,KAAK,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IACpC,OAAO,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;AACzD,CAAC;AAED,2DAA2D;AAC3D,SAAS,gBAAgB,CAAC,OAAe;IACvC,MAAM,UAAU,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;IAC9D,IAAI,CAAC,GAAG,IAAI,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC3C,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACpD,CAAC;IACD,OAAO,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;AACxC,CAAC;AAED,iFAAiF;AAEjF;;;;;;;;GAQG;AACH,MAAM,UAAU,sBAAsB,CACpC,GAAsB,EACtB,OAAuB,EACvB,KAA8B,EAC9B,MAA4B;IAE5B,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO,YAAY,CAAC,QAAQ,CAAC,CAAC;IAChC,CAAC;IAED,IAAI,GAAG,CAAC,UAAU,KAAK,iBAAiB,EAAE,CAAC;QACzC,OAAO,YAAY,CAAC,mBAAmB,CAAC,CAAC;IAC3C,CAAC;IAED,IAAI,OAAO,CAAC,SAAS,KAAK,aAAa,EAAE,CAAC;QACxC,OAAO,YAAY,CAAC,iBAAiB,CAAC,CAAC;IACzC,CAAC;IAED,MAAM,SAAS,GAAG,2BAA2B,CAAC,OAAO,CAAC,CAAC;IAEvD,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,IAAI,GAAG,CAAC,yBAAyB,GAAG,CAAC,EAAE,CAAC;YACtC,OAAO;gBACL,cAAc,EAAE,KAAK;gBACrB,UAAU,EAAE;oBACV,GAAG,GAAG;oBACN,yBAAyB,EAAE,CAAC;oBAC5B,2BAA2B,EAAE,IAAI;oBACjC,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;iBACrC;gBACD,gBAAgB,EAAE,IAAI;gBACtB,MAAM,EAAE,eAAe;aACxB,CAAC;QACJ,CAAC;QACD,OAAO,YAAY,CAAC,YAAY,CAAC,CAAC;IACpC,CAAC;IAED,MAAM,WAAW,GAAG,GAAG,CAAC,2BAA2B,KAAK,SAAS,CAAC;IAClE,MAAM,QAAQ,GAAG,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,yBAAyB,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAErE,MAAM,OAAO,GAAe;QAC1B,GAAG,GAAG;QACN,yBAAyB,EAAE,QAAQ;QACnC,2BAA2B,EAAE,SAAS;QACtC,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACrC,CAAC;IAEF,IAAI,QAAQ,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;QACjC,MAAM,MAAM,GAAG,sBAAsB,CAAC,KAAK,EAAE,GAAG,CAAC,eAAe,CAAC,CAAC;QAClE,IAAI,MAAM,EAAE,CAAC;YACX,OAAO;gBACL,cAAc,EAAE,IAAI;gBACpB,UAAU,EAAE,OAAO;gBACnB,gBAAgB,EAAE,MAAM;gBACxB,MAAM,EAAE,oBAAoB;aAC7B,CAAC;QACJ,CAAC;QACD,OAAO;YACL,cAAc,EAAE,KAAK;YACrB,UAAU,EAAE,OAAO;YACnB,gBAAgB,EAAE,IAAI;YACtB,MAAM,EAAE,uBAAuB;SAChC,CAAC;IACJ,CAAC;IAED,OAAO;QACL,cAAc,EAAE,KAAK;QACrB,UAAU,EAAE,OAAO;QACnB,gBAAgB,EAAE,IAAI;QACtB,MAAM,EAAE,iBAAiB;KAC1B,CAAC;AACJ,CAAC;AAED,iFAAiF;AAEjF,SAAS,YAAY,CAAC,MAAc;IAClC,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AACrF,CAAC;AAED,sFAAsF;AACtF,SAAS,sBAAsB,CAC7B,KAA8B,EAC9B,cAAsB;IAEtB,OAAO,CACL,KAAK,CAAC,IAAI,CACR,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,gBAAgB,IAAI,CAAC,CAAC,EAAE,KAAK,cAAc,IAAI,CAAC,CAAC,OAAO,KAAK,KAAK,CACrF,IAAI,IAAI,CACV,CAAC;AACJ,CAAC"}