@narumitw/pi-subagents 0.49.2 → 0.51.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 +313 -53
- package/package.json +11 -8
- package/src/adaptive-scheduler.ts +196 -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 +848 -158
- package/src/in-process-transport.ts +269 -25
- package/src/inspect-render.ts +101 -1
- package/src/inspect.ts +296 -3
- package/src/integration-controller.ts +98 -0
- package/src/limits.ts +3 -0
- package/src/orchestration-metrics.ts +78 -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 +772 -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 +172 -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 +17 -0
- package/src/work-item-ledger.ts +682 -0
- package/src/work-item-persistence.ts +218 -0
- package/src/workflow-planning.ts +150 -0
- package/src/workflow-ui.ts +61 -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,41 @@ 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";
|
|
10
24
|
import {
|
|
11
25
|
assertDelegationTargetAllowed,
|
|
12
26
|
type ResolvedSubagentTarget,
|
|
13
27
|
resolveSubagentTarget,
|
|
14
28
|
targetPolicyAudit,
|
|
15
29
|
} from "./cwd-policy.js";
|
|
16
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
appendDelegationContract,
|
|
32
|
+
type DelegationContract,
|
|
33
|
+
normalizeDelegationContract,
|
|
34
|
+
} from "./delegation-contract.js";
|
|
35
|
+
import {
|
|
36
|
+
acknowledgeExecutionPlan,
|
|
37
|
+
createExecutionPlan,
|
|
38
|
+
resolveContractTools,
|
|
39
|
+
} from "./execution-plan.js";
|
|
40
|
+
import {
|
|
41
|
+
DEFAULT_MAX_CONTEXT_BYTES,
|
|
42
|
+
MAX_BLOCKING_PARALLEL_CONCURRENCY,
|
|
43
|
+
MAX_SUBAGENT_TIMEOUT_MS,
|
|
44
|
+
truncateUtf8,
|
|
45
|
+
} from "./limits.js";
|
|
46
|
+
import { calculateOrchestrationMetrics } from "./orchestration-metrics.js";
|
|
47
|
+
import { executePanel, preflightPanelExecution } from "./panel-execution.js";
|
|
48
|
+
import { validatePanelRequest } from "./panel-planning.js";
|
|
17
49
|
import { hasUsableAggregator, type SubagentParams } from "./params.js";
|
|
50
|
+
import { appendResultInstruction, type SubagentResultFormat } from "./result-contract.js";
|
|
18
51
|
import {
|
|
19
52
|
buildFanInContext,
|
|
20
53
|
formatResultFailure,
|
|
@@ -30,14 +63,21 @@ import { safeTerminalLine } from "./safe-text.js";
|
|
|
30
63
|
import {
|
|
31
64
|
DEFAULT_DELEGATION_CWD_POLICY,
|
|
32
65
|
readSubagentSettings,
|
|
66
|
+
resolveBlockingMaxParallelTasks,
|
|
33
67
|
resolveSubagentThinkingLevel,
|
|
34
68
|
} from "./settings.js";
|
|
69
|
+
import { isRetryableResult, runHedgedAttempt, supervisionDelay } from "./supervision.js";
|
|
70
|
+
import { TimeoutProgressJournal, TURN_TERMINATION_VERSION } from "./timeout-checkpoint.js";
|
|
71
|
+
import type { TurnLimits } from "./turn-budget.js";
|
|
72
|
+
import { requiresIndependentVerification } from "./verification-policy.js";
|
|
73
|
+
import type { WorkItemLedger } from "./work-item-ledger.js";
|
|
74
|
+
import {
|
|
75
|
+
createSessionWorkItemPersistence,
|
|
76
|
+
type WorkItemPersistence,
|
|
77
|
+
} from "./work-item-persistence.js";
|
|
78
|
+
import { createBlockingWorkLedger, resolveWorkflowTasks } from "./workflow-planning.js";
|
|
35
79
|
|
|
36
|
-
const MAX_PARALLEL_TASKS = 8;
|
|
37
|
-
const MAX_CONCURRENCY = 4;
|
|
38
80
|
export const FALLBACK_TIMEOUT_MS = 10 * 60 * 1000;
|
|
39
|
-
const STATUS_KEY = "subagents";
|
|
40
|
-
const activeStatuses = new Map<string, string>();
|
|
41
81
|
|
|
42
82
|
export function parsePositiveInteger(value: string | undefined): number | undefined {
|
|
43
83
|
if (!value) return undefined;
|
|
@@ -57,59 +97,6 @@ export function assertSubagentDepthAllowed(): void {
|
|
|
57
97
|
}
|
|
58
98
|
}
|
|
59
99
|
|
|
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
100
|
export async function executeSubagent(
|
|
114
101
|
toolCallId: string,
|
|
115
102
|
params: SubagentParams,
|
|
@@ -125,8 +112,10 @@ export async function executeSubagent(
|
|
|
125
112
|
}
|
|
126
113
|
const aggregator = hasUsableAggregator(params.aggregator) ? params.aggregator : undefined;
|
|
127
114
|
const config = settingsOverride ?? readSubagentSettings();
|
|
115
|
+
const maxParallelTasks = resolveBlockingMaxParallelTasks(config);
|
|
128
116
|
const discovery = discoverAgents(ctx.cwd, agentScope, config);
|
|
129
117
|
const agents = discovery.agents;
|
|
118
|
+
const resolvedWorkflowTasks = resolveWorkflowTasks(params, agents);
|
|
130
119
|
const confirmProjectAgents = params.confirmProjectAgents ?? true;
|
|
131
120
|
const resolveTimeoutMs = (agentName: string, localTimeoutMs?: number) =>
|
|
132
121
|
localTimeoutMs ??
|
|
@@ -135,21 +124,112 @@ export async function executeSubagent(
|
|
|
135
124
|
resolveDefaultSubagentTimeoutMs();
|
|
136
125
|
const resolveThinkingLevel = (agentName: string, localThinkingLevel?: SubagentThinkingLevel) =>
|
|
137
126
|
resolveSubagentThinkingLevel(agents, agentName, params.thinkingLevel, localThinkingLevel);
|
|
127
|
+
let orchestrationDeadline: number | undefined;
|
|
128
|
+
const resolveTurnLimits = (local?: TurnLimits): TurnLimits => ({
|
|
129
|
+
idleTimeoutMs: local?.idleTimeoutMs ?? params.idleTimeoutMs,
|
|
130
|
+
maxTurns: local?.maxTurns ?? params.maxTurns,
|
|
131
|
+
maxToolCalls: local?.maxToolCalls ?? params.maxToolCalls,
|
|
132
|
+
});
|
|
133
|
+
const resolveExecutionBudget = (
|
|
134
|
+
agentName: string,
|
|
135
|
+
localTimeoutMs?: number,
|
|
136
|
+
):
|
|
137
|
+
| {
|
|
138
|
+
timeoutMs: number;
|
|
139
|
+
workTimeoutReason: "work_timeout" | "orchestration_timeout";
|
|
140
|
+
workTimeoutReportLimit: number;
|
|
141
|
+
}
|
|
142
|
+
| undefined => {
|
|
143
|
+
const requested = resolveTimeoutMs(agentName, localTimeoutMs);
|
|
144
|
+
if (orchestrationDeadline === undefined) {
|
|
145
|
+
return {
|
|
146
|
+
timeoutMs: requested,
|
|
147
|
+
workTimeoutReason: "work_timeout",
|
|
148
|
+
workTimeoutReportLimit: requested,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
const remaining = Math.floor(orchestrationDeadline - Date.now());
|
|
152
|
+
if (remaining < 1) return undefined;
|
|
153
|
+
const orchestrationLimited = remaining < requested;
|
|
154
|
+
return {
|
|
155
|
+
timeoutMs: Math.min(requested, remaining),
|
|
156
|
+
workTimeoutReason: orchestrationLimited ? "orchestration_timeout" : "work_timeout",
|
|
157
|
+
workTimeoutReportLimit: orchestrationLimited
|
|
158
|
+
? Math.floor(params.totalTimeoutMs as number)
|
|
159
|
+
: requested,
|
|
160
|
+
};
|
|
161
|
+
};
|
|
138
162
|
|
|
139
163
|
const hasChain = (params.chain?.length ?? 0) > 0;
|
|
140
164
|
const hasTasks = (params.tasks?.length ?? 0) > 0;
|
|
165
|
+
const hasWorkflow = (params.workflow?.tasks.length ?? 0) > 0;
|
|
166
|
+
const hasPanel = params.panel !== undefined;
|
|
141
167
|
const hasSingle = Boolean(params.agent && params.task);
|
|
142
|
-
const modeCount =
|
|
168
|
+
const modeCount =
|
|
169
|
+
Number(hasChain) +
|
|
170
|
+
Number(hasTasks) +
|
|
171
|
+
Number(hasWorkflow) +
|
|
172
|
+
Number(hasPanel) +
|
|
173
|
+
Number(hasSingle);
|
|
174
|
+
let workLedger: WorkItemLedger | undefined;
|
|
175
|
+
const workflowScheduling: ReturnType<AdaptiveScheduler["decide"]>[] = [];
|
|
143
176
|
|
|
144
177
|
const makeDetails =
|
|
145
|
-
(mode: "single" | "parallel" | "chain") =>
|
|
146
|
-
(results: SingleResult[], aggregator?: SingleResult): SubagentDetails =>
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
178
|
+
(mode: "single" | "parallel" | "chain" | "workflow" | "panel") =>
|
|
179
|
+
(results: SingleResult[], aggregator?: SingleResult): SubagentDetails => {
|
|
180
|
+
const workflow = workLedger?.snapshot();
|
|
181
|
+
const metricResults = aggregator ? [...results, aggregator] : results;
|
|
182
|
+
return {
|
|
183
|
+
mode,
|
|
184
|
+
agentScope,
|
|
185
|
+
projectAgentsDir: discovery.projectAgentsDir,
|
|
186
|
+
results,
|
|
187
|
+
aggregator,
|
|
188
|
+
workflow,
|
|
189
|
+
schedulerDecisions:
|
|
190
|
+
workflowScheduling.length > 0 ? workflowScheduling.slice(-64) : undefined,
|
|
191
|
+
metrics: calculateOrchestrationMetrics(workflow, metricResults),
|
|
192
|
+
};
|
|
193
|
+
};
|
|
194
|
+
const exhaustedResult = (
|
|
195
|
+
agentName: string,
|
|
196
|
+
task: string,
|
|
197
|
+
thinkingLevel: SubagentThinkingLevel | undefined,
|
|
198
|
+
step?: number,
|
|
199
|
+
): SingleResult => {
|
|
200
|
+
const limit = Math.floor(params.totalTimeoutMs as number);
|
|
201
|
+
const message = `Subagent orchestration deadline expired after ${limit}ms`;
|
|
202
|
+
return {
|
|
203
|
+
agent: agentName,
|
|
204
|
+
agentSource: agents.find((agent) => agent.name === agentName)?.source ?? "unknown",
|
|
205
|
+
task,
|
|
206
|
+
exitCode: 124,
|
|
207
|
+
messages: [],
|
|
208
|
+
stderr: message,
|
|
209
|
+
errorMessage: message,
|
|
210
|
+
usage: {
|
|
211
|
+
input: 0,
|
|
212
|
+
output: 0,
|
|
213
|
+
cacheRead: 0,
|
|
214
|
+
cacheWrite: 0,
|
|
215
|
+
cost: 0,
|
|
216
|
+
contextTokens: 0,
|
|
217
|
+
turns: 0,
|
|
218
|
+
},
|
|
219
|
+
thinkingLevel,
|
|
220
|
+
step,
|
|
221
|
+
finalOutput: "",
|
|
222
|
+
timedOut: true,
|
|
223
|
+
stopReason: "timeout",
|
|
224
|
+
termination: {
|
|
225
|
+
version: TURN_TERMINATION_VERSION,
|
|
226
|
+
reason: "orchestration_timeout",
|
|
227
|
+
limit,
|
|
228
|
+
checkpoint: new TimeoutProgressJournal().checkpoint(task),
|
|
229
|
+
finalization: { attempted: false, status: "skipped", durationMs: 0 },
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
};
|
|
153
233
|
|
|
154
234
|
if (modeCount !== 1 || (aggregator && !hasTasks)) {
|
|
155
235
|
const available = agents.map((a) => `${a.name} (${a.source})`).join(", ") || "none";
|
|
@@ -167,6 +247,117 @@ export async function executeSubagent(
|
|
|
167
247
|
details: makeDetails("single")([]),
|
|
168
248
|
};
|
|
169
249
|
}
|
|
250
|
+
if (
|
|
251
|
+
(hasWorkflow || hasPanel) &&
|
|
252
|
+
(Number.parseInt(process.env.PI_SUBAGENT_DEPTH ?? "0", 10) || 0) > 0
|
|
253
|
+
) {
|
|
254
|
+
throw new Error("Explicit workflow and panel recursion is disabled until separately evaluated");
|
|
255
|
+
}
|
|
256
|
+
if (params.panel) {
|
|
257
|
+
validatePanelRequest(params.panel, maxParallelTasks);
|
|
258
|
+
for (const agentName of [
|
|
259
|
+
...params.panel.reviewers.map((reviewer) => reviewer.agent),
|
|
260
|
+
params.panel.synthesizer.agent,
|
|
261
|
+
]) {
|
|
262
|
+
if (!agents.some((agent) => agent.name === agentName)) {
|
|
263
|
+
throw new Error(`Unknown panel agent: ${agentName}`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (
|
|
268
|
+
(hasTasks && (params.tasks?.length ?? 0) > maxParallelTasks) ||
|
|
269
|
+
(hasWorkflow && (params.workflow?.tasks.length ?? 0) > maxParallelTasks) ||
|
|
270
|
+
(hasPanel && (params.panel?.reviewers.length ?? 0) > maxParallelTasks)
|
|
271
|
+
) {
|
|
272
|
+
const count = hasWorkflow
|
|
273
|
+
? (params.workflow?.tasks.length ?? 0)
|
|
274
|
+
: hasPanel
|
|
275
|
+
? (params.panel?.reviewers.length ?? 0)
|
|
276
|
+
: (params.tasks?.length ?? 0);
|
|
277
|
+
throw new Error(`Too many delegated tasks (${count}). Configured max is ${maxParallelTasks}.`);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const nonWorkflowRetryConfigured =
|
|
281
|
+
!hasWorkflow &&
|
|
282
|
+
Boolean(
|
|
283
|
+
params.retryPolicy ||
|
|
284
|
+
params.hedgeAfterMs ||
|
|
285
|
+
params.tasks?.some((task) => task.retryPolicy || task.hedgeAfterMs) ||
|
|
286
|
+
params.chain?.some((task) => task.retryPolicy || task.hedgeAfterMs) ||
|
|
287
|
+
aggregator?.retryPolicy ||
|
|
288
|
+
aggregator?.hedgeAfterMs,
|
|
289
|
+
);
|
|
290
|
+
if (nonWorkflowRetryConfigured) {
|
|
291
|
+
throw new Error("Retry and hedge policies are supported only by explicit workflow tasks");
|
|
292
|
+
}
|
|
293
|
+
if (hasWorkflow && params.workflow) {
|
|
294
|
+
for (const task of resolvedWorkflowTasks) {
|
|
295
|
+
const contract = normalizeDelegationContract(task.contract);
|
|
296
|
+
if (params.workflow.honorAdmission) {
|
|
297
|
+
const admission = contract?.admission;
|
|
298
|
+
const decision = evaluateDelegationAdmission({
|
|
299
|
+
contextPressure: admission?.contextPressure ?? "low",
|
|
300
|
+
independentWorkItems: admission?.independentWorkItems ?? 1,
|
|
301
|
+
coupling: admission?.coupling ?? "dense",
|
|
302
|
+
verificationRequired: admission?.verificationRequired ?? false,
|
|
303
|
+
verificationAvailable: admission?.verificationAvailable ?? false,
|
|
304
|
+
capabilitiesSupported: true,
|
|
305
|
+
budgetAllowsChildren: admission?.budgetAllowsChildren ?? false,
|
|
306
|
+
generationCurrent: true,
|
|
307
|
+
requirementsComplete: admission?.requirementsComplete ?? false,
|
|
308
|
+
});
|
|
309
|
+
if (
|
|
310
|
+
decision.recommendation === "parent-owned-direct" ||
|
|
311
|
+
decision.recommendation === "abstain-insufficient-evidence"
|
|
312
|
+
) {
|
|
313
|
+
throw new Error(
|
|
314
|
+
`Admission declined workflow task ${task.id}: ${decision.reasonCodes.join(", ")}`,
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (
|
|
319
|
+
requiresIndependentVerification({
|
|
320
|
+
contract,
|
|
321
|
+
integrationOwner: task.integrationOwner === true,
|
|
322
|
+
requiredCapabilities: task.requiredCapabilities ?? [],
|
|
323
|
+
})
|
|
324
|
+
) {
|
|
325
|
+
const verifier = resolvedWorkflowTasks.find(
|
|
326
|
+
(candidate) =>
|
|
327
|
+
candidate.verifierFor === task.id &&
|
|
328
|
+
candidate.dependsOn?.includes(task.id) &&
|
|
329
|
+
candidate.agent !== task.agent,
|
|
330
|
+
);
|
|
331
|
+
if (!verifier) {
|
|
332
|
+
throw new Error(
|
|
333
|
+
`Workflow task ${task.id} requires a distinct dependent verifier before launch`,
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
if (!task.retryPolicy && !task.hedgeAfterMs) continue;
|
|
338
|
+
const policy = contract?.sideEffectPolicy;
|
|
339
|
+
if (
|
|
340
|
+
(task.retryPolicy && policy !== "read-only" && policy !== "idempotent") ||
|
|
341
|
+
(task.hedgeAfterMs && policy !== "read-only")
|
|
342
|
+
) {
|
|
343
|
+
throw new Error(
|
|
344
|
+
`Workflow task ${task.id} must declare an idempotent retry or read-only hedge delegation contract`,
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
workLedger = createBlockingWorkLedger(params, resolvedWorkflowTasks, aggregator);
|
|
350
|
+
let workflowPersistence: WorkItemPersistence | undefined;
|
|
351
|
+
if (hasWorkflow && workLedger) {
|
|
352
|
+
const owner =
|
|
353
|
+
ctx.sessionManager.getSessionId?.() ??
|
|
354
|
+
ctx.sessionManager.getSessionFile?.() ??
|
|
355
|
+
`ephemeral:${ctx.cwd}`;
|
|
356
|
+
workflowPersistence = createSessionWorkItemPersistence(owner, workLedger.workflowId);
|
|
357
|
+
}
|
|
358
|
+
const persistWorkLedger = async () => {
|
|
359
|
+
if (workflowPersistence && workLedger) await workflowPersistence.save(workLedger.snapshot());
|
|
360
|
+
};
|
|
170
361
|
|
|
171
362
|
const delegationPolicy = config?.cwdPolicy?.delegation ?? DEFAULT_DELEGATION_CWD_POLICY;
|
|
172
363
|
const resolveTarget = (cwd: string | undefined): ResolvedSubagentTarget => {
|
|
@@ -179,22 +370,215 @@ export async function executeSubagent(
|
|
|
179
370
|
return target;
|
|
180
371
|
};
|
|
181
372
|
const singleTarget = hasSingle ? resolveTarget(params.cwd) : undefined;
|
|
373
|
+
const panelTarget = hasPanel ? resolveTarget(undefined) : undefined;
|
|
182
374
|
const chainTargets = params.chain?.map((step) => resolveTarget(step.cwd)) ?? [];
|
|
183
375
|
const parallelTargets = params.tasks?.map((task) => resolveTarget(task.cwd)) ?? [];
|
|
376
|
+
const workflowTargets = resolvedWorkflowTasks.map((task) => resolveTarget(task.cwd));
|
|
184
377
|
const aggregatorTarget = aggregator ? resolveTarget(aggregator.cwd) : undefined;
|
|
378
|
+
if (params.panel && panelTarget) {
|
|
379
|
+
await preflightPanelExecution({
|
|
380
|
+
panel: params.panel,
|
|
381
|
+
agents,
|
|
382
|
+
signal,
|
|
383
|
+
target: panelTarget,
|
|
384
|
+
resolveThinkingLevel,
|
|
385
|
+
resolveTimeoutMs,
|
|
386
|
+
});
|
|
387
|
+
}
|
|
185
388
|
const attachTarget = (result: SingleResult, target: ResolvedSubagentTarget): SingleResult => {
|
|
186
389
|
result.target = targetPolicyAudit(target);
|
|
187
390
|
return result;
|
|
188
391
|
};
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
392
|
+
type ContractRequest = {
|
|
393
|
+
contract?: unknown;
|
|
394
|
+
resultFormat?: SubagentResultFormat;
|
|
395
|
+
};
|
|
396
|
+
const prepareTask = (task: string, local?: ContractRequest) => {
|
|
397
|
+
const contracted = appendDelegationContract(task, local?.contract ?? params.contract);
|
|
398
|
+
const resultFormat = local?.resultFormat ?? params.resultFormat;
|
|
399
|
+
return {
|
|
400
|
+
text: appendResultInstruction(contracted.text, resultFormat, DEFAULT_MAX_CONTEXT_BYTES),
|
|
401
|
+
contract: contracted.contract,
|
|
402
|
+
resultFormat,
|
|
403
|
+
};
|
|
404
|
+
};
|
|
405
|
+
const launchPolicy = (
|
|
406
|
+
target: ResolvedSubagentTarget,
|
|
407
|
+
prepared: { contract?: DelegationContract; resultFormat?: SubagentResultFormat },
|
|
408
|
+
displayTask: string,
|
|
409
|
+
agentName: string,
|
|
410
|
+
thinkingLevel: SubagentThinkingLevel | undefined,
|
|
411
|
+
timeoutMs: number,
|
|
412
|
+
taskGeneration = 0,
|
|
413
|
+
budget?: NonNullable<ReturnType<typeof resolveExecutionBudget>>,
|
|
414
|
+
turnLimits?: TurnLimits,
|
|
415
|
+
) => {
|
|
416
|
+
const agent = agents.find((candidate) => candidate.name === agentName);
|
|
417
|
+
const effectiveTools = agent ? resolveContractTools(agent.tools, prepared.contract) : undefined;
|
|
418
|
+
const executionPlan = agent
|
|
419
|
+
? createExecutionPlan({
|
|
420
|
+
contract: prepared.contract,
|
|
421
|
+
agent,
|
|
422
|
+
effectiveTools,
|
|
423
|
+
target: targetPolicyAudit(target),
|
|
424
|
+
workspaceMode: "shared",
|
|
425
|
+
transport: "subprocess",
|
|
426
|
+
resultFormat: prepared.resultFormat ?? "text",
|
|
427
|
+
model: agent.model,
|
|
428
|
+
thinkingLevel,
|
|
429
|
+
timeoutMs,
|
|
430
|
+
taskGeneration,
|
|
431
|
+
})
|
|
432
|
+
: undefined;
|
|
433
|
+
if (executionPlan) {
|
|
434
|
+
const acknowledgement = acknowledgeExecutionPlan(executionPlan);
|
|
435
|
+
if (acknowledgement.status === "rejected") {
|
|
436
|
+
throw new Error(`Execution plan rejected: ${JSON.stringify(acknowledgement)}`);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
const capabilityGrant = executionPlan
|
|
440
|
+
? issueCapabilityGrant(executionPlan, Date.now(), Math.max(1, timeoutMs + 60_000))
|
|
441
|
+
: undefined;
|
|
442
|
+
return {
|
|
443
|
+
projectTrust: target.trust.projectTrusted,
|
|
444
|
+
turnLimits,
|
|
445
|
+
workTimeoutReason: budget?.workTimeoutReason,
|
|
446
|
+
workTimeoutReportLimit: budget?.workTimeoutReportLimit,
|
|
447
|
+
orchestrationDeadlineAt: budget ? orchestrationDeadline : undefined,
|
|
448
|
+
tools: effectiveTools,
|
|
449
|
+
contract: prepared.contract,
|
|
450
|
+
resultFormat: prepared.resultFormat,
|
|
451
|
+
displayTask,
|
|
452
|
+
executionPlan,
|
|
453
|
+
capabilityGrant,
|
|
454
|
+
};
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
// Build and acknowledge every contracted plan before confirmation or child launch.
|
|
458
|
+
if (hasSingle && singleTarget && params.agent && params.task) {
|
|
459
|
+
const prepared = prepareTask(params.task, params);
|
|
460
|
+
launchPolicy(
|
|
461
|
+
singleTarget,
|
|
462
|
+
prepared,
|
|
463
|
+
params.task,
|
|
464
|
+
params.agent,
|
|
465
|
+
resolveThinkingLevel(params.agent, params.thinkingLevel),
|
|
466
|
+
resolveTimeoutMs(params.agent, params.timeoutMs),
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
for (const [index, step] of (params.chain ?? []).entries()) {
|
|
470
|
+
const prepared = prepareTask(step.task, step);
|
|
471
|
+
launchPolicy(
|
|
472
|
+
chainTargets[index],
|
|
473
|
+
prepared,
|
|
474
|
+
step.task,
|
|
475
|
+
step.agent,
|
|
476
|
+
resolveThinkingLevel(step.agent, step.thinkingLevel),
|
|
477
|
+
resolveTimeoutMs(step.agent, step.timeoutMs),
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
for (const [index, task] of (params.tasks ?? []).entries()) {
|
|
481
|
+
const prepared = prepareTask(task.task, task);
|
|
482
|
+
launchPolicy(
|
|
483
|
+
parallelTargets[index],
|
|
484
|
+
prepared,
|
|
485
|
+
task.task,
|
|
486
|
+
task.agent,
|
|
487
|
+
resolveThinkingLevel(task.agent, task.thinkingLevel),
|
|
488
|
+
resolveTimeoutMs(task.agent, task.timeoutMs),
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
for (const [index, task] of resolvedWorkflowTasks.entries()) {
|
|
492
|
+
const prepared = prepareTask(task.task, task);
|
|
493
|
+
launchPolicy(
|
|
494
|
+
workflowTargets[index],
|
|
495
|
+
prepared,
|
|
496
|
+
task.task,
|
|
497
|
+
task.agent,
|
|
498
|
+
resolveThinkingLevel(task.agent, task.thinkingLevel),
|
|
499
|
+
resolveTimeoutMs(task.agent, task.timeoutMs),
|
|
500
|
+
workLedger?.get(task.id)?.taskGeneration ?? 0,
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
if (aggregator && aggregatorTarget) {
|
|
504
|
+
const prepared = prepareTask(aggregator.task, aggregator);
|
|
505
|
+
launchPolicy(
|
|
506
|
+
aggregatorTarget,
|
|
507
|
+
prepared,
|
|
508
|
+
aggregator.task,
|
|
509
|
+
aggregator.agent,
|
|
510
|
+
resolveThinkingLevel(aggregator.agent, aggregator.thinkingLevel),
|
|
511
|
+
resolveTimeoutMs(aggregator.agent, aggregator.timeoutMs),
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const startWorkItem = (id: string, agentName: string) => {
|
|
516
|
+
if (workLedger?.get(id)?.state === "ready") {
|
|
517
|
+
return workLedger.start(id, `agent:${agentName}`);
|
|
518
|
+
}
|
|
519
|
+
return workLedger?.get(id);
|
|
520
|
+
};
|
|
521
|
+
const settleWorkItem = (id: string, result: SingleResult, taskGeneration: number) => {
|
|
522
|
+
if (!workLedger) return;
|
|
523
|
+
if (workLedger.get(id)?.taskGeneration !== taskGeneration) {
|
|
524
|
+
result.outcome = {
|
|
525
|
+
status: "stale",
|
|
526
|
+
reasonCode: "stale-task-generation",
|
|
527
|
+
recoveryActions: ["discard", "replan"],
|
|
528
|
+
retryable: false,
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
if (result.outcome?.status === "stale") {
|
|
532
|
+
workLedger.invalidate(id, result.outcome.reasonCode ?? "stale-result");
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
if (isResultError(result)) {
|
|
536
|
+
const state =
|
|
537
|
+
result.outcome?.status === "blocked"
|
|
538
|
+
? "blocked"
|
|
539
|
+
: result.outcome?.status === "needs-input"
|
|
540
|
+
? "needs-input"
|
|
541
|
+
: result.aborted || result.outcome?.status === "interrupted"
|
|
542
|
+
? "interrupted"
|
|
543
|
+
: "failed";
|
|
544
|
+
workLedger.settle(
|
|
545
|
+
id,
|
|
546
|
+
state,
|
|
547
|
+
result.outcome?.reasonCode ?? result.errorMessage ?? result.stopReason,
|
|
548
|
+
);
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
const structured =
|
|
552
|
+
result.structuredResult?.version === "pi-subagents:result:v2"
|
|
553
|
+
? result.structuredResult
|
|
554
|
+
: undefined;
|
|
555
|
+
workLedger.complete(id, {
|
|
556
|
+
taskGeneration,
|
|
557
|
+
executionPlanId: result.executionPlan?.id,
|
|
558
|
+
artifacts: (structured?.artifacts ?? []).map((artifact) => ({
|
|
559
|
+
id: artifact.id,
|
|
560
|
+
kind: artifact.kind,
|
|
561
|
+
version: artifact.version ?? artifact.digest ?? "unversioned",
|
|
562
|
+
digest: artifact.digest,
|
|
563
|
+
verified:
|
|
564
|
+
structured?.verification.some((verification) => verification.status === "passed") ??
|
|
565
|
+
false,
|
|
566
|
+
})),
|
|
567
|
+
verificationAccepted:
|
|
568
|
+
structured?.verification.some((verification) => verification.status === "passed") ?? false,
|
|
569
|
+
});
|
|
570
|
+
};
|
|
192
571
|
|
|
193
572
|
if (agentScope === "project" || agentScope === "both") {
|
|
194
573
|
const requestedAgentNames = new Set<string>();
|
|
195
574
|
if (params.chain) for (const step of params.chain) requestedAgentNames.add(step.agent);
|
|
196
575
|
if (params.tasks) for (const t of params.tasks) requestedAgentNames.add(t.agent);
|
|
576
|
+
for (const task of resolvedWorkflowTasks) requestedAgentNames.add(task.agent);
|
|
197
577
|
if (aggregator) requestedAgentNames.add(aggregator.agent);
|
|
578
|
+
if (params.panel) {
|
|
579
|
+
for (const reviewer of params.panel.reviewers) requestedAgentNames.add(reviewer.agent);
|
|
580
|
+
requestedAgentNames.add(params.panel.synthesizer.agent);
|
|
581
|
+
}
|
|
198
582
|
if (params.agent) requestedAgentNames.add(params.agent);
|
|
199
583
|
|
|
200
584
|
const projectAgentsRequested = Array.from(requestedAgentNames)
|
|
@@ -222,13 +606,258 @@ export async function executeSubagent(
|
|
|
222
606
|
if (!ok) {
|
|
223
607
|
return {
|
|
224
608
|
content: [{ type: "text", text: "Canceled: project-local agents not approved." }],
|
|
225
|
-
details: makeDetails(
|
|
609
|
+
details: makeDetails(
|
|
610
|
+
hasChain
|
|
611
|
+
? "chain"
|
|
612
|
+
: hasTasks
|
|
613
|
+
? "parallel"
|
|
614
|
+
: hasWorkflow
|
|
615
|
+
? "workflow"
|
|
616
|
+
: hasPanel
|
|
617
|
+
? "panel"
|
|
618
|
+
: "single",
|
|
619
|
+
)([]),
|
|
226
620
|
};
|
|
227
621
|
}
|
|
228
622
|
}
|
|
229
623
|
}
|
|
230
624
|
}
|
|
231
625
|
|
|
626
|
+
orchestrationDeadline =
|
|
627
|
+
params.totalTimeoutMs === undefined
|
|
628
|
+
? undefined
|
|
629
|
+
: Date.now() + Math.floor(params.totalTimeoutMs);
|
|
630
|
+
if (params.panel && panelTarget) {
|
|
631
|
+
return executePanel({
|
|
632
|
+
toolCallId,
|
|
633
|
+
params,
|
|
634
|
+
panel: params.panel,
|
|
635
|
+
signal,
|
|
636
|
+
onUpdate,
|
|
637
|
+
ctx,
|
|
638
|
+
agents,
|
|
639
|
+
agentScope,
|
|
640
|
+
projectAgentsDir: discovery.projectAgentsDir,
|
|
641
|
+
maxParallelTasks,
|
|
642
|
+
target: panelTarget,
|
|
643
|
+
resolveThinkingLevel,
|
|
644
|
+
resolveTimeoutMs,
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
if (params.workflow && resolvedWorkflowTasks.length > 0 && workLedger) {
|
|
648
|
+
await persistWorkLedger();
|
|
649
|
+
const status = startSubagentStatus(
|
|
650
|
+
ctx,
|
|
651
|
+
toolCallId,
|
|
652
|
+
parallelStatus(0, resolvedWorkflowTasks.length, 0),
|
|
653
|
+
);
|
|
654
|
+
const scheduler = new AdaptiveScheduler();
|
|
655
|
+
const taskById = new Map(
|
|
656
|
+
resolvedWorkflowTasks.map((task, index) => [task.id, { task, index }]),
|
|
657
|
+
);
|
|
658
|
+
const resultsById = new Map<string, SingleResult>();
|
|
659
|
+
const deadline = orchestrationDeadline;
|
|
660
|
+
const cancelWorkflowGeneration = () => {
|
|
661
|
+
for (const item of workLedger.snapshot().items) {
|
|
662
|
+
if (item.state === "running") workLedger.invalidate(item.id, "parent-aborted");
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
signal?.addEventListener("abort", cancelWorkflowGeneration, { once: true });
|
|
666
|
+
try {
|
|
667
|
+
while (true) {
|
|
668
|
+
const snapshot = workLedger.snapshot();
|
|
669
|
+
const remainingBudgetMs =
|
|
670
|
+
deadline === undefined
|
|
671
|
+
? MAX_SUBAGENT_TIMEOUT_MS
|
|
672
|
+
: Math.max(0, Math.floor(deadline - Date.now()));
|
|
673
|
+
const decision = scheduler.decide(snapshot, {
|
|
674
|
+
maxConcurrency: Math.min(MAX_BLOCKING_PARALLEL_CONCURRENCY, maxParallelTasks),
|
|
675
|
+
activeCount: 0,
|
|
676
|
+
transportCapacity: MAX_BLOCKING_PARALLEL_CONCURRENCY,
|
|
677
|
+
remainingBudgetMs,
|
|
678
|
+
});
|
|
679
|
+
workflowScheduling.push(decision);
|
|
680
|
+
if (decision.selected.length === 0) break;
|
|
681
|
+
status.update(
|
|
682
|
+
parallelStatus(resultsById.size, resolvedWorkflowTasks.length, decision.selected.length),
|
|
683
|
+
);
|
|
684
|
+
const batch = await mapWithConcurrencyLimit(
|
|
685
|
+
decision.selected,
|
|
686
|
+
decision.effectiveConcurrency,
|
|
687
|
+
async (workItemId) => {
|
|
688
|
+
const entry = taskById.get(workItemId);
|
|
689
|
+
if (!entry) throw new Error(`Missing workflow task ${workItemId}`);
|
|
690
|
+
const { task, index } = entry;
|
|
691
|
+
const dependencies = (task.dependsOn ?? [])
|
|
692
|
+
.map((dependency) => resultsById.get(dependency))
|
|
693
|
+
.filter((result): result is SingleResult => result !== undefined);
|
|
694
|
+
const dependencyContext = dependencies.length
|
|
695
|
+
? `\n\nDependency results:\n${buildFanInContext(dependencies)}`
|
|
696
|
+
: "";
|
|
697
|
+
const displayTask = task.task;
|
|
698
|
+
const taskWithContext = truncateUtf8(
|
|
699
|
+
`${task.task}${dependencyContext}`,
|
|
700
|
+
DEFAULT_MAX_CONTEXT_BYTES,
|
|
701
|
+
).text;
|
|
702
|
+
const prepared = prepareTask(taskWithContext, task);
|
|
703
|
+
const target = workflowTargets[index];
|
|
704
|
+
const thinkingLevel = resolveThinkingLevel(task.agent, task.thinkingLevel);
|
|
705
|
+
const startedItem = startWorkItem(workItemId, task.agent);
|
|
706
|
+
const acceptedTaskGeneration = startedItem?.taskGeneration ?? 0;
|
|
707
|
+
await persistWorkLedger();
|
|
708
|
+
const runAttempt = (attemptSignal: AbortSignal | undefined) => {
|
|
709
|
+
const budget = resolveExecutionBudget(task.agent, task.timeoutMs);
|
|
710
|
+
if (!budget) {
|
|
711
|
+
return Promise.resolve(exhaustedResult(task.agent, displayTask, thinkingLevel));
|
|
712
|
+
}
|
|
713
|
+
return runSingleAgent(
|
|
714
|
+
ctx.cwd,
|
|
715
|
+
agents,
|
|
716
|
+
task.agent,
|
|
717
|
+
prepared.text,
|
|
718
|
+
target.cwd,
|
|
719
|
+
undefined,
|
|
720
|
+
attemptSignal,
|
|
721
|
+
thinkingLevel,
|
|
722
|
+
budget.timeoutMs,
|
|
723
|
+
undefined,
|
|
724
|
+
makeDetails("workflow"),
|
|
725
|
+
undefined,
|
|
726
|
+
launchPolicy(
|
|
727
|
+
target,
|
|
728
|
+
prepared,
|
|
729
|
+
displayTask,
|
|
730
|
+
task.agent,
|
|
731
|
+
thinkingLevel,
|
|
732
|
+
budget.timeoutMs,
|
|
733
|
+
acceptedTaskGeneration,
|
|
734
|
+
budget,
|
|
735
|
+
resolveTurnLimits(task),
|
|
736
|
+
),
|
|
737
|
+
);
|
|
738
|
+
};
|
|
739
|
+
const maxAttempts = task.retryPolicy?.maxAttempts ?? 1;
|
|
740
|
+
let result: SingleResult | undefined;
|
|
741
|
+
let hedged = false;
|
|
742
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
743
|
+
if (attempt > 1 && deadline !== undefined && Date.now() >= deadline) break;
|
|
744
|
+
const attempted = await runHedgedAttempt(runAttempt, signal, task.hedgeAfterMs);
|
|
745
|
+
hedged ||= attempted.hedged;
|
|
746
|
+
result = attachTarget(
|
|
747
|
+
{
|
|
748
|
+
...attempted.result,
|
|
749
|
+
attemptCount: attempt,
|
|
750
|
+
hedged: hedged || undefined,
|
|
751
|
+
},
|
|
752
|
+
target,
|
|
753
|
+
);
|
|
754
|
+
if (!isRetryableResult(result) || attempt >= maxAttempts) break;
|
|
755
|
+
if (deadline !== undefined && Date.now() >= deadline) break;
|
|
756
|
+
await supervisionDelay(task.retryPolicy?.backoffMs ?? 0, signal);
|
|
757
|
+
}
|
|
758
|
+
if (!result) throw new Error(`Workflow task ${workItemId} produced no result`);
|
|
759
|
+
if (workLedger.get(workItemId)?.taskGeneration !== acceptedTaskGeneration) {
|
|
760
|
+
result.outcome = {
|
|
761
|
+
status: "stale",
|
|
762
|
+
reasonCode: "cancelled-generation",
|
|
763
|
+
recoveryActions: ["discard", "replan"],
|
|
764
|
+
retryable: false,
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
resultsById.set(workItemId, result);
|
|
768
|
+
settleWorkItem(workItemId, result, acceptedTaskGeneration);
|
|
769
|
+
await persistWorkLedger();
|
|
770
|
+
return result;
|
|
771
|
+
},
|
|
772
|
+
signal,
|
|
773
|
+
);
|
|
774
|
+
if (batch.length === 0 || signal?.aborted) break;
|
|
775
|
+
}
|
|
776
|
+
for (const item of workLedger.snapshot().items) {
|
|
777
|
+
if (item.state !== "pending" && item.state !== "ready") continue;
|
|
778
|
+
if (signal?.aborted) {
|
|
779
|
+
workLedger.settle(item.id, "interrupted", "parent-aborted");
|
|
780
|
+
} else if (deadline !== undefined && Date.now() >= deadline) {
|
|
781
|
+
workLedger.settle(item.id, "blocked", "budget-exhausted");
|
|
782
|
+
} else {
|
|
783
|
+
const dependencyBlocked = item.dependencies.some(
|
|
784
|
+
(dependency) => workLedger.get(dependency)?.state !== "completed",
|
|
785
|
+
);
|
|
786
|
+
workLedger.settle(
|
|
787
|
+
item.id,
|
|
788
|
+
dependencyBlocked ? "blocked" : "needs-input",
|
|
789
|
+
dependencyBlocked ? "dependency-not-completed" : "artifact-version-mismatch",
|
|
790
|
+
);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
await persistWorkLedger();
|
|
794
|
+
const results = resolvedWorkflowTasks.map((task) => {
|
|
795
|
+
const completed = resultsById.get(task.id);
|
|
796
|
+
if (completed) return completed;
|
|
797
|
+
const item = workLedger.get(task.id);
|
|
798
|
+
const outcomeStatus =
|
|
799
|
+
item?.state === "interrupted"
|
|
800
|
+
? "interrupted"
|
|
801
|
+
: item?.state === "needs-input"
|
|
802
|
+
? "needs-input"
|
|
803
|
+
: "blocked";
|
|
804
|
+
const reasonCode = item?.outcomeReason ?? "dependency-not-satisfied";
|
|
805
|
+
return {
|
|
806
|
+
agent: task.agent,
|
|
807
|
+
agentSource: agents.find((agent) => agent.name === task.agent)?.source ?? "unknown",
|
|
808
|
+
task: task.task,
|
|
809
|
+
exitCode: 1,
|
|
810
|
+
messages: [],
|
|
811
|
+
stderr: "Workflow dependency was not satisfied",
|
|
812
|
+
errorMessage: `Workflow task did not start: ${reasonCode}`,
|
|
813
|
+
aborted: outcomeStatus === "interrupted",
|
|
814
|
+
outcome: {
|
|
815
|
+
status: outcomeStatus,
|
|
816
|
+
reasonCode,
|
|
817
|
+
recoveryActions:
|
|
818
|
+
outcomeStatus === "needs-input"
|
|
819
|
+
? ["supply-input"]
|
|
820
|
+
: outcomeStatus === "interrupted"
|
|
821
|
+
? ["retry"]
|
|
822
|
+
: ["resolve-dependency"],
|
|
823
|
+
retryable: outcomeStatus === "interrupted",
|
|
824
|
+
},
|
|
825
|
+
usage: {
|
|
826
|
+
input: 0,
|
|
827
|
+
output: 0,
|
|
828
|
+
cacheRead: 0,
|
|
829
|
+
cacheWrite: 0,
|
|
830
|
+
cost: 0,
|
|
831
|
+
contextTokens: 0,
|
|
832
|
+
turns: 0,
|
|
833
|
+
},
|
|
834
|
+
finalOutput: "",
|
|
835
|
+
} satisfies SingleResult;
|
|
836
|
+
});
|
|
837
|
+
const successCount = results.filter((result) => !isResultError(result)).length;
|
|
838
|
+
const attemptCount = results.reduce((sum, result) => sum + (result.attemptCount ?? 1), 0);
|
|
839
|
+
const hedgeCount = results.filter((result) => result.hedged).length;
|
|
840
|
+
const isError = successCount !== results.length;
|
|
841
|
+
return {
|
|
842
|
+
content: [
|
|
843
|
+
{
|
|
844
|
+
type: "text",
|
|
845
|
+
text: `Workflow: ${successCount}/${results.length} succeeded; ${attemptCount} attempt(s), ${hedgeCount} hedged task(s).`,
|
|
846
|
+
},
|
|
847
|
+
],
|
|
848
|
+
details: { ...makeDetails("workflow")(results), isError },
|
|
849
|
+
isError: isError || undefined,
|
|
850
|
+
};
|
|
851
|
+
} finally {
|
|
852
|
+
signal?.removeEventListener("abort", cancelWorkflowGeneration);
|
|
853
|
+
try {
|
|
854
|
+
await persistWorkLedger();
|
|
855
|
+
} finally {
|
|
856
|
+
status.clear();
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
|
|
232
861
|
if (params.chain && params.chain.length > 0) {
|
|
233
862
|
const results: SingleResult[] = [];
|
|
234
863
|
let previousOutput = "";
|
|
@@ -242,6 +871,7 @@ export async function executeSubagent(
|
|
|
242
871
|
step.task.replace(/\{previous\}/g, previousOutput),
|
|
243
872
|
DEFAULT_MAX_CONTEXT_BYTES,
|
|
244
873
|
).text;
|
|
874
|
+
const prepared = prepareTask(taskWithContext, step);
|
|
245
875
|
|
|
246
876
|
// Create update callback that includes all previous results
|
|
247
877
|
const chainUpdate: OnUpdateCallback | undefined = onUpdate
|
|
@@ -259,25 +889,41 @@ export async function executeSubagent(
|
|
|
259
889
|
: undefined;
|
|
260
890
|
|
|
261
891
|
const target = chainTargets[i];
|
|
892
|
+
const thinkingLevel = resolveThinkingLevel(step.agent, step.thinkingLevel);
|
|
893
|
+
const budget = resolveExecutionBudget(step.agent, step.timeoutMs);
|
|
894
|
+
const taskGeneration = startWorkItem(`step-${i + 1}`, step.agent)?.taskGeneration ?? 0;
|
|
262
895
|
const result = attachTarget(
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
896
|
+
budget
|
|
897
|
+
? await runSingleAgent(
|
|
898
|
+
ctx.cwd,
|
|
899
|
+
agents,
|
|
900
|
+
step.agent,
|
|
901
|
+
prepared.text,
|
|
902
|
+
target.cwd,
|
|
903
|
+
i + 1,
|
|
904
|
+
signal,
|
|
905
|
+
thinkingLevel,
|
|
906
|
+
budget.timeoutMs,
|
|
907
|
+
chainUpdate,
|
|
908
|
+
makeDetails("chain"),
|
|
909
|
+
undefined,
|
|
910
|
+
launchPolicy(
|
|
911
|
+
target,
|
|
912
|
+
prepared,
|
|
913
|
+
taskWithContext,
|
|
914
|
+
step.agent,
|
|
915
|
+
thinkingLevel,
|
|
916
|
+
budget.timeoutMs,
|
|
917
|
+
taskGeneration,
|
|
918
|
+
budget,
|
|
919
|
+
resolveTurnLimits(step),
|
|
920
|
+
),
|
|
921
|
+
)
|
|
922
|
+
: exhaustedResult(step.agent, taskWithContext, thinkingLevel, i + 1),
|
|
278
923
|
target,
|
|
279
924
|
);
|
|
280
925
|
results.push(result);
|
|
926
|
+
settleWorkItem(`step-${i + 1}`, result, taskGeneration);
|
|
281
927
|
|
|
282
928
|
const isError = isResultError(result);
|
|
283
929
|
if (isError) {
|
|
@@ -290,7 +936,9 @@ export async function executeSubagent(
|
|
|
290
936
|
isError: true,
|
|
291
937
|
};
|
|
292
938
|
}
|
|
293
|
-
previousOutput =
|
|
939
|
+
previousOutput = result.structuredResult
|
|
940
|
+
? JSON.stringify(result.structuredResult)
|
|
941
|
+
: getResultFinalOutput(result);
|
|
294
942
|
}
|
|
295
943
|
return {
|
|
296
944
|
content: [
|
|
@@ -307,17 +955,6 @@ export async function executeSubagent(
|
|
|
307
955
|
}
|
|
308
956
|
|
|
309
957
|
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
958
|
const status = startSubagentStatus(
|
|
322
959
|
ctx,
|
|
323
960
|
toolCallId,
|
|
@@ -395,34 +1032,50 @@ export async function executeSubagent(
|
|
|
395
1032
|
|
|
396
1033
|
const results = await mapWithConcurrencyLimit(
|
|
397
1034
|
params.tasks,
|
|
398
|
-
|
|
1035
|
+
MAX_BLOCKING_PARALLEL_CONCURRENCY,
|
|
399
1036
|
async (t, index) => {
|
|
400
1037
|
const target = parallelTargets[index];
|
|
1038
|
+
const prepared = prepareTask(t.task, t);
|
|
1039
|
+
const thinkingLevel = resolveThinkingLevel(t.agent, t.thinkingLevel);
|
|
1040
|
+
const budget = resolveExecutionBudget(t.agent, t.timeoutMs);
|
|
1041
|
+
const taskGeneration = startWorkItem(`task-${index + 1}`, t.agent)?.taskGeneration ?? 0;
|
|
401
1042
|
const result = attachTarget(
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
1043
|
+
budget
|
|
1044
|
+
? await runSingleAgent(
|
|
1045
|
+
ctx.cwd,
|
|
1046
|
+
agents,
|
|
1047
|
+
t.agent,
|
|
1048
|
+
prepared.text,
|
|
1049
|
+
target.cwd,
|
|
1050
|
+
undefined,
|
|
1051
|
+
signal,
|
|
1052
|
+
thinkingLevel,
|
|
1053
|
+
budget.timeoutMs,
|
|
1054
|
+
(partial) => {
|
|
1055
|
+
if (partial.details?.results[0]) {
|
|
1056
|
+
allResults[index] = { ...partial.details.results[0], exitCode: -1 };
|
|
1057
|
+
emitParallelUpdate();
|
|
1058
|
+
}
|
|
1059
|
+
},
|
|
1060
|
+
makeDetails("parallel"),
|
|
1061
|
+
undefined,
|
|
1062
|
+
launchPolicy(
|
|
1063
|
+
target,
|
|
1064
|
+
prepared,
|
|
1065
|
+
t.task,
|
|
1066
|
+
t.agent,
|
|
1067
|
+
thinkingLevel,
|
|
1068
|
+
budget.timeoutMs,
|
|
1069
|
+
taskGeneration,
|
|
1070
|
+
budget,
|
|
1071
|
+
resolveTurnLimits(t),
|
|
1072
|
+
),
|
|
1073
|
+
)
|
|
1074
|
+
: exhaustedResult(t.agent, t.task, thinkingLevel),
|
|
423
1075
|
target,
|
|
424
1076
|
);
|
|
425
1077
|
allResults[index] = result;
|
|
1078
|
+
settleWorkItem(`task-${index + 1}`, result, taskGeneration);
|
|
426
1079
|
doneCount += 1;
|
|
427
1080
|
runningCount -= 1;
|
|
428
1081
|
emitParallelUpdate();
|
|
@@ -430,6 +1083,8 @@ export async function executeSubagent(
|
|
|
430
1083
|
},
|
|
431
1084
|
signal,
|
|
432
1085
|
(task, index) => {
|
|
1086
|
+
const taskGeneration =
|
|
1087
|
+
startWorkItem(`task-${index + 1}`, task.agent)?.taskGeneration ?? 0;
|
|
433
1088
|
const skipped: SingleResult = {
|
|
434
1089
|
...allResults[index],
|
|
435
1090
|
task: task.task,
|
|
@@ -439,6 +1094,7 @@ export async function executeSubagent(
|
|
|
439
1094
|
errorMessage: "Subagent was not started because the parent call was aborted",
|
|
440
1095
|
};
|
|
441
1096
|
allResults[index] = skipped;
|
|
1097
|
+
settleWorkItem(`task-${index + 1}`, skipped, taskGeneration);
|
|
442
1098
|
doneCount += 1;
|
|
443
1099
|
runningCount -= 1;
|
|
444
1100
|
emitParallelUpdate();
|
|
@@ -456,33 +1112,50 @@ export async function executeSubagent(
|
|
|
456
1112
|
: `${aggregator.task}\n\nParallel task outputs:\n\n${fanInContext}`,
|
|
457
1113
|
DEFAULT_MAX_CONTEXT_BYTES,
|
|
458
1114
|
).text;
|
|
1115
|
+
const prepared = prepareTask(aggregatorTask, aggregator);
|
|
459
1116
|
const target = aggregatorTarget as ResolvedSubagentTarget;
|
|
1117
|
+
const thinkingLevel = resolveThinkingLevel(aggregator.agent, aggregator.thinkingLevel);
|
|
1118
|
+
const budget = resolveExecutionBudget(aggregator.agent, aggregator.timeoutMs);
|
|
1119
|
+
const taskGeneration = startWorkItem("aggregator", aggregator.agent)?.taskGeneration ?? 0;
|
|
460
1120
|
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
|
-
|
|
1121
|
+
budget
|
|
1122
|
+
? await runSingleAgent(
|
|
1123
|
+
ctx.cwd,
|
|
1124
|
+
agents,
|
|
1125
|
+
aggregator.agent,
|
|
1126
|
+
prepared.text,
|
|
1127
|
+
target.cwd,
|
|
1128
|
+
undefined,
|
|
1129
|
+
signal,
|
|
1130
|
+
thinkingLevel,
|
|
1131
|
+
budget.timeoutMs,
|
|
1132
|
+
(partial) => {
|
|
1133
|
+
status.update(fanInStatus(aggregator.agent));
|
|
1134
|
+
if (onUpdate && partial.details?.results[0]) {
|
|
1135
|
+
onUpdate({
|
|
1136
|
+
content: partial.content,
|
|
1137
|
+
details: makeDetails("parallel")(results, partial.details.results[0]),
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
},
|
|
1141
|
+
makeDetails("parallel"),
|
|
1142
|
+
undefined,
|
|
1143
|
+
launchPolicy(
|
|
1144
|
+
target,
|
|
1145
|
+
prepared,
|
|
1146
|
+
aggregatorTask,
|
|
1147
|
+
aggregator.agent,
|
|
1148
|
+
thinkingLevel,
|
|
1149
|
+
budget.timeoutMs,
|
|
1150
|
+
taskGeneration,
|
|
1151
|
+
budget,
|
|
1152
|
+
resolveTurnLimits(aggregator),
|
|
1153
|
+
),
|
|
1154
|
+
)
|
|
1155
|
+
: exhaustedResult(aggregator.agent, aggregatorTask, thinkingLevel),
|
|
484
1156
|
target,
|
|
485
1157
|
);
|
|
1158
|
+
settleWorkItem("aggregator", aggregatorResult, taskGeneration);
|
|
486
1159
|
}
|
|
487
1160
|
|
|
488
1161
|
const successCount = results.filter((result) => !isResultError(result)).length;
|
|
@@ -527,24 +1200,41 @@ export async function executeSubagent(
|
|
|
527
1200
|
|
|
528
1201
|
try {
|
|
529
1202
|
const target = singleTarget as ResolvedSubagentTarget;
|
|
1203
|
+
const prepared = prepareTask(params.task, params);
|
|
1204
|
+
const thinkingLevel = resolveThinkingLevel(params.agent, params.thinkingLevel);
|
|
1205
|
+
const budget = resolveExecutionBudget(params.agent, params.timeoutMs);
|
|
1206
|
+
const taskGeneration = startWorkItem("task-1", params.agent)?.taskGeneration ?? 0;
|
|
530
1207
|
const result = attachTarget(
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
1208
|
+
budget
|
|
1209
|
+
? await runSingleAgent(
|
|
1210
|
+
ctx.cwd,
|
|
1211
|
+
agents,
|
|
1212
|
+
params.agent,
|
|
1213
|
+
prepared.text,
|
|
1214
|
+
target.cwd,
|
|
1215
|
+
undefined,
|
|
1216
|
+
signal,
|
|
1217
|
+
thinkingLevel,
|
|
1218
|
+
budget.timeoutMs,
|
|
1219
|
+
onUpdate,
|
|
1220
|
+
makeDetails("single"),
|
|
1221
|
+
undefined,
|
|
1222
|
+
launchPolicy(
|
|
1223
|
+
target,
|
|
1224
|
+
prepared,
|
|
1225
|
+
params.task,
|
|
1226
|
+
params.agent,
|
|
1227
|
+
thinkingLevel,
|
|
1228
|
+
budget.timeoutMs,
|
|
1229
|
+
taskGeneration,
|
|
1230
|
+
budget,
|
|
1231
|
+
resolveTurnLimits(params),
|
|
1232
|
+
),
|
|
1233
|
+
)
|
|
1234
|
+
: exhaustedResult(params.agent, params.task, thinkingLevel),
|
|
546
1235
|
target,
|
|
547
1236
|
);
|
|
1237
|
+
settleWorkItem("task-1", result, taskGeneration);
|
|
548
1238
|
const isError = isResultError(result);
|
|
549
1239
|
if (isError) {
|
|
550
1240
|
const errorMsg = formatResultFailure(result);
|