@inflexa-ai/harness 0.45.0 → 0.45.1

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 (122) hide show
  1. package/dist/agents/conversation-agent.d.ts +10 -0
  2. package/dist/agents/conversation-agent.js +19 -1
  3. package/dist/agents/sandbox/shared.d.ts +23 -2
  4. package/dist/agents/sandbox/shared.js +51 -18
  5. package/dist/agents/sandbox/translational-safety-agent.js +26 -0
  6. package/dist/agents/sandbox/types.d.ts +24 -1
  7. package/dist/agents/sandbox-catalog.d.ts +14 -0
  8. package/dist/agents/sandbox-catalog.js +19 -0
  9. package/dist/app/approve-plan.d.ts +5 -2
  10. package/dist/app/approve-plan.js +1 -1
  11. package/dist/app/chat-turn.js +6 -3
  12. package/dist/config/environment-stores.d.ts +4 -2
  13. package/dist/contracts/chat-events.d.ts +12 -2
  14. package/dist/contracts/chat-events.js +1 -1
  15. package/dist/contracts/chat-frame.js +6 -2
  16. package/dist/contracts/index.d.ts +1 -1
  17. package/dist/contracts/schemas/chat-events.d.ts +25 -0
  18. package/dist/contracts/schemas/chat-events.js +7 -0
  19. package/dist/contracts/schemas/chat-parts.d.ts +39 -0
  20. package/dist/contracts/schemas/chat-parts.js +17 -0
  21. package/dist/contracts/schemas/index.d.ts +1 -1
  22. package/dist/contracts/schemas/index.js +1 -1
  23. package/dist/execution/dbos-workflow-purger.d.ts +2 -0
  24. package/dist/execution/dbos-workflow-purger.js +21 -14
  25. package/dist/execution/run-canceler.d.ts +33 -11
  26. package/dist/execution/run-canceler.js +129 -27
  27. package/dist/execution/run-end-record.d.ts +30 -0
  28. package/dist/execution/run-end-record.js +27 -0
  29. package/dist/index.d.ts +9 -5
  30. package/dist/index.js +11 -1
  31. package/dist/loop/call-guard.d.ts +50 -0
  32. package/dist/loop/call-guard.js +90 -0
  33. package/dist/loop/compaction.d.ts +5 -0
  34. package/dist/loop/compaction.js +17 -0
  35. package/dist/loop/run-agent.d.ts +9 -0
  36. package/dist/loop/run-agent.js +15 -1
  37. package/dist/loop/run-to-terminal.js +4 -1
  38. package/dist/loop/types.d.ts +6 -0
  39. package/dist/memory/conversation-display-recorder.d.ts +0 -2
  40. package/dist/memory/conversation-display-recorder.js +28 -3
  41. package/dist/memory/thread-history.d.ts +34 -7
  42. package/dist/memory/thread-history.js +151 -42
  43. package/dist/prompts/briefing.d.ts +57 -3
  44. package/dist/prompts/briefing.js +129 -2
  45. package/dist/prompts/planner.js +13 -2
  46. package/dist/provenance/seam.d.ts +23 -15
  47. package/dist/provenance/seam.js +7 -5
  48. package/dist/providers/ai-sdk.js +87 -7
  49. package/dist/providers/types.d.ts +6 -0
  50. package/dist/reference-data/catalog.js +141 -2
  51. package/dist/report-model/authoring-grammar.d.ts +26 -26
  52. package/dist/sandbox/image-packages.d.ts +26 -4
  53. package/dist/sandbox/image-packages.js +18 -4
  54. package/dist/schemas/data-profile-schemas.d.ts +12 -12
  55. package/dist/schemas/plan-schemas.d.ts +57 -3
  56. package/dist/schemas/plan-schemas.js +1 -1
  57. package/dist/schemas/workflow-state.d.ts +113 -0
  58. package/dist/schemas/workflow-state.js +54 -0
  59. package/dist/state/artifacts.d.ts +17 -1
  60. package/dist/state/artifacts.js +21 -3
  61. package/dist/state/index.d.ts +2 -2
  62. package/dist/state/index.js +1 -1
  63. package/dist/state/runs.d.ts +43 -9
  64. package/dist/state/runs.js +85 -14
  65. package/dist/state/step-executions.d.ts +9 -0
  66. package/dist/state/step-executions.js +22 -0
  67. package/dist/tools/ad-hoc-router.d.ts +2 -0
  68. package/dist/tools/ad-hoc-router.js +18 -2
  69. package/dist/tools/capability-data.d.ts +36 -0
  70. package/dist/tools/capability-data.js +73 -0
  71. package/dist/tools/execute-analysis.d.ts +37 -9
  72. package/dist/tools/execute-analysis.js +131 -21
  73. package/dist/tools/knowledge/check.d.ts +60 -0
  74. package/dist/tools/knowledge/check.js +92 -0
  75. package/dist/tools/knowledge/client.d.ts +382 -0
  76. package/dist/tools/knowledge/client.js +302 -0
  77. package/dist/tools/knowledge/environment.d.ts +78 -0
  78. package/dist/tools/knowledge/environment.js +105 -0
  79. package/dist/tools/knowledge/index.d.ts +49 -0
  80. package/dist/tools/knowledge/index.js +70 -0
  81. package/dist/tools/knowledge/local-slots.d.ts +62 -0
  82. package/dist/tools/knowledge/local-slots.js +177 -0
  83. package/dist/tools/knowledge/pin.d.ts +14 -0
  84. package/dist/tools/knowledge/pin.js +17 -0
  85. package/dist/tools/knowledge/recommend.d.ts +92 -0
  86. package/dist/tools/knowledge/recommend.js +98 -0
  87. package/dist/tools/knowledge/situation.d.ts +143 -0
  88. package/dist/tools/knowledge/situation.js +164 -0
  89. package/dist/tools/knowledge/skeleton.d.ts +69 -0
  90. package/dist/tools/knowledge/skeleton.js +202 -0
  91. package/dist/tools/knowledge/template.d.ts +137 -0
  92. package/dist/tools/knowledge/template.js +267 -0
  93. package/dist/tools/research/generate-plan.d.ts +23 -0
  94. package/dist/tools/research/generate-plan.js +203 -12
  95. package/dist/tools/research/guarded-address.d.ts +20 -0
  96. package/dist/tools/research/guarded-address.js +148 -0
  97. package/dist/tools/research/searxng-engine.d.ts +40 -0
  98. package/dist/tools/research/searxng-engine.js +120 -0
  99. package/dist/tools/research/web-fetch.d.ts +124 -0
  100. package/dist/tools/research/web-fetch.js +627 -0
  101. package/dist/tools/research/web-search.d.ts +75 -0
  102. package/dist/tools/research/web-search.js +112 -0
  103. package/dist/tools/sandbox/list-available-packages.d.ts +7 -4
  104. package/dist/tools/sandbox/list-available-packages.js +12 -4
  105. package/dist/tools/sandbox/list-available-refs.d.ts +21 -0
  106. package/dist/tools/sandbox/list-available-refs.js +60 -3
  107. package/dist/tools/sandbox/report-blocker.js +5 -1
  108. package/dist/tools/terminal-text.d.ts +11 -0
  109. package/dist/tools/terminal-text.js +19 -0
  110. package/dist/tools/workspace/decision-record.d.ts +81 -0
  111. package/dist/tools/workspace/decision-record.js +117 -0
  112. package/dist/tools/workspace/edit-file.d.ts +3 -1
  113. package/dist/tools/workspace/edit-file.js +20 -2
  114. package/dist/tools/workspace/execute-command.d.ts +17 -0
  115. package/dist/tools/workspace/execute-command.js +28 -1
  116. package/dist/tools/workspace/index.d.ts +1 -0
  117. package/dist/tools/workspace/index.js +1 -0
  118. package/dist/tools/workspace/mutator.d.ts +1 -1
  119. package/dist/workflows/execute-analysis.d.ts +58 -18
  120. package/dist/workflows/execute-analysis.js +273 -58
  121. package/dist/workflows/sandbox-step.d.ts +12 -0
  122. package/package.json +5 -1
@@ -37,6 +37,7 @@ import type { ExtendAnalysisFarm } from "../sandbox/types.js";
37
37
  import type { WorkspaceFilesystem } from "../workspace/filesystem.js";
38
38
  import type { ResolveWorkspaceRoot } from "../workspace/paths.js";
39
39
  import { type BioToolKeys } from "../tools/bio/keys.js";
40
+ import type { KnowledgeClient } from "../tools/knowledge/index.js";
40
41
  import type { EnvironmentStorePaths } from "../config/environment-stores.js";
41
42
  import type { RunAuthorizer } from "../execution/run-authorizer.js";
42
43
  import type { RunLauncher } from "../execution/run-launcher.js";
@@ -44,6 +45,7 @@ import type { ProvenanceSeam } from "../provenance/seam.js";
44
45
  import type { Logger } from "../lib/logger.js";
45
46
  import type { UsageRecorder } from "../billing/usage-recorder.js";
46
47
  import type { CitationResolver } from "../citations/types.js";
48
+ import { type WebSearchEngine } from "../tools/research/web-search.js";
47
49
  /** Canonical agent id — the single source of truth. */
48
50
  export declare const CONVERSATION_AGENT_ID: "conversation-agent";
49
51
  /**
@@ -63,6 +65,8 @@ export interface ConversationAgentDeps extends EnvironmentStorePaths {
63
65
  readonly usageRecorder?: UsageRecorder;
64
66
  /** Adds `read_tool_output`, and each loop-driving tool gives the same store to its sub-agent. */
65
67
  readonly toolOutputStore?: ToolOutputStore;
68
+ /** The web-search engine the embedder injected; set, `web_search` attaches, absent it does not. `web_fetch` attaches either way. */
69
+ readonly webSearch?: WebSearchEngine;
66
70
  /** Shared host-side bibliographic verification service. */
67
71
  readonly citationResolver: CitationResolver;
68
72
  /** The LLM seam every loop-driving tool runs its sub-agent on. */
@@ -107,6 +111,12 @@ export interface ConversationAgentDeps extends EnvironmentStorePaths {
107
111
  readonly runLauncher: RunLauncher;
108
112
  /** API keys for the external bio/chem data sources. */
109
113
  readonly bioKeys: BioToolKeys;
114
+ /**
115
+ * The knowledge service client, for the planner that `generate_plan` drives.
116
+ * Absent, the planner attaches no knowledge tool. The conversation agent
117
+ * itself gets `knowledge_recommend` through `hostTools`, from the embedder.
118
+ */
119
+ readonly knowledge?: KnowledgeClient;
110
120
  /** Skills root. No tool of the roster reads it. */
111
121
  readonly skillsDir: string;
112
122
  /** Headless-Chrome config for report snapshot/preview rendering. */
@@ -36,10 +36,13 @@ import { createEditFileTool, createFileStatTool, createGrepTool, createListFiles
36
36
  import { showUserTool } from "../tools/display/index.js";
37
37
  import { createUpdateWorkingMemoryTool } from "../tools/memory/index.js";
38
38
  import { createListAvailablePackagesTool } from "../tools/sandbox/list-available-packages.js";
39
+ import { createInspectCapabilityDataTool } from "../tools/capability-data.js";
39
40
  import { createListAvailableRefsTool } from "../tools/sandbox/list-available-refs.js";
40
41
  import { createExecuteAnalysisTool } from "../tools/execute-analysis.js";
41
42
  import { createStartReportSessionTool } from "../tools/start-report-session.js";
42
43
  import { createResolveCitationTool } from "../tools/research/resolve-citation.js";
44
+ import { createWebFetchTool } from "../tools/research/web-fetch.js";
45
+ import { createWebSearchTool } from "../tools/research/web-search.js";
43
46
  /** Canonical agent id — the single source of truth. */
44
47
  export const CONVERSATION_AGENT_ID = "conversation-agent";
45
48
  /** Runaway guard — heavy tool-driving turns need generous headroom. */
@@ -48,7 +51,7 @@ const CONVERSATION_MAX_ITERATIONS = 200;
48
51
  const CONVERSATION_COMPACTION = { turnStartBudget: 150_000, budget: 200_000 };
49
52
  /** Build the conversation `AgentDefinition` with every tool fully dep-bound. */
50
53
  export function createConversationAgent(deps) {
51
- const { provider, pool, embedding, workspaceFs, model, utilityProvider, utilityModel, executeAnalysisWorkflow, runAuthorizer, runLauncher, bioKeys, chrome, resourcePolicy, hostTools, refStorePath, farmLockFile, imagePackagesFile, readPoolInventory, usageRecorder, toolOutputStore, citationResolver, } = deps;
54
+ const { provider, pool, embedding, workspaceFs, model, utilityProvider, utilityModel, executeAnalysisWorkflow, runAuthorizer, runLauncher, bioKeys, chrome, resourcePolicy, hostTools, refStorePath, farmLockFile, imagePackagesFile, readPoolInventory, usageRecorder, toolOutputStore, citationResolver, webSearch, } = deps;
52
55
  const workingMemory = createWorkingMemory(pool);
53
56
  const ncbi = createNcbiTools(bioKeys);
54
57
  const chemDb = createChemDbTools(bioKeys, { ...(deps.logger ? { logger: deps.logger } : {}) });
@@ -81,6 +84,11 @@ export function createConversationAgent(deps) {
81
84
  // preprint has no MEDLINE record, thus PubMed alone cannot answer it.
82
85
  createSearchSemanticScholarTool({ ...(bioKeys.semanticScholar === undefined ? {} : { apiKey: bioKeys.semanticScholar }) }),
83
86
  searchArxivTool,
87
+ // The web step of research — the injected web-search engine (the CLI runs
88
+ // SearXNG and binds its realization), then the guarded page fetch that
89
+ // reads what the search found.
90
+ ...(webSearch ? [createWebSearchTool({ engine: webSearch })] : []),
91
+ createWebFetchTool({ ...(toolOutputStore ? { toolOutputStore } : {}) }),
84
92
  // ChEMBL (compounds / drug / mechanism / bioactivity / targets behind one action).
85
93
  chemblTool,
86
94
  // PubChem (compound / crossrefs / assays behind one action).
@@ -109,6 +117,11 @@ export function createConversationAgent(deps) {
109
117
  // blind is how a user gets asked to download something already installed, or
110
118
  // told nothing is missing when it is; this is the only tool that can tell.
111
119
  createListAvailableRefsTool({ ...(refStorePath ? { refStorePath } : {}) }),
120
+ // Which datasets a specialist capability cannot run without. `list_available_refs` answers
121
+ // what the store HOLDS; this answers what a capability WANTS, which no prompt may name and
122
+ // only a skill otherwise records — and a skill is read by a step agent, not here. Without
123
+ // it this agent meets an absent source as a dead end and designs around it.
124
+ createInspectCapabilityDataTool({ ...(refStorePath ? { refStorePath } : {}) }),
112
125
  // What the STORE holds, pool-scope where the embedder binds the reader:
113
126
  // the ask flow of this agent marks the packages the pool does not hold,
114
127
  // and the farm of a new analysis is empty — a farm view here would read
@@ -130,6 +143,7 @@ export function createConversationAgent(deps) {
130
143
  usageRecorder,
131
144
  ...(toolOutputStore ? { toolOutputStore } : {}),
132
145
  bioKeys,
146
+ ...(deps.knowledge ? { knowledge: deps.knowledge } : {}),
133
147
  ...(refStorePath ? { refStorePath } : {}),
134
148
  ...(farmLockFile ? { farmLockFile } : {}),
135
149
  ...(imagePackagesFile ? { imagePackagesFile } : {}),
@@ -147,6 +161,10 @@ export function createConversationAgent(deps) {
147
161
  ...(farmLockFile ? { farmLockFile } : {}),
148
162
  ...(imagePackagesFile ? { imagePackagesFile } : {}),
149
163
  ...(readPoolInventory ? { readPoolInventory } : {}),
164
+ // The pre-launch pass resolves the declared reference data of a capability against the
165
+ // store. Without this path it would read the CONTAINER path, which no host holds, and
166
+ // refuse every launch of a plan that declares one.
167
+ ...(refStorePath ? { refStorePath } : {}),
150
168
  ...(deps.extendAnalysisFarm ? { extendAnalysisFarm: deps.extendAnalysisFarm } : {}),
151
169
  ...(deps.logger ? { logger: deps.logger } : {}),
152
170
  ...(usageRecorder ? { usageRecorder } : {}),
@@ -31,11 +31,19 @@ import type { AgentMeta, SandboxToolName } from "./types.js";
31
31
  import type { EnvironmentStorePaths } from "../../config/environment-stores.js";
32
32
  import type { Logger } from "../../lib/logger.js";
33
33
  import type { CitationResolver } from "../../citations/types.js";
34
+ import { type KnowledgeClient, type TemplateBinding } from "../../tools/knowledge/index.js";
34
35
  /**
35
36
  * Tools every sandbox agent receives — sandbox-environment introspection,
36
- * library docs, and run inspection. Spread into each agent's
37
- * `meta.tools` so planner-facing metadata and the resolved tool record
37
+ * library docs, run inspection, and the knowledge template. Spread into each
38
+ * agent's `meta.tools` so planner-facing metadata and the resolved tool record
38
39
  * stay in sync. Keep minimal — only tools every agent genuinely needs.
40
+ *
41
+ * The knowledge template is here, not on a specialist, because the template
42
+ * follows the step: the planner binds a template to a step from the
43
+ * recommendation and routes that step to whichever specialist owns its data
44
+ * object. The tool acts only on the template the step grounding names, thus
45
+ * a step without a template costs nothing, and it attaches only when the
46
+ * embedder binds a knowledge client (see the registry below).
39
47
  */
40
48
  export declare const BASE_SANDBOX_TOOLS: readonly SandboxToolName[];
41
49
  /** Per-step coordinates the composition root threads through every mutate tool. */
@@ -48,6 +56,12 @@ export interface SandboxStepCoords {
48
56
  readonly allowedWritePrefix: string;
49
57
  /** Absolute unix-ms deadline of each exec. */
50
58
  readonly deadlineMs: () => number;
59
+ /**
60
+ * The plan settings bound to the template of this step, from the durable
61
+ * step input. Reaches `knowledge_template` alone. Absent when the step
62
+ * grounds on no template, or when the host bound nothing.
63
+ */
64
+ readonly templateBinding?: TemplateBinding;
51
65
  }
52
66
  /** The shared dependency graph every sandbox agent draws from. */
53
67
  export interface SandboxAgentDeps extends EnvironmentStorePaths {
@@ -95,6 +109,13 @@ export interface SandboxAgentDeps extends EnvironmentStorePaths {
95
109
  * and the package-link prompt layer appends. Without it, neither exists.
96
110
  */
97
111
  readonly extendAnalysisFarm?: ExtendAnalysisFarm;
112
+ /**
113
+ * The knowledge service client. Bound, an agent that declares
114
+ * `knowledgeTemplate` gets the tool, over the same mutator as `write_file`.
115
+ * Absent, the declaration resolves to nothing, which is the default state
116
+ * of the open-source host and never an error.
117
+ */
118
+ readonly knowledge?: KnowledgeClient;
98
119
  }
99
120
  /** Per-agent override for the prompt composition and tool surface. */
100
121
  export interface SandboxAgentPromptOptions {
@@ -42,19 +42,34 @@ import { createSubmitFileMetadataTool } from "../../tools/sandbox/submit-file-me
42
42
  import { toSandboxPath } from "../../workspace/paths.js";
43
43
  import { SANDBOX_AGENT_DEFAULT_MAX_ITERATIONS } from "./types.js";
44
44
  import { createResolveCitationTool } from "../../tools/research/resolve-citation.js";
45
+ import { createKnowledgeTemplateTool } from "../../tools/knowledge/index.js";
45
46
  /**
46
47
  * Tools every sandbox agent receives — sandbox-environment introspection,
47
- * library docs, and run inspection. Spread into each agent's
48
- * `meta.tools` so planner-facing metadata and the resolved tool record
48
+ * library docs, run inspection, and the knowledge template. Spread into each
49
+ * agent's `meta.tools` so planner-facing metadata and the resolved tool record
49
50
  * stay in sync. Keep minimal — only tools every agent genuinely needs.
51
+ *
52
+ * The knowledge template is here, not on a specialist, because the template
53
+ * follows the step: the planner binds a template to a step from the
54
+ * recommendation and routes that step to whichever specialist owns its data
55
+ * object. The tool acts only on the template the step grounding names, thus
56
+ * a step without a template costs nothing, and it attaches only when the
57
+ * embedder binds a knowledge client (see the registry below).
50
58
  */
51
- export const BASE_SANDBOX_TOOLS = ["listAvailablePackages", "listAvailableRefs", "resolveLibraryId", "queryDocs", "inspectRun"];
59
+ export const BASE_SANDBOX_TOOLS = [
60
+ "listAvailablePackages",
61
+ "listAvailableRefs",
62
+ "resolveLibraryId",
63
+ "queryDocs",
64
+ "inspectRun",
65
+ "knowledgeTemplate",
66
+ ];
52
67
  /**
53
68
  * Resolve `meta.tools` into the bio/research tools record. The workspace
54
69
  * read/mutate tools and `inspect_data_profile` are added by `createSandboxAgent`
55
70
  * regardless of meta — they are the always-on substrate, not in the allowlist.
56
71
  */
57
- function resolveSandboxTools(deps, tools) {
72
+ function resolveSandboxTools(deps, tools, mutator) {
58
73
  const ncbi = createNcbiTools(deps.bioKeys);
59
74
  const chemDb = createChemDbTools(deps.bioKeys, { ...(deps.logger ? { logger: deps.logger } : {}) });
60
75
  const amass = createAmassTools(deps.bioKeys);
@@ -97,6 +112,15 @@ function resolveSandboxTools(deps, tools) {
97
112
  searchRegulatoryApprovals: amass.regulatoryApprovals,
98
113
  targetSafety: targetSafetyTool,
99
114
  comptox: chemDb.comptox,
115
+ knowledgeTemplate: deps.knowledge && mutator
116
+ ? createKnowledgeTemplateTool({
117
+ client: deps.knowledge,
118
+ mutator,
119
+ ...(deps.farmLockFile ? { farmLockFile: deps.farmLockFile } : {}),
120
+ ...(deps.imagePackagesFile ? { imagePackagesFile: deps.imagePackagesFile } : {}),
121
+ ...(deps.step.templateBinding ? { binding: deps.step.templateBinding } : {}),
122
+ })
123
+ : undefined,
100
124
  };
101
125
  const seen = new Set();
102
126
  const resolved = [];
@@ -105,6 +129,12 @@ function resolveSandboxTools(deps, tools) {
105
129
  continue;
106
130
  seen.add(name);
107
131
  const tool = registry[name];
132
+ // The knowledge template is the one optional member of the allowlist: every
133
+ // agent lists it through BASE_SANDBOX_TOOLS, and it attaches only when the
134
+ // embedder binds a client and the agent can write. Absence is a normal
135
+ // state, not a wiring fault.
136
+ if (name === "knowledgeTemplate" && !tool)
137
+ continue;
108
138
  if (!tool) {
109
139
  throw new Error(`createSandboxAgent: unknown SandboxToolName "${name}" — ` + `agent meta references a tool with no harness implementation.`);
110
140
  }
@@ -124,24 +154,25 @@ function buildWorkspaceTools(deps, readOnly) {
124
154
  // it is correct for non-step agents such as the data profiler too.
125
155
  const hostWorkingDir = step.allowedWritePrefix;
126
156
  const sandboxWorkingDir = toSandboxPath(step.workspaceRoot, step.analysisId, hostWorkingDir);
127
- const mutateTools = readOnly
128
- ? []
129
- : (() => {
130
- const mutator = createWorkspaceMutator({
131
- workspaceRoot: step.workspaceRoot,
132
- analysisId: step.analysisId,
133
- workingDir: hostWorkingDir,
134
- ...(lineageCollector ? { lineageCollector } : {}),
135
- });
136
- return [createWriteFileTool({ mutator }), createEditFileTool({ mutator, workspaceFilesystem: workspaceFs, workingDir: hostWorkingDir })];
137
- })();
138
- return [
157
+ const mutator = readOnly
158
+ ? undefined
159
+ : createWorkspaceMutator({
160
+ workspaceRoot: step.workspaceRoot,
161
+ analysisId: step.analysisId,
162
+ workingDir: hostWorkingDir,
163
+ ...(lineageCollector ? { lineageCollector } : {}),
164
+ });
165
+ const mutateTools = mutator
166
+ ? [createWriteFileTool({ mutator }), createEditFileTool({ mutator, workspaceFilesystem: workspaceFs, workingDir: hostWorkingDir })]
167
+ : [];
168
+ const tools = [
139
169
  createExecuteCommandTool({
140
170
  sandboxClient,
141
171
  sandbox: step.sandbox,
142
172
  deadlineMs: step.deadlineMs,
143
173
  defaultCwd: sandboxWorkingDir,
144
174
  ...(lineageCollector ? { lineageCollector, mountRoot: `/${step.analysisId}` } : {}),
175
+ ...(mutator ? { workspaceFilesystem: workspaceFs, mutator } : {}),
145
176
  }),
146
177
  ...mutateTools,
147
178
  createReadFileTool(workspaceFs, hostWorkingDir),
@@ -150,6 +181,7 @@ function buildWorkspaceTools(deps, readOnly) {
150
181
  createGrepTool(workspaceFs, hostWorkingDir),
151
182
  ...(embedding ? [createWorkspaceSearchTool(pool, embedding)] : []),
152
183
  ];
184
+ return { tools, mutator };
153
185
  }
154
186
  /**
155
187
  * Build one sandbox `AgentDefinition`. The system prompt is composed once
@@ -185,8 +217,9 @@ export function createSandboxAgent(deps, meta, body, opts = {}) {
185
217
  const agentBody = [body, ...sandboxLayer].map((s) => s.trim()).join("\n\n");
186
218
  const systemPrompt = composeSystemPrompt(agentBody);
187
219
  const skillTools = deps.skillsDir ? Object.values(createSkillTools({ skillsDir: deps.skillsDir, skills: meta.skills })) : [];
220
+ const workspace = buildWorkspaceTools(deps, opts.readOnly ?? false);
188
221
  const tools = [
189
- ...buildWorkspaceTools(deps, opts.readOnly ?? false),
222
+ ...workspace.tools,
190
223
  ...(deps.toolOutputStore ? [createReadToolOutputTool(deps.toolOutputStore)] : []),
191
224
  // Always-on, not in the `meta.tools` allowlist: the data profile is the only
192
225
  // record of what the analysis's input dataset IS, and no file carries it (the
@@ -199,7 +232,7 @@ export function createSandboxAgent(deps, meta, body, opts = {}) {
199
232
  // seam (the harness-sandbox-agents spec).
200
233
  ...(deps.extendAnalysisFarm ? [createLinkPackagesTool({ extendAnalysisFarm: deps.extendAnalysisFarm, analysisId: deps.step.analysisId })] : []),
201
234
  ...skillTools,
202
- ...resolveSandboxTools(deps, meta.tools),
235
+ ...resolveSandboxTools(deps, meta.tools, workspace.mutator),
203
236
  ...(deps.blockerHolder ? [createReportBlockerTool(deps.blockerHolder)] : []),
204
237
  ...(deps.fileMetadata ? [createSubmitFileMetadataTool(deps.fileMetadata)] : []),
205
238
  ];
@@ -3,6 +3,11 @@
3
3
  */
4
4
  import { translationalSafetyAgentPrompt } from "../../prompts/sandbox/translational-safety-agent.js";
5
5
  import { BASE_SANDBOX_TOOLS, createSandboxAgent } from "./shared.js";
6
+ /**
7
+ * Named once, because the capability phrase is the key of its own data requirement below and a
8
+ * plan step copies it verbatim. Two spellings of one capability would lose the pre-launch check.
9
+ */
10
+ const MATCHED_NEGATIVE_CAPABILITY = "matched negative gene set for a tissue risk benchmark (annotation_free or tested_clean negatives) matched on tissue expression and PPI connectivity with mouse and human phenotype annotation exclusions";
6
11
  export const meta = {
7
12
  id: "translational-safety-agent",
8
13
  capabilities: [
@@ -20,6 +25,8 @@ export const meta = {
20
25
  "hematological toxicity assessment",
21
26
  "mouse KO phenotype lookup",
22
27
  "cross-species baseline expression lookup",
28
+ MATCHED_NEGATIVE_CAPABILITY,
29
+ "expression and PPI connectivity matching",
23
30
  ],
24
31
  suitableFor: [
25
32
  "safety-biomarkers",
@@ -29,6 +36,25 @@ export const meta = {
29
36
  "clinical-safety-labs",
30
37
  "drug-interaction-data",
31
38
  "pharmacogenomic-data",
39
+ "gene-lists",
40
+ ],
41
+ // The method of this capability reads six opt-in datasets. Without any one of them a negatives
42
+ // file would carry a label the run cannot defend, so the launch refuses rather than spend a run.
43
+ // The recommended datasets it also reads (the expression table, the network, the gene tables,
44
+ // the GO collection) stay out: a default store holds them, and declaring one would refuse a
45
+ // launch an embedder already provisioned.
46
+ dataRequirements: [
47
+ {
48
+ capability: MATCHED_NEGATIVE_CAPABILITY,
49
+ datasets: [
50
+ "string-aliases-human",
51
+ "mgi-mouse-human-homology",
52
+ "impc-phenotyping",
53
+ "hpo-annotations",
54
+ "opentargets-mouse-phenotype",
55
+ "opentargets-target-safety-event",
56
+ ],
57
+ },
32
58
  ],
33
59
  skills: ["translational-safety", "cheminformatics", "shared/omics-general"],
34
60
  tools: [
@@ -12,7 +12,21 @@
12
12
  * in the tool's `perSource` report; the rest of the call still answers.
13
13
  */
14
14
  /** Closed allowlist of tools any sandbox agent may declare. */
15
- export type SandboxToolName = "listAvailablePackages" | "listAvailableRefs" | "scanInputs" | "resolveLibraryId" | "queryDocs" | "inspectRun" | "pubmed" | "resolve_citation" | "searchGene" | "lookupAnnotation" | "searchInteractions" | "chembl" | "pubchem" | "opentargets" | "geneDiseaseEvidence" | "drugGeneInteractions" | "genePreclinicalProfile" | "searchFaers" | "searchClinicalTrials" | "searchGeoDatasets" | "searchRegulatoryApprovals" | "targetSafety" | "comptox";
15
+ export type SandboxToolName = "listAvailablePackages" | "listAvailableRefs" | "scanInputs" | "resolveLibraryId" | "queryDocs" | "inspectRun" | "pubmed" | "resolve_citation" | "searchGene" | "lookupAnnotation" | "searchInteractions" | "chembl" | "pubchem" | "opentargets" | "geneDiseaseEvidence" | "drugGeneInteractions" | "genePreclinicalProfile" | "searchFaers" | "searchClinicalTrials" | "searchGeoDatasets" | "searchRegulatoryApprovals" | "targetSafety" | "comptox" | "knowledgeTemplate";
16
+ /**
17
+ * The reference datasets that one capability of one agent cannot run without.
18
+ *
19
+ * A requirement belongs to a capability, never to the agent: an agent holds capabilities that need
20
+ * no reference data at all, so an agent-wide requirement would refuse a step that needs nothing.
21
+ * The skill of the agent still states what the data IS and how to use it. This states which catalog
22
+ * entry carries it, in a form a test can pin.
23
+ */
24
+ export interface CapabilityDataRequirement {
25
+ /** One phrase of the same agent's `capabilities`, verbatim — the step copies it from the catalog. */
26
+ readonly capability: string;
27
+ /** Reference-catalog dataset ids. Declare only an opt-in dataset: a recommended one is already in a default store. */
28
+ readonly datasets: readonly string[];
29
+ }
16
30
  /** Planner-facing metadata + tool allowlist for one sandbox agent. */
17
31
  export interface AgentMeta {
18
32
  /** Stable agent id — also the key in the catalog and the `AgentDefinition.id`. */
@@ -29,6 +43,15 @@ export interface AgentMeta {
29
43
  readonly defaultMaxSteps?: number;
30
44
  /** False for agents the planner must NOT assign to plan steps (executors, the profiler). */
31
45
  readonly plannable?: boolean;
46
+ /**
47
+ * Per-capability reference-data requirements. Omitted by an agent whose capabilities all run on
48
+ * what a default store holds.
49
+ *
50
+ * The pre-launch pass resolves these against the live store, so an absent dataset refuses the
51
+ * launch instead of costing a run. The planner-facing projection deliberately drops this field:
52
+ * a prompt names no dataset, so the ids reach a caller through a tool and the launch through here.
53
+ */
54
+ readonly dataRequirements?: readonly CapabilityDataRequirement[];
32
55
  }
33
56
  /** Default runaway-guard for sandbox agents. */
34
57
  export declare const SANDBOX_AGENT_DEFAULT_MAX_ITERATIONS = 200;
@@ -15,6 +15,20 @@ export interface PlannerAgentMeta {
15
15
  }
16
16
  /** The sandbox agents the planner may assign to analysis-plan steps. */
17
17
  export declare const PLANNABLE_AGENT_CATALOG: readonly PlannerAgentMeta[];
18
+ /**
19
+ * The reference datasets one capability of one agent cannot run without, by catalog id.
20
+ *
21
+ * Empty for an unknown agent, for an agent that declares nothing, and for a phrase no declaration
22
+ * matches. An unmatched phrase must NOT refuse a launch: the pass can only demand what a
23
+ * declaration states, so a paraphrased phrase degrades to the behaviour of a step with no phrase.
24
+ */
25
+ export declare function capabilityDataRequirement(agentId: string, capability: string): readonly string[];
26
+ /** Every declared requirement, for a caller that asks what a capability wants before it plans. */
27
+ export declare function declaredDataRequirements(): readonly {
28
+ agentId: string;
29
+ capability: string;
30
+ datasets: readonly string[];
31
+ }[];
18
32
  /**
19
33
  * Ids of the agents the planner may assign — the `z.enum` domain for
20
34
  * `PlanStepSchema.agent`. A non-empty tuple, as `z.enum` requires.
@@ -16,6 +16,25 @@ export const PLANNABLE_AGENT_CATALOG = Object.values(SANDBOX_AGENT_META)
16
16
  capabilities: [...meta.capabilities],
17
17
  suitableFor: [...meta.suitableFor],
18
18
  }));
19
+ /**
20
+ * The reference datasets one capability of one agent cannot run without, by catalog id.
21
+ *
22
+ * Empty for an unknown agent, for an agent that declares nothing, and for a phrase no declaration
23
+ * matches. An unmatched phrase must NOT refuse a launch: the pass can only demand what a
24
+ * declaration states, so a paraphrased phrase degrades to the behaviour of a step with no phrase.
25
+ */
26
+ export function capabilityDataRequirement(agentId, capability) {
27
+ const entry = SANDBOX_AGENT_META[agentId]?.dataRequirements?.find((requirement) => requirement.capability === capability);
28
+ return entry?.datasets ?? [];
29
+ }
30
+ /** Every declared requirement, for a caller that asks what a capability wants before it plans. */
31
+ export function declaredDataRequirements() {
32
+ return Object.values(SANDBOX_AGENT_META).flatMap((meta) => (meta.dataRequirements ?? []).map((requirement) => ({
33
+ agentId: meta.id,
34
+ capability: requirement.capability,
35
+ datasets: requirement.datasets,
36
+ })));
37
+ }
19
38
  /**
20
39
  * Ids of the agents the planner may assign — the `z.enum` domain for
21
40
  * `PlanStepSchema.agent`. A non-empty tuple, as `z.enum` requires.
@@ -23,12 +23,15 @@ export type ApprovePlanInput = {
23
23
  analysisId: string;
24
24
  threadId: string;
25
25
  planId: string;
26
+ /** The newest run of the plan in the view that the person approved, or `null` when that view showed no run. */
27
+ newestRunId: string | null;
26
28
  identity: Identity;
27
29
  auth: AuthContext;
28
30
  };
29
31
  /**
30
- * The outcome of an approval. `started` is false when a run of the plan was already active: the
31
- * approval then changed nothing, and `message` is `null`. `message` is also `null` when the record
32
+ * The outcome of an approval. `started` is false when a run of the plan was already active, or when the
33
+ * view of the person was stale: the approval then changed nothing, `runId` names the active run or the
34
+ * newest run of the plan, and `message` is `null`. `message` is also `null` when the record
32
35
  * could not be appended, because the run started anyway. It has no `createdAt`: the transcript read
33
36
  * adds the time of the row.
34
37
  */
@@ -55,7 +55,7 @@ async function approve(deps, input) {
55
55
  scope: { kind: "analysis", analysisId, threadId },
56
56
  provenance: APPROVAL_PROVENANCE,
57
57
  };
58
- const launched = await launchPlanRun(deps, { session, planId });
58
+ const launched = await launchPlanRun(deps, { session, planId, newestRunId: input.newestRunId });
59
59
  if (launched.isErr())
60
60
  return err(launched.error);
61
61
  const { runId, started } = launched.value;
@@ -4,6 +4,7 @@
4
4
  * values, and the harness stores the opening, each round, and the outcome.
5
5
  */
6
6
  import { unwrapOrThrow } from "../lib/result.js";
7
+ import { rulesForContextWindow } from "../loop/compaction.js";
7
8
  import { finalText, runAgent } from "../loop/run-agent.js";
8
9
  import { passthroughStep } from "../loop/run-step.js";
9
10
  import { compactionExchangeOf, syntheticUserMessage } from "../memory/ai-sdk-message-storage.js";
@@ -244,10 +245,12 @@ function compactionPolicy(deps, analysisId, params, threadType, agent, emit) {
244
245
  // A report thread reads a frozen copy of the working memory, thus its agent declares no memory tool.
245
246
  const remembers = agent.tools.some((tool) => tool.id === WORKING_MEMORY_TOOL_ID);
246
247
  const workingMemory = createWorkingMemory(deps.pool);
248
+ // A streaming provider sends each text delta with no source, thus a delta of the summary would show as a reply.
249
+ const provider = params.chat((event) => (event.type === "text-delta" ? undefined : emit(event)));
247
250
  return {
248
- ...agent.compaction,
249
- // A streaming provider sends each text delta with no source, thus a delta of the summary would show as a reply.
250
- provider: params.chat((event) => (event.type === "text-delta" ? undefined : emit(event))),
251
+ // The capabilities are read for each turn, because a host can switch the model of the conversation between turns.
252
+ ...rulesForContextWindow(agent.compaction, provider.capabilities.contextWindow),
253
+ provider,
251
254
  request: remembers ? MEMORY_COMPACTION_REQUEST : SUMMARY_COMPACTION_REQUEST,
252
255
  mask: remembers ? { allow: [WORKING_MEMORY_TOOL_ID] } : "none",
253
256
  keepFirstTurn: threadType === "report",
@@ -49,8 +49,10 @@ export interface EnvironmentStorePaths {
49
49
  readonly farmLockFile?: string | ((analysisId: string) => string);
50
50
  /**
51
51
  * Host path of the image inventory record, `image-packages.json` at the
52
- * root of the package store — the image-owned tools and Node packages
53
- * that `list_available_packages` merges into its report. Omit when the
52
+ * root of the package store — the image-owned tools, the Node packages,
53
+ * and the R packages that ship with the R runtime, which
54
+ * `list_available_packages` merges into its report and which the
55
+ * knowledge tools read for the environment of a step. Omit when the
54
56
  * host's own process sees the store at the container mountpoint, which
55
57
  * makes `/mnt/libs/image-packages.json` correct as-is; set it when the
56
58
  * host reads the store somewhere else. An absent or invalid record
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Chat-stream events — the harness wire vocabulary.
3
3
  *
4
- * Distinct from the 15 `data-*` chat parts (`chat-parts.ts`): those are UI
4
+ * Distinct from the `data-*` chat parts (`chat-parts.ts`): those are UI
5
5
  * presentation payloads a tool emits; these are the agent-loop stream events
6
6
  * the host chat route frames as SSE. Together they are the sole
7
7
  * host↔frontend wire contract — there is no AI SDK UI Message Stream
@@ -142,8 +142,18 @@ export interface ChatErrorEvent {
142
142
  reason?: string;
143
143
  source: EventSource;
144
144
  }
145
+ /**
146
+ * The id that the stored display gives a message of the turn. The frames after it belong to that message, up to
147
+ * the next `message-start` of the same role.
148
+ */
149
+ export interface MessageStartEvent {
150
+ type: "message-start";
151
+ messageId: string;
152
+ role: "user" | "assistant";
153
+ source: EventSource;
154
+ }
145
155
  /** The discriminated union of all chat-stream events. */
146
- export type ChatEvent = TextDeltaEvent | IterationEvent | ToolStartedEvent | ToolFinishedEvent | FinishEvent | ChatErrorEvent;
156
+ export type ChatEvent = TextDeltaEvent | IterationEvent | ToolStartedEvent | ToolFinishedEvent | FinishEvent | ChatErrorEvent | MessageStartEvent;
147
157
  /** A `data-*` part as a frame, with the source of its emitter when the emitter stamped one. */
148
158
  export type ChatPartFrame = ChatPart & {
149
159
  source?: EventSource;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Chat-stream events — the harness wire vocabulary.
3
3
  *
4
- * Distinct from the 15 `data-*` chat parts (`chat-parts.ts`): those are UI
4
+ * Distinct from the `data-*` chat parts (`chat-parts.ts`): those are UI
5
5
  * presentation payloads a tool emits; these are the agent-loop stream events
6
6
  * the host chat route frames as SSE. Together they are the sole
7
7
  * host↔frontend wire contract — there is no AI SDK UI Message Stream
@@ -53,6 +53,8 @@ export function toChatFrame(event, fallbackSource) {
53
53
  };
54
54
  case "text-delta":
55
55
  return { type: "text-delta", text: event.text, source: toSource(fallbackSource) };
56
+ case "message-start":
57
+ return { type: "message-start", messageId: event.messageId, role: event.role, source: toSource(event.source) };
56
58
  default: {
57
59
  // The cast names the frame that the type claims. `checkChatPart` is the check.
58
60
  const part = flattenDataPart(event);
@@ -117,7 +119,7 @@ export function applyChatFrame(messages, frame, assistantId) {
117
119
  if (frame.type === "error") {
118
120
  return { messages, terminal: { error: frame } };
119
121
  }
120
- if (frame.type === "iteration")
122
+ if (frame.type === "iteration" || frame.type === "message-start")
121
123
  return { messages, terminal: null };
122
124
  const partFrame = frame;
123
125
  const next = updateAssistant(messages, assistantId, (parts) => applyPartFrame(parts, partFrame));
@@ -157,9 +159,11 @@ function applyPartFrame(parts, frame) {
157
159
  return started ? next : [...parts, finished];
158
160
  }
159
161
  default: {
162
+ // A chat event type that no case above takes is not a part frame, thus this annotation fails to compile.
163
+ const partFrame = frame;
160
164
  // The source routes the frame, and it is not a field of the part. A rest type does not distribute over the
161
165
  // union of the part frames, thus the cast names the part that remains.
162
- const { source: _source, ...part } = frame;
166
+ const { source: _source, ...part } = partFrame;
163
167
  return upsertPart(parts, part);
164
168
  }
165
169
  }
@@ -3,7 +3,7 @@ export type { OrganSystem } from "./organ-system.js";
3
3
  export { SEVERITIES, SeveritySchema } from "./severity.js";
4
4
  export type { Severity } from "./severity.js";
5
5
  export type { PlanStep, PresentationContent, PresentationPart, PlanPart, RunCardPart, PlanApprovalPart, FileReferenceEntry, FileReferencePart, AskPart, RunStartedPart, StepStatus, DagStepState, PlanGraphStep, DagStatePart, StepPhase, StepActivityPart, ActivityEntryPart, SubAgentActivityEntry, SubAgentActivityPart, FileTreeEntry, StepFileTreePart, StepOutputFile, StepOutputPart, StepSummaryPart, StepUsagePart, StepBlockedPart, FindingReference, SynthesizedFinding, BiologicalTheme, RunSynthesisPart, SynthesisPhase, SynthesisProgressPart, RunCompletedFinding, RunCompletedPart, RunFailedPart, ChildSessionStartedPart, ReportRenderedPart, CompactionPart, ChatPart, } from "./chat-parts.js";
6
- export type { EventSource, TextDeltaEvent, IterationEvent, ToolStartedEvent, ToolFinishedEvent, FinishEvent, ChatErrorEvent, ChatEvent, ChatFrame, ChatPartFrame, } from "./chat-events.js";
6
+ export type { EventSource, TextDeltaEvent, IterationEvent, ToolStartedEvent, ToolFinishedEvent, FinishEvent, ChatErrorEvent, MessageStartEvent, ChatEvent, ChatFrame, ChatPartFrame, } from "./chat-events.js";
7
7
  export type { TokenUsageRollup } from "./usage.js";
8
8
  export { DATA_PROFILE_RUN_LITERAL } from "./data-profile.js";
9
9
  export type { DataProfileAxis, DataProfileChecked, DataProfileCompanionCompleteness, DataProfileCoverage, DataProfileDimension, DataProfileFile, DataProfileGroup, DataProfileGroupSlot, DataProfileInputSignature, DataProfileKind, DataProfileLifecycleStatus, DataProfileMemberAnnotation, DataProfileObservation, DataProfileOrganism, DataProfilePartition, DataProfileProbeReport, DataProfileQualityAssessment, DataProfileQuarantine, DataProfileRecipeStep, DataProfileResult, DataProfileSubjectSource, } from "./data-profile.js";
@@ -97,6 +97,19 @@ export declare const ChatErrorEventSchema: z.ZodObject<{
97
97
  invocationId: z.ZodOptional<z.ZodString>;
98
98
  }, z.core.$strip>;
99
99
  }, z.core.$strip>;
100
+ export declare const MessageStartEventSchema: z.ZodObject<{
101
+ type: z.ZodLiteral<"message-start">;
102
+ messageId: z.ZodUUID;
103
+ role: z.ZodEnum<{
104
+ assistant: "assistant";
105
+ user: "user";
106
+ }>;
107
+ source: z.ZodObject<{
108
+ agentId: z.ZodString;
109
+ callPath: z.ZodArray<z.ZodString>;
110
+ invocationId: z.ZodOptional<z.ZodString>;
111
+ }, z.core.$strip>;
112
+ }, z.core.$strip>;
100
113
  export declare const ChatEventSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
101
114
  type: z.ZodLiteral<"text-delta">;
102
115
  text: z.ZodString;
@@ -168,4 +181,16 @@ export declare const ChatEventSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
168
181
  callPath: z.ZodArray<z.ZodString>;
169
182
  invocationId: z.ZodOptional<z.ZodString>;
170
183
  }, z.core.$strip>;
184
+ }, z.core.$strip>, z.ZodObject<{
185
+ type: z.ZodLiteral<"message-start">;
186
+ messageId: z.ZodUUID;
187
+ role: z.ZodEnum<{
188
+ assistant: "assistant";
189
+ user: "user";
190
+ }>;
191
+ source: z.ZodObject<{
192
+ agentId: z.ZodString;
193
+ callPath: z.ZodArray<z.ZodString>;
194
+ invocationId: z.ZodOptional<z.ZodString>;
195
+ }, z.core.$strip>;
171
196
  }, z.core.$strip>], "type">;
@@ -50,6 +50,12 @@ export const ChatErrorEventSchema = z.object({
50
50
  reason: z.string().optional(),
51
51
  source: EventSourceSchema,
52
52
  });
53
+ export const MessageStartEventSchema = z.object({
54
+ type: z.literal("message-start"),
55
+ messageId: z.uuid(),
56
+ role: z.enum(["user", "assistant"]),
57
+ source: EventSourceSchema,
58
+ });
53
59
  export const ChatEventSchema = z.discriminatedUnion("type", [
54
60
  TextDeltaEventSchema,
55
61
  IterationEventSchema,
@@ -57,4 +63,5 @@ export const ChatEventSchema = z.discriminatedUnion("type", [
57
63
  ToolFinishedEventSchema,
58
64
  FinishEventSchema,
59
65
  ChatErrorEventSchema,
66
+ MessageStartEventSchema,
60
67
  ]);