@inflexa-ai/harness 0.46.2 → 0.47.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 +1 -1
- package/dist/agents/conversation-agent.d.ts +3 -8
- package/dist/agents/conversation-agent.js +24 -4
- package/dist/agents/report-session-agent.d.ts +11 -14
- package/dist/agents/report-session-agent.js +30 -17
- 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 +2 -5
- package/dist/index.js +3 -6
- package/dist/input-scan/enrich.d.ts +5 -10
- package/dist/input-scan/enrich.js +12 -8
- 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/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 +14 -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 +26 -57
- 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 +20 -9
- package/dist/runtime/assemble.js +47 -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 +0 -1
- package/dist/state/index.js +0 -1
- 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 +0 -2
- package/dist/state/step-executions.js +1 -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-session/index.d.ts +0 -1
- package/dist/tools/report-session/index.js +0 -1
- package/dist/tools/report-session/script-tools.d.ts +30 -0
- package/dist/tools/report-session/script-tools.js +177 -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/codemode-worker.d.ts +5 -0
- package/dist/tools/script/codemode-worker.js +5 -0
- package/dist/tools/script/file-tools.d.ts +100 -0
- package/dist/tools/script/file-tools.js +377 -0
- package/dist/tools/script/metrics.d.ts +21 -0
- package/dist/tools/script/metrics.js +38 -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 +326 -0
- package/dist/tools/script/scratch.d.ts +81 -0
- package/dist/tools/script/scratch.js +142 -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 +48 -4
- package/dist/tools/workspace/mutator.js +43 -12
- 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/package.json +6 -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
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* generateAnalogyReport — cross-domain analogy report as a sub-agent tool.
|
|
3
3
|
*
|
|
4
|
-
* An inline harness `AgentDefinition`
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* with no outcome gets one salvage request, then an `extraction-failed`
|
|
10
|
-
* envelope, so the frontend never renders raw prose.
|
|
4
|
+
* An inline harness `AgentDefinition` driven by `runWithSalvage`. The reasoner records its report in parts: the
|
|
5
|
+
* problem with its planned analogies, then each analogy when its search is done. A provider filter can stop a reply in
|
|
6
|
+
* the middle of a call, and a cut call is lost whole, thus a small part loses little. The wrapper assembles the report
|
|
7
|
+
* from the last accepted record of each part, else it gives the first blocker. A run with no outcome gets one salvage
|
|
8
|
+
* request, then an `extraction-failed` envelope, so the frontend never renders raw prose.
|
|
11
9
|
*/
|
|
12
10
|
import { errorMessageOf } from "../../lib/thrown.js";
|
|
13
11
|
import { textOf } from "../../lib/unknown-text.js";
|
|
14
12
|
import { ok } from "neverthrow";
|
|
15
13
|
import { z } from "zod";
|
|
16
|
-
import {
|
|
14
|
+
import { AnalogyProblemSchema, AnalogySchema, } from "@inflexa-ai/harness/contracts/analogy-report.js";
|
|
17
15
|
import { analogicalReasonerPrompt } from "../../prompts/analogical-reasoner.js";
|
|
18
16
|
import { composeSystemPrompt } from "../../agents/system-prompt.js";
|
|
19
17
|
import { forSubAgent } from "../../auth/types.js";
|
|
@@ -33,34 +31,80 @@ import { createNcbiTools } from "../bio/keys.js";
|
|
|
33
31
|
const AGENT_ID = "analogical-reasoner";
|
|
34
32
|
/** Tool-call budget for the inner research agent. */
|
|
35
33
|
const RESEARCH_MAX_ITERATIONS = 40;
|
|
36
|
-
const ANALOGY_SALVAGE_NUDGE = "You ended without a terminal outcome.
|
|
37
|
-
"
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
34
|
+
const ANALOGY_SALVAGE_NUDGE = "You ended without a terminal outcome. Record the problem with record_problem and each analogy with " +
|
|
35
|
+
"record_analogy now, or call report_blocker if phase 1 cannot run. Do not reply with prose.";
|
|
36
|
+
const RECORD_PROBLEM_TOOL_ID = "record_problem";
|
|
37
|
+
const RECORD_ANALOGY_TOOL_ID = "record_analogy";
|
|
38
|
+
const RecordProblemInputSchema = AnalogyProblemSchema.extend({
|
|
39
|
+
plannedAnalogies: z
|
|
40
|
+
.array(z.object({ analogyTitle: z.string(), targetDomain: z.string() }))
|
|
41
|
+
.min(1)
|
|
42
|
+
.describe("Each analogy of phase 1. An analogy that gets no record_analogy call arrives in the report as not loaded."),
|
|
43
|
+
});
|
|
44
|
+
function createRecordProblemTool() {
|
|
41
45
|
return defineTool({
|
|
42
|
-
id:
|
|
43
|
-
description: "
|
|
44
|
-
"
|
|
45
|
-
|
|
46
|
-
"the full report. If it rejects the report, correct the fields that it " +
|
|
47
|
-
"names and call it again. The last accepted report is the result.",
|
|
48
|
-
inputSchema: AnalogyReportSchema,
|
|
46
|
+
id: RECORD_PROBLEM_TOOL_ID,
|
|
47
|
+
description: "Record the extraction of phase 1: the problem summary, objects, relations, key terms, and the analogies " +
|
|
48
|
+
"that phase 2 searches. A later call replaces the record.",
|
|
49
|
+
inputSchema: RecordProblemInputSchema,
|
|
49
50
|
describeCall: "none",
|
|
50
|
-
execute: () => Promise.resolve(ok({ recorded: true
|
|
51
|
+
execute: () => Promise.resolve(ok({ recorded: true })),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
function createRecordAnalogyTool() {
|
|
55
|
+
return defineTool({
|
|
56
|
+
id: RECORD_ANALOGY_TOOL_ID,
|
|
57
|
+
description: "Record one analogy with its coverage and its cited solutions, when its search is done. A later call " +
|
|
58
|
+
"with the same analogyTitle replaces the record.",
|
|
59
|
+
inputSchema: AnalogySchema,
|
|
60
|
+
describeCall: ({ analogyTitle }) => analogyTitle,
|
|
61
|
+
execute: () => Promise.resolve(ok({ recorded: true })),
|
|
51
62
|
});
|
|
52
63
|
}
|
|
53
|
-
/** The
|
|
54
|
-
|
|
55
|
-
let
|
|
64
|
+
/** The parts that a run recorded: the last accepted problem, and the last accepted record of each analogy title. */
|
|
65
|
+
function recordedParts(steps) {
|
|
66
|
+
let problem;
|
|
67
|
+
const analogies = new Map();
|
|
56
68
|
for (const step of steps) {
|
|
57
69
|
for (const call of step.calls) {
|
|
58
|
-
if (call.
|
|
70
|
+
if (call.output?.outcome !== "ok")
|
|
59
71
|
continue;
|
|
60
|
-
|
|
72
|
+
if (call.toolName === RECORD_PROBLEM_TOOL_ID)
|
|
73
|
+
problem = recordedInputOf(RecordProblemInputSchema, call.input) ?? problem;
|
|
74
|
+
if (call.toolName === RECORD_ANALOGY_TOOL_ID) {
|
|
75
|
+
const analogy = recordedInputOf(AnalogySchema, call.input);
|
|
76
|
+
if (analogy !== undefined)
|
|
77
|
+
analogies.set(analogy.analogyTitle, analogy);
|
|
78
|
+
}
|
|
61
79
|
}
|
|
62
80
|
}
|
|
63
|
-
return
|
|
81
|
+
return { problem, analogies };
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The report that a run recorded, or nothing when it recorded no problem. The analogies keep the planned order, and a
|
|
85
|
+
* recorded analogy that the plan does not name follows them. A planned analogy with no record is not loaded.
|
|
86
|
+
*/
|
|
87
|
+
export function assembledReportOf(steps) {
|
|
88
|
+
const { problem, analogies } = recordedParts(steps);
|
|
89
|
+
if (problem === undefined)
|
|
90
|
+
return undefined;
|
|
91
|
+
const { plannedAnalogies, ...extraction } = problem;
|
|
92
|
+
const planned = plannedAnalogies.map((plan) => analogies.get(plan.analogyTitle) ?? {
|
|
93
|
+
targetDomain: plan.targetDomain,
|
|
94
|
+
analogyTitle: plan.analogyTitle,
|
|
95
|
+
objectMappings: [],
|
|
96
|
+
sharedRelations: "",
|
|
97
|
+
coverage: "not_loaded",
|
|
98
|
+
solutions: [],
|
|
99
|
+
});
|
|
100
|
+
const titles = new Set(plannedAnalogies.map((plan) => plan.analogyTitle));
|
|
101
|
+
const extra = [...analogies.values()].filter((analogy) => !titles.has(analogy.analogyTitle));
|
|
102
|
+
return { schemaVersion: "1", ...extraction, analogies: [...planned, ...extra] };
|
|
103
|
+
}
|
|
104
|
+
/** The run recorded the problem and each analogy that it planned, thus nothing is left to record. */
|
|
105
|
+
function everyPartRecorded(steps) {
|
|
106
|
+
const { problem, analogies } = recordedParts(steps);
|
|
107
|
+
return problem !== undefined && problem.plannedAnalogies.every((plan) => analogies.has(plan.analogyTitle));
|
|
64
108
|
}
|
|
65
109
|
function createAnalogyBlockerTool() {
|
|
66
110
|
return createReportBlockerToolFor({
|
|
@@ -124,29 +168,35 @@ export function buildResearchPrompt(input) {
|
|
|
124
168
|
return lines.join("\n");
|
|
125
169
|
}
|
|
126
170
|
/** Build the `generate_analogy_report` delegation tool bound to its provider. */
|
|
171
|
+
/** The analogical reasoner: its searches run in a script, and it records its report and a blocker directly. */
|
|
172
|
+
export function analogyAgentOf(deps) {
|
|
173
|
+
const ncbi = createNcbiTools(deps.bioKeys);
|
|
174
|
+
return {
|
|
175
|
+
id: AGENT_ID,
|
|
176
|
+
systemPrompt: composeSystemPrompt(analogicalReasonerPrompt),
|
|
177
|
+
model: deps.model,
|
|
178
|
+
// The order and the definitions of the tools stay fixed across calls so the cache-keyed prefix holds.
|
|
179
|
+
tools: [
|
|
180
|
+
...(deps.toolOutputStore ? [createReadToolOutputTool(deps.toolOutputStore)] : []),
|
|
181
|
+
createRecordProblemTool(),
|
|
182
|
+
createRecordAnalogyTool(),
|
|
183
|
+
createAnalogyBlockerTool(),
|
|
184
|
+
],
|
|
185
|
+
scriptTools: [
|
|
186
|
+
createSearchSemanticScholarTool({ apiKey: deps.bioKeys.semanticScholar }),
|
|
187
|
+
searchArxivTool,
|
|
188
|
+
createSearchGithubReposTool({ githubToken: deps.bioKeys.github }),
|
|
189
|
+
ncbi.pubmed,
|
|
190
|
+
],
|
|
191
|
+
scriptRuntime: deps.scriptRuntime,
|
|
192
|
+
maxIterations: RESEARCH_MAX_ITERATIONS,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
127
195
|
export function createGenerateAnalogyReportTool(deps, registerSubAgent) {
|
|
128
196
|
const logger = (deps.logger ?? createNoopLogger()).named("generate-analogy-report");
|
|
129
|
-
const ncbi = createNcbiTools(deps.bioKeys);
|
|
130
|
-
const searchTools = [
|
|
131
|
-
createSearchSemanticScholarTool({ apiKey: deps.bioKeys.semanticScholar }),
|
|
132
|
-
searchArxivTool,
|
|
133
|
-
createSearchGithubReposTool({ githubToken: deps.bioKeys.github }),
|
|
134
|
-
ncbi.pubmed,
|
|
135
|
-
...(deps.toolOutputStore ? [createReadToolOutputTool(deps.toolOutputStore)] : []),
|
|
136
|
-
];
|
|
137
|
-
const systemPrompt = composeSystemPrompt(analogicalReasonerPrompt);
|
|
138
197
|
const research = registerSubAgent(AGENT_ID, async ({ payload: input, ctx, loop }) => {
|
|
139
198
|
const childSession = forSubAgent(ctx.session, AGENT_ID);
|
|
140
|
-
|
|
141
|
-
const submitReportTool = createSubmitAnalogyReportTool();
|
|
142
|
-
const blockerTool = createAnalogyBlockerTool();
|
|
143
|
-
const agent = {
|
|
144
|
-
id: AGENT_ID,
|
|
145
|
-
systemPrompt,
|
|
146
|
-
model: deps.model,
|
|
147
|
-
tools: [...searchTools, submitReportTool, blockerTool],
|
|
148
|
-
maxIterations: RESEARCH_MAX_ITERATIONS,
|
|
149
|
-
};
|
|
199
|
+
const agent = analogyAgentOf(deps);
|
|
150
200
|
let steps;
|
|
151
201
|
try {
|
|
152
202
|
const run = await runWithSalvage(agent, [{ role: "user", content: buildResearchPrompt(input) }], childSession, {
|
|
@@ -155,10 +205,12 @@ export function createGenerateAnalogyReportTool(deps, registerSubAgent) {
|
|
|
155
205
|
logger,
|
|
156
206
|
usageRecorder: deps.usageRecorder,
|
|
157
207
|
toolOutputStore: deps.toolOutputStore,
|
|
208
|
+
// The run ends when each planned analogy has a record, because the wrapper reads no prose.
|
|
209
|
+
stopWhen: everyPartRecorded,
|
|
158
210
|
}, {
|
|
159
|
-
tools: [
|
|
211
|
+
tools: [RECORD_PROBLEM_TOOL_ID, RECORD_ANALOGY_TOOL_ID, REPORT_BLOCKER_TOOL_ID],
|
|
160
212
|
nudge: ANALOGY_SALVAGE_NUDGE,
|
|
161
|
-
hasOutcome: (done) =>
|
|
213
|
+
hasOutcome: (done) => assembledReportOf(done) !== undefined || blockerOf(done) !== null,
|
|
162
214
|
});
|
|
163
215
|
steps = run.steps;
|
|
164
216
|
}
|
|
@@ -173,12 +225,22 @@ export function createGenerateAnalogyReportTool(deps, registerSubAgent) {
|
|
|
173
225
|
};
|
|
174
226
|
}
|
|
175
227
|
// A report wins over a blocker.
|
|
176
|
-
const report =
|
|
228
|
+
const report = assembledReportOf(steps);
|
|
177
229
|
if (report !== undefined)
|
|
178
230
|
return report;
|
|
179
231
|
const blocker = blockerOf(steps);
|
|
180
232
|
if (blocker !== null)
|
|
181
233
|
return { schemaVersion: "1", error: { kind: "extraction-failed", message: blocker.reason } };
|
|
234
|
+
if (steps.some((step) => step.finishReason === "content-filter")) {
|
|
235
|
+
logger.warn("the content filter of the provider stopped the analogical reasoner", { steps: steps.length });
|
|
236
|
+
return {
|
|
237
|
+
schemaVersion: "1",
|
|
238
|
+
error: {
|
|
239
|
+
kind: "extraction-failed",
|
|
240
|
+
message: "The content filter of the model provider stopped the reasoner before it recorded the problem. A different wording of the problem can pass the filter.",
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
}
|
|
182
244
|
return buildExtractionFailedEnvelope();
|
|
183
245
|
});
|
|
184
246
|
return defineTool({
|
|
@@ -22,17 +22,94 @@
|
|
|
22
22
|
* last accepted plan, else the last clarification, else the first blocker —
|
|
23
23
|
* and persists an accepted plan under an id that a rerun of the call gives again.
|
|
24
24
|
*/
|
|
25
|
+
import type { ResolveWorkspaceRoot } from "../../workspace/paths.js";
|
|
26
|
+
import type { ScriptRuntime } from "../script/run-script.js";
|
|
27
|
+
import { type ReadSurfaceDeps } from "../workspace/read-surface.js";
|
|
25
28
|
import type { Pool } from "pg";
|
|
29
|
+
import { z } from "zod";
|
|
26
30
|
import type { SubAgentRegistrar } from "../../loop/sub-agent.js";
|
|
27
31
|
import type { ToolOutputStore } from "../../loop/tool-output.js";
|
|
32
|
+
import type { AgentDefinition } from "../../loop/types.js";
|
|
28
33
|
import { type ChatProvider } from "../../providers/types.js";
|
|
29
34
|
import type { UsageRecorder } from "../../billing/usage-recorder.js";
|
|
30
35
|
import { type Tool } from "../define-tool.js";
|
|
31
36
|
import type { EnvironmentStorePaths } from "../../config/environment-stores.js";
|
|
32
37
|
import { type BioToolKeys } from "../bio/keys.js";
|
|
33
38
|
import { type KnowledgeClient, type KnowledgeRecommendAnswer } from "../knowledge/index.js";
|
|
34
|
-
import { type ResourcePolicy } from "../../config/resource-limits.js";
|
|
35
39
|
import type { Logger } from "../../lib/logger.js";
|
|
40
|
+
import { type DataProfileResult } from "../../state/index.js";
|
|
41
|
+
/** The planner system prompt is deterministic for the process: the catalog is fixed at load. */
|
|
42
|
+
export declare function plannerInstructions(): string;
|
|
43
|
+
/**
|
|
44
|
+
* Format a loaded prior plan as a markdown block the planner can read —
|
|
45
|
+
* narrative + one line per step. Used only when `parentPlanId` is set.
|
|
46
|
+
*/
|
|
47
|
+
export declare function formatPriorPlan(parentPlanId: string, plan: unknown): string | null;
|
|
48
|
+
/**
|
|
49
|
+
* What the planner actually holds about the dataset.
|
|
50
|
+
*
|
|
51
|
+
* Every lifecycle state is a variant because every one of them is ordinary: an
|
|
52
|
+
* analysis can be planned before it has been profiled, while profiling is in
|
|
53
|
+
* flight, after an attempt failed, and against a profile whose input files have
|
|
54
|
+
* since changed. None of those is an error, and none of them may stop a plan
|
|
55
|
+
* being produced — a planner told which state it is in can plan accordingly; a
|
|
56
|
+
* planner told nothing plans on facts it cannot know are rotten.
|
|
57
|
+
*/
|
|
58
|
+
export type DataGrounding = {
|
|
59
|
+
kind: "ready";
|
|
60
|
+
result: DataProfileResult;
|
|
61
|
+
}
|
|
62
|
+
/** A profile IS available, but may not describe the analysis's current inputs. */
|
|
63
|
+
| {
|
|
64
|
+
kind: "provisional";
|
|
65
|
+
result: DataProfileResult;
|
|
66
|
+
reasons: readonly string[];
|
|
67
|
+
}
|
|
68
|
+
/** Profiling is in flight and no earlier profile exists. */
|
|
69
|
+
| {
|
|
70
|
+
kind: "pending";
|
|
71
|
+
}
|
|
72
|
+
/** Profiling failed and no earlier profile exists. */
|
|
73
|
+
| {
|
|
74
|
+
kind: "failed";
|
|
75
|
+
error: string | null;
|
|
76
|
+
}
|
|
77
|
+
/** Never profiled, cleared, or nothing to profile. */
|
|
78
|
+
| {
|
|
79
|
+
kind: "absent";
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Render the seed's `## Data Context` from the grounding.
|
|
83
|
+
*
|
|
84
|
+
* The facts come from `buildDataProfileOrientation` — the same bounded projector
|
|
85
|
+
* the sandbox step seed uses, so a step and the plan that produced it are oriented
|
|
86
|
+
* by identical text. It is not re-derived here, and the planner cannot reach past
|
|
87
|
+
* it: the planner has no `inspect_data_profile` tool, so what this section does not
|
|
88
|
+
* say is simply not known to it.
|
|
89
|
+
*
|
|
90
|
+
* An absent profile renders NOTHING — no heading, no apology. Planning without
|
|
91
|
+
* dataset facts is worse-grounded but entirely possible, and a section announcing
|
|
92
|
+
* its own emptiness is an invitation to fill it. Every other state renders,
|
|
93
|
+
* including the two that carry no facts either: a planner told that its record is
|
|
94
|
+
* still being built, or that its facts may have moved, can act on that; one handed
|
|
95
|
+
* stale facts silently cannot.
|
|
96
|
+
*
|
|
97
|
+
* Pure — the same grounding renders the same string.
|
|
98
|
+
*/
|
|
99
|
+
export declare function renderDataContext(grounding: DataGrounding, analysisId: string): string;
|
|
100
|
+
/** The planner agent over its search tools and its terminal tools. */
|
|
101
|
+
export declare function plannerAgentOf(deps: GeneratePlanDeps, searchTools: readonly Tool[], terminal: readonly Tool[]): AgentDefinition;
|
|
102
|
+
/** The terminal tools of a planner with no knowledge answer, for a consumer that runs the planner outside this tool. */
|
|
103
|
+
export declare function plannerTerminalTools(logger: Logger): Tool[];
|
|
104
|
+
/**
|
|
105
|
+
* Render one environment inventory into the seed, reporting a lookup that failed.
|
|
106
|
+
*
|
|
107
|
+
* A failed lookup is written INTO the prompt as prose the planner then plans
|
|
108
|
+
* around — a silent degradation of its grounding that no caller is told about and
|
|
109
|
+
* no outcome distinguishes. The record is what makes "the planner produced a plan
|
|
110
|
+
* naming packages this install does not have" traceable to its cause.
|
|
111
|
+
*/
|
|
112
|
+
export declare function inventoryContent(label: string, result: Awaited<ReturnType<Tool["execute"]>>, logger: Logger): string;
|
|
36
113
|
export type GeneratePlanDeps = EnvironmentStorePaths & {
|
|
37
114
|
/**
|
|
38
115
|
* The conversation-role backend the planner is pinned to. Keeping the
|
|
@@ -45,12 +122,6 @@ export type GeneratePlanDeps = EnvironmentStorePaths & {
|
|
|
45
122
|
};
|
|
46
123
|
/** Database pool — plan persistence and prior-plan loading. */
|
|
47
124
|
readonly pool: Pool;
|
|
48
|
-
/**
|
|
49
|
-
* Host resource policy — stated to the planner as concrete per-step
|
|
50
|
-
* ceilings and enforced by `submit_plan`. Absent, the prompt keeps its
|
|
51
|
-
* default guidance and validation skips the ceiling check.
|
|
52
|
-
*/
|
|
53
|
-
readonly resourcePolicy?: ResourcePolicy;
|
|
54
125
|
/** Diagnostic sink. Absent, the tool runs silently — see `LoopOptions.logger`. */
|
|
55
126
|
readonly logger?: Logger;
|
|
56
127
|
/** LLM usage-accounting seam for the planner loop; omitted falls back to the no-op recorder. */
|
|
@@ -59,6 +130,12 @@ export type GeneratePlanDeps = EnvironmentStorePaths & {
|
|
|
59
130
|
readonly toolOutputStore?: ToolOutputStore;
|
|
60
131
|
/** API keys for the search tools the planner uses to ground a plan. */
|
|
61
132
|
readonly bioKeys: BioToolKeys;
|
|
133
|
+
/** The read-only workspace: the file tools, the search, the data profile, and the script reads of a PDF and a table. */
|
|
134
|
+
readonly workspace: ReadSurfaceDeps & {
|
|
135
|
+
readonly resolveWorkspaceRoot: ResolveWorkspaceRoot;
|
|
136
|
+
};
|
|
137
|
+
/** Where the VM of `run_script` comes from, for the script reads. See `ScriptRuntime`. */
|
|
138
|
+
readonly scriptRuntime?: ScriptRuntime;
|
|
62
139
|
/**
|
|
63
140
|
* The knowledge service client. Bound, the planner gains `knowledge_recommend`
|
|
64
141
|
* and `knowledge_check`. Absent, no knowledge tool attaches and no
|
|
@@ -82,5 +159,23 @@ export type GeneratePlanDeps = EnvironmentStorePaths & {
|
|
|
82
159
|
export declare function buildPlannerSearchTools(deps: GeneratePlanDeps, hooks?: {
|
|
83
160
|
readonly onRecommend?: (answer: KnowledgeRecommendAnswer) => void;
|
|
84
161
|
}): Tool[];
|
|
162
|
+
/**
|
|
163
|
+
* The seed of the planner: the prior plan when the call revises one, the data context, the two censuses, and then
|
|
164
|
+
* what the caller gave. Each block is optional where the call holds none.
|
|
165
|
+
*/
|
|
166
|
+
export declare function composePlannerSeed(parts: {
|
|
167
|
+
readonly priorPlanBlock: string | null;
|
|
168
|
+
readonly dataContextBlock: string;
|
|
169
|
+
readonly groundingBlock: string;
|
|
170
|
+
readonly input: Pick<z.infer<typeof generatePlanInputSchema>, "researchQuestion" | "analystNotes" | "priorRuns" | "userConstraints">;
|
|
171
|
+
}): string;
|
|
172
|
+
declare const generatePlanInputSchema: z.ZodObject<{
|
|
173
|
+
researchQuestion: z.ZodString;
|
|
174
|
+
analystNotes: z.ZodOptional<z.ZodString>;
|
|
175
|
+
priorRuns: z.ZodOptional<z.ZodString>;
|
|
176
|
+
userConstraints: z.ZodOptional<z.ZodString>;
|
|
177
|
+
parentPlanId: z.ZodOptional<z.ZodString>;
|
|
178
|
+
}, z.core.$strip>;
|
|
85
179
|
/** Build the `generate_plan` tool bound to its provider and pool. */
|
|
86
180
|
export declare function createGeneratePlanTool(deps: GeneratePlanDeps, registerSubAgent: SubAgentRegistrar): Tool;
|
|
181
|
+
export {};
|
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
* last accepted plan, else the last clarification, else the first blocker —
|
|
23
23
|
* and persists an accepted plan under an id that a rerun of the call gives again.
|
|
24
24
|
*/
|
|
25
|
+
import { createReadScriptTools } from "../script/file-tools.js";
|
|
26
|
+
import { createReadSurfaceTools } from "../workspace/read-surface.js";
|
|
25
27
|
import { errorMessageOf } from "../../lib/thrown.js";
|
|
26
28
|
import { textOf } from "../../lib/unknown-text.js";
|
|
27
29
|
import { createHash } from "node:crypto";
|
|
@@ -51,6 +53,7 @@ import { createSearchSemanticScholarTool } from "./search-semantic-scholar.js";
|
|
|
51
53
|
import { DATA_PROFILE_ORIENTATION_MAX_CHARS, buildDataProfileOrientation } from "../../app/data-profile-orientation.js";
|
|
52
54
|
import { DEFAULT_SANDBOX_MAX_STEPS } from "../../config/resource-limits.js";
|
|
53
55
|
import { plannerPrompt } from "../../prompts/planner.js";
|
|
56
|
+
import { planCardOf } from "../../memory/card-builders.js";
|
|
54
57
|
import { hydratePlanSteps, PlannerPlanSchema } from "../../schemas/plan-schemas.js";
|
|
55
58
|
import { validatePlan } from "../../schemas/validate-plan.js";
|
|
56
59
|
import { AnalysisPlanSchema } from "../../schemas/workflow-state.js";
|
|
@@ -169,10 +172,9 @@ function traceOf(steps) {
|
|
|
169
172
|
return { submitAttempts, rejectedAttempts, rejections };
|
|
170
173
|
}
|
|
171
174
|
// ── Prompt / catalog ────────────────────────────────────────────────
|
|
172
|
-
/** The planner system prompt is deterministic
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
return plannerPrompt(formatAgentCatalog(), resourcePolicy);
|
|
175
|
+
/** The planner system prompt is deterministic for the process: the catalog is fixed at load. */
|
|
176
|
+
export function plannerInstructions() {
|
|
177
|
+
return plannerPrompt(formatAgentCatalog());
|
|
176
178
|
}
|
|
177
179
|
const ValidationIssueSchema = z.object({ path: z.string(), code: z.enum(["schema", "semantic"]), message: z.string(), hint: z.string().optional() });
|
|
178
180
|
const SubmitPlanOutputSchema = z.discriminatedUnion("accepted", [
|
|
@@ -226,7 +228,7 @@ function selectedOutcomeOf(steps) {
|
|
|
226
228
|
* Format a loaded prior plan as a markdown block the planner can read —
|
|
227
229
|
* narrative + one line per step. Used only when `parentPlanId` is set.
|
|
228
230
|
*/
|
|
229
|
-
function formatPriorPlan(parentPlanId, plan) {
|
|
231
|
+
export function formatPriorPlan(parentPlanId, plan) {
|
|
230
232
|
const parsed = AnalysisPlanSchema.safeParse(plan);
|
|
231
233
|
if (!parsed.success)
|
|
232
234
|
return null;
|
|
@@ -243,10 +245,9 @@ function formatPriorPlan(parentPlanId, plan) {
|
|
|
243
245
|
"**Steps:**",
|
|
244
246
|
...stepLines,
|
|
245
247
|
"",
|
|
246
|
-
"The user is
|
|
247
|
-
"
|
|
248
|
-
"
|
|
249
|
-
"references survive.",
|
|
248
|
+
"The user is revising this plan, not asking for a new one. The changes are in `## User Constraints`. " +
|
|
249
|
+
"Each step keeps its id, its agent, and its order, also a step that the user asked to change: apply the change to that step. " +
|
|
250
|
+
"Thus `depends_on` and the stored artifacts survive. Give a new id to new work only.",
|
|
250
251
|
].join("\n");
|
|
251
252
|
}
|
|
252
253
|
/**
|
|
@@ -305,7 +306,7 @@ const NO_FACTS_GUIDANCE = "Plan from the research question alone, do not invent
|
|
|
305
306
|
*
|
|
306
307
|
* Pure — the same grounding renders the same string.
|
|
307
308
|
*/
|
|
308
|
-
function renderDataContext(grounding, analysisId) {
|
|
309
|
+
export function renderDataContext(grounding, analysisId) {
|
|
309
310
|
switch (grounding.kind) {
|
|
310
311
|
case "absent":
|
|
311
312
|
return "";
|
|
@@ -356,7 +357,7 @@ async function persistPlan(plan, ctx, pool, logger) {
|
|
|
356
357
|
unwrapOrThrow(await upsertPlan(pool, {
|
|
357
358
|
planId,
|
|
358
359
|
analysisId: ctx.analysisId,
|
|
359
|
-
plan: hydratePlanSteps(plan),
|
|
360
|
+
plan: { ...hydratePlanSteps(plan), request: ctx.request },
|
|
360
361
|
parentPlanId: ctx.parentPlanId,
|
|
361
362
|
}));
|
|
362
363
|
return { ok: true, planId };
|
|
@@ -523,7 +524,7 @@ function restoreSkeletonGrounding(candidate, memory) {
|
|
|
523
524
|
* Full validation: Zod schema + semantic checks. The plan is valid only if
|
|
524
525
|
* BOTH pass.
|
|
525
526
|
*/
|
|
526
|
-
function fullyValidate(candidate
|
|
527
|
+
function fullyValidate(candidate) {
|
|
527
528
|
// The permissive arg schema of the planner tools accepts a string, thus the
|
|
528
529
|
// loop-boundary repair never sees a plan that a model sent as a JSON-encoded
|
|
529
530
|
// string. A small model does this on a large nested schema. Decode it here,
|
|
@@ -546,7 +547,7 @@ function fullyValidate(candidate, resourcePolicy) {
|
|
|
546
547
|
created_at: parsed.data.created_at,
|
|
547
548
|
omicsType: parsed.data.omicsType,
|
|
548
549
|
omicsSubtype: parsed.data.omicsSubtype,
|
|
549
|
-
}
|
|
550
|
+
});
|
|
550
551
|
if (!semantic.valid) {
|
|
551
552
|
return {
|
|
552
553
|
valid: false,
|
|
@@ -559,11 +560,30 @@ function fullyValidate(candidate, resourcePolicy) {
|
|
|
559
560
|
}
|
|
560
561
|
return { valid: true, plan: parsed.data };
|
|
561
562
|
}
|
|
563
|
+
/** The planner agent over its search tools and its terminal tools. */
|
|
564
|
+
export function plannerAgentOf(deps, searchTools, terminal) {
|
|
565
|
+
const readTools = deps.toolOutputStore ? [createReadToolOutputTool(deps.toolOutputStore)] : [];
|
|
566
|
+
return {
|
|
567
|
+
id: PLANNER_AGENT_ID,
|
|
568
|
+
systemPrompt: composeSystemPrompt(plannerInstructions()),
|
|
569
|
+
model: deps.conversation.model,
|
|
570
|
+
// The search tools come first, and the terminal tools come last. The order is the order of the prompt, and it
|
|
571
|
+
// is stable across every invocation, thus the cached request prefix holds.
|
|
572
|
+
tools: [...searchTools, ...createReadSurfaceTools(deps.workspace), ...readTools, ...terminal],
|
|
573
|
+
scriptTools: createReadScriptTools(deps.workspace),
|
|
574
|
+
scriptRuntime: deps.scriptRuntime,
|
|
575
|
+
maxIterations: PLANNER_MAX_ITERATIONS,
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
/** The terminal tools of a planner with no knowledge answer, for a consumer that runs the planner outside this tool. */
|
|
579
|
+
export function plannerTerminalTools(logger) {
|
|
580
|
+
return buildInnerTools(logger, { steps: new Map(), claims: new Set() }).terminal;
|
|
581
|
+
}
|
|
562
582
|
/**
|
|
563
583
|
* Build the planner's inner tools for one invocation. `memory` holds the last
|
|
564
584
|
* `knowledge_recommend` answer of the run, which the loop folds after each round.
|
|
565
585
|
*/
|
|
566
|
-
function buildInnerTools(
|
|
586
|
+
function buildInnerTools(logger, memory) {
|
|
567
587
|
const submitPlanTool = defineTool({
|
|
568
588
|
id: SUBMIT_PLAN_TOOL_ID,
|
|
569
589
|
description: "Submit the plan. Checks the plan; on success returns {accepted: true, plan} " +
|
|
@@ -592,7 +612,7 @@ function buildInnerTools(resourcePolicy, logger, memory) {
|
|
|
592
612
|
logger.info("submit_plan restored the template of skeleton steps", { steps: restored });
|
|
593
613
|
if (repaired.length > 0)
|
|
594
614
|
logger.info("submit_plan repaired the claim ids of plan steps", { claims: repaired });
|
|
595
|
-
const result = fullyValidate(candidate
|
|
615
|
+
const result = fullyValidate(candidate);
|
|
596
616
|
// An unknown claim rejects a plan that the schema accepts, beside the
|
|
597
617
|
// schema issues when the schema rejects it, thus one attempt shows both.
|
|
598
618
|
if (!result.valid || unknown.length > 0) {
|
|
@@ -644,7 +664,7 @@ function buildInnerTools(resourcePolicy, logger, memory) {
|
|
|
644
664
|
* no outcome distinguishes. The record is what makes "the planner produced a plan
|
|
645
665
|
* naming packages this install does not have" traceable to its cause.
|
|
646
666
|
*/
|
|
647
|
-
function inventoryContent(label, result, logger) {
|
|
667
|
+
export function inventoryContent(label, result, logger) {
|
|
648
668
|
if (result.isErr()) {
|
|
649
669
|
logger.warn("planner grounding inventory lookup failed", { inventory: label, error: result.error.error });
|
|
650
670
|
return `## ${label}\n\nInventory lookup failed: ${result.error.error}`;
|
|
@@ -855,8 +875,28 @@ export function buildPlannerSearchTools(deps, hooks = {}) {
|
|
|
855
875
|
}),
|
|
856
876
|
];
|
|
857
877
|
}
|
|
878
|
+
/**
|
|
879
|
+
* The seed of the planner: the prior plan when the call revises one, the data context, the two censuses, and then
|
|
880
|
+
* what the caller gave. Each block is optional where the call holds none.
|
|
881
|
+
*/
|
|
882
|
+
export function composePlannerSeed(parts) {
|
|
883
|
+
const { priorPlanBlock, dataContextBlock, groundingBlock, input } = parts;
|
|
884
|
+
return [
|
|
885
|
+
...(priorPlanBlock ? [priorPlanBlock, ""] : []),
|
|
886
|
+
...(dataContextBlock ? [dataContextBlock, ""] : []),
|
|
887
|
+
groundingBlock,
|
|
888
|
+
"",
|
|
889
|
+
"## Research Question",
|
|
890
|
+
input.researchQuestion,
|
|
891
|
+
...(input.analystNotes ? ["", "## Analyst Notes (from the user — facts about the data the profile does not record)", input.analystNotes] : []),
|
|
892
|
+
...(input.priorRuns ? ["", "## Prior Run Results", input.priorRuns] : []),
|
|
893
|
+
...(input.userConstraints ? ["", "## User Constraints", input.userConstraints] : []),
|
|
894
|
+
].join("\n");
|
|
895
|
+
}
|
|
858
896
|
const generatePlanInputSchema = z.object({
|
|
859
|
-
researchQuestion: z
|
|
897
|
+
researchQuestion: z
|
|
898
|
+
.string()
|
|
899
|
+
.describe("The request of the user, in the words of the user: the goal, the questions, and each deliverable that the user named, with its format. The plan stores it, and each step of the run reads it."),
|
|
860
900
|
analystNotes: z
|
|
861
901
|
.string()
|
|
862
902
|
.optional()
|
|
@@ -983,17 +1023,7 @@ export function createGeneratePlanTool(deps, registerSubAgent) {
|
|
|
983
1023
|
const refsBlock = inventoryContent("Available Reference Data", refsResult, logger);
|
|
984
1024
|
const packagesBlock = inventoryContent("Available Packages", packagesResult, logger);
|
|
985
1025
|
const groundingBlock = [refsBlock, packagesBlock].join("\n\n");
|
|
986
|
-
const prompt =
|
|
987
|
-
...(priorPlanBlock ? [priorPlanBlock, ""] : []),
|
|
988
|
-
...(dataContextBlock ? [dataContextBlock, ""] : []),
|
|
989
|
-
groundingBlock,
|
|
990
|
-
"",
|
|
991
|
-
"## Research Question",
|
|
992
|
-
input.researchQuestion,
|
|
993
|
-
...(input.analystNotes ? ["", "## Analyst Notes (from the user — facts about the data the profile does not record)", input.analystNotes] : []),
|
|
994
|
-
...(input.priorRuns ? ["", "## Prior Run Results", input.priorRuns] : []),
|
|
995
|
-
...(input.userConstraints ? ["", "## User Constraints", input.userConstraints] : []),
|
|
996
|
-
].join("\n");
|
|
1026
|
+
const prompt = composePlannerSeed({ priorPlanBlock, dataContextBlock, groundingBlock, input });
|
|
997
1027
|
return {
|
|
998
1028
|
prompt,
|
|
999
1029
|
grounding: grounding.kind,
|
|
@@ -1037,13 +1067,14 @@ export function createGeneratePlanTool(deps, registerSubAgent) {
|
|
|
1037
1067
|
analysisId,
|
|
1038
1068
|
parentPlanId: input.parentPlanId ?? null,
|
|
1039
1069
|
invocationId: ctx.invocationId,
|
|
1070
|
+
request: input.researchQuestion,
|
|
1040
1071
|
};
|
|
1041
1072
|
// The last recommend answer of this invocation, thus `submit_plan`
|
|
1042
1073
|
// stamps the snapshot digest, restores a template the model dropped,
|
|
1043
1074
|
// and repairs or refuses a claim id the answer did not return. The loop
|
|
1044
1075
|
// folds each answer after its round, on the first run and on a replay.
|
|
1045
1076
|
const memory = { steps: new Map(), claims: new Set() };
|
|
1046
|
-
const innerTools = buildInnerTools(
|
|
1077
|
+
const innerTools = buildInnerTools(logger, memory);
|
|
1047
1078
|
// Built here rather than at construction: a `describeCall` hook reads no
|
|
1048
1079
|
// dep, thus the tool must stay constructible from an empty bag. The tool
|
|
1049
1080
|
// definitions are identical across invocations, thus the request prefix
|
|
@@ -1051,17 +1082,7 @@ export function createGeneratePlanTool(deps, registerSubAgent) {
|
|
|
1051
1082
|
const searchTools = buildPlannerSearchTools(deps, {
|
|
1052
1083
|
onRecommend: (answer) => rememberAnswer(memory, answer),
|
|
1053
1084
|
});
|
|
1054
|
-
const
|
|
1055
|
-
const planner = {
|
|
1056
|
-
id: PLANNER_AGENT_ID,
|
|
1057
|
-
systemPrompt: composeSystemPrompt(plannerInstructions(deps.resourcePolicy)),
|
|
1058
|
-
model: deps.conversation.model,
|
|
1059
|
-
// The search tools come first, and the terminal tools come last.
|
|
1060
|
-
// The order is the order of the prompt, and it is stable across
|
|
1061
|
-
// every invocation, thus the cached request prefix holds.
|
|
1062
|
-
tools: [...searchTools, ...readTools, ...innerTools.terminal],
|
|
1063
|
-
maxIterations: PLANNER_MAX_ITERATIONS,
|
|
1064
|
-
};
|
|
1085
|
+
const planner = plannerAgentOf(deps, searchTools, innerTools.terminal);
|
|
1065
1086
|
// Merge the outer abort signal with a wall-clock timeout — either
|
|
1066
1087
|
// cancels the planner promptly.
|
|
1067
1088
|
const signal = AbortSignal.any([ctx.signal, AbortSignal.timeout(planTimeoutMs)]);
|
|
@@ -1155,13 +1176,23 @@ export function createGeneratePlanTool(deps, registerSubAgent) {
|
|
|
1155
1176
|
"record more faithfully than the record itself. Pass only what the profile cannot hold: the research " +
|
|
1156
1177
|
"question, facts the user told you (analystNotes), prior run results, and their constraints. " +
|
|
1157
1178
|
"The planner is a multi-step sub-agent, thus a call can take many minutes. " +
|
|
1158
|
-
"The result carries an `event`: `plan_complete` with {planId, plan} — the plan is saved
|
|
1159
|
-
"
|
|
1179
|
+
"The result carries an `event`: `plan_complete` with {planId, plan} — the plan is saved, the call shows its plan card to the user, " +
|
|
1180
|
+
"and it does not run it (execute_analysis plan mode does, after the approval); `clarification_needed` with {question, questionContext?} " +
|
|
1160
1181
|
"when the planner is missing a fact it cannot infer; or `error` with {error} when no plan was saved (the planner reported " +
|
|
1161
1182
|
"a blocker, the save failed, the call timed out or was cancelled, or parentPlanId is not a plan of this analysis).",
|
|
1162
1183
|
inputSchema: generatePlanInputSchema,
|
|
1163
1184
|
describeCall: ({ researchQuestion }) => researchQuestion,
|
|
1164
1185
|
startsWorkflow: true,
|
|
1165
|
-
execute: async (input, ctx) =>
|
|
1186
|
+
execute: async (input, ctx) => {
|
|
1187
|
+
const output = await plan(input, ctx);
|
|
1188
|
+
// The card comes from the result of the planner, with no read of the store, thus a replay of the body
|
|
1189
|
+
// gives the same card under the same id, and a reader keeps one card.
|
|
1190
|
+
if (output.event === "plan_complete" && output.planId !== undefined && output.plan !== undefined) {
|
|
1191
|
+
const card = planCardOf({ planId: output.planId, plan: hydratePlanSteps(output.plan), parentPlanId: input.parentPlanId ?? null });
|
|
1192
|
+
if (card !== null)
|
|
1193
|
+
await ctx.emit({ type: "data-plan", source: ctx.session.provenance, data: card });
|
|
1194
|
+
}
|
|
1195
|
+
return ok(output);
|
|
1196
|
+
},
|
|
1166
1197
|
});
|
|
1167
1198
|
}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Research barrel — dependency-bearing planning, run inspection, the
|
|
3
|
-
*
|
|
4
|
-
* lookup, and the cross-domain search tools the
|
|
3
|
+
* analogical-reasoner sub-agent, the lookups of the run synthesizer,
|
|
4
|
+
* developer-docs lookup, and the cross-domain search tools the
|
|
5
|
+
* analogical-reasoner uses.
|
|
5
6
|
* Dep-bearing tools are factory closures (`createXTool(deps)`); pure tools
|
|
6
7
|
* are module-scope `defineTool` values.
|
|
7
8
|
*/
|
|
8
9
|
export * from "./generate-plan.js";
|
|
9
10
|
export * from "./inspect-run.js";
|
|
10
11
|
export * from "./inspect-data-profile.js";
|
|
11
|
-
export * from "./
|
|
12
|
+
export * from "./synthesis-lookups.js";
|
|
12
13
|
export * from "./generate-analogy-report.js";
|
|
13
14
|
export * from "./context7-docs.js";
|
|
14
15
|
export * from "./search-semantic-scholar.js";
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Research barrel — dependency-bearing planning, run inspection, the
|
|
3
|
-
*
|
|
4
|
-
* lookup, and the cross-domain search tools the
|
|
3
|
+
* analogical-reasoner sub-agent, the lookups of the run synthesizer,
|
|
4
|
+
* developer-docs lookup, and the cross-domain search tools the
|
|
5
|
+
* analogical-reasoner uses.
|
|
5
6
|
* Dep-bearing tools are factory closures (`createXTool(deps)`); pure tools
|
|
6
7
|
* are module-scope `defineTool` values.
|
|
7
8
|
*/
|
|
8
9
|
export * from "./generate-plan.js";
|
|
9
10
|
export * from "./inspect-run.js";
|
|
10
11
|
export * from "./inspect-data-profile.js";
|
|
11
|
-
export * from "./
|
|
12
|
+
export * from "./synthesis-lookups.js";
|
|
12
13
|
export * from "./generate-analogy-report.js";
|
|
13
14
|
export * from "./context7-docs.js";
|
|
14
15
|
export * from "./search-semantic-scholar.js";
|