@chllming/wave-orchestration 0.6.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/CHANGELOG.md +64 -1
  2. package/README.md +44 -8
  3. package/docs/agents/wave-orchestrator-role.md +50 -0
  4. package/docs/agents/wave-planner-role.md +39 -0
  5. package/docs/context7/bundles.json +9 -0
  6. package/docs/context7/planner-agent/README.md +25 -0
  7. package/docs/context7/planner-agent/manifest.json +83 -0
  8. package/docs/context7/planner-agent/papers/cooperbench-why-coding-agents-cannot-be-your-teammates-yet.md +3283 -0
  9. package/docs/context7/planner-agent/papers/dova-deliberation-first-multi-agent-orchestration-for-autonomous-research-automation.md +1699 -0
  10. package/docs/context7/planner-agent/papers/dpbench-large-language-models-struggle-with-simultaneous-coordination.md +2251 -0
  11. package/docs/context7/planner-agent/papers/incremental-planning-to-control-a-blackboard-based-problem-solver.md +1729 -0
  12. package/docs/context7/planner-agent/papers/silo-bench-a-scalable-environment-for-evaluating-distributed-coordination-in-multi-agent-llm-systems.md +3747 -0
  13. package/docs/context7/planner-agent/papers/todoevolve-learning-to-architect-agent-planning-systems.md +1675 -0
  14. package/docs/context7/planner-agent/papers/verified-multi-agent-orchestration-a-plan-execute-verify-replan-framework-for-complex-query-resolution.md +1173 -0
  15. package/docs/context7/planner-agent/papers/why-do-multi-agent-llm-systems-fail.md +5211 -0
  16. package/docs/context7/planner-agent/topics/planning-and-orchestration.md +24 -0
  17. package/docs/evals/README.md +96 -1
  18. package/docs/evals/arm-templates/README.md +13 -0
  19. package/docs/evals/arm-templates/full-wave.json +15 -0
  20. package/docs/evals/arm-templates/single-agent.json +15 -0
  21. package/docs/evals/benchmark-catalog.json +7 -0
  22. package/docs/evals/cases/README.md +47 -0
  23. package/docs/evals/cases/wave-blackboard-inbox-targeting.json +73 -0
  24. package/docs/evals/cases/wave-contradiction-conflict.json +104 -0
  25. package/docs/evals/cases/wave-expert-routing-preservation.json +69 -0
  26. package/docs/evals/cases/wave-hidden-profile-private-evidence.json +81 -0
  27. package/docs/evals/cases/wave-premature-closure-guard.json +71 -0
  28. package/docs/evals/cases/wave-silo-cross-agent-state.json +77 -0
  29. package/docs/evals/cases/wave-simultaneous-lockstep.json +92 -0
  30. package/docs/evals/cooperbench/real-world-mitigation.md +341 -0
  31. package/docs/evals/external-benchmarks.json +85 -0
  32. package/docs/evals/external-command-config.sample.json +9 -0
  33. package/docs/evals/external-command-config.swe-bench-pro.json +8 -0
  34. package/docs/evals/pilots/README.md +47 -0
  35. package/docs/evals/pilots/swe-bench-pro-public-full-wave-review-10.json +64 -0
  36. package/docs/evals/pilots/swe-bench-pro-public-pilot.json +111 -0
  37. package/docs/evals/wave-benchmark-program.md +302 -0
  38. package/docs/guides/planner.md +48 -11
  39. package/docs/plans/context7-wave-orchestrator.md +20 -0
  40. package/docs/plans/current-state.md +9 -1
  41. package/docs/plans/examples/wave-benchmark-improvement.md +108 -0
  42. package/docs/plans/examples/wave-example-live-proof.md +1 -1
  43. package/docs/plans/examples/wave-example-rollout-fidelity.md +340 -0
  44. package/docs/plans/wave-orchestrator.md +73 -11
  45. package/docs/plans/waves/reviews/wave-1-benchmark-operator.md +118 -0
  46. package/docs/reference/coordination-and-closure.md +436 -0
  47. package/docs/reference/live-proof-waves.md +25 -3
  48. package/docs/reference/npmjs-trusted-publishing.md +3 -3
  49. package/docs/reference/proof-metrics.md +90 -0
  50. package/docs/reference/runtime-config/README.md +61 -0
  51. package/docs/reference/sample-waves.md +29 -18
  52. package/docs/reference/wave-control.md +164 -0
  53. package/docs/reference/wave-planning-lessons.md +131 -0
  54. package/package.json +5 -4
  55. package/releases/manifest.json +33 -0
  56. package/scripts/research/agent-context-archive.mjs +18 -0
  57. package/scripts/research/manifests/agent-context-expanded-2026-03-22.mjs +17 -0
  58. package/scripts/research/sync-planner-context7-bundle.mjs +133 -0
  59. package/scripts/wave-autonomous.mjs +2 -4
  60. package/scripts/wave-orchestrator/adhoc.mjs +32 -11
  61. package/scripts/wave-orchestrator/artifact-schemas.mjs +232 -0
  62. package/scripts/wave-orchestrator/autonomous.mjs +27 -6
  63. package/scripts/wave-orchestrator/benchmark-cases.mjs +374 -0
  64. package/scripts/wave-orchestrator/benchmark-external.mjs +1384 -0
  65. package/scripts/wave-orchestrator/benchmark.mjs +972 -0
  66. package/scripts/wave-orchestrator/clarification-triage.mjs +78 -12
  67. package/scripts/wave-orchestrator/config.mjs +175 -0
  68. package/scripts/wave-orchestrator/control-cli.mjs +1123 -0
  69. package/scripts/wave-orchestrator/control-plane.mjs +697 -0
  70. package/scripts/wave-orchestrator/coord-cli.mjs +360 -2
  71. package/scripts/wave-orchestrator/coordination-store.mjs +211 -9
  72. package/scripts/wave-orchestrator/coordination.mjs +84 -0
  73. package/scripts/wave-orchestrator/dashboard-renderer.mjs +38 -3
  74. package/scripts/wave-orchestrator/dashboard-state.mjs +22 -0
  75. package/scripts/wave-orchestrator/evals.mjs +23 -0
  76. package/scripts/wave-orchestrator/executors.mjs +3 -2
  77. package/scripts/wave-orchestrator/feedback.mjs +55 -0
  78. package/scripts/wave-orchestrator/install.mjs +253 -26
  79. package/scripts/wave-orchestrator/launcher-closure.mjs +4 -1
  80. package/scripts/wave-orchestrator/launcher-runtime.mjs +24 -21
  81. package/scripts/wave-orchestrator/launcher.mjs +800 -35
  82. package/scripts/wave-orchestrator/package-update-notice.mjs +230 -0
  83. package/scripts/wave-orchestrator/package-version.mjs +32 -0
  84. package/scripts/wave-orchestrator/planner-context.mjs +75 -0
  85. package/scripts/wave-orchestrator/planner.mjs +2270 -136
  86. package/scripts/wave-orchestrator/proof-cli.mjs +195 -0
  87. package/scripts/wave-orchestrator/proof-registry.mjs +317 -0
  88. package/scripts/wave-orchestrator/replay.mjs +10 -4
  89. package/scripts/wave-orchestrator/retry-cli.mjs +184 -0
  90. package/scripts/wave-orchestrator/retry-control.mjs +225 -0
  91. package/scripts/wave-orchestrator/shared.mjs +26 -0
  92. package/scripts/wave-orchestrator/swe-bench-pro-task.mjs +1004 -0
  93. package/scripts/wave-orchestrator/traces.mjs +157 -2
  94. package/scripts/wave-orchestrator/wave-control-client.mjs +532 -0
  95. package/scripts/wave-orchestrator/wave-control-schema.mjs +309 -0
  96. package/scripts/wave-orchestrator/wave-files.mjs +17 -5
  97. package/scripts/wave.mjs +39 -2
  98. package/skills/repo-coding-rules/SKILL.md +1 -0
  99. package/skills/role-cont-eval/SKILL.md +1 -0
  100. package/skills/role-cont-qa/SKILL.md +13 -6
  101. package/skills/role-deploy/SKILL.md +1 -0
  102. package/skills/role-documentation/SKILL.md +4 -0
  103. package/skills/role-implementation/SKILL.md +4 -0
  104. package/skills/role-infra/SKILL.md +2 -1
  105. package/skills/role-integration/SKILL.md +15 -8
  106. package/skills/role-planner/SKILL.md +39 -0
  107. package/skills/role-planner/skill.json +21 -0
  108. package/skills/role-research/SKILL.md +1 -0
  109. package/skills/role-security/SKILL.md +2 -2
  110. package/skills/runtime-claude/SKILL.md +2 -1
  111. package/skills/runtime-codex/SKILL.md +1 -0
  112. package/skills/runtime-local/SKILL.md +2 -0
  113. package/skills/runtime-opencode/SKILL.md +1 -0
  114. package/skills/wave-core/SKILL.md +25 -6
  115. package/skills/wave-core/references/marker-syntax.md +16 -8
  116. package/wave.config.json +45 -0
@@ -2,7 +2,10 @@ import crypto from "node:crypto";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import {
5
+ DEFAULT_COORDINATION_ACK_TIMEOUT_MS,
6
+ DEFAULT_COORDINATION_RESOLUTION_STALE_MS,
5
7
  REPO_ROOT,
8
+ buildLanePaths,
6
9
  compactSingleLine,
7
10
  ensureDirectory,
8
11
  readJsonOrNull,
@@ -10,6 +13,7 @@ import {
10
13
  truncate,
11
14
  writeTextAtomic,
12
15
  } from "./shared.mjs";
16
+ import { safeQueueWaveControlEvent } from "./wave-control-client.mjs";
13
17
 
14
18
  export const COORDINATION_KIND_VALUES = [
15
19
  "request",
@@ -147,6 +151,50 @@ export function appendCoordinationRecord(filePath, rawRecord, defaults = {}) {
147
151
  const record = normalizeCoordinationRecord(rawRecord, defaults);
148
152
  ensureDirectory(path.dirname(filePath));
149
153
  fs.appendFileSync(filePath, `${JSON.stringify(record)}\n`, "utf8");
154
+ const runIdHint = normalizeString(rawRecord?.runId ?? defaults.runId, "");
155
+ try {
156
+ const lanePaths = buildLanePaths(record.lane, {
157
+ ...(runIdHint ? { adhocRunId: runIdHint } : {}),
158
+ });
159
+ if (lanePaths?.waveControl?.captureCoordinationRecords !== false) {
160
+ safeQueueWaveControlEvent(lanePaths, {
161
+ category: "coordination",
162
+ entityType: "coordination_record",
163
+ entityId: record.id,
164
+ action: "recorded",
165
+ source: record.source,
166
+ actor: record.agentId,
167
+ recordedAt: record.updatedAt || record.createdAt,
168
+ identity: {
169
+ lane: record.lane,
170
+ wave: record.wave,
171
+ attempt: record.attempt,
172
+ agentId: record.agentId,
173
+ runKind: lanePaths.runKind,
174
+ runId: lanePaths.runId,
175
+ },
176
+ tags: [`kind:${record.kind}`, `status:${record.status}`],
177
+ data: {
178
+ kind: record.kind,
179
+ status: record.status,
180
+ priority: record.priority,
181
+ confidence: record.confidence,
182
+ summary: record.summary,
183
+ detail: record.detail,
184
+ targets: record.targets,
185
+ artifactRefs: record.artifactRefs,
186
+ dependsOn: record.dependsOn,
187
+ closureCondition: record.closureCondition,
188
+ required: record.required,
189
+ executorId: record.executorId || null,
190
+ requesterLane: record.requesterLane || null,
191
+ ownerLane: record.ownerLane || null,
192
+ },
193
+ });
194
+ }
195
+ } catch {
196
+ // Telemetry is best-effort and must never block canonical coordination writes.
197
+ }
150
198
  return record;
151
199
  }
152
200
 
@@ -311,11 +359,137 @@ export function serializeCoordinationState(state) {
311
359
  };
312
360
  }
313
361
 
314
- function renderOpenRecord(record) {
362
+ function parseRecordStartMs(record) {
363
+ const createdAtMs = Date.parse(record?.createdAt || "");
364
+ if (Number.isFinite(createdAtMs)) {
365
+ return createdAtMs;
366
+ }
367
+ const updatedAtMs = Date.parse(record?.updatedAt || "");
368
+ return Number.isFinite(updatedAtMs) ? updatedAtMs : null;
369
+ }
370
+
371
+ function formatAgeMs(ageMs) {
372
+ if (!Number.isFinite(ageMs)) {
373
+ return "n/a";
374
+ }
375
+ const totalSeconds = Math.max(0, Math.floor(ageMs / 1000));
376
+ if (totalSeconds < 60) {
377
+ return `${totalSeconds}s`;
378
+ }
379
+ const minutes = Math.floor(totalSeconds / 60);
380
+ if (minutes < 60) {
381
+ return `${minutes}m`;
382
+ }
383
+ const hours = Math.floor(minutes / 60);
384
+ return `${hours}h ${minutes % 60}m`;
385
+ }
386
+
387
+ function isAckTrackedRecord(record) {
388
+ if (!record || typeof record !== "object") {
389
+ return false;
390
+ }
391
+ if (["clarification-request", "human-feedback", "human-escalation"].includes(record.kind)) {
392
+ return true;
393
+ }
394
+ if (record.kind !== "request") {
395
+ return false;
396
+ }
397
+ return record.source !== "launcher" || isClarificationLinkedRequest(record);
398
+ }
399
+
400
+ export function buildCoordinationResponseMetrics(state, options = {}) {
401
+ const nowMs = Number.isFinite(options.nowMs) ? options.nowMs : Date.now();
402
+ const ackTimeoutMs = Number.isFinite(options.ackTimeoutMs)
403
+ ? options.ackTimeoutMs
404
+ : DEFAULT_COORDINATION_ACK_TIMEOUT_MS;
405
+ const resolutionStaleMs = Number.isFinite(options.resolutionStaleMs)
406
+ ? options.resolutionStaleMs
407
+ : DEFAULT_COORDINATION_RESOLUTION_STALE_MS;
408
+ const recordMetricsById = new Map();
409
+ const overdueAckRecordIds = [];
410
+ const overdueClarificationIds = new Set();
411
+ let oldestOpenCoordinationAgeMs = null;
412
+ let oldestUnackedRequestAgeMs = null;
413
+
414
+ for (const record of state?.openRecords || []) {
415
+ const startMs = parseRecordStartMs(record);
416
+ const ageMs = Number.isFinite(startMs) ? Math.max(0, nowMs - startMs) : null;
417
+ const ackTracked = isAckTrackedRecord(record);
418
+ const ackPending = ackTracked && record.status === "open";
419
+ const clarificationLinked =
420
+ record.kind === "clarification-request" || isClarificationLinkedRequest(record);
421
+ const overdueAck = ackPending && Number.isFinite(ageMs) && ageMs >= ackTimeoutMs;
422
+ const staleClarification =
423
+ clarificationLinked && Number.isFinite(ageMs) && ageMs >= resolutionStaleMs;
424
+
425
+ if (Number.isFinite(ageMs)) {
426
+ oldestOpenCoordinationAgeMs =
427
+ oldestOpenCoordinationAgeMs === null
428
+ ? ageMs
429
+ : Math.max(oldestOpenCoordinationAgeMs, ageMs);
430
+ if (ackPending) {
431
+ oldestUnackedRequestAgeMs =
432
+ oldestUnackedRequestAgeMs === null
433
+ ? ageMs
434
+ : Math.max(oldestUnackedRequestAgeMs, ageMs);
435
+ }
436
+ }
437
+ if (overdueAck) {
438
+ overdueAckRecordIds.push(record.id);
439
+ }
440
+ if (staleClarification) {
441
+ overdueClarificationIds.add(
442
+ record.kind === "clarification-request"
443
+ ? record.id
444
+ : clarificationIdFromClosureCondition(record.closureCondition) || record.id,
445
+ );
446
+ }
447
+ recordMetricsById.set(record.id, {
448
+ ageMs,
449
+ ageLabel: formatAgeMs(ageMs),
450
+ ackTracked,
451
+ ackPending,
452
+ overdueAck,
453
+ clarificationLinked,
454
+ staleClarification,
455
+ });
456
+ }
457
+
458
+ return {
459
+ ackTimeoutMs,
460
+ resolutionStaleMs,
461
+ oldestOpenCoordinationAgeMs,
462
+ oldestUnackedRequestAgeMs,
463
+ overdueAckCount: overdueAckRecordIds.length,
464
+ overdueClarificationCount: overdueClarificationIds.size,
465
+ overdueAckRecordIds: overdueAckRecordIds.toSorted((a, b) => a.localeCompare(b)),
466
+ overdueClarificationIds: Array.from(overdueClarificationIds).toSorted((a, b) =>
467
+ a.localeCompare(b),
468
+ ),
469
+ openHumanEscalationCount: (state?.humanEscalations || []).filter((record) =>
470
+ isOpenCoordinationStatus(record.status),
471
+ ).length,
472
+ recordMetricsById,
473
+ };
474
+ }
475
+
476
+ function renderOpenRecord(record, responseMetrics = null) {
315
477
  const targets = record.targets.length > 0 ? ` -> ${record.targets.join(", ")}` : "";
316
478
  const artifacts =
317
479
  record.artifactRefs.length > 0 ? ` [artifacts: ${record.artifactRefs.join(", ")}]` : "";
318
- return `- [${record.priority}] ${record.kind}/${record.status} ${record.agentId}${targets}: ${compactSingleLine(record.summary || record.detail || "no summary", 160)}${artifacts}`;
480
+ const recordMetrics = responseMetrics?.recordMetricsById?.get?.(record.id) || null;
481
+ const tags = [];
482
+ if (recordMetrics?.ageLabel && recordMetrics.ageLabel !== "n/a") {
483
+ tags.push(`age=${recordMetrics.ageLabel}`);
484
+ }
485
+ if (recordMetrics?.overdueAck) {
486
+ tags.push("overdue-ack");
487
+ }
488
+ if (recordMetrics?.staleClarification) {
489
+ tags.push("stale-clarification");
490
+ }
491
+ const timing = tags.length > 0 ? ` [${tags.join(", ")}]` : "";
492
+ return `- [${record.priority}] ${record.kind}/${record.status} ${record.agentId}${targets}${timing} id=${record.id}: ${compactSingleLine(record.summary || record.detail || "no summary", 160)}${artifacts}`;
319
493
  }
320
494
 
321
495
  function renderActivityRecord(record) {
@@ -345,6 +519,7 @@ export function renderCoordinationBoardProjection({
345
519
  state,
346
520
  capabilityAssignments = [],
347
521
  dependencySnapshot = null,
522
+ timingOptions = null,
348
523
  }) {
349
524
  const latestRecords = Array.isArray(state?.latestRecords) ? state.latestRecords : [];
350
525
  const openRecords = latestRecords.filter((record) => OPEN_COORDINATION_STATUSES.has(record.status));
@@ -352,16 +527,29 @@ export function renderCoordinationBoardProjection({
352
527
  const openAssignments = (capabilityAssignments || []).filter((assignment) => assignment.blocking);
353
528
  const openInboundDependencies = dependencySnapshot?.openInbound || [];
354
529
  const openOutboundDependencies = dependencySnapshot?.openOutbound || [];
530
+ const responseMetrics = buildCoordinationResponseMetrics(state, timingOptions || {});
531
+ const oldestOpenAge =
532
+ responseMetrics.oldestOpenCoordinationAgeMs === null
533
+ ? "none"
534
+ : formatAgeMs(responseMetrics.oldestOpenCoordinationAgeMs);
535
+ const oldestUnackedAge =
536
+ responseMetrics.oldestUnackedRequestAgeMs === null
537
+ ? "none"
538
+ : formatAgeMs(responseMetrics.oldestUnackedRequestAgeMs);
355
539
  return [
356
540
  `# Wave ${wave} Message Board`,
357
541
  "",
358
542
  `- Wave file: \`${waveFile}\``,
359
543
  `- Agents: ${(agents || []).map((agent) => agent.agentId).join(", ")}`,
360
544
  `- Generated: ${toIsoTimestamp()}`,
545
+ `- Oldest open coordination age: ${oldestOpenAge}`,
546
+ `- Oldest unacknowledged request age: ${oldestUnackedAge}`,
547
+ `- Overdue acknowledgements: ${responseMetrics.overdueAckCount}`,
548
+ `- Overdue clarification chains: ${responseMetrics.overdueClarificationCount}`,
361
549
  "",
362
550
  "## Open Coordination State",
363
551
  ...(openRecords.length > 0
364
- ? openRecords.map((record) => renderOpenRecord(record))
552
+ ? openRecords.map((record) => renderOpenRecord(record, responseMetrics))
365
553
  : ["- None."]),
366
554
  "",
367
555
  "## Helper Assignments",
@@ -457,6 +645,7 @@ export function compileSharedSummary({
457
645
  capabilityAssignments = [],
458
646
  dependencySnapshot = null,
459
647
  maxChars = 4000,
648
+ timingOptions = null,
460
649
  }) {
461
650
  const openBlockers = state.blockers.filter((record) => OPEN_COORDINATION_STATUSES.has(record.status));
462
651
  const openRequests = state.requests.filter((record) => OPEN_COORDINATION_STATUSES.has(record.status));
@@ -469,6 +658,7 @@ export function compileSharedSummary({
469
658
  const openHelperAssignments = (capabilityAssignments || []).filter((assignment) => assignment.blocking);
470
659
  const openInboundDependencies = dependencySnapshot?.openInbound || [];
471
660
  const openOutboundDependencies = dependencySnapshot?.openOutbound || [];
661
+ const responseMetrics = buildCoordinationResponseMetrics(state, timingOptions || {});
472
662
  const summary = [
473
663
  `# Wave ${wave.wave} Shared Summary`,
474
664
  "",
@@ -480,6 +670,10 @@ export function compileSharedSummary({
480
670
  `- Open helper assignments: ${openHelperAssignments.length}`,
481
671
  `- Open inbound dependencies: ${openInboundDependencies.length}`,
482
672
  `- Open outbound dependencies: ${openOutboundDependencies.length}`,
673
+ `- Oldest open coordination age: ${responseMetrics.oldestOpenCoordinationAgeMs === null ? "none" : formatAgeMs(responseMetrics.oldestOpenCoordinationAgeMs)}`,
674
+ `- Oldest unacknowledged request age: ${responseMetrics.oldestUnackedRequestAgeMs === null ? "none" : formatAgeMs(responseMetrics.oldestUnackedRequestAgeMs)}`,
675
+ `- Overdue acknowledgements: ${responseMetrics.overdueAckCount}`,
676
+ `- Overdue clarification chains: ${responseMetrics.overdueClarificationCount}`,
483
677
  ...(integrationSummary
484
678
  ? [`- Integration recommendation: ${integrationSummary.recommendation || "n/a"}`]
485
679
  : []),
@@ -498,12 +692,12 @@ export function compileSharedSummary({
498
692
  "",
499
693
  "## Current blockers",
500
694
  ...(openBlockers.length > 0
501
- ? openBlockers.map((record) => renderOpenRecord(record))
695
+ ? openBlockers.map((record) => renderOpenRecord(record, responseMetrics))
502
696
  : ["- None."]),
503
697
  "",
504
698
  "## Current clarifications",
505
699
  ...(openClarifications.length > 0
506
- ? openClarifications.map((record) => renderOpenRecord(record))
700
+ ? openClarifications.map((record) => renderOpenRecord(record, responseMetrics))
507
701
  : ["- None."]),
508
702
  "",
509
703
  "## Helper assignments",
@@ -588,6 +782,7 @@ export function compileAgentInbox({
588
782
  capabilityAssignments = [],
589
783
  dependencySnapshot = null,
590
784
  maxChars = 8000,
785
+ timingOptions = null,
591
786
  }) {
592
787
  const targetedRecords = state.openRecords.filter((record) => isTargetedToAgent(record, agent));
593
788
  const ownedRecords = (state.recordsByAgentId.get(agent.agentId) || []).filter((record) =>
@@ -624,6 +819,7 @@ export function compileAgentInbox({
624
819
  const helperAssignments = (capabilityAssignments || []).filter(
625
820
  (assignment) => assignment.blocking && assignment.assignedAgentId === agent.agentId,
626
821
  );
822
+ const responseMetrics = buildCoordinationResponseMetrics(state, timingOptions || {});
627
823
  const dependencyItems = [
628
824
  ...((dependencySnapshot?.inbound || []).filter(
629
825
  (record) =>
@@ -641,24 +837,30 @@ export function compileAgentInbox({
641
837
  const text = [
642
838
  `# Wave ${wave.wave} Inbox for ${agent.agentId}`,
643
839
  "",
840
+ "## Response timing",
841
+ `- Oldest open coordination age: ${responseMetrics.oldestOpenCoordinationAgeMs === null ? "none" : formatAgeMs(responseMetrics.oldestOpenCoordinationAgeMs)}`,
842
+ `- Oldest unacknowledged request age: ${responseMetrics.oldestUnackedRequestAgeMs === null ? "none" : formatAgeMs(responseMetrics.oldestUnackedRequestAgeMs)}`,
843
+ `- Overdue acknowledgements: ${responseMetrics.overdueAckCount}`,
844
+ `- Overdue clarification chains: ${responseMetrics.overdueClarificationCount}`,
845
+ "",
644
846
  "## Targeted open coordination",
645
847
  ...(targetedRecords.length > 0
646
- ? targetedRecords.map((record) => renderOpenRecord(record))
848
+ ? targetedRecords.map((record) => renderOpenRecord(record, responseMetrics))
647
849
  : ["- None."]),
648
850
  "",
649
851
  "## Your open coordination items",
650
852
  ...(ownedRecords.length > 0
651
- ? ownedRecords.map((record) => renderOpenRecord(record))
853
+ ? ownedRecords.map((record) => renderOpenRecord(record, responseMetrics))
652
854
  : ["- None."]),
653
855
  "",
654
856
  "## Clarifications",
655
857
  ...(clarificationRecords.length > 0
656
- ? clarificationRecords.map((record) => renderOpenRecord(record))
858
+ ? clarificationRecords.map((record) => renderOpenRecord(record, responseMetrics))
657
859
  : ["- None."]),
658
860
  "",
659
861
  "## Relevant open coordination",
660
862
  ...(relevantRecords.length > 0
661
- ? relevantRecords.map((record) => renderOpenRecord(record))
863
+ ? relevantRecords.map((record) => renderOpenRecord(record, responseMetrics))
662
864
  : ["- None."]),
663
865
  "",
664
866
  "## Helper assignments",
@@ -455,6 +455,7 @@ export function buildExecutionPrompt({
455
455
  "- Follow repository instructions in AGENTS.md and CLAUDE.md if present.",
456
456
  "- Read the compiled shared summary and your compiled inbox before taking action on every turn.",
457
457
  "- Post a coordination record on every meaningful turn with progress, decisions, blockers, handoffs, evidence, or explicit acknowledgement.",
458
+ "- If your inbox or the coordination log shows a targeted open request for you, your first durable action is to acknowledge it, resolve it, or emit a clarification or human-feedback request. Silent targeted requests are treated as active blockers.",
458
459
  "- Re-read the generated board projection before major edits, before commit or push, and before your final report.",
459
460
  "- If you change interfaces or contracts, include exact files and exact keys or fields affected.",
460
461
  "- If your task touches persisted state, implement the required schema or migration work instead of leaving TODOs.",
@@ -511,6 +512,89 @@ export function buildExecutionPrompt({
511
512
  ].join("\n");
512
513
  }
513
514
 
515
+ export function buildResidentOrchestratorPrompt({
516
+ lane,
517
+ wave,
518
+ waveFile,
519
+ orchestratorId,
520
+ coordinationLogPath,
521
+ messageBoardPath,
522
+ sharedSummaryPath,
523
+ dashboardPath,
524
+ triagePath = null,
525
+ rolePrompt = "",
526
+ }) {
527
+ const coordinationCommand = [
528
+ "pnpm exec wave coord post",
529
+ `--lane ${lane}`,
530
+ `--wave ${wave}`,
531
+ '--agent "launcher"',
532
+ '--kind "<request|ack|decision|blocker|clarification-request|orchestrator-guidance|resolved-by-policy|human-escalation|human-feedback>"',
533
+ '--summary "<one-line summary>"',
534
+ '--detail "<short detail>"',
535
+ ].join(" ");
536
+ const feedbackCommand = [
537
+ "pnpm exec wave-feedback ask",
538
+ `--lane ${lane}`,
539
+ `--wave ${wave}`,
540
+ '--agent "launcher"',
541
+ `--orchestrator-id ${orchestratorId}`,
542
+ '--question "<specific clarification needed>"',
543
+ '--context "<why repo-state or routed ownership was insufficient>"',
544
+ "--timeout-seconds 30",
545
+ ].join(" ");
546
+ const roleSection = String(rolePrompt || "").trim();
547
+ return [
548
+ `Working directory: ${REPO_ROOT}`,
549
+ "",
550
+ `You are the resident Wave orchestrator for lane ${lane}, wave ${wave}.`,
551
+ "Your job is to stay alive for the duration of the wave, monitor coordination state, and intervene when timing, routing, or escalation policy requires it.",
552
+ "The launcher remains the scheduler truth and closure authority. You are an intervention and triage role, not a product-code owner.",
553
+ "",
554
+ "Hard limits:",
555
+ "- Do not edit product code, test code, docs, or owned deliverables.",
556
+ "- Do not claim another agent's ownership slice or emit proof markers for owned implementation work.",
557
+ "- Do not declare the wave complete or override launcher gate results.",
558
+ "",
559
+ "Primary responsibilities:",
560
+ "- Monitor the canonical coordination log, shared summary, wave dashboard, and feedback triage artifacts.",
561
+ "- Keep clarification handling orchestrator-first: resolve from repo state, ownership, prior decisions, or targeted rerouting before opening human escalation.",
562
+ "- Watch for overdue acknowledgements, stale clarification chains, and pending human tickets.",
563
+ "- Post durable coordination records and orchestrator notes when you intervene.",
564
+ "- Stay available. If there is no immediate action to take, keep monitoring instead of exiting early.",
565
+ "",
566
+ "Files for this run:",
567
+ `- Wave file: ${waveFile}`,
568
+ `- Coordination log: ${coordinationLogPath}`,
569
+ `- Shared summary: ${sharedSummaryPath}`,
570
+ `- Wave dashboard: ${dashboardPath}`,
571
+ `- Message board projection: ${messageBoardPath}`,
572
+ ...(triagePath ? [`- Feedback triage log: ${triagePath}`] : []),
573
+ "",
574
+ "Action surface:",
575
+ `- Coordination command: \`${coordinationCommand}\``,
576
+ `- Human feedback command: \`${feedbackCommand}\``,
577
+ "- Use repository inspection and read-only analysis aggressively before escalating anything to a human.",
578
+ "- When you route or reroute work, name the owner, exact unblock condition, and the artifact or decision needed.",
579
+ "",
580
+ "Operating loop:",
581
+ "1. Read the shared summary, dashboard, and coordination log.",
582
+ "2. Identify open clarifications, open clarification-linked requests, overdue acknowledgements, and human-feedback state.",
583
+ "3. If action is needed, write a durable coordination update and explain the policy basis for the action.",
584
+ "4. If nothing needs action, continue monitoring. Do not exit until the wave is clearly terminal or the launcher stops the session.",
585
+ "",
586
+ ...(roleSection
587
+ ? [
588
+ "Resident orchestrator role prompt:",
589
+ "```text",
590
+ roleSection,
591
+ "```",
592
+ "",
593
+ ]
594
+ : []),
595
+ ].join("\n");
596
+ }
597
+
514
598
  export function buildOrchestratorBoardTemplate(boardPath) {
515
599
  const now = toIsoTimestamp();
516
600
  return [
@@ -182,6 +182,22 @@ function paintWaveAgentSummary(summary, wave, colorize = false) {
182
182
  return paint(summary, color, colorize);
183
183
  }
184
184
 
185
+ function formatDurationMs(value) {
186
+ if (!Number.isFinite(value)) {
187
+ return "n/a";
188
+ }
189
+ const totalSeconds = Math.max(0, Math.floor(value / 1000));
190
+ if (totalSeconds < 60) {
191
+ return `${totalSeconds}s`;
192
+ }
193
+ const minutes = Math.floor(totalSeconds / 60);
194
+ if (minutes < 60) {
195
+ return `${minutes}m`;
196
+ }
197
+ const hours = Math.floor(minutes / 60);
198
+ return `${hours}h ${minutes % 60}m`;
199
+ }
200
+
185
201
  function renderWaveDashboard({ state, dashboardPath, messageBoardPath, lane, colorize = false }) {
186
202
  if (!state) {
187
203
  return `Dashboard file not found or invalid: ${dashboardPath}`;
@@ -200,15 +216,21 @@ function renderWaveDashboard({ state, dashboardPath, messageBoardPath, lane, col
200
216
  );
201
217
  lines.push(`Run tag: ${state.runTag || "n/a"} | Wave file: ${state.waveFile || "n/a"}`);
202
218
  lines.push(`Counts: ${renderColoredCountsByState(state.agents || [], colorize) || "none"}`);
219
+ lines.push(
220
+ `Coordination: open=${state.coordinationOpen ?? 0} clarifications=${state.openClarifications ?? 0} human=${state.openHumanEscalations ?? 0} overdue-ack=${state.overdueAckCount ?? 0} overdue-clarification=${state.overdueClarificationCount ?? 0}`,
221
+ );
222
+ lines.push(
223
+ `Coordination age: oldest-open=${formatDurationMs(state.oldestOpenCoordinationAgeMs)} oldest-unack=${formatDurationMs(state.oldestUnackedRequestAgeMs)}`,
224
+ );
203
225
  const comms = analyzeMessageBoardCommunication(messageBoardPath);
204
226
  if (!comms.available) {
205
- lines.push(`Comms: unavailable ${comms.reason || ""}`.trim());
227
+ lines.push(`Board comms: unavailable ${comms.reason || ""}`.trim());
206
228
  } else {
207
229
  lines.push(
208
- `Comms: requests=${comms.actionableRequests} unresolved=${comms.unresolvedRequests} unacknowledged=${comms.unacknowledgedRequests} malformed=${comms.malformedEntries} placeholder-ts=${comms.placeholderTimestampEntries}`,
230
+ `Board comms: requests=${comms.actionableRequests} unresolved=${comms.unresolvedRequests} unacknowledged=${comms.unacknowledgedRequests} malformed=${comms.malformedEntries} placeholder-ts=${comms.placeholderTimestampEntries}`,
209
231
  );
210
232
  lines.push(
211
- `Comms age: last-ack=${commsAgeSummary(comms.lastAcknowledgementTimestamp)} oldest-unack=${commsAgeSummary(comms.oldestUnacknowledgedTimestamp)}`,
233
+ `Board comms age: last-ack=${commsAgeSummary(comms.lastAcknowledgementTimestamp)} oldest-unack=${commsAgeSummary(comms.oldestUnacknowledgedTimestamp)}`,
212
234
  );
213
235
  }
214
236
  lines.push("");
@@ -306,6 +328,19 @@ function renderGlobalDashboard({ state, dashboardPath, lane, colorize = false })
306
328
  12,
307
329
  )} ${truncate(wave.lastMessage || "", 70)}`,
308
330
  );
331
+ if (
332
+ Number(wave?.coordinationOpen ?? 0) > 0 ||
333
+ Number(wave?.overdueAckCount ?? 0) > 0 ||
334
+ Number(wave?.overdueClarificationCount ?? 0) > 0 ||
335
+ Number(wave?.openHumanEscalations ?? 0) > 0
336
+ ) {
337
+ lines.push(
338
+ ` Coord: open ${wave.coordinationOpen ?? 0} clarifications ${wave.openClarifications ?? 0} human ${wave.openHumanEscalations ?? 0} overdue-ack ${wave.overdueAckCount ?? 0} overdue-clarification ${wave.overdueClarificationCount ?? 0}`,
339
+ );
340
+ lines.push(
341
+ ` Ages: oldest-open ${formatDurationMs(wave.oldestOpenCoordinationAgeMs)} oldest-unack ${formatDurationMs(wave.oldestUnackedRequestAgeMs)}`,
342
+ );
343
+ }
309
344
  const deployments = Array.isArray(wave.deployments) ? wave.deployments : [];
310
345
  if (deployments.length > 0) {
311
346
  const deployLine = deployments
@@ -149,6 +149,13 @@ export function buildWaveDashboardState({
149
149
  helperAssignmentsOpen: 0,
150
150
  inboundDependenciesOpen: 0,
151
151
  outboundDependenciesOpen: 0,
152
+ coordinationOpen: 0,
153
+ openClarifications: 0,
154
+ openHumanEscalations: 0,
155
+ oldestOpenCoordinationAgeMs: null,
156
+ oldestUnackedRequestAgeMs: null,
157
+ overdueAckCount: 0,
158
+ overdueClarificationCount: 0,
152
159
  agents: agentRuns.map((run) => ({
153
160
  agentId: run.agent.agentId,
154
161
  title: run.agent.title,
@@ -200,6 +207,7 @@ export function buildGlobalDashboardState({
200
207
  maxRetriesPerWave: options.maxRetriesPerWave,
201
208
  dashboard: options.dashboard,
202
209
  cleanupSessions: options.cleanupSessions,
210
+ residentOrchestrator: options.residentOrchestrator === true,
203
211
  orchestratorId: options.orchestratorId,
204
212
  orchestratorBoardPath: options.orchestratorBoardPath
205
213
  ? path.relative(REPO_ROOT, options.orchestratorBoardPath)
@@ -235,6 +243,13 @@ export function buildGlobalDashboardState({
235
243
  helperAssignmentsOpen: 0,
236
244
  inboundDependenciesOpen: 0,
237
245
  outboundDependenciesOpen: 0,
246
+ coordinationOpen: 0,
247
+ openClarifications: 0,
248
+ openHumanEscalations: 0,
249
+ oldestOpenCoordinationAgeMs: null,
250
+ oldestUnackedRequestAgeMs: null,
251
+ overdueAckCount: 0,
252
+ overdueClarificationCount: 0,
238
253
  lastMessage: "",
239
254
  deployments: [],
240
255
  infraFindings: [],
@@ -323,6 +338,13 @@ export function syncGlobalWaveFromWaveDashboard(globalState, waveDashboard) {
323
338
  entry.helperAssignmentsOpen = waveDashboard.helperAssignmentsOpen || 0;
324
339
  entry.inboundDependenciesOpen = waveDashboard.inboundDependenciesOpen || 0;
325
340
  entry.outboundDependenciesOpen = waveDashboard.outboundDependenciesOpen || 0;
341
+ entry.coordinationOpen = waveDashboard.coordinationOpen || 0;
342
+ entry.openClarifications = waveDashboard.openClarifications || 0;
343
+ entry.openHumanEscalations = waveDashboard.openHumanEscalations || 0;
344
+ entry.oldestOpenCoordinationAgeMs = waveDashboard.oldestOpenCoordinationAgeMs ?? null;
345
+ entry.oldestUnackedRequestAgeMs = waveDashboard.oldestUnackedRequestAgeMs ?? null;
346
+ entry.overdueAckCount = waveDashboard.overdueAckCount || 0;
347
+ entry.overdueClarificationCount = waveDashboard.overdueClarificationCount || 0;
326
348
  entry.deployments = agents
327
349
  .filter((agent) => agent.deploymentState)
328
350
  .map((agent) => ({
@@ -351,6 +351,12 @@ export function loadBenchmarkCatalog(options = {}) {
351
351
  id: benchmarkId,
352
352
  title: cleanText(benchmark.title) || benchmarkId,
353
353
  summary: cleanText(benchmark.summary) || null,
354
+ localCases: normalizeStringArray(
355
+ benchmark.localCases,
356
+ `families.${familyId}.benchmarks.${benchmarkId}.localCases`,
357
+ ).map((entry, index) =>
358
+ normalizeEvalTargetId(entry, `families.${familyId}.benchmarks.${benchmarkId}.localCases[${index}]`),
359
+ ),
354
360
  goal: cleanText(benchmark.goal) || null,
355
361
  failureModes: normalizeStringArray(
356
362
  benchmark.failureModes,
@@ -380,6 +386,12 @@ export function loadBenchmarkCatalog(options = {}) {
380
386
  id: familyId,
381
387
  title: cleanText(rawFamily.title) || familyId,
382
388
  summary: cleanText(rawFamily.summary) || null,
389
+ localCases: normalizeStringArray(
390
+ rawFamily.localCases,
391
+ `families.${familyId}.localCases`,
392
+ ).map((entry, index) =>
393
+ normalizeEvalTargetId(entry, `families.${familyId}.localCases[${index}]`),
394
+ ),
383
395
  category: cleanText(rawFamily.category) || null,
384
396
  coordinationModel: cleanText(rawFamily.coordinationModel) || null,
385
397
  primaryMetric: familyPrimaryMetric,
@@ -395,6 +407,17 @@ export function loadBenchmarkCatalog(options = {}) {
395
407
  absolutePath,
396
408
  families,
397
409
  benchmarkIndex,
410
+ localCaseIndex: Object.fromEntries(
411
+ Object.values(families).flatMap((family) => [
412
+ ...(family.localCases || []).map((caseId) => [caseId, { familyId: family.id, benchmarkId: null }]),
413
+ ...Object.values(family.benchmarks).flatMap((benchmark) =>
414
+ (benchmark.localCases || []).map((caseId) => [
415
+ caseId,
416
+ { familyId: family.id, benchmarkId: benchmark.id },
417
+ ]),
418
+ ),
419
+ ]),
420
+ ),
398
421
  };
399
422
  }
400
423
 
@@ -210,7 +210,8 @@ function buildClaudeLaunchSpec({ agent, promptPath, logPath, overlayDir }) {
210
210
  systemPromptPath,
211
211
  `${renderHarnessSystemPrompt(agent, "claude")}${skillText ? `\n\n${skillText}` : ""}\n`,
212
212
  );
213
- const tokens = [executor.claude.command, "-p", "--no-session-persistence"];
213
+ const command = executor?.claude?.command || "claude";
214
+ const tokens = [command, "-p", "--no-session-persistence"];
214
215
  const settingsPath = buildClaudeSettingsPath(executor, overlayDir);
215
216
  appendSingleValueFlag(tokens, "--output-format", executor.claude.outputFormat || "text");
216
217
  appendSingleValueFlag(tokens, "--model", executor.claude.model || executor.model);
@@ -234,7 +235,7 @@ function buildClaudeLaunchSpec({ agent, promptPath, logPath, overlayDir }) {
234
235
  );
235
236
  return {
236
237
  executorId: "claude",
237
- command: executor.claude.command,
238
+ command,
238
239
  useRateLimitRetries: true,
239
240
  invocationLines: [
240
241
  `task_prompt=$(cat ${shellQuote(promptPath)})`,