@chllming/wave-orchestration 0.7.3 → 0.8.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 (48) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +14 -13
  3. package/docs/README.md +3 -1
  4. package/docs/agents/wave-cont-qa-role.md +1 -0
  5. package/docs/agents/wave-integration-role.md +1 -0
  6. package/docs/agents/wave-launcher-role.md +4 -1
  7. package/docs/agents/wave-orchestrator-role.md +5 -3
  8. package/docs/concepts/operating-modes.md +1 -1
  9. package/docs/concepts/runtime-agnostic-orchestration.md +5 -4
  10. package/docs/concepts/what-is-a-wave.md +12 -10
  11. package/docs/guides/author-and-run-waves.md +3 -3
  12. package/docs/plans/architecture-hardening-migration.md +206 -0
  13. package/docs/plans/current-state.md +5 -3
  14. package/docs/plans/examples/wave-example-live-proof.md +1 -1
  15. package/docs/plans/master-plan.md +2 -2
  16. package/docs/plans/migration.md +12 -2
  17. package/docs/plans/wave-orchestrator.md +10 -8
  18. package/docs/reference/coordination-and-closure.md +8 -4
  19. package/docs/reference/npmjs-trusted-publishing.md +2 -2
  20. package/docs/reference/sample-waves.md +4 -4
  21. package/docs/reference/skills.md +3 -0
  22. package/docs/reference/wave-control.md +4 -2
  23. package/docs/research/coordination-failure-review.md +4 -4
  24. package/docs/roadmap.md +1 -1
  25. package/package.json +1 -1
  26. package/releases/manifest.json +19 -0
  27. package/scripts/wave-orchestrator/agent-state.mjs +405 -89
  28. package/scripts/wave-orchestrator/contradiction-entity.mjs +487 -0
  29. package/scripts/wave-orchestrator/launcher-gates.mjs +79 -11
  30. package/scripts/wave-orchestrator/launcher-retry.mjs +36 -6
  31. package/scripts/wave-orchestrator/launcher.mjs +163 -2
  32. package/scripts/wave-orchestrator/task-entity.mjs +425 -51
  33. package/scripts/wave-orchestrator/wave-control-schema.mjs +2 -0
  34. package/scripts/wave-orchestrator/wave-state-reducer.mjs +260 -111
  35. package/skills/README.md +3 -0
  36. package/skills/repo-coding-rules/SKILL.md +1 -1
  37. package/skills/role-cont-qa/SKILL.md +2 -2
  38. package/skills/role-deploy/SKILL.md +1 -1
  39. package/skills/role-documentation/SKILL.md +1 -1
  40. package/skills/role-implementation/SKILL.md +1 -1
  41. package/skills/role-infra/SKILL.md +1 -1
  42. package/skills/role-integration/SKILL.md +2 -2
  43. package/skills/role-security/SKILL.md +1 -1
  44. package/skills/runtime-claude/SKILL.md +1 -1
  45. package/skills/runtime-codex/SKILL.md +1 -1
  46. package/skills/runtime-opencode/SKILL.md +1 -1
  47. package/skills/wave-core/SKILL.md +14 -6
  48. package/skills/wave-core/references/marker-syntax.md +1 -1
@@ -226,6 +226,33 @@ export function relaunchReasonBuckets(runs, failures, derivedState) {
226
226
  };
227
227
  }
228
228
 
229
+ const HUMAN_INPUT_BLOCKER_KINDS = new Set([
230
+ "human-input",
231
+ "human-feedback",
232
+ "human-escalation",
233
+ ]);
234
+
235
+ function isHumanInputBlocker(blocker) {
236
+ return HUMAN_INPUT_BLOCKER_KINDS.has(String(blocker?.kind || "").trim().toLowerCase());
237
+ }
238
+
239
+ function normalizeRetryTargets(retryTargetSet) {
240
+ if (Array.isArray(retryTargetSet)) {
241
+ return retryTargetSet;
242
+ }
243
+ if (Array.isArray(retryTargetSet?.targets)) {
244
+ return retryTargetSet.targets;
245
+ }
246
+ if (Array.isArray(retryTargetSet?.agentIds)) {
247
+ return retryTargetSet.agentIds.map((agentId) => ({
248
+ agentId,
249
+ reason: retryTargetSet.reason || null,
250
+ retryOverride: retryTargetSet.retryOverride || null,
251
+ }));
252
+ }
253
+ return [];
254
+ }
255
+
229
256
  export function applySharedComponentWaitStateToDashboard(componentGate, dashboardState) {
230
257
  const waitingSummary = (componentGate?.waitingOnAgentIds || []).join("/");
231
258
  if (!waitingSummary) {
@@ -769,7 +796,7 @@ function classifyResumeReason(waveState) {
769
796
  return "all-gates-pass";
770
797
  }
771
798
  const humanBlockers = (waveState.openBlockers || []).filter(
772
- (blocker) => blocker.kind === "human-input",
799
+ (blocker) => isHumanInputBlocker(blocker),
773
800
  );
774
801
  if (humanBlockers.length > 0) {
775
802
  return "human-request";
@@ -792,11 +819,14 @@ function classifyResumeReason(waveState) {
792
819
 
793
820
  function collectResumeHumanInputBlockers(waveState) {
794
821
  return (waveState.openBlockers || [])
795
- .filter((b) => b.kind === "human-input")
822
+ .filter((b) => isHumanInputBlocker(b))
796
823
  .map((b) => ({
797
824
  taskId: b.taskId || b.id || null,
798
825
  title: b.title || b.detail || null,
799
- assigneeAgentId: b.agentId || b.assigneeAgentId || null,
826
+ assigneeAgentId:
827
+ b.agentId ||
828
+ b.assigneeAgentId ||
829
+ (Array.isArray(b.blockedAgentIds) ? b.blockedAgentIds[0] || null : null),
800
830
  }));
801
831
  }
802
832
 
@@ -813,13 +843,13 @@ function collectResumeGateBlockers(gateSnapshot) {
813
843
  }
814
844
 
815
845
  function collectResumeExecutorChanges(waveState) {
816
- const retryTargets = waveState.retryTargetSet || [];
817
- return (Array.isArray(retryTargets) ? retryTargets : [])
846
+ const retryTargets = normalizeRetryTargets(waveState.retryTargetSet);
847
+ return retryTargets
818
848
  .filter((t) => t.reason === "rate-limit-exhausted" || t.reason === "rate-limit" || t.retriesExhausted === true)
819
849
  .map((t) => ({
820
850
  agentId: t.agentId,
821
851
  currentExecutor: t.currentExecutor || t.executor || null,
822
- suggestedFallback: "claude",
852
+ suggestedFallback: t.retryOverride?.executorId || "claude",
823
853
  reason: t.reason || "rate-limit-exhausted",
824
854
  }));
825
855
  }
@@ -145,9 +145,11 @@ import {
145
145
  resolveRetryOverrideRuns,
146
146
  waveRelaunchPlanPath,
147
147
  } from "./retry-control.mjs";
148
- import { appendWaveControlEvent } from "./control-plane.mjs";
148
+ import { appendWaveControlEvent, readControlPlaneEvents } from "./control-plane.mjs";
149
+ import { materializeContradictionsFromControlPlaneEvents } from "./contradiction-entity.mjs";
149
150
  import { buildQualityMetrics, writeTraceBundle } from "./traces.mjs";
150
151
  import { flushWaveControlQueue } from "./wave-control-client.mjs";
152
+ import { reduceWaveState } from "./wave-state-reducer.mjs";
151
153
  import { triageClarificationRequests } from "./clarification-triage.mjs";
152
154
  import { readProjectProfile, resolveDefaultTerminalSurface } from "./project-profile.mjs";
153
155
  import {
@@ -167,9 +169,11 @@ import {
167
169
  writeDependencySnapshotMarkdown,
168
170
  } from "./routing-state.mjs";
169
171
  import {
172
+ readWaveStateSnapshot,
170
173
  writeAssignmentSnapshot,
171
174
  writeDependencySnapshot,
172
175
  writeRelaunchPlan,
176
+ writeWaveStateSnapshot,
173
177
  } from "./artifact-schemas.mjs";
174
178
  import {
175
179
  collectUnexpectedSessionFailures as collectUnexpectedSessionFailuresImpl,
@@ -238,6 +242,7 @@ export {
238
242
 
239
243
  // --- Re-exports from launcher-retry.mjs ---
240
244
  import {
245
+ buildResumePlan,
241
246
  readWaveRelaunchPlan,
242
247
  writeWaveRelaunchPlan,
243
248
  clearWaveRelaunchPlan,
@@ -1070,6 +1075,18 @@ export async function runLauncherCli(argv) {
1070
1075
  attempt: attemptNumber,
1071
1076
  orchestratorId: options.orchestratorId,
1072
1077
  });
1078
+ const controlPlaneLogPath = path.join(
1079
+ lanePaths.controlPlaneDir,
1080
+ `wave-${wave.wave}.jsonl`,
1081
+ );
1082
+ const controlPlaneEvents = fs.existsSync(controlPlaneLogPath)
1083
+ ? readControlPlaneEvents(controlPlaneLogPath)
1084
+ : [];
1085
+ derivedState = {
1086
+ ...derivedState,
1087
+ controlPlaneEvents,
1088
+ contradictions: materializeContradictionsFromControlPlaneEvents(controlPlaneEvents),
1089
+ };
1073
1090
  for (const run of agentRuns) {
1074
1091
  run.messageBoardSnapshot = derivedState.messageBoardText;
1075
1092
  run.sharedSummaryPath = derivedState.sharedSummaryPath;
@@ -1988,6 +2005,22 @@ export async function runLauncherCli(argv) {
1988
2005
  validationMode: "live",
1989
2006
  });
1990
2007
  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
+ }
1991
2024
  const traceDir = writeTraceBundle({
1992
2025
  tracesDir: lanePaths.tracesDir,
1993
2026
  lanePaths,
@@ -2039,7 +2072,11 @@ export async function runLauncherCli(argv) {
2039
2072
  traceDir: path.relative(REPO_ROOT, traceDir),
2040
2073
  gateSnapshot,
2041
2074
  qualitySummary: {
2042
- contradictionCount: gateSnapshot?.integration?.conflictingClaims?.length || 0,
2075
+ contradictionCount: Array.isArray(derivedState?.contradictions)
2076
+ ? derivedState.contradictions.length
2077
+ : derivedState?.contradictions instanceof Map
2078
+ ? derivedState.contradictions.size
2079
+ : 0,
2043
2080
  finalRecommendation: derivedState.integrationSummary?.recommendation || "unknown",
2044
2081
  },
2045
2082
  },
@@ -2409,3 +2446,127 @@ export async function runLauncherCli(argv) {
2409
2446
  }
2410
2447
  }
2411
2448
  }
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
+ }