@chllming/wave-orchestration 0.8.5 → 0.8.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +14 -9
  3. package/docs/README.md +3 -1
  4. package/docs/context7/bundles.json +19 -20
  5. package/docs/context7/planner-agent/README.md +4 -1
  6. package/docs/guides/author-and-run-waves.md +4 -1
  7. package/docs/guides/planner.md +3 -1
  8. package/docs/guides/signal-wrappers.md +165 -0
  9. package/docs/guides/terminal-surfaces.md +15 -0
  10. package/docs/plans/context7-wave-orchestrator.md +24 -7
  11. package/docs/plans/current-state.md +7 -3
  12. package/docs/plans/end-state-architecture.md +16 -4
  13. package/docs/plans/examples/wave-example-design-handoff.md +1 -1
  14. package/docs/plans/examples/wave-example-live-proof.md +1 -1
  15. package/docs/plans/migration.md +179 -72
  16. package/docs/plans/wave-orchestrator.md +11 -5
  17. package/docs/reference/cli-reference.md +21 -4
  18. package/docs/reference/coordination-and-closure.md +26 -5
  19. package/docs/reference/live-proof-waves.md +9 -0
  20. package/docs/reference/npmjs-trusted-publishing.md +2 -2
  21. package/docs/reference/runtime-config/README.md +9 -3
  22. package/docs/reference/sample-waves.md +5 -5
  23. package/docs/reference/skills.md +9 -1
  24. package/docs/reference/wave-control.md +18 -0
  25. package/docs/reference/wave-planning-lessons.md +7 -1
  26. package/docs/research/coordination-failure-review.md +6 -6
  27. package/package.json +1 -1
  28. package/releases/manifest.json +38 -0
  29. package/scripts/context7-api-check.sh +57 -13
  30. package/scripts/wave-orchestrator/agent-state.mjs +42 -0
  31. package/scripts/wave-orchestrator/autonomous.mjs +42 -6
  32. package/scripts/wave-orchestrator/clarification-triage.mjs +4 -3
  33. package/scripts/wave-orchestrator/control-cli.mjs +145 -11
  34. package/scripts/wave-orchestrator/control-plane.mjs +12 -1
  35. package/scripts/wave-orchestrator/coordination-store.mjs +124 -4
  36. package/scripts/wave-orchestrator/coordination.mjs +35 -0
  37. package/scripts/wave-orchestrator/executors.mjs +11 -6
  38. package/scripts/wave-orchestrator/gate-engine.mjs +5 -5
  39. package/scripts/wave-orchestrator/install.mjs +2 -0
  40. package/scripts/wave-orchestrator/launcher-runtime.mjs +12 -1
  41. package/scripts/wave-orchestrator/launcher.mjs +236 -0
  42. package/scripts/wave-orchestrator/ledger.mjs +14 -12
  43. package/scripts/wave-orchestrator/reducer-snapshot.mjs +8 -6
  44. package/scripts/wave-orchestrator/retry-engine.mjs +19 -11
  45. package/scripts/wave-orchestrator/routing-state.mjs +50 -3
  46. package/scripts/wave-orchestrator/session-supervisor.mjs +119 -10
  47. package/scripts/wave-orchestrator/shared.mjs +1 -0
  48. package/scripts/wave-orchestrator/signals.mjs +681 -0
  49. package/scripts/wave-orchestrator/task-entity.mjs +4 -4
  50. package/scripts/wave-orchestrator/terminals.mjs +14 -14
  51. package/scripts/wave-orchestrator/wave-control-schema.mjs +2 -0
  52. package/scripts/wave-orchestrator/wave-files.mjs +15 -21
  53. package/scripts/wave-orchestrator/wave-state-reducer.mjs +72 -5
  54. package/scripts/wave-status.sh +200 -0
  55. package/scripts/wave-watch.sh +200 -0
  56. package/skills/README.md +3 -0
  57. package/skills/signal-hygiene/SKILL.md +51 -0
  58. package/skills/signal-hygiene/skill.json +20 -0
@@ -27,8 +27,13 @@ import {
27
27
  maybeAnnouncePackageUpdate,
28
28
  WAVE_SUPPRESS_UPDATE_NOTICE_ENV,
29
29
  } from "./package-update-notice.mjs";
30
+ import { buildTaskSnapshots } from "./control-plane.mjs";
31
+ import { readWaveHumanFeedbackRequests } from "./coordination.mjs";
30
32
  import { readRunState } from "./wave-files.mjs";
31
- import { readDependencyTickets } from "./coordination-store.mjs";
33
+ import {
34
+ readDependencyTickets,
35
+ readMaterializedCoordinationState,
36
+ } from "./coordination-store.mjs";
32
37
  import { readWaveLedger } from "./ledger.mjs";
33
38
 
34
39
  const AUTONOMOUS_EXECUTOR_MODES = SUPPORTED_EXECUTOR_MODES.filter((mode) => mode !== "local");
@@ -249,7 +254,38 @@ function requiredInboundDependenciesOpen(lanePaths, lane) {
249
254
  });
250
255
  }
251
256
 
252
- function pendingHumanItemsForWave(lanePaths, wave) {
257
+ function liveBlockingHumanItemsForWave(lanePaths, lane, wave) {
258
+ if (!lanePaths?.coordinationDir || !lanePaths?.feedbackRequestsDir) {
259
+ return null;
260
+ }
261
+ const coordinationState = readMaterializedCoordinationState(
262
+ path.join(lanePaths.coordinationDir, `wave-${wave}.jsonl`),
263
+ );
264
+ const feedbackRequests = readWaveHumanFeedbackRequests({
265
+ feedbackRequestsDir: lanePaths.feedbackRequestsDir,
266
+ lane,
267
+ waveNumber: wave,
268
+ agentIds: [],
269
+ orchestratorId: "",
270
+ });
271
+ return buildTaskSnapshots({
272
+ coordinationState,
273
+ feedbackRequests,
274
+ })
275
+ .filter(
276
+ (task) =>
277
+ ["human-input", "escalation"].includes(task.taskType) &&
278
+ task.blocking !== false &&
279
+ ["open", "working", "input-required"].includes(task.state),
280
+ )
281
+ .map((task) => task.taskId);
282
+ }
283
+
284
+ function pendingHumanItemsForWave(lanePaths, lane, wave) {
285
+ const liveItems = liveBlockingHumanItemsForWave(lanePaths, lane, wave);
286
+ if (Array.isArray(liveItems)) {
287
+ return liveItems;
288
+ }
253
289
  const existingLedger = readWaveLedger(path.join(lanePaths.ledgerDir, `wave-${wave}.json`));
254
290
  return [
255
291
  ...(existingLedger?.humanFeedback || []),
@@ -257,7 +293,7 @@ function pendingHumanItemsForWave(lanePaths, wave) {
257
293
  ];
258
294
  }
259
295
 
260
- function pendingHumanItemsForLane(lanePaths) {
296
+ function pendingHumanItemsForLane(lanePaths, lane) {
261
297
  if (!fs.existsSync(lanePaths.ledgerDir)) {
262
298
  return [];
263
299
  }
@@ -271,7 +307,7 @@ function pendingHumanItemsForLane(lanePaths) {
271
307
  .filter((item) => Number.isFinite(item.wave))
272
308
  .sort((left, right) => left.wave - right.wave)
273
309
  .flatMap((item) =>
274
- pendingHumanItemsForWave(lanePaths, item.wave).map((id) => ({
310
+ pendingHumanItemsForWave(lanePaths, lane, item.wave).map((id) => ({
275
311
  wave: item.wave,
276
312
  id,
277
313
  })),
@@ -292,7 +328,7 @@ export function readAutonomousBarrier(lanePaths, lane, wave = null) {
292
328
  };
293
329
  }
294
330
  if (wave === null) {
295
- const pendingHumanEntries = pendingHumanItemsForLane(lanePaths);
331
+ const pendingHumanEntries = pendingHumanItemsForLane(lanePaths, lane);
296
332
  if (pendingHumanEntries.length > 0) {
297
333
  return {
298
334
  kind: "human-input",
@@ -303,7 +339,7 @@ export function readAutonomousBarrier(lanePaths, lane, wave = null) {
303
339
  }
304
340
  return null;
305
341
  }
306
- const pendingHumanItems = pendingHumanItemsForWave(lanePaths, wave);
342
+ const pendingHumanItems = pendingHumanItemsForWave(lanePaths, lane, wave);
307
343
  if (pendingHumanItems.length > 0) {
308
344
  return {
309
345
  kind: "human-input",
@@ -4,6 +4,7 @@ import {
4
4
  appendCoordinationRecord,
5
5
  clarificationClosureCondition,
6
6
  clarificationLinkedRequests,
7
+ coordinationRecordBlocksWave,
7
8
  isOpenCoordinationStatus,
8
9
  readMaterializedCoordinationState,
9
10
  } from "./coordination-store.mjs";
@@ -467,14 +468,14 @@ export function triageClarificationRequests({
467
468
  ensureDirectory(lanePaths.feedbackTriageDir);
468
469
  const triagePath = triageLogPath(lanePaths, wave.wave);
469
470
  const openClarifications = (coordinationState?.clarifications || []).filter((record) =>
470
- isOpenCoordinationStatus(record.status),
471
+ coordinationRecordBlocksWave(record),
471
472
  );
472
473
  let changed = false;
473
474
 
474
475
  for (const record of openClarifications) {
475
476
  const linkedRequests = clarificationLinkedRequests(coordinationState, record.id);
476
477
  const openLinkedRequests = linkedRequests.filter((entry) =>
477
- isOpenCoordinationStatus(entry.status),
478
+ coordinationRecordBlocksWave(entry),
478
479
  );
479
480
  const openAckPendingLinkedRequests = openLinkedRequests.filter(
480
481
  (entry) => entry.status === "open",
@@ -487,7 +488,7 @@ export function triageClarificationRequests({
487
488
  const openEscalations = (coordinationState?.humanEscalations || []).filter(
488
489
  (entry) =>
489
490
  entry.closureCondition === clarificationClosureCondition(record.id) &&
490
- isOpenCoordinationStatus(entry.status),
491
+ coordinationRecordBlocksWave(entry),
491
492
  );
492
493
  if (resolvedLinkedRequest || resolvedEscalation) {
493
494
  if (openEscalations.length > 0) {
@@ -1,6 +1,13 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import { appendCoordinationRecord, clarificationClosureCondition, clarificationLinkedRequests, isOpenCoordinationStatus, readMaterializedCoordinationState, updateSeedRecords } from "./coordination-store.mjs";
3
+ import {
4
+ appendCoordinationRecord,
5
+ clarificationClosureCondition,
6
+ clarificationLinkedRequests,
7
+ isOpenCoordinationStatus,
8
+ readMaterializedCoordinationState,
9
+ updateSeedRecords,
10
+ } from "./coordination-store.mjs";
4
11
  import { answerFeedbackRequest, createFeedbackRequest } from "./feedback.mjs";
5
12
  import { readWaveHumanFeedbackRequests } from "./coordination.mjs";
6
13
  import { readWaveLedger } from "./ledger.mjs";
@@ -18,6 +25,7 @@ import {
18
25
  REPO_ROOT,
19
26
  sanitizeAdhocRunId,
20
27
  sanitizeLaneName,
28
+ toIsoTimestamp,
21
29
  } from "./shared.mjs";
22
30
  import {
23
31
  appendWaveControlEvent,
@@ -36,6 +44,10 @@ import { readWaveRelaunchPlanSnapshot, readWaveRetryOverride, resolveRetryOverri
36
44
  import { flushWaveControlQueue, readWaveControlQueueState } from "./wave-control-client.mjs";
37
45
  import { readAgentExecutionSummary, validateImplementationSummary } from "./agent-state.mjs";
38
46
  import { isContEvalReportOnlyAgent, isSecurityReviewAgent } from "./role-helpers.mjs";
47
+ import {
48
+ buildSignalStatusLine,
49
+ syncWaveSignalProjections,
50
+ } from "./signals.mjs";
39
51
 
40
52
  function printUsage() {
41
53
  console.log(`Usage:
@@ -46,7 +58,7 @@ function printUsage() {
46
58
  wave control task create --lane <lane> --wave <n> --agent <id> --kind <request|blocker|clarification|handoff|evidence|claim|decision|human-input> --summary <text> [options]
47
59
  wave control task list --lane <lane> --wave <n> [--agent <id>] [--json]
48
60
  wave control task get --lane <lane> --wave <n> --id <task-id> [--json]
49
- wave control task act <start|resolve|dismiss|cancel|reassign|answer|escalate> --lane <lane> --wave <n> --id <task-id> [options]
61
+ wave control task act <start|resolve|dismiss|cancel|reassign|answer|escalate|defer|mark-advisory|mark-stale|resolve-policy> --lane <lane> --wave <n> --id <task-id> [options]
50
62
 
51
63
  wave control rerun request --lane <lane> --wave <n> [--agent <id> ...] [--resume-cursor <cursor>] [--reuse-attempt <id> ...] [--reuse-proof <id> ...] [--reuse-derived-summaries <true|false>] [--invalidate-component <id> ...] [--clear-reuse <id> ...] [--preserve-reuse <id> ...] [--requested-by <name>] [--reason <text>] [--json]
52
64
  wave control rerun get --lane <lane> --wave <n> [--json]
@@ -90,6 +102,8 @@ function parseArgs(argv) {
90
102
  detail: "",
91
103
  targets: [],
92
104
  priority: "normal",
105
+ blocking: null,
106
+ blockerSeverity: "",
93
107
  dependsOn: [],
94
108
  artifactRefs: [],
95
109
  status: "open",
@@ -151,6 +165,10 @@ function parseArgs(argv) {
151
165
  options.targets.push(String(args[++i] || "").trim());
152
166
  } else if (arg === "--priority") {
153
167
  options.priority = String(args[++i] || "").trim();
168
+ } else if (arg === "--blocking") {
169
+ options.blocking = normalizeBooleanish(args[++i], true);
170
+ } else if (arg === "--severity") {
171
+ options.blockerSeverity = String(args[++i] || "").trim();
154
172
  } else if (arg === "--depends-on") {
155
173
  options.dependsOn.push(String(args[++i] || "").trim());
156
174
  } else if (arg === "--artifact") {
@@ -260,7 +278,10 @@ const BLOCKING_TASK_TYPES = new Set([
260
278
  ]);
261
279
 
262
280
  function taskBlocksAgent(task) {
263
- return BLOCKING_TASK_TYPES.has(String(task?.taskType || "").trim().toLowerCase());
281
+ return (
282
+ BLOCKING_TASK_TYPES.has(String(task?.taskType || "").trim().toLowerCase()) &&
283
+ task?.blocking !== false
284
+ );
264
285
  }
265
286
 
266
287
  function assignmentRelevantToAgent(assignment, agentId = "") {
@@ -458,7 +479,9 @@ function buildBlockingEdge({
458
479
  selectionTargetsAgent(task.assigneeAgentId, attemptSelection)
459
480
  );
460
481
  });
461
- const pendingHuman = scopedTasks.find((task) => task.state === "input-required");
482
+ const pendingHuman = scopedTasks.find(
483
+ (task) => task.state === "input-required" && task.blocking !== false,
484
+ );
462
485
  if (pendingHuman) {
463
486
  return {
464
487
  kind: "human-input",
@@ -468,7 +491,10 @@ function buildBlockingEdge({
468
491
  };
469
492
  }
470
493
  const escalation = scopedTasks.find(
471
- (task) => task.taskType === "escalation" && ["open", "working"].includes(task.state),
494
+ (task) =>
495
+ task.taskType === "escalation" &&
496
+ task.blocking !== false &&
497
+ ["open", "working"].includes(task.state),
472
498
  );
473
499
  if (escalation) {
474
500
  return {
@@ -479,7 +505,10 @@ function buildBlockingEdge({
479
505
  };
480
506
  }
481
507
  const clarification = scopedTasks.find(
482
- (task) => task.taskType === "clarification" && ["open", "working"].includes(task.state),
508
+ (task) =>
509
+ task.taskType === "clarification" &&
510
+ task.blocking !== false &&
511
+ ["open", "working"].includes(task.state),
483
512
  );
484
513
  if (clarification) {
485
514
  return {
@@ -560,7 +589,10 @@ function buildBlockingEdge({
560
589
  };
561
590
  }
562
591
  const blocker = scopedTasks.find(
563
- (task) => task.taskType === "blocker" && ["open", "working"].includes(task.state),
592
+ (task) =>
593
+ task.taskType === "blocker" &&
594
+ task.blocking !== false &&
595
+ ["open", "working"].includes(task.state),
564
596
  );
565
597
  if (blocker) {
566
598
  return {
@@ -571,7 +603,10 @@ function buildBlockingEdge({
571
603
  };
572
604
  }
573
605
  const request = scopedTasks.find(
574
- (task) => task.taskType === "request" && ["open", "working"].includes(task.state),
606
+ (task) =>
607
+ task.taskType === "request" &&
608
+ task.blocking !== false &&
609
+ ["open", "working"].includes(task.state),
575
610
  );
576
611
  if (request) {
577
612
  return {
@@ -676,6 +711,7 @@ export function buildControlStatusPayload({ lanePaths, wave, agentId = "" }) {
676
711
  proofBundles: (proofRegistry?.entries || []).filter(
677
712
  (entry) => !agentId || entry.agentId === agentId,
678
713
  ),
714
+ feedbackRequests,
679
715
  selectionSource: selection.source,
680
716
  rerunRequest,
681
717
  relaunchPlan,
@@ -690,6 +726,7 @@ function ensureWaveStateDirs(lanePaths) {
690
726
  ensureDirectory(lanePaths.controlPlaneDir);
691
727
  ensureDirectory(lanePaths.assignmentsDir);
692
728
  ensureDirectory(lanePaths.inboxesDir);
729
+ ensureDirectory(lanePaths.signalsDir);
693
730
  ensureDirectory(lanePaths.messageboardsDir);
694
731
  ensureDirectory(lanePaths.docsQueueDir);
695
732
  ensureDirectory(lanePaths.ledgerDir);
@@ -714,6 +751,9 @@ function printStatus(payload) {
714
751
  ? `${payload.blockingEdge.kind} ${payload.blockingEdge.id}: ${payload.blockingEdge.detail}`
715
752
  : "none";
716
753
  console.log(`lane=${payload.lane} wave=${payload.wave} phase=${payload.phase}`);
754
+ if (payload.signals?.wave) {
755
+ console.log(buildSignalStatusLine(payload.signals.wave, payload));
756
+ }
717
757
  console.log(`blocking=${blocking}`);
718
758
  if (payload.nextTimer) {
719
759
  console.log(`next-timer=${payload.nextTimer.kind} ${payload.nextTimer.taskId} at ${payload.nextTimer.at}`);
@@ -729,7 +769,9 @@ function printStatus(payload) {
729
769
  function appendCoordinationStatusUpdate(logPath, record, status, options = {}) {
730
770
  return appendCoordinationRecord(logPath, {
731
771
  ...record,
772
+ ...(options.patch || {}),
732
773
  status,
774
+ updatedAt: options.updatedAt || toIsoTimestamp(),
733
775
  summary: options.summary || record.summary,
734
776
  detail: options.detail || record.detail,
735
777
  source: options.source || "operator",
@@ -814,6 +856,73 @@ function appendTaskCoordinationEvent(logPath, lanePaths, wave, record, action, o
814
856
  source: "operator",
815
857
  });
816
858
  }
859
+ if (action === "defer") {
860
+ return appendCoordinationStatusUpdate(logPath, record, record.status, {
861
+ detail:
862
+ options.detail ||
863
+ `${record.summary || record.id} deferred by operator; keep visible but do not block wave progression.`,
864
+ patch: {
865
+ blocking: false,
866
+ blockerSeverity: "soft",
867
+ },
868
+ });
869
+ }
870
+ if (action === "mark-advisory") {
871
+ return appendCoordinationStatusUpdate(logPath, record, record.status, {
872
+ detail:
873
+ options.detail ||
874
+ `${record.summary || record.id} marked advisory by operator; keep visible without blocking closure.`,
875
+ patch: {
876
+ blocking: false,
877
+ blockerSeverity: "advisory",
878
+ },
879
+ });
880
+ }
881
+ if (action === "mark-stale") {
882
+ return appendCoordinationStatusUpdate(logPath, record, record.status, {
883
+ detail:
884
+ options.detail ||
885
+ `${record.summary || record.id} marked stale by operator; historical context preserved without blocking.`,
886
+ patch: {
887
+ blocking: false,
888
+ blockerSeverity: "stale",
889
+ },
890
+ });
891
+ }
892
+ if (action === "resolve-policy") {
893
+ const resolvedRecord = appendCoordinationStatusUpdate(logPath, record, "resolved", {
894
+ detail: options.detail || `Resolved by operator policy: ${record.summary || record.id}.`,
895
+ patch: {
896
+ blocking: false,
897
+ blockerSeverity: "advisory",
898
+ },
899
+ });
900
+ const policyRecord = appendCoordinationRecord(logPath, {
901
+ id: `policy-${record.id}`,
902
+ lane: lanePaths.lane,
903
+ wave: wave.wave,
904
+ agentId: options.agent || "operator",
905
+ kind: "resolved-by-policy",
906
+ targets: record.targets,
907
+ priority: record.priority,
908
+ artifactRefs: record.artifactRefs,
909
+ dependsOn: Array.from(new Set([record.id, ...(record.dependsOn || [])])),
910
+ closureCondition:
911
+ record.kind === "clarification-request"
912
+ ? clarificationClosureCondition(record.id)
913
+ : record.closureCondition || "",
914
+ summary: record.summary,
915
+ detail: options.detail || `Operator resolved ${record.id} by policy.`,
916
+ status: "resolved",
917
+ source: "operator",
918
+ blocking: false,
919
+ blockerSeverity: "advisory",
920
+ });
921
+ return {
922
+ resolvedRecord,
923
+ policyRecord,
924
+ };
925
+ }
817
926
  throw new Error(`Unsupported task action: ${action}`);
818
927
  }
819
928
 
@@ -901,6 +1010,16 @@ export async function runControlCli(argv) {
901
1010
  wave,
902
1011
  agentId: options.agent || "",
903
1012
  });
1013
+ const signalSync = syncWaveSignalProjections({
1014
+ lanePaths,
1015
+ wave,
1016
+ statusPayload: payload,
1017
+ includeResident: Boolean(options.orchestratorId),
1018
+ });
1019
+ payload.signals = {
1020
+ wave: signalSync.wave?.snapshot || null,
1021
+ agents: (signalSync.agents || []).map((entry) => entry.snapshot),
1022
+ };
904
1023
  if (options.json) {
905
1024
  console.log(JSON.stringify(payload, null, 2));
906
1025
  } else {
@@ -956,6 +1075,8 @@ export async function runControlCli(argv) {
956
1075
  artifactRefs: options.artifactRefs,
957
1076
  status: options.status,
958
1077
  source: "operator",
1078
+ ...(options.blocking !== null ? { blocking: options.blocking } : {}),
1079
+ ...(options.blockerSeverity ? { blockerSeverity: options.blockerSeverity } : {}),
959
1080
  });
960
1081
  console.log(JSON.stringify(record, null, 2));
961
1082
  return;
@@ -1048,14 +1169,27 @@ export async function runControlCli(argv) {
1048
1169
  throw new Error(`Task not found: ${options.id}`);
1049
1170
  }
1050
1171
  const updated = appendTaskCoordinationEvent(logPath, lanePaths, wave, record, action, options);
1051
- if (record.kind === "clarification-request" && ["resolve", "dismiss"].includes(action)) {
1172
+ if (record.kind === "clarification-request" && ["resolve", "dismiss", "resolve-policy"].includes(action)) {
1052
1173
  const nextStatus = action === "resolve" ? "resolved" : "cancelled";
1174
+ const linkedStatus = action === "resolve-policy" ? "resolved" : nextStatus;
1053
1175
  for (const linked of clarificationLinkedRequests(coordinationState, record.id).filter((entry) =>
1054
1176
  isOpenCoordinationStatus(entry.status),
1055
1177
  )) {
1056
- appendCoordinationStatusUpdate(logPath, linked, nextStatus, {
1057
- detail: `${action === "resolve" ? "Resolved" : "Cancelled"} via clarification ${record.id}.`,
1178
+ appendCoordinationStatusUpdate(logPath, linked, linkedStatus, {
1179
+ detail:
1180
+ action === "resolve"
1181
+ ? `Resolved via clarification ${record.id}.`
1182
+ : action === "resolve-policy"
1183
+ ? `Resolved by policy via clarification ${record.id}.`
1184
+ : `Cancelled via clarification ${record.id}.`,
1058
1185
  summary: linked.summary,
1186
+ patch:
1187
+ action === "resolve-policy"
1188
+ ? {
1189
+ blocking: false,
1190
+ blockerSeverity: "advisory",
1191
+ }
1192
+ : undefined,
1059
1193
  });
1060
1194
  }
1061
1195
  }
@@ -11,6 +11,8 @@ import {
11
11
  import {
12
12
  CLARIFICATION_CLOSURE_PREFIX,
13
13
  buildCoordinationResponseMetrics,
14
+ coordinationBlockerSeverity,
15
+ coordinationRecordBlocksWave,
14
16
  } from "./coordination-store.mjs";
15
17
  import {
16
18
  DEFAULT_COORDINATION_ACK_TIMEOUT_MS,
@@ -586,6 +588,8 @@ export function buildTaskSnapshots({
586
588
  const metrics = responseMetrics.recordMetricsById.get(record.id) || {};
587
589
  const feedbackRequest = feedbackById.get(record.id) || null;
588
590
  const taskState = taskStateForCoordinationRecord(record, feedbackRequest);
591
+ const blocking = coordinationRecordBlocksWave(record);
592
+ const blockerSeverity = coordinationBlockerSeverity(record);
589
593
  tasks.push({
590
594
  taskId: record.id,
591
595
  sourceRecordId: record.id,
@@ -598,6 +602,8 @@ export function buildTaskSnapshots({
598
602
  assigneeAgentId: firstTargetAgentId(record),
599
603
  leaseOwnerAgentId:
600
604
  ["acknowledged", "in_progress"].includes(record.status) ? firstTargetAgentId(record) : null,
605
+ blocking,
606
+ blockerSeverity,
601
607
  needsHuman:
602
608
  record.kind === "human-feedback" ||
603
609
  feedbackRequest?.status === "pending" ||
@@ -627,7 +633,7 @@ export function buildTaskSnapshots({
627
633
  ? feedbackRequest?.updatedAt || record.updatedAt || record.createdAt
628
634
  : null,
629
635
  overdueAck: metrics.overdueAck === true,
630
- stale: metrics.staleClarification === true,
636
+ stale: metrics.staleClarification === true || blockerSeverity === "stale",
631
637
  feedbackRequestId: feedbackRequest?.id || null,
632
638
  humanResponse: feedbackRequest?.responseText || null,
633
639
  humanOperator: feedbackRequest?.responseOperator || null,
@@ -648,6 +654,8 @@ export function buildTaskSnapshots({
648
654
  ownerAgentId: request.agentId || null,
649
655
  assigneeAgentId: request.agentId || null,
650
656
  leaseOwnerAgentId: null,
657
+ blocking: true,
658
+ blockerSeverity: "hard",
651
659
  needsHuman: request.status !== "answered",
652
660
  dependsOn: [],
653
661
  evidenceRefs: [],
@@ -676,6 +684,9 @@ export function buildTaskSnapshots({
676
684
  export function nextTaskDeadline(tasks) {
677
685
  const candidates = [];
678
686
  for (const task of tasks || []) {
687
+ if (task?.blocking === false) {
688
+ continue;
689
+ }
679
690
  for (const [kind, value] of [
680
691
  ["ack", task.ackDeadlineAt],
681
692
  ["resolve", task.resolveDeadlineAt],
@@ -43,7 +43,17 @@ export const COORDINATION_STATUS_VALUES = [
43
43
 
44
44
  export const COORDINATION_PRIORITY_VALUES = ["low", "normal", "high", "urgent"];
45
45
  export const COORDINATION_CONFIDENCE_VALUES = ["low", "medium", "high"];
46
+ export const COORDINATION_BLOCKER_SEVERITY_VALUES = [
47
+ "hard",
48
+ "soft",
49
+ "stale",
50
+ "advisory",
51
+ "proof-critical",
52
+ "closure-critical",
53
+ ];
46
54
  const OPEN_COORDINATION_STATUSES = new Set(["open", "acknowledged", "in_progress"]);
55
+ const NON_BLOCKING_BLOCKER_SEVERITIES = new Set(["stale", "advisory"]);
56
+ const HARD_BLOCKER_SEVERITIES = new Set(["hard", "proof-critical", "closure-critical"]);
47
57
  export const CLARIFICATION_CLOSURE_PREFIX = "clarification:";
48
58
 
49
59
  function normalizeString(value, fallback = "") {
@@ -65,6 +75,23 @@ function normalizeStringArray(values) {
65
75
  );
66
76
  }
67
77
 
78
+ function normalizeOptionalBoolean(value, fallback = null) {
79
+ if (value === undefined || value === null || value === "") {
80
+ return fallback;
81
+ }
82
+ if (typeof value === "boolean") {
83
+ return value;
84
+ }
85
+ const normalized = String(value).trim().toLowerCase();
86
+ if (["1", "true", "yes", "y", "on"].includes(normalized)) {
87
+ return true;
88
+ }
89
+ if (["0", "false", "no", "n", "off"].includes(normalized)) {
90
+ return false;
91
+ }
92
+ return Boolean(value);
93
+ }
94
+
68
95
  function validateEnum(value, allowed, label) {
69
96
  if (!allowed.includes(value)) {
70
97
  throw new Error(`${label} must be one of ${allowed.join(", ")} (got: ${value || "empty"})`);
@@ -75,6 +102,74 @@ function stableId(prefix) {
75
102
  return `${prefix}-${crypto.randomBytes(4).toString("hex")}`;
76
103
  }
77
104
 
105
+ function defaultBlockingForKind(kind) {
106
+ return ["request", "blocker", "clarification-request", "human-escalation", "human-feedback"].includes(
107
+ kind,
108
+ );
109
+ }
110
+
111
+ function defaultSeverityForRecord(kind, priority, blocking) {
112
+ if (blocking === false) {
113
+ return "advisory";
114
+ }
115
+ if (kind === "human-escalation" || kind === "human-feedback") {
116
+ return "hard";
117
+ }
118
+ if (kind === "request" || kind === "clarification-request") {
119
+ return "closure-critical";
120
+ }
121
+ if (kind === "blocker") {
122
+ return ["high", "urgent"].includes(priority) ? "hard" : "soft";
123
+ }
124
+ return "advisory";
125
+ }
126
+
127
+ function normalizeBlockerSeverity(value, defaults = {}) {
128
+ const normalized = normalizeString(value ?? defaults.blockerSeverity, "").toLowerCase();
129
+ return normalized || null;
130
+ }
131
+
132
+ export function coordinationBlockerSeverity(record) {
133
+ if (!record || typeof record !== "object") {
134
+ return "advisory";
135
+ }
136
+ const blocking =
137
+ record.blocking === undefined || record.blocking === null
138
+ ? defaultBlockingForKind(String(record.kind || "").trim().toLowerCase())
139
+ : record.blocking !== false;
140
+ const explicitSeverity = normalizeBlockerSeverity(record.blockerSeverity);
141
+ const derivedSeverity =
142
+ explicitSeverity ||
143
+ defaultSeverityForRecord(
144
+ String(record.kind || "").trim().toLowerCase(),
145
+ String(record.priority || "normal").trim().toLowerCase(),
146
+ blocking,
147
+ );
148
+ if (COORDINATION_BLOCKER_SEVERITY_VALUES.includes(derivedSeverity)) {
149
+ return derivedSeverity;
150
+ }
151
+ return defaultSeverityForRecord(
152
+ String(record.kind || "").trim().toLowerCase(),
153
+ String(record.priority || "normal").trim().toLowerCase(),
154
+ blocking,
155
+ );
156
+ }
157
+
158
+ export function coordinationRecordBlocksWave(record) {
159
+ if (!record || !isOpenCoordinationStatus(record.status)) {
160
+ return false;
161
+ }
162
+ if (record.blocking === false) {
163
+ return false;
164
+ }
165
+ return !NON_BLOCKING_BLOCKER_SEVERITIES.has(coordinationBlockerSeverity(record));
166
+ }
167
+
168
+ export function coordinationRecordIsHardBlocker(record) {
169
+ return coordinationRecordBlocksWave(record) &&
170
+ HARD_BLOCKER_SEVERITIES.has(coordinationBlockerSeverity(record));
171
+ }
172
+
78
173
  export function normalizeCoordinationRecord(rawRecord, defaults = {}) {
79
174
  if (!rawRecord || typeof rawRecord !== "object" || Array.isArray(rawRecord)) {
80
175
  throw new Error("Coordination record must be an object");
@@ -93,12 +188,25 @@ export function normalizeCoordinationRecord(rawRecord, defaults = {}) {
93
188
  ).toLowerCase();
94
189
  const priority = normalizeString(rawRecord.priority || defaults.priority || "normal").toLowerCase();
95
190
  const confidence = normalizeString(rawRecord.confidence || defaults.confidence || "medium").toLowerCase();
191
+ const explicitBlocking = normalizeOptionalBoolean(
192
+ rawRecord.blocking,
193
+ normalizeOptionalBoolean(defaults.blocking, null),
194
+ );
195
+ const blocking = explicitBlocking ?? defaultBlockingForKind(kind);
196
+ const blockerSeverity =
197
+ normalizeBlockerSeverity(rawRecord.blockerSeverity, defaults) ||
198
+ defaultSeverityForRecord(kind, priority, blocking);
96
199
  const createdAt = normalizeString(rawRecord.createdAt || defaults.createdAt || now);
97
200
  const updatedAt = normalizeString(rawRecord.updatedAt || defaults.updatedAt || createdAt);
98
201
  validateEnum(kind, COORDINATION_KIND_VALUES, "Coordination kind");
99
202
  validateEnum(status, COORDINATION_STATUS_VALUES, "Coordination status");
100
203
  validateEnum(priority, COORDINATION_PRIORITY_VALUES, "Coordination priority");
101
204
  validateEnum(confidence, COORDINATION_CONFIDENCE_VALUES, "Coordination confidence");
205
+ validateEnum(
206
+ blockerSeverity,
207
+ COORDINATION_BLOCKER_SEVERITY_VALUES,
208
+ "Coordination blockerSeverity",
209
+ );
102
210
  if (!lane) {
103
211
  throw new Error("Coordination lane is required");
104
212
  }
@@ -118,6 +226,8 @@ export function normalizeCoordinationRecord(rawRecord, defaults = {}) {
118
226
  targets: normalizeStringArray(rawRecord.targets ?? defaults.targets),
119
227
  status,
120
228
  priority,
229
+ blocking,
230
+ blockerSeverity,
121
231
  artifactRefs: normalizeStringArray(rawRecord.artifactRefs ?? defaults.artifactRefs),
122
232
  dependsOn: normalizeStringArray(rawRecord.dependsOn ?? defaults.dependsOn),
123
233
  closureCondition: normalizeString(rawRecord.closureCondition ?? defaults.closureCondition, ""),
@@ -180,6 +290,8 @@ export function appendCoordinationRecord(filePath, rawRecord, defaults = {}) {
180
290
  kind: record.kind,
181
291
  status: record.status,
182
292
  priority: record.priority,
293
+ blocking: record.blocking !== false,
294
+ blockerSeverity: record.blockerSeverity,
183
295
  confidence: record.confidence,
184
296
  summary: record.summary,
185
297
  detail: record.detail,
@@ -416,15 +528,16 @@ export function buildCoordinationResponseMetrics(state, options = {}) {
416
528
  for (const record of state?.openRecords || []) {
417
529
  const startMs = parseRecordStartMs(record);
418
530
  const ageMs = Number.isFinite(startMs) ? Math.max(0, nowMs - startMs) : null;
419
- const ackTracked = isAckTrackedRecord(record);
531
+ const blocking = coordinationRecordBlocksWave(record);
532
+ const ackTracked = blocking && isAckTrackedRecord(record);
420
533
  const ackPending = ackTracked && record.status === "open";
421
534
  const clarificationLinked =
422
- record.kind === "clarification-request" || isClarificationLinkedRequest(record);
535
+ blocking && (record.kind === "clarification-request" || isClarificationLinkedRequest(record));
423
536
  const overdueAck = ackPending && Number.isFinite(ageMs) && ageMs >= ackTimeoutMs;
424
537
  const staleClarification =
425
538
  clarificationLinked && Number.isFinite(ageMs) && ageMs >= resolutionStaleMs;
426
539
 
427
- if (Number.isFinite(ageMs)) {
540
+ if (blocking && Number.isFinite(ageMs)) {
428
541
  oldestOpenCoordinationAgeMs =
429
542
  oldestOpenCoordinationAgeMs === null
430
543
  ? ageMs
@@ -454,6 +567,7 @@ export function buildCoordinationResponseMetrics(state, options = {}) {
454
567
  overdueAck,
455
568
  clarificationLinked,
456
569
  staleClarification,
570
+ blocking,
457
571
  });
458
572
  }
459
573
 
@@ -469,7 +583,7 @@ export function buildCoordinationResponseMetrics(state, options = {}) {
469
583
  a.localeCompare(b),
470
584
  ),
471
585
  openHumanEscalationCount: (state?.humanEscalations || []).filter((record) =>
472
- isOpenCoordinationStatus(record.status),
586
+ coordinationRecordBlocksWave(record),
473
587
  ).length,
474
588
  recordMetricsById,
475
589
  };
@@ -490,6 +604,12 @@ function renderOpenRecord(record, responseMetrics = null) {
490
604
  if (recordMetrics?.staleClarification) {
491
605
  tags.push("stale-clarification");
492
606
  }
607
+ if (record.blocking === false) {
608
+ tags.push("non-blocking");
609
+ }
610
+ if (record.blockerSeverity) {
611
+ tags.push(`severity=${record.blockerSeverity}`);
612
+ }
493
613
  const timing = tags.length > 0 ? ` [${tags.join(", ")}]` : "";
494
614
  return `- [${record.priority}] ${record.kind}/${record.status} ${record.agentId}${targets}${timing} id=${record.id}: ${compactSingleLine(record.summary || record.detail || "no summary", 160)}${artifacts}`;
495
615
  }