@inflexa-ai/harness 0.45.0 → 0.45.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/agents/conversation-agent.d.ts +10 -0
- package/dist/agents/conversation-agent.js +19 -1
- package/dist/agents/sandbox/shared.d.ts +23 -2
- package/dist/agents/sandbox/shared.js +51 -18
- package/dist/agents/sandbox/translational-safety-agent.js +26 -0
- package/dist/agents/sandbox/types.d.ts +24 -1
- package/dist/agents/sandbox-catalog.d.ts +14 -0
- package/dist/agents/sandbox-catalog.js +19 -0
- package/dist/app/approve-plan.d.ts +5 -2
- package/dist/app/approve-plan.js +1 -1
- package/dist/app/chat-turn.js +6 -3
- package/dist/config/environment-stores.d.ts +4 -2
- package/dist/contracts/chat-events.d.ts +12 -2
- package/dist/contracts/chat-events.js +1 -1
- package/dist/contracts/chat-frame.js +6 -2
- package/dist/contracts/index.d.ts +1 -1
- package/dist/contracts/schemas/chat-events.d.ts +25 -0
- package/dist/contracts/schemas/chat-events.js +7 -0
- package/dist/contracts/schemas/chat-parts.d.ts +39 -0
- package/dist/contracts/schemas/chat-parts.js +17 -0
- package/dist/contracts/schemas/index.d.ts +1 -1
- package/dist/contracts/schemas/index.js +1 -1
- package/dist/execution/dbos-workflow-purger.d.ts +2 -0
- package/dist/execution/dbos-workflow-purger.js +21 -14
- package/dist/execution/run-canceler.d.ts +33 -11
- package/dist/execution/run-canceler.js +129 -27
- package/dist/execution/run-end-record.d.ts +30 -0
- package/dist/execution/run-end-record.js +27 -0
- package/dist/index.d.ts +9 -5
- package/dist/index.js +11 -1
- package/dist/loop/call-guard.d.ts +50 -0
- package/dist/loop/call-guard.js +90 -0
- package/dist/loop/compaction.d.ts +5 -0
- package/dist/loop/compaction.js +17 -0
- package/dist/loop/run-agent.d.ts +9 -0
- package/dist/loop/run-agent.js +15 -1
- package/dist/loop/run-to-terminal.js +4 -1
- package/dist/loop/types.d.ts +6 -0
- package/dist/memory/conversation-display-recorder.d.ts +0 -2
- package/dist/memory/conversation-display-recorder.js +28 -3
- package/dist/memory/thread-history.d.ts +34 -7
- package/dist/memory/thread-history.js +151 -42
- package/dist/prompts/briefing.d.ts +57 -3
- package/dist/prompts/briefing.js +129 -2
- package/dist/prompts/planner.js +13 -2
- package/dist/provenance/seam.d.ts +23 -15
- package/dist/provenance/seam.js +7 -5
- package/dist/providers/ai-sdk.js +87 -7
- package/dist/providers/types.d.ts +6 -0
- package/dist/reference-data/catalog.js +141 -2
- package/dist/report-model/authoring-grammar.d.ts +26 -26
- package/dist/sandbox/image-packages.d.ts +26 -4
- package/dist/sandbox/image-packages.js +18 -4
- package/dist/schemas/data-profile-schemas.d.ts +12 -12
- package/dist/schemas/plan-schemas.d.ts +57 -3
- package/dist/schemas/plan-schemas.js +1 -1
- package/dist/schemas/workflow-state.d.ts +113 -0
- package/dist/schemas/workflow-state.js +54 -0
- package/dist/state/artifacts.d.ts +17 -1
- package/dist/state/artifacts.js +21 -3
- package/dist/state/index.d.ts +2 -2
- package/dist/state/index.js +1 -1
- package/dist/state/runs.d.ts +43 -9
- package/dist/state/runs.js +85 -14
- package/dist/state/step-executions.d.ts +9 -0
- package/dist/state/step-executions.js +22 -0
- package/dist/tools/ad-hoc-router.d.ts +2 -0
- package/dist/tools/ad-hoc-router.js +18 -2
- package/dist/tools/capability-data.d.ts +36 -0
- package/dist/tools/capability-data.js +73 -0
- package/dist/tools/execute-analysis.d.ts +37 -9
- package/dist/tools/execute-analysis.js +131 -21
- package/dist/tools/knowledge/check.d.ts +60 -0
- package/dist/tools/knowledge/check.js +92 -0
- package/dist/tools/knowledge/client.d.ts +382 -0
- package/dist/tools/knowledge/client.js +302 -0
- package/dist/tools/knowledge/environment.d.ts +78 -0
- package/dist/tools/knowledge/environment.js +105 -0
- package/dist/tools/knowledge/index.d.ts +49 -0
- package/dist/tools/knowledge/index.js +70 -0
- package/dist/tools/knowledge/local-slots.d.ts +62 -0
- package/dist/tools/knowledge/local-slots.js +177 -0
- package/dist/tools/knowledge/pin.d.ts +14 -0
- package/dist/tools/knowledge/pin.js +17 -0
- package/dist/tools/knowledge/recommend.d.ts +92 -0
- package/dist/tools/knowledge/recommend.js +98 -0
- package/dist/tools/knowledge/situation.d.ts +143 -0
- package/dist/tools/knowledge/situation.js +164 -0
- package/dist/tools/knowledge/skeleton.d.ts +69 -0
- package/dist/tools/knowledge/skeleton.js +202 -0
- package/dist/tools/knowledge/template.d.ts +137 -0
- package/dist/tools/knowledge/template.js +267 -0
- package/dist/tools/research/generate-plan.d.ts +23 -0
- package/dist/tools/research/generate-plan.js +203 -12
- package/dist/tools/research/guarded-address.d.ts +20 -0
- package/dist/tools/research/guarded-address.js +148 -0
- package/dist/tools/research/searxng-engine.d.ts +40 -0
- package/dist/tools/research/searxng-engine.js +120 -0
- package/dist/tools/research/web-fetch.d.ts +124 -0
- package/dist/tools/research/web-fetch.js +627 -0
- package/dist/tools/research/web-search.d.ts +75 -0
- package/dist/tools/research/web-search.js +112 -0
- package/dist/tools/sandbox/list-available-packages.d.ts +7 -4
- package/dist/tools/sandbox/list-available-packages.js +12 -4
- package/dist/tools/sandbox/list-available-refs.d.ts +21 -0
- package/dist/tools/sandbox/list-available-refs.js +60 -3
- package/dist/tools/sandbox/report-blocker.js +5 -1
- package/dist/tools/terminal-text.d.ts +11 -0
- package/dist/tools/terminal-text.js +19 -0
- package/dist/tools/workspace/decision-record.d.ts +81 -0
- package/dist/tools/workspace/decision-record.js +117 -0
- package/dist/tools/workspace/edit-file.d.ts +3 -1
- package/dist/tools/workspace/edit-file.js +20 -2
- package/dist/tools/workspace/execute-command.d.ts +17 -0
- package/dist/tools/workspace/execute-command.js +28 -1
- package/dist/tools/workspace/index.d.ts +1 -0
- package/dist/tools/workspace/index.js +1 -0
- package/dist/tools/workspace/mutator.d.ts +1 -1
- package/dist/workflows/execute-analysis.d.ts +58 -18
- package/dist/workflows/execute-analysis.js +273 -58
- package/dist/workflows/sandbox-step.d.ts +12 -0
- package/package.json +5 -1
|
@@ -24,14 +24,20 @@
|
|
|
24
24
|
import { type WorkingMemory } from "../memory/working-memory.js";
|
|
25
25
|
import type { DataProfileResult } from "../state/data-profile.js";
|
|
26
26
|
import type { AnalysisStep } from "../schemas/workflow-state.js";
|
|
27
|
+
import type { TemplateParameter } from "../tools/knowledge/client.js";
|
|
28
|
+
import type { TemplateBindingValue } from "../tools/knowledge/template.js";
|
|
27
29
|
/** Completed dependencies rendered as upstream blocks; the tail is counted, not rendered. */
|
|
28
30
|
export declare const MAX_UPSTREAM_DEPS = 5;
|
|
29
31
|
/** Characters of each dependency's summary carried in the seed — the gist, not the document. */
|
|
30
32
|
export declare const UPSTREAM_SUMMARY_MAX_CHARS = 500;
|
|
31
33
|
/** Artifact paths listed per dependency; the rest are reachable from its output directory. */
|
|
32
34
|
export declare const MAX_UPSTREAM_ARTIFACTS = 8;
|
|
35
|
+
/** Adaptable slots rendered in the Template contract section; the tail is counted, not rendered. */
|
|
36
|
+
export declare const MAX_TEMPLATE_SLOTS = 24;
|
|
37
|
+
/** Characters of one slot description carried in the seed — the constraint, not the manual. */
|
|
38
|
+
export declare const TEMPLATE_SLOT_DESCRIPTION_MAX_CHARS = 160;
|
|
33
39
|
/** Fields whose content is agent-facing and MUST appear in the rendered task. */
|
|
34
|
-
export declare const STEP_TASK_FIELDS: readonly ["name", "question", "description", "context", "constraints", "acceptance_criteria", "caveats"];
|
|
40
|
+
export declare const STEP_TASK_FIELDS: readonly ["name", "question", "description", "context", "constraints", "acceptance_criteria", "caveats", "grounding"];
|
|
35
41
|
/**
|
|
36
42
|
* Fields deliberately excluded from the task section: identity/DAG structure
|
|
37
43
|
* threaded separately through the workflow input, execution knobs, and result
|
|
@@ -41,7 +47,7 @@ export declare const STEP_TASK_FIELDS: readonly ["name", "question", "descriptio
|
|
|
41
47
|
* {@link renderResources} section, because the budget is an execution bound,
|
|
42
48
|
* not a task instruction.
|
|
43
49
|
*/
|
|
44
|
-
export declare const STEP_NON_TASK_FIELDS: readonly ["id", "track", "step_type", "depends_on", "status", "resources", "agent", "timeout", "maxSteps", "summary", "artifactIds", "error", "packages"];
|
|
50
|
+
export declare const STEP_NON_TASK_FIELDS: readonly ["id", "track", "step_type", "depends_on", "status", "resources", "agent", "timeout", "maxSteps", "summary", "artifactIds", "error", "packages", "capability"];
|
|
45
51
|
/**
|
|
46
52
|
* The step's own instructions. Only `question` is guaranteed present; the
|
|
47
53
|
* remaining task fields are appended as dedicated sections when populated, so a
|
|
@@ -50,7 +56,7 @@ export declare const STEP_NON_TASK_FIELDS: readonly ["id", "track", "step_type",
|
|
|
50
56
|
* folded in here — otherwise the agent runs against the bare question and
|
|
51
57
|
* improvises the rest.
|
|
52
58
|
*/
|
|
53
|
-
export declare function renderTask(step: AnalysisStep): string;
|
|
59
|
+
export declare function renderTask(step: AnalysisStep, contract?: Pick<StepBriefing, "template" | "templateNotRetrieved">): string;
|
|
54
60
|
/**
|
|
55
61
|
* The heading of the analysis-memory section. The orient core of the sandbox
|
|
56
62
|
* prompt names this same text, thus a test pins the two together.
|
|
@@ -75,6 +81,46 @@ export declare function emptyStepMemory(): StepMemory;
|
|
|
75
81
|
* entry — it has no memory tool.
|
|
76
82
|
*/
|
|
77
83
|
export declare function renderMemory(memory: StepMemory): string;
|
|
84
|
+
/** A file the script of the template reads. `path` names the slot that gives it, for example `{{counts_path}}`. */
|
|
85
|
+
export interface TemplateBriefInput {
|
|
86
|
+
readonly name: string;
|
|
87
|
+
readonly path: string;
|
|
88
|
+
readonly description?: string;
|
|
89
|
+
}
|
|
90
|
+
/** One plan setting bound to an adaptable slot of the template. */
|
|
91
|
+
export interface TemplateBoundSetting {
|
|
92
|
+
readonly name: string;
|
|
93
|
+
readonly value: TemplateBindingValue;
|
|
94
|
+
/** The source of the value (a doi or a document), or `plan` when the procedure names none. */
|
|
95
|
+
readonly source: string;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* The contract of the template a step renders, projected for the seed. The
|
|
99
|
+
* parent fetches it at dispatch and binds the plan settings to it; the
|
|
100
|
+
* section is what lets an agent that has never seen the template make one
|
|
101
|
+
* valid render call.
|
|
102
|
+
*/
|
|
103
|
+
export interface TemplateBrief {
|
|
104
|
+
/** The template reference of the plan step, with its version when the plan names one. */
|
|
105
|
+
readonly ref: string;
|
|
106
|
+
/** The version the service serves. Differs from the version of `ref` when the snapshot moved on. */
|
|
107
|
+
readonly version_served: string;
|
|
108
|
+
/** The adaptable slots of the contract, in the order the template declares them. A pinned slot is never rendered. */
|
|
109
|
+
readonly slots: readonly TemplateParameter[];
|
|
110
|
+
readonly inputs: readonly TemplateBriefInput[];
|
|
111
|
+
/** The plan settings bound to a slot, in plan order. Empty when the served version differs from the plan. */
|
|
112
|
+
readonly bound: readonly TemplateBoundSetting[];
|
|
113
|
+
/** The plan settings no bound slot carries, one line each with the reason. */
|
|
114
|
+
readonly unbound_settings: readonly string[];
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* The Template contract section. Only the adaptable slots render, bounded by
|
|
118
|
+
* {@link MAX_TEMPLATE_SLOTS} and the description clamp, so a wide template
|
|
119
|
+
* cannot blow the seed. A served version that differs from the plan renders
|
|
120
|
+
* as a caveat: the render refuses the plan version, and the settings are not
|
|
121
|
+
* bound to a version the plan did not name.
|
|
122
|
+
*/
|
|
123
|
+
export declare function renderTemplateContract(brief: TemplateBrief | undefined): string;
|
|
78
124
|
/**
|
|
79
125
|
* What anchors every path the agent writes or reads: the id the tree is mounted
|
|
80
126
|
* at, and the step's writable directory.
|
|
@@ -136,6 +182,14 @@ export interface StepBriefing {
|
|
|
136
182
|
readonly upstream: readonly UpstreamHandoff[];
|
|
137
183
|
/** The goal and the constraints of the analysis, read from the working memory at dispatch. */
|
|
138
184
|
readonly memory: StepMemory;
|
|
185
|
+
/** The contract of the template the step renders, with the plan settings bound to it. Absent when none was retrieved. */
|
|
186
|
+
readonly template?: TemplateBrief;
|
|
187
|
+
/**
|
|
188
|
+
* Why the seed carries no template contract for a step whose grounding
|
|
189
|
+
* names one: no client is bound, the service did not answer, or the
|
|
190
|
+
* service does not hold the template. Read only when `template` is absent.
|
|
191
|
+
*/
|
|
192
|
+
readonly templateNotRetrieved?: string;
|
|
139
193
|
}
|
|
140
194
|
/**
|
|
141
195
|
* Compose the seed. Absent sections collapse out entirely — an independent step
|
package/dist/prompts/briefing.js
CHANGED
|
@@ -30,6 +30,10 @@ export const MAX_UPSTREAM_DEPS = 5;
|
|
|
30
30
|
export const UPSTREAM_SUMMARY_MAX_CHARS = 500;
|
|
31
31
|
/** Artifact paths listed per dependency; the rest are reachable from its output directory. */
|
|
32
32
|
export const MAX_UPSTREAM_ARTIFACTS = 8;
|
|
33
|
+
/** Adaptable slots rendered in the Template contract section; the tail is counted, not rendered. */
|
|
34
|
+
export const MAX_TEMPLATE_SLOTS = 24;
|
|
35
|
+
/** Characters of one slot description carried in the seed — the constraint, not the manual. */
|
|
36
|
+
export const TEMPLATE_SLOT_DESCRIPTION_MAX_CHARS = 160;
|
|
33
37
|
// ── Field partition (the coverage guard's two halves) ─────────────────
|
|
34
38
|
/** Fields whose content is agent-facing and MUST appear in the rendered task. */
|
|
35
39
|
export const STEP_TASK_FIELDS = [
|
|
@@ -40,6 +44,9 @@ export const STEP_TASK_FIELDS = [
|
|
|
40
44
|
"constraints",
|
|
41
45
|
"acceptance_criteria",
|
|
42
46
|
"caveats",
|
|
47
|
+
// The step agent reads the template id and the claim identifiers from the
|
|
48
|
+
// step, not from prose: the grounding renders beside the task fields.
|
|
49
|
+
"grounding",
|
|
43
50
|
];
|
|
44
51
|
/**
|
|
45
52
|
* Fields deliberately excluded from the task section: identity/DAG structure
|
|
@@ -70,6 +77,11 @@ export const STEP_NON_TASK_FIELDS = [
|
|
|
70
77
|
// the agent does need to know about them — a dropped entry, or a step that
|
|
71
78
|
// declares none — rides in `caveats`.
|
|
72
79
|
"packages",
|
|
80
|
+
// Withheld for the same reason as `packages`, one layer up: the phrase is how the router and
|
|
81
|
+
// the planner name what they routed on, and the launch has already resolved the reference data
|
|
82
|
+
// it declares. The step agent learns the work from its skill pack, which names each dataset by
|
|
83
|
+
// what it is. Handing it a routing phrase would invite it to treat that phrase as the method.
|
|
84
|
+
"capability",
|
|
73
85
|
];
|
|
74
86
|
// ── Formatting primitives ─────────────────────────────────────────────
|
|
75
87
|
function section(heading, body) {
|
|
@@ -95,7 +107,7 @@ function clamp(text, max) {
|
|
|
95
107
|
* folded in here — otherwise the agent runs against the bare question and
|
|
96
108
|
* improvises the rest.
|
|
97
109
|
*/
|
|
98
|
-
export function renderTask(step) {
|
|
110
|
+
export function renderTask(step, contract = {}) {
|
|
99
111
|
const parts = [`# ${step.name}`, section("Task", step.question)];
|
|
100
112
|
if (step.description?.trim()) {
|
|
101
113
|
parts.push(section("What this step produces", step.description.trim()));
|
|
@@ -112,8 +124,46 @@ export function renderTask(step) {
|
|
|
112
124
|
if (step.caveats && step.caveats.length > 0) {
|
|
113
125
|
parts.push(section("Caveats", bullets(step.caveats)));
|
|
114
126
|
}
|
|
127
|
+
if (step.grounding) {
|
|
128
|
+
parts.push(section("Grounding", renderGrounding(step.grounding, contract)));
|
|
129
|
+
}
|
|
115
130
|
return parts.join("\n\n");
|
|
116
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* The grounding as data lines. The template id is the one value the agent
|
|
134
|
+
* acts on (it names it to `knowledge_template`), thus it renders first after
|
|
135
|
+
* the status. The claim identifiers and the digest are for the record.
|
|
136
|
+
*
|
|
137
|
+
* A grounding that names a template promises a Template contract section.
|
|
138
|
+
* When the seed carries none, one line says so with the reason, thus the
|
|
139
|
+
* agent knows the contract was not fetched rather than guessing at slots.
|
|
140
|
+
*
|
|
141
|
+
* The settings of the procedure ride here as vetted values, not as
|
|
142
|
+
* constraints: the agent uses each one unless the data gives a reason for
|
|
143
|
+
* another value, and then it states the reason. They render only when the
|
|
144
|
+
* seed carries no template contract, because the bound and the unbound
|
|
145
|
+
* lists of the contract hold every setting already.
|
|
146
|
+
*/
|
|
147
|
+
function renderGrounding(grounding, contract) {
|
|
148
|
+
const lines = [`- Status: ${grounding.status}`];
|
|
149
|
+
if (grounding.template) {
|
|
150
|
+
lines.push(`- Template: \`${grounding.template}\``);
|
|
151
|
+
if (!contract.template)
|
|
152
|
+
lines.push(`- Template contract: not retrieved (${contract.templateNotRetrieved ?? "no contract was fetched for this step"})`);
|
|
153
|
+
}
|
|
154
|
+
lines.push(`- Snapshot: ${grounding.snapshot}`);
|
|
155
|
+
lines.push(`- Claims: ${grounding.claims.length > 0 ? grounding.claims.join(", ") : "none"}`);
|
|
156
|
+
lines.push(`- Reason: ${grounding.reason}`);
|
|
157
|
+
const settings = grounding.settings ?? [];
|
|
158
|
+
if (settings.length > 0 && !contract.template) {
|
|
159
|
+
lines.push("- Settings (the vetted values of the procedure; another value is permitted when your summary states the reason):");
|
|
160
|
+
for (const setting of settings) {
|
|
161
|
+
const value = Array.isArray(setting.value) ? setting.value.join(", ") : String(setting.value);
|
|
162
|
+
lines.push(` - ${setting.step}: ${setting.name} = ${value}${setting.source ? ` (${setting.source})` : ""}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return lines.join("\n");
|
|
166
|
+
}
|
|
117
167
|
// ── (1b) Analysis memory ──────────────────────────────────────────────
|
|
118
168
|
/**
|
|
119
169
|
* The heading of the analysis-memory section. The orient core of the sandbox
|
|
@@ -155,6 +205,82 @@ export function renderMemory(memory) {
|
|
|
155
205
|
}
|
|
156
206
|
return section(ANALYSIS_MEMORY_HEADING, parts.join("\n\n"));
|
|
157
207
|
}
|
|
208
|
+
/** A value as the render request carries it: JSON, thus `"apeglm"` and `10` cannot be confused. */
|
|
209
|
+
function slotValue(value) {
|
|
210
|
+
return JSON.stringify(value);
|
|
211
|
+
}
|
|
212
|
+
/** The version of a template reference (`tpl-x@1.0.0` gives `1.0.0`), or `undefined` when the reference has none. */
|
|
213
|
+
function versionOfRef(ref) {
|
|
214
|
+
const at = ref.indexOf("@");
|
|
215
|
+
return at < 0 ? undefined : ref.slice(at + 1);
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* One adaptable slot as a bullet: the name, the type, the default with its
|
|
219
|
+
* source or the required state, the description, and the constraints the
|
|
220
|
+
* render enforces. A slot without a default is required unless the contract
|
|
221
|
+
* says otherwise, which is the rule of the renderer.
|
|
222
|
+
*/
|
|
223
|
+
function renderSlot(slot) {
|
|
224
|
+
const facts = [slot.type];
|
|
225
|
+
// A local slot is bound on this machine: the agent sends it as any other slot, and the value never leaves.
|
|
226
|
+
if (slot.local)
|
|
227
|
+
facts.push("local");
|
|
228
|
+
if (slot.default !== undefined) {
|
|
229
|
+
facts.push(`default ${slotValue(slot.default)}${slot.default_source ? ` [${slot.default_source}]` : ""}`);
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
facts.push(slot.required === false ? "optional" : "required");
|
|
233
|
+
}
|
|
234
|
+
if (slot.minimum !== undefined)
|
|
235
|
+
facts.push(`min ${slot.minimum}`);
|
|
236
|
+
if (slot.maximum !== undefined)
|
|
237
|
+
facts.push(`max ${slot.maximum}`);
|
|
238
|
+
const tail = [];
|
|
239
|
+
if (slot.enum && slot.enum.length > 0)
|
|
240
|
+
tail.push(`Permitted: ${slot.enum.map(slotValue).join(", ")}.`);
|
|
241
|
+
if (slot.pattern)
|
|
242
|
+
tail.push(`Pattern: \`${slot.pattern}\`.`);
|
|
243
|
+
const description = clamp(slot.description.trim(), TEMPLATE_SLOT_DESCRIPTION_MAX_CHARS);
|
|
244
|
+
return `\`${slot.name}\` (${facts.join("; ")})${description ? `: ${description}` : ""}${tail.length > 0 ? ` ${tail.join(" ")}` : ""}`;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* The Template contract section. Only the adaptable slots render, bounded by
|
|
248
|
+
* {@link MAX_TEMPLATE_SLOTS} and the description clamp, so a wide template
|
|
249
|
+
* cannot blow the seed. A served version that differs from the plan renders
|
|
250
|
+
* as a caveat: the render refuses the plan version, and the settings are not
|
|
251
|
+
* bound to a version the plan did not name.
|
|
252
|
+
*/
|
|
253
|
+
export function renderTemplateContract(brief) {
|
|
254
|
+
if (!brief)
|
|
255
|
+
return "";
|
|
256
|
+
const refVersion = versionOfRef(brief.ref);
|
|
257
|
+
const lead = [
|
|
258
|
+
`\`${brief.ref}\` (the service serves version ${brief.version_served}). Send only the slots listed here to \`knowledge_template\`; a slot not listed is pinned or unknown, and the render refuses it.`,
|
|
259
|
+
];
|
|
260
|
+
if (refVersion !== undefined && refVersion !== brief.version_served) {
|
|
261
|
+
lead.push(`Caveat: the plan names version ${refVersion}, and the service serves version ${brief.version_served}. The plan settings are not bound to this render. Render the served version, and state each plan value yourself.`);
|
|
262
|
+
}
|
|
263
|
+
const blocks = [lead.join("\n")];
|
|
264
|
+
const adaptable = brief.slots.filter((slot) => slot.adaptable);
|
|
265
|
+
if (adaptable.length > 0) {
|
|
266
|
+
const shown = adaptable.slice(0, MAX_TEMPLATE_SLOTS);
|
|
267
|
+
const lines = [bullets(shown.map(renderSlot))];
|
|
268
|
+
const omitted = adaptable.length - shown.length;
|
|
269
|
+
if (omitted > 0)
|
|
270
|
+
lines.push(`(+${omitted} more adaptable ${omitted === 1 ? "slot" : "slots"} not listed. The render answer names a required one that is absent.)`);
|
|
271
|
+
blocks.push(`Slots:\n${lines.join("\n")}`);
|
|
272
|
+
}
|
|
273
|
+
if (brief.inputs.length > 0) {
|
|
274
|
+
blocks.push(`Inputs the script reads:\n${bullets(brief.inputs.map((input) => `${input.name}: \`${input.path}\`${input.description ? ` — ${input.description}` : ""}`))}`);
|
|
275
|
+
}
|
|
276
|
+
if (brief.bound.length > 0) {
|
|
277
|
+
blocks.push(`Bound by the plan (send each value as it is, or add an \`overrides\` entry with the slot and the reason for the change; a changed value without an override is refused):\n${bullets(brief.bound.map((setting) => `\`${setting.name}\` = ${slotValue(setting.value)} (${setting.source})`))}`);
|
|
278
|
+
}
|
|
279
|
+
if (brief.unbound_settings.length > 0) {
|
|
280
|
+
blocks.push(`Unbound settings (the plan states them, and no bound slot carries them):\n${bullets(brief.unbound_settings)}`);
|
|
281
|
+
}
|
|
282
|
+
return section("Template contract", blocks.join("\n\n"));
|
|
283
|
+
}
|
|
158
284
|
/** The in-sandbox analysis root of a frame — the read-only mount of the whole tree. */
|
|
159
285
|
export function analysisRootOf(frame) {
|
|
160
286
|
return `/${frame.analysisId}`;
|
|
@@ -240,7 +366,8 @@ function renderHandoff(handoff) {
|
|
|
240
366
|
*/
|
|
241
367
|
export function composeStepBriefing(briefing) {
|
|
242
368
|
return [
|
|
243
|
-
renderTask(briefing.step),
|
|
369
|
+
renderTask(briefing.step, briefing),
|
|
370
|
+
renderTemplateContract(briefing.template),
|
|
244
371
|
renderMemory(briefing.memory),
|
|
245
372
|
renderWorkspace(briefing.workspace),
|
|
246
373
|
renderResources(briefing.step.resources),
|
package/dist/prompts/planner.js
CHANGED
|
@@ -120,7 +120,8 @@ leaves open:
|
|
|
120
120
|
- An inventory block that reports a failed lookup. Absence in it proves
|
|
121
121
|
nothing, so read that inventory through its tool.
|
|
122
122
|
- A reference census that says it is cut short, where a step needs a
|
|
123
|
-
collection it does not show.
|
|
123
|
+
collection it does not show. A bounded listing is not evidence of absence,
|
|
124
|
+
so confirm through the tool before you call that data missing.
|
|
124
125
|
- A package whose exact spelling or language track the census leaves unclear.
|
|
125
126
|
|
|
126
127
|
Batch the lookups. \`names\` takes every package you are unsure of in one call,
|
|
@@ -212,10 +213,18 @@ ${agentCatalog}
|
|
|
212
213
|
### Routing Rules
|
|
213
214
|
- Route by primary data object: if the step works on a count matrix, assign
|
|
214
215
|
the transcriptomics agent; if on a gene list, assign the enrichment agent.
|
|
216
|
+
- A gene list does not settle the assignment by itself. Read what the step
|
|
217
|
+
asks of the list. A step that builds, labels, or benchmarks a gene set for a
|
|
218
|
+
safety or tissue-risk question goes to the specialist whose capabilities name
|
|
219
|
+
that work, not to the enrichment agent.
|
|
215
220
|
- When a step could go to either a modality or cross-cutting agent, prefer
|
|
216
221
|
cross-cutting if the input is already in generic format (CSV/TSV). Prefer
|
|
217
222
|
modality if it needs modality-specific objects (AnnData, SummarizedExperiment).
|
|
218
223
|
- If no agent is an exact fit, pick the closest specialist from the list.
|
|
224
|
+
- Name the capability you routed on. Copy one phrase out of that agent's
|
|
225
|
+
capabilities into the step's \`capability\` field, spelled exactly as the
|
|
226
|
+
catalog spells it. The launch resolves what reference data that capability
|
|
227
|
+
needs before the run starts, so a paraphrase costs the step that check.
|
|
219
228
|
|
|
220
229
|
## Grounding
|
|
221
230
|
|
|
@@ -287,7 +296,9 @@ safety, toxicity, or treatment outcomes:
|
|
|
287
296
|
5. **Route safety data correctly.** If the data includes safety
|
|
288
297
|
biomarkers (ALT, AST, troponin, creatinine, hERG), adverse event
|
|
289
298
|
records, or toxicity classifications, route to the
|
|
290
|
-
translational-safety-agent.
|
|
299
|
+
translational-safety-agent. Route a request for matched negative
|
|
300
|
+
controls, or for a tissue-risk benchmark set, to the same agent even
|
|
301
|
+
when the only input is a gene list.
|
|
291
302
|
|
|
292
303
|
6. **Route immune profiling correctly.** If the research question
|
|
293
304
|
involves immune cell composition, tumor microenvironment
|
|
@@ -8,14 +8,16 @@
|
|
|
8
8
|
*
|
|
9
9
|
* One surface carries the three, and each member is optional alone. Thus a composition binds the members
|
|
10
10
|
* that it records, and the harness reads an absent member as absence and never as an error. The run emit
|
|
11
|
-
* takes
|
|
12
|
-
*
|
|
11
|
+
* takes an `AgentSession`, because a persisting realization needs the credential of the actor. The
|
|
12
|
+
* workflow body passes the `RunSession` of its durable input, and the run canceler passes the session of
|
|
13
|
+
* the cancel. A session act carries no session, thus the session emit takes the event alone.
|
|
13
14
|
*
|
|
14
|
-
* Both emit members are fire-and-forget. Neither gives a result, and the harness reads none.
|
|
15
|
-
*
|
|
16
|
-
*
|
|
15
|
+
* Both emit members are fire-and-forget. Neither gives a result, and the harness reads none. The writer
|
|
16
|
+
* that wins the conditional terminal write of the run row emits `run_completed`. The body emits it before
|
|
17
|
+
* its status write commits, or after a refused or failed write. The canceler emits it only when its write
|
|
18
|
+
* changes the row, thus a refused cancel emits nothing.
|
|
17
19
|
*/
|
|
18
|
-
import type {
|
|
20
|
+
import type { AgentSession } from "../auth/types.js";
|
|
19
21
|
import type { Logger } from "../lib/logger.js";
|
|
20
22
|
import type { ProvenanceExport } from "../report-render/provenance-data.js";
|
|
21
23
|
import type { AuthoringBlock } from "../report-model/authoring-grammar.js";
|
|
@@ -26,14 +28,19 @@ export type { ProvenanceExport } from "../report-render/provenance-data.js";
|
|
|
26
28
|
* Harness-owned plain union — the harness stays tsprov-free and bus-free; the
|
|
27
29
|
* host maps these execution facts onto its own ledger vocabulary.
|
|
28
30
|
*
|
|
29
|
-
*
|
|
30
|
-
* step: a body re-executed by DBOS recovery reads the recorded value, so a
|
|
31
|
+
* In the workflow body, every `atMs` is epoch milliseconds read via `await DBOS.now()`, a
|
|
32
|
+
* checkpointed step: a body re-executed by DBOS recovery reads the recorded value, so a
|
|
31
33
|
* re-emitted event carries the identical timestamp and merges on the host's
|
|
32
|
-
* ledger without a value conflict.
|
|
34
|
+
* ledger without a value conflict. The body never sources these from a wall clock
|
|
33
35
|
* (`Date.now()`) — that would diverge across replays and defeat the merge.
|
|
34
36
|
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
+
* The run canceler is the second emit site of `step_completed` and `run_completed`, and it emits only
|
|
38
|
+
* when its write of the run row changes the row. It is not a workflow, thus no replay fires its emits,
|
|
39
|
+
* and it reads the wall clock one time for all of them.
|
|
40
|
+
*
|
|
41
|
+
* In the body, `run_completed.durationMs` is the terminal `atMs` minus the `run_started`
|
|
42
|
+
* `atMs` (both `DBOS.now()` reads) — the true workflow-observed run span. The canceler holds no
|
|
43
|
+
* `run_started` time, thus it measures from the `started_at` of the run row.
|
|
37
44
|
*
|
|
38
45
|
* `step_completed` fires once at EVERY scheduler-loop settlement — the only site
|
|
39
46
|
* that observes every executed step (registration sees only artifact-producing
|
|
@@ -192,11 +199,12 @@ export type ProvenanceSeam = {
|
|
|
192
199
|
* The run engine emits the facts of one run here.
|
|
193
200
|
*
|
|
194
201
|
* Synchronous by signature (`void`, never a promise): a host that needs input and output dispatches
|
|
195
|
-
* the work rather than making a workflow body wait behind it. The
|
|
196
|
-
*
|
|
197
|
-
*
|
|
202
|
+
* the work rather than making a workflow body wait behind it. The session rides beside the event,
|
|
203
|
+
* because a persisting realization needs the credential, the scope, and the identity of the actor.
|
|
204
|
+
* The body passes the `RunSession` of its durable workflow input. The run canceler passes the
|
|
205
|
+
* session of the cancel, because it holds no `RunSession`. A realization can ignore the parameter.
|
|
198
206
|
*/
|
|
199
|
-
readonly emitRunEvent?: (event: RunProvenanceEvent, session:
|
|
207
|
+
readonly emitRunEvent?: (event: RunProvenanceEvent, session: AgentSession) => void;
|
|
200
208
|
/**
|
|
201
209
|
* A session emits one act here. A session act runs under no authorized run, thus this member takes
|
|
202
210
|
* the event alone. It is synchronous by signature for the same reason as the run emit.
|
package/dist/provenance/seam.js
CHANGED
|
@@ -8,12 +8,14 @@
|
|
|
8
8
|
*
|
|
9
9
|
* One surface carries the three, and each member is optional alone. Thus a composition binds the members
|
|
10
10
|
* that it records, and the harness reads an absent member as absence and never as an error. The run emit
|
|
11
|
-
* takes
|
|
12
|
-
*
|
|
11
|
+
* takes an `AgentSession`, because a persisting realization needs the credential of the actor. The
|
|
12
|
+
* workflow body passes the `RunSession` of its durable input, and the run canceler passes the session of
|
|
13
|
+
* the cancel. A session act carries no session, thus the session emit takes the event alone.
|
|
13
14
|
*
|
|
14
|
-
* Both emit members are fire-and-forget. Neither gives a result, and the harness reads none.
|
|
15
|
-
*
|
|
16
|
-
*
|
|
15
|
+
* Both emit members are fire-and-forget. Neither gives a result, and the harness reads none. The writer
|
|
16
|
+
* that wins the conditional terminal write of the run row emits `run_completed`. The body emits it before
|
|
17
|
+
* its status write commits, or after a refused or failed write. The canceler emits it only when its write
|
|
18
|
+
* changes the row, thus a refused cancel emits nothing.
|
|
17
19
|
*/
|
|
18
20
|
/**
|
|
19
21
|
* Bind the optional session emit and the logger of one site into a total emit.
|
package/dist/providers/ai-sdk.js
CHANGED
|
@@ -97,6 +97,46 @@ function thinkingWithBinding(reasoning, mode) {
|
|
|
97
97
|
return { blockBinding };
|
|
98
98
|
return { type: "adaptive", display: "summarized", blockBinding };
|
|
99
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* The model that answered after a server-side fallback hop, `""` when the hop is
|
|
102
|
+
* reported without one, and `undefined` when no hop happened.
|
|
103
|
+
*
|
|
104
|
+
* A model can refuse a request by category — a biology prompt is one such category —
|
|
105
|
+
* and the API then re-serves it on another model. It reports the hop TWICE: as a
|
|
106
|
+
* `fallback` content block inside the assistant turn, and on
|
|
107
|
+
* `providerMetadata.anthropic.iterations`. `@ai-sdk/anthropic` keeps only the second,
|
|
108
|
+
* because the AI SDK has no part for a model hop, and it drops the block from the
|
|
109
|
+
* content it returns (`anthropic-messages-language-model`, the `content_block_start`
|
|
110
|
+
* case). Thus the turn the harness holds is not the turn the API produced, and every
|
|
111
|
+
* signed thinking block of that turn is rejected from the next request on. This reader
|
|
112
|
+
* is what lets {@link settleFallbackReply} see the hop the content no longer shows.
|
|
113
|
+
*/
|
|
114
|
+
function fallbackModelOf(metadata) {
|
|
115
|
+
const entries = metadata?.["anthropic"]?.["iterations"];
|
|
116
|
+
if (!Array.isArray(entries))
|
|
117
|
+
return undefined;
|
|
118
|
+
for (const entry of entries) {
|
|
119
|
+
if (typeof entry !== "object" || entry === null)
|
|
120
|
+
continue;
|
|
121
|
+
const fields = entry;
|
|
122
|
+
if (fields["type"] !== "fallback_message")
|
|
123
|
+
continue;
|
|
124
|
+
return typeof fields["model"] === "string" ? fields["model"] : "";
|
|
125
|
+
}
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* The assistant message with no thinking block, and with `""` for content when it held
|
|
130
|
+
* nothing else. An empty message reaches no wire: `sanitizeMessages` drops it.
|
|
131
|
+
*/
|
|
132
|
+
function withoutThinking(message) {
|
|
133
|
+
if (typeof message.content === "string")
|
|
134
|
+
return message;
|
|
135
|
+
const content = message.content.filter((part) => part.type !== "reasoning" && part.type !== "reasoning-file");
|
|
136
|
+
if (content.length === message.content.length)
|
|
137
|
+
return message;
|
|
138
|
+
return content.length === 0 ? { ...message, content: "" } : { ...message, content };
|
|
139
|
+
}
|
|
100
140
|
/** Reads `providerMetadata.anthropic.inputTransformations`; empty for another vendor. */
|
|
101
141
|
function inputTransformationsOf(metadata) {
|
|
102
142
|
const entries = metadata?.["anthropic"]?.["inputTransformations"];
|
|
@@ -476,6 +516,7 @@ export function createAiSdkProvider(deps) {
|
|
|
476
516
|
// wire that carries no picture there.
|
|
477
517
|
...(deps.capabilities?.imageToolResults !== undefined ? { imageToolResults: deps.capabilities.imageToolResults } : {}),
|
|
478
518
|
...(deps.capabilities?.imageUserMessages !== undefined ? { imageUserMessages: deps.capabilities.imageUserMessages } : {}),
|
|
519
|
+
...(deps.capabilities?.contextWindow !== undefined ? { contextWindow: deps.capabilities.contextWindow } : {}),
|
|
479
520
|
};
|
|
480
521
|
const logger = (deps.logger ?? createNoopLogger()).named("providers.ai-sdk");
|
|
481
522
|
const maxOutputTokens = deps.maxOutputTokens ?? DEFAULT_MAX_OUTPUT_TOKENS;
|
|
@@ -524,6 +565,44 @@ export function createAiSdkProvider(deps) {
|
|
|
524
565
|
logger.warn("thinking block dropped", { workload: workloadOf(session), type: drop.type, path: drop.path, reason: drop.reason });
|
|
525
566
|
}
|
|
526
567
|
}
|
|
568
|
+
/**
|
|
569
|
+
* The reply as the harness must hold it when a fallback model served it: with no
|
|
570
|
+
* thinking block, and with the model that answered named.
|
|
571
|
+
*
|
|
572
|
+
* A reply that carries a fallback hop ({@link fallbackModelOf}) reaches the harness
|
|
573
|
+
* already short of the `fallback` content block the API put in the turn. The API holds
|
|
574
|
+
* the whole turn against the signature of each of its thinking blocks, so echoing that
|
|
575
|
+
* turn earns `thinking ... blocks in the latest assistant message cannot be modified`,
|
|
576
|
+
* which is a 400 that no retry can pass. The conversation is then dead: the loop sends
|
|
577
|
+
* its history on every request, and nothing downstream can repair a turn.
|
|
578
|
+
*
|
|
579
|
+
* A thinking block is optional on the way back, thus the reply without them is a valid
|
|
580
|
+
* turn, and the next request continues. The cost is the reasoning of this one turn and
|
|
581
|
+
* the cache write that its new prefix pays for. A reply of nothing but thinking carries
|
|
582
|
+
* no part at all, and `sanitizeMessages` drops such a message from the next request.
|
|
583
|
+
*
|
|
584
|
+
* `servedModelId` needs the same correction. {@link captureServedModelId} reads the id
|
|
585
|
+
* the response metadata of the package carries, which is `message_start.message.model`,
|
|
586
|
+
* and that field keeps the REQUESTED model across a hop. Published as it stands, it
|
|
587
|
+
* makes the usage ledger of an embedder attribute the tokens to the model that refused
|
|
588
|
+
* the work. The iteration names the model that answered, thus it wins. An iteration that
|
|
589
|
+
* names no model changes nothing: there is no better id to publish, and the record above
|
|
590
|
+
* is then the only account of the hop.
|
|
591
|
+
*/
|
|
592
|
+
function settleFallbackReply(session, response, metadata) {
|
|
593
|
+
const servedBy = fallbackModelOf(metadata);
|
|
594
|
+
if (servedBy === undefined)
|
|
595
|
+
return response;
|
|
596
|
+
logger.warn("reply served by a fallback model, thus the harness drops the thinking of that turn", {
|
|
597
|
+
workload: workloadOf(session),
|
|
598
|
+
...(servedBy === "" ? {} : { servedBy }),
|
|
599
|
+
});
|
|
600
|
+
return {
|
|
601
|
+
...response,
|
|
602
|
+
message: withoutThinking(response.message),
|
|
603
|
+
...(servedBy === "" ? {} : { servedModelId: servedBy }),
|
|
604
|
+
};
|
|
605
|
+
}
|
|
527
606
|
function chat(req, session, signal) {
|
|
528
607
|
const reasoning = effortOf(req);
|
|
529
608
|
const providerOptions = deps.providerOptionsFor?.({ session, reasoning });
|
|
@@ -602,14 +681,14 @@ export function createAiSdkProvider(deps) {
|
|
|
602
681
|
// reports its drops.
|
|
603
682
|
const { providerMetadata: metadata, ...reply } = collected;
|
|
604
683
|
logDroppedThinkingBlocks(session, metadata);
|
|
605
|
-
return ok(responseFromMessages({
|
|
684
|
+
return ok(settleFallbackReply(session, responseFromMessages({
|
|
606
685
|
...reply,
|
|
607
686
|
requestedModelId,
|
|
608
687
|
// Read after the drain: the metadata chunk that carries the
|
|
609
688
|
// served id reaches the capture only as the stream is consumed.
|
|
610
689
|
servedModelId: capture.servedModelId(),
|
|
611
690
|
provider: providerId,
|
|
612
|
-
}));
|
|
691
|
+
}), metadata));
|
|
613
692
|
}
|
|
614
693
|
catch (e) {
|
|
615
694
|
if (isAbortError(e) || signal?.aborted || hookCall.unsettled)
|
|
@@ -692,7 +771,7 @@ export function createAiSdkProvider(deps) {
|
|
|
692
771
|
});
|
|
693
772
|
if (opened.kind === "completed") {
|
|
694
773
|
logDroppedThinkingBlocks(session, opened.providerMetadata);
|
|
695
|
-
const response = responseFromMessages({
|
|
774
|
+
const response = settleFallbackReply(session, responseFromMessages({
|
|
696
775
|
messages: opened.messages,
|
|
697
776
|
fallbackText: "",
|
|
698
777
|
finishReason: opened.finishReason,
|
|
@@ -701,7 +780,7 @@ export function createAiSdkProvider(deps) {
|
|
|
701
780
|
requestedModelId,
|
|
702
781
|
servedModelId: capture.servedModelId(),
|
|
703
782
|
provider: providerId,
|
|
704
|
-
});
|
|
783
|
+
}), opened.providerMetadata);
|
|
705
784
|
yield { type: "done", response };
|
|
706
785
|
return;
|
|
707
786
|
}
|
|
@@ -721,7 +800,8 @@ export function createAiSdkProvider(deps) {
|
|
|
721
800
|
text += pull.text;
|
|
722
801
|
yield { type: "text-delta", text: pull.text };
|
|
723
802
|
}
|
|
724
|
-
const
|
|
803
|
+
const metadata = (await result.finalStep).providerMetadata;
|
|
804
|
+
const response = settleFallbackReply(session, responseFromMessages({
|
|
725
805
|
messages: await result.responseMessages,
|
|
726
806
|
fallbackText: text,
|
|
727
807
|
finishReason: await result.finishReason,
|
|
@@ -732,8 +812,8 @@ export function createAiSdkProvider(deps) {
|
|
|
732
812
|
// reaches the capture only as the stream is consumed.
|
|
733
813
|
servedModelId: capture.servedModelId(),
|
|
734
814
|
provider: providerId,
|
|
735
|
-
});
|
|
736
|
-
logDroppedThinkingBlocks(session,
|
|
815
|
+
}), metadata);
|
|
816
|
+
logDroppedThinkingBlocks(session, metadata);
|
|
737
817
|
yield { type: "done", response };
|
|
738
818
|
}
|
|
739
819
|
catch (e) {
|
|
@@ -31,6 +31,12 @@ export interface ProviderCapabilities {
|
|
|
31
31
|
* "unknown".
|
|
32
32
|
*/
|
|
33
33
|
readonly imageUserMessages?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* The context window of the model in tokens, when the host knows it. A chat
|
|
36
|
+
* thread then compacts at 80% of the window at the latest. Absent means "at
|
|
37
|
+
* least the compaction budget of the agent", thus the budget stays as declared.
|
|
38
|
+
*/
|
|
39
|
+
readonly contextWindow?: number;
|
|
34
40
|
}
|
|
35
41
|
/**
|
|
36
42
|
* The request carries no free-form provider options. That would let a caller
|