@henryqw/pi-subagent 5.0.0 → 6.1.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/CONTEXT.md +8 -8
- package/README.md +15 -11
- package/dist/ephemeral.d.ts +13 -0
- package/dist/ephemeral.js +85 -6
- package/dist/index.d.ts +1 -1
- package/docs/adr/001-composable-ephemeral-execution.md +3 -1
- package/docs/adr/002-package-owned-delegate-flow-orchestration.md +8 -8
- package/docs/orchestration.md +30 -17
- package/examples/roles/reviewer.md +2 -2
- package/extensions/delegate-flow.ts +162 -68
- package/extensions/subagent.ts +139 -26
- package/extensions/tool-render.ts +16 -0
- package/package.json +2 -2
- package/skills/pi-subagent-delegated-development/SKILL.md +7 -7
package/extensions/subagent.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { basename } from "node:path";
|
|
1
2
|
import type { Usage } from "@earendil-works/pi-ai";
|
|
2
3
|
import { type ExtensionAPI, type ExtensionContext, type Theme } from "@earendil-works/pi-coding-agent";
|
|
3
|
-
import { type Component, type TUI,
|
|
4
|
+
import { type Component, type TUI, truncateToWidth } from "@earendil-works/pi-tui";
|
|
4
5
|
import {
|
|
5
6
|
availableTaskModels,
|
|
6
7
|
type ThinkingLevel,
|
|
@@ -21,6 +22,7 @@ import {
|
|
|
21
22
|
loadRoles,
|
|
22
23
|
resolveTaskRoute,
|
|
23
24
|
worktreeContextNote,
|
|
25
|
+
type EphemeralSubagentActivityEvent,
|
|
24
26
|
type EphemeralSubagentResult,
|
|
25
27
|
type EphemeralSubagentTimeout,
|
|
26
28
|
type Role,
|
|
@@ -29,6 +31,7 @@ import {
|
|
|
29
31
|
} from "@henryqw/pi-subagent";
|
|
30
32
|
import { DEFAULT_TIMEOUT_CONFIG, readSubagentConfig, type SubagentTimeoutConfig } from "./config.ts";
|
|
31
33
|
import { registerDelegateFlow } from "./delegate-flow.ts";
|
|
34
|
+
import { renderToolLines } from "./tool-render.ts";
|
|
32
35
|
import { runDelegation } from "./delegation.ts";
|
|
33
36
|
import {
|
|
34
37
|
formatBackgroundWorkflowResult,
|
|
@@ -55,6 +58,7 @@ const SUBAGENT_TASK = "pi-subagent/delegateTask";
|
|
|
55
58
|
const WIDGET_KEY = "subagent-status";
|
|
56
59
|
const WIDGET_INTERVAL_MS = 80;
|
|
57
60
|
const MAX_WIDGET_ROWS = 8;
|
|
61
|
+
export const MAX_WIDGET_ACTIVE_TOOLS = 8;
|
|
58
62
|
const DEFAULT_TIMEOUT_POLICY = {
|
|
59
63
|
idleMs: DEFAULT_TIMEOUT_CONFIG.idleMinutes * 60_000,
|
|
60
64
|
maxMs: DEFAULT_TIMEOUT_CONFIG.maxMinutes * 60_000,
|
|
@@ -71,6 +75,12 @@ export function resolveTimeoutPolicy(partial: SubagentTimeoutConfig | undefined)
|
|
|
71
75
|
};
|
|
72
76
|
}
|
|
73
77
|
type WidgetStatus = "working" | "success" | "failure" | "aborted";
|
|
78
|
+
type WidgetActiveTool = {
|
|
79
|
+
toolName: string;
|
|
80
|
+
path?: string;
|
|
81
|
+
startedAt: number;
|
|
82
|
+
order: number;
|
|
83
|
+
};
|
|
74
84
|
type WidgetItem = {
|
|
75
85
|
role: string;
|
|
76
86
|
model: string;
|
|
@@ -80,6 +90,11 @@ type WidgetItem = {
|
|
|
80
90
|
startedAt: number;
|
|
81
91
|
status: WidgetStatus;
|
|
82
92
|
finishedAt?: number;
|
|
93
|
+
completedAssistantTurns: number;
|
|
94
|
+
startedToolCount: number;
|
|
95
|
+
activeTools: Map<string, WidgetActiveTool>;
|
|
96
|
+
activeToolId?: string;
|
|
97
|
+
activityOrder: number;
|
|
83
98
|
};
|
|
84
99
|
|
|
85
100
|
function taskSummary(task: string): string {
|
|
@@ -111,6 +126,34 @@ function statusLabel(status: WidgetStatus): string {
|
|
|
111
126
|
}
|
|
112
127
|
}
|
|
113
128
|
|
|
129
|
+
function activityLabel(item: WidgetItem, now: number): string {
|
|
130
|
+
if (item.status === "success") return "Done";
|
|
131
|
+
if (item.status === "failure") return "Failed";
|
|
132
|
+
if (item.status === "aborted") return "Stopped";
|
|
133
|
+
const activeTool = item.activeToolId === undefined ? undefined : item.activeTools.get(item.activeToolId);
|
|
134
|
+
if (!activeTool) return "thinking…";
|
|
135
|
+
return [
|
|
136
|
+
activeTool.toolName,
|
|
137
|
+
formatDuration(now - activeTool.startedAt),
|
|
138
|
+
...(activeTool.path === undefined ? [] : [activeTool.path]),
|
|
139
|
+
].join(" · ");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function activityMetrics(item: WidgetItem, now: number): string {
|
|
143
|
+
return [
|
|
144
|
+
...(item.completedAssistantTurns === 0
|
|
145
|
+
? []
|
|
146
|
+
: [`${item.completedAssistantTurns} turn${item.completedAssistantTurns === 1 ? "" : "s"}`]),
|
|
147
|
+
...(item.startedToolCount === 0
|
|
148
|
+
? []
|
|
149
|
+
: [`${item.startedToolCount} tool${item.startedToolCount === 1 ? "" : "s"}`]),
|
|
150
|
+
item.model,
|
|
151
|
+
item.thinkingLevel,
|
|
152
|
+
`${formatTokens(item.tokens)} tok`,
|
|
153
|
+
formatDuration((item.finishedAt ?? now) - item.startedAt),
|
|
154
|
+
].join(" · ");
|
|
155
|
+
}
|
|
156
|
+
|
|
114
157
|
function isWorkflowTransportDetails(value: unknown): value is WorkflowTransportDetails {
|
|
115
158
|
const isRecord = (candidate: unknown): candidate is Record<string, unknown> => typeof candidate === "object" && candidate !== null && !Array.isArray(candidate);
|
|
116
159
|
const isOptionalString = (candidate: unknown) => candidate === undefined || typeof candidate === "string";
|
|
@@ -134,18 +177,34 @@ function isWorkflowTransportDetails(value: unknown): value is WorkflowTransportD
|
|
|
134
177
|
&& entries.every((entry, index) => index === 0 || (entry as { index: number }).index > (entries[index - 1] as { index: number }).index);
|
|
135
178
|
}
|
|
136
179
|
|
|
137
|
-
function
|
|
180
|
+
function workflowCallLabel(args: { tasks?: unknown; chain?: unknown }): string {
|
|
181
|
+
if (Array.isArray(args.chain)) return `delegate_task · working: chain · ${args.chain.length} task${args.chain.length === 1 ? "" : "s"}`;
|
|
182
|
+
if (Array.isArray(args.tasks)) return `delegate_task · working: parallel · ${args.tasks.length} task${args.tasks.length === 1 ? "" : "s"}`;
|
|
183
|
+
return "delegate_task · working: single · 1 task";
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function workflowResultLines(details: WorkflowTransportDetails, theme: Theme): string[] {
|
|
138
187
|
if (details.entries.some(({ status }) => status === "pending" || status === "running")) return [];
|
|
139
|
-
|
|
140
|
-
|
|
188
|
+
const entries = details.entries.filter(({ status }) => status !== "skipped");
|
|
189
|
+
const withRecovery = (entry: typeof entries[0]) => entry.worktree && !entry.worktree.pruned;
|
|
190
|
+
const isTerminalFailure = (entry: typeof entries[0]) => entry.status === "failed" || entry.status === "rejected";
|
|
191
|
+
const sorted = [...entries].sort((a, b) => {
|
|
192
|
+
const aFailure = isTerminalFailure(a);
|
|
193
|
+
const bFailure = isTerminalFailure(b);
|
|
194
|
+
if (aFailure !== bFailure) return aFailure ? -1 : 1;
|
|
195
|
+
const aRecovery = !aFailure && withRecovery(a);
|
|
196
|
+
const bRecovery = !bFailure && withRecovery(b);
|
|
197
|
+
if (aRecovery !== bRecovery) return aRecovery ? -1 : 1;
|
|
198
|
+
return 0;
|
|
199
|
+
});
|
|
200
|
+
return sorted.map((entry) => {
|
|
141
201
|
const summary = entry.summary || "(no output)";
|
|
142
202
|
const text = details.mode === "single" ? summary : `${entry.role}: ${summary}`;
|
|
143
203
|
const style = entry.status === "failed" || entry.status === "rejected" ? "error" : "text";
|
|
144
|
-
const recovery = entry
|
|
145
|
-
return
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
];
|
|
204
|
+
const recovery = withRecovery(entry) ? `Recovery: ${entry.worktree!.path}` : undefined;
|
|
205
|
+
return recovery === undefined
|
|
206
|
+
? theme.fg(style, text)
|
|
207
|
+
: `${theme.fg("warning", recovery)} · ${theme.fg(style, text)}`;
|
|
149
208
|
});
|
|
150
209
|
}
|
|
151
210
|
|
|
@@ -159,13 +218,15 @@ function renderWidgetRows(
|
|
|
159
218
|
const visible = items.slice(0, MAX_WIDGET_ROWS);
|
|
160
219
|
if (!visible.length) return [];
|
|
161
220
|
const indent = " ".repeat(Math.min(2, Math.max(0, width - 1)));
|
|
162
|
-
const contentWidth = Math.max(
|
|
221
|
+
const contentWidth = Math.max(0, width - indent.length);
|
|
163
222
|
const lines = visible.flatMap((item) => [
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
223
|
+
truncateToWidth(
|
|
224
|
+
`${statusGlyph(item.status, spinnerIndex, theme)} ${theme.fg("accent", item.role)} · ${statusLabel(item.status)} · ${theme.fg("text", item.task)}`,
|
|
225
|
+
width,
|
|
226
|
+
),
|
|
227
|
+
`${indent}${truncateToWidth(`${theme.fg("text", activityLabel(item, now))} · ${theme.fg("muted", activityMetrics(item, now))}`, contentWidth)}`,
|
|
167
228
|
]);
|
|
168
|
-
if (items.length > visible.length) lines.push(
|
|
229
|
+
if (items.length > visible.length) lines.push(truncateToWidth(theme.fg("muted", `… ${items.length - visible.length} more`), width));
|
|
169
230
|
return lines;
|
|
170
231
|
}
|
|
171
232
|
|
|
@@ -311,6 +372,10 @@ export default function subagentExtension(
|
|
|
311
372
|
tokens: 0,
|
|
312
373
|
startedAt: Date.now(),
|
|
313
374
|
status: "working",
|
|
375
|
+
completedAssistantTurns: 0,
|
|
376
|
+
startedToolCount: 0,
|
|
377
|
+
activeTools: new Map(),
|
|
378
|
+
activityOrder: 0,
|
|
314
379
|
});
|
|
315
380
|
startWidgetTimer();
|
|
316
381
|
requestWidgetRender();
|
|
@@ -323,11 +388,55 @@ export default function subagentExtension(
|
|
|
323
388
|
requestWidgetRender();
|
|
324
389
|
};
|
|
325
390
|
|
|
391
|
+
const updateWidgetActivity = (id: string, event: EphemeralSubagentActivityEvent) => {
|
|
392
|
+
const item = widgetItems.get(id);
|
|
393
|
+
if (!item || item.status !== "working") return;
|
|
394
|
+
switch (event.type) {
|
|
395
|
+
case "tool_execution_start": {
|
|
396
|
+
if (item.activeTools.has(event.toolCallId)) break;
|
|
397
|
+
if (item.activeTools.size >= MAX_WIDGET_ACTIVE_TOOLS) {
|
|
398
|
+
let oldest: [string, WidgetActiveTool] | undefined;
|
|
399
|
+
for (const candidate of item.activeTools) {
|
|
400
|
+
if (!oldest || candidate[1].order < oldest[1].order) oldest = candidate;
|
|
401
|
+
}
|
|
402
|
+
if (oldest) item.activeTools.delete(oldest[0]);
|
|
403
|
+
}
|
|
404
|
+
const path = event.path === undefined ? undefined : basename(event.path);
|
|
405
|
+
item.startedToolCount += 1;
|
|
406
|
+
item.activeTools.set(event.toolCallId, {
|
|
407
|
+
toolName: event.toolName,
|
|
408
|
+
...(path ? { path } : {}),
|
|
409
|
+
startedAt: Date.now(),
|
|
410
|
+
order: ++item.activityOrder,
|
|
411
|
+
});
|
|
412
|
+
item.activeToolId = event.toolCallId;
|
|
413
|
+
break;
|
|
414
|
+
}
|
|
415
|
+
case "tool_execution_end": {
|
|
416
|
+
item.activeTools.delete(event.toolCallId);
|
|
417
|
+
if (item.activeToolId === event.toolCallId) {
|
|
418
|
+
let latest: [string, WidgetActiveTool] | undefined;
|
|
419
|
+
for (const candidate of item.activeTools) {
|
|
420
|
+
if (!latest || candidate[1].order > latest[1].order) latest = candidate;
|
|
421
|
+
}
|
|
422
|
+
item.activeToolId = latest?.[0];
|
|
423
|
+
}
|
|
424
|
+
break;
|
|
425
|
+
}
|
|
426
|
+
case "message_end":
|
|
427
|
+
item.completedAssistantTurns += 1;
|
|
428
|
+
break;
|
|
429
|
+
}
|
|
430
|
+
requestWidgetRender();
|
|
431
|
+
};
|
|
432
|
+
|
|
326
433
|
const finishWidgetItem = (id: string, status: Exclude<WidgetStatus, "working">) => {
|
|
327
434
|
const item = widgetItems.get(id);
|
|
328
435
|
if (!item) return;
|
|
329
436
|
item.status = status;
|
|
330
437
|
item.finishedAt = Date.now();
|
|
438
|
+
item.activeTools.clear();
|
|
439
|
+
item.activeToolId = undefined;
|
|
331
440
|
if (![...widgetItems.values()].some(({ status }) => status === "working")) stopWidgetTimer();
|
|
332
441
|
requestWidgetRender();
|
|
333
442
|
};
|
|
@@ -443,15 +552,18 @@ export default function subagentExtension(
|
|
|
443
552
|
maxRuntimeMs: timeoutPolicy.maxMs,
|
|
444
553
|
getSessionGeneration: () => sessionEpoch,
|
|
445
554
|
loadRoles,
|
|
446
|
-
resolveLaunch: (role, ctx) => {
|
|
555
|
+
resolveLaunch: (role, modelClass, ctx) => {
|
|
447
556
|
const launchCtx = latestCtx ?? ctx;
|
|
448
557
|
return createRoleLaunch(pi, launchCtx, {
|
|
449
558
|
role,
|
|
450
|
-
route:
|
|
559
|
+
route: modelClass === undefined
|
|
560
|
+
? resolveConfiguredTaskRoute(launchCtx, SUBAGENT_TASK)
|
|
561
|
+
: resolveTaskRoute(launchCtx, modelClass),
|
|
451
562
|
});
|
|
452
563
|
},
|
|
453
564
|
startWidget: startWidgetItem,
|
|
454
565
|
updateWidgetTokens,
|
|
566
|
+
updateWidgetActivity,
|
|
455
567
|
finishWidget: finishWidgetItem,
|
|
456
568
|
});
|
|
457
569
|
|
|
@@ -463,24 +575,24 @@ export default function subagentExtension(
|
|
|
463
575
|
promptGuidelines: [
|
|
464
576
|
"Call delegate_task with exactly one mode: role+task for one task, tasks for 1–8 independent parallel tasks, or chain for 1–8 dependent sequential tasks using {previous} for the immediately preceding assistant output.",
|
|
465
577
|
"Every delegate_task entry must state its objective, exact scope and exclusions, relevant context and constraints, expected deliverable, and validation; never pass the parent request unchanged.",
|
|
466
|
-
"For each delegate_task entry,
|
|
578
|
+
"For each delegate_task entry, populate model and thinking only for an explicit user override; otherwise choose only modelClass: fast normally, or balanced upfront for obviously complex work. This is Main policy, not runtime enforcement.",
|
|
467
579
|
"Parallel delegate_task entries must own non-overlapping files. Keep integration and cross-cutting decisions in Main, and use the minimum number of Subagents needed.",
|
|
468
580
|
"delegate_task background applies to the whole selected workflow and returns before results exist; use it only when the user explicitly asks for non-blocking work.",
|
|
469
581
|
],
|
|
470
582
|
parameters: WorkflowSchema,
|
|
471
|
-
|
|
583
|
+
renderShell: "self",
|
|
584
|
+
renderCall(args, theme, _context) {
|
|
585
|
+
return renderToolLines([theme.fg("toolTitle", workflowCallLabel(args))], theme);
|
|
586
|
+
},
|
|
587
|
+
renderResult(result, { isPartial }, theme, _context) {
|
|
588
|
+
if (isPartial) return renderToolLines([], theme);
|
|
472
589
|
const details = result.details;
|
|
473
|
-
if (isWorkflowTransportDetails(details))
|
|
474
|
-
return {
|
|
475
|
-
invalidate() {},
|
|
476
|
-
render: (width) => renderWorkflowResult(details, width, theme),
|
|
477
|
-
};
|
|
478
|
-
}
|
|
590
|
+
if (isWorkflowTransportDetails(details)) return renderToolLines(workflowResultLines(details, theme), theme);
|
|
479
591
|
if (typeof details === "object" && details !== null && (details as { background?: unknown }).background === true) {
|
|
480
|
-
return
|
|
592
|
+
return renderToolLines([theme.fg("muted", "Background workflow accepted.")], theme);
|
|
481
593
|
}
|
|
482
594
|
const text = result.content.find((part) => part.type === "text")?.text ?? "(no output)";
|
|
483
|
-
return
|
|
595
|
+
return renderToolLines([theme.fg("muted", text)], theme);
|
|
484
596
|
},
|
|
485
597
|
prepareArguments(args) {
|
|
486
598
|
try {
|
|
@@ -591,6 +703,7 @@ export default function subagentExtension(
|
|
|
591
703
|
emitUpdate(emitToolUpdates);
|
|
592
704
|
},
|
|
593
705
|
onTokens: (tokens) => updateWidgetTokens(entry.id, tokens),
|
|
706
|
+
onActivity: (event) => updateWidgetActivity(entry.id, event),
|
|
594
707
|
prepare: async () => {
|
|
595
708
|
// Route and effective Role resources resolve only after this entry's
|
|
596
709
|
// shared executor permit, before isolated state is created.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { type Component, truncateToWidth } from "@earendil-works/pi-tui";
|
|
3
|
+
|
|
4
|
+
export const MAX_RENDERED_RESULT_LINES = 3;
|
|
5
|
+
|
|
6
|
+
export function renderToolLines(lines: readonly string[], theme: Theme): Component {
|
|
7
|
+
return {
|
|
8
|
+
invalidate() {},
|
|
9
|
+
render: (width) => {
|
|
10
|
+
const shown = lines.length > MAX_RENDERED_RESULT_LINES
|
|
11
|
+
? [...lines.slice(0, MAX_RENDERED_RESULT_LINES - 1), theme.fg("muted", `… ${lines.length - MAX_RENDERED_RESULT_LINES + 1} more`)]
|
|
12
|
+
: lines;
|
|
13
|
+
return shown.map((line) => truncateToWidth(line.replace(/[\r\n]+/g, " "), width));
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@henryqw/pi-subagent",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.1.0",
|
|
4
4
|
"description": "Delegate bounded single, parallel, or chained tasks to isolated Pi roles.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -66,6 +66,6 @@
|
|
|
66
66
|
"dependencies": {
|
|
67
67
|
"@henryqw/pi-herdr": "^0.4.0",
|
|
68
68
|
"@henryqw/pi-multi-codex": "^0.3.8",
|
|
69
|
-
"@henryqw/pi-task-models": "^
|
|
69
|
+
"@henryqw/pi-task-models": "^2.0.0"
|
|
70
70
|
}
|
|
71
71
|
}
|
|
@@ -5,28 +5,28 @@ description: Run bounded independent implementation through the runtime-managed
|
|
|
5
5
|
|
|
6
6
|
# Delegated Development
|
|
7
7
|
|
|
8
|
-
You are Main: slice work and call `delegate_flow`. Do not implement child work yourself or use external model tools, push, publish, or release.
|
|
8
|
+
You are Main, the planner/orchestrator: slice work and call `delegate_flow`. Do not implement child work yourself or use external model tools, push, publish, or release.
|
|
9
9
|
|
|
10
10
|
## Slice
|
|
11
11
|
|
|
12
12
|
Use the fewest cohesive units. `delegate_flow` is for independent units expected to commute; combine or sequence work that overlaps files, APIs, schemas, generated output, package metadata, lockfiles, or invariants. Dependent work remains outside Flow, in one task or ordinary caller-controlled sequencing.
|
|
13
13
|
|
|
14
|
-
Give every unit a bounded objective, owned scope and exclusions,
|
|
14
|
+
Give every unit a bounded objective, owned scope and exclusions, and its direct validation command/argument array. Do not pass the parent request unchanged. Use `modelClass: "fast"` normally; use `"balanced"` upfront only for obviously complex work. Add non-empty `review` only for an explicit judgment that automated validation cannot establish. Call `delegate_flow` with 1–8 units; the runtime always supplies the effective Implementer and supplies the Reviewer only when a unit needs review.
|
|
15
15
|
|
|
16
16
|
## Runtime Flow
|
|
17
17
|
|
|
18
|
-
The runtime owns the unit worktrees and all Git identity, rebasing, declared validation, exact read-only review, fast-forward integration, and cleanup.
|
|
18
|
+
The runtime owns the unit worktrees and all Git identity, rebasing, committed-state inspection, declared validation, conditional exact read-only review, fast-forward integration, and cleanup. Declared validation is the authority for objective verification. A validated unit without `review` skips review evidence and Reviewer launch, then integrates its exact validated tip. A unit with `review` receives the existing exact `{base, tip, patchPath}` protocol and must receive exactly `PASS` before integration.
|
|
19
19
|
|
|
20
20
|
Trust the structured Flow outcome. Never edit a child worktree, manage its branches, prepare review evidence, reimplement Flow, or manually integrate its changes. Do not repeat Flow validation after it has completed or integrated a unit.
|
|
21
21
|
|
|
22
|
-
A successful Flow owns integration and cleanup. A blocked outcome is repairable: provide one explicit continuation and no more:
|
|
22
|
+
A successful Flow owns integration and cleanup. A blocked outcome is repairable once: provide one explicit continuation and no more:
|
|
23
23
|
|
|
24
24
|
```ts
|
|
25
|
-
delegate_flow_continue({ guidance: "Address the reported block and complete the bounded unit." })
|
|
25
|
+
delegate_flow_continue({ guidance: "Address the reported block and complete the bounded unit.", modelClass: "balanced" })
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
Make the guidance specific to the reported implementation, validation, or review failure. Do not call continuation unless Flow reports a repairable block. If continuation or Flow returns a terminal failure, inspect every retained path reported by the runtime, then reslice or manually recover from Main; do not retry the Flow or guess a rebase resolution. A cleanup warning does not undo successful integration.
|
|
28
|
+
Make the guidance specific to the reported implementation, validation, or review failure. Omit `modelClass` to retain the blocked unit's current class; supply it only to replace that one repair's class. Do not call continuation unless Flow reports a repairable block. If continuation or Flow returns a terminal failure, inspect every retained path reported by the runtime, then reslice or manually recover from Main; do not retry the Flow or guess a rebase resolution. A cleanup warning does not undo successful integration.
|
|
29
29
|
|
|
30
30
|
## Ordinary delegation
|
|
31
31
|
|
|
32
|
-
Use `delegate_task` for a single bounded task, independent parallel tasks, or dependent chain work that is not a Flow. Give each entry its objective, exact scope and exclusions, relevant context and constraints, expected deliverable, and focused validation.
|
|
32
|
+
Use `delegate_task` for a single bounded task, independent parallel tasks, or dependent chain work that is not a Flow. Give each entry its objective, exact scope and exclusions, relevant context and constraints, expected deliverable, and focused validation. Populate direct `model` and `thinking` only when the user explicitly asks for those overrides; otherwise choose only `modelClass` (`fast` normally, `balanced` upfront for obviously complex work). Keep integration and cross-cutting decisions in Main, and use the minimum number of Subagents needed.
|