@deepstrike/sdk 0.2.50 → 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.
- package/README.md +83 -60
- package/dist/index.d.ts +5 -7
- package/dist/index.js +3 -3
- package/dist/kernel.d.ts +61 -31
- package/dist/runtime/canonical-kernel-step.d.ts +143 -0
- package/dist/runtime/canonical-kernel-step.js +1444 -0
- package/dist/runtime/execution-plane.d.ts +0 -3
- package/dist/runtime/execution-plane.js +0 -24
- package/dist/runtime/facade.js +3 -0
- package/dist/runtime/kernel-event-log.js +7 -13
- package/dist/runtime/kernel-journal.d.ts +264 -0
- package/dist/runtime/kernel-journal.js +741 -0
- package/dist/runtime/kernel-primitives-dashboard.d.ts +0 -2
- package/dist/runtime/kernel-primitives-dashboard.js +1 -8
- package/dist/runtime/kernel-step.d.ts +29 -109
- package/dist/runtime/kernel-step.js +47 -317
- package/dist/runtime/os-snapshot.d.ts +2 -2
- package/dist/runtime/os-snapshot.js +2 -6
- package/dist/runtime/payload-store.d.ts +16 -0
- package/dist/runtime/payload-store.js +80 -0
- package/dist/runtime/runner.d.ts +31 -114
- package/dist/runtime/runner.js +689 -774
- package/dist/runtime/session-log.d.ts +34 -32
- package/dist/runtime/session-log.js +21 -131
- package/dist/runtime/session-repair.d.ts +2 -36
- package/dist/runtime/session-repair.js +2 -47
- package/dist/runtime/sub-agent-orchestrator.d.ts +1 -1
- package/dist/runtime/sub-agent-orchestrator.js +42 -40
- package/dist/types/agent.d.ts +22 -19
- package/dist/types/agent.js +26 -42
- package/dist/workflow/public.d.ts +1 -1
- package/dist/workflow/public.js +1 -1
- package/package.json +2 -2
- package/dist/runtime/kernel-rebuild.d.ts +0 -13
- package/dist/runtime/kernel-rebuild.js +0 -75
- package/dist/runtime/kernel-transaction-log.d.ts +0 -61
- package/dist/runtime/kernel-transaction-log.js +0 -149
- package/dist/runtime/large-result-spool.d.ts +0 -93
- 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 {
|
|
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
|
-
|
|
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
|
-
|
|
332
|
-
|
|
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 {
|
|
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
|
-
|
|
12
|
-
|
|
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
|
-
|
|
90
|
-
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
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
|
|
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
|
-
/**
|
|
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 {
|
|
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
|
-
/**
|
|
258
|
+
/** Canonical single-node root workflow for harness / coordinator use. */
|
|
262
259
|
export async function spawnStandalone(parentOpts, parentSessionId, spec, orchestrator = defaultSubAgentOrchestrator, contextInput) {
|
|
263
|
-
|
|
264
|
-
|
|
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
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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
|
}
|
package/dist/types/agent.d.ts
CHANGED
|
@@ -82,12 +82,6 @@ export interface AgentProcessChangedObservation {
|
|
|
82
82
|
permitted_capability_ids?: string[];
|
|
83
83
|
result_termination?: string;
|
|
84
84
|
}
|
|
85
|
-
/** Map kernel spawn observation → host manifest. */
|
|
86
|
-
export declare function spawnObservationToManifest(obs: AgentProcessChangedObservation | Record<string, unknown>, spec: AgentRunSpec, parentSessionId: string): AgentProcessChangedObservation;
|
|
87
|
-
export declare function findSpawnProcessObservation(observations: Array<{
|
|
88
|
-
kind: string;
|
|
89
|
-
agent_id?: string;
|
|
90
|
-
}>): AgentProcessChangedObservation | undefined;
|
|
91
85
|
export interface LoopResult {
|
|
92
86
|
termination: TerminationReason | string;
|
|
93
87
|
finalMessage?: Message;
|
|
@@ -146,6 +140,18 @@ export declare function milestoneCheckResultToKernel(result: MilestoneCheckResul
|
|
|
146
140
|
export declare function subAgentResultToKernel(result: SubAgentResult): Record<string, unknown>;
|
|
147
141
|
export declare function milestoneCheckPass(phaseId: string): MilestoneCheckResult;
|
|
148
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)";
|
|
149
155
|
/** A task for a workflow node: a full object, or a bare goal string. */
|
|
150
156
|
export type WorkflowTaskSpec = {
|
|
151
157
|
goal: string;
|
|
@@ -256,6 +262,8 @@ export interface WorkflowSpawnInfo {
|
|
|
256
262
|
/** The dependency agent ids for EVERY dependent node (W-N2: a DAG edge carries data). A reduce
|
|
257
263
|
* node's registered function consumes them; every other node gets its deps' outputs in context. */
|
|
258
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>;
|
|
259
267
|
/** A#2: present only for a tournament *judge* spawn — the two entrant agent ids whose produced
|
|
260
268
|
* outputs this judge compares. The runner looks them up and reports the winner as `tournamentWinner`. */
|
|
261
269
|
judge_match?: {
|
|
@@ -278,10 +286,10 @@ export interface WorkflowSpawnInfo {
|
|
|
278
286
|
/** G4 budget-as-signal: the workflow's remaining headroom under the active quota, carried on the
|
|
279
287
|
* `workflow_batch_spawned` observation so a coordinator node can scale its next submission. */
|
|
280
288
|
export interface WorkflowBudget {
|
|
281
|
-
nodes_used
|
|
289
|
+
nodes_used?: number;
|
|
282
290
|
nodes_max?: number;
|
|
283
291
|
nodes_remaining?: number;
|
|
284
|
-
running_subagents
|
|
292
|
+
running_subagents?: number;
|
|
285
293
|
max_concurrent_subagents?: number;
|
|
286
294
|
concurrency_remaining?: number;
|
|
287
295
|
/** M4/G5 token headroom: cumulative tokens used, the run-level cap, and tokens remaining before the
|
|
@@ -289,25 +297,20 @@ export interface WorkflowBudget {
|
|
|
289
297
|
tokens_used?: number;
|
|
290
298
|
tokens_max?: number;
|
|
291
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;
|
|
292
304
|
}
|
|
293
305
|
/** G4: a concise, human-readable budget note appended to a coordinator node's goal, so its agent can
|
|
294
306
|
* size a `submit_workflow_nodes` batch to what is actually available. Returns "" when nothing is
|
|
295
307
|
* bounded (no quota ⇒ no signal). */
|
|
296
308
|
export declare function workflowBudgetNote(budget: WorkflowBudget | undefined): string;
|
|
297
|
-
/** Map one host `WorkflowNodeSpec` to its snake_case
|
|
309
|
+
/** Map one host `WorkflowNodeSpec` to its snake_case canonical JSON. Shared by the workflow root (the
|
|
298
310
|
* whole spec) and `submit_workflow_nodes` (R3-1 runtime append) so the two encodings never drift. */
|
|
299
311
|
export declare function workflowNodeSpecToKernel(n: WorkflowNodeSpec): Record<string, unknown>;
|
|
300
|
-
/** Map a host `WorkflowSpec` to the
|
|
312
|
+
/** Map a host `WorkflowSpec` to the canonical workflow-root JSON. */
|
|
301
313
|
export declare function workflowSpecToKernel(spec: WorkflowSpec): Record<string, unknown>;
|
|
302
|
-
/** R3-1: map a batch of host nodes to the `submit_workflow_nodes` kernel event body. G1: pass
|
|
303
|
-
* `submitterAgentId` (the node that requested the append) so the kernel can enforce no-privilege-
|
|
304
|
-
* escalation — a quarantined submitter's nodes are coerced to quarantined. Omitted ⇒ no coercion. */
|
|
305
|
-
export declare function submitWorkflowNodesToKernel(nodes: WorkflowNodeSpec[], submitterAgentId?: string): Record<string, unknown>;
|
|
306
|
-
/** M5/G1: map an agent-authored spec to the `submit_workflow` kernel event body (the agent-reachable
|
|
307
|
-
* `Syscall::LoadWorkflow`). The kernel bootstraps the DAG when none is active, else flattens onto it.
|
|
308
|
-
* `parentSessionId` seeds child session ids on bootstrap; `submitterAgentId` carries G1 trust coercion
|
|
309
|
-
* on the flatten case (a quarantined author's nodes are coerced quarantined). */
|
|
310
|
-
export declare function submitWorkflowToKernel(spec: WorkflowSpec, parentSessionId: string, submitterAgentId?: string): Record<string, unknown>;
|
|
311
314
|
/** R3-1: the tool a workflow-coordinator node's agent calls to append work to the running DAG
|
|
312
315
|
* (true loop-until-done / dynamic fan-out). Give it to nodes meant to fan out; the runner intercepts
|
|
313
316
|
* the call and routes the nodes to the parent kernel (the child's own kernel holds no workflow). */
|