@inflexa-ai/harness 0.46.2 → 0.47.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 (271) hide show
  1. package/README.md +1 -1
  2. package/dist/agents/conversation-agent.d.ts +3 -8
  3. package/dist/agents/conversation-agent.js +24 -4
  4. package/dist/agents/report-session-agent.d.ts +11 -14
  5. package/dist/agents/report-session-agent.js +30 -17
  6. package/dist/agents/sandbox/shared.d.ts +18 -13
  7. package/dist/agents/sandbox/shared.js +42 -27
  8. package/dist/agents/sandbox/types.d.ts +1 -1
  9. package/dist/agents/sandbox-catalog.d.ts +2 -0
  10. package/dist/agents/sandbox-catalog.js +30 -1
  11. package/dist/app/synthesize-run.d.ts +10 -6
  12. package/dist/app/synthesize-run.js +4 -2
  13. package/dist/contracts/analogy-report.d.ts +46 -2
  14. package/dist/contracts/analogy-report.js +31 -25
  15. package/dist/contracts/chat-parts.d.ts +3 -14
  16. package/dist/contracts/report-reference.d.ts +2 -1
  17. package/dist/contracts/report-reference.js +2 -1
  18. package/dist/contracts/schemas/chat-parts.d.ts +0 -27
  19. package/dist/contracts/schemas/chat-parts.js +1 -3
  20. package/dist/execution/run-canceler.d.ts +2 -0
  21. package/dist/execution/run-canceler.js +9 -0
  22. package/dist/execution/run-synthesis.d.ts +36 -8
  23. package/dist/execution/run-synthesis.js +114 -32
  24. package/dist/index.d.ts +2 -5
  25. package/dist/index.js +3 -6
  26. package/dist/input-scan/enrich.d.ts +5 -10
  27. package/dist/input-scan/enrich.js +12 -8
  28. package/dist/lib/markdown-pdf.d.ts +27 -0
  29. package/dist/lib/markdown-pdf.js +236 -0
  30. package/dist/lib/metrics.d.ts +31 -0
  31. package/dist/lib/metrics.js +64 -0
  32. package/dist/lib/pdf-text.d.ts +23 -0
  33. package/dist/lib/pdf-text.js +34 -0
  34. package/dist/lib/regulatory-corpus/fetch-fda-guidance.js +3 -5
  35. package/dist/lib/regulatory-corpus/fetch-ich.js +3 -5
  36. package/dist/loop/run-loop.d.ts +5 -0
  37. package/dist/loop/run-loop.js +30 -2
  38. package/dist/loop/tool-name-repair.d.ts +11 -0
  39. package/dist/loop/tool-name-repair.js +23 -0
  40. package/dist/loop/types.d.ts +14 -0
  41. package/dist/memory/card-builders.d.ts +1 -1
  42. package/dist/prompts/SOUL.d.ts +6 -6
  43. package/dist/prompts/SOUL.js +22 -121
  44. package/dist/prompts/analogical-reasoner.d.ts +1 -1
  45. package/dist/prompts/analogical-reasoner.js +35 -101
  46. package/dist/prompts/briefing.d.ts +20 -11
  47. package/dist/prompts/briefing.js +36 -19
  48. package/dist/prompts/conversation.d.ts +1 -1
  49. package/dist/prompts/conversation.js +103 -383
  50. package/dist/prompts/planner.d.ts +1 -5
  51. package/dist/prompts/planner.js +45 -128
  52. package/dist/prompts/report-session.d.ts +1 -1
  53. package/dist/prompts/report-session.js +26 -57
  54. package/dist/prompts/sandbox/bulk-transcriptomics-agent.d.ts +1 -1
  55. package/dist/prompts/sandbox/bulk-transcriptomics-agent.js +3 -3
  56. package/dist/prompts/sandbox/cheminformatics-agent.d.ts +1 -1
  57. package/dist/prompts/sandbox/cheminformatics-agent.js +12 -47
  58. package/dist/prompts/sandbox/chromatin-agent.d.ts +1 -1
  59. package/dist/prompts/sandbox/chromatin-agent.js +4 -5
  60. package/dist/prompts/sandbox/data-profiler.js +11 -34
  61. package/dist/prompts/sandbox/dna-methylation-agent.d.ts +1 -1
  62. package/dist/prompts/sandbox/dna-methylation-agent.js +3 -1
  63. package/dist/prompts/sandbox/drug-repurposing-agent.d.ts +1 -1
  64. package/dist/prompts/sandbox/drug-repurposing-agent.js +23 -73
  65. package/dist/prompts/sandbox/enrichment-agent.d.ts +1 -1
  66. package/dist/prompts/sandbox/enrichment-agent.js +3 -1
  67. package/dist/prompts/sandbox/genomic-variant-agent.d.ts +1 -1
  68. package/dist/prompts/sandbox/genomic-variant-agent.js +3 -4
  69. package/dist/prompts/sandbox/immune-profiling-agent.d.ts +1 -1
  70. package/dist/prompts/sandbox/immune-profiling-agent.js +5 -4
  71. package/dist/prompts/sandbox/metabolomics-agent.d.ts +1 -1
  72. package/dist/prompts/sandbox/metabolomics-agent.js +3 -1
  73. package/dist/prompts/sandbox/microbiome-agent.d.ts +1 -1
  74. package/dist/prompts/sandbox/microbiome-agent.js +3 -1
  75. package/dist/prompts/sandbox/multi-omics-integration-agent.d.ts +1 -1
  76. package/dist/prompts/sandbox/multi-omics-integration-agent.js +3 -1
  77. package/dist/prompts/sandbox/multimodal-sc-agent.d.ts +1 -1
  78. package/dist/prompts/sandbox/multimodal-sc-agent.js +3 -1
  79. package/dist/prompts/sandbox/network-agent.d.ts +1 -1
  80. package/dist/prompts/sandbox/network-agent.js +3 -1
  81. package/dist/prompts/sandbox/pkpd-clinical-response-agent.d.ts +1 -1
  82. package/dist/prompts/sandbox/pkpd-clinical-response-agent.js +4 -7
  83. package/dist/prompts/sandbox/proteomics-agent.d.ts +1 -1
  84. package/dist/prompts/sandbox/proteomics-agent.js +3 -1
  85. package/dist/prompts/sandbox/scientific-executor.d.ts +1 -1
  86. package/dist/prompts/sandbox/scientific-executor.js +0 -12
  87. package/dist/prompts/sandbox/single-cell-agent.d.ts +1 -1
  88. package/dist/prompts/sandbox/single-cell-agent.js +3 -1
  89. package/dist/prompts/sandbox/spatial-omics-agent.d.ts +1 -1
  90. package/dist/prompts/sandbox/spatial-omics-agent.js +3 -1
  91. package/dist/prompts/sandbox/statistical-modeling-agent.d.ts +1 -1
  92. package/dist/prompts/sandbox/statistical-modeling-agent.js +4 -4
  93. package/dist/prompts/sandbox/translational-safety-agent.d.ts +1 -1
  94. package/dist/prompts/sandbox/translational-safety-agent.js +10 -29
  95. package/dist/prompts/sandbox-standards.d.ts +15 -18
  96. package/dist/prompts/sandbox-standards.js +96 -305
  97. package/dist/prompts/synthesis-agent.d.ts +1 -1
  98. package/dist/prompts/synthesis-agent.js +24 -90
  99. package/dist/provenance/collector.d.ts +2 -5
  100. package/dist/provenance/collector.js +2 -5
  101. package/dist/provenance/seam.d.ts +36 -1
  102. package/dist/provenance/workspace-reads.d.ts +36 -0
  103. package/dist/provenance/workspace-reads.js +33 -0
  104. package/dist/report-model/production-resolver.d.ts +22 -52
  105. package/dist/report-model/production-resolver.js +43 -112
  106. package/dist/report-render/chart-export.d.ts +9 -0
  107. package/dist/report-render/chart-export.js +1 -1
  108. package/dist/report-render/views/chart-view.d.ts +1 -0
  109. package/dist/report-render/views/chart-view.js +1 -0
  110. package/dist/report-render/views/lineage.d.ts +1 -0
  111. package/dist/report-render/views/page-view.d.ts +1 -0
  112. package/dist/report-render/views/page-view.js +1 -0
  113. package/dist/report-render/views/prose.d.ts +1 -0
  114. package/dist/report-render/views/prose.js +1 -0
  115. package/dist/report-render/views/references-view.d.ts +1 -0
  116. package/dist/report-render/views/values.d.ts +1 -0
  117. package/dist/report-render/views/values.js +1 -0
  118. package/dist/runtime/assemble.d.ts +20 -9
  119. package/dist/runtime/assemble.js +47 -38
  120. package/dist/sandbox/client.d.ts +5 -3
  121. package/dist/sandbox/create-sandbox.d.ts +7 -12
  122. package/dist/sandbox/create-sandbox.js +15 -37
  123. package/dist/sandbox/docker-client.d.ts +1 -3
  124. package/dist/sandbox/docker-client.js +3 -5
  125. package/dist/sandbox/exec-outcome.d.ts +11 -11
  126. package/dist/sandbox/exec-outcome.js +22 -22
  127. package/dist/sandbox/exec.d.ts +5 -0
  128. package/dist/sandbox/exec.js +9 -1
  129. package/dist/sandbox/identity.d.ts +4 -16
  130. package/dist/sandbox/identity.js +5 -21
  131. package/dist/sandbox/k8s-client.d.ts +0 -1
  132. package/dist/sandbox/k8s-client.js +8 -13
  133. package/dist/sandbox/lease-names.d.ts +6 -0
  134. package/dist/sandbox/lease-names.js +12 -0
  135. package/dist/sandbox/leases.d.ts +105 -0
  136. package/dist/sandbox/leases.js +519 -0
  137. package/dist/sandbox/liveness.d.ts +5 -2
  138. package/dist/sandbox/mount-plan.d.ts +10 -1
  139. package/dist/sandbox/mount-plan.js +7 -1
  140. package/dist/sandbox/reconciler.d.ts +58 -0
  141. package/dist/sandbox/reconciler.js +153 -0
  142. package/dist/sandbox/sandbox-step-translate.d.ts +1 -1
  143. package/dist/sandbox/types.d.ts +25 -18
  144. package/dist/sandbox/types.js +10 -4
  145. package/dist/schemas/plan-schemas.d.ts +0 -21
  146. package/dist/schemas/plan-schemas.js +2 -3
  147. package/dist/schemas/run-synthesis.d.ts +2 -2
  148. package/dist/schemas/run-synthesis.js +3 -3
  149. package/dist/schemas/validate-plan.d.ts +1 -14
  150. package/dist/schemas/validate-plan.js +5 -24
  151. package/dist/schemas/workflow-state.d.ts +3 -32
  152. package/dist/schemas/workflow-state.js +4 -13
  153. package/dist/state/index.d.ts +0 -1
  154. package/dist/state/index.js +0 -1
  155. package/dist/state/migrations/20261030120000_lease_the_sandboxes.d.ts +7 -0
  156. package/dist/state/migrations/20261030120000_lease_the_sandboxes.js +34 -0
  157. package/dist/state/migrations/20261030120100_drop_the_report_derivations.d.ts +5 -0
  158. package/dist/state/migrations/20261030120100_drop_the_report_derivations.js +9 -0
  159. package/dist/state/migrations/20261030120200_keep_the_report_derivations.d.ts +6 -0
  160. package/dist/state/migrations/20261030120200_keep_the_report_derivations.js +10 -0
  161. package/dist/state/migrations/index.js +6 -0
  162. package/dist/state/purge-analysis.d.ts +4 -2
  163. package/dist/state/purge-analysis.js +22 -11
  164. package/dist/state/sandbox-leases.d.ts +81 -0
  165. package/dist/state/sandbox-leases.js +247 -0
  166. package/dist/state/schema.d.ts +1 -13
  167. package/dist/state/schema.js +1 -6
  168. package/dist/state/step-executions.d.ts +0 -2
  169. package/dist/state/step-executions.js +1 -4
  170. package/dist/tasks/data-profile.d.ts +40 -2
  171. package/dist/tasks/data-profile.js +89 -87
  172. package/dist/tools/ad-hoc-router.d.ts +1 -6
  173. package/dist/tools/ad-hoc-router.js +5 -48
  174. package/dist/tools/bio/comptox.js +1 -1
  175. package/dist/tools/bio/drug-gene-interactions.js +1 -1
  176. package/dist/tools/bio/gene-disease-evidence.js +1 -1
  177. package/dist/tools/bio/keys.d.ts +1 -1
  178. package/dist/tools/bio/pubmed.js +31 -0
  179. package/dist/tools/bio/search-gene.js +17 -0
  180. package/dist/tools/bio/search-geo-datasets.js +13 -0
  181. package/dist/tools/bio/search-regulatory-approvals.js +1 -1
  182. package/dist/tools/define-tool.d.ts +25 -2
  183. package/dist/tools/define-tool.js +11 -1
  184. package/dist/tools/display/show-user.js +1 -1
  185. package/dist/tools/execute-analysis.d.ts +1 -3
  186. package/dist/tools/execute-analysis.js +5 -33
  187. package/dist/tools/report-session/index.d.ts +0 -1
  188. package/dist/tools/report-session/index.js +0 -1
  189. package/dist/tools/report-session/script-tools.d.ts +30 -0
  190. package/dist/tools/report-session/script-tools.js +177 -0
  191. package/dist/tools/research/generate-analogy-report.d.ts +16 -9
  192. package/dist/tools/research/generate-analogy-report.js +111 -49
  193. package/dist/tools/research/generate-plan.d.ts +102 -7
  194. package/dist/tools/research/generate-plan.js +74 -43
  195. package/dist/tools/research/index.d.ts +4 -3
  196. package/dist/tools/research/index.js +4 -3
  197. package/dist/tools/research/inspect-run.d.ts +0 -1
  198. package/dist/tools/research/inspect-run.js +5 -10
  199. package/dist/tools/research/search-arxiv.js +15 -0
  200. package/dist/tools/research/search-github-repos.js +13 -0
  201. package/dist/tools/research/search-semantic-scholar.js +16 -0
  202. package/dist/tools/research/synthesis-lookups.d.ts +21 -0
  203. package/dist/tools/research/synthesis-lookups.js +29 -0
  204. package/dist/tools/research/web-fetch.d.ts +1 -1
  205. package/dist/tools/research/web-fetch.js +14 -12
  206. package/dist/tools/sandbox/lease-tools.d.ts +32 -0
  207. package/dist/tools/sandbox/lease-tools.js +60 -0
  208. package/dist/tools/script/codemode-worker.d.ts +5 -0
  209. package/dist/tools/script/codemode-worker.js +5 -0
  210. package/dist/tools/script/file-tools.d.ts +100 -0
  211. package/dist/tools/script/file-tools.js +377 -0
  212. package/dist/tools/script/metrics.d.ts +21 -0
  213. package/dist/tools/script/metrics.js +38 -0
  214. package/dist/tools/script/quickjs.wasm +0 -0
  215. package/dist/tools/script/run-script.d.ts +76 -0
  216. package/dist/tools/script/run-script.js +326 -0
  217. package/dist/tools/script/scratch.d.ts +81 -0
  218. package/dist/tools/script/scratch.js +142 -0
  219. package/dist/tools/start-report-session.js +1 -1
  220. package/dist/tools/workspace/edit-file.js +2 -1
  221. package/dist/tools/workspace/execute-command.d.ts +9 -13
  222. package/dist/tools/workspace/execute-command.js +23 -19
  223. package/dist/tools/workspace/file-stat.js +1 -1
  224. package/dist/tools/workspace/grep.d.ts +5 -2
  225. package/dist/tools/workspace/grep.js +19 -10
  226. package/dist/tools/workspace/mutator.d.ts +48 -4
  227. package/dist/tools/workspace/mutator.js +43 -12
  228. package/dist/tools/workspace/read-file.d.ts +5 -2
  229. package/dist/tools/workspace/read-file.js +15 -11
  230. package/dist/tools/workspace/read-surface.d.ts +17 -0
  231. package/dist/tools/workspace/read-surface.js +17 -0
  232. package/dist/tools/workspace/result-bounds.d.ts +1 -0
  233. package/dist/tools/workspace/run-exec.d.ts +12 -13
  234. package/dist/tools/workspace/run-exec.js +13 -13
  235. package/dist/tools/workspace/scan-inputs.d.ts +2 -5
  236. package/dist/tools/workspace/scan-inputs.js +2 -3
  237. package/dist/tools/workspace/show-file.js +1 -1
  238. package/dist/tools/workspace/show-plan.js +4 -3
  239. package/dist/tools/workspace/write-file.js +2 -1
  240. package/dist/workflows/execute-analysis-scheduler.d.ts +0 -26
  241. package/dist/workflows/execute-analysis-scheduler.js +12 -32
  242. package/dist/workflows/execute-analysis.d.ts +17 -35
  243. package/dist/workflows/execute-analysis.js +13 -107
  244. package/dist/workflows/pause.js +29 -22
  245. package/dist/workflows/sandbox-step.d.ts +16 -31
  246. package/dist/workflows/sandbox-step.js +36 -71
  247. package/dist/workflows/test/sql-writes.d.ts +0 -1
  248. package/dist/workflows/test/sql-writes.js +0 -2
  249. package/dist/workspace/filesystem.d.ts +2 -2
  250. package/dist/workspace/filesystem.js +10 -5
  251. package/dist/workspace/paths.d.ts +19 -1
  252. package/dist/workspace/paths.js +22 -1
  253. package/package.json +6 -3
  254. package/dist/prompts/literature-reviewer.d.ts +0 -1
  255. package/dist/prompts/literature-reviewer.js +0 -114
  256. package/dist/sandbox/reaper.d.ts +0 -81
  257. package/dist/sandbox/reaper.js +0 -161
  258. package/dist/sandbox/step-sandbox.d.ts +0 -47
  259. package/dist/sandbox/step-sandbox.js +0 -53
  260. package/dist/state/active-sandboxes.d.ts +0 -33
  261. package/dist/state/active-sandboxes.js +0 -65
  262. package/dist/tasks/extract-values-script.d.ts +0 -64
  263. package/dist/tasks/extract-values-script.js +0 -180
  264. package/dist/tasks/extract-values.d.ts +0 -77
  265. package/dist/tasks/extract-values.js +0 -146
  266. package/dist/tools/report-session/derive-table.d.ts +0 -164
  267. package/dist/tools/report-session/derive-table.js +0 -419
  268. package/dist/tools/research/literature-reviewer.d.ts +0 -42
  269. package/dist/tools/research/literature-reviewer.js +0 -107
  270. package/dist/workflows/metrics.d.ts +0 -23
  271. package/dist/workflows/metrics.js +0 -38
@@ -1,19 +1,17 @@
1
1
  /**
2
2
  * generateAnalogyReport — cross-domain analogy report as a sub-agent tool.
3
3
  *
4
- * An inline harness `AgentDefinition` (mirroring `createLiteratureReviewerTool`)
5
- * driven by `runWithSalvage`. The result reaches the wrapper through one of two
6
- * submit tools because the output envelope is a union and `defineTool` takes
7
- * only a top-level object. Neither tool ends the run: the wrapper selects the
8
- * last accepted report from the recorded calls, else the first blocker. A run
9
- * with no outcome gets one salvage request, then an `extraction-failed`
10
- * envelope, so the frontend never renders raw prose.
4
+ * An inline harness `AgentDefinition` driven by `runWithSalvage`. The reasoner records its report in parts: the
5
+ * problem with its planned analogies, then each analogy when its search is done. A provider filter can stop a reply in
6
+ * the middle of a call, and a cut call is lost whole, thus a small part loses little. The wrapper assembles the report
7
+ * from the last accepted record of each part, else it gives the first blocker. A run with no outcome gets one salvage
8
+ * request, then an `extraction-failed` envelope, so the frontend never renders raw prose.
11
9
  */
12
10
  import { errorMessageOf } from "../../lib/thrown.js";
13
11
  import { textOf } from "../../lib/unknown-text.js";
14
12
  import { ok } from "neverthrow";
15
13
  import { z } from "zod";
16
- import { AnalogyReportSchema } from "@inflexa-ai/harness/contracts/analogy-report.js";
14
+ import { AnalogyProblemSchema, AnalogySchema, } from "@inflexa-ai/harness/contracts/analogy-report.js";
17
15
  import { analogicalReasonerPrompt } from "../../prompts/analogical-reasoner.js";
18
16
  import { composeSystemPrompt } from "../../agents/system-prompt.js";
19
17
  import { forSubAgent } from "../../auth/types.js";
@@ -33,34 +31,80 @@ import { createNcbiTools } from "../bio/keys.js";
33
31
  const AGENT_ID = "analogical-reasoner";
34
32
  /** Tool-call budget for the inner research agent. */
35
33
  const RESEARCH_MAX_ITERATIONS = 40;
36
- const ANALOGY_SALVAGE_NUDGE = "You ended without a terminal outcome. Call submit_analogy_report with " +
37
- "your report now, or report_blocker if phase 1 cannot run. Do not reply " +
38
- "with prose.";
39
- const SUBMIT_ANALOGY_REPORT_TOOL_ID = "submit_analogy_report";
40
- function createSubmitAnalogyReportTool() {
34
+ const ANALOGY_SALVAGE_NUDGE = "You ended without a terminal outcome. Record the problem with record_problem and each analogy with " +
35
+ "record_analogy now, or call report_blocker if phase 1 cannot run. Do not reply with prose.";
36
+ const RECORD_PROBLEM_TOOL_ID = "record_problem";
37
+ const RECORD_ANALOGY_TOOL_ID = "record_analogy";
38
+ const RecordProblemInputSchema = AnalogyProblemSchema.extend({
39
+ plannedAnalogies: z
40
+ .array(z.object({ analogyTitle: z.string(), targetDomain: z.string() }))
41
+ .min(1)
42
+ .describe("Each analogy of phase 1. An analogy that gets no record_analogy call arrives in the report as not loaded."),
43
+ });
44
+ function createRecordProblemTool() {
41
45
  return defineTool({
42
- id: SUBMIT_ANALOGY_REPORT_TOOL_ID,
43
- description: "Submit the analogy report: the problem summary, objects, " +
44
- "relations, and key terms of phase 1, and each analogy with its " +
45
- "coverage and its cited solutions from phase 2. Call it one time with " +
46
- "the full report. If it rejects the report, correct the fields that it " +
47
- "names and call it again. The last accepted report is the result.",
48
- inputSchema: AnalogyReportSchema,
46
+ id: RECORD_PROBLEM_TOOL_ID,
47
+ description: "Record the extraction of phase 1: the problem summary, objects, relations, key terms, and the analogies " +
48
+ "that phase 2 searches. A later call replaces the record.",
49
+ inputSchema: RecordProblemInputSchema,
49
50
  describeCall: "none",
50
- execute: () => Promise.resolve(ok({ recorded: true, message: "Report recorded. Stop now." })),
51
+ execute: () => Promise.resolve(ok({ recorded: true })),
52
+ });
53
+ }
54
+ function createRecordAnalogyTool() {
55
+ return defineTool({
56
+ id: RECORD_ANALOGY_TOOL_ID,
57
+ description: "Record one analogy with its coverage and its cited solutions, when its search is done. A later call " +
58
+ "with the same analogyTitle replaces the record.",
59
+ inputSchema: AnalogySchema,
60
+ describeCall: ({ analogyTitle }) => analogyTitle,
61
+ execute: () => Promise.resolve(ok({ recorded: true })),
51
62
  });
52
63
  }
53
- /** The last report of a run that its call accepted. The input schema of the tool checked it. */
54
- export function acceptedReportOf(steps) {
55
- let accepted;
64
+ /** The parts that a run recorded: the last accepted problem, and the last accepted record of each analogy title. */
65
+ function recordedParts(steps) {
66
+ let problem;
67
+ const analogies = new Map();
56
68
  for (const step of steps) {
57
69
  for (const call of step.calls) {
58
- if (call.toolName !== SUBMIT_ANALOGY_REPORT_TOOL_ID || call.output?.outcome !== "ok")
70
+ if (call.output?.outcome !== "ok")
59
71
  continue;
60
- accepted = recordedInputOf(AnalogyReportSchema, call.input) ?? accepted;
72
+ if (call.toolName === RECORD_PROBLEM_TOOL_ID)
73
+ problem = recordedInputOf(RecordProblemInputSchema, call.input) ?? problem;
74
+ if (call.toolName === RECORD_ANALOGY_TOOL_ID) {
75
+ const analogy = recordedInputOf(AnalogySchema, call.input);
76
+ if (analogy !== undefined)
77
+ analogies.set(analogy.analogyTitle, analogy);
78
+ }
61
79
  }
62
80
  }
63
- return accepted;
81
+ return { problem, analogies };
82
+ }
83
+ /**
84
+ * The report that a run recorded, or nothing when it recorded no problem. The analogies keep the planned order, and a
85
+ * recorded analogy that the plan does not name follows them. A planned analogy with no record is not loaded.
86
+ */
87
+ export function assembledReportOf(steps) {
88
+ const { problem, analogies } = recordedParts(steps);
89
+ if (problem === undefined)
90
+ return undefined;
91
+ const { plannedAnalogies, ...extraction } = problem;
92
+ const planned = plannedAnalogies.map((plan) => analogies.get(plan.analogyTitle) ?? {
93
+ targetDomain: plan.targetDomain,
94
+ analogyTitle: plan.analogyTitle,
95
+ objectMappings: [],
96
+ sharedRelations: "",
97
+ coverage: "not_loaded",
98
+ solutions: [],
99
+ });
100
+ const titles = new Set(plannedAnalogies.map((plan) => plan.analogyTitle));
101
+ const extra = [...analogies.values()].filter((analogy) => !titles.has(analogy.analogyTitle));
102
+ return { schemaVersion: "1", ...extraction, analogies: [...planned, ...extra] };
103
+ }
104
+ /** The run recorded the problem and each analogy that it planned, thus nothing is left to record. */
105
+ function everyPartRecorded(steps) {
106
+ const { problem, analogies } = recordedParts(steps);
107
+ return problem !== undefined && problem.plannedAnalogies.every((plan) => analogies.has(plan.analogyTitle));
64
108
  }
65
109
  function createAnalogyBlockerTool() {
66
110
  return createReportBlockerToolFor({
@@ -124,29 +168,35 @@ export function buildResearchPrompt(input) {
124
168
  return lines.join("\n");
125
169
  }
126
170
  /** Build the `generate_analogy_report` delegation tool bound to its provider. */
171
+ /** The analogical reasoner: its searches run in a script, and it records its report and a blocker directly. */
172
+ export function analogyAgentOf(deps) {
173
+ const ncbi = createNcbiTools(deps.bioKeys);
174
+ return {
175
+ id: AGENT_ID,
176
+ systemPrompt: composeSystemPrompt(analogicalReasonerPrompt),
177
+ model: deps.model,
178
+ // The order and the definitions of the tools stay fixed across calls so the cache-keyed prefix holds.
179
+ tools: [
180
+ ...(deps.toolOutputStore ? [createReadToolOutputTool(deps.toolOutputStore)] : []),
181
+ createRecordProblemTool(),
182
+ createRecordAnalogyTool(),
183
+ createAnalogyBlockerTool(),
184
+ ],
185
+ scriptTools: [
186
+ createSearchSemanticScholarTool({ apiKey: deps.bioKeys.semanticScholar }),
187
+ searchArxivTool,
188
+ createSearchGithubReposTool({ githubToken: deps.bioKeys.github }),
189
+ ncbi.pubmed,
190
+ ],
191
+ scriptRuntime: deps.scriptRuntime,
192
+ maxIterations: RESEARCH_MAX_ITERATIONS,
193
+ };
194
+ }
127
195
  export function createGenerateAnalogyReportTool(deps, registerSubAgent) {
128
196
  const logger = (deps.logger ?? createNoopLogger()).named("generate-analogy-report");
129
- const ncbi = createNcbiTools(deps.bioKeys);
130
- const searchTools = [
131
- createSearchSemanticScholarTool({ apiKey: deps.bioKeys.semanticScholar }),
132
- searchArxivTool,
133
- createSearchGithubReposTool({ githubToken: deps.bioKeys.github }),
134
- ncbi.pubmed,
135
- ...(deps.toolOutputStore ? [createReadToolOutputTool(deps.toolOutputStore)] : []),
136
- ];
137
- const systemPrompt = composeSystemPrompt(analogicalReasonerPrompt);
138
197
  const research = registerSubAgent(AGENT_ID, async ({ payload: input, ctx, loop }) => {
139
198
  const childSession = forSubAgent(ctx.session, AGENT_ID);
140
- // The order and the definitions of the tools stay fixed across calls so the cache-keyed prefix holds.
141
- const submitReportTool = createSubmitAnalogyReportTool();
142
- const blockerTool = createAnalogyBlockerTool();
143
- const agent = {
144
- id: AGENT_ID,
145
- systemPrompt,
146
- model: deps.model,
147
- tools: [...searchTools, submitReportTool, blockerTool],
148
- maxIterations: RESEARCH_MAX_ITERATIONS,
149
- };
199
+ const agent = analogyAgentOf(deps);
150
200
  let steps;
151
201
  try {
152
202
  const run = await runWithSalvage(agent, [{ role: "user", content: buildResearchPrompt(input) }], childSession, {
@@ -155,10 +205,12 @@ export function createGenerateAnalogyReportTool(deps, registerSubAgent) {
155
205
  logger,
156
206
  usageRecorder: deps.usageRecorder,
157
207
  toolOutputStore: deps.toolOutputStore,
208
+ // The run ends when each planned analogy has a record, because the wrapper reads no prose.
209
+ stopWhen: everyPartRecorded,
158
210
  }, {
159
- tools: [SUBMIT_ANALOGY_REPORT_TOOL_ID, REPORT_BLOCKER_TOOL_ID],
211
+ tools: [RECORD_PROBLEM_TOOL_ID, RECORD_ANALOGY_TOOL_ID, REPORT_BLOCKER_TOOL_ID],
160
212
  nudge: ANALOGY_SALVAGE_NUDGE,
161
- hasOutcome: (done) => acceptedReportOf(done) !== undefined || blockerOf(done) !== null,
213
+ hasOutcome: (done) => assembledReportOf(done) !== undefined || blockerOf(done) !== null,
162
214
  });
163
215
  steps = run.steps;
164
216
  }
@@ -173,12 +225,22 @@ export function createGenerateAnalogyReportTool(deps, registerSubAgent) {
173
225
  };
174
226
  }
175
227
  // A report wins over a blocker.
176
- const report = acceptedReportOf(steps);
228
+ const report = assembledReportOf(steps);
177
229
  if (report !== undefined)
178
230
  return report;
179
231
  const blocker = blockerOf(steps);
180
232
  if (blocker !== null)
181
233
  return { schemaVersion: "1", error: { kind: "extraction-failed", message: blocker.reason } };
234
+ if (steps.some((step) => step.finishReason === "content-filter")) {
235
+ logger.warn("the content filter of the provider stopped the analogical reasoner", { steps: steps.length });
236
+ return {
237
+ schemaVersion: "1",
238
+ error: {
239
+ kind: "extraction-failed",
240
+ message: "The content filter of the model provider stopped the reasoner before it recorded the problem. A different wording of the problem can pass the filter.",
241
+ },
242
+ };
243
+ }
182
244
  return buildExtractionFailedEnvelope();
183
245
  });
184
246
  return defineTool({
@@ -22,17 +22,94 @@
22
22
  * last accepted plan, else the last clarification, else the first blocker —
23
23
  * and persists an accepted plan under an id that a rerun of the call gives again.
24
24
  */
25
+ import type { ResolveWorkspaceRoot } from "../../workspace/paths.js";
26
+ import type { ScriptRuntime } from "../script/run-script.js";
27
+ import { type ReadSurfaceDeps } from "../workspace/read-surface.js";
25
28
  import type { Pool } from "pg";
29
+ import { z } from "zod";
26
30
  import type { SubAgentRegistrar } from "../../loop/sub-agent.js";
27
31
  import type { ToolOutputStore } from "../../loop/tool-output.js";
32
+ import type { AgentDefinition } from "../../loop/types.js";
28
33
  import { type ChatProvider } from "../../providers/types.js";
29
34
  import type { UsageRecorder } from "../../billing/usage-recorder.js";
30
35
  import { type Tool } from "../define-tool.js";
31
36
  import type { EnvironmentStorePaths } from "../../config/environment-stores.js";
32
37
  import { type BioToolKeys } from "../bio/keys.js";
33
38
  import { type KnowledgeClient, type KnowledgeRecommendAnswer } from "../knowledge/index.js";
34
- import { type ResourcePolicy } from "../../config/resource-limits.js";
35
39
  import type { Logger } from "../../lib/logger.js";
40
+ import { type DataProfileResult } from "../../state/index.js";
41
+ /** The planner system prompt is deterministic for the process: the catalog is fixed at load. */
42
+ export declare function plannerInstructions(): string;
43
+ /**
44
+ * Format a loaded prior plan as a markdown block the planner can read —
45
+ * narrative + one line per step. Used only when `parentPlanId` is set.
46
+ */
47
+ export declare function formatPriorPlan(parentPlanId: string, plan: unknown): string | null;
48
+ /**
49
+ * What the planner actually holds about the dataset.
50
+ *
51
+ * Every lifecycle state is a variant because every one of them is ordinary: an
52
+ * analysis can be planned before it has been profiled, while profiling is in
53
+ * flight, after an attempt failed, and against a profile whose input files have
54
+ * since changed. None of those is an error, and none of them may stop a plan
55
+ * being produced — a planner told which state it is in can plan accordingly; a
56
+ * planner told nothing plans on facts it cannot know are rotten.
57
+ */
58
+ export type DataGrounding = {
59
+ kind: "ready";
60
+ result: DataProfileResult;
61
+ }
62
+ /** A profile IS available, but may not describe the analysis's current inputs. */
63
+ | {
64
+ kind: "provisional";
65
+ result: DataProfileResult;
66
+ reasons: readonly string[];
67
+ }
68
+ /** Profiling is in flight and no earlier profile exists. */
69
+ | {
70
+ kind: "pending";
71
+ }
72
+ /** Profiling failed and no earlier profile exists. */
73
+ | {
74
+ kind: "failed";
75
+ error: string | null;
76
+ }
77
+ /** Never profiled, cleared, or nothing to profile. */
78
+ | {
79
+ kind: "absent";
80
+ };
81
+ /**
82
+ * Render the seed's `## Data Context` from the grounding.
83
+ *
84
+ * The facts come from `buildDataProfileOrientation` — the same bounded projector
85
+ * the sandbox step seed uses, so a step and the plan that produced it are oriented
86
+ * by identical text. It is not re-derived here, and the planner cannot reach past
87
+ * it: the planner has no `inspect_data_profile` tool, so what this section does not
88
+ * say is simply not known to it.
89
+ *
90
+ * An absent profile renders NOTHING — no heading, no apology. Planning without
91
+ * dataset facts is worse-grounded but entirely possible, and a section announcing
92
+ * its own emptiness is an invitation to fill it. Every other state renders,
93
+ * including the two that carry no facts either: a planner told that its record is
94
+ * still being built, or that its facts may have moved, can act on that; one handed
95
+ * stale facts silently cannot.
96
+ *
97
+ * Pure — the same grounding renders the same string.
98
+ */
99
+ export declare function renderDataContext(grounding: DataGrounding, analysisId: string): string;
100
+ /** The planner agent over its search tools and its terminal tools. */
101
+ export declare function plannerAgentOf(deps: GeneratePlanDeps, searchTools: readonly Tool[], terminal: readonly Tool[]): AgentDefinition;
102
+ /** The terminal tools of a planner with no knowledge answer, for a consumer that runs the planner outside this tool. */
103
+ export declare function plannerTerminalTools(logger: Logger): Tool[];
104
+ /**
105
+ * Render one environment inventory into the seed, reporting a lookup that failed.
106
+ *
107
+ * A failed lookup is written INTO the prompt as prose the planner then plans
108
+ * around — a silent degradation of its grounding that no caller is told about and
109
+ * no outcome distinguishes. The record is what makes "the planner produced a plan
110
+ * naming packages this install does not have" traceable to its cause.
111
+ */
112
+ export declare function inventoryContent(label: string, result: Awaited<ReturnType<Tool["execute"]>>, logger: Logger): string;
36
113
  export type GeneratePlanDeps = EnvironmentStorePaths & {
37
114
  /**
38
115
  * The conversation-role backend the planner is pinned to. Keeping the
@@ -45,12 +122,6 @@ export type GeneratePlanDeps = EnvironmentStorePaths & {
45
122
  };
46
123
  /** Database pool — plan persistence and prior-plan loading. */
47
124
  readonly pool: Pool;
48
- /**
49
- * Host resource policy — stated to the planner as concrete per-step
50
- * ceilings and enforced by `submit_plan`. Absent, the prompt keeps its
51
- * default guidance and validation skips the ceiling check.
52
- */
53
- readonly resourcePolicy?: ResourcePolicy;
54
125
  /** Diagnostic sink. Absent, the tool runs silently — see `LoopOptions.logger`. */
55
126
  readonly logger?: Logger;
56
127
  /** LLM usage-accounting seam for the planner loop; omitted falls back to the no-op recorder. */
@@ -59,6 +130,12 @@ export type GeneratePlanDeps = EnvironmentStorePaths & {
59
130
  readonly toolOutputStore?: ToolOutputStore;
60
131
  /** API keys for the search tools the planner uses to ground a plan. */
61
132
  readonly bioKeys: BioToolKeys;
133
+ /** The read-only workspace: the file tools, the search, the data profile, and the script reads of a PDF and a table. */
134
+ readonly workspace: ReadSurfaceDeps & {
135
+ readonly resolveWorkspaceRoot: ResolveWorkspaceRoot;
136
+ };
137
+ /** Where the VM of `run_script` comes from, for the script reads. See `ScriptRuntime`. */
138
+ readonly scriptRuntime?: ScriptRuntime;
62
139
  /**
63
140
  * The knowledge service client. Bound, the planner gains `knowledge_recommend`
64
141
  * and `knowledge_check`. Absent, no knowledge tool attaches and no
@@ -82,5 +159,23 @@ export type GeneratePlanDeps = EnvironmentStorePaths & {
82
159
  export declare function buildPlannerSearchTools(deps: GeneratePlanDeps, hooks?: {
83
160
  readonly onRecommend?: (answer: KnowledgeRecommendAnswer) => void;
84
161
  }): Tool[];
162
+ /**
163
+ * The seed of the planner: the prior plan when the call revises one, the data context, the two censuses, and then
164
+ * what the caller gave. Each block is optional where the call holds none.
165
+ */
166
+ export declare function composePlannerSeed(parts: {
167
+ readonly priorPlanBlock: string | null;
168
+ readonly dataContextBlock: string;
169
+ readonly groundingBlock: string;
170
+ readonly input: Pick<z.infer<typeof generatePlanInputSchema>, "researchQuestion" | "analystNotes" | "priorRuns" | "userConstraints">;
171
+ }): string;
172
+ declare const generatePlanInputSchema: z.ZodObject<{
173
+ researchQuestion: z.ZodString;
174
+ analystNotes: z.ZodOptional<z.ZodString>;
175
+ priorRuns: z.ZodOptional<z.ZodString>;
176
+ userConstraints: z.ZodOptional<z.ZodString>;
177
+ parentPlanId: z.ZodOptional<z.ZodString>;
178
+ }, z.core.$strip>;
85
179
  /** Build the `generate_plan` tool bound to its provider and pool. */
86
180
  export declare function createGeneratePlanTool(deps: GeneratePlanDeps, registerSubAgent: SubAgentRegistrar): Tool;
181
+ export {};
@@ -22,6 +22,8 @@
22
22
  * last accepted plan, else the last clarification, else the first blocker —
23
23
  * and persists an accepted plan under an id that a rerun of the call gives again.
24
24
  */
25
+ import { createReadScriptTools } from "../script/file-tools.js";
26
+ import { createReadSurfaceTools } from "../workspace/read-surface.js";
25
27
  import { errorMessageOf } from "../../lib/thrown.js";
26
28
  import { textOf } from "../../lib/unknown-text.js";
27
29
  import { createHash } from "node:crypto";
@@ -51,6 +53,7 @@ import { createSearchSemanticScholarTool } from "./search-semantic-scholar.js";
51
53
  import { DATA_PROFILE_ORIENTATION_MAX_CHARS, buildDataProfileOrientation } from "../../app/data-profile-orientation.js";
52
54
  import { DEFAULT_SANDBOX_MAX_STEPS } from "../../config/resource-limits.js";
53
55
  import { plannerPrompt } from "../../prompts/planner.js";
56
+ import { planCardOf } from "../../memory/card-builders.js";
54
57
  import { hydratePlanSteps, PlannerPlanSchema } from "../../schemas/plan-schemas.js";
55
58
  import { validatePlan } from "../../schemas/validate-plan.js";
56
59
  import { AnalysisPlanSchema } from "../../schemas/workflow-state.js";
@@ -169,10 +172,9 @@ function traceOf(steps) {
169
172
  return { submitAttempts, rejectedAttempts, rejections };
170
173
  }
171
174
  // ── Prompt / catalog ────────────────────────────────────────────────
172
- /** The planner system prompt is deterministic per policy — build it once per
173
- * factory (the policy is a construction-time dep, fixed for the process). */
174
- function plannerInstructions(resourcePolicy) {
175
- return plannerPrompt(formatAgentCatalog(), resourcePolicy);
175
+ /** The planner system prompt is deterministic for the process: the catalog is fixed at load. */
176
+ export function plannerInstructions() {
177
+ return plannerPrompt(formatAgentCatalog());
176
178
  }
177
179
  const ValidationIssueSchema = z.object({ path: z.string(), code: z.enum(["schema", "semantic"]), message: z.string(), hint: z.string().optional() });
178
180
  const SubmitPlanOutputSchema = z.discriminatedUnion("accepted", [
@@ -226,7 +228,7 @@ function selectedOutcomeOf(steps) {
226
228
  * Format a loaded prior plan as a markdown block the planner can read —
227
229
  * narrative + one line per step. Used only when `parentPlanId` is set.
228
230
  */
229
- function formatPriorPlan(parentPlanId, plan) {
231
+ export function formatPriorPlan(parentPlanId, plan) {
230
232
  const parsed = AnalysisPlanSchema.safeParse(plan);
231
233
  if (!parsed.success)
232
234
  return null;
@@ -243,10 +245,9 @@ function formatPriorPlan(parentPlanId, plan) {
243
245
  "**Steps:**",
244
246
  ...stepLines,
245
247
  "",
246
- "The user is iterating on this plan. Preserve steps and IDs that are " +
247
- "not being changed; modify only what `userConstraints` describes. " +
248
- "Reuse step IDs when a step's purpose is unchanged so downstream " +
249
- "references survive.",
248
+ "The user is revising this plan, not asking for a new one. The changes are in `## User Constraints`. " +
249
+ "Each step keeps its id, its agent, and its order, also a step that the user asked to change: apply the change to that step. " +
250
+ "Thus `depends_on` and the stored artifacts survive. Give a new id to new work only.",
250
251
  ].join("\n");
251
252
  }
252
253
  /**
@@ -305,7 +306,7 @@ const NO_FACTS_GUIDANCE = "Plan from the research question alone, do not invent
305
306
  *
306
307
  * Pure — the same grounding renders the same string.
307
308
  */
308
- function renderDataContext(grounding, analysisId) {
309
+ export function renderDataContext(grounding, analysisId) {
309
310
  switch (grounding.kind) {
310
311
  case "absent":
311
312
  return "";
@@ -356,7 +357,7 @@ async function persistPlan(plan, ctx, pool, logger) {
356
357
  unwrapOrThrow(await upsertPlan(pool, {
357
358
  planId,
358
359
  analysisId: ctx.analysisId,
359
- plan: hydratePlanSteps(plan),
360
+ plan: { ...hydratePlanSteps(plan), request: ctx.request },
360
361
  parentPlanId: ctx.parentPlanId,
361
362
  }));
362
363
  return { ok: true, planId };
@@ -523,7 +524,7 @@ function restoreSkeletonGrounding(candidate, memory) {
523
524
  * Full validation: Zod schema + semantic checks. The plan is valid only if
524
525
  * BOTH pass.
525
526
  */
526
- function fullyValidate(candidate, resourcePolicy) {
527
+ function fullyValidate(candidate) {
527
528
  // The permissive arg schema of the planner tools accepts a string, thus the
528
529
  // loop-boundary repair never sees a plan that a model sent as a JSON-encoded
529
530
  // string. A small model does this on a large nested schema. Decode it here,
@@ -546,7 +547,7 @@ function fullyValidate(candidate, resourcePolicy) {
546
547
  created_at: parsed.data.created_at,
547
548
  omicsType: parsed.data.omicsType,
548
549
  omicsSubtype: parsed.data.omicsSubtype,
549
- }, { perStepCeiling: resourcePolicy?.perStep });
550
+ });
550
551
  if (!semantic.valid) {
551
552
  return {
552
553
  valid: false,
@@ -559,11 +560,30 @@ function fullyValidate(candidate, resourcePolicy) {
559
560
  }
560
561
  return { valid: true, plan: parsed.data };
561
562
  }
563
+ /** The planner agent over its search tools and its terminal tools. */
564
+ export function plannerAgentOf(deps, searchTools, terminal) {
565
+ const readTools = deps.toolOutputStore ? [createReadToolOutputTool(deps.toolOutputStore)] : [];
566
+ return {
567
+ id: PLANNER_AGENT_ID,
568
+ systemPrompt: composeSystemPrompt(plannerInstructions()),
569
+ model: deps.conversation.model,
570
+ // The search tools come first, and the terminal tools come last. The order is the order of the prompt, and it
571
+ // is stable across every invocation, thus the cached request prefix holds.
572
+ tools: [...searchTools, ...createReadSurfaceTools(deps.workspace), ...readTools, ...terminal],
573
+ scriptTools: createReadScriptTools(deps.workspace),
574
+ scriptRuntime: deps.scriptRuntime,
575
+ maxIterations: PLANNER_MAX_ITERATIONS,
576
+ };
577
+ }
578
+ /** The terminal tools of a planner with no knowledge answer, for a consumer that runs the planner outside this tool. */
579
+ export function plannerTerminalTools(logger) {
580
+ return buildInnerTools(logger, { steps: new Map(), claims: new Set() }).terminal;
581
+ }
562
582
  /**
563
583
  * Build the planner's inner tools for one invocation. `memory` holds the last
564
584
  * `knowledge_recommend` answer of the run, which the loop folds after each round.
565
585
  */
566
- function buildInnerTools(resourcePolicy, logger, memory) {
586
+ function buildInnerTools(logger, memory) {
567
587
  const submitPlanTool = defineTool({
568
588
  id: SUBMIT_PLAN_TOOL_ID,
569
589
  description: "Submit the plan. Checks the plan; on success returns {accepted: true, plan} " +
@@ -592,7 +612,7 @@ function buildInnerTools(resourcePolicy, logger, memory) {
592
612
  logger.info("submit_plan restored the template of skeleton steps", { steps: restored });
593
613
  if (repaired.length > 0)
594
614
  logger.info("submit_plan repaired the claim ids of plan steps", { claims: repaired });
595
- const result = fullyValidate(candidate, resourcePolicy);
615
+ const result = fullyValidate(candidate);
596
616
  // An unknown claim rejects a plan that the schema accepts, beside the
597
617
  // schema issues when the schema rejects it, thus one attempt shows both.
598
618
  if (!result.valid || unknown.length > 0) {
@@ -644,7 +664,7 @@ function buildInnerTools(resourcePolicy, logger, memory) {
644
664
  * no outcome distinguishes. The record is what makes "the planner produced a plan
645
665
  * naming packages this install does not have" traceable to its cause.
646
666
  */
647
- function inventoryContent(label, result, logger) {
667
+ export function inventoryContent(label, result, logger) {
648
668
  if (result.isErr()) {
649
669
  logger.warn("planner grounding inventory lookup failed", { inventory: label, error: result.error.error });
650
670
  return `## ${label}\n\nInventory lookup failed: ${result.error.error}`;
@@ -855,8 +875,28 @@ export function buildPlannerSearchTools(deps, hooks = {}) {
855
875
  }),
856
876
  ];
857
877
  }
878
+ /**
879
+ * The seed of the planner: the prior plan when the call revises one, the data context, the two censuses, and then
880
+ * what the caller gave. Each block is optional where the call holds none.
881
+ */
882
+ export function composePlannerSeed(parts) {
883
+ const { priorPlanBlock, dataContextBlock, groundingBlock, input } = parts;
884
+ return [
885
+ ...(priorPlanBlock ? [priorPlanBlock, ""] : []),
886
+ ...(dataContextBlock ? [dataContextBlock, ""] : []),
887
+ groundingBlock,
888
+ "",
889
+ "## Research Question",
890
+ input.researchQuestion,
891
+ ...(input.analystNotes ? ["", "## Analyst Notes (from the user — facts about the data the profile does not record)", input.analystNotes] : []),
892
+ ...(input.priorRuns ? ["", "## Prior Run Results", input.priorRuns] : []),
893
+ ...(input.userConstraints ? ["", "## User Constraints", input.userConstraints] : []),
894
+ ].join("\n");
895
+ }
858
896
  const generatePlanInputSchema = z.object({
859
- researchQuestion: z.string().describe("What the user wants to analyze — their goal and specific questions."),
897
+ researchQuestion: z
898
+ .string()
899
+ .describe("The request of the user, in the words of the user: the goal, the questions, and each deliverable that the user named, with its format. The plan stores it, and each step of the run reads it."),
860
900
  analystNotes: z
861
901
  .string()
862
902
  .optional()
@@ -983,17 +1023,7 @@ export function createGeneratePlanTool(deps, registerSubAgent) {
983
1023
  const refsBlock = inventoryContent("Available Reference Data", refsResult, logger);
984
1024
  const packagesBlock = inventoryContent("Available Packages", packagesResult, logger);
985
1025
  const groundingBlock = [refsBlock, packagesBlock].join("\n\n");
986
- const prompt = [
987
- ...(priorPlanBlock ? [priorPlanBlock, ""] : []),
988
- ...(dataContextBlock ? [dataContextBlock, ""] : []),
989
- groundingBlock,
990
- "",
991
- "## Research Question",
992
- input.researchQuestion,
993
- ...(input.analystNotes ? ["", "## Analyst Notes (from the user — facts about the data the profile does not record)", input.analystNotes] : []),
994
- ...(input.priorRuns ? ["", "## Prior Run Results", input.priorRuns] : []),
995
- ...(input.userConstraints ? ["", "## User Constraints", input.userConstraints] : []),
996
- ].join("\n");
1026
+ const prompt = composePlannerSeed({ priorPlanBlock, dataContextBlock, groundingBlock, input });
997
1027
  return {
998
1028
  prompt,
999
1029
  grounding: grounding.kind,
@@ -1037,13 +1067,14 @@ export function createGeneratePlanTool(deps, registerSubAgent) {
1037
1067
  analysisId,
1038
1068
  parentPlanId: input.parentPlanId ?? null,
1039
1069
  invocationId: ctx.invocationId,
1070
+ request: input.researchQuestion,
1040
1071
  };
1041
1072
  // The last recommend answer of this invocation, thus `submit_plan`
1042
1073
  // stamps the snapshot digest, restores a template the model dropped,
1043
1074
  // and repairs or refuses a claim id the answer did not return. The loop
1044
1075
  // folds each answer after its round, on the first run and on a replay.
1045
1076
  const memory = { steps: new Map(), claims: new Set() };
1046
- const innerTools = buildInnerTools(deps.resourcePolicy, logger, memory);
1077
+ const innerTools = buildInnerTools(logger, memory);
1047
1078
  // Built here rather than at construction: a `describeCall` hook reads no
1048
1079
  // dep, thus the tool must stay constructible from an empty bag. The tool
1049
1080
  // definitions are identical across invocations, thus the request prefix
@@ -1051,17 +1082,7 @@ export function createGeneratePlanTool(deps, registerSubAgent) {
1051
1082
  const searchTools = buildPlannerSearchTools(deps, {
1052
1083
  onRecommend: (answer) => rememberAnswer(memory, answer),
1053
1084
  });
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
- };
1085
+ const planner = plannerAgentOf(deps, searchTools, innerTools.terminal);
1065
1086
  // Merge the outer abort signal with a wall-clock timeout — either
1066
1087
  // cancels the planner promptly.
1067
1088
  const signal = AbortSignal.any([ctx.signal, AbortSignal.timeout(planTimeoutMs)]);
@@ -1155,13 +1176,23 @@ export function createGeneratePlanTool(deps, registerSubAgent) {
1155
1176
  "record more faithfully than the record itself. Pass only what the profile cannot hold: the research " +
1156
1177
  "question, facts the user told you (analystNotes), prior run results, and their constraints. " +
1157
1178
  "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?} " +
1179
+ "The result carries an `event`: `plan_complete` with {planId, plan} — the plan is saved, the call shows its plan card to the user, " +
1180
+ "and it does not run it (execute_analysis plan mode does, after the approval); `clarification_needed` with {question, questionContext?} " +
1160
1181
  "when the planner is missing a fact it cannot infer; or `error` with {error} when no plan was saved (the planner reported " +
1161
1182
  "a blocker, the save failed, the call timed out or was cancelled, or parentPlanId is not a plan of this analysis).",
1162
1183
  inputSchema: generatePlanInputSchema,
1163
1184
  describeCall: ({ researchQuestion }) => researchQuestion,
1164
1185
  startsWorkflow: true,
1165
- execute: async (input, ctx) => ok(await plan(input, ctx)),
1186
+ execute: async (input, ctx) => {
1187
+ const output = await plan(input, ctx);
1188
+ // The card comes from the result of the planner, with no read of the store, thus a replay of the body
1189
+ // gives the same card under the same id, and a reader keeps one card.
1190
+ if (output.event === "plan_complete" && output.planId !== undefined && output.plan !== undefined) {
1191
+ const card = planCardOf({ planId: output.planId, plan: hydratePlanSteps(output.plan), parentPlanId: input.parentPlanId ?? null });
1192
+ if (card !== null)
1193
+ await ctx.emit({ type: "data-plan", source: ctx.session.provenance, data: card });
1194
+ }
1195
+ return ok(output);
1196
+ },
1166
1197
  });
1167
1198
  }
@@ -1,14 +1,15 @@
1
1
  /**
2
2
  * Research barrel — dependency-bearing planning, run inspection, the
3
- * literature-reviewer and analogical-reasoner sub-agents, developer-docs
4
- * lookup, and the cross-domain search tools the analogical-reasoner uses.
3
+ * analogical-reasoner sub-agent, the lookups of the run synthesizer,
4
+ * developer-docs lookup, and the cross-domain search tools the
5
+ * analogical-reasoner uses.
5
6
  * Dep-bearing tools are factory closures (`createXTool(deps)`); pure tools
6
7
  * are module-scope `defineTool` values.
7
8
  */
8
9
  export * from "./generate-plan.js";
9
10
  export * from "./inspect-run.js";
10
11
  export * from "./inspect-data-profile.js";
11
- export * from "./literature-reviewer.js";
12
+ export * from "./synthesis-lookups.js";
12
13
  export * from "./generate-analogy-report.js";
13
14
  export * from "./context7-docs.js";
14
15
  export * from "./search-semantic-scholar.js";
@@ -1,14 +1,15 @@
1
1
  /**
2
2
  * Research barrel — dependency-bearing planning, run inspection, the
3
- * literature-reviewer and analogical-reasoner sub-agents, developer-docs
4
- * lookup, and the cross-domain search tools the analogical-reasoner uses.
3
+ * analogical-reasoner sub-agent, the lookups of the run synthesizer,
4
+ * developer-docs lookup, and the cross-domain search tools the
5
+ * analogical-reasoner uses.
5
6
  * Dep-bearing tools are factory closures (`createXTool(deps)`); pure tools
6
7
  * are module-scope `defineTool` values.
7
8
  */
8
9
  export * from "./generate-plan.js";
9
10
  export * from "./inspect-run.js";
10
11
  export * from "./inspect-data-profile.js";
11
- export * from "./literature-reviewer.js";
12
+ export * from "./synthesis-lookups.js";
12
13
  export * from "./generate-analogy-report.js";
13
14
  export * from "./context7-docs.js";
14
15
  export * from "./search-semantic-scholar.js";