@zq-silk/yui 0.13.9 → 0.14.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 (229) hide show
  1. package/ARCHITECTURE.md +69 -428
  2. package/README.md +108 -298
  3. package/dist/agent/managedRuntimeEnvironment.js +2 -2
  4. package/dist/cli/commandCatalog.js +39 -77
  5. package/dist/cli/interactionCandidates.js +4 -4
  6. package/dist/cli/interactionPolicy.js +7 -10
  7. package/dist/cli/invocationRouter.js +1 -1
  8. package/dist/cli/updateCommand.js +6 -27
  9. package/dist/cli/updateOrchestrator.js +63 -484
  10. package/dist/cli/updatePorts.js +15 -333
  11. package/dist/cli/upgradeCommand.js +8 -65
  12. package/dist/cli.js +57 -242
  13. package/dist/commands/configCommands.js +11 -62
  14. package/dist/commands/executionAuditCommands.js +23 -23
  15. package/dist/commands/globalRoleCommands.js +2 -7
  16. package/dist/commands/jobCommands.js +1 -4
  17. package/dist/commands/projectCommands.js +5 -5
  18. package/dist/commands/releaseCommands.js +3 -10
  19. package/dist/commands/resourcesCommands.js +2 -2
  20. package/dist/commands/sessionCommands.js +2 -2
  21. package/dist/commands/taskActor.js +31 -39
  22. package/dist/commands/taskChangeSetCommands.js +5 -12
  23. package/dist/commands/taskCommands.js +464 -1249
  24. package/dist/commands/taskContextCommand.js +35 -36
  25. package/dist/commands/taskExecutionCommands.js +33 -46
  26. package/dist/commands/taskInputCommands.js +20 -42
  27. package/dist/commands/taskNextActionCommand.js +9 -9
  28. package/dist/commands/taskOverviewCommand.js +56 -95
  29. package/dist/commands/taskRoleRuntimeStatus.js +77 -107
  30. package/dist/commands/taskUpstreamCommands.js +3 -3
  31. package/dist/commands/taskWorkspaceCommands.js +7 -79
  32. package/dist/commands/telemetryCommands.js +32 -124
  33. package/dist/commands/workflowCommands.js +3 -3
  34. package/dist/completion/fileCompletionManager.js +1 -1
  35. package/dist/config/configCatalog.js +4 -6
  36. package/dist/config/yuiConfig.js +6 -43
  37. package/dist/context/dispatchContext.js +14 -30
  38. package/dist/context/roleSessionContext.js +2 -2
  39. package/dist/context/sessionBootstrapManifest.js +2 -2
  40. package/dist/context/{runContextPack.js → turnContextPack.js} +58 -73
  41. package/dist/context/turnInputContract.js +204 -0
  42. package/dist/context/wakeNotification.js +2 -2
  43. package/dist/controller/agentRuntimeObserver.js +18 -18
  44. package/dist/controller/clientRuntime.js +13 -20
  45. package/dist/controller/controller.js +89 -116
  46. package/dist/controller/fileSchedulerStoreAdapter.js +511 -742
  47. package/dist/controller/jobControl.js +17 -17
  48. package/dist/controller/resourceInventory.js +6 -6
  49. package/dist/controller/resourceInventoryLinux.js +38 -59
  50. package/dist/controller/resourceInventoryRpc.js +3 -3
  51. package/dist/controller/resourceInventoryWorker.js +1 -1
  52. package/dist/controller/runtime.js +46 -53
  53. package/dist/controller/runtimeEventInbox.js +23 -31
  54. package/dist/controller/runtimeEventProcessor.js +15 -15
  55. package/dist/controller/{runtimeHookRunFence.js → runtimeHookTurnFence.js} +87 -81
  56. package/dist/controller/runtimeLaunchCoordinator.js +3 -3
  57. package/dist/controller/runtimeObservationHook.js +11 -8
  58. package/dist/controller/sessionNotify.js +11 -11
  59. package/dist/controller/structuredProviderObservation.js +89 -16
  60. package/dist/coordination/workMailbox.js +309 -579
  61. package/dist/coordination/workMailboxQueue.js +5 -6
  62. package/dist/core/controllerClient.js +1 -91
  63. package/dist/core/controllerServer.js +3 -6
  64. package/dist/doctor/doctor.js +51 -314
  65. package/dist/execution/candidateConvergence.js +3 -3
  66. package/dist/execution/executionGroup.js +32 -33
  67. package/dist/execution/executionHealth.js +36 -38
  68. package/dist/execution/resourceBroker.js +12 -12
  69. package/dist/executor/agentExecutor.js +30 -265
  70. package/dist/executor/effectiveLaunch.js +9 -19
  71. package/dist/executor/executorRegistry.js +43 -17
  72. package/dist/executor/fileRoleLaunchPlanner.js +56 -56
  73. package/dist/executor/launchPlan.js +3 -3
  74. package/dist/executor/workspacePreflightClassification.js +16 -16
  75. package/dist/grant/capabilityGrant.js +20 -25
  76. package/dist/input/inputRequest.js +12 -12
  77. package/dist/integration/changeSet.js +9 -13
  78. package/dist/integration/changeSetManifest.js +1 -1
  79. package/dist/integration/gitIntegrationService.js +2 -2
  80. package/dist/integration/integrationQueueService.js +5 -21
  81. package/dist/integration/overlapDiagnostics.js +2 -2
  82. package/dist/interaction/operatorPresentation.js +1 -1
  83. package/dist/job/jobRunner.js +1 -1
  84. package/dist/lifecycle/canonicalLifecycleEvent.js +25 -25
  85. package/dist/lifecycle/{exactRunTerminalization.js → exactTurnTerminalization.js} +116 -232
  86. package/dist/message/message.js +6 -6
  87. package/dist/observability/executionAudit.js +74 -86
  88. package/dist/observability/faultClassification.js +7 -24
  89. package/dist/observability/orchestrationMetrics.js +5 -5
  90. package/dist/observability/runtimeIdentity.js +34 -101
  91. package/dist/profile/agentProfile.js +1 -1
  92. package/dist/release/cliHomeReleaseFence.js +3 -3
  93. package/dist/release/releaseWorkflowEngine.js +6 -10
  94. package/dist/release/releaseWorkflowPorts.js +2 -2
  95. package/dist/release/workflowFileLock.js +1 -1
  96. package/dist/repository/checkoutSwap.js +1 -1
  97. package/dist/repository/gitWorkspace.js +0 -13
  98. package/dist/repository/project.js +1 -1
  99. package/dist/repository/projectMaintenanceLock.js +1 -2
  100. package/dist/repository/taskBaseFreshness.js +13 -13
  101. package/dist/repository/taskWorkspaceCoordinator.js +21 -25
  102. package/dist/repository/taskWorkspaceIdentity.js +8 -21
  103. package/dist/repository/taskWorkspacePreparer.js +63 -620
  104. package/dist/resources/autoResourceGc.js +3 -3
  105. package/dist/resources/liveReferences.js +1 -1
  106. package/dist/resources/resourceGc.js +1 -1
  107. package/dist/resources/sqliteResourceRegistry.js +1 -1
  108. package/dist/review/reviewDecision.js +7 -7
  109. package/dist/review/reviewFindingLedger.js +1 -1
  110. package/dist/review/reviewOutcomeClassifier.js +35 -50
  111. package/dist/review/reviewRound.js +14 -25
  112. package/dist/review/reviewerAvailability.js +4 -4
  113. package/dist/review/taskFinalReviewContractResolution.js +41 -0
  114. package/dist/role/role.js +6 -3
  115. package/dist/runtime/agentDriver.js +4 -0
  116. package/dist/runtime/agentHost.js +151 -36
  117. package/dist/runtime/builtinAgentDrivers.js +79 -2
  118. package/dist/runtime/codexAppServerRuntime.js +122 -0
  119. package/dist/runtime/continuationManager.js +3 -5
  120. package/dist/runtime/exactControlPlane.js +17 -42
  121. package/dist/runtime/firstProgressAdvisory.js +1 -1
  122. package/dist/runtime/index.js +2 -2
  123. package/dist/runtime/launchDiagnostics.js +2 -2
  124. package/dist/runtime/lifecycleReservation.js +4 -4
  125. package/dist/runtime/processExitObservation.js +1 -1
  126. package/dist/runtime/processExitOutbox.js +0 -32
  127. package/dist/runtime/promptEnvelope.js +9 -9
  128. package/dist/runtime/providerContinuation.js +2 -2
  129. package/dist/runtime/providerContinuationReconciliationService.js +2 -2
  130. package/dist/runtime/providerRuntimeIdentity.js +76 -24
  131. package/dist/runtime/recentTurnIds.js +38 -0
  132. package/dist/runtime/runtimeContinuationProjection.js +2 -2
  133. package/dist/runtime/runtimeHealthPolicy.js +1 -1
  134. package/dist/runtime/runtimeObservation.js +58 -21
  135. package/dist/runtime/runtimeProjection.js +19 -27
  136. package/dist/runtime/sessionLaunchRequest.js +2 -2
  137. package/dist/runtime/structuredProviderHost.js +177 -12
  138. package/dist/runtime/tmuxAdapters.js +46 -11
  139. package/dist/scheduler/activeRoleTurnDelivery.js +245 -0
  140. package/dist/scheduler/leaderWakeupProcessor.js +147 -445
  141. package/dist/scheduler/operatorEvent.js +4 -6
  142. package/dist/scheduler/resourceQueueProjection.js +71 -0
  143. package/dist/scheduler/{roleRunLiveness.js → roleTurnLiveness.js} +24 -28
  144. package/dist/scheduler/{roleRunStall.js → roleTurnStall.js} +162 -247
  145. package/dist/scheduler/taskExecutionProjection.js +99 -99
  146. package/dist/scheduler/taskObservabilityProjection.js +10 -10
  147. package/dist/scheduler/taskWake.js +4 -4
  148. package/dist/scheduler/wakeReason.js +6 -10
  149. package/dist/scheduler/wakeupQueue.js +0 -9
  150. package/dist/setup/setupCommand.js +2 -2
  151. package/dist/storage/currentTaskStore.js +46 -0
  152. package/dist/storage/persistenceWorker.js +1 -1
  153. package/dist/storage/sqliteSchema.js +81 -485
  154. package/dist/storage/sqliteStore.js +206 -365
  155. package/dist/storage/storageSchema.js +1 -15
  156. package/dist/storage/storageVersions.js +8 -9
  157. package/dist/storage/storeRpc.js +10 -34
  158. package/dist/storage/taskStore.js +63 -3415
  159. package/dist/storage/upgrade/recordVersions.js +55 -149
  160. package/dist/storage/upgrade/upgradeOrchestrator.js +89 -1752
  161. package/dist/task/completionReadiness.js +5 -5
  162. package/dist/task/deliveryGuard.js +10 -10
  163. package/dist/task/nextAction.js +74 -77
  164. package/dist/task/task.js +2 -4
  165. package/dist/task/taskRecordReference.js +3 -3
  166. package/dist/task/taskRecordRetirement.js +1 -1
  167. package/dist/telemetry/sqliteTelemetryStore.js +53 -53
  168. package/dist/telemetry/telemetryConfig.js +14 -14
  169. package/dist/telemetry/telemetryWiring.js +8 -14
  170. package/dist/turn/turn.js +356 -0
  171. package/dist/{run/runIdentity.js → turn/turnIdentity.js} +4 -4
  172. package/dist/verification/gateArtifactStore.js +2 -2
  173. package/dist/web/assets/client/app.js +1 -1
  174. package/dist/web/assets/client/components.js +29 -54
  175. package/dist/web/assets/client/i18n.js +17 -37
  176. package/dist/web/assets/client/view.js +37 -37
  177. package/dist/web/assets/shell.js +1 -1
  178. package/dist/web/assets/styles/cards.js +5 -5
  179. package/dist/web/assets/styles/widgets.js +1 -2
  180. package/dist/web/webSnapshot.js +65 -60
  181. package/dist/workItem/workItem.js +11 -11
  182. package/dist/workspace/workItemChangeSetManager.js +1 -1
  183. package/docs/task-local-identity.md +5 -5
  184. package/i18n/README.zh-CN.md +41 -54
  185. package/package.json +1 -1
  186. package/skills/yui-leader/SKILL.md +85 -83
  187. package/skills/yui-operator/SKILL.md +16 -16
  188. package/skills/yui-reviewer/SKILL.md +10 -10
  189. package/skills/yui-runtime/SKILL.md +17 -18
  190. package/skills/yui-worker/SKILL.md +16 -18
  191. package/dist/context/runContextContract.js +0 -162
  192. package/dist/executor/turnCompletion.js +0 -126
  193. package/dist/review/taskFinalReviewContractEvent.js +0 -1
  194. package/dist/review/taskFinalReviewContractRebind.js +0 -367
  195. package/dist/run/agentRun.js +0 -356
  196. package/dist/run/rejectedYieldAttempt.js +0 -221
  197. package/dist/run/runControlRequest.js +0 -50
  198. package/dist/run/yieldReceipt.js +0 -65
  199. package/dist/runtime/turnCompletion.js +0 -3
  200. package/dist/scheduler/actionability.js +0 -323
  201. package/dist/scheduler/activeRoleRunDelivery.js +0 -868
  202. package/dist/scheduler/activeTaskProgress.js +0 -126
  203. package/dist/storage/compatibleTaskStore.js +0 -248
  204. package/dist/storage/migration/baseline.js +0 -78
  205. package/dist/storage/migration/classifier.js +0 -51
  206. package/dist/storage/migration/compatibleCodec.js +0 -53
  207. package/dist/storage/migration/engine.js +0 -147
  208. package/dist/storage/migration/index.js +0 -33
  209. package/dist/storage/migration/planner.js +0 -154
  210. package/dist/storage/migration/productionRegistry.js +0 -3931
  211. package/dist/storage/migration/registry.js +0 -169
  212. package/dist/storage/migration/report.js +0 -54
  213. package/dist/storage/migration/types.js +0 -31
  214. package/dist/storage/upgrade/homeClassification.js +0 -353
  215. package/dist/storage/upgrade/homeMigrationTarget.js +0 -595
  216. package/dist/storage/upgrade/migrationReceipt.js +0 -67
  217. package/dist/storage/upgrade/offlineUpgradeInventory.js +0 -453
  218. package/dist/storage/upgrade/productionMigrationRegistry.js +0 -6
  219. package/dist/storage/upgrade/pseudoLayoutRepair.js +0 -241
  220. package/dist/storage/upgrade/recordVersionScan.js +0 -176
  221. package/dist/storage/upgrade/sqliteFileSet.js +0 -50
  222. package/dist/storage/upgrade/sqliteMigrationTarget.js +0 -375
  223. package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +0 -305
  224. package/dist/storage/upgrade/sqliteStateMigration.js +0 -918
  225. package/dist/storage/upgrade/switchProgress.js +0 -80
  226. package/dist/storage/upgrade/upgradeReceipt.js +0 -161
  227. package/dist/storage/upgradeCoordination.js +0 -187
  228. package/dist/storage/upgradeFence.js +0 -366
  229. package/dist/telemetry/telemetryCompaction.js +0 -252
@@ -1,8 +1,8 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { isDeepStrictEqual } from "node:util";
3
- import { activeLiveRoleAgentSession, bindTaskRoleProviderRuntime, bindTaskRoleRun, clearTaskRoleRun, createRoleSessionSet, markTaskRoleRunDelivered, markTaskRoleRunPushed, recordRoleAgentSession, replaceTaskRoleAgentSession, recordTaskRoleTurnBoundary, rememberRoleAgentCompletedTurn, detachRoleAgentSessionHost, updateRoleAgentSessionStatus, updateTaskRoleProviderRuntime } from "../executor/agentExecutor.js";
4
- import { acceptProviderTurn, beginProviderTurn, createProviderRuntimeBinding, endProviderActivation, currentProviderActivation, currentProviderConversation, markProviderTurnDeliveryUnknown, rejectProviderTurn, settleProviderTurnSubmission, settleProviderTurn, startProviderActivation, supersedeProviderConversation, updateProviderConversationRecoverability } from "../runtime/providerRuntimeIdentity.js";
5
- import { hasRecentTurnId } from "../executor/turnCompletion.js";
3
+ import { activeLiveRoleAgentSession, bindTaskRoleProviderRuntime, createRoleSessionSet, recordRoleAgentSession, replaceTaskRoleAgentSession, recordTaskRoleTurnBoundary, rememberRoleAgentCompletedTurn, detachRoleAgentSessionHost, updateRoleAgentSessionStatus, updateTaskRoleProviderRuntime } from "../executor/agentExecutor.js";
4
+ import { acceptProviderTurn, beginProviderTurn, createProviderRuntimeBinding, endProviderActivation, currentProviderActivation, currentProviderConversation, managedProviderTurnId, clearProviderGoal, providerGoalContinues, settleProviderTurnSubmission, settleProviderTurn, startProviderActivation, supersedeProviderConversation, updateProviderConversationRecoverability, updateProviderGoal } from "../runtime/providerRuntimeIdentity.js";
5
+ import { hasRecentTurnId } from "../runtime/recentTurnIds.js";
6
6
  import { createTaskEvent } from "../event/taskEvent.js";
7
7
  import { operationalTaskRecords } from "../task/taskRecordRetirement.js";
8
8
  import { buildTaskWakeEnvelope } from "../context/wakeNotification.js";
@@ -11,37 +11,38 @@ import { answerInputRequest } from "../input/inputRequest.js";
11
11
  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
- import { markAgentRunDelivered, markAgentRunPushed, agentRunDeliveryReceiptId } from "../run/agentRun.js";
15
- import { markWorkflowOutcomeRequestDispatched } from "../run/runControlRequest.js";
14
+ import { appendTurnInput, createTurn } from "../turn/turn.js";
15
+ import { createTurnInput } from "../context/turnInputContract.js";
16
+ import { parseReviewResultReport } from "../review/reviewRound.js";
16
17
  import { classifyRuntimeProcessExit, validateRuntimeProcessExitObservation } from "../runtime/processExitObservation.js";
17
- import { terminalizeExactTaskRun } from "../lifecycle/exactRunTerminalization.js";
18
+ import { terminalizeExactTaskTurn } from "../lifecycle/exactTurnTerminalization.js";
18
19
  import { createCanonicalLifecycleEvent, foldCanonicalLifecycleEvent } from "../lifecycle/canonicalLifecycleEvent.js";
19
20
  import { recordLeaderFailure } from "../scheduler/leaderFailure.js";
20
21
  import { recordLeaderAttentionRequired, routeRoleEvent } from "../scheduler/operatorEvent.js";
21
22
  import { pendingWakeupsMatch } from "../scheduler/pendingWakeup.js";
22
23
  import { queueLeaderWakeup } from "../scheduler/wakeupQueue.js";
23
24
  import { wakeReason } from "../scheduler/wakeReason.js";
24
- import { foldRunProgressFacts, latestRunDurableProgressAt, latestRunEventTime, latestStallEvidenceKey, isRoleRunStalled, RUN_PROGRESS_EVENT, RUN_DIAGNOSTIC_FINISHED_EVENT, RUN_RECOVERED_EVENT, RUN_STALLED_EVENT } from "../scheduler/roleRunStall.js";
25
+ import { foldTurnProgressFacts, latestTurnDurableProgressAt, latestTurnEventTime, latestStallEvidenceKey, isRoleTurnStalled, TURN_PROGRESS_EVENT, TURN_DIAGNOSTIC_FINISHED_EVENT, TURN_RECOVERED_EVENT, TURN_STALLED_EVENT } from "../scheduler/roleTurnStall.js";
26
+ import { pendingWakeupProjection } from "../storage/taskStore.js";
25
27
  import { projectProviderContinuations } from "../runtime/runtimeContinuationProjection.js";
26
28
  import { providerContinuationKey } from "../runtime/providerContinuation.js";
27
- import { formatAgentRunReceiptId } from "../task/taskRecordReference.js";
28
- import { bindExecution, claimPending, claimInputDelivery as claimMailboxInputDelivery, completeInputDelivery as completeMailboxInputDelivery, completeProcessing, mailboxHasPending, mailboxHasWork, pendingLane, markInputDeliveryPushed as markMailboxInputDeliveryPushed, markInputDeliveryUnknown as markMailboxInputDeliveryUnknown, releaseInputDelivery as releaseMailboxInputDelivery, resolveInputDeliveryNotAccepted as resolveMailboxInputDeliveryNotAccepted, releaseProcessing } from "../coordination/workMailbox.js";
29
+ import { formatTurnReceiptId } from "../task/taskRecordReference.js";
30
+ import { bindExecution, claimPending, completeProcessing, consumePendingBatch, mailboxHasPending, mailboxHasWork, releaseProcessing } from "../coordination/workMailbox.js";
29
31
  import { enqueueWork } from "../coordination/workMailboxQueue.js";
30
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";
31
33
  import { nativeSessionIdForLaunch } from "../runtime/preallocatedNativeSession.js";
32
34
  import { builtinAgentDriverRegistry } from "../runtime/builtinAgentDrivers.js";
33
35
  import { standardAgentError } from "../runtime/agentError.js";
34
- import { RUNTIME_OBSERVATION_TASK_EVENT, createRuntimeObservation, isRuntimeTokenEvidence, runtimeObservationFenceMatches, runtimeObservationFromTaskEvent, runtimeObservationRunFenceMatches, runtimeObservationTaskEventPayload } from "../runtime/runtimeObservation.js";
35
- import { projectRuntimeTaskEvents } from "../runtime/runtimeProjection.js";
36
+ import { RUNTIME_OBSERVATION_TASK_EVENT, createRuntimeObservation, isRuntimeTokenEvidence, runtimeObservationFenceMatches, runtimeObservationFromTaskEvent, runtimeObservationTurnFenceMatches, runtimeObservationTaskEventPayload } from "../runtime/runtimeObservation.js";
36
37
  import { contextSnapshotRef } from "../context/contextSnapshot.js";
37
- import { contextSnapshotDeltaRefIds, freezeRunContextSnapshot } from "../context/runContextPack.js";
38
+ import { contextSnapshotDeltaRefIds, freezeTurnContextSnapshot } from "../context/turnContextPack.js";
38
39
  export class AgentHostProviderTurnFenceError extends Error {
39
40
  constructor(message) {
40
41
  super(message);
41
42
  this.name = "AgentHostProviderTurnFenceError";
42
43
  }
43
44
  }
44
- /** Maps the authoritative FileTaskStore records to the scheduler's narrow port. */
45
+ /** Maps authoritative TaskStore records to the scheduler's narrow port. */
45
46
  export class FileSchedulerStoreAdapter {
46
47
  store;
47
48
  telemetry;
@@ -54,7 +55,7 @@ export class FileSchedulerStoreAdapter {
54
55
  }
55
56
  freezeLeaderContextSnapshot(taskId, roleName, now) {
56
57
  return this.store.transaction((tx) => {
57
- const snapshot = freezeRunContextSnapshot(tx, {
58
+ const snapshot = freezeTurnContextSnapshot(tx, {
58
59
  taskId,
59
60
  roleName,
60
61
  purpose: "execution"
@@ -68,7 +69,7 @@ export class FileSchedulerStoreAdapter {
68
69
  /**
69
70
  * Sole provider-independent ingress for structured runtime state. Driver
70
71
  * mapping has already happened before this boundary; this method validates
71
- * the exact durable Run/session fence, applies the small workflow-relevant
72
+ * the exact durable Turn/session fence, applies the small workflow-relevant
72
73
  * transitions, and retains the canonical observation for status projection.
73
74
  */
74
75
  observeRuntimeObservation(raw, now = new Date()) {
@@ -81,7 +82,7 @@ export class FileSchedulerStoreAdapter {
81
82
  }
82
83
  const adapterId = this.adapterForRuntimeObservation(input);
83
84
  if (adapterId === null) {
84
- return this.recordObsoleteCanonicalObservation(input, "driver-or-run-mismatch", now);
85
+ return this.recordObsoleteCanonicalObservation(input, "driver-or-turn-mismatch", now);
85
86
  }
86
87
  if (hasPersistedRuntimeObservation(this.store.listEvents(taskId), input))
87
88
  return "applied";
@@ -103,26 +104,19 @@ export class FileSchedulerStoreAdapter {
103
104
  adapterId,
104
105
  launchId: input.fence.launchId,
105
106
  nativeSessionId: input.fence.nativeSessionId,
106
- turnId: input.fence.nativeTurnId,
107
+ nativeTurnId: input.fence.nativeTurnId,
107
108
  attemptId: input.fence.receiptId,
108
- runId: input.fence.runId
109
+ turnId: input.fence.turnId
109
110
  });
110
111
  if (classification !== "apply")
111
112
  return classification;
112
- if (this.runtimeTurnHasActiveNativeSubagents(input)) {
113
- // This is an intermediate provider Turn boundary, not the end of the
114
- // durable Yui Run. The provider will deliver child completion
115
- // notifications as later native Turns in the same Run generation.
116
- outcome = this.validateCanonicalRunObservation(input, now);
117
- break;
118
- }
119
113
  outcome = this.foldProviderTurnBoundary(input, adapterId, "completed", input.payload.summary ?? "Agent turn completed without a workflow outcome.", now);
120
114
  break;
121
115
  }
122
116
  case "turn.failed": {
123
117
  const failureEvidence = input.payload.failure;
124
- // A Provider Turn is an activation boundary, not a Yui Run outcome.
125
- // Keep the Run and Session identity available for Agent-directed
118
+ // A Provider Turn is an activation boundary, not a Task outcome.
119
+ // Keep the Turn and Session identity available for Agent-directed
126
120
  // restore + submit, even when a provider marks its Turn terminal.
127
121
  outcome = this.foldProviderTurnBoundary(input, adapterId, "failed", input.payload.summary ?? `Agent Turn failed: ${failureEvidence.error.message}`, now);
128
122
  break;
@@ -138,7 +132,7 @@ export class FileSchedulerStoreAdapter {
138
132
  case "continuation.reported":
139
133
  case "continuation.settled":
140
134
  case "input.delivery-unknown":
141
- outcome = this.validateCanonicalRunObservation(input, now);
135
+ outcome = this.validateCanonicalTurnObservation(input, now);
142
136
  break;
143
137
  case "turn.cancelled": {
144
138
  outcome = this.foldProviderTurnBoundary(input, adapterId, "cancelled", input.payload.summary ?? "Provider Turn was cancelled.", now);
@@ -150,6 +144,10 @@ export class FileSchedulerStoreAdapter {
150
144
  case "activation.failed":
151
145
  outcome = this.observeProviderRuntimeIdentity(input, now);
152
146
  break;
147
+ case "goal.updated":
148
+ case "goal.cleared":
149
+ outcome = this.observeProviderGoal(input, now);
150
+ break;
153
151
  case "session.ended":
154
152
  case "session.failed":
155
153
  outcome = this.validateCanonicalSessionObservation(input, now);
@@ -166,10 +164,26 @@ export class FileSchedulerStoreAdapter {
166
164
  }
167
165
  adapterForRuntimeObservation(input) {
168
166
  const taskId = input.fence.taskId;
169
- const runId = input.fence.runId;
170
- if (taskId === undefined || runId === undefined)
167
+ const turnId = input.fence.turnId;
168
+ if (taskId === undefined)
171
169
  return null;
172
- const run = this.store.getAgentRun(taskId, runId);
170
+ if (turnId === undefined) {
171
+ const role = this.store.getRole(taskId, input.fence.roleName);
172
+ const sessions = this.store.getTaskRoleSessionSet(taskId, input.fence.roleName);
173
+ const session = sessions?.sessions[input.fence.agentId];
174
+ if (role?.activeAgentId !== input.fence.agentId
175
+ || session?.adapterId === undefined)
176
+ return null;
177
+ try {
178
+ return this.drivers.requireByAdapterId(session.adapterId).id === input.fence.driverId
179
+ ? session.adapterId
180
+ : null;
181
+ }
182
+ catch {
183
+ return null;
184
+ }
185
+ }
186
+ const run = this.store.getTurn(taskId, turnId);
173
187
  if (run === null
174
188
  || run.roleName !== input.fence.roleName
175
189
  || run.effective.agentId !== input.fence.agentId)
@@ -183,17 +197,17 @@ export class FileSchedulerStoreAdapter {
183
197
  return null;
184
198
  }
185
199
  }
186
- validateCanonicalRunObservation(input, now) {
200
+ validateCanonicalTurnObservation(input, now) {
187
201
  return this.store.transaction((store) => {
188
202
  if (hasPersistedRuntimeObservation(store.listEvents(input.fence.taskId), input)) {
189
203
  return "applied";
190
204
  }
191
- const run = store.getAgentRun(input.fence.taskId, input.fence.runId);
192
- const active = store.getActiveAgentRun(input.fence.taskId, input.fence.roleName);
205
+ const run = store.getTurn(input.fence.taskId, input.fence.turnId);
206
+ const active = store.getActiveTurn(input.fence.taskId, input.fence.roleName);
193
207
  const sessions = store.getTaskRoleSessionSet(input.fence.taskId, input.fence.roleName);
194
208
  const session = sessions?.sessions[input.fence.agentId];
195
209
  const knownContinuation = input.kind.startsWith("continuation.")
196
- && projectProviderContinuations(store.listEvents(input.fence.taskId)).some((entry) => (entry.runId === input.fence.runId
210
+ && projectProviderContinuations(store.listEvents(input.fence.taskId)).some((entry) => (entry.turnId === input.fence.turnId
197
211
  && entry.identity.providerNamespace === input.fence.driverId
198
212
  && entry.identity.accountScope === input.fence.agentId
199
213
  && entry.identity.conversationId === input.fence.conversationId
@@ -209,8 +223,7 @@ export class FileSchedulerStoreAdapter {
209
223
  && (knownContinuation || (session?.launchId === input.fence.launchId
210
224
  && session.nativeSessionId === input.fence.nativeSessionId
211
225
  && input.fence.sessionGenerationId === input.fence.launchId))
212
- && runtimeReceiptBelongsToRun(store, input)
213
- && (run.deliveredAt !== undefined || input.kind === "turn.cancelled");
226
+ && runtimeReceiptBelongsToTurn(store, input);
214
227
  if (!valid) {
215
228
  recordCanonicalObservationObsolete(store, input, "runtime-fence-not-current", now);
216
229
  return "obsolete";
@@ -218,14 +231,6 @@ export class FileSchedulerStoreAdapter {
218
231
  return "applied";
219
232
  });
220
233
  }
221
- runtimeTurnHasActiveNativeSubagents(input) {
222
- const taskId = input.fence.taskId;
223
- const run = this.store.getAgentRun(taskId, input.fence.runId);
224
- if (run === null)
225
- return false;
226
- const projection = projectRuntimeTaskEvents(input.fence, run.createdAt, this.store.listEvents(taskId));
227
- return Object.values(projection.operations).some(({ kind }) => kind === "subagent");
228
- }
229
234
  foldProviderTurnBoundary(input, adapterId, providerStatus, summary, now) {
230
235
  const completed = {
231
236
  taskId: input.fence.taskId,
@@ -234,9 +239,10 @@ export class FileSchedulerStoreAdapter {
234
239
  adapterId,
235
240
  launchId: input.fence.launchId,
236
241
  nativeSessionId: input.fence.nativeSessionId,
237
- turnId: input.fence.nativeTurnId,
242
+ nativeTurnId: input.fence.nativeTurnId,
238
243
  attemptId: input.fence.receiptId,
239
- runId: input.fence.runId,
244
+ turnId: input.fence.turnId,
245
+ ...(input.payload.input === undefined ? {} : { input: input.payload.input }),
240
246
  summary,
241
247
  providerStatus
242
248
  };
@@ -248,7 +254,7 @@ export class FileSchedulerStoreAdapter {
248
254
  }
249
255
  validateCanonicalSessionObservation(input, now) {
250
256
  return this.store.transaction((store) => {
251
- const run = store.getAgentRun(input.fence.taskId, input.fence.runId);
257
+ const run = store.getTurn(input.fence.taskId, input.fence.turnId);
252
258
  const sessions = store.getTaskRoleSessionSet(input.fence.taskId, input.fence.roleName);
253
259
  const session = sessions?.sessions[input.fence.agentId];
254
260
  if (run === null
@@ -273,7 +279,7 @@ export class FileSchedulerStoreAdapter {
273
279
  }
274
280
  store.saveTaskRoleSessionSet(updatedSessions);
275
281
  for (const continuation of projectProviderContinuations(store.listEvents(input.fence.taskId))) {
276
- if (continuation.runId !== input.fence.runId
282
+ if (continuation.turnId !== input.fence.turnId
277
283
  || continuation.identity.conversationId
278
284
  !== (input.fence.conversationId ?? input.fence.nativeSessionId)
279
285
  || continuation.identity.activationId
@@ -322,29 +328,26 @@ export class FileSchedulerStoreAdapter {
322
328
  { type: "event", taskId: input.fence.taskId, id: eventId }
323
329
  ], {
324
330
  source: input.fence.driverId,
325
- dedupeKey: detached.semanticKey,
326
- deliveryMode: "followup",
327
- lane: "normal"
331
+ dedupeKey: detached.semanticKey
328
332
  });
329
333
  }
330
- const active = store.getActiveAgentRun(input.fence.taskId, input.fence.roleName);
334
+ const active = store.getActiveTurn(input.fence.taskId, input.fence.roleName);
331
335
  const role = store.getRole(input.fence.taskId, input.fence.roleName);
332
336
  if ((input.kind === "session.ended" || input.kind === "session.failed")
333
337
  && active !== null
334
- && active.id === input.fence.runId
338
+ && active.id === input.fence.turnId
335
339
  && active.status === "active"
336
- && active.deliveredAt !== undefined
337
340
  && role !== null) {
338
341
  if (role.name === "leader") {
339
342
  const message = [
340
- `Leader native Session became unavailable while Run ${active.id} remains active.`,
341
- "Yui preserved the Run because Session/host termination is not a workflow outcome.",
342
- "Retire the disposable Run, or use Task execution stop/start if the current runtime cannot be settled normally."
343
+ `Leader native Session became unavailable while Turn ${active.id} remains active.`,
344
+ "Yui preserved the Turn because Session/host termination is not a Task outcome.",
345
+ "Retire the disposable Turn, or use Task execution stop/start if the current runtime cannot be settled normally."
343
346
  ].join(" ");
344
347
  recordLeaderAttentionRequired(store, {
345
348
  taskId: input.fence.taskId,
346
349
  reason: "leader-runtime-detached",
347
- payload: { message, runId: active.id },
350
+ payload: { message, turnId: active.id },
348
351
  now
349
352
  });
350
353
  }
@@ -354,7 +357,7 @@ export class FileSchedulerStoreAdapter {
354
357
  taskId: input.fence.taskId,
355
358
  roleName: "leader"
356
359
  }, "role-runtime-detached", now, [
357
- { type: "run", taskId: input.fence.taskId, id: active.id }
360
+ { type: "turn", taskId: input.fence.taskId, id: active.id }
358
361
  ]);
359
362
  }
360
363
  }
@@ -379,7 +382,7 @@ export class FileSchedulerStoreAdapter {
379
382
  && input.payload.snapshotComplete === true
380
383
  && input.payload.observationQuality === "exact") {
381
384
  for (const continuation of projectProviderContinuations(events)) {
382
- if (continuation.runId !== input.fence.runId
385
+ if (continuation.turnId !== input.fence.turnId
383
386
  || continuation.identity.conversationId !== input.fence.conversationId
384
387
  || continuation.identity.activationId !== input.fence.activationId
385
388
  || continuation.execution === "quiescent")
@@ -419,9 +422,7 @@ export class FileSchedulerStoreAdapter {
419
422
  store.saveEvent(taskId, createTaskEvent(settledEventId, taskId, RUNTIME_OBSERVATION_TASK_EVENT, runtimeObservationTaskEventPayload(settled), now));
420
423
  enqueueWork(store, { kind: "role", taskId, roleName: "leader" }, "provider-continuation-settled", now, [{ type: "event", taskId, id: settledEventId }], {
421
424
  source: input.fence.driverId,
422
- dedupeKey: settled.semanticKey,
423
- deliveryMode: "followup",
424
- lane: "normal"
425
+ dedupeKey: settled.semanticKey
425
426
  });
426
427
  }
427
428
  }
@@ -431,21 +432,26 @@ export class FileSchedulerStoreAdapter {
431
432
  ? "provider-continuation-report"
432
433
  : "provider-continuation-settled", now, [{ type: "event", taskId, id: observationEventId }], {
433
434
  source: input.fence.driverId,
434
- dedupeKey: input.semanticKey,
435
- deliveryMode: "followup",
436
- lane: "normal"
435
+ dedupeKey: input.semanticKey
437
436
  });
438
437
  }
438
+ if ((input.kind === "goal.cleared"
439
+ || (input.kind === "goal.updated" && input.payload.goalStatus !== "active"))
440
+ && input.fence.roleName !== "leader") {
441
+ routeRoleEvent(store, observationEvent, input.fence.roleName, input.kind === "goal.cleared"
442
+ ? "provider-goal-cleared"
443
+ : `provider-goal-${input.payload.goalStatus}`, now);
444
+ }
439
445
  if (input.kind === "turn.failed" && input.payload.failure !== undefined) {
440
446
  const failure = input.payload.failure;
441
447
  const error = failure.error;
442
- const run = input.fence.runId === undefined
448
+ const run = input.fence.turnId === undefined
443
449
  ? null
444
- : store.getAgentRun(taskId, input.fence.runId);
450
+ : store.getTurn(taskId, input.fence.turnId);
445
451
  const errorEvent = createTaskEvent(store.nextEventId(taskId), taskId, "runtime.agent-error", {
446
452
  sourceEventId: input.eventId,
447
453
  observationEventId,
448
- runId: input.fence.runId ?? "",
454
+ turnId: input.fence.turnId ?? "",
449
455
  roleName: input.fence.roleName,
450
456
  agentId: input.fence.agentId,
451
457
  adapterId: run?.effective.adapterId ?? "unknown",
@@ -469,17 +475,13 @@ export class FileSchedulerStoreAdapter {
469
475
  store.saveEvent(taskId, errorEvent);
470
476
  enqueueWork(store, { kind: "role", taskId, roleName: "leader" }, wakeReason("agent-error", errorEvent.id), now, [{ type: "event", taskId, id: errorEvent.id }], {
471
477
  source: input.fence.driverId,
472
- dedupeKey: `agent-error:${taskId}:${input.eventId}`,
473
- deliveryMode: "followup",
474
- lane: "normal"
478
+ dedupeKey: `agent-error:${taskId}:${input.eventId}`
475
479
  });
476
480
  if (input.fence.roleName === "leader"
477
481
  && error.sessionDisposition === "unrecoverable") {
478
482
  enqueueWork(store, { kind: "operator" }, "leader-session-unrecoverable", now, [{ type: "event", taskId, id: errorEvent.id }], {
479
483
  source: input.fence.driverId,
480
- dedupeKey: `leader-session-unrecoverable:${taskId}:${input.eventId}`,
481
- deliveryMode: "followup",
482
- lane: "normal"
484
+ dedupeKey: `leader-session-unrecoverable:${taskId}:${input.eventId}`
483
485
  });
484
486
  }
485
487
  }
@@ -495,14 +497,14 @@ export class FileSchedulerStoreAdapter {
495
497
  }
496
498
  }
497
499
  });
498
- if (this.telemetry !== null && input.fence.runId !== undefined) {
500
+ if (this.telemetry !== null && input.fence.turnId !== undefined) {
499
501
  try {
500
502
  const entry = runtimeObservationTelemetryEntry(input);
501
503
  this.telemetry.sink.observe(entry);
502
- const run = this.store.getAgentRun(entry.taskId, input.fence.runId);
504
+ const run = this.store.getTurn(entry.taskId, input.fence.turnId);
503
505
  if (run !== null && run.status !== "active") {
504
506
  void this.telemetry.retention.flush().then(() => {
505
- this.telemetry?.retention.pruneGeneration(entry.taskId, entry.roleName, entry.runId, entry.generation);
507
+ this.telemetry?.retention.pruneGeneration(entry.taskId, entry.roleName, entry.turnId, entry.generation);
506
508
  }).catch(() => undefined);
507
509
  }
508
510
  }
@@ -539,8 +541,8 @@ export class FileSchedulerStoreAdapter {
539
541
  const events = this.store.listEvents(taskId);
540
542
  task = {
541
543
  events,
542
- runFacts: foldRunProgressFacts(events),
543
- agentRuns: this.store.listAgentRuns(taskId),
544
+ turnFacts: foldTurnProgressFacts(events),
545
+ turns: this.store.listTurns(taskId),
544
546
  workItems: this.store.listWorkItems(taskId),
545
547
  reviewRounds: this.store.listReviewRounds(taskId),
546
548
  changeSets: this.store.listChangeSets(taskId),
@@ -576,7 +578,7 @@ export class FileSchedulerStoreAdapter {
576
578
  const latest = latestTaskWake(reader.listTaskWakes(taskId));
577
579
  const fromCursor = latest?.toCursor ?? fallbackWakeCursor({
578
580
  taskCreatedAt: task.createdAt,
579
- leaderRunCreatedAt: operationalTaskRecords(reader.listAgentRuns(taskId), reader.listEvents(taskId), "agent-run")
581
+ leaderTurnCreatedAt: operationalTaskRecords(reader.listTurns(taskId), reader.listEvents(taskId), "turn")
580
582
  .filter((run) => run.roleName === "leader")
581
583
  .at(-1)?.createdAt
582
584
  });
@@ -595,8 +597,8 @@ export class FileSchedulerStoreAdapter {
595
597
  const role = this.store.getRole(taskId, roleName);
596
598
  return role === null ? null : mapRole(this.store, role);
597
599
  }
598
- getActiveAgentRun(taskId, roleName) {
599
- return this.store.getActiveAgentRun(taskId, roleName);
600
+ getActiveTurn(taskId, roleName) {
601
+ return this.store.getActiveTurn(taskId, roleName);
600
602
  }
601
603
  hasOpenInputRequest(taskId) {
602
604
  return this.store.listOpenInputRequests([taskId]).length > 0;
@@ -660,8 +662,8 @@ export class FileSchedulerStoreAdapter {
660
662
  listEvents(taskId) {
661
663
  return this.#taskReadProjection(taskId).events;
662
664
  }
663
- listAgentRuns(taskId) {
664
- return this.#taskReadProjection(taskId).agentRuns;
665
+ listTurns(taskId) {
666
+ return this.#taskReadProjection(taskId).turns;
665
667
  }
666
668
  listWorkItems(taskId) {
667
669
  return this.#taskReadProjection(taskId).workItems;
@@ -681,17 +683,17 @@ export class FileSchedulerStoreAdapter {
681
683
  listMessages(taskId) {
682
684
  return this.#taskReadProjection(taskId).messages;
683
685
  }
684
- getRunProgressFacts(taskId, runId) {
685
- return this.#taskReadProjection(taskId).runFacts.get(runId);
686
+ getTurnProgressFacts(taskId, turnId) {
687
+ return this.#taskReadProjection(taskId).turnFacts.get(turnId);
686
688
  }
687
- getRunDurableProgress(taskId, roleName, runId) {
689
+ getTurnDurableProgress(taskId, roleName, turnId) {
688
690
  const projected = this.#taskReadProjection(taskId);
689
691
  // Serve the related-record fold from the same revision's projection: the
690
692
  // event history and the WorkItem/Review/ChangeSet/Integration/Input lists
691
- // are read once per Task per revision, and the per-Run checkpoint/activity
693
+ // are read once per Task per revision, and the per-Turn checkpoint/activity
692
694
  // facts come from the one-pass fold instead of per-candidate scans.
693
695
  const view = {
694
- getAgentRun: (id, agentRunId) => this.store.getAgentRun(id, agentRunId),
696
+ getTurn: (id, turnId) => this.store.getTurn(id, turnId),
695
697
  listEvents: () => projected.events,
696
698
  getWorkItem: (id, workItemId) => this.store.getWorkItem(id, workItemId),
697
699
  listReviewRounds: () => projected.reviewRounds,
@@ -700,24 +702,24 @@ export class FileSchedulerStoreAdapter {
700
702
  listInputRequests: () => projected.inputRequests
701
703
  };
702
704
  // A missing fold entry is an authoritative empty fold, not a signal to
703
- // re-scan the whole history. Pass {} so latestRunDurableProgressAt treats
705
+ // re-scan the whole history. Pass {} so latestTurnDurableProgressAt treats
704
706
  // the fold as present and skips the per-candidate fallback scans.
705
- return latestRunDurableProgressAt(view, taskId, roleName, runId, projected.runFacts.get(runId) ?? {});
707
+ return latestTurnDurableProgressAt(view, taskId, roleName, turnId, projected.turnFacts.get(turnId) ?? {});
706
708
  }
707
- recordRoleRunDiagnostic(input) {
709
+ recordRoleTurnDiagnostic(input) {
708
710
  return this.store.transaction((store) => {
709
711
  const task = store.getTask(input.taskId);
710
- const run = store.getActiveAgentRun(input.taskId, input.roleName);
712
+ const run = store.getActiveTurn(input.taskId, input.roleName);
711
713
  if (task === null || task.status !== "active" || task.executionGate.state !== "enabled"
712
- || run === null || run.id !== input.runId || run.status !== "active") {
714
+ || run === null || run.id !== input.turnId || run.status !== "active") {
713
715
  return "state-changed";
714
716
  }
715
- const latest = latestRunEventTime(store.listEvents(input.taskId), RUN_DIAGNOSTIC_FINISHED_EVENT, input.runId);
717
+ const latest = latestTurnEventTime(store.listEvents(input.taskId), TURN_DIAGNOSTIC_FINISHED_EVENT, input.turnId);
716
718
  if (latest !== undefined && Date.parse(latest) >= Date.parse(input.startedAt)) {
717
719
  return "already-recorded";
718
720
  }
719
- store.saveEvent(input.taskId, createTaskEvent(store.nextEventId(input.taskId), input.taskId, RUN_DIAGNOSTIC_FINISHED_EVENT, {
720
- runId: input.runId,
721
+ store.saveEvent(input.taskId, createTaskEvent(store.nextEventId(input.taskId), input.taskId, TURN_DIAGNOSTIC_FINISHED_EVENT, {
722
+ turnId: input.turnId,
721
723
  roleName: input.roleName,
722
724
  outcome: input.outcome,
723
725
  startedAt: input.startedAt
@@ -725,22 +727,22 @@ export class FileSchedulerStoreAdapter {
725
727
  return "recorded";
726
728
  });
727
729
  }
728
- recordRoleRunStall(input) {
730
+ recordRoleTurnStall(input) {
729
731
  return this.store.transaction((store) => {
730
732
  const task = store.getTask(input.taskId);
731
733
  const role = store.getRole(input.taskId, input.roleName);
732
- const run = store.getActiveAgentRun(input.taskId, input.roleName);
734
+ const run = store.getActiveTurn(input.taskId, input.roleName);
733
735
  if (task === null
734
736
  || task.status !== "active"
735
737
  || task.executionGate.state !== "enabled"
736
738
  || role === null
737
739
  || run === null
738
- || run.id !== input.runId
740
+ || run.id !== input.turnId
739
741
  || run.status !== "active"
740
742
  || run.effective.agentId !== input.agentId
741
743
  || run.effective.adapterId !== input.adapterId)
742
744
  return "state-changed";
743
- const progress = latestRunDurableProgressAt(store, input.taskId, input.roleName, input.runId);
745
+ const progress = latestTurnDurableProgressAt(store, input.taskId, input.roleName, input.turnId);
744
746
  if (progress?.progressAt !== input.progressAt)
745
747
  return "state-changed";
746
748
  const session = store.getRoleSession(input.taskId, input.roleName);
@@ -749,8 +751,8 @@ export class FileSchedulerStoreAdapter {
749
751
  const existing = latestStallEvidenceKey(store.listEvents(task.id), run.id);
750
752
  if (existing?.progressAt === input.progressAt)
751
753
  return "already-raised";
752
- const event = createTaskEvent(store.nextEventId(task.id), task.id, RUN_STALLED_EVENT, {
753
- runId: run.id,
754
+ const event = createTaskEvent(store.nextEventId(task.id), task.id, TURN_STALLED_EVENT, {
755
+ turnId: run.id,
754
756
  roleName: role.name,
755
757
  kind: input.kind,
756
758
  classification: input.classification,
@@ -763,15 +765,15 @@ export class FileSchedulerStoreAdapter {
763
765
  return "raised";
764
766
  });
765
767
  }
766
- recordRoleRunProgress(input) {
768
+ recordRoleTurnProgress(input) {
767
769
  return this.store.transaction((store) => {
768
770
  const task = store.getTask(input.taskId);
769
- const run = store.getActiveAgentRun(input.taskId, input.roleName);
771
+ const run = store.getActiveTurn(input.taskId, input.roleName);
770
772
  if (task === null
771
773
  || task.status !== "active"
772
774
  || task.executionGate.state !== "enabled"
773
775
  || run === null
774
- || run.id !== input.runId
776
+ || run.id !== input.turnId
775
777
  || run.status !== "active")
776
778
  return "state-changed";
777
779
  const events = store.listEvents(task.id);
@@ -782,18 +784,18 @@ export class FileSchedulerStoreAdapter {
782
784
  && Date.parse(input.progressAt) <= Date.parse(previousStall.progressAt)) {
783
785
  return "already-recorded";
784
786
  }
785
- const existing = events.some((event) => (event.type === RUN_PROGRESS_EVENT
786
- && event.payload.runId === run.id
787
+ const existing = events.some((event) => (event.type === TURN_PROGRESS_EVENT
788
+ && event.payload.turnId === run.id
787
789
  && (event.payload.progressAt === input.progressAt
788
790
  || (typeof event.payload.progressAt === "string"
789
791
  && Number.isFinite(Date.parse(event.payload.progressAt))
790
792
  && Date.parse(event.payload.progressAt) >= Date.parse(input.progressAt)))));
791
793
  // Advisory 30-minute diagnostics are intentionally not lifecycle
792
- // episodes and therefore must never synthesize run.recovered.
793
- const recovered = isRoleRunStalled(events, run.id);
794
+ // episodes and therefore must never synthesize turn.recovered.
795
+ const recovered = isRoleTurnStalled(events, run.id);
794
796
  if (!existing) {
795
- store.saveEvent(task.id, createTaskEvent(store.nextEventId(task.id), task.id, RUN_PROGRESS_EVENT, {
796
- runId: run.id,
797
+ store.saveEvent(task.id, createTaskEvent(store.nextEventId(task.id), task.id, TURN_PROGRESS_EVENT, {
798
+ turnId: run.id,
797
799
  roleName: input.roleName,
798
800
  kind: "durable-fold",
799
801
  progressAt: input.progressAt,
@@ -801,8 +803,8 @@ export class FileSchedulerStoreAdapter {
801
803
  }, input.now));
802
804
  }
803
805
  if (recovered) {
804
- store.saveEvent(task.id, createTaskEvent(store.nextEventId(task.id), task.id, RUN_RECOVERED_EVENT, {
805
- runId: run.id,
806
+ store.saveEvent(task.id, createTaskEvent(store.nextEventId(task.id), task.id, TURN_RECOVERED_EVENT, {
807
+ turnId: run.id,
806
808
  roleName: input.roleName,
807
809
  progressAt: input.progressAt,
808
810
  kind: "durable-progress"
@@ -816,34 +818,32 @@ export class FileSchedulerStoreAdapter {
816
818
  const sessions = store.getTaskRoleSessionSet(input.taskId, input.roleName);
817
819
  const session = sessions?.sessions[input.agentId];
818
820
  const binding = sessions?.providerBinding;
819
- const mailbox = store.getWorkMailbox({
820
- kind: "role",
821
- taskId: input.taskId,
822
- roleName: input.roleName
823
- });
821
+ const active = store.getActiveTurn(input.taskId, input.roleName);
824
822
  if (sessions === null || sessions === undefined
825
823
  || binding === null || binding === undefined
826
- || sessions.inFlight?.runId !== input.runId
824
+ || (input.turnId !== undefined
825
+ && (active?.id !== input.turnId || active.effective.agentId !== input.agentId))
827
826
  || session?.launchId !== input.launchId
828
827
  || session.nativeSessionId !== input.nativeSessionId
829
828
  || currentProviderConversation(binding).conversationId !== input.nativeSessionId
830
829
  || binding.authority.owner !== input.authorityOwner
831
830
  || binding.authority.epoch !== input.authorityEpoch
832
- || binding.authority.holderId !== input.holderId
833
- || !processingOwnsExactInitialDelivery(mailbox, input.taskId, input.runId, input.attemptId)) {
831
+ || binding.authority.holderId !== input.holderId) {
834
832
  throw new AgentHostProviderTurnFenceError("Agent Host Provider Turn carries a stale durable writer fence. Release and reacquire Provider authority before retrying input.");
835
833
  }
836
- const exactReplay = binding.turn?.runId === input.runId
837
- && binding.turn.attemptId === input.attemptId
838
- && binding.turn.authorityEpoch === input.authorityEpoch
839
- && binding.turn.status === "submitting";
834
+ const currentTurn = binding.turn;
835
+ const exactReplay = currentTurn !== null
836
+ && currentTurn.turnId === input.turnId
837
+ && currentTurn.attemptId === input.attemptId
838
+ && currentTurn.authorityEpoch === input.authorityEpoch
839
+ && currentTurn.status === "submitting";
840
840
  if (!exactReplay && binding.turn !== null
841
- && ["submitting", "accepted", "running", "delivery-unknown"]
841
+ && ["submitting", "accepted", "delivery-unknown"]
842
842
  .includes(binding.turn.status)) {
843
843
  throw new AgentHostProviderTurnFenceError("Provider Conversation already has an unsettled Turn.");
844
844
  }
845
845
  store.saveTaskRoleSessionSet(updateTaskRoleProviderRuntime(sessions, beginProviderTurn(binding, {
846
- runId: input.runId,
846
+ ...(input.turnId === undefined ? {} : { turnId: input.turnId }),
847
847
  attemptId: input.attemptId,
848
848
  authorityEpoch: input.authorityEpoch,
849
849
  submittedAt: input.now.toISOString()
@@ -856,7 +856,7 @@ export class FileSchedulerStoreAdapter {
856
856
  const binding = sessions?.providerBinding;
857
857
  if (sessions === null || sessions === undefined
858
858
  || binding === null || binding === undefined
859
- || binding.turn?.runId !== input.runId
859
+ || binding.turn?.turnId !== input.turnId
860
860
  || binding.turn?.attemptId !== input.attemptId) {
861
861
  throw new Error("Agent Host Provider Turn submission is no longer current.");
862
862
  }
@@ -867,7 +867,9 @@ export class FileSchedulerStoreAdapter {
867
867
  resolvedAt: input.now.toISOString()
868
868
  });
869
869
  store.saveTaskRoleSessionSet(updateTaskRoleProviderRuntime(sessions, updated, input.now));
870
- const run = store.getAgentRun(input.taskId, input.runId);
870
+ if (input.turnId === undefined)
871
+ return;
872
+ const run = store.getTurn(input.taskId, input.turnId);
871
873
  const session = sessions.sessions[sessions.activeAgentId];
872
874
  const driver = run === null
873
875
  ? null
@@ -885,7 +887,7 @@ export class FileSchedulerStoreAdapter {
885
887
  });
886
888
  const errorEvent = createTaskEvent(store.nextEventId(input.taskId), input.taskId, "runtime.agent-error", {
887
889
  sourceEventId: input.attemptId,
888
- runId: input.runId,
890
+ turnId: input.turnId,
889
891
  roleName: input.roleName,
890
892
  agentId: run?.effective.agentId ?? sessions.activeAgentId,
891
893
  adapterId: run?.effective.adapterId ?? session?.adapterId ?? "unknown",
@@ -910,9 +912,7 @@ export class FileSchedulerStoreAdapter {
910
912
  ? "leader-turn-submission-error"
911
913
  : wakeReason("agent-error", errorEvent.id), input.now, [{ type: "event", taskId: input.taskId, id: errorEvent.id }], {
912
914
  source: driver?.id ?? "agent-host",
913
- dedupeKey: `agent-error:${input.taskId}:${input.attemptId}`,
914
- deliveryMode: "followup",
915
- lane: "normal"
915
+ dedupeKey: `agent-error:${input.taskId}:${input.attemptId}`
916
916
  });
917
917
  });
918
918
  }
@@ -934,8 +934,8 @@ export class FileSchedulerStoreAdapter {
934
934
  ...binding.authority
935
935
  };
936
936
  }
937
- peekNextAgentRunId(taskId) {
938
- return this.store.peekNextAgentRunId(taskId);
937
+ peekNextTurnId(taskId) {
938
+ return this.store.peekNextTurnId(taskId);
939
939
  }
940
940
  getWorkMailbox(target) { return this.store.getWorkMailbox(target); }
941
941
  listWorkMailboxes() { return this.store.listWorkMailboxes(); }
@@ -948,17 +948,23 @@ export class FileSchedulerStoreAdapter {
948
948
  });
949
949
  }
950
950
  enqueueLeaderWakeup(taskId, reason, now) {
951
- return this.store.transaction((store) => (queueLeaderWakeup(store, taskId, reason, now)));
951
+ return this.store.transaction((store) => {
952
+ const mailbox = enqueueWork(store, { kind: "role", taskId, roleName: "leader" }, reason, now);
953
+ const wakeup = pendingWakeupProjection(mailbox);
954
+ if (wakeup === null)
955
+ throw new Error(`Leader wakeup was not persisted: ${taskId}.`);
956
+ return wakeup;
957
+ });
952
958
  }
953
959
  recordAgentError(input, now) {
954
960
  return this.store.transaction((store) => {
955
961
  const duplicate = [...store.listEvents(input.taskId)].reverse().find((event) => (event.type === "runtime.agent-error"
956
- && event.payload.runId === input.runId
962
+ && event.payload.turnId === input.turnId
957
963
  && event.payload.phase === input.phase
958
964
  && event.payload.raw === input.raw));
959
965
  if (duplicate !== undefined)
960
966
  return duplicate.id;
961
- const run = store.getAgentRun(input.taskId, input.runId);
967
+ const run = store.getTurn(input.taskId, input.turnId);
962
968
  const sessionSet = store.getTaskRoleSessionSet(input.taskId, input.roleName);
963
969
  const sessionAgentId = run?.effective.agentId ?? sessionSet?.activeAgentId;
964
970
  const session = sessionAgentId === undefined
@@ -984,8 +990,8 @@ export class FileSchedulerStoreAdapter {
984
990
  : { sessionDisposition: input.sessionDisposition })
985
991
  });
986
992
  const event = createTaskEvent(store.nextEventId(input.taskId), input.taskId, "runtime.agent-error", {
987
- sourceEventId: `${input.runId}:${input.phase}`,
988
- runId: input.runId,
993
+ sourceEventId: `${input.turnId}:${input.phase}`,
994
+ turnId: input.turnId,
989
995
  roleName: input.roleName,
990
996
  agentId: run?.effective.agentId ?? session?.agentId ?? "unknown",
991
997
  adapterId: run?.effective.adapterId ?? session?.adapterId ?? "unknown",
@@ -1008,17 +1014,13 @@ export class FileSchedulerStoreAdapter {
1008
1014
  if (!leaderCannotReceive) {
1009
1015
  enqueueWork(store, { kind: "role", taskId: input.taskId, roleName: "leader" }, wakeReason("agent-error", event.id), now, [{ type: "event", taskId: input.taskId, id: event.id }], {
1010
1016
  source: driver?.id ?? input.source,
1011
- dedupeKey: `agent-error:${input.taskId}:${input.runId}:${input.phase}:${event.id}`,
1012
- deliveryMode: "followup",
1013
- lane: "normal"
1017
+ dedupeKey: `agent-error:${input.taskId}:${input.turnId}:${input.phase}:${event.id}`
1014
1018
  });
1015
1019
  }
1016
1020
  if (leaderCannotReceive) {
1017
1021
  enqueueWork(store, { kind: "operator" }, "leader-agent-error", now, [{ type: "event", taskId: input.taskId, id: event.id }], {
1018
1022
  source: driver?.id ?? input.source,
1019
- dedupeKey: `leader-agent-error:${input.taskId}:${input.runId}:${event.id}`,
1020
- deliveryMode: "followup",
1021
- lane: "normal"
1023
+ dedupeKey: `leader-agent-error:${input.taskId}:${input.turnId}:${event.id}`
1022
1024
  });
1023
1025
  }
1024
1026
  return event.id;
@@ -1028,7 +1030,7 @@ export class FileSchedulerStoreAdapter {
1028
1030
  * Records that a Leader wake was suppressed by scheduler single-flight
1029
1031
  * (the Role runtime lifecycle lane was busy). The wake stays durable and
1030
1032
  * is retried after the lane settles; this event is the audit trail that
1031
- * separates scheduler backpressure from real Run failures.
1033
+ * separates scheduler backpressure from real Turn failures.
1032
1034
  */
1033
1035
  recordWakeSuppression(taskId, reason, now) {
1034
1036
  this.store.transaction((store) => {
@@ -1048,7 +1050,7 @@ export class FileSchedulerStoreAdapter {
1048
1050
  *
1049
1051
  * f6: The wakeup targets the Leader role mailbox (not the Task mailbox)
1050
1052
  * and also queues a pending wakeup so processLeaderWakeups dispatches
1051
- * the Leader Run with the exact job-finished reason.
1053
+ * the Leader Turn with the exact job-finished reason.
1052
1054
  */
1053
1055
  transitionDurableJob(taskId, jobId, transition, now, wakeup) {
1054
1056
  return this.store.transaction((store) => {
@@ -1096,128 +1098,15 @@ export class FileSchedulerStoreAdapter {
1096
1098
  return { status: "claimed", processing: claimed.processing };
1097
1099
  });
1098
1100
  }
1099
- claimInputDelivery(input) {
1100
- return this.store.transaction((store) => {
1101
- const mailbox = store.getWorkMailbox(input.target);
1102
- if (mailbox === null)
1103
- return { status: "empty" };
1104
- if (mailbox.inputDelivery !== null) {
1105
- return { status: "delivery", delivery: mailbox.inputDelivery };
1106
- }
1107
- const pending = pendingLane(mailbox, input.lane);
1108
- if (pending === null)
1109
- return { status: "empty" };
1110
- if (input.target.kind === "role"
1111
- && input.executionRef.type === "run"
1112
- && input.executionRef.taskId === input.target.taskId) {
1113
- const sessions = store.getTaskRoleSessionSet(input.target.taskId, input.target.roleName);
1114
- const binding = sessions?.providerBinding;
1115
- if (sessions === null || sessions === undefined
1116
- || binding === null || binding === undefined
1117
- || binding.authority.owner !== "controller") {
1118
- throw new Error("Controller does not own the Provider writer authority.");
1119
- }
1120
- if (binding.turn !== null
1121
- && ["submitting", "accepted", "running", "delivery-unknown"]
1122
- .includes(binding.turn.status)) {
1123
- return { status: "busy" };
1124
- }
1125
- const conversation = currentProviderConversation(binding);
1126
- const activation = currentProviderActivation(binding);
1127
- if (input.providerFence === undefined
1128
- || conversation.conversationId !== input.providerFence.conversationId
1129
- || activation?.activationId !== input.providerFence.activationId) {
1130
- throw new Error("Provider input claim carries a stale Conversation/Activation fence.");
1131
- }
1132
- const next = beginProviderTurn(binding, {
1133
- runId: input.executionRef.id,
1134
- attemptId: input.attemptId,
1135
- authorityEpoch: binding.authority.epoch,
1136
- submittedAt: input.now.toISOString()
1137
- });
1138
- store.saveTaskRoleSessionSet(updateTaskRoleProviderRuntime(sessions, next, input.now));
1139
- }
1140
- const claimed = claimMailboxInputDelivery(mailbox, {
1141
- attemptId: input.attemptId,
1142
- lane: input.lane,
1143
- mode: input.mode,
1144
- owner: input.owner,
1145
- startedAt: input.now.toISOString(),
1146
- executionRef: input.executionRef,
1147
- providerFence: input.providerFence
1148
- });
1149
- store.saveWorkMailbox(claimed);
1150
- return { status: "claimed", delivery: claimed.inputDelivery };
1151
- });
1152
- }
1153
- markInputDeliveryPushed(target, attemptId, now) {
1154
- return this.store.transaction((store) => {
1155
- const mailbox = store.getWorkMailbox(target);
1156
- if (mailbox?.inputDelivery?.attemptId !== attemptId)
1157
- return false;
1158
- store.saveWorkMailbox(markMailboxInputDeliveryPushed(mailbox, attemptId, now));
1159
- return true;
1160
- });
1161
- }
1162
- completeInputDelivery(target, attemptId, now) {
1101
+ consumeWorkMailbox(target, expected) {
1163
1102
  return this.store.transaction((store) => {
1164
1103
  const mailbox = store.getWorkMailbox(target);
1165
- if (mailbox?.inputDelivery?.attemptId !== attemptId)
1104
+ if (mailbox?.pending === null || mailbox?.pending === undefined
1105
+ || mailbox.pending.fromSequence !== expected.fromSequence
1106
+ || mailbox.pending.toSequence < expected.toSequence) {
1166
1107
  return false;
1167
- const delivery = mailbox.inputDelivery;
1168
- if (target.kind === "role"
1169
- && delivery.executionRef.type === "run"
1170
- && delivery.executionRef.taskId === target.taskId) {
1171
- store.saveEvent(target.taskId, createTaskEvent(store.nextEventId(target.taskId), target.taskId, "run.input-delivered", {
1172
- attemptId: delivery.attemptId,
1173
- runId: delivery.executionRef.id,
1174
- ...(delivery.providerFence === undefined ? {} : {
1175
- conversationId: delivery.providerFence.conversationId,
1176
- activationId: delivery.providerFence.activationId,
1177
- ...(delivery.providerFence.nativeTurnId === undefined ? {} : {
1178
- nativeTurnId: delivery.providerFence.nativeTurnId
1179
- })
1180
- })
1181
- }, now));
1182
- }
1183
- store.saveWorkMailbox(completeMailboxInputDelivery(mailbox, attemptId, now));
1184
- return true;
1185
- });
1186
- }
1187
- releaseInputDelivery(target, attemptId, now) {
1188
- return this.store.transaction((store) => {
1189
- const mailbox = store.getWorkMailbox(target);
1190
- if (mailbox?.inputDelivery?.attemptId !== attemptId)
1191
- return false;
1192
- rejectSubmittingProviderTurn(store, target, mailbox.inputDelivery, now, "Provider write was not attempted.");
1193
- store.saveWorkMailbox(releaseMailboxInputDelivery(mailbox, attemptId));
1194
- return true;
1195
- });
1196
- }
1197
- resolveInputDeliveryNotAccepted(target, attemptId, now) {
1198
- return this.store.transaction((store) => {
1199
- const mailbox = store.getWorkMailbox(target);
1200
- if (mailbox?.inputDelivery?.attemptId !== attemptId)
1201
- return false;
1202
- rejectSubmittingProviderTurn(store, target, mailbox.inputDelivery, now, "Provider returned an exact negative acknowledgement.");
1203
- store.saveWorkMailbox(resolveMailboxInputDeliveryNotAccepted(mailbox, attemptId));
1204
- return true;
1205
- });
1206
- }
1207
- markInputDeliveryUnknown(target, attemptId, reason, now) {
1208
- return this.store.transaction((store) => {
1209
- const mailbox = store.getWorkMailbox(target);
1210
- if (mailbox?.inputDelivery?.attemptId !== attemptId)
1211
- return false;
1212
- const unknown = markMailboxInputDeliveryUnknown(mailbox, attemptId, reason, now);
1213
- markSubmittingProviderTurnUnknown(store, target, mailbox.inputDelivery, now, reason);
1214
- store.saveWorkMailbox(unknown);
1215
- if (target.kind === "role") {
1216
- const taskId = target.taskId;
1217
- const event = createTaskEvent(store.nextEventId(taskId), taskId, "run.input-delivery-unknown", { attemptId, reason }, now);
1218
- store.saveEvent(taskId, event);
1219
- routeRoleEvent(store, event, target.roleName, "run-input-delivery-unknown", now);
1220
1108
  }
1109
+ store.saveWorkMailbox(consumePendingBatch(mailbox, expected));
1221
1110
  return true;
1222
1111
  });
1223
1112
  }
@@ -1259,7 +1148,7 @@ export class FileSchedulerStoreAdapter {
1259
1148
  }
1260
1149
  mailbox = completeProcessing(mailbox, mailbox.processing.batchId);
1261
1150
  }
1262
- const pending = pendingLane(mailbox, "normal");
1151
+ const pending = mailbox.pending;
1263
1152
  if (pending !== null) {
1264
1153
  if (pending.reasons.length === 0
1265
1154
  || !pending.reasons.every(isRuntimeCleanupReason)) {
@@ -1298,7 +1187,7 @@ export class FileSchedulerStoreAdapter {
1298
1187
  return this.listRuntimeSessionCandidates().flatMap((candidate) => {
1299
1188
  if (hasRuntimeLifecycleWork(this.store.getWorkMailbox(runtimeLifecycleTarget(candidate.owner)))
1300
1189
  || (candidate.owner.scope === "task"
1301
- && this.store.getActiveAgentRun(candidate.owner.taskId, candidate.owner.roleName) !== null)) {
1190
+ && this.store.getActiveTurn(candidate.owner.taskId, candidate.owner.roleName) !== null)) {
1302
1191
  return [];
1303
1192
  }
1304
1193
  return [{
@@ -1366,12 +1255,12 @@ export class FileSchedulerStoreAdapter {
1366
1255
  || role.workspace !== input.role.workspace) {
1367
1256
  return "state-changed";
1368
1257
  }
1369
- if (!isDeepStrictEqual(input.run.effective, input.role.effective)) {
1258
+ if (!isDeepStrictEqual(input.turn.effective, input.role.effective)) {
1370
1259
  return "state-changed";
1371
1260
  }
1372
- if (store.getAgentRun(input.task.id, input.run.id) !== null)
1261
+ if (store.getTurn(input.task.id, input.turn.id) !== null)
1373
1262
  return "state-changed";
1374
- if (store.getActiveAgentRun(input.task.id, input.role.name) !== null)
1263
+ if (store.getActiveTurn(input.task.id, input.role.name) !== null)
1375
1264
  return "busy";
1376
1265
  const pending = store.getPendingWakeup(input.task.id);
1377
1266
  if (pending === null || !pendingWakeupsMatch(pending, input.wakeup)) {
@@ -1381,29 +1270,20 @@ export class FileSchedulerStoreAdapter {
1381
1270
  const mailbox = store.getWorkMailbox(target);
1382
1271
  if (mailbox === null || !mailboxHasPending(mailbox))
1383
1272
  return "state-changed";
1384
- if (mailbox.processing !== null)
1385
- return "busy";
1386
- if (store.peekNextAgentRunId(input.task.id) !== input.run.id) {
1273
+ if (store.peekNextTurnId(input.task.id) !== input.turn.id) {
1387
1274
  return "state-changed";
1388
1275
  }
1389
- const allocatedRunId = store.nextAgentRunId(input.task.id);
1390
- if (allocatedRunId !== input.run.id) {
1391
- throw new Error(`Leader Run allocation changed unexpectedly: ${input.task.id}.`);
1392
- }
1393
- const batchId = formatAgentRunReceiptId(input.task.id, input.run.id);
1394
- const claimed = bindExecution(claimPending(mailbox, {
1395
- batchId,
1396
- owner: "controller",
1397
- startedAt: input.now.toISOString()
1398
- }), batchId, { type: "run", taskId: input.task.id, id: input.run.id });
1399
- // SQLite stores the durable Run row and its active pointer separately;
1400
- // FileTaskStore happens to persist both from saveActiveAgentRun. Keep
1401
- // the adapter contract backend-neutral and make the two writes atomic.
1402
- store.saveAgentRun(input.run);
1403
- store.saveActiveAgentRun(input.run);
1404
- store.saveWorkMailbox(claimed);
1405
- bindTaskRoleRunInFlight(store, role, input.run, input.now);
1406
- store.saveEvent(input.task.id, createTaskEvent(store.nextEventId(input.task.id), input.task.id, "run.dispatched", runLaunchEventPayload(input.run), input.now));
1276
+ const allocatedTurnId = store.nextTurnId(input.task.id);
1277
+ if (allocatedTurnId !== input.turn.id) {
1278
+ throw new Error(`Leader Turn allocation changed unexpectedly: ${input.task.id}.`);
1279
+ }
1280
+ // The durable Turn row and its active pointer are separate projections;
1281
+ // keep both writes in the same storage transaction.
1282
+ store.saveTurn(input.turn);
1283
+ store.saveActiveTurn(input.turn);
1284
+ store.saveWorkMailbox(consumePendingBatch(mailbox));
1285
+ store.clearPendingWakeup(input.task.id);
1286
+ store.saveEvent(input.task.id, createTaskEvent(store.nextEventId(input.task.id), input.task.id, "turn.dispatched", turnLaunchEventPayload(input.turn), input.now));
1407
1287
  if (input.wakeId !== undefined && input.wakeFromCursor !== undefined) {
1408
1288
  if (store.peekNextTaskWakeId(input.task.id) !== input.wakeId) {
1409
1289
  return "state-changed";
@@ -1418,11 +1298,11 @@ export class FileSchedulerStoreAdapter {
1418
1298
  reasons: input.wakeup.reasons,
1419
1299
  fromCursor: input.wakeFromCursor,
1420
1300
  toCursor: input.now.toISOString(),
1421
- runId: input.run.id,
1301
+ turnId: input.turn.id,
1422
1302
  now: input.now
1423
1303
  }));
1424
1304
  }
1425
- if (input.run.mode !== "new"
1305
+ if (input.turn.mode !== "new"
1426
1306
  && input.session !== null
1427
1307
  && input.session.nativeSessionId !== undefined) {
1428
1308
  saveTaskSession(store, role, {
@@ -1434,73 +1314,48 @@ export class FileSchedulerStoreAdapter {
1434
1314
  return "claimed";
1435
1315
  });
1436
1316
  }
1437
- saveRoleRunDelivery(input) {
1438
- this.store.transaction((store) => {
1439
- const task = store.getTask(input.task.id);
1317
+ saveLeaderSteer(input) {
1318
+ return this.store.transaction((store) => {
1319
+ const task = store.getTask(input.taskId);
1320
+ const active = store.getActiveTurn(input.taskId, "leader");
1440
1321
  if (task === null || task.status !== "active" || task.executionGate.state !== "enabled") {
1441
- throw new Error(`Task is not active: ${input.task.id}.`);
1442
- }
1443
- const role = requireRole(store, input.task.id, input.role.name);
1444
- const active = store.getActiveAgentRun(input.task.id, input.role.name);
1445
- if (active === null || active.id !== input.run.id) {
1446
- throw new Error(`Active Agent run changed before delivery was persisted: ${input.run.id}.`);
1447
- }
1448
- let deliveryRun = active;
1449
- if (active.controlRequest?.state === "dispatching") {
1450
- deliveryRun = {
1451
- ...active,
1452
- controlRequest: markWorkflowOutcomeRequestDispatched(active.controlRequest)
1453
- };
1454
- store.saveAgentRun(deliveryRun);
1455
- store.saveEvent(task.id, createTaskEvent(store.nextEventId(task.id), task.id, "runtime.workflow-outcome-request-dispatched", {
1456
- runId: active.id,
1457
- roleName: active.roleName,
1458
- requestId: active.controlRequest.requestId,
1459
- receiptId: active.controlRequest.receiptId,
1460
- nativeTurnId: active.controlRequest.nativeTurnId
1461
- }, input.now));
1462
- }
1463
- // Transport success records prompt-pushed ONLY. Acceptance (deliveredAt)
1464
- // is written exclusively by an exact provider-accepted fold, never by the
1465
- // transport receipt — that removes the transport-to-delivered false path.
1466
- if (deliveryRun.pushedAt === undefined) {
1467
- const pushed = markAgentRunPushed(deliveryRun, input.now);
1468
- store.saveAgentRun(pushed);
1469
- markTaskRoleRunPushedInFlight(store, role, pushed, input.now);
1470
- store.saveEvent(task.id, createTaskEvent(store.nextEventId(task.id), task.id, "run.pushed", runLaunchEventPayload(pushed), input.now));
1471
- }
1472
- else {
1473
- const sessions = store.getTaskRoleSessionSet(input.task.id, input.role.name);
1474
- if (sessions?.inFlight?.runId === input.run.id
1475
- && sessions.inFlight.pushedAt === undefined) {
1476
- markTaskRoleRunPushedInFlight(store, role, deliveryRun, input.now);
1477
- }
1478
- }
1479
- if (input.session !== null && input.session.nativeSessionId !== undefined) {
1480
- const existing = store.getRoleSession(input.task.id, input.role.name);
1481
- if (existing?.agentId !== input.session.agentId
1482
- || existing.adapterId !== input.session.adapterId
1483
- || existing.nativeSessionId !== input.session.nativeSessionId
1484
- || (input.launchId !== undefined && existing.launchId !== input.launchId)
1485
- || existing.status !== "active") {
1486
- saveTaskSession(store, role, {
1487
- ...input.session,
1488
- nativeSessionId: input.session.nativeSessionId
1489
- }, "active", input.now, input.launchId);
1490
- }
1322
+ return "unavailable";
1491
1323
  }
1324
+ if (active === null || active.id !== input.turnId || active.status !== "active")
1325
+ return "busy";
1326
+ const target = { kind: "role", taskId: input.taskId, roleName: "leader" };
1327
+ const mailbox = store.getWorkMailbox(target);
1328
+ const processing = mailbox?.processing;
1329
+ if (mailbox === null
1330
+ || processing?.batchId !== input.batchId
1331
+ || processing.owner !== `leader-steer:${input.turnId}`)
1332
+ return "state-changed";
1333
+ const updated = appendTurnInput(active, input.input, input.now);
1334
+ store.saveTurn(updated);
1335
+ store.saveActiveTurn(updated);
1336
+ store.saveWorkMailbox(completeProcessing(mailbox, input.batchId));
1337
+ store.saveEvent(input.taskId, createTaskEvent(store.nextEventId(input.taskId), input.taskId, "turn.input-submitted", {
1338
+ turnId: active.id,
1339
+ sequence: String(updated.inputs.length),
1340
+ source: `${input.input.source.type}/${input.input.source.channel}`,
1341
+ reasons: processing.batch.reasons.join(",")
1342
+ }, input.now));
1343
+ return "claimed";
1492
1344
  });
1493
1345
  }
1494
- saveRoleRunPrepared(input) {
1346
+ saveRoleTurnPrepared(input) {
1495
1347
  this.store.transaction((store) => {
1496
1348
  const task = store.getTask(input.task.id);
1497
1349
  if (task === null || task.status !== "active" || task.executionGate.state !== "enabled") {
1498
1350
  throw new Error(`Task is not active: ${input.task.id}.`);
1499
1351
  }
1500
1352
  const role = requireRole(store, input.task.id, input.role.name);
1501
- const active = store.getActiveAgentRun(input.task.id, input.role.name);
1502
- if (active === null || active.id !== input.run.id) {
1503
- throw new Error(`Active Agent run changed before preparation was persisted: ${input.run.id}.`);
1353
+ const active = store.getActiveTurn(input.task.id, input.role.name);
1354
+ if (active === null || active.id !== input.turn.id) {
1355
+ throw new Error(`Active Turn changed before preparation was persisted: ${input.turn.id}.`);
1356
+ }
1357
+ if (store.getTaskRoleSessionSet(input.task.id, input.role.name) === null) {
1358
+ store.saveTaskRoleSessionSet(createRoleSessionSet({ scope: "task", taskId: input.task.id, roleName: input.role.name }, input.turn.effective.agentId, input.now));
1504
1359
  }
1505
1360
  if (input.session !== null && input.session.nativeSessionId !== undefined) {
1506
1361
  const existing = store.getRoleSession(input.task.id, input.role.name);
@@ -1522,71 +1377,51 @@ export class FileSchedulerStoreAdapter {
1522
1377
  }, "active", input.now, input.launchId);
1523
1378
  }
1524
1379
  }
1525
- bindTaskRoleRunInFlight(store, role, input.run, input.now);
1526
1380
  });
1527
1381
  }
1528
- saveRoleRunDeliveryFailure(input) {
1382
+ saveRoleTurnDeliveryFailure(input) {
1529
1383
  return this.store.transaction((store) => {
1530
1384
  const task = store.getTask(input.taskId);
1531
1385
  const role = store.getRole(input.taskId, input.roleName);
1532
- const active = store.getActiveAgentRun(input.taskId, input.roleName);
1386
+ const active = store.getActiveTurn(input.taskId, input.roleName);
1533
1387
  const sessions = store.getTaskRoleSessionSet(input.taskId, input.roleName);
1534
1388
  const session = sessions?.sessions[input.agentId];
1535
- const mailbox = store.getWorkMailbox({
1536
- kind: "role",
1537
- taskId: input.taskId,
1538
- roleName: input.roleName
1539
- });
1540
1389
  if (task === null
1541
1390
  || task.status !== "active"
1542
1391
  || task.executionGate.state !== "enabled"
1543
1392
  || role === null
1544
1393
  || active === null
1545
- || active.id !== input.runId
1394
+ || active.id !== input.turnId
1546
1395
  || active.status !== "active"
1547
- || active.pushedAt !== undefined
1548
1396
  || active.effective.agentId !== input.agentId
1549
- || active.effective.adapterId !== input.adapterId
1550
- || mailbox?.processing?.batchId !== input.mailboxBatchId
1551
- || mailbox.processing.executionRef?.type !== "run"
1552
- || mailbox.processing.executionRef.taskId !== input.taskId
1553
- || mailbox.processing.executionRef.id !== input.runId
1554
- || !preparedDeliveryFailureSessionFenceMatches(sessions, session, input, agentRunDeliveryReceiptId(active))) {
1397
+ || active.effective.adapterId !== input.adapterId) {
1555
1398
  return "state-changed";
1556
1399
  }
1557
1400
  const summary = input.summary
1558
- ?? `Role delivery failed conclusively before exact Run input acceptance: ${input.runId}.`;
1559
- const result = terminalizeExactTaskRun(store, {
1401
+ ?? `Role delivery failed conclusively before exact Turn input acceptance: ${input.turnId}.`;
1402
+ const result = terminalizeExactTaskTurn(store, {
1560
1403
  taskId: input.taskId,
1561
1404
  roleName: input.roleName,
1562
1405
  agentId: input.agentId,
1563
- runId: input.runId,
1564
- receiptId: agentRunDeliveryReceiptId(active),
1406
+ turnId: input.turnId,
1565
1407
  ...(session?.nativeSessionId === undefined
1566
1408
  ? {}
1567
1409
  : { nativeSessionId: session.nativeSessionId }),
1568
1410
  ...(input.launchId === undefined ? {} : { launchId: input.launchId }),
1569
- outcome: { status: "failed", summary }
1411
+ outcome: { status: "failed", summary, failureReason: input.failureReason }
1570
1412
  }, input.now);
1571
- if (result.disposition !== "applied" || result.run === null) {
1413
+ if (result.disposition !== "applied" || result.turn === null) {
1572
1414
  return "state-changed";
1573
1415
  }
1574
- if (input.cleanupRequired !== false) {
1575
- enqueueWork(store, runtimeLifecycleTarget({
1576
- scope: "task",
1577
- taskId: input.taskId,
1578
- roleName: input.roleName
1579
- }), RUNTIME_HOST_DETACH_REQUIRED_REASON, input.now, [{ type: "task", id: input.taskId }]);
1580
- }
1581
- const terminal = result.run;
1416
+ const terminal = result.turn;
1582
1417
  const deliveryFailureEvent = createTaskEvent(store.nextEventId(input.taskId), input.taskId, "runtime.role-delivery-failed", {
1583
- runId: terminal.id,
1418
+ turnId: terminal.id,
1584
1419
  roleName: input.roleName,
1585
1420
  outcome: terminal.status
1586
1421
  }, input.now);
1587
1422
  store.saveEvent(input.taskId, deliveryFailureEvent);
1588
1423
  if (input.roleName !== "leader") {
1589
- routeRoleEvent(store, deliveryFailureEvent, input.roleName, terminal.purpose === "review" ? "review-failed" : "role-run-failed", input.now);
1424
+ routeRoleEvent(store, deliveryFailureEvent, input.roleName, terminal.purpose === "review" ? "review-failed" : "role-turn-failed", input.now);
1590
1425
  }
1591
1426
  else {
1592
1427
  store.saveLeaderFailure(recordLeaderFailure(input.taskId, session?.nativeSessionId ?? "(unregistered)", summary, input.now, store.getLeaderFailure(input.taskId)));
@@ -1620,7 +1455,7 @@ export class FileSchedulerStoreAdapter {
1620
1455
  : []);
1621
1456
  const queued = store.getWorkMailbox(target);
1622
1457
  if (queued === null
1623
- || pendingLane(queued, "normal") === null
1458
+ || queued.pending === null
1624
1459
  || queued.processing !== null) {
1625
1460
  throw new Error("Runtime launch reservation could not be queued.");
1626
1461
  }
@@ -1704,7 +1539,7 @@ export class FileSchedulerStoreAdapter {
1704
1539
  * The exact reservation is authoritative. If its matching Hook won the
1705
1540
  * transaction race and already cleared that reservation, the session
1706
1541
  * fallback is permitted only while the same Agent/native identity remains
1707
- * current and no later lifecycle work or Task Run exists.
1542
+ * current and no later lifecycle work or Task Turn exists.
1708
1543
  */
1709
1544
  settleStoppedRuntimeLaunch(input, now = new Date()) {
1710
1545
  return this.store.transaction((store) => {
@@ -1722,7 +1557,7 @@ export class FileSchedulerStoreAdapter {
1722
1557
  if (hasRuntimeLifecycleWork(mailbox))
1723
1558
  return false;
1724
1559
  if (input.owner.scope === "task"
1725
- && store.getActiveAgentRun(input.owner.taskId, input.owner.roleName) !== null) {
1560
+ && store.getActiveTurn(input.owner.taskId, input.owner.roleName) !== null) {
1726
1561
  return false;
1727
1562
  }
1728
1563
  const sessions = runtimeOwnerSessionSet(store, input.owner);
@@ -1769,57 +1604,43 @@ export class FileSchedulerStoreAdapter {
1769
1604
  }
1770
1605
  const providerTurn = sessions?.providerBinding?.turn;
1771
1606
  const canonicalTurnId = sessions === null || sessions === undefined
1772
- ? input.turnId
1773
- : canonicalStructuredProviderTurnId(sessions, input.turnId, input.attemptId);
1774
- const recordedProviderTurn = input.runId !== undefined
1775
- && providerTurn?.runId === input.runId
1776
- && providerTurn.turnId === canonicalTurnId;
1607
+ ? input.nativeTurnId
1608
+ : canonicalStructuredProviderTurnId(sessions, input.nativeTurnId, input.attemptId);
1609
+ const recordedProviderTurn = providerTurn !== null
1610
+ && providerTurn !== undefined
1611
+ && providerTurn.nativeTurnId === input.nativeTurnId
1612
+ && providerTurn.nativeTurnId === canonicalTurnId;
1777
1613
  if (recordedProviderTurn) {
1778
- const run = this.store.getAgentRun(input.taskId, input.runId);
1779
- return run === null
1780
- ? "obsolete"
1781
- : run.pushedAt === undefined ? "deferred" : "apply";
1782
- }
1783
- // A normal explicit CLI yield may precede its native Hook; that Hook still
1784
- // owns the turn fact. A forced cleanup boundary or stopped process instead
1785
- // makes the old generation obsolete.
1786
- if (sessions?.inFlight === null || sessions === null) {
1787
- const cleanup = hasRuntimeCleanupObligation(this.store.getWorkMailbox(runtimeLifecycleTarget({
1788
- scope: "task",
1789
- taskId: input.taskId,
1790
- roleName: input.roleName
1791
- })));
1792
- return cleanup || isObsoleteTerminalRuntimeRun(this.store, input)
1793
- ? "obsolete"
1794
- : "apply";
1795
- }
1796
- if (input.runId === undefined || sessions.inFlight.runId !== input.runId) {
1797
- return "obsolete";
1614
+ if (input.turnId === undefined)
1615
+ return "apply";
1616
+ const run = this.store.getTurn(input.taskId, input.turnId);
1617
+ return run === null ? "obsolete" : "apply";
1798
1618
  }
1799
- // Provider completion is not a transport acknowledgement. Retain the
1800
- // immutable Hook until the exact pane receipt independently reconciles
1801
- // run.pushed; never infer transport from Driver activity/terminal facts.
1802
- return sessions.inFlight.pushedAt === undefined ? "deferred" : "apply";
1619
+ const active = this.store.getActiveTurn(input.taskId, input.roleName);
1620
+ if (input.turnId === undefined)
1621
+ return active === null ? "apply" : "obsolete";
1622
+ return active?.id === input.turnId ? "apply" : "obsolete";
1803
1623
  }
1804
1624
  observeRuntimeTurnCompleted(input, now = new Date()) {
1805
1625
  return this.store.transaction((store) => {
1806
1626
  const task = store.getTask(input.taskId);
1807
1627
  if (task === null)
1808
1628
  throw new Error(`Task not found: ${input.taskId}.`);
1809
- if (task.status !== "active" || task.executionGate.state !== "enabled") {
1810
- throw new Error(`Cannot complete a runtime turn for inactive Task: ${input.taskId}.`);
1629
+ if (task.status !== "active" && task.status !== "completed") {
1630
+ throw new Error(`Cannot complete a runtime turn for unavailable Task: ${input.taskId}.`);
1811
1631
  }
1812
1632
  const role = requireRole(store, input.taskId, input.roleName);
1813
1633
  let sessions = store.getTaskRoleSessionSet(input.taskId, input.roleName)
1814
1634
  ?? createRoleSessionSet({ scope: "task", taskId: input.taskId, roleName: input.roleName }, input.agentId, now);
1815
- const canonicalTurnId = canonicalStructuredProviderTurnId(sessions, input.turnId, input.attemptId);
1635
+ const canonicalTurnId = canonicalStructuredProviderTurnId(sessions, input.nativeTurnId, input.attemptId);
1816
1636
  const providerTurn = sessions.providerBinding?.turn;
1817
- const recordedProviderTurn = input.runId !== undefined
1818
- && providerTurn?.runId === input.runId
1819
- && providerTurn.turnId === canonicalTurnId;
1820
- const observedRun = input.runId === undefined
1821
- ? null
1822
- : store.getAgentRun(input.taskId, input.runId);
1637
+ const recordedProviderTurn = providerTurn !== null
1638
+ && providerTurn !== undefined
1639
+ && providerTurn.nativeTurnId === input.nativeTurnId
1640
+ && providerTurn.nativeTurnId === canonicalTurnId;
1641
+ const observedTurn = input.turnId === undefined
1642
+ ? store.listTurns(input.taskId).find((turn) => (turn.result?.provider?.nativeTurnId === canonicalTurnId)) ?? null
1643
+ : store.getTurn(input.taskId, input.turnId);
1823
1644
  const existing = sessions.sessions[input.agentId];
1824
1645
  const owner = {
1825
1646
  scope: "task",
@@ -1837,30 +1658,21 @@ export class FileSchedulerStoreAdapter {
1837
1658
  }
1838
1659
  if (effectiveExisting !== undefined
1839
1660
  && hasRecentTurnId(effectiveExisting.recentCompletedTurnIds, canonicalTurnId)) {
1840
- return { session: effectiveExisting, duplicate: true };
1661
+ return {
1662
+ session: effectiveExisting,
1663
+ duplicate: true,
1664
+ ...(observedTurn === null ? {} : { turn: observedTurn })
1665
+ };
1841
1666
  }
1842
1667
  if (!recordedProviderTurn
1843
- && sessions.inFlight === null
1844
1668
  && existing !== undefined
1845
1669
  && (hasRuntimeCleanupObligation(store.getWorkMailbox(runtimeLifecycleTarget(owner)))
1846
- || isObsoleteTerminalRuntimeRun(store, input))) {
1670
+ || isObsoleteTerminalRuntimeTurn(store, input))) {
1847
1671
  return { session: existing, duplicate: false, disposition: "obsolete" };
1848
1672
  }
1849
- // Classification is only an optimization. Revalidate the Run inside the
1850
- // authoritative transaction before a Hook may claim a native identity.
1851
- if (!recordedProviderTurn
1852
- && sessions.inFlight !== null
1853
- && (input.runId === undefined
1854
- || sessions.inFlight.runId !== input.runId)) {
1855
- if (effectiveExisting === undefined) {
1856
- throw new Error("Runtime turn completion no longer matches the in-flight Run.");
1857
- }
1858
- return { session: effectiveExisting, duplicate: false };
1859
- }
1860
- if (recordedProviderTurn
1861
- ? observedRun?.pushedAt === undefined
1862
- : sessions.inFlight !== null && sessions.inFlight.pushedAt === undefined) {
1863
- throw new Error("Runtime turn completion requires an independently committed transport receipt.");
1673
+ if (!recordedProviderTurn && input.turnId !== undefined
1674
+ && observedTurn?.id !== input.turnId) {
1675
+ return { session: effectiveExisting, duplicate: false, disposition: "obsolete" };
1864
1676
  }
1865
1677
  const sessionStatus = effectiveExisting?.status ?? "active";
1866
1678
  sessions = recordRoleAgentSession(sessions, {
@@ -1876,16 +1688,6 @@ export class FileSchedulerStoreAdapter {
1876
1688
  effective
1877
1689
  }, now);
1878
1690
  sessions = settleStructuredProviderTurn(sessions, canonicalTurnId, input.providerStatus ?? "completed", now);
1879
- if (sessions.inFlight === null) {
1880
- sessions = rememberRoleAgentCompletedTurn(sessions, input.agentId, input.nativeSessionId, canonicalTurnId, now);
1881
- store.saveTaskRoleSessionSet(sessions);
1882
- completeRuntimeHookReservation(store, owner, input.launchId);
1883
- return { session: sessions.sessions[input.agentId], duplicate: false };
1884
- }
1885
- // A provider Turn is an activation boundary, not a Yui Run outcome.
1886
- // Preserve the Run fence and make the fixed native Session available for
1887
- // a later mailbox batch. Provider-owned background subagents may finish
1888
- // after this point and Yui must not invent a failure while waiting.
1889
1691
  sessions = recordTaskRoleTurnBoundary(sessions, {
1890
1692
  agentId: input.agentId,
1891
1693
  nativeSessionId: input.nativeSessionId,
@@ -1893,9 +1695,89 @@ export class FileSchedulerStoreAdapter {
1893
1695
  }, now);
1894
1696
  store.saveTaskRoleSessionSet(sessions);
1895
1697
  completeRuntimeHookReservation(store, owner, input.launchId);
1698
+ let terminalTurn;
1699
+ if (recordedProviderTurn && observedTurn?.status === "active") {
1700
+ const binding = sessions.providerBinding;
1701
+ const conversation = currentProviderConversation(binding);
1702
+ const activation = binding.activations.find((entry) => (entry.activationId === input.launchId
1703
+ || (entry.conversationId === conversation.conversationId && entry.status === "active")));
1704
+ if (activation === undefined) {
1705
+ throw new Error("Provider Turn result has no matching Activation.");
1706
+ }
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
+ };
1727
+ }
1728
+ catch {
1729
+ reviewResult = { report: input.summary, checks: [] };
1730
+ }
1731
+ }
1732
+ const terminalized = terminalizeExactTaskTurn(store, {
1733
+ taskId: input.taskId,
1734
+ roleName: input.roleName,
1735
+ agentId: input.agentId,
1736
+ turnId: observedTurn.id,
1737
+ nativeSessionId: input.nativeSessionId,
1738
+ ...(input.launchId === undefined ? {} : { launchId: input.launchId }),
1739
+ 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
+ }),
1749
+ provider: {
1750
+ providerNamespace: binding.providerNamespace,
1751
+ accountScope: binding.accountScope,
1752
+ conversationId: conversation.conversationId,
1753
+ activationId: activation.activationId,
1754
+ nativeTurnId: canonicalTurnId,
1755
+ status: providerStatus
1756
+ }
1757
+ },
1758
+ ...(reviewResult === undefined ? {} : { reviewResult })
1759
+ }, now);
1760
+ if (terminalized.disposition !== "applied" || terminalized.turn === null) {
1761
+ throw new Error(`Provider Turn terminal could not complete its exact Turn: ${terminalized.reason ?? "obsolete"}.`);
1762
+ }
1763
+ terminalTurn = terminalized.turn;
1764
+ const event = createTaskEvent(store.nextEventId(input.taskId), input.taskId, terminalTurn.status === "completed" ? "turn.completed" : "turn.failed", {
1765
+ turnId: terminalTurn.id,
1766
+ roleName: terminalTurn.roleName,
1767
+ providerTurnId: canonicalTurnId,
1768
+ providerStatus,
1769
+ summary: input.summary
1770
+ }, now);
1771
+ store.saveEvent(input.taskId, event);
1772
+ if (terminalTurn.roleName !== "leader"
1773
+ && (providerStatus !== "completed" || !providerGoalContinues(binding.goal))) {
1774
+ routeRoleEvent(store, event, terminalTurn.roleName, terminalTurn.purpose === "review" ? "review-result" : "role-turn-result", now);
1775
+ }
1776
+ }
1896
1777
  return {
1897
1778
  session: sessions.sessions[input.agentId],
1898
- duplicate: false
1779
+ duplicate: false,
1780
+ ...(terminalTurn === undefined ? {} : { turn: terminalTurn })
1899
1781
  };
1900
1782
  });
1901
1783
  }
@@ -1904,7 +1786,7 @@ export class FileSchedulerStoreAdapter {
1904
1786
  const identity = [
1905
1787
  input.taskId,
1906
1788
  input.roleName,
1907
- input.runId,
1789
+ input.turnId,
1908
1790
  input.launchId ?? "unknown-launch",
1909
1791
  String(input.deadStatus ?? "unknown-status")
1910
1792
  ].join("\0");
@@ -1925,8 +1807,8 @@ export class FileSchedulerStoreAdapter {
1925
1807
  hostInstanceId: `tmux-${input.launchId ?? input.roleName}`,
1926
1808
  taskId: input.taskId,
1927
1809
  roleName: input.roleName,
1928
- runId: input.runId,
1929
- launchId: input.launchId ?? `unknown-${input.runId}`,
1810
+ turnId: input.turnId,
1811
+ launchId: input.launchId ?? `unknown-${input.turnId}`,
1930
1812
  ...(input.nativeSessionId === undefined
1931
1813
  ? {}
1932
1814
  : { nativeSessionId: input.nativeSessionId }),
@@ -1948,7 +1830,7 @@ export class FileSchedulerStoreAdapter {
1948
1830
  });
1949
1831
  }
1950
1832
  /**
1951
- * Issue 04: reopens each due retry on its original Native Session. A Run
1833
+ * Issue 04: reopens each due retry on its original Native Session. A Turn
1952
1834
  * whose Session is proven dead terminalizes with an exact replacement
1953
1835
  * blocker; a live Session is reopened for the existing delivery path, which
1954
1836
  * re-pushes the exact same input in the same pass.
@@ -1957,8 +1839,7 @@ export class FileSchedulerStoreAdapter {
1957
1839
  return this.store.transaction((store) => {
1958
1840
  const taskId = input.fence.taskId;
1959
1841
  const sessions = store.getTaskRoleSessionSet(taskId, input.fence.roleName);
1960
- const run = store.getAgentRun(taskId, input.fence.runId);
1961
- if (sessions === null || sessions.providerBinding === null || run === null
1842
+ if (sessions === null || sessions.providerBinding === null
1962
1843
  || input.fence.conversationId === undefined) {
1963
1844
  recordCanonicalObservationObsolete(store, input, "provider-binding-missing", now);
1964
1845
  return "obsolete";
@@ -1997,6 +1878,33 @@ export class FileSchedulerStoreAdapter {
1997
1878
  return "applied";
1998
1879
  });
1999
1880
  }
1881
+ observeProviderGoal(input, now) {
1882
+ return this.store.transaction((store) => {
1883
+ const taskId = input.fence.taskId;
1884
+ const sessions = store.getTaskRoleSessionSet(taskId, input.fence.roleName);
1885
+ const binding = sessions?.providerBinding;
1886
+ if (sessions === null || sessions === undefined || binding === null || binding === undefined
1887
+ || input.fence.conversationId !== currentProviderConversation(binding).conversationId) {
1888
+ recordCanonicalObservationObsolete(store, input, "provider-goal-session-mismatch", now);
1889
+ return "obsolete";
1890
+ }
1891
+ const updated = input.kind === "goal.cleared"
1892
+ ? clearProviderGoal(binding)
1893
+ : updateProviderGoal(binding, {
1894
+ status: input.payload.goalStatus,
1895
+ objective: input.payload.goalObjective,
1896
+ updatedAt: input.payload.goalUpdatedAt,
1897
+ ...(input.payload.goalNativeTurnId === undefined
1898
+ ? {}
1899
+ : { nativeTurnId: input.payload.goalNativeTurnId }),
1900
+ ...(input.payload.goalTokenBudget === undefined
1901
+ ? {}
1902
+ : { tokenBudget: input.payload.goalTokenBudget })
1903
+ });
1904
+ store.saveTaskRoleSessionSet(updateTaskRoleProviderRuntime(sessions, updated, now));
1905
+ return "applied";
1906
+ });
1907
+ }
2000
1908
  observeRuntimeSession(input, adapterId, now) {
2001
1909
  return this.store.transaction((store) => {
2002
1910
  const event = createCanonicalLifecycleEvent({
@@ -2032,7 +1940,7 @@ export class FileSchedulerStoreAdapter {
2032
1940
  const taskId = input.fence.taskId;
2033
1941
  const role = store.getRole(taskId, input.fence.roleName);
2034
1942
  const sessions = store.getTaskRoleSessionSet(taskId, input.fence.roleName);
2035
- const run = store.getAgentRun(taskId, input.fence.runId);
1943
+ const run = store.getTurn(taskId, input.fence.turnId);
2036
1944
  if (role === null || sessions === null || run === null) {
2037
1945
  recordCanonicalObservationObsolete(store, input, "bind-state-missing", now);
2038
1946
  return "obsolete";
@@ -2069,20 +1977,11 @@ export class FileSchedulerStoreAdapter {
2069
1977
  : replaceTaskRoleAgentSession(sessions, sessionInput, now);
2070
1978
  const withProvider = bindOrSupersedeProviderRuntime(bound, input, now, replacementBasis ?? undefined);
2071
1979
  store.saveTaskRoleSessionSet(withProvider);
2072
- const driver = this.drivers.require(input.fence.driverId);
2073
- if (driver.capabilities.observation.sessionBootstrap === "discovered"
2074
- && run.pushedAt === undefined) {
2075
- const pushed = markAgentRunPushed(run, now);
2076
- store.saveAgentRun(pushed);
2077
- markTaskRoleRunPushedInFlight(store, role, pushed, now);
2078
- store.saveEvent(taskId, createTaskEvent(store.nextEventId(taskId), taskId, "run.pushed", runLaunchEventPayload(pushed), now));
2079
- }
2080
1980
  completeRuntimeHookReservation(store, { scope: "task", taskId, roleName: input.fence.roleName }, input.fence.launchId);
2081
1981
  }
2082
1982
  const current = store.getTaskRoleSessionSet(input.fence.taskId, input.fence.roleName);
2083
1983
  const currentSession = current?.sessions[input.fence.agentId];
2084
1984
  if (current !== null && current !== undefined
2085
- && current.inFlight !== null
2086
1985
  && currentSession?.nativeSessionId === input.fence.nativeSessionId) {
2087
1986
  store.saveTaskRoleSessionSet(bindOrSupersedeProviderRuntime(current, input, now));
2088
1987
  }
@@ -2094,72 +1993,104 @@ export class FileSchedulerStoreAdapter {
2094
1993
  /** Provider acceptance is canonical before storage and remains receipt-fenced. */
2095
1994
  observeRuntimePromptAccepted(input, adapterId, now) {
2096
1995
  return this.store.transaction((store) => {
2097
- const roleTarget = {
2098
- kind: "role",
2099
- taskId: input.fence.taskId,
2100
- roleName: input.fence.roleName
2101
- };
2102
- const mailbox = store.getWorkMailbox(roleTarget);
2103
- const processing = mailbox?.processing;
2104
- const inputDelivery = mailbox?.inputDelivery;
2105
- const continuation = inputDelivery !== null
2106
- && inputDelivery !== undefined
2107
- && inputDelivery.attemptId === input.fence.receiptId
2108
- && inputDelivery.executionRef.type === "run"
2109
- && inputDelivery.executionRef.taskId === input.fence.taskId
2110
- && inputDelivery.executionRef.id === input.fence.runId
2111
- && inputDelivery.attemptId !== formatAgentRunReceiptId(input.fence.taskId, input.fence.runId);
2112
- const humanAttemptId = input.fence.receiptId;
2113
- if (humanAttemptId !== undefined && humanAttemptId.startsWith("human:")) {
2114
- const active = store.getActiveAgentRun(input.fence.taskId, input.fence.roleName);
2115
- const sessions = store.getTaskRoleSessionSet(input.fence.taskId, input.fence.roleName);
1996
+ const continuation = input.fence.receiptId?.startsWith("turn-input:") === true;
1997
+ const ordinaryAttemptId = input.fence.receiptId;
1998
+ if (input.fence.turnId === undefined
1999
+ && ordinaryAttemptId?.startsWith("direct:") === true) {
2000
+ const taskId = input.fence.taskId;
2001
+ const sessions = store.getTaskRoleSessionSet(taskId, input.fence.roleName);
2116
2002
  const session = sessions?.sessions[input.fence.agentId];
2117
2003
  const binding = sessions?.providerBinding;
2118
- if (active === null || active.id !== input.fence.runId
2119
- || sessions === null || sessions === undefined
2004
+ const nativeTurnId = input.fence.nativeTurnId;
2005
+ const active = store.getActiveTurn(taskId, input.fence.roleName);
2006
+ if (sessions === null || sessions === undefined
2120
2007
  || binding === null || binding === undefined
2121
- || binding.authority.owner !== "human"
2122
- || !humanAttemptId.startsWith(`human:${binding.authority.holderId}:`)
2123
- || binding.turn?.attemptId !== humanAttemptId
2124
2008
  || session?.launchId !== input.fence.launchId
2125
- || session.nativeSessionId !== input.fence.nativeSessionId) {
2126
- recordCanonicalObservationObsolete(store, input, "human-turn-fence-mismatch", now);
2009
+ || session.nativeSessionId !== input.fence.nativeSessionId
2010
+ || currentProviderConversation(binding).conversationId !== input.fence.nativeSessionId) {
2011
+ recordCanonicalObservationObsolete(store, input, "direct-turn-session-mismatch", now);
2127
2012
  return "obsolete";
2128
2013
  }
2129
- store.saveTaskRoleSessionSet(recordStructuredProviderAcceptance(sessions, input, now));
2130
- store.saveEvent(input.fence.taskId, createTaskEvent(store.nextEventId(input.fence.taskId), input.fence.taskId, "run.human-input-delivered", {
2131
- attemptId: humanAttemptId,
2132
- runId: active.id,
2133
- authorityEpoch: String(binding.authority.epoch),
2134
- nativeTurnId: input.fence.nativeTurnId
2135
- }, now));
2014
+ if (binding.turn?.attemptId === ordinaryAttemptId
2015
+ && binding.turn.nativeTurnId === nativeTurnId
2016
+ && binding.turn.turnId !== undefined
2017
+ && active?.id === binding.turn.turnId) {
2018
+ return "applied";
2019
+ }
2020
+ if (active !== null) {
2021
+ recordCanonicalObservationObsolete(store, input, "direct-turn-conflicts-with-active-turn", now);
2022
+ return "obsolete";
2023
+ }
2024
+ const turnId = store.nextTurnId(taskId);
2025
+ const goalContinuation = input.payload.input === undefined
2026
+ && providerGoalContinues(binding.goal);
2027
+ const direct = createTurn(turnId, taskId, input.fence.roleName, "resume", createTurnInput({
2028
+ source: goalContinuation
2029
+ ? { type: "provider", channel: "goal-continuation" }
2030
+ : { type: "user", channel: "direct" },
2031
+ ...(input.payload.input === undefined
2032
+ ? {}
2033
+ : { directive: input.payload.input }),
2034
+ deltaRefIds: []
2035
+ }), now, { effective: session.effective });
2036
+ const submittedAt = input.observedAt ?? input.receivedAt;
2037
+ const accepted = acceptProviderTurn(beginProviderTurn(binding, {
2038
+ turnId,
2039
+ attemptId: ordinaryAttemptId,
2040
+ authorityEpoch: binding.authority.epoch,
2041
+ submittedAt
2042
+ }), {
2043
+ attemptId: ordinaryAttemptId,
2044
+ nativeTurnId,
2045
+ acceptedAt: submittedAt
2046
+ });
2047
+ store.saveTurn(direct);
2048
+ store.saveActiveTurn(direct);
2049
+ store.saveTaskRoleSessionSet(updateTaskRoleProviderRuntime(sessions, accepted, now));
2050
+ store.saveEvent(taskId, createTaskEvent(store.nextEventId(taskId), taskId, "turn.dispatched", turnLaunchEventPayload(direct), now));
2136
2051
  return "applied";
2137
2052
  }
2053
+ if (ordinaryAttemptId !== undefined) {
2054
+ const sessions = store.getTaskRoleSessionSet(input.fence.taskId, input.fence.roleName);
2055
+ const session = sessions?.sessions[input.fence.agentId];
2056
+ const binding = sessions?.providerBinding;
2057
+ if (sessions !== null && sessions !== undefined
2058
+ && binding !== null && binding !== undefined
2059
+ && binding.turn?.turnId === undefined
2060
+ && binding.turn?.attemptId === ordinaryAttemptId) {
2061
+ if (session?.launchId !== input.fence.launchId
2062
+ || session.nativeSessionId !== input.fence.nativeSessionId) {
2063
+ recordCanonicalObservationObsolete(store, input, "ordinary-turn-session-mismatch", now);
2064
+ return "obsolete";
2065
+ }
2066
+ store.saveTaskRoleSessionSet(recordStructuredProviderAcceptance(sessions, input, now));
2067
+ return "applied";
2068
+ }
2069
+ }
2138
2070
  if (continuation) {
2139
- const active = store.getActiveAgentRun(input.fence.taskId, input.fence.roleName);
2071
+ const active = store.getActiveTurn(input.fence.taskId, input.fence.roleName);
2140
2072
  const sessions = store.getTaskRoleSessionSet(input.fence.taskId, input.fence.roleName);
2141
2073
  const session = sessions?.sessions[input.fence.agentId];
2142
2074
  if (active === null
2143
- || active.id !== input.fence.runId
2075
+ || active.id !== input.fence.turnId
2144
2076
  || active.status !== "active"
2145
- || active.pushedAt === undefined
2146
- || sessions?.inFlight?.runId !== active.id
2077
+ || sessions === null
2078
+ || sessions === undefined
2147
2079
  || session?.launchId !== input.fence.launchId
2148
2080
  || session.nativeSessionId !== input.fence.nativeSessionId) {
2149
2081
  recordCanonicalObservationObsolete(store, input, "continuation-fence-mismatch", now);
2150
2082
  return "obsolete";
2151
2083
  }
2152
2084
  store.saveTaskRoleSessionSet(recordStructuredProviderAcceptance(sessions, input, now));
2153
- store.saveEvent(input.fence.taskId, createTaskEvent(store.nextEventId(input.fence.taskId), input.fence.taskId, "run.input-delivered", {
2154
- attemptId: inputDelivery.attemptId,
2155
- runId: active.id,
2085
+ store.saveEvent(input.fence.taskId, createTaskEvent(store.nextEventId(input.fence.taskId), input.fence.taskId, "turn.input-delivered", {
2086
+ attemptId: input.fence.receiptId,
2087
+ turnId: active.id,
2156
2088
  conversationId: input.fence.conversationId ?? input.fence.nativeSessionId,
2157
2089
  activationId: input.fence.activationId ?? input.fence.launchId,
2158
2090
  ...(input.fence.nativeTurnId === undefined
2159
2091
  ? {}
2160
2092
  : { nativeTurnId: input.fence.nativeTurnId })
2161
2093
  }, now));
2162
- store.saveWorkMailbox(completeMailboxInputDelivery(mailbox, inputDelivery.attemptId, now));
2163
2094
  return "applied";
2164
2095
  }
2165
2096
  const event = createCanonicalLifecycleEvent({
@@ -2177,44 +2108,26 @@ export class FileSchedulerStoreAdapter {
2177
2108
  return "deferred";
2178
2109
  if (decision.kind === "idempotent")
2179
2110
  return "applied";
2180
- const active = store.getActiveAgentRun(input.fence.taskId, input.fence.roleName);
2111
+ const active = store.getActiveTurn(input.fence.taskId, input.fence.roleName);
2181
2112
  if (active === null
2182
- || active.id !== input.fence.runId
2113
+ || active.id !== input.fence.turnId
2183
2114
  || active.status !== "active") {
2184
- recordCanonicalObservationObsolete(store, input, "run-not-active", now);
2185
- return "obsolete";
2186
- }
2187
- const role = store.getRole(input.fence.taskId, input.fence.roleName);
2188
- if (role === null) {
2189
- recordCanonicalObservationObsolete(store, input, "role-missing", now);
2115
+ recordCanonicalObservationObsolete(store, input, "turn-not-active", now);
2190
2116
  return "obsolete";
2191
2117
  }
2192
2118
  const sessions = store.getTaskRoleSessionSet(input.fence.taskId, input.fence.roleName);
2193
2119
  if (sessions !== null) {
2194
2120
  store.saveTaskRoleSessionSet(recordStructuredProviderAcceptance(sessions, input, now));
2195
2121
  }
2196
- if (active.deliveredAt === undefined) {
2197
- const delivered = markAgentRunDelivered(active, now);
2198
- store.saveAgentRun(delivered);
2199
- markTaskRoleRunDeliveredInFlight(store, role, delivered, now);
2200
- store.saveEvent(input.fence.taskId, createTaskEvent(store.nextEventId(input.fence.taskId), input.fence.taskId, "run.delivered", runLaunchEventPayload(delivered), now));
2201
- }
2202
- if (processing !== null && processing !== undefined
2203
- && processing.batchId === input.fence.receiptId
2204
- && processing.executionRef?.type === "run"
2205
- && processing.executionRef.taskId === input.fence.taskId
2206
- && processing.executionRef.id === input.fence.runId) {
2207
- store.saveWorkMailbox(completeProcessing(mailbox, processing.batchId));
2208
- }
2209
2122
  return "applied";
2210
2123
  });
2211
2124
  }
2212
2125
  foldRuntimeLifecycleEvent(store, event, observation) {
2213
- const expectation = this.projectRunExpectation(store, event.fence, observation.fence.runId);
2126
+ const expectation = this.projectTurnExpectation(store, event.fence, observation.fence.turnId);
2214
2127
  if (expectation === null) {
2215
2128
  return preallocatedRuntimeReadyAwaitingProjection(store, observation, this.drivers)
2216
2129
  ? { kind: "apply", outcome: { outcome: "mark-ready", preInputReady: true } }
2217
- : { kind: "obsolete", reason: "run-or-role-missing" };
2130
+ : { kind: "obsolete", reason: "turn-or-role-missing" };
2218
2131
  }
2219
2132
  const outcome = foldCanonicalLifecycleEvent(event, expectation);
2220
2133
  switch (outcome.outcome) {
@@ -2230,14 +2143,14 @@ export class FileSchedulerStoreAdapter {
2230
2143
  return { kind: "apply", outcome };
2231
2144
  }
2232
2145
  }
2233
- /** Reads durable Run + session state into the pure fold's expectation shape. */
2234
- projectRunExpectation(store, fence, runId) {
2146
+ /** Reads durable Turn + Session state into the pure fold's expectation shape. */
2147
+ projectTurnExpectation(store, fence, turnId) {
2235
2148
  const role = store.getRole(fence.taskId, fence.roleName);
2236
2149
  if (role === null)
2237
2150
  return null;
2238
2151
  const sessionSet = store.getTaskRoleSessionSet(fence.taskId, fence.roleName);
2239
2152
  const session = sessionSet?.sessions[fence.agentId] ?? null;
2240
- if (runId === undefined) {
2153
+ if (turnId === undefined) {
2241
2154
  return {
2242
2155
  fence,
2243
2156
  sessionStarted: false,
@@ -2250,13 +2163,8 @@ export class FileSchedulerStoreAdapter {
2250
2163
  : { boundNativeSessionId: session.nativeSessionId })
2251
2164
  };
2252
2165
  }
2253
- const run = store.getAgentRun(fence.taskId, runId);
2254
- const inFlight = sessionSet?.inFlight;
2255
- if (run === null
2256
- || inFlight === null
2257
- || inFlight === undefined
2258
- || inFlight.runId !== run.id
2259
- || inFlight.agentId !== run.effective.agentId)
2166
+ const run = store.getTurn(fence.taskId, turnId);
2167
+ if (run === null || run.status !== "active")
2260
2168
  return null;
2261
2169
  const owner = { scope: "task", taskId: fence.taskId, roleName: fence.roleName };
2262
2170
  const freshConversationLaunch = run.mode === "new"
@@ -2270,14 +2178,18 @@ export class FileSchedulerStoreAdapter {
2270
2178
  ?? (runtimeHookMatchesReservation(store, owner, fence.launchId) ? fence.launchId : undefined);
2271
2179
  if (launchId === undefined)
2272
2180
  return null;
2181
+ const providerTurn = sessionSet?.providerBinding?.turn;
2182
+ const managedTurnMatches = managedProviderTurnId(providerTurn) === run.id;
2273
2183
  const expectedFence = {
2274
2184
  taskId: fence.taskId,
2275
2185
  roleName: fence.roleName,
2276
2186
  agentId: run.effective.agentId,
2277
2187
  adapterId: run.effective.adapterId,
2278
- runId: run.id,
2188
+ turnId: run.id,
2279
2189
  launchId,
2280
- receiptId: inFlight.receiptId,
2190
+ receiptId: managedTurnMatches && providerTurn !== null && providerTurn !== undefined
2191
+ ? providerTurn.attemptId
2192
+ : formatTurnReceiptId(run.taskId, run.id),
2281
2193
  ...(boundNativeSessionId === undefined ? {} : { nativeSessionId: boundNativeSessionId })
2282
2194
  };
2283
2195
  const driverId = this.drivers.requireByAdapterId(run.effective.adapterId).id;
@@ -2297,8 +2209,10 @@ export class FileSchedulerStoreAdapter {
2297
2209
  fence: expectedFence,
2298
2210
  sessionStarted: lifecycleEvents.length > 0,
2299
2211
  ready: lifecycleEvents.some((event) => event.kind === "session.ready"),
2300
- pushed: run.pushedAt !== undefined,
2301
- accepted: run.deliveredAt !== undefined,
2212
+ pushed: managedTurnMatches,
2213
+ accepted: managedTurnMatches && providerTurn !== null && providerTurn !== undefined
2214
+ && ["accepted", "completed", "failed", "cancelled"]
2215
+ .includes(providerTurn.status),
2302
2216
  terminal: run.status !== "active",
2303
2217
  ...(boundNativeSessionId === undefined ? {} : { boundNativeSessionId })
2304
2218
  };
@@ -2350,7 +2264,7 @@ export class FileSchedulerStoreAdapter {
2350
2264
  : { endReason: effectiveExisting.endReason }),
2351
2265
  effective
2352
2266
  }, now);
2353
- current = rememberRoleAgentCompletedTurn(current, input.agentId, nativeSessionId, input.turnId, now);
2267
+ current = rememberRoleAgentCompletedTurn(current, input.agentId, nativeSessionId, input.nativeTurnId, now);
2354
2268
  store.saveGlobalRoleSessionSet(current);
2355
2269
  completeRuntimeHookReservation(store, owner, input.launchId);
2356
2270
  if (input.roleName === SYSTEM_OPERATOR_ROLE
@@ -2402,26 +2316,26 @@ function runtimeObservationLifecycleFence(input, adapterId) {
2402
2316
  agentId: input.fence.agentId,
2403
2317
  adapterId,
2404
2318
  launchId: input.fence.launchId,
2405
- ...(input.fence.runId === undefined ? {} : { runId: input.fence.runId }),
2319
+ ...(input.fence.turnId === undefined ? {} : { turnId: input.fence.turnId }),
2406
2320
  ...(input.fence.nativeSessionId === undefined
2407
2321
  ? {}
2408
2322
  : { nativeSessionId: input.fence.nativeSessionId }),
2409
2323
  ...(input.fence.receiptId === undefined ? {} : { receiptId: input.fence.receiptId })
2410
2324
  };
2411
2325
  }
2412
- /** Accepts the original Run receipt or a later mailbox activation receipt. */
2413
- function runtimeReceiptBelongsToRun(store, input) {
2326
+ /** Accepts the original Turn receipt or a later mailbox activation receipt. */
2327
+ function runtimeReceiptBelongsToTurn(store, input) {
2414
2328
  const taskId = input.fence.taskId;
2415
- const runId = input.fence.runId;
2329
+ const turnId = input.fence.turnId;
2416
2330
  const receiptId = input.fence.receiptId;
2417
- if (receiptId === formatAgentRunReceiptId(taskId, runId))
2331
+ if (receiptId === formatTurnReceiptId(taskId, turnId))
2418
2332
  return true;
2419
2333
  if (receiptId === undefined)
2420
2334
  return false;
2421
2335
  return store.listEvents(taskId).some((event) => {
2422
2336
  const accepted = runtimeObservationFromTaskEvent(event);
2423
2337
  return accepted?.kind === "turn.accepted"
2424
- && accepted.fence.runId === runId
2338
+ && accepted.fence.turnId === turnId
2425
2339
  && accepted.fence.roleName === input.fence.roleName
2426
2340
  && accepted.fence.agentId === input.fence.agentId
2427
2341
  && accepted.fence.launchId === input.fence.launchId
@@ -2440,7 +2354,7 @@ function recordCanonicalObservationObsolete(store, input, reason, now) {
2440
2354
  taskId,
2441
2355
  roleName: input.fence.roleName,
2442
2356
  agentId: input.fence.agentId,
2443
- ...(input.fence.runId === undefined ? {} : { runId: input.fence.runId }),
2357
+ ...(input.fence.turnId === undefined ? {} : { turnId: input.fence.turnId }),
2444
2358
  launchId: input.fence.launchId,
2445
2359
  nativeSessionId: input.fence.nativeSessionId ?? input.fence.sessionGenerationId
2446
2360
  }, reason, now);
@@ -2456,15 +2370,15 @@ function recordObsoleteRuntimeEvent(store, input, reason, now) {
2456
2370
  roleName: input.roleName,
2457
2371
  agentId: input.agentId,
2458
2372
  nativeSessionId: input.nativeSessionId,
2459
- ...(input.runId === undefined ? {} : { runId: input.runId }),
2373
+ ...(input.turnId === undefined ? {} : { turnId: input.turnId }),
2460
2374
  ...(input.launchId === undefined ? {} : { launchId: input.launchId }),
2461
2375
  reason
2462
2376
  }, now));
2463
2377
  }
2464
- function isLeaderDisposedWorkItemRun(store, input) {
2465
- if (input.runId === undefined)
2378
+ function isLeaderDisposedWorkItemTurn(store, input) {
2379
+ if (input.turnId === undefined)
2466
2380
  return false;
2467
- const run = store.getAgentRun(input.taskId, input.runId);
2381
+ const run = store.getTurn(input.taskId, input.turnId);
2468
2382
  if (run === null
2469
2383
  || run.status !== "failed"
2470
2384
  || run.roleName !== input.roleName
@@ -2474,12 +2388,12 @@ function isLeaderDisposedWorkItemRun(store, input) {
2474
2388
  }
2475
2389
  return store.getWorkItem(input.taskId, run.workItemId)?.disposition !== undefined;
2476
2390
  }
2477
- function isObsoleteTerminalRuntimeRun(store, input) {
2478
- if (isLeaderDisposedWorkItemRun(store, input))
2391
+ function isObsoleteTerminalRuntimeTurn(store, input) {
2392
+ if (isLeaderDisposedWorkItemTurn(store, input))
2479
2393
  return true;
2480
- if (input.runId === undefined)
2394
+ if (input.turnId === undefined)
2481
2395
  return false;
2482
- const run = store.getAgentRun(input.taskId, input.runId);
2396
+ const run = store.getTurn(input.taskId, input.turnId);
2483
2397
  if (run === null
2484
2398
  || run.status !== "failed"
2485
2399
  || run.roleName !== input.roleName
@@ -2487,7 +2401,7 @@ function isObsoleteTerminalRuntimeRun(store, input) {
2487
2401
  return false;
2488
2402
  }
2489
2403
  return store.listEvents(input.taskId).some((event) => (event.type === "runtime.role-delivery-failed"
2490
- && event.payload.runId === input.runId));
2404
+ && event.payload.turnId === input.turnId));
2491
2405
  }
2492
2406
  function sessionPreview(value) {
2493
2407
  const normalized = value.trim().replaceAll(/\s+/g, " ");
@@ -2513,28 +2427,27 @@ function runtimeHookMatchesReservation(store, owner, launchId) {
2513
2427
  }
2514
2428
  function preallocatedRuntimeReadyAwaitingProjection(store, observation, drivers) {
2515
2429
  const taskId = observation.fence.taskId;
2516
- const runId = observation.fence.runId;
2430
+ const turnId = observation.fence.turnId;
2517
2431
  const nativeSessionId = observation.fence.nativeSessionId;
2518
2432
  const driver = drivers.find(observation.fence.driverId);
2519
2433
  if (observation.kind !== "session.ready"
2520
2434
  || driver?.capabilities.observation.sessionBootstrap !== "preallocated"
2521
2435
  || taskId === undefined
2522
- || runId === undefined
2436
+ || turnId === undefined
2523
2437
  || nativeSessionId === undefined)
2524
2438
  return false;
2525
2439
  const task = store.getTask(taskId);
2526
2440
  const role = store.getRole(taskId, observation.fence.roleName);
2527
- const run = store.getActiveAgentRun(taskId, observation.fence.roleName);
2441
+ const run = store.getActiveTurn(taskId, observation.fence.roleName);
2528
2442
  const sessions = store.getTaskRoleSessionSet(taskId, observation.fence.roleName);
2529
2443
  if (task?.status !== "active"
2530
2444
  || task.executionGate.state !== "enabled"
2531
2445
  || role?.activeAgentId !== observation.fence.agentId
2532
- || run?.id !== runId
2446
+ || run?.id !== turnId
2533
2447
  || run.status !== "active"
2534
2448
  || run.effective.agentId !== observation.fence.agentId
2535
2449
  || run.effective.adapterId !== driver.adapterId
2536
- || observation.fence.receiptId !== agentRunDeliveryReceiptId(run)
2537
- || (sessions?.inFlight !== null && sessions?.inFlight !== undefined)
2450
+ || observation.fence.receiptId !== formatTurnReceiptId(run.taskId, run.id)
2538
2451
  || sessions?.sessions[observation.fence.agentId] !== undefined)
2539
2452
  return false;
2540
2453
  const mailbox = store.getWorkMailbox(runtimeLifecycleTarget({
@@ -2586,7 +2499,7 @@ function dormantRuntimeCandidateIsCurrent(store, candidate) {
2586
2499
  return false;
2587
2500
  }
2588
2501
  if (owner.scope === "task"
2589
- && store.getActiveAgentRun(owner.taskId, owner.roleName) !== null) {
2502
+ && store.getActiveTurn(owner.taskId, owner.roleName) !== null) {
2590
2503
  return false;
2591
2504
  }
2592
2505
  const sessions = runtimeOwnerSessionSet(store, owner);
@@ -2666,34 +2579,6 @@ function runtimeSessionMatchesSettledLaunch(session, input) {
2666
2579
  && (input.nativeSessionId === undefined
2667
2580
  || session.nativeSessionId === input.nativeSessionId);
2668
2581
  }
2669
- function preparedSessionMatches(session, agentId, adapterId, nativeSessionId, launchId) {
2670
- if (session === undefined)
2671
- return nativeSessionId === undefined;
2672
- if (session.agentId !== agentId
2673
- || session.adapterId !== adapterId
2674
- || session.status === "ended"
2675
- || (nativeSessionId !== undefined
2676
- && session.nativeSessionId !== nativeSessionId)) {
2677
- return false;
2678
- }
2679
- return launchId === undefined
2680
- ? session.launchId === undefined
2681
- : session.launchId === launchId;
2682
- }
2683
- function preparedDeliveryFailureSessionFenceMatches(sessions, session, input, expectedReceiptId) {
2684
- if (sessions === null)
2685
- return input.nativeSessionId === undefined;
2686
- if (sessions.activeAgentId !== input.agentId)
2687
- return false;
2688
- if (sessions.inFlight === null) {
2689
- return preparedSessionMatches(session, input.agentId, input.adapterId, input.nativeSessionId, input.launchId);
2690
- }
2691
- return sessions.inFlight.agentId === input.agentId
2692
- && sessions.inFlight.runId === input.runId
2693
- && sessions.inFlight.receiptId === expectedReceiptId
2694
- && sessions.inFlight.pushedAt === undefined
2695
- && preparedSessionMatches(session, input.agentId, input.adapterId, input.nativeSessionId, input.launchId);
2696
- }
2697
2582
  function recordTaskRuntimeNativeSession(store, input, now) {
2698
2583
  const task = store.getTask(input.taskId);
2699
2584
  if (task === null)
@@ -2826,14 +2711,14 @@ function mapRole(store, role) {
2826
2711
  };
2827
2712
  }
2828
2713
  function taskSessionEffective(store, taskId, roleName, agentId, existing) {
2829
- const active = store.getActiveAgentRun(taskId, roleName);
2714
+ const active = store.getActiveTurn(taskId, roleName);
2830
2715
  if (active !== null) {
2831
2716
  if (active.effective.agentId !== agentId) {
2832
- throw new Error(`Native Session registration does not match the effective Run Agent: ${taskId}/${roleName}.`);
2717
+ throw new Error(`Native Session registration does not match the effective Turn Agent: ${taskId}/${roleName}.`);
2833
2718
  }
2834
2719
  if (existing !== undefined) {
2835
2720
  if (!effectiveLaunchSnapshotsCompatibleForTaskSession(existing.effective, active.effective)) {
2836
- throw new Error(`Native Session effective launch does not match the active Run: ${taskId}/${roleName}.`);
2721
+ throw new Error(`Native Session effective launch does not match the active Turn: ${taskId}/${roleName}.`);
2837
2722
  }
2838
2723
  return existing.effective;
2839
2724
  }
@@ -2896,82 +2781,6 @@ function saveTaskSession(store, role, session, status, now, launchId) {
2896
2781
  }, now);
2897
2782
  store.saveRoleSessionSet(updated);
2898
2783
  }
2899
- function bindTaskRoleRunInFlight(store, role, run, now) {
2900
- const agentId = run.effective.agentId;
2901
- let current = store.getRoleSessionSet(role.taskId, role.name)
2902
- ?? createRoleSessionSet({ scope: "task", taskId: role.taskId, roleName: role.name }, agentId, now);
2903
- if (current.activeAgentId !== agentId) {
2904
- if (current.inFlight !== null || current.providerBinding !== null) {
2905
- throw new Error("Task Role Session identity cannot change with an unsettled Run fence.");
2906
- }
2907
- const liveConflict = Object.values(current.sessions).find((session) => (session.agentId !== agentId
2908
- && session.status === "active"));
2909
- if (liveConflict !== undefined) {
2910
- throw new Error(`Task Role Session identity cannot change while ${liveConflict.agentId} is live.`);
2911
- }
2912
- current = {
2913
- ...current,
2914
- activeAgentId: agentId,
2915
- updatedAt: now.toISOString()
2916
- };
2917
- }
2918
- const updated = bindTaskRoleRun(current, {
2919
- agentId,
2920
- runId: run.id,
2921
- receiptId: agentRunDeliveryReceiptId(run)
2922
- }, now, run.mode);
2923
- store.saveRoleSessionSet(updated);
2924
- }
2925
- function markTaskRoleRunPushedInFlight(store, role, run, now) {
2926
- const current = store.getRoleSessionSet(role.taskId, role.name);
2927
- if (current === null) {
2928
- throw new Error(`Task Role session set is missing for pushed Run: ${run.id}.`);
2929
- }
2930
- const updated = markTaskRoleRunPushed(current, {
2931
- agentId: run.effective.agentId,
2932
- runId: run.id,
2933
- receiptId: agentRunDeliveryReceiptId(run)
2934
- }, now);
2935
- store.saveRoleSessionSet(updated);
2936
- }
2937
- function markTaskRoleRunDeliveredInFlight(store, role, run, now) {
2938
- const current = store.getRoleSessionSet(role.taskId, role.name);
2939
- if (current === null) {
2940
- throw new Error(`Task Role session set is missing for delivered Run: ${run.id}.`);
2941
- }
2942
- const updated = markTaskRoleRunDelivered(current, {
2943
- agentId: run.effective.agentId,
2944
- runId: run.id,
2945
- receiptId: agentRunDeliveryReceiptId(run)
2946
- }, now);
2947
- store.saveRoleSessionSet(updated);
2948
- }
2949
- function clearTaskRoleRunInFlight(store, role, run, now) {
2950
- const current = store.getRoleSessionSet(role.taskId, role.name);
2951
- if (current === null)
2952
- return;
2953
- if (current.inFlight?.runId !== run.id)
2954
- return;
2955
- const updated = clearTaskRoleRun(current, {
2956
- agentId: run.effective.agentId,
2957
- runId: run.id,
2958
- receiptId: agentRunDeliveryReceiptId(run)
2959
- }, now);
2960
- store.saveRoleSessionSet(updated);
2961
- }
2962
- /**
2963
- * The durable Run/mailbox claim is the pre-Host delivery intent. The Host may
2964
- * create a ProviderTurn only while that exact reservation still owns the Run;
2965
- * this closes the gap where a Session could start without any user Turn.
2966
- */
2967
- function processingOwnsExactInitialDelivery(mailbox, taskId, runId, attemptId) {
2968
- const processing = mailbox?.processing;
2969
- return processing?.batchId === attemptId
2970
- && processing.owner === "controller"
2971
- && processing.executionRef?.type === "run"
2972
- && processing.executionRef.taskId === taskId
2973
- && processing.executionRef.id === runId;
2974
- }
2975
2784
  function matchesStallSessionFence(current, expected) {
2976
2785
  if (current === null || expected === null)
2977
2786
  return current === expected;
@@ -2987,24 +2796,24 @@ function requireRole(store, taskId, roleName) {
2987
2796
  throw new Error(`Role not found: ${taskId}/${roleName}.`);
2988
2797
  return role;
2989
2798
  }
2990
- function runLaunchEventPayload(run) {
2799
+ function turnLaunchEventPayload(turn) {
2991
2800
  return {
2992
- runId: run.id,
2993
- role: run.roleName,
2994
- purpose: run.purpose,
2995
- mode: run.mode,
2996
- agent: `${run.effective.agentId}/${run.effective.adapterId}`,
2997
- effectiveRevision: String(run.effective.sourceDesiredRevision),
2998
- profileAccess: run.effective.profileAccess,
2999
- effectivePermission: run.effective.permission.strategy,
3000
- writeProjectIds: run.effective.writeProjectIds.join(",") || "none"
2801
+ turnId: turn.id,
2802
+ role: turn.roleName,
2803
+ purpose: turn.purpose,
2804
+ mode: turn.mode,
2805
+ agent: `${turn.effective.agentId}/${turn.effective.adapterId}`,
2806
+ effectiveRevision: String(turn.effective.sourceDesiredRevision),
2807
+ profileAccess: turn.effective.profileAccess,
2808
+ effectivePermission: turn.effective.permission.strategy,
2809
+ writeProjectIds: turn.effective.writeProjectIds.join(",") || "none"
3001
2810
  };
3002
2811
  }
3003
2812
  function runtimeObservationTelemetryEntry(input) {
3004
2813
  return {
3005
2814
  taskId: input.fence.taskId,
3006
2815
  roleName: input.fence.roleName,
3007
- runId: input.fence.runId,
2816
+ turnId: input.fence.turnId,
3008
2817
  generation: input.fence.sessionGenerationId,
3009
2818
  progressId: [
3010
2819
  input.kind,
@@ -3043,7 +2852,7 @@ function compactedRuntimeObservationIds(events, incoming) {
3043
2852
  const observation = runtimeObservationFromTaskEvent(event);
3044
2853
  const matches = incoming.kind.startsWith("operation.")
3045
2854
  ? observation !== null
3046
- && runtimeObservationRunFenceMatches(observation.fence, incoming.fence)
2855
+ && runtimeObservationTurnFenceMatches(observation.fence, incoming.fence)
3047
2856
  : observation !== null
3048
2857
  && runtimeObservationFenceMatches(observation.fence, incoming.fence);
3049
2858
  return observation !== null
@@ -3098,14 +2907,15 @@ function recordStructuredProviderAcceptance(sessions, input, now) {
3098
2907
  const turnId = input.fence.nativeTurnId;
3099
2908
  if (current === null || attemptId === undefined || turnId === undefined)
3100
2909
  return sessions;
3101
- if (current.turn === null || current.turn.runId !== input.fence.runId)
2910
+ if (current.turn === null
2911
+ || current.turn.turnId !== input.fence.turnId)
3102
2912
  return sessions;
3103
2913
  if (current.turn.attemptId === attemptId
3104
- && ["accepted", "running", "completed", "failed", "cancelled"].includes(current.turn.status))
2914
+ && ["accepted", "completed", "failed", "cancelled"].includes(current.turn.status))
3105
2915
  return sessions;
3106
2916
  const binding = acceptProviderTurn(current, {
3107
2917
  attemptId,
3108
- turnId,
2918
+ nativeTurnId: turnId,
3109
2919
  acceptedAt: input.observedAt ?? input.receivedAt
3110
2920
  });
3111
2921
  return updateTaskRoleProviderRuntime(sessions, binding, now);
@@ -3120,13 +2930,11 @@ function canonicalStructuredProviderTurnId(sessions, observedTurnId, attemptId)
3120
2930
  const turn = sessions.providerBinding?.turn;
3121
2931
  return attemptId !== undefined
3122
2932
  && turn?.attemptId === attemptId
3123
- && turn.turnId !== undefined
3124
- ? turn.turnId
2933
+ && turn.nativeTurnId !== undefined
2934
+ ? turn.nativeTurnId
3125
2935
  : observedTurnId;
3126
2936
  }
3127
2937
  function bindOrSupersedeProviderRuntime(sessions, input, now, replacementBasis) {
3128
- if (sessions.inFlight === null)
3129
- return sessions;
3130
2938
  const conversationId = input.fence.conversationId ?? input.fence.nativeSessionId;
3131
2939
  const activationId = input.fence.activationId ?? input.fence.launchId;
3132
2940
  if (sessions.providerBinding === null) {
@@ -3135,7 +2943,7 @@ function bindOrSupersedeProviderRuntime(sessions, input, now, replacementBasis)
3135
2943
  accountScope: input.fence.agentId,
3136
2944
  conversationId,
3137
2945
  activationId,
3138
- startedAt: sessions.inFlight.preparedAt
2946
+ startedAt: input.observedAt ?? input.receivedAt
3139
2947
  }), now);
3140
2948
  }
3141
2949
  const current = currentProviderConversation(sessions.providerBinding);
@@ -3161,54 +2969,15 @@ function bindOrSupersedeProviderRuntime(sessions, input, now, replacementBasis)
3161
2969
  basis: replacementBasis
3162
2970
  }), now);
3163
2971
  }
3164
- function rejectSubmittingProviderTurn(store, target, delivery, now, reason) {
3165
- if (target.kind !== "role"
3166
- || delivery.executionRef.type !== "run"
3167
- || delivery.executionRef.taskId !== target.taskId)
3168
- return;
3169
- const sessions = store.getTaskRoleSessionSet(target.taskId, target.roleName);
3170
- const binding = sessions?.providerBinding;
3171
- if (sessions === null || sessions === undefined
3172
- || binding === null || binding === undefined
3173
- || binding.turn?.runId !== delivery.executionRef.id
3174
- || binding.turn?.attemptId !== delivery.attemptId
3175
- || (binding.turn.status !== "submitting"
3176
- && binding.turn.status !== "delivery-unknown"))
3177
- return;
3178
- store.saveTaskRoleSessionSet(updateTaskRoleProviderRuntime(sessions, rejectProviderTurn(binding, {
3179
- attemptId: delivery.attemptId,
3180
- rejectedAt: now.toISOString(),
3181
- reason
3182
- }), now));
3183
- }
3184
- function markSubmittingProviderTurnUnknown(store, target, delivery, now, reason) {
3185
- if (target.kind !== "role"
3186
- || delivery.executionRef.type !== "run"
3187
- || delivery.executionRef.taskId !== target.taskId)
3188
- return;
3189
- const sessions = store.getTaskRoleSessionSet(target.taskId, target.roleName);
3190
- const binding = sessions?.providerBinding;
3191
- if (sessions === null || sessions === undefined
3192
- || binding === null || binding === undefined
3193
- || binding.turn?.runId !== delivery.executionRef.id
3194
- || binding.turn?.attemptId !== delivery.attemptId
3195
- || binding.turn.status !== "submitting")
3196
- return;
3197
- store.saveTaskRoleSessionSet(updateTaskRoleProviderRuntime(sessions, markProviderTurnDeliveryUnknown(binding, {
3198
- attemptId: delivery.attemptId,
3199
- observedAt: now.toISOString(),
3200
- reason
3201
- }), now));
3202
- }
3203
2972
  function settleStructuredProviderTurn(sessions, turnId, status, now) {
3204
2973
  const binding = sessions.providerBinding;
3205
- if (binding === null || binding.turn === null || binding.turn.turnId !== turnId) {
2974
+ if (binding === null || binding.turn === null || binding.turn.nativeTurnId !== turnId) {
3206
2975
  return sessions;
3207
2976
  }
3208
2977
  if (["completed", "failed", "cancelled"].includes(binding.turn.status))
3209
2978
  return sessions;
3210
2979
  return updateTaskRoleProviderRuntime(sessions, settleProviderTurn(binding, {
3211
- turnId,
2980
+ nativeTurnId: turnId,
3212
2981
  status,
3213
2982
  settledAt: now.toISOString()
3214
2983
  }), now);