@inflexa-ai/harness 0.48.1 → 0.48.3

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 (120) hide show
  1. package/dist/app/chat-turn-workflow.d.ts +3 -1
  2. package/dist/app/chat-turn-workflow.js +18 -3
  3. package/dist/app/chat-turn.js +27 -1
  4. package/dist/app/in-process-turn.js +3 -6
  5. package/dist/contracts/chat-events.d.ts +45 -0
  6. package/dist/contracts/chat-frame.js +11 -0
  7. package/dist/contracts/chat-parts.d.ts +19 -1
  8. package/dist/contracts/chat-parts.js +1 -0
  9. package/dist/contracts/index.d.ts +2 -2
  10. package/dist/contracts/message.d.ts +7 -1
  11. package/dist/contracts/part-registry.js +1 -0
  12. package/dist/contracts/schemas/chat-events.d.ts +321 -0
  13. package/dist/contracts/schemas/chat-events.js +30 -0
  14. package/dist/contracts/schemas/chat-parts.d.ts +29 -1
  15. package/dist/contracts/schemas/chat-parts.js +10 -0
  16. package/dist/execution/run-synthesis.js +1 -0
  17. package/dist/index.d.ts +4 -2
  18. package/dist/index.js +1 -0
  19. package/dist/input-sanitization.d.ts +30 -2
  20. package/dist/input-sanitization.js +187 -2
  21. package/dist/loop/chat-stream.js +1 -1
  22. package/dist/loop/run-loop.js +66 -5
  23. package/dist/loop/tool-adapter.d.ts +8 -0
  24. package/dist/loop/tool-adapter.js +75 -4
  25. package/dist/loop/tool-detail.d.ts +5 -1
  26. package/dist/loop/tool-detail.js +7 -3
  27. package/dist/loop/tool-value.d.ts +57 -0
  28. package/dist/loop/tool-value.js +249 -0
  29. package/dist/loop/types.d.ts +9 -2
  30. package/dist/memory/conversation-display-storage.d.ts +3 -2
  31. package/dist/memory/conversation-display-storage.js +14 -2
  32. package/dist/memory/count-tokens.js +29 -2
  33. package/dist/providers/ai-sdk.d.ts +17 -14
  34. package/dist/providers/ai-sdk.js +14 -16
  35. package/dist/sandbox/sandbox-step-translate.d.ts +5 -9
  36. package/dist/sandbox/sandbox-step-translate.js +5 -9
  37. package/dist/tasks/data-profile-activity.d.ts +3 -7
  38. package/dist/tasks/data-profile-activity.js +1 -1
  39. package/dist/tasks/data-profile.js +3 -6
  40. package/dist/tools/ad-hoc-router.js +1 -0
  41. package/dist/tools/bio/alphafold-prediction.js +1 -0
  42. package/dist/tools/bio/chembl.js +1 -0
  43. package/dist/tools/bio/comptox.js +1 -0
  44. package/dist/tools/bio/drug-gene-interactions.js +1 -0
  45. package/dist/tools/bio/gene-disease-evidence.js +1 -0
  46. package/dist/tools/bio/gene-preclinical-profile.js +1 -0
  47. package/dist/tools/bio/lookup-annotation.js +1 -0
  48. package/dist/tools/bio/opentargets.js +1 -0
  49. package/dist/tools/bio/pubchem.js +1 -0
  50. package/dist/tools/bio/pubmed.js +1 -0
  51. package/dist/tools/bio/search-clinical-trials.js +1 -0
  52. package/dist/tools/bio/search-faers.js +1 -0
  53. package/dist/tools/bio/search-gene.js +1 -0
  54. package/dist/tools/bio/search-geo-datasets.js +1 -0
  55. package/dist/tools/bio/search-interactions.js +1 -0
  56. package/dist/tools/bio/search-patents.js +1 -0
  57. package/dist/tools/bio/search-protein.js +1 -0
  58. package/dist/tools/bio/search-regulatory-approvals.js +1 -0
  59. package/dist/tools/bio/target-safety.js +1 -0
  60. package/dist/tools/capability-data.js +5 -0
  61. package/dist/tools/define-tool.d.ts +18 -0
  62. package/dist/tools/define-tool.js +2 -0
  63. package/dist/tools/display/show-user.js +1 -0
  64. package/dist/tools/execute-analysis.js +4 -0
  65. package/dist/tools/knowledge/check.d.ts +47 -1
  66. package/dist/tools/knowledge/check.js +10 -1
  67. package/dist/tools/knowledge/client.d.ts +1 -1
  68. package/dist/tools/knowledge/recommend.js +4 -3
  69. package/dist/tools/knowledge/situation.d.ts +39 -1
  70. package/dist/tools/knowledge/situation.js +80 -8
  71. package/dist/tools/knowledge/skeleton.d.ts +7 -0
  72. package/dist/tools/knowledge/skeleton.js +88 -2
  73. package/dist/tools/knowledge/template.js +1 -0
  74. package/dist/tools/memory/update-working-memory.js +5 -0
  75. package/dist/tools/read-tool-output.js +4 -0
  76. package/dist/tools/report-authoring/authoring-tools.js +8 -0
  77. package/dist/tools/report-session/examine-page.js +5 -0
  78. package/dist/tools/report-session/list-artifacts.js +5 -0
  79. package/dist/tools/report-session/preview-report.js +1 -0
  80. package/dist/tools/report-session/record-version.js +1 -0
  81. package/dist/tools/report-session/script-tools.js +2 -0
  82. package/dist/tools/research/context7-docs.js +2 -0
  83. package/dist/tools/research/generate-analogy-report.js +3 -0
  84. package/dist/tools/research/generate-plan.js +6 -0
  85. package/dist/tools/research/inspect-data-profile.js +5 -0
  86. package/dist/tools/research/inspect-run.js +5 -0
  87. package/dist/tools/research/resolve-citation.js +1 -0
  88. package/dist/tools/research/search-arxiv.js +1 -0
  89. package/dist/tools/research/search-github-repos.js +1 -0
  90. package/dist/tools/research/search-semantic-scholar.js +1 -0
  91. package/dist/tools/research/web-fetch.js +1 -0
  92. package/dist/tools/research/web-search.js +1 -0
  93. package/dist/tools/sandbox/lease-tools.js +2 -0
  94. package/dist/tools/sandbox/link-packages.js +1 -0
  95. package/dist/tools/sandbox/list-available-packages.js +5 -0
  96. package/dist/tools/sandbox/list-available-refs.js +5 -0
  97. package/dist/tools/sandbox/report-blocker.js +1 -0
  98. package/dist/tools/sandbox/skills.js +2 -0
  99. package/dist/tools/sandbox/submit-file-metadata.js +1 -0
  100. package/dist/tools/script/file-tools.js +6 -0
  101. package/dist/tools/script/run-script.js +20 -1
  102. package/dist/tools/start-report-session.js +1 -0
  103. package/dist/tools/tool-view.d.ts +63 -0
  104. package/dist/tools/tool-view.js +68 -0
  105. package/dist/tools/workspace/edit-file.js +5 -0
  106. package/dist/tools/workspace/execute-command.js +5 -2
  107. package/dist/tools/workspace/file-stat.js +5 -0
  108. package/dist/tools/workspace/grep.js +4 -0
  109. package/dist/tools/workspace/list-files.js +5 -0
  110. package/dist/tools/workspace/read-file.js +4 -0
  111. package/dist/tools/workspace/scan-inputs.js +5 -0
  112. package/dist/tools/workspace/show-file.js +1 -0
  113. package/dist/tools/workspace/show-plan.js +1 -0
  114. package/dist/tools/workspace/workspace-search.js +5 -0
  115. package/dist/tools/workspace/write-file.js +7 -2
  116. package/dist/workflows/execute-analysis.js +2 -1
  117. package/dist/workflows/sandbox-step.js +3 -7
  118. package/package.json +1 -1
  119. package/dist/tools/detail-resolver.d.ts +0 -40
  120. package/dist/tools/detail-resolver.js +0 -44
@@ -18,16 +18,28 @@ export const SituationFieldsSchema = z.object({
18
18
  "clustering",
19
19
  "survival",
20
20
  "signature_scoring",
21
+ "dose_response",
21
22
  ])
22
23
  .describe("What the plan needs a procedure for. `full_plan` returns QC, filtering, the model, the test, shrinkage, multiple testing, enrichment, and the report in one answer. " +
23
24
  "Use `differential_expression` when the research question does not ask for pathways or gene sets; an enrichment step the user did not ask for is unasked scope. " +
24
25
  "Use `enrichment` when a results table is the input and no new test is fitted, and `qc` when the question stops at the sample structure. " +
25
26
  "Use `tf_activity` for regulator or pathway activity, `deconvolution` for cell type proportions, `coexpression` for modules, `clustering` for sample subgroups, `survival` for an outcome association, " +
26
- "and `signature_scoring` for a per-sample score, or with `classifier: true` for a classifier of the samples."),
27
- modality: z.literal("bulk_rna_seq").describe("The assay. Phase 0 serves bulk RNA-seq only."),
27
+ "and `signature_scoring` for a per-sample score, or with `classifier: true` for a classifier of the samples. " +
28
+ "Use `dose_response` when the question asks at which exposure the expression leaves the control: the walk fits a curve per gene over the dose, takes the benchmark dose of each gene set, " +
29
+ "reads the transcriptomic point of departure from the lowest set median, and divides it by an efficacious exposure for the exposure margin. " +
30
+ "A `dose_response` situation carries `n_dose_levels`, `tissue`, and `exposure_axis`, because the fit, the point of departure, and the margin each stop without one of them, " +
31
+ "and it carries `control_placement`, `grid_complete`, `recovery_group`, and `n_compounds` when the design holds them."),
32
+ modality: z
33
+ .enum(["bulk_rna_seq", "microarray", "tempo_seq"])
34
+ .describe("The platform that measured the expression: `bulk_rna_seq` for a sequencing run over the whole transcriptome; `microarray` for the probe intensities of a hybridization array, the platform of the legacy toxicogenomics compendia; " +
35
+ "`tempo_seq` for the targeted read counts of a TempO-Seq probe panel, where the panel and not the transcriptome is the gene universe. " +
36
+ "An array reads no sequence, thus it has no read count and no library size: a `microarray` takes `data_state` `log_normalized`, `de_results`, or `effect_sizes_only`, and `fastq`, `counts`, or `tpm_or_fpkm` stops the plan as a contradiction. " +
37
+ "A probe panel is a sequencing assay and it keeps the read count, thus `tempo_seq` takes `counts`; a panel gives no transcript length, thus never `tpm_or_fpkm`."),
28
38
  data_state: z
29
- .enum(["fastq", "counts", "tpm_or_fpkm", "log_normalized"])
30
- .describe("The state of the expression data in the inputs: raw reads, a raw integer count matrix, TPM/FPKM/RPKM abundances, or log-scale normalized values (log-CPM, microarray)."),
39
+ .enum(["fastq", "counts", "tpm_or_fpkm", "log_normalized", "de_results", "effect_sizes_only"])
40
+ .describe("The state of the expression data in the inputs: raw reads, a raw integer count matrix, TPM/FPKM/RPKM abundances, or log-scale normalized values (log-CPM, microarray). " +
41
+ "`de_results` is a table of contrasts with an effect size and a p-value per gene, and `effect_sizes_only` the same table with no p-value. " +
42
+ "A table of contrasts holds no value of a sample and no variance inside a dose group: without the p-values no step counts calls at a false discovery rate, and either table stops a benchmark dose fit."),
31
43
  count_source: z
32
44
  .enum(["salmon", "kallisto", "star_featurecounts", "rsem", "unknown"])
33
45
  .optional()
@@ -41,7 +53,9 @@ export const SituationFieldsSchema = z.object({
41
53
  "`corrected_counts`: a table described as lengthScaledTPM or scaledTPM counts. " +
42
54
  "`integer_counts`: integer counts from an aligner and a counter (STAR, featureCounts) or from a 3' protocol, with no length data. " +
43
55
  "`unknown`: a count table from a quantifier with no statement of the correction. Omit when `data_state` is not `counts`."),
44
- organism: z.enum(["human", "mouse", "other"]).describe("The organism of the samples."),
56
+ organism: z
57
+ .enum(["human", "mouse", "rat", "dog", "monkey", "other"])
58
+ .describe("The organism of the samples. `rat`, `dog`, and `monkey` are the species of a regulatory toxicology study; use `other` only for a species this list does not name."),
45
59
  n_groups: z
46
60
  .number()
47
61
  .int()
@@ -71,10 +85,10 @@ export const SituationFieldsSchema = z.object({
71
85
  .describe("For a survival question, the number of samples with an observed event (event indicator 1) in the outcome column. Omit when the question is not survival or the count is not in the profile."),
72
86
  paired: z.boolean().describe("True when each subject contributes a sample to more than one condition (paired or repeated measures)."),
73
87
  blocking_factor: z
74
- .enum(["individual", "cell_line", "litter", "site", "pair", "other"])
88
+ .enum(["individual", "cell_line", "litter", "site", "pair", "plate", "other"])
75
89
  .nullable()
76
90
  .optional()
77
- .describe("The role of the blocking factor that is not the condition, never its column name: `individual` when the same subject, donor, or animal gives more than one sample; `cell_line`; `litter`; `site` for a center or a lab of a multi-site study; `pair` for matched pairs of different individuals; `other`. Omit when there is none. The column name stays in the step."),
91
+ .describe("The role of the blocking factor that is not the condition, never its column name: `individual` when the same subject, donor, or animal gives more than one sample; `cell_line`; `litter`; `site` for a center or a lab of a multi-site study; `pair` for matched pairs of different individuals; `plate` when the samples sit on culture or assay plates and one plate holds a set of them; `other`. Omit when there is none. The column name stays in the step."),
78
92
  block_structure: z
79
93
  .enum(["crossed", "nested"])
80
94
  .optional()
@@ -126,10 +140,14 @@ export const SituationFieldsSchema = z.object({
126
140
  "survival",
127
141
  "transcript_level",
128
142
  "annotation",
143
+ "reference_module_score",
144
+ "classifier_call",
129
145
  ]))
130
146
  .optional()
131
147
  .describe("Analyses the research question asks for beside the question kind, for example a regulator activity beside a differential expression. Each joins the procedure at its place. " +
132
- "Add `annotation` when the rows of the count table are not gene symbols (Ensembl or Entrez identifiers) and a later step needs symbols: the identifier map is then a vetted step with its own template, and no other step rebuilds it. Omit when none."),
148
+ "Add `annotation` when the rows of the count table are not gene symbols (Ensembl or Entrez identifiers) and a later step needs symbols: the identifier map is then a vetted step with its own template, and no other step rebuilds it. " +
149
+ "Add `reference_module_score` for a score of each sample against a reference co-expression module set, and `classifier_call` for the call of a validated classifier on each sample. " +
150
+ "Each of the two contrasts two levels of the group column, thus either one needs two or more groups, a time course, or a numeric predictor. Omit when none."),
133
151
  preferred_language: z
134
152
  .enum(["R", "python"])
135
153
  .optional()
@@ -142,6 +160,60 @@ export const SituationFieldsSchema = z.object({
142
160
  .array(z.enum(["low_depth_sample", "outlier_sample", "sample_identity_doubt", "high_duplication", "degraded_rna"]))
143
161
  .optional()
144
162
  .describe("The quality concerns of the profile as typed flags: a sample with a library size far below the median; a sample that does not cluster with its group; a doubt about the identity of a sample; high duplication; degraded RNA (a low RNA integrity number, a 3' coverage bias, or formalin-fixed material)."),
163
+ tissue: z
164
+ .enum(["liver", "kidney", "heart", "muscle", "brain", "adipose", "blood", "other"])
165
+ .optional()
166
+ .describe("The tissue the samples come from: `liver`, `kidney`, `heart`, `muscle` for skeletal muscle, `brain` whole or one region, `adipose`, `blood` whole or a fraction, " +
167
+ "or `other` for a tissue this list does not name, for example a cell line. " +
168
+ "A transcriptomic point of departure belongs to the tissue of its samples, and the point of departure of one tissue says nothing about another, thus one call covers one tissue and the exposure margin stops without this field. " +
169
+ "Read it from the sample description of the profile or from the user request. Omit it when the question is not `dose_response`, or when the samples are not from one tissue."),
170
+ exposure_axis: z
171
+ .enum(["administered_dose", "in_vitro_concentration", "systemic_exposure"])
172
+ .optional()
173
+ .describe("The axis on which the exposure values of the study lie: `administered_dose` for the dose the study gives to the animal or to the culture, for example in mg/kg; " +
174
+ "`in_vitro_concentration` for the concentration of the compound in the medium of a cell culture; `systemic_exposure` for a measured level in the plasma, for example a Cmax or an AUC. " +
175
+ "The exposure margin divides the point of departure by an efficacious exposure, and that quotient means something only when both values lie on one axis and in one unit. " +
176
+ "Thus the margin stops without this field, and `in_vitro_concentration` stops it too, because a concentration in a medium is no dose of an intact organism and the extrapolation from in vitro to in vivo comes first. " +
177
+ "Read it from the dose column of the profile or from the user request. Omit it when the question is not `dose_response`."),
178
+ n_dose_levels: z
179
+ .number()
180
+ .int()
181
+ .min(1)
182
+ .optional()
183
+ .describe("The number of dose levels of the design, the control level included. The dose levels are groups of the design, thus this count is at most `n_groups`, " +
184
+ "unless `continuous_predictor` is `dose` and the dose itself, not a group, is the factor of interest. " +
185
+ "Three levels is the floor of the walk: the model families of the average hold three or more parameters, thus below three levels no gene and no gene set has a benchmark dose and the point of departure stops. " +
186
+ "Read it from the dose column of the profile. Omit it when the question is not `dose_response`."),
187
+ n_compounds: z
188
+ .number()
189
+ .int()
190
+ .min(1)
191
+ .optional()
192
+ .describe("The number of compounds the study exposes the samples to. One call covers one compound: no design term names a compound as a factor, and the point of departure and the exposure margin each give one value for one compound. " +
193
+ "Thus 2 or more says that the plan runs the walk one time for each compound, each run over the samples of that compound and the control samples of that compound, and it pools no two compounds in one fit. " +
194
+ "Read it from the sample table of the profile. Omit it when the question is not `dose_response`."),
195
+ control_placement: z
196
+ .enum(["single_baseline", "per_time", "per_block", "per_time_and_block"])
197
+ .optional()
198
+ .describe("Where the control samples sit in the design, which decides the samples that form the contrast of each treated sample: `single_baseline` when one control group serves every treated group; " +
199
+ "`per_time` when each time point holds its own control group; `per_block` when each block, for example a plate, holds its own control group; `per_time_and_block` for both. " +
200
+ "With a control at each time the design takes the dose and the time as fixed factors and the control of the same time as the reference level, so that a time effect all dose levels share leaves the dose contrast. " +
201
+ "`per_time` and `per_time_and_block` need `n_timepoints` or `continuous_predictor: time`, and `per_block` and `per_time_and_block` need `blocking_factor`. " +
202
+ "Read it from the sample table of the profile. Omit it when the question is not `dose_response`."),
203
+ grid_complete: z
204
+ .boolean()
205
+ .optional()
206
+ .describe("Whether every cell of the grid of the dose levels, the times, and the blocks holds at least one sample. Set false when a cell holds none: a model that assumes every cell, " +
207
+ "for example a cell-means model with one coefficient per combination or a full interaction of the factors, then carries a coefficient with no data and the design matrix loses rank. " +
208
+ "The plan then fits the terms the design can estimate and states the estimable contrasts only. " +
209
+ "Read it from the sample table of the profile. Omit it when every cell holds a sample."),
210
+ recovery_group: z
211
+ .boolean()
212
+ .optional()
213
+ .describe("Set true when the study holds a group that it observes after the exposure ends, for example animals that rest after the last dose. " +
214
+ "Such a group carries no dose at the time of its sample, thus it lies at no point of the dose-response curve and the fit leaves it out; " +
215
+ "a recovery sample placed at the dose of its exposure makes the response of that dose lower than the exposure gives. " +
216
+ "Read it from the time column or the group labels of the sample table. Omit it when the study samples every group during the exposure."),
145
217
  });
146
218
  /** The situation as the client sends it: the fields as given, with the caller preference held out of the situation fields. */
147
219
  export function toSituation(fields) {
@@ -19,6 +19,13 @@
19
19
  * one enrichment step on its own track, and one report step. A group with
20
20
  * no step in the procedure is absent from the skeleton.
21
21
  *
22
+ * A step whose own template reads the output of another template gets its
23
+ * own group, because a fold keeps the template of the central step alone and
24
+ * it would drop the others. Thus the four steps of the dose-response chain
25
+ * (the benchmark dose of each gene, the benchmark dose of each gene set, the
26
+ * point of departure, and the exposure margin) are four groups of one track,
27
+ * in that order, and each of the other four new steps is a group of its own.
28
+ *
22
29
  * The central step of a group names the method of the skeleton step, and it
23
30
  * gives the alternatives, the template, and the environment: an alternative
24
31
  * of a folded step (a normalization in the differential expression group)
@@ -19,6 +19,13 @@
19
19
  * one enrichment step on its own track, and one report step. A group with
20
20
  * no step in the procedure is absent from the skeleton.
21
21
  *
22
+ * A step whose own template reads the output of another template gets its
23
+ * own group, because a fold keeps the template of the central step alone and
24
+ * it would drop the others. Thus the four steps of the dose-response chain
25
+ * (the benchmark dose of each gene, the benchmark dose of each gene set, the
26
+ * point of departure, and the exposure margin) are four groups of one track,
27
+ * in that order, and each of the other four new steps is a group of its own.
28
+ *
22
29
  * The central step of a group names the method of the skeleton step, and it
23
30
  * gives the alternatives, the template, and the environment: an alternative
24
31
  * of a folded step (a normalization in the differential expression group)
@@ -40,6 +47,21 @@ const GROUPS = [
40
47
  steps: ["filter_low_counts", "normalize", "model_design", "differential_expression", "shrink_lfc", "multiple_testing"],
41
48
  name: "Differential expression",
42
49
  },
50
+ // The dose-response chain: each step reads the table that the step before
51
+ // it writes, thus the four are one track in the order of the chain. The
52
+ // track is its own, because the chain is a branch off the test and not a
53
+ // part of the core walk, and it ends in a regulatory quantity.
54
+ { id: "T4S1", track: "T4", step_type: "analysis", agent: "bulk-transcriptomics-agent", steps: ["dose_response_fit"], name: "Benchmark dose of each gene" },
55
+ {
56
+ id: "T4S2",
57
+ track: "T4",
58
+ step_type: "analysis",
59
+ agent: "bulk-transcriptomics-agent",
60
+ steps: ["benchmark_dose_sets"],
61
+ name: "Benchmark dose of each gene set",
62
+ },
63
+ { id: "T4S3", track: "T4", step_type: "analysis", agent: "bulk-transcriptomics-agent", steps: ["point_of_departure"], name: "Point of departure" },
64
+ { id: "T4S4", track: "T4", step_type: "analysis", agent: "bulk-transcriptomics-agent", steps: ["exposure_margin"], name: "Exposure margin" },
43
65
  { id: "T2S1", track: "T2", step_type: "enrichment", agent: "enrichment-agent", steps: ["enrichment"], name: "Gene set enrichment" },
44
66
  {
45
67
  id: "T2S2",
@@ -49,12 +71,45 @@ const GROUPS = [
49
71
  steps: ["tf_activity", "pathway_activity"],
50
72
  name: "Regulator and pathway activity",
51
73
  },
74
+ // The connectivity reads the contrast of the study against a reference
75
+ // resource, which is the shape and the agent of this track. The resource
76
+ // is a compendium of contrasts and not a gene set collection, thus the
77
+ // step is a group of its own beside the enrichment and the activity.
78
+ {
79
+ id: "T2S3",
80
+ track: "T2",
81
+ step_type: "analysis",
82
+ agent: "enrichment-agent",
83
+ steps: ["compendium_connectivity"],
84
+ name: "Compendium connectivity",
85
+ },
52
86
  { id: "T1S4", track: "T1", step_type: "analysis", agent: "bulk-transcriptomics-agent", steps: ["variance_partition"], name: "Variance partition" },
53
87
  { id: "T3S1", track: "T3", step_type: "analysis", agent: "bulk-transcriptomics-agent", steps: ["signature_scoring"], name: "Signature scoring" },
54
88
  { id: "T3S2", track: "T3", step_type: "analysis", agent: "bulk-transcriptomics-agent", steps: ["deconvolution"], name: "Cell type deconvolution" },
55
89
  { id: "T3S3", track: "T3", step_type: "analysis", agent: "bulk-transcriptomics-agent", steps: ["coexpression"], name: "Co-expression modules" },
56
90
  { id: "T3S4", track: "T3", step_type: "analysis", agent: "bulk-transcriptomics-agent", steps: ["clustering"], name: "Sample clustering" },
57
91
  { id: "T3S5", track: "T3", step_type: "analysis", agent: "bulk-transcriptomics-agent", steps: ["survival"], name: "Outcome association" },
92
+ // Three computations over the value matrix and the sample table, each
93
+ // independent of the others and of the chain, thus each is a group of
94
+ // this track: a class per gene over the time points, a score per sample
95
+ // against a reference module set, and a call per sample.
96
+ {
97
+ id: "T3S6",
98
+ track: "T3",
99
+ step_type: "analysis",
100
+ agent: "bulk-transcriptomics-agent",
101
+ steps: ["trajectory_classification"],
102
+ name: "Trajectory classification",
103
+ },
104
+ {
105
+ id: "T3S7",
106
+ track: "T3",
107
+ step_type: "analysis",
108
+ agent: "bulk-transcriptomics-agent",
109
+ steps: ["reference_module_score"],
110
+ name: "Reference module score",
111
+ },
112
+ { id: "T3S8", track: "T3", step_type: "analysis", agent: "bulk-transcriptomics-agent", steps: ["classifier_call"], name: "Classifier call" },
58
113
  { id: "T1S5", track: "T1", step_type: "analysis", agent: "bulk-transcriptomics-agent", steps: ["transcript_level"], name: "Transcript-level analysis" },
59
114
  { id: "T1S6", track: "T1", step_type: "analysis", agent: "bulk-transcriptomics-agent", steps: ["annotation"], name: "Identifier annotation" },
60
115
  { id: "T1S3", track: "T1", step_type: "report", agent: "bulk-transcriptomics-agent", steps: ["report"], name: "Report" },
@@ -63,23 +118,54 @@ const GROUPS = [
63
118
  * The dependencies of each group. The QC depends on the cohort assembly when
64
119
  * one is present, a group depends on the QC, on the differential expression
65
120
  * when its input is a results table, and the report depends on every group
66
- * that is present.
121
+ * that is present. A step of the dose-response chain depends on the step
122
+ * whose table it reads: the fit reads the genes that passed the test, the
123
+ * gene sets read the gene table of the fit, the point of departure reads the
124
+ * set table and the gene table, and the margin reads the point of departure.
67
125
  */
68
126
  const DEPENDS = {
69
127
  T0S1: [],
70
128
  T1S1: ["T0S1"],
71
129
  T1S2: ["T1S1"],
130
+ T4S1: ["T1S2"],
131
+ T4S2: ["T4S1"],
132
+ T4S3: ["T4S2", "T4S1"],
133
+ T4S4: ["T4S3"],
72
134
  T2S1: ["T1S2"],
73
135
  T2S2: ["T1S2", "T1S1"],
136
+ T2S3: ["T1S2"],
74
137
  T1S4: ["T1S1"],
75
138
  T3S1: ["T1S1"],
76
139
  T3S2: ["T1S1"],
77
140
  T3S3: ["T1S1"],
78
141
  T3S4: ["T1S1"],
79
142
  T3S5: ["T3S1", "T1S1"],
143
+ T3S6: ["T1S1"],
144
+ T3S7: ["T1S1"],
145
+ T3S8: ["T1S1"],
80
146
  T1S5: ["T1S1"],
81
147
  T1S6: ["T1S1"],
82
- T1S3: ["T1S2", "T2S1", "T2S2", "T1S4", "T3S1", "T3S2", "T3S3", "T3S4", "T3S5", "T1S5", "T1S6"],
148
+ T1S3: [
149
+ "T1S2",
150
+ "T4S1",
151
+ "T4S2",
152
+ "T4S3",
153
+ "T4S4",
154
+ "T2S1",
155
+ "T2S2",
156
+ "T2S3",
157
+ "T1S4",
158
+ "T3S1",
159
+ "T3S2",
160
+ "T3S3",
161
+ "T3S4",
162
+ "T3S5",
163
+ "T3S6",
164
+ "T3S7",
165
+ "T3S8",
166
+ "T1S5",
167
+ "T1S6",
168
+ ],
83
169
  };
84
170
  /** A parameter value as a setting: a scalar stays, a list becomes a list of strings, anything else its text. */
85
171
  function settingValue(value) {
@@ -124,6 +124,7 @@ export function createKnowledgeTemplateTool(deps) {
124
124
  "A change the slots do not cover: use `edit_file` on a line marked `# [adaptable: ...]` in the rendered script, and keep the edit small; the record lists each such edit. " +
125
125
  "`match: unavailable` means the service did not answer, and `match: snapshot_mismatch` means the service moved to another release than the plan pinned; in both cases write the script yourself as you would without this tool, and state it in your summary.",
126
126
  inputSchema: KnowledgeTemplateInputSchema,
127
+ view: "none",
127
128
  describeCall: ({ template }) => template,
128
129
  describeResult: (_input, result) => "status" in result ? (result.status === "ok" ? `${result.script_path} (${result.environment_match})` : result.status) : result.match,
129
130
  execute: async ({ template, slots, overrides, script_name }, ctx) => {
@@ -165,6 +165,11 @@ export function createUpdateWorkingMemoryTool(workingMemory, pool, opts = DEFAUL
165
165
  "user's last message, run contents (tables, gene lists, file paths), or anything you can retrieve " +
166
166
  "again with inspect_run, read_file, or search.",
167
167
  inputSchema,
168
+ view: {
169
+ input: (input) => input,
170
+ result: (_input, result) => result,
171
+ error: true,
172
+ },
168
173
  // Four writes in one turn are four identical chips without this. The
169
174
  // entry id rides along on revise/retire because it is the only thing
170
175
  // separating two retirements from the same section.
@@ -122,6 +122,10 @@ export function createReadToolOutputTool(store) {
122
122
  `${MAX_MATCHES} matches, each with its offset and the text around it. When a result gives \`more: true\`, ` +
123
123
  "call again with `offset` set to its `end`.",
124
124
  inputSchema: ReadToolOutputInputSchema,
125
+ view: {
126
+ input: (input) => input,
127
+ error: true,
128
+ },
125
129
  // The reference alone repeats across the pages of one text, and the pattern is what tells two searches apart.
126
130
  describeCall: ({ ref, pattern }) => (pattern === undefined ? ref : `${pattern} in ${ref}`),
127
131
  execute: async ({ ref, offset = 0, limit, pattern }, ctx) => {
@@ -524,6 +524,7 @@ export function createReportAuthoringTools(gateway, deps = {}) {
524
524
  "A reference names the path of a pinned artifact, and the session stamps the hash from the pinned evidence.",
525
525
  inputSchema: addBlockInput,
526
526
  outputSchema: MutationOutputSchema,
527
+ view: "none",
527
528
  describeCall: (input) => {
528
529
  const block = decodeBlockPayload(input.block);
529
530
  const kind = readKind(block);
@@ -559,6 +560,7 @@ export function createReportAuthoringTools(gateway, deps = {}) {
559
560
  "A reference names the path of a pinned artifact, and the session stamps the hash from the pinned evidence.",
560
561
  inputSchema: changeBlockInput,
561
562
  outputSchema: MutationOutputSchema,
563
+ view: "none",
562
564
  describeCall: (input) => `change ${input.targetId}`,
563
565
  execute: async (input, ctx) => {
564
566
  const opening = await openMutation(ctx);
@@ -582,6 +584,7 @@ export function createReportAuthoringTools(gateway, deps = {}) {
582
584
  description: "Remove one block by its id. A removed section takes its whole subtree with it, and the id is free for a later add.",
583
585
  inputSchema: removeBlockInput,
584
586
  outputSchema: MutationOutputSchema,
587
+ view: "none",
585
588
  describeCall: (input) => `remove ${input.targetId}`,
586
589
  execute: async (input, ctx) => {
587
590
  const opening = await openMutation(ctx);
@@ -602,6 +605,7 @@ export function createReportAuthoringTools(gateway, deps = {}) {
602
605
  "A section cannot move into its own subtree.",
603
606
  inputSchema: moveBlockInput,
604
607
  outputSchema: MutationOutputSchema,
608
+ view: "none",
605
609
  describeCall: (input) => `move ${input.targetId}`,
606
610
  execute: async (input, ctx) => {
607
611
  const opening = await openMutation(ctx);
@@ -622,6 +626,7 @@ export function createReportAuthoringTools(gateway, deps = {}) {
622
626
  description: "Set the title of the whole report, not the title of a section. A draft starts with no title, and a document needs one.",
623
627
  inputSchema: setTitleInput,
624
628
  outputSchema: MutationOutputSchema,
629
+ view: "none",
625
630
  describeCall: (input) => `title the report ${input.title}`,
626
631
  // The title sits on the document, thus no container changes its child order.
627
632
  execute: async (input, ctx) => {
@@ -648,6 +653,7 @@ export function createReportAuthoringTools(gateway, deps = {}) {
648
653
  "and read one block when the label is not enough.",
649
654
  inputSchema: readOutlineInput,
650
655
  outputSchema: OutlineOutputSchema,
656
+ view: "none",
651
657
  // The input carries no field, thus a hook can only restate the name of the tool.
652
658
  describeCall: "none",
653
659
  execute: async (_input, ctx) => {
@@ -665,6 +671,7 @@ export function createReportAuthoringTools(gateway, deps = {}) {
665
671
  "because the outline already names every block under it. Use it when the outline label is not enough.",
666
672
  inputSchema: readBlockInput,
667
673
  outputSchema: ReadBlockOutputSchema,
674
+ view: "none",
668
675
  describeCall: (input) => `read ${input.targetId}`,
669
676
  execute: async (input, ctx) => {
670
677
  const opened = await openThread(ctx);
@@ -685,6 +692,7 @@ export function createReportAuthoringTools(gateway, deps = {}) {
685
692
  "preview_report and record_report_version run the same check before they act, thus a finish directly before one of them does that check a second time. " +
686
693
  "Use it to make sure that the draft passes the whole document schema, the id rule, and the structural tier.",
687
694
  inputSchema: finishDraftInput,
695
+ view: "none",
688
696
  // The input carries no field, thus a hook can only restate the name of the tool.
689
697
  describeCall: "none",
690
698
  execute: async (_input, ctx) => {
@@ -254,6 +254,11 @@ export function createExaminePageTool(deps) {
254
254
  "The outcomes no-page and missed-stamp mean that no preview rendered the current page: run preview_report, then look again. " +
255
255
  "The outcome no-browser means that this host gives no browser, and a repeat gives the same outcome.",
256
256
  inputSchema: examinePageInput,
257
+ view: {
258
+ input: (input) => input,
259
+ result: (_input, result) => result,
260
+ error: true,
261
+ },
257
262
  describeCall: (input) => {
258
263
  const blockId = input.blockId ?? undefined;
259
264
  return blockId === undefined ? "look at the page" : `look at ${blockId}`;
@@ -148,6 +148,11 @@ export function createListPinnedArtifactsTool(deps) {
148
148
  "Read the listing to orient before you bind a block. Read the columns of one artifact to choose the column that a locator names. " +
149
149
  "A reference names the path alone: the session stamps the hash from this evidence, thus the tool gives no hash.",
150
150
  inputSchema: listPinnedArtifactsInput,
151
+ view: {
152
+ input: (input) => input,
153
+ result: (_input, result) => result,
154
+ error: true,
155
+ },
151
156
  describeCall: (input) => input.path ?? "the pinned set",
152
157
  // The size of the listing is what a watcher reads, and the cap makes that size differ from the
153
158
  // pinned set. Thus a truncated listing names the total beside the count, and a whole listing names
@@ -376,6 +376,7 @@ export function createPreviewReportTool(deps) {
376
376
  "The render marks the page that examine_page looks at next. " +
377
377
  "Use it to see the report, and to confirm that each reference resolves.",
378
378
  inputSchema: previewReportInput,
379
+ view: "none",
379
380
  describeCall: "none",
380
381
  // The page path is the one fact of a preview that a watcher wants, and it exists only in the
381
382
  // result. Each other arm is a degraded condition whose kind already names it, thus the kind
@@ -138,6 +138,7 @@ export function createRecordVersionTool(deps) {
138
138
  "A thread holds one version, thus a later record replaces it whole. Amend the draft, look at the page again, and record again. " +
139
139
  "After the version lands, the tool removes the file of each derived table that no block of the recorded report binds.",
140
140
  inputSchema: recordVersionInput,
141
+ view: "none",
141
142
  describeCall: "none",
142
143
  // The version id is the one durable product of the first record, thus the created arm names it. A
143
144
  // later record writes over that same id, thus the line names the update and never reads as a
@@ -40,6 +40,7 @@ function createReadTableTool(deps) {
40
40
  "A larger table does not derive; tell the user which table is absent.",
41
41
  inputSchema: readTableInput,
42
42
  outputSchema: ReadTableOutputSchema,
43
+ view: "none",
43
44
  describeCall: ({ path }) => path,
44
45
  describeResult: (_input, result) => `${String(result.rowCount)} rows`,
45
46
  execute: async ({ path, columns }, ctx) => {
@@ -100,6 +101,7 @@ function createWriteDerivedTool(deps) {
100
101
  "Name each pinned table that the script read in `inputs`. A name that this session already holds is refused, thus a second derivation of one table takes a new name.",
101
102
  inputSchema: writeDerivedInput,
102
103
  outputSchema: DerivedOutputSchema,
104
+ view: "none",
103
105
  describeCall: ({ name }) => name,
104
106
  describeResult: (_input, result) => result.path,
105
107
  execute: async ({ name, rows, inputs }, ctx) => {
@@ -51,6 +51,7 @@ export const resolveLibraryIdTool = defineTool({
51
51
  libraryName: z.string().describe("Package or library name to search for (e.g., 'scanpy', 'pysam', 'lifelines')."),
52
52
  query: z.string().describe("What you need help with — used to rank results by relevance (e.g., 'how to run differential expression')."),
53
53
  }),
54
+ view: "none",
54
55
  describeCall: "none",
55
56
  execute: async ({ libraryName, query }) => {
56
57
  const result = await apiFetchValidated(`${CONTEXT7_BASE}/search?query=${encodeURIComponent(libraryName)}&topic=${encodeURIComponent(query)}`, Context7SearchResponseSchema, { maxRetries: 1 });
@@ -80,6 +81,7 @@ export const queryDocsTool = defineTool({
80
81
  libraryId: z.string().describe("Context7 library ID exactly as `resolve_library_id` gave it — an owner and a library, for example '/scverse/scanpy'."),
81
82
  query: z.string().describe("Specific question about the library (e.g., 'rank_genes_groups function parameters and usage')."),
82
83
  }),
84
+ view: "none",
83
85
  describeCall: "none",
84
86
  execute: async ({ libraryId, query }) => {
85
87
  // The library id is the path of the route, not a query parameter. Each
@@ -47,6 +47,7 @@ function createRecordProblemTool() {
47
47
  description: "Record the extraction of phase 1: the problem summary, objects, relations, key terms, and the analogies " +
48
48
  "that phase 2 searches. A later call replaces the record.",
49
49
  inputSchema: RecordProblemInputSchema,
50
+ view: "none",
50
51
  describeCall: "none",
51
52
  execute: () => Promise.resolve(ok({ recorded: true })),
52
53
  });
@@ -57,6 +58,7 @@ function createRecordAnalogyTool() {
57
58
  description: "Record one analogy with its coverage and its cited solutions, when its search is done. A later call " +
58
59
  "with the same analogyTitle replaces the record.",
59
60
  inputSchema: AnalogySchema,
61
+ view: "none",
60
62
  describeCall: ({ analogyTitle }) => analogyTitle,
61
63
  execute: () => Promise.resolve(ok({ recorded: true })),
62
64
  });
@@ -268,6 +270,7 @@ export function createGenerateAnalogyReportTool(deps, registerSubAgent) {
268
270
  "A retry with the same or a similar problem statement meets the same " +
269
271
  "failure, thus surface the message and ask the user to narrow the problem.",
270
272
  inputSchema: generateAnalogyReportInputSchema,
273
+ view: "none",
271
274
  describeCall: "none",
272
275
  startsWorkflow: true,
273
276
  execute: async (input, ctx) => ok(await research(input, ctx)),
@@ -602,6 +602,7 @@ function buildInnerTools(logger, memory) {
602
602
  // structured issue reporting.
603
603
  plan: z.union([PlannerPlanSchema, z.unknown()]).describe("Candidate plan; malformed values are reported in issues."),
604
604
  }),
605
+ view: "none",
605
606
  describeCall: "none",
606
607
  execute: (input) => {
607
608
  // The host owns the grounding fields: it fills them from the last answer of the run.
@@ -639,6 +640,7 @@ function buildInnerTools(logger, memory) {
639
640
  question: terminalTextSchema("One short question that names each missing fact, and the step that needs it."),
640
641
  questionContext: z.string().optional().describe("Optional context: why the plan needs the fact, and what the inputs already say about it."),
641
642
  }),
643
+ view: "none",
642
644
  describeCall: "none",
643
645
  execute: (input) => Promise.resolve(ok({
644
646
  kind: "clarification",
@@ -1181,6 +1183,10 @@ export function createGeneratePlanTool(deps, registerSubAgent) {
1181
1183
  "when the planner is missing a fact it cannot infer; or `error` with {error} when no plan was saved (the planner reported " +
1182
1184
  "a blocker, the save failed, the call timed out or was cancelled, or parentPlanId is not a plan of this analysis).",
1183
1185
  inputSchema: generatePlanInputSchema,
1186
+ view: {
1187
+ input: (input) => input,
1188
+ error: true,
1189
+ },
1184
1190
  describeCall: ({ researchQuestion }) => researchQuestion,
1185
1191
  startsWorkflow: true,
1186
1192
  execute: async (input, ctx) => {
@@ -136,6 +136,11 @@ export function createInspectDataProfileTool(pool) {
136
136
  "against when the input set last changed before you conclude anything: if the data moved since, say the profile " +
137
137
  "needs re-running rather than diagnosing the data itself.",
138
138
  inputSchema: InspectDataProfileInputSchema,
139
+ view: {
140
+ input: (input) => input,
141
+ result: (_input, result) => result,
142
+ error: true,
143
+ },
139
144
  // `execute` defaults both fields internally, and the hook applies the same
140
145
  // defaults. Without them the ordinary call, which names no field at all,
141
146
  // gives no detail. `page` acts only on the `files` scope, as in `execute`.
@@ -109,6 +109,11 @@ export function createInspectRunTool(pool, clock = systemClock) {
109
109
  "For a user-directed check, waitForTerminalSeconds can wait once for 1-30 seconds; if the cutoff returns in_progress, stop polling for that turn and tell the user that the run still runs. " +
110
110
  "Default response is lightweight — pass includeDiagnostics:true to add error/duration/retry telemetry.",
111
111
  inputSchema,
112
+ view: {
113
+ input: (input) => input,
114
+ result: (_input, result) => result,
115
+ error: true,
116
+ },
112
117
  // The two calls do different work: one inspects a named run, the other
113
118
  // pages the list. Naming the run id is what separates repeated polls of
114
119
  // the same run from polls of different ones.
@@ -14,6 +14,7 @@ export function createResolveCitationTool(resolver) {
14
14
  "not_found, unverifiable, or inconclusive. Source failure never becomes not_found — an unreachable authority reports unavailable, and " +
15
15
  "not_found means the authorities that answered hold no such record.",
16
16
  inputSchema: CitationInputSchema,
17
+ view: "none",
17
18
  describeCall: ({ citation }) => `verify ${citation}`,
18
19
  execute: async (input, ctx) => ok(await resolver.resolveOne(input, { signal: ctx.signal })),
19
20
  });
@@ -39,6 +39,7 @@ export const searchArxivTool = defineTool({
39
39
  "and that filter is combined with the query using AND."),
40
40
  limit: z.number().int().min(1).max(20).default(10).describe("Maximum results (1–20, default 10)."),
41
41
  }),
42
+ view: "none",
42
43
  describeCall: "none",
43
44
  execute: async ({ query, categories, limit }, context) => {
44
45
  const result = await source.search({ query, categories, limit }, context.signal);
@@ -87,6 +87,7 @@ export function createSearchGithubReposTool(deps) {
87
87
  "lands here). Report either one and continue, do not retry unchanged.",
88
88
  outputSchema: SearchGithubReposOutputSchema,
89
89
  inputSchema: SearchGithubReposInputSchema,
90
+ view: "none",
90
91
  describeCall: "none",
91
92
  execute: async ({ query, language, limit }) => {
92
93
  const q = language ? `${query} language:${language}` : query;
@@ -46,6 +46,7 @@ export function createSearchSemanticScholarTool(options = {}) {
46
46
  '"reinforcement learning for control" or "feedback stabilisation in oscillators".'),
47
47
  limit: z.number().int().min(1).max(20).default(10).describe("Maximum number of results to return (1–20, default 10)."),
48
48
  }),
49
+ view: "none",
49
50
  describeCall: "none",
50
51
  execute: async ({ query, limit }, context) => {
51
52
  const result = await source.search(query, limit, context.signal);
@@ -614,6 +614,7 @@ export function createWebFetchTool(deps = {}) {
614
614
  "private, link-local, or reserved), http_error, redirect_limit, body_too_large, unextractable, and request_failed each name what " +
615
615
  "happened. Report the status and continue, do not retry unchanged.",
616
616
  inputSchema: WebFetchInputSchema,
617
+ view: "none",
617
618
  describeCall: ({ url }) => url,
618
619
  execute: async ({ url }, ctx) => ok(await fetchOneUrl(url, deps, ctx)),
619
620
  });
@@ -77,6 +77,7 @@ export function createWebSearchTool(deps) {
77
77
  "ABSENCE IS NORMAL: an empty results array means no page matched, and a status of http_error, retryable_status, " +
78
78
  "schema_mismatch, or not_json names a search that did not answer. Report either one and continue, do not retry unchanged.",
79
79
  inputSchema: WebSearchInputSchema,
80
+ view: "none",
80
81
  describeCall: "none",
81
82
  execute: async ({ query, queries, numResults, recencyFilter, domainFilter }) => {
82
83
  const filter = parseDomainFilter(domainFilter);
@@ -24,6 +24,7 @@ export function createAcquireSandboxTool(lease) {
24
24
  "size replaces your sandbox: the files of your working directory stay, and the next command runs on the new " +
25
25
  "size. Your files and your outputs live in the workspace, not in the sandbox.",
26
26
  inputSchema: AcquireSandboxInputSchema,
27
+ view: "none",
27
28
  describeCall: ({ cpu, memoryGb }) => `Waiting for a sandbox (${cpu} CPU, ${memoryGb} GB)`,
28
29
  execute: async ({ cpu, memoryGb, gpuCount }) => {
29
30
  const size = gpuCount === undefined || gpuCount === 0 ? { cpu, memoryGb } : { cpu, memoryGb, gpu: { count: gpuCount } };
@@ -51,6 +52,7 @@ export function createReleaseSandboxTool(lease) {
51
52
  description: "Release your sandbox when your commands are done and you work only with files or research. The files stay. " +
52
53
  "A later `execute_command` needs `acquire_sandbox` again.",
53
54
  inputSchema: ReleaseSandboxInputSchema,
55
+ view: "none",
54
56
  describeCall: () => "Releasing the sandbox",
55
57
  execute: async () => {
56
58
  await lease.release();