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