iosm-cli 0.2.3 → 0.2.5

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.
Files changed (93) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/README.md +43 -10
  3. package/dist/cli/args.d.ts +1 -1
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +1 -1
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/core/agent-profiles.d.ts +2 -1
  8. package/dist/core/agent-profiles.d.ts.map +1 -1
  9. package/dist/core/agent-profiles.js +18 -5
  10. package/dist/core/agent-profiles.js.map +1 -1
  11. package/dist/core/agent-session.d.ts +7 -0
  12. package/dist/core/agent-session.d.ts.map +1 -1
  13. package/dist/core/agent-session.js +64 -15
  14. package/dist/core/agent-session.js.map +1 -1
  15. package/dist/core/agent-teams.d.ts.map +1 -1
  16. package/dist/core/agent-teams.js +90 -19
  17. package/dist/core/agent-teams.js.map +1 -1
  18. package/dist/core/extensions/types.d.ts +1 -1
  19. package/dist/core/extensions/types.d.ts.map +1 -1
  20. package/dist/core/extensions/types.js.map +1 -1
  21. package/dist/core/footer-data-provider.d.ts +6 -1
  22. package/dist/core/footer-data-provider.d.ts.map +1 -1
  23. package/dist/core/footer-data-provider.js +9 -0
  24. package/dist/core/footer-data-provider.js.map +1 -1
  25. package/dist/core/orchestration-limits.d.ts +6 -0
  26. package/dist/core/orchestration-limits.d.ts.map +1 -0
  27. package/dist/core/orchestration-limits.js +6 -0
  28. package/dist/core/orchestration-limits.js.map +1 -0
  29. package/dist/core/parallel-task-agent.d.ts +23 -1
  30. package/dist/core/parallel-task-agent.d.ts.map +1 -1
  31. package/dist/core/parallel-task-agent.js +110 -20
  32. package/dist/core/parallel-task-agent.js.map +1 -1
  33. package/dist/core/sdk.d.ts +6 -1
  34. package/dist/core/sdk.d.ts.map +1 -1
  35. package/dist/core/sdk.js +34 -6
  36. package/dist/core/sdk.js.map +1 -1
  37. package/dist/core/shared-memory.d.ts +2 -2
  38. package/dist/core/shared-memory.d.ts.map +1 -1
  39. package/dist/core/shared-memory.js +220 -91
  40. package/dist/core/shared-memory.js.map +1 -1
  41. package/dist/core/singular.d.ts.map +1 -1
  42. package/dist/core/singular.js +3 -1
  43. package/dist/core/singular.js.map +1 -1
  44. package/dist/core/subagents.d.ts +1 -1
  45. package/dist/core/subagents.d.ts.map +1 -1
  46. package/dist/core/subagents.js +32 -10
  47. package/dist/core/subagents.js.map +1 -1
  48. package/dist/core/swarm/planner.d.ts.map +1 -1
  49. package/dist/core/swarm/planner.js +200 -12
  50. package/dist/core/swarm/planner.js.map +1 -1
  51. package/dist/core/swarm/scheduler.d.ts +2 -0
  52. package/dist/core/swarm/scheduler.d.ts.map +1 -1
  53. package/dist/core/swarm/scheduler.js +87 -6
  54. package/dist/core/swarm/scheduler.js.map +1 -1
  55. package/dist/core/system-prompt.d.ts.map +1 -1
  56. package/dist/core/system-prompt.js +1 -0
  57. package/dist/core/system-prompt.js.map +1 -1
  58. package/dist/core/tools/ast-grep.d.ts.map +1 -1
  59. package/dist/core/tools/ast-grep.js +2 -0
  60. package/dist/core/tools/ast-grep.js.map +1 -1
  61. package/dist/core/tools/edit.d.ts +8 -4
  62. package/dist/core/tools/edit.d.ts.map +1 -1
  63. package/dist/core/tools/edit.js +18 -3
  64. package/dist/core/tools/edit.js.map +1 -1
  65. package/dist/core/tools/index.d.ts +9 -7
  66. package/dist/core/tools/index.d.ts.map +1 -1
  67. package/dist/core/tools/shared-memory.d.ts.map +1 -1
  68. package/dist/core/tools/shared-memory.js +34 -6
  69. package/dist/core/tools/shared-memory.js.map +1 -1
  70. package/dist/core/tools/task.d.ts +10 -3
  71. package/dist/core/tools/task.d.ts.map +1 -1
  72. package/dist/core/tools/task.js +830 -184
  73. package/dist/core/tools/task.js.map +1 -1
  74. package/dist/core/tools/todo.d.ts +10 -10
  75. package/dist/core/tools/todo.d.ts.map +1 -1
  76. package/dist/core/tools/todo.js +135 -17
  77. package/dist/core/tools/todo.js.map +1 -1
  78. package/dist/core/tools/yq.d.ts.map +1 -1
  79. package/dist/core/tools/yq.js +2 -0
  80. package/dist/core/tools/yq.js.map +1 -1
  81. package/dist/modes/interactive/components/footer.d.ts +1 -1
  82. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  83. package/dist/modes/interactive/components/footer.js +10 -9
  84. package/dist/modes/interactive/components/footer.js.map +1 -1
  85. package/dist/modes/interactive/interactive-mode.d.ts +22 -0
  86. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  87. package/dist/modes/interactive/interactive-mode.js +957 -75
  88. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  89. package/docs/cli-reference.md +8 -0
  90. package/docs/configuration.md +5 -1
  91. package/docs/interactive-mode.md +7 -1
  92. package/docs/orchestration-and-subagents.md +5 -0
  93. package/package.json +1 -1
@@ -4,19 +4,27 @@ import { spawnSync } from "node:child_process";
4
4
  import { Type } from "@sinclair/typebox";
5
5
  import { getTeamRun, updateTeamTaskStatus } from "../agent-teams.js";
6
6
  import { buildRetrospectiveDirective, classifyFailureCause, formatFailureCauseCounts, isRetrospectiveRetryable, } from "../failure-retrospective.js";
7
+ import { MAX_ORCHESTRATION_AGENTS, MAX_ORCHESTRATION_PARALLEL, MAX_SUBAGENT_DELEGATE_PARALLEL, MAX_SUBAGENT_DELEGATION_DEPTH, MAX_SUBAGENT_DELEGATIONS_PER_TASK, } from "../orchestration-limits.js";
8
+ import { AGENT_PROFILES, isReadOnlyProfileName, isValidProfileName, } from "../agent-profiles.js";
7
9
  const taskSchema = Type.Object({
8
- description: Type.String({
9
- description: "Short 3-5 word description of what the subagent will do",
10
- }),
11
- prompt: Type.String({
12
- description: "Full task prompt for the subagent",
13
- }),
10
+ description: Type.Optional(Type.String({
11
+ description: "Optional short 3-5 word description of what the subagent will do. If omitted, it is derived from prompt.",
12
+ })),
13
+ task: Type.Optional(Type.String({
14
+ description: "Legacy alias for prompt. If provided, it is treated as the subagent prompt when prompt is omitted.",
15
+ })),
16
+ args: Type.Optional(Type.String({
17
+ description: "Legacy alias for prompt used by some models. If provided, it is treated as the subagent prompt when prompt/task are omitted.",
18
+ })),
19
+ prompt: Type.Optional(Type.String({
20
+ description: "Optional full task prompt for the subagent. If omitted, the description is used as the prompt.",
21
+ })),
14
22
  agent: Type.Optional(Type.String({
15
23
  description: "Optional custom subagent name loaded from .iosm/agents or global agents directory.",
16
24
  })),
17
- profile: Type.String({
18
- description: "Subagent capability profile. Recommended values: explore, plan, iosm, iosm_analyst, iosm_verifier, cycle_planner, full. For custom agents, pass the agent name via `agent`, not `profile`.",
19
- }),
25
+ profile: Type.Optional(Type.String({
26
+ description: "Optional subagent capability profile. Defaults to the current host profile when omitted (or full if host profile is unavailable). Recommended values: explore, plan, iosm, meta, iosm_analyst, iosm_verifier, cycle_planner, full. For custom agents, pass the agent name via `agent`, not `profile`.",
27
+ })),
20
28
  cwd: Type.Optional(Type.String({
21
29
  description: "Optional working directory for this subagent. Relative paths are resolved from the current workspace.",
22
30
  })),
@@ -40,31 +48,30 @@ const taskSchema = Type.Object({
40
48
  })),
41
49
  delegate_parallel_hint: Type.Optional(Type.Integer({
42
50
  minimum: 1,
43
- maximum: 10,
51
+ maximum: MAX_SUBAGENT_DELEGATE_PARALLEL,
44
52
  description: "Optional hint for intra-task delegation fan-out. Higher value allows more delegated subtasks to run in parallel inside a single task execution.",
45
53
  })),
46
54
  });
47
- /** Tool names available per profile */
48
- const toolsByProfile = {
49
- explore: ["read", "grep", "find", "ls"],
50
- plan: ["read", "bash", "grep", "find", "ls"],
51
- iosm: ["read", "bash", "edit", "write", "grep", "find", "ls"],
52
- iosm_analyst: ["read", "bash", "grep", "find", "ls"],
53
- iosm_verifier: ["read", "bash", "write"],
54
- cycle_planner: ["read", "bash", "write"],
55
- full: ["read", "bash", "edit", "write", "grep", "find", "ls"],
56
- };
55
+ /** Tool names available per profile (kept in sync with AGENT_PROFILES). */
56
+ const toolsByProfile = Object.values(AGENT_PROFILES).reduce((acc, profile) => {
57
+ acc[profile.name] = [...profile.tools];
58
+ return acc;
59
+ }, {});
57
60
  /** System prompt injected per profile */
58
61
  const systemPromptByProfile = {
59
62
  explore: "You are a fast read-only codebase explorer. Answer concisely. Never write or edit files.",
60
63
  plan: "You are a technical architect. Analyze the codebase and produce a clear implementation plan. Do not write or edit files.",
61
64
  iosm: "You are an IOSM execution agent. Use IOSM methodology and keep IOSM artifacts synchronized with implementation.",
65
+ meta: "You are a meta orchestration agent. Your main job is to maximize safe parallel execution through delegates, not to personally do most of the implementation. Start with bounded read-only recon, then form a concrete execution graph: subtasks, delegate subtasks, dependencies, lock domains, and verification steps. The parent agent remains responsible for orchestration and synthesis, so decompose work aggressively instead of collapsing complex work into one worker. For any non-trivial task, orchestration is required: after recon, launch multiple focused delegates instead of continuing manual implementation in the parent agent, avoid direct write/edit work in the parent agent before delegation unless the task is clearly trivial, and do not hand the whole task to one specialist child when independent workstreams exist. If a delegated workstream still contains multiple independent slices, split it again with nested <delegate_task> blocks. Default to aggressive safe parallelism. If the user requested a specific degree of parallelism, honor it when feasible or explain the exact blocker. Use shared_memory as the default coordination channel between delegates: use stable namespaced keys, prefer read-before-write, and use CAS (if_version) for contested updates; reserve append mode for timeline/log keys. When delegation is not used for non-trivial work, explain why in one line and include DELEGATION_IMPOSSIBLE. Enforce test verification for code changes, complete only after all delegated branches are resolved, and explicitly justify any no-code path where tests are skipped.",
62
66
  iosm_analyst: "You are an IOSM metrics analyst. Analyze .iosm/ artifacts and codebase metrics. Be precise and evidence-based.",
63
67
  iosm_verifier: "You are an IOSM verifier. Validate checks and update only required IOSM artifacts with deterministic reasoning.",
64
68
  cycle_planner: "You are an IOSM cycle planner. Propose and align cycle goals with measurable outcomes and concrete risks.",
65
69
  full: "You are a software engineering agent. Execute the task end-to-end.",
66
70
  };
67
- const writeCapableProfiles = new Set(["full", "iosm_verifier", "cycle_planner"]);
71
+ const writeCapableTools = new Set(["bash", "edit", "write"]);
72
+ const backgroundUnsafeTools = new Set(writeCapableTools);
73
+ const writeCapableProfiles = new Set(Object.keys(toolsByProfile).filter((profileName) => toolsByProfile[profileName].some((tool) => writeCapableTools.has(tool))));
74
+ const backgroundSafeProfiles = Object.keys(toolsByProfile).filter((profileName) => toolsByProfile[profileName].every((tool) => !backgroundUnsafeTools.has(tool)));
68
75
  const delegationTagName = "delegate_task";
69
76
  class Semaphore {
70
77
  constructor(limit) {
@@ -121,11 +128,11 @@ class Mutex {
121
128
  return !this.locked && this.waiters.length === 0;
122
129
  }
123
130
  }
124
- const maxParallelFromEnv = parseBoundedInt(process.env.IOSM_SUBAGENT_MAX_PARALLEL, 20, 1, 20);
131
+ const maxParallelFromEnv = parseBoundedInt(process.env.IOSM_SUBAGENT_MAX_PARALLEL, MAX_ORCHESTRATION_PARALLEL, 1, MAX_ORCHESTRATION_PARALLEL);
125
132
  const subagentSemaphore = new Semaphore(maxParallelFromEnv);
126
- const maxDelegationDepthFromEnv = parseBoundedInt(process.env.IOSM_SUBAGENT_MAX_DELEGATION_DEPTH, 1, 0, 3);
127
- const maxDelegationsPerTaskFromEnv = parseBoundedInt(process.env.IOSM_SUBAGENT_MAX_DELEGATIONS_PER_TASK, 10, 0, 10);
128
- const maxDelegatedParallelFromEnv = parseBoundedInt(process.env.IOSM_SUBAGENT_MAX_DELEGATE_PARALLEL, 10, 1, 10);
133
+ const maxDelegationDepthFromEnv = parseBoundedInt(process.env.IOSM_SUBAGENT_MAX_DELEGATION_DEPTH, 2, 0, MAX_SUBAGENT_DELEGATION_DEPTH);
134
+ const maxDelegationsPerTaskFromEnv = parseBoundedInt(process.env.IOSM_SUBAGENT_MAX_DELEGATIONS_PER_TASK, MAX_SUBAGENT_DELEGATIONS_PER_TASK, 0, MAX_SUBAGENT_DELEGATIONS_PER_TASK);
135
+ const maxDelegatedParallelFromEnv = parseBoundedInt(process.env.IOSM_SUBAGENT_MAX_DELEGATE_PARALLEL, MAX_SUBAGENT_DELEGATE_PARALLEL, 1, MAX_SUBAGENT_DELEGATE_PARALLEL);
129
136
  const emptyOutputRetriesFromEnv = parseBoundedInt(process.env.IOSM_SUBAGENT_EMPTY_OUTPUT_RETRIES, 1, 0, 2);
130
137
  const retrospectiveRetriesFromEnv = parseBoundedInt(process.env.IOSM_SUBAGENT_RETRO_RETRIES, 1, 0, 1);
131
138
  const orchestrationDependencyWaitTimeoutMsFromEnv = parseBoundedInt(process.env.IOSM_ORCHESTRATION_DEPENDENCY_WAIT_TIMEOUT_MS, 120_000, 5_000, 900_000);
@@ -141,14 +148,21 @@ function parseBoundedInt(raw, fallback, min, max) {
141
148
  return fallback;
142
149
  return Math.max(min, Math.min(max, parsed));
143
150
  }
144
- function shouldAutoDelegateByAgent(agentName) {
145
- if (!agentName)
146
- return false;
147
- const normalized = agentName.trim().toLowerCase();
148
- return normalized.includes("orchestrator");
151
+ function shouldAutoDelegate(input) {
152
+ const profile = input.profile?.trim().toLowerCase();
153
+ if (profile === "meta")
154
+ return true;
155
+ const hostProfile = input.hostProfile?.trim().toLowerCase();
156
+ if (hostProfile === "meta")
157
+ return true;
158
+ const agentName = input.agentName?.trim().toLowerCase();
159
+ return !!agentName && agentName.includes("orchestrator");
149
160
  }
150
- function deriveAutoDelegateParallelHint(agentName, description, prompt) {
151
- if (!shouldAutoDelegateByAgent(agentName))
161
+ function deriveAutoDelegateParallelHint(profile, agentName, hostProfile, description, prompt) {
162
+ const normalizedProfile = profile?.trim().toLowerCase();
163
+ const isMetaProfile = normalizedProfile === "meta";
164
+ const isMetaHost = hostProfile?.trim().toLowerCase() === "meta";
165
+ if (!shouldAutoDelegate({ profile: normalizedProfile, agentName, hostProfile }))
152
166
  return undefined;
153
167
  const text = `${description}\n${prompt}`.trim();
154
168
  if (!text)
@@ -163,6 +177,9 @@ function deriveAutoDelegateParallelHint(agentName, description, prompt) {
163
177
  const fileLikeMatches = normalized.match(/\b[A-Za-z0-9_.-]+\.[A-Za-z0-9]{1,8}\b/g) ?? [];
164
178
  const listMarkers = text.match(/(?:^|\n)\s*(?:[-*]|\d+[.)])\s+/g)?.length ?? 0;
165
179
  const hasCodeBlock = text.includes("```");
180
+ const actionTokenMatches = normalized.match(/\b(?:audit|security|auth|rbac|sqli|sql|injection|fix|implement|refactor|migrat|harden|verify|test|scan|orchestrate|parallel|delegate|bug|vulnerab)\w*/gi) ?? [];
181
+ const strongActionSignal = new Set(actionTokenMatches.map((token) => token.toLowerCase())).size >= 2;
182
+ const metaOrchestratorContext = isMetaProfile || isMetaHost;
166
183
  let score = 0;
167
184
  if (words >= 40) {
168
185
  score += 2;
@@ -180,12 +197,33 @@ function deriveAutoDelegateParallelHint(agentName, description, prompt) {
180
197
  score += 1;
181
198
  }
182
199
  const referenceCount = pathLikeMatches.length + fileLikeMatches.length;
200
+ const metaNonTrivialSignal = words >= 12 ||
201
+ clauses >= 3 ||
202
+ listMarkers >= 1 ||
203
+ referenceCount >= 1 ||
204
+ hasCodeBlock ||
205
+ (strongActionSignal && words >= 4);
183
206
  if (referenceCount >= 3 || (referenceCount >= 1 && words >= 20)) {
184
207
  score += 1;
185
208
  }
186
209
  if (hasCodeBlock) {
187
210
  score += 1;
188
211
  }
212
+ if (metaOrchestratorContext) {
213
+ // In meta orchestration, require delegation pressure for non-trivial prompts
214
+ // even when lexical scoring is still low.
215
+ if (score === 0) {
216
+ if (strongActionSignal && words >= 4) {
217
+ score = 1;
218
+ }
219
+ else if (metaNonTrivialSignal) {
220
+ score = 2;
221
+ }
222
+ }
223
+ else if (score > 0) {
224
+ score += 1;
225
+ }
226
+ }
189
227
  if (score >= 6)
190
228
  return 10;
191
229
  if (score >= 5)
@@ -203,6 +241,40 @@ function deriveAutoDelegateParallelHint(agentName, description, prompt) {
203
241
  function normalizeSpacing(text) {
204
242
  return text.replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
205
243
  }
244
+ function deriveTaskDescriptionFromPrompt(prompt) {
245
+ const firstMeaningfulLine = prompt
246
+ .split("\n")
247
+ .map((line) => line.trim())
248
+ .find((line) => line.length > 0) ?? "Run subtask";
249
+ const normalized = firstMeaningfulLine
250
+ .replace(/^[-*]\s+/, "")
251
+ .replace(/^\d+[.)]\s+/, "")
252
+ .replace(/\s+/g, " ")
253
+ .trim();
254
+ if (normalized.length <= 80) {
255
+ return normalized;
256
+ }
257
+ return `${normalized.slice(0, 77).trimEnd()}...`;
258
+ }
259
+ function normalizeTaskPayload(input) {
260
+ const rawDescription = input.description?.trim();
261
+ const rawTask = input.task?.trim();
262
+ const rawArgs = input.args?.trim();
263
+ const rawPrompt = input.prompt?.trim() || rawTask || rawArgs;
264
+ if (rawDescription && rawPrompt) {
265
+ return { description: rawDescription, prompt: rawPrompt };
266
+ }
267
+ if (rawDescription) {
268
+ return { description: rawDescription, prompt: rawDescription };
269
+ }
270
+ if (rawPrompt) {
271
+ return {
272
+ description: deriveTaskDescriptionFromPrompt(rawPrompt),
273
+ prompt: rawPrompt,
274
+ };
275
+ }
276
+ throw new Error('Task tool requires at least one of "description", "task", "args", or "prompt".');
277
+ }
206
278
  function cloneDelegateItems(items) {
207
279
  return items ? items.map((item) => ({ ...item })) : undefined;
208
280
  }
@@ -248,26 +320,193 @@ function mergeRunStats(base, next) {
248
320
  assistantMessages: (base?.assistantMessages ?? 0) + (next?.assistantMessages ?? 0),
249
321
  };
250
322
  }
251
- function buildDelegationProtocolPrompt(depthRemaining, maxDelegations) {
323
+ function buildDelegationProtocolPrompt(depthRemaining, maxDelegations, minDelegationsPreferred = 0) {
252
324
  if (depthRemaining <= 0) {
253
325
  return [
254
326
  `Delegation protocol: depth limit reached.`,
255
327
  `Do not emit <${delegationTagName}> blocks.`,
256
328
  ].join("\n");
257
329
  }
330
+ if (minDelegationsPreferred > 0) {
331
+ const required = Math.min(Math.max(1, minDelegationsPreferred), maxDelegations);
332
+ return [
333
+ `Delegation protocol (required for this run): emit at least ${required} XML block(s) when the assigned work still contains independent slices.`,
334
+ `For broad audit, implementation, or verification tasks, split by subsystem, file family, or verification stream instead of producing one monolithic answer.`,
335
+ `<${delegationTagName} profile="explore|plan|iosm|meta|iosm_analyst|iosm_verifier|cycle_planner|full" agent="optional custom subagent name" description="short title" cwd="optional relative path" lock_key="optional lock key" model="optional model override" isolation="none|worktree" depends_on="optional indices like 1|3">`,
336
+ "Detailed delegated task prompt",
337
+ `</${delegationTagName}>`,
338
+ `Keep a brief coordinator note outside the blocks, but do not collapse the full workload into one monolithic answer.`,
339
+ `If safe decomposition is truly impossible, output exactly one line: DELEGATION_IMPOSSIBLE: <precise reason>.`,
340
+ `When shared_memory tools are available, exchange intermediate state through shared_memory_write/shared_memory_read instead of repeating large context.`,
341
+ `Shared-memory protocol: use stable namespaced keys (findings/<stream>, plan/<stream>, risks/<stream>).`,
342
+ `Use scope=run for cross-stream coordination, scope=task for local scratch state, read before overwrite, and use if_version for contested updates.`,
343
+ `Reserve mode=append for timeline/log keys only; avoid append on canonical state keys.`,
344
+ ].join("\n");
345
+ }
258
346
  return [
259
347
  `Delegation protocol (optional): if you discover concrete independent follow-ups, emit up to ${maxDelegations} XML block(s):`,
260
- `<${delegationTagName} profile="explore|plan|iosm|iosm_analyst|iosm_verifier|cycle_planner|full" agent="optional custom subagent name" description="short title" cwd="optional relative path" lock_key="optional lock key" model="optional model override" isolation="none|worktree" depends_on="optional indices like 1|3">`,
348
+ `<${delegationTagName} profile="explore|plan|iosm|meta|iosm_analyst|iosm_verifier|cycle_planner|full" agent="optional custom subagent name" description="short title" cwd="optional relative path" lock_key="optional lock key" model="optional model override" isolation="none|worktree" depends_on="optional indices like 1|3">`,
261
349
  "Detailed delegated task prompt",
262
350
  `</${delegationTagName}>`,
263
351
  `Only emit blocks when necessary. Keep normal analysis/answer text outside those blocks.`,
264
352
  `When shared_memory tools are available, exchange intermediate state through shared_memory_write/shared_memory_read instead of repeating large context.`,
353
+ `Shared-memory protocol: prefer namespaced keys and read-before-write discipline; use CAS (if_version) on shared state updates.`,
354
+ `Reserve mode=append for timeline/log keys only.`,
265
355
  ].join("\n");
266
356
  }
267
- function withDelegationPrompt(basePrompt, depthRemaining, maxDelegations) {
268
- const protocol = buildDelegationProtocolPrompt(depthRemaining, maxDelegations);
357
+ function truncateForDelegationContext(text, maxChars = 2200) {
358
+ const normalized = normalizeSpacing(text);
359
+ if (normalized.length <= maxChars)
360
+ return normalized;
361
+ return `${normalized.slice(0, Math.max(100, maxChars - 3)).trimEnd()}...`;
362
+ }
363
+ function extractDelegationWorkstreams(text, maxItems) {
364
+ if (maxItems <= 0)
365
+ return [];
366
+ const seen = new Set();
367
+ const pushUnique = (raw) => {
368
+ const cleaned = raw
369
+ .replace(/^[-*]\s+/, "")
370
+ .replace(/^\d+[.)]\s+/, "")
371
+ .replace(/\s+/g, " ")
372
+ .trim();
373
+ if (cleaned.length < 5)
374
+ return;
375
+ const key = cleaned.toLowerCase();
376
+ if (seen.has(key))
377
+ return;
378
+ seen.add(key);
379
+ };
380
+ for (const line of text.split("\n")) {
381
+ if (!/^\s*(?:[-*]|\d+[.)])\s+/.test(line))
382
+ continue;
383
+ pushUnique(line);
384
+ if (seen.size >= maxItems)
385
+ break;
386
+ }
387
+ if (seen.size < maxItems) {
388
+ const fragments = text
389
+ .split(/[\n.;:]+/g)
390
+ .map((fragment) => fragment.trim())
391
+ .filter((fragment) => fragment.length >= 10)
392
+ .slice(0, maxItems * 3);
393
+ for (const fragment of fragments) {
394
+ pushUnique(fragment);
395
+ if (seen.size >= maxItems)
396
+ break;
397
+ }
398
+ }
399
+ return Array.from(seen).slice(0, maxItems);
400
+ }
401
+ function deriveAutoDelegateProfile(baseProfile, description, prompt) {
402
+ const signal = `${description}\n${prompt}`.toLowerCase();
403
+ const writeIntent = /\b(?:implement|fix|patch|refactor|rewrite|edit|update|migrate|change|write|apply)\b/.test(signal);
404
+ if (baseProfile === "full")
405
+ return writeIntent ? "full" : "explore";
406
+ if (baseProfile === "meta")
407
+ return writeIntent ? "full" : "explore";
408
+ if (baseProfile === "iosm")
409
+ return writeIntent ? "full" : "explore";
410
+ if (baseProfile === "iosm_verifier")
411
+ return "iosm_verifier";
412
+ if (baseProfile === "cycle_planner")
413
+ return "cycle_planner";
414
+ if (baseProfile === "plan" || baseProfile === "iosm_analyst")
415
+ return "explore";
416
+ return "explore";
417
+ }
418
+ function pickAutoDelegateAgent(workstream, availableCustomNames) {
419
+ if (availableCustomNames.length === 0)
420
+ return undefined;
421
+ const normalizedWorkstream = workstream.toLowerCase();
422
+ const names = availableCustomNames.map((name) => ({ raw: name, normalized: name.toLowerCase() }));
423
+ const findByHint = (hints) => {
424
+ for (const hint of hints) {
425
+ const exact = names.find((item) => item.normalized === hint);
426
+ if (exact)
427
+ return exact.raw;
428
+ const contains = names.find((item) => item.normalized.includes(hint));
429
+ if (contains)
430
+ return contains.raw;
431
+ }
432
+ return undefined;
433
+ };
434
+ if (/\b(?:test|qa|coverage|verification|regression)\b/.test(normalizedWorkstream)) {
435
+ return findByHint(["qa_test_engineer", "qa", "tester", "verification"]);
436
+ }
437
+ if (/\b(?:ui|ux|design|layout|accessibility)\b/.test(normalizedWorkstream)) {
438
+ return findByHint(["uiux_top_senior", "uiux", "ui", "ux", "design"]);
439
+ }
440
+ if (/\b(?:architecture|codebase|refactor|security|rbac|auth|database|api)\b/.test(normalizedWorkstream)) {
441
+ return findByHint(["codebase_auditor", "architect", "security", "backend"]);
442
+ }
443
+ return undefined;
444
+ }
445
+ function buildAutoDelegationPrompt(input) {
446
+ const objective = truncateForDelegationContext(`${input.rootDescription}\n\n${input.rootPrompt}`);
447
+ return normalizeSpacing([
448
+ `Workstream ${input.ordinal}/${input.total}: ${input.streamTitle}`,
449
+ "Scope:",
450
+ `- Own this stream end-to-end and avoid duplicating sibling streams.`,
451
+ `- Produce concrete findings/changes for this stream only.`,
452
+ "Coordinator objective:",
453
+ objective,
454
+ ].join("\n"));
455
+ }
456
+ function synthesizeDelegationRequests(input) {
457
+ const desiredTotal = Math.max(0, Math.min(input.maxDelegations, input.minDelegationsPreferred));
458
+ const missing = Math.max(0, desiredTotal - input.currentDelegates);
459
+ if (missing <= 0)
460
+ return [];
461
+ const combined = `${input.description}\n${input.prompt}`.trim();
462
+ const extracted = extractDelegationWorkstreams(combined, Math.max(missing, desiredTotal));
463
+ const fallbackByIndex = [
464
+ "Architecture and structure analysis",
465
+ "Behavioral verification and tests",
466
+ "Risk, regressions, and remediation",
467
+ "Integration and dependency checks",
468
+ "Delivery summary and rollout constraints",
469
+ ];
470
+ const titles = [];
471
+ for (const stream of extracted) {
472
+ titles.push(stream);
473
+ if (titles.length >= missing)
474
+ break;
475
+ }
476
+ for (let index = 0; titles.length < missing && index < fallbackByIndex.length; index += 1) {
477
+ titles.push(fallbackByIndex[index]);
478
+ }
479
+ while (titles.length < missing) {
480
+ titles.push(`Independent workstream ${titles.length + 1}`);
481
+ }
482
+ const defaultProfile = deriveAutoDelegateProfile(input.baseProfile, input.description, input.prompt);
483
+ const synthesizedLockKey = writeCapableProfiles.has(defaultProfile) ? "auto-synth-write-lock" : undefined;
484
+ return titles.map((streamTitle, index) => ({
485
+ description: `Auto: ${streamTitle}`,
486
+ profile: defaultProfile,
487
+ agent: pickAutoDelegateAgent(streamTitle, input.availableCustomNames),
488
+ prompt: buildAutoDelegationPrompt({
489
+ streamTitle,
490
+ rootDescription: input.description,
491
+ rootPrompt: input.prompt,
492
+ ordinal: input.currentDelegates + index + 1,
493
+ total: input.currentDelegates + titles.length,
494
+ }),
495
+ cwd: undefined,
496
+ lockKey: synthesizedLockKey,
497
+ model: undefined,
498
+ isolation: undefined,
499
+ dependsOn: undefined,
500
+ }));
501
+ }
502
+ function withDelegationPrompt(basePrompt, depthRemaining, maxDelegations, minDelegationsPreferred = 0) {
503
+ const protocol = buildDelegationProtocolPrompt(depthRemaining, maxDelegations, minDelegationsPreferred);
269
504
  return `${basePrompt}\n\n${protocol}`;
270
505
  }
506
+ function withSubagentInstructions(basePrompt, instructions) {
507
+ const trimmed = instructions?.trim();
508
+ return trimmed ? `${basePrompt}\n\n${trimmed}` : basePrompt;
509
+ }
271
510
  function buildSharedMemoryGuidance(runId, taskId) {
272
511
  return [
273
512
  "[SHARED_MEMORY]",
@@ -277,7 +516,10 @@ function buildSharedMemoryGuidance(runId, taskId) {
277
516
  "Guidelines:",
278
517
  "- Use scope=run for cross-agent data and scope=task for task-local notes.",
279
518
  "- Keep entries compact and key-based (for example: findings/auth, plan/step-1, risks/session).",
519
+ "- Prefer one canonical key per stream and deduplicate updates; avoid redundant writes in loops.",
280
520
  "- Read before overwrite when collaborating on the same key.",
521
+ "- Use if_version CAS for contested updates on shared keys.",
522
+ "- Use mode=append only for log/timeline keys; use mode=set for canonical state.",
281
523
  "[/SHARED_MEMORY]",
282
524
  ].join("\n");
283
525
  }
@@ -295,19 +537,21 @@ function parseDelegationRequests(output, maxRequests) {
295
537
  return "";
296
538
  }
297
539
  const attrs = {};
298
- for (const match of attrsRaw.matchAll(/([A-Za-z_][A-Za-z0-9_-]*)="([^"]*)"/g)) {
299
- attrs[match[1].toLowerCase()] = match[2];
540
+ for (const match of attrsRaw.matchAll(/([A-Za-z_][A-Za-z0-9_-]*)\s*=\s*(?:"([^"]*)"|'([^']*)')/g)) {
541
+ attrs[match[1].toLowerCase()] = (match[2] ?? match[3] ?? "").trim();
300
542
  }
301
543
  const prompt = normalizeSpacing(bodyRaw ?? "");
302
544
  if (!prompt) {
303
545
  warnings.push(`Ignored delegation block with empty prompt.`);
304
546
  return "";
305
547
  }
306
- const profile = (attrs.profile ?? "explore").trim();
307
- if (!(profile in toolsByProfile)) {
308
- warnings.push(`Ignored delegation block with unknown profile "${profile}".`);
548
+ const profileRaw = (attrs.profile ?? "explore").trim();
549
+ if (!profileRaw) {
550
+ warnings.push(`Ignored delegation block with empty profile.`);
309
551
  return "";
310
552
  }
553
+ const normalizedProfile = profileRaw.toLowerCase();
554
+ const profile = isValidProfileName(normalizedProfile) ? normalizedProfile : profileRaw;
311
555
  const isolationRaw = (attrs.isolation ?? "").trim().toLowerCase();
312
556
  const isolation = isolationRaw === "worktree" ? "worktree" : isolationRaw === "none" ? "none" : undefined;
313
557
  if (isolationRaw && !isolation) {
@@ -338,6 +582,9 @@ function parseDelegationRequests(output, maxRequests) {
338
582
  cleanedOutput: normalizeSpacing(cleaned),
339
583
  };
340
584
  }
585
+ function isBackgroundSafeToolset(tools) {
586
+ return tools.every((toolName) => !backgroundUnsafeTools.has(toolName));
587
+ }
341
588
  function getCwdLockKey(cwd) {
342
589
  // Normalize lock key to keep behavior consistent across path aliases.
343
590
  return path.resolve(cwd).toLowerCase();
@@ -368,9 +615,9 @@ function getRunParallelLimit(cwd, runId) {
368
615
  return 1;
369
616
  const maxParallel = teamRun.maxParallel;
370
617
  if (!Number.isInteger(maxParallel) || !maxParallel || maxParallel < 1) {
371
- return Math.max(1, Math.min(teamRun.agents, 20));
618
+ return Math.max(1, Math.min(teamRun.agents, MAX_ORCHESTRATION_AGENTS));
372
619
  }
373
- return Math.max(1, Math.min(maxParallel, 20));
620
+ return Math.max(1, Math.min(maxParallel, MAX_ORCHESTRATION_PARALLEL));
374
621
  }
375
622
  function getOrCreateOrchestrationSemaphore(cwd, runId) {
376
623
  const limit = getRunParallelLimit(cwd, runId);
@@ -587,13 +834,13 @@ export function createTaskTool(cwd, runner, options) {
587
834
  label: "task",
588
835
  description: "Launch a specialized subagent to handle a subtask in isolation. " +
589
836
  "Use for: codebase exploration (profile=explore), architectural planning (profile=plan), " +
590
- "IOSM artifact analysis (profile=iosm_analyst/iosm_verifier/cycle_planner), or end-to-end implementation (profile=full). " +
837
+ "IOSM artifact analysis (profile=iosm_analyst/iosm_verifier/cycle_planner), orchestration-first execution (profile=meta), or end-to-end implementation (profile=full). " +
591
838
  "Set cwd to isolate subagents into different project areas when orchestrating parallel work. " +
592
839
  "The subagent runs to completion and returns its full text output. " +
593
840
  "It may request bounded follow-up delegation via <delegate_task> blocks that are executed by the parent task tool." +
594
841
  customAgentsSnippet,
595
842
  parameters: taskSchema,
596
- execute: async (_toolCallId, { description, prompt, agent: agentName, profile, cwd: targetCwd, lock_key: lockKey, run_id: orchestrationRunId, task_id: orchestrationTaskId, model: requestedModel, background, isolation, delegate_parallel_hint: delegateParallelHint, }, _signal, onUpdate) => {
843
+ execute: async (_toolCallId, { description: rawDescription, task: rawTask, args: rawArgs, prompt: rawPrompt, agent: agentName, profile, cwd: targetCwd, lock_key: lockKey, run_id: orchestrationRunId, task_id: orchestrationTaskId, model: requestedModel, background, isolation, delegate_parallel_hint: delegateParallelHint, }, _signal, onUpdate) => {
597
844
  const updateTrackedTaskStatus = (status) => {
598
845
  if (!orchestrationRunId || !orchestrationTaskId)
599
846
  return;
@@ -610,6 +857,12 @@ export function createTaskTool(cwd, runner, options) {
610
857
  throw new Error("Operation aborted");
611
858
  }
612
859
  };
860
+ const { description, prompt } = normalizeTaskPayload({
861
+ description: rawDescription,
862
+ task: rawTask,
863
+ args: rawArgs,
864
+ prompt: rawPrompt,
865
+ });
613
866
  const runId = `subagent_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
614
867
  const sharedMemoryRunId = orchestrationRunId?.trim() || runId;
615
868
  const sharedMemoryTaskId = orchestrationTaskId?.trim() || runId;
@@ -617,13 +870,26 @@ export function createTaskTool(cwd, runner, options) {
617
870
  const resolveCustom = (name) => {
618
871
  if (!name || !options?.resolveCustomSubagent)
619
872
  return undefined;
620
- return options.resolveCustomSubagent(name);
873
+ const trimmed = name.trim();
874
+ if (!trimmed)
875
+ return undefined;
876
+ const resolved = options.resolveCustomSubagent(trimmed);
877
+ if (resolved)
878
+ return resolved;
879
+ const lowered = trimmed.toLowerCase();
880
+ if (lowered !== trimmed) {
881
+ return options.resolveCustomSubagent(lowered);
882
+ }
883
+ return undefined;
621
884
  };
622
885
  let normalizedAgentName = agentName?.trim() || undefined;
623
- let normalizedProfile = profile.trim().toLowerCase();
624
- if (!normalizedProfile)
625
- normalizedProfile = "full";
626
886
  let customSubagent = resolveCustom(normalizedAgentName);
887
+ const requestedProfileRaw = profile?.trim() || undefined;
888
+ const normalizedHostProfile = options?.getHostProfileName?.()?.trim().toLowerCase() ?? options?.hostProfileName?.trim().toLowerCase();
889
+ const hostProfileFallback = normalizedHostProfile && isValidProfileName(normalizedHostProfile) ? normalizedHostProfile : "full";
890
+ let normalizedProfile = requestedProfileRaw?.toLowerCase() ||
891
+ customSubagent?.profile?.trim().toLowerCase() ||
892
+ hostProfileFallback;
627
893
  if (normalizedAgentName && !customSubagent) {
628
894
  const available = availableCustomNames.length > 0 ? ` Available custom agents: ${availableCustomNames.join(", ")}.` : "";
629
895
  throw new Error(`Unknown subagent: ${normalizedAgentName}.${available}`);
@@ -637,37 +903,61 @@ export function createTaskTool(cwd, runner, options) {
637
903
  normalizedProfile = (profileAsAgent.profile ?? "full").trim().toLowerCase();
638
904
  }
639
905
  }
640
- if (!toolsByProfile[normalizedProfile]) {
641
- normalizedProfile = "full";
906
+ if (!customSubagent && !isValidProfileName(normalizedProfile)) {
907
+ throw new Error(`Unknown profile "${requestedProfileRaw ?? normalizedProfile}". Valid profiles: ${Object.keys(toolsByProfile).join(", ")}.`);
908
+ }
909
+ const effectiveProfileCandidate = (customSubagent?.profile ?? normalizedProfile).trim().toLowerCase();
910
+ if (!isValidProfileName(effectiveProfileCandidate)) {
911
+ throw new Error(`Invalid resolved profile "${effectiveProfileCandidate}". Valid profiles: ${Object.keys(toolsByProfile).join(", ")}.`);
642
912
  }
643
- const effectiveProfile = customSubagent?.profile ?? normalizedProfile;
913
+ const effectiveProfile = effectiveProfileCandidate;
644
914
  let tools = customSubagent?.tools
645
915
  ? [...customSubagent.tools]
646
- : [...(toolsByProfile[effectiveProfile] ?? toolsByProfile.explore)];
916
+ : [...toolsByProfile[effectiveProfile]];
647
917
  if (customSubagent?.disallowedTools?.length) {
648
918
  const blocked = new Set(customSubagent.disallowedTools);
649
919
  tools = tools.filter((tool) => !blocked.has(tool));
650
920
  }
921
+ if (isReadOnlyProfileName(normalizedHostProfile) && tools.some((tool) => writeCapableTools.has(tool))) {
922
+ throw new Error(`Host profile "${normalizedHostProfile}" is read-only. Switch to full/meta/iosm to launch write-capable subtasks.`);
923
+ }
651
924
  const delegationDepth = maxDelegationDepthFromEnv;
652
925
  const requestedDelegateParallelHint = typeof delegateParallelHint === "number" && Number.isInteger(delegateParallelHint)
653
- ? Math.max(1, Math.min(10, delegateParallelHint))
926
+ ? Math.max(1, Math.min(MAX_SUBAGENT_DELEGATE_PARALLEL, delegateParallelHint))
654
927
  : undefined;
655
928
  const autoDelegateParallelHint = requestedDelegateParallelHint === undefined
656
- ? deriveAutoDelegateParallelHint(normalizedAgentName, description, prompt)
929
+ ? deriveAutoDelegateParallelHint(effectiveProfile, normalizedAgentName, normalizedHostProfile, description, prompt)
657
930
  : undefined;
658
- const effectiveDelegateParallelHint = requestedDelegateParallelHint ?? autoDelegateParallelHint;
659
- const effectiveMaxDelegations = Math.max(0, Math.min(maxDelegationsPerTaskFromEnv, effectiveDelegateParallelHint ?? maxDelegationsPerTaskFromEnv));
660
- const effectiveMaxDelegateParallel = Math.max(1, Math.min(maxDelegatedParallelFromEnv, effectiveDelegateParallelHint ?? maxDelegatedParallelFromEnv));
931
+ let effectiveDelegateParallelHint = requestedDelegateParallelHint ?? autoDelegateParallelHint;
932
+ const effectiveDelegationDepth = effectiveProfile === "meta" || normalizedHostProfile === "meta" || normalizedAgentName?.toLowerCase().includes("orchestrator")
933
+ ? Math.max(delegationDepth, 2)
934
+ : delegationDepth;
935
+ const orchestratedRunContext = !!(orchestrationRunId && orchestrationTaskId);
936
+ const strictDelegationContract = effectiveProfile === "meta" ||
937
+ normalizedHostProfile === "meta" ||
938
+ normalizedAgentName?.toLowerCase().includes("orchestrator") ||
939
+ (orchestratedRunContext && (effectiveDelegateParallelHint ?? 0) >= 2);
940
+ let effectiveMaxDelegations = Math.max(0, Math.min(maxDelegationsPerTaskFromEnv, effectiveDelegateParallelHint ?? maxDelegationsPerTaskFromEnv));
941
+ let effectiveMaxDelegateParallel = Math.max(1, Math.min(maxDelegatedParallelFromEnv, effectiveDelegateParallelHint ?? maxDelegatedParallelFromEnv));
942
+ const isMetaDelegationContext = effectiveProfile === "meta" || normalizedHostProfile === "meta";
943
+ const preferredDelegationFloorBase = isMetaDelegationContext ? 3 : 2;
944
+ const preferredDelegationFloorMin = isMetaDelegationContext ? 2 : 1;
945
+ const metaDelegationCapacityFloor = 3;
946
+ const preferredDelegationFloor = Math.max(preferredDelegationFloorMin, Math.min(preferredDelegationFloorBase, effectiveDelegateParallelHint ?? preferredDelegationFloorBase));
947
+ const applyMetaDelegationFloor = requestedDelegateParallelHint === undefined &&
948
+ (effectiveProfile === "meta" || normalizedHostProfile === "meta");
949
+ if (applyMetaDelegationFloor) {
950
+ effectiveMaxDelegations = Math.max(effectiveMaxDelegations, Math.min(maxDelegationsPerTaskFromEnv, metaDelegationCapacityFloor));
951
+ effectiveMaxDelegateParallel = Math.max(effectiveMaxDelegateParallel, Math.min(maxDelegatedParallelFromEnv, preferredDelegationFloor));
952
+ }
661
953
  const minDelegationsPreferred = (effectiveDelegateParallelHint ?? 0) >= 2 && effectiveMaxDelegations >= 2
662
- ? Math.min(2, effectiveMaxDelegations)
954
+ ? Math.min(preferredDelegationFloor, effectiveMaxDelegations, effectiveDelegateParallelHint ?? preferredDelegationFloor)
663
955
  : 0;
664
- const baseSystemPrompt = customSubagent?.systemPrompt ??
956
+ const baseSystemPrompt = withSubagentInstructions(customSubagent?.systemPrompt ??
665
957
  systemPromptByProfile[effectiveProfile] ??
666
- systemPromptByProfile.full;
667
- const systemPrompt = withDelegationPrompt(baseSystemPrompt, delegationDepth, effectiveMaxDelegations);
668
- const promptWithInstructions = customSubagent?.instructions && customSubagent.instructions.trim().length > 0
669
- ? `${customSubagent.instructions.trim()}\n\nUser task:\n${prompt}`
670
- : prompt;
958
+ systemPromptByProfile.full, customSubagent?.instructions);
959
+ const systemPrompt = withDelegationPrompt(baseSystemPrompt, effectiveDelegationDepth, effectiveMaxDelegations, minDelegationsPreferred);
960
+ const promptWithInstructions = prompt;
671
961
  const effectiveModelOverride = requestedModel?.trim() || customSubagent?.model?.trim() || undefined;
672
962
  const requestedBackground = background === true || customSubagent?.background === true;
673
963
  const trackedOrchestrationRun = orchestrationRunId && orchestrationTaskId ? getTeamRun(cwd, orchestrationRunId) : undefined;
@@ -680,8 +970,10 @@ export function createTaskTool(cwd, runner, options) {
680
970
  if (!existsSync(requestedSubagentCwd) || !statSync(requestedSubagentCwd).isDirectory()) {
681
971
  throw new Error(`Subagent cwd does not exist or is not a directory: ${requestedSubagentCwd}`);
682
972
  }
683
- if (runInBackground && writeCapableProfiles.has(effectiveProfile)) {
684
- throw new Error(`Background policy violation: profile "${effectiveProfile}" is write-capable. Use explore/plan/iosm_analyst for background mode.`);
973
+ if (runInBackground && !isBackgroundSafeToolset(tools)) {
974
+ throw new Error(`Background policy violation: profile "${effectiveProfile}" has mutable tools (${tools
975
+ .filter((toolName) => backgroundUnsafeTools.has(toolName))
976
+ .join(", ")}). Background mode requires read-only toolsets. Safe baseline profiles: ${backgroundSafeProfiles.join(", ")}.`);
685
977
  }
686
978
  const useWorktree = isolation === "worktree";
687
979
  const queuedAt = Date.now();
@@ -737,6 +1029,42 @@ export function createTaskTool(cwd, runner, options) {
737
1029
  let subagentCwd = requestedSubagentCwd;
738
1030
  let worktreePath;
739
1031
  let runStats;
1032
+ const heldWriteLocks = new Map();
1033
+ const acquireLocalWriteLock = async (rawLockKey) => {
1034
+ const trimmed = rawLockKey?.trim();
1035
+ if (!trimmed)
1036
+ return undefined;
1037
+ const normalizedKey = getCwdLockKey(trimmed);
1038
+ const existing = heldWriteLocks.get(normalizedKey);
1039
+ if (existing) {
1040
+ existing.count += 1;
1041
+ return () => {
1042
+ const current = heldWriteLocks.get(normalizedKey);
1043
+ if (!current)
1044
+ return;
1045
+ current.count -= 1;
1046
+ if (current.count <= 0) {
1047
+ heldWriteLocks.delete(normalizedKey);
1048
+ current.release();
1049
+ cleanupWriteLock(trimmed);
1050
+ }
1051
+ };
1052
+ }
1053
+ const lock = getOrCreateWriteLock(trimmed);
1054
+ const release = await lock.acquire();
1055
+ heldWriteLocks.set(normalizedKey, { count: 1, release });
1056
+ return () => {
1057
+ const current = heldWriteLocks.get(normalizedKey);
1058
+ if (!current)
1059
+ return;
1060
+ current.count -= 1;
1061
+ if (current.count <= 0) {
1062
+ heldWriteLocks.delete(normalizedKey);
1063
+ current.release();
1064
+ cleanupWriteLock(trimmed);
1065
+ }
1066
+ };
1067
+ };
740
1068
  try {
741
1069
  throwIfAborted();
742
1070
  if (orchestrationRunId && orchestrationTaskId) {
@@ -798,8 +1126,7 @@ export function createTaskTool(cwd, runner, options) {
798
1126
  // Parallel orchestration should remain truly parallel by default.
799
1127
  // Serialize write-capable agents only when an explicit lock_key is provided.
800
1128
  if (explicitRootLockKey) {
801
- const lock = getOrCreateWriteLock(explicitRootLockKey);
802
- releaseWriteLock = await lock.acquire();
1129
+ releaseWriteLock = await acquireLocalWriteLock(explicitRootLockKey);
803
1130
  }
804
1131
  }
805
1132
  if (useWorktree) {
@@ -850,6 +1177,7 @@ export function createTaskTool(cwd, runner, options) {
850
1177
  try {
851
1178
  const result = await runner({
852
1179
  systemPrompt,
1180
+ profileName: effectiveProfile,
853
1181
  tools,
854
1182
  prompt: promptForAttempt,
855
1183
  cwd: subagentCwd,
@@ -942,7 +1270,7 @@ export function createTaskTool(cwd, runner, options) {
942
1270
  output = firstPass.output;
943
1271
  subagentSessionId = firstPass.sessionId;
944
1272
  runStats = firstPass.stats;
945
- let parsedDelegation = parseDelegationRequests(output, delegationDepth > 0 ? effectiveMaxDelegations : 0);
1273
+ let parsedDelegation = parseDelegationRequests(output, effectiveDelegationDepth > 0 ? effectiveMaxDelegations : 0);
946
1274
  if (minDelegationsPreferred > 0 && parsedDelegation.requests.length < minDelegationsPreferred) {
947
1275
  emitProgress({
948
1276
  kind: "subagent_progress",
@@ -965,10 +1293,35 @@ export function createTaskTool(cwd, runner, options) {
965
1293
  output = secondPass.output;
966
1294
  subagentSessionId = secondPass.sessionId ?? subagentSessionId;
967
1295
  runStats = secondPass.stats ?? runStats;
968
- parsedDelegation = parseDelegationRequests(output, delegationDepth > 0 ? effectiveMaxDelegations : 0);
1296
+ parsedDelegation = parseDelegationRequests(output, effectiveDelegationDepth > 0 ? effectiveMaxDelegations : 0);
1297
+ }
1298
+ if (minDelegationsPreferred > 0 &&
1299
+ parsedDelegation.requests.length === 0 &&
1300
+ strictDelegationContract) {
1301
+ const impossibleMatch = output.match(/^\s*DELEGATION_IMPOSSIBLE\s*:\s*(.+)$/im);
1302
+ if (!impossibleMatch) {
1303
+ const synthesizedRequests = synthesizeDelegationRequests({
1304
+ description,
1305
+ prompt,
1306
+ baseProfile: effectiveProfile,
1307
+ currentDelegates: parsedDelegation.requests.length,
1308
+ minDelegationsPreferred,
1309
+ maxDelegations: effectiveMaxDelegations,
1310
+ availableCustomNames,
1311
+ });
1312
+ if (synthesizedRequests.length > 0) {
1313
+ parsedDelegation.requests.push(...synthesizedRequests);
1314
+ delegationWarnings.push(`Delegation auto-fanout: synthesized ${synthesizedRequests.length} delegate(s) to satisfy parallelism contract.`);
1315
+ }
1316
+ }
969
1317
  }
970
1318
  if (minDelegationsPreferred > 0 && parsedDelegation.requests.length < minDelegationsPreferred) {
971
- const impossibleReason = output.match(/^\s*DELEGATION_IMPOSSIBLE\s*:\s*(.+)$/im)?.[1]?.trim() ?? "not provided";
1319
+ const impossibleMatch = output.match(/^\s*DELEGATION_IMPOSSIBLE\s*:\s*(.+)$/im);
1320
+ const impossibleReason = impossibleMatch?.[1]?.trim() ?? "not provided";
1321
+ if (strictDelegationContract && parsedDelegation.requests.length === 0 && !impossibleMatch) {
1322
+ throw new Error(`Delegation contract violated: expected >=${minDelegationsPreferred} delegates, got ${parsedDelegation.requests.length}. ` +
1323
+ `Provide nested <delegate_task> fan-out or an explicit "DELEGATION_IMPOSSIBLE: <reason>" line.`);
1324
+ }
972
1325
  delegationWarnings.push(`Delegation fallback: kept single-agent execution (preferred >=${minDelegationsPreferred} delegates, got ${parsedDelegation.requests.length}). Reason: ${impossibleReason}.`);
973
1326
  }
974
1327
  output = parsedDelegation.cleanedOutput;
@@ -1037,19 +1390,241 @@ export function createTaskTool(cwd, runner, options) {
1037
1390
  delegateItems,
1038
1391
  });
1039
1392
  };
1393
+ const executeNestedDelegates = async (requests, parentCwd, depthRemaining, lineage) => {
1394
+ if (requests.length === 0 || depthRemaining <= 0) {
1395
+ return { sections: [], warnings: [] };
1396
+ }
1397
+ const nestedWarnings = [];
1398
+ const sectionsByIndex = new Array(requests.length);
1399
+ const statuses = Array.from({ length: requests.length }, () => "pending");
1400
+ const totalNested = requests.length;
1401
+ const normalizedDependsOn = requests.map((request, index) => {
1402
+ const current = index + 1;
1403
+ const raw = request.dependsOn ?? [];
1404
+ const unique = new Set();
1405
+ for (const dep of raw) {
1406
+ if (!Number.isInteger(dep) || dep <= 0 || dep > totalNested || dep === current) {
1407
+ nestedWarnings.push(`Nested delegated task ${lineage}${current} has invalid depends_on reference "${dep}" and it was ignored.`);
1408
+ continue;
1409
+ }
1410
+ unique.add(dep);
1411
+ }
1412
+ return Array.from(unique).sort((a, b) => a - b);
1413
+ });
1414
+ const statusOf = (index) => statuses[index] ?? "pending";
1415
+ const markNestedFailed = (nestedIndex, requestLabel, nestedRequest, nestedProfileLabel, message, cause) => {
1416
+ statuses[nestedIndex] = "failed";
1417
+ recordFailureCause(cause);
1418
+ delegatedTasks += 1;
1419
+ delegatedFailed += 1;
1420
+ sectionsByIndex[nestedIndex] =
1421
+ `###### ${requestLabel}. ${nestedRequest.description} (${nestedProfileLabel})\nERROR [cause=${cause}]: ${message}`;
1422
+ };
1423
+ const runNestedDelegate = async (nestedIndex) => {
1424
+ const nestedRequest = requests[nestedIndex];
1425
+ const requestLabel = `${lineage}${nestedIndex + 1}`;
1426
+ statuses[nestedIndex] = "running";
1427
+ let requestedNestedAgent = nestedRequest.agent?.trim() || undefined;
1428
+ let nestedCustomSubagent = resolveCustom(requestedNestedAgent);
1429
+ if (!nestedCustomSubagent && !requestedNestedAgent) {
1430
+ const profileAsAgent = resolveCustom(nestedRequest.profile);
1431
+ if (profileAsAgent) {
1432
+ nestedCustomSubagent = profileAsAgent;
1433
+ requestedNestedAgent = profileAsAgent.name;
1434
+ }
1435
+ }
1436
+ if (requestedNestedAgent && !nestedCustomSubagent) {
1437
+ nestedWarnings.push(`Nested delegated task "${nestedRequest.description}" requested unknown agent "${requestedNestedAgent}". Falling back to profile "${nestedRequest.profile}".`);
1438
+ }
1439
+ const nestedProfileCandidate = (nestedCustomSubagent?.profile ?? nestedRequest.profile).trim();
1440
+ const normalizedNestedProfile = nestedProfileCandidate.toLowerCase();
1441
+ if (!isValidProfileName(normalizedNestedProfile)) {
1442
+ markNestedFailed(nestedIndex, requestLabel, nestedRequest, nestedProfileCandidate || "unknown", `nested delegate skipped: unknown profile "${nestedProfileCandidate || nestedRequest.profile}"`, "logic_error");
1443
+ return;
1444
+ }
1445
+ const nestedProfile = normalizedNestedProfile;
1446
+ const nestedProfileLabel = nestedCustomSubagent?.name
1447
+ ? `${nestedCustomSubagent.name}/${nestedProfile}`
1448
+ : nestedProfile;
1449
+ let nestedTools = nestedCustomSubagent?.tools
1450
+ ? [...nestedCustomSubagent.tools]
1451
+ : [...toolsByProfile[nestedProfile]];
1452
+ if (nestedCustomSubagent?.disallowedTools?.length) {
1453
+ const blocked = new Set(nestedCustomSubagent.disallowedTools);
1454
+ nestedTools = nestedTools.filter((tool) => !blocked.has(tool));
1455
+ }
1456
+ const nestedBaseSystemPrompt = withSubagentInstructions(nestedCustomSubagent?.systemPrompt ??
1457
+ systemPromptByProfile[nestedProfile] ??
1458
+ systemPromptByProfile.full, nestedCustomSubagent?.instructions);
1459
+ const nestedSystemPrompt = withDelegationPrompt(nestedBaseSystemPrompt, Math.max(0, depthRemaining - 1), effectiveMaxDelegations);
1460
+ const requestedNestedCwd = nestedRequest.cwd
1461
+ ? path.resolve(parentCwd, nestedRequest.cwd)
1462
+ : nestedCustomSubagent?.cwd ?? parentCwd;
1463
+ if (!existsSync(requestedNestedCwd) || !statSync(requestedNestedCwd).isDirectory()) {
1464
+ markNestedFailed(nestedIndex, requestLabel, nestedRequest, nestedProfileLabel, "nested delegate skipped: missing cwd", "dependency_env");
1465
+ return;
1466
+ }
1467
+ let nestedReleaseLock;
1468
+ let nestedReleaseIsolation;
1469
+ let nestedCwd = requestedNestedCwd;
1470
+ try {
1471
+ if (writeCapableProfiles.has(nestedProfile) && nestedRequest.lockKey?.trim()) {
1472
+ nestedReleaseLock = await acquireLocalWriteLock(nestedRequest.lockKey.trim());
1473
+ }
1474
+ if (nestedRequest.isolation === "worktree") {
1475
+ const isolated = provisionWorktree(cwd, requestedNestedCwd, `${runId}_nested_${requestLabel.replace(/\./g, "_")}`);
1476
+ nestedCwd = isolated.runCwd;
1477
+ nestedReleaseIsolation = isolated.cleanup;
1478
+ }
1479
+ const nestedPromptWithInstructions = nestedRequest.prompt;
1480
+ const nestedSharedMemoryGuidance = buildSharedMemoryGuidance(sharedMemoryRunId, sharedMemoryTaskId);
1481
+ const nestedPrompt = `${nestedPromptWithInstructions}\n\n${nestedSharedMemoryGuidance}`;
1482
+ const nestedModelOverride = nestedRequest.model?.trim() || nestedCustomSubagent?.model?.trim() || undefined;
1483
+ const nestedSharedMemoryContext = {
1484
+ rootCwd: cwd,
1485
+ runId: sharedMemoryRunId,
1486
+ taskId: sharedMemoryTaskId,
1487
+ delegateId: requestLabel,
1488
+ profile: nestedProfile,
1489
+ };
1490
+ const nestedResult = await runner({
1491
+ systemPrompt: nestedSystemPrompt,
1492
+ profileName: nestedProfile,
1493
+ tools: nestedTools,
1494
+ prompt: nestedPrompt,
1495
+ cwd: nestedCwd,
1496
+ modelOverride: nestedModelOverride,
1497
+ sharedMemoryContext: nestedSharedMemoryContext,
1498
+ signal: _signal,
1499
+ onProgress: (progress) => {
1500
+ emitProgress({
1501
+ kind: "subagent_progress",
1502
+ phase: "running",
1503
+ message: `delegate ${requestLabel}: ${progress.message}`,
1504
+ cwd: progress.cwd ?? nestedCwd,
1505
+ activeTool: progress.activeTool,
1506
+ });
1507
+ },
1508
+ });
1509
+ throwIfAborted();
1510
+ let nestedOutput = typeof nestedResult === "string" ? nestedResult : nestedResult.output;
1511
+ const nestedStats = typeof nestedResult === "string" ? undefined : nestedResult.stats;
1512
+ delegatedTasks += 1;
1513
+ delegatedSucceeded += 1;
1514
+ statuses[nestedIndex] = "done";
1515
+ delegatedStats.toolCallsStarted += nestedStats?.toolCallsStarted ?? 0;
1516
+ delegatedStats.toolCallsCompleted += nestedStats?.toolCallsCompleted ?? 0;
1517
+ delegatedStats.assistantMessages += nestedStats?.assistantMessages ?? 0;
1518
+ const parsedNestedDelegation = parseDelegationRequests(nestedOutput, depthRemaining > 1 ? effectiveMaxDelegations : 0);
1519
+ nestedOutput = parsedNestedDelegation.cleanedOutput;
1520
+ nestedWarnings.push(...parsedNestedDelegation.warnings.map((warning) => `Nested child ${requestLabel}: ${warning}`));
1521
+ let nestedSection = `###### ${requestLabel}. ${nestedRequest.description} (${nestedProfileLabel})\n${nestedOutput.trim() || "(no output)"}`;
1522
+ if (parsedNestedDelegation.requests.length > 0 && depthRemaining > 1) {
1523
+ const deeper = await executeNestedDelegates(parsedNestedDelegation.requests, nestedCwd, depthRemaining - 1, `${requestLabel}.`);
1524
+ nestedWarnings.push(...deeper.warnings);
1525
+ if (deeper.sections.length > 0) {
1526
+ nestedSection = `${nestedSection}\n\n##### Nested Delegated Subtasks\n\n${deeper.sections.join("\n\n")}`;
1527
+ }
1528
+ }
1529
+ sectionsByIndex[nestedIndex] = nestedSection;
1530
+ }
1531
+ catch (error) {
1532
+ const message = error instanceof Error ? error.message : String(error);
1533
+ const cause = classifyFailureCause(message);
1534
+ markNestedFailed(nestedIndex, requestLabel, nestedRequest, nestedProfileLabel, message, cause);
1535
+ }
1536
+ finally {
1537
+ nestedReleaseIsolation?.();
1538
+ nestedReleaseLock?.();
1539
+ }
1540
+ };
1541
+ const pendingIndices = new Set(Array.from({ length: totalNested }, (_v, i) => i));
1542
+ const runningNested = new Map();
1543
+ const maxNestedParallel = Math.max(1, Math.min(totalNested, effectiveMaxDelegateParallel));
1544
+ const resolveBlockedByFailedDependencies = () => {
1545
+ let changed = false;
1546
+ for (const nestedIndex of Array.from(pendingIndices)) {
1547
+ const deps = normalizedDependsOn[nestedIndex] ?? [];
1548
+ if (deps.length === 0)
1549
+ continue;
1550
+ const failedDep = deps.find((dep) => statusOf(dep - 1) === "failed");
1551
+ if (!failedDep)
1552
+ continue;
1553
+ pendingIndices.delete(nestedIndex);
1554
+ const nestedRequest = requests[nestedIndex];
1555
+ const requestLabel = `${lineage}${nestedIndex + 1}`;
1556
+ markNestedFailed(nestedIndex, requestLabel, nestedRequest, nestedRequest.profile, `nested delegate skipped: dependency ${lineage}${failedDep} failed`, "logic_error");
1557
+ changed = true;
1558
+ }
1559
+ return changed;
1560
+ };
1561
+ const launchReadyNested = () => {
1562
+ let launched = false;
1563
+ while (runningNested.size < maxNestedParallel) {
1564
+ let nextIndex;
1565
+ for (const nestedIndex of pendingIndices) {
1566
+ const deps = normalizedDependsOn[nestedIndex] ?? [];
1567
+ const allDone = deps.every((dep) => statusOf(dep - 1) === "done");
1568
+ if (allDone) {
1569
+ nextIndex = nestedIndex;
1570
+ break;
1571
+ }
1572
+ }
1573
+ if (nextIndex === undefined)
1574
+ break;
1575
+ pendingIndices.delete(nextIndex);
1576
+ const promise = runNestedDelegate(nextIndex).finally(() => {
1577
+ runningNested.delete(nextIndex);
1578
+ });
1579
+ runningNested.set(nextIndex, promise);
1580
+ launched = true;
1581
+ }
1582
+ return launched;
1583
+ };
1584
+ while (pendingIndices.size > 0 || runningNested.size > 0) {
1585
+ throwIfAborted();
1586
+ const changed = resolveBlockedByFailedDependencies();
1587
+ const launched = launchReadyNested();
1588
+ if (runningNested.size === 0) {
1589
+ if (pendingIndices.size > 0 && !changed && !launched) {
1590
+ for (const nestedIndex of Array.from(pendingIndices)) {
1591
+ pendingIndices.delete(nestedIndex);
1592
+ const nestedRequest = requests[nestedIndex];
1593
+ const deps = normalizedDependsOn[nestedIndex] ?? [];
1594
+ const requestLabel = `${lineage}${nestedIndex + 1}`;
1595
+ markNestedFailed(nestedIndex, requestLabel, nestedRequest, nestedRequest.profile, `nested delegate blocked: unresolved depends_on (${deps.join(", ") || "unknown"})`, "logic_error");
1596
+ }
1597
+ }
1598
+ break;
1599
+ }
1600
+ await Promise.race(Array.from(runningNested.values()));
1601
+ }
1602
+ const sections = sectionsByIndex.filter((section) => typeof section === "string" && section.trim().length > 0);
1603
+ return { sections, warnings: nestedWarnings };
1604
+ };
1040
1605
  const runDelegate = async (index) => {
1041
1606
  throwIfAborted();
1042
1607
  const request = parsedDelegation.requests[index];
1043
- const requestedChildAgent = request.agent?.trim() || undefined;
1044
- const childCustomSubagent = resolveCustom(requestedChildAgent);
1608
+ let requestedChildAgent = request.agent?.trim() || undefined;
1609
+ let childCustomSubagent = resolveCustom(requestedChildAgent);
1610
+ if (!childCustomSubagent && !requestedChildAgent) {
1611
+ const profileAsAgent = resolveCustom(request.profile);
1612
+ if (profileAsAgent) {
1613
+ childCustomSubagent = profileAsAgent;
1614
+ requestedChildAgent = profileAsAgent.name;
1615
+ }
1616
+ }
1045
1617
  if (requestedChildAgent && !childCustomSubagent) {
1046
1618
  delegationWarnings.push(`Delegated task "${request.description}" requested unknown agent "${requestedChildAgent}". Falling back to profile "${request.profile}".`);
1047
1619
  }
1048
- const childProfileRaw = childCustomSubagent?.profile ?? request.profile;
1049
- const normalizedChildProfile = childProfileRaw.trim().toLowerCase();
1050
- const childProfile = normalizedChildProfile && toolsByProfile[normalizedChildProfile]
1051
- ? normalizedChildProfile
1052
- : "full";
1620
+ const childProfileRaw = (childCustomSubagent?.profile ?? request.profile).trim();
1621
+ const normalizedChildProfile = childProfileRaw.toLowerCase();
1622
+ if (!isValidProfileName(normalizedChildProfile)) {
1623
+ recordFailureCause("logic_error");
1624
+ markDelegateFailed(index, `delegate ${index + 1}/${delegateTotal} skipped: unknown profile "${childProfileRaw || request.profile}"`, `Delegated task "${request.description}" requested unknown profile "${childProfileRaw || request.profile}".`, "logic_error");
1625
+ return;
1626
+ }
1627
+ const childProfile = normalizedChildProfile;
1053
1628
  const childProfileLabel = childCustomSubagent?.name
1054
1629
  ? `${childCustomSubagent.name}/${childProfile}`
1055
1630
  : childProfile;
@@ -1070,15 +1645,20 @@ export function createTaskTool(cwd, runner, options) {
1070
1645
  });
1071
1646
  let childTools = childCustomSubagent?.tools
1072
1647
  ? [...childCustomSubagent.tools]
1073
- : [...(toolsByProfile[childProfile] ?? toolsByProfile.explore)];
1648
+ : [...toolsByProfile[childProfile]];
1074
1649
  if (childCustomSubagent?.disallowedTools?.length) {
1075
1650
  const blocked = new Set(childCustomSubagent.disallowedTools);
1076
1651
  childTools = childTools.filter((tool) => !blocked.has(tool));
1077
1652
  }
1078
- const childBaseSystemPrompt = childCustomSubagent?.systemPrompt ??
1653
+ const childBaseSystemPrompt = withSubagentInstructions(childCustomSubagent?.systemPrompt ??
1079
1654
  systemPromptByProfile[childProfile] ??
1080
- systemPromptByProfile.full;
1081
- const childSystemPrompt = withDelegationPrompt(childBaseSystemPrompt, Math.max(0, delegationDepth - 1), effectiveMaxDelegations);
1655
+ systemPromptByProfile.full, childCustomSubagent?.instructions);
1656
+ const childAutoDelegateParallelHint = deriveAutoDelegateParallelHint(childProfile, requestedChildAgent, normalizedHostProfile, request.description, request.prompt);
1657
+ const childMinDelegationsPreferred = Math.max(0, effectiveDelegationDepth - 1) > 0 &&
1658
+ (childAutoDelegateParallelHint ?? 0) >= 2
1659
+ ? Math.min(preferredDelegationFloor, effectiveMaxDelegations, childAutoDelegateParallelHint ?? preferredDelegationFloor)
1660
+ : 0;
1661
+ const childSystemPrompt = withDelegationPrompt(childBaseSystemPrompt, Math.max(0, effectiveDelegationDepth - 1), effectiveMaxDelegations, childMinDelegationsPreferred);
1082
1662
  const requestedChildCwd = request.cwd
1083
1663
  ? path.resolve(subagentCwd, request.cwd)
1084
1664
  : childCustomSubagent?.cwd ?? subagentCwd;
@@ -1094,8 +1674,7 @@ export function createTaskTool(cwd, runner, options) {
1094
1674
  try {
1095
1675
  throwIfAborted();
1096
1676
  if (writeCapableProfiles.has(childProfile) && explicitChildLock) {
1097
- const lock = getOrCreateWriteLock(explicitChildLock);
1098
- childReleaseLock = await lock.acquire();
1677
+ childReleaseLock = await acquireLocalWriteLock(explicitChildLock);
1099
1678
  throwIfAborted();
1100
1679
  }
1101
1680
  if (request.isolation === "worktree") {
@@ -1104,9 +1683,7 @@ export function createTaskTool(cwd, runner, options) {
1104
1683
  childReleaseIsolation = isolated.cleanup;
1105
1684
  }
1106
1685
  const delegateMeta = formatMetaCheckpoint(options?.getMetaMessages?.());
1107
- const childPromptWithInstructions = childCustomSubagent?.instructions && childCustomSubagent.instructions.trim().length > 0
1108
- ? `${childCustomSubagent.instructions.trim()}\n\nUser task:\n${request.prompt}`
1109
- : request.prompt;
1686
+ const childPromptWithInstructions = request.prompt;
1110
1687
  const delegateSharedMemoryGuidance = buildSharedMemoryGuidance(sharedMemoryRunId, sharedMemoryTaskId);
1111
1688
  const delegatePromptBase = `${childPromptWithInstructions}\n\n${delegateSharedMemoryGuidance}`;
1112
1689
  const delegatePrompt = delegateMeta.section && delegateMeta.appliedCount > 0
@@ -1136,108 +1713,179 @@ export function createTaskTool(cwd, runner, options) {
1136
1713
  };
1137
1714
  let childOutput = "";
1138
1715
  let childStats;
1139
- let childEmptyAttempt = 0;
1140
- let childRetrospectiveAttempt = 0;
1141
- let childPromptForAttempt = delegatePrompt;
1142
- while (true) {
1143
- try {
1144
- const childResult = await runner({
1145
- systemPrompt: childSystemPrompt,
1146
- tools: childTools,
1147
- prompt: childPromptForAttempt,
1148
- cwd: childCwd,
1149
- modelOverride: childModelOverride,
1150
- sharedMemoryContext: childSharedMemoryContext,
1151
- signal: _signal,
1152
- onProgress: (progress) => {
1153
- emitProgress({
1154
- kind: "subagent_progress",
1155
- phase: "running",
1156
- message: `delegate ${index + 1}/${delegateTotal}: ${progress.message}`,
1157
- cwd: progress.cwd ?? childCwd,
1158
- activeTool: progress.activeTool,
1159
- delegateIndex: index + 1,
1160
- delegateTotal,
1161
- delegateDescription: request.description,
1162
- delegateProfile: childProfileLabel,
1163
- delegateItems,
1164
- });
1165
- },
1166
- });
1167
- throwIfAborted();
1168
- let attemptOutput;
1169
- let attemptStats;
1170
- if (typeof childResult === "string") {
1171
- attemptOutput = childResult;
1172
- }
1173
- else {
1174
- attemptOutput = childResult.output;
1175
- attemptStats = childResult.stats;
1176
- }
1177
- childStats = mergeRunStats(childStats, attemptStats);
1178
- if (attemptOutput.trim().length > 0) {
1179
- childOutput = attemptOutput;
1180
- if (childRetrospectiveAttempt > 0) {
1181
- retrospectiveRecovered += 1;
1716
+ const runChildPass = async (runPrompt) => {
1717
+ let childEmptyAttempt = 0;
1718
+ let childRetrospectiveAttempt = 0;
1719
+ let childPromptForAttempt = runPrompt;
1720
+ while (true) {
1721
+ try {
1722
+ const childResult = await runner({
1723
+ systemPrompt: childSystemPrompt,
1724
+ profileName: childProfile,
1725
+ tools: childTools,
1726
+ prompt: childPromptForAttempt,
1727
+ cwd: childCwd,
1728
+ modelOverride: childModelOverride,
1729
+ sharedMemoryContext: childSharedMemoryContext,
1730
+ signal: _signal,
1731
+ onProgress: (progress) => {
1732
+ emitProgress({
1733
+ kind: "subagent_progress",
1734
+ phase: "running",
1735
+ message: `delegate ${index + 1}/${delegateTotal}: ${progress.message}`,
1736
+ cwd: progress.cwd ?? childCwd,
1737
+ activeTool: progress.activeTool,
1738
+ delegateIndex: index + 1,
1739
+ delegateTotal,
1740
+ delegateDescription: request.description,
1741
+ delegateProfile: childProfileLabel,
1742
+ delegateItems,
1743
+ });
1744
+ },
1745
+ });
1746
+ throwIfAborted();
1747
+ let attemptOutput;
1748
+ let attemptStats;
1749
+ if (typeof childResult === "string") {
1750
+ attemptOutput = childResult;
1182
1751
  }
1183
- break;
1752
+ else {
1753
+ attemptOutput = childResult.output;
1754
+ attemptStats = childResult.stats;
1755
+ }
1756
+ childStats = mergeRunStats(childStats, attemptStats);
1757
+ if (attemptOutput.trim().length > 0) {
1758
+ if (childRetrospectiveAttempt > 0) {
1759
+ retrospectiveRecovered += 1;
1760
+ }
1761
+ return attemptOutput;
1762
+ }
1763
+ if (childEmptyAttempt >= emptyOutputRetriesFromEnv) {
1764
+ const totalAttempts = childEmptyAttempt + 1;
1765
+ throw new Error(`delegate ${index + 1}/${delegateTotal} returned empty output after ${totalAttempts} attempt${totalAttempts === 1 ? "" : "s"}.`);
1766
+ }
1767
+ childEmptyAttempt += 1;
1768
+ emitProgress({
1769
+ kind: "subagent_progress",
1770
+ phase: "running",
1771
+ message: `delegate ${index + 1}/${delegateTotal}: empty output, retry ${childEmptyAttempt}/${emptyOutputRetriesFromEnv}`,
1772
+ cwd: childCwd,
1773
+ activeTool: undefined,
1774
+ delegateIndex: index + 1,
1775
+ delegateTotal,
1776
+ delegateDescription: request.description,
1777
+ delegateProfile: childProfileLabel,
1778
+ delegateItems,
1779
+ });
1184
1780
  }
1185
- if (childEmptyAttempt >= emptyOutputRetriesFromEnv) {
1186
- const totalAttempts = childEmptyAttempt + 1;
1187
- throw new Error(`delegate ${index + 1}/${delegateTotal} returned empty output after ${totalAttempts} attempt${totalAttempts === 1 ? "" : "s"}.`);
1781
+ catch (error) {
1782
+ if (_signal?.aborted || isAbortError(error)) {
1783
+ throw new Error("Operation aborted");
1784
+ }
1785
+ const message = error instanceof Error ? error.message : String(error);
1786
+ const cause = classifyFailureCause(message);
1787
+ recordFailureCause(cause);
1788
+ const canRetryRetrospective = childRetrospectiveAttempt < retrospectiveRetriesFromEnv &&
1789
+ isRetrospectiveRetryable(cause);
1790
+ if (!canRetryRetrospective) {
1791
+ throw Object.assign(new Error(message), { failureCause: cause });
1792
+ }
1793
+ childRetrospectiveAttempt += 1;
1794
+ retrospectiveAttempts += 1;
1795
+ const directive = buildRetrospectiveDirective({
1796
+ cause,
1797
+ errorMessage: message,
1798
+ attempt: childRetrospectiveAttempt,
1799
+ target: "delegate",
1800
+ });
1801
+ childPromptForAttempt = `${runPrompt}\n\n${directive}`;
1802
+ emitProgress({
1803
+ kind: "subagent_progress",
1804
+ phase: "running",
1805
+ message: `delegate ${index + 1}/${delegateTotal}: retrospective retry ${childRetrospectiveAttempt}/${retrospectiveRetriesFromEnv} (${cause})`,
1806
+ cwd: childCwd,
1807
+ activeTool: undefined,
1808
+ delegateIndex: index + 1,
1809
+ delegateTotal,
1810
+ delegateDescription: request.description,
1811
+ delegateProfile: childProfileLabel,
1812
+ delegateItems,
1813
+ });
1188
1814
  }
1189
- childEmptyAttempt += 1;
1190
- emitProgress({
1191
- kind: "subagent_progress",
1192
- phase: "running",
1193
- message: `delegate ${index + 1}/${delegateTotal}: empty output, retry ${childEmptyAttempt}/${emptyOutputRetriesFromEnv}`,
1194
- cwd: childCwd,
1195
- activeTool: undefined,
1196
- delegateIndex: index + 1,
1197
- delegateTotal,
1198
- delegateDescription: request.description,
1199
- delegateProfile: childProfileLabel,
1200
- delegateItems,
1201
- });
1202
1815
  }
1203
- catch (error) {
1204
- if (_signal?.aborted || isAbortError(error)) {
1205
- throw new Error("Operation aborted");
1206
- }
1207
- const message = error instanceof Error ? error.message : String(error);
1208
- const cause = classifyFailureCause(message);
1209
- recordFailureCause(cause);
1210
- const canRetryRetrospective = childRetrospectiveAttempt < retrospectiveRetriesFromEnv &&
1211
- isRetrospectiveRetryable(cause);
1212
- if (!canRetryRetrospective) {
1213
- throw Object.assign(new Error(message), { failureCause: cause });
1214
- }
1215
- childRetrospectiveAttempt += 1;
1216
- retrospectiveAttempts += 1;
1217
- const directive = buildRetrospectiveDirective({
1218
- cause,
1219
- errorMessage: message,
1220
- attempt: childRetrospectiveAttempt,
1221
- target: "delegate",
1222
- });
1223
- childPromptForAttempt = `${delegatePrompt}\n\n${directive}`;
1224
- emitProgress({
1225
- kind: "subagent_progress",
1226
- phase: "running",
1227
- message: `delegate ${index + 1}/${delegateTotal}: retrospective retry ${childRetrospectiveAttempt}/${retrospectiveRetriesFromEnv} (${cause})`,
1228
- cwd: childCwd,
1229
- activeTool: undefined,
1230
- delegateIndex: index + 1,
1231
- delegateTotal,
1232
- delegateDescription: request.description,
1233
- delegateProfile: childProfileLabel,
1234
- delegateItems,
1816
+ };
1817
+ childOutput = await runChildPass(delegatePrompt);
1818
+ let parsedChildDelegation = parseDelegationRequests(childOutput, effectiveDelegationDepth > 1 ? effectiveMaxDelegations : 0);
1819
+ if (childMinDelegationsPreferred > 0 &&
1820
+ parsedChildDelegation.requests.length < childMinDelegationsPreferred) {
1821
+ emitProgress({
1822
+ kind: "subagent_progress",
1823
+ phase: "running",
1824
+ message: `delegate ${index + 1}/${delegateTotal}: nested delegation preference unmet (${parsedChildDelegation.requests.length}/${childMinDelegationsPreferred}), retrying with stronger split guidance`,
1825
+ cwd: childCwd,
1826
+ activeTool: undefined,
1827
+ delegateIndex: index + 1,
1828
+ delegateTotal,
1829
+ delegateDescription: request.description,
1830
+ delegateProfile: childProfileLabel,
1831
+ delegateItems,
1832
+ });
1833
+ const enforcedChildPrompt = [
1834
+ delegatePrompt,
1835
+ "",
1836
+ "[DELEGATION_ENFORCEMENT]",
1837
+ `This delegated workstream must emit at least ${childMinDelegationsPreferred} <delegate_task> blocks for independent sub-work when beneficial.`,
1838
+ `Target parallel fan-out: up to ${Math.min(effectiveMaxDelegateParallel, effectiveMaxDelegations)}.`,
1839
+ "For broad audits or implementations, split by subsystem / file cluster / verification stream instead of doing everything in one pass.",
1840
+ "If safe decomposition is impossible, output exactly one line:",
1841
+ "DELEGATION_IMPOSSIBLE: <reason>",
1842
+ "[/DELEGATION_ENFORCEMENT]",
1843
+ ].join("\n");
1844
+ childOutput = await runChildPass(enforcedChildPrompt);
1845
+ parsedChildDelegation = parseDelegationRequests(childOutput, effectiveDelegationDepth > 1 ? effectiveMaxDelegations : 0);
1846
+ }
1847
+ if (childMinDelegationsPreferred > 0 &&
1848
+ parsedChildDelegation.requests.length === 0 &&
1849
+ strictDelegationContract) {
1850
+ const impossibleMatch = childOutput.match(/^\s*DELEGATION_IMPOSSIBLE\s*:\s*(.+)$/im);
1851
+ if (!impossibleMatch) {
1852
+ const synthesizedChildRequests = synthesizeDelegationRequests({
1853
+ description: request.description,
1854
+ prompt: request.prompt,
1855
+ baseProfile: childProfile,
1856
+ currentDelegates: parsedChildDelegation.requests.length,
1857
+ minDelegationsPreferred: childMinDelegationsPreferred,
1858
+ maxDelegations: effectiveMaxDelegations,
1859
+ availableCustomNames,
1235
1860
  });
1861
+ if (synthesizedChildRequests.length > 0) {
1862
+ parsedChildDelegation.requests.push(...synthesizedChildRequests);
1863
+ delegationWarnings.push(`Child ${index + 1}: delegation auto-fanout synthesized ${synthesizedChildRequests.length} nested delegate(s).`);
1864
+ }
1236
1865
  }
1237
1866
  }
1238
- const parsedChildDelegation = parseDelegationRequests(childOutput, 0);
1867
+ if (childMinDelegationsPreferred > 0 &&
1868
+ parsedChildDelegation.requests.length < childMinDelegationsPreferred) {
1869
+ const impossibleMatch = childOutput.match(/^\s*DELEGATION_IMPOSSIBLE\s*:\s*(.+)$/im);
1870
+ const impossibleReason = impossibleMatch?.[1]?.trim() ?? "not provided";
1871
+ if (strictDelegationContract &&
1872
+ parsedChildDelegation.requests.length === 0 &&
1873
+ !impossibleMatch) {
1874
+ throw new Error(`Delegation contract violated for child ${index + 1}: expected >=${childMinDelegationsPreferred} nested delegates, got ${parsedChildDelegation.requests.length}. ` +
1875
+ `Provide nested <delegate_task> fan-out or "DELEGATION_IMPOSSIBLE: <reason>".`);
1876
+ }
1877
+ delegationWarnings.push(`Child ${index + 1}: delegation fallback (preferred >=${childMinDelegationsPreferred}, got ${parsedChildDelegation.requests.length}). Reason: ${impossibleReason}.`);
1878
+ }
1239
1879
  childOutput = parsedChildDelegation.cleanedOutput;
1240
1880
  delegationWarnings.push(...parsedChildDelegation.warnings.map((warning) => `Child ${index + 1}: ${warning}`));
1881
+ let nestedSection = "";
1882
+ if (parsedChildDelegation.requests.length > 0 && effectiveDelegationDepth > 1) {
1883
+ const nested = await executeNestedDelegates(parsedChildDelegation.requests, childCwd, effectiveDelegationDepth - 1, `${index + 1}.`);
1884
+ delegationWarnings.push(...nested.warnings);
1885
+ if (nested.sections.length > 0) {
1886
+ nestedSection = `\n\n##### Nested Delegated Subtasks\n\n${nested.sections.join("\n\n")}`;
1887
+ }
1888
+ }
1241
1889
  delegatedSucceeded += 1;
1242
1890
  if (delegateItems[index]) {
1243
1891
  delegateItems[index].status = "done";
@@ -1250,7 +1898,7 @@ export function createTaskTool(cwd, runner, options) {
1250
1898
  ? `${normalizedChildOutput.slice(0, Math.max(1, maxDelegatedOutputCharsFromEnv - 3))}...`
1251
1899
  : normalizedChildOutput;
1252
1900
  delegatedSections[index] =
1253
- `#### ${index + 1}. ${request.description} (${childProfileLabel})\n${childOutputExcerpt}`;
1901
+ `#### ${index + 1}. ${request.description} (${childProfileLabel})\n${childOutputExcerpt}${nestedSection}`;
1254
1902
  emitProgress({
1255
1903
  kind: "subagent_progress",
1256
1904
  phase: "running",
@@ -1280,7 +1928,6 @@ export function createTaskTool(cwd, runner, options) {
1280
1928
  finally {
1281
1929
  childReleaseIsolation?.();
1282
1930
  childReleaseLock?.();
1283
- cleanupWriteLock(explicitChildLock);
1284
1931
  }
1285
1932
  };
1286
1933
  const resolveBlockedByFailedDependencies = () => {
@@ -1505,7 +2152,6 @@ export function createTaskTool(cwd, runner, options) {
1505
2152
  finally {
1506
2153
  releaseIsolation?.();
1507
2154
  releaseWriteLock?.();
1508
- cleanupWriteLock(explicitRootLockKey);
1509
2155
  releaseSlot?.();
1510
2156
  releaseRunSlot?.();
1511
2157
  if (orchestrationRunId) {