@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 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 can also be selected through the provider catalog:
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
+ }
@@ -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): Promise<{
346
+ resumeWorkflow(spec: WorkflowSpec, opts?: {
347
+ sessionId?: string;
348
+ }): Promise<{
329
349
  completed: string[];
330
350
  failed: string[];
331
351
  }>;
@@ -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
- if (!this.activeKernel || !this.currentSessionId) {
396
- throw new Error("runWorkflow requires an active parent run");
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
- const observations = kernelApply(runtime, this.pendingObservations, {
401
- kind: "load_workflow",
402
- spec: workflowSpecToKernel(spec),
403
- parent_session_id: parentSessionId,
404
- // W0-ABI resume: skip nodes already completed before an interruption.
405
- ...(opts?.resumedCompleted?.length ? { resumed_completed: opts.resumedCompleted } : {}),
406
- // R3-1: re-apply recorded runtime submissions so dynamically-appended nodes are reconstructed.
407
- ...(opts?.resumedSubmissions?.length ? { resumed_submissions: opts.resumedSubmissions } : {}),
408
- });
409
- return this.driveWorkflow(observations, parentSessionId, runtime);
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
- if (!this.currentSessionId) {
580
- throw new Error("resumeWorkflow requires an active parent run");
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(this.currentSessionId);
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
- const osProfile = assertNativeProfile(this.opts.osProfile ?? "native");
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.27",
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.27",
23
+ "@deepstrike/core": "0.2.28",
24
24
  "@google/generative-ai": "^0.24.1",
25
25
  "openai": "^5.23.2"
26
26
  },