@inflexa-ai/harness 0.4.2 → 0.4.4
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/app/synthesize-run.d.ts +3 -1
- package/dist/app/synthesize-run.js +14 -3
- package/dist/contracts/chat-parts.d.ts +0 -2
- package/dist/contracts/schemas/chat-parts.d.ts +3 -5
- package/dist/contracts/schemas/chat-parts.js +0 -1
- package/dist/execution/run-synthesis.d.ts +7 -1
- package/dist/execution/run-synthesis.js +181 -39
- package/dist/lib/zod-issues.d.ts +80 -0
- package/dist/lib/zod-issues.js +227 -0
- package/dist/loop/run-agent.js +18 -6
- package/dist/prompts/conversation.d.ts +1 -1
- package/dist/prompts/conversation.js +12 -5
- package/dist/prompts/synthesis-agent.d.ts +1 -1
- package/dist/prompts/synthesis-agent.js +39 -8
- package/dist/schemas/run-synthesis.d.ts +8 -2
- package/dist/schemas/run-synthesis.js +6 -5
- package/dist/state/index.d.ts +2 -2
- package/dist/state/index.js +2 -2
- package/dist/state/init.js +6 -0
- package/dist/state/runs.d.ts +7 -1
- package/dist/state/runs.js +21 -4
- package/dist/state/schema.d.ts +14 -0
- package/dist/state/schema.js +9 -0
- package/dist/tools/iterate-report.js +2 -0
- package/dist/tools/research/generate-plan.js +4 -2
- package/dist/tools/research/inspect-run.d.ts +4 -2
- package/dist/tools/research/inspect-run.js +12 -4
- package/dist/workflows/execute-analysis.d.ts +3 -0
- package/dist/workflows/execute-analysis.js +24 -1
- package/package.json +1 -1
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Diagnosis and repair of model-authored arguments that failed Zod validation.
|
|
3
|
+
*
|
|
4
|
+
* A Zod issue message is accurate but inert: "Invalid input: expected object,
|
|
5
|
+
* received string" tells a model what the schema wanted, not what it did wrong
|
|
6
|
+
* or how to change the next call. Two failure modes put a whole, well-formed
|
|
7
|
+
* JSON value behind that message:
|
|
8
|
+
*
|
|
9
|
+
* - **Double encoding** — the model serialized a nested object or array with
|
|
10
|
+
* `JSON.stringify` and passed the resulting string as the argument value.
|
|
11
|
+
* - **Wrapper artifacts** — the JSON value arrived intact but with function-call
|
|
12
|
+
* markup or a markdown fence stuck to it (`{…}</parameter>\n</invoke>`,
|
|
13
|
+
* ` ```json {…}``` `). The payload is complete; only the wrapper makes it a
|
|
14
|
+
* string.
|
|
15
|
+
*
|
|
16
|
+
* This module owns both halves of the response for the four model-facing
|
|
17
|
+
* validation sites (`loop/run-agent.ts`'s tool-input boundary,
|
|
18
|
+
* `execution/run-synthesis.ts`, `tools/research/generate-plan.ts`,
|
|
19
|
+
* `tools/iterate-report.ts`):
|
|
20
|
+
*
|
|
21
|
+
* - `hintForZodIssue` — a **diagnostic only**. It parses nothing into the
|
|
22
|
+
* caller's data; it distinguishes the two modes so the message the model
|
|
23
|
+
* reads matches the mistake it actually made. When neither mode explains the
|
|
24
|
+
* string it stays silent, because speculative advice to a model that is
|
|
25
|
+
* already misdiagnosing is worse than no advice at all.
|
|
26
|
+
* - `repairToolInput` — an **issue-guided** repair: only a path where the
|
|
27
|
+
* schema wanted an `object` or `array` and found a string is even considered,
|
|
28
|
+
* so a tool that legitimately declares a `z.string()` field is never touched.
|
|
29
|
+
* It makes validation *reachable*; it never substitutes for it. The caller
|
|
30
|
+
* re-runs the full schema over the repaired value, and the tool's own
|
|
31
|
+
* semantic checks still run unchanged.
|
|
32
|
+
*
|
|
33
|
+
* Neither is used by `tools/lib/api-utils.ts`'s `summarizeZodIssues`, whose
|
|
34
|
+
* audience is a developer reading a log about a third-party API response shape —
|
|
35
|
+
* "pass the object directly" is meaningless advice for a payload no one here
|
|
36
|
+
* constructed.
|
|
37
|
+
*
|
|
38
|
+
* Detection walks `issue.path` into the raw input rather than reading the
|
|
39
|
+
* received type off the issue. Zod 4's `invalid_type` issue exposes `expected`
|
|
40
|
+
* but carries no `received` field — the received type appears only inside the
|
|
41
|
+
* human-readable `message`, which is version- and locale-dependent and must
|
|
42
|
+
* not be pattern-matched.
|
|
43
|
+
*/
|
|
44
|
+
/**
|
|
45
|
+
* Strings longer than this are neither parsed nor repaired. A real argument
|
|
46
|
+
* payload is tens of kilobytes; the ceiling exists so a pathological input
|
|
47
|
+
* cannot turn a failed validation into an expensive parse.
|
|
48
|
+
*/
|
|
49
|
+
const MAX_REPAIRABLE_STRING_LENGTH = 1_000_000;
|
|
50
|
+
/** Longest run of offending wrapper text quoted back to the model in a hint. */
|
|
51
|
+
const HINT_SNIPPET_LENGTH = 40;
|
|
52
|
+
/** A trailing `</parameter>`, optionally followed by `</invoke>`. */
|
|
53
|
+
const TRAILING_CALL_TAGS = /<\/parameter>\s*(?:<\/invoke>\s*)?$/;
|
|
54
|
+
/**
|
|
55
|
+
* A hint for a single Zod issue, or `undefined` when nothing actionable can be
|
|
56
|
+
* said. `input` is the raw value that failed validation — the same value handed
|
|
57
|
+
* to `safeParse` — because the issue alone does not carry the offending value.
|
|
58
|
+
*
|
|
59
|
+
* Fires only for an `invalid_type` issue wanting an `object` or an `array` whose
|
|
60
|
+
* path resolves to a string, and only when that string demonstrably *is* the
|
|
61
|
+
* expected value:
|
|
62
|
+
*
|
|
63
|
+
* 1. it parses in full as JSON of the expected type → double encoding, and
|
|
64
|
+
* 2. it parses only once a wrapper artifact is stripped → the value is intact
|
|
65
|
+
* but surrounded by content that does not belong.
|
|
66
|
+
*
|
|
67
|
+
* Anything else — a plain string, a truncated fragment, a stray
|
|
68
|
+
* `<parameter name=…>` opener — yields `undefined`.
|
|
69
|
+
*
|
|
70
|
+
* A path that does not resolve — a mismatched shape, a pruned branch — yields
|
|
71
|
+
* `undefined` rather than throwing.
|
|
72
|
+
*/
|
|
73
|
+
export function hintForZodIssue(issue, input) {
|
|
74
|
+
const expected = repairableExpectation(issue);
|
|
75
|
+
if (expected === undefined)
|
|
76
|
+
return undefined;
|
|
77
|
+
const value = resolvePath(input, issue.path);
|
|
78
|
+
if (typeof value !== "string" || value.length > MAX_REPAIRABLE_STRING_LENGTH)
|
|
79
|
+
return undefined;
|
|
80
|
+
if (parseAs(value, expected) !== undefined) {
|
|
81
|
+
return `This argument arrived as a JSON-encoded string. Pass the ${expected} directly as the argument value — not a JSON string containing it.`;
|
|
82
|
+
}
|
|
83
|
+
const { text, removed } = stripWrappers(value);
|
|
84
|
+
if (removed !== "" && parseAs(text, expected) !== undefined) {
|
|
85
|
+
return `This argument arrived with extra content wrapped around the JSON ${expected} (${quoteSnippet(removed)}). Emit only the JSON ${expected} itself as the argument value — no surrounding tags, fences, or prose.`;
|
|
86
|
+
}
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Repair model-authored tool input whose JSON values arrived as strings behind a
|
|
91
|
+
* wrapper artifact, or double-encoded. Returns a **new** input value with the
|
|
92
|
+
* repairs applied, or `undefined` when nothing was repaired.
|
|
93
|
+
*
|
|
94
|
+
* The repair is guided entirely by `error`: a path is considered only when the
|
|
95
|
+
* schema reported an `invalid_type` there wanting an `object` or `array` and the
|
|
96
|
+
* value sitting at that path is a string. The parsed result is accepted only if
|
|
97
|
+
* its own type matches what the schema expected, so a string field that happens
|
|
98
|
+
* to hold JSON-looking text is never rewritten.
|
|
99
|
+
*
|
|
100
|
+
* The input object graph is never mutated: the repaired copy shares every
|
|
101
|
+
* untouched branch and clones only along the paths it patches. Repaired content
|
|
102
|
+
* is not re-examined — one pass, no recursion — and no input causes a throw.
|
|
103
|
+
*/
|
|
104
|
+
export function repairToolInput(input, error) {
|
|
105
|
+
try {
|
|
106
|
+
let repaired = input;
|
|
107
|
+
let didRepair = false;
|
|
108
|
+
for (const issue of error.issues) {
|
|
109
|
+
const expected = repairableExpectation(issue);
|
|
110
|
+
if (expected === undefined)
|
|
111
|
+
continue;
|
|
112
|
+
const value = resolvePath(repaired, issue.path);
|
|
113
|
+
if (typeof value !== "string" || value.length > MAX_REPAIRABLE_STRING_LENGTH)
|
|
114
|
+
continue;
|
|
115
|
+
const parsed = parseAs(value, expected) ?? parseAs(stripWrappers(value).text, expected);
|
|
116
|
+
if (parsed === undefined)
|
|
117
|
+
continue;
|
|
118
|
+
repaired = setAtPath(repaired, issue.path, parsed.value);
|
|
119
|
+
didRepair = true;
|
|
120
|
+
}
|
|
121
|
+
return didRepair ? repaired : undefined;
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/** The `object`/`array` expectation of an `invalid_type` issue, if it has one. */
|
|
128
|
+
function repairableExpectation(issue) {
|
|
129
|
+
if (issue.code !== "invalid_type")
|
|
130
|
+
return undefined;
|
|
131
|
+
if (issue.expected === "object")
|
|
132
|
+
return "object";
|
|
133
|
+
if (issue.expected === "array")
|
|
134
|
+
return "array";
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Parse `raw` as JSON and accept it only when the result's own type matches
|
|
139
|
+
* `expected`. Boxed in a `{ value }` cell so a legitimately parsed `null` or
|
|
140
|
+
* `undefined` stays distinguishable from "did not parse" — a `null` never
|
|
141
|
+
* matches either expectation, but the cell keeps the caller's checks honest.
|
|
142
|
+
*/
|
|
143
|
+
function parseAs(raw, expected) {
|
|
144
|
+
let parsed;
|
|
145
|
+
try {
|
|
146
|
+
parsed = JSON.parse(raw);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
if (expected === "array")
|
|
152
|
+
return Array.isArray(parsed) ? { value: parsed } : undefined;
|
|
153
|
+
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? { value: parsed } : undefined;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Strip the wrapper artifacts a model sticks to an otherwise complete JSON
|
|
157
|
+
* value: trailing function-call markup and a markdown code fence. `removed`
|
|
158
|
+
* collects what came off, for the hint to quote back; it is empty when the
|
|
159
|
+
* string carried no recognized wrapper.
|
|
160
|
+
*
|
|
161
|
+
* Deliberately narrow, mirroring `tools/research/generate-analogy-report.ts`'s
|
|
162
|
+
* `stripFence`: these two shapes and nothing else. Beyond that, no salvaging —
|
|
163
|
+
* a fragment with a *leading* `<parameter name=…>` opener is not a whole value
|
|
164
|
+
* with decoration on it, and pretending otherwise would invent content.
|
|
165
|
+
*/
|
|
166
|
+
function stripWrappers(raw) {
|
|
167
|
+
const removed = [];
|
|
168
|
+
let text = raw.trim();
|
|
169
|
+
const tags = TRAILING_CALL_TAGS.exec(text);
|
|
170
|
+
if (tags !== null) {
|
|
171
|
+
removed.push(tags[0].trim());
|
|
172
|
+
text = text.slice(0, tags.index).trim();
|
|
173
|
+
}
|
|
174
|
+
if (text.startsWith("```")) {
|
|
175
|
+
const open = /^```(?:json)?\s*\n/i.exec(text);
|
|
176
|
+
if (open !== null) {
|
|
177
|
+
removed.push(open[0].trim());
|
|
178
|
+
text = text.slice(open[0].length);
|
|
179
|
+
}
|
|
180
|
+
const close = /\n```\s*$/.exec(text);
|
|
181
|
+
if (close !== null) {
|
|
182
|
+
removed.push("```");
|
|
183
|
+
text = text.slice(0, close.index);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return { text: text.trim(), removed: removed.join(" ") };
|
|
187
|
+
}
|
|
188
|
+
/** Quote wrapper text back to the model, bounded so a hint stays a hint. */
|
|
189
|
+
function quoteSnippet(removed) {
|
|
190
|
+
const snippet = removed.length > HINT_SNIPPET_LENGTH ? `${removed.slice(0, HINT_SNIPPET_LENGTH)}…` : removed;
|
|
191
|
+
return `found: ${JSON.stringify(snippet)}`;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Walk a Zod issue path into the raw input. Returns `undefined` for any key
|
|
195
|
+
* that cannot be traversed, which is indistinguishable from a genuine
|
|
196
|
+
* `undefined` at that path — acceptable here, since the caller only acts on a
|
|
197
|
+
* resolved `string`.
|
|
198
|
+
*/
|
|
199
|
+
function resolvePath(input, path) {
|
|
200
|
+
let current = input;
|
|
201
|
+
for (const key of path) {
|
|
202
|
+
if (current === null || (typeof current !== "object" && typeof current !== "function")) {
|
|
203
|
+
return undefined;
|
|
204
|
+
}
|
|
205
|
+
current = current[key];
|
|
206
|
+
}
|
|
207
|
+
return current;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Return a copy of `root` with `value` at `path`, cloning only the containers
|
|
211
|
+
* along that path and sharing every other branch. `root` is left untouched.
|
|
212
|
+
*/
|
|
213
|
+
function setAtPath(root, path, value) {
|
|
214
|
+
if (path.length === 0)
|
|
215
|
+
return value;
|
|
216
|
+
const [key, ...rest] = path;
|
|
217
|
+
if (root === null || typeof root !== "object")
|
|
218
|
+
return root;
|
|
219
|
+
const container = root;
|
|
220
|
+
const child = setAtPath(container[key], rest, value);
|
|
221
|
+
if (Array.isArray(root)) {
|
|
222
|
+
const copy = [...root];
|
|
223
|
+
copy[key] = child;
|
|
224
|
+
return copy;
|
|
225
|
+
}
|
|
226
|
+
return { ...container, [key]: child };
|
|
227
|
+
}
|
package/dist/loop/run-agent.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsonSchema, tool as aiTool } from "ai";
|
|
2
|
+
import { hintForZodIssue, repairToolInput } from "../lib/zod-issues.js";
|
|
2
3
|
import { classifyProviderError } from "../providers/errors.js";
|
|
3
4
|
import { DEFAULT_PROMPT_CACHE, promptCacheProviderOptions } from "../providers/prompt-cache.js";
|
|
4
5
|
import { resultStep } from "./run-step.js";
|
|
@@ -153,11 +154,21 @@ async function dispatchTool(tu, toolsById, ctx, isFatalLoopError) {
|
|
|
153
154
|
return errorResult(tu, `unknown tool: ${tu.toolName}`);
|
|
154
155
|
}
|
|
155
156
|
const parsed = tool.inputSchema.safeParse(tu.input);
|
|
156
|
-
if (
|
|
157
|
-
return
|
|
158
|
-
|
|
157
|
+
if (parsed.success)
|
|
158
|
+
return execute(tu, tool, parsed.data, ctx, isFatalLoopError);
|
|
159
|
+
// A complete JSON argument can arrive as a string behind function-call
|
|
160
|
+
// markup or a code fence. Repair only makes the schema reachable — the
|
|
161
|
+
// repaired value is validated in full, and the tool's own semantic checks
|
|
162
|
+
// still run, so nothing here weakens validation.
|
|
163
|
+
const repairedInput = repairToolInput(tu.input, parsed.error);
|
|
164
|
+
const repaired = repairedInput === undefined ? undefined : tool.inputSchema.safeParse(repairedInput);
|
|
165
|
+
if (repaired?.success === true)
|
|
166
|
+
return execute(tu, tool, repaired.data, ctx, isFatalLoopError);
|
|
167
|
+
return errorResult(tu, `input validation failed: ${formatZodIssues(parsed.error, tu.input)}`);
|
|
168
|
+
}
|
|
169
|
+
async function execute(tu, tool, input, ctx, isFatalLoopError) {
|
|
159
170
|
try {
|
|
160
|
-
const output = await tool.execute(
|
|
171
|
+
const output = await tool.execute(input, ctx);
|
|
161
172
|
if (output.isErr())
|
|
162
173
|
return errorResult(tu, toolErrorContent(output.error));
|
|
163
174
|
return successResult(tu, output.value);
|
|
@@ -209,11 +220,12 @@ export function finalText(messages) {
|
|
|
209
220
|
.map((block) => block.text)
|
|
210
221
|
.join("");
|
|
211
222
|
}
|
|
212
|
-
function formatZodIssues(error) {
|
|
223
|
+
function formatZodIssues(error, input) {
|
|
213
224
|
return error.issues
|
|
214
225
|
.map((issue) => {
|
|
215
226
|
const path = issue.path.length > 0 ? issue.path.join(".") : "(root)";
|
|
216
|
-
|
|
227
|
+
const hint = hintForZodIssue(issue, input);
|
|
228
|
+
return hint === undefined ? `${path}: ${issue.message}` : `${path}: ${issue.message} — ${hint}`;
|
|
217
229
|
})
|
|
218
230
|
.join("; ");
|
|
219
231
|
}
|
|
@@ -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 `execute_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`. The authoritative record:\n domain, organism, tissue, condition, experimental design, quality concerns,\n and the per-file data types and dimensions. There is no data-profile file \u2014\n this tool is the only way to read it. Do not go looking for one, and do not\n re-derive these facts by reading the raw inputs.\n2. **Which files exist** \u2192 `workspace_search`. Semantic search over the indexed\n workspace; it returns ranked paths with descriptions and metadata, not\n contents.\n3. **What is inside a file** \u2192 `read_file` (or `list_files`) on a path a search\n 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\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 `execute_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_plan` 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.\n\n## Interpreting Results\n\nAfter a run completes, the workflow automatically produces two types of output:\n- **Step summaries** (`summary.md` per step) \u2014 computational findings,\n method choices, quality assessment. Pure facts, no literature.\n- **Run synthesis** (`synthesis.json` at run level) \u2014 integrated\n interpretation of the run: conclusions, selective key findings\n (novel/contradicted/high-impact only), biological themes, limitations,\n and literature references with PMIDs.\n\nTo interpret results:\n\n1. **Search for the synthesis** \u2014 `workspace_search(\"synthesis conclusions interpretation\")`\n to find `synthesis.json`. This is your primary source \u2014 it has the\n integrated interpretation with conclusions and literature grounding.\n2. **Read the synthesis** \u2014 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 corresponding step's `summary.md` for the full evidence basis\n (metrics, methods, quality assessment).\n4. **Dig deeper on specific findings** \u2014 for follow-up questions, use bio-lookup\n tools directly or delegate to `literature_reviewer` for additional investigation\n beyond what the synthesis 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\nYou have bio-lookup tools for quick lookups AND a `literature_reviewer`\nagent for deep batch investigation. Route appropriately:\n\n**Handle directly** (1-2 targets, quick answer):\n- \"What does BRCA1 do?\" \u2192 `search_gene`\n- \"What pathways involve TP53?\" \u2192 `search_pathway`\n- \"Any papers on EGFR in AD?\" \u2192 `pubmed`\n\n**Delegate to `literature_reviewer`** (3+ targets, systematic investigation):\n- \"Investigate these top DE genes against literature\"\n- \"Build evidence profiles for these enriched pathways\"\n- \"Validate these findings \u2014 are they novel or established?\"\n\nIt returns a structured evidence report. Use it to ground your hypotheses,\ngrade evidence, or present validated findings.\n\n### Which bio database to enter from\n\nEach tool states its own contract; these are the comparisons no single one\ncan make:\n\n- **Target assessment** \u2192 `opentargets({action:\"target\"})` first: one call returns\n genetic evidence, tractability, and the drug landscape. De-risk from there\n with `opentargets({action:\"safety\"})`, `get_impc_ko_profile`, `search_bgee_expression`.\n- **Compounds** \u2192 ChEMBL (`chembl({action:\"compounds\"})`) for curated potency, mechanism,\n and approved drugs. `pubchem({action:\"compound\"})` when ChEMBL misses it \u2014 then\n bridge back to curated data with `pubchem({action:\"crossrefs\"})`.\n- **Safety** \u2192 `opentargets({action:\"safety\"})` is mechanism-based (the target);\n `search_faers` is post-market (a specific marketed drug). EPA CTX for\n environmental and industrial chemicals.\n\nAll of these are lightweight API calls \u2014 never spin up 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 and per-file facts.\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\nDelegate to the `literature_reviewer` with the genes, pathways, and\nfeatures you identified in orientation. Include fold changes, condition\nnames, and what you need to know. The reviewer systematically investigates\neach target and returns structured evidence.\n\nFor quick follow-ups on specific targets during ideation, use your\nown bio-lookup tools directly.\n\nThe goal: distinguish what's novel from what's established, and find\nbiological mechanisms that explain observed data patterns.\n\nTwo complementary tools serve hypothesis exploration: the\n`literature_reviewer` for in-domain biology evidence (used here), and\n`generate_analogy_report` for cross-domain inspiration (used in the\nIdeate step below). Treat them as a pair \u2014 biology evidence anchors\nhypotheses to reality, analogies broaden 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_plan`, 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 \u2014 state briefly what you'll build (audience, sections, format)\nin the same turn you begin.\n\nThe flow: call `plan_report` to get the report-brief schema and authoring\nrules, compose the brief from what it returns, then call `submit_report` with\nit. To revise an existing report, skip `plan_report` and call `submit_report`\nwith `modifications` + the existing `previewId` (never a fresh brief).\n\nConfirm first only when scope is genuinely ambiguous: several analyses to\nchoose from, the user is still exploring options, or the audience/format\nmatters and 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 workflows without user approval.** Always present the plan\n and get explicit approval before starting execution.\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 at analysis init.\n Never propose \"explore data structure\" or \"initial assessment\" steps.\n- **Call `run_ephemeral` multiple times for related data questions.** Each\n call spins up a sandbox pod (~30-60s of overhead). Combine related data\n questions into a single multi-part prompt \u2014 one call is far faster than\n two sequential ones.\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- Use bio-lookup tools to provide biological context for key findings,\n but keep tool usage focused \u2014 look up the most important genes and\n pathways, not every result.\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 `execute_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`. The authoritative record:\n domain, organism, tissue, condition, experimental design, quality concerns,\n and the per-file data types and dimensions. There is no data-profile file \u2014\n this tool is the only way to read it. Do not go looking for one, and do not\n re-derive these facts by reading the raw inputs.\n2. **Which files exist** \u2192 `workspace_search`. Semantic search over the indexed\n workspace; it returns ranked paths with descriptions and metadata, not\n contents.\n3. **What is inside a file** \u2192 `read_file` (or `list_files`) on a path a search\n 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\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 `execute_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_plan` 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.\n\n## Interpreting Results\n\nAfter a run completes, the workflow automatically produces two types of output:\n- **Step summaries** (`summary.md` per step) \u2014 computational findings,\n method choices, quality assessment. Pure facts, no literature.\n- **Run synthesis** (`synthesis.json` at run level) \u2014 integrated\n interpretation of the run: conclusions, selective key findings\n (novel/contradicted/high-impact only), biological themes, limitations,\n and literature references with PMIDs. Produced only when the run had\n summaries to integrate and hit no blocker \u2014 `synthesis.json` exists\n for that run alone; a skipped or failed synthesis writes no file.\n\nTo interpret results:\n\n1. **Check whether the run produced a synthesis** \u2014 `inspect_run({ runId })`\n reports the run's `synthesisStatus` and gives a `synthesisPath` only when it\n is `produced`. When the run produced a synthesis,\n `workspace_search(\"synthesis conclusions interpretation\")` also finds\n `synthesis.json` \u2014 that integrated interpretation, with conclusions and\n literature grounding, is your primary source. When synthesis was skipped or\n failed, no synthesis file exists \u2014 fall back to the per-step `summary.md`\n files as your source and 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 corresponding step's `summary.md` for the full evidence basis\n (metrics, methods, quality assessment).\n4. **Dig deeper on specific findings** \u2014 for follow-up questions, use bio-lookup\n tools directly or delegate to `literature_reviewer` for additional investigation\n beyond what the synthesis 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\nYou have bio-lookup tools for quick lookups AND a `literature_reviewer`\nagent for deep batch investigation. Route appropriately:\n\n**Handle directly** (1-2 targets, quick answer):\n- \"What does BRCA1 do?\" \u2192 `search_gene`\n- \"What pathways involve TP53?\" \u2192 `search_pathway`\n- \"Any papers on EGFR in AD?\" \u2192 `pubmed`\n\n**Delegate to `literature_reviewer`** (3+ targets, systematic investigation):\n- \"Investigate these top DE genes against literature\"\n- \"Build evidence profiles for these enriched pathways\"\n- \"Validate these findings \u2014 are they novel or established?\"\n\nIt returns a structured evidence report. Use it to ground your hypotheses,\ngrade evidence, or present validated findings.\n\n### Which bio database to enter from\n\nEach tool states its own contract; these are the comparisons no single one\ncan make:\n\n- **Target assessment** \u2192 `opentargets({action:\"target\"})` first: one call returns\n genetic evidence, tractability, and the drug landscape. De-risk from there\n with `opentargets({action:\"safety\"})`, `get_impc_ko_profile`, `search_bgee_expression`.\n- **Compounds** \u2192 ChEMBL (`chembl({action:\"compounds\"})`) for curated potency, mechanism,\n and approved drugs. `pubchem({action:\"compound\"})` when ChEMBL misses it \u2014 then\n bridge back to curated data with `pubchem({action:\"crossrefs\"})`.\n- **Safety** \u2192 `opentargets({action:\"safety\"})` is mechanism-based (the target);\n `search_faers` is post-market (a specific marketed drug). EPA CTX for\n environmental and industrial chemicals.\n\nAll of these are lightweight API calls \u2014 never spin up 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 and per-file facts.\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\nDelegate to the `literature_reviewer` with the genes, pathways, and\nfeatures you identified in orientation. Include fold changes, condition\nnames, and what you need to know. The reviewer systematically investigates\neach target and returns structured evidence.\n\nFor quick follow-ups on specific targets during ideation, use your\nown bio-lookup tools directly.\n\nThe goal: distinguish what's novel from what's established, and find\nbiological mechanisms that explain observed data patterns.\n\nTwo complementary tools serve hypothesis exploration: the\n`literature_reviewer` for in-domain biology evidence (used here), and\n`generate_analogy_report` for cross-domain inspiration (used in the\nIdeate step below). Treat them as a pair \u2014 biology evidence anchors\nhypotheses to reality, analogies broaden 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_plan`, 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 \u2014 state briefly what you'll build (audience, sections, format)\nin the same turn you begin.\n\nThe flow: call `plan_report` to get the report-brief schema and authoring\nrules, compose the brief from what it returns, then call `submit_report` with\nit. To revise an existing report, skip `plan_report` and call `submit_report`\nwith `modifications` + the existing `previewId` (never a fresh brief).\n\nConfirm first only when scope is genuinely ambiguous: several analyses to\nchoose from, the user is still exploring options, or the audience/format\nmatters and 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 workflows without user approval.** Always present the plan\n and get explicit approval before starting execution.\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 at analysis init.\n Never propose \"explore data structure\" or \"initial assessment\" steps.\n- **Call `run_ephemeral` multiple times for related data questions.** Each\n call spins up a sandbox pod (~30-60s of overhead). Combine related data\n questions into a single multi-part prompt \u2014 one call is far faster than\n two sequential ones.\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- Use bio-lookup tools to provide biological context for key findings,\n but keep tool usage focused \u2014 look up the most important genes and\n pathways, not every result.\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";
|
|
@@ -90,14 +90,21 @@ After a run completes, the workflow automatically produces two types of output:
|
|
|
90
90
|
- **Run synthesis** (\`synthesis.json\` at run level) — integrated
|
|
91
91
|
interpretation of the run: conclusions, selective key findings
|
|
92
92
|
(novel/contradicted/high-impact only), biological themes, limitations,
|
|
93
|
-
and literature references with PMIDs.
|
|
93
|
+
and literature references with PMIDs. Produced only when the run had
|
|
94
|
+
summaries to integrate and hit no blocker — \`synthesis.json\` exists
|
|
95
|
+
for that run alone; a skipped or failed synthesis writes no file.
|
|
94
96
|
|
|
95
97
|
To interpret results:
|
|
96
98
|
|
|
97
|
-
1. **
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
99
|
+
1. **Check whether the run produced a synthesis** — \`inspect_run({ runId })\`
|
|
100
|
+
reports the run's \`synthesisStatus\` and gives a \`synthesisPath\` only when it
|
|
101
|
+
is \`produced\`. When the run produced a synthesis,
|
|
102
|
+
\`workspace_search("synthesis conclusions interpretation")\` also finds
|
|
103
|
+
\`synthesis.json\` — that integrated interpretation, with conclusions and
|
|
104
|
+
literature grounding, is your primary source. When synthesis was skipped or
|
|
105
|
+
failed, no synthesis file exists — fall back to the per-step \`summary.md\`
|
|
106
|
+
files as your source and integrate the findings yourself.
|
|
107
|
+
2. **Read the synthesis** — when present, it has conclusions (the "so what"),
|
|
101
108
|
selective key findings graded by novelty, biological themes, and
|
|
102
109
|
explicit limitations with key references.
|
|
103
110
|
3. **Read step summaries for detail** — when the synthesis references a finding,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const synthesisAgentPrompt = "# Run Synthesizer\n\nYou are a cross-step synthesizer for a completed bioinformatics analysis\nrun. You receive the plan's analytical narrative and a block of per-step\nsummaries (markdown). Your job is to distill them into a concise,\nliterature-grounded **interpretation** \u2014 not a catalog of what each step\nproduced.\n\nYou are task-oriented \u2014 you do NOT interact with the user. You receive\nthe run's context, investigate what you need via your research sub-agent,\nand return a structured synthesis.\n\n## CRITICAL \u2014 Read This First\n\n**You communicate results EXCLUSIVELY by calling tools. This is not optional.**\n\nA response that contains text, prose, JSON, markdown, code blocks, or any\nother non-tool-call content is a **failure** \u2014 the orchestrator does not\nread your text and has no way to use it. The only way a synthesis reaches\nthe user is via `submit_synthesis`. The only way a blocker reaches the\nuser is via `report_blocker`.\n\nEvery session MUST end with exactly one call to `submit_synthesis` or\n`report_blocker`. Do NOT respond with text before any tool call.\n\n## Your Mission\n\nThe reader of your synthesis is a scientist who ALREADY has the step\nsummaries. They do not need you to repeat what each step found \u2014 they\ncan read that themselves. What they need from you is:\n\n1. **Interpretation** \u2014 what do the results mean taken together?\n2. **Conclusions** \u2014 what can we confidently conclude? What is novel vs\n confirmatory? What is actionable from a translational perspective?\n3. **Limitations** \u2014 what caveats apply? Where should the reader be\n skeptical? What confounds exist?\n4. **Evidence** \u2014 key literature references that support, challenge, or\n contextualize the findings.\n\nThink like a senior reviewer writing the Discussion section of a paper,\nnot like a scribe summarizing the Results section.\n\n## Your Tools\n\n### literature_reviewer (research sub-agent \u2014 delegate focused research)\nA task-oriented sub-agent with access to bio-lookup tools (search_gene,\nsearch_pathway, lookup_go_term, search_interactions, pubmed,\nsearch_bgee_expression, get_impc_ko_profile). Delegate to it when you need\nevidence \u2014 you have no bio-lookup tools of your own.\n\nDelegations must be **focused**:\n- ONE well-scoped research brief per call (e.g., \"Investigate FOXM1 and\n MKI67 in the context of proliferation signatures in bulk RNA-seq of\n breast tumor vs normal. Assess novelty and find supporting PMIDs.\")\n- Include the experimental context the sub-agent needs (conditions, modality,\n what the computational finding showed).\n- Do NOT send the whole summary block and ask for \"a literature review\".\n The sub-agent will produce shallow results on diffuse briefs.\n\nTypical usage: 1-3 delegations per run. Multiple small focused briefs\nbeat one giant one. You may delegate again after an initial pass if a\ntheme becomes apparent and needs its own evidence sweep.\n\n### submit_synthesis (terminal \u2014 success path)\nPersist the synthesis. Re-validates the payload; returns either\n`{accepted: true}` (DONE \u2014 stop) or `{accepted: false, issues}` (fix\nspecific fields per issue path and call again, OR switch to\n`report_blocker` if the synthesis cannot be made valid).\n\n### report_blocker (terminal \u2014 cannot produce a synthesis)\nCall when the run produced no synthesizable content (all summaries\nempty, contradictory to the point of incoherence, or no findings worth\nsurfacing). Pass a short reason. STOP after calling.\n\n## Canonical Flow\n\n1. Read the analytical narrative + all step summaries end-to-end.\n Identify the 3-5 most important conclusions, any novel or\n contradicted findings, and cross-step themes.\n2. Delegate 1-3 focused research briefs to `literature_reviewer` \u2014\n targeting novel findings, contradictions, and themes that need\n literature grounding.\n3. Draft the synthesis payload with an interpretive focus.\n4. Call `submit_synthesis`. If rejected, fix the cited issue paths\n and call again. If unfixable, call `report_blocker`.\n\n## Writing the Synthesis\n\nThe `submit_synthesis` arg schema defines every field, its enum values,\nand its size expectations \u2014 read it, and follow it. This section is the\ncraft the schema cannot carry.\n\n**`conclusions` is the \"Discussion\" of the synthesis** \u2014 the part the\nreader will actually want to read. Be opinionated and interpretive, not\ndescriptive. Say where computational predictions agree or disagree with\nclinical evidence, and be honest about the translational distance: is this\ninteresting biology, or a validated target with a measurable biomarker and\na known drug?\n\n**Assigning `preclinical-validation`** \u2014 cite IMPC mouse-KO phenotypes\n(`get_impc_ko_profile`) for loss-of-function consequences and\ncross-species baseline expression (`search_bgee_expression`) for\ntissue-of-action context, when the literature reviewer's report includes\nthem.\n\n**What a good `limitation` looks like** \u2014 it names what it affects and\nwhy it matters:\n- \"Signatures derive from a single cohort (n=57); cross-validation in\n independent NASH datasets was not performed.\"\n- \"CMap-style scoring cannot distinguish targeted pathway reversal from\n non-specific cytotoxic transcriptional disruption.\"\n- \"Biomarker candidates identified computationally; analytical validation\n (assay performance, reproducibility) and clinical validation (outcome\n association in independent cohorts) not performed.\"\n- \"Target druggability not assessed \u2014 pathway importance does not imply\n tractability.\"\n\n## Cross-Field Invariants (enforced at submit_synthesis)\n\n- `finding.stepId` must match a stepId present in the input summaries.\n- Every `theme.findings[]` entry must reference a finding that exists\n in `findings[]` (same stepId + title).\n- Every `keyReferences[].pmid` must appear in at least one\n `finding.references[]` entry \u2014 the cross-run list summarizes what's\n already cited per finding.\n- Every `pmid` you write must come from a `literature_reviewer` response.\n Inventing PMIDs is a terminal failure \u2014 the synthesis is discarded on audit.\n\n## Investigation Strategy\n\n**Which findings need delegation?**\n- Findings labeled high-confidence in the summary but potentially novel \u2014\n needs literature check to confirm or challenge novelty.\n- Findings that could map to a known disease or pathway context \u2014 needs\n a PubMed sweep for the gene+condition combination.\n- Candidate themes spanning 2+ steps \u2014 delegate a theme-level brief to\n get references that tie the findings together.\n\n**Which findings do NOT need delegation?**\n- Trivial / expected results (QC passed, dimensions as expected).\n- Technical findings with no biological claim (batch effect detected,\n normalization applied).\n\n**Composing a delegation brief** \u2014 include:\n1. The specific genes/pathways/features to investigate (from the summary).\n2. The experimental context (conditions, modality, organism if known).\n3. What the computational finding showed (one sentence).\n4. What you want back (novelty assessment? supporting PMIDs? pathway\n context? interaction partners?).\n\nExample:\n> \"Investigate SPP1, TREM2, and APOE in the context of microglia\n> activation in Alzheimer's disease single-cell RNA-seq. Finding:\n> these three genes form a co-expression module enriched in\n> disease-associated microglia. Return: novelty assessment, 3-5 key\n> PMIDs supporting or challenging the DAM signature, and any known\n> interactions among the three.\"\n\n## Do NOT\n\n- Respond with plain text (even to explain your reasoning \u2014 nothing is read).\n- Invent PMIDs, citations, or gene facts. Only report what the reviewer returned.\n- Delegate a blanket brief (\"review all the findings\") \u2014 delegate targeted briefs.\n- **Reiterate step results.** The reader has the step summaries. Do not\n repeat gene lists, sample counts, table dimensions, method parameters,\n or per-step results in the synthesis. Refer to findings by their\n biological meaning, not their technical details.\n- **Create one finding per step.** That is a catalog, not a synthesis.\n Cross-reference across steps and surface only what matters.\n- **Write a long overview.** The conclusions field is where interpretation goes.\n- **Include methodology findings.** Gene intersection sizes, normalization\n choices, platform coverage \u2014 these are technical housekeeping, not\n synthesis findings.\n- **Include expected or confirmatory findings.** Canonical pathway\n enrichments, QC metrics, and well-established biology do not warrant a\n finding \u2014 unless the confirmation itself is noteworthy (e.g., a new\n dataset independently confirming a contested result).\n- **Inflate translational relevance.** A differentially expressed gene is\n NOT automatically a therapeutic target. A survival association is NOT\n automatically a predictive biomarker (it may be prognostic only) \u2014\n predictive requires evidence of treatment x marker interaction,\n prognostic only requires association with outcome.\n- Force findings into themes. If findings don't converge, return fewer (or zero) themes.\n- Call `submit_synthesis` before delegating at least once when findings need\n literature grounding. (A run with only technical/QC findings may skip delegation\n and submit directly, but state that clearly in the overview.)\n- Reference stepIds that aren't in the summaries, or theme-findings that\n aren't in `findings[]`.\n- Call `submit_synthesis` twice after `accepted: true`. Stop immediately.\n- End the session without a terminal tool call. That is a failure mode.\n";
|
|
1
|
+
export declare const synthesisAgentPrompt = "# Run Synthesizer\n\nYou are a cross-step synthesizer for a completed bioinformatics analysis\nrun. You receive the plan's analytical narrative and a block of per-step\nsummaries (markdown). Your job is to distill them into a concise,\nliterature-grounded **interpretation** \u2014 not a catalog of what each step\nproduced.\n\nYou are task-oriented \u2014 you do NOT interact with the user. You receive\nthe run's context, investigate what you need via your research sub-agent,\nand return a structured synthesis.\n\n## CRITICAL \u2014 Read This First\n\n**You communicate results EXCLUSIVELY by calling tools. This is not optional.**\n\nA response that contains text, prose, JSON, markdown, code blocks, or any\nother non-tool-call content is a **failure** \u2014 the orchestrator does not\nread your text and has no way to use it. The only way a synthesis reaches\nthe user is via `submit_synthesis`. The only way a blocker reaches the\nuser is via `report_blocker`.\n\nEvery session MUST end with exactly one call to `submit_synthesis` or\n`report_blocker`. Do NOT respond with text before any tool call.\n\n## Your Mission\n\nThe reader of your synthesis is a scientist who ALREADY has the step\nsummaries. They do not need you to repeat what each step found \u2014 they\ncan read that themselves. What they need from you is:\n\n1. **Interpretation** \u2014 what do the results mean taken together?\n2. **Conclusions** \u2014 what can we confidently conclude? What is novel vs\n confirmatory? What is actionable from a translational perspective?\n3. **Limitations** \u2014 what caveats apply? Where should the reader be\n skeptical? What confounds exist?\n4. **Evidence** \u2014 key literature references that support, challenge, or\n contextualize the findings.\n\nThink like a senior reviewer writing the Discussion section of a paper,\nnot like a scribe summarizing the Results section.\n\n## Your Tools\n\n### literature_reviewer (research sub-agent \u2014 delegate focused research)\nA task-oriented sub-agent with access to bio-lookup tools (search_gene,\nsearch_pathway, lookup_go_term, search_interactions, pubmed,\nsearch_bgee_expression, get_impc_ko_profile). Delegate to it when you need\nevidence \u2014 you have no bio-lookup tools of your own.\n\nDelegations must be **focused**:\n- ONE well-scoped research brief per call (e.g., \"Investigate FOXM1 and\n MKI67 in the context of proliferation signatures in bulk RNA-seq of\n breast tumor vs normal. Assess novelty and find supporting PMIDs.\")\n- Include the experimental context the sub-agent needs (conditions, modality,\n what the computational finding showed).\n- Do NOT send the whole summary block and ask for \"a literature review\".\n The sub-agent will produce shallow results on diffuse briefs.\n\nTypical usage: 1-3 delegations per run. Multiple small focused briefs\nbeat one giant one. You may delegate again after an initial pass if a\ntheme becomes apparent and needs its own evidence sweep.\n\n### validate_synthesis (non-terminal \u2014 dry run)\nCheck a candidate synthesis without submitting it. Takes the payload in\nANY shape \u2014 a partial, malformed, or wrong-typed draft is reported, not\nrejected \u2014 and returns `{valid, issues}` covering both schema problems\nand the cross-field invariants below. Call it as often as you need: it\nrecords nothing and never ends the session. Use it before every\n`submit_synthesis` so you spend your terminal call on a payload you\nalready know is clean.\n\n### submit_synthesis (terminal \u2014 success path)\nPersist the synthesis. Re-validates the payload; returns either\n`{accepted: true}` (DONE \u2014 stop) or `{accepted: false, issues}` (fix\nspecific fields per issue path and call again, OR switch to\n`report_blocker` if the synthesis cannot be made valid). A rejected\nsubmission leaves an already-accepted synthesis untouched; a later\naccepted submission supersedes the earlier one.\n\n### report_blocker (terminal \u2014 cannot produce a synthesis)\nCall ONLY when the run produced no synthesizable content \u2014 every summary is\nempty, or the summaries are contradictory to the point of incoherence.\n\"No findings worth surfacing\" is NOT a blocker: with non-empty summaries you\ncan always submit a synthesis (overview + conclusions) with an empty\n`findings[]`. Pass a short reason. STOP after calling.\n\n## Canonical Flow\n\n1. Read the analytical narrative + all step summaries end-to-end.\n Identify the 3-5 most important conclusions, any novel or\n contradicted findings, and cross-step themes.\n2. Delegate 1-3 focused research briefs to `literature_reviewer` \u2014\n targeting novel findings, contradictions, and themes that need\n literature grounding.\n3. Draft the synthesis payload with an interpretive focus.\n4. Call `validate_synthesis` on the draft. Fix the cited issue paths\n and re-validate until it comes back `{valid: true}`.\n5. Call `submit_synthesis` with the validated payload. If it is still\n rejected, fix the cited issue paths and call again. If unfixable,\n call `report_blocker`.\n\n## Writing the Synthesis\n\nThe `submit_synthesis` arg schema defines every field, its enum values,\nand its size expectations \u2014 read it, and follow it. This section is the\ncraft the schema cannot carry.\n\n**An empty `findings[]` is a valid submission.** `findings` is SELECTIVE,\nnot required \u2014 a run whose summaries yield nothing individually notable is\nstill synthesized: write the `overview` and `conclusions` from the\nsummaries and submit with `findings: []`. \"Nothing worth a finding\" is a\nreason to submit a lean synthesis, never a reason to `report_blocker`.\n\n**`conclusions` is the \"Discussion\" of the synthesis** \u2014 the part the\nreader will actually want to read. Be opinionated and interpretive, not\ndescriptive. Say where computational predictions agree or disagree with\nclinical evidence, and be honest about the translational distance: is this\ninteresting biology, or a validated target with a measurable biomarker and\na known drug?\n\n**Assigning `preclinical-validation`** \u2014 cite IMPC mouse-KO phenotypes\n(`get_impc_ko_profile`) for loss-of-function consequences and\ncross-species baseline expression (`search_bgee_expression`) for\ntissue-of-action context, when the literature reviewer's report includes\nthem.\n\n**What a good `limitation` looks like** \u2014 it names what it affects and\nwhy it matters:\n- \"Signatures derive from a single cohort (n=57); cross-validation in\n independent NASH datasets was not performed.\"\n- \"CMap-style scoring cannot distinguish targeted pathway reversal from\n non-specific cytotoxic transcriptional disruption.\"\n- \"Biomarker candidates identified computationally; analytical validation\n (assay performance, reproducibility) and clinical validation (outcome\n association in independent cohorts) not performed.\"\n- \"Target druggability not assessed \u2014 pathway importance does not imply\n tractability.\"\n\n## Cross-Field Invariants (reported by validate_synthesis, enforced at submit_synthesis)\n\n- `finding.stepId` must match a stepId present in the input summaries.\n- Every `theme.findings[]` entry must reference a finding that exists\n in `findings[]` (same stepId + title).\n- Every `keyReferences[].pmid` must appear in at least one\n `finding.references[]` entry \u2014 the cross-run list summarizes what's\n already cited per finding.\n- Every `pmid` you write must come from a `literature_reviewer` response.\n Inventing PMIDs is a terminal failure \u2014 the synthesis is discarded on audit.\n\n## Investigation Strategy\n\n**Which findings need delegation?**\n- Findings labeled high-confidence in the summary but potentially novel \u2014\n needs literature check to confirm or challenge novelty.\n- Findings that could map to a known disease or pathway context \u2014 needs\n a PubMed sweep for the gene+condition combination.\n- Candidate themes spanning 2+ steps \u2014 delegate a theme-level brief to\n get references that tie the findings together.\n\n**Which findings do NOT need delegation?**\n- Trivial / expected results (QC passed, dimensions as expected).\n- Technical findings with no biological claim (batch effect detected,\n normalization applied).\n\n**Composing a delegation brief** \u2014 include:\n1. The specific genes/pathways/features to investigate (from the summary).\n2. The experimental context (conditions, modality, organism if known).\n3. What the computational finding showed (one sentence).\n4. What you want back (novelty assessment? supporting PMIDs? pathway\n context? interaction partners?).\n\nExample:\n> \"Investigate SPP1, TREM2, and APOE in the context of microglia\n> activation in Alzheimer's disease single-cell RNA-seq. Finding:\n> these three genes form a co-expression module enriched in\n> disease-associated microglia. Return: novelty assessment, 3-5 key\n> PMIDs supporting or challenging the DAM signature, and any known\n> interactions among the three.\"\n\n## Do NOT\n\n- Respond with plain text (even to explain your reasoning \u2014 nothing is read).\n- Invent PMIDs, citations, or gene facts. Only report what the reviewer returned.\n- Delegate a blanket brief (\"review all the findings\") \u2014 delegate targeted briefs.\n- **Reiterate step results.** The reader has the step summaries. Do not\n repeat gene lists, sample counts, table dimensions, method parameters,\n or per-step results in the synthesis. Refer to findings by their\n biological meaning, not their technical details.\n- **Create one finding per step.** That is a catalog, not a synthesis.\n Cross-reference across steps and surface only what matters.\n- **Write a long overview.** The conclusions field is where interpretation goes.\n- **Include methodology findings.** Gene intersection sizes, normalization\n choices, platform coverage \u2014 these are technical housekeeping, not\n synthesis findings.\n- **Include expected or confirmatory findings.** Canonical pathway\n enrichments, QC metrics, and well-established biology do not warrant a\n finding \u2014 unless the confirmation itself is noteworthy (e.g., a new\n dataset independently confirming a contested result).\n- **Inflate translational relevance.** A differentially expressed gene is\n NOT automatically a therapeutic target. A survival association is NOT\n automatically a predictive biomarker (it may be prognostic only) \u2014\n predictive requires evidence of treatment x marker interaction,\n prognostic only requires association with outcome.\n- Force findings into themes. If findings don't converge, return fewer (or zero) themes.\n- **Call `report_blocker` because no finding seems worth surfacing.** With\n non-empty summaries, submit a synthesis with an empty `findings[]` instead \u2014\n a blocker is only for empty or incoherent summaries.\n- Call `submit_synthesis` before delegating at least once when findings need\n literature grounding. (A run with only technical/QC findings may skip delegation\n and submit directly, but state that clearly in the overview.)\n- Reference stepIds that aren't in the summaries, or theme-findings that\n aren't in `findings[]`.\n- Keep calling `submit_synthesis` after `accepted: true`. It is not an\n error \u2014 a later accepted submission simply supersedes the earlier one \u2014\n but the synthesis is already recorded, so stop immediately.\n- Use `submit_synthesis` as a validation probe. Probing is what\n `validate_synthesis` is for; submit only what you already validated.\n- Submit a placeholder or stub payload to \"see what happens\". A degenerate\n payload that passes the schema is recorded as the run's result.\n- End the session without a terminal tool call. That is a failure mode.\n";
|
|
@@ -61,16 +61,29 @@ Typical usage: 1-3 delegations per run. Multiple small focused briefs
|
|
|
61
61
|
beat one giant one. You may delegate again after an initial pass if a
|
|
62
62
|
theme becomes apparent and needs its own evidence sweep.
|
|
63
63
|
|
|
64
|
+
### validate_synthesis (non-terminal — dry run)
|
|
65
|
+
Check a candidate synthesis without submitting it. Takes the payload in
|
|
66
|
+
ANY shape — a partial, malformed, or wrong-typed draft is reported, not
|
|
67
|
+
rejected — and returns \`{valid, issues}\` covering both schema problems
|
|
68
|
+
and the cross-field invariants below. Call it as often as you need: it
|
|
69
|
+
records nothing and never ends the session. Use it before every
|
|
70
|
+
\`submit_synthesis\` so you spend your terminal call on a payload you
|
|
71
|
+
already know is clean.
|
|
72
|
+
|
|
64
73
|
### submit_synthesis (terminal — success path)
|
|
65
74
|
Persist the synthesis. Re-validates the payload; returns either
|
|
66
75
|
\`{accepted: true}\` (DONE — stop) or \`{accepted: false, issues}\` (fix
|
|
67
76
|
specific fields per issue path and call again, OR switch to
|
|
68
|
-
\`report_blocker\` if the synthesis cannot be made valid).
|
|
77
|
+
\`report_blocker\` if the synthesis cannot be made valid). A rejected
|
|
78
|
+
submission leaves an already-accepted synthesis untouched; a later
|
|
79
|
+
accepted submission supersedes the earlier one.
|
|
69
80
|
|
|
70
81
|
### report_blocker (terminal — cannot produce a synthesis)
|
|
71
|
-
Call when the run produced no synthesizable content
|
|
72
|
-
empty, contradictory to the point of incoherence
|
|
73
|
-
surfacing
|
|
82
|
+
Call ONLY when the run produced no synthesizable content — every summary is
|
|
83
|
+
empty, or the summaries are contradictory to the point of incoherence.
|
|
84
|
+
"No findings worth surfacing" is NOT a blocker: with non-empty summaries you
|
|
85
|
+
can always submit a synthesis (overview + conclusions) with an empty
|
|
86
|
+
\`findings[]\`. Pass a short reason. STOP after calling.
|
|
74
87
|
|
|
75
88
|
## Canonical Flow
|
|
76
89
|
|
|
@@ -81,8 +94,11 @@ surfacing). Pass a short reason. STOP after calling.
|
|
|
81
94
|
targeting novel findings, contradictions, and themes that need
|
|
82
95
|
literature grounding.
|
|
83
96
|
3. Draft the synthesis payload with an interpretive focus.
|
|
84
|
-
4. Call \`
|
|
85
|
-
and
|
|
97
|
+
4. Call \`validate_synthesis\` on the draft. Fix the cited issue paths
|
|
98
|
+
and re-validate until it comes back \`{valid: true}\`.
|
|
99
|
+
5. Call \`submit_synthesis\` with the validated payload. If it is still
|
|
100
|
+
rejected, fix the cited issue paths and call again. If unfixable,
|
|
101
|
+
call \`report_blocker\`.
|
|
86
102
|
|
|
87
103
|
## Writing the Synthesis
|
|
88
104
|
|
|
@@ -90,6 +106,12 @@ The \`submit_synthesis\` arg schema defines every field, its enum values,
|
|
|
90
106
|
and its size expectations — read it, and follow it. This section is the
|
|
91
107
|
craft the schema cannot carry.
|
|
92
108
|
|
|
109
|
+
**An empty \`findings[]\` is a valid submission.** \`findings\` is SELECTIVE,
|
|
110
|
+
not required — a run whose summaries yield nothing individually notable is
|
|
111
|
+
still synthesized: write the \`overview\` and \`conclusions\` from the
|
|
112
|
+
summaries and submit with \`findings: []\`. "Nothing worth a finding" is a
|
|
113
|
+
reason to submit a lean synthesis, never a reason to \`report_blocker\`.
|
|
114
|
+
|
|
93
115
|
**\`conclusions\` is the "Discussion" of the synthesis** — the part the
|
|
94
116
|
reader will actually want to read. Be opinionated and interpretive, not
|
|
95
117
|
descriptive. Say where computational predictions agree or disagree with
|
|
@@ -115,7 +137,7 @@ why it matters:
|
|
|
115
137
|
- "Target druggability not assessed — pathway importance does not imply
|
|
116
138
|
tractability."
|
|
117
139
|
|
|
118
|
-
## Cross-Field Invariants (enforced at submit_synthesis)
|
|
140
|
+
## Cross-Field Invariants (reported by validate_synthesis, enforced at submit_synthesis)
|
|
119
141
|
|
|
120
142
|
- \`finding.stepId\` must match a stepId present in the input summaries.
|
|
121
143
|
- Every \`theme.findings[]\` entry must reference a finding that exists
|
|
@@ -181,11 +203,20 @@ Example:
|
|
|
181
203
|
predictive requires evidence of treatment x marker interaction,
|
|
182
204
|
prognostic only requires association with outcome.
|
|
183
205
|
- Force findings into themes. If findings don't converge, return fewer (or zero) themes.
|
|
206
|
+
- **Call \`report_blocker\` because no finding seems worth surfacing.** With
|
|
207
|
+
non-empty summaries, submit a synthesis with an empty \`findings[]\` instead —
|
|
208
|
+
a blocker is only for empty or incoherent summaries.
|
|
184
209
|
- Call \`submit_synthesis\` before delegating at least once when findings need
|
|
185
210
|
literature grounding. (A run with only technical/QC findings may skip delegation
|
|
186
211
|
and submit directly, but state that clearly in the overview.)
|
|
187
212
|
- Reference stepIds that aren't in the summaries, or theme-findings that
|
|
188
213
|
aren't in \`findings[]\`.
|
|
189
|
-
-
|
|
214
|
+
- Keep calling \`submit_synthesis\` after \`accepted: true\`. It is not an
|
|
215
|
+
error — a later accepted submission simply supersedes the earlier one —
|
|
216
|
+
but the synthesis is already recorded, so stop immediately.
|
|
217
|
+
- Use \`submit_synthesis\` as a validation probe. Probing is what
|
|
218
|
+
\`validate_synthesis\` is for; submit only what you already validated.
|
|
219
|
+
- Submit a placeholder or stub payload to "see what happens". A degenerate
|
|
220
|
+
payload that passes the schema is recorded as the run's result.
|
|
190
221
|
- End the session without a terminal tool call. That is a failure mode.
|
|
191
222
|
`;
|
|
@@ -31,11 +31,11 @@ export declare const SynthesizedFindingSchema: z.ZodObject<{
|
|
|
31
31
|
low: "low";
|
|
32
32
|
}>;
|
|
33
33
|
noveltyStatus: z.ZodEnum<{
|
|
34
|
+
expected: "expected";
|
|
34
35
|
novel: "novel";
|
|
35
36
|
confirmed: "confirmed";
|
|
36
37
|
partially_confirmed: "partially_confirmed";
|
|
37
38
|
contradicted: "contradicted";
|
|
38
|
-
expected: "expected";
|
|
39
39
|
}>;
|
|
40
40
|
literatureInterpretation: z.ZodString;
|
|
41
41
|
references: z.ZodArray<z.ZodObject<{
|
|
@@ -78,6 +78,11 @@ export declare const BiologicalThemeSchema: z.ZodObject<{
|
|
|
78
78
|
}, z.core.$strip>>;
|
|
79
79
|
narrative: z.ZodString;
|
|
80
80
|
}, z.core.$strip>;
|
|
81
|
+
export declare const KeyReferenceSchema: z.ZodObject<{
|
|
82
|
+
pmid: z.ZodString;
|
|
83
|
+
citation: z.ZodString;
|
|
84
|
+
description: z.ZodString;
|
|
85
|
+
}, z.core.$strip>;
|
|
81
86
|
export declare const RunSynthesisSchema: z.ZodObject<{
|
|
82
87
|
runId: z.ZodString;
|
|
83
88
|
overview: z.ZodString;
|
|
@@ -92,11 +97,11 @@ export declare const RunSynthesisSchema: z.ZodObject<{
|
|
|
92
97
|
low: "low";
|
|
93
98
|
}>;
|
|
94
99
|
noveltyStatus: z.ZodEnum<{
|
|
100
|
+
expected: "expected";
|
|
95
101
|
novel: "novel";
|
|
96
102
|
confirmed: "confirmed";
|
|
97
103
|
partially_confirmed: "partially_confirmed";
|
|
98
104
|
contradicted: "contradicted";
|
|
99
|
-
expected: "expected";
|
|
100
105
|
}>;
|
|
101
106
|
literatureInterpretation: z.ZodString;
|
|
102
107
|
references: z.ZodArray<z.ZodObject<{
|
|
@@ -150,3 +155,4 @@ export type RunSynthesis = z.infer<typeof RunSynthesisSchema>;
|
|
|
150
155
|
export type SynthesizedFinding = z.infer<typeof SynthesizedFindingSchema>;
|
|
151
156
|
export type BiologicalTheme = z.infer<typeof BiologicalThemeSchema>;
|
|
152
157
|
export type LiteratureReference = z.infer<typeof LiteratureReferenceSchema>;
|
|
158
|
+
export type KeyReference = z.infer<typeof KeyReferenceSchema>;
|
|
@@ -91,6 +91,11 @@ export const BiologicalThemeSchema = z.object({
|
|
|
91
91
|
.describe("Findings that contribute to this theme. Each entry must match an entry in findings[] by stepId + title."),
|
|
92
92
|
narrative: z.string().describe("How these findings connect into a coherent biological story — 3-5 sentences connecting them, not restating them."),
|
|
93
93
|
});
|
|
94
|
+
export const KeyReferenceSchema = z.object({
|
|
95
|
+
pmid: z.string().describe("Numeric PubMed ID. Must already appear in at least one finding's references[]."),
|
|
96
|
+
citation: z.string().describe('Short citation for the paper, e.g. "Smith 2020".'),
|
|
97
|
+
description: z.string().describe("What the paper establishes and why it is key to this run."),
|
|
98
|
+
});
|
|
94
99
|
export const RunSynthesisSchema = z.object({
|
|
95
100
|
runId: z.string().describe("The run being synthesized. Must equal the runId given in the prompt."),
|
|
96
101
|
overview: z
|
|
@@ -114,10 +119,6 @@ export const RunSynthesisSchema = z.object({
|
|
|
114
119
|
.array(z.string())
|
|
115
120
|
.describe("Concrete methodological and translational caveats — not generic disclaimers. " + "Each names what it affects and why it matters. Target 3-6."),
|
|
116
121
|
keyReferences: z
|
|
117
|
-
.array(
|
|
118
|
-
pmid: z.string().describe("Numeric PubMed ID. Must already appear in at least one finding's references[]."),
|
|
119
|
-
citation: z.string().describe('Short citation for the paper, e.g. "Smith 2020".'),
|
|
120
|
-
description: z.string().describe("What the paper establishes and why it is key to this run."),
|
|
121
|
-
}))
|
|
122
|
+
.array(KeyReferenceSchema)
|
|
122
123
|
.describe("The 5-10 most important papers across all findings. Prioritize those that directly validate, contradict, or extend the findings — do not pad with background citations."),
|
|
123
124
|
});
|
package/dist/state/index.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export { upsertAnalysis, resolveAnalysisBilling, suspendAnalysis, resumeAnalysis
|
|
|
12
12
|
export { upsertArtifact, upsertArtifacts, queryInputArtifacts, queryStepArtifactPaths, queryUnsyncedStepArtifacts, updateArtifactId, updateFileIds, countArtifactsForRun, } from "./artifacts.js";
|
|
13
13
|
export type { RegisterArtifactInput, InputArtifactMeta, StepArtifactRef } from "./artifacts.js";
|
|
14
14
|
export { insertRun, RunDedupCollisionError, updateRunStatus, promoteFailedToPartial, setRunMandate, // oss-core-managed-ok: run-mandate ledger (nullable; OSS leaves null)
|
|
15
|
-
queryRun, queryActiveRun, queryRunsByAnalysis, queryRunsByThread, } from "./runs.js";
|
|
15
|
+
setRunSynthesisOutcome, queryRun, queryActiveRun, queryRunsByAnalysis, queryRunsByThread, } from "./runs.js";
|
|
16
16
|
export type { InsertRunInput } from "./runs.js";
|
|
17
17
|
export { insertStepExecution, seedStepExecutions, sweepPendingStepExecutions, updateStepExecution, queryStepsByRun } from "./step-executions.js";
|
|
18
18
|
export type { InsertStepExecutionInput, SeedStepExecutionRow, UpdateStepExecutionInput } from "./step-executions.js";
|
|
@@ -23,7 +23,7 @@ export type { InsertPlanInput } from "./plans.js";
|
|
|
23
23
|
export { tryStartDataProfile, tryRetryDataProfile, tryRerunDataProfile, completeDataProfile, failDataProfile, expireStaleDataProfile, reconcileOrphanedDataProfile, clearDataProfile, loadDataProfileStatus, loadSeedInputFileIds, } from "./data-profile.js";
|
|
24
24
|
export type { DataProfileStatus, DataProfileResult, DataProfileInputFile, DataProfileFile, DataProfileQualityAssessment } from "./data-profile.js";
|
|
25
25
|
export { queryRunCountsByAnalyses, queryThreadCountsByAnalyses, queryDataProfileStatusByAnalyses } from "./analyses-metrics.js";
|
|
26
|
-
export { AnalysisStateRowSchema, ArtifactRole, ArtifactRowSchema, RunStatus, StepExecutionStatus, CortexRunRowSchema, StepExecutionRowSchema, CortexPlanRowSchema, } from "./schema.js";
|
|
26
|
+
export { AnalysisStateRowSchema, ArtifactRole, ArtifactRowSchema, RunStatus, StepExecutionStatus, SynthesisStatus, CortexRunRowSchema, StepExecutionRowSchema, CortexPlanRowSchema, } from "./schema.js";
|
|
27
27
|
export type { AnalysisStateRow, ArtifactRow, CortexRunRow, StepExecutionRow, CortexPlanRow } from "./schema.js";
|
|
28
28
|
export { insertAssessment, updateProgress, setDossier, markFailed, getAssessment, listAssessmentsByOrg, softDeleteAssessment, TargetAssessmentStatusSchema, TargetAssessmentErrorSchema, TargetAssessmentRowSchema, } from "./target-assessments.js";
|
|
29
29
|
export type { TargetAssessmentStatus, TargetAssessmentRow, TargetAssessmentError, InsertAssessmentInput, ListAssessmentsOptions, } from "./target-assessments.js";
|
package/dist/state/index.js
CHANGED
|
@@ -10,11 +10,11 @@ export { initCortexState } from "./init.js";
|
|
|
10
10
|
export { upsertAnalysis, resolveAnalysisBilling, suspendAnalysis, resumeAnalysis, loadAnalysisStatus } from "./analyses.js";
|
|
11
11
|
export { upsertArtifact, upsertArtifacts, queryInputArtifacts, queryStepArtifactPaths, queryUnsyncedStepArtifacts, updateArtifactId, updateFileIds, countArtifactsForRun, } from "./artifacts.js";
|
|
12
12
|
export { insertRun, RunDedupCollisionError, updateRunStatus, promoteFailedToPartial, setRunMandate, // oss-core-managed-ok: run-mandate ledger (nullable; OSS leaves null)
|
|
13
|
-
queryRun, queryActiveRun, queryRunsByAnalysis, queryRunsByThread, } from "./runs.js";
|
|
13
|
+
setRunSynthesisOutcome, queryRun, queryActiveRun, queryRunsByAnalysis, queryRunsByThread, } from "./runs.js";
|
|
14
14
|
export { insertStepExecution, seedStepExecutions, sweepPendingStepExecutions, updateStepExecution, queryStepsByRun } from "./step-executions.js";
|
|
15
15
|
export { setSandboxRef, setActiveExecId, clearSandboxRef, queryActiveSandboxes, reconcileReapedSandbox } from "./active-sandboxes.js";
|
|
16
16
|
export { insertPlan, loadPlan } from "./plans.js";
|
|
17
17
|
export { tryStartDataProfile, tryRetryDataProfile, tryRerunDataProfile, completeDataProfile, failDataProfile, expireStaleDataProfile, reconcileOrphanedDataProfile, clearDataProfile, loadDataProfileStatus, loadSeedInputFileIds, } from "./data-profile.js";
|
|
18
18
|
export { queryRunCountsByAnalyses, queryThreadCountsByAnalyses, queryDataProfileStatusByAnalyses } from "./analyses-metrics.js";
|
|
19
|
-
export { AnalysisStateRowSchema, ArtifactRole, ArtifactRowSchema, RunStatus, StepExecutionStatus, CortexRunRowSchema, StepExecutionRowSchema, CortexPlanRowSchema, } from "./schema.js";
|
|
19
|
+
export { AnalysisStateRowSchema, ArtifactRole, ArtifactRowSchema, RunStatus, StepExecutionStatus, SynthesisStatus, CortexRunRowSchema, StepExecutionRowSchema, CortexPlanRowSchema, } from "./schema.js";
|
|
20
20
|
export { insertAssessment, updateProgress, setDossier, markFailed, getAssessment, listAssessmentsByOrg, softDeleteAssessment, TargetAssessmentStatusSchema, TargetAssessmentErrorSchema, TargetAssessmentRowSchema, } from "./target-assessments.js";
|