@inflexa-ai/harness 0.39.2 → 0.41.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 (184) hide show
  1. package/README.md +2 -3
  2. package/dist/agents/conversation-agent.d.ts +3 -0
  3. package/dist/agents/conversation-agent.js +13 -2
  4. package/dist/agents/report-session-agent.d.ts +3 -0
  5. package/dist/agents/report-session-agent.js +2 -0
  6. package/dist/agents/sandbox/cheminformatics-agent.js +0 -3
  7. package/dist/agents/sandbox/shared.d.ts +11 -2
  8. package/dist/agents/sandbox/shared.js +4 -0
  9. package/dist/app/chat-turn.d.ts +104 -12
  10. package/dist/app/chat-turn.js +180 -21
  11. package/dist/app/message-assembly.d.ts +16 -52
  12. package/dist/app/message-assembly.js +41 -68
  13. package/dist/app/run-activity.d.ts +4 -6
  14. package/dist/app/run-activity.js +7 -27
  15. package/dist/app/spawn-report-session.d.ts +6 -11
  16. package/dist/app/spawn-report-session.js +5 -6
  17. package/dist/app/synthesize-run.d.ts +3 -0
  18. package/dist/app/synthesize-run.js +1 -0
  19. package/dist/billing/usage-recorder.d.ts +3 -4
  20. package/dist/billing/usage-recorder.js +3 -4
  21. package/dist/contracts/chat-parts.d.ts +11 -1
  22. package/dist/contracts/chat-parts.js +1 -0
  23. package/dist/contracts/index.d.ts +1 -1
  24. package/dist/contracts/part-registry.js +1 -0
  25. package/dist/contracts/report-blocks.d.ts +8 -8
  26. package/dist/contracts/schemas/chat-parts.d.ts +32 -9
  27. package/dist/contracts/schemas/chat-parts.js +10 -0
  28. package/dist/execution/artifact-metadata.d.ts +26 -33
  29. package/dist/execution/artifact-metadata.js +44 -106
  30. package/dist/execution/post-step-pipeline.d.ts +23 -16
  31. package/dist/execution/post-step-pipeline.js +19 -21
  32. package/dist/execution/run-synthesis.d.ts +3 -0
  33. package/dist/execution/run-synthesis.js +11 -1
  34. package/dist/execution/step-summary.d.ts +21 -30
  35. package/dist/execution/step-summary.js +30 -57
  36. package/dist/index.d.ts +9 -8
  37. package/dist/index.js +15 -34
  38. package/dist/lib/zod-issues.js +2 -4
  39. package/dist/loop/compaction.d.ts +20 -0
  40. package/dist/loop/compaction.js +2 -0
  41. package/dist/loop/continue-agent.d.ts +40 -0
  42. package/dist/loop/continue-agent.js +38 -0
  43. package/dist/loop/metrics.d.ts +30 -22
  44. package/dist/loop/metrics.js +51 -22
  45. package/dist/loop/run-agent.d.ts +111 -13
  46. package/dist/loop/run-agent.js +467 -220
  47. package/dist/loop/run-to-terminal.d.ts +16 -17
  48. package/dist/loop/run-to-terminal.js +24 -35
  49. package/dist/loop/tool-mask.d.ts +23 -0
  50. package/dist/loop/tool-mask.js +46 -0
  51. package/dist/loop/tool-output.d.ts +48 -0
  52. package/dist/loop/tool-output.js +79 -0
  53. package/dist/loop/types.d.ts +6 -3
  54. package/dist/memory/ai-sdk-message-storage.d.ts +49 -2
  55. package/dist/memory/ai-sdk-message-storage.js +78 -2
  56. package/dist/memory/conversation-display-recorder.d.ts +6 -0
  57. package/dist/memory/conversation-display-recorder.js +64 -22
  58. package/dist/memory/conversation-display-replay.d.ts +5 -14
  59. package/dist/memory/conversation-display-replay.js +66 -32
  60. package/dist/memory/conversation-display-storage.d.ts +2 -1
  61. package/dist/memory/conversation-display-storage.js +2 -1
  62. package/dist/memory/conversation-view.d.ts +53 -0
  63. package/dist/memory/conversation-view.js +130 -0
  64. package/dist/memory/count-tokens.d.ts +5 -7
  65. package/dist/memory/count-tokens.js +7 -9
  66. package/dist/memory/thread-history.d.ts +74 -102
  67. package/dist/memory/thread-history.js +181 -246
  68. package/dist/memory/thread-store.d.ts +3 -3
  69. package/dist/memory/thread-store.js +7 -2
  70. package/dist/memory/tool-call-integrity.d.ts +15 -21
  71. package/dist/memory/tool-call-integrity.js +31 -38
  72. package/dist/memory/working-memory.d.ts +2 -5
  73. package/dist/memory/working-memory.js +2 -5
  74. package/dist/prompts/SOUL.d.ts +2 -2
  75. package/dist/prompts/SOUL.js +2 -7
  76. package/dist/prompts/analogical-reasoner.d.ts +1 -1
  77. package/dist/prompts/analogical-reasoner.js +16 -32
  78. package/dist/prompts/briefing.js +1 -1
  79. package/dist/prompts/compaction.d.ts +4 -0
  80. package/dist/prompts/compaction.js +24 -0
  81. package/dist/prompts/conversation.d.ts +1 -1
  82. package/dist/prompts/conversation.js +26 -35
  83. package/dist/prompts/execute-analysis/step-summary.js +7 -5
  84. package/dist/prompts/literature-reviewer.d.ts +1 -1
  85. package/dist/prompts/literature-reviewer.js +29 -34
  86. package/dist/prompts/planner.js +15 -29
  87. package/dist/prompts/report-session.d.ts +1 -1
  88. package/dist/prompts/report-session.js +2 -33
  89. package/dist/prompts/sandbox/bulk-transcriptomics-agent.d.ts +1 -1
  90. package/dist/prompts/sandbox/bulk-transcriptomics-agent.js +1 -2
  91. package/dist/prompts/sandbox/cheminformatics-agent.d.ts +1 -1
  92. package/dist/prompts/sandbox/cheminformatics-agent.js +10 -29
  93. package/dist/prompts/sandbox/chromatin-agent.d.ts +1 -1
  94. package/dist/prompts/sandbox/chromatin-agent.js +1 -2
  95. package/dist/prompts/sandbox/data-profiler.js +6 -3
  96. package/dist/prompts/sandbox/dna-methylation-agent.d.ts +1 -1
  97. package/dist/prompts/sandbox/dna-methylation-agent.js +2 -7
  98. package/dist/prompts/sandbox/drug-repurposing-agent.d.ts +1 -1
  99. package/dist/prompts/sandbox/drug-repurposing-agent.js +20 -24
  100. package/dist/prompts/sandbox/enrichment-agent.d.ts +1 -1
  101. package/dist/prompts/sandbox/enrichment-agent.js +3 -2
  102. package/dist/prompts/sandbox/genomic-variant-agent.d.ts +1 -1
  103. package/dist/prompts/sandbox/genomic-variant-agent.js +0 -2
  104. package/dist/prompts/sandbox/immune-profiling-agent.d.ts +1 -1
  105. package/dist/prompts/sandbox/immune-profiling-agent.js +7 -14
  106. package/dist/prompts/sandbox/metabolomics-agent.d.ts +1 -1
  107. package/dist/prompts/sandbox/metabolomics-agent.js +0 -2
  108. package/dist/prompts/sandbox/microbiome-agent.d.ts +1 -1
  109. package/dist/prompts/sandbox/microbiome-agent.js +14 -21
  110. package/dist/prompts/sandbox/multi-omics-integration-agent.d.ts +1 -1
  111. package/dist/prompts/sandbox/multi-omics-integration-agent.js +9 -19
  112. package/dist/prompts/sandbox/multimodal-sc-agent.d.ts +1 -1
  113. package/dist/prompts/sandbox/multimodal-sc-agent.js +4 -5
  114. package/dist/prompts/sandbox/network-agent.d.ts +1 -1
  115. package/dist/prompts/sandbox/network-agent.js +7 -8
  116. package/dist/prompts/sandbox/pkpd-clinical-response-agent.d.ts +1 -1
  117. package/dist/prompts/sandbox/pkpd-clinical-response-agent.js +19 -19
  118. package/dist/prompts/sandbox/proteomics-agent.d.ts +1 -1
  119. package/dist/prompts/sandbox/proteomics-agent.js +3 -4
  120. package/dist/prompts/sandbox/scientific-executor.d.ts +1 -1
  121. package/dist/prompts/sandbox/scientific-executor.js +7 -10
  122. package/dist/prompts/sandbox/single-cell-agent.d.ts +1 -1
  123. package/dist/prompts/sandbox/single-cell-agent.js +2 -4
  124. package/dist/prompts/sandbox/spatial-omics-agent.d.ts +1 -1
  125. package/dist/prompts/sandbox/spatial-omics-agent.js +3 -5
  126. package/dist/prompts/sandbox/statistical-modeling-agent.d.ts +1 -1
  127. package/dist/prompts/sandbox/statistical-modeling-agent.js +1 -1
  128. package/dist/prompts/sandbox/translational-safety-agent.d.ts +1 -1
  129. package/dist/prompts/sandbox/translational-safety-agent.js +10 -19
  130. package/dist/prompts/sandbox-standards.d.ts +7 -7
  131. package/dist/prompts/sandbox-standards.js +74 -76
  132. package/dist/prompts/synthesis-agent.d.ts +1 -1
  133. package/dist/prompts/synthesis-agent.js +16 -42
  134. package/dist/providers/ai-sdk.d.ts +39 -6
  135. package/dist/providers/ai-sdk.js +117 -18
  136. package/dist/providers/errors.d.ts +2 -0
  137. package/dist/providers/errors.js +5 -0
  138. package/dist/providers/prompt-cache.d.ts +43 -41
  139. package/dist/providers/prompt-cache.js +47 -41
  140. package/dist/providers/reasoning.d.ts +10 -21
  141. package/dist/providers/reasoning.js +10 -21
  142. package/dist/providers/types.d.ts +21 -5
  143. package/dist/report-model/authoring-grammar.d.ts +2 -2
  144. package/dist/runtime/assemble.d.ts +9 -7
  145. package/dist/runtime/assemble.js +7 -3
  146. package/dist/sandbox/create-sandbox.d.ts +2 -2
  147. package/dist/sandbox/submit-exec.d.ts +1 -1
  148. package/dist/schemas/artifact-manifest.d.ts +4 -4
  149. package/dist/state/init.js +39 -4
  150. package/dist/state/purge-analysis.js +4 -0
  151. package/dist/state/tool-outputs.d.ts +4 -0
  152. package/dist/state/tool-outputs.js +30 -0
  153. package/dist/tasks/data-profile.d.ts +3 -0
  154. package/dist/tasks/data-profile.js +2 -0
  155. package/dist/tools/ad-hoc-router.d.ts +8 -0
  156. package/dist/tools/ad-hoc-router.js +21 -2
  157. package/dist/tools/display/show-user.d.ts +1 -1
  158. package/dist/tools/execute-analysis.d.ts +3 -0
  159. package/dist/tools/execute-analysis.js +9 -1
  160. package/dist/tools/read-tool-output.d.ts +7 -0
  161. package/dist/tools/read-tool-output.js +130 -0
  162. package/dist/tools/research/generate-analogy-report.d.ts +10 -26
  163. package/dist/tools/research/generate-analogy-report.js +76 -163
  164. package/dist/tools/research/generate-plan.d.ts +3 -0
  165. package/dist/tools/research/generate-plan.js +4 -1
  166. package/dist/tools/research/literature-reviewer.d.ts +3 -0
  167. package/dist/tools/research/literature-reviewer.js +3 -0
  168. package/dist/tools/sandbox/report-blocker.d.ts +18 -5
  169. package/dist/tools/sandbox/report-blocker.js +54 -6
  170. package/dist/tools/sandbox/submit-file-metadata.d.ts +30 -0
  171. package/dist/tools/sandbox/submit-file-metadata.js +67 -0
  172. package/dist/tools/start-report-session.d.ts +1 -3
  173. package/dist/tools/start-report-session.js +0 -2
  174. package/dist/tools/workspace/execute-command.js +7 -4
  175. package/dist/tools/workspace/result-bounds.d.ts +4 -4
  176. package/dist/tools/workspace/result-bounds.js +6 -5
  177. package/dist/tools/workspace/workspace-search.d.ts +1 -1
  178. package/dist/workflows/execute-analysis.d.ts +3 -0
  179. package/dist/workflows/execute-analysis.js +1 -0
  180. package/dist/workflows/sandbox-step.d.ts +30 -9
  181. package/dist/workflows/sandbox-step.js +37 -8
  182. package/package.json +9 -9
  183. package/dist/providers/anthropic.d.ts +0 -25
  184. package/dist/providers/anthropic.js +0 -28
@@ -204,7 +204,6 @@ export declare const DagStatePartSchema: z.ZodObject<{
204
204
  export declare const StepPhaseSchema: z.ZodEnum<{
205
205
  failed: "failed";
206
206
  complete: "complete";
207
- warning: "warning";
208
207
  "sandbox-init": "sandbox-init";
209
208
  executing: "executing";
210
209
  "generating-metadata": "generating-metadata";
@@ -212,6 +211,7 @@ export declare const StepPhaseSchema: z.ZodEnum<{
212
211
  indexing: "indexing";
213
212
  persisting: "persisting";
214
213
  retrying: "retrying";
214
+ warning: "warning";
215
215
  }>;
216
216
  export declare const StepActivityPartSchema: z.ZodObject<{
217
217
  type: z.ZodLiteral<"data-step-activity">;
@@ -221,7 +221,6 @@ export declare const StepActivityPartSchema: z.ZodObject<{
221
221
  phase: z.ZodEnum<{
222
222
  failed: "failed";
223
223
  complete: "complete";
224
- warning: "warning";
225
224
  "sandbox-init": "sandbox-init";
226
225
  executing: "executing";
227
226
  "generating-metadata": "generating-metadata";
@@ -229,6 +228,7 @@ export declare const StepActivityPartSchema: z.ZodObject<{
229
228
  indexing: "indexing";
230
229
  persisting: "persisting";
231
230
  retrying: "retrying";
231
+ warning: "warning";
232
232
  }>;
233
233
  activity: z.ZodString;
234
234
  }, z.core.$strip>;
@@ -260,9 +260,9 @@ export declare const StepOutputFileSchema: z.ZodObject<{
260
260
  fileType: z.ZodEnum<{
261
261
  output: "output";
262
262
  summary: "summary";
263
- log: "log";
264
- figure: "figure";
265
263
  script: "script";
264
+ figure: "figure";
265
+ log: "log";
266
266
  notebook: "notebook";
267
267
  }>;
268
268
  description: z.ZodString;
@@ -279,9 +279,9 @@ export declare const StepOutputPartSchema: z.ZodObject<{
279
279
  fileType: z.ZodEnum<{
280
280
  output: "output";
281
281
  summary: "summary";
282
- log: "log";
283
- figure: "figure";
284
282
  script: "script";
283
+ figure: "figure";
284
+ log: "log";
285
285
  notebook: "notebook";
286
286
  }>;
287
287
  description: z.ZodString;
@@ -473,6 +473,18 @@ export declare const ReportRenderedPartSchema: z.ZodObject<{
473
473
  renderedAt: z.ZodString;
474
474
  title: z.ZodOptional<z.ZodString>;
475
475
  }, z.core.$strip>;
476
+ export declare const CompactionPartSchema: z.ZodObject<{
477
+ type: z.ZodLiteral<"data-compaction">;
478
+ id: z.ZodString;
479
+ status: z.ZodEnum<{
480
+ failed: "failed";
481
+ done: "done";
482
+ running: "running";
483
+ }>;
484
+ tokensBefore: z.ZodNumber;
485
+ tokensAfter: z.ZodOptional<z.ZodNumber>;
486
+ durationMs: z.ZodOptional<z.ZodNumber>;
487
+ }, z.core.$strip>;
476
488
  export declare const CortexChatPartSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
477
489
  type: z.ZodLiteral<"data-presentation">;
478
490
  id: z.ZodString;
@@ -592,7 +604,6 @@ export declare const CortexChatPartSchema: z.ZodDiscriminatedUnion<[z.ZodObject<
592
604
  phase: z.ZodEnum<{
593
605
  failed: "failed";
594
606
  complete: "complete";
595
- warning: "warning";
596
607
  "sandbox-init": "sandbox-init";
597
608
  executing: "executing";
598
609
  "generating-metadata": "generating-metadata";
@@ -600,6 +611,7 @@ export declare const CortexChatPartSchema: z.ZodDiscriminatedUnion<[z.ZodObject<
600
611
  indexing: "indexing";
601
612
  persisting: "persisting";
602
613
  retrying: "retrying";
614
+ warning: "warning";
603
615
  }>;
604
616
  activity: z.ZodString;
605
617
  }, z.core.$strip>, z.ZodObject<{
@@ -627,9 +639,9 @@ export declare const CortexChatPartSchema: z.ZodDiscriminatedUnion<[z.ZodObject<
627
639
  fileType: z.ZodEnum<{
628
640
  output: "output";
629
641
  summary: "summary";
630
- log: "log";
631
- figure: "figure";
632
642
  script: "script";
643
+ figure: "figure";
644
+ log: "log";
633
645
  notebook: "notebook";
634
646
  }>;
635
647
  description: z.ZodString;
@@ -765,4 +777,15 @@ export declare const CortexChatPartSchema: z.ZodDiscriminatedUnion<[z.ZodObject<
765
777
  id: z.ZodString;
766
778
  renderedAt: z.ZodString;
767
779
  title: z.ZodOptional<z.ZodString>;
780
+ }, z.core.$strip>, z.ZodObject<{
781
+ type: z.ZodLiteral<"data-compaction">;
782
+ id: z.ZodString;
783
+ status: z.ZodEnum<{
784
+ failed: "failed";
785
+ done: "done";
786
+ running: "running";
787
+ }>;
788
+ tokensBefore: z.ZodNumber;
789
+ tokensAfter: z.ZodOptional<z.ZodNumber>;
790
+ durationMs: z.ZodOptional<z.ZodNumber>;
768
791
  }, z.core.$strip>], "type">;
@@ -299,6 +299,15 @@ export const ReportRenderedPartSchema = z.object({
299
299
  renderedAt: z.string(),
300
300
  title: z.string().optional(),
301
301
  });
302
+ // ── Compaction ──────────────────────────────────────────────────────
303
+ export const CompactionPartSchema = z.object({
304
+ type: z.literal("data-compaction"),
305
+ id: z.string(),
306
+ status: z.enum(["running", "done", "failed"]),
307
+ tokensBefore: z.number().int().nonnegative(),
308
+ tokensAfter: z.number().int().nonnegative().optional(),
309
+ durationMs: z.number().int().nonnegative().optional(),
310
+ });
302
311
  // ── Union ───────────────────────────────────────────────────────────
303
312
  export const CortexChatPartSchema = z.discriminatedUnion("type", [
304
313
  PresentationPartSchema,
@@ -320,4 +329,5 @@ export const CortexChatPartSchema = z.discriminatedUnion("type", [
320
329
  RunFailedPartSchema,
321
330
  ChildSessionStartedPartSchema,
322
331
  ReportRenderedPartSchema,
332
+ CompactionPartSchema,
323
333
  ]);
@@ -1,29 +1,26 @@
1
1
  /**
2
2
  * Per-step artifact metadata generation — describes a step's output files
3
- * via a focused `runAgent` tool-call loop on the harness `ChatProvider`.
3
+ * through a continuation of the step agent's conversation (`continueAgent`)
4
+ * on the harness `ChatProvider`.
4
5
  *
5
- * Design (mirrors `generate-plan.ts`): the describer agent communicates
6
- * exclusively through one tool, `submit_file_metadata`, whose `execute`
7
- * validates every entry's `path` against the known artifact set. Unknown
8
- * paths (hallucinated files) are rejected with feedback; uncovered files are
9
- * reported as `remaining` so the model resubmits. Descriptions are matched
10
- * to files BY PATH — there is no positional array-index alignment, so a
11
- * dropped, reordered, or extra entry can never attach a description to the
12
- * wrong file.
6
+ * The continuation extends the prefix that the task cached, so each signed
7
+ * thinking block of the task must stay valid across the extension.
8
+ *
9
+ * Descriptions match files BY PATH, not by array index, so a dropped,
10
+ * reordered, or extra entry can never attach to the wrong file.
13
11
  *
14
12
  * Lossless contract: every input artifact appears in the result exactly
15
- * once. A file the model never describes (loop budget exhausted, persistent
16
- * tool errors, model refusal) gets a deterministic fallback description
17
- * synthesised from its path + inferred type + size. Fallbacks are logged —
18
- * never silently dropped, never silently chunked out.
13
+ * once. A file the model never describes gets a deterministic fallback
14
+ * description, logged, never silently dropped.
19
15
  *
20
16
  * `Session` is taken explicitly (see the harness-durable-runtime spec) — billing is a compile-time
21
17
  * obligation on every provider call.
22
18
  */
23
19
  import type { AgentSession } from "../auth/types.js";
24
- import type { LoopMessage } from "../loop/types.js";
20
+ import { type ToolOutputStore } from "../loop/tool-output.js";
21
+ import type { AgentDefinition, LoopMessage } from "../loop/types.js";
25
22
  import type { AgentChat } from "../providers/types.js";
26
- import type { WorkspaceFilesystem } from "../workspace/filesystem.js";
23
+ import { type FileMetadataCell } from "../tools/sandbox/submit-file-metadata.js";
27
24
  import type { Logger } from "../lib/logger.js";
28
25
  import type { UsageRecorder } from "../billing/usage-recorder.js";
29
26
  export interface ArtifactForMetadata {
@@ -37,28 +34,26 @@ export interface ArtifactForMetadata {
37
34
  export interface GenerateFileMetadataOptions {
38
35
  /** Operational logging seam; omitted falls back to no-op. */
39
36
  readonly logger?: Logger;
40
- /** LLM usage-accounting seam for the describer loop; omitted falls back to the no-op recorder. */
37
+ /** LLM usage-accounting seam for the continuation; omitted falls back to the no-op recorder. */
41
38
  readonly usageRecorder?: UsageRecorder;
39
+ /** The store of the task, thus the loop of the continuation keeps its texts where the read tool of the agent reads them. */
40
+ readonly toolOutputStore?: ToolOutputStore;
41
+ /** The provider of the task, thus each request extends the prefix that the task cached. */
42
42
  readonly provider: AgentChat;
43
43
  readonly session: AgentSession;
44
44
  readonly artifacts: readonly ArtifactForMetadata[];
45
45
  readonly resourceId: string;
46
46
  /** Extra metadata fields merged into every entry's `metadata`. */
47
47
  readonly extraMetadata?: Record<string, unknown>;
48
- /** Model id — provenance label only; the provider owns the wire model. */
49
- readonly modelId: string;
48
+ /** The step agent: the continuation sends its system prompt and its declared tools. */
49
+ readonly agent: AgentDefinition;
50
+ /** The cell that the `submit_file_metadata` tool of `agent` records into. */
51
+ readonly cell: FileMetadataCell;
50
52
  /**
51
- * In-memory step transcript from `runAgent` — gives the describer the
52
- * agent's intent so descriptions reflect what the file was produced for.
53
- * Optional: empty/absent degrades to path-only describing.
53
+ * The transcript from `runAgent` that the continuation extends, to
54
+ * ground each file's description in why the step produced it.
54
55
  */
55
- readonly messages?: readonly LoopMessage[];
56
- /** Workspace read seam — backs the scoped `read_file` tool. */
57
- readonly workspaceFs?: WorkspaceFilesystem;
58
- /** Absolute host path to the step's writable output tree — `read_file`'s working dir. */
59
- readonly workingDir?: string;
60
- /** Iteration budget for the describer loop. Defaults to {@link DEFAULT_MAX_ITERATIONS}. */
61
- readonly maxIterations?: number;
56
+ readonly transcript: readonly LoopMessage[];
62
57
  readonly signal?: AbortSignal;
63
58
  }
64
59
  export interface FileMetadataEntry {
@@ -71,10 +66,8 @@ export interface FileMetadataResult {
71
66
  readonly indexed: number;
72
67
  /** One entry per input artifact (described or fallback), input order. */
73
68
  readonly entries: readonly FileMetadataEntry[];
69
+ /** The continuation's new messages: the request, then its replies. Empty when no continuation ran. */
70
+ readonly messages: readonly LoopMessage[];
74
71
  }
75
- /**
76
- * Describe a step's known artifacts via a tool-call loop. Non-fatal: a loop
77
- * failure or partial coverage degrades to deterministic fallbacks, so the
78
- * result always carries exactly one entry per input artifact.
79
- */
72
+ /** Describes a step's known artifacts; a continuation failure degrades to per-file fallbacks rather than throwing. */
80
73
  export declare function generateFileMetadata(opts: GenerateFileMetadataOptions): Promise<FileMetadataResult>;
@@ -1,44 +1,33 @@
1
1
  /**
2
2
  * Per-step artifact metadata generation — describes a step's output files
3
- * via a focused `runAgent` tool-call loop on the harness `ChatProvider`.
3
+ * through a continuation of the step agent's conversation (`continueAgent`)
4
+ * on the harness `ChatProvider`.
4
5
  *
5
- * Design (mirrors `generate-plan.ts`): the describer agent communicates
6
- * exclusively through one tool, `submit_file_metadata`, whose `execute`
7
- * validates every entry's `path` against the known artifact set. Unknown
8
- * paths (hallucinated files) are rejected with feedback; uncovered files are
9
- * reported as `remaining` so the model resubmits. Descriptions are matched
10
- * to files BY PATH — there is no positional array-index alignment, so a
11
- * dropped, reordered, or extra entry can never attach a description to the
12
- * wrong file.
6
+ * The continuation extends the prefix that the task cached, so each signed
7
+ * thinking block of the task must stay valid across the extension.
8
+ *
9
+ * Descriptions match files BY PATH, not by array index, so a dropped,
10
+ * reordered, or extra entry can never attach to the wrong file.
13
11
  *
14
12
  * Lossless contract: every input artifact appears in the result exactly
15
- * once. A file the model never describes (loop budget exhausted, persistent
16
- * tool errors, model refusal) gets a deterministic fallback description
17
- * synthesised from its path + inferred type + size. Fallbacks are logged —
18
- * never silently dropped, never silently chunked out.
13
+ * once. A file the model never describes gets a deterministic fallback
14
+ * description, logged, never silently dropped.
19
15
  *
20
16
  * `Session` is taken explicitly (see the harness-durable-runtime spec) — billing is a compile-time
21
17
  * obligation on every provider call.
22
18
  */
23
- import { ok } from "neverthrow";
24
- import { forSubAgent } from "../auth/types.js";
25
- import { runAgent } from "../loop/run-agent.js";
19
+ import { continueAgent } from "../loop/continue-agent.js";
26
20
  import { passthroughStep } from "../loop/run-step.js";
27
- import { defineTool } from "../tools/define-tool.js";
28
- import { createReadFileTool } from "../tools/workspace/read-file.js";
29
- import { composeSystemPrompt } from "../agents/system-prompt.js";
21
+ import { READ_TOOL_OUTPUT_TOOL_ID } from "../loop/tool-output.js";
30
22
  import { inferArtifactType } from "../schemas/artifact-manifest.js";
31
- import { SubmitFileMetadataInputSchema } from "../schemas/file-metadata.js";
23
+ import { SUBMIT_FILE_METADATA_TOOL_ID } from "../tools/sandbox/submit-file-metadata.js";
32
24
  import { createNoopLogger } from "../lib/console-logger.js";
33
- /** Sub-agent identity for the describer loop — provenance only. */
34
25
  const DESCRIBER_AGENT_ID = "file-metadata-describer";
35
- /**
36
- * Iteration budget: one full submission + a few correction rounds. The
37
- * fallback backstop means an exhausted budget degrades gracefully rather
38
- * than dropping files, so the cap stays small.
39
- */
40
- const DEFAULT_MAX_ITERATIONS = 8;
41
- const SYSTEM_PROMPT = `You are a deterministic metadata describer for bioinformatics output files.
26
+ /** Request cap: one submission plus a few correction rounds; an exhausted cap falls back safely. */
27
+ const DESCRIBER_MAX_REQUESTS = 8;
28
+ /** Tool ids the continuation's mask allows; each must be a tool the step agent already declares. */
29
+ const DESCRIBER_TOOLS = [SUBMIT_FILE_METADATA_TOOL_ID, "read_file", "grep", READ_TOOL_OUTPUT_TOOL_ID];
30
+ const DESCRIBER_INSTRUCTIONS = `Your work on this step has ended. Now describe its output files.
42
31
 
43
32
  You describe files by calling the submit_file_metadata tool. For each file provide:
44
33
  - path — copy the file's path EXACTLY from the list you are given.
@@ -59,60 +48,9 @@ Rules:
59
48
  alone is not enough to describe it accurately.
60
49
  - Do not guess file contents you cannot infer; a terse, honest description is
61
50
  better than a confident wrong one.`;
62
- function buildPrompt(artifacts) {
51
+ function describerRequest(artifacts) {
63
52
  const list = artifacts.map((a) => `- ${a.displayPath}`).join("\n");
64
- return `Describe the following output files by calling submit_file_metadata. Copy each \`path\` EXACTLY from this list. Read a file with read_file when its path alone is not enough to describe it accurately:\n\n${list}`;
65
- }
66
- /**
67
- * Drop a trailing partial assistant `tool_use` round from a loop transcript:
68
- * it has no matching `tool_result`, so prefixing it onto a fresh loop turn
69
- * would be an invalid message sequence.
70
- */
71
- function sanitizeTranscript(messages) {
72
- const out = [...messages];
73
- while (out.length > 0) {
74
- const last = out[out.length - 1];
75
- const blocks = Array.isArray(last.content) ? last.content : [];
76
- const hasOpenToolUse = last.role === "assistant" && blocks.some((b) => b.type === "tool-call");
77
- if (hasOpenToolUse) {
78
- out.pop();
79
- continue;
80
- }
81
- break;
82
- }
83
- return out;
84
- }
85
- /**
86
- * The single describer tool. Accumulates accepted descriptions into
87
- * `holder` keyed by path; rejects paths outside `knownPaths`. Returns the
88
- * coverage state the model uses to decide whether to continue.
89
- */
90
- function buildSubmitTool(holder, knownPaths) {
91
- return defineTool({
92
- id: "submit_file_metadata",
93
- description: "Submit metadata for output files. Each entry's `path` MUST exactly " +
94
- "match one of the listed files. Returns {accepted, unknownPaths, " +
95
- "remaining}: accepted=true means every file now has metadata — STOP. " +
96
- "Otherwise drop the unknownPaths and describe the remaining files.",
97
- inputSchema: SubmitFileMetadataInputSchema,
98
- describeCall: "none",
99
- execute: async (input) => {
100
- const unknownPaths = [];
101
- for (const entry of input.files) {
102
- if (!knownPaths.has(entry.path)) {
103
- unknownPaths.push(entry.path);
104
- continue;
105
- }
106
- holder.set(entry.path, entry);
107
- }
108
- const remaining = [...knownPaths].filter((p) => !holder.has(p));
109
- return ok({
110
- accepted: unknownPaths.length === 0 && remaining.length === 0,
111
- unknownPaths,
112
- remaining,
113
- });
114
- },
115
- });
53
+ return `${DESCRIBER_INSTRUCTIONS}\n\nDescribe the following output files by calling submit_file_metadata. Copy each \`path\` EXACTLY from this list. Read a file with read_file when its path alone is not enough to describe it accurately:\n\n${list}`;
116
54
  }
117
55
  function buildEntry(artifact, desc, extra) {
118
56
  const metadata = {
@@ -152,47 +90,47 @@ function fallbackEntry(artifact, extra) {
152
90
  metadata: { ...extra, dataType: fileType, format },
153
91
  };
154
92
  }
155
- /**
156
- * Describe a step's known artifacts via a tool-call loop. Non-fatal: a loop
157
- * failure or partial coverage degrades to deterministic fallbacks, so the
158
- * result always carries exactly one entry per input artifact.
159
- */
93
+ /** Describes a step's known artifacts; a continuation failure degrades to per-file fallbacks rather than throwing. */
160
94
  export async function generateFileMetadata(opts) {
161
95
  const logger = (opts.logger ?? createNoopLogger()).named("artifact-metadata").with({ resourceId: opts.resourceId });
162
96
  if (opts.artifacts.length === 0) {
163
- return { indexed: 0, entries: [] };
97
+ return { indexed: 0, entries: [], messages: [] };
164
98
  }
165
- const knownPaths = new Set(opts.artifacts.map((a) => a.displayPath));
166
- const holder = new Map();
167
- const tools = [buildSubmitTool(holder, knownPaths)];
168
- if (opts.workspaceFs) {
169
- tools.push(createReadFileTool(opts.workspaceFs, opts.workingDir));
99
+ // The mask can let only a declared tool run, and an embedder whose agent
100
+ // factory gives no cell to the agent declares no output tool.
101
+ if (!opts.agent.tools.some((tool) => tool.id === SUBMIT_FILE_METADATA_TOOL_ID)) {
102
+ logger.warn("the step agent declares no submit_file_metadata, thus each file gets a deterministic fallback description", {
103
+ agentId: opts.agent.id,
104
+ artifactCount: opts.artifacts.length,
105
+ });
106
+ return { indexed: 0, entries: opts.artifacts.map((artifact) => fallbackEntry(artifact, opts.extraMetadata)), messages: [] };
170
107
  }
171
- const describer = {
172
- id: DESCRIBER_AGENT_ID,
173
- systemPrompt: composeSystemPrompt(SYSTEM_PROMPT),
174
- model: opts.modelId,
175
- tools,
176
- maxIterations: opts.maxIterations ?? DEFAULT_MAX_ITERATIONS,
177
- };
178
- const signal = opts.signal ?? new AbortController().signal;
179
- const transcript = opts.messages ? sanitizeTranscript(opts.messages) : [];
108
+ opts.cell.expect(opts.artifacts.map((a) => a.displayPath));
109
+ let messages = [];
180
110
  try {
181
- await runAgent(describer, [...transcript, { role: "user", content: buildPrompt(opts.artifacts) }], forSubAgent(opts.session, DESCRIBER_AGENT_ID), {
111
+ const continued = await continueAgent(opts.agent, opts.transcript, {
112
+ text: describerRequest(opts.artifacts),
113
+ mask: { allow: DESCRIBER_TOOLS },
114
+ maxRequests: DESCRIBER_MAX_REQUESTS,
115
+ stepNamespace: "file-metadata",
116
+ accountingAgentId: DESCRIBER_AGENT_ID,
117
+ }, opts.session, {
182
118
  provider: opts.provider,
183
- signal,
119
+ signal: opts.signal ?? new AbortController().signal,
184
120
  emit: () => { },
185
121
  runStep: passthroughStep,
186
122
  usageRecorder: opts.usageRecorder,
123
+ toolOutputStore: opts.toolOutputStore,
187
124
  });
125
+ messages = continued.messages;
188
126
  }
189
127
  catch (err) {
190
- logger.warn("describer loop failed; using fallbacks", logger.errorFields(err));
128
+ logger.warn("describer continuation failed; using fallbacks", logger.errorFields(err));
191
129
  }
192
130
  const entries = [];
193
131
  const fallbackPaths = [];
194
132
  for (const artifact of opts.artifacts) {
195
- const desc = holder.get(artifact.displayPath);
133
+ const desc = opts.cell.descriptions.get(artifact.displayPath);
196
134
  if (desc) {
197
135
  entries.push(buildEntry(artifact, desc, opts.extraMetadata));
198
136
  }
@@ -208,5 +146,5 @@ export async function generateFileMetadata(opts) {
208
146
  fallbackPaths,
209
147
  });
210
148
  }
211
- return { indexed: holder.size, entries };
149
+ return { indexed: entries.length - fallbackPaths.length, entries, messages };
212
150
  }
@@ -2,7 +2,8 @@
2
2
  * The harness's post-step pipeline — the per-step bookkeeping that runs after a sandbox
3
3
  * agent loop finishes: describe the output files, write an interpretive
4
4
  * summary, reconcile + register the artifact manifest, and index the
5
- * descriptions into the per-analysis vector store.
5
+ * descriptions into the per-analysis vector store. The description and the
6
+ * summary are continuations of the conversation of the step agent.
6
7
  *
7
8
  * These leaf bodies are host-neutral: the only place an embedder differs is the
8
9
  * injected `ArtifactRegistry` (filesystem vs. external provenance) and the
@@ -12,8 +13,9 @@
12
13
  */
13
14
  import { type Result } from "neverthrow";
14
15
  import type { Pool } from "pg";
16
+ import type { ToolOutputStore } from "../loop/tool-output.js";
17
+ import type { LoopMessage } from "../loop/types.js";
15
18
  import type { AgentChat, EmbeddingProvider } from "../providers/types.js";
16
- import type { WorkspaceFilesystem } from "../workspace/filesystem.js";
17
19
  import type { ResolveWorkspaceRoot } from "../workspace/paths.js";
18
20
  import type { ArtifactManifestEntry } from "../schemas/artifact-manifest.js";
19
21
  import type { StepSummary } from "../schemas/step-summary.js";
@@ -31,30 +33,35 @@ export interface PostStepPipelineDeps {
31
33
  readonly pool: Pool;
32
34
  /** Operational logging seam; omitted falls back to no-op. */
33
35
  readonly logger?: Logger;
34
- /** LLM usage-accounting seam for the metadata + summary loops; omitted falls back to the no-op recorder. */
36
+ /** LLM usage-accounting seam for the metadata + summary continuations; omitted falls back to the no-op recorder. */
35
37
  readonly usageRecorder?: UsageRecorder;
36
- /** Non-streaming chat — the metadata + summary sub-agent loops. */
38
+ /** The store of the task, thus a reference of the task stays readable in the metadata + summary continuations. */
39
+ readonly toolOutputStore?: ToolOutputStore;
40
+ /** Non-streaming chat — the provider of the task, which the metadata + summary continuations extend. */
37
41
  readonly provider: AgentChat;
38
42
  /** Write-side embedder for the vector index. */
39
43
  readonly embedding: EmbeddingProvider;
40
- readonly workspaceFs: WorkspaceFilesystem;
41
44
  readonly artifactRegistry: ArtifactRegistry;
42
45
  /** Workspace-root resolution seam (see workspace/paths.ts). */
43
46
  readonly resolveWorkspaceRoot: ResolveWorkspaceRoot;
44
- /** Sandbox model id — provenance label for metadata + summary. */
45
- readonly model: string;
46
47
  }
48
+ /** The product of the file-metadata stage, as its `DBOS.runStep` checkpoints it. */
49
+ export interface StepFileMetadata {
50
+ /** One entry per manifest file, described or fallback. */
51
+ readonly entries: readonly FileMetadataEntry[];
52
+ /**
53
+ * The file-metadata exchange's messages, empty if none ran. Checkpointed by
54
+ * `DBOS.runStep`, so a replay must give the summary the same prefix.
55
+ */
56
+ readonly messages: readonly LoopMessage[];
57
+ }
58
+ /** Describes each manifest file; a file the model cannot describe gets a deterministic fallback, never a dropped entry. */
59
+ export declare function generateStepFileMetadata(deps: PostStepPipelineDeps, postCtx: PostStepContext, manifest: readonly ArtifactManifestEntry[]): Promise<StepFileMetadata>;
47
60
  /**
48
- * Describe each manifest file. Returns one entry per file — a file the model
49
- * fails to describe gets a deterministic fallback, never a dropped entry.
50
- */
51
- export declare function generateStepFileMetadata(deps: PostStepPipelineDeps, postCtx: PostStepContext, manifest: readonly ArtifactManifestEntry[]): Promise<readonly FileMetadataEntry[]>;
52
- /**
53
- * Generate the interpretive step summary and persist it to
54
- * `output/summary.md`. A write failure is non-fatal (logged) — the summary is
55
- * still returned for vector indexing.
61
+ * Generates the interpretive step summary, continuing after `metadataMessages`,
62
+ * and writes it to `output/summary.md`. A write failure is non-fatal.
56
63
  */
57
- export declare function generateStepSummaryAndWrite(deps: PostStepPipelineDeps, postCtx: PostStepContext, manifest: readonly ArtifactManifestEntry[]): Promise<StepSummary | undefined>;
64
+ export declare function generateStepSummaryAndWrite(deps: PostStepPipelineDeps, postCtx: PostStepContext, manifest: readonly ArtifactManifestEntry[], metadataMessages: readonly LoopMessage[]): Promise<StepSummary | undefined>;
58
65
  export type StepRegistrationFailure = ArtifactRegistrationFailure | {
59
66
  readonly kind: "rejected";
60
67
  readonly message: string;
@@ -2,7 +2,8 @@
2
2
  * The harness's post-step pipeline — the per-step bookkeeping that runs after a sandbox
3
3
  * agent loop finishes: describe the output files, write an interpretive
4
4
  * summary, reconcile + register the artifact manifest, and index the
5
- * descriptions into the per-analysis vector store.
5
+ * descriptions into the per-analysis vector store. The description and the
6
+ * summary are continuations of the conversation of the step agent.
6
7
  *
7
8
  * These leaf bodies are host-neutral: the only place an embedder differs is the
8
9
  * injected `ArtifactRegistry` (filesystem vs. external provenance) and the
@@ -22,14 +23,11 @@ import { generateFileMetadata as coreGenerateFileMetadata } from "./artifact-met
22
23
  import { generateStepSummary as coreGenerateStepSummary } from "./step-summary.js";
23
24
  import { createVectorStore } from "../state/vector-store.js";
24
25
  import { ensureSearchIndex, searchIndexName } from "../workspace/search-config.js";
25
- /**
26
- * Describe each manifest file. Returns one entry per file — a file the model
27
- * fails to describe gets a deterministic fallback, never a dropped entry.
28
- */
26
+ /** Describes each manifest file; a file the model cannot describe gets a deterministic fallback, never a dropped entry. */
29
27
  export async function generateStepFileMetadata(deps, postCtx, manifest) {
30
- const { input, session, transcript, writePrefix } = postCtx;
28
+ const { input, session, transcript, agent, fileMetadata } = postCtx;
31
29
  if (manifest.length === 0)
32
- return [];
30
+ return { entries: [], messages: [] };
33
31
  const dbPathPrefix = `runs/${input.runId}/${input.stepId}/`;
34
32
  const artifactsForMeta = manifest.map((a) => ({
35
33
  dbPath: `${dbPathPrefix}${a.path}`,
@@ -37,9 +35,14 @@ export async function generateStepFileMetadata(deps, postCtx, manifest) {
37
35
  sizeBytes: a.size,
38
36
  }));
39
37
  const result = await coreGenerateFileMetadata({
38
+ logger: deps.logger,
40
39
  provider: deps.provider,
41
40
  usageRecorder: deps.usageRecorder,
41
+ toolOutputStore: deps.toolOutputStore,
42
42
  session,
43
+ agent,
44
+ cell: fileMetadata,
45
+ transcript,
43
46
  artifacts: artifactsForMeta,
44
47
  resourceId: input.analysisId,
45
48
  extraMetadata: {
@@ -48,29 +51,24 @@ export async function generateStepFileMetadata(deps, postCtx, manifest) {
48
51
  producerRun: input.runId,
49
52
  producerAgent: input.agentId,
50
53
  },
51
- modelId: deps.model,
52
- messages: transcript,
53
- workspaceFs: deps.workspaceFs,
54
- workingDir: writePrefix,
55
54
  });
56
- return result.entries;
55
+ return { entries: result.entries, messages: result.messages };
57
56
  }
58
57
  /**
59
- * Generate the interpretive step summary and persist it to
60
- * `output/summary.md`. A write failure is non-fatal (logged) — the summary is
61
- * still returned for vector indexing.
58
+ * Generates the interpretive step summary, continuing after `metadataMessages`,
59
+ * and writes it to `output/summary.md`. A write failure is non-fatal.
62
60
  */
63
- export async function generateStepSummaryAndWrite(deps, postCtx, manifest) {
64
- const { input, session, transcript, writePrefix } = postCtx;
61
+ export async function generateStepSummaryAndWrite(deps, postCtx, manifest, metadataMessages) {
62
+ const { input, session, transcript, agent, writePrefix } = postCtx;
65
63
  const summary = await coreGenerateStepSummary({
64
+ logger: deps.logger,
66
65
  provider: deps.provider,
67
66
  usageRecorder: deps.usageRecorder,
67
+ toolOutputStore: deps.toolOutputStore,
68
68
  session,
69
- modelId: deps.model,
70
- messages: transcript,
69
+ agent,
70
+ conversation: [...transcript, ...metadataMessages],
71
71
  artifactPaths: manifest.map((a) => a.path),
72
- workspaceFs: deps.workspaceFs,
73
- workingDir: writePrefix,
74
72
  stepId: input.stepId,
75
73
  agentId: input.agentId,
76
74
  runId: input.runId,
@@ -25,6 +25,7 @@
25
25
  */
26
26
  import { type RunSynthesis } from "../schemas/run-synthesis.js";
27
27
  import type { StepSummary } from "../schemas/step-summary.js";
28
+ import type { ToolOutputStore } from "../loop/tool-output.js";
28
29
  import type { ChatDataPart, EmitFn } from "../loop/types.js";
29
30
  import type { ChatProvider } from "../providers/types.js";
30
31
  import type { UsageRecorder } from "../billing/usage-recorder.js";
@@ -72,6 +73,8 @@ export interface GenerateRunSynthesisInput {
72
73
  readonly citationResolver: CitationResolver;
73
74
  /** LLM usage-accounting seam for the synthesizer + reviewer loops; omitted falls back to the no-op recorder. */
74
75
  readonly usageRecorder?: UsageRecorder;
76
+ /** The store of the synthesizer and reviewer loops and of their read tools. */
77
+ readonly toolOutputStore?: ToolOutputStore;
75
78
  /** Step summaries from the completed run. Non-empty. */
76
79
  readonly summaries: readonly StepSummary[];
77
80
  /** Plan analytical narrative for context. */