@inflexa-ai/harness 0.41.0 → 0.41.1
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.
- package/dist/prompts/briefing.d.ts +28 -1
- package/dist/prompts/briefing.js +44 -1
- package/dist/prompts/compaction.d.ts +2 -2
- package/dist/prompts/compaction.js +9 -5
- package/dist/prompts/sandbox-standards.d.ts +1 -1
- package/dist/prompts/sandbox-standards.js +4 -0
- package/dist/runtime/assemble.d.ts +4 -2
- package/dist/runtime/assemble.js +8 -1
- package/dist/schemas/file-metadata.js +9 -9
- package/dist/schemas/plan-schemas.d.ts +3 -3
- package/dist/tools/ad-hoc-router.js +9 -4
- package/dist/tools/bio/chembl.js +8 -5
- package/dist/tools/bio/comptox.js +4 -4
- package/dist/tools/bio/drug-gene-interactions.js +5 -4
- package/dist/tools/bio/gene-disease-evidence.js +4 -4
- package/dist/tools/bio/lookup-annotation.js +7 -3
- package/dist/tools/bio/opentargets.js +8 -6
- package/dist/tools/bio/pubchem.js +4 -2
- package/dist/tools/bio/pubmed.d.ts +1 -1
- package/dist/tools/bio/pubmed.js +10 -6
- package/dist/tools/bio/search-clinical-trials.js +2 -1
- package/dist/tools/bio/search-gene.js +2 -1
- package/dist/tools/bio/search-interactions.js +8 -4
- package/dist/tools/bio/target-safety.js +2 -1
- package/dist/tools/display/show-user.js +1 -1
- package/dist/tools/execute-analysis.js +14 -4
- package/dist/tools/lib/opentargets-client.d.ts +8 -3
- package/dist/tools/lib/opentargets-client.js +12 -3
- package/dist/tools/read-tool-output.js +7 -4
- package/dist/tools/report-authoring/authoring-tools.js +22 -15
- package/dist/tools/report-session/derive-table.js +5 -1
- package/dist/tools/report-session/examine-page.js +5 -4
- package/dist/tools/report-session/preview-report.js +6 -1
- package/dist/tools/report-session/record-version.js +2 -1
- package/dist/tools/research/context7-docs.js +8 -2
- package/dist/tools/research/generate-analogy-report.js +15 -15
- package/dist/tools/research/generate-plan.js +12 -7
- package/dist/tools/research/inspect-data-profile.js +7 -5
- package/dist/tools/research/inspect-run.js +6 -2
- package/dist/tools/research/literature-reviewer.js +11 -6
- package/dist/tools/research/search-arxiv.js +2 -1
- package/dist/tools/research/search-github-repos.js +9 -3
- package/dist/tools/sandbox/list-available-refs.js +3 -2
- package/dist/tools/sandbox/report-blocker.js +6 -1
- package/dist/tools/sandbox/skills.js +4 -2
- package/dist/tools/start-report-session.js +4 -1
- package/dist/tools/workspace/edit-file.js +4 -3
- package/dist/tools/workspace/execute-command.js +6 -5
- package/dist/tools/workspace/grep.js +9 -7
- package/dist/tools/workspace/list-files.js +1 -1
- package/dist/tools/workspace/read-file.js +2 -1
- package/dist/tools/workspace/scan-inputs.js +3 -1
- package/dist/tools/workspace/show-file.js +2 -0
- package/dist/tools/workspace/workspace-search.js +3 -1
- package/dist/tools/workspace/write-file.js +5 -3
- package/dist/workflows/execute-analysis.d.ts +28 -5
- package/dist/workflows/execute-analysis.js +34 -10
- package/package.json +1 -1
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
* field so the field-coverage guard test fails when a new field is added to the
|
|
22
22
|
* schema without a deliberate decision about whether the agent needs to see it.
|
|
23
23
|
*/
|
|
24
|
+
import { type WorkingMemory } from "../memory/working-memory.js";
|
|
24
25
|
import type { DataProfileResult } from "../state/data-profile.js";
|
|
25
26
|
import type { AnalysisStep } from "../schemas/workflow-state.js";
|
|
26
27
|
/** Completed dependencies rendered as upstream blocks; the tail is counted, not rendered. */
|
|
@@ -50,6 +51,30 @@ export declare const STEP_NON_TASK_FIELDS: readonly ["id", "track", "step_type",
|
|
|
50
51
|
* improvises the rest.
|
|
51
52
|
*/
|
|
52
53
|
export declare function renderTask(step: AnalysisStep): string;
|
|
54
|
+
/**
|
|
55
|
+
* The heading of the analysis-memory section. The orient core of the sandbox
|
|
56
|
+
* prompt names this same text, thus a test pins the two together.
|
|
57
|
+
*/
|
|
58
|
+
export declare const ANALYSIS_MEMORY_HEADING = "Analysis memory (read only)";
|
|
59
|
+
/**
|
|
60
|
+
* The part of the working memory a step agent sees: the goal and the
|
|
61
|
+
* constraints. Hypotheses and findings stay out — they are the interpretive
|
|
62
|
+
* state of the conversation, not a rule for a step.
|
|
63
|
+
*/
|
|
64
|
+
export type StepMemory = Pick<WorkingMemory, "goal" | "constraints">;
|
|
65
|
+
/** A memory with no goal and no constraints — the section renders nothing. */
|
|
66
|
+
export declare function emptyStepMemory(): StepMemory;
|
|
67
|
+
/**
|
|
68
|
+
* The goal and the constraints of the analysis, frozen at dispatch. The heading
|
|
69
|
+
* differs from the "Constraints" section of {@link renderTask}: those are the
|
|
70
|
+
* requirements of this plan step, these are the rules of the whole analysis.
|
|
71
|
+
*
|
|
72
|
+
* Bounded to `WORKING_MEMORY_LIMITS` the same way the chat render is: a row
|
|
73
|
+
* written before the caps existed degrades to its newest constraints, and each
|
|
74
|
+
* text is clamped. No id is rendered, because the step agent cannot address an
|
|
75
|
+
* entry — it has no memory tool.
|
|
76
|
+
*/
|
|
77
|
+
export declare function renderMemory(memory: StepMemory): string;
|
|
53
78
|
/**
|
|
54
79
|
* What anchors every path the agent writes or reads: the id the tree is mounted
|
|
55
80
|
* at, and the step's writable directory.
|
|
@@ -109,10 +134,12 @@ export interface StepBriefing {
|
|
|
109
134
|
readonly profile: DataProfileResult | null;
|
|
110
135
|
/** The step's completed dependencies, in the plan's declared `depends_on` order. */
|
|
111
136
|
readonly upstream: readonly UpstreamHandoff[];
|
|
137
|
+
/** The goal and the constraints of the analysis, read from the working memory at dispatch. */
|
|
138
|
+
readonly memory: StepMemory;
|
|
112
139
|
}
|
|
113
140
|
/**
|
|
114
141
|
* Compose the seed. Absent sections collapse out entirely — an independent step
|
|
115
|
-
* with no profile yields exactly the task and workspace sections, byte-identical
|
|
142
|
+
* with no profile and an empty memory yields exactly the task and workspace sections, byte-identical
|
|
116
143
|
* to what a step got before any of this existed plus its paths.
|
|
117
144
|
*
|
|
118
145
|
* Pure: the same briefing composes to the same string, which is what makes the
|
package/dist/prompts/briefing.js
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
* schema without a deliberate decision about whether the agent needs to see it.
|
|
23
23
|
*/
|
|
24
24
|
import { DATA_PROFILE_ORIENTATION_MAX_CHARS, buildDataProfileOrientation } from "../app/data-profile-orientation.js";
|
|
25
|
+
import { WORKING_MEMORY_LIMITS } from "../memory/working-memory.js";
|
|
25
26
|
// ── Bounds ────────────────────────────────────────────────────────────
|
|
26
27
|
/** Completed dependencies rendered as upstream blocks; the tail is counted, not rendered. */
|
|
27
28
|
export const MAX_UPSTREAM_DEPS = 5;
|
|
@@ -113,6 +114,47 @@ export function renderTask(step) {
|
|
|
113
114
|
}
|
|
114
115
|
return parts.join("\n\n");
|
|
115
116
|
}
|
|
117
|
+
// ── (1b) Analysis memory ──────────────────────────────────────────────
|
|
118
|
+
/**
|
|
119
|
+
* The heading of the analysis-memory section. The orient core of the sandbox
|
|
120
|
+
* prompt names this same text, thus a test pins the two together.
|
|
121
|
+
*/
|
|
122
|
+
export const ANALYSIS_MEMORY_HEADING = "Analysis memory (read only)";
|
|
123
|
+
/** A memory with no goal and no constraints — the section renders nothing. */
|
|
124
|
+
export function emptyStepMemory() {
|
|
125
|
+
return { goal: "", constraints: [] };
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* The goal and the constraints of the analysis, frozen at dispatch. The heading
|
|
129
|
+
* differs from the "Constraints" section of {@link renderTask}: those are the
|
|
130
|
+
* requirements of this plan step, these are the rules of the whole analysis.
|
|
131
|
+
*
|
|
132
|
+
* Bounded to `WORKING_MEMORY_LIMITS` the same way the chat render is: a row
|
|
133
|
+
* written before the caps existed degrades to its newest constraints, and each
|
|
134
|
+
* text is clamped. No id is rendered, because the step agent cannot address an
|
|
135
|
+
* entry — it has no memory tool.
|
|
136
|
+
*/
|
|
137
|
+
export function renderMemory(memory) {
|
|
138
|
+
const goal = clamp(memory.goal.trim(), WORKING_MEMORY_LIMITS.goalChars);
|
|
139
|
+
const all = memory.constraints.filter((c) => c.text.trim().length > 0);
|
|
140
|
+
const shown = all.slice(Math.max(0, all.length - WORKING_MEMORY_LIMITS.constraints));
|
|
141
|
+
if (goal.length === 0 && shown.length === 0)
|
|
142
|
+
return "";
|
|
143
|
+
const parts = [
|
|
144
|
+
"These entries come from the working memory of the analysis, read when this step was dispatched. " +
|
|
145
|
+
"A constraint from the user is binding. A constraint from the agent is context.",
|
|
146
|
+
];
|
|
147
|
+
if (goal.length > 0)
|
|
148
|
+
parts.push(`Goal: ${goal}`);
|
|
149
|
+
if (shown.length > 0) {
|
|
150
|
+
const lines = shown.map((c) => `(${c.origin}) ${clamp(c.text.trim(), WORKING_MEMORY_LIMITS.entryChars)}`);
|
|
151
|
+
const omitted = all.length - shown.length;
|
|
152
|
+
if (omitted > 0)
|
|
153
|
+
lines.push(`(+${omitted} older ${omitted === 1 ? "constraint" : "constraints"} not shown)`);
|
|
154
|
+
parts.push(`Constraints:\n${bullets(lines)}`);
|
|
155
|
+
}
|
|
156
|
+
return section(ANALYSIS_MEMORY_HEADING, parts.join("\n\n"));
|
|
157
|
+
}
|
|
116
158
|
/** The in-sandbox analysis root of a frame — the read-only mount of the whole tree. */
|
|
117
159
|
export function analysisRootOf(frame) {
|
|
118
160
|
return `/${frame.analysisId}`;
|
|
@@ -190,7 +232,7 @@ function renderHandoff(handoff) {
|
|
|
190
232
|
}
|
|
191
233
|
/**
|
|
192
234
|
* Compose the seed. Absent sections collapse out entirely — an independent step
|
|
193
|
-
* with no profile yields exactly the task and workspace sections, byte-identical
|
|
235
|
+
* with no profile and an empty memory yields exactly the task and workspace sections, byte-identical
|
|
194
236
|
* to what a step got before any of this existed plus its paths.
|
|
195
237
|
*
|
|
196
238
|
* Pure: the same briefing composes to the same string, which is what makes the
|
|
@@ -199,6 +241,7 @@ function renderHandoff(handoff) {
|
|
|
199
241
|
export function composeStepBriefing(briefing) {
|
|
200
242
|
return [
|
|
201
243
|
renderTask(briefing.step),
|
|
244
|
+
renderMemory(briefing.memory),
|
|
202
245
|
renderWorkspace(briefing.workspace),
|
|
203
246
|
renderResources(briefing.step.resources),
|
|
204
247
|
renderOrientation(briefing.profile, briefing.workspace.analysisId),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
/** The compaction request of a thread whose agent maintains the working memory. */
|
|
2
|
-
export declare const MEMORY_COMPACTION_REQUEST = "The conversation is too long for the context window. The harness replaces the conversation above with your summary.\n\nFirst, move each lasting fact into the working memory with update_working_memory: the goal, each constraint, each hypothesis, and each finding with its run id. No other tool can run now.\n\nThen reply in plain text with the summary. Leave out
|
|
2
|
+
export declare const MEMORY_COMPACTION_REQUEST = "The conversation is too long for the context window. The harness replaces the conversation above with your summary.\n\nFirst, move each lasting fact into the working memory with update_working_memory: the goal, each constraint, each hypothesis, and each finding with its run id. Keep each constraint that the user gave as it is, because the user set it. No other tool can run now. Send all the memory edits in at most 3 replies, and put many calls in one reply. The reply after the edits must be the summary: if it calls a tool, the harness gets no summary.\n\nThen reply in plain text with the summary. Leave out each fact that a memory edit accepted. If the memory refused an edit, put that fact in the summary. Give:\n- the current request of the user in its exact words\n- the decisions, with their reasons\n- the approaches that failed or that the user ruled out, with the reasons\n- the open questions\n- the file paths, the run ids, and the other exact values that the work uses\n- the state of the work in progress\n\nThe conversation continues from your summary, the working memory, and the messages after them.";
|
|
3
3
|
/** The compaction request of a thread that reads a frozen copy of the working memory, for example a report thread. */
|
|
4
|
-
export declare const SUMMARY_COMPACTION_REQUEST = "The conversation is too long for the context window. The harness replaces the conversation above with your summary. No tool can run now.\n\nReply in plain text with the summary. Leave out what the working memory holds. Give:\n- the goals, and the current request of the user in its exact words\n- the decisions, with their reasons\n- the open questions\n- the file paths
|
|
4
|
+
export declare const SUMMARY_COMPACTION_REQUEST = "The conversation is too long for the context window. The harness replaces the conversation above with your summary. No tool can run now.\n\nReply in plain text with the summary. Leave out what the working memory holds. Give:\n- the goals, and the current request of the user in its exact words\n- each change that the user made to the brief\n- the decisions, with their reasons\n- the approaches that failed or that the user ruled out, with the reasons\n- the open questions\n- the file paths, the run ids, and the other exact values that the work uses\n- the state of the work in progress\n\nThe conversation continues from your summary, the working memory, and the messages after them.";
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
+
import { COMPACTION_MAX_REQUESTS } from "../loop/compaction.js";
|
|
1
2
|
/** The compaction request of a thread whose agent maintains the working memory. */
|
|
2
3
|
export const MEMORY_COMPACTION_REQUEST = `The conversation is too long for the context window. The harness replaces the conversation above with your summary.
|
|
3
4
|
|
|
4
|
-
First, move each lasting fact into the working memory with update_working_memory: the goal, each constraint, each hypothesis, and each finding with its run id. No other tool can run now.
|
|
5
|
+
First, move each lasting fact into the working memory with update_working_memory: the goal, each constraint, each hypothesis, and each finding with its run id. Keep each constraint that the user gave as it is, because the user set it. No other tool can run now. Send all the memory edits in at most ${COMPACTION_MAX_REQUESTS - 1} replies, and put many calls in one reply. The reply after the edits must be the summary: if it calls a tool, the harness gets no summary.
|
|
5
6
|
|
|
6
|
-
Then reply in plain text with the summary. Leave out
|
|
7
|
-
- the
|
|
7
|
+
Then reply in plain text with the summary. Leave out each fact that a memory edit accepted. If the memory refused an edit, put that fact in the summary. Give:
|
|
8
|
+
- the current request of the user in its exact words
|
|
8
9
|
- the decisions, with their reasons
|
|
10
|
+
- the approaches that failed or that the user ruled out, with the reasons
|
|
9
11
|
- the open questions
|
|
10
|
-
- the file paths
|
|
12
|
+
- the file paths, the run ids, and the other exact values that the work uses
|
|
11
13
|
- the state of the work in progress
|
|
12
14
|
|
|
13
15
|
The conversation continues from your summary, the working memory, and the messages after them.`;
|
|
@@ -16,9 +18,11 @@ export const SUMMARY_COMPACTION_REQUEST = `The conversation is too long for the
|
|
|
16
18
|
|
|
17
19
|
Reply in plain text with the summary. Leave out what the working memory holds. Give:
|
|
18
20
|
- the goals, and the current request of the user in its exact words
|
|
21
|
+
- each change that the user made to the brief
|
|
19
22
|
- the decisions, with their reasons
|
|
23
|
+
- the approaches that failed or that the user ruled out, with the reasons
|
|
20
24
|
- the open questions
|
|
21
|
-
- the file paths
|
|
25
|
+
- the file paths, the run ids, and the other exact values that the work uses
|
|
22
26
|
- the state of the work in progress
|
|
23
27
|
|
|
24
28
|
The conversation continues from your summary, the working memory, and the messages after them.`;
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
* each composition stays byte-stable across its own steps.
|
|
30
30
|
*/
|
|
31
31
|
import type { ToolchainSource } from "../sandbox/types.js";
|
|
32
|
-
export declare const sandboxOrientCorePrompt = "# Sandbox Orient-Core\n\nThese apply to every agent that talks to a sandbox.\n\n## Your Briefing Is Authoritative\n\nYour first message is your briefing, composed for this step at the instant it was\ndispatched \u2014 after every step it depends on had finished. It names your task, your\nworking directory and the read-only analysis root, what the input dataset is\n(domain, organism, design, quality concerns, files), and what each upstream step\nproduced and where it put it. Work from it.\n\nDo NOT re-derive what it already gives you: no filesystem hunt for your inputs, no\nre-reading an upstream step whose summary you were handed, no re-deriving the\norganism or the dimensions from raw bytes. Reach further only where the briefing is\ngenuinely thin for what you must do:\n\n- `inspect_data_profile` \u2014 the full dataset profile: `scope:'overview'` for the\n identity and the file census, `scope:'groups'` for the groups and the dimensions\n that vary across them, `scope:'files'` for the members the profiler annotated\n individually. It is the ONLY record of what the input data IS; no file on disk\n carries it. Use it when the briefing's orientation is not enough, or when it\n carried none.\n- `read_file` on an upstream step's summary \u2014 its path is in your briefing \u2014 when\n the excerpt you were handed is not enough to build on.\n- `workspace_search({ query })` \u2014 natural-language search for a file the briefing\n does not name. Optional `type` filter (`input-group` | `input-dimension` |\n `input` | `output` | `summary` | `synthesis`) and `limit` (1-50, default 8).\n Returns paths, descriptions, and metadata \u2014 not file contents. Most input files\n carry no entry of their own; reach them through their group and `list_files`.\n\n## Your Workspace\n\nYour **working directory** \u2014 writable, and your cwd \u2014 and the read-only\n**analysis root** are named in your briefing. Use those paths exactly; do not\ninvent or guess one.\n\n- **Relative paths resolve against your working directory** \u2014 in `read_file`,\n `list_files`, `file_stat`, `grep`, `write_file`, `edit_file`,\n `execute_command`, and any script you run. `output/de.csv` means\n `<working directory>/output/de.csv` everywhere \u2014 a file you write at a\n relative path is read back at the same relative path.\n- **You may write only inside your working directory.** A write outside it\n comes back as an `out_of_prefix` result (no I/O performed); fix the path.\n- The rest of the analysis is mounted **read-only** at the analysis root. Reach\n input data (under `data/inputs/`) and other steps' outputs (under `runs/`)\n with an **absolute** path beneath it.\n- **Absolute analysis-root paths are canonical** \u2014 use one whenever you\n reference a file you didn't create in this step.\n\n`write_file` creates parent directories as needed \u2014 no separate mkdir.\n\n## Scope \u2014 Analysis Starts at Processed Data\n\nThis environment does **tertiary analysis**: turning processed data into\nbiological findings. Read processing \u2014 alignment, quantification, variant\ncalling, peak calling \u2014 belongs upstream, wherever the instrument output was\nhandled. No tool for it is installed, and none can be.\n\nYour inputs are count and expression matrices, called variants, peak sets,\nsignal tracks, and the metadata describing them. When a request arrives as raw\nreads or unprocessed instrument output, name the processed form you need and\nstop. Do not improvise a pipeline, do not substitute a Python reimplementation\nof an aligner or peak caller, and do not treat a missing upstream tool as an\nobstacle to route around \u2014 it is the boundary of the product, not a gap.\n\nData that has already been processed can still be **read**: `samtools`,\n`bcftools`, `tabix`, `bedtools` and `vcftools` are installed so you can\ninspect, subset, and convert existing BAM/CRAM, VCF and BED. Reading evidence at\nsites you already have is in scope; producing the calls is not.\n\n## Environment \u2014 No Network, No Installs\n\n**There is no egress from this sandbox.** Outbound connections are blocked at the\nfirewall, not merely discouraged: every download, API call, package install, and\n`ExperimentHub`/`AnnotationHub`/`models.download_*` style fetch fails outright.\nThere is no proxy, no mirror, and no retry that succeeds. Packages and reference\ndata were staged onto read-only mounts by the host **before** this container\nstarted, and that is the whole of what exists. Work that genuinely requires the\ninternet \u2014 fetching a dataset, querying an external API, literature lookup \u2014 is\ndone **outside** the sandbox through the host's tools, not from your scripts.\n\nSo: check what you need when you need it \u2014 a targeted lookup, never a catalog dump\nup front.\n\n- **Packages** \u2014 before importing a package you are not certain is present, look it\n up with `list_available_packages`, narrowed to the packages you actually intend\n to import. Importing one that isn't installed fails the script; the lookup costs\n one call.\n- **Reference data** \u2014 `list_available_refs` resolves reference data into a real\n path against the store mounted read-only at `/mnt/refs`. **Search by what the\n data IS, not by where you expect it** (\"TF-target regulons\", \"hallmark gene\n sets\", \"mouse\"): the store's directory layout is an installer detail, it differs\n between hosts, and no path you have seen before is guaranteed to exist here.\n Catalogued entries come back with the organism, the format, and the file's\n internal shape, so read the entry and use the reader and species it names rather\n than assuming \u2014 formats vary per dataset, and there is no guarantee any given\n collection ships in the format your tool prefers.\n The store is OPTIONAL and may be absent or hold none of what you want \u2014 a normal\n state, not an error. Never assume a reference path exists and never hardcode one:\n if a reference you need is not in the inventory, say so plainly and proceed with\n what you do have (or state what must be provisioned) rather than inventing a path\n or substituting something else unannounced. Pass reference paths EXPLICITLY to the\n library \u2014 nothing in the image points a library at the store, so a library that\n resolves data by name from an env var (CellTypist reads `$CELLTYPIST_FOLDER`)\n will not find it unless you set that variable in `execute_command`'s `env`\n for the command that needs it, using a path the inventory actually returned.\n- Do NOT call a library's built-in data fetcher \u2014 `dc.op.collectri()`,\n `dc.op.progeny()`, `dc.op.msigdb()` and every other `dc.op.*()`, CellTypist's\n `models.download_models()`, gseapy's Enrichr library names, `ExperimentHub`/\n `AnnotationHub` \u2014 they all reach for the network and will fail. Load from an\n inventory path instead. Do NOT download data or install packages at runtime.\n\n## CPU Budget and Parallelism\n\nYour briefing names this step's CPU quota and memory limit. The container's\ncore-count calls (`parallel::detectCores()`, `os.cpu_count()`) report the\nquota, so worker counts sized from them are safe.\n\nBLAS/OpenMP thread pools default to **1 thread** (`OMP_NUM_THREADS`,\n`OPENBLAS_NUM_THREADS`, `MKL_NUM_THREADS`, \u2026). A forked worker inherits its\nparent's pool size, so this default is what keeps `mclapply` /\n`multiprocessing` from running workers \u00D7 threads on a quota sized for one of\nthem. The rule: **workers \u00D7 threads-per-worker must not exceed the quota.** A\nmix is fine while the product stays inside it.\n\n- **Fork/worker parallelism** (`mclapply`, `BiocParallel`, `joblib`,\n `multiprocessing`): size the workers from the reported core count and leave\n the thread defaults alone \u2014 there is nothing to raise. `joblib` also\n divides its own thread budget across its workers; do not manage it.\n- **Thread parallelism** (one process: xgboost, WGCNA, data.table, polars, a\n single large BLAS fit): raise the thread variables for that command through\n `execute_command`'s `env`, e.g.\n `env: { \"OMP_NUM_THREADS\": \"4\", \"OPENBLAS_NUM_THREADS\": \"4\" }` on a 4-CPU\n quota.\n\nExceeding the quota does not kill the step \u2014 the cgroup throttles it \u2014 but an\noversubscribed step runs slower and can starve the exec server that runs your\ncommands.\n\n## Context7 \u2014 Documentation Lookup\n\nWhen you are not certain of a function's signature or behavior, look it up\nwith context7 before you write the call: `resolve_library_id` with the\npackage name, then `query_docs` with the library ID and the function.\nRapidly-evolving packages (scvi-tools, spatialdata, cellrank, pertpy, muon)\nchange their APIs between releases, so check them against the docs or the\nskill references rather than memory.\n\n## Command Execution\n\n`execute_command` runs analysis work \u2014 scripts, bioinformatics CLI tools,\nshell pipes, and anything the workspace tools don't express. It starts in your\nworking directory; a relative `cwd` argument is resolved against it.\nIts `command` is an argv array. A one-element array runs through `sh -c`,\nso pipes, `&&`, redirection, quoting, and globbing work there. An array of\ntwo or more elements runs directly, with no shell.\n\nFor these tasks, prefer the dedicated workspace tools \u2014 they are faster than\nshelling out and don't waste a turn on path discovery:\n\n| Task | Use this tool | Instead of |\n|-|-|-|\n| List files in a directory | `list_files` | `ls` / `ls -la` |\n| Read a text/source/result file | `read_file` | `cat` / `less` |\n| Search file contents by pattern | `grep` | shell `grep` / `rg` |\n| Size / type of a path | `file_stat` | `stat` / `wc -c` |\n\n**Use `execute_command` for everything else**, including:\n\n- **Running scripts** \u2014 `python scripts/run.py`, `Rscript scripts/de.R`,\n `ruff check scripts/foo.py`.\n- **Previewing huge / binary files** \u2014 `wc -l file.tsv`, `zcat file.gz | head`.\n For text files, prefer `read_file` with `headLines` / `tailLines`.\n- **Shell pipes / chaining** \u2014 `sort | uniq -c | sort -rn | head`.\n- **`find`** \u2014 a recursive search by name, age, or size: `find . -name '*.h5ad'`,\n `find . -size +10M`. `list_files` lists one directory only.\n- **Real CLI tools** \u2014 `samtools`, `bcftools`, `tabix`, `bedtools`,\n `vcftools`, version probes like `python -c \"import x; print(x.__version__)\"`.\n\n**Shell behavior to know:**\n\n- `cd` does NOT persist across calls \u2014 each exec starts in your working\n directory. For cross-call state, pass `cwd` or chain with `&&` in one exec.\n- Quote paths with spaces: `head \"data/inputs/My Folder/file.csv\"`. In Python,\n use `pathlib.Path` or `os.path`.\n\n## Editing Files\n\n`edit_file` replaces `old_string` with `new_string`. Read the file first to\nget the exact text. When `replace_all` is false (default), `old_string` must\noccur exactly once \u2014 include surrounding context to make it unique.\n";
|
|
32
|
+
export declare const sandboxOrientCorePrompt = "# Sandbox Orient-Core\n\nThese apply to every agent that talks to a sandbox.\n\n## Your Briefing Is Authoritative\n\nYour first message is your briefing, composed for this step at the instant it was\ndispatched \u2014 after every step it depends on had finished. It names your task, your\nworking directory and the read-only analysis root, what the input dataset is\n(domain, organism, design, quality concerns, files), and what each upstream step\nproduced and where it put it. Work from it.\n\nWhen the analysis has a recorded goal or constraints, the briefing carries them\nunder \"Analysis memory (read only)\": obey each constraint from the user, and treat\neach constraint from the agent as context. You cannot change that memory.\n\nDo NOT re-derive what it already gives you: no filesystem hunt for your inputs, no\nre-reading an upstream step whose summary you were handed, no re-deriving the\norganism or the dimensions from raw bytes. Reach further only where the briefing is\ngenuinely thin for what you must do:\n\n- `inspect_data_profile` \u2014 the full dataset profile: `scope:'overview'` for the\n identity and the file census, `scope:'groups'` for the groups and the dimensions\n that vary across them, `scope:'files'` for the members the profiler annotated\n individually. It is the ONLY record of what the input data IS; no file on disk\n carries it. Use it when the briefing's orientation is not enough, or when it\n carried none.\n- `read_file` on an upstream step's summary \u2014 its path is in your briefing \u2014 when\n the excerpt you were handed is not enough to build on.\n- `workspace_search({ query })` \u2014 natural-language search for a file the briefing\n does not name. Optional `type` filter (`input-group` | `input-dimension` |\n `input` | `output` | `summary` | `synthesis`) and `limit` (1-50, default 8).\n Returns paths, descriptions, and metadata \u2014 not file contents. Most input files\n carry no entry of their own; reach them through their group and `list_files`.\n\n## Your Workspace\n\nYour **working directory** \u2014 writable, and your cwd \u2014 and the read-only\n**analysis root** are named in your briefing. Use those paths exactly; do not\ninvent or guess one.\n\n- **Relative paths resolve against your working directory** \u2014 in `read_file`,\n `list_files`, `file_stat`, `grep`, `write_file`, `edit_file`,\n `execute_command`, and any script you run. `output/de.csv` means\n `<working directory>/output/de.csv` everywhere \u2014 a file you write at a\n relative path is read back at the same relative path.\n- **You may write only inside your working directory.** A write outside it\n comes back as an `out_of_prefix` result (no I/O performed); fix the path.\n- The rest of the analysis is mounted **read-only** at the analysis root. Reach\n input data (under `data/inputs/`) and other steps' outputs (under `runs/`)\n with an **absolute** path beneath it.\n- **Absolute analysis-root paths are canonical** \u2014 use one whenever you\n reference a file you didn't create in this step.\n\n`write_file` creates parent directories as needed \u2014 no separate mkdir.\n\n## Scope \u2014 Analysis Starts at Processed Data\n\nThis environment does **tertiary analysis**: turning processed data into\nbiological findings. Read processing \u2014 alignment, quantification, variant\ncalling, peak calling \u2014 belongs upstream, wherever the instrument output was\nhandled. No tool for it is installed, and none can be.\n\nYour inputs are count and expression matrices, called variants, peak sets,\nsignal tracks, and the metadata describing them. When a request arrives as raw\nreads or unprocessed instrument output, name the processed form you need and\nstop. Do not improvise a pipeline, do not substitute a Python reimplementation\nof an aligner or peak caller, and do not treat a missing upstream tool as an\nobstacle to route around \u2014 it is the boundary of the product, not a gap.\n\nData that has already been processed can still be **read**: `samtools`,\n`bcftools`, `tabix`, `bedtools` and `vcftools` are installed so you can\ninspect, subset, and convert existing BAM/CRAM, VCF and BED. Reading evidence at\nsites you already have is in scope; producing the calls is not.\n\n## Environment \u2014 No Network, No Installs\n\n**There is no egress from this sandbox.** Outbound connections are blocked at the\nfirewall, not merely discouraged: every download, API call, package install, and\n`ExperimentHub`/`AnnotationHub`/`models.download_*` style fetch fails outright.\nThere is no proxy, no mirror, and no retry that succeeds. Packages and reference\ndata were staged onto read-only mounts by the host **before** this container\nstarted, and that is the whole of what exists. Work that genuinely requires the\ninternet \u2014 fetching a dataset, querying an external API, literature lookup \u2014 is\ndone **outside** the sandbox through the host's tools, not from your scripts.\n\nSo: check what you need when you need it \u2014 a targeted lookup, never a catalog dump\nup front.\n\n- **Packages** \u2014 before importing a package you are not certain is present, look it\n up with `list_available_packages`, narrowed to the packages you actually intend\n to import. Importing one that isn't installed fails the script; the lookup costs\n one call.\n- **Reference data** \u2014 `list_available_refs` resolves reference data into a real\n path against the store mounted read-only at `/mnt/refs`. **Search by what the\n data IS, not by where you expect it** (\"TF-target regulons\", \"hallmark gene\n sets\", \"mouse\"): the store's directory layout is an installer detail, it differs\n between hosts, and no path you have seen before is guaranteed to exist here.\n Catalogued entries come back with the organism, the format, and the file's\n internal shape, so read the entry and use the reader and species it names rather\n than assuming \u2014 formats vary per dataset, and there is no guarantee any given\n collection ships in the format your tool prefers.\n The store is OPTIONAL and may be absent or hold none of what you want \u2014 a normal\n state, not an error. Never assume a reference path exists and never hardcode one:\n if a reference you need is not in the inventory, say so plainly and proceed with\n what you do have (or state what must be provisioned) rather than inventing a path\n or substituting something else unannounced. Pass reference paths EXPLICITLY to the\n library \u2014 nothing in the image points a library at the store, so a library that\n resolves data by name from an env var (CellTypist reads `$CELLTYPIST_FOLDER`)\n will not find it unless you set that variable in `execute_command`'s `env`\n for the command that needs it, using a path the inventory actually returned.\n- Do NOT call a library's built-in data fetcher \u2014 `dc.op.collectri()`,\n `dc.op.progeny()`, `dc.op.msigdb()` and every other `dc.op.*()`, CellTypist's\n `models.download_models()`, gseapy's Enrichr library names, `ExperimentHub`/\n `AnnotationHub` \u2014 they all reach for the network and will fail. Load from an\n inventory path instead. Do NOT download data or install packages at runtime.\n\n## CPU Budget and Parallelism\n\nYour briefing names this step's CPU quota and memory limit. The container's\ncore-count calls (`parallel::detectCores()`, `os.cpu_count()`) report the\nquota, so worker counts sized from them are safe.\n\nBLAS/OpenMP thread pools default to **1 thread** (`OMP_NUM_THREADS`,\n`OPENBLAS_NUM_THREADS`, `MKL_NUM_THREADS`, \u2026). A forked worker inherits its\nparent's pool size, so this default is what keeps `mclapply` /\n`multiprocessing` from running workers \u00D7 threads on a quota sized for one of\nthem. The rule: **workers \u00D7 threads-per-worker must not exceed the quota.** A\nmix is fine while the product stays inside it.\n\n- **Fork/worker parallelism** (`mclapply`, `BiocParallel`, `joblib`,\n `multiprocessing`): size the workers from the reported core count and leave\n the thread defaults alone \u2014 there is nothing to raise. `joblib` also\n divides its own thread budget across its workers; do not manage it.\n- **Thread parallelism** (one process: xgboost, WGCNA, data.table, polars, a\n single large BLAS fit): raise the thread variables for that command through\n `execute_command`'s `env`, e.g.\n `env: { \"OMP_NUM_THREADS\": \"4\", \"OPENBLAS_NUM_THREADS\": \"4\" }` on a 4-CPU\n quota.\n\nExceeding the quota does not kill the step \u2014 the cgroup throttles it \u2014 but an\noversubscribed step runs slower and can starve the exec server that runs your\ncommands.\n\n## Context7 \u2014 Documentation Lookup\n\nWhen you are not certain of a function's signature or behavior, look it up\nwith context7 before you write the call: `resolve_library_id` with the\npackage name, then `query_docs` with the library ID and the function.\nRapidly-evolving packages (scvi-tools, spatialdata, cellrank, pertpy, muon)\nchange their APIs between releases, so check them against the docs or the\nskill references rather than memory.\n\n## Command Execution\n\n`execute_command` runs analysis work \u2014 scripts, bioinformatics CLI tools,\nshell pipes, and anything the workspace tools don't express. It starts in your\nworking directory; a relative `cwd` argument is resolved against it.\nIts `command` is an argv array. A one-element array runs through `sh -c`,\nso pipes, `&&`, redirection, quoting, and globbing work there. An array of\ntwo or more elements runs directly, with no shell.\n\nFor these tasks, prefer the dedicated workspace tools \u2014 they are faster than\nshelling out and don't waste a turn on path discovery:\n\n| Task | Use this tool | Instead of |\n|-|-|-|\n| List files in a directory | `list_files` | `ls` / `ls -la` |\n| Read a text/source/result file | `read_file` | `cat` / `less` |\n| Search file contents by pattern | `grep` | shell `grep` / `rg` |\n| Size / type of a path | `file_stat` | `stat` / `wc -c` |\n\n**Use `execute_command` for everything else**, including:\n\n- **Running scripts** \u2014 `python scripts/run.py`, `Rscript scripts/de.R`,\n `ruff check scripts/foo.py`.\n- **Previewing huge / binary files** \u2014 `wc -l file.tsv`, `zcat file.gz | head`.\n For text files, prefer `read_file` with `headLines` / `tailLines`.\n- **Shell pipes / chaining** \u2014 `sort | uniq -c | sort -rn | head`.\n- **`find`** \u2014 a recursive search by name, age, or size: `find . -name '*.h5ad'`,\n `find . -size +10M`. `list_files` lists one directory only.\n- **Real CLI tools** \u2014 `samtools`, `bcftools`, `tabix`, `bedtools`,\n `vcftools`, version probes like `python -c \"import x; print(x.__version__)\"`.\n\n**Shell behavior to know:**\n\n- `cd` does NOT persist across calls \u2014 each exec starts in your working\n directory. For cross-call state, pass `cwd` or chain with `&&` in one exec.\n- Quote paths with spaces: `head \"data/inputs/My Folder/file.csv\"`. In Python,\n use `pathlib.Path` or `os.path`.\n\n## Editing Files\n\n`edit_file` replaces `old_string` with `new_string`. Read the file first to\nget the exact text. When `replace_all` is false (default), `old_string` must\noccur exactly once \u2014 include surrounding context to make it unique.\n";
|
|
33
33
|
/**
|
|
34
34
|
* The orient-core layer for one declared toolchain. `"image"` states that an
|
|
35
35
|
* acquisition is a host action and directs the agent to report a missing
|
|
@@ -40,6 +40,10 @@ working directory and the read-only analysis root, what the input dataset is
|
|
|
40
40
|
(domain, organism, design, quality concerns, files), and what each upstream step
|
|
41
41
|
produced and where it put it. Work from it.
|
|
42
42
|
|
|
43
|
+
When the analysis has a recorded goal or constraints, the briefing carries them
|
|
44
|
+
under "Analysis memory (read only)": obey each constraint from the user, and treat
|
|
45
|
+
each constraint from the agent as context. You cannot change that memory.
|
|
46
|
+
|
|
43
47
|
Do NOT re-derive what it already gives you: no filesystem hunt for your inputs, no
|
|
44
48
|
re-reading an upstream step whose summary you were handed, no re-deriving the
|
|
45
49
|
organism or the dimensions from raw bytes. Reach further only where the briefing is
|
|
@@ -50,11 +50,13 @@ export type SandboxStepCallable = (input: SandboxStepInput) => Promise<SandboxSt
|
|
|
50
50
|
* conversation agent does not — a half-wired ledger that reads as a complete
|
|
51
51
|
* one. The protection has to hold on both sides of the assembly, not just the
|
|
52
52
|
* conversation side. Each bag omits `toolOutputStore` for the same reason: each
|
|
53
|
-
* loop must keep its texts in the one store that each read tool reads.
|
|
53
|
+
* loop must keep its texts in the one store that each read tool reads. The
|
|
54
|
+
* `executeAnalysis` bag omits `workingMemory`, because the step seeds must read
|
|
55
|
+
* the same memory that the conversation agent writes.
|
|
54
56
|
*/
|
|
55
57
|
export interface CoreWorkflowDeps {
|
|
56
58
|
readonly sandboxStep: Omit<SandboxStepDeps, "usageRecorder" | "citationResolver" | "toolOutputStore">;
|
|
57
|
-
readonly buildExecuteAnalysis: (sandboxStep: SandboxStepCallable) => Omit<ExecuteAnalysisDeps, "usageRecorder" | "citationResolver" | "toolOutputStore">;
|
|
59
|
+
readonly buildExecuteAnalysis: (sandboxStep: SandboxStepCallable) => Omit<ExecuteAnalysisDeps, "usageRecorder" | "citationResolver" | "toolOutputStore" | "workingMemory">;
|
|
58
60
|
readonly dataProfile: Omit<DataProfileDeps, "usageRecorder" | "toolOutputStore">;
|
|
59
61
|
}
|
|
60
62
|
/** The registered, callable workflow handles. */
|
package/dist/runtime/assemble.js
CHANGED
|
@@ -28,6 +28,7 @@ import { bindExtractionTrigger, createExtractionArm, registerExtractValuesWorkfl
|
|
|
28
28
|
import { registerDeriveTableExecWorkflow, triggerDeriveTableExec } from "../tasks/derive-table-exec.js";
|
|
29
29
|
import { createCitationResolver } from "../citations/resolve.js";
|
|
30
30
|
import { createThreadStore } from "../memory/thread-store.js";
|
|
31
|
+
import { createWorkingMemory } from "../memory/working-memory.js";
|
|
31
32
|
import { createArtifactReadStore, createProductionResolver } from "../report-model/production-resolver.js";
|
|
32
33
|
import { createReportVersionStore } from "../state/report-versions.js";
|
|
33
34
|
import { createToolOutputStore } from "../state/tool-outputs.js";
|
|
@@ -86,7 +87,13 @@ export function assembleCoreRuntime(deps) {
|
|
|
86
87
|
});
|
|
87
88
|
const toolOutputStore = createToolOutputStore(conversation.pool);
|
|
88
89
|
const sandboxStep = registerSandboxStep({ ...wf.sandboxStep, citationResolver, usageRecorder, toolOutputStore });
|
|
89
|
-
const executeAnalysis = registerExecuteAnalysis({
|
|
90
|
+
const executeAnalysis = registerExecuteAnalysis({
|
|
91
|
+
...wf.buildExecuteAnalysis(sandboxStep),
|
|
92
|
+
citationResolver,
|
|
93
|
+
usageRecorder,
|
|
94
|
+
toolOutputStore,
|
|
95
|
+
workingMemory: createWorkingMemory(conversation.pool),
|
|
96
|
+
});
|
|
90
97
|
const dataProfile = registerDataProfileWorkflow({ ...wf.dataProfile, usageRecorder, toolOutputStore });
|
|
91
98
|
// The extraction workflow shares the profile's sandbox and authorization rails, thus it draws the same
|
|
92
99
|
// three seams from the profile deps. The report resolver factory binds the extraction arm over this
|
|
@@ -24,16 +24,16 @@ export const FileMetadataSchema = z
|
|
|
24
24
|
*/
|
|
25
25
|
export const SubmittedFileDescriptionSchema = z.object({
|
|
26
26
|
/** Exact path of the file being described, copied from the prompt's list. */
|
|
27
|
-
path: z.string(),
|
|
28
|
-
description: z.string(),
|
|
29
|
-
dataType: z.string(),
|
|
30
|
-
format: z.string(),
|
|
31
|
-
rows: z.number().nullish(),
|
|
32
|
-
cols: z.number().nullish(),
|
|
33
|
-
tags: z.array(z.string()).optional(),
|
|
34
|
-
warnings: z.array(z.string()).optional(),
|
|
27
|
+
path: z.string().describe("The path of the file, copied exactly from the list of files that the harness gave."),
|
|
28
|
+
description: z.string().describe("One sentence that says what the file contains. A semantic search of the workspace later finds the file by this text."),
|
|
29
|
+
dataType: z.string().describe('The semantic type of the data, for example "count matrix" or "QC report".'),
|
|
30
|
+
format: z.string().describe('The file format, for example "csv", "tsv", "h5ad", or "png".'),
|
|
31
|
+
rows: z.number().nullish().describe("The count of rows, when you know it."),
|
|
32
|
+
cols: z.number().nullish().describe("The count of columns, when you know it."),
|
|
33
|
+
tags: z.array(z.string()).optional().describe("Short labels that help a search find the file."),
|
|
34
|
+
warnings: z.array(z.string()).optional().describe("Known problems of the file that a reader must know before use."),
|
|
35
35
|
});
|
|
36
36
|
/** Input contract for the `submit_file_metadata` tool. */
|
|
37
37
|
export const SubmitFileMetadataInputSchema = z.object({
|
|
38
|
-
files: z.array(SubmittedFileDescriptionSchema),
|
|
38
|
+
files: z.array(SubmittedFileDescriptionSchema).describe("One entry per output file. You can cover the files in one call or in several."),
|
|
39
39
|
});
|
|
@@ -11,10 +11,10 @@ export declare const PlanStepSchema: z.ZodObject<{
|
|
|
11
11
|
context: z.ZodOptional<z.ZodString>;
|
|
12
12
|
description: z.ZodOptional<z.ZodString>;
|
|
13
13
|
id: z.ZodString;
|
|
14
|
+
constraints: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
14
15
|
track: z.ZodString;
|
|
15
16
|
step_type: z.ZodString;
|
|
16
17
|
question: z.ZodString;
|
|
17
|
-
constraints: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
18
18
|
acceptance_criteria: z.ZodArray<z.ZodString>;
|
|
19
19
|
caveats: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
20
20
|
packages: z.ZodNonOptional<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
@@ -44,10 +44,10 @@ export declare const PlannerPlanSchema: z.ZodObject<{
|
|
|
44
44
|
context: z.ZodOptional<z.ZodString>;
|
|
45
45
|
description: z.ZodOptional<z.ZodString>;
|
|
46
46
|
id: z.ZodString;
|
|
47
|
+
constraints: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
47
48
|
track: z.ZodString;
|
|
48
49
|
step_type: z.ZodString;
|
|
49
50
|
question: z.ZodString;
|
|
50
|
-
constraints: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
51
51
|
acceptance_criteria: z.ZodArray<z.ZodString>;
|
|
52
52
|
caveats: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
53
53
|
packages: z.ZodNonOptional<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
@@ -86,10 +86,10 @@ export declare const PlanningAgentOutputSchema: z.ZodObject<{
|
|
|
86
86
|
context: z.ZodOptional<z.ZodString>;
|
|
87
87
|
description: z.ZodOptional<z.ZodString>;
|
|
88
88
|
id: z.ZodString;
|
|
89
|
+
constraints: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
89
90
|
track: z.ZodString;
|
|
90
91
|
step_type: z.ZodString;
|
|
91
92
|
question: z.ZodString;
|
|
92
|
-
constraints: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
93
93
|
acceptance_criteria: z.ZodArray<z.ZodString>;
|
|
94
94
|
caveats: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
95
95
|
packages: z.ZodNonOptional<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
@@ -23,10 +23,15 @@ const resourcesSchema = z.object({
|
|
|
23
23
|
gpu: z.object({ count: z.number().int().positive() }).optional(),
|
|
24
24
|
});
|
|
25
25
|
const routeSchema = z.object({
|
|
26
|
-
agentId: z.string().optional(),
|
|
27
|
-
resources: resourcesSchema
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
agentId: z.string().optional().describe("The id of one specialist from the eligible catalog, copied exactly."),
|
|
27
|
+
resources: resourcesSchema
|
|
28
|
+
.optional()
|
|
29
|
+
.describe("The sandbox resources of the step, inside the stated ranges. Omit to take the defaults; a value outside the ranges is replaced by the defaults."),
|
|
30
|
+
rationale: z.string().optional().describe("One or two sentences on why this specialist fits the request. The step agent reads it."),
|
|
31
|
+
packages: z
|
|
32
|
+
.array(z.string())
|
|
33
|
+
.optional()
|
|
34
|
+
.describe("The packages that the scripts of the step import, each as `[python:|r:]<name>[==<version>]`. An empty array is valid."),
|
|
30
35
|
});
|
|
31
36
|
export function defaultAdHocResources(policy) {
|
|
32
37
|
return policy ? { cpu: Math.min(4, policy.perStep.maxCpu), memoryGb: Math.min(8, policy.perStep.maxMemoryGb) } : { cpu: 4, memoryGb: 8 };
|
package/dist/tools/bio/chembl.js
CHANGED
|
@@ -38,10 +38,11 @@ const inputSchema = z
|
|
|
38
38
|
.describe("Which ChEMBL lookup to run; each names its params and return fields.\n" +
|
|
39
39
|
"'compounds' (query + searchType) — molecules by target, name or SMILES: resolve a named compound to its ID and structure, or list " +
|
|
40
40
|
"what was assayed against a target. → chemblId, preferredCompoundName, canonicalSmiles, molecularWeight, alogp, molecularFormula.\n" +
|
|
41
|
-
"'drug' (query) — approved drugs by
|
|
42
|
-
"preferredName, maxPhase (4 = approved), moleculeType, firstApproval, indication.
|
|
43
|
-
"
|
|
44
|
-
"
|
|
41
|
+
"'drug' (query) — approved drugs by drug name or by indication: 'is Y approved, since when?', 'what treats X?'. → moleculeChemblId, " +
|
|
42
|
+
"preferredName, maxPhase (4 = approved), moleculeType, firstApproval, indication. The query is read as a molecule name first: the " +
|
|
43
|
+
"approved molecules (max_phase 4) that it names come back with their indications. A query that names no approved molecule is then " +
|
|
44
|
+
"read as a disease term against the curated indication registry, highest phase first, and those rows can hold clinical-stage " +
|
|
45
|
+
"molecules. Thus a clinical-stage compound queried by name gives an empty array; action 'compounds' resolves it.\n" +
|
|
45
46
|
"'mechanism' (chemblId, molecule only) — curated mechanism of ONE molecule: 'how does X work?'. → mechanismOfAction, actionType " +
|
|
46
47
|
"(INHIBITOR, AGONIST, …), targetChemblId + targetName, moleculeChemblId. Curated mainly for clinical/approved molecules, so tool " +
|
|
47
48
|
"compounds often have none.\n" +
|
|
@@ -62,7 +63,9 @@ const inputSchema = z
|
|
|
62
63
|
searchType: z
|
|
63
64
|
.enum(["target", "compound", "smiles"])
|
|
64
65
|
.optional()
|
|
65
|
-
.describe("Required for 'compounds' — how to read `query`. 'target': resolve to
|
|
66
|
+
.describe("Required for 'compounds' — how to read `query`. 'target': resolve `query` to the TOP ChEMBL target hit only — the result does not " +
|
|
67
|
+
"name that target, and a symbol can hit a non-human ortholog — then return what was assayed against it; a target ChEMBL ID from " +
|
|
68
|
+
"action 'targets' pins the target. " +
|
|
66
69
|
"'compound': free-text over molecule names. 'smiles': flexmatch structure search."),
|
|
67
70
|
chemblId: z
|
|
68
71
|
.string()
|
|
@@ -45,8 +45,8 @@ const inputSchema = z
|
|
|
45
45
|
"HTTK toxicokinetic parameters, reported functional uses, consumer-product composition."),
|
|
46
46
|
query: z
|
|
47
47
|
.string()
|
|
48
|
-
.describe("Chemical identifier (all datasets). A DTXSID ('DTXSID7020182') is used directly; a CASRN ('80-05-7'), a name ('bisphenol A'), or
|
|
49
|
-
"
|
|
48
|
+
.describe("Chemical identifier (all datasets). A DTXSID ('DTXSID7020182') is used directly; a CASRN ('80-05-7'), a name ('bisphenol A'), or " +
|
|
49
|
+
"an InChIKey is resolved by EXACT match, so a non-canonical or misspelled name yields found: false."),
|
|
50
50
|
dataType: z
|
|
51
51
|
.enum(["toxval", "genetox", "cancer", "seem", "httk", "functional-use", "product-data", "all"])
|
|
52
52
|
.optional()
|
|
@@ -103,8 +103,8 @@ export function createComptoxTool(deps) {
|
|
|
103
103
|
"bioactivity), 'hazard' (in-vivo: " +
|
|
104
104
|
"NOAEL/LOAEL/LD50, genotoxicity, cancer), 'chemical' (identity + physicochemical/ADMET), 'exposure' (SEEM predictions, toxicokinetics, uses, " +
|
|
105
105
|
"products). See `dataset` for what each returns; all four resolve `query` to a DTXSID first.\n" +
|
|
106
|
-
"ACCEPTED IDENTIFIERS: a DTXSID ('DTXSID7020182') used directly, or a CASRN ('80-05-7'), a chemical name
|
|
107
|
-
"'
|
|
106
|
+
"ACCEPTED IDENTIFIERS, for every dataset: a DTXSID ('DTXSID7020182') used directly, or a CASRN ('80-05-7'), a chemical name " +
|
|
107
|
+
"('bisphenol A') or an InChIKey, each resolved by EXACT match.\n" +
|
|
108
108
|
"Requires EPA_CCTE_API_KEY — a missing key fails terminally: do NOT retry, tell the user the key needs configuring and proceed without EPA data.\n" +
|
|
109
109
|
"found: false means the query did not resolve to a chemical; a present-but-empty section is likewise valid no-data. Do not retry either.\n" +
|
|
110
110
|
"This is environmental/industrial-chemical data: for drug-like compounds prefer PubChem or ChEMBL — dataset 'chemical' returns " +
|
|
@@ -106,11 +106,12 @@ export function createDrugGeneInteractionsTool(deps) {
|
|
|
106
106
|
description: "Drug↔gene interactions across DGIdb (the Drug Gene Interaction Database), DrugBank and PharmGKB, in one call — 'what drugs hit these " +
|
|
107
107
|
"genes?' and 'what genes does this drug act on?'. See `direction` and `sources` for the modes.\n" +
|
|
108
108
|
"ACCEPTED IDENTIFIERS: a HUGO gene symbol ('EGFR') for direction 'gene_to_drugs'; a drug name ('imatinib'), a DrugBank ID ('DB00619') or a " +
|
|
109
|
-
"DGIdb concept ID for 'drug_to_genes'. A brand name and a non-HUGO symbol match nothing — resolve the symbol with search_gene and
|
|
110
|
-
"name
|
|
111
|
-
"It answers whether an interaction is KNOWN, by whom, and of what type.
|
|
109
|
+
"DGIdb concept ID for 'drug_to_genes'. A brand name and a non-HUGO symbol match nothing — resolve the symbol with search_gene, and pass " +
|
|
110
|
+
"the generic drug name, not the brand name.\n" +
|
|
111
|
+
"It answers whether an interaction is KNOWN, by whom, and of what type. It carries no potency value (IC50/Ki).\n" +
|
|
112
112
|
"Verbose fields (`includeAttributes`, `includeDrugRecord`) are off by default — both cost a lot of context and are rarely what the question needs.\n" +
|
|
113
|
-
"
|
|
113
|
+
"`perSource` gives the outcome of each corpus, and a conclusion that an interaction is unknown rests on it: 'no_data' means that corpus " +
|
|
114
|
+
"has nothing; 'unavailable' means it could " +
|
|
114
115
|
"not be reached (a missing DRUGBANK_API_KEY lands here — tell the user and proceed with DGIdb). A `found: false` entry means the identifier " +
|
|
115
116
|
"is not in DGIdb at all, usually a non-HUGO symbol or a brand name. Neither is worth retrying unchanged.",
|
|
116
117
|
inputSchema,
|
|
@@ -156,15 +156,15 @@ export function createGeneDiseaseEvidenceTool(deps) {
|
|
|
156
156
|
id: "gene_disease_evidence",
|
|
157
157
|
description: "Genetic evidence linking a gene to a disease or trait, across the NHGRI-EBI GWAS Catalog, DisGeNET, NCBI ClinVar and cBioPortal in one call " +
|
|
158
158
|
"— 'is there human genetic support for this target in this indication?'. See `sources` for what each corpus carries.\n" +
|
|
159
|
-
"
|
|
160
|
-
"and
|
|
161
|
-
"frequencies with their studies and PMIDs.\n" +
|
|
159
|
+
"It returns the underlying records — actual SNPs, effect sizes, scores, pathogenicity calls and somatic mutation frequencies with their " +
|
|
160
|
+
"studies and PMIDs — not one integrated score across evidence types.\n" +
|
|
162
161
|
"ACCEPTED IDENTIFIERS, each named by its `queryType`: a HUGO gene symbol or an Entrez ID ('PCSK9', '5008'); a disease or trait name or a UMLS " +
|
|
163
162
|
"CUI ('asthma', 'C0006142'); a dbSNP rsID ('rs11591147'); a GWAS Catalog study accession ('GCST000392'); and a ClinVar accession " +
|
|
164
163
|
"('VCV000012345'). The last two read one record back by the accession that an earlier result of this same tool reported. " +
|
|
165
164
|
"The GWAS Catalog matches the EXACT EFO trait label (case-insensitive): 'asthma' resolves, a paraphrase such as 'LDL cholesterol' reads as " +
|
|
166
165
|
"no_data there ('low density lipoprotein cholesterol measurement' is the label) while the other corpora still answer it.\n" +
|
|
167
|
-
"
|
|
166
|
+
"`perSource` gives the outcome of each corpus, and a conclusion of absence rests on it. 'no_data' means that corpus genuinely has nothing; " +
|
|
167
|
+
"'unavailable' means it " +
|
|
168
168
|
"could not be reached (a missing DISGENET_API_KEY lands here — tell the user and proceed with the others); 'not_applicable' means that " +
|
|
169
169
|
"corpus has no lookup for this queryType. Only 'no_data' is evidence of absence, and none is worth retrying unchanged.",
|
|
170
170
|
inputSchema,
|
|
@@ -84,7 +84,9 @@ const inputSchema = z
|
|
|
84
84
|
vocabulary: z
|
|
85
85
|
.enum(["go", "kegg", "reactome", "pathways"])
|
|
86
86
|
.describe("'go' — Gene Ontology via QuickGO (EBI); needs ONE of goId, query, or geneProductId. Returns terms[] { id, name, definition, aspect } " +
|
|
87
|
-
"and/or annotations[] { geneProductId, goId, goName, aspect, evidenceCode, qualifier }
|
|
87
|
+
"and/or annotations[] { geneProductId, goId, goName, aspect, evidenceCode, qualifier } — the first `limit` rows that QuickGO " +
|
|
88
|
+
"serves, one row for each term, evidence and reference, thus a term can repeat, a well-studied protein holds hundreds of rows, " +
|
|
89
|
+
"and the result gives no total. " +
|
|
88
90
|
"'kegg' / 'reactome' — one pathway database; needs query or pathwayId. 'pathways' — both, in parallel; needs query or pathwayId. " +
|
|
89
91
|
"Returns pathways[] { id, name, source, url, description?, genes? }."),
|
|
90
92
|
query: z
|
|
@@ -110,8 +112,10 @@ const inputSchema = z
|
|
|
110
112
|
includeGenes: z
|
|
111
113
|
.boolean()
|
|
112
114
|
.optional()
|
|
113
|
-
.describe("Pathway vocabularies only. Default FALSE — member
|
|
114
|
-
"
|
|
115
|
+
.describe("Pathway vocabularies only. Default FALSE — member lists cost one request per pathway and run to hundreds of entries. KEGG members " +
|
|
116
|
+
"are NCBI Gene (Entrez) IDs ('7157'), not symbols. Reactome members are participant entity names with their compartment " +
|
|
117
|
+
"('p-T389/412-RPS6KB1 [cytosol]', 'ATP [cytosol]'), small molecules included. True only when you need the members, not the " +
|
|
118
|
+
"pathway identities."),
|
|
115
119
|
limit: z
|
|
116
120
|
.number()
|
|
117
121
|
.int()
|
|
@@ -32,7 +32,9 @@ const inputSchema = z
|
|
|
32
32
|
action: z
|
|
33
33
|
.enum(["target", "disease", "resolve_disease"])
|
|
34
34
|
.describe("'target' (needs ensemblId) — the diseases associated with the gene: targetInfo (approvedSymbol, approvedName, " +
|
|
35
|
-
"tractability
|
|
35
|
+
"tractability — for smallMolecule, antibody and otherModalities, the labels of the positive Open Targets buckets, from " +
|
|
36
|
+
"'Approved Drug' and 'Advanced Clinical' down to 'High-Quality Pocket' or 'UniProt loc high conf'; an empty list means no " +
|
|
37
|
+
"positive bucket and null means no assessment) plus associations[], each with an overall score and " +
|
|
36
38
|
"its per-datatype breakdown — genetic association, known drug, literature, animal model, somatic mutation. " +
|
|
37
39
|
"'disease' (needs efoId) — the targets ranked for that disease; each association carries targetId/targetSymbol/targetName " +
|
|
38
40
|
"and the same score breakdown. " +
|
|
@@ -75,11 +77,11 @@ const inputSchema = z
|
|
|
75
77
|
});
|
|
76
78
|
export const openTargetsTool = defineTool({
|
|
77
79
|
id: "opentargets",
|
|
78
|
-
description: "Query the Open Targets Platform — the integrated target-disease evidence corpus of EMBL-EBI, GSK and their partners
|
|
79
|
-
"
|
|
80
|
-
"
|
|
81
|
-
"It is the integrated view
|
|
82
|
-
"
|
|
80
|
+
description: "Query the Open Targets Platform — the integrated target-disease evidence corpus of EMBL-EBI, GSK and their partners. One 'target' query " +
|
|
81
|
+
"gives the tractability of the target and its disease associations, already scored and ranked, each with a per-datatype score for genetic " +
|
|
82
|
+
"association, known drug, literature, animal model and somatic mutation. See the action parameter for what each mode needs and returns.\n" +
|
|
83
|
+
"It is the integrated view, not the underlying records: it names no drug, no SNP, no effect size, no ClinVar call and no PMID, and it " +
|
|
84
|
+
"holds no safety liability.\n" +
|
|
83
85
|
"ACCEPTED IDENTIFIERS: an Ensembl gene ID for action 'target' (ENSG00000141510); an Open Targets disease ID for action 'disease' — EFO " +
|
|
84
86
|
"(EFO_0000311), MONDO (MONDO_0005148) or HP (HP_0001250), because Open Targets keys a disease on EFO and EFO imports the other two; and a plain " +
|
|
85
87
|
"disease NAME for action 'resolve_disease' ('type 2 diabetes'), which is how you obtain that disease ID. A bare gene symbol is NOT accepted by " +
|
|
@@ -19,14 +19,16 @@ const inputSchema = z
|
|
|
19
19
|
action: z
|
|
20
20
|
.enum(["compound", "crossrefs", "assays"])
|
|
21
21
|
.describe("Which PubChem lookup to run. " +
|
|
22
|
-
"'compound' — resolve `query` (in the namespace named by `searchBy`) to a compound. Returns results[]: identity (cid, canonicalSmiles
|
|
22
|
+
"'compound' — resolve `query` (in the namespace named by `searchBy`) to a compound. Returns results[]: identity (cid, canonicalSmiles — " +
|
|
23
|
+
"the PubChem connectivity SMILES, with no stereochemistry and no isotopes, thus inchi is the full structure — inchi, inchiKey, iupacName, " +
|
|
23
24
|
"molecularFormula) and computed properties (molecularWeight, xlogp, tpsa, hbondDonorCount, hbondAcceptorCount, rotatableBondCount, complexity). " +
|
|
24
25
|
"'crossrefs' — external registry ids for a `cid`. Returns crossRefs[]: a flat list of { source, id } entries. `source` names the registry that the id pattern " +
|
|
25
26
|
"identifies — ChEMBL, ChEBI, DrugBank, KEGG, HMDB, CAS, PDB or UNII — and it is null for an id of any other registry; filter by `source` for the one you want. " +
|
|
26
27
|
"This is the bridge out of PubChem. " +
|
|
27
28
|
"'assays' — bioassay screening summaries for a `cid`; answers 'has this compound ever been screened, and against what?'. Returns assays[]: aid, assayName, targetName " +
|
|
28
29
|
"(the target accession, for example a UniProt or PDB id), " +
|
|
29
|
-
"activityOutcome (Active / Inactive / Inconclusive / Unspecified), activityValue
|
|
30
|
+
"activityOutcome (Active / Inactive / Inconclusive / Unspecified), activityValue (in µM; null when the assay reports none). Broad HTS " +
|
|
31
|
+
"screening coverage, NOT curated potency — for numbers you intend to quote " +
|
|
30
32
|
"(IC50/Ki), prefer chembl action='bioactivity'."),
|
|
31
33
|
query: z
|
|
32
34
|
.string()
|
package/dist/tools/bio/pubmed.js
CHANGED
|
@@ -34,8 +34,9 @@ const inputSchema = z
|
|
|
34
34
|
action: z
|
|
35
35
|
.enum(["search", "details", "fulltext"])
|
|
36
36
|
.describe("'search' (needs query) — returns totalFound plus, per hit, PMID, title, journal, year and an author summary. " +
|
|
37
|
-
"'details' (needs pmids, max 20 per call) — each article's full abstract, journal, year, DOI, pmcId (non-null
|
|
38
|
-
"
|
|
37
|
+
"'details' (needs pmids, max 20 per call) — each article's full abstract, journal, year, DOI, pmcId (non-null when the article is in " +
|
|
38
|
+
"PubMed Central, which includes articles outside the open-access subset), plus the leading authors and MeSH terms with authorCount / " +
|
|
39
|
+
"meshTermCount giving the true totals. " +
|
|
39
40
|
"'fulltext' (needs pmcId, taken from a 'details' result) — the body of ONE open-access PMC article as plain text with section headers " +
|
|
40
41
|
"preserved, trimmed to maxChars; `outline` lists every section with its size and whether it came back, and truncated says whether " +
|
|
41
42
|
"anything was left out. available: false when the article is not open-access."),
|
|
@@ -122,10 +123,10 @@ export function createPubMedTool(deps) {
|
|
|
122
123
|
"the U.S. National Library of Medicine, through the NCBI E-utilities. The three actions form a chain " +
|
|
123
124
|
"— search, then details on the relevant hits, then fulltext; see the action parameter for what each needs and returns.\n" +
|
|
124
125
|
"ACCEPTED IDENTIFIERS: an Entrez free-text query with optional field tags for 'search' ('PCSK9[Title] AND 2020:2024[dp]'); PMIDs for " +
|
|
125
|
-
"'details' ('34567890'); and a
|
|
126
|
-
"
|
|
126
|
+
"'details' ('34567890'); and a PMC ID for 'fulltext' ('PMC7096066'), never a PMID — the PMC number of an article differs from its PMID. " +
|
|
127
|
+
"PubMed holds MEDLINE and PMC only, thus a method, a model or a preprint outside MEDLINE is often absent here.\n" +
|
|
127
128
|
"Read " +
|
|
128
|
-
"fulltext sparingly — only where the 'details' abstract is not enough, and only for articles that have a pmcId
|
|
129
|
+
"fulltext sparingly — only where the 'details' abstract is not enough, and only for articles that have a pmcId. " +
|
|
129
130
|
"A full article is larger than everything else this tool returns combined, so it comes back trimmed to a character budget: take the " +
|
|
130
131
|
"`outline` from the first call and re-request the specific `sections` you need rather than raising maxChars. " +
|
|
131
132
|
"An empty result set and available: false are expected outcomes, not errors — an article with no PMC record simply has no open full text. " +
|
|
@@ -172,7 +173,10 @@ export function createPubMedTool(deps) {
|
|
|
172
173
|
// "Not open-access" is an expected outcome — a data variant, not an error.
|
|
173
174
|
if (!parsed)
|
|
174
175
|
return ok({ pmcId: input.pmcId, available: false });
|
|
175
|
-
|
|
176
|
+
// `fullText` already holds each admitted section under its heading, thus the
|
|
177
|
+
// `sections` array would send the same text to the model a second time.
|
|
178
|
+
const { sections: _sections, ...bounded } = parsed;
|
|
179
|
+
return ok({ pmcId: input.pmcId, available: true, ...bounded });
|
|
176
180
|
}
|
|
177
181
|
}
|
|
178
182
|
},
|