@inflexa-ai/harness 0.39.2 → 0.40.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 +75 -12
  10. package/dist/app/chat-turn.js +149 -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
@@ -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. */
@@ -37,6 +37,7 @@ import { decodeObjectString } from "../lib/zod-issues.js";
37
37
  import { runToTerminal } from "../loop/run-to-terminal.js";
38
38
  import { passthroughStep } from "../loop/run-step.js";
39
39
  import { defineTool } from "../tools/define-tool.js";
40
+ import { createReadToolOutputTool } from "../tools/read-tool-output.js";
40
41
  import { createReportBlockerToolFor } from "../tools/sandbox/report-blocker.js";
41
42
  import { createLiteratureReviewerTool } from "../tools/research/literature-reviewer.js";
42
43
  // ── Agent identity / budgets ────────────────────────────────────────
@@ -52,6 +53,11 @@ export const SYNTHESIS_AGENT_ID = "run-synthesizer";
52
53
  * + draft + 1–2 submit/fix cycles + headroom.
53
54
  */
54
55
  const SYNTHESIZER_MAX_ITERATIONS = 25;
56
+ /**
57
+ * Cap on `literature_reviewer` calls per run. Not named in the prompt: a
58
+ * model treats a stated call-count limit as a target to hit, not a maximum.
59
+ */
60
+ const LITERATURE_REVIEWER_BUDGET = 3;
55
61
  // ── Validation ──────────────────────────────────────────────────────
56
62
  function zodIssuesToValidationIssues(error, input, rootPath = "synthesis") {
57
63
  return error.issues.map((i) => ({
@@ -358,11 +364,13 @@ export async function generateRunSynthesis(input) {
358
364
  bioKeys: input.bioKeys,
359
365
  citationResolver: input.citationResolver,
360
366
  usageRecorder: input.usageRecorder,
367
+ ...(input.toolOutputStore ? { toolOutputStore: input.toolOutputStore } : {}),
361
368
  });
362
369
  const validateTool = buildValidateTool(innerCtx);
363
370
  const submitTool = buildSubmitTool(holder, innerCtx);
364
371
  const blockerTool = buildBlockerTool(holder);
365
- const tools = [validateTool, submitTool, blockerTool, reviewer];
372
+ const readTools = input.toolOutputStore ? [createReadToolOutputTool(input.toolOutputStore)] : [];
373
+ const tools = [validateTool, submitTool, blockerTool, reviewer, ...readTools];
366
374
  const agent = {
367
375
  id: SYNTHESIS_AGENT_ID,
368
376
  systemPrompt: composeSystemPrompt(synthesisAgentPrompt),
@@ -380,6 +388,8 @@ export async function generateRunSynthesis(input) {
380
388
  runStep: passthroughStep,
381
389
  resolved: () => holder.outcome !== null,
382
390
  usageRecorder: input.usageRecorder,
391
+ toolOutputStore: input.toolOutputStore,
392
+ toolBudget: { [reviewer.id]: LITERATURE_REVIEWER_BUDGET },
383
393
  };
384
394
  await runToTerminal(agent, [{ role: "user", content: prompt }], input.session, loopDeps, {
385
395
  tools: [submitTool, blockerTool],
@@ -1,61 +1,52 @@
1
1
  /**
2
- * Per-step interpretive markdown summary — a focused `runAgent` tool-loop on
3
- * the harness `ChatProvider` that consumes the step's in-memory transcript
4
- * (the `runAgent` `messages` array the workflow body already holds) AND a
5
- * scoped `read_file` so it grounds every quantitative claim in the persisted
6
- * output files rather than confabulating from `execute_command` stdout.
2
+ * Per-step interpretive summary. Continues the step agent's conversation
3
+ * (`continueAgent`) so the request reuses the cached prefix and keeps
4
+ * signed thinking blocks valid.
7
5
  *
8
6
  * Contract:
9
- * - `runAgent` loop with a single `read_file` tool scoped to the step's
10
- * writable output tree (via `createReadFileTool(workspaceFs, workingDir)`).
11
- * The summarizer reads any output file it needs, then emits the markdown
7
+ * - Sends the step agent's system prompt and tools, masked to `read_file`
8
+ * and `grep` in the step's output tree plus `read_tool_output`, and emits the markdown
12
9
  * summary as its final assistant text.
13
10
  * - Returns `{ stepId, agentId, markdown }` on non-empty final text;
14
- * `undefined` on empty/empty-after-trim, on a loop throw, or on no
15
- * text in the final assistant turn. Non-fatal — the workflow body decides
16
- * whether to write `output/summary.md`.
11
+ * `undefined` on empty text, on a throw, or when the request cap is hit
12
+ * before a final answer (a continuation runs no wrap-up). Non-fatal.
17
13
  * - `Session` is taken explicitly (see the harness-durable-runtime spec).
18
14
  *
19
- * Honest empty: with no output artifacts the prompt directs the model to
15
+ * Honest empty: with no output artifacts the request directs the model to
20
16
  * state plainly that the step produced no output files — no synthesized
21
17
  * results. Blocked steps skip this producer entirely (handled upstream).
22
18
  *
23
19
  * Transcript-from-memory: per the harness-thread-store spec, workflow loops have no `messages`
24
20
  * table; reconstruction from `operation_outputs` is read-side only. At
25
- * step time the workflow body already holds the array — pass it in.
21
+ * step time the workflow body already holds the array — pass it in. The task
22
+ * always closes its unanswered calls, so the transcript needs no repair.
26
23
  */
27
24
  import type { AgentSession } from "../auth/types.js";
28
- import type { LoopMessage } from "../loop/types.js";
25
+ import { type ToolOutputStore } from "../loop/tool-output.js";
26
+ import type { AgentDefinition, LoopMessage } from "../loop/types.js";
29
27
  import type { AgentChat } from "../providers/types.js";
30
- import type { WorkspaceFilesystem } from "../workspace/filesystem.js";
31
28
  import { type StepSummary } from "../schemas/step-summary.js";
32
29
  import type { Logger } from "../lib/logger.js";
33
30
  import type { UsageRecorder } from "../billing/usage-recorder.js";
34
31
  export interface GenerateStepSummaryOptions {
35
32
  /** Operational logging seam; omitted falls back to no-op. */
36
33
  readonly logger?: Logger;
37
- /** LLM usage-accounting seam for the summary loop; omitted falls back to the no-op recorder. */
34
+ /** LLM usage-accounting seam for the continuation; omitted falls back to the no-op recorder. */
38
35
  readonly usageRecorder?: UsageRecorder;
36
+ /** The store of the task, thus the loop of the continuation keeps its texts where the read tool of the agent reads them. */
37
+ readonly toolOutputStore?: ToolOutputStore;
38
+ /** The provider of the task, thus each request extends the prefix that the task cached. */
39
39
  readonly provider: AgentChat;
40
40
  readonly session: AgentSession;
41
- readonly modelId: string;
42
- /** In-memory transcript from `runAgent` — assistant + tool-result rounds. */
43
- readonly messages: readonly LoopMessage[];
41
+ /** The step agent: the continuation sends its system prompt and its declared tools. */
42
+ readonly agent: AgentDefinition;
43
+ /** Task transcript, then the file-metadata exchange messages if one ran. */
44
+ readonly conversation: readonly LoopMessage[];
44
45
  readonly artifactPaths: readonly string[];
45
- /** Workspace read seam — backs the scoped `read_file` tool. */
46
- readonly workspaceFs: WorkspaceFilesystem;
47
- /** Absolute host path to the step's writable output tree — `read_file`'s working dir. */
48
- readonly workingDir: string;
49
46
  readonly stepId: string;
50
47
  readonly agentId: string;
51
48
  readonly runId: string;
52
- readonly maxIterations?: number;
53
49
  readonly signal?: AbortSignal;
54
50
  }
55
- /**
56
- * Run the post-step summary loop on the harness provider against the supplied
57
- * transcript, grounding claims in persisted files via `read_file`. Returns
58
- * `undefined` on any non-fatal failure mode; the workflow body proceeds
59
- * without `summary.md` in that case.
60
- */
51
+ /** Returns `undefined` on non-fatal failure instead of throwing. */
61
52
  export declare function generateStepSummary(opts: GenerateStepSummaryOptions): Promise<StepSummary | undefined>;