@narumitw/pi-subagents 0.49.3 → 0.52.0
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 +362 -53
- package/package.json +10 -7
- package/src/adaptive-scheduler.ts +224 -0
- package/src/admission-benchmark.ts +95 -0
- package/src/admission-policy.ts +78 -0
- package/src/agent-projection.ts +53 -0
- package/src/agents.ts +58 -1
- package/src/auto-transport.ts +114 -0
- package/src/blocking-status.ts +63 -0
- package/src/capabilities.ts +145 -0
- package/src/capability-grant.ts +115 -0
- package/src/capability-router.ts +107 -0
- package/src/completion-delivery.ts +257 -0
- package/src/config-status.ts +221 -0
- package/src/config-ui.ts +215 -236
- package/src/consult-resources.ts +4 -27
- package/src/consult.ts +9 -1
- package/src/create-stateful-transport.ts +55 -0
- package/src/delegation-contract.ts +417 -0
- package/src/execution-plan.ts +322 -0
- package/src/execution-profiles.ts +95 -0
- package/src/execution-ui.ts +320 -0
- package/src/execution.ts +1098 -158
- package/src/in-process-transport.ts +269 -25
- package/src/inspect-render.ts +101 -1
- package/src/inspect.ts +321 -3
- package/src/integration-controller.ts +98 -0
- package/src/limits.ts +3 -0
- package/src/orchestration-metrics.ts +109 -0
- package/src/outcome.ts +61 -0
- package/src/panel-child-group.ts +35 -0
- package/src/panel-contract.ts +343 -0
- package/src/panel-evidence.ts +59 -0
- package/src/panel-execution.ts +770 -0
- package/src/panel-failure.ts +56 -0
- package/src/panel-planning.ts +175 -0
- package/src/panel-prompts.ts +132 -0
- package/src/panel-reconciliation.ts +57 -0
- package/src/panel-render.ts +103 -0
- package/src/parallel-limit-ui.ts +112 -0
- package/src/params.ts +179 -3
- package/src/persistence.ts +182 -32
- package/src/prompt-resources.ts +38 -0
- package/src/registry-types.ts +175 -0
- package/src/registry.ts +466 -143
- package/src/render.ts +72 -6
- package/src/result-contract.ts +416 -0
- package/src/retained-semantic-state.ts +100 -0
- package/src/rpc-timeout-finalization.ts +207 -0
- package/src/rpc-transport-metadata.ts +65 -0
- package/src/rpc-transport.ts +990 -0
- package/src/rpc-turn-capture.ts +142 -0
- package/src/runner-result.ts +55 -0
- package/src/runner-usage.ts +48 -0
- package/src/runner.ts +325 -73
- package/src/semantic-snapshot.ts +214 -0
- package/src/settings.ts +254 -35
- package/src/spawn-idempotency.ts +61 -0
- package/src/stateful-config.ts +13 -0
- package/src/stateful-guidance.ts +1 -0
- package/src/stateful-lifecycle.ts +45 -2
- package/src/stateful-limit-ui.ts +246 -0
- package/src/stateful-limits.ts +96 -0
- package/src/stateful-prompt.ts +11 -2
- package/src/stateful-render.ts +48 -3
- package/src/stateful.ts +467 -357
- package/src/subagents.ts +114 -46
- package/src/subprocess-transport.ts +64 -5
- package/src/supervision.ts +103 -0
- package/src/timeout-checkpoint.ts +305 -0
- package/src/timeout-finalization.ts +75 -0
- package/src/transport-types.ts +68 -0
- package/src/transport-ui.ts +169 -0
- package/src/transport.ts +16 -4
- package/src/turn-budget.ts +109 -0
- package/src/verification-policy.ts +67 -0
- package/src/work-item-ledger.ts +931 -0
- package/src/work-item-persistence.ts +223 -0
- package/src/workflow-planning.ts +162 -0
- package/src/workflow-tree-identity.ts +289 -0
- package/src/workflow-ui.ts +61 -0
- package/src/workflow-verification.ts +296 -0
- package/src/workspace.ts +69 -12
package/src/execution.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Blocking execution stays in one module so preflight, confirmation, cancellation generation,
|
|
3
|
+
* launch, and settlement retain one ordered lifecycle owner across every mode.
|
|
4
|
+
*/
|
|
1
5
|
import type { AgentToolResult, AgentToolUpdateCallback } from "@earendil-works/pi-agent-core";
|
|
2
6
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
7
|
+
import { AdaptiveScheduler } from "./adaptive-scheduler.js";
|
|
8
|
+
import { evaluateDelegationAdmission } from "./admission-policy.js";
|
|
3
9
|
import {
|
|
4
10
|
type AgentConfig,
|
|
5
11
|
type AgentScope,
|
|
@@ -7,14 +13,42 @@ import {
|
|
|
7
13
|
type SubagentSettings,
|
|
8
14
|
type SubagentThinkingLevel,
|
|
9
15
|
} from "./agents.js";
|
|
16
|
+
import {
|
|
17
|
+
chainStatus,
|
|
18
|
+
fanInStatus,
|
|
19
|
+
parallelStatus,
|
|
20
|
+
singleStatus,
|
|
21
|
+
startSubagentStatus,
|
|
22
|
+
} from "./blocking-status.js";
|
|
23
|
+
import { issueCapabilityGrant } from "./capability-grant.js";
|
|
24
|
+
import { redactPrivateText } from "./context.js";
|
|
10
25
|
import {
|
|
11
26
|
assertDelegationTargetAllowed,
|
|
12
27
|
type ResolvedSubagentTarget,
|
|
13
28
|
resolveSubagentTarget,
|
|
14
29
|
targetPolicyAudit,
|
|
15
30
|
} from "./cwd-policy.js";
|
|
16
|
-
import {
|
|
31
|
+
import {
|
|
32
|
+
appendDelegationContract,
|
|
33
|
+
type DelegationContract,
|
|
34
|
+
normalizeDelegationContract,
|
|
35
|
+
} from "./delegation-contract.js";
|
|
36
|
+
import {
|
|
37
|
+
acknowledgeExecutionPlan,
|
|
38
|
+
createExecutionPlan,
|
|
39
|
+
resolveContractTools,
|
|
40
|
+
} from "./execution-plan.js";
|
|
41
|
+
import {
|
|
42
|
+
DEFAULT_MAX_CONTEXT_BYTES,
|
|
43
|
+
MAX_BLOCKING_PARALLEL_CONCURRENCY,
|
|
44
|
+
MAX_SUBAGENT_TIMEOUT_MS,
|
|
45
|
+
truncateUtf8,
|
|
46
|
+
} from "./limits.js";
|
|
47
|
+
import { calculateOrchestrationMetrics } from "./orchestration-metrics.js";
|
|
48
|
+
import { executePanel, preflightPanelExecution } from "./panel-execution.js";
|
|
49
|
+
import { validatePanelRequest } from "./panel-planning.js";
|
|
17
50
|
import { hasUsableAggregator, type SubagentParams } from "./params.js";
|
|
51
|
+
import { appendResultInstruction, type SubagentResultFormat } from "./result-contract.js";
|
|
18
52
|
import {
|
|
19
53
|
buildFanInContext,
|
|
20
54
|
formatResultFailure,
|
|
@@ -30,14 +64,29 @@ import { safeTerminalLine } from "./safe-text.js";
|
|
|
30
64
|
import {
|
|
31
65
|
DEFAULT_DELEGATION_CWD_POLICY,
|
|
32
66
|
readSubagentSettings,
|
|
67
|
+
resolveBlockingMaxParallelTasks,
|
|
33
68
|
resolveSubagentThinkingLevel,
|
|
34
69
|
} from "./settings.js";
|
|
70
|
+
import { isRetryableResult, runHedgedAttempt, supervisionDelay } from "./supervision.js";
|
|
71
|
+
import { TimeoutProgressJournal, TURN_TERMINATION_VERSION } from "./timeout-checkpoint.js";
|
|
72
|
+
import type { TurnLimits } from "./turn-budget.js";
|
|
73
|
+
import {
|
|
74
|
+
requiresIndependentVerification,
|
|
75
|
+
validateWorkflowVerificationGraph,
|
|
76
|
+
} from "./verification-policy.js";
|
|
77
|
+
import type { WorkItemLedger } from "./work-item-ledger.js";
|
|
78
|
+
import {
|
|
79
|
+
createSessionWorkItemPersistence,
|
|
80
|
+
type WorkItemPersistence,
|
|
81
|
+
} from "./work-item-persistence.js";
|
|
82
|
+
import { createBlockingWorkLedger, resolveWorkflowTasks } from "./workflow-planning.js";
|
|
83
|
+
import { captureWorkflowTreeIdentity, sameWorkflowTreeIdentity } from "./workflow-tree-identity.js";
|
|
84
|
+
import {
|
|
85
|
+
createWorkflowVerificationReceipt,
|
|
86
|
+
workflowVerificationInstruction,
|
|
87
|
+
} from "./workflow-verification.js";
|
|
35
88
|
|
|
36
|
-
const MAX_PARALLEL_TASKS = 8;
|
|
37
|
-
const MAX_CONCURRENCY = 4;
|
|
38
89
|
export const FALLBACK_TIMEOUT_MS = 10 * 60 * 1000;
|
|
39
|
-
const STATUS_KEY = "subagents";
|
|
40
|
-
const activeStatuses = new Map<string, string>();
|
|
41
90
|
|
|
42
91
|
export function parsePositiveInteger(value: string | undefined): number | undefined {
|
|
43
92
|
if (!value) return undefined;
|
|
@@ -57,59 +106,6 @@ export function assertSubagentDepthAllowed(): void {
|
|
|
57
106
|
}
|
|
58
107
|
}
|
|
59
108
|
|
|
60
|
-
interface StatusContext {
|
|
61
|
-
ui: { setStatus: (key: string, value: string | undefined) => void };
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function startSubagentStatus(ctx: StatusContext, toolCallId: string, status: string) {
|
|
65
|
-
let cleared = false;
|
|
66
|
-
|
|
67
|
-
const update = (nextStatus: string) => {
|
|
68
|
-
if (cleared) return;
|
|
69
|
-
activeStatuses.set(toolCallId, nextStatus);
|
|
70
|
-
publishSubagentStatus(ctx);
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
update(status);
|
|
74
|
-
|
|
75
|
-
return {
|
|
76
|
-
update,
|
|
77
|
-
clear() {
|
|
78
|
-
if (cleared) return;
|
|
79
|
-
cleared = true;
|
|
80
|
-
activeStatuses.delete(toolCallId);
|
|
81
|
-
publishSubagentStatus(ctx);
|
|
82
|
-
},
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function publishSubagentStatus(ctx: StatusContext) {
|
|
87
|
-
const statuses = [...activeStatuses.values()];
|
|
88
|
-
if (statuses.length === 0) {
|
|
89
|
-
ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const suffix = statuses.length > 1 ? ` +${statuses.length - 1}` : "";
|
|
94
|
-
ctx.ui.setStatus(STATUS_KEY, `${statuses[0]}${suffix}`);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function singleStatus(agent: string): string {
|
|
98
|
-
return `${agent}`;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function chainStatus(step: number, total: number, agent?: string): string {
|
|
102
|
-
return `chain ${step}/${total}${agent ? ` ${agent}` : ""}`;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function parallelStatus(done: number, total: number, running: number): string {
|
|
106
|
-
return `parallel ${done}/${total} done${running > 0 ? ` ${running} running` : ""}`;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function fanInStatus(agent: string): string {
|
|
110
|
-
return `fan-in ${agent}`;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
109
|
export async function executeSubagent(
|
|
114
110
|
toolCallId: string,
|
|
115
111
|
params: SubagentParams,
|
|
@@ -125,8 +121,10 @@ export async function executeSubagent(
|
|
|
125
121
|
}
|
|
126
122
|
const aggregator = hasUsableAggregator(params.aggregator) ? params.aggregator : undefined;
|
|
127
123
|
const config = settingsOverride ?? readSubagentSettings();
|
|
124
|
+
const maxParallelTasks = resolveBlockingMaxParallelTasks(config);
|
|
128
125
|
const discovery = discoverAgents(ctx.cwd, agentScope, config);
|
|
129
126
|
const agents = discovery.agents;
|
|
127
|
+
const resolvedWorkflowTasks = resolveWorkflowTasks(params, agents);
|
|
130
128
|
const confirmProjectAgents = params.confirmProjectAgents ?? true;
|
|
131
129
|
const resolveTimeoutMs = (agentName: string, localTimeoutMs?: number) =>
|
|
132
130
|
localTimeoutMs ??
|
|
@@ -135,21 +133,145 @@ export async function executeSubagent(
|
|
|
135
133
|
resolveDefaultSubagentTimeoutMs();
|
|
136
134
|
const resolveThinkingLevel = (agentName: string, localThinkingLevel?: SubagentThinkingLevel) =>
|
|
137
135
|
resolveSubagentThinkingLevel(agents, agentName, params.thinkingLevel, localThinkingLevel);
|
|
136
|
+
let orchestrationDeadline: number | undefined;
|
|
137
|
+
const resolveTurnLimits = (local?: TurnLimits): TurnLimits => ({
|
|
138
|
+
idleTimeoutMs: local?.idleTimeoutMs ?? params.idleTimeoutMs,
|
|
139
|
+
maxTurns: local?.maxTurns ?? params.maxTurns,
|
|
140
|
+
maxToolCalls: local?.maxToolCalls ?? params.maxToolCalls,
|
|
141
|
+
});
|
|
142
|
+
const resolveExecutionBudget = (
|
|
143
|
+
agentName: string,
|
|
144
|
+
localTimeoutMs?: number,
|
|
145
|
+
):
|
|
146
|
+
| {
|
|
147
|
+
timeoutMs: number;
|
|
148
|
+
workTimeoutReason: "work_timeout" | "orchestration_timeout";
|
|
149
|
+
workTimeoutReportLimit: number;
|
|
150
|
+
}
|
|
151
|
+
| undefined => {
|
|
152
|
+
const requested = resolveTimeoutMs(agentName, localTimeoutMs);
|
|
153
|
+
if (orchestrationDeadline === undefined) {
|
|
154
|
+
return {
|
|
155
|
+
timeoutMs: requested,
|
|
156
|
+
workTimeoutReason: "work_timeout",
|
|
157
|
+
workTimeoutReportLimit: requested,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
const remaining = Math.floor(orchestrationDeadline - Date.now());
|
|
161
|
+
if (remaining < 1) return undefined;
|
|
162
|
+
const orchestrationLimited = remaining < requested;
|
|
163
|
+
return {
|
|
164
|
+
timeoutMs: Math.min(requested, remaining),
|
|
165
|
+
workTimeoutReason: orchestrationLimited ? "orchestration_timeout" : "work_timeout",
|
|
166
|
+
workTimeoutReportLimit: orchestrationLimited
|
|
167
|
+
? Math.floor(params.totalTimeoutMs as number)
|
|
168
|
+
: requested,
|
|
169
|
+
};
|
|
170
|
+
};
|
|
138
171
|
|
|
139
172
|
const hasChain = (params.chain?.length ?? 0) > 0;
|
|
140
173
|
const hasTasks = (params.tasks?.length ?? 0) > 0;
|
|
174
|
+
const hasWorkflow = (params.workflow?.tasks.length ?? 0) > 0;
|
|
175
|
+
const hasPanel = params.panel !== undefined;
|
|
141
176
|
const hasSingle = Boolean(params.agent && params.task);
|
|
142
|
-
const modeCount =
|
|
177
|
+
const modeCount =
|
|
178
|
+
Number(hasChain) +
|
|
179
|
+
Number(hasTasks) +
|
|
180
|
+
Number(hasWorkflow) +
|
|
181
|
+
Number(hasPanel) +
|
|
182
|
+
Number(hasSingle);
|
|
183
|
+
let workLedger: WorkItemLedger | undefined;
|
|
184
|
+
const workflowScheduling: ReturnType<AdaptiveScheduler["decide"]>[] = [];
|
|
185
|
+
const verificationTargetIds = new Set<string>();
|
|
143
186
|
|
|
144
187
|
const makeDetails =
|
|
145
|
-
(mode: "single" | "parallel" | "chain") =>
|
|
146
|
-
(results: SingleResult[], aggregator?: SingleResult): SubagentDetails =>
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
188
|
+
(mode: "single" | "parallel" | "chain" | "workflow" | "panel") =>
|
|
189
|
+
(results: SingleResult[], aggregator?: SingleResult): SubagentDetails => {
|
|
190
|
+
const workflow = workLedger?.snapshot();
|
|
191
|
+
const metricResults = aggregator ? [...results, aggregator] : results;
|
|
192
|
+
return {
|
|
193
|
+
mode,
|
|
194
|
+
agentScope,
|
|
195
|
+
projectAgentsDir: discovery.projectAgentsDir,
|
|
196
|
+
results,
|
|
197
|
+
aggregator,
|
|
198
|
+
workflow,
|
|
199
|
+
schedulerDecisions:
|
|
200
|
+
workflowScheduling.length > 0 ? workflowScheduling.slice(-64) : undefined,
|
|
201
|
+
metrics: calculateOrchestrationMetrics(workflow, metricResults),
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
const workflowFailureResult = (
|
|
205
|
+
agentName: string,
|
|
206
|
+
task: string,
|
|
207
|
+
reasonCode: string,
|
|
208
|
+
message: string,
|
|
209
|
+
thinkingLevel?: SubagentThinkingLevel,
|
|
210
|
+
): SingleResult => ({
|
|
211
|
+
agent: agentName,
|
|
212
|
+
agentSource: agents.find((agent) => agent.name === agentName)?.source ?? "unknown",
|
|
213
|
+
task,
|
|
214
|
+
exitCode: 1,
|
|
215
|
+
messages: [],
|
|
216
|
+
stderr: message,
|
|
217
|
+
errorMessage: message,
|
|
218
|
+
usage: {
|
|
219
|
+
input: 0,
|
|
220
|
+
output: 0,
|
|
221
|
+
cacheRead: 0,
|
|
222
|
+
cacheWrite: 0,
|
|
223
|
+
cost: 0,
|
|
224
|
+
contextTokens: 0,
|
|
225
|
+
turns: 0,
|
|
226
|
+
},
|
|
227
|
+
thinkingLevel,
|
|
228
|
+
finalOutput: "",
|
|
229
|
+
outcome: {
|
|
230
|
+
status: "failed",
|
|
231
|
+
reasonCode,
|
|
232
|
+
recoveryActions: ["revalidate"],
|
|
233
|
+
retryable: false,
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
const exhaustedResult = (
|
|
237
|
+
agentName: string,
|
|
238
|
+
task: string,
|
|
239
|
+
thinkingLevel: SubagentThinkingLevel | undefined,
|
|
240
|
+
step?: number,
|
|
241
|
+
): SingleResult => {
|
|
242
|
+
const limit = Math.floor(params.totalTimeoutMs as number);
|
|
243
|
+
const message = `Subagent orchestration deadline expired after ${limit}ms`;
|
|
244
|
+
return {
|
|
245
|
+
agent: agentName,
|
|
246
|
+
agentSource: agents.find((agent) => agent.name === agentName)?.source ?? "unknown",
|
|
247
|
+
task,
|
|
248
|
+
exitCode: 124,
|
|
249
|
+
messages: [],
|
|
250
|
+
stderr: message,
|
|
251
|
+
errorMessage: message,
|
|
252
|
+
usage: {
|
|
253
|
+
input: 0,
|
|
254
|
+
output: 0,
|
|
255
|
+
cacheRead: 0,
|
|
256
|
+
cacheWrite: 0,
|
|
257
|
+
cost: 0,
|
|
258
|
+
contextTokens: 0,
|
|
259
|
+
turns: 0,
|
|
260
|
+
},
|
|
261
|
+
thinkingLevel,
|
|
262
|
+
step,
|
|
263
|
+
finalOutput: "",
|
|
264
|
+
timedOut: true,
|
|
265
|
+
stopReason: "timeout",
|
|
266
|
+
termination: {
|
|
267
|
+
version: TURN_TERMINATION_VERSION,
|
|
268
|
+
reason: "orchestration_timeout",
|
|
269
|
+
limit,
|
|
270
|
+
checkpoint: new TimeoutProgressJournal().checkpoint(task),
|
|
271
|
+
finalization: { attempted: false, status: "skipped", durationMs: 0 },
|
|
272
|
+
},
|
|
273
|
+
};
|
|
274
|
+
};
|
|
153
275
|
|
|
154
276
|
if (modeCount !== 1 || (aggregator && !hasTasks)) {
|
|
155
277
|
const available = agents.map((a) => `${a.name} (${a.source})`).join(", ") || "none";
|
|
@@ -167,6 +289,115 @@ export async function executeSubagent(
|
|
|
167
289
|
details: makeDetails("single")([]),
|
|
168
290
|
};
|
|
169
291
|
}
|
|
292
|
+
if (
|
|
293
|
+
(hasWorkflow || hasPanel) &&
|
|
294
|
+
(Number.parseInt(process.env.PI_SUBAGENT_DEPTH ?? "0", 10) || 0) > 0
|
|
295
|
+
) {
|
|
296
|
+
throw new Error("Explicit workflow and panel recursion is disabled until separately evaluated");
|
|
297
|
+
}
|
|
298
|
+
if (params.panel) {
|
|
299
|
+
validatePanelRequest(params.panel, maxParallelTasks);
|
|
300
|
+
for (const agentName of [
|
|
301
|
+
...params.panel.reviewers.map((reviewer) => reviewer.agent),
|
|
302
|
+
params.panel.synthesizer.agent,
|
|
303
|
+
]) {
|
|
304
|
+
if (!agents.some((agent) => agent.name === agentName)) {
|
|
305
|
+
throw new Error(`Unknown panel agent: ${agentName}`);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
if (
|
|
310
|
+
(hasTasks && (params.tasks?.length ?? 0) > maxParallelTasks) ||
|
|
311
|
+
(hasWorkflow && (params.workflow?.tasks.length ?? 0) > maxParallelTasks) ||
|
|
312
|
+
(hasPanel && (params.panel?.reviewers.length ?? 0) > maxParallelTasks)
|
|
313
|
+
) {
|
|
314
|
+
const count = hasWorkflow
|
|
315
|
+
? (params.workflow?.tasks.length ?? 0)
|
|
316
|
+
: hasPanel
|
|
317
|
+
? (params.panel?.reviewers.length ?? 0)
|
|
318
|
+
: (params.tasks?.length ?? 0);
|
|
319
|
+
throw new Error(`Too many delegated tasks (${count}). Configured max is ${maxParallelTasks}.`);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const nonWorkflowRetryConfigured =
|
|
323
|
+
!hasWorkflow &&
|
|
324
|
+
Boolean(
|
|
325
|
+
params.retryPolicy ||
|
|
326
|
+
params.hedgeAfterMs ||
|
|
327
|
+
params.tasks?.some((task) => task.retryPolicy || task.hedgeAfterMs) ||
|
|
328
|
+
params.chain?.some((task) => task.retryPolicy || task.hedgeAfterMs) ||
|
|
329
|
+
aggregator?.retryPolicy ||
|
|
330
|
+
aggregator?.hedgeAfterMs,
|
|
331
|
+
);
|
|
332
|
+
if (nonWorkflowRetryConfigured) {
|
|
333
|
+
throw new Error("Retry and hedge policies are supported only by explicit workflow tasks");
|
|
334
|
+
}
|
|
335
|
+
if (hasWorkflow && params.workflow) {
|
|
336
|
+
for (const task of resolvedWorkflowTasks) {
|
|
337
|
+
if (task.verifierFor) verificationTargetIds.add(task.verifierFor);
|
|
338
|
+
const contract = normalizeDelegationContract(task.contract);
|
|
339
|
+
if (params.workflow.honorAdmission) {
|
|
340
|
+
const admission = contract?.admission;
|
|
341
|
+
const decision = evaluateDelegationAdmission({
|
|
342
|
+
contextPressure: admission?.contextPressure ?? "low",
|
|
343
|
+
independentWorkItems: admission?.independentWorkItems ?? 1,
|
|
344
|
+
coupling: admission?.coupling ?? "dense",
|
|
345
|
+
verificationRequired: admission?.verificationRequired ?? false,
|
|
346
|
+
verificationAvailable: admission?.verificationAvailable ?? false,
|
|
347
|
+
capabilitiesSupported: true,
|
|
348
|
+
budgetAllowsChildren: admission?.budgetAllowsChildren ?? false,
|
|
349
|
+
generationCurrent: true,
|
|
350
|
+
requirementsComplete: admission?.requirementsComplete ?? false,
|
|
351
|
+
});
|
|
352
|
+
if (
|
|
353
|
+
decision.recommendation === "parent-owned-direct" ||
|
|
354
|
+
decision.recommendation === "abstain-insufficient-evidence"
|
|
355
|
+
) {
|
|
356
|
+
throw new Error(
|
|
357
|
+
`Admission declined workflow task ${task.id}: ${decision.reasonCodes.join(", ")}`,
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
if (
|
|
362
|
+
requiresIndependentVerification({
|
|
363
|
+
contract,
|
|
364
|
+
integrationOwner: task.integrationOwner === true,
|
|
365
|
+
requiredCapabilities: task.requiredCapabilities ?? [],
|
|
366
|
+
})
|
|
367
|
+
) {
|
|
368
|
+
verificationTargetIds.add(task.id);
|
|
369
|
+
}
|
|
370
|
+
if (!task.retryPolicy && !task.hedgeAfterMs) continue;
|
|
371
|
+
const policy = contract?.sideEffectPolicy;
|
|
372
|
+
if (
|
|
373
|
+
(task.retryPolicy && policy !== "read-only" && policy !== "idempotent") ||
|
|
374
|
+
(task.hedgeAfterMs && policy !== "read-only")
|
|
375
|
+
) {
|
|
376
|
+
throw new Error(
|
|
377
|
+
`Workflow task ${task.id} must declare an idempotent retry or read-only hedge delegation contract`,
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
validateWorkflowVerificationGraph(
|
|
382
|
+
resolvedWorkflowTasks.map((task) => ({
|
|
383
|
+
...task,
|
|
384
|
+
resultFormat: task.resultFormat ?? params.resultFormat,
|
|
385
|
+
})),
|
|
386
|
+
verificationTargetIds,
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
workLedger = createBlockingWorkLedger(params, resolvedWorkflowTasks, aggregator);
|
|
390
|
+
let workflowPersistence: WorkItemPersistence | undefined;
|
|
391
|
+
if (hasWorkflow && workLedger) {
|
|
392
|
+
const owner =
|
|
393
|
+
ctx.sessionManager.getSessionId?.() ??
|
|
394
|
+
ctx.sessionManager.getSessionFile?.() ??
|
|
395
|
+
`ephemeral:${ctx.cwd}`;
|
|
396
|
+
workflowPersistence = createSessionWorkItemPersistence(owner, workLedger.workflowId);
|
|
397
|
+
}
|
|
398
|
+
const persistWorkLedger = async () => {
|
|
399
|
+
if (workflowPersistence && workLedger) await workflowPersistence.save(workLedger.snapshot());
|
|
400
|
+
};
|
|
170
401
|
|
|
171
402
|
const delegationPolicy = config?.cwdPolicy?.delegation ?? DEFAULT_DELEGATION_CWD_POLICY;
|
|
172
403
|
const resolveTarget = (cwd: string | undefined): ResolvedSubagentTarget => {
|
|
@@ -179,22 +410,214 @@ export async function executeSubagent(
|
|
|
179
410
|
return target;
|
|
180
411
|
};
|
|
181
412
|
const singleTarget = hasSingle ? resolveTarget(params.cwd) : undefined;
|
|
413
|
+
const panelTarget = hasPanel ? resolveTarget(undefined) : undefined;
|
|
182
414
|
const chainTargets = params.chain?.map((step) => resolveTarget(step.cwd)) ?? [];
|
|
183
415
|
const parallelTargets = params.tasks?.map((task) => resolveTarget(task.cwd)) ?? [];
|
|
416
|
+
const workflowTargets = resolvedWorkflowTasks.map((task) => resolveTarget(task.cwd));
|
|
184
417
|
const aggregatorTarget = aggregator ? resolveTarget(aggregator.cwd) : undefined;
|
|
418
|
+
if (params.panel && panelTarget) {
|
|
419
|
+
await preflightPanelExecution({
|
|
420
|
+
panel: params.panel,
|
|
421
|
+
agents,
|
|
422
|
+
signal,
|
|
423
|
+
target: panelTarget,
|
|
424
|
+
resolveThinkingLevel,
|
|
425
|
+
resolveTimeoutMs,
|
|
426
|
+
});
|
|
427
|
+
}
|
|
185
428
|
const attachTarget = (result: SingleResult, target: ResolvedSubagentTarget): SingleResult => {
|
|
186
429
|
result.target = targetPolicyAudit(target);
|
|
187
430
|
return result;
|
|
188
431
|
};
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
432
|
+
type ContractRequest = {
|
|
433
|
+
contract?: unknown;
|
|
434
|
+
resultFormat?: SubagentResultFormat;
|
|
435
|
+
};
|
|
436
|
+
const prepareTask = (task: string, local?: ContractRequest) => {
|
|
437
|
+
const contracted = appendDelegationContract(task, local?.contract ?? params.contract);
|
|
438
|
+
const resultFormat = local?.resultFormat ?? params.resultFormat;
|
|
439
|
+
return {
|
|
440
|
+
text: appendResultInstruction(contracted.text, resultFormat, DEFAULT_MAX_CONTEXT_BYTES),
|
|
441
|
+
contract: contracted.contract,
|
|
442
|
+
resultFormat,
|
|
443
|
+
};
|
|
444
|
+
};
|
|
445
|
+
const launchPolicy = (
|
|
446
|
+
target: ResolvedSubagentTarget,
|
|
447
|
+
prepared: { contract?: DelegationContract; resultFormat?: SubagentResultFormat },
|
|
448
|
+
displayTask: string,
|
|
449
|
+
agentName: string,
|
|
450
|
+
thinkingLevel: SubagentThinkingLevel | undefined,
|
|
451
|
+
timeoutMs: number,
|
|
452
|
+
taskGeneration = 0,
|
|
453
|
+
budget?: NonNullable<ReturnType<typeof resolveExecutionBudget>>,
|
|
454
|
+
turnLimits?: TurnLimits,
|
|
455
|
+
) => {
|
|
456
|
+
const agent = agents.find((candidate) => candidate.name === agentName);
|
|
457
|
+
const effectiveTools = agent ? resolveContractTools(agent.tools, prepared.contract) : undefined;
|
|
458
|
+
const executionPlan = agent
|
|
459
|
+
? createExecutionPlan({
|
|
460
|
+
contract: prepared.contract,
|
|
461
|
+
agent,
|
|
462
|
+
effectiveTools,
|
|
463
|
+
target: targetPolicyAudit(target),
|
|
464
|
+
workspaceMode: "shared",
|
|
465
|
+
transport: "subprocess",
|
|
466
|
+
resultFormat: prepared.resultFormat ?? "text",
|
|
467
|
+
model: agent.model,
|
|
468
|
+
thinkingLevel,
|
|
469
|
+
timeoutMs,
|
|
470
|
+
taskGeneration,
|
|
471
|
+
})
|
|
472
|
+
: undefined;
|
|
473
|
+
if (executionPlan) {
|
|
474
|
+
const acknowledgement = acknowledgeExecutionPlan(executionPlan);
|
|
475
|
+
if (acknowledgement.status === "rejected") {
|
|
476
|
+
throw new Error(`Execution plan rejected: ${JSON.stringify(acknowledgement)}`);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
const capabilityGrant = executionPlan
|
|
480
|
+
? issueCapabilityGrant(executionPlan, Date.now(), Math.max(1, timeoutMs + 60_000))
|
|
481
|
+
: undefined;
|
|
482
|
+
return {
|
|
483
|
+
projectTrust: target.trust.projectTrusted,
|
|
484
|
+
turnLimits,
|
|
485
|
+
workTimeoutReason: budget?.workTimeoutReason,
|
|
486
|
+
workTimeoutReportLimit: budget?.workTimeoutReportLimit,
|
|
487
|
+
orchestrationDeadlineAt: budget ? orchestrationDeadline : undefined,
|
|
488
|
+
tools: effectiveTools,
|
|
489
|
+
contract: prepared.contract,
|
|
490
|
+
resultFormat: prepared.resultFormat,
|
|
491
|
+
displayTask,
|
|
492
|
+
executionPlan,
|
|
493
|
+
capabilityGrant,
|
|
494
|
+
};
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
// Build and acknowledge every contracted plan before confirmation or child launch.
|
|
498
|
+
if (hasSingle && singleTarget && params.agent && params.task) {
|
|
499
|
+
const prepared = prepareTask(params.task, params);
|
|
500
|
+
launchPolicy(
|
|
501
|
+
singleTarget,
|
|
502
|
+
prepared,
|
|
503
|
+
params.task,
|
|
504
|
+
params.agent,
|
|
505
|
+
resolveThinkingLevel(params.agent, params.thinkingLevel),
|
|
506
|
+
resolveTimeoutMs(params.agent, params.timeoutMs),
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
for (const [index, step] of (params.chain ?? []).entries()) {
|
|
510
|
+
const prepared = prepareTask(step.task, step);
|
|
511
|
+
launchPolicy(
|
|
512
|
+
chainTargets[index],
|
|
513
|
+
prepared,
|
|
514
|
+
step.task,
|
|
515
|
+
step.agent,
|
|
516
|
+
resolveThinkingLevel(step.agent, step.thinkingLevel),
|
|
517
|
+
resolveTimeoutMs(step.agent, step.timeoutMs),
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
for (const [index, task] of (params.tasks ?? []).entries()) {
|
|
521
|
+
const prepared = prepareTask(task.task, task);
|
|
522
|
+
launchPolicy(
|
|
523
|
+
parallelTargets[index],
|
|
524
|
+
prepared,
|
|
525
|
+
task.task,
|
|
526
|
+
task.agent,
|
|
527
|
+
resolveThinkingLevel(task.agent, task.thinkingLevel),
|
|
528
|
+
resolveTimeoutMs(task.agent, task.timeoutMs),
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
for (const [index, task] of resolvedWorkflowTasks.entries()) {
|
|
532
|
+
const prepared = prepareTask(task.task, task);
|
|
533
|
+
launchPolicy(
|
|
534
|
+
workflowTargets[index],
|
|
535
|
+
prepared,
|
|
536
|
+
task.task,
|
|
537
|
+
task.agent,
|
|
538
|
+
resolveThinkingLevel(task.agent, task.thinkingLevel),
|
|
539
|
+
resolveTimeoutMs(task.agent, task.timeoutMs),
|
|
540
|
+
workLedger?.get(task.id)?.taskGeneration ?? 0,
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
if (aggregator && aggregatorTarget) {
|
|
544
|
+
const prepared = prepareTask(aggregator.task, aggregator);
|
|
545
|
+
launchPolicy(
|
|
546
|
+
aggregatorTarget,
|
|
547
|
+
prepared,
|
|
548
|
+
aggregator.task,
|
|
549
|
+
aggregator.agent,
|
|
550
|
+
resolveThinkingLevel(aggregator.agent, aggregator.thinkingLevel),
|
|
551
|
+
resolveTimeoutMs(aggregator.agent, aggregator.timeoutMs),
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
const artifactsFromResult = (result: SingleResult) => {
|
|
556
|
+
const structured =
|
|
557
|
+
result.structuredResult?.version === "pi-subagents:result:v2"
|
|
558
|
+
? result.structuredResult
|
|
559
|
+
: undefined;
|
|
560
|
+
return (structured?.artifacts ?? []).map((artifact) => ({
|
|
561
|
+
id: artifact.id,
|
|
562
|
+
kind: artifact.kind,
|
|
563
|
+
version: artifact.version ?? artifact.digest ?? "unversioned",
|
|
564
|
+
digest: artifact.digest,
|
|
565
|
+
verified: false,
|
|
566
|
+
}));
|
|
567
|
+
};
|
|
568
|
+
const startWorkItem = (id: string, agentName: string) => {
|
|
569
|
+
if (workLedger?.get(id)?.state === "ready") {
|
|
570
|
+
return workLedger.start(id, `agent:${agentName}`);
|
|
571
|
+
}
|
|
572
|
+
return workLedger?.get(id);
|
|
573
|
+
};
|
|
574
|
+
const settleWorkItem = (id: string, result: SingleResult, taskGeneration: number) => {
|
|
575
|
+
if (!workLedger) return;
|
|
576
|
+
if (workLedger.get(id)?.taskGeneration !== taskGeneration) {
|
|
577
|
+
result.outcome = {
|
|
578
|
+
status: "stale",
|
|
579
|
+
reasonCode: "stale-task-generation",
|
|
580
|
+
recoveryActions: ["discard", "replan"],
|
|
581
|
+
retryable: false,
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
if (result.outcome?.status === "stale") {
|
|
585
|
+
workLedger.invalidate(id, result.outcome.reasonCode ?? "stale-result");
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
if (isResultError(result)) {
|
|
589
|
+
const state =
|
|
590
|
+
result.outcome?.status === "blocked"
|
|
591
|
+
? "blocked"
|
|
592
|
+
: result.outcome?.status === "needs-input"
|
|
593
|
+
? "needs-input"
|
|
594
|
+
: result.aborted || result.outcome?.status === "interrupted"
|
|
595
|
+
? "interrupted"
|
|
596
|
+
: "failed";
|
|
597
|
+
workLedger.settle(
|
|
598
|
+
id,
|
|
599
|
+
state,
|
|
600
|
+
result.outcome?.reasonCode ?? result.errorMessage ?? result.stopReason,
|
|
601
|
+
);
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
workLedger.complete(id, {
|
|
605
|
+
taskGeneration,
|
|
606
|
+
executionPlanId: result.executionPlan?.id,
|
|
607
|
+
artifacts: artifactsFromResult(result),
|
|
608
|
+
});
|
|
609
|
+
};
|
|
192
610
|
|
|
193
611
|
if (agentScope === "project" || agentScope === "both") {
|
|
194
612
|
const requestedAgentNames = new Set<string>();
|
|
195
613
|
if (params.chain) for (const step of params.chain) requestedAgentNames.add(step.agent);
|
|
196
614
|
if (params.tasks) for (const t of params.tasks) requestedAgentNames.add(t.agent);
|
|
615
|
+
for (const task of resolvedWorkflowTasks) requestedAgentNames.add(task.agent);
|
|
197
616
|
if (aggregator) requestedAgentNames.add(aggregator.agent);
|
|
617
|
+
if (params.panel) {
|
|
618
|
+
for (const reviewer of params.panel.reviewers) requestedAgentNames.add(reviewer.agent);
|
|
619
|
+
requestedAgentNames.add(params.panel.synthesizer.agent);
|
|
620
|
+
}
|
|
198
621
|
if (params.agent) requestedAgentNames.add(params.agent);
|
|
199
622
|
|
|
200
623
|
const projectAgentsRequested = Array.from(requestedAgentNames)
|
|
@@ -222,13 +645,469 @@ export async function executeSubagent(
|
|
|
222
645
|
if (!ok) {
|
|
223
646
|
return {
|
|
224
647
|
content: [{ type: "text", text: "Canceled: project-local agents not approved." }],
|
|
225
|
-
details: makeDetails(
|
|
648
|
+
details: makeDetails(
|
|
649
|
+
hasChain
|
|
650
|
+
? "chain"
|
|
651
|
+
: hasTasks
|
|
652
|
+
? "parallel"
|
|
653
|
+
: hasWorkflow
|
|
654
|
+
? "workflow"
|
|
655
|
+
: hasPanel
|
|
656
|
+
? "panel"
|
|
657
|
+
: "single",
|
|
658
|
+
)([]),
|
|
226
659
|
};
|
|
227
660
|
}
|
|
228
661
|
}
|
|
229
662
|
}
|
|
230
663
|
}
|
|
231
664
|
|
|
665
|
+
orchestrationDeadline =
|
|
666
|
+
params.totalTimeoutMs === undefined
|
|
667
|
+
? undefined
|
|
668
|
+
: Date.now() + Math.floor(params.totalTimeoutMs);
|
|
669
|
+
if (params.panel && panelTarget) {
|
|
670
|
+
return executePanel({
|
|
671
|
+
toolCallId,
|
|
672
|
+
params,
|
|
673
|
+
panel: params.panel,
|
|
674
|
+
signal,
|
|
675
|
+
onUpdate,
|
|
676
|
+
ctx,
|
|
677
|
+
agents,
|
|
678
|
+
agentScope,
|
|
679
|
+
projectAgentsDir: discovery.projectAgentsDir,
|
|
680
|
+
maxParallelTasks,
|
|
681
|
+
target: panelTarget,
|
|
682
|
+
resolveThinkingLevel,
|
|
683
|
+
resolveTimeoutMs,
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
if (params.workflow && resolvedWorkflowTasks.length > 0 && workLedger) {
|
|
687
|
+
await persistWorkLedger();
|
|
688
|
+
const status = startSubagentStatus(
|
|
689
|
+
ctx,
|
|
690
|
+
toolCallId,
|
|
691
|
+
parallelStatus(0, resolvedWorkflowTasks.length, 0),
|
|
692
|
+
);
|
|
693
|
+
const scheduler = new AdaptiveScheduler();
|
|
694
|
+
const taskById = new Map(
|
|
695
|
+
resolvedWorkflowTasks.map((task, index) => [task.id, { task, index }]),
|
|
696
|
+
);
|
|
697
|
+
const resultsById = new Map<string, SingleResult>();
|
|
698
|
+
const deadline = orchestrationDeadline;
|
|
699
|
+
const cancelWorkflowGeneration = () => {
|
|
700
|
+
for (const item of workLedger.snapshot().items) {
|
|
701
|
+
if (item.state === "running" || item.state === "awaiting-verification") {
|
|
702
|
+
workLedger.invalidate(item.id, "parent-aborted");
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
};
|
|
706
|
+
signal?.addEventListener("abort", cancelWorkflowGeneration, { once: true });
|
|
707
|
+
try {
|
|
708
|
+
while (true) {
|
|
709
|
+
const snapshot = workLedger.snapshot();
|
|
710
|
+
const remainingBudgetMs =
|
|
711
|
+
deadline === undefined
|
|
712
|
+
? MAX_SUBAGENT_TIMEOUT_MS
|
|
713
|
+
: Math.max(0, Math.floor(deadline - Date.now()));
|
|
714
|
+
const decision = scheduler.decide(snapshot, {
|
|
715
|
+
maxConcurrency: Math.min(MAX_BLOCKING_PARALLEL_CONCURRENCY, maxParallelTasks),
|
|
716
|
+
activeCount: 0,
|
|
717
|
+
transportCapacity: MAX_BLOCKING_PARALLEL_CONCURRENCY,
|
|
718
|
+
remainingBudgetMs,
|
|
719
|
+
});
|
|
720
|
+
workflowScheduling.push(decision);
|
|
721
|
+
if (decision.selected.length === 0) break;
|
|
722
|
+
status.update(
|
|
723
|
+
parallelStatus(resultsById.size, resolvedWorkflowTasks.length, decision.selected.length),
|
|
724
|
+
);
|
|
725
|
+
const batch = await mapWithConcurrencyLimit(
|
|
726
|
+
decision.selected,
|
|
727
|
+
decision.effectiveConcurrency,
|
|
728
|
+
async (workItemId) => {
|
|
729
|
+
const entry = taskById.get(workItemId);
|
|
730
|
+
if (!entry) throw new Error(`Missing workflow task ${workItemId}`);
|
|
731
|
+
const { task, index } = entry;
|
|
732
|
+
const dependencies = (task.dependsOn ?? [])
|
|
733
|
+
.map((dependency) => resultsById.get(dependency))
|
|
734
|
+
.filter((result): result is SingleResult => result !== undefined);
|
|
735
|
+
const verifierDependency = task.verifierFor
|
|
736
|
+
? resultsById.get(task.verifierFor)
|
|
737
|
+
: undefined;
|
|
738
|
+
const verifierStructuredResult =
|
|
739
|
+
verifierDependency?.structuredResult?.version === "pi-subagents:result:v2"
|
|
740
|
+
? verifierDependency.structuredResult
|
|
741
|
+
: undefined;
|
|
742
|
+
const dependencyContext = task.verifierFor
|
|
743
|
+
? verifierStructuredResult
|
|
744
|
+
? `\n\nStaged target result:\n${redactPrivateText(
|
|
745
|
+
JSON.stringify(verifierStructuredResult),
|
|
746
|
+
)}`
|
|
747
|
+
: ""
|
|
748
|
+
: dependencies.length
|
|
749
|
+
? `\n\nDependency results:\n${buildFanInContext(dependencies)}`
|
|
750
|
+
: "";
|
|
751
|
+
const displayTask = task.task;
|
|
752
|
+
const target = workflowTargets[index];
|
|
753
|
+
const thinkingLevel = resolveThinkingLevel(task.agent, task.thinkingLevel);
|
|
754
|
+
let verifierTreeIdentity:
|
|
755
|
+
| Awaited<ReturnType<typeof captureWorkflowTreeIdentity>>
|
|
756
|
+
| undefined;
|
|
757
|
+
let verifierPreflightError: string | undefined;
|
|
758
|
+
let verifierPreflightCode: string | undefined;
|
|
759
|
+
if (task.verifierFor) {
|
|
760
|
+
const staged = workLedger.get(task.verifierFor);
|
|
761
|
+
if (!staged?.stagedTreeIdentity) {
|
|
762
|
+
verifierPreflightCode = "verification-tree-unavailable";
|
|
763
|
+
verifierPreflightError = `Verification target ${task.verifierFor} has no staged tree identity`;
|
|
764
|
+
} else {
|
|
765
|
+
try {
|
|
766
|
+
verifierTreeIdentity = await captureWorkflowTreeIdentity(target.cwd, { signal });
|
|
767
|
+
if (!sameWorkflowTreeIdentity(staged.stagedTreeIdentity, verifierTreeIdentity)) {
|
|
768
|
+
verifierPreflightCode = "verification-tree-mismatch";
|
|
769
|
+
verifierPreflightError =
|
|
770
|
+
"Workflow verification tree changed before verifier launch";
|
|
771
|
+
}
|
|
772
|
+
} catch (error) {
|
|
773
|
+
if (signal?.aborted) throw error;
|
|
774
|
+
verifierPreflightCode = "verification-tree-unavailable";
|
|
775
|
+
verifierPreflightError = error instanceof Error ? error.message : String(error);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
const verificationTargetTask = task.verifierFor
|
|
780
|
+
? taskById.get(task.verifierFor)?.task
|
|
781
|
+
: undefined;
|
|
782
|
+
const verificationTargetContract = normalizeDelegationContract(
|
|
783
|
+
verificationTargetTask?.contract,
|
|
784
|
+
);
|
|
785
|
+
const verificationSuffix =
|
|
786
|
+
task.verifierFor && verifierTreeIdentity
|
|
787
|
+
? `\n\n${workflowVerificationInstruction(task.verifierFor, verifierTreeIdentity, {
|
|
788
|
+
acceptanceCriteria: [
|
|
789
|
+
...(verificationTargetTask?.acceptanceCriteria ?? []),
|
|
790
|
+
...(verificationTargetContract?.acceptanceCriteria ?? []),
|
|
791
|
+
],
|
|
792
|
+
requiredEvidence: verificationTargetContract?.requiredEvidence ?? [],
|
|
793
|
+
})}`
|
|
794
|
+
: "";
|
|
795
|
+
const baseTask = `${task.task}${dependencyContext}`;
|
|
796
|
+
const baseBudget = Math.max(
|
|
797
|
+
0,
|
|
798
|
+
DEFAULT_MAX_CONTEXT_BYTES - Buffer.byteLength(verificationSuffix, "utf8"),
|
|
799
|
+
);
|
|
800
|
+
const taskWithContext = `${truncateUtf8(baseTask, baseBudget).text}${verificationSuffix}`;
|
|
801
|
+
const prepared = prepareTask(taskWithContext, task);
|
|
802
|
+
const startedItem = startWorkItem(workItemId, task.agent);
|
|
803
|
+
const acceptedTaskGeneration = startedItem?.taskGeneration ?? 0;
|
|
804
|
+
await persistWorkLedger();
|
|
805
|
+
const runAttempt = (attemptSignal: AbortSignal | undefined) => {
|
|
806
|
+
const budget = resolveExecutionBudget(task.agent, task.timeoutMs);
|
|
807
|
+
if (!budget) {
|
|
808
|
+
return Promise.resolve(exhaustedResult(task.agent, displayTask, thinkingLevel));
|
|
809
|
+
}
|
|
810
|
+
return runSingleAgent(
|
|
811
|
+
ctx.cwd,
|
|
812
|
+
agents,
|
|
813
|
+
task.agent,
|
|
814
|
+
prepared.text,
|
|
815
|
+
target.cwd,
|
|
816
|
+
undefined,
|
|
817
|
+
attemptSignal,
|
|
818
|
+
thinkingLevel,
|
|
819
|
+
budget.timeoutMs,
|
|
820
|
+
undefined,
|
|
821
|
+
makeDetails("workflow"),
|
|
822
|
+
undefined,
|
|
823
|
+
launchPolicy(
|
|
824
|
+
target,
|
|
825
|
+
prepared,
|
|
826
|
+
displayTask,
|
|
827
|
+
task.agent,
|
|
828
|
+
thinkingLevel,
|
|
829
|
+
budget.timeoutMs,
|
|
830
|
+
acceptedTaskGeneration,
|
|
831
|
+
budget,
|
|
832
|
+
resolveTurnLimits(task),
|
|
833
|
+
),
|
|
834
|
+
);
|
|
835
|
+
};
|
|
836
|
+
const maxAttempts = task.retryPolicy?.maxAttempts ?? 1;
|
|
837
|
+
let result: SingleResult | undefined = verifierPreflightError
|
|
838
|
+
? attachTarget(
|
|
839
|
+
workflowFailureResult(
|
|
840
|
+
task.agent,
|
|
841
|
+
displayTask,
|
|
842
|
+
verifierPreflightCode ?? "verification-tree-unavailable",
|
|
843
|
+
verifierPreflightError,
|
|
844
|
+
thinkingLevel,
|
|
845
|
+
),
|
|
846
|
+
target,
|
|
847
|
+
)
|
|
848
|
+
: undefined;
|
|
849
|
+
let hedged = false;
|
|
850
|
+
for (let attempt = 1; !verifierPreflightError && attempt <= maxAttempts; attempt++) {
|
|
851
|
+
if (attempt > 1 && deadline !== undefined && Date.now() >= deadline) break;
|
|
852
|
+
const attempted = await runHedgedAttempt(runAttempt, signal, task.hedgeAfterMs);
|
|
853
|
+
hedged ||= attempted.hedged;
|
|
854
|
+
result = attachTarget(
|
|
855
|
+
{
|
|
856
|
+
...attempted.result,
|
|
857
|
+
attemptCount: attempt,
|
|
858
|
+
hedged: hedged || undefined,
|
|
859
|
+
},
|
|
860
|
+
target,
|
|
861
|
+
);
|
|
862
|
+
if (!isRetryableResult(result) || attempt >= maxAttempts) break;
|
|
863
|
+
if (deadline !== undefined && Date.now() >= deadline) break;
|
|
864
|
+
await supervisionDelay(task.retryPolicy?.backoffMs ?? 0, signal);
|
|
865
|
+
}
|
|
866
|
+
if (!result) throw new Error(`Workflow task ${workItemId} produced no result`);
|
|
867
|
+
if (workLedger.get(workItemId)?.taskGeneration !== acceptedTaskGeneration) {
|
|
868
|
+
result.outcome = {
|
|
869
|
+
status: "stale",
|
|
870
|
+
reasonCode: "cancelled-generation",
|
|
871
|
+
recoveryActions: ["discard", "replan"],
|
|
872
|
+
retryable: false,
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
resultsById.set(workItemId, result);
|
|
876
|
+
if (result.outcome?.status === "stale") {
|
|
877
|
+
// Cancellation or replacement already rotated and invalidated this generation.
|
|
878
|
+
} else if (task.verifierFor) {
|
|
879
|
+
const staged = workLedger.get(task.verifierFor);
|
|
880
|
+
const structured =
|
|
881
|
+
result.structuredResult?.version === "pi-subagents:result:v2"
|
|
882
|
+
? result.structuredResult
|
|
883
|
+
: undefined;
|
|
884
|
+
let failureReason: string | undefined = verifierPreflightError;
|
|
885
|
+
let failureCode: string | undefined = verifierPreflightCode;
|
|
886
|
+
let postVerifierIdentity = verifierTreeIdentity;
|
|
887
|
+
if (!failureReason) {
|
|
888
|
+
try {
|
|
889
|
+
postVerifierIdentity = await captureWorkflowTreeIdentity(target.cwd, { signal });
|
|
890
|
+
if (
|
|
891
|
+
!verifierTreeIdentity ||
|
|
892
|
+
!staged?.stagedTreeIdentity ||
|
|
893
|
+
!sameWorkflowTreeIdentity(verifierTreeIdentity, postVerifierIdentity) ||
|
|
894
|
+
!sameWorkflowTreeIdentity(staged.stagedTreeIdentity, postVerifierIdentity)
|
|
895
|
+
) {
|
|
896
|
+
failureCode = "verification-tree-mismatch";
|
|
897
|
+
failureReason = "Workflow verification tree changed during verifier execution";
|
|
898
|
+
}
|
|
899
|
+
} catch (error) {
|
|
900
|
+
if (signal?.aborted) throw error;
|
|
901
|
+
failureCode = "verification-tree-unavailable";
|
|
902
|
+
failureReason = error instanceof Error ? error.message : String(error);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
if (
|
|
906
|
+
!failureReason &&
|
|
907
|
+
structured &&
|
|
908
|
+
staged?.acceptedExecutionPlanId &&
|
|
909
|
+
result.executionPlan?.id &&
|
|
910
|
+
postVerifierIdentity
|
|
911
|
+
) {
|
|
912
|
+
try {
|
|
913
|
+
const receipt = createWorkflowVerificationReceipt(structured, {
|
|
914
|
+
targetTaskId: staged.id,
|
|
915
|
+
targetTaskGeneration: staged.taskGeneration,
|
|
916
|
+
targetExecutionPlanId: staged.acceptedExecutionPlanId,
|
|
917
|
+
verifierTaskId: workItemId,
|
|
918
|
+
verifierTaskGeneration: acceptedTaskGeneration,
|
|
919
|
+
verifierExecutionPlanId: result.executionPlan.id,
|
|
920
|
+
treeIdentity: postVerifierIdentity,
|
|
921
|
+
sourceTruncated: result.truncated === true,
|
|
922
|
+
});
|
|
923
|
+
workLedger.completeVerification(workItemId, {
|
|
924
|
+
taskGeneration: acceptedTaskGeneration,
|
|
925
|
+
executionPlanId: result.executionPlan.id,
|
|
926
|
+
receipt,
|
|
927
|
+
});
|
|
928
|
+
if (receipt.decision !== "accept") {
|
|
929
|
+
const targetResult = resultsById.get(staged.id);
|
|
930
|
+
if (targetResult) {
|
|
931
|
+
targetResult.outcome = {
|
|
932
|
+
status: receipt.decision === "rework" ? "blocked" : "failed",
|
|
933
|
+
reasonCode:
|
|
934
|
+
receipt.decision === "rework"
|
|
935
|
+
? "verification-rework"
|
|
936
|
+
: "verification-rejected",
|
|
937
|
+
recoveryActions:
|
|
938
|
+
receipt.decision === "rework" ? ["replan", "verify"] : ["stop"],
|
|
939
|
+
retryable: false,
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
} catch (error) {
|
|
944
|
+
failureCode = "verification-receipt-invalid";
|
|
945
|
+
failureReason = error instanceof Error ? error.message : String(error);
|
|
946
|
+
}
|
|
947
|
+
} else if (!failureReason) {
|
|
948
|
+
failureCode = "verification-receipt-invalid";
|
|
949
|
+
failureReason = "Workflow verifier did not return a current structured-v2 result";
|
|
950
|
+
}
|
|
951
|
+
if (failureReason) {
|
|
952
|
+
const reasonCode = failureCode ?? "verification-receipt-invalid";
|
|
953
|
+
result.outcome = {
|
|
954
|
+
status:
|
|
955
|
+
reasonCode === "verification-receipt-invalid" ? "contract-invalid" : "failed",
|
|
956
|
+
reasonCode,
|
|
957
|
+
recoveryActions:
|
|
958
|
+
reasonCode === "verification-receipt-invalid"
|
|
959
|
+
? ["repair-contract"]
|
|
960
|
+
: ["revalidate"],
|
|
961
|
+
retryable: false,
|
|
962
|
+
};
|
|
963
|
+
result.errorMessage = failureReason;
|
|
964
|
+
workLedger.failVerification(workItemId, reasonCode);
|
|
965
|
+
}
|
|
966
|
+
} else if (verificationTargetIds.has(workItemId) && !isResultError(result)) {
|
|
967
|
+
try {
|
|
968
|
+
const treeIdentity = await captureWorkflowTreeIdentity(target.cwd, { signal });
|
|
969
|
+
if (!result.executionPlan?.id) {
|
|
970
|
+
throw new Error("Verification-required producer has no accepted ExecutionPlan");
|
|
971
|
+
}
|
|
972
|
+
workLedger.stageForVerification(workItemId, {
|
|
973
|
+
taskGeneration: acceptedTaskGeneration,
|
|
974
|
+
executionPlanId: result.executionPlan.id,
|
|
975
|
+
artifacts: artifactsFromResult(result),
|
|
976
|
+
treeIdentity,
|
|
977
|
+
});
|
|
978
|
+
} catch (error) {
|
|
979
|
+
if (signal?.aborted) throw error;
|
|
980
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
981
|
+
result.outcome = {
|
|
982
|
+
status: "failed",
|
|
983
|
+
reasonCode: "verification-tree-unavailable",
|
|
984
|
+
recoveryActions: ["revalidate"],
|
|
985
|
+
retryable: false,
|
|
986
|
+
};
|
|
987
|
+
result.errorMessage = message;
|
|
988
|
+
settleWorkItem(workItemId, result, acceptedTaskGeneration);
|
|
989
|
+
}
|
|
990
|
+
} else {
|
|
991
|
+
settleWorkItem(workItemId, result, acceptedTaskGeneration);
|
|
992
|
+
}
|
|
993
|
+
await persistWorkLedger();
|
|
994
|
+
return result;
|
|
995
|
+
},
|
|
996
|
+
signal,
|
|
997
|
+
);
|
|
998
|
+
if (batch.length === 0 || signal?.aborted) break;
|
|
999
|
+
}
|
|
1000
|
+
for (const item of workLedger.snapshot().items) {
|
|
1001
|
+
if (
|
|
1002
|
+
item.state !== "pending" &&
|
|
1003
|
+
item.state !== "ready" &&
|
|
1004
|
+
item.state !== "awaiting-verification"
|
|
1005
|
+
) {
|
|
1006
|
+
continue;
|
|
1007
|
+
}
|
|
1008
|
+
if (signal?.aborted) {
|
|
1009
|
+
workLedger.settle(item.id, "interrupted", "parent-aborted");
|
|
1010
|
+
} else if (deadline !== undefined && Date.now() >= deadline) {
|
|
1011
|
+
workLedger.settle(item.id, "blocked", "budget-exhausted");
|
|
1012
|
+
} else if (item.state === "awaiting-verification") {
|
|
1013
|
+
workLedger.settle(item.id, "blocked", "verification-not-completed");
|
|
1014
|
+
} else {
|
|
1015
|
+
const dependencyBlocked = item.dependencies.some(
|
|
1016
|
+
(dependency) => workLedger.get(dependency)?.state !== "completed",
|
|
1017
|
+
);
|
|
1018
|
+
workLedger.settle(
|
|
1019
|
+
item.id,
|
|
1020
|
+
dependencyBlocked ? "blocked" : "needs-input",
|
|
1021
|
+
dependencyBlocked ? "dependency-not-completed" : "artifact-version-mismatch",
|
|
1022
|
+
);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
await persistWorkLedger();
|
|
1026
|
+
const results = resolvedWorkflowTasks.map((task) => {
|
|
1027
|
+
const completed = resultsById.get(task.id);
|
|
1028
|
+
const item = workLedger.get(task.id);
|
|
1029
|
+
if (completed) {
|
|
1030
|
+
if (item && item.state !== "completed" && !isResultError(completed)) {
|
|
1031
|
+
completed.outcome = {
|
|
1032
|
+
status:
|
|
1033
|
+
item.state === "needs-input"
|
|
1034
|
+
? "needs-input"
|
|
1035
|
+
: item.state === "interrupted"
|
|
1036
|
+
? "interrupted"
|
|
1037
|
+
: item.state === "failed"
|
|
1038
|
+
? "failed"
|
|
1039
|
+
: "blocked",
|
|
1040
|
+
reasonCode: item.outcomeReason ?? "verification-not-accepted",
|
|
1041
|
+
recoveryActions:
|
|
1042
|
+
item.outcomeReason === "verification-rework" ? ["replan", "verify"] : ["stop"],
|
|
1043
|
+
retryable: false,
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
return completed;
|
|
1047
|
+
}
|
|
1048
|
+
const outcomeStatus =
|
|
1049
|
+
item?.state === "interrupted"
|
|
1050
|
+
? "interrupted"
|
|
1051
|
+
: item?.state === "needs-input"
|
|
1052
|
+
? "needs-input"
|
|
1053
|
+
: "blocked";
|
|
1054
|
+
const reasonCode = item?.outcomeReason ?? "dependency-not-satisfied";
|
|
1055
|
+
return {
|
|
1056
|
+
agent: task.agent,
|
|
1057
|
+
agentSource: agents.find((agent) => agent.name === task.agent)?.source ?? "unknown",
|
|
1058
|
+
task: task.task,
|
|
1059
|
+
exitCode: 1,
|
|
1060
|
+
messages: [],
|
|
1061
|
+
stderr: "Workflow dependency was not satisfied",
|
|
1062
|
+
errorMessage: `Workflow task did not start: ${reasonCode}`,
|
|
1063
|
+
aborted: outcomeStatus === "interrupted",
|
|
1064
|
+
outcome: {
|
|
1065
|
+
status: outcomeStatus,
|
|
1066
|
+
reasonCode,
|
|
1067
|
+
recoveryActions:
|
|
1068
|
+
outcomeStatus === "needs-input"
|
|
1069
|
+
? ["supply-input"]
|
|
1070
|
+
: outcomeStatus === "interrupted"
|
|
1071
|
+
? ["retry"]
|
|
1072
|
+
: ["resolve-dependency"],
|
|
1073
|
+
retryable: outcomeStatus === "interrupted",
|
|
1074
|
+
},
|
|
1075
|
+
usage: {
|
|
1076
|
+
input: 0,
|
|
1077
|
+
output: 0,
|
|
1078
|
+
cacheRead: 0,
|
|
1079
|
+
cacheWrite: 0,
|
|
1080
|
+
cost: 0,
|
|
1081
|
+
contextTokens: 0,
|
|
1082
|
+
turns: 0,
|
|
1083
|
+
},
|
|
1084
|
+
finalOutput: "",
|
|
1085
|
+
} satisfies SingleResult;
|
|
1086
|
+
});
|
|
1087
|
+
const successCount = results.filter((result) => !isResultError(result)).length;
|
|
1088
|
+
const attemptCount = results.reduce((sum, result) => sum + (result.attemptCount ?? 1), 0);
|
|
1089
|
+
const hedgeCount = results.filter((result) => result.hedged).length;
|
|
1090
|
+
const isError = successCount !== results.length;
|
|
1091
|
+
return {
|
|
1092
|
+
content: [
|
|
1093
|
+
{
|
|
1094
|
+
type: "text",
|
|
1095
|
+
text: `Workflow: ${successCount}/${results.length} succeeded; ${attemptCount} attempt(s), ${hedgeCount} hedged task(s).`,
|
|
1096
|
+
},
|
|
1097
|
+
],
|
|
1098
|
+
details: { ...makeDetails("workflow")(results), isError },
|
|
1099
|
+
isError: isError || undefined,
|
|
1100
|
+
};
|
|
1101
|
+
} finally {
|
|
1102
|
+
signal?.removeEventListener("abort", cancelWorkflowGeneration);
|
|
1103
|
+
try {
|
|
1104
|
+
await persistWorkLedger();
|
|
1105
|
+
} finally {
|
|
1106
|
+
status.clear();
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
|
|
232
1111
|
if (params.chain && params.chain.length > 0) {
|
|
233
1112
|
const results: SingleResult[] = [];
|
|
234
1113
|
let previousOutput = "";
|
|
@@ -242,6 +1121,7 @@ export async function executeSubagent(
|
|
|
242
1121
|
step.task.replace(/\{previous\}/g, previousOutput),
|
|
243
1122
|
DEFAULT_MAX_CONTEXT_BYTES,
|
|
244
1123
|
).text;
|
|
1124
|
+
const prepared = prepareTask(taskWithContext, step);
|
|
245
1125
|
|
|
246
1126
|
// Create update callback that includes all previous results
|
|
247
1127
|
const chainUpdate: OnUpdateCallback | undefined = onUpdate
|
|
@@ -259,25 +1139,41 @@ export async function executeSubagent(
|
|
|
259
1139
|
: undefined;
|
|
260
1140
|
|
|
261
1141
|
const target = chainTargets[i];
|
|
1142
|
+
const thinkingLevel = resolveThinkingLevel(step.agent, step.thinkingLevel);
|
|
1143
|
+
const budget = resolveExecutionBudget(step.agent, step.timeoutMs);
|
|
1144
|
+
const taskGeneration = startWorkItem(`step-${i + 1}`, step.agent)?.taskGeneration ?? 0;
|
|
262
1145
|
const result = attachTarget(
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
1146
|
+
budget
|
|
1147
|
+
? await runSingleAgent(
|
|
1148
|
+
ctx.cwd,
|
|
1149
|
+
agents,
|
|
1150
|
+
step.agent,
|
|
1151
|
+
prepared.text,
|
|
1152
|
+
target.cwd,
|
|
1153
|
+
i + 1,
|
|
1154
|
+
signal,
|
|
1155
|
+
thinkingLevel,
|
|
1156
|
+
budget.timeoutMs,
|
|
1157
|
+
chainUpdate,
|
|
1158
|
+
makeDetails("chain"),
|
|
1159
|
+
undefined,
|
|
1160
|
+
launchPolicy(
|
|
1161
|
+
target,
|
|
1162
|
+
prepared,
|
|
1163
|
+
taskWithContext,
|
|
1164
|
+
step.agent,
|
|
1165
|
+
thinkingLevel,
|
|
1166
|
+
budget.timeoutMs,
|
|
1167
|
+
taskGeneration,
|
|
1168
|
+
budget,
|
|
1169
|
+
resolveTurnLimits(step),
|
|
1170
|
+
),
|
|
1171
|
+
)
|
|
1172
|
+
: exhaustedResult(step.agent, taskWithContext, thinkingLevel, i + 1),
|
|
278
1173
|
target,
|
|
279
1174
|
);
|
|
280
1175
|
results.push(result);
|
|
1176
|
+
settleWorkItem(`step-${i + 1}`, result, taskGeneration);
|
|
281
1177
|
|
|
282
1178
|
const isError = isResultError(result);
|
|
283
1179
|
if (isError) {
|
|
@@ -290,7 +1186,9 @@ export async function executeSubagent(
|
|
|
290
1186
|
isError: true,
|
|
291
1187
|
};
|
|
292
1188
|
}
|
|
293
|
-
previousOutput =
|
|
1189
|
+
previousOutput = result.structuredResult
|
|
1190
|
+
? JSON.stringify(result.structuredResult)
|
|
1191
|
+
: getResultFinalOutput(result);
|
|
294
1192
|
}
|
|
295
1193
|
return {
|
|
296
1194
|
content: [
|
|
@@ -307,17 +1205,6 @@ export async function executeSubagent(
|
|
|
307
1205
|
}
|
|
308
1206
|
|
|
309
1207
|
if (params.tasks && params.tasks.length > 0) {
|
|
310
|
-
if (params.tasks.length > MAX_PARALLEL_TASKS)
|
|
311
|
-
return {
|
|
312
|
-
content: [
|
|
313
|
-
{
|
|
314
|
-
type: "text",
|
|
315
|
-
text: `Too many parallel tasks (${params.tasks.length}). Max is ${MAX_PARALLEL_TASKS}.`,
|
|
316
|
-
},
|
|
317
|
-
],
|
|
318
|
-
details: makeDetails("parallel")([]),
|
|
319
|
-
};
|
|
320
|
-
|
|
321
1208
|
const status = startSubagentStatus(
|
|
322
1209
|
ctx,
|
|
323
1210
|
toolCallId,
|
|
@@ -395,34 +1282,50 @@ export async function executeSubagent(
|
|
|
395
1282
|
|
|
396
1283
|
const results = await mapWithConcurrencyLimit(
|
|
397
1284
|
params.tasks,
|
|
398
|
-
|
|
1285
|
+
MAX_BLOCKING_PARALLEL_CONCURRENCY,
|
|
399
1286
|
async (t, index) => {
|
|
400
1287
|
const target = parallelTargets[index];
|
|
1288
|
+
const prepared = prepareTask(t.task, t);
|
|
1289
|
+
const thinkingLevel = resolveThinkingLevel(t.agent, t.thinkingLevel);
|
|
1290
|
+
const budget = resolveExecutionBudget(t.agent, t.timeoutMs);
|
|
1291
|
+
const taskGeneration = startWorkItem(`task-${index + 1}`, t.agent)?.taskGeneration ?? 0;
|
|
401
1292
|
const result = attachTarget(
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
1293
|
+
budget
|
|
1294
|
+
? await runSingleAgent(
|
|
1295
|
+
ctx.cwd,
|
|
1296
|
+
agents,
|
|
1297
|
+
t.agent,
|
|
1298
|
+
prepared.text,
|
|
1299
|
+
target.cwd,
|
|
1300
|
+
undefined,
|
|
1301
|
+
signal,
|
|
1302
|
+
thinkingLevel,
|
|
1303
|
+
budget.timeoutMs,
|
|
1304
|
+
(partial) => {
|
|
1305
|
+
if (partial.details?.results[0]) {
|
|
1306
|
+
allResults[index] = { ...partial.details.results[0], exitCode: -1 };
|
|
1307
|
+
emitParallelUpdate();
|
|
1308
|
+
}
|
|
1309
|
+
},
|
|
1310
|
+
makeDetails("parallel"),
|
|
1311
|
+
undefined,
|
|
1312
|
+
launchPolicy(
|
|
1313
|
+
target,
|
|
1314
|
+
prepared,
|
|
1315
|
+
t.task,
|
|
1316
|
+
t.agent,
|
|
1317
|
+
thinkingLevel,
|
|
1318
|
+
budget.timeoutMs,
|
|
1319
|
+
taskGeneration,
|
|
1320
|
+
budget,
|
|
1321
|
+
resolveTurnLimits(t),
|
|
1322
|
+
),
|
|
1323
|
+
)
|
|
1324
|
+
: exhaustedResult(t.agent, t.task, thinkingLevel),
|
|
423
1325
|
target,
|
|
424
1326
|
);
|
|
425
1327
|
allResults[index] = result;
|
|
1328
|
+
settleWorkItem(`task-${index + 1}`, result, taskGeneration);
|
|
426
1329
|
doneCount += 1;
|
|
427
1330
|
runningCount -= 1;
|
|
428
1331
|
emitParallelUpdate();
|
|
@@ -430,6 +1333,8 @@ export async function executeSubagent(
|
|
|
430
1333
|
},
|
|
431
1334
|
signal,
|
|
432
1335
|
(task, index) => {
|
|
1336
|
+
const taskGeneration =
|
|
1337
|
+
startWorkItem(`task-${index + 1}`, task.agent)?.taskGeneration ?? 0;
|
|
433
1338
|
const skipped: SingleResult = {
|
|
434
1339
|
...allResults[index],
|
|
435
1340
|
task: task.task,
|
|
@@ -439,6 +1344,7 @@ export async function executeSubagent(
|
|
|
439
1344
|
errorMessage: "Subagent was not started because the parent call was aborted",
|
|
440
1345
|
};
|
|
441
1346
|
allResults[index] = skipped;
|
|
1347
|
+
settleWorkItem(`task-${index + 1}`, skipped, taskGeneration);
|
|
442
1348
|
doneCount += 1;
|
|
443
1349
|
runningCount -= 1;
|
|
444
1350
|
emitParallelUpdate();
|
|
@@ -456,33 +1362,50 @@ export async function executeSubagent(
|
|
|
456
1362
|
: `${aggregator.task}\n\nParallel task outputs:\n\n${fanInContext}`,
|
|
457
1363
|
DEFAULT_MAX_CONTEXT_BYTES,
|
|
458
1364
|
).text;
|
|
1365
|
+
const prepared = prepareTask(aggregatorTask, aggregator);
|
|
459
1366
|
const target = aggregatorTarget as ResolvedSubagentTarget;
|
|
1367
|
+
const thinkingLevel = resolveThinkingLevel(aggregator.agent, aggregator.thinkingLevel);
|
|
1368
|
+
const budget = resolveExecutionBudget(aggregator.agent, aggregator.timeoutMs);
|
|
1369
|
+
const taskGeneration = startWorkItem("aggregator", aggregator.agent)?.taskGeneration ?? 0;
|
|
460
1370
|
aggregatorResult = attachTarget(
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
1371
|
+
budget
|
|
1372
|
+
? await runSingleAgent(
|
|
1373
|
+
ctx.cwd,
|
|
1374
|
+
agents,
|
|
1375
|
+
aggregator.agent,
|
|
1376
|
+
prepared.text,
|
|
1377
|
+
target.cwd,
|
|
1378
|
+
undefined,
|
|
1379
|
+
signal,
|
|
1380
|
+
thinkingLevel,
|
|
1381
|
+
budget.timeoutMs,
|
|
1382
|
+
(partial) => {
|
|
1383
|
+
status.update(fanInStatus(aggregator.agent));
|
|
1384
|
+
if (onUpdate && partial.details?.results[0]) {
|
|
1385
|
+
onUpdate({
|
|
1386
|
+
content: partial.content,
|
|
1387
|
+
details: makeDetails("parallel")(results, partial.details.results[0]),
|
|
1388
|
+
});
|
|
1389
|
+
}
|
|
1390
|
+
},
|
|
1391
|
+
makeDetails("parallel"),
|
|
1392
|
+
undefined,
|
|
1393
|
+
launchPolicy(
|
|
1394
|
+
target,
|
|
1395
|
+
prepared,
|
|
1396
|
+
aggregatorTask,
|
|
1397
|
+
aggregator.agent,
|
|
1398
|
+
thinkingLevel,
|
|
1399
|
+
budget.timeoutMs,
|
|
1400
|
+
taskGeneration,
|
|
1401
|
+
budget,
|
|
1402
|
+
resolveTurnLimits(aggregator),
|
|
1403
|
+
),
|
|
1404
|
+
)
|
|
1405
|
+
: exhaustedResult(aggregator.agent, aggregatorTask, thinkingLevel),
|
|
484
1406
|
target,
|
|
485
1407
|
);
|
|
1408
|
+
settleWorkItem("aggregator", aggregatorResult, taskGeneration);
|
|
486
1409
|
}
|
|
487
1410
|
|
|
488
1411
|
const successCount = results.filter((result) => !isResultError(result)).length;
|
|
@@ -527,24 +1450,41 @@ export async function executeSubagent(
|
|
|
527
1450
|
|
|
528
1451
|
try {
|
|
529
1452
|
const target = singleTarget as ResolvedSubagentTarget;
|
|
1453
|
+
const prepared = prepareTask(params.task, params);
|
|
1454
|
+
const thinkingLevel = resolveThinkingLevel(params.agent, params.thinkingLevel);
|
|
1455
|
+
const budget = resolveExecutionBudget(params.agent, params.timeoutMs);
|
|
1456
|
+
const taskGeneration = startWorkItem("task-1", params.agent)?.taskGeneration ?? 0;
|
|
530
1457
|
const result = attachTarget(
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
1458
|
+
budget
|
|
1459
|
+
? await runSingleAgent(
|
|
1460
|
+
ctx.cwd,
|
|
1461
|
+
agents,
|
|
1462
|
+
params.agent,
|
|
1463
|
+
prepared.text,
|
|
1464
|
+
target.cwd,
|
|
1465
|
+
undefined,
|
|
1466
|
+
signal,
|
|
1467
|
+
thinkingLevel,
|
|
1468
|
+
budget.timeoutMs,
|
|
1469
|
+
onUpdate,
|
|
1470
|
+
makeDetails("single"),
|
|
1471
|
+
undefined,
|
|
1472
|
+
launchPolicy(
|
|
1473
|
+
target,
|
|
1474
|
+
prepared,
|
|
1475
|
+
params.task,
|
|
1476
|
+
params.agent,
|
|
1477
|
+
thinkingLevel,
|
|
1478
|
+
budget.timeoutMs,
|
|
1479
|
+
taskGeneration,
|
|
1480
|
+
budget,
|
|
1481
|
+
resolveTurnLimits(params),
|
|
1482
|
+
),
|
|
1483
|
+
)
|
|
1484
|
+
: exhaustedResult(params.agent, params.task, thinkingLevel),
|
|
546
1485
|
target,
|
|
547
1486
|
);
|
|
1487
|
+
settleWorkItem("task-1", result, taskGeneration);
|
|
548
1488
|
const isError = isResultError(result);
|
|
549
1489
|
if (isError) {
|
|
550
1490
|
const errorMsg = formatResultFailure(result);
|