iosm-cli 0.2.4 → 0.2.6

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 (66) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +447 -285
  3. package/dist/core/agent-profiles.d.ts +1 -0
  4. package/dist/core/agent-profiles.d.ts.map +1 -1
  5. package/dist/core/agent-profiles.js +10 -6
  6. package/dist/core/agent-profiles.js.map +1 -1
  7. package/dist/core/agent-session.d.ts +1 -1
  8. package/dist/core/agent-session.d.ts.map +1 -1
  9. package/dist/core/agent-session.js +6 -2
  10. package/dist/core/agent-session.js.map +1 -1
  11. package/dist/core/agent-teams.d.ts.map +1 -1
  12. package/dist/core/agent-teams.js +90 -19
  13. package/dist/core/agent-teams.js.map +1 -1
  14. package/dist/core/footer-data-provider.d.ts +6 -1
  15. package/dist/core/footer-data-provider.d.ts.map +1 -1
  16. package/dist/core/footer-data-provider.js +9 -0
  17. package/dist/core/footer-data-provider.js.map +1 -1
  18. package/dist/core/parallel-task-agent.d.ts +23 -1
  19. package/dist/core/parallel-task-agent.d.ts.map +1 -1
  20. package/dist/core/parallel-task-agent.js +110 -20
  21. package/dist/core/parallel-task-agent.js.map +1 -1
  22. package/dist/core/shared-memory.d.ts +16 -2
  23. package/dist/core/shared-memory.d.ts.map +1 -1
  24. package/dist/core/shared-memory.js +283 -91
  25. package/dist/core/shared-memory.js.map +1 -1
  26. package/dist/core/singular.d.ts.map +1 -1
  27. package/dist/core/singular.js +3 -1
  28. package/dist/core/singular.js.map +1 -1
  29. package/dist/core/subagents.d.ts +1 -1
  30. package/dist/core/subagents.d.ts.map +1 -1
  31. package/dist/core/subagents.js +11 -3
  32. package/dist/core/subagents.js.map +1 -1
  33. package/dist/core/swarm/planner.d.ts.map +1 -1
  34. package/dist/core/swarm/planner.js +200 -12
  35. package/dist/core/swarm/planner.js.map +1 -1
  36. package/dist/core/swarm/scheduler.d.ts +2 -0
  37. package/dist/core/swarm/scheduler.d.ts.map +1 -1
  38. package/dist/core/swarm/scheduler.js +87 -6
  39. package/dist/core/swarm/scheduler.js.map +1 -1
  40. package/dist/core/system-prompt.d.ts.map +1 -1
  41. package/dist/core/system-prompt.js +1 -0
  42. package/dist/core/system-prompt.js.map +1 -1
  43. package/dist/core/tools/ast-grep.d.ts.map +1 -1
  44. package/dist/core/tools/ast-grep.js +2 -0
  45. package/dist/core/tools/ast-grep.js.map +1 -1
  46. package/dist/core/tools/shared-memory.d.ts.map +1 -1
  47. package/dist/core/tools/shared-memory.js +79 -11
  48. package/dist/core/tools/shared-memory.js.map +1 -1
  49. package/dist/core/tools/task.d.ts +12 -1
  50. package/dist/core/tools/task.d.ts.map +1 -1
  51. package/dist/core/tools/task.js +1023 -76
  52. package/dist/core/tools/task.js.map +1 -1
  53. package/dist/core/tools/yq.d.ts.map +1 -1
  54. package/dist/core/tools/yq.js +2 -0
  55. package/dist/core/tools/yq.js.map +1 -1
  56. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  57. package/dist/modes/interactive/components/footer.js +2 -1
  58. package/dist/modes/interactive/components/footer.js.map +1 -1
  59. package/dist/modes/interactive/interactive-mode.d.ts +13 -0
  60. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  61. package/dist/modes/interactive/interactive-mode.js +881 -74
  62. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  63. package/docs/cli-reference.md +4 -0
  64. package/docs/interactive-mode.md +2 -0
  65. package/docs/orchestration-and-subagents.md +5 -0
  66. package/package.json +1 -1
@@ -5,6 +5,8 @@ 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
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";
9
+ import { readSharedMemory, summarizeSharedMemoryUsage, writeSharedMemory, } from "../shared-memory.js";
8
10
  const taskSchema = Type.Object({
9
11
  description: Type.Optional(Type.String({
10
12
  description: "Optional short 3-5 word description of what the subagent will do. If omitted, it is derived from prompt.",
@@ -22,7 +24,7 @@ const taskSchema = Type.Object({
22
24
  description: "Optional custom subagent name loaded from .iosm/agents or global agents directory.",
23
25
  })),
24
26
  profile: Type.Optional(Type.String({
25
- description: "Optional subagent capability profile. Defaults to full when omitted. 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
+ 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`.",
26
28
  })),
27
29
  cwd: Type.Optional(Type.String({
28
30
  description: "Optional working directory for this subagent. Relative paths are resolved from the current workspace.",
@@ -51,29 +53,26 @@ const taskSchema = Type.Object({
51
53
  description: "Optional hint for intra-task delegation fan-out. Higher value allows more delegated subtasks to run in parallel inside a single task execution.",
52
54
  })),
53
55
  });
54
- /** Tool names available per profile */
55
- const toolsByProfile = {
56
- explore: ["read", "grep", "find", "ls"],
57
- plan: ["read", "bash", "grep", "find", "ls"],
58
- iosm: ["read", "bash", "edit", "write", "grep", "find", "ls"],
59
- meta: ["read", "bash", "edit", "write", "grep", "find", "ls"],
60
- iosm_analyst: ["read", "bash", "grep", "find", "ls"],
61
- iosm_verifier: ["read", "bash", "write"],
62
- cycle_planner: ["read", "bash", "write"],
63
- full: ["read", "bash", "edit", "write", "grep", "find", "ls"],
64
- };
56
+ /** Tool names available per profile (kept in sync with AGENT_PROFILES). */
57
+ const toolsByProfile = Object.values(AGENT_PROFILES).reduce((acc, profile) => {
58
+ acc[profile.name] = [...profile.tools];
59
+ return acc;
60
+ }, {});
65
61
  /** System prompt injected per profile */
66
62
  const systemPromptByProfile = {
67
63
  explore: "You are a fast read-only codebase explorer. Answer concisely. Never write or edit files.",
68
64
  plan: "You are a technical architect. Analyze the codebase and produce a clear implementation plan. Do not write or edit files.",
69
65
  iosm: "You are an IOSM execution agent. Use IOSM methodology and keep IOSM artifacts synchronized with implementation.",
70
- 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. 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.",
66
+ 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. For any metrics (speedup, compliance, conflict counts, quality scores), report only values backed by observed runtime evidence; if evidence is missing, mark the metric as unknown. Do not claim report files/artifacts unless they were produced in this run or verified on disk.",
71
67
  iosm_analyst: "You are an IOSM metrics analyst. Analyze .iosm/ artifacts and codebase metrics. Be precise and evidence-based.",
72
68
  iosm_verifier: "You are an IOSM verifier. Validate checks and update only required IOSM artifacts with deterministic reasoning.",
73
69
  cycle_planner: "You are an IOSM cycle planner. Propose and align cycle goals with measurable outcomes and concrete risks.",
74
70
  full: "You are a software engineering agent. Execute the task end-to-end.",
75
71
  };
76
- const writeCapableProfiles = new Set(["full", "meta", "iosm_verifier", "cycle_planner"]);
72
+ const writeCapableTools = new Set(["bash", "edit", "write"]);
73
+ const backgroundUnsafeTools = new Set(writeCapableTools);
74
+ const writeCapableProfiles = new Set(Object.keys(toolsByProfile).filter((profileName) => toolsByProfile[profileName].some((tool) => writeCapableTools.has(tool))));
75
+ const backgroundSafeProfiles = Object.keys(toolsByProfile).filter((profileName) => toolsByProfile[profileName].every((tool) => !backgroundUnsafeTools.has(tool)));
77
76
  const delegationTagName = "delegate_task";
78
77
  class Semaphore {
79
78
  constructor(limit) {
@@ -132,7 +131,7 @@ class Mutex {
132
131
  }
133
132
  const maxParallelFromEnv = parseBoundedInt(process.env.IOSM_SUBAGENT_MAX_PARALLEL, MAX_ORCHESTRATION_PARALLEL, 1, MAX_ORCHESTRATION_PARALLEL);
134
133
  const subagentSemaphore = new Semaphore(maxParallelFromEnv);
135
- const maxDelegationDepthFromEnv = parseBoundedInt(process.env.IOSM_SUBAGENT_MAX_DELEGATION_DEPTH, 1, 0, MAX_SUBAGENT_DELEGATION_DEPTH);
134
+ const maxDelegationDepthFromEnv = parseBoundedInt(process.env.IOSM_SUBAGENT_MAX_DELEGATION_DEPTH, 2, 0, MAX_SUBAGENT_DELEGATION_DEPTH);
136
135
  const maxDelegationsPerTaskFromEnv = parseBoundedInt(process.env.IOSM_SUBAGENT_MAX_DELEGATIONS_PER_TASK, MAX_SUBAGENT_DELEGATIONS_PER_TASK, 0, MAX_SUBAGENT_DELEGATIONS_PER_TASK);
137
136
  const maxDelegatedParallelFromEnv = parseBoundedInt(process.env.IOSM_SUBAGENT_MAX_DELEGATE_PARALLEL, MAX_SUBAGENT_DELEGATE_PARALLEL, 1, MAX_SUBAGENT_DELEGATE_PARALLEL);
138
137
  const emptyOutputRetriesFromEnv = parseBoundedInt(process.env.IOSM_SUBAGENT_EMPTY_OUTPUT_RETRIES, 1, 0, 2);
@@ -179,6 +178,9 @@ function deriveAutoDelegateParallelHint(profile, agentName, hostProfile, descrip
179
178
  const fileLikeMatches = normalized.match(/\b[A-Za-z0-9_.-]+\.[A-Za-z0-9]{1,8}\b/g) ?? [];
180
179
  const listMarkers = text.match(/(?:^|\n)\s*(?:[-*]|\d+[.)])\s+/g)?.length ?? 0;
181
180
  const hasCodeBlock = text.includes("```");
181
+ 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) ?? [];
182
+ const strongActionSignal = new Set(actionTokenMatches.map((token) => token.toLowerCase())).size >= 2;
183
+ const metaOrchestratorContext = isMetaProfile || isMetaHost;
182
184
  let score = 0;
183
185
  if (words >= 40) {
184
186
  score += 2;
@@ -196,15 +198,32 @@ function deriveAutoDelegateParallelHint(profile, agentName, hostProfile, descrip
196
198
  score += 1;
197
199
  }
198
200
  const referenceCount = pathLikeMatches.length + fileLikeMatches.length;
201
+ const metaNonTrivialSignal = words >= 12 ||
202
+ clauses >= 3 ||
203
+ listMarkers >= 1 ||
204
+ referenceCount >= 1 ||
205
+ hasCodeBlock ||
206
+ (strongActionSignal && words >= 4);
199
207
  if (referenceCount >= 3 || (referenceCount >= 1 && words >= 20)) {
200
208
  score += 1;
201
209
  }
202
210
  if (hasCodeBlock) {
203
211
  score += 1;
204
212
  }
205
- if ((isMetaProfile || isMetaHost) && score > 0) {
206
- // Meta profile is intentionally parallel-biased for non-trivial work.
207
- score += 1;
213
+ if (metaOrchestratorContext) {
214
+ // In meta orchestration, require delegation pressure for non-trivial prompts
215
+ // even when lexical scoring is still low.
216
+ if (score === 0) {
217
+ if (strongActionSignal && words >= 4) {
218
+ score = 1;
219
+ }
220
+ else if (metaNonTrivialSignal) {
221
+ score = 2;
222
+ }
223
+ }
224
+ else if (score > 0) {
225
+ score += 1;
226
+ }
208
227
  }
209
228
  if (score >= 6)
210
229
  return 10;
@@ -320,6 +339,9 @@ function buildDelegationProtocolPrompt(depthRemaining, maxDelegations, minDelega
320
339
  `Keep a brief coordinator note outside the blocks, but do not collapse the full workload into one monolithic answer.`,
321
340
  `If safe decomposition is truly impossible, output exactly one line: DELEGATION_IMPOSSIBLE: <precise reason>.`,
322
341
  `When shared_memory tools are available, exchange intermediate state through shared_memory_write/shared_memory_read instead of repeating large context.`,
342
+ `Shared-memory protocol: use stable namespaced keys (findings/<stream>, plan/<stream>, risks/<stream>).`,
343
+ `Use scope=run for cross-stream coordination, scope=task for local scratch state, read before overwrite, and use if_version for contested updates.`,
344
+ `Reserve mode=append for timeline/log keys only; avoid append on canonical state keys.`,
323
345
  ].join("\n");
324
346
  }
325
347
  return [
@@ -329,8 +351,270 @@ function buildDelegationProtocolPrompt(depthRemaining, maxDelegations, minDelega
329
351
  `</${delegationTagName}>`,
330
352
  `Only emit blocks when necessary. Keep normal analysis/answer text outside those blocks.`,
331
353
  `When shared_memory tools are available, exchange intermediate state through shared_memory_write/shared_memory_read instead of repeating large context.`,
354
+ `Shared-memory protocol: prefer namespaced keys and read-before-write discipline; use CAS (if_version) on shared state updates.`,
355
+ `Reserve mode=append for timeline/log keys only.`,
332
356
  ].join("\n");
333
357
  }
358
+ function truncateForDelegationContext(text, maxChars = 2200) {
359
+ const normalized = normalizeSpacing(text);
360
+ if (normalized.length <= maxChars)
361
+ return normalized;
362
+ return `${normalized.slice(0, Math.max(100, maxChars - 3)).trimEnd()}...`;
363
+ }
364
+ function stripDelegatedSectionHeading(section) {
365
+ return section.replace(/^####\s+[^\n]*\n?/i, "").trim();
366
+ }
367
+ function normalizeDelegatedSectionBody(section) {
368
+ return stripDelegatedSectionHeading(section)
369
+ .toLowerCase()
370
+ .replace(/[`"'*_#~[\](){}<>\\|]/g, " ")
371
+ .replace(/[^\w\s]/g, " ")
372
+ .replace(/\s+/g, " ")
373
+ .trim();
374
+ }
375
+ function detectDuplicateDelegatedSections(sections) {
376
+ const duplicatePairs = [];
377
+ const normalizedSections = sections.map((section) => normalizeDelegatedSectionBody(section));
378
+ for (let index = 0; index < normalizedSections.length; index += 1) {
379
+ const current = normalizedSections[index] ?? "";
380
+ if (current.length < 60)
381
+ continue;
382
+ for (let previous = 0; previous < index; previous += 1) {
383
+ const baseline = normalizedSections[previous] ?? "";
384
+ if (baseline.length < 60)
385
+ continue;
386
+ if (current === baseline) {
387
+ duplicatePairs.push({ duplicate: index + 1, original: previous + 1 });
388
+ break;
389
+ }
390
+ const shorter = current.length <= baseline.length ? current : baseline;
391
+ const longer = current.length > baseline.length ? current : baseline;
392
+ if (shorter.length >= 100 && longer.includes(shorter)) {
393
+ const coverage = shorter.length / Math.max(1, longer.length);
394
+ if (coverage >= 0.92) {
395
+ duplicatePairs.push({ duplicate: index + 1, original: previous + 1 });
396
+ break;
397
+ }
398
+ }
399
+ }
400
+ }
401
+ return {
402
+ duplicates: duplicatePairs.length,
403
+ duplicatePairs,
404
+ };
405
+ }
406
+ function extractDelegationWorkstreams(text, maxItems) {
407
+ if (maxItems <= 0)
408
+ return [];
409
+ const seen = new Set();
410
+ const pushUnique = (raw) => {
411
+ const cleaned = raw
412
+ .replace(/^[-*]\s+/, "")
413
+ .replace(/^\d+[.)]\s+/, "")
414
+ .replace(/\s+/g, " ")
415
+ .trim();
416
+ if (cleaned.length < 5)
417
+ return;
418
+ const key = cleaned.toLowerCase();
419
+ if (seen.has(key))
420
+ return;
421
+ seen.add(key);
422
+ };
423
+ for (const line of text.split("\n")) {
424
+ if (!/^\s*(?:[-*]|\d+[.)])\s+/.test(line))
425
+ continue;
426
+ pushUnique(line);
427
+ if (seen.size >= maxItems)
428
+ break;
429
+ }
430
+ if (seen.size < maxItems) {
431
+ const fragments = text
432
+ .split(/[\n.;:]+/g)
433
+ .map((fragment) => fragment.trim())
434
+ .filter((fragment) => fragment.length >= 10)
435
+ .slice(0, maxItems * 3);
436
+ for (const fragment of fragments) {
437
+ pushUnique(fragment);
438
+ if (seen.size >= maxItems)
439
+ break;
440
+ }
441
+ }
442
+ return Array.from(seen).slice(0, maxItems);
443
+ }
444
+ function deriveAutoDelegateProfile(baseProfile, description, prompt) {
445
+ const signal = `${description}\n${prompt}`.toLowerCase();
446
+ const writeIntent = /\b(?:implement|fix|patch|refactor|rewrite|edit|update|migrate|change|write|apply)\b/.test(signal);
447
+ if (baseProfile === "full")
448
+ return writeIntent ? "full" : "explore";
449
+ if (baseProfile === "meta")
450
+ return writeIntent ? "full" : "explore";
451
+ if (baseProfile === "iosm")
452
+ return writeIntent ? "full" : "explore";
453
+ if (baseProfile === "iosm_verifier")
454
+ return "iosm_verifier";
455
+ if (baseProfile === "cycle_planner")
456
+ return "cycle_planner";
457
+ if (baseProfile === "plan" || baseProfile === "iosm_analyst")
458
+ return "explore";
459
+ return "explore";
460
+ }
461
+ function pickAutoDelegateAgent(workstream, availableCustomNames) {
462
+ if (availableCustomNames.length === 0)
463
+ return undefined;
464
+ const normalizedWorkstream = workstream.toLowerCase();
465
+ const names = availableCustomNames.map((name) => ({ raw: name, normalized: name.toLowerCase() }));
466
+ const findByHint = (hints) => {
467
+ for (const hint of hints) {
468
+ const exact = names.find((item) => item.normalized === hint);
469
+ if (exact)
470
+ return exact.raw;
471
+ const contains = names.find((item) => item.normalized.includes(hint));
472
+ if (contains)
473
+ return contains.raw;
474
+ }
475
+ return undefined;
476
+ };
477
+ if (/\b(?:test|qa|coverage|verification|regression)\b/.test(normalizedWorkstream)) {
478
+ return findByHint(["qa_test_engineer", "qa", "tester", "verification"]);
479
+ }
480
+ if (/\b(?:ui|ux|design|layout|accessibility)\b/.test(normalizedWorkstream)) {
481
+ return findByHint(["uiux_top_senior", "uiux", "ui", "ux", "design"]);
482
+ }
483
+ if (/\b(?:architecture|codebase|refactor|security|rbac|auth|database|api)\b/.test(normalizedWorkstream)) {
484
+ return findByHint(["codebase_auditor", "architect", "security", "backend"]);
485
+ }
486
+ return undefined;
487
+ }
488
+ function buildAutoDelegationPrompt(input) {
489
+ const objective = truncateForDelegationContext(`${input.rootDescription}\n\n${input.rootPrompt}`);
490
+ return normalizeSpacing([
491
+ `Workstream ${input.ordinal}/${input.total}: ${input.streamTitle}`,
492
+ "Scope:",
493
+ `- Own this stream end-to-end and avoid duplicating sibling streams.`,
494
+ `- Produce concrete findings/changes for this stream only.`,
495
+ "Coordinator objective:",
496
+ objective,
497
+ ].join("\n"));
498
+ }
499
+ function uniquifyWorkstreamTitles(titles) {
500
+ const counts = new Map();
501
+ return titles.map((title) => {
502
+ const key = title
503
+ .toLowerCase()
504
+ .replace(/[^a-z0-9]+/g, " ")
505
+ .trim();
506
+ const next = (counts.get(key) ?? 0) + 1;
507
+ counts.set(key, next);
508
+ if (next <= 1)
509
+ return title;
510
+ return `${title} (${next})`;
511
+ });
512
+ }
513
+ function semanticallyDeduplicateWorkstreamTitles(titles) {
514
+ const kept = [];
515
+ const tokenized = (value) => new Set(value
516
+ .toLowerCase()
517
+ .replace(/[^a-z0-9]+/g, " ")
518
+ .split(/\s+/)
519
+ .map((token) => token.trim())
520
+ .filter((token) => token.length >= 3));
521
+ const jaccard = (left, right) => {
522
+ if (left.size === 0 || right.size === 0)
523
+ return 0;
524
+ let intersection = 0;
525
+ for (const token of left) {
526
+ if (right.has(token))
527
+ intersection += 1;
528
+ }
529
+ const union = left.size + right.size - intersection;
530
+ return union > 0 ? intersection / union : 0;
531
+ };
532
+ for (const candidate of titles) {
533
+ const candidateTokens = tokenized(candidate);
534
+ const duplicate = kept.some((existing) => {
535
+ const existingTokens = tokenized(existing);
536
+ const similarity = jaccard(existingTokens, candidateTokens);
537
+ return similarity >= 0.82;
538
+ });
539
+ if (!duplicate) {
540
+ kept.push(candidate);
541
+ }
542
+ }
543
+ return kept;
544
+ }
545
+ function deriveAutoSynthLockKey(streamTitle, ordinal) {
546
+ const pathLike = streamTitle.match(/\b(?:[A-Za-z0-9_.-]+\/)+[A-Za-z0-9_.-]+\b/)?.[0];
547
+ if (pathLike) {
548
+ const normalizedPath = pathLike
549
+ .replace(/^[./]+/, "")
550
+ .split("/")
551
+ .filter((segment) => segment.length > 0)
552
+ .slice(0, 2)
553
+ .join("/");
554
+ return `auto-synth:${toSharedMemoryKeySegment(normalizedPath, `stream-${ordinal}`)}`;
555
+ }
556
+ if (/\b(auth|rbac|acl|permission)\b/i.test(streamTitle))
557
+ return "auto-synth:auth";
558
+ if (/\b(db|database|sql|storage|schema)\b/i.test(streamTitle))
559
+ return "auto-synth:data";
560
+ if (/\b(ui|ux|frontend|view|component)\b/i.test(streamTitle))
561
+ return "auto-synth:ui";
562
+ if (/\b(test|qa|verification|coverage)\b/i.test(streamTitle))
563
+ return "auto-synth:test";
564
+ if (/\b(api|gateway|route|http)\b/i.test(streamTitle))
565
+ return "auto-synth:api";
566
+ return `auto-synth:${toSharedMemoryKeySegment(streamTitle, `stream-${ordinal}`)}`;
567
+ }
568
+ function synthesizeDelegationRequests(input) {
569
+ const desiredTotal = Math.max(0, Math.min(input.maxDelegations, input.minDelegationsPreferred));
570
+ const missing = Math.max(0, desiredTotal - input.currentDelegates);
571
+ if (missing <= 0)
572
+ return [];
573
+ const combined = `${input.description}\n${input.prompt}`.trim();
574
+ const extracted = extractDelegationWorkstreams(combined, Math.max(missing, desiredTotal));
575
+ const fallbackByIndex = [
576
+ "Architecture and structure analysis",
577
+ "Behavioral verification and tests",
578
+ "Risk, regressions, and remediation",
579
+ "Integration and dependency checks",
580
+ "Delivery summary and rollout constraints",
581
+ ];
582
+ const titles = [];
583
+ for (const stream of extracted) {
584
+ titles.push(stream);
585
+ if (titles.length >= missing)
586
+ break;
587
+ }
588
+ for (let index = 0; titles.length < missing && index < fallbackByIndex.length; index += 1) {
589
+ titles.push(fallbackByIndex[index]);
590
+ }
591
+ while (titles.length < missing) {
592
+ titles.push(`Independent workstream ${titles.length + 1}`);
593
+ }
594
+ const semanticallyDeduped = semanticallyDeduplicateWorkstreamTitles(titles);
595
+ while (semanticallyDeduped.length < missing) {
596
+ semanticallyDeduped.push(`Independent workstream ${semanticallyDeduped.length + 1}`);
597
+ }
598
+ const uniqueTitles = uniquifyWorkstreamTitles(semanticallyDeduped);
599
+ const defaultProfile = deriveAutoDelegateProfile(input.baseProfile, input.description, input.prompt);
600
+ return uniqueTitles.map((streamTitle, index) => ({
601
+ description: `Auto: ${streamTitle}`,
602
+ profile: defaultProfile,
603
+ agent: pickAutoDelegateAgent(streamTitle, input.availableCustomNames),
604
+ prompt: buildAutoDelegationPrompt({
605
+ streamTitle,
606
+ rootDescription: input.description,
607
+ rootPrompt: input.prompt,
608
+ ordinal: input.currentDelegates + index + 1,
609
+ total: input.currentDelegates + uniqueTitles.length,
610
+ }),
611
+ cwd: undefined,
612
+ lockKey: writeCapableProfiles.has(defaultProfile) ? deriveAutoSynthLockKey(streamTitle, index + 1) : undefined,
613
+ model: undefined,
614
+ isolation: undefined,
615
+ dependsOn: undefined,
616
+ }));
617
+ }
334
618
  function withDelegationPrompt(basePrompt, depthRemaining, maxDelegations, minDelegationsPreferred = 0) {
335
619
  const protocol = buildDelegationProtocolPrompt(depthRemaining, maxDelegations, minDelegationsPreferred);
336
620
  return `${basePrompt}\n\n${protocol}`;
@@ -348,10 +632,78 @@ function buildSharedMemoryGuidance(runId, taskId) {
348
632
  "Guidelines:",
349
633
  "- Use scope=run for cross-agent data and scope=task for task-local notes.",
350
634
  "- Keep entries compact and key-based (for example: findings/auth, plan/step-1, risks/session).",
635
+ "- Prefer one canonical key per stream and deduplicate updates; avoid redundant writes in loops.",
351
636
  "- Read before overwrite when collaborating on the same key.",
637
+ "- Use if_version CAS for contested updates on shared keys.",
638
+ "- Use mode=append only for log/timeline keys; use mode=set for canonical state.",
352
639
  "[/SHARED_MEMORY]",
353
640
  ].join("\n");
354
641
  }
642
+ function toSharedMemoryKeySegment(raw, fallback) {
643
+ const normalized = (raw ?? "")
644
+ .trim()
645
+ .toLowerCase()
646
+ .replace(/[^a-z0-9._-]+/g, "-")
647
+ .replace(/-+/g, "-")
648
+ .replace(/^-+|-+$/g, "");
649
+ if (!normalized)
650
+ return fallback;
651
+ return normalized.slice(0, 64);
652
+ }
653
+ function buildTaskPlanSharedMemoryKey(taskId) {
654
+ return `plan/${toSharedMemoryKeySegment(taskId, "task")}`;
655
+ }
656
+ function buildDelegateFindingSharedMemoryKey(taskId, delegateLabel) {
657
+ return `findings/${toSharedMemoryKeySegment(taskId, "task")}/${toSharedMemoryKeySegment(delegateLabel, "stream")}`;
658
+ }
659
+ function extractClaimCandidates(text, maxItems = 3) {
660
+ const matches = text.match(/\b(?:[A-Za-z0-9_.-]+\/)+[A-Za-z0-9_.-]+\b/g) ?? [];
661
+ const normalized = new Set();
662
+ for (const match of matches) {
663
+ const cleaned = match
664
+ .replace(/^[./]+/, "")
665
+ .replace(/\/+/g, "/")
666
+ .trim();
667
+ if (!cleaned)
668
+ continue;
669
+ normalized.add(cleaned);
670
+ if (normalized.size >= maxItems)
671
+ break;
672
+ }
673
+ return Array.from(normalized);
674
+ }
675
+ function buildClaimKey(pathLike) {
676
+ const segments = pathLike
677
+ .split("/")
678
+ .map((segment) => toSharedMemoryKeySegment(segment, "segment"))
679
+ .filter((segment) => segment.length > 0);
680
+ if (segments.length === 0) {
681
+ return "claims/unknown";
682
+ }
683
+ return `claims/${segments.slice(0, 6).join("/")}`;
684
+ }
685
+ function buildDelegateCoordinationGuidance(input) {
686
+ const planKey = buildTaskPlanSharedMemoryKey(input.taskId);
687
+ const findingKey = buildDelegateFindingSharedMemoryKey(input.taskId, input.delegateLabel);
688
+ return [
689
+ "[DELEGATE_COORDINATION]",
690
+ `delegate_label: ${input.delegateLabel}`,
691
+ `delegate_description: ${input.delegateDescription}`,
692
+ `read_first_key: ${planKey}`,
693
+ `publish_key: ${findingKey}`,
694
+ "Before heavy repository reads, check current coordination state via shared_memory_read.",
695
+ "Use claims/<path> run-scoped keys with CAS (if_version) to announce file ownership and reduce duplicate reads.",
696
+ "Before responding, publish concise stream findings via shared_memory_write.",
697
+ "Keep ownership strict: do not duplicate sibling streams.",
698
+ "[/DELEGATE_COORDINATION]",
699
+ ].join("\n");
700
+ }
701
+ function createSharedMemoryExcerpt(value, maxChars = 1200) {
702
+ const normalized = normalizeSpacing(value);
703
+ if (normalized.length <= maxChars)
704
+ return normalized;
705
+ return `${normalized.slice(0, Math.max(120, maxChars - 3)).trimEnd()}...`;
706
+ }
355
707
  function parseDelegationRequests(output, maxRequests) {
356
708
  const requests = [];
357
709
  const warnings = [];
@@ -366,19 +718,21 @@ function parseDelegationRequests(output, maxRequests) {
366
718
  return "";
367
719
  }
368
720
  const attrs = {};
369
- for (const match of attrsRaw.matchAll(/([A-Za-z_][A-Za-z0-9_-]*)="([^"]*)"/g)) {
370
- attrs[match[1].toLowerCase()] = match[2];
721
+ for (const match of attrsRaw.matchAll(/([A-Za-z_][A-Za-z0-9_-]*)\s*=\s*(?:"([^"]*)"|'([^']*)')/g)) {
722
+ attrs[match[1].toLowerCase()] = (match[2] ?? match[3] ?? "").trim();
371
723
  }
372
724
  const prompt = normalizeSpacing(bodyRaw ?? "");
373
725
  if (!prompt) {
374
726
  warnings.push(`Ignored delegation block with empty prompt.`);
375
727
  return "";
376
728
  }
377
- const profile = (attrs.profile ?? "explore").trim();
378
- if (!(profile in toolsByProfile)) {
379
- warnings.push(`Ignored delegation block with unknown profile "${profile}".`);
729
+ const profileRaw = (attrs.profile ?? "explore").trim();
730
+ if (!profileRaw) {
731
+ warnings.push(`Ignored delegation block with empty profile.`);
380
732
  return "";
381
733
  }
734
+ const normalizedProfile = profileRaw.toLowerCase();
735
+ const profile = isValidProfileName(normalizedProfile) ? normalizedProfile : profileRaw;
382
736
  const isolationRaw = (attrs.isolation ?? "").trim().toLowerCase();
383
737
  const isolation = isolationRaw === "worktree" ? "worktree" : isolationRaw === "none" ? "none" : undefined;
384
738
  if (isolationRaw && !isolation) {
@@ -409,6 +763,9 @@ function parseDelegationRequests(output, maxRequests) {
409
763
  cleanedOutput: normalizeSpacing(cleaned),
410
764
  };
411
765
  }
766
+ function isBackgroundSafeToolset(tools) {
767
+ return tools.every((toolName) => !backgroundUnsafeTools.has(toolName));
768
+ }
412
769
  function getCwdLockKey(cwd) {
413
770
  // Normalize lock key to keep behavior consistent across path aliases.
414
771
  return path.resolve(cwd).toLowerCase();
@@ -694,12 +1051,26 @@ export function createTaskTool(cwd, runner, options) {
694
1051
  const resolveCustom = (name) => {
695
1052
  if (!name || !options?.resolveCustomSubagent)
696
1053
  return undefined;
697
- return options.resolveCustomSubagent(name);
1054
+ const trimmed = name.trim();
1055
+ if (!trimmed)
1056
+ return undefined;
1057
+ const resolved = options.resolveCustomSubagent(trimmed);
1058
+ if (resolved)
1059
+ return resolved;
1060
+ const lowered = trimmed.toLowerCase();
1061
+ if (lowered !== trimmed) {
1062
+ return options.resolveCustomSubagent(lowered);
1063
+ }
1064
+ return undefined;
698
1065
  };
699
1066
  let normalizedAgentName = agentName?.trim() || undefined;
700
1067
  let customSubagent = resolveCustom(normalizedAgentName);
701
- let normalizedProfile = profile?.trim().toLowerCase() || customSubagent?.profile?.trim().toLowerCase() || "full";
1068
+ const requestedProfileRaw = profile?.trim() || undefined;
702
1069
  const normalizedHostProfile = options?.getHostProfileName?.()?.trim().toLowerCase() ?? options?.hostProfileName?.trim().toLowerCase();
1070
+ const hostProfileFallback = normalizedHostProfile && isValidProfileName(normalizedHostProfile) ? normalizedHostProfile : "full";
1071
+ let normalizedProfile = requestedProfileRaw?.toLowerCase() ||
1072
+ customSubagent?.profile?.trim().toLowerCase() ||
1073
+ hostProfileFallback;
703
1074
  if (normalizedAgentName && !customSubagent) {
704
1075
  const available = availableCustomNames.length > 0 ? ` Available custom agents: ${availableCustomNames.join(", ")}.` : "";
705
1076
  throw new Error(`Unknown subagent: ${normalizedAgentName}.${available}`);
@@ -713,17 +1084,24 @@ export function createTaskTool(cwd, runner, options) {
713
1084
  normalizedProfile = (profileAsAgent.profile ?? "full").trim().toLowerCase();
714
1085
  }
715
1086
  }
716
- if (!toolsByProfile[normalizedProfile]) {
717
- normalizedProfile = "full";
1087
+ if (!customSubagent && !isValidProfileName(normalizedProfile)) {
1088
+ throw new Error(`Unknown profile "${requestedProfileRaw ?? normalizedProfile}". Valid profiles: ${Object.keys(toolsByProfile).join(", ")}.`);
718
1089
  }
719
- const effectiveProfile = customSubagent?.profile ?? normalizedProfile;
1090
+ const effectiveProfileCandidate = (customSubagent?.profile ?? normalizedProfile).trim().toLowerCase();
1091
+ if (!isValidProfileName(effectiveProfileCandidate)) {
1092
+ throw new Error(`Invalid resolved profile "${effectiveProfileCandidate}". Valid profiles: ${Object.keys(toolsByProfile).join(", ")}.`);
1093
+ }
1094
+ const effectiveProfile = effectiveProfileCandidate;
720
1095
  let tools = customSubagent?.tools
721
1096
  ? [...customSubagent.tools]
722
- : [...(toolsByProfile[effectiveProfile] ?? toolsByProfile.explore)];
1097
+ : [...toolsByProfile[effectiveProfile]];
723
1098
  if (customSubagent?.disallowedTools?.length) {
724
1099
  const blocked = new Set(customSubagent.disallowedTools);
725
1100
  tools = tools.filter((tool) => !blocked.has(tool));
726
1101
  }
1102
+ if (isReadOnlyProfileName(normalizedHostProfile) && tools.some((tool) => writeCapableTools.has(tool))) {
1103
+ throw new Error(`Host profile "${normalizedHostProfile}" is read-only. Switch to full/meta/iosm to launch write-capable subtasks.`);
1104
+ }
727
1105
  const delegationDepth = maxDelegationDepthFromEnv;
728
1106
  const requestedDelegateParallelHint = typeof delegateParallelHint === "number" && Number.isInteger(delegateParallelHint)
729
1107
  ? Math.max(1, Math.min(MAX_SUBAGENT_DELEGATE_PARALLEL, delegateParallelHint))
@@ -735,13 +1113,22 @@ export function createTaskTool(cwd, runner, options) {
735
1113
  const effectiveDelegationDepth = effectiveProfile === "meta" || normalizedHostProfile === "meta" || normalizedAgentName?.toLowerCase().includes("orchestrator")
736
1114
  ? Math.max(delegationDepth, 2)
737
1115
  : delegationDepth;
1116
+ const orchestratedRunContext = !!(orchestrationRunId && orchestrationTaskId);
1117
+ const strictDelegationContract = effectiveProfile === "meta" ||
1118
+ normalizedHostProfile === "meta" ||
1119
+ normalizedAgentName?.toLowerCase().includes("orchestrator") ||
1120
+ (orchestratedRunContext && (effectiveDelegateParallelHint ?? 0) >= 2);
738
1121
  let effectiveMaxDelegations = Math.max(0, Math.min(maxDelegationsPerTaskFromEnv, effectiveDelegateParallelHint ?? maxDelegationsPerTaskFromEnv));
739
1122
  let effectiveMaxDelegateParallel = Math.max(1, Math.min(maxDelegatedParallelFromEnv, effectiveDelegateParallelHint ?? maxDelegatedParallelFromEnv));
740
- const preferredDelegationFloor = effectiveProfile === "meta" || normalizedHostProfile === "meta" ? 3 : 2;
1123
+ const isMetaDelegationContext = effectiveProfile === "meta" || normalizedHostProfile === "meta";
1124
+ const preferredDelegationFloorBase = isMetaDelegationContext ? 3 : 2;
1125
+ const preferredDelegationFloorMin = isMetaDelegationContext ? 2 : 1;
1126
+ const metaDelegationCapacityFloor = 3;
1127
+ const preferredDelegationFloor = Math.max(preferredDelegationFloorMin, Math.min(preferredDelegationFloorBase, effectiveDelegateParallelHint ?? preferredDelegationFloorBase));
741
1128
  const applyMetaDelegationFloor = requestedDelegateParallelHint === undefined &&
742
1129
  (effectiveProfile === "meta" || normalizedHostProfile === "meta");
743
1130
  if (applyMetaDelegationFloor) {
744
- effectiveMaxDelegations = Math.max(effectiveMaxDelegations, Math.min(maxDelegationsPerTaskFromEnv, preferredDelegationFloor));
1131
+ effectiveMaxDelegations = Math.max(effectiveMaxDelegations, Math.min(maxDelegationsPerTaskFromEnv, metaDelegationCapacityFloor));
745
1132
  effectiveMaxDelegateParallel = Math.max(effectiveMaxDelegateParallel, Math.min(maxDelegatedParallelFromEnv, preferredDelegationFloor));
746
1133
  }
747
1134
  const minDelegationsPreferred = (effectiveDelegateParallelHint ?? 0) >= 2 && effectiveMaxDelegations >= 2
@@ -764,8 +1151,10 @@ export function createTaskTool(cwd, runner, options) {
764
1151
  if (!existsSync(requestedSubagentCwd) || !statSync(requestedSubagentCwd).isDirectory()) {
765
1152
  throw new Error(`Subagent cwd does not exist or is not a directory: ${requestedSubagentCwd}`);
766
1153
  }
767
- if (runInBackground && writeCapableProfiles.has(effectiveProfile)) {
768
- throw new Error(`Background policy violation: profile "${effectiveProfile}" is write-capable. Use explore/plan/iosm_analyst for background mode.`);
1154
+ if (runInBackground && !isBackgroundSafeToolset(tools)) {
1155
+ throw new Error(`Background policy violation: profile "${effectiveProfile}" has mutable tools (${tools
1156
+ .filter((toolName) => backgroundUnsafeTools.has(toolName))
1157
+ .join(", ")}). Background mode requires read-only toolsets. Safe baseline profiles: ${backgroundSafeProfiles.join(", ")}.`);
769
1158
  }
770
1159
  const useWorktree = isolation === "worktree";
771
1160
  const queuedAt = Date.now();
@@ -821,6 +1210,42 @@ export function createTaskTool(cwd, runner, options) {
821
1210
  let subagentCwd = requestedSubagentCwd;
822
1211
  let worktreePath;
823
1212
  let runStats;
1213
+ const heldWriteLocks = new Map();
1214
+ const acquireLocalWriteLock = async (rawLockKey) => {
1215
+ const trimmed = rawLockKey?.trim();
1216
+ if (!trimmed)
1217
+ return undefined;
1218
+ const normalizedKey = getCwdLockKey(trimmed);
1219
+ const existing = heldWriteLocks.get(normalizedKey);
1220
+ if (existing) {
1221
+ existing.count += 1;
1222
+ return () => {
1223
+ const current = heldWriteLocks.get(normalizedKey);
1224
+ if (!current)
1225
+ return;
1226
+ current.count -= 1;
1227
+ if (current.count <= 0) {
1228
+ heldWriteLocks.delete(normalizedKey);
1229
+ current.release();
1230
+ cleanupWriteLock(trimmed);
1231
+ }
1232
+ };
1233
+ }
1234
+ const lock = getOrCreateWriteLock(trimmed);
1235
+ const release = await lock.acquire();
1236
+ heldWriteLocks.set(normalizedKey, { count: 1, release });
1237
+ return () => {
1238
+ const current = heldWriteLocks.get(normalizedKey);
1239
+ if (!current)
1240
+ return;
1241
+ current.count -= 1;
1242
+ if (current.count <= 0) {
1243
+ heldWriteLocks.delete(normalizedKey);
1244
+ current.release();
1245
+ cleanupWriteLock(trimmed);
1246
+ }
1247
+ };
1248
+ };
824
1249
  try {
825
1250
  throwIfAborted();
826
1251
  if (orchestrationRunId && orchestrationTaskId) {
@@ -882,8 +1307,7 @@ export function createTaskTool(cwd, runner, options) {
882
1307
  // Parallel orchestration should remain truly parallel by default.
883
1308
  // Serialize write-capable agents only when an explicit lock_key is provided.
884
1309
  if (explicitRootLockKey) {
885
- const lock = getOrCreateWriteLock(explicitRootLockKey);
886
- releaseWriteLock = await lock.acquire();
1310
+ releaseWriteLock = await acquireLocalWriteLock(explicitRootLockKey);
887
1311
  }
888
1312
  }
889
1313
  if (useWorktree) {
@@ -923,6 +1347,131 @@ export function createTaskTool(cwd, runner, options) {
923
1347
  taskId: sharedMemoryTaskId,
924
1348
  profile: effectiveProfile,
925
1349
  };
1350
+ const publishTaskCoordinationPlan = async () => {
1351
+ const key = buildTaskPlanSharedMemoryKey(sharedMemoryTaskId);
1352
+ const payload = JSON.stringify({
1353
+ taskId: sharedMemoryTaskId,
1354
+ description,
1355
+ profile: effectiveProfile,
1356
+ objective: createSharedMemoryExcerpt(prompt, 900),
1357
+ });
1358
+ try {
1359
+ await writeSharedMemory(rootSharedMemoryContext, {
1360
+ key,
1361
+ value: payload,
1362
+ scope: "run",
1363
+ mode: "set",
1364
+ }, _signal);
1365
+ }
1366
+ catch (error) {
1367
+ const message = error instanceof Error ? error.message : String(error);
1368
+ delegationWarnings.push(`Shared memory plan publish skipped: ${message}`);
1369
+ }
1370
+ };
1371
+ const publishDelegateFinding = async (input) => {
1372
+ const key = buildDelegateFindingSharedMemoryKey(sharedMemoryTaskId, input.delegateLabel);
1373
+ const payload = JSON.stringify({
1374
+ taskId: sharedMemoryTaskId,
1375
+ delegate: input.delegateLabel,
1376
+ description: input.delegateDescription,
1377
+ profile: input.delegateProfile,
1378
+ status: input.status,
1379
+ summary: createSharedMemoryExcerpt(input.content, 1000),
1380
+ });
1381
+ try {
1382
+ await writeSharedMemory({
1383
+ rootCwd: cwd,
1384
+ runId: sharedMemoryRunId,
1385
+ taskId: sharedMemoryTaskId,
1386
+ delegateId: input.delegateLabel,
1387
+ profile: input.delegateProfile,
1388
+ }, {
1389
+ key,
1390
+ value: payload,
1391
+ scope: "run",
1392
+ mode: "set",
1393
+ }, _signal);
1394
+ }
1395
+ catch (error) {
1396
+ const message = error instanceof Error ? error.message : String(error);
1397
+ delegationWarnings.push(`Shared memory finding publish skipped for delegate ${input.delegateLabel}: ${message}`);
1398
+ }
1399
+ };
1400
+ const publishStreamClaims = async (input) => {
1401
+ const claimPaths = extractClaimCandidates(`${input.description}\n${input.promptText}`, 3);
1402
+ if (claimPaths.length === 0)
1403
+ return;
1404
+ for (const claimPath of claimPaths) {
1405
+ const key = buildClaimKey(claimPath);
1406
+ let lastError;
1407
+ for (let attempt = 0; attempt < 2; attempt += 1) {
1408
+ try {
1409
+ const snapshot = await readSharedMemory(rootSharedMemoryContext, {
1410
+ scope: "run",
1411
+ key,
1412
+ includeValues: true,
1413
+ }, _signal);
1414
+ const current = snapshot.items[0];
1415
+ if (!current) {
1416
+ await writeSharedMemory(rootSharedMemoryContext, {
1417
+ key,
1418
+ value: JSON.stringify({
1419
+ path: claimPath,
1420
+ owners: [input.owner],
1421
+ updatedAt: new Date().toISOString(),
1422
+ }),
1423
+ scope: "run",
1424
+ mode: "set",
1425
+ }, _signal);
1426
+ lastError = undefined;
1427
+ break;
1428
+ }
1429
+ let existingOwners = [];
1430
+ if (current.value) {
1431
+ try {
1432
+ const parsed = JSON.parse(current.value);
1433
+ if (Array.isArray(parsed.owners)) {
1434
+ existingOwners = parsed.owners
1435
+ .filter((value) => typeof value === "string" && value.trim().length > 0)
1436
+ .slice(0, 12);
1437
+ }
1438
+ }
1439
+ catch {
1440
+ // tolerate malformed payload and overwrite with normalized shape
1441
+ }
1442
+ }
1443
+ if (existingOwners.includes(input.owner)) {
1444
+ lastError = undefined;
1445
+ break;
1446
+ }
1447
+ const nextOwners = [...existingOwners, input.owner].slice(0, 12);
1448
+ await writeSharedMemory(rootSharedMemoryContext, {
1449
+ key,
1450
+ value: JSON.stringify({
1451
+ path: claimPath,
1452
+ owners: nextOwners,
1453
+ updatedAt: new Date().toISOString(),
1454
+ }),
1455
+ scope: "run",
1456
+ mode: "set",
1457
+ ifVersion: current.version,
1458
+ }, _signal);
1459
+ lastError = undefined;
1460
+ break;
1461
+ }
1462
+ catch (error) {
1463
+ const message = error instanceof Error ? error.message : String(error);
1464
+ lastError = message;
1465
+ if (!/version mismatch/i.test(message)) {
1466
+ break;
1467
+ }
1468
+ }
1469
+ }
1470
+ if (lastError) {
1471
+ delegationWarnings.push(`Shared memory claim publish skipped (${key}) for ${input.owner}: ${lastError}`);
1472
+ }
1473
+ }
1474
+ };
926
1475
  try {
927
1476
  const runRootPass = async (runPrompt) => {
928
1477
  let emptyAttempt = 0;
@@ -1023,6 +1572,12 @@ export function createTaskTool(cwd, runner, options) {
1023
1572
  activeTool: undefined,
1024
1573
  });
1025
1574
  }
1575
+ await publishTaskCoordinationPlan();
1576
+ await publishStreamClaims({
1577
+ owner: "root",
1578
+ description,
1579
+ promptText: prompt,
1580
+ });
1026
1581
  const firstPass = await runRootPass(rootPrompt);
1027
1582
  output = firstPass.output;
1028
1583
  subagentSessionId = firstPass.sessionId;
@@ -1052,8 +1607,33 @@ export function createTaskTool(cwd, runner, options) {
1052
1607
  runStats = secondPass.stats ?? runStats;
1053
1608
  parsedDelegation = parseDelegationRequests(output, effectiveDelegationDepth > 0 ? effectiveMaxDelegations : 0);
1054
1609
  }
1610
+ if (minDelegationsPreferred > 0 &&
1611
+ parsedDelegation.requests.length === 0 &&
1612
+ strictDelegationContract) {
1613
+ const impossibleMatch = output.match(/^\s*DELEGATION_IMPOSSIBLE\s*:\s*(.+)$/im);
1614
+ if (!impossibleMatch) {
1615
+ const synthesizedRequests = synthesizeDelegationRequests({
1616
+ description,
1617
+ prompt,
1618
+ baseProfile: effectiveProfile,
1619
+ currentDelegates: parsedDelegation.requests.length,
1620
+ minDelegationsPreferred,
1621
+ maxDelegations: effectiveMaxDelegations,
1622
+ availableCustomNames,
1623
+ });
1624
+ if (synthesizedRequests.length > 0) {
1625
+ parsedDelegation.requests.push(...synthesizedRequests);
1626
+ delegationWarnings.push(`Delegation auto-fanout: synthesized ${synthesizedRequests.length} delegate(s) to satisfy parallelism contract.`);
1627
+ }
1628
+ }
1629
+ }
1055
1630
  if (minDelegationsPreferred > 0 && parsedDelegation.requests.length < minDelegationsPreferred) {
1056
- const impossibleReason = output.match(/^\s*DELEGATION_IMPOSSIBLE\s*:\s*(.+)$/im)?.[1]?.trim() ?? "not provided";
1631
+ const impossibleMatch = output.match(/^\s*DELEGATION_IMPOSSIBLE\s*:\s*(.+)$/im);
1632
+ const impossibleReason = impossibleMatch?.[1]?.trim() ?? "not provided";
1633
+ if (strictDelegationContract && parsedDelegation.requests.length === 0 && !impossibleMatch) {
1634
+ throw new Error(`Delegation contract violated: expected >=${minDelegationsPreferred} delegates, got ${parsedDelegation.requests.length}. ` +
1635
+ `Provide nested <delegate_task> fan-out or an explicit "DELEGATION_IMPOSSIBLE: <reason>" line.`);
1636
+ }
1057
1637
  delegationWarnings.push(`Delegation fallback: kept single-agent execution (preferred >=${minDelegationsPreferred} delegates, got ${parsedDelegation.requests.length}). Reason: ${impossibleReason}.`);
1058
1638
  }
1059
1639
  output = parsedDelegation.cleanedOutput;
@@ -1109,6 +1689,13 @@ export function createTaskTool(cwd, runner, options) {
1109
1689
  }
1110
1690
  const causeLabel = cause ? ` [cause=${cause}]` : "";
1111
1691
  delegatedSections[index] = `#### ${index + 1}. ${request.description} (${formatDelegateTarget(request)})\nERROR${causeLabel}: ${message}`;
1692
+ void publishDelegateFinding({
1693
+ delegateLabel: String(index + 1),
1694
+ delegateDescription: request.description,
1695
+ delegateProfile: formatDelegateTarget(request),
1696
+ status: "failed",
1697
+ content: message,
1698
+ });
1112
1699
  emitProgress({
1113
1700
  kind: "subagent_progress",
1114
1701
  phase: "running",
@@ -1127,25 +1714,67 @@ export function createTaskTool(cwd, runner, options) {
1127
1714
  return { sections: [], warnings: [] };
1128
1715
  }
1129
1716
  const nestedWarnings = [];
1130
- const sections = [];
1131
- for (const [nestedIndex, nestedRequest] of requests.entries()) {
1717
+ const sectionsByIndex = new Array(requests.length);
1718
+ const statuses = Array.from({ length: requests.length }, () => "pending");
1719
+ const totalNested = requests.length;
1720
+ const normalizedDependsOn = requests.map((request, index) => {
1721
+ const current = index + 1;
1722
+ const raw = request.dependsOn ?? [];
1723
+ const unique = new Set();
1724
+ for (const dep of raw) {
1725
+ if (!Number.isInteger(dep) || dep <= 0 || dep > totalNested || dep === current) {
1726
+ nestedWarnings.push(`Nested delegated task ${lineage}${current} has invalid depends_on reference "${dep}" and it was ignored.`);
1727
+ continue;
1728
+ }
1729
+ unique.add(dep);
1730
+ }
1731
+ return Array.from(unique).sort((a, b) => a - b);
1732
+ });
1733
+ const statusOf = (index) => statuses[index] ?? "pending";
1734
+ const markNestedFailed = (nestedIndex, requestLabel, nestedRequest, nestedProfileLabel, message, cause) => {
1735
+ statuses[nestedIndex] = "failed";
1736
+ recordFailureCause(cause);
1737
+ delegatedTasks += 1;
1738
+ delegatedFailed += 1;
1739
+ sectionsByIndex[nestedIndex] =
1740
+ `###### ${requestLabel}. ${nestedRequest.description} (${nestedProfileLabel})\nERROR [cause=${cause}]: ${message}`;
1741
+ void publishDelegateFinding({
1742
+ delegateLabel: requestLabel,
1743
+ delegateDescription: nestedRequest.description,
1744
+ delegateProfile: nestedProfileLabel,
1745
+ status: "failed",
1746
+ content: message,
1747
+ });
1748
+ };
1749
+ const runNestedDelegate = async (nestedIndex) => {
1750
+ const nestedRequest = requests[nestedIndex];
1132
1751
  const requestLabel = `${lineage}${nestedIndex + 1}`;
1133
- const requestedNestedAgent = nestedRequest.agent?.trim() || undefined;
1134
- const nestedCustomSubagent = resolveCustom(requestedNestedAgent);
1752
+ statuses[nestedIndex] = "running";
1753
+ let requestedNestedAgent = nestedRequest.agent?.trim() || undefined;
1754
+ let nestedCustomSubagent = resolveCustom(requestedNestedAgent);
1755
+ if (!nestedCustomSubagent && !requestedNestedAgent) {
1756
+ const profileAsAgent = resolveCustom(nestedRequest.profile);
1757
+ if (profileAsAgent) {
1758
+ nestedCustomSubagent = profileAsAgent;
1759
+ requestedNestedAgent = profileAsAgent.name;
1760
+ }
1761
+ }
1135
1762
  if (requestedNestedAgent && !nestedCustomSubagent) {
1136
1763
  nestedWarnings.push(`Nested delegated task "${nestedRequest.description}" requested unknown agent "${requestedNestedAgent}". Falling back to profile "${nestedRequest.profile}".`);
1137
1764
  }
1138
- const nestedProfileRaw = nestedCustomSubagent?.profile ?? nestedRequest.profile;
1139
- const normalizedNestedProfile = nestedProfileRaw.trim().toLowerCase();
1140
- const nestedProfile = normalizedNestedProfile && toolsByProfile[normalizedNestedProfile]
1141
- ? normalizedNestedProfile
1142
- : "full";
1765
+ const nestedProfileCandidate = (nestedCustomSubagent?.profile ?? nestedRequest.profile).trim();
1766
+ const normalizedNestedProfile = nestedProfileCandidate.toLowerCase();
1767
+ if (!isValidProfileName(normalizedNestedProfile)) {
1768
+ markNestedFailed(nestedIndex, requestLabel, nestedRequest, nestedProfileCandidate || "unknown", `nested delegate skipped: unknown profile "${nestedProfileCandidate || nestedRequest.profile}"`, "logic_error");
1769
+ return;
1770
+ }
1771
+ const nestedProfile = normalizedNestedProfile;
1143
1772
  const nestedProfileLabel = nestedCustomSubagent?.name
1144
1773
  ? `${nestedCustomSubagent.name}/${nestedProfile}`
1145
1774
  : nestedProfile;
1146
1775
  let nestedTools = nestedCustomSubagent?.tools
1147
1776
  ? [...nestedCustomSubagent.tools]
1148
- : [...(toolsByProfile[nestedProfile] ?? toolsByProfile.explore)];
1777
+ : [...toolsByProfile[nestedProfile]];
1149
1778
  if (nestedCustomSubagent?.disallowedTools?.length) {
1150
1779
  const blocked = new Set(nestedCustomSubagent.disallowedTools);
1151
1780
  nestedTools = nestedTools.filter((tool) => !blocked.has(tool));
@@ -1158,19 +1787,15 @@ export function createTaskTool(cwd, runner, options) {
1158
1787
  ? path.resolve(parentCwd, nestedRequest.cwd)
1159
1788
  : nestedCustomSubagent?.cwd ?? parentCwd;
1160
1789
  if (!existsSync(requestedNestedCwd) || !statSync(requestedNestedCwd).isDirectory()) {
1161
- recordFailureCause("dependency_env");
1162
- delegatedFailed += 1;
1163
- delegatedTasks += 1;
1164
- sections.push(`###### ${requestLabel}. ${nestedRequest.description} (${nestedProfileLabel})\nERROR [cause=dependency_env]: nested delegate skipped: missing cwd`);
1165
- continue;
1790
+ markNestedFailed(nestedIndex, requestLabel, nestedRequest, nestedProfileLabel, "nested delegate skipped: missing cwd", "dependency_env");
1791
+ return;
1166
1792
  }
1167
1793
  let nestedReleaseLock;
1168
1794
  let nestedReleaseIsolation;
1169
1795
  let nestedCwd = requestedNestedCwd;
1170
1796
  try {
1171
1797
  if (writeCapableProfiles.has(nestedProfile) && nestedRequest.lockKey?.trim()) {
1172
- const lock = getOrCreateWriteLock(nestedRequest.lockKey.trim());
1173
- nestedReleaseLock = await lock.acquire();
1798
+ nestedReleaseLock = await acquireLocalWriteLock(nestedRequest.lockKey.trim());
1174
1799
  }
1175
1800
  if (nestedRequest.isolation === "worktree") {
1176
1801
  const isolated = provisionWorktree(cwd, requestedNestedCwd, `${runId}_nested_${requestLabel.replace(/\./g, "_")}`);
@@ -1179,7 +1804,17 @@ export function createTaskTool(cwd, runner, options) {
1179
1804
  }
1180
1805
  const nestedPromptWithInstructions = nestedRequest.prompt;
1181
1806
  const nestedSharedMemoryGuidance = buildSharedMemoryGuidance(sharedMemoryRunId, sharedMemoryTaskId);
1182
- const nestedPrompt = `${nestedPromptWithInstructions}\n\n${nestedSharedMemoryGuidance}`;
1807
+ const nestedCoordinationGuidance = buildDelegateCoordinationGuidance({
1808
+ taskId: sharedMemoryTaskId,
1809
+ delegateLabel: requestLabel,
1810
+ delegateDescription: nestedRequest.description,
1811
+ });
1812
+ const nestedPrompt = `${nestedPromptWithInstructions}\n\n${nestedSharedMemoryGuidance}\n\n${nestedCoordinationGuidance}`;
1813
+ await publishStreamClaims({
1814
+ owner: requestLabel,
1815
+ description: nestedRequest.description,
1816
+ promptText: nestedRequest.prompt,
1817
+ });
1183
1818
  const nestedModelOverride = nestedRequest.model?.trim() || nestedCustomSubagent?.model?.trim() || undefined;
1184
1819
  const nestedSharedMemoryContext = {
1185
1820
  rootCwd: cwd,
@@ -1212,6 +1847,7 @@ export function createTaskTool(cwd, runner, options) {
1212
1847
  const nestedStats = typeof nestedResult === "string" ? undefined : nestedResult.stats;
1213
1848
  delegatedTasks += 1;
1214
1849
  delegatedSucceeded += 1;
1850
+ statuses[nestedIndex] = "done";
1215
1851
  delegatedStats.toolCallsStarted += nestedStats?.toolCallsStarted ?? 0;
1216
1852
  delegatedStats.toolCallsCompleted += nestedStats?.toolCallsCompleted ?? 0;
1217
1853
  delegatedStats.assistantMessages += nestedStats?.assistantMessages ?? 0;
@@ -1226,37 +1862,112 @@ export function createTaskTool(cwd, runner, options) {
1226
1862
  nestedSection = `${nestedSection}\n\n##### Nested Delegated Subtasks\n\n${deeper.sections.join("\n\n")}`;
1227
1863
  }
1228
1864
  }
1229
- sections.push(nestedSection);
1865
+ await publishDelegateFinding({
1866
+ delegateLabel: requestLabel,
1867
+ delegateDescription: nestedRequest.description,
1868
+ delegateProfile: nestedProfileLabel,
1869
+ status: "done",
1870
+ content: nestedSection,
1871
+ });
1872
+ sectionsByIndex[nestedIndex] = nestedSection;
1230
1873
  }
1231
1874
  catch (error) {
1232
1875
  const message = error instanceof Error ? error.message : String(error);
1233
1876
  const cause = classifyFailureCause(message);
1234
- recordFailureCause(cause);
1235
- delegatedTasks += 1;
1236
- delegatedFailed += 1;
1237
- sections.push(`###### ${requestLabel}. ${nestedRequest.description} (${nestedProfileLabel})\nERROR [cause=${cause}]: ${message}`);
1877
+ markNestedFailed(nestedIndex, requestLabel, nestedRequest, nestedProfileLabel, message, cause);
1238
1878
  }
1239
1879
  finally {
1240
1880
  nestedReleaseIsolation?.();
1241
1881
  nestedReleaseLock?.();
1242
- cleanupWriteLock(nestedRequest.lockKey?.trim());
1243
1882
  }
1883
+ };
1884
+ const pendingIndices = new Set(Array.from({ length: totalNested }, (_v, i) => i));
1885
+ const runningNested = new Map();
1886
+ const maxNestedParallel = Math.max(1, Math.min(totalNested, effectiveMaxDelegateParallel));
1887
+ const resolveBlockedByFailedDependencies = () => {
1888
+ let changed = false;
1889
+ for (const nestedIndex of Array.from(pendingIndices)) {
1890
+ const deps = normalizedDependsOn[nestedIndex] ?? [];
1891
+ if (deps.length === 0)
1892
+ continue;
1893
+ const failedDep = deps.find((dep) => statusOf(dep - 1) === "failed");
1894
+ if (!failedDep)
1895
+ continue;
1896
+ pendingIndices.delete(nestedIndex);
1897
+ const nestedRequest = requests[nestedIndex];
1898
+ const requestLabel = `${lineage}${nestedIndex + 1}`;
1899
+ markNestedFailed(nestedIndex, requestLabel, nestedRequest, nestedRequest.profile, `nested delegate skipped: dependency ${lineage}${failedDep} failed`, "logic_error");
1900
+ changed = true;
1901
+ }
1902
+ return changed;
1903
+ };
1904
+ const launchReadyNested = () => {
1905
+ let launched = false;
1906
+ while (runningNested.size < maxNestedParallel) {
1907
+ let nextIndex;
1908
+ for (const nestedIndex of pendingIndices) {
1909
+ const deps = normalizedDependsOn[nestedIndex] ?? [];
1910
+ const allDone = deps.every((dep) => statusOf(dep - 1) === "done");
1911
+ if (allDone) {
1912
+ nextIndex = nestedIndex;
1913
+ break;
1914
+ }
1915
+ }
1916
+ if (nextIndex === undefined)
1917
+ break;
1918
+ pendingIndices.delete(nextIndex);
1919
+ const promise = runNestedDelegate(nextIndex).finally(() => {
1920
+ runningNested.delete(nextIndex);
1921
+ });
1922
+ runningNested.set(nextIndex, promise);
1923
+ launched = true;
1924
+ }
1925
+ return launched;
1926
+ };
1927
+ while (pendingIndices.size > 0 || runningNested.size > 0) {
1928
+ throwIfAborted();
1929
+ const changed = resolveBlockedByFailedDependencies();
1930
+ const launched = launchReadyNested();
1931
+ if (runningNested.size === 0) {
1932
+ if (pendingIndices.size > 0 && !changed && !launched) {
1933
+ for (const nestedIndex of Array.from(pendingIndices)) {
1934
+ pendingIndices.delete(nestedIndex);
1935
+ const nestedRequest = requests[nestedIndex];
1936
+ const deps = normalizedDependsOn[nestedIndex] ?? [];
1937
+ const requestLabel = `${lineage}${nestedIndex + 1}`;
1938
+ markNestedFailed(nestedIndex, requestLabel, nestedRequest, nestedRequest.profile, `nested delegate blocked: unresolved depends_on (${deps.join(", ") || "unknown"})`, "logic_error");
1939
+ }
1940
+ }
1941
+ break;
1942
+ }
1943
+ await Promise.race(Array.from(runningNested.values()));
1244
1944
  }
1945
+ const sections = sectionsByIndex.filter((section) => typeof section === "string" && section.trim().length > 0);
1245
1946
  return { sections, warnings: nestedWarnings };
1246
1947
  };
1247
1948
  const runDelegate = async (index) => {
1248
1949
  throwIfAborted();
1249
1950
  const request = parsedDelegation.requests[index];
1250
- const requestedChildAgent = request.agent?.trim() || undefined;
1251
- const childCustomSubagent = resolveCustom(requestedChildAgent);
1951
+ let requestedChildAgent = request.agent?.trim() || undefined;
1952
+ let childCustomSubagent = resolveCustom(requestedChildAgent);
1953
+ if (!childCustomSubagent && !requestedChildAgent) {
1954
+ const profileAsAgent = resolveCustom(request.profile);
1955
+ if (profileAsAgent) {
1956
+ childCustomSubagent = profileAsAgent;
1957
+ requestedChildAgent = profileAsAgent.name;
1958
+ }
1959
+ }
1252
1960
  if (requestedChildAgent && !childCustomSubagent) {
1253
1961
  delegationWarnings.push(`Delegated task "${request.description}" requested unknown agent "${requestedChildAgent}". Falling back to profile "${request.profile}".`);
1254
1962
  }
1255
- const childProfileRaw = childCustomSubagent?.profile ?? request.profile;
1256
- const normalizedChildProfile = childProfileRaw.trim().toLowerCase();
1257
- const childProfile = normalizedChildProfile && toolsByProfile[normalizedChildProfile]
1258
- ? normalizedChildProfile
1259
- : "full";
1963
+ const childProfileRaw = (childCustomSubagent?.profile ?? request.profile).trim();
1964
+ const normalizedChildProfile = childProfileRaw.toLowerCase();
1965
+ if (!isValidProfileName(normalizedChildProfile)) {
1966
+ recordFailureCause("logic_error");
1967
+ markDelegateFailed(index, `delegate ${index + 1}/${delegateTotal} skipped: unknown profile "${childProfileRaw || request.profile}"`, `Delegated task "${request.description}" requested unknown profile "${childProfileRaw || request.profile}".`, "logic_error");
1968
+ return;
1969
+ }
1970
+ const childProfile = normalizedChildProfile;
1260
1971
  const childProfileLabel = childCustomSubagent?.name
1261
1972
  ? `${childCustomSubagent.name}/${childProfile}`
1262
1973
  : childProfile;
@@ -1277,7 +1988,7 @@ export function createTaskTool(cwd, runner, options) {
1277
1988
  });
1278
1989
  let childTools = childCustomSubagent?.tools
1279
1990
  ? [...childCustomSubagent.tools]
1280
- : [...(toolsByProfile[childProfile] ?? toolsByProfile.explore)];
1991
+ : [...toolsByProfile[childProfile]];
1281
1992
  if (childCustomSubagent?.disallowedTools?.length) {
1282
1993
  const blocked = new Set(childCustomSubagent.disallowedTools);
1283
1994
  childTools = childTools.filter((tool) => !blocked.has(tool));
@@ -1306,8 +2017,7 @@ export function createTaskTool(cwd, runner, options) {
1306
2017
  try {
1307
2018
  throwIfAborted();
1308
2019
  if (writeCapableProfiles.has(childProfile) && explicitChildLock) {
1309
- const lock = getOrCreateWriteLock(explicitChildLock);
1310
- childReleaseLock = await lock.acquire();
2020
+ childReleaseLock = await acquireLocalWriteLock(explicitChildLock);
1311
2021
  throwIfAborted();
1312
2022
  }
1313
2023
  if (request.isolation === "worktree") {
@@ -1318,7 +2028,12 @@ export function createTaskTool(cwd, runner, options) {
1318
2028
  const delegateMeta = formatMetaCheckpoint(options?.getMetaMessages?.());
1319
2029
  const childPromptWithInstructions = request.prompt;
1320
2030
  const delegateSharedMemoryGuidance = buildSharedMemoryGuidance(sharedMemoryRunId, sharedMemoryTaskId);
1321
- const delegatePromptBase = `${childPromptWithInstructions}\n\n${delegateSharedMemoryGuidance}`;
2031
+ const delegateCoordinationGuidance = buildDelegateCoordinationGuidance({
2032
+ taskId: sharedMemoryTaskId,
2033
+ delegateLabel: String(index + 1),
2034
+ delegateDescription: request.description,
2035
+ });
2036
+ const delegatePromptBase = `${childPromptWithInstructions}\n\n${delegateSharedMemoryGuidance}\n\n${delegateCoordinationGuidance}`;
1322
2037
  const delegatePrompt = delegateMeta.section && delegateMeta.appliedCount > 0
1323
2038
  ? `${delegatePromptBase}\n\n${delegateMeta.section}`
1324
2039
  : delegatePromptBase;
@@ -1336,6 +2051,11 @@ export function createTaskTool(cwd, runner, options) {
1336
2051
  delegateItems,
1337
2052
  });
1338
2053
  }
2054
+ await publishStreamClaims({
2055
+ owner: String(index + 1),
2056
+ description: request.description,
2057
+ promptText: request.prompt,
2058
+ });
1339
2059
  const childModelOverride = request.model?.trim() || childCustomSubagent?.model?.trim() || undefined;
1340
2060
  const childSharedMemoryContext = {
1341
2061
  rootCwd: cwd,
@@ -1477,10 +2197,36 @@ export function createTaskTool(cwd, runner, options) {
1477
2197
  childOutput = await runChildPass(enforcedChildPrompt);
1478
2198
  parsedChildDelegation = parseDelegationRequests(childOutput, effectiveDelegationDepth > 1 ? effectiveMaxDelegations : 0);
1479
2199
  }
2200
+ if (childMinDelegationsPreferred > 0 &&
2201
+ parsedChildDelegation.requests.length === 0 &&
2202
+ strictDelegationContract) {
2203
+ const impossibleMatch = childOutput.match(/^\s*DELEGATION_IMPOSSIBLE\s*:\s*(.+)$/im);
2204
+ if (!impossibleMatch) {
2205
+ const synthesizedChildRequests = synthesizeDelegationRequests({
2206
+ description: request.description,
2207
+ prompt: request.prompt,
2208
+ baseProfile: childProfile,
2209
+ currentDelegates: parsedChildDelegation.requests.length,
2210
+ minDelegationsPreferred: childMinDelegationsPreferred,
2211
+ maxDelegations: effectiveMaxDelegations,
2212
+ availableCustomNames,
2213
+ });
2214
+ if (synthesizedChildRequests.length > 0) {
2215
+ parsedChildDelegation.requests.push(...synthesizedChildRequests);
2216
+ delegationWarnings.push(`Child ${index + 1}: delegation auto-fanout synthesized ${synthesizedChildRequests.length} nested delegate(s).`);
2217
+ }
2218
+ }
2219
+ }
1480
2220
  if (childMinDelegationsPreferred > 0 &&
1481
2221
  parsedChildDelegation.requests.length < childMinDelegationsPreferred) {
1482
- const impossibleReason = childOutput.match(/^\s*DELEGATION_IMPOSSIBLE\s*:\s*(.+)$/im)?.[1]?.trim() ??
1483
- "not provided";
2222
+ const impossibleMatch = childOutput.match(/^\s*DELEGATION_IMPOSSIBLE\s*:\s*(.+)$/im);
2223
+ const impossibleReason = impossibleMatch?.[1]?.trim() ?? "not provided";
2224
+ if (strictDelegationContract &&
2225
+ parsedChildDelegation.requests.length === 0 &&
2226
+ !impossibleMatch) {
2227
+ throw new Error(`Delegation contract violated for child ${index + 1}: expected >=${childMinDelegationsPreferred} nested delegates, got ${parsedChildDelegation.requests.length}. ` +
2228
+ `Provide nested <delegate_task> fan-out or "DELEGATION_IMPOSSIBLE: <reason>".`);
2229
+ }
1484
2230
  delegationWarnings.push(`Child ${index + 1}: delegation fallback (preferred >=${childMinDelegationsPreferred}, got ${parsedChildDelegation.requests.length}). Reason: ${impossibleReason}.`);
1485
2231
  }
1486
2232
  childOutput = parsedChildDelegation.cleanedOutput;
@@ -1506,6 +2252,13 @@ export function createTaskTool(cwd, runner, options) {
1506
2252
  : normalizedChildOutput;
1507
2253
  delegatedSections[index] =
1508
2254
  `#### ${index + 1}. ${request.description} (${childProfileLabel})\n${childOutputExcerpt}${nestedSection}`;
2255
+ await publishDelegateFinding({
2256
+ delegateLabel: String(index + 1),
2257
+ delegateDescription: request.description,
2258
+ delegateProfile: childProfileLabel,
2259
+ status: "done",
2260
+ content: `${childOutputExcerpt}${nestedSection}`,
2261
+ });
1509
2262
  emitProgress({
1510
2263
  kind: "subagent_progress",
1511
2264
  phase: "running",
@@ -1535,7 +2288,6 @@ export function createTaskTool(cwd, runner, options) {
1535
2288
  finally {
1536
2289
  childReleaseIsolation?.();
1537
2290
  childReleaseLock?.();
1538
- cleanupWriteLock(explicitChildLock);
1539
2291
  }
1540
2292
  };
1541
2293
  const resolveBlockedByFailedDependencies = () => {
@@ -1667,11 +2419,205 @@ export function createTaskTool(cwd, runner, options) {
1667
2419
  }
1668
2420
  const normalizedOutput = output.trim().length > 0 ? output.trim() : "(Subagent completed with no output)";
1669
2421
  const finalSections = [normalizedOutput];
2422
+ const delegatedBlocks = delegatedSections.filter((section) => typeof section === "string" && section.trim().length > 0);
2423
+ let duplicateDelegatedOutputs = 0;
1670
2424
  if (delegatedTasks > 0) {
1671
2425
  const header = `### Delegated Subtasks (${delegatedSucceeded}/${delegatedTasks} done)`;
1672
- const delegatedBlocks = delegatedSections.filter((section) => typeof section === "string" && section.trim().length > 0);
2426
+ if (delegatedBlocks.length > 1) {
2427
+ const duplicateReport = detectDuplicateDelegatedSections(delegatedBlocks);
2428
+ duplicateDelegatedOutputs = duplicateReport.duplicates;
2429
+ if (duplicateReport.duplicates > 0) {
2430
+ const duplicateHints = duplicateReport.duplicatePairs
2431
+ .slice(0, 5)
2432
+ .map((pair) => `${pair.duplicate}->${pair.original}`)
2433
+ .join(", ");
2434
+ delegationWarnings.push(`Delegation quality: detected ${duplicateReport.duplicates} near-duplicate delegated output(s) (${duplicateHints}). Consider stricter stream partitioning or stronger shared-memory coordination keys.`);
2435
+ }
2436
+ }
1673
2437
  finalSections.push([header, ...delegatedBlocks].join("\n\n"));
1674
2438
  }
2439
+ let sharedMemorySummaryKey;
2440
+ if (orchestrationRunId && orchestrationTaskId) {
2441
+ const summaryExcerpt = normalizedOutput.length > 1200
2442
+ ? `${normalizedOutput.slice(0, 1197).trimEnd()}...`
2443
+ : normalizedOutput;
2444
+ const summaryPayload = JSON.stringify({
2445
+ taskId: orchestrationTaskId,
2446
+ description,
2447
+ profile: effectiveProfile,
2448
+ delegated: {
2449
+ total: delegatedTasks,
2450
+ succeeded: delegatedSucceeded,
2451
+ failed: delegatedFailed,
2452
+ duplicatesDetected: duplicateDelegatedOutputs,
2453
+ },
2454
+ retrospective: {
2455
+ attempts: retrospectiveAttempts,
2456
+ recovered: retrospectiveRecovered,
2457
+ failureCauses: failureCauses,
2458
+ },
2459
+ summary: summaryExcerpt,
2460
+ });
2461
+ try {
2462
+ const summaryWrite = await writeSharedMemory({
2463
+ rootCwd: cwd,
2464
+ runId: sharedMemoryRunId,
2465
+ taskId: sharedMemoryTaskId,
2466
+ profile: effectiveProfile,
2467
+ }, {
2468
+ key: `results/${orchestrationTaskId}`,
2469
+ value: summaryPayload,
2470
+ scope: "run",
2471
+ mode: "set",
2472
+ }, _signal);
2473
+ sharedMemorySummaryKey = summaryWrite.key;
2474
+ }
2475
+ catch (error) {
2476
+ const message = error instanceof Error ? error.message : String(error);
2477
+ delegationWarnings.push(`Shared memory summary write skipped: ${message}`);
2478
+ }
2479
+ }
2480
+ let coordinationSummary;
2481
+ let claimSummary;
2482
+ let sharedFindingsSnapshot;
2483
+ if (delegatedTasks > 0 || (orchestrationRunId && orchestrationTaskId)) {
2484
+ try {
2485
+ const usage = await summarizeSharedMemoryUsage({
2486
+ rootCwd: cwd,
2487
+ runId: sharedMemoryRunId,
2488
+ taskId: sharedMemoryTaskId,
2489
+ }, _signal);
2490
+ if (delegatedTasks > 1 && usage.currentTaskDelegateWrites === 0) {
2491
+ delegationWarnings.push("No shared_memory writes detected from delegates in this task. Cross-stream coordination may be weak; use stable keys (findings/<stream>, risks/<stream>, plan/<stream>).");
2492
+ }
2493
+ coordinationSummary = {
2494
+ sharedMemoryWrites: usage.totalWrites,
2495
+ currentTaskWrites: usage.currentTaskWrites,
2496
+ currentTaskDelegateWrites: usage.currentTaskDelegateWrites,
2497
+ runScopeWrites: usage.runScopeWrites,
2498
+ taskScopeWrites: usage.taskScopeWrites,
2499
+ duplicatesDetected: duplicateDelegatedOutputs,
2500
+ claimKeysMatched: 0,
2501
+ claimCollisions: 0,
2502
+ };
2503
+ }
2504
+ catch {
2505
+ // shared-memory summary is advisory; never fail task completion on analytics path.
2506
+ }
2507
+ }
2508
+ if (delegatedTasks > 0) {
2509
+ try {
2510
+ const claims = await readSharedMemory({
2511
+ rootCwd: cwd,
2512
+ runId: sharedMemoryRunId,
2513
+ taskId: sharedMemoryTaskId,
2514
+ }, {
2515
+ scope: "run",
2516
+ prefix: "claims/",
2517
+ includeValues: true,
2518
+ limit: Math.max(40, delegatedTasks * 8),
2519
+ }, _signal);
2520
+ const collisions = [];
2521
+ for (const item of claims.items) {
2522
+ if (!item.value)
2523
+ continue;
2524
+ try {
2525
+ const parsed = JSON.parse(item.value);
2526
+ if (!Array.isArray(parsed.owners))
2527
+ continue;
2528
+ const owners = Array.from(new Set(parsed.owners
2529
+ .filter((value) => typeof value === "string" && value.trim().length > 0)
2530
+ .map((value) => value.trim())));
2531
+ if (owners.length > 1) {
2532
+ collisions.push({ key: item.key, owners: owners.slice(0, 8) });
2533
+ }
2534
+ }
2535
+ catch {
2536
+ // ignore malformed claim entries and continue best-effort aggregation
2537
+ }
2538
+ }
2539
+ claimSummary = {
2540
+ keysMatched: claims.totalMatched,
2541
+ collisions: collisions.length,
2542
+ examples: collisions.slice(0, 6).map((item) => `${item.key}: ${item.owners.join(", ")}`),
2543
+ };
2544
+ if (coordinationSummary) {
2545
+ coordinationSummary.claimKeysMatched = claimSummary.keysMatched;
2546
+ coordinationSummary.claimCollisions = claimSummary.collisions;
2547
+ }
2548
+ }
2549
+ catch {
2550
+ // advisory only
2551
+ }
2552
+ }
2553
+ if (delegatedTasks > 0) {
2554
+ try {
2555
+ const findingsPrefix = `findings/${toSharedMemoryKeySegment(sharedMemoryTaskId, "task")}/`;
2556
+ const findings = await readSharedMemory({
2557
+ rootCwd: cwd,
2558
+ runId: sharedMemoryRunId,
2559
+ taskId: sharedMemoryTaskId,
2560
+ }, {
2561
+ scope: "run",
2562
+ prefix: findingsPrefix,
2563
+ includeValues: true,
2564
+ limit: Math.max(20, delegatedTasks * 4),
2565
+ }, _signal);
2566
+ const examples = findings.items.slice(0, 6).map((item) => {
2567
+ let excerpt = "";
2568
+ if (item.value) {
2569
+ try {
2570
+ const parsed = JSON.parse(item.value);
2571
+ if (typeof parsed.summary === "string" && parsed.summary.trim().length > 0) {
2572
+ excerpt = parsed.summary.trim().replace(/\s+/g, " ").slice(0, 120);
2573
+ }
2574
+ }
2575
+ catch {
2576
+ excerpt = item.value.trim().replace(/\s+/g, " ").slice(0, 120);
2577
+ }
2578
+ }
2579
+ return excerpt.length > 0 ? `${item.key}: ${excerpt}` : item.key;
2580
+ });
2581
+ sharedFindingsSnapshot = {
2582
+ keysMatched: findings.totalMatched,
2583
+ examples,
2584
+ };
2585
+ }
2586
+ catch {
2587
+ // advisory only
2588
+ }
2589
+ }
2590
+ if (coordinationSummary && delegatedTasks > 0) {
2591
+ finalSections.push([
2592
+ "### Orchestration Summary",
2593
+ `- delegated: ${delegatedSucceeded}/${delegatedTasks} succeeded (${delegatedFailed} failed)`,
2594
+ `- duplicate_delegated_outputs: ${coordinationSummary.duplicatesDetected}`,
2595
+ `- shared_memory_writes_total: ${coordinationSummary.sharedMemoryWrites}`,
2596
+ `- shared_memory_writes_current_task: ${coordinationSummary.currentTaskWrites}`,
2597
+ `- shared_memory_delegate_writes_current_task: ${coordinationSummary.currentTaskDelegateWrites}`,
2598
+ `- shared_memory_scope_distribution: run=${coordinationSummary.runScopeWrites}, task=${coordinationSummary.taskScopeWrites}`,
2599
+ `- claims_keys_matched: ${coordinationSummary.claimKeysMatched}`,
2600
+ `- claims_collisions: ${coordinationSummary.claimCollisions}`,
2601
+ sharedMemorySummaryKey ? `- shared_memory_summary_key: ${sharedMemorySummaryKey}` : undefined,
2602
+ ]
2603
+ .filter((line) => typeof line === "string" && line.trim().length > 0)
2604
+ .join("\n"));
2605
+ }
2606
+ if (claimSummary && delegatedTasks > 0) {
2607
+ finalSections.push([
2608
+ "### Claim Overlap Snapshot",
2609
+ `- matched_keys: ${claimSummary.keysMatched}`,
2610
+ `- collisions: ${claimSummary.collisions}`,
2611
+ ...claimSummary.examples.map((line) => `- ${line}`),
2612
+ ].join("\n"));
2613
+ }
2614
+ if (sharedFindingsSnapshot && delegatedTasks > 0) {
2615
+ finalSections.push([
2616
+ "### Shared Findings Snapshot",
2617
+ `- matched_keys: ${sharedFindingsSnapshot.keysMatched}`,
2618
+ ...sharedFindingsSnapshot.examples.map((line) => `- ${line}`),
2619
+ ].join("\n"));
2620
+ }
1675
2621
  if (delegationWarnings.length > 0) {
1676
2622
  finalSections.push(`### Delegation Notes\n${delegationWarnings.map((w) => `- ${w}`).join("\n")}`);
1677
2623
  }
@@ -1753,6 +2699,8 @@ export function createTaskTool(cwd, runner, options) {
1753
2699
  retrospectiveAttempts: retrospectiveAttempts > 0 ? retrospectiveAttempts : undefined,
1754
2700
  retrospectiveRecovered: retrospectiveRecovered > 0 ? retrospectiveRecovered : undefined,
1755
2701
  failureCauses: hasFailureCauses ? { ...failureCauses } : undefined,
2702
+ coordination: coordinationSummary,
2703
+ sharedMemorySummaryKey,
1756
2704
  };
1757
2705
  updateTrackedTaskStatus("done");
1758
2706
  return { text, details };
@@ -1760,7 +2708,6 @@ export function createTaskTool(cwd, runner, options) {
1760
2708
  finally {
1761
2709
  releaseIsolation?.();
1762
2710
  releaseWriteLock?.();
1763
- cleanupWriteLock(explicitRootLockKey);
1764
2711
  releaseSlot?.();
1765
2712
  releaseRunSlot?.();
1766
2713
  if (orchestrationRunId) {