pi-crew 0.1.19 → 0.1.20
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/CHANGELOG.md +8 -0
- package/package.json +1 -1
- package/src/extension/register.ts +11 -3
- package/src/runtime/child-pi.ts +5 -2
- package/src/runtime/pi-args.ts +4 -2
- package/src/runtime/team-runner.ts +170 -2
- package/src/state/types.ts +4 -0
- package/teams/implementation.team.md +5 -2
- package/workflows/implementation.workflow.md +33 -42
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.20
|
|
4
|
+
|
|
5
|
+
- Reworked the implementation workflow into an adaptive planner-led orchestration flow that decides the number, roles, and phases of subagents from the task instead of using a fixed fanout template.
|
|
6
|
+
- Added dynamic adaptive task injection, persisted adaptive task metadata, and resume reconstruction for planner-selected subagent steps.
|
|
7
|
+
- Block implementation runs when the planner does not produce a valid adaptive plan, including missing/unreadable planner artifacts and malformed/oversized plans.
|
|
8
|
+
- Added tests for adaptive plan parsing, dynamic batch fanout, invalid-plan blocking, writer-role support, and adaptive resume recovery.
|
|
9
|
+
- Hardened subagent/runtime fixes from post-0.1.19 review: env-isolated depth tests, foreground failure status updates, generic tool conflict aliases, and max_turns propagation.
|
|
10
|
+
|
|
3
11
|
## 0.1.19
|
|
4
12
|
|
|
5
13
|
- Added Claude-style `Agent`, `get_subagent_result`, and `steer_subagent` tools backed by pi-crew's durable worker runtime, plus conflict-safe `crew_agent`, `crew_agent_result`, and `crew_agent_steer` aliases.
|
package/package.json
CHANGED
|
@@ -16,7 +16,7 @@ import { registerPiCrewRpc, type PiCrewRpcHandle } from "./cross-extension-rpc.t
|
|
|
16
16
|
import { stopCrewWidget, updateCrewWidget, type CrewWidgetState } from "../ui/crew-widget.ts";
|
|
17
17
|
import { clearPiCrewPowerbar, registerPiCrewPowerbarSegments, updatePiCrewPowerbar } from "../ui/powerbar-publisher.ts";
|
|
18
18
|
import { DurableTextViewer, DurableTranscriptViewer } from "../ui/transcript-viewer.ts";
|
|
19
|
-
import { loadRunManifestById } from "../state/state-store.ts";
|
|
19
|
+
import { loadRunManifestById, updateRunStatus } from "../state/state-store.ts";
|
|
20
20
|
import { readCrewAgents } from "../runtime/crew-agent-records.ts";
|
|
21
21
|
import { terminateActiveChildPiProcesses } from "../runtime/child-pi.ts";
|
|
22
22
|
import { SubagentManager, type SubagentRecord, type SubagentSpawnOptions } from "../runtime/subagent-manager.ts";
|
|
@@ -215,6 +215,12 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
215
215
|
void runner(controller.signal)
|
|
216
216
|
.catch((error) => {
|
|
217
217
|
const message = error instanceof Error ? error.message : String(error);
|
|
218
|
+
if (runId) {
|
|
219
|
+
try {
|
|
220
|
+
const loaded = loadRunManifestById(ctx.cwd, runId);
|
|
221
|
+
if (loaded && loaded.manifest.status !== "completed" && loaded.manifest.status !== "failed" && loaded.manifest.status !== "cancelled" && loaded.manifest.status !== "blocked") updateRunStatus(loaded.manifest, "failed", message);
|
|
222
|
+
} catch {}
|
|
223
|
+
}
|
|
218
224
|
ctx.ui.notify(`pi-crew foreground run failed: ${message}`, "error");
|
|
219
225
|
})
|
|
220
226
|
.finally(() => {
|
|
@@ -333,6 +339,7 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
333
339
|
goal: spawnOptions.prompt,
|
|
334
340
|
model: spawnOptions.model,
|
|
335
341
|
async: false,
|
|
342
|
+
config: spawnOptions.maxTurns ? { runtime: { maxTurns: spawnOptions.maxTurns } } : undefined,
|
|
336
343
|
}, spawnOptions.background ? { ...ctx, signal: childSignal, startForegroundRun: (run, runId) => startForegroundRun(ctx, run, runId), onRunStarted: (runId) => openLiveSidebar(ctx, runId) } : { ...ctx, signal: childSignal });
|
|
337
344
|
const record = subagentManager.spawn(options, runner, options.background ? undefined : signal);
|
|
338
345
|
if (options.background || record.status === "queued") {
|
|
@@ -402,8 +409,9 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
402
409
|
label: "Steer Crew Agent",
|
|
403
410
|
description: "Send a steering note to a pi-crew subagent using the conflict-safe tool name.",
|
|
404
411
|
};
|
|
405
|
-
for (const extraTool of [
|
|
406
|
-
|
|
412
|
+
for (const extraTool of [crewAgentTool, crewAgentResultTool, crewAgentSteerTool]) pi.registerTool(extraTool);
|
|
413
|
+
for (const extraTool of [agentTool, getSubagentResultTool, steerSubagentTool]) {
|
|
414
|
+
try { pi.registerTool(extraTool); } catch {}
|
|
407
415
|
}
|
|
408
416
|
|
|
409
417
|
pi.registerCommand("teams", {
|
package/src/runtime/child-pi.ts
CHANGED
|
@@ -210,8 +210,11 @@ export async function runChildPi(input: ChildPiRunInput): Promise<ChildPiRunResu
|
|
|
210
210
|
observeStdoutChunk(input, stdout);
|
|
211
211
|
return { exitCode: 0, stdout, stderr: "" };
|
|
212
212
|
}
|
|
213
|
-
if (mock === "json-success") {
|
|
214
|
-
const
|
|
213
|
+
if (mock === "json-success" || mock === "adaptive-plan") {
|
|
214
|
+
const text = mock === "adaptive-plan" && input.task.includes("ADAPTIVE_PLAN_JSON_START")
|
|
215
|
+
? `Adaptive mock plan\nADAPTIVE_PLAN_JSON_START\n${JSON.stringify({ phases: [{ name: "research", tasks: [{ role: "explorer", task: "Explore adaptive target" }, { role: "analyst", task: "Analyze adaptive target" }, { role: "planner", task: "Plan adaptive target" }] }, { name: "build", tasks: [{ role: "executor", task: "Implement adaptive target" }] }, { name: "check", tasks: [{ role: "reviewer", task: "Review adaptive target" }, { role: "test-engineer", task: "Test adaptive target" }, { role: "writer", task: "Summarize adaptive target" }] }] })}\nADAPTIVE_PLAN_JSON_END`
|
|
216
|
+
: `Mock JSON success for ${input.agent.name}`;
|
|
217
|
+
const stdout = `${JSON.stringify({ type: "message", message: { role: "assistant", content: [{ type: "text", text }] } })}\n${JSON.stringify({ type: "message_end", usage: { input: 10, output: 5, cost: 0.001, turns: 1 } })}\n`;
|
|
215
218
|
observeStdoutChunk(input, stdout);
|
|
216
219
|
return { exitCode: 0, stdout, stderr: "" };
|
|
217
220
|
}
|
package/src/runtime/pi-args.ts
CHANGED
|
@@ -15,6 +15,7 @@ export interface BuildPiWorkerArgsInput {
|
|
|
15
15
|
model?: string;
|
|
16
16
|
sessionEnabled?: boolean;
|
|
17
17
|
maxDepth?: number;
|
|
18
|
+
env?: NodeJS.ProcessEnv;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
export interface BuildPiWorkerArgsResult {
|
|
@@ -82,8 +83,9 @@ export function buildPiWorkerArgs(input: BuildPiWorkerArgsInput): BuildPiWorkerA
|
|
|
82
83
|
args.push(`Task: ${input.task}`);
|
|
83
84
|
}
|
|
84
85
|
|
|
85
|
-
const
|
|
86
|
-
const
|
|
86
|
+
const env = input.env ?? process.env;
|
|
87
|
+
const parentDepth = currentCrewDepth(env);
|
|
88
|
+
const maxDepth = resolveCrewMaxDepth(input.maxDepth, env);
|
|
87
89
|
return {
|
|
88
90
|
args,
|
|
89
91
|
env: {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
1
2
|
import type { AgentConfig } from "../agents/agent-config.ts";
|
|
2
3
|
import type { CrewLimitsConfig, CrewRuntimeConfig } from "../config/config.ts";
|
|
3
4
|
import type { CrewRuntimeCapabilities } from "./runtime-resolver.ts";
|
|
@@ -76,6 +77,129 @@ function mergeTaskUpdates(base: TeamTaskState[], results: Array<{ tasks: TeamTas
|
|
|
76
77
|
return refreshTaskGraphQueues(merged);
|
|
77
78
|
}
|
|
78
79
|
|
|
80
|
+
interface AdaptivePlanTask {
|
|
81
|
+
role: string;
|
|
82
|
+
title?: string;
|
|
83
|
+
task: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
interface AdaptivePlanPhase {
|
|
87
|
+
name: string;
|
|
88
|
+
tasks: AdaptivePlanTask[];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
interface AdaptivePlan {
|
|
92
|
+
phases: AdaptivePlanPhase[];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const MAX_ADAPTIVE_TASKS = 12;
|
|
96
|
+
|
|
97
|
+
function slug(value: string): string {
|
|
98
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 32) || "task";
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function __test__parseAdaptivePlan(text: string, allowedRoles: string[]): AdaptivePlan | undefined {
|
|
102
|
+
const markerMatch = text.match(/ADAPTIVE_PLAN_JSON_START\s*([\s\S]*?)\s*ADAPTIVE_PLAN_JSON_END/);
|
|
103
|
+
const fencedMatch = markerMatch ? undefined : text.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
104
|
+
const raw = markerMatch?.[1] ?? fencedMatch?.[1];
|
|
105
|
+
if (!raw) return undefined;
|
|
106
|
+
let parsed: unknown;
|
|
107
|
+
try { parsed = JSON.parse(raw); } catch { return undefined; }
|
|
108
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return undefined;
|
|
109
|
+
const phasesRaw = Array.isArray((parsed as { phases?: unknown }).phases) ? (parsed as { phases: unknown[] }).phases : Array.isArray((parsed as { tasks?: unknown }).tasks) ? [{ name: "adaptive", tasks: (parsed as { tasks: unknown[] }).tasks }] : undefined;
|
|
110
|
+
if (!phasesRaw) return undefined;
|
|
111
|
+
const allowed = new Set(allowedRoles);
|
|
112
|
+
const phases: AdaptivePlanPhase[] = [];
|
|
113
|
+
let total = 0;
|
|
114
|
+
for (const [phaseIndex, phaseRaw] of phasesRaw.entries()) {
|
|
115
|
+
if (!phaseRaw || typeof phaseRaw !== "object" || Array.isArray(phaseRaw)) return undefined;
|
|
116
|
+
const phaseObj = phaseRaw as { name?: unknown; tasks?: unknown };
|
|
117
|
+
if (!Array.isArray(phaseObj.tasks) || phaseObj.tasks.length === 0) return undefined;
|
|
118
|
+
const tasks: AdaptivePlanTask[] = [];
|
|
119
|
+
for (const taskRaw of phaseObj.tasks) {
|
|
120
|
+
if (!taskRaw || typeof taskRaw !== "object" || Array.isArray(taskRaw)) return undefined;
|
|
121
|
+
const taskObj = taskRaw as { role?: unknown; title?: unknown; task?: unknown };
|
|
122
|
+
if (typeof taskObj.role !== "string" || !allowed.has(taskObj.role)) return undefined;
|
|
123
|
+
if (typeof taskObj.task !== "string" || !taskObj.task.trim()) return undefined;
|
|
124
|
+
if (total >= MAX_ADAPTIVE_TASKS) return undefined;
|
|
125
|
+
tasks.push({ role: taskObj.role, title: typeof taskObj.title === "string" ? taskObj.title : undefined, task: taskObj.task.trim() });
|
|
126
|
+
total++;
|
|
127
|
+
}
|
|
128
|
+
phases.push({ name: typeof phaseObj.name === "string" && phaseObj.name.trim() ? phaseObj.name.trim() : `phase-${phaseIndex + 1}`, tasks });
|
|
129
|
+
}
|
|
130
|
+
return phases.length ? { phases } : undefined;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function reconstructAdaptiveWorkflow(workflow: WorkflowConfig, tasks: TeamTaskState[]): WorkflowConfig {
|
|
134
|
+
const existing = new Set(workflow.steps.map((step) => step.id));
|
|
135
|
+
const steps: WorkflowStep[] = [];
|
|
136
|
+
for (const task of tasks) {
|
|
137
|
+
if (!task.stepId?.startsWith("adaptive-") || !task.adaptive?.task || existing.has(task.stepId)) continue;
|
|
138
|
+
steps.push({ id: task.stepId, role: task.role, dependsOn: task.graph?.dependencies ?? task.dependsOn, parallelGroup: `adaptive-${slug(task.adaptive.phase)}`, task: task.adaptive.task });
|
|
139
|
+
}
|
|
140
|
+
return steps.length ? { ...workflow, steps: [...workflow.steps, ...steps] } : workflow;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function injectAdaptivePlanIfReady(input: { manifest: TeamRunManifest; tasks: TeamTaskState[]; workflow: WorkflowConfig; team: TeamConfig }): { tasks: TeamTaskState[]; workflow: WorkflowConfig; injected: boolean; missingPlan: boolean } {
|
|
144
|
+
if (input.workflow.name !== "implementation") return { tasks: input.tasks, workflow: input.workflow, injected: false, missingPlan: false };
|
|
145
|
+
if (input.tasks.some((task) => task.stepId?.startsWith("adaptive-"))) return { tasks: input.tasks, workflow: reconstructAdaptiveWorkflow(input.workflow, input.tasks), injected: false, missingPlan: false };
|
|
146
|
+
const completedAssess = input.tasks.find((task) => task.stepId === "assess" && task.status === "completed");
|
|
147
|
+
if (!completedAssess) return { tasks: input.tasks, workflow: input.workflow, injected: false, missingPlan: false };
|
|
148
|
+
if (!completedAssess.resultArtifact?.path) {
|
|
149
|
+
appendEvent(input.manifest.eventsPath, { type: "adaptive.plan_missing", runId: input.manifest.runId, taskId: completedAssess.id, message: "Adaptive planner result artifact is missing." });
|
|
150
|
+
return { tasks: input.tasks, workflow: input.workflow, injected: false, missingPlan: true };
|
|
151
|
+
}
|
|
152
|
+
const assessTask = completedAssess;
|
|
153
|
+
const resultPath = completedAssess.resultArtifact.path;
|
|
154
|
+
let text = "";
|
|
155
|
+
try { text = fs.readFileSync(resultPath, "utf-8"); } catch {
|
|
156
|
+
appendEvent(input.manifest.eventsPath, { type: "adaptive.plan_missing", runId: input.manifest.runId, taskId: assessTask.id, message: "Adaptive planner result artifact could not be read." });
|
|
157
|
+
return { tasks: input.tasks, workflow: input.workflow, injected: false, missingPlan: true };
|
|
158
|
+
}
|
|
159
|
+
const plan = __test__parseAdaptivePlan(text, input.team.roles.map((role) => role.name));
|
|
160
|
+
if (!plan) {
|
|
161
|
+
appendEvent(input.manifest.eventsPath, { type: "adaptive.plan_missing", runId: input.manifest.runId, taskId: assessTask.id, message: "Adaptive planner did not produce a valid plan; no dynamic subagents were spawned." });
|
|
162
|
+
return { tasks: input.tasks, workflow: input.workflow, injected: false, missingPlan: true };
|
|
163
|
+
}
|
|
164
|
+
const steps: WorkflowStep[] = [];
|
|
165
|
+
const tasks: TeamTaskState[] = [];
|
|
166
|
+
let previousStepIds = ["assess"];
|
|
167
|
+
let counter = 0;
|
|
168
|
+
for (const [phaseIndex, phase] of plan.phases.entries()) {
|
|
169
|
+
const currentStepIds: string[] = [];
|
|
170
|
+
for (const [taskIndex, planned] of phase.tasks.entries()) {
|
|
171
|
+
counter++;
|
|
172
|
+
const stepId = `adaptive-${phaseIndex + 1}-${taskIndex + 1}-${slug(planned.role)}`;
|
|
173
|
+
const taskId = `adaptive-${String(counter).padStart(2, "0")}-${slug(planned.role)}`;
|
|
174
|
+
steps.push({ id: stepId, role: planned.role, dependsOn: previousStepIds, parallelGroup: `adaptive-${slug(phase.name)}`, task: planned.task });
|
|
175
|
+
tasks.push({
|
|
176
|
+
id: taskId,
|
|
177
|
+
runId: input.manifest.runId,
|
|
178
|
+
stepId,
|
|
179
|
+
role: planned.role,
|
|
180
|
+
agent: input.team.roles.find((role) => role.name === planned.role)?.agent ?? planned.role,
|
|
181
|
+
title: planned.title ?? stepId,
|
|
182
|
+
status: "queued",
|
|
183
|
+
dependsOn: previousStepIds,
|
|
184
|
+
cwd: input.manifest.cwd,
|
|
185
|
+
adaptive: { phase: phase.name, task: planned.task },
|
|
186
|
+
graph: { taskId, dependencies: previousStepIds, children: [], queue: "blocked" },
|
|
187
|
+
});
|
|
188
|
+
currentStepIds.push(stepId);
|
|
189
|
+
}
|
|
190
|
+
previousStepIds = currentStepIds;
|
|
191
|
+
}
|
|
192
|
+
const dependencyTaskIdByStep = new Map<string, string>([["assess", assessTask.id], ...tasks.map((task) => [task.stepId ?? task.id, task.id] as const)]);
|
|
193
|
+
const withGraph = tasks.map((task) => ({
|
|
194
|
+
...task,
|
|
195
|
+
dependsOn: task.dependsOn.map((dep) => dependencyTaskIdByStep.get(dep) ?? dep),
|
|
196
|
+
graph: task.graph ? { ...task.graph, dependencies: task.dependsOn.map((dep) => dependencyTaskIdByStep.get(dep) ?? dep), queue: "blocked" as const } : task.graph,
|
|
197
|
+
}));
|
|
198
|
+
const allTasks = refreshTaskGraphQueues([...input.tasks, ...withGraph]);
|
|
199
|
+
appendEvent(input.manifest.eventsPath, { type: "adaptive.plan_injected", runId: input.manifest.runId, taskId: assessTask.id, message: `Injected ${withGraph.length} adaptive subagent task(s) across ${plan.phases.length} phase(s).`, data: { phases: plan.phases.map((phase) => ({ name: phase.name, count: phase.tasks.length, roles: phase.tasks.map((task) => task.role) })) } });
|
|
200
|
+
return { tasks: allTasks, workflow: { ...input.workflow, steps: [...input.workflow.steps, ...steps] }, injected: true, missingPlan: false };
|
|
201
|
+
}
|
|
202
|
+
|
|
79
203
|
function formatTaskProgress(task: TeamTaskState): string {
|
|
80
204
|
return `- ${task.id}: ${task.status} (${task.role} -> ${task.agent})${task.taskPacket ? ` scope=${task.taskPacket.scope}` : ""}${task.verification ? ` green=${task.verification.observedGreenLevel}/${task.verification.requiredGreenLevel}` : ""}${task.error ? ` - ${task.error}` : ""}`;
|
|
81
205
|
}
|
|
@@ -144,8 +268,22 @@ function applyPolicy(manifest: TeamRunManifest, tasks: TeamTaskState[], limits?:
|
|
|
144
268
|
}
|
|
145
269
|
|
|
146
270
|
export async function executeTeamRun(input: ExecuteTeamRunInput): Promise<{ manifest: TeamRunManifest; tasks: TeamTaskState[] }> {
|
|
271
|
+
let workflow = input.workflow;
|
|
147
272
|
let manifest = updateRunStatus(input.manifest, "running", input.executeWorkers ? "Executing team workflow." : "Creating workflow prompts and placeholder results.");
|
|
148
273
|
let tasks = refreshTaskGraphQueues(input.tasks);
|
|
274
|
+
const initialAdaptive = injectAdaptivePlanIfReady({ manifest, tasks, workflow, team: input.team });
|
|
275
|
+
if (initialAdaptive.missingPlan) {
|
|
276
|
+
tasks = markBlocked(tasks, "Adaptive planner did not produce a valid subagent plan.");
|
|
277
|
+
saveRunTasks(manifest, tasks);
|
|
278
|
+
manifest = updateRunStatus(manifest, "blocked", "Adaptive planner did not produce a valid subagent plan.");
|
|
279
|
+
return { manifest, tasks };
|
|
280
|
+
}
|
|
281
|
+
if (initialAdaptive.injected) {
|
|
282
|
+
tasks = initialAdaptive.tasks;
|
|
283
|
+
workflow = initialAdaptive.workflow;
|
|
284
|
+
} else {
|
|
285
|
+
workflow = initialAdaptive.workflow;
|
|
286
|
+
}
|
|
149
287
|
manifest = writeProgress(manifest, tasks, "team-runner");
|
|
150
288
|
saveRunManifest(manifest);
|
|
151
289
|
const runtimeKind = input.runtime?.kind ?? (input.executeWorkers ? "child-process" : "scaffold");
|
|
@@ -168,9 +306,25 @@ export async function executeTeamRun(input: ExecuteTeamRunInput): Promise<{ mani
|
|
|
168
306
|
return { manifest, tasks };
|
|
169
307
|
}
|
|
170
308
|
|
|
309
|
+
const injectedPlan = injectAdaptivePlanIfReady({ manifest, tasks, workflow, team: input.team });
|
|
310
|
+
if (injectedPlan.missingPlan) {
|
|
311
|
+
tasks = markBlocked(tasks, "Adaptive planner did not produce a valid subagent plan.");
|
|
312
|
+
saveRunTasks(manifest, tasks);
|
|
313
|
+
saveCrewAgents(manifest, recordsForMaterializedTasks(manifest, tasks, runtimeKind));
|
|
314
|
+
manifest = updateRunStatus(manifest, "blocked", "Adaptive planner did not produce a valid subagent plan.");
|
|
315
|
+
return { manifest, tasks };
|
|
316
|
+
}
|
|
317
|
+
if (injectedPlan.injected) {
|
|
318
|
+
tasks = injectedPlan.tasks;
|
|
319
|
+
workflow = injectedPlan.workflow;
|
|
320
|
+
saveRunTasks(manifest, tasks);
|
|
321
|
+
saveCrewAgents(manifest, recordsForMaterializedTasks(manifest, tasks, runtimeKind));
|
|
322
|
+
} else {
|
|
323
|
+
workflow = injectedPlan.workflow;
|
|
324
|
+
}
|
|
171
325
|
const snapshot = taskGraphSnapshot(tasks);
|
|
172
326
|
const readyRoles = snapshot.ready.map((taskId) => tasks.find((task) => task.id === taskId)?.role).filter((role): role is string => Boolean(role));
|
|
173
|
-
const concurrency = resolveBatchConcurrency({ workflowName:
|
|
327
|
+
const concurrency = resolveBatchConcurrency({ workflowName: workflow.name, teamMaxConcurrency: input.team.maxConcurrency, limitMaxConcurrentWorkers: input.limits?.maxConcurrentWorkers, readyCount: snapshot.ready.length, workspaceMode: manifest.workspaceMode, readyRoles });
|
|
174
328
|
const readyBatch = getReadyTasks(tasks, concurrency.selectedCount);
|
|
175
329
|
if (readyBatch.length === 0) {
|
|
176
330
|
tasks = markBlocked(tasks, "No ready queued task; dependency graph may be invalid.");
|
|
@@ -182,12 +336,26 @@ export async function executeTeamRun(input: ExecuteTeamRunInput): Promise<{ mani
|
|
|
182
336
|
|
|
183
337
|
appendEvent(manifest.eventsPath, { type: "task.progress", runId: manifest.runId, message: `Starting ready batch with ${readyBatch.length} task(s).`, data: { taskIds: readyBatch.map((task) => task.id), readyCount: snapshot.ready.length, blockedCount: snapshot.blocked.length, runningCount: snapshot.running.length, doneCount: snapshot.done.length, selectedCount: readyBatch.length, maxConcurrent: concurrency.maxConcurrent, defaultConcurrency: concurrency.defaultConcurrency, concurrencyReason: concurrency.reason } });
|
|
184
338
|
const results = await Promise.all(readyBatch.map((task) => {
|
|
185
|
-
const step = findStep(
|
|
339
|
+
const step = findStep(workflow, task);
|
|
186
340
|
const agent = findAgent(input.agents, task);
|
|
187
341
|
return runTeamTask({ manifest, tasks, task, step, agent, signal: input.signal, executeWorkers: input.executeWorkers, runtimeKind: input.runtime?.kind, runtimeConfig: input.runtimeConfig, parentContext: input.parentContext, parentModel: input.parentModel, modelRegistry: input.modelRegistry, modelOverride: input.modelOverride, limits: input.limits });
|
|
188
342
|
}));
|
|
189
343
|
manifest = { ...results.at(-1)!.manifest, artifacts: mergeArtifacts([manifest.artifacts, ...results.map((item) => item.manifest.artifacts)].flat()) };
|
|
190
344
|
tasks = mergeTaskUpdates(tasks, results);
|
|
345
|
+
const injectedAfterBatch = injectAdaptivePlanIfReady({ manifest, tasks, workflow, team: input.team });
|
|
346
|
+
if (injectedAfterBatch.missingPlan) {
|
|
347
|
+
tasks = markBlocked(tasks, "Adaptive planner did not produce a valid subagent plan.");
|
|
348
|
+
saveRunTasks(manifest, tasks);
|
|
349
|
+
saveCrewAgents(manifest, recordsForMaterializedTasks(manifest, tasks, runtimeKind));
|
|
350
|
+
manifest = updateRunStatus(manifest, "blocked", "Adaptive planner did not produce a valid subagent plan.");
|
|
351
|
+
return { manifest, tasks };
|
|
352
|
+
}
|
|
353
|
+
if (injectedAfterBatch.injected) {
|
|
354
|
+
tasks = injectedAfterBatch.tasks;
|
|
355
|
+
workflow = injectedAfterBatch.workflow;
|
|
356
|
+
} else {
|
|
357
|
+
workflow = injectedAfterBatch.workflow;
|
|
358
|
+
}
|
|
191
359
|
saveRunTasks(manifest, tasks);
|
|
192
360
|
saveCrewAgents(manifest, recordsForMaterializedTasks(manifest, tasks, runtimeKind));
|
|
193
361
|
const completedBatch = readyBatch.map((task) => tasks.find((item) => item.id === task.id) ?? task);
|
package/src/state/types.ts
CHANGED
|
@@ -152,6 +152,10 @@ export interface TeamTaskState {
|
|
|
152
152
|
taskPacket?: TaskPacket;
|
|
153
153
|
verification?: VerificationEvidence;
|
|
154
154
|
graph?: TaskGraphNode;
|
|
155
|
+
adaptive?: {
|
|
156
|
+
phase: string;
|
|
157
|
+
task: string;
|
|
158
|
+
};
|
|
155
159
|
policy?: {
|
|
156
160
|
retryCount?: number;
|
|
157
161
|
lastDecision?: PolicyDecision;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: implementation
|
|
3
|
-
description: Full implementation team with
|
|
3
|
+
description: Full implementation team with parallel specialists, critique, execution, review, and verification
|
|
4
4
|
defaultWorkflow: implementation
|
|
5
5
|
workspaceMode: single
|
|
6
6
|
maxConcurrency: 3
|
|
@@ -9,7 +9,10 @@ maxConcurrency: 3
|
|
|
9
9
|
- explorer: agent=explorer map the codebase
|
|
10
10
|
- analyst: agent=analyst clarify requirements and constraints
|
|
11
11
|
- planner: agent=planner create execution plan
|
|
12
|
-
- critic: agent=critic challenge
|
|
12
|
+
- critic: agent=critic challenge and synthesize specialist findings
|
|
13
13
|
- executor: agent=executor implement the plan
|
|
14
14
|
- reviewer: agent=reviewer review the implementation
|
|
15
|
+
- security-reviewer: agent=security-reviewer review security and trust boundaries
|
|
16
|
+
- test-engineer: agent=test-engineer design and run verification
|
|
15
17
|
- verifier: agent=verifier verify done
|
|
18
|
+
- writer: agent=writer summarize documentation or release notes when needed
|
|
@@ -1,47 +1,38 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: implementation
|
|
3
|
-
description:
|
|
3
|
+
description: Adaptive implementation workflow where a planner agent decides the subagent fanout
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
##
|
|
7
|
-
role: explorer
|
|
8
|
-
|
|
9
|
-
Map relevant files, APIs, and constraints for: {goal}
|
|
10
|
-
|
|
11
|
-
## analyze
|
|
12
|
-
role: analyst
|
|
13
|
-
dependsOn: explore
|
|
14
|
-
|
|
15
|
-
Analyze requirements, ambiguities, risks, and acceptance criteria for: {goal}
|
|
16
|
-
|
|
17
|
-
## plan
|
|
6
|
+
## assess
|
|
18
7
|
role: planner
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
8
|
+
output: adaptive-plan.json
|
|
9
|
+
|
|
10
|
+
Assess this task and decide how many subagents are actually needed for: {goal}
|
|
11
|
+
|
|
12
|
+
You are the orchestration planner. Inspect the repository enough to choose an efficient crew; do not use a fixed template. Small/simple tasks may need one executor plus one verifier. Risky or broad tasks may need parallel explorers, specialists, implementers, reviewers, security reviewers, or test engineers.
|
|
13
|
+
|
|
14
|
+
Return a concise rationale, then include exactly one JSON block between these markers:
|
|
15
|
+
|
|
16
|
+
ADAPTIVE_PLAN_JSON_START
|
|
17
|
+
{
|
|
18
|
+
"phases": [
|
|
19
|
+
{
|
|
20
|
+
"name": "short-phase-name",
|
|
21
|
+
"tasks": [
|
|
22
|
+
{
|
|
23
|
+
"role": "explorer|analyst|planner|critic|executor|reviewer|security-reviewer|test-engineer|verifier|writer",
|
|
24
|
+
"title": "short task title",
|
|
25
|
+
"task": "specific autonomous task prompt for this subagent"
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
ADAPTIVE_PLAN_JSON_END
|
|
32
|
+
|
|
33
|
+
Rules:
|
|
34
|
+
- Choose the smallest effective number of subagents.
|
|
35
|
+
- Use parallel tasks in the same phase only when their work is independent.
|
|
36
|
+
- Later phases depend on all tasks in the previous phase.
|
|
37
|
+
- Include verification/review tasks when implementation is requested.
|
|
38
|
+
- Do not include more than 12 total subagents unless the user explicitly asks for a large crew.
|