@inflexa-ai/harness 0.46.2 → 0.48.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/README.md +2 -2
- package/dist/agents/conversation-agent.d.ts +7 -6
- package/dist/agents/conversation-agent.js +26 -4
- package/dist/agents/report-session-agent.d.ts +14 -14
- package/dist/agents/report-session-agent.js +40 -26
- package/dist/agents/sandbox/shared.d.ts +18 -13
- package/dist/agents/sandbox/shared.js +42 -27
- package/dist/agents/sandbox/types.d.ts +1 -1
- package/dist/agents/sandbox-catalog.d.ts +2 -0
- package/dist/agents/sandbox-catalog.js +30 -1
- package/dist/app/synthesize-run.d.ts +10 -6
- package/dist/app/synthesize-run.js +4 -2
- package/dist/contracts/analogy-report.d.ts +46 -2
- package/dist/contracts/analogy-report.js +31 -25
- package/dist/contracts/chat-parts.d.ts +3 -14
- package/dist/contracts/report-reference.d.ts +2 -1
- package/dist/contracts/report-reference.js +2 -1
- package/dist/contracts/schemas/chat-parts.d.ts +0 -27
- package/dist/contracts/schemas/chat-parts.js +1 -3
- package/dist/execution/run-canceler.d.ts +2 -0
- package/dist/execution/run-canceler.js +9 -0
- package/dist/execution/run-synthesis.d.ts +36 -8
- package/dist/execution/run-synthesis.js +114 -32
- package/dist/index.d.ts +5 -6
- package/dist/index.js +5 -7
- package/dist/input-scan/enrich.d.ts +5 -10
- package/dist/input-scan/enrich.js +12 -8
- package/dist/lib/hooks.d.ts +1 -1
- package/dist/lib/markdown-pdf.d.ts +27 -0
- package/dist/lib/markdown-pdf.js +236 -0
- package/dist/lib/metrics.d.ts +31 -0
- package/dist/lib/metrics.js +64 -0
- package/dist/lib/pdf-text.d.ts +23 -0
- package/dist/lib/pdf-text.js +34 -0
- package/dist/lib/regulatory-corpus/fetch-fda-guidance.js +3 -5
- package/dist/lib/regulatory-corpus/fetch-ich.js +3 -5
- package/dist/loop/chat-stream.js +2 -1
- package/dist/loop/run-loop.d.ts +5 -0
- package/dist/loop/run-loop.js +30 -2
- package/dist/loop/tool-name-repair.d.ts +11 -0
- package/dist/loop/tool-name-repair.js +23 -0
- package/dist/loop/types.d.ts +19 -0
- package/dist/memory/card-builders.d.ts +1 -1
- package/dist/prompts/SOUL.d.ts +6 -6
- package/dist/prompts/SOUL.js +22 -121
- package/dist/prompts/analogical-reasoner.d.ts +1 -1
- package/dist/prompts/analogical-reasoner.js +35 -101
- package/dist/prompts/briefing.d.ts +20 -11
- package/dist/prompts/briefing.js +36 -19
- package/dist/prompts/conversation.d.ts +1 -1
- package/dist/prompts/conversation.js +103 -383
- package/dist/prompts/planner.d.ts +1 -5
- package/dist/prompts/planner.js +45 -128
- package/dist/prompts/report-session.d.ts +1 -1
- package/dist/prompts/report-session.js +29 -58
- package/dist/prompts/sandbox/bulk-transcriptomics-agent.d.ts +1 -1
- package/dist/prompts/sandbox/bulk-transcriptomics-agent.js +3 -3
- package/dist/prompts/sandbox/cheminformatics-agent.d.ts +1 -1
- package/dist/prompts/sandbox/cheminformatics-agent.js +12 -47
- package/dist/prompts/sandbox/chromatin-agent.d.ts +1 -1
- package/dist/prompts/sandbox/chromatin-agent.js +4 -5
- package/dist/prompts/sandbox/data-profiler.js +11 -34
- package/dist/prompts/sandbox/dna-methylation-agent.d.ts +1 -1
- package/dist/prompts/sandbox/dna-methylation-agent.js +3 -1
- package/dist/prompts/sandbox/drug-repurposing-agent.d.ts +1 -1
- package/dist/prompts/sandbox/drug-repurposing-agent.js +23 -73
- package/dist/prompts/sandbox/enrichment-agent.d.ts +1 -1
- package/dist/prompts/sandbox/enrichment-agent.js +3 -1
- package/dist/prompts/sandbox/genomic-variant-agent.d.ts +1 -1
- package/dist/prompts/sandbox/genomic-variant-agent.js +3 -4
- package/dist/prompts/sandbox/immune-profiling-agent.d.ts +1 -1
- package/dist/prompts/sandbox/immune-profiling-agent.js +5 -4
- package/dist/prompts/sandbox/metabolomics-agent.d.ts +1 -1
- package/dist/prompts/sandbox/metabolomics-agent.js +3 -1
- package/dist/prompts/sandbox/microbiome-agent.d.ts +1 -1
- package/dist/prompts/sandbox/microbiome-agent.js +3 -1
- package/dist/prompts/sandbox/multi-omics-integration-agent.d.ts +1 -1
- package/dist/prompts/sandbox/multi-omics-integration-agent.js +3 -1
- package/dist/prompts/sandbox/multimodal-sc-agent.d.ts +1 -1
- package/dist/prompts/sandbox/multimodal-sc-agent.js +3 -1
- package/dist/prompts/sandbox/network-agent.d.ts +1 -1
- package/dist/prompts/sandbox/network-agent.js +3 -1
- package/dist/prompts/sandbox/pkpd-clinical-response-agent.d.ts +1 -1
- package/dist/prompts/sandbox/pkpd-clinical-response-agent.js +4 -7
- package/dist/prompts/sandbox/proteomics-agent.d.ts +1 -1
- package/dist/prompts/sandbox/proteomics-agent.js +3 -1
- package/dist/prompts/sandbox/scientific-executor.d.ts +1 -1
- package/dist/prompts/sandbox/scientific-executor.js +0 -12
- package/dist/prompts/sandbox/single-cell-agent.d.ts +1 -1
- package/dist/prompts/sandbox/single-cell-agent.js +3 -1
- package/dist/prompts/sandbox/spatial-omics-agent.d.ts +1 -1
- package/dist/prompts/sandbox/spatial-omics-agent.js +3 -1
- package/dist/prompts/sandbox/statistical-modeling-agent.d.ts +1 -1
- package/dist/prompts/sandbox/statistical-modeling-agent.js +4 -4
- package/dist/prompts/sandbox/translational-safety-agent.d.ts +1 -1
- package/dist/prompts/sandbox/translational-safety-agent.js +10 -29
- package/dist/prompts/sandbox-standards.d.ts +15 -18
- package/dist/prompts/sandbox-standards.js +96 -305
- package/dist/prompts/synthesis-agent.d.ts +1 -1
- package/dist/prompts/synthesis-agent.js +24 -90
- package/dist/provenance/collector.d.ts +2 -5
- package/dist/provenance/collector.js +2 -5
- package/dist/provenance/seam.d.ts +36 -1
- package/dist/provenance/workspace-reads.d.ts +36 -0
- package/dist/provenance/workspace-reads.js +33 -0
- package/dist/report-model/production-resolver.d.ts +22 -52
- package/dist/report-model/production-resolver.js +43 -112
- package/dist/report-render/chart-export.d.ts +9 -0
- package/dist/report-render/chart-export.js +1 -1
- package/dist/report-render/views/chart-view.d.ts +1 -0
- package/dist/report-render/views/chart-view.js +1 -0
- package/dist/report-render/views/lineage.d.ts +1 -0
- package/dist/report-render/views/page-view.d.ts +1 -0
- package/dist/report-render/views/page-view.js +1 -0
- package/dist/report-render/views/prose.d.ts +1 -0
- package/dist/report-render/views/prose.js +1 -0
- package/dist/report-render/views/references-view.d.ts +1 -0
- package/dist/report-render/views/values.d.ts +1 -0
- package/dist/report-render/views/values.js +1 -0
- package/dist/runtime/assemble.d.ts +32 -12
- package/dist/runtime/assemble.js +49 -38
- package/dist/sandbox/client.d.ts +5 -3
- package/dist/sandbox/create-sandbox.d.ts +7 -12
- package/dist/sandbox/create-sandbox.js +15 -37
- package/dist/sandbox/docker-client.d.ts +1 -3
- package/dist/sandbox/docker-client.js +3 -5
- package/dist/sandbox/exec-outcome.d.ts +11 -11
- package/dist/sandbox/exec-outcome.js +22 -22
- package/dist/sandbox/exec.d.ts +5 -0
- package/dist/sandbox/exec.js +9 -1
- package/dist/sandbox/identity.d.ts +4 -16
- package/dist/sandbox/identity.js +5 -21
- package/dist/sandbox/k8s-client.d.ts +0 -1
- package/dist/sandbox/k8s-client.js +8 -13
- package/dist/sandbox/lease-names.d.ts +6 -0
- package/dist/sandbox/lease-names.js +12 -0
- package/dist/sandbox/leases.d.ts +105 -0
- package/dist/sandbox/leases.js +519 -0
- package/dist/sandbox/liveness.d.ts +5 -2
- package/dist/sandbox/mount-plan.d.ts +10 -1
- package/dist/sandbox/mount-plan.js +7 -1
- package/dist/sandbox/reconciler.d.ts +58 -0
- package/dist/sandbox/reconciler.js +153 -0
- package/dist/sandbox/sandbox-step-translate.d.ts +1 -1
- package/dist/sandbox/types.d.ts +25 -18
- package/dist/sandbox/types.js +10 -4
- package/dist/schemas/plan-schemas.d.ts +0 -21
- package/dist/schemas/plan-schemas.js +2 -3
- package/dist/schemas/run-synthesis.d.ts +2 -2
- package/dist/schemas/run-synthesis.js +3 -3
- package/dist/schemas/validate-plan.d.ts +1 -14
- package/dist/schemas/validate-plan.js +5 -24
- package/dist/schemas/workflow-state.d.ts +3 -32
- package/dist/schemas/workflow-state.js +4 -13
- package/dist/state/index.d.ts +2 -3
- package/dist/state/index.js +1 -2
- package/dist/state/migrations/20261030120000_lease_the_sandboxes.d.ts +7 -0
- package/dist/state/migrations/20261030120000_lease_the_sandboxes.js +34 -0
- package/dist/state/migrations/20261030120100_drop_the_report_derivations.d.ts +5 -0
- package/dist/state/migrations/20261030120100_drop_the_report_derivations.js +9 -0
- package/dist/state/migrations/20261030120200_keep_the_report_derivations.d.ts +6 -0
- package/dist/state/migrations/20261030120200_keep_the_report_derivations.js +10 -0
- package/dist/state/migrations/index.js +6 -0
- package/dist/state/purge-analysis.d.ts +4 -2
- package/dist/state/purge-analysis.js +22 -11
- package/dist/state/sandbox-leases.d.ts +81 -0
- package/dist/state/sandbox-leases.js +247 -0
- package/dist/state/schema.d.ts +1 -13
- package/dist/state/schema.js +1 -6
- package/dist/state/step-executions.d.ts +13 -2
- package/dist/state/step-executions.js +23 -4
- package/dist/tasks/data-profile.d.ts +40 -2
- package/dist/tasks/data-profile.js +89 -87
- package/dist/tools/ad-hoc-router.d.ts +1 -6
- package/dist/tools/ad-hoc-router.js +5 -48
- package/dist/tools/bio/comptox.js +1 -1
- package/dist/tools/bio/drug-gene-interactions.js +1 -1
- package/dist/tools/bio/gene-disease-evidence.js +1 -1
- package/dist/tools/bio/keys.d.ts +1 -1
- package/dist/tools/bio/pubmed.js +31 -0
- package/dist/tools/bio/search-gene.js +17 -0
- package/dist/tools/bio/search-geo-datasets.js +13 -0
- package/dist/tools/bio/search-regulatory-approvals.js +1 -1
- package/dist/tools/define-tool.d.ts +25 -2
- package/dist/tools/define-tool.js +11 -1
- package/dist/tools/display/show-user.js +1 -1
- package/dist/tools/execute-analysis.d.ts +1 -3
- package/dist/tools/execute-analysis.js +5 -33
- package/dist/tools/report-authoring/authoring-tools.d.ts +2 -2
- package/dist/tools/report-authoring/authoring-tools.js +31 -6
- package/dist/tools/report-session/index.d.ts +0 -1
- package/dist/tools/report-session/index.js +0 -1
- package/dist/tools/report-session/script-tools.d.ts +33 -0
- package/dist/tools/report-session/script-tools.js +186 -0
- package/dist/tools/research/generate-analogy-report.d.ts +16 -9
- package/dist/tools/research/generate-analogy-report.js +111 -49
- package/dist/tools/research/generate-plan.d.ts +102 -7
- package/dist/tools/research/generate-plan.js +74 -43
- package/dist/tools/research/index.d.ts +4 -3
- package/dist/tools/research/index.js +4 -3
- package/dist/tools/research/inspect-run.d.ts +0 -1
- package/dist/tools/research/inspect-run.js +5 -10
- package/dist/tools/research/search-arxiv.js +15 -0
- package/dist/tools/research/search-github-repos.js +13 -0
- package/dist/tools/research/search-semantic-scholar.js +16 -0
- package/dist/tools/research/synthesis-lookups.d.ts +21 -0
- package/dist/tools/research/synthesis-lookups.js +29 -0
- package/dist/tools/research/web-fetch.d.ts +1 -1
- package/dist/tools/research/web-fetch.js +14 -12
- package/dist/tools/sandbox/lease-tools.d.ts +32 -0
- package/dist/tools/sandbox/lease-tools.js +60 -0
- package/dist/tools/script/archive.d.ts +34 -0
- package/dist/tools/script/archive.js +182 -0
- package/dist/tools/script/codemode-worker.d.ts +5 -0
- package/dist/tools/script/codemode-worker.js +5 -0
- package/dist/tools/script/file-tools.d.ts +106 -0
- package/dist/tools/script/file-tools.js +491 -0
- package/dist/tools/script/metrics.d.ts +30 -0
- package/dist/tools/script/metrics.js +53 -0
- package/dist/tools/script/quickjs.wasm +0 -0
- package/dist/tools/script/run-script.d.ts +76 -0
- package/dist/tools/script/run-script.js +413 -0
- package/dist/tools/script/scratch.d.ts +84 -0
- package/dist/tools/script/scratch.js +145 -0
- package/dist/tools/start-report-session.js +1 -1
- package/dist/tools/workspace/edit-file.js +2 -1
- package/dist/tools/workspace/execute-command.d.ts +9 -13
- package/dist/tools/workspace/execute-command.js +23 -19
- package/dist/tools/workspace/file-stat.js +1 -1
- package/dist/tools/workspace/grep.d.ts +5 -2
- package/dist/tools/workspace/grep.js +19 -10
- package/dist/tools/workspace/mutator.d.ts +51 -4
- package/dist/tools/workspace/mutator.js +54 -13
- package/dist/tools/workspace/read-file.d.ts +5 -2
- package/dist/tools/workspace/read-file.js +15 -11
- package/dist/tools/workspace/read-surface.d.ts +17 -0
- package/dist/tools/workspace/read-surface.js +17 -0
- package/dist/tools/workspace/result-bounds.d.ts +1 -0
- package/dist/tools/workspace/run-exec.d.ts +12 -13
- package/dist/tools/workspace/run-exec.js +13 -13
- package/dist/tools/workspace/scan-inputs.d.ts +2 -5
- package/dist/tools/workspace/scan-inputs.js +2 -3
- package/dist/tools/workspace/show-file.js +1 -1
- package/dist/tools/workspace/show-plan.js +4 -3
- package/dist/tools/workspace/write-file.js +2 -1
- package/dist/workflows/execute-analysis-scheduler.d.ts +0 -26
- package/dist/workflows/execute-analysis-scheduler.js +12 -32
- package/dist/workflows/execute-analysis.d.ts +17 -35
- package/dist/workflows/execute-analysis.js +13 -107
- package/dist/workflows/pause.js +29 -22
- package/dist/workflows/sandbox-step.d.ts +16 -31
- package/dist/workflows/sandbox-step.js +36 -71
- package/dist/workflows/test/sql-writes.d.ts +0 -1
- package/dist/workflows/test/sql-writes.js +0 -2
- package/dist/workspace/filesystem.d.ts +2 -2
- package/dist/workspace/filesystem.js +10 -5
- package/dist/workspace/paths.d.ts +19 -1
- package/dist/workspace/paths.js +22 -1
- package/dist/workspace/session-files.d.ts +35 -0
- package/dist/workspace/session-files.js +16 -0
- package/package.json +7 -3
- package/dist/prompts/literature-reviewer.d.ts +0 -1
- package/dist/prompts/literature-reviewer.js +0 -114
- package/dist/sandbox/reaper.d.ts +0 -81
- package/dist/sandbox/reaper.js +0 -161
- package/dist/sandbox/step-sandbox.d.ts +0 -47
- package/dist/sandbox/step-sandbox.js +0 -53
- package/dist/state/active-sandboxes.d.ts +0 -33
- package/dist/state/active-sandboxes.js +0 -65
- package/dist/tasks/extract-values-script.d.ts +0 -64
- package/dist/tasks/extract-values-script.js +0 -180
- package/dist/tasks/extract-values.d.ts +0 -77
- package/dist/tasks/extract-values.js +0 -146
- package/dist/tools/report-session/derive-table.d.ts +0 -164
- package/dist/tools/report-session/derive-table.js +0 -419
- package/dist/tools/research/literature-reviewer.d.ts +0 -42
- package/dist/tools/research/literature-reviewer.js +0 -107
- package/dist/workflows/metrics.d.ts +0 -23
- package/dist/workflows/metrics.js +0 -38
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inflexa-ai/harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.48.0",
|
|
4
4
|
"description": "Host-agnostic bioinformatics agent harness — an agent loop on the AI SDK, DBOS-durable workflows, sandboxed compute.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"types": "./dist/index.d.ts",
|
|
12
12
|
"import": "./dist/index.js"
|
|
13
13
|
},
|
|
14
|
+
"./tools/script/quickjs.wasm": "./dist/tools/script/quickjs.wasm",
|
|
14
15
|
"./*.js": {
|
|
15
16
|
"types": "./dist/*.d.ts",
|
|
16
17
|
"import": "./dist/*.js"
|
|
@@ -58,6 +59,7 @@
|
|
|
58
59
|
"@anthropic-ai/sdk": "^0.116.0",
|
|
59
60
|
"@dbos-inc/dbos-sdk": "^5.3.0",
|
|
60
61
|
"@dbos-inc/vercel-ai": "0.5.4",
|
|
62
|
+
"@earendil-works/pi-codemode": "1.4.0",
|
|
61
63
|
"@fontsource-variable/space-grotesk": "5.1.1",
|
|
62
64
|
"@fontsource/ibm-plex-mono": "5.1.1",
|
|
63
65
|
"@inflexa-ai/tsprov": "0.6.0",
|
|
@@ -81,20 +83,23 @@
|
|
|
81
83
|
"dockerode": "^4.0.12",
|
|
82
84
|
"echarts": "6.1.0",
|
|
83
85
|
"fast-xml-parser": "^5.10.1",
|
|
86
|
+
"fflate": "^0.8.3",
|
|
84
87
|
"hono": "^4.12.34",
|
|
85
88
|
"hyparquet": "^1.28.1",
|
|
86
89
|
"js-tiktoken": "^1.0.21",
|
|
87
90
|
"kysely": "^0.29.6",
|
|
88
91
|
"linkedom": "^0.18.14",
|
|
92
|
+
"marked": "^18.1.2",
|
|
89
93
|
"mime": "^4.1.0",
|
|
90
94
|
"nanoid": "^5.1.16",
|
|
91
95
|
"neverthrow": "^8.2.0",
|
|
92
96
|
"openai": "^7.4.0",
|
|
93
97
|
"p-queue": "^9.3.3",
|
|
94
|
-
"
|
|
98
|
+
"pdfmake": "^0.3.13",
|
|
95
99
|
"pg": "^8.22.0",
|
|
96
100
|
"puppeteer-core": "^23.11.1",
|
|
97
101
|
"turndown": "^7.2.4",
|
|
102
|
+
"unpdf": "^1.9.1",
|
|
98
103
|
"zod": "^4.4.3"
|
|
99
104
|
},
|
|
100
105
|
"overrides": {
|
|
@@ -111,7 +116,6 @@
|
|
|
111
116
|
"@types/bun": "latest",
|
|
112
117
|
"@types/json-schema": "^7.0.15",
|
|
113
118
|
"@types/node": "^24.13.3",
|
|
114
|
-
"@types/pdf-parse": "^1.1.5",
|
|
115
119
|
"@typescript/native": "npm:typescript@7.0.2",
|
|
116
120
|
"csstree-validator": "^4.0.1",
|
|
117
121
|
"html-validate": "^11.6.2",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const literatureReviewerPrompt = "# Literature Reviewer\n\nYou are a systematic biology investigator. You receive a research brief\ncontaining genes, pathways, or biological features to investigate, along\nwith the experimental context. Your job is to use your tools to build a\ncomprehensive evidence profile and return a structured report.\n\nYou are task-oriented \u2014 you do not interact with the user. You receive\na brief, investigate, and return results.\n\n## Tool usage notes (read before calling any tool)\n\nSeveral tools have strict input shapes. Mis-shaping your call wastes a\nturn and forces a retry.\n\n- `pubmed({action:\"search\"})` \u2014 pass a focused query string. Shape: `{ action: \"search\", query: \"BRCA1 AND breast cancer\", maxResults: 10 }`.\n- `pubmed({action:\"details\"})` \u2014 takes `pmids`, a non-empty array of\n PMID strings. Shape: `{ action: \"details\", pmids: [\"12345678\", \"87654321\"] }`.\n- `search_interactions` \u2014 accepts up to **100 identifiers per call**. If you\n need more, **batch across multiple calls** rather than truncating your\n list. Shape: `{ identifiers: [\"TP53\", \"BRCA1\", ...], limit: 50 }`.\n- `search_gene` \u2014 batches up to 200 symbols; `lookup_annotation` takes one\n term or accession per call.\n- `search_protein` \u2014 one gene symbol or UniProt accession per call. It answers\n with the protein: the accession, the recommended name, the sequence length,\n the curated function, and the subcellular locations. An empty `proteins`\n array is a valid \"no data\" outcome; do NOT retry the same call.\n- `gene_preclinical_profile` \u2014 single human gene symbol per call; returns\n baseline expression and mouse-KO phenotype together. Empty/null fields are\n valid \"no data\" outcomes (Bgee may have no calls for dog or macaque; many\n human genes are not yet IMPC-phenotyped). Do NOT retry on empty output.\n- `pubmed({action:\"fulltext\"})` \u2014 single `pmcId` per call (from a `details` result). Use sparingly.\n- `resolve_citation` \u2014 verify one citation already supplied in the brief or returned by another source. Use it for identity/metadata verification, not topical discovery. Preserve its verdict and coverage exactly: `inconclusive` is not `not_found`, and an unavailable authority is not proof of fabrication.\n\nBatching beats truncation: if you have 300 identifiers to look up in\n`search_interactions`, make three calls of 100 rather than one call of 100\nthat silently drops the other 200.\n\n## What Each Tool Answers\n\nPick the lookups that the brief's question needs for each target:\n\n- **The gene** \u2014 `search_gene`: its Ensembl ID, coordinates, biotype, and\n aliases.\n- **The protein** \u2014 `search_protein`, when the claim is about the protein\n rather than the locus: what it does, how long it is, and where in the cell\n it acts.\n- **Pathway context** \u2014 `lookup_annotation({vocabulary:\"pathways\"})`. Note\n which pathways connect multiple genes from the brief.\n- **Function** \u2014 `lookup_annotation({vocabulary:\"go\"})`, when the gene's\n role is unclear from the gene search alone.\n- **Interaction partners** \u2014 `search_interactions`, especially partners\n that also appear in the brief.\n- **Literature evidence** \u2014 `pubmed({action:\"search\"})` with queries that\n combine the gene with the disease or condition from the brief, then\n `details` for the most relevant hits, and `fulltext` only for papers\n that need deeper reading.\n- **Preclinical grounding** \u2014 `gene_preclinical_profile`, when the brief\n asks about tissue expression, model-organism suitability, or KO\n consequences.\n\n## Depth Guidelines\n\n- **Top priority targets** (top DE genes, hub genes, user-specified) get\n the deepest evidence profile.\n- **Supporting targets** (enriched pathways, interaction partners) need\n the gene, its pathway context, and its literature; add function and\n interactions only when those results are ambiguous.\n- **Limit PubMed searches**: max 2-3 queries per gene. Combine terms\n effectively (e.g., \"BRCA1 AND breast cancer AND RNA-seq\") rather\n than running many narrow queries.\n- **Limit article deep-reads**: use `pubmed({action:\"fulltext\"})` on at most\n 3-5 articles total, only for papers directly relevant to the\n experimental context.\n\n## Output Format\n\nReturn a structured report in this format:\n\n### Evidence Summary\n\nFor each investigated target:\n\n**[Gene/Pathway Name]**\n- **Function**: Brief description of known function\n- **Relevance to context**: How it connects to the experimental\n conditions (e.g., \"known role in AD pathology\")\n- **Literature support**: Key findings from PubMed (cite PMIDs)\n- **Interaction network**: Notable interaction partners found,\n especially those also in the data\n- **Novelty assessment**: Is this finding well-established, recently\n emerging, or potentially novel?\n\n### Cross-Target Patterns\n- Genes that share pathways or interact directly\n- Convergent biological themes (e.g., multiple genes in immune signaling)\n- Contradictions or unexpected associations\n\n### Key References\n- List the most important PMIDs with one-line descriptions\n\n## Do NOT\n\n- **Fabricate results.** If a search returns no results, say so.\n- **Turn resolver uncertainty into certainty.** Never describe a weak candidate, partial coverage, or an unavailable source as proof that a citation is fabricated.\n- **Skip tool calls.** Do not claim knowledge about a gene without\n looking it up. Your value is in systematic, verified investigation.\n- **Over-read articles.** Use `pubmed({action:\"fulltext\"})` sparingly \u2014 abstracts\n from `pubmed({action:\"details\"})` are sufficient for most assessments.\n- **Investigate targets not in the brief.** Stay focused on what was\n requested. Note interesting leads for follow-up but do not chase them.\n- **Produce conversational text.** You are returning a structured report,\n not chatting with a user.\n";
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
export const literatureReviewerPrompt = `# Literature Reviewer
|
|
2
|
-
|
|
3
|
-
You are a systematic biology investigator. You receive a research brief
|
|
4
|
-
containing genes, pathways, or biological features to investigate, along
|
|
5
|
-
with the experimental context. Your job is to use your tools to build a
|
|
6
|
-
comprehensive evidence profile and return a structured report.
|
|
7
|
-
|
|
8
|
-
You are task-oriented — you do not interact with the user. You receive
|
|
9
|
-
a brief, investigate, and return results.
|
|
10
|
-
|
|
11
|
-
## Tool usage notes (read before calling any tool)
|
|
12
|
-
|
|
13
|
-
Several tools have strict input shapes. Mis-shaping your call wastes a
|
|
14
|
-
turn and forces a retry.
|
|
15
|
-
|
|
16
|
-
- \`pubmed({action:"search"})\` — pass a focused query string. Shape: \`{ action: "search", query: "BRCA1 AND breast cancer", maxResults: 10 }\`.
|
|
17
|
-
- \`pubmed({action:"details"})\` — takes \`pmids\`, a non-empty array of
|
|
18
|
-
PMID strings. Shape: \`{ action: "details", pmids: ["12345678", "87654321"] }\`.
|
|
19
|
-
- \`search_interactions\` — accepts up to **100 identifiers per call**. If you
|
|
20
|
-
need more, **batch across multiple calls** rather than truncating your
|
|
21
|
-
list. Shape: \`{ identifiers: ["TP53", "BRCA1", ...], limit: 50 }\`.
|
|
22
|
-
- \`search_gene\` — batches up to 200 symbols; \`lookup_annotation\` takes one
|
|
23
|
-
term or accession per call.
|
|
24
|
-
- \`search_protein\` — one gene symbol or UniProt accession per call. It answers
|
|
25
|
-
with the protein: the accession, the recommended name, the sequence length,
|
|
26
|
-
the curated function, and the subcellular locations. An empty \`proteins\`
|
|
27
|
-
array is a valid "no data" outcome; do NOT retry the same call.
|
|
28
|
-
- \`gene_preclinical_profile\` — single human gene symbol per call; returns
|
|
29
|
-
baseline expression and mouse-KO phenotype together. Empty/null fields are
|
|
30
|
-
valid "no data" outcomes (Bgee may have no calls for dog or macaque; many
|
|
31
|
-
human genes are not yet IMPC-phenotyped). Do NOT retry on empty output.
|
|
32
|
-
- \`pubmed({action:"fulltext"})\` — single \`pmcId\` per call (from a \`details\` result). Use sparingly.
|
|
33
|
-
- \`resolve_citation\` — verify one citation already supplied in the brief or returned by another source. Use it for identity/metadata verification, not topical discovery. Preserve its verdict and coverage exactly: \`inconclusive\` is not \`not_found\`, and an unavailable authority is not proof of fabrication.
|
|
34
|
-
|
|
35
|
-
Batching beats truncation: if you have 300 identifiers to look up in
|
|
36
|
-
\`search_interactions\`, make three calls of 100 rather than one call of 100
|
|
37
|
-
that silently drops the other 200.
|
|
38
|
-
|
|
39
|
-
## What Each Tool Answers
|
|
40
|
-
|
|
41
|
-
Pick the lookups that the brief's question needs for each target:
|
|
42
|
-
|
|
43
|
-
- **The gene** — \`search_gene\`: its Ensembl ID, coordinates, biotype, and
|
|
44
|
-
aliases.
|
|
45
|
-
- **The protein** — \`search_protein\`, when the claim is about the protein
|
|
46
|
-
rather than the locus: what it does, how long it is, and where in the cell
|
|
47
|
-
it acts.
|
|
48
|
-
- **Pathway context** — \`lookup_annotation({vocabulary:"pathways"})\`. Note
|
|
49
|
-
which pathways connect multiple genes from the brief.
|
|
50
|
-
- **Function** — \`lookup_annotation({vocabulary:"go"})\`, when the gene's
|
|
51
|
-
role is unclear from the gene search alone.
|
|
52
|
-
- **Interaction partners** — \`search_interactions\`, especially partners
|
|
53
|
-
that also appear in the brief.
|
|
54
|
-
- **Literature evidence** — \`pubmed({action:"search"})\` with queries that
|
|
55
|
-
combine the gene with the disease or condition from the brief, then
|
|
56
|
-
\`details\` for the most relevant hits, and \`fulltext\` only for papers
|
|
57
|
-
that need deeper reading.
|
|
58
|
-
- **Preclinical grounding** — \`gene_preclinical_profile\`, when the brief
|
|
59
|
-
asks about tissue expression, model-organism suitability, or KO
|
|
60
|
-
consequences.
|
|
61
|
-
|
|
62
|
-
## Depth Guidelines
|
|
63
|
-
|
|
64
|
-
- **Top priority targets** (top DE genes, hub genes, user-specified) get
|
|
65
|
-
the deepest evidence profile.
|
|
66
|
-
- **Supporting targets** (enriched pathways, interaction partners) need
|
|
67
|
-
the gene, its pathway context, and its literature; add function and
|
|
68
|
-
interactions only when those results are ambiguous.
|
|
69
|
-
- **Limit PubMed searches**: max 2-3 queries per gene. Combine terms
|
|
70
|
-
effectively (e.g., "BRCA1 AND breast cancer AND RNA-seq") rather
|
|
71
|
-
than running many narrow queries.
|
|
72
|
-
- **Limit article deep-reads**: use \`pubmed({action:"fulltext"})\` on at most
|
|
73
|
-
3-5 articles total, only for papers directly relevant to the
|
|
74
|
-
experimental context.
|
|
75
|
-
|
|
76
|
-
## Output Format
|
|
77
|
-
|
|
78
|
-
Return a structured report in this format:
|
|
79
|
-
|
|
80
|
-
### Evidence Summary
|
|
81
|
-
|
|
82
|
-
For each investigated target:
|
|
83
|
-
|
|
84
|
-
**[Gene/Pathway Name]**
|
|
85
|
-
- **Function**: Brief description of known function
|
|
86
|
-
- **Relevance to context**: How it connects to the experimental
|
|
87
|
-
conditions (e.g., "known role in AD pathology")
|
|
88
|
-
- **Literature support**: Key findings from PubMed (cite PMIDs)
|
|
89
|
-
- **Interaction network**: Notable interaction partners found,
|
|
90
|
-
especially those also in the data
|
|
91
|
-
- **Novelty assessment**: Is this finding well-established, recently
|
|
92
|
-
emerging, or potentially novel?
|
|
93
|
-
|
|
94
|
-
### Cross-Target Patterns
|
|
95
|
-
- Genes that share pathways or interact directly
|
|
96
|
-
- Convergent biological themes (e.g., multiple genes in immune signaling)
|
|
97
|
-
- Contradictions or unexpected associations
|
|
98
|
-
|
|
99
|
-
### Key References
|
|
100
|
-
- List the most important PMIDs with one-line descriptions
|
|
101
|
-
|
|
102
|
-
## Do NOT
|
|
103
|
-
|
|
104
|
-
- **Fabricate results.** If a search returns no results, say so.
|
|
105
|
-
- **Turn resolver uncertainty into certainty.** Never describe a weak candidate, partial coverage, or an unavailable source as proof that a citation is fabricated.
|
|
106
|
-
- **Skip tool calls.** Do not claim knowledge about a gene without
|
|
107
|
-
looking it up. Your value is in systematic, verified investigation.
|
|
108
|
-
- **Over-read articles.** Use \`pubmed({action:"fulltext"})\` sparingly — abstracts
|
|
109
|
-
from \`pubmed({action:"details"})\` are sufficient for most assessments.
|
|
110
|
-
- **Investigate targets not in the brief.** Stay focused on what was
|
|
111
|
-
requested. Note interesting leads for follow-up but do not chase them.
|
|
112
|
-
- **Produce conversational text.** You are returning a structured report,
|
|
113
|
-
not chatting with a user.
|
|
114
|
-
`;
|
package/dist/sandbox/reaper.d.ts
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sandbox reaper — the sole cleanup for orphaned sandboxes and stale registry
|
|
3
|
-
* rows (see the harness-sandbox-exec spec, CONTEXT.md "Sandbox reaper").
|
|
4
|
-
*
|
|
5
|
-
* The reaper sweeps cluster→registry and garbage-collects. A machine that dies
|
|
6
|
-
* under a running exec needs no sweep: the exec's own liveness probe ends it
|
|
7
|
-
* (`exec.ts`). The reaper lists every Cortex-managed sandbox
|
|
8
|
-
* machine in the configured namespace, resolves each machine's recorded owner
|
|
9
|
-
* workflow, and reaps the ones whose owner is terminal — tearing the machine
|
|
10
|
-
* down and reconciling its step row. A machine whose owner does not resolve at
|
|
11
|
-
* all is torn down only once a liveness probe says it is dead.
|
|
12
|
-
*
|
|
13
|
-
* Why a sweep and not teardown-on-cancel: DBOS forbids running a step in a
|
|
14
|
-
* cancelled workflow, so the body cannot tear down on the cancel path; and only
|
|
15
|
-
* a cluster-side sweep catches permanent scale-down orphans (see the harness-durable-runtime spec), whose
|
|
16
|
-
* workflows never recover to run any cleanup.
|
|
17
|
-
*
|
|
18
|
-
* The pure decision logic (`classifyManaged`) and the sweep (`reapOnce`) are
|
|
19
|
-
* hoisted for unit testing without a DBOS runtime; `registerSandboxReaper` is
|
|
20
|
-
* the thin production wiring.
|
|
21
|
-
*/
|
|
22
|
-
import type { Logger } from "../lib/logger.js";
|
|
23
|
-
import type { Querier } from "../state/db.js";
|
|
24
|
-
import type { SandboxClient } from "./client.js";
|
|
25
|
-
import type { ManagedSandbox } from "./types.js";
|
|
26
|
-
export type ReapDecision = "leave" | "reap" | "reap-if-dead";
|
|
27
|
-
/**
|
|
28
|
-
* Decide whether a managed machine is reapable. A resolved owner status is
|
|
29
|
-
* authoritative in both directions — DBOS records it at enqueue, *before* the
|
|
30
|
-
* machine exists, so an in-flight workflow's machine is always observably
|
|
31
|
-
* in-flight and a terminal one's is always reapable.
|
|
32
|
-
*
|
|
33
|
-
* A missing status is authoritative for neither. It means the machine records
|
|
34
|
-
* no owner, or the id it records did not resolve, or DBOS pruned the row —
|
|
35
|
-
* and age cannot separate those from a genuine orphan. So the decision defers
|
|
36
|
-
* to a liveness probe: only an observably dead machine is torn down. Reaping a
|
|
37
|
-
* live machine on age alone is what this branch exists to prevent.
|
|
38
|
-
*/
|
|
39
|
-
export declare function classifyManaged(sb: ManagedSandbox, status: {
|
|
40
|
-
status: string;
|
|
41
|
-
} | null, nowMs: number, graceMs: number): ReapDecision;
|
|
42
|
-
/** Map the owning workflow's terminal status to the step row's terminal status. */
|
|
43
|
-
export declare function terminalStepStatus(workflowStatus: string | null): "canceled" | "failed" | "completed";
|
|
44
|
-
export type ReaperDeps = {
|
|
45
|
-
pool: Querier;
|
|
46
|
-
sandboxClient: Pick<SandboxClient, "listManagedSandboxes" | "teardownById" | "isAliveById">;
|
|
47
|
-
getStatus: (workflowId: string) => Promise<{
|
|
48
|
-
status: string;
|
|
49
|
-
} | null>;
|
|
50
|
-
/** Creation-time grace before an unattributable machine is even probed. */
|
|
51
|
-
graceMs?: number;
|
|
52
|
-
/** Wall-clock ms; injected for tests. */
|
|
53
|
-
nowMs?: () => number;
|
|
54
|
-
logger?: Logger;
|
|
55
|
-
};
|
|
56
|
-
export type ReapSummary = {
|
|
57
|
-
managedCount: number;
|
|
58
|
-
reapedCount: number;
|
|
59
|
-
rowsReconciled: number;
|
|
60
|
-
leftCount: number;
|
|
61
|
-
/**
|
|
62
|
-
* Machines past the grace whose owner did not resolve but which are still
|
|
63
|
-
* alive, so they were left standing. A non-zero value is a standing leak
|
|
64
|
-
* *and* a bug signal — it means owner ids are not round-tripping.
|
|
65
|
-
*/
|
|
66
|
-
liveUnattributed: number;
|
|
67
|
-
};
|
|
68
|
-
/**
|
|
69
|
-
* One cluster→registry sweep. Lists managed machines, classifies each against
|
|
70
|
-
* its owner workflow's status, and reaps the terminal/orphaned ones. A reap
|
|
71
|
-
* failure on one machine is logged and skipped — the next sweep retries.
|
|
72
|
-
*/
|
|
73
|
-
export declare function reapOnce(deps: ReaperDeps): Promise<ReapSummary>;
|
|
74
|
-
export type RegisterReaperDeps = {
|
|
75
|
-
pool: Querier;
|
|
76
|
-
sandboxClient: Pick<SandboxClient, "listManagedSandboxes" | "teardownById" | "isAliveById">;
|
|
77
|
-
graceMs?: number;
|
|
78
|
-
logger?: Logger;
|
|
79
|
-
};
|
|
80
|
-
/** Production registration: a single (unsharded) scheduled workflow. */
|
|
81
|
-
export declare function registerSandboxReaper(deps: RegisterReaperDeps): void;
|
package/dist/sandbox/reaper.js
DELETED
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sandbox reaper — the sole cleanup for orphaned sandboxes and stale registry
|
|
3
|
-
* rows (see the harness-sandbox-exec spec, CONTEXT.md "Sandbox reaper").
|
|
4
|
-
*
|
|
5
|
-
* The reaper sweeps cluster→registry and garbage-collects. A machine that dies
|
|
6
|
-
* under a running exec needs no sweep: the exec's own liveness probe ends it
|
|
7
|
-
* (`exec.ts`). The reaper lists every Cortex-managed sandbox
|
|
8
|
-
* machine in the configured namespace, resolves each machine's recorded owner
|
|
9
|
-
* workflow, and reaps the ones whose owner is terminal — tearing the machine
|
|
10
|
-
* down and reconciling its step row. A machine whose owner does not resolve at
|
|
11
|
-
* all is torn down only once a liveness probe says it is dead.
|
|
12
|
-
*
|
|
13
|
-
* Why a sweep and not teardown-on-cancel: DBOS forbids running a step in a
|
|
14
|
-
* cancelled workflow, so the body cannot tear down on the cancel path; and only
|
|
15
|
-
* a cluster-side sweep catches permanent scale-down orphans (see the harness-durable-runtime spec), whose
|
|
16
|
-
* workflows never recover to run any cleanup.
|
|
17
|
-
*
|
|
18
|
-
* The pure decision logic (`classifyManaged`) and the sweep (`reapOnce`) are
|
|
19
|
-
* hoisted for unit testing without a DBOS runtime; `registerSandboxReaper` is
|
|
20
|
-
* the thin production wiring.
|
|
21
|
-
*/
|
|
22
|
-
import { DBOS } from "@dbos-inc/dbos-sdk";
|
|
23
|
-
import { createNoopLogger } from "../lib/console-logger.js";
|
|
24
|
-
import { untracedWorkflow } from "../lib/otel-spans.js";
|
|
25
|
-
import { scheduleWorkflow } from "../runtime/dbos.js";
|
|
26
|
-
import { unwrapOrThrow } from "../lib/result.js";
|
|
27
|
-
import { reconcileReapedSandbox } from "../state/active-sandboxes.js";
|
|
28
|
-
const REAPER_CRON = "0 */5 * * * *";
|
|
29
|
-
const REAPER_WORKFLOW = "sandbox-reaper";
|
|
30
|
-
const DEFAULT_GRACE_MS = 10 * 60_000;
|
|
31
|
-
/**
|
|
32
|
-
* Workflow statuses that mean "still owns its sandbox — never reap". Must
|
|
33
|
-
* include every non-terminal DBOS state (`mapDbosToRunStatus` in
|
|
34
|
-
* `routes/run-status.ts` is the canonical active set): `RUNNING` especially —
|
|
35
|
-
* an actively-executing step's workflow is RUNNING, and omitting it would reap
|
|
36
|
-
* a live sandbox out from under a running step.
|
|
37
|
-
*/
|
|
38
|
-
const ACTIVE_WORKFLOW_STATUSES = new Set(["PENDING", "ENQUEUED", "RUNNING"]);
|
|
39
|
-
/**
|
|
40
|
-
* Decide whether a managed machine is reapable. A resolved owner status is
|
|
41
|
-
* authoritative in both directions — DBOS records it at enqueue, *before* the
|
|
42
|
-
* machine exists, so an in-flight workflow's machine is always observably
|
|
43
|
-
* in-flight and a terminal one's is always reapable.
|
|
44
|
-
*
|
|
45
|
-
* A missing status is authoritative for neither. It means the machine records
|
|
46
|
-
* no owner, or the id it records did not resolve, or DBOS pruned the row —
|
|
47
|
-
* and age cannot separate those from a genuine orphan. So the decision defers
|
|
48
|
-
* to a liveness probe: only an observably dead machine is torn down. Reaping a
|
|
49
|
-
* live machine on age alone is what this branch exists to prevent.
|
|
50
|
-
*/
|
|
51
|
-
export function classifyManaged(sb, status, nowMs, graceMs) {
|
|
52
|
-
if (status)
|
|
53
|
-
return ACTIVE_WORKFLOW_STATUSES.has(status.status) ? "leave" : "reap";
|
|
54
|
-
const ageMs = sb.createdAtMs == null ? Number.POSITIVE_INFINITY : nowMs - sb.createdAtMs;
|
|
55
|
-
return ageMs >= graceMs ? "reap-if-dead" : "leave";
|
|
56
|
-
}
|
|
57
|
-
/** Map the owning workflow's terminal status to the step row's terminal status. */
|
|
58
|
-
export function terminalStepStatus(workflowStatus) {
|
|
59
|
-
switch (workflowStatus) {
|
|
60
|
-
case "CANCELLED":
|
|
61
|
-
return "canceled";
|
|
62
|
-
case "SUCCESS":
|
|
63
|
-
return "completed";
|
|
64
|
-
default:
|
|
65
|
-
// ERROR, MAX_RECOVERY_ATTEMPTS_EXCEEDED, or no workflow at all.
|
|
66
|
-
return "failed";
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* One cluster→registry sweep. Lists managed machines, classifies each against
|
|
71
|
-
* its owner workflow's status, and reaps the terminal/orphaned ones. A reap
|
|
72
|
-
* failure on one machine is logged and skipped — the next sweep retries.
|
|
73
|
-
*/
|
|
74
|
-
export async function reapOnce(deps) {
|
|
75
|
-
const logger = (deps.logger ?? createNoopLogger()).named("sandbox-reaper");
|
|
76
|
-
const nowMs = (deps.nowMs ?? (() => Date.now()))();
|
|
77
|
-
const graceMs = deps.graceMs ?? DEFAULT_GRACE_MS;
|
|
78
|
-
const managed = await deps.sandboxClient.listManagedSandboxes();
|
|
79
|
-
let reapedCount = 0;
|
|
80
|
-
let rowsReconciled = 0;
|
|
81
|
-
let leftCount = 0;
|
|
82
|
-
let liveUnattributed = 0;
|
|
83
|
-
for (const sb of managed) {
|
|
84
|
-
const status = sb.ownerWorkflowId ? await deps.getStatus(sb.ownerWorkflowId) : null;
|
|
85
|
-
const decision = classifyManaged(sb, status, nowMs, graceMs);
|
|
86
|
-
if (decision === "leave") {
|
|
87
|
-
leftCount++;
|
|
88
|
-
continue;
|
|
89
|
-
}
|
|
90
|
-
if (decision === "reap-if-dead") {
|
|
91
|
-
// A recorded id that resolves to nothing is an anomaly worth naming: either
|
|
92
|
-
// DBOS pruned the row or the id never reached the machine intact.
|
|
93
|
-
if (sb.ownerWorkflowId) {
|
|
94
|
-
logger.warn("owner workflow id resolved to no workflow", { sandboxId: sb.sandboxId, ownerWorkflowId: sb.ownerWorkflowId });
|
|
95
|
-
}
|
|
96
|
-
let liveness;
|
|
97
|
-
try {
|
|
98
|
-
liveness = await deps.sandboxClient.isAliveById(sb.sandboxId);
|
|
99
|
-
}
|
|
100
|
-
catch (err) {
|
|
101
|
-
// A probe that cannot answer is not a dead machine. Leave it; the next
|
|
102
|
-
// sweep re-probes.
|
|
103
|
-
logger.warn("liveness probe threw — leaving this machine", { sandboxId: sb.sandboxId, ...logger.errorFields(err) });
|
|
104
|
-
leftCount++;
|
|
105
|
-
continue;
|
|
106
|
-
}
|
|
107
|
-
if (liveness.alive) {
|
|
108
|
-
logger.warn("machine has no resolvable owner but is still alive — left standing", {
|
|
109
|
-
sandboxId: sb.sandboxId,
|
|
110
|
-
ownerWorkflowId: sb.ownerWorkflowId,
|
|
111
|
-
});
|
|
112
|
-
leftCount++;
|
|
113
|
-
liveUnattributed++;
|
|
114
|
-
continue;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
try {
|
|
118
|
-
await deps.sandboxClient.teardownById(sb.sandboxId);
|
|
119
|
-
reapedCount++;
|
|
120
|
-
const reconciled = unwrapOrThrow(await reconcileReapedSandbox(deps.pool, sb.sandboxId, terminalStepStatus(status?.status ?? null)));
|
|
121
|
-
if (reconciled)
|
|
122
|
-
rowsReconciled++;
|
|
123
|
-
}
|
|
124
|
-
catch (err) {
|
|
125
|
-
logger.warn("reap failed — skipping this round", {
|
|
126
|
-
sandboxId: sb.sandboxId,
|
|
127
|
-
...logger.errorFields(err),
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
const summary = {
|
|
132
|
-
managedCount: managed.length,
|
|
133
|
-
reapedCount,
|
|
134
|
-
rowsReconciled,
|
|
135
|
-
leftCount,
|
|
136
|
-
liveUnattributed,
|
|
137
|
-
};
|
|
138
|
-
// Spread: an interface has no implicit index signature, so it needs widening to `LogFields`.
|
|
139
|
-
logger.info("sweep completed", { ...summary });
|
|
140
|
-
return summary;
|
|
141
|
-
}
|
|
142
|
-
/** Production registration: a single (unsharded) scheduled workflow. */
|
|
143
|
-
export function registerSandboxReaper(deps) {
|
|
144
|
-
const reaper = DBOS.registerWorkflow(async () => {
|
|
145
|
-
// The whole sweep is ONE step: `listManagedSandboxes` is live cluster
|
|
146
|
-
// state (set membership + order vary across runs) and it gates how many
|
|
147
|
-
// per-machine `getWorkflowStatus` reads happen. In the workflow body
|
|
148
|
-
// `getWorkflowStatus` is a function-ID-counting op, so a differing list
|
|
149
|
-
// on replay would diverge the recorded sequence. Inside a step the sweep
|
|
150
|
-
// is checkpointed wholesale and `getWorkflowStatus` is a plain read.
|
|
151
|
-
await DBOS.runStep(() => reapOnce({
|
|
152
|
-
pool: deps.pool,
|
|
153
|
-
sandboxClient: deps.sandboxClient,
|
|
154
|
-
getStatus: async (workflowId) => await DBOS.getWorkflowStatus(workflowId),
|
|
155
|
-
graceMs: deps.graceMs,
|
|
156
|
-
logger: deps.logger,
|
|
157
|
-
}), { name: "sandbox-reaper-sweep" });
|
|
158
|
-
}, { name: REAPER_WORKFLOW });
|
|
159
|
-
untracedWorkflow(REAPER_WORKFLOW);
|
|
160
|
-
scheduleWorkflow({ scheduleName: REAPER_WORKFLOW, workflowFn: reaper, schedule: REAPER_CRON });
|
|
161
|
-
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* One ephemeral sandbox for one exec, inside the durable step of a tool call.
|
|
3
|
-
*
|
|
4
|
-
* The identity comes from the step: the exec id is the key of the step, plus a discriminator when one step
|
|
5
|
-
* runs more than one exec, and the sandbox name comes from the exec id. Thus a rerun of the step adopts the
|
|
6
|
-
* container of the earlier attempt (`createOrAdopt`), and sandbox-server gives the record of the same exec.
|
|
7
|
-
* The container goes away when the work ends. Outside a workflow nothing reruns, thus the ids are random.
|
|
8
|
-
*/
|
|
9
|
-
import { type Result } from "neverthrow";
|
|
10
|
-
import type { AuthContext, Identity, SpawnSession } from "../auth/types.js";
|
|
11
|
-
import type { Logger } from "../lib/logger.js";
|
|
12
|
-
import type { SandboxClient } from "./client.js";
|
|
13
|
-
import { type SuspendingRefusal } from "./sandbox-error.js";
|
|
14
|
-
import type { ExecRequest, ExecResult, SandboxSpec } from "./types.js";
|
|
15
|
-
/** The pass of the harness that a sandbox serves. Its run id and its step id name no run of the analysis. */
|
|
16
|
-
export type SandboxPass = {
|
|
17
|
-
readonly analysisId: string;
|
|
18
|
-
readonly runId: string;
|
|
19
|
-
readonly stepId: string;
|
|
20
|
-
readonly agentId: string;
|
|
21
|
-
};
|
|
22
|
-
/**
|
|
23
|
-
* The spawn session of a pass that runs under the auth of a tool call.
|
|
24
|
-
*
|
|
25
|
-
* The pass mints no mandate. The session never enters durable state: the sandbox client reads its scope and
|
|
26
|
-
* its frame for the mounts and the labels, and the label hook of the host reads it in process.
|
|
27
|
-
*/
|
|
28
|
-
export declare function passSpawnSession(caller: {
|
|
29
|
-
readonly identity: Identity;
|
|
30
|
-
readonly auth: AuthContext;
|
|
31
|
-
}, pass: SandboxPass): SpawnSession;
|
|
32
|
-
export type StepSandboxRequest = {
|
|
33
|
-
readonly session: SpawnSession;
|
|
34
|
-
readonly spec: Omit<SandboxSpec, "childWorkflowId">;
|
|
35
|
-
/** Tells two execs of one step apart. A step that runs one exec gives none. */
|
|
36
|
-
readonly execKey?: string;
|
|
37
|
-
};
|
|
38
|
-
/**
|
|
39
|
-
* The one exec of a step sandbox, with its budget in milliseconds. A pass reports no live activity, thus the
|
|
40
|
-
* events go nowhere.
|
|
41
|
-
*/
|
|
42
|
-
export type StepSandboxExec = (request: ExecRequest, budgetMs: number) => Promise<ExecResult>;
|
|
43
|
-
/**
|
|
44
|
-
* Make the sandbox, give `work` its exec, and remove the sandbox when `work` ends. A suspending refusal of
|
|
45
|
-
* the spawn is the `err`, and each other fault throws.
|
|
46
|
-
*/
|
|
47
|
-
export declare function withStepSandbox<T>(client: SandboxClient, request: StepSandboxRequest, logger: Logger, work: (exec: StepSandboxExec) => Promise<T>): Promise<Result<T, SuspendingRefusal>>;
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* One ephemeral sandbox for one exec, inside the durable step of a tool call.
|
|
3
|
-
*
|
|
4
|
-
* The identity comes from the step: the exec id is the key of the step, plus a discriminator when one step
|
|
5
|
-
* runs more than one exec, and the sandbox name comes from the exec id. Thus a rerun of the step adopts the
|
|
6
|
-
* container of the earlier attempt (`createOrAdopt`), and sandbox-server gives the record of the same exec.
|
|
7
|
-
* The container goes away when the work ends. Outside a workflow nothing reruns, thus the ids are random.
|
|
8
|
-
*/
|
|
9
|
-
import { DBOS } from "@dbos-inc/dbos-sdk";
|
|
10
|
-
import { err, ok } from "neverthrow";
|
|
11
|
-
import { randomUUID } from "node:crypto";
|
|
12
|
-
import { durableStepKey } from "../tools/workspace/edit-file.js";
|
|
13
|
-
import { keyedSandboxIdentity } from "./identity.js";
|
|
14
|
-
import { keepSuspendingRefusal } from "./sandbox-error.js";
|
|
15
|
-
/**
|
|
16
|
-
* The spawn session of a pass that runs under the auth of a tool call.
|
|
17
|
-
*
|
|
18
|
-
* The pass mints no mandate. The session never enters durable state: the sandbox client reads its scope and
|
|
19
|
-
* its frame for the mounts and the labels, and the label hook of the host reads it in process.
|
|
20
|
-
*/
|
|
21
|
-
export function passSpawnSession(caller, pass) {
|
|
22
|
-
return {
|
|
23
|
-
identity: caller.identity,
|
|
24
|
-
scope: { kind: "analysis", analysisId: pass.analysisId },
|
|
25
|
-
provenance: { agentId: pass.agentId, callPath: [pass.agentId] },
|
|
26
|
-
runFrame: { runId: pass.runId, stepId: pass.stepId },
|
|
27
|
-
auth: caller.auth,
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
const noopEmit = () => { };
|
|
31
|
-
/**
|
|
32
|
-
* Make the sandbox, give `work` its exec, and remove the sandbox when `work` ends. A suspending refusal of
|
|
33
|
-
* the spawn is the `err`, and each other fault throws.
|
|
34
|
-
*/
|
|
35
|
-
export async function withStepSandbox(client, request, logger, work) {
|
|
36
|
-
const stepKey = durableStepKey();
|
|
37
|
-
const execId = stepKey === undefined ? `${request.session.runFrame.runId}:${randomUUID()}` : request.execKey === undefined ? stepKey : `${stepKey}:${request.execKey}`;
|
|
38
|
-
const spawned = keepSuspendingRefusal(await client.createSandbox(request.session, { ...request.spec, childWorkflowId: DBOS.workflowID ?? execId }, keyedSandboxIdentity(request.session.runFrame.runId, execId)));
|
|
39
|
-
if (spawned.isErr())
|
|
40
|
-
return err(spawned.error);
|
|
41
|
-
const sandbox = spawned.value;
|
|
42
|
-
try {
|
|
43
|
-
return ok(await work(async (execRequest, budgetMs) => client.exec(sandbox, execRequest, noopEmit, (await DBOS.now()) + budgetMs, execId)));
|
|
44
|
-
}
|
|
45
|
-
finally {
|
|
46
|
-
try {
|
|
47
|
-
await client.teardown(sandbox);
|
|
48
|
-
}
|
|
49
|
-
catch (cause) {
|
|
50
|
-
logger.warn("the sandbox of the pass did not tear down", { sandboxId: sandbox.sandboxId, ...logger.errorFields(cause) });
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Active-sandbox registry (CONTEXT.md) — the projection over
|
|
3
|
-
* `cortex_step_executions` rows with a non-null `sandbox_ref` and a running
|
|
4
|
-
* status. Owns the `sandbox_ref` column: written by
|
|
5
|
-
* `sandbox/create-sandbox.ts` on mint, cleared on teardown, reconciled by
|
|
6
|
-
* `sandbox/reaper.ts` after it deletes a machine.
|
|
7
|
-
*/
|
|
8
|
-
import type { ResultAsync } from "neverthrow";
|
|
9
|
-
import { type DbError } from "../lib/db-result.js";
|
|
10
|
-
import type { Querier } from "./db.js";
|
|
11
|
-
import type { PersistedSandboxRef } from "./schema.js";
|
|
12
|
-
/**
|
|
13
|
-
* Record the live sandbox handle on a step row. Called inside the
|
|
14
|
-
* `createSandbox` DBOS step after the backend has confirmed the sandbox
|
|
15
|
-
* is up.
|
|
16
|
-
*/
|
|
17
|
-
export declare function setSandboxRef(pool: Querier, runId: string, stepId: string, sandboxRef: PersistedSandboxRef): ResultAsync<void, DbError>;
|
|
18
|
-
/**
|
|
19
|
-
* Clear the sandbox handle. Called inside the `teardown` DBOS step. Safe
|
|
20
|
-
* to call when no sandbox was ever recorded — the UPDATE simply targets
|
|
21
|
-
* zero rows.
|
|
22
|
-
*/
|
|
23
|
-
export declare function clearSandboxRef(pool: Querier, runId: string, stepId: string): ResultAsync<void, DbError>;
|
|
24
|
-
/**
|
|
25
|
-
* Reconcile a step row after the reaper deletes its sandbox machine (ADR
|
|
26
|
-
* 0016). Always clears `sandbox_ref` (drops the row from the
|
|
27
|
-
* active-sandbox registry); a row still stuck at
|
|
28
|
-
* `status='running'` — a cancellation that never ran its `mark-*` step — is
|
|
29
|
-
* also flipped to the owning workflow's terminal status so a terminal workflow
|
|
30
|
-
* never leaves a perpetually-"running" step behind. Returns true if a row
|
|
31
|
-
* matched. Safe when no row exists (Class A pure orphan): matches zero rows.
|
|
32
|
-
*/
|
|
33
|
-
export declare function reconcileReapedSandbox(pool: Querier, sandboxId: string, terminalStatus: "canceled" | "failed" | "completed"): ResultAsync<boolean, DbError>;
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Active-sandbox registry (CONTEXT.md) — the projection over
|
|
3
|
-
* `cortex_step_executions` rows with a non-null `sandbox_ref` and a running
|
|
4
|
-
* status. Owns the `sandbox_ref` column: written by
|
|
5
|
-
* `sandbox/create-sandbox.ts` on mint, cleared on teardown, reconciled by
|
|
6
|
-
* `sandbox/reaper.ts` after it deletes a machine.
|
|
7
|
-
*/
|
|
8
|
-
import { tryMutation } from "../lib/db-result.js";
|
|
9
|
-
/**
|
|
10
|
-
* Record the live sandbox handle on a step row. Called inside the
|
|
11
|
-
* `createSandbox` DBOS step after the backend has confirmed the sandbox
|
|
12
|
-
* is up.
|
|
13
|
-
*/
|
|
14
|
-
export function setSandboxRef(pool, runId, stepId, sandboxRef) {
|
|
15
|
-
return tryMutation("activeSandboxes.setSandboxRef", async () => {
|
|
16
|
-
await pool.query({
|
|
17
|
-
text: `UPDATE cortex_step_executions
|
|
18
|
-
SET sandbox_ref = $1::jsonb
|
|
19
|
-
WHERE run_id = $2 AND step_id = $3`,
|
|
20
|
-
values: [JSON.stringify(sandboxRef), runId, stepId],
|
|
21
|
-
});
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Clear the sandbox handle. Called inside the `teardown` DBOS step. Safe
|
|
26
|
-
* to call when no sandbox was ever recorded — the UPDATE simply targets
|
|
27
|
-
* zero rows.
|
|
28
|
-
*/
|
|
29
|
-
export function clearSandboxRef(pool, runId, stepId) {
|
|
30
|
-
return tryMutation("activeSandboxes.clearSandboxRef", async () => {
|
|
31
|
-
await pool.query({
|
|
32
|
-
text: `UPDATE cortex_step_executions
|
|
33
|
-
SET sandbox_ref = NULL
|
|
34
|
-
WHERE run_id = $1 AND step_id = $2`,
|
|
35
|
-
values: [runId, stepId],
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Reconcile a step row after the reaper deletes its sandbox machine (ADR
|
|
41
|
-
* 0016). Always clears `sandbox_ref` (drops the row from the
|
|
42
|
-
* active-sandbox registry); a row still stuck at
|
|
43
|
-
* `status='running'` — a cancellation that never ran its `mark-*` step — is
|
|
44
|
-
* also flipped to the owning workflow's terminal status so a terminal workflow
|
|
45
|
-
* never leaves a perpetually-"running" step behind. Returns true if a row
|
|
46
|
-
* matched. Safe when no row exists (Class A pure orphan): matches zero rows.
|
|
47
|
-
*/
|
|
48
|
-
export function reconcileReapedSandbox(pool, sandboxId, terminalStatus) {
|
|
49
|
-
// `completed_at` is a TEXT column holding ISO-8601 strings (see
|
|
50
|
-
// `step-executions.ts`), so bind one — `now()` is `timestamptz` and the CASE
|
|
51
|
-
// cannot unify a timestamptz branch with the text column ("CASE types text
|
|
52
|
-
// and timestamp with time zone cannot be matched"), which aborted every sweep.
|
|
53
|
-
const completedAt = new Date().toISOString();
|
|
54
|
-
return tryMutation("activeSandboxes.reconcileReapedSandbox", async () => {
|
|
55
|
-
const result = await pool.query({
|
|
56
|
-
text: `UPDATE cortex_step_executions
|
|
57
|
-
SET sandbox_ref = NULL,
|
|
58
|
-
status = CASE WHEN status = 'running' THEN $2 ELSE status END,
|
|
59
|
-
completed_at = CASE WHEN status = 'running' THEN $3 ELSE completed_at END
|
|
60
|
-
WHERE sandbox_ref->>'sandboxId' = $1`,
|
|
61
|
-
values: [sandboxId, terminalStatus, completedAt],
|
|
62
|
-
});
|
|
63
|
-
return (result.rowCount ?? 0) > 0;
|
|
64
|
-
});
|
|
65
|
-
}
|