@deepstrike/sdk 0.2.16 → 0.2.18
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/harness/harness.js +10 -15
- package/dist/index.d.ts +5 -1
- package/dist/index.js +4 -1
- package/dist/kernel.d.ts +8 -15
- package/dist/providers/anthropic.d.ts +2 -2
- package/dist/providers/anthropic.js +7 -5
- package/dist/providers/openai.d.ts +2 -2
- package/dist/providers/openai.js +3 -2
- package/dist/runtime/execution-plane.d.ts +5 -0
- package/dist/runtime/execution-plane.js +3 -1
- package/dist/runtime/process-sandbox-plane.js +14 -8
- package/dist/runtime/runner.d.ts +69 -0
- package/dist/runtime/runner.js +261 -35
- package/dist/runtime/sub-agent-orchestrator.d.ts +11 -0
- package/dist/runtime/sub-agent-orchestrator.js +78 -13
- package/dist/runtime/workflow-control-flow.d.ts +17 -0
- package/dist/runtime/workflow-control-flow.js +78 -0
- package/dist/runtime/workflow-store.d.ts +15 -0
- package/dist/runtime/workflow-store.js +47 -0
- package/dist/runtime/worktree-plane.d.ts +43 -0
- package/dist/runtime/worktree-plane.js +81 -0
- package/dist/tools/index.d.ts +9 -3
- package/dist/tools/index.js +2 -2
- package/dist/types/agent.d.ts +70 -0
- package/dist/types/agent.js +213 -44
- package/dist/types.d.ts +6 -1
- package/package.json +2 -2
package/dist/types/agent.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getKernel } from "../kernel.js";
|
|
1
2
|
/** Map kernel spawn observation → host manifest. */
|
|
2
3
|
export function spawnObservationToManifest(obs, spec, parentSessionId) {
|
|
3
4
|
const o = obs;
|
|
@@ -85,6 +86,11 @@ export function subAgentResultToKernel(result) {
|
|
|
85
86
|
: null,
|
|
86
87
|
turns_used: result.result.turnsUsed,
|
|
87
88
|
total_tokens_used: result.result.totalTokensUsed,
|
|
89
|
+
// A#2: control-flow signals — additive, omitted on the wire when unset so a plain spawn's
|
|
90
|
+
// result is byte-identical to before. The kernel reads each only for the matching node kind.
|
|
91
|
+
...(result.result.loopContinue !== undefined ? { loop_continue: result.result.loopContinue } : {}),
|
|
92
|
+
...(result.result.classifyBranch !== undefined ? { classify_branch: result.result.classifyBranch } : {}),
|
|
93
|
+
...(result.result.tournamentWinner !== undefined ? { tournament_winner: result.result.tournamentWinner } : {}),
|
|
88
94
|
},
|
|
89
95
|
};
|
|
90
96
|
}
|
|
@@ -107,22 +113,49 @@ export function workflowBudgetNote(budget) {
|
|
|
107
113
|
if (budget.concurrency_remaining != null && budget.max_concurrent_subagents != null) {
|
|
108
114
|
parts.push(`concurrency ${budget.running_subagents}/${budget.max_concurrent_subagents} running, ${budget.concurrency_remaining} free`);
|
|
109
115
|
}
|
|
116
|
+
if (budget.tokens_remaining != null && budget.tokens_max != null) {
|
|
117
|
+
parts.push(`tokens ${budget.tokens_used ?? 0}/${budget.tokens_max} used, ${budget.tokens_remaining} remaining`);
|
|
118
|
+
}
|
|
110
119
|
if (parts.length === 0)
|
|
111
120
|
return "";
|
|
112
121
|
return (`[workflow budget] ${parts.join(" · ")}. ` +
|
|
113
|
-
"If you submit more workflow nodes, keep the batch within the remaining node budget.");
|
|
122
|
+
"If you submit more workflow nodes, keep the batch within the remaining node and token budget.");
|
|
123
|
+
}
|
|
124
|
+
/** Normalize a `WorkflowTaskSpec` (object or bare goal string) to the kernel's `RuntimeTask` JSON. */
|
|
125
|
+
function workflowTaskToKernel(t) {
|
|
126
|
+
const task = typeof t === "string" ? { goal: t } : t;
|
|
127
|
+
return {
|
|
128
|
+
goal: task.goal,
|
|
129
|
+
// `criteria` is required by the kernel's RuntimeTask serde (no default).
|
|
130
|
+
criteria: task.criteria ?? [],
|
|
131
|
+
...(task.lane ? { lane: task.lane } : {}),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/** Lower a node's control-flow kind to the kernel's serde-tagged `NodeKind` JSON, or `undefined` for
|
|
135
|
+
* a plain spawn. `reducer` / `loop` / `classify` / `tournament` are mutually exclusive — declaring
|
|
136
|
+
* more than one is a spec error (a node has exactly one kind). */
|
|
137
|
+
function nodeKindToKernel(n) {
|
|
138
|
+
const declared = [n.reducer != null, n.loop != null, n.classify != null, n.tournament != null].filter(Boolean).length;
|
|
139
|
+
if (declared > 1) {
|
|
140
|
+
throw new Error("a workflow node may declare at most one of: reducer, loop, classify, tournament");
|
|
141
|
+
}
|
|
142
|
+
if (n.reducer != null)
|
|
143
|
+
return { type: "reduce", reducer: n.reducer };
|
|
144
|
+
if (n.loop != null)
|
|
145
|
+
return { type: "loop", max_iters: n.loop.maxIters };
|
|
146
|
+
if (n.classify != null) {
|
|
147
|
+
return { type: "classify", branches: n.classify.branches.map(b => ({ label: b.label, nodes: b.nodes })) };
|
|
148
|
+
}
|
|
149
|
+
if (n.tournament != null)
|
|
150
|
+
return { type: "tournament", entrants: n.tournament.entrants.map(workflowTaskToKernel) };
|
|
151
|
+
return undefined;
|
|
114
152
|
}
|
|
115
153
|
/** Map one host `WorkflowNodeSpec` to its snake_case kernel JSON. Shared by `load_workflow` (the
|
|
116
154
|
* whole spec) and `submit_workflow_nodes` (R3-1 runtime append) so the two encodings never drift. */
|
|
117
155
|
export function workflowNodeSpecToKernel(n) {
|
|
118
|
-
const
|
|
156
|
+
const kind = nodeKindToKernel(n);
|
|
119
157
|
return {
|
|
120
|
-
task:
|
|
121
|
-
goal: task.goal,
|
|
122
|
-
// `criteria` is required by the kernel's RuntimeTask serde (no default).
|
|
123
|
-
criteria: task.criteria ?? [],
|
|
124
|
-
...(task.lane ? { lane: task.lane } : {}),
|
|
125
|
-
},
|
|
158
|
+
task: workflowTaskToKernel(n.task),
|
|
126
159
|
role: n.role,
|
|
127
160
|
// role/isolation/context_inheritance have no serde default in the kernel — always emit.
|
|
128
161
|
isolation: n.isolation ?? "shared",
|
|
@@ -130,8 +163,10 @@ export function workflowNodeSpecToKernel(n) {
|
|
|
130
163
|
...(n.modelHint ? { model_hint: n.modelHint } : {}),
|
|
131
164
|
...(n.trust && n.trust !== "trusted" ? { trust: n.trust } : {}),
|
|
132
165
|
...(n.outputSchema ? { output_schema: n.outputSchema } : {}),
|
|
133
|
-
// G2:
|
|
134
|
-
...(
|
|
166
|
+
// A#2/G2: loop / classify / tournament / reduce lower to a serde-tagged `NodeKind`; spawn omits it.
|
|
167
|
+
...(kind ? { kind } : {}),
|
|
168
|
+
// M4/G5: per-node token cap (additive; omitted when unset).
|
|
169
|
+
...(n.tokenBudget != null ? { token_budget: n.tokenBudget } : {}),
|
|
135
170
|
...(n.dependsOn && n.dependsOn.length ? { depends_on: n.dependsOn } : {}),
|
|
136
171
|
};
|
|
137
172
|
}
|
|
@@ -149,24 +184,94 @@ export function submitWorkflowNodesToKernel(nodes, submitterAgentId) {
|
|
|
149
184
|
...(submitterAgentId ? { submitter_agent_id: submitterAgentId } : {}),
|
|
150
185
|
};
|
|
151
186
|
}
|
|
152
|
-
/**
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
187
|
+
/** M5/G1: map an agent-authored spec to the `submit_workflow` kernel event body (the agent-reachable
|
|
188
|
+
* `Syscall::LoadWorkflow`). The kernel bootstraps the DAG when none is active, else flattens onto it.
|
|
189
|
+
* `parentSessionId` seeds child session ids on bootstrap; `submitterAgentId` carries G1 trust coercion
|
|
190
|
+
* on the flatten case (a quarantined author's nodes are coerced quarantined). */
|
|
191
|
+
export function submitWorkflowToKernel(spec, parentSessionId, submitterAgentId) {
|
|
192
|
+
return {
|
|
193
|
+
kind: "submit_workflow",
|
|
194
|
+
spec: workflowSpecToKernel(spec),
|
|
195
|
+
parent_session_id: parentSessionId,
|
|
196
|
+
...(submitterAgentId ? { submitter_agent_id: submitterAgentId } : {}),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
/** Shared JSON-Schema for a workflow-node batch (a DAG). Used by both `submit_workflow_nodes`
|
|
200
|
+
* (append) and `start_workflow` (M5 v1: author a sub-workflow), so the two tools never drift. */
|
|
201
|
+
const workflowNodesArraySchema = {
|
|
202
|
+
type: "array",
|
|
203
|
+
description: "Workflow nodes (a DAG); each runs as a gated sub-agent. A node may declare ONE control-flow kind " +
|
|
204
|
+
"— `loop` / `classify` / `tournament` / `reducer` — otherwise it is a plain spawn. `dependsOn` and " +
|
|
205
|
+
"`classify.branches[].nodes` are batch-relative (index 0 = this batch's first node).",
|
|
206
|
+
items: {
|
|
160
207
|
type: "object",
|
|
161
208
|
properties: {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
209
|
+
task: {
|
|
210
|
+
description: "The node's goal: a string, or an object { goal, criteria?, lane? }.",
|
|
211
|
+
oneOf: [
|
|
212
|
+
{ type: "string" },
|
|
213
|
+
{
|
|
214
|
+
type: "object",
|
|
215
|
+
properties: {
|
|
216
|
+
goal: { type: "string" },
|
|
217
|
+
criteria: { type: "array", items: { type: "string" } },
|
|
218
|
+
},
|
|
219
|
+
required: ["goal"],
|
|
220
|
+
},
|
|
221
|
+
],
|
|
222
|
+
},
|
|
223
|
+
role: { type: "string", enum: ["explore", "plan", "implement", "verify", "custom"] },
|
|
224
|
+
isolation: { type: "string", enum: ["shared", "read_only", "worktree", "remote"] },
|
|
225
|
+
contextInheritance: { type: "string", enum: ["none", "system_only", "full"] },
|
|
226
|
+
trust: { type: "string", enum: ["trusted", "quarantined"] },
|
|
227
|
+
outputSchema: {
|
|
228
|
+
type: "object",
|
|
229
|
+
description: "Optional JSON Schema the node's output must conform to (validated + retried SDK-side).",
|
|
230
|
+
},
|
|
231
|
+
modelHint: {
|
|
232
|
+
type: "string",
|
|
233
|
+
description: "Preferred model for this node (e.g. \"opus\"/\"sonnet\"/\"haiku\"); the host routes it.",
|
|
234
|
+
},
|
|
235
|
+
reducer: {
|
|
236
|
+
type: "string",
|
|
237
|
+
description: "Make this a deterministic reduce node (no LLM); names a registered reducer.",
|
|
238
|
+
},
|
|
239
|
+
loop: {
|
|
240
|
+
type: "object",
|
|
241
|
+
description: "Make this a loop node: re-run its agent up to maxIters times, ending early when it reports done.",
|
|
242
|
+
properties: { maxIters: { type: "integer", description: "Hard iteration cap." } },
|
|
243
|
+
required: ["maxIters"],
|
|
244
|
+
},
|
|
245
|
+
classify: {
|
|
246
|
+
type: "object",
|
|
247
|
+
description: "Make this a classify node: its agent picks one branch label; that branch's nodes run, the rest are pruned.",
|
|
248
|
+
properties: {
|
|
249
|
+
branches: {
|
|
250
|
+
type: "array",
|
|
251
|
+
items: {
|
|
252
|
+
type: "object",
|
|
253
|
+
properties: {
|
|
254
|
+
label: { type: "string" },
|
|
255
|
+
nodes: {
|
|
256
|
+
type: "array",
|
|
257
|
+
items: { type: "integer" },
|
|
258
|
+
description: "Batch-relative indices of the nodes to run when this branch is chosen.",
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
required: ["label", "nodes"],
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
required: ["branches"],
|
|
266
|
+
},
|
|
267
|
+
tournament: {
|
|
268
|
+
type: "object",
|
|
269
|
+
description: "Make this a tournament controller: generate each entrant, then pairwise-judge to one winner (this node's task is the criterion).",
|
|
270
|
+
properties: {
|
|
271
|
+
entrants: {
|
|
272
|
+
type: "array",
|
|
273
|
+
description: "≥2 candidate tasks to generate and judge.",
|
|
274
|
+
items: {
|
|
170
275
|
oneOf: [
|
|
171
276
|
{ type: "string" },
|
|
172
277
|
{
|
|
@@ -179,31 +284,63 @@ export const submitWorkflowNodesTool = {
|
|
|
179
284
|
},
|
|
180
285
|
],
|
|
181
286
|
},
|
|
182
|
-
role: { type: "string", enum: ["explore", "plan", "implement", "verify", "custom"] },
|
|
183
|
-
isolation: { type: "string", enum: ["shared", "read_only", "worktree", "remote"] },
|
|
184
|
-
contextInheritance: { type: "string", enum: ["none", "system_only", "full"] },
|
|
185
|
-
trust: { type: "string", enum: ["trusted", "quarantined"] },
|
|
186
|
-
outputSchema: {
|
|
187
|
-
type: "object",
|
|
188
|
-
description: "Optional JSON Schema the node's output must conform to (validated + retried SDK-side).",
|
|
189
|
-
},
|
|
190
|
-
reducer: {
|
|
191
|
-
type: "string",
|
|
192
|
-
description: "Make this a deterministic reduce node (no LLM); names a registered reducer.",
|
|
193
|
-
},
|
|
194
|
-
dependsOn: {
|
|
195
|
-
type: "array",
|
|
196
|
-
items: { type: "integer" },
|
|
197
|
-
description: "Batch-relative, backward-only dependency indices within this submission.",
|
|
198
|
-
},
|
|
199
287
|
},
|
|
200
|
-
required: ["task", "role"],
|
|
201
288
|
},
|
|
289
|
+
required: ["entrants"],
|
|
290
|
+
},
|
|
291
|
+
tokenBudget: {
|
|
292
|
+
type: "integer",
|
|
293
|
+
description: "Cap this node's child run at this many cumulative tokens.",
|
|
294
|
+
},
|
|
295
|
+
dependsOn: {
|
|
296
|
+
type: "array",
|
|
297
|
+
items: { type: "integer" },
|
|
298
|
+
description: "Batch-relative, backward-only dependency indices within this submission.",
|
|
202
299
|
},
|
|
203
300
|
},
|
|
301
|
+
required: ["task", "role"],
|
|
302
|
+
},
|
|
303
|
+
};
|
|
304
|
+
/** R3-1: the tool a workflow-coordinator node's agent calls to append work to the running DAG
|
|
305
|
+
* (true loop-until-done / dynamic fan-out). Give it to nodes meant to fan out; the runner intercepts
|
|
306
|
+
* the call and routes the nodes to the parent kernel (the child's own kernel holds no workflow). */
|
|
307
|
+
export const submitWorkflowNodesTool = {
|
|
308
|
+
name: "submit_workflow_nodes",
|
|
309
|
+
description: "Append new nodes to the running workflow DAG (dynamic fan-out / loop-until-done). Each node " +
|
|
310
|
+
"spawns as a gated sub-agent. Use when you discover more work that should run as its own node. " +
|
|
311
|
+
"A node may declare ONE control-flow kind — `loop` (re-run until done), `classify` (route to one " +
|
|
312
|
+
"branch), `tournament` (pairwise-judge candidates to a winner), or `reducer` (deterministic, no " +
|
|
313
|
+
"LLM) — otherwise it is a plain spawn. Within a submission, `dependsOn` and `classify.branches[].nodes` " +
|
|
314
|
+
"are batch-relative (index 0 = this batch's first node).",
|
|
315
|
+
parameters: JSON.stringify({
|
|
316
|
+
type: "object",
|
|
317
|
+
properties: { nodes: workflowNodesArraySchema },
|
|
204
318
|
required: ["nodes"],
|
|
205
319
|
}),
|
|
206
320
|
};
|
|
321
|
+
/** M5 v1 (flatten): the tool an agent calls to **author a sub-workflow** — a cohesive DAG of nodes
|
|
322
|
+
* (incl. loop/classify/tournament/reduce) composed onto the running workflow. Mechanically it lowers
|
|
323
|
+
* to the same append path as `submit_workflow_nodes` (a `WorkflowSpec` is a node batch), but reads as
|
|
324
|
+
* "write a harness" rather than "append nodes". v2 adds top-level bootstrap (the `LoadWorkflow`
|
|
325
|
+
* kernel syscall) so a plain run can start a workflow from scratch. */
|
|
326
|
+
export const startWorkflowTool = {
|
|
327
|
+
name: "start_workflow",
|
|
328
|
+
description: "Author and run a sub-workflow: a DAG of nodes (fan-out / classify / tournament / loop / reduce) " +
|
|
329
|
+
"composed onto the current run. Use to structure a multi-step task as its own harness. The nodes " +
|
|
330
|
+
"spawn as gated sub-agents; `dependsOn` / `classify.branches[].nodes` are spec-relative.",
|
|
331
|
+
parameters: JSON.stringify({
|
|
332
|
+
type: "object",
|
|
333
|
+
properties: {
|
|
334
|
+
spec: {
|
|
335
|
+
type: "object",
|
|
336
|
+
description: "The workflow specification.",
|
|
337
|
+
properties: { nodes: workflowNodesArraySchema },
|
|
338
|
+
required: ["nodes"],
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
required: ["spec"],
|
|
342
|
+
}),
|
|
343
|
+
};
|
|
207
344
|
/** Build a sub-agent run spec for a kernel-generated workflow node. */
|
|
208
345
|
export function workflowNodeToSpec(node, parentSessionId) {
|
|
209
346
|
return {
|
|
@@ -216,6 +353,10 @@ export function workflowNodeToSpec(node, parentSessionId) {
|
|
|
216
353
|
role: node.role,
|
|
217
354
|
isolation: node.isolation,
|
|
218
355
|
goal: node.goal,
|
|
356
|
+
// M1/G3: carry the node's model preference so the orchestrator can route to a provider.
|
|
357
|
+
...(node.model_hint ? { modelHint: node.model_hint } : {}),
|
|
358
|
+
// M4/G5: carry the node's token cap so the orchestrator can bound the child run.
|
|
359
|
+
...(node.token_budget != null ? { tokenBudget: node.token_budget } : {}),
|
|
219
360
|
};
|
|
220
361
|
}
|
|
221
362
|
/** Build the host manifest for a kernel-generated workflow node. */
|
|
@@ -269,6 +410,34 @@ export function generateAndFilter(generators, filter) {
|
|
|
269
410
|
});
|
|
270
411
|
return { nodes };
|
|
271
412
|
}
|
|
413
|
+
/**
|
|
414
|
+
* Generate→evaluate quality gate (the EvalPipeline successor, #6): a `loop` worker node (re-run up
|
|
415
|
+
* to `maxIters`, stopping early on a `loop_continue=false` self-signal) + a bias-resistant `verify`
|
|
416
|
+
* eval node gated on it, carrying the kernel's verdict `outputSchema`. Mirrors the kernel `gen_eval`
|
|
417
|
+
* template. For the iterative retry-with-feedback variant, drive it with `HarnessLoop`.
|
|
418
|
+
*/
|
|
419
|
+
export function genEval(worker, evaluate, maxIters = 3, extractSkillOnPass = true) {
|
|
420
|
+
const schema = JSON.parse(getKernel().verdictOutputSchema(extractSkillOnPass));
|
|
421
|
+
return {
|
|
422
|
+
nodes: [
|
|
423
|
+
{
|
|
424
|
+
task: asTask(worker),
|
|
425
|
+
role: "implement",
|
|
426
|
+
isolation: "worktree",
|
|
427
|
+
contextInheritance: "full",
|
|
428
|
+
loop: { maxIters: Math.max(1, maxIters) },
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
task: asTask(evaluate),
|
|
432
|
+
role: "verify",
|
|
433
|
+
isolation: "read_only",
|
|
434
|
+
contextInheritance: "none",
|
|
435
|
+
dependsOn: [0],
|
|
436
|
+
outputSchema: schema,
|
|
437
|
+
},
|
|
438
|
+
],
|
|
439
|
+
};
|
|
440
|
+
}
|
|
272
441
|
/**
|
|
273
442
|
* One fresh-context verifier per rule/claim (parallel) + optional skeptic that depends on all and
|
|
274
443
|
* re-checks flags. Verifiers run read-only with no inherited author context (bias-resistant).
|
package/dist/types.d.ts
CHANGED
|
@@ -304,7 +304,12 @@ export interface LLMProvider {
|
|
|
304
304
|
*/
|
|
305
305
|
assessReplayability?(context: RenderedContext, extensions?: Record<string, unknown>): ReplayabilityAssessment;
|
|
306
306
|
complete(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): Promise<Message>;
|
|
307
|
-
stream(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>, state?: ProviderRunState
|
|
307
|
+
stream(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>, state?: ProviderRunState,
|
|
308
|
+
/** #2-B-ii: when provided, a preempting `InterruptNow` (or `interrupt()`) aborts the in-flight
|
|
309
|
+
* request. SDK-client providers should forward it to the client (`{ signal }`); the runner also
|
|
310
|
+
* breaks the consume loop on abort, so providers that ignore it still stop processing immediately
|
|
311
|
+
* (only the socket lingers). Optional ⇒ backward-compatible; providers may ignore it. */
|
|
312
|
+
signal?: AbortSignal): AsyncIterable<StreamEvent>;
|
|
308
313
|
}
|
|
309
314
|
/**
|
|
310
315
|
* Optional async summarizer called after context compression.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepstrike/sdk",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.18",
|
|
4
4
|
"description": "DeepStrike Node.js SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@anthropic-ai/sdk": "^0.99.0",
|
|
23
|
-
"@deepstrike/core": "0.2.
|
|
23
|
+
"@deepstrike/core": "0.2.18",
|
|
24
24
|
"@google/generative-ai": "^0.24.1",
|
|
25
25
|
"openai": "^5.23.2"
|
|
26
26
|
},
|