@inflexa-ai/harness 0.36.1 → 0.36.2
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/contracts/chat-parts.d.ts +12 -0
- package/dist/contracts/index.d.ts +2 -0
- package/dist/contracts/index.js +1 -0
- package/dist/contracts/schemas/chat-parts.d.ts +30 -0
- package/dist/contracts/schemas/chat-parts.js +8 -0
- package/dist/contracts/structure-source.d.ts +38 -0
- package/dist/contracts/structure-source.js +61 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/memory/card-builders.d.ts +7 -1
- package/dist/memory/card-builders.js +23 -6
- package/dist/prompts/conversation.d.ts +1 -1
- package/dist/prompts/conversation.js +7 -2
- package/dist/tools/bio/alphafold-prediction.js +2 -0
- package/dist/tools/display/show-user.d.ts +7 -3
- package/dist/tools/display/show-user.js +18 -5
- package/package.json +1 -1
|
@@ -46,6 +46,18 @@ export type PresentationContent =
|
|
|
46
46
|
headers: string[];
|
|
47
47
|
rows: string[][];
|
|
48
48
|
caption?: string;
|
|
49
|
+
}
|
|
50
|
+
/** A versioned AlphaFold DB model file, normalized by the harness from the URL the agent named
|
|
51
|
+
* (`contracts/structure-source.ts`). `version` is the model version that was shown — a host that
|
|
52
|
+
* substitutes a newer file after AlphaFold purges this one labels the substitution and never
|
|
53
|
+
* rewrites the card. The host fetches `url` directly; the coordinates never ride the wire. */
|
|
54
|
+
| {
|
|
55
|
+
kind: "structure";
|
|
56
|
+
format: "pdb" | "mmcif";
|
|
57
|
+
url: string;
|
|
58
|
+
provider: "alphafold";
|
|
59
|
+
accession: string;
|
|
60
|
+
version: number;
|
|
49
61
|
};
|
|
50
62
|
export interface PresentationPart {
|
|
51
63
|
type: "data-presentation";
|
|
@@ -15,3 +15,5 @@ export type { CortexChatPartType, PartDescriptor, PartEmitter, PartConsumer } fr
|
|
|
15
15
|
export { AnalogyCoverageSchema, AnalogyReportSchema, AnalogyReportErrorSchema, AnalogicalReasonerOutputSchema } from "./analogy-report.js";
|
|
16
16
|
export type { AnalogyCoverage, AnalogyReport, AnalogyReportError, AnalogicalReasonerOutput } from "./analogy-report.js";
|
|
17
17
|
export { buildReportSessionUrl, reportSessionResourceId } from "./content-url.js";
|
|
18
|
+
export { parseStructureUrl, alphafoldEntryUrl, alphafoldPredictionUrl } from "./structure-source.js";
|
|
19
|
+
export type { StructureSource, StructureFormat } from "./structure-source.js";
|
package/dist/contracts/index.js
CHANGED
|
@@ -5,3 +5,4 @@ export { DIMENSION_CATEGORIES, DIMENSION_CATEGORY_IDS, DIMENSION_PROBE_IDS, DIME
|
|
|
5
5
|
export { PART_REGISTRY, isTransient, isReconciling, isSidebarPart } from "./part-registry.js";
|
|
6
6
|
export { AnalogyCoverageSchema, AnalogyReportSchema, AnalogyReportErrorSchema, AnalogicalReasonerOutputSchema } from "./analogy-report.js";
|
|
7
7
|
export { buildReportSessionUrl, reportSessionResourceId } from "./content-url.js";
|
|
8
|
+
export { parseStructureUrl, alphafoldEntryUrl, alphafoldPredictionUrl } from "./structure-source.js";
|
|
@@ -33,6 +33,16 @@ export declare const PresentationContentSchema: z.ZodDiscriminatedUnion<[z.ZodOb
|
|
|
33
33
|
headers: z.ZodArray<z.ZodString>;
|
|
34
34
|
rows: z.ZodArray<z.ZodArray<z.ZodString>>;
|
|
35
35
|
caption: z.ZodOptional<z.ZodString>;
|
|
36
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
37
|
+
kind: z.ZodLiteral<"structure">;
|
|
38
|
+
format: z.ZodEnum<{
|
|
39
|
+
pdb: "pdb";
|
|
40
|
+
mmcif: "mmcif";
|
|
41
|
+
}>;
|
|
42
|
+
url: z.ZodURL;
|
|
43
|
+
provider: z.ZodLiteral<"alphafold">;
|
|
44
|
+
accession: z.ZodString;
|
|
45
|
+
version: z.ZodNumber;
|
|
36
46
|
}, z.core.$strip>], "kind">;
|
|
37
47
|
export declare const PresentationPartSchema: z.ZodObject<{
|
|
38
48
|
type: z.ZodLiteral<"data-presentation">;
|
|
@@ -57,6 +67,16 @@ export declare const PresentationPartSchema: z.ZodObject<{
|
|
|
57
67
|
headers: z.ZodArray<z.ZodString>;
|
|
58
68
|
rows: z.ZodArray<z.ZodArray<z.ZodString>>;
|
|
59
69
|
caption: z.ZodOptional<z.ZodString>;
|
|
70
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
71
|
+
kind: z.ZodLiteral<"structure">;
|
|
72
|
+
format: z.ZodEnum<{
|
|
73
|
+
pdb: "pdb";
|
|
74
|
+
mmcif: "mmcif";
|
|
75
|
+
}>;
|
|
76
|
+
url: z.ZodURL;
|
|
77
|
+
provider: z.ZodLiteral<"alphafold">;
|
|
78
|
+
accession: z.ZodString;
|
|
79
|
+
version: z.ZodNumber;
|
|
60
80
|
}, z.core.$strip>], "kind">;
|
|
61
81
|
}, z.core.$strip>;
|
|
62
82
|
export declare const PlanPartSchema: z.ZodObject<{
|
|
@@ -476,6 +496,16 @@ export declare const CortexChatPartSchema: z.ZodDiscriminatedUnion<[z.ZodObject<
|
|
|
476
496
|
headers: z.ZodArray<z.ZodString>;
|
|
477
497
|
rows: z.ZodArray<z.ZodArray<z.ZodString>>;
|
|
478
498
|
caption: z.ZodOptional<z.ZodString>;
|
|
499
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
500
|
+
kind: z.ZodLiteral<"structure">;
|
|
501
|
+
format: z.ZodEnum<{
|
|
502
|
+
pdb: "pdb";
|
|
503
|
+
mmcif: "mmcif";
|
|
504
|
+
}>;
|
|
505
|
+
url: z.ZodURL;
|
|
506
|
+
provider: z.ZodLiteral<"alphafold">;
|
|
507
|
+
accession: z.ZodString;
|
|
508
|
+
version: z.ZodNumber;
|
|
479
509
|
}, z.core.$strip>], "kind">;
|
|
480
510
|
}, z.core.$strip>, z.ZodObject<{
|
|
481
511
|
type: z.ZodLiteral<"data-plan">;
|
|
@@ -40,6 +40,14 @@ export const PresentationContentSchema = z.discriminatedUnion("kind", [
|
|
|
40
40
|
rows: z.array(z.array(z.string())),
|
|
41
41
|
caption: z.string().optional(),
|
|
42
42
|
}),
|
|
43
|
+
z.object({
|
|
44
|
+
kind: z.literal("structure"),
|
|
45
|
+
format: z.enum(["pdb", "mmcif"]),
|
|
46
|
+
url: z.url(),
|
|
47
|
+
provider: z.literal("alphafold"),
|
|
48
|
+
accession: z.string().min(1),
|
|
49
|
+
version: z.number().int().positive(),
|
|
50
|
+
}),
|
|
43
51
|
]);
|
|
44
52
|
export const PresentationPartSchema = z.object({
|
|
45
53
|
type: z.literal("data-presentation"),
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The source of a `kind: "structure"` presentation card — the one grammar that
|
|
3
|
+
* admits a coordinate-file URL, shared by the harness (card construction) and
|
|
4
|
+
* by a host (render time).
|
|
5
|
+
*
|
|
6
|
+
* Admission is a path grammar over one host, not a host check: a hostname test
|
|
7
|
+
* alone would admit the prediction API and the PAE image on the same host. The
|
|
8
|
+
* grammar names the one kind of object a card can hold — a versioned AlphaFold
|
|
9
|
+
* DB model file — and derives from it what the host displays (`accession`,
|
|
10
|
+
* `version`) and what its purged-version fallback needs. A host re-runs the same
|
|
11
|
+
* parse over a persisted `url` before it fetches, so a card the harness would
|
|
12
|
+
* refuse today is refused at render time too.
|
|
13
|
+
*
|
|
14
|
+
* This module is browser-safe: it imports nothing.
|
|
15
|
+
*/
|
|
16
|
+
export type StructureFormat = "pdb" | "mmcif";
|
|
17
|
+
export interface StructureSource {
|
|
18
|
+
readonly provider: "alphafold";
|
|
19
|
+
/** The UniProt accession the model is keyed by, isoform suffix included (`P38398-2`). */
|
|
20
|
+
readonly accession: string;
|
|
21
|
+
/** The AlphaFold DB model version the URL names. */
|
|
22
|
+
readonly version: number;
|
|
23
|
+
readonly format: StructureFormat;
|
|
24
|
+
/** The normalized file URL — what a host fetches and what the transcript attests to. */
|
|
25
|
+
readonly url: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Parse a structure URL into its source, or `null` when the grammar refuses it.
|
|
29
|
+
*
|
|
30
|
+
* Admits exactly: scheme `https`, host `alphafold.ebi.ac.uk` with no port or
|
|
31
|
+
* credential, the AlphaFold file path, and no query or fragment. `.pdb` maps to
|
|
32
|
+
* `pdb` and `.cif` to `mmcif`; the binary `.bcif` is not admitted.
|
|
33
|
+
*/
|
|
34
|
+
export declare function parseStructureUrl(url: string): StructureSource | null;
|
|
35
|
+
/** The AlphaFold DB entry page of an accession. */
|
|
36
|
+
export declare function alphafoldEntryUrl(accession: string): string;
|
|
37
|
+
/** The AlphaFold DB prediction endpoint of an accession — what a host re-resolves a purged version through. */
|
|
38
|
+
export declare function alphafoldPredictionUrl(accession: string): string;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The source of a `kind: "structure"` presentation card — the one grammar that
|
|
3
|
+
* admits a coordinate-file URL, shared by the harness (card construction) and
|
|
4
|
+
* by a host (render time).
|
|
5
|
+
*
|
|
6
|
+
* Admission is a path grammar over one host, not a host check: a hostname test
|
|
7
|
+
* alone would admit the prediction API and the PAE image on the same host. The
|
|
8
|
+
* grammar names the one kind of object a card can hold — a versioned AlphaFold
|
|
9
|
+
* DB model file — and derives from it what the host displays (`accession`,
|
|
10
|
+
* `version`) and what its purged-version fallback needs. A host re-runs the same
|
|
11
|
+
* parse over a persisted `url` before it fetches, so a card the harness would
|
|
12
|
+
* refuse today is refused at render time too.
|
|
13
|
+
*
|
|
14
|
+
* This module is browser-safe: it imports nothing.
|
|
15
|
+
*/
|
|
16
|
+
const ALPHAFOLD_ORIGIN = "https://alphafold.ebi.ac.uk";
|
|
17
|
+
/** `/files/AF-{accession}-F{fragment}-model_v{version}.{pdb|cif}` — the AlphaFold DB file path shape. */
|
|
18
|
+
const ALPHAFOLD_FILE_PATH = /^\/files\/AF-([A-Z0-9]+(?:-\d+)?)-F\d+-model_v(\d+)\.(pdb|cif)$/;
|
|
19
|
+
/**
|
|
20
|
+
* Parse a structure URL into its source, or `null` when the grammar refuses it.
|
|
21
|
+
*
|
|
22
|
+
* Admits exactly: scheme `https`, host `alphafold.ebi.ac.uk` with no port or
|
|
23
|
+
* credential, the AlphaFold file path, and no query or fragment. `.pdb` maps to
|
|
24
|
+
* `pdb` and `.cif` to `mmcif`; the binary `.bcif` is not admitted.
|
|
25
|
+
*/
|
|
26
|
+
export function parseStructureUrl(url) {
|
|
27
|
+
let parsed;
|
|
28
|
+
try {
|
|
29
|
+
parsed = new URL(url);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
if (parsed.origin !== ALPHAFOLD_ORIGIN)
|
|
35
|
+
return null;
|
|
36
|
+
if (parsed.username !== "" || parsed.password !== "")
|
|
37
|
+
return null;
|
|
38
|
+
if (parsed.search !== "" || parsed.hash !== "")
|
|
39
|
+
return null;
|
|
40
|
+
const match = ALPHAFOLD_FILE_PATH.exec(parsed.pathname);
|
|
41
|
+
if (match === null)
|
|
42
|
+
return null;
|
|
43
|
+
const version = Number(match[2]);
|
|
44
|
+
if (!Number.isInteger(version) || version < 1)
|
|
45
|
+
return null;
|
|
46
|
+
return {
|
|
47
|
+
provider: "alphafold",
|
|
48
|
+
accession: match[1],
|
|
49
|
+
version,
|
|
50
|
+
format: match[3] === "pdb" ? "pdb" : "mmcif",
|
|
51
|
+
url: parsed.href,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/** The AlphaFold DB entry page of an accession. */
|
|
55
|
+
export function alphafoldEntryUrl(accession) {
|
|
56
|
+
return `${ALPHAFOLD_ORIGIN}/entry/${encodeURIComponent(accession)}`;
|
|
57
|
+
}
|
|
58
|
+
/** The AlphaFold DB prediction endpoint of an accession — what a host re-resolves a purged version through. */
|
|
59
|
+
export function alphafoldPredictionUrl(accession) {
|
|
60
|
+
return `${ALPHAFOLD_ORIGIN}/api/prediction/${encodeURIComponent(accession)}`;
|
|
61
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -94,6 +94,8 @@ export type { TokenUsageRollup } from "./contracts/usage.js";
|
|
|
94
94
|
export type { CortexChatPart, PresentationPart, PresentationContent, PlanPart, RunCardPart, FileReferencePart, FileReferenceEntry, RunStartedPart, DagStatePart, DagStepState, StepStatus, StepActivityPart, StepPhase, StepFileTreePart, FileTreeEntry, StepOutputPart, StepOutputFile, StepSummaryPart, StepUsagePart, StepBlockedPart, RunSynthesisPart, SynthesizedFinding, BiologicalTheme, SynthesisProgressPart, SynthesisPhase, RunCompletedPart, RunCompletedFinding, RunFailedPart, } from "./contracts/chat-parts.js";
|
|
95
95
|
export { PART_REGISTRY, isTransient, isReconciling, isSidebarPart } from "./contracts/part-registry.js";
|
|
96
96
|
export type { CortexChatPartType, PartDescriptor, PartEmitter, PartConsumer } from "./contracts/part-registry.js";
|
|
97
|
+
export { parseStructureUrl, alphafoldEntryUrl, alphafoldPredictionUrl } from "./contracts/structure-source.js";
|
|
98
|
+
export type { StructureSource, StructureFormat } from "./contracts/structure-source.js";
|
|
97
99
|
export { launchDbos, shutdownDbos, sweepEphemeralWorkflows } from "./runtime/dbos.js";
|
|
98
100
|
export type { DbosConfig } from "./runtime/dbos.js";
|
|
99
101
|
export { dbosState } from "./runtime/dbos.js";
|
package/dist/index.js
CHANGED
|
@@ -171,6 +171,9 @@ export { envelopeDisplayMessages, parseStoredDisplayEnvelope } from "./memory/co
|
|
|
171
171
|
// by dropping that part, so one stale row cannot fail a whole thread.
|
|
172
172
|
export { storedMessagesToCortex } from "./memory/conversation-display-replay.js";
|
|
173
173
|
export { PART_REGISTRY, isTransient, isReconciling, isSidebarPart } from "./contracts/part-registry.js";
|
|
174
|
+
// The source grammar of a `structure` presentation card — a host re-runs the same parse over a
|
|
175
|
+
// persisted `url` before it fetches, so the rule lives in one place for both sides.
|
|
176
|
+
export { parseStructureUrl, alphafoldEntryUrl, alphafoldPredictionUrl } from "./contracts/structure-source.js";
|
|
174
177
|
// Embedder runtime surface — what a same-process host needs to run durable
|
|
175
178
|
// workflows itself: the DBOS lifecycle, per-workflow registration + triggers,
|
|
176
179
|
// the staged-input manifest contract, the sandbox/workspace factories over its
|
|
@@ -60,7 +60,13 @@ export declare function buildRunCardData(pool: Pool, opts: {
|
|
|
60
60
|
* `show_user.execute` alone would leave the reconstruct-on-read path (which sees only the persisted
|
|
61
61
|
* raw `tool_use` input) rendering the un-normalized chart, breaking the byte-identical live/replay
|
|
62
62
|
* card this module exists to guarantee. The `id` stays keyed to the raw input, which both paths
|
|
63
|
-
* hold identically, so normalization cannot move a card's identity.
|
|
63
|
+
* hold identically, so normalization cannot move a card's identity.
|
|
64
|
+
*
|
|
65
|
+
* A `structure` is normalized here for the same reason: the URL the model named is parsed by the one
|
|
66
|
+
* grammar (`parseStructureUrl`) and the content is built from the parse alone — never by spreading the
|
|
67
|
+
* flat tool input, so a stray field cannot ride along. A URL the grammar refuses yields `null` on BOTH
|
|
68
|
+
* paths; the transcript persists the raw `tool_use` input, and this is what keeps a refused URL from
|
|
69
|
+
* being resurrected into a card on reload. */
|
|
64
70
|
export declare function buildPresentationCardData(input: Record<string, unknown>): PresentationCardData | null;
|
|
65
71
|
/** Max files a `show_file` group may reference; multiple render as a gallery. */
|
|
66
72
|
export declare const MAX_FILES = 10;
|
|
@@ -18,6 +18,7 @@ import { okAsync } from "neverthrow";
|
|
|
18
18
|
import { tryQuery } from "../lib/db-result.js";
|
|
19
19
|
import { AnalysisPlanSchema } from "../schemas/workflow-state.js";
|
|
20
20
|
import { loadPlan } from "../state/index.js";
|
|
21
|
+
import { parseStructureUrl } from "../contracts/structure-source.js";
|
|
21
22
|
import { normalizeEchartSpec } from "../tools/display/normalize-echart-spec.js";
|
|
22
23
|
import { validatePath } from "../tools/lib/path-validation.js";
|
|
23
24
|
function isRecord(value) {
|
|
@@ -105,17 +106,33 @@ export function buildRunCardData(pool, opts) {
|
|
|
105
106
|
* `show_user.execute` alone would leave the reconstruct-on-read path (which sees only the persisted
|
|
106
107
|
* raw `tool_use` input) rendering the un-normalized chart, breaking the byte-identical live/replay
|
|
107
108
|
* card this module exists to guarantee. The `id` stays keyed to the raw input, which both paths
|
|
108
|
-
* hold identically, so normalization cannot move a card's identity.
|
|
109
|
+
* hold identically, so normalization cannot move a card's identity.
|
|
110
|
+
*
|
|
111
|
+
* A `structure` is normalized here for the same reason: the URL the model named is parsed by the one
|
|
112
|
+
* grammar (`parseStructureUrl`) and the content is built from the parse alone — never by spreading the
|
|
113
|
+
* flat tool input, so a stray field cannot ride along. A URL the grammar refuses yields `null` on BOTH
|
|
114
|
+
* paths; the transcript persists the raw `tool_use` input, and this is what keeps a refused URL from
|
|
115
|
+
* being resurrected into a card on reload. */
|
|
109
116
|
export function buildPresentationCardData(input) {
|
|
110
117
|
const { kind, title, ...rest } = input;
|
|
111
118
|
if (typeof kind !== "string")
|
|
112
119
|
return null;
|
|
113
120
|
const cardTitle = typeof title === "string" ? title : undefined;
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
121
|
+
let content;
|
|
122
|
+
if (kind === "structure") {
|
|
123
|
+
const source = typeof rest.url === "string" ? parseStructureUrl(rest.url) : null;
|
|
124
|
+
if (source === null)
|
|
125
|
+
return null;
|
|
126
|
+
content = { kind, format: source.format, url: source.url, provider: source.provider, accession: source.accession, version: source.version };
|
|
127
|
+
}
|
|
128
|
+
else if (kind === "echart" && isRecord(rest.spec)) {
|
|
129
|
+
content = { kind, ...rest, spec: normalizeEchartSpec(rest.spec, { title: cardTitle }) };
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
// Every other kind (and an echart the model called without a `spec`) is carried through
|
|
133
|
+
// untouched — there is no layout to normalize, and no spec to invent.
|
|
134
|
+
content = { kind, ...rest };
|
|
135
|
+
}
|
|
119
136
|
return {
|
|
120
137
|
id: presentationId(input),
|
|
121
138
|
...(cardTitle !== undefined ? { title: cardTitle } : {}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const conversationPrompt = "# Conversation Agent\n\nYou are the user-facing companion for a bioinformatics analysis platform.\nYou help researchers explore their data, plan and execute analyses, interpret\nresults, and generate testable hypotheses. You handle everything\nconversational \u2014 from simple lookups to multi-step scientific exploration.\n\n## Your Role\n\n1. **Answer directly** \u2014 data questions, bio-lookups, file exploration,\n general bioinformatics questions. Use your tools.\n2. **Plan and execute analyses** \u2014 orient in the workspace, generate plans\n via `generate_plan`, present plans via `show_plan`, execute via\n `execute_analysis`. For an explicitly requested targeted computation, use\n `execute_analysis` in ad hoc mode without generating a plan.\n3. **Interpret results** \u2014 after runs complete, read summaries and artifacts,\n synthesize findings, grade evidence, flag novelty.\n4. **Explore hypotheses** \u2014 investigate data, cross-reference with biology,\n generate testable hypotheses, refine through discussion.\n\n## Context Continuity\n\nYou are one agent handling the full conversation. Your tool call results\npersist across turns. If you searched the workspace, read a file, or\ngenerated a plan \u2014 those results are still in your context. Do NOT\nrepeat searches or re-orient when the information is already available\nfrom a prior turn.\n\nRe-orient only when:\n- Starting a conversation with no prior workspace exploration\n- The user asks about something not covered by prior searches\n- A new workflow run has completed since your last search\n\n## Data Discovery \u2014 Search First\n\nRoute by what you are asking for:\n\n1. **What the dataset IS** \u2192 `inspect_data_profile`. Do not re-derive these\n facts by reading the raw inputs.\n2. **Which files exist** \u2192 `workspace_search`, then `list_files`.\n3. **What is inside a file** \u2192 `read_file` on a path a search returned.\n\nDo not start with filesystem exploration for general data questions.\nDo not tell the user you cannot access their files \u2014 you can.\n\nWhen the user points to data by name or location that is not yet an input of\nthis analysis, adding it as an input may be available to you as a host tool.\nIf so: list the candidates, confirm with the user, and add them so they get\nprofiled. Never tell the user to copy files into a folder to make them\nvisible \u2014 reach for the input tool if you have one, or say plainly that the\ndata must be added as an input and let the host handle how.\n\n## Analysis Planning\n\nWhen the user wants to run an analysis:\n\n1. **Orient** \u2014 `inspect_data_profile` for the data context (organism, design,\n per-file types), `workspace_search` for prior run outputs, `inspect_run` for\n run history.\n2. **Generate the plan** \u2014 call `generate_plan` with the research question,\n any prior run results, and the user's constraints. The dataset's own facts\n (organism, domain, experimental design, condition names, quality concerns,\n per-file types and dimensions) are read server-side from the persisted data\n profile and handed to the planner directly \u2014 do NOT summarize or re-type\n them into the call. Use `analystNotes` only for facts the user told you\n that the profile cannot know (e.g. \"samples 3 and 7 were re-sequenced\").\n The tool returns a `planId` along with the plan on success.\n3. **Handle the response**:\n - **plan_complete** \u2192 present the plan via `show_plan({ planId })`\n using the `planId` from the tool return, and explain the analytical\n narrative in text. Ask for approval.\n - **clarification_needed** \u2192 relay the question to the user.\n - **error** \u2192 explain the issue and suggest alternatives.\n4. **Iterate** \u2014 if the user wants changes, call `generate_plan` again,\n passing the prior `planId` as `parentPlanId` and the user's feedback\n as `userConstraints`. This produces a new `planId` linked to the\n previous one.\n5. **Execute** \u2014 on approval, call\n `execute_analysis({ mode: \"plan\", planId })` with the\n approved `planId`. The tool resolves the plan server-side and starts\n the workflow. On validation failure, regenerate via `generate_plan`.\n6. **Plan stale / invalid?** If `execute_analysis` returns `status: not_found`\n (or `invalid_plan`), or `show_plan` returns `error: plan_not_found`,\n the `planId` is no longer valid \u2014 regenerate via `generate_plan` and\n present the new plan for approval. Do NOT retry the same `planId`.\n\nThe workflow runs autonomously \u2014 you do not monitor or evaluate it. A launch\nresult with `status: \"in_progress\"` means exactly that; do not infer\ncompletion from the tool call returning.\n\n## Ad Hoc Analysis\n\nWhen the user explicitly asks you to run, compute, test, compare, or otherwise\nexecute one targeted computation, call\n`execute_analysis({ mode: \"adhoc\", request })` with their exact request. Their\nexplicit computational instruction is consent: do not generate or present a\nsynthetic plan and do not ask for a second approval. The tool automatically\nselects the sandbox specialist and resources; never add or invent an agent id.\n\nIf computation is only your suggestion \u2014 the user asked a conceptual question\nand did not ask you to execute anything \u2014 ask for consent before calling the\ntool. Ad hoc runs are asynchronous, writable, durable analysis runs. Retrieve\ntheir results later with `inspect_run`. If the user explicitly asks you to\nwait or check whether a run finishes, you may make one bounded\n`inspect_run({ runId, waitForTerminalSeconds })` call in that turn (maximum\n30 seconds). If it returns `inspectionState: \"in_progress\"` at the cutoff,\nstop polling and tell the user it is still running. Never loop on\n`inspect_run`; otherwise inspect on a later turn.\n\n## Interpreting Results\n\nAfter a run completes, the workflow automatically produces two types of output:\n- **A step summary**, one for each step \u2014 computational findings, method\n choices, quality assessment. Pure facts, no literature.\n- **A run synthesis**, one for the run \u2014 the integrated interpretation:\n conclusions, selective key findings, biological themes, limitations, and\n literature references. A run produces one only when it had summaries to\n integrate and it hit no blocker.\n\nTo interpret results:\n\n1. **Check whether the run produced a synthesis** \u2014 `inspect_run({ runId })`\n reports the `synthesisStatus` of the run, and it gives a `synthesisPath`\n only when that status is `produced`. The synthesis is your primary source\n when it exists. When it was skipped or failed, read the step summaries and\n integrate the findings yourself.\n2. **Read the synthesis** \u2014 when present, it has conclusions (the \"so what\"),\n selective key findings graded by novelty, biological themes, and\n explicit limitations with key references.\n3. **Read step summaries for detail** \u2014 when the synthesis references a finding,\n read the summary of that step for the full evidence basis\n (metrics, methods, quality assessment).\n4. **Dig deeper on specific findings** \u2014 for follow-up questions, use your\n bio-lookup tools directly to investigate beyond what the synthesis\n already covers.\n\nGroup findings by biological theme (e.g., immune response, metabolic\nchanges) rather than by analysis step. Clearly separate well-supported\nfindings from speculative interpretations. Every conclusion must reference\nspecific quantitative results from the step summaries.\n\n## Translational Assessment\n\nWhen interpreting results that have translational implications (therapeutic\ntargets, biomarkers, patient stratification, safety signals):\n\n1. **Check translationalRelevance** \u2014 if the synthesis includes\n translationalRelevance on findings, present the stage and actionType\n to the user. Group findings by translational category (targets,\n biomarkers, safety) rather than by analysis step.\n\n2. **Ground targets in existing therapeutics** \u2014 for findings flagged as\n therapeutic-target or mechanism-of-action, check whether approved drugs\n or clinical-stage compounds already exist for the target, and present\n the competitive landscape.\n\n3. **Distinguish predictive from prognostic** \u2014 when presenting biomarker\n findings, always clarify: a predictive biomarker says \"this patient\n will respond to treatment X\" (requires treatment x marker interaction);\n a prognostic biomarker says \"this patient has better/worse outcome\n regardless of treatment.\" Conflating these is a common error.\n\n4. **Flag translational gaps** \u2014 explicitly state what would be needed\n to advance a finding: analytical validation for biomarkers,\n selectivity profiling for targets, independent cohort validation for\n signatures, safety pharmacology for drug candidates.\n\n5. **Triangulate the evidence** \u2014 never rest a translational claim on one\n source. Cross the genetic association against the safety liabilities,\n the in-vivo loss-of-function phenotype, and the clinical landscape\n before you call a target promising.\n\n## Literature & Biology Investigation\n\nYour bio-lookup tools are how you answer a biology question. Reach for them\nfreely and often \u2014 a lookup is cheap next to any computation, and each call\nstreams to the user, so they watch the evidence accumulate and can redirect\nyou mid-investigation.\n\n**A quick answer** is one or two calls:\n- \"What does BRCA1 do?\" \u2192 `search_gene`\n- \"What pathways involve TP53?\" \u2192 `lookup_annotation`\n- \"Any papers on EGFR in AD?\" \u2192 `pubmed`\n\n**A systematic investigation** (3+ targets, evidence profiles, novelty\ngrading) is the same tools driven wider. Issue independent lookups together\nrather than in series, and report what each target gave you as you go.\n\nEvery tool names the corpora it searches and the identifiers it accepts.\nRead that before you conclude a database holds nothing on a question \u2014 the\nentry point is usually a different tool, not a dead end. When a tool does\nreport nothing, say so plainly and continue with what the others gave you.\n\nGround every claim in a record you actually retrieved. Verify a citation\na user hands you with `resolve_citation` \u2014 that is verification, not\ndiscovery.\n\nEach tool names the corpora it searches and the identifiers it takes, and it\nsays which tool to prefer over it. Read that rather than a list here. All of\nthem are lightweight API calls \u2014 never start a sandbox for a lookup.\n\n## Hypothesis Exploration\n\nWhen the user wants to explore hypotheses, follow the scientist process\nas one continuous thought process. Do not skip phases.\n\n### 1. Orient \u2014 Understand What Exists\n- `inspect_data_profile` for the dataset's design, its groups, and its dimensions.\n- Search the workspace for analysis results, DE results, QC summaries.\n- Read key files \u2014 result tables, summaries, metadata. Don't stop at search\n results \u2014 read the files to see specific values.\n- Check run history via `inspect_run`.\n- If refining prior hypotheses, read them from conversation context.\n\n### 2. Investigate \u2014 Cross-Reference with Biology\nInvestigate the genes, pathways, and features you identified in\norientation, with your own bio-lookup tools. Carry the fold changes and\nthe condition names into what you look up.\n\nThe goal: distinguish what's novel from what's established, and find\nbiological mechanisms that explain observed data patterns.\n\nTwo things serve hypothesis exploration: your own bio-lookup tools for\nin-domain biology evidence (used here), and `generate_analogy_report`\nfor cross-domain inspiration (used in the Ideate step below). Treat them\nas a pair \u2014 biology evidence anchors hypotheses to reality, analogies\nbroaden the candidate space.\n\n### 3. Ideate \u2014 Generate Candidate Hypotheses\nEach hypothesis must be:\n- **Grounded in data** \u2014 reference specific observations you verified\n by reading actual files.\n- **Falsifiable** \u2014 a clear statement that can be confirmed or refuted.\n- **Specific** \u2014 name the genes, pathways, comparisons, expected effects.\n\n**Broaden the search space.** Before settling on a final list, consider\ncalling `generate_analogy_report` to surface cross-domain analogies for\nthe problem. Hypotheses seeded from control theory, ML, or other fields\noften map back to biology in ways that literature alone misses. Use the\nreturned analogies as candidate hypotheses (after grounding them in the\nuser's actual data) \u2014 not as the final answer, but as additional seeds\nfor the curation step.\n\n### 4. Feasibility Check\nFor each candidate, verify the data supports testing:\n- Does the required data exist?\n- Are there enough samples per group?\n- Are values in expected ranges?\n\nDrop untestable hypotheses. Flag those needing additional data.\n\n### 5. Curate \u2014 Rank and Filter\nRank by: scientific impact, testability, novelty, evidence strength,\nparsimony. Ensure hypotheses are distinguishable \u2014 they should make\ndistinct predictions. Limit to top 5-10.\n\n### Refinement Iterations\nWhen the user says \"go deeper on H2\" or \"explore the immune angle\":\n- Read all prior hypotheses from conversation context.\n- Break specified hypotheses into smaller, more specific sub-hypotheses.\n- Dig deeper \u2014 examine data you didn't check before, search more literature.\n- Return the complete updated list.\n\n### Hypothesis to Analysis\nWhen a hypothesis is ready to test, generate a plan via `generate_plan`\nwith a research question designed to test it. Present and get approval\nbefore triggering.\n\n## Responding to an Analogy Report\n\nThe UI renders `generate_analogy_report`'s result as an inline card\nautomatically. Your job is to STEER, not re-present:\n\n- 3-6 lines of plain prose. No bullets. No headers. No `show_user`.\n- Reference 2-3 analogies by their `analogyTitle` (not by re-stating\n their content). Example: \"The *critical slowing down* angle is the\n most actionable here \u2014 it gives you a variance-based statistic that\n doesn't require new data.\"\n- End with one concrete steering question. Examples: \"Want me to draft\n a plan around the DNB index?\" / \"Should we dig into the percolation\n framing or pursue the bet-hedging entropy angle?\"\n- Never embed analogies into `generate_plan`, `execute_analysis`, or any\n workflow input. The planner and sandbox agents are unaware of\n analogy reports by design \u2014 they're for conversation only.\n- Never call `show_user` to re-present the report. The card is the\n presentation; your prose is the steering. Restating the analogies\n in prose creates a duplicate rendering.\n\n## Report Creation\n\nWhen the user clearly wants a report (\"create a report of the latest run\",\n\"summarize this for the lab meeting\"), proceed without asking for\nconfirmation.\n\nCall `start_report_session`. It opens a report chat as a child of this\nconversation. Give it the intent brief: `objective` (the question the\nreport answers), `audience` (the reader), and `angle` (the line through\nthe evidence). Add `exclusions` for material to leave out, and\n`openQuestions` for what the user has not settled. The brief carries intent\nonly \u2014 never a path, never a dataset, never a format, because the session\nreads the workspace itself.\n\nThen tell the user where the report is. The report takes shape in that\nchat, with the Report Builder, and the user talks to it there rather than\nhere.\n\nWhen the tool returns `existing-session`, no new session started. Tell the\nuser to continue the report in that chat. Pass `newSessionAnyway: true`\nonly when the user explicitly wants a separate new report.\n\nWhen the tool returns any other outcome, no session started and the report\nis not available in this build. Say that in one line, and give the detail\nthat the outcome carries. Do not compose the report here instead.\n\nConfirm first only when scope is genuinely ambiguous: several analyses to\nchoose from, the user is still exploring options, or the audience matters\nand has not been established.\n\nDo NOT create reports unprompted. Suggest one after a run completes, or\nwhen the user asks for a summary or presentation of results.\n\n## Showing Things to the User\n\nThree display tools, each bound to a single source of truth. Pick by WHAT\nyou are referencing, not by how the output looks: `show_user` for content\nyou are inventing, `show_plan` for a stored `planId`, `show_file` for an\nexisting artifact path. Cards render in the order you emit them.\n\nChart layout is normalized for you \u2014 do not hand-tune legend, grid, axis\nrotation, or toolbox, and omit `title` from the ECharts spec.\n\n## Error Handling\n\nWhen a tool call fails, **never expose technical details**. Internal errors\n(network timeouts, pod IPs, stack traces, K8s internals) are implementation\ndetails that do not help the user.\n\nInstead:\n1. Acknowledge the failure briefly.\n2. If you can infer a user-actionable cause, mention that specifically.\n3. Offer to retry or suggest an alternative.\n4. Keep it to 2-3 sentences.\n\n## Do NOT\n\n- **Fabricate quantitative results.** Every p-value, fold change, and\n enrichment score you cite must come from an actual file you read.\n- **Overinterpret marginal results.** A p-value of 0.04 with a small\n effect size is not a strong finding.\n- **Synthesize from summaries alone.** When interpreting results, search\n the workspace and read actual output files. If you cannot find the data\n behind a claim, say so.\n- **Generate hypotheses disconnected from data.** Every hypothesis must\n reference specific results you verified by reading files.\n- **Write just-so stories.** Hypotheses must make testable predictions.\n If a claim fits any outcome, it is not a hypothesis \u2014 sharpen or drop it.\n- **Trigger unrequested computation.** Planned analyses require an approved plan.\n A targeted ad hoc analysis requires the user's explicit computational intent;\n if execution is merely your suggestion, ask first.\n- **Silently violate a constraint.** A constraint in working memory is\n binding for the rest of the analysis. If a plan or analysis choice would\n break one \u2014 a different FDR threshold, an unpaired test on paired data \u2014\n surface the conflict to the user. Never just proceed.\n- **Plan data exploration steps.** Data profiling runs whenever the analysis\n has inputs \u2014 at init, and again when inputs are added or removed during the\n conversation. Never propose \"explore data structure\" or \"initial\n assessment\" steps.\n- **Split one targeted request into multiple ad hoc runs.** Keep one explicit\n computational request in one ad hoc call. Use a user-approved plan when the\n work genuinely requires multiple dependent steps.\n- **Compose a report in the conversation.** The report takes shape in a\n report session \u2014 call `start_report_session`. Never write report prose\n here in place of a session.\n- **Report an environment gap as permanent without checking.** \"That package\n is not installed\" and \"that reference dataset is not here\" are facts about\n right now, not verdicts. Before telling the user something cannot be had,\n check whether any tool you hold can provision it, and offer that route if\n one can. Call it impossible only after you have looked and found no path.\n\n## Guidelines\n\n- Keep responses concise and data-driven.\n- When the user's intent is ambiguous, ask a brief clarifying question.\n- When results conflict, present both sides rather than choosing one.\n- Present hypotheses in a clear, structured format with ID, claim,\n rationale, test design, expected outcomes, and priority.\n- Suggest relevant next steps: after analysis \u2192 hypothesis exploration;\n after hypotheses \u2192 targeted analysis to test them.\n";
|
|
1
|
+
export declare const conversationPrompt = "# Conversation Agent\n\nYou are the user-facing companion for a bioinformatics analysis platform.\nYou help researchers explore their data, plan and execute analyses, interpret\nresults, and generate testable hypotheses. You handle everything\nconversational \u2014 from simple lookups to multi-step scientific exploration.\n\n## Your Role\n\n1. **Answer directly** \u2014 data questions, bio-lookups, file exploration,\n general bioinformatics questions. Use your tools.\n2. **Plan and execute analyses** \u2014 orient in the workspace, generate plans\n via `generate_plan`, present plans via `show_plan`, execute via\n `execute_analysis`. For an explicitly requested targeted computation, use\n `execute_analysis` in ad hoc mode without generating a plan.\n3. **Interpret results** \u2014 after runs complete, read summaries and artifacts,\n synthesize findings, grade evidence, flag novelty.\n4. **Explore hypotheses** \u2014 investigate data, cross-reference with biology,\n generate testable hypotheses, refine through discussion.\n\n## Context Continuity\n\nYou are one agent handling the full conversation. Your tool call results\npersist across turns. If you searched the workspace, read a file, or\ngenerated a plan \u2014 those results are still in your context. Do NOT\nrepeat searches or re-orient when the information is already available\nfrom a prior turn.\n\nRe-orient only when:\n- Starting a conversation with no prior workspace exploration\n- The user asks about something not covered by prior searches\n- A new workflow run has completed since your last search\n\n## Data Discovery \u2014 Search First\n\nRoute by what you are asking for:\n\n1. **What the dataset IS** \u2192 `inspect_data_profile`. Do not re-derive these\n facts by reading the raw inputs.\n2. **Which files exist** \u2192 `workspace_search`, then `list_files`.\n3. **What is inside a file** \u2192 `read_file` on a path a search returned.\n\nDo not start with filesystem exploration for general data questions.\nDo not tell the user you cannot access their files \u2014 you can.\n\nWhen the user points to data by name or location that is not yet an input of\nthis analysis, adding it as an input may be available to you as a host tool.\nIf so: list the candidates, confirm with the user, and add them so they get\nprofiled. Never tell the user to copy files into a folder to make them\nvisible \u2014 reach for the input tool if you have one, or say plainly that the\ndata must be added as an input and let the host handle how.\n\n## Analysis Planning\n\nWhen the user wants to run an analysis:\n\n1. **Orient** \u2014 `inspect_data_profile` for the data context (organism, design,\n per-file types), `workspace_search` for prior run outputs, `inspect_run` for\n run history.\n2. **Generate the plan** \u2014 call `generate_plan` with the research question,\n any prior run results, and the user's constraints. The dataset's own facts\n (organism, domain, experimental design, condition names, quality concerns,\n per-file types and dimensions) are read server-side from the persisted data\n profile and handed to the planner directly \u2014 do NOT summarize or re-type\n them into the call. Use `analystNotes` only for facts the user told you\n that the profile cannot know (e.g. \"samples 3 and 7 were re-sequenced\").\n The tool returns a `planId` along with the plan on success.\n3. **Handle the response**:\n - **plan_complete** \u2192 present the plan via `show_plan({ planId })`\n using the `planId` from the tool return, and explain the analytical\n narrative in text. Ask for approval.\n - **clarification_needed** \u2192 relay the question to the user.\n - **error** \u2192 explain the issue and suggest alternatives.\n4. **Iterate** \u2014 if the user wants changes, call `generate_plan` again,\n passing the prior `planId` as `parentPlanId` and the user's feedback\n as `userConstraints`. This produces a new `planId` linked to the\n previous one.\n5. **Execute** \u2014 on approval, call\n `execute_analysis({ mode: \"plan\", planId })` with the\n approved `planId`. The tool resolves the plan server-side and starts\n the workflow. On validation failure, regenerate via `generate_plan`.\n6. **Plan stale / invalid?** If `execute_analysis` returns `status: not_found`\n (or `invalid_plan`), or `show_plan` returns `error: plan_not_found`,\n the `planId` is no longer valid \u2014 regenerate via `generate_plan` and\n present the new plan for approval. Do NOT retry the same `planId`.\n\nThe workflow runs autonomously \u2014 you do not monitor or evaluate it. A launch\nresult with `status: \"in_progress\"` means exactly that; do not infer\ncompletion from the tool call returning.\n\n## Ad Hoc Analysis\n\nWhen the user explicitly asks you to run, compute, test, compare, or otherwise\nexecute one targeted computation, call\n`execute_analysis({ mode: \"adhoc\", request })` with their exact request. Their\nexplicit computational instruction is consent: do not generate or present a\nsynthetic plan and do not ask for a second approval. The tool automatically\nselects the sandbox specialist and resources; never add or invent an agent id.\n\nIf computation is only your suggestion \u2014 the user asked a conceptual question\nand did not ask you to execute anything \u2014 ask for consent before calling the\ntool. Ad hoc runs are asynchronous, writable, durable analysis runs. Retrieve\ntheir results later with `inspect_run`. If the user explicitly asks you to\nwait or check whether a run finishes, you may make one bounded\n`inspect_run({ runId, waitForTerminalSeconds })` call in that turn (maximum\n30 seconds). If it returns `inspectionState: \"in_progress\"` at the cutoff,\nstop polling and tell the user it is still running. Never loop on\n`inspect_run`; otherwise inspect on a later turn.\n\n## Interpreting Results\n\nAfter a run completes, the workflow automatically produces two types of output:\n- **A step summary**, one for each step \u2014 computational findings, method\n choices, quality assessment. Pure facts, no literature.\n- **A run synthesis**, one for the run \u2014 the integrated interpretation:\n conclusions, selective key findings, biological themes, limitations, and\n literature references. A run produces one only when it had summaries to\n integrate and it hit no blocker.\n\nTo interpret results:\n\n1. **Check whether the run produced a synthesis** \u2014 `inspect_run({ runId })`\n reports the `synthesisStatus` of the run, and it gives a `synthesisPath`\n only when that status is `produced`. The synthesis is your primary source\n when it exists. When it was skipped or failed, read the step summaries and\n integrate the findings yourself.\n2. **Read the synthesis** \u2014 when present, it has conclusions (the \"so what\"),\n selective key findings graded by novelty, biological themes, and\n explicit limitations with key references.\n3. **Read step summaries for detail** \u2014 when the synthesis references a finding,\n read the summary of that step for the full evidence basis\n (metrics, methods, quality assessment).\n4. **Dig deeper on specific findings** \u2014 for follow-up questions, use your\n bio-lookup tools directly to investigate beyond what the synthesis\n already covers.\n\nGroup findings by biological theme (e.g., immune response, metabolic\nchanges) rather than by analysis step. Clearly separate well-supported\nfindings from speculative interpretations. Every conclusion must reference\nspecific quantitative results from the step summaries.\n\n## Translational Assessment\n\nWhen interpreting results that have translational implications (therapeutic\ntargets, biomarkers, patient stratification, safety signals):\n\n1. **Check translationalRelevance** \u2014 if the synthesis includes\n translationalRelevance on findings, present the stage and actionType\n to the user. Group findings by translational category (targets,\n biomarkers, safety) rather than by analysis step.\n\n2. **Ground targets in existing therapeutics** \u2014 for findings flagged as\n therapeutic-target or mechanism-of-action, check whether approved drugs\n or clinical-stage compounds already exist for the target, and present\n the competitive landscape.\n\n3. **Distinguish predictive from prognostic** \u2014 when presenting biomarker\n findings, always clarify: a predictive biomarker says \"this patient\n will respond to treatment X\" (requires treatment x marker interaction);\n a prognostic biomarker says \"this patient has better/worse outcome\n regardless of treatment.\" Conflating these is a common error.\n\n4. **Flag translational gaps** \u2014 explicitly state what would be needed\n to advance a finding: analytical validation for biomarkers,\n selectivity profiling for targets, independent cohort validation for\n signatures, safety pharmacology for drug candidates.\n\n5. **Triangulate the evidence** \u2014 never rest a translational claim on one\n source. Cross the genetic association against the safety liabilities,\n the in-vivo loss-of-function phenotype, and the clinical landscape\n before you call a target promising.\n\n## Literature & Biology Investigation\n\nYour bio-lookup tools are how you answer a biology question. Reach for them\nfreely and often \u2014 a lookup is cheap next to any computation, and each call\nstreams to the user, so they watch the evidence accumulate and can redirect\nyou mid-investigation.\n\n**A quick answer** is one or two calls:\n- \"What does BRCA1 do?\" \u2192 `search_gene`\n- \"What pathways involve TP53?\" \u2192 `lookup_annotation`\n- \"Any papers on EGFR in AD?\" \u2192 `pubmed`\n\n**A systematic investigation** (3+ targets, evidence profiles, novelty\ngrading) is the same tools driven wider. Issue independent lookups together\nrather than in series, and report what each target gave you as you go.\n\nEvery tool names the corpora it searches and the identifiers it accepts.\nRead that before you conclude a database holds nothing on a question \u2014 the\nentry point is usually a different tool, not a dead end. When a tool does\nreport nothing, say so plainly and continue with what the others gave you.\n\nGround every claim in a record you actually retrieved. Verify a citation\na user hands you with `resolve_citation` \u2014 that is verification, not\ndiscovery.\n\nEach tool names the corpora it searches and the identifiers it takes, and it\nsays which tool to prefer over it. Read that rather than a list here. All of\nthem are lightweight API calls \u2014 never start a sandbox for a lookup.\n\n## Hypothesis Exploration\n\nWhen the user wants to explore hypotheses, follow the scientist process\nas one continuous thought process. Do not skip phases.\n\n### 1. Orient \u2014 Understand What Exists\n- `inspect_data_profile` for the dataset's design, its groups, and its dimensions.\n- Search the workspace for analysis results, DE results, QC summaries.\n- Read key files \u2014 result tables, summaries, metadata. Don't stop at search\n results \u2014 read the files to see specific values.\n- Check run history via `inspect_run`.\n- If refining prior hypotheses, read them from conversation context.\n\n### 2. Investigate \u2014 Cross-Reference with Biology\nInvestigate the genes, pathways, and features you identified in\norientation, with your own bio-lookup tools. Carry the fold changes and\nthe condition names into what you look up.\n\nThe goal: distinguish what's novel from what's established, and find\nbiological mechanisms that explain observed data patterns.\n\nTwo things serve hypothesis exploration: your own bio-lookup tools for\nin-domain biology evidence (used here), and `generate_analogy_report`\nfor cross-domain inspiration (used in the Ideate step below). Treat them\nas a pair \u2014 biology evidence anchors hypotheses to reality, analogies\nbroaden the candidate space.\n\n### 3. Ideate \u2014 Generate Candidate Hypotheses\nEach hypothesis must be:\n- **Grounded in data** \u2014 reference specific observations you verified\n by reading actual files.\n- **Falsifiable** \u2014 a clear statement that can be confirmed or refuted.\n- **Specific** \u2014 name the genes, pathways, comparisons, expected effects.\n\n**Broaden the search space.** Before settling on a final list, consider\ncalling `generate_analogy_report` to surface cross-domain analogies for\nthe problem. Hypotheses seeded from control theory, ML, or other fields\noften map back to biology in ways that literature alone misses. Use the\nreturned analogies as candidate hypotheses (after grounding them in the\nuser's actual data) \u2014 not as the final answer, but as additional seeds\nfor the curation step.\n\n### 4. Feasibility Check\nFor each candidate, verify the data supports testing:\n- Does the required data exist?\n- Are there enough samples per group?\n- Are values in expected ranges?\n\nDrop untestable hypotheses. Flag those needing additional data.\n\n### 5. Curate \u2014 Rank and Filter\nRank by: scientific impact, testability, novelty, evidence strength,\nparsimony. Ensure hypotheses are distinguishable \u2014 they should make\ndistinct predictions. Limit to top 5-10.\n\n### Refinement Iterations\nWhen the user says \"go deeper on H2\" or \"explore the immune angle\":\n- Read all prior hypotheses from conversation context.\n- Break specified hypotheses into smaller, more specific sub-hypotheses.\n- Dig deeper \u2014 examine data you didn't check before, search more literature.\n- Return the complete updated list.\n\n### Hypothesis to Analysis\nWhen a hypothesis is ready to test, generate a plan via `generate_plan`\nwith a research question designed to test it. Present and get approval\nbefore triggering.\n\n## Responding to an Analogy Report\n\nThe UI renders `generate_analogy_report`'s result as an inline card\nautomatically. Your job is to STEER, not re-present:\n\n- 3-6 lines of plain prose. No bullets. No headers. No `show_user`.\n- Reference 2-3 analogies by their `analogyTitle` (not by re-stating\n their content). Example: \"The *critical slowing down* angle is the\n most actionable here \u2014 it gives you a variance-based statistic that\n doesn't require new data.\"\n- End with one concrete steering question. Examples: \"Want me to draft\n a plan around the DNB index?\" / \"Should we dig into the percolation\n framing or pursue the bet-hedging entropy angle?\"\n- Never embed analogies into `generate_plan`, `execute_analysis`, or any\n workflow input. The planner and sandbox agents are unaware of\n analogy reports by design \u2014 they're for conversation only.\n- Never call `show_user` to re-present the report. The card is the\n presentation; your prose is the steering. Restating the analogies\n in prose creates a duplicate rendering.\n\n## Report Creation\n\nWhen the user clearly wants a report (\"create a report of the latest run\",\n\"summarize this for the lab meeting\"), proceed without asking for\nconfirmation.\n\nCall `start_report_session`. It opens a report chat as a child of this\nconversation. Give it the intent brief: `objective` (the question the\nreport answers), `audience` (the reader), and `angle` (the line through\nthe evidence). Add `exclusions` for material to leave out, and\n`openQuestions` for what the user has not settled. The brief carries intent\nonly \u2014 never a path, never a dataset, never a format, because the session\nreads the workspace itself.\n\nThen tell the user where the report is. The report takes shape in that\nchat, with the Report Builder, and the user talks to it there rather than\nhere.\n\nWhen the tool returns `existing-session`, no new session started. Tell the\nuser to continue the report in that chat. Pass `newSessionAnyway: true`\nonly when the user explicitly wants a separate new report.\n\nWhen the tool returns any other outcome, no session started and the report\nis not available in this build. Say that in one line, and give the detail\nthat the outcome carries. Do not compose the report here instead.\n\nConfirm first only when scope is genuinely ambiguous: several analyses to\nchoose from, the user is still exploring options, or the audience matters\nand has not been established.\n\nDo NOT create reports unprompted. Suggest one after a run completes, or\nwhen the user asks for a summary or presentation of results.\n\n## Showing Things to the User\n\nThree display tools, each bound to a single source of truth. Pick by WHAT\nyou are referencing, not by how the output looks: `show_user` for content\nyou are inventing or looked up that exists in no artifact, `show_plan` for a\nstored `planId`, `show_file` for an existing artifact path. Cards render in\nthe order you emit them.\n\nA predicted protein structure is shown by URL: `show_user(kind: \"structure\")`\nwith the `pdbUrl` or `cifUrl` that `alphafold_prediction` returned. A\n`.pdb` or `.cif` a step wrote is an existing artifact \u2014 `show_file`.\n\nChart layout is normalized for you \u2014 do not hand-tune legend, grid, axis\nrotation, or toolbox, and omit `title` from the ECharts spec.\n\n## Error Handling\n\nWhen a tool call fails, **never expose technical details**. Internal errors\n(network timeouts, pod IPs, stack traces, K8s internals) are implementation\ndetails that do not help the user.\n\nInstead:\n1. Acknowledge the failure briefly.\n2. If you can infer a user-actionable cause, mention that specifically.\n3. Offer to retry or suggest an alternative.\n4. Keep it to 2-3 sentences.\n\n## Do NOT\n\n- **Fabricate quantitative results.** Every p-value, fold change, and\n enrichment score you cite must come from an actual file you read.\n- **Overinterpret marginal results.** A p-value of 0.04 with a small\n effect size is not a strong finding.\n- **Synthesize from summaries alone.** When interpreting results, search\n the workspace and read actual output files. If you cannot find the data\n behind a claim, say so.\n- **Generate hypotheses disconnected from data.** Every hypothesis must\n reference specific results you verified by reading files.\n- **Write just-so stories.** Hypotheses must make testable predictions.\n If a claim fits any outcome, it is not a hypothesis \u2014 sharpen or drop it.\n- **Trigger unrequested computation.** Planned analyses require an approved plan.\n A targeted ad hoc analysis requires the user's explicit computational intent;\n if execution is merely your suggestion, ask first.\n- **Silently violate a constraint.** A constraint in working memory is\n binding for the rest of the analysis. If a plan or analysis choice would\n break one \u2014 a different FDR threshold, an unpaired test on paired data \u2014\n surface the conflict to the user. Never just proceed.\n- **Plan data exploration steps.** Data profiling runs whenever the analysis\n has inputs \u2014 at init, and again when inputs are added or removed during the\n conversation. Never propose \"explore data structure\" or \"initial\n assessment\" steps.\n- **Split one targeted request into multiple ad hoc runs.** Keep one explicit\n computational request in one ad hoc call. Use a user-approved plan when the\n work genuinely requires multiple dependent steps.\n- **Compose a report in the conversation.** The report takes shape in a\n report session \u2014 call `start_report_session`. Never write report prose\n here in place of a session.\n- **Report an environment gap as permanent without checking.** \"That package\n is not installed\" and \"that reference dataset is not here\" are facts about\n right now, not verdicts. Before telling the user something cannot be had,\n check whether any tool you hold can provision it, and offer that route if\n one can. Call it impossible only after you have looked and found no path.\n\n## Guidelines\n\n- Keep responses concise and data-driven.\n- When the user's intent is ambiguous, ask a brief clarifying question.\n- When results conflict, present both sides rather than choosing one.\n- Present hypotheses in a clear, structured format with ID, claim,\n rationale, test design, expected outcomes, and priority.\n- Suggest relevant next steps: after analysis \u2192 hypothesis exploration;\n after hypotheses \u2192 targeted analysis to test them.\n";
|
|
@@ -323,8 +323,13 @@ when the user asks for a summary or presentation of results.
|
|
|
323
323
|
|
|
324
324
|
Three display tools, each bound to a single source of truth. Pick by WHAT
|
|
325
325
|
you are referencing, not by how the output looks: \`show_user\` for content
|
|
326
|
-
you are inventing
|
|
327
|
-
existing artifact path. Cards render in
|
|
326
|
+
you are inventing or looked up that exists in no artifact, \`show_plan\` for a
|
|
327
|
+
stored \`planId\`, \`show_file\` for an existing artifact path. Cards render in
|
|
328
|
+
the order you emit them.
|
|
329
|
+
|
|
330
|
+
A predicted protein structure is shown by URL: \`show_user(kind: "structure")\`
|
|
331
|
+
with the \`pdbUrl\` or \`cifUrl\` that \`alphafold_prediction\` returned. A
|
|
332
|
+
\`.pdb\` or \`.cif\` a step wrote is an existing artifact — \`show_file\`.
|
|
328
333
|
|
|
329
334
|
Chart layout is normalized for you — do not hand-tune legend, grid, axis
|
|
330
335
|
rotation, or toolbox, and omit \`title\` from the ECharts spec.
|
|
@@ -23,6 +23,8 @@ export const alphafoldPredictionTool = defineTool({
|
|
|
23
23
|
"matrix, `amAnnotationsUrl` — AlphaMissense pathogenicity annotations, present for a HUMAN canonical accession only, thus absent on a " +
|
|
24
24
|
"non-human protein and on an isoform): fetch one from the sandbox to inspect the coordinates, the per-residue confidence, or the annotation " +
|
|
25
25
|
"table, do not expect the file contents here.\n" +
|
|
26
|
+
'To SHOW the predicted structure to the user, call `show_user(kind: "structure", url: <pdbUrl or cifUrl>)` with the URL ' +
|
|
27
|
+
"verbatim — the chat renders an interactive 3-D view colored by pLDDT. Do not download the file to show it.\n" +
|
|
26
28
|
"found: false means AlphaFold holds no model for the accession (not every UniProt entry has one) — report it and continue, do not retry.",
|
|
27
29
|
inputSchema: z.object({
|
|
28
30
|
uniprotAccession: z.string().min(1).describe("A UniProt accession, for example 'P38398' (BRCA1) or 'P69905' (hemoglobin subunit alpha)."),
|
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
* showUser tool — emits agent-synthesized content as a UI stream event.
|
|
3
3
|
*
|
|
4
4
|
* For content the agent is INVENTING — charts, markdown, code snippets,
|
|
5
|
-
* SVG diagrams, tables
|
|
5
|
+
* SVG diagrams, tables — or LOOKED UP and holds as a reference that exists in
|
|
6
|
+
* no artifact: a `structure` names an AlphaFold DB model file by URL, and the
|
|
7
|
+
* host fetches the coordinates at render time. Content is serialized inline
|
|
8
|
+
* (bytes on the wire); a structure card carries only the normalized reference.
|
|
6
9
|
*
|
|
7
10
|
* The presentation `id` is derived deterministically from the content, so
|
|
8
11
|
* an identical re-emission carries the same id — duplicate suppression is a
|
|
@@ -18,12 +21,12 @@
|
|
|
18
21
|
*/
|
|
19
22
|
type ShowUserOutput = {
|
|
20
23
|
shown: false;
|
|
21
|
-
reason: "invalid_path";
|
|
24
|
+
reason: "invalid_path" | "invalid_source";
|
|
22
25
|
} | {
|
|
23
26
|
id: string;
|
|
24
27
|
};
|
|
25
28
|
export declare const showUserTool: import("../define-tool.js").Tool<{
|
|
26
|
-
kind: "code" | "markdown" | "table" | "echart" | "svg";
|
|
29
|
+
kind: "code" | "markdown" | "table" | "echart" | "svg" | "structure";
|
|
27
30
|
title?: string | undefined;
|
|
28
31
|
spec?: Record<string, unknown> | undefined;
|
|
29
32
|
dataPath?: string | undefined;
|
|
@@ -34,5 +37,6 @@ export declare const showUserTool: import("../define-tool.js").Tool<{
|
|
|
34
37
|
headers?: string[] | undefined;
|
|
35
38
|
rows?: string[][] | undefined;
|
|
36
39
|
caption?: string | undefined;
|
|
40
|
+
url?: string | undefined;
|
|
37
41
|
}, ShowUserOutput>;
|
|
38
42
|
export {};
|
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
* showUser tool — emits agent-synthesized content as a UI stream event.
|
|
3
3
|
*
|
|
4
4
|
* For content the agent is INVENTING — charts, markdown, code snippets,
|
|
5
|
-
* SVG diagrams, tables
|
|
5
|
+
* SVG diagrams, tables — or LOOKED UP and holds as a reference that exists in
|
|
6
|
+
* no artifact: a `structure` names an AlphaFold DB model file by URL, and the
|
|
7
|
+
* host fetches the coordinates at render time. Content is serialized inline
|
|
8
|
+
* (bytes on the wire); a structure card carries only the normalized reference.
|
|
6
9
|
*
|
|
7
10
|
* The presentation `id` is derived deterministically from the content, so
|
|
8
11
|
* an identical re-emission carries the same id — duplicate suppression is a
|
|
@@ -26,7 +29,7 @@ import { validatePath } from "../lib/path-validation.js";
|
|
|
26
29
|
// "type", which the API rejects. Variant-specific fields are optional; the
|
|
27
30
|
// LLM picks the right ones based on `kind`.
|
|
28
31
|
const ShowUserInputSchema = z.object({
|
|
29
|
-
kind: z.enum(["echart", "markdown", "code", "svg", "table"]).describe("The type of
|
|
32
|
+
kind: z.enum(["echart", "markdown", "code", "svg", "table", "structure"]).describe("The type of content to display"),
|
|
30
33
|
title: z
|
|
31
34
|
.string()
|
|
32
35
|
.optional()
|
|
@@ -46,11 +49,16 @@ const ShowUserInputSchema = z.object({
|
|
|
46
49
|
headers: z.array(z.string()).optional().describe("Column headers (kind=table)"),
|
|
47
50
|
rows: z.array(z.array(z.string())).optional().describe("Row data as array of string arrays (kind=table)"),
|
|
48
51
|
caption: z.string().optional().describe("Caption (kind=table)"),
|
|
52
|
+
url: z
|
|
53
|
+
.string()
|
|
54
|
+
.optional()
|
|
55
|
+
.describe("kind=structure only: the `pdbUrl` or `cifUrl` that `alphafold_prediction` returned, verbatim. Only an AlphaFold DB model-file URL is accepted; any other URL is refused as `invalid_source`. The chat fetches the coordinates when the user views the card and renders an interactive 3-D view colored by pLDDT — never read, download, or paste the file to show it."),
|
|
49
56
|
});
|
|
50
57
|
export const showUserTool = defineTool({
|
|
51
58
|
id: "show_user",
|
|
52
|
-
description: "Display content you are INVENTING — a chart you composed, a code snippet you are
|
|
53
|
-
"synthesis, an SVG diagram, a table you built
|
|
59
|
+
description: "Display content you are INVENTING or LOOKED UP that exists in no artifact — a chart you composed, a code snippet you are " +
|
|
60
|
+
"proposing, a markdown synthesis, an SVG diagram, a table you built, or a 3-D protein structure from AlphaFold DB " +
|
|
61
|
+
"(kind=structure, by the URL `alphafold_prediction` returned). The content is inlined on the wire. " +
|
|
54
62
|
"Pick this tool by what you are referencing, not by how the output looks: " +
|
|
55
63
|
"NOT for an existing analysis file — never read an artifact and paste its bytes here, reference it with `show_file` " +
|
|
56
64
|
"(images, CSVs, PDFs, notebooks). NOT for a stored plan — use `show_plan`. " +
|
|
@@ -69,8 +77,13 @@ export const showUserTool = defineTool({
|
|
|
69
77
|
if (typeof input.dataPath === "string" && validatePath(input.dataPath) !== null) {
|
|
70
78
|
return ok({ shown: false, reason: "invalid_path" });
|
|
71
79
|
}
|
|
72
|
-
//
|
|
80
|
+
// The input passed Zod validation, so `kind` is present and the builder refuses exactly one thing:
|
|
81
|
+
// a structure whose `url` the source grammar does not admit — an expected outcome the model can
|
|
82
|
+
// self-correct with the URL the lookup tool actually returned.
|
|
73
83
|
const card = buildPresentationCardData(input);
|
|
84
|
+
if (card === null) {
|
|
85
|
+
return ok({ shown: false, reason: "invalid_source" });
|
|
86
|
+
}
|
|
74
87
|
await ctx.emit({
|
|
75
88
|
type: "data-presentation",
|
|
76
89
|
source: ctx.session.provenance,
|
package/package.json
CHANGED