stageflow 0.39.0 → 0.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (360) hide show
  1. package/README.md +46 -19
  2. package/dist/agent/claudeAdapter.js +15 -1
  3. package/dist/agent/claudeTools.d.ts +13 -0
  4. package/dist/agent/claudeTools.js +98 -0
  5. package/dist/agent/piAdapter.js +38 -16
  6. package/dist/agent/sandboxPiTools.d.ts +99 -0
  7. package/dist/agent/sandboxPiTools.js +109 -0
  8. package/dist/agents/authorChat.d.ts +4 -0
  9. package/dist/agents/authorChat.js +113 -12
  10. package/dist/agents/authorSessionStore.d.ts +35 -0
  11. package/dist/agents/authorSessionStore.js +152 -0
  12. package/dist/agents/chatTurn.d.ts +2 -0
  13. package/dist/agents/chatTurn.js +17 -1
  14. package/dist/agents/errors.d.ts +1 -1
  15. package/dist/agents/errors.js +1 -0
  16. package/dist/agents/evalGate.d.ts +61 -0
  17. package/dist/agents/evalGate.js +103 -0
  18. package/dist/agents/evalSession.d.ts +26 -0
  19. package/dist/agents/evalSession.js +29 -0
  20. package/dist/agents/hostContext.d.ts +3 -0
  21. package/dist/agents/hostContext.js +4 -1
  22. package/dist/agents/runtime/piRuntime.d.ts +1 -1
  23. package/dist/agents/runtime/piRuntime.js +4 -4
  24. package/dist/agents/runtime/types.d.ts +3 -1
  25. package/dist/agents/service.d.ts +27 -2
  26. package/dist/agents/service.js +88 -13
  27. package/dist/agents/sessions.d.ts +8 -0
  28. package/dist/agents/sessions.js +50 -10
  29. package/dist/agents/store.d.ts +14 -3
  30. package/dist/agents/store.js +45 -7
  31. package/dist/agents/toolRisk.d.ts +21 -0
  32. package/dist/agents/toolRisk.js +50 -0
  33. package/dist/cli/doctorCommand.js +26 -0
  34. package/dist/cli/evalCommand.d.ts +20 -0
  35. package/dist/cli/evalCommand.js +720 -0
  36. package/dist/cli/exportAllCommand.js +1 -1
  37. package/dist/cli/hostClient.d.ts +60 -0
  38. package/dist/cli/hostClient.js +71 -0
  39. package/dist/cli/validateOutput.d.ts +2 -2
  40. package/dist/cli/validateOutput.js +2 -2
  41. package/dist/cli.js +10 -0
  42. package/dist/config/atomicWrite.d.ts +15 -0
  43. package/dist/config/atomicWrite.js +43 -0
  44. package/dist/config/browseCatalog.d.ts +2 -0
  45. package/dist/config/browseCatalog.js +4 -2
  46. package/dist/config/csv.d.ts +9 -0
  47. package/dist/config/csv.js +81 -0
  48. package/dist/config/datasetAgentCheck.d.ts +18 -0
  49. package/dist/config/datasetAgentCheck.js +109 -0
  50. package/dist/config/datasetCandidates.d.ts +22 -0
  51. package/dist/config/datasetCandidates.js +86 -0
  52. package/dist/config/datasetFromSessions.d.ts +34 -0
  53. package/dist/config/datasetFromSessions.js +105 -0
  54. package/dist/config/datasetHash.d.ts +1 -0
  55. package/dist/config/datasetHash.js +4 -0
  56. package/dist/config/datasetImport.d.ts +21 -0
  57. package/dist/config/datasetImport.js +120 -0
  58. package/dist/config/datasetStageCheck.d.ts +9 -0
  59. package/dist/config/datasetStageCheck.js +17 -0
  60. package/dist/config/datasetTypes.d.ts +27 -0
  61. package/dist/config/datasetTypes.js +1 -0
  62. package/dist/config/ensureCatalogScanRoot.d.ts +2 -1
  63. package/dist/config/ensureCatalogScanRoot.js +2 -6
  64. package/dist/config/evalCatalog.d.ts +22 -0
  65. package/dist/config/evalCatalog.js +67 -0
  66. package/dist/config/evalWriteRoots.d.ts +17 -0
  67. package/dist/config/evalWriteRoots.js +109 -0
  68. package/dist/config/hostConfig.js +3 -0
  69. package/dist/config/installEvalSample.d.ts +34 -0
  70. package/dist/config/installEvalSample.js +182 -0
  71. package/dist/config/loadDataset.d.ts +16 -0
  72. package/dist/config/loadDataset.js +318 -0
  73. package/dist/config/loadEval.d.ts +32 -0
  74. package/dist/config/loadEval.js +1085 -0
  75. package/dist/config/loadOutcome.d.ts +5 -0
  76. package/dist/config/loadStageflowManifest.js +64 -0
  77. package/dist/config/loadTask.js +34 -1
  78. package/dist/config/resolveAgentTarget.d.ts +32 -0
  79. package/dist/config/resolveAgentTarget.js +118 -0
  80. package/dist/config/resolveEvalTarget.d.ts +21 -0
  81. package/dist/config/resolveEvalTarget.js +89 -0
  82. package/dist/config/scanCatalogPaths.d.ts +1 -1
  83. package/dist/config/scanCatalogPaths.js +17 -5
  84. package/dist/config/validateCatalog.d.ts +1 -1
  85. package/dist/config/validateCatalog.js +9 -1
  86. package/dist/config/validateEval.d.ts +36 -0
  87. package/dist/config/validateEval.js +405 -0
  88. package/dist/config/writeDataset.d.ts +54 -0
  89. package/dist/config/writeDataset.js +186 -0
  90. package/dist/config/writeEval.d.ts +25 -0
  91. package/dist/config/writeEval.js +398 -0
  92. package/dist/diagnostics/checks.js +1 -1
  93. package/dist/evals/agentDiff.d.ts +13 -0
  94. package/dist/evals/agentDiff.js +11 -0
  95. package/dist/evals/canonical.d.ts +3 -0
  96. package/dist/evals/canonical.js +24 -0
  97. package/dist/evals/caseTask.d.ts +3 -0
  98. package/dist/evals/caseTask.js +9 -0
  99. package/dist/evals/compare.d.ts +94 -0
  100. package/dist/evals/compare.js +201 -0
  101. package/dist/evals/constants.d.ts +60 -0
  102. package/dist/evals/constants.js +95 -0
  103. package/dist/evals/engine/FakeEngine.d.ts +33 -0
  104. package/dist/evals/engine/FakeEngine.js +176 -0
  105. package/dist/evals/engine/index.d.ts +9 -0
  106. package/dist/evals/engine/index.js +19 -0
  107. package/dist/evals/engine/promptfoo/PromptfooEngine.d.ts +52 -0
  108. package/dist/evals/engine/promptfoo/PromptfooEngine.js +311 -0
  109. package/dist/evals/engine/promptfoo/errors.d.ts +5 -0
  110. package/dist/evals/engine/promptfoo/errors.js +8 -0
  111. package/dist/evals/engine/promptfoo/generate.d.ts +30 -0
  112. package/dist/evals/engine/promptfoo/generate.js +342 -0
  113. package/dist/evals/engine/promptfoo/guard.d.ts +10 -0
  114. package/dist/evals/engine/promptfoo/guard.js +116 -0
  115. package/dist/evals/engine/promptfoo/install.d.ts +17 -0
  116. package/dist/evals/engine/promptfoo/install.js +73 -0
  117. package/dist/evals/engine/promptfoo/judgeProviders.d.ts +2 -0
  118. package/dist/evals/engine/promptfoo/judgeProviders.js +9 -0
  119. package/dist/evals/engine/promptfoo/judgeServices.d.ts +6 -0
  120. package/dist/evals/engine/promptfoo/judgeServices.js +41 -0
  121. package/dist/evals/engine/promptfoo/locate.d.ts +21 -0
  122. package/dist/evals/engine/promptfoo/locate.js +68 -0
  123. package/dist/evals/engine/promptfoo/nodeVersion.d.ts +7 -0
  124. package/dist/evals/engine/promptfoo/nodeVersion.js +14 -0
  125. package/dist/evals/engine/promptfoo/normalize.d.ts +66 -0
  126. package/dist/evals/engine/promptfoo/normalize.js +261 -0
  127. package/dist/evals/engine/promptfoo/pinned.d.ts +5 -0
  128. package/dist/evals/engine/promptfoo/pinned.js +5 -0
  129. package/dist/evals/engine/promptfoo/rubricPrompt.d.ts +14 -0
  130. package/dist/evals/engine/promptfoo/rubricPrompt.js +103 -0
  131. package/dist/evals/engine/promptfoo/runTokens.d.ts +18 -0
  132. package/dist/evals/engine/promptfoo/runTokens.js +31 -0
  133. package/dist/evals/engine/promptfoo/status.d.ts +13 -0
  134. package/dist/evals/engine/promptfoo/status.js +16 -0
  135. package/dist/evals/estimate.d.ts +56 -0
  136. package/dist/evals/estimate.js +66 -0
  137. package/dist/evals/graders/codeGrader.d.ts +6 -0
  138. package/dist/evals/graders/codeGrader.js +85 -0
  139. package/dist/evals/graders/conversationCheckGrader.d.ts +6 -0
  140. package/dist/evals/graders/conversationCheckGrader.js +62 -0
  141. package/dist/evals/graders/fieldGrader.d.ts +11 -0
  142. package/dist/evals/graders/fieldGrader.js +117 -0
  143. package/dist/evals/graders/goal.d.ts +3 -0
  144. package/dist/evals/graders/goal.js +14 -0
  145. package/dist/evals/graders/index.d.ts +18 -0
  146. package/dist/evals/graders/index.js +34 -0
  147. package/dist/evals/graders/notApplicable.d.ts +5 -0
  148. package/dist/evals/graders/notApplicable.js +9 -0
  149. package/dist/evals/graders/path.d.ts +26 -0
  150. package/dist/evals/graders/path.js +75 -0
  151. package/dist/evals/graders/referenceGrader.d.ts +5 -0
  152. package/dist/evals/graders/referenceGrader.js +59 -0
  153. package/dist/evals/graders/schemaGrader.d.ts +5 -0
  154. package/dist/evals/graders/schemaGrader.js +29 -0
  155. package/dist/evals/graders/subject.d.ts +6 -0
  156. package/dist/evals/graders/subject.js +33 -0
  157. package/dist/evals/graders/toolCallsGrader.d.ts +6 -0
  158. package/dist/evals/graders/toolCallsGrader.js +100 -0
  159. package/dist/evals/graders/transcript.d.ts +12 -0
  160. package/dist/evals/graders/transcript.js +57 -0
  161. package/dist/evals/graders/types.d.ts +20 -0
  162. package/dist/evals/graders/types.js +1 -0
  163. package/dist/evals/judgeProviders.d.ts +16 -0
  164. package/dist/evals/judgeProviders.js +27 -0
  165. package/dist/evals/lineDiff.d.ts +5 -0
  166. package/dist/evals/lineDiff.js +34 -0
  167. package/dist/evals/listStatus.d.ts +31 -0
  168. package/dist/evals/listStatus.js +48 -0
  169. package/dist/evals/manager.d.ts +174 -0
  170. package/dist/evals/manager.js +787 -0
  171. package/dist/evals/plan.d.ts +58 -0
  172. package/dist/evals/plan.js +602 -0
  173. package/dist/evals/providerKeys.d.ts +1 -0
  174. package/dist/evals/providerKeys.js +21 -0
  175. package/dist/evals/read/agentTargets.d.ts +37 -0
  176. package/dist/evals/read/agentTargets.js +101 -0
  177. package/dist/evals/read/api.d.ts +196 -0
  178. package/dist/evals/read/api.js +358 -0
  179. package/dist/evals/read/compare.d.ts +4 -0
  180. package/dist/evals/read/compare.js +39 -0
  181. package/dist/evals/read/datasets.d.ts +105 -0
  182. package/dist/evals/read/datasets.js +318 -0
  183. package/dist/evals/read/list.d.ts +66 -0
  184. package/dist/evals/read/list.js +173 -0
  185. package/dist/evals/read/results.d.ts +46 -0
  186. package/dist/evals/read/results.js +107 -0
  187. package/dist/evals/read/runs.d.ts +149 -0
  188. package/dist/evals/read/runs.js +289 -0
  189. package/dist/evals/read/suggestions.d.ts +43 -0
  190. package/dist/evals/read/suggestions.js +73 -0
  191. package/dist/evals/runner/FakeAgentBackend.d.ts +40 -0
  192. package/dist/evals/runner/FakeAgentBackend.js +82 -0
  193. package/dist/evals/runner/agentCaseRunner.d.ts +24 -0
  194. package/dist/evals/runner/agentCaseRunner.js +388 -0
  195. package/dist/evals/runner/caseRunner.d.ts +7 -0
  196. package/dist/evals/runner/caseRunner.js +17 -0
  197. package/dist/evals/runner/conversationSubject.d.ts +18 -0
  198. package/dist/evals/runner/conversationSubject.js +123 -0
  199. package/dist/evals/runner/simulatorModel.d.ts +51 -0
  200. package/dist/evals/runner/simulatorModel.js +58 -0
  201. package/dist/evals/runner/simulatorPrompt.d.ts +29 -0
  202. package/dist/evals/runner/simulatorPrompt.js +59 -0
  203. package/dist/evals/runner/stageCaseRunner.d.ts +30 -0
  204. package/dist/evals/runner/stageCaseRunner.js +279 -0
  205. package/dist/evals/runner/userDriver.d.ts +71 -0
  206. package/dist/evals/runner/userDriver.js +104 -0
  207. package/dist/evals/scoring.d.ts +34 -0
  208. package/dist/evals/scoring.js +67 -0
  209. package/dist/evals/snapshot.d.ts +183 -0
  210. package/dist/evals/snapshot.js +65 -0
  211. package/dist/evals/stageDiff.d.ts +8 -0
  212. package/dist/evals/stageDiff.js +48 -0
  213. package/dist/evals/stats.d.ts +95 -0
  214. package/dist/evals/stats.js +220 -0
  215. package/dist/evals/statsAdapter.d.ts +4 -0
  216. package/dist/evals/statsAdapter.js +30 -0
  217. package/dist/evals/statsTypes.d.ts +51 -0
  218. package/dist/evals/statsTypes.js +18 -0
  219. package/dist/evals/storeError.d.ts +5 -0
  220. package/dist/evals/storeError.js +8 -0
  221. package/dist/evals/storeTypes.d.ts +207 -0
  222. package/dist/evals/storeTypes.js +1 -0
  223. package/dist/evals/tTable.d.ts +3 -0
  224. package/dist/evals/tTable.js +15 -0
  225. package/dist/evals/triage/constants.d.ts +26 -0
  226. package/dist/evals/triage/constants.js +33 -0
  227. package/dist/evals/triage/credentials.d.ts +13 -0
  228. package/dist/evals/triage/credentials.js +20 -0
  229. package/dist/evals/triage/jevClassifier.d.ts +14 -0
  230. package/dist/evals/triage/jevClassifier.js +124 -0
  231. package/dist/evals/triage/judgeClassifier.d.ts +10 -0
  232. package/dist/evals/triage/judgeClassifier.js +76 -0
  233. package/dist/evals/triage/prompts.d.ts +16 -0
  234. package/dist/evals/triage/prompts.js +64 -0
  235. package/dist/evals/triage/service.d.ts +79 -0
  236. package/dist/evals/triage/service.js +217 -0
  237. package/dist/evals/triage/stateBuilder.d.ts +19 -0
  238. package/dist/evals/triage/stateBuilder.js +125 -0
  239. package/dist/evals/triage/types.d.ts +60 -0
  240. package/dist/evals/triage/types.js +8 -0
  241. package/dist/evals/types.d.ts +347 -0
  242. package/dist/evals/types.js +1 -0
  243. package/dist/mcp/catalogTools.js +11 -1
  244. package/dist/mcp/deps.d.ts +7 -0
  245. package/dist/mcp/evalTools.d.ts +3 -0
  246. package/dist/mcp/evalTools.js +280 -0
  247. package/dist/mcp/server.js +1 -0
  248. package/dist/mcp/tools.js +2 -0
  249. package/dist/models/response.d.ts +7 -2
  250. package/dist/models/response.js +6 -1
  251. package/dist/operatorAgent/agentAuthorContext.d.ts +1 -1
  252. package/dist/operatorAgent/agentAuthorContext.js +1 -1
  253. package/dist/operatorAgent/piModel.js +17 -4
  254. package/dist/operatorAgent/profiles/agentAuthor.d.ts +3 -1
  255. package/dist/operatorAgent/profiles/agentAuthor.js +145 -20
  256. package/dist/operatorAgent/profiles/agentAuthorPlaybook.d.ts +1 -1
  257. package/dist/operatorAgent/profiles/agentAuthorPlaybook.js +48 -15
  258. package/dist/operatorAgent/profiles/authorReadable.d.ts +2 -0
  259. package/dist/operatorAgent/profiles/authorReadable.js +26 -0
  260. package/dist/package-meta.d.ts +1 -1
  261. package/dist/package-meta.js +1 -1
  262. package/dist/runstore/port.d.ts +43 -0
  263. package/dist/runstore/sqlite/SqliteRunStore.d.ts +28 -0
  264. package/dist/runstore/sqlite/SqliteRunStore.js +99 -5
  265. package/dist/runstore/sqlite/evalStore.d.ts +20 -0
  266. package/dist/runstore/sqlite/evalStore.js +541 -0
  267. package/dist/runstore/sqlite/migrations/015-evals.d.ts +12 -0
  268. package/dist/runstore/sqlite/migrations/015-evals.js +157 -0
  269. package/dist/runstore/sqlite/migrations/016-eval-subject-ref.d.ts +12 -0
  270. package/dist/runstore/sqlite/migrations/016-eval-subject-ref.js +20 -0
  271. package/dist/runstore/sqlite/migrations/017-eval-result-suggestions.d.ts +11 -0
  272. package/dist/runstore/sqlite/migrations/017-eval-result-suggestions.js +36 -0
  273. package/dist/runstore/sqlite/migrations/index.js +6 -0
  274. package/dist/runstore/sqlite/suggestionStore.d.ts +14 -0
  275. package/dist/runstore/sqlite/suggestionStore.js +132 -0
  276. package/dist/runtime/gitCheckoutCapability.d.ts +1 -0
  277. package/dist/runtime/gitCheckoutCapability.js +1 -1
  278. package/dist/runtime/gitWorktreeCheckout.d.ts +22 -0
  279. package/dist/runtime/gitWorktreeCheckout.js +120 -0
  280. package/dist/runtime/pipelineRunner.d.ts +2 -1
  281. package/dist/runtime/pipelineRunner.js +2 -0
  282. package/dist/runtime/runManager.d.ts +2 -1
  283. package/dist/runtime/runManager.js +8 -6
  284. package/dist/runtime/runRetentionSweep.js +2 -2
  285. package/dist/runtime/sandboxContainer.d.ts +62 -0
  286. package/dist/runtime/sandboxContainer.js +151 -0
  287. package/dist/runtime/sandboxDesk.d.ts +28 -0
  288. package/dist/runtime/sandboxDesk.js +242 -0
  289. package/dist/runtime/sandboxFiles.d.ts +20 -0
  290. package/dist/runtime/sandboxFiles.js +150 -0
  291. package/dist/runtime/stageEnvironment.js +2 -0
  292. package/dist/runtime/stageRoots.d.ts +9 -0
  293. package/dist/runtime/stageRoots.js +14 -0
  294. package/dist/runtime/stageRunner.d.ts +5 -0
  295. package/dist/runtime/stageRunner.js +38 -4
  296. package/dist/runtime/stageSandbox.d.ts +16 -0
  297. package/dist/runtime/stageSandbox.js +54 -0
  298. package/dist/runtime/stageWorker.js +138 -105
  299. package/dist/runtime/workspaceBinding.d.ts +2 -1
  300. package/dist/runtime/workspaceBinding.js +20 -4
  301. package/dist/server/agentAuthorRoutes.js +50 -1
  302. package/dist/server/bootstrap.d.ts +8 -0
  303. package/dist/server/bootstrap.js +56 -0
  304. package/dist/server/controlToken.js +3 -0
  305. package/dist/server/evalInternalRoutes.d.ts +14 -0
  306. package/dist/server/evalInternalRoutes.js +87 -0
  307. package/dist/server/evalRouteContext.d.ts +46 -0
  308. package/dist/server/evalRouteContext.js +43 -0
  309. package/dist/server/evalRoutes.d.ts +5 -0
  310. package/dist/server/evalRoutes.js +1427 -0
  311. package/dist/server/evalTriageRoutes.d.ts +2 -0
  312. package/dist/server/evalTriageRoutes.js +95 -0
  313. package/dist/server/http.d.ts +5 -0
  314. package/dist/server/http.js +32 -2
  315. package/dist/server/mcpHost.js +3 -0
  316. package/dist/toolkit/meta.d.ts +1 -1
  317. package/dist/toolkit/meta.js +15 -0
  318. package/dist/toolkit/profiles.js +1 -1
  319. package/dist/tools/readEmail.d.ts +3 -3
  320. package/dist/types/stageflowManifest.d.ts +6 -0
  321. package/dist/types/task.d.ts +11 -1
  322. package/dist/ui/assets/AssistantRuntimeProvider-qIX1xNoH.js +9 -0
  323. package/dist/ui/assets/{MarkdownText-D7tYaIFO.js → MarkdownText-BTrOqu5J.js} +1 -1
  324. package/dist/ui/assets/chat-Df_wCpak.js +5 -0
  325. package/dist/ui/assets/chat-P7KQznHm.css +2 -0
  326. package/dist/ui/assets/{main-BFo79krC.css → main-CHlmTNJe.css} +1 -1
  327. package/dist/ui/assets/main-CvVfxmMd.js +271 -0
  328. package/dist/ui/chat.html +3 -3
  329. package/dist/ui/index.html +3 -3
  330. package/examples/README.md +2 -0
  331. package/examples/document-policy-review/README.md +35 -0
  332. package/examples/document-policy-review/agents/compliance.agent.yaml +42 -0
  333. package/examples/document-policy-review/documents/acme-vendor-sow.md +15 -0
  334. package/examples/document-policy-review/documents/support-runbook.md +13 -0
  335. package/examples/document-policy-review/policies/acceptable-use.md +13 -0
  336. package/examples/document-policy-review/policies/data-handling.md +17 -0
  337. package/examples/document-policy-review/policies/vendor-security.md +17 -0
  338. package/examples/evals/README.md +123 -0
  339. package/examples/evals/critique-sample.dataset.jsonl +8 -0
  340. package/examples/evals/critique-sample.eval.yaml +34 -0
  341. package/examples/evals/critique-sample.task.yaml +4 -0
  342. package/examples/evals/eval-sample.pipeline.yaml +35 -0
  343. package/examples/evals/support-sample.agent.yaml +20 -0
  344. package/examples/evals/support-sample.dataset.jsonl +6 -0
  345. package/examples/evals/support-sample.eval.yaml +38 -0
  346. package/examples/memory/README.md +2 -0
  347. package/examples/memory/probe-bash.task.yaml +3 -0
  348. package/examples/memory/sandbox-memory-bash.pipeline.yaml +28 -0
  349. package/examples/ship-feature/README.md +15 -0
  350. package/examples/stageflow.yaml +6 -0
  351. package/package.json +1 -1
  352. package/skills/stageflow/SKILL.md +5 -2
  353. package/skills/stageflow/references/control-surface.md +4 -2
  354. package/skills/stageflow/references/evals.md +68 -0
  355. package/skills/stageflow-author/SKILL.md +4 -0
  356. package/skills/stageflow-run/SKILL.md +4 -0
  357. package/dist/ui/assets/AssistantRuntimeProvider-CsiPFhCT.js +0 -9
  358. package/dist/ui/assets/chat-BbLukq9W.js +0 -5
  359. package/dist/ui/assets/chat-zIZU_bP1.css +0 -2
  360. package/dist/ui/assets/main-CijYMeM0.js +0 -259
@@ -12,6 +12,7 @@ import { buildToolkit } from "../toolkit/index.js";
12
12
  import { createPiAgentRuntime } from "./runtime/piRuntime.js";
13
13
  import { AgentService } from "./service.js";
14
14
  import { getModelCatalog } from "../models/catalog.js";
15
+ import { EvalGateRegistry } from "./evalGate.js";
15
16
  import { defaultAgentProjectRoot } from "./projectRoot.js";
16
17
  import { PublicWebChat } from "./webChat/publicChat.js";
17
18
  import { AgentStore } from "./store.js";
@@ -82,9 +83,11 @@ export function createAgentHostContext(options) {
82
83
  }
83
84
  : {}),
84
85
  });
86
+ const evalGates = new EvalGateRegistry();
85
87
  const chat = new AgentChatController({
86
88
  service,
87
89
  backend,
90
+ evalGates,
88
91
  otlpSink: "otlpSink" in options ? options.otlpSink : otlpSinkFromEnv(),
89
92
  logError: (message) => console.error(message),
90
93
  });
@@ -147,7 +150,7 @@ export function createAgentHostContext(options) {
147
150
  delivery.start();
148
151
  if (options.listenOnChannels)
149
152
  void inbound.resumeInterrupted().catch((error) => console.error(`agent resume failed: ${String(error)}`));
150
- return { service, chat, backend, inbound, publicChat, channelContact, ...(channelListeners ? { channelListeners } : {}), delivery, stop: async () => {
153
+ return { service, chat, evalGates, backend, inbound, publicChat, channelContact, ...(channelListeners ? { channelListeners } : {}), delivery, stop: async () => {
151
154
  delivery.stop();
152
155
  await channelListeners?.stop();
153
156
  // The Host owns every runtime session's lifetime: nothing outlives it.
@@ -34,7 +34,7 @@ export type OpenSessionInput = {
34
34
  reason: string;
35
35
  terminate?: boolean;
36
36
  } | undefined>;
37
- delegateTool?: (agentId: string, message: string) => Promise<string>;
37
+ delegateTool?: (agentId: string, message: string, callId: string) => Promise<string>;
38
38
  /** Stageflow tools this agent is granted. */
39
39
  stageflowTools?: ToolkitTool[];
40
40
  memory?: StageMemory;
@@ -149,8 +149,8 @@ async function openPiSession(options, input) {
149
149
  label: `Ask ${agentId}`,
150
150
  description: `Hand a task to the "${agentId}" agent and get its reply.`,
151
151
  parameters: Type.Object({ message: Type.String({ description: "The task or question for the agent." }) }),
152
- execute: async (_id, args) => {
153
- const reply = await input.delegateTool(agentId, args.message);
152
+ execute: async (callId, args) => {
153
+ const reply = await input.delegateTool(agentId, args.message, callId);
154
154
  return { content: [{ type: "text", text: reply }], details: {} };
155
155
  },
156
156
  }));
@@ -296,7 +296,7 @@ export function createPiAgentRuntime(options) {
296
296
  hooks: () => state.hooks,
297
297
  ...(memory ? { memory } : {}),
298
298
  ...(stageflowTools.length > 0 ? { stageflowTools } : {}),
299
- ...(subAgents.length > 0 ? { delegateTool: (agentId, message) => state.hooks.delegate(agentId, message) } : {}),
299
+ ...(subAgents.length > 0 ? { delegateTool: (agentId, message, callId) => state.hooks.delegate(agentId, message, callId) } : {}),
300
300
  gate: async (call) => {
301
301
  const hooks = state.hooks;
302
302
  if (!hooks)
@@ -433,7 +433,7 @@ export function createPiAgentRuntime(options) {
433
433
  const opened = await open({
434
434
  projectRoot: input.projectRoot,
435
435
  definition: { ...input.definition, tools: { ...input.definition.tools, builtin: [], builtin_tools: [], mcp: {}, skills: [] }, sub_agents: [] },
436
- systemPrompt: "You are a strict evaluator. Answer only with the requested number.",
436
+ systemPrompt: input.systemPrompt ?? "You are a strict evaluator. Answer only with the requested number.",
437
437
  toolNames: [],
438
438
  gate: async () => ({ block: true, reason: "No tools" }),
439
439
  hooks: () => undefined,
@@ -39,7 +39,7 @@ export type TurnHooks = {
39
39
  model?: string;
40
40
  }): void;
41
41
  /** Run another agent for a `agent:<id>` call and return its reply. */
42
- delegate(agentId: string, message: string): Promise<string>;
42
+ delegate(agentId: string, message: string, callId?: string): Promise<string>;
43
43
  };
44
44
  export type CompleteInput = {
45
45
  sessionId: string;
@@ -64,6 +64,8 @@ export type ConversationBackend = {
64
64
  projectRoot: string;
65
65
  definition: AgentDefinition;
66
66
  prompt: string;
67
+ /** Replaces the default "strict evaluator" system prompt. */
68
+ systemPrompt?: string;
67
69
  signal: AbortSignal;
68
70
  }): Promise<string>;
69
71
  /** Drop any cached model session for a conversation session. */
@@ -3,6 +3,7 @@ import { type AgentInboundMessage } from "./communications/port.js";
3
3
  import { ApprovalBroker } from "./approvalBroker.js";
4
4
  import { type DeliveryLedger, type ScheduleLedger } from "./ledgers.js";
5
5
  import { type FinishInput } from "./turnLifecycle.js";
6
+ import { type EvalSessionOverrides } from "./evalSession.js";
6
7
  import { type DiffLine } from "./lineDiff.js";
7
8
  import { AgentStore, type AgentApproval, type AgentEvalScore, type AgentMessage, type AgentRevision, type AgentRevisionMeta, type AgentSession, type AgentSessionListItem, type AgentDelivery, type AgentSpan, type AgentTurn, type ApprovalActor, type RevisionSource, type SessionQuery, type SessionStats, type SessionStatus } from "./store.js";
8
9
  /** Lookups outside the agents directory, used only to produce non-blocking warnings. */
@@ -88,6 +89,8 @@ export declare class AgentService {
88
89
  private readonly environment;
89
90
  private readonly now;
90
91
  private readonly tails;
92
+ /** Overrides of open eval sessions. Memory only: eval sessions do not survive a Host restart. */
93
+ private readonly evalOverrides;
91
94
  /** Wakes a turn paused at an ask-first tool when its approval is decided. */
92
95
  readonly approvalBroker: ApprovalBroker;
93
96
  /** Owns every legal change of a turn, its session and its approval. */
@@ -140,6 +143,25 @@ export declare class AgentService {
140
143
  get(projectRoot: string, agentId: string): Promise<AgentDetail>;
141
144
  /** The definition a session is pinned to. */
142
145
  definitionAt(projectRoot: string, agentId: string, revision: number): Promise<AgentDefinition>;
146
+ /** The pinned definition, with an eval session's overrides applied. Real sessions get it unchanged. */
147
+ private effectiveDefinition;
148
+ /** The overrides an eval session was opened with (sub-agent sessions inherit them), or undefined for a real session. */
149
+ evalOverridesOf(sessionId: string): EvalSessionOverrides | undefined;
150
+ /** Every eval session below this one (sub-agents and theirs). */
151
+ listChildSessions(sessionId: string): AgentSession[];
152
+ /** Delete eval sessions and everything below them. Refuses a session that is not an eval session. */
153
+ deleteEvalSessions(projectRoot: string, sessionIds: string[]): Promise<string[]>;
154
+ /**
155
+ * Boot cleanup of eval sessions: those of the given (recovered) eval runs, and older root sessions no eval result
156
+ * references. Returns how many sessions were deleted, descendants included.
157
+ */
158
+ sweepEvalSessions(input: {
159
+ runIds: string[];
160
+ olderThanMs: number;
161
+ isReferenced: (sessionId: string) => Promise<boolean> | boolean;
162
+ }): Promise<number>;
163
+ /** Forget a finished eval session's in-memory overrides. The rows stay readable. */
164
+ forgetEvalSession(sessionId: string): void;
143
165
  private warningsFor;
144
166
  /**
145
167
  * Web chat defaults and links: a new agent starts with web chat on, an enabled block without a link
@@ -182,10 +204,11 @@ export declare class AgentService {
182
204
  sourceRef?: string | null;
183
205
  threadKey?: string | null;
184
206
  title?: string | null;
207
+ eval?: EvalSessionOverrides;
185
208
  }): Promise<AgentSession>;
186
209
  /** Id of the session's pending approval, if a turn is waiting on a person. */
187
210
  pendingApprovalFor(_projectRoot: string, sessionId: string): string | null;
188
- listSessionsForRef(projectRoot: string, agentId: string, source: string, sourceRef: string, limit: number): AgentSession[];
211
+ listSessionsForRef(projectRoot: string, agentId: string, source: string, sourceRef: string, limit: number, includeEval?: boolean): AgentSession[];
189
212
  private sessionInProject;
190
213
  getSession(projectRoot: string, sessionId: string): Promise<AgentSessionDetail>;
191
214
  listSessions(projectRoot: string, agentId: string, query: SessionQuery): Promise<{
@@ -274,7 +297,9 @@ export declare class AgentService {
274
297
  turnId?: string | null;
275
298
  }): Promise<AgentApproval>;
276
299
  decideApproval(projectRoot: string, sessionId: string, decision: "approve" | "deny", actor: ApprovalActor): Promise<AgentApproval>;
277
- pendingApprovals(projectRoot: string): Promise<Array<AgentApproval & {
300
+ pendingApprovals(projectRoot: string, options?: {
301
+ includeEval?: boolean;
302
+ }): Promise<Array<AgentApproval & {
278
303
  agentId: string;
279
304
  }>>;
280
305
  receive(projectRoot: string, message: AgentInboundMessage): Promise<{
@@ -11,6 +11,7 @@ import { ApprovalBroker } from "./approvalBroker.js";
11
11
  import { deliveryLedger, scheduleLedger } from "./ledgers.js";
12
12
  import { TurnLifecycle } from "./turnLifecycle.js";
13
13
  import { AgentError } from "./errors.js";
14
+ import { applyEvalOverrides, EVAL_SESSION_SOURCE, isEvalSession } from "./evalSession.js";
14
15
  import { agentMemoryIssues } from "../memory/agentIssues.js";
15
16
  import { diffLines } from "./lineDiff.js";
16
17
  import { exportTurnsToOtlp } from "./otlpExport.js";
@@ -38,6 +39,8 @@ export class AgentService {
38
39
  environment;
39
40
  now;
40
41
  tails = new Map();
42
+ /** Overrides of open eval sessions. Memory only: eval sessions do not survive a Host restart. */
43
+ evalOverrides = new Map();
41
44
  /** Wakes a turn paused at an ask-first tool when its approval is decided. */
42
45
  approvalBroker = new ApprovalBroker();
43
46
  /** Owns every legal change of a turn, its session and its approval. */
@@ -311,6 +314,64 @@ export class AgentService {
311
314
  throw new AgentError("agent.revision_not_found");
312
315
  return found.definition;
313
316
  }
317
+ /** The pinned definition, with an eval session's overrides applied. Real sessions get it unchanged. */
318
+ async effectiveDefinition(projectRoot, session) {
319
+ const base = await this.definitionAt(projectRoot, session.agentId, session.revision);
320
+ return isEvalSession(session) ? applyEvalOverrides(base, this.evalOverrides.get(session.id) ?? {}) : base;
321
+ }
322
+ /** The overrides an eval session was opened with (sub-agent sessions inherit them), or undefined for a real session. */
323
+ evalOverridesOf(sessionId) {
324
+ return this.evalOverrides.get(sessionId);
325
+ }
326
+ /** Every eval session below this one (sub-agents and theirs). */
327
+ listChildSessions(sessionId) {
328
+ return this.store.listChildSessions(sessionId);
329
+ }
330
+ /** Delete eval sessions and everything below them. Refuses a session that is not an eval session. */
331
+ async deleteEvalSessions(projectRoot, sessionIds) {
332
+ const roots = sessionIds.map((sessionId) => this.sessionInProject(projectRoot, sessionId));
333
+ const refused = roots.find((session) => !isEvalSession(session));
334
+ if (refused)
335
+ throw new AgentError("agent.invalid", `Session ${refused.id} is not an eval session`);
336
+ const all = new Set(roots.map((session) => session.id));
337
+ for (const root of roots)
338
+ for (const child of this.store.listChildSessions(root.id))
339
+ all.add(child.id);
340
+ const ids = [...all];
341
+ this.store.deleteSessions(ids);
342
+ for (const sessionId of ids)
343
+ this.evalOverrides.delete(sessionId);
344
+ return ids;
345
+ }
346
+ /**
347
+ * Boot cleanup of eval sessions: those of the given (recovered) eval runs, and older root sessions no eval result
348
+ * references. Returns how many sessions were deleted, descendants included.
349
+ */
350
+ async sweepEvalSessions(input) {
351
+ const roots = new Map();
352
+ for (const session of this.store.listEvalRootSessions({ sourceRefs: input.runIds }))
353
+ roots.set(session.id, session);
354
+ const cutoff = new Date(this.clockMs() - input.olderThanMs).toISOString();
355
+ for (const session of this.store.listEvalRootSessions({ createdBefore: cutoff })) {
356
+ if (!roots.has(session.id) && !(await input.isReferenced(session.id)))
357
+ roots.set(session.id, session);
358
+ }
359
+ const all = new Set();
360
+ for (const root of roots.values()) {
361
+ all.add(root.id);
362
+ for (const child of this.store.listChildSessions(root.id))
363
+ all.add(child.id);
364
+ }
365
+ const ids = [...all];
366
+ this.store.deleteSessions(ids);
367
+ for (const sessionId of ids)
368
+ this.evalOverrides.delete(sessionId);
369
+ return ids.length;
370
+ }
371
+ /** Forget a finished eval session's in-memory overrides. The rows stay readable. */
372
+ forgetEvalSession(sessionId) {
373
+ this.evalOverrides.delete(sessionId);
374
+ }
314
375
  /* ---------- validation ---------- */
315
376
  async warningsFor(projectRoot, definition) {
316
377
  const warnings = [];
@@ -636,10 +697,20 @@ export class AgentService {
636
697
  }
637
698
  /* ---------- sessions ---------- */
638
699
  async openSession(projectRoot, agentId, input) {
700
+ if (input.eval && input.source !== EVAL_SESSION_SOURCE)
701
+ throw new AgentError("agent.invalid", "Eval overrides need an eval session");
639
702
  return this.exclusive(projectRoot, async () => {
640
703
  await this.sync(projectRoot);
641
- const { revision } = this.currentRevision(projectRoot, agentId);
642
- if (!revision.definition.enabled)
704
+ const isEval = input.source === EVAL_SESSION_SOURCE;
705
+ const { revision: current } = this.currentRevision(projectRoot, agentId);
706
+ let revision = current;
707
+ if (input.eval?.revision !== undefined && input.eval.revision !== current.revision) {
708
+ const pinned = this.store.getRevision(agentProjectKey(projectRoot), agentId, input.eval.revision);
709
+ if (!pinned)
710
+ throw new AgentError("agent.revision_not_found");
711
+ revision = pinned;
712
+ }
713
+ if (!isEval && !revision.definition.enabled)
643
714
  throw new AgentError("agent.disabled");
644
715
  const now = this.iso();
645
716
  const session = {
@@ -656,6 +727,8 @@ export class AgentService {
656
727
  updatedAt: now,
657
728
  };
658
729
  this.store.insertSession(agentProjectKey(projectRoot), session);
730
+ if (isEval)
731
+ this.evalOverrides.set(session.id, input.eval ?? {});
659
732
  return session;
660
733
  });
661
734
  }
@@ -663,8 +736,8 @@ export class AgentService {
663
736
  pendingApprovalFor(_projectRoot, sessionId) {
664
737
  return this.store.listApprovals(sessionId).find((approval) => approval.status === "pending")?.id ?? null;
665
738
  }
666
- listSessionsForRef(projectRoot, agentId, source, sourceRef, limit) {
667
- return this.store.listSessionsForRef(agentProjectKey(projectRoot), agentId, source, sourceRef, limit);
739
+ listSessionsForRef(projectRoot, agentId, source, sourceRef, limit, includeEval) {
740
+ return this.store.listSessionsForRef(agentProjectKey(projectRoot), agentId, source, sourceRef, limit, includeEval);
668
741
  }
669
742
  sessionInProject(projectRoot, sessionId) {
670
743
  const session = this.store.getSession(sessionId);
@@ -698,7 +771,7 @@ export class AgentService {
698
771
  const since = new Date(this.now().getTime() - RECENT_WINDOW_MS).toISOString();
699
772
  return {
700
773
  ...this.store.listSessions(projectKey, agentId, query),
701
- stats: this.store.sessionStats(projectKey, agentId, since),
774
+ stats: this.store.sessionStats(projectKey, agentId, since, query.includeEval),
702
775
  };
703
776
  });
704
777
  }
@@ -711,7 +784,7 @@ export class AgentService {
711
784
  const since = new Date(this.now().getTime() - RECENT_WINDOW_MS).toISOString();
712
785
  return {
713
786
  ...this.store.listSessions(projectKey, agentId, query),
714
- stats: this.store.sessionStats(projectKey, agentId, since),
787
+ stats: this.store.sessionStats(projectKey, agentId, since, query.includeEval),
715
788
  };
716
789
  });
717
790
  }
@@ -745,11 +818,11 @@ export class AgentService {
745
818
  await this.sync(projectRoot);
746
819
  const session = this.sessionInProject(projectRoot, sessionId);
747
820
  const { revision: live } = this.currentRevision(projectRoot, session.agentId);
748
- if (!live.definition.enabled)
821
+ if (!live.definition.enabled && !isEvalSession(session))
749
822
  throw new AgentError("agent.disabled");
750
823
  if (session.status === "running" || session.status === "waiting")
751
824
  throw new AgentError("agent.session_busy");
752
- const definition = await this.definitionAt(projectRoot, session.agentId, session.revision);
825
+ const definition = await this.effectiveDefinition(projectRoot, session);
753
826
  this.assertWithinLimits(definition, session);
754
827
  const now = this.iso();
755
828
  return this.store.transaction(() => {
@@ -930,9 +1003,9 @@ export class AgentService {
930
1003
  this.expireApprovals();
931
1004
  const session = this.sessionInProject(projectRoot, sessionId);
932
1005
  const { revision: live } = this.currentRevision(projectRoot, session.agentId);
933
- if (!live.definition.enabled)
1006
+ if (!live.definition.enabled && !isEvalSession(session))
934
1007
  throw new AgentError("agent.disabled");
935
- const definition = await this.definitionAt(projectRoot, session.agentId, session.revision);
1008
+ const definition = await this.effectiveDefinition(projectRoot, session);
936
1009
  if (!isToolGranted(definition, input.tool))
937
1010
  throw new AgentError("agent.tool_not_granted");
938
1011
  if (!requiresAskFirst(definition, input.tool))
@@ -975,7 +1048,9 @@ export class AgentService {
975
1048
  return this.exclusive(projectRoot, async () => {
976
1049
  await this.sync(projectRoot);
977
1050
  const session = this.sessionInProject(projectRoot, sessionId);
978
- const definition = await this.definitionAt(projectRoot, session.agentId, session.revision);
1051
+ if (isEvalSession(session))
1052
+ throw new AgentError("agent.session_eval_readonly", "Eval sessions are read only");
1053
+ const definition = await this.effectiveDefinition(projectRoot, session);
979
1054
  if (!canApprove(definition, actor))
980
1055
  throw new AgentError("agent.approval_forbidden");
981
1056
  const pending = this.store.getPendingApproval(sessionId);
@@ -997,9 +1072,9 @@ export class AgentService {
997
1072
  return decided.approval;
998
1073
  });
999
1074
  }
1000
- async pendingApprovals(projectRoot) {
1075
+ async pendingApprovals(projectRoot, options = {}) {
1001
1076
  this.expireApprovals();
1002
- return this.store.listPendingApprovals(agentProjectKey(projectRoot));
1077
+ return this.store.listPendingApprovals(agentProjectKey(projectRoot), options.includeEval);
1003
1078
  }
1004
1079
  /* ---------- communications ---------- */
1005
1080
  async receive(projectRoot, message) {
@@ -1,3 +1,5 @@
1
+ import type { EvalGateRegistry } from "./evalGate.js";
2
+ import { type EvalSessionOverrides } from "./evalSession.js";
1
3
  import type { OtlpSink } from "./otlpSink.js";
2
4
  import type { ConversationBackend } from "./runtime/types.js";
3
5
  import type { AgentService, AgentSessionDetail } from "./service.js";
@@ -11,6 +13,10 @@ export type SendTurnInput = {
11
13
  author?: string | null;
12
14
  /** Where a new session came from: console, cli, mcp, schedule, or a provider id. */
13
15
  source?: string;
16
+ /** Where a new session points to; defaults to the author. Eval sessions point at their eval run or parent session. */
17
+ sourceRef?: string | null;
18
+ /** Overrides for a new eval session (needs `source: "eval"`). */
19
+ eval?: EvalSessionOverrides;
14
20
  onDelta?: (text: string) => void;
15
21
  onSessionOpened?: (sessionId: string) => void;
16
22
  /** Retry this earlier, interrupted turn: its user message is reused, not asked again. */
@@ -20,6 +26,8 @@ export type ConversationOptions = {
20
26
  service: AgentService;
21
27
  backend: ConversationBackend;
22
28
  otlpSink?: OtlpSink | undefined;
29
+ /** Eval tool gates by session id. An eval session without one is denied every tool. */
30
+ evalGates?: EvalGateRegistry;
23
31
  logError?: (message: string) => void;
24
32
  };
25
33
  /** One turn: open (if needed) → record user message → model + tools → guardrails → record reply. */
@@ -1,6 +1,7 @@
1
1
  import { randomBytes } from "node:crypto";
2
2
  import { assertDelegateAllowed } from "./delegate.js";
3
3
  import { AgentError, isAgentError } from "./errors.js";
4
+ import { EVAL_SESSION_SOURCE, isEvalSession } from "./evalSession.js";
4
5
  import { guardedStream } from "./guardrails.js";
5
6
  import { fitHistory } from "./historyFit.js";
6
7
  import { isToolGranted, requiresAskFirst } from "./policy.js";
@@ -51,7 +52,8 @@ export class AgentConversation {
51
52
  if (!sessionId) {
52
53
  const opened = await service.openSession(input.projectRoot, input.agentId, {
53
54
  source: input.source ?? "console",
54
- sourceRef: input.author ?? null,
55
+ sourceRef: input.sourceRef !== undefined ? input.sourceRef : (input.author ?? null),
56
+ ...(input.eval ? { eval: input.eval } : {}),
55
57
  });
56
58
  sessionId = opened.id;
57
59
  input.onSessionOpened?.(sessionId);
@@ -98,9 +100,31 @@ export class AgentConversation {
98
100
  service.recordSpan(input.projectRoot, span);
99
101
  };
100
102
  const toolStarts = new Map();
103
+ const childSessions = new Map();
104
+ const childSessionOf = (call) => (isEvalSession(active.session) ? (childSessions.get(call.id) ?? childSessions.get(call.tool)) : undefined);
101
105
  let stopReason;
102
106
  let gateChain = Promise.resolve();
103
107
  const gateOne = async (call) => {
108
+ if (active.session.source === EVAL_SESSION_SOURCE) {
109
+ const gate = this.options.evalGates?.get(sessionId);
110
+ if (!gate)
111
+ return { allow: false, reason: "No eval policy is registered for this session." };
112
+ const decision = gate.decide(call, definition);
113
+ if (gate.recordOf(call.id)?.policyOutcome === "would_ask_first") {
114
+ const at = new Date();
115
+ record({
116
+ turnId: turn.id,
117
+ parentSpanId: rootId,
118
+ name: `approval ${call.tool}`,
119
+ kind: "gate",
120
+ status: "error",
121
+ startedAt: at,
122
+ endedAt: at,
123
+ attributes: { "stageflow.approval.outcome": "would_ask_first", "gen_ai.tool.name": call.tool },
124
+ });
125
+ }
126
+ return decision;
127
+ }
104
128
  if (!isToolGranted(definition, call.tool)) {
105
129
  return { allow: false, reason: `Tool ${call.tool} is not granted to this agent.` };
106
130
  }
@@ -162,6 +186,8 @@ export class AgentConversation {
162
186
  toolStarts.set(call.id, new Date());
163
187
  },
164
188
  toolFinished: (call, result) => {
189
+ if (active.session.source === EVAL_SESSION_SOURCE)
190
+ this.options.evalGates?.get(sessionId)?.observeFinished(call, result);
165
191
  const isAgent = call.tool.startsWith("agent:");
166
192
  record({
167
193
  turnId: turn.id,
@@ -171,7 +197,11 @@ export class AgentConversation {
171
197
  status: result.ok ? "ok" : "error",
172
198
  startedAt: toolStarts.get(call.id) ?? new Date(),
173
199
  endedAt: new Date(),
174
- attributes: { "gen_ai.tool.name": call.tool },
200
+ attributes: {
201
+ "gen_ai.tool.name": call.tool,
202
+ ...(isEvalSession(active.session) ? { "gen_ai.tool.call.id": call.id } : {}),
203
+ ...(childSessionOf(call) ? { "stageflow.sub_agent.session_id": childSessionOf(call) } : {}),
204
+ },
175
205
  input: toJson(call.input),
176
206
  output: result.output,
177
207
  });
@@ -193,16 +223,26 @@ export class AgentConversation {
193
223
  },
194
224
  });
195
225
  },
196
- delegate: async (agentId, message) => {
226
+ delegate: async (agentId, message, callId) => {
197
227
  assertDelegateAllowed(definition, agentId, this.depth);
198
228
  const child = new AgentConversation(this.options, this.depth + 1, controller.signal);
229
+ const inEval = isEvalSession(active.session);
230
+ const inherited = { ...service.evalOverridesOf(active.session.id) };
231
+ delete inherited.revision;
199
232
  try {
200
233
  const detail = await child.send({
201
234
  projectRoot: input.projectRoot,
202
235
  agentId,
203
236
  text: message,
204
- source: "agent",
237
+ source: inEval ? EVAL_SESSION_SOURCE : "agent",
205
238
  author: active.session.agentId,
239
+ ...(inEval ? { sourceRef: active.session.id, eval: inherited } : {}),
240
+ onSessionOpened: (childId) => {
241
+ childSessions.set(callId ?? `agent:${agentId}`, childId);
242
+ const parentGate = inEval ? this.options.evalGates?.get(sessionId) : undefined;
243
+ if (parentGate)
244
+ this.options.evalGates.set(childId, parentGate);
245
+ },
206
246
  });
207
247
  return [...detail.messages].reverse().find((m) => m.role === "assistant")?.text ?? "(no reply)";
208
248
  }
@@ -230,7 +270,7 @@ export class AgentConversation {
230
270
  const code = isAgentError(error) ? error.code : "agent.backend_failed";
231
271
  record({ turnId: turn.id, name: `invoke_agent ${definition.id}`, kind: "agent", status: "error", startedAt, endedAt: new Date(), attributes: { "error.type": code } });
232
272
  await service.completeTurn(input.projectRoot, turn.id, { status: "failed", errorCode: code });
233
- this.exportLive(input.projectRoot, definition, turn.id);
273
+ this.exportLive(input.projectRoot, active.session, definition, turn.id);
234
274
  throw error;
235
275
  }
236
276
  const cancelled = controller.signal.aborted;
@@ -264,10 +304,10 @@ export class AgentConversation {
264
304
  errorCode: "agent.guardrail_blocked",
265
305
  ...(usage ? { usage, costUsd: usage.costUsd } : {}),
266
306
  });
267
- this.exportLive(input.projectRoot, definition, turn.id);
307
+ this.exportLive(input.projectRoot, active.session, definition, turn.id);
268
308
  throw new AgentError("agent.guardrail_blocked", `Reply blocked by guardrail ${guard.pattern}`);
269
309
  }
270
- const evalScores = await this.score(input.projectRoot, definition, turn.id, rootId, result, controller.signal, cancelled || stopReason !== undefined);
310
+ const evalScores = await this.score(input.projectRoot, definition, turn.id, rootId, result, controller.signal, cancelled || stopReason !== undefined || isEvalSession(active.session));
271
311
  const failed = stopReason !== undefined;
272
312
  endRoot(cancelled ? "cancelled" : failed ? "error" : "ok");
273
313
  const detail = await service.completeTurn(input.projectRoot, turn.id, {
@@ -277,7 +317,7 @@ export class AgentConversation {
277
317
  ...(usage ? { usage, costUsd: usage.costUsd } : {}),
278
318
  ...(evalScores.length > 0 ? { evalScores } : {}),
279
319
  });
280
- this.exportLive(input.projectRoot, definition, turn.id);
320
+ this.exportLive(input.projectRoot, active.session, definition, turn.id);
281
321
  return detail;
282
322
  }
283
323
  async score(projectRoot, definition, turnId, rootId, result, signal, skip) {
@@ -305,9 +345,9 @@ export class AgentConversation {
305
345
  }
306
346
  }
307
347
  /** Best-effort push to the configured OTLP collector; a collector outage never fails a turn. */
308
- exportLive(projectRoot, definition, turnId) {
348
+ exportLive(projectRoot, session, definition, turnId) {
309
349
  const sink = this.options.otlpSink;
310
- if (!sink || !definition.observability.otlp || definition.observability.capture === "off")
350
+ if (!sink || isEvalSession(session) || !definition.observability.otlp || definition.observability.capture === "off")
311
351
  return;
312
352
  void (async () => {
313
353
  try {
@@ -149,6 +149,8 @@ export type SessionQuery = {
149
149
  limit: number;
150
150
  /** Opaque cursor from a previous page. */
151
151
  cursor?: string;
152
+ /** Eval sessions are hidden unless an internal reader (eval read models, tests) asks for them. */
153
+ includeEval?: boolean;
152
154
  };
153
155
  export type SessionStats = {
154
156
  total: number;
@@ -204,9 +206,18 @@ export declare class AgentStore {
204
206
  nextCursor: string | null;
205
207
  };
206
208
  /** One visitor's sessions on one agent, newest first (public web chat). */
207
- listSessionsForRef(projectKey: string, agentId: string, source: string, sourceRef: string, limit: number): AgentSession[];
209
+ listSessionsForRef(projectKey: string, agentId: string, source: string, sourceRef: string, limit: number, includeEval?: boolean): AgentSession[];
208
210
  /** Stats for one agent, or for every agent when `agentId` is omitted. */
209
- sessionStats(projectKey: string, agentId: string | undefined, since: string): SessionStats;
211
+ sessionStats(projectKey: string, agentId: string | undefined, since: string, includeEval?: boolean): SessionStats;
212
+ /** Eval sessions below a session or an eval run: those whose `source_ref` is it, and theirs, recursively. */
213
+ listChildSessions(parentId: string): AgentSession[];
214
+ /** Root eval sessions (those pointing at an eval run, not at a parent session), optionally of given runs or older than a time. */
215
+ listEvalRootSessions(filter?: {
216
+ sourceRefs?: string[];
217
+ createdBefore?: string;
218
+ }): AgentSession[];
219
+ /** Delete eval sessions; messages, turns, spans, scores and approvals go with them. Never touches other sessions. */
220
+ deleteSessions(sessionIds: string[]): void;
210
221
  /** Ids of every session an agent has had, for releasing what a runtime holds for them. */
211
222
  sessionIdsOf(projectKey: string, agentId: string): string[];
212
223
  appendMessage(message: Omit<AgentMessage, "seq">): AgentMessage;
@@ -250,7 +261,7 @@ export declare class AgentStore {
250
261
  getPendingApproval(sessionId: string): AgentApproval | undefined;
251
262
  findApprovalByPayload(sessionId: string, payloadHash: string): AgentApproval | undefined;
252
263
  listApprovals(sessionId: string): AgentApproval[];
253
- listPendingApprovals(projectKey: string): Array<AgentApproval & {
264
+ listPendingApprovals(projectKey: string, includeEval?: boolean): Array<AgentApproval & {
254
265
  agentId: string;
255
266
  }>;
256
267
  decideApproval(approvalId: string, patch: {
@@ -9,6 +9,7 @@ export const SESSION_STATUSES = ["open", "running", "waiting", "held", "closed"]
9
9
  export function isSessionStatus(value) {
10
10
  return SESSION_STATUSES.includes(value);
11
11
  }
12
+ const HIDE_EVAL = "source <> 'eval'";
12
13
  export function agentProjectKey(projectRoot) {
13
14
  return createHash("sha256").update(path.resolve(projectRoot)).digest("hex").slice(0, 16);
14
15
  }
@@ -274,6 +275,8 @@ export class AgentStore {
274
275
  /** One agent's sessions, or every agent's when `agentId` is omitted. */
275
276
  listSessions(projectKey, agentId, query) {
276
277
  const where = ["project_key = @projectKey"];
278
+ if (!query.includeEval)
279
+ where.push(HIDE_EVAL);
277
280
  const params = { projectKey, limit: query.limit + 1 };
278
281
  if (agentId !== undefined) {
279
282
  where.push("agent_id = @agentId");
@@ -327,17 +330,17 @@ export class AgentStore {
327
330
  };
328
331
  }
329
332
  /** One visitor's sessions on one agent, newest first (public web chat). */
330
- listSessionsForRef(projectKey, agentId, source, sourceRef, limit) {
333
+ listSessionsForRef(projectKey, agentId, source, sourceRef, limit, includeEval = false) {
331
334
  return this.db
332
335
  .prepare(`SELECT * FROM agent_sessions
333
- WHERE project_key = ? AND agent_id = ? AND source = ? AND source_ref = ?
336
+ WHERE project_key = ? AND agent_id = ? AND source = ? AND source_ref = ?${includeEval ? "" : ` AND ${HIDE_EVAL}`}
334
337
  ORDER BY updated_at DESC, session_id DESC LIMIT ?`)
335
338
  .all(projectKey, agentId, source, sourceRef, limit).map(toSession);
336
339
  }
337
340
  /** Stats for one agent, or for every agent when `agentId` is omitted. */
338
- sessionStats(projectKey, agentId, since) {
339
- const agentFilter = agentId === undefined ? "" : " AND agent_id = @agentId";
340
- const sessionAgentFilter = agentId === undefined ? "" : " AND s.agent_id = @agentId";
341
+ sessionStats(projectKey, agentId, since, includeEval = false) {
342
+ const agentFilter = (agentId === undefined ? "" : " AND agent_id = @agentId") + (includeEval ? "" : ` AND ${HIDE_EVAL}`);
343
+ const sessionAgentFilter = (agentId === undefined ? "" : " AND s.agent_id = @agentId") + (includeEval ? "" : ` AND s.${HIDE_EVAL}`);
341
344
  const bind = (extra) => (agentId === undefined ? extra : { ...extra, agentId });
342
345
  const row = this.db
343
346
  .prepare(`SELECT COUNT(*) AS total,
@@ -367,6 +370,41 @@ export class AgentStore {
367
370
  costTotal: numOrNull(turns.cost_all),
368
371
  };
369
372
  }
373
+ /** Eval sessions below a session or an eval run: those whose `source_ref` is it, and theirs, recursively. */
374
+ listChildSessions(parentId) {
375
+ return this.db
376
+ .prepare(`WITH RECURSIVE below(session_id) AS (
377
+ SELECT session_id FROM agent_sessions WHERE source = 'eval' AND source_ref = ?
378
+ UNION
379
+ SELECT s.session_id FROM agent_sessions s JOIN below b ON s.source_ref = b.session_id WHERE s.source = 'eval'
380
+ )
381
+ SELECT s.* FROM agent_sessions s JOIN below b ON b.session_id = s.session_id ORDER BY s.created_at, s.session_id`)
382
+ .all(parentId).map(toSession);
383
+ }
384
+ /** Root eval sessions (those pointing at an eval run, not at a parent session), optionally of given runs or older than a time. */
385
+ listEvalRootSessions(filter = {}) {
386
+ const clauses = [`source = 'eval'`, `(source_ref IS NULL OR source_ref NOT IN (SELECT session_id FROM agent_sessions))`];
387
+ const params = [];
388
+ if (filter.sourceRefs) {
389
+ if (filter.sourceRefs.length === 0)
390
+ return [];
391
+ clauses.push(`source_ref IN (${filter.sourceRefs.map(() => "?").join(", ")})`);
392
+ params.push(...filter.sourceRefs);
393
+ }
394
+ if (filter.createdBefore) {
395
+ clauses.push(`created_at < ?`);
396
+ params.push(filter.createdBefore);
397
+ }
398
+ return this.db.prepare(`SELECT * FROM agent_sessions WHERE ${clauses.join(" AND ")} ORDER BY created_at, session_id`).all(...params).map(toSession);
399
+ }
400
+ /** Delete eval sessions; messages, turns, spans, scores and approvals go with them. Never touches other sessions. */
401
+ deleteSessions(sessionIds) {
402
+ this.transaction(() => {
403
+ const remove = this.db.prepare(`DELETE FROM agent_sessions WHERE session_id = ? AND source = 'eval'`);
404
+ for (const sessionId of sessionIds)
405
+ remove.run(sessionId);
406
+ });
407
+ }
370
408
  /** Ids of every session an agent has had, for releasing what a runtime holds for them. */
371
409
  sessionIdsOf(projectKey, agentId) {
372
410
  return this.db.prepare(`SELECT session_id FROM agent_sessions WHERE project_key = ? AND agent_id = ?`).all(projectKey, agentId).map((row) => str(row.session_id));
@@ -562,10 +600,10 @@ export class AgentStore {
562
600
  .prepare(`SELECT * FROM agent_approvals WHERE session_id = ? ORDER BY created_at`)
563
601
  .all(sessionId).map(toApproval);
564
602
  }
565
- listPendingApprovals(projectKey) {
603
+ listPendingApprovals(projectKey, includeEval = false) {
566
604
  return this.db
567
605
  .prepare(`SELECT a.*, s.agent_id FROM agent_approvals a JOIN agent_sessions s ON s.session_id = a.session_id
568
- WHERE s.project_key = ? AND a.status = 'pending' ORDER BY a.created_at`)
606
+ WHERE s.project_key = ? AND a.status = 'pending'${includeEval ? "" : ` AND s.${HIDE_EVAL}`} ORDER BY a.created_at`)
569
607
  .all(projectKey).map((row) => ({ ...toApproval(row), agentId: str(row.agent_id) }));
570
608
  }
571
609
  decideApproval(approvalId, patch) {