@zq-silk/yui 0.14.1 → 0.14.2

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 (177) hide show
  1. package/ARCHITECTURE.md +64 -35
  2. package/README.md +254 -124
  3. package/dist/agent/managedRuntimeEnvironment.js +5 -8
  4. package/dist/cli/commandCatalog.js +74 -109
  5. package/dist/cli/helpRenderer.js +1 -2
  6. package/dist/cli/interactionCandidates.js +10 -0
  7. package/dist/cli/interactionPolicy.js +76 -37
  8. package/dist/cli/roleWizard.js +5 -6
  9. package/dist/cli/updateOrchestrator.js +10 -8
  10. package/dist/cli/updatePorts.js +17 -13
  11. package/dist/cli/upgradeCommand.js +2 -5
  12. package/dist/cli.js +268 -231
  13. package/dist/commands/agentCommands.js +13 -0
  14. package/dist/commands/configCommands.js +7 -19
  15. package/dist/commands/configOverview.js +3 -2
  16. package/dist/commands/durableJobCommands.js +1 -1
  17. package/dist/commands/executionAuditCommands.js +4 -9
  18. package/dist/commands/globalRoleCommands.js +17 -0
  19. package/dist/commands/profileCommands.js +200 -30
  20. package/dist/commands/sessionCommands.js +1 -1
  21. package/dist/commands/taskActor.js +29 -127
  22. package/dist/commands/taskChangeSetCommands.js +1 -3
  23. package/dist/commands/taskCommands.js +1771 -2107
  24. package/dist/commands/taskCompletionGate.js +27 -103
  25. package/dist/commands/taskContextCommand.js +47 -80
  26. package/dist/commands/taskExecutionCommands.js +9 -27
  27. package/dist/commands/taskInputCommands.js +10 -10
  28. package/dist/commands/taskIntegrationCommands.js +69 -61
  29. package/dist/commands/taskIntegrationQueueCommands.js +1 -1
  30. package/dist/commands/taskNextActionCommand.js +8 -29
  31. package/dist/commands/taskOverviewCommand.js +25 -3
  32. package/dist/commands/taskPublicationCommands.js +151 -85
  33. package/dist/commands/taskPublicationVerifyCommand.js +181 -0
  34. package/dist/commands/taskRemoteDeliveryCommand.js +134 -0
  35. package/dist/commands/taskRoleRuntimeStatus.js +5 -6
  36. package/dist/commands/taskUpstreamCommands.js +79 -95
  37. package/dist/config/configCatalog.js +1 -1
  38. package/dist/context/contextSnapshot.js +17 -2
  39. package/dist/context/sourceTurnContext.js +30 -0
  40. package/dist/context/turnContextPack.js +107 -48
  41. package/dist/context/turnInputContract.js +11 -4
  42. package/dist/context/wakeNotification.js +84 -22
  43. package/dist/controller/agentRuntimeObserver.js +9 -12
  44. package/dist/controller/clientRuntime.js +13 -13
  45. package/dist/controller/controller.js +11 -11
  46. package/dist/controller/fileSchedulerStoreAdapter.js +262 -219
  47. package/dist/controller/jobControl.js +11 -10
  48. package/dist/controller/resourceInventory.js +6 -6
  49. package/dist/controller/resourceInventoryLinux.js +2 -2
  50. package/dist/controller/runtime.js +18 -61
  51. package/dist/controller/runtimeEventInbox.js +57 -34
  52. package/dist/controller/runtimeEventProcessor.js +36 -96
  53. package/dist/controller/runtimeHookTurnFence.js +33 -57
  54. package/dist/controller/runtimeLaunchCoordinator.js +44 -40
  55. package/dist/controller/runtimeObservationHook.js +18 -12
  56. package/dist/controller/sessionNotify.js +43 -28
  57. package/dist/controller/sessionOwnerReconciliation.js +21 -21
  58. package/dist/controller/structuredProviderObservation.js +13 -16
  59. package/dist/coordination/workMailboxQueue.js +132 -0
  60. package/dist/core/controllerServer.js +1 -0
  61. package/dist/domain/agentResultTransport.js +60 -0
  62. package/dist/execution/executionHealth.js +111 -118
  63. package/dist/execution/reviewMainTurn.js +161 -0
  64. package/dist/execution/workItemExecution.js +454 -0
  65. package/dist/execution/workItemExecutionProjection.js +309 -0
  66. package/dist/execution/workItemMainTurn.js +164 -0
  67. package/dist/executor/agentExecutor.js +19 -19
  68. package/dist/executor/executorRegistry.js +7 -28
  69. package/dist/executor/fileRoleLaunchPlanner.js +16 -82
  70. package/dist/external/githubPublicationVerifier.js +114 -0
  71. package/dist/external/gitlabPublicationVerifier.js +158 -0
  72. package/dist/external/pinnedCommandRunner.js +73 -0
  73. package/dist/integration/changeSetManifest.js +0 -3
  74. package/dist/integration/deliveryObligation.js +43 -6
  75. package/dist/integration/gitIntegrationService.js +284 -101
  76. package/dist/integration/integrationAttempt.js +64 -14
  77. package/dist/integration/integrationQueueEntry.js +5 -35
  78. package/dist/integration/integrationQueueService.js +65 -194
  79. package/dist/lifecycle/canonicalLifecycleEvent.js +16 -15
  80. package/dist/lifecycle/exactTurnTerminalization.js +100 -97
  81. package/dist/message/message.js +16 -0
  82. package/dist/observability/executionAudit.js +4 -25
  83. package/dist/observability/faultClassification.js +58 -70
  84. package/dist/observability/orchestrationMetrics.js +8 -50
  85. package/dist/profile/agentProfile.js +60 -15
  86. package/dist/profile/agentProfileRuntime.js +100 -0
  87. package/dist/release/releaseWorkflowPorts.js +5 -68
  88. package/dist/repository/executionLaneGitSnapshot.js +80 -0
  89. package/dist/repository/gitWorkspace.js +9 -67
  90. package/dist/repository/taskBaseFreshness.js +3 -1
  91. package/dist/repository/taskWorkspaceCoordinator.js +32 -4
  92. package/dist/repository/taskWorkspacePreparer.js +252 -411
  93. package/dist/resources/liveReferences.js +6 -55
  94. package/dist/resources/resourceDiscovery.js +1 -30
  95. package/dist/resources/resourceRegistrar.js +0 -23
  96. package/dist/review/deltaRecheck.js +8 -65
  97. package/dist/review/reviewAcceptance.js +22 -19
  98. package/dist/review/reviewConfig.js +0 -10
  99. package/dist/review/reviewDecision.js +2 -2
  100. package/dist/review/reviewRound.js +147 -273
  101. package/dist/review/reviewerAvailability.js +13 -23
  102. package/dist/role/role.js +3 -0
  103. package/dist/runtime/agentDriverObservation.js +1 -1
  104. package/dist/runtime/agentHost.js +51 -51
  105. package/dist/runtime/builtinAgentDrivers.js +9 -2
  106. package/dist/runtime/codexAppServerRuntime.js +23 -5
  107. package/dist/runtime/exactControlPlane.js +2 -267
  108. package/dist/runtime/firstProgressAdvisory.js +1 -1
  109. package/dist/runtime/index.js +1 -1
  110. package/dist/runtime/launchBroker.js +11 -11
  111. package/dist/runtime/lifecycleReservation.js +2 -2
  112. package/dist/runtime/managedCaller.js +111 -0
  113. package/dist/runtime/ports.js +16 -3
  114. package/dist/runtime/preallocatedNativeSession.js +2 -2
  115. package/dist/runtime/processExitObservation.js +3 -2
  116. package/dist/runtime/providerContinuationReconciliationService.js +1 -1
  117. package/dist/runtime/runtimeBinding.js +1 -1
  118. package/dist/runtime/runtimeObservation.js +33 -16
  119. package/dist/runtime/runtimeSessionCandidate.js +2 -2
  120. package/dist/runtime/runtimeStopReceipt.js +11 -11
  121. package/dist/runtime/sessionLaunchRequest.js +2 -2
  122. package/dist/runtime/sessionOwnerIdentity.js +9 -9
  123. package/dist/runtime/sessionOwnerRegistry.js +13 -13
  124. package/dist/runtime/sessionReconciliation.js +4 -4
  125. package/dist/runtime/sessionTerminationGuard.js +9 -9
  126. package/dist/runtime/sessionTokenMetrics.js +5 -7
  127. package/dist/runtime/structuredProviderHost.js +5 -5
  128. package/dist/runtime/taskRuntimeIsolation.js +24 -24
  129. package/dist/runtime/tmuxAdapters.js +32 -30
  130. package/dist/scheduler/activeRoleTurnDelivery.js +37 -15
  131. package/dist/scheduler/leaderWakeupProcessor.js +78 -55
  132. package/dist/scheduler/roleTurnLiveness.js +6 -6
  133. package/dist/scheduler/roleTurnStall.js +8 -9
  134. package/dist/scheduler/taskExecutionProjection.js +19 -41
  135. package/dist/scheduler/taskObservabilityProjection.js +75 -122
  136. package/dist/storage/sqliteSchema.js +1 -21
  137. package/dist/storage/sqliteStore.js +62 -47
  138. package/dist/storage/storageSchema.js +1 -8
  139. package/dist/storage/storageVersions.js +7 -4
  140. package/dist/storage/taskStore.js +8 -8
  141. package/dist/storage/upgrade/upgradeOrchestrator.js +112 -83
  142. package/dist/task/completionReadiness.js +17 -98
  143. package/dist/task/deliveryGuard.js +9 -11
  144. package/dist/task/draftPlan.js +142 -0
  145. package/dist/task/nextAction.js +179 -477
  146. package/dist/task/publicationVerification.js +1 -0
  147. package/dist/task/remoteDelivery.js +233 -0
  148. package/dist/task/task.js +108 -4
  149. package/dist/task/taskRecordReference.js +0 -1
  150. package/dist/turn/turn.js +109 -16
  151. package/dist/verification/verificationGateService.js +0 -26
  152. package/dist/web/assets/client/components.js +99 -121
  153. package/dist/web/assets/client/i18n.js +136 -58
  154. package/dist/web/assets/client/view.js +44 -0
  155. package/dist/web/assets/styles/cards.js +3 -15
  156. package/dist/web/assets/styles/widgets.js +4 -0
  157. package/dist/web/webSnapshot.js +29 -14
  158. package/dist/workItem/dependencyGate.js +34 -0
  159. package/dist/workItem/workItem.js +99 -266
  160. package/dist/workspace/workItemChangeSetManager.js +15 -59
  161. package/docs/task-local-identity.md +1 -1
  162. package/i18n/README.zh-CN.md +164 -58
  163. package/package.json +1 -1
  164. package/skills/yui-leader/SKILL.md +272 -782
  165. package/skills/yui-operator/SKILL.md +190 -365
  166. package/skills/yui-reviewer/SKILL.md +40 -33
  167. package/skills/yui-runtime/SKILL.md +22 -0
  168. package/skills/yui-worker/SKILL.md +19 -6
  169. package/dist/execution/candidateConvergence.js +0 -623
  170. package/dist/execution/executionGroup.js +0 -854
  171. package/dist/execution/resourceBroker.js +0 -426
  172. package/dist/executor/launchPlan.js +0 -16
  173. package/dist/review/reviewFinding.js +0 -240
  174. package/dist/review/reviewFindingLedger.js +0 -550
  175. package/dist/review/reviewOutcomeClassifier.js +0 -255
  176. package/dist/scheduler/resourceQueueProjection.js +0 -71
  177. package/dist/task/repairWave.js +0 -150
@@ -12,8 +12,8 @@ import { activeRoleAgentBinding } from "../role/role.js";
12
12
  import { effectiveLaunchWithTaskMainWorkspace, effectiveLaunchSnapshotsCompatible, effectiveLaunchSnapshotsCompatibleForTaskSession, resolveEffectiveLaunch, validateEffectiveLaunchSnapshot } from "../executor/effectiveLaunch.js";
13
13
  import { SYSTEM_OPERATOR_ROLE } from "../role/systemRoles.js";
14
14
  import { appendTurnInput, createTurn } from "../turn/turn.js";
15
+ import { transportAgentResult } from "../domain/agentResultTransport.js";
15
16
  import { createTurnInput } from "../context/turnInputContract.js";
16
- import { parseReviewResultReport } from "../review/reviewRound.js";
17
17
  import { classifyRuntimeProcessExit, validateRuntimeProcessExitObservation } from "../runtime/processExitObservation.js";
18
18
  import { terminalizeExactTaskTurn } from "../lifecycle/exactTurnTerminalization.js";
19
19
  import { createCanonicalLifecycleEvent, foldCanonicalLifecycleEvent } from "../lifecycle/canonicalLifecycleEvent.js";
@@ -28,13 +28,14 @@ import { projectProviderContinuations } from "../runtime/runtimeContinuationProj
28
28
  import { providerContinuationKey } from "../runtime/providerContinuation.js";
29
29
  import { formatTurnReceiptId } from "../task/taskRecordReference.js";
30
30
  import { bindExecution, claimPending, completeProcessing, consumePendingBatch, mailboxHasPending, mailboxHasWork, releaseProcessing } from "../coordination/workMailbox.js";
31
- import { enqueueWork } from "../coordination/workMailboxQueue.js";
31
+ import { enqueueWork, settleRoleTurnDispatch as settleRoleTurnDispatchMailbox } from "../coordination/workMailboxQueue.js";
32
32
  import { RUNTIME_CLEANUP_REQUIRED_REASON, RUNTIME_HOST_DETACH_REQUIRED_REASON, RUNTIME_LAUNCH_RESERVED_REASON, RUNTIME_LIFECYCLE_OWNER, RuntimeLifecycleBusyError, hasRuntimeCleanupObligation, hasRuntimeLifecycleWork, isRuntimeCleanupReason, isRuntimeLaunchReservation, runtimeCleanupDisposition, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
33
33
  import { nativeSessionIdForLaunch } from "../runtime/preallocatedNativeSession.js";
34
34
  import { builtinAgentDriverRegistry } from "../runtime/builtinAgentDrivers.js";
35
35
  import { standardAgentError } from "../runtime/agentError.js";
36
36
  import { RUNTIME_OBSERVATION_TASK_EVENT, createRuntimeObservation, isRuntimeTokenEvidence, runtimeObservationFenceMatches, runtimeObservationFromTaskEvent, runtimeObservationTurnFenceMatches, runtimeObservationTaskEventPayload } from "../runtime/runtimeObservation.js";
37
37
  import { contextSnapshotRef } from "../context/contextSnapshot.js";
38
+ import { snapshotExecutionLaneWorkspaceSync } from "../repository/executionLaneGitSnapshot.js";
38
39
  import { contextSnapshotDeltaRefIds, freezeTurnContextSnapshot } from "../context/turnContextPack.js";
39
40
  export class AgentHostProviderTurnFenceError extends Error {
40
41
  constructor(message) {
@@ -47,11 +48,13 @@ export class FileSchedulerStoreAdapter {
47
48
  store;
48
49
  telemetry;
49
50
  drivers;
51
+ snapshotExecutionLaneWorkspace;
50
52
  /** Diagnostic telemetry is optional and never participates in runtime truth. */
51
- constructor(store, telemetry = null, drivers = builtinAgentDriverRegistry()) {
53
+ constructor(store, telemetry = null, drivers = builtinAgentDriverRegistry(), snapshotExecutionLaneWorkspace = snapshotExecutionLaneWorkspaceSync) {
52
54
  this.store = store;
53
55
  this.telemetry = telemetry;
54
56
  this.drivers = drivers;
57
+ this.snapshotExecutionLaneWorkspace = snapshotExecutionLaneWorkspace;
55
58
  }
56
59
  freezeLeaderContextSnapshot(taskId, roleName, now) {
57
60
  return this.store.transaction((tx) => {
@@ -96,29 +99,14 @@ export class FileSchedulerStoreAdapter {
96
99
  case "input.accepted":
97
100
  outcome = this.observeRuntimePromptAccepted(input, adapterId, now);
98
101
  break;
99
- case "turn.completed": {
100
- const classification = this.classifyRuntimeTurnCompleted({
101
- taskId,
102
- roleName: input.fence.roleName,
103
- agentId: input.fence.agentId,
104
- adapterId,
105
- launchId: input.fence.launchId,
106
- nativeSessionId: input.fence.nativeSessionId,
107
- nativeTurnId: input.fence.nativeTurnId,
108
- attemptId: input.fence.receiptId,
109
- turnId: input.fence.turnId
110
- });
111
- if (classification !== "apply")
112
- return classification;
113
- outcome = this.foldProviderTurnBoundary(input, adapterId, "completed", input.payload.summary ?? "Agent turn completed without a workflow outcome.", now);
102
+ case "turn.completed":
103
+ outcome = this.foldProviderTurnBoundary(input, adapterId, "completed", now);
114
104
  break;
115
- }
116
105
  case "turn.failed": {
117
- const failureEvidence = input.payload.failure;
118
106
  // A Provider Turn is an activation boundary, not a Task outcome.
119
107
  // Keep the Turn and Session identity available for Agent-directed
120
108
  // restore + submit, even when a provider marks its Turn terminal.
121
- outcome = this.foldProviderTurnBoundary(input, adapterId, "failed", input.payload.summary ?? `Agent Turn failed: ${failureEvidence.error.message}`, now);
109
+ outcome = this.foldProviderTurnBoundary(input, adapterId, "failed", now);
122
110
  break;
123
111
  }
124
112
  case "operation.started":
@@ -135,7 +123,7 @@ export class FileSchedulerStoreAdapter {
135
123
  outcome = this.validateCanonicalTurnObservation(input, now);
136
124
  break;
137
125
  case "turn.cancelled": {
138
- outcome = this.foldProviderTurnBoundary(input, adapterId, "cancelled", input.payload.summary ?? "Provider Turn was cancelled.", now);
126
+ outcome = this.foldProviderTurnBoundary(input, adapterId, "cancelled", now);
139
127
  break;
140
128
  }
141
129
  case "conversation.observed":
@@ -220,9 +208,8 @@ export class FileSchedulerStoreAdapter {
220
208
  && run.roleName === input.fence.roleName
221
209
  && run.effective.agentId === input.fence.agentId
222
210
  && this.drivers.requireByAdapterId(run.effective.adapterId).id === input.fence.driverId
223
- && (knownContinuation || (session?.launchId === input.fence.launchId
224
- && session.nativeSessionId === input.fence.nativeSessionId
225
- && input.fence.sessionGenerationId === input.fence.launchId))
211
+ && (knownContinuation || (session?.runtimeGenerationId === input.fence.runtimeGenerationId
212
+ && session.nativeSessionId === input.fence.nativeSessionId))
226
213
  && runtimeReceiptBelongsToTurn(store, input);
227
214
  if (!valid) {
228
215
  recordCanonicalObservationObsolete(store, input, "runtime-fence-not-current", now);
@@ -231,25 +218,42 @@ export class FileSchedulerStoreAdapter {
231
218
  return "applied";
232
219
  });
233
220
  }
234
- foldProviderTurnBoundary(input, adapterId, providerStatus, summary, now) {
221
+ foldProviderTurnBoundary(input, adapterId, providerStatus, now) {
222
+ const outcome = providerStatus === "completed"
223
+ ? input.payload.resultTransportDiagnostic === undefined
224
+ ? transportAgentResult(input.payload.output)
225
+ : {
226
+ status: "failed",
227
+ diagnostic: input.payload.resultTransportDiagnostic,
228
+ failureReason: "missing-result"
229
+ }
230
+ : {
231
+ status: "failed",
232
+ diagnostic: providerStatus === "cancelled"
233
+ ? "Provider cancelled the Agent Turn."
234
+ : `Provider Agent Turn failed: ${input.payload.failure?.error.message ?? "unknown provider failure"}`,
235
+ failureReason: providerStatus === "cancelled"
236
+ ? "cancelled"
237
+ : "runtime-failed"
238
+ };
235
239
  const completed = {
236
240
  taskId: input.fence.taskId,
237
241
  roleName: input.fence.roleName,
238
242
  agentId: input.fence.agentId,
239
243
  adapterId,
240
- launchId: input.fence.launchId,
244
+ runtimeGenerationId: input.fence.runtimeGenerationId,
241
245
  nativeSessionId: input.fence.nativeSessionId,
242
246
  nativeTurnId: input.fence.nativeTurnId,
243
247
  attemptId: input.fence.receiptId,
244
248
  turnId: input.fence.turnId,
245
249
  ...(input.payload.input === undefined ? {} : { input: input.payload.input }),
246
- summary,
247
- providerStatus
250
+ providerStatus,
251
+ outcome
248
252
  };
249
- const classification = this.classifyRuntimeTurnCompleted(completed);
253
+ const classification = this.classifyRuntimeTurnTerminal(completed);
250
254
  if (classification !== "apply")
251
255
  return classification;
252
- const result = this.observeRuntimeTurnCompleted(completed, now);
256
+ const result = this.observeRuntimeTurnTerminal(completed, now);
253
257
  return result.disposition === "obsolete" ? "obsolete" : "applied";
254
258
  }
255
259
  validateCanonicalSessionObservation(input, now) {
@@ -261,16 +265,15 @@ export class FileSchedulerStoreAdapter {
261
265
  || run.roleName !== input.fence.roleName
262
266
  || run.effective.agentId !== input.fence.agentId
263
267
  || this.drivers.requireByAdapterId(run.effective.adapterId).id !== input.fence.driverId
264
- || session?.launchId !== input.fence.launchId
265
- || session.nativeSessionId !== input.fence.nativeSessionId
266
- || input.fence.sessionGenerationId !== input.fence.launchId) {
268
+ || session?.runtimeGenerationId !== input.fence.runtimeGenerationId
269
+ || session.nativeSessionId !== input.fence.nativeSessionId) {
267
270
  recordCanonicalObservationObsolete(store, input, "runtime-session-not-current", now);
268
271
  return "obsolete";
269
272
  }
270
273
  const status = "ended";
271
274
  let updatedSessions = updateRoleAgentSessionStatus(sessions, input.fence.agentId, status, now, input.kind === "session.failed" ? "failed" : "stopped");
272
275
  if (updatedSessions.providerBinding !== null) {
273
- const activationId = input.fence.activationId ?? input.fence.launchId;
276
+ const activationId = input.fence.activationId ?? input.fence.runtimeGenerationId;
274
277
  updatedSessions = updateTaskRoleProviderRuntime(updatedSessions, endProviderActivation(updatedSessions.providerBinding, activationId, {
275
278
  status: input.kind === "session.failed" ? "failed" : "ended",
276
279
  endedAt: input.observedAt ?? input.receivedAt,
@@ -283,14 +286,14 @@ export class FileSchedulerStoreAdapter {
283
286
  || continuation.identity.conversationId
284
287
  !== (input.fence.conversationId ?? input.fence.nativeSessionId)
285
288
  || continuation.identity.activationId
286
- !== (input.fence.activationId ?? input.fence.launchId)
289
+ !== (input.fence.activationId ?? input.fence.runtimeGenerationId)
287
290
  || continuation.execution === "quiescent"
288
291
  || continuation.attachment === "detached")
289
292
  continue;
290
293
  const key = providerContinuationKey(continuation.identity);
291
294
  const identityDigest = createHash("sha256").update(key).digest("hex");
292
295
  const detached = createRuntimeObservation({
293
- schemaVersion: 2,
296
+ schemaVersion: 4,
294
297
  eventId: `derived-continuation-detached:${identityDigest}`,
295
298
  semanticKey: `continuation-detached:${identityDigest}`,
296
299
  kind: "continuation.started",
@@ -319,17 +322,9 @@ export class FileSchedulerStoreAdapter {
319
322
  }
320
323
  });
321
324
  const eventId = store.nextEventId(input.fence.taskId);
322
- store.saveEvent(input.fence.taskId, createTaskEvent(eventId, input.fence.taskId, RUNTIME_OBSERVATION_TASK_EVENT, runtimeObservationTaskEventPayload(detached), now));
323
- enqueueWork(store, {
324
- kind: "role",
325
- taskId: input.fence.taskId,
326
- roleName: "leader"
327
- }, "provider-continuation-detached", now, [
328
- { type: "event", taskId: input.fence.taskId, id: eventId }
329
- ], {
330
- source: input.fence.driverId,
331
- dedupeKey: detached.semanticKey
332
- });
325
+ const detachedEvent = createTaskEvent(eventId, input.fence.taskId, RUNTIME_OBSERVATION_TASK_EVENT, runtimeObservationTaskEventPayload(detached), now);
326
+ store.saveEvent(input.fence.taskId, detachedEvent);
327
+ routeContinuationResult(store, detached, detachedEvent, "provider-continuation-detached", now);
333
328
  }
334
329
  const active = store.getActiveTurn(input.fence.taskId, input.fence.roleName);
335
330
  const role = store.getRole(input.fence.taskId, input.fence.roleName);
@@ -392,7 +387,7 @@ export class FileSchedulerStoreAdapter {
392
387
  .update(`${input.semanticKey}\u0000${identityKey}`)
393
388
  .digest("hex");
394
389
  const settled = createRuntimeObservation({
395
- schemaVersion: 2,
390
+ schemaVersion: 4,
396
391
  eventId: `native-snapshot-settled:${digest}`,
397
392
  semanticKey: `native-snapshot-settled:${digest}`,
398
393
  kind: "continuation.settled",
@@ -419,21 +414,16 @@ export class FileSchedulerStoreAdapter {
419
414
  }
420
415
  });
421
416
  const settledEventId = store.nextEventId(taskId);
422
- store.saveEvent(taskId, createTaskEvent(settledEventId, taskId, RUNTIME_OBSERVATION_TASK_EVENT, runtimeObservationTaskEventPayload(settled), now));
423
- enqueueWork(store, { kind: "role", taskId, roleName: "leader" }, "provider-continuation-settled", now, [{ type: "event", taskId, id: settledEventId }], {
424
- source: input.fence.driverId,
425
- dedupeKey: settled.semanticKey
426
- });
417
+ const settledEvent = createTaskEvent(settledEventId, taskId, RUNTIME_OBSERVATION_TASK_EVENT, runtimeObservationTaskEventPayload(settled), now);
418
+ store.saveEvent(taskId, settledEvent);
419
+ routeContinuationResult(store, settled, settledEvent, "provider-continuation-settled", now);
427
420
  }
428
421
  }
429
422
  if (input.kind === "continuation.reported"
430
423
  || input.kind === "continuation.settled") {
431
- enqueueWork(store, { kind: "role", taskId, roleName: "leader" }, input.kind === "continuation.reported"
424
+ routeContinuationResult(store, input, observationEvent, input.kind === "continuation.reported"
432
425
  ? "provider-continuation-report"
433
- : "provider-continuation-settled", now, [{ type: "event", taskId, id: observationEventId }], {
434
- source: input.fence.driverId,
435
- dedupeKey: input.semanticKey
436
- });
426
+ : "provider-continuation-settled", now);
437
427
  }
438
428
  if ((input.kind === "goal.cleared"
439
429
  || (input.kind === "goal.updated" && input.payload.goalStatus !== "active"))
@@ -456,7 +446,7 @@ export class FileSchedulerStoreAdapter {
456
446
  agentId: input.fence.agentId,
457
447
  adapterId: run?.effective.adapterId ?? "unknown",
458
448
  driverId: input.fence.driverId,
459
- launchId: input.fence.launchId,
449
+ runtimeGenerationId: input.fence.runtimeGenerationId,
460
450
  nativeSessionId: input.fence.nativeSessionId ?? "",
461
451
  nativeTurnId: input.fence.nativeTurnId ?? "",
462
452
  source: error.source,
@@ -823,7 +813,7 @@ export class FileSchedulerStoreAdapter {
823
813
  || binding === null || binding === undefined
824
814
  || (input.turnId !== undefined
825
815
  && (active?.id !== input.turnId || active.effective.agentId !== input.agentId))
826
- || session?.launchId !== input.launchId
816
+ || session?.runtimeGenerationId !== input.runtimeGenerationId
827
817
  || session.nativeSessionId !== input.nativeSessionId
828
818
  || currentProviderConversation(binding).conversationId !== input.nativeSessionId
829
819
  || binding.authority.owner !== input.authorityOwner
@@ -892,7 +882,7 @@ export class FileSchedulerStoreAdapter {
892
882
  agentId: run?.effective.agentId ?? sessions.activeAgentId,
893
883
  adapterId: run?.effective.adapterId ?? session?.adapterId ?? "unknown",
894
884
  driverId: driver?.id ?? "unknown",
895
- launchId: session?.launchId ?? "",
885
+ runtimeGenerationId: session?.runtimeGenerationId ?? "",
896
886
  nativeSessionId: session?.nativeSessionId ?? "",
897
887
  nativeTurnId: "",
898
888
  source: error.source,
@@ -921,7 +911,7 @@ export class FileSchedulerStoreAdapter {
921
911
  const session = sessions?.sessions[input.agentId];
922
912
  const binding = sessions?.providerBinding;
923
913
  if (binding === null || binding === undefined
924
- || session?.launchId !== input.launchId
914
+ || session?.runtimeGenerationId !== input.runtimeGenerationId
925
915
  || session.nativeSessionId !== input.nativeSessionId
926
916
  || currentProviderConversation(binding).conversationId !== input.nativeSessionId)
927
917
  return null;
@@ -996,7 +986,7 @@ export class FileSchedulerStoreAdapter {
996
986
  agentId: run?.effective.agentId ?? session?.agentId ?? "unknown",
997
987
  adapterId: run?.effective.adapterId ?? session?.adapterId ?? "unknown",
998
988
  driverId: driver?.id ?? "unknown",
999
- launchId: session?.launchId ?? "",
989
+ runtimeGenerationId: session?.runtimeGenerationId ?? "",
1000
990
  nativeSessionId: session?.nativeSessionId ?? "",
1001
991
  nativeTurnId: "",
1002
992
  source: error.source,
@@ -1048,9 +1038,9 @@ export class FileSchedulerStoreAdapter {
1048
1038
  * the Leader wakeup. A terminal job without its wakeup enqueued is a lost
1049
1039
  * wakeup, so the two writes commit together or not at all.
1050
1040
  *
1051
- * f6: The wakeup targets the Leader role mailbox (not the Task mailbox)
1052
- * and also queues a pending wakeup so processLeaderWakeups dispatches
1053
- * the Leader Turn with the exact job-finished reason.
1041
+ * f6: The wakeup targets the Leader role mailbox (not the Task mailbox).
1042
+ * That mailbox is also the PendingWakeup authority consumed by
1043
+ * processLeaderWakeups, so the signal must be enqueued exactly once.
1054
1044
  */
1055
1045
  transitionDurableJob(taskId, jobId, transition, now, wakeup) {
1056
1046
  return this.store.transaction((store) => {
@@ -1061,7 +1051,6 @@ export class FileSchedulerStoreAdapter {
1061
1051
  store.saveDurableJob(taskId, next);
1062
1052
  if (wakeup !== undefined) {
1063
1053
  enqueueWork(store, { kind: "role", taskId, roleName: "leader" }, wakeup.reason, now, [...wakeup.refs]);
1064
- queueLeaderWakeup(store, taskId, wakeup.reason, now);
1065
1054
  }
1066
1055
  return next;
1067
1056
  });
@@ -1098,17 +1087,8 @@ export class FileSchedulerStoreAdapter {
1098
1087
  return { status: "claimed", processing: claimed.processing };
1099
1088
  });
1100
1089
  }
1101
- consumeWorkMailbox(target, expected) {
1102
- return this.store.transaction((store) => {
1103
- const mailbox = store.getWorkMailbox(target);
1104
- if (mailbox?.pending === null || mailbox?.pending === undefined
1105
- || mailbox.pending.fromSequence !== expected.fromSequence
1106
- || mailbox.pending.toSequence < expected.toSequence) {
1107
- return false;
1108
- }
1109
- store.saveWorkMailbox(consumePendingBatch(mailbox, expected));
1110
- return true;
1111
- });
1090
+ settleRoleTurnDispatch(input) {
1091
+ return this.store.transaction((store) => (settleRoleTurnDispatchMailbox(store, input, input.expected)));
1112
1092
  }
1113
1093
  completeWorkMailbox(target, batchId) {
1114
1094
  return this.store.transaction((store) => {
@@ -1195,7 +1175,7 @@ export class FileSchedulerStoreAdapter {
1195
1175
  agentId: candidate.agentId,
1196
1176
  adapterId: candidate.adapterId,
1197
1177
  nativeSessionId: candidate.nativeSessionId,
1198
- ...(candidate.launchId === undefined ? {} : { launchId: candidate.launchId }),
1178
+ ...(candidate.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: candidate.runtimeGenerationId }),
1199
1179
  sessionUpdatedAt: candidate.sessionUpdatedAt
1200
1180
  }];
1201
1181
  });
@@ -1369,12 +1349,12 @@ export class FileSchedulerStoreAdapter {
1369
1349
  && existing.status === "active";
1370
1350
  if (!defersConversationReplacement
1371
1351
  && (existing?.nativeSessionId !== input.session.nativeSessionId
1372
- || (input.launchId !== undefined && existing.launchId !== input.launchId)
1352
+ || (input.runtimeGenerationId !== undefined && existing.runtimeGenerationId !== input.runtimeGenerationId)
1373
1353
  || existing.status !== "active")) {
1374
1354
  saveTaskSession(store, role, {
1375
1355
  ...input.session,
1376
1356
  nativeSessionId: input.session.nativeSessionId
1377
- }, "active", input.now, input.launchId);
1357
+ }, "active", input.now, input.runtimeGenerationId);
1378
1358
  }
1379
1359
  }
1380
1360
  });
@@ -1407,8 +1387,8 @@ export class FileSchedulerStoreAdapter {
1407
1387
  ...(session?.nativeSessionId === undefined
1408
1388
  ? {}
1409
1389
  : { nativeSessionId: session.nativeSessionId }),
1410
- ...(input.launchId === undefined ? {} : { launchId: input.launchId }),
1411
- outcome: { status: "failed", summary, failureReason: input.failureReason }
1390
+ ...(input.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: input.runtimeGenerationId }),
1391
+ outcome: { status: "failed", diagnostic: summary, failureReason: input.failureReason }
1412
1392
  }, input.now);
1413
1393
  if (result.disposition !== "applied" || result.turn === null) {
1414
1394
  return "state-changed";
@@ -1444,7 +1424,7 @@ export class FileSchedulerStoreAdapter {
1444
1424
  }
1445
1425
  return {
1446
1426
  status: "existing",
1447
- launchId: existing.processing.batchId
1427
+ runtimeGenerationId: existing.processing.batchId
1448
1428
  };
1449
1429
  }
1450
1430
  if (hasRuntimeLifecycleWork(existing)) {
@@ -1460,14 +1440,14 @@ export class FileSchedulerStoreAdapter {
1460
1440
  throw new Error("Runtime launch reservation could not be queued.");
1461
1441
  }
1462
1442
  const claimed = claimPending(queued, {
1463
- batchId: input.launchId,
1443
+ batchId: input.runtimeGenerationId,
1464
1444
  owner: RUNTIME_LIFECYCLE_OWNER,
1465
1445
  startedAt: now.toISOString()
1466
1446
  });
1467
1447
  store.saveWorkMailbox(claimed);
1468
1448
  return {
1469
1449
  status: "reserved",
1470
- launchId: input.launchId
1450
+ runtimeGenerationId: input.runtimeGenerationId
1471
1451
  };
1472
1452
  });
1473
1453
  }
@@ -1475,7 +1455,7 @@ export class FileSchedulerStoreAdapter {
1475
1455
  return this.store.transaction((store) => {
1476
1456
  assertCurrent();
1477
1457
  const mailbox = store.getWorkMailbox(runtimeLifecycleTarget(input.owner));
1478
- if (isRuntimeLaunchReservation(mailbox?.processing, input.launchId)) {
1458
+ if (isRuntimeLaunchReservation(mailbox?.processing, input.runtimeGenerationId)) {
1479
1459
  return "reserved";
1480
1460
  }
1481
1461
  if (hasRuntimeCleanupObligation(mailbox)) {
@@ -1488,7 +1468,7 @@ export class FileSchedulerStoreAdapter {
1488
1468
  const sessions = runtimeOwnerSessionSet(store, input.owner);
1489
1469
  const active = sessions?.sessions[sessions.activeAgentId];
1490
1470
  if (active?.status === "active"
1491
- && active.launchId === input.launchId)
1471
+ && active.runtimeGenerationId === input.runtimeGenerationId)
1492
1472
  return "provider-bound";
1493
1473
  throw new Error("Runtime launch reservation no longer matches the launch.");
1494
1474
  });
@@ -1496,7 +1476,7 @@ export class FileSchedulerStoreAdapter {
1496
1476
  recordReservedRuntimeNativeSession(input, assertCurrent, now = new Date()) {
1497
1477
  return this.store.transaction((store) => {
1498
1478
  assertCurrent();
1499
- const mailbox = requireRuntimeLaunchReservation(store, input.owner, input.launchId);
1479
+ const mailbox = requireRuntimeLaunchReservation(store, input.owner, input.runtimeGenerationId);
1500
1480
  if (hasRuntimeCleanupObligation(mailbox)) {
1501
1481
  throw new Error("Runtime cleanup is still pending.");
1502
1482
  }
@@ -1507,7 +1487,7 @@ export class FileSchedulerStoreAdapter {
1507
1487
  agentId: input.agentId,
1508
1488
  adapterId: input.adapterId,
1509
1489
  nativeSessionId: input.nativeSessionId,
1510
- launchId: input.launchId,
1490
+ runtimeGenerationId: input.runtimeGenerationId,
1511
1491
  effective: input.effective
1512
1492
  }, now)
1513
1493
  : recordGlobalRuntimeNativeSession(store, {
@@ -1515,21 +1495,21 @@ export class FileSchedulerStoreAdapter {
1515
1495
  agentId: input.agentId,
1516
1496
  adapterId: input.adapterId,
1517
1497
  nativeSessionId: input.nativeSessionId,
1518
- launchId: input.launchId,
1498
+ runtimeGenerationId: input.runtimeGenerationId,
1519
1499
  effective: input.effective
1520
1500
  }, now);
1521
- saveRuntimeLifecycleMailbox(store, completeProcessing(mailbox, input.launchId));
1501
+ saveRuntimeLifecycleMailbox(store, completeProcessing(mailbox, input.runtimeGenerationId));
1522
1502
  return session;
1523
1503
  });
1524
1504
  }
1525
- completeRuntimeLaunchReservation(owner, launchId, beforeComplete) {
1505
+ completeRuntimeLaunchReservation(owner, runtimeGenerationId, beforeComplete) {
1526
1506
  return this.store.transaction((store) => {
1527
1507
  const target = runtimeLifecycleTarget(owner);
1528
1508
  const mailbox = store.getWorkMailbox(target);
1529
- if (!isRuntimeLaunchReservation(mailbox?.processing, launchId))
1509
+ if (!isRuntimeLaunchReservation(mailbox?.processing, runtimeGenerationId))
1530
1510
  return false;
1531
1511
  beforeComplete?.();
1532
- saveRuntimeLifecycleMailbox(store, completeProcessing(mailbox, launchId));
1512
+ saveRuntimeLifecycleMailbox(store, completeProcessing(mailbox, runtimeGenerationId));
1533
1513
  return true;
1534
1514
  });
1535
1515
  }
@@ -1545,13 +1525,13 @@ export class FileSchedulerStoreAdapter {
1545
1525
  return this.store.transaction((store) => {
1546
1526
  const target = runtimeLifecycleTarget(input.owner);
1547
1527
  const mailbox = store.getWorkMailbox(target);
1548
- if (isRuntimeLaunchReservation(mailbox?.processing, input.launchId)) {
1528
+ if (isRuntimeLaunchReservation(mailbox?.processing, input.runtimeGenerationId)) {
1549
1529
  const sessions = runtimeOwnerSessionSet(store, input.owner);
1550
1530
  const active = sessions?.sessions[sessions.activeAgentId];
1551
1531
  if (runtimeSessionMatchesSettledLaunch(active, input)) {
1552
1532
  endRuntimeOwnerSession(store, input.owner, now);
1553
1533
  }
1554
- saveRuntimeLifecycleMailbox(store, completeProcessing(mailbox, input.launchId));
1534
+ saveRuntimeLifecycleMailbox(store, completeProcessing(mailbox, input.runtimeGenerationId));
1555
1535
  return true;
1556
1536
  }
1557
1537
  if (hasRuntimeLifecycleWork(mailbox))
@@ -1577,7 +1557,7 @@ export class FileSchedulerStoreAdapter {
1577
1557
  * retained as an intermediate child wait or recorded as a ready boundary
1578
1558
  * for later mailbox input. It never performs tmux, workspace, or Controller I/O.
1579
1559
  */
1580
- classifyRuntimeTurnCompleted(input) {
1560
+ classifyRuntimeTurnTerminal(input) {
1581
1561
  const task = this.store.getTask(input.taskId);
1582
1562
  const role = this.store.getRole(input.taskId, input.roleName);
1583
1563
  if (task === null || task.status === "archived" || role === null)
@@ -1590,10 +1570,10 @@ export class FileSchedulerStoreAdapter {
1590
1570
  roleName: input.roleName
1591
1571
  };
1592
1572
  if (existing === undefined
1593
- && !runtimeHookMatchesReservation(this.store, owner, input.launchId))
1573
+ && !runtimeHookMatchesReservation(this.store, owner, input.runtimeGenerationId))
1594
1574
  return "obsolete";
1595
1575
  try {
1596
- const effectiveExisting = nativeTransitionExisting(this.store, owner, existing, input.nativeSessionId, input.launchId, "Runtime turn completion conflicts with the fixed Role session.");
1576
+ const effectiveExisting = nativeTransitionExisting(this.store, owner, existing, input.nativeSessionId, input.runtimeGenerationId, "Runtime turn completion conflicts with the fixed Role session.");
1597
1577
  const effective = taskSessionEffective(this.store, input.taskId, input.roleName, input.agentId, effectiveExisting);
1598
1578
  if (effective.agentId !== input.agentId || effective.adapterId !== input.adapterId) {
1599
1579
  return "obsolete";
@@ -1621,7 +1601,7 @@ export class FileSchedulerStoreAdapter {
1621
1601
  return active === null ? "apply" : "obsolete";
1622
1602
  return active?.id === input.turnId ? "apply" : "obsolete";
1623
1603
  }
1624
- observeRuntimeTurnCompleted(input, now = new Date()) {
1604
+ observeRuntimeTurnTerminal(input, now = new Date()) {
1625
1605
  return this.store.transaction((store) => {
1626
1606
  const task = store.getTask(input.taskId);
1627
1607
  if (task === null)
@@ -1648,13 +1628,13 @@ export class FileSchedulerStoreAdapter {
1648
1628
  roleName: input.roleName
1649
1629
  };
1650
1630
  if (existing === undefined
1651
- && !runtimeHookMatchesReservation(store, owner, input.launchId)) {
1631
+ && !runtimeHookMatchesReservation(store, owner, input.runtimeGenerationId)) {
1652
1632
  throw new Error("Runtime turn completion does not match the launch reservation.");
1653
1633
  }
1654
- const effectiveExisting = nativeTransitionExisting(store, owner, existing, input.nativeSessionId, input.launchId, "Runtime turn completion conflicts with the fixed Role session.");
1634
+ const effectiveExisting = nativeTransitionExisting(store, owner, existing, input.nativeSessionId, input.runtimeGenerationId, "Runtime turn completion conflicts with the fixed Role session.");
1655
1635
  const effective = taskSessionEffective(store, task.id, role.name, input.agentId, effectiveExisting);
1656
1636
  if (effective.agentId !== input.agentId || effective.adapterId !== input.adapterId) {
1657
- throw new Error("Runtime turn completion does not match the effective launch identity.");
1637
+ throw new Error("Runtime turn completion does not match the effective runtime generation identity.");
1658
1638
  }
1659
1639
  if (effectiveExisting !== undefined
1660
1640
  && hasRecentTurnId(effectiveExisting.recentCompletedTurnIds, canonicalTurnId)) {
@@ -1679,7 +1659,7 @@ export class FileSchedulerStoreAdapter {
1679
1659
  agentId: input.agentId,
1680
1660
  adapterId: input.adapterId,
1681
1661
  nativeSessionId: input.nativeSessionId,
1682
- ...(input.launchId === undefined ? {} : { launchId: input.launchId }),
1662
+ ...(input.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: input.runtimeGenerationId }),
1683
1663
  policy: "fixed",
1684
1664
  status: sessionStatus,
1685
1665
  ...(effectiveExisting?.endReason === undefined
@@ -1687,46 +1667,44 @@ export class FileSchedulerStoreAdapter {
1687
1667
  : { endReason: effectiveExisting.endReason }),
1688
1668
  effective
1689
1669
  }, now);
1690
- sessions = settleStructuredProviderTurn(sessions, canonicalTurnId, input.providerStatus ?? "completed", now);
1670
+ sessions = settleStructuredProviderTurn(sessions, canonicalTurnId, input.providerStatus, now);
1691
1671
  sessions = recordTaskRoleTurnBoundary(sessions, {
1692
1672
  agentId: input.agentId,
1693
1673
  nativeSessionId: input.nativeSessionId,
1694
1674
  turnId: canonicalTurnId
1695
1675
  }, now);
1696
1676
  store.saveTaskRoleSessionSet(sessions);
1697
- completeRuntimeHookReservation(store, owner, input.launchId);
1677
+ completeRuntimeHookReservation(store, owner, input.runtimeGenerationId);
1698
1678
  let terminalTurn;
1699
1679
  if (recordedProviderTurn && observedTurn?.status === "active") {
1700
1680
  const binding = sessions.providerBinding;
1701
1681
  const conversation = currentProviderConversation(binding);
1702
- const activation = binding.activations.find((entry) => (entry.activationId === input.launchId
1682
+ const activation = binding.activations.find((entry) => (entry.activationId === input.runtimeGenerationId
1703
1683
  || (entry.conversationId === conversation.conversationId && entry.status === "active")));
1704
1684
  if (activation === undefined) {
1705
1685
  throw new Error("Provider Turn result has no matching Activation.");
1706
1686
  }
1707
- const providerStatus = input.providerStatus ?? "completed";
1708
- let reviewResult;
1709
- if (observedTurn.purpose === "review" || observedTurn.executionGroupId !== undefined) {
1710
- try {
1711
- const parsed = parseReviewResultReport(input.summary);
1712
- reviewResult = {
1713
- report: parsed.report,
1714
- checks: parsed.checks,
1715
- ...(parsed.findings === undefined ? {} : { findings: parsed.findings }),
1716
- ...(parsed.evidence === undefined ? {} : { evidence: parsed.evidence }),
1717
- ...(parsed.evidenceCommit === undefined
1718
- ? {}
1719
- : { evidenceCommit: parsed.evidenceCommit }),
1720
- ...(parsed.deltaDisposition === undefined
1721
- ? {}
1722
- : { deltaDisposition: parsed.deltaDisposition }),
1723
- ...(parsed.deltaReasoning === undefined
1724
- ? {}
1725
- : { deltaReasoning: parsed.deltaReasoning })
1726
- };
1687
+ const providerStatus = input.providerStatus;
1688
+ let systemEvidence;
1689
+ let workspaceFailure;
1690
+ if ((observedTurn.purpose === "execution" || observedTurn.purpose === "review")
1691
+ && observedTurn.executionGroupId !== undefined
1692
+ && observedTurn.executionLaneId !== undefined
1693
+ && observedTurn.workspace !== undefined
1694
+ && input.outcome.status === "completed") {
1695
+ const gitSnapshot = this.snapshotExecutionLaneWorkspace(store, observedTurn.workspace);
1696
+ if (gitSnapshot.status === "captured") {
1697
+ systemEvidence = { workspaceSnapshot: gitSnapshot.snapshot };
1727
1698
  }
1728
- catch {
1729
- reviewResult = { report: input.summary, checks: [] };
1699
+ else {
1700
+ workspaceFailure = {
1701
+ failureReason: gitSnapshot.cause === "workspace-dirty"
1702
+ ? "workspace-dirty"
1703
+ : gitSnapshot.cause === "branch-mismatch"
1704
+ ? "workspace-branch-mismatch"
1705
+ : "workspace-unavailable",
1706
+ diagnostic: gitSnapshot.diagnostic
1707
+ };
1730
1708
  }
1731
1709
  }
1732
1710
  const terminalized = terminalizeExactTaskTurn(store, {
@@ -1735,17 +1713,9 @@ export class FileSchedulerStoreAdapter {
1735
1713
  agentId: input.agentId,
1736
1714
  turnId: observedTurn.id,
1737
1715
  nativeSessionId: input.nativeSessionId,
1738
- ...(input.launchId === undefined ? {} : { launchId: input.launchId }),
1716
+ ...(input.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: input.runtimeGenerationId }),
1739
1717
  outcome: {
1740
- status: providerStatus === "completed" ? "completed" : "failed",
1741
- summary: input.summary,
1742
- ...(providerStatus === "completed"
1743
- ? {}
1744
- : {
1745
- failureReason: providerStatus === "cancelled"
1746
- ? "cancelled"
1747
- : "runtime-failed"
1748
- }),
1718
+ ...input.outcome,
1749
1719
  provider: {
1750
1720
  providerNamespace: binding.providerNamespace,
1751
1721
  accountScope: binding.accountScope,
@@ -1755,7 +1725,8 @@ export class FileSchedulerStoreAdapter {
1755
1725
  status: providerStatus
1756
1726
  }
1757
1727
  },
1758
- ...(reviewResult === undefined ? {} : { reviewResult })
1728
+ ...(systemEvidence === undefined ? {} : { systemEvidence }),
1729
+ ...(workspaceFailure === undefined ? {} : { workspaceFailure })
1759
1730
  }, now);
1760
1731
  if (terminalized.disposition !== "applied" || terminalized.turn === null) {
1761
1732
  throw new Error(`Provider Turn terminal could not complete its exact Turn: ${terminalized.reason ?? "obsolete"}.`);
@@ -1765,12 +1736,11 @@ export class FileSchedulerStoreAdapter {
1765
1736
  turnId: terminalTurn.id,
1766
1737
  roleName: terminalTurn.roleName,
1767
1738
  providerTurnId: canonicalTurnId,
1768
- providerStatus,
1769
- summary: input.summary
1739
+ providerStatus
1770
1740
  }, now);
1771
1741
  store.saveEvent(input.taskId, event);
1772
1742
  if (terminalTurn.roleName !== "leader"
1773
- && (providerStatus !== "completed" || !providerGoalContinues(binding.goal))) {
1743
+ && (terminalTurn.status !== "completed" || !providerGoalContinues(binding.goal))) {
1774
1744
  routeRoleEvent(store, event, terminalTurn.roleName, terminalTurn.purpose === "review" ? "review-result" : "role-turn-result", now);
1775
1745
  }
1776
1746
  }
@@ -1787,7 +1757,7 @@ export class FileSchedulerStoreAdapter {
1787
1757
  input.taskId,
1788
1758
  input.roleName,
1789
1759
  input.turnId,
1790
- input.launchId ?? "unknown-launch",
1760
+ input.runtimeGenerationId ?? "unknown-runtime-generation",
1791
1761
  String(input.deadStatus ?? "unknown-status")
1792
1762
  ].join("\0");
1793
1763
  const observationId = `tmux-host-exit-${createHash("sha256").update(identity).digest("hex")}`;
@@ -1797,18 +1767,18 @@ export class FileSchedulerStoreAdapter {
1797
1767
  return;
1798
1768
  const stopReceipt = [...events].reverse().find((event) => (event.type === "runtime.session-termination"
1799
1769
  && event.payload.roleName === input.roleName
1800
- && (input.launchId === undefined || event.payload.launchId === input.launchId)
1770
+ && (input.runtimeGenerationId === undefined || event.payload.runtimeGenerationId === input.runtimeGenerationId)
1801
1771
  && ["stop-requested", "graceful-stop", "forced-stop", "stop-confirmed"]
1802
1772
  .includes(event.payload.outcome ?? "")));
1803
1773
  const observation = validateRuntimeProcessExitObservation({
1804
- schemaVersion: 1,
1774
+ schemaVersion: 2,
1805
1775
  observationId,
1806
1776
  hostSequence: 1,
1807
- hostInstanceId: `tmux-${input.launchId ?? input.roleName}`,
1777
+ hostInstanceId: `tmux-${input.runtimeGenerationId ?? input.roleName}`,
1808
1778
  taskId: input.taskId,
1809
1779
  roleName: input.roleName,
1810
1780
  turnId: input.turnId,
1811
- launchId: input.launchId ?? `unknown-${input.turnId}`,
1781
+ runtimeGenerationId: input.runtimeGenerationId ?? `unknown-${input.turnId}`,
1812
1782
  ...(input.nativeSessionId === undefined
1813
1783
  ? {}
1814
1784
  : { nativeSessionId: input.nativeSessionId }),
@@ -1822,7 +1792,7 @@ export class FileSchedulerStoreAdapter {
1822
1792
  observationId,
1823
1793
  processKind: "agent-host",
1824
1794
  roleName: input.roleName,
1825
- launchId: observation.launchId,
1795
+ runtimeGenerationId: observation.runtimeGenerationId,
1826
1796
  observedAt: observation.observedAt,
1827
1797
  classification,
1828
1798
  observation: JSON.stringify(observation)
@@ -1954,11 +1924,7 @@ export class FileSchedulerStoreAdapter {
1954
1924
  const existingNativeSessionId = existingSession?.nativeSessionId;
1955
1925
  const replacingNativeSession = existingNativeSessionId !== undefined
1956
1926
  && existingNativeSessionId !== decision.outcome.nativeSessionId;
1957
- const replacementBasis = replacingNativeSession
1958
- && run.mode === "new"
1959
- && existingSession?.status === "ended"
1960
- ? "terminal-session"
1961
- : null;
1927
+ const replacementBasis = terminalSessionReplacementBasis(sessions, input, run) ?? null;
1962
1928
  if (replacingNativeSession && replacementBasis === null) {
1963
1929
  recordCanonicalObservationObsolete(store, input, "session-replacement-not-terminal", now);
1964
1930
  return "obsolete";
@@ -1967,7 +1933,7 @@ export class FileSchedulerStoreAdapter {
1967
1933
  agentId: input.fence.agentId,
1968
1934
  adapterId,
1969
1935
  nativeSessionId: decision.outcome.nativeSessionId,
1970
- launchId: input.fence.launchId,
1936
+ runtimeGenerationId: input.fence.runtimeGenerationId,
1971
1937
  policy: "fixed",
1972
1938
  status: "active",
1973
1939
  effective: run.effective
@@ -1975,15 +1941,30 @@ export class FileSchedulerStoreAdapter {
1975
1941
  const bound = replacementBasis === null
1976
1942
  ? recordRoleAgentSession(sessions, sessionInput, now)
1977
1943
  : replaceTaskRoleAgentSession(sessions, sessionInput, now);
1978
- const withProvider = bindOrSupersedeProviderRuntime(bound, input, now, replacementBasis ?? undefined);
1944
+ const withProvider = bindOrSupersedeProviderRuntime(bound, input, now, replacementBasis
1945
+ ?? terminalProviderReplacementBasis(bound, input, run.mode));
1979
1946
  store.saveTaskRoleSessionSet(withProvider);
1980
- completeRuntimeHookReservation(store, { scope: "task", taskId, roleName: input.fence.roleName }, input.fence.launchId);
1947
+ completeRuntimeHookReservation(store, { scope: "task", taskId, roleName: input.fence.roleName }, input.fence.runtimeGenerationId);
1981
1948
  }
1982
1949
  const current = store.getTaskRoleSessionSet(input.fence.taskId, input.fence.roleName);
1983
1950
  const currentSession = current?.sessions[input.fence.agentId];
1984
1951
  if (current !== null && current !== undefined
1985
1952
  && currentSession?.nativeSessionId === input.fence.nativeSessionId) {
1986
- store.saveTaskRoleSessionSet(bindOrSupersedeProviderRuntime(current, input, now));
1953
+ const run = input.fence.turnId === undefined
1954
+ ? null
1955
+ : store.getTurn(input.fence.taskId, input.fence.turnId);
1956
+ const replacementBasis = run === null
1957
+ ? undefined
1958
+ : terminalSessionReplacementBasis(current, input, run)
1959
+ ?? terminalProviderReplacementBasis(current, input, run.mode);
1960
+ if (current.providerBinding !== null
1961
+ && currentProviderConversation(current.providerBinding).conversationId
1962
+ !== (input.fence.conversationId ?? input.fence.nativeSessionId)
1963
+ && replacementBasis === undefined) {
1964
+ recordCanonicalObservationObsolete(store, input, "session-replacement-not-terminal", now);
1965
+ return "obsolete";
1966
+ }
1967
+ store.saveTaskRoleSessionSet(bindOrSupersedeProviderRuntime(current, input, now, replacementBasis));
1987
1968
  }
1988
1969
  return "applied";
1989
1970
  }
@@ -2005,7 +1986,7 @@ export class FileSchedulerStoreAdapter {
2005
1986
  const active = store.getActiveTurn(taskId, input.fence.roleName);
2006
1987
  if (sessions === null || sessions === undefined
2007
1988
  || binding === null || binding === undefined
2008
- || session?.launchId !== input.fence.launchId
1989
+ || session?.runtimeGenerationId !== input.fence.runtimeGenerationId
2009
1990
  || session.nativeSessionId !== input.fence.nativeSessionId
2010
1991
  || currentProviderConversation(binding).conversationId !== input.fence.nativeSessionId) {
2011
1992
  recordCanonicalObservationObsolete(store, input, "direct-turn-session-mismatch", now);
@@ -2058,7 +2039,7 @@ export class FileSchedulerStoreAdapter {
2058
2039
  && binding !== null && binding !== undefined
2059
2040
  && binding.turn?.turnId === undefined
2060
2041
  && binding.turn?.attemptId === ordinaryAttemptId) {
2061
- if (session?.launchId !== input.fence.launchId
2042
+ if (session?.runtimeGenerationId !== input.fence.runtimeGenerationId
2062
2043
  || session.nativeSessionId !== input.fence.nativeSessionId) {
2063
2044
  recordCanonicalObservationObsolete(store, input, "ordinary-turn-session-mismatch", now);
2064
2045
  return "obsolete";
@@ -2076,7 +2057,7 @@ export class FileSchedulerStoreAdapter {
2076
2057
  || active.status !== "active"
2077
2058
  || sessions === null
2078
2059
  || sessions === undefined
2079
- || session?.launchId !== input.fence.launchId
2060
+ || session?.runtimeGenerationId !== input.fence.runtimeGenerationId
2080
2061
  || session.nativeSessionId !== input.fence.nativeSessionId) {
2081
2062
  recordCanonicalObservationObsolete(store, input, "continuation-fence-mismatch", now);
2082
2063
  return "obsolete";
@@ -2086,7 +2067,7 @@ export class FileSchedulerStoreAdapter {
2086
2067
  attemptId: input.fence.receiptId,
2087
2068
  turnId: active.id,
2088
2069
  conversationId: input.fence.conversationId ?? input.fence.nativeSessionId,
2089
- activationId: input.fence.activationId ?? input.fence.launchId,
2070
+ activationId: input.fence.activationId ?? input.fence.runtimeGenerationId,
2090
2071
  ...(input.fence.nativeTurnId === undefined
2091
2072
  ? {}
2092
2073
  : { nativeTurnId: input.fence.nativeTurnId })
@@ -2168,15 +2149,15 @@ export class FileSchedulerStoreAdapter {
2168
2149
  return null;
2169
2150
  const owner = { scope: "task", taskId: fence.taskId, roleName: fence.roleName };
2170
2151
  const freshConversationLaunch = run.mode === "new"
2171
- && runtimeHookMatchesReservation(store, owner, fence.launchId);
2152
+ && runtimeHookMatchesReservation(store, owner, fence.runtimeGenerationId);
2172
2153
  const boundNativeSessionId = freshConversationLaunch
2173
2154
  ? undefined
2174
2155
  : session?.nativeSessionId;
2175
- const launchId = freshConversationLaunch
2176
- ? fence.launchId
2177
- : session?.launchId
2178
- ?? (runtimeHookMatchesReservation(store, owner, fence.launchId) ? fence.launchId : undefined);
2179
- if (launchId === undefined)
2156
+ const runtimeGenerationId = freshConversationLaunch
2157
+ ? fence.runtimeGenerationId
2158
+ : session?.runtimeGenerationId
2159
+ ?? (runtimeHookMatchesReservation(store, owner, fence.runtimeGenerationId) ? fence.runtimeGenerationId : undefined);
2160
+ if (runtimeGenerationId === undefined)
2180
2161
  return null;
2181
2162
  const providerTurn = sessionSet?.providerBinding?.turn;
2182
2163
  const managedTurnMatches = managedProviderTurnId(providerTurn) === run.id;
@@ -2186,7 +2167,7 @@ export class FileSchedulerStoreAdapter {
2186
2167
  agentId: run.effective.agentId,
2187
2168
  adapterId: run.effective.adapterId,
2188
2169
  turnId: run.id,
2189
- launchId,
2170
+ runtimeGenerationId,
2190
2171
  receiptId: managedTurnMatches && providerTurn !== null && providerTurn !== undefined
2191
2172
  ? providerTurn.attemptId
2192
2173
  : formatTurnReceiptId(run.taskId, run.id),
@@ -2200,7 +2181,7 @@ export class FileSchedulerStoreAdapter {
2200
2181
  && observation.fence.roleName === fence.roleName
2201
2182
  && observation.fence.agentId === run.effective.agentId
2202
2183
  && observation.fence.driverId === driverId
2203
- && observation.fence.launchId === launchId
2184
+ && observation.fence.runtimeGenerationId === runtimeGenerationId
2204
2185
  && observation.fence.nativeSessionId === (boundNativeSessionId ?? fence.nativeSessionId)
2205
2186
  ? [observation]
2206
2187
  : [];
@@ -2227,7 +2208,7 @@ export class FileSchedulerStoreAdapter {
2227
2208
  recordGlobalRuntimeNativeSession(input, now = new Date()) {
2228
2209
  return this.store.transaction((store) => (recordGlobalRuntimeNativeSession(store, input, now)));
2229
2210
  }
2230
- observeGlobalRuntimeTurnCompleted(input, now = new Date()) {
2211
+ observeGlobalRuntimeTurnTerminal(input, now = new Date()) {
2231
2212
  return this.store.transaction((store) => {
2232
2213
  const role = store.getGlobalRole(input.roleName);
2233
2214
  if (role === null)
@@ -2240,23 +2221,25 @@ export class FileSchedulerStoreAdapter {
2240
2221
  roleName: input.roleName
2241
2222
  };
2242
2223
  if (existing === undefined
2243
- && !runtimeHookMatchesReservation(store, owner, input.launchId)) {
2224
+ && !runtimeHookMatchesReservation(store, owner, input.runtimeGenerationId)) {
2244
2225
  throw new Error("Runtime turn completion does not match the global launch reservation.");
2245
2226
  }
2246
2227
  const nativeSessionId = globalCompletionNativeSessionId(existing, input);
2247
- const effectiveExisting = nativeTransitionExisting(store, owner, existing, nativeSessionId, input.launchId, "Runtime turn completion conflicts with the fixed global Role session.");
2228
+ const effectiveExisting = nativeTransitionExisting(store, owner, existing, nativeSessionId, input.runtimeGenerationId, "Runtime turn completion conflicts with the fixed global Role session.");
2248
2229
  const effective = globalSessionEffective(role, effectiveExisting);
2249
2230
  if (effective.agentId !== input.agentId || effective.adapterId !== input.adapterId) {
2250
- throw new Error("Runtime turn completion does not match the effective global launch identity.");
2231
+ throw new Error("Runtime turn completion does not match the effective global runtime generation identity.");
2251
2232
  }
2252
2233
  const completedStatus = effectiveExisting?.status ?? "active";
2253
2234
  current = recordRoleAgentSession(current, {
2254
2235
  agentId: input.agentId,
2255
2236
  adapterId: input.adapterId,
2256
2237
  nativeSessionId,
2257
- ...(input.launchId === undefined ? {} : { launchId: input.launchId }),
2238
+ ...(input.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: input.runtimeGenerationId }),
2258
2239
  title: effectiveExisting?.title ?? input.title,
2259
- preview: effectiveExisting?.preview ?? (input.summary === undefined ? undefined : sessionPreview(input.summary)),
2240
+ preview: effectiveExisting?.preview ?? sessionPreview(input.outcome.status === "completed"
2241
+ ? input.outcome.output
2242
+ : input.outcome.diagnostic),
2260
2243
  policy: "fixed",
2261
2244
  status: completedStatus,
2262
2245
  ...(effectiveExisting?.endReason === undefined
@@ -2266,7 +2249,7 @@ export class FileSchedulerStoreAdapter {
2266
2249
  }, now);
2267
2250
  current = rememberRoleAgentCompletedTurn(current, input.agentId, nativeSessionId, input.nativeTurnId, now);
2268
2251
  store.saveGlobalRoleSessionSet(current);
2269
- completeRuntimeHookReservation(store, owner, input.launchId);
2252
+ completeRuntimeHookReservation(store, owner, input.runtimeGenerationId);
2270
2253
  if (input.roleName === SYSTEM_OPERATOR_ROLE
2271
2254
  && input.adapterId === "codex"
2272
2255
  && completedStatus === "active") {
@@ -2281,7 +2264,7 @@ export class FileSchedulerStoreAdapter {
2281
2264
  return current.sessions[input.agentId];
2282
2265
  });
2283
2266
  }
2284
- classifyGlobalRuntimeTurnCompleted(input) {
2267
+ classifyGlobalRuntimeTurnTerminal(input) {
2285
2268
  const role = this.store.getGlobalRole(input.roleName);
2286
2269
  if (role === null)
2287
2270
  return "obsolete";
@@ -2292,12 +2275,12 @@ export class FileSchedulerStoreAdapter {
2292
2275
  roleName: input.roleName
2293
2276
  };
2294
2277
  if (existing === undefined
2295
- && !runtimeHookMatchesReservation(this.store, owner, input.launchId))
2278
+ && !runtimeHookMatchesReservation(this.store, owner, input.runtimeGenerationId))
2296
2279
  return "obsolete";
2297
2280
  const nativeSessionId = globalCompletionNativeSessionId(existing, input);
2298
2281
  let effectiveExisting;
2299
2282
  try {
2300
- effectiveExisting = nativeTransitionExisting(this.store, owner, existing, nativeSessionId, input.launchId, "Runtime turn completion conflicts with the fixed global Role session.");
2283
+ effectiveExisting = nativeTransitionExisting(this.store, owner, existing, nativeSessionId, input.runtimeGenerationId, "Runtime turn completion conflicts with the fixed global Role session.");
2301
2284
  }
2302
2285
  catch {
2303
2286
  return "obsolete";
@@ -2315,7 +2298,7 @@ function runtimeObservationLifecycleFence(input, adapterId) {
2315
2298
  roleName: input.fence.roleName,
2316
2299
  agentId: input.fence.agentId,
2317
2300
  adapterId,
2318
- launchId: input.fence.launchId,
2301
+ runtimeGenerationId: input.fence.runtimeGenerationId,
2319
2302
  ...(input.fence.turnId === undefined ? {} : { turnId: input.fence.turnId }),
2320
2303
  ...(input.fence.nativeSessionId === undefined
2321
2304
  ? {}
@@ -2323,6 +2306,25 @@ function runtimeObservationLifecycleFence(input, adapterId) {
2323
2306
  ...(input.fence.receiptId === undefined ? {} : { receiptId: input.fence.receiptId })
2324
2307
  };
2325
2308
  }
2309
+ /**
2310
+ * A live parent Turn still owns its native child result. Only after that Turn
2311
+ * is terminal or absent does Yui route the continuation fact to the original
2312
+ * Role's supervisor. Mailbox coalescing remains the downstream batching
2313
+ * mechanism; this guard decides ownership before any wake is enqueued.
2314
+ */
2315
+ function routeContinuationResult(store, observation, event, reason, now) {
2316
+ const taskId = observation.fence.taskId;
2317
+ if (taskId === undefined)
2318
+ return;
2319
+ const parentTurn = observation.fence.turnId === undefined
2320
+ ? null
2321
+ : store.getTurn(taskId, observation.fence.turnId);
2322
+ if (parentTurn?.status === "active")
2323
+ return;
2324
+ if (store.getRole(taskId, observation.fence.roleName) === null)
2325
+ return;
2326
+ routeRoleEvent(store, event, observation.fence.roleName, reason, now);
2327
+ }
2326
2328
  /** Accepts the original Turn receipt or a later mailbox activation receipt. */
2327
2329
  function runtimeReceiptBelongsToTurn(store, input) {
2328
2330
  const taskId = input.fence.taskId;
@@ -2338,7 +2340,7 @@ function runtimeReceiptBelongsToTurn(store, input) {
2338
2340
  && accepted.fence.turnId === turnId
2339
2341
  && accepted.fence.roleName === input.fence.roleName
2340
2342
  && accepted.fence.agentId === input.fence.agentId
2341
- && accepted.fence.launchId === input.fence.launchId
2343
+ && accepted.fence.runtimeGenerationId === input.fence.runtimeGenerationId
2342
2344
  && accepted.fence.nativeSessionId === input.fence.nativeSessionId
2343
2345
  && accepted.fence.receiptId === receiptId;
2344
2346
  });
@@ -2355,8 +2357,8 @@ function recordCanonicalObservationObsolete(store, input, reason, now) {
2355
2357
  roleName: input.fence.roleName,
2356
2358
  agentId: input.fence.agentId,
2357
2359
  ...(input.fence.turnId === undefined ? {} : { turnId: input.fence.turnId }),
2358
- launchId: input.fence.launchId,
2359
- nativeSessionId: input.fence.nativeSessionId ?? input.fence.sessionGenerationId
2360
+ runtimeGenerationId: input.fence.runtimeGenerationId,
2361
+ nativeSessionId: input.fence.nativeSessionId ?? input.fence.runtimeGenerationId
2360
2362
  }, reason, now);
2361
2363
  }
2362
2364
  function recordObsoleteRuntimeEvent(store, input, reason, now) {
@@ -2371,7 +2373,7 @@ function recordObsoleteRuntimeEvent(store, input, reason, now) {
2371
2373
  agentId: input.agentId,
2372
2374
  nativeSessionId: input.nativeSessionId,
2373
2375
  ...(input.turnId === undefined ? {} : { turnId: input.turnId }),
2374
- ...(input.launchId === undefined ? {} : { launchId: input.launchId }),
2376
+ ...(input.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: input.runtimeGenerationId }),
2375
2377
  reason
2376
2378
  }, now));
2377
2379
  }
@@ -2410,20 +2412,20 @@ function sessionPreview(value) {
2410
2412
  ? truncated.slice(0, -1)
2411
2413
  : truncated;
2412
2414
  }
2413
- function requireRuntimeLaunchReservation(store, owner, launchId) {
2415
+ function requireRuntimeLaunchReservation(store, owner, runtimeGenerationId) {
2414
2416
  const mailbox = store.getWorkMailbox(runtimeLifecycleTarget(owner));
2415
- if (!isRuntimeLaunchReservation(mailbox?.processing, launchId)) {
2417
+ if (!isRuntimeLaunchReservation(mailbox?.processing, runtimeGenerationId)) {
2416
2418
  throw new Error("Runtime launch reservation no longer matches the launch.");
2417
2419
  }
2418
2420
  return mailbox;
2419
2421
  }
2420
- function runtimeHookMatchesReservation(store, owner, launchId) {
2422
+ function runtimeHookMatchesReservation(store, owner, runtimeGenerationId) {
2421
2423
  const mailbox = store.getWorkMailbox(runtimeLifecycleTarget(owner));
2422
2424
  if (hasRuntimeCleanupObligation(mailbox))
2423
2425
  return false;
2424
2426
  const processing = mailbox?.processing;
2425
- return launchId !== undefined
2426
- && isRuntimeLaunchReservation(processing, launchId);
2427
+ return runtimeGenerationId !== undefined
2428
+ && isRuntimeLaunchReservation(processing, runtimeGenerationId);
2427
2429
  }
2428
2430
  function preallocatedRuntimeReadyAwaitingProjection(store, observation, drivers) {
2429
2431
  const taskId = observation.fence.taskId;
@@ -2455,17 +2457,17 @@ function preallocatedRuntimeReadyAwaitingProjection(store, observation, drivers)
2455
2457
  taskId,
2456
2458
  roleName: observation.fence.roleName
2457
2459
  }));
2458
- return isRuntimeLaunchReservation(mailbox?.processing, observation.fence.launchId)
2460
+ return isRuntimeLaunchReservation(mailbox?.processing, observation.fence.runtimeGenerationId)
2459
2461
  && !hasRuntimeCleanupObligation(mailbox)
2460
- && nativeSessionId === nativeSessionIdForLaunch(store.rootDirectory(), observation.fence.launchId, observation.fence.agentId, driver.adapterId);
2462
+ && nativeSessionId === nativeSessionIdForLaunch(store.rootDirectory(), observation.fence.runtimeGenerationId, observation.fence.agentId, driver.adapterId);
2461
2463
  }
2462
- function completeRuntimeHookReservation(store, owner, launchId) {
2463
- if (launchId === undefined)
2464
+ function completeRuntimeHookReservation(store, owner, runtimeGenerationId) {
2465
+ if (runtimeGenerationId === undefined)
2464
2466
  return;
2465
2467
  const mailbox = store.getWorkMailbox(runtimeLifecycleTarget(owner));
2466
- if (!isRuntimeLaunchReservation(mailbox?.processing, launchId))
2468
+ if (!isRuntimeLaunchReservation(mailbox?.processing, runtimeGenerationId))
2467
2469
  return;
2468
- saveRuntimeLifecycleMailbox(store, completeProcessing(mailbox, launchId));
2470
+ saveRuntimeLifecycleMailbox(store, completeProcessing(mailbox, runtimeGenerationId));
2469
2471
  }
2470
2472
  function saveRuntimeLifecycleMailbox(store, mailbox) {
2471
2473
  if (!mailboxHasWork(mailbox)) {
@@ -2509,7 +2511,7 @@ function dormantRuntimeCandidateIsCurrent(store, candidate) {
2509
2511
  && active.agentId === candidate.agentId
2510
2512
  && active.adapterId === candidate.adapterId
2511
2513
  && active.nativeSessionId === candidate.nativeSessionId
2512
- && active.launchId === candidate.launchId
2514
+ && active.runtimeGenerationId === candidate.runtimeGenerationId
2513
2515
  && active.updatedAt === candidate.sessionUpdatedAt;
2514
2516
  }
2515
2517
  function endRuntimeOwnerSession(store, owner, now) {
@@ -2593,9 +2595,9 @@ function recordTaskRuntimeNativeSession(store, input, now) {
2593
2595
  const current = store.getRoleSessionSet(input.taskId, input.roleName)
2594
2596
  ?? createRoleSessionSet({ scope: "task", taskId: input.taskId, roleName: input.roleName }, input.agentId, now);
2595
2597
  const existing = current.sessions[input.agentId];
2596
- const effectiveExisting = nativeTransitionExisting(store, { scope: "task", taskId: input.taskId, roleName: input.roleName }, existing, input.nativeSessionId, input.launchId, "Native session registration conflicts with the fixed Role session.");
2598
+ const effectiveExisting = nativeTransitionExisting(store, { scope: "task", taskId: input.taskId, roleName: input.roleName }, existing, input.nativeSessionId, input.runtimeGenerationId, "Native session registration conflicts with the fixed Role session.");
2597
2599
  if (existing?.status === "active"
2598
- && (input.launchId === undefined || existing.launchId === input.launchId))
2600
+ && (input.runtimeGenerationId === undefined || existing.runtimeGenerationId === input.runtimeGenerationId))
2599
2601
  return existing;
2600
2602
  const resolvedEffective = taskSessionEffective(store, input.taskId, input.roleName, input.agentId, effectiveExisting);
2601
2603
  const effective = input.effective === undefined
@@ -2605,13 +2607,13 @@ function recordTaskRuntimeNativeSession(store, input, now) {
2605
2607
  throw new Error("Reserved native Session effective launch changed before persistence.");
2606
2608
  }
2607
2609
  if (effective.agentId !== input.agentId || effective.adapterId !== input.adapterId) {
2608
- throw new Error("Native session registration does not match the effective launch identity.");
2610
+ throw new Error("Native session registration does not match the effective runtime generation identity.");
2609
2611
  }
2610
2612
  const updated = recordRoleAgentSession(current, {
2611
2613
  agentId: input.agentId,
2612
2614
  adapterId: input.adapterId,
2613
2615
  nativeSessionId: input.nativeSessionId,
2614
- ...(input.launchId === undefined ? {} : { launchId: input.launchId }),
2616
+ ...(input.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: input.runtimeGenerationId }),
2615
2617
  policy: "fixed",
2616
2618
  status: "active",
2617
2619
  effective: effectiveExisting?.effective ?? effective
@@ -2626,9 +2628,9 @@ function recordGlobalRuntimeNativeSession(store, input, now) {
2626
2628
  const current = store.getGlobalRoleSessionSet(input.roleName)
2627
2629
  ?? createRoleSessionSet({ scope: "global", roleName: input.roleName }, input.agentId, now);
2628
2630
  const existing = current.sessions[input.agentId];
2629
- const effectiveExisting = nativeTransitionExisting(store, { scope: "global", roleName: input.roleName }, existing, input.nativeSessionId, input.launchId, "Native session registration conflicts with the fixed global Role session.");
2631
+ const effectiveExisting = nativeTransitionExisting(store, { scope: "global", roleName: input.roleName }, existing, input.nativeSessionId, input.runtimeGenerationId, "Native session registration conflicts with the fixed global Role session.");
2630
2632
  if (existing?.status === "active"
2631
- && (input.launchId === undefined || existing.launchId === input.launchId))
2633
+ && (input.runtimeGenerationId === undefined || existing.runtimeGenerationId === input.runtimeGenerationId))
2632
2634
  return existing;
2633
2635
  const resolvedEffective = globalSessionEffective(role, effectiveExisting);
2634
2636
  const effective = input.effective === undefined
@@ -2638,13 +2640,13 @@ function recordGlobalRuntimeNativeSession(store, input, now) {
2638
2640
  throw new Error("Reserved global native Session effective launch changed before persistence.");
2639
2641
  }
2640
2642
  if (effective.agentId !== input.agentId || effective.adapterId !== input.adapterId) {
2641
- throw new Error("Native session registration does not match the effective global launch identity.");
2643
+ throw new Error("Native session registration does not match the effective global runtime generation identity.");
2642
2644
  }
2643
2645
  const updated = recordRoleAgentSession(current, {
2644
2646
  agentId: input.agentId,
2645
2647
  adapterId: input.adapterId,
2646
2648
  nativeSessionId: input.nativeSessionId,
2647
- ...(input.launchId === undefined ? {} : { launchId: input.launchId }),
2649
+ ...(input.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: input.runtimeGenerationId }),
2648
2650
  policy: "fixed",
2649
2651
  status: "active",
2650
2652
  effective
@@ -2652,22 +2654,22 @@ function recordGlobalRuntimeNativeSession(store, input, now) {
2652
2654
  store.saveGlobalRoleSessionSet(updated);
2653
2655
  return updated.sessions[input.agentId];
2654
2656
  }
2655
- function nativeTransitionExisting(store, owner, existing, nativeSessionId, launchId, conflictMessage) {
2657
+ function nativeTransitionExisting(store, owner, existing, nativeSessionId, runtimeGenerationId, conflictMessage) {
2656
2658
  if (existing === undefined || existing.nativeSessionId === nativeSessionId) {
2657
2659
  return existing;
2658
2660
  }
2659
2661
  if (existing.status === "ended"
2660
- && runtimeHookMatchesReservation(store, owner, launchId)) {
2662
+ && runtimeHookMatchesReservation(store, owner, runtimeGenerationId)) {
2661
2663
  return undefined;
2662
2664
  }
2663
2665
  throw new Error(conflictMessage);
2664
2666
  }
2665
2667
  function globalCompletionNativeSessionId(existing, input) {
2666
2668
  // Codex structured event sources can expose different native IDs for the
2667
- // same launch. The launch generation remains the authoritative fence.
2669
+ // same runtime generation. That generation remains the authoritative fence.
2668
2670
  return input.adapterId === "codex"
2669
- && input.launchId !== undefined
2670
- && existing?.launchId === input.launchId
2671
+ && input.runtimeGenerationId !== undefined
2672
+ && existing?.runtimeGenerationId === input.runtimeGenerationId
2671
2673
  && existing.agentId === input.agentId
2672
2674
  && existing.adapterId === input.adapterId
2673
2675
  ? existing.nativeSessionId
@@ -2755,7 +2757,7 @@ function mapSession(session) {
2755
2757
  agentId: session.agentId,
2756
2758
  adapterId: session.adapterId,
2757
2759
  nativeSessionId: session.nativeSessionId,
2758
- ...(session.launchId === undefined ? {} : { launchId: session.launchId }),
2760
+ ...(session.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: session.runtimeGenerationId }),
2759
2761
  ...(session.title === undefined ? {} : { title: session.title }),
2760
2762
  status: session.status,
2761
2763
  ...(session.endReason === undefined ? {} : { endReason: session.endReason }),
@@ -2763,14 +2765,14 @@ function mapSession(session) {
2763
2765
  updatedAt: session.updatedAt
2764
2766
  };
2765
2767
  }
2766
- function saveTaskSession(store, role, session, status, now, launchId) {
2768
+ function saveTaskSession(store, role, session, status, now, runtimeGenerationId) {
2767
2769
  const current = store.getRoleSessionSet(role.taskId, role.name)
2768
2770
  ?? createRoleSessionSet({ scope: "task", taskId: role.taskId, roleName: role.name }, session.agentId, now);
2769
2771
  const updated = recordRoleAgentSession(current, {
2770
2772
  agentId: session.agentId,
2771
2773
  adapterId: session.adapterId,
2772
2774
  nativeSessionId: session.nativeSessionId,
2773
- ...(launchId === undefined ? {} : { launchId }),
2775
+ ...(runtimeGenerationId === undefined ? {} : { runtimeGenerationId }),
2774
2776
  ...(session.title === undefined ? {} : { title: session.title }),
2775
2777
  policy: "fixed",
2776
2778
  status,
@@ -2787,7 +2789,7 @@ function matchesStallSessionFence(current, expected) {
2787
2789
  return current.agentId === expected.agentId
2788
2790
  && current.adapterId === expected.adapterId
2789
2791
  && current.nativeSessionId === expected.nativeSessionId
2790
- && current.launchId === expected.launchId
2792
+ && current.runtimeGenerationId === expected.runtimeGenerationId
2791
2793
  && current.status === expected.status;
2792
2794
  }
2793
2795
  function requireRole(store, taskId, roleName) {
@@ -2814,7 +2816,7 @@ function runtimeObservationTelemetryEntry(input) {
2814
2816
  taskId: input.fence.taskId,
2815
2817
  roleName: input.fence.roleName,
2816
2818
  turnId: input.fence.turnId,
2817
- generation: input.fence.sessionGenerationId,
2819
+ generation: input.fence.runtimeGenerationId,
2818
2820
  progressId: [
2819
2821
  input.kind,
2820
2822
  input.payload.operationId ?? input.payload.activity ?? "state"
@@ -2825,7 +2827,7 @@ function runtimeObservationTelemetryEntry(input) {
2825
2827
  kind: input.kind,
2826
2828
  authority: input.authority,
2827
2829
  driverId: input.fence.driverId,
2828
- launchId: input.fence.launchId,
2830
+ runtimeGenerationId: input.fence.runtimeGenerationId,
2829
2831
  nativeSessionId: input.fence.nativeSessionId ?? "",
2830
2832
  nativeTurnId: input.fence.nativeTurnId ?? "",
2831
2833
  ...(input.payload.operation === undefined
@@ -2934,9 +2936,30 @@ function canonicalStructuredProviderTurnId(sessions, observedTurnId, attemptId)
2934
2936
  ? turn.nativeTurnId
2935
2937
  : observedTurnId;
2936
2938
  }
2939
+ function terminalSessionReplacementBasis(sessions, input, run) {
2940
+ const binding = sessions.providerBinding;
2941
+ const session = sessions.sessions[input.fence.agentId];
2942
+ const incomingConversationId = input.fence.conversationId ?? input.fence.nativeSessionId;
2943
+ if (run.mode !== "new"
2944
+ || binding === null
2945
+ || session === undefined
2946
+ || incomingConversationId === undefined
2947
+ || currentProviderConversation(binding).conversationId === incomingConversationId
2948
+ || currentProviderActivation(binding) !== null
2949
+ || binding.authority.owner !== "none") {
2950
+ return undefined;
2951
+ }
2952
+ // Pre-start persistence may already have replaced the terminal Role Session
2953
+ // before Provider readiness arrives. A matching new Session plus the
2954
+ // quiescent old Provider binding preserves the same terminal-session proof.
2955
+ return session.status === "ended"
2956
+ || session.nativeSessionId === input.fence.nativeSessionId
2957
+ ? "terminal-session"
2958
+ : undefined;
2959
+ }
2937
2960
  function bindOrSupersedeProviderRuntime(sessions, input, now, replacementBasis) {
2938
2961
  const conversationId = input.fence.conversationId ?? input.fence.nativeSessionId;
2939
- const activationId = input.fence.activationId ?? input.fence.launchId;
2962
+ const activationId = input.fence.activationId ?? input.fence.runtimeGenerationId;
2940
2963
  if (sessions.providerBinding === null) {
2941
2964
  return bindTaskRoleProviderRuntime(sessions, createProviderRuntimeBinding({
2942
2965
  providerNamespace: input.fence.driverId,
@@ -2969,6 +2992,26 @@ function bindOrSupersedeProviderRuntime(sessions, input, now, replacementBasis)
2969
2992
  basis: replacementBasis
2970
2993
  }), now);
2971
2994
  }
2995
+ function terminalProviderReplacementBasis(sessions, input, mode) {
2996
+ if (mode !== "new" || sessions.providerBinding === null)
2997
+ return undefined;
2998
+ if (sessions.providerBinding.providerNamespace !== input.fence.driverId
2999
+ || sessions.providerBinding.accountScope !== input.fence.agentId) {
3000
+ return undefined;
3001
+ }
3002
+ const current = sessions.sessions[input.fence.agentId];
3003
+ if (current === undefined
3004
+ || current.status !== "active"
3005
+ || current.nativeSessionId !== input.fence.nativeSessionId
3006
+ || current.runtimeGenerationId !== input.fence.runtimeGenerationId) {
3007
+ return undefined;
3008
+ }
3009
+ const replaced = [...(sessions.history ?? [])].reverse().find((session) => (session.agentId === current.agentId
3010
+ && session.adapterId === current.adapterId
3011
+ && session.status === "ended"
3012
+ && session.nativeSessionId !== current.nativeSessionId));
3013
+ return replaced === undefined ? undefined : "terminal-session";
3014
+ }
2972
3015
  function settleStructuredProviderTurn(sessions, turnId, status, now) {
2973
3016
  const binding = sessions.providerBinding;
2974
3017
  if (binding === null || binding.turn === null || binding.turn.nativeTurnId !== turnId) {