@zq-silk/yui 0.11.2 → 0.12.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 (91) hide show
  1. package/ARCHITECTURE.md +23 -4
  2. package/README.md +80 -13
  3. package/dist/cli/commandCatalog.js +30 -5
  4. package/dist/cli.js +105 -43
  5. package/dist/commands/configCommands.js +12 -45
  6. package/dist/commands/executionAuditCommands.js +1 -1
  7. package/dist/commands/jobCommands.js +4 -10
  8. package/dist/commands/projectCommands.js +581 -2
  9. package/dist/commands/taskCommands.js +159 -172
  10. package/dist/commands/taskCompletionGate.js +29 -54
  11. package/dist/commands/taskContextCommand.js +55 -11
  12. package/dist/commands/taskInputCommands.js +8 -5
  13. package/dist/commands/taskIntegrationCommands.js +2 -1
  14. package/dist/commands/taskNextActionCommand.js +56 -6
  15. package/dist/commands/taskOverviewCommand.js +17 -31
  16. package/dist/commands/taskRoleRuntimeStatus.js +24 -0
  17. package/dist/commands/taskWorkspaceCommands.js +5 -2
  18. package/dist/config/configCatalog.js +2 -2
  19. package/dist/config/yuiConfig.js +3 -7
  20. package/dist/context/wakeNotification.js +20 -5
  21. package/dist/controller/agentRuntimeObserver.js +247 -51
  22. package/dist/controller/clientRuntime.js +1 -1
  23. package/dist/controller/controller.js +8 -12
  24. package/dist/controller/fileSchedulerStoreAdapter.js +146 -198
  25. package/dist/controller/runtime.js +3 -3
  26. package/dist/controller/runtimeEventInbox.js +17 -7
  27. package/dist/controller/runtimeEventProcessor.js +12 -19
  28. package/dist/controller/runtimeObservationHook.js +45 -0
  29. package/dist/execution/resourceBroker.js +5 -4
  30. package/dist/executor/agentExecutor.js +4 -4
  31. package/dist/executor/effectiveLaunch.js +8 -48
  32. package/dist/executor/fileRoleLaunchPlanner.js +36 -16
  33. package/dist/executor/workspacePreflightClassification.js +23 -2
  34. package/dist/interaction/operatorPresentation.js +33 -89
  35. package/dist/lifecycle/exactRunTerminalization.js +1 -2
  36. package/dist/lifecycle/taskRoleSessionReset.js +5 -13
  37. package/dist/observability/orchestrationMetrics.js +8 -3
  38. package/dist/profile/agentProfile.js +1 -1
  39. package/dist/release/cliHomeReleaseFence.js +123 -0
  40. package/dist/repository/checkoutSwap.js +61 -0
  41. package/dist/repository/gitWorkspace.js +36 -0
  42. package/dist/repository/project.js +53 -3
  43. package/dist/repository/taskWorkspacePreparer.js +176 -60
  44. package/dist/resources/sqliteResourceRegistry.js +1 -1
  45. package/dist/review/deltaRecheck.js +12 -51
  46. package/dist/review/reviewAcceptance.js +26 -0
  47. package/dist/review/reviewConfig.js +0 -31
  48. package/dist/review/reviewDecision.js +113 -0
  49. package/dist/review/reviewOutcomeClassifier.js +1 -1
  50. package/dist/review/reviewRound.js +1 -1
  51. package/dist/review/reviewerAvailability.js +69 -0
  52. package/dist/runtime/agentDriverObservation.js +24 -10
  53. package/dist/runtime/builtinAgentDrivers.js +4 -3
  54. package/dist/runtime/builtinTranscriptObserver.js +301 -64
  55. package/dist/runtime/builtinTranscriptUsage.js +9 -7
  56. package/dist/runtime/index.js +1 -0
  57. package/dist/runtime/runtimeObservation.js +13 -0
  58. package/dist/runtime/runtimeProjection.js +16 -25
  59. package/dist/runtime/sessionTokenMetrics.js +181 -0
  60. package/dist/runtime/structuredProviderHost.js +7 -1
  61. package/dist/scheduler/activeRoleRunDelivery.js +4 -4
  62. package/dist/scheduler/activeTaskProgress.js +7 -6
  63. package/dist/scheduler/leaderWakeupProcessor.js +3 -5
  64. package/dist/scheduler/operatorEvent.js +34 -0
  65. package/dist/scheduler/operatorInputNotificationProcessor.js +54 -94
  66. package/dist/scheduler/roleRunStall.js +0 -14
  67. package/dist/scheduler/taskExecutionProjection.js +97 -76
  68. package/dist/scheduler/taskObservabilityProjection.js +10 -11
  69. package/dist/storage/migration/productionRegistry.js +467 -0
  70. package/dist/storage/sqliteSchema.js +18 -8
  71. package/dist/storage/sqliteStore.js +61 -21
  72. package/dist/storage/storeRpc.js +0 -1
  73. package/dist/storage/taskStore.js +47 -31
  74. package/dist/storage/upgrade/recordVersions.js +3 -2
  75. package/dist/storage/upgrade/sqliteMigrationTarget.js +30 -8
  76. package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +23 -11
  77. package/dist/storage/upgrade/sqliteStateMigration.js +66 -8
  78. package/dist/task/completionReadiness.js +10 -6
  79. package/dist/task/nextAction.js +82 -24
  80. package/dist/telemetry/sqliteTelemetryStore.js +1 -1
  81. package/dist/web/assets/client/components.js +13 -3
  82. package/dist/web/assets/client/i18n.js +6 -2
  83. package/dist/web/webSnapshot.js +4 -0
  84. package/i18n/README.zh-CN.md +2 -2
  85. package/package.json +1 -1
  86. package/skills/yui-leader/SKILL.md +53 -16
  87. package/skills/yui-operator/SKILL.md +9 -0
  88. package/skills/yui-reviewer/SKILL.md +28 -7
  89. package/dist/context/sessionContextBudget.js +0 -71
  90. package/dist/lifecycle/contextBudgetRollover.js +0 -81
  91. package/dist/scheduler/operatorNotification.js +0 -59
@@ -8,11 +8,9 @@ import { createTaskEvent } from "../event/taskEvent.js";
8
8
  import { operationalTaskRecords } from "../task/taskRecordRetirement.js";
9
9
  import { buildTaskWakeEnvelope } from "../context/wakeNotification.js";
10
10
  import { createTaskWake, fallbackWakeCursor, latestTaskWake } from "../scheduler/taskWake.js";
11
- import { rolloverTaskRoleSessionForContextBudget } from "../lifecycle/contextBudgetRollover.js";
12
- import { resolveContextBudget } from "../config/yuiConfig.js";
13
11
  import { answerInputRequest } from "../input/inputRequest.js";
14
12
  import { activeRoleAgentBinding, updateRoleStatus } from "../role/role.js";
15
- import { effectiveLaunchWithTaskMainWorkspace, effectiveLaunchSnapshotsCompatible, effectiveLaunchSnapshotsCompatibleForTaskMain, resolveEffectiveLaunch, validateEffectiveLaunchSnapshot } from "../executor/effectiveLaunch.js";
13
+ import { effectiveLaunchWithTaskMainWorkspace, effectiveLaunchSnapshotsCompatible, effectiveLaunchSnapshotsCompatibleForTaskSession, resolveEffectiveLaunch, validateEffectiveLaunchSnapshot } from "../executor/effectiveLaunch.js";
16
14
  import { SYSTEM_OPERATOR_ROLE } from "../role/systemRoles.js";
17
15
  import { failAgentRun, markAgentRunDelivered, markAgentRunPushed, reopenRunForProviderRetry, clearProviderRetryOnProgress, agentRunDeliveryReceiptId, withProviderRetry } from "../run/agentRun.js";
18
16
  import { markWorkflowOutcomeRequestDispatched } from "../run/runControlRequest.js";
@@ -23,11 +21,11 @@ import { classifyRuntimeProcessExit, validateRuntimeProcessExitObservation } fro
23
21
  import { terminalizeExactTaskRun } from "../lifecycle/exactRunTerminalization.js";
24
22
  import { createCanonicalLifecycleEvent, foldCanonicalLifecycleEvent } from "../lifecycle/canonicalLifecycleEvent.js";
25
23
  import { recordLeaderFailure } from "../scheduler/leaderFailure.js";
26
- import { createLeaderRecoveryNotification, createLeaderStallNotification } from "../scheduler/operatorNotification.js";
24
+ import { enqueueOperatorEvent, recordLeaderAttentionRequired, routeRoleEvent } from "../scheduler/operatorEvent.js";
27
25
  import { pendingWakeupsMatch } from "../scheduler/pendingWakeup.js";
28
26
  import { queueLeaderWakeup } from "../scheduler/wakeupQueue.js";
29
27
  import { wakeReason } from "../scheduler/wakeReason.js";
30
- import { foldRunProgressFacts, latestRunDurableProgressAt, latestRunEventTime, latestStallEvidenceKey, clearMatchingLeaderStallAttention, RUN_PROGRESS_EVENT, RUN_RECOVERED_EVENT, RUN_STALLED_EVENT } from "../scheduler/roleRunStall.js";
28
+ import { foldRunProgressFacts, latestRunDurableProgressAt, latestRunEventTime, latestStallEvidenceKey, RUN_PROGRESS_EVENT, RUN_RECOVERED_EVENT, RUN_STALLED_EVENT } from "../scheduler/roleRunStall.js";
31
29
  import { projectProviderContinuations } from "../runtime/runtimeContinuationProjection.js";
32
30
  import { providerContinuationKey } from "../runtime/providerContinuation.js";
33
31
  import { currentWorkItemExecutionGroup, updateWorkItemExecutionGroup, updateWorkItemStatus, workItemOwnsUnresolvedExecutionLane } from "../workItem/workItem.js";
@@ -38,7 +36,7 @@ import { enqueueWork } from "../coordination/workMailboxQueue.js";
38
36
  import { RUNTIME_CLEANUP_REQUIRED_REASON, RUNTIME_LAUNCH_RESERVED_REASON, RUNTIME_LIFECYCLE_OWNER, RuntimeLifecycleBusyError, hasRuntimeCleanupObligation, hasRuntimeLifecycleWork, isRuntimeLaunchReservation, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
39
37
  import { nativeSessionIdForLaunch } from "../runtime/preallocatedNativeSession.js";
40
38
  import { builtinAgentDriverRegistry } from "../runtime/builtinAgentDrivers.js";
41
- import { RUNTIME_OBSERVATION_TASK_EVENT, createRuntimeObservation, runtimeObservationFenceMatches, runtimeObservationFromTaskEvent, runtimeObservationRunFenceMatches, runtimeObservationTaskEventPayload } from "../runtime/runtimeObservation.js";
39
+ import { RUNTIME_OBSERVATION_TASK_EVENT, createRuntimeObservation, isRuntimeTokenEvidence, runtimeObservationFenceMatches, runtimeObservationFromTaskEvent, runtimeObservationRunFenceMatches, runtimeObservationTaskEventPayload } from "../runtime/runtimeObservation.js";
42
40
  import { projectRuntimeTaskEvents } from "../runtime/runtimeProjection.js";
43
41
  import { contextSnapshotRef } from "../context/contextSnapshot.js";
44
42
  import { contextSnapshotDeltaRefIds, freezeRunContextSnapshot } from "../context/runContextPack.js";
@@ -90,8 +88,7 @@ export class FileSchedulerStoreAdapter {
90
88
  if (adapterId === null) {
91
89
  return this.recordObsoleteCanonicalObservation(input, "driver-or-run-mismatch", now);
92
90
  }
93
- if (this.store.listEvents(taskId).some((event) => (event.type === RUNTIME_OBSERVATION_TASK_EVENT
94
- && event.payload.semanticKey === input.semanticKey)))
91
+ if (hasPersistedRuntimeObservation(this.store.listEvents(taskId), input))
95
92
  return "applied";
96
93
  let outcome;
97
94
  switch (input.kind) {
@@ -112,6 +109,7 @@ export class FileSchedulerStoreAdapter {
112
109
  launchId: input.fence.launchId,
113
110
  nativeSessionId: input.fence.nativeSessionId,
114
111
  turnId: input.fence.nativeTurnId,
112
+ attemptId: input.fence.receiptId,
115
113
  runId: input.fence.runId,
116
114
  summary: input.payload.summary ?? "Agent turn completed without a workflow outcome."
117
115
  };
@@ -152,6 +150,7 @@ export class FileSchedulerStoreAdapter {
152
150
  launchId: input.fence.launchId,
153
151
  nativeSessionId: input.fence.nativeSessionId,
154
152
  turnId: input.fence.nativeTurnId,
153
+ attemptId: input.fence.receiptId,
155
154
  runId: input.fence.runId,
156
155
  summary: input.payload.summary ?? `Provider Turn failed: ${failureEvidence.code}.`,
157
156
  providerStatus: "failed"
@@ -223,7 +222,8 @@ export class FileSchedulerStoreAdapter {
223
222
  }
224
223
  if (outcome === "applied"
225
224
  && input.kind === "activity.observed"
226
- && input.payload.activityId !== undefined) {
225
+ && input.payload.activityId !== undefined
226
+ && input.payload.usage === undefined) {
227
227
  this.clearProviderRetryForProgress(input, "correlated-activity", now);
228
228
  }
229
229
  if (outcome === "applied")
@@ -251,9 +251,9 @@ export class FileSchedulerStoreAdapter {
251
251
  }
252
252
  validateCanonicalRunObservation(input, now) {
253
253
  return this.store.transaction((store) => {
254
- if (store.listEvents(input.fence.taskId).some((event) => (event.type === RUNTIME_OBSERVATION_TASK_EVENT
255
- && event.payload.semanticKey === input.semanticKey)))
254
+ if (hasPersistedRuntimeObservation(store.listEvents(input.fence.taskId), input)) {
256
255
  return "applied";
256
+ }
257
257
  const run = store.getAgentRun(input.fence.taskId, input.fence.runId);
258
258
  const active = store.getActiveAgentRun(input.fence.taskId, input.fence.roleName);
259
259
  const sessions = store.getTaskRoleSessionSet(input.fence.taskId, input.fence.roleName);
@@ -388,10 +388,12 @@ export class FileSchedulerStoreAdapter {
388
388
  "Yui preserved the Run because Session/host termination is not a workflow outcome.",
389
389
  "Inspect native-child and mailbox facts, then explicitly continue, reset, or request user input."
390
390
  ].join(" ");
391
- store.saveOperatorNotification(createLeaderRecoveryNotification(input.fence.taskId, message, now, store.getOperatorNotification(input.fence.taskId)));
392
- enqueueWork(store, { kind: "operator" }, "leader-runtime-detached", now, [
393
- { type: "run", taskId: input.fence.taskId, id: active.id }
394
- ]);
391
+ recordLeaderAttentionRequired(store, {
392
+ taskId: input.fence.taskId,
393
+ reason: "leader-runtime-detached",
394
+ payload: { message, runId: active.id },
395
+ now
396
+ });
395
397
  }
396
398
  else {
397
399
  enqueueWork(store, {
@@ -410,22 +412,16 @@ export class FileSchedulerStoreAdapter {
410
412
  this.store.transaction((store) => {
411
413
  const taskId = input.fence.taskId;
412
414
  const events = store.listEvents(taskId);
413
- if (events.some((event) => (event.type === RUNTIME_OBSERVATION_TASK_EVENT
414
- && event.payload.semanticKey === input.semanticKey)))
415
+ if (hasPersistedRuntimeObservation(events, input))
415
416
  return;
416
417
  if (usageSnapshotIsSuperseded(events, input))
417
418
  return;
418
- const confirmedActivity = confirmedUsageActivityObservation(events, input);
419
- const removable = [
420
- ...compactedRuntimeObservationIds(events, input),
421
- ...(confirmedActivity === null
422
- ? []
423
- : compactedRuntimeObservationIds(events, confirmedActivity))
424
- ];
419
+ const removable = compactedRuntimeObservationIds(events, input);
425
420
  if (removable.length > 0)
426
421
  store.removeEvents(taskId, removable);
427
422
  const observationEventId = store.nextEventId(taskId);
428
- store.saveEvent(taskId, createTaskEvent(observationEventId, taskId, RUNTIME_OBSERVATION_TASK_EVENT, runtimeObservationTaskEventPayload(input), now));
423
+ const observationEvent = createTaskEvent(observationEventId, taskId, RUNTIME_OBSERVATION_TASK_EVENT, runtimeObservationTaskEventPayload(input), now);
424
+ store.saveEvent(taskId, observationEvent);
429
425
  if (input.kind === "native-work.snapshot"
430
426
  && input.payload.snapshotComplete === true
431
427
  && input.payload.observationQuality === "exact") {
@@ -503,14 +499,9 @@ export class FileSchedulerStoreAdapter {
503
499
  // Route a Yui wake only when that parent is gone and another observer
504
500
  // supplied the child's terminal fact.
505
501
  if (role !== null && (session?.status === "stopped" || session?.status === "broken")) {
506
- enqueueWork(store, role.name === "leader"
507
- ? { kind: "operator" }
508
- : { kind: "role", taskId, roleName: "leader" }, "detached-native-subagent-terminal", now, [{ type: "run", taskId, id: input.fence.runId }]);
502
+ routeRoleEvent(store, observationEvent, role.name, "detached-native-subagent-terminal", now);
509
503
  }
510
504
  }
511
- if (confirmedActivity !== null) {
512
- store.saveEvent(taskId, createTaskEvent(store.nextEventId(taskId), taskId, RUNTIME_OBSERVATION_TASK_EVENT, runtimeObservationTaskEventPayload(confirmedActivity), now));
513
- }
514
505
  });
515
506
  if (this.telemetry !== null && input.fence.runId !== undefined) {
516
507
  try {
@@ -573,9 +564,6 @@ export class FileSchedulerStoreAdapter {
573
564
  withRuntimeEventTransaction(execute) {
574
565
  return this.store.withRuntimeEventTransaction(execute);
575
566
  }
576
- getPresentationContext() {
577
- return { timeZone: this.store.getConfig().timeZone };
578
- }
579
567
  listTasks() { return this.store.listTasks(); }
580
568
  listActiveTaskIds() {
581
569
  return [...this.store.listActiveTaskIds()].sort((left, right) => (left.localeCompare(right, undefined, { numeric: true })));
@@ -608,12 +596,6 @@ export class FileSchedulerStoreAdapter {
608
596
  });
609
597
  });
610
598
  }
611
- rolloverTaskRoleSessionForContextBudget(input) {
612
- return this.store.transaction((reader) => rolloverTaskRoleSessionForContextBudget(reader, input.taskId, input.roleName, { peakTokens: input.peakTokens, hardTokens: input.hardTokens }, input.now));
613
- }
614
- getContextBudget() {
615
- return resolveContextBudget(this.store.getConfig().contextBudget);
616
- }
617
599
  listRoles(taskId) {
618
600
  return this.store.listRoles(taskId).map((role) => mapRole(this.store, role));
619
601
  }
@@ -646,6 +628,17 @@ export class FileSchedulerStoreAdapter {
646
628
  adapterId: effectiveSession.effective.adapterId
647
629
  };
648
630
  }
631
+ markOperatorTurnStarted(now) {
632
+ this.store.transaction((store) => {
633
+ const sessions = store.getGlobalRoleSessionSet(SYSTEM_OPERATOR_ROLE);
634
+ if (sessions === null)
635
+ return;
636
+ const active = sessions.sessions[sessions.activeAgentId];
637
+ if (active === undefined || active.status !== "ready")
638
+ return;
639
+ store.saveGlobalRoleSessionSet(updateRoleAgentSessionStatus(sessions, sessions.activeAgentId, "running", now));
640
+ });
641
+ }
649
642
  resolveExpiredInputRecommendations(now, taskIds) {
650
643
  return this.store.transaction((store) => {
651
644
  const selectedTaskIds = taskIds === undefined
@@ -760,10 +753,7 @@ export class FileSchedulerStoreAdapter {
760
753
  }, input.now);
761
754
  store.saveEvent(task.id, event);
762
755
  if (role.name === "leader") {
763
- store.saveOperatorNotification(createLeaderStallNotification(task.id, run.id, input.progressAt, input.evidenceKey, input.now, store.getOperatorNotification(task.id)));
764
- enqueueWork(store, { kind: "operator" }, "leader-run-stalled", input.now, [
765
- { type: "task", id: task.id }
766
- ]);
756
+ enqueueOperatorEvent(store, event, "leader-run-stalled", input.now);
767
757
  }
768
758
  else {
769
759
  queueLeaderWakeup(store, task.id, wakeReason("role-run-stalled"), input.now);
@@ -815,7 +805,6 @@ export class FileSchedulerStoreAdapter {
815
805
  progressAt: input.progressAt,
816
806
  kind: "durable-progress"
817
807
  }, input.now));
818
- clearMatchingLeaderStallAttention(store, task.id, run.id);
819
808
  }
820
809
  return existing && !recovered ? "already-recorded" : "recorded";
821
810
  });
@@ -1161,17 +1150,11 @@ export class FileSchedulerStoreAdapter {
1161
1150
  const unknown = markMailboxInputDeliveryUnknown(mailbox, attemptId, reason, now);
1162
1151
  markSubmittingProviderTurnUnknown(store, target, mailbox.inputDelivery, now, reason);
1163
1152
  store.saveWorkMailbox(unknown);
1164
- const taskId = target.kind === "role" ? target.taskId : undefined;
1165
- if (taskId !== undefined) {
1166
- store.saveEvent(taskId, createTaskEvent(store.nextEventId(taskId), taskId, "run.input-delivery-unknown", { attemptId, reason }, now));
1167
- enqueueWork(store, { kind: "operator" }, "run-input-delivery-unknown", now, [
1168
- unknown.inputDelivery.executionRef
1169
- ], {
1170
- source: "input-router",
1171
- dedupeKey: `input-delivery-unknown:${attemptId}`,
1172
- deliveryMode: "followup",
1173
- lane: "normal"
1174
- });
1153
+ if (target.kind === "role") {
1154
+ const taskId = target.taskId;
1155
+ const event = createTaskEvent(store.nextEventId(taskId), taskId, "run.input-delivery-unknown", { attemptId, reason }, now);
1156
+ store.saveEvent(taskId, event);
1157
+ routeRoleEvent(store, event, target.roleName, "run-input-delivery-unknown", now);
1175
1158
  }
1176
1159
  return true;
1177
1160
  });
@@ -1298,7 +1281,6 @@ export class FileSchedulerStoreAdapter {
1298
1281
  }
1299
1282
  clearPendingWakeup(taskId) { this.store.clearPendingWakeup(taskId); }
1300
1283
  getLeaderFailure(taskId) { return this.store.getLeaderFailure(taskId); }
1301
- getOperatorNotification(taskId) { return this.store.getOperatorNotification(taskId); }
1302
1284
  saveLeaderDispatch(input) {
1303
1285
  return this.store.transaction((store) => {
1304
1286
  const task = store.getTask(input.task.id);
@@ -1378,7 +1360,6 @@ export class FileSchedulerStoreAdapter {
1378
1360
  }, "running", input.now);
1379
1361
  }
1380
1362
  store.clearLeaderFailure(input.task.id);
1381
- store.clearOperatorNotification(input.task.id);
1382
1363
  return "claimed";
1383
1364
  });
1384
1365
  }
@@ -1537,11 +1518,12 @@ export class FileSchedulerStoreAdapter {
1537
1518
  roleName: input.roleName
1538
1519
  }), RUNTIME_CLEANUP_REQUIRED_REASON, input.now, [{ type: "task", id: input.taskId }]);
1539
1520
  const terminal = result.run;
1540
- store.saveEvent(input.taskId, createTaskEvent(store.nextEventId(input.taskId), input.taskId, "runtime.role-delivery-failed", {
1521
+ const deliveryFailureEvent = createTaskEvent(store.nextEventId(input.taskId), input.taskId, "runtime.role-delivery-failed", {
1541
1522
  runId: terminal.id,
1542
1523
  roleName: input.roleName,
1543
1524
  outcome: terminal.status
1544
- }, input.now));
1525
+ }, input.now);
1526
+ store.saveEvent(input.taskId, deliveryFailureEvent);
1545
1527
  if (terminal.purpose === "execution" && terminal.workItemId !== undefined) {
1546
1528
  const item = store.getWorkItem(input.taskId, terminal.workItemId);
1547
1529
  if (item !== null && ![
@@ -1553,12 +1535,7 @@ export class FileSchedulerStoreAdapter {
1553
1535
  }
1554
1536
  }
1555
1537
  if (input.roleName !== "leader") {
1556
- enqueueWork(store, { kind: "role", taskId: input.taskId, roleName: "leader" }, terminal.purpose === "review" ? "review-failed" : "role-run-failed", input.now, [
1557
- { type: "run", taskId: input.taskId, id: terminal.id },
1558
- ...(terminal.workItemId === undefined
1559
- ? []
1560
- : [{ type: "work-item", taskId: input.taskId, id: terminal.workItemId }])
1561
- ]);
1538
+ routeRoleEvent(store, deliveryFailureEvent, input.roleName, terminal.purpose === "review" ? "review-failed" : "role-run-failed", input.now);
1562
1539
  }
1563
1540
  else {
1564
1541
  const failedRole = store.getRole(input.taskId, input.roleName);
@@ -1566,11 +1543,7 @@ export class FileSchedulerStoreAdapter {
1566
1543
  store.saveRole(input.taskId, updateRoleStatus(failedRole, "failed", input.now));
1567
1544
  }
1568
1545
  store.saveLeaderFailure(recordLeaderFailure(input.taskId, session?.nativeSessionId ?? "(unregistered)", summary, input.now, store.getLeaderFailure(input.taskId)));
1569
- store.saveOperatorNotification(createLeaderRecoveryNotification(input.taskId, summary, input.now, store.getOperatorNotification(input.taskId)));
1570
- enqueueWork(store, { kind: "operator" }, "leader-run-failed", input.now, [
1571
- { type: "task", id: input.taskId },
1572
- { type: "run", taskId: input.taskId, id: terminal.id }
1573
- ]);
1546
+ routeRoleEvent(store, deliveryFailureEvent, input.roleName, "leader-run-failed", input.now);
1574
1547
  }
1575
1548
  return "failed";
1576
1549
  });
@@ -1606,10 +1579,15 @@ export class FileSchedulerStoreAdapter {
1606
1579
  breakTaskSessionIfPresent(store, input.task.id, role.name, active.effective.agentId, input.now);
1607
1580
  const failure = recordLeaderFailure(input.task.id, input.failure.nativeSessionId, input.failure.message, input.now, store.getLeaderFailure(input.task.id));
1608
1581
  store.saveLeaderFailure(failure);
1609
- store.saveOperatorNotification(createLeaderRecoveryNotification(input.task.id, input.notification.message, input.now, store.getOperatorNotification(input.task.id)));
1610
- enqueueWork(store, { kind: "operator" }, "leader-recovery-failed", input.now, [
1611
- { type: "task", id: input.task.id }
1612
- ]);
1582
+ recordLeaderAttentionRequired(store, {
1583
+ taskId: input.task.id,
1584
+ reason: "leader-recovery-failed",
1585
+ payload: {
1586
+ message: input.failure.message,
1587
+ runId: active.id
1588
+ },
1589
+ now: input.now
1590
+ });
1613
1591
  return "failed";
1614
1592
  });
1615
1593
  }
@@ -1661,7 +1639,6 @@ export class FileSchedulerStoreAdapter {
1661
1639
  }, input.now);
1662
1640
  if (terminal.disposition !== "applied")
1663
1641
  return "state-changed";
1664
- clearMatchingLeaderStallAttention(store, task.id, currentRun.id);
1665
1642
  store.saveRole(task.id, updateRoleStatus(role, "exited", input.now));
1666
1643
  stopTaskSessionIfPresent(store, task.id, role.name, currentRun.effective.agentId, input.now);
1667
1644
  queueLeaderWakeup(store, task.id, wakeReason("review-failed"), input.now);
@@ -1679,7 +1656,6 @@ export class FileSchedulerStoreAdapter {
1679
1656
  }
1680
1657
  }
1681
1658
  store.saveAgentRun(failAgentRun(currentRun, input.summary, input.now));
1682
- clearMatchingLeaderStallAttention(store, task.id, currentRun.id);
1683
1659
  store.clearActiveAgentRun(task.id, role.name);
1684
1660
  clearTaskRoleRunInFlight(store, role, currentRun, input.now);
1685
1661
  if (mailbox !== null && processing !== null && processing !== undefined) {
@@ -1945,6 +1921,7 @@ export class FileSchedulerStoreAdapter {
1945
1921
  const role = requireRole(store, input.taskId, input.roleName);
1946
1922
  let sessions = store.getTaskRoleSessionSet(input.taskId, input.roleName)
1947
1923
  ?? createRoleSessionSet({ scope: "task", taskId: input.taskId, roleName: input.roleName }, input.agentId, now);
1924
+ const canonicalTurnId = canonicalStructuredProviderTurnId(sessions, input.turnId, input.attemptId);
1948
1925
  const existing = sessions.sessions[input.agentId];
1949
1926
  const owner = {
1950
1927
  scope: "task",
@@ -1961,7 +1938,7 @@ export class FileSchedulerStoreAdapter {
1961
1938
  throw new Error("Runtime turn completion does not match the effective launch identity.");
1962
1939
  }
1963
1940
  if (effectiveExisting !== undefined
1964
- && hasRecentTurnId(effectiveExisting.recentCompletedTurnIds, input.turnId)) {
1941
+ && hasRecentTurnId(effectiveExisting.recentCompletedTurnIds, canonicalTurnId)) {
1965
1942
  return { session: effectiveExisting, duplicate: true };
1966
1943
  }
1967
1944
  if (sessions.inFlight === null
@@ -1998,9 +1975,9 @@ export class FileSchedulerStoreAdapter {
1998
1975
  : sessions.inFlight === null ? idleStatus : "running",
1999
1976
  effective
2000
1977
  }, now);
2001
- sessions = settleStructuredProviderTurn(sessions, input.turnId, input.providerStatus ?? "completed", now);
1978
+ sessions = settleStructuredProviderTurn(sessions, canonicalTurnId, input.providerStatus ?? "completed", now);
2002
1979
  if (sessions.inFlight === null) {
2003
- sessions = rememberRoleAgentCompletedTurn(sessions, input.agentId, input.nativeSessionId, input.turnId, now);
1980
+ sessions = rememberRoleAgentCompletedTurn(sessions, input.agentId, input.nativeSessionId, canonicalTurnId, now);
2004
1981
  store.saveTaskRoleSessionSet(sessions);
2005
1982
  completeRuntimeHookReservation(store, owner, input.launchId);
2006
1983
  // The in-flight fence was cleared by an in-place provider retry
@@ -2024,7 +2001,7 @@ export class FileSchedulerStoreAdapter {
2024
2001
  sessions = recordTaskRoleTurnBoundary(sessions, {
2025
2002
  agentId: input.agentId,
2026
2003
  nativeSessionId: input.nativeSessionId,
2027
- turnId: input.turnId
2004
+ turnId: canonicalTurnId
2028
2005
  }, now);
2029
2006
  store.saveTaskRoleSessionSet(sessions);
2030
2007
  completeRuntimeHookReservation(store, owner, input.launchId);
@@ -2109,27 +2086,16 @@ export class FileSchedulerStoreAdapter {
2109
2086
  if (role !== null) {
2110
2087
  store.saveRole(input.taskId, updateRoleStatus(role, input.roleName === "leader" ? "failed" : "idle", now));
2111
2088
  }
2112
- store.saveEvent(input.taskId, createTaskEvent(store.nextEventId(input.taskId), input.taskId, "runtime.turn-failed", {
2089
+ const failureEvent = createTaskEvent(store.nextEventId(input.taskId), input.taskId, "runtime.turn-failed", {
2113
2090
  eventId: input.eventId,
2114
2091
  runId: input.runId,
2115
2092
  roleName: input.roleName,
2116
2093
  outcome: terminal.status
2117
- }, now));
2118
- if (input.roleName !== "leader") {
2119
- enqueueWork(store, { kind: "role", taskId: input.taskId, roleName: "leader" }, before.purpose === "review" ? "review-failed" : "role-run-failed", now, [
2120
- { type: "run", taskId: input.taskId, id: terminal.id },
2121
- ...(terminal.workItemId === undefined
2122
- ? []
2123
- : [{ type: "work-item", taskId: input.taskId, id: terminal.workItemId }])
2124
- ]);
2125
- }
2126
- else {
2127
- store.saveOperatorNotification(createLeaderRecoveryNotification(input.taskId, summary, now, store.getOperatorNotification(input.taskId)));
2128
- enqueueWork(store, { kind: "operator" }, "leader-run-failed", now, [
2129
- { type: "task", id: input.taskId },
2130
- { type: "run", taskId: input.taskId, id: terminal.id }
2131
- ]);
2132
- }
2094
+ }, now);
2095
+ store.saveEvent(input.taskId, failureEvent);
2096
+ routeRoleEvent(store, failureEvent, input.roleName, input.roleName !== "leader" && before.purpose === "review"
2097
+ ? "review-failed"
2098
+ : input.roleName === "leader" ? "leader-run-failed" : "role-run-failed", now);
2133
2099
  return { disposition: "applied", runId: input.runId };
2134
2100
  });
2135
2101
  }
@@ -2222,14 +2188,12 @@ export class FileSchedulerStoreAdapter {
2222
2188
  if (blocked.outcome === "exhausted")
2223
2189
  return null;
2224
2190
  store.saveAgentRun(withProviderRetry(run, blocked.retry));
2225
- this.recordProviderRetryClassified(store, input, classification.errorClass, {
2191
+ const classifiedEvent = this.recordProviderRetryClassified(store, input, classification.errorClass, {
2226
2192
  wouldRetry: "false",
2227
2193
  shadow: "false",
2228
2194
  note: "delivery-deduplication-unproven"
2229
2195
  }, now);
2230
- enqueueWork(store, input.roleName === "leader"
2231
- ? { kind: "operator" }
2232
- : { kind: "role", taskId: input.taskId, roleName: "leader" }, "provider-delivery-uncertain", now, [{ type: "run", taskId: input.taskId, id: input.runId }]);
2196
+ routeRoleEvent(store, classifiedEvent, input.roleName, "provider-delivery-uncertain", now);
2233
2197
  return { disposition: "applied", runId: input.runId };
2234
2198
  }
2235
2199
  }
@@ -2314,22 +2278,14 @@ export class FileSchedulerStoreAdapter {
2314
2278
  if (retryDecision.outcome === "exhausted")
2315
2279
  return null;
2316
2280
  store.saveAgentRun(withProviderRetry(run, retryDecision.retry));
2317
- this.recordProviderRetryClassified(store, input, "policy-denied", {
2281
+ const classifiedEvent = this.recordProviderRetryClassified(store, input, "policy-denied", {
2318
2282
  wouldRetry: "false",
2319
2283
  shadow: "false",
2320
2284
  note: "policy-denied-blocker"
2321
2285
  }, now);
2322
- if (input.roleName !== "leader") {
2323
- enqueueWork(store, { kind: "role", taskId: input.taskId, roleName: "leader" }, "provider-policy-blocked", now, [{ type: "run", taskId: input.taskId, id: input.runId }]);
2324
- }
2325
- else {
2326
- const message = `Leader Run ${input.runId} is blocked by Provider policy: ${summary}`;
2327
- store.saveOperatorNotification(createLeaderRecoveryNotification(input.taskId, message, now, store.getOperatorNotification(input.taskId)));
2328
- enqueueWork(store, { kind: "operator" }, "leader-provider-policy-blocked", now, [
2329
- { type: "task", id: input.taskId },
2330
- { type: "run", taskId: input.taskId, id: input.runId }
2331
- ]);
2332
- }
2286
+ routeRoleEvent(store, classifiedEvent, input.roleName, input.roleName === "leader"
2287
+ ? "leader-provider-policy-blocked"
2288
+ : "provider-policy-blocked", now);
2333
2289
  return { disposition: "applied", runId: input.runId };
2334
2290
  }
2335
2291
  /**
@@ -2355,7 +2311,7 @@ export class FileSchedulerStoreAdapter {
2355
2311
  return null;
2356
2312
  store.saveAgentRun(withProviderRetry(run, decision.retry));
2357
2313
  const driver = this.drivers.requireByAdapterId(input.adapterId);
2358
- store.saveEvent(input.taskId, createTaskEvent(store.nextEventId(input.taskId), input.taskId, "runtime.context-capacity-failure", {
2314
+ const capacityEvent = createTaskEvent(store.nextEventId(input.taskId), input.taskId, "runtime.context-capacity-failure", {
2359
2315
  eventId: input.eventId,
2360
2316
  runId: input.runId,
2361
2317
  roleName: input.roleName,
@@ -2366,15 +2322,14 @@ export class FileSchedulerStoreAdapter {
2366
2322
  action: driver.capabilities.lifecycle.compaction === "native-explicit"
2367
2323
  ? "native-explicit-compaction-required"
2368
2324
  : "await-provider-native-recovery"
2369
- }, now));
2325
+ }, now);
2326
+ store.saveEvent(input.taskId, capacityEvent);
2370
2327
  this.recordProviderRetryClassified(store, input, "context-capacity", {
2371
2328
  wouldRetry: "false",
2372
2329
  shadow: "false",
2373
2330
  note: "provider-native-capacity-recovery-only"
2374
2331
  }, now);
2375
- enqueueWork(store, input.roleName === "leader"
2376
- ? { kind: "operator" }
2377
- : { kind: "role", taskId: input.taskId, roleName: "leader" }, "provider-context-capacity-recovery-required", now, [{ type: "run", taskId: input.taskId, id: input.runId }]);
2332
+ routeRoleEvent(store, capacityEvent, input.roleName, "provider-context-capacity-recovery-required", now);
2378
2333
  return { disposition: "applied", runId: input.runId };
2379
2334
  }
2380
2335
  applyNativeSessionRecoverabilityBlock(store, input, summary, now) {
@@ -2393,27 +2348,28 @@ export class FileSchedulerStoreAdapter {
2393
2348
  if (decision.outcome === "exhausted")
2394
2349
  return null;
2395
2350
  store.saveAgentRun(withProviderRetry(run, decision.retry));
2396
- store.saveEvent(input.taskId, createTaskEvent(store.nextEventId(input.taskId), input.taskId, "runtime.native-session-recoverability-unproven", {
2351
+ const recoverabilityEvent = createTaskEvent(store.nextEventId(input.taskId), input.taskId, "runtime.native-session-recoverability-unproven", {
2397
2352
  eventId: input.eventId,
2398
2353
  runId: input.runId,
2399
2354
  roleName: input.roleName,
2400
2355
  launchId: input.launchId,
2401
2356
  nativeSessionId: input.nativeSessionId,
2402
2357
  requiredEvidence: "exact-native-resume-outcome"
2403
- }, now));
2404
- enqueueWork(store, input.roleName === "leader"
2405
- ? { kind: "operator" }
2406
- : { kind: "role", taskId: input.taskId, roleName: "leader" }, "native-session-recoverability-unproven", now, [{ type: "run", taskId: input.taskId, id: input.runId }]);
2358
+ }, now);
2359
+ store.saveEvent(input.taskId, recoverabilityEvent);
2360
+ routeRoleEvent(store, recoverabilityEvent, input.roleName, "native-session-recoverability-unproven", now);
2407
2361
  return { disposition: "applied", runId: input.runId };
2408
2362
  }
2409
2363
  recordProviderRetryClassified(store, input, errorClass, extra, now) {
2410
- store.saveEvent(input.taskId, createTaskEvent(store.nextEventId(input.taskId), input.taskId, "runtime.provider-retry-classified", {
2364
+ const event = createTaskEvent(store.nextEventId(input.taskId), input.taskId, "runtime.provider-retry-classified", {
2411
2365
  eventId: input.eventId,
2412
2366
  runId: input.runId,
2413
2367
  roleName: input.roleName,
2414
2368
  errorClass,
2415
2369
  ...extra
2416
- }, now));
2370
+ }, now);
2371
+ store.saveEvent(input.taskId, event);
2372
+ return event;
2417
2373
  }
2418
2374
  /**
2419
2375
  * Issue 04 durable retry timer: lists active Runs whose in-place retry is
@@ -2580,18 +2536,17 @@ export class FileSchedulerStoreAdapter {
2580
2536
  return;
2581
2537
  }
2582
2538
  store.saveAgentRun(withProviderRetry(run, deferred));
2583
- if (!store.listEvents(entry.taskId).some((event) => (event.type === "runtime.provider-retry-native-resume-unproven"
2584
- && event.payload.runId === run.id))) {
2585
- store.saveEvent(entry.taskId, createTaskEvent(store.nextEventId(entry.taskId), entry.taskId, "runtime.provider-retry-native-resume-unproven", {
2539
+ let attentionEvent = store.listEvents(entry.taskId).find((event) => (event.type === "runtime.provider-retry-native-resume-unproven"
2540
+ && event.payload.runId === run.id));
2541
+ if (attentionEvent === undefined) {
2542
+ attentionEvent = createTaskEvent(store.nextEventId(entry.taskId), entry.taskId, "runtime.provider-retry-native-resume-unproven", {
2586
2543
  runId: run.id,
2587
2544
  consecutiveFailures: String(run.providerRetry.consecutiveFailures),
2588
2545
  dispatchedRetries: String(run.providerRetry.dispatchedRetries)
2589
- }, now));
2546
+ }, now);
2547
+ store.saveEvent(entry.taskId, attentionEvent);
2590
2548
  }
2591
- enqueueWork(store, { kind: "operator" }, "provider-retry-native-resume-unproven", now, [
2592
- { type: "task", id: entry.taskId },
2593
- { type: "run", taskId: entry.taskId, id: run.id }
2594
- ]);
2549
+ routeRoleEvent(store, attentionEvent, entry.roleName, "provider-retry-native-resume-unproven", now);
2595
2550
  return;
2596
2551
  }
2597
2552
  if (hasUnsettledRuntimeOperations(store, entry.taskId, run.id)) {
@@ -3027,7 +2982,8 @@ export class FileSchedulerStoreAdapter {
3027
2982
  && !runtimeHookMatchesReservation(store, owner, input.launchId)) {
3028
2983
  throw new Error("Runtime turn completion does not match the global launch reservation.");
3029
2984
  }
3030
- const effectiveExisting = nativeTransitionExisting(store, owner, existing, input.nativeSessionId, input.launchId, "Runtime turn completion conflicts with the fixed global Role session.");
2985
+ const nativeSessionId = globalCompletionNativeSessionId(existing, input);
2986
+ const effectiveExisting = nativeTransitionExisting(store, owner, existing, nativeSessionId, input.launchId, "Runtime turn completion conflicts with the fixed global Role session.");
3031
2987
  const effective = globalSessionEffective(role, effectiveExisting);
3032
2988
  if (effective.agentId !== input.agentId || effective.adapterId !== input.adapterId) {
3033
2989
  throw new Error("Runtime turn completion does not match the effective global launch identity.");
@@ -3039,7 +2995,7 @@ export class FileSchedulerStoreAdapter {
3039
2995
  current = recordRoleAgentSession(current, {
3040
2996
  agentId: input.agentId,
3041
2997
  adapterId: input.adapterId,
3042
- nativeSessionId: input.nativeSessionId,
2998
+ nativeSessionId,
3043
2999
  ...(input.launchId === undefined ? {} : { launchId: input.launchId }),
3044
3000
  title: effectiveExisting?.title ?? input.title,
3045
3001
  preview: effectiveExisting?.preview ?? (input.summary === undefined ? undefined : sessionPreview(input.summary)),
@@ -3047,7 +3003,7 @@ export class FileSchedulerStoreAdapter {
3047
3003
  status: completedStatus,
3048
3004
  effective
3049
3005
  }, now);
3050
- current = rememberRoleAgentCompletedTurn(current, input.agentId, input.nativeSessionId, input.turnId, now);
3006
+ current = rememberRoleAgentCompletedTurn(current, input.agentId, nativeSessionId, input.turnId, now);
3051
3007
  store.saveGlobalRoleSessionSet(current);
3052
3008
  completeRuntimeHookReservation(store, owner, input.launchId);
3053
3009
  return current.sessions[input.agentId];
@@ -3066,9 +3022,10 @@ export class FileSchedulerStoreAdapter {
3066
3022
  if (existing === undefined
3067
3023
  && !runtimeHookMatchesReservation(this.store, owner, input.launchId))
3068
3024
  return "obsolete";
3025
+ const nativeSessionId = globalCompletionNativeSessionId(existing, input);
3069
3026
  let effectiveExisting;
3070
3027
  try {
3071
- effectiveExisting = nativeTransitionExisting(this.store, owner, existing, input.nativeSessionId, input.launchId, "Runtime turn completion conflicts with the fixed global Role session.");
3028
+ effectiveExisting = nativeTransitionExisting(this.store, owner, existing, nativeSessionId, input.launchId, "Runtime turn completion conflicts with the fixed global Role session.");
3072
3029
  }
3073
3030
  catch {
3074
3031
  return "obsolete";
@@ -3369,7 +3326,7 @@ function finalizeProviderRetryDeadline(store, run, summary, reason, now) {
3369
3326
  if (terminalization.disposition !== "applied" || terminalization.run === null)
3370
3327
  return false;
3371
3328
  const terminal = terminalization.run;
3372
- store.saveEvent(run.taskId, createTaskEvent(store.nextEventId(run.taskId), run.taskId, "runtime.provider-retry-exhausted", {
3329
+ const exhaustionEvent = createTaskEvent(store.nextEventId(run.taskId), run.taskId, "runtime.provider-retry-exhausted", {
3373
3330
  runId: run.id,
3374
3331
  roleName: run.roleName,
3375
3332
  episodeId: retry.episodeId,
@@ -3377,7 +3334,8 @@ function finalizeProviderRetryDeadline(store, run, summary, reason, now) {
3377
3334
  consecutiveFailures: String(retry.consecutiveFailures),
3378
3335
  dispatchedRetries: String(retry.dispatchedRetries),
3379
3336
  elapsedMs: String(Math.max(0, now.getTime() - Date.parse(retry.firstFailureAt)))
3380
- }, now));
3337
+ }, now);
3338
+ store.saveEvent(run.taskId, exhaustionEvent);
3381
3339
  if (terminal.purpose === "execution" && terminal.workItemId !== undefined) {
3382
3340
  const item = store.getWorkItem(run.taskId, terminal.workItemId);
3383
3341
  if (item !== null
@@ -3392,15 +3350,8 @@ function finalizeProviderRetryDeadline(store, run, summary, reason, now) {
3392
3350
  }
3393
3351
  if (run.roleName === "leader") {
3394
3352
  store.saveLeaderFailure(recordLeaderFailure(run.taskId, retry.nativeSessionId ?? "(unproven)", summary, now, store.getLeaderFailure(run.taskId)));
3395
- store.saveOperatorNotification(createLeaderRecoveryNotification(run.taskId, summary, now, store.getOperatorNotification(run.taskId)));
3396
- enqueueWork(store, { kind: "operator" }, "provider-retry-exhausted", now, [
3397
- { type: "task", id: run.taskId },
3398
- { type: "run", taskId: run.taskId, id: run.id }
3399
- ]);
3400
- }
3401
- else {
3402
- enqueueWork(store, { kind: "role", taskId: run.taskId, roleName: "leader" }, "provider-retry-exhausted", now, [{ type: "run", taskId: run.taskId, id: run.id }]);
3403
3353
  }
3354
+ routeRoleEvent(store, exhaustionEvent, run.roleName, "provider-retry-exhausted", now);
3404
3355
  return true;
3405
3356
  }
3406
3357
  function preparedDeliveryFailureSessionFenceMatches(sessions, session, input, expectedReceiptId) {
@@ -3475,7 +3426,7 @@ function recordTaskRuntimeNativeSession(store, input, now) {
3475
3426
  const effective = input.effective === undefined
3476
3427
  ? resolvedEffective
3477
3428
  : validateEffectiveLaunchSnapshot(input.effective);
3478
- if (!effectiveLaunchSnapshotsCompatibleForTaskMain(resolvedEffective, effective, store.getTaskWorkspace(input.taskId))) {
3429
+ if (!effectiveLaunchSnapshotsCompatibleForTaskSession(resolvedEffective, effective)) {
3479
3430
  throw new Error("Reserved native Session effective launch changed before persistence.");
3480
3431
  }
3481
3432
  if (effective.agentId !== input.agentId || effective.adapterId !== input.adapterId) {
@@ -3536,6 +3487,17 @@ function nativeTransitionExisting(store, owner, existing, nativeSessionId, launc
3536
3487
  }
3537
3488
  throw new Error(conflictMessage);
3538
3489
  }
3490
+ function globalCompletionNativeSessionId(existing, input) {
3491
+ // Codex app-server and its notify Hook expose different native IDs for the
3492
+ // same launch. The launch generation remains the authoritative fence.
3493
+ return input.adapterId === "codex"
3494
+ && input.launchId !== undefined
3495
+ && existing?.launchId === input.launchId
3496
+ && existing.agentId === input.agentId
3497
+ && existing.adapterId === input.adapterId
3498
+ ? existing.nativeSessionId
3499
+ : input.nativeSessionId;
3500
+ }
3539
3501
  function mapRole(store, role) {
3540
3502
  const binding = activeRoleAgentBinding(role);
3541
3503
  const item = store.listWorkItems(role.taskId).find((candidate) => (candidate.assignee === role.name
@@ -3581,8 +3543,7 @@ function taskSessionEffective(store, taskId, roleName, agentId, existing) {
3581
3543
  throw new Error(`Native Session registration does not match the effective Run Agent: ${taskId}/${roleName}.`);
3582
3544
  }
3583
3545
  if (existing !== undefined) {
3584
- const workspace = active.workspace ?? store.getTaskWorkspace(taskId);
3585
- if (!effectiveLaunchSnapshotsCompatibleForTaskMain(existing.effective, active.effective, workspace)) {
3546
+ if (!effectiveLaunchSnapshotsCompatibleForTaskSession(existing.effective, active.effective)) {
3586
3547
  throw new Error(`Native Session effective launch does not match the active Run: ${taskId}/${roleName}.`);
3587
3548
  }
3588
3549
  return existing.effective;
@@ -3788,11 +3749,10 @@ function runtimeObservationTelemetryEntry(input) {
3788
3749
  };
3789
3750
  }
3790
3751
  /**
3791
- * Runtime observations are a current-state snapshot, not an append-only
3792
- * semantic history. Retain active operations, one latest usage baseline, and
3793
- * one latest confirmed activity boundary so a token stream remains
3794
- * O(active operations) per Run without losing the delta that was already
3795
- * proven before an unchanged later snapshot arrived.
3752
+ * Usage observations and incomplete request boundaries are authoritative
3753
+ * read-only history: retain each one so consecutive deltas remain projectable.
3754
+ * Other activity observations are a current explicit boundary and may replace
3755
+ * their predecessor. Token evidence never becomes lifecycle activity.
3796
3756
  */
3797
3757
  function compactedRuntimeObservationIds(events, incoming) {
3798
3758
  const existing = events.flatMap((event) => {
@@ -3809,9 +3769,10 @@ function compactedRuntimeObservationIds(events, incoming) {
3809
3769
  });
3810
3770
  const remove = ({ observation }) => {
3811
3771
  if (incoming.kind === "activity.observed") {
3772
+ if (isRuntimeTokenEvidence(incoming))
3773
+ return false;
3812
3774
  return observation.kind === "activity.observed"
3813
- && (observation.payload.usage === undefined)
3814
- === (incoming.payload.usage === undefined);
3775
+ && !isRuntimeTokenEvidence(observation);
3815
3776
  }
3816
3777
  if (incoming.kind === "operation.started") {
3817
3778
  return (observation.kind === "operation.started"
@@ -3854,7 +3815,6 @@ function recordStructuredProviderAcceptance(sessions, input, now) {
3854
3815
  if (current === null || attemptId === undefined || turnId === undefined)
3855
3816
  return sessions;
3856
3817
  if (current.turn?.attemptId === attemptId
3857
- && current.turn.turnId === turnId
3858
3818
  && ["accepted", "running", "completed", "failed", "cancelled"].includes(current.turn.status))
3859
3819
  return sessions;
3860
3820
  const binding = acceptProviderTurn(current, {
@@ -3864,6 +3824,20 @@ function recordStructuredProviderAcceptance(sessions, input, now) {
3864
3824
  });
3865
3825
  return updateTaskRoleProviderRuntime(sessions, binding, now);
3866
3826
  }
3827
+ /**
3828
+ * A Provider may expose the same accepted input through more than one exact
3829
+ * observation surface. Preserve the first native Turn identity bound to the
3830
+ * durable attempt; a later transport alias may settle that same attempt but
3831
+ * must neither replace nor strand the canonical identity.
3832
+ */
3833
+ function canonicalStructuredProviderTurnId(sessions, observedTurnId, attemptId) {
3834
+ const turn = sessions.providerBinding?.turn;
3835
+ return attemptId !== undefined
3836
+ && turn?.attemptId === attemptId
3837
+ && turn.turnId !== undefined
3838
+ ? turn.turnId
3839
+ : observedTurnId;
3840
+ }
3867
3841
  function bindOrSupersedeProviderRuntime(sessions, input, now) {
3868
3842
  if (sessions.inFlight === null)
3869
3843
  return sessions;
@@ -3931,16 +3905,14 @@ function deferUnprovenProviderRecovery(store, run, roleName, reason, now) {
3931
3905
  return;
3932
3906
  }
3933
3907
  store.saveAgentRun(withProviderRetry(run, deferred));
3934
- store.saveEvent(run.taskId, createTaskEvent(store.nextEventId(run.taskId), run.taskId, "runtime.provider-recovery-attention", {
3908
+ const attentionEvent = createTaskEvent(store.nextEventId(run.taskId), run.taskId, "runtime.provider-recovery-attention", {
3935
3909
  runId: run.id,
3936
3910
  roleName,
3937
3911
  reason,
3938
3912
  nextAttemptAt: deferred.nextAttemptAt ?? ""
3939
- }, now));
3940
- enqueueWork(store, { kind: "operator" }, "provider-recovery-attention", now, [
3941
- { type: "task", id: run.taskId },
3942
- { type: "run", taskId: run.taskId, id: run.id }
3943
- ]);
3913
+ }, now);
3914
+ store.saveEvent(run.taskId, attentionEvent);
3915
+ routeRoleEvent(store, attentionEvent, roleName, "provider-recovery-attention", now);
3944
3916
  }
3945
3917
  function markSubmittingProviderTurnUnknown(store, target, delivery, now, reason) {
3946
3918
  if (target.kind !== "role"
@@ -3974,39 +3946,6 @@ function settleStructuredProviderTurn(sessions, turnId, status, now) {
3974
3946
  settledAt: now.toISOString()
3975
3947
  }), now);
3976
3948
  }
3977
- function confirmedUsageActivityObservation(events, incoming) {
3978
- const usage = incoming.payload.usage;
3979
- if (incoming.kind !== "activity.observed" || usage === undefined)
3980
- return null;
3981
- const previous = events
3982
- .map(runtimeObservationFromTaskEvent)
3983
- .filter((observation) => (observation !== null
3984
- && observation.kind === "activity.observed"
3985
- && observation.payload.usage !== undefined
3986
- && runtimeObservationFenceMatches(observation.fence, incoming.fence)))
3987
- .sort(compareCanonicalObservationOrder)
3988
- .at(-1)?.payload.usage;
3989
- if (previous === undefined
3990
- || usage.inputTokens + usage.outputTokens
3991
- <= previous.inputTokens + previous.outputTokens)
3992
- return null;
3993
- const digest = createHash("sha256")
3994
- .update(`confirmed-runtime-activity\0${incoming.eventId}`)
3995
- .digest("hex");
3996
- return createRuntimeObservation({
3997
- schemaVersion: 2,
3998
- eventId: `runtime-activity-${digest}`,
3999
- semanticKey: `runtime-activity-${digest}`,
4000
- kind: "activity.observed",
4001
- authority: "controller",
4002
- receivedAt: incoming.receivedAt,
4003
- ...(incoming.observedAt === undefined ? {} : { observedAt: incoming.observedAt }),
4004
- ...(incoming.sequence === undefined ? {} : { sequence: incoming.sequence }),
4005
- ...(incoming.ordinal === undefined ? {} : { ordinal: incoming.ordinal }),
4006
- fence: incoming.fence,
4007
- payload: { activity: incoming.payload.activity ?? "model" }
4008
- });
4009
- }
4010
3949
  function usageSnapshotIsSuperseded(events, incoming) {
4011
3950
  if (incoming.kind !== "activity.observed" || incoming.payload.usage === undefined) {
4012
3951
  return false;
@@ -4019,6 +3958,15 @@ function usageSnapshotIsSuperseded(events, incoming) {
4019
3958
  && runtimeObservationFenceMatches(observation.fence, incoming.fence)
4020
3959
  && observationIsStrictlyNewer(observation, incoming));
4021
3960
  }
3961
+ function hasPersistedRuntimeObservation(events, incoming) {
3962
+ const usageIdentity = isRuntimeTokenEvidence(incoming)
3963
+ ? incoming.eventId
3964
+ : undefined;
3965
+ return events.some((event) => (event.type === RUNTIME_OBSERVATION_TASK_EVENT
3966
+ && (usageIdentity === undefined
3967
+ ? event.payload.semanticKey === incoming.semanticKey
3968
+ : event.payload.eventId === usageIdentity)));
3969
+ }
4022
3970
  function observationIsStrictlyNewer(left, right) {
4023
3971
  return compareCanonicalObservationOrder(left, right) > 0;
4024
3972
  }