@inflexa-ai/harness 0.41.0 → 0.41.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.
Files changed (72) hide show
  1. package/dist/contracts/report-blocks.d.ts +10 -10
  2. package/dist/contracts/report-reference.d.ts +4 -4
  3. package/dist/memory/message-backfill.d.ts +8 -2
  4. package/dist/prompts/briefing.d.ts +28 -1
  5. package/dist/prompts/briefing.js +44 -1
  6. package/dist/prompts/compaction.d.ts +2 -2
  7. package/dist/prompts/compaction.js +9 -5
  8. package/dist/prompts/conversation.d.ts +1 -1
  9. package/dist/prompts/conversation.js +12 -0
  10. package/dist/prompts/sandbox-standards.d.ts +1 -1
  11. package/dist/prompts/sandbox-standards.js +4 -0
  12. package/dist/report-model/authoring-grammar.d.ts +4 -4
  13. package/dist/runtime/assemble.d.ts +4 -2
  14. package/dist/runtime/assemble.js +8 -1
  15. package/dist/runtime/boot.d.ts +2 -2
  16. package/dist/runtime/boot.js +3 -3
  17. package/dist/schemas/file-metadata.js +9 -9
  18. package/dist/schemas/plan-schemas.d.ts +3 -3
  19. package/dist/state/init.d.ts +7 -6
  20. package/dist/state/init.js +51 -672
  21. package/dist/state/migrations/20260927120000_baseline.d.ts +11 -0
  22. package/dist/state/migrations/20260927120000_baseline.js +675 -0
  23. package/dist/state/migrations/index.d.ts +10 -0
  24. package/dist/state/migrations/index.js +13 -0
  25. package/dist/tools/ad-hoc-router.js +9 -4
  26. package/dist/tools/bio/chembl.js +8 -5
  27. package/dist/tools/bio/comptox.js +4 -4
  28. package/dist/tools/bio/drug-gene-interactions.js +5 -4
  29. package/dist/tools/bio/gene-disease-evidence.js +4 -4
  30. package/dist/tools/bio/lookup-annotation.js +7 -3
  31. package/dist/tools/bio/opentargets.js +8 -6
  32. package/dist/tools/bio/pubchem.js +4 -2
  33. package/dist/tools/bio/pubmed.d.ts +1 -1
  34. package/dist/tools/bio/pubmed.js +10 -6
  35. package/dist/tools/bio/search-clinical-trials.js +2 -1
  36. package/dist/tools/bio/search-gene.js +2 -1
  37. package/dist/tools/bio/search-interactions.js +8 -4
  38. package/dist/tools/bio/target-safety.js +2 -1
  39. package/dist/tools/display/show-user.js +7 -5
  40. package/dist/tools/execute-analysis.js +14 -4
  41. package/dist/tools/lib/opentargets-client.d.ts +8 -3
  42. package/dist/tools/lib/opentargets-client.js +12 -3
  43. package/dist/tools/read-tool-output.js +7 -4
  44. package/dist/tools/report-authoring/authoring-tools.js +22 -15
  45. package/dist/tools/report-session/derive-table.js +5 -1
  46. package/dist/tools/report-session/examine-page.js +5 -4
  47. package/dist/tools/report-session/preview-report.js +6 -1
  48. package/dist/tools/report-session/record-version.js +2 -1
  49. package/dist/tools/research/context7-docs.js +8 -2
  50. package/dist/tools/research/generate-analogy-report.js +15 -15
  51. package/dist/tools/research/generate-plan.js +12 -7
  52. package/dist/tools/research/inspect-data-profile.js +7 -5
  53. package/dist/tools/research/inspect-run.js +6 -2
  54. package/dist/tools/research/literature-reviewer.js +11 -6
  55. package/dist/tools/research/search-arxiv.js +2 -1
  56. package/dist/tools/research/search-github-repos.js +9 -3
  57. package/dist/tools/sandbox/list-available-refs.js +3 -2
  58. package/dist/tools/sandbox/report-blocker.js +6 -1
  59. package/dist/tools/sandbox/skills.js +4 -2
  60. package/dist/tools/start-report-session.js +4 -1
  61. package/dist/tools/workspace/edit-file.js +4 -3
  62. package/dist/tools/workspace/execute-command.js +6 -5
  63. package/dist/tools/workspace/grep.js +9 -7
  64. package/dist/tools/workspace/list-files.js +1 -1
  65. package/dist/tools/workspace/read-file.js +2 -1
  66. package/dist/tools/workspace/scan-inputs.js +3 -1
  67. package/dist/tools/workspace/show-file.js +6 -3
  68. package/dist/tools/workspace/workspace-search.js +3 -1
  69. package/dist/tools/workspace/write-file.js +5 -3
  70. package/dist/workflows/execute-analysis.d.ts +28 -5
  71. package/dist/workflows/execute-analysis.js +34 -10
  72. package/package.json +2 -1
@@ -190,8 +190,8 @@ export declare const ChartAnnotationSchema: z.ZodDiscriminatedUnion<[z.ZodObject
190
190
  kind: z.ZodLiteral<"point-labels">;
191
191
  column: z.ZodString;
192
192
  order: z.ZodEnum<{
193
- desc: "desc";
194
193
  asc: "asc";
194
+ desc: "desc";
195
195
  }>;
196
196
  n: z.ZodNumber;
197
197
  }, z.core.$strict>], "kind">;
@@ -288,8 +288,8 @@ export declare const ChartCompositionSchema: z.ZodObject<{
288
288
  kind: z.ZodLiteral<"point-labels">;
289
289
  column: z.ZodString;
290
290
  order: z.ZodEnum<{
291
- desc: "desc";
292
291
  asc: "asc";
292
+ desc: "desc";
293
293
  }>;
294
294
  n: z.ZodNumber;
295
295
  }, z.core.$strict>], "kind">>>;
@@ -367,8 +367,8 @@ export declare const ClaimBlockSchema: z.ZodObject<{
367
367
  column: z.ZodString;
368
368
  count: z.ZodNumber;
369
369
  order: z.ZodOptional<z.ZodEnum<{
370
- desc: "desc";
371
370
  asc: "asc";
371
+ desc: "desc";
372
372
  }>>;
373
373
  }, z.core.$strict>>;
374
374
  columns: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -519,8 +519,8 @@ export declare const TableBlockSchema: z.ZodObject<{
519
519
  column: z.ZodString;
520
520
  count: z.ZodNumber;
521
521
  order: z.ZodOptional<z.ZodEnum<{
522
- desc: "desc";
523
522
  asc: "asc";
523
+ desc: "desc";
524
524
  }>>;
525
525
  }, z.core.$strict>>;
526
526
  columns: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -561,8 +561,8 @@ export declare const ChartBlockSchema: z.ZodObject<{
561
561
  column: z.ZodString;
562
562
  count: z.ZodNumber;
563
563
  order: z.ZodOptional<z.ZodEnum<{
564
- desc: "desc";
565
564
  asc: "asc";
565
+ desc: "desc";
566
566
  }>>;
567
567
  }, z.core.$strict>>;
568
568
  columns: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -715,8 +715,8 @@ export declare const ChartBlockSchema: z.ZodObject<{
715
715
  kind: z.ZodLiteral<"point-labels">;
716
716
  column: z.ZodString;
717
717
  order: z.ZodEnum<{
718
- desc: "desc";
719
718
  asc: "asc";
719
+ desc: "desc";
720
720
  }>;
721
721
  n: z.ZodNumber;
722
722
  }, z.core.$strict>], "kind">>>;
@@ -826,8 +826,8 @@ export declare const ATOM_BLOCK_SCHEMAS: readonly [z.ZodObject<{
826
826
  column: z.ZodString;
827
827
  count: z.ZodNumber;
828
828
  order: z.ZodOptional<z.ZodEnum<{
829
- desc: "desc";
830
829
  asc: "asc";
830
+ desc: "desc";
831
831
  }>>;
832
832
  }, z.core.$strict>>;
833
833
  columns: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -974,8 +974,8 @@ export declare const ATOM_BLOCK_SCHEMAS: readonly [z.ZodObject<{
974
974
  column: z.ZodString;
975
975
  count: z.ZodNumber;
976
976
  order: z.ZodOptional<z.ZodEnum<{
977
- desc: "desc";
978
977
  asc: "asc";
978
+ desc: "desc";
979
979
  }>>;
980
980
  }, z.core.$strict>>;
981
981
  columns: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -1005,8 +1005,8 @@ export declare const ATOM_BLOCK_SCHEMAS: readonly [z.ZodObject<{
1005
1005
  column: z.ZodString;
1006
1006
  count: z.ZodNumber;
1007
1007
  order: z.ZodOptional<z.ZodEnum<{
1008
- desc: "desc";
1009
1008
  asc: "asc";
1009
+ desc: "desc";
1010
1010
  }>>;
1011
1011
  }, z.core.$strict>>;
1012
1012
  columns: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -1159,8 +1159,8 @@ export declare const ATOM_BLOCK_SCHEMAS: readonly [z.ZodObject<{
1159
1159
  kind: z.ZodLiteral<"point-labels">;
1160
1160
  column: z.ZodString;
1161
1161
  order: z.ZodEnum<{
1162
- desc: "desc";
1163
1162
  asc: "asc";
1163
+ desc: "desc";
1164
1164
  }>;
1165
1165
  n: z.ZodNumber;
1166
1166
  }, z.core.$strict>], "kind">>>;
@@ -59,8 +59,8 @@ export declare const RowBoundSchema: z.ZodObject<{
59
59
  column: z.ZodString;
60
60
  count: z.ZodNumber;
61
61
  order: z.ZodOptional<z.ZodEnum<{
62
- desc: "desc";
63
62
  asc: "asc";
63
+ desc: "desc";
64
64
  }>>;
65
65
  }, z.core.$strict>;
66
66
  /**
@@ -79,8 +79,8 @@ export declare const ArtifactTableReferenceSchema: z.ZodObject<{
79
79
  column: z.ZodString;
80
80
  count: z.ZodNumber;
81
81
  order: z.ZodOptional<z.ZodEnum<{
82
- desc: "desc";
83
82
  asc: "asc";
83
+ desc: "desc";
84
84
  }>>;
85
85
  }, z.core.$strict>>;
86
86
  columns: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -231,8 +231,8 @@ export declare const ReferenceSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
231
231
  column: z.ZodString;
232
232
  count: z.ZodNumber;
233
233
  order: z.ZodOptional<z.ZodEnum<{
234
- desc: "desc";
235
234
  asc: "asc";
235
+ desc: "desc";
236
236
  }>>;
237
237
  }, z.core.$strict>>;
238
238
  columns: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -417,8 +417,8 @@ export declare const UnresolvedReferenceSchema: z.ZodObject<{
417
417
  column: z.ZodString;
418
418
  count: z.ZodNumber;
419
419
  order: z.ZodOptional<z.ZodEnum<{
420
- desc: "desc";
421
420
  asc: "asc";
421
+ desc: "desc";
422
422
  }>>;
423
423
  }, z.core.$strict>>;
424
424
  columns: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -1,2 +1,8 @@
1
- import type { PoolClient } from "pg";
2
- export declare function backfillAiSdkMessageEnvelopes(client: PoolClient): Promise<void>;
1
+ import type { QueryResultRow } from "pg";
2
+ /** The query method of a `pg` client, which the migration transaction also supplies. */
3
+ export interface BackfillClient {
4
+ query<R extends QueryResultRow>(text: string, values?: unknown[]): Promise<{
5
+ rows: R[];
6
+ }>;
7
+ }
8
+ export declare function backfillAiSdkMessageEnvelopes(client: BackfillClient): Promise<void>;
@@ -21,6 +21,7 @@
21
21
  * field so the field-coverage guard test fails when a new field is added to the
22
22
  * schema without a deliberate decision about whether the agent needs to see it.
23
23
  */
24
+ import { type WorkingMemory } from "../memory/working-memory.js";
24
25
  import type { DataProfileResult } from "../state/data-profile.js";
25
26
  import type { AnalysisStep } from "../schemas/workflow-state.js";
26
27
  /** Completed dependencies rendered as upstream blocks; the tail is counted, not rendered. */
@@ -50,6 +51,30 @@ export declare const STEP_NON_TASK_FIELDS: readonly ["id", "track", "step_type",
50
51
  * improvises the rest.
51
52
  */
52
53
  export declare function renderTask(step: AnalysisStep): string;
54
+ /**
55
+ * The heading of the analysis-memory section. The orient core of the sandbox
56
+ * prompt names this same text, thus a test pins the two together.
57
+ */
58
+ export declare const ANALYSIS_MEMORY_HEADING = "Analysis memory (read only)";
59
+ /**
60
+ * The part of the working memory a step agent sees: the goal and the
61
+ * constraints. Hypotheses and findings stay out — they are the interpretive
62
+ * state of the conversation, not a rule for a step.
63
+ */
64
+ export type StepMemory = Pick<WorkingMemory, "goal" | "constraints">;
65
+ /** A memory with no goal and no constraints — the section renders nothing. */
66
+ export declare function emptyStepMemory(): StepMemory;
67
+ /**
68
+ * The goal and the constraints of the analysis, frozen at dispatch. The heading
69
+ * differs from the "Constraints" section of {@link renderTask}: those are the
70
+ * requirements of this plan step, these are the rules of the whole analysis.
71
+ *
72
+ * Bounded to `WORKING_MEMORY_LIMITS` the same way the chat render is: a row
73
+ * written before the caps existed degrades to its newest constraints, and each
74
+ * text is clamped. No id is rendered, because the step agent cannot address an
75
+ * entry — it has no memory tool.
76
+ */
77
+ export declare function renderMemory(memory: StepMemory): string;
53
78
  /**
54
79
  * What anchors every path the agent writes or reads: the id the tree is mounted
55
80
  * at, and the step's writable directory.
@@ -109,10 +134,12 @@ export interface StepBriefing {
109
134
  readonly profile: DataProfileResult | null;
110
135
  /** The step's completed dependencies, in the plan's declared `depends_on` order. */
111
136
  readonly upstream: readonly UpstreamHandoff[];
137
+ /** The goal and the constraints of the analysis, read from the working memory at dispatch. */
138
+ readonly memory: StepMemory;
112
139
  }
113
140
  /**
114
141
  * Compose the seed. Absent sections collapse out entirely — an independent step
115
- * with no profile yields exactly the task and workspace sections, byte-identical
142
+ * with no profile and an empty memory yields exactly the task and workspace sections, byte-identical
116
143
  * to what a step got before any of this existed plus its paths.
117
144
  *
118
145
  * Pure: the same briefing composes to the same string, which is what makes the
@@ -22,6 +22,7 @@
22
22
  * schema without a deliberate decision about whether the agent needs to see it.
23
23
  */
24
24
  import { DATA_PROFILE_ORIENTATION_MAX_CHARS, buildDataProfileOrientation } from "../app/data-profile-orientation.js";
25
+ import { WORKING_MEMORY_LIMITS } from "../memory/working-memory.js";
25
26
  // ── Bounds ────────────────────────────────────────────────────────────
26
27
  /** Completed dependencies rendered as upstream blocks; the tail is counted, not rendered. */
27
28
  export const MAX_UPSTREAM_DEPS = 5;
@@ -113,6 +114,47 @@ export function renderTask(step) {
113
114
  }
114
115
  return parts.join("\n\n");
115
116
  }
117
+ // ── (1b) Analysis memory ──────────────────────────────────────────────
118
+ /**
119
+ * The heading of the analysis-memory section. The orient core of the sandbox
120
+ * prompt names this same text, thus a test pins the two together.
121
+ */
122
+ export const ANALYSIS_MEMORY_HEADING = "Analysis memory (read only)";
123
+ /** A memory with no goal and no constraints — the section renders nothing. */
124
+ export function emptyStepMemory() {
125
+ return { goal: "", constraints: [] };
126
+ }
127
+ /**
128
+ * The goal and the constraints of the analysis, frozen at dispatch. The heading
129
+ * differs from the "Constraints" section of {@link renderTask}: those are the
130
+ * requirements of this plan step, these are the rules of the whole analysis.
131
+ *
132
+ * Bounded to `WORKING_MEMORY_LIMITS` the same way the chat render is: a row
133
+ * written before the caps existed degrades to its newest constraints, and each
134
+ * text is clamped. No id is rendered, because the step agent cannot address an
135
+ * entry — it has no memory tool.
136
+ */
137
+ export function renderMemory(memory) {
138
+ const goal = clamp(memory.goal.trim(), WORKING_MEMORY_LIMITS.goalChars);
139
+ const all = memory.constraints.filter((c) => c.text.trim().length > 0);
140
+ const shown = all.slice(Math.max(0, all.length - WORKING_MEMORY_LIMITS.constraints));
141
+ if (goal.length === 0 && shown.length === 0)
142
+ return "";
143
+ const parts = [
144
+ "These entries come from the working memory of the analysis, read when this step was dispatched. " +
145
+ "A constraint from the user is binding. A constraint from the agent is context.",
146
+ ];
147
+ if (goal.length > 0)
148
+ parts.push(`Goal: ${goal}`);
149
+ if (shown.length > 0) {
150
+ const lines = shown.map((c) => `(${c.origin}) ${clamp(c.text.trim(), WORKING_MEMORY_LIMITS.entryChars)}`);
151
+ const omitted = all.length - shown.length;
152
+ if (omitted > 0)
153
+ lines.push(`(+${omitted} older ${omitted === 1 ? "constraint" : "constraints"} not shown)`);
154
+ parts.push(`Constraints:\n${bullets(lines)}`);
155
+ }
156
+ return section(ANALYSIS_MEMORY_HEADING, parts.join("\n\n"));
157
+ }
116
158
  /** The in-sandbox analysis root of a frame — the read-only mount of the whole tree. */
117
159
  export function analysisRootOf(frame) {
118
160
  return `/${frame.analysisId}`;
@@ -190,7 +232,7 @@ function renderHandoff(handoff) {
190
232
  }
191
233
  /**
192
234
  * Compose the seed. Absent sections collapse out entirely — an independent step
193
- * with no profile yields exactly the task and workspace sections, byte-identical
235
+ * with no profile and an empty memory yields exactly the task and workspace sections, byte-identical
194
236
  * to what a step got before any of this existed plus its paths.
195
237
  *
196
238
  * Pure: the same briefing composes to the same string, which is what makes the
@@ -199,6 +241,7 @@ function renderHandoff(handoff) {
199
241
  export function composeStepBriefing(briefing) {
200
242
  return [
201
243
  renderTask(briefing.step),
244
+ renderMemory(briefing.memory),
202
245
  renderWorkspace(briefing.workspace),
203
246
  renderResources(briefing.step.resources),
204
247
  renderOrientation(briefing.profile, briefing.workspace.analysisId),
@@ -1,4 +1,4 @@
1
1
  /** The compaction request of a thread whose agent maintains the working memory. */
2
- export declare const MEMORY_COMPACTION_REQUEST = "The conversation is too long for the context window. The harness replaces the conversation above with your summary.\n\nFirst, move each lasting fact into the working memory with update_working_memory: the goal, each constraint, each hypothesis, and each finding with its run id. No other tool can run now.\n\nThen reply in plain text with the summary. Leave out what the working memory holds. Give:\n- the goals, and the current request of the user in its exact words\n- the decisions, with their reasons\n- the open questions\n- the file paths and the run ids that the work uses\n- the state of the work in progress\n\nThe conversation continues from your summary, the working memory, and the messages after them.";
2
+ export declare const MEMORY_COMPACTION_REQUEST = "The conversation is too long for the context window. The harness replaces the conversation above with your summary.\n\nFirst, move each lasting fact into the working memory with update_working_memory: the goal, each constraint, each hypothesis, and each finding with its run id. Keep each constraint that the user gave as it is, because the user set it. No other tool can run now. Send all the memory edits in at most 3 replies, and put many calls in one reply. The reply after the edits must be the summary: if it calls a tool, the harness gets no summary.\n\nThen reply in plain text with the summary. Leave out each fact that a memory edit accepted. If the memory refused an edit, put that fact in the summary. Give:\n- the current request of the user in its exact words\n- the decisions, with their reasons\n- the approaches that failed or that the user ruled out, with the reasons\n- the open questions\n- the file paths, the run ids, and the other exact values that the work uses\n- the state of the work in progress\n\nThe conversation continues from your summary, the working memory, and the messages after them.";
3
3
  /** The compaction request of a thread that reads a frozen copy of the working memory, for example a report thread. */
4
- export declare const SUMMARY_COMPACTION_REQUEST = "The conversation is too long for the context window. The harness replaces the conversation above with your summary. No tool can run now.\n\nReply in plain text with the summary. Leave out what the working memory holds. Give:\n- the goals, and the current request of the user in its exact words\n- the decisions, with their reasons\n- the open questions\n- the file paths and the run ids that the work uses\n- the state of the work in progress\n\nThe conversation continues from your summary, the working memory, and the messages after them.";
4
+ export declare const SUMMARY_COMPACTION_REQUEST = "The conversation is too long for the context window. The harness replaces the conversation above with your summary. No tool can run now.\n\nReply in plain text with the summary. Leave out what the working memory holds. Give:\n- the goals, and the current request of the user in its exact words\n- each change that the user made to the brief\n- the decisions, with their reasons\n- the approaches that failed or that the user ruled out, with the reasons\n- the open questions\n- the file paths, the run ids, and the other exact values that the work uses\n- the state of the work in progress\n\nThe conversation continues from your summary, the working memory, and the messages after them.";
@@ -1,13 +1,15 @@
1
+ import { COMPACTION_MAX_REQUESTS } from "../loop/compaction.js";
1
2
  /** The compaction request of a thread whose agent maintains the working memory. */
2
3
  export const MEMORY_COMPACTION_REQUEST = `The conversation is too long for the context window. The harness replaces the conversation above with your summary.
3
4
 
4
- First, move each lasting fact into the working memory with update_working_memory: the goal, each constraint, each hypothesis, and each finding with its run id. No other tool can run now.
5
+ First, move each lasting fact into the working memory with update_working_memory: the goal, each constraint, each hypothesis, and each finding with its run id. Keep each constraint that the user gave as it is, because the user set it. No other tool can run now. Send all the memory edits in at most ${COMPACTION_MAX_REQUESTS - 1} replies, and put many calls in one reply. The reply after the edits must be the summary: if it calls a tool, the harness gets no summary.
5
6
 
6
- Then reply in plain text with the summary. Leave out what the working memory holds. Give:
7
- - the goals, and the current request of the user in its exact words
7
+ Then reply in plain text with the summary. Leave out each fact that a memory edit accepted. If the memory refused an edit, put that fact in the summary. Give:
8
+ - the current request of the user in its exact words
8
9
  - the decisions, with their reasons
10
+ - the approaches that failed or that the user ruled out, with the reasons
9
11
  - the open questions
10
- - the file paths and the run ids that the work uses
12
+ - the file paths, the run ids, and the other exact values that the work uses
11
13
  - the state of the work in progress
12
14
 
13
15
  The conversation continues from your summary, the working memory, and the messages after them.`;
@@ -16,9 +18,11 @@ export const SUMMARY_COMPACTION_REQUEST = `The conversation is too long for the
16
18
 
17
19
  Reply in plain text with the summary. Leave out what the working memory holds. Give:
18
20
  - the goals, and the current request of the user in its exact words
21
+ - each change that the user made to the brief
19
22
  - the decisions, with their reasons
23
+ - the approaches that failed or that the user ruled out, with the reasons
20
24
  - the open questions
21
- - the file paths and the run ids that the work uses
25
+ - the file paths, the run ids, and the other exact values that the work uses
22
26
  - the state of the work in progress
23
27
 
24
28
  The conversation continues from your summary, the working memory, and the messages after them.`;
@@ -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 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.\n6. **Plan stale / invalid?** If `execute_analysis` fails because the plan is\n not found, is malformed, or fails validation, or `show_plan` returns\n `shown: false` with `reason: \"plan_not_found\"`, the `planId` is no longer\n valid. Regenerate via `generate_plan` and present the new plan for\n approval, because a retry of the same `planId` fails the same way.\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. A lookup is\ncheap next to any computation, and each call streams to the user, so they\nwatch the evidence accumulate and can redirect you 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\nEach tool names the corpora it searches, the identifiers it accepts, and\nwhich tool to prefer over it. Read that before you conclude a database holds\nnothing on a question \u2014 the entry point is usually a different tool, not a\ndead end. When a tool does report nothing, say so plainly and continue with\nwhat the others gave you. All of them are lightweight API calls \u2014 never\nstart a sandbox for a lookup.\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\n## Hypothesis Exploration\n\nWhen the user wants to explore hypotheses, the phases below cover the\nevidence that a hypothesis set needs.\n\n### 1. Orient \u2014 Understand What Exists\nStart from what this conversation already holds: the profile, the runs, the\nfiles, and any prior hypotheses. Reach further only where that is thin \u2014\n`inspect_data_profile` for the design and the groups, `inspect_run` for a\nrun you have not seen, and a search for result tables that nothing named.\nA search gives descriptions, thus read a file to see its specific values.\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. Keep only the hypotheses that earn a test.\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 the result of `generate_analogy_report` as a card, so the\ncard is the presentation and your reply is the steering. Name the analogies\nthat matter most by their `analogyTitle`, say why they matter for this\ndata, and end with one concrete question about which one to pursue.\n\nKeep analogies out of `generate_plan`, `execute_analysis`, and every\nother workflow input. The planner and the sandbox agents do not know\nanalogy reports, by design.\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\nStart a report session only when the user asks for a report. Suggest one\nafter a run completes, or when the user asks for a summary or presentation\nof 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 brief.\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- **Interpret from search descriptions.** A `workspace_search` hit gives a\n description, not the values. Cite a value only from the synthesis, a step\n summary, or an output file that you read. If you cannot find the data\n behind a claim, say so.\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- **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- **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 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.\n6. **Plan stale / invalid?** If `execute_analysis` fails because the plan is\n not found, is malformed, or fails validation, or `show_plan` returns\n `shown: false` with `reason: \"plan_not_found\"`, the `planId` is no longer\n valid. Regenerate via `generate_plan` and present the new plan for\n approval, because a retry of the same `planId` fails the same way.\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. A lookup is\ncheap next to any computation, and each call streams to the user, so they\nwatch the evidence accumulate and can redirect you 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\nEach tool names the corpora it searches, the identifiers it accepts, and\nwhich tool to prefer over it. Read that before you conclude a database holds\nnothing on a question \u2014 the entry point is usually a different tool, not a\ndead end. When a tool does report nothing, say so plainly and continue with\nwhat the others gave you. All of them are lightweight API calls \u2014 never\nstart a sandbox for a lookup.\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\n## Hypothesis Exploration\n\nWhen the user wants to explore hypotheses, the phases below cover the\nevidence that a hypothesis set needs.\n\n### 1. Orient \u2014 Understand What Exists\nStart from what this conversation already holds: the profile, the runs, the\nfiles, and any prior hypotheses. Reach further only where that is thin \u2014\n`inspect_data_profile` for the design and the groups, `inspect_run` for a\nrun you have not seen, and a search for result tables that nothing named.\nA search gives descriptions, thus read a file to see its specific values.\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. Keep only the hypotheses that earn a test.\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 the result of `generate_analogy_report` as a card, so the\ncard is the presentation and your reply is the steering. Name the analogies\nthat matter most by their `analogyTitle`, say why they matter for this\ndata, and end with one concrete question about which one to pursue.\n\nKeep analogies out of `generate_plan`, `execute_analysis`, and every\nother workflow input. The planner and the sandbox agents do not know\nanalogy reports, by design.\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\nStart a report session only when the user asks for a report. Suggest one\nafter a run completes, or when the user asks for a summary or presentation\nof 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\nPut each figure next to the prose that discusses it. Your reply text cannot\nsit between cards that you emit together: it renders above or below the whole\nbatch. Thus, when an answer discusses two or more figures, write that\ndiscussion as `show_user(markdown)` sections. Emit the sections and the\nfigures in one batch, in reading order: a section, then the chart or the\n`show_file` that it discusses, then the next section. Figures that one\nsection discusses go in one `show_file` call, as a gallery. Figures for\ndifferent sections go in separate calls. Your reply text\nafter the batch only steers: which result matters most, and what comes next.\nAn answer about one figure needs no sections. Show the figure, then write the\nanswer.\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 brief.\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- **Interpret from search descriptions.** A `workspace_search` hit gives a\n description, not the values. Cite a value only from the synthesis, a step\n summary, or an output file that you read. If you cannot find the data\n behind a claim, say so.\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- **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- **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";
@@ -315,6 +315,18 @@ you are inventing or looked up that exists in no artifact, \`show_plan\` for a
315
315
  stored \`planId\`, \`show_file\` for an existing artifact path. Cards render in
316
316
  the order you emit them.
317
317
 
318
+ Put each figure next to the prose that discusses it. Your reply text cannot
319
+ sit between cards that you emit together: it renders above or below the whole
320
+ batch. Thus, when an answer discusses two or more figures, write that
321
+ discussion as \`show_user(markdown)\` sections. Emit the sections and the
322
+ figures in one batch, in reading order: a section, then the chart or the
323
+ \`show_file\` that it discusses, then the next section. Figures that one
324
+ section discusses go in one \`show_file\` call, as a gallery. Figures for
325
+ different sections go in separate calls. Your reply text
326
+ after the batch only steers: which result matters most, and what comes next.
327
+ An answer about one figure needs no sections. Show the figure, then write the
328
+ answer.
329
+
318
330
  A predicted protein structure is shown by URL: \`show_user(kind: "structure")\`
319
331
  with the \`pdbUrl\` or \`cifUrl\` that \`alphafold_prediction\` returned. A
320
332
  \`.pdb\` or \`.cif\` a step wrote is an existing artifact — \`show_file\`.
@@ -29,7 +29,7 @@
29
29
  * each composition stays byte-stable across its own steps.
30
30
  */
31
31
  import type { ToolchainSource } from "../sandbox/types.js";
32
- export declare const sandboxOrientCorePrompt = "# Sandbox Orient-Core\n\nThese apply to every agent that talks to a sandbox.\n\n## Your Briefing Is Authoritative\n\nYour first message is your briefing, composed for this step at the instant it was\ndispatched \u2014 after every step it depends on had finished. It names your task, your\nworking directory and the read-only analysis root, what the input dataset is\n(domain, organism, design, quality concerns, files), and what each upstream step\nproduced and where it put it. Work from it.\n\nDo NOT re-derive what it already gives you: no filesystem hunt for your inputs, no\nre-reading an upstream step whose summary you were handed, no re-deriving the\norganism or the dimensions from raw bytes. Reach further only where the briefing is\ngenuinely thin for what you must do:\n\n- `inspect_data_profile` \u2014 the full dataset profile: `scope:'overview'` for the\n identity and the file census, `scope:'groups'` for the groups and the dimensions\n that vary across them, `scope:'files'` for the members the profiler annotated\n individually. It is the ONLY record of what the input data IS; no file on disk\n carries it. Use it when the briefing's orientation is not enough, or when it\n carried none.\n- `read_file` on an upstream step's summary \u2014 its path is in your briefing \u2014 when\n the excerpt you were handed is not enough to build on.\n- `workspace_search({ query })` \u2014 natural-language search for a file the briefing\n does not name. Optional `type` filter (`input-group` | `input-dimension` |\n `input` | `output` | `summary` | `synthesis`) and `limit` (1-50, default 8).\n Returns paths, descriptions, and metadata \u2014 not file contents. Most input files\n carry no entry of their own; reach them through their group and `list_files`.\n\n## Your Workspace\n\nYour **working directory** \u2014 writable, and your cwd \u2014 and the read-only\n**analysis root** are named in your briefing. Use those paths exactly; do not\ninvent or guess one.\n\n- **Relative paths resolve against your working directory** \u2014 in `read_file`,\n `list_files`, `file_stat`, `grep`, `write_file`, `edit_file`,\n `execute_command`, and any script you run. `output/de.csv` means\n `<working directory>/output/de.csv` everywhere \u2014 a file you write at a\n relative path is read back at the same relative path.\n- **You may write only inside your working directory.** A write outside it\n comes back as an `out_of_prefix` result (no I/O performed); fix the path.\n- The rest of the analysis is mounted **read-only** at the analysis root. Reach\n input data (under `data/inputs/`) and other steps' outputs (under `runs/`)\n with an **absolute** path beneath it.\n- **Absolute analysis-root paths are canonical** \u2014 use one whenever you\n reference a file you didn't create in this step.\n\n`write_file` creates parent directories as needed \u2014 no separate mkdir.\n\n## Scope \u2014 Analysis Starts at Processed Data\n\nThis environment does **tertiary analysis**: turning processed data into\nbiological findings. Read processing \u2014 alignment, quantification, variant\ncalling, peak calling \u2014 belongs upstream, wherever the instrument output was\nhandled. No tool for it is installed, and none can be.\n\nYour inputs are count and expression matrices, called variants, peak sets,\nsignal tracks, and the metadata describing them. When a request arrives as raw\nreads or unprocessed instrument output, name the processed form you need and\nstop. Do not improvise a pipeline, do not substitute a Python reimplementation\nof an aligner or peak caller, and do not treat a missing upstream tool as an\nobstacle to route around \u2014 it is the boundary of the product, not a gap.\n\nData that has already been processed can still be **read**: `samtools`,\n`bcftools`, `tabix`, `bedtools` and `vcftools` are installed so you can\ninspect, subset, and convert existing BAM/CRAM, VCF and BED. Reading evidence at\nsites you already have is in scope; producing the calls is not.\n\n## Environment \u2014 No Network, No Installs\n\n**There is no egress from this sandbox.** Outbound connections are blocked at the\nfirewall, not merely discouraged: every download, API call, package install, and\n`ExperimentHub`/`AnnotationHub`/`models.download_*` style fetch fails outright.\nThere is no proxy, no mirror, and no retry that succeeds. Packages and reference\ndata were staged onto read-only mounts by the host **before** this container\nstarted, and that is the whole of what exists. Work that genuinely requires the\ninternet \u2014 fetching a dataset, querying an external API, literature lookup \u2014 is\ndone **outside** the sandbox through the host's tools, not from your scripts.\n\nSo: check what you need when you need it \u2014 a targeted lookup, never a catalog dump\nup front.\n\n- **Packages** \u2014 before importing a package you are not certain is present, look it\n up with `list_available_packages`, narrowed to the packages you actually intend\n to import. Importing one that isn't installed fails the script; the lookup costs\n one call.\n- **Reference data** \u2014 `list_available_refs` resolves reference data into a real\n path against the store mounted read-only at `/mnt/refs`. **Search by what the\n data IS, not by where you expect it** (\"TF-target regulons\", \"hallmark gene\n sets\", \"mouse\"): the store's directory layout is an installer detail, it differs\n between hosts, and no path you have seen before is guaranteed to exist here.\n Catalogued entries come back with the organism, the format, and the file's\n internal shape, so read the entry and use the reader and species it names rather\n than assuming \u2014 formats vary per dataset, and there is no guarantee any given\n collection ships in the format your tool prefers.\n The store is OPTIONAL and may be absent or hold none of what you want \u2014 a normal\n state, not an error. Never assume a reference path exists and never hardcode one:\n if a reference you need is not in the inventory, say so plainly and proceed with\n what you do have (or state what must be provisioned) rather than inventing a path\n or substituting something else unannounced. Pass reference paths EXPLICITLY to the\n library \u2014 nothing in the image points a library at the store, so a library that\n resolves data by name from an env var (CellTypist reads `$CELLTYPIST_FOLDER`)\n will not find it unless you set that variable in `execute_command`'s `env`\n for the command that needs it, using a path the inventory actually returned.\n- Do NOT call a library's built-in data fetcher \u2014 `dc.op.collectri()`,\n `dc.op.progeny()`, `dc.op.msigdb()` and every other `dc.op.*()`, CellTypist's\n `models.download_models()`, gseapy's Enrichr library names, `ExperimentHub`/\n `AnnotationHub` \u2014 they all reach for the network and will fail. Load from an\n inventory path instead. Do NOT download data or install packages at runtime.\n\n## CPU Budget and Parallelism\n\nYour briefing names this step's CPU quota and memory limit. The container's\ncore-count calls (`parallel::detectCores()`, `os.cpu_count()`) report the\nquota, so worker counts sized from them are safe.\n\nBLAS/OpenMP thread pools default to **1 thread** (`OMP_NUM_THREADS`,\n`OPENBLAS_NUM_THREADS`, `MKL_NUM_THREADS`, \u2026). A forked worker inherits its\nparent's pool size, so this default is what keeps `mclapply` /\n`multiprocessing` from running workers \u00D7 threads on a quota sized for one of\nthem. The rule: **workers \u00D7 threads-per-worker must not exceed the quota.** A\nmix is fine while the product stays inside it.\n\n- **Fork/worker parallelism** (`mclapply`, `BiocParallel`, `joblib`,\n `multiprocessing`): size the workers from the reported core count and leave\n the thread defaults alone \u2014 there is nothing to raise. `joblib` also\n divides its own thread budget across its workers; do not manage it.\n- **Thread parallelism** (one process: xgboost, WGCNA, data.table, polars, a\n single large BLAS fit): raise the thread variables for that command through\n `execute_command`'s `env`, e.g.\n `env: { \"OMP_NUM_THREADS\": \"4\", \"OPENBLAS_NUM_THREADS\": \"4\" }` on a 4-CPU\n quota.\n\nExceeding the quota does not kill the step \u2014 the cgroup throttles it \u2014 but an\noversubscribed step runs slower and can starve the exec server that runs your\ncommands.\n\n## Context7 \u2014 Documentation Lookup\n\nWhen you are not certain of a function's signature or behavior, look it up\nwith context7 before you write the call: `resolve_library_id` with the\npackage name, then `query_docs` with the library ID and the function.\nRapidly-evolving packages (scvi-tools, spatialdata, cellrank, pertpy, muon)\nchange their APIs between releases, so check them against the docs or the\nskill references rather than memory.\n\n## Command Execution\n\n`execute_command` runs analysis work \u2014 scripts, bioinformatics CLI tools,\nshell pipes, and anything the workspace tools don't express. It starts in your\nworking directory; a relative `cwd` argument is resolved against it.\nIts `command` is an argv array. A one-element array runs through `sh -c`,\nso pipes, `&&`, redirection, quoting, and globbing work there. An array of\ntwo or more elements runs directly, with no shell.\n\nFor these tasks, prefer the dedicated workspace tools \u2014 they are faster than\nshelling out and don't waste a turn on path discovery:\n\n| Task | Use this tool | Instead of |\n|-|-|-|\n| List files in a directory | `list_files` | `ls` / `ls -la` |\n| Read a text/source/result file | `read_file` | `cat` / `less` |\n| Search file contents by pattern | `grep` | shell `grep` / `rg` |\n| Size / type of a path | `file_stat` | `stat` / `wc -c` |\n\n**Use `execute_command` for everything else**, including:\n\n- **Running scripts** \u2014 `python scripts/run.py`, `Rscript scripts/de.R`,\n `ruff check scripts/foo.py`.\n- **Previewing huge / binary files** \u2014 `wc -l file.tsv`, `zcat file.gz | head`.\n For text files, prefer `read_file` with `headLines` / `tailLines`.\n- **Shell pipes / chaining** \u2014 `sort | uniq -c | sort -rn | head`.\n- **`find`** \u2014 a recursive search by name, age, or size: `find . -name '*.h5ad'`,\n `find . -size +10M`. `list_files` lists one directory only.\n- **Real CLI tools** \u2014 `samtools`, `bcftools`, `tabix`, `bedtools`,\n `vcftools`, version probes like `python -c \"import x; print(x.__version__)\"`.\n\n**Shell behavior to know:**\n\n- `cd` does NOT persist across calls \u2014 each exec starts in your working\n directory. For cross-call state, pass `cwd` or chain with `&&` in one exec.\n- Quote paths with spaces: `head \"data/inputs/My Folder/file.csv\"`. In Python,\n use `pathlib.Path` or `os.path`.\n\n## Editing Files\n\n`edit_file` replaces `old_string` with `new_string`. Read the file first to\nget the exact text. When `replace_all` is false (default), `old_string` must\noccur exactly once \u2014 include surrounding context to make it unique.\n";
32
+ export declare const sandboxOrientCorePrompt = "# Sandbox Orient-Core\n\nThese apply to every agent that talks to a sandbox.\n\n## Your Briefing Is Authoritative\n\nYour first message is your briefing, composed for this step at the instant it was\ndispatched \u2014 after every step it depends on had finished. It names your task, your\nworking directory and the read-only analysis root, what the input dataset is\n(domain, organism, design, quality concerns, files), and what each upstream step\nproduced and where it put it. Work from it.\n\nWhen the analysis has a recorded goal or constraints, the briefing carries them\nunder \"Analysis memory (read only)\": obey each constraint from the user, and treat\neach constraint from the agent as context. You cannot change that memory.\n\nDo NOT re-derive what it already gives you: no filesystem hunt for your inputs, no\nre-reading an upstream step whose summary you were handed, no re-deriving the\norganism or the dimensions from raw bytes. Reach further only where the briefing is\ngenuinely thin for what you must do:\n\n- `inspect_data_profile` \u2014 the full dataset profile: `scope:'overview'` for the\n identity and the file census, `scope:'groups'` for the groups and the dimensions\n that vary across them, `scope:'files'` for the members the profiler annotated\n individually. It is the ONLY record of what the input data IS; no file on disk\n carries it. Use it when the briefing's orientation is not enough, or when it\n carried none.\n- `read_file` on an upstream step's summary \u2014 its path is in your briefing \u2014 when\n the excerpt you were handed is not enough to build on.\n- `workspace_search({ query })` \u2014 natural-language search for a file the briefing\n does not name. Optional `type` filter (`input-group` | `input-dimension` |\n `input` | `output` | `summary` | `synthesis`) and `limit` (1-50, default 8).\n Returns paths, descriptions, and metadata \u2014 not file contents. Most input files\n carry no entry of their own; reach them through their group and `list_files`.\n\n## Your Workspace\n\nYour **working directory** \u2014 writable, and your cwd \u2014 and the read-only\n**analysis root** are named in your briefing. Use those paths exactly; do not\ninvent or guess one.\n\n- **Relative paths resolve against your working directory** \u2014 in `read_file`,\n `list_files`, `file_stat`, `grep`, `write_file`, `edit_file`,\n `execute_command`, and any script you run. `output/de.csv` means\n `<working directory>/output/de.csv` everywhere \u2014 a file you write at a\n relative path is read back at the same relative path.\n- **You may write only inside your working directory.** A write outside it\n comes back as an `out_of_prefix` result (no I/O performed); fix the path.\n- The rest of the analysis is mounted **read-only** at the analysis root. Reach\n input data (under `data/inputs/`) and other steps' outputs (under `runs/`)\n with an **absolute** path beneath it.\n- **Absolute analysis-root paths are canonical** \u2014 use one whenever you\n reference a file you didn't create in this step.\n\n`write_file` creates parent directories as needed \u2014 no separate mkdir.\n\n## Scope \u2014 Analysis Starts at Processed Data\n\nThis environment does **tertiary analysis**: turning processed data into\nbiological findings. Read processing \u2014 alignment, quantification, variant\ncalling, peak calling \u2014 belongs upstream, wherever the instrument output was\nhandled. No tool for it is installed, and none can be.\n\nYour inputs are count and expression matrices, called variants, peak sets,\nsignal tracks, and the metadata describing them. When a request arrives as raw\nreads or unprocessed instrument output, name the processed form you need and\nstop. Do not improvise a pipeline, do not substitute a Python reimplementation\nof an aligner or peak caller, and do not treat a missing upstream tool as an\nobstacle to route around \u2014 it is the boundary of the product, not a gap.\n\nData that has already been processed can still be **read**: `samtools`,\n`bcftools`, `tabix`, `bedtools` and `vcftools` are installed so you can\ninspect, subset, and convert existing BAM/CRAM, VCF and BED. Reading evidence at\nsites you already have is in scope; producing the calls is not.\n\n## Environment \u2014 No Network, No Installs\n\n**There is no egress from this sandbox.** Outbound connections are blocked at the\nfirewall, not merely discouraged: every download, API call, package install, and\n`ExperimentHub`/`AnnotationHub`/`models.download_*` style fetch fails outright.\nThere is no proxy, no mirror, and no retry that succeeds. Packages and reference\ndata were staged onto read-only mounts by the host **before** this container\nstarted, and that is the whole of what exists. Work that genuinely requires the\ninternet \u2014 fetching a dataset, querying an external API, literature lookup \u2014 is\ndone **outside** the sandbox through the host's tools, not from your scripts.\n\nSo: check what you need when you need it \u2014 a targeted lookup, never a catalog dump\nup front.\n\n- **Packages** \u2014 before importing a package you are not certain is present, look it\n up with `list_available_packages`, narrowed to the packages you actually intend\n to import. Importing one that isn't installed fails the script; the lookup costs\n one call.\n- **Reference data** \u2014 `list_available_refs` resolves reference data into a real\n path against the store mounted read-only at `/mnt/refs`. **Search by what the\n data IS, not by where you expect it** (\"TF-target regulons\", \"hallmark gene\n sets\", \"mouse\"): the store's directory layout is an installer detail, it differs\n between hosts, and no path you have seen before is guaranteed to exist here.\n Catalogued entries come back with the organism, the format, and the file's\n internal shape, so read the entry and use the reader and species it names rather\n than assuming \u2014 formats vary per dataset, and there is no guarantee any given\n collection ships in the format your tool prefers.\n The store is OPTIONAL and may be absent or hold none of what you want \u2014 a normal\n state, not an error. Never assume a reference path exists and never hardcode one:\n if a reference you need is not in the inventory, say so plainly and proceed with\n what you do have (or state what must be provisioned) rather than inventing a path\n or substituting something else unannounced. Pass reference paths EXPLICITLY to the\n library \u2014 nothing in the image points a library at the store, so a library that\n resolves data by name from an env var (CellTypist reads `$CELLTYPIST_FOLDER`)\n will not find it unless you set that variable in `execute_command`'s `env`\n for the command that needs it, using a path the inventory actually returned.\n- Do NOT call a library's built-in data fetcher \u2014 `dc.op.collectri()`,\n `dc.op.progeny()`, `dc.op.msigdb()` and every other `dc.op.*()`, CellTypist's\n `models.download_models()`, gseapy's Enrichr library names, `ExperimentHub`/\n `AnnotationHub` \u2014 they all reach for the network and will fail. Load from an\n inventory path instead. Do NOT download data or install packages at runtime.\n\n## CPU Budget and Parallelism\n\nYour briefing names this step's CPU quota and memory limit. The container's\ncore-count calls (`parallel::detectCores()`, `os.cpu_count()`) report the\nquota, so worker counts sized from them are safe.\n\nBLAS/OpenMP thread pools default to **1 thread** (`OMP_NUM_THREADS`,\n`OPENBLAS_NUM_THREADS`, `MKL_NUM_THREADS`, \u2026). A forked worker inherits its\nparent's pool size, so this default is what keeps `mclapply` /\n`multiprocessing` from running workers \u00D7 threads on a quota sized for one of\nthem. The rule: **workers \u00D7 threads-per-worker must not exceed the quota.** A\nmix is fine while the product stays inside it.\n\n- **Fork/worker parallelism** (`mclapply`, `BiocParallel`, `joblib`,\n `multiprocessing`): size the workers from the reported core count and leave\n the thread defaults alone \u2014 there is nothing to raise. `joblib` also\n divides its own thread budget across its workers; do not manage it.\n- **Thread parallelism** (one process: xgboost, WGCNA, data.table, polars, a\n single large BLAS fit): raise the thread variables for that command through\n `execute_command`'s `env`, e.g.\n `env: { \"OMP_NUM_THREADS\": \"4\", \"OPENBLAS_NUM_THREADS\": \"4\" }` on a 4-CPU\n quota.\n\nExceeding the quota does not kill the step \u2014 the cgroup throttles it \u2014 but an\noversubscribed step runs slower and can starve the exec server that runs your\ncommands.\n\n## Context7 \u2014 Documentation Lookup\n\nWhen you are not certain of a function's signature or behavior, look it up\nwith context7 before you write the call: `resolve_library_id` with the\npackage name, then `query_docs` with the library ID and the function.\nRapidly-evolving packages (scvi-tools, spatialdata, cellrank, pertpy, muon)\nchange their APIs between releases, so check them against the docs or the\nskill references rather than memory.\n\n## Command Execution\n\n`execute_command` runs analysis work \u2014 scripts, bioinformatics CLI tools,\nshell pipes, and anything the workspace tools don't express. It starts in your\nworking directory; a relative `cwd` argument is resolved against it.\nIts `command` is an argv array. A one-element array runs through `sh -c`,\nso pipes, `&&`, redirection, quoting, and globbing work there. An array of\ntwo or more elements runs directly, with no shell.\n\nFor these tasks, prefer the dedicated workspace tools \u2014 they are faster than\nshelling out and don't waste a turn on path discovery:\n\n| Task | Use this tool | Instead of |\n|-|-|-|\n| List files in a directory | `list_files` | `ls` / `ls -la` |\n| Read a text/source/result file | `read_file` | `cat` / `less` |\n| Search file contents by pattern | `grep` | shell `grep` / `rg` |\n| Size / type of a path | `file_stat` | `stat` / `wc -c` |\n\n**Use `execute_command` for everything else**, including:\n\n- **Running scripts** \u2014 `python scripts/run.py`, `Rscript scripts/de.R`,\n `ruff check scripts/foo.py`.\n- **Previewing huge / binary files** \u2014 `wc -l file.tsv`, `zcat file.gz | head`.\n For text files, prefer `read_file` with `headLines` / `tailLines`.\n- **Shell pipes / chaining** \u2014 `sort | uniq -c | sort -rn | head`.\n- **`find`** \u2014 a recursive search by name, age, or size: `find . -name '*.h5ad'`,\n `find . -size +10M`. `list_files` lists one directory only.\n- **Real CLI tools** \u2014 `samtools`, `bcftools`, `tabix`, `bedtools`,\n `vcftools`, version probes like `python -c \"import x; print(x.__version__)\"`.\n\n**Shell behavior to know:**\n\n- `cd` does NOT persist across calls \u2014 each exec starts in your working\n directory. For cross-call state, pass `cwd` or chain with `&&` in one exec.\n- Quote paths with spaces: `head \"data/inputs/My Folder/file.csv\"`. In Python,\n use `pathlib.Path` or `os.path`.\n\n## Editing Files\n\n`edit_file` replaces `old_string` with `new_string`. Read the file first to\nget the exact text. When `replace_all` is false (default), `old_string` must\noccur exactly once \u2014 include surrounding context to make it unique.\n";
33
33
  /**
34
34
  * The orient-core layer for one declared toolchain. `"image"` states that an
35
35
  * acquisition is a host action and directs the agent to report a missing
@@ -40,6 +40,10 @@ working directory and the read-only analysis root, what the input dataset is
40
40
  (domain, organism, design, quality concerns, files), and what each upstream step
41
41
  produced and where it put it. Work from it.
42
42
 
43
+ When the analysis has a recorded goal or constraints, the briefing carries them
44
+ under "Analysis memory (read only)": obey each constraint from the user, and treat
45
+ each constraint from the agent as context. You cannot change that memory.
46
+
43
47
  Do NOT re-derive what it already gives you: no filesystem hunt for your inputs, no
44
48
  re-reading an upstream step whose summary you were handed, no re-deriving the
45
49
  organism or the dimensions from raw bytes. Reach further only where the briefing is
@@ -65,8 +65,8 @@ declare const AUTHORING_ATOM_SCHEMAS: readonly [z.ZodObject<{
65
65
  column: z.ZodString;
66
66
  count: z.ZodNumber;
67
67
  order: z.ZodOptional<z.ZodEnum<{
68
- desc: "desc";
69
68
  asc: "asc";
69
+ desc: "desc";
70
70
  }>>;
71
71
  }, z.core.$strict>>;
72
72
  columns: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -209,8 +209,8 @@ declare const AUTHORING_ATOM_SCHEMAS: readonly [z.ZodObject<{
209
209
  column: z.ZodString;
210
210
  count: z.ZodNumber;
211
211
  order: z.ZodOptional<z.ZodEnum<{
212
- desc: "desc";
213
212
  asc: "asc";
213
+ desc: "desc";
214
214
  }>>;
215
215
  }, z.core.$strict>>;
216
216
  columns: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -235,8 +235,8 @@ declare const AUTHORING_ATOM_SCHEMAS: readonly [z.ZodObject<{
235
235
  column: z.ZodString;
236
236
  count: z.ZodNumber;
237
237
  order: z.ZodOptional<z.ZodEnum<{
238
- desc: "desc";
239
238
  asc: "asc";
239
+ desc: "desc";
240
240
  }>>;
241
241
  }, z.core.$strict>>;
242
242
  columns: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -389,8 +389,8 @@ declare const AUTHORING_ATOM_SCHEMAS: readonly [z.ZodObject<{
389
389
  kind: z.ZodLiteral<"point-labels">;
390
390
  column: z.ZodString;
391
391
  order: z.ZodEnum<{
392
- desc: "desc";
393
392
  asc: "asc";
393
+ desc: "desc";
394
394
  }>;
395
395
  n: z.ZodNumber;
396
396
  }, z.core.$strict>], "kind">>>;
@@ -50,11 +50,13 @@ export type SandboxStepCallable = (input: SandboxStepInput) => Promise<SandboxSt
50
50
  * conversation agent does not — a half-wired ledger that reads as a complete
51
51
  * one. The protection has to hold on both sides of the assembly, not just the
52
52
  * conversation side. Each bag omits `toolOutputStore` for the same reason: each
53
- * loop must keep its texts in the one store that each read tool reads.
53
+ * loop must keep its texts in the one store that each read tool reads. The
54
+ * `executeAnalysis` bag omits `workingMemory`, because the step seeds must read
55
+ * the same memory that the conversation agent writes.
54
56
  */
55
57
  export interface CoreWorkflowDeps {
56
58
  readonly sandboxStep: Omit<SandboxStepDeps, "usageRecorder" | "citationResolver" | "toolOutputStore">;
57
- readonly buildExecuteAnalysis: (sandboxStep: SandboxStepCallable) => Omit<ExecuteAnalysisDeps, "usageRecorder" | "citationResolver" | "toolOutputStore">;
59
+ readonly buildExecuteAnalysis: (sandboxStep: SandboxStepCallable) => Omit<ExecuteAnalysisDeps, "usageRecorder" | "citationResolver" | "toolOutputStore" | "workingMemory">;
58
60
  readonly dataProfile: Omit<DataProfileDeps, "usageRecorder" | "toolOutputStore">;
59
61
  }
60
62
  /** The registered, callable workflow handles. */
@@ -28,6 +28,7 @@ import { bindExtractionTrigger, createExtractionArm, registerExtractValuesWorkfl
28
28
  import { registerDeriveTableExecWorkflow, triggerDeriveTableExec } from "../tasks/derive-table-exec.js";
29
29
  import { createCitationResolver } from "../citations/resolve.js";
30
30
  import { createThreadStore } from "../memory/thread-store.js";
31
+ import { createWorkingMemory } from "../memory/working-memory.js";
31
32
  import { createArtifactReadStore, createProductionResolver } from "../report-model/production-resolver.js";
32
33
  import { createReportVersionStore } from "../state/report-versions.js";
33
34
  import { createToolOutputStore } from "../state/tool-outputs.js";
@@ -86,7 +87,13 @@ export function assembleCoreRuntime(deps) {
86
87
  });
87
88
  const toolOutputStore = createToolOutputStore(conversation.pool);
88
89
  const sandboxStep = registerSandboxStep({ ...wf.sandboxStep, citationResolver, usageRecorder, toolOutputStore });
89
- const executeAnalysis = registerExecuteAnalysis({ ...wf.buildExecuteAnalysis(sandboxStep), citationResolver, usageRecorder, toolOutputStore });
90
+ const executeAnalysis = registerExecuteAnalysis({
91
+ ...wf.buildExecuteAnalysis(sandboxStep),
92
+ citationResolver,
93
+ usageRecorder,
94
+ toolOutputStore,
95
+ workingMemory: createWorkingMemory(conversation.pool),
96
+ });
90
97
  const dataProfile = registerDataProfileWorkflow({ ...wf.dataProfile, usageRecorder, toolOutputStore });
91
98
  // The extraction workflow shares the profile's sandbox and authorization rails, thus it draws the same
92
99
  // three seams from the profile deps. The report resolver factory binds the extraction arm over this
@@ -14,8 +14,8 @@
14
14
  * 2. `validateAgentSkills(skillsDir, ...)` — pure fs stat, zero external deps.
15
15
  * A `meta.skills` typo or a `skillsDir` / image drift dies in milliseconds,
16
16
  * before any Postgres or DBOS cost is paid (agent-skill-assignment).
17
- * 3. `initCortexState(pool)` — app tables must exist before launch; recovery
18
- * queries them on the first step.
17
+ * 3. `initCortexState(pool, logger)` — applies the pending schema migrations.
18
+ * App tables must exist before launch; recovery queries them on the first step.
19
19
  * 4. `assertConnectionBudget(...)` — needs the live pool; gates launch so a
20
20
  * misconfigured pool fails loudly at boot, not under load.
21
21
  * 5. `assembleCoreRuntime(core)` — registers the durable-workflow cohort and
@@ -14,8 +14,8 @@
14
14
  * 2. `validateAgentSkills(skillsDir, ...)` — pure fs stat, zero external deps.
15
15
  * A `meta.skills` typo or a `skillsDir` / image drift dies in milliseconds,
16
16
  * before any Postgres or DBOS cost is paid (agent-skill-assignment).
17
- * 3. `initCortexState(pool)` — app tables must exist before launch; recovery
18
- * queries them on the first step.
17
+ * 3. `initCortexState(pool, logger)` — applies the pending schema migrations.
18
+ * App tables must exist before launch; recovery queries them on the first step.
19
19
  * 4. `assertConnectionBudget(...)` — needs the live pool; gates launch so a
20
20
  * misconfigured pool fails loudly at boot, not under load.
21
21
  * 5. `assembleCoreRuntime(core)` — registers the durable-workflow cohort and
@@ -51,7 +51,7 @@ export async function bootHarness(deps) {
51
51
  const { core, pool, skillsDir, logger } = deps;
52
52
  (deps.initTelemetry ?? noop)();
53
53
  validateAgentSkills(skillsDir, SKILL_DECLARING_AGENTS);
54
- await initCortexState(pool);
54
+ await initCortexState(pool, logger);
55
55
  await assertConnectionBudget({ pool, logger, config: deps.connectionBudget });
56
56
  const runtime = assembleCoreRuntime(core);
57
57
  await deps.beforeLaunch?.();