@inflexa-ai/harness 0.46.0 → 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 (280) 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/chat-turn-workflow.js +5 -6
  12. package/dist/app/synthesize-run.d.ts +10 -6
  13. package/dist/app/synthesize-run.js +4 -2
  14. package/dist/contracts/analogy-report.d.ts +46 -2
  15. package/dist/contracts/analogy-report.js +31 -25
  16. package/dist/contracts/chat-parts.d.ts +3 -14
  17. package/dist/contracts/report-reference.d.ts +2 -1
  18. package/dist/contracts/report-reference.js +2 -1
  19. package/dist/contracts/schemas/chat-parts.d.ts +0 -27
  20. package/dist/contracts/schemas/chat-parts.js +1 -3
  21. package/dist/execution/run-canceler.d.ts +2 -0
  22. package/dist/execution/run-canceler.js +9 -0
  23. package/dist/execution/run-synthesis.d.ts +36 -8
  24. package/dist/execution/run-synthesis.js +114 -32
  25. package/dist/index.d.ts +2 -5
  26. package/dist/index.js +3 -6
  27. package/dist/input-scan/enrich.d.ts +5 -10
  28. package/dist/input-scan/enrich.js +12 -8
  29. package/dist/lib/hooks.d.ts +9 -0
  30. package/dist/lib/hooks.js +6 -1
  31. package/dist/lib/markdown-pdf.d.ts +27 -0
  32. package/dist/lib/markdown-pdf.js +236 -0
  33. package/dist/lib/metrics.d.ts +31 -0
  34. package/dist/lib/metrics.js +64 -0
  35. package/dist/lib/pdf-text.d.ts +23 -0
  36. package/dist/lib/pdf-text.js +34 -0
  37. package/dist/lib/regulatory-corpus/fetch-fda-guidance.js +3 -5
  38. package/dist/lib/regulatory-corpus/fetch-ich.js +3 -5
  39. package/dist/loop/run-loop.d.ts +5 -0
  40. package/dist/loop/run-loop.js +30 -2
  41. package/dist/loop/sub-agent.js +5 -6
  42. package/dist/loop/tool-name-repair.d.ts +11 -0
  43. package/dist/loop/tool-name-repair.js +23 -0
  44. package/dist/loop/turn-credentials.d.ts +5 -0
  45. package/dist/loop/turn-credentials.js +17 -0
  46. package/dist/loop/types.d.ts +14 -0
  47. package/dist/memory/card-builders.d.ts +1 -1
  48. package/dist/prompts/SOUL.d.ts +6 -6
  49. package/dist/prompts/SOUL.js +22 -121
  50. package/dist/prompts/analogical-reasoner.d.ts +1 -1
  51. package/dist/prompts/analogical-reasoner.js +35 -101
  52. package/dist/prompts/briefing.d.ts +20 -11
  53. package/dist/prompts/briefing.js +36 -19
  54. package/dist/prompts/conversation.d.ts +1 -1
  55. package/dist/prompts/conversation.js +103 -383
  56. package/dist/prompts/planner.d.ts +1 -5
  57. package/dist/prompts/planner.js +45 -128
  58. package/dist/prompts/report-session.d.ts +1 -1
  59. package/dist/prompts/report-session.js +26 -57
  60. package/dist/prompts/sandbox/bulk-transcriptomics-agent.d.ts +1 -1
  61. package/dist/prompts/sandbox/bulk-transcriptomics-agent.js +3 -3
  62. package/dist/prompts/sandbox/cheminformatics-agent.d.ts +1 -1
  63. package/dist/prompts/sandbox/cheminformatics-agent.js +12 -47
  64. package/dist/prompts/sandbox/chromatin-agent.d.ts +1 -1
  65. package/dist/prompts/sandbox/chromatin-agent.js +4 -5
  66. package/dist/prompts/sandbox/data-profiler.js +11 -34
  67. package/dist/prompts/sandbox/dna-methylation-agent.d.ts +1 -1
  68. package/dist/prompts/sandbox/dna-methylation-agent.js +3 -1
  69. package/dist/prompts/sandbox/drug-repurposing-agent.d.ts +1 -1
  70. package/dist/prompts/sandbox/drug-repurposing-agent.js +23 -73
  71. package/dist/prompts/sandbox/enrichment-agent.d.ts +1 -1
  72. package/dist/prompts/sandbox/enrichment-agent.js +3 -1
  73. package/dist/prompts/sandbox/genomic-variant-agent.d.ts +1 -1
  74. package/dist/prompts/sandbox/genomic-variant-agent.js +3 -4
  75. package/dist/prompts/sandbox/immune-profiling-agent.d.ts +1 -1
  76. package/dist/prompts/sandbox/immune-profiling-agent.js +5 -4
  77. package/dist/prompts/sandbox/metabolomics-agent.d.ts +1 -1
  78. package/dist/prompts/sandbox/metabolomics-agent.js +3 -1
  79. package/dist/prompts/sandbox/microbiome-agent.d.ts +1 -1
  80. package/dist/prompts/sandbox/microbiome-agent.js +3 -1
  81. package/dist/prompts/sandbox/multi-omics-integration-agent.d.ts +1 -1
  82. package/dist/prompts/sandbox/multi-omics-integration-agent.js +3 -1
  83. package/dist/prompts/sandbox/multimodal-sc-agent.d.ts +1 -1
  84. package/dist/prompts/sandbox/multimodal-sc-agent.js +3 -1
  85. package/dist/prompts/sandbox/network-agent.d.ts +1 -1
  86. package/dist/prompts/sandbox/network-agent.js +3 -1
  87. package/dist/prompts/sandbox/pkpd-clinical-response-agent.d.ts +1 -1
  88. package/dist/prompts/sandbox/pkpd-clinical-response-agent.js +4 -7
  89. package/dist/prompts/sandbox/proteomics-agent.d.ts +1 -1
  90. package/dist/prompts/sandbox/proteomics-agent.js +3 -1
  91. package/dist/prompts/sandbox/scientific-executor.d.ts +1 -1
  92. package/dist/prompts/sandbox/scientific-executor.js +0 -12
  93. package/dist/prompts/sandbox/single-cell-agent.d.ts +1 -1
  94. package/dist/prompts/sandbox/single-cell-agent.js +3 -1
  95. package/dist/prompts/sandbox/spatial-omics-agent.d.ts +1 -1
  96. package/dist/prompts/sandbox/spatial-omics-agent.js +3 -1
  97. package/dist/prompts/sandbox/statistical-modeling-agent.d.ts +1 -1
  98. package/dist/prompts/sandbox/statistical-modeling-agent.js +4 -4
  99. package/dist/prompts/sandbox/translational-safety-agent.d.ts +1 -1
  100. package/dist/prompts/sandbox/translational-safety-agent.js +10 -29
  101. package/dist/prompts/sandbox-standards.d.ts +15 -18
  102. package/dist/prompts/sandbox-standards.js +96 -305
  103. package/dist/prompts/synthesis-agent.d.ts +1 -1
  104. package/dist/prompts/synthesis-agent.js +24 -90
  105. package/dist/provenance/collector.d.ts +2 -5
  106. package/dist/provenance/collector.js +2 -5
  107. package/dist/provenance/seam.d.ts +36 -1
  108. package/dist/provenance/workspace-reads.d.ts +36 -0
  109. package/dist/provenance/workspace-reads.js +33 -0
  110. package/dist/providers/request-headers.js +12 -3
  111. package/dist/report-model/production-resolver.d.ts +22 -52
  112. package/dist/report-model/production-resolver.js +43 -112
  113. package/dist/report-render/chart-export.d.ts +9 -0
  114. package/dist/report-render/chart-export.js +1 -1
  115. package/dist/report-render/views/chart-view.d.ts +1 -0
  116. package/dist/report-render/views/chart-view.js +1 -0
  117. package/dist/report-render/views/lineage.d.ts +1 -0
  118. package/dist/report-render/views/page-view.d.ts +1 -0
  119. package/dist/report-render/views/page-view.js +1 -0
  120. package/dist/report-render/views/prose.d.ts +1 -0
  121. package/dist/report-render/views/prose.js +1 -0
  122. package/dist/report-render/views/references-view.d.ts +1 -0
  123. package/dist/report-render/views/values.d.ts +1 -0
  124. package/dist/report-render/views/values.js +1 -0
  125. package/dist/runtime/assemble.d.ts +20 -9
  126. package/dist/runtime/assemble.js +47 -38
  127. package/dist/sandbox/client.d.ts +5 -3
  128. package/dist/sandbox/create-sandbox.d.ts +7 -12
  129. package/dist/sandbox/create-sandbox.js +15 -37
  130. package/dist/sandbox/docker-client.d.ts +1 -3
  131. package/dist/sandbox/docker-client.js +3 -5
  132. package/dist/sandbox/exec-outcome.d.ts +11 -11
  133. package/dist/sandbox/exec-outcome.js +22 -22
  134. package/dist/sandbox/exec.d.ts +5 -0
  135. package/dist/sandbox/exec.js +9 -1
  136. package/dist/sandbox/identity.d.ts +4 -16
  137. package/dist/sandbox/identity.js +5 -21
  138. package/dist/sandbox/k8s-client.d.ts +0 -1
  139. package/dist/sandbox/k8s-client.js +8 -13
  140. package/dist/sandbox/lease-names.d.ts +6 -0
  141. package/dist/sandbox/lease-names.js +12 -0
  142. package/dist/sandbox/leases.d.ts +105 -0
  143. package/dist/sandbox/leases.js +519 -0
  144. package/dist/sandbox/liveness.d.ts +5 -2
  145. package/dist/sandbox/mount-plan.d.ts +10 -1
  146. package/dist/sandbox/mount-plan.js +7 -1
  147. package/dist/sandbox/reconciler.d.ts +58 -0
  148. package/dist/sandbox/reconciler.js +153 -0
  149. package/dist/sandbox/sandbox-step-translate.d.ts +1 -1
  150. package/dist/sandbox/types.d.ts +25 -18
  151. package/dist/sandbox/types.js +10 -4
  152. package/dist/schemas/plan-schemas.d.ts +0 -21
  153. package/dist/schemas/plan-schemas.js +2 -3
  154. package/dist/schemas/run-synthesis.d.ts +2 -2
  155. package/dist/schemas/run-synthesis.js +3 -3
  156. package/dist/schemas/validate-plan.d.ts +1 -14
  157. package/dist/schemas/validate-plan.js +5 -24
  158. package/dist/schemas/workflow-state.d.ts +3 -32
  159. package/dist/schemas/workflow-state.js +4 -13
  160. package/dist/state/index.d.ts +0 -1
  161. package/dist/state/index.js +0 -1
  162. package/dist/state/migrations/20261030120000_lease_the_sandboxes.d.ts +7 -0
  163. package/dist/state/migrations/20261030120000_lease_the_sandboxes.js +34 -0
  164. package/dist/state/migrations/20261030120100_drop_the_report_derivations.d.ts +5 -0
  165. package/dist/state/migrations/20261030120100_drop_the_report_derivations.js +9 -0
  166. package/dist/state/migrations/20261030120200_keep_the_report_derivations.d.ts +6 -0
  167. package/dist/state/migrations/20261030120200_keep_the_report_derivations.js +10 -0
  168. package/dist/state/migrations/index.js +6 -0
  169. package/dist/state/purge-analysis.d.ts +4 -2
  170. package/dist/state/purge-analysis.js +22 -11
  171. package/dist/state/sandbox-leases.d.ts +81 -0
  172. package/dist/state/sandbox-leases.js +247 -0
  173. package/dist/state/schema.d.ts +1 -13
  174. package/dist/state/schema.js +1 -6
  175. package/dist/state/step-executions.d.ts +0 -2
  176. package/dist/state/step-executions.js +1 -4
  177. package/dist/tasks/data-profile.d.ts +40 -2
  178. package/dist/tasks/data-profile.js +89 -87
  179. package/dist/testing.d.ts +2 -0
  180. package/dist/testing.js +6 -1
  181. package/dist/tools/ad-hoc-router.d.ts +1 -6
  182. package/dist/tools/ad-hoc-router.js +5 -48
  183. package/dist/tools/bio/comptox.js +1 -1
  184. package/dist/tools/bio/drug-gene-interactions.js +1 -1
  185. package/dist/tools/bio/gene-disease-evidence.js +1 -1
  186. package/dist/tools/bio/keys.d.ts +1 -1
  187. package/dist/tools/bio/pubmed.js +31 -0
  188. package/dist/tools/bio/search-gene.js +17 -0
  189. package/dist/tools/bio/search-geo-datasets.js +13 -0
  190. package/dist/tools/bio/search-regulatory-approvals.js +1 -1
  191. package/dist/tools/define-tool.d.ts +25 -2
  192. package/dist/tools/define-tool.js +11 -1
  193. package/dist/tools/display/show-user.js +1 -1
  194. package/dist/tools/execute-analysis.d.ts +1 -3
  195. package/dist/tools/execute-analysis.js +5 -33
  196. package/dist/tools/report-session/index.d.ts +0 -1
  197. package/dist/tools/report-session/index.js +0 -1
  198. package/dist/tools/report-session/script-tools.d.ts +30 -0
  199. package/dist/tools/report-session/script-tools.js +177 -0
  200. package/dist/tools/research/generate-analogy-report.d.ts +16 -9
  201. package/dist/tools/research/generate-analogy-report.js +111 -49
  202. package/dist/tools/research/generate-plan.d.ts +102 -7
  203. package/dist/tools/research/generate-plan.js +74 -43
  204. package/dist/tools/research/index.d.ts +4 -3
  205. package/dist/tools/research/index.js +4 -3
  206. package/dist/tools/research/inspect-run.d.ts +0 -1
  207. package/dist/tools/research/inspect-run.js +5 -10
  208. package/dist/tools/research/search-arxiv.js +15 -0
  209. package/dist/tools/research/search-github-repos.js +13 -0
  210. package/dist/tools/research/search-semantic-scholar.js +16 -0
  211. package/dist/tools/research/synthesis-lookups.d.ts +21 -0
  212. package/dist/tools/research/synthesis-lookups.js +29 -0
  213. package/dist/tools/research/web-fetch.d.ts +1 -1
  214. package/dist/tools/research/web-fetch.js +14 -12
  215. package/dist/tools/sandbox/lease-tools.d.ts +32 -0
  216. package/dist/tools/sandbox/lease-tools.js +60 -0
  217. package/dist/tools/script/codemode-worker.d.ts +5 -0
  218. package/dist/tools/script/codemode-worker.js +5 -0
  219. package/dist/tools/script/file-tools.d.ts +100 -0
  220. package/dist/tools/script/file-tools.js +377 -0
  221. package/dist/tools/script/metrics.d.ts +21 -0
  222. package/dist/tools/script/metrics.js +38 -0
  223. package/dist/tools/script/quickjs.wasm +0 -0
  224. package/dist/tools/script/run-script.d.ts +76 -0
  225. package/dist/tools/script/run-script.js +326 -0
  226. package/dist/tools/script/scratch.d.ts +81 -0
  227. package/dist/tools/script/scratch.js +142 -0
  228. package/dist/tools/start-report-session.js +1 -1
  229. package/dist/tools/workspace/edit-file.js +2 -1
  230. package/dist/tools/workspace/execute-command.d.ts +9 -13
  231. package/dist/tools/workspace/execute-command.js +23 -19
  232. package/dist/tools/workspace/file-stat.js +1 -1
  233. package/dist/tools/workspace/grep.d.ts +5 -2
  234. package/dist/tools/workspace/grep.js +19 -10
  235. package/dist/tools/workspace/mutator.d.ts +48 -4
  236. package/dist/tools/workspace/mutator.js +43 -12
  237. package/dist/tools/workspace/read-file.d.ts +5 -2
  238. package/dist/tools/workspace/read-file.js +15 -11
  239. package/dist/tools/workspace/read-surface.d.ts +17 -0
  240. package/dist/tools/workspace/read-surface.js +17 -0
  241. package/dist/tools/workspace/result-bounds.d.ts +1 -0
  242. package/dist/tools/workspace/run-exec.d.ts +12 -13
  243. package/dist/tools/workspace/run-exec.js +13 -13
  244. package/dist/tools/workspace/scan-inputs.d.ts +2 -5
  245. package/dist/tools/workspace/scan-inputs.js +2 -3
  246. package/dist/tools/workspace/show-file.js +1 -1
  247. package/dist/tools/workspace/show-plan.js +4 -3
  248. package/dist/tools/workspace/write-file.js +2 -1
  249. package/dist/workflows/execute-analysis-scheduler.d.ts +0 -26
  250. package/dist/workflows/execute-analysis-scheduler.js +12 -32
  251. package/dist/workflows/execute-analysis.d.ts +17 -35
  252. package/dist/workflows/execute-analysis.js +13 -107
  253. package/dist/workflows/pause.js +29 -22
  254. package/dist/workflows/sandbox-step.d.ts +16 -31
  255. package/dist/workflows/sandbox-step.js +36 -71
  256. package/dist/workflows/test/sql-writes.d.ts +0 -1
  257. package/dist/workflows/test/sql-writes.js +0 -2
  258. package/dist/workspace/filesystem.d.ts +2 -2
  259. package/dist/workspace/filesystem.js +10 -5
  260. package/dist/workspace/paths.d.ts +19 -1
  261. package/dist/workspace/paths.js +22 -1
  262. package/package.json +6 -3
  263. package/dist/prompts/literature-reviewer.d.ts +0 -1
  264. package/dist/prompts/literature-reviewer.js +0 -114
  265. package/dist/sandbox/reaper.d.ts +0 -81
  266. package/dist/sandbox/reaper.js +0 -161
  267. package/dist/sandbox/step-sandbox.d.ts +0 -47
  268. package/dist/sandbox/step-sandbox.js +0 -53
  269. package/dist/state/active-sandboxes.d.ts +0 -33
  270. package/dist/state/active-sandboxes.js +0 -65
  271. package/dist/tasks/extract-values-script.d.ts +0 -64
  272. package/dist/tasks/extract-values-script.js +0 -180
  273. package/dist/tasks/extract-values.d.ts +0 -77
  274. package/dist/tasks/extract-values.js +0 -146
  275. package/dist/tools/report-session/derive-table.d.ts +0 -164
  276. package/dist/tools/report-session/derive-table.js +0 -419
  277. package/dist/tools/research/literature-reviewer.d.ts +0 -42
  278. package/dist/tools/research/literature-reviewer.js +0 -107
  279. package/dist/workflows/metrics.d.ts +0 -23
  280. package/dist/workflows/metrics.js +0 -38
@@ -6,7 +6,8 @@
6
6
  * replays from the cache instead of re-issuing work):
7
7
  *
8
8
  * 1. `mark-running` — update `cortex_step_executions` to running.
9
- * 2. `createSandbox` — provision the sandbox; persist its handle.
9
+ * 2. the lease — bind the lease of the step. The agent acquires
10
+ * its sandbox with `acquire_sandbox`.
10
11
  * 3. `runLoop` — the harness agent loop. Every model call and
11
12
  * tool call inside is its own durable step.
12
13
  * 4. `generateFileMetadata`— the file-metadata follow-up request of the step agent.
@@ -16,15 +17,16 @@
16
17
  * 7. `syncArtifacts` — register provenance + upload missing file_ids
17
18
  * via the managed root (scoped to this run+step).
18
19
  * 8. `vector-index` — index file descriptions + summary into pgvector.
19
- * 9. `teardown` — destroy the sandbox + clear sandbox_ref.
20
+ * 9. `sandbox.release` — release the lease, which removes its machine.
20
21
  * 10. `mark-terminal` — write the final status, emit `data-step-activity`
21
22
  * onto the parent's DBOS stream.
22
23
  *
23
- * The parent owns sibling lifecycle. This child owns its own teardown and its
24
- * own pause (work-pause spec): a failed model request of the agent loop with a
25
- * cause of a pause, or a refusal with the suspend flag of the label gate or the
26
- * register gate. The child waits in place, and the run row stays `running`. The
27
- * other steps go on, and each one pauses at its own failure.
24
+ * The parent owns sibling lifecycle. This child releases its own lease, and it
25
+ * owns its own pause (work-pause spec): a failed model request of the agent loop
26
+ * with a cause of a pause, or a refusal with the suspend flag of the register
27
+ * gate. The child waits in place, and the run row stays `running`. The other
28
+ * steps go on, and each one pauses at its own failure. A refused spawn pauses
29
+ * the lease workflow under the run, not this child.
28
30
  */
29
31
  import type { TokenUsageRollup } from "@inflexa-ai/harness/contracts/usage.js";
30
32
  import type { Querier } from "../state/db.js";
@@ -39,13 +41,11 @@ import type { RunAuthorizer } from "../execution/run-authorizer.js";
39
41
  import type { FileMetadataEntry } from "../execution/artifact-metadata.js";
40
42
  import type { ArtifactManifestEntry } from "../schemas/artifact-manifest.js";
41
43
  import type { StepSummary } from "../schemas/step-summary.js";
42
- import type { SandboxClient } from "../sandbox/client.js";
44
+ import type { SandboxLeases, BoundLease } from "../sandbox/leases.js";
43
45
  import type { WorkspaceFilesystem } from "../workspace/filesystem.js";
44
46
  import type { ResolveWorkspaceRoot } from "../workspace/paths.js";
45
47
  import type { ArtifactRegistry } from "../execution/artifact-registry.js";
46
48
  import { type StepFileMetadata } from "../execution/post-step-pipeline.js";
47
- import type { ResourceSpec } from "../config/resource-limits.js";
48
- import type { SandboxRef } from "../sandbox/types.js";
49
49
  import { ProvenanceCollector } from "../provenance/collector.js";
50
50
  import { type FileMetadataCell } from "../tools/sandbox/submit-file-metadata.js";
51
51
  import type { TemplateBinding } from "../tools/knowledge/template.js";
@@ -89,15 +89,6 @@ export type SandboxStepInput = {
89
89
  readonly image?: string;
90
90
  /** Per-step env overrides forwarded to sandbox-server. */
91
91
  readonly extraEnv?: Record<string, string>;
92
- /** Planner-estimated CPU/memory/GPU request for this step's sandbox. */
93
- readonly resources: ResourceSpec;
94
- /**
95
- * Per-step execution budget in seconds. The body translates this into the
96
- * absolute unix-ms deadline that each exec of the step honours. Comes from the plan's `step.timeout` (defaulted on the parent
97
- * side); falls back to `DEFAULT_STEP_TIMEOUT_SECONDS` when unset so a
98
- * client that omits the field still gets a non-pathological ceiling.
99
- */
100
- readonly timeoutSeconds?: number;
101
92
  /**
102
93
  * Durable `RunSession` derived by the parent via `forStep`. Carries the
103
94
  * run-authorization credential, identity, scope, and `runFrame = { runId, stepId }`.
@@ -106,11 +97,6 @@ export type SandboxStepInput = {
106
97
  */
107
98
  readonly runSession: SpawnSession;
108
99
  };
109
- /**
110
- * Fallback when the plan step omits `timeout`. Plans that need 3-hour runs
111
- * declare it explicitly.
112
- */
113
- export declare const DEFAULT_STEP_TIMEOUT_SECONDS = 3600;
114
100
  /**
115
101
  * Deterministic `blocked_reason` for a loop that spent every iteration and
116
102
  * persisted no output files (see the harness-sandbox-agents spec). Fixed prose,
@@ -170,15 +156,15 @@ export type PostStepArtifacts = {
170
156
  };
171
157
  /**
172
158
  * Per-step coordinates the workflow body computes once and threads to the
173
- * deps factory. The parent supplies the durable inputs; the factory
174
- * supplies the live sandbox ref and the deadline.
159
+ * deps factory. The parent supplies the durable inputs; the body supplies the
160
+ * lease of the step.
175
161
  */
176
162
  export type SandboxAgentBuildContext = {
177
163
  readonly input: SandboxStepInput;
178
164
  readonly session: RunSession;
179
- readonly sandbox: SandboxRef;
165
+ /** The lease of the step. The agent tools acquire, release, and run commands through it. */
166
+ readonly lease: BoundLease;
180
167
  readonly stepWritePrefix: string;
181
- readonly deadlineMs: () => number;
182
168
  /**
183
169
  * Step-scoped lineage collector. The body owns one per step and threads
184
170
  * it both here (so `execute_command` feeds each exec's provenance frame)
@@ -227,7 +213,8 @@ export type SandboxStepDeps = {
227
213
  readonly provider: ChatProvider;
228
214
  /** Write-side embedder for the post-step vector index. */
229
215
  readonly embedding: EmbeddingProvider;
230
- readonly sandboxClient: SandboxClient;
216
+ /** The sandbox module. The step binds its lease, and releases it at the end. */
217
+ readonly leases: SandboxLeases;
231
218
  /**
232
219
  * External artifact registration + sync seam. The harness's post-step pipeline
233
220
  * registers each step's outputs through it (filesystem index in the
@@ -277,8 +264,6 @@ export type PostStepContext = {
277
264
  readonly fileMetadata: FileMetadataCell;
278
265
  /** Absolute path to the step's writable artifact directory. */
279
266
  readonly writePrefix: string;
280
- /** Sandbox handle id — informational only, used in log lines. */
281
- readonly sandboxId: string;
282
267
  /** Same step-scoped collector the agent loop fed each exec frame into. */
283
268
  readonly lineageCollector: ProvenanceCollector;
284
269
  };
@@ -6,7 +6,8 @@
6
6
  * replays from the cache instead of re-issuing work):
7
7
  *
8
8
  * 1. `mark-running` — update `cortex_step_executions` to running.
9
- * 2. `createSandbox` — provision the sandbox; persist its handle.
9
+ * 2. the lease — bind the lease of the step. The agent acquires
10
+ * its sandbox with `acquire_sandbox`.
10
11
  * 3. `runLoop` — the harness agent loop. Every model call and
11
12
  * tool call inside is its own durable step.
12
13
  * 4. `generateFileMetadata`— the file-metadata follow-up request of the step agent.
@@ -16,15 +17,16 @@
16
17
  * 7. `syncArtifacts` — register provenance + upload missing file_ids
17
18
  * via the managed root (scoped to this run+step).
18
19
  * 8. `vector-index` — index file descriptions + summary into pgvector.
19
- * 9. `teardown` — destroy the sandbox + clear sandbox_ref.
20
+ * 9. `sandbox.release` — release the lease, which removes its machine.
20
21
  * 10. `mark-terminal` — write the final status, emit `data-step-activity`
21
22
  * onto the parent's DBOS stream.
22
23
  *
23
- * The parent owns sibling lifecycle. This child owns its own teardown and its
24
- * own pause (work-pause spec): a failed model request of the agent loop with a
25
- * cause of a pause, or a refusal with the suspend flag of the label gate or the
26
- * register gate. The child waits in place, and the run row stays `running`. The
27
- * other steps go on, and each one pauses at its own failure.
24
+ * The parent owns sibling lifecycle. This child releases its own lease, and it
25
+ * owns its own pause (work-pause spec): a failed model request of the agent loop
26
+ * with a cause of a pause, or a refusal with the suspend flag of the register
27
+ * gate. The child waits in place, and the run row stays `running`. The other
28
+ * steps go on, and each one pauses at its own failure. A refused spawn pauses
29
+ * the lease workflow under the run, not this child.
28
30
  */
29
31
  import { DBOS } from "@dbos-inc/dbos-sdk";
30
32
  import { isWorkflowCancellation } from "../runtime/dbos.js";
@@ -34,7 +36,7 @@ import { createNoopLogger } from "../lib/console-logger.js";
34
36
  import { deliverNotice } from "../lib/hooks.js";
35
37
  import { unwrapOrThrow } from "../lib/result.js";
36
38
  import { maskExcept } from "../loop/call-policy.js";
37
- import { runLoop } from "../loop/run-loop.js";
39
+ import { runLoop, withRunScript } from "../loop/run-loop.js";
38
40
  import { READ_TOOL_OUTPUT_TOOL_ID } from "../loop/tool-output.js";
39
41
  import { lastExecOutcome } from "../sandbox/exec-outcome.js";
40
42
  import { activityEmission, activityForTool, applyTreeDelta, isChatDataPart, sandboxTreeDelta, stepPartId, toolActivityKey, } from "../sandbox/sandbox-step-translate.js";
@@ -42,17 +44,10 @@ import { createDetailResolver } from "../tools/detail-resolver.js";
42
44
  import { forSubAgent } from "../auth/types.js";
43
45
  import { walkStepArtifacts } from "../execution/post-step.js";
44
46
  import { collectStepOutputs, describeStepRegistrationFailure, generateStepFileMetadata, generateStepSummaryAndWrite, reconcileAndRegisterStepArtifacts, vectorIndexStepOutputs, } from "../execution/post-step-pipeline.js";
45
- import { mintSandboxIdentity } from "../sandbox/identity.js";
46
- import { keepSuspendingRefusal } from "../sandbox/sandbox-error.js";
47
47
  import { ProvenanceCollector } from "../provenance/collector.js";
48
48
  import { blockerOf } from "../tools/sandbox/report-blocker.js";
49
49
  import { createFileMetadataCell, SUBMIT_FILE_METADATA_TOOL_ID } from "../tools/sandbox/submit-file-metadata.js";
50
50
  import { pauseAtGate, workPause } from "./pause.js";
51
- /**
52
- * Fallback when the plan step omits `timeout`. Plans that need 3-hour runs
53
- * declare it explicitly.
54
- */
55
- export const DEFAULT_STEP_TIMEOUT_SECONDS = 3600;
56
51
  /**
57
52
  * Deterministic `blocked_reason` for a loop that spent every iteration and
58
53
  * persisted no output files (see the harness-sandbox-agents spec). Fixed prose,
@@ -157,7 +152,7 @@ export async function runSandboxStepBody(input, deps) {
157
152
  logger.warn("emitToParentStream failed (non-fatal)", logger.errorFields(err));
158
153
  }
159
154
  };
160
- // The step waits in place, and the sandbox, if one stands, stays up for the resume.
155
+ // The step waits in place. The lease stays, and its machine goes away when the pause outlasts the idle period.
161
156
  const pause = workPause({
162
157
  pool: deps.pool,
163
158
  workId: input.runId,
@@ -175,57 +170,23 @@ export async function runSandboxStepBody(input, deps) {
175
170
  const session = forSubAgent(input.runSession, input.agentId);
176
171
  const writePrefix = await deps.resolveWritePrefix(input);
177
172
  const lineageCollector = createLineageCollector(input);
178
- // (2a) sandbox.mint — checkpoint the machine's identity (its name)
179
- // BEFORE it is spawned (see the harness-sandbox-exec spec). Durable-before-create is what makes (2b)
180
- // idempotent: a crash between spawn and the spawn checkpoint re-runs (2b)
181
- // with the same identity, which adopts the already-created machine instead
182
- // of leaking a second one.
183
- const identity = await DBOS.runStep(() => Promise.resolve(mintSandboxIdentity(input.runId)), { name: "sandbox.mint" });
184
- // (2b) sandbox.create — spawn (or adopt) the machine under the minted
185
- // identity. The handle is cached so recovery picks the same machine back up
186
- // without re-provisioning.
187
- const sandboxSpec = {
188
- childWorkflowId,
189
- image: input.image,
190
- extraEnv: input.extraEnv,
191
- resources: input.resources,
192
- };
193
- const spawn = () => DBOS.runStep(async () => keepSuspendingRefusal(await deps.sandboxClient.createSandbox(session, sandboxSpec, identity)), {
194
- name: "sandbox.create",
173
+ // (2) The lease of the step. The agent acquires its sandbox with `acquire_sandbox` when it needs one, and the
174
+ // sandbox module owns the machine: the admission, the spawn, a new machine after a pause or a death, and the
175
+ // pause of a refused spawn. The lease key is the id of this workflow.
176
+ const lease = deps.leases.bind({
177
+ leaseKey: childWorkflowId,
178
+ session: input.runSession,
179
+ work: { kind: "run", id: input.runId },
180
+ spawn: { image: input.image, extraEnv: input.extraEnv },
181
+ emit: safeEmit,
195
182
  });
196
- let spawned = await spawn();
197
- // A refusal of the label gate with the suspend flag waits in a pause, and the spawn runs again after the resume.
198
- while (spawned.isErr()) {
199
- await pauseAtGate(pause, spawned.error.reason);
200
- spawned = await spawn();
201
- }
202
- const sandbox = spawned.value;
203
- // Recovery path: re-check `isAlive` on the persisted ref before continuing.
204
- // A classified-dead sandbox triggers a fresh `createSandbox` (task 4.6).
205
- // For first execution this is a cheap no-op; on replay it's the seam
206
- // that catches a dead pod between checkpoints.
207
- await DBOS.runStep(async () => {
208
- const liveness = await deps.sandboxClient.isAlive(sandbox);
209
- if (!liveness.alive) {
210
- const cause = liveness.oomKilled ? "was killed for exceeding its memory limit (sandbox-oom-killed)" : "no longer alive on replay";
211
- throw new Error(`sandbox ${sandbox.sandboxId} ${cause} — caller must restart the step`);
212
- }
213
- }, { name: "sandbox.recheck-alive" });
214
183
  // (3) runLoop — the harness loop, each call a durable step.
215
- // The step deadline is captured ONCE at step start from the checkpointed
216
- // clock so it reproduces on replay — a raw `Date.now()` here would grow the
217
- // deadline on each recovery (see the harness-durable-runtime spec). `deadlineMs` is a getter so
218
- // deps read the absolute ms without holding a captured value; returning a
219
- // fresh `now + timeout` per call would make a step with N tool calls runnable
220
- // for up to N × `timeoutSeconds`.
221
- const stepDeadlineMs = (await DBOS.now()) + (input.timeoutSeconds ?? DEFAULT_STEP_TIMEOUT_SECONDS) * 1000;
222
184
  const fileMetadata = createFileMetadataCell();
223
185
  const agent = await deps.buildAgent({
224
186
  input,
225
187
  session,
226
- sandbox,
188
+ lease,
227
189
  stepWritePrefix: writePrefix,
228
- deadlineMs: () => stepDeadlineMs,
229
190
  lineageCollector,
230
191
  reportsBlocker: true,
231
192
  fileMetadata,
@@ -237,7 +198,9 @@ export async function runSandboxStepBody(input, deps) {
237
198
  }
238
199
  // Built over THIS agent's tools, not a shared table: a sandbox agent's roster
239
200
  // differs per agent type, and each tool describes its own calls.
240
- const resolveDetail = createDetailResolver(agent.tools, logger);
201
+ // The declared tools and `run_script` describe the calls of the agent, and its script tools describe the nested calls.
202
+ const declared = withRunScript(agent).tools;
203
+ const resolveDetail = createDetailResolver([...declared, ...(agent.scriptTools ?? [])], logger);
241
204
  // Forward every loop event onto the child's own DBOS `"events"` stream
242
205
  // (the route fans-in parent + every active child stream — Pattern A).
243
206
  // Pre-shaped chat data parts (e.g. `data-sandbox-event` from `run-exec.ts`)
@@ -275,7 +238,7 @@ export async function runSandboxStepBody(input, deps) {
275
238
  const failStep = async (errorClass, err) => {
276
239
  if (isWorkflowCancellation(err))
277
240
  throw err;
278
- await tryTeardown(logger, deps, sandbox);
241
+ await tryRelease(logger, deps, childWorkflowId);
279
242
  const durationMs = (await DBOS.now()) - startedAt;
280
243
  // The scrub below is deliberate and load-bearing, which makes this record
281
244
  // the ONLY account of why the step died — the thrown error carries just the
@@ -283,7 +246,7 @@ export async function runSandboxStepBody(input, deps) {
283
246
  // `durationMs`, the iteration cap, and the last exec of the sandbox are
284
247
  // what separate a step that died in the model from one that died in the
285
248
  // machine. `lastExec` holds no stream text (see `exec-outcome.ts`).
286
- const lastExec = lastExecOutcome(sandbox.sandboxId);
249
+ const lastExec = lastExecOutcome(childWorkflowId);
287
250
  logger.error("step failure", {
288
251
  errorClass,
289
252
  durationMs,
@@ -369,7 +332,7 @@ export async function runSandboxStepBody(input, deps) {
369
332
  // The agent declares the output tool of the file-metadata follow-up
370
333
  // request from its first request, and the task must not use it.
371
334
  policy: {
372
- mask: maskExcept(agent.tools.map((tool) => tool.id), [SUBMIT_FILE_METADATA_TOOL_ID]),
335
+ mask: maskExcept(declared.map((tool) => tool.id), [SUBMIT_FILE_METADATA_TOOL_ID]),
373
336
  },
374
337
  // A blocker ends the step: the agent declared that it cannot do the work.
375
338
  stopWhen: (done) => blockerOf(done) !== null,
@@ -435,7 +398,7 @@ export async function runSandboxStepBody(input, deps) {
435
398
  const cappedOutEmpty = hitMaxSteps && manifest.length === 0;
436
399
  if (blockerOutcome || cappedOutEmpty) {
437
400
  const blockedReason = blockerOutcome?.reason ?? CAPPED_OUT_EMPTY_REASON;
438
- await tryTeardown(logger, deps, sandbox);
401
+ await tryRelease(logger, deps, childWorkflowId);
439
402
  const durationMs = (await DBOS.now()) - startedAt;
440
403
  await DBOS.runStep(async () => {
441
404
  unwrapOrThrow(await updateStepExecution(deps.pool, input.runId, input.stepId, {
@@ -479,7 +442,6 @@ export async function runSandboxStepBody(input, deps) {
479
442
  agent,
480
443
  fileMetadata,
481
444
  writePrefix,
482
- sandboxId: sandbox.sandboxId,
483
445
  lineageCollector,
484
446
  };
485
447
  await emitActivity("generating-metadata", "Describing output files");
@@ -552,8 +514,8 @@ export async function runSandboxStepBody(input, deps) {
552
514
  const stepOutputs = collectStepOutputs(postCtx, postArtifacts);
553
515
  await emitActivity("indexing", "Indexing outputs for search");
554
516
  await safeRun(logger, () => vectorIndexStepOutputs(deps, postCtx, postArtifacts), "post-step.vector-index");
555
- // (9) teardown — destroy sandbox + clear sandbox_ref.
556
- await DBOS.runStep(() => deps.sandboxClient.teardown(sandbox), { name: "sandbox.teardown" });
517
+ // (9) release — the lease workflow removes the machine of the step.
518
+ await deps.leases.release(childWorkflowId);
557
519
  // (10) mark-terminal + emit. The outcome metric is recorded inside the same
558
520
  // step as the ledger row: a replayed body reads the cached step and records
559
521
  // nothing again.
@@ -662,11 +624,14 @@ async function safeRunValue(logger, fn, label, fallback) {
662
624
  return fallback;
663
625
  }
664
626
  }
665
- async function tryTeardown(logger, deps, sandbox) {
627
+ /** Release the lease of a step that failed or blocked. The owner test of the lease workflow covers a failed release. */
628
+ async function tryRelease(logger, deps, leaseKey) {
666
629
  try {
667
- await DBOS.runStep(() => deps.sandboxClient.teardown(sandbox), { name: "sandbox.teardown" });
630
+ await deps.leases.release(leaseKey);
668
631
  }
669
632
  catch (err) {
670
- logger.warn("teardown failed (non-fatal)", logger.errorFields(err));
633
+ if (isWorkflowCancellation(err))
634
+ throw err;
635
+ logger.warn("release failed (non-fatal)", logger.errorFields(err));
671
636
  }
672
637
  }
@@ -7,7 +7,6 @@
7
7
  export declare const ANALYSIS_STATE_WRITE: RegExp;
8
8
  export declare const PAUSE_RECORD_WRITE: RegExp;
9
9
  export declare const PAUSE_CLEAR_WRITE: RegExp;
10
- export declare const PAUSES_OF_WORK_CLEAR_WRITE: RegExp;
11
10
  export declare const RUNS_STATUS_WRITE: RegExp;
12
11
  export declare const RUNS_STATUS_WRITE_ANY_CASE: RegExp;
13
12
  export declare const SYNTHESIS_STATUS_WRITE: RegExp;
@@ -10,8 +10,6 @@ export const ANALYSIS_STATE_WRITE = /UPDATE\s+cortex_analysis_state/i;
10
10
  export const PAUSE_RECORD_WRITE = /INSERT INTO cortex_paused_work/i;
11
11
  // oxlint-disable-next-line eslint-js/no-restricted-syntax -- the delete of the row of one waiting workflow
12
12
  export const PAUSE_CLEAR_WRITE = /DELETE FROM cortex_paused_work WHERE workflow_id = \$1/i;
13
- // oxlint-disable-next-line eslint-js/no-restricted-syntax -- the delete of each row of a pause of one work
14
- export const PAUSES_OF_WORK_CLEAR_WRITE = /DELETE FROM cortex_paused_work WHERE work_id = \$1/i;
15
13
  // oxlint-disable-next-line eslint-js/no-restricted-syntax -- the run status write, across wrapped SQL lines
16
14
  export const RUNS_STATUS_WRITE = /UPDATE cortex_runs\s+SET status/;
17
15
  // oxlint-disable-next-line eslint-js/no-restricted-syntax -- the run status write in any letter case, across wrapped SQL lines
@@ -5,8 +5,8 @@
5
5
  *
6
6
  * Reads resolve from the analysis's workspace root when the file is present and
7
7
  * fall back to an embedder-supplied presigned URL otherwise. The seam writes no
8
- * provenance — reads don't produce lineage — and does not depend on any
9
- * sandbox.
8
+ * provenance — a read tool records its reads in the lineage of a step itself —
9
+ * and does not depend on any sandbox.
10
10
  *
11
11
  * Scoping is enforced once, here, by `resolveWorkspacePath`: any path that
12
12
  * escapes the analysis's resolved workspace root is rejected before any I/O.
@@ -5,8 +5,8 @@
5
5
  *
6
6
  * Reads resolve from the analysis's workspace root when the file is present and
7
7
  * fall back to an embedder-supplied presigned URL otherwise. The seam writes no
8
- * provenance — reads don't produce lineage — and does not depend on any
9
- * sandbox.
8
+ * provenance — a read tool records its reads in the lineage of a step itself —
9
+ * and does not depend on any sandbox.
10
10
  *
11
11
  * Scoping is enforced once, here, by `resolveWorkspacePath`: any path that
12
12
  * escapes the analysis's resolved workspace root is rejected before any I/O.
@@ -37,7 +37,7 @@ import { scopeResource } from "../auth/types.js";
37
37
  import { tryFetch, tryFs, errnoOf } from "../lib/fs-result.js";
38
38
  import { splitLines } from "../lib/text.js";
39
39
  import { classifyWithinRoot } from "../lib/fs-helpers.js";
40
- import { resolveWorkspacePath } from "./paths.js";
40
+ import { HARNESS_PRIVATE_DIR, resolveWorkspacePath } from "./paths.js";
41
41
  // ── Constructor ───────────────────────────────────────────────────────────
42
42
  export function createWorkspaceFilesystem(deps) {
43
43
  const { resolveWorkspaceRoot, presignedFallback } = deps;
@@ -77,7 +77,7 @@ export function createWorkspaceFilesystem(deps) {
77
77
  op: "workspace.confineRealpath",
78
78
  path: resolved.absolute,
79
79
  cause,
80
- })).map((verdict) => (verdict === "escaped" ? { kind: "out_of_scope" } : resolved));
80
+ })).map((verdict) => (verdict === "escaped" || isHarnessPrivate(resolved.relative) ? { kind: "out_of_scope" } : resolved));
81
81
  });
82
82
  }
83
83
  return {
@@ -159,7 +159,8 @@ export function createWorkspaceFilesystem(deps) {
159
159
  return tryFs("workspace.list", () => fsReaddir(absolute, { withFileTypes: true }), { path: absolute, onAbsent: () => ({ notFound: true }) }).andThen((dirents) => {
160
160
  if ("notFound" in dirents)
161
161
  return okAsync({ kind: "not_found" });
162
- return collectEntries(absolute, dirents).map((entries) => ({ kind: "ok", entries }));
162
+ const visible = resolved.relative === "" ? dirents.filter((d) => d.name !== HARNESS_PRIVATE_DIR) : dirents;
163
+ return collectEntries(absolute, visible).map((entries) => ({ kind: "ok", entries }));
163
164
  });
164
165
  });
165
166
  });
@@ -190,6 +191,10 @@ export function createWorkspaceFilesystem(deps) {
190
191
  };
191
192
  }
192
193
  // ── Helpers ───────────────────────────────────────────────────────────────
194
+ /** True for a root-relative path inside the private directory of the harness, which no workspace tool reaches. */
195
+ function isHarnessPrivate(relative) {
196
+ return relative === HARNESS_PRIVATE_DIR || relative.startsWith(`${HARNESS_PRIVATE_DIR}/`);
197
+ }
193
198
  /** A bare `ok(value)` as a `ResultAsync`, with no `fs` call to wrap. */
194
199
  function okAsync(value) {
195
200
  return new ResultAsync(Promise.resolve(ok(value)));
@@ -181,6 +181,12 @@ export declare function toSandboxPath(workspaceRoot: string, resourceId: string,
181
181
  * the resolver as `out_of_scope`. A prefix would only make it look canonical.
182
182
  */
183
183
  export declare function toAnalysisRootPath(resourceId: string, path: string): string;
184
+ /**
185
+ * Where the embedder stages the inputs of an analysis, workspace-root-relative
186
+ * (see the data-profile-init spec). The inputs are immutable: no file tool of the
187
+ * harness writes under this directory.
188
+ */
189
+ export declare const STAGED_INPUT_ROOT = "data/inputs";
184
190
  /**
185
191
  * Parent of every report session, workspace-root-relative. Callers that
186
192
  * enumerate the sessions join this. Callers that address one join
@@ -199,10 +205,17 @@ export declare function reportSessionDir(threadId: string): string;
199
205
  * Directory of the derived tables of one report session, workspace-root-relative.
200
206
  *
201
207
  * The derivation writes its output here, and the record prunes an output that the recorded document does
202
- * not name. Both spell the segment through this helper, thus the write mount and the prune scope cannot
208
+ * not name. Both spell the segment through this helper, thus the write and the prune scope cannot
203
209
  * disagree.
204
210
  */
205
211
  export declare function reportSessionDerivedDir(threadId: string): string;
212
+ /** Parent of the scratch directory of each conversation, workspace-root-relative. */
213
+ export declare const CONVERSATION_SESSIONS_ROOT = "conversation-sessions";
214
+ /**
215
+ * The scratch directory of one conversation, workspace-root-relative: the scripts that its turns ran, and the
216
+ * files that those scripts derived.
217
+ */
218
+ export declare function conversationSessionDir(threadId: string): string;
206
219
  /**
207
220
  * True when `value` is a safe single path segment: the safe-id charset (word
208
221
  * chars, `.`, `-` — no slash, NUL, or shell-hostile char) AND not a pure-dot
@@ -221,6 +234,11 @@ export declare function isSafeId(value: string): boolean;
221
234
  * (the container-path source of truth) validate the same way.
222
235
  */
223
236
  export declare function assertSafeId(value: string, label: string): void;
237
+ /**
238
+ * The directory of the harness under each workspace root: the exec records of the sandbox leases. No workspace tool
239
+ * lists or reads it, and no artifact walk reaches it.
240
+ */
241
+ export declare const HARNESS_PRIVATE_DIR = ".sandbox";
224
242
  /** Root directory for a specific workflow run, workspace-root-relative. */
225
243
  export declare function runDir(runId: string): string;
226
244
  /** Step directory within a run, workspace-root-relative. */
@@ -197,6 +197,12 @@ function normalizeTail(tail) {
197
197
  return "";
198
198
  return normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
199
199
  }
200
+ /**
201
+ * Where the embedder stages the inputs of an analysis, workspace-root-relative
202
+ * (see the data-profile-init spec). The inputs are immutable: no file tool of the
203
+ * harness writes under this directory.
204
+ */
205
+ export const STAGED_INPUT_ROOT = "data/inputs";
200
206
  /**
201
207
  * Parent of every report session, workspace-root-relative. Callers that
202
208
  * enumerate the sessions join this. Callers that address one join
@@ -218,12 +224,22 @@ export function reportSessionDir(threadId) {
218
224
  * Directory of the derived tables of one report session, workspace-root-relative.
219
225
  *
220
226
  * The derivation writes its output here, and the record prunes an output that the recorded document does
221
- * not name. Both spell the segment through this helper, thus the write mount and the prune scope cannot
227
+ * not name. Both spell the segment through this helper, thus the write and the prune scope cannot
222
228
  * disagree.
223
229
  */
224
230
  export function reportSessionDerivedDir(threadId) {
225
231
  return `${reportSessionDir(threadId)}/derived`;
226
232
  }
233
+ /** Parent of the scratch directory of each conversation, workspace-root-relative. */
234
+ export const CONVERSATION_SESSIONS_ROOT = "conversation-sessions";
235
+ /**
236
+ * The scratch directory of one conversation, workspace-root-relative: the scripts that its turns ran, and the
237
+ * files that those scripts derived.
238
+ */
239
+ export function conversationSessionDir(threadId) {
240
+ assertSafeId(threadId, "threadId");
241
+ return `${CONVERSATION_SESSIONS_ROOT}/${threadId}`;
242
+ }
227
243
  /**
228
244
  * For an analysis-rooted input (`/{analysisId}/...`), strip the leading
229
245
  * `/{analysisId}/` segment so it can be resolved relative to the analysis
@@ -275,6 +291,11 @@ export function assertSafeId(value, label) {
275
291
  throw new Error(`Invalid ${label}: ${value}`);
276
292
  }
277
293
  }
294
+ /**
295
+ * The directory of the harness under each workspace root: the exec records of the sandbox leases. No workspace tool
296
+ * lists or reads it, and no artifact walk reaches it.
297
+ */
298
+ export const HARNESS_PRIVATE_DIR = ".sandbox";
278
299
  /** Root directory for a specific workflow run, workspace-root-relative. */
279
300
  export function runDir(runId) {
280
301
  assertSafeId(runId, "runId");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inflexa-ai/harness",
3
- "version": "0.46.0",
3
+ "version": "0.47.0",
4
4
  "description": "Host-agnostic bioinformatics agent harness — an agent loop on the AI SDK, DBOS-durable workflows, sandboxed compute.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -11,6 +11,7 @@
11
11
  "types": "./dist/index.d.ts",
12
12
  "import": "./dist/index.js"
13
13
  },
14
+ "./tools/script/quickjs.wasm": "./dist/tools/script/quickjs.wasm",
14
15
  "./*.js": {
15
16
  "types": "./dist/*.d.ts",
16
17
  "import": "./dist/*.js"
@@ -58,6 +59,7 @@
58
59
  "@anthropic-ai/sdk": "^0.116.0",
59
60
  "@dbos-inc/dbos-sdk": "^5.3.0",
60
61
  "@dbos-inc/vercel-ai": "0.5.4",
62
+ "@earendil-works/pi-codemode": "1.4.0",
61
63
  "@fontsource-variable/space-grotesk": "5.1.1",
62
64
  "@fontsource/ibm-plex-mono": "5.1.1",
63
65
  "@inflexa-ai/tsprov": "0.6.0",
@@ -86,15 +88,17 @@
86
88
  "js-tiktoken": "^1.0.21",
87
89
  "kysely": "^0.29.6",
88
90
  "linkedom": "^0.18.14",
91
+ "marked": "^18.1.2",
89
92
  "mime": "^4.1.0",
90
93
  "nanoid": "^5.1.16",
91
94
  "neverthrow": "^8.2.0",
92
95
  "openai": "^7.4.0",
93
96
  "p-queue": "^9.3.3",
94
- "pdf-parse": "^2.4.5",
97
+ "pdfmake": "^0.3.13",
95
98
  "pg": "^8.22.0",
96
99
  "puppeteer-core": "^23.11.1",
97
100
  "turndown": "^7.2.4",
101
+ "unpdf": "^1.9.1",
98
102
  "zod": "^4.4.3"
99
103
  },
100
104
  "overrides": {
@@ -111,7 +115,6 @@
111
115
  "@types/bun": "latest",
112
116
  "@types/json-schema": "^7.0.15",
113
117
  "@types/node": "^24.13.3",
114
- "@types/pdf-parse": "^1.1.5",
115
118
  "@typescript/native": "npm:typescript@7.0.2",
116
119
  "csstree-validator": "^4.0.1",
117
120
  "html-validate": "^11.6.2",
@@ -1 +0,0 @@
1
- export declare const literatureReviewerPrompt = "# Literature Reviewer\n\nYou are a systematic biology investigator. You receive a research brief\ncontaining genes, pathways, or biological features to investigate, along\nwith the experimental context. Your job is to use your tools to build a\ncomprehensive evidence profile and return a structured report.\n\nYou are task-oriented \u2014 you do not interact with the user. You receive\na brief, investigate, and return results.\n\n## Tool usage notes (read before calling any tool)\n\nSeveral tools have strict input shapes. Mis-shaping your call wastes a\nturn and forces a retry.\n\n- `pubmed({action:\"search\"})` \u2014 pass a focused query string. Shape: `{ action: \"search\", query: \"BRCA1 AND breast cancer\", maxResults: 10 }`.\n- `pubmed({action:\"details\"})` \u2014 takes `pmids`, a non-empty array of\n PMID strings. Shape: `{ action: \"details\", pmids: [\"12345678\", \"87654321\"] }`.\n- `search_interactions` \u2014 accepts up to **100 identifiers per call**. If you\n need more, **batch across multiple calls** rather than truncating your\n list. Shape: `{ identifiers: [\"TP53\", \"BRCA1\", ...], limit: 50 }`.\n- `search_gene` \u2014 batches up to 200 symbols; `lookup_annotation` takes one\n term or accession per call.\n- `search_protein` \u2014 one gene symbol or UniProt accession per call. It answers\n with the protein: the accession, the recommended name, the sequence length,\n the curated function, and the subcellular locations. An empty `proteins`\n array is a valid \"no data\" outcome; do NOT retry the same call.\n- `gene_preclinical_profile` \u2014 single human gene symbol per call; returns\n baseline expression and mouse-KO phenotype together. Empty/null fields are\n valid \"no data\" outcomes (Bgee may have no calls for dog or macaque; many\n human genes are not yet IMPC-phenotyped). Do NOT retry on empty output.\n- `pubmed({action:\"fulltext\"})` \u2014 single `pmcId` per call (from a `details` result). Use sparingly.\n- `resolve_citation` \u2014 verify one citation already supplied in the brief or returned by another source. Use it for identity/metadata verification, not topical discovery. Preserve its verdict and coverage exactly: `inconclusive` is not `not_found`, and an unavailable authority is not proof of fabrication.\n\nBatching beats truncation: if you have 300 identifiers to look up in\n`search_interactions`, make three calls of 100 rather than one call of 100\nthat silently drops the other 200.\n\n## What Each Tool Answers\n\nPick the lookups that the brief's question needs for each target:\n\n- **The gene** \u2014 `search_gene`: its Ensembl ID, coordinates, biotype, and\n aliases.\n- **The protein** \u2014 `search_protein`, when the claim is about the protein\n rather than the locus: what it does, how long it is, and where in the cell\n it acts.\n- **Pathway context** \u2014 `lookup_annotation({vocabulary:\"pathways\"})`. Note\n which pathways connect multiple genes from the brief.\n- **Function** \u2014 `lookup_annotation({vocabulary:\"go\"})`, when the gene's\n role is unclear from the gene search alone.\n- **Interaction partners** \u2014 `search_interactions`, especially partners\n that also appear in the brief.\n- **Literature evidence** \u2014 `pubmed({action:\"search\"})` with queries that\n combine the gene with the disease or condition from the brief, then\n `details` for the most relevant hits, and `fulltext` only for papers\n that need deeper reading.\n- **Preclinical grounding** \u2014 `gene_preclinical_profile`, when the brief\n asks about tissue expression, model-organism suitability, or KO\n consequences.\n\n## Depth Guidelines\n\n- **Top priority targets** (top DE genes, hub genes, user-specified) get\n the deepest evidence profile.\n- **Supporting targets** (enriched pathways, interaction partners) need\n the gene, its pathway context, and its literature; add function and\n interactions only when those results are ambiguous.\n- **Limit PubMed searches**: max 2-3 queries per gene. Combine terms\n effectively (e.g., \"BRCA1 AND breast cancer AND RNA-seq\") rather\n than running many narrow queries.\n- **Limit article deep-reads**: use `pubmed({action:\"fulltext\"})` on at most\n 3-5 articles total, only for papers directly relevant to the\n experimental context.\n\n## Output Format\n\nReturn a structured report in this format:\n\n### Evidence Summary\n\nFor each investigated target:\n\n**[Gene/Pathway Name]**\n- **Function**: Brief description of known function\n- **Relevance to context**: How it connects to the experimental\n conditions (e.g., \"known role in AD pathology\")\n- **Literature support**: Key findings from PubMed (cite PMIDs)\n- **Interaction network**: Notable interaction partners found,\n especially those also in the data\n- **Novelty assessment**: Is this finding well-established, recently\n emerging, or potentially novel?\n\n### Cross-Target Patterns\n- Genes that share pathways or interact directly\n- Convergent biological themes (e.g., multiple genes in immune signaling)\n- Contradictions or unexpected associations\n\n### Key References\n- List the most important PMIDs with one-line descriptions\n\n## Do NOT\n\n- **Fabricate results.** If a search returns no results, say so.\n- **Turn resolver uncertainty into certainty.** Never describe a weak candidate, partial coverage, or an unavailable source as proof that a citation is fabricated.\n- **Skip tool calls.** Do not claim knowledge about a gene without\n looking it up. Your value is in systematic, verified investigation.\n- **Over-read articles.** Use `pubmed({action:\"fulltext\"})` sparingly \u2014 abstracts\n from `pubmed({action:\"details\"})` are sufficient for most assessments.\n- **Investigate targets not in the brief.** Stay focused on what was\n requested. Note interesting leads for follow-up but do not chase them.\n- **Produce conversational text.** You are returning a structured report,\n not chatting with a user.\n";