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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-crew",
3
- "version": "0.1.19",
3
+ "version": "0.1.20",
4
4
  "description": "Pi extension for coordinated AI teams, workflows, worktrees, and async task orchestration",
5
5
  "author": "baphuongna",
6
6
  "license": "MIT",
@@ -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 [agentTool, getSubagentResultTool, steerSubagentTool, crewAgentTool, crewAgentResultTool, crewAgentSteerTool]) {
406
- pi.registerTool(extraTool);
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", {
@@ -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 stdout = `${JSON.stringify({ type: "message", message: { role: "assistant", content: [{ type: "text", text: `Mock JSON success for ${input.agent.name}` }] } })}\n${JSON.stringify({ type: "message_end", usage: { input: 10, output: 5, cost: 0.001, turns: 1 } })}\n`;
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
  }
@@ -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 parentDepth = currentCrewDepth();
86
- const maxDepth = resolveCrewMaxDepth(input.maxDepth);
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: input.workflow.name, teamMaxConcurrency: input.team.maxConcurrency, limitMaxConcurrentWorkers: input.limits?.maxConcurrentWorkers, readyCount: snapshot.ready.length, workspaceMode: manifest.workspaceMode, readyRoles });
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(input.workflow, task);
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);
@@ -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 analysis, critique, execution, review, and verification
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 the plan
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: Full implementation workflow with critique and review gates
3
+ description: Adaptive implementation workflow where a planner agent decides the subagent fanout
4
4
  ---
5
5
 
6
- ## explore
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
- dependsOn: analyze
20
- output: plan.md
21
-
22
- Create an execution plan for: {goal}
23
-
24
- ## critique
25
- role: critic
26
- dependsOn: plan
27
-
28
- Critique the plan and identify required improvements.
29
-
30
- ## execute
31
- role: executor
32
- dependsOn: critique
33
-
34
- Implement the improved plan for: {goal}
35
-
36
- ## review
37
- role: reviewer
38
- dependsOn: execute
39
-
40
- Review the implementation.
41
-
42
- ## verify
43
- role: verifier
44
- dependsOn: review
45
- verify: true
46
-
47
- Verify the final result.
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.