@inflexa-ai/harness 0.1.1 → 0.2.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 (268) hide show
  1. package/dist/agents/conversation-agent.d.ts +12 -11
  2. package/dist/agents/conversation-agent.js +30 -33
  3. package/dist/agents/report-builder.js +2 -1
  4. package/dist/agents/sandbox/bulk-transcriptomics-agent.js +1 -1
  5. package/dist/agents/sandbox/cheminformatics-agent.js +1 -19
  6. package/dist/agents/sandbox/chromatin-agent.js +1 -10
  7. package/dist/agents/sandbox/dna-methylation-agent.js +1 -10
  8. package/dist/agents/sandbox/drug-repurposing-agent.js +4 -14
  9. package/dist/agents/sandbox/enrichment-agent.js +1 -10
  10. package/dist/agents/sandbox/ephemeral-executor.js +1 -10
  11. package/dist/agents/sandbox/genomic-variant-agent.js +1 -3
  12. package/dist/agents/sandbox/immune-profiling-agent.js +1 -13
  13. package/dist/agents/sandbox/metabolomics-agent.js +1 -1
  14. package/dist/agents/sandbox/microbiome-agent.js +1 -1
  15. package/dist/agents/sandbox/multi-omics-integration-agent.js +1 -12
  16. package/dist/agents/sandbox/multimodal-sc-agent.js +1 -1
  17. package/dist/agents/sandbox/network-agent.js +1 -10
  18. package/dist/agents/sandbox/pkpd-clinical-response-agent.js +2 -8
  19. package/dist/agents/sandbox/proteomics-agent.js +1 -1
  20. package/dist/agents/sandbox/scientific-executor.js +1 -10
  21. package/dist/agents/sandbox/shared.d.ts +17 -6
  22. package/dist/agents/sandbox/shared.js +49 -38
  23. package/dist/agents/sandbox/single-cell-agent.js +1 -1
  24. package/dist/agents/sandbox/spatial-omics-agent.js +1 -1
  25. package/dist/agents/sandbox/statistical-modeling-agent.js +1 -1
  26. package/dist/agents/sandbox/translational-safety-agent.js +3 -9
  27. package/dist/agents/sandbox/types.d.ts +1 -1
  28. package/dist/agents/system-prompt.d.ts +37 -11
  29. package/dist/agents/system-prompt.js +35 -12
  30. package/dist/app/data-profile-orientation.d.ts +34 -0
  31. package/dist/app/data-profile-orientation.js +117 -0
  32. package/dist/contracts/schemas/chat-parts.d.ts +9 -9
  33. package/dist/execution/artifact-metadata.js +2 -1
  34. package/dist/execution/ephemeral-runner.d.ts +13 -0
  35. package/dist/execution/ephemeral-runner.js +23 -1
  36. package/dist/execution/run-synthesis.js +20 -19
  37. package/dist/execution/step-summary.js +2 -1
  38. package/dist/index.d.ts +6 -2
  39. package/dist/index.js +19 -5
  40. package/dist/loop/__fixtures__/scripted-provider.d.ts +2 -2
  41. package/dist/loop/__fixtures__/scripted-provider.js +2 -1
  42. package/dist/loop/metrics.d.ts +33 -1
  43. package/dist/loop/metrics.js +56 -2
  44. package/dist/loop/run-agent.d.ts +13 -1
  45. package/dist/loop/run-agent.js +19 -4
  46. package/dist/memory/card-builders.d.ts +8 -1
  47. package/dist/memory/card-builders.js +20 -3
  48. package/dist/memory/reconstruct-cards.js +4 -3
  49. package/dist/memory/thread-history.d.ts +27 -2
  50. package/dist/memory/thread-history.js +45 -5
  51. package/dist/memory/working-memory.d.ts +93 -10
  52. package/dist/memory/working-memory.js +220 -84
  53. package/dist/prompts/SOUL.d.ts +22 -9
  54. package/dist/prompts/SOUL.js +56 -37
  55. package/dist/prompts/analogical-reasoner.d.ts +1 -1
  56. package/dist/prompts/analogical-reasoner.js +2 -2
  57. package/dist/prompts/briefing.d.ts +100 -0
  58. package/dist/prompts/briefing.js +166 -0
  59. package/dist/prompts/conversation.d.ts +1 -1
  60. package/dist/prompts/conversation.js +94 -440
  61. package/dist/prompts/literature-reviewer.d.ts +1 -1
  62. package/dist/prompts/literature-reviewer.js +10 -10
  63. package/dist/prompts/planner.js +28 -59
  64. package/dist/prompts/report-builder.d.ts +1 -1
  65. package/dist/prompts/report-builder.js +71 -24
  66. package/dist/prompts/sandbox/bulk-transcriptomics-agent.d.ts +1 -1
  67. package/dist/prompts/sandbox/bulk-transcriptomics-agent.js +2 -7
  68. package/dist/prompts/sandbox/cheminformatics-agent.d.ts +1 -1
  69. package/dist/prompts/sandbox/cheminformatics-agent.js +11 -15
  70. package/dist/prompts/sandbox/chromatin-agent.d.ts +1 -1
  71. package/dist/prompts/sandbox/chromatin-agent.js +3 -7
  72. package/dist/prompts/sandbox/data-profiler.d.ts +1 -1
  73. package/dist/prompts/sandbox/data-profiler.js +13 -8
  74. package/dist/prompts/sandbox/dna-methylation-agent.d.ts +1 -1
  75. package/dist/prompts/sandbox/dna-methylation-agent.js +2 -7
  76. package/dist/prompts/sandbox/drug-repurposing-agent.d.ts +1 -1
  77. package/dist/prompts/sandbox/drug-repurposing-agent.js +15 -16
  78. package/dist/prompts/sandbox/enrichment-agent.d.ts +1 -1
  79. package/dist/prompts/sandbox/enrichment-agent.js +2 -9
  80. package/dist/prompts/sandbox/genomic-variant-agent.d.ts +1 -1
  81. package/dist/prompts/sandbox/genomic-variant-agent.js +2 -6
  82. package/dist/prompts/sandbox/immune-profiling-agent.d.ts +1 -1
  83. package/dist/prompts/sandbox/immune-profiling-agent.js +3 -8
  84. package/dist/prompts/sandbox/metabolomics-agent.d.ts +1 -1
  85. package/dist/prompts/sandbox/metabolomics-agent.js +1 -7
  86. package/dist/prompts/sandbox/microbiome-agent.d.ts +1 -1
  87. package/dist/prompts/sandbox/microbiome-agent.js +2 -7
  88. package/dist/prompts/sandbox/multi-omics-integration-agent.d.ts +1 -1
  89. package/dist/prompts/sandbox/multi-omics-integration-agent.js +2 -8
  90. package/dist/prompts/sandbox/multimodal-sc-agent.d.ts +1 -1
  91. package/dist/prompts/sandbox/multimodal-sc-agent.js +2 -7
  92. package/dist/prompts/sandbox/network-agent.d.ts +1 -1
  93. package/dist/prompts/sandbox/network-agent.js +2 -7
  94. package/dist/prompts/sandbox/proteomics-agent.d.ts +1 -1
  95. package/dist/prompts/sandbox/proteomics-agent.js +2 -9
  96. package/dist/prompts/sandbox/single-cell-agent.d.ts +1 -1
  97. package/dist/prompts/sandbox/single-cell-agent.js +2 -9
  98. package/dist/prompts/sandbox/spatial-omics-agent.d.ts +1 -1
  99. package/dist/prompts/sandbox/spatial-omics-agent.js +3 -6
  100. package/dist/prompts/sandbox/statistical-modeling-agent.d.ts +1 -1
  101. package/dist/prompts/sandbox/statistical-modeling-agent.js +2 -7
  102. package/dist/prompts/sandbox/translational-safety-agent.d.ts +1 -1
  103. package/dist/prompts/sandbox/translational-safety-agent.js +13 -14
  104. package/dist/prompts/sandbox-standards.d.ts +22 -14
  105. package/dist/prompts/sandbox-standards.js +91 -71
  106. package/dist/prompts/synthesis-agent.d.ts +1 -1
  107. package/dist/prompts/synthesis-agent.js +33 -131
  108. package/dist/provenance/collector.js +1 -1
  109. package/dist/providers/ai-sdk.js +31 -4
  110. package/dist/providers/prompt-cache.d.ts +74 -0
  111. package/dist/providers/prompt-cache.js +77 -0
  112. package/dist/providers/types.d.ts +33 -0
  113. package/dist/reference-data/catalog.d.ts +131 -0
  114. package/dist/reference-data/catalog.js +348 -0
  115. package/dist/reference-data/receipt.d.ts +41 -0
  116. package/dist/reference-data/receipt.js +40 -0
  117. package/dist/schemas/data-profile-schemas.js +5 -3
  118. package/dist/schemas/plan-schemas.d.ts +21 -21
  119. package/dist/schemas/plan-schemas.js +9 -3
  120. package/dist/schemas/run-synthesis.d.ts +8 -4
  121. package/dist/schemas/run-synthesis.js +92 -56
  122. package/dist/schemas/validate-plan.js +1 -1
  123. package/dist/schemas/workflow-state.d.ts +7 -1
  124. package/dist/schemas/workflow-state.js +33 -19
  125. package/dist/state/artifacts.d.ts +13 -0
  126. package/dist/state/artifacts.js +27 -0
  127. package/dist/state/data-profile.d.ts +64 -5
  128. package/dist/state/index.d.ts +3 -3
  129. package/dist/state/index.js +1 -1
  130. package/dist/tasks/data-profile.d.ts +20 -2
  131. package/dist/tasks/data-profile.js +71 -22
  132. package/dist/tools/bio/chembl.d.ts +38 -0
  133. package/dist/tools/bio/chembl.js +138 -0
  134. package/dist/tools/bio/comptox.d.ts +50 -0
  135. package/dist/tools/bio/comptox.js +149 -0
  136. package/dist/tools/bio/get-impc-ko-profile.js +9 -6
  137. package/dist/tools/bio/index.d.ts +5 -17
  138. package/dist/tools/bio/index.js +5 -17
  139. package/dist/tools/bio/keys.d.ts +14 -9
  140. package/dist/tools/bio/keys.js +16 -16
  141. package/dist/tools/bio/opentargets.d.ts +38 -0
  142. package/dist/tools/bio/opentargets.js +89 -0
  143. package/dist/tools/bio/pubchem.d.ts +29 -0
  144. package/dist/tools/bio/pubchem.js +92 -0
  145. package/dist/tools/bio/pubmed.d.ts +51 -0
  146. package/dist/tools/bio/pubmed.js +100 -0
  147. package/dist/tools/bio/search-bgee-expression.js +12 -6
  148. package/dist/tools/bio/search-clinical-trials.js +16 -5
  149. package/dist/tools/bio/search-faers.js +8 -4
  150. package/dist/tools/bio/search-geo-datasets.js +20 -6
  151. package/dist/tools/bio/search-gwas-catalog.d.ts +1 -1
  152. package/dist/tools/bio/search-pharmgkb.js +10 -4
  153. package/dist/tools/display/index.d.ts +1 -0
  154. package/dist/tools/display/index.js +1 -0
  155. package/dist/tools/display/normalize-echart-spec.d.ts +61 -0
  156. package/dist/tools/display/normalize-echart-spec.js +211 -0
  157. package/dist/tools/display/show-user.d.ts +6 -1
  158. package/dist/tools/display/show-user.js +25 -12
  159. package/dist/tools/execute-plan.js +5 -2
  160. package/dist/tools/iterate-report.d.ts +168 -118
  161. package/dist/tools/iterate-report.js +334 -96
  162. package/dist/tools/lib/chembl-client.d.ts +12 -0
  163. package/dist/tools/lib/chembl-client.js +127 -0
  164. package/dist/tools/lib/ctx-ops.d.ts +224 -0
  165. package/dist/tools/lib/ctx-ops.js +450 -0
  166. package/dist/tools/lib/iuphar-client.js +10 -1
  167. package/dist/tools/lib/openfda-client.js +11 -3
  168. package/dist/tools/lib/pathway-client.js +10 -4
  169. package/dist/tools/lib/pubchem-ops.d.ts +74 -0
  170. package/dist/tools/lib/pubchem-ops.js +224 -0
  171. package/dist/tools/memory/update-working-memory.d.ts +45 -7
  172. package/dist/tools/memory/update-working-memory.js +111 -41
  173. package/dist/tools/report/index.d.ts +3 -2
  174. package/dist/tools/report/index.js +3 -2
  175. package/dist/tools/research/generate-analogy-report.js +25 -12
  176. package/dist/tools/research/generate-plan.d.ts +12 -5
  177. package/dist/tools/research/generate-plan.js +168 -37
  178. package/dist/tools/research/index.d.ts +1 -0
  179. package/dist/tools/research/index.js +1 -0
  180. package/dist/tools/research/inspect-data-profile.d.ts +85 -0
  181. package/dist/tools/research/inspect-data-profile.js +161 -0
  182. package/dist/tools/research/literature-reviewer.js +3 -4
  183. package/dist/tools/sandbox/list-available-packages.d.ts +79 -5
  184. package/dist/tools/sandbox/list-available-packages.js +152 -10
  185. package/dist/tools/sandbox/list-available-refs.d.ts +141 -5
  186. package/dist/tools/sandbox/list-available-refs.js +647 -134
  187. package/dist/tools/sandbox/report-blocker.d.ts +39 -9
  188. package/dist/tools/sandbox/report-blocker.js +43 -20
  189. package/dist/tools/workspace/read-file.js +4 -5
  190. package/dist/tools/workspace/run-exec.d.ts +4 -4
  191. package/dist/tools/workspace/run-exec.js +4 -4
  192. package/dist/tools/workspace/show-file.js +10 -6
  193. package/dist/tools/workspace/show-plan.js +12 -6
  194. package/dist/tools/workspace/workspace-search.d.ts +1 -1
  195. package/dist/tools/workspace/workspace-search.js +11 -5
  196. package/dist/workflows/execute-analysis.d.ts +43 -4
  197. package/dist/workflows/execute-analysis.js +107 -6
  198. package/dist/workflows/target-assessment/lib/citation-validator.js +2 -2
  199. package/dist/workflows/target-assessment/lib/clinical-consequence-annotator.js +2 -1
  200. package/dist/workspace/paths.d.ts +2 -2
  201. package/dist/workspace/paths.js +2 -2
  202. package/package.json +33 -33
  203. package/dist/execution/filesystem-artifact-registry.d.ts +0 -57
  204. package/dist/execution/filesystem-artifact-registry.js +0 -92
  205. package/dist/lib/identifiers.d.ts +0 -8
  206. package/dist/lib/identifiers.js +0 -10
  207. package/dist/lib/mime-detect.d.ts +0 -17
  208. package/dist/lib/mime-detect.js +0 -29
  209. package/dist/lib/streamed-download.d.ts +0 -28
  210. package/dist/lib/streamed-download.js +0 -111
  211. package/dist/lib/trace-context.d.ts +0 -11
  212. package/dist/lib/trace-context.js +0 -49
  213. package/dist/providers/llm-capabilities.d.ts +0 -33
  214. package/dist/providers/llm-capabilities.js +0 -58
  215. package/dist/schemas/render-step-prompt.d.ts +0 -32
  216. package/dist/schemas/render-step-prompt.js +0 -76
  217. package/dist/tools/bio/find-approval-precedent.d.ts +0 -32
  218. package/dist/tools/bio/find-approval-precedent.js +0 -72
  219. package/dist/tools/bio/get-article-details.d.ts +0 -11
  220. package/dist/tools/bio/get-article-details.js +0 -20
  221. package/dist/tools/bio/get-article-full-text.d.ts +0 -19
  222. package/dist/tools/bio/get-article-full-text.js +0 -32
  223. package/dist/tools/bio/get-bioactivity.d.ts +0 -11
  224. package/dist/tools/bio/get-bioactivity.js +0 -23
  225. package/dist/tools/bio/get-drug-info.d.ts +0 -9
  226. package/dist/tools/bio/get-drug-info.js +0 -22
  227. package/dist/tools/bio/get-mechanism.d.ts +0 -8
  228. package/dist/tools/bio/get-mechanism.js +0 -18
  229. package/dist/tools/bio/get-pubchem-assays.d.ts +0 -22
  230. package/dist/tools/bio/get-pubchem-assays.js +0 -107
  231. package/dist/tools/bio/get-pubchem-cross-refs.d.ts +0 -15
  232. package/dist/tools/bio/get-pubchem-cross-refs.js +0 -68
  233. package/dist/tools/bio/get-target-safety.d.ts +0 -18
  234. package/dist/tools/bio/get-target-safety.js +0 -26
  235. package/dist/tools/bio/search-compounds.d.ts +0 -22
  236. package/dist/tools/bio/search-compounds.js +0 -160
  237. package/dist/tools/bio/search-ctx-chemical.d.ts +0 -50
  238. package/dist/tools/bio/search-ctx-chemical.js +0 -130
  239. package/dist/tools/bio/search-ctx-exposure.d.ts +0 -55
  240. package/dist/tools/bio/search-ctx-exposure.js +0 -176
  241. package/dist/tools/bio/search-ctx-hazard.d.ts +0 -111
  242. package/dist/tools/bio/search-ctx-hazard.js +0 -178
  243. package/dist/tools/bio/search-opentargets.d.ts +0 -11
  244. package/dist/tools/bio/search-opentargets.js +0 -25
  245. package/dist/tools/bio/search-pubchem-compound.d.ts +0 -26
  246. package/dist/tools/bio/search-pubchem-compound.js +0 -110
  247. package/dist/tools/bio/search-pubmed.d.ts +0 -17
  248. package/dist/tools/bio/search-pubmed.js +0 -31
  249. package/dist/tools/bio/search-targets.d.ts +0 -9
  250. package/dist/tools/bio/search-targets.js +0 -20
  251. package/dist/tools/bio/search-toxcast.d.ts +0 -36
  252. package/dist/tools/bio/search-toxcast.js +0 -141
  253. package/dist/workflows/register-workflows.d.ts +0 -51
  254. package/dist/workflows/register-workflows.js +0 -48
  255. package/dist/workflows/resume-execute-analysis.d.ts +0 -70
  256. package/dist/workflows/resume-execute-analysis.js +0 -80
  257. package/dist/workflows/target-assessment/correction-loop.d.ts +0 -45
  258. package/dist/workflows/target-assessment/correction-loop.js +0 -62
  259. package/dist/workflows/target-assessment/steps/phase0-resolve.d.ts +0 -71
  260. package/dist/workflows/target-assessment/steps/phase0-resolve.js +0 -21
  261. package/dist/workflows/target-assessment/steps/phase1-aggregate.d.ts +0 -1018
  262. package/dist/workflows/target-assessment/steps/phase1-aggregate.js +0 -25
  263. package/dist/workspace/mount-strategy.d.ts +0 -50
  264. package/dist/workspace/mount-strategy.js +0 -67
  265. package/dist/workspace/provenance-collector.d.ts +0 -34
  266. package/dist/workspace/provenance-collector.js +0 -16
  267. package/dist/workspace/workspace-paths.d.ts +0 -26
  268. package/dist/workspace/workspace-paths.js +0 -31
@@ -2,12 +2,19 @@
2
2
  * generatePlan — a loop-driving tool: a focused `runAgent` loop that builds
3
3
  * an analysis plan (a DAG of steps).
4
4
  *
5
- * The outer tool receives structured context from the conversation agent
6
- * (data profile, research question, prior runs, constraints) and drives an
7
- * internal "planner" agent that communicates outcomes EXCLUSIVELY via four
8
- * terminal tool calls:
5
+ * The dataset's facts are NOT an input. They are read here, server-side, from
6
+ * the analysis's persisted data profile — the one durable record of what the
7
+ * input data is — and projected into the planner's seed. The caller supplies
8
+ * only what the profile cannot hold: the research question, facts the user
9
+ * volunteered (`analystNotes`), prior run results, and plan constraints. A
10
+ * caller-typed dataset summary would be a model re-transcribing a record the
11
+ * database already holds, and it would lose fidelity at every hop.
9
12
  *
10
- * - validate_plan(plan) → non-terminal dry-run (Zod + semantic)
13
+ * The tool drives an internal "planner" agent that communicates outcomes
14
+ * EXCLUSIVELY via four terminal tool calls:
15
+ *
16
+ * - validate_plan(plan) → non-terminal dry-run: an any-shape candidate
17
+ * in, {valid, issues} out (schema + semantic)
11
18
  * - submit_plan(plan) → terminal success: re-validates + persists
12
19
  * - request_clarification(…) → terminal: planner needs more context
13
20
  * - report_blocker(reason) → terminal: no viable plan
@@ -20,17 +27,21 @@
20
27
  import { ok } from "neverthrow";
21
28
  import { z } from "zod";
22
29
  import { formatAgentCatalog } from "../../agents/sandbox-catalog.js";
30
+ import { composeSystemPrompt } from "../../agents/system-prompt.js";
23
31
  import { runToTerminal } from "../../loop/run-to-terminal.js";
24
32
  import { passthroughStep } from "../../loop/run-step.js";
25
33
  import { forSubAgent, scopeResource } from "../../auth/types.js";
26
34
  import { defineTool } from "../define-tool.js";
35
+ import { createReportBlockerToolFor } from "../sandbox/report-blocker.js";
36
+ import { DATA_PROFILE_ORIENTATION_MAX_CHARS, buildDataProfileOrientation } from "../../app/data-profile-orientation.js";
37
+ import { isDataProfileStale } from "../../app/data-profile-policy.js";
27
38
  import { DEFAULT_SANDBOX_MAX_STEPS } from "../../config/resource-limits.js";
28
39
  import { plannerPrompt } from "../../prompts/planner.js";
29
40
  import { hydratePlanSteps, PlannerPlanSchema } from "../../schemas/plan-schemas.js";
30
41
  import { validatePlan } from "../../schemas/validate-plan.js";
31
42
  import { AnalysisPlanSchema } from "../../schemas/workflow-state.js";
32
43
  import { unwrapOrThrow } from "../../lib/result.js";
33
- import { insertPlan, loadPlan } from "../../state/index.js";
44
+ import { insertPlan, loadDataProfileStatus, loadPlan } from "../../state/index.js";
34
45
  // ── Tool-level config ──────────────────────────────────────────────
35
46
  /** Sub-agent identity for the planner — provenance only. */
36
47
  const PLANNER_AGENT_ID = "planner";
@@ -75,6 +86,97 @@ function formatPriorPlan(parentPlanId, plan) {
75
86
  "references survive.",
76
87
  ].join("\n");
77
88
  }
89
+ /**
90
+ * Read the profile ledger row into a grounding variant.
91
+ *
92
+ * Staleness is `isDataProfileStale` — the single definition in
93
+ * `app/data-profile-policy.ts` that the embedder's re-trigger policy and
94
+ * `inspect_data_profile` also use, so the planner and the conversation agent can
95
+ * never disagree about whether a profile still describes the data.
96
+ */
97
+ function classifyGrounding(status) {
98
+ // `loadDataProfileStatus` collapses "no analysis row" and "profile cleared"
99
+ // into one null, deliberately — both mean "no profile" to a consumer.
100
+ if (!status)
101
+ return { kind: "absent" };
102
+ const result = status.result;
103
+ if (!result) {
104
+ if (status.status === "failed")
105
+ return { kind: "failed", error: status.error };
106
+ // `completed` with no result is the empty-manifest path: there were no
107
+ // input files to profile. "Nothing is known about the data" is the honest
108
+ // reading of that, not a failure.
109
+ if (status.status === "completed")
110
+ return { kind: "absent" };
111
+ return { kind: "pending" };
112
+ }
113
+ // A result outlives the attempt that superseded it: `tryRerun` / `tryRetry`
114
+ // preserve `data_profile_result` on purpose, so a non-`completed` status
115
+ // carrying a result means what is on the row is the PREVIOUS profile.
116
+ const reasons = [];
117
+ if (isDataProfileStale(status.seedInputFileIds ?? [], result.inputFileIds)) {
118
+ reasons.push("the analysis's input file set changed after this profile was taken");
119
+ }
120
+ if (status.status === "pending" || status.status === "running") {
121
+ reasons.push("a re-profile is in progress — this is the previous profile");
122
+ }
123
+ if (status.status === "failed") {
124
+ reasons.push(`the most recent profiling attempt failed (${status.error ?? "no reason recorded"}) — this is the previous profile`);
125
+ }
126
+ return reasons.length > 0 ? { kind: "provisional", result, reasons } : { kind: "ready", result };
127
+ }
128
+ /** What a seed says when it has no dataset facts to give the planner. */
129
+ const NO_FACTS_GUIDANCE = "Plan from the research question alone, do not invent dataset specifics, and call " +
130
+ "`request_clarification` if a specific fact about the data is essential to the plan.";
131
+ /**
132
+ * Render the seed's `## Data Context` from the grounding.
133
+ *
134
+ * The facts come from `buildDataProfileOrientation` — the same bounded projector
135
+ * the sandbox step seed uses, so a step and the plan that produced it are oriented
136
+ * by identical text. It is not re-derived here, and the planner cannot reach past
137
+ * it: the planner has no `inspect_data_profile` tool, so what this section does not
138
+ * say is simply not known to it.
139
+ *
140
+ * An absent profile renders NOTHING — no heading, no apology. Planning without
141
+ * dataset facts is worse-grounded but entirely possible, and a section announcing
142
+ * its own emptiness is an invitation to fill it. Every other state renders,
143
+ * including the two that carry no facts either: a planner told that its record is
144
+ * still being built, or that its facts may have moved, can act on that; one handed
145
+ * stale facts silently cannot.
146
+ *
147
+ * Pure — the same grounding renders the same string.
148
+ */
149
+ function renderDataContext(grounding) {
150
+ switch (grounding.kind) {
151
+ case "absent":
152
+ return "";
153
+ case "pending":
154
+ return ["## Data Context", "", `This analysis is still being profiled — no dataset facts are available yet. ${NO_FACTS_GUIDANCE}`].join("\n");
155
+ case "failed":
156
+ return [
157
+ "## Data Context",
158
+ "",
159
+ `Data profiling failed (${grounding.error ?? "no reason recorded"}) and no earlier profile exists, ` +
160
+ `so no dataset facts are available. ${NO_FACTS_GUIDANCE}`,
161
+ ].join("\n");
162
+ case "ready":
163
+ case "provisional": {
164
+ const lines = [
165
+ "## Data Context",
166
+ "",
167
+ "From this analysis's persisted data profile — the authoritative record of what the input data " +
168
+ "is, derived by profiling the files themselves. Nobody typed it; ground your plan in it.",
169
+ "",
170
+ buildDataProfileOrientation(grounding.result, DATA_PROFILE_ORIENTATION_MAX_CHARS),
171
+ ];
172
+ if (grounding.kind === "provisional") {
173
+ lines.push("", `PROVISIONAL — this profile may not describe the analysis's current inputs: ${grounding.reasons.join("; ")}. ` +
174
+ "Plan on it, but keep the plan robust to any fact above having moved.");
175
+ }
176
+ return lines.join("\n");
177
+ }
178
+ }
179
+ }
78
180
  // ── Persistence ─────────────────────────────────────────────────────
79
181
  /**
80
182
  * Persist a validated plan. Returns the planId, or an outcome-ready error
@@ -146,14 +248,24 @@ function fullyValidate(candidate, resourcePolicy) {
146
248
  * loop finishes.
147
249
  */
148
250
  function buildInnerTools(holder, persistCtx, pool, resourcePolicy) {
149
- const candidatePlan = z.object({ plan: z.unknown() });
150
251
  const validatePlanTool = defineTool({
151
252
  id: "validate_plan",
152
- description: "Dry-run a candidate plan against the schema and semantic checks " +
153
- "(agent IDs, DAG cycles, dependency references, unique step IDs, " +
154
- "resources). Returns {valid, issues}. Non-terminal — call as many " +
155
- "times as needed to iterate toward a clean plan before submit_plan.",
156
- inputSchema: candidatePlan,
253
+ description: "Dry-run a candidate plan and get back everything that is wrong with it. " +
254
+ "Takes the plan in ANY shape: a malformed, partial, or wrong-typed " +
255
+ "candidate is REPORTED, not rejected. Returns {valid, issues[]} covering " +
256
+ "both schema problems (missing / wrong-typed fields) and the semantic " +
257
+ "checks a schema cannot express (unknown agent IDs, DAG cycles, dangling " +
258
+ "depends_on references, duplicate step IDs, resource ceilings). The " +
259
+ "authoritative field-by-field plan schema is the arg schema of " +
260
+ "submit_plan — this tool deliberately does not restate it. Non-terminal: " +
261
+ "call as often as needed to iterate toward a clean plan, then submit_plan.",
262
+ // Deliberately permissive: a structurally-invalid candidate must reach
263
+ // `execute` so the model gets a structured {valid:false, issues} result —
264
+ // including semantic issues — instead of a bare Zod rejection at the loop's
265
+ // input boundary. `execute` re-parses against PlannerPlanSchema itself.
266
+ inputSchema: z.object({
267
+ plan: z.unknown().describe("The candidate plan, in any shape. Field-by-field schema: see submit_plan."),
268
+ }),
157
269
  execute: async (input) => {
158
270
  const result = fullyValidate(input.plan, resourcePolicy);
159
271
  return ok(result.valid ? { valid: true, issues: [] } : { valid: false, issues: result.issues });
@@ -164,8 +276,11 @@ function buildInnerTools(holder, persistCtx, pool, resourcePolicy) {
164
276
  description: "Submit the final plan for persistence. Re-validates the plan; on " +
165
277
  "success returns {accepted: true, planId} — STOP after this. On " +
166
278
  "rejection returns {accepted: false, issues} — fix and call again, " +
167
- "or switch to report_blocker if the plan cannot be made valid.",
168
- inputSchema: candidatePlan,
279
+ "or switch to report_blocker if the plan cannot be made valid. This " +
280
+ "arg schema is the authoritative plan contract.",
281
+ // Strict: a malformed plan must not reach submission. `execute` still
282
+ // re-validates (schema + semantic) — it does not lean on this boundary.
283
+ inputSchema: z.object({ plan: PlannerPlanSchema }),
169
284
  execute: async (input) => {
170
285
  if (holder.outcome !== null) {
171
286
  return ok({
@@ -226,18 +341,15 @@ function buildInnerTools(holder, persistCtx, pool, resourcePolicy) {
226
341
  return ok({ recorded: true });
227
342
  },
228
343
  });
229
- const reportBlockerTool = defineTool({
230
- id: "report_blocker",
231
- description: "Terminal. Use when no valid plan can be produced (out of scope, " +
232
- "data incompatible with every available agent, etc.). Pass a short " +
233
- "reason. Stop after calling.",
234
- inputSchema: z.object({ reason: z.string().min(1) }),
235
- execute: async (input) => {
236
- if (holder.outcome === null) {
237
- holder.outcome = { kind: "blocker", reason: input.reason };
238
- }
239
- return ok({ recorded: true });
344
+ const reportBlockerTool = createReportBlockerToolFor({
345
+ record: (outcome) => {
346
+ if (holder.outcome === null)
347
+ holder.outcome = outcome;
240
348
  },
349
+ blockedWhen: "Ends plan generation with no plan saved. Use it when no valid plan can " +
350
+ "be produced for this data and research question (out of scope, data " +
351
+ "incompatible with every available agent, etc.) — not for a plan you " +
352
+ "could fix and submit.",
241
353
  });
242
354
  const terminal = [submitPlanTool, requestClarificationTool, reportBlockerTool];
243
355
  return { all: [validatePlanTool, ...terminal], terminal };
@@ -288,16 +400,26 @@ function shapeOutcome(args) {
288
400
  export function createGeneratePlanTool(deps) {
289
401
  return defineTool({
290
402
  id: "generate_plan",
291
- description: "Generate an analysis plan (DAG of steps) for the given data context " +
292
- "and research question. Pass all relevant context: data profile " +
293
- "summary, omics type, experimental design, prior run results, and " +
294
- "user constraints. Returns a structured plan ready for show_plan and " +
295
- "execute_plan, or a clarification question if context is insufficient.",
403
+ description: "Generate an analysis plan (DAG of steps) for this analysis's data and the user's research question. " +
404
+ "The dataset's own facts — domain, organism, experimental design, condition names, quality concerns, " +
405
+ "per-file data types and dimensions — are read server-side from the persisted data profile and handed " +
406
+ "to the planner directly. Do NOT summarize or re-type them into this call; you cannot restate that " +
407
+ "record more faithfully than the record itself. Pass only what the profile cannot hold: the research " +
408
+ "question, facts the user told you (analystNotes), prior run results, and their constraints. " +
409
+ "Returns a structured plan ready for show_plan and execute_plan, or a clarification question if " +
410
+ "the planner is missing something it cannot infer.",
296
411
  inputSchema: z.object({
297
- dataContext: z
298
- .string()
299
- .describe("Data profile summary: data type, modality, file descriptions, " + "feature/sample counts, experimental design, condition names."),
300
412
  researchQuestion: z.string().describe("What the user wants to analyze — their goal and specific questions."),
413
+ analystNotes: z
414
+ .string()
415
+ .optional()
416
+ .describe("Facts about the data that ONLY the user could have told you and the data profile cannot know — " +
417
+ 'e.g. "samples 3 and 7 were re-sequenced", "treat batch B as the reference", ' +
418
+ '"the tumor/normal labels in column 4 are swapped". ' +
419
+ "This is NOT a place to restate the data profile: organism, omics type, experimental design, " +
420
+ "condition names, file names and dimensions are already loaded server-side and given to the " +
421
+ "planner — repeating them here only risks contradicting the record. Omit entirely unless the " +
422
+ "user has told you something the profile does not already hold."),
301
423
  priorRuns: z
302
424
  .string()
303
425
  .optional()
@@ -338,13 +460,22 @@ export function createGeneratePlanTool(deps) {
338
460
  });
339
461
  }
340
462
  }
463
+ // The dataset's facts come from the ledger, not from a string a model
464
+ // typed: the profile is the only durable record of what the input data
465
+ // is, and every re-transcription of it loses fidelity.
466
+ //
467
+ // A read failure degrades to "no profile" rather than throwing. Planning
468
+ // without dataset facts is a real, supported state (see `renderDataContext`),
469
+ // so a ledger read that fails must cost the planner its grounding — never
470
+ // the user their plan.
471
+ const profileStatus = await loadDataProfileStatus(deps.pool, analysisId).unwrapOr(null);
472
+ const dataContextBlock = renderDataContext(classifyGrounding(profileStatus));
341
473
  const prompt = [
342
474
  ...(priorPlanBlock ? [priorPlanBlock, ""] : []),
343
- "## Data Context",
344
- input.dataContext,
345
- "",
475
+ ...(dataContextBlock ? [dataContextBlock, ""] : []),
346
476
  "## Research Question",
347
477
  input.researchQuestion,
478
+ ...(input.analystNotes ? ["", "## Analyst Notes (from the user — facts about the data the profile does not record)", input.analystNotes] : []),
348
479
  ...(input.priorRuns ? ["", "## Prior Run Results", input.priorRuns] : []),
349
480
  ...(input.userConstraints ? ["", "## User Constraints", input.userConstraints] : []),
350
481
  ].join("\n");
@@ -356,7 +487,7 @@ export function createGeneratePlanTool(deps) {
356
487
  const innerTools = buildInnerTools(holder, persistCtx, deps.pool, deps.resourcePolicy);
357
488
  const planner = {
358
489
  id: PLANNER_AGENT_ID,
359
- systemPrompt: plannerInstructions(deps.resourcePolicy),
490
+ systemPrompt: composeSystemPrompt(plannerInstructions(deps.resourcePolicy)),
360
491
  model: deps.model,
361
492
  tools: innerTools.all,
362
493
  maxIterations: PLANNER_MAX_ITERATIONS,
@@ -7,6 +7,7 @@
7
7
  */
8
8
  export * from "./generate-plan.js";
9
9
  export * from "./inspect-run.js";
10
+ export * from "./inspect-data-profile.js";
10
11
  export * from "./literature-reviewer.js";
11
12
  export * from "./generate-analogy-report.js";
12
13
  export * from "./context7-docs.js";
@@ -7,6 +7,7 @@
7
7
  */
8
8
  export * from "./generate-plan.js";
9
9
  export * from "./inspect-run.js";
10
+ export * from "./inspect-data-profile.js";
10
11
  export * from "./literature-reviewer.js";
11
12
  export * from "./generate-analogy-report.js";
12
13
  export * from "./context7-docs.js";
@@ -0,0 +1,85 @@
1
+ /**
2
+ * inspectDataProfile — read the analysis's data profile.
3
+ *
4
+ * Dependency-bearing: the database `Pool` is captured by the factory (see the
5
+ * harness-durable-runtime spec). The analysis id is read from the request-scoped
6
+ * `Session`, not an ambient request context — the same shape as `inspectRun`.
7
+ *
8
+ * **There is no data-profile file.** The profiler's `runs/data-profile/` scratch tree
9
+ * is deleted when profiling completes; the profile's only durable home is the
10
+ * `cortex_analysis_state` row this tool reads. That makes this the single authoritative
11
+ * source for dataset facts (organism, domain, design, per-file type/format/dimensions),
12
+ * and it is why the record is served in full rather than summarized: an agent that
13
+ * cannot pull a fact here has no fallback but to re-derive it from the raw bytes.
14
+ *
15
+ * Bounded by construction. `inspect_run` is the cautionary counter-example — it caps
16
+ * its query at 50 rows with no `limit` parameter and no signal to the caller, so a
17
+ * 51st run is simply invisible. Here the per-file scope is explicitly paged and always
18
+ * reports `total` and `hasMore`: an elided tail is a fact the model can see and act on,
19
+ * never a silent truncation.
20
+ *
21
+ * Lifecycle states are data variants, not errors (the `defineTool` contract): a missing,
22
+ * in-flight, failed, or stale profile is an ordinary, expected outcome the model must be
23
+ * able to reason about, so each returns in the ok channel.
24
+ */
25
+ import type { Pool } from "pg";
26
+ import { type DataProfileFile, type DataProfileResult } from "../../state/index.js";
27
+ /**
28
+ * Whether the served profile can be trusted to describe the analysis's CURRENT inputs.
29
+ * `"stale"` still carries the full profile — a stale profile is far better than none —
30
+ * but says plainly why it may be wrong.
31
+ */
32
+ type Freshness = "ready" | "stale";
33
+ interface ProfileEnvelope {
34
+ readonly state: Freshness;
35
+ /** Why the profile may not describe the current inputs. Present only when `state` is `"stale"`. */
36
+ readonly staleReason?: string;
37
+ readonly profiledAt: string;
38
+ }
39
+ interface OverviewOutput extends ProfileEnvelope {
40
+ readonly scope: "overview";
41
+ readonly summary: string;
42
+ readonly domain?: string;
43
+ readonly subtype?: string;
44
+ readonly organism?: DataProfileResult["organism"];
45
+ readonly tissue?: string | null;
46
+ readonly cellType?: string | null;
47
+ readonly condition?: string | null;
48
+ readonly experimentalDesign?: string;
49
+ readonly qualityAssessment?: DataProfileResult["qualityAssessment"];
50
+ readonly accessions?: string[];
51
+ /** How many per-file records `scope:"files"` would page through. */
52
+ readonly fileCount: number;
53
+ }
54
+ interface FilesOutput extends ProfileEnvelope {
55
+ readonly scope: "files";
56
+ readonly page: number;
57
+ readonly pageSize: number;
58
+ /** Every per-file record the profile holds — not just this page. */
59
+ readonly total: number;
60
+ /** True when records remain past this page. Truncation is always stated, never silent. */
61
+ readonly hasMore: boolean;
62
+ readonly files: DataProfileFile[];
63
+ }
64
+ /** No profile to serve — the honest lifecycle states, in the ok channel. */
65
+ interface AbsentOutput {
66
+ readonly state: "absent";
67
+ readonly message: string;
68
+ }
69
+ interface PendingOutput {
70
+ readonly state: "pending";
71
+ readonly status: "pending" | "running";
72
+ readonly message: string;
73
+ }
74
+ interface FailedOutput {
75
+ readonly state: "failed";
76
+ readonly error: string | null;
77
+ readonly message: string;
78
+ }
79
+ export type InspectDataProfileOutput = OverviewOutput | FilesOutput | AbsentOutput | PendingOutput | FailedOutput;
80
+ export declare function createInspectDataProfileTool(pool: Pool): import("../define-tool.js").Tool<{
81
+ scope?: "files" | "overview" | undefined;
82
+ page?: number | undefined;
83
+ pageSize?: number | undefined;
84
+ }, InspectDataProfileOutput>;
85
+ export {};
@@ -0,0 +1,161 @@
1
+ /**
2
+ * inspectDataProfile — read the analysis's data profile.
3
+ *
4
+ * Dependency-bearing: the database `Pool` is captured by the factory (see the
5
+ * harness-durable-runtime spec). The analysis id is read from the request-scoped
6
+ * `Session`, not an ambient request context — the same shape as `inspectRun`.
7
+ *
8
+ * **There is no data-profile file.** The profiler's `runs/data-profile/` scratch tree
9
+ * is deleted when profiling completes; the profile's only durable home is the
10
+ * `cortex_analysis_state` row this tool reads. That makes this the single authoritative
11
+ * source for dataset facts (organism, domain, design, per-file type/format/dimensions),
12
+ * and it is why the record is served in full rather than summarized: an agent that
13
+ * cannot pull a fact here has no fallback but to re-derive it from the raw bytes.
14
+ *
15
+ * Bounded by construction. `inspect_run` is the cautionary counter-example — it caps
16
+ * its query at 50 rows with no `limit` parameter and no signal to the caller, so a
17
+ * 51st run is simply invisible. Here the per-file scope is explicitly paged and always
18
+ * reports `total` and `hasMore`: an elided tail is a fact the model can see and act on,
19
+ * never a silent truncation.
20
+ *
21
+ * Lifecycle states are data variants, not errors (the `defineTool` contract): a missing,
22
+ * in-flight, failed, or stale profile is an ordinary, expected outcome the model must be
23
+ * able to reason about, so each returns in the ok channel.
24
+ */
25
+ import { ok } from "neverthrow";
26
+ import { z } from "zod";
27
+ import { isDataProfileStale } from "../../app/data-profile-policy.js";
28
+ import { scopeResource } from "../../auth/types.js";
29
+ import { unwrapOrThrow } from "../../lib/result.js";
30
+ import { loadDataProfileStatus } from "../../state/index.js";
31
+ import { defineTool } from "../define-tool.js";
32
+ /** Per-file records returned per page when the caller names none. */
33
+ const DEFAULT_PAGE_SIZE = 20;
34
+ /** Ceiling on `pageSize` — a page is a context-window budget, not a dump. */
35
+ const MAX_PAGE_SIZE = 100;
36
+ /**
37
+ * Name every reason the served profile may not describe the current inputs.
38
+ *
39
+ * Two independent things make a profile stale, and both are ordinary. The input set can
40
+ * change under a completed profile (`isDataProfileStale` — the same predicate the
41
+ * embedder's re-trigger policy uses; staleness is defined once, in
42
+ * `app/data-profile-policy.ts`, not re-derived here). And the ledger can have moved off
43
+ * `completed` while an older result is still on the row: `tryRerun`/`tryRetry` preserve
44
+ * `data_profile_result` precisely so a prior profile stays servable while the next
45
+ * attempt runs or after it fails.
46
+ */
47
+ function stalenessReasons(status, result) {
48
+ const reasons = [];
49
+ if (isDataProfileStale(status.seedInputFileIds ?? [], result.inputFileIds)) {
50
+ reasons.push("the analysis's input file set changed after this profile was taken");
51
+ }
52
+ if (status.status === "running" || status.status === "pending") {
53
+ reasons.push("a re-profile is in progress — this is the previous profile");
54
+ }
55
+ if (status.status === "failed") {
56
+ reasons.push(`the most recent profiling attempt failed (${status.error ?? "no reason recorded"}) — this is the previous profile`);
57
+ }
58
+ return reasons;
59
+ }
60
+ export function createInspectDataProfileTool(pool) {
61
+ return defineTool({
62
+ id: "inspect_data_profile",
63
+ description: "Read this analysis's data profile — the AUTHORITATIVE record of what the input dataset is: " +
64
+ "organism and taxon id, scientific domain and subtype, tissue, cell type, condition, experimental design, " +
65
+ "dataset-wide quality concerns and strengths, public accessions, and per-file data type, format, " +
66
+ "row/column dimensions, tags, warnings, and profiling metrics. " +
67
+ "There is NO data-profile file in the workspace — this tool is the only way to read it. Do not search for one, " +
68
+ "and do not rediscover these facts by listing or reading the raw input files. " +
69
+ "Call it before you reason about the data (planning, writing analysis code, interpreting results). " +
70
+ "scope:'overview' (the default) returns the dataset-level orientation plus the number of profiled files. " +
71
+ "scope:'files' returns the per-file records, paged: page (1-based, default 1) and pageSize (default 20, max 100), " +
72
+ "always with the true total and hasMore, so you can see exactly what you have not read yet. " +
73
+ "The state field says what you got: 'ready'; 'stale' (a profile is returned but may not describe the current " +
74
+ "inputs — staleReason says why); 'pending' (profiling is still running); 'failed'; or 'absent' (never profiled, " +
75
+ "or the analysis has no input files).",
76
+ inputSchema: z.object({
77
+ scope: z
78
+ .enum(["overview", "files"])
79
+ .optional()
80
+ .describe("'overview' (default): dataset-level facts + file count. 'files': paged per-file records."),
81
+ page: z.number().int().min(1).optional().describe("1-based page of per-file records. Only used when scope is 'files'. Default 1."),
82
+ pageSize: z
83
+ .number()
84
+ .int()
85
+ .min(1)
86
+ .max(MAX_PAGE_SIZE)
87
+ .optional()
88
+ .describe(`Per-file records per page. Only used when scope is 'files'. Default ${DEFAULT_PAGE_SIZE}, max ${MAX_PAGE_SIZE}.`),
89
+ }),
90
+ execute: async (input, ctx) => {
91
+ const resourceId = scopeResource(ctx.session.scope).resourceId;
92
+ const status = unwrapOrThrow(await loadDataProfileStatus(pool, resourceId));
93
+ // `loadDataProfileStatus` collapses "no analysis row" and "profile cleared"
94
+ // into the same null, on purpose — both mean "no profile" to a consumer.
95
+ if (!status) {
96
+ return ok({
97
+ state: "absent",
98
+ message: "No data profile exists for this analysis. It has not been profiled, or its input files were removed.",
99
+ });
100
+ }
101
+ const result = status.result;
102
+ if (!result) {
103
+ if (status.status === "failed") {
104
+ return ok({
105
+ state: "failed",
106
+ error: status.error,
107
+ message: `Data profiling failed and no earlier profile exists: ${status.error ?? "no reason recorded"}.`,
108
+ });
109
+ }
110
+ if (status.status === "completed") {
111
+ // A completed profile with no result is the empty-manifest path: there
112
+ // were no input files to profile. "Absent" is the honest reading.
113
+ return ok({
114
+ state: "absent",
115
+ message: "Data profiling completed with no result — this analysis has no input files.",
116
+ });
117
+ }
118
+ return ok({
119
+ state: "pending",
120
+ status: status.status,
121
+ message: "Data profiling is still running; no profile is available yet. Proceed without it or ask the user to wait.",
122
+ });
123
+ }
124
+ const reasons = stalenessReasons(status, result);
125
+ const envelope = reasons.length > 0
126
+ ? { state: "stale", staleReason: reasons.join("; "), profiledAt: result.profiledAt }
127
+ : { state: "ready", profiledAt: result.profiledAt };
128
+ if ((input.scope ?? "overview") === "overview") {
129
+ return ok({
130
+ ...envelope,
131
+ scope: "overview",
132
+ summary: result.summary,
133
+ domain: result.domain,
134
+ subtype: result.subtype,
135
+ organism: result.organism,
136
+ tissue: result.tissue,
137
+ cellType: result.cellType,
138
+ condition: result.condition,
139
+ experimentalDesign: result.experimentalDesign,
140
+ qualityAssessment: result.qualityAssessment,
141
+ accessions: result.accessions,
142
+ fileCount: result.files.length,
143
+ });
144
+ }
145
+ const pageSize = input.pageSize ?? DEFAULT_PAGE_SIZE;
146
+ const page = input.page ?? 1;
147
+ const total = result.files.length;
148
+ const start = (page - 1) * pageSize;
149
+ const files = result.files.slice(start, start + pageSize);
150
+ return ok({
151
+ ...envelope,
152
+ scope: "files",
153
+ page,
154
+ pageSize,
155
+ total,
156
+ hasMore: start + files.length < total,
157
+ files,
158
+ });
159
+ },
160
+ });
161
+ }
@@ -19,6 +19,7 @@ import { z } from "zod";
19
19
  import { finalText, runAgent } from "../../loop/run-agent.js";
20
20
  import { passthroughStep } from "../../loop/run-step.js";
21
21
  import { literatureReviewerPrompt } from "../../prompts/literature-reviewer.js";
22
+ import { composeSystemPrompt } from "../../agents/system-prompt.js";
22
23
  import { forSubAgent } from "../../auth/types.js";
23
24
  import { defineTool } from "../define-tool.js";
24
25
  import { createNcbiTools } from "../bio/keys.js";
@@ -41,16 +42,14 @@ export function createLiteratureReviewerTool(deps) {
41
42
  searchPathwayTool,
42
43
  lookupGoTermTool,
43
44
  searchInteractionsTool,
44
- ncbi.searchPubMed,
45
- ncbi.getArticleDetails,
46
- ncbi.getArticleFullText,
45
+ ncbi.pubmed,
47
46
  searchDgidbTool,
48
47
  searchBgeeExpressionTool,
49
48
  getImpcKoProfileTool,
50
49
  ];
51
50
  const agent = {
52
51
  id: AGENT_ID,
53
- systemPrompt: literatureReviewerPrompt,
52
+ systemPrompt: composeSystemPrompt(literatureReviewerPrompt),
54
53
  model: deps.model,
55
54
  tools: reviewerTools,
56
55
  maxIterations: REVIEWER_MAX_ITERATIONS,