@deepstrike/sdk 0.2.27 → 0.2.28
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/README.md +62 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +9 -0
- package/dist/runtime/facade.d.ts +49 -0
- package/dist/runtime/facade.js +63 -0
- package/dist/runtime/runner.d.ts +21 -1
- package/dist/runtime/runner.js +119 -62
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -76,6 +76,51 @@ const reply = await collectText(runner.run({ sessionId: "chat-1", goal: "What is
|
|
|
76
76
|
|
|
77
77
|
Use `InMemorySessionLog` for process-local sessions or `FileSessionLog` when replay should survive restarts. `wake(sessionId)` resumes from the event log without inserting a duplicate `run_started` event.
|
|
78
78
|
|
|
79
|
+
### Recipes — the canonical entry points
|
|
80
|
+
|
|
81
|
+
The package exports a large surface, but most apps need one of three shapes. Start with the facades and drop down to `RuntimeRunner` only when you need streaming, signals, memory, or governance hooks.
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import { runAgent, runFanout } from "@deepstrike/sdk"
|
|
85
|
+
|
|
86
|
+
// 1) Single agent — one prompt, one model, the text back.
|
|
87
|
+
const answer = await runAgent({ provider, goal: "What is 17 + 28?", tools: [add] })
|
|
88
|
+
|
|
89
|
+
// 2) Parallel fan-out → synthesize — N workers, then a synthesis pass, over the kernel-gated DAG.
|
|
90
|
+
// Bootstraps and tears down its own kernel, so it's safe from a stateless request handler.
|
|
91
|
+
const { synthesis } = await runFanout({
|
|
92
|
+
provider,
|
|
93
|
+
tasks: [
|
|
94
|
+
"Summarize the security posture of the auth module",
|
|
95
|
+
"Summarize the data-retention posture",
|
|
96
|
+
],
|
|
97
|
+
synthesize: "Combine the worker findings into one risk summary.",
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
// 3) Full control — sub-agents, governance, signals, streaming, resume → use RuntimeRunner directly.
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`runFanout` is sugar over the **standalone `runWorkflow`** path: with no active `run()`, `runner.runWorkflow(spec)` auto-bootstraps a kernel that owns the DAG (governed · resumable), drives it, and tears it down — exactly what a Vercel/Lambda handler needs. See [Dynamic workflows](#dynamic-workflows). For parallel work you can also give each worker its own `RuntimeRunner`; `RuntimeRunner` carries per-run state, so **never share one instance across concurrent runs** — use a fresh instance per worker (or the `AgentPool` primitive).
|
|
104
|
+
|
|
105
|
+
### Deploying to serverless / bundlers
|
|
106
|
+
|
|
107
|
+
`@deepstrike/core` is a native N-API addon; its platform binary ships via `optionalDependencies`. Bundlers (Next.js/Vercel, webpack, esbuild) don't trace `.node` files by default, so the function fails at runtime with `Cannot find module '@deepstrike/core'`. Tell your bundler to treat the package as external and trace its files:
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
// next.config.ts (Next.js / Vercel)
|
|
111
|
+
const nextConfig = {
|
|
112
|
+
serverExternalPackages: ["@deepstrike/sdk"],
|
|
113
|
+
outputFileTracingIncludes: {
|
|
114
|
+
"/api/**": ["./node_modules/@deepstrike/**/*"],
|
|
115
|
+
},
|
|
116
|
+
}
|
|
117
|
+
export default nextConfig
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
- **webpack:** add `@deepstrike/core` (and `@deepstrike/sdk`) to `externals`, or use `node-loader` for `.node` files.
|
|
121
|
+
- **esbuild:** `--external:@deepstrike/core --external:@deepstrike/sdk` and ensure the platform binary is copied next to the bundle.
|
|
122
|
+
- **Docker/standalone:** the build host's platform binary must match the runtime's (e.g. build on `linux-x64-gnu` for Vercel). Alpine images need the `-musl` binary.
|
|
123
|
+
|
|
79
124
|
Streaming:
|
|
80
125
|
|
|
81
126
|
```typescript
|
|
@@ -161,9 +206,11 @@ const outcome = await runner.runWorkflow({
|
|
|
161
206
|
{ task: "Skeptic: which flags are real violations?", role: "verify", dependsOn: [0, 1, 2] },
|
|
162
207
|
],
|
|
163
208
|
})
|
|
164
|
-
// → { completed: ["wf-node0", … ], failed: [] }
|
|
209
|
+
// → { completed: ["wf-node0", … ], failed: [], outputs: { "wf-node3": "…" } }
|
|
165
210
|
```
|
|
166
211
|
|
|
212
|
+
`runWorkflow` works **standalone** — call it on a freshly-constructed runner (e.g. inside a stateless HTTP handler) and it auto-bootstraps a kernel that owns the DAG, drives it under the same governance/quota/attention policies a full `run()` gets, and tears it down on completion. Called *during* a `run()`, it instead drives the workflow on the active kernel. Either way every node's final text comes back in `outputs`, keyed by node agent-id. To resume an interrupted standalone run, pass the prior session id: `runner.resumeWorkflow(spec, { sessionId })`.
|
|
213
|
+
|
|
167
214
|
A node's `kind` selects the control-flow shape; the same executor drives them all, every spawn passing the syscall gate:
|
|
168
215
|
|
|
169
216
|
| Node `kind` | Behavior |
|
|
@@ -201,7 +248,20 @@ All providers accept `RetryConfig` for exponential backoff and share a `CircuitB
|
|
|
201
248
|
|
|
202
249
|
`extensions` are forwarded by every provider in both `complete()` and `stream()` while SDK-owned structural fields such as `model`, `messages`, `tools`, and streaming flags remain protected.
|
|
203
250
|
|
|
204
|
-
OpenAI
|
|
251
|
+
**Custom OpenAI-compatible endpoint** (MiMo, DeepSeek, Kimi, Qwen, GLM via their `/v1` base URL): use `OpenAIChatProvider` (alias `OpenAIProvider`) and pass the base URL as the **4th** argument — the 3rd is `RetryConfig`:
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
import { OpenAIProvider } from "@deepstrike/sdk"
|
|
255
|
+
|
|
256
|
+
const provider = new OpenAIProvider(
|
|
257
|
+
apiKey,
|
|
258
|
+
"mimo-v2.5-pro",
|
|
259
|
+
{ maxRetries: 3, baseDelay: 1000 }, // RetryConfig (pass undefined for defaults)
|
|
260
|
+
"https://token-plan-cn.xiaomimimo.com/v1",
|
|
261
|
+
)
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Prefer a dedicated `*Provider` (e.g. `DeepSeekProvider`, `KimiProvider`) when one exists for your backend — they default the base URL and add backend-specific reasoning handling. OpenAI itself can also be selected through the provider catalog:
|
|
205
265
|
|
|
206
266
|
```typescript
|
|
207
267
|
import { createProvider } from "@deepstrike/sdk"
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export { runAgent, runFanout } from "./runtime/facade.js";
|
|
2
|
+
export type { RunAgentOptions, RunFanoutOptions } from "./runtime/facade.js";
|
|
1
3
|
export { RuntimeRunner, collectText } from "./runtime/runner.js";
|
|
2
4
|
export type { RuntimeOptions, SchedulerBudget } from "./runtime/runner.js";
|
|
3
5
|
export { builtinReducers, resolveReducer } from "./runtime/reducers.js";
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
// ╔══════════════════════════════════════════════════════════════════════════╗
|
|
2
|
+
// ║ START HERE — the canonical entry points for the common cases. ║
|
|
3
|
+
// ║ runAgent → one prompt, one model, the text back. ║
|
|
4
|
+
// ║ runFanout → run N tasks in parallel, then synthesize (kernel-gated DAG).║
|
|
5
|
+
// ║ RuntimeRunner → drop down to this for streaming, tools, signals, memory, ║
|
|
6
|
+
// ║ governance, and the standalone `runWorkflow` driver. ║
|
|
7
|
+
// ║ Everything below the providers block is advanced / opt-in surface. ║
|
|
8
|
+
// ╚══════════════════════════════════════════════════════════════════════════╝
|
|
9
|
+
export { runAgent, runFanout } from "./runtime/facade.js";
|
|
1
10
|
// ── Runtime (Layer 1.5) ────────────────────────────────────────────────────
|
|
2
11
|
export { RuntimeRunner, collectText } from "./runtime/runner.js";
|
|
3
12
|
export { builtinReducers, resolveReducer } from "./runtime/reducers.js";
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { ExecutionPlane } from "./execution-plane.js";
|
|
2
|
+
import type { SessionLog } from "./session-log.js";
|
|
3
|
+
import type { LLMProvider } from "../types.js";
|
|
4
|
+
import type { RegisteredTool } from "../tools/index.js";
|
|
5
|
+
import type { WorkflowTaskSpec, KernelAgentRole } from "../types/agent.js";
|
|
6
|
+
/** Shared knobs for the facade entry points. */
|
|
7
|
+
export interface RunAgentOptions {
|
|
8
|
+
provider: LLMProvider;
|
|
9
|
+
goal: string;
|
|
10
|
+
systemPrompt?: string;
|
|
11
|
+
tools?: RegisteredTool[];
|
|
12
|
+
sessionId?: string;
|
|
13
|
+
maxTokens?: number;
|
|
14
|
+
maxTurns?: number;
|
|
15
|
+
/** Persist the run (resume / audit). Defaults to an in-memory, throwaway log. */
|
|
16
|
+
sessionLog?: SessionLog;
|
|
17
|
+
/** Custom execution plane (tools, sandboxing). Overrides `tools` when both are given. */
|
|
18
|
+
executionPlane?: ExecutionPlane;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Run a single agent to completion and return its final text — `RuntimeRunner` + `run` + `collectText`
|
|
22
|
+
* in one call. Register tools by passing `tools`; everything else has a working default.
|
|
23
|
+
*/
|
|
24
|
+
export declare function runAgent(opts: RunAgentOptions): Promise<string>;
|
|
25
|
+
export interface RunFanoutOptions {
|
|
26
|
+
provider: LLMProvider;
|
|
27
|
+
/** One parallel worker per task. A string is shorthand for `{ goal }`. */
|
|
28
|
+
tasks: WorkflowTaskSpec[];
|
|
29
|
+
/** Final synthesis prompt; runs once after every worker completes, with their outputs in context. */
|
|
30
|
+
synthesize: string;
|
|
31
|
+
/** Role for the parallel workers (default `explore`) and the synthesis node (default `plan`). */
|
|
32
|
+
workerRole?: KernelAgentRole;
|
|
33
|
+
synthesisRole?: KernelAgentRole;
|
|
34
|
+
sessionId?: string;
|
|
35
|
+
maxTokens?: number;
|
|
36
|
+
maxTurns?: number;
|
|
37
|
+
sessionLog?: SessionLog;
|
|
38
|
+
executionPlane?: ExecutionPlane;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Parallel fan-out → synthesize, driven by the kernel-gated DAG (the standalone `runWorkflow` path):
|
|
42
|
+
* each task becomes a fresh-context worker node, and a final synthesis node depends on all of them.
|
|
43
|
+
* Returns the synthesis text plus every node's raw output. Safe to call from a stateless handler — it
|
|
44
|
+
* bootstraps and tears down its own kernel.
|
|
45
|
+
*/
|
|
46
|
+
export declare function runFanout(opts: RunFanoutOptions): Promise<{
|
|
47
|
+
synthesis: string;
|
|
48
|
+
outputs: Record<string, string>;
|
|
49
|
+
}>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* High-level facades for the two bread-and-butter cases, so a caller doesn't have to assemble
|
|
3
|
+
* `RuntimeRunner` + session log + execution plane + `collectText` by hand (integration feedback #4:
|
|
4
|
+
* the package exports ~150 symbols and the canonical entry point for common work wasn't discoverable).
|
|
5
|
+
*
|
|
6
|
+
* - `runAgent` — one prompt, one model, the text back. The 90%-case single-agent call.
|
|
7
|
+
* - `runFanout` — run N tasks in parallel, then synthesize, from a stateless request handler. Drives
|
|
8
|
+
* the kernel-gated DAG via the standalone `runWorkflow` path (governed · resumable),
|
|
9
|
+
* instead of hand-rolling a multi-runner fan-out.
|
|
10
|
+
*
|
|
11
|
+
* Both build a throwaway `RuntimeRunner` with sensible defaults; pass `sessionLog` / `executionPlane`
|
|
12
|
+
* to opt into persistence or custom tools. Reach for the underlying `RuntimeRunner` directly when you
|
|
13
|
+
* need streaming events, signals, memory, or governance hooks.
|
|
14
|
+
*/
|
|
15
|
+
import { RuntimeRunner, collectText } from "./runner.js";
|
|
16
|
+
import { LocalExecutionPlane } from "./execution-plane.js";
|
|
17
|
+
import { InMemorySessionLog } from "./session-log.js";
|
|
18
|
+
/**
|
|
19
|
+
* Run a single agent to completion and return its final text — `RuntimeRunner` + `run` + `collectText`
|
|
20
|
+
* in one call. Register tools by passing `tools`; everything else has a working default.
|
|
21
|
+
*/
|
|
22
|
+
export async function runAgent(opts) {
|
|
23
|
+
const plane = opts.executionPlane ??
|
|
24
|
+
(opts.tools ?? []).reduce((p, t) => p.register(t), new LocalExecutionPlane());
|
|
25
|
+
const runner = new RuntimeRunner({
|
|
26
|
+
provider: opts.provider,
|
|
27
|
+
executionPlane: plane,
|
|
28
|
+
sessionLog: opts.sessionLog ?? new InMemorySessionLog(),
|
|
29
|
+
maxTokens: opts.maxTokens ?? 32_000,
|
|
30
|
+
...(opts.maxTurns !== undefined ? { maxTurns: opts.maxTurns } : {}),
|
|
31
|
+
...(opts.systemPrompt !== undefined ? { systemPrompt: opts.systemPrompt } : {}),
|
|
32
|
+
});
|
|
33
|
+
return collectText(runner.run({ sessionId: opts.sessionId ?? `agent-${crypto.randomUUID()}`, goal: opts.goal }));
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Parallel fan-out → synthesize, driven by the kernel-gated DAG (the standalone `runWorkflow` path):
|
|
37
|
+
* each task becomes a fresh-context worker node, and a final synthesis node depends on all of them.
|
|
38
|
+
* Returns the synthesis text plus every node's raw output. Safe to call from a stateless handler — it
|
|
39
|
+
* bootstraps and tears down its own kernel.
|
|
40
|
+
*/
|
|
41
|
+
export async function runFanout(opts) {
|
|
42
|
+
const runner = new RuntimeRunner({
|
|
43
|
+
provider: opts.provider,
|
|
44
|
+
executionPlane: opts.executionPlane ?? new LocalExecutionPlane(),
|
|
45
|
+
sessionLog: opts.sessionLog ?? new InMemorySessionLog(),
|
|
46
|
+
maxTokens: opts.maxTokens ?? 32_000,
|
|
47
|
+
...(opts.maxTurns !== undefined ? { maxTurns: opts.maxTurns } : {}),
|
|
48
|
+
});
|
|
49
|
+
const workerRole = opts.workerRole ?? "explore";
|
|
50
|
+
const spec = {
|
|
51
|
+
nodes: [
|
|
52
|
+
...opts.tasks.map(task => ({ task, role: workerRole })),
|
|
53
|
+
{
|
|
54
|
+
task: opts.synthesize,
|
|
55
|
+
role: opts.synthesisRole ?? "plan",
|
|
56
|
+
dependsOn: opts.tasks.map((_, i) => i),
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
};
|
|
60
|
+
const outcome = await runner.runWorkflow(spec, opts.sessionId ? { sessionId: opts.sessionId } : undefined);
|
|
61
|
+
const synthesisId = `wf-node${opts.tasks.length}`;
|
|
62
|
+
return { synthesis: outcome.outputs[synthesisId] ?? "", outputs: outcome.outputs };
|
|
63
|
+
}
|
package/dist/runtime/runner.d.ts
CHANGED
|
@@ -230,6 +230,14 @@ export declare class RuntimeRunner {
|
|
|
230
230
|
}): Promise<MemoryEntry[]>;
|
|
231
231
|
private logMemoryRetrievalResult;
|
|
232
232
|
private createSyscallRuntime;
|
|
233
|
+
/**
|
|
234
|
+
* Lower the declarative governance / attention / scheduler-budget / resource-quota policies into a
|
|
235
|
+
* freshly-created kernel. Shared by `execute()` (full agent run) and `bootstrapWorkflowKernel()`
|
|
236
|
+
* (standalone host-driven workflow) so a workflow's DAG-node spawns are gated, queued, and quota'd
|
|
237
|
+
* exactly as a mid-run spawn would be. Must run BEFORE `start_run` so the in-kernel gate enforces
|
|
238
|
+
* every policy from the first spawn. No config ⇒ the native-profile defaults (铁律: defaults only).
|
|
239
|
+
*/
|
|
240
|
+
private applyKernelPolicies;
|
|
233
241
|
private appendMemorySyscallObservations;
|
|
234
242
|
/** Mount a tool capability on the currently-running kernel runtime. No-op if not running. */
|
|
235
243
|
mountTool(schema: ToolSchema): void;
|
|
@@ -273,11 +281,21 @@ export declare class RuntimeRunner {
|
|
|
273
281
|
runWorkflow(spec: WorkflowSpec, opts?: {
|
|
274
282
|
resumedCompleted?: string[];
|
|
275
283
|
resumedSubmissions?: Record<string, unknown>[][];
|
|
284
|
+
/** Standalone session id when bootstrapping (no active parent run). Defaults to a fresh uuid. */
|
|
285
|
+
sessionId?: string;
|
|
276
286
|
}): Promise<{
|
|
277
287
|
completed: string[];
|
|
278
288
|
failed: string[];
|
|
279
289
|
outputs: Record<string, string>;
|
|
280
290
|
}>;
|
|
291
|
+
/**
|
|
292
|
+
* Bootstrap a standalone kernel for a host-driven workflow with NO active parent run — the path a
|
|
293
|
+
* stateless request handler takes when it calls `runWorkflow(spec)` directly. Mirrors `execute()`'s
|
|
294
|
+
* pre-run kernel setup (governance / attention / quota via `applyKernelPolicies`, then `start_run`)
|
|
295
|
+
* and records a `run_started` event so the standalone run is resumable from the session log. Sets
|
|
296
|
+
* `activeKernel` / `currentSessionId`; `runWorkflow` is responsible for tearing them down.
|
|
297
|
+
*/
|
|
298
|
+
private bootstrapWorkflowKernel;
|
|
281
299
|
/**
|
|
282
300
|
* M5/G1: bootstrap an **agent-authored** workflow ("the model writes its own harness"). Unlike
|
|
283
301
|
* `runWorkflow` (the host fires the privileged `load_workflow`), this routes the spec through the
|
|
@@ -325,7 +343,9 @@ export declare class RuntimeRunner {
|
|
|
325
343
|
* Reads the session log, extracts completed workflow node agent_ids, and
|
|
326
344
|
* calls runWorkflow with resumedCompleted so the kernel skips those nodes.
|
|
327
345
|
*/
|
|
328
|
-
resumeWorkflow(spec: WorkflowSpec
|
|
346
|
+
resumeWorkflow(spec: WorkflowSpec, opts?: {
|
|
347
|
+
sessionId?: string;
|
|
348
|
+
}): Promise<{
|
|
329
349
|
completed: string[];
|
|
330
350
|
failed: string[];
|
|
331
351
|
}>;
|
package/dist/runtime/runner.js
CHANGED
|
@@ -139,6 +139,60 @@ export class RuntimeRunner {
|
|
|
139
139
|
timeoutMs: this.opts.timeoutMs !== undefined ? BigInt(this.opts.timeoutMs) : undefined,
|
|
140
140
|
});
|
|
141
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* Lower the declarative governance / attention / scheduler-budget / resource-quota policies into a
|
|
144
|
+
* freshly-created kernel. Shared by `execute()` (full agent run) and `bootstrapWorkflowKernel()`
|
|
145
|
+
* (standalone host-driven workflow) so a workflow's DAG-node spawns are gated, queued, and quota'd
|
|
146
|
+
* exactly as a mid-run spawn would be. Must run BEFORE `start_run` so the in-kernel gate enforces
|
|
147
|
+
* every policy from the first spawn. No config ⇒ the native-profile defaults (铁律: defaults only).
|
|
148
|
+
*/
|
|
149
|
+
applyKernelPolicies(runtime) {
|
|
150
|
+
const osProfile = assertNativeProfile(this.opts.osProfile ?? "native");
|
|
151
|
+
const attentionPolicy = this.opts.attentionPolicy ?? osProfile.attentionPolicy;
|
|
152
|
+
const governancePolicy = this.opts.governancePolicy ?? osProfile.governancePolicy;
|
|
153
|
+
// Load the declarative governance policy into the kernel before the run starts,
|
|
154
|
+
// so the in-kernel gate enforces deny/veto/rate-limit/param before any tool runs.
|
|
155
|
+
kernelApply(runtime, this.pendingObservations, governancePolicyToKernelEvent(governancePolicy));
|
|
156
|
+
// Enable in-kernel signal routing so the kernel owns disposition + queuing.
|
|
157
|
+
kernelApply(runtime, this.pendingObservations, {
|
|
158
|
+
kind: "set_attention_policy",
|
|
159
|
+
...(attentionPolicy.maxQueueSize !== undefined
|
|
160
|
+
? { max_queue_size: attentionPolicy.maxQueueSize }
|
|
161
|
+
: {}),
|
|
162
|
+
});
|
|
163
|
+
// Set optional wall-clock budget override.
|
|
164
|
+
if (this.opts.schedulerBudget) {
|
|
165
|
+
kernelApply(runtime, this.pendingObservations, {
|
|
166
|
+
kind: "set_scheduler_budget",
|
|
167
|
+
...(this.opts.schedulerBudget.maxWallMs !== undefined
|
|
168
|
+
? { max_wall_ms: this.opts.schedulerBudget.maxWallMs }
|
|
169
|
+
: {}),
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
// Install optional resource quotas at the syscall trap (M2). Maps the ergonomic camelCase
|
|
173
|
+
// option onto the kernel's snake_case quota shape; the write-rate window is the serde tuple
|
|
174
|
+
// `[maxWrites, windowMs]`. Omitting the option leaves spawn / memory writes unbounded.
|
|
175
|
+
if (this.opts.resourceQuota) {
|
|
176
|
+
const q = this.opts.resourceQuota;
|
|
177
|
+
kernelApply(runtime, this.pendingObservations, {
|
|
178
|
+
kind: "set_resource_quota",
|
|
179
|
+
quota: {
|
|
180
|
+
...(q.maxConcurrentSubagents !== undefined
|
|
181
|
+
? { max_concurrent_subagents: q.maxConcurrentSubagents }
|
|
182
|
+
: {}),
|
|
183
|
+
...(q.maxSpawnDepth !== undefined ? { max_spawn_depth: q.maxSpawnDepth } : {}),
|
|
184
|
+
...(q.memoryWritesPerWindow !== undefined
|
|
185
|
+
? {
|
|
186
|
+
memory_writes_per_window: [
|
|
187
|
+
q.memoryWritesPerWindow.maxWrites,
|
|
188
|
+
q.memoryWritesPerWindow.windowMs,
|
|
189
|
+
],
|
|
190
|
+
}
|
|
191
|
+
: {}),
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
142
196
|
async appendMemorySyscallObservations(sessionId, observations) {
|
|
143
197
|
if (!sessionId)
|
|
144
198
|
return;
|
|
@@ -392,21 +446,65 @@ export class RuntimeRunner {
|
|
|
392
446
|
* Returns the completed / failed node agent-ids.
|
|
393
447
|
*/
|
|
394
448
|
async runWorkflow(spec, opts) {
|
|
395
|
-
|
|
396
|
-
|
|
449
|
+
// Standalone entry: with no active parent run (e.g. a stateless HTTP handler), auto-bootstrap a
|
|
450
|
+
// kernel that owns the DAG — start_run + the same governance/quota/attention policies a full run
|
|
451
|
+
// gets — then tear it down on completion so the runner is reusable. Mid-run callers (activeKernel
|
|
452
|
+
// already set by an in-flight `run()`) keep the original in-place behavior with no teardown.
|
|
453
|
+
const bootstrapped = !this.activeKernel || !this.currentSessionId;
|
|
454
|
+
if (bootstrapped) {
|
|
455
|
+
this.bootstrapWorkflowKernel(opts?.sessionId ?? `wf-${crypto.randomUUID()}`, spec);
|
|
397
456
|
}
|
|
398
457
|
const parentSessionId = this.currentSessionId;
|
|
399
458
|
const runtime = this.activeKernel;
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
459
|
+
try {
|
|
460
|
+
const observations = kernelApply(runtime, this.pendingObservations, {
|
|
461
|
+
kind: "load_workflow",
|
|
462
|
+
spec: workflowSpecToKernel(spec),
|
|
463
|
+
parent_session_id: parentSessionId,
|
|
464
|
+
// W0-ABI resume: skip nodes already completed before an interruption.
|
|
465
|
+
...(opts?.resumedCompleted?.length ? { resumed_completed: opts.resumedCompleted } : {}),
|
|
466
|
+
// R3-1: re-apply recorded runtime submissions so dynamically-appended nodes are reconstructed.
|
|
467
|
+
...(opts?.resumedSubmissions?.length ? { resumed_submissions: opts.resumedSubmissions } : {}),
|
|
468
|
+
});
|
|
469
|
+
return await this.driveWorkflow(observations, parentSessionId, runtime);
|
|
470
|
+
}
|
|
471
|
+
finally {
|
|
472
|
+
if (bootstrapped) {
|
|
473
|
+
this.activeKernel = null;
|
|
474
|
+
this.currentSessionId = null;
|
|
475
|
+
this.pendingObservations = [];
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Bootstrap a standalone kernel for a host-driven workflow with NO active parent run — the path a
|
|
481
|
+
* stateless request handler takes when it calls `runWorkflow(spec)` directly. Mirrors `execute()`'s
|
|
482
|
+
* pre-run kernel setup (governance / attention / quota via `applyKernelPolicies`, then `start_run`)
|
|
483
|
+
* and records a `run_started` event so the standalone run is resumable from the session log. Sets
|
|
484
|
+
* `activeKernel` / `currentSessionId`; `runWorkflow` is responsible for tearing them down.
|
|
485
|
+
*/
|
|
486
|
+
bootstrapWorkflowKernel(sessionId, spec) {
|
|
487
|
+
this.interrupted = false;
|
|
488
|
+
this.abortController = new AbortController();
|
|
489
|
+
this.pendingObservations = [];
|
|
490
|
+
this.pendingSpoolOutputs.clear();
|
|
491
|
+
this.currentSessionId = sessionId;
|
|
492
|
+
const runtime = this.createSyscallRuntime();
|
|
493
|
+
this.activeKernel = runtime;
|
|
494
|
+
const goal = `workflow:${spec.nodes.length} nodes`;
|
|
495
|
+
// Best-effort run_started log so a standalone workflow can be resumed via `resumeWorkflow`. The
|
|
496
|
+
// session log is fire-and-forget here (the kernel state, not the log, drives the DAG); a logless
|
|
497
|
+
// store simply means no resume.
|
|
498
|
+
void this.opts.sessionLog.append(sessionId, {
|
|
499
|
+
kind: "run_started",
|
|
500
|
+
run_id: crypto.randomUUID(),
|
|
501
|
+
goal,
|
|
502
|
+
criteria: [],
|
|
503
|
+
agent_id: this.opts.agentId,
|
|
504
|
+
}).catch(() => { });
|
|
505
|
+
this.applyKernelPolicies(runtime);
|
|
506
|
+
kernelApply(runtime, this.pendingObservations, { kind: "start_run", task: { goal, criteria: [] } });
|
|
507
|
+
return runtime;
|
|
410
508
|
}
|
|
411
509
|
/**
|
|
412
510
|
* M5/G1: bootstrap an **agent-authored** workflow ("the model writes its own harness"). Unlike
|
|
@@ -575,14 +673,17 @@ export class RuntimeRunner {
|
|
|
575
673
|
* Reads the session log, extracts completed workflow node agent_ids, and
|
|
576
674
|
* calls runWorkflow with resumedCompleted so the kernel skips those nodes.
|
|
577
675
|
*/
|
|
578
|
-
async resumeWorkflow(spec) {
|
|
579
|
-
|
|
580
|
-
|
|
676
|
+
async resumeWorkflow(spec, opts) {
|
|
677
|
+
// Standalone resume: a stateless handler passes the prior `sessionId` to pick up an interrupted
|
|
678
|
+
// workflow from the session log. Mid-run callers omit it and resume the active session.
|
|
679
|
+
const sessionId = opts?.sessionId ?? this.currentSessionId;
|
|
680
|
+
if (!sessionId) {
|
|
681
|
+
throw new Error("resumeWorkflow requires an active parent run or an explicit sessionId");
|
|
581
682
|
}
|
|
582
|
-
const events = await this.opts.sessionLog.read(
|
|
683
|
+
const events = await this.opts.sessionLog.read(sessionId);
|
|
583
684
|
const resumedCompleted = recoverCompletedWorkflowNodes(events);
|
|
584
685
|
const resumedSubmissions = recoverSubmittedWorkflowNodes(events);
|
|
585
|
-
return this.runWorkflow(spec, { resumedCompleted, resumedSubmissions });
|
|
686
|
+
return this.runWorkflow(spec, { resumedCompleted, resumedSubmissions, sessionId });
|
|
586
687
|
}
|
|
587
688
|
interrupt() { this.interrupted = true; this.abortController?.abort(); }
|
|
588
689
|
async *run(req) {
|
|
@@ -919,51 +1020,7 @@ export class RuntimeRunner {
|
|
|
919
1020
|
: baseSpec;
|
|
920
1021
|
startPayload.run_spec = agentRunSpecToKernel(spec);
|
|
921
1022
|
}
|
|
922
|
-
|
|
923
|
-
const attentionPolicy = this.opts.attentionPolicy ?? osProfile.attentionPolicy;
|
|
924
|
-
const governancePolicy = this.opts.governancePolicy ?? osProfile.governancePolicy;
|
|
925
|
-
// Load the declarative governance policy into the kernel before the run starts,
|
|
926
|
-
// so the in-kernel gate enforces deny/veto/rate-limit/param before any tool runs.
|
|
927
|
-
kernelApply(runtime, this.pendingObservations, governancePolicyToKernelEvent(governancePolicy));
|
|
928
|
-
// Enable in-kernel signal routing so the kernel owns disposition + queuing.
|
|
929
|
-
kernelApply(runtime, this.pendingObservations, {
|
|
930
|
-
kind: "set_attention_policy",
|
|
931
|
-
...(attentionPolicy.maxQueueSize !== undefined
|
|
932
|
-
? { max_queue_size: attentionPolicy.maxQueueSize }
|
|
933
|
-
: {}),
|
|
934
|
-
});
|
|
935
|
-
// Set optional wall-clock budget override.
|
|
936
|
-
if (this.opts.schedulerBudget) {
|
|
937
|
-
kernelApply(runtime, this.pendingObservations, {
|
|
938
|
-
kind: "set_scheduler_budget",
|
|
939
|
-
...(this.opts.schedulerBudget.maxWallMs !== undefined
|
|
940
|
-
? { max_wall_ms: this.opts.schedulerBudget.maxWallMs }
|
|
941
|
-
: {}),
|
|
942
|
-
});
|
|
943
|
-
}
|
|
944
|
-
// Install optional resource quotas at the syscall trap (M2). Maps the ergonomic camelCase
|
|
945
|
-
// option onto the kernel's snake_case quota shape; the write-rate window is the serde tuple
|
|
946
|
-
// `[maxWrites, windowMs]`. Omitting the option leaves spawn / memory writes unbounded.
|
|
947
|
-
if (this.opts.resourceQuota) {
|
|
948
|
-
const q = this.opts.resourceQuota;
|
|
949
|
-
kernelApply(runtime, this.pendingObservations, {
|
|
950
|
-
kind: "set_resource_quota",
|
|
951
|
-
quota: {
|
|
952
|
-
...(q.maxConcurrentSubagents !== undefined
|
|
953
|
-
? { max_concurrent_subagents: q.maxConcurrentSubagents }
|
|
954
|
-
: {}),
|
|
955
|
-
...(q.maxSpawnDepth !== undefined ? { max_spawn_depth: q.maxSpawnDepth } : {}),
|
|
956
|
-
...(q.memoryWritesPerWindow !== undefined
|
|
957
|
-
? {
|
|
958
|
-
memory_writes_per_window: [
|
|
959
|
-
q.memoryWritesPerWindow.maxWrites,
|
|
960
|
-
q.memoryWritesPerWindow.windowMs,
|
|
961
|
-
],
|
|
962
|
-
}
|
|
963
|
-
: {}),
|
|
964
|
-
},
|
|
965
|
-
});
|
|
966
|
-
}
|
|
1023
|
+
this.applyKernelPolicies(runtime);
|
|
967
1024
|
// Multimodal upload: seed the user's attachments (images/audio) as a history
|
|
968
1025
|
// message before start_run pushes the "[TASK STATE]" anchor. init_task does not
|
|
969
1026
|
// clear history, so order becomes [attachment user msg, "Proceed…"] — both land
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepstrike/sdk",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.28",
|
|
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.28",
|
|
24
24
|
"@google/generative-ai": "^0.24.1",
|
|
25
25
|
"openai": "^5.23.2"
|
|
26
26
|
},
|