@inflexa-ai/harness 0.41.0 → 0.41.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/prompts/briefing.d.ts +28 -1
- package/dist/prompts/briefing.js +44 -1
- package/dist/prompts/compaction.d.ts +2 -2
- package/dist/prompts/compaction.js +9 -5
- package/dist/prompts/sandbox-standards.d.ts +1 -1
- package/dist/prompts/sandbox-standards.js +4 -0
- package/dist/runtime/assemble.d.ts +4 -2
- package/dist/runtime/assemble.js +8 -1
- package/dist/schemas/file-metadata.js +9 -9
- package/dist/schemas/plan-schemas.d.ts +3 -3
- package/dist/tools/ad-hoc-router.js +9 -4
- package/dist/tools/bio/chembl.js +8 -5
- package/dist/tools/bio/comptox.js +4 -4
- package/dist/tools/bio/drug-gene-interactions.js +5 -4
- package/dist/tools/bio/gene-disease-evidence.js +4 -4
- package/dist/tools/bio/lookup-annotation.js +7 -3
- package/dist/tools/bio/opentargets.js +8 -6
- package/dist/tools/bio/pubchem.js +4 -2
- package/dist/tools/bio/pubmed.d.ts +1 -1
- package/dist/tools/bio/pubmed.js +10 -6
- package/dist/tools/bio/search-clinical-trials.js +2 -1
- package/dist/tools/bio/search-gene.js +2 -1
- package/dist/tools/bio/search-interactions.js +8 -4
- package/dist/tools/bio/target-safety.js +2 -1
- package/dist/tools/display/show-user.js +1 -1
- package/dist/tools/execute-analysis.js +14 -4
- package/dist/tools/lib/opentargets-client.d.ts +8 -3
- package/dist/tools/lib/opentargets-client.js +12 -3
- package/dist/tools/read-tool-output.js +7 -4
- package/dist/tools/report-authoring/authoring-tools.js +22 -15
- package/dist/tools/report-session/derive-table.js +5 -1
- package/dist/tools/report-session/examine-page.js +5 -4
- package/dist/tools/report-session/preview-report.js +6 -1
- package/dist/tools/report-session/record-version.js +2 -1
- package/dist/tools/research/context7-docs.js +8 -2
- package/dist/tools/research/generate-analogy-report.js +15 -15
- package/dist/tools/research/generate-plan.js +12 -7
- package/dist/tools/research/inspect-data-profile.js +7 -5
- package/dist/tools/research/inspect-run.js +6 -2
- package/dist/tools/research/literature-reviewer.js +11 -6
- package/dist/tools/research/search-arxiv.js +2 -1
- package/dist/tools/research/search-github-repos.js +9 -3
- package/dist/tools/sandbox/list-available-refs.js +3 -2
- package/dist/tools/sandbox/report-blocker.js +6 -1
- package/dist/tools/sandbox/skills.js +4 -2
- package/dist/tools/start-report-session.js +4 -1
- package/dist/tools/workspace/edit-file.js +4 -3
- package/dist/tools/workspace/execute-command.js +6 -5
- package/dist/tools/workspace/grep.js +9 -7
- package/dist/tools/workspace/list-files.js +1 -1
- package/dist/tools/workspace/read-file.js +2 -1
- package/dist/tools/workspace/scan-inputs.js +3 -1
- package/dist/tools/workspace/show-file.js +2 -0
- package/dist/tools/workspace/workspace-search.js +3 -1
- package/dist/tools/workspace/write-file.js +5 -3
- package/dist/workflows/execute-analysis.d.ts +28 -5
- package/dist/workflows/execute-analysis.js +34 -10
- package/package.json +1 -1
|
@@ -48,8 +48,10 @@ export function createSkillTools(deps) {
|
|
|
48
48
|
id: "skill_search",
|
|
49
49
|
description: "Search your skills (method decision trees, API references, worked " +
|
|
50
50
|
"examples) by keyword. Start here when picking a method or verifying an " +
|
|
51
|
-
"API detail.
|
|
52
|
-
"
|
|
51
|
+
"API detail. The query splits on whitespace into words, and each word matches as a " +
|
|
52
|
+
"case-insensitive substring. Returns one entry per matching skill file — its skill, its path, " +
|
|
53
|
+
"and the first line that holds a query word — ranked by how many distinct query words the file " +
|
|
54
|
+
"holds. Then `skill_read` the file. No matches returns a data variant.",
|
|
53
55
|
inputSchema: z.object({
|
|
54
56
|
query: z.string().min(1).describe("Keywords to match (e.g. 'PyDESeq2 contrast syntax')."),
|
|
55
57
|
topK: z.number().int().min(1).max(MAX_TOP_K).optional().describe(`Max results to return. Defaults to ${DEFAULT_TOP_K}.`),
|
|
@@ -127,7 +127,10 @@ export function createStartReportSessionTool(deps) {
|
|
|
127
127
|
"Keep the whole brief under approximately 2000 tokens. " +
|
|
128
128
|
"When the conversation holds no user turn of new work after the last report session, the tool " +
|
|
129
129
|
"starts nothing and it names that session. The ask that started that session is not new work. " +
|
|
130
|
-
"Then tell the user to continue in that chat."
|
|
130
|
+
"Then tell the user to continue in that chat. " +
|
|
131
|
+
"The result `outcome` is `started` or `existing-session` with the `threadId` of the report chat. " +
|
|
132
|
+
"`no_browser` means that this deployment has no browser for the report agent, thus no report session can start; tell the user. " +
|
|
133
|
+
"`refused`, `parent_not_found`, `parent_not_a_conversation`, and `failed` mean that nothing started, and `detail` gives the reason where there is one.",
|
|
131
134
|
inputSchema: startReportSessionInput,
|
|
132
135
|
executionMode: "inline",
|
|
133
136
|
describeCall: "none",
|
|
@@ -116,9 +116,10 @@ export function createEditFileTool(deps) {
|
|
|
116
116
|
"(default), old_string must occur exactly once. For a bulk pattern " +
|
|
117
117
|
"edit set regex=true: old_string is a JS regex, new_string may use $1 " +
|
|
118
118
|
"references, and exactly one of replace_all=true or expected_matches " +
|
|
119
|
-
"gates the write. Returns `out_of_prefix` / `out_of_scope` / " +
|
|
120
|
-
"`file_not_found` / `not_found` / `not_unique` / `match_count_mismatch` " +
|
|
121
|
-
"data variants — never throws on expected outcomes."
|
|
119
|
+
"gates the write. Returns `out_of_prefix` / `out_of_scope` / `symlink_denied` / " +
|
|
120
|
+
"`file_not_found` / `not_found` / `not_unique` / `match_count_mismatch` / " +
|
|
121
|
+
"`invalid_pattern` / `invalid_arguments` data variants — never throws on expected outcomes. " +
|
|
122
|
+
"A data variant other than `ok` writes nothing.",
|
|
122
123
|
inputSchema: EditFileInputSchema,
|
|
123
124
|
// The path only. The replaced text is file content, not a description.
|
|
124
125
|
describeCall: ({ path }) => path,
|
|
@@ -61,11 +61,12 @@ export function createExecuteCommandTool(deps) {
|
|
|
61
61
|
`tools don't express. stdout and stderr are each capped at ${EXEC_STREAM_BYTE_CAP / (1024 * 1024)} MiB; the end ` +
|
|
62
62
|
"of a longer stream is dropped, and the result carries its truncation flag. A long result comes back as an " +
|
|
63
63
|
"excerpt of its start and its end, with a reference to read the rest when the harness keeps it. " +
|
|
64
|
-
"Failures are returned as data, " +
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
"
|
|
64
|
+
"Failures are returned as data, not thrown: `status` is `ok` whenever the call completed, so read " +
|
|
65
|
+
"`exitCode` for the outcome of the command. `timedOut: true` means the timeout stopped the command, and a " +
|
|
66
|
+
"`syntheticFailure` with `exitCode: null` means the sandbox died under the command (its reason names an " +
|
|
67
|
+
"out-of-memory kill). stdout and stderr are not a deliverable: they are ephemeral, " +
|
|
68
|
+
"so use them to confirm that a command ran. A result that you compute with 'python -c' or an inline " +
|
|
69
|
+
"one-liner and read from stdout is lost. Write a script and persist its outputs to output/.",
|
|
69
70
|
inputSchema: ExecuteCommandInputSchema,
|
|
70
71
|
// The script is what a reader recognises, and it survives a long
|
|
71
72
|
// invocation whose flags would otherwise push it past the length cap.
|
|
@@ -21,7 +21,8 @@ const GrepInputSchema = z.object({
|
|
|
21
21
|
pattern: z
|
|
22
22
|
.string()
|
|
23
23
|
.min(1)
|
|
24
|
-
.describe("
|
|
24
|
+
.describe("JavaScript regular expression, tested against each line separately. It matches anywhere in the line " +
|
|
25
|
+
"unless you add `^` or `$`; case-sensitive unless `ignoreCase` is true."),
|
|
25
26
|
path: z
|
|
26
27
|
.string()
|
|
27
28
|
.min(1)
|
|
@@ -31,12 +32,13 @@ const GrepInputSchema = z.object({
|
|
|
31
32
|
export function createGrepTool(fs, workingDir) {
|
|
32
33
|
return defineTool({
|
|
33
34
|
id: "grep",
|
|
34
|
-
description: "Regex search over
|
|
35
|
-
"outputs, summaries, syntheses
|
|
36
|
-
|
|
37
|
-
`capped at ${DEFAULT_MAX_MATCHES} matches and ${DEFAULT_MAX_MATCH_BYTES} ` +
|
|
38
|
-
|
|
39
|
-
"
|
|
35
|
+
description: "Regex search over the text files of the workspace (input data, prior runs, step " +
|
|
36
|
+
"outputs, summaries, syntheses). Pass a file or a " +
|
|
37
|
+
`directory path; directories are walked recursively, to a depth of ${MAX_DIR_DEPTH}. Results are ` +
|
|
38
|
+
`capped at ${DEFAULT_MAX_MATCHES} matches, and each preview at ${DEFAULT_MAX_MATCH_BYTES} characters of its line. ` +
|
|
39
|
+
`Only the first ${MAX_GREP_FILE_BYTES / (1024 * 1024)} MiB of each file is searched, and a file with a NUL byte ` +
|
|
40
|
+
"in its first KiB is skipped as binary. A `truncated` status means the match cap or the file cap cut the search; " +
|
|
41
|
+
"narrow your path or pattern. No matches, missing paths, and out-of-scope paths return data variants, not errors.",
|
|
40
42
|
inputSchema: GrepInputSchema,
|
|
41
43
|
// Both fields, because either alone loses the call: the same pattern over
|
|
42
44
|
// two trees and two patterns over one tree are both ordinary sequences.
|
|
@@ -20,7 +20,7 @@ export function createListFilesTool(fs, workingDir) {
|
|
|
20
20
|
return defineTool({
|
|
21
21
|
id: "list_files",
|
|
22
22
|
description: "List the entries (files and directories, with file sizes) in a directory of the analysis's " +
|
|
23
|
-
"own workspace tree.
|
|
23
|
+
"own workspace tree. It reads the tree directly and runs no sandbox command. It does not recurse. Its scope is that tree and nothing " +
|
|
24
24
|
"else: it never reaches a directory outside the analysis, including the host process's current " +
|
|
25
25
|
"working directory. Missing and out-of-scope paths return a data variant, not an error.",
|
|
26
26
|
inputSchema: ListFilesInputSchema,
|
|
@@ -57,7 +57,8 @@ export function createReadFileTool(fs, workingDir) {
|
|
|
57
57
|
"as UTF-8 text. Output is capped at " +
|
|
58
58
|
`${DEFAULT_MAX_BYTES} bytes; pass headLines or tailLines to read a ` +
|
|
59
59
|
"specific window of a large file (typical for bio CSV/TSV/log files). " +
|
|
60
|
-
"
|
|
60
|
+
"An oversize read returns status `truncated` with the bytes that fit (the start of the file, or " +
|
|
61
|
+
"the end for tailLines), `totalSize`, and `returnedBytes`. This tool is for " +
|
|
61
62
|
"text; a binary file (one holding NUL bytes, e.g. a .gz or image) comes " +
|
|
62
63
|
"back as a `binary` data variant rather than decoded garbage. Missing " +
|
|
63
64
|
"paths and paths outside the analysis tree return a data variant — not an error.",
|
|
@@ -23,7 +23,9 @@ export function createScanInputsTool(deps) {
|
|
|
23
23
|
"the shapes its filenames form (sets of files whose names differ only at marked positions), " +
|
|
24
24
|
"the distinct values each varying position takes, how those positions co-occur, value overlap " +
|
|
25
25
|
"between shapes, and the files that share structure with nothing else. " +
|
|
26
|
-
"
|
|
26
|
+
"It also reads the header of one member file for each shape (magic bytes, a preamble, a header row, or a container index). " +
|
|
27
|
+
"Deterministic — no model — and the manifest it returns is bounded, so it costs the same context on three files as on three thousand. " +
|
|
28
|
+
"The walk stops at a ceiling of files; a manifest marked INCOMPLETE describes part of the subtree, so scan a narrower path. " +
|
|
27
29
|
"Your briefing already carries a scan of the whole input tree; call this to look at a subtree more closely, " +
|
|
28
30
|
"or to check a grouping you are unsure about. " +
|
|
29
31
|
"It reports observations only: which files are one KIND of data, and what a varying position MEANS, are yours to decide.",
|
|
@@ -34,6 +34,8 @@ export const showFileTool = defineTool({
|
|
|
34
34
|
"inline, or paste the bytes. Pick this tool by what you are referencing, not by how the output looks: " +
|
|
35
35
|
"NOT for content you synthesized (a chart, a table, a snippet you invented — use `show_user`), NOT for a stored plan (`show_plan`). " +
|
|
36
36
|
"Paths are analysis-rooted (no leading slash, no `..`); discover them with `workspace_search` or `list_files`. " +
|
|
37
|
+
"The tool checks the shape of each path only, not that the file exists, so a wrong path fails only when the user views the card; " +
|
|
38
|
+
"`shown: false` with `invalid_path` means a path has a leading slash or a `..` segment. " +
|
|
37
39
|
"Up to 10 files per call — pass a related set in ONE call and they render as a gallery, rather than one call per figure. " +
|
|
38
40
|
"Cards render in call order, so to interleave figures with prose alternate `show_user(markdown)` and `show_file` calls " +
|
|
39
41
|
"(markdown image syntax pointing at a workspace file does NOT render — this tool is the only way to show one).",
|
|
@@ -53,7 +53,9 @@ export function createWorkspaceSearchTool(pool, embedding) {
|
|
|
53
53
|
return defineTool({
|
|
54
54
|
id: "workspace_search",
|
|
55
55
|
description: "Semantic search over the analysis workspace. Returns ranked entries with descriptions " +
|
|
56
|
-
"and metadata; read a file separately to see its contents.
|
|
56
|
+
"and metadata; read a file separately to see its contents. The nearest entries always come back, up to " +
|
|
57
|
+
"`limit`, with no relevance cut: `score` is the cosine similarity to the query, and a low score is a weak match, " +
|
|
58
|
+
"not a hit. An empty `results` list means that nothing of that type is indexed yet. `type` restricts results to one " +
|
|
57
59
|
'entry type: "input-group" (a GROUP of input data the data profiler declared, carrying its ' +
|
|
58
60
|
'derived count, its display pattern, and its slots), "input-dimension" (something that VARIES ' +
|
|
59
61
|
"across the dataset — subject, timepoint, batch — with the observations evidencing it), " +
|
|
@@ -25,9 +25,11 @@ export function createWriteFileTool(deps) {
|
|
|
25
25
|
// body runs unwrapped in the workflow body (see the harness-tools spec).
|
|
26
26
|
executionMode: "workflow",
|
|
27
27
|
description: "Write a UTF-8 text file in your working directory. Relative paths " +
|
|
28
|
-
"resolve against it
|
|
29
|
-
"
|
|
30
|
-
"`
|
|
28
|
+
"resolve against it. The write replaces an existing file whole, and it makes " +
|
|
29
|
+
"missing parent directories. On success it returns `status: ok`, the absolute " +
|
|
30
|
+
"'/<analysisId>/...' path, and `bytesWritten`. A path outside the working directory " +
|
|
31
|
+
"returns an `out_of_prefix` data variant (no I/O), one escaping the analysis tree returns " +
|
|
32
|
+
"`out_of_scope`, and a symbolic link on the path returns `symlink_denied`.",
|
|
31
33
|
inputSchema: WriteFileInputSchema,
|
|
32
34
|
// The path only. `content` is a whole file and must never ride a display
|
|
33
35
|
// channel, which the emit-site length cap enforces regardless.
|
|
@@ -76,6 +76,8 @@ import type { Logger } from "../lib/logger.js";
|
|
|
76
76
|
import type { UsageRecorder } from "../billing/usage-recorder.js";
|
|
77
77
|
import type { CitationResolver } from "../citations/types.js";
|
|
78
78
|
import type { SynthesisStatus, UpdateStepExecutionInput } from "../state/index.js";
|
|
79
|
+
import { type StepBriefing } from "../prompts/briefing.js";
|
|
80
|
+
import type { WorkingMemoryStore } from "../memory/working-memory.js";
|
|
79
81
|
import type { AnalysisStep } from "../schemas/workflow-state.js";
|
|
80
82
|
import type { ChatProvider, EmbeddingProvider } from "../providers/types.js";
|
|
81
83
|
import type { BioToolKeys } from "../tools/bio/keys.js";
|
|
@@ -248,6 +250,11 @@ export interface ExecuteAnalysisDeps {
|
|
|
248
250
|
readonly usageRecorder?: UsageRecorder;
|
|
249
251
|
/** The store of the loops of the run synthesis and of their read tools. */
|
|
250
252
|
readonly toolOutputStore?: ToolOutputStore;
|
|
253
|
+
/**
|
|
254
|
+
* The working memory of the analysis. The body only reads it: each step seed
|
|
255
|
+
* carries the goal and the constraints as they are at the dispatch of that step.
|
|
256
|
+
*/
|
|
257
|
+
readonly workingMemory: WorkingMemoryStore;
|
|
251
258
|
/**
|
|
252
259
|
* The provenance seam of the composition. The body reads its run emit member,
|
|
253
260
|
* and it reads no other member.
|
|
@@ -309,11 +316,6 @@ export declare function buildChildInput(args: {
|
|
|
309
316
|
* rendered any earlier (at plan submission, say) cannot mention them, and the
|
|
310
317
|
* agent is left to rediscover its own upstream.
|
|
311
318
|
*
|
|
312
|
-
* Everything read here is durable state written by an already-settled child
|
|
313
|
-
* (`output/summary.md`, `cortex_artifacts`) or by the profiler
|
|
314
|
-
* (`cortex_analysis_state`) — never live in-flight state, which would not
|
|
315
|
-
* reproduce.
|
|
316
|
-
*
|
|
317
319
|
* REPLAY: the caller wraps this in a `DBOS.runStep`, so the composed string is
|
|
318
320
|
* checkpointed and a replay returns it verbatim — the DB and disk are not read
|
|
319
321
|
* again and cannot drift the seed under a recovered run. Never call it
|
|
@@ -325,6 +327,27 @@ export declare function composeStepSeed(args: {
|
|
|
325
327
|
runId: string;
|
|
326
328
|
deps: ExecuteAnalysisDeps;
|
|
327
329
|
}): Promise<string>;
|
|
330
|
+
/**
|
|
331
|
+
* Do every read of one step's package and give the data its seed is rendered
|
|
332
|
+
* from. All reads live here, so `composeStepBriefing` stays a pure renderer.
|
|
333
|
+
*
|
|
334
|
+
* Everything read is durable state: written by an already-settled child
|
|
335
|
+
* (`output/summary.md`, `cortex_artifacts`), by the profiler
|
|
336
|
+
* (`cortex_analysis_state`), or by the conversation agent
|
|
337
|
+
* (`cortex_working_memory`). The working memory is the one read that can
|
|
338
|
+
* change while the run is live — it is read per dispatch, not once per run,
|
|
339
|
+
* so an edit during a run reaches each later step, and two steps of one run
|
|
340
|
+
* can carry different copies.
|
|
341
|
+
*
|
|
342
|
+
* A read failure throws (house rules): a swallowed error would be frozen into
|
|
343
|
+
* the checkpointed seed and served to every replay of this step.
|
|
344
|
+
*/
|
|
345
|
+
export declare function gatherStepPackage(args: {
|
|
346
|
+
input: ExecuteAnalysisInput;
|
|
347
|
+
stepId: string;
|
|
348
|
+
runId: string;
|
|
349
|
+
deps: ExecuteAnalysisDeps;
|
|
350
|
+
}): Promise<StepBriefing>;
|
|
328
351
|
/**
|
|
329
352
|
* Project a classified synthesis outcome onto the run-phase `synthesis` ledger
|
|
330
353
|
* row's terminal update (see the run-synthesis-outcome spec). The blocker keeps
|
|
@@ -82,7 +82,7 @@ import { unwrapOrThrow } from "../lib/result.js";
|
|
|
82
82
|
import { countArtifactsForRun, insertStepExecution, loadDataProfileStatus, queryActiveRun, queryStepArtifactPaths, queryStepsByRun, seedStepExecutions, setRunSynthesisOutcome, suspendAnalysis as suspendAnalysisQuery, sweepPendingStepExecutions, updateRunStatus, updateStepExecution, } from "../state/index.js";
|
|
83
83
|
import { addChatUsage, hasReportedUsage } from "../loop/metrics.js";
|
|
84
84
|
import { SYNTHESIS_AGENT_ID, loadStepSummariesFromDisk } from "../execution/run-synthesis.js";
|
|
85
|
-
import { MAX_UPSTREAM_ARTIFACTS, composeStepBriefing } from "../prompts/briefing.js";
|
|
85
|
+
import { MAX_UPSTREAM_ARTIFACTS, composeStepBriefing, emptyStepMemory } from "../prompts/briefing.js";
|
|
86
86
|
import { SYNTHESIS_STEP_ID, runDir, runStepDir, stepSubdir, stepWritePrefix, toSandboxPath } from "../workspace/paths.js";
|
|
87
87
|
import { isChatDataPart } from "../sandbox/sandbox-step-translate.js";
|
|
88
88
|
import { synthesizeRun } from "../app/synthesize-run.js";
|
|
@@ -220,17 +220,30 @@ export function buildChildInput(args) {
|
|
|
220
220
|
* rendered any earlier (at plan submission, say) cannot mention them, and the
|
|
221
221
|
* agent is left to rediscover its own upstream.
|
|
222
222
|
*
|
|
223
|
-
* Everything read here is durable state written by an already-settled child
|
|
224
|
-
* (`output/summary.md`, `cortex_artifacts`) or by the profiler
|
|
225
|
-
* (`cortex_analysis_state`) — never live in-flight state, which would not
|
|
226
|
-
* reproduce.
|
|
227
|
-
*
|
|
228
223
|
* REPLAY: the caller wraps this in a `DBOS.runStep`, so the composed string is
|
|
229
224
|
* checkpointed and a replay returns it verbatim — the DB and disk are not read
|
|
230
225
|
* again and cannot drift the seed under a recovered run. Never call it
|
|
231
226
|
* unwrapped from the workflow body.
|
|
232
227
|
*/
|
|
233
228
|
export async function composeStepSeed(args) {
|
|
229
|
+
return composeStepBriefing(await gatherStepPackage(args));
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Do every read of one step's package and give the data its seed is rendered
|
|
233
|
+
* from. All reads live here, so `composeStepBriefing` stays a pure renderer.
|
|
234
|
+
*
|
|
235
|
+
* Everything read is durable state: written by an already-settled child
|
|
236
|
+
* (`output/summary.md`, `cortex_artifacts`), by the profiler
|
|
237
|
+
* (`cortex_analysis_state`), or by the conversation agent
|
|
238
|
+
* (`cortex_working_memory`). The working memory is the one read that can
|
|
239
|
+
* change while the run is live — it is read per dispatch, not once per run,
|
|
240
|
+
* so an edit during a run reaches each later step, and two steps of one run
|
|
241
|
+
* can carry different copies.
|
|
242
|
+
*
|
|
243
|
+
* A read failure throws (house rules): a swallowed error would be frozen into
|
|
244
|
+
* the checkpointed seed and served to every replay of this step.
|
|
245
|
+
*/
|
|
246
|
+
export async function gatherStepPackage(args) {
|
|
234
247
|
const { input, stepId, runId, deps } = args;
|
|
235
248
|
const step = input.planStepById[stepId];
|
|
236
249
|
if (!step) {
|
|
@@ -246,9 +259,19 @@ export async function composeStepSeed(args) {
|
|
|
246
259
|
analysisId,
|
|
247
260
|
workingDir: toSandboxPath(workspaceRoot, analysisId, stepWritePrefix({ workspaceRoot, runId, stepId })),
|
|
248
261
|
};
|
|
249
|
-
// A DB failure here throws (house rules): a swallowed error would be frozen
|
|
250
|
-
// into the checkpointed seed and served to every replay of this step.
|
|
251
262
|
const profile = unwrapOrThrow(await loadDataProfileStatus(deps.pool, analysisId));
|
|
263
|
+
// A failed memory read leaves the section out: the step runs without the copy of the memory, not without a seed.
|
|
264
|
+
const memoryRead = await deps.workingMemory.load(analysisId);
|
|
265
|
+
if (memoryRead.isErr()) {
|
|
266
|
+
const log = (deps.logger ?? createNoopLogger()).named("executeAnalysis.seed");
|
|
267
|
+
log.warn("the working-memory read failed, thus the seed has no memory section", {
|
|
268
|
+
analysisId,
|
|
269
|
+
stepId,
|
|
270
|
+
error: describeDbError(memoryRead.error),
|
|
271
|
+
...log.errorFields(memoryRead.error.cause),
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
const memory = memoryRead.isOk() ? memoryRead.value : emptyStepMemory();
|
|
252
275
|
const upstream = await collectUpstreamHandoffs({
|
|
253
276
|
analysisId,
|
|
254
277
|
runId,
|
|
@@ -257,12 +280,13 @@ export async function composeStepSeed(args) {
|
|
|
257
280
|
agentByStepId: input.agentByStepId,
|
|
258
281
|
pool: deps.pool,
|
|
259
282
|
});
|
|
260
|
-
return
|
|
283
|
+
return {
|
|
261
284
|
step,
|
|
262
285
|
workspace,
|
|
263
286
|
profile: profile?.result ?? null,
|
|
264
287
|
upstream,
|
|
265
|
-
|
|
288
|
+
memory: { goal: memory.goal, constraints: memory.constraints },
|
|
289
|
+
};
|
|
266
290
|
}
|
|
267
291
|
/**
|
|
268
292
|
* Gather what each completed dependency produced. Dependency order is the
|
package/package.json
CHANGED