stageflow 0.40.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 (326) hide show
  1. package/README.md +3 -1
  2. package/dist/agents/authorChat.d.ts +4 -0
  3. package/dist/agents/authorChat.js +113 -12
  4. package/dist/agents/authorSessionStore.d.ts +35 -0
  5. package/dist/agents/authorSessionStore.js +152 -0
  6. package/dist/agents/chatTurn.d.ts +2 -0
  7. package/dist/agents/chatTurn.js +17 -1
  8. package/dist/agents/errors.d.ts +1 -1
  9. package/dist/agents/errors.js +1 -0
  10. package/dist/agents/evalGate.d.ts +61 -0
  11. package/dist/agents/evalGate.js +103 -0
  12. package/dist/agents/evalSession.d.ts +26 -0
  13. package/dist/agents/evalSession.js +29 -0
  14. package/dist/agents/hostContext.d.ts +3 -0
  15. package/dist/agents/hostContext.js +4 -1
  16. package/dist/agents/runtime/piRuntime.d.ts +1 -1
  17. package/dist/agents/runtime/piRuntime.js +4 -4
  18. package/dist/agents/runtime/types.d.ts +3 -1
  19. package/dist/agents/service.d.ts +27 -2
  20. package/dist/agents/service.js +88 -13
  21. package/dist/agents/sessions.d.ts +8 -0
  22. package/dist/agents/sessions.js +50 -10
  23. package/dist/agents/store.d.ts +14 -3
  24. package/dist/agents/store.js +45 -7
  25. package/dist/agents/toolRisk.d.ts +21 -0
  26. package/dist/agents/toolRisk.js +50 -0
  27. package/dist/cli/doctorCommand.js +26 -0
  28. package/dist/cli/evalCommand.d.ts +20 -0
  29. package/dist/cli/evalCommand.js +720 -0
  30. package/dist/cli/exportAllCommand.js +1 -1
  31. package/dist/cli/hostClient.d.ts +60 -0
  32. package/dist/cli/hostClient.js +71 -0
  33. package/dist/cli/validateOutput.d.ts +2 -2
  34. package/dist/cli/validateOutput.js +2 -2
  35. package/dist/cli.js +10 -0
  36. package/dist/config/atomicWrite.d.ts +15 -0
  37. package/dist/config/atomicWrite.js +43 -0
  38. package/dist/config/browseCatalog.d.ts +2 -0
  39. package/dist/config/browseCatalog.js +4 -2
  40. package/dist/config/csv.d.ts +9 -0
  41. package/dist/config/csv.js +81 -0
  42. package/dist/config/datasetAgentCheck.d.ts +18 -0
  43. package/dist/config/datasetAgentCheck.js +109 -0
  44. package/dist/config/datasetCandidates.d.ts +22 -0
  45. package/dist/config/datasetCandidates.js +86 -0
  46. package/dist/config/datasetFromSessions.d.ts +34 -0
  47. package/dist/config/datasetFromSessions.js +105 -0
  48. package/dist/config/datasetHash.d.ts +1 -0
  49. package/dist/config/datasetHash.js +4 -0
  50. package/dist/config/datasetImport.d.ts +21 -0
  51. package/dist/config/datasetImport.js +120 -0
  52. package/dist/config/datasetStageCheck.d.ts +9 -0
  53. package/dist/config/datasetStageCheck.js +17 -0
  54. package/dist/config/datasetTypes.d.ts +27 -0
  55. package/dist/config/datasetTypes.js +1 -0
  56. package/dist/config/ensureCatalogScanRoot.d.ts +2 -1
  57. package/dist/config/ensureCatalogScanRoot.js +2 -6
  58. package/dist/config/evalCatalog.d.ts +22 -0
  59. package/dist/config/evalCatalog.js +67 -0
  60. package/dist/config/evalWriteRoots.d.ts +17 -0
  61. package/dist/config/evalWriteRoots.js +109 -0
  62. package/dist/config/hostConfig.js +1 -0
  63. package/dist/config/installEvalSample.d.ts +34 -0
  64. package/dist/config/installEvalSample.js +182 -0
  65. package/dist/config/loadDataset.d.ts +16 -0
  66. package/dist/config/loadDataset.js +318 -0
  67. package/dist/config/loadEval.d.ts +32 -0
  68. package/dist/config/loadEval.js +1085 -0
  69. package/dist/config/loadOutcome.d.ts +5 -0
  70. package/dist/config/loadStageflowManifest.js +64 -0
  71. package/dist/config/resolveAgentTarget.d.ts +32 -0
  72. package/dist/config/resolveAgentTarget.js +118 -0
  73. package/dist/config/resolveEvalTarget.d.ts +21 -0
  74. package/dist/config/resolveEvalTarget.js +89 -0
  75. package/dist/config/scanCatalogPaths.d.ts +1 -1
  76. package/dist/config/scanCatalogPaths.js +17 -5
  77. package/dist/config/validateCatalog.d.ts +1 -1
  78. package/dist/config/validateCatalog.js +9 -1
  79. package/dist/config/validateEval.d.ts +36 -0
  80. package/dist/config/validateEval.js +405 -0
  81. package/dist/config/writeDataset.d.ts +54 -0
  82. package/dist/config/writeDataset.js +186 -0
  83. package/dist/config/writeEval.d.ts +25 -0
  84. package/dist/config/writeEval.js +398 -0
  85. package/dist/diagnostics/checks.js +1 -1
  86. package/dist/evals/agentDiff.d.ts +13 -0
  87. package/dist/evals/agentDiff.js +11 -0
  88. package/dist/evals/canonical.d.ts +3 -0
  89. package/dist/evals/canonical.js +24 -0
  90. package/dist/evals/caseTask.d.ts +3 -0
  91. package/dist/evals/caseTask.js +9 -0
  92. package/dist/evals/compare.d.ts +94 -0
  93. package/dist/evals/compare.js +201 -0
  94. package/dist/evals/constants.d.ts +60 -0
  95. package/dist/evals/constants.js +95 -0
  96. package/dist/evals/engine/FakeEngine.d.ts +33 -0
  97. package/dist/evals/engine/FakeEngine.js +176 -0
  98. package/dist/evals/engine/index.d.ts +9 -0
  99. package/dist/evals/engine/index.js +19 -0
  100. package/dist/evals/engine/promptfoo/PromptfooEngine.d.ts +52 -0
  101. package/dist/evals/engine/promptfoo/PromptfooEngine.js +311 -0
  102. package/dist/evals/engine/promptfoo/errors.d.ts +5 -0
  103. package/dist/evals/engine/promptfoo/errors.js +8 -0
  104. package/dist/evals/engine/promptfoo/generate.d.ts +30 -0
  105. package/dist/evals/engine/promptfoo/generate.js +342 -0
  106. package/dist/evals/engine/promptfoo/guard.d.ts +10 -0
  107. package/dist/evals/engine/promptfoo/guard.js +116 -0
  108. package/dist/evals/engine/promptfoo/install.d.ts +17 -0
  109. package/dist/evals/engine/promptfoo/install.js +73 -0
  110. package/dist/evals/engine/promptfoo/judgeProviders.d.ts +2 -0
  111. package/dist/evals/engine/promptfoo/judgeProviders.js +9 -0
  112. package/dist/evals/engine/promptfoo/judgeServices.d.ts +6 -0
  113. package/dist/evals/engine/promptfoo/judgeServices.js +41 -0
  114. package/dist/evals/engine/promptfoo/locate.d.ts +21 -0
  115. package/dist/evals/engine/promptfoo/locate.js +68 -0
  116. package/dist/evals/engine/promptfoo/nodeVersion.d.ts +7 -0
  117. package/dist/evals/engine/promptfoo/nodeVersion.js +14 -0
  118. package/dist/evals/engine/promptfoo/normalize.d.ts +66 -0
  119. package/dist/evals/engine/promptfoo/normalize.js +261 -0
  120. package/dist/evals/engine/promptfoo/pinned.d.ts +5 -0
  121. package/dist/evals/engine/promptfoo/pinned.js +5 -0
  122. package/dist/evals/engine/promptfoo/rubricPrompt.d.ts +14 -0
  123. package/dist/evals/engine/promptfoo/rubricPrompt.js +103 -0
  124. package/dist/evals/engine/promptfoo/runTokens.d.ts +18 -0
  125. package/dist/evals/engine/promptfoo/runTokens.js +31 -0
  126. package/dist/evals/engine/promptfoo/status.d.ts +13 -0
  127. package/dist/evals/engine/promptfoo/status.js +16 -0
  128. package/dist/evals/estimate.d.ts +56 -0
  129. package/dist/evals/estimate.js +66 -0
  130. package/dist/evals/graders/codeGrader.d.ts +6 -0
  131. package/dist/evals/graders/codeGrader.js +85 -0
  132. package/dist/evals/graders/conversationCheckGrader.d.ts +6 -0
  133. package/dist/evals/graders/conversationCheckGrader.js +62 -0
  134. package/dist/evals/graders/fieldGrader.d.ts +11 -0
  135. package/dist/evals/graders/fieldGrader.js +117 -0
  136. package/dist/evals/graders/goal.d.ts +3 -0
  137. package/dist/evals/graders/goal.js +14 -0
  138. package/dist/evals/graders/index.d.ts +18 -0
  139. package/dist/evals/graders/index.js +34 -0
  140. package/dist/evals/graders/notApplicable.d.ts +5 -0
  141. package/dist/evals/graders/notApplicable.js +9 -0
  142. package/dist/evals/graders/path.d.ts +26 -0
  143. package/dist/evals/graders/path.js +75 -0
  144. package/dist/evals/graders/referenceGrader.d.ts +5 -0
  145. package/dist/evals/graders/referenceGrader.js +59 -0
  146. package/dist/evals/graders/schemaGrader.d.ts +5 -0
  147. package/dist/evals/graders/schemaGrader.js +29 -0
  148. package/dist/evals/graders/subject.d.ts +6 -0
  149. package/dist/evals/graders/subject.js +33 -0
  150. package/dist/evals/graders/toolCallsGrader.d.ts +6 -0
  151. package/dist/evals/graders/toolCallsGrader.js +100 -0
  152. package/dist/evals/graders/transcript.d.ts +12 -0
  153. package/dist/evals/graders/transcript.js +57 -0
  154. package/dist/evals/graders/types.d.ts +20 -0
  155. package/dist/evals/graders/types.js +1 -0
  156. package/dist/evals/judgeProviders.d.ts +16 -0
  157. package/dist/evals/judgeProviders.js +27 -0
  158. package/dist/evals/lineDiff.d.ts +5 -0
  159. package/dist/evals/lineDiff.js +34 -0
  160. package/dist/evals/listStatus.d.ts +31 -0
  161. package/dist/evals/listStatus.js +48 -0
  162. package/dist/evals/manager.d.ts +174 -0
  163. package/dist/evals/manager.js +787 -0
  164. package/dist/evals/plan.d.ts +58 -0
  165. package/dist/evals/plan.js +602 -0
  166. package/dist/evals/providerKeys.d.ts +1 -0
  167. package/dist/evals/providerKeys.js +21 -0
  168. package/dist/evals/read/agentTargets.d.ts +37 -0
  169. package/dist/evals/read/agentTargets.js +101 -0
  170. package/dist/evals/read/api.d.ts +196 -0
  171. package/dist/evals/read/api.js +358 -0
  172. package/dist/evals/read/compare.d.ts +4 -0
  173. package/dist/evals/read/compare.js +39 -0
  174. package/dist/evals/read/datasets.d.ts +105 -0
  175. package/dist/evals/read/datasets.js +318 -0
  176. package/dist/evals/read/list.d.ts +66 -0
  177. package/dist/evals/read/list.js +173 -0
  178. package/dist/evals/read/results.d.ts +46 -0
  179. package/dist/evals/read/results.js +107 -0
  180. package/dist/evals/read/runs.d.ts +149 -0
  181. package/dist/evals/read/runs.js +289 -0
  182. package/dist/evals/read/suggestions.d.ts +43 -0
  183. package/dist/evals/read/suggestions.js +73 -0
  184. package/dist/evals/runner/FakeAgentBackend.d.ts +40 -0
  185. package/dist/evals/runner/FakeAgentBackend.js +82 -0
  186. package/dist/evals/runner/agentCaseRunner.d.ts +24 -0
  187. package/dist/evals/runner/agentCaseRunner.js +388 -0
  188. package/dist/evals/runner/caseRunner.d.ts +7 -0
  189. package/dist/evals/runner/caseRunner.js +17 -0
  190. package/dist/evals/runner/conversationSubject.d.ts +18 -0
  191. package/dist/evals/runner/conversationSubject.js +123 -0
  192. package/dist/evals/runner/simulatorModel.d.ts +51 -0
  193. package/dist/evals/runner/simulatorModel.js +58 -0
  194. package/dist/evals/runner/simulatorPrompt.d.ts +29 -0
  195. package/dist/evals/runner/simulatorPrompt.js +59 -0
  196. package/dist/evals/runner/stageCaseRunner.d.ts +30 -0
  197. package/dist/evals/runner/stageCaseRunner.js +279 -0
  198. package/dist/evals/runner/userDriver.d.ts +71 -0
  199. package/dist/evals/runner/userDriver.js +104 -0
  200. package/dist/evals/scoring.d.ts +34 -0
  201. package/dist/evals/scoring.js +67 -0
  202. package/dist/evals/snapshot.d.ts +183 -0
  203. package/dist/evals/snapshot.js +65 -0
  204. package/dist/evals/stageDiff.d.ts +8 -0
  205. package/dist/evals/stageDiff.js +48 -0
  206. package/dist/evals/stats.d.ts +95 -0
  207. package/dist/evals/stats.js +220 -0
  208. package/dist/evals/statsAdapter.d.ts +4 -0
  209. package/dist/evals/statsAdapter.js +30 -0
  210. package/dist/evals/statsTypes.d.ts +51 -0
  211. package/dist/evals/statsTypes.js +18 -0
  212. package/dist/evals/storeError.d.ts +5 -0
  213. package/dist/evals/storeError.js +8 -0
  214. package/dist/evals/storeTypes.d.ts +207 -0
  215. package/dist/evals/storeTypes.js +1 -0
  216. package/dist/evals/tTable.d.ts +3 -0
  217. package/dist/evals/tTable.js +15 -0
  218. package/dist/evals/triage/constants.d.ts +26 -0
  219. package/dist/evals/triage/constants.js +33 -0
  220. package/dist/evals/triage/credentials.d.ts +13 -0
  221. package/dist/evals/triage/credentials.js +20 -0
  222. package/dist/evals/triage/jevClassifier.d.ts +14 -0
  223. package/dist/evals/triage/jevClassifier.js +124 -0
  224. package/dist/evals/triage/judgeClassifier.d.ts +10 -0
  225. package/dist/evals/triage/judgeClassifier.js +76 -0
  226. package/dist/evals/triage/prompts.d.ts +16 -0
  227. package/dist/evals/triage/prompts.js +64 -0
  228. package/dist/evals/triage/service.d.ts +79 -0
  229. package/dist/evals/triage/service.js +217 -0
  230. package/dist/evals/triage/stateBuilder.d.ts +19 -0
  231. package/dist/evals/triage/stateBuilder.js +125 -0
  232. package/dist/evals/triage/types.d.ts +60 -0
  233. package/dist/evals/triage/types.js +8 -0
  234. package/dist/evals/types.d.ts +347 -0
  235. package/dist/evals/types.js +1 -0
  236. package/dist/mcp/deps.d.ts +7 -0
  237. package/dist/mcp/evalTools.d.ts +3 -0
  238. package/dist/mcp/evalTools.js +280 -0
  239. package/dist/mcp/server.js +1 -0
  240. package/dist/mcp/tools.js +2 -0
  241. package/dist/models/response.d.ts +7 -2
  242. package/dist/models/response.js +6 -1
  243. package/dist/operatorAgent/agentAuthorContext.d.ts +1 -1
  244. package/dist/operatorAgent/agentAuthorContext.js +1 -1
  245. package/dist/operatorAgent/piModel.js +17 -4
  246. package/dist/operatorAgent/profiles/agentAuthor.d.ts +3 -1
  247. package/dist/operatorAgent/profiles/agentAuthor.js +145 -20
  248. package/dist/operatorAgent/profiles/agentAuthorPlaybook.d.ts +1 -1
  249. package/dist/operatorAgent/profiles/agentAuthorPlaybook.js +48 -15
  250. package/dist/operatorAgent/profiles/authorReadable.d.ts +2 -0
  251. package/dist/operatorAgent/profiles/authorReadable.js +26 -0
  252. package/dist/package-meta.d.ts +1 -1
  253. package/dist/package-meta.js +1 -1
  254. package/dist/runstore/port.d.ts +43 -0
  255. package/dist/runstore/sqlite/SqliteRunStore.d.ts +28 -0
  256. package/dist/runstore/sqlite/SqliteRunStore.js +99 -5
  257. package/dist/runstore/sqlite/evalStore.d.ts +20 -0
  258. package/dist/runstore/sqlite/evalStore.js +541 -0
  259. package/dist/runstore/sqlite/migrations/015-evals.d.ts +12 -0
  260. package/dist/runstore/sqlite/migrations/015-evals.js +157 -0
  261. package/dist/runstore/sqlite/migrations/016-eval-subject-ref.d.ts +12 -0
  262. package/dist/runstore/sqlite/migrations/016-eval-subject-ref.js +20 -0
  263. package/dist/runstore/sqlite/migrations/017-eval-result-suggestions.d.ts +11 -0
  264. package/dist/runstore/sqlite/migrations/017-eval-result-suggestions.js +36 -0
  265. package/dist/runstore/sqlite/migrations/index.js +6 -0
  266. package/dist/runstore/sqlite/suggestionStore.d.ts +14 -0
  267. package/dist/runstore/sqlite/suggestionStore.js +132 -0
  268. package/dist/runtime/pipelineRunner.d.ts +2 -1
  269. package/dist/runtime/pipelineRunner.js +2 -0
  270. package/dist/runtime/runManager.d.ts +2 -1
  271. package/dist/runtime/runManager.js +8 -6
  272. package/dist/runtime/runRetentionSweep.js +2 -2
  273. package/dist/server/agentAuthorRoutes.js +50 -1
  274. package/dist/server/bootstrap.d.ts +8 -0
  275. package/dist/server/bootstrap.js +56 -0
  276. package/dist/server/controlToken.js +3 -0
  277. package/dist/server/evalInternalRoutes.d.ts +14 -0
  278. package/dist/server/evalInternalRoutes.js +87 -0
  279. package/dist/server/evalRouteContext.d.ts +46 -0
  280. package/dist/server/evalRouteContext.js +43 -0
  281. package/dist/server/evalRoutes.d.ts +5 -0
  282. package/dist/server/evalRoutes.js +1427 -0
  283. package/dist/server/evalTriageRoutes.d.ts +2 -0
  284. package/dist/server/evalTriageRoutes.js +95 -0
  285. package/dist/server/http.d.ts +5 -0
  286. package/dist/server/http.js +32 -2
  287. package/dist/server/mcpHost.js +3 -0
  288. package/dist/toolkit/meta.d.ts +1 -1
  289. package/dist/toolkit/meta.js +15 -0
  290. package/dist/toolkit/profiles.js +1 -1
  291. package/dist/types/stageflowManifest.d.ts +6 -0
  292. package/dist/ui/assets/AssistantRuntimeProvider-qIX1xNoH.js +9 -0
  293. package/dist/ui/assets/{MarkdownText-D7tYaIFO.js → MarkdownText-BTrOqu5J.js} +1 -1
  294. package/dist/ui/assets/chat-Df_wCpak.js +5 -0
  295. package/dist/ui/assets/chat-P7KQznHm.css +2 -0
  296. package/dist/ui/assets/{main-BFo79krC.css → main-CHlmTNJe.css} +1 -1
  297. package/dist/ui/assets/main-CvVfxmMd.js +271 -0
  298. package/dist/ui/chat.html +3 -3
  299. package/dist/ui/index.html +3 -3
  300. package/examples/README.md +2 -0
  301. package/examples/document-policy-review/README.md +35 -0
  302. package/examples/document-policy-review/agents/compliance.agent.yaml +42 -0
  303. package/examples/document-policy-review/documents/acme-vendor-sow.md +15 -0
  304. package/examples/document-policy-review/documents/support-runbook.md +13 -0
  305. package/examples/document-policy-review/policies/acceptable-use.md +13 -0
  306. package/examples/document-policy-review/policies/data-handling.md +17 -0
  307. package/examples/document-policy-review/policies/vendor-security.md +17 -0
  308. package/examples/evals/README.md +123 -0
  309. package/examples/evals/critique-sample.dataset.jsonl +8 -0
  310. package/examples/evals/critique-sample.eval.yaml +34 -0
  311. package/examples/evals/critique-sample.task.yaml +4 -0
  312. package/examples/evals/eval-sample.pipeline.yaml +35 -0
  313. package/examples/evals/support-sample.agent.yaml +20 -0
  314. package/examples/evals/support-sample.dataset.jsonl +6 -0
  315. package/examples/evals/support-sample.eval.yaml +38 -0
  316. package/examples/stageflow.yaml +6 -0
  317. package/package.json +1 -1
  318. package/skills/stageflow/SKILL.md +5 -2
  319. package/skills/stageflow/references/control-surface.md +4 -2
  320. package/skills/stageflow/references/evals.md +68 -0
  321. package/skills/stageflow-author/SKILL.md +4 -0
  322. package/skills/stageflow-run/SKILL.md +4 -0
  323. package/dist/ui/assets/AssistantRuntimeProvider-CsiPFhCT.js +0 -9
  324. package/dist/ui/assets/chat-BbLukq9W.js +0 -5
  325. package/dist/ui/assets/chat-zIZU_bP1.css +0 -2
  326. package/dist/ui/assets/main-CijYMeM0.js +0 -259
@@ -0,0 +1,29 @@
1
+ export const EVAL_SESSION_SOURCE = "eval";
2
+ export function isEvalSession(session) {
3
+ return session.source === EVAL_SESSION_SOURCE;
4
+ }
5
+ /**
6
+ * The definition an eval session runs on: the pinned revision with the eval's model, limits and capture, and with
7
+ * every side effect off (always-on, OTLP push, groundedness judge). Capture defaults to full so tool calls are recorded.
8
+ */
9
+ export function applyEvalOverrides(base, overrides) {
10
+ const { definition } = overrides;
11
+ const limits = {
12
+ ...base.limits,
13
+ ...definition?.limits,
14
+ ...(overrides.limits ? { cost_usd: null, ...overrides.limits } : {}),
15
+ always_on: false,
16
+ };
17
+ return {
18
+ ...base,
19
+ model: { ...base.model, ...definition?.model },
20
+ limits,
21
+ observability: {
22
+ ...base.observability,
23
+ ...definition?.observability,
24
+ capture: overrides.capture ?? definition?.observability?.capture ?? "full",
25
+ otlp: false,
26
+ },
27
+ evals: { ...base.evals, ...definition?.evals, groundedness: false },
28
+ };
29
+ }
@@ -9,12 +9,15 @@ import type { ContactInfo } from "../channels/types.js";
9
9
  import type { McpToolDeps } from "../mcp/deps.js";
10
10
  import { AgentService, type AgentEnvironment } from "./service.js";
11
11
  import type { ConversationBackend } from "./sessions.js";
12
+ import { EvalGateRegistry } from "./evalGate.js";
12
13
  import { PublicWebChat } from "./webChat/publicChat.js";
13
14
  export type AgentHostContext = {
14
15
  service: AgentService;
15
16
  inbound: AgentInboundRouter;
16
17
  publicChat: PublicWebChat;
17
18
  chat: AgentChatController;
19
+ /** Eval tool gates by session id; the eval runner registers a gate for each eval session it opens. */
20
+ evalGates: EvalGateRegistry;
18
21
  backend: ConversationBackend;
19
22
  /** Streams held open to AgentMail and iMessage; absent unless the Host listens (not in tests). */
20
23
  channelListeners?: ChannelSupervisor;
@@ -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: {