@inflexa-ai/harness 0.46.2 → 0.47.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (271) hide show
  1. package/README.md +1 -1
  2. package/dist/agents/conversation-agent.d.ts +3 -8
  3. package/dist/agents/conversation-agent.js +24 -4
  4. package/dist/agents/report-session-agent.d.ts +11 -14
  5. package/dist/agents/report-session-agent.js +30 -17
  6. package/dist/agents/sandbox/shared.d.ts +18 -13
  7. package/dist/agents/sandbox/shared.js +42 -27
  8. package/dist/agents/sandbox/types.d.ts +1 -1
  9. package/dist/agents/sandbox-catalog.d.ts +2 -0
  10. package/dist/agents/sandbox-catalog.js +30 -1
  11. package/dist/app/synthesize-run.d.ts +10 -6
  12. package/dist/app/synthesize-run.js +4 -2
  13. package/dist/contracts/analogy-report.d.ts +46 -2
  14. package/dist/contracts/analogy-report.js +31 -25
  15. package/dist/contracts/chat-parts.d.ts +3 -14
  16. package/dist/contracts/report-reference.d.ts +2 -1
  17. package/dist/contracts/report-reference.js +2 -1
  18. package/dist/contracts/schemas/chat-parts.d.ts +0 -27
  19. package/dist/contracts/schemas/chat-parts.js +1 -3
  20. package/dist/execution/run-canceler.d.ts +2 -0
  21. package/dist/execution/run-canceler.js +9 -0
  22. package/dist/execution/run-synthesis.d.ts +36 -8
  23. package/dist/execution/run-synthesis.js +114 -32
  24. package/dist/index.d.ts +2 -5
  25. package/dist/index.js +3 -6
  26. package/dist/input-scan/enrich.d.ts +5 -10
  27. package/dist/input-scan/enrich.js +12 -8
  28. package/dist/lib/markdown-pdf.d.ts +27 -0
  29. package/dist/lib/markdown-pdf.js +236 -0
  30. package/dist/lib/metrics.d.ts +31 -0
  31. package/dist/lib/metrics.js +64 -0
  32. package/dist/lib/pdf-text.d.ts +23 -0
  33. package/dist/lib/pdf-text.js +34 -0
  34. package/dist/lib/regulatory-corpus/fetch-fda-guidance.js +3 -5
  35. package/dist/lib/regulatory-corpus/fetch-ich.js +3 -5
  36. package/dist/loop/run-loop.d.ts +5 -0
  37. package/dist/loop/run-loop.js +30 -2
  38. package/dist/loop/tool-name-repair.d.ts +11 -0
  39. package/dist/loop/tool-name-repair.js +23 -0
  40. package/dist/loop/types.d.ts +14 -0
  41. package/dist/memory/card-builders.d.ts +1 -1
  42. package/dist/prompts/SOUL.d.ts +6 -6
  43. package/dist/prompts/SOUL.js +22 -121
  44. package/dist/prompts/analogical-reasoner.d.ts +1 -1
  45. package/dist/prompts/analogical-reasoner.js +35 -101
  46. package/dist/prompts/briefing.d.ts +20 -11
  47. package/dist/prompts/briefing.js +36 -19
  48. package/dist/prompts/conversation.d.ts +1 -1
  49. package/dist/prompts/conversation.js +103 -383
  50. package/dist/prompts/planner.d.ts +1 -5
  51. package/dist/prompts/planner.js +45 -128
  52. package/dist/prompts/report-session.d.ts +1 -1
  53. package/dist/prompts/report-session.js +26 -57
  54. package/dist/prompts/sandbox/bulk-transcriptomics-agent.d.ts +1 -1
  55. package/dist/prompts/sandbox/bulk-transcriptomics-agent.js +3 -3
  56. package/dist/prompts/sandbox/cheminformatics-agent.d.ts +1 -1
  57. package/dist/prompts/sandbox/cheminformatics-agent.js +12 -47
  58. package/dist/prompts/sandbox/chromatin-agent.d.ts +1 -1
  59. package/dist/prompts/sandbox/chromatin-agent.js +4 -5
  60. package/dist/prompts/sandbox/data-profiler.js +11 -34
  61. package/dist/prompts/sandbox/dna-methylation-agent.d.ts +1 -1
  62. package/dist/prompts/sandbox/dna-methylation-agent.js +3 -1
  63. package/dist/prompts/sandbox/drug-repurposing-agent.d.ts +1 -1
  64. package/dist/prompts/sandbox/drug-repurposing-agent.js +23 -73
  65. package/dist/prompts/sandbox/enrichment-agent.d.ts +1 -1
  66. package/dist/prompts/sandbox/enrichment-agent.js +3 -1
  67. package/dist/prompts/sandbox/genomic-variant-agent.d.ts +1 -1
  68. package/dist/prompts/sandbox/genomic-variant-agent.js +3 -4
  69. package/dist/prompts/sandbox/immune-profiling-agent.d.ts +1 -1
  70. package/dist/prompts/sandbox/immune-profiling-agent.js +5 -4
  71. package/dist/prompts/sandbox/metabolomics-agent.d.ts +1 -1
  72. package/dist/prompts/sandbox/metabolomics-agent.js +3 -1
  73. package/dist/prompts/sandbox/microbiome-agent.d.ts +1 -1
  74. package/dist/prompts/sandbox/microbiome-agent.js +3 -1
  75. package/dist/prompts/sandbox/multi-omics-integration-agent.d.ts +1 -1
  76. package/dist/prompts/sandbox/multi-omics-integration-agent.js +3 -1
  77. package/dist/prompts/sandbox/multimodal-sc-agent.d.ts +1 -1
  78. package/dist/prompts/sandbox/multimodal-sc-agent.js +3 -1
  79. package/dist/prompts/sandbox/network-agent.d.ts +1 -1
  80. package/dist/prompts/sandbox/network-agent.js +3 -1
  81. package/dist/prompts/sandbox/pkpd-clinical-response-agent.d.ts +1 -1
  82. package/dist/prompts/sandbox/pkpd-clinical-response-agent.js +4 -7
  83. package/dist/prompts/sandbox/proteomics-agent.d.ts +1 -1
  84. package/dist/prompts/sandbox/proteomics-agent.js +3 -1
  85. package/dist/prompts/sandbox/scientific-executor.d.ts +1 -1
  86. package/dist/prompts/sandbox/scientific-executor.js +0 -12
  87. package/dist/prompts/sandbox/single-cell-agent.d.ts +1 -1
  88. package/dist/prompts/sandbox/single-cell-agent.js +3 -1
  89. package/dist/prompts/sandbox/spatial-omics-agent.d.ts +1 -1
  90. package/dist/prompts/sandbox/spatial-omics-agent.js +3 -1
  91. package/dist/prompts/sandbox/statistical-modeling-agent.d.ts +1 -1
  92. package/dist/prompts/sandbox/statistical-modeling-agent.js +4 -4
  93. package/dist/prompts/sandbox/translational-safety-agent.d.ts +1 -1
  94. package/dist/prompts/sandbox/translational-safety-agent.js +10 -29
  95. package/dist/prompts/sandbox-standards.d.ts +15 -18
  96. package/dist/prompts/sandbox-standards.js +96 -305
  97. package/dist/prompts/synthesis-agent.d.ts +1 -1
  98. package/dist/prompts/synthesis-agent.js +24 -90
  99. package/dist/provenance/collector.d.ts +2 -5
  100. package/dist/provenance/collector.js +2 -5
  101. package/dist/provenance/seam.d.ts +36 -1
  102. package/dist/provenance/workspace-reads.d.ts +36 -0
  103. package/dist/provenance/workspace-reads.js +33 -0
  104. package/dist/report-model/production-resolver.d.ts +22 -52
  105. package/dist/report-model/production-resolver.js +43 -112
  106. package/dist/report-render/chart-export.d.ts +9 -0
  107. package/dist/report-render/chart-export.js +1 -1
  108. package/dist/report-render/views/chart-view.d.ts +1 -0
  109. package/dist/report-render/views/chart-view.js +1 -0
  110. package/dist/report-render/views/lineage.d.ts +1 -0
  111. package/dist/report-render/views/page-view.d.ts +1 -0
  112. package/dist/report-render/views/page-view.js +1 -0
  113. package/dist/report-render/views/prose.d.ts +1 -0
  114. package/dist/report-render/views/prose.js +1 -0
  115. package/dist/report-render/views/references-view.d.ts +1 -0
  116. package/dist/report-render/views/values.d.ts +1 -0
  117. package/dist/report-render/views/values.js +1 -0
  118. package/dist/runtime/assemble.d.ts +20 -9
  119. package/dist/runtime/assemble.js +47 -38
  120. package/dist/sandbox/client.d.ts +5 -3
  121. package/dist/sandbox/create-sandbox.d.ts +7 -12
  122. package/dist/sandbox/create-sandbox.js +15 -37
  123. package/dist/sandbox/docker-client.d.ts +1 -3
  124. package/dist/sandbox/docker-client.js +3 -5
  125. package/dist/sandbox/exec-outcome.d.ts +11 -11
  126. package/dist/sandbox/exec-outcome.js +22 -22
  127. package/dist/sandbox/exec.d.ts +5 -0
  128. package/dist/sandbox/exec.js +9 -1
  129. package/dist/sandbox/identity.d.ts +4 -16
  130. package/dist/sandbox/identity.js +5 -21
  131. package/dist/sandbox/k8s-client.d.ts +0 -1
  132. package/dist/sandbox/k8s-client.js +8 -13
  133. package/dist/sandbox/lease-names.d.ts +6 -0
  134. package/dist/sandbox/lease-names.js +12 -0
  135. package/dist/sandbox/leases.d.ts +105 -0
  136. package/dist/sandbox/leases.js +519 -0
  137. package/dist/sandbox/liveness.d.ts +5 -2
  138. package/dist/sandbox/mount-plan.d.ts +10 -1
  139. package/dist/sandbox/mount-plan.js +7 -1
  140. package/dist/sandbox/reconciler.d.ts +58 -0
  141. package/dist/sandbox/reconciler.js +153 -0
  142. package/dist/sandbox/sandbox-step-translate.d.ts +1 -1
  143. package/dist/sandbox/types.d.ts +25 -18
  144. package/dist/sandbox/types.js +10 -4
  145. package/dist/schemas/plan-schemas.d.ts +0 -21
  146. package/dist/schemas/plan-schemas.js +2 -3
  147. package/dist/schemas/run-synthesis.d.ts +2 -2
  148. package/dist/schemas/run-synthesis.js +3 -3
  149. package/dist/schemas/validate-plan.d.ts +1 -14
  150. package/dist/schemas/validate-plan.js +5 -24
  151. package/dist/schemas/workflow-state.d.ts +3 -32
  152. package/dist/schemas/workflow-state.js +4 -13
  153. package/dist/state/index.d.ts +0 -1
  154. package/dist/state/index.js +0 -1
  155. package/dist/state/migrations/20261030120000_lease_the_sandboxes.d.ts +7 -0
  156. package/dist/state/migrations/20261030120000_lease_the_sandboxes.js +34 -0
  157. package/dist/state/migrations/20261030120100_drop_the_report_derivations.d.ts +5 -0
  158. package/dist/state/migrations/20261030120100_drop_the_report_derivations.js +9 -0
  159. package/dist/state/migrations/20261030120200_keep_the_report_derivations.d.ts +6 -0
  160. package/dist/state/migrations/20261030120200_keep_the_report_derivations.js +10 -0
  161. package/dist/state/migrations/index.js +6 -0
  162. package/dist/state/purge-analysis.d.ts +4 -2
  163. package/dist/state/purge-analysis.js +22 -11
  164. package/dist/state/sandbox-leases.d.ts +81 -0
  165. package/dist/state/sandbox-leases.js +247 -0
  166. package/dist/state/schema.d.ts +1 -13
  167. package/dist/state/schema.js +1 -6
  168. package/dist/state/step-executions.d.ts +0 -2
  169. package/dist/state/step-executions.js +1 -4
  170. package/dist/tasks/data-profile.d.ts +40 -2
  171. package/dist/tasks/data-profile.js +89 -87
  172. package/dist/tools/ad-hoc-router.d.ts +1 -6
  173. package/dist/tools/ad-hoc-router.js +5 -48
  174. package/dist/tools/bio/comptox.js +1 -1
  175. package/dist/tools/bio/drug-gene-interactions.js +1 -1
  176. package/dist/tools/bio/gene-disease-evidence.js +1 -1
  177. package/dist/tools/bio/keys.d.ts +1 -1
  178. package/dist/tools/bio/pubmed.js +31 -0
  179. package/dist/tools/bio/search-gene.js +17 -0
  180. package/dist/tools/bio/search-geo-datasets.js +13 -0
  181. package/dist/tools/bio/search-regulatory-approvals.js +1 -1
  182. package/dist/tools/define-tool.d.ts +25 -2
  183. package/dist/tools/define-tool.js +11 -1
  184. package/dist/tools/display/show-user.js +1 -1
  185. package/dist/tools/execute-analysis.d.ts +1 -3
  186. package/dist/tools/execute-analysis.js +5 -33
  187. package/dist/tools/report-session/index.d.ts +0 -1
  188. package/dist/tools/report-session/index.js +0 -1
  189. package/dist/tools/report-session/script-tools.d.ts +30 -0
  190. package/dist/tools/report-session/script-tools.js +177 -0
  191. package/dist/tools/research/generate-analogy-report.d.ts +16 -9
  192. package/dist/tools/research/generate-analogy-report.js +111 -49
  193. package/dist/tools/research/generate-plan.d.ts +102 -7
  194. package/dist/tools/research/generate-plan.js +74 -43
  195. package/dist/tools/research/index.d.ts +4 -3
  196. package/dist/tools/research/index.js +4 -3
  197. package/dist/tools/research/inspect-run.d.ts +0 -1
  198. package/dist/tools/research/inspect-run.js +5 -10
  199. package/dist/tools/research/search-arxiv.js +15 -0
  200. package/dist/tools/research/search-github-repos.js +13 -0
  201. package/dist/tools/research/search-semantic-scholar.js +16 -0
  202. package/dist/tools/research/synthesis-lookups.d.ts +21 -0
  203. package/dist/tools/research/synthesis-lookups.js +29 -0
  204. package/dist/tools/research/web-fetch.d.ts +1 -1
  205. package/dist/tools/research/web-fetch.js +14 -12
  206. package/dist/tools/sandbox/lease-tools.d.ts +32 -0
  207. package/dist/tools/sandbox/lease-tools.js +60 -0
  208. package/dist/tools/script/codemode-worker.d.ts +5 -0
  209. package/dist/tools/script/codemode-worker.js +5 -0
  210. package/dist/tools/script/file-tools.d.ts +100 -0
  211. package/dist/tools/script/file-tools.js +377 -0
  212. package/dist/tools/script/metrics.d.ts +21 -0
  213. package/dist/tools/script/metrics.js +38 -0
  214. package/dist/tools/script/quickjs.wasm +0 -0
  215. package/dist/tools/script/run-script.d.ts +76 -0
  216. package/dist/tools/script/run-script.js +326 -0
  217. package/dist/tools/script/scratch.d.ts +81 -0
  218. package/dist/tools/script/scratch.js +142 -0
  219. package/dist/tools/start-report-session.js +1 -1
  220. package/dist/tools/workspace/edit-file.js +2 -1
  221. package/dist/tools/workspace/execute-command.d.ts +9 -13
  222. package/dist/tools/workspace/execute-command.js +23 -19
  223. package/dist/tools/workspace/file-stat.js +1 -1
  224. package/dist/tools/workspace/grep.d.ts +5 -2
  225. package/dist/tools/workspace/grep.js +19 -10
  226. package/dist/tools/workspace/mutator.d.ts +48 -4
  227. package/dist/tools/workspace/mutator.js +43 -12
  228. package/dist/tools/workspace/read-file.d.ts +5 -2
  229. package/dist/tools/workspace/read-file.js +15 -11
  230. package/dist/tools/workspace/read-surface.d.ts +17 -0
  231. package/dist/tools/workspace/read-surface.js +17 -0
  232. package/dist/tools/workspace/result-bounds.d.ts +1 -0
  233. package/dist/tools/workspace/run-exec.d.ts +12 -13
  234. package/dist/tools/workspace/run-exec.js +13 -13
  235. package/dist/tools/workspace/scan-inputs.d.ts +2 -5
  236. package/dist/tools/workspace/scan-inputs.js +2 -3
  237. package/dist/tools/workspace/show-file.js +1 -1
  238. package/dist/tools/workspace/show-plan.js +4 -3
  239. package/dist/tools/workspace/write-file.js +2 -1
  240. package/dist/workflows/execute-analysis-scheduler.d.ts +0 -26
  241. package/dist/workflows/execute-analysis-scheduler.js +12 -32
  242. package/dist/workflows/execute-analysis.d.ts +17 -35
  243. package/dist/workflows/execute-analysis.js +13 -107
  244. package/dist/workflows/pause.js +29 -22
  245. package/dist/workflows/sandbox-step.d.ts +16 -31
  246. package/dist/workflows/sandbox-step.js +36 -71
  247. package/dist/workflows/test/sql-writes.d.ts +0 -1
  248. package/dist/workflows/test/sql-writes.js +0 -2
  249. package/dist/workspace/filesystem.d.ts +2 -2
  250. package/dist/workspace/filesystem.js +10 -5
  251. package/dist/workspace/paths.d.ts +19 -1
  252. package/dist/workspace/paths.js +22 -1
  253. package/package.json +6 -3
  254. package/dist/prompts/literature-reviewer.d.ts +0 -1
  255. package/dist/prompts/literature-reviewer.js +0 -114
  256. package/dist/sandbox/reaper.d.ts +0 -81
  257. package/dist/sandbox/reaper.js +0 -161
  258. package/dist/sandbox/step-sandbox.d.ts +0 -47
  259. package/dist/sandbox/step-sandbox.js +0 -53
  260. package/dist/state/active-sandboxes.d.ts +0 -33
  261. package/dist/state/active-sandboxes.js +0 -65
  262. package/dist/tasks/extract-values-script.d.ts +0 -64
  263. package/dist/tasks/extract-values-script.js +0 -180
  264. package/dist/tasks/extract-values.d.ts +0 -77
  265. package/dist/tasks/extract-values.js +0 -146
  266. package/dist/tools/report-session/derive-table.d.ts +0 -164
  267. package/dist/tools/report-session/derive-table.js +0 -419
  268. package/dist/tools/research/literature-reviewer.d.ts +0 -42
  269. package/dist/tools/research/literature-reviewer.js +0 -107
  270. package/dist/workflows/metrics.d.ts +0 -23
  271. package/dist/workflows/metrics.js +0 -38
@@ -1,7 +1,5 @@
1
1
  /**
2
2
  * `cortex_step_executions` operations — step lifecycle and retry telemetry.
3
- * The `sandbox_ref` column is read/written by the sibling `active-sandboxes`
4
- * module.
5
3
  */
6
4
  import { tryMutation, tryQuery } from "../lib/db-result.js";
7
5
  import { StepExecutionRowSchema } from "./schema.js";
@@ -165,7 +163,7 @@ export function queryStepsByRun(pool, runId) {
165
163
  text: `SELECT run_id, step_id, analysis_id, wave, agent_id,
166
164
  status, started_at, completed_at, duration_ms, error,
167
165
  attempts, last_error_class, finish_reason, hit_max_steps,
168
- blocked_reason, sandbox_ref, child_workflow_id
166
+ blocked_reason, child_workflow_id
169
167
  FROM cortex_step_executions
170
168
  WHERE run_id = $1
171
169
  ORDER BY wave, started_at NULLS LAST, step_id`,
@@ -192,7 +190,6 @@ function mapStepExecutionRow(row) {
192
190
  finishReason: row.finish_reason ?? null,
193
191
  hitMaxSteps: row.hit_max_steps === 1 || row.hit_max_steps === true,
194
192
  blockedReason: row.blocked_reason ?? null,
195
- sandboxRef: row.sandbox_ref ?? null,
196
193
  childWorkflowId: row.child_workflow_id ?? null,
197
194
  });
198
195
  }
@@ -17,6 +17,7 @@
17
17
  * triggers dedup via the ledger CAS (only one `tryStart/tryRerun/tryRetry`
18
18
  * UPDATE wins); each winning attempt starts a workflow under a per-attempt id.
19
19
  */
20
+ import type { ScriptRuntime } from "../tools/script/run-script.js";
20
21
  import { ResultAsync } from "neverthrow";
21
22
  import { type AuthContext, type RunSession } from "../auth/types.js";
22
23
  import type { EnvironmentStorePaths } from "../config/environment-stores.js";
@@ -32,13 +33,46 @@ import type { Querier } from "../state/db.js";
32
33
  import { type GateRefusal, type NoticeFailure } from "../lib/hooks.js";
33
34
  import type { Logger } from "../lib/logger.js";
34
35
  import type { UsageRecorder } from "../billing/usage-recorder.js";
36
+ import { type Tool } from "../tools/define-tool.js";
35
37
  import type { ChatProvider, EmbeddingProvider } from "../providers/types.js";
36
- import type { SandboxClient } from "../sandbox/client.js";
38
+ import type { ResourceSpec } from "../config/resource-limits.js";
39
+ import type { SandboxLeases } from "../sandbox/leases.js";
37
40
  import type { WorkspaceFilesystem } from "../workspace/filesystem.js";
38
41
  import { type ResolveWorkspaceRoot } from "../workspace/paths.js";
42
+ import { z } from "zod";
39
43
  import { type ProfileSubmission } from "../schemas/data-profile-schemas.js";
44
+ import { type SetMenu } from "../input-scan/menu.js";
45
+ import type { DetectedSets } from "../input-scan/set-types.js";
46
+ import { type AbsorbOutcome } from "./data-profile-absorb.js";
40
47
  import { type ProfileResolution } from "./data-profile-resolve.js";
41
48
  import { type DataProfileResult } from "../state/index.js";
49
+ /** What one call of `submit_profile` tells the agent. */
50
+ declare const SubmitOutcomeSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
51
+ status: z.ZodLiteral<"needs_repair">;
52
+ detail: z.ZodString;
53
+ }, z.core.$strip>, z.ZodObject<{
54
+ status: z.ZodLiteral<"accepted">;
55
+ groups: z.ZodNumber;
56
+ keptFiles: z.ZodNumber;
57
+ unclassifiedFiles: z.ZodNumber;
58
+ unresolvedOperations: z.ZodNumber;
59
+ }, z.core.$strip>], "status">;
60
+ type SubmitOutcome = z.infer<typeof SubmitOutcomeSchema>;
61
+ /**
62
+ * The first message of the profiler. No scheduler composes a briefing for it, thus this message is its briefing: the
63
+ * system prompt names no paths, because it is static for each agent type. The workspace frame and the resource budget
64
+ * use the same words as the step briefing.
65
+ */
66
+ export declare function composeProfilerSeed(args: {
67
+ readonly analysisId: string;
68
+ /** The in-sandbox working directory of the profiler. */
69
+ readonly workingDir: string;
70
+ readonly resources: ResourceSpec;
71
+ readonly menu: SetMenu;
72
+ readonly absorb: AbsorbOutcome;
73
+ }): string;
74
+ /** The terminal tool of the profiler: it resolves the operations of a submission against the scan and the menu. */
75
+ export declare function createSubmitProfileTool(detected: DetectedSets, menu: SetMenu): Tool<ProfileSubmission, SubmitOutcome>;
42
76
  /** The settle of one profile claim: the ledger row left `running`, and the terminal write stamped it. */
43
77
  export type DataProfileSettlement = {
44
78
  readonly analysisId: string;
@@ -54,7 +88,8 @@ export type DataProfileDeps = EnvironmentStorePaths & {
54
88
  readonly logger?: Logger;
55
89
  readonly provider: ChatProvider;
56
90
  readonly pool: Querier;
57
- readonly sandboxClient: SandboxClient;
91
+ /** The sandbox module. The profile acquires its lease from it, and its agent runs commands on that lease. */
92
+ readonly leases: SandboxLeases;
58
93
  readonly workspaceFs: WorkspaceFilesystem;
59
94
  /** Workspace-root resolution seam (see workspace/paths.ts). */
60
95
  readonly resolveWorkspaceRoot: ResolveWorkspaceRoot;
@@ -90,6 +125,8 @@ export type DataProfileDeps = EnvironmentStorePaths & {
90
125
  readonly notifyDataProfileSettled?: NotifyDataProfileSettled;
91
126
  /** The delays of the timer of an outage. Absent: the default of the pause. */
92
127
  readonly pauseRetryDelaysMs?: readonly number[];
128
+ /** Where the VM of `run_script` comes from, for the lookups of the profiler. */
129
+ readonly scriptRuntime?: ScriptRuntime;
93
130
  };
94
131
  /**
95
132
  * Workflow input. JSON-serialisable — DBOS persists it as the workflow's
@@ -270,3 +307,4 @@ export type DataProfileStopDeps = {
270
307
  * teardown of the sandbox stay in the body, which runs them on its way out. The `running` guard of the row write keeps a profile that settled first.
271
308
  */
272
309
  export declare function stopDataProfile(deps: DataProfileStopDeps, analysisId: string, workflowId: string): Promise<void>;
310
+ export {};
@@ -27,21 +27,19 @@ import { forStep, forSubAgent } from "../auth/types.js";
27
27
  import { presentFirst } from "../lib/present.js";
28
28
  import { collapseWhitespace } from "../lib/text.js";
29
29
  import { createDataProfilerAgent } from "../agents/sandbox/data-profiler.js";
30
- import { runWithSalvage } from "../loop/run-loop.js";
30
+ import { runWithSalvage, withRunScript } from "../loop/run-loop.js";
31
31
  import { recordedInputOf } from "../loop/tool-adapter.js";
32
32
  import { createNoopLogger } from "../lib/console-logger.js";
33
33
  import { deliverNotice, passGate } from "../lib/hooks.js";
34
34
  import { unwrapOrThrow } from "../lib/result.js";
35
35
  import { defineTool } from "../tools/define-tool.js";
36
36
  import { createDetailResolver } from "../tools/detail-resolver.js";
37
- import { renderWorkspace } from "../prompts/briefing.js";
38
- import { keepSuspendingRefusal, SandboxFailure } from "../sandbox/sandbox-error.js";
37
+ import { renderResources, renderWorkspace } from "../prompts/briefing.js";
39
38
  import { workPause } from "../workflows/pause.js";
40
39
  import { clearPausesOfWork } from "../state/paused-work.js";
41
- import { toSandboxPath } from "../workspace/paths.js";
40
+ import { STAGED_INPUT_ROOT, toSandboxPath } from "../workspace/paths.js";
42
41
  import { estimateDataProfileResources } from "../sandbox/estimate-data-profile-resources.js";
43
42
  import { generateExecutionId } from "../sandbox/execution-id.js";
44
- import { mintSandboxIdentity } from "../sandbox/identity.js";
45
43
  import { createVectorStore } from "../state/vector-store.js";
46
44
  import { z } from "zod";
47
45
  import { ProfileSubmissionSchema } from "../schemas/data-profile-schemas.js";
@@ -63,12 +61,6 @@ import { ensureSearchIndex, searchIndexName } from "../workspace/search-config.j
63
61
  import { DATA_PROFILE_RUN_LITERAL } from "../contracts/data-profile.js";
64
62
  const DATA_PROFILE_STEP_LITERAL = "profile";
65
63
  const DATA_PROFILE_AGENT_ID = "data-profiler";
66
- /**
67
- * Sandbox-server exec budget for the profile run. Twenty minutes: the deterministic
68
- * scan bounds the discovery half, and what remains is the agent's own reading of a
69
- * bounded number of example files.
70
- */
71
- const DEFAULT_DEADLINE_MS = 1_200_000;
72
64
  /** Index entries embedded and upserted per round trip. */
73
65
  const INDEX_BATCH_SIZE = 256;
74
66
  const SUBMIT_PROFILE_TOOL_ID = "submit_profile";
@@ -83,6 +75,56 @@ const SubmitOutcomeSchema = z.discriminatedUnion("status", [
83
75
  unresolvedOperations: z.number(),
84
76
  }),
85
77
  ]);
78
+ /**
79
+ * The first message of the profiler. No scheduler composes a briefing for it, thus this message is its briefing: the
80
+ * system prompt names no paths, because it is static for each agent type. The workspace frame and the resource budget
81
+ * use the same words as the step briefing.
82
+ */
83
+ export function composeProfilerSeed(args) {
84
+ return [
85
+ `Profile the input data for this analysis: say what the dataset IS, so planning can proceed.`,
86
+ ``,
87
+ renderWorkspace({ analysisId: args.analysisId, workingDir: args.workingDir }),
88
+ ``,
89
+ renderResources(args.resources),
90
+ ``,
91
+ renderSetMenu(args.menu, STAGED_INPUT_ROOT),
92
+ ``,
93
+ ...(args.absorb.kind === "partial"
94
+ ? [renderAbsorbDelta(args.absorb), ``]
95
+ : [
96
+ `Author the dataset's groups as operations on this menu: \`use\` a set, \`split\` one by a slot or by a`,
97
+ `value mapping, \`merge\` several, or \`group\` explicit paths the scan left over. Membership and counts`,
98
+ `are computed from your operations — you do not state them. Inspect ONE example file per group where a`,
99
+ `description needs content the scan did not capture, then deliver the groups with \`submit_profile\`.`,
100
+ ]),
101
+ ].join("\n");
102
+ }
103
+ /** The terminal tool of the profiler: it resolves the operations of a submission against the scan and the menu. */
104
+ export function createSubmitProfileTool(detected, menu) {
105
+ return defineTool({
106
+ id: SUBMIT_PROFILE_TOOL_ID,
107
+ description: "Submit the profiling results. Call this tool after completing all profiling work — it resolves your " +
108
+ "operations against the scan, computes every group's membership and counts, and checks the profile. " +
109
+ "A submission whose operations do not resolve, or that leaves kept files unclaimed, comes back with " +
110
+ "the errors; the repair is a FULL resubmit of the whole operation list. The last accepted submission is the profile.",
111
+ inputSchema: ProfileSubmissionSchema,
112
+ describeCall: "none",
113
+ execute: (input) => {
114
+ const resolution = resolveProfileSubmission(input, detected, menu, { finalRound: false });
115
+ if (resolution.errors.length > 0 || resolution.unclaimed.length > 0) {
116
+ return Promise.resolve(ok({ status: "needs_repair", detail: formatResolutionErrors(resolution) }));
117
+ }
118
+ return Promise.resolve(ok({
119
+ status: "accepted",
120
+ groups: resolution.groups.length,
121
+ keptFiles: resolution.partition.keptFiles,
122
+ unclassifiedFiles: resolution.partition.unclassifiedFiles,
123
+ unresolvedOperations: resolution.errors.length,
124
+ }));
125
+ },
126
+ });
127
+ }
86
128
  /** The submissions of a run, in call order, with the outcome that the tool gave each one. */
87
129
  function profileSubmissions(steps) {
88
130
  const submissions = [];
@@ -110,8 +152,6 @@ function selectedSubmission(steps) {
110
152
  const chosen = submissions.filter((entry) => entry.accepted).at(-1) ?? submissions.at(-1);
111
153
  return chosen === undefined ? undefined : { ...chosen, count: submissions.length };
112
154
  }
113
- /** Where the embedder stages this analysis's inputs (see the data-profile-init spec). */
114
- const STAGED_INPUT_ROOT = "data/inputs";
115
155
  /** Build the canonical artifact path for a staged input file. */
116
156
  function inputArtifactPath(f) {
117
157
  return `data/${f.relativePath}`;
@@ -164,16 +204,14 @@ export function buildDataProfileResult(submission, resolution, stagedInputs, pro
164
204
  * rests on, so failing the profile over it would trade the whole capability for a nicety.
165
205
  */
166
206
  async function readSetHeaders(args) {
167
- const { session, deps, analysisId, detected, sandbox, deadlineMs, logger } = args;
207
+ const { session, deps, analysisId, detected, lease, logger } = args;
168
208
  try {
169
209
  return await readHeaders({
170
210
  targets: readoutTargets(detected),
171
211
  session,
172
212
  fs: deps.workspaceFs,
173
- sandboxClient: deps.sandboxClient,
174
- sandbox,
213
+ lease,
175
214
  mountRoot: `/${analysisId}`,
176
- deadlineMs,
177
215
  emit: async () => { },
178
216
  });
179
217
  }
@@ -298,7 +336,7 @@ export function profilerSandboxAgentDeps(deps, step) {
298
336
  return {
299
337
  provider: deps.provider,
300
338
  pool: deps.pool,
301
- sandboxClient: deps.sandboxClient,
339
+ toolchainSource: deps.leases.toolchainSource,
302
340
  workspaceFs: deps.workspaceFs,
303
341
  embedding: deps.embedding,
304
342
  model: deps.model,
@@ -309,6 +347,7 @@ export function profilerSandboxAgentDeps(deps, step) {
309
347
  extendAnalysisFarm: deps.extendAnalysisFarm,
310
348
  toolOutputStore: deps.toolOutputStore,
311
349
  bioKeys: deps.bioKeys,
350
+ scriptRuntime: deps.scriptRuntime,
312
351
  step,
313
352
  };
314
353
  }
@@ -458,95 +497,57 @@ export async function runDataProfileBody(input, deps) {
458
497
  // from the agent loop would leave the longest wait of all unreported — the same defect the
459
498
  // run panel's activity readout was built to remove.
460
499
  await activity.sandboxInit();
461
- const spawn = async () => keepSuspendingRefusal(await deps.sandboxClient.createSandbox(forStep(runSession, DATA_PROFILE_STEP_LITERAL), { childWorkflowId: workflowId, resources: estimateDataProfileResources(stagedInputs) }, mintSandboxIdentity(DATA_PROFILE_RUN_LITERAL)));
462
- let spawned = await spawn();
463
- // A refusal of the label gate with the suspend flag waits in a pause, and the spawn runs again after the
464
- // resume. A body that runs outside a workflow cannot wait, thus the refusal fails the profile there.
465
- while (spawned.isErr()) {
466
- const waited = await pause.forCause({ kind: "host", reason: spawned.error.reason }, 0);
467
- if (waited === undefined)
468
- throw new SandboxFailure(spawned.error);
469
- spawned = await spawn();
470
- }
471
- const sandbox = spawned.value;
500
+ // The lease of the profile. A refused spawn with the suspend flag pauses the lease workflow under the
501
+ // profile, and the acquire waits through the pause.
502
+ const lease = deps.leases.bind({
503
+ leaseKey: workflowId,
504
+ session: forStep(runSession, DATA_PROFILE_STEP_LITERAL),
505
+ work: { kind: "profile", id: workflowId },
506
+ emit: (part) => DBOS.writeStream("events", part),
507
+ });
508
+ const acquired = await lease.acquire(estimateDataProfileResources(stagedInputs));
509
+ if (acquired.kind === "refused")
510
+ throw new Error(acquired.reason);
511
+ if (acquired.kind === "failed")
512
+ throw new Error(`the profiler sandbox did not start: ${acquired.detail}`);
513
+ const resources = acquired.size;
472
514
  try {
473
- // Checkpointed clock, not `Date.now()`: one deadline bounds every exec of the
474
- // profile, and a wall-clock deadline would grow on each recovery (see
475
- // sandbox-step.ts, which captures its step deadline the same way).
476
- const deadlineAbs = (await DBOS.now()) + DEFAULT_DEADLINE_MS;
477
515
  const headers = await readSetHeaders({
478
516
  session: childSession,
479
517
  deps,
480
518
  analysisId,
481
519
  detected,
482
- sandbox,
483
- deadlineMs: deadlineAbs,
520
+ lease,
484
521
  logger,
485
522
  });
486
523
  const briefingMenu = { ...menu, headers };
487
524
  // The profiler runs outside `executeAnalysis`, so no scheduler composes a
488
525
  // briefing for it — this prompt IS its briefing, and the sandbox system
489
526
  // prompt names no paths (it is static per agent type, for the prompt cache).
490
- // Its workspace frame therefore has to be stated here, in the same words the
491
- // step briefing uses.
492
- const prompt = [
493
- `Profile the input data for this analysis: say what the dataset IS, so planning can proceed.`,
494
- ``,
495
- renderWorkspace({
496
- analysisId,
497
- workingDir: toSandboxPath(workspaceRoot, analysisId, profileWritePrefix),
498
- }),
499
- ``,
500
- `CRITICAL: File and directory paths may contain spaces. You MUST always double-quote paths in shell commands (e.g. head "/${analysisId}/data/inputs/My Folder/file.csv"). Unquoted paths with spaces will silently break commands.`,
501
- ``,
502
- renderSetMenu(briefingMenu, STAGED_INPUT_ROOT),
503
- ``,
504
- ...(absorb.kind === "partial"
505
- ? [renderAbsorbDelta(absorb), ``]
506
- : [
507
- `Author the dataset's groups as operations on this menu: \`use\` a set, \`split\` one by a slot or by a`,
508
- `value mapping, \`merge\` several, or \`group\` explicit paths the scan left over. Membership and counts`,
509
- `are computed from your operations — you do not state them. Inspect ONE example file per group where a`,
510
- `description needs content the scan did not capture, then call \`submit_profile\` once.`,
511
- ]),
512
- ].join("\n");
527
+ // Its workspace frame and its resource budget therefore have to be stated here,
528
+ // in the same words the step briefing uses.
529
+ const prompt = composeProfilerSeed({
530
+ analysisId,
531
+ workingDir: toSandboxPath(workspaceRoot, analysisId, profileWritePrefix),
532
+ resources,
533
+ menu: briefingMenu,
534
+ absorb,
535
+ });
513
536
  const sandboxAgentDeps = profilerSandboxAgentDeps(deps, {
514
- sandbox,
537
+ lease,
515
538
  workspaceRoot,
516
539
  analysisId,
517
540
  // The profiler writes Python scripts and intermediate artifacts under
518
541
  // the synthetic step path; the post-agent `rm -rf runs/data-profile/`
519
542
  // cleanup wipes them.
520
543
  allowedWritePrefix: profileWritePrefix,
521
- deadlineMs: () => deadlineAbs,
522
- });
523
- const submitProfileTool = defineTool({
524
- id: SUBMIT_PROFILE_TOOL_ID,
525
- description: "Submit the profiling results. Call this tool after completing all profiling work — it resolves your " +
526
- "operations against the scan, computes every group's membership and counts, and checks the profile. " +
527
- "A submission whose operations do not resolve, or that leaves kept files unclaimed, comes back with " +
528
- "the errors; the repair is a FULL resubmit of the whole operation list. The last accepted submission is the profile.",
529
- inputSchema: ProfileSubmissionSchema,
530
- describeCall: "none",
531
- execute: (input) => {
532
- const resolution = resolveProfileSubmission(input, detected, briefingMenu, { finalRound: false });
533
- if (resolution.errors.length > 0 || resolution.unclaimed.length > 0) {
534
- return Promise.resolve(ok({ status: "needs_repair", detail: formatResolutionErrors(resolution) }));
535
- }
536
- return Promise.resolve(ok({
537
- status: "accepted",
538
- groups: resolution.groups.length,
539
- keptFiles: resolution.partition.keptFiles,
540
- unclassifiedFiles: resolution.partition.unclassifiedFiles,
541
- unresolvedOperations: resolution.errors.length,
542
- }));
543
- },
544
544
  });
545
+ const submitProfileTool = createSubmitProfileTool(detected, briefingMenu);
545
546
  const baseAgent = createDataProfilerAgent(sandboxAgentDeps);
546
547
  const agentDef = { ...baseAgent, tools: [...baseAgent.tools, submitProfileTool] };
547
548
  // The profiler's own roster, `submit_profile` included, so every call it
548
549
  // makes is phrased by the tool that was called.
549
- const resolveDetail = createDetailResolver(agentDef.tools, logger);
550
+ const resolveDetail = createDetailResolver([...withRunScript(agentDef).tools, ...(agentDef.scriptTools ?? [])], logger);
550
551
  const signal = new AbortController().signal;
551
552
  // Covers the gap between a ready sandbox and the agent's first tool call, which would
552
553
  // otherwise still read as `Starting sandbox` long after the container was up. The run
@@ -609,15 +610,16 @@ export async function runDataProfileBody(input, deps) {
609
610
  // write above, the revoke — is already behind us, so any failure on the way here reaches
610
611
  // the catch having emitted no terminal at all and reports `failed` alone. Emitted any
611
612
  // earlier, a failing write would produce BOTH terminals and leave the fold's winner
612
- // decided by arrival order. The sandbox teardown below emits nothing for the same reason.
613
+ // decided by arrival order. The release below emits nothing for the same reason.
613
614
  await activity.complete();
614
615
  }
615
616
  finally {
616
617
  try {
617
- await deps.sandboxClient.teardown(sandbox);
618
+ await lease.release();
618
619
  }
619
- catch (teardownErr) {
620
- logger.warn("teardown failed (non-fatal)", { executionId, ...logger.errorFields(teardownErr) });
620
+ catch (releaseErr) {
621
+ // The owner test of the lease workflow releases a lease that this release missed.
622
+ logger.warn("release failed (non-fatal)", { executionId, ...logger.errorFields(releaseErr) });
621
623
  }
622
624
  }
623
625
  }
@@ -1,7 +1,6 @@
1
1
  import type { Querier } from "../state/db.js";
2
2
  import { type AgentSession } from "../auth/types.js";
3
3
  import type { UsageRecorder } from "../billing/usage-recorder.js";
4
- import type { ResourcePolicy, ResourceSpec } from "../config/resource-limits.js";
5
4
  import type { Logger } from "../lib/logger.js";
6
5
  import type { AgentRunUsage } from "../loop/metrics.js";
7
6
  import { type ChatProvider } from "../providers/types.js";
@@ -22,7 +21,6 @@ export type DroppedPackage = {
22
21
  };
23
22
  export type AdHocRoute = {
24
23
  readonly agentId: string;
25
- readonly resources: ResourceSpec;
26
24
  readonly rationale: string;
27
25
  /** The capability phrase of the chosen agent, when the router copied one that the agent advertises. */
28
26
  readonly capability?: string;
@@ -30,13 +28,12 @@ export type AdHocRoute = {
30
28
  readonly packages: readonly string[];
31
29
  /** Every entry that the validation removed, with the reason for the caveat. */
32
30
  readonly droppedPackages: readonly DroppedPackage[];
33
- readonly fallbackClass?: "timeout" | "provider_error" | "malformed" | "no_match" | "invalid_agent" | "invalid_resources";
31
+ readonly fallbackClass?: "timeout" | "provider_error" | "malformed" | "no_match" | "invalid_agent";
34
32
  };
35
33
  export type AdHocRouterDeps = {
36
34
  readonly provider: ChatProvider;
37
35
  readonly model: string;
38
36
  readonly pool: Querier;
39
- readonly resourcePolicy?: ResourcePolicy;
40
37
  readonly logger?: Logger;
41
38
  /**
42
39
  * Test seam that sets an explicit deadline. An explicit value overrides the
@@ -55,8 +52,6 @@ export type AdHocRouterDeps = {
55
52
  /** Falls back to the no-op recorder when omitted. */
56
53
  readonly usageRecorder?: UsageRecorder;
57
54
  };
58
- export declare function defaultAdHocResources(policy?: ResourcePolicy): ResourceSpec;
59
- export declare function validAdHocResources(value: unknown, policy?: ResourcePolicy): ResourceSpec | null;
60
55
  export declare function routeAdHocRequest(deps: AdHocRouterDeps, input: {
61
56
  analysisId: string;
62
57
  request: string;
@@ -10,7 +10,7 @@ import { createNoopLogger } from "../lib/console-logger.js";
10
10
  import { toThrowable, unwrapOrThrow } from "../lib/result.js";
11
11
  import { parseUnknownRecord } from "../lib/unknown-containers.js";
12
12
  import { runLoop } from "../loop/run-loop.js";
13
- import { packagesSection, resourceEstimationSection } from "../prompts/planner.js";
13
+ import { packagesSection } from "../prompts/planner.js";
14
14
  import { effectiveDeadlineMs } from "../providers/types.js";
15
15
  import { formatQuery, parseQuery } from "../sandbox/package-identity.js";
16
16
  import { loadDataProfileStatus } from "../state/index.js";
@@ -19,50 +19,15 @@ export const AD_HOC_ROUTER_AGENT_ID = "adhoc-router";
19
19
  export const AD_HOC_ROUTER_TIMEOUT_MS = 10_000;
20
20
  export const AD_HOC_FALLBACK_AGENT_ID = "scientific-executor";
21
21
  const SUBMIT_ROUTE_TOOL_ID = "submit_route";
22
- const resourcesSchema = z.object({
23
- cpu: z.number().positive(),
24
- memoryGb: z.number().positive(),
25
- gpu: z.object({ count: z.number().int().positive() }).optional(),
26
- });
27
22
  const routeSchema = z.object({
28
23
  agentId: z.string().optional().describe("The id of one specialist from the eligible catalog, copied exactly."),
29
24
  capability: z.string().optional().describe("The one capability phrase of that specialist which this request uses, copied exactly from its capabilities."),
30
- resources: resourcesSchema
31
- .optional()
32
- .describe("The sandbox resources of the step, inside the stated ranges. Omit to take the defaults; a value outside the ranges is replaced by the defaults."),
33
25
  rationale: z.string().optional().describe("One or two sentences on why this specialist fits the request. The step agent reads it."),
34
26
  packages: z
35
27
  .array(z.string())
36
28
  .optional()
37
29
  .describe("The packages that the scripts of the step import, each as `[python:|r:]<name>[==<version>]`. An empty array is valid."),
38
30
  });
39
- export function defaultAdHocResources(policy) {
40
- return policy ? { cpu: Math.min(4, policy.perStep.maxCpu), memoryGb: Math.min(8, policy.perStep.maxMemoryGb) } : { cpu: 4, memoryGb: 8 };
41
- }
42
- export function validAdHocResources(value, policy) {
43
- const parsed = resourcesSchema.safeParse(value);
44
- if (!parsed.success)
45
- return null;
46
- const r = parsed.data;
47
- const maxCpu = policy?.perStep.maxCpu ?? 4;
48
- const maxMemoryGb = policy?.perStep.maxMemoryGb ?? 8;
49
- const maxGpuCount = policy?.perStep.maxGpuCount ?? 0;
50
- if (r.cpu > maxCpu || r.memoryGb > maxMemoryGb || (r.gpu?.count ?? 0) > maxGpuCount)
51
- return null;
52
- return r;
53
- }
54
- function resourceBounds(policy) {
55
- const defaults = defaultAdHocResources(policy);
56
- if (!policy) {
57
- return `CPU range 1..${defaults.cpu}; default ${defaults.cpu}. Memory range 1..${defaults.memoryGb} GB; default ${defaults.memoryGb}. GPU range 0..0.`;
58
- }
59
- const { perStep } = policy;
60
- return [
61
- `CPU range ${Math.min(1, perStep.maxCpu)}..${perStep.maxCpu}; default ${defaults.cpu}.`,
62
- `Memory range ${Math.min(1, perStep.maxMemoryGb)}..${perStep.maxMemoryGb} GB; default ${defaults.memoryGb}.`,
63
- `GPU range 0..${perStep.maxGpuCount}; omit gpu unless required.`,
64
- ].join(" ");
65
- }
66
31
  async function profileOrientation(pool, analysisId, logger) {
67
32
  try {
68
33
  const status = unwrapOrThrow(await loadDataProfileStatus(pool, analysisId));
@@ -85,8 +50,7 @@ function suggestedSpelling(rows) {
85
50
  }
86
51
  /**
87
52
  * Validate the package entries of a route on their own axis: a bad entry costs
88
- * that entry and nothing else, because the specialist and the resources are
89
- * sound without it.
53
+ * that entry and nothing else, because the specialist is sound without it.
90
54
  *
91
55
  * A guess of a model reaches here, and no person reviewed it. Thus a name that
92
56
  * the pool does not hold leaves the step with a caveat, where the same name in
@@ -159,7 +123,7 @@ async function routePackages(raw, resolve, logger) {
159
123
  function routeTool() {
160
124
  return defineTool({
161
125
  id: SUBMIT_ROUTE_TOOL_ID,
162
- description: "Submit the single specialist, the resource recommendation, and the packages of the step. This is the only valid response.",
126
+ description: "Submit the single specialist and the packages of the step. This is the only valid response.",
163
127
  inputSchema: routeSchema,
164
128
  describeCall: "none",
165
129
  execute: () => Promise.resolve(ok({ recorded: true })),
@@ -180,10 +144,8 @@ export async function routeAdHocRequest(deps, input) {
180
144
  const router = {
181
145
  id: AD_HOC_ROUTER_AGENT_ID,
182
146
  systemPrompt: [
183
- "Select exactly one specialist for a targeted one-step analysis, estimate its sandbox resources, and name the packages that the step imports.",
147
+ "Select exactly one specialist for a targeted one-step analysis, and name the packages that the step imports.",
184
148
  "Choose only from the supplied catalog. Do not create a plan, reject the request, or select scientific-executor.",
185
- resourceEstimationSection(deps.resourcePolicy),
186
- resourceBounds(deps.resourcePolicy),
187
149
  packagesSection(),
188
150
  "You hold no package census. For a name that both ecosystems hold, the language of the specialist you select and the wording of the request decide the track.",
189
151
  "Respond only by calling submit_route.",
@@ -243,10 +205,6 @@ export async function routeAdHocRequest(deps, input) {
243
205
  failure = "no_match";
244
206
  if (!failure && !validAgent)
245
207
  failure = "invalid_agent";
246
- const validResources = validAdHocResources(candidate.resources, deps.resourcePolicy);
247
- const resources = validResources ?? defaultAdHocResources(deps.resourcePolicy);
248
- if (!validResources && candidate.resources !== undefined && !failure)
249
- failure = "invalid_resources";
250
208
  const rationale = typeof candidate.rationale === "string" && candidate.rationale.trim()
251
209
  ? candidate.rationale.trim()
252
210
  : failure
@@ -262,10 +220,9 @@ export async function routeAdHocRequest(deps, input) {
262
220
  // most requests use a capability that declares nothing.
263
221
  const requestedCapability = typeof candidate.capability === "string" ? candidate.capability.trim() : "";
264
222
  const capability = (SANDBOX_AGENT_META[agentId]?.capabilities ?? []).includes(requestedCapability) ? requestedCapability : undefined;
265
- logger.info("ad hoc route selected", { agentId, resources, rationale, failure, packages, droppedPackages, capability });
223
+ logger.info("ad hoc route selected", { agentId, rationale, failure, packages, droppedPackages, capability });
266
224
  return {
267
225
  agentId,
268
- resources,
269
226
  rationale,
270
227
  packages,
271
228
  droppedPackages,
@@ -107,7 +107,7 @@ export function createComptoxTool(deps) {
107
107
  "products). See `dataset` for what each returns; all four resolve `query` to a DTXSID first.\n" +
108
108
  "ACCEPTED IDENTIFIERS, for every dataset: a DTXSID ('DTXSID7020182') used directly, or a CASRN ('80-05-7'), a chemical name " +
109
109
  "('bisphenol A') or an InChIKey, each resolved by EXACT match.\n" +
110
- "Requires EPA_CCTE_API_KEY — a missing key fails terminally: do NOT retry, tell the user the key needs configuring and proceed without EPA data.\n" +
110
+ "Requires EPA_CCTE_API_KEY — a missing key fails terminally: do NOT retry; state that the key is not configured, and proceed without EPA data.\n" +
111
111
  "found: false means the query did not resolve to a chemical; a present-but-empty section is likewise valid no-data. Do not retry either.\n" +
112
112
  "This is environmental/industrial-chemical data: for drug-like compounds prefer PubChem or ChEMBL — dataset 'chemical' returns " +
113
113
  "`detail.pubchemCid` to bridge back.",
@@ -115,7 +115,7 @@ export function createDrugGeneInteractionsTool(deps) {
115
115
  "Verbose fields (`includeAttributes`, `includeDrugRecord`) are off by default — both cost a lot of context and are rarely what the question needs.\n" +
116
116
  "`perSource` gives the outcome of each corpus, and a conclusion that an interaction is unknown rests on it: 'no_data' means that corpus " +
117
117
  "has nothing; 'unavailable' means it could " +
118
- "not be reached (a missing DRUGBANK_API_KEY lands here — tell the user and proceed with DGIdb). A `found: false` entry means the identifier " +
118
+ "not be reached (a missing DRUGBANK_API_KEY lands here — state that the key is missing, and proceed with DGIdb). A `found: false` entry means the identifier " +
119
119
  "is not in DGIdb at all, usually a non-HUGO symbol or a brand name. Neither is worth retrying unchanged.",
120
120
  inputSchema,
121
121
  describeCall: "none",
@@ -167,7 +167,7 @@ export function createGeneDiseaseEvidenceTool(deps) {
167
167
  "no_data there ('low density lipoprotein cholesterol measurement' is the label) while the other corpora still answer it.\n" +
168
168
  "`perSource` gives the outcome of each corpus, and a conclusion of absence rests on it. 'no_data' means that corpus genuinely has nothing; " +
169
169
  "'unavailable' means it " +
170
- "could not be reached (a missing DISGENET_API_KEY lands here — tell the user and proceed with the others); 'not_applicable' means that " +
170
+ "could not be reached (a missing DISGENET_API_KEY lands here — state that the key is missing, and proceed with the others); 'not_applicable' means that " +
171
171
  "corpus has no lookup for this queryType. Only 'no_data' is evidence of absence, and none is worth retrying unchanged.",
172
172
  inputSchema,
173
173
  describeCall: "none",
@@ -3,7 +3,7 @@ import type { Tool } from "../define-tool.js";
3
3
  /**
4
4
  * API keys for the external bio/chem data sources. Threaded from the
5
5
  * composition root to every place that assembles bio tools (conversation
6
- * agent, sandbox agents, literature reviewer, analogy reporter). Each key
6
+ * agent, sandbox agents, run synthesizer, analogy reporter). Each key
7
7
  * may be empty when its source is unconfigured — the tool surfaces the
8
8
  * resulting auth failure as a normal tool error.
9
9
  */
@@ -116,6 +116,36 @@ const inputSchema = z
116
116
  message: "maxAuthors and maxMeshTerms apply to action 'details' only",
117
117
  path: ["action"],
118
118
  });
119
+ const ArticleSummarySchema = z.object({ pmid: z.string(), title: z.string(), journal: z.string(), year: z.string(), authors: z.string() });
120
+ const ArticleSchema = z.object({
121
+ pmid: z.string(),
122
+ title: z.string(),
123
+ abstract: z.string(),
124
+ authors: z.array(z.string()),
125
+ authorCount: z.number(),
126
+ journal: z.string(),
127
+ year: z.string(),
128
+ doi: z.string(),
129
+ meshTerms: z.array(z.string()),
130
+ meshTermCount: z.number(),
131
+ pmcId: z.string().nullable(),
132
+ });
133
+ const outputSchema = z.union([
134
+ z.object({ totalFound: z.number(), results: z.array(ArticleSummarySchema) }).describe("action 'search'"),
135
+ z.object({ articles: z.array(ArticleSchema), notFound: z.array(z.string()) }).describe("action 'details'"),
136
+ z.object({ pmcId: z.string(), available: z.literal(false) }).describe("action 'fulltext', not open access"),
137
+ z
138
+ .object({
139
+ pmcId: z.string(),
140
+ available: z.literal(true),
141
+ fullText: z.string(),
142
+ outline: z.array(z.object({ heading: z.string(), chars: z.number(), included: z.boolean() })),
143
+ totalChars: z.number(),
144
+ returnedChars: z.number(),
145
+ truncated: z.boolean(),
146
+ })
147
+ .describe("action 'fulltext'"),
148
+ ]);
119
149
  export function createPubMedTool(deps) {
120
150
  return defineTool({
121
151
  id: "pubmed",
@@ -132,6 +162,7 @@ export function createPubMedTool(deps) {
132
162
  "An empty result set and available: false are expected outcomes, not errors — an article with no PMC record simply has no open full text. " +
133
163
  "Report it and continue, do not retry it.",
134
164
  inputSchema,
165
+ outputSchema,
135
166
  // One tool, three jobs. The action alone would still render three
136
167
  // different searches identically, so each action names its own subject:
137
168
  // the query, how many articles, or which article.