@deepstrike/wasm 0.2.35 → 0.2.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/runtime/facade.js +11 -7
- package/dist/runtime/kernel-event-log.d.ts +0 -6
- package/dist/runtime/kernel-event-log.js +38 -62
- package/dist/runtime/kernel-step.d.ts +9 -0
- package/dist/runtime/kernel-step.js +12 -0
- package/dist/runtime/large-result-spool.d.ts +7 -0
- package/dist/runtime/large-result-spool.js +25 -0
- package/dist/runtime/os-snapshot.d.ts +0 -1
- package/dist/runtime/os-snapshot.js +0 -19
- package/dist/runtime/runner.d.ts +103 -8
- package/dist/runtime/runner.js +415 -100
- package/dist/runtime/session-log.d.ts +15 -54
- package/dist/runtime/session-repair.d.ts +29 -6
- package/dist/runtime/session-repair.js +37 -8
- package/dist/runtime/sub-agent-orchestrator.d.ts +7 -0
- package/dist/runtime/sub-agent-orchestrator.js +39 -4
- package/dist/runtime/types/agent.d.ts +46 -2
- package/dist/runtime/types/agent.js +54 -1
- package/dist/runtime/workflow-control-flow.d.ts +10 -2
- package/dist/runtime/workflow-control-flow.js +27 -6
- package/dist/types.d.ts +2 -1
- package/package.json +2 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ProviderReplay, ToolCall, ToolErrorKind } from "../types.js";
|
|
2
|
-
import type {
|
|
2
|
+
import type { KernelPrimitive } from "./kernel-event-log.js";
|
|
3
3
|
export type RollbackReason = {
|
|
4
4
|
kind: "fatal_tool_error";
|
|
5
5
|
tool_name: string;
|
|
@@ -74,8 +74,6 @@ export type SessionEvent = {
|
|
|
74
74
|
} | {
|
|
75
75
|
kind: "compressed";
|
|
76
76
|
turn: number;
|
|
77
|
-
category?: KernelEventCategory;
|
|
78
|
-
primitive?: KernelPrimitive;
|
|
79
77
|
archived_seq_range: [number, number];
|
|
80
78
|
action?: "snip_compact" | "micro_compact" | "context_collapse" | "auto_compact";
|
|
81
79
|
summary?: string;
|
|
@@ -85,8 +83,6 @@ export type SessionEvent = {
|
|
|
85
83
|
} | {
|
|
86
84
|
kind: "page_out";
|
|
87
85
|
turn: number;
|
|
88
|
-
category?: KernelEventCategory;
|
|
89
|
-
primitive?: KernelPrimitive;
|
|
90
86
|
action?: "snip_compact" | "micro_compact" | "context_collapse" | "auto_compact";
|
|
91
87
|
summary?: string;
|
|
92
88
|
tier_hint?: string;
|
|
@@ -94,14 +90,10 @@ export type SessionEvent = {
|
|
|
94
90
|
} | {
|
|
95
91
|
kind: "page_in";
|
|
96
92
|
turn: number;
|
|
97
|
-
category?: KernelEventCategory;
|
|
98
|
-
primitive?: KernelPrimitive;
|
|
99
93
|
entry_count: number;
|
|
100
94
|
} | {
|
|
101
95
|
kind: "large_result_spooled";
|
|
102
96
|
turn: number;
|
|
103
|
-
category?: KernelEventCategory;
|
|
104
|
-
primitive?: KernelPrimitive;
|
|
105
97
|
call_id: string;
|
|
106
98
|
tool: string;
|
|
107
99
|
original_size: number;
|
|
@@ -110,15 +102,11 @@ export type SessionEvent = {
|
|
|
110
102
|
} | {
|
|
111
103
|
kind: "rollbacked";
|
|
112
104
|
turn: number;
|
|
113
|
-
category?: KernelEventCategory;
|
|
114
|
-
primitive?: KernelPrimitive;
|
|
115
105
|
checkpoint_history_len: number;
|
|
116
106
|
reason?: RollbackReason;
|
|
117
107
|
} | {
|
|
118
108
|
kind: "capability_changed";
|
|
119
109
|
turn: number;
|
|
120
|
-
category?: KernelEventCategory;
|
|
121
|
-
primitive?: KernelPrimitive;
|
|
122
110
|
added: string[];
|
|
123
111
|
removed: string[];
|
|
124
112
|
change_kind?: string;
|
|
@@ -129,78 +117,51 @@ export type SessionEvent = {
|
|
|
129
117
|
} | {
|
|
130
118
|
kind: "context_renewed";
|
|
131
119
|
turn: number;
|
|
132
|
-
category?: KernelEventCategory;
|
|
133
|
-
primitive?: KernelPrimitive;
|
|
134
120
|
sprint: number;
|
|
135
121
|
handoff_ref: string;
|
|
136
122
|
} | {
|
|
137
123
|
kind: "suspended";
|
|
138
124
|
turn: number;
|
|
139
|
-
category?: KernelEventCategory;
|
|
140
|
-
primitive?: KernelPrimitive;
|
|
141
125
|
reason: string;
|
|
142
126
|
pending_calls?: string[];
|
|
143
127
|
} | {
|
|
144
128
|
kind: "resumed";
|
|
145
129
|
turn: number;
|
|
146
|
-
category?: KernelEventCategory;
|
|
147
|
-
primitive?: KernelPrimitive;
|
|
148
130
|
approved?: string[];
|
|
149
131
|
denied?: string[];
|
|
150
132
|
} | {
|
|
151
133
|
kind: "tool_gated";
|
|
152
134
|
turn: number;
|
|
153
|
-
category?: KernelEventCategory;
|
|
154
|
-
primitive?: KernelPrimitive;
|
|
155
135
|
call_id: string;
|
|
156
136
|
tool: string;
|
|
157
137
|
reason: string;
|
|
158
138
|
} | {
|
|
159
139
|
kind: "signal_disposed";
|
|
160
140
|
turn: number;
|
|
161
|
-
category?: KernelEventCategory;
|
|
162
|
-
primitive?: KernelPrimitive;
|
|
163
141
|
signal_id: string;
|
|
164
142
|
disposition: string;
|
|
165
143
|
queue_depth: number;
|
|
166
144
|
} | {
|
|
167
145
|
kind: "budget_exceeded";
|
|
168
146
|
turn: number;
|
|
169
|
-
category?: KernelEventCategory;
|
|
170
|
-
primitive?: KernelPrimitive;
|
|
171
147
|
budget: string;
|
|
172
148
|
} | {
|
|
173
149
|
kind: "milestone_advanced";
|
|
174
150
|
turn: number;
|
|
175
|
-
category?: KernelEventCategory;
|
|
176
|
-
primitive?: KernelPrimitive;
|
|
177
151
|
phase_id: string;
|
|
178
152
|
capabilities_unlocked: string[];
|
|
179
153
|
} | {
|
|
180
154
|
kind: "milestone_blocked";
|
|
181
155
|
turn: number;
|
|
182
|
-
category?: KernelEventCategory;
|
|
183
|
-
primitive?: KernelPrimitive;
|
|
184
156
|
phase_id: string;
|
|
185
157
|
reason: string;
|
|
186
|
-
} | {
|
|
187
|
-
kind: "milestone_evidence";
|
|
188
|
-
turn: number;
|
|
189
|
-
category?: KernelEventCategory;
|
|
190
|
-
primitive?: KernelPrimitive;
|
|
191
|
-
phase_id: string;
|
|
192
|
-
evidence: string[];
|
|
193
158
|
} | {
|
|
194
159
|
kind: "checkpoint_taken";
|
|
195
160
|
turn: number;
|
|
196
|
-
category?: KernelEventCategory;
|
|
197
|
-
primitive?: KernelPrimitive;
|
|
198
161
|
history_len: number;
|
|
199
162
|
} | {
|
|
200
163
|
kind: "agent_process_changed";
|
|
201
164
|
turn: number;
|
|
202
|
-
category?: KernelEventCategory;
|
|
203
|
-
primitive?: KernelPrimitive;
|
|
204
165
|
agent_id: string;
|
|
205
166
|
parent_session_id: string;
|
|
206
167
|
role: string;
|
|
@@ -212,52 +173,52 @@ export type SessionEvent = {
|
|
|
212
173
|
} | {
|
|
213
174
|
kind: "memory_written";
|
|
214
175
|
turn: number;
|
|
215
|
-
category?: KernelEventCategory;
|
|
216
|
-
primitive?: KernelPrimitive;
|
|
217
176
|
memory_id: string;
|
|
218
177
|
memory_kind: string;
|
|
219
178
|
size_bytes: number;
|
|
220
179
|
} | {
|
|
221
180
|
kind: "memory_queried";
|
|
222
181
|
turn: number;
|
|
223
|
-
category?: KernelEventCategory;
|
|
224
|
-
primitive?: KernelPrimitive;
|
|
225
182
|
query_context: string;
|
|
226
183
|
requested_k: number;
|
|
227
184
|
requires_async_response: boolean;
|
|
228
185
|
} | {
|
|
229
186
|
kind: "memory_validation_failed";
|
|
230
187
|
turn: number;
|
|
231
|
-
category?: KernelEventCategory;
|
|
232
|
-
primitive?: KernelPrimitive;
|
|
233
188
|
memory_id: string;
|
|
234
189
|
error: string;
|
|
235
190
|
} | {
|
|
236
191
|
kind: "workflow_node_completed";
|
|
237
192
|
turn: number;
|
|
238
|
-
category?: KernelEventCategory;
|
|
239
|
-
primitive?: KernelPrimitive;
|
|
240
193
|
agent_id: string;
|
|
241
194
|
termination: string;
|
|
195
|
+
/** W-1: result-borne control signals, persisted so resume replays control flow faithfully —
|
|
196
|
+
* a classifier re-prunes its rejected branches, a recorded loop stop is honored. */
|
|
197
|
+
classify_branch?: string;
|
|
198
|
+
tournament_winner?: string;
|
|
199
|
+
loop_continue?: boolean;
|
|
200
|
+
/** W-1: the node's final output text — resume re-seeds the driver's outputs map from it so
|
|
201
|
+
* post-resume reduce/judge/dependent nodes still see their dependencies' outputs. */
|
|
202
|
+
output?: string;
|
|
242
203
|
} | {
|
|
243
204
|
kind: "workflow_nodes_submitted";
|
|
244
205
|
turn: number;
|
|
245
|
-
category?: KernelEventCategory;
|
|
246
|
-
primitive?: KernelPrimitive;
|
|
247
206
|
/** Kernel-shape (snake_case) submitted node specs — persisted so resume can re-apply them. */
|
|
248
207
|
nodes: Record<string, unknown>[];
|
|
208
|
+
/** R3-1: graph base index the batch was appended at (from the kernel's
|
|
209
|
+
* WorkflowNodesSubmitted observation) — lets resume rebuild exact indices. */
|
|
210
|
+
base_index?: number;
|
|
211
|
+
/** W-N3: the submitting node's agent id (absent = host/bootstrap). Resume DROPS batches whose
|
|
212
|
+
* submitter re-runs — it will re-submit — instead of duplicating their nodes. */
|
|
213
|
+
submitter_agent_id?: string;
|
|
249
214
|
} | {
|
|
250
215
|
kind: "workflow_batch_spawned";
|
|
251
216
|
turn: number;
|
|
252
|
-
category?: KernelEventCategory;
|
|
253
|
-
primitive?: KernelPrimitive;
|
|
254
217
|
node_count: number;
|
|
255
218
|
node_ids: string[];
|
|
256
219
|
} | {
|
|
257
220
|
kind: "workflow_completed";
|
|
258
221
|
turn: number;
|
|
259
|
-
category?: KernelEventCategory;
|
|
260
|
-
primitive?: KernelPrimitive;
|
|
261
222
|
completed: string[];
|
|
262
223
|
failed: string[];
|
|
263
224
|
total_nodes: number;
|
|
@@ -36,33 +36,56 @@ export declare function buildRunTerminalEvent(input: {
|
|
|
36
36
|
}): Extract<SessionEvent, {
|
|
37
37
|
kind: "run_terminal";
|
|
38
38
|
}>;
|
|
39
|
+
/** Build workflow_node_completed for persistence after a node finishes. W-1: carries the
|
|
40
|
+
* result-borne control signals + output so resume replays control flow and re-seeds outputs. */
|
|
39
41
|
export declare function buildWorkflowNodeCompletedEvent(input: {
|
|
40
42
|
turn: number;
|
|
41
43
|
agentId: string;
|
|
42
44
|
termination: string;
|
|
45
|
+
classifyBranch?: string;
|
|
46
|
+
tournamentWinner?: string;
|
|
47
|
+
loopContinue?: boolean;
|
|
48
|
+
output?: string;
|
|
43
49
|
}): Extract<SessionEvent, {
|
|
44
50
|
kind: "workflow_node_completed";
|
|
45
51
|
}>;
|
|
52
|
+
/** One recovered node completion: the agent id plus its persisted control signals and output. */
|
|
53
|
+
export interface RecoveredNodeCompletion {
|
|
54
|
+
agentId: string;
|
|
55
|
+
classifyBranch?: string;
|
|
56
|
+
tournamentWinner?: string;
|
|
57
|
+
loopContinue?: boolean;
|
|
58
|
+
output?: string;
|
|
59
|
+
}
|
|
46
60
|
/**
|
|
47
|
-
* Recover completed workflow node
|
|
48
|
-
*
|
|
49
|
-
*
|
|
61
|
+
* Recover completed workflow node records from a session event stream. Scans for
|
|
62
|
+
* workflow_node_completed events with termination "completed" and returns them WITH their
|
|
63
|
+
* result-borne control signals (W-1) — resumeWorkflow lowers these to the kernel's
|
|
64
|
+
* `resumed_results` so a classifier re-prunes and a loop stop is honored, and re-seeds the
|
|
65
|
+
* driver's outputs map from the persisted output text.
|
|
50
66
|
*/
|
|
51
67
|
export declare function recoverCompletedWorkflowNodes(events: Array<{
|
|
52
68
|
seq: number;
|
|
53
69
|
event: SessionEvent;
|
|
54
|
-
}>):
|
|
70
|
+
}>): RecoveredNodeCompletion[];
|
|
55
71
|
/** R3-1: build workflow_nodes_submitted for persistence after a runtime submission, so resume can
|
|
56
72
|
* re-apply it. `nodes` is the kernel-shape (snake_case) submitted node array. */
|
|
57
73
|
export declare function buildWorkflowNodesSubmittedEvent(input: {
|
|
58
74
|
turn: number;
|
|
59
75
|
nodes: Record<string, unknown>[];
|
|
76
|
+
baseIndex?: number;
|
|
77
|
+
submitterAgentId?: string;
|
|
60
78
|
}): Extract<SessionEvent, {
|
|
61
79
|
kind: "workflow_nodes_submitted";
|
|
62
80
|
}>;
|
|
63
81
|
/** R3-1: recover the runtime submission batches (in order) to rebuild `resumed_submissions` for
|
|
64
|
-
* resumeWorkflow, so dynamically-appended nodes are reconstructed.
|
|
82
|
+
* resumeWorkflow, so dynamically-appended nodes are reconstructed.
|
|
83
|
+
* `submitters` is parallel to `submissions` (undefined = host/bootstrap submission). */
|
|
65
84
|
export declare function recoverSubmittedWorkflowNodes(events: Array<{
|
|
66
85
|
seq: number;
|
|
67
86
|
event: SessionEvent;
|
|
68
|
-
}>):
|
|
87
|
+
}>): {
|
|
88
|
+
submissions: Record<string, unknown>[][];
|
|
89
|
+
bases: number[];
|
|
90
|
+
submitters: Array<string | undefined>;
|
|
91
|
+
};
|
|
@@ -48,24 +48,38 @@ export function buildRunTerminalEvent(input) {
|
|
|
48
48
|
total_tokens: Math.max(0, input.totalTokens),
|
|
49
49
|
};
|
|
50
50
|
}
|
|
51
|
+
/** Build workflow_node_completed for persistence after a node finishes. W-1: carries the
|
|
52
|
+
* result-borne control signals + output so resume replays control flow and re-seeds outputs. */
|
|
51
53
|
export function buildWorkflowNodeCompletedEvent(input) {
|
|
52
54
|
return {
|
|
53
55
|
kind: "workflow_node_completed",
|
|
54
56
|
turn: input.turn,
|
|
55
57
|
agent_id: input.agentId,
|
|
56
58
|
termination: input.termination,
|
|
59
|
+
...(input.classifyBranch !== undefined ? { classify_branch: input.classifyBranch } : {}),
|
|
60
|
+
...(input.tournamentWinner !== undefined ? { tournament_winner: input.tournamentWinner } : {}),
|
|
61
|
+
...(input.loopContinue !== undefined ? { loop_continue: input.loopContinue } : {}),
|
|
62
|
+
...(input.output ? { output: input.output } : {}),
|
|
57
63
|
};
|
|
58
64
|
}
|
|
59
65
|
/**
|
|
60
|
-
* Recover completed workflow node
|
|
61
|
-
*
|
|
62
|
-
*
|
|
66
|
+
* Recover completed workflow node records from a session event stream. Scans for
|
|
67
|
+
* workflow_node_completed events with termination "completed" and returns them WITH their
|
|
68
|
+
* result-borne control signals (W-1) — resumeWorkflow lowers these to the kernel's
|
|
69
|
+
* `resumed_results` so a classifier re-prunes and a loop stop is honored, and re-seeds the
|
|
70
|
+
* driver's outputs map from the persisted output text.
|
|
63
71
|
*/
|
|
64
72
|
export function recoverCompletedWorkflowNodes(events) {
|
|
65
73
|
const completed = [];
|
|
66
74
|
for (const { event } of events) {
|
|
67
75
|
if (event.kind === "workflow_node_completed" && event.termination === "completed") {
|
|
68
|
-
completed.push(
|
|
76
|
+
completed.push({
|
|
77
|
+
agentId: event.agent_id,
|
|
78
|
+
...(event.classify_branch !== undefined ? { classifyBranch: event.classify_branch } : {}),
|
|
79
|
+
...(event.tournament_winner !== undefined ? { tournamentWinner: event.tournament_winner } : {}),
|
|
80
|
+
...(event.loop_continue !== undefined ? { loopContinue: event.loop_continue } : {}),
|
|
81
|
+
...(event.output !== undefined ? { output: event.output } : {}),
|
|
82
|
+
});
|
|
69
83
|
}
|
|
70
84
|
}
|
|
71
85
|
return completed;
|
|
@@ -73,15 +87,30 @@ export function recoverCompletedWorkflowNodes(events) {
|
|
|
73
87
|
/** R3-1: build workflow_nodes_submitted for persistence after a runtime submission, so resume can
|
|
74
88
|
* re-apply it. `nodes` is the kernel-shape (snake_case) submitted node array. */
|
|
75
89
|
export function buildWorkflowNodesSubmittedEvent(input) {
|
|
76
|
-
return {
|
|
90
|
+
return {
|
|
91
|
+
kind: "workflow_nodes_submitted",
|
|
92
|
+
turn: input.turn,
|
|
93
|
+
nodes: input.nodes,
|
|
94
|
+
...(input.baseIndex !== undefined ? { base_index: input.baseIndex } : {}),
|
|
95
|
+
...(input.submitterAgentId !== undefined ? { submitter_agent_id: input.submitterAgentId } : {}),
|
|
96
|
+
};
|
|
77
97
|
}
|
|
78
98
|
/** R3-1: recover the runtime submission batches (in order) to rebuild `resumed_submissions` for
|
|
79
|
-
* resumeWorkflow, so dynamically-appended nodes are reconstructed.
|
|
99
|
+
* resumeWorkflow, so dynamically-appended nodes are reconstructed.
|
|
100
|
+
* `submitters` is parallel to `submissions` (undefined = host/bootstrap submission). */
|
|
80
101
|
export function recoverSubmittedWorkflowNodes(events) {
|
|
81
102
|
const submissions = [];
|
|
103
|
+
const bases = [];
|
|
104
|
+
const submitters = [];
|
|
82
105
|
for (const { event } of events) {
|
|
83
|
-
if (event.kind === "workflow_nodes_submitted")
|
|
106
|
+
if (event.kind === "workflow_nodes_submitted") {
|
|
84
107
|
submissions.push(event.nodes);
|
|
108
|
+
submitters.push(event.submitter_agent_id);
|
|
109
|
+
// Absent on legacy logs → order-only replay (bases array stays parallel-short only
|
|
110
|
+
// if ALL records carry it; a mixed log degrades to order-only for safety).
|
|
111
|
+
if (event.base_index !== undefined)
|
|
112
|
+
bases.push(event.base_index);
|
|
113
|
+
}
|
|
85
114
|
}
|
|
86
|
-
return submissions;
|
|
115
|
+
return { submissions, bases: bases.length === submissions.length ? bases : [], submitters };
|
|
87
116
|
}
|
|
@@ -10,6 +10,13 @@ export interface SubAgentRunContext {
|
|
|
10
10
|
/** M5 v2.1: set when this child is a workflow node — propagated so a nested `start_workflow`
|
|
11
11
|
* FLATTENS to the parent kernel rather than auto-pivoting into its own bootstrap. */
|
|
12
12
|
isWorkflowNode?: boolean;
|
|
13
|
+
/** W-N1 tool exposure. The kernel omits an EMPTY `permitted_capability_ids` on the wire, so a
|
|
14
|
+
* grant-less workflow node is indistinguishable from a zero-cap spawn at the manifest — the
|
|
15
|
+
* caller states intent instead: `"inherit"` = run on the parent's execution plane with its
|
|
16
|
+
* meta-tool availability (trusted workflow nodes — they carried no grant list by design, and
|
|
17
|
+
* filtering on the missing list ran every DAG node TOOL-LESS); `"filtered"` (default) = filter
|
|
18
|
+
* to the manifest grants, empty ⇒ deny-all (spawn path, quarantined nodes). */
|
|
19
|
+
toolAccess?: "inherit" | "filtered";
|
|
13
20
|
/** #2-B-ii: parent-controlled abort — when the kernel preempts this node (`AgentPreempted`), the
|
|
14
21
|
* orchestrator interrupts the child runner, cancelling its in-flight LLM call. */
|
|
15
22
|
abortSignal?: AbortSignal;
|
|
@@ -19,7 +19,9 @@ function terminationFromStatus(status) {
|
|
|
19
19
|
normalized === "timeout" ||
|
|
20
20
|
normalized === "user_abort" ||
|
|
21
21
|
normalized === "error" ||
|
|
22
|
-
normalized === "milestone_exceeded"
|
|
22
|
+
normalized === "milestone_exceeded" ||
|
|
23
|
+
normalized === "context_overflow" ||
|
|
24
|
+
normalized === "no_progress") {
|
|
23
25
|
return normalized;
|
|
24
26
|
}
|
|
25
27
|
return status;
|
|
@@ -47,12 +49,32 @@ function deriveMetaTools(permitted, opts) {
|
|
|
47
49
|
metaTools.add("update_plan");
|
|
48
50
|
return metaTools;
|
|
49
51
|
}
|
|
52
|
+
/** W-N1: meta-tools by source availability alone — a `toolAccess: "inherit"` child gets the same
|
|
53
|
+
* meta surface a top-level run of these options would. */
|
|
54
|
+
function availableMetaTools(opts) {
|
|
55
|
+
const metaTools = new Set();
|
|
56
|
+
if (opts.skillContentMap?.size)
|
|
57
|
+
metaTools.add("skill");
|
|
58
|
+
if (opts.dreamStore)
|
|
59
|
+
metaTools.add("memory");
|
|
60
|
+
if (opts.knowledgeSource)
|
|
61
|
+
metaTools.add("knowledge");
|
|
62
|
+
if (opts.enablePlanTool)
|
|
63
|
+
metaTools.add("update_plan");
|
|
64
|
+
return metaTools;
|
|
65
|
+
}
|
|
50
66
|
/** Host-side driver for kernel-isolated sub-agent runs. */
|
|
51
67
|
export class SubAgentOrchestrator {
|
|
52
68
|
async run(ctx) {
|
|
69
|
+
// W-N1: "inherit" runs the child on the parent's plane with availability-derived meta-tools
|
|
70
|
+
// (trusted workflow nodes); "filtered" (default) filters to the manifest grants, empty ⇒
|
|
71
|
+
// deny-all (spawn path, quarantined nodes).
|
|
72
|
+
const inherit = ctx.toolAccess === "inherit";
|
|
53
73
|
const permitted = new Set(ctx.manifest.permitted_capability_ids ?? []);
|
|
54
|
-
const metaTools = deriveMetaTools(permitted, ctx.parentOpts);
|
|
55
|
-
const
|
|
74
|
+
const metaTools = inherit ? availableMetaTools(ctx.parentOpts) : deriveMetaTools(permitted, ctx.parentOpts);
|
|
75
|
+
const execPlane = inherit
|
|
76
|
+
? ctx.parentOpts.executionPlane
|
|
77
|
+
: new FilteredExecutionPlane(ctx.parentOpts.executionPlane, permitted, metaTools);
|
|
56
78
|
let systemPrompt = ctx.parentOpts.systemPrompt;
|
|
57
79
|
let inheritEvents;
|
|
58
80
|
if (ctx.manifest.context_inheritance === "full") {
|
|
@@ -72,7 +94,10 @@ export class SubAgentOrchestrator {
|
|
|
72
94
|
provider: resolveProvider(ctx.parentOpts, ctx.spec.modelHint),
|
|
73
95
|
// M4/G5: cap the child run at the node's token budget (falls back to the inherited cap).
|
|
74
96
|
maxTotalTokens: ctx.spec.tokenBudget ?? ctx.parentOpts.maxTotalTokens,
|
|
75
|
-
|
|
97
|
+
// O3: per-child turn / wall-clock caps (fall back to the inherited limits).
|
|
98
|
+
maxTurns: ctx.spec.maxTurns ?? ctx.parentOpts.maxTurns,
|
|
99
|
+
timeoutMs: ctx.spec.maxWallMs ?? ctx.parentOpts.timeoutMs,
|
|
100
|
+
executionPlane: execPlane,
|
|
76
101
|
agentId: ctx.spec.identity.agentId,
|
|
77
102
|
systemPrompt,
|
|
78
103
|
sessionLog: ctx.sessionLog,
|
|
@@ -82,6 +107,13 @@ export class SubAgentOrchestrator {
|
|
|
82
107
|
enablePlanTool: metaTools.has("update_plan") ? ctx.parentOpts.enablePlanTool : undefined,
|
|
83
108
|
// M5 v2.1: a workflow node's `start_workflow` flattens to the parent kernel (no nested pivot).
|
|
84
109
|
isWorkflowNode: ctx.isWorkflowNode,
|
|
110
|
+
// The child runs under ITS OWN spec, never the parent's: the spread above would otherwise
|
|
111
|
+
// leak the parent's `runSpec` (identity, capability filter — and an armed `loopRound`,
|
|
112
|
+
// giving every child a phantom pace tool). A loop-node iteration carries its own minimal
|
|
113
|
+
// spec to arm the pacing trap (DW-3); everything else runs spec-less as before.
|
|
114
|
+
runSpec: ctx.spec.loopRound
|
|
115
|
+
? { identity: ctx.spec.identity, role: ctx.spec.role, goal: ctx.spec.goal, loopRound: ctx.spec.loopRound }
|
|
116
|
+
: undefined,
|
|
85
117
|
});
|
|
86
118
|
// #2-B-ii: parent preempt → interrupt the child (cancels its in-flight LLM call).
|
|
87
119
|
linkAbort(ctx.abortSignal, childRunner);
|
|
@@ -107,6 +139,9 @@ export class SubAgentOrchestrator {
|
|
|
107
139
|
...(finalText
|
|
108
140
|
? { finalMessage: { role: "assistant", content: finalText, toolCalls: [] } }
|
|
109
141
|
: {}),
|
|
142
|
+
// DW-3: surface the kernel-adjudicated pace decision (loop-node iterations consume it as the
|
|
143
|
+
// continuation vocabulary). SDK-internal; stripped by subAgentResultToKernel.
|
|
144
|
+
...(done?.paceDecision ? { paceDecision: done.paceDecision } : {}),
|
|
110
145
|
};
|
|
111
146
|
return {
|
|
112
147
|
agentId: ctx.spec.identity.agentId,
|
|
@@ -2,7 +2,12 @@ import type { Message, ToolSchema } from "../../types.js";
|
|
|
2
2
|
export type KernelAgentRole = "explore" | "plan" | "implement" | "verify" | "custom";
|
|
3
3
|
export type AgentIsolation = "shared" | "read_only" | "worktree" | "remote";
|
|
4
4
|
export type ContextInheritance = "none" | "system_only" | "full";
|
|
5
|
-
export type TerminationReason = "completed" | "max_turns" | "token_budget" | "timeout" | "user_abort" | "error" | "milestone_exceeded"
|
|
5
|
+
export type TerminationReason = "completed" | "max_turns" | "token_budget" | "timeout" | "user_abort" | "error" | "milestone_exceeded"
|
|
6
|
+
/** v0.2.35 recovery ladder: compaction exhausted and the prompt still exceeds the provider window. */
|
|
7
|
+
| "context_overflow"
|
|
8
|
+
/** Repeat-fuse escalation: the same tool call (name AND args) re-issued past `terminateAfter` —
|
|
9
|
+
* a stall, distinct from `max_turns` which productive runs can also hit. */
|
|
10
|
+
| "no_progress";
|
|
6
11
|
export type MilestonePolicy = "require_verifier" | "terminate" | "auto_pass";
|
|
7
12
|
export interface AgentIdentity {
|
|
8
13
|
agentId: string;
|
|
@@ -14,6 +19,16 @@ export interface AgentCapabilityFilter {
|
|
|
14
19
|
allowedKinds?: string[];
|
|
15
20
|
allowedIds?: string[];
|
|
16
21
|
}
|
|
22
|
+
export interface LoopRoundSpec {
|
|
23
|
+
/** Hard round cap across the loop's lifetime; continue/sleep at the cap is coerced to stop. */
|
|
24
|
+
maxRounds?: number;
|
|
25
|
+
/** Sleep clamp floor (ms). */
|
|
26
|
+
minSleepMs?: number;
|
|
27
|
+
/** Sleep clamp ceiling (ms). */
|
|
28
|
+
maxSleepMs?: number;
|
|
29
|
+
/** Fallback when a round ends without a pace call: "stop" (goal loops, default) | "sleep" (cron loops). */
|
|
30
|
+
defaultAction?: "stop" | "sleep";
|
|
31
|
+
}
|
|
17
32
|
export interface AgentRunSpec {
|
|
18
33
|
identity: AgentIdentity;
|
|
19
34
|
role: KernelAgentRole;
|
|
@@ -23,11 +38,18 @@ export interface AgentRunSpec {
|
|
|
23
38
|
capabilityFilter?: AgentCapabilityFilter;
|
|
24
39
|
milestones?: MilestoneContract;
|
|
25
40
|
metadata?: Record<string, unknown>;
|
|
41
|
+
/** ③ loop-agent rounds: presence makes this run ONE round of a paced loop (gates the
|
|
42
|
+
* kernel `pace` meta-tool and arms the pacing trap). */
|
|
43
|
+
loopRound?: LoopRoundSpec;
|
|
26
44
|
/** M1/G3: per-agent model preference; the host resolves it via `RuntimeOptions.providerFor`.
|
|
27
45
|
* Host-side routing only — not sent to the kernel. */
|
|
28
46
|
modelHint?: string;
|
|
29
47
|
/** M4/G5: cumulative token cap for this sub-agent's run (sets the child kernel's `maxTotalTokens`). */
|
|
30
48
|
tokenBudget?: number;
|
|
49
|
+
/** O3: per-child turn cap (sets the child runner's `maxTurns`; falls back to the parent's). */
|
|
50
|
+
maxTurns?: number;
|
|
51
|
+
/** O3: per-child wall-clock cap in ms (sets the child runner's `timeoutMs`; falls back to the parent's). */
|
|
52
|
+
maxWallMs?: number;
|
|
31
53
|
}
|
|
32
54
|
/** Kernel process-table observation (Phase 3 canonical spawn signal). */
|
|
33
55
|
export interface AgentProcessChangedObservation {
|
|
@@ -61,6 +83,11 @@ export interface LoopResult {
|
|
|
61
83
|
classifyBranch?: string;
|
|
62
84
|
/** A#2 tournament verdict: a judge reports the winning entrant's agent id here. Sent only when set. */
|
|
63
85
|
tournamentWinner?: string;
|
|
86
|
+
/** ③ loop-agent pacing: the kernel-adjudicated after-round decision, surfaced by the orchestrator
|
|
87
|
+
* from the child's done event. For a loop-node iteration this is the PRIMARY continuation
|
|
88
|
+
* vocabulary (stop → loopContinue=false); the legacy text-sniffed signal is the fallback.
|
|
89
|
+
* SDK-internal — stripped by `subAgentResultToKernel`. */
|
|
90
|
+
paceDecision?: import("../kernel-step.js").PaceDecision;
|
|
64
91
|
}
|
|
65
92
|
export interface SubAgentResult {
|
|
66
93
|
agentId: string;
|
|
@@ -132,6 +159,10 @@ export interface WorkflowNodeSpec {
|
|
|
132
159
|
};
|
|
133
160
|
/** M4/G5: cap this node's child run at `tokenBudget` cumulative tokens (the per-node "use N tokens"). */
|
|
134
161
|
tokenBudget?: number;
|
|
162
|
+
/** O3: cap this node's child run at `maxTurns` provider turns (falls back to the parent's). */
|
|
163
|
+
maxTurns?: number;
|
|
164
|
+
/** O3: cap this node's child run at `maxWallMs` wall-clock milliseconds. */
|
|
165
|
+
maxWallMs?: number;
|
|
135
166
|
/** Indices of nodes this node depends on. */
|
|
136
167
|
dependsOn?: number[];
|
|
137
168
|
}
|
|
@@ -147,11 +178,14 @@ export interface WorkflowSpawnInfo {
|
|
|
147
178
|
isolation: string;
|
|
148
179
|
context_inheritance: string;
|
|
149
180
|
model_hint?: string;
|
|
181
|
+
/** W3 trust level: `"trusted"` | `"quarantined"`. */
|
|
182
|
+
trust?: string;
|
|
150
183
|
/** G3: JSON Schema the node's output must conform to (carried verbatim from the spec). */
|
|
151
184
|
output_schema?: Record<string, unknown>;
|
|
152
185
|
/** G2: for a reduce node, the registered reducer name to run (no LLM). */
|
|
153
186
|
reducer?: string;
|
|
154
|
-
/**
|
|
187
|
+
/** The dependency agent ids for EVERY dependent node (W-N2: a DAG edge carries data). A reduce
|
|
188
|
+
* node's registered function consumes them; every other node gets its deps' outputs in context. */
|
|
155
189
|
input_agent_ids?: string[];
|
|
156
190
|
/** A#2: present only for a tournament *judge* spawn — the two entrant agent ids whose produced
|
|
157
191
|
* outputs this judge compares. The runner looks them up and reports the winner as `tournamentWinner`. */
|
|
@@ -167,6 +201,10 @@ export interface WorkflowSpawnInfo {
|
|
|
167
201
|
classify_labels?: string[];
|
|
168
202
|
/** M4/G5: the node's per-node cumulative token cap, if set — the runner caps the child run here. */
|
|
169
203
|
token_budget?: number;
|
|
204
|
+
/** O3: per-node turn cap → the child run's `maxTurns`. */
|
|
205
|
+
max_turns?: number;
|
|
206
|
+
/** O3: per-node wall-clock cap (ms) → the child run's timeout. */
|
|
207
|
+
max_wall_ms?: number;
|
|
170
208
|
}
|
|
171
209
|
/** G4 budget-as-signal: the workflow's remaining headroom under the active quota, carried on the
|
|
172
210
|
* `workflow_batch_spawned` observation so a coordinator node can scale its next submission. */
|
|
@@ -216,3 +254,9 @@ export declare function generateAndFilter(generators: WorkflowTaskSpec[], filter
|
|
|
216
254
|
* re-checks flags. Verifiers run read-only with no inherited author context (bias-resistant).
|
|
217
255
|
*/
|
|
218
256
|
export declare function verifyRules(rules: WorkflowTaskSpec[], skeptic?: WorkflowTaskSpec): WorkflowSpec;
|
|
257
|
+
/**
|
|
258
|
+
* Generate → evaluate loop template (parity with node/python `genEval`): a worker Loop node
|
|
259
|
+
* (worktree isolation, full context) feeding a fresh-context verifier whose output is pinned
|
|
260
|
+
* to the kernel's verdict schema. Async because the wasm kernel module loads lazily.
|
|
261
|
+
*/
|
|
262
|
+
export declare function genEval(worker: WorkflowTaskSpec, evaluate: WorkflowTaskSpec, maxIters?: number, extractSkillOnPass?: boolean): Promise<WorkflowSpec>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getKernel } from "../kernel.js";
|
|
1
2
|
/** Map kernel spawn observation → host manifest. */
|
|
2
3
|
export function spawnObservationToManifest(obs, spec, parentSessionId) {
|
|
3
4
|
const o = obs;
|
|
@@ -45,6 +46,14 @@ export function agentRunSpecToKernel(spec) {
|
|
|
45
46
|
out.verification_contract_id = spec.verificationContractId;
|
|
46
47
|
if (spec.milestones)
|
|
47
48
|
out.milestones = milestoneContractToKernel(spec.milestones);
|
|
49
|
+
if (spec.loopRound) {
|
|
50
|
+
out.loop_round = {
|
|
51
|
+
...(spec.loopRound.maxRounds !== undefined ? { max_rounds: spec.loopRound.maxRounds } : {}),
|
|
52
|
+
...(spec.loopRound.minSleepMs !== undefined ? { min_sleep_ms: spec.loopRound.minSleepMs } : {}),
|
|
53
|
+
...(spec.loopRound.maxSleepMs !== undefined ? { max_sleep_ms: spec.loopRound.maxSleepMs } : {}),
|
|
54
|
+
...(spec.loopRound.defaultAction !== undefined ? { default_action: spec.loopRound.defaultAction } : {}),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
48
57
|
return out;
|
|
49
58
|
}
|
|
50
59
|
export function milestoneContractToKernel(contract) {
|
|
@@ -175,6 +184,9 @@ export function workflowNodeSpecToKernel(n) {
|
|
|
175
184
|
...(kind ? { kind } : {}),
|
|
176
185
|
// M4/G5: per-node token cap (additive; omitted when unset).
|
|
177
186
|
...(n.tokenBudget != null ? { token_budget: n.tokenBudget } : {}),
|
|
187
|
+
// O3: per-node turn / wall-clock caps (additive; omitted when unset).
|
|
188
|
+
...(n.maxTurns != null ? { max_turns: n.maxTurns } : {}),
|
|
189
|
+
...(n.maxWallMs != null ? { max_wall_ms: n.maxWallMs } : {}),
|
|
178
190
|
...(n.dependsOn && n.dependsOn.length ? { depends_on: n.dependsOn } : {}),
|
|
179
191
|
};
|
|
180
192
|
}
|
|
@@ -302,10 +314,15 @@ export const startWorkflowTool = {
|
|
|
302
314
|
};
|
|
303
315
|
/** Build a sub-agent run spec for a kernel-generated workflow node. */
|
|
304
316
|
export function workflowNodeToSpec(node, parentSessionId) {
|
|
317
|
+
// W-N6 transcript-as-carry: a loop node's iterations share ONE stable session id (the `-i{k}`
|
|
318
|
+
// suffix names the spawn, not the session), so iteration k replays the transcript of 0..k-1 —
|
|
319
|
+
// "do the next increment" actually sees the previous increments. The agent_id keeps the
|
|
320
|
+
// per-iteration suffix (kernel completion routing).
|
|
321
|
+
const sessionNodeId = node.loop_max_iters != null ? node.agent_id.replace(/-i\d+$/, "") : node.agent_id;
|
|
305
322
|
return {
|
|
306
323
|
identity: {
|
|
307
324
|
agentId: node.agent_id,
|
|
308
|
-
sessionId: `${parentSessionId}-${
|
|
325
|
+
sessionId: `${parentSessionId}-${sessionNodeId}`,
|
|
309
326
|
isSubAgent: true,
|
|
310
327
|
parentSessionId,
|
|
311
328
|
},
|
|
@@ -316,6 +333,14 @@ export function workflowNodeToSpec(node, parentSessionId) {
|
|
|
316
333
|
...(node.model_hint ? { modelHint: node.model_hint } : {}),
|
|
317
334
|
// M4/G5: carry the node's token cap so the orchestrator can bound the child run.
|
|
318
335
|
...(node.token_budget != null ? { tokenBudget: node.token_budget } : {}),
|
|
336
|
+
// O3: carry the node's turn / wall-clock caps (the orchestrator already honors these).
|
|
337
|
+
...(node.max_turns != null ? { maxTurns: node.max_turns } : {}),
|
|
338
|
+
...(node.max_wall_ms != null ? { maxWallMs: node.max_wall_ms } : {}),
|
|
339
|
+
// DW-3 one continuation vocabulary: a loop ITERATION runs with the pacing trap armed, so the
|
|
340
|
+
// agent signals continue/stop through the kernel-adjudicated `pace` meta-tool instead of a
|
|
341
|
+
// text-sniffed JSON blob. One iteration = one round; the DAG (not max_rounds) caps iterations,
|
|
342
|
+
// and default stop means "ended without pacing" = done — the CC silence-is-completion contract.
|
|
343
|
+
...(node.loop_max_iters != null ? { loopRound: { defaultAction: "stop" } } : {}),
|
|
319
344
|
};
|
|
320
345
|
}
|
|
321
346
|
/** Build the host manifest for a kernel-generated workflow node. */
|
|
@@ -391,3 +416,31 @@ export function verifyRules(rules, skeptic) {
|
|
|
391
416
|
}
|
|
392
417
|
return { nodes };
|
|
393
418
|
}
|
|
419
|
+
/**
|
|
420
|
+
* Generate → evaluate loop template (parity with node/python `genEval`): a worker Loop node
|
|
421
|
+
* (worktree isolation, full context) feeding a fresh-context verifier whose output is pinned
|
|
422
|
+
* to the kernel's verdict schema. Async because the wasm kernel module loads lazily.
|
|
423
|
+
*/
|
|
424
|
+
export async function genEval(worker, evaluate, maxIters = 3, extractSkillOnPass = true) {
|
|
425
|
+
const kernel = await getKernel();
|
|
426
|
+
const schema = JSON.parse(kernel.verdictOutputSchema(extractSkillOnPass));
|
|
427
|
+
return {
|
|
428
|
+
nodes: [
|
|
429
|
+
{
|
|
430
|
+
task: asTask(worker),
|
|
431
|
+
role: "implement",
|
|
432
|
+
isolation: "worktree",
|
|
433
|
+
contextInheritance: "full",
|
|
434
|
+
loop: { maxIters: Math.max(1, maxIters) },
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
task: asTask(evaluate),
|
|
438
|
+
role: "verify",
|
|
439
|
+
isolation: "read_only",
|
|
440
|
+
contextInheritance: "none",
|
|
441
|
+
dependsOn: [0],
|
|
442
|
+
outputSchema: schema,
|
|
443
|
+
},
|
|
444
|
+
],
|
|
445
|
+
};
|
|
446
|
+
}
|
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
/** Instruction appended to a loop
|
|
2
|
-
|
|
1
|
+
/** Instruction appended to a loop iteration's goal. DW-3: the continuation verb is the
|
|
2
|
+
* kernel-adjudicated `pace` meta-tool (armed on every iteration run), not a text blob — one
|
|
3
|
+
* vocabulary shared with the round-level loop agent. Iterations share one session, so "the work
|
|
4
|
+
* so far" is simply the visible transcript. */
|
|
5
|
+
export declare function loopInstruction(maxIters: number, iteration?: number): string;
|
|
6
|
+
/** W-N2: dependency outputs appended to a dependent node's goal — a DAG edge carries data, not
|
|
7
|
+
* just ordering (fan-out→synthesize was an uninformed synthesis without this). Each dependency's
|
|
8
|
+
* output is clipped so a chain of large nodes can't blow the child's context; empty/unknown
|
|
9
|
+
* outputs are skipped. Returns "" when the node has no dependencies. */
|
|
10
|
+
export declare function dependencyOutputsNote(inputAgentIds: string[] | undefined, outputs: Map<string, string> | undefined, maxPerDep?: number): string;
|
|
3
11
|
/** Instruction appended to a classify node's goal: pick exactly one of the kernel's branch labels. */
|
|
4
12
|
export declare function classifyInstruction(labels: string[]): string;
|
|
5
13
|
/** Build a tournament judge's goal: the controller's criterion + the two candidates to compare. */
|