@inflexa-ai/harness 0.45.1 → 0.46.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 (789) hide show
  1. package/README.md +6 -3
  2. package/dist/agents/conversation-agent.d.ts +9 -3
  3. package/dist/agents/conversation-agent.js +34 -33
  4. package/dist/agents/report-session-agent.d.ts +15 -15
  5. package/dist/agents/report-session-agent.js +23 -15
  6. package/dist/agents/sandbox/index.d.ts +1 -1
  7. package/dist/agents/sandbox/index.js +1 -1
  8. package/dist/agents/sandbox/shared.d.ts +12 -14
  9. package/dist/agents/sandbox/shared.js +18 -12
  10. package/dist/agents/sandbox/types.d.ts +4 -4
  11. package/dist/agents/sandbox/validate-skills.d.ts +2 -2
  12. package/dist/agents/sandbox/validate-skills.js +3 -1
  13. package/dist/agents/sandbox-catalog.d.ts +2 -2
  14. package/dist/agents/sandbox-catalog.js +3 -3
  15. package/dist/agents/system-prompt.d.ts +2 -2
  16. package/dist/app/chat-turn-workflow.d.ts +114 -0
  17. package/dist/app/chat-turn-workflow.js +307 -0
  18. package/dist/app/chat-turn.d.ts +85 -57
  19. package/dist/app/chat-turn.js +123 -100
  20. package/dist/app/data-profile-orientation.js +10 -8
  21. package/dist/app/data-profile-view.d.ts +4 -4
  22. package/dist/app/data-profile-view.js +9 -8
  23. package/dist/app/in-process-turn.d.ts +42 -0
  24. package/dist/app/in-process-turn.js +139 -0
  25. package/dist/app/message-assembly.d.ts +10 -4
  26. package/dist/app/message-assembly.js +13 -8
  27. package/dist/app/report-session-runtime.d.ts +4 -4
  28. package/dist/app/report-session-runtime.js +36 -21
  29. package/dist/app/spawn-report-session.d.ts +33 -28
  30. package/dist/app/spawn-report-session.js +25 -40
  31. package/dist/app/synthesize-run.d.ts +22 -16
  32. package/dist/app/synthesize-run.js +41 -35
  33. package/dist/app/work.d.ts +96 -0
  34. package/dist/app/work.js +147 -0
  35. package/dist/auth/local-auth-context.d.ts +1 -3
  36. package/dist/auth/local-auth-context.js +1 -3
  37. package/dist/auth/local-run-authorizer.d.ts +1 -1
  38. package/dist/auth/local-run-authorizer.js +5 -3
  39. package/dist/auth/types.d.ts +26 -26
  40. package/dist/billing/cached-resolver.d.ts +4 -4
  41. package/dist/billing/run-charge.d.ts +5 -8
  42. package/dist/billing/run-charge.js +2 -2
  43. package/dist/billing/usage-recorder.d.ts +9 -9
  44. package/dist/citations/aggregate.js +1 -1
  45. package/dist/citations/clients/arxiv.d.ts +2 -2
  46. package/dist/citations/clients/arxiv.js +12 -4
  47. package/dist/citations/clients/common.js +4 -2
  48. package/dist/citations/clients/crossref.d.ts +2 -2
  49. package/dist/citations/clients/crossref.js +37 -37
  50. package/dist/citations/clients/doi-registry.js +15 -16
  51. package/dist/citations/clients/pubmed.d.ts +2 -2
  52. package/dist/citations/clients/pubmed.js +20 -13
  53. package/dist/citations/clients/semantic-scholar.d.ts +2 -2
  54. package/dist/citations/clients/semantic-scholar.js +9 -9
  55. package/dist/citations/compare.js +2 -2
  56. package/dist/citations/match.d.ts +2 -2
  57. package/dist/citations/match.js +12 -7
  58. package/dist/citations/normalize.js +35 -26
  59. package/dist/citations/plan.js +6 -2
  60. package/dist/citations/resolve.d.ts +6 -6
  61. package/dist/citations/resolve.js +34 -25
  62. package/dist/citations/types.d.ts +77 -77
  63. package/dist/config/environment-stores.d.ts +6 -6
  64. package/dist/config/resource-limits.js +1 -3
  65. package/dist/contracts/analogy-report.d.ts +7 -7
  66. package/dist/contracts/analogy-report.js +2 -2
  67. package/dist/contracts/chat-events.d.ts +38 -24
  68. package/dist/contracts/chat-frame.d.ts +15 -5
  69. package/dist/contracts/chat-frame.js +64 -41
  70. package/dist/contracts/chat-parts.d.ts +130 -67
  71. package/dist/contracts/chat-parts.js +1 -0
  72. package/dist/contracts/content-url.js +16 -2
  73. package/dist/contracts/data-profile.d.ts +38 -38
  74. package/dist/contracts/index.d.ts +2 -2
  75. package/dist/contracts/index.js +1 -1
  76. package/dist/contracts/message.d.ts +6 -6
  77. package/dist/contracts/organ-system.d.ts +11 -11
  78. package/dist/contracts/part-registry.d.ts +5 -4
  79. package/dist/contracts/part-registry.js +5 -0
  80. package/dist/contracts/profile-vocabulary.d.ts +11 -10
  81. package/dist/contracts/profile-vocabulary.js +10 -3
  82. package/dist/contracts/report-blocks.d.ts +329 -329
  83. package/dist/contracts/report-reference.d.ts +105 -81
  84. package/dist/contracts/report-reference.js +30 -13
  85. package/dist/contracts/schemas/chat-events.d.ts +30 -16
  86. package/dist/contracts/schemas/chat-events.js +15 -6
  87. package/dist/contracts/schemas/chat-parts.d.ts +175 -102
  88. package/dist/contracts/schemas/chat-parts.js +33 -7
  89. package/dist/contracts/severity.d.ts +1 -1
  90. package/dist/contracts/structure-source.d.ts +3 -3
  91. package/dist/contracts/structure-source.js +6 -2
  92. package/dist/contracts/subagent-activity.d.ts +2 -2
  93. package/dist/contracts/subagent-activity.js +5 -3
  94. package/dist/contracts/usage.d.ts +2 -2
  95. package/dist/data/safety-panel-schema.d.ts +25 -25
  96. package/dist/data/safety-panel-schema.js +13 -4
  97. package/dist/execution/artifact-metadata.d.ts +23 -15
  98. package/dist/execution/artifact-metadata.js +33 -10
  99. package/dist/execution/artifact-record.d.ts +2 -2
  100. package/dist/execution/artifact-registration.d.ts +5 -5
  101. package/dist/execution/artifact-registry.d.ts +8 -8
  102. package/dist/execution/dbos-run-launcher.d.ts +24 -4
  103. package/dist/execution/dbos-run-launcher.js +58 -4
  104. package/dist/execution/dbos-workflow-purger.d.ts +5 -6
  105. package/dist/execution/dbos-workflow-purger.js +8 -7
  106. package/dist/execution/input-signature.d.ts +2 -2
  107. package/dist/execution/post-step-pipeline.d.ts +9 -9
  108. package/dist/execution/post-step-pipeline.js +1 -1
  109. package/dist/execution/post-step.js +2 -2
  110. package/dist/execution/reconcile-manifest.d.ts +4 -4
  111. package/dist/execution/reconcile-manifest.js +3 -2
  112. package/dist/execution/run-authorizer.d.ts +46 -37
  113. package/dist/execution/run-authorizer.js +8 -8
  114. package/dist/execution/run-canceler.d.ts +7 -7
  115. package/dist/execution/run-canceler.js +14 -7
  116. package/dist/execution/run-end-record.d.ts +2 -3
  117. package/dist/execution/run-event-parts.d.ts +0 -8
  118. package/dist/execution/run-event-parts.js +29 -24
  119. package/dist/execution/run-event-stream.d.ts +8 -8
  120. package/dist/execution/run-event-stream.js +3 -4
  121. package/dist/execution/run-launcher.d.ts +4 -4
  122. package/dist/execution/run-synthesis.d.ts +49 -48
  123. package/dist/execution/run-synthesis.js +96 -255
  124. package/dist/execution/staged-input.d.ts +2 -2
  125. package/dist/execution/step-summary.d.ts +6 -6
  126. package/dist/execution/step-summary.js +4 -8
  127. package/dist/execution/topo-sort.js +4 -3
  128. package/dist/execution/workflow-purger.d.ts +2 -2
  129. package/dist/index.d.ts +17 -20
  130. package/dist/index.js +25 -36
  131. package/dist/input-sanitization.js +9 -2
  132. package/dist/input-scan/clustering.d.ts +6 -6
  133. package/dist/input-scan/clustering.js +11 -5
  134. package/dist/input-scan/companions.js +3 -2
  135. package/dist/input-scan/detect-sets.d.ts +2 -2
  136. package/dist/input-scan/detect-sets.js +25 -21
  137. package/dist/input-scan/enrich.d.ts +6 -6
  138. package/dist/input-scan/enrich.js +29 -12
  139. package/dist/input-scan/formats.d.ts +2 -2
  140. package/dist/input-scan/formats.js +8 -2
  141. package/dist/input-scan/markers.d.ts +2 -2
  142. package/dist/input-scan/markers.js +14 -5
  143. package/dist/input-scan/menu.d.ts +10 -10
  144. package/dist/input-scan/menu.js +0 -0
  145. package/dist/input-scan/quarantine.d.ts +2 -2
  146. package/dist/input-scan/quarantine.js +23 -18
  147. package/dist/input-scan/readout-budget.d.ts +2 -2
  148. package/dist/input-scan/readout-budget.js +1 -1
  149. package/dist/input-scan/readout.d.ts +2 -2
  150. package/dist/input-scan/readout.js +47 -34
  151. package/dist/input-scan/scan.d.ts +2 -2
  152. package/dist/input-scan/scan.js +7 -5
  153. package/dist/input-scan/set-types.d.ts +28 -28
  154. package/dist/input-scan/shapes.d.ts +4 -4
  155. package/dist/input-scan/shapes.js +0 -0
  156. package/dist/input-scan/templates.d.ts +10 -10
  157. package/dist/input-scan/templates.js +30 -22
  158. package/dist/input-scan/tokens.d.ts +6 -4
  159. package/dist/input-scan/tokens.js +100 -38
  160. package/dist/input-scan/types.d.ts +22 -22
  161. package/dist/lib/async-utils.d.ts +3 -7
  162. package/dist/lib/async-utils.js +5 -9
  163. package/dist/lib/cache.js +1 -1
  164. package/dist/lib/chrome.d.ts +46 -6
  165. package/dist/lib/console-logger.js +2 -2
  166. package/dist/lib/db-result.d.ts +22 -6
  167. package/dist/lib/db-result.js +9 -13
  168. package/dist/lib/eyes.d.ts +4 -4
  169. package/dist/lib/eyes.js +1 -1
  170. package/dist/lib/fs-helpers.js +18 -6
  171. package/dist/lib/fs-result.d.ts +1 -0
  172. package/dist/lib/fs-result.js +5 -6
  173. package/dist/lib/hooks.d.ts +23 -7
  174. package/dist/lib/hooks.js +17 -3
  175. package/dist/lib/host-concurrency.js +3 -2
  176. package/dist/lib/logger.d.ts +2 -2
  177. package/dist/lib/logger.js +2 -1
  178. package/dist/lib/metrics.d.ts +1 -1
  179. package/dist/lib/null-proto.d.ts +6 -0
  180. package/dist/lib/null-proto.js +17 -0
  181. package/dist/lib/otel-spans.d.ts +1 -1
  182. package/dist/lib/otel-spans.js +9 -5
  183. package/dist/lib/otel.d.ts +4 -2
  184. package/dist/lib/otel.js +10 -2
  185. package/dist/lib/page-capture.d.ts +8 -8
  186. package/dist/lib/page-capture.js +9 -4
  187. package/dist/lib/parse-json.d.ts +5 -0
  188. package/dist/lib/parse-json.js +12 -0
  189. package/dist/lib/present.d.ts +12 -0
  190. package/dist/lib/present.js +39 -0
  191. package/dist/lib/rate-limit.d.ts +4 -4
  192. package/dist/lib/rate-limit.js +12 -13
  193. package/dist/lib/regulatory-corpus/fetch-fda-guidance.d.ts +4 -4
  194. package/dist/lib/regulatory-corpus/fetch-fda-guidance.js +4 -10
  195. package/dist/lib/regulatory-corpus/fetch-ich.d.ts +4 -4
  196. package/dist/lib/regulatory-corpus/fetch-ich.js +18 -14
  197. package/dist/lib/regulatory-corpus/normalize-whitespace.d.ts +6 -0
  198. package/dist/lib/regulatory-corpus/normalize-whitespace.js +41 -0
  199. package/dist/lib/regulatory-corpus.d.ts +10 -10
  200. package/dist/lib/regulatory-corpus.js +3 -1
  201. package/dist/lib/result.d.ts +10 -11
  202. package/dist/lib/result.js +15 -13
  203. package/dist/lib/storage.d.ts +2 -2
  204. package/dist/lib/test/async-iterable.d.ts +9 -0
  205. package/dist/lib/test/async-iterable.js +28 -0
  206. package/dist/lib/test/request-url.d.ts +5 -0
  207. package/dist/lib/test/request-url.js +7 -0
  208. package/dist/lib/test/result-assert.d.ts +12 -0
  209. package/dist/lib/test/result-assert.js +17 -0
  210. package/dist/lib/test/text-shapes.d.ts +24 -0
  211. package/dist/lib/test/text-shapes.js +63 -0
  212. package/dist/lib/text.d.ts +22 -0
  213. package/dist/lib/text.js +68 -0
  214. package/dist/lib/thrown.d.ts +18 -0
  215. package/dist/lib/thrown.js +30 -0
  216. package/dist/lib/unknown-array.d.ts +6 -0
  217. package/dist/lib/unknown-array.js +8 -0
  218. package/dist/lib/unknown-containers.d.ts +14 -0
  219. package/dist/lib/unknown-containers.js +17 -0
  220. package/dist/lib/unknown-text.d.ts +8 -0
  221. package/dist/lib/unknown-text.js +28 -0
  222. package/dist/lib/zod-issue-shape.d.ts +1 -1
  223. package/dist/lib/zod-issue-shape.js +31 -31
  224. package/dist/lib/zod-issues.d.ts +3 -3
  225. package/dist/lib/zod-issues.js +30 -17
  226. package/dist/literature/sources/arxiv.d.ts +6 -6
  227. package/dist/literature/sources/arxiv.js +92 -35
  228. package/dist/literature/sources/http.d.ts +2 -2
  229. package/dist/literature/sources/http.js +16 -15
  230. package/dist/literature/sources/pubmed.d.ts +16 -16
  231. package/dist/literature/sources/pubmed.js +96 -61
  232. package/dist/literature/sources/semantic-scholar.d.ts +8 -8
  233. package/dist/literature/sources/semantic-scholar.js +22 -13
  234. package/dist/loop/accounting.d.ts +63 -0
  235. package/dist/loop/accounting.js +141 -0
  236. package/dist/loop/call-policy.d.ts +73 -0
  237. package/dist/loop/call-policy.js +109 -0
  238. package/dist/loop/chat-stream.d.ts +62 -0
  239. package/dist/loop/chat-stream.js +237 -0
  240. package/dist/loop/compaction.d.ts +9 -9
  241. package/dist/loop/compaction.js +1 -1
  242. package/dist/loop/metrics.d.ts +12 -15
  243. package/dist/loop/metrics.js +14 -26
  244. package/dist/loop/run-loop.d.ts +233 -0
  245. package/dist/loop/run-loop.js +770 -0
  246. package/dist/loop/sub-agent.d.ts +61 -0
  247. package/dist/loop/sub-agent.js +161 -0
  248. package/dist/loop/tool-adapter.d.ts +98 -0
  249. package/dist/loop/tool-adapter.js +364 -0
  250. package/dist/loop/tool-detail.d.ts +1 -1
  251. package/dist/loop/tool-detail.js +6 -5
  252. package/dist/loop/tool-outcome.d.ts +1 -1
  253. package/dist/loop/tool-output.d.ts +4 -4
  254. package/dist/loop/tool-output.js +2 -2
  255. package/dist/loop/turn-credentials.d.ts +34 -0
  256. package/dist/loop/turn-credentials.js +73 -0
  257. package/dist/loop/types.d.ts +29 -38
  258. package/dist/loop/types.js +2 -7
  259. package/dist/memory/ai-sdk-message-storage.d.ts +5 -5
  260. package/dist/memory/ai-sdk-message-storage.js +13 -11
  261. package/dist/memory/card-builders.d.ts +14 -14
  262. package/dist/memory/card-builders.js +19 -15
  263. package/dist/memory/conversation-display-recorder.d.ts +22 -4
  264. package/dist/memory/conversation-display-recorder.js +43 -24
  265. package/dist/memory/conversation-display-replay.js +3 -2
  266. package/dist/memory/conversation-display-storage.d.ts +15 -7
  267. package/dist/memory/conversation-display-storage.js +141 -36
  268. package/dist/memory/conversation-view.d.ts +4 -4
  269. package/dist/memory/conversation-view.js +5 -4
  270. package/dist/memory/count-tokens.js +10 -8
  271. package/dist/memory/derive-thread-title.d.ts +0 -13
  272. package/dist/memory/derive-thread-title.js +2 -1
  273. package/dist/memory/message-backfill.d.ts +12 -5
  274. package/dist/memory/message-backfill.js +5 -3
  275. package/dist/memory/thread-history.d.ts +36 -21
  276. package/dist/memory/thread-history.js +78 -54
  277. package/dist/memory/thread-store.d.ts +26 -17
  278. package/dist/memory/thread-store.js +15 -9
  279. package/dist/memory/tool-call-integrity.d.ts +2 -2
  280. package/dist/memory/tool-call-integrity.js +4 -3
  281. package/dist/memory/working-memory.d.ts +16 -12
  282. package/dist/memory/working-memory.js +72 -61
  283. package/dist/prompts/briefing.d.ts +12 -12
  284. package/dist/prompts/briefing.js +1 -1
  285. package/dist/prompts/synthesis-agent.d.ts +1 -1
  286. package/dist/prompts/synthesis-agent.js +6 -14
  287. package/dist/provenance/collector.d.ts +6 -6
  288. package/dist/provenance/collector.js +15 -8
  289. package/dist/provenance/exec-frame.d.ts +2 -2
  290. package/dist/provenance/exec-frame.js +6 -1
  291. package/dist/provenance/seam.d.ts +1 -4
  292. package/dist/provenance/types.d.ts +6 -6
  293. package/dist/providers/ai-sdk.d.ts +113 -62
  294. package/dist/providers/ai-sdk.js +283 -518
  295. package/dist/providers/embedding.d.ts +2 -2
  296. package/dist/providers/embedding.js +1 -1
  297. package/dist/providers/errors.d.ts +22 -2
  298. package/dist/providers/errors.js +98 -50
  299. package/dist/providers/prompt-cache.d.ts +8 -8
  300. package/dist/providers/prompt-cache.js +13 -11
  301. package/dist/providers/reasoning.d.ts +2 -2
  302. package/dist/providers/reasoning.js +2 -2
  303. package/dist/providers/types.d.ts +55 -94
  304. package/dist/reference-data/catalog.d.ts +2 -15
  305. package/dist/reference-data/catalog.js +25 -8
  306. package/dist/reference-data/receipt.d.ts +2 -2
  307. package/dist/report-model/assert-rules.js +1 -1
  308. package/dist/report-model/authoring-grammar.d.ts +211 -233
  309. package/dist/report-model/block-walk.d.ts +10 -10
  310. package/dist/report-model/block-walk.js +2 -0
  311. package/dist/report-model/draft-finish.d.ts +8 -8
  312. package/dist/report-model/draft-finish.js +1 -1
  313. package/dist/report-model/draft-hash.js +8 -7
  314. package/dist/report-model/draft-operations.d.ts +22 -10
  315. package/dist/report-model/draft-operations.js +48 -30
  316. package/dist/report-model/draft-read.d.ts +19 -7
  317. package/dist/report-model/draft-read.js +16 -6
  318. package/dist/report-model/draft.d.ts +844 -2
  319. package/dist/report-model/draft.js +3 -1
  320. package/dist/report-model/fixture-resolver.js +39 -16
  321. package/dist/report-model/pin-snapshot.d.ts +2 -2
  322. package/dist/report-model/pin-snapshot.js +15 -15
  323. package/dist/report-model/production-resolver.d.ts +18 -18
  324. package/dist/report-model/production-resolver.js +50 -42
  325. package/dist/report-model/reference-resolver.d.ts +26 -10
  326. package/dist/report-model/reference-resolver.js +68 -5
  327. package/dist/report-model/reference-stamp.js +30 -38
  328. package/dist/report-model/structural-validation.js +12 -4
  329. package/dist/report-model/validate.d.ts +7 -7
  330. package/dist/report-model/validate.js +6 -6
  331. package/dist/report-render/assets.d.ts +2 -2
  332. package/dist/report-render/assets.js +27 -3
  333. package/dist/report-render/chart-export.d.ts +10 -2
  334. package/dist/report-render/chart-export.js +37 -11
  335. package/dist/report-render/chart-presets.d.ts +8 -8
  336. package/dist/report-render/chart-presets.js +3 -3
  337. package/dist/report-render/chart-renderers.d.ts +23 -16
  338. package/dist/report-render/chart-renderers.js +74 -53
  339. package/dist/report-render/chart-toolbox.d.ts +2 -2
  340. package/dist/report-render/chart-toolbox.js +7 -6
  341. package/dist/report-render/chart.d.ts +15 -16
  342. package/dist/report-render/chart.js +369 -252
  343. package/dist/report-render/design.d.ts +36 -297
  344. package/dist/report-render/design.js +0 -12
  345. package/dist/report-render/figures/common.d.ts +12 -12
  346. package/dist/report-render/figures/common.js +42 -31
  347. package/dist/report-render/figures/dendrogram.d.ts +3 -3
  348. package/dist/report-render/figures/dendrogram.js +10 -10
  349. package/dist/report-render/figures/dense.js +15 -8
  350. package/dist/report-render/figures/dotplot.js +46 -33
  351. package/dist/report-render/figures/embedding.js +52 -43
  352. package/dist/report-render/figures/equal-units.d.ts +10 -10
  353. package/dist/report-render/figures/equal-units.js +27 -27
  354. package/dist/report-render/figures/forest.js +9 -7
  355. package/dist/report-render/figures/gsea.js +21 -11
  356. package/dist/report-render/figures/heatmap.js +13 -9
  357. package/dist/report-render/figures/index.d.ts +10 -10
  358. package/dist/report-render/figures/index.js +4 -2
  359. package/dist/report-render/figures/km.js +10 -9
  360. package/dist/report-render/figures/label-room.d.ts +12 -12
  361. package/dist/report-render/figures/label-room.js +7 -2
  362. package/dist/report-render/figures/locuszoom.d.ts +2 -2
  363. package/dist/report-render/figures/locuszoom.js +59 -31
  364. package/dist/report-render/figures/lollipop.js +3 -2
  365. package/dist/report-render/figures/ma.js +8 -4
  366. package/dist/report-render/figures/manhattan.d.ts +1 -2
  367. package/dist/report-render/figures/manhattan.js +52 -34
  368. package/dist/report-render/figures/oncoprint.js +4 -5
  369. package/dist/report-render/figures/pca.js +25 -11
  370. package/dist/report-render/figures/qq.js +20 -9
  371. package/dist/report-render/figures/roc.js +1 -1
  372. package/dist/report-render/figures/sankey.d.ts +2 -2
  373. package/dist/report-render/figures/sankey.js +11 -10
  374. package/dist/report-render/figures/upset.d.ts +6 -6
  375. package/dist/report-render/figures/upset.js +7 -4
  376. package/dist/report-render/figures/volcano.js +12 -10
  377. package/dist/report-render/hybrid-svg.d.ts +4 -4
  378. package/dist/report-render/hybrid-svg.js +7 -5
  379. package/dist/report-render/number-format.d.ts +4 -4
  380. package/dist/report-render/number-format.js +62 -27
  381. package/dist/report-render/page.js +2 -1
  382. package/dist/report-render/provenance-data.d.ts +2 -2
  383. package/dist/report-render/references.d.ts +4 -4
  384. package/dist/report-render/render.d.ts +2 -2
  385. package/dist/report-render/render.js +10 -11
  386. package/dist/report-render/script-json.js +1 -1
  387. package/dist/report-render/table-data.d.ts +6 -6
  388. package/dist/report-render/test/options.d.ts +49 -0
  389. package/dist/report-render/test/options.js +143 -0
  390. package/dist/report-render/test/page-function.d.ts +19 -0
  391. package/dist/report-render/test/page-function.js +33 -0
  392. package/dist/report-render/types.d.ts +10 -10
  393. package/dist/report-render/value-bridge.d.ts +4 -4
  394. package/dist/report-render/value-bridge.js +27 -14
  395. package/dist/report-render/views/chart-view.d.ts +2 -2
  396. package/dist/report-render/views/chart-view.js +2 -2
  397. package/dist/report-render/views/lineage.d.ts +6 -6
  398. package/dist/report-render/views/lineage.js +2 -0
  399. package/dist/report-render/views/page-view.d.ts +2 -2
  400. package/dist/report-render/views/page-view.js +1 -0
  401. package/dist/report-render/views/prose.js +25 -8
  402. package/dist/report-render/views/references-view.js +12 -1
  403. package/dist/report-render/views/values.js +1 -1
  404. package/dist/runtime/assemble.d.ts +53 -26
  405. package/dist/runtime/assemble.js +103 -56
  406. package/dist/runtime/boot.d.ts +8 -7
  407. package/dist/runtime/boot.js +1 -1
  408. package/dist/runtime/connection-budget.d.ts +6 -6
  409. package/dist/runtime/connection-budget.js +1 -1
  410. package/dist/runtime/dbos.d.ts +29 -19
  411. package/dist/runtime/dbos.js +35 -20
  412. package/dist/runtime/result-serialization.js +18 -5
  413. package/dist/runtime/shutdown.d.ts +2 -2
  414. package/dist/sandbox/client.d.ts +7 -3
  415. package/dist/sandbox/cpu-files.d.ts +2 -2
  416. package/dist/sandbox/cpu-files.js +21 -4
  417. package/dist/sandbox/create-sandbox.d.ts +7 -8
  418. package/dist/sandbox/create-sandbox.js +6 -12
  419. package/dist/sandbox/docker-client.d.ts +48 -3
  420. package/dist/sandbox/docker-client.js +10 -6
  421. package/dist/sandbox/estimate-data-profile-resources.js +2 -7
  422. package/dist/sandbox/exec-outcome.d.ts +2 -2
  423. package/dist/sandbox/exec-outcome.js +1 -1
  424. package/dist/sandbox/exec.d.ts +10 -6
  425. package/dist/sandbox/exec.js +27 -12
  426. package/dist/sandbox/execution-id.js +14 -6
  427. package/dist/sandbox/farm.d.ts +2 -2
  428. package/dist/sandbox/farm.js +22 -27
  429. package/dist/sandbox/identity.d.ts +7 -0
  430. package/dist/sandbox/identity.js +14 -3
  431. package/dist/sandbox/image-packages.js +15 -27
  432. package/dist/sandbox/k8s-client.d.ts +35 -8
  433. package/dist/sandbox/k8s-client.js +6 -5
  434. package/dist/sandbox/labels.js +1 -0
  435. package/dist/sandbox/liveness.js +3 -1
  436. package/dist/sandbox/mount-plan.d.ts +10 -10
  437. package/dist/sandbox/mount-plan.js +13 -14
  438. package/dist/sandbox/notification-sweep.d.ts +4 -4
  439. package/dist/sandbox/notification-sweep.js +4 -7
  440. package/dist/sandbox/package-identity.d.ts +6 -13
  441. package/dist/sandbox/package-identity.js +46 -28
  442. package/dist/sandbox/reaper.d.ts +10 -10
  443. package/dist/sandbox/reaper.js +4 -6
  444. package/dist/sandbox/sandbox-error.d.ts +2 -9
  445. package/dist/sandbox/sandbox-error.js +21 -8
  446. package/dist/sandbox/sandbox-step-translate.d.ts +8 -5
  447. package/dist/sandbox/sandbox-step-translate.js +22 -8
  448. package/dist/sandbox/step-sandbox.d.ts +47 -0
  449. package/dist/sandbox/step-sandbox.js +53 -0
  450. package/dist/sandbox/types.d.ts +18 -19
  451. package/dist/sandbox/types.js +1 -1
  452. package/dist/schemas/artifact-manifest.d.ts +4 -4
  453. package/dist/schemas/data-profile-schemas.d.ts +343 -298
  454. package/dist/schemas/file-metadata.js +2 -4
  455. package/dist/schemas/plan-schemas.d.ts +31 -31
  456. package/dist/schemas/run-synthesis.d.ts +95 -26
  457. package/dist/schemas/run-synthesis.js +3 -1
  458. package/dist/schemas/validate-plan.d.ts +4 -4
  459. package/dist/schemas/validate-plan.js +17 -5
  460. package/dist/schemas/workflow-state.d.ts +19 -19
  461. package/dist/schemas/workflow-state.js +2 -2
  462. package/dist/state/active-turns.d.ts +36 -0
  463. package/dist/state/active-turns.js +67 -0
  464. package/dist/state/analyses-metrics.d.ts +8 -8
  465. package/dist/state/analyses-metrics.js +4 -4
  466. package/dist/state/analyses.d.ts +0 -14
  467. package/dist/state/analyses.js +0 -32
  468. package/dist/state/artifacts.d.ts +32 -19
  469. package/dist/state/artifacts.js +28 -5
  470. package/dist/state/data-profile.d.ts +2 -2
  471. package/dist/state/db.d.ts +12 -2
  472. package/dist/state/db.js +5 -0
  473. package/dist/state/edit-intents.d.ts +4 -0
  474. package/dist/state/edit-intents.js +71 -0
  475. package/dist/state/index.d.ts +4 -4
  476. package/dist/state/index.js +2 -2
  477. package/dist/state/init.d.ts +2 -1
  478. package/dist/state/init.js +5 -2
  479. package/dist/state/migrations/20260927120000_baseline.js +3 -1
  480. package/dist/state/migrations/20261026120000_add_the_edit_intents.d.ts +7 -0
  481. package/dist/state/migrations/20261026120000_add_the_edit_intents.js +21 -0
  482. package/dist/state/migrations/20261027120000_record_the_applied_memory_edits.d.ts +7 -0
  483. package/dist/state/migrations/20261027120000_record_the_applied_memory_edits.js +16 -0
  484. package/dist/state/migrations/20261027120100_store_the_authoring_results.d.ts +8 -0
  485. package/dist/state/migrations/20261027120100_store_the_authoring_results.js +20 -0
  486. package/dist/state/migrations/20261027120200_key_the_groups_of_a_turn.d.ts +7 -0
  487. package/dist/state/migrations/20261027120200_key_the_groups_of_a_turn.js +12 -0
  488. package/dist/state/migrations/20261027120300_mark_the_active_turn_of_a_thread.d.ts +7 -0
  489. package/dist/state/migrations/20261027120300_mark_the_active_turn_of_a_thread.js +11 -0
  490. package/dist/state/migrations/20261028120000_record_the_paused_work.d.ts +7 -0
  491. package/dist/state/migrations/20261028120000_record_the_paused_work.js +25 -0
  492. package/dist/state/migrations/20261028120100_end_the_suspended_runs.d.ts +8 -0
  493. package/dist/state/migrations/20261028120100_end_the_suspended_runs.js +13 -0
  494. package/dist/state/migrations/20261029120000_reference_the_run_mandate.d.ts +7 -0
  495. package/dist/state/migrations/20261029120000_reference_the_run_mandate.js +23 -0
  496. package/dist/state/migrations/20261029120100_record_the_run_session_of_a_pause.d.ts +7 -0
  497. package/dist/state/migrations/20261029120100_record_the_run_session_of_a_pause.js +11 -0
  498. package/dist/state/migrations/index.js +18 -0
  499. package/dist/state/paused-work.d.ts +52 -0
  500. package/dist/state/paused-work.js +105 -0
  501. package/dist/state/plans.d.ts +27 -8
  502. package/dist/state/plans.js +14 -11
  503. package/dist/state/purge-analysis.d.ts +6 -6
  504. package/dist/state/purge-analysis.js +10 -2
  505. package/dist/state/report-session-state.d.ts +43 -18
  506. package/dist/state/report-session-state.js +43 -4
  507. package/dist/state/report-versions.d.ts +16 -11
  508. package/dist/state/report-versions.js +5 -3
  509. package/dist/state/runs.d.ts +30 -19
  510. package/dist/state/runs.js +43 -51
  511. package/dist/state/schema.d.ts +18 -21
  512. package/dist/state/schema.js +4 -5
  513. package/dist/state/snapshot-parse.d.ts +2 -2
  514. package/dist/state/snapshot-parse.js +14 -8
  515. package/dist/state/step-executions.d.ts +20 -8
  516. package/dist/state/step-executions.js +6 -4
  517. package/dist/state/tool-outputs.d.ts +2 -2
  518. package/dist/state/tool-outputs.js +2 -1
  519. package/dist/state/vector-store.d.ts +16 -16
  520. package/dist/state/vector-store.js +11 -7
  521. package/dist/tasks/data-profile-absorb.d.ts +3 -3
  522. package/dist/tasks/data-profile-absorb.js +21 -16
  523. package/dist/tasks/data-profile-activity.d.ts +5 -5
  524. package/dist/tasks/data-profile-activity.js +5 -5
  525. package/dist/tasks/data-profile-index.d.ts +4 -4
  526. package/dist/tasks/data-profile-index.js +32 -17
  527. package/dist/tasks/data-profile-resolve.d.ts +8 -8
  528. package/dist/tasks/data-profile-resolve.js +88 -62
  529. package/dist/tasks/data-profile.d.ts +40 -31
  530. package/dist/tasks/data-profile.js +138 -95
  531. package/dist/tasks/extract-values.d.ts +26 -79
  532. package/dist/tasks/extract-values.js +48 -108
  533. package/dist/tasks/refresh-regulatory-corpus.d.ts +4 -20
  534. package/dist/tasks/refresh-regulatory-corpus.js +12 -8
  535. package/dist/tasks/regulatory-corpus-boot-refresh.d.ts +2 -2
  536. package/dist/tasks/regulatory-corpus-boot-refresh.js +12 -10
  537. package/dist/testing.d.ts +60 -0
  538. package/dist/testing.js +96 -0
  539. package/dist/tools/ad-hoc-router.d.ts +9 -9
  540. package/dist/tools/ad-hoc-router.js +65 -52
  541. package/dist/tools/analysis-invocation.d.ts +7 -0
  542. package/dist/tools/analysis-invocation.js +29 -5
  543. package/dist/tools/approval/contract.d.ts +8 -52
  544. package/dist/tools/approval/contract.js +7 -36
  545. package/dist/tools/approval/gateway.d.ts +27 -28
  546. package/dist/tools/approval/gateway.js +33 -130
  547. package/dist/tools/approval/queries.d.ts +6 -11
  548. package/dist/tools/approval/queries.js +43 -29
  549. package/dist/tools/approval/uuidv7.js +2 -2
  550. package/dist/tools/bio/chembl.d.ts +3 -3
  551. package/dist/tools/bio/chembl.js +10 -0
  552. package/dist/tools/bio/comptox.d.ts +25 -8
  553. package/dist/tools/bio/comptox.js +4 -2
  554. package/dist/tools/bio/drug-gene-interactions.d.ts +27 -17
  555. package/dist/tools/bio/drug-gene-interactions.js +11 -8
  556. package/dist/tools/bio/gene-disease-evidence.d.ts +61 -15
  557. package/dist/tools/bio/gene-disease-evidence.js +10 -6
  558. package/dist/tools/bio/gene-preclinical-profile.d.ts +11 -11
  559. package/dist/tools/bio/keys.d.ts +2 -2
  560. package/dist/tools/bio/keys.js +3 -3
  561. package/dist/tools/bio/lookup-annotation.d.ts +5 -5
  562. package/dist/tools/bio/lookup-annotation.js +3 -0
  563. package/dist/tools/bio/opentargets.d.ts +1 -1
  564. package/dist/tools/bio/opentargets.js +6 -0
  565. package/dist/tools/bio/pubchem.d.ts +2 -2
  566. package/dist/tools/bio/pubchem.js +6 -0
  567. package/dist/tools/bio/pubmed.d.ts +26 -16
  568. package/dist/tools/bio/pubmed.js +9 -3
  569. package/dist/tools/bio/search-clinical-trials.d.ts +4 -4
  570. package/dist/tools/bio/search-clinical-trials.js +5 -1
  571. package/dist/tools/bio/search-faers.d.ts +2 -2
  572. package/dist/tools/bio/search-faers.js +1 -1
  573. package/dist/tools/bio/search-gene.d.ts +2 -2
  574. package/dist/tools/bio/search-gene.js +1 -0
  575. package/dist/tools/bio/search-geo-datasets.d.ts +4 -4
  576. package/dist/tools/bio/search-geo-datasets.js +5 -5
  577. package/dist/tools/bio/search-interactions.d.ts +1 -1
  578. package/dist/tools/bio/search-patents.d.ts +28 -22
  579. package/dist/tools/bio/search-patents.js +20 -17
  580. package/dist/tools/bio/search-protein.d.ts +2 -2
  581. package/dist/tools/bio/search-protein.js +1 -1
  582. package/dist/tools/bio/search-regulatory-approvals.d.ts +64 -18
  583. package/dist/tools/bio/search-regulatory-approvals.js +21 -16
  584. package/dist/tools/bio/target-safety.d.ts +8 -8
  585. package/dist/tools/bio/target-safety.js +11 -6
  586. package/dist/tools/capability-data.d.ts +9 -5
  587. package/dist/tools/capability-data.js +2 -1
  588. package/dist/tools/define-tool.d.ts +55 -46
  589. package/dist/tools/define-tool.js +39 -27
  590. package/dist/tools/display/normalize-echart-spec.d.ts +2 -2
  591. package/dist/tools/display/normalize-echart-spec.js +51 -27
  592. package/dist/tools/display/show-user.d.ts +2 -2
  593. package/dist/tools/display/show-user.js +1 -1
  594. package/dist/tools/execute-analysis.d.ts +19 -10
  595. package/dist/tools/execute-analysis.js +83 -62
  596. package/dist/tools/knowledge/check.d.ts +166 -44
  597. package/dist/tools/knowledge/check.js +5 -3
  598. package/dist/tools/knowledge/client.d.ts +31 -62
  599. package/dist/tools/knowledge/client.js +44 -27
  600. package/dist/tools/knowledge/environment.d.ts +6 -6
  601. package/dist/tools/knowledge/environment.js +18 -7
  602. package/dist/tools/knowledge/index.d.ts +2 -2
  603. package/dist/tools/knowledge/index.js +9 -9
  604. package/dist/tools/knowledge/local-slots.d.ts +4 -4
  605. package/dist/tools/knowledge/local-slots.js +11 -4
  606. package/dist/tools/knowledge/pin.d.ts +2 -2
  607. package/dist/tools/knowledge/recommend.d.ts +20 -45
  608. package/dist/tools/knowledge/recommend.js +27 -19
  609. package/dist/tools/knowledge/situation.d.ts +40 -40
  610. package/dist/tools/knowledge/situation.js +2 -9
  611. package/dist/tools/knowledge/skeleton.d.ts +4 -4
  612. package/dist/tools/knowledge/skeleton.js +10 -11
  613. package/dist/tools/knowledge/template.d.ts +20 -18
  614. package/dist/tools/knowledge/template.js +38 -32
  615. package/dist/tools/lib/alphafold-client.js +2 -1
  616. package/dist/tools/lib/amass-client.d.ts +28 -28
  617. package/dist/tools/lib/amass-client.js +20 -10
  618. package/dist/tools/lib/api-utils.d.ts +13 -3
  619. package/dist/tools/lib/api-utils.js +17 -3
  620. package/dist/tools/lib/bgee-client.d.ts +6 -6
  621. package/dist/tools/lib/bgee-client.js +16 -10
  622. package/dist/tools/lib/cbioportal-client.d.ts +4 -4
  623. package/dist/tools/lib/cbioportal-client.js +9 -8
  624. package/dist/tools/lib/chembl-client.d.ts +14 -14
  625. package/dist/tools/lib/chembl-client.js +11 -4
  626. package/dist/tools/lib/clinical-benchmarks-client.d.ts +4 -4
  627. package/dist/tools/lib/clinical-benchmarks-client.js +4 -7
  628. package/dist/tools/lib/clinical-trials-client.d.ts +18 -18
  629. package/dist/tools/lib/clinical-trials-client.js +10 -10
  630. package/dist/tools/lib/clinvar-client.d.ts +2 -2
  631. package/dist/tools/lib/clinvar-client.js +6 -6
  632. package/dist/tools/lib/ctx-ops.d.ts +23 -23
  633. package/dist/tools/lib/ctx-ops.js +13 -10
  634. package/dist/tools/lib/dgidb-client.d.ts +6 -6
  635. package/dist/tools/lib/dgidb-client.js +10 -12
  636. package/dist/tools/lib/disgenet-client.d.ts +4 -4
  637. package/dist/tools/lib/ema-client.d.ts +2 -2
  638. package/dist/tools/lib/ensembl-client.d.ts +4 -4
  639. package/dist/tools/lib/ensembl-client.js +5 -4
  640. package/dist/tools/lib/gwas-catalog-client.d.ts +4 -4
  641. package/dist/tools/lib/gwas-catalog-client.js +17 -17
  642. package/dist/tools/lib/html-text.d.ts +6 -0
  643. package/dist/tools/lib/html-text.js +9 -0
  644. package/dist/tools/lib/identifier-resolver.d.ts +4 -4
  645. package/dist/tools/lib/identifier-resolver.js +7 -3
  646. package/dist/tools/lib/impc-client.d.ts +8 -8
  647. package/dist/tools/lib/impc-client.js +46 -35
  648. package/dist/tools/lib/iuphar-client.d.ts +2 -2
  649. package/dist/tools/lib/iuphar-client.js +7 -16
  650. package/dist/tools/lib/monarch-client.d.ts +4 -4
  651. package/dist/tools/lib/openfda-client.d.ts +22 -22
  652. package/dist/tools/lib/openfda-client.js +23 -22
  653. package/dist/tools/lib/opentargets-client.d.ts +12 -12
  654. package/dist/tools/lib/pathway-client.d.ts +4 -4
  655. package/dist/tools/lib/pathway-client.js +15 -19
  656. package/dist/tools/lib/pharmgkb-client.d.ts +2 -2
  657. package/dist/tools/lib/pharmgkb-client.js +1 -1
  658. package/dist/tools/lib/protein-family-modality.d.ts +2 -2
  659. package/dist/tools/lib/protein-family-modality.js +0 -2
  660. package/dist/tools/lib/protein-family-normalize.js +7 -0
  661. package/dist/tools/lib/pubchem-client.d.ts +2 -2
  662. package/dist/tools/lib/pubchem-client.js +1 -0
  663. package/dist/tools/lib/pubchem-ops.d.ts +4 -4
  664. package/dist/tools/lib/pubchem-ops.js +8 -0
  665. package/dist/tools/lib/pubmed-client.d.ts +9 -9
  666. package/dist/tools/lib/pubmed-client.js +1 -1
  667. package/dist/tools/lib/string-client.d.ts +6 -6
  668. package/dist/tools/lib/uniprot-client.d.ts +8 -8
  669. package/dist/tools/lib/uniprot-client.js +5 -1
  670. package/dist/tools/memory/update-working-memory.d.ts +16 -13
  671. package/dist/tools/memory/update-working-memory.js +21 -3
  672. package/dist/tools/read-tool-output.js +8 -2
  673. package/dist/tools/registry.d.ts +2 -2
  674. package/dist/tools/report-authoring/authoring-tools.d.ts +54 -23
  675. package/dist/tools/report-authoring/authoring-tools.js +128 -62
  676. package/dist/tools/report-session/derive-table.d.ts +19 -59
  677. package/dist/tools/report-session/derive-table.js +120 -122
  678. package/dist/tools/report-session/examine-page.d.ts +6 -6
  679. package/dist/tools/report-session/examine-page.js +14 -12
  680. package/dist/tools/report-session/list-artifacts.d.ts +6 -6
  681. package/dist/tools/report-session/list-artifacts.js +5 -5
  682. package/dist/tools/report-session/page-faults.d.ts +6 -6
  683. package/dist/tools/report-session/page-faults.js +3 -1
  684. package/dist/tools/report-session/preview-report.d.ts +4 -3
  685. package/dist/tools/report-session/preview-report.js +26 -7
  686. package/dist/tools/report-session/record-version.d.ts +9 -4
  687. package/dist/tools/report-session/record-version.js +16 -9
  688. package/dist/tools/report-session/session-page-publisher.d.ts +6 -6
  689. package/dist/tools/research/generate-analogy-report.d.ts +14 -7
  690. package/dist/tools/research/generate-analogy-report.js +80 -76
  691. package/dist/tools/research/generate-plan.d.ts +16 -16
  692. package/dist/tools/research/generate-plan.js +410 -374
  693. package/dist/tools/research/guarded-address.js +3 -2
  694. package/dist/tools/research/inspect-data-profile.d.ts +27 -20
  695. package/dist/tools/research/inspect-data-profile.js +26 -25
  696. package/dist/tools/research/inspect-run.d.ts +54 -27
  697. package/dist/tools/research/inspect-run.js +50 -40
  698. package/dist/tools/research/literature-reviewer.d.ts +8 -7
  699. package/dist/tools/research/literature-reviewer.js +29 -28
  700. package/dist/tools/research/search-arxiv.d.ts +1 -1
  701. package/dist/tools/research/search-arxiv.js +1 -1
  702. package/dist/tools/research/search-github-repos.d.ts +10 -7
  703. package/dist/tools/research/search-github-repos.js +25 -22
  704. package/dist/tools/research/search-semantic-scholar.js +1 -1
  705. package/dist/tools/research/searxng-engine.d.ts +2 -2
  706. package/dist/tools/research/searxng-engine.js +6 -4
  707. package/dist/tools/research/web-fetch.d.ts +24 -21
  708. package/dist/tools/research/web-fetch.js +65 -74
  709. package/dist/tools/research/web-search.d.ts +26 -17
  710. package/dist/tools/research/web-search.js +26 -21
  711. package/dist/tools/sandbox/link-packages.d.ts +11 -7
  712. package/dist/tools/sandbox/link-packages.js +11 -8
  713. package/dist/tools/sandbox/list-available-packages.d.ts +17 -8
  714. package/dist/tools/sandbox/list-available-packages.js +31 -25
  715. package/dist/tools/sandbox/list-available-refs.d.ts +17 -14
  716. package/dist/tools/sandbox/list-available-refs.js +61 -36
  717. package/dist/tools/sandbox/report-blocker.d.ts +30 -37
  718. package/dist/tools/sandbox/report-blocker.js +23 -73
  719. package/dist/tools/sandbox/skills.d.ts +16 -12
  720. package/dist/tools/sandbox/skills.js +16 -12
  721. package/dist/tools/sandbox/submit-file-metadata.d.ts +12 -9
  722. package/dist/tools/sandbox/submit-file-metadata.js +18 -21
  723. package/dist/tools/start-report-session.d.ts +13 -5
  724. package/dist/tools/start-report-session.js +35 -8
  725. package/dist/tools/terminal-text.d.ts +5 -2
  726. package/dist/tools/terminal-text.js +35 -3
  727. package/dist/tools/workspace/decision-record.d.ts +4 -4
  728. package/dist/tools/workspace/decision-record.js +23 -13
  729. package/dist/tools/workspace/edit-file.d.ts +59 -20
  730. package/dist/tools/workspace/edit-file.js +81 -16
  731. package/dist/tools/workspace/execute-command.d.ts +15 -13
  732. package/dist/tools/workspace/execute-command.js +11 -9
  733. package/dist/tools/workspace/file-stat.d.ts +6 -3
  734. package/dist/tools/workspace/grep.d.ts +10 -7
  735. package/dist/tools/workspace/grep.js +9 -3
  736. package/dist/tools/workspace/list-files.d.ts +6 -3
  737. package/dist/tools/workspace/mutator.d.ts +28 -15
  738. package/dist/tools/workspace/mutator.js +107 -53
  739. package/dist/tools/workspace/read-file.d.ts +8 -5
  740. package/dist/tools/workspace/result-bounds.d.ts +5 -5
  741. package/dist/tools/workspace/result-bounds.js +4 -2
  742. package/dist/tools/workspace/run-exec.d.ts +2 -2
  743. package/dist/tools/workspace/run-exec.js +3 -3
  744. package/dist/tools/workspace/scan-inputs.d.ts +8 -5
  745. package/dist/tools/workspace/scan-inputs.js +17 -5
  746. package/dist/tools/workspace/show-file.d.ts +1 -1
  747. package/dist/tools/workspace/show-file.js +2 -1
  748. package/dist/tools/workspace/show-plan.d.ts +8 -5
  749. package/dist/tools/workspace/show-plan.js +9 -8
  750. package/dist/tools/workspace/workspace-search.d.ts +41 -13
  751. package/dist/tools/workspace/workspace-search.js +27 -14
  752. package/dist/tools/workspace/write-file.d.ts +9 -7
  753. package/dist/workflows/execute-analysis-scheduler.d.ts +9 -9
  754. package/dist/workflows/execute-analysis-scheduler.js +6 -3
  755. package/dist/workflows/execute-analysis.d.ts +46 -62
  756. package/dist/workflows/execute-analysis.js +137 -219
  757. package/dist/workflows/pause.d.ts +71 -0
  758. package/dist/workflows/pause.js +173 -0
  759. package/dist/workflows/sandbox-step.d.ts +38 -61
  760. package/dist/workflows/sandbox-step.js +99 -106
  761. package/dist/workflows/test/sql-writes.d.ts +21 -0
  762. package/dist/workflows/test/sql-writes.js +36 -0
  763. package/dist/workspace/filesystem.d.ts +6 -6
  764. package/dist/workspace/filesystem.js +10 -8
  765. package/dist/workspace/paths.d.ts +1 -1
  766. package/dist/workspace/paths.js +15 -4
  767. package/dist/workspace/search-config.d.ts +2 -2
  768. package/dist/workspace/search-config.js +1 -1
  769. package/package.json +21 -22
  770. package/dist/loop/call-guard.d.ts +0 -50
  771. package/dist/loop/call-guard.js +0 -90
  772. package/dist/loop/continue-agent.d.ts +0 -40
  773. package/dist/loop/continue-agent.js +0 -38
  774. package/dist/loop/genai-spans.d.ts +0 -47
  775. package/dist/loop/genai-spans.js +0 -134
  776. package/dist/loop/run-agent.d.ts +0 -245
  777. package/dist/loop/run-agent.js +0 -1124
  778. package/dist/loop/run-step.d.ts +0 -29
  779. package/dist/loop/run-step.js +0 -34
  780. package/dist/loop/run-to-terminal.d.ts +0 -66
  781. package/dist/loop/run-to-terminal.js +0 -93
  782. package/dist/loop/tool-mask.d.ts +0 -23
  783. package/dist/loop/tool-mask.js +0 -46
  784. package/dist/providers/streaming-chat.d.ts +0 -32
  785. package/dist/providers/streaming-chat.js +0 -91
  786. package/dist/tasks/derive-table-exec.d.ts +0 -54
  787. package/dist/tasks/derive-table-exec.js +0 -86
  788. package/dist/workflows/suspension.d.ts +0 -32
  789. package/dist/workflows/suspension.js +0 -38
@@ -1,6 +1,6 @@
1
1
  /**
2
- * generatePlan — a loop-driving tool: a focused `runAgent` loop that builds
3
- * an analysis plan (a DAG of steps).
2
+ * generatePlan — a loop-driving tool: a focused agent loop that builds an
3
+ * analysis plan (a DAG of steps).
4
4
  *
5
5
  * The dataset's facts are NOT an input. They are read here, server-side, from
6
6
  * the analysis's persisted data profile — the one durable record of what the
@@ -10,33 +10,37 @@
10
10
  * caller-typed dataset summary would be a model re-transcribing a record the
11
11
  * database already holds, and it would lose fidelity at every hop.
12
12
  *
13
- * The tool drives an internal "planner" agent that communicates outcomes
14
- * EXCLUSIVELY via three terminal tool calls:
13
+ * The tool drives an internal "planner" agent that gives its outcome through
14
+ * three terminal tools. None of them ends the run, and none writes:
15
15
  *
16
- * - submit_plan(plan) → terminal success: re-validates + persists;
17
- * rejected candidates return structured issues
18
- * - request_clarification(…) → terminal: planner needs more context
19
- * - report_blocker(reason) → terminal: no viable plan
16
+ * - submit_plan(plan) → checks a candidate; gives back the accepted
17
+ * plan or structured issues
18
+ * - request_clarification(…) → the planner needs more context
19
+ * - report_blocker(reason) → no viable plan
20
20
  *
21
- * The planner is an `AgentDefinition` driven by `runAgent`; the pool and
22
- * resource id are an explicit `Pool` dependency and the request-scoped
23
- * `Session`. The planner prompt injects the agent catalog and exposes the
24
- * terminal-tool surface above.
21
+ * After the run, the tool selects the outcome from the recorded calls — the
22
+ * last accepted plan, else the last clarification, else the first blocker —
23
+ * and persists an accepted plan under an id that a rerun of the call gives again.
25
24
  */
26
- import { err, ok } from "neverthrow";
25
+ import { errorMessageOf } from "../../lib/thrown.js";
26
+ import { textOf } from "../../lib/unknown-text.js";
27
+ import { createHash } from "node:crypto";
28
+ import { ok } from "neverthrow";
27
29
  import { z } from "zod";
28
30
  import { formatAgentCatalog } from "../../agents/sandbox-catalog.js";
29
31
  import { composeSystemPrompt } from "../../agents/system-prompt.js";
30
- import { DEFAULT_SALVAGE_ITERATIONS, runToTerminal } from "../../loop/run-to-terminal.js";
31
- import { passthroughStep } from "../../loop/run-step.js";
32
+ import { DEFAULT_SALVAGE_REQUESTS, runWithSalvage } from "../../loop/run-loop.js";
32
33
  import { forSubAgent, scopeResource } from "../../auth/types.js";
34
+ import { PLAN_ID_RE } from "../../contracts/schemas/chat-parts.js";
33
35
  import { effectiveDeadlineMs } from "../../providers/types.js";
36
+ import { isUnknownArray } from "../../lib/unknown-array.js";
37
+ import { parseUnknownRecord } from "../../lib/unknown-containers.js";
34
38
  import { defineTool } from "../define-tool.js";
35
39
  import { createReadToolOutputTool } from "../read-tool-output.js";
36
40
  import { createListAvailablePackagesTool } from "../sandbox/list-available-packages.js";
37
41
  import { createListAvailableRefsTool } from "../sandbox/list-available-refs.js";
38
- import { createReportBlockerToolFor } from "../sandbox/report-blocker.js";
39
- import { degenerateTerminalText } from "../terminal-text.js";
42
+ import { blockerOf, createReportBlockerToolFor } from "../sandbox/report-blocker.js";
43
+ import { terminalTextSchema } from "../terminal-text.js";
40
44
  import { searchGeoDatasetsTool } from "../bio/search-geo-datasets.js";
41
45
  import { createNcbiTools } from "../bio/keys.js";
42
46
  import { createKnowledgeTools } from "../knowledge/index.js";
@@ -46,7 +50,6 @@ import { createSearchGithubReposTool } from "./search-github-repos.js";
46
50
  import { createSearchSemanticScholarTool } from "./search-semantic-scholar.js";
47
51
  import { DATA_PROFILE_ORIENTATION_MAX_CHARS, buildDataProfileOrientation } from "../../app/data-profile-orientation.js";
48
52
  import { DEFAULT_SANDBOX_MAX_STEPS } from "../../config/resource-limits.js";
49
- import { guardRepeatedCalls } from "../../loop/call-guard.js";
50
53
  import { plannerPrompt } from "../../prompts/planner.js";
51
54
  import { hydratePlanSteps, PlannerPlanSchema } from "../../schemas/plan-schemas.js";
52
55
  import { validatePlan } from "../../schemas/validate-plan.js";
@@ -54,7 +57,7 @@ import { AnalysisPlanSchema } from "../../schemas/workflow-state.js";
54
57
  import { createNoopLogger } from "../../lib/console-logger.js";
55
58
  import { unwrapOrThrow } from "../../lib/result.js";
56
59
  import { decodeObjectString, hintForZodIssue } from "../../lib/zod-issues.js";
57
- import { insertPlan, loadDataProfileStatus, loadPlan } from "../../state/index.js";
60
+ import { loadDataProfileStatus, loadPlan, upsertPlan } from "../../state/index.js";
58
61
  // ── Tool-level config ──────────────────────────────────────────────
59
62
  /** Sub-agent identity for the planner — provenance only. */
60
63
  const PLANNER_AGENT_ID = "planner";
@@ -65,9 +68,9 @@ const PLANNER_AGENT_ID = "planner";
65
68
  * The number is deliberately far above what a plan costs. A well-grounded plan
66
69
  * still costs two or three calls, because the prompt gates the research phase on
67
70
  * what the seed does not already answer. What the ceiling must never do is stop
68
- * a planner that is searching correctly. A capped run takes the forced wrap-up
69
- * path and submits the plan that it had, not the plan that it was building.
70
- * Thus the wall-clock guard, not this number, is what bounds the worst case.
71
+ * a planner that is searching correctly. A capped run gets the salvage request
72
+ * and submits the plan that it had, not the plan that it was building. Thus the
73
+ * wall-clock guard, not this number, is what bounds the worst case.
71
74
  */
72
75
  const PLANNER_MAX_ITERATIONS = 300;
73
76
  /** Wall-clock guard for a single plan-generation invocation. */
@@ -75,15 +78,15 @@ const PLAN_TIMEOUT_MS = 1_800_000;
75
78
  /**
76
79
  * The refusals of the call guard that end the search phase of one plan. A
77
80
  * planner that the guard refused this many times is not searching, it is
78
- * looping, and the wrap-up plus the salvage turn give it the plan it has. Six
81
+ * looping, and the salvage request lets it submit the plan it has. Six
79
82
  * is above any count a frontier planner reached in the Phase 0 campaign
80
83
  * (zero) and far below the hundred-plus refused calls of a looping run.
81
84
  */
82
85
  const PLANNER_REFUSAL_LIMIT = 6;
83
86
  // ── Diagnostic bounds ───────────────────────────────────────────────
84
87
  //
85
- // This tool runs on `passthroughStep`: it writes no ledger row and owns no
86
- // durable stream, so its log records are the ONLY account of an invocation that
88
+ // This tool writes no ledger row before its persist and owns no durable stream,
89
+ // so its log records are the ONLY account of an invocation that
87
90
  // survives the turn. That makes them worth spending detail on — and makes every
88
91
  // one of them a place an unbounded model-authored string could reach a log file.
89
92
  // Each bound below is what keeps a record readable and a sink affordable when the
@@ -125,7 +128,12 @@ function bounded(value, max) {
125
128
  const MODEL_AUTHORED_KEY = "modelAuthored";
126
129
  /** Nest model-authored prose under {@link MODEL_AUTHORED_KEY}, or contribute nothing when there is none. */
127
130
  function modelAuthored(fields) {
128
- const present = Object.entries(fields).filter((entry) => entry[1] !== undefined && entry[1].length > 0);
131
+ const present = Object.entries(fields).reduce((kept, entry) => {
132
+ const value = entry[1];
133
+ if (value !== undefined && value.length > 0)
134
+ kept.push([entry[0], value]);
135
+ return kept;
136
+ }, []);
129
137
  return present.length === 0 ? {} : { [MODEL_AUTHORED_KEY]: Object.fromEntries(present) };
130
138
  }
131
139
  function toRejectionRecord(attempt, issues) {
@@ -141,8 +149,24 @@ function toRejectionRecord(attempt, issues) {
141
149
  messages: kept.map((i) => bounded(i.message, MAX_LOGGED_ISSUE_CHARS)),
142
150
  };
143
151
  }
144
- function createPlannerTrace() {
145
- return { submitAttempts: 0, rejectedAttempts: 0, rejections: [], duplicateTerminalCalls: 0 };
152
+ function traceOf(steps) {
153
+ let submitAttempts = 0;
154
+ let rejectedAttempts = 0;
155
+ const rejections = [];
156
+ for (const step of steps) {
157
+ for (const call of step.calls) {
158
+ if (call.toolName !== SUBMIT_PLAN_TOOL_ID || call.output === undefined)
159
+ continue;
160
+ submitAttempts += 1;
161
+ const issues = submitIssuesOf(call.output);
162
+ if (issues === undefined)
163
+ continue;
164
+ rejectedAttempts += 1;
165
+ if (rejections.length < MAX_LOGGED_REJECTIONS)
166
+ rejections.push(toRejectionRecord(submitAttempts, issues));
167
+ }
168
+ }
169
+ return { submitAttempts, rejectedAttempts, rejections };
146
170
  }
147
171
  // ── Prompt / catalog ────────────────────────────────────────────────
148
172
  /** The planner system prompt is deterministic per policy — build it once per
@@ -150,6 +174,53 @@ function createPlannerTrace() {
150
174
  function plannerInstructions(resourcePolicy) {
151
175
  return plannerPrompt(formatAgentCatalog(), resourcePolicy);
152
176
  }
177
+ const ValidationIssueSchema = z.object({ path: z.string(), code: z.enum(["schema", "semantic"]), message: z.string(), hint: z.string().optional() });
178
+ const SubmitPlanOutputSchema = z.discriminatedUnion("accepted", [
179
+ z.object({ accepted: z.literal(false), issues: z.array(ValidationIssueSchema) }),
180
+ z.object({ accepted: z.literal(true), plan: PlannerPlanSchema }),
181
+ ]);
182
+ const SUBMIT_PLAN_TOOL_ID = "submit_plan";
183
+ const REQUEST_CLARIFICATION_TOOL_ID = "request_clarification";
184
+ const ClarificationSchema = z.object({ kind: z.literal("clarification"), question: z.string(), questionContext: z.string().optional() });
185
+ /** The issues of a refused submission, or `undefined` for an accepted one and for an output that is no submission. */
186
+ function submitIssuesOf(output) {
187
+ if (output.outcome === "error")
188
+ return [{ path: "plan", code: "schema", message: output.text }];
189
+ if (output.outcome !== "ok")
190
+ return undefined;
191
+ const parsed = SubmitPlanOutputSchema.safeParse(output.value);
192
+ return parsed.success && !parsed.data.accepted ? parsed.data.issues : undefined;
193
+ }
194
+ /**
195
+ * The outcome of a run, selected from its recorded calls: the last accepted
196
+ * plan, else the last clarification, else the first blocker.
197
+ */
198
+ function selectedOutcomeOf(steps) {
199
+ let plan;
200
+ let clarification;
201
+ for (const step of steps) {
202
+ for (const call of step.calls) {
203
+ if (call.output?.outcome !== "ok")
204
+ continue;
205
+ if (call.toolName === SUBMIT_PLAN_TOOL_ID) {
206
+ const parsed = SubmitPlanOutputSchema.safeParse(call.output.value);
207
+ if (parsed.success && parsed.data.accepted)
208
+ plan = parsed.data.plan;
209
+ }
210
+ else if (call.toolName === REQUEST_CLARIFICATION_TOOL_ID) {
211
+ const parsed = ClarificationSchema.safeParse(call.output.value);
212
+ if (parsed.success)
213
+ clarification = { kind: "clarification", question: parsed.data.question, questionContext: parsed.data.questionContext };
214
+ }
215
+ }
216
+ }
217
+ if (plan !== undefined)
218
+ return { kind: "plan", plan };
219
+ if (clarification !== undefined)
220
+ return clarification;
221
+ const blocker = blockerOf(steps);
222
+ return blocker === null ? null : { kind: "blocker", reason: blocker.reason };
223
+ }
153
224
  // ── Prior plan serialization (iteration context) ───────────────────
154
225
  /**
155
226
  * Format a loaded prior plan as a markdown block the planner can read —
@@ -266,6 +337,10 @@ function renderDataContext(grounding, analysisId) {
266
337
  }
267
338
  }
268
339
  // ── Persistence ─────────────────────────────────────────────────────
340
+ /** The id of the plan of one call. A rerun of the call gives the same id, thus the upsert keeps one plan. */
341
+ function planIdFor(ctx) {
342
+ return `pln-${createHash("sha256").update(`${ctx.analysisId}:${ctx.invocationId}`).digest("hex").slice(0, 8)}`;
343
+ }
269
344
  /**
270
345
  * Persist a validated plan. Returns the planId, or an outcome-ready error
271
346
  * with a sanitized message — DB-shape errors never leak to the planner.
@@ -277,7 +352,9 @@ function renderDataContext(grounding, analysisId) {
277
352
  */
278
353
  async function persistPlan(plan, ctx, pool, logger) {
279
354
  try {
280
- const planId = unwrapOrThrow(await insertPlan(pool, {
355
+ const planId = planIdFor(ctx);
356
+ unwrapOrThrow(await upsertPlan(pool, {
357
+ planId,
281
358
  analysisId: ctx.analysisId,
282
359
  plan: hydratePlanSteps(plan),
283
360
  parentPlanId: ctx.parentPlanId,
@@ -285,8 +362,9 @@ async function persistPlan(plan, ctx, pool, logger) {
285
362
  return { ok: true, planId };
286
363
  }
287
364
  catch (err) {
288
- const raw = err instanceof Error ? err.message : String(err);
289
- const isParentValidation = /^parent plan .* (?:not found|belongs to a different analysis)$/i.test(raw);
365
+ const raw = errorMessageOf(err) ?? textOf(err);
366
+ const lower = raw.toLowerCase();
367
+ const isParentValidation = lower.startsWith("parent plan ") && (lower.endsWith("not found") || lower.endsWith("belongs to a different analysis"));
290
368
  logger.error("plan persistence failed", {
291
369
  parentPlanId: ctx.parentPlanId,
292
370
  stepCount: plan.steps.length,
@@ -379,7 +457,9 @@ function claimsByRule(claims) {
379
457
  */
380
458
  function restoreSkeletonGrounding(candidate, memory) {
381
459
  const decoded = decodeObjectString(candidate);
382
- if (memory.steps.size === 0 || typeof decoded !== "object" || decoded === null || !Array.isArray(decoded.steps)) {
460
+ const decodedRecord = parseUnknownRecord(decoded);
461
+ const decodedSteps = decodedRecord?.steps;
462
+ if (memory.steps.size === 0 || !isUnknownArray(decodedSteps)) {
383
463
  return { candidate: decoded, stamped: [], restored: [], repaired: [], unknown: [] };
384
464
  }
385
465
  const byRule = claimsByRule(memory.claims);
@@ -387,13 +467,14 @@ function restoreSkeletonGrounding(candidate, memory) {
387
467
  const restored = [];
388
468
  const repaired = [];
389
469
  const unknown = [];
390
- const steps = decoded.steps.map((step, index) => {
391
- if (typeof step !== "object" || step === null)
470
+ const steps = decodedSteps.map((step, index) => {
471
+ const fields = parseUnknownRecord(step);
472
+ if (fields === undefined)
392
473
  return step;
393
- const { id, grounding } = step;
394
- if (typeof grounding !== "object" || grounding === null)
474
+ const { id, grounding } = fields;
475
+ const current = parseUnknownRecord(grounding);
476
+ if (current === undefined)
395
477
  return step;
396
- const current = grounding;
397
478
  const label = typeof id === "string" ? id : String(index);
398
479
  let next = current;
399
480
  const source = typeof id === "string" ? memory.steps.get(id) : undefined;
@@ -407,8 +488,8 @@ function restoreSkeletonGrounding(candidate, memory) {
407
488
  next = { ...next, template: source.grounding.template };
408
489
  }
409
490
  }
410
- if (Array.isArray(current.claims)) {
411
- let changed = false;
491
+ if (isUnknownArray(current.claims)) {
492
+ let changed;
412
493
  const claims = current.claims.map((claim, position) => {
413
494
  if (typeof claim !== "string" || memory.claims.has(claim))
414
495
  return claim;
@@ -434,9 +515,9 @@ function restoreSkeletonGrounding(candidate, memory) {
434
515
  stamped.push(label);
435
516
  next = { ...next, snapshot: memory.digest };
436
517
  }
437
- return next === current ? step : { ...step, grounding: next };
518
+ return next === current ? step : { ...fields, grounding: next };
438
519
  });
439
- return { candidate: { ...decoded, steps }, stamped, restored, repaired, unknown };
520
+ return { candidate: { ...decodedRecord, steps }, stamped, restored, repaired, unknown };
440
521
  }
441
522
  /**
442
523
  * Full validation: Zod schema + semantic checks. The plan is valid only if
@@ -479,19 +560,18 @@ function fullyValidate(candidate, resourcePolicy) {
479
560
  return { valid: true, plan: parsed.data };
480
561
  }
481
562
  /**
482
- * Build the planner's inner tools for one invocation. They close over the
483
- * shared `holder` so the outer `execute` reads the terminal outcome after the
484
- * loop finishes.
563
+ * Build the planner's inner tools for one invocation. `memory` holds the last
564
+ * `knowledge_recommend` answer of the run, which the loop folds after each round.
485
565
  */
486
- function buildInnerTools(holder, trace, persistCtx, pool, resourcePolicy, logger, memory) {
566
+ function buildInnerTools(resourcePolicy, logger, memory) {
487
567
  const submitPlanTool = defineTool({
488
- id: "submit_plan",
489
- description: "Submit the final plan for persistence. Re-validates the plan; on " +
490
- "success returns {accepted: true, planId}, and an accepted call ends " +
491
- "the planning session. On " +
568
+ id: SUBMIT_PLAN_TOOL_ID,
569
+ description: "Submit the plan. Checks the plan; on success returns {accepted: true, plan} " +
570
+ "with the plan as the harness saves it — stop after an accepted plan. On " +
492
571
  "rejection returns {accepted: false, issues} — fix and call again, " +
493
- "or switch to report_blocker if the plan cannot be made valid. This " +
494
- "arg schema is the authoritative plan contract.",
572
+ "or switch to report_blocker if the plan cannot be made valid. The last " +
573
+ "accepted plan is the plan of this session. This arg schema is the " +
574
+ "authoritative plan contract.",
495
575
  // Permissive on purpose: every candidate reaches `execute`, where the
496
576
  // model gets structured schema and semantic issues in one response.
497
577
  // The field-by-field contract remains visible in this tool's description
@@ -503,25 +583,8 @@ function buildInnerTools(holder, trace, persistCtx, pool, resourcePolicy, logger
503
583
  plan: z.union([PlannerPlanSchema, z.unknown()]).describe("Candidate plan; malformed values are reported in issues."),
504
584
  }),
505
585
  describeCall: "none",
506
- execute: async (input) => {
507
- if (holder.outcome !== null) {
508
- trace.duplicateTerminalCalls++;
509
- logger.warn("submit_plan called after a terminal outcome was recorded", {
510
- recordedOutcome: holder.outcome.kind,
511
- duplicateTerminalCalls: trace.duplicateTerminalCalls,
512
- });
513
- return ok({
514
- accepted: false,
515
- issues: [
516
- {
517
- path: "plan",
518
- code: "semantic",
519
- message: "A terminal outcome has already been recorded; submit_plan " + "can only be called once per invocation.",
520
- },
521
- ],
522
- });
523
- }
524
- const attempt = ++trace.submitAttempts;
586
+ execute: (input) => {
587
+ // The host owns the grounding fields: it fills them from the last answer of the run.
525
588
  const { candidate, stamped, restored, repaired, unknown } = restoreSkeletonGrounding(input.plan, memory);
526
589
  if (stamped.length > 0)
527
590
  logger.info("submit_plan stamped the snapshot digest of the answer on steps", { steps: stamped });
@@ -534,107 +597,37 @@ function buildInnerTools(holder, trace, persistCtx, pool, resourcePolicy, logger
534
597
  // schema issues when the schema rejects it, thus one attempt shows both.
535
598
  if (!result.valid || unknown.length > 0) {
536
599
  const issues = [...(result.valid ? [] : result.issues), ...unknown];
537
- trace.rejectedAttempts++;
538
- const rejection = toRejectionRecord(attempt, issues);
539
- if (trace.rejections.length < MAX_LOGGED_REJECTIONS)
540
- trace.rejections.push(rejection);
541
600
  // `warn`, not `debug`: a rejection is non-terminal by design, so the model
542
601
  // reads the issues and the invocation carries on — nothing else reports that
543
- // an attempt was spent. A run that exhausts its budget on rejections ends
544
- // looking identical to one that never tried, and this is the difference.
545
- logger.warn("submit_plan rejected a plan", { ...rejection });
546
- return ok({ accepted: false, issues });
547
- }
548
- const persisted = await persistPlan(result.plan, persistCtx, pool, logger);
549
- if (!persisted.ok) {
550
- holder.outcome = { kind: "persist_error", message: persisted.message };
551
- return ok({
552
- accepted: false,
553
- issues: [
554
- {
555
- path: "plan",
556
- code: "semantic",
557
- message: persisted.message,
558
- hint: "Persistence failed — do not retry submit_plan.",
559
- },
560
- ],
561
- });
602
+ // an attempt was spent.
603
+ logger.warn("submit_plan rejected a plan", { issueCount: issues.length, paths: issues.slice(0, MAX_LOGGED_ISSUES).map((i) => i.path) });
604
+ return Promise.resolve(ok({ accepted: false, issues }));
562
605
  }
563
- holder.outcome = {
564
- kind: "plan_submitted",
565
- planId: persisted.planId,
566
- plan: result.plan,
567
- };
568
- // The attempt count is the cost of this plan, and it is only knowable here.
569
- // A plan accepted on attempt 5 is a success that nearly was not one.
570
606
  logger.info("submit_plan accepted a plan", {
571
- planId: persisted.planId,
572
- attempt,
573
607
  stepCount: result.plan.steps.length,
574
608
  agents: [...new Set(result.plan.steps.map((s) => s.agent))],
575
609
  });
576
- return ok({ accepted: true, planId: persisted.planId });
610
+ return Promise.resolve(ok({ accepted: true, plan: result.plan }));
577
611
  },
578
612
  });
579
613
  const requestClarificationTool = defineTool({
580
- id: "request_clarification",
614
+ id: REQUEST_CLARIFICATION_TOOL_ID,
581
615
  description: "Terminal. Use when a specific fact you need is missing from the " +
582
616
  "input and cannot be inferred. The question reaches the user through " +
583
- "the conversation agent. A call ends the planning session with no " +
584
- "plan saved.",
617
+ "the conversation agent, and no plan is saved. Stop after this call.",
585
618
  inputSchema: z.object({
586
- question: z.string().min(1).describe("One short question that names each missing fact, and the step that needs it."),
619
+ question: terminalTextSchema("One short question that names each missing fact, and the step that needs it."),
587
620
  questionContext: z.string().optional().describe("Optional context: why the plan needs the fact, and what the inputs already say about it."),
588
621
  }),
589
622
  describeCall: "none",
590
- execute: async (input) => {
591
- const degenerate = degenerateTerminalText(input.question);
592
- if (degenerate) {
593
- logger.warn("request_clarification refused a placeholder question", { question: input.question.slice(0, 80) });
594
- return err({ error: `${degenerate} Ask the real question, or continue: submit the plan you have.`, retryable: true });
595
- }
596
- if (holder.outcome !== null) {
597
- trace.duplicateTerminalCalls++;
598
- logger.warn("request_clarification called after a terminal outcome was recorded", {
599
- recordedOutcome: holder.outcome.kind,
600
- duplicateTerminalCalls: trace.duplicateTerminalCalls,
601
- });
602
- return ok({ recorded: true });
603
- }
604
- holder.outcome = {
605
- kind: "clarification",
606
- question: input.question,
607
- questionContext: input.questionContext,
608
- };
609
- // The question is the answer to "why did planning stop?" — and it reaches the
610
- // user through the conversation agent, which may paraphrase it into something
611
- // that no longer identifies the missing fact.
612
- logger.info("planner requested clarification", {
613
- submitAttempts: trace.submitAttempts,
614
- ...modelAuthored({ clarificationQuestion: bounded(input.question, MAX_LOGGED_PROSE_CHARS) }),
615
- });
616
- return ok({ recorded: true });
617
- },
623
+ execute: (input) => Promise.resolve(ok({
624
+ kind: "clarification",
625
+ question: input.question,
626
+ questionContext: input.questionContext,
627
+ message: "Clarification recorded. Stop now.",
628
+ })),
618
629
  });
619
630
  const reportBlockerTool = createReportBlockerToolFor({
620
- record: (outcome) => {
621
- if (holder.outcome !== null) {
622
- trace.duplicateTerminalCalls++;
623
- logger.warn("report_blocker called after a terminal outcome was recorded", {
624
- recordedOutcome: holder.outcome.kind,
625
- duplicateTerminalCalls: trace.duplicateTerminalCalls,
626
- });
627
- return;
628
- }
629
- holder.outcome = outcome;
630
- // A blocker after several rejected submits is a planner giving up on a plan it
631
- // could not make valid — a validation problem wearing a blocker's clothes. The
632
- // attempt count is what tells those apart, and only this record carries both.
633
- logger.warn("planner reported a blocker", {
634
- submitAttempts: trace.submitAttempts,
635
- ...modelAuthored({ blockerReason: bounded(outcome.reason, MAX_LOGGED_PROSE_CHARS) }),
636
- });
637
- },
638
631
  blockedWhen: "Ends plan generation with no plan saved. Use it when no valid plan can " +
639
632
  "be produced for this data and research question (out of scope, data " +
640
633
  "incompatible with every available agent, etc.) — not for a plan you " +
@@ -657,17 +650,18 @@ function inventoryContent(label, result, logger) {
657
650
  return `## ${label}\n\nInventory lookup failed: ${result.error.error}`;
658
651
  }
659
652
  const value = result.value;
660
- if (typeof value === "object" && value !== null && "content" in value && typeof value.content === "string") {
661
- return `## ${label}\n\n${value.content}`;
653
+ const content = parseUnknownRecord(value)?.["content"];
654
+ if (typeof content === "string") {
655
+ return `## ${label}\n\n${content}`;
662
656
  }
663
657
  return `## ${label}\n\n${JSON.stringify(value)}`;
664
658
  }
665
659
  // ── Transcript reading (diagnostics only) ───────────────────────────
666
660
  /**
667
- * The planner's own account of the run, read off the message array `runToTerminal`
661
+ * The planner's own account of the run, read off the message array that the run
668
662
  * returns. Nothing else can produce it: the transcript is ephemeral — a sub-agent
669
- * loop on `passthroughStep` persists no messages anywhere — so it is read here or
670
- * it is lost with the turn.
663
+ * loop persists no messages anywhere — so it is read here or it is lost with the
664
+ * turn.
671
665
  *
672
666
  * `toolCalls` in order is what shows the SHAPE of a failure at a glance:
673
667
  * `[submit_plan × 13]` is a planner thrashing on validation, `[]` is a planner
@@ -702,32 +696,34 @@ function readTranscript(messages) {
702
696
  * The loop half of the finish record: how the planner's run ended, and what it
703
697
  * did to get there.
704
698
  *
705
- * Built from the `runToTerminal` result that the tool previously discarded
706
- * entirely. Without it, `no_outcome` is a single word covering three unrelated
707
- * failures — a budget spent on rejected submits (`max_iterations`), a planner that
708
- * answered in prose (`stop`), and a reply cut off at the output-token limit
709
- * (`length`) — which is why three identical-looking retries taught nobody anything.
699
+ * Without it, `no_outcome` is a single word covering three unrelated failures — a
700
+ * budget spent on rejected submits (`max_iterations`), a planner that answered in
701
+ * prose (`stop`), and a reply cut off at the output-token limit (`length`) — which
702
+ * is why three identical-looking retries taught nobody anything.
710
703
  */
711
704
  function loopFields(run, kind) {
712
705
  const transcript = readTranscript(run.messages);
713
- return {
706
+ const fields = {
714
707
  loop: {
715
708
  finishReason: run.finish.reason,
716
709
  cappedOut: run.finish.cappedOut,
717
- truncationRecoveries: run.finish.truncationRecoveries,
718
710
  salvaged: run.salvage !== null,
719
- ...(run.salvage ? { firstFinishReason: run.salvage.firstFinish.reason, salvageFinishReason: run.salvage.finish.reason } : {}),
711
+ firstFinishReason: run.salvage === null ? undefined : run.salvage.firstFinish.reason,
712
+ salvageFinishReason: run.salvage === null ? undefined : run.salvage.finish.reason,
720
713
  toolCalls: transcript.toolCalls,
721
- ...(transcript.truncatedToolCalls ? { toolCallsTruncated: true } : {}),
714
+ toolCallsTruncated: transcript.truncatedToolCalls ? true : undefined,
722
715
  },
723
- ...(PROSE_EXPLAINS_OUTCOME.has(kind) ? modelAuthored({ plannerFinalProse: transcript.finalProse }) : {}),
724
716
  };
717
+ if (PROSE_EXPLAINS_OUTCOME.has(kind))
718
+ Object.assign(fields, modelAuthored({ plannerFinalProse: transcript.finalProse }));
719
+ return fields;
725
720
  }
726
721
  /**
727
722
  * The endings whose only account is what the planner said last.
728
723
  *
729
- * Everywhere else something better already explains the outcome: a terminal tool
730
- * recorded its own argument, or a thrown cause carries the fault. Logging the
724
+ * Everywhere else something better already explains the outcome: the selected
725
+ * result carries its own argument, the blocker record gives its reason, or a
726
+ * thrown cause carries the fault. Logging the
731
727
  * prose there would put model output describing the user's data into a record
732
728
  * that had no use for it — including on every successful plan, where the closing
733
729
  * text is the planner narrating work that demonstrably went fine.
@@ -743,8 +739,7 @@ const PROSE_EXPLAINS_OUTCOME = new Set(["no_outcome", "loop_error", "timeout"]);
743
739
  * caller actually returned.
744
740
  */
745
741
  function shapeOutcome(args) {
746
- const { holder, runError, timedOut, outerAborted } = args;
747
- const outcome = holder.outcome;
742
+ const { outcome, runError, timedOut, outerAborted } = args;
748
743
  if (outcome?.kind === "plan_submitted") {
749
744
  return { output: { event: "plan_complete", planId: outcome.planId, plan: outcome.plan }, kind: "plan_submitted" };
750
745
  }
@@ -753,7 +748,7 @@ function shapeOutcome(args) {
753
748
  output: {
754
749
  event: "clarification_needed",
755
750
  question: outcome.question,
756
- ...(outcome.questionContext ? { questionContext: outcome.questionContext } : {}),
751
+ questionContext: outcome.questionContext ? outcome.questionContext : undefined,
757
752
  },
758
753
  kind: "clarification",
759
754
  };
@@ -778,7 +773,7 @@ function shapeOutcome(args) {
778
773
  return { output: { event: "error", error: "Plan generation was cancelled." }, kind: "cancelled" };
779
774
  }
780
775
  if (runError) {
781
- const msg = runError instanceof Error ? runError.message : String(runError);
776
+ const msg = errorMessageOf(runError) ?? textOf(runError);
782
777
  return { output: { event: "error", error: `Plan generation failed: ${msg}` }, kind: "loop_error" };
783
778
  }
784
779
  return {
@@ -831,7 +826,7 @@ export function buildPlannerSearchTools(deps, hooks = {}) {
831
826
  // cross-field index that ranks a method paper best.
832
827
  ncbi.pubmed,
833
828
  searchArxivTool,
834
- createSearchSemanticScholarTool({ ...(bioKeys.semanticScholar === undefined ? {} : { apiKey: bioKeys.semanticScholar }) }),
829
+ createSearchSemanticScholarTool({ apiKey: bioKeys.semanticScholar }),
835
830
  // The same approach as running code — a pipeline that someone published.
836
831
  createSearchGithubReposTool({ githubToken: bioKeys.github }),
837
832
  // The API of a staged package, so a step names a function that exists.
@@ -845,92 +840,78 @@ export function buildPlannerSearchTools(deps, hooks = {}) {
845
840
  // The planner is a conversation surface, thus the pool-scope reader binds
846
841
  // when the embedder gives one, and the answer names what the store HOLDS.
847
842
  createListAvailablePackagesTool({
848
- ...(deps.farmLockFile === undefined ? {} : { farmLockFile: deps.farmLockFile }),
849
- ...(deps.imagePackagesFile === undefined ? {} : { imagePackagesFile: deps.imagePackagesFile }),
850
- ...(deps.readPoolInventory === undefined ? {} : { readPoolInventory: deps.readPoolInventory }),
843
+ farmLockFile: deps.farmLockFile,
844
+ imagePackagesFile: deps.imagePackagesFile,
845
+ readPoolInventory: deps.readPoolInventory,
851
846
  }),
852
847
  // The knowledge plane: one cited procedure per situation, and one check
853
848
  // of the draft. Both attach only when the embedder binds a client.
854
849
  ...createKnowledgeTools({
855
- ...(deps.knowledge === undefined ? {} : { client: deps.knowledge }),
856
- ...(deps.farmLockFile === undefined ? {} : { farmLockFile: deps.farmLockFile }),
857
- ...(deps.imagePackagesFile === undefined ? {} : { imagePackagesFile: deps.imagePackagesFile }),
858
- ...(deps.refStorePath === undefined ? {} : { refStorePath: deps.refStorePath }),
859
- ...(hooks.onRecommend === undefined ? {} : { onRecommend: hooks.onRecommend }),
850
+ client: deps.knowledge,
851
+ farmLockFile: deps.farmLockFile,
852
+ imagePackagesFile: deps.imagePackagesFile,
853
+ refStorePath: deps.refStorePath,
854
+ onRecommend: hooks.onRecommend,
860
855
  }),
861
856
  ];
862
857
  }
858
+ const generatePlanInputSchema = z.object({
859
+ researchQuestion: z.string().describe("What the user wants to analyze — their goal and specific questions."),
860
+ analystNotes: z
861
+ .string()
862
+ .optional()
863
+ .describe("Facts about the data that ONLY the user could have told you and the data profile cannot know — " +
864
+ 'e.g. "samples 3 and 7 were re-sequenced", "treat batch B as the reference", ' +
865
+ '"the tumor/normal labels in column 4 are swapped". ' +
866
+ "This is NOT a place to restate the data profile: organism, omics type, experimental design, " +
867
+ "condition names, file names and dimensions are already loaded server-side and given to the " +
868
+ "planner — repeating them here only risks contradicting the record. Omit entirely unless the " +
869
+ "user has told you something the profile does not already hold."),
870
+ priorRuns: z
871
+ .string()
872
+ .optional()
873
+ .describe("Summary of prior run results if any exist: which steps ran, " + "what succeeded/failed, key findings. Omit if no prior runs."),
874
+ userConstraints: z
875
+ .string()
876
+ .optional()
877
+ .describe("User-specified constraints: preferred methods, steps to " + "include/exclude, resource limits, modifications to a prior plan."),
878
+ parentPlanId: z
879
+ .string()
880
+ .regex(PLAN_ID_RE, "parentPlanId must be a pln-<8hex> value")
881
+ .optional()
882
+ .describe("The planId of the prior plan being iterated on. Set only when " + "the user is asking for modifications to an existing plan."),
883
+ });
863
884
  /** Build the `generate_plan` tool bound to its provider and pool. */
864
- export function createGeneratePlanTool(deps) {
885
+ export function createGeneratePlanTool(deps, registerSubAgent) {
865
886
  const baseLogger = (deps.logger ?? createNoopLogger()).named("generate-plan");
866
- return defineTool({
867
- id: "generate_plan",
868
- description: "Generate an analysis plan (DAG of steps) for this analysis's data and the user's research question. " +
869
- "The dataset's own facts — domain, organism, experimental design, condition names, quality concerns, " +
870
- "per-file data types and dimensions — are read server-side from the persisted data profile and handed " +
871
- "to the planner directly. Do NOT summarize or re-type them into this call; you cannot restate that " +
872
- "record more faithfully than the record itself. Pass only what the profile cannot hold: the research " +
873
- "question, facts the user told you (analystNotes), prior run results, and their constraints. " +
874
- "The planner is a multi-step sub-agent, thus a call can take many minutes. " +
875
- "The result carries an `event`: `plan_complete` with {planId, plan} — the plan is saved and ready for show_plan and " +
876
- "execute_analysis plan mode, and this call neither shows nor runs it; `clarification_needed` with {question, questionContext?} " +
877
- "when the planner is missing a fact it cannot infer; or `error` with {error} when no plan was saved (the planner reported " +
878
- "a blocker, the save failed, the call timed out or was cancelled, or parentPlanId is not a plan of this analysis).",
879
- inputSchema: z.object({
880
- researchQuestion: z.string().describe("What the user wants to analyze — their goal and specific questions."),
881
- analystNotes: z
882
- .string()
883
- .optional()
884
- .describe("Facts about the data that ONLY the user could have told you and the data profile cannot know — " +
885
- 'e.g. "samples 3 and 7 were re-sequenced", "treat batch B as the reference", ' +
886
- '"the tumor/normal labels in column 4 are swapped". ' +
887
- "This is NOT a place to restate the data profile: organism, omics type, experimental design, " +
888
- "condition names, file names and dimensions are already loaded server-side and given to the " +
889
- "planner — repeating them here only risks contradicting the record. Omit entirely unless the " +
890
- "user has told you something the profile does not already hold."),
891
- priorRuns: z
892
- .string()
893
- .optional()
894
- .describe("Summary of prior run results if any exist: which steps ran, " + "what succeeded/failed, key findings. Omit if no prior runs."),
895
- userConstraints: z
896
- .string()
897
- .optional()
898
- .describe("User-specified constraints: preferred methods, steps to " + "include/exclude, resource limits, modifications to a prior plan."),
899
- parentPlanId: z
900
- .string()
901
- .regex(/^pln-[a-f0-9]{8}$/, "parentPlanId must be a pln-<8hex> value")
902
- .optional()
903
- .describe("The planId of the prior plan being iterated on. Set only when " + "the user is asking for modifications to an existing plan."),
904
- }),
905
- describeCall: ({ researchQuestion }) => researchQuestion,
906
- execute: async (input, ctx) => {
907
- const analysisId = scopeResource(ctx.session.scope).resourceId;
908
- // Bound per invocation: `analysisId` comes from the call-time session, not from
909
- // the construction-time deps the factory closes over.
910
- const logger = baseLogger.with({ analysisId });
911
- const startedAt = Date.now();
912
- const trace = createPlannerTrace();
913
- // Every exit routes through here, so "recorded exactly once per invocation" holds
914
- // by construction rather than by remembering. `elapsedMs` is what separates a
915
- // planner that gave up early from one still working when the guard cut it — the
916
- // fixed budget above makes the number readable without any other context.
917
- //
918
- // `extra` carries the loop account on the paths that reached the loop. The two
919
- // parent-plan early returns never do, and a record claiming `loop.finishReason`
920
- // for a run that never started would be worse than one that omits the field.
921
- const finish = (shaped, extra = {}) => {
922
- logger[OUTCOME_LEVEL[shaped.kind]]("plan generation finished", {
923
- outcome: shaped.kind,
924
- elapsedMs: Date.now() - startedAt,
925
- submitAttempts: trace.submitAttempts,
926
- rejectedAttempts: trace.rejectedAttempts,
927
- ...(trace.rejections.length > 0 ? { rejections: trace.rejections } : {}),
928
- ...(trace.rejectedAttempts > trace.rejections.length ? { rejectionsTruncated: true } : {}),
929
- ...(trace.duplicateTerminalCalls > 0 ? { duplicateTerminalCalls: trace.duplicateTerminalCalls } : {}),
930
- ...extra,
931
- });
932
- return ok(shaped.output);
933
- };
887
+ const plan = registerSubAgent(PLANNER_AGENT_ID, async ({ payload: input, ctx, loop, durably }) => {
888
+ const analysisId = scopeResource(ctx.session.scope).resourceId;
889
+ // Bound per invocation: `analysisId` comes from the call-time session, not from
890
+ // the construction-time deps the factory closes over.
891
+ const logger = baseLogger.with({ analysisId });
892
+ const startedAt = Date.now();
893
+ let trace = { submitAttempts: 0, rejectedAttempts: 0, rejections: [] };
894
+ // Every exit routes through here, so "recorded exactly once per invocation" holds
895
+ // by construction rather than by remembering. `elapsedMs` is what separates a
896
+ // planner that gave up early from one still working when the guard cut it — the
897
+ // fixed budget above makes the number readable without any other context.
898
+ //
899
+ // `extra` carries the loop account on the paths that reached the loop. The two
900
+ // parent-plan early returns never do, and a record claiming `loop.finishReason`
901
+ // for a run that never started would be worse than one that omits the field.
902
+ const finish = (shaped, extra = {}) => {
903
+ logger[OUTCOME_LEVEL[shaped.kind]]("plan generation finished", {
904
+ outcome: shaped.kind,
905
+ elapsedMs: Date.now() - startedAt,
906
+ submitAttempts: trace.submitAttempts,
907
+ rejectedAttempts: trace.rejectedAttempts,
908
+ rejections: trace.rejections,
909
+ rejectionsTruncated: trace.rejectedAttempts > trace.rejections.length ? true : undefined,
910
+ ...extra,
911
+ });
912
+ return shaped.output;
913
+ };
914
+ const seed = await durably("planner.seed", async () => {
934
915
  // If iterating, load the parent plan so the planner sees what it is
935
916
  // revising. Fails fast with the sanitized message submit_plan would
936
917
  // have surfaced — saves a wasted planner run.
@@ -941,10 +922,12 @@ export function createGeneratePlanTool(deps) {
941
922
  analysisId,
942
923
  }));
943
924
  if (!priorPlan) {
944
- return finish({
945
- output: { event: "error", error: "parentPlanId is not a valid plan for this analysis" },
946
- kind: "invalid_parent_plan",
947
- });
925
+ return {
926
+ early: {
927
+ output: { event: "error", error: "parentPlanId is not a valid plan for this analysis" },
928
+ kind: "invalid_parent_plan",
929
+ },
930
+ };
948
931
  }
949
932
  priorPlanBlock = formatPriorPlan(input.parentPlanId, priorPlan);
950
933
  // `formatPriorPlan` returns null on a stored plan that no longer parses as an
@@ -956,10 +939,12 @@ export function createGeneratePlanTool(deps) {
956
939
  }
957
940
  catch (err) {
958
941
  logger.error("parent plan could not be loaded", { parentPlanId: input.parentPlanId, ...logger.errorFields(err) });
959
- return finish({
960
- output: { event: "error", error: "Plan iteration failed — parent plan could not be loaded." },
961
- kind: "parent_plan_load_failed",
962
- });
942
+ return {
943
+ early: {
944
+ output: { event: "error", error: "Plan iteration failed — parent plan could not be loaded." },
945
+ kind: "parent_plan_load_failed",
946
+ },
947
+ };
963
948
  }
964
949
  }
965
950
  // The dataset's facts come from the ledger, not from a string a model
@@ -974,11 +959,10 @@ export function createGeneratePlanTool(deps) {
974
959
  // Degrading to "no profile" is correct behaviour and indistinguishable, from
975
960
  // the outcome, from an analysis that genuinely has none — so the read failure
976
961
  // is reported here or it is never known to have happened.
977
- logger.warn("data profile read failed — planning without dataset facts", {
978
- dbErrorType: error.type,
979
- ...("op" in error ? { dbOp: error.op } : {}),
980
- ...("cause" in error ? logger.errorFields(error.cause) : {}),
981
- });
962
+ const fields = { dbErrorType: error.type, dbOp: error.op };
963
+ if ("cause" in error)
964
+ Object.assign(fields, logger.errorFields(error.cause));
965
+ logger.warn("data profile read failed — planning without dataset facts", fields);
982
966
  return null;
983
967
  });
984
968
  const grounding = classifyGrounding(profileStatus);
@@ -988,9 +972,9 @@ export function createGeneratePlanTool(deps) {
988
972
  // the loop and put their rendered content in the planner's seed.
989
973
  const listAvailableRefs = createListAvailableRefsTool(deps.refStorePath === undefined ? {} : { refStorePath: deps.refStorePath });
990
974
  const listAvailablePackages = createListAvailablePackagesTool({
991
- ...(deps.farmLockFile === undefined ? {} : { farmLockFile: deps.farmLockFile }),
992
- ...(deps.imagePackagesFile === undefined ? {} : { imagePackagesFile: deps.imagePackagesFile }),
993
- ...(deps.readPoolInventory === undefined ? {} : { readPoolInventory: deps.readPoolInventory }),
975
+ farmLockFile: deps.farmLockFile,
976
+ imagePackagesFile: deps.imagePackagesFile,
977
+ readPoolInventory: deps.readPoolInventory,
994
978
  });
995
979
  // A path-separator query matches every reference path and selects the
996
980
  // tool's recursive leaf scan, so the seed contains usable file paths,
@@ -1010,122 +994,174 @@ export function createGeneratePlanTool(deps) {
1010
994
  ...(input.priorRuns ? ["", "## Prior Run Results", input.priorRuns] : []),
1011
995
  ...(input.userConstraints ? ["", "## User Constraints", input.userConstraints] : []),
1012
996
  ].join("\n");
1013
- const planTimeoutMs = effectiveDeadlineMs(deps.conversation.provider, PLAN_TIMEOUT_MS);
1014
- // Opens the invocation. Two things are only knowable here and both are
1015
- // first-order suspects when a planner runs long and returns nothing: the size
1016
- // of the seed it was handed, and which of its blocks made it that size. The
1017
- // reference inventory in particular is a recursive leaf scan of whatever this
1018
- // install has staged — a quantity the harness does not control and no other
1019
- // record reports. The census is sizes only: the seed carries the user's
1020
- // research question and dataset facts, which belong in the model's context and
1021
- // not in a log file.
1022
- logger.info("plan generation started", {
1023
- model: deps.conversation.model,
1024
- maxIterations: PLANNER_MAX_ITERATIONS,
1025
- salvageIterations: DEFAULT_SALVAGE_ITERATIONS,
1026
- timeoutMs: planTimeoutMs,
997
+ return {
998
+ prompt,
1027
999
  grounding: grounding.kind,
1028
- ...(input.parentPlanId ? { parentPlanId: input.parentPlanId } : {}),
1029
- seedChars: {
1030
- total: prompt.length,
1000
+ chars: {
1031
1001
  priorPlan: priorPlanBlock?.length ?? 0,
1032
1002
  dataContext: dataContextBlock.length,
1033
1003
  referenceData: refsBlock.length,
1034
1004
  packages: packagesBlock.length,
1035
- researchQuestion: input.researchQuestion.length,
1036
- analystNotes: input.analystNotes?.length ?? 0,
1037
- priorRuns: input.priorRuns?.length ?? 0,
1038
- userConstraints: input.userConstraints?.length ?? 0,
1039
1005
  },
1040
- });
1041
- const holder = { outcome: null };
1042
- const persistCtx = {
1043
- analysisId,
1044
- parentPlanId: input.parentPlanId ?? null,
1045
1006
  };
1046
- // The last recommend answer of this invocation, thus `submit_plan`
1047
- // stamps the snapshot digest, restores a template the model dropped,
1048
- // and repairs or refuses a claim id the answer did not return.
1049
- const memory = { steps: new Map(), claims: new Set() };
1050
- const innerTools = buildInnerTools(holder, trace, persistCtx, deps.pool, deps.resourcePolicy, logger, memory);
1051
- // Built here rather than at construction: a `describeCall` hook reads no
1052
- // dep, thus the tool must stay constructible from an empty bag. The tool
1053
- // definitions are identical across invocations, thus the request prefix
1054
- // that the cache keys on does not move.
1055
- // The guard bounds the repeated calls of one plan: the third call with
1056
- // an input the plan already sent, or the call past the budget of one
1057
- // tool, answers a refusal that says to continue. The terminal tools
1058
- // stay outside it, because a refused submit would strand the plan.
1059
- let refusals = 0;
1060
- const searchTools = guardRepeatedCalls(buildPlannerSearchTools(deps, {
1061
- onRecommend: (answer) => rememberAnswer(memory, answer),
1062
- }), {
1063
- onRefusal: (refusal) => {
1064
- refusals += 1;
1065
- logger.warn("planner call refused by the guard", { ...refusal, refusals });
1007
+ });
1008
+ if ("early" in seed)
1009
+ return finish(seed.early);
1010
+ const { prompt } = seed;
1011
+ const planTimeoutMs = effectiveDeadlineMs(deps.conversation.provider, PLAN_TIMEOUT_MS);
1012
+ // Opens the invocation. Two things are only knowable here and both are
1013
+ // first-order suspects when a planner runs long and returns nothing: the size
1014
+ // of the seed it was handed, and which of its blocks made it that size. The
1015
+ // reference inventory in particular is a recursive leaf scan of whatever this
1016
+ // install has staged — a quantity the harness does not control and no other
1017
+ // record reports. The census is sizes only: the seed carries the user's
1018
+ // research question and dataset facts, which belong in the model's context and
1019
+ // not in a log file.
1020
+ logger.info("plan generation started", {
1021
+ model: deps.conversation.model,
1022
+ maxIterations: PLANNER_MAX_ITERATIONS,
1023
+ salvageRequests: DEFAULT_SALVAGE_REQUESTS,
1024
+ timeoutMs: planTimeoutMs,
1025
+ grounding: seed.grounding,
1026
+ parentPlanId: input.parentPlanId,
1027
+ seedChars: {
1028
+ total: prompt.length,
1029
+ ...seed.chars,
1030
+ researchQuestion: input.researchQuestion.length,
1031
+ analystNotes: input.analystNotes?.length ?? 0,
1032
+ priorRuns: input.priorRuns?.length ?? 0,
1033
+ userConstraints: input.userConstraints?.length ?? 0,
1034
+ },
1035
+ });
1036
+ const persistCtx = {
1037
+ analysisId,
1038
+ parentPlanId: input.parentPlanId ?? null,
1039
+ invocationId: ctx.invocationId,
1040
+ };
1041
+ // The last recommend answer of this invocation, thus `submit_plan`
1042
+ // stamps the snapshot digest, restores a template the model dropped,
1043
+ // and repairs or refuses a claim id the answer did not return. The loop
1044
+ // folds each answer after its round, on the first run and on a replay.
1045
+ const memory = { steps: new Map(), claims: new Set() };
1046
+ const innerTools = buildInnerTools(deps.resourcePolicy, logger, memory);
1047
+ // Built here rather than at construction: a `describeCall` hook reads no
1048
+ // dep, thus the tool must stay constructible from an empty bag. The tool
1049
+ // definitions are identical across invocations, thus the request prefix
1050
+ // that the cache keys on does not move.
1051
+ const searchTools = buildPlannerSearchTools(deps, {
1052
+ onRecommend: (answer) => rememberAnswer(memory, answer),
1053
+ });
1054
+ const readTools = deps.toolOutputStore ? [createReadToolOutputTool(deps.toolOutputStore)] : [];
1055
+ const planner = {
1056
+ id: PLANNER_AGENT_ID,
1057
+ systemPrompt: composeSystemPrompt(plannerInstructions(deps.resourcePolicy)),
1058
+ model: deps.conversation.model,
1059
+ // The search tools come first, and the terminal tools come last.
1060
+ // The order is the order of the prompt, and it is stable across
1061
+ // every invocation, thus the cached request prefix holds.
1062
+ tools: [...searchTools, ...readTools, ...innerTools.terminal],
1063
+ maxIterations: PLANNER_MAX_ITERATIONS,
1064
+ };
1065
+ // Merge the outer abort signal with a wall-clock timeout — either
1066
+ // cancels the planner promptly.
1067
+ const signal = AbortSignal.any([ctx.signal, AbortSignal.timeout(planTimeoutMs)]);
1068
+ // The guard bounds the repeated calls of one plan: the third call with
1069
+ // an input the plan already sent, or the call past the budget of one
1070
+ // tool, answers a refusal that says to continue. The terminal tools
1071
+ // stay outside it, because a refused submit would strand the plan.
1072
+ let refusals = 0;
1073
+ let runError = null;
1074
+ let run = null;
1075
+ try {
1076
+ run = await runWithSalvage(planner, [{ role: "user", content: prompt }], forSubAgent(ctx.session, PLANNER_AGENT_ID), {
1077
+ ...loop,
1078
+ provider: deps.conversation.provider,
1079
+ signal,
1080
+ policy: {
1081
+ guard: {
1082
+ tools: searchTools.map((tool) => tool.id),
1083
+ onRefusal: (refusal) => {
1084
+ refusals += 1;
1085
+ logger.warn("planner call refused by the guard", { ...refusal, refusals });
1086
+ },
1087
+ },
1066
1088
  },
1089
+ // A planner the guard refused too many times is looping:
1090
+ // the early cap ends the search, and the salvage request
1091
+ // submits the plan it has.
1092
+ capWhen: () => refusals >= PLANNER_REFUSAL_LIMIT,
1093
+ // No forced tool choice: some Claude models reject it with a
1094
+ // 400. The prompt names the terminal tools, and the salvage
1095
+ // request below recovers a run that ends on prose.
1096
+ logger,
1097
+ usageRecorder: deps.usageRecorder,
1098
+ toolOutputStore: deps.toolOutputStore,
1099
+ }, {
1100
+ tools: innerTools.terminal.map((tool) => tool.id),
1101
+ nudge: "You ended without a terminal outcome. Call submit_plan with " +
1102
+ "your final plan now, or request_clarification / report_blocker " +
1103
+ "if you cannot. Do not reply with prose.",
1104
+ hasOutcome: (steps) => selectedOutcomeOf(steps) !== null,
1067
1105
  });
1068
- const readTools = deps.toolOutputStore ? [createReadToolOutputTool(deps.toolOutputStore)] : [];
1069
- const planner = {
1070
- id: PLANNER_AGENT_ID,
1071
- systemPrompt: composeSystemPrompt(plannerInstructions(deps.resourcePolicy)),
1072
- model: deps.conversation.model,
1073
- // The search tools come first, and the terminal tools come last.
1074
- // The order is the order of the prompt, and it is stable across
1075
- // every invocation, thus the cached request prefix holds.
1076
- tools: [...searchTools, ...readTools, ...innerTools.terminal],
1077
- maxIterations: PLANNER_MAX_ITERATIONS,
1078
- };
1079
- // Merge the outer abort signal with a wall-clock timeout — either
1080
- // cancels the planner promptly.
1081
- const signal = AbortSignal.any([ctx.signal, AbortSignal.timeout(planTimeoutMs)]);
1082
- let runError = null;
1083
- let run = null;
1084
- try {
1085
- run = await runToTerminal(planner, [{ role: "user", content: prompt }], forSubAgent(ctx.session, PLANNER_AGENT_ID), {
1086
- provider: deps.conversation.provider,
1087
- signal,
1088
- emit: ctx.emit,
1089
- runStep: passthroughStep,
1090
- resolved: () => holder.outcome !== null,
1091
- // A planner the guard refused too many times is looping:
1092
- // the early cap ends the search, and the salvage turn
1093
- // submits the plan it has.
1094
- stopWhen: () => refusals >= PLANNER_REFUSAL_LIMIT,
1095
- // No forced tool choice: some Claude models reject it with a
1096
- // 400. The prompt names the terminal tools, and the salvage
1097
- // turn below recovers a run that ends on prose.
1098
- logger,
1099
- usageRecorder: deps.usageRecorder,
1100
- toolOutputStore: deps.toolOutputStore,
1101
- // Fold the planner's calls into the turn total the root loop reports.
1102
- turnUsage: ctx.turnUsage,
1103
- // Keeps the usage record keys of two parallel dispatches disjoint —
1104
- // same frame, same call path, same loop-local step names.
1105
- invocationId: ctx.invocationId,
1106
- }, {
1107
- tools: innerTools.terminal,
1108
- nudge: "You ended without a terminal outcome. Call submit_plan with " +
1109
- "your final plan now, or request_clarification / report_blocker " +
1110
- "if you cannot. Do not reply with prose.",
1111
- });
1106
+ }
1107
+ catch (err) {
1108
+ runError = err;
1109
+ }
1110
+ let outcome = null;
1111
+ if (run !== null) {
1112
+ trace = traceOf(run.steps);
1113
+ const selected = selectedOutcomeOf(run.steps);
1114
+ if (selected?.kind === "plan") {
1115
+ const persisted = await durably("planner.persist", () => persistPlan(selected.plan, persistCtx, deps.pool, logger));
1116
+ outcome = persisted.ok
1117
+ ? { kind: "plan_submitted", planId: persisted.planId, plan: selected.plan }
1118
+ : { kind: "persist_error", message: persisted.message };
1112
1119
  }
1113
- catch (err) {
1114
- runError = err;
1120
+ else {
1121
+ outcome = selected;
1115
1122
  }
1116
- const shaped = shapeOutcome({
1117
- holder,
1118
- runError,
1119
- timedOut: signal.aborted && !ctx.signal.aborted,
1120
- outerAborted: ctx.signal.aborted,
1121
- });
1122
- return finish(shaped, {
1123
- // A throw leaves no result to read the transcript off, so the cause takes its
1124
- // place — `shapeOutcome` folds it into one prose sentence for the model, and
1125
- // this is the only place its type and stack survive.
1126
- ...(run ? loopFields(run, shaped.kind) : {}),
1127
- ...(runError ? logger.errorFields(runError) : {}),
1128
- });
1129
- },
1123
+ if (selected?.kind === "blocker") {
1124
+ // A blocker after several rejected submits is a planner giving up on a plan it
1125
+ // could not make valid — a validation problem wearing a blocker's clothes. The
1126
+ // attempt count is what tells those apart, and only this record carries both.
1127
+ logger.warn("planner reported a blocker", {
1128
+ submitAttempts: trace.submitAttempts,
1129
+ ...modelAuthored({ blockerReason: bounded(selected.reason, MAX_LOGGED_PROSE_CHARS) }),
1130
+ });
1131
+ }
1132
+ }
1133
+ const shaped = shapeOutcome({
1134
+ outcome,
1135
+ runError,
1136
+ timedOut: signal.aborted && !ctx.signal.aborted,
1137
+ outerAborted: ctx.signal.aborted,
1138
+ });
1139
+ const extra = {};
1140
+ if (run)
1141
+ Object.assign(extra, loopFields(run, shaped.kind));
1142
+ // A throw leaves no result to read the transcript off, so the cause takes its
1143
+ // place — `shapeOutcome` folds it into one prose sentence for the model, and
1144
+ // this is the only place its type and stack survive.
1145
+ if (runError)
1146
+ Object.assign(extra, logger.errorFields(runError));
1147
+ return finish(shaped, extra);
1148
+ });
1149
+ return defineTool({
1150
+ id: "generate_plan",
1151
+ description: "Generate an analysis plan (DAG of steps) for this analysis's data and the user's research question. " +
1152
+ "The dataset's own facts — domain, organism, experimental design, condition names, quality concerns, " +
1153
+ "per-file data types and dimensions — are read server-side from the persisted data profile and handed " +
1154
+ "to the planner directly. Do NOT summarize or re-type them into this call; you cannot restate that " +
1155
+ "record more faithfully than the record itself. Pass only what the profile cannot hold: the research " +
1156
+ "question, facts the user told you (analystNotes), prior run results, and their constraints. " +
1157
+ "The planner is a multi-step sub-agent, thus a call can take many minutes. " +
1158
+ "The result carries an `event`: `plan_complete` with {planId, plan} — the plan is saved and ready for show_plan and " +
1159
+ "execute_analysis plan mode, and this call neither shows nor runs it; `clarification_needed` with {question, questionContext?} " +
1160
+ "when the planner is missing a fact it cannot infer; or `error` with {error} when no plan was saved (the planner reported " +
1161
+ "a blocker, the save failed, the call timed out or was cancelled, or parentPlanId is not a plan of this analysis).",
1162
+ inputSchema: generatePlanInputSchema,
1163
+ describeCall: ({ researchQuestion }) => researchQuestion,
1164
+ startsWorkflow: true,
1165
+ execute: async (input, ctx) => ok(await plan(input, ctx)),
1130
1166
  });
1131
1167
  }