@chllming/wave-orchestration 0.8.3 → 0.8.4

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 (39) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +47 -11
  3. package/docs/README.md +6 -2
  4. package/docs/concepts/what-is-a-wave.md +1 -1
  5. package/docs/plans/architecture-hardening-migration.md +8 -1
  6. package/docs/plans/current-state.md +15 -7
  7. package/docs/plans/end-state-architecture.md +82 -69
  8. package/docs/plans/examples/wave-example-live-proof.md +1 -1
  9. package/docs/plans/migration.md +235 -62
  10. package/docs/plans/wave-orchestrator.md +37 -11
  11. package/docs/reference/cli-reference.md +34 -14
  12. package/docs/reference/coordination-and-closure.md +19 -6
  13. package/docs/reference/npmjs-trusted-publishing.md +5 -4
  14. package/docs/reference/sample-waves.md +4 -4
  15. package/package.json +1 -1
  16. package/releases/manifest.json +20 -0
  17. package/scripts/wave-orchestrator/agent-state.mjs +0 -491
  18. package/scripts/wave-orchestrator/autonomous.mjs +10 -6
  19. package/scripts/wave-orchestrator/{launcher-closure.mjs → closure-engine.mjs} +190 -74
  20. package/scripts/wave-orchestrator/{launcher-derived-state.mjs → derived-state-engine.mjs} +34 -146
  21. package/scripts/wave-orchestrator/{launcher-gates.mjs → gate-engine.mjs} +395 -139
  22. package/scripts/wave-orchestrator/human-input-resolution.mjs +14 -10
  23. package/scripts/wave-orchestrator/human-input-workflow.mjs +104 -0
  24. package/scripts/wave-orchestrator/implementation-engine.mjs +120 -0
  25. package/scripts/wave-orchestrator/launcher-runtime.mjs +5 -6
  26. package/scripts/wave-orchestrator/launcher.mjs +271 -724
  27. package/scripts/wave-orchestrator/projection-writer.mjs +256 -0
  28. package/scripts/wave-orchestrator/reconcile-format.mjs +32 -0
  29. package/scripts/wave-orchestrator/reducer-snapshot.mjs +297 -0
  30. package/scripts/wave-orchestrator/replay.mjs +3 -1
  31. package/scripts/wave-orchestrator/result-envelope.mjs +589 -0
  32. package/scripts/wave-orchestrator/retry-control.mjs +5 -0
  33. package/scripts/wave-orchestrator/{launcher-retry.mjs → retry-engine.mjs} +267 -18
  34. package/scripts/wave-orchestrator/role-helpers.mjs +51 -0
  35. package/scripts/wave-orchestrator/{launcher-supervisor.mjs → session-supervisor.mjs} +178 -103
  36. package/scripts/wave-orchestrator/shared.mjs +1 -0
  37. package/scripts/wave-orchestrator/traces.mjs +10 -1
  38. package/scripts/wave-orchestrator/wave-files.mjs +11 -9
  39. package/scripts/wave-orchestrator/wave-state-reducer.mjs +52 -5
@@ -11,24 +11,12 @@ import {
11
11
  } from "./config.mjs";
12
12
  import {
13
13
  appendOrchestratorBoardEntry,
14
- buildResidentOrchestratorPrompt,
15
14
  ensureOrchestratorBoard,
16
15
  feedbackStateSignature,
17
16
  readWaveHumanFeedbackRequests,
18
17
  } from "./coordination.mjs";
19
18
  import {
20
- appendCoordinationRecord,
21
19
  buildCoordinationResponseMetrics,
22
- compileAgentInbox,
23
- compileSharedSummary,
24
- isOpenCoordinationStatus,
25
- openClarificationLinkedRequests,
26
- readMaterializedCoordinationState,
27
- renderCoordinationBoardProjection,
28
- updateSeedRecords,
29
- writeCompiledInbox,
30
- writeCoordinationBoardProjection,
31
- writeJsonArtifact,
32
20
  } from "./coordination-store.mjs";
33
21
  import {
34
22
  applyContext7SelectionsToWave,
@@ -40,16 +28,12 @@ import {
40
28
  buildGlobalDashboardState,
41
29
  buildWaveDashboardState,
42
30
  getGlobalWaveEntry,
43
- parseStructuredSignalsFromLog,
44
31
  readStatusCodeIfPresent,
45
32
  recordGlobalDashboardEvent,
46
33
  recordWaveDashboardEvent,
47
34
  refreshWaveDashboardAgentStates,
48
35
  setWaveDashboardAgent,
49
- syncGlobalWaveFromWaveDashboard,
50
36
  updateWaveDashboardMessageBoard,
51
- writeGlobalDashboard,
52
- writeWaveDashboard,
53
37
  } from "./dashboard-state.mjs";
54
38
  import {
55
39
  DEFAULT_AGENT_LAUNCH_STAGGER_MS,
@@ -61,7 +45,6 @@ import {
61
45
  DEFAULT_MAX_RETRIES_PER_WAVE,
62
46
  DEFAULT_TIMEOUT_MINUTES,
63
47
  DEFAULT_WAVE_LANE,
64
- compactSingleLine,
65
48
  parseVerdictFromText,
66
49
  readStatusRecordIfPresent,
67
50
  REPO_ROOT,
@@ -131,8 +114,6 @@ import {
131
114
  validateSecuritySummary,
132
115
  writeAgentExecutionSummary,
133
116
  } from "./agent-state.mjs";
134
- import { buildDocsQueue, readDocsQueue, writeDocsQueue } from "./docs-queue.mjs";
135
- import { deriveWaveLedger, readWaveLedger, writeWaveLedger } from "./ledger.mjs";
136
117
  import {
137
118
  augmentSummaryWithProofRegistry,
138
119
  readWaveProofRegistry,
@@ -140,41 +121,23 @@ import {
140
121
  } from "./proof-registry.mjs";
141
122
  import {
142
123
  clearWaveRetryOverride,
143
- readWaveRelaunchPlanSnapshot,
144
124
  readWaveRetryOverride,
145
- resolveRetryOverrideRuns,
146
- waveRelaunchPlanPath,
147
125
  } from "./retry-control.mjs";
148
126
  import { appendWaveControlEvent, readControlPlaneEvents } from "./control-plane.mjs";
149
127
  import { materializeContradictionsFromControlPlaneEvents } from "./contradiction-entity.mjs";
150
- import { buildQualityMetrics, writeTraceBundle } from "./traces.mjs";
151
128
  import { flushWaveControlQueue } from "./wave-control-client.mjs";
152
- import { reduceWaveState } from "./wave-state-reducer.mjs";
153
- import { triageClarificationRequests } from "./clarification-triage.mjs";
154
129
  import { readProjectProfile, resolveDefaultTerminalSurface } from "./project-profile.mjs";
155
130
  import {
156
131
  isContEvalImplementationOwningAgent,
157
132
  isContEvalReportOnlyAgent,
133
+ isClosureRoleAgentId,
158
134
  isSecurityReviewAgent,
135
+ resolveWaveRoleBindings,
159
136
  resolveSecurityReviewReportPath,
160
137
  } from "./role-helpers.mjs";
161
138
  import {
162
139
  summarizeResolvedSkills,
163
140
  } from "./skills.mjs";
164
- import {
165
- buildDependencySnapshot,
166
- buildRequestAssignments,
167
- renderDependencySnapshotMarkdown,
168
- syncAssignmentRecords,
169
- writeDependencySnapshotMarkdown,
170
- } from "./routing-state.mjs";
171
- import {
172
- readWaveStateSnapshot,
173
- writeAssignmentSnapshot,
174
- writeDependencySnapshot,
175
- writeRelaunchPlan,
176
- writeWaveStateSnapshot,
177
- } from "./artifact-schemas.mjs";
178
141
  import {
179
142
  collectUnexpectedSessionFailures as collectUnexpectedSessionFailuresImpl,
180
143
  launchAgentSession as launchAgentSessionImpl,
@@ -184,9 +147,7 @@ import {
184
147
  import {
185
148
  readWaveInfraGate as readWaveInfraGateImpl,
186
149
  runClosureSweepPhase as runClosureSweepPhaseImpl,
187
- } from "./launcher-closure.mjs";
188
-
189
- // --- Re-exports from launcher-gates.mjs ---
150
+ } from "./closure-engine.mjs";
190
151
  import {
191
152
  materializeAgentExecutionSummaryForRun,
192
153
  readRunExecutionSummary,
@@ -207,71 +168,28 @@ import {
207
168
  readWaveAssignmentBarrier,
208
169
  readWaveDependencyBarrier,
209
170
  buildGateSnapshot as buildGateSnapshotImpl,
210
- } from "./launcher-gates.mjs";
211
-
212
- export {
213
- readWaveContQaGate,
214
- readWaveContEvalGate,
215
- readWaveEvaluatorGate,
216
- readWaveImplementationGate,
217
- readWaveComponentGate,
218
- readWaveComponentMatrixGate,
219
- readWaveDocumentationGate,
220
- readWaveSecurityGate,
221
- readWaveIntegrationGate,
222
- readWaveIntegrationBarrier,
223
- readClarificationBarrier,
224
- readWaveAssignmentBarrier,
225
- readWaveDependencyBarrier,
226
- };
227
-
228
- // --- Re-exports from launcher-derived-state.mjs ---
171
+ } from "./gate-engine.mjs";
229
172
  import {
230
173
  waveAssignmentsPath,
231
174
  waveDependencySnapshotPath,
232
- writeWaveDerivedState,
175
+ buildWaveDerivedState,
233
176
  applyDerivedStateToDashboard,
234
- buildWaveSecuritySummary,
235
- buildWaveIntegrationSummary,
236
- } from "./launcher-derived-state.mjs";
237
-
238
- export {
239
- buildWaveSecuritySummary,
240
- buildWaveIntegrationSummary,
241
- };
242
-
243
- // --- Re-exports from launcher-retry.mjs ---
177
+ } from "./derived-state-engine.mjs";
244
178
  import {
245
- buildResumePlan,
246
179
  readWaveRelaunchPlan,
247
- writeWaveRelaunchPlan,
248
180
  clearWaveRelaunchPlan,
249
181
  resetPersistedWaveLaunchState,
250
182
  persistedRelaunchPlanMatchesCurrentState,
251
183
  resolveSharedComponentContinuationRuns,
252
- relaunchReasonBuckets,
253
184
  applySharedComponentWaitStateToDashboard,
254
185
  reconcileFailuresAgainstSharedComponentState,
255
186
  hasReusableSuccessStatus,
256
187
  selectReusablePreCompletedAgentIds,
257
188
  selectInitialWaveRuns,
258
189
  resolveRelaunchRuns,
259
- applyPersistedRelaunchPlan,
260
190
  executorFallbackChain,
261
191
  preflightWavesForExecutorAvailability,
262
- } from "./launcher-retry.mjs";
263
-
264
- export {
265
- resetPersistedWaveLaunchState,
266
- persistedRelaunchPlanMatchesCurrentState,
267
- resolveSharedComponentContinuationRuns,
268
- hasReusableSuccessStatus,
269
- selectReusablePreCompletedAgentIds,
270
- selectInitialWaveRuns,
271
- resolveRelaunchRuns,
272
- };
273
-
274
- // --- Re-exports from launcher-supervisor.mjs ---
192
+ } from "./retry-engine.mjs";
275
193
  import {
276
194
  markLauncherFailed,
277
195
  acquireLauncherLock,
@@ -286,50 +204,25 @@ import {
286
204
  launchWaveDashboardSession,
287
205
  cleanupLaneTmuxSessions,
288
206
  pruneDryRunExecutorPreviewDirs,
207
+ recordAttemptState,
208
+ recordWaveRunState,
289
209
  runTmux,
290
- } from "./launcher-supervisor.mjs";
291
-
292
- export {
293
- markLauncherFailed,
294
- acquireLauncherLock,
295
- releaseLauncherLock,
296
- reconcileStaleLauncherArtifacts,
297
- collectUnexpectedSessionFailures,
298
- };
299
-
300
- // --- Original re-exports that stay ---
301
- export { CODEX_SANDBOX_MODES, DEFAULT_CODEX_SANDBOX_MODE, normalizeCodexSandboxMode, buildCodexExecInvocation };
302
-
303
- export function formatReconcileBlockedWaveLine(blockedWave) {
304
- const parts = Array.isArray(blockedWave?.reasons)
305
- ? blockedWave.reasons
306
- .map((reason) => {
307
- const code = compactSingleLine(reason?.code || "", 80);
308
- const detail = compactSingleLine(reason?.detail || "", 240);
309
- return code && detail ? `${code}=${detail}` : "";
310
- })
311
- .filter(Boolean)
312
- : [];
313
- return `[reconcile] wave ${blockedWave?.wave ?? "unknown"} not reconstructable: ${
314
- parts.join("; ") || "unknown reason"
315
- }`;
316
- }
317
-
318
- export function formatReconcilePreservedWaveLine(preservedWave) {
319
- const parts = Array.isArray(preservedWave?.reasons)
320
- ? preservedWave.reasons
321
- .map((reason) => {
322
- const code = compactSingleLine(reason?.code || "", 80);
323
- const detail = compactSingleLine(reason?.detail || "", 240);
324
- return code && detail ? `${code}=${detail}` : "";
325
- })
326
- .filter(Boolean)
327
- : [];
328
- const previousState = compactSingleLine(preservedWave?.previousState || "completed", 80);
329
- return `[reconcile] wave ${preservedWave?.wave ?? "unknown"} preserved as ${previousState}: ${
330
- parts.join("; ") || "unknown reason"
331
- }`;
332
- }
210
+ } from "./session-supervisor.mjs";
211
+ import {
212
+ planInitialWaveAttempt,
213
+ planRetryWaveAttempt,
214
+ } from "./implementation-engine.mjs";
215
+ import {
216
+ writeDashboardProjections,
217
+ writeWaveDerivedProjections,
218
+ writeWaveAttemptTraceProjection,
219
+ writeWaveRelaunchProjection,
220
+ } from "./projection-writer.mjs";
221
+ import {
222
+ formatReconcileBlockedWaveLine,
223
+ formatReconcilePreservedWaveLine,
224
+ } from "./reconcile-format.mjs";
225
+ import { computeReducerSnapshot } from "./reducer-snapshot.mjs";
333
226
 
334
227
  function printUsage(lanePaths, terminalSurface) {
335
228
  console.log(`Usage: pnpm exec wave launch [options]
@@ -548,9 +441,9 @@ function parseArgs(argv) {
548
441
  return { help: false, lanePaths, options };
549
442
  }
550
443
 
551
- // --- Wrappers that bind local scope ---
444
+ // --- Local wrappers that bind engine calls to launcher scope ---
552
445
 
553
- export async function runClosureSweepPhase({
446
+ async function runClosureSweepPhase({
554
447
  lanePaths,
555
448
  wave,
556
449
  closureRuns,
@@ -590,17 +483,93 @@ export async function runClosureSweepPhase({
590
483
  });
591
484
  }
592
485
 
593
- export function readWaveInfraGate(agentRuns) {
486
+ function readWaveInfraGate(agentRuns) {
594
487
  return readWaveInfraGateImpl(agentRuns);
595
488
  }
596
489
 
597
- export function buildGateSnapshot(params) {
490
+ function buildGateSnapshot(params) {
598
491
  return buildGateSnapshotImpl({
599
492
  ...params,
600
493
  readWaveInfraGateFn: readWaveInfraGate,
601
494
  });
602
495
  }
603
496
 
497
+ function waveGateLabel(gateName) {
498
+ switch (gateName) {
499
+ case "implementationGate":
500
+ return "Implementation exit contract";
501
+ case "componentGate":
502
+ return "Component promotion";
503
+ case "helperAssignmentBarrier":
504
+ return "Helper assignment barrier";
505
+ case "dependencyBarrier":
506
+ return "Dependency barrier";
507
+ case "contEvalGate":
508
+ return "cont-EVAL";
509
+ case "securityGate":
510
+ return "Security review";
511
+ case "integrationBarrier":
512
+ return "Integration gate";
513
+ case "documentationGate":
514
+ return "Documentation closure";
515
+ case "componentMatrixGate":
516
+ return "Component matrix update";
517
+ case "contQaGate":
518
+ return "cont-QA gate";
519
+ case "infraGate":
520
+ return "Infra gate";
521
+ case "clarificationBarrier":
522
+ return "Clarification barrier";
523
+ default:
524
+ return "Wave gate";
525
+ }
526
+ }
527
+
528
+ function waveGateActionRequested(gateName, lanePaths) {
529
+ switch (gateName) {
530
+ case "implementationGate":
531
+ return `Lane ${lanePaths.lane} owners should resolve the implementation contract gap before wave progression.`;
532
+ case "componentGate":
533
+ return `Lane ${lanePaths.lane} owners should close the component promotion gap before wave progression.`;
534
+ case "helperAssignmentBarrier":
535
+ return `Lane ${lanePaths.lane} owners should resolve helper assignments before wave progression.`;
536
+ case "dependencyBarrier":
537
+ return `Lane ${lanePaths.lane} owners should resolve required dependencies before wave progression.`;
538
+ case "contEvalGate":
539
+ return `Lane ${lanePaths.lane} owners should resolve cont-EVAL tuning gaps before integration closure.`;
540
+ case "securityGate":
541
+ return `Lane ${lanePaths.lane} owners should resolve blocked security findings or missing approvals before integration closure.`;
542
+ case "integrationBarrier":
543
+ return `Lane ${lanePaths.lane} owners should resolve integration contradictions or blockers before documentation and cont-QA closure.`;
544
+ case "documentationGate":
545
+ return `Lane ${lanePaths.lane} owners should resolve the shared-plan closure state before wave progression.`;
546
+ case "componentMatrixGate":
547
+ return `Lane ${lanePaths.lane} owners should update the component cutover matrix current levels before wave progression.`;
548
+ case "contQaGate":
549
+ return `Lane ${lanePaths.lane} owners should resolve the cont-QA gate before wave progression.`;
550
+ case "infraGate":
551
+ return `Lane ${lanePaths.lane} owners should resolve the infra gate before wave progression.`;
552
+ case "clarificationBarrier":
553
+ return `Lane ${lanePaths.lane} owners should resolve open clarification chains before wave progression.`;
554
+ default:
555
+ return `Lane ${lanePaths.lane} owners should resolve the failing gate before wave progression.`;
556
+ }
557
+ }
558
+
559
+ function buildFailureFromGate(gateName, gate, fallbackLogPath) {
560
+ return {
561
+ agentId: gate?.agentId || null,
562
+ statusCode: gate?.statusCode || gateName,
563
+ logPath: gate?.logPath || fallbackLogPath,
564
+ detail: gate?.detail || null,
565
+ componentId: gate?.componentId || null,
566
+ ownerAgentIds: gate?.ownerAgentIds || [],
567
+ satisfiedAgentIds: gate?.satisfiedAgentIds || [],
568
+ waitingOnAgentIds: gate?.waitingOnAgentIds || [],
569
+ failedOwnContractAgentIds: gate?.failedOwnContractAgentIds || [],
570
+ };
571
+ }
572
+
604
573
  // --- Main entry point ---
605
574
 
606
575
  export async function runLauncherCli(argv) {
@@ -699,16 +668,17 @@ export async function runLauncherCli(argv) {
699
668
  }),
700
669
  )
701
670
  .map((wave) =>
702
- ({
703
- ...applyContext7SelectionsToWave(wave, {
671
+ {
672
+ const waveWithContext7 = applyContext7SelectionsToWave(wave, {
704
673
  lane: lanePaths.lane,
705
674
  bundleIndex: context7BundleIndex,
706
- }),
707
- contQaAgentId: lanePaths.contQaAgentId,
708
- contEvalAgentId: lanePaths.contEvalAgentId,
709
- integrationAgentId: lanePaths.integrationAgentId,
710
- documentationAgentId: lanePaths.documentationAgentId,
711
- }),
675
+ });
676
+ return {
677
+ ...waveWithContext7,
678
+ lane: lanePaths.lane,
679
+ ...resolveWaveRoleBindings(waveWithContext7, lanePaths, waveWithContext7.agents),
680
+ };
681
+ },
712
682
  )
713
683
  .map((wave) => validateWaveDefinition(wave, { laneProfile: lanePaths.laneProfile }));
714
684
  const reconciliation = reconcileRunStateFromStatusFiles(
@@ -823,7 +793,7 @@ export async function runLauncherCli(argv) {
823
793
  if (options.dryRun) {
824
794
  pruneDryRunExecutorPreviewDirs(lanePaths, allWaves);
825
795
  for (const wave of filteredWaves) {
826
- const derivedState = writeWaveDerivedState({
796
+ const derivedState = buildWaveDerivedState({
827
797
  lanePaths,
828
798
  wave,
829
799
  summariesByAgentId: {},
@@ -831,10 +801,14 @@ export async function runLauncherCli(argv) {
831
801
  attempt: 0,
832
802
  orchestratorId: options.orchestratorId,
833
803
  });
804
+ writeWaveDerivedProjections({ lanePaths, wave, derivedState });
834
805
  const agentRuns = wave.agents.map((agent) => {
835
806
  const safeName = `wave-${wave.wave}-${agent.slug}`;
836
807
  return {
837
808
  agent,
809
+ lane: lanePaths.lane,
810
+ wave: wave.wave,
811
+ resultsDir: lanePaths.resultsDir,
838
812
  sessionName: `dry-run-wave-${wave.wave}-${agent.slug}`,
839
813
  promptPath: path.join(lanePaths.promptsDir, `${safeName}.prompt.md`),
840
814
  logPath: path.join(lanePaths.logsDir, `${safeName}.log`),
@@ -892,7 +866,7 @@ export async function runLauncherCli(argv) {
892
866
  manifestOut: options.manifestOut,
893
867
  feedbackRequestsDir: lanePaths.feedbackRequestsDir,
894
868
  });
895
- writeGlobalDashboard(lanePaths.globalDashboardPath, globalDashboard);
869
+ writeDashboardProjections({ lanePaths, globalDashboard });
896
870
 
897
871
  if (terminalRegistryEnabled && !options.keepTerminals) {
898
872
  const removed = removeLaneTemporaryTerminalEntries(lanePaths.terminalsPath, lanePaths);
@@ -911,7 +885,7 @@ export async function runLauncherCli(argv) {
911
885
  });
912
886
  }
913
887
  }
914
- writeGlobalDashboard(lanePaths.globalDashboardPath, globalDashboard);
888
+ writeDashboardProjections({ lanePaths, globalDashboard });
915
889
 
916
890
  if (options.dashboard) {
917
891
  globalDashboardTerminalEntry = createGlobalDashboardTerminalEntry(
@@ -955,10 +929,10 @@ export async function runLauncherCli(argv) {
955
929
  status: "running",
956
930
  details: `agents=${wave.agents.map((agent) => agent.agentId).join(", ")}; wave_file=${wave.file}`,
957
931
  });
958
- writeGlobalDashboard(lanePaths.globalDashboardPath, globalDashboard);
932
+ writeDashboardProjections({ lanePaths, globalDashboard });
959
933
 
960
934
  const runTag = crypto.randomBytes(3).toString("hex");
961
- let derivedState = writeWaveDerivedState({
935
+ let derivedState = buildWaveDerivedState({
962
936
  lanePaths,
963
937
  wave,
964
938
  summariesByAgentId: {},
@@ -966,6 +940,7 @@ export async function runLauncherCli(argv) {
966
940
  attempt: 0,
967
941
  orchestratorId: options.orchestratorId,
968
942
  });
943
+ writeWaveDerivedProjections({ lanePaths, wave, derivedState });
969
944
  const messageBoardPath = derivedState.messageBoardPath;
970
945
  console.log(`Wave message board: ${path.relative(REPO_ROOT, messageBoardPath)}`);
971
946
 
@@ -977,12 +952,15 @@ export async function runLauncherCli(argv) {
977
952
  const residentOrchestratorState = { closed: false };
978
953
 
979
954
  const flushDashboards = () => {
980
- if (!dashboardState) {
955
+ if (!dashboardState && !globalDashboard) {
981
956
  return;
982
957
  }
983
- writeWaveDashboard(dashboardPath, dashboardState);
984
- syncGlobalWaveFromWaveDashboard(globalDashboard, dashboardState);
985
- writeGlobalDashboard(lanePaths.globalDashboardPath, globalDashboard);
958
+ writeDashboardProjections({
959
+ lanePaths,
960
+ globalDashboard,
961
+ dashboardState,
962
+ dashboardPath,
963
+ });
986
964
  };
987
965
 
988
966
  const recordCombinedEvent = ({ level = "info", agentId = null, message }) => {
@@ -1030,6 +1008,9 @@ export async function runLauncherCli(argv) {
1030
1008
  }
1031
1009
  return {
1032
1010
  agent,
1011
+ lane: lanePaths.lane,
1012
+ wave: wave.wave,
1013
+ resultsDir: lanePaths.resultsDir,
1033
1014
  sessionName,
1034
1015
  promptPath: path.join(lanePaths.promptsDir, `${safeName}.prompt.md`),
1035
1016
  logPath: path.join(lanePaths.logsDir, `${safeName}.log`),
@@ -1048,6 +1029,7 @@ export async function runLauncherCli(argv) {
1048
1029
  inboxText: derivedState.inboxesByAgentId[agent.agentId]?.text || "",
1049
1030
  };
1050
1031
  });
1032
+ const roleBindings = resolveWaveRoleBindings(wave, lanePaths, wave.agents);
1051
1033
 
1052
1034
  const refreshDerivedState = (attemptNumber = 0) => {
1053
1035
  const proofRegistry = readWaveProofRegistry(lanePaths, wave.wave);
@@ -1066,7 +1048,7 @@ export async function runLauncherCli(argv) {
1066
1048
  agentIds: agentRuns.map((run) => run.agent.agentId),
1067
1049
  orchestratorId: options.orchestratorId,
1068
1050
  });
1069
- derivedState = writeWaveDerivedState({
1051
+ derivedState = buildWaveDerivedState({
1070
1052
  lanePaths,
1071
1053
  wave,
1072
1054
  agentRuns,
@@ -1075,6 +1057,7 @@ export async function runLauncherCli(argv) {
1075
1057
  attempt: attemptNumber,
1076
1058
  orchestratorId: options.orchestratorId,
1077
1059
  });
1060
+ writeWaveDerivedProjections({ lanePaths, wave, derivedState });
1078
1061
  const controlPlaneLogPath = path.join(
1079
1062
  lanePaths.controlPlaneDir,
1080
1063
  `wave-${wave.wave}.jsonl`,
@@ -1098,6 +1081,20 @@ export async function runLauncherCli(argv) {
1098
1081
  return derivedState;
1099
1082
  };
1100
1083
 
1084
+ let latestReducerSnapshot = null;
1085
+ const refreshReducerSnapshot = (attemptNumber = 0, extra = {}) => {
1086
+ latestReducerSnapshot = computeReducerSnapshot({
1087
+ lanePaths,
1088
+ wave,
1089
+ agentRuns,
1090
+ derivedState,
1091
+ attempt: attemptNumber,
1092
+ options,
1093
+ ...extra,
1094
+ });
1095
+ return latestReducerSnapshot;
1096
+ };
1097
+
1101
1098
  refreshDerivedState(0);
1102
1099
  const launchStateReset = resetPersistedWaveLaunchState(lanePaths, wave.wave, options);
1103
1100
  if (launchStateReset.clearedRelaunchPlan) {
@@ -1187,12 +1184,16 @@ export async function runLauncherCli(argv) {
1187
1184
  };
1188
1185
 
1189
1186
  const proofRegistryForReuse = readWaveProofRegistry(lanePaths, wave.wave);
1190
- const preCompletedAgentIds = selectReusablePreCompletedAgentIds(agentRuns, lanePaths, {
1191
- retryOverride,
1187
+ const initialAttemptPlan = planInitialWaveAttempt({
1188
+ agentRuns,
1189
+ lanePaths,
1192
1190
  wave,
1193
1191
  derivedState,
1194
1192
  proofRegistry: proofRegistryForReuse,
1193
+ retryOverride,
1194
+ persistedRelaunchPlan,
1195
1195
  });
1196
+ const preCompletedAgentIds = initialAttemptPlan.preCompletedAgentIds;
1196
1197
  for (const agentId of preCompletedAgentIds) {
1197
1198
  setWaveDashboardAgent(dashboardState, agentId, {
1198
1199
  state: "completed",
@@ -1201,13 +1202,7 @@ export async function runLauncherCli(argv) {
1201
1202
  detail: "Pre-existing status=0",
1202
1203
  });
1203
1204
  }
1204
- const staleCompletedAgentIds = agentRuns
1205
- .filter(
1206
- (run) =>
1207
- !preCompletedAgentIds.has(run.agent.agentId) &&
1208
- readStatusCodeIfPresent(run.statusPath) === 0,
1209
- )
1210
- .map((run) => run.agent.agentId);
1205
+ const staleCompletedAgentIds = initialAttemptPlan.staleCompletedAgentIds;
1211
1206
  for (const agentId of staleCompletedAgentIds) {
1212
1207
  setWaveDashboardAgent(dashboardState, agentId, {
1213
1208
  state: "pending",
@@ -1283,26 +1278,11 @@ export async function runLauncherCli(argv) {
1283
1278
  }
1284
1279
  }
1285
1280
 
1286
- const availableRuns = agentRuns.filter((run) => !preCompletedAgentIds.has(run.agent.agentId));
1287
- if (
1288
- persistedRelaunchPlan &&
1289
- !persistedRelaunchPlanMatchesCurrentState(
1290
- agentRuns,
1291
- persistedRelaunchPlan,
1292
- lanePaths,
1293
- wave,
1294
- )
1295
- ) {
1281
+ if (initialAttemptPlan.shouldClearPersistedRelaunchPlan) {
1296
1282
  clearWaveRelaunchPlan(lanePaths, wave.wave);
1297
1283
  persistedRelaunchPlan = null;
1298
1284
  }
1299
- const persistedRuns = applyPersistedRelaunchPlan(
1300
- availableRuns,
1301
- persistedRelaunchPlan,
1302
- lanePaths,
1303
- wave,
1304
- );
1305
- const overrideRuns = resolveRetryOverrideRuns(availableRuns, retryOverride, lanePaths, wave);
1285
+ const overrideRuns = initialAttemptPlan.overrideResolution;
1306
1286
  if (overrideRuns.unknownAgentIds.length > 0) {
1307
1287
  appendCoordination({
1308
1288
  event: "retry_override_invalid",
@@ -1315,13 +1295,8 @@ export async function runLauncherCli(argv) {
1315
1295
  clearWaveRetryOverride(lanePaths, wave.wave);
1316
1296
  retryOverride = null;
1317
1297
  }
1318
- let runsToLaunch =
1319
- overrideRuns.unknownAgentIds.length === 0 && overrideRuns.runs.length > 0
1320
- ? overrideRuns.runs
1321
- : persistedRuns.length > 0
1322
- ? persistedRuns
1323
- : selectInitialWaveRuns(availableRuns, lanePaths);
1324
- if (overrideRuns.runs.length > 0) {
1298
+ let runsToLaunch = initialAttemptPlan.selectedRuns;
1299
+ if (initialAttemptPlan.source === "override" && overrideRuns.runs.length > 0) {
1325
1300
  appendCoordination({
1326
1301
  event: "retry_override_applied",
1327
1302
  waves: [wave.wave],
@@ -1338,35 +1313,9 @@ export async function runLauncherCli(argv) {
1338
1313
  let traceAttempt = 1;
1339
1314
  let completionGateSnapshot = null;
1340
1315
  let completionTraceDir = null;
1341
- const recordAttemptState = (attemptNumber, state, data = {}) =>
1342
- appendWaveControlEvent(lanePaths, wave.wave, {
1343
- entityType: "attempt",
1344
- entityId: `wave-${wave.wave}-attempt-${attemptNumber}`,
1345
- action: state,
1346
- source: "launcher",
1347
- actor: "launcher",
1348
- data: {
1349
- attemptId: `wave-${wave.wave}-attempt-${attemptNumber}`,
1350
- attemptNumber,
1351
- state,
1352
- selectedAgentIds: data.selectedAgentIds || [],
1353
- detail: data.detail || null,
1354
- updatedAt: toIsoTimestamp(),
1355
- ...(data.createdAt ? { createdAt: data.createdAt } : {}),
1356
- },
1357
- });
1358
- appendWaveControlEvent(lanePaths, wave.wave, {
1359
- entityType: "wave_run",
1360
- entityId: `wave-${wave.wave}`,
1361
- action: "started",
1362
- source: "launcher",
1363
- actor: "launcher",
1364
- data: {
1365
- waveId: `wave-${wave.wave}`,
1366
- waveNumber: wave.wave,
1367
- agentIds: wave.agents.map((agent) => agent.agentId),
1368
- runVariant: lanePaths.runVariant || "live",
1369
- },
1316
+ recordWaveRunState(lanePaths, wave.wave, "started", {
1317
+ agentIds: wave.agents.map((agent) => agent.agentId),
1318
+ runVariant: lanePaths.runVariant || "live",
1370
1319
  });
1371
1320
 
1372
1321
  while (attempt <= options.maxRetriesPerWave + 1) {
@@ -1379,36 +1328,19 @@ export async function runLauncherCli(argv) {
1379
1328
  recordCombinedEvent({
1380
1329
  message: `Attempt ${attempt}/${options.maxRetriesPerWave + 1}; launching agents: ${runsToLaunch.map((run) => run.agent.agentId).join(", ") || "none"}`,
1381
1330
  });
1382
- recordAttemptState(attempt, "running", {
1331
+ recordAttemptState(lanePaths, wave.wave, attempt, "running", {
1383
1332
  selectedAgentIds: runsToLaunch.map((run) => run.agent.agentId),
1384
1333
  detail: `Launching ${runsToLaunch.map((run) => run.agent.agentId).join(", ") || "no"} agents.`,
1385
1334
  createdAt: toIsoTimestamp(),
1386
1335
  });
1387
1336
 
1388
1337
  const launchedImplementationRuns = runsToLaunch.filter(
1389
- (run) =>
1390
- ![
1391
- lanePaths.contEvalAgentId,
1392
- lanePaths.contQaAgentId,
1393
- lanePaths.integrationAgentId,
1394
- lanePaths.documentationAgentId,
1395
- ].includes(
1396
- run.agent.agentId,
1397
- ),
1338
+ (run) => !isClosureRoleAgentId(run.agent.agentId, roleBindings),
1398
1339
  );
1399
1340
  const closureOnlyRetry =
1400
1341
  runsToLaunch.length > 0 &&
1401
1342
  launchedImplementationRuns.length === 0 &&
1402
- runsToLaunch.every((run) =>
1403
- [
1404
- lanePaths.contEvalAgentId,
1405
- lanePaths.contQaAgentId,
1406
- lanePaths.integrationAgentId,
1407
- lanePaths.documentationAgentId,
1408
- ].includes(
1409
- run.agent.agentId,
1410
- ),
1411
- );
1343
+ runsToLaunch.every((run) => isClosureRoleAgentId(run.agent.agentId, roleBindings));
1412
1344
 
1413
1345
  let failures = [];
1414
1346
  let timedOut = false;
@@ -1463,6 +1395,11 @@ export async function runLauncherCli(argv) {
1463
1395
  agentRateLimitBaseDelaySeconds: options.agentRateLimitBaseDelaySeconds,
1464
1396
  agentRateLimitMaxDelaySeconds: options.agentRateLimitMaxDelaySeconds,
1465
1397
  context7Enabled: options.context7Enabled,
1398
+ attempt,
1399
+ controlPlane: {
1400
+ waveNumber: wave.wave,
1401
+ attempt,
1402
+ },
1466
1403
  });
1467
1404
  runInfo.lastLaunchAttempt = attempt;
1468
1405
  runInfo.lastPromptHash = launchResult?.promptHash || null;
@@ -1474,23 +1411,6 @@ export async function runLauncherCli(argv) {
1474
1411
  state: "running",
1475
1412
  detail: "Session launched",
1476
1413
  });
1477
- appendWaveControlEvent(lanePaths, wave.wave, {
1478
- entityType: "agent_run",
1479
- entityId: `wave-${wave.wave}-attempt-${attempt}-agent-${runInfo.agent.agentId}`,
1480
- action: "started",
1481
- source: "launcher",
1482
- actor: runInfo.agent.agentId,
1483
- attempt,
1484
- data: {
1485
- agentId: runInfo.agent.agentId,
1486
- attemptNumber: attempt,
1487
- sessionName: runInfo.sessionName,
1488
- executorId: runInfo.lastExecutorId,
1489
- promptPath: path.relative(REPO_ROOT, runInfo.promptPath),
1490
- statusPath: path.relative(REPO_ROOT, runInfo.statusPath),
1491
- logPath: path.relative(REPO_ROOT, runInfo.logPath),
1492
- },
1493
- });
1494
1414
  recordCombinedEvent({
1495
1415
  agentId: runInfo.agent.agentId,
1496
1416
  message: `Launched in tmux session ${runInfo.sessionName}`,
@@ -1551,34 +1471,18 @@ export async function runLauncherCli(argv) {
1551
1471
  flushDashboards();
1552
1472
  }
1553
1473
  },
1474
+ {
1475
+ controlPlane: {
1476
+ waveNumber: wave.wave,
1477
+ attempt,
1478
+ },
1479
+ },
1554
1480
  );
1555
1481
  failures = waitResult.failures;
1556
1482
  timedOut = waitResult.timedOut;
1557
1483
  }
1558
1484
 
1559
1485
  materializeAgentExecutionSummaries(wave, agentRuns);
1560
- for (const runInfo of runsToLaunch) {
1561
- const statusRecord = readStatusRecordIfPresent(runInfo.statusPath);
1562
- const action = Number(statusRecord?.code) === 0 ? "completed" : "failed";
1563
- appendWaveControlEvent(lanePaths, wave.wave, {
1564
- entityType: "agent_run",
1565
- entityId: `wave-${wave.wave}-attempt-${attempt}-agent-${runInfo.agent.agentId}`,
1566
- action,
1567
- source: "launcher",
1568
- actor: runInfo.agent.agentId,
1569
- attempt,
1570
- data: {
1571
- agentId: runInfo.agent.agentId,
1572
- attemptNumber: attempt,
1573
- exitCode: statusRecord?.code ?? null,
1574
- completedAt: statusRecord?.completedAt || null,
1575
- promptHash: statusRecord?.promptHash || runInfo.lastPromptHash || null,
1576
- executorId: runInfo.lastExecutorId || null,
1577
- logPath: path.relative(REPO_ROOT, runInfo.logPath),
1578
- statusPath: path.relative(REPO_ROOT, runInfo.statusPath),
1579
- },
1580
- });
1581
- }
1582
1486
  refreshDerivedState(attempt);
1583
1487
  lastLiveCoordinationRefreshAt = Date.now();
1584
1488
  emitCoordinationAlertEvents(derivedState);
@@ -1627,6 +1531,7 @@ export async function runLauncherCli(argv) {
1627
1531
  } else {
1628
1532
  const componentGate = readWaveComponentGate(wave, agentRuns, {
1629
1533
  laneProfile: lanePaths.laneProfile,
1534
+ mode: "live",
1630
1535
  });
1631
1536
  if (!componentGate.ok) {
1632
1537
  if (componentGate.statusCode === "shared-component-sibling-pending") {
@@ -1658,8 +1563,13 @@ export async function runLauncherCli(argv) {
1658
1563
  actionRequested: `Lane ${lanePaths.lane} owners should close the component promotion gap before wave progression.`,
1659
1564
  });
1660
1565
  } else if (launchedImplementationRuns.length > 0) {
1661
- const helperAssignmentBarrier = readWaveAssignmentBarrier(derivedState);
1662
- const dependencyBarrier = readWaveDependencyBarrier(derivedState);
1566
+ const reducerDecision = refreshReducerSnapshot(attempt);
1567
+ const helperAssignmentBarrier =
1568
+ reducerDecision?.reducerState?.gateSnapshot?.helperAssignmentBarrier ||
1569
+ readWaveAssignmentBarrier(derivedState);
1570
+ const dependencyBarrier =
1571
+ reducerDecision?.reducerState?.gateSnapshot?.dependencyBarrier ||
1572
+ readWaveDependencyBarrier(derivedState);
1663
1573
  if (!helperAssignmentBarrier.ok) {
1664
1574
  failures = [
1665
1575
  {
@@ -1708,14 +1618,7 @@ export async function runLauncherCli(argv) {
1708
1618
  lanePaths,
1709
1619
  wave,
1710
1620
  closureRuns: agentRuns.filter((run) =>
1711
- [
1712
- lanePaths.contEvalAgentId,
1713
- lanePaths.contQaAgentId,
1714
- lanePaths.integrationAgentId,
1715
- lanePaths.documentationAgentId,
1716
- ].includes(
1717
- run.agent.agentId,
1718
- ),
1621
+ isClosureRoleAgentId(run.agent.agentId, roleBindings),
1719
1622
  ),
1720
1623
  coordinationLogPath: derivedState.coordinationLogPath,
1721
1624
  refreshDerivedState,
@@ -1740,7 +1643,10 @@ export async function runLauncherCli(argv) {
1740
1643
  }
1741
1644
 
1742
1645
  if (failures.length === 0) {
1743
- const helperAssignmentBarrier = readWaveAssignmentBarrier(derivedState);
1646
+ const reducerDecision = refreshReducerSnapshot(attempt);
1647
+ const helperAssignmentBarrier =
1648
+ reducerDecision?.reducerState?.gateSnapshot?.helperAssignmentBarrier ||
1649
+ readWaveAssignmentBarrier(derivedState);
1744
1650
  if (!helperAssignmentBarrier.ok) {
1745
1651
  failures = [
1746
1652
  {
@@ -1765,7 +1671,10 @@ export async function runLauncherCli(argv) {
1765
1671
  }
1766
1672
 
1767
1673
  if (failures.length === 0) {
1768
- const dependencyBarrier = readWaveDependencyBarrier(derivedState);
1674
+ const reducerDecision = refreshReducerSnapshot(attempt);
1675
+ const dependencyBarrier =
1676
+ reducerDecision?.reducerState?.gateSnapshot?.dependencyBarrier ||
1677
+ readWaveDependencyBarrier(derivedState);
1769
1678
  if (!dependencyBarrier.ok) {
1770
1679
  failures = [
1771
1680
  {
@@ -1790,140 +1699,44 @@ export async function runLauncherCli(argv) {
1790
1699
  }
1791
1700
 
1792
1701
  if (failures.length === 0) {
1793
- const contEvalGate = readWaveContEvalGate(wave, agentRuns, {
1794
- contEvalAgentId: lanePaths.contEvalAgentId,
1795
- mode: "live",
1796
- evalTargets: wave.evalTargets,
1797
- benchmarkCatalogPath: lanePaths.laneProfile?.paths?.benchmarkCatalogPath,
1798
- });
1799
- if (!contEvalGate.ok) {
1800
- failures = [
1801
- {
1802
- agentId: contEvalGate.agentId,
1803
- statusCode: contEvalGate.statusCode,
1804
- logPath: contEvalGate.logPath || path.relative(REPO_ROOT, messageBoardPath),
1805
- },
1806
- ];
1807
- recordCombinedEvent({
1808
- level: "error",
1809
- agentId: contEvalGate.agentId,
1810
- message: `cont-EVAL blocked wave ${wave.wave}: ${contEvalGate.detail}`,
1811
- });
1812
- appendCoordination({
1813
- event: "wave_gate_blocked",
1814
- waves: [wave.wave],
1815
- status: "blocked",
1816
- details: `agent=${contEvalGate.agentId}; reason=${contEvalGate.statusCode}; ${contEvalGate.detail}`,
1817
- actionRequested: `Lane ${lanePaths.lane} owners should resolve cont-EVAL tuning gaps before integration closure.`,
1818
- });
1819
- }
1820
- }
1821
-
1822
- if (failures.length === 0) {
1823
- const integrationGate = readWaveIntegrationGate(wave, agentRuns, {
1824
- integrationAgentId: lanePaths.integrationAgentId,
1825
- requireIntegrationStewardFromWave: lanePaths.requireIntegrationStewardFromWave,
1826
- });
1827
- if (!integrationGate.ok) {
1828
- failures = [
1829
- {
1830
- agentId: integrationGate.agentId,
1831
- statusCode: integrationGate.statusCode,
1832
- logPath: integrationGate.logPath || path.relative(REPO_ROOT, messageBoardPath),
1833
- },
1834
- ];
1835
- recordCombinedEvent({
1836
- level: "error",
1837
- agentId: integrationGate.agentId,
1838
- message: `Integration gate blocked wave ${wave.wave}: ${integrationGate.detail}`,
1839
- });
1840
- appendCoordination({
1841
- event: "wave_gate_blocked",
1842
- waves: [wave.wave],
1843
- status: "blocked",
1844
- details: `agent=${integrationGate.agentId}; reason=${integrationGate.statusCode}; ${integrationGate.detail}`,
1845
- actionRequested: `Lane ${lanePaths.lane} owners should resolve integration contradictions or blockers before documentation and cont-QA closure.`,
1846
- });
1847
- }
1848
- }
1849
-
1850
- if (failures.length === 0) {
1851
- const documentationGate = readWaveDocumentationGate(wave, agentRuns);
1852
- if (!documentationGate.ok) {
1853
- failures = [
1854
- {
1855
- agentId: documentationGate.agentId,
1856
- statusCode: documentationGate.statusCode,
1857
- logPath: documentationGate.logPath || path.relative(REPO_ROOT, messageBoardPath),
1858
- },
1859
- ];
1860
- recordCombinedEvent({
1861
- level: "error",
1862
- agentId: documentationGate.agentId,
1863
- message: `Documentation closure blocked wave ${wave.wave}: ${documentationGate.detail}`,
1864
- });
1865
- appendCoordination({
1866
- event: "wave_gate_blocked",
1867
- waves: [wave.wave],
1868
- status: "blocked",
1869
- details: `agent=${documentationGate.agentId}; reason=${documentationGate.statusCode}; ${documentationGate.detail}`,
1870
- actionRequested: `Lane ${lanePaths.lane} owners should resolve the shared-plan closure state before wave progression.`,
1871
- });
1872
- }
1873
- }
1874
-
1875
- if (failures.length === 0) {
1876
- const componentMatrixGate = readWaveComponentMatrixGate(wave, agentRuns, {
1877
- laneProfile: lanePaths.laneProfile,
1878
- documentationAgentId: lanePaths.documentationAgentId,
1879
- });
1880
- if (!componentMatrixGate.ok) {
1881
- failures = [
1882
- {
1883
- agentId: componentMatrixGate.agentId,
1884
- statusCode: componentMatrixGate.statusCode,
1885
- logPath:
1886
- componentMatrixGate.logPath || path.relative(REPO_ROOT, messageBoardPath),
1887
- },
1888
- ];
1889
- recordCombinedEvent({
1890
- level: "error",
1891
- agentId: componentMatrixGate.agentId,
1892
- message: `Component matrix update blocked wave ${wave.wave}: ${componentMatrixGate.detail}`,
1893
- });
1894
- appendCoordination({
1895
- event: "wave_gate_blocked",
1896
- waves: [wave.wave],
1897
- status: "blocked",
1898
- details: `component=${componentMatrixGate.componentId || "unknown"}; reason=${componentMatrixGate.statusCode}; ${componentMatrixGate.detail}`,
1899
- actionRequested: `Lane ${lanePaths.lane} owners should update the component cutover matrix current levels before wave progression.`,
1900
- });
1901
- }
1902
- }
1903
-
1904
- if (failures.length === 0) {
1905
- const contQaGate = readWaveContQaGate(wave, agentRuns, { mode: "live" });
1906
- if (!contQaGate.ok) {
1702
+ const reducerDecision = refreshReducerSnapshot(attempt);
1703
+ const authoritativeGateSnapshot = reducerDecision?.reducerState?.gateSnapshot;
1704
+ completionGateSnapshot = authoritativeGateSnapshot;
1705
+ const failingGateName = authoritativeGateSnapshot?.overall?.ok === false
1706
+ ? authoritativeGateSnapshot.overall.gate
1707
+ : null;
1708
+ const failingGate =
1709
+ failingGateName && authoritativeGateSnapshot
1710
+ ? authoritativeGateSnapshot[failingGateName]
1711
+ : null;
1712
+ if (failingGateName && failingGate) {
1713
+ if (
1714
+ failingGateName === "componentGate" &&
1715
+ failingGate.statusCode === "shared-component-sibling-pending"
1716
+ ) {
1717
+ applySharedComponentWaitStateToDashboard(failingGate, dashboardState);
1718
+ }
1907
1719
  failures = [
1908
- {
1909
- agentId: contQaGate.agentId,
1910
- statusCode: contQaGate.statusCode,
1911
- logPath: contQaGate.logPath || path.relative(REPO_ROOT, messageBoardPath),
1912
- },
1720
+ buildFailureFromGate(
1721
+ failingGateName,
1722
+ failingGate,
1723
+ path.relative(REPO_ROOT, messageBoardPath),
1724
+ ),
1913
1725
  ];
1914
1726
  recordCombinedEvent({
1915
1727
  level: "error",
1916
- agentId: contQaGate.agentId,
1917
- message: `cont-QA gate blocked wave ${wave.wave}: ${contQaGate.detail}`,
1728
+ agentId: failingGate.agentId || null,
1729
+ message: `${waveGateLabel(failingGateName)} blocked wave ${wave.wave}: ${failingGate.detail}`,
1918
1730
  });
1919
1731
  appendCoordination({
1920
1732
  event: "wave_gate_blocked",
1921
1733
  waves: [wave.wave],
1922
1734
  status: "blocked",
1923
- details: `agent=${contQaGate.agentId}; reason=${contQaGate.statusCode}; ${contQaGate.detail}`,
1924
- actionRequested: `Lane ${lanePaths.lane} owners should resolve the cont-QA gate before wave progression.`,
1735
+ details: `${failingGate.componentId ? `component=${failingGate.componentId}; ` : ""}${failingGate.agentId ? `agent=${failingGate.agentId}; ` : ""}reason=${failingGate.statusCode}; ${failingGate.detail}`,
1736
+ actionRequested: waveGateActionRequested(failingGateName, lanePaths),
1925
1737
  });
1926
- } else {
1738
+ } else if (authoritativeGateSnapshot?.contQaGate?.ok) {
1739
+ const contQaGate = authoritativeGateSnapshot.contQaGate;
1927
1740
  setWaveDashboardAgent(dashboardState, contQaGate.agentId, {
1928
1741
  detail: contQaGate.detail
1929
1742
  ? `Exit 0; cont-QA PASS (${contQaGate.detail})`
@@ -1938,127 +1751,21 @@ export async function runLauncherCli(argv) {
1938
1751
  }
1939
1752
  }
1940
1753
 
1941
- if (failures.length === 0) {
1942
- const infraGate = readWaveInfraGate(agentRuns);
1943
- if (!infraGate.ok) {
1944
- failures = [
1945
- {
1946
- agentId: infraGate.agentId,
1947
- statusCode: infraGate.statusCode,
1948
- logPath: infraGate.logPath || path.relative(REPO_ROOT, messageBoardPath),
1949
- },
1950
- ];
1951
- recordCombinedEvent({
1952
- level: "error",
1953
- agentId: infraGate.agentId,
1954
- message: `Infra gate blocked wave ${wave.wave}: ${infraGate.detail}`,
1955
- });
1956
- appendCoordination({
1957
- event: "wave_gate_blocked",
1958
- waves: [wave.wave],
1959
- status: "blocked",
1960
- details: `agent=${infraGate.agentId}; reason=${infraGate.statusCode}; ${infraGate.detail}`,
1961
- actionRequested: `Lane ${lanePaths.lane} owners should resolve the infra gate before wave progression.`,
1962
- });
1963
- }
1964
- }
1965
-
1966
- if (failures.length === 0) {
1967
- const clarificationBarrier = readClarificationBarrier(derivedState);
1968
- if (!clarificationBarrier.ok) {
1969
- failures = [
1970
- {
1971
- agentId: lanePaths.integrationAgentId,
1972
- statusCode: clarificationBarrier.statusCode,
1973
- logPath: path.relative(REPO_ROOT, messageBoardPath),
1974
- detail: clarificationBarrier.detail,
1975
- },
1976
- ];
1977
- recordCombinedEvent({
1978
- level: "error",
1979
- agentId: lanePaths.integrationAgentId,
1980
- message: `Clarification barrier blocked wave ${wave.wave}: ${clarificationBarrier.detail}`,
1981
- });
1982
- appendCoordination({
1983
- event: "wave_gate_blocked",
1984
- waves: [wave.wave],
1985
- status: "blocked",
1986
- details: `reason=${clarificationBarrier.statusCode}; ${clarificationBarrier.detail}`,
1987
- actionRequested: `Lane ${lanePaths.lane} owners should resolve open clarification chains before wave progression.`,
1988
- });
1989
- }
1990
- }
1991
-
1992
- const structuredSignals = Object.fromEntries(
1993
- agentRuns.map((run) => [run.agent.agentId, parseStructuredSignalsFromLog(run.logPath)]),
1994
- );
1995
- const summariesByAgentId = Object.fromEntries(
1996
- agentRuns
1997
- .map((run) => [run.agent.agentId, readRunExecutionSummary(run, wave)])
1998
- .filter(([, summary]) => summary),
1999
- );
2000
- const gateSnapshot = buildGateSnapshot({
2001
- wave,
2002
- agentRuns,
2003
- derivedState,
2004
- lanePaths,
2005
- validationMode: "live",
2006
- });
1754
+ const gateSnapshot =
1755
+ completionGateSnapshot || refreshReducerSnapshot(attempt).reducerState.gateSnapshot;
2007
1756
  completionGateSnapshot = gateSnapshot;
2008
- try {
2009
- computeReducerSnapshot({
2010
- lanePaths,
2011
- wave,
2012
- agentRuns,
2013
- derivedState,
2014
- attempt,
2015
- options,
2016
- });
2017
- } catch (error) {
2018
- recordCombinedEvent({
2019
- level: "warn",
2020
- agentId: lanePaths.integrationAgentId,
2021
- message: `Reducer shadow snapshot failed for wave ${wave.wave}: ${error instanceof Error ? error.message : String(error)}`,
2022
- });
2023
- }
2024
- const traceDir = writeTraceBundle({
1757
+ const traceProjection = writeWaveAttemptTraceProjection({
2025
1758
  tracesDir: lanePaths.tracesDir,
2026
1759
  lanePaths,
2027
1760
  launcherOptions: options,
2028
1761
  wave,
2029
1762
  attempt: traceAttempt,
2030
1763
  manifest: buildManifest(lanePaths, [wave]),
2031
- coordinationLogPath: derivedState.coordinationLogPath,
2032
- coordinationState: derivedState.coordinationState,
2033
- ledger: derivedState.ledger,
2034
- docsQueue: derivedState.docsQueue,
2035
- capabilityAssignments: derivedState.capabilityAssignments,
2036
- dependencySnapshot: derivedState.dependencySnapshot,
2037
- securitySummary: derivedState.securitySummary,
2038
- integrationSummary: derivedState.integrationSummary,
2039
- integrationMarkdownPath: derivedState.integrationMarkdownPath,
2040
- proofRegistryPath: waveProofRegistryPath(lanePaths, wave.wave),
2041
- controlPlanePath: path.join(lanePaths.controlPlaneDir, `wave-${wave.wave}.jsonl`),
2042
- clarificationTriage: derivedState.clarificationTriage,
2043
1764
  agentRuns,
2044
- structuredSignals,
2045
1765
  gateSnapshot,
2046
- quality: buildQualityMetrics({
2047
- tracesDir: lanePaths.tracesDir,
2048
- wave,
2049
- coordinationState: derivedState.coordinationState,
2050
- integrationSummary: derivedState.integrationSummary,
2051
- ledger: derivedState.ledger,
2052
- docsQueue: derivedState.docsQueue,
2053
- capabilityAssignments: derivedState.capabilityAssignments,
2054
- dependencySnapshot: derivedState.dependencySnapshot,
2055
- summariesByAgentId,
2056
- agentRuns,
2057
- gateSnapshot,
2058
- attempt: traceAttempt,
2059
- coordinationLogPath: derivedState.coordinationLogPath,
2060
- }),
1766
+ derivedState,
2061
1767
  });
1768
+ const traceDir = traceProjection.traceDir;
2062
1769
  completionTraceDir = traceDir;
2063
1770
  appendWaveControlEvent(lanePaths, wave.wave, {
2064
1771
  entityType: "gate",
@@ -2092,7 +1799,7 @@ export async function runLauncherCli(argv) {
2092
1799
  wave,
2093
1800
  );
2094
1801
  if (sharedComponentContinuationRuns.length > 0) {
2095
- recordAttemptState(attempt, "completed", {
1802
+ recordAttemptState(lanePaths, wave.wave, attempt, "completed", {
2096
1803
  selectedAgentIds: runsToLaunch.map((run) => run.agent.agentId),
2097
1804
  detail: `Attempt completed; continuing with sibling owners ${sharedComponentContinuationRuns.map((run) => run.agent.agentId).join(", ")}.`,
2098
1805
  });
@@ -2115,22 +1822,13 @@ export async function runLauncherCli(argv) {
2115
1822
  detail: "Queued for shared component closure",
2116
1823
  });
2117
1824
  }
2118
- writeWaveRelaunchPlan(lanePaths, wave.wave, {
2119
- wave: wave.wave,
1825
+ writeWaveRelaunchProjection({
1826
+ lanePaths,
1827
+ wave,
2120
1828
  attempt,
2121
- phase: derivedState?.ledger?.phase || null,
2122
- selectedAgentIds: nextAgentIds,
2123
- reasonBuckets: relaunchReasonBuckets(runsToLaunch, failures, derivedState),
2124
- executorStates: Object.fromEntries(
2125
- runsToLaunch.map((run) => [run.agent.agentId, run.agent.executorResolved || null]),
2126
- ),
2127
- fallbackHistory: Object.fromEntries(
2128
- runsToLaunch.map((run) => [
2129
- run.agent.agentId,
2130
- run.agent.executorResolved?.executorHistory || [],
2131
- ]),
2132
- ),
2133
- createdAt: toIsoTimestamp(),
1829
+ runs: runsToLaunch,
1830
+ failures,
1831
+ derivedState,
2134
1832
  });
2135
1833
  flushDashboards();
2136
1834
  traceAttempt += 1;
@@ -2138,23 +1836,14 @@ export async function runLauncherCli(argv) {
2138
1836
  }
2139
1837
 
2140
1838
  if (failures.length === 0) {
2141
- recordAttemptState(attempt, "completed", {
1839
+ recordAttemptState(lanePaths, wave.wave, attempt, "completed", {
2142
1840
  selectedAgentIds: runsToLaunch.map((run) => run.agent.agentId),
2143
1841
  detail: "Wave gates passed for this attempt.",
2144
1842
  });
2145
- appendWaveControlEvent(lanePaths, wave.wave, {
2146
- entityType: "wave_run",
2147
- entityId: `wave-${wave.wave}`,
2148
- action: "completed",
2149
- source: "launcher",
2150
- actor: "launcher",
2151
- data: {
2152
- waveId: `wave-${wave.wave}`,
2153
- waveNumber: wave.wave,
2154
- attempts: attempt,
2155
- traceDir: completionTraceDir ? path.relative(REPO_ROOT, completionTraceDir) : null,
2156
- gateSnapshot: completionGateSnapshot,
2157
- },
1843
+ recordWaveRunState(lanePaths, wave.wave, "completed", {
1844
+ attempts: attempt,
1845
+ traceDir: completionTraceDir ? path.relative(REPO_ROOT, completionTraceDir) : null,
1846
+ gateSnapshot: completionGateSnapshot,
2158
1847
  });
2159
1848
  dashboardState.status = "completed";
2160
1849
  recordCombinedEvent({ message: `Wave ${wave.wave} completed successfully.` });
@@ -2168,30 +1857,21 @@ export async function runLauncherCli(argv) {
2168
1857
  }
2169
1858
 
2170
1859
  if (attempt >= options.maxRetriesPerWave + 1) {
2171
- recordAttemptState(attempt, "failed", {
1860
+ recordAttemptState(lanePaths, wave.wave, attempt, "failed", {
2172
1861
  selectedAgentIds: runsToLaunch.map((run) => run.agent.agentId),
2173
1862
  detail: failures
2174
1863
  .map((failure) => `${failure.agentId || "wave"}:${failure.statusCode}`)
2175
1864
  .join(", "),
2176
1865
  });
2177
- appendWaveControlEvent(lanePaths, wave.wave, {
2178
- entityType: "wave_run",
2179
- entityId: `wave-${wave.wave}`,
2180
- action: "failed",
2181
- source: "launcher",
2182
- actor: "launcher",
2183
- data: {
2184
- waveId: `wave-${wave.wave}`,
2185
- waveNumber: wave.wave,
2186
- attempts: attempt,
2187
- traceDir: completionTraceDir ? path.relative(REPO_ROOT, completionTraceDir) : null,
2188
- gateSnapshot: completionGateSnapshot,
2189
- failures: failures.map((failure) => ({
2190
- agentId: failure.agentId || null,
2191
- statusCode: failure.statusCode,
2192
- detail: failure.detail || null,
2193
- })),
2194
- },
1866
+ recordWaveRunState(lanePaths, wave.wave, "failed", {
1867
+ attempts: attempt,
1868
+ traceDir: completionTraceDir ? path.relative(REPO_ROOT, completionTraceDir) : null,
1869
+ gateSnapshot: completionGateSnapshot,
1870
+ failures: failures.map((failure) => ({
1871
+ agentId: failure.agentId || null,
1872
+ statusCode: failure.statusCode,
1873
+ detail: failure.detail || null,
1874
+ })),
2195
1875
  });
2196
1876
  dashboardState.status = timedOut ? "timed_out" : "failed";
2197
1877
  for (const failure of failures) {
@@ -2225,7 +1905,7 @@ export async function runLauncherCli(argv) {
2225
1905
 
2226
1906
  const failedAgentIds = new Set(failures.map((failure) => failure.agentId));
2227
1907
  const failedList = Array.from(failedAgentIds).join(", ");
2228
- recordAttemptState(attempt, "failed", {
1908
+ recordAttemptState(lanePaths, wave.wave, attempt, "failed", {
2229
1909
  selectedAgentIds: runsToLaunch.map((run) => run.agent.agentId),
2230
1910
  detail: failures
2231
1911
  .map((failure) => `${failure.agentId || "wave"}:${failure.statusCode}`)
@@ -2241,20 +1921,19 @@ export async function runLauncherCli(argv) {
2241
1921
  details: `attempt=${attempt + 1}/${options.maxRetriesPerWave + 1}; failed_agents=${failedList}; timed_out=${timedOut ? "yes" : "no"}`,
2242
1922
  actionRequested: `Lane ${lanePaths.lane} owners should inspect failed agent logs before retry completion.`,
2243
1923
  });
2244
- const relaunchResolution = resolveRelaunchRuns(
1924
+ retryOverride = readWaveRetryOverride(lanePaths, wave.wave);
1925
+ const reducerDecision = refreshReducerSnapshot(attempt);
1926
+ const retryPlan = planRetryWaveAttempt({
2245
1927
  agentRuns,
2246
1928
  failures,
2247
1929
  derivedState,
2248
1930
  lanePaths,
2249
1931
  wave,
2250
- );
2251
- retryOverride = readWaveRetryOverride(lanePaths, wave.wave);
2252
- const overrideResolution = resolveRetryOverrideRuns(
2253
- agentRuns,
2254
1932
  retryOverride,
2255
- lanePaths,
2256
- wave,
2257
- );
1933
+ waveState: reducerDecision?.reducerState || null,
1934
+ });
1935
+ const relaunchResolution = retryPlan.relaunchResolution;
1936
+ const overrideResolution = retryPlan.overrideResolution;
2258
1937
  if (overrideResolution.unknownAgentIds.length > 0) {
2259
1938
  appendCoordination({
2260
1939
  event: "retry_override_invalid",
@@ -2266,8 +1945,8 @@ export async function runLauncherCli(argv) {
2266
1945
  });
2267
1946
  clearWaveRetryOverride(lanePaths, wave.wave);
2268
1947
  retryOverride = null;
2269
- } else if (overrideResolution.runs.length > 0) {
2270
- runsToLaunch = overrideResolution.runs;
1948
+ } else if (retryPlan.source === "override" && overrideResolution.runs.length > 0) {
1949
+ runsToLaunch = retryPlan.selectedRuns;
2271
1950
  appendCoordination({
2272
1951
  event: "retry_override_applied",
2273
1952
  waves: [wave.wave],
@@ -2306,7 +1985,7 @@ export async function runLauncherCli(argv) {
2306
1985
  error.exitCode = 43;
2307
1986
  throw error;
2308
1987
  } else {
2309
- runsToLaunch = relaunchResolution.runs;
1988
+ runsToLaunch = retryPlan.selectedRuns;
2310
1989
  }
2311
1990
  if (runsToLaunch.length === 0) {
2312
1991
  clearWaveRelaunchPlan(lanePaths, wave.wave);
@@ -2322,22 +2001,13 @@ export async function runLauncherCli(argv) {
2322
2001
  detail: "Queued for retry",
2323
2002
  });
2324
2003
  }
2325
- writeWaveRelaunchPlan(lanePaths, wave.wave, {
2326
- wave: wave.wave,
2004
+ writeWaveRelaunchProjection({
2005
+ lanePaths,
2006
+ wave,
2327
2007
  attempt: attempt + 1,
2328
- phase: derivedState?.ledger?.phase || null,
2329
- selectedAgentIds: runsToLaunch.map((run) => run.agent.agentId),
2330
- reasonBuckets: relaunchReasonBuckets(runsToLaunch, failures, derivedState),
2331
- executorStates: Object.fromEntries(
2332
- runsToLaunch.map((run) => [run.agent.agentId, run.agent.executorResolved || null]),
2333
- ),
2334
- fallbackHistory: Object.fromEntries(
2335
- runsToLaunch.map((run) => [
2336
- run.agent.agentId,
2337
- run.agent.executorResolved?.executorHistory || [],
2338
- ]),
2339
- ),
2340
- createdAt: toIsoTimestamp(),
2008
+ runs: runsToLaunch,
2009
+ failures,
2010
+ derivedState,
2341
2011
  });
2342
2012
  flushDashboards();
2343
2013
  attempt += 1;
@@ -2390,14 +2060,14 @@ export async function runLauncherCli(argv) {
2390
2060
  if (WAVE_TERMINAL_STATES.has(globalWave.status)) {
2391
2061
  globalWave.completedAt = toIsoTimestamp();
2392
2062
  }
2393
- writeGlobalDashboard(lanePaths.globalDashboardPath, globalDashboard);
2063
+ writeDashboardProjections({ lanePaths, globalDashboard });
2394
2064
  }
2395
2065
  }
2396
2066
  }
2397
2067
 
2398
2068
  globalDashboard.status = "completed";
2399
2069
  recordGlobalDashboardEvent(globalDashboard, { message: "All selected waves completed." });
2400
- writeGlobalDashboard(lanePaths.globalDashboardPath, globalDashboard);
2070
+ writeDashboardProjections({ lanePaths, globalDashboard });
2401
2071
  appendCoordination({
2402
2072
  event: "launcher_finish",
2403
2073
  waves: selectedWavesForCoordination,
@@ -2412,6 +2082,7 @@ export async function runLauncherCli(argv) {
2412
2082
  appendCoordination,
2413
2083
  error,
2414
2084
  );
2085
+ writeDashboardProjections({ lanePaths, globalDashboard });
2415
2086
  throw error;
2416
2087
  } finally {
2417
2088
  if (globalDashboardTerminalAppended && globalDashboardTerminalEntry && !options.keepTerminals) {
@@ -2446,127 +2117,3 @@ export async function runLauncherCli(argv) {
2446
2117
  }
2447
2118
  }
2448
2119
  }
2449
-
2450
- /**
2451
- * Compute and persist a reducer snapshot alongside the traditional gate evaluation.
2452
- * Shadow mode: the reducer runs and its output is written to disk, but decisions
2453
- * still come from the traditional gate readers. This enables comparison and validation.
2454
- *
2455
- * @param {object} params
2456
- * @param {object} params.lanePaths
2457
- * @param {object} params.wave - Wave definition
2458
- * @param {object} params.agentRuns - Array of run info objects
2459
- * @param {object} params.derivedState - Current derived state
2460
- * @param {number} params.attempt - Current attempt number
2461
- * @param {object} params.options - Launcher options
2462
- * @returns {object} { reducerState, resumePlan, snapshotPath }
2463
- */
2464
- export function computeReducerSnapshot({
2465
- lanePaths,
2466
- wave,
2467
- agentRuns,
2468
- derivedState,
2469
- attempt,
2470
- options = {},
2471
- }) {
2472
- // Build agentResults from agentRuns
2473
- const agentResults = {};
2474
- for (const run of agentRuns) {
2475
- const summary = readRunExecutionSummary(run, wave);
2476
- if (summary) {
2477
- agentResults[run.agent.agentId] = summary;
2478
- }
2479
- }
2480
-
2481
- // Load canonical event sources
2482
- const controlPlaneLogPath = path.join(
2483
- lanePaths.controlPlaneDir,
2484
- `wave-${wave.wave}.jsonl`,
2485
- );
2486
- const controlPlaneEvents = fs.existsSync(controlPlaneLogPath)
2487
- ? readControlPlaneEvents(controlPlaneLogPath)
2488
- : [];
2489
-
2490
- const coordinationLogPath = path.join(
2491
- lanePaths.coordinationDir,
2492
- `wave-${wave.wave}.jsonl`,
2493
- );
2494
- const coordinationRecords = fs.existsSync(coordinationLogPath)
2495
- ? readMaterializedCoordinationState(coordinationLogPath)
2496
- : null;
2497
-
2498
- const feedbackRequests = readWaveHumanFeedbackRequests({
2499
- feedbackRequestsDir: lanePaths.feedbackRequestsDir,
2500
- lane: lanePaths.lane,
2501
- waveNumber: wave.wave,
2502
- agentIds: (agentRuns || []).map((run) => run.agent.agentId),
2503
- orchestratorId: options.orchestratorId,
2504
- });
2505
-
2506
- // Build dependency tickets from derivedState
2507
- const dependencyTickets = derivedState?.dependencySnapshot || null;
2508
-
2509
- // Run the reducer
2510
- const reducerState = reduceWaveState({
2511
- controlPlaneEvents,
2512
- coordinationRecords: coordinationRecords?.latestRecords || [],
2513
- agentResults,
2514
- waveDefinition: wave,
2515
- dependencyTickets,
2516
- feedbackRequests: feedbackRequests || [],
2517
- laneConfig: {
2518
- lane: lanePaths.lane,
2519
- contQaAgentId: lanePaths.contQaAgentId || "A0",
2520
- contEvalAgentId: lanePaths.contEvalAgentId || "E0",
2521
- integrationAgentId: lanePaths.integrationAgentId || "A8",
2522
- documentationAgentId: lanePaths.documentationAgentId || "A9",
2523
- validationMode: "live",
2524
- evalTargets: wave.evalTargets,
2525
- benchmarkCatalogPath: lanePaths.laneProfile?.paths?.benchmarkCatalogPath,
2526
- laneProfile: lanePaths.laneProfile,
2527
- requireIntegrationStewardFromWave: lanePaths.requireIntegrationStewardFromWave,
2528
- capabilityRouting: lanePaths.capabilityRouting,
2529
- },
2530
- });
2531
-
2532
- // Build resume plan
2533
- const resumePlan = buildResumePlan(reducerState, {
2534
- waveDefinition: wave,
2535
- lanePaths,
2536
- });
2537
-
2538
- // Persist snapshot
2539
- const stateDir = path.join(lanePaths.stateDir, "reducer");
2540
- ensureDirectory(stateDir);
2541
- const snapshotPath = path.join(stateDir, `wave-${wave.wave}.json`);
2542
- writeWaveStateSnapshot(snapshotPath, {
2543
- ...reducerState,
2544
- attempt,
2545
- resumePlan,
2546
- }, {
2547
- lane: lanePaths.lane,
2548
- wave: wave.wave,
2549
- });
2550
-
2551
- return {
2552
- reducerState,
2553
- resumePlan,
2554
- snapshotPath,
2555
- };
2556
- }
2557
-
2558
- /**
2559
- * Read a previously persisted reducer snapshot from disk.
2560
- *
2561
- * @param {object} lanePaths
2562
- * @param {number} waveNumber
2563
- * @returns {object|null} The persisted snapshot, or null if not found
2564
- */
2565
- export function readPersistedReducerSnapshot(lanePaths, waveNumber) {
2566
- const stateDir = path.join(lanePaths.stateDir, "reducer");
2567
- const snapshotPath = path.join(stateDir, `wave-${waveNumber}.json`);
2568
- return readWaveStateSnapshot(snapshotPath, {
2569
- lane: lanePaths.lane,
2570
- wave: waveNumber,
2571
- });
2572
- }