@inflexa-ai/harness 0.31.1 → 0.33.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 (96) hide show
  1. package/dist/agents/conversation-agent.d.ts +15 -0
  2. package/dist/agents/conversation-agent.js +16 -6
  3. package/dist/agents/report-session-agent.d.ts +9 -0
  4. package/dist/agents/report-session-agent.js +10 -2
  5. package/dist/agents/sandbox/shared.d.ts +13 -1
  6. package/dist/agents/sandbox/shared.js +22 -5
  7. package/dist/app/chat-turn.d.ts +11 -0
  8. package/dist/app/chat-turn.js +17 -1
  9. package/dist/app/spawn-report-session.d.ts +12 -0
  10. package/dist/app/spawn-report-session.js +21 -1
  11. package/dist/config/environment-stores.d.ts +53 -5
  12. package/dist/contracts/report-blocks.d.ts +8 -8
  13. package/dist/contracts/schemas/chat-parts.d.ts +6 -6
  14. package/dist/index.d.ts +8 -1
  15. package/dist/index.js +11 -0
  16. package/dist/prompts/briefing.d.ts +1 -1
  17. package/dist/prompts/briefing.js +4 -0
  18. package/dist/prompts/planner.js +13 -0
  19. package/dist/prompts/sandbox/multi-omics-integration-agent.d.ts +1 -1
  20. package/dist/prompts/sandbox/multi-omics-integration-agent.js +5 -5
  21. package/dist/prompts/sandbox/network-agent.d.ts +1 -1
  22. package/dist/prompts/sandbox/network-agent.js +4 -3
  23. package/dist/prompts/sandbox-standards.d.ts +20 -0
  24. package/dist/prompts/sandbox-standards.js +65 -0
  25. package/dist/provenance/seam.d.ts +203 -0
  26. package/dist/provenance/seam.js +71 -0
  27. package/dist/reference-data/catalog.js +11 -9
  28. package/dist/report-model/authoring-grammar.d.ts +2 -2
  29. package/dist/report-render/assets.d.ts +10 -0
  30. package/dist/report-render/assets.js +14 -0
  31. package/dist/report-render/design.js +256 -0
  32. package/dist/report-render/page.d.ts +75 -0
  33. package/dist/report-render/page.js +793 -1
  34. package/dist/report-render/provenance-data.d.ts +42 -0
  35. package/dist/report-render/provenance-data.js +62 -0
  36. package/dist/report-render/render.d.ts +20 -1
  37. package/dist/report-render/render.js +23 -10
  38. package/dist/report-render/types.d.ts +3 -0
  39. package/dist/report-render/views/chart-view.d.ts +5 -1
  40. package/dist/report-render/views/chart-view.js +6 -2
  41. package/dist/report-render/views/lineage.d.ts +93 -0
  42. package/dist/report-render/views/lineage.js +86 -0
  43. package/dist/report-render/views/page-view.d.ts +22 -1
  44. package/dist/report-render/views/page-view.js +12 -3
  45. package/dist/report-render/views/prose.d.ts +7 -1
  46. package/dist/report-render/views/prose.js +10 -6
  47. package/dist/report-render/views/references-view.d.ts +17 -1
  48. package/dist/report-render/views/references-view.js +12 -4
  49. package/dist/report-render/views/values.d.ts +17 -4
  50. package/dist/report-render/views/values.js +21 -8
  51. package/dist/runtime/assemble.d.ts +22 -0
  52. package/dist/runtime/assemble.js +4 -0
  53. package/dist/sandbox/create-sandbox.d.ts +20 -4
  54. package/dist/sandbox/create-sandbox.js +4 -0
  55. package/dist/sandbox/docker-client.d.ts +16 -6
  56. package/dist/sandbox/docker-client.js +47 -31
  57. package/dist/sandbox/farm.d.ts +95 -0
  58. package/dist/sandbox/farm.js +127 -0
  59. package/dist/sandbox/k8s-client.d.ts +24 -10
  60. package/dist/sandbox/k8s-client.js +85 -122
  61. package/dist/sandbox/mount-plan.d.ts +23 -1
  62. package/dist/sandbox/mount-plan.js +44 -7
  63. package/dist/sandbox/sandbox-error.d.ts +12 -0
  64. package/dist/sandbox/sandbox-error.js +2 -0
  65. package/dist/sandbox/types.d.ts +118 -0
  66. package/dist/schemas/artifact-manifest.d.ts +4 -4
  67. package/dist/schemas/plan-schemas.d.ts +3 -0
  68. package/dist/schemas/plan-schemas.js +1 -1
  69. package/dist/schemas/validate-plan.js +20 -1
  70. package/dist/schemas/workflow-state.d.ts +4 -0
  71. package/dist/schemas/workflow-state.js +10 -0
  72. package/dist/tasks/data-profile.d.ts +15 -0
  73. package/dist/tasks/data-profile.js +36 -26
  74. package/dist/tools/display/show-user.d.ts +1 -1
  75. package/dist/tools/execute-analysis.d.ts +18 -0
  76. package/dist/tools/execute-analysis.js +87 -0
  77. package/dist/tools/report-authoring/authoring-tools.d.ts +16 -1
  78. package/dist/tools/report-authoring/authoring-tools.js +58 -16
  79. package/dist/tools/report-session/derive-table.d.ts +3 -0
  80. package/dist/tools/report-session/derive-table.js +15 -0
  81. package/dist/tools/report-session/preview-report.d.ts +13 -4
  82. package/dist/tools/report-session/preview-report.js +23 -6
  83. package/dist/tools/report-session/record-version.d.ts +3 -0
  84. package/dist/tools/report-session/record-version.js +7 -3
  85. package/dist/tools/research/generate-plan.js +12 -2
  86. package/dist/tools/sandbox/index.d.ts +1 -0
  87. package/dist/tools/sandbox/index.js +1 -0
  88. package/dist/tools/sandbox/link-packages.d.ts +27 -0
  89. package/dist/tools/sandbox/link-packages.js +52 -0
  90. package/dist/tools/sandbox/list-available-packages.d.ts +50 -30
  91. package/dist/tools/sandbox/list-available-packages.js +131 -40
  92. package/dist/tools/start-report-session.d.ts +7 -0
  93. package/dist/tools/start-report-session.js +1 -0
  94. package/dist/workflows/execute-analysis.d.ts +15 -69
  95. package/dist/workflows/execute-analysis.js +5 -4
  96. package/package.json +2 -1
@@ -29,12 +29,14 @@ import type { AcquireEyes } from "../lib/eyes.js";
29
29
  import type { AgentDefinition } from "../loop/types.js";
30
30
  import type { ChatProvider, EmbeddingProvider } from "../providers/types.js";
31
31
  import type { Tool } from "../tools/define-tool.js";
32
+ import type { ExtendAnalysisFarm } from "../sandbox/types.js";
32
33
  import type { WorkspaceFilesystem } from "../workspace/filesystem.js";
33
34
  import type { ResolveWorkspaceRoot } from "../workspace/paths.js";
34
35
  import { type BioToolKeys } from "../tools/bio/keys.js";
35
36
  import type { EnvironmentStorePaths } from "../config/environment-stores.js";
36
37
  import type { RunAuthorizer } from "../execution/run-authorizer.js";
37
38
  import type { RunLauncher } from "../execution/run-launcher.js";
39
+ import type { ProvenanceSeam } from "../provenance/seam.js";
38
40
  import type { Logger } from "../lib/logger.js";
39
41
  import type { UsageRecorder } from "../billing/usage-recorder.js";
40
42
  import type { CitationResolver } from "../citations/types.js";
@@ -111,6 +113,12 @@ export interface ConversationAgentDeps extends EnvironmentStorePaths {
111
113
  * at a page reads that same answer.
112
114
  */
113
115
  readonly eyes?: AcquireEyes;
116
+ /**
117
+ * The provenance seam. `start_report_session` hands it to its spawn, which
118
+ * emits the creation of the session at the moment that the child lands. No
119
+ * other tool of this roster reads it.
120
+ */
121
+ readonly provenance?: ProvenanceSeam;
114
122
  /**
115
123
  * Host resource policy — per-step ceilings + machine budget. `generate_plan`
116
124
  * states the ceilings to the planner and validates against them;
@@ -125,6 +133,13 @@ export interface ConversationAgentDeps extends EnvironmentStorePaths {
125
133
  * built-in tool.
126
134
  */
127
135
  readonly hostTools?: readonly Tool[];
136
+ /**
137
+ * The farm-extension seam of the embedder. When it is bound,
138
+ * `execute_analysis` links the packages of the plan into the farm of the
139
+ * analysis before the launch, and a pool miss refuses the launch. Without
140
+ * it, the link pass returns at once.
141
+ */
142
+ readonly extendAnalysisFarm?: ExtendAnalysisFarm;
128
143
  }
129
144
  /** Build the conversation `AgentDefinition` with every tool fully dep-bound. */
130
145
  export declare function createConversationAgent(deps: ConversationAgentDeps): AgentDefinition;
@@ -42,7 +42,7 @@ export const CONVERSATION_AGENT_ID = "conversation-agent";
42
42
  const CONVERSATION_MAX_ITERATIONS = 200;
43
43
  /** Build the conversation `AgentDefinition` with every tool fully dep-bound. */
44
44
  export function createConversationAgent(deps) {
45
- const { provider, pool, embedding, workspaceFs, model, utilityProvider, utilityModel, executeAnalysisWorkflow, runAuthorizer, runLauncher, bioKeys, chrome, resourcePolicy, hostTools, refStorePath, packagesFile, usageRecorder, citationResolver, } = deps;
45
+ const { provider, pool, embedding, workspaceFs, model, utilityProvider, utilityModel, executeAnalysisWorkflow, runAuthorizer, runLauncher, bioKeys, chrome, resourcePolicy, hostTools, refStorePath, farmLockFile, imagePackagesFile, readPoolInventory, usageRecorder, citationResolver, } = deps;
46
46
  const workingMemory = createWorkingMemory(pool);
47
47
  const ncbi = createNcbiTools(bioKeys);
48
48
  const chemDb = createChemDbTools(bioKeys, { ...(deps.logger ? { logger: deps.logger } : {}) });
@@ -86,10 +86,16 @@ export function createConversationAgent(deps) {
86
86
  // blind is how a user gets asked to download something already installed, or
87
87
  // told nothing is missing when it is; this is the only tool that can tell.
88
88
  createListAvailableRefsTool({ ...(refStorePath ? { refStorePath } : {}) }),
89
- // What is importable inside a sandbox. Reads the same manifest a sandbox agent
90
- // reads, host-side — so "is scanpy available?" is a manifest lookup here rather
91
- // than launching analysis computation to run one import.
92
- createListAvailablePackagesTool({ ...(packagesFile ? { packagesFile } : {}) }),
89
+ // What the STORE holds, pool-scope where the embedder binds the reader:
90
+ // the ask flow of this agent marks the packages the pool does not hold,
91
+ // and the farm of a new analysis is empty — a farm view here would read
92
+ // every pool package as absent. The farm-lock fallback keeps a host
93
+ // with no pool reader on the old answer.
94
+ createListAvailablePackagesTool({
95
+ ...(farmLockFile ? { farmLockFile } : {}),
96
+ ...(imagePackagesFile ? { imagePackagesFile } : {}),
97
+ ...(readPoolInventory ? { readPoolInventory } : {}),
98
+ }),
93
99
  // Execution.
94
100
  createInspectRunTool(pool),
95
101
  // The dataset's own record. No file backs it — the DB row is the only copy.
@@ -101,7 +107,9 @@ export function createConversationAgent(deps) {
101
107
  usageRecorder,
102
108
  bioKeys,
103
109
  ...(refStorePath ? { refStorePath } : {}),
104
- ...(packagesFile ? { packagesFile } : {}),
110
+ ...(farmLockFile ? { farmLockFile } : {}),
111
+ ...(imagePackagesFile ? { imagePackagesFile } : {}),
112
+ ...(readPoolInventory ? { readPoolInventory } : {}),
105
113
  ...(deps.logger ? { logger: deps.logger } : {}),
106
114
  }),
107
115
  createExecuteAnalysisTool({
@@ -112,6 +120,7 @@ export function createConversationAgent(deps) {
112
120
  resourcePolicy,
113
121
  utilityProvider,
114
122
  utilityModel,
123
+ ...(deps.extendAnalysisFarm ? { extendAnalysisFarm: deps.extendAnalysisFarm } : {}),
115
124
  ...(deps.logger ? { logger: deps.logger } : {}),
116
125
  }),
117
126
  // The one report path. The tool starts a report thread as a child of the
@@ -122,6 +131,7 @@ export function createConversationAgent(deps) {
122
131
  chrome,
123
132
  anchorSession: deps.anchorReportSession,
124
133
  ...(deps.eyes ? { eyes: deps.eyes } : {}),
134
+ ...(deps.provenance ? { provenance: deps.provenance } : {}),
125
135
  ...(deps.logger ? { logger: deps.logger } : {}),
126
136
  }),
127
137
  // Display.
@@ -38,6 +38,7 @@ import type { ThreadStore } from "../memory/thread-store.js";
38
38
  import type { ReferenceResolver } from "../report-model/reference-resolver.js";
39
39
  import type { ReportVersionStore } from "../state/report-versions.js";
40
40
  import type { ReportSessionStateGateway } from "../tools/report-authoring/authoring-tools.js";
41
+ import type { ProvenanceSeam } from "../provenance/seam.js";
41
42
  import { type MakeSessionPagePublisher, type ResolvePageAsset, type ResolvePageUrl } from "../tools/report-session/index.js";
42
43
  /** Canonical agent id -- distinct from the id of the old templating agent. */
43
44
  export declare const REPORT_SESSION_AGENT_ID: "report-session";
@@ -114,6 +115,14 @@ export interface ReportSessionAgentDeps {
114
115
  * tool navigates through a `file://` URL of the page path.
115
116
  */
116
117
  readonly resolvePageUrl?: ResolvePageUrl;
118
+ /**
119
+ * The provenance seam -- the acts of the session as typed events, and the frozen document of the
120
+ * analysis as opaque bytes. The tools that change the draft, derive a table, preview the page, and
121
+ * record a version each emit through the session member, thus one bound member carries the whole
122
+ * session. The preview asks the read member before each render, and the page then carries the
123
+ * document. An unbound member emits nothing, or gives absence, and every tool acts the same.
124
+ */
125
+ readonly provenance?: ProvenanceSeam;
117
126
  /** Operational logging seam; omitted falls back to no-op. */
118
127
  readonly logger?: Logger;
119
128
  }
@@ -39,8 +39,11 @@ const REPORT_SESSION_MAX_ITERATIONS = 200;
39
39
  /** Build the report `AgentDefinition` with every tool bound to its deps. */
40
40
  export function createReportSessionAgent(deps) {
41
41
  const { model, pool, embedding, workspaceFs, gateway, resolveWorkspaceRoot, store, threads, chrome, eyes, makeResolver, resolvePageAsset, logger } = deps;
42
- const { derivations, runDerivation, runAuthorizer, makeSessionPages, resolvePageUrl } = deps;
43
- const authoring = createReportAuthoringTools(gateway);
42
+ const { derivations, runDerivation, runAuthorizer, makeSessionPages, resolvePageUrl, provenance } = deps;
43
+ // The seam rides into each tool that lands an act of the session, thus the composition binds it one
44
+ // time and no tool reads a second source.
45
+ const observation = provenance ? { provenance } : {};
46
+ const authoring = createReportAuthoringTools(gateway, { ...observation, ...(logger ? { logger } : {}) });
44
47
  const tools = [
45
48
  // The read surface toward the analysis. It roams the tree read-only.
46
49
  createReadFileTool(workspaceFs),
@@ -75,6 +78,7 @@ export function createReportSessionAgent(deps) {
75
78
  derivations,
76
79
  ...(runDerivation ? { runDerivation } : {}),
77
80
  ...(runAuthorizer ? { runAuthorizer } : {}),
81
+ ...observation,
78
82
  ...(logger ? { logger } : {}),
79
83
  }),
80
84
  // The render-and-preview tool. It resolves the page root per call.
@@ -84,6 +88,9 @@ export function createReportSessionAgent(deps) {
84
88
  ...(makeResolver ? { makeResolver } : {}),
85
89
  ...(resolvePageAsset ? { resolvePageAsset } : {}),
86
90
  ...(makeSessionPages ? { makeSessionPages } : {}),
91
+ // The preview is the one site that carries the document into the page, thus the read member
92
+ // reaches this tool alone.
93
+ ...observation,
87
94
  ...(logger ? { logger } : {}),
88
95
  }),
89
96
  // The eyes tool. It opens the rendered page in headless Chrome.
@@ -103,6 +110,7 @@ export function createReportSessionAgent(deps) {
103
110
  threads,
104
111
  resolveWorkspaceRoot,
105
112
  ...(makeResolver ? { makeResolver } : {}),
113
+ ...observation,
106
114
  ...(logger ? { logger } : {}),
107
115
  }),
108
116
  ];
@@ -19,7 +19,7 @@ import type { Pool } from "pg";
19
19
  import type { AgentDefinition } from "../../loop/types.js";
20
20
  import type { ChatProvider, EmbeddingProvider } from "../../providers/types.js";
21
21
  import type { SandboxClient } from "../../sandbox/client.js";
22
- import type { SandboxRef } from "../../sandbox/types.js";
22
+ import type { ExtendAnalysisFarm, SandboxRef, ToolchainSource } from "../../sandbox/types.js";
23
23
  import type { WorkspaceFilesystem } from "../../workspace/filesystem.js";
24
24
  import type { ProvenanceCollector as LineageCollector } from "../../provenance/collector.js";
25
25
  import { type BioToolKeys } from "../../tools/bio/keys.js";
@@ -84,6 +84,18 @@ export interface SandboxAgentDeps extends EnvironmentStorePaths {
84
84
  * agents that have no terminal status to declare (such as the data profiler).
85
85
  */
86
86
  readonly blockerHolder?: BlockerHolder;
87
+ /**
88
+ * The farm-extension seam of the embedder. When it is bound, every
89
+ * sandbox agent gets the `link_packages` tool in its always-on substrate,
90
+ * and the package-link prompt layer appends. Without it, neither exists.
91
+ */
92
+ readonly extendAnalysisFarm?: ExtendAnalysisFarm;
93
+ /**
94
+ * The declared toolchain owner. Keys the orient-core environment text.
95
+ * Absent keeps the legacy text, thus an old embedder keeps its cached
96
+ * prompt prefix.
97
+ */
98
+ readonly toolchainSource?: ToolchainSource;
87
99
  }
88
100
  /** Per-agent override for the prompt composition and tool surface. */
89
101
  export interface SandboxAgentPromptOptions {
@@ -15,10 +15,10 @@
15
15
  * must map to a concrete `Tool`; unknown names throw at composition time
16
16
  * so misconfigured agents fail at startup, not at the first LLM call.
17
17
  */
18
- import { sandboxOrientCorePrompt, sandboxAnalysisStepStandardsPrompt } from "../../prompts/sandbox-standards.js";
18
+ import { sandboxOrientCorePromptFor, sandboxPackageLinkPrompt, sandboxAnalysisStepStandardsPrompt } from "../../prompts/sandbox-standards.js";
19
19
  import { composeSystemPrompt } from "../system-prompt.js";
20
- // Sandbox-environment introspection.
21
- import { createListAvailablePackagesTool, createListAvailableRefsTool } from "../../tools/sandbox/index.js";
20
+ // Sandbox-environment introspection, plus the farm-extension seam face.
21
+ import { createLinkPackagesTool, createListAvailablePackagesTool, createListAvailableRefsTool } from "../../tools/sandbox/index.js";
22
22
  // Context7 docs (pure leaves).
23
23
  import { queryDocsTool, resolveLibraryIdTool } from "../../tools/research/context7-docs.js";
24
24
  // Run-inspection (dependency-bearing).
@@ -59,7 +59,13 @@ function resolveSandboxTools(deps, tools) {
59
59
  throw new Error('createSandboxAgent: SandboxToolName "resolve_citation" requires a CitationResolver dependency.');
60
60
  }
61
61
  const registry = {
62
- listAvailablePackages: createListAvailablePackagesTool({ ...(deps.packagesFile ? { packagesFile: deps.packagesFile } : {}) }),
62
+ listAvailablePackages: createListAvailablePackagesTool({
63
+ ...(deps.farmLockFile ? { farmLockFile: deps.farmLockFile } : {}),
64
+ ...(deps.imagePackagesFile ? { imagePackagesFile: deps.imagePackagesFile } : {}),
65
+ // The bound seam adds `link_packages` below, and the description of
66
+ // the listing must then stop at "absent", not at "missing".
67
+ linkToolPresent: deps.extendAnalysisFarm !== undefined,
68
+ }),
63
69
  listAvailableRefs: createListAvailableRefsTool({ ...(deps.refStorePath ? { refStorePath: deps.refStorePath } : {}) }),
64
70
  scanInputs: createScanInputsTool({
65
71
  workspaceFs: deps.workspaceFs,
@@ -182,7 +188,14 @@ function buildWorkspaceTools(deps, readOnly) {
182
188
  */
183
189
  export function createSandboxAgent(deps, meta, body, opts = {}) {
184
190
  const appendStandards = opts.appendAnalysisStepStandards ?? true;
185
- const sandboxLayer = appendStandards ? [sandboxOrientCorePrompt, sandboxAnalysisStepStandardsPrompt] : [sandboxOrientCorePrompt];
191
+ // The layer order is fixed by the harness-sandbox-agents spec: the agent
192
+ // body, the orient core keyed on the declared toolchain, the package-link
193
+ // layer only when the seam is bound, then the step standards.
194
+ const sandboxLayer = [
195
+ sandboxOrientCorePromptFor(deps.toolchainSource),
196
+ ...(deps.extendAnalysisFarm ? [sandboxPackageLinkPrompt] : []),
197
+ ...(appendStandards ? [sandboxAnalysisStepStandardsPrompt] : []),
198
+ ];
186
199
  const agentBody = [body, ...sandboxLayer].map((s) => s.trim()).join("\n\n");
187
200
  const systemPrompt = composeSystemPrompt(agentBody);
188
201
  const skillTools = deps.skillsDir ? Object.values(createSkillTools({ skillsDir: deps.skillsDir, skills: meta.skills })) : [];
@@ -194,6 +207,10 @@ export function createSandboxAgent(deps, meta, body, opts = {}) {
194
207
  // it has no fallback but to re-derive organism, dimensions, and format from the
195
208
  // raw bytes — so every sandbox agent gets it, whatever its meta declares.
196
209
  createInspectDataProfileTool(deps.pool),
210
+ // Always-on when the seam is bound, and never in the `meta.tools`
211
+ // allowlist — an allowlist entry would break an embedder that binds no
212
+ // seam (the harness-sandbox-agents spec).
213
+ ...(deps.extendAnalysisFarm ? [createLinkPackagesTool({ extendAnalysisFarm: deps.extendAnalysisFarm, analysisId: deps.step.analysisId })] : []),
197
214
  ...skillTools,
198
215
  ...resolveSandboxTools(deps, meta.tools),
199
216
  ...(deps.blockerHolder ? [createReportBlockerTool(deps.blockerHolder)] : []),
@@ -16,10 +16,21 @@ import type { Pool } from "pg";
16
16
  import { type ThreadType } from "../memory/thread-store.js";
17
17
  import { type AssembledMessages } from "./message-assembly.js";
18
18
  import type { Logger } from "../lib/logger.js";
19
+ import { type ProvenanceSeam } from "../provenance/seam.js";
19
20
  export interface PrepareChatTurnDeps {
20
21
  /** Operational logging seam; omitted falls back to no-op. */
21
22
  readonly logger?: Logger;
22
23
  readonly pool: Pool;
24
+ /**
25
+ * The provenance seam. The turn reads its session emit member, and no other
26
+ * member. The turn holds the one site that writes the conversation thread of
27
+ * an analysis, thus it is the one site that can tell the embedder the true
28
+ * moment of that creation.
29
+ *
30
+ * The member is optional and fire-and-forget, the same as it is at each site
31
+ * of a report session.
32
+ */
33
+ readonly provenance?: ProvenanceSeam;
23
34
  }
24
35
  export interface PrepareChatTurnParams {
25
36
  readonly analysisId: string;
@@ -21,6 +21,7 @@ import { loadAnalysisStatus, queryNonTerminalRunsByAnalysis } from "../state/ind
21
21
  import { assembleMessages } from "./message-assembly.js";
22
22
  import { renderRunActivity, renderRunActivityUnavailable, RUN_ACTIVITY_DETAIL_LIMIT } from "./run-activity.js";
23
23
  import { createNoopLogger } from "../lib/console-logger.js";
24
+ import { bindSessionEmit } from "../provenance/seam.js";
24
25
  /**
25
26
  * Prepare one chat turn: resolve thread ownership, seed the title, load
26
27
  * analysis status, and assemble the message array. Input sanitization stays
@@ -29,6 +30,10 @@ import { createNoopLogger } from "../lib/console-logger.js";
29
30
  export async function prepareChatTurn(deps, params) {
30
31
  const { pool } = deps;
31
32
  const { analysisId, threadId, userInput } = params;
33
+ const logger = (deps.logger ?? createNoopLogger()).named("harness.chat");
34
+ // An unbound member gives a call that does nothing, thus the emit below needs
35
+ // no test of its own.
36
+ const observe = bindSessionEmit(deps.provenance, logger);
32
37
  // Ownership check before any read/write of the thread — a `threadId`
33
38
  // owned by a different analysis is indistinguishable from a missing one.
34
39
  const store = createThreadStore(pool);
@@ -54,13 +59,24 @@ export async function prepareChatTurn(deps, params) {
54
59
  title: deriveThreadTitle(userInput),
55
60
  }));
56
61
  threadType = created.threadType;
62
+ // The row that comes back is not proof that this call wrote it. The read
63
+ // above hides an archived thread, and the idempotent insert then reads
64
+ // that same row back. Thus a turn on an archived thread reaches here with
65
+ // a row that a different site created, and an emit would record a start
66
+ // that never happened.
67
+ //
68
+ // This site writes a live conversation thread and nothing else. An
69
+ // archived row, or a row of another kind, is therefore one that already
70
+ // existed. A root session has no parent, thus the event carries none.
71
+ if (created.deletedAt === null && created.threadType === "conversation") {
72
+ observe({ type: "create-session", analysisId, threadId, sessionKind: created.threadType });
73
+ }
57
74
  }
58
75
  else if (!existing.title || existing.title.length === 0) {
59
76
  unwrapOrThrow(await store.updateTitle(threadId, deriveThreadTitle(userInput)));
60
77
  }
61
78
  }
62
79
  catch (err) {
63
- const logger = (deps.logger ?? createNoopLogger()).named("harness.chat");
64
80
  logger.warn("title-seed failed (non-fatal)", logger.errorFields(err));
65
81
  }
66
82
  const analysisState = await loadAnalysisStatus(pool, analysisId).unwrapOr(null);
@@ -66,6 +66,7 @@ import type { AcquireEyes } from "../lib/eyes.js";
66
66
  import type { Logger } from "../lib/logger.js";
67
67
  import type { CapturePage } from "../lib/page-capture.js";
68
68
  import { type Thread, type ThreadInputError, type ThreadPage, type ThreadType } from "../memory/thread-store.js";
69
+ import { type ProvenanceSeam } from "../provenance/seam.js";
69
70
  import type { EnsureSessionStateResult } from "./report-session-runtime.js";
70
71
  /**
71
72
  * A spawn the operation refuses before it writes a row. Distinct from
@@ -192,6 +193,17 @@ export interface ReportSessionSpawnDeps {
192
193
  * is idempotent.
193
194
  */
194
195
  readonly anchorSession?: (threadId: string) => Promise<EnsureSessionStateResult>;
196
+ /**
197
+ * The provenance seam. The spawn reads its session emit member, and no other
198
+ * member. The spawn is the birth of the session, thus it is the one site that
199
+ * can tell the embedder the true moment of the ask. A consumer that waits for
200
+ * the first authoring act reads a later moment, and it reads nothing at all
201
+ * for a session that stays empty.
202
+ *
203
+ * The member is optional and fire-and-forget, the same as it is at each
204
+ * authoring site.
205
+ */
206
+ readonly provenance?: ProvenanceSeam;
195
207
  /** Operational logging seam. An omitted logger falls back to the no-op. */
196
208
  readonly logger?: Logger;
197
209
  }
@@ -65,6 +65,7 @@ import { createNoopLogger } from "../lib/console-logger.js";
65
65
  import { conversationRecordTurn, createThreadHistory } from "../memory/thread-history.js";
66
66
  import { createThreadStore } from "../memory/thread-store.js";
67
67
  import { createWorkingMemory } from "../memory/working-memory.js";
68
+ import { bindSessionEmit } from "../provenance/seam.js";
68
69
  const _assertDomainError = true;
69
70
  /**
70
71
  * Whether the composition gives a route to a look at the rendered page. The
@@ -165,6 +166,9 @@ export function createReportSessionSpawn(deps) {
165
166
  // The routes are fixed at construction, thus the gate reads one boolean and
166
167
  // never a live probe of the sidecar.
167
168
  const eyesAvailable = compositionHasEyes(deps);
169
+ // An unbound member gives a call that does nothing, thus the spawn emits at
170
+ // one site with no test of its own.
171
+ const observe = bindSessionEmit(deps.provenance, log);
168
172
  /**
169
173
  * Write the one seed message of the child and give the child back. The
170
174
  * message rides as a record, not as user input: nobody typed it in the child
@@ -250,7 +254,7 @@ export function createReportSessionSpawn(deps) {
250
254
  if (!eyesAvailable) {
251
255
  return errAsync({ type: "no_browser", op: OP, parentThreadId, detail: NO_BROWSER_DETAIL });
252
256
  }
253
- return store.getThread(parentThreadId).andThen((parent) => {
257
+ const spawned = store.getThread(parentThreadId).andThen((parent) => {
254
258
  // An absent parent and an archived one arrive the same way — `getThread`
255
259
  // filters the tombstone — and both refuse as `parent_not_found`.
256
260
  if (parent === null) {
@@ -288,6 +292,22 @@ export function createReportSessionSpawn(deps) {
288
292
  .andThen((child) => ResultAsync.fromSafePromise(pinSessionSnapshot(child))));
289
293
  });
290
294
  });
295
+ // The emit rides the ok channel alone, thus a refusal and a fault tell the
296
+ // embedder nothing. The child row is on disk at this point, and no
297
+ // authoring act of the session ran yet.
298
+ //
299
+ // The kind comes from the row and not from the input, because the insert
300
+ // is idempotent and the store tells a caller to read the type back.
301
+ return spawned.map((child) => {
302
+ observe({
303
+ type: "create-session",
304
+ analysisId: child.analysisId,
305
+ threadId: child.threadId,
306
+ sessionKind: child.threadType,
307
+ parentThreadId,
308
+ });
309
+ return child;
310
+ });
291
311
  }
292
312
  function listReportSessions(analysisId, paging = {}) {
293
313
  return store.listThreads({ analysisId, type: "report", ...paging });
@@ -37,11 +37,59 @@ export interface EnvironmentStorePaths {
37
37
  */
38
38
  readonly refStorePath?: string;
39
39
  /**
40
- * Host path of the library store's `packages.txt` — the inventory of what is
41
- * importable inside a sandbox. Omit when the host mounts the store at the
40
+ * Host path of the `inflexa.lock` of the farm — the inventory of what is
41
+ * importable inside a sandbox. Omit when the host mounts the farm at the
42
42
  * sandbox's own path, which makes the container path correct as-is; a host
43
- * whose store is baked into the image and never bind-mounted must inject the
44
- * path to its own extracted copy, or the inventory reads as unknown.
43
+ * that reads the farm somewhere else must inject the path, or the
44
+ * inventory reads as unknown.
45
45
  */
46
- readonly packagesFile?: string;
46
+ readonly farmLockFile?: string;
47
+ /**
48
+ * Host path of the baked image inventory fragment
49
+ * (`image-packages.txt`) — the image-owned tools that
50
+ * `list_available_packages` merges into its report. Omit when the host
51
+ * cannot read one; a missing fragment merges nothing, and that is a
52
+ * normal state.
53
+ */
54
+ readonly imagePackagesFile?: string;
55
+ /**
56
+ * The pool-scope inventory of the package store: every package the pool
57
+ * holds, whether or not a farm links it yet. Bind it for a conversation
58
+ * or planning surface — the ask flow marks the packages that the POOL
59
+ * does not hold, and the farm of a new analysis is empty, which would
60
+ * read as "everything is absent". Leave it unbound for a sandbox agent,
61
+ * because a step imports only what its farm links. An `unavailable` read
62
+ * carries the reason of the embedder — a damaged dependency graph reads
63
+ * as unavailable WITH its cause, never as an empty pool, because the
64
+ * cause is what tells the agent the state is structural and not
65
+ * transient.
66
+ */
67
+ readonly readPoolInventory?: () => Promise<PoolInventoryRead>;
68
+ }
69
+ /**
70
+ * What one pool-scope inventory read produced: the sections, or the reason
71
+ * the pool cannot answer. There is no empty-on-error shape — an unreadable
72
+ * pool must stay distinguishable from a pool that holds nothing.
73
+ */
74
+ export type PoolInventoryRead = {
75
+ readonly kind: "sections";
76
+ readonly sections: readonly PoolInventorySection[];
77
+ } | {
78
+ readonly kind: "unavailable";
79
+ readonly reason: string;
80
+ };
81
+ /** One package entry of an inventory section, with the store identity where the source records it. */
82
+ export interface PoolInventoryPackage {
83
+ readonly name: string;
84
+ /** The pinned version, rendered as `name==version`. Absent for an image-fragment tool. */
85
+ readonly version?: string;
86
+ /** The content-addressed store directory of the package. */
87
+ readonly storeDir?: string;
88
+ /** The full sha256 of the sorted store tree. */
89
+ readonly hash?: string;
90
+ }
91
+ /** One language-track section of an inventory, as `list_available_packages` renders it. */
92
+ export interface PoolInventorySection {
93
+ readonly title: string;
94
+ readonly packages: readonly PoolInventoryPackage[];
47
95
  }
@@ -200,15 +200,15 @@ export declare const ChartAxesSchema: z.ZodObject<{
200
200
  x: z.ZodOptional<z.ZodObject<{
201
201
  title: z.ZodOptional<z.ZodString>;
202
202
  scale: z.ZodOptional<z.ZodEnum<{
203
- log: "log";
204
203
  linear: "linear";
204
+ log: "log";
205
205
  }>>;
206
206
  }, z.core.$strict>>;
207
207
  y: z.ZodOptional<z.ZodObject<{
208
208
  title: z.ZodOptional<z.ZodString>;
209
209
  scale: z.ZodOptional<z.ZodEnum<{
210
- log: "log";
211
210
  linear: "linear";
211
+ log: "log";
212
212
  }>>;
213
213
  }, z.core.$strict>>;
214
214
  }, z.core.$strict>;
@@ -297,15 +297,15 @@ export declare const ChartCompositionSchema: z.ZodObject<{
297
297
  x: z.ZodOptional<z.ZodObject<{
298
298
  title: z.ZodOptional<z.ZodString>;
299
299
  scale: z.ZodOptional<z.ZodEnum<{
300
- log: "log";
301
300
  linear: "linear";
301
+ log: "log";
302
302
  }>>;
303
303
  }, z.core.$strict>>;
304
304
  y: z.ZodOptional<z.ZodObject<{
305
305
  title: z.ZodOptional<z.ZodString>;
306
306
  scale: z.ZodOptional<z.ZodEnum<{
307
- log: "log";
308
307
  linear: "linear";
308
+ log: "log";
309
309
  }>>;
310
310
  }, z.core.$strict>>;
311
311
  }, z.core.$strict>>;
@@ -724,15 +724,15 @@ export declare const ChartBlockSchema: z.ZodObject<{
724
724
  x: z.ZodOptional<z.ZodObject<{
725
725
  title: z.ZodOptional<z.ZodString>;
726
726
  scale: z.ZodOptional<z.ZodEnum<{
727
- log: "log";
728
727
  linear: "linear";
728
+ log: "log";
729
729
  }>>;
730
730
  }, z.core.$strict>>;
731
731
  y: z.ZodOptional<z.ZodObject<{
732
732
  title: z.ZodOptional<z.ZodString>;
733
733
  scale: z.ZodOptional<z.ZodEnum<{
734
- log: "log";
735
734
  linear: "linear";
735
+ log: "log";
736
736
  }>>;
737
737
  }, z.core.$strict>>;
738
738
  }, z.core.$strict>>;
@@ -1168,15 +1168,15 @@ export declare const ATOM_BLOCK_SCHEMAS: readonly [z.ZodObject<{
1168
1168
  x: z.ZodOptional<z.ZodObject<{
1169
1169
  title: z.ZodOptional<z.ZodString>;
1170
1170
  scale: z.ZodOptional<z.ZodEnum<{
1171
- log: "log";
1172
1171
  linear: "linear";
1172
+ log: "log";
1173
1173
  }>>;
1174
1174
  }, z.core.$strict>>;
1175
1175
  y: z.ZodOptional<z.ZodObject<{
1176
1176
  title: z.ZodOptional<z.ZodString>;
1177
1177
  scale: z.ZodOptional<z.ZodEnum<{
1178
- log: "log";
1179
1178
  linear: "linear";
1179
+ log: "log";
1180
1180
  }>>;
1181
1181
  }, z.core.$strict>>;
1182
1182
  }, z.core.$strict>>;
@@ -240,9 +240,9 @@ export declare const StepOutputFileSchema: z.ZodObject<{
240
240
  fileType: z.ZodEnum<{
241
241
  output: "output";
242
242
  summary: "summary";
243
- script: "script";
244
- figure: "figure";
245
243
  log: "log";
244
+ figure: "figure";
245
+ script: "script";
246
246
  notebook: "notebook";
247
247
  }>;
248
248
  description: z.ZodString;
@@ -259,9 +259,9 @@ export declare const StepOutputPartSchema: z.ZodObject<{
259
259
  fileType: z.ZodEnum<{
260
260
  output: "output";
261
261
  summary: "summary";
262
- script: "script";
263
- figure: "figure";
264
262
  log: "log";
263
+ figure: "figure";
264
+ script: "script";
265
265
  notebook: "notebook";
266
266
  }>;
267
267
  description: z.ZodString;
@@ -597,9 +597,9 @@ export declare const CortexChatPartSchema: z.ZodDiscriminatedUnion<[z.ZodObject<
597
597
  fileType: z.ZodEnum<{
598
598
  output: "output";
599
599
  summary: "summary";
600
- script: "script";
601
- figure: "figure";
602
600
  log: "log";
601
+ figure: "figure";
602
+ script: "script";
603
603
  notebook: "notebook";
604
604
  }>;
605
605
  description: z.ZodString;
package/dist/index.d.ts CHANGED
@@ -36,6 +36,9 @@ export type { ProvenanceCollector } from "./provenance/collector.js";
36
36
  export { UnavailableSessionPagePublisher } from "./tools/report-session/session-page-publisher.js";
37
37
  export type { MakeSessionPagePublisher, SessionPagePublisher, SessionPageMintResult } from "./tools/report-session/session-page-publisher.js";
38
38
  export type { ResolvePageUrl, SessionPageAccess } from "./tools/report-session/index.js";
39
+ export { bindReadExport, bindSessionEmit } from "./provenance/seam.js";
40
+ export type { ProvenanceExport, ProvenanceSeam, RunProvenanceEvent, SessionProvenanceEvent } from "./provenance/seam.js";
41
+ export type { DerivationSource } from "./state/report-session-state.js";
39
42
  export { createDbosRunLauncher } from "./execution/dbos-run-launcher.js";
40
43
  export type { RunLauncher, LaunchOptions } from "./execution/run-launcher.js";
41
44
  export { createDbosWorkflowPurger } from "./execution/dbos-workflow-purger.js";
@@ -131,6 +134,10 @@ export type { ResolveWorkspaceRoot } from "./workspace/paths.js";
131
134
  export { createSandboxClient } from "./sandbox/create-sandbox.js";
132
135
  export type { CreateSandboxClientConfig, SandboxBackendConfig } from "./sandbox/create-sandbox.js";
133
136
  export type { SandboxClient } from "./sandbox/client.js";
137
+ export type { ExtendAnalysisFarm, FarmLocation, FarmResolution, FarmSource, PackageRequest, PackageRequestOutcome, ResolveAnalysisFarm, ToolchainSource, } from "./sandbox/types.js";
138
+ export { FARM_LOCK_FILE, FarmLockSchema, readFarmLock, readFarmLockFile } from "./sandbox/farm.js";
139
+ export type { FarmLock, FarmLockError } from "./sandbox/farm.js";
140
+ export type { EnvironmentStorePaths, PoolInventoryPackage, PoolInventoryRead, PoolInventorySection } from "./config/environment-stores.js";
134
141
  export { MachineBudgetSchema, ResourceLimitsSchema, ResourcePolicySchema, ResourceSpecSchema, parseResourcePolicy } from "./config/resource-limits.js";
135
142
  export type { MachineBudget, ResourceLimits, ResourcePolicy, ResourceSpec } from "./config/resource-limits.js";
136
143
  export { createWorkspaceFilesystem } from "./workspace/filesystem.js";
@@ -140,7 +147,7 @@ export { deliverExecEvent, execEventTopic } from "./sandbox/deliver-exec-event.j
140
147
  export { ExecEventMessageSchema } from "./sandbox/types.js";
141
148
  export type { ExecEventMessage, DoneMarker } from "./sandbox/types.js";
142
149
  export { registerExecuteAnalysis } from "./workflows/execute-analysis.js";
143
- export type { ExecuteAnalysisDeps, ExecuteAnalysisInput, ExecuteAnalysisResult, RunObservation, RunProvenanceEvent, RunStepObservation, StepRuntimeStatus, } from "./workflows/execute-analysis.js";
150
+ export type { ExecuteAnalysisDeps, ExecuteAnalysisInput, ExecuteAnalysisResult, RunObservation, RunStepObservation, StepRuntimeStatus, } from "./workflows/execute-analysis.js";
144
151
  export { registerSandboxStep } from "./workflows/sandbox-step.js";
145
152
  export type { SandboxStepDeps, SandboxStepInput, SandboxStepResult, SandboxAgentBuildContext } from "./workflows/sandbox-step.js";
146
153
  export { DEFAULT_CLAIM_INVESTIGATION_CONFIG } from "./workflows/target-assessment/investigation/index.js";
package/dist/index.js CHANGED
@@ -47,6 +47,14 @@ export { createNoopArtifactRegistry } from "./execution/noop-artifact-registry.j
47
47
  // state. The URL seam of the eyes rides beside the publisher factory: a host whose
48
48
  // browser cannot reach the workspace tree binds a served URL for the look.
49
49
  export { UnavailableSessionPagePublisher } from "./tools/report-session/session-page-publisher.js";
50
+ // Seam: the one provenance surface. The embedder records the facts of a run and the acts of a
51
+ // session in the document of the analysis that it owns, and it gives the bytes of that document
52
+ // back for the report page. Thus the seam and its vocabulary belong on the front door: an embedder
53
+ // cannot type its realization without them. The two binders ride beside the types, because an
54
+ // embedder that calls `prepareChatTurn` or one tool factory directly binds the same total forms
55
+ // that the assembly binds. `DerivationSource` rides beside the event union, because the derivation
56
+ // event carries the chain of the derived table.
57
+ export { bindReadExport, bindSessionEmit } from "./provenance/seam.js";
50
58
  // Run launching.
51
59
  export { createDbosRunLauncher } from "./execution/dbos-run-launcher.js";
52
60
  // Seam: workflow-ledger reclamation — cancels and deletes an analysis's rows in
@@ -229,6 +237,9 @@ export { createAnalysisPurge } from "./state/purge-analysis.js";
229
237
  // the failure a fixed-width comparand exists to prevent.
230
238
  export { computeInputSignature } from "./execution/input-signature.js";
231
239
  export { createSandboxClient } from "./sandbox/create-sandbox.js";
240
+ // The farm contract: one `inflexa.lock` per farm. An embedder that composes
241
+ // farms writes this shape, and the mount gate reads it — one schema, no copy.
242
+ export { FARM_LOCK_FILE, FarmLockSchema, readFarmLock, readFarmLockFile } from "./sandbox/farm.js";
232
243
  export { MachineBudgetSchema, ResourceLimitsSchema, ResourcePolicySchema, ResourceSpecSchema, parseResourcePolicy } from "./config/resource-limits.js";
233
244
  export { createWorkspaceFilesystem } from "./workspace/filesystem.js";
234
245
  // Exec-callback ingress building blocks. Delivery goes through
@@ -40,7 +40,7 @@ export declare const STEP_TASK_FIELDS: readonly ["name", "question", "descriptio
40
40
  * {@link renderResources} section, because the budget is an execution bound,
41
41
  * not a task instruction.
42
42
  */
43
- export declare const STEP_NON_TASK_FIELDS: readonly ["id", "track", "step_type", "depends_on", "status", "resources", "agent", "timeout", "maxSteps", "summary", "artifactIds", "error"];
43
+ export declare const STEP_NON_TASK_FIELDS: readonly ["id", "track", "step_type", "depends_on", "status", "resources", "agent", "timeout", "maxSteps", "summary", "artifactIds", "error", "packages"];
44
44
  /**
45
45
  * The step's own instructions. Only `question` is guaranteed present; the
46
46
  * remaining task fields are appended as dedicated sections when populated, so a