iosm-cli 0.2.5 → 0.2.7

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 (59) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +460 -304
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +4 -1
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/core/agent-profiles.d.ts.map +1 -1
  7. package/dist/core/agent-profiles.js +4 -2
  8. package/dist/core/agent-profiles.js.map +1 -1
  9. package/dist/core/agent-session.d.ts.map +1 -1
  10. package/dist/core/agent-session.js +14 -1
  11. package/dist/core/agent-session.js.map +1 -1
  12. package/dist/core/sdk.d.ts +2 -2
  13. package/dist/core/sdk.d.ts.map +1 -1
  14. package/dist/core/sdk.js +3 -3
  15. package/dist/core/sdk.js.map +1 -1
  16. package/dist/core/shadow-guard.js +1 -1
  17. package/dist/core/shadow-guard.js.map +1 -1
  18. package/dist/core/shared-memory.d.ts +14 -0
  19. package/dist/core/shared-memory.d.ts.map +1 -1
  20. package/dist/core/shared-memory.js +63 -0
  21. package/dist/core/shared-memory.js.map +1 -1
  22. package/dist/core/system-prompt.d.ts.map +1 -1
  23. package/dist/core/system-prompt.js +17 -2
  24. package/dist/core/system-prompt.js.map +1 -1
  25. package/dist/core/tools/fetch.d.ts +56 -0
  26. package/dist/core/tools/fetch.d.ts.map +1 -0
  27. package/dist/core/tools/fetch.js +272 -0
  28. package/dist/core/tools/fetch.js.map +1 -0
  29. package/dist/core/tools/fs-ops.d.ts +54 -0
  30. package/dist/core/tools/fs-ops.d.ts.map +1 -0
  31. package/dist/core/tools/fs-ops.js +206 -0
  32. package/dist/core/tools/fs-ops.js.map +1 -0
  33. package/dist/core/tools/git-read.d.ts +60 -0
  34. package/dist/core/tools/git-read.d.ts.map +1 -0
  35. package/dist/core/tools/git-read.js +267 -0
  36. package/dist/core/tools/git-read.js.map +1 -0
  37. package/dist/core/tools/index.d.ts +44 -0
  38. package/dist/core/tools/index.d.ts.map +1 -1
  39. package/dist/core/tools/index.js +22 -0
  40. package/dist/core/tools/index.js.map +1 -1
  41. package/dist/core/tools/shared-memory.d.ts.map +1 -1
  42. package/dist/core/tools/shared-memory.js +45 -5
  43. package/dist/core/tools/shared-memory.js.map +1 -1
  44. package/dist/core/tools/task.d.ts +12 -1
  45. package/dist/core/tools/task.d.ts.map +1 -1
  46. package/dist/core/tools/task.js +565 -9
  47. package/dist/core/tools/task.js.map +1 -1
  48. package/dist/index.d.ts +1 -1
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +1 -1
  51. package/dist/index.js.map +1 -1
  52. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  53. package/dist/modes/interactive/interactive-mode.js +185 -26
  54. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  55. package/docs/cli-reference.md +4 -1
  56. package/docs/configuration.md +2 -2
  57. package/docs/interactive-mode.md +2 -2
  58. package/docs/rpc-json-sdk.md +1 -1
  59. package/package.json +1 -1
@@ -6,6 +6,7 @@ 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
8
  import { AGENT_PROFILES, isReadOnlyProfileName, isValidProfileName, } from "../agent-profiles.js";
9
+ import { readSharedMemory, summarizeSharedMemoryUsage, writeSharedMemory, } from "../shared-memory.js";
9
10
  const taskSchema = Type.Object({
10
11
  description: Type.Optional(Type.String({
11
12
  description: "Optional short 3-5 word description of what the subagent will do. If omitted, it is derived from prompt.",
@@ -62,13 +63,13 @@ const systemPromptByProfile = {
62
63
  explore: "You are a fast read-only codebase explorer. Answer concisely. Never write or edit files.",
63
64
  plan: "You are a technical architect. Analyze the codebase and produce a clear implementation plan. Do not write or edit files.",
64
65
  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.",
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.",
66
67
  iosm_analyst: "You are an IOSM metrics analyst. Analyze .iosm/ artifacts and codebase metrics. Be precise and evidence-based.",
67
68
  iosm_verifier: "You are an IOSM verifier. Validate checks and update only required IOSM artifacts with deterministic reasoning.",
68
69
  cycle_planner: "You are an IOSM cycle planner. Propose and align cycle goals with measurable outcomes and concrete risks.",
69
70
  full: "You are a software engineering agent. Execute the task end-to-end.",
70
71
  };
71
- const writeCapableTools = new Set(["bash", "edit", "write"]);
72
+ const writeCapableTools = new Set(["bash", "edit", "write", "fs_ops"]);
72
73
  const backgroundUnsafeTools = new Set(writeCapableTools);
73
74
  const writeCapableProfiles = new Set(Object.keys(toolsByProfile).filter((profileName) => toolsByProfile[profileName].some((tool) => writeCapableTools.has(tool))));
74
75
  const backgroundSafeProfiles = Object.keys(toolsByProfile).filter((profileName) => toolsByProfile[profileName].every((tool) => !backgroundUnsafeTools.has(tool)));
@@ -360,6 +361,48 @@ function truncateForDelegationContext(text, maxChars = 2200) {
360
361
  return normalized;
361
362
  return `${normalized.slice(0, Math.max(100, maxChars - 3)).trimEnd()}...`;
362
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
+ }
363
406
  function extractDelegationWorkstreams(text, maxItems) {
364
407
  if (maxItems <= 0)
365
408
  return [];
@@ -453,6 +496,75 @@ function buildAutoDelegationPrompt(input) {
453
496
  objective,
454
497
  ].join("\n"));
455
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
+ }
456
568
  function synthesizeDelegationRequests(input) {
457
569
  const desiredTotal = Math.max(0, Math.min(input.maxDelegations, input.minDelegationsPreferred));
458
570
  const missing = Math.max(0, desiredTotal - input.currentDelegates);
@@ -479,9 +591,13 @@ function synthesizeDelegationRequests(input) {
479
591
  while (titles.length < missing) {
480
592
  titles.push(`Independent workstream ${titles.length + 1}`);
481
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);
482
599
  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) => ({
600
+ return uniqueTitles.map((streamTitle, index) => ({
485
601
  description: `Auto: ${streamTitle}`,
486
602
  profile: defaultProfile,
487
603
  agent: pickAutoDelegateAgent(streamTitle, input.availableCustomNames),
@@ -490,10 +606,10 @@ function synthesizeDelegationRequests(input) {
490
606
  rootDescription: input.description,
491
607
  rootPrompt: input.prompt,
492
608
  ordinal: input.currentDelegates + index + 1,
493
- total: input.currentDelegates + titles.length,
609
+ total: input.currentDelegates + uniqueTitles.length,
494
610
  }),
495
611
  cwd: undefined,
496
- lockKey: synthesizedLockKey,
612
+ lockKey: writeCapableProfiles.has(defaultProfile) ? deriveAutoSynthLockKey(streamTitle, index + 1) : undefined,
497
613
  model: undefined,
498
614
  isolation: undefined,
499
615
  dependsOn: undefined,
@@ -523,6 +639,71 @@ function buildSharedMemoryGuidance(runId, taskId) {
523
639
  "[/SHARED_MEMORY]",
524
640
  ].join("\n");
525
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
+ }
526
707
  function parseDelegationRequests(output, maxRequests) {
527
708
  const requests = [];
528
709
  const warnings = [];
@@ -1166,6 +1347,131 @@ export function createTaskTool(cwd, runner, options) {
1166
1347
  taskId: sharedMemoryTaskId,
1167
1348
  profile: effectiveProfile,
1168
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
+ };
1169
1475
  try {
1170
1476
  const runRootPass = async (runPrompt) => {
1171
1477
  let emptyAttempt = 0;
@@ -1266,6 +1572,12 @@ export function createTaskTool(cwd, runner, options) {
1266
1572
  activeTool: undefined,
1267
1573
  });
1268
1574
  }
1575
+ await publishTaskCoordinationPlan();
1576
+ await publishStreamClaims({
1577
+ owner: "root",
1578
+ description,
1579
+ promptText: prompt,
1580
+ });
1269
1581
  const firstPass = await runRootPass(rootPrompt);
1270
1582
  output = firstPass.output;
1271
1583
  subagentSessionId = firstPass.sessionId;
@@ -1377,6 +1689,13 @@ export function createTaskTool(cwd, runner, options) {
1377
1689
  }
1378
1690
  const causeLabel = cause ? ` [cause=${cause}]` : "";
1379
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
+ });
1380
1699
  emitProgress({
1381
1700
  kind: "subagent_progress",
1382
1701
  phase: "running",
@@ -1419,6 +1738,13 @@ export function createTaskTool(cwd, runner, options) {
1419
1738
  delegatedFailed += 1;
1420
1739
  sectionsByIndex[nestedIndex] =
1421
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
+ });
1422
1748
  };
1423
1749
  const runNestedDelegate = async (nestedIndex) => {
1424
1750
  const nestedRequest = requests[nestedIndex];
@@ -1478,7 +1804,17 @@ export function createTaskTool(cwd, runner, options) {
1478
1804
  }
1479
1805
  const nestedPromptWithInstructions = nestedRequest.prompt;
1480
1806
  const nestedSharedMemoryGuidance = buildSharedMemoryGuidance(sharedMemoryRunId, sharedMemoryTaskId);
1481
- 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
+ });
1482
1818
  const nestedModelOverride = nestedRequest.model?.trim() || nestedCustomSubagent?.model?.trim() || undefined;
1483
1819
  const nestedSharedMemoryContext = {
1484
1820
  rootCwd: cwd,
@@ -1526,6 +1862,13 @@ export function createTaskTool(cwd, runner, options) {
1526
1862
  nestedSection = `${nestedSection}\n\n##### Nested Delegated Subtasks\n\n${deeper.sections.join("\n\n")}`;
1527
1863
  }
1528
1864
  }
1865
+ await publishDelegateFinding({
1866
+ delegateLabel: requestLabel,
1867
+ delegateDescription: nestedRequest.description,
1868
+ delegateProfile: nestedProfileLabel,
1869
+ status: "done",
1870
+ content: nestedSection,
1871
+ });
1529
1872
  sectionsByIndex[nestedIndex] = nestedSection;
1530
1873
  }
1531
1874
  catch (error) {
@@ -1685,7 +2028,12 @@ export function createTaskTool(cwd, runner, options) {
1685
2028
  const delegateMeta = formatMetaCheckpoint(options?.getMetaMessages?.());
1686
2029
  const childPromptWithInstructions = request.prompt;
1687
2030
  const delegateSharedMemoryGuidance = buildSharedMemoryGuidance(sharedMemoryRunId, sharedMemoryTaskId);
1688
- 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}`;
1689
2037
  const delegatePrompt = delegateMeta.section && delegateMeta.appliedCount > 0
1690
2038
  ? `${delegatePromptBase}\n\n${delegateMeta.section}`
1691
2039
  : delegatePromptBase;
@@ -1703,6 +2051,11 @@ export function createTaskTool(cwd, runner, options) {
1703
2051
  delegateItems,
1704
2052
  });
1705
2053
  }
2054
+ await publishStreamClaims({
2055
+ owner: String(index + 1),
2056
+ description: request.description,
2057
+ promptText: request.prompt,
2058
+ });
1706
2059
  const childModelOverride = request.model?.trim() || childCustomSubagent?.model?.trim() || undefined;
1707
2060
  const childSharedMemoryContext = {
1708
2061
  rootCwd: cwd,
@@ -1899,6 +2252,13 @@ export function createTaskTool(cwd, runner, options) {
1899
2252
  : normalizedChildOutput;
1900
2253
  delegatedSections[index] =
1901
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
+ });
1902
2262
  emitProgress({
1903
2263
  kind: "subagent_progress",
1904
2264
  phase: "running",
@@ -2059,11 +2419,205 @@ export function createTaskTool(cwd, runner, options) {
2059
2419
  }
2060
2420
  const normalizedOutput = output.trim().length > 0 ? output.trim() : "(Subagent completed with no output)";
2061
2421
  const finalSections = [normalizedOutput];
2422
+ const delegatedBlocks = delegatedSections.filter((section) => typeof section === "string" && section.trim().length > 0);
2423
+ let duplicateDelegatedOutputs = 0;
2062
2424
  if (delegatedTasks > 0) {
2063
2425
  const header = `### Delegated Subtasks (${delegatedSucceeded}/${delegatedTasks} done)`;
2064
- 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
+ }
2065
2437
  finalSections.push([header, ...delegatedBlocks].join("\n\n"));
2066
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
+ }
2067
2621
  if (delegationWarnings.length > 0) {
2068
2622
  finalSections.push(`### Delegation Notes\n${delegationWarnings.map((w) => `- ${w}`).join("\n")}`);
2069
2623
  }
@@ -2145,6 +2699,8 @@ export function createTaskTool(cwd, runner, options) {
2145
2699
  retrospectiveAttempts: retrospectiveAttempts > 0 ? retrospectiveAttempts : undefined,
2146
2700
  retrospectiveRecovered: retrospectiveRecovered > 0 ? retrospectiveRecovered : undefined,
2147
2701
  failureCauses: hasFailureCauses ? { ...failureCauses } : undefined,
2702
+ coordination: coordinationSummary,
2703
+ sharedMemorySummaryKey,
2148
2704
  };
2149
2705
  updateTrackedTaskStatus("done");
2150
2706
  return { text, details };