@prismatic-io/lux 0.0.1

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 (636) hide show
  1. package/LICENSE +21 -0
  2. package/lib/answerers/claude-code/index.d.ts +17 -0
  3. package/lib/answerers/claude-code/index.d.ts.map +1 -0
  4. package/lib/answerers/claude-code/index.js +280 -0
  5. package/lib/answerers/claude-code/index.js.map +1 -0
  6. package/lib/answerers/persona/index.d.ts +40 -0
  7. package/lib/answerers/persona/index.d.ts.map +1 -0
  8. package/lib/answerers/persona/index.js +265 -0
  9. package/lib/answerers/persona/index.js.map +1 -0
  10. package/lib/answerers/scripted/index.d.ts +8 -0
  11. package/lib/answerers/scripted/index.d.ts.map +1 -0
  12. package/lib/answerers/scripted/index.js +163 -0
  13. package/lib/answerers/scripted/index.js.map +1 -0
  14. package/lib/answerers/shared.d.ts +13 -0
  15. package/lib/answerers/shared.d.ts.map +1 -0
  16. package/lib/answerers/shared.js +17 -0
  17. package/lib/answerers/shared.js.map +1 -0
  18. package/lib/answerers/terminal/index.d.ts +9 -0
  19. package/lib/answerers/terminal/index.d.ts.map +1 -0
  20. package/lib/answerers/terminal/index.js +97 -0
  21. package/lib/answerers/terminal/index.js.map +1 -0
  22. package/lib/assertions/core/command-exits-zero.d.ts +21 -0
  23. package/lib/assertions/core/command-exits-zero.d.ts.map +1 -0
  24. package/lib/assertions/core/command-exits-zero.js +87 -0
  25. package/lib/assertions/core/command-exits-zero.js.map +1 -0
  26. package/lib/assertions/core/event-checks.d.ts +57 -0
  27. package/lib/assertions/core/event-checks.d.ts.map +1 -0
  28. package/lib/assertions/core/event-checks.js +123 -0
  29. package/lib/assertions/core/event-checks.js.map +1 -0
  30. package/lib/assertions/core/file-checks.d.ts +103 -0
  31. package/lib/assertions/core/file-checks.d.ts.map +1 -0
  32. package/lib/assertions/core/file-checks.js +288 -0
  33. package/lib/assertions/core/file-checks.js.map +1 -0
  34. package/lib/assertions/core/helpers.d.ts +66 -0
  35. package/lib/assertions/core/helpers.d.ts.map +1 -0
  36. package/lib/assertions/core/helpers.js +170 -0
  37. package/lib/assertions/core/helpers.js.map +1 -0
  38. package/lib/assertions/core/index.d.ts +15 -0
  39. package/lib/assertions/core/index.d.ts.map +1 -0
  40. package/lib/assertions/core/index.js +56 -0
  41. package/lib/assertions/core/index.js.map +1 -0
  42. package/lib/assertions/core/json-pointer-equals.d.ts +19 -0
  43. package/lib/assertions/core/json-pointer-equals.d.ts.map +1 -0
  44. package/lib/assertions/core/json-pointer-equals.js +71 -0
  45. package/lib/assertions/core/json-pointer-equals.js.map +1 -0
  46. package/lib/assertions/core/output-checks.d.ts +46 -0
  47. package/lib/assertions/core/output-checks.d.ts.map +1 -0
  48. package/lib/assertions/core/output-checks.js +121 -0
  49. package/lib/assertions/core/output-checks.js.map +1 -0
  50. package/lib/assertions/core/predicate.d.ts +14 -0
  51. package/lib/assertions/core/predicate.d.ts.map +1 -0
  52. package/lib/assertions/core/predicate.js +55 -0
  53. package/lib/assertions/core/predicate.js.map +1 -0
  54. package/lib/assertions/core/resource-checks.d.ts +71 -0
  55. package/lib/assertions/core/resource-checks.d.ts.map +1 -0
  56. package/lib/assertions/core/resource-checks.js +159 -0
  57. package/lib/assertions/core/resource-checks.js.map +1 -0
  58. package/lib/assertions/core/safe-regex.d.ts +23 -0
  59. package/lib/assertions/core/safe-regex.d.ts.map +1 -0
  60. package/lib/assertions/core/safe-regex.js +102 -0
  61. package/lib/assertions/core/safe-regex.js.map +1 -0
  62. package/lib/assertions/core/tool-called-with.d.ts +21 -0
  63. package/lib/assertions/core/tool-called-with.d.ts.map +1 -0
  64. package/lib/assertions/core/tool-called-with.js +69 -0
  65. package/lib/assertions/core/tool-called-with.js.map +1 -0
  66. package/lib/assertions/core/tool-calls.d.ts +22 -0
  67. package/lib/assertions/core/tool-calls.d.ts.map +1 -0
  68. package/lib/assertions/core/tool-calls.js +17 -0
  69. package/lib/assertions/core/tool-calls.js.map +1 -0
  70. package/lib/assertions/core/tool-invocation.d.ts +36 -0
  71. package/lib/assertions/core/tool-invocation.d.ts.map +1 -0
  72. package/lib/assertions/core/tool-invocation.js +170 -0
  73. package/lib/assertions/core/tool-invocation.js.map +1 -0
  74. package/lib/assertions/core/tool-result-checks.d.ts +59 -0
  75. package/lib/assertions/core/tool-result-checks.d.ts.map +1 -0
  76. package/lib/assertions/core/tool-result-checks.js +193 -0
  77. package/lib/assertions/core/tool-result-checks.js.map +1 -0
  78. package/lib/assertions/mcp/discovery-checks.d.ts +36 -0
  79. package/lib/assertions/mcp/discovery-checks.d.ts.map +1 -0
  80. package/lib/assertions/mcp/discovery-checks.js +122 -0
  81. package/lib/assertions/mcp/discovery-checks.js.map +1 -0
  82. package/lib/assertions/mcp/index.d.ts +10 -0
  83. package/lib/assertions/mcp/index.d.ts.map +1 -0
  84. package/lib/assertions/mcp/index.js +25 -0
  85. package/lib/assertions/mcp/index.js.map +1 -0
  86. package/lib/assertions/rubric/index.d.ts +104 -0
  87. package/lib/assertions/rubric/index.d.ts.map +1 -0
  88. package/lib/assertions/rubric/index.js +191 -0
  89. package/lib/assertions/rubric/index.js.map +1 -0
  90. package/lib/assertions/rubric/internal.d.ts +14 -0
  91. package/lib/assertions/rubric/internal.d.ts.map +1 -0
  92. package/lib/assertions/rubric/internal.js +335 -0
  93. package/lib/assertions/rubric/internal.js.map +1 -0
  94. package/lib/authoring.d.ts +3 -0
  95. package/lib/authoring.d.ts.map +1 -0
  96. package/lib/authoring.js +3 -0
  97. package/lib/authoring.js.map +1 -0
  98. package/lib/cli/bin.d.ts +3 -0
  99. package/lib/cli/bin.d.ts.map +1 -0
  100. package/lib/cli/bin.js +15 -0
  101. package/lib/cli/bin.js.map +1 -0
  102. package/lib/cli/command-runtime.d.ts +5 -0
  103. package/lib/cli/command-runtime.d.ts.map +1 -0
  104. package/lib/cli/command-runtime.js +30 -0
  105. package/lib/cli/command-runtime.js.map +1 -0
  106. package/lib/cli/init.d.ts +21 -0
  107. package/lib/cli/init.d.ts.map +1 -0
  108. package/lib/cli/init.js +340 -0
  109. package/lib/cli/init.js.map +1 -0
  110. package/lib/cli/program.d.ts +22 -0
  111. package/lib/cli/program.d.ts.map +1 -0
  112. package/lib/cli/program.js +757 -0
  113. package/lib/cli/program.js.map +1 -0
  114. package/lib/cli/render/format.d.ts +14 -0
  115. package/lib/cli/render/format.d.ts.map +1 -0
  116. package/lib/cli/render/format.js +71 -0
  117. package/lib/cli/render/format.js.map +1 -0
  118. package/lib/cli/render/reporter.d.ts +73 -0
  119. package/lib/cli/render/reporter.d.ts.map +1 -0
  120. package/lib/cli/render/reporter.js +519 -0
  121. package/lib/cli/render/reporter.js.map +1 -0
  122. package/lib/cli/render/tty.d.ts +77 -0
  123. package/lib/cli/render/tty.d.ts.map +1 -0
  124. package/lib/cli/render/tty.js +283 -0
  125. package/lib/cli/render/tty.js.map +1 -0
  126. package/lib/cli/run-options.d.ts +27 -0
  127. package/lib/cli/run-options.d.ts.map +1 -0
  128. package/lib/cli/run-options.js +58 -0
  129. package/lib/cli/run-options.js.map +1 -0
  130. package/lib/cli/skills.d.ts +38 -0
  131. package/lib/cli/skills.d.ts.map +1 -0
  132. package/lib/cli/skills.js +84 -0
  133. package/lib/cli/skills.js.map +1 -0
  134. package/lib/core/annotation.d.ts +56 -0
  135. package/lib/core/annotation.d.ts.map +1 -0
  136. package/lib/core/annotation.js +92 -0
  137. package/lib/core/annotation.js.map +1 -0
  138. package/lib/core/answerer.d.ts +68 -0
  139. package/lib/core/answerer.d.ts.map +1 -0
  140. package/lib/core/answerer.js +17 -0
  141. package/lib/core/answerer.js.map +1 -0
  142. package/lib/core/artifact-evidence.d.ts +50 -0
  143. package/lib/core/artifact-evidence.d.ts.map +1 -0
  144. package/lib/core/artifact-evidence.js +185 -0
  145. package/lib/core/artifact-evidence.js.map +1 -0
  146. package/lib/core/artifact-snapshot.d.ts +37 -0
  147. package/lib/core/artifact-snapshot.d.ts.map +1 -0
  148. package/lib/core/artifact-snapshot.js +339 -0
  149. package/lib/core/artifact-snapshot.js.map +1 -0
  150. package/lib/core/assertion.d.ts +129 -0
  151. package/lib/core/assertion.d.ts.map +1 -0
  152. package/lib/core/assertion.js +75 -0
  153. package/lib/core/assertion.js.map +1 -0
  154. package/lib/core/case.d.ts +79 -0
  155. package/lib/core/case.d.ts.map +1 -0
  156. package/lib/core/case.js +90 -0
  157. package/lib/core/case.js.map +1 -0
  158. package/lib/core/conversation-turn.d.ts +23 -0
  159. package/lib/core/conversation-turn.d.ts.map +1 -0
  160. package/lib/core/conversation-turn.js +31 -0
  161. package/lib/core/conversation-turn.js.map +1 -0
  162. package/lib/core/driver.d.ts +64 -0
  163. package/lib/core/driver.d.ts.map +1 -0
  164. package/lib/core/driver.js +18 -0
  165. package/lib/core/driver.js.map +1 -0
  166. package/lib/core/emitter.d.ts +20 -0
  167. package/lib/core/emitter.d.ts.map +1 -0
  168. package/lib/core/emitter.js +34 -0
  169. package/lib/core/emitter.js.map +1 -0
  170. package/lib/core/events.d.ts +202 -0
  171. package/lib/core/events.d.ts.map +1 -0
  172. package/lib/core/events.js +107 -0
  173. package/lib/core/events.js.map +1 -0
  174. package/lib/core/exec.d.ts +82 -0
  175. package/lib/core/exec.d.ts.map +1 -0
  176. package/lib/core/exec.js +168 -0
  177. package/lib/core/exec.js.map +1 -0
  178. package/lib/core/experiment.d.ts +285 -0
  179. package/lib/core/experiment.d.ts.map +1 -0
  180. package/lib/core/experiment.js +389 -0
  181. package/lib/core/experiment.js.map +1 -0
  182. package/lib/core/glob.d.ts +3 -0
  183. package/lib/core/glob.d.ts.map +1 -0
  184. package/lib/core/glob.js +32 -0
  185. package/lib/core/glob.js.map +1 -0
  186. package/lib/core/hash.d.ts +8 -0
  187. package/lib/core/hash.d.ts.map +1 -0
  188. package/lib/core/hash.js +214 -0
  189. package/lib/core/hash.js.map +1 -0
  190. package/lib/core/index.d.ts +29 -0
  191. package/lib/core/index.d.ts.map +1 -0
  192. package/lib/core/index.js +29 -0
  193. package/lib/core/index.js.map +1 -0
  194. package/lib/core/lifecycle-fixtures.d.ts +20 -0
  195. package/lib/core/lifecycle-fixtures.d.ts.map +1 -0
  196. package/lib/core/lifecycle-fixtures.js +109 -0
  197. package/lib/core/lifecycle-fixtures.js.map +1 -0
  198. package/lib/core/lifecycle-hooks.d.ts +52 -0
  199. package/lib/core/lifecycle-hooks.d.ts.map +1 -0
  200. package/lib/core/lifecycle-hooks.js +42 -0
  201. package/lib/core/lifecycle-hooks.js.map +1 -0
  202. package/lib/core/mcp-events.d.ts +44 -0
  203. package/lib/core/mcp-events.d.ts.map +1 -0
  204. package/lib/core/mcp-events.js +23 -0
  205. package/lib/core/mcp-events.js.map +1 -0
  206. package/lib/core/model-cli.d.ts +57 -0
  207. package/lib/core/model-cli.d.ts.map +1 -0
  208. package/lib/core/model-cli.js +237 -0
  209. package/lib/core/model-cli.js.map +1 -0
  210. package/lib/core/observer.d.ts +31 -0
  211. package/lib/core/observer.d.ts.map +1 -0
  212. package/lib/core/observer.js +25 -0
  213. package/lib/core/observer.js.map +1 -0
  214. package/lib/core/platform-process.d.ts +33 -0
  215. package/lib/core/platform-process.d.ts.map +1 -0
  216. package/lib/core/platform-process.js +214 -0
  217. package/lib/core/platform-process.js.map +1 -0
  218. package/lib/core/registry.d.ts +22 -0
  219. package/lib/core/registry.d.ts.map +1 -0
  220. package/lib/core/registry.js +45 -0
  221. package/lib/core/registry.js.map +1 -0
  222. package/lib/core/rubric.d.ts +18 -0
  223. package/lib/core/rubric.d.ts.map +1 -0
  224. package/lib/core/rubric.js +49 -0
  225. package/lib/core/rubric.js.map +1 -0
  226. package/lib/core/run.d.ts +419 -0
  227. package/lib/core/run.d.ts.map +1 -0
  228. package/lib/core/run.js +126 -0
  229. package/lib/core/run.js.map +1 -0
  230. package/lib/core/statistics.d.ts +39 -0
  231. package/lib/core/statistics.d.ts.map +1 -0
  232. package/lib/core/statistics.js +163 -0
  233. package/lib/core/statistics.js.map +1 -0
  234. package/lib/core/subject.d.ts +4 -0
  235. package/lib/core/subject.d.ts.map +1 -0
  236. package/lib/core/subject.js +46 -0
  237. package/lib/core/subject.js.map +1 -0
  238. package/lib/core/tool-events.d.ts +69 -0
  239. package/lib/core/tool-events.d.ts.map +1 -0
  240. package/lib/core/tool-events.js +242 -0
  241. package/lib/core/tool-events.js.map +1 -0
  242. package/lib/core/usage.d.ts +78 -0
  243. package/lib/core/usage.d.ts.map +1 -0
  244. package/lib/core/usage.js +104 -0
  245. package/lib/core/usage.js.map +1 -0
  246. package/lib/core/wire.d.ts +83 -0
  247. package/lib/core/wire.d.ts.map +1 -0
  248. package/lib/core/wire.js +101 -0
  249. package/lib/core/wire.js.map +1 -0
  250. package/lib/core/zod.d.ts +10 -0
  251. package/lib/core/zod.d.ts.map +1 -0
  252. package/lib/core/zod.js +18 -0
  253. package/lib/core/zod.js.map +1 -0
  254. package/lib/drivers/claude-code/index.d.ts +55 -0
  255. package/lib/drivers/claude-code/index.d.ts.map +1 -0
  256. package/lib/drivers/claude-code/index.js +779 -0
  257. package/lib/drivers/claude-code/index.js.map +1 -0
  258. package/lib/drivers/claude-code/parse-events.d.ts +46 -0
  259. package/lib/drivers/claude-code/parse-events.d.ts.map +1 -0
  260. package/lib/drivers/claude-code/parse-events.js +387 -0
  261. package/lib/drivers/claude-code/parse-events.js.map +1 -0
  262. package/lib/drivers/codex/app-events.d.ts +16 -0
  263. package/lib/drivers/codex/app-events.d.ts.map +1 -0
  264. package/lib/drivers/codex/app-events.js +184 -0
  265. package/lib/drivers/codex/app-events.js.map +1 -0
  266. package/lib/drivers/codex/config.d.ts +55 -0
  267. package/lib/drivers/codex/config.d.ts.map +1 -0
  268. package/lib/drivers/codex/config.js +125 -0
  269. package/lib/drivers/codex/config.js.map +1 -0
  270. package/lib/drivers/codex/index.d.ts +8 -0
  271. package/lib/drivers/codex/index.d.ts.map +1 -0
  272. package/lib/drivers/codex/index.js +904 -0
  273. package/lib/drivers/codex/index.js.map +1 -0
  274. package/lib/drivers/mcp/index.d.ts +52 -0
  275. package/lib/drivers/mcp/index.d.ts.map +1 -0
  276. package/lib/drivers/mcp/index.js +282 -0
  277. package/lib/drivers/mcp/index.js.map +1 -0
  278. package/lib/drivers/mcp/transport.d.ts +50 -0
  279. package/lib/drivers/mcp/transport.d.ts.map +1 -0
  280. package/lib/drivers/mcp/transport.js +190 -0
  281. package/lib/drivers/mcp/transport.js.map +1 -0
  282. package/lib/drivers/shared/artifacts.d.ts +4 -0
  283. package/lib/drivers/shared/artifacts.d.ts.map +1 -0
  284. package/lib/drivers/shared/artifacts.js +81 -0
  285. package/lib/drivers/shared/artifacts.js.map +1 -0
  286. package/lib/drivers/shared/secure-copy.d.ts +2 -0
  287. package/lib/drivers/shared/secure-copy.d.ts.map +1 -0
  288. package/lib/drivers/shared/secure-copy.js +35 -0
  289. package/lib/drivers/shared/secure-copy.js.map +1 -0
  290. package/lib/drivers/subprocess/index.d.ts +24 -0
  291. package/lib/drivers/subprocess/index.d.ts.map +1 -0
  292. package/lib/drivers/subprocess/index.js +451 -0
  293. package/lib/drivers/subprocess/index.js.map +1 -0
  294. package/lib/index.d.ts +40 -0
  295. package/lib/index.d.ts.map +1 -0
  296. package/lib/index.js +48 -0
  297. package/lib/index.js.map +1 -0
  298. package/lib/optimization/reflective.d.ts +27 -0
  299. package/lib/optimization/reflective.d.ts.map +1 -0
  300. package/lib/optimization/reflective.js +257 -0
  301. package/lib/optimization/reflective.js.map +1 -0
  302. package/lib/orchestrator/annotation-loader.d.ts +65 -0
  303. package/lib/orchestrator/annotation-loader.d.ts.map +1 -0
  304. package/lib/orchestrator/annotation-loader.js +73 -0
  305. package/lib/orchestrator/annotation-loader.js.map +1 -0
  306. package/lib/orchestrator/annotation-store.d.ts +3 -0
  307. package/lib/orchestrator/annotation-store.d.ts.map +1 -0
  308. package/lib/orchestrator/annotation-store.js +100 -0
  309. package/lib/orchestrator/annotation-store.js.map +1 -0
  310. package/lib/orchestrator/authored-dependencies.d.ts +3 -0
  311. package/lib/orchestrator/authored-dependencies.d.ts.map +1 -0
  312. package/lib/orchestrator/authored-dependencies.js +146 -0
  313. package/lib/orchestrator/authored-dependencies.js.map +1 -0
  314. package/lib/orchestrator/campaign-lifecycle.d.ts +105 -0
  315. package/lib/orchestrator/campaign-lifecycle.d.ts.map +1 -0
  316. package/lib/orchestrator/campaign-lifecycle.js +141 -0
  317. package/lib/orchestrator/campaign-lifecycle.js.map +1 -0
  318. package/lib/orchestrator/candidate-integrity.d.ts +4 -0
  319. package/lib/orchestrator/candidate-integrity.d.ts.map +1 -0
  320. package/lib/orchestrator/candidate-integrity.js +14 -0
  321. package/lib/orchestrator/candidate-integrity.js.map +1 -0
  322. package/lib/orchestrator/candidates.d.ts +13 -0
  323. package/lib/orchestrator/candidates.d.ts.map +1 -0
  324. package/lib/orchestrator/candidates.js +577 -0
  325. package/lib/orchestrator/candidates.js.map +1 -0
  326. package/lib/orchestrator/compare.d.ts +80 -0
  327. package/lib/orchestrator/compare.d.ts.map +1 -0
  328. package/lib/orchestrator/compare.js +270 -0
  329. package/lib/orchestrator/compare.js.map +1 -0
  330. package/lib/orchestrator/comparison-identity.d.ts +10 -0
  331. package/lib/orchestrator/comparison-identity.d.ts.map +1 -0
  332. package/lib/orchestrator/comparison-identity.js +39 -0
  333. package/lib/orchestrator/comparison-identity.js.map +1 -0
  334. package/lib/orchestrator/config.d.ts +121 -0
  335. package/lib/orchestrator/config.d.ts.map +1 -0
  336. package/lib/orchestrator/config.js +176 -0
  337. package/lib/orchestrator/config.js.map +1 -0
  338. package/lib/orchestrator/content-bound-json.d.ts +5 -0
  339. package/lib/orchestrator/content-bound-json.d.ts.map +1 -0
  340. package/lib/orchestrator/content-bound-json.js +59 -0
  341. package/lib/orchestrator/content-bound-json.js.map +1 -0
  342. package/lib/orchestrator/discover.d.ts +45 -0
  343. package/lib/orchestrator/discover.d.ts.map +1 -0
  344. package/lib/orchestrator/discover.js +98 -0
  345. package/lib/orchestrator/discover.js.map +1 -0
  346. package/lib/orchestrator/doctor.d.ts +141 -0
  347. package/lib/orchestrator/doctor.d.ts.map +1 -0
  348. package/lib/orchestrator/doctor.js +525 -0
  349. package/lib/orchestrator/doctor.js.map +1 -0
  350. package/lib/orchestrator/driver-session.d.ts +177 -0
  351. package/lib/orchestrator/driver-session.d.ts.map +1 -0
  352. package/lib/orchestrator/driver-session.js +396 -0
  353. package/lib/orchestrator/driver-session.js.map +1 -0
  354. package/lib/orchestrator/evaluator-identity.d.ts +7 -0
  355. package/lib/orchestrator/evaluator-identity.d.ts.map +1 -0
  356. package/lib/orchestrator/evaluator-identity.js +42 -0
  357. package/lib/orchestrator/evaluator-identity.js.map +1 -0
  358. package/lib/orchestrator/experiment-budget.d.ts +11 -0
  359. package/lib/orchestrator/experiment-budget.d.ts.map +1 -0
  360. package/lib/orchestrator/experiment-budget.js +51 -0
  361. package/lib/orchestrator/experiment-budget.js.map +1 -0
  362. package/lib/orchestrator/experiment-context.d.ts +43 -0
  363. package/lib/orchestrator/experiment-context.d.ts.map +1 -0
  364. package/lib/orchestrator/experiment-context.js +9 -0
  365. package/lib/orchestrator/experiment-context.js.map +1 -0
  366. package/lib/orchestrator/experiment-contracts.d.ts +290 -0
  367. package/lib/orchestrator/experiment-contracts.d.ts.map +1 -0
  368. package/lib/orchestrator/experiment-contracts.js +38 -0
  369. package/lib/orchestrator/experiment-contracts.js.map +1 -0
  370. package/lib/orchestrator/experiment-corpus.d.ts +21 -0
  371. package/lib/orchestrator/experiment-corpus.d.ts.map +1 -0
  372. package/lib/orchestrator/experiment-corpus.js +276 -0
  373. package/lib/orchestrator/experiment-corpus.js.map +1 -0
  374. package/lib/orchestrator/experiment-evaluation.d.ts +44 -0
  375. package/lib/orchestrator/experiment-evaluation.d.ts.map +1 -0
  376. package/lib/orchestrator/experiment-evaluation.js +542 -0
  377. package/lib/orchestrator/experiment-evaluation.js.map +1 -0
  378. package/lib/orchestrator/experiment-finish.d.ts +6 -0
  379. package/lib/orchestrator/experiment-finish.d.ts.map +1 -0
  380. package/lib/orchestrator/experiment-finish.js +19 -0
  381. package/lib/orchestrator/experiment-finish.js.map +1 -0
  382. package/lib/orchestrator/experiment-identity.d.ts +6 -0
  383. package/lib/orchestrator/experiment-identity.d.ts.map +1 -0
  384. package/lib/orchestrator/experiment-identity.js +257 -0
  385. package/lib/orchestrator/experiment-identity.js.map +1 -0
  386. package/lib/orchestrator/experiment-loader.d.ts +175 -0
  387. package/lib/orchestrator/experiment-loader.d.ts.map +1 -0
  388. package/lib/orchestrator/experiment-loader.js +77 -0
  389. package/lib/orchestrator/experiment-loader.js.map +1 -0
  390. package/lib/orchestrator/experiment-report.d.ts +450 -0
  391. package/lib/orchestrator/experiment-report.d.ts.map +1 -0
  392. package/lib/orchestrator/experiment-report.js +570 -0
  393. package/lib/orchestrator/experiment-report.js.map +1 -0
  394. package/lib/orchestrator/experiment-runs.d.ts +379 -0
  395. package/lib/orchestrator/experiment-runs.d.ts.map +1 -0
  396. package/lib/orchestrator/experiment-runs.js +796 -0
  397. package/lib/orchestrator/experiment-runs.js.map +1 -0
  398. package/lib/orchestrator/experiment-runtime-identity.d.ts +41 -0
  399. package/lib/orchestrator/experiment-runtime-identity.d.ts.map +1 -0
  400. package/lib/orchestrator/experiment-runtime-identity.js +411 -0
  401. package/lib/orchestrator/experiment-runtime-identity.js.map +1 -0
  402. package/lib/orchestrator/experiment-selection.d.ts +25 -0
  403. package/lib/orchestrator/experiment-selection.d.ts.map +1 -0
  404. package/lib/orchestrator/experiment-selection.js +286 -0
  405. package/lib/orchestrator/experiment-selection.js.map +1 -0
  406. package/lib/orchestrator/experiment.d.ts +11 -0
  407. package/lib/orchestrator/experiment.d.ts.map +1 -0
  408. package/lib/orchestrator/experiment.js +639 -0
  409. package/lib/orchestrator/experiment.js.map +1 -0
  410. package/lib/orchestrator/fixture-identity.d.ts +7 -0
  411. package/lib/orchestrator/fixture-identity.d.ts.map +1 -0
  412. package/lib/orchestrator/fixture-identity.js +55 -0
  413. package/lib/orchestrator/fixture-identity.js.map +1 -0
  414. package/lib/orchestrator/fs-read.d.ts +19 -0
  415. package/lib/orchestrator/fs-read.d.ts.map +1 -0
  416. package/lib/orchestrator/fs-read.js +113 -0
  417. package/lib/orchestrator/fs-read.js.map +1 -0
  418. package/lib/orchestrator/grader-experiment.d.ts +190 -0
  419. package/lib/orchestrator/grader-experiment.d.ts.map +1 -0
  420. package/lib/orchestrator/grader-experiment.js +579 -0
  421. package/lib/orchestrator/grader-experiment.js.map +1 -0
  422. package/lib/orchestrator/grading.d.ts +31 -0
  423. package/lib/orchestrator/grading.d.ts.map +1 -0
  424. package/lib/orchestrator/grading.js +186 -0
  425. package/lib/orchestrator/grading.js.map +1 -0
  426. package/lib/orchestrator/index.d.ts +22 -0
  427. package/lib/orchestrator/index.d.ts.map +1 -0
  428. package/lib/orchestrator/index.js +30 -0
  429. package/lib/orchestrator/index.js.map +1 -0
  430. package/lib/orchestrator/lifecycle-hook-execution.d.ts +4 -0
  431. package/lib/orchestrator/lifecycle-hook-execution.d.ts.map +1 -0
  432. package/lib/orchestrator/lifecycle-hook-execution.js +67 -0
  433. package/lib/orchestrator/lifecycle-hook-execution.js.map +1 -0
  434. package/lib/orchestrator/list-runs.d.ts +33 -0
  435. package/lib/orchestrator/list-runs.d.ts.map +1 -0
  436. package/lib/orchestrator/list-runs.js +308 -0
  437. package/lib/orchestrator/list-runs.js.map +1 -0
  438. package/lib/orchestrator/loader.d.ts +3 -0
  439. package/lib/orchestrator/loader.d.ts.map +1 -0
  440. package/lib/orchestrator/loader.js +17 -0
  441. package/lib/orchestrator/loader.js.map +1 -0
  442. package/lib/orchestrator/optimization-lifecycle.d.ts +12 -0
  443. package/lib/orchestrator/optimization-lifecycle.d.ts.map +1 -0
  444. package/lib/orchestrator/optimization-lifecycle.js +92 -0
  445. package/lib/orchestrator/optimization-lifecycle.js.map +1 -0
  446. package/lib/orchestrator/orchestrator.d.ts +144 -0
  447. package/lib/orchestrator/orchestrator.d.ts.map +1 -0
  448. package/lib/orchestrator/orchestrator.js +632 -0
  449. package/lib/orchestrator/orchestrator.js.map +1 -0
  450. package/lib/orchestrator/owned-lock.d.ts +17 -0
  451. package/lib/orchestrator/owned-lock.d.ts.map +1 -0
  452. package/lib/orchestrator/owned-lock.js +149 -0
  453. package/lib/orchestrator/owned-lock.js.map +1 -0
  454. package/lib/orchestrator/package-version.d.ts +2 -0
  455. package/lib/orchestrator/package-version.d.ts.map +1 -0
  456. package/lib/orchestrator/package-version.js +4 -0
  457. package/lib/orchestrator/package-version.js.map +1 -0
  458. package/lib/orchestrator/provenance.d.ts +5 -0
  459. package/lib/orchestrator/provenance.d.ts.map +1 -0
  460. package/lib/orchestrator/provenance.js +137 -0
  461. package/lib/orchestrator/provenance.js.map +1 -0
  462. package/lib/orchestrator/regrade.d.ts +80 -0
  463. package/lib/orchestrator/regrade.d.ts.map +1 -0
  464. package/lib/orchestrator/regrade.js +433 -0
  465. package/lib/orchestrator/regrade.js.map +1 -0
  466. package/lib/orchestrator/rubric-loader.d.ts +7 -0
  467. package/lib/orchestrator/rubric-loader.d.ts.map +1 -0
  468. package/lib/orchestrator/rubric-loader.js +65 -0
  469. package/lib/orchestrator/rubric-loader.js.map +1 -0
  470. package/lib/orchestrator/run-dir.d.ts +40 -0
  471. package/lib/orchestrator/run-dir.d.ts.map +1 -0
  472. package/lib/orchestrator/run-dir.js +161 -0
  473. package/lib/orchestrator/run-dir.js.map +1 -0
  474. package/lib/orchestrator/run-execution.d.ts +30 -0
  475. package/lib/orchestrator/run-execution.d.ts.map +1 -0
  476. package/lib/orchestrator/run-execution.js +531 -0
  477. package/lib/orchestrator/run-execution.js.map +1 -0
  478. package/lib/orchestrator/run-lifecycle.d.ts +115 -0
  479. package/lib/orchestrator/run-lifecycle.d.ts.map +1 -0
  480. package/lib/orchestrator/run-lifecycle.js +136 -0
  481. package/lib/orchestrator/run-lifecycle.js.map +1 -0
  482. package/lib/orchestrator/suite-compare.d.ts +76 -0
  483. package/lib/orchestrator/suite-compare.d.ts.map +1 -0
  484. package/lib/orchestrator/suite-compare.js +159 -0
  485. package/lib/orchestrator/suite-compare.js.map +1 -0
  486. package/lib/orchestrator/suite-lifecycle.d.ts +157 -0
  487. package/lib/orchestrator/suite-lifecycle.d.ts.map +1 -0
  488. package/lib/orchestrator/suite-lifecycle.js +253 -0
  489. package/lib/orchestrator/suite-lifecycle.js.map +1 -0
  490. package/lib/orchestrator/suite-runs.d.ts +91 -0
  491. package/lib/orchestrator/suite-runs.d.ts.map +1 -0
  492. package/lib/orchestrator/suite-runs.js +157 -0
  493. package/lib/orchestrator/suite-runs.js.map +1 -0
  494. package/lib/orchestrator/suite-summary.d.ts +109 -0
  495. package/lib/orchestrator/suite-summary.d.ts.map +1 -0
  496. package/lib/orchestrator/suite-summary.js +213 -0
  497. package/lib/orchestrator/suite-summary.js.map +1 -0
  498. package/lib/orchestrator/trace-limits.d.ts +20 -0
  499. package/lib/orchestrator/trace-limits.d.ts.map +1 -0
  500. package/lib/orchestrator/trace-limits.js +28 -0
  501. package/lib/orchestrator/trace-limits.js.map +1 -0
  502. package/lib/orchestrator/validate-plugin-config.d.ts +10 -0
  503. package/lib/orchestrator/validate-plugin-config.d.ts.map +1 -0
  504. package/lib/orchestrator/validate-plugin-config.js +26 -0
  505. package/lib/orchestrator/validate-plugin-config.js.map +1 -0
  506. package/package.json +84 -0
  507. package/skills/lux/SKILL.md +85 -0
  508. package/skills/lux/references/cli.md +102 -0
  509. package/skills/lux/references/eval-authoring.md +105 -0
  510. package/skills/lux-answerer/SKILL.md +169 -0
  511. package/src/answerers/claude-code/index.ts +342 -0
  512. package/src/answerers/persona/index.ts +327 -0
  513. package/src/answerers/scripted/index.ts +186 -0
  514. package/src/answerers/shared.ts +23 -0
  515. package/src/answerers/terminal/index.ts +117 -0
  516. package/src/assertions/core/command-exits-zero.ts +94 -0
  517. package/src/assertions/core/event-checks.ts +138 -0
  518. package/src/assertions/core/file-checks.ts +336 -0
  519. package/src/assertions/core/helpers.ts +203 -0
  520. package/src/assertions/core/index.ts +80 -0
  521. package/src/assertions/core/json-pointer-equals.ts +84 -0
  522. package/src/assertions/core/output-checks.ts +134 -0
  523. package/src/assertions/core/predicate.ts +67 -0
  524. package/src/assertions/core/resource-checks.ts +180 -0
  525. package/src/assertions/core/safe-regex.ts +196 -0
  526. package/src/assertions/core/tool-called-with.ts +68 -0
  527. package/src/assertions/core/tool-calls.ts +28 -0
  528. package/src/assertions/core/tool-invocation.ts +198 -0
  529. package/src/assertions/core/tool-result-checks.ts +211 -0
  530. package/src/assertions/mcp/discovery-checks.ts +139 -0
  531. package/src/assertions/mcp/index.ts +37 -0
  532. package/src/assertions/rubric/index.ts +258 -0
  533. package/src/assertions/rubric/internal.ts +367 -0
  534. package/src/authoring.ts +2 -0
  535. package/src/cli/bin.ts +14 -0
  536. package/src/cli/command-runtime.ts +34 -0
  537. package/src/cli/init.ts +410 -0
  538. package/src/cli/program.ts +992 -0
  539. package/src/cli/render/format.ts +69 -0
  540. package/src/cli/render/reporter.ts +643 -0
  541. package/src/cli/render/tty.ts +314 -0
  542. package/src/cli/run-options.ts +82 -0
  543. package/src/cli/skills.ts +129 -0
  544. package/src/core/annotation.ts +104 -0
  545. package/src/core/answerer.ts +75 -0
  546. package/src/core/artifact-evidence.ts +249 -0
  547. package/src/core/artifact-snapshot.ts +429 -0
  548. package/src/core/assertion.ts +156 -0
  549. package/src/core/case.ts +103 -0
  550. package/src/core/conversation-turn.ts +47 -0
  551. package/src/core/driver.ts +75 -0
  552. package/src/core/emitter.ts +38 -0
  553. package/src/core/events.ts +135 -0
  554. package/src/core/exec.ts +263 -0
  555. package/src/core/experiment.ts +467 -0
  556. package/src/core/glob.ts +31 -0
  557. package/src/core/hash.ts +237 -0
  558. package/src/core/index.ts +28 -0
  559. package/src/core/lifecycle-fixtures.ts +145 -0
  560. package/src/core/lifecycle-hooks.ts +101 -0
  561. package/src/core/mcp-events.ts +57 -0
  562. package/src/core/model-cli.ts +271 -0
  563. package/src/core/observer.ts +50 -0
  564. package/src/core/platform-process.ts +284 -0
  565. package/src/core/registry.ts +57 -0
  566. package/src/core/rubric.ts +52 -0
  567. package/src/core/run.ts +144 -0
  568. package/src/core/statistics.ts +194 -0
  569. package/src/core/subject.ts +50 -0
  570. package/src/core/tool-events.ts +292 -0
  571. package/src/core/usage.ts +123 -0
  572. package/src/core/wire.ts +135 -0
  573. package/src/core/zod.ts +20 -0
  574. package/src/drivers/claude-code/index.ts +866 -0
  575. package/src/drivers/claude-code/parse-events.ts +464 -0
  576. package/src/drivers/codex/app-events.ts +218 -0
  577. package/src/drivers/codex/config.ts +128 -0
  578. package/src/drivers/codex/index.ts +995 -0
  579. package/src/drivers/mcp/index.ts +351 -0
  580. package/src/drivers/mcp/transport.ts +222 -0
  581. package/src/drivers/shared/artifacts.ts +86 -0
  582. package/src/drivers/shared/secure-copy.ts +40 -0
  583. package/src/drivers/subprocess/index.ts +490 -0
  584. package/src/index.ts +594 -0
  585. package/src/optimization/reflective.ts +319 -0
  586. package/src/orchestrator/annotation-loader.ts +97 -0
  587. package/src/orchestrator/annotation-store.ts +112 -0
  588. package/src/orchestrator/authored-dependencies.ts +193 -0
  589. package/src/orchestrator/campaign-lifecycle.ts +181 -0
  590. package/src/orchestrator/candidate-integrity.ts +18 -0
  591. package/src/orchestrator/candidates.ts +683 -0
  592. package/src/orchestrator/compare.ts +358 -0
  593. package/src/orchestrator/comparison-identity.ts +45 -0
  594. package/src/orchestrator/config.ts +205 -0
  595. package/src/orchestrator/content-bound-json.ts +71 -0
  596. package/src/orchestrator/discover.ts +132 -0
  597. package/src/orchestrator/doctor.ts +781 -0
  598. package/src/orchestrator/driver-session.ts +507 -0
  599. package/src/orchestrator/evaluator-identity.ts +45 -0
  600. package/src/orchestrator/experiment-budget.ts +61 -0
  601. package/src/orchestrator/experiment-context.ts +41 -0
  602. package/src/orchestrator/experiment-contracts.ts +43 -0
  603. package/src/orchestrator/experiment-corpus.ts +416 -0
  604. package/src/orchestrator/experiment-evaluation.ts +749 -0
  605. package/src/orchestrator/experiment-finish.ts +33 -0
  606. package/src/orchestrator/experiment-identity.ts +356 -0
  607. package/src/orchestrator/experiment-loader.ts +105 -0
  608. package/src/orchestrator/experiment-report.ts +788 -0
  609. package/src/orchestrator/experiment-runs.ts +960 -0
  610. package/src/orchestrator/experiment-runtime-identity.ts +465 -0
  611. package/src/orchestrator/experiment-selection.ts +467 -0
  612. package/src/orchestrator/experiment.ts +991 -0
  613. package/src/orchestrator/fixture-identity.ts +67 -0
  614. package/src/orchestrator/fs-read.ts +126 -0
  615. package/src/orchestrator/grader-experiment.ts +856 -0
  616. package/src/orchestrator/grading.ts +260 -0
  617. package/src/orchestrator/index.ts +186 -0
  618. package/src/orchestrator/lifecycle-hook-execution.ts +90 -0
  619. package/src/orchestrator/list-runs.ts +384 -0
  620. package/src/orchestrator/loader.ts +19 -0
  621. package/src/orchestrator/optimization-lifecycle.ts +126 -0
  622. package/src/orchestrator/orchestrator.ts +919 -0
  623. package/src/orchestrator/owned-lock.ts +147 -0
  624. package/src/orchestrator/package-version.ts +5 -0
  625. package/src/orchestrator/provenance.ts +140 -0
  626. package/src/orchestrator/regrade.ts +580 -0
  627. package/src/orchestrator/rubric-loader.ts +74 -0
  628. package/src/orchestrator/run-dir.ts +209 -0
  629. package/src/orchestrator/run-execution.ts +694 -0
  630. package/src/orchestrator/run-lifecycle.ts +174 -0
  631. package/src/orchestrator/suite-compare.ts +195 -0
  632. package/src/orchestrator/suite-lifecycle.ts +335 -0
  633. package/src/orchestrator/suite-runs.ts +202 -0
  634. package/src/orchestrator/suite-summary.ts +276 -0
  635. package/src/orchestrator/trace-limits.ts +65 -0
  636. package/src/orchestrator/validate-plugin-config.ts +34 -0
@@ -0,0 +1,991 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { rm } from "node:fs/promises";
3
+ import type { z } from "zod";
4
+ import {
5
+ bootstrapMeanConfidenceInterval,
6
+ type Candidate,
7
+ type CandidateProposal,
8
+ type ExperimentCampaign,
9
+ type ExperimentSelector,
10
+ type Optimizer,
11
+ } from "../core/index.js";
12
+ import { createReflectiveOptimizer } from "../optimization/reflective.js";
13
+ import { selectGraderAnnotations } from "./annotation-loader.js";
14
+ import { type CampaignLifecycleEffects, executeCampaignLifecycle } from "./campaign-lifecycle.js";
15
+ import {
16
+ captureSeedCandidate,
17
+ createCandidate,
18
+ hashSubjectTree,
19
+ materializeCandidate,
20
+ validateCandidate,
21
+ } from "./candidates.js";
22
+ import { selectCases } from "./discover.js";
23
+ import { boundedExecutionSignal, ensureBudget, throwIfAborted } from "./experiment-budget.js";
24
+ import type { CampaignExecutionOptions, EvaluationCorpus } from "./experiment-context.js";
25
+ import {
26
+ type CampaignExecutionResult,
27
+ CampaignExecutionResultSchema,
28
+ type ExperimentCampaignPlan,
29
+ ExperimentCampaignPlanSchema,
30
+ } from "./experiment-contracts.js";
31
+ import {
32
+ agentCorpusHash,
33
+ assertGraderSubject,
34
+ graderCorpusHash,
35
+ prepareCorpus,
36
+ selectorsBySplit,
37
+ } from "./experiment-corpus.js";
38
+ import {
39
+ type EvaluationAccounting,
40
+ evaluateAgentCandidate,
41
+ evaluateOfflineGraderCandidate,
42
+ invokeWithVerifiedExecutionIdentity,
43
+ } from "./experiment-evaluation.js";
44
+ import { finishExperiment } from "./experiment-finish.js";
45
+ import {
46
+ evaluationIdentity,
47
+ resolvedProfileSnapshot,
48
+ runtimeCliReferences,
49
+ } from "./experiment-identity.js";
50
+ import {
51
+ type ExperimentEvaluation,
52
+ loadExperimentCandidates,
53
+ loadExperimentRun,
54
+ type OpenedExperimentRun,
55
+ openExperimentRun,
56
+ resumeExperimentRun,
57
+ } from "./experiment-runs.js";
58
+ import {
59
+ claudeConfigRoot,
60
+ claudeUserBehaviorHash,
61
+ cliIdentity,
62
+ configuredClaudeSourceHashes,
63
+ LUX_VERSION,
64
+ runtimeCliIdentityHashForReferences,
65
+ } from "./experiment-runtime-identity.js";
66
+ import {
67
+ candidateEvaluations,
68
+ type ExperimentSelectionDerivationOptionsSchema,
69
+ rankCandidates,
70
+ searchFrontier,
71
+ } from "./experiment-selection.js";
72
+ import { loadEvalCase } from "./loader.js";
73
+ import { executeOptimizationLifecycle } from "./optimization-lifecycle.js";
74
+
75
+ export type { CampaignExecutionOptions } from "./experiment-context.js";
76
+ export {
77
+ type CampaignExecutionResult,
78
+ CampaignExecutionResultSchema,
79
+ type ExperimentCampaignPlan,
80
+ ExperimentCampaignPlanSchema,
81
+ } from "./experiment-contracts.js";
82
+ export {
83
+ deriveExperimentSelection,
84
+ type ExperimentDecisionCandidate,
85
+ ExperimentDecisionCandidateSchema,
86
+ paretoFrontier,
87
+ rankCandidates,
88
+ } from "./experiment-selection.js";
89
+
90
+ type ExperimentSplit = "train" | "validation" | "test";
91
+ const evaluateAcross = async (options: {
92
+ execution: CampaignExecutionOptions;
93
+ experiment: OpenedExperimentRun;
94
+ candidate: Candidate;
95
+ splits: Array<{ split: ExperimentSplit; selector: ExperimentSelector; repetitions: number }>;
96
+ corpus: EvaluationCorpus;
97
+ verifyExecutionIdentity: () => Promise<void>;
98
+ }): Promise<ExperimentEvaluation[]> => {
99
+ const boundedSignal = boundedExecutionSignal(options.execution, options.experiment);
100
+ const boundedExecution = { ...options.execution, abortSignal: boundedSignal };
101
+ throwIfAborted(boundedSignal);
102
+ ensureBudget(options.execution.loaded.campaign, options.experiment);
103
+ const currentCorpusHash =
104
+ options.corpus.kind === "agent"
105
+ ? await agentCorpusHash(
106
+ options.execution,
107
+ options.corpus.cases,
108
+ selectorsBySplit(options.execution.loaded.campaign),
109
+ )
110
+ : await graderCorpusHash(
111
+ options.execution,
112
+ selectorsBySplit(options.execution.loaded.campaign),
113
+ );
114
+ if (currentCorpusHash !== options.corpus.hash) {
115
+ throw new Error("experiment evaluation corpus changed while the campaign was running");
116
+ }
117
+ const materializedRoot = await materializeCandidate(
118
+ options.execution.loaded.subjectRoot,
119
+ options.execution.loaded.campaign.subject,
120
+ options.candidate,
121
+ );
122
+ try {
123
+ await validateCandidate(
124
+ materializedRoot,
125
+ options.execution.loaded.campaign.subject,
126
+ boundedSignal,
127
+ options.verifyExecutionIdentity,
128
+ );
129
+ throwIfAborted(boundedSignal);
130
+ const evaluations: ExperimentEvaluation[] = [];
131
+ for (const profileId of options.execution.loaded.campaign.profiles) {
132
+ for (const split of options.splits) {
133
+ throwIfAborted(boundedSignal);
134
+ evaluations.push(
135
+ options.corpus.kind === "agent"
136
+ ? await evaluateAgentCandidate({
137
+ execution: boundedExecution,
138
+ experiment: options.experiment,
139
+ candidate: options.candidate,
140
+ allCases: options.corpus.cases,
141
+ profileId,
142
+ verifyExecutionIdentity: options.verifyExecutionIdentity,
143
+ ...split,
144
+ })
145
+ : await evaluateOfflineGraderCandidate({
146
+ execution: boundedExecution,
147
+ experiment: options.experiment,
148
+ candidate: options.candidate,
149
+ materializedRoot,
150
+ profileId,
151
+ verifyExecutionIdentity: options.verifyExecutionIdentity,
152
+ ...split,
153
+ }),
154
+ );
155
+ throwIfAborted(boundedSignal);
156
+ ensureBudget(options.execution.loaded.campaign, options.experiment);
157
+ }
158
+ }
159
+ const finalCorpusHash =
160
+ options.corpus.kind === "agent"
161
+ ? await agentCorpusHash(
162
+ options.execution,
163
+ options.corpus.cases,
164
+ selectorsBySplit(options.execution.loaded.campaign),
165
+ )
166
+ : await graderCorpusHash(
167
+ options.execution,
168
+ selectorsBySplit(options.execution.loaded.campaign),
169
+ );
170
+ if (finalCorpusHash !== options.corpus.hash) {
171
+ throw new Error(
172
+ "experiment evaluation corpus changed while candidate evaluation was running",
173
+ );
174
+ }
175
+ const finalSourceHash = await hashSubjectTree(
176
+ options.execution.loaded.subjectRoot,
177
+ options.execution.loaded.campaign.subject,
178
+ );
179
+ if (finalSourceHash !== options.candidate.baseSourceHash) {
180
+ throw new Error("experiment subject source changed while candidate evaluation was running");
181
+ }
182
+ return evaluations;
183
+ } finally {
184
+ await rm(materializedRoot, { recursive: true, force: true });
185
+ }
186
+ };
187
+
188
+ export const planExperimentCampaign = async (
189
+ execution: CampaignExecutionOptions,
190
+ mode: "experiment" | "optimize",
191
+ ): Promise<ExperimentCampaignPlan> => {
192
+ const campaign = execution.loaded.campaign;
193
+ if (mode === "optimize") {
194
+ if (!campaign.optimizer) {
195
+ throw new Error(`experiment '${campaign.id}' has no optimizer configuration`);
196
+ }
197
+ createReflectiveOptimizer(campaign.optimizer.config);
198
+ }
199
+ const baseline = await captureSeedCandidate(execution.loaded.subjectRoot, campaign.subject);
200
+ await assertGraderSubject(execution, baseline);
201
+ const corpus = await prepareCorpus(execution);
202
+ // Plan is the deterministic preflight for execution. Build the exact same
203
+ // identity now so profile, behavior-tree, and custom CLI failures happen
204
+ // before a user approves any model spend.
205
+ await evaluationIdentity(execution, corpus, mode);
206
+ const splitSelectors = selectorsBySplit(campaign);
207
+ const graderAnnotations = execution.loaded.graderAnnotations?.annotations ?? [];
208
+ const splits = splitSelectors.map(({ name, selector }) => ({
209
+ split: name,
210
+ examples:
211
+ corpus.kind === "agent"
212
+ ? selectCases(corpus.cases, selector).length
213
+ : selectGraderAnnotations(graderAnnotations, selector).length,
214
+ }));
215
+ const profiles = campaign.profiles.length;
216
+ const count = (names: ExperimentSplit[]): number =>
217
+ splits
218
+ .filter((split) => names.includes(split.split))
219
+ .reduce((total, split) => total + split.examples, 0);
220
+ const projectedCandidates =
221
+ mode === "experiment" ? 1 + campaign.variants.length : campaign.budget.maxCandidates;
222
+ const projectedMetricCalls =
223
+ mode === "experiment"
224
+ ? projectedCandidates *
225
+ profiles *
226
+ count(["train", "validation", "test"]) *
227
+ campaign.repetitions.promotion
228
+ : projectedCandidates *
229
+ profiles *
230
+ count(["train", "validation"]) *
231
+ campaign.repetitions.search +
232
+ (1 + (campaign.optimizer?.promotionTopK ?? 1)) *
233
+ profiles *
234
+ count(["validation", "test"]) *
235
+ campaign.repetitions.promotion;
236
+ let maxRubricAssertions = 0;
237
+ if (corpus.kind === "grader") {
238
+ // Grader candidates may grow any rubric to the loader's hard cap.
239
+ maxRubricAssertions = 20;
240
+ } else {
241
+ const selected = splitSelectors.flatMap((split) => selectCases(corpus.cases, split.selector));
242
+ maxRubricAssertions = Math.max(
243
+ 0,
244
+ ...(await Promise.all(selected.map((evalCase) => loadEvalCase(evalCase.path)))).map(
245
+ (evalCase) =>
246
+ (evalCase.assertions ?? []).filter((assertion) => assertion.type === "rubric").length,
247
+ ),
248
+ );
249
+ }
250
+ const projectedJudgeCalls = projectedMetricCalls * maxRubricAssertions * 2;
251
+ const projectedProposalCalls = mode === "optimize" ? campaign.budget.maxProposalCalls : 0;
252
+ const warnings = campaign.objectives.constraints.flatMap((constraint) => {
253
+ if (!constraint.confidence) return [];
254
+ const splitSelector = splitSelectors.find((split) => split.name === constraint.split)?.selector;
255
+ let examples = splits.find((split) => split.split === constraint.split)?.examples ?? 0;
256
+ if (corpus.kind === "grader" && splitSelector) {
257
+ const selected = selectGraderAnnotations(graderAnnotations, splitSelector);
258
+ if (constraint.metric === "grader.macroFalsePositiveRate") {
259
+ examples = selected.filter((annotation) =>
260
+ [
261
+ annotation.expected.casePassed,
262
+ ...Object.values(annotation.expected.assertions).map((label) => label.passed),
263
+ ].includes(false),
264
+ ).length;
265
+ } else if (constraint.metric === "grader.macroFalseNegativeRate") {
266
+ examples = selected.filter((annotation) =>
267
+ [
268
+ annotation.expected.casePassed,
269
+ ...Object.values(annotation.expected.assertions).map((label) => label.passed),
270
+ ].includes(true),
271
+ ).length;
272
+ } else if (constraint.metric === "grader.scoreMae") {
273
+ examples = selected.filter(
274
+ (annotation) =>
275
+ annotation.expected.caseScore !== undefined ||
276
+ Object.values(annotation.expected.assertions).some(
277
+ (label) => label.score !== undefined,
278
+ ),
279
+ ).length;
280
+ } else if (constraint.metric === "grader.caseAgreement") {
281
+ examples = selected.filter(
282
+ (annotation) =>
283
+ annotation.expected.casePassed !== undefined ||
284
+ annotation.expected.caseScore !== undefined,
285
+ ).length;
286
+ } else if (constraint.metric === "grader.assertionAgreement") {
287
+ examples = selected.filter(
288
+ (annotation) => Object.keys(annotation.expected.assertions).length > 0,
289
+ ).length;
290
+ } else if (
291
+ constraint.metric === "grader.abstentionRate" ||
292
+ constraint.metric === "grader.coverage"
293
+ ) {
294
+ examples = selected.filter(
295
+ (annotation) =>
296
+ annotation.expected.casePassed !== undefined ||
297
+ Object.keys(annotation.expected.assertions).length > 0,
298
+ ).length;
299
+ }
300
+ }
301
+ if (constraint.metric === "subject.bytes" || constraint.metric === "quality.caseRegressions") {
302
+ examples = 1;
303
+ }
304
+ const notes: string[] = [];
305
+ if (examples < constraint.confidence.minSamples) {
306
+ notes.push(
307
+ `${constraint.split} metric '${constraint.metric}' has ${examples} independent samples, below confidence minSamples=${constraint.confidence.minSamples}`,
308
+ );
309
+ } else if (
310
+ constraint.confidence.method === "wilson" ||
311
+ constraint.confidence.method === "hoeffding"
312
+ ) {
313
+ const bestForMin = bootstrapMeanConfidenceInterval(
314
+ Array<number>(examples).fill(1),
315
+ constraint.confidence,
316
+ );
317
+ const bestForMax = bootstrapMeanConfidenceInterval(
318
+ Array<number>(examples).fill(0),
319
+ constraint.confidence,
320
+ );
321
+ if (constraint.min !== undefined && bestForMin.lower < constraint.min) {
322
+ notes.push(
323
+ `${constraint.split} metric '${constraint.metric}' cannot attain confidence lower bound ${constraint.min} with only ${examples} samples (best ${bestForMin.lower.toFixed(3)})`,
324
+ );
325
+ }
326
+ if (constraint.max !== undefined && bestForMax.upper > constraint.max) {
327
+ notes.push(
328
+ `${constraint.split} metric '${constraint.metric}' cannot attain confidence upper bound ${constraint.max} with only ${examples} samples (best ${bestForMax.upper.toFixed(3)})`,
329
+ );
330
+ }
331
+ }
332
+ return notes;
333
+ });
334
+ warnings.push(
335
+ "token, reported-cost, and wall-time limits are enforced from observed runtime usage and cannot be proven by the no-call plan",
336
+ );
337
+ return ExperimentCampaignPlanSchema.parse({
338
+ campaignId: campaign.id,
339
+ mode,
340
+ evaluationKind: campaign.evaluation.kind,
341
+ subjectRoot: execution.loaded.subjectRoot,
342
+ mutableComponents: Object.keys(baseline.components).sort(),
343
+ profiles: [...campaign.profiles],
344
+ splits,
345
+ projected: {
346
+ candidates: projectedCandidates,
347
+ metricCalls: projectedMetricCalls,
348
+ judgeCalls: projectedJudgeCalls,
349
+ proposalCalls: projectedProposalCalls,
350
+ },
351
+ budget: campaign.budget,
352
+ fitsProjectedCallBudgets:
353
+ projectedCandidates <= campaign.budget.maxCandidates &&
354
+ projectedMetricCalls <= campaign.budget.maxMetricCalls &&
355
+ projectedJudgeCalls <= campaign.budget.maxJudgeCalls &&
356
+ projectedProposalCalls <= campaign.budget.maxProposalCalls,
357
+ warnings,
358
+ });
359
+ };
360
+
361
+ const prepareExecution = async (
362
+ execution: CampaignExecutionOptions,
363
+ mode: "experiment" | "optimize",
364
+ ): Promise<{
365
+ baseline: Candidate;
366
+ candidates: Candidate[];
367
+ experiment: OpenedExperimentRun;
368
+ corpus: EvaluationCorpus;
369
+ verifyExecutionIdentity: () => Promise<void>;
370
+ }> => {
371
+ const campaign = execution.loaded.campaign;
372
+ const baseline = await captureSeedCandidate(execution.loaded.subjectRoot, campaign.subject);
373
+ await assertGraderSubject(execution, baseline);
374
+ const corpus = await prepareCorpus(execution);
375
+ const runtimeReferences = await runtimeCliReferences(execution, corpus, mode);
376
+ const expectedRuntimeCliIdentityHash =
377
+ await runtimeCliIdentityHashForReferences(runtimeReferences);
378
+ const currentEvaluationHash = await evaluationIdentity(
379
+ execution,
380
+ corpus,
381
+ mode,
382
+ expectedRuntimeCliIdentityHash,
383
+ );
384
+ const verifyExecutionIdentity = async (): Promise<void> => {
385
+ const observed = await runtimeCliIdentityHashForReferences(runtimeReferences);
386
+ if (observed !== expectedRuntimeCliIdentityHash) {
387
+ throw new Error("experiment CLI identity changed during execution");
388
+ }
389
+ };
390
+ await verifyExecutionIdentity();
391
+ if (execution.resumeFrom) {
392
+ const saved = await loadExperimentRun(execution.resumeFrom);
393
+ if (!saved) {
394
+ throw new Error(`experiment manifest not found or invalid at ${execution.resumeFrom}`);
395
+ }
396
+ if (saved.mode !== mode) {
397
+ throw new Error(`cannot resume ${saved.mode} run with lux ${mode}`);
398
+ }
399
+ if (saved.campaignId !== campaign.id || saved.campaignHash !== execution.loaded.campaignHash) {
400
+ throw new Error("cannot resume: the experiment campaign definition changed");
401
+ }
402
+ if (saved.evaluationKind !== campaign.evaluation.kind) {
403
+ throw new Error("cannot resume: the experiment evaluation kind changed");
404
+ }
405
+ if (saved.evaluationHash !== currentEvaluationHash) {
406
+ throw new Error("cannot resume: the evaluation corpus or profile configuration changed");
407
+ }
408
+ if (
409
+ saved.subjectRoot !== execution.loaded.subjectRoot ||
410
+ saved.baseSourceHash !== baseline.baseSourceHash
411
+ ) {
412
+ throw new Error("cannot resume: the experiment subject source changed");
413
+ }
414
+ const experiment = await resumeExperimentRun(execution.resumeFrom);
415
+ try {
416
+ const candidates = await loadExperimentCandidates(experiment.path);
417
+ const savedBaseline = candidates.find((candidate) => candidate.id === "baseline");
418
+ if (!savedBaseline) throw new Error("cannot resume: baseline candidate is missing");
419
+ return {
420
+ baseline: savedBaseline,
421
+ candidates,
422
+ experiment,
423
+ corpus,
424
+ verifyExecutionIdentity,
425
+ };
426
+ } catch (error) {
427
+ await experiment.release();
428
+ throw error;
429
+ }
430
+ }
431
+ const experiment = await openExperimentRun({
432
+ runsRoot: execution.runsRoot ?? execution.config.runsRoot,
433
+ campaignId: campaign.id,
434
+ campaignPath: execution.loaded.path,
435
+ campaignHash: execution.loaded.campaignHash,
436
+ campaignSnapshot: campaign,
437
+ resolvedProfiles: await resolvedProfileSnapshot(execution),
438
+ luxVersion: LUX_VERSION,
439
+ subjectRoot: execution.loaded.subjectRoot,
440
+ baseSourceHash: baseline.baseSourceHash,
441
+ mode,
442
+ evaluationKind: campaign.evaluation.kind,
443
+ evaluationHash: currentEvaluationHash,
444
+ });
445
+ try {
446
+ await experiment.saveCandidate(baseline);
447
+ return {
448
+ baseline,
449
+ candidates: [baseline],
450
+ experiment,
451
+ corpus,
452
+ verifyExecutionIdentity,
453
+ };
454
+ } catch (error) {
455
+ await experiment.release();
456
+ throw error;
457
+ }
458
+ };
459
+
460
+ const splitPlan = (
461
+ campaign: ExperimentCampaign,
462
+ repetitions: number,
463
+ includeTrain: boolean,
464
+ ): Array<{ split: ExperimentSplit; selector: ExperimentSelector; repetitions: number }> => [
465
+ ...(includeTrain && campaign.splits.train
466
+ ? [{ split: "train" as const, selector: campaign.splits.train, repetitions }]
467
+ : []),
468
+ { split: "validation", selector: campaign.splits.validation, repetitions },
469
+ ...(campaign.splits.test
470
+ ? [{ split: "test" as const, selector: campaign.splits.test, repetitions }]
471
+ : []),
472
+ ];
473
+
474
+ type PreparedCampaignExecution = Awaited<ReturnType<typeof prepareExecution>>;
475
+ type CampaignWork = {
476
+ candidates: Candidate[];
477
+ selectionOptions?: z.input<typeof ExperimentSelectionDerivationOptionsSchema>;
478
+ };
479
+ type CampaignWorker = (
480
+ execution: CampaignExecutionOptions,
481
+ prepared: PreparedCampaignExecution,
482
+ ) => Promise<CampaignWork>;
483
+
484
+ class CampaignExecution implements CampaignLifecycleEffects<CampaignWork, CampaignExecutionResult> {
485
+ private prepared: PreparedCampaignExecution | undefined;
486
+ private readonly execution: CampaignExecutionOptions;
487
+ private readonly mode: "experiment" | "optimize";
488
+ private readonly worker: CampaignWorker;
489
+
490
+ constructor(
491
+ execution: CampaignExecutionOptions,
492
+ mode: "experiment" | "optimize",
493
+ worker: CampaignWorker,
494
+ ) {
495
+ this.execution = execution;
496
+ this.mode = mode;
497
+ this.worker = worker;
498
+ }
499
+
500
+ async prepare(): Promise<void> {
501
+ this.prepared = await prepareExecution(this.execution, this.mode);
502
+ }
503
+
504
+ execute(): Promise<CampaignWork> {
505
+ return this.worker(this.execution, this.requirePrepared());
506
+ }
507
+
508
+ async complete(work: CampaignWork): Promise<CampaignExecutionResult> {
509
+ const prepared = this.requirePrepared();
510
+ const champion = await finishExperiment(
511
+ prepared.experiment,
512
+ this.execution.loaded.campaign,
513
+ work.candidates,
514
+ work.selectionOptions,
515
+ );
516
+ return CampaignExecutionResultSchema.parse({
517
+ experimentDir: prepared.experiment.path,
518
+ champion,
519
+ manifest: prepared.experiment.manifest,
520
+ });
521
+ }
522
+
523
+ resultStatus(result: CampaignExecutionResult): "passed" | "failed" {
524
+ return result.manifest.status === "passed" ? "passed" : "failed";
525
+ }
526
+
527
+ failureStatus(_error: unknown): "failed" | "aborted" {
528
+ return this.execution.abortSignal?.aborted ? "aborted" : "failed";
529
+ }
530
+
531
+ async recordFailure(status: "failed" | "aborted", error: unknown): Promise<void> {
532
+ if (!this.prepared) return;
533
+ const { manifest } = this.prepared.experiment;
534
+ manifest.status = status;
535
+ manifest.endedAt = new Date().toISOString();
536
+ manifest.errorMessage = error instanceof Error ? error.message : String(error);
537
+ await this.prepared.experiment.write();
538
+ }
539
+
540
+ async close(_primaryError: unknown | null): Promise<void> {
541
+ await this.prepared?.experiment.release();
542
+ }
543
+
544
+ private requirePrepared(): PreparedCampaignExecution {
545
+ if (!this.prepared) throw new Error("campaign lifecycle entered execution before preparation");
546
+ return this.prepared;
547
+ }
548
+ }
549
+
550
+ const runStaticCampaignWork = async (
551
+ execution: CampaignExecutionOptions,
552
+ prepared: PreparedCampaignExecution,
553
+ ): Promise<CampaignWork> => {
554
+ const campaign = execution.loaded.campaign;
555
+ const {
556
+ baseline,
557
+ candidates: savedCandidates,
558
+ experiment,
559
+ corpus,
560
+ verifyExecutionIdentity,
561
+ } = prepared;
562
+ const candidates = [
563
+ baseline,
564
+ ...campaign.variants.map((variant) => {
565
+ const saved = savedCandidates.find((savedCandidate) => savedCandidate.id === variant.id);
566
+ if (saved) return saved;
567
+ const candidate = createCandidate(
568
+ baseline,
569
+ { updates: variant.components },
570
+ campaign.subject,
571
+ { id: variant.id, proposer: "manual" },
572
+ );
573
+ candidate.parentIds = variant.parentIds.length > 0 ? variant.parentIds : [baseline.id];
574
+ return candidate;
575
+ }),
576
+ ];
577
+ for (const candidate of candidates) {
578
+ throwIfAborted(execution.abortSignal);
579
+ await experiment.saveCandidate(candidate);
580
+ await evaluateAcross({
581
+ execution,
582
+ experiment,
583
+ candidate,
584
+ corpus,
585
+ verifyExecutionIdentity,
586
+ splits: splitPlan(campaign, campaign.repetitions.promotion, true),
587
+ });
588
+ }
589
+ return { candidates };
590
+ };
591
+
592
+ type OptimizationContext = {
593
+ execution: CampaignExecutionOptions;
594
+ prepared: PreparedCampaignExecution;
595
+ optimizer: Optimizer;
596
+ configuration: NonNullable<ExperimentCampaign["optimizer"]>;
597
+ searchSplits: ReturnType<typeof splitPlan>;
598
+ };
599
+
600
+ type OptimizationSearchState = {
601
+ readonly iteration: number;
602
+ readonly staleIterations: number;
603
+ readonly bestId: string;
604
+ };
605
+
606
+ type OptimizationIterationPlan = {
607
+ readonly current: Candidate;
608
+ readonly alternatives: readonly Candidate[];
609
+ readonly frontierHashes: ReadonlySet<string>;
610
+ };
611
+
612
+ type OptimizationPromotionPlan = {
613
+ readonly finalists: readonly Candidate[];
614
+ };
615
+
616
+ type OptimizationDeploymentPlan = {
617
+ readonly deployable: readonly Candidate[];
618
+ readonly finalistIds: readonly string[];
619
+ };
620
+
621
+ const createOptimizationContext = (
622
+ execution: CampaignExecutionOptions,
623
+ prepared: PreparedCampaignExecution,
624
+ ): OptimizationContext => {
625
+ const campaign = execution.loaded.campaign;
626
+ const configuration = campaign.optimizer;
627
+ if (!configuration) {
628
+ throw new Error(`experiment '${campaign.id}' has no optimizer configuration`);
629
+ }
630
+ return {
631
+ execution,
632
+ prepared,
633
+ configuration,
634
+ optimizer: createReflectiveOptimizer(configuration.config),
635
+ searchSplits: splitPlan(campaign, campaign.repetitions.search, true).filter(
636
+ (entry) => entry.split !== "test",
637
+ ),
638
+ };
639
+ };
640
+
641
+ const rankedSearchCandidates = (
642
+ context: OptimizationContext,
643
+ candidates: readonly Candidate[],
644
+ ): Candidate[] =>
645
+ rankCandidates(
646
+ context.execution.loaded.campaign,
647
+ candidates,
648
+ context.prepared.experiment.manifest,
649
+ ["train", "validation"],
650
+ );
651
+
652
+ const optimizationFrontier = (
653
+ context: OptimizationContext,
654
+ candidates: Candidate[],
655
+ ): Candidate[] => {
656
+ if (context.configuration.candidateSelection === "pareto") {
657
+ return searchFrontier(
658
+ context.execution.loaded.campaign,
659
+ candidates,
660
+ context.prepared.experiment.manifest,
661
+ );
662
+ }
663
+ return [rankedSearchCandidates(context, candidates)[0] ?? context.prepared.baseline];
664
+ };
665
+
666
+ const alternativeCandidates = (
667
+ context: OptimizationContext,
668
+ frontier: readonly Candidate[],
669
+ current: Candidate,
670
+ ): Candidate[] =>
671
+ context.configuration.mergeFrontier
672
+ ? frontier.filter((candidate) => candidate.id !== current.id).slice(0, 2)
673
+ : [];
674
+
675
+ const trainEvaluations = (
676
+ experiment: OpenedExperimentRun,
677
+ candidateId: string,
678
+ ): ExperimentEvaluation[] =>
679
+ candidateEvaluations(experiment.manifest, candidateId).filter(
680
+ (evaluation) => evaluation.split === "train",
681
+ );
682
+
683
+ const requestOptimizationProposals = async (
684
+ context: OptimizationContext,
685
+ state: OptimizationSearchState,
686
+ current: Candidate,
687
+ alternatives: Candidate[],
688
+ ): Promise<CandidateProposal[]> => {
689
+ const { execution, prepared, optimizer } = context;
690
+ const { experiment, verifyExecutionIdentity, candidates } = prepared;
691
+ const campaign = execution.loaded.campaign;
692
+ const proposalSignal = boundedExecutionSignal(execution, experiment);
693
+ const proposalAttemptIds: string[] = [];
694
+ let pendingAccounting: EvaluationAccounting | undefined;
695
+
696
+ const input: Parameters<Optimizer["propose"]>[0] = {
697
+ campaign,
698
+ candidate: current,
699
+ observations: trainEvaluations(experiment, current.id),
700
+ remainingCandidates: campaign.budget.maxCandidates - candidates.length,
701
+ cwd: execution.loaded.directory,
702
+ abortSignal: proposalSignal,
703
+ recordModelAttemptStart: async (kind) => {
704
+ await verifyExecutionIdentity();
705
+ proposalAttemptIds.push(await experiment.startModelAttempt(kind));
706
+ },
707
+ completeModelAttempts: async () => {
708
+ await verifyExecutionIdentity();
709
+ if (pendingAccounting) {
710
+ await experiment.settleModelAttempts(
711
+ `proposal-spend-${randomUUID()}`,
712
+ pendingAccounting,
713
+ proposalAttemptIds,
714
+ );
715
+ pendingAccounting = undefined;
716
+ } else {
717
+ await experiment.completeModelAttempts(proposalAttemptIds);
718
+ }
719
+ proposalAttemptIds.length = 0;
720
+ ensureBudget(campaign, experiment);
721
+ },
722
+ recordModelCall: async (accounting) => {
723
+ await verifyExecutionIdentity();
724
+ pendingAccounting = {
725
+ usage: accounting.usage,
726
+ costUsd: accounting.costUsd ?? 0,
727
+ unpricedModelCalls: accounting.costUsd === undefined ? 1 : 0,
728
+ };
729
+ },
730
+ };
731
+ if (alternatives.length > 0) {
732
+ input.alternatives = alternatives;
733
+ input.alternativeObservations = Object.fromEntries(
734
+ alternatives.map((candidate) => [candidate.id, trainEvaluations(experiment, candidate.id)]),
735
+ );
736
+ }
737
+
738
+ const proposals = await invokeWithVerifiedExecutionIdentity(verifyExecutionIdentity, async () => {
739
+ ensureBudget(campaign, experiment, { proposalCalls: 1 });
740
+ await experiment.recordAccounting(`proposal:${state.iteration}:${randomUUID()}`, {
741
+ proposalCalls: 1,
742
+ });
743
+ await experiment.write();
744
+ return optimizer.propose(input);
745
+ });
746
+ throwIfAborted(proposalSignal);
747
+ ensureBudget(campaign, experiment);
748
+ return proposals;
749
+ };
750
+
751
+ const addOptimizationCandidates = async (
752
+ context: OptimizationContext,
753
+ current: Candidate,
754
+ alternatives: readonly Candidate[],
755
+ proposals: readonly CandidateProposal[],
756
+ ): Promise<number> => {
757
+ const { execution, prepared, configuration, searchSplits } = context;
758
+ const { candidates, experiment, corpus, verifyExecutionIdentity } = prepared;
759
+ const campaign = execution.loaded.campaign;
760
+ let added = 0;
761
+ for (const proposal of proposals) {
762
+ const candidate = createCandidate(current, proposal, campaign.subject, {
763
+ proposer: `reflective:${configuration.algorithm}`,
764
+ });
765
+ if (alternatives.length > 0) {
766
+ candidate.parentIds = [current.id, ...alternatives.map((entry) => entry.id)];
767
+ }
768
+ if (candidates.some((entry) => entry.candidateHash === candidate.candidateHash)) continue;
769
+ candidates.push(candidate);
770
+ added++;
771
+ await experiment.saveCandidate(candidate);
772
+ await evaluateAcross({
773
+ execution,
774
+ experiment,
775
+ candidate,
776
+ corpus,
777
+ verifyExecutionIdentity,
778
+ splits: searchSplits,
779
+ });
780
+ if (candidates.length >= campaign.budget.maxCandidates) break;
781
+ }
782
+ return added;
783
+ };
784
+
785
+ const advanceSearchState = (
786
+ context: OptimizationContext,
787
+ state: OptimizationSearchState,
788
+ added: number,
789
+ previousFrontier: ReadonlySet<string>,
790
+ ): OptimizationSearchState => {
791
+ const { candidates, baseline } = context.prepared;
792
+ const nextBest = rankedSearchCandidates(context, candidates)[0]?.id ?? baseline.id;
793
+ const expandedFrontier =
794
+ context.configuration.candidateSelection === "pareto" &&
795
+ optimizationFrontier(context, candidates).some(
796
+ (candidate) => !previousFrontier.has(candidate.candidateHash),
797
+ );
798
+ return {
799
+ iteration: state.iteration + 1,
800
+ staleIterations:
801
+ added > 0 && (nextBest !== state.bestId || expandedFrontier) ? 0 : state.staleIterations + 1,
802
+ bestId: nextBest,
803
+ };
804
+ };
805
+
806
+ const planOptimizationIteration = (
807
+ context: OptimizationContext,
808
+ state: OptimizationSearchState,
809
+ ): OptimizationIterationPlan => {
810
+ throwIfAborted(context.execution.abortSignal);
811
+ const frontier = optimizationFrontier(context, context.prepared.candidates);
812
+ const current = frontier[state.iteration % frontier.length] ?? context.prepared.baseline;
813
+ return {
814
+ current,
815
+ alternatives: alternativeCandidates(context, frontier, current),
816
+ frontierHashes: new Set(frontier.map((candidate) => candidate.candidateHash)),
817
+ };
818
+ };
819
+
820
+ const searchCanContinue = (
821
+ context: OptimizationContext,
822
+ state: OptimizationSearchState,
823
+ ): boolean => {
824
+ const { budget } = context.execution.loaded.campaign;
825
+ return (
826
+ context.prepared.candidates.length < budget.maxCandidates &&
827
+ state.staleIterations < budget.patience &&
828
+ context.prepared.experiment.manifest.budgetUsage.proposalCalls < budget.maxProposalCalls &&
829
+ !context.execution.abortSignal?.aborted
830
+ );
831
+ };
832
+
833
+ const evaluateSearchSeeds = async (context: OptimizationContext): Promise<void> => {
834
+ const { prepared, searchSplits } = context;
835
+ for (const candidate of prepared.candidates) {
836
+ await evaluateAcross({
837
+ execution: context.execution,
838
+ experiment: prepared.experiment,
839
+ candidate,
840
+ corpus: prepared.corpus,
841
+ verifyExecutionIdentity: prepared.verifyExecutionIdentity,
842
+ splits: searchSplits,
843
+ });
844
+ }
845
+ throwIfAborted(context.execution.abortSignal);
846
+ };
847
+
848
+ const initialOptimizationSearch = (context: OptimizationContext): OptimizationSearchState => ({
849
+ iteration: 0,
850
+ staleIterations: 0,
851
+ bestId:
852
+ rankedSearchCandidates(context, context.prepared.candidates)[0]?.id ??
853
+ context.prepared.baseline.id,
854
+ });
855
+
856
+ const evaluateOptimizationProposals = async (
857
+ context: OptimizationContext,
858
+ state: OptimizationSearchState,
859
+ plan: OptimizationIterationPlan,
860
+ proposals: readonly CandidateProposal[],
861
+ ): Promise<OptimizationSearchState> => {
862
+ const added = await addOptimizationCandidates(
863
+ context,
864
+ plan.current,
865
+ plan.alternatives,
866
+ proposals,
867
+ );
868
+ return advanceSearchState(context, state, added, plan.frontierHashes);
869
+ };
870
+
871
+ const selectPromotionCandidates = (context: OptimizationContext): OptimizationPromotionPlan => {
872
+ const { prepared, configuration } = context;
873
+ const { baseline, candidates } = prepared;
874
+ const searchLeaders = rankedSearchCandidates(context, candidates)
875
+ .filter((candidate) => candidate.id !== baseline.id)
876
+ .slice(0, configuration.promotionTopK);
877
+ return { finalists: [baseline, ...searchLeaders] };
878
+ };
879
+
880
+ const evaluatePromotionCandidates = async (
881
+ context: OptimizationContext,
882
+ plan: OptimizationPromotionPlan,
883
+ ): Promise<OptimizationDeploymentPlan> => {
884
+ const { execution, prepared } = context;
885
+ const { baseline, experiment, corpus, verifyExecutionIdentity } = prepared;
886
+ const campaign = execution.loaded.campaign;
887
+ for (const candidate of plan.finalists) {
888
+ await evaluateAcross({
889
+ execution,
890
+ experiment,
891
+ candidate,
892
+ corpus,
893
+ verifyExecutionIdentity,
894
+ splits: [
895
+ {
896
+ split: "validation",
897
+ selector: campaign.splits.validation,
898
+ repetitions: campaign.repetitions.promotion,
899
+ },
900
+ ],
901
+ });
902
+ }
903
+ const promoted = rankedSearchCandidates(context, plan.finalists)[0] ?? baseline;
904
+ return {
905
+ deployable: promoted.id === baseline.id ? [baseline] : [baseline, promoted],
906
+ finalistIds: plan.finalists.map((candidate) => candidate.id),
907
+ };
908
+ };
909
+
910
+ const evaluateDeploymentCandidates = async (
911
+ context: OptimizationContext,
912
+ plan: OptimizationDeploymentPlan,
913
+ ): Promise<CampaignWork> => {
914
+ const { execution, prepared } = context;
915
+ const { experiment, corpus, verifyExecutionIdentity } = prepared;
916
+ const campaign = execution.loaded.campaign;
917
+ if (campaign.splits.test) {
918
+ for (const candidate of plan.deployable) {
919
+ await evaluateAcross({
920
+ execution,
921
+ experiment,
922
+ candidate,
923
+ corpus,
924
+ verifyExecutionIdentity,
925
+ splits: [
926
+ {
927
+ split: "test",
928
+ selector: campaign.splits.test,
929
+ repetitions: campaign.repetitions.promotion,
930
+ },
931
+ ],
932
+ });
933
+ }
934
+ }
935
+ return {
936
+ candidates: [...plan.deployable],
937
+ selectionOptions: {
938
+ promotionFinalistCandidateIds: [...plan.finalistIds],
939
+ },
940
+ };
941
+ };
942
+
943
+ const runOptimizationCampaignWork = async (
944
+ execution: CampaignExecutionOptions,
945
+ prepared: PreparedCampaignExecution,
946
+ ): Promise<CampaignWork> => {
947
+ const context = createOptimizationContext(execution, prepared);
948
+ return executeOptimizationLifecycle({
949
+ initialSearch: () => initialOptimizationSearch(context),
950
+ evaluateSearchSeeds: () => evaluateSearchSeeds(context),
951
+ searchCanContinue: (search) => searchCanContinue(context, search),
952
+ runSearchIteration: async (search) => {
953
+ const plan = planOptimizationIteration(context, search);
954
+ const proposals = await requestOptimizationProposals(context, search, plan.current, [
955
+ ...plan.alternatives,
956
+ ]);
957
+ return evaluateOptimizationProposals(context, search, plan, proposals);
958
+ },
959
+ promote: async () => evaluatePromotionCandidates(context, selectPromotionCandidates(context)),
960
+ test: (deployment) => evaluateDeploymentCandidates(context, deployment),
961
+ });
962
+ };
963
+
964
+ export const runExperimentCampaign = async (
965
+ execution: CampaignExecutionOptions,
966
+ ): Promise<CampaignExecutionResult> => {
967
+ const campaign = execution.loaded.campaign;
968
+ const candidateCount = 1 + campaign.variants.length;
969
+ if (candidateCount > campaign.budget.maxCandidates) {
970
+ throw new Error(
971
+ `experiment defines ${candidateCount} candidates but budget.maxCandidates is ${campaign.budget.maxCandidates}`,
972
+ );
973
+ }
974
+ return executeCampaignLifecycle(
975
+ new CampaignExecution(execution, "experiment", runStaticCampaignWork),
976
+ );
977
+ };
978
+
979
+ export const optimizeExperimentCampaign = async (
980
+ execution: CampaignExecutionOptions,
981
+ ): Promise<CampaignExecutionResult> => {
982
+ if (!execution.loaded.campaign.optimizer) {
983
+ throw new Error(`experiment '${execution.loaded.campaign.id}' has no optimizer configuration`);
984
+ }
985
+ return executeCampaignLifecycle(
986
+ new CampaignExecution(execution, "optimize", runOptimizationCampaignWork),
987
+ );
988
+ };
989
+
990
+ // Internal exports for identity contract tests.
991
+ export { claudeConfigRoot, claudeUserBehaviorHash, cliIdentity, configuredClaudeSourceHashes };