@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
@@ -1,14 +1,19 @@
1
1
  /**
2
- * `report_blocker` — a step agent's honest exit when it cannot fulfil its
3
- * step (see the harness-sandbox-agents spec). There is no `submit`/`done` tool: a step's deliverable is
4
- * its persisted files, so a clean end-of-turn after writing them is the
5
- * implicit success. Calling `report_blocker` is the alternative to
6
- * improvising an inline result the harness would launder into a green run.
2
+ * `report_blocker` — an agent's honest exit when it cannot fulfil the loop it
3
+ * is driving (see the harness-sandbox-agents spec). There is no `submit`/`done`
4
+ * tool on a sandbox step: a step's deliverable is its persisted files, so a
5
+ * clean end-of-turn after writing them is the implicit success. Calling
6
+ * `report_blocker` is the alternative to improvising an inline result the
7
+ * harness would launder into a green run.
7
8
  *
8
- * Mirrors the synthesizer's blocker (`execution/run-synthesis.ts`): the
9
- * workflow body owns a per-run `BlockerHolder` and reads `holder.outcome`
10
- * after `runAgent`. The tool only records the reason and tells the agent to
11
- * stop — the body decides the terminal status.
9
+ * This module is the canonical home of the tool. Three loops offer it — the
10
+ * sandbox step agent, the planner (`tools/research/generate-plan.ts`) and the
11
+ * run synthesizer (`execution/run-synthesis.ts`) — and all three share one id,
12
+ * one input schema and one terminal contract via `createReportBlockerToolFor`.
13
+ * What differs per loop is only what a blocker *means* there, so each call site
14
+ * injects its own `blockedWhen` prose and its own capture closure. The tool
15
+ * records the reason and tells the agent to stop; the driving body reads its
16
+ * own outcome cell after the loop and decides the terminal status.
12
17
  */
13
18
  import { type Tool } from "../define-tool.js";
14
19
  export interface BlockerOutcome {
@@ -20,4 +25,29 @@ export interface BlockerHolder {
20
25
  outcome: BlockerOutcome | null;
21
26
  }
22
27
  export declare function createBlockerHolder(): BlockerHolder;
28
+ export interface ReportBlockerDeps {
29
+ /**
30
+ * The calling loop's blocker-capture side effect — writes the outcome into
31
+ * whatever cell that body reads after its loop. Invoked on every call; the
32
+ * closure owns the first-call-wins guard, because only the call site knows
33
+ * what else may already have terminated its loop.
34
+ */
35
+ readonly record: (outcome: BlockerOutcome) => void;
36
+ /**
37
+ * What a blocker means in THIS loop: which loop the call ends and the
38
+ * conditions that warrant it. Composed into the shared terminal contract,
39
+ * so a description can never promise a loop this instance does not end.
40
+ */
41
+ readonly blockedWhen: string;
42
+ }
43
+ /**
44
+ * Build the `report_blocker` tool for one loop. The id, the input schema and
45
+ * the terminal contract ("pass a reason, then stop") are fixed here; the call
46
+ * site supplies only the per-loop meaning and the capture closure.
47
+ */
48
+ export declare function createReportBlockerToolFor(deps: ReportBlockerDeps): Tool;
49
+ /**
50
+ * The sandbox step agent's `report_blocker`, bound to the step body's holder.
51
+ * A blocker here ends the step with no deliverables and a blocked reason.
52
+ */
23
53
  export declare function createReportBlockerTool(holder: BlockerHolder): Tool;
@@ -1,14 +1,19 @@
1
1
  /**
2
- * `report_blocker` — a step agent's honest exit when it cannot fulfil its
3
- * step (see the harness-sandbox-agents spec). There is no `submit`/`done` tool: a step's deliverable is
4
- * its persisted files, so a clean end-of-turn after writing them is the
5
- * implicit success. Calling `report_blocker` is the alternative to
6
- * improvising an inline result the harness would launder into a green run.
2
+ * `report_blocker` — an agent's honest exit when it cannot fulfil the loop it
3
+ * is driving (see the harness-sandbox-agents spec). There is no `submit`/`done`
4
+ * tool on a sandbox step: a step's deliverable is its persisted files, so a
5
+ * clean end-of-turn after writing them is the implicit success. Calling
6
+ * `report_blocker` is the alternative to improvising an inline result the
7
+ * harness would launder into a green run.
7
8
  *
8
- * Mirrors the synthesizer's blocker (`execution/run-synthesis.ts`): the
9
- * workflow body owns a per-run `BlockerHolder` and reads `holder.outcome`
10
- * after `runAgent`. The tool only records the reason and tells the agent to
11
- * stop — the body decides the terminal status.
9
+ * This module is the canonical home of the tool. Three loops offer it — the
10
+ * sandbox step agent, the planner (`tools/research/generate-plan.ts`) and the
11
+ * run synthesizer (`execution/run-synthesis.ts`) — and all three share one id,
12
+ * one input schema and one terminal contract via `createReportBlockerToolFor`.
13
+ * What differs per loop is only what a blocker *means* there, so each call site
14
+ * injects its own `blockedWhen` prose and its own capture closure. The tool
15
+ * records the reason and tells the agent to stop; the driving body reads its
16
+ * own outcome cell after the loop and decides the terminal status.
12
17
  */
13
18
  import { ok } from "neverthrow";
14
19
  import { z } from "zod";
@@ -16,20 +21,20 @@ import { defineTool } from "../define-tool.js";
16
21
  export function createBlockerHolder() {
17
22
  return { outcome: null };
18
23
  }
19
- export function createReportBlockerTool(holder) {
24
+ /** The one input every `report_blocker` takes, wherever it is offered. */
25
+ const ReportBlockerInputSchema = z.object({ reason: z.string().min(1) });
26
+ /**
27
+ * Build the `report_blocker` tool for one loop. The id, the input schema and
28
+ * the terminal contract ("pass a reason, then stop") are fixed here; the call
29
+ * site supplies only the per-loop meaning and the capture closure.
30
+ */
31
+ export function createReportBlockerToolFor(deps) {
20
32
  return defineTool({
21
33
  id: "report_blocker",
22
- description: "Terminal. Call this when you cannot fulfil the step — required inputs " +
23
- "are missing or unreadable, the requested analysis is infeasible with " +
24
- "the available data/packages, or you cannot produce the persisted " +
25
- "deliverables (scripts, output files, figures). Pass a clear reason. Do " +
26
- "NOT improvise an inline result and narrate it; if you cannot persist " +
27
- "real output files, report a blocker. Stop after calling.",
28
- inputSchema: z.object({ reason: z.string().min(1) }),
34
+ description: `Terminal. ${deps.blockedWhen} Pass a clear reason. Stop immediately after calling — take no further actions.`,
35
+ inputSchema: ReportBlockerInputSchema,
29
36
  execute: async (input) => {
30
- if (holder.outcome === null) {
31
- holder.outcome = { kind: "blocker", reason: input.reason };
32
- }
37
+ deps.record({ kind: "blocker", reason: input.reason });
33
38
  return ok({
34
39
  recorded: true,
35
40
  message: "Blocker recorded. You are done — stop now and do not take further actions.",
@@ -37,3 +42,21 @@ export function createReportBlockerTool(holder) {
37
42
  },
38
43
  });
39
44
  }
45
+ /**
46
+ * The sandbox step agent's `report_blocker`, bound to the step body's holder.
47
+ * A blocker here ends the step with no deliverables and a blocked reason.
48
+ */
49
+ export function createReportBlockerTool(holder) {
50
+ return createReportBlockerToolFor({
51
+ record: (outcome) => {
52
+ if (holder.outcome === null)
53
+ holder.outcome = outcome;
54
+ },
55
+ blockedWhen: "Ends this analysis step with no deliverables. Call it when you cannot " +
56
+ "fulfil the step — required inputs are missing or unreadable, the " +
57
+ "requested analysis is infeasible with the available data/packages, or " +
58
+ "you cannot produce the persisted deliverables (scripts, output files, " +
59
+ "figures). Do NOT improvise an inline result and narrate it: if you " +
60
+ "cannot persist real output files, report a blocker.",
61
+ });
62
+ }
@@ -19,8 +19,7 @@ const ReadFileInputSchema = z.object({
19
19
  .min(1)
20
20
  .describe("Workspace path. Relative paths resolve against your working directory; " +
21
21
  "an absolute '/<analysisId>/...' path is resolved against the analysis " +
22
- "root (use absolute to read input data, the data profile, or other " +
23
- "steps' outputs)."),
22
+ "root (use absolute to read input data or other steps' outputs)."),
24
23
  headLines: z
25
24
  .number()
26
25
  .int()
@@ -28,7 +27,7 @@ const ReadFileInputSchema = z.object({
28
27
  .max(10_000)
29
28
  .optional()
30
29
  .describe("Read only the first N lines (head). Use for inspecting CSV/TSV " +
31
- "headers, profile summaries, or the top of any large file. Mutually " +
30
+ "headers, step summaries, or the top of any large file. Mutually " +
32
31
  "exclusive with tailLines."),
33
32
  tailLines: z
34
33
  .number()
@@ -42,8 +41,8 @@ export function createReadFileTool(fs, workingDir) {
42
41
  return defineTool({
43
42
  id: "read_file",
44
43
  description: "Read a workspace file: input data, prior-run outputs, current-run " +
45
- "outputs, step summaries, run syntheses, or the data profile. Returns " +
46
- "the file content as UTF-8 text. Output is capped at " +
44
+ "outputs, step summaries, or run syntheses. Returns the file content " +
45
+ "as UTF-8 text. Output is capped at " +
47
46
  `${DEFAULT_MAX_BYTES} bytes; pass headLines or tailLines to read a ` +
48
47
  "specific window of a large file (typical for bio CSV/TSV/log files). " +
49
48
  "Oversize reads come back truncated with a marker. Missing paths and " +
@@ -1,10 +1,10 @@
1
1
  /**
2
- * Internal helper used by the three mutate tools to drive one sandbox exec
2
+ * Internal helper used by dependency-bearing tools to drive one sandbox exec
3
3
  * through the `SandboxClient`. Centralises the submit/await pair, exec-id
4
4
  * derivation, and intermediate-event forwarding so all three tools share
5
- * one chokepoint — the contract `execute_command` makes structural is that
6
- * `write_file` / `edit_file` are layered on top of it, not parallel side
7
- * paths into sandbox-server.
5
+ * one chokepoint. `write_file` / `edit_file` are layered on the workspace
6
+ * mutator and environment introspection tools such as `list_available_refs`
7
+ * use this same replay-safe submit/await path rather than a parallel protocol.
8
8
  *
9
9
  * Not a registered tool — `defineTool` is called by the user-facing tools
10
10
  * (`execute-command.ts`, `write-file.ts`, `edit-file.ts`) which wrap this.
@@ -1,10 +1,10 @@
1
1
  /**
2
- * Internal helper used by the three mutate tools to drive one sandbox exec
2
+ * Internal helper used by dependency-bearing tools to drive one sandbox exec
3
3
  * through the `SandboxClient`. Centralises the submit/await pair, exec-id
4
4
  * derivation, and intermediate-event forwarding so all three tools share
5
- * one chokepoint — the contract `execute_command` makes structural is that
6
- * `write_file` / `edit_file` are layered on top of it, not parallel side
7
- * paths into sandbox-server.
5
+ * one chokepoint. `write_file` / `edit_file` are layered on the workspace
6
+ * mutator and environment introspection tools such as `list_available_refs`
7
+ * use this same replay-safe submit/await path rather than a parallel protocol.
8
8
  *
9
9
  * Not a registered tool — `defineTool` is called by the user-facing tools
10
10
  * (`execute-command.ts`, `write-file.ts`, `edit-file.ts`) which wrap this.
@@ -20,19 +20,23 @@ const ShowFileInputSchema = z.object({
20
20
  title: z.string().optional().describe("Group heading shown above the card or gallery"),
21
21
  files: z
22
22
  .array(z.object({
23
- path: z.string().describe("Analysis-rooted file path (e.g., 'runs/run-abc/step-1/figures/volcano.png')"),
23
+ path: z.string().describe("Analysis-rooted file path — no leading slash, no `..` (e.g., 'runs/run-abc/step-1/figures/volcano.png')"),
24
24
  caption: z.string().optional().describe("One-sentence context rendered beside this file"),
25
25
  }))
26
26
  .min(1)
27
27
  .max(MAX_FILES)
28
- .describe(`1 to ${MAX_FILES} files; multiple files render as a gallery`),
28
+ .describe(`The related set of files to show together, 1 to ${MAX_FILES} per call; multiple render as ONE gallery card. Pass a related set in a single call — do not make one call per figure.`),
29
29
  });
30
30
  export const showFileTool = defineTool({
31
31
  id: "show_file",
32
- description: "Display one or more existing analysis artifacts (images, CSVs, PDFs, notebooks, logs) in chat by path. " +
33
- "Paths must be analysis-rooted (no leading slash, no `..`). Up to 10 files per call — multiple render as a gallery. " +
34
- "Do NOT read a file and paste its bytes elsewhere; this tool references the file directly. " +
35
- "File content is fetched when the user views the card — you do not need to provide it.",
32
+ description: "Display EXISTING analysis artifacts in chat by path — images, CSVs, PDFs, notebooks, logs. " +
33
+ "The card references the file directly and its content is fetched when the user views it: you never read, " +
34
+ "inline, or paste the bytes. Pick this tool by what you are referencing, not by how the output looks: " +
35
+ "NOT for content you synthesized (a chart, a table, a snippet you invented — use `show_user`), NOT for a stored plan (`show_plan`). " +
36
+ "Paths are analysis-rooted (no leading slash, no `..`); discover them with `workspace_search` or `list_files`. " +
37
+ "Up to 10 files per call — pass a related set in ONE call and they render as a gallery, rather than one call per figure. " +
38
+ "Cards render in call order, so to interleave figures with prose alternate `show_user(markdown)` and `show_file` calls " +
39
+ "(markdown image syntax pointing at a workspace file does NOT render — this tool is the only way to show one).",
36
40
  inputSchema: ShowFileInputSchema,
37
41
  execute: async (input, ctx) => {
38
42
  const card = buildFileReferenceCardData(input);
@@ -18,13 +18,19 @@ const PLAN_ID_REGEX = /^pln-[a-f0-9]{8}$/;
18
18
  export function createShowPlanTool(pool) {
19
19
  return defineTool({
20
20
  id: "show_plan",
21
- description: "Display a previously generated plan in chat by its planId. " +
22
- "Use when the user asks to see a plan again, or to reference a historical plan. " +
23
- "Get planIds from `inspect-run` (each run carries its planId) or `generatePlan` (newly created). " +
24
- "The plan content is embedded in the chat part so the UI renders it directly.",
21
+ description: "Display a STORED plan in chat by its planId. The plan is loaded from storage and embedded in the card, " +
22
+ "so the UI renders it directly. Use it to present the plan `generate_plan` just produced, and when the user " +
23
+ "asks to see an earlier plan again. " +
24
+ "Pick this tool by what you are referencing, not by how the output looks: " +
25
+ "NOT for a plan you are drafting in conversation (that is `generate_plan`), NOT for other synthesized content " +
26
+ "(`show_user`), NOT for analysis files (`show_file`). Never re-type a plan's steps into a `show_user` markdown " +
27
+ "card — that renders a second, divergent copy of the same plan.",
25
28
  inputSchema: z.object({
26
- planId: z.string().regex(PLAN_ID_REGEX, "planId must match pln-<8hex>").describe("Plan ID from generatePlan or inspect-run"),
27
- title: z.string().optional().describe("Optional display heading"),
29
+ planId: z
30
+ .string()
31
+ .regex(PLAN_ID_REGEX, "planId must match pln-<8hex>")
32
+ .describe("Plan ID (`pln-<8hex>`) from `generate_plan` (newly created) or `inspect_run` (each run carries the planId it ran)"),
33
+ title: z.string().optional().describe("Optional card heading; defaults to the plan's own title"),
28
34
  }),
29
35
  execute: async ({ planId, title }, ctx) => {
30
36
  const resourceId = scopeResource(ctx.session.scope).resourceId;
@@ -15,7 +15,7 @@ import type { EmbeddingProvider } from "../../providers/types.js";
15
15
  export declare function createWorkspaceSearchTool(pool: Pool, embedding: EmbeddingProvider): import("../define-tool.js").Tool<{
16
16
  query: string;
17
17
  limit: number;
18
- type?: "output" | "input" | "summary" | "synthesis" | undefined;
18
+ type?: "output" | "input" | "synthesis" | "summary" | undefined;
19
19
  }, {
20
20
  results: {
21
21
  id: string;
@@ -32,13 +32,19 @@ function indexName(analysisId) {
32
32
  export function createWorkspaceSearchTool(pool, embedding) {
33
33
  return defineTool({
34
34
  id: "workspace_search",
35
- description: "Semantic search over the analysis workspace — input data, analysis " +
36
- "outputs, step summaries, run syntheses, and the data profile. Returns " +
37
- "ranked file paths with descriptions and metadata; read a file " +
38
- "separately to see its contents. Optionally filter by entry type.",
35
+ description: "Semantic search over the analysis workspace. Returns ranked file paths " +
36
+ "with descriptions and metadata; read a file separately to see its " +
37
+ "contents. Indexed entries are exactly four types, and `type` restricts " +
38
+ 'results to one of them: "input" (staged input data files — their ' +
39
+ 'descriptions come from the data profiler), "output" (files a step ' +
40
+ 'produced), "summary" (a step\'s summary), "synthesis" (a run\'s ' +
41
+ "literature-grounded synthesis).",
39
42
  inputSchema: z.object({
40
43
  query: z.string().min(1).describe("What to search for, in natural language"),
41
- type: z.enum(["input", "output", "summary", "synthesis"]).optional().describe("Restrict results to one workspace entry type"),
44
+ type: z
45
+ .enum(["input", "output", "summary", "synthesis"])
46
+ .optional()
47
+ .describe('Restrict results to one entry type: "input", "output", "summary", or "synthesis"'),
42
48
  limit: z.number().int().min(1).max(50).default(8).describe("Maximum number of results to return"),
43
49
  }),
44
50
  execute: async ({ query, type, limit }, ctx) => {
@@ -20,6 +20,10 @@
20
20
  * - dispatch every `scheduleReady(plan, completedSet, startedSet)` via
21
21
  * `DBOS.startWorkflow` with deterministic child id
22
22
  * `"${parent.workflowID}-${N}"` (N is the step's stable index)
23
+ * - compose each child's seed prompt HERE, at dispatch (`composeStepSeed`,
24
+ * inside a durable step): the moment a step is dispatched is the first
25
+ * moment its dependencies' results exist, so this is the only place the
26
+ * seed can name them
23
27
  * - each child input is `forStep(input.runSession, stepId)` for the
24
28
  * session field
25
29
  * - emit `data-dag-state` (reconciling by id) on every dispatch +
@@ -53,6 +57,7 @@ import type { Pool } from "pg";
53
57
  import type { MachineBudget, ResourceSpec } from "../config/resource-limits.js";
54
58
  import type { RunSession } from "../auth/types.js";
55
59
  import type { RunAuthorizer } from "../execution/run-authorizer.js";
60
+ import type { AnalysisStep } from "../schemas/workflow-state.js";
56
61
  import type { ChatProvider, EmbeddingProvider } from "../providers/types.js";
57
62
  import type { BioToolKeys } from "../tools/bio/keys.js";
58
63
  import type { RunCharge } from "../billing/run-charge.js";
@@ -76,8 +81,15 @@ export interface ExecuteAnalysisInput {
76
81
  readonly steps: readonly PlanStep[];
77
82
  /** Optional thread id for cortex_runs.thread_id (UI breadcrumb only). */
78
83
  readonly threadId?: string | null;
79
- /** Per-step prompt body the child workflow forwards to its agent. */
80
- readonly promptByStepId: Readonly<Record<string, string>>;
84
+ /**
85
+ * The plan's steps, keyed by id — the instruction DATA each step's seed is
86
+ * composed FROM, not a pre-rendered prompt string. The seed itself is
87
+ * composed per step at dispatch (`composeStepSeed`), because that is the
88
+ * first moment a step's dependencies have produced anything to tell it
89
+ * about. Snapshotted here at the async edge so the body never re-reads the
90
+ * plan row (a mid-run plan edit cannot change a running run's instructions).
91
+ */
92
+ readonly planStepById: Readonly<Record<string, AnalysisStep>>;
81
93
  /** Per-step agent id (sandbox catalog entry). */
82
94
  readonly agentByStepId: Readonly<Record<string, string>>;
83
95
  /** Per-step planner-estimated sandbox resource request. */
@@ -229,8 +241,9 @@ export interface ExecuteAnalysisDeps {
229
241
  }
230
242
  /**
231
243
  * Project the sandbox-step input handed to each child. The parent owns the
232
- * level computation; the rest comes from `ExecuteAnalysisInput`. The body
233
- * fills in `runSession` via `forStep` after this returns. Exported for the
244
+ * level computation and the seed (`prompt`, composed by `composeStepSeed` at
245
+ * dispatch); the rest comes from `ExecuteAnalysisInput`. The body fills in
246
+ * `runSession` via `forStep` after this returns. Exported for the
234
247
  * projection/timeout-propagation tests.
235
248
  */
236
249
  export declare function buildChildInput(args: {
@@ -239,7 +252,33 @@ export declare function buildChildInput(args: {
239
252
  level: number;
240
253
  runId: string;
241
254
  workflowId: string;
255
+ prompt: string;
242
256
  }): Omit<SandboxStepInput, "runSession">;
257
+ /**
258
+ * Compose one step's seed — the user-content message its agent opens on.
259
+ *
260
+ * The composition point is the design: the scheduler calls this at the instant
261
+ * it dispatches a step, which is the first instant at which every one of that
262
+ * step's dependencies has *finished* and written its results down. A seed
263
+ * rendered any earlier (at plan submission, say) cannot mention them, and the
264
+ * agent is left to rediscover its own upstream.
265
+ *
266
+ * Everything read here is durable state written by an already-settled child
267
+ * (`output/summary.md`, `cortex_artifacts`) or by the profiler
268
+ * (`cortex_analysis_state`) — never live in-flight state, which would not
269
+ * reproduce.
270
+ *
271
+ * REPLAY: the caller wraps this in a `DBOS.runStep`, so the composed string is
272
+ * checkpointed and a replay returns it verbatim — the DB and disk are not read
273
+ * again and cannot drift the seed under a recovered run. Never call it
274
+ * unwrapped from the workflow body.
275
+ */
276
+ export declare function composeStepSeed(args: {
277
+ input: ExecuteAnalysisInput;
278
+ stepId: string;
279
+ runId: string;
280
+ deps: ExecuteAnalysisDeps;
281
+ }): Promise<string>;
243
282
  /**
244
283
  * Run-level literature-grounded synthesis (no sandbox). Returns the
245
284
  * quick-display findings for the run-completed card; empty when synthesis is
@@ -20,6 +20,10 @@
20
20
  * - dispatch every `scheduleReady(plan, completedSet, startedSet)` via
21
21
  * `DBOS.startWorkflow` with deterministic child id
22
22
  * `"${parent.workflowID}-${N}"` (N is the step's stable index)
23
+ * - compose each child's seed prompt HERE, at dispatch (`composeStepSeed`,
24
+ * inside a durable step): the moment a step is dispatched is the first
25
+ * moment its dependencies' results exist, so this is the only place the
26
+ * seed can name them
23
27
  * - each child input is `forStep(input.runSession, stepId)` for the
24
28
  * session field
25
29
  * - emit `data-dag-state` (reconciling by id) on every dispatch +
@@ -54,9 +58,11 @@ import { mkdir } from "node:fs/promises";
54
58
  import { join } from "node:path";
55
59
  import { forStep } from "../auth/types.js";
56
60
  import { unwrapOrThrow } from "../lib/result.js";
57
- import { RunDedupCollisionError, countArtifactsForRun, queryActiveRun, seedStepExecutions, suspendAnalysis as suspendAnalysisQuery, sweepPendingStepExecutions, updateRunStatus, } from "../state/index.js";
61
+ import { RunDedupCollisionError, countArtifactsForRun, loadDataProfileStatus, queryActiveRun, queryStepArtifactPaths, seedStepExecutions, suspendAnalysis as suspendAnalysisQuery, sweepPendingStepExecutions, updateRunStatus, } from "../state/index.js";
58
62
  import { isBudgetExceeded } from "../loop/budget-exceeded.js";
59
- import { runDir } from "../workspace/paths.js";
63
+ import { loadStepSummariesFromDisk } from "../execution/run-synthesis.js";
64
+ import { MAX_UPSTREAM_ARTIFACTS, composeStepBriefing } from "../prompts/briefing.js";
65
+ import { runDir, runStepDir, stepSubdir, stepWritePrefix, toSandboxPath } from "../workspace/paths.js";
60
66
  import { isChatDataPart } from "../sandbox/sandbox-step-translate.js";
61
67
  import { synthesizeRun } from "../app/synthesize-run.js";
62
68
  import { computeTopologicalLevels, scheduleReady, validatePlanDag } from "./execute-analysis-scheduler.js";
@@ -86,12 +92,13 @@ function emitProvenanceGuarded(deps, event) {
86
92
  }
87
93
  /**
88
94
  * Project the sandbox-step input handed to each child. The parent owns the
89
- * level computation; the rest comes from `ExecuteAnalysisInput`. The body
90
- * fills in `runSession` via `forStep` after this returns. Exported for the
95
+ * level computation and the seed (`prompt`, composed by `composeStepSeed` at
96
+ * dispatch); the rest comes from `ExecuteAnalysisInput`. The body fills in
97
+ * `runSession` via `forStep` after this returns. Exported for the
91
98
  * projection/timeout-propagation tests.
92
99
  */
93
100
  export function buildChildInput(args) {
94
- const { input, stepId, level, runId, workflowId } = args;
101
+ const { input, stepId, level, runId, workflowId, prompt } = args;
95
102
  const resources = input.resourcesByStepId[stepId];
96
103
  if (!resources) {
97
104
  throw new Error(`executeAnalysis: step "${stepId}" missing from resourcesByStepId — every step must declare resources`);
@@ -102,12 +109,100 @@ export function buildChildInput(args) {
102
109
  stepId,
103
110
  agentId: input.agentByStepId[stepId] ?? "scientific-executor",
104
111
  level,
105
- prompt: input.promptByStepId[stepId] ?? "",
112
+ prompt,
106
113
  parentWorkflowId: workflowId,
107
114
  resources,
108
115
  timeoutSeconds: input.timeoutByStepId?.[stepId],
109
116
  };
110
117
  }
118
+ // ── Seed composition (at dispatch, not at plan submission) ────────────
119
+ /**
120
+ * Compose one step's seed — the user-content message its agent opens on.
121
+ *
122
+ * The composition point is the design: the scheduler calls this at the instant
123
+ * it dispatches a step, which is the first instant at which every one of that
124
+ * step's dependencies has *finished* and written its results down. A seed
125
+ * rendered any earlier (at plan submission, say) cannot mention them, and the
126
+ * agent is left to rediscover its own upstream.
127
+ *
128
+ * Everything read here is durable state written by an already-settled child
129
+ * (`output/summary.md`, `cortex_artifacts`) or by the profiler
130
+ * (`cortex_analysis_state`) — never live in-flight state, which would not
131
+ * reproduce.
132
+ *
133
+ * REPLAY: the caller wraps this in a `DBOS.runStep`, so the composed string is
134
+ * checkpointed and a replay returns it verbatim — the DB and disk are not read
135
+ * again and cannot drift the seed under a recovered run. Never call it
136
+ * unwrapped from the workflow body.
137
+ */
138
+ export async function composeStepSeed(args) {
139
+ const { input, stepId, runId, deps } = args;
140
+ const step = input.planStepById[stepId];
141
+ if (!step) {
142
+ throw new Error(`executeAnalysis: step "${stepId}" missing from planStepById — every step must carry its plan data`);
143
+ }
144
+ const analysisId = input.analysisId;
145
+ const workspaceRoot = deps.resolveWorkspaceRoot(analysisId);
146
+ // The paths the agent actually sees: the tree is bind-mounted at
147
+ // `/{analysisId}`, and the step's writable directory is its cwd. Derived
148
+ // through the same helpers the sandbox mount and the mutate tools use, so
149
+ // the seed cannot disagree with the container about where anything is.
150
+ const workspace = {
151
+ analysisRoot: `/${analysisId}`,
152
+ workingDir: toSandboxPath(workspaceRoot, analysisId, stepWritePrefix({ workspaceRoot, runId, stepId })),
153
+ };
154
+ // A DB failure here throws (house rules): a swallowed error would be frozen
155
+ // into the checkpointed seed and served to every replay of this step.
156
+ const profile = unwrapOrThrow(await loadDataProfileStatus(deps.pool, analysisId));
157
+ const upstream = await collectUpstreamHandoffs({
158
+ analysisId,
159
+ runId,
160
+ workspaceRoot,
161
+ dependsOn: step.depends_on,
162
+ agentByStepId: input.agentByStepId,
163
+ pool: deps.pool,
164
+ });
165
+ return composeStepBriefing({
166
+ step,
167
+ workspace,
168
+ profile: profile?.result ?? null,
169
+ upstream,
170
+ });
171
+ }
172
+ /**
173
+ * Gather what each completed dependency produced. Dependency order is the
174
+ * plan's declared `depends_on` order — deterministic, so the seed is stable.
175
+ *
176
+ * A dependency that wrote no summary is omitted rather than blocking the
177
+ * dispatch (`loadStepSummariesFromDisk` skips a missing file): a step that
178
+ * completed without a summary has nothing to hand off, and waiting for one that
179
+ * will never arrive would deadlock the run.
180
+ */
181
+ async function collectUpstreamHandoffs(args) {
182
+ const { analysisId, runId, workspaceRoot, dependsOn, agentByStepId, pool } = args;
183
+ if (dependsOn.length === 0)
184
+ return [];
185
+ const summaries = await loadStepSummariesFromDisk({
186
+ workspaceRoot,
187
+ runId,
188
+ completedSteps: dependsOn,
189
+ agentByStepId,
190
+ });
191
+ const handoffs = [];
192
+ for (const summary of summaries) {
193
+ const outputDir = stepSubdir(runStepDir(runId, summary.stepId), "output");
194
+ const artifacts = await queryStepArtifactPaths(pool, analysisId, runId, summary.stepId, MAX_UPSTREAM_ARTIFACTS);
195
+ handoffs.push({
196
+ stepId: summary.stepId,
197
+ agentId: summary.agentId,
198
+ summaryMarkdown: summary.markdown,
199
+ summaryPath: `/${analysisId}/${outputDir}/summary.md`,
200
+ outputDir: `/${analysisId}/${outputDir}`,
201
+ artifacts: artifacts.map((a) => `/${analysisId}/${a.path}`),
202
+ });
203
+ }
204
+ return handoffs;
205
+ }
111
206
  /**
112
207
  * Run-level literature-grounded synthesis (no sandbox). Returns the
113
208
  * quick-display findings for the run-completed card; empty when synthesis is
@@ -416,12 +511,18 @@ async function runSchedulerLoop(args) {
416
511
  for (const stepId of admit) {
417
512
  const idx = stepIndexById.get(stepId);
418
513
  const childWorkflowId = `${workflowId}-${idx}`;
514
+ // Compose the seed HERE — the dependencies of `stepId` are all in
515
+ // `completed`, so this is the first (and only) moment their results
516
+ // can be named. Checkpointed so a replay re-dispatches the child
517
+ // with a byte-identical prompt instead of re-reading the DB/disk.
518
+ const prompt = await DBOS.runStep(() => composeStepSeed({ input, stepId, runId, deps }), { name: `compose-step-seed:${stepId}` });
419
519
  const baseChildInput = buildChildInput({
420
520
  input,
421
521
  stepId,
422
522
  level: levels.get(stepId) ?? 0,
423
523
  runId,
424
524
  workflowId,
525
+ prompt,
425
526
  });
426
527
  const childInput = {
427
528
  ...baseChildInput,
@@ -5,11 +5,11 @@
5
5
  // was always 0. These patterns match the format the agent actually writes.
6
6
  // The block content may include array index notation like `[0]`, so we
7
7
  // use a pattern that allows `[digits]` inside the outer brackets.
8
- const DOSSIER_CITE_BLOCK_RE = /\[dossier:\s*((?:[^[\]]*(?:\[\d+\])?)*)\]/gi;
8
+ const DOSSIER_CITE_BLOCK_RE = /\[dossier:\s*((?:[^[\]]|\[\d+\])*)\]/gi;
9
9
  // External citation IDs are plain strings — no array-index notation like `[n]` — so the simple [^\]]+ class suffices here instead of the array-index-aware pattern used by DOSSIER_CITE_BLOCK_RE.
10
10
  const EXTERNAL_CITE_BLOCK_RE = /\[external citation:\s*([^\]]+)\]/gi;
11
11
  // Assumes at most one `external citation:` segment per dossier block (the recommendation prompt produces at most one); a hypothetical second segment in the same block would be parsed imperfectly.
12
- const EXTERNAL_SEGMENT_RE = /external citation:\s*((?:[^[\]]*(?:\[\d+\])?)*)/i;
12
+ const EXTERNAL_SEGMENT_RE = /external citation:\s*((?:[^[\]]|\[\d+\])*)/i;
13
13
  const NCT_RE = /\bNCT\d{8}\b/g;
14
14
  const PMID_RE = /\bPMID[:\s]?(\d{6,9})\b/g;
15
15
  // Legacy inline format: `dossier.path.foo` written directly in prose, no
@@ -19,6 +19,7 @@ import { z } from "zod";
19
19
  import { withHost } from "../../../lib/host-concurrency.js";
20
20
  import { tryQuery, tryMutation } from "../../../lib/db-result.js";
21
21
  import { offTargetAnnotatorPrompt } from "../../../prompts/target-assessment/off-target-annotator.js";
22
+ import { composeSystemPrompt } from "../../../agents/system-prompt.js";
22
23
  import { forSubAgent } from "../../../auth/types.js";
23
24
  import { finalText, runAgent } from "../../../loop/run-agent.js";
24
25
  import { passthroughStep } from "../../../loop/run-step.js";
@@ -93,7 +94,7 @@ const ANNOTATOR_AGENT_ID = "off-target-annotator";
93
94
  function buildAnnotatorAgent(model) {
94
95
  return {
95
96
  id: ANNOTATOR_AGENT_ID,
96
- systemPrompt: offTargetAnnotatorPrompt,
97
+ systemPrompt: composeSystemPrompt(offTargetAnnotatorPrompt),
97
98
  model,
98
99
  tools: [],
99
100
  maxIterations: 2,
@@ -127,8 +127,8 @@ export declare function stepWritePrefix(args: {
127
127
  * in-sandbox absolute path. The tree is bind-mounted at `/{resourceId}`, so
128
128
  * the sandbox path is the resource id plus the root-relative tail — the host
129
129
  * location of the root never leaks into the container. Single source of truth
130
- * for the `execute_command` / `write_file` cwd and the `{{WORKING_DIR}}`
131
- * prompt substitution (see the harness-workspace-tools spec).
130
+ * for the `execute_command` / `write_file` cwd and for the working directory
131
+ * a step briefing names (see the harness-workspace-tools spec).
132
132
  */
133
133
  export declare function toSandboxPath(workspaceRoot: string, resourceId: string, hostAbsPath: string): string;
134
134
  /**
@@ -101,8 +101,8 @@ export function stepWritePrefix(args) {
101
101
  * in-sandbox absolute path. The tree is bind-mounted at `/{resourceId}`, so
102
102
  * the sandbox path is the resource id plus the root-relative tail — the host
103
103
  * location of the root never leaks into the container. Single source of truth
104
- * for the `execute_command` / `write_file` cwd and the `{{WORKING_DIR}}`
105
- * prompt substitution (see the harness-workspace-tools spec).
104
+ * for the `execute_command` / `write_file` cwd and for the working directory
105
+ * a step briefing names (see the harness-workspace-tools spec).
106
106
  */
107
107
  export function toSandboxPath(workspaceRoot, resourceId, hostAbsPath) {
108
108
  const tail = relativePath(workspaceRoot, hostAbsPath).split(sep).join("/");