@inflexa-ai/harness 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/conversation-agent.d.ts +12 -11
- package/dist/agents/conversation-agent.js +30 -33
- 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 +17 -6
- package/dist/agents/sandbox/shared.js +49 -38
- 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/data-profile-orientation.d.ts +34 -0
- package/dist/app/data-profile-orientation.js +117 -0
- package/dist/contracts/schemas/chat-parts.d.ts +9 -9
- package/dist/execution/artifact-metadata.js +2 -1
- package/dist/execution/ephemeral-runner.d.ts +13 -0
- package/dist/execution/ephemeral-runner.js +23 -1
- package/dist/execution/run-synthesis.js +20 -19
- package/dist/execution/step-summary.js +2 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.js +19 -5
- 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/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/tasks/data-profile.d.ts +20 -2
- package/dist/tasks/data-profile.js +71 -22
- 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-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 +168 -118
- package/dist/tools/iterate-report.js +334 -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.js +3 -4
- 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/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 +43 -4
- package/dist/workflows/execute-analysis.js +107 -6
- 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/workspace/paths.d.ts +2 -2
- package/dist/workspace/paths.js +2 -2
- package/package.json +33 -33
- 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
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PubChem PUG-REST operations — the three lookups the PubChem tools expose.
|
|
3
|
+
*
|
|
4
|
+
* One home for the wire schemas, the URL construction, and the "PubChem 404
|
|
5
|
+
* means no data" contract, so a caller cannot drift from it: every function
|
|
6
|
+
* returns an empty collection for a 404 and throws on any other API failure.
|
|
7
|
+
*/
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
import { apiFetchValidated, describeApiError } from "./api-utils.js";
|
|
10
|
+
import { PUBCHEM_HEADERS as HEADERS, PUBCHEM_BASE } from "./pubchem-config.js";
|
|
11
|
+
const PROPERTY_LIST = [
|
|
12
|
+
"MolecularFormula",
|
|
13
|
+
"MolecularWeight",
|
|
14
|
+
"CanonicalSMILES",
|
|
15
|
+
"InChI",
|
|
16
|
+
"InChIKey",
|
|
17
|
+
"IUPACName",
|
|
18
|
+
"XLogP",
|
|
19
|
+
"TPSA",
|
|
20
|
+
"HBondDonorCount",
|
|
21
|
+
"HBondAcceptorCount",
|
|
22
|
+
"RotatableBondCount",
|
|
23
|
+
"Complexity",
|
|
24
|
+
].join(",");
|
|
25
|
+
// A single schema that both validates and normalizes one PUG-REST property row:
|
|
26
|
+
// the `.object(...)` half is the wire shape (every field optional — PubChem
|
|
27
|
+
// omits absent properties), the `.transform(...)` half maps it to the camelCase
|
|
28
|
+
// `PubChemCompound` we return. Parsing IS the validation, and because the
|
|
29
|
+
// transform rides on the schema, `z.infer` below is the OUTPUT type — no
|
|
30
|
+
// separate raw interface or mapper.
|
|
31
|
+
const PugPropertyRowSchema = z
|
|
32
|
+
.object({
|
|
33
|
+
CID: z.number().optional(),
|
|
34
|
+
CanonicalSMILES: z.string().optional(),
|
|
35
|
+
MolecularFormula: z.string().optional(),
|
|
36
|
+
// PUG-REST serializes MolecularWeight as a string (e.g. "180.16") to
|
|
37
|
+
// preserve significant figures; accept both and let the transform's
|
|
38
|
+
// `Number(...)` normalize it.
|
|
39
|
+
MolecularWeight: z.union([z.number(), z.string()]).optional(),
|
|
40
|
+
IUPACName: z.string().optional(),
|
|
41
|
+
InChI: z.string().optional(),
|
|
42
|
+
InChIKey: z.string().optional(),
|
|
43
|
+
XLogP: z.number().optional(),
|
|
44
|
+
TPSA: z.number().optional(),
|
|
45
|
+
HBondDonorCount: z.number().optional(),
|
|
46
|
+
HBondAcceptorCount: z.number().optional(),
|
|
47
|
+
RotatableBondCount: z.number().optional(),
|
|
48
|
+
Complexity: z.number().optional(),
|
|
49
|
+
})
|
|
50
|
+
.transform((raw) => ({
|
|
51
|
+
cid: raw.CID ?? 0,
|
|
52
|
+
canonicalSmiles: raw.CanonicalSMILES ?? null,
|
|
53
|
+
molecularFormula: raw.MolecularFormula ?? null,
|
|
54
|
+
molecularWeight: raw.MolecularWeight != null ? Number(raw.MolecularWeight) : null,
|
|
55
|
+
iupacName: raw.IUPACName ?? null,
|
|
56
|
+
inchi: raw.InChI ?? null,
|
|
57
|
+
inchiKey: raw.InChIKey ?? null,
|
|
58
|
+
xlogp: raw.XLogP ?? null,
|
|
59
|
+
tpsa: raw.TPSA ?? null,
|
|
60
|
+
hbondDonorCount: raw.HBondDonorCount ?? null,
|
|
61
|
+
hbondAcceptorCount: raw.HBondAcceptorCount ?? null,
|
|
62
|
+
rotatableBondCount: raw.RotatableBondCount ?? null,
|
|
63
|
+
complexity: raw.Complexity ?? null,
|
|
64
|
+
}));
|
|
65
|
+
const PubChemPropertyResponseSchema = z.object({
|
|
66
|
+
PropertyTable: z.object({ Properties: z.array(PugPropertyRowSchema).optional() }).optional(),
|
|
67
|
+
});
|
|
68
|
+
// The PUG-REST xrefs wire shape, validated at the fetch boundary: one
|
|
69
|
+
// `Information` entry per CID with parallel `RegistryID[]` / `SourceName[]`
|
|
70
|
+
// arrays. Every field is optional — PubChem omits absent values.
|
|
71
|
+
const PubChemXrefResponseSchema = z.object({
|
|
72
|
+
InformationList: z
|
|
73
|
+
.object({
|
|
74
|
+
Information: z
|
|
75
|
+
.array(z.object({
|
|
76
|
+
CID: z.number().optional(),
|
|
77
|
+
RegistryID: z.array(z.string()).optional(),
|
|
78
|
+
SourceName: z.array(z.string()).optional(),
|
|
79
|
+
}))
|
|
80
|
+
.optional(),
|
|
81
|
+
})
|
|
82
|
+
.optional(),
|
|
83
|
+
});
|
|
84
|
+
// The PUG-REST assay-summary wire shape (a table of columns + rows), validated
|
|
85
|
+
// at the fetch boundary; `parseAssaySummary` maps the heading-indexed cells into
|
|
86
|
+
// `PubChemAssay` records. Every field is optional — PubChem omits absent values.
|
|
87
|
+
const PugAssaySummarySchema = z.object({
|
|
88
|
+
Table: z
|
|
89
|
+
.object({
|
|
90
|
+
Columns: z
|
|
91
|
+
.object({
|
|
92
|
+
Column: z.array(z.object({ Heading: z.string().optional() })).optional(),
|
|
93
|
+
})
|
|
94
|
+
.optional(),
|
|
95
|
+
Row: z
|
|
96
|
+
.array(z.object({
|
|
97
|
+
Cell: z
|
|
98
|
+
.array(z.object({
|
|
99
|
+
intval: z.number().optional(),
|
|
100
|
+
fval: z.number().optional(),
|
|
101
|
+
sval: z.string().optional(),
|
|
102
|
+
}))
|
|
103
|
+
.optional(),
|
|
104
|
+
}))
|
|
105
|
+
.optional(),
|
|
106
|
+
})
|
|
107
|
+
.optional(),
|
|
108
|
+
});
|
|
109
|
+
/** Build the PUG-REST URL path segment for the given search type. */
|
|
110
|
+
function buildNamespace(searchBy, query) {
|
|
111
|
+
switch (searchBy) {
|
|
112
|
+
case "name":
|
|
113
|
+
return `compound/name/${encodeURIComponent(query)}`;
|
|
114
|
+
case "smiles":
|
|
115
|
+
return `compound/smiles/${encodeURIComponent(query)}`;
|
|
116
|
+
case "inchi":
|
|
117
|
+
return `compound/inchi/${encodeURIComponent(query)}`;
|
|
118
|
+
case "inchikey":
|
|
119
|
+
return `compound/inchikey/${encodeURIComponent(query)}`;
|
|
120
|
+
case "cid":
|
|
121
|
+
return `compound/cid/${encodeURIComponent(query)}`;
|
|
122
|
+
default:
|
|
123
|
+
return `compound/name/${encodeURIComponent(query)}`;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/** Resolve a compound to its identity + computed properties. Empty array = no match. */
|
|
127
|
+
export async function fetchPubchemCompounds(query, searchBy) {
|
|
128
|
+
const namespace = buildNamespace(searchBy, query);
|
|
129
|
+
const url = `${PUBCHEM_BASE}/${namespace}/property/${PROPERTY_LIST}/JSON`;
|
|
130
|
+
const res = await apiFetchValidated(url, PubChemPropertyResponseSchema, { headers: HEADERS });
|
|
131
|
+
if (res.isErr()) {
|
|
132
|
+
// PubChem returns 404 when the query matches no compound — expected.
|
|
133
|
+
if (res.error.type === "http_status" && res.error.status === 404)
|
|
134
|
+
return [];
|
|
135
|
+
throw new Error(describeApiError(res.error));
|
|
136
|
+
}
|
|
137
|
+
// Already validated + normalized by PugPropertyRowSchema's transform.
|
|
138
|
+
return res.value.PropertyTable?.Properties ?? [];
|
|
139
|
+
}
|
|
140
|
+
/** External registry ids for a CID. Empty array = the CID is in no external registry. */
|
|
141
|
+
export async function fetchPubchemCrossRefs(cid) {
|
|
142
|
+
const url = `${PUBCHEM_BASE}/compound/cid/${cid}/xrefs/RegistryID,SourceName/JSON`;
|
|
143
|
+
const res = await apiFetchValidated(url, PubChemXrefResponseSchema, { headers: HEADERS });
|
|
144
|
+
if (res.isErr()) {
|
|
145
|
+
// PubChem returns 404 when the CID has no cross-references — expected.
|
|
146
|
+
if (res.error.type === "http_status" && res.error.status === 404)
|
|
147
|
+
return [];
|
|
148
|
+
throw new Error(describeApiError(res.error));
|
|
149
|
+
}
|
|
150
|
+
// PUG-REST xrefs returns an InformationList with one entry per CID.
|
|
151
|
+
// Each entry has parallel RegistryID[] and SourceName[] arrays.
|
|
152
|
+
const info = res.value.InformationList?.Information?.[0];
|
|
153
|
+
if (!info?.RegistryID?.length)
|
|
154
|
+
return [];
|
|
155
|
+
const crossRefs = [];
|
|
156
|
+
const registryIds = info.RegistryID;
|
|
157
|
+
const sourceNames = info.SourceName ?? [];
|
|
158
|
+
for (let i = 0; i < registryIds.length; i++) {
|
|
159
|
+
crossRefs.push({
|
|
160
|
+
source: sourceNames[i] ?? "Unknown",
|
|
161
|
+
id: registryIds[i] ?? "",
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
return crossRefs;
|
|
165
|
+
}
|
|
166
|
+
/** Bioassay screening summaries for a CID. Empty array = never screened. */
|
|
167
|
+
export async function fetchPubchemAssays(cid, opts) {
|
|
168
|
+
const url = `${PUBCHEM_BASE}/compound/cid/${cid}/assaysummary/JSON`;
|
|
169
|
+
const res = await apiFetchValidated(url, PugAssaySummarySchema, { headers: HEADERS });
|
|
170
|
+
if (res.isErr()) {
|
|
171
|
+
// PubChem returns 404 when the CID has no assay data — expected.
|
|
172
|
+
if (res.error.type === "http_status" && res.error.status === 404)
|
|
173
|
+
return [];
|
|
174
|
+
throw new Error(describeApiError(res.error));
|
|
175
|
+
}
|
|
176
|
+
let assays = parseAssaySummary(res.value);
|
|
177
|
+
if (opts.activeOnly) {
|
|
178
|
+
assays = assays.filter((a) => a.activityOutcome?.toLowerCase() === "active");
|
|
179
|
+
}
|
|
180
|
+
return assays.slice(0, opts.limit);
|
|
181
|
+
}
|
|
182
|
+
function parseAssaySummary(data) {
|
|
183
|
+
const columns = data.Table?.Columns?.Column ?? [];
|
|
184
|
+
const rows = data.Table?.Row ?? [];
|
|
185
|
+
// Find column indices by heading
|
|
186
|
+
const headingIndex = new Map();
|
|
187
|
+
for (let i = 0; i < columns.length; i++) {
|
|
188
|
+
if (columns[i].Heading) {
|
|
189
|
+
headingIndex.set(columns[i].Heading, i);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const aidIdx = headingIndex.get("AID");
|
|
193
|
+
const nameIdx = headingIndex.get("AssayName");
|
|
194
|
+
const targetIdx = headingIndex.get("TargetName");
|
|
195
|
+
const outcomeIdx = headingIndex.get("ActivityOutcome");
|
|
196
|
+
const valueIdx = headingIndex.get("ActivityValue");
|
|
197
|
+
const results = [];
|
|
198
|
+
for (const row of rows) {
|
|
199
|
+
const cells = row.Cell ?? [];
|
|
200
|
+
const getCellStr = (idx) => {
|
|
201
|
+
if (idx === undefined || idx >= cells.length)
|
|
202
|
+
return null;
|
|
203
|
+
return cells[idx]?.sval ?? null;
|
|
204
|
+
};
|
|
205
|
+
const getCellNum = (idx) => {
|
|
206
|
+
if (idx === undefined || idx >= cells.length)
|
|
207
|
+
return null;
|
|
208
|
+
const cell = cells[idx];
|
|
209
|
+
if (cell?.fval !== undefined)
|
|
210
|
+
return cell.fval;
|
|
211
|
+
if (cell?.intval !== undefined)
|
|
212
|
+
return cell.intval;
|
|
213
|
+
return null;
|
|
214
|
+
};
|
|
215
|
+
results.push({
|
|
216
|
+
aid: getCellNum(aidIdx) ?? 0,
|
|
217
|
+
assayName: getCellStr(nameIdx),
|
|
218
|
+
targetName: getCellStr(targetIdx),
|
|
219
|
+
activityOutcome: getCellStr(outcomeIdx),
|
|
220
|
+
activityValue: getCellNum(valueIdx),
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
return results;
|
|
224
|
+
}
|
|
@@ -2,24 +2,62 @@
|
|
|
2
2
|
* update-working-memory — the agent's single interface for maintaining
|
|
3
3
|
* working memory.
|
|
4
4
|
*
|
|
5
|
-
* Dependency-bearing: the `WorkingMemoryStore`
|
|
6
|
-
* (see the harness-durable-runtime spec). The analysis
|
|
5
|
+
* Dependency-bearing: the `WorkingMemoryStore` and the database `Pool` are
|
|
6
|
+
* captured by the factory (see the harness-durable-runtime spec). The analysis
|
|
7
|
+
* id is read from the request-scoped `Session`.
|
|
7
8
|
*
|
|
8
9
|
* The input is a flat object with a `section` discriminator — not a
|
|
9
10
|
* `z.discriminatedUnion`, which `defineTool` rejects (Anthropic needs a
|
|
10
11
|
* top-level `"type":"object"`). The variant fields are validated by
|
|
11
12
|
* `.refine` so a malformed call (e.g. a `finding` with no `runId`) fails
|
|
12
13
|
* at the loop boundary and surfaces as an `is_error` tool result.
|
|
14
|
+
*
|
|
15
|
+
* Working memory is re-injected in full on every turn, so it is bounded at the
|
|
16
|
+
* write. The text caps are declared in the input schema — a violation is a
|
|
17
|
+
* validation error the model sees before `execute` runs — and the entry-count
|
|
18
|
+
* caps are enforced by the store under its row lock. Neither ever truncates: an
|
|
19
|
+
* over-cap write is refused with a message telling the model to shorten the
|
|
20
|
+
* text or retire a stale entry first.
|
|
21
|
+
*
|
|
22
|
+
* A finding cites the run that produced it, so adding one verifies the run
|
|
23
|
+
* exists AND belongs to this analysis (`cortex_runs.analysis_id`) before it is
|
|
24
|
+
* stored: an unknown runId is an `err(ToolError)` the model can correct from,
|
|
25
|
+
* not a silent reference to a run that never existed.
|
|
13
26
|
*/
|
|
27
|
+
import type { Pool } from "pg";
|
|
28
|
+
import { z } from "zod";
|
|
14
29
|
import type { WorkingMemoryStore } from "../../memory/working-memory.js";
|
|
15
|
-
|
|
30
|
+
declare const inputSchema: z.ZodObject<{
|
|
31
|
+
section: z.ZodEnum<{
|
|
32
|
+
constraint: "constraint";
|
|
33
|
+
goal: "goal";
|
|
34
|
+
hypothesis: "hypothesis";
|
|
35
|
+
finding: "finding";
|
|
36
|
+
}>;
|
|
37
|
+
operation: z.ZodOptional<z.ZodEnum<{
|
|
38
|
+
add: "add";
|
|
39
|
+
revise: "revise";
|
|
40
|
+
retire: "retire";
|
|
41
|
+
}>>;
|
|
42
|
+
text: z.ZodOptional<z.ZodString>;
|
|
43
|
+
id: z.ZodOptional<z.ZodString>;
|
|
44
|
+
origin: z.ZodOptional<z.ZodEnum<{
|
|
45
|
+
user: "user";
|
|
46
|
+
agent: "agent";
|
|
47
|
+
}>>;
|
|
48
|
+
runId: z.ZodOptional<z.ZodString>;
|
|
49
|
+
}, z.core.$strip>;
|
|
50
|
+
type Input = z.infer<typeof inputSchema>;
|
|
51
|
+
interface UpdateOutput {
|
|
52
|
+
readonly ok: true;
|
|
53
|
+
readonly section: Input["section"];
|
|
54
|
+
}
|
|
55
|
+
export declare function createUpdateWorkingMemoryTool(workingMemory: WorkingMemoryStore, pool: Pool): import("../define-tool.js").Tool<{
|
|
16
56
|
section: "constraint" | "goal" | "hypothesis" | "finding";
|
|
17
57
|
operation?: "add" | "revise" | "retire" | undefined;
|
|
18
58
|
text?: string | undefined;
|
|
19
59
|
id?: string | undefined;
|
|
20
60
|
origin?: "user" | "agent" | undefined;
|
|
21
61
|
runId?: string | undefined;
|
|
22
|
-
},
|
|
23
|
-
|
|
24
|
-
section: "constraint" | "goal" | "hypothesis" | "finding";
|
|
25
|
-
}>;
|
|
62
|
+
}, UpdateOutput>;
|
|
63
|
+
export {};
|
|
@@ -2,31 +2,54 @@
|
|
|
2
2
|
* update-working-memory — the agent's single interface for maintaining
|
|
3
3
|
* working memory.
|
|
4
4
|
*
|
|
5
|
-
* Dependency-bearing: the `WorkingMemoryStore`
|
|
6
|
-
* (see the harness-durable-runtime spec). The analysis
|
|
5
|
+
* Dependency-bearing: the `WorkingMemoryStore` and the database `Pool` are
|
|
6
|
+
* captured by the factory (see the harness-durable-runtime spec). The analysis
|
|
7
|
+
* id is read from the request-scoped `Session`.
|
|
7
8
|
*
|
|
8
9
|
* The input is a flat object with a `section` discriminator — not a
|
|
9
10
|
* `z.discriminatedUnion`, which `defineTool` rejects (Anthropic needs a
|
|
10
11
|
* top-level `"type":"object"`). The variant fields are validated by
|
|
11
12
|
* `.refine` so a malformed call (e.g. a `finding` with no `runId`) fails
|
|
12
13
|
* at the loop boundary and surfaces as an `is_error` tool result.
|
|
14
|
+
*
|
|
15
|
+
* Working memory is re-injected in full on every turn, so it is bounded at the
|
|
16
|
+
* write. The text caps are declared in the input schema — a violation is a
|
|
17
|
+
* validation error the model sees before `execute` runs — and the entry-count
|
|
18
|
+
* caps are enforced by the store under its row lock. Neither ever truncates: an
|
|
19
|
+
* over-cap write is refused with a message telling the model to shorten the
|
|
20
|
+
* text or retire a stale entry first.
|
|
21
|
+
*
|
|
22
|
+
* A finding cites the run that produced it, so adding one verifies the run
|
|
23
|
+
* exists AND belongs to this analysis (`cortex_runs.analysis_id`) before it is
|
|
24
|
+
* stored: an unknown runId is an `err(ToolError)` the model can correct from,
|
|
25
|
+
* not a silent reference to a run that never existed.
|
|
13
26
|
*/
|
|
14
|
-
import { ok } from "neverthrow";
|
|
27
|
+
import { err, ok } from "neverthrow";
|
|
15
28
|
import { z } from "zod";
|
|
16
29
|
import { scopeResource } from "../../auth/types.js";
|
|
17
|
-
import { unwrapOrThrow } from "../../lib/result.js";
|
|
30
|
+
import { toThrowable, unwrapOrThrow } from "../../lib/result.js";
|
|
31
|
+
import { isWorkingMemoryRejection, WORKING_MEMORY_LIMITS } from "../../memory/working-memory.js";
|
|
32
|
+
import { queryRun } from "../../state/index.js";
|
|
18
33
|
import { defineTool } from "../define-tool.js";
|
|
34
|
+
const { goalChars, entryChars, constraints, hypotheses, findings } = WORKING_MEMORY_LIMITS;
|
|
19
35
|
const inputSchema = z
|
|
20
36
|
.object({
|
|
21
37
|
section: z.enum(["goal", "constraint", "hypothesis", "finding"]).describe("Which working-memory section to update."),
|
|
22
38
|
operation: z
|
|
23
39
|
.enum(["add", "revise", "retire"])
|
|
24
40
|
.optional()
|
|
25
|
-
.describe("For constraint/hypothesis: add a new entry (default), revise an " +
|
|
41
|
+
.describe("For constraint/hypothesis/finding: add a new entry (default), revise an " +
|
|
42
|
+
"existing entry's text, or retire one that no longer earns its place. " +
|
|
43
|
+
"Ignored for goal — a goal write always replaces the previous goal."),
|
|
26
44
|
text: z
|
|
27
45
|
.string()
|
|
46
|
+
.min(1)
|
|
47
|
+
.max(goalChars, `text is over the ${goalChars}-character cap — nothing was recorded; shorten it and retry.`)
|
|
28
48
|
.optional()
|
|
29
|
-
.describe(
|
|
49
|
+
.describe(`Entry text. Required for goal, and for add/revise on any section; omit when retiring. ` +
|
|
50
|
+
`Max ${entryChars} characters for a constraint/hypothesis/finding, ${goalChars} for the goal. ` +
|
|
51
|
+
`Over the cap is REJECTED, never truncated — write one concise line and leave the detail ` +
|
|
52
|
+
`in the run, which inspect_run still serves.`),
|
|
30
53
|
id: z
|
|
31
54
|
.string()
|
|
32
55
|
.optional()
|
|
@@ -35,30 +58,33 @@ const inputSchema = z
|
|
|
35
58
|
.enum(["user", "agent"])
|
|
36
59
|
.optional()
|
|
37
60
|
.describe("For a new constraint: whether the user stated the rule or you " + "derived it. Defaults to 'agent'."),
|
|
38
|
-
runId: z
|
|
61
|
+
runId: z
|
|
62
|
+
.string()
|
|
63
|
+
.optional()
|
|
64
|
+
.describe("The run that produced the finding. Required when ADDING a finding (revise " +
|
|
65
|
+
"and retire address the finding by its own id instead). Must be an existing " +
|
|
66
|
+
"run of this analysis — copy it verbatim from inspect_run. A runId that does " +
|
|
67
|
+
"not exist is rejected; never invent one."),
|
|
39
68
|
})
|
|
40
|
-
.refine((d) => d.section !== "goal" || d.text !== undefined, {
|
|
69
|
+
.refine((d) => d.section !== "goal" || (d.text !== undefined && d.text.trim().length > 0), {
|
|
41
70
|
message: "text is required when section is 'goal'",
|
|
42
71
|
path: ["text"],
|
|
43
72
|
})
|
|
44
|
-
.refine((d) => d.section !== "finding" || (d.runId !== undefined && d.runId.length > 0), {
|
|
45
|
-
message: "runId is required when
|
|
73
|
+
.refine((d) => d.section !== "finding" || (d.operation ?? "add") !== "add" || (d.runId !== undefined && d.runId.length > 0), {
|
|
74
|
+
message: "runId is required when adding a finding — copy an existing runId from inspect_run",
|
|
46
75
|
path: ["runId"],
|
|
47
|
-
})
|
|
48
|
-
.refine((d) => d.section !== "finding" || (d.text !== undefined && d.text.length > 0), {
|
|
49
|
-
message: "text is required when section is 'finding'",
|
|
50
|
-
path: ["text"],
|
|
51
76
|
})
|
|
52
77
|
.refine((d) => !(d.operation === "revise" || d.operation === "retire") || (d.id !== undefined && d.id.length > 0), {
|
|
53
78
|
message: "id is required when operation is 'revise' or 'retire'",
|
|
54
79
|
path: ["id"],
|
|
55
80
|
})
|
|
56
|
-
.refine((d) => {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
message:
|
|
81
|
+
.refine((d) => d.section === "goal" || (d.operation ?? "add") === "retire" || (d.text !== undefined && d.text.trim().length > 0), {
|
|
82
|
+
message: "text is required to add or revise a constraint/hypothesis/finding",
|
|
83
|
+
path: ["text"],
|
|
84
|
+
})
|
|
85
|
+
.refine((d) => d.section === "goal" || d.text === undefined || d.text.trim().length <= entryChars, {
|
|
86
|
+
message: `a constraint/hypothesis/finding is capped at ${entryChars} characters (only the goal may reach ` +
|
|
87
|
+
`${goalChars}). Nothing was recorded — rewrite it as one concise line and retry.`,
|
|
62
88
|
path: ["text"],
|
|
63
89
|
});
|
|
64
90
|
/** Map a validated flat input to a constraint/hypothesis list amendment. */
|
|
@@ -76,37 +102,81 @@ function toConstraintOp(input) {
|
|
|
76
102
|
const base = toListOp(input);
|
|
77
103
|
return base.op === "add" ? { ...base, origin: input.origin ?? "agent" } : base;
|
|
78
104
|
}
|
|
79
|
-
|
|
105
|
+
/** As `toListOp`, but an added finding also carries the run it is attributed to. */
|
|
106
|
+
function toFindingOp(input) {
|
|
107
|
+
const base = toListOp(input);
|
|
108
|
+
return base.op === "add" ? { ...base, runId: input.runId } : base;
|
|
109
|
+
}
|
|
110
|
+
export function createUpdateWorkingMemoryTool(workingMemory, pool) {
|
|
111
|
+
/** Write one amendment, mapping a refused write to a model-visible error. */
|
|
112
|
+
async function apply(analysisId, section, value) {
|
|
113
|
+
const written = await workingMemory.updateSection(analysisId, section, value);
|
|
114
|
+
if (written.isErr()) {
|
|
115
|
+
// A refused write (cap hit, unknown id) is an expected outcome the model
|
|
116
|
+
// can act on — its message carries the remedy. A DbError is not: it throws,
|
|
117
|
+
// and the loop's dispatch catch maps it to a generic error tool result.
|
|
118
|
+
if (isWorkingMemoryRejection(written.error)) {
|
|
119
|
+
return err({ error: written.error.message, retryable: true });
|
|
120
|
+
}
|
|
121
|
+
throw toThrowable(written.error);
|
|
122
|
+
}
|
|
123
|
+
return ok({ ok: true, section });
|
|
124
|
+
}
|
|
80
125
|
return defineTool({
|
|
81
126
|
id: "update_working_memory",
|
|
82
|
-
description: "Maintain the analysis's working memory —
|
|
83
|
-
"
|
|
84
|
-
"the
|
|
85
|
-
"
|
|
86
|
-
"
|
|
127
|
+
description: "Maintain the analysis's working memory — the durable interpretive layer that survives " +
|
|
128
|
+
"context-window eviction. It is re-injected IN FULL on every turn, so every entry is re-read " +
|
|
129
|
+
"for the life of the analysis: promote only what must outlive the window, and retire what no " +
|
|
130
|
+
"longer earns its place.\n\n" +
|
|
131
|
+
"Sections (one per call; the other three are left untouched):\n" +
|
|
132
|
+
`- goal — the analysis's current objective as you understand it. A write replaces it. Max ${goalChars} chars.\n` +
|
|
133
|
+
`- constraint — a binding rule the rest of the analysis must obey. origin 'user' (the user ` +
|
|
134
|
+
`stated it: "use FDR 0.01") or 'agent' (you derived it: "the data is paired, so every test must ` +
|
|
135
|
+
`be paired"). Max ${constraints} entries.\n` +
|
|
136
|
+
`- hypothesis — a hypothesis under active exploration. Retire it once it is resolved or ` +
|
|
137
|
+
`abandoned. Max ${hypotheses} entries.\n` +
|
|
138
|
+
`- finding — a durable conclusion, cited to the run that produced it (runId, copied verbatim ` +
|
|
139
|
+
`from inspect_run). Record the conclusion, never the run's contents — the run itself stays ` +
|
|
140
|
+
`retrievable with inspect_run. Max ${findings} entries across all runs.\n\n` +
|
|
141
|
+
"Operations (constraint / hypothesis / finding): 'add' (default), 'revise' (id + new text), " +
|
|
142
|
+
"'retire' (id). Copy the id from the [id] shown against each entry in the rendered working memory.\n\n" +
|
|
143
|
+
`Caps are enforced, never truncated: text over the cap (${entryChars} chars; ${goalChars} for the goal), or an ` +
|
|
144
|
+
"add into a full section, is REJECTED and nothing is stored. Shorten the text, or retire a stale " +
|
|
145
|
+
"entry before adding the new one.\n\n" +
|
|
146
|
+
"PROMOTE: a binding rule the user states or you derive; a durable conclusion a run produced; a " +
|
|
147
|
+
"shift in the analysis's objective; a hypothesis you are actively testing.\n" +
|
|
148
|
+
'DO NOT PROMOTE: transient conversational asks ("show me the top genes"), restatements of the ' +
|
|
149
|
+
"user's last message, run contents (tables, gene lists, file paths), or anything you can retrieve " +
|
|
150
|
+
"again with inspect_run, read_file, or search.",
|
|
87
151
|
inputSchema,
|
|
88
152
|
execute: async (input, ctx) => {
|
|
89
153
|
const analysisId = scopeResource(ctx.session.scope).resourceId;
|
|
90
154
|
switch (input.section) {
|
|
91
155
|
case "goal":
|
|
92
|
-
|
|
93
|
-
text: input.text,
|
|
94
|
-
}));
|
|
95
|
-
break;
|
|
156
|
+
return apply(analysisId, "goal", { text: input.text });
|
|
96
157
|
case "constraint":
|
|
97
|
-
|
|
98
|
-
break;
|
|
158
|
+
return apply(analysisId, "constraint", toConstraintOp(input));
|
|
99
159
|
case "hypothesis":
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
160
|
+
return apply(analysisId, "hypothesis", toListOp(input));
|
|
161
|
+
case "finding": {
|
|
162
|
+
const op = toFindingOp(input);
|
|
163
|
+
if (op.op === "add") {
|
|
164
|
+
// A run of another analysis is as wrong as one that does not exist —
|
|
165
|
+
// both are "no such run here", so they share one message.
|
|
166
|
+
const run = unwrapOrThrow(await queryRun(pool, op.runId));
|
|
167
|
+
if (!run || run.analysisId !== analysisId) {
|
|
168
|
+
return err({
|
|
169
|
+
error: `unknown runId "${op.runId}" — this analysis has no such run, so the ` +
|
|
170
|
+
`finding was NOT recorded. Call inspect_run with no arguments to list ` +
|
|
171
|
+
`this analysis's runs, copy an exact runId from that list, and retry. ` +
|
|
172
|
+
`Do not guess a runId.`,
|
|
173
|
+
retryable: true,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return apply(analysisId, "finding", op);
|
|
178
|
+
}
|
|
108
179
|
}
|
|
109
|
-
return ok({ ok: true, section: input.section });
|
|
110
180
|
},
|
|
111
181
|
});
|
|
112
182
|
}
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
* agent — they are constructed inside `harness/execution/report-runner.ts`
|
|
4
4
|
* so each tool factory shares the runner's closure-captured outcome state
|
|
5
5
|
* and preview-dir paths. The conversation agent reaches the report-builder
|
|
6
|
-
* through
|
|
7
|
-
*
|
|
6
|
+
* through its `submit_report` tool, which orchestrates the whole iteration.
|
|
7
|
+
* (That conversation-agent tool shares the id `submit_report` with this
|
|
8
|
+
* barrel's builder-side terminal tool — different rosters, never mixed.)
|
|
8
9
|
*/
|
|
9
10
|
export { createBuildReportTool, type BuildReportToolState } from "./build-report.js";
|
|
10
11
|
export { createSubmitReportTool, type ReportOutcome, type SubmitReportToolState } from "./submit-report.js";
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
* agent — they are constructed inside `harness/execution/report-runner.ts`
|
|
4
4
|
* so each tool factory shares the runner's closure-captured outcome state
|
|
5
5
|
* and preview-dir paths. The conversation agent reaches the report-builder
|
|
6
|
-
* through
|
|
7
|
-
*
|
|
6
|
+
* through its `submit_report` tool, which orchestrates the whole iteration.
|
|
7
|
+
* (That conversation-agent tool shares the id `submit_report` with this
|
|
8
|
+
* barrel's builder-side terminal tool — different rosters, never mixed.)
|
|
8
9
|
*/
|
|
9
10
|
export { createBuildReportTool } from "./build-report.js";
|
|
10
11
|
export { createSubmitReportTool } from "./submit-report.js";
|
|
@@ -16,6 +16,7 @@ import { ok } from "neverthrow";
|
|
|
16
16
|
import { z } from "zod";
|
|
17
17
|
import { AnalogicalReasonerOutputSchema } from "@inflexa-ai/harness/contracts/analogy-report.js";
|
|
18
18
|
import { analogicalReasonerPrompt } from "../../prompts/analogical-reasoner.js";
|
|
19
|
+
import { composeSystemPrompt } from "../../agents/system-prompt.js";
|
|
19
20
|
import { forSubAgent } from "../../auth/types.js";
|
|
20
21
|
import { unwrapOrThrow } from "../../lib/result.js";
|
|
21
22
|
import { finalText, runAgent } from "../../loop/run-agent.js";
|
|
@@ -158,27 +159,39 @@ export function createGenerateAnalogyReportTool(deps) {
|
|
|
158
159
|
searchSemanticScholarTool,
|
|
159
160
|
searchArxivTool,
|
|
160
161
|
createSearchGithubReposTool({ githubToken: deps.bioKeys.github }),
|
|
161
|
-
ncbi.
|
|
162
|
-
ncbi.getArticleDetails,
|
|
163
|
-
ncbi.getArticleFullText,
|
|
162
|
+
ncbi.pubmed,
|
|
164
163
|
];
|
|
165
164
|
const agent = {
|
|
166
165
|
id: AGENT_ID,
|
|
167
|
-
systemPrompt: analogicalReasonerPrompt,
|
|
166
|
+
systemPrompt: composeSystemPrompt(analogicalReasonerPrompt),
|
|
168
167
|
model: deps.model,
|
|
169
168
|
tools: reasonerTools,
|
|
170
169
|
maxIterations: RESEARCH_MAX_ITERATIONS,
|
|
171
170
|
};
|
|
172
171
|
return defineTool({
|
|
173
172
|
id: "generate_analogy_report",
|
|
174
|
-
description: "
|
|
175
|
-
"problem
|
|
176
|
-
"
|
|
177
|
-
"
|
|
178
|
-
"
|
|
179
|
-
"
|
|
180
|
-
"
|
|
181
|
-
"
|
|
173
|
+
description: "Idea-generation engine. Extracts structural analogies for an open-ended " +
|
|
174
|
+
"scientific problem and returns real, cited solutions from other fields " +
|
|
175
|
+
"(control theory, ML, ecology, logistics, economics…).\n" +
|
|
176
|
+
"WHEN: any exploratory or hypothesis-generation turn — 'what could we do " +
|
|
177
|
+
"about X', 'what should we try', 'how could we approach this', 'we're " +
|
|
178
|
+
"stuck', 'brainstorm ideas', 'are there precedents outside biology'. The " +
|
|
179
|
+
"trigger is exploratory INTENT, not the words 'analogy' or 'cross-domain' " +
|
|
180
|
+
"— users rarely name it, so infer it and reach for the tool proactively.\n" +
|
|
181
|
+
"NOT for: in-domain literature review (use `literature_reviewer` or " +
|
|
182
|
+
"`pubmed`), single-gene factual lookup (use `search_gene`), or " +
|
|
183
|
+
"execution-mode turns where the user just wants the next step done. It " +
|
|
184
|
+
"drives a multi-step research sub-agent over live literature search, so " +
|
|
185
|
+
"it is slow — never spend it on a fact you could look up.\n" +
|
|
186
|
+
"RETURNS an AnalogyReport envelope the UI renders as an inline card. An " +
|
|
187
|
+
"analogy the search could not fill carries a `coverage` tag " +
|
|
188
|
+
"(`queried_no_data` | `search_failed` | `not_loaded`) with an empty " +
|
|
189
|
+
"`solutions` array — informational, NOT an error. A top-level `error` " +
|
|
190
|
+
"field means no report was produced: on `error.kind === " +
|
|
191
|
+
'"extraction-failed"` do NOT retry with the same or a similar problem ' +
|
|
192
|
+
"statement (the wrapper already retried internally — you would burn " +
|
|
193
|
+
"latency on the same failure). Surface the message and ask the user to " +
|
|
194
|
+
"narrow the problem.",
|
|
182
195
|
inputSchema: generateAnalogyReportInputSchema,
|
|
183
196
|
execute: async (input, ctx) => {
|
|
184
197
|
const childSession = forSubAgent(ctx.session, AGENT_ID);
|
|
@@ -2,12 +2,19 @@
|
|
|
2
2
|
* generatePlan — a loop-driving tool: a focused `runAgent` loop that builds
|
|
3
3
|
* an analysis plan (a DAG of steps).
|
|
4
4
|
*
|
|
5
|
-
* The
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* The dataset's facts are NOT an input. They are read here, server-side, from
|
|
6
|
+
* the analysis's persisted data profile — the one durable record of what the
|
|
7
|
+
* input data is — and projected into the planner's seed. The caller supplies
|
|
8
|
+
* only what the profile cannot hold: the research question, facts the user
|
|
9
|
+
* volunteered (`analystNotes`), prior run results, and plan constraints. A
|
|
10
|
+
* caller-typed dataset summary would be a model re-transcribing a record the
|
|
11
|
+
* database already holds, and it would lose fidelity at every hop.
|
|
9
12
|
*
|
|
10
|
-
*
|
|
13
|
+
* The tool drives an internal "planner" agent that communicates outcomes
|
|
14
|
+
* EXCLUSIVELY via four terminal tool calls:
|
|
15
|
+
*
|
|
16
|
+
* - validate_plan(plan) → non-terminal dry-run: an any-shape candidate
|
|
17
|
+
* in, {valid, issues} out (schema + semantic)
|
|
11
18
|
* - submit_plan(plan) → terminal success: re-validates + persists
|
|
12
19
|
* - request_clarification(…) → terminal: planner needs more context
|
|
13
20
|
* - report_blocker(reason) → terminal: no viable plan
|