@opensearch-project/agent-health 0.3.0 → 0.5.2

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 (518) hide show
  1. package/README.md +77 -6
  2. package/cli/dist/index.js +10072 -4502
  3. package/deployment/cloudformation/agent-health-observability.yaml +762 -0
  4. package/dist/assets/index-CCQRDlO0.js +243 -0
  5. package/dist/assets/index-CNHQVbcj.css +1 -0
  6. package/dist/index.html +2 -2
  7. package/docs/ARCHITECTURE.md +450 -0
  8. package/docs/BACKEND_JOB_QUEUE.md +405 -0
  9. package/docs/CLAUDE_CODE_TELEMETRY.md +283 -0
  10. package/docs/CLI.md +431 -0
  11. package/docs/CODING_AGENT_ANALYTICS.md +298 -0
  12. package/docs/CONFIGURATION.md +388 -0
  13. package/docs/CONNECTORS.md +536 -0
  14. package/docs/INSTRUMENT_WITH_OTEL.md +390 -0
  15. package/docs/ML-COMMONS-SETUP.md +289 -0
  16. package/docs/NPX_PACKAGING.md +195 -0
  17. package/docs/PERFORMANCE-MONITORING.md +200 -0
  18. package/docs/PERFORMANCE.md +390 -0
  19. package/docs/PI_PROFILING.md +169 -0
  20. package/docs/PLAN-non-agui-agent-support.md +525 -0
  21. package/docs/SDK.md +577 -0
  22. package/docs/SKILLS.md +264 -0
  23. package/docs/blogs/2026-02-28-opensearch-agent-health.md +200 -0
  24. package/docs/blogs/getting-started-blog.md +608 -0
  25. package/docs/diagrams/Agent-health.excalidraw +5656 -0
  26. package/docs/diagrams/architecture.png +0 -0
  27. package/docs/plans/field-redesign.md +468 -0
  28. package/docs/rfcs/001-coding-agent-analytics.md +374 -0
  29. package/docs/rfcs/002-enterprise-leaderboard.md +267 -0
  30. package/docs/rfcs/003-remote-aggregation.md +146 -0
  31. package/docs/rfcs/004-test-sdk-v2.md +599 -0
  32. package/docs/skills/AGENT_HEALTH.md +598 -0
  33. package/docs/skills/AGENT_PROFILE.md +191 -0
  34. package/docs/skills/add-connector/SKILL.md +68 -0
  35. package/docs/skills/agent-health-profile/SKILL.md +40 -0
  36. package/docs/skills/config-auth/SKILL.md +194 -0
  37. package/docs/skills/config-auth/evals/evals.json +35 -0
  38. package/docs/skills/create-pr/SKILL.md +73 -0
  39. package/docs/skills/instrument-otel/SKILL.md +84 -0
  40. package/docs/skills/write-test/SKILL.md +124 -0
  41. package/docs/ui prd.md +376 -0
  42. package/examples/README.md +53 -0
  43. package/examples/config/agent-health.config.example.ts +155 -0
  44. package/examples/connectors/echo-connector.ts +131 -0
  45. package/examples/eval-files/demo.eval.js +128 -0
  46. package/examples/eval-files/sdk-hooks-demo.eval.js +99 -0
  47. package/examples/pi-profiling/README.md +77 -0
  48. package/examples/pi-profiling/agent-health-profile.ts +417 -0
  49. package/lib/dist/lib/agentUtils.d.ts +29 -0
  50. package/lib/dist/lib/agentUtils.d.ts.map +1 -0
  51. package/lib/dist/lib/agentUtils.js +43 -0
  52. package/lib/dist/lib/agentUtils.js.map +1 -0
  53. package/lib/dist/lib/benchmarkExport.d.ts +14 -0
  54. package/lib/dist/lib/benchmarkExport.d.ts.map +1 -0
  55. package/lib/dist/lib/benchmarkExport.js +41 -0
  56. package/lib/dist/lib/benchmarkExport.js.map +1 -0
  57. package/lib/dist/lib/benchmarkVersionUtils.d.ts +37 -0
  58. package/lib/dist/lib/benchmarkVersionUtils.d.ts.map +1 -0
  59. package/lib/dist/lib/benchmarkVersionUtils.js +68 -0
  60. package/lib/dist/lib/benchmarkVersionUtils.js.map +1 -0
  61. package/lib/dist/lib/config/defineConfig.d.ts +27 -0
  62. package/lib/dist/lib/config/defineConfig.d.ts.map +1 -0
  63. package/lib/dist/lib/config/defineConfig.js +28 -0
  64. package/lib/dist/lib/config/defineConfig.js.map +1 -0
  65. package/lib/dist/lib/config/index.d.ts +9 -0
  66. package/lib/dist/lib/config/index.d.ts.map +1 -0
  67. package/lib/dist/lib/config/index.js +8 -0
  68. package/lib/dist/lib/config/index.js.map +1 -0
  69. package/lib/dist/lib/config/loader.d.ts +39 -0
  70. package/lib/dist/lib/config/loader.d.ts.map +1 -0
  71. package/lib/dist/lib/config/loader.js +258 -0
  72. package/lib/dist/lib/config/loader.js.map +1 -0
  73. package/lib/dist/lib/config/statePaths.d.ts +61 -0
  74. package/lib/dist/lib/config/statePaths.d.ts.map +1 -0
  75. package/lib/dist/lib/config/statePaths.js +188 -0
  76. package/lib/dist/lib/config/statePaths.js.map +1 -0
  77. package/lib/dist/lib/config/types.d.ts +231 -0
  78. package/lib/dist/lib/config/types.d.ts.map +1 -0
  79. package/lib/dist/lib/config/types.js +6 -0
  80. package/lib/dist/lib/config/types.js.map +1 -0
  81. package/lib/dist/lib/config.d.ts +39 -0
  82. package/lib/dist/lib/config.d.ts.map +1 -0
  83. package/lib/dist/lib/config.js +118 -0
  84. package/lib/dist/lib/config.js.map +1 -0
  85. package/lib/dist/lib/constants.d.ts +70 -0
  86. package/lib/dist/lib/constants.d.ts.map +1 -0
  87. package/lib/dist/lib/constants.js +365 -0
  88. package/lib/dist/lib/constants.js.map +1 -0
  89. package/lib/dist/lib/contextUtilization.d.ts +23 -0
  90. package/lib/dist/lib/contextUtilization.d.ts.map +1 -0
  91. package/lib/dist/lib/contextUtilization.js +72 -0
  92. package/lib/dist/lib/contextUtilization.js.map +1 -0
  93. package/lib/dist/lib/dashboardMetrics.d.ts +87 -0
  94. package/lib/dist/lib/dashboardMetrics.d.ts.map +1 -0
  95. package/lib/dist/lib/dashboardMetrics.js +242 -0
  96. package/lib/dist/lib/dashboardMetrics.js.map +1 -0
  97. package/lib/dist/lib/dataSourceConfig.d.ts +108 -0
  98. package/lib/dist/lib/dataSourceConfig.d.ts.map +1 -0
  99. package/lib/dist/lib/dataSourceConfig.js +166 -0
  100. package/lib/dist/lib/dataSourceConfig.js.map +1 -0
  101. package/lib/dist/lib/debug.d.ts +26 -0
  102. package/lib/dist/lib/debug.d.ts.map +1 -0
  103. package/lib/dist/lib/debug.js +132 -0
  104. package/lib/dist/lib/debug.js.map +1 -0
  105. package/lib/dist/lib/diagnostics.d.ts +28 -0
  106. package/lib/dist/lib/diagnostics.d.ts.map +1 -0
  107. package/lib/dist/lib/diagnostics.js +65 -0
  108. package/lib/dist/lib/diagnostics.js.map +1 -0
  109. package/lib/dist/lib/envCompat.d.ts +27 -0
  110. package/lib/dist/lib/envCompat.d.ts.map +1 -0
  111. package/lib/dist/lib/envCompat.js +73 -0
  112. package/lib/dist/lib/envCompat.js.map +1 -0
  113. package/lib/dist/lib/findPackageRoot.d.ts +7 -0
  114. package/lib/dist/lib/findPackageRoot.d.ts.map +1 -0
  115. package/lib/dist/lib/findPackageRoot.js +57 -0
  116. package/lib/dist/lib/findPackageRoot.js.map +1 -0
  117. package/lib/dist/lib/hooks.d.ts +36 -0
  118. package/lib/dist/lib/hooks.d.ts.map +1 -0
  119. package/lib/dist/lib/hooks.js +112 -0
  120. package/lib/dist/lib/hooks.js.map +1 -0
  121. package/lib/dist/lib/index.d.ts +47 -0
  122. package/lib/dist/lib/index.d.ts.map +1 -0
  123. package/lib/dist/lib/index.js +62 -0
  124. package/lib/dist/lib/index.js.map +1 -0
  125. package/lib/dist/lib/labels.d.ts +90 -0
  126. package/lib/dist/lib/labels.d.ts.map +1 -0
  127. package/lib/dist/lib/labels.js +158 -0
  128. package/lib/dist/lib/labels.js.map +1 -0
  129. package/lib/dist/lib/markdown.d.ts +16 -0
  130. package/lib/dist/lib/markdown.d.ts.map +1 -0
  131. package/lib/dist/lib/markdown.js +42 -0
  132. package/lib/dist/lib/markdown.js.map +1 -0
  133. package/lib/dist/lib/matchers/expect.d.ts +3 -0
  134. package/lib/dist/lib/matchers/expect.d.ts.map +1 -0
  135. package/lib/dist/lib/matchers/expect.js +225 -0
  136. package/lib/dist/lib/matchers/expect.js.map +1 -0
  137. package/lib/dist/lib/matchers/index.d.ts +8 -0
  138. package/lib/dist/lib/matchers/index.d.ts.map +1 -0
  139. package/lib/dist/lib/matchers/index.js +9 -0
  140. package/lib/dist/lib/matchers/index.js.map +1 -0
  141. package/lib/dist/lib/matchers/judgeAccessor.d.ts +113 -0
  142. package/lib/dist/lib/matchers/judgeAccessor.d.ts.map +1 -0
  143. package/lib/dist/lib/matchers/judgeAccessor.js +183 -0
  144. package/lib/dist/lib/matchers/judgeAccessor.js.map +1 -0
  145. package/lib/dist/lib/matchers/session.d.ts +39 -0
  146. package/lib/dist/lib/matchers/session.d.ts.map +1 -0
  147. package/lib/dist/lib/matchers/session.js +116 -0
  148. package/lib/dist/lib/matchers/session.js.map +1 -0
  149. package/lib/dist/lib/matchers/traces.d.ts +55 -0
  150. package/lib/dist/lib/matchers/traces.d.ts.map +1 -0
  151. package/lib/dist/lib/matchers/traces.js +116 -0
  152. package/lib/dist/lib/matchers/traces.js.map +1 -0
  153. package/lib/dist/lib/matchers/types.d.ts +75 -0
  154. package/lib/dist/lib/matchers/types.d.ts.map +1 -0
  155. package/lib/dist/lib/matchers/types.js +6 -0
  156. package/lib/dist/lib/matchers/types.js.map +1 -0
  157. package/lib/dist/lib/packagePaths.d.ts +29 -0
  158. package/lib/dist/lib/packagePaths.d.ts.map +1 -0
  159. package/lib/dist/lib/packagePaths.js +63 -0
  160. package/lib/dist/lib/packagePaths.js.map +1 -0
  161. package/lib/dist/lib/performance.d.ts +51 -0
  162. package/lib/dist/lib/performance.d.ts.map +1 -0
  163. package/lib/dist/lib/performance.js +159 -0
  164. package/lib/dist/lib/performance.js.map +1 -0
  165. package/lib/dist/lib/portConfig.d.ts +29 -0
  166. package/lib/dist/lib/portConfig.d.ts.map +1 -0
  167. package/lib/dist/lib/portConfig.js +64 -0
  168. package/lib/dist/lib/portConfig.js.map +1 -0
  169. package/lib/dist/lib/preferences.d.ts +63 -0
  170. package/lib/dist/lib/preferences.d.ts.map +1 -0
  171. package/lib/dist/lib/preferences.js +117 -0
  172. package/lib/dist/lib/preferences.js.map +1 -0
  173. package/lib/dist/lib/resolveAgentModel.d.ts +22 -0
  174. package/lib/dist/lib/resolveAgentModel.d.ts.map +1 -0
  175. package/lib/dist/lib/resolveAgentModel.js +37 -0
  176. package/lib/dist/lib/resolveAgentModel.js.map +1 -0
  177. package/lib/dist/lib/runStats.d.ts +92 -0
  178. package/lib/dist/lib/runStats.d.ts.map +1 -0
  179. package/lib/dist/lib/runStats.js +160 -0
  180. package/lib/dist/lib/runStats.js.map +1 -0
  181. package/lib/dist/lib/telemetry/constants.d.ts +60 -0
  182. package/lib/dist/lib/telemetry/constants.d.ts.map +1 -0
  183. package/lib/dist/lib/telemetry/constants.js +87 -0
  184. package/lib/dist/lib/telemetry/constants.js.map +1 -0
  185. package/lib/dist/lib/telemetry/evalSpans.d.ts +61 -0
  186. package/lib/dist/lib/telemetry/evalSpans.d.ts.map +1 -0
  187. package/lib/dist/lib/telemetry/evalSpans.js +254 -0
  188. package/lib/dist/lib/telemetry/evalSpans.js.map +1 -0
  189. package/lib/dist/lib/telemetry/index.d.ts +11 -0
  190. package/lib/dist/lib/telemetry/index.d.ts.map +1 -0
  191. package/lib/dist/lib/telemetry/index.js +15 -0
  192. package/lib/dist/lib/telemetry/index.js.map +1 -0
  193. package/lib/dist/lib/telemetry/opensearchExporter.d.ts +43 -0
  194. package/lib/dist/lib/telemetry/opensearchExporter.d.ts.map +1 -0
  195. package/lib/dist/lib/telemetry/opensearchExporter.js +217 -0
  196. package/lib/dist/lib/telemetry/opensearchExporter.js.map +1 -0
  197. package/lib/dist/lib/telemetry/provider.d.ts +55 -0
  198. package/lib/dist/lib/telemetry/provider.d.ts.map +1 -0
  199. package/lib/dist/lib/telemetry/provider.js +140 -0
  200. package/lib/dist/lib/telemetry/provider.js.map +1 -0
  201. package/lib/dist/lib/testCaseLabels.d.ts +34 -0
  202. package/lib/dist/lib/testCaseLabels.d.ts.map +1 -0
  203. package/lib/dist/lib/testCaseLabels.js +88 -0
  204. package/lib/dist/lib/testCaseLabels.js.map +1 -0
  205. package/lib/dist/lib/testCaseValidation.d.ts +140 -0
  206. package/lib/dist/lib/testCaseValidation.d.ts.map +1 -0
  207. package/lib/dist/lib/testCaseValidation.js +162 -0
  208. package/lib/dist/lib/testCaseValidation.js.map +1 -0
  209. package/lib/dist/lib/testCases/agentFixture.d.ts +80 -0
  210. package/lib/dist/lib/testCases/agentFixture.d.ts.map +1 -0
  211. package/lib/dist/lib/testCases/agentFixture.js +43 -0
  212. package/lib/dist/lib/testCases/agentFixture.js.map +1 -0
  213. package/lib/dist/lib/testCases/authoringSurface.d.ts +10 -0
  214. package/lib/dist/lib/testCases/authoringSurface.d.ts.map +1 -0
  215. package/lib/dist/lib/testCases/authoringSurface.js +54 -0
  216. package/lib/dist/lib/testCases/authoringSurface.js.map +1 -0
  217. package/lib/dist/lib/testCases/codemod.d.ts +13 -0
  218. package/lib/dist/lib/testCases/codemod.d.ts.map +1 -0
  219. package/lib/dist/lib/testCases/codemod.js +169 -0
  220. package/lib/dist/lib/testCases/codemod.js.map +1 -0
  221. package/lib/dist/lib/testCases/define.d.ts +114 -0
  222. package/lib/dist/lib/testCases/define.d.ts.map +1 -0
  223. package/lib/dist/lib/testCases/define.js +253 -0
  224. package/lib/dist/lib/testCases/define.js.map +1 -0
  225. package/lib/dist/lib/testCases/evaluators.d.ts +80 -0
  226. package/lib/dist/lib/testCases/evaluators.d.ts.map +1 -0
  227. package/lib/dist/lib/testCases/evaluators.js +105 -0
  228. package/lib/dist/lib/testCases/evaluators.js.map +1 -0
  229. package/lib/dist/lib/testCases/index.d.ts +14 -0
  230. package/lib/dist/lib/testCases/index.d.ts.map +1 -0
  231. package/lib/dist/lib/testCases/index.js +12 -0
  232. package/lib/dist/lib/testCases/index.js.map +1 -0
  233. package/lib/dist/lib/testCases/judge.d.ts +165 -0
  234. package/lib/dist/lib/testCases/judge.d.ts.map +1 -0
  235. package/lib/dist/lib/testCases/judge.js +359 -0
  236. package/lib/dist/lib/testCases/judge.js.map +1 -0
  237. package/lib/dist/lib/testCases/loader.d.ts +26 -0
  238. package/lib/dist/lib/testCases/loader.d.ts.map +1 -0
  239. package/lib/dist/lib/testCases/loader.js +149 -0
  240. package/lib/dist/lib/testCases/loader.js.map +1 -0
  241. package/lib/dist/lib/testCases/types.d.ts +242 -0
  242. package/lib/dist/lib/testCases/types.d.ts.map +1 -0
  243. package/lib/dist/lib/testCases/types.js +6 -0
  244. package/lib/dist/lib/testCases/types.js.map +1 -0
  245. package/lib/dist/lib/theme.d.ts +6 -0
  246. package/lib/dist/lib/theme.d.ts.map +1 -0
  247. package/lib/dist/lib/theme.js +36 -0
  248. package/lib/dist/lib/theme.js.map +1 -0
  249. package/lib/dist/lib/uiTelemetry.d.ts +7 -0
  250. package/lib/dist/lib/uiTelemetry.d.ts.map +1 -0
  251. package/lib/dist/lib/uiTelemetry.js +25 -0
  252. package/lib/dist/lib/uiTelemetry.js.map +1 -0
  253. package/lib/dist/lib/utils.d.ts +96 -0
  254. package/lib/dist/lib/utils.d.ts.map +1 -0
  255. package/lib/dist/lib/utils.js +232 -0
  256. package/lib/dist/lib/utils.js.map +1 -0
  257. package/lib/dist/lib/workflow/consolidate.d.ts +12 -0
  258. package/lib/dist/lib/workflow/consolidate.d.ts.map +1 -0
  259. package/lib/dist/lib/workflow/consolidate.js +33 -0
  260. package/lib/dist/lib/workflow/consolidate.js.map +1 -0
  261. package/lib/dist/lib/workflow/index.d.ts +13 -0
  262. package/lib/dist/lib/workflow/index.d.ts.map +1 -0
  263. package/lib/dist/lib/workflow/index.js +12 -0
  264. package/lib/dist/lib/workflow/index.js.map +1 -0
  265. package/lib/dist/lib/workflow/ledger.d.ts +30 -0
  266. package/lib/dist/lib/workflow/ledger.d.ts.map +1 -0
  267. package/lib/dist/lib/workflow/ledger.js +41 -0
  268. package/lib/dist/lib/workflow/ledger.js.map +1 -0
  269. package/lib/dist/lib/workflow/pool.d.ts +13 -0
  270. package/lib/dist/lib/workflow/pool.d.ts.map +1 -0
  271. package/lib/dist/lib/workflow/pool.js +44 -0
  272. package/lib/dist/lib/workflow/pool.js.map +1 -0
  273. package/lib/dist/lib/workflow/source.d.ts +22 -0
  274. package/lib/dist/lib/workflow/source.d.ts.map +1 -0
  275. package/lib/dist/lib/workflow/source.js +29 -0
  276. package/lib/dist/lib/workflow/source.js.map +1 -0
  277. package/lib/dist/lib/workflow/stepB.d.ts +71 -0
  278. package/lib/dist/lib/workflow/stepB.d.ts.map +1 -0
  279. package/lib/dist/lib/workflow/stepB.js +99 -0
  280. package/lib/dist/lib/workflow/stepB.js.map +1 -0
  281. package/lib/dist/lib/workflow/types.d.ts +86 -0
  282. package/lib/dist/lib/workflow/types.d.ts.map +1 -0
  283. package/lib/dist/lib/workflow/types.js +6 -0
  284. package/lib/dist/lib/workflow/types.js.map +1 -0
  285. package/lib/dist/lib/workflow/workflow.d.ts +119 -0
  286. package/lib/dist/lib/workflow/workflow.d.ts.map +1 -0
  287. package/lib/dist/lib/workflow/workflow.js +195 -0
  288. package/lib/dist/lib/workflow/workflow.js.map +1 -0
  289. package/lib/dist/services/agent/aguiConverter.d.ts +50 -0
  290. package/lib/dist/services/agent/aguiConverter.d.ts.map +1 -0
  291. package/lib/dist/services/agent/aguiConverter.js +449 -0
  292. package/lib/dist/services/agent/aguiConverter.js.map +1 -0
  293. package/lib/dist/services/agent/index.d.ts +10 -0
  294. package/lib/dist/services/agent/index.d.ts.map +1 -0
  295. package/lib/dist/services/agent/index.js +12 -0
  296. package/lib/dist/services/agent/index.js.map +1 -0
  297. package/lib/dist/services/agent/payloadBuilder.d.ts +33 -0
  298. package/lib/dist/services/agent/payloadBuilder.d.ts.map +1 -0
  299. package/lib/dist/services/agent/payloadBuilder.js +75 -0
  300. package/lib/dist/services/agent/payloadBuilder.js.map +1 -0
  301. package/lib/dist/services/agent/sseStream.d.ts +43 -0
  302. package/lib/dist/services/agent/sseStream.d.ts.map +1 -0
  303. package/lib/dist/services/agent/sseStream.js +223 -0
  304. package/lib/dist/services/agent/sseStream.js.map +1 -0
  305. package/lib/dist/services/connectors/agui/AGUIStreamingConnector.d.ts +44 -0
  306. package/lib/dist/services/connectors/agui/AGUIStreamingConnector.d.ts.map +1 -0
  307. package/lib/dist/services/connectors/agui/AGUIStreamingConnector.js +95 -0
  308. package/lib/dist/services/connectors/agui/AGUIStreamingConnector.js.map +1 -0
  309. package/lib/dist/services/connectors/base/BaseConnector.d.ts +81 -0
  310. package/lib/dist/services/connectors/base/BaseConnector.d.ts.map +1 -0
  311. package/lib/dist/services/connectors/base/BaseConnector.js +170 -0
  312. package/lib/dist/services/connectors/base/BaseConnector.js.map +1 -0
  313. package/lib/dist/services/connectors/claude-code/ClaudeCodeConnector.d.ts +116 -0
  314. package/lib/dist/services/connectors/claude-code/ClaudeCodeConnector.d.ts.map +1 -0
  315. package/lib/dist/services/connectors/claude-code/ClaudeCodeConnector.js +403 -0
  316. package/lib/dist/services/connectors/claude-code/ClaudeCodeConnector.js.map +1 -0
  317. package/lib/dist/services/connectors/index.d.ts +13 -0
  318. package/lib/dist/services/connectors/index.d.ts.map +1 -0
  319. package/lib/dist/services/connectors/index.js +32 -0
  320. package/lib/dist/services/connectors/index.js.map +1 -0
  321. package/lib/dist/services/connectors/kiro/KiroConnector.d.ts +48 -0
  322. package/lib/dist/services/connectors/kiro/KiroConnector.d.ts.map +1 -0
  323. package/lib/dist/services/connectors/kiro/KiroConnector.js +158 -0
  324. package/lib/dist/services/connectors/kiro/KiroConnector.js.map +1 -0
  325. package/lib/dist/services/connectors/langgraph/LangGraphConnector.d.ts +36 -0
  326. package/lib/dist/services/connectors/langgraph/LangGraphConnector.d.ts.map +1 -0
  327. package/lib/dist/services/connectors/langgraph/LangGraphConnector.js +175 -0
  328. package/lib/dist/services/connectors/langgraph/LangGraphConnector.js.map +1 -0
  329. package/lib/dist/services/connectors/mock/MockConnector.d.ts +37 -0
  330. package/lib/dist/services/connectors/mock/MockConnector.d.ts.map +1 -0
  331. package/lib/dist/services/connectors/mock/MockConnector.js +120 -0
  332. package/lib/dist/services/connectors/mock/MockConnector.js.map +1 -0
  333. package/lib/dist/services/connectors/openai-compatible/OpenAICompatibleConnector.d.ts +42 -0
  334. package/lib/dist/services/connectors/openai-compatible/OpenAICompatibleConnector.d.ts.map +1 -0
  335. package/lib/dist/services/connectors/openai-compatible/OpenAICompatibleConnector.js +133 -0
  336. package/lib/dist/services/connectors/openai-compatible/OpenAICompatibleConnector.js.map +1 -0
  337. package/lib/dist/services/connectors/pi/PiConnector.d.ts +87 -0
  338. package/lib/dist/services/connectors/pi/PiConnector.d.ts.map +1 -0
  339. package/lib/dist/services/connectors/pi/PiConnector.js +274 -0
  340. package/lib/dist/services/connectors/pi/PiConnector.js.map +1 -0
  341. package/lib/dist/services/connectors/registry.d.ts +57 -0
  342. package/lib/dist/services/connectors/registry.d.ts.map +1 -0
  343. package/lib/dist/services/connectors/registry.js +106 -0
  344. package/lib/dist/services/connectors/registry.js.map +1 -0
  345. package/lib/dist/services/connectors/rest/RESTConnector.d.ts +38 -0
  346. package/lib/dist/services/connectors/rest/RESTConnector.d.ts.map +1 -0
  347. package/lib/dist/services/connectors/rest/RESTConnector.js +117 -0
  348. package/lib/dist/services/connectors/rest/RESTConnector.js.map +1 -0
  349. package/lib/dist/services/connectors/server.d.ts +13 -0
  350. package/lib/dist/services/connectors/server.d.ts.map +1 -0
  351. package/lib/dist/services/connectors/server.js +34 -0
  352. package/lib/dist/services/connectors/server.js.map +1 -0
  353. package/lib/dist/services/connectors/strands/StrandsConnector.d.ts +48 -0
  354. package/lib/dist/services/connectors/strands/StrandsConnector.d.ts.map +1 -0
  355. package/lib/dist/services/connectors/strands/StrandsConnector.js +221 -0
  356. package/lib/dist/services/connectors/strands/StrandsConnector.js.map +1 -0
  357. package/lib/dist/services/connectors/subprocess/SubprocessConnector.d.ts +88 -0
  358. package/lib/dist/services/connectors/subprocess/SubprocessConnector.d.ts.map +1 -0
  359. package/lib/dist/services/connectors/subprocess/SubprocessConnector.js +418 -0
  360. package/lib/dist/services/connectors/subprocess/SubprocessConnector.js.map +1 -0
  361. package/lib/dist/services/connectors/types.d.ts +213 -0
  362. package/lib/dist/services/connectors/types.d.ts.map +1 -0
  363. package/lib/dist/services/connectors/types.js +6 -0
  364. package/lib/dist/services/connectors/types.js.map +1 -0
  365. package/lib/dist/services/evaluation/bedrockJudge.d.ts +64 -0
  366. package/lib/dist/services/evaluation/bedrockJudge.d.ts.map +1 -0
  367. package/lib/dist/services/evaluation/bedrockJudge.js +167 -0
  368. package/lib/dist/services/evaluation/bedrockJudge.js.map +1 -0
  369. package/lib/dist/services/evaluation/evaluatorError.d.ts +56 -0
  370. package/lib/dist/services/evaluation/evaluatorError.d.ts.map +1 -0
  371. package/lib/dist/services/evaluation/evaluatorError.js +56 -0
  372. package/lib/dist/services/evaluation/evaluatorError.js.map +1 -0
  373. package/lib/dist/services/evaluation/index.d.ts +106 -0
  374. package/lib/dist/services/evaluation/index.d.ts.map +1 -0
  375. package/lib/dist/services/evaluation/index.js +684 -0
  376. package/lib/dist/services/evaluation/index.js.map +1 -0
  377. package/lib/dist/services/evaluation/mockTrajectory.d.ts +3 -0
  378. package/lib/dist/services/evaluation/mockTrajectory.d.ts.map +1 -0
  379. package/lib/dist/services/evaluation/mockTrajectory.js +72 -0
  380. package/lib/dist/services/evaluation/mockTrajectory.js.map +1 -0
  381. package/lib/dist/services/opensearch/client.d.ts +26 -0
  382. package/lib/dist/services/opensearch/client.d.ts.map +1 -0
  383. package/lib/dist/services/opensearch/client.js +131 -0
  384. package/lib/dist/services/opensearch/client.js.map +1 -0
  385. package/lib/dist/services/opensearch/index.d.ts +16 -0
  386. package/lib/dist/services/opensearch/index.d.ts.map +1 -0
  387. package/lib/dist/services/opensearch/index.js +25 -0
  388. package/lib/dist/services/opensearch/index.js.map +1 -0
  389. package/lib/dist/services/storage/asyncBenchmarkStorage.d.ts +123 -0
  390. package/lib/dist/services/storage/asyncBenchmarkStorage.d.ts.map +1 -0
  391. package/lib/dist/services/storage/asyncBenchmarkStorage.js +429 -0
  392. package/lib/dist/services/storage/asyncBenchmarkStorage.js.map +1 -0
  393. package/lib/dist/services/storage/asyncRunStorage.d.ts +127 -0
  394. package/lib/dist/services/storage/asyncRunStorage.d.ts.map +1 -0
  395. package/lib/dist/services/storage/asyncRunStorage.js +448 -0
  396. package/lib/dist/services/storage/asyncRunStorage.js.map +1 -0
  397. package/lib/dist/services/storage/asyncTestCaseStorage.d.ts +156 -0
  398. package/lib/dist/services/storage/asyncTestCaseStorage.d.ts.map +1 -0
  399. package/lib/dist/services/storage/asyncTestCaseStorage.js +285 -0
  400. package/lib/dist/services/storage/asyncTestCaseStorage.js.map +1 -0
  401. package/lib/dist/services/storage/index.d.ts +17 -0
  402. package/lib/dist/services/storage/index.d.ts.map +1 -0
  403. package/lib/dist/services/storage/index.js +20 -0
  404. package/lib/dist/services/storage/index.js.map +1 -0
  405. package/lib/dist/services/storage/migration.d.ts +54 -0
  406. package/lib/dist/services/storage/migration.d.ts.map +1 -0
  407. package/lib/dist/services/storage/migration.js +296 -0
  408. package/lib/dist/services/storage/migration.js.map +1 -0
  409. package/lib/dist/services/storage/opensearchClient.d.ts +924 -0
  410. package/lib/dist/services/storage/opensearchClient.d.ts.map +1 -0
  411. package/lib/dist/services/storage/opensearchClient.js +435 -0
  412. package/lib/dist/services/storage/opensearchClient.js.map +1 -0
  413. package/lib/dist/services/traces/browserRecovery.d.ts +26 -0
  414. package/lib/dist/services/traces/browserRecovery.d.ts.map +1 -0
  415. package/lib/dist/services/traces/browserRecovery.js +81 -0
  416. package/lib/dist/services/traces/browserRecovery.js.map +1 -0
  417. package/lib/dist/services/traces/categoryStyles.d.ts +21 -0
  418. package/lib/dist/services/traces/categoryStyles.d.ts.map +1 -0
  419. package/lib/dist/services/traces/categoryStyles.js +56 -0
  420. package/lib/dist/services/traces/categoryStyles.js.map +1 -0
  421. package/lib/dist/services/traces/executionOrderTransform.d.ts +35 -0
  422. package/lib/dist/services/traces/executionOrderTransform.d.ts.map +1 -0
  423. package/lib/dist/services/traces/executionOrderTransform.js +313 -0
  424. package/lib/dist/services/traces/executionOrderTransform.js.map +1 -0
  425. package/lib/dist/services/traces/fetchSpansForRun.d.ts +86 -0
  426. package/lib/dist/services/traces/fetchSpansForRun.d.ts.map +1 -0
  427. package/lib/dist/services/traces/fetchSpansForRun.js +69 -0
  428. package/lib/dist/services/traces/fetchSpansForRun.js.map +1 -0
  429. package/lib/dist/services/traces/flowTransform.d.ts +24 -0
  430. package/lib/dist/services/traces/flowTransform.d.ts.map +1 -0
  431. package/lib/dist/services/traces/flowTransform.js +228 -0
  432. package/lib/dist/services/traces/flowTransform.js.map +1 -0
  433. package/lib/dist/services/traces/index.d.ts +121 -0
  434. package/lib/dist/services/traces/index.d.ts.map +1 -0
  435. package/lib/dist/services/traces/index.js +255 -0
  436. package/lib/dist/services/traces/index.js.map +1 -0
  437. package/lib/dist/services/traces/intentTransform.d.ts +20 -0
  438. package/lib/dist/services/traces/intentTransform.d.ts.map +1 -0
  439. package/lib/dist/services/traces/intentTransform.js +131 -0
  440. package/lib/dist/services/traces/intentTransform.js.map +1 -0
  441. package/lib/dist/services/traces/judgeAgentsHints.d.ts +63 -0
  442. package/lib/dist/services/traces/judgeAgentsHints.d.ts.map +1 -0
  443. package/lib/dist/services/traces/judgeAgentsHints.js +89 -0
  444. package/lib/dist/services/traces/judgeAgentsHints.js.map +1 -0
  445. package/lib/dist/services/traces/messageExtraction.d.ts +15 -0
  446. package/lib/dist/services/traces/messageExtraction.d.ts.map +1 -0
  447. package/lib/dist/services/traces/messageExtraction.js +251 -0
  448. package/lib/dist/services/traces/messageExtraction.js.map +1 -0
  449. package/lib/dist/services/traces/spanCategorization.d.ts +63 -0
  450. package/lib/dist/services/traces/spanCategorization.d.ts.map +1 -0
  451. package/lib/dist/services/traces/spanCategorization.js +276 -0
  452. package/lib/dist/services/traces/spanCategorization.js.map +1 -0
  453. package/lib/dist/services/traces/spanPreprocessing.d.ts +37 -0
  454. package/lib/dist/services/traces/spanPreprocessing.d.ts.map +1 -0
  455. package/lib/dist/services/traces/spanPreprocessing.js +102 -0
  456. package/lib/dist/services/traces/spanPreprocessing.js.map +1 -0
  457. package/lib/dist/services/traces/spansToTrajectory.d.ts +36 -0
  458. package/lib/dist/services/traces/spansToTrajectory.d.ts.map +1 -0
  459. package/lib/dist/services/traces/spansToTrajectory.js +387 -0
  460. package/lib/dist/services/traces/spansToTrajectory.js.map +1 -0
  461. package/lib/dist/services/traces/toolSimilarity.d.ts +35 -0
  462. package/lib/dist/services/traces/toolSimilarity.d.ts.map +1 -0
  463. package/lib/dist/services/traces/toolSimilarity.js +203 -0
  464. package/lib/dist/services/traces/toolSimilarity.js.map +1 -0
  465. package/lib/dist/services/traces/traceComparison.d.ts +31 -0
  466. package/lib/dist/services/traces/traceComparison.d.ts.map +1 -0
  467. package/lib/dist/services/traces/traceComparison.js +318 -0
  468. package/lib/dist/services/traces/traceComparison.js.map +1 -0
  469. package/lib/dist/services/traces/traceGrouping.d.ts +19 -0
  470. package/lib/dist/services/traces/traceGrouping.d.ts.map +1 -0
  471. package/lib/dist/services/traces/traceGrouping.js +107 -0
  472. package/lib/dist/services/traces/traceGrouping.js.map +1 -0
  473. package/lib/dist/services/traces/tracePoller.d.ts +84 -0
  474. package/lib/dist/services/traces/tracePoller.d.ts.map +1 -0
  475. package/lib/dist/services/traces/tracePoller.js +309 -0
  476. package/lib/dist/services/traces/tracePoller.js.map +1 -0
  477. package/lib/dist/services/traces/traceStats.d.ts +45 -0
  478. package/lib/dist/services/traces/traceStats.d.ts.map +1 -0
  479. package/lib/dist/services/traces/traceStats.js +114 -0
  480. package/lib/dist/services/traces/traceStats.js.map +1 -0
  481. package/lib/dist/services/traces/traceSummary.d.ts +47 -0
  482. package/lib/dist/services/traces/traceSummary.d.ts.map +1 -0
  483. package/lib/dist/services/traces/traceSummary.js +68 -0
  484. package/lib/dist/services/traces/traceSummary.js.map +1 -0
  485. package/lib/dist/services/traces/utils.d.ts +33 -0
  486. package/lib/dist/services/traces/utils.d.ts.map +1 -0
  487. package/lib/dist/services/traces/utils.js +114 -0
  488. package/lib/dist/services/traces/utils.js.map +1 -0
  489. package/lib/dist/types/agui.d.ts +13 -0
  490. package/lib/dist/types/agui.d.ts.map +1 -0
  491. package/lib/dist/types/agui.js +16 -0
  492. package/lib/dist/types/agui.js.map +1 -0
  493. package/lib/dist/types/index.d.ts +1175 -0
  494. package/lib/dist/types/index.d.ts.map +1 -0
  495. package/lib/dist/types/index.js +12 -0
  496. package/lib/dist/types/index.js.map +1 -0
  497. package/lib/dist/types/skills.d.ts +146 -0
  498. package/lib/dist/types/skills.d.ts.map +1 -0
  499. package/lib/dist/types/skills.js +6 -0
  500. package/lib/dist/types/skills.js.map +1 -0
  501. package/observio-sample-agent/pi-package/README.md +112 -0
  502. package/observio-sample-agent/pi-package/extensions/agent-health.ts +373 -0
  503. package/observio-sample-agent/pi-package/package.json +17 -0
  504. package/observio-sample-agent/pi-package/prompts/agent-health.md +37 -0
  505. package/observio-sample-agent/pi-package/skills/create-pr/SKILL.md +88 -0
  506. package/observio-sample-agent/pi-package/skills/fix-bug/SKILL.md +71 -0
  507. package/observio-sample-agent/pi-package/skills/implement-feature/SKILL.md +156 -0
  508. package/observio-sample-agent/pi-package/skills/instrument-otel/SKILL.md +208 -0
  509. package/observio-sample-agent/pi-package/skills/setup-collector/SKILL.md +146 -0
  510. package/observio-sample-agent/pi-package/skills/write-test/SKILL.md +115 -0
  511. package/package.json +64 -13
  512. package/server/dist/app.js +32651 -17637
  513. package/server/dist/index.js +29875 -14638
  514. package/tsconfig.lib.json +71 -0
  515. package/dist/assets/index-EvPLSTAS.js +0 -267
  516. package/dist/assets/index-RXasQKUs.css +0 -1
  517. package/lib/dist/config/index.js +0 -404
  518. package/lib/dist/index.js +0 -1665
@@ -0,0 +1,599 @@
1
+ <!--
2
+ * Copyright OpenSearch Contributors
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ -->
5
+
6
+ # RFC 004: Test SDK v2 — a real Playwright-style harness
7
+
8
+ | Field | Value |
9
+ |--------------|--------------------------------------------------------------------|
10
+ | **Status** | Draft |
11
+ | **Author** | Ashwin P Chandran |
12
+ | **Created** | 2026-06-05 |
13
+ | **Tracking** | [#256](https://github.com/opensearch-project/agent-health/issues/256) |
14
+ | **Supersedes** | the experimental `test()/judge()/expect()` SDK (#207, #198) |
15
+ | **Resolves** | #245, #243, #242, #244, #238, #248, #230, #232, #246 |
16
+
17
+ ## 1. TL;DR
18
+
19
+ The current SDK is *"a thin layer over the existing UI/JSON execution path"* (its own words, #238). That single decision — keep the framework in charge of invoking the agent **eagerly**, and only call the user's body afterwards to *assert on* a result — is the root cause of almost every bug filed against it.
20
+
21
+ The fix is one idea, applied without compromise: **invert control.** The test body owns execution. The framework supplies *capabilities* (an `agent` fixture, `expect`, `judge`, `traces`) exactly the way Playwright supplies `page` — and the body decides when and how to use them. There is **one** execution engine behind both the CLI and the server, **one** grading surface, and **no** global mutable state.
22
+
23
+ This collapses the current "two runners × two entry points × two grading systems × eager invocation × global session" combinatorial mess into a single linear pipeline.
24
+
25
+ ---
26
+
27
+ ## 2. Why the current SDK fails (one root cause, many symptoms)
28
+
29
+ The current execution model (verified in `services/evaluationRunner.ts`, `services/benchmarkRunner.ts`, `lib/testCases/*`, `server/routes/storage/benchmarks.ts`):
30
+
31
+ ```
32
+ connector.buildPayload(prompt, context) ← framework reads test() options
33
+ → invoke agent ← framework, eagerly, before any user code
34
+ → buildEvalResult(...) ← framework
35
+ → startSession() [GLOBAL singleton] ← framework
36
+ → evalFn({ result, judge, traces }) ← user body runs LAST, only asserts
37
+ → endSession() ← framework collects verdicts
38
+ ```
39
+
40
+ Five structural problems fall out of this:
41
+
42
+ | # | Structural flaw | Symptoms it causes |
43
+ |---|-----------------|--------------------|
44
+ | A | **Eager, framework-owned invocation.** The body runs *after* the agent, so it can never influence the prompt, seed state first, invoke twice, or do multi-turn. | #238 (no `invoke`), #248 (hooks run after the agent, so "seed a resource the agent reads" can't work) |
45
+ | B | **Two entry points that don't share an execution path.** The server runs (`evaluation-runs`, `benchmarks` routes) re-import the file to materialize bodies into an `evaluateFnMap` and *do* run them. `agent-health benchmark -f` imports only the test-case **data** and grades server-side. So whether your body runs depends on how you launched it. | #245 (body never runs under `benchmark -f`) — the headline value prop is silently absent half the time |
46
+ | C | **Two grading systems competing.** The body's `expect()/judge()` vs. a server-side evaluator that needs `expectedOutcomes`. They collide: the eager path tries the server judge unless `skipJudge` is threaded through; inline `judge()` criteria never populate `expectedOutcomes`. | #243 (inline criteria ignored), #242 (validation error silently recorded as `completed`, `metrics: 0`, misleading "Waiting for traces…") |
47
+ | D | **The framework must anticipate every capability.** Because the body can't *do* anything, the framework has to pre-load it. Traces are fetched by the runner before the body; when it can't, it returns silent zeros. | #230 (traces fixture silently returns 0 → `lessThan(N)` matchers falsely pass) |
48
+ | E | **Global mutable session + duplicated runners.** `lib/matchers/session.ts` holds a single module-level `activeSession`; `recordVerdict` pushes into it with no per-test isolation. Under `concurrency > 1`, interleaved bodies cross-contaminate verdicts. And the ~130-line body-execution block is **copy-pasted** between `evaluationRunner.ts` and `benchmarkRunner.ts`. | Latent: wrong matcher attribution under parallelism. #246: regressions land in one runner but not the other. |
49
+
50
+ Plus the loader's module-instance fragility: `.js` files get a hand-built `require` shim that injects the framework's `test()`, but `.ts/.mjs` go through real `import` and can bind a *different* instance of the SDK module → tests register into a registry nobody reads → "has no test cases" (#232).
51
+
52
+ **Every one of these is downstream of "the framework drives, the body observes."** Patching them one by one (which is what #229/#234/#241 have been doing) keeps re-introducing the next variant. We should fix the model once.
53
+
54
+ ---
55
+
56
+ ## 3. Design principles (what Playwright actually teaches)
57
+
58
+ #198 quoted the right lesson but the implementation didn't follow it. Playwright's power is **not** its matchers — it's that it *drives a non-deterministic system and hands you stable primitives to observe it*, and **it does not act on your behalf**:
59
+
60
+ ```ts
61
+ test('checkout', async ({ page }) => {
62
+ await page.goto('/cart'); // YOU drive
63
+ await expect(page.locator(...)) // YOU observe, scoped to a real handle
64
+ });
65
+ ```
66
+
67
+ There is no "Playwright reads a `url:` field off your test and navigates before your body." The navigation *is* the test. Translating faithfully:
68
+
69
+ 1. **The body is the test.** No code runs "before the body" that the body can't see or control.
70
+ 2. **The agent is a fixture you call**, not a step the framework performs. `await agent.run(...)` is `await page.goto(...)`.
71
+ 3. **Matchers are scoped to a result handle**, never to ambient global state. `expect(result)` / `judge(result, …)` operate on the thing you got back, the way `expect(locator)` operates on a locator.
72
+ 4. **One engine. Two transports.** Like `playwright test` (CLI) and the VS Code extension both run the *same* runner, our CLI and server must call one engine and differ only in how they *report*.
73
+ 5. **Declarative metadata is discovery-only.** Playwright can list tests without running them via static `test(name, …)` registration. Keep that — but it feeds *discovery and the UI*, never a hidden execution step.
74
+
75
+ ---
76
+
77
+ ## 4. The v2 model
78
+
79
+ ### 4.1 Control inversion — the `agent` fixture
80
+
81
+ ```js
82
+ const { test, expect } = require('@opensearch-project/agent-health');
83
+
84
+ test('payment RCA identifies the DB outage', {
85
+ labels: ['category:RCA', 'difficulty:Hard'],
86
+ }, async ({ agent, expect, judge }) => {
87
+ // YOU drive the agent — like page.goto()
88
+ const result = await agent.run('Diagnose why the payment service is failing.', {
89
+ context: [{ description: 'Error log', value: 'Connection refused to db-primary:5432' }],
90
+ });
91
+
92
+ // Deterministic, cheap checks first (scoped to *this* result)
93
+ expect(result).toHaveCalledTool('search_logs');
94
+ expect(result.output).toMatch(/payment[- ]service/i);
95
+ expect(result).toCompleteWithin(120_000);
96
+
97
+ // LLM judge only for the semantic part — scoped to this result, inline criteria
98
+ await judge(result, 'identifies the payment-service cannot reach its database');
99
+ });
100
+ ```
101
+
102
+ `agent.run()` returns a fully-captured, **immutable** `RunResult`:
103
+
104
+ ```ts
105
+ interface RunResult {
106
+ trajectory: Trajectory; // array + sugar (toolCalls(), firstToolCall(), stepsOfType())
107
+ output: string; // final assistant text (was agentOutput)
108
+ json<T = unknown>(): T | undefined;
109
+ runId: string;
110
+ durationMs: number;
111
+ tokenUsage?: { prompt: number; completion: number; total: number };
112
+ traces: TracesView; // lazily resolved from THIS run's runId (see 4.4)
113
+ raw: AgUiEvent[];
114
+ }
115
+ ```
116
+
117
+ This is the capability the framework owns end-to-end (connector wiring, trajectory capture, `runId` correlation, timeout, trace association). The *user* owns *when* and *with what* to call it.
118
+
119
+ **Exactly one `agent.run()` per test (enforced).** The goal is to *benchmark*
120
+ an agent: one test ⇒ one invocation ⇒ one trajectory ⇒ one comparable data
121
+ point. A second `agent.run()` call throws. This keeps trajectories comparable
122
+ across runs, keeps hashing/dedup clean, and removes any "which run do we
123
+ report?" ambiguity. The valuable #238 cases survive — *computed prompt* and
124
+ *pre-invocation setup* both need only a single call:
125
+
126
+ ```js
127
+ // computed prompt + pre-invocation setup — impossible today (#238 case 1), one run
128
+ const id = await seedTicket({ status: 'open' });
129
+ const r = await agent.run(`Triage ticket ${id}`);
130
+ ```
131
+
132
+ Multi-turn conversations, if a connector models them, happen *inside* that single
133
+ `run()` (the connector owns the turns); they are not multiple harness
134
+ invocations. "Multiple/conditional invocations" (#238 cases 2–4) are
135
+ intentionally **out of scope** — they're at odds with benchmarking.
136
+
137
+ **How `agent.run()` is implemented (no new transport).** It wraps the existing
138
+ connector stack — AG-UI streaming, REST, subprocess, Claude Code all work
139
+ unchanged. Today `runEvaluationWithConnector()` mashes three things together
140
+ (resolve+invoke the connector, run the Bedrock judge, synthesize a report). We
141
+ split out the pure invocation core:
142
+
143
+ ```ts
144
+ // services/sdkEngine.ts — resolved ONCE per run; the `agent` fixture closes over it.
145
+ interface AgentRuntime {
146
+ agentConfig: AgentConfig; // from agentKey
147
+ connector: AgentConnector; // connectorRegistry.getForAgent(...)
148
+ auth: ConnectorAuth; // buildConnectorAuth(agentConfig)
149
+ modelId: string;
150
+ }
151
+
152
+ // the one genuinely new primitive — pure invocation, NO judge, NO report synthesis
153
+ async function invokeAgent(rt: AgentRuntime, prompt: string, opts): Promise<RunResult> {
154
+ const testCase = synthesizeTestCase(prompt, opts.context); // ad-hoc TestCase
155
+ const request: ConnectorRequest = {
156
+ testCase, modelId: rt.modelId,
157
+ connectorConfig: rt.agentConfig.connectorConfig,
158
+ };
159
+ // reuse the existing beforeRequest / afterResponse hook wiring verbatim
160
+ const resp = await rt.connector.execute(endpoint, request, rt.auth, onStep, onRawEvent);
161
+ return wrapRunResult(resp); // sugar accessors + lazy traces bound to resp.runId
162
+ }
163
+ ```
164
+
165
+ - `agent.run(prompt, opts)` is exactly **one** `invokeAgent` call; a second call
166
+ throws (see above).
167
+ - The only new code is the `RunResult` wrapper plus pulling runtime resolution
168
+ (currently duplicated across both runners) into one place.
169
+ `runEvaluationWithConnector()` becomes `invokeAgent()` + the legacy
170
+ judge/report glue, and is deleted once v1 is removed.
171
+
172
+ ### 4.2 `test()` keeps declarative metadata, drops eager invocation
173
+
174
+ `test(name, options?, body)` is unchanged in *shape*, but `options` is now **purely metadata + defaults** — never an instruction to invoke:
175
+
176
+ ```ts
177
+ interface TestOptions {
178
+ description?: string;
179
+ labels?: string[]; // 'category:RCA', 'difficulty:Hard', free-form
180
+ timeout?: number;
181
+ defaultPrompt?: string; // optional: discovery/UI display + agent.run() default
182
+ context?: ContextItem[]; // optional default context for agent.run()
183
+ expectedOutcomes?: string[]; // optional: for non-code consumers / round-trip JSON
184
+ expectedTrajectory?: ExpectedStep[];
185
+ }
186
+ ```
187
+
188
+ - **No prompt anywhere?** The body simply never calls `agent.run()` — a pure data test. No special "skipAgentInvocation" branch in the runner; it's just a body that didn't use the fixture.
189
+ - **`defaultPrompt` set?** Discovery shows it; `agent.run()` with no args uses it. The agent is still only invoked *if the body calls `agent.run()`*. There is exactly one way the agent runs.
190
+
191
+ There is **no eager-invocation compatibility path** in the engine — v1 is removed
192
+ outright (§8), so the engine has exactly one code path.
193
+
194
+ ### 4.3 Result-scoped matchers (kills the global session)
195
+
196
+ Matchers attach to the result, not to a hidden global `activeSession`. The recording context is the `RunResult` (and, for non-agent assertions, the per-test context object the engine passes in):
197
+
198
+ ```js
199
+ expect(result).toHaveCalledTool('search_logs'); // records onto result's test context
200
+ await judge(result, 'criteria'); // records onto result's test context
201
+ expect(result.traces.totalTokens).toBeLessThan(10_000);
202
+ ```
203
+
204
+ Implementation: the engine creates **one `TestContext` per test invocation** (not a module global) and threads it through fixtures. `expect`/`judge`/`traces` close over that context. `lib/matchers/session.ts`'s module-level `activeSession` is deleted. This makes `concurrency > 1` correct by construction — there is no shared mutable cell to corrupt.
205
+
206
+ ### 4.4 `traces` is lazy and honest (#230)
207
+
208
+ `result.traces` is resolved from *that run's* `runId` on first access, not pre-loaded blindly:
209
+
210
+ - `useTraces` on the agent + traces available → real OTel data.
211
+ - `useTraces` true but none found yet → **awaits** (bounded) then **throws a clear error** on a `traces.*` read, never silent zeros.
212
+ - `useTraces` false → accessing `traces.*` throws `"traces not enabled for agent X; set useTraces:true"`.
213
+
214
+ Because the body drives, `await result.traces.totalTokens` can legitimately *wait* for the exporter — the framework no longer has to guess a pre-load window.
215
+
216
+ ### 4.5 Lifecycle via fixtures + `use` (and hooks that actually wrap the agent — #248)
217
+
218
+ Two complementary mechanisms, both Playwright-faithful:
219
+
220
+ **(a) Fixtures with teardown** — the composable primitive. A fixture is setup + teardown in one place, and because the body calls `agent.run()` *after* fixtures resolve, setup is guaranteed to land **before** the agent (fixing #248 by construction):
221
+
222
+ ```js
223
+ const { test } = require('@opensearch-project/agent-health');
224
+
225
+ const it = test.extend({
226
+ // value fixture with teardown via `use`
227
+ workspace: async ({}, use) => {
228
+ const dir = await fs.mkdtemp('/tmp/eval-');
229
+ await use(dir); // body + agent.run run here
230
+ await fs.rm(dir, { recursive: true, force: true }); // always-after teardown
231
+ },
232
+ });
233
+
234
+ it('reads the workspace', async ({ agent, workspace }) => {
235
+ await fs.writeFile(`${workspace}/input.json`, '...'); // BEFORE the agent
236
+ const r = await agent.run('Process input.json', { env: { WORKSPACE: workspace } });
237
+ expect(r.output).toContain('done');
238
+ });
239
+ ```
240
+
241
+ **(b) `beforeEach/afterEach/beforeAll/afterAll`** stay for familiarity, but they execute **inside the engine, around the body** — which now contains the agent call — so they wrap the agent automatically. No env-var smuggling, no `result: {} as any` lie (#248's three coupled changes evaporate because the agent call is *in* the body).
242
+
243
+ ### 4.6 Connector access from fixtures (provisioning that the agent can read)
244
+
245
+ `agent.run({ env, files, provisioned })` passes structured values straight into the connector's `buildPayload`. This is the type-safe option from #248 §2, made trivial because the body — which has the fixture values in scope — is the caller. No orchestrator-stringifies-into-env hack required (it remains available as `env:` sugar).
246
+
247
+ ### 4.7 The judge — a pluggable, run-scoped evaluator
248
+
249
+ Today `judge(result, claim)` posts only `{trajectory, expectedOutcomes:[claim]}` —
250
+ no traces, no `runId`, no tools, no model choice — and the server gates on
251
+ `expectedOutcomes`. That can't express either of the two judging styles real
252
+ evals need: *(a)* a cheap contextual LLM call, or *(b)* a judge that actively
253
+ **queries the run's trace data** (OpenSearch) to reach a verdict.
254
+
255
+ The fix builds on machinery that already exists: `server/services/agenticJudgeService.ts`
256
+ can already run a tool-using agent judge; it's just not wired to trace data.
257
+ Control inversion helps here too — `judge(result, …)` is scoped to a concrete
258
+ `RunResult` carrying a `runId`, so trace-querying is well-defined per run.
259
+
260
+ **Ergonomic forms:**
261
+
262
+ ```js
263
+ // (a) simple — single contextual LLM call
264
+ await judge(result, 'identifies the DB outage as the root cause');
265
+
266
+ // (b) rich — control context, give the judge tools, pick the model/evaluator
267
+ await judge(result, {
268
+ criteria: 'the remediation is grounded in the actual trace data, not hallucinated',
269
+ context: ['trajectory', 'output', 'traces', 'logs'], // what the judge sees
270
+ tools: ['trace_query'], // judge becomes agentic; can query OpenSearch
271
+ evaluator: 'my-trace-judge', // or: model: 'claude-sonnet'
272
+ });
273
+ ```
274
+
275
+ **Three execution modes, one `Verdict` shape:**
276
+
277
+ | Mode | Trigger | How it runs |
278
+ |------|---------|-------------|
279
+ | **Contextual** (default) | `judge(result, 'criteria')` | One LLM call via `/api/judge`. SDK sends `{runId, trajectory, output, traces?, logs?, criteria}`. The criteria *is* the expectation — no `expectedOutcomes` gate (#243). Cheap, fast. |
280
+ | **Agentic / tool** | `tools: ['trace_query']` | Judge runs as an agent with the built-in **trace-query tool pack**. It pulls *your* spans by `runId`, iterates, returns a verdict. Extends the existing agentic judge; enables the tool path that is currently `--print` read-only. |
281
+ | **Custom** | `evaluator: 'my-trace-judge'` | A judge function you define in `agent-health.config.ts` (closes #244). Runs in-process in the engine (it's JS from your config). |
282
+
283
+ **The trace-query tool pack** is a thin wrapper over primitives that already
284
+ exist (`fetchSpansForRun`, `fetchTracesByRunIds`, `fetchLogsForRun`), pointed at
285
+ the configured LOGS cluster:
286
+
287
+ ```ts
288
+ tools.trace_query = {
289
+ getSpans(runId), // → fetchSpansForRun
290
+ query(openSearchDsl), // raw DSL against the LOGS cluster
291
+ getLogs(runId), // → fetchLogsForRun
292
+ metrics(runId), // token / cost / duration rollups
293
+ };
294
+ ```
295
+
296
+ **Custom judge in config** — the full escape hatch. Gets the run context, a
297
+ `tools` accessor, and an `llm` helper; returns a `Verdict`:
298
+
299
+ ```js
300
+ defineConfig({
301
+ evaluators: {
302
+ 'my-trace-judge': async ({ result, criteria, tools, llm }) => {
303
+ const spans = await tools.trace_query.getSpans(result.runId);
304
+ const slow = spans.filter(s => s.durationMs > 1000);
305
+ const v = await llm.complete(`Spans: ${JSON.stringify(slow)}\nClaim: ${criteria}`);
306
+ return { pass: v.pass, score: v.score, reasoning: v.reasoning };
307
+ },
308
+ },
309
+ });
310
+ ```
311
+
312
+ **`judge()` records, it does not throw.** It returns a `Verdict` and records one
313
+ matcher result. Today it throws on failure, which bails the body before later
314
+ judges run. Since any failed matcher already fails the test (§6), recording is
315
+ sufficient and lets you run several judges and see *all* verdicts in the UI. For
316
+ a hard stop, write `await judge(...).orThrow()` or `expect(verdict).toPass()`.
317
+
318
+ ```ts
319
+ interface Verdict {
320
+ pass: boolean;
321
+ score?: number; // 0–1
322
+ reasoning: string; // why this single claim passed/failed — feeds the insight layer (§4.8)
323
+ role: 'gate' | 'observe'; // does this verdict gate pass/fail? (§4.7.2)
324
+ cached?: boolean; // verdict came from the judge cache
325
+ skipped?: boolean; // judge was skipped (§4.7.1) — neutral, not a failure
326
+ toPass(): void; // chai-style hard assertion
327
+ orThrow(): Verdict; // bail the body on failure
328
+ }
329
+ ```
330
+
331
+ #### 4.7.2 A judge verdict does not always gate the test
332
+
333
+ A judge **always** produces a `Verdict` (`pass`/`score`/`reasoning`), but whether
334
+ that verdict can flip the test to `failed` is a choice. Two uses:
335
+
336
+ - **Assertive** (default) — `judge(result, claim)`. The criteria is a yes/no
337
+ proposition; `verdict.pass` participates in the gate (§6). `role: 'gate'`.
338
+ - **Observational** — `judge.observe(result, criteria)` (or
339
+ `judge(result, criteria, { gate: false })`). A *graded measurement* you want
340
+ for the **score** and the **insight** layer, but which must never fail the
341
+ benchmark on its own. `role: 'observe'`.
342
+
343
+ This generalises to every signal via a `role`:
344
+
345
+ | Signal source | Default role | Gates pass/fail? |
346
+ |---------------|-------------|------------------|
347
+ | `expect(...)` | `gate` | yes (always) |
348
+ | `judge(result, claim)` | `gate` | yes — on `verdict.pass` |
349
+ | `judge.observe(result, criteria)` | `observe` | no — feeds score + diagnosis only |
350
+ | derived metrics (§4.8) | `observe` | no — unless wrapped in `expect(...).toBeLessThan(n)` |
351
+ | `skipped` judge (§4.7.1) | neutral | no |
352
+
353
+ Observational judges are what let a *low-ground-truth* test still produce rich
354
+ signal: run several graded-quality judges for diagnosis without turning each
355
+ into a brittle pass/fail.
356
+ ```
357
+
358
+ #### 4.7.1 Caching & skipping (cost control)
359
+
360
+ Agentic, trace-querying judges are expensive; two knobs keep them affordable:
361
+
362
+ - **Content-addressed cache.** Key =
363
+ `sha256(evaluatorId + model + criteria + contextSelection + canonical(trajectory|output))`.
364
+ A hit returns the verdict instantly (`cached: true`). This pays off when a
365
+ body is re-run against a stored/replayed run, when a deterministic agent
366
+ reproduces a trajectory, or when the same criteria is judged across a sweep.
367
+ Cache lives server-side keyed by content, so it survives process restarts.
368
+ Invalidation is automatic — any change to trajectory/criteria/model changes
369
+ the key.
370
+ - **Skip mode.** `--no-judge` (CLI), `AGENT_HEALTH_SKIP_JUDGE=1` (env), or
371
+ per-test `{ skipJudge: true }`. Every `judge()` returns a `skipped` verdict:
372
+ neutral, excluded from pass/fail (not counted as a failure), labelled in the
373
+ UI. Deterministic `expect()` still runs, giving a fast structural-only inner
374
+ loop. Trace-derived metrics (§4.8) are still computed — they're free.
375
+
376
+ ### 4.8 From verdicts to insight — the composition model
377
+
378
+ The current SDK treats evaluation as a **gate** (matchers → pass/fail). The more
379
+ valuable job, especially with little ground-truth data, is **diagnosis**: use
380
+ the trace to explain *why* the agent performed as it did and recommend *how to
381
+ improve it*. v2 makes both first-class by composing three layers over one
382
+ `RunResult`:
383
+
384
+ ```
385
+ RunResult (trajectory + traces)
386
+ ├─ deterministic signals expect() → objective facts (cheap, hard gate)
387
+ ├─ judge signals judge() → graded + reasoning (semantic, few needed)
388
+ ├─ derived metrics automatic → tokens, latency, tool histogram,
389
+ │ retries, ignored tool errors, redundant calls
390
+ └─ synthesis (optional) insights → why-narrative + ranked recommendations
391
+ ```
392
+
393
+ **Each layer has a distinct role:**
394
+
395
+ - **`expect` → facts.** Objective, deterministic, cheap. The hard gate. Each is a
396
+ `Signal { kind: 'deterministic', pass, evidence }`.
397
+ - **`judge` → graded claims *with reasoning*.** You only need a few. The verdict's
398
+ `reasoning` is diagnostic evidence the synthesizer consumes — a verdict scores
399
+ *one* claim and never carries cross-cutting fix advice.
400
+ `Signal { kind: 'judge', pass, score, reasoning }`.
401
+ - **Derived metrics → no assertion required.** Computed from the trace
402
+ automatically — *this is where "few ground-truth" stops mattering*. The trace
403
+ is rich: tool-call histograms, near-duplicate/redundant calls, tool errors the
404
+ agent ignored, latency hot-spots, token spend per phase. Emitted as
405
+ `Signal { kind: 'metric', name, value, threshold? }`.
406
+ - **Synthesis (`insights`) → the "why".** An optional, config-defined evaluator
407
+ that runs **once after the body**, receives the *whole* `TestResult` (all
408
+ signals + metrics + trajectory + traces), and may use the same trace-query
409
+ tools as the agentic judge. It emits structured insight — and needs **zero
410
+ ground truth** because it reasons over the trace itself.
411
+
412
+ ```js
413
+ defineConfig({
414
+ insights: async ({ result, signals, metrics, tools, llm }) => {
415
+ // e.g. spot inefficiency the assertions didn't encode
416
+ const dupes = metrics.redundantToolCalls;
417
+ const v = await llm.complete(`Given these signals/metrics/trace, explain why
418
+ the agent scored as it did and list concrete improvements.`);
419
+ return {
420
+ summary: v.summary,
421
+ why: v.why,
422
+ recommendations: v.recommendations, // ['Cache search_logs results', ...]
423
+ derivedMetrics: { redundantToolCalls: dupes },
424
+ };
425
+ },
426
+ });
427
+ ```
428
+
429
+ **How the layers combine — three views of one `TestResult`, no either/or:**
430
+
431
+ | Audience | Derived from | Output |
432
+ |----------|-------------|--------|
433
+ | **Gate** (benchmark pass-rate) | hard `expect` signals + non-skipped failed judges | `passed` / `failed` / `errored` |
434
+ | **Score** (hill-climbing) | weighted judge scores + metric thresholds | 0–1 aggregate |
435
+ | **Diagnosis** (improve the agent) | failed `expect`s + judge `reasoning` + trace-derived metric anomalies, run through `insights` | why-narrative + ranked recommendations |
436
+
437
+ So a test with **two** `expect`s and **one** `judge` still yields a full
438
+ diagnostic report: the gate from the asserts, plus an insight narrative the
439
+ synthesizer mined from the trace. The UI renders all three — the per-matcher
440
+ breakdown (gate), the score, and an Insights panel (why + recommendations).
441
+ This is the shift from "did it pass?" to "why did it behave this way, and what
442
+ should I change?".
443
+
444
+ ```ts
445
+ interface TestResult {
446
+ status: 'passed' | 'failed' | 'errored';
447
+ score?: number;
448
+ signals: Signal[]; // expect + judge + metric, in body order
449
+ metrics: DerivedMetrics; // always computed from the trace
450
+ insights?: { // present when an `insights` evaluator is configured
451
+ summary: string;
452
+ why: string;
453
+ recommendations: string[];
454
+ };
455
+ runId: string;
456
+ }
457
+ ```
458
+
459
+ #### 4.8.1 Where the insight layer lives
460
+
461
+ The `insights` synthesizer is **not** in the test body — it runs *after* it,
462
+ uniformly, so every test gets diagnosis with no per-test boilerplate.
463
+
464
+ - **Defined** in `defineConfig({ insights })` — project-wide by default (keeps the
465
+ benchmark consistent), with an optional per-test override.
466
+ - **Runs** in the `sdkEngine`, **server-side**, **once per test**, *after* the
467
+ body finishes and *after* the run's spans have landed (it shares the bounded
468
+ trace-availability wait with the lazy `traces` fixture, §4.4). Server-side
469
+ because that's where the OpenSearch LOGS creds + Bedrock live, and where the
470
+ read-only, `runId`-scoped trace-query tools run.
471
+ - **Receives** the whole `TestResult` — all signals (gate + observe), derived
472
+ metrics, trajectory, and the trace tools.
473
+ - **Stored** on the persisted `TestResult.insights` and surfaced in the run
474
+ inspector's Insights panel.
475
+ - **Optionally rolled up** to a run-level synthesis ("across the suite the agent
476
+ over-calls `search_logs`") by aggregating per-test signals + recommendations —
477
+ a separate, opt-in pass.
478
+
479
+ Data flow: `body → signals` → engine waits for traces → engine runs the per-test
480
+ `insights` synthesizer with trace tools → output persisted next to
481
+ signals/metrics → *(optional)* run-level rollup.
482
+
483
+ ---
484
+
485
+ ## 5. Execution architecture: one engine, two transports
486
+
487
+ ```
488
+ ┌────────────────────────────┐
489
+ CLI (benchmark/run) ──┤ │
490
+ │ sdkEngine.runFile(file) │── load → discover tests
491
+ Server (eval-runs, ──┤ sdkEngine.runTest(test) │── per test:
492
+ benchmarks routes) │ │ beforeEach → body(fixtures) → afterEach
493
+ └────────────────────────────┘ (body calls agent.run via connector)
494
+ │
495
+ emits TestRunResult { matcherResults[], status, runId, ... }
496
+ │
497
+ ┌─────────────────────────┴─────────────────────────┐
498
+ CLI Reporter (spinner/JSON) Server Reporter (storage + SSE → UI)
499
+ ```
500
+
501
+ - **Delete** the duplicated body-execution blocks in `evaluationRunner.ts` and `benchmarkRunner.ts` (#246). Both become thin reporters over `sdkEngine`.
502
+ - **Delete** the server-side re-import / `evaluateFnMap` materialization in `benchmarks.ts` — the engine loads the file once, in-process, and runs bodies directly. (When the server runs a code benchmark, it invokes the same engine; it does not reconstruct closures across a process/HTTP boundary.)
503
+ - `agent.run()` calls the connector directly via the engine; the `runId` it returns is the correlation key the server reporter persists. No second "did the body run?" path.
504
+
505
+ ### Loader unification (#232)
506
+
507
+ One loader for `.js`, `.ts`, `.mjs`:
508
+ - Resolve the SDK module **once** and force every entry point — the custom `.js` require shim *and* the `import()` path for `.ts/.mjs` — to bind that exact instance (inject via `import` hook / `--import` register, or a tiny ESM loader that aliases the package specifier to the in-process module). Tests then always register into the registry the engine reads. The "module-instance mismatch" class of bug is closed.
509
+
510
+ ---
511
+
512
+ ## 6. Grading & status — one surface, honest errors (#242, #243)
513
+
514
+ Grading derives entirely from the `signals[]` the body produced (§4.8) — the
515
+ `expect`/`judge` results. The legacy server-evaluator-needs-`expectedOutcomes`
516
+ path is **not** in the code-SDK pipeline at all. Status is the *gate* view of
517
+ those signals; `score` and `insights` are the other two views (§4.8).
518
+
519
+ - `judge(result, criteria)` sends `criteria` as the judge's claim directly — it *is* the expectation. No `expectedOutcomes` requirement. (#243 closed.)
520
+ - A judge/connector/infra failure produces a distinct terminal status, never a `completed` run with `metrics: 0`:
521
+
522
+ ```ts
523
+ type TestStatus =
524
+ | 'passed' // all GATE-role signals passed (observe/skipped don't count)
525
+ | 'failed' // ≥1 GATE-role signal failed (real signal)
526
+ | 'errored'; // body/agent/judge threw an infra/validation error — EXCLUDED from pass-rate
527
+ ```
528
+
529
+ A `skipped` judge (§4.7.1) is **neutral** — it never flips `passed`→`failed`.
530
+ `errored` is surfaced with the *actual* error string and excluded from pass-rate aggregation (#242 closed). No more "Waiting for traces…" masking "Missing required field: expectedOutcomes."
531
+
532
+ `expectedOutcomes`/`expectedTrajectory` remain on `TestOptions` purely so JSON round-trip and *non-code* consumers keep working — they never gate the code body.
533
+
534
+ ---
535
+
536
+ ## 7. How each issue is resolved
537
+
538
+ | Issue | Resolution in v2 |
539
+ |-------|------------------|
540
+ | **#245** body never runs under `benchmark -f` | One engine; CLI and server both call `sdkEngine.runTest`. The body always runs because running the body *is* the engine. |
541
+ | **#238** lazy/`invoke` fixture | `agent.run()` *is* the invoke fixture. Computed prompts, multi-turn, N invocations all natural. Eager mode deleted. |
542
+ | **#248** hooks run after the agent | Agent call lives in the body; fixtures/`beforeEach` resolve before the body → before the agent, by construction. The 3 coupled type hacks disappear. |
543
+ | **#243** inline `judge()` criteria ignored | `judge(result, criteria)` sends the criteria as the claim; no `expectedOutcomes` needed (§4.7). |
544
+ | **#244** custom evaluators in code/config | `evaluators: {}` in `defineConfig` + `judge(result, { evaluator })`; runs in-process with run context + trace tools (§4.7). |
545
+ | **#242** judge error → silent `completed/0` | Distinct `errored` status with the real message; excluded from pass-rate. |
546
+ | **#230** traces silently zero | `result.traces` is lazy, scoped to the run's `runId`, and throws (or awaits then throws) instead of returning silent zeros. |
547
+ | **#232** `.ts/.mjs` register nothing | Single loader binds one SDK module instance across `.js/.ts/.mjs`. |
548
+ | **#246** duplicated runners | Both runners become reporters over the single engine; the duplicated block is deleted. |
549
+ | *latent* global session under concurrency | One `TestContext` per test; module-global `activeSession` deleted. |
550
+
551
+ ---
552
+
553
+ ## 8. Migration: v1 is removed outright (no compat)
554
+
555
+ The SDK is experimental with effectively one user (the author), so we **delete
556
+ v1 rather than shim it**. This is a deliberate simplification: keeping an
557
+ eager-invocation compatibility path would re-introduce the very dual-path
558
+ complexity this RFC removes.
559
+
560
+ - The old eager `test(name, { prompt }, ({ result }) => …)` form is **gone**.
561
+ Bodies receive `agent`/`expect`/`judge`/`traces` and drive invocation
562
+ themselves. There is no `result` fixture and no auto-`agent.run()`.
563
+ - Matcher names move to the `toX` convention (`toHaveCalledTool`,
564
+ `toCompleteWithin`, …); `agentOutput` → `output`. No deprecated aliases.
565
+ - `judge()` no longer throws by default (§4.7).
566
+ - A one-off `npx agent-health migrate-sdk` codemod is offered as a convenience
567
+ (rewrite `{ result }` bodies to `{ agent }` + explicit `agent.run()`, rename
568
+ matchers), but it is *not* a compatibility guarantee — just author ergonomics.
569
+
570
+ Net effect: the engine has a single code path, no `AGENT_HEALTH_SDK_COMPAT`
571
+ flag, and no eager branch to maintain.
572
+
573
+ ---
574
+
575
+ ## 9. Phased implementation plan
576
+
577
+ 1. **Engine extraction.** Create `services/sdkEngine.ts`: `runFile`, `runTest`, per-test `TestContext`, one trajectory/trace/judge capture path. Factor `invokeAgent()` out of `runEvaluationWithConnector()`. Move the body-execution logic out of `evaluationRunner.ts`; have that runner call the engine. (Pure de-dup — kills the global `activeSession`.)
578
+ 2. **`agent` fixture + control inversion.** Add `agent.run()` (= one `invokeAgent` call, **second call throws**); make `expect/judge/traces` result-scoped; delete `activeSession`. Rewrite the demo evals to the new form (no compat shim).
579
+ 3. **Judge v2** (§4.7): run-scoped `judge(result, spec)` returning a non-throwing `Verdict`; contextual mode over `/api/judge` with traces/logs context; **errored** status (#242, #243). Includes the **content-addressed cache** and **skip mode** (§4.7.1).
580
+ 4. **Trace-query tool pack + agentic/custom judge** (#244): wire `trace_query` to `fetchSpansForRun`/`fetchLogsForRun` (read-only, runId-scoped); enable the tool-using judge path; `evaluators: {}` in `defineConfig`.
581
+ 5. **Lazy honest traces** (#230) on `RunResult.traces`.
582
+ 6. **Second transport.** Point `benchmarkRunner.ts` and `benchmark -f` at the engine; delete the `benchmarks.ts` re-import/`evaluateFnMap` materialization (#245, #246).
583
+ 7. **Loader unification** (#232): single SDK-module binding for `.js/.ts/.mjs`.
584
+ 8. **Fixtures (`test.extend` + `use`)** and hooks-wrap-agent (#248).
585
+ 9. **Composition & insight layer** (§4.8): always-computed derived trace metrics; opt-in `insights` synthesizer in `defineConfig`; `TestResult { signals, metrics, insights }` + the UI Insights panel (why + recommendations).
586
+ 10. **Docs rewrite + codemod**; remove v1 entirely.
587
+
588
+ Each phase is independently shippable and testable. After phase 6 the headline bug (#245) and the whole #238 RFC are resolved with net *less* code than today; phases 9–10 deliver the diagnosis vision (insights/recommendations) on top.
589
+
590
+ ---
591
+
592
+ ## 10. Open questions
593
+
594
+ - **Discovery for dynamic prompts.** Tests with no `defaultPrompt` show as "dynamic"; we capture the actual prompt from the single `agent.run()` at run time and attach it to the report.
595
+ - **Hashing/dedup** when there's no static prompt: hash `name + labels + body source` (already partially done in `computeTestCaseHash`).
596
+ - **Naming:** `agent.run()` vs `agent.invoke()` vs `agent.send()`. Recommend `run` (reads well, matches "agent run").
597
+ - **Insight cost.** The `insights` synthesizer is an extra LLM call per test. Default it **off** (opt-in via config), and let it reuse the judge cache so a sweep over identical trajectories synthesizes once.
598
+ - **Where derived metrics live.** Some are connector-agnostic (token/latency from `RunResult`); richer ones (redundant tool calls, ignored tool errors) need trace access. Compute the cheap ones always; gate the trace-derived ones on `useTraces`.
599
+ - **Trace-query tool safety:** the `query(dsl)` raw-DSL tool is powerful — restrict it to read-only and scope it to the run's own `runId` so a judge/insight prompt-injection can't exfiltrate unrelated data.