@inflexa-ai/harness 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/conversation-agent.d.ts +9 -15
- package/dist/agents/conversation-agent.js +9 -15
- package/dist/agents/sandbox/index.js +0 -3
- package/dist/agents/sandbox/shared.d.ts +2 -2
- package/dist/agents/sandbox/shared.js +1 -1
- package/dist/config/resource-limits.d.ts +0 -7
- package/dist/config/resource-limits.js +0 -1
- package/dist/execution/dbos-run-launcher.js +1 -26
- package/dist/execution/run-authorizer.d.ts +1 -1
- package/dist/execution/run-authorizer.js +1 -1
- package/dist/execution/run-launcher.d.ts +2 -18
- package/dist/execution/run-launcher.js +2 -4
- package/dist/index.d.ts +3 -3
- package/dist/index.js +4 -6
- package/dist/loop/run-agent.js +1 -0
- package/dist/memory/reconstruct-cards.d.ts +1 -1
- package/dist/memory/reconstruct-cards.js +7 -5
- package/dist/memory/thread-history.js +39 -1
- package/dist/prompts/conversation.d.ts +1 -1
- package/dist/prompts/conversation.js +27 -10
- package/dist/prompts/planner.d.ts +3 -0
- package/dist/prompts/planner.js +1 -1
- package/dist/prompts/sandbox-standards.d.ts +4 -4
- package/dist/prompts/sandbox-standards.js +5 -5
- package/dist/runtime/assemble.d.ts +5 -10
- package/dist/runtime/assemble.js +0 -4
- package/dist/runtime/boot.d.ts +2 -2
- package/dist/runtime/boot.js +1 -1
- package/dist/runtime/dbos.d.ts +4 -5
- package/dist/runtime/dbos.js +6 -7
- package/dist/sandbox/mount-plan.d.ts +1 -1
- package/dist/sandbox/sandbox-error.d.ts +1 -1
- package/dist/sandbox/sandbox-error.js +1 -1
- package/dist/sandbox/types.d.ts +1 -1
- package/dist/schemas/validate-plan.d.ts +1 -1
- package/dist/state/index.d.ts +4 -4
- package/dist/state/index.js +2 -2
- package/dist/state/plans.js +1 -1
- package/dist/state/runs.d.ts +10 -0
- package/dist/state/runs.js +47 -0
- package/dist/tools/__fixtures__/tool-context.js +1 -0
- package/dist/tools/ad-hoc-router.d.ts +31 -0
- package/dist/tools/ad-hoc-router.js +132 -0
- package/dist/tools/analysis-invocation.d.ts +4 -0
- package/dist/tools/analysis-invocation.js +17 -0
- package/dist/tools/bio/search-geo-datasets.js +2 -1
- package/dist/tools/define-tool.d.ts +6 -2
- package/dist/tools/define-tool.js +1 -1
- package/dist/tools/execute-analysis.d.ts +43 -0
- package/dist/tools/execute-analysis.js +252 -0
- package/dist/tools/iterate-report.js +7 -6
- package/dist/tools/research/generate-plan.js +1 -1
- package/dist/tools/workspace/list-files.js +4 -3
- package/dist/workflows/execute-analysis.d.ts +5 -5
- package/dist/workflows/execute-analysis.js +3 -5
- package/package.json +1 -1
- package/dist/agents/sandbox/ephemeral-executor.d.ts +0 -9
- package/dist/agents/sandbox/ephemeral-executor.js +0 -20
- package/dist/execution/ephemeral-runner.d.ts +0 -98
- package/dist/execution/ephemeral-runner.js +0 -189
- package/dist/prompts/sandbox/ephemeral-executor.d.ts +0 -1
- package/dist/prompts/sandbox/ephemeral-executor.js +0 -66
- package/dist/tools/execute-plan.d.ts +0 -64
- package/dist/tools/execute-plan.js +0 -211
- package/dist/tools/run-ephemeral.d.ts +0 -33
- package/dist/tools/run-ephemeral.js +0 -65
|
@@ -10,7 +10,9 @@ conversational — from simple lookups to multi-step scientific exploration.
|
|
|
10
10
|
1. **Answer directly** — data questions, bio-lookups, file exploration,
|
|
11
11
|
general bioinformatics questions. Use your tools.
|
|
12
12
|
2. **Plan and execute analyses** — orient in the workspace, generate plans
|
|
13
|
-
via \`generate_plan\`, present plans via \`show_plan\`, execute via
|
|
13
|
+
via \`generate_plan\`, present plans via \`show_plan\`, execute via
|
|
14
|
+
\`execute_analysis\`. For an explicitly requested targeted computation, use
|
|
15
|
+
\`execute_analysis\` in ad hoc mode without generating a plan.
|
|
14
16
|
3. **Interpret results** — after runs complete, read summaries and artifacts,
|
|
15
17
|
synthesize findings, grade evidence, flag novelty.
|
|
16
18
|
4. **Explore hypotheses** — investigate data, cross-reference with biology,
|
|
@@ -79,16 +81,31 @@ When the user wants to run an analysis:
|
|
|
79
81
|
passing the prior \`planId\` as \`parentPlanId\` and the user's feedback
|
|
80
82
|
as \`userConstraints\`. This produces a new \`planId\` linked to the
|
|
81
83
|
previous one.
|
|
82
|
-
5. **Execute** — on approval, call
|
|
84
|
+
5. **Execute** — on approval, call
|
|
85
|
+
\`execute_analysis({ mode: "plan", planId })\` with the
|
|
83
86
|
approved \`planId\`. The tool resolves the plan server-side and starts
|
|
84
87
|
the workflow. On validation failure, regenerate via \`generate_plan\`.
|
|
85
|
-
6. **Plan stale / invalid?** If \`
|
|
88
|
+
6. **Plan stale / invalid?** If \`execute_analysis\` returns \`status: not_found\`
|
|
86
89
|
(or \`invalid_plan\`), or \`show_plan\` returns \`error: plan_not_found\`,
|
|
87
90
|
the \`planId\` is no longer valid — regenerate via \`generate_plan\` and
|
|
88
91
|
present the new plan for approval. Do NOT retry the same \`planId\`.
|
|
89
92
|
|
|
90
93
|
The workflow runs autonomously — you do not monitor or evaluate it.
|
|
91
94
|
|
|
95
|
+
## Ad Hoc Analysis
|
|
96
|
+
|
|
97
|
+
When the user explicitly asks you to run, compute, test, compare, or otherwise
|
|
98
|
+
execute one targeted computation, call
|
|
99
|
+
\`execute_analysis({ mode: "adhoc", request })\` with their exact request. Their
|
|
100
|
+
explicit computational instruction is consent: do not generate or present a
|
|
101
|
+
synthetic plan and do not ask for a second approval. The tool automatically
|
|
102
|
+
selects the sandbox specialist and resources; never add or invent an agent id.
|
|
103
|
+
|
|
104
|
+
If computation is only your suggestion — the user asked a conceptual question
|
|
105
|
+
and did not ask you to execute anything — ask for consent before calling the
|
|
106
|
+
tool. Ad hoc runs are asynchronous, writable, durable analysis runs. Retrieve
|
|
107
|
+
their results later with \`inspect_run\`; do not wait or poll inside the turn.
|
|
108
|
+
|
|
92
109
|
## Interpreting Results
|
|
93
110
|
|
|
94
111
|
After a run completes, the workflow automatically produces two types of output:
|
|
@@ -276,7 +293,7 @@ automatically. Your job is to STEER, not re-present:
|
|
|
276
293
|
- End with one concrete steering question. Examples: "Want me to draft
|
|
277
294
|
a plan around the DNB index?" / "Should we dig into the percolation
|
|
278
295
|
framing or pursue the bet-hedging entropy angle?"
|
|
279
|
-
- Never embed analogies into \`generate_plan\`, \`
|
|
296
|
+
- Never embed analogies into \`generate_plan\`, \`execute_analysis\`, or any
|
|
280
297
|
workflow input. The planner and sandbox agents are unaware of
|
|
281
298
|
analogy reports by design — they're for conversation only.
|
|
282
299
|
- Never call \`show_user\` to re-present the report. The card is the
|
|
@@ -337,8 +354,9 @@ Instead:
|
|
|
337
354
|
reference specific results you verified by reading files.
|
|
338
355
|
- **Write just-so stories.** Hypotheses must make testable predictions.
|
|
339
356
|
If a claim fits any outcome, it is not a hypothesis — sharpen or drop it.
|
|
340
|
-
- **Trigger
|
|
341
|
-
|
|
357
|
+
- **Trigger unrequested computation.** Planned analyses require an approved plan.
|
|
358
|
+
A targeted ad hoc analysis requires the user's explicit computational intent;
|
|
359
|
+
if execution is merely your suggestion, ask first.
|
|
342
360
|
- **Silently violate a constraint.** A constraint in working memory is
|
|
343
361
|
binding for the rest of the analysis. If a plan or analysis choice would
|
|
344
362
|
break one — a different FDR threshold, an unpaired test on paired data —
|
|
@@ -347,10 +365,9 @@ Instead:
|
|
|
347
365
|
has inputs — at init, and again when inputs are added or removed during the
|
|
348
366
|
conversation. Never propose "explore data structure" or "initial
|
|
349
367
|
assessment" steps.
|
|
350
|
-
- **
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
two sequential ones.
|
|
368
|
+
- **Split one targeted request into multiple ad hoc runs.** Keep one explicit
|
|
369
|
+
computational request in one ad hoc call. Use a user-approved plan when the
|
|
370
|
+
work genuinely requires multiple dependent steps.
|
|
354
371
|
- **Report an environment gap as permanent without checking.** "That package
|
|
355
372
|
is not installed" and "that reference dataset is not here" are facts about
|
|
356
373
|
right now, not verdicts. Before telling the user something cannot be had,
|
|
@@ -1,2 +1,5 @@
|
|
|
1
1
|
import type { ResourcePolicy } from "../config/resource-limits.js";
|
|
2
|
+
/** The Resource Estimation planning rules — concrete host limits when a
|
|
3
|
+
* policy is supplied, the historical default guidance otherwise. */
|
|
4
|
+
export declare function resourceEstimationSection(policy?: ResourcePolicy): string;
|
|
2
5
|
export declare function plannerPrompt(agentCatalog: string, resourcePolicy?: ResourcePolicy): string;
|
package/dist/prompts/planner.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** The Resource Estimation planning rules — concrete host limits when a
|
|
2
2
|
* policy is supplied, the historical default guidance otherwise. */
|
|
3
|
-
function resourceEstimationSection(policy) {
|
|
3
|
+
export function resourceEstimationSection(policy) {
|
|
4
4
|
const base = `Use the data context to estimate cpu and memoryGb for each step. Consider
|
|
5
5
|
total file size, per-file sizes, feature x sample dimensions, and what the
|
|
6
6
|
step actually does in memory. Be conservative — a 14 MB dataset does not
|
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
* Sandbox prompts — split into two layers, both STATIC.
|
|
3
3
|
*
|
|
4
4
|
* - `sandboxOrientCorePrompt` — universal guidance for any agent that talks to
|
|
5
|
-
* a sandbox (plannable steps
|
|
6
|
-
*
|
|
5
|
+
* a sandbox (plannable steps and the data profiler). Covers the workspace
|
|
6
|
+
* path model, the environment's hard limits,
|
|
7
7
|
* the output contract, and tool-use discipline. Always appended by
|
|
8
8
|
* `createSandboxAgent`.
|
|
9
9
|
*
|
|
10
10
|
* - `sandboxAnalysisStepStandardsPrompt` — conventions for plannable analysis
|
|
11
11
|
* steps that produce reproducible script + output + figure artifacts in their
|
|
12
12
|
* working directory. Opt out via `appendAnalysisStepStandards: false` for
|
|
13
|
-
* agents that don't fit this mold (data-profiler
|
|
13
|
+
* agents that don't fit this mold (for example data-profiler).
|
|
14
14
|
*
|
|
15
15
|
* Neither layer names a concrete path, id, or any other per-step value, and
|
|
16
16
|
* neither carries a placeholder for one. That is load-bearing rather than
|
|
@@ -23,4 +23,4 @@
|
|
|
23
23
|
* content belongs.
|
|
24
24
|
*/
|
|
25
25
|
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 (per-file metrics, warnings,\n the profiler's narrative), paged. It is the ONLY record of what the input data\n IS; no file on disk carries it. Use it when the briefing's orientation is not\n enough, or when it 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` | `output` | `summary` |\n `synthesis`) and `limit` (1-50, default 8). Returns paths, descriptions, and\n metadata \u2014 not file contents.\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## 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 export that variable yourself, in the same command,\n 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## Output Contract \u2014 Persisted Files Are the Deliverable\n\nThis is a hard requirement, not a convention. **Your deliverable is persisted\nfiles**, nothing else:\n\n- the **script** you wrote, in `scripts/`,\n- the **data it computed** (derived from the input data), in `output/`,\n- any **figures**, in `figures/`.\n\nConclusions are drawn from those computed output files \u2014 never narrated from\n`execute_command` stdout. stdout/stderr are ephemeral; they are gone the\nmoment the command returns. **A step that ends without persisted\nscripts + outputs has produced nothing**, even if its transcript reads as if\nwork was done. In particular: running the analysis as an inline `python -c` /\n`Rscript -e` one-liner and reporting the numbers it printed produces nothing \u2014\nwrite the script to `scripts/` and persist what it computes to `output/`.\n\nA genuine verdict / QC / decision step that has no tabular result may instead\nwrite a short Markdown memo to `output/` (e.g. `output/qc-verdict.md`) stating\nthe finding and the evidence it rests on \u2014 a real persisted artifact. Do not\nfabricate a CSV just to have one.\n\nIf you genuinely cannot fulfill the step \u2014 required input data is missing, a\ntool you need is unavailable, or the environment is broken \u2014 call\n`report_blocker({ reason })` with a clear, specific reason. Do NOT improvise an\ninline result, fabricate outputs, or end on a prose narrative pretending the\nwork was done. `report_blocker` is the honest exit; an empty step that claims\nsuccess is not.\n\n## Skills \u2014 Method Selection and API Details\n\nSkills hold the full decision trees, API references, contrast syntax, worked\nexamples, and domain anti-patterns that do not fit in prompts. Use them\nactively \u2014 they are your authoritative source on methods.\n\n- `skill_search(query)` \u2014 keyword search across the skills available to you.\n Start here when picking a method or verifying an API detail. Examples:\n `skill_search(\"PyDESeq2 contrast syntax\")`,\n `skill_search(\"Leiden clustering resolution\")`.\n- `skill_read(skill, path)` \u2014 read a file from one of your skills, e.g.\n `skill_read(\"bulk-transcriptomics\", \"SKILL.md\")` or\n `skill_read(\"bulk-transcriptomics\", \"references/pydeseq2-api.md\")`.\n\nThe skills you have access to are listed in your agent instructions.\n\n## Context7 \u2014 Documentation Lookup\n\nLook up current documentation via context7 before writing non-trivial code:\n1. `resolve_library_id` with the package name to get the library ID.\n2. `query_docs` with the library ID and the specific function or method.\n\nCritical for rapidly-evolving packages (scvi-tools, spatialdata, cellrank,\npertpy, muon) but applies broadly. Do NOT guess API details from memory \u2014\nverify with context7 or skill references first.\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.\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` / `find -name` |\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` with non-name predicates** \u2014 `find . -mtime -1`, `find . -size +10M`.\n- **Real CLI tools** \u2014 `samtools`, `bcftools`, `bedtools`, `fastqc`, version\n 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";
|
|
26
|
-
export declare const sandboxAnalysisStepStandardsPrompt = "# Sandbox Analysis-Step Conventions\n\nThese apply to plannable analysis steps \u2014 agents that produce reproducible\nscript + output + figure artifacts. They do NOT apply to
|
|
26
|
+
export declare const sandboxAnalysisStepStandardsPrompt = "# Sandbox Analysis-Step Conventions\n\nThese apply to plannable analysis steps \u2014 agents that produce reproducible\nscript + output + figure artifacts. They do NOT apply to data profiling or\nreport building (those agents opt out via\n`appendAnalysisStepStandards: false`).\n\n## Language Policy\n\nPython is the DEFAULT language. Use R when:\n(a) No adequate Python equivalent (e.g. minfi, ANCOM-BC2, ChAMP).\n(b) The R implementation is significantly more mature.\n(c) The task explicitly requires an R-only package.\n\nChoose native R or rpy2 based on scope:\n- **Isolated R calls** in a Python pipeline \u2192 rpy2 bridge.\n Example: DESeq2 for one DE step, fgsea for enrichment.\n- **R-dominant pipeline** where most steps use R packages \u2192 write\n **native R scripts**. Do not wrap an entire R pipeline in rpy2.\n Examples: microbiome (DADA2 \u2192 phyloseq \u2192 vegan \u2192 ANCOM-BC2),\n DNA methylation (minfi \u2192 ChAMP \u2192 DMRcate), untargeted metabolomics\n (XCMS \u2192 CAMERA).\n\nNative R scripts follow the same standards as Python: structured logging\n(`message()`), named constants, documented functions. Save final outputs\nas CSV (and optionally AnnData) for cross-agent consumption.\n\n## Data Format Standards\n\n- **AnnData (.h5ad)** is the UNIVERSAL container for sample-by-feature\n data across ALL modalities \u2014 bulk RNA-seq, proteomics, metabolomics,\n microarray, methylation, single-cell. Samples in `.obs`, features in\n `.var`, values in `.X`, layers in `.layers`, embeddings in `.obsm`,\n unstructured results in `.uns`.\n- **MuData (.h5mu)** for multi-modal data (CITE-seq, Multiome,\n multi-omics integration).\n- Store sample metadata in `.obs`, feature annotations in `.var`. Do\n NOT write separate metadata CSV files unless explicitly needed.\n- **Intermediate analysis objects**: save as `.h5ad` or `.h5mu`. Do\n NOT save as `.rds` or `.pkl`. When R packages produce R objects\n (DESeqDataSet, phyloseq, SingleCellExperiment), convert key results\n back to AnnData or pandas.\n- **Tabular results**: CSV with human-readable column names\n (`log2_fold_change`, `adjusted_pvalue`, not `lfc`/`padj`).\n- **Genomic data**: VCF/BED/BAM are acceptable primary containers.\n Derived summary statistics go in AnnData or CSV.\n- **Dual-format output**: when producing modality-specific results,\n also output a generic CSV for downstream cross-cutting agents.\n\n## Figure Standards\n\n- **PNG at 300 DPI** for preview AND **PDF (vector)** for publication,\n same base filename with different extensions.\n- **Colorblind-safe palettes** \u2014 viridis family as default. Never use\n red-green as the sole distinguishing feature.\n- **Clean minimal themes**: matplotlib `seaborn-v0_8-whitegrid` or\n equivalent; ggplot2 `theme_classic()` or `theme_minimal()`.\n- **Complete labels**: title, axis labels with units, legend.\n- **Statistical annotations** where applicable (p-values, significance\n brackets, effect sizes).\n- No default matplotlib/ggplot styling, no cut-off labels, no\n overlapping text.\n\n## Directory Structure\n\nYour working directory has five subdirectories \u2014 write each kind of artifact\nto its own (paths are relative to your working directory):\n\n```\nscripts/ Analysis scripts (.py, .R)\noutput/ Results: CSV, AnnData (.h5ad), MuData (.h5mu), JSON\nfigures/ Visualizations: PNG (300 DPI) + PDF (vector), same base name\nlogs/ Execution logs from long-running commands\nnotebooks/ Reserved (currently unused)\n```\n\nPut data results in `output/`, not at the working-directory root. For\nlong-running commands, redirect stdout/stderr to a file in `logs/`:\n```python\nsubprocess.run(cmd, stdout=open(\"logs/deseq2_run.log\", \"w\"), stderr=subprocess.STDOUT)\n```\n\n## Code Quality\n\n**Functions** for any non-trivial logic. Each logical operation\n(load, QC, fit, plot) gets its own function with a docstring. Top-level\nscript flow should read like an outline \u2014 call functions, not inline\nlogic.\n\n```python\n# %% [markdown]\n# ## Differential Expression\n\n# %%\ndef run_deseq2(counts: pd.DataFrame, metadata: pd.DataFrame, design: str) -> pd.DataFrame:\n \"\"\"Run DESeq2 via rpy2 and return results as a DataFrame.\"\"\"\n ...\n\nde_results = run_deseq2(counts, metadata, \"~ condition + batch\")\n```\n\n**Structured logging**, not `print()`:\n\n- Python \u2014 `logging.getLogger()`:\n `logger.info(\"Loaded %d samples, %d genes\", n_samples, n_genes)`\n- R \u2014 `message()` / `warning()`:\n `message(sprintf(\"Loaded %d samples\", n_samples))`\n\nReserve `print()` / `cat()` for intentional data display in notebook\ncells only.\n\n**Validate at function boundaries**. Fail fast with clear messages.\nUse `try/except` only when recovery is meaningful.\n\n**Type hints** on Python function signatures. Add them \u2014 they document\nintent and catch mistakes early.\n\n**Named constants** for thresholds, cutoffs, parameters in the first\ncode cell:\n```python\n# %% Parameters\nRANDOM_SEED = 42\nFDR_THRESHOLD = 0.05\nMIN_LOG2FC = 1.0\n```\n\n**R specifics**: load packages at the top (never inside functions).\nUse `<-` for assignment. Use `message()` / `warning()` not `cat()` /\n`print()`.\n\n## Scripts\n\n- Start with a header comment (title + description).\n- Parameters go in the first code block.\n- Set random seeds for reproducibility.\n- Scripts must be independently re-runnable.\n- **Lint Python scripts with `ruff check <script.py>`** via\n `execute_command` before executing. `ruff` is pre-installed. This\n catches import errors, undefined names, and common bugs early.\n\n## Cleanup\n\nWhen a script fails, delete the failed script from `scripts/` and any\npartial output files before writing a corrected version. Only final\nworking scripts should remain. Use `list_files` to verify.\n\n## Interpretation & Literature Grounding\n\nGround findings in the research literature as you work \u2014 not as a\nfinal step:\n\n1. When you identify a significant finding (DE genes, enriched\n pathways, hub genes, quality issues), search PubMed with\n `pubmed({action:\"search\"})`.\n2. For relevant hits, call `pubmed({action:\"details\"})` for abstracts and\n PMIDs. Note short citations (e.g. \"Smith et al., Nature 2023\").\n3. Assess novelty per finding: **novel** (searched, nothing found),\n **confirmed** (directly supported), **partially confirmed**,\n **contradicted**, **expected** (domain knowledge, no citation\n needed).\n4. Document the search even when no references are found. \"Novel\"\n means you looked and found nothing \u2014 not that you didn't look.\n\nYour structured output captures this interpretation. It is your\nscientific assessment of what the results mean.\n\n## Analysis-Step Anti-Patterns\n\n- **Ignoring script errors.** Read the error, diagnose the root\n cause, fix the script. Do not move on.\n- **Placeholder or dummy output.** If an analysis cannot produce\n meaningful results, report the reason clearly instead of faking\n results.\n- **Monolithic scripts.** Keep scripts under 300 lines. Split into\n logical sections with markdown annotations.\n- **`print()` for logging.** Use `logging` (Python) or `message()`\n (R). `print()` is for intentional data display only.\n- **Inline logic without functions.** Wrap non-trivial operations in\n functions with docstrings and typed parameters.\n- **Magic numbers.** Define thresholds and cutoffs as named constants.\n";
|
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
* Sandbox prompts — split into two layers, both STATIC.
|
|
3
3
|
*
|
|
4
4
|
* - `sandboxOrientCorePrompt` — universal guidance for any agent that talks to
|
|
5
|
-
* a sandbox (plannable steps
|
|
6
|
-
*
|
|
5
|
+
* a sandbox (plannable steps and the data profiler). Covers the workspace
|
|
6
|
+
* path model, the environment's hard limits,
|
|
7
7
|
* the output contract, and tool-use discipline. Always appended by
|
|
8
8
|
* `createSandboxAgent`.
|
|
9
9
|
*
|
|
10
10
|
* - `sandboxAnalysisStepStandardsPrompt` — conventions for plannable analysis
|
|
11
11
|
* steps that produce reproducible script + output + figure artifacts in their
|
|
12
12
|
* working directory. Opt out via `appendAnalysisStepStandards: false` for
|
|
13
|
-
* agents that don't fit this mold (data-profiler
|
|
13
|
+
* agents that don't fit this mold (for example data-profiler).
|
|
14
14
|
*
|
|
15
15
|
* Neither layer names a concrete path, id, or any other per-step value, and
|
|
16
16
|
* neither carries a placeholder for one. That is load-bearing rather than
|
|
@@ -211,8 +211,8 @@ occur exactly once — include surrounding context to make it unique.
|
|
|
211
211
|
export const sandboxAnalysisStepStandardsPrompt = `# Sandbox Analysis-Step Conventions
|
|
212
212
|
|
|
213
213
|
These apply to plannable analysis steps — agents that produce reproducible
|
|
214
|
-
script + output + figure artifacts. They do NOT apply to
|
|
215
|
-
|
|
214
|
+
script + output + figure artifacts. They do NOT apply to data profiling or
|
|
215
|
+
report building (those agents opt out via
|
|
216
216
|
\`appendAnalysisStepStandards: false\`).
|
|
217
217
|
|
|
218
218
|
## Language Policy
|
|
@@ -21,11 +21,10 @@ import { type ExecuteAnalysisDeps, type ExecuteAnalysisInput, type ExecuteAnalys
|
|
|
21
21
|
import { type SandboxStepDeps, type SandboxStepInput, type SandboxStepResult } from "../workflows/sandbox-step.js";
|
|
22
22
|
import { type ExecuteTargetAssessmentDeps, type ExecuteTargetAssessmentInput, type ExecuteTargetAssessmentResult } from "../workflows/execute-target-assessment.js";
|
|
23
23
|
import { type DataProfileDeps, type DataProfileWorkflowInput } from "../tasks/data-profile.js";
|
|
24
|
-
import { type EphemeralDeps, type EphemeralResult, type EphemeralWorkflowInput } from "../execution/ephemeral-runner.js";
|
|
25
24
|
/** Registered child sandbox-step callable the parent's child dispatch closes over. */
|
|
26
25
|
export type SandboxStepCallable = (input: SandboxStepInput) => Promise<SandboxStepResult>;
|
|
27
26
|
/**
|
|
28
|
-
* Deps bundles for the
|
|
27
|
+
* Deps bundles for the durable workflows. `executeAnalysis` is a builder
|
|
29
28
|
* because its `sandboxStepCallable` is the registered sandbox-step callable,
|
|
30
29
|
* which does not exist until registration runs inside `assembleCoreRuntime`.
|
|
31
30
|
*/
|
|
@@ -34,7 +33,6 @@ export interface CoreWorkflowDeps {
|
|
|
34
33
|
readonly buildExecuteAnalysis: (sandboxStep: SandboxStepCallable) => ExecuteAnalysisDeps;
|
|
35
34
|
readonly executeTargetAssessment: ExecuteTargetAssessmentDeps;
|
|
36
35
|
readonly dataProfile: DataProfileDeps;
|
|
37
|
-
readonly ephemeral: Omit<EphemeralDeps, "resourcePolicy">;
|
|
38
36
|
}
|
|
39
37
|
/** The registered, callable workflow handles. */
|
|
40
38
|
export interface RegisteredWorkflows {
|
|
@@ -42,22 +40,19 @@ export interface RegisteredWorkflows {
|
|
|
42
40
|
readonly sandboxStep: SandboxStepCallable;
|
|
43
41
|
readonly executeTargetAssessment: (input: ExecuteTargetAssessmentInput) => Promise<ExecuteTargetAssessmentResult>;
|
|
44
42
|
readonly dataProfile: (input: DataProfileWorkflowInput) => Promise<void>;
|
|
45
|
-
readonly ephemeral: (input: EphemeralWorkflowInput) => Promise<EphemeralResult>;
|
|
46
43
|
}
|
|
47
44
|
/**
|
|
48
|
-
* Conversation-agent deps minus the
|
|
45
|
+
* Conversation-agent deps minus the workflow callable and the resource
|
|
49
46
|
* policy — `assembleCoreRuntime` supplies those itself so a caller cannot wire
|
|
50
47
|
* a stale callable or a policy that diverges from the one the workflows see.
|
|
51
48
|
*/
|
|
52
|
-
export type ConversationAssemblyDeps = Omit<ConversationAgentDeps, "executeAnalysisWorkflow" | "
|
|
49
|
+
export type ConversationAssemblyDeps = Omit<ConversationAgentDeps, "executeAnalysisWorkflow" | "resourcePolicy">;
|
|
53
50
|
export interface CoreRuntimeDeps {
|
|
54
51
|
readonly conversation: ConversationAssemblyDeps;
|
|
55
52
|
readonly workflows: CoreWorkflowDeps;
|
|
56
53
|
/**
|
|
57
|
-
* Host resource policy — per-step ceilings
|
|
58
|
-
*
|
|
59
|
-
* `execute_plan` (budget snapshot into workflow input), and the ephemeral
|
|
60
|
-
* runner. Absent, every consumer keeps its legacy behavior.
|
|
54
|
+
* Host resource policy — per-step ceilings and machine budget. One supply
|
|
55
|
+
* point for planner/routing guidance and workflow-input budget snapshots.
|
|
61
56
|
*/
|
|
62
57
|
readonly resourcePolicy?: ResourcePolicy;
|
|
63
58
|
}
|
package/dist/runtime/assemble.js
CHANGED
|
@@ -19,18 +19,15 @@ import { registerExecuteAnalysis } from "../workflows/execute-analysis.js";
|
|
|
19
19
|
import { registerSandboxStep } from "../workflows/sandbox-step.js";
|
|
20
20
|
import { registerExecuteTargetAssessment, } from "../workflows/execute-target-assessment.js";
|
|
21
21
|
import { registerDataProfileWorkflow } from "../tasks/data-profile.js";
|
|
22
|
-
import { registerEphemeralWorkflow } from "../execution/ephemeral-runner.js";
|
|
23
22
|
export function assembleCoreRuntime(deps) {
|
|
24
23
|
const { conversation, workflows: wf, resourcePolicy } = deps;
|
|
25
24
|
const sandboxStep = registerSandboxStep(wf.sandboxStep);
|
|
26
25
|
const executeAnalysis = registerExecuteAnalysis(wf.buildExecuteAnalysis(sandboxStep));
|
|
27
26
|
const executeTargetAssessment = registerExecuteTargetAssessment(wf.executeTargetAssessment);
|
|
28
27
|
const dataProfile = registerDataProfileWorkflow(wf.dataProfile);
|
|
29
|
-
const ephemeral = registerEphemeralWorkflow({ ...wf.ephemeral, resourcePolicy });
|
|
30
28
|
const conversationAgent = createConversationAgent({
|
|
31
29
|
...conversation,
|
|
32
30
|
executeAnalysisWorkflow: executeAnalysis,
|
|
33
|
-
ephemeralWorkflow: ephemeral,
|
|
34
31
|
resourcePolicy,
|
|
35
32
|
});
|
|
36
33
|
return {
|
|
@@ -40,7 +37,6 @@ export function assembleCoreRuntime(deps) {
|
|
|
40
37
|
sandboxStep,
|
|
41
38
|
executeTargetAssessment,
|
|
42
39
|
dataProfile,
|
|
43
|
-
ephemeral,
|
|
44
40
|
},
|
|
45
41
|
};
|
|
46
42
|
}
|
package/dist/runtime/boot.d.ts
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* builds the conversation agent (register-before-launch invariant).
|
|
23
23
|
* 6. `beforeLaunch()` — embedder hook for host-specific pre-launch work that
|
|
24
24
|
* must attach before DBOS launch re-emits events (scheduled sweeps, an
|
|
25
|
-
*
|
|
25
|
+
* legacy-workflow reap, an agent-switch install). Runs after registration
|
|
26
26
|
* so it may close over the registered callables.
|
|
27
27
|
* 7. `launchDbos(...)` — the last registration-dependent step.
|
|
28
28
|
*
|
|
@@ -49,7 +49,7 @@ export interface BootHarnessDeps {
|
|
|
49
49
|
readonly logger: Logger;
|
|
50
50
|
/**
|
|
51
51
|
* Host-specific work that must run AFTER workflow registration and BEFORE
|
|
52
|
-
* DBOS launch (scheduled sweeps,
|
|
52
|
+
* DBOS launch (scheduled sweeps, legacy-row reap, agent-switch install). The
|
|
53
53
|
* booted runtime is not returned until after launch, so this hook captures
|
|
54
54
|
* whatever it needs from the embedder's own composition root.
|
|
55
55
|
*/
|
package/dist/runtime/boot.js
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* builds the conversation agent (register-before-launch invariant).
|
|
23
23
|
* 6. `beforeLaunch()` — embedder hook for host-specific pre-launch work that
|
|
24
24
|
* must attach before DBOS launch re-emits events (scheduled sweeps, an
|
|
25
|
-
*
|
|
25
|
+
* legacy-workflow reap, an agent-switch install). Runs after registration
|
|
26
26
|
* so it may close over the registered callables.
|
|
27
27
|
* 7. `launchDbos(...)` — the last registration-dependent step.
|
|
28
28
|
*
|
package/dist/runtime/dbos.d.ts
CHANGED
|
@@ -67,11 +67,10 @@ export declare function shutdownDbos({ logger: injected }: {
|
|
|
67
67
|
logger: Logger;
|
|
68
68
|
}): Promise<void>;
|
|
69
69
|
/**
|
|
70
|
-
* Cancel any `ephemeral:`-prefixed PENDING workflow this executor owns
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
* awaiter and must never re-execute. DBOS has no "zero recovery" knob, and
|
|
70
|
+
* Cancel any legacy `ephemeral:`-prefixed PENDING workflow this executor owns
|
|
71
|
+
* before `launchDbos`, whose recovery would otherwise re-dispatch rows created
|
|
72
|
+
* by older releases. No current workflow creates this prefix. DBOS has no
|
|
73
|
+
* "zero recovery" knob, and
|
|
75
74
|
* `launch()` starts recovery itself, so there is no post-launch window to
|
|
76
75
|
* cancel from — the only race-free point is a direct system-DB UPDATE before
|
|
77
76
|
* launch. A CANCELLED row is excluded from the recovery query (which selects
|
package/dist/runtime/dbos.js
CHANGED
|
@@ -109,11 +109,10 @@ export async function shutdownDbos({ logger: injected }) {
|
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
/**
|
|
112
|
-
* Cancel any `ephemeral:`-prefixed PENDING workflow this executor owns
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
* awaiter and must never re-execute. DBOS has no "zero recovery" knob, and
|
|
112
|
+
* Cancel any legacy `ephemeral:`-prefixed PENDING workflow this executor owns
|
|
113
|
+
* before `launchDbos`, whose recovery would otherwise re-dispatch rows created
|
|
114
|
+
* by older releases. No current workflow creates this prefix. DBOS has no
|
|
115
|
+
* "zero recovery" knob, and
|
|
117
116
|
* `launch()` starts recovery itself, so there is no post-launch window to
|
|
118
117
|
* cancel from — the only race-free point is a direct system-DB UPDATE before
|
|
119
118
|
* launch. A CANCELLED row is excluded from the recovery query (which selects
|
|
@@ -134,7 +133,7 @@ export async function sweepEphemeralWorkflows({ pool, logger: injectedLogger, ex
|
|
|
134
133
|
values: [Date.now(), executorID],
|
|
135
134
|
});
|
|
136
135
|
if (rowCount && rowCount > 0) {
|
|
137
|
-
logger.info("swept
|
|
136
|
+
logger.info("swept legacy ephemeral workflow rows", { executorID, swept: rowCount });
|
|
138
137
|
}
|
|
139
138
|
}
|
|
140
139
|
catch (err) {
|
|
@@ -142,7 +141,7 @@ export async function sweepEphemeralWorkflows({ pool, logger: injectedLogger, ex
|
|
|
142
141
|
if (err && typeof err === "object" && "code" in err && err.code === "42P01") {
|
|
143
142
|
return;
|
|
144
143
|
}
|
|
145
|
-
logger.error("ephemeral sweep failed", { executorID, ...logger.errorFields(err) });
|
|
144
|
+
logger.error("legacy ephemeral-row sweep failed", { executorID, ...logger.errorFields(err) });
|
|
146
145
|
}
|
|
147
146
|
}
|
|
148
147
|
/** Snapshot of DBOS lifecycle state — read by the readiness probe. */
|
|
@@ -24,7 +24,7 @@ export interface MountPlanCoords {
|
|
|
24
24
|
/**
|
|
25
25
|
* Enforced read-only: emit no read-write step mount. The container sees only
|
|
26
26
|
* the read-only analysis tree (plus container-local `/tmp`). Used by the
|
|
27
|
-
*
|
|
27
|
+
* a generic read-only agent that must not mutate analysis files.
|
|
28
28
|
*/
|
|
29
29
|
readOnly?: boolean;
|
|
30
30
|
}
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
* step and throws directly, so the seam forwards its `Promise<void>` as-is.)
|
|
33
33
|
* Workflow consumers (`sandbox-step.ts`) already wrap each seam call in
|
|
34
34
|
* `DBOS.runStep`, so the re-thrown error records the step as failed;
|
|
35
|
-
* non-workflow consumers (`reaper`, `watchdog`,
|
|
35
|
+
* non-workflow consumers (`reaper`, `watchdog`,
|
|
36
36
|
* `data-profile`, `run-exec`) already handle a thrown failure. They are
|
|
37
37
|
* untouched by this wave.
|
|
38
38
|
*/
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
* step and throws directly, so the seam forwards its `Promise<void>` as-is.)
|
|
33
33
|
* Workflow consumers (`sandbox-step.ts`) already wrap each seam call in
|
|
34
34
|
* `DBOS.runStep`, so the re-thrown error records the step as failed;
|
|
35
|
-
* non-workflow consumers (`reaper`, `watchdog`,
|
|
35
|
+
* non-workflow consumers (`reaper`, `watchdog`,
|
|
36
36
|
* `data-profile`, `run-exec`) already handle a thrown failure. They are
|
|
37
37
|
* untouched by this wave.
|
|
38
38
|
*/
|
package/dist/sandbox/types.d.ts
CHANGED
|
@@ -241,7 +241,7 @@ export interface CreateSandboxMeta {
|
|
|
241
241
|
* a sandbox with none. */
|
|
242
242
|
resources: ResourceSpec;
|
|
243
243
|
/** Enforced read-only: provision with no read-write step mount, only the
|
|
244
|
-
* read-only analysis tree
|
|
244
|
+
* read-only analysis tree for generic read-only agents. */
|
|
245
245
|
readOnly?: boolean;
|
|
246
246
|
}
|
|
247
247
|
/**
|
|
@@ -22,7 +22,7 @@ export interface ValidatePlanOptions {
|
|
|
22
22
|
* Host per-step resource ceilings. When present, a step whose declared
|
|
23
23
|
* `resources` exceed them is an error — the planner gets actionable
|
|
24
24
|
* feedback at plan time instead of a silent clamp at sandbox creation.
|
|
25
|
-
* The plan-generation path passes this; `
|
|
25
|
+
* The plan-generation path passes this; `execute_analysis` plan mode deliberately does
|
|
26
26
|
* not, so stored plans that predate the policy keep running (the
|
|
27
27
|
* sandbox-creation clamp remains their backstop).
|
|
28
28
|
*/
|
package/dist/state/index.d.ts
CHANGED
|
@@ -11,15 +11,15 @@ export type { Querier } from "./db.js";
|
|
|
11
11
|
export { upsertAnalysis, resolveAnalysisBilling, suspendAnalysis, resumeAnalysis, loadAnalysisStatus } from "./analyses.js";
|
|
12
12
|
export { upsertArtifact, upsertArtifacts, queryInputArtifacts, queryStepArtifactPaths, queryUnsyncedStepArtifacts, updateArtifactId, updateFileIds, countArtifactsForRun, } from "./artifacts.js";
|
|
13
13
|
export type { RegisterArtifactInput, InputArtifactMeta, StepArtifactRef } from "./artifacts.js";
|
|
14
|
-
export { insertRun, RunDedupCollisionError, updateRunStatus, promoteFailedToPartial, setRunMandate, // oss-core-managed-ok: run-mandate ledger (nullable; OSS leaves null)
|
|
14
|
+
export { insertRun, reserveRunById, RunDedupCollisionError, RunIdentityCollisionError, updateRunStatus, promoteFailedToPartial, setRunMandate, // oss-core-managed-ok: run-mandate ledger (nullable; OSS leaves null)
|
|
15
15
|
setRunSynthesisOutcome, queryRun, queryActiveRun, queryRunsByAnalysis, queryRunsByThread, } from "./runs.js";
|
|
16
|
-
export type { InsertRunInput } from "./runs.js";
|
|
16
|
+
export type { InsertRunInput, RunReservation } from "./runs.js";
|
|
17
17
|
export { insertStepExecution, seedStepExecutions, sweepPendingStepExecutions, updateStepExecution, queryStepsByRun } from "./step-executions.js";
|
|
18
18
|
export type { InsertStepExecutionInput, SeedStepExecutionRow, UpdateStepExecutionInput } from "./step-executions.js";
|
|
19
19
|
export { setSandboxRef, setActiveExecId, clearSandboxRef, queryActiveSandboxes, reconcileReapedSandbox } from "./active-sandboxes.js";
|
|
20
20
|
export type { ActiveSandboxRow } from "./active-sandboxes.js";
|
|
21
|
-
export { insertPlan, loadPlan } from "./plans.js";
|
|
22
|
-
export type { InsertPlanInput } from "./plans.js";
|
|
21
|
+
export { insertPlan, upsertPlan, loadPlan } from "./plans.js";
|
|
22
|
+
export type { InsertPlanInput, UpsertPlanInput } from "./plans.js";
|
|
23
23
|
export { tryStartDataProfile, tryRetryDataProfile, tryRerunDataProfile, completeDataProfile, failDataProfile, expireStaleDataProfile, reconcileOrphanedDataProfile, clearDataProfile, loadDataProfileStatus, loadSeedInputFileIds, } from "./data-profile.js";
|
|
24
24
|
export type { DataProfileStatus, DataProfileResult, DataProfileInputFile, DataProfileFile, DataProfileQualityAssessment } from "./data-profile.js";
|
|
25
25
|
export { queryRunCountsByAnalyses, queryThreadCountsByAnalyses, queryDataProfileStatusByAnalyses } from "./analyses-metrics.js";
|
package/dist/state/index.js
CHANGED
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
export { initCortexState } from "./init.js";
|
|
10
10
|
export { upsertAnalysis, resolveAnalysisBilling, suspendAnalysis, resumeAnalysis, loadAnalysisStatus } from "./analyses.js";
|
|
11
11
|
export { upsertArtifact, upsertArtifacts, queryInputArtifacts, queryStepArtifactPaths, queryUnsyncedStepArtifacts, updateArtifactId, updateFileIds, countArtifactsForRun, } from "./artifacts.js";
|
|
12
|
-
export { insertRun, RunDedupCollisionError, updateRunStatus, promoteFailedToPartial, setRunMandate, // oss-core-managed-ok: run-mandate ledger (nullable; OSS leaves null)
|
|
12
|
+
export { insertRun, reserveRunById, RunDedupCollisionError, RunIdentityCollisionError, updateRunStatus, promoteFailedToPartial, setRunMandate, // oss-core-managed-ok: run-mandate ledger (nullable; OSS leaves null)
|
|
13
13
|
setRunSynthesisOutcome, queryRun, queryActiveRun, queryRunsByAnalysis, queryRunsByThread, } from "./runs.js";
|
|
14
14
|
export { insertStepExecution, seedStepExecutions, sweepPendingStepExecutions, updateStepExecution, queryStepsByRun } from "./step-executions.js";
|
|
15
15
|
export { setSandboxRef, setActiveExecId, clearSandboxRef, queryActiveSandboxes, reconcileReapedSandbox } from "./active-sandboxes.js";
|
|
16
|
-
export { insertPlan, loadPlan } from "./plans.js";
|
|
16
|
+
export { insertPlan, upsertPlan, loadPlan } from "./plans.js";
|
|
17
17
|
export { tryStartDataProfile, tryRetryDataProfile, tryRerunDataProfile, completeDataProfile, failDataProfile, expireStaleDataProfile, reconcileOrphanedDataProfile, clearDataProfile, loadDataProfileStatus, loadSeedInputFileIds, } from "./data-profile.js";
|
|
18
18
|
export { queryRunCountsByAnalyses, queryThreadCountsByAnalyses, queryDataProfileStatusByAnalyses } from "./analyses-metrics.js";
|
|
19
19
|
export { AnalysisStateRowSchema, ArtifactRole, ArtifactRowSchema, RunStatus, StepExecutionStatus, SynthesisStatus, CortexRunRowSchema, StepExecutionRowSchema, CortexPlanRowSchema, } from "./schema.js";
|
package/dist/state/plans.js
CHANGED
|
@@ -6,7 +6,7 @@ import { randomUUID } from "node:crypto";
|
|
|
6
6
|
import { tryMutation, tryQuery } from "../lib/db-result.js";
|
|
7
7
|
/**
|
|
8
8
|
* `cortex_plans.plan_id` shape contract — `pln-` followed by 8 lowercase hex
|
|
9
|
-
* chars. The chat trigger (`tools/execute-
|
|
9
|
+
* chars. The chat trigger (`tools/execute-analysis.ts`) enforces the same regex on
|
|
10
10
|
* its tool input; `upsertPlan` re-asserts it because it accepts a caller-derived
|
|
11
11
|
* id rather than minting one.
|
|
12
12
|
*/
|
package/dist/state/runs.d.ts
CHANGED
|
@@ -22,6 +22,14 @@ export interface InsertRunInput {
|
|
|
22
22
|
mandateJti?: string | null;
|
|
23
23
|
mandateExpiresAt?: string | null;
|
|
24
24
|
}
|
|
25
|
+
export interface RunReservation {
|
|
26
|
+
readonly row: CortexRunRow;
|
|
27
|
+
readonly inserted: boolean;
|
|
28
|
+
}
|
|
29
|
+
export declare class RunIdentityCollisionError extends Error {
|
|
30
|
+
readonly runId: string;
|
|
31
|
+
constructor(runId: string);
|
|
32
|
+
}
|
|
25
33
|
/**
|
|
26
34
|
* Raised when `insertRun` collides with the partial-unique index
|
|
27
35
|
* `idx_cortex_runs_active_plan` — i.e., an active run already exists for
|
|
@@ -43,6 +51,8 @@ export declare class RunDedupCollisionError extends Error {
|
|
|
43
51
|
* the err channel as a `DbError`.
|
|
44
52
|
*/
|
|
45
53
|
export declare function insertRun(pool: Querier, input: InsertRunInput): ResultAsync<void, DbError>;
|
|
54
|
+
/** Reserve a deterministic run id, treating same-analysis PK conflict as redelivery. */
|
|
55
|
+
export declare function reserveRunById(pool: Querier, input: InsertRunInput): ResultAsync<RunReservation, DbError>;
|
|
46
56
|
/**
|
|
47
57
|
* Recover the active run row for `(analysisId, planId)` after `insertRun`
|
|
48
58
|
* raises `RunDedupCollisionError`. The partial-unique index guarantees at
|
package/dist/state/runs.js
CHANGED
|
@@ -10,6 +10,14 @@
|
|
|
10
10
|
* `err(constraint_violation)` via `tryMutation`.
|
|
11
11
|
*/
|
|
12
12
|
import { tryMutation, tryQuery } from "../lib/db-result.js";
|
|
13
|
+
export class RunIdentityCollisionError extends Error {
|
|
14
|
+
runId;
|
|
15
|
+
constructor(runId) {
|
|
16
|
+
super(`run id ${runId} already belongs to another analysis`);
|
|
17
|
+
this.runId = runId;
|
|
18
|
+
this.name = "RunIdentityCollisionError";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
13
21
|
/**
|
|
14
22
|
* Raised when `insertRun` collides with the partial-unique index
|
|
15
23
|
* `idx_cortex_runs_active_plan` — i.e., an active run already exists for
|
|
@@ -65,6 +73,45 @@ export function insertRun(pool, input) {
|
|
|
65
73
|
return e;
|
|
66
74
|
});
|
|
67
75
|
}
|
|
76
|
+
/** Reserve a deterministic run id, treating same-analysis PK conflict as redelivery. */
|
|
77
|
+
export function reserveRunById(pool, input) {
|
|
78
|
+
const now = new Date().toISOString();
|
|
79
|
+
return tryMutation("runs.reserveRunById", async () => {
|
|
80
|
+
const result = await pool.query({
|
|
81
|
+
text: `INSERT INTO cortex_runs
|
|
82
|
+
(run_id, analysis_id, thread_id, workflow_name,
|
|
83
|
+
status, started_at, plan_id, mandate_jti, mandate_expires_at)
|
|
84
|
+
VALUES ($1, $2, $3, $4, 'running', $5, $6, $7, $8)
|
|
85
|
+
ON CONFLICT (run_id) DO NOTHING
|
|
86
|
+
RETURNING run_id`,
|
|
87
|
+
values: [
|
|
88
|
+
input.runId,
|
|
89
|
+
input.analysisId,
|
|
90
|
+
input.threadId ?? null,
|
|
91
|
+
input.workflowName,
|
|
92
|
+
now,
|
|
93
|
+
input.planId ?? null,
|
|
94
|
+
input.mandateJti ?? null,
|
|
95
|
+
input.mandateExpiresAt ?? null,
|
|
96
|
+
],
|
|
97
|
+
});
|
|
98
|
+
return (result.rowCount ?? 0) > 0;
|
|
99
|
+
}).andThen((inserted) => tryQuery("runs.reserveRunById.reload", async () => {
|
|
100
|
+
const result = await pool.query({
|
|
101
|
+
text: `SELECT run_id, analysis_id, thread_id, workflow_name,
|
|
102
|
+
status, started_at, completed_at, error, synthesis_status, synthesis_reason, parts,
|
|
103
|
+
mandate_jti, mandate_expires_at, plan_id
|
|
104
|
+
FROM cortex_runs
|
|
105
|
+
WHERE run_id = $1`,
|
|
106
|
+
values: [input.runId],
|
|
107
|
+
});
|
|
108
|
+
return result.rows[0] ?? null;
|
|
109
|
+
}).map((row) => {
|
|
110
|
+
if (!row || row.analysis_id !== input.analysisId)
|
|
111
|
+
throw new RunIdentityCollisionError(input.runId);
|
|
112
|
+
return { row: mapRunRow(row), inserted };
|
|
113
|
+
}));
|
|
114
|
+
}
|
|
68
115
|
function isPartialUniqueViolation(err) {
|
|
69
116
|
if (err === null || typeof err !== "object")
|
|
70
117
|
return false;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Pool } from "pg";
|
|
2
|
+
import { type AgentSession } from "../auth/types.js";
|
|
3
|
+
import type { ResourcePolicy, ResourceSpec } from "../config/resource-limits.js";
|
|
4
|
+
import type { Logger } from "../lib/logger.js";
|
|
5
|
+
import type { ChatProvider } from "../providers/types.js";
|
|
6
|
+
export declare const AD_HOC_ROUTER_AGENT_ID = "adhoc-router";
|
|
7
|
+
export declare const AD_HOC_ROUTER_TIMEOUT_MS = 10000;
|
|
8
|
+
export declare const AD_HOC_FALLBACK_AGENT_ID = "scientific-executor";
|
|
9
|
+
export interface AdHocRoute {
|
|
10
|
+
readonly agentId: string;
|
|
11
|
+
readonly resources: ResourceSpec;
|
|
12
|
+
readonly rationale: string;
|
|
13
|
+
readonly fallbackClass?: "timeout" | "provider_error" | "malformed" | "no_match" | "invalid_agent" | "invalid_resources";
|
|
14
|
+
}
|
|
15
|
+
export interface AdHocRouterDeps {
|
|
16
|
+
readonly provider: ChatProvider;
|
|
17
|
+
readonly model: string;
|
|
18
|
+
readonly pool: Pool;
|
|
19
|
+
readonly resourcePolicy?: ResourcePolicy;
|
|
20
|
+
readonly logger?: Logger;
|
|
21
|
+
/** Test seam; production always uses {@link AD_HOC_ROUTER_TIMEOUT_MS}. */
|
|
22
|
+
readonly timeoutMs?: number;
|
|
23
|
+
}
|
|
24
|
+
export declare function defaultAdHocResources(policy?: ResourcePolicy): ResourceSpec;
|
|
25
|
+
export declare function validAdHocResources(value: unknown, policy?: ResourcePolicy): ResourceSpec | null;
|
|
26
|
+
export declare function routeAdHocRequest(deps: AdHocRouterDeps, input: {
|
|
27
|
+
analysisId: string;
|
|
28
|
+
request: string;
|
|
29
|
+
session: AgentSession;
|
|
30
|
+
signal: AbortSignal;
|
|
31
|
+
}): Promise<AdHocRoute>;
|