@inflexa-ai/harness 0.1.1 → 0.3.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 +48 -0
- package/dist/agents/conversation-agent.d.ts +15 -11
- package/dist/agents/conversation-agent.js +31 -34
- package/dist/agents/report-builder.js +2 -1
- package/dist/agents/sandbox/bulk-transcriptomics-agent.js +1 -1
- package/dist/agents/sandbox/cheminformatics-agent.js +1 -19
- package/dist/agents/sandbox/chromatin-agent.js +1 -10
- package/dist/agents/sandbox/dna-methylation-agent.js +1 -10
- package/dist/agents/sandbox/drug-repurposing-agent.js +4 -14
- package/dist/agents/sandbox/enrichment-agent.js +1 -10
- package/dist/agents/sandbox/ephemeral-executor.js +1 -10
- package/dist/agents/sandbox/genomic-variant-agent.js +1 -3
- package/dist/agents/sandbox/immune-profiling-agent.js +1 -13
- package/dist/agents/sandbox/metabolomics-agent.js +1 -1
- package/dist/agents/sandbox/microbiome-agent.js +1 -1
- package/dist/agents/sandbox/multi-omics-integration-agent.js +1 -12
- package/dist/agents/sandbox/multimodal-sc-agent.js +1 -1
- package/dist/agents/sandbox/network-agent.js +1 -10
- package/dist/agents/sandbox/pkpd-clinical-response-agent.js +2 -8
- package/dist/agents/sandbox/proteomics-agent.js +1 -1
- package/dist/agents/sandbox/scientific-executor.js +1 -10
- package/dist/agents/sandbox/shared.d.ts +20 -6
- package/dist/agents/sandbox/shared.js +50 -39
- package/dist/agents/sandbox/single-cell-agent.js +1 -1
- package/dist/agents/sandbox/spatial-omics-agent.js +1 -1
- package/dist/agents/sandbox/statistical-modeling-agent.js +1 -1
- package/dist/agents/sandbox/translational-safety-agent.js +3 -9
- package/dist/agents/sandbox/types.d.ts +1 -1
- package/dist/agents/system-prompt.d.ts +37 -11
- package/dist/agents/system-prompt.js +35 -12
- package/dist/app/chat-turn.d.ts +3 -0
- package/dist/app/chat-turn.js +3 -1
- package/dist/app/data-profile-orientation.d.ts +34 -0
- package/dist/app/data-profile-orientation.js +117 -0
- package/dist/app/synthesize-run.d.ts +3 -0
- package/dist/app/synthesize-run.js +3 -1
- package/dist/contracts/schemas/chat-parts.d.ts +9 -9
- package/dist/execution/artifact-metadata.d.ts +3 -0
- package/dist/execution/artifact-metadata.js +10 -3
- package/dist/execution/artifact-registration.d.ts +2 -1
- package/dist/execution/artifact-registration.js +8 -2
- package/dist/execution/ephemeral-runner.d.ts +16 -0
- package/dist/execution/ephemeral-runner.js +31 -5
- package/dist/execution/post-step-pipeline.d.ts +3 -0
- package/dist/execution/post-step-pipeline.js +24 -4
- package/dist/execution/reconcile-manifest.d.ts +7 -0
- package/dist/execution/reconcile-manifest.js +19 -5
- package/dist/execution/report-runner.d.ts +3 -0
- package/dist/execution/report-runner.js +4 -4
- package/dist/execution/run-synthesis.js +20 -19
- package/dist/execution/step-summary.d.ts +3 -0
- package/dist/execution/step-summary.js +6 -3
- package/dist/index.d.ts +9 -2
- package/dist/index.js +24 -5
- package/dist/lib/chrome.d.ts +4 -1
- package/dist/lib/chrome.js +10 -7
- package/dist/lib/console-logger.d.ts +19 -0
- package/dist/lib/console-logger.js +62 -0
- package/dist/lib/logger.d.ts +88 -0
- package/dist/lib/logger.js +37 -0
- package/dist/lib/otel.d.ts +2 -1
- package/dist/lib/otel.js +8 -3
- package/dist/loop/__fixtures__/scripted-provider.d.ts +2 -2
- package/dist/loop/__fixtures__/scripted-provider.js +2 -1
- package/dist/loop/metrics.d.ts +33 -1
- package/dist/loop/metrics.js +56 -2
- package/dist/loop/run-agent.d.ts +13 -1
- package/dist/loop/run-agent.js +19 -4
- package/dist/memory/card-builders.d.ts +8 -1
- package/dist/memory/card-builders.js +20 -3
- package/dist/memory/reconstruct-cards.js +4 -3
- package/dist/memory/thread-history.d.ts +27 -2
- package/dist/memory/thread-history.js +45 -5
- package/dist/memory/working-memory.d.ts +93 -10
- package/dist/memory/working-memory.js +220 -84
- package/dist/prompts/SOUL.d.ts +22 -9
- package/dist/prompts/SOUL.js +56 -37
- package/dist/prompts/analogical-reasoner.d.ts +1 -1
- package/dist/prompts/analogical-reasoner.js +2 -2
- package/dist/prompts/briefing.d.ts +100 -0
- package/dist/prompts/briefing.js +166 -0
- package/dist/prompts/conversation.d.ts +1 -1
- package/dist/prompts/conversation.js +94 -440
- package/dist/prompts/literature-reviewer.d.ts +1 -1
- package/dist/prompts/literature-reviewer.js +10 -10
- package/dist/prompts/planner.js +28 -59
- package/dist/prompts/report-builder.d.ts +1 -1
- package/dist/prompts/report-builder.js +71 -24
- package/dist/prompts/sandbox/bulk-transcriptomics-agent.d.ts +1 -1
- package/dist/prompts/sandbox/bulk-transcriptomics-agent.js +2 -7
- package/dist/prompts/sandbox/cheminformatics-agent.d.ts +1 -1
- package/dist/prompts/sandbox/cheminformatics-agent.js +11 -15
- package/dist/prompts/sandbox/chromatin-agent.d.ts +1 -1
- package/dist/prompts/sandbox/chromatin-agent.js +3 -7
- package/dist/prompts/sandbox/data-profiler.d.ts +1 -1
- package/dist/prompts/sandbox/data-profiler.js +13 -8
- package/dist/prompts/sandbox/dna-methylation-agent.d.ts +1 -1
- package/dist/prompts/sandbox/dna-methylation-agent.js +2 -7
- package/dist/prompts/sandbox/drug-repurposing-agent.d.ts +1 -1
- package/dist/prompts/sandbox/drug-repurposing-agent.js +15 -16
- package/dist/prompts/sandbox/enrichment-agent.d.ts +1 -1
- package/dist/prompts/sandbox/enrichment-agent.js +2 -9
- package/dist/prompts/sandbox/genomic-variant-agent.d.ts +1 -1
- package/dist/prompts/sandbox/genomic-variant-agent.js +2 -6
- package/dist/prompts/sandbox/immune-profiling-agent.d.ts +1 -1
- package/dist/prompts/sandbox/immune-profiling-agent.js +3 -8
- package/dist/prompts/sandbox/metabolomics-agent.d.ts +1 -1
- package/dist/prompts/sandbox/metabolomics-agent.js +1 -7
- package/dist/prompts/sandbox/microbiome-agent.d.ts +1 -1
- package/dist/prompts/sandbox/microbiome-agent.js +2 -7
- package/dist/prompts/sandbox/multi-omics-integration-agent.d.ts +1 -1
- package/dist/prompts/sandbox/multi-omics-integration-agent.js +2 -8
- package/dist/prompts/sandbox/multimodal-sc-agent.d.ts +1 -1
- package/dist/prompts/sandbox/multimodal-sc-agent.js +2 -7
- package/dist/prompts/sandbox/network-agent.d.ts +1 -1
- package/dist/prompts/sandbox/network-agent.js +2 -7
- package/dist/prompts/sandbox/proteomics-agent.d.ts +1 -1
- package/dist/prompts/sandbox/proteomics-agent.js +2 -9
- package/dist/prompts/sandbox/single-cell-agent.d.ts +1 -1
- package/dist/prompts/sandbox/single-cell-agent.js +2 -9
- package/dist/prompts/sandbox/spatial-omics-agent.d.ts +1 -1
- package/dist/prompts/sandbox/spatial-omics-agent.js +3 -6
- package/dist/prompts/sandbox/statistical-modeling-agent.d.ts +1 -1
- package/dist/prompts/sandbox/statistical-modeling-agent.js +2 -7
- package/dist/prompts/sandbox/translational-safety-agent.d.ts +1 -1
- package/dist/prompts/sandbox/translational-safety-agent.js +13 -14
- package/dist/prompts/sandbox-standards.d.ts +22 -14
- package/dist/prompts/sandbox-standards.js +91 -71
- package/dist/prompts/synthesis-agent.d.ts +1 -1
- package/dist/prompts/synthesis-agent.js +33 -131
- package/dist/provenance/collector.js +1 -1
- package/dist/providers/ai-sdk.js +31 -4
- package/dist/providers/prompt-cache.d.ts +74 -0
- package/dist/providers/prompt-cache.js +77 -0
- package/dist/providers/types.d.ts +33 -0
- package/dist/reference-data/catalog.d.ts +131 -0
- package/dist/reference-data/catalog.js +348 -0
- package/dist/reference-data/receipt.d.ts +41 -0
- package/dist/reference-data/receipt.js +40 -0
- package/dist/runtime/boot.d.ts +2 -2
- package/dist/runtime/boot.js +1 -1
- package/dist/runtime/connection-budget.d.ts +2 -2
- package/dist/runtime/connection-budget.js +5 -3
- package/dist/runtime/dbos.d.ts +7 -7
- package/dist/runtime/dbos.js +14 -11
- package/dist/runtime/shutdown.d.ts +2 -2
- package/dist/runtime/shutdown.js +4 -3
- package/dist/sandbox/create-sandbox.d.ts +2 -2
- package/dist/sandbox/docker-client.d.ts +2 -2
- package/dist/sandbox/docker-client.js +4 -1
- package/dist/sandbox/k8s-client.d.ts +3 -0
- package/dist/sandbox/k8s-client.js +5 -4
- package/dist/sandbox/notification-sweep.d.ts +3 -3
- package/dist/sandbox/notification-sweep.js +4 -2
- package/dist/sandbox/reaper.d.ts +3 -3
- package/dist/sandbox/reaper.js +7 -4
- package/dist/sandbox/watchdog.d.ts +3 -3
- package/dist/sandbox/watchdog.js +8 -2
- package/dist/schemas/data-profile-schemas.js +5 -3
- package/dist/schemas/plan-schemas.d.ts +21 -21
- package/dist/schemas/plan-schemas.js +9 -3
- package/dist/schemas/run-synthesis.d.ts +8 -4
- package/dist/schemas/run-synthesis.js +92 -56
- package/dist/schemas/validate-plan.js +1 -1
- package/dist/schemas/workflow-state.d.ts +7 -1
- package/dist/schemas/workflow-state.js +33 -19
- package/dist/state/artifacts.d.ts +13 -0
- package/dist/state/artifacts.js +27 -0
- package/dist/state/data-profile.d.ts +64 -5
- package/dist/state/index.d.ts +3 -3
- package/dist/state/index.js +1 -1
- package/dist/state/init.d.ts +2 -1
- package/dist/state/init.js +5 -6
- package/dist/tasks/data-profile.d.ts +25 -2
- package/dist/tasks/data-profile.js +95 -39
- package/dist/tools/bio/chembl.d.ts +38 -0
- package/dist/tools/bio/chembl.js +138 -0
- package/dist/tools/bio/comptox.d.ts +50 -0
- package/dist/tools/bio/comptox.js +149 -0
- package/dist/tools/bio/get-impc-ko-profile.js +9 -6
- package/dist/tools/bio/index.d.ts +5 -17
- package/dist/tools/bio/index.js +5 -17
- package/dist/tools/bio/keys.d.ts +14 -9
- package/dist/tools/bio/keys.js +16 -16
- package/dist/tools/bio/opentargets.d.ts +38 -0
- package/dist/tools/bio/opentargets.js +89 -0
- package/dist/tools/bio/pubchem.d.ts +29 -0
- package/dist/tools/bio/pubchem.js +92 -0
- package/dist/tools/bio/pubmed.d.ts +51 -0
- package/dist/tools/bio/pubmed.js +100 -0
- package/dist/tools/bio/search-bgee-expression.js +12 -6
- package/dist/tools/bio/search-clinical-trials.js +16 -5
- package/dist/tools/bio/search-dgidb.d.ts +11 -1
- package/dist/tools/bio/search-dgidb.js +92 -78
- package/dist/tools/bio/search-faers.js +8 -4
- package/dist/tools/bio/search-geo-datasets.js +20 -6
- package/dist/tools/bio/search-gwas-catalog.d.ts +1 -1
- package/dist/tools/bio/search-pharmgkb.js +10 -4
- package/dist/tools/display/index.d.ts +1 -0
- package/dist/tools/display/index.js +1 -0
- package/dist/tools/display/normalize-echart-spec.d.ts +61 -0
- package/dist/tools/display/normalize-echart-spec.js +211 -0
- package/dist/tools/display/show-user.d.ts +6 -1
- package/dist/tools/display/show-user.js +25 -12
- package/dist/tools/execute-plan.js +5 -2
- package/dist/tools/iterate-report.d.ts +171 -118
- package/dist/tools/iterate-report.js +336 -96
- package/dist/tools/lib/chembl-client.d.ts +12 -0
- package/dist/tools/lib/chembl-client.js +127 -0
- package/dist/tools/lib/ctx-ops.d.ts +224 -0
- package/dist/tools/lib/ctx-ops.js +450 -0
- package/dist/tools/lib/iuphar-client.js +10 -1
- package/dist/tools/lib/openfda-client.js +11 -3
- package/dist/tools/lib/pathway-client.js +10 -4
- package/dist/tools/lib/pubchem-ops.d.ts +74 -0
- package/dist/tools/lib/pubchem-ops.js +224 -0
- package/dist/tools/memory/update-working-memory.d.ts +45 -7
- package/dist/tools/memory/update-working-memory.js +111 -41
- package/dist/tools/report/index.d.ts +3 -2
- package/dist/tools/report/index.js +3 -2
- package/dist/tools/research/generate-analogy-report.js +25 -12
- package/dist/tools/research/generate-plan.d.ts +12 -5
- package/dist/tools/research/generate-plan.js +168 -37
- package/dist/tools/research/index.d.ts +1 -0
- package/dist/tools/research/index.js +1 -0
- package/dist/tools/research/inspect-data-profile.d.ts +85 -0
- package/dist/tools/research/inspect-data-profile.js +161 -0
- package/dist/tools/research/literature-reviewer.d.ts +3 -0
- package/dist/tools/research/literature-reviewer.js +5 -6
- package/dist/tools/sandbox/list-available-packages.d.ts +79 -5
- package/dist/tools/sandbox/list-available-packages.js +152 -10
- package/dist/tools/sandbox/list-available-refs.d.ts +141 -5
- package/dist/tools/sandbox/list-available-refs.js +647 -134
- package/dist/tools/sandbox/report-blocker.d.ts +39 -9
- package/dist/tools/sandbox/report-blocker.js +43 -20
- package/dist/tools/workspace/execute-command.d.ts +3 -0
- package/dist/tools/workspace/execute-command.js +3 -1
- package/dist/tools/workspace/read-file.js +4 -5
- package/dist/tools/workspace/run-exec.d.ts +4 -4
- package/dist/tools/workspace/run-exec.js +4 -4
- package/dist/tools/workspace/show-file.js +10 -6
- package/dist/tools/workspace/show-plan.js +12 -6
- package/dist/tools/workspace/workspace-search.d.ts +1 -1
- package/dist/tools/workspace/workspace-search.js +11 -5
- package/dist/workflows/execute-analysis.d.ts +46 -4
- package/dist/workflows/execute-analysis.js +118 -13
- package/dist/workflows/execute-target-assessment.d.ts +3 -0
- package/dist/workflows/execute-target-assessment.js +19 -15
- package/dist/workflows/sandbox-step.d.ts +9 -0
- package/dist/workflows/sandbox-step.js +39 -17
- package/dist/workflows/target-assessment/lib/citation-validator.js +2 -2
- package/dist/workflows/target-assessment/lib/clinical-consequence-annotator.js +2 -1
- package/dist/workflows/target-assessment/lib/llm-step.d.ts +3 -0
- package/dist/workflows/target-assessment/lib/llm-step.js +3 -1
- package/dist/workflows/target-assessment/lib/structured-llm.d.ts +3 -0
- package/dist/workflows/target-assessment/lib/structured-llm.js +1 -0
- package/dist/workflows/target-assessment/phase5-persist.d.ts +3 -0
- package/dist/workflows/target-assessment/phase5-persist.js +8 -3
- package/dist/workflows/target-assessment/progress.d.ts +2 -1
- package/dist/workflows/target-assessment/progress.js +4 -3
- package/dist/workspace/filesystem.js +163 -74
- package/dist/workspace/paths.d.ts +2 -2
- package/dist/workspace/paths.js +2 -2
- package/package.json +95 -96
- package/dist/execution/filesystem-artifact-registry.d.ts +0 -57
- package/dist/execution/filesystem-artifact-registry.js +0 -92
- package/dist/lib/identifiers.d.ts +0 -8
- package/dist/lib/identifiers.js +0 -10
- package/dist/lib/mime-detect.d.ts +0 -17
- package/dist/lib/mime-detect.js +0 -29
- package/dist/lib/streamed-download.d.ts +0 -28
- package/dist/lib/streamed-download.js +0 -111
- package/dist/lib/trace-context.d.ts +0 -11
- package/dist/lib/trace-context.js +0 -49
- package/dist/providers/llm-capabilities.d.ts +0 -33
- package/dist/providers/llm-capabilities.js +0 -58
- package/dist/schemas/render-step-prompt.d.ts +0 -32
- package/dist/schemas/render-step-prompt.js +0 -76
- package/dist/tools/bio/find-approval-precedent.d.ts +0 -32
- package/dist/tools/bio/find-approval-precedent.js +0 -72
- package/dist/tools/bio/get-article-details.d.ts +0 -11
- package/dist/tools/bio/get-article-details.js +0 -20
- package/dist/tools/bio/get-article-full-text.d.ts +0 -19
- package/dist/tools/bio/get-article-full-text.js +0 -32
- package/dist/tools/bio/get-bioactivity.d.ts +0 -11
- package/dist/tools/bio/get-bioactivity.js +0 -23
- package/dist/tools/bio/get-drug-info.d.ts +0 -9
- package/dist/tools/bio/get-drug-info.js +0 -22
- package/dist/tools/bio/get-mechanism.d.ts +0 -8
- package/dist/tools/bio/get-mechanism.js +0 -18
- package/dist/tools/bio/get-pubchem-assays.d.ts +0 -22
- package/dist/tools/bio/get-pubchem-assays.js +0 -107
- package/dist/tools/bio/get-pubchem-cross-refs.d.ts +0 -15
- package/dist/tools/bio/get-pubchem-cross-refs.js +0 -68
- package/dist/tools/bio/get-target-safety.d.ts +0 -18
- package/dist/tools/bio/get-target-safety.js +0 -26
- package/dist/tools/bio/search-compounds.d.ts +0 -22
- package/dist/tools/bio/search-compounds.js +0 -160
- package/dist/tools/bio/search-ctx-chemical.d.ts +0 -50
- package/dist/tools/bio/search-ctx-chemical.js +0 -130
- package/dist/tools/bio/search-ctx-exposure.d.ts +0 -55
- package/dist/tools/bio/search-ctx-exposure.js +0 -176
- package/dist/tools/bio/search-ctx-hazard.d.ts +0 -111
- package/dist/tools/bio/search-ctx-hazard.js +0 -178
- package/dist/tools/bio/search-opentargets.d.ts +0 -11
- package/dist/tools/bio/search-opentargets.js +0 -25
- package/dist/tools/bio/search-pubchem-compound.d.ts +0 -26
- package/dist/tools/bio/search-pubchem-compound.js +0 -110
- package/dist/tools/bio/search-pubmed.d.ts +0 -17
- package/dist/tools/bio/search-pubmed.js +0 -31
- package/dist/tools/bio/search-targets.d.ts +0 -9
- package/dist/tools/bio/search-targets.js +0 -20
- package/dist/tools/bio/search-toxcast.d.ts +0 -36
- package/dist/tools/bio/search-toxcast.js +0 -141
- package/dist/workflows/register-workflows.d.ts +0 -51
- package/dist/workflows/register-workflows.js +0 -48
- package/dist/workflows/resume-execute-analysis.d.ts +0 -70
- package/dist/workflows/resume-execute-analysis.js +0 -80
- package/dist/workflows/target-assessment/correction-loop.d.ts +0 -45
- package/dist/workflows/target-assessment/correction-loop.js +0 -62
- package/dist/workflows/target-assessment/steps/phase0-resolve.d.ts +0 -71
- package/dist/workflows/target-assessment/steps/phase0-resolve.js +0 -21
- package/dist/workflows/target-assessment/steps/phase1-aggregate.d.ts +0 -1018
- package/dist/workflows/target-assessment/steps/phase1-aggregate.js +0 -25
- package/dist/workspace/mount-strategy.d.ts +0 -50
- package/dist/workspace/mount-strategy.js +0 -67
- package/dist/workspace/provenance-collector.d.ts +0 -34
- package/dist/workspace/provenance-collector.js +0 -16
- package/dist/workspace/workspace-paths.d.ts +0 -26
- package/dist/workspace/workspace-paths.js +0 -31
|
@@ -20,6 +20,10 @@
|
|
|
20
20
|
* - dispatch every `scheduleReady(plan, completedSet, startedSet)` via
|
|
21
21
|
* `DBOS.startWorkflow` with deterministic child id
|
|
22
22
|
* `"${parent.workflowID}-${N}"` (N is the step's stable index)
|
|
23
|
+
* - compose each child's seed prompt HERE, at dispatch (`composeStepSeed`,
|
|
24
|
+
* inside a durable step): the moment a step is dispatched is the first
|
|
25
|
+
* moment its dependencies' results exist, so this is the only place the
|
|
26
|
+
* seed can name them
|
|
23
27
|
* - each child input is `forStep(input.runSession, stepId)` for the
|
|
24
28
|
* session field
|
|
25
29
|
* - emit `data-dag-state` (reconciling by id) on every dispatch +
|
|
@@ -53,10 +57,13 @@ import { DBOS } from "@dbos-inc/dbos-sdk";
|
|
|
53
57
|
import { mkdir } from "node:fs/promises";
|
|
54
58
|
import { join } from "node:path";
|
|
55
59
|
import { forStep } from "../auth/types.js";
|
|
60
|
+
import { createNoopLogger } from "../lib/console-logger.js";
|
|
56
61
|
import { unwrapOrThrow } from "../lib/result.js";
|
|
57
|
-
import { RunDedupCollisionError, countArtifactsForRun, queryActiveRun, seedStepExecutions, suspendAnalysis as suspendAnalysisQuery, sweepPendingStepExecutions, updateRunStatus, } from "../state/index.js";
|
|
62
|
+
import { RunDedupCollisionError, countArtifactsForRun, loadDataProfileStatus, queryActiveRun, queryStepArtifactPaths, seedStepExecutions, suspendAnalysis as suspendAnalysisQuery, sweepPendingStepExecutions, updateRunStatus, } from "../state/index.js";
|
|
58
63
|
import { isBudgetExceeded } from "../loop/budget-exceeded.js";
|
|
59
|
-
import {
|
|
64
|
+
import { loadStepSummariesFromDisk } from "../execution/run-synthesis.js";
|
|
65
|
+
import { MAX_UPSTREAM_ARTIFACTS, composeStepBriefing } from "../prompts/briefing.js";
|
|
66
|
+
import { runDir, runStepDir, stepSubdir, stepWritePrefix, toSandboxPath } from "../workspace/paths.js";
|
|
60
67
|
import { isChatDataPart } from "../sandbox/sandbox-step-translate.js";
|
|
61
68
|
import { synthesizeRun } from "../app/synthesize-run.js";
|
|
62
69
|
import { computeTopologicalLevels, scheduleReady, validatePlanDag } from "./execute-analysis-scheduler.js";
|
|
@@ -75,23 +82,25 @@ function emitStreamPart(part) {
|
|
|
75
82
|
* host's own ledger, not here.
|
|
76
83
|
*/
|
|
77
84
|
function emitProvenanceGuarded(deps, event) {
|
|
85
|
+
const logger = (deps.logger ?? createNoopLogger()).named("executeAnalysis");
|
|
78
86
|
if (!deps.emitProvenance)
|
|
79
87
|
return;
|
|
80
88
|
try {
|
|
81
89
|
deps.emitProvenance(event);
|
|
82
90
|
}
|
|
83
91
|
catch (err) {
|
|
84
|
-
|
|
92
|
+
logger.error("emitProvenance threw", { runId: event.runId, event: event.type, ...logger.errorFields(err) });
|
|
85
93
|
}
|
|
86
94
|
}
|
|
87
95
|
/**
|
|
88
96
|
* Project the sandbox-step input handed to each child. The parent owns the
|
|
89
|
-
* level computation
|
|
90
|
-
*
|
|
97
|
+
* level computation and the seed (`prompt`, composed by `composeStepSeed` at
|
|
98
|
+
* dispatch); the rest comes from `ExecuteAnalysisInput`. The body fills in
|
|
99
|
+
* `runSession` via `forStep` after this returns. Exported for the
|
|
91
100
|
* projection/timeout-propagation tests.
|
|
92
101
|
*/
|
|
93
102
|
export function buildChildInput(args) {
|
|
94
|
-
const { input, stepId, level, runId, workflowId } = args;
|
|
103
|
+
const { input, stepId, level, runId, workflowId, prompt } = args;
|
|
95
104
|
const resources = input.resourcesByStepId[stepId];
|
|
96
105
|
if (!resources) {
|
|
97
106
|
throw new Error(`executeAnalysis: step "${stepId}" missing from resourcesByStepId — every step must declare resources`);
|
|
@@ -102,12 +111,100 @@ export function buildChildInput(args) {
|
|
|
102
111
|
stepId,
|
|
103
112
|
agentId: input.agentByStepId[stepId] ?? "scientific-executor",
|
|
104
113
|
level,
|
|
105
|
-
prompt
|
|
114
|
+
prompt,
|
|
106
115
|
parentWorkflowId: workflowId,
|
|
107
116
|
resources,
|
|
108
117
|
timeoutSeconds: input.timeoutByStepId?.[stepId],
|
|
109
118
|
};
|
|
110
119
|
}
|
|
120
|
+
// ── Seed composition (at dispatch, not at plan submission) ────────────
|
|
121
|
+
/**
|
|
122
|
+
* Compose one step's seed — the user-content message its agent opens on.
|
|
123
|
+
*
|
|
124
|
+
* The composition point is the design: the scheduler calls this at the instant
|
|
125
|
+
* it dispatches a step, which is the first instant at which every one of that
|
|
126
|
+
* step's dependencies has *finished* and written its results down. A seed
|
|
127
|
+
* rendered any earlier (at plan submission, say) cannot mention them, and the
|
|
128
|
+
* agent is left to rediscover its own upstream.
|
|
129
|
+
*
|
|
130
|
+
* Everything read here is durable state written by an already-settled child
|
|
131
|
+
* (`output/summary.md`, `cortex_artifacts`) or by the profiler
|
|
132
|
+
* (`cortex_analysis_state`) — never live in-flight state, which would not
|
|
133
|
+
* reproduce.
|
|
134
|
+
*
|
|
135
|
+
* REPLAY: the caller wraps this in a `DBOS.runStep`, so the composed string is
|
|
136
|
+
* checkpointed and a replay returns it verbatim — the DB and disk are not read
|
|
137
|
+
* again and cannot drift the seed under a recovered run. Never call it
|
|
138
|
+
* unwrapped from the workflow body.
|
|
139
|
+
*/
|
|
140
|
+
export async function composeStepSeed(args) {
|
|
141
|
+
const { input, stepId, runId, deps } = args;
|
|
142
|
+
const step = input.planStepById[stepId];
|
|
143
|
+
if (!step) {
|
|
144
|
+
throw new Error(`executeAnalysis: step "${stepId}" missing from planStepById — every step must carry its plan data`);
|
|
145
|
+
}
|
|
146
|
+
const analysisId = input.analysisId;
|
|
147
|
+
const workspaceRoot = deps.resolveWorkspaceRoot(analysisId);
|
|
148
|
+
// The paths the agent actually sees: the tree is bind-mounted at
|
|
149
|
+
// `/{analysisId}`, and the step's writable directory is its cwd. Derived
|
|
150
|
+
// through the same helpers the sandbox mount and the mutate tools use, so
|
|
151
|
+
// the seed cannot disagree with the container about where anything is.
|
|
152
|
+
const workspace = {
|
|
153
|
+
analysisRoot: `/${analysisId}`,
|
|
154
|
+
workingDir: toSandboxPath(workspaceRoot, analysisId, stepWritePrefix({ workspaceRoot, runId, stepId })),
|
|
155
|
+
};
|
|
156
|
+
// A DB failure here throws (house rules): a swallowed error would be frozen
|
|
157
|
+
// into the checkpointed seed and served to every replay of this step.
|
|
158
|
+
const profile = unwrapOrThrow(await loadDataProfileStatus(deps.pool, analysisId));
|
|
159
|
+
const upstream = await collectUpstreamHandoffs({
|
|
160
|
+
analysisId,
|
|
161
|
+
runId,
|
|
162
|
+
workspaceRoot,
|
|
163
|
+
dependsOn: step.depends_on,
|
|
164
|
+
agentByStepId: input.agentByStepId,
|
|
165
|
+
pool: deps.pool,
|
|
166
|
+
});
|
|
167
|
+
return composeStepBriefing({
|
|
168
|
+
step,
|
|
169
|
+
workspace,
|
|
170
|
+
profile: profile?.result ?? null,
|
|
171
|
+
upstream,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Gather what each completed dependency produced. Dependency order is the
|
|
176
|
+
* plan's declared `depends_on` order — deterministic, so the seed is stable.
|
|
177
|
+
*
|
|
178
|
+
* A dependency that wrote no summary is omitted rather than blocking the
|
|
179
|
+
* dispatch (`loadStepSummariesFromDisk` skips a missing file): a step that
|
|
180
|
+
* completed without a summary has nothing to hand off, and waiting for one that
|
|
181
|
+
* will never arrive would deadlock the run.
|
|
182
|
+
*/
|
|
183
|
+
async function collectUpstreamHandoffs(args) {
|
|
184
|
+
const { analysisId, runId, workspaceRoot, dependsOn, agentByStepId, pool } = args;
|
|
185
|
+
if (dependsOn.length === 0)
|
|
186
|
+
return [];
|
|
187
|
+
const summaries = await loadStepSummariesFromDisk({
|
|
188
|
+
workspaceRoot,
|
|
189
|
+
runId,
|
|
190
|
+
completedSteps: dependsOn,
|
|
191
|
+
agentByStepId,
|
|
192
|
+
});
|
|
193
|
+
const handoffs = [];
|
|
194
|
+
for (const summary of summaries) {
|
|
195
|
+
const outputDir = stepSubdir(runStepDir(runId, summary.stepId), "output");
|
|
196
|
+
const artifacts = await queryStepArtifactPaths(pool, analysisId, runId, summary.stepId, MAX_UPSTREAM_ARTIFACTS);
|
|
197
|
+
handoffs.push({
|
|
198
|
+
stepId: summary.stepId,
|
|
199
|
+
agentId: summary.agentId,
|
|
200
|
+
summaryMarkdown: summary.markdown,
|
|
201
|
+
summaryPath: `/${analysisId}/${outputDir}/summary.md`,
|
|
202
|
+
outputDir: `/${analysisId}/${outputDir}`,
|
|
203
|
+
artifacts: artifacts.map((a) => `/${analysisId}/${a.path}`),
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
return handoffs;
|
|
207
|
+
}
|
|
111
208
|
/**
|
|
112
209
|
* Run-level literature-grounded synthesis (no sandbox). Returns the
|
|
113
210
|
* quick-display findings for the run-completed card; empty when synthesis is
|
|
@@ -166,6 +263,7 @@ export function registerExecuteAnalysis(deps) {
|
|
|
166
263
|
* (the DBOS calls inside still rely on a workflow context being present).
|
|
167
264
|
*/
|
|
168
265
|
export async function runExecuteAnalysisBody(input, deps) {
|
|
266
|
+
const logger = (deps.logger ?? createNoopLogger()).named("executeAnalysis").with({ analysisId: input.analysisId });
|
|
169
267
|
// (0) Validate up-front — no side effects yet, so a malformed plan does
|
|
170
268
|
// NOT leak a runId, run authorization, or running charge.
|
|
171
269
|
validatePlanDag(input.steps);
|
|
@@ -249,7 +347,7 @@ export async function runExecuteAnalysisBody(input, deps) {
|
|
|
249
347
|
}
|
|
250
348
|
catch (err) {
|
|
251
349
|
synthesisError = err;
|
|
252
|
-
|
|
350
|
+
logger.error("synthesizeFindings failed — failing the run", { runId, ...logger.errorFields(err) });
|
|
253
351
|
}
|
|
254
352
|
}
|
|
255
353
|
// (6) collectAndComplete — terminal block. Runs on EVERY path. When synthesis
|
|
@@ -416,12 +514,18 @@ async function runSchedulerLoop(args) {
|
|
|
416
514
|
for (const stepId of admit) {
|
|
417
515
|
const idx = stepIndexById.get(stepId);
|
|
418
516
|
const childWorkflowId = `${workflowId}-${idx}`;
|
|
517
|
+
// Compose the seed HERE — the dependencies of `stepId` are all in
|
|
518
|
+
// `completed`, so this is the first (and only) moment their results
|
|
519
|
+
// can be named. Checkpointed so a replay re-dispatches the child
|
|
520
|
+
// with a byte-identical prompt instead of re-reading the DB/disk.
|
|
521
|
+
const prompt = await DBOS.runStep(() => composeStepSeed({ input, stepId, runId, deps }), { name: `compose-step-seed:${stepId}` });
|
|
419
522
|
const baseChildInput = buildChildInput({
|
|
420
523
|
input,
|
|
421
524
|
stepId,
|
|
422
525
|
level: levels.get(stepId) ?? 0,
|
|
423
526
|
runId,
|
|
424
527
|
workflowId,
|
|
528
|
+
prompt,
|
|
425
529
|
});
|
|
426
530
|
const childInput = {
|
|
427
531
|
...baseChildInput,
|
|
@@ -592,6 +696,7 @@ async function cancelInFlight(inFlight, cause, canceledByParent) {
|
|
|
592
696
|
}
|
|
593
697
|
async function collectAndComplete(args) {
|
|
594
698
|
const { input, runId, workflowId, startedAtMs, completed, failed, canceled, budgetExceeded, failureReason, forceFailed, deps } = args;
|
|
699
|
+
const logger = (deps.logger ?? createNoopLogger()).named("executeAnalysis").with({ runId, analysisId: input.analysisId });
|
|
595
700
|
const status = forceFailed
|
|
596
701
|
? "failed"
|
|
597
702
|
: deriveFinalStatus({
|
|
@@ -634,7 +739,7 @@ async function collectAndComplete(args) {
|
|
|
634
739
|
}, { name: "persist-final-status" });
|
|
635
740
|
}
|
|
636
741
|
catch (err) {
|
|
637
|
-
|
|
742
|
+
logger.error("persist-final-status failed", { status, ...logger.errorFields(err) });
|
|
638
743
|
}
|
|
639
744
|
// Sweep never-started steps to `skipped` — but ONLY on genuinely-terminal
|
|
640
745
|
// paths. The gate is the BRANCH, not the written run status: the resumable
|
|
@@ -648,7 +753,7 @@ async function collectAndComplete(args) {
|
|
|
648
753
|
}, { name: "sweep-pending-steps" });
|
|
649
754
|
}
|
|
650
755
|
catch (err) {
|
|
651
|
-
|
|
756
|
+
logger.error("sweep-pending-steps failed", logger.errorFields(err));
|
|
652
757
|
}
|
|
653
758
|
}
|
|
654
759
|
// A forced failure (synthesis threw) is terminal, not a resumable budget
|
|
@@ -660,7 +765,7 @@ async function collectAndComplete(args) {
|
|
|
660
765
|
}, { name: "suspend-analysis" });
|
|
661
766
|
}
|
|
662
767
|
catch (err) {
|
|
663
|
-
|
|
768
|
+
logger.error("suspend-analysis failed", logger.errorFields(err));
|
|
664
769
|
}
|
|
665
770
|
}
|
|
666
771
|
try {
|
|
@@ -672,7 +777,7 @@ async function collectAndComplete(args) {
|
|
|
672
777
|
}), { name: "close-running-charge" });
|
|
673
778
|
}
|
|
674
779
|
catch (err) {
|
|
675
|
-
|
|
780
|
+
logger.error("closeRunningCharge failed", { chargeReason, ...logger.errorFields(err) });
|
|
676
781
|
}
|
|
677
782
|
// Revoke the run authorization for the terminal run state. Ownership
|
|
678
783
|
// defaults to true for inputs persisted before the field existed (a
|
|
@@ -685,7 +790,7 @@ async function collectAndComplete(args) {
|
|
|
685
790
|
await DBOS.runStep(() => deps.runAuthorizer.revoke(authorization, revokeReason), { name: "revoke-run-auth" });
|
|
686
791
|
}
|
|
687
792
|
catch (err) {
|
|
688
|
-
|
|
793
|
+
logger.error("revokeRunAuthorization failed", { revokeReason, ...logger.errorFields(err) });
|
|
689
794
|
}
|
|
690
795
|
// The `run_completed` provenance was already emitted above (before the status write, to
|
|
691
796
|
// beat the CLI's poll-and-shutdown). These branches only fan out the UI stream part, whose
|
|
@@ -34,6 +34,7 @@ import type { Pool } from "pg";
|
|
|
34
34
|
import { z } from "zod";
|
|
35
35
|
import type { RunSession } from "../auth/types.js";
|
|
36
36
|
import type { RunAuthorizer } from "../execution/run-authorizer.js";
|
|
37
|
+
import type { Logger } from "../lib/logger.js";
|
|
37
38
|
import type { AgentChat } from "../providers/types.js";
|
|
38
39
|
import { TargetAssessmentInputSchema } from "./target-assessment/schemas.js";
|
|
39
40
|
import { type ProgressPhase } from "./target-assessment/progress.js";
|
|
@@ -69,6 +70,8 @@ export interface ExecuteTargetAssessmentResult {
|
|
|
69
70
|
readonly bytes: number;
|
|
70
71
|
}
|
|
71
72
|
export interface ExecuteTargetAssessmentDeps {
|
|
73
|
+
/** Operational logging seam; omitted falls back to no-op. */
|
|
74
|
+
readonly logger?: Logger;
|
|
72
75
|
readonly pool: Pool;
|
|
73
76
|
/** Run-authorization seam — the terminal path revokes through `revoke`. */
|
|
74
77
|
readonly runAuthorizer: RunAuthorizer;
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
import { DBOS } from "@dbos-inc/dbos-sdk";
|
|
34
34
|
import { z } from "zod";
|
|
35
35
|
import { forSubAgent } from "../auth/types.js";
|
|
36
|
+
import { createNoopLogger } from "../lib/console-logger.js";
|
|
36
37
|
import { unwrapOrThrow } from "../lib/result.js";
|
|
37
38
|
import { resolveTarget } from "../tools/lib/identifier-resolver.js";
|
|
38
39
|
import { TargetAssessmentInputSchema } from "./target-assessment/schemas.js";
|
|
@@ -111,6 +112,7 @@ export function registerExecuteTargetAssessment(deps) {
|
|
|
111
112
|
* (the DBOS calls inside still rely on a workflow context being present).
|
|
112
113
|
*/
|
|
113
114
|
export async function runExecuteTargetAssessmentBody(input, deps) {
|
|
115
|
+
const logger = (deps.logger ?? createNoopLogger()).with({ assessmentId: input.assessmentId });
|
|
114
116
|
// (§6.1) Outermost try/finally — every termination path dispatches the
|
|
115
117
|
// matching terminal handler. Phase 0 throws are caught here; every
|
|
116
118
|
// other phase wraps its body in coverage envelopes and does not throw.
|
|
@@ -137,7 +139,7 @@ export async function runExecuteTargetAssessmentBody(input, deps) {
|
|
|
137
139
|
// (§5.3, §6.3) Phase 0 — target resolution. The only legitimate throw
|
|
138
140
|
// site. On throw, the catch below records `phase0Error` and the
|
|
139
141
|
// terminal block dispatches `markFailed({kind: "target-unresolved"})`.
|
|
140
|
-
await emitProgress(deps.pool, input.assessmentId, "resolving");
|
|
142
|
+
await emitProgress(deps.pool, logger, input.assessmentId, "resolving");
|
|
141
143
|
const resolved = await DBOS.runStep(async () => {
|
|
142
144
|
const r = await resolveTarget(input.target);
|
|
143
145
|
return {
|
|
@@ -154,7 +156,7 @@ export async function runExecuteTargetAssessmentBody(input, deps) {
|
|
|
154
156
|
// returns the cached envelope. The per-collector run functions emit
|
|
155
157
|
// different bundle shapes; the union is widened to `unknown` here
|
|
156
158
|
// and `Phase1Bundle` is reconstructed by manifest key below.
|
|
157
|
-
await emitProgress(deps.pool, input.assessmentId, "collecting");
|
|
159
|
+
await emitProgress(deps.pool, logger, input.assessmentId, "collecting");
|
|
158
160
|
const manifest = COLLECTOR_MANIFEST;
|
|
159
161
|
const collectorCtx = { ncbiApiKey: deps.ncbiApiKey };
|
|
160
162
|
const collectorPairs = await Promise.all(manifest.map((entry) => DBOS.runStep(() => entry.run(resolved, collectorCtx), {
|
|
@@ -185,7 +187,7 @@ export async function runExecuteTargetAssessmentBody(input, deps) {
|
|
|
185
187
|
// (§5.5, §5.11) Phase 2 — decisions. Lazy billing resolution happens
|
|
186
188
|
// inside the chat provider's closure (TA-aware provider in §12). The
|
|
187
189
|
// session carries the `billingContextId` the provider reads from.
|
|
188
|
-
await emitProgress(deps.pool, input.assessmentId, "deciding");
|
|
190
|
+
await emitProgress(deps.pool, logger, input.assessmentId, "deciding");
|
|
189
191
|
const decisionSession = buildSession(input, "ta-decisions");
|
|
190
192
|
const [modulatorRes, drugsRes] = await Promise.all([
|
|
191
193
|
modulatorTriage(phase1, {
|
|
@@ -226,7 +228,7 @@ export async function runExecuteTargetAssessmentBody(input, deps) {
|
|
|
226
228
|
// (§5.6) Phase 3 — fan-out blocks. Each block iterates items via
|
|
227
229
|
// Promise.all over DBOS.runStep; `withHost` semaphores inside each
|
|
228
230
|
// item function cap concurrency to 4 per host.
|
|
229
|
-
await emitProgress(deps.pool, input.assessmentId, "fanning_out");
|
|
231
|
+
await emitProgress(deps.pool, logger, input.assessmentId, "fanning_out");
|
|
230
232
|
const triage = phase2.decisions.modulatorTriage;
|
|
231
233
|
const triageShortlist = triage.coverage === "available"
|
|
232
234
|
? triage.data.shortlist.map((m) => ({
|
|
@@ -287,7 +289,7 @@ export async function runExecuteTargetAssessmentBody(input, deps) {
|
|
|
287
289
|
},
|
|
288
290
|
};
|
|
289
291
|
// (§5.7) Phase 4 — deterministic assembly. Single DBOS step.
|
|
290
|
-
await emitProgress(deps.pool, input.assessmentId, "assembling");
|
|
292
|
+
await emitProgress(deps.pool, logger, input.assessmentId, "assembling");
|
|
291
293
|
const phase4 = await DBOS.runStep(() => phase4Assemble(deps.pool, phase3), { name: "ta-phase4-assemble" });
|
|
292
294
|
// (§5.8-pre) Approval-precedent grounding — one deterministic openFDA
|
|
293
295
|
// lookup, rendered to markdown and injected into every synthesis prompt.
|
|
@@ -301,14 +303,14 @@ export async function runExecuteTargetAssessmentBody(input, deps) {
|
|
|
301
303
|
result = await fetchApprovalPrecedents({ indication });
|
|
302
304
|
}
|
|
303
305
|
catch (err) {
|
|
304
|
-
|
|
306
|
+
logger.named("ta-approval-precedents").warn("openFDA lookup failed", { indication, ...logger.errorFields(err) });
|
|
305
307
|
result = null;
|
|
306
308
|
}
|
|
307
309
|
}
|
|
308
310
|
return renderApprovalPrecedents(indication, result);
|
|
309
311
|
}, { name: "ta-approval-precedents" });
|
|
310
312
|
// (§5.8) Phase 5 — three per-section syntheses in parallel.
|
|
311
|
-
await emitProgress(deps.pool, input.assessmentId, "synthesizing");
|
|
313
|
+
await emitProgress(deps.pool, logger, input.assessmentId, "synthesizing");
|
|
312
314
|
const synthesisDeps = (agentId) => ({
|
|
313
315
|
chatProvider: deps.chatProvider,
|
|
314
316
|
session: { ...buildSession(input, agentId) },
|
|
@@ -343,6 +345,7 @@ export async function runExecuteTargetAssessmentBody(input, deps) {
|
|
|
343
345
|
// and returns the persisted-shape bundle. Schema/derived violations
|
|
344
346
|
// throw and the terminal handler dispatches to `markFailed`.
|
|
345
347
|
const persisted = await DBOS.runStep(() => phase5Persist({
|
|
348
|
+
logger,
|
|
346
349
|
assessmentId: input.assessmentId,
|
|
347
350
|
phase4Dossier: phase4.dossier,
|
|
348
351
|
phase2,
|
|
@@ -390,10 +393,10 @@ export async function runExecuteTargetAssessmentBody(input, deps) {
|
|
|
390
393
|
await DBOS.runStep(async () => unwrapOrThrow(await setDossier(deps.pool, input.assessmentId, dossierForPersist)), {
|
|
391
394
|
name: "ta-terminal-completed",
|
|
392
395
|
});
|
|
393
|
-
await emitProgress(deps.pool, input.assessmentId, "completed");
|
|
396
|
+
await emitProgress(deps.pool, logger, input.assessmentId, "completed");
|
|
394
397
|
}
|
|
395
398
|
catch (err) {
|
|
396
|
-
|
|
399
|
+
logger.named("ta-terminal").error("setDossier failed", logger.errorFields(err));
|
|
397
400
|
}
|
|
398
401
|
await revokeRunAuthorizationSafe(deps, authorization, "target-assessment-completed");
|
|
399
402
|
recordTerminalReason("completed");
|
|
@@ -409,7 +412,7 @@ export async function runExecuteTargetAssessmentBody(input, deps) {
|
|
|
409
412
|
kind: "target-unresolved",
|
|
410
413
|
message: phase0Error instanceof Error ? phase0Error.message : String(phase0Error),
|
|
411
414
|
})), { name: "ta-terminal-failed-resolve" });
|
|
412
|
-
await emitProgress(deps.pool, input.assessmentId, "failed");
|
|
415
|
+
await emitProgress(deps.pool, logger, input.assessmentId, "failed");
|
|
413
416
|
await revokeRunAuthorizationSafe(deps, authorization, "target-assessment-failed");
|
|
414
417
|
recordTerminalReason("target-unresolved");
|
|
415
418
|
return {
|
|
@@ -425,7 +428,7 @@ export async function runExecuteTargetAssessmentBody(input, deps) {
|
|
|
425
428
|
message: schemaViolation.message,
|
|
426
429
|
details: schemaViolation.issues,
|
|
427
430
|
})), { name: "ta-terminal-failed-schema" });
|
|
428
|
-
await emitProgress(deps.pool, input.assessmentId, "failed");
|
|
431
|
+
await emitProgress(deps.pool, logger, input.assessmentId, "failed");
|
|
429
432
|
await revokeRunAuthorizationSafe(deps, authorization, "target-assessment-failed");
|
|
430
433
|
recordTerminalReason("schema-violation");
|
|
431
434
|
return {
|
|
@@ -440,7 +443,7 @@ export async function runExecuteTargetAssessmentBody(input, deps) {
|
|
|
440
443
|
kind: "derived-invariant-violation",
|
|
441
444
|
message: derivedViolation.message,
|
|
442
445
|
})), { name: "ta-terminal-failed-derived" });
|
|
443
|
-
await emitProgress(deps.pool, input.assessmentId, "failed");
|
|
446
|
+
await emitProgress(deps.pool, logger, input.assessmentId, "failed");
|
|
444
447
|
await revokeRunAuthorizationSafe(deps, authorization, "target-assessment-failed");
|
|
445
448
|
recordTerminalReason("derived-invariant-violation");
|
|
446
449
|
return {
|
|
@@ -459,7 +462,7 @@ export async function runExecuteTargetAssessmentBody(input, deps) {
|
|
|
459
462
|
name: "ta-terminal-drain-marker",
|
|
460
463
|
}).catch(() => null);
|
|
461
464
|
await DBOS.runStep(async () => unwrapOrThrow(await markAssessmentSuspended(deps.pool, input.assessmentId)), { name: "ta-terminal-suspended" });
|
|
462
|
-
await emitProgress(deps.pool, input.assessmentId, "suspended");
|
|
465
|
+
await emitProgress(deps.pool, logger, input.assessmentId, "suspended");
|
|
463
466
|
await revokeRunAuthorizationSafe(deps, authorization, "target-assessment-canceled");
|
|
464
467
|
const workflowId = DBOS.workflowID;
|
|
465
468
|
if (workflowId) {
|
|
@@ -485,7 +488,7 @@ export async function runExecuteTargetAssessmentBody(input, deps) {
|
|
|
485
488
|
kind: "unexpected-throw",
|
|
486
489
|
message: unexpectedError instanceof Error ? unexpectedError.message : String(unexpectedError),
|
|
487
490
|
})), { name: "ta-terminal-failed-unexpected" });
|
|
488
|
-
await emitProgress(deps.pool, input.assessmentId, "failed");
|
|
491
|
+
await emitProgress(deps.pool, logger, input.assessmentId, "failed");
|
|
489
492
|
await revokeRunAuthorizationSafe(deps, authorization, "target-assessment-failed");
|
|
490
493
|
recordTerminalReason("unexpected-throw");
|
|
491
494
|
return {
|
|
@@ -514,13 +517,14 @@ export async function runExecuteTargetAssessmentBody(input, deps) {
|
|
|
514
517
|
* aborted by a transient revoke failure.
|
|
515
518
|
*/
|
|
516
519
|
async function revokeRunAuthorizationSafe(deps, authorization, reason) {
|
|
520
|
+
const logger = (deps.logger ?? createNoopLogger()).named("execute-target-assessment");
|
|
517
521
|
try {
|
|
518
522
|
await DBOS.runStep(() => deps.runAuthorizer.revoke(authorization, reason), {
|
|
519
523
|
name: `ta-revoke-run-auth:${reason}`,
|
|
520
524
|
});
|
|
521
525
|
}
|
|
522
526
|
catch (err) {
|
|
523
|
-
|
|
527
|
+
logger.warn("revokeRunAuthorization failed", { reason, ...logger.errorFields(err) });
|
|
524
528
|
}
|
|
525
529
|
}
|
|
526
530
|
// Re-export the canonical TA input schema (consumers and the managed root
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
* its own self-cancel on 402.
|
|
28
28
|
*/
|
|
29
29
|
import type { Pool } from "pg";
|
|
30
|
+
import type { Logger } from "../lib/logger.js";
|
|
30
31
|
import type { AgentDefinition, LoopMessage } from "../loop/types.js";
|
|
31
32
|
import type { AgentChat, EmbeddingProvider } from "../providers/types.js";
|
|
32
33
|
import { type RunSession } from "../auth/types.js";
|
|
@@ -183,6 +184,14 @@ export interface SandboxAgentBuildContext {
|
|
|
183
184
|
*/
|
|
184
185
|
export interface SandboxStepDeps {
|
|
185
186
|
readonly pool: Pool;
|
|
187
|
+
/**
|
|
188
|
+
* Operational logging seam. Omit it and the body falls back to a no-op —
|
|
189
|
+
* but a failed step's cause is written here and NOWHERE else (`failStep`
|
|
190
|
+
* scrubs the error before it reaches the DB row, the run panel, or the
|
|
191
|
+
* parent's re-raise), so an embedder that wires nothing gives up its only
|
|
192
|
+
* account of why a step died.
|
|
193
|
+
*/
|
|
194
|
+
readonly logger?: Logger;
|
|
186
195
|
/** Non-streaming chat — drives the agent loop + the post-step sub-agents. */
|
|
187
196
|
readonly provider: AgentChat;
|
|
188
197
|
/** Write-side embedder for the post-step vector index. */
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
*/
|
|
29
29
|
import { DBOS, Error as DBOSErrors } from "@dbos-inc/dbos-sdk";
|
|
30
30
|
import { insertStepExecution, updateStepExecution } from "../state/index.js";
|
|
31
|
+
import { createNoopLogger } from "../lib/console-logger.js";
|
|
31
32
|
import { unwrapOrThrow } from "../lib/result.js";
|
|
32
33
|
import { isBudgetExceeded } from "../loop/budget-exceeded.js";
|
|
33
34
|
import { runAgent } from "../loop/run-agent.js";
|
|
@@ -101,6 +102,14 @@ export function registerSandboxStep(deps) {
|
|
|
101
102
|
* (the DBOS calls inside still rely on a workflow context being present).
|
|
102
103
|
*/
|
|
103
104
|
async function runSandboxStepBody(input, deps) {
|
|
105
|
+
// Step coordinates bind once — every record below carries them without each
|
|
106
|
+
// call site restating them. REPLAY: a recovered workflow re-executes this body
|
|
107
|
+
// and re-emits its records, so a reader dedups by (runId, stepId), not by count.
|
|
108
|
+
const logger = (deps.logger ?? createNoopLogger()).named("sandbox-step").with({
|
|
109
|
+
runId: input.runId,
|
|
110
|
+
stepId: input.stepId,
|
|
111
|
+
agentId: input.agentId,
|
|
112
|
+
});
|
|
104
113
|
const childWorkflowId = DBOS.workflowID ?? `${input.runId}-${input.stepId}`;
|
|
105
114
|
// Checkpointed so the value (and every `durationMs` derived from it) replays
|
|
106
115
|
// identically — a raw `Date.now()` in the workflow body drifts on recovery.
|
|
@@ -206,7 +215,7 @@ async function runSandboxStepBody(input, deps) {
|
|
|
206
215
|
await emitToParentStream(part);
|
|
207
216
|
}
|
|
208
217
|
catch (err) {
|
|
209
|
-
|
|
218
|
+
logger.warn("emitToParentStream failed (non-fatal)", logger.errorFields(err));
|
|
210
219
|
}
|
|
211
220
|
};
|
|
212
221
|
// Schema-conformant step-activity emitter — stable id per (runId, stepId)
|
|
@@ -230,10 +239,15 @@ async function runSandboxStepBody(input, deps) {
|
|
|
230
239
|
const failStep = async (errorClass, err) => {
|
|
231
240
|
if (err instanceof DBOSErrors.DBOSWorkflowCancelledError)
|
|
232
241
|
throw err;
|
|
233
|
-
await tryTeardown(deps, sandbox);
|
|
242
|
+
await tryTeardown(logger, deps, sandbox);
|
|
234
243
|
const durationMs = (await DBOS.now()) - startedAt;
|
|
235
|
-
|
|
236
|
-
|
|
244
|
+
// The scrub below is deliberate and load-bearing, which makes this record
|
|
245
|
+
// the ONLY account of why the step died — the thrown error carries just the
|
|
246
|
+
// generic phrase, so nothing downstream can reconstruct the cause.
|
|
247
|
+
logger.error("step failure", {
|
|
248
|
+
errorClass,
|
|
249
|
+
...logger.errorFields(err),
|
|
250
|
+
});
|
|
237
251
|
const safe = userFacingStepFailure(errorClass);
|
|
238
252
|
await DBOS.runStep(async () => {
|
|
239
253
|
unwrapOrThrow(await updateStepExecution(deps.pool, input.runId, input.stepId, {
|
|
@@ -334,7 +348,9 @@ async function runSandboxStepBody(input, deps) {
|
|
|
334
348
|
await DBOS.runStep(() => DBOS.send(input.parentWorkflowId, notification, BUDGET_EXCEEDED_TOPIC), { name: "notify-parent-budget-exceeded" });
|
|
335
349
|
}
|
|
336
350
|
catch (sendErr) {
|
|
337
|
-
|
|
351
|
+
logger.warn("notify-parent-budget-exceeded failed (non-fatal)", {
|
|
352
|
+
...logger.errorFields(sendErr),
|
|
353
|
+
});
|
|
338
354
|
}
|
|
339
355
|
// Self-cancel to CANCELLED so the parent's `getResult` observes the
|
|
340
356
|
// cancellation and triggers the 402 pause cascade. ERROR is NOT
|
|
@@ -369,7 +385,7 @@ async function runSandboxStepBody(input, deps) {
|
|
|
369
385
|
// terminal-failure status so the parent's fail-fast cascade fires.
|
|
370
386
|
const blockerOutcome = blockerHolder.outcome;
|
|
371
387
|
if (blockerOutcome) {
|
|
372
|
-
await tryTeardown(deps, sandbox);
|
|
388
|
+
await tryTeardown(logger, deps, sandbox);
|
|
373
389
|
const durationMs = (await DBOS.now()) - startedAt;
|
|
374
390
|
await DBOS.runStep(async () => {
|
|
375
391
|
unwrapOrThrow(await updateStepExecution(deps.pool, input.runId, input.stepId, {
|
|
@@ -413,7 +429,7 @@ async function runSandboxStepBody(input, deps) {
|
|
|
413
429
|
lineageCollector,
|
|
414
430
|
};
|
|
415
431
|
await emitActivity("generating-metadata", "Describing output files");
|
|
416
|
-
const manifest = await safeRunValue(() => walkStepArtifacts({
|
|
432
|
+
const manifest = await safeRunValue(logger, () => walkStepArtifacts({
|
|
417
433
|
writePrefix,
|
|
418
434
|
stepId: input.stepId,
|
|
419
435
|
runId: input.runId,
|
|
@@ -423,11 +439,11 @@ async function runSandboxStepBody(input, deps) {
|
|
|
423
439
|
// gate (`data-step-summary`, file-tree) stay replay-stable and the billed
|
|
424
440
|
// LLM calls are not re-issued on recovery. The remaining stages (walk /
|
|
425
441
|
// reconcile / sync / index) stay inline — a separate follow-up.
|
|
426
|
-
const metadataEntries = await safeRunValue(() => DBOS.runStep(() => generateStepFileMetadata(deps, postCtx, manifest), {
|
|
442
|
+
const metadataEntries = await safeRunValue(logger, () => DBOS.runStep(() => generateStepFileMetadata(deps, postCtx, manifest), {
|
|
427
443
|
name: "post-step.generate-file-metadata",
|
|
428
444
|
}), "post-step.metadata", []);
|
|
429
445
|
await emitActivity("generating-summary", "Summarizing step results");
|
|
430
|
-
const summary = await safeRunValue(() => DBOS.runStep(() => generateStepSummaryAndWrite(deps, postCtx, manifest), {
|
|
446
|
+
const summary = await safeRunValue(logger, () => DBOS.runStep(() => generateStepSummaryAndWrite(deps, postCtx, manifest), {
|
|
431
447
|
name: "post-step.generate-step-summary",
|
|
432
448
|
}), "post-step.summary", undefined);
|
|
433
449
|
// Lineage attestation (registration + sync) is fail-fast (see the artifact-manifest spec): unlike
|
|
@@ -459,10 +475,10 @@ async function runSandboxStepBody(input, deps) {
|
|
|
459
475
|
stepOutputs = collectStepOutputs(postCtx, postArtifacts);
|
|
460
476
|
}
|
|
461
477
|
catch (err) {
|
|
462
|
-
|
|
478
|
+
logger.warn("post-step.collect failed (non-fatal)", logger.errorFields(err));
|
|
463
479
|
}
|
|
464
480
|
await emitActivity("indexing", "Indexing outputs for search");
|
|
465
|
-
await safeRun(() => vectorIndexStepOutputs(deps, postCtx, postArtifacts), "post-step.vector-index");
|
|
481
|
+
await safeRun(logger, () => vectorIndexStepOutputs(deps, postCtx, postArtifacts), "post-step.vector-index");
|
|
466
482
|
// (9) teardown — destroy sandbox + clear sandbox_ref.
|
|
467
483
|
await DBOS.runStep(() => deps.sandboxClient.teardown(sandbox), { name: "sandbox.teardown" });
|
|
468
484
|
// (10) mark-terminal + emit.
|
|
@@ -537,14 +553,17 @@ async function runSandboxStepBody(input, deps) {
|
|
|
537
553
|
* swallows non-fatal failures — these are reflected in the step summary
|
|
538
554
|
* but do not fail the step itself (synthesis/sync/index can be retried).
|
|
539
555
|
*/
|
|
540
|
-
async function safeRun(fn, label) {
|
|
556
|
+
async function safeRun(logger, fn, label) {
|
|
541
557
|
try {
|
|
542
558
|
await fn();
|
|
543
559
|
}
|
|
544
560
|
catch (err) {
|
|
545
561
|
if (err instanceof DBOSErrors.DBOSWorkflowCancelledError)
|
|
546
562
|
throw err;
|
|
547
|
-
|
|
563
|
+
// The stage rides as a namespace, not interpolated prose: `label` is what
|
|
564
|
+
// says WHICH post-step stage degraded, and a reader must be able to filter
|
|
565
|
+
// on it without parsing the message.
|
|
566
|
+
logger.named(label).warn("failed (non-fatal)", logger.errorFields(err));
|
|
548
567
|
}
|
|
549
568
|
}
|
|
550
569
|
/**
|
|
@@ -552,22 +571,25 @@ async function safeRun(fn, label) {
|
|
|
552
571
|
* logs and yields `fallback` so the body can thread a safe default downstream
|
|
553
572
|
* instead of aborting the post-step pipeline.
|
|
554
573
|
*/
|
|
555
|
-
async function safeRunValue(fn, label, fallback) {
|
|
574
|
+
async function safeRunValue(logger, fn, label, fallback) {
|
|
556
575
|
try {
|
|
557
576
|
return await fn();
|
|
558
577
|
}
|
|
559
578
|
catch (err) {
|
|
560
579
|
if (err instanceof DBOSErrors.DBOSWorkflowCancelledError)
|
|
561
580
|
throw err;
|
|
562
|
-
|
|
581
|
+
// The stage rides as a namespace, not interpolated prose: `label` is what
|
|
582
|
+
// says WHICH post-step stage degraded, and a reader must be able to filter
|
|
583
|
+
// on it without parsing the message.
|
|
584
|
+
logger.named(label).warn("failed (non-fatal)", logger.errorFields(err));
|
|
563
585
|
return fallback;
|
|
564
586
|
}
|
|
565
587
|
}
|
|
566
|
-
async function tryTeardown(deps, sandbox) {
|
|
588
|
+
async function tryTeardown(logger, deps, sandbox) {
|
|
567
589
|
try {
|
|
568
590
|
await DBOS.runStep(() => deps.sandboxClient.teardown(sandbox), { name: "sandbox.teardown" });
|
|
569
591
|
}
|
|
570
592
|
catch (err) {
|
|
571
|
-
|
|
593
|
+
logger.warn("teardown failed (non-fatal)", logger.errorFields(err));
|
|
572
594
|
}
|
|
573
595
|
}
|
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
// was always 0. These patterns match the format the agent actually writes.
|
|
6
6
|
// The block content may include array index notation like `[0]`, so we
|
|
7
7
|
// use a pattern that allows `[digits]` inside the outer brackets.
|
|
8
|
-
const DOSSIER_CITE_BLOCK_RE = /\[dossier:\s*((?:[^[\]]
|
|
8
|
+
const DOSSIER_CITE_BLOCK_RE = /\[dossier:\s*((?:[^[\]]|\[\d+\])*)\]/gi;
|
|
9
9
|
// External citation IDs are plain strings — no array-index notation like `[n]` — so the simple [^\]]+ class suffices here instead of the array-index-aware pattern used by DOSSIER_CITE_BLOCK_RE.
|
|
10
10
|
const EXTERNAL_CITE_BLOCK_RE = /\[external citation:\s*([^\]]+)\]/gi;
|
|
11
11
|
// Assumes at most one `external citation:` segment per dossier block (the recommendation prompt produces at most one); a hypothetical second segment in the same block would be parsed imperfectly.
|
|
12
|
-
const EXTERNAL_SEGMENT_RE = /external citation:\s*((?:[^[\]]
|
|
12
|
+
const EXTERNAL_SEGMENT_RE = /external citation:\s*((?:[^[\]]|\[\d+\])*)/i;
|
|
13
13
|
const NCT_RE = /\bNCT\d{8}\b/g;
|
|
14
14
|
const PMID_RE = /\bPMID[:\s]?(\d{6,9})\b/g;
|
|
15
15
|
// Legacy inline format: `dossier.path.foo` written directly in prose, no
|
|
@@ -19,6 +19,7 @@ import { z } from "zod";
|
|
|
19
19
|
import { withHost } from "../../../lib/host-concurrency.js";
|
|
20
20
|
import { tryQuery, tryMutation } from "../../../lib/db-result.js";
|
|
21
21
|
import { offTargetAnnotatorPrompt } from "../../../prompts/target-assessment/off-target-annotator.js";
|
|
22
|
+
import { composeSystemPrompt } from "../../../agents/system-prompt.js";
|
|
22
23
|
import { forSubAgent } from "../../../auth/types.js";
|
|
23
24
|
import { finalText, runAgent } from "../../../loop/run-agent.js";
|
|
24
25
|
import { passthroughStep } from "../../../loop/run-step.js";
|
|
@@ -93,7 +94,7 @@ const ANNOTATOR_AGENT_ID = "off-target-annotator";
|
|
|
93
94
|
function buildAnnotatorAgent(model) {
|
|
94
95
|
return {
|
|
95
96
|
id: ANNOTATOR_AGENT_ID,
|
|
96
|
-
systemPrompt: offTargetAnnotatorPrompt,
|
|
97
|
+
systemPrompt: composeSystemPrompt(offTargetAnnotatorPrompt),
|
|
97
98
|
model,
|
|
98
99
|
tools: [],
|
|
99
100
|
maxIterations: 2,
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
* the caller bumps the attempt counter so the resumed call lands a fresh
|
|
24
24
|
* DBOS cache slot rather than replaying the cancelled prior attempt.
|
|
25
25
|
*/
|
|
26
|
+
import type { Logger } from "../../../lib/logger.js";
|
|
26
27
|
import type { AgentChat, ChatRequest, ChatResponse } from "../../../providers/types.js";
|
|
27
28
|
import type { AgentSession } from "../../../auth/types.js";
|
|
28
29
|
/**
|
|
@@ -38,6 +39,8 @@ export interface BudgetExceededMarker {
|
|
|
38
39
|
readonly error: string;
|
|
39
40
|
}
|
|
40
41
|
export interface RunLlmStepOptions {
|
|
42
|
+
/** Operational logging seam; omitted falls back to no-op. */
|
|
43
|
+
readonly logger?: Logger;
|
|
41
44
|
/** Durable-step name — already attempt-numbered by the caller. */
|
|
42
45
|
readonly stepName: string;
|
|
43
46
|
/** Agent id stamped in the marker for telemetry. */
|