@deepstrike/sdk 0.2.49 → 0.2.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +83 -60
  2. package/dist/harness/manifest.d.ts +1 -1
  3. package/dist/harness/manifest.js +43 -29
  4. package/dist/index.d.ts +5 -7
  5. package/dist/index.js +3 -3
  6. package/dist/kernel.d.ts +61 -31
  7. package/dist/runtime/canonical-kernel-step.d.ts +143 -0
  8. package/dist/runtime/canonical-kernel-step.js +1444 -0
  9. package/dist/runtime/execution-plane.d.ts +0 -3
  10. package/dist/runtime/execution-plane.js +0 -24
  11. package/dist/runtime/facade.js +3 -0
  12. package/dist/runtime/kernel-event-log.js +7 -13
  13. package/dist/runtime/kernel-journal.d.ts +264 -0
  14. package/dist/runtime/kernel-journal.js +741 -0
  15. package/dist/runtime/kernel-primitives-dashboard.d.ts +0 -2
  16. package/dist/runtime/kernel-primitives-dashboard.js +1 -8
  17. package/dist/runtime/kernel-step.d.ts +29 -109
  18. package/dist/runtime/kernel-step.js +47 -317
  19. package/dist/runtime/os-snapshot.d.ts +2 -2
  20. package/dist/runtime/os-snapshot.js +2 -6
  21. package/dist/runtime/payload-store.d.ts +16 -0
  22. package/dist/runtime/payload-store.js +80 -0
  23. package/dist/runtime/runner.d.ts +80 -119
  24. package/dist/runtime/runner.js +706 -779
  25. package/dist/runtime/session-log.d.ts +34 -32
  26. package/dist/runtime/session-log.js +21 -131
  27. package/dist/runtime/session-repair.d.ts +2 -36
  28. package/dist/runtime/session-repair.js +2 -47
  29. package/dist/runtime/sub-agent-orchestrator.d.ts +1 -1
  30. package/dist/runtime/sub-agent-orchestrator.js +42 -40
  31. package/dist/types/agent.d.ts +31 -19
  32. package/dist/types/agent.js +31 -42
  33. package/dist/workflow/public.d.ts +1 -1
  34. package/dist/workflow/public.js +1 -1
  35. package/package.json +2 -2
  36. package/dist/runtime/kernel-rebuild.d.ts +0 -13
  37. package/dist/runtime/kernel-rebuild.js +0 -75
  38. package/dist/runtime/kernel-transaction-log.d.ts +0 -61
  39. package/dist/runtime/kernel-transaction-log.js +0 -149
  40. package/dist/runtime/large-result-spool.d.ts +0 -93
  41. package/dist/runtime/large-result-spool.js +0 -214
@@ -1,11 +1,7 @@
1
1
  import type { KernelPrimitive } from "./kernel-event-log.js";
2
2
  import type { ContentPart, ProviderReplay, ToolCall, ToolErrorKind } from "../types.js";
3
3
  import type { MemoryRecall, MemoryScope } from "../memory/protocols.js";
4
- import type { DurableAppendReceipt, KernelGenesisReceipt, KernelOperationGenesis, KernelTransaction } from "./kernel-transaction-log.js";
5
- export interface KernelTransactionEntry {
6
- log_seq: number;
7
- transaction: KernelTransaction;
8
- }
4
+ import type { KernelJournal } from "./kernel-journal.js";
9
5
  export type RollbackReason = {
10
6
  kind: "fatal_tool_error";
11
7
  tool_name: string;
@@ -98,14 +94,6 @@ export type SessionEvent = {
98
94
  kind: "page_in";
99
95
  turn: number;
100
96
  entry_count: number;
101
- } | {
102
- kind: "large_result_spooled";
103
- turn: number;
104
- call_id: string;
105
- tool: string;
106
- original_size: number;
107
- preview_size: number;
108
- spool_ref?: string;
109
97
  } | {
110
98
  kind: "rollbacked";
111
99
  turn: number;
@@ -204,7 +192,9 @@ export type SessionEvent = {
204
192
  kind: "agent_process_changed";
205
193
  turn: number;
206
194
  agent_id: string;
207
- parent_session_id: string;
195
+ parent_task_id?: string;
196
+ /** Host audit identity; canonical kernel observations never populate it. */
197
+ parent_session_id?: string;
208
198
  role: string;
209
199
  isolation: string;
210
200
  context_inheritance: string;
@@ -278,6 +268,11 @@ export type SessionEvent = {
278
268
  turn: number;
279
269
  node_outcomes: import("../types/agent.js").KernelWorkflowNodeOutcome[];
280
270
  total_nodes: number;
271
+ } | {
272
+ kind: "kernel_observation";
273
+ turn: number;
274
+ observation_kind: string;
275
+ raw: Record<string, unknown>;
281
276
  } | {
282
277
  kind: "run_terminal";
283
278
  reason: string;
@@ -312,6 +307,13 @@ export type SessionEvent = {
312
307
  reason: string;
313
308
  coerced_from?: string;
314
309
  };
310
+ /**
311
+ * The business-projection log (spec §9.2): run started/terminal, stream events, observations,
312
+ * provider/tool presentation, audit metadata.
313
+ *
314
+ * Durable kernel records use the separate `KernelJournal` capability. `SessionLog` contains only
315
+ * business observations and never adapts or reserializes canonical records.
316
+ */
315
317
  export interface SessionLog {
316
318
  append(sessionId: string, event: SessionEvent): Promise<number>;
317
319
  read(sessionId: string, fromSeq?: number, primitiveFilter?: KernelPrimitive): Promise<Array<{
@@ -319,18 +321,18 @@ export interface SessionLog {
319
321
  event: SessionEvent;
320
322
  }>>;
321
323
  latestSeq(sessionId: string): Promise<number>;
322
- appendKernelGenesis(sessionId: string, genesis: KernelOperationGenesis): Promise<KernelGenesisReceipt>;
323
- readKernelGenesis(sessionId: string, operationId: string): Promise<KernelOperationGenesis | undefined>;
324
- compareAndAppendKernelTransaction(sessionId: string, expectedTransactionHead: string, transaction: KernelTransaction): Promise<DurableAppendReceipt>;
325
- readKernelTransactions(sessionId: string, operationId: string, fromStepSeq?: number): Promise<KernelTransactionEntry[]>;
326
- kernelTransactionHead(sessionId: string, operationId: string): Promise<string | undefined>;
327
324
  }
325
+ /**
326
+ * **Single-process dev/test implementation** of both capabilities (spec §9.4: one class may
327
+ * implement several capabilities; the *interfaces* stay separate). Its `KernelJournal` half is
328
+ * `InMemoryKernelJournal`, whose CAS is atomic within one process only.
329
+ */
328
330
  export declare class InMemorySessionLog implements SessionLog {
329
331
  private store;
332
+ /** Business event sequence space only — journal records number themselves by `step_seq`. */
330
333
  private seqCounters;
331
- private genesisStore;
332
- private transactionStore;
333
- private operationKey;
334
+ /** The durable transaction capability, held rather than inherited (spec §9.1/§9.4). */
335
+ readonly kernelJournal: KernelJournal;
334
336
  private nextSeq;
335
337
  append(sessionId: string, event: SessionEvent): Promise<number>;
336
338
  read(sessionId: string, fromSeq?: number, primitiveFilter?: KernelPrimitive): Promise<Array<{
@@ -338,16 +340,21 @@ export declare class InMemorySessionLog implements SessionLog {
338
340
  event: SessionEvent;
339
341
  }>>;
340
342
  latestSeq(sessionId: string): Promise<number>;
341
- appendKernelGenesis(sessionId: string, genesis: KernelOperationGenesis): Promise<KernelGenesisReceipt>;
342
- readKernelGenesis(sessionId: string, operationId: string): Promise<KernelOperationGenesis | undefined>;
343
- compareAndAppendKernelTransaction(sessionId: string, expectedTransactionHead: string, transaction: KernelTransaction): Promise<DurableAppendReceipt>;
344
- readKernelTransactions(sessionId: string, operationId: string, fromStepSeq?: number): Promise<KernelTransactionEntry[]>;
345
- kernelTransactionHead(sessionId: string, operationId: string): Promise<string | undefined>;
346
343
  }
344
+ /**
345
+ * File-backed `SessionLog`. Business appends are single-writer per session: safe within one
346
+ * instance, **not** across processes — that limitation is confined to the projection log now.
347
+ *
348
+ * The durable transaction capability is delegated to {@link FileKernelJournal}, which *is*
349
+ * cross-process atomic (spec Task 8b), so the two capabilities no longer share a file, a sequence
350
+ * space, or a concurrency story.
351
+ */
347
352
  export declare class FileSessionLog implements SessionLog {
348
353
  private dir;
349
354
  private seqCounters;
350
355
  private readonly appends;
356
+ /** The durable transaction capability, held rather than inherited (spec §9.1/§9.4). */
357
+ readonly kernelJournal: KernelJournal;
351
358
  constructor(dir: string);
352
359
  private path;
353
360
  private nextSeq;
@@ -357,11 +364,6 @@ export declare class FileSessionLog implements SessionLog {
357
364
  event: SessionEvent;
358
365
  }>>;
359
366
  latestSeq(sessionId: string): Promise<number>;
360
- appendKernelGenesis(sessionId: string, genesis: KernelOperationGenesis): Promise<KernelGenesisReceipt>;
361
- readKernelGenesis(sessionId: string, operationId: string): Promise<KernelOperationGenesis | undefined>;
362
- compareAndAppendKernelTransaction(sessionId: string, expectedTransactionHead: string, transaction: KernelTransaction): Promise<DurableAppendReceipt>;
363
- readKernelTransactions(sessionId: string, operationId: string, fromStepSeq?: number): Promise<KernelTransactionEntry[]>;
364
- kernelTransactionHead(sessionId: string, operationId: string): Promise<string | undefined>;
365
367
  private appendRecord;
366
368
  private readRecords;
367
369
  }
@@ -4,15 +4,18 @@ import { join } from "node:path";
4
4
  import { createInterface } from "node:readline";
5
5
  import { primitiveForKind } from "./kernel-event-log.js";
6
6
  import { KeyedSerialExecutor } from "./reliability.js";
7
- import { KernelLogConflictError, KernelLogIntegrityError, verifyKernelOperationGenesis, verifyKernelTransaction, verifyKernelTransactionSuccessor, } from "./kernel-transaction-log.js";
7
+ import { FileKernelJournal, InMemoryKernelJournal } from "./kernel-journal.js";
8
+ /**
9
+ * **Single-process dev/test implementation** of both capabilities (spec §9.4: one class may
10
+ * implement several capabilities; the *interfaces* stay separate). Its `KernelJournal` half is
11
+ * `InMemoryKernelJournal`, whose CAS is atomic within one process only.
12
+ */
8
13
  export class InMemorySessionLog {
9
14
  store = new Map();
15
+ /** Business event sequence space only — journal records number themselves by `step_seq`. */
10
16
  seqCounters = new Map();
11
- genesisStore = new Map();
12
- transactionStore = new Map();
13
- operationKey(sessionId, operationId) {
14
- return `${sessionId}\u0000${operationId}`;
15
- }
17
+ /** The durable transaction capability, held rather than inherited (spec §9.1/§9.4). */
18
+ kernelJournal = new InMemoryKernelJournal();
16
19
  nextSeq(sessionId) {
17
20
  const seq = this.seqCounters.get(sessionId) ?? 0;
18
21
  this.seqCounters.set(sessionId, seq + 1);
@@ -39,62 +42,25 @@ export class InMemorySessionLog {
39
42
  async latestSeq(sessionId) {
40
43
  return (this.seqCounters.get(sessionId) ?? 0) - 1;
41
44
  }
42
- async appendKernelGenesis(sessionId, genesis) {
43
- verifyKernelOperationGenesis(genesis);
44
- const operationKey = this.operationKey(sessionId, genesis.operation_id);
45
- const existing = this.genesisStore.get(operationKey);
46
- if (existing) {
47
- if (existing.genesis.genesis_digest !== genesis.genesis_digest) {
48
- throw new KernelLogConflictError("session already has a different kernel operation genesis");
49
- }
50
- return { log_seq: existing.log_seq, genesis_digest: genesis.genesis_digest };
51
- }
52
- const log_seq = this.nextSeq(sessionId);
53
- this.genesisStore.set(operationKey, { log_seq, genesis });
54
- return { log_seq, genesis_digest: genesis.genesis_digest };
55
- }
56
- async readKernelGenesis(sessionId, operationId) {
57
- return this.genesisStore.get(this.operationKey(sessionId, operationId))?.genesis;
58
- }
59
- async compareAndAppendKernelTransaction(sessionId, expectedTransactionHead, transaction) {
60
- verifyKernelTransaction(transaction);
61
- const operationKey = this.operationKey(sessionId, transaction.operation_id);
62
- const genesis = this.genesisStore.get(operationKey)?.genesis;
63
- if (!genesis)
64
- throw new KernelLogIntegrityError("kernel transaction requires a durable genesis");
65
- if (transaction.operation_id !== genesis.operation_id) {
66
- throw new KernelLogIntegrityError("kernel transaction operation_id does not match genesis");
67
- }
68
- const head = await this.kernelTransactionHead(sessionId, transaction.operation_id);
69
- if (head !== expectedTransactionHead || transaction.previous_transaction_digest !== head) {
70
- throw new KernelLogConflictError("kernel transaction head changed before compare-and-append");
71
- }
72
- const entries = this.transactionStore.get(operationKey) ?? [];
73
- verifyKernelTransactionSuccessor(entries.at(-1)?.transaction, transaction);
74
- const log_seq = this.nextSeq(sessionId);
75
- entries.push({ log_seq, transaction });
76
- this.transactionStore.set(operationKey, entries);
77
- return { log_seq, transaction_digest: transaction.transaction_digest };
78
- }
79
- async readKernelTransactions(sessionId, operationId, fromStepSeq = 1) {
80
- return (this.transactionStore.get(this.operationKey(sessionId, operationId)) ?? []).filter(entry => entry.transaction.step_seq >= fromStepSeq);
81
- }
82
- async kernelTransactionHead(sessionId, operationId) {
83
- const operationKey = this.operationKey(sessionId, operationId);
84
- const entries = this.transactionStore.get(operationKey) ?? [];
85
- return entries.at(-1)?.transaction.transaction_digest
86
- ?? this.genesisStore.get(operationKey)?.genesis.genesis_digest;
87
- }
88
45
  }
89
- // Single-writer per session. Safe for concurrent appends within one instance.
90
- // Cross-instance (multi-process) safety requires an external lock.
46
+ /**
47
+ * File-backed `SessionLog`. Business appends are single-writer per session: safe within one
48
+ * instance, **not** across processes — that limitation is confined to the projection log now.
49
+ *
50
+ * The durable transaction capability is delegated to {@link FileKernelJournal}, which *is*
51
+ * cross-process atomic (spec Task 8b), so the two capabilities no longer share a file, a sequence
52
+ * space, or a concurrency story.
53
+ */
91
54
  export class FileSessionLog {
92
55
  dir;
93
- // Lazy-initialized per-session counter. Avoids re-reading the file on every append.
56
+ // Lazy-initialized per-session counter for business events only.
94
57
  seqCounters = new Map();
95
58
  appends = new KeyedSerialExecutor();
59
+ /** The durable transaction capability, held rather than inherited (spec §9.1/§9.4). */
60
+ kernelJournal;
96
61
  constructor(dir) {
97
62
  this.dir = dir;
63
+ this.kernelJournal = new FileKernelJournal(join(dir, "kernel-journal"));
98
64
  }
99
65
  path(sessionId) {
100
66
  return join(this.dir, `${sessionId}.jsonl`);
@@ -130,82 +96,6 @@ export class FileSessionLog {
130
96
  const records = await this.readRecords(sessionId);
131
97
  return records.reduce((latest, record) => Math.max(latest, record.seq), -1);
132
98
  }
133
- async appendKernelGenesis(sessionId, genesis) {
134
- return this.appends.run(sessionId, async () => {
135
- verifyKernelOperationGenesis(genesis);
136
- const existing = (await this.readRecords(sessionId)).find((record) => "record_type" in record
137
- && record.record_type === "kernel_genesis"
138
- && record.genesis.operation_id === genesis.operation_id);
139
- if (existing) {
140
- if (existing.genesis.genesis_digest !== genesis.genesis_digest) {
141
- throw new KernelLogConflictError("session already has a different kernel operation genesis");
142
- }
143
- return { log_seq: existing.seq, genesis_digest: genesis.genesis_digest };
144
- }
145
- const log_seq = await this.nextSeq(sessionId);
146
- await this.appendRecord(sessionId, {
147
- seq: log_seq,
148
- record_type: "kernel_genesis",
149
- genesis,
150
- });
151
- return { log_seq, genesis_digest: genesis.genesis_digest };
152
- });
153
- }
154
- async readKernelGenesis(sessionId, operationId) {
155
- const record = (await this.readRecords(sessionId)).find((entry) => "record_type" in entry
156
- && entry.record_type === "kernel_genesis"
157
- && entry.genesis.operation_id === operationId);
158
- return record?.genesis;
159
- }
160
- async compareAndAppendKernelTransaction(sessionId, expectedTransactionHead, transaction) {
161
- return this.appends.run(sessionId, async () => {
162
- verifyKernelTransaction(transaction);
163
- const records = await this.readRecords(sessionId);
164
- const genesisRecord = records.find((record) => "record_type" in record
165
- && record.record_type === "kernel_genesis"
166
- && record.genesis.operation_id === transaction.operation_id);
167
- if (!genesisRecord)
168
- throw new KernelLogIntegrityError("kernel transaction requires a durable genesis");
169
- if (transaction.operation_id !== genesisRecord.genesis.operation_id) {
170
- throw new KernelLogIntegrityError("kernel transaction operation_id does not match genesis");
171
- }
172
- const transactions = records.filter((record) => "record_type" in record
173
- && record.record_type === "kernel_transaction"
174
- && record.transaction.operation_id === transaction.operation_id);
175
- const head = transactions.at(-1)?.transaction.transaction_digest
176
- ?? genesisRecord.genesis.genesis_digest;
177
- if (head !== expectedTransactionHead || transaction.previous_transaction_digest !== head) {
178
- throw new KernelLogConflictError("kernel transaction head changed before compare-and-append");
179
- }
180
- verifyKernelTransactionSuccessor(transactions.at(-1)?.transaction, transaction);
181
- const log_seq = await this.nextSeq(sessionId);
182
- await this.appendRecord(sessionId, {
183
- seq: log_seq,
184
- record_type: "kernel_transaction",
185
- transaction,
186
- });
187
- return { log_seq, transaction_digest: transaction.transaction_digest };
188
- });
189
- }
190
- async readKernelTransactions(sessionId, operationId, fromStepSeq = 1) {
191
- return (await this.readRecords(sessionId))
192
- .filter((record) => "record_type" in record
193
- && record.record_type === "kernel_transaction"
194
- && record.transaction.operation_id === operationId
195
- && record.transaction.step_seq >= fromStepSeq)
196
- .map(record => ({ log_seq: record.seq, transaction: record.transaction }));
197
- }
198
- async kernelTransactionHead(sessionId, operationId) {
199
- const records = await this.readRecords(sessionId);
200
- const transaction = records.filter((record) => "record_type" in record
201
- && record.record_type === "kernel_transaction"
202
- && record.transaction.operation_id === operationId).at(-1);
203
- if (transaction)
204
- return transaction.transaction.transaction_digest;
205
- return records.find((record) => "record_type" in record
206
- && record.record_type === "kernel_genesis"
207
- && record.genesis.operation_id === operationId)?.genesis.genesis_digest;
208
- }
209
99
  async appendRecord(sessionId, record) {
210
100
  await mkdir(this.dir, { recursive: true });
211
101
  const path = this.path(sessionId);
@@ -42,8 +42,7 @@ export declare function buildRunTerminalEvent(input: {
42
42
  }): Extract<SessionEvent, {
43
43
  kind: "run_terminal";
44
44
  }>;
45
- /** Build workflow_node_completed for persistence after a node finishes. W-1: carries the
46
- * result-borne control signals + output so resume replays control flow and re-seeds outputs. */
45
+ /** Build the audit projection emitted after a workflow node finishes. */
47
46
  export declare function buildWorkflowNodeCompletedEvent(input: {
48
47
  turn: number;
49
48
  agentId: string;
@@ -56,29 +55,7 @@ export declare function buildWorkflowNodeCompletedEvent(input: {
56
55
  }): Extract<SessionEvent, {
57
56
  kind: "workflow_node_completed";
58
57
  }>;
59
- /** One recovered node completion: the agent id plus its persisted control signals and output. */
60
- export interface RecoveredNodeOutcome {
61
- agentId: string;
62
- status: WorkflowNodeStatus;
63
- termination: string;
64
- classifyBranch?: string;
65
- tournamentWinner?: string;
66
- loopContinue?: boolean;
67
- output?: Message;
68
- }
69
- /**
70
- * Recover completed workflow node records from a session event stream. Scans for
71
- * workflow_node_completed events with termination "completed" and returns them WITH their
72
- * result-borne control signals (W-1) — resumeWorkflow lowers these to the kernel's
73
- * `resumed_outcomes` so a classifier re-prunes and a loop stop is honored, and re-seeds the
74
- * driver's outputs map from the persisted output text.
75
- */
76
- export declare function recoverWorkflowNodeOutcomes(events: Array<{
77
- seq: number;
78
- event: SessionEvent;
79
- }>): RecoveredNodeOutcome[];
80
- /** R3-1: build workflow_nodes_submitted for persistence after a runtime submission, so resume can
81
- * re-apply it. `nodes` is the kernel-shape (snake_case) submitted node array. */
58
+ /** Build the audit projection emitted after a runtime workflow submission. */
82
59
  export declare function buildWorkflowNodesSubmittedEvent(input: {
83
60
  turn: number;
84
61
  nodes: Record<string, unknown>[];
@@ -87,14 +64,3 @@ export declare function buildWorkflowNodesSubmittedEvent(input: {
87
64
  }): Extract<SessionEvent, {
88
65
  kind: "workflow_nodes_submitted";
89
66
  }>;
90
- /** R3-1: recover the runtime submission batches (in order) from a session event stream, to rebuild
91
- * `resumed_submissions` for resumeWorkflow so dynamically-appended nodes are reconstructed.
92
- * `submitters` is parallel to `submissions` (undefined = host/bootstrap submission). */
93
- export declare function recoverSubmittedWorkflowNodes(events: Array<{
94
- seq: number;
95
- event: SessionEvent;
96
- }>): {
97
- submissions: Record<string, unknown>[][];
98
- bases: number[];
99
- submitters: Array<string | undefined>;
100
- };
@@ -53,8 +53,7 @@ export function buildRunTerminalEvent(input) {
53
53
  total_tokens: Math.max(0, input.totalTokens),
54
54
  };
55
55
  }
56
- /** Build workflow_node_completed for persistence after a node finishes. W-1: carries the
57
- * result-borne control signals + output so resume replays control flow and re-seeds outputs. */
56
+ /** Build the audit projection emitted after a workflow node finishes. */
58
57
  export function buildWorkflowNodeCompletedEvent(input) {
59
58
  return {
60
59
  kind: "workflow_node_completed",
@@ -68,32 +67,7 @@ export function buildWorkflowNodeCompletedEvent(input) {
68
67
  ...(input.output ? { output: input.output } : {}),
69
68
  };
70
69
  }
71
- /**
72
- * Recover completed workflow node records from a session event stream. Scans for
73
- * workflow_node_completed events with termination "completed" and returns them WITH their
74
- * result-borne control signals (W-1) — resumeWorkflow lowers these to the kernel's
75
- * `resumed_outcomes` so a classifier re-prunes and a loop stop is honored, and re-seeds the
76
- * driver's outputs map from the persisted output text.
77
- */
78
- export function recoverWorkflowNodeOutcomes(events) {
79
- const completed = [];
80
- for (const { event } of events) {
81
- if (event.kind === "workflow_node_completed") {
82
- completed.push({
83
- agentId: event.agent_id,
84
- status: event.status,
85
- termination: event.termination,
86
- ...(event.classify_branch !== undefined ? { classifyBranch: event.classify_branch } : {}),
87
- ...(event.tournament_winner !== undefined ? { tournamentWinner: event.tournament_winner } : {}),
88
- ...(event.loop_continue !== undefined ? { loopContinue: event.loop_continue } : {}),
89
- ...(event.output !== undefined ? { output: event.output } : {}),
90
- });
91
- }
92
- }
93
- return completed;
94
- }
95
- /** R3-1: build workflow_nodes_submitted for persistence after a runtime submission, so resume can
96
- * re-apply it. `nodes` is the kernel-shape (snake_case) submitted node array. */
70
+ /** Build the audit projection emitted after a runtime workflow submission. */
97
71
  export function buildWorkflowNodesSubmittedEvent(input) {
98
72
  return {
99
73
  kind: "workflow_nodes_submitted",
@@ -103,22 +77,3 @@ export function buildWorkflowNodesSubmittedEvent(input) {
103
77
  ...(input.submitterAgentId !== undefined ? { submitter_agent_id: input.submitterAgentId } : {}),
104
78
  };
105
79
  }
106
- /** R3-1: recover the runtime submission batches (in order) from a session event stream, to rebuild
107
- * `resumed_submissions` for resumeWorkflow so dynamically-appended nodes are reconstructed.
108
- * `submitters` is parallel to `submissions` (undefined = host/bootstrap submission). */
109
- export function recoverSubmittedWorkflowNodes(events) {
110
- const submissions = [];
111
- const bases = [];
112
- const submitters = [];
113
- for (const { event } of events) {
114
- if (event.kind === "workflow_nodes_submitted") {
115
- submissions.push(event.nodes);
116
- submitters.push(event.submitter_agent_id);
117
- if (event.base_index === undefined) {
118
- throw new Error("workflow_nodes_submitted is missing required base_index");
119
- }
120
- bases.push(event.base_index);
121
- }
122
- }
123
- return { submissions, bases, submitters };
124
- }
@@ -46,5 +46,5 @@ export declare class SubAgentOrchestrator {
46
46
  }
47
47
  export declare const defaultSubAgentOrchestrator: SubAgentOrchestrator;
48
48
  export declare function attemptOutcomeToLoopResult(outcome: AttemptOutcome): LoopResult;
49
- /** Kernel spawn without an active parent run loop (harness / coordinator use). */
49
+ /** Canonical single-node root workflow for harness / coordinator use. */
50
50
  export declare function spawnStandalone(parentOpts: RuntimeOptions, parentSessionId: string, spec: AgentRunSpec, orchestrator?: SubAgentOrchestrator, contextInput?: string): Promise<SubAgentResult>;
@@ -1,7 +1,6 @@
1
- import { agentRunSpecToKernel, findSpawnProcessObservation, spawnObservationToManifest } from "../types/agent.js";
1
+ import { randomUUID } from "node:crypto";
2
2
  import { FilteredExecutionPlane } from "./filtered-plane.js";
3
3
  import { WorktreeExecutionPlane } from "./worktree-plane.js";
4
- import { durableKernelApply } from "./kernel-step.js";
5
4
  function terminationFromStatus(status) {
6
5
  const normalized = status.toLowerCase();
7
6
  if (normalized === "completed" ||
@@ -133,8 +132,6 @@ export class SubAgentOrchestrator {
133
132
  dreamStore: metaTools.has("memory") ? ctx.parentOpts.dreamStore : undefined,
134
133
  knowledgeSource: metaTools.has("knowledge") ? ctx.parentOpts.knowledgeSource : undefined,
135
134
  enablePlanTool: metaTools.has("update_plan") ? ctx.parentOpts.enablePlanTool : undefined,
136
- // M5 v2.1: a workflow node's `start_workflow` flattens to the parent kernel (no nested pivot).
137
- isWorkflowNode: ctx.isWorkflowNode,
138
135
  // Nested vehicle: the child joins the inherited runGroup for lineage/settlement only — it
139
136
  // must NOT re-reserve budget axes the parent already holds (that double-reserve squeezed the
140
137
  // child's grant to 0 and the kernel stripped its first-turn tools).
@@ -258,43 +255,48 @@ export function attemptOutcomeToLoopResult(outcome) {
258
255
  },
259
256
  };
260
257
  }
261
- /** Kernel spawn without an active parent run loop (harness / coordinator use). */
258
+ /** Canonical single-node root workflow for harness / coordinator use. */
262
259
  export async function spawnStandalone(parentOpts, parentSessionId, spec, orchestrator = defaultSubAgentOrchestrator, contextInput) {
263
- const kernel = (await import("../kernel.js")).getKernel();
264
- const runtime = new kernel.KernelRuntime({
265
- maxTokens: parentOpts.maxTokens,
266
- maxTurns: parentOpts.maxTurns ?? 25,
267
- timeoutMs: parentOpts.timeoutMs !== undefined ? BigInt(parentOpts.timeoutMs) : undefined,
268
- });
269
- const pending = [];
270
- await durableKernelApply(runtime, parentOpts.sessionLog, parentSessionId, pending, { kind: "start_run", task: { goal: "coordinator", criteria: [] } });
271
- const observations = await durableKernelApply(runtime, parentOpts.sessionLog, parentSessionId, pending, {
272
- kind: "spawn_sub_agent",
273
- spec: agentRunSpecToKernel(spec),
274
- parent_session_id: parentSessionId,
275
- });
276
- const spawned = findSpawnProcessObservation(observations);
277
- if (!spawned) {
278
- throw new Error("spawn_sub_agent did not emit agent_process_changed");
260
+ if (spec.tokenBudget !== undefined || spec.maxTurns !== undefined || spec.maxWallMs !== undefined) {
261
+ throw new Error("spawnStandalone cannot represent per-node resource caps under canonical ABI v3");
279
262
  }
280
- const manifest = spawnObservationToManifest(spawned, spec, parentSessionId);
281
- await parentOpts.sessionLog.append(parentSessionId, {
282
- kind: "agent_process_changed",
283
- turn: manifest.turn ?? 0,
284
- agent_id: manifest.agent_id,
285
- parent_session_id: manifest.parent_session_id,
286
- role: manifest.role,
287
- isolation: manifest.isolation,
288
- context_inheritance: manifest.context_inheritance,
289
- state: "running",
290
- permitted_capability_ids: manifest.permitted_capability_ids ?? [],
291
- });
292
- return orchestrator.run({
293
- parentOpts,
294
- parentSessionId,
295
- spec,
296
- manifest,
297
- sessionLog: parentOpts.sessionLog,
298
- ...(contextInput ? { contextInput } : {}),
263
+ const { RuntimeRunner } = await import("./runner.js");
264
+ let captured;
265
+ const bridge = {
266
+ async run(ctx) {
267
+ const child = await orchestrator.run({
268
+ ...ctx,
269
+ parentSessionId,
270
+ spec,
271
+ manifest: {
272
+ ...ctx.manifest,
273
+ agent_id: spec.identity.agentId,
274
+ parent_session_id: parentSessionId,
275
+ },
276
+ ...(contextInput ? { contextInput } : {}),
277
+ });
278
+ captured = child;
279
+ return { ...child, agentId: ctx.manifest.agent_id };
280
+ },
281
+ };
282
+ const runner = new RuntimeRunner({
283
+ ...parentOpts,
284
+ subAgentOrchestrator: bridge,
285
+ nestedGroupVehicle: true,
299
286
  });
287
+ const outcome = await runner.runWorkflow({
288
+ nodes: [{
289
+ task: spec.goal,
290
+ role: spec.role,
291
+ isolation: spec.isolation,
292
+ ...(spec.modelHint ? { modelHint: spec.modelHint } : {}),
293
+ }],
294
+ }, { sessionId: `${spec.identity.sessionId}:spawn-root:${randomUUID()}` });
295
+ if (outcome.rejection) {
296
+ throw new Error(`canonical standalone spawn rejected: ${outcome.rejection.reason}`);
297
+ }
298
+ if (!captured) {
299
+ throw new Error("canonical standalone spawn completed without a child result");
300
+ }
301
+ return captured;
300
302
  }
@@ -41,6 +41,15 @@ export interface AgentRunSpec {
41
41
  /** ③ loop-agent rounds: presence makes this run ONE round of a paced loop (gates the
42
42
  * kernel `pace` meta-tool and arms the pacing trap). */
43
43
  loopRound?: LoopRoundSpec;
44
+ /** Exposure baseline — the PRE-ACTIVATION tool surface *under* the `capabilityFilter` ceiling.
45
+ * The ceiling bounds what this run may EVER expose; the baseline selects which of those are
46
+ * advertised before any skill activates, so `exposed = meta ∪ ((baseline ∪ stableCore ∪
47
+ * ⋃ activeSkills.allowed_tools) ∩ ceiling)`. That makes narrow→wide progressive disclosure
48
+ * expressible: a tool can be reachable after `skill(x)` without being advertised beforehand.
49
+ * Absent ⇒ legacy behavior (ceiling + errs-open skill narrowing). `[]` is meaningful and
50
+ * distinct from absent: the minimal surface (meta-tools + stable-core only). Entries outside
51
+ * the ceiling silently intersect away. Lowered from `RuntimeOptions.baselineToolIds`. */
52
+ exposureBaseline?: string[];
44
53
  /** M1/G3: per-agent model preference (e.g. "opus"/"sonnet"/"haiku"); the host resolves it to a
45
54
  * provider via `RuntimeOptions.providerFor`. Host-side routing only — not sent to the kernel. */
46
55
  modelHint?: string;
@@ -73,12 +82,6 @@ export interface AgentProcessChangedObservation {
73
82
  permitted_capability_ids?: string[];
74
83
  result_termination?: string;
75
84
  }
76
- /** Map kernel spawn observation → host manifest. */
77
- export declare function spawnObservationToManifest(obs: AgentProcessChangedObservation | Record<string, unknown>, spec: AgentRunSpec, parentSessionId: string): AgentProcessChangedObservation;
78
- export declare function findSpawnProcessObservation(observations: Array<{
79
- kind: string;
80
- agent_id?: string;
81
- }>): AgentProcessChangedObservation | undefined;
82
85
  export interface LoopResult {
83
86
  termination: TerminationReason | string;
84
87
  finalMessage?: Message;
@@ -137,6 +140,18 @@ export declare function milestoneCheckResultToKernel(result: MilestoneCheckResul
137
140
  export declare function subAgentResultToKernel(result: SubAgentResult): Record<string, unknown>;
138
141
  export declare function milestoneCheckPass(phaseId: string): MilestoneCheckResult;
139
142
  export declare function milestoneCheckFail(phaseId: string, reason: string): MilestoneCheckResult;
143
+ /**
144
+ * R-B27: the conservative resolution the runner feeds back when an `evaluate_milestone` effect
145
+ * arrives with no phase `verifier` and no host `onMilestoneEvaluate` hook. Nothing can attest the
146
+ * phase, but the kernel is already holding the effect in its pending table — returning without an
147
+ * answer leaves a dangling effect that a logical-checkpoint recovery cannot resolve. The current
148
+ * `MilestoneResult` wire has no error field, so "could not be verified" is expressed with the
149
+ * shape the wire does have: `passed: false` (the phase does not advance — fail-closed).
150
+ *
151
+ * The string is part of the cross-SDK contract: WASM (`wasm/src/runtime/types/agent.ts`),
152
+ * Python (`deepstrike/types/agent.py`) and Rust must feed back the byte-identical reason.
153
+ */
154
+ export declare const MILESTONE_UNVERIFIED_REASON = "milestone unverified: no verifier configured and no host evaluation hook (fail-closed)";
140
155
  /** A task for a workflow node: a full object, or a bare goal string. */
141
156
  export type WorkflowTaskSpec = {
142
157
  goal: string;
@@ -247,6 +262,8 @@ export interface WorkflowSpawnInfo {
247
262
  /** The dependency agent ids for EVERY dependent node (W-N2: a DAG edge carries data). A reduce
248
263
  * node's registered function consumes them; every other node gets its deps' outputs in context. */
249
264
  input_agent_ids?: string[];
265
+ /** Dependency outputs projected by canonical core so a post-crash launch keeps its data edges. */
266
+ dependency_outputs?: Record<string, string>;
250
267
  /** A#2: present only for a tournament *judge* spawn — the two entrant agent ids whose produced
251
268
  * outputs this judge compares. The runner looks them up and reports the winner as `tournamentWinner`. */
252
269
  judge_match?: {
@@ -269,10 +286,10 @@ export interface WorkflowSpawnInfo {
269
286
  /** G4 budget-as-signal: the workflow's remaining headroom under the active quota, carried on the
270
287
  * `workflow_batch_spawned` observation so a coordinator node can scale its next submission. */
271
288
  export interface WorkflowBudget {
272
- nodes_used: number;
289
+ nodes_used?: number;
273
290
  nodes_max?: number;
274
291
  nodes_remaining?: number;
275
- running_subagents: number;
292
+ running_subagents?: number;
276
293
  max_concurrent_subagents?: number;
277
294
  concurrency_remaining?: number;
278
295
  /** M4/G5 token headroom: cumulative tokens used, the run-level cap, and tokens remaining before the
@@ -280,25 +297,20 @@ export interface WorkflowBudget {
280
297
  tokens_used?: number;
281
298
  tokens_max?: number;
282
299
  tokens_remaining?: number;
300
+ /** Canonical ABI v3 publishes immutable caps rather than a host-authored remaining snapshot. */
301
+ max_total_tokens?: string | number;
302
+ max_turns?: number;
303
+ max_concurrency?: number;
283
304
  }
284
305
  /** G4: a concise, human-readable budget note appended to a coordinator node's goal, so its agent can
285
306
  * size a `submit_workflow_nodes` batch to what is actually available. Returns "" when nothing is
286
307
  * bounded (no quota ⇒ no signal). */
287
308
  export declare function workflowBudgetNote(budget: WorkflowBudget | undefined): string;
288
- /** Map one host `WorkflowNodeSpec` to its snake_case kernel JSON. Shared by `load_workflow` (the
309
+ /** Map one host `WorkflowNodeSpec` to its snake_case canonical JSON. Shared by the workflow root (the
289
310
  * whole spec) and `submit_workflow_nodes` (R3-1 runtime append) so the two encodings never drift. */
290
311
  export declare function workflowNodeSpecToKernel(n: WorkflowNodeSpec): Record<string, unknown>;
291
- /** Map a host `WorkflowSpec` to the snake_case kernel JSON (`load_workflow.spec`). */
312
+ /** Map a host `WorkflowSpec` to the canonical workflow-root JSON. */
292
313
  export declare function workflowSpecToKernel(spec: WorkflowSpec): Record<string, unknown>;
293
- /** R3-1: map a batch of host nodes to the `submit_workflow_nodes` kernel event body. G1: pass
294
- * `submitterAgentId` (the node that requested the append) so the kernel can enforce no-privilege-
295
- * escalation — a quarantined submitter's nodes are coerced to quarantined. Omitted ⇒ no coercion. */
296
- export declare function submitWorkflowNodesToKernel(nodes: WorkflowNodeSpec[], submitterAgentId?: string): Record<string, unknown>;
297
- /** M5/G1: map an agent-authored spec to the `submit_workflow` kernel event body (the agent-reachable
298
- * `Syscall::LoadWorkflow`). The kernel bootstraps the DAG when none is active, else flattens onto it.
299
- * `parentSessionId` seeds child session ids on bootstrap; `submitterAgentId` carries G1 trust coercion
300
- * on the flatten case (a quarantined author's nodes are coerced quarantined). */
301
- export declare function submitWorkflowToKernel(spec: WorkflowSpec, parentSessionId: string, submitterAgentId?: string): Record<string, unknown>;
302
314
  /** R3-1: the tool a workflow-coordinator node's agent calls to append work to the running DAG
303
315
  * (true loop-until-done / dynamic fan-out). Give it to nodes meant to fan out; the runner intercepts
304
316
  * the call and routes the nodes to the parent kernel (the child's own kernel holds no workflow). */