@inflexa-ai/harness 0.1.1 → 0.3.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 (330) hide show
  1. package/README.md +48 -0
  2. package/dist/agents/conversation-agent.d.ts +15 -11
  3. package/dist/agents/conversation-agent.js +31 -34
  4. package/dist/agents/report-builder.js +2 -1
  5. package/dist/agents/sandbox/bulk-transcriptomics-agent.js +1 -1
  6. package/dist/agents/sandbox/cheminformatics-agent.js +1 -19
  7. package/dist/agents/sandbox/chromatin-agent.js +1 -10
  8. package/dist/agents/sandbox/dna-methylation-agent.js +1 -10
  9. package/dist/agents/sandbox/drug-repurposing-agent.js +4 -14
  10. package/dist/agents/sandbox/enrichment-agent.js +1 -10
  11. package/dist/agents/sandbox/ephemeral-executor.js +1 -10
  12. package/dist/agents/sandbox/genomic-variant-agent.js +1 -3
  13. package/dist/agents/sandbox/immune-profiling-agent.js +1 -13
  14. package/dist/agents/sandbox/metabolomics-agent.js +1 -1
  15. package/dist/agents/sandbox/microbiome-agent.js +1 -1
  16. package/dist/agents/sandbox/multi-omics-integration-agent.js +1 -12
  17. package/dist/agents/sandbox/multimodal-sc-agent.js +1 -1
  18. package/dist/agents/sandbox/network-agent.js +1 -10
  19. package/dist/agents/sandbox/pkpd-clinical-response-agent.js +2 -8
  20. package/dist/agents/sandbox/proteomics-agent.js +1 -1
  21. package/dist/agents/sandbox/scientific-executor.js +1 -10
  22. package/dist/agents/sandbox/shared.d.ts +20 -6
  23. package/dist/agents/sandbox/shared.js +50 -39
  24. package/dist/agents/sandbox/single-cell-agent.js +1 -1
  25. package/dist/agents/sandbox/spatial-omics-agent.js +1 -1
  26. package/dist/agents/sandbox/statistical-modeling-agent.js +1 -1
  27. package/dist/agents/sandbox/translational-safety-agent.js +3 -9
  28. package/dist/agents/sandbox/types.d.ts +1 -1
  29. package/dist/agents/system-prompt.d.ts +37 -11
  30. package/dist/agents/system-prompt.js +35 -12
  31. package/dist/app/chat-turn.d.ts +3 -0
  32. package/dist/app/chat-turn.js +3 -1
  33. package/dist/app/data-profile-orientation.d.ts +34 -0
  34. package/dist/app/data-profile-orientation.js +117 -0
  35. package/dist/app/synthesize-run.d.ts +3 -0
  36. package/dist/app/synthesize-run.js +3 -1
  37. package/dist/contracts/schemas/chat-parts.d.ts +9 -9
  38. package/dist/execution/artifact-metadata.d.ts +3 -0
  39. package/dist/execution/artifact-metadata.js +10 -3
  40. package/dist/execution/artifact-registration.d.ts +2 -1
  41. package/dist/execution/artifact-registration.js +8 -2
  42. package/dist/execution/ephemeral-runner.d.ts +16 -0
  43. package/dist/execution/ephemeral-runner.js +31 -5
  44. package/dist/execution/post-step-pipeline.d.ts +3 -0
  45. package/dist/execution/post-step-pipeline.js +24 -4
  46. package/dist/execution/reconcile-manifest.d.ts +7 -0
  47. package/dist/execution/reconcile-manifest.js +19 -5
  48. package/dist/execution/report-runner.d.ts +3 -0
  49. package/dist/execution/report-runner.js +4 -4
  50. package/dist/execution/run-synthesis.js +20 -19
  51. package/dist/execution/step-summary.d.ts +3 -0
  52. package/dist/execution/step-summary.js +6 -3
  53. package/dist/index.d.ts +9 -2
  54. package/dist/index.js +24 -5
  55. package/dist/lib/chrome.d.ts +4 -1
  56. package/dist/lib/chrome.js +10 -7
  57. package/dist/lib/console-logger.d.ts +19 -0
  58. package/dist/lib/console-logger.js +62 -0
  59. package/dist/lib/logger.d.ts +88 -0
  60. package/dist/lib/logger.js +37 -0
  61. package/dist/lib/otel.d.ts +2 -1
  62. package/dist/lib/otel.js +8 -3
  63. package/dist/loop/__fixtures__/scripted-provider.d.ts +2 -2
  64. package/dist/loop/__fixtures__/scripted-provider.js +2 -1
  65. package/dist/loop/metrics.d.ts +33 -1
  66. package/dist/loop/metrics.js +56 -2
  67. package/dist/loop/run-agent.d.ts +13 -1
  68. package/dist/loop/run-agent.js +19 -4
  69. package/dist/memory/card-builders.d.ts +8 -1
  70. package/dist/memory/card-builders.js +20 -3
  71. package/dist/memory/reconstruct-cards.js +4 -3
  72. package/dist/memory/thread-history.d.ts +27 -2
  73. package/dist/memory/thread-history.js +45 -5
  74. package/dist/memory/working-memory.d.ts +93 -10
  75. package/dist/memory/working-memory.js +220 -84
  76. package/dist/prompts/SOUL.d.ts +22 -9
  77. package/dist/prompts/SOUL.js +56 -37
  78. package/dist/prompts/analogical-reasoner.d.ts +1 -1
  79. package/dist/prompts/analogical-reasoner.js +2 -2
  80. package/dist/prompts/briefing.d.ts +100 -0
  81. package/dist/prompts/briefing.js +166 -0
  82. package/dist/prompts/conversation.d.ts +1 -1
  83. package/dist/prompts/conversation.js +94 -440
  84. package/dist/prompts/literature-reviewer.d.ts +1 -1
  85. package/dist/prompts/literature-reviewer.js +10 -10
  86. package/dist/prompts/planner.js +28 -59
  87. package/dist/prompts/report-builder.d.ts +1 -1
  88. package/dist/prompts/report-builder.js +71 -24
  89. package/dist/prompts/sandbox/bulk-transcriptomics-agent.d.ts +1 -1
  90. package/dist/prompts/sandbox/bulk-transcriptomics-agent.js +2 -7
  91. package/dist/prompts/sandbox/cheminformatics-agent.d.ts +1 -1
  92. package/dist/prompts/sandbox/cheminformatics-agent.js +11 -15
  93. package/dist/prompts/sandbox/chromatin-agent.d.ts +1 -1
  94. package/dist/prompts/sandbox/chromatin-agent.js +3 -7
  95. package/dist/prompts/sandbox/data-profiler.d.ts +1 -1
  96. package/dist/prompts/sandbox/data-profiler.js +13 -8
  97. package/dist/prompts/sandbox/dna-methylation-agent.d.ts +1 -1
  98. package/dist/prompts/sandbox/dna-methylation-agent.js +2 -7
  99. package/dist/prompts/sandbox/drug-repurposing-agent.d.ts +1 -1
  100. package/dist/prompts/sandbox/drug-repurposing-agent.js +15 -16
  101. package/dist/prompts/sandbox/enrichment-agent.d.ts +1 -1
  102. package/dist/prompts/sandbox/enrichment-agent.js +2 -9
  103. package/dist/prompts/sandbox/genomic-variant-agent.d.ts +1 -1
  104. package/dist/prompts/sandbox/genomic-variant-agent.js +2 -6
  105. package/dist/prompts/sandbox/immune-profiling-agent.d.ts +1 -1
  106. package/dist/prompts/sandbox/immune-profiling-agent.js +3 -8
  107. package/dist/prompts/sandbox/metabolomics-agent.d.ts +1 -1
  108. package/dist/prompts/sandbox/metabolomics-agent.js +1 -7
  109. package/dist/prompts/sandbox/microbiome-agent.d.ts +1 -1
  110. package/dist/prompts/sandbox/microbiome-agent.js +2 -7
  111. package/dist/prompts/sandbox/multi-omics-integration-agent.d.ts +1 -1
  112. package/dist/prompts/sandbox/multi-omics-integration-agent.js +2 -8
  113. package/dist/prompts/sandbox/multimodal-sc-agent.d.ts +1 -1
  114. package/dist/prompts/sandbox/multimodal-sc-agent.js +2 -7
  115. package/dist/prompts/sandbox/network-agent.d.ts +1 -1
  116. package/dist/prompts/sandbox/network-agent.js +2 -7
  117. package/dist/prompts/sandbox/proteomics-agent.d.ts +1 -1
  118. package/dist/prompts/sandbox/proteomics-agent.js +2 -9
  119. package/dist/prompts/sandbox/single-cell-agent.d.ts +1 -1
  120. package/dist/prompts/sandbox/single-cell-agent.js +2 -9
  121. package/dist/prompts/sandbox/spatial-omics-agent.d.ts +1 -1
  122. package/dist/prompts/sandbox/spatial-omics-agent.js +3 -6
  123. package/dist/prompts/sandbox/statistical-modeling-agent.d.ts +1 -1
  124. package/dist/prompts/sandbox/statistical-modeling-agent.js +2 -7
  125. package/dist/prompts/sandbox/translational-safety-agent.d.ts +1 -1
  126. package/dist/prompts/sandbox/translational-safety-agent.js +13 -14
  127. package/dist/prompts/sandbox-standards.d.ts +22 -14
  128. package/dist/prompts/sandbox-standards.js +91 -71
  129. package/dist/prompts/synthesis-agent.d.ts +1 -1
  130. package/dist/prompts/synthesis-agent.js +33 -131
  131. package/dist/provenance/collector.js +1 -1
  132. package/dist/providers/ai-sdk.js +31 -4
  133. package/dist/providers/prompt-cache.d.ts +74 -0
  134. package/dist/providers/prompt-cache.js +77 -0
  135. package/dist/providers/types.d.ts +33 -0
  136. package/dist/reference-data/catalog.d.ts +131 -0
  137. package/dist/reference-data/catalog.js +348 -0
  138. package/dist/reference-data/receipt.d.ts +41 -0
  139. package/dist/reference-data/receipt.js +40 -0
  140. package/dist/runtime/boot.d.ts +2 -2
  141. package/dist/runtime/boot.js +1 -1
  142. package/dist/runtime/connection-budget.d.ts +2 -2
  143. package/dist/runtime/connection-budget.js +5 -3
  144. package/dist/runtime/dbos.d.ts +7 -7
  145. package/dist/runtime/dbos.js +14 -11
  146. package/dist/runtime/shutdown.d.ts +2 -2
  147. package/dist/runtime/shutdown.js +4 -3
  148. package/dist/sandbox/create-sandbox.d.ts +2 -2
  149. package/dist/sandbox/docker-client.d.ts +2 -2
  150. package/dist/sandbox/docker-client.js +4 -1
  151. package/dist/sandbox/k8s-client.d.ts +3 -0
  152. package/dist/sandbox/k8s-client.js +5 -4
  153. package/dist/sandbox/notification-sweep.d.ts +3 -3
  154. package/dist/sandbox/notification-sweep.js +4 -2
  155. package/dist/sandbox/reaper.d.ts +3 -3
  156. package/dist/sandbox/reaper.js +7 -4
  157. package/dist/sandbox/watchdog.d.ts +3 -3
  158. package/dist/sandbox/watchdog.js +8 -2
  159. package/dist/schemas/data-profile-schemas.js +5 -3
  160. package/dist/schemas/plan-schemas.d.ts +21 -21
  161. package/dist/schemas/plan-schemas.js +9 -3
  162. package/dist/schemas/run-synthesis.d.ts +8 -4
  163. package/dist/schemas/run-synthesis.js +92 -56
  164. package/dist/schemas/validate-plan.js +1 -1
  165. package/dist/schemas/workflow-state.d.ts +7 -1
  166. package/dist/schemas/workflow-state.js +33 -19
  167. package/dist/state/artifacts.d.ts +13 -0
  168. package/dist/state/artifacts.js +27 -0
  169. package/dist/state/data-profile.d.ts +64 -5
  170. package/dist/state/index.d.ts +3 -3
  171. package/dist/state/index.js +1 -1
  172. package/dist/state/init.d.ts +2 -1
  173. package/dist/state/init.js +5 -6
  174. package/dist/tasks/data-profile.d.ts +25 -2
  175. package/dist/tasks/data-profile.js +95 -39
  176. package/dist/tools/bio/chembl.d.ts +38 -0
  177. package/dist/tools/bio/chembl.js +138 -0
  178. package/dist/tools/bio/comptox.d.ts +50 -0
  179. package/dist/tools/bio/comptox.js +149 -0
  180. package/dist/tools/bio/get-impc-ko-profile.js +9 -6
  181. package/dist/tools/bio/index.d.ts +5 -17
  182. package/dist/tools/bio/index.js +5 -17
  183. package/dist/tools/bio/keys.d.ts +14 -9
  184. package/dist/tools/bio/keys.js +16 -16
  185. package/dist/tools/bio/opentargets.d.ts +38 -0
  186. package/dist/tools/bio/opentargets.js +89 -0
  187. package/dist/tools/bio/pubchem.d.ts +29 -0
  188. package/dist/tools/bio/pubchem.js +92 -0
  189. package/dist/tools/bio/pubmed.d.ts +51 -0
  190. package/dist/tools/bio/pubmed.js +100 -0
  191. package/dist/tools/bio/search-bgee-expression.js +12 -6
  192. package/dist/tools/bio/search-clinical-trials.js +16 -5
  193. package/dist/tools/bio/search-dgidb.d.ts +11 -1
  194. package/dist/tools/bio/search-dgidb.js +92 -78
  195. package/dist/tools/bio/search-faers.js +8 -4
  196. package/dist/tools/bio/search-geo-datasets.js +20 -6
  197. package/dist/tools/bio/search-gwas-catalog.d.ts +1 -1
  198. package/dist/tools/bio/search-pharmgkb.js +10 -4
  199. package/dist/tools/display/index.d.ts +1 -0
  200. package/dist/tools/display/index.js +1 -0
  201. package/dist/tools/display/normalize-echart-spec.d.ts +61 -0
  202. package/dist/tools/display/normalize-echart-spec.js +211 -0
  203. package/dist/tools/display/show-user.d.ts +6 -1
  204. package/dist/tools/display/show-user.js +25 -12
  205. package/dist/tools/execute-plan.js +5 -2
  206. package/dist/tools/iterate-report.d.ts +171 -118
  207. package/dist/tools/iterate-report.js +336 -96
  208. package/dist/tools/lib/chembl-client.d.ts +12 -0
  209. package/dist/tools/lib/chembl-client.js +127 -0
  210. package/dist/tools/lib/ctx-ops.d.ts +224 -0
  211. package/dist/tools/lib/ctx-ops.js +450 -0
  212. package/dist/tools/lib/iuphar-client.js +10 -1
  213. package/dist/tools/lib/openfda-client.js +11 -3
  214. package/dist/tools/lib/pathway-client.js +10 -4
  215. package/dist/tools/lib/pubchem-ops.d.ts +74 -0
  216. package/dist/tools/lib/pubchem-ops.js +224 -0
  217. package/dist/tools/memory/update-working-memory.d.ts +45 -7
  218. package/dist/tools/memory/update-working-memory.js +111 -41
  219. package/dist/tools/report/index.d.ts +3 -2
  220. package/dist/tools/report/index.js +3 -2
  221. package/dist/tools/research/generate-analogy-report.js +25 -12
  222. package/dist/tools/research/generate-plan.d.ts +12 -5
  223. package/dist/tools/research/generate-plan.js +168 -37
  224. package/dist/tools/research/index.d.ts +1 -0
  225. package/dist/tools/research/index.js +1 -0
  226. package/dist/tools/research/inspect-data-profile.d.ts +85 -0
  227. package/dist/tools/research/inspect-data-profile.js +161 -0
  228. package/dist/tools/research/literature-reviewer.d.ts +3 -0
  229. package/dist/tools/research/literature-reviewer.js +5 -6
  230. package/dist/tools/sandbox/list-available-packages.d.ts +79 -5
  231. package/dist/tools/sandbox/list-available-packages.js +152 -10
  232. package/dist/tools/sandbox/list-available-refs.d.ts +141 -5
  233. package/dist/tools/sandbox/list-available-refs.js +647 -134
  234. package/dist/tools/sandbox/report-blocker.d.ts +39 -9
  235. package/dist/tools/sandbox/report-blocker.js +43 -20
  236. package/dist/tools/workspace/execute-command.d.ts +3 -0
  237. package/dist/tools/workspace/execute-command.js +3 -1
  238. package/dist/tools/workspace/read-file.js +4 -5
  239. package/dist/tools/workspace/run-exec.d.ts +4 -4
  240. package/dist/tools/workspace/run-exec.js +4 -4
  241. package/dist/tools/workspace/show-file.js +10 -6
  242. package/dist/tools/workspace/show-plan.js +12 -6
  243. package/dist/tools/workspace/workspace-search.d.ts +1 -1
  244. package/dist/tools/workspace/workspace-search.js +11 -5
  245. package/dist/workflows/execute-analysis.d.ts +46 -4
  246. package/dist/workflows/execute-analysis.js +118 -13
  247. package/dist/workflows/execute-target-assessment.d.ts +3 -0
  248. package/dist/workflows/execute-target-assessment.js +19 -15
  249. package/dist/workflows/sandbox-step.d.ts +9 -0
  250. package/dist/workflows/sandbox-step.js +39 -17
  251. package/dist/workflows/target-assessment/lib/citation-validator.js +2 -2
  252. package/dist/workflows/target-assessment/lib/clinical-consequence-annotator.js +2 -1
  253. package/dist/workflows/target-assessment/lib/llm-step.d.ts +3 -0
  254. package/dist/workflows/target-assessment/lib/llm-step.js +3 -1
  255. package/dist/workflows/target-assessment/lib/structured-llm.d.ts +3 -0
  256. package/dist/workflows/target-assessment/lib/structured-llm.js +1 -0
  257. package/dist/workflows/target-assessment/phase5-persist.d.ts +3 -0
  258. package/dist/workflows/target-assessment/phase5-persist.js +8 -3
  259. package/dist/workflows/target-assessment/progress.d.ts +2 -1
  260. package/dist/workflows/target-assessment/progress.js +4 -3
  261. package/dist/workspace/filesystem.js +163 -74
  262. package/dist/workspace/paths.d.ts +2 -2
  263. package/dist/workspace/paths.js +2 -2
  264. package/package.json +95 -96
  265. package/dist/execution/filesystem-artifact-registry.d.ts +0 -57
  266. package/dist/execution/filesystem-artifact-registry.js +0 -92
  267. package/dist/lib/identifiers.d.ts +0 -8
  268. package/dist/lib/identifiers.js +0 -10
  269. package/dist/lib/mime-detect.d.ts +0 -17
  270. package/dist/lib/mime-detect.js +0 -29
  271. package/dist/lib/streamed-download.d.ts +0 -28
  272. package/dist/lib/streamed-download.js +0 -111
  273. package/dist/lib/trace-context.d.ts +0 -11
  274. package/dist/lib/trace-context.js +0 -49
  275. package/dist/providers/llm-capabilities.d.ts +0 -33
  276. package/dist/providers/llm-capabilities.js +0 -58
  277. package/dist/schemas/render-step-prompt.d.ts +0 -32
  278. package/dist/schemas/render-step-prompt.js +0 -76
  279. package/dist/tools/bio/find-approval-precedent.d.ts +0 -32
  280. package/dist/tools/bio/find-approval-precedent.js +0 -72
  281. package/dist/tools/bio/get-article-details.d.ts +0 -11
  282. package/dist/tools/bio/get-article-details.js +0 -20
  283. package/dist/tools/bio/get-article-full-text.d.ts +0 -19
  284. package/dist/tools/bio/get-article-full-text.js +0 -32
  285. package/dist/tools/bio/get-bioactivity.d.ts +0 -11
  286. package/dist/tools/bio/get-bioactivity.js +0 -23
  287. package/dist/tools/bio/get-drug-info.d.ts +0 -9
  288. package/dist/tools/bio/get-drug-info.js +0 -22
  289. package/dist/tools/bio/get-mechanism.d.ts +0 -8
  290. package/dist/tools/bio/get-mechanism.js +0 -18
  291. package/dist/tools/bio/get-pubchem-assays.d.ts +0 -22
  292. package/dist/tools/bio/get-pubchem-assays.js +0 -107
  293. package/dist/tools/bio/get-pubchem-cross-refs.d.ts +0 -15
  294. package/dist/tools/bio/get-pubchem-cross-refs.js +0 -68
  295. package/dist/tools/bio/get-target-safety.d.ts +0 -18
  296. package/dist/tools/bio/get-target-safety.js +0 -26
  297. package/dist/tools/bio/search-compounds.d.ts +0 -22
  298. package/dist/tools/bio/search-compounds.js +0 -160
  299. package/dist/tools/bio/search-ctx-chemical.d.ts +0 -50
  300. package/dist/tools/bio/search-ctx-chemical.js +0 -130
  301. package/dist/tools/bio/search-ctx-exposure.d.ts +0 -55
  302. package/dist/tools/bio/search-ctx-exposure.js +0 -176
  303. package/dist/tools/bio/search-ctx-hazard.d.ts +0 -111
  304. package/dist/tools/bio/search-ctx-hazard.js +0 -178
  305. package/dist/tools/bio/search-opentargets.d.ts +0 -11
  306. package/dist/tools/bio/search-opentargets.js +0 -25
  307. package/dist/tools/bio/search-pubchem-compound.d.ts +0 -26
  308. package/dist/tools/bio/search-pubchem-compound.js +0 -110
  309. package/dist/tools/bio/search-pubmed.d.ts +0 -17
  310. package/dist/tools/bio/search-pubmed.js +0 -31
  311. package/dist/tools/bio/search-targets.d.ts +0 -9
  312. package/dist/tools/bio/search-targets.js +0 -20
  313. package/dist/tools/bio/search-toxcast.d.ts +0 -36
  314. package/dist/tools/bio/search-toxcast.js +0 -141
  315. package/dist/workflows/register-workflows.d.ts +0 -51
  316. package/dist/workflows/register-workflows.js +0 -48
  317. package/dist/workflows/resume-execute-analysis.d.ts +0 -70
  318. package/dist/workflows/resume-execute-analysis.js +0 -80
  319. package/dist/workflows/target-assessment/correction-loop.d.ts +0 -45
  320. package/dist/workflows/target-assessment/correction-loop.js +0 -62
  321. package/dist/workflows/target-assessment/steps/phase0-resolve.d.ts +0 -71
  322. package/dist/workflows/target-assessment/steps/phase0-resolve.js +0 -21
  323. package/dist/workflows/target-assessment/steps/phase1-aggregate.d.ts +0 -1018
  324. package/dist/workflows/target-assessment/steps/phase1-aggregate.js +0 -25
  325. package/dist/workspace/mount-strategy.d.ts +0 -50
  326. package/dist/workspace/mount-strategy.js +0 -67
  327. package/dist/workspace/provenance-collector.d.ts +0 -34
  328. package/dist/workspace/provenance-collector.js +0 -16
  329. package/dist/workspace/workspace-paths.d.ts +0 -26
  330. package/dist/workspace/workspace-paths.js +0 -31
@@ -0,0 +1,224 @@
1
+ /**
2
+ * PubChem PUG-REST operations — the three lookups the PubChem tools expose.
3
+ *
4
+ * One home for the wire schemas, the URL construction, and the "PubChem 404
5
+ * means no data" contract, so a caller cannot drift from it: every function
6
+ * returns an empty collection for a 404 and throws on any other API failure.
7
+ */
8
+ import { z } from "zod";
9
+ import { apiFetchValidated, describeApiError } from "./api-utils.js";
10
+ import { PUBCHEM_HEADERS as HEADERS, PUBCHEM_BASE } from "./pubchem-config.js";
11
+ const PROPERTY_LIST = [
12
+ "MolecularFormula",
13
+ "MolecularWeight",
14
+ "CanonicalSMILES",
15
+ "InChI",
16
+ "InChIKey",
17
+ "IUPACName",
18
+ "XLogP",
19
+ "TPSA",
20
+ "HBondDonorCount",
21
+ "HBondAcceptorCount",
22
+ "RotatableBondCount",
23
+ "Complexity",
24
+ ].join(",");
25
+ // A single schema that both validates and normalizes one PUG-REST property row:
26
+ // the `.object(...)` half is the wire shape (every field optional — PubChem
27
+ // omits absent properties), the `.transform(...)` half maps it to the camelCase
28
+ // `PubChemCompound` we return. Parsing IS the validation, and because the
29
+ // transform rides on the schema, `z.infer` below is the OUTPUT type — no
30
+ // separate raw interface or mapper.
31
+ const PugPropertyRowSchema = z
32
+ .object({
33
+ CID: z.number().optional(),
34
+ CanonicalSMILES: z.string().optional(),
35
+ MolecularFormula: z.string().optional(),
36
+ // PUG-REST serializes MolecularWeight as a string (e.g. "180.16") to
37
+ // preserve significant figures; accept both and let the transform's
38
+ // `Number(...)` normalize it.
39
+ MolecularWeight: z.union([z.number(), z.string()]).optional(),
40
+ IUPACName: z.string().optional(),
41
+ InChI: z.string().optional(),
42
+ InChIKey: z.string().optional(),
43
+ XLogP: z.number().optional(),
44
+ TPSA: z.number().optional(),
45
+ HBondDonorCount: z.number().optional(),
46
+ HBondAcceptorCount: z.number().optional(),
47
+ RotatableBondCount: z.number().optional(),
48
+ Complexity: z.number().optional(),
49
+ })
50
+ .transform((raw) => ({
51
+ cid: raw.CID ?? 0,
52
+ canonicalSmiles: raw.CanonicalSMILES ?? null,
53
+ molecularFormula: raw.MolecularFormula ?? null,
54
+ molecularWeight: raw.MolecularWeight != null ? Number(raw.MolecularWeight) : null,
55
+ iupacName: raw.IUPACName ?? null,
56
+ inchi: raw.InChI ?? null,
57
+ inchiKey: raw.InChIKey ?? null,
58
+ xlogp: raw.XLogP ?? null,
59
+ tpsa: raw.TPSA ?? null,
60
+ hbondDonorCount: raw.HBondDonorCount ?? null,
61
+ hbondAcceptorCount: raw.HBondAcceptorCount ?? null,
62
+ rotatableBondCount: raw.RotatableBondCount ?? null,
63
+ complexity: raw.Complexity ?? null,
64
+ }));
65
+ const PubChemPropertyResponseSchema = z.object({
66
+ PropertyTable: z.object({ Properties: z.array(PugPropertyRowSchema).optional() }).optional(),
67
+ });
68
+ // The PUG-REST xrefs wire shape, validated at the fetch boundary: one
69
+ // `Information` entry per CID with parallel `RegistryID[]` / `SourceName[]`
70
+ // arrays. Every field is optional — PubChem omits absent values.
71
+ const PubChemXrefResponseSchema = z.object({
72
+ InformationList: z
73
+ .object({
74
+ Information: z
75
+ .array(z.object({
76
+ CID: z.number().optional(),
77
+ RegistryID: z.array(z.string()).optional(),
78
+ SourceName: z.array(z.string()).optional(),
79
+ }))
80
+ .optional(),
81
+ })
82
+ .optional(),
83
+ });
84
+ // The PUG-REST assay-summary wire shape (a table of columns + rows), validated
85
+ // at the fetch boundary; `parseAssaySummary` maps the heading-indexed cells into
86
+ // `PubChemAssay` records. Every field is optional — PubChem omits absent values.
87
+ const PugAssaySummarySchema = z.object({
88
+ Table: z
89
+ .object({
90
+ Columns: z
91
+ .object({
92
+ Column: z.array(z.object({ Heading: z.string().optional() })).optional(),
93
+ })
94
+ .optional(),
95
+ Row: z
96
+ .array(z.object({
97
+ Cell: z
98
+ .array(z.object({
99
+ intval: z.number().optional(),
100
+ fval: z.number().optional(),
101
+ sval: z.string().optional(),
102
+ }))
103
+ .optional(),
104
+ }))
105
+ .optional(),
106
+ })
107
+ .optional(),
108
+ });
109
+ /** Build the PUG-REST URL path segment for the given search type. */
110
+ function buildNamespace(searchBy, query) {
111
+ switch (searchBy) {
112
+ case "name":
113
+ return `compound/name/${encodeURIComponent(query)}`;
114
+ case "smiles":
115
+ return `compound/smiles/${encodeURIComponent(query)}`;
116
+ case "inchi":
117
+ return `compound/inchi/${encodeURIComponent(query)}`;
118
+ case "inchikey":
119
+ return `compound/inchikey/${encodeURIComponent(query)}`;
120
+ case "cid":
121
+ return `compound/cid/${encodeURIComponent(query)}`;
122
+ default:
123
+ return `compound/name/${encodeURIComponent(query)}`;
124
+ }
125
+ }
126
+ /** Resolve a compound to its identity + computed properties. Empty array = no match. */
127
+ export async function fetchPubchemCompounds(query, searchBy) {
128
+ const namespace = buildNamespace(searchBy, query);
129
+ const url = `${PUBCHEM_BASE}/${namespace}/property/${PROPERTY_LIST}/JSON`;
130
+ const res = await apiFetchValidated(url, PubChemPropertyResponseSchema, { headers: HEADERS });
131
+ if (res.isErr()) {
132
+ // PubChem returns 404 when the query matches no compound — expected.
133
+ if (res.error.type === "http_status" && res.error.status === 404)
134
+ return [];
135
+ throw new Error(describeApiError(res.error));
136
+ }
137
+ // Already validated + normalized by PugPropertyRowSchema's transform.
138
+ return res.value.PropertyTable?.Properties ?? [];
139
+ }
140
+ /** External registry ids for a CID. Empty array = the CID is in no external registry. */
141
+ export async function fetchPubchemCrossRefs(cid) {
142
+ const url = `${PUBCHEM_BASE}/compound/cid/${cid}/xrefs/RegistryID,SourceName/JSON`;
143
+ const res = await apiFetchValidated(url, PubChemXrefResponseSchema, { headers: HEADERS });
144
+ if (res.isErr()) {
145
+ // PubChem returns 404 when the CID has no cross-references — expected.
146
+ if (res.error.type === "http_status" && res.error.status === 404)
147
+ return [];
148
+ throw new Error(describeApiError(res.error));
149
+ }
150
+ // PUG-REST xrefs returns an InformationList with one entry per CID.
151
+ // Each entry has parallel RegistryID[] and SourceName[] arrays.
152
+ const info = res.value.InformationList?.Information?.[0];
153
+ if (!info?.RegistryID?.length)
154
+ return [];
155
+ const crossRefs = [];
156
+ const registryIds = info.RegistryID;
157
+ const sourceNames = info.SourceName ?? [];
158
+ for (let i = 0; i < registryIds.length; i++) {
159
+ crossRefs.push({
160
+ source: sourceNames[i] ?? "Unknown",
161
+ id: registryIds[i] ?? "",
162
+ });
163
+ }
164
+ return crossRefs;
165
+ }
166
+ /** Bioassay screening summaries for a CID. Empty array = never screened. */
167
+ export async function fetchPubchemAssays(cid, opts) {
168
+ const url = `${PUBCHEM_BASE}/compound/cid/${cid}/assaysummary/JSON`;
169
+ const res = await apiFetchValidated(url, PugAssaySummarySchema, { headers: HEADERS });
170
+ if (res.isErr()) {
171
+ // PubChem returns 404 when the CID has no assay data — expected.
172
+ if (res.error.type === "http_status" && res.error.status === 404)
173
+ return [];
174
+ throw new Error(describeApiError(res.error));
175
+ }
176
+ let assays = parseAssaySummary(res.value);
177
+ if (opts.activeOnly) {
178
+ assays = assays.filter((a) => a.activityOutcome?.toLowerCase() === "active");
179
+ }
180
+ return assays.slice(0, opts.limit);
181
+ }
182
+ function parseAssaySummary(data) {
183
+ const columns = data.Table?.Columns?.Column ?? [];
184
+ const rows = data.Table?.Row ?? [];
185
+ // Find column indices by heading
186
+ const headingIndex = new Map();
187
+ for (let i = 0; i < columns.length; i++) {
188
+ if (columns[i].Heading) {
189
+ headingIndex.set(columns[i].Heading, i);
190
+ }
191
+ }
192
+ const aidIdx = headingIndex.get("AID");
193
+ const nameIdx = headingIndex.get("AssayName");
194
+ const targetIdx = headingIndex.get("TargetName");
195
+ const outcomeIdx = headingIndex.get("ActivityOutcome");
196
+ const valueIdx = headingIndex.get("ActivityValue");
197
+ const results = [];
198
+ for (const row of rows) {
199
+ const cells = row.Cell ?? [];
200
+ const getCellStr = (idx) => {
201
+ if (idx === undefined || idx >= cells.length)
202
+ return null;
203
+ return cells[idx]?.sval ?? null;
204
+ };
205
+ const getCellNum = (idx) => {
206
+ if (idx === undefined || idx >= cells.length)
207
+ return null;
208
+ const cell = cells[idx];
209
+ if (cell?.fval !== undefined)
210
+ return cell.fval;
211
+ if (cell?.intval !== undefined)
212
+ return cell.intval;
213
+ return null;
214
+ };
215
+ results.push({
216
+ aid: getCellNum(aidIdx) ?? 0,
217
+ assayName: getCellStr(nameIdx),
218
+ targetName: getCellStr(targetIdx),
219
+ activityOutcome: getCellStr(outcomeIdx),
220
+ activityValue: getCellNum(valueIdx),
221
+ });
222
+ }
223
+ return results;
224
+ }
@@ -2,24 +2,62 @@
2
2
  * update-working-memory — the agent's single interface for maintaining
3
3
  * working memory.
4
4
  *
5
- * Dependency-bearing: the `WorkingMemoryStore` is captured by the factory
6
- * (see the harness-durable-runtime spec). The analysis id is read from the request-scoped `Session`.
5
+ * Dependency-bearing: the `WorkingMemoryStore` and the database `Pool` are
6
+ * captured by the factory (see the harness-durable-runtime spec). The analysis
7
+ * id is read from the request-scoped `Session`.
7
8
  *
8
9
  * The input is a flat object with a `section` discriminator — not a
9
10
  * `z.discriminatedUnion`, which `defineTool` rejects (Anthropic needs a
10
11
  * top-level `"type":"object"`). The variant fields are validated by
11
12
  * `.refine` so a malformed call (e.g. a `finding` with no `runId`) fails
12
13
  * at the loop boundary and surfaces as an `is_error` tool result.
14
+ *
15
+ * Working memory is re-injected in full on every turn, so it is bounded at the
16
+ * write. The text caps are declared in the input schema — a violation is a
17
+ * validation error the model sees before `execute` runs — and the entry-count
18
+ * caps are enforced by the store under its row lock. Neither ever truncates: an
19
+ * over-cap write is refused with a message telling the model to shorten the
20
+ * text or retire a stale entry first.
21
+ *
22
+ * A finding cites the run that produced it, so adding one verifies the run
23
+ * exists AND belongs to this analysis (`cortex_runs.analysis_id`) before it is
24
+ * stored: an unknown runId is an `err(ToolError)` the model can correct from,
25
+ * not a silent reference to a run that never existed.
13
26
  */
27
+ import type { Pool } from "pg";
28
+ import { z } from "zod";
14
29
  import type { WorkingMemoryStore } from "../../memory/working-memory.js";
15
- export declare function createUpdateWorkingMemoryTool(workingMemory: WorkingMemoryStore): import("../define-tool.js").Tool<{
30
+ declare const inputSchema: z.ZodObject<{
31
+ section: z.ZodEnum<{
32
+ constraint: "constraint";
33
+ goal: "goal";
34
+ hypothesis: "hypothesis";
35
+ finding: "finding";
36
+ }>;
37
+ operation: z.ZodOptional<z.ZodEnum<{
38
+ add: "add";
39
+ revise: "revise";
40
+ retire: "retire";
41
+ }>>;
42
+ text: z.ZodOptional<z.ZodString>;
43
+ id: z.ZodOptional<z.ZodString>;
44
+ origin: z.ZodOptional<z.ZodEnum<{
45
+ user: "user";
46
+ agent: "agent";
47
+ }>>;
48
+ runId: z.ZodOptional<z.ZodString>;
49
+ }, z.core.$strip>;
50
+ type Input = z.infer<typeof inputSchema>;
51
+ interface UpdateOutput {
52
+ readonly ok: true;
53
+ readonly section: Input["section"];
54
+ }
55
+ export declare function createUpdateWorkingMemoryTool(workingMemory: WorkingMemoryStore, pool: Pool): import("../define-tool.js").Tool<{
16
56
  section: "constraint" | "goal" | "hypothesis" | "finding";
17
57
  operation?: "add" | "revise" | "retire" | undefined;
18
58
  text?: string | undefined;
19
59
  id?: string | undefined;
20
60
  origin?: "user" | "agent" | undefined;
21
61
  runId?: string | undefined;
22
- }, {
23
- ok: boolean;
24
- section: "constraint" | "goal" | "hypothesis" | "finding";
25
- }>;
62
+ }, UpdateOutput>;
63
+ export {};
@@ -2,31 +2,54 @@
2
2
  * update-working-memory — the agent's single interface for maintaining
3
3
  * working memory.
4
4
  *
5
- * Dependency-bearing: the `WorkingMemoryStore` is captured by the factory
6
- * (see the harness-durable-runtime spec). The analysis id is read from the request-scoped `Session`.
5
+ * Dependency-bearing: the `WorkingMemoryStore` and the database `Pool` are
6
+ * captured by the factory (see the harness-durable-runtime spec). The analysis
7
+ * id is read from the request-scoped `Session`.
7
8
  *
8
9
  * The input is a flat object with a `section` discriminator — not a
9
10
  * `z.discriminatedUnion`, which `defineTool` rejects (Anthropic needs a
10
11
  * top-level `"type":"object"`). The variant fields are validated by
11
12
  * `.refine` so a malformed call (e.g. a `finding` with no `runId`) fails
12
13
  * at the loop boundary and surfaces as an `is_error` tool result.
14
+ *
15
+ * Working memory is re-injected in full on every turn, so it is bounded at the
16
+ * write. The text caps are declared in the input schema — a violation is a
17
+ * validation error the model sees before `execute` runs — and the entry-count
18
+ * caps are enforced by the store under its row lock. Neither ever truncates: an
19
+ * over-cap write is refused with a message telling the model to shorten the
20
+ * text or retire a stale entry first.
21
+ *
22
+ * A finding cites the run that produced it, so adding one verifies the run
23
+ * exists AND belongs to this analysis (`cortex_runs.analysis_id`) before it is
24
+ * stored: an unknown runId is an `err(ToolError)` the model can correct from,
25
+ * not a silent reference to a run that never existed.
13
26
  */
14
- import { ok } from "neverthrow";
27
+ import { err, ok } from "neverthrow";
15
28
  import { z } from "zod";
16
29
  import { scopeResource } from "../../auth/types.js";
17
- import { unwrapOrThrow } from "../../lib/result.js";
30
+ import { toThrowable, unwrapOrThrow } from "../../lib/result.js";
31
+ import { isWorkingMemoryRejection, WORKING_MEMORY_LIMITS } from "../../memory/working-memory.js";
32
+ import { queryRun } from "../../state/index.js";
18
33
  import { defineTool } from "../define-tool.js";
34
+ const { goalChars, entryChars, constraints, hypotheses, findings } = WORKING_MEMORY_LIMITS;
19
35
  const inputSchema = z
20
36
  .object({
21
37
  section: z.enum(["goal", "constraint", "hypothesis", "finding"]).describe("Which working-memory section to update."),
22
38
  operation: z
23
39
  .enum(["add", "revise", "retire"])
24
40
  .optional()
25
- .describe("For constraint/hypothesis: add a new entry (default), revise an " + "existing entry's text, or retire a stale one. Ignored for goal/finding."),
41
+ .describe("For constraint/hypothesis/finding: add a new entry (default), revise an " +
42
+ "existing entry's text, or retire one that no longer earns its place. " +
43
+ "Ignored for goal — a goal write always replaces the previous goal."),
26
44
  text: z
27
45
  .string()
46
+ .min(1)
47
+ .max(goalChars, `text is over the ${goalChars}-character cap — nothing was recorded; shorten it and retry.`)
28
48
  .optional()
29
- .describe("Entry text. Required for goal, finding, and constraint/hypothesis " + "add/revise. Omit when retiring."),
49
+ .describe(`Entry text. Required for goal, and for add/revise on any section; omit when retiring. ` +
50
+ `Max ${entryChars} characters for a constraint/hypothesis/finding, ${goalChars} for the goal. ` +
51
+ `Over the cap is REJECTED, never truncated — write one concise line and leave the detail ` +
52
+ `in the run, which inspect_run still serves.`),
30
53
  id: z
31
54
  .string()
32
55
  .optional()
@@ -35,30 +58,33 @@ const inputSchema = z
35
58
  .enum(["user", "agent"])
36
59
  .optional()
37
60
  .describe("For a new constraint: whether the user stated the rule or you " + "derived it. Defaults to 'agent'."),
38
- runId: z.string().optional().describe("The run that produced the finding. Required when section is 'finding'."),
61
+ runId: z
62
+ .string()
63
+ .optional()
64
+ .describe("The run that produced the finding. Required when ADDING a finding (revise " +
65
+ "and retire address the finding by its own id instead). Must be an existing " +
66
+ "run of this analysis — copy it verbatim from inspect_run. A runId that does " +
67
+ "not exist is rejected; never invent one."),
39
68
  })
40
- .refine((d) => d.section !== "goal" || d.text !== undefined, {
69
+ .refine((d) => d.section !== "goal" || (d.text !== undefined && d.text.trim().length > 0), {
41
70
  message: "text is required when section is 'goal'",
42
71
  path: ["text"],
43
72
  })
44
- .refine((d) => d.section !== "finding" || (d.runId !== undefined && d.runId.length > 0), {
45
- message: "runId is required when section is 'finding'",
73
+ .refine((d) => d.section !== "finding" || (d.operation ?? "add") !== "add" || (d.runId !== undefined && d.runId.length > 0), {
74
+ message: "runId is required when adding a finding — copy an existing runId from inspect_run",
46
75
  path: ["runId"],
47
- })
48
- .refine((d) => d.section !== "finding" || (d.text !== undefined && d.text.length > 0), {
49
- message: "text is required when section is 'finding'",
50
- path: ["text"],
51
76
  })
52
77
  .refine((d) => !(d.operation === "revise" || d.operation === "retire") || (d.id !== undefined && d.id.length > 0), {
53
78
  message: "id is required when operation is 'revise' or 'retire'",
54
79
  path: ["id"],
55
80
  })
56
- .refine((d) => {
57
- const isList = d.section === "constraint" || d.section === "hypothesis";
58
- const op = d.operation ?? "add";
59
- return !(isList && op !== "retire") || (d.text !== undefined && d.text.length > 0);
60
- }, {
61
- message: "text is required to add or revise a constraint/hypothesis",
81
+ .refine((d) => d.section === "goal" || (d.operation ?? "add") === "retire" || (d.text !== undefined && d.text.trim().length > 0), {
82
+ message: "text is required to add or revise a constraint/hypothesis/finding",
83
+ path: ["text"],
84
+ })
85
+ .refine((d) => d.section === "goal" || d.text === undefined || d.text.trim().length <= entryChars, {
86
+ message: `a constraint/hypothesis/finding is capped at ${entryChars} characters (only the goal may reach ` +
87
+ `${goalChars}). Nothing was recorded — rewrite it as one concise line and retry.`,
62
88
  path: ["text"],
63
89
  });
64
90
  /** Map a validated flat input to a constraint/hypothesis list amendment. */
@@ -76,37 +102,81 @@ function toConstraintOp(input) {
76
102
  const base = toListOp(input);
77
103
  return base.op === "add" ? { ...base, origin: input.origin ?? "agent" } : base;
78
104
  }
79
- export function createUpdateWorkingMemoryTool(workingMemory) {
105
+ /** As `toListOp`, but an added finding also carries the run it is attributed to. */
106
+ function toFindingOp(input) {
107
+ const base = toListOp(input);
108
+ return base.op === "add" ? { ...base, runId: input.runId } : base;
109
+ }
110
+ export function createUpdateWorkingMemoryTool(workingMemory, pool) {
111
+ /** Write one amendment, mapping a refused write to a model-visible error. */
112
+ async function apply(analysisId, section, value) {
113
+ const written = await workingMemory.updateSection(analysisId, section, value);
114
+ if (written.isErr()) {
115
+ // A refused write (cap hit, unknown id) is an expected outcome the model
116
+ // can act on — its message carries the remedy. A DbError is not: it throws,
117
+ // and the loop's dispatch catch maps it to a generic error tool result.
118
+ if (isWorkingMemoryRejection(written.error)) {
119
+ return err({ error: written.error.message, retryable: true });
120
+ }
121
+ throw toThrowable(written.error);
122
+ }
123
+ return ok({ ok: true, section });
124
+ }
80
125
  return defineTool({
81
126
  id: "update_working_memory",
82
- description: "Maintain the analysis's working memory — its durable interpretive " +
83
- "layer that survives token-window eviction. Section-addressable: set " +
84
- "the 'goal', add/revise/retire a 'constraint' (a binding rule) or a " +
85
- "'hypothesis', or record a 'finding' under a runId. Updates one " +
86
- "section at a time; the others are untouched.",
127
+ description: "Maintain the analysis's working memory — the durable interpretive layer that survives " +
128
+ "context-window eviction. It is re-injected IN FULL on every turn, so every entry is re-read " +
129
+ "for the life of the analysis: promote only what must outlive the window, and retire what no " +
130
+ "longer earns its place.\n\n" +
131
+ "Sections (one per call; the other three are left untouched):\n" +
132
+ `- goal — the analysis's current objective as you understand it. A write replaces it. Max ${goalChars} chars.\n` +
133
+ `- constraint — a binding rule the rest of the analysis must obey. origin 'user' (the user ` +
134
+ `stated it: "use FDR 0.01") or 'agent' (you derived it: "the data is paired, so every test must ` +
135
+ `be paired"). Max ${constraints} entries.\n` +
136
+ `- hypothesis — a hypothesis under active exploration. Retire it once it is resolved or ` +
137
+ `abandoned. Max ${hypotheses} entries.\n` +
138
+ `- finding — a durable conclusion, cited to the run that produced it (runId, copied verbatim ` +
139
+ `from inspect_run). Record the conclusion, never the run's contents — the run itself stays ` +
140
+ `retrievable with inspect_run. Max ${findings} entries across all runs.\n\n` +
141
+ "Operations (constraint / hypothesis / finding): 'add' (default), 'revise' (id + new text), " +
142
+ "'retire' (id). Copy the id from the [id] shown against each entry in the rendered working memory.\n\n" +
143
+ `Caps are enforced, never truncated: text over the cap (${entryChars} chars; ${goalChars} for the goal), or an ` +
144
+ "add into a full section, is REJECTED and nothing is stored. Shorten the text, or retire a stale " +
145
+ "entry before adding the new one.\n\n" +
146
+ "PROMOTE: a binding rule the user states or you derive; a durable conclusion a run produced; a " +
147
+ "shift in the analysis's objective; a hypothesis you are actively testing.\n" +
148
+ 'DO NOT PROMOTE: transient conversational asks ("show me the top genes"), restatements of the ' +
149
+ "user's last message, run contents (tables, gene lists, file paths), or anything you can retrieve " +
150
+ "again with inspect_run, read_file, or search.",
87
151
  inputSchema,
88
152
  execute: async (input, ctx) => {
89
153
  const analysisId = scopeResource(ctx.session.scope).resourceId;
90
154
  switch (input.section) {
91
155
  case "goal":
92
- unwrapOrThrow(await workingMemory.updateSection(analysisId, "goal", {
93
- text: input.text,
94
- }));
95
- break;
156
+ return apply(analysisId, "goal", { text: input.text });
96
157
  case "constraint":
97
- unwrapOrThrow(await workingMemory.updateSection(analysisId, "constraint", toConstraintOp(input)));
98
- break;
158
+ return apply(analysisId, "constraint", toConstraintOp(input));
99
159
  case "hypothesis":
100
- unwrapOrThrow(await workingMemory.updateSection(analysisId, "hypothesis", toListOp(input)));
101
- break;
102
- case "finding":
103
- unwrapOrThrow(await workingMemory.updateSection(analysisId, "finding", {
104
- runId: input.runId,
105
- text: input.text,
106
- }));
107
- break;
160
+ return apply(analysisId, "hypothesis", toListOp(input));
161
+ case "finding": {
162
+ const op = toFindingOp(input);
163
+ if (op.op === "add") {
164
+ // A run of another analysis is as wrong as one that does not exist —
165
+ // both are "no such run here", so they share one message.
166
+ const run = unwrapOrThrow(await queryRun(pool, op.runId));
167
+ if (!run || run.analysisId !== analysisId) {
168
+ return err({
169
+ error: `unknown runId "${op.runId}" — this analysis has no such run, so the ` +
170
+ `finding was NOT recorded. Call inspect_run with no arguments to list ` +
171
+ `this analysis's runs, copy an exact runId from that list, and retry. ` +
172
+ `Do not guess a runId.`,
173
+ retryable: true,
174
+ });
175
+ }
176
+ }
177
+ return apply(analysisId, "finding", op);
178
+ }
108
179
  }
109
- return ok({ ok: true, section: input.section });
110
180
  },
111
181
  });
112
182
  }
@@ -3,8 +3,9 @@
3
3
  * agent — they are constructed inside `harness/execution/report-runner.ts`
4
4
  * so each tool factory shares the runner's closure-captured outcome state
5
5
  * and preview-dir paths. The conversation agent reaches the report-builder
6
- * through the `iterate_report` tool, which orchestrates the whole
7
- * iteration.
6
+ * through its `submit_report` tool, which orchestrates the whole iteration.
7
+ * (That conversation-agent tool shares the id `submit_report` with this
8
+ * barrel's builder-side terminal tool — different rosters, never mixed.)
8
9
  */
9
10
  export { createBuildReportTool, type BuildReportToolState } from "./build-report.js";
10
11
  export { createSubmitReportTool, type ReportOutcome, type SubmitReportToolState } from "./submit-report.js";
@@ -3,8 +3,9 @@
3
3
  * agent — they are constructed inside `harness/execution/report-runner.ts`
4
4
  * so each tool factory shares the runner's closure-captured outcome state
5
5
  * and preview-dir paths. The conversation agent reaches the report-builder
6
- * through the `iterate_report` tool, which orchestrates the whole
7
- * iteration.
6
+ * through its `submit_report` tool, which orchestrates the whole iteration.
7
+ * (That conversation-agent tool shares the id `submit_report` with this
8
+ * barrel's builder-side terminal tool — different rosters, never mixed.)
8
9
  */
9
10
  export { createBuildReportTool } from "./build-report.js";
10
11
  export { createSubmitReportTool } from "./submit-report.js";
@@ -16,6 +16,7 @@ import { ok } from "neverthrow";
16
16
  import { z } from "zod";
17
17
  import { AnalogicalReasonerOutputSchema } from "@inflexa-ai/harness/contracts/analogy-report.js";
18
18
  import { analogicalReasonerPrompt } from "../../prompts/analogical-reasoner.js";
19
+ import { composeSystemPrompt } from "../../agents/system-prompt.js";
19
20
  import { forSubAgent } from "../../auth/types.js";
20
21
  import { unwrapOrThrow } from "../../lib/result.js";
21
22
  import { finalText, runAgent } from "../../loop/run-agent.js";
@@ -158,27 +159,39 @@ export function createGenerateAnalogyReportTool(deps) {
158
159
  searchSemanticScholarTool,
159
160
  searchArxivTool,
160
161
  createSearchGithubReposTool({ githubToken: deps.bioKeys.github }),
161
- ncbi.searchPubMed,
162
- ncbi.getArticleDetails,
163
- ncbi.getArticleFullText,
162
+ ncbi.pubmed,
164
163
  ];
165
164
  const agent = {
166
165
  id: AGENT_ID,
167
- systemPrompt: analogicalReasonerPrompt,
166
+ systemPrompt: composeSystemPrompt(analogicalReasonerPrompt),
168
167
  model: deps.model,
169
168
  tools: reasonerTools,
170
169
  maxIterations: RESEARCH_MAX_ITERATIONS,
171
170
  };
172
171
  return defineTool({
173
172
  id: "generate_analogy_report",
174
- description: "Generate a cross-domain analogy report for an open-ended scientific " +
175
- "problem. Pass the user's problem as a single field plus optional " +
176
- "context (data profile excerpts, prior findings) and knobs (domain " +
177
- "counts, preferred/excluded domains). Returns a structured " +
178
- "AnalogyReport envelope — analogies with object mappings, shared " +
179
- "relations, and cited solutions from other domains. The UI renders " +
180
- "the envelope as an inline card; reach for this tool whenever the " +
181
- "user is in exploratory or hypothesis-generation mode.",
173
+ description: "Idea-generation engine. Extracts structural analogies for an open-ended " +
174
+ "scientific problem and returns real, cited solutions from other fields " +
175
+ "(control theory, ML, ecology, logistics, economics…).\n" +
176
+ "WHEN: any exploratory or hypothesis-generation turn — 'what could we do " +
177
+ "about X', 'what should we try', 'how could we approach this', 'we're " +
178
+ "stuck', 'brainstorm ideas', 'are there precedents outside biology'. The " +
179
+ "trigger is exploratory INTENT, not the words 'analogy' or 'cross-domain' " +
180
+ "— users rarely name it, so infer it and reach for the tool proactively.\n" +
181
+ "NOT for: in-domain literature review (use `literature_reviewer` or " +
182
+ "`pubmed`), single-gene factual lookup (use `search_gene`), or " +
183
+ "execution-mode turns where the user just wants the next step done. It " +
184
+ "drives a multi-step research sub-agent over live literature search, so " +
185
+ "it is slow — never spend it on a fact you could look up.\n" +
186
+ "RETURNS an AnalogyReport envelope the UI renders as an inline card. An " +
187
+ "analogy the search could not fill carries a `coverage` tag " +
188
+ "(`queried_no_data` | `search_failed` | `not_loaded`) with an empty " +
189
+ "`solutions` array — informational, NOT an error. A top-level `error` " +
190
+ "field means no report was produced: on `error.kind === " +
191
+ '"extraction-failed"` do NOT retry with the same or a similar problem ' +
192
+ "statement (the wrapper already retried internally — you would burn " +
193
+ "latency on the same failure). Surface the message and ask the user to " +
194
+ "narrow the problem.",
182
195
  inputSchema: generateAnalogyReportInputSchema,
183
196
  execute: async (input, ctx) => {
184
197
  const childSession = forSubAgent(ctx.session, AGENT_ID);
@@ -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