@zq-silk/yui 0.15.6 → 0.15.8

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 (282) hide show
  1. package/ARCHITECTURE.md +192 -399
  2. package/README.md +127 -1149
  3. package/dist/agent/adapterCatalog.js +15 -2
  4. package/dist/agent/agent.js +23 -3
  5. package/dist/agent/argumentPolicy.js +7 -1
  6. package/dist/agent/connectionPlan.js +62 -0
  7. package/dist/agent/executionComponents.js +158 -0
  8. package/dist/agent/launchEnvironment.js +31 -3
  9. package/dist/agent/managedRuntimeEnvironment.js +3 -5
  10. package/dist/{turn/turn.js → agentRun/agentRun.js} +166 -109
  11. package/dist/{turn/turnIdentity.js → agentRun/runIdentity.js} +4 -4
  12. package/dist/brief/taskBrief.js +12 -0
  13. package/dist/cli/agentConfigurationPicker.js +13 -0
  14. package/dist/cli/commandCatalog.js +167 -70
  15. package/dist/cli/interactionCandidates.js +5 -5
  16. package/dist/cli/interactionPolicy.js +38 -8
  17. package/dist/cli/invocationRouter.js +1 -1
  18. package/dist/cli/managedDiagnostics.js +28 -0
  19. package/dist/cli/operatorWizard.js +1 -7
  20. package/dist/cli/roleOptionOrder.js +27 -0
  21. package/dist/cli/roleWizard.js +50 -14
  22. package/dist/cli/updateOrchestrator.js +1 -1
  23. package/dist/cli/updatePorts.js +3 -4
  24. package/dist/cli.js +188 -95
  25. package/dist/commands/agentCommands.js +72 -14
  26. package/dist/commands/capabilityCommands.js +57 -0
  27. package/dist/commands/configCommands.js +20 -20
  28. package/dist/commands/deliveryGuardPreflight.js +2 -2
  29. package/dist/commands/executionAuditCommands.js +24 -24
  30. package/dist/commands/globalRoleCommands.js +1 -1
  31. package/dist/commands/grantCommands.js +4 -4
  32. package/dist/commands/operatorCommands.js +1 -7
  33. package/dist/commands/projectCommands.js +4 -4
  34. package/dist/commands/resourcesCommands.js +2 -2
  35. package/dist/commands/roleConfiguration.js +25 -5
  36. package/dist/commands/roleRuntimeGuard.js +4 -5
  37. package/dist/commands/sessionCommands.js +3 -7
  38. package/dist/commands/taskActivationCommands.js +259 -0
  39. package/dist/commands/taskActor.js +28 -49
  40. package/dist/commands/taskCommands.js +1216 -730
  41. package/dist/commands/taskContextCommand.js +39 -583
  42. package/dist/commands/taskExecutionCommands.js +32 -32
  43. package/dist/commands/taskInputCommands.js +40 -104
  44. package/dist/commands/taskIntegrationCommands.js +3 -2
  45. package/dist/commands/taskIntegrationQueueCommands.js +1 -1
  46. package/dist/commands/taskNextActionCommand.js +8 -8
  47. package/dist/commands/taskOverviewCommand.js +33 -45
  48. package/dist/commands/taskRemoteDeliveryCommand.js +2 -2
  49. package/dist/commands/taskRoleRuntimeStatus.js +133 -102
  50. package/dist/commands/telemetryCommands.js +36 -38
  51. package/dist/config/configCatalog.js +4 -4
  52. package/dist/config/yuiConfig.js +8 -8
  53. package/dist/context/contextSnapshot.js +10 -10
  54. package/dist/context/dispatchContext.js +11 -11
  55. package/dist/context/roleSessionContext.js +6 -3
  56. package/dist/context/{turnContextPack.js → runContextPack.js} +146 -81
  57. package/dist/context/{turnInputContract.js → runInputContract.js} +73 -60
  58. package/dist/context/sessionBootstrapManifest.js +21 -2
  59. package/dist/context/sourceRunContext.js +30 -0
  60. package/dist/context/taskContext.js +458 -0
  61. package/dist/context/wakeNotification.js +27 -27
  62. package/dist/controller/agentRuntimeObserver.js +21 -24
  63. package/dist/controller/capabilityBridge.js +77 -0
  64. package/dist/controller/clientRuntime.js +65 -92
  65. package/dist/controller/controller.js +127 -188
  66. package/dist/controller/fileSchedulerStoreAdapter.js +787 -887
  67. package/dist/controller/jobControl.js +54 -85
  68. package/dist/controller/resourceInventory.js +8 -27
  69. package/dist/controller/resourceInventoryLinux.js +12 -13
  70. package/dist/controller/runtime.js +530 -476
  71. package/dist/controller/runtimeEventInbox.js +55 -25
  72. package/dist/controller/runtimeEventProcessor.js +22 -31
  73. package/dist/controller/{runtimeHookTurnFence.js → runtimeHookRunFence.js} +91 -115
  74. package/dist/controller/runtimeLaunchCoordinator.js +80 -426
  75. package/dist/controller/runtimeObservationHook.js +14 -18
  76. package/dist/controller/sessionNotify.js +16 -24
  77. package/dist/controller/sessionOwnerReconciliation.js +168 -50
  78. package/dist/controller/structuredProviderObservation.js +138 -99
  79. package/dist/coordination/workMailbox.js +3 -3
  80. package/dist/coordination/workMailboxQueue.js +36 -33
  81. package/dist/core/boundedRpc.js +8 -1
  82. package/dist/core/controllerClient.js +20 -1
  83. package/dist/core/controllerServer.js +4 -4
  84. package/dist/doctor/doctor.js +13 -2
  85. package/dist/domain/agentResultTransport.js +9 -9
  86. package/dist/execution/codexThreadNaming.js +2 -8
  87. package/dist/execution/executionHealth.js +51 -63
  88. package/dist/execution/reviewMainRun.js +137 -0
  89. package/dist/execution/workItemExecution.js +28 -29
  90. package/dist/execution/workItemExecutionProjection.js +99 -107
  91. package/dist/execution/workItemMainRun.js +141 -0
  92. package/dist/executor/agentAdapter.js +227 -20
  93. package/dist/executor/agentConfigurationCatalog.js +126 -4
  94. package/dist/executor/agentConfigurationProbe.js +162 -4
  95. package/dist/executor/agentExecutor.js +79 -78
  96. package/dist/executor/effectiveLaunch.js +105 -18
  97. package/dist/executor/executorRegistry.js +29 -44
  98. package/dist/executor/fileRoleLaunchPlanner.js +229 -154
  99. package/dist/executor/workspacePreflightClassification.js +16 -16
  100. package/dist/grant/capabilityGrant.js +6 -3
  101. package/dist/input/inputRequest.js +12 -10
  102. package/dist/integration/gitIntegrationService.js +4 -11
  103. package/dist/integration/integrationQueueService.js +4 -4
  104. package/dist/interaction/operatorPresentation.js +1 -1
  105. package/dist/kernel/builtinCapabilities.js +423 -0
  106. package/dist/kernel/capabilityRegistry.js +314 -0
  107. package/dist/kernel/capabilitySchema.js +91 -0
  108. package/dist/kernel/instanceHost.js +18 -1
  109. package/dist/kernel/kernelPorts.js +4 -1
  110. package/dist/lifecycle/canonicalLifecycleEvent.js +44 -49
  111. package/dist/lifecycle/exactRunTerminalization.js +449 -0
  112. package/dist/message/message.js +62 -6
  113. package/dist/message/messageContinuation.js +204 -0
  114. package/dist/observability/executionAudit.js +70 -72
  115. package/dist/observability/faultClassification.js +2 -2
  116. package/dist/observability/orchestrationMetrics.js +8 -8
  117. package/dist/operator/operatorSessionHistory.js +1 -7
  118. package/dist/output/agentConfigurationPresentation.js +8 -3
  119. package/dist/output/agentRunConfigurationPresentation.js +128 -0
  120. package/dist/output/rolePresentation.js +54 -3
  121. package/dist/plugins/pluginChild.js +104 -0
  122. package/dist/plugins/pluginIntent.js +26 -0
  123. package/dist/plugins/pluginInterpreter.js +43 -0
  124. package/dist/plugins/pluginPackage.js +101 -0
  125. package/dist/plugins/pluginProcess.js +112 -0
  126. package/dist/plugins/pluginService.js +380 -0
  127. package/dist/profile/agentProfile.js +1 -1
  128. package/dist/repository/gitWorkspace.js +26 -4
  129. package/dist/repository/project.js +19 -4
  130. package/dist/repository/taskBaseFreshness.js +13 -13
  131. package/dist/repository/taskWorkspaceCoordinator.js +20 -27
  132. package/dist/repository/taskWorkspacePreparer.js +344 -83
  133. package/dist/resources/autoResourceGc.js +3 -3
  134. package/dist/resources/liveReferences.js +3 -3
  135. package/dist/resources/projectResource.js +123 -0
  136. package/dist/resources/projectResourceService.js +421 -0
  137. package/dist/resources/resourceDiscovery.js +6 -6
  138. package/dist/resources/resourceGc.js +1 -1
  139. package/dist/resources/resourceRegistrar.js +1 -1
  140. package/dist/resources/resourceTypes.js +1 -1
  141. package/dist/review/deltaRecheck.js +3 -3
  142. package/dist/review/reviewAcceptance.js +16 -16
  143. package/dist/review/reviewDecision.js +7 -7
  144. package/dist/review/reviewRound.js +21 -20
  145. package/dist/review/reviewerAvailability.js +2 -2
  146. package/dist/role/role.js +51 -7
  147. package/dist/role/taskRoleUpdate.js +30 -0
  148. package/dist/runtime/acpProtocol.js +425 -0
  149. package/dist/runtime/acpSession.js +731 -0
  150. package/dist/runtime/acpSessionConfiguration.js +260 -0
  151. package/dist/runtime/agentDriver.js +30 -11
  152. package/dist/runtime/agentEndpoint.js +278 -0
  153. package/dist/runtime/agentEndpointIdentity.js +86 -0
  154. package/dist/runtime/agentEndpointOwnership.js +239 -0
  155. package/dist/runtime/agentError.js +2 -10
  156. package/dist/runtime/agentHost.js +565 -314
  157. package/dist/runtime/agentRunConfiguration.js +258 -0
  158. package/dist/runtime/builtinAgentDrivers.js +134 -18
  159. package/dist/runtime/builtinAgentErrorMappers.js +55 -3
  160. package/dist/runtime/builtinTranscriptUsage.js +1 -1
  161. package/dist/runtime/claude-process-owner +0 -0
  162. package/dist/runtime/codexAppServerRuntime.js +38 -30
  163. package/dist/runtime/codexInteractiveHost.js +41 -6
  164. package/dist/runtime/continuationManager.js +2 -6
  165. package/dist/runtime/executionEnvironment.js +30 -0
  166. package/dist/runtime/firstProgressAdvisory.js +11 -11
  167. package/dist/runtime/index.js +4 -3
  168. package/dist/runtime/jsonLineChannel.js +109 -0
  169. package/dist/runtime/launchBroker.js +91 -16
  170. package/dist/runtime/launchDiagnostics.js +2 -2
  171. package/dist/runtime/lifecycleReservation.js +10 -18
  172. package/dist/runtime/managedCaller.js +61 -17
  173. package/dist/runtime/nativeSessionControl.js +102 -0
  174. package/dist/runtime/ports.js +6 -21
  175. package/dist/runtime/processExitObservation.js +8 -7
  176. package/dist/runtime/promptEnvelope.js +17 -6
  177. package/dist/runtime/providerContinuation.js +3 -9
  178. package/dist/runtime/providerContinuationReconciliationService.js +4 -13
  179. package/dist/runtime/providerControl.js +2 -7
  180. package/dist/runtime/providerRuntimeIdentity.js +110 -222
  181. package/dist/runtime/providerRuntimeReconciler.js +5 -9
  182. package/dist/runtime/runtimeBinding.js +0 -1
  183. package/dist/runtime/runtimeContinuationProjection.js +4 -7
  184. package/dist/runtime/runtimeDeadlines.js +9 -0
  185. package/dist/runtime/runtimeHealthPolicy.js +1 -1
  186. package/dist/runtime/runtimeObservation.js +29 -65
  187. package/dist/runtime/runtimeProjection.js +43 -51
  188. package/dist/runtime/runtimeSessionCandidate.js +1 -3
  189. package/dist/runtime/sessionLaunchRequest.js +3 -7
  190. package/dist/runtime/sessionOwnerIdentity.js +7 -54
  191. package/dist/runtime/sessionOwnerRegistry.js +22 -17
  192. package/dist/runtime/sessionReconciliation.js +4 -8
  193. package/dist/runtime/sessionTerminationGuard.js +70 -259
  194. package/dist/runtime/sessionTokenMetrics.js +5 -16
  195. package/dist/runtime/structuredProviderHost.js +237 -117
  196. package/dist/runtime/taskRuntimeIsolation.js +39 -122
  197. package/dist/runtime/tmuxAdapters.js +39 -86
  198. package/dist/scheduler/activeRoleRunDelivery.js +354 -0
  199. package/dist/scheduler/leaderWakeupProcessor.js +75 -266
  200. package/dist/scheduler/operatorInputNotificationProcessor.js +1 -1
  201. package/dist/scheduler/ports.js +80 -9
  202. package/dist/scheduler/{roleTurnLiveness.js → roleRunLiveness.js} +26 -30
  203. package/dist/scheduler/{roleTurnStall.js → roleRunStall.js} +128 -139
  204. package/dist/scheduler/taskExecutionProjection.js +120 -124
  205. package/dist/scheduler/taskObservabilityProjection.js +29 -29
  206. package/dist/scheduler/taskWake.js +11 -4
  207. package/dist/scheduler/wakeReason.js +9 -1
  208. package/dist/setup/setupCommand.js +1 -0
  209. package/dist/storage/migrations/agentRunContract.js +159 -0
  210. package/dist/storage/migrations/removeRuntimeGeneration.js +207 -0
  211. package/dist/storage/sqliteSchema.js +431 -5
  212. package/dist/storage/sqliteStore.js +375 -220
  213. package/dist/storage/storageVersions.js +1 -1
  214. package/dist/storage/storeRpc.js +10 -5
  215. package/dist/storage/taskStore.js +13 -11
  216. package/dist/storage/upgrade/upgradeOrchestrator.js +5 -7
  217. package/dist/surface/surfaceContributions.js +102 -0
  218. package/dist/task/completionReadiness.js +32 -6
  219. package/dist/task/deliveryGuard.js +16 -16
  220. package/dist/task/draftPlan.js +72 -12
  221. package/dist/task/nextAction.js +144 -128
  222. package/dist/task/remoteDelivery.js +6 -6
  223. package/dist/task/task.js +184 -18
  224. package/dist/task/taskActivation.js +301 -0
  225. package/dist/task/taskActivationService.js +392 -0
  226. package/dist/task/taskRecordReference.js +5 -4
  227. package/dist/task/taskRecordRetirement.js +1 -1
  228. package/dist/telemetry/sqliteTelemetryStore.js +55 -68
  229. package/dist/telemetry/telemetryConfig.js +14 -14
  230. package/dist/telemetry/telemetryWiring.js +2 -2
  231. package/dist/web/assets/assetManifest.js +2 -0
  232. package/dist/web/assets/client/app.js +120 -20
  233. package/dist/web/assets/client/components.js +87 -54
  234. package/dist/web/assets/client/i18n.js +83 -41
  235. package/dist/web/assets/client/markdown.js +1 -1
  236. package/dist/web/assets/client/taskSurface.js +353 -0
  237. package/dist/web/assets/client/view.js +49 -44
  238. package/dist/web/assets/shell.js +1 -1
  239. package/dist/web/assets/styles/cards.js +22 -4
  240. package/dist/web/controllerWeb.js +60 -0
  241. package/dist/web/webMutation.js +28 -0
  242. package/dist/web/webServer.js +118 -8
  243. package/dist/web/webSnapshot.js +81 -74
  244. package/dist/web/webTaskSurface.js +64 -0
  245. package/dist/workItem/dependencyGate.js +1 -1
  246. package/dist/workItem/workItem.js +80 -48
  247. package/dist/workspace/workItemChangeSetManager.js +16 -9
  248. package/docs/agent-result-consumption.md +94 -0
  249. package/docs/agent-runtime-drivers.md +91 -0
  250. package/docs/architecture/README.md +38 -0
  251. package/docs/architecture/capabilities-and-resources.md +79 -0
  252. package/docs/managed-turn-and-session-runtime.md +222 -0
  253. package/docs/observability/README.md +81 -0
  254. package/docs/plugin-sdk.md +290 -0
  255. package/docs/provider-runtime.md +163 -0
  256. package/docs/release-workflow.md +303 -0
  257. package/docs/roles-and-configuration.md +113 -0
  258. package/docs/sqlite-control-plane-design.md +76 -0
  259. package/docs/task-dag-semantics.md +57 -0
  260. package/docs/task-delivery.md +103 -0
  261. package/docs/task-local-identity.md +6 -6
  262. package/docs/testing/verification-levels.md +86 -0
  263. package/i18n/README.zh-CN.md +98 -739
  264. package/package.json +5 -5
  265. package/skills/yui-leader/SKILL.md +130 -103
  266. package/skills/yui-leader/references/integration.md +39 -0
  267. package/skills/yui-leader/references/replicated-execution.md +42 -0
  268. package/skills/yui-leader/references/task-plugins.md +33 -0
  269. package/skills/yui-operator/SKILL.md +30 -59
  270. package/skills/yui-reviewer/SKILL.md +35 -36
  271. package/skills/yui-runtime/SKILL.md +88 -24
  272. package/skills/yui-runtime/references/publication.md +22 -0
  273. package/skills/yui-runtime/references/recovery.md +64 -0
  274. package/skills/yui-worker/SKILL.md +37 -39
  275. package/dist/cli/roleOptionCatalog.js +0 -68
  276. package/dist/context/sourceTurnContext.js +0 -30
  277. package/dist/execution/reviewMainTurn.js +0 -161
  278. package/dist/execution/workItemMainTurn.js +0 -164
  279. package/dist/lifecycle/exactTurnTerminalization.js +0 -407
  280. package/dist/runtime/preallocatedNativeSession.js +0 -13
  281. package/dist/runtime/runtimeStopReceipt.js +0 -42
  282. package/dist/scheduler/activeRoleTurnDelivery.js +0 -315
@@ -1,27 +1,31 @@
1
+ import { roleLaunchEventPayload, saveTaskRoleUpdate } from "../role/taskRoleUpdate.js";
1
2
  import { randomUUID } from "node:crypto";
3
+ import { join } from "node:path";
2
4
  import { isDeepStrictEqual } from "node:util";
3
- import { createTurnInput } from "../context/turnInputContract.js";
4
- import { buildTurnContextPack, buildTurnContextDelta, contextSnapshotDeltaRefIds, expandTurnContextRef, freezeWorkItemExecutionAssignmentContextSnapshot, freezeReviewStageContextSnapshot, freezeTurnContextSnapshot } from "../context/turnContextPack.js";
5
+ import { createRunInput } from "../context/runInputContract.js";
6
+ import { buildRunContextPack, buildRunContextDelta, contextSnapshotDeltaRefIds, expandRunContextRef, freezeWorkItemExecutionAssignmentContextSnapshot, freezeReviewStageContextSnapshot, freezeRunContextSnapshot } from "../context/runContextPack.js";
5
7
  import { contextSnapshotRef } from "../context/contextSnapshot.js";
6
8
  import { CliError, dataError, roleNotFound, runtimeError, taskNotFound, usageError } from "../errors/cliError.js";
7
9
  import { createTaskEvent } from "../event/taskEvent.js";
8
10
  import { createTaskRecordRetirement, isTaskRecordRetired, operationalTaskRecords, taskRecordRetirement } from "../task/taskRecordRetirement.js";
9
- import { referencedWakeTurnIds } from "../context/wakeNotification.js";
10
- import { isRoleTurnStalled, TURN_PROGRESS_EVENT, TURN_RECOVERED_EVENT } from "../scheduler/roleTurnStall.js";
11
+ import { referencedWakeRunIds } from "../context/wakeNotification.js";
12
+ import { isRoleRunStalled, RUN_PROGRESS_EVENT, RUN_RECOVERED_EVENT } from "../scheduler/roleRunStall.js";
11
13
  import { readCommandText } from "./textInput.js";
12
14
  import { assertTaskCompletionPublishedTreeProof } from "./taskCompletionGate.js";
13
- import { createRoleSessionSet, roleAgentSessionResumeMode, updateTaskRoleProviderRuntime } from "../executor/agentExecutor.js";
14
- import { currentProviderActivation, transferProviderAuthority } from "../runtime/providerRuntimeIdentity.js";
15
+ import { createRoleSessionSet, roleAgentSessionResumeMode, updateTaskRoleProviderRuntime, taskRoleControlTarget } from "../executor/agentExecutor.js";
16
+ import { transferProviderAuthority, currentProviderConversation } from "../runtime/providerRuntimeIdentity.js";
15
17
  import { resolveEffectiveLaunch } from "../executor/effectiveLaunch.js";
16
18
  import { defaultTableWidth, renderTable } from "../output/table.js";
19
+ import { agentExecutionComponentLabel } from "../agent/executionComponents.js";
20
+ import { agentRunConfigurationLabel, renderAgentRunConfiguration } from "../output/agentRunConfigurationPresentation.js";
17
21
  import { formatTimestamp } from "../output/timePresentation.js";
18
- import { renderRoleDetails } from "../output/rolePresentation.js";
19
- import { createTaskMessage, taskMessageAuthorLabel, updateDraftTaskMessage } from "../message/message.js";
22
+ import { renderRoleDetails, renderRoleLaunchComparison } from "../output/rolePresentation.js";
23
+ import { createTaskMessage, expandTaskMessageResult, taskMessageAuthorLabel, updateDraftTaskMessage } from "../message/message.js";
20
24
  import { assertDraftTaskExecutionFree, validateDraftWorkItemEdit } from "../task/draftPlan.js";
21
25
  import { cancelInputRequest } from "../input/inputRequest.js";
22
- import { retireExactActiveTurn, terminalizeExactTaskTurn, validateExactTurnReviewRound } from "../lifecycle/exactTurnTerminalization.js";
26
+ import { retireExactActiveRun, terminalizeExactTaskRun, validateExactRunReviewRound } from "../lifecycle/exactRunTerminalization.js";
23
27
  import { copyGlobalRoleToTaskRole, createRole, createRoleAgentBinding, switchActiveRoleAgent, unbindRoleAgent, updateRole } from "../role/role.js";
24
- import { createTurn, withTurnContextSnapshot } from "../turn/turn.js";
28
+ import { createRun, runPurposeAdmitsTaskState, runExecutionObservation, withRunContextSnapshot } from "../agentRun/agentRun.js";
25
29
  import { createReviewRound, createTaskReviewRound, createTaskDeltaReviewRound, attachReviewExecutionGroup, finishReviewRound, recordReviewWorkspaceDisposition, retryReviewRound, retryRunningReviewExecutionLane, retryTaskReviewRound, startReplicatedReviewRound, startReviewRound, updateReviewExecutionGroup, validateTaskReviewCandidate } from "../review/reviewRound.js";
26
30
  import { buildDeltaRecheckDispatchContext, verifyDeltaRecheckDiff } from "../review/deltaRecheck.js";
27
31
  import { isCompletedTaskReviewEvidence } from "../review/reviewAcceptance.js";
@@ -30,38 +34,44 @@ import { createTaskBrief, updateTaskBrief } from "../brief/taskBrief.js";
30
34
  import { createDecision, supersedeDecision } from "../decision/decision.js";
31
35
  import { createMilestone } from "../milestone/milestone.js";
32
36
  import { runPublicationCommand } from "./taskPublicationCommands.js";
37
+ import { runTaskActivationCommand } from "./taskActivationCommands.js";
33
38
  import { assertTaskRemoteDeliveryProof, projectTaskRemoteDeliveryFromStore, renderTaskRemoteDelivery, runTaskRemoteDeliveryCommand } from "./taskRemoteDeliveryCommand.js";
34
- import { enqueueRoleTurnDispatch, enqueueWork, settleExactWorkExecution } from "../coordination/workMailboxQueue.js";
35
- import { mailboxHasWork as workMailboxHasWork } from "../coordination/workMailbox.js";
36
- import { runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
39
+ import { enqueueRoleRunDispatch, enqueueWork, settleExactWorkExecution } from "../coordination/workMailboxQueue.js";
40
+ import { completeProcessing } from "../coordination/workMailbox.js";
41
+ import { runtimeLifecycleTarget, RUNTIME_SESSION_REPLACE_REQUIRED_REASON } from "../runtime/lifecycleReservation.js";
37
42
  import { projectProviderContinuations } from "../runtime/runtimeContinuationProjection.js";
38
43
  import { runtimeObservationFromTaskEvent } from "../runtime/runtimeObservation.js";
39
- import { addTaskProjectBinding, archiveTask, completeTask, createTask, retireTask, reopenTask, updateTaskMetadata } from "../task/task.js";
44
+ import { addTaskProjectBinding, archiveTask, completeTask, createTask, retireTask, reopenTask, taskOwnsManagedWorkspace, updateTaskMetadata } from "../task/task.js";
40
45
  import { resolveTaskRecordReference } from "../task/taskRecordReference.js";
41
46
  import { projectCompletionReadiness } from "../task/completionReadiness.js";
42
47
  import { requireResolvedAgentProfileRuntime } from "../profile/agentProfileRuntime.js";
43
48
  import { assertProjectActive, resolveProject } from "../repository/project.js";
44
- import { currentWorkItemCandidate, currentWorkItemExecutionGroup, workItemExecutionGroupById, createWorkItem, editDraftWorkItemDefinition, attachWorkItemExecutionGroup, updateWorkItemExecutionGroup, retireWorkItem, retryFailedWorkItem, submitWorkItemCandidate, updateWorkItemWriteProjects, updateWorkItemStatus } from "../workItem/workItem.js";
49
+ import { currentWorkItemCandidate, currentWorkItemExecutionGroup, workItemExecutionGroupById, createWorkItem, editWorkItemDefinition, attachWorkItemExecutionGroup, updateWorkItemExecutionGroup, retireWorkItem, prepareWorkItemDispatch, submitWorkItemCandidate, updateWorkItemWriteProjects, updateWorkItemStatus } from "../workItem/workItem.js";
45
50
  import { assertWorkItemDependenciesCompleted as assertWorkItemDependencyGate, WorkItemDependencyGateError } from "../workItem/dependencyGate.js";
46
- import { createExecutionGroup, createReviewExecutionAssignment, createWorkItemExecutionAssignment, createWorkItemExecutionGroup, MINIMUM_WORK_ITEM_SYNTHESIS_RESULTS, updateExecutionLane as updateUnifiedExecutionLane, updateWorkItemExecutionLane, workItemExecutionGroupSettled } from "../execution/workItemExecution.js";
47
- import { reconcileWorkItemMainTurns, successfulWorkItemSynthesisProducers } from "../execution/workItemMainTurn.js";
48
- import { reconcileReviewMainTurns } from "../execution/reviewMainTurn.js";
51
+ import { createExecutionGroup, createReviewExecutionAssignment, createWorkItemExecutionAssignment, createWorkItemExecutionGroup, updateExecutionLane as updateUnifiedExecutionLane, updateWorkItemExecutionLane, workItemExecutionGroupSettled } from "../execution/workItemExecution.js";
52
+ import { dispatchWorkItemSynthesis, selectedWorkItemSynthesisProducers } from "../execution/workItemMainRun.js";
53
+ import { dispatchReviewSynthesis, selectedReviewSynthesisProducers } from "../execution/reviewMainRun.js";
54
+ import { synthesisSourceRunIds } from "../context/runContextPack.js";
49
55
  import { projectWorkItemExecution } from "../execution/workItemExecutionProjection.js";
50
56
  import { sameTaskFinalReviewContract, taskFinalReviewConfig, validateTaskFinalReviewContract } from "../review/taskFinalReviewContract.js";
51
57
  import { resolveRecordedTaskFinalReviewContract } from "../review/taskFinalReviewContractResolution.js";
52
58
  import { managedWorkspaceKey } from "../worktree/managedWorkspace.js";
53
59
  import { hasAgentConfigOptions, hasNoRoleMutation, parseRoleOptions, patchRoleAgentBinding, roleOptionSpecs, roleProfilePatch } from "./roleConfiguration.js";
54
60
  import { hasRoleLaunchContextOptions, validateConfiguredRoleSkills } from "./roleSkillValidation.js";
55
- import { assertLiveRoleSessionAcknowledged, assertRoleRuntimeMutationAllowed, LIVE_SESSION_ACKNOWLEDGEMENT_OPTION } from "./roleRuntimeGuard.js";
61
+ import { assertRoleRuntimeMutationAllowed } from "./roleRuntimeGuard.js";
56
62
  import { runTaskContextCommand } from "./taskContextCommand.js";
63
+ import { listContextMessages } from "../context/taskContext.js";
64
+ import { createProjectResources, validateArtifactInput } from "../resources/projectResourceService.js";
65
+ import { artifactSummary } from "../resources/projectResource.js";
57
66
  import { runTaskNextActionCommand } from "./taskNextActionCommand.js";
58
67
  import { runDeliveryGuardPreflight, withGuardWarnings } from "./deliveryGuardPreflight.js";
59
- import { inspectTaskRoleRuntimeStatuses, renderTaskRoleRuntimeStatus, taskRoleActiveWorkLabel, taskRoleLastTurnLabel, taskRoleNativeSessionLabel, taskRoleOpenInputLabel, taskRoleTmuxLabel } from "./taskRoleRuntimeStatus.js";
68
+ import { inspectTaskRoleRuntimeStatuses, renderTaskRoleRuntimeStatus, taskRoleActiveWorkLabel, taskRoleLastRunLabel, taskRoleNativeSessionLabel, taskRoleOpenInputLabel, taskRoleTmuxLabel } from "./taskRoleRuntimeStatus.js";
60
69
  import { assertNoOpenInputRequests, openInputRequestCount, runTaskInputCommand } from "./taskInputCommands.js";
61
70
  import { runGrantCommand } from "./grantCommands.js";
62
71
  import { runWorkflowCommand } from "./workflowCommands.js";
63
- import { taskLocalActor as resolveTaskLocalActor, taskLeaderActionTurnId } from "./taskActor.js";
64
- import { currentManagedRuntime } from "../runtime/managedCaller.js";
72
+ import { taskLocalActor as resolveTaskLocalActor, assertTaskDeliveryAuthority } from "./taskActor.js";
73
+ import { currentManagedRuntime, resolveManagedTaskReader } from "../runtime/managedCaller.js";
74
+ import { resolveMessageRecipient, messageContinuationBlocker } from "../message/messageContinuation.js";
65
75
  import { enqueueOperatorEvent } from "../scheduler/operatorEvent.js";
66
76
  import { queueLeaderWakeup } from "../scheduler/wakeupQueue.js";
67
77
  import { renderWakeReason, wakeReason } from "../scheduler/wakeReason.js";
@@ -181,8 +191,11 @@ export function previewTaskRoleAgentConfigurationMutation(args, store) {
181
191
  }
182
192
  const parsed = parseRoleOptions(tail, new Map([
183
193
  ...roleOptionSpecs({ update: true, includeAgent: true }),
184
- ["--profile", "value"]
194
+ ["--profile", "value"],
195
+ ["--environment", "value"],
196
+ ["--managed-environment", "flag"]
185
197
  ]), usage);
198
+ validateTaskRoleEnvironmentOptions(parsed, usage);
186
199
  if (parsed.has("--agent") && (parsed.one("--agent")?.trim().length ?? 0) === 0) {
187
200
  throw usageError("--agent is required.", usage);
188
201
  }
@@ -201,7 +214,7 @@ export function previewTaskRoleAgentConfigurationMutation(args, store) {
201
214
  }
202
215
  export function parseTaskCompletionRequest(args, summaryOverride) {
203
216
  const usage = "Task complete usage: yui task complete <id> (--summary <text>|--summary-file <path|->) [--refresh-remote] [--accept-published-tree <publication-id>].";
204
- const parsed = parseTail(args, new Set(["--summary", "--summary-file", "--accept-published-tree"]), usage, new Set(["--refresh-remote"]));
217
+ const parsed = parseMultiValueTail(args, new Set(["--summary", "--summary-file", "--accept-published-tree"]), new Set(["--artifact-ref"]), usage, new Set(["--refresh-remote"]));
205
218
  exactPositionals(parsed.positionals, 1, usage);
206
219
  const inlineSummary = parsed.options.get("--summary");
207
220
  const summaryFile = parsed.options.get("--summary-file");
@@ -213,6 +226,7 @@ export function parseTaskCompletionRequest(args, summaryOverride) {
213
226
  return {
214
227
  taskId: parsed.positionals[0],
215
228
  summary,
229
+ artifactRefs: parsed.multiOptions.get("--artifact-ref") ?? [],
216
230
  ...(acceptedPublishedTreePublicationId === undefined
217
231
  ? {}
218
232
  : { acceptedPublishedTreePublicationId })
@@ -233,6 +247,7 @@ export function parseTaskCompletionRequest(args, summaryOverride) {
233
247
  export function preflightTaskCompletion(taskId, store, options = {}, request = {}) {
234
248
  const task = requireTask(store, taskId);
235
249
  const actor = taskActor(store, options, task.id);
250
+ assertTaskDeliveryAuthority(store, options.environment, task.id);
236
251
  if (task.status === "completed") {
237
252
  return { task, actor, completed: true, activeTaskReview: false };
238
253
  }
@@ -279,19 +294,67 @@ function formatCompletionBlockers(taskId, blockers) {
279
294
  }
280
295
  export function runTaskCommand(args, store, options = {}) {
281
296
  const [command, ...rest] = args;
297
+ const delivery = command === "complete"
298
+ || (command === "work" && ["dispatch", "synthesize", "review", "accept"].includes(rest[0] ?? ""))
299
+ || (command === "review" && !["list", "show"].includes(rest[0] ?? ""))
300
+ || ((command === "run" || command === "run") && rest[0] === "retry");
301
+ if (delivery && options.environment?.YUI_SESSION_SCOPE === "task"
302
+ && options.environment.YUI_TASK_ID !== undefined) {
303
+ assertTaskDeliveryAuthority(store, options.environment, options.environment.YUI_TASK_ID);
304
+ }
282
305
  switch (command) {
306
+ case "artifact": {
307
+ const [action, taskId, value] = rest;
308
+ if (!taskId || !["list", "show", "save"].includes(action)
309
+ || rest.length !== (action === "list" ? 2 : 3)) {
310
+ throw usageError("Usage: yui task artifact list <task> | show <task> <artifact-id> | save <task> <artifact-json>");
311
+ }
312
+ if (options.environment?.YUI_SESSION_SCOPE === "task" && options.environment.YUI_TASK_ID !== taskId) {
313
+ throw usageError("Artifact is outside the managed Task scope.");
314
+ }
315
+ requireTask(store, taskId);
316
+ let data;
317
+ if (action === "list")
318
+ data = store.listArtifacts(taskId).map(artifactSummary);
319
+ else if (action === "show") {
320
+ data = store.getArtifact(taskId, value);
321
+ if (data === null)
322
+ throw usageError("Artifact not found in this Task.");
323
+ }
324
+ else {
325
+ taskActor(store, options, taskId);
326
+ let parsed;
327
+ try {
328
+ parsed = JSON.parse(value);
329
+ }
330
+ catch {
331
+ throw usageError("Artifact input must be JSON.");
332
+ }
333
+ let input;
334
+ try {
335
+ input = validateArtifactInput(parsed);
336
+ }
337
+ catch (error) {
338
+ throw usageError(`Artifact input is invalid: ${error instanceof Error ? error.message : String(error)}`);
339
+ }
340
+ data = createProjectResources(store).saveArtifact(taskId, input);
341
+ }
342
+ return output(JSON.stringify(data, null, 2), data);
343
+ }
283
344
  case "create": return createTaskCommand(rest, store, options);
284
345
  case "update": return output(updateTaskCommand(rest, store, options));
285
346
  case "list": return listTaskCommand(rest, store);
286
347
  case "show": return showTaskCommand(rest, store, options.actualTaskReviewCandidate ?? null);
287
- case "context": return runTaskContextCommand(rest, store, options.actualTaskReviewCandidate ?? null);
348
+ case "context": return runTaskContextCommand(rest, store, options.environment);
288
349
  case "next-action": return runTaskNextActionCommand(rest, store, options.actualTaskReviewCandidate ?? null);
289
350
  case "remote-delivery": return runTaskRemoteDeliveryCommand(rest, store, options.actualTaskReviewCandidate ?? null);
290
351
  case "activate": return output(activateTaskCommand(rest, store, options));
352
+ case "activation": return runTaskActivationCommand(rest, store, options);
291
353
  case "complete": return completeTaskCommand(rest, store, options);
292
354
  case "reopen": return output(reopenTaskCommand(rest, store, options));
293
355
  case "archive": return output(archiveTaskCommand(rest, store, options));
294
356
  case "retire": return retireTaskCommand(rest, store, options);
357
+ case "cancel": return cancelTaskCommand(rest, store, options);
295
358
  case "reconcile": return output(reconcileTaskCommand(rest, store, options));
296
359
  case "message": {
297
360
  const execution = taskMessageCommand(rest, store, options);
@@ -306,7 +369,8 @@ export function runTaskCommand(args, store, options = {}) {
306
369
  case "role": return taskRoleCommand(rest, store, options);
307
370
  case "work": return taskWorkCommand(rest, store, options);
308
371
  case "review": return taskReviewCommand(rest, store, options);
309
- case "turn": return taskTurnCommand(rest, store, options);
372
+ case "run":
373
+ case "run": return taskRunCommand(rest, store, options);
310
374
  case "brief": return taskBriefCommand(rest, store, options);
311
375
  case "decision": return taskDecisionCommand(rest, store, options);
312
376
  case "milestone": return taskMilestoneCommand(rest, store, options);
@@ -356,21 +420,21 @@ function workItemCandidateProducerRoles(store, item, candidate) {
356
420
  roles.add(LEADER_ROLE);
357
421
  return roles;
358
422
  }
359
- const sourceTurn = store.getTurn(item.taskId, candidate.source.turnId);
360
- if (sourceTurn === null
361
- || sourceTurn.workItemId !== item.id
362
- || sourceTurn.purpose !== "execution"
363
- || sourceTurn.status !== "completed") {
364
- throw dataError(`WorkItem Candidate producer Turn is unavailable: ${item.id}/${candidate.source.turnId}.`);
423
+ const sourceRun = store.getRun(item.taskId, candidate.source.runId);
424
+ if (sourceRun === null
425
+ || sourceRun.workItemId !== item.id
426
+ || sourceRun.purpose !== "execution"
427
+ || sourceRun.status !== "completed") {
428
+ throw dataError(`WorkItem Candidate producer AgentRun is unavailable: ${item.id}/${candidate.source.runId}.`);
365
429
  }
366
- roles.add(sourceTurn.roleName);
367
- if (sourceTurn.sourceExecutionGroupId !== undefined) {
368
- const group = workItemExecutionGroupById(item, sourceTurn.sourceExecutionGroupId);
430
+ roles.add(sourceRun.roleName);
431
+ if (sourceRun.sourceExecutionGroupId !== undefined) {
432
+ const group = workItemExecutionGroupById(item, sourceRun.sourceExecutionGroupId);
369
433
  if (group === undefined) {
370
434
  throw dataError(`WorkItem Candidate ExecutionGroup is unavailable: `
371
- + `${item.id}/${sourceTurn.sourceExecutionGroupId}.`);
435
+ + `${item.id}/${sourceRun.sourceExecutionGroupId}.`);
372
436
  }
373
- for (const producer of successfulWorkItemSynthesisProducers(store, item, group)) {
437
+ for (const producer of selectedWorkItemSynthesisProducers(store, item, group, synthesisSourceRunIds(store, sourceRun))) {
374
438
  roles.add(producer.roleName);
375
439
  }
376
440
  }
@@ -471,40 +535,50 @@ function updateTaskCommand(args, store, options) {
471
535
  const tags = parsed.options.has("--tags")
472
536
  ? parseTaskTags(requiredOption(parsed.options, "--tags"))
473
537
  : undefined;
538
+ const result = updateTaskMetadataCommand(store, parsed.positionals[0], {
539
+ ...(parsed.options.has("--title") ? { title: requiredOption(parsed.options, "--title") } : {}),
540
+ ...(parsed.options.has("--type")
541
+ ? { type: requiredOption(parsed.options, "--type") }
542
+ : parsed.options.has("--clear-type") ? { type: null } : {}),
543
+ ...(parsed.options.has("--description")
544
+ ? { description: requiredOption(parsed.options, "--description") }
545
+ : parsed.options.has("--clear-description") ? { description: null } : {}),
546
+ ...(priority === undefined
547
+ ? parsed.options.has("--clear-priority") ? { priority: null } : {}
548
+ : { priority }),
549
+ ...(tags === undefined
550
+ ? parsed.options.has("--clear-tags") ? { tags: null } : {}
551
+ : { tags }),
552
+ ...(dueAt === undefined
553
+ ? parsed.options.has("--clear-due-at") ? { dueAt: null } : {}
554
+ : { dueAt })
555
+ }, options);
556
+ return `Updated task ${result.id}\n`;
557
+ }
558
+ /** Shared domain transaction for structured capabilities and the legacy CLI.
559
+ * Parsing text flags must not become an alternate business write path. */
560
+ export function updateTaskMetadataCommand(store, taskId, patch, options = {}) {
561
+ if (Object.keys(patch).length === 0)
562
+ throw usageError("At least one Task metadata field is required.");
474
563
  const now = clock(options);
475
564
  const result = store.transaction((tx) => {
476
- const current = requireTask(tx, parsed.positionals[0]);
565
+ const current = requireTask(tx, taskId);
477
566
  if (current.status === "archived")
478
567
  throw usageError(`Task is archived: ${current.id}.`);
479
568
  taskActor(tx, options, current.id);
480
- const updated = updateTaskMetadata(current, {
481
- ...(parsed.options.has("--title") ? { title: requiredOption(parsed.options, "--title") } : {}),
482
- ...(parsed.options.has("--type")
483
- ? { type: requiredOption(parsed.options, "--type") }
484
- : parsed.options.has("--clear-type") ? { type: null } : {}),
485
- ...(parsed.options.has("--description")
486
- ? { description: requiredOption(parsed.options, "--description") }
487
- : parsed.options.has("--clear-description") ? { description: null } : {}),
488
- ...(priority === undefined
489
- ? parsed.options.has("--clear-priority") ? { priority: null } : {}
490
- : { priority }),
491
- ...(tags === undefined
492
- ? parsed.options.has("--clear-tags") ? { tags: null } : {}
493
- : { tags }),
494
- ...(dueAt === undefined
495
- ? parsed.options.has("--clear-due-at") ? { dueAt: null } : {}
496
- : { dueAt })
497
- }, now);
569
+ const updated = updateTaskMetadata(current, patch, now);
498
570
  tx.saveTask(updated);
499
571
  recordTaskEvent(tx, updated.id, "task.updated", {
500
572
  status: updated.status,
573
+ previous: editedFieldValues(current, Object.keys(patch)),
574
+ current: editedFieldValues(updated, Object.keys(patch)),
501
575
  ...(updated.type === undefined ? {} : { taskType: updated.type })
502
576
  }, now);
503
577
  enqueueWork(tx, taskMailbox(updated.id), "task-updated", now, [taskRef(updated.id)]);
504
578
  return updated;
505
579
  });
506
580
  notifyMailbox(options.runtime, taskMailbox(result.id), result.id);
507
- return `Updated task ${result.id}\n`;
581
+ return result;
508
582
  }
509
583
  export function submitOperatorMessage(body, taskId, store, options = {}) {
510
584
  const now = clock(options);
@@ -513,16 +587,17 @@ export function submitOperatorMessage(body, taskId, store, options = {}) {
513
587
  const task = requireTask(tx, taskId);
514
588
  assertTaskOpen(task);
515
589
  const message = appendMessage(tx, task.id, body, "operator", { type: "operator" }, now);
516
- if (task.status === "active") {
590
+ if (leaderWakingTaskStatus(task.status)) {
517
591
  enqueueWork(tx, leaderMailbox(task.id), "operator-input", now, [messageRef(task.id, message.id)]);
518
592
  }
519
593
  return { task, message, created: false };
520
594
  }
521
595
  const created = createTaskAggregate(tx, titleFrom(body), {}, now);
522
596
  const message = appendMessage(tx, created.task.id, body, "operator", { type: "operator" }, now);
597
+ enqueueWork(tx, leaderMailbox(created.task.id), "operator-input", now, [messageRef(created.task.id, message.id)]);
523
598
  return { ...created, message, created: true };
524
599
  });
525
- notifyMailbox(options.runtime, result.task.status === "active" ? leaderMailbox(result.task.id) : taskMailbox(result.task.id), result.task.id);
600
+ notifyMailbox(options.runtime, leaderWakingTaskStatus(result.task.status) ? leaderMailbox(result.task.id) : taskMailbox(result.task.id), result.task.id);
526
601
  return result.created
527
602
  ? `Created Draft task ${result.task.id}: ${result.task.title}\nSubmitted message ${result.message.id}\n`
528
603
  : `Submitted message ${result.message.id} to ${result.task.id}\n`;
@@ -649,7 +724,7 @@ function showTaskCommand(args, store, currentTaskCandidate) {
649
724
  events: events.length,
650
725
  workItems: work.length,
651
726
  currentWorkItems: currentWorkItemCount,
652
- turns: store.listTurns(task.id).length,
727
+ runs: store.listRuns(task.id).length,
653
728
  changeSets: changeSets.length,
654
729
  integrations: integrations.length,
655
730
  publications: publications.length,
@@ -690,7 +765,7 @@ function showTaskCommand(args, store, currentTaskCandidate) {
690
765
  `Events: ${counts.events}`,
691
766
  `Work items: ${counts.workItems}`,
692
767
  `Current work items: ${currentWorkItemCount}`,
693
- `Turns: ${counts.turns}`,
768
+ `AgentRuns: ${counts.runs}`,
694
769
  `ChangeSets: ${counts.changeSets}`,
695
770
  `Integration Attempts: ${counts.integrations}`,
696
771
  `Publication references: ${counts.publications} (${verifiedMergedPublications} verified merged)`,
@@ -713,7 +788,7 @@ function activateTaskCommand(args, store, options) {
713
788
  const task = requireTask(tx, args[0]);
714
789
  if (task.status === "archived")
715
790
  throw usageError(`Task is archived: ${task.id}.`);
716
- if (task.status === "retired")
791
+ if (task.status === "cancelled")
717
792
  throw usageError(`Task is retired: ${task.id}.`);
718
793
  if (task.status === "completed") {
719
794
  throw usageError(`Task ${task.id} is completed; use task reopen before activating it.`);
@@ -726,13 +801,26 @@ function activateTaskCommand(args, store, options) {
726
801
  || activation.task.id !== task.id
727
802
  || activation.task.status !== "active"
728
803
  || !isDeepStrictEqual(activation.task.workspaceIdentity, task.workspaceIdentity)
729
- || activation.path !== task.cwd
730
- || workspace === null
731
- || workspace.owner.type !== "task"
732
- || workspace.owner.taskId !== task.id
733
- || workspace.root !== task.cwd) {
804
+ || activation.path !== task.cwd) {
734
805
  throw usageError(`Task workspace activation proof does not match ${task.id}.`);
735
806
  }
807
+ // An empty environment plan over no bound Project is a legal Task shape,
808
+ // so the proof of adoption is the *absence* of a workspace rather than a
809
+ // ManagedWorkspace record. Demanding one here would have forced every
810
+ // such activation to create a worktree purely to satisfy this check.
811
+ if (taskOwnsManagedWorkspace(task)) {
812
+ if (workspace === null
813
+ || workspace.owner.type !== "task"
814
+ || workspace.owner.taskId !== task.id
815
+ || workspace.root !== task.cwd) {
816
+ throw usageError(`Task workspace activation proof does not match ${task.id}.`);
817
+ }
818
+ }
819
+ else if (workspace !== null
820
+ || task.workspaceIdentity !== undefined
821
+ || activation.path !== undefined) {
822
+ throw usageError(`Workspace-free Task activation proof claims a workspace: ${task.id}.`);
823
+ }
736
824
  return { task, changed: activation.changed };
737
825
  }
738
826
  throw usageError(`Task ${task.id} activation requires atomic workspace adoption through the CLI preflight.`);
@@ -782,7 +870,7 @@ function completeTaskCommand(args, store, options) {
782
870
  };
783
871
  }
784
872
  // Completion is a Task decision only. The current Provider Turn remains
785
- // responsible for closing its own Turn, and the reusable Session keeps
873
+ // responsible for closing its own AgentRun, and the reusable Session keeps
786
874
  // its independent lifecycle.
787
875
  // Issue 06: re-validate the full completion readiness inside the
788
876
  // transaction (the CAS fence) after final-review preparation. This is the
@@ -795,7 +883,21 @@ function completeTaskCommand(args, store, options) {
795
883
  if (!readiness.ready) {
796
884
  throw usageError(formatCompletionBlockers(task.id, readiness.blockers));
797
885
  }
798
- const completed = completeTask(task, now, { by: actor, summary });
886
+ for (const ref of request.artifactRefs) {
887
+ if (ref.startsWith("artifact-")) {
888
+ fixedArtifactRefs(tx, task.id, [ref]);
889
+ }
890
+ else if (ref.startsWith("turn:")) {
891
+ const run = tx.getRun(task.id, ref.slice("turn:".length));
892
+ if (run === null || run.result === undefined) {
893
+ throw usageError(`Task completion result ref is not readable: ${ref}.`);
894
+ }
895
+ }
896
+ else if (!/^https?:\/\/[^\s]+$/u.test(ref)) {
897
+ throw usageError("Completion --artifact-ref must be a saved artifact id, turn:<local-turn-id>, or an explicit HTTP(S) reference URL.");
898
+ }
899
+ }
900
+ const completed = completeTask(task, now, { by: actor, summary, artifactRefs: request.artifactRefs });
799
901
  tx.saveTask(completed);
800
902
  tx.clearPendingWakeup(task.id);
801
903
  tx.clearLeaderFailure(task.id);
@@ -826,6 +928,10 @@ function completeTaskCommand(args, store, options) {
826
928
  const terminalEvent = recordTaskEvent(tx, task.id, "task.completed", {
827
929
  by: actor,
828
930
  summary,
931
+ ...(completed.completionArtifactRefs === undefined ? {} : {
932
+ artifactRefs: JSON.stringify(completed.completionArtifactRefs)
933
+ }),
934
+ dispatchHistory: JSON.stringify(taskDispatchMailboxes(tx, task.id)),
829
935
  ...(completedProjectHeads === undefined
830
936
  ? {}
831
937
  : { projectHeads: completedProjectHeads }),
@@ -842,7 +948,7 @@ function completeTaskCommand(args, store, options) {
842
948
  // discarded at this lifecycle boundary.
843
949
  tx.removeWorkMailbox(taskMailbox(task.id));
844
950
  // Role mailboxes are also derived wake state. A Worker result or runtime
845
- // signal queued while the final Turn was being settled must not survive a
951
+ // signal queued while the final AgentRun was being settled must not survive a
846
952
  // completed Task and become actionable after a later explicit reopen.
847
953
  for (const role of roles) {
848
954
  tx.removeWorkMailbox(roleMailbox(task.id, role.name));
@@ -887,19 +993,39 @@ function reopenTaskCommand(args, store, options) {
887
993
  return { task, changed: false };
888
994
  if (task.status === "archived")
889
995
  throw usageError(`Task is archived: ${task.id}.`);
890
- if (task.status !== "completed")
891
- throw usageError(`Task is not completed: ${task.id}.`);
996
+ if (task.status !== "completed" && task.status !== "cancelled") {
997
+ throw usageError(`Task is not completed or cancelled: ${task.id}.`);
998
+ }
999
+ const actor = taskActor(tx, options, task.id);
1000
+ if (task.status === "cancelled" && actor === "leader") {
1001
+ throw usageError("Restoring a cancelled Task requires user or Operator authority.");
1002
+ }
1003
+ const dispatchHistory = JSON.stringify(taskDispatchMailboxes(tx, task.id));
1004
+ // Reopening changes Task intent, not the disposition of existing inputs.
1005
+ // In particular, external messages and unknown deliveries remain owned by
1006
+ // their existing mailbox fences.
892
1007
  const active = reopenTask(task, now);
893
1008
  tx.saveTask(active);
894
1009
  const reopenedReason = wakeReason("task-reopened");
895
- enqueueWork(tx, leaderMailbox(task.id), reopenedReason, now, [taskRef(task.id)]);
1010
+ if (actor !== "leader") {
1011
+ enqueueWork(tx, leaderMailbox(task.id), reopenedReason, now, [taskRef(task.id)]);
1012
+ }
896
1013
  enqueueWork(tx, taskMailbox(task.id), reopenedReason, now, [taskRef(task.id)]);
897
- recordTaskEvent(tx, task.id, "task.reopened", { status: active.status }, now);
898
- return { task: active, changed: true };
1014
+ recordTaskEvent(tx, task.id, "task.reopened", {
1015
+ status: active.status, by: actor, historicalInputs: "preserved", dispatchHistory,
1016
+ ...(actor === "leader" ? leaderActionEventPayload(tx, task.id, options) : {}),
1017
+ previous: editedFieldValues(task, [
1018
+ "status", "completedAt", "completedBy", "completionSummary", "completionArtifactRefs",
1019
+ "retiredAt", "retiredBy", "retirementSummary", "replacementTaskId", "retirementIsolation"
1020
+ ])
1021
+ }, now);
1022
+ return { task: active, changed: true, wakeLeader: actor !== "leader" };
899
1023
  });
900
1024
  if (result.changed) {
901
1025
  notifyMailbox(options.runtime, taskMailbox(result.task.id), result.task.id);
902
- notifyMailbox(options.runtime, leaderMailbox(result.task.id), result.task.id);
1026
+ if (result.wakeLeader) {
1027
+ notifyMailbox(options.runtime, leaderMailbox(result.task.id), result.task.id);
1028
+ }
903
1029
  }
904
1030
  return result.changed
905
1031
  ? `Reopened task ${result.task.id}\n`
@@ -914,13 +1040,17 @@ function archiveTaskCommand(args, store, options) {
914
1040
  if (task.status === "archived")
915
1041
  return { task, changed: false };
916
1042
  if (task.status !== "completed"
917
- && task.status !== "retired") {
1043
+ && task.status !== "cancelled") {
918
1044
  throw usageError(`Task ${task.id} must be completed or retired before it can be archived.`);
919
1045
  }
920
1046
  const remoteDelivery = request.disposition === "integrated"
921
1047
  ? assertTaskRemoteDeliveryProof(tx, task, options.archiveRemoteDeliveryProof, { forceUnverified: request.forceUnverified })
922
1048
  : undefined;
923
1049
  assertNoOpenInputRequests(tx, task.id, "archiving the Task");
1050
+ const unsettledWork = tx.listWorkItems(task.id).find((item) => item.status === "open");
1051
+ if (unsettledWork !== undefined) {
1052
+ throw usageError(`Work Item ${unsettledWork.id} must be accepted or explicitly retired before archive.`);
1053
+ }
924
1054
  const unresolvedIntegration = tx.listIntegrationAttempts(task.id).find((integration) => (integration.status === "running"
925
1055
  || integration.status === "blocked"
926
1056
  || integration.status === "validating"));
@@ -937,9 +1067,9 @@ function archiveTaskCommand(args, store, options) {
937
1067
  throw usageError(`Task ${task.id} still has managed worktrees; clean them before archiving.`);
938
1068
  }
939
1069
  const activeRole = tx.listRoles(task.id)
940
- .find((role) => tx.getActiveTurn(task.id, role.name) !== null);
1070
+ .find((role) => tx.getActiveRun(task.id, role.name) !== null);
941
1071
  if (activeRole !== undefined) {
942
- throw usageError(`Task ${task.id} still has an active Turn for Role ${activeRole.name}; `
1072
+ throw usageError(`Task ${task.id} still has an active AgentRun for Role ${activeRole.name}; `
943
1073
  + "stop its runtime before archiving.");
944
1074
  }
945
1075
  const liveSessionRole = tx.listRoles(task.id).find((role) => {
@@ -995,6 +1125,41 @@ function archiveTaskCommand(args, store, options) {
995
1125
  ? `Archived task ${result.task.id}\n`
996
1126
  : `Task ${result.task.id} is already archived\n`;
997
1127
  }
1128
+ function cancelTaskCommand(args, store, options) {
1129
+ const usage = "Task cancel usage: yui task cancel <task> (--summary <text>|--summary-file <path|->).";
1130
+ const parsed = parseTail(args, new Set(["--summary", "--summary-file"]), usage);
1131
+ exactPositionals(parsed.positionals, 1, usage);
1132
+ const summary = readCommandText(parsed.options.get("--summary"), parsed.options.get("--summary-file"), "--summary", usage);
1133
+ const now = clock(options);
1134
+ const result = store.transaction((tx) => {
1135
+ const task = requireTask(tx, parsed.positionals[0]);
1136
+ const actor = taskActor(tx, options, task.id);
1137
+ const cancelled = retireTask(task, { by: actor, summary }, now);
1138
+ if (cancelled === task)
1139
+ return task;
1140
+ const dispatchHistory = JSON.stringify(taskDispatchMailboxes(tx, task.id));
1141
+ tx.saveTask(cancelled);
1142
+ tx.clearPendingWakeup(task.id);
1143
+ tx.clearLeaderFailure(task.id);
1144
+ for (const mailbox of tx.listWorkMailboxes()) {
1145
+ if ((mailbox.target.kind === "task" || mailbox.target.kind === "role")
1146
+ && mailbox.target.taskId === task.id)
1147
+ tx.removeWorkMailbox(mailbox.target);
1148
+ }
1149
+ const event = recordTaskEvent(tx, task.id, "task.cancelled", { by: actor, summary, dispatchHistory }, now);
1150
+ enqueueOperatorEvent(tx, event, "task-terminal", now);
1151
+ // Cancellation is intent, not fabricated proof that old processes stopped.
1152
+ // AgentRuns, inputs, WorkItems and their results remain independently readable.
1153
+ return cancelled;
1154
+ });
1155
+ options.runtime?.notifyStateChanged(result.id);
1156
+ notifyMailbox(options.runtime, { kind: "operator" }, result.id);
1157
+ return output(`Cancelled task ${result.id}\n`, { task: result });
1158
+ }
1159
+ /** Historical evidence only; these snapshots are never dispatch input. */
1160
+ function taskDispatchMailboxes(store, taskId) {
1161
+ return store.listWorkMailboxes().filter(({ target }) => (target.kind === "task" || target.kind === "role") && target.taskId === taskId);
1162
+ }
998
1163
  function retireTaskCommand(args, store, options) {
999
1164
  const usage = "Task retire usage: yui task retire <task> (--summary <text>|--summary-file <path|->) [--replacement <task>].";
1000
1165
  const parsed = parseTail(args, new Set(["--summary", "--summary-file", "--replacement"]), usage);
@@ -1006,7 +1171,7 @@ function retireTaskCommand(args, store, options) {
1006
1171
  const result = store.transaction((tx) => {
1007
1172
  const task = requireTask(tx, taskId);
1008
1173
  const actor = taskActor(tx, options, task.id);
1009
- if (task.status === "retired") {
1174
+ if (task.status === "cancelled") {
1010
1175
  const same = task.retirementSummary === summary
1011
1176
  && task.replacementTaskId === replacementTaskId;
1012
1177
  if (!same)
@@ -1035,12 +1200,12 @@ function retireTaskCommand(args, store, options) {
1035
1200
  throw usageError(`Task ${task.id} has an active DurableJob: ${activeRetireJob.id}/${activeRetireJob.status}.`);
1036
1201
  }
1037
1202
  assertTaskRetirementProof(tx, task, options.taskRetirementProof);
1038
- for (const run of tx.listTurns(task.id).filter(({ status: runStatus }) => (runStatus === "active"))) {
1039
- const terminal = terminalizeExactTaskTurn(tx, {
1203
+ for (const run of tx.listRuns(task.id).filter(({ status: runStatus }) => (runStatus === "active"))) {
1204
+ const terminal = terminalizeExactTaskRun(tx, {
1040
1205
  taskId: task.id,
1041
1206
  roleName: run.roleName,
1042
1207
  agentId: run.effective.agentId,
1043
- turnId: run.id,
1208
+ runId: run.id,
1044
1209
  mailboxDisposition: "discard",
1045
1210
  outcome: {
1046
1211
  status: "failed",
@@ -1049,11 +1214,11 @@ function retireTaskCommand(args, store, options) {
1049
1214
  }
1050
1215
  }, now);
1051
1216
  if (terminal.disposition !== "applied") {
1052
- throw usageError(`Task Turn changed during retirement: ${run.id}/${terminal.reason ?? "obsolete"}.`);
1217
+ throw usageError(`Task AgentRun changed during retirement: ${run.id}/${terminal.reason ?? "obsolete"}.`);
1053
1218
  }
1054
1219
  }
1055
1220
  for (const item of tx.listWorkItems(task.id)) {
1056
- if (item.status === "completed" || item.status === "retired")
1221
+ if (item.status === "accepted" || item.status === "retired")
1057
1222
  continue;
1058
1223
  tx.saveWorkItem(task.id, updateWorkItemStatus(item, "retired", now, `Task retired: ${summary}`));
1059
1224
  }
@@ -1074,12 +1239,14 @@ function retireTaskCommand(args, store, options) {
1074
1239
  const retired = retireTask(task, {
1075
1240
  by: actor,
1076
1241
  summary,
1242
+ isolated: true,
1077
1243
  ...(replacementTaskId === undefined ? {} : { replacementTaskId })
1078
1244
  }, now);
1079
1245
  tx.saveTask(retired);
1080
1246
  const terminalEvent = recordTaskEvent(tx, task.id, "task.retired", {
1081
1247
  by: actor,
1082
1248
  summary,
1249
+ isolationEstablished: "true",
1083
1250
  ...(replacementTaskId === undefined ? {} : { replacementTaskId })
1084
1251
  }, now);
1085
1252
  enqueueOperatorEvent(tx, terminalEvent, "task-terminal", now);
@@ -1145,10 +1312,13 @@ function formatProjectCommits(projects) {
1145
1312
  export function validateTaskArchiveRequest(args, store, options = {}) {
1146
1313
  const request = parseTaskArchiveArguments(args);
1147
1314
  const task = requireTask(store, request.taskId);
1148
- taskActor(store, options, task.id);
1315
+ const archiveActor = taskActor(store, options, task.id);
1316
+ if (archiveActor === "leader") {
1317
+ throw usageError("Task archive requires independent user or Operator authorization.");
1318
+ }
1149
1319
  if (task.status !== "archived"
1150
1320
  && task.status !== "completed"
1151
- && task.status !== "retired") {
1321
+ && task.status !== "cancelled") {
1152
1322
  throw usageError(`Task ${task.id} must be completed or retired before it can be archived.`);
1153
1323
  }
1154
1324
  if (task.status !== "archived") {
@@ -1171,9 +1341,55 @@ function reconcileTaskCommand(args, store, options) {
1171
1341
  }
1172
1342
  function taskMessageCommand(args, store, options) {
1173
1343
  const [command, ...rest] = args;
1344
+ if (command === "handoff") {
1345
+ const usage = "Task message handoff usage: yui task message handoff <task/message> --to <role>.";
1346
+ const parsed = parseTail(rest, new Set(["--to"]), usage);
1347
+ exactPositionals(parsed.positionals, 1, usage);
1348
+ const ref = taskRecordReference(parsed.positionals[0], "message", "Message reference", options);
1349
+ const now = clock(options);
1350
+ const message = store.transaction((tx) => {
1351
+ assertTaskDeliveryAuthority(tx, options.environment, ref.taskId);
1352
+ const current = tx.listMessages(ref.taskId).find((entry) => entry.id === ref.localId);
1353
+ if (current?.recipient?.ownerRunId === undefined || current.continuation?.runId !== undefined) {
1354
+ throw usageError("Only an unassigned pending Message can be explicitly handed off.");
1355
+ }
1356
+ const recipient = resolveMessageRecipient(tx, ref.taskId, requiredOption(parsed.options, "--to"), {
1357
+ ...(current.recipient.workItemId === undefined ? {} : { workItemId: current.recipient.workItemId }),
1358
+ ...(current.recipient.reviewRoundId === undefined ? {} : { reviewRoundId: current.recipient.reviewRoundId })
1359
+ });
1360
+ const updated = { ...current, recipient, continuation: {},
1361
+ handovers: [...(current.handovers ?? []), { from: current.recipient, at: now.toISOString() }] };
1362
+ const blocker = messageContinuationBlocker(tx, updated);
1363
+ if (blocker !== undefined)
1364
+ throw usageError(`Message handoff cannot proceed: ${blocker}.`);
1365
+ tx.updateMessage(ref.taskId, updated);
1366
+ recordTaskEvent(tx, ref.taskId, "message.handed-off", {
1367
+ messageId: current.id, fromRole: current.recipient.roleName, toRole: recipient.roleName,
1368
+ ownerRunId: recipient.ownerRunId
1369
+ }, now);
1370
+ enqueueWork(tx, taskMailbox(ref.taskId), "message-continuation", now, [messageRef(ref.taskId, current.id)]);
1371
+ return updated;
1372
+ });
1373
+ notifyMailbox(options.runtime, taskMailbox(ref.taskId), ref.taskId);
1374
+ return output(`Handed off Message ${ref.localId} to ${message.recipient.roleName}.\n`, message);
1375
+ }
1376
+ if (command === "show") {
1377
+ const usage = "Task message show usage: yui task message show <task/message>.";
1378
+ exactPositionals(rest, 1, usage);
1379
+ const ref = taskRecordReference(rest[0], "message", "Message reference", options);
1380
+ const message = listContextMessages(store, ref.taskId, options.environment)
1381
+ .find((entry) => entry.id === ref.localId);
1382
+ if (message === undefined)
1383
+ throw dataError("Message is unavailable in the caller's scope.");
1384
+ const continuationRun = message.continuation?.runId === undefined
1385
+ ? null : store.getRun(ref.taskId, message.continuation.runId);
1386
+ const expanded = { ...expandTaskMessageResult(message, (task, run) => store.getRun(task, run)),
1387
+ ...(continuationRun === null ? {} : { execution: runExecutionObservation(continuationRun, store.getTaskRoleSessionSet(ref.taskId, continuationRun.roleName)?.providerBinding, store.listEvents(ref.taskId)) }) };
1388
+ return { kind: "output", output: `${JSON.stringify(expanded, null, 2)}\n`, data: expanded };
1389
+ }
1174
1390
  if (command === "send") {
1175
- const usage = "Task message send usage: yui task message send <id> (<body>|--body-file <path|->) [--wake-policy leader|none].";
1176
- const parsed = parseTail(rest, new Set(["--body-file", "--wake-policy"]), usage);
1391
+ const usage = "Task message send usage: yui task message send <id> (<body>|--body-file <path|->) [--wake-policy leader|none] [--to <role> --work-item <id>|--review-round <id>].";
1392
+ const parsed = parseTail(rest, new Set(["--body-file", "--wake-policy", "--to", "--work-item", "--review-round"]), usage);
1177
1393
  if (parsed.positionals.length < 1 || parsed.positionals.length > 2)
1178
1394
  throw usageError(usage);
1179
1395
  const body = readCommandText(parsed.positionals[1], parsed.options.get("--body-file"), "--body", usage);
@@ -1188,42 +1404,24 @@ function taskMessageCommand(args, store, options) {
1188
1404
  else {
1189
1405
  throw usageError(`--wake-policy must be 'leader' or 'none': ${wakePolicyRaw}.`);
1190
1406
  }
1191
- const now = clock(options);
1192
- const result = store.transaction((tx) => {
1193
- const task = requireTask(tx, parsed.positionals[0]);
1194
- assertTaskOpen(task);
1195
- const actor = taskActor(tx, options, task.id);
1196
- const message = actor === "leader"
1197
- ? appendMessage(tx, task.id, body, "role-result", { type: "role", roleName: LEADER_ROLE }, now)
1198
- : actor === "operator"
1199
- ? appendMessage(tx, task.id, body, "operator", { type: "operator" }, now, { wakePolicy })
1200
- : appendMessage(tx, task.id, body, "user", { type: "user" }, now, { wakePolicy });
1201
- // Issue 05: only `wakePolicy=leader` (the default for backward
1202
- // compatibility) enqueues Leader work. `wakePolicy=none` persists the
1203
- // message as context without waking the Leader.
1204
- if (task.status === "active"
1205
- && actor !== "leader"
1206
- && wakePolicy !== "none") {
1207
- enqueueWork(tx, leaderMailbox(task.id), actor === "operator" ? "operator-input" : "user-message", now, [messageRef(task.id, message.id)], {
1208
- source: actor,
1209
- dedupeKey: `message:${task.id}:${message.id}`
1210
- });
1211
- }
1212
- return { task, message, actor };
1213
- });
1214
- if (result.actor !== "leader") {
1215
- notifyMailbox(options.runtime, result.task.status === "active"
1216
- ? leaderMailbox(result.task.id)
1217
- : taskMailbox(result.task.id), result.task.id);
1218
- }
1219
- return `Sent message ${result.message.id} to ${result.task.id}\n`;
1407
+ const recipientRole = parsed.options.get("--to");
1408
+ const workItemId = parsed.options.get("--work-item");
1409
+ const reviewRoundId = parsed.options.get("--review-round");
1410
+ if (recipientRole === undefined && (workItemId !== undefined || reviewRoundId !== undefined))
1411
+ throw usageError("--to is required for scoped Message delivery.");
1412
+ const result = sendTaskMessageCommand(store, parsed.positionals[0], body, wakePolicy, options, recipientRole === undefined ? undefined : { roleName: recipientRole, workItemId, reviewRoundId });
1413
+ const reason = result.message.continuation?.notDeliveredReason;
1414
+ const delivery = reason !== undefined ? { state: "not-delivered", reason }
1415
+ : recipientRole !== undefined || result.actor !== "leader" && wakePolicy !== "none"
1416
+ ? { state: "queued" } : { state: "saved" };
1417
+ return output(`Saved message ${result.message.id} to ${result.task.id} (${delivery.state}${reason === undefined ? "" : `: ${reason}`}).\n`, { taskId: result.task.id, message: result.message, delivery });
1220
1418
  }
1221
1419
  if (command === "list") {
1222
1420
  const messageListUsage = "Task message list usage: yui task message list <id> [--after <timestamp>] [--limit <n>].";
1223
1421
  const parsed = parseTail(rest, new Set(["--after", "--limit"]), messageListUsage);
1224
1422
  exactPositionals(parsed.positionals, 1, messageListUsage);
1225
1423
  const task = requireTask(store, parsed.positionals[0]);
1226
- let messages = store.listMessages(task.id);
1424
+ let messages = listContextMessages(store, task.id, options.environment);
1227
1425
  const after = optionalNonEmptyOption(parsed.options, "--after");
1228
1426
  if (after !== undefined) {
1229
1427
  const afterMs = Date.parse(after);
@@ -1271,6 +1469,100 @@ function taskMessageCommand(args, store, options) {
1271
1469
  ? "Task message command is required."
1272
1470
  : `Unknown command: task message ${command}`);
1273
1471
  }
1472
+ /** CLI and authenticated user Surface share the same message and mailbox
1473
+ * transaction. Talking to Leader does not impersonate Leader authority. */
1474
+ /**
1475
+ * The one transaction that turns an inbound Task Message into durable facts and,
1476
+ * when the Task's own lifecycle calls for it, Leader work.
1477
+ *
1478
+ * CLI `task message send` and the Web Task surface both call this, so neither
1479
+ * owns a private notion of what "sending a message" means. The Draft case is the
1480
+ * reason that matters here: a Draft's Leader conversation is its planning Turn,
1481
+ * so a Draft must wake its Leader exactly like an active Task does. Gating the
1482
+ * wake on `active` would leave the Draft entry point saving text that no Leader
1483
+ * ever reads — the message would be persisted and silently go nowhere, which is
1484
+ * indistinguishable to the user from a Provider that never answered.
1485
+ *
1486
+ * The wake carries intent only. Whether the resulting Turn is planning or
1487
+ * execution is decided by the Controller from the Task's own status, so this
1488
+ * function never names a purpose and no second planning path exists.
1489
+ */
1490
+ export function sendTaskMessageCommand(store, taskId, body, wakePolicy, options = {}, recipient) {
1491
+ if (!body.trim())
1492
+ throw usageError("Message body is required.");
1493
+ if (recipient !== undefined && wakePolicy !== undefined) {
1494
+ throw usageError("--wake-policy applies only to unaddressed Leader Messages; an owner-directed Message uses its exact continuation boundary.");
1495
+ }
1496
+ const now = clock(options);
1497
+ const result = store.transaction((tx) => {
1498
+ const task = requireTask(tx, taskId);
1499
+ if (recipient === undefined)
1500
+ assertTaskOpen(task);
1501
+ const caller = currentManagedRuntime(tx, options.environment, task.id);
1502
+ const roleCaller = caller !== undefined && caller.roleName !== "leader" ? caller : undefined;
1503
+ if (roleCaller !== undefined) {
1504
+ const run = roleCaller.currentRunId === undefined ? null : tx.getRun(task.id, roleCaller.currentRunId);
1505
+ if (run === null || recipient?.roleName !== "leader"
1506
+ || recipient.workItemId !== run.workItemId || recipient.reviewRoundId !== run.reviewRoundId) {
1507
+ throw usageError("A Worker or Reviewer may send only to Leader within its current Assignment.");
1508
+ }
1509
+ }
1510
+ const actor = roleCaller === undefined ? taskActor(tx, options, task.id) : "role";
1511
+ if (recipient !== undefined && actor === "leader")
1512
+ assertTaskDeliveryAuthority(tx, options.environment, task.id);
1513
+ const target = recipient === undefined ? undefined
1514
+ : recipient.roleName === "leader" && roleCaller !== undefined ? {
1515
+ roleName: "leader", ...(recipient.workItemId === undefined ? {} : { workItemId: recipient.workItemId }),
1516
+ ...(recipient.reviewRoundId === undefined ? {} : { reviewRoundId: recipient.reviewRoundId })
1517
+ } : resolveMessageRecipient(tx, task.id, recipient.roleName, {
1518
+ ...(recipient.workItemId === undefined ? {} : { workItemId: recipient.workItemId }),
1519
+ ...(recipient.reviewRoundId === undefined ? {} : { reviewRoundId: recipient.reviewRoundId })
1520
+ });
1521
+ const context = {
1522
+ ...(wakePolicy === undefined || actor === "leader" || actor === "role" ? {} : { wakePolicy }),
1523
+ ...(target === undefined ? {} : { recipient: target }),
1524
+ ...(recipient?.workItemId === undefined ? {} : { workItemId: recipient.workItemId })
1525
+ };
1526
+ const message = actor === "leader"
1527
+ ? appendMessage(tx, task.id, body, "role-result", { type: "role", roleName: LEADER_ROLE }, now, context)
1528
+ : actor === "role"
1529
+ ? appendMessage(tx, task.id, body, "role-result", { type: "role", roleName: roleCaller.roleName }, now, context)
1530
+ : actor === "operator"
1531
+ ? appendMessage(tx, task.id, body, "operator", { type: "operator" }, now, context)
1532
+ : appendMessage(tx, task.id, body, "user", { type: "user" }, now, context);
1533
+ const queuedForLeader = target?.ownerRunId === undefined
1534
+ && leaderWakingTaskStatus(task.status) && actor !== "leader" && wakePolicy !== "none";
1535
+ if (target?.ownerRunId !== undefined) {
1536
+ const reason = messageContinuationBlocker(tx, message);
1537
+ if (reason !== undefined) {
1538
+ message.continuation = { notDeliveredReason: reason };
1539
+ tx.updateMessage(task.id, message);
1540
+ }
1541
+ enqueueWork(tx, taskMailbox(task.id), "message-continuation", now, [messageRef(task.id, message.id)], { dedupeKey: `message:${task.id}:${message.id}` });
1542
+ }
1543
+ else if (queuedForLeader) {
1544
+ enqueueWork(tx, leaderMailbox(task.id), actor === "operator" ? "operator-input" : "user-message", now, [messageRef(task.id, message.id)], { source: actor, dedupeKey: `message:${task.id}:${message.id}` });
1545
+ }
1546
+ return { task, message, actor, queuedForLeader };
1547
+ });
1548
+ if (recipient !== undefined) {
1549
+ notifyMailbox(options.runtime, taskMailbox(result.task.id), result.task.id);
1550
+ }
1551
+ else if (result.actor !== "leader") {
1552
+ notifyMailbox(options.runtime, result.queuedForLeader
1553
+ ? leaderMailbox(result.task.id) : taskMailbox(result.task.id), result.task.id);
1554
+ }
1555
+ return result;
1556
+ }
1557
+ /**
1558
+ * Whether an inbound Message on a Task in this status is delivered to its
1559
+ * Leader. Draft qualifies because planning is a Leader conversation that
1560
+ * deliberately precedes any delivery environment or Activation (S01); a
1561
+ * terminal Task has no Leader lane and keeps the message as context only.
1562
+ */
1563
+ export function leaderWakingTaskStatus(status) {
1564
+ return status === "active" || status === "draft";
1565
+ }
1274
1566
  function updateMessage(args, store, options) {
1275
1567
  const usage = "Task message update usage: yui task message update <task>/<message> (<body>|--body-file <path|->) [--wake-policy leader|none].";
1276
1568
  const parsed = parseTail(args, new Set(["--body-file", "--wake-policy"]), usage);
@@ -1314,9 +1606,14 @@ function updateMessage(args, store, options) {
1314
1606
  updatedBy: actor,
1315
1607
  ...(wakePolicy === undefined ? {} : { wakePolicy })
1316
1608
  }, now);
1317
- return { task, message: updated };
1609
+ const queuedForLeader = updated.wakePolicy !== "none";
1610
+ if (queuedForLeader) {
1611
+ enqueueWork(tx, leaderMailbox(task.id), actor === "operator" ? "operator-input" : "user-message", now, [messageRef(task.id, updated.id)], { source: actor });
1612
+ }
1613
+ return { task, message: updated, queuedForLeader };
1318
1614
  });
1319
- options.runtime?.notifyStateChanged(result.task.id);
1615
+ notifyMailbox(options.runtime, result.queuedForLeader
1616
+ ? leaderMailbox(result.task.id) : taskMailbox(result.task.id), result.task.id);
1320
1617
  return output(`Updated Task Message ${result.task.id}/${result.message.id}\n`, {
1321
1618
  message: result.message
1322
1619
  });
@@ -1345,7 +1642,7 @@ function retireMessage(args, store, options) {
1345
1642
  // Remove an isolated pending wake for this exact directive. A merged batch
1346
1643
  // is retained because its other signals remain actionable; context and
1347
1644
  // actionability projections still filter the retired message below.
1348
- if (task.status === "active") {
1645
+ if (leaderWakingTaskStatus(task.status)) {
1349
1646
  try {
1350
1647
  settleExactWorkExecution(tx, leaderMailbox(task.id), messageRef(task.id, message.id));
1351
1648
  }
@@ -1400,9 +1697,9 @@ function taskRoleCommand(args, store, options) {
1400
1697
  if (command === "status")
1401
1698
  return taskRoleStatus(rest, store, options);
1402
1699
  if (command === "show")
1403
- return output(showTaskRole(rest, store));
1700
+ return showTaskRole(rest, store);
1404
1701
  if (command === "update")
1405
- return output(updateTaskRole(rest, store, options));
1702
+ return updateTaskRole(rest, store, options);
1406
1703
  if (command === "remove")
1407
1704
  return output(removeTaskRole(rest, store, options));
1408
1705
  if (command === "bind")
@@ -1427,20 +1724,76 @@ function taskRoleSessionCommand(args, store, options) {
1427
1724
  exactPositionals(rest, 2, "Task Role Session inspect usage: yui task role session inspect <task> <role>.");
1428
1725
  const task = requireTask(store, rest[0]);
1429
1726
  const role = requireRole(store, task.id, rest[1]);
1430
- taskActor(store, options, task.id);
1727
+ const reader = resolveManagedTaskReader(store, options.environment);
1728
+ if (reader === undefined)
1729
+ taskActor(store, options, task.id);
1730
+ else if (reader.taskId !== task.id || (reader.roleName !== "leader" && reader.roleName !== role.name)) {
1731
+ throw usageError("Session inspection is outside the caller's Task/Role.");
1732
+ }
1431
1733
  const sessions = store.getTaskRoleSessionSet(task.id, role.name);
1432
1734
  const active = sessions?.sessions[sessions.activeAgentId] ?? null;
1433
1735
  const binding = sessions?.providerBinding ?? null;
1736
+ // The live reading, when the CLI took one. Rendered under the persisted
1737
+ // facts rather than merged into them: the Session's own record is what Yui
1738
+ // launched, and this is what the Agent says about it now. An empty render
1739
+ // means there was nothing an Agent reported, and the one-line label above
1740
+ // still states which of the "no value" cases applies.
1741
+ const runConfiguration = active === null ? undefined : options.liveRunConfiguration;
1742
+ const runConfigurationDetail = renderAgentRunConfiguration(runConfiguration);
1434
1743
  return output(active === null
1435
- ? `No Session exists for ${task.id}/${role.name}.\n`
1744
+ ? `No attached Session exists for ${task.id}/${role.name}.\n`
1745
+ + (binding === null ? "" : `Retained native execution: ${currentProviderConversation(binding).conversationId}; input=${binding.run?.status ?? "none"}. Use session new to replace from durable context.\n`)
1436
1746
  : [
1437
1747
  `Session ${task.id}/${role.name}`,
1438
- `Agent: ${active.agentId}/${active.adapterId}`,
1748
+ // The component, not just the connection plan: several products are
1749
+ // reached over `acp`, and the plan alone cannot say which one ran.
1750
+ `Agent: ${active.agentId}/${agentExecutionComponentLabel(active.effective.component)}`
1751
+ + ` (${active.adapterId} plan)`,
1439
1752
  `Native id: ${active.nativeSessionId}`,
1440
- `Host activation: ${active.runtimeGenerationId ?? "none"}`,
1441
1753
  `Session: ${active.status}${active.endReason === undefined ? "" : `/${active.endReason}`}`,
1442
- `Turn: ${binding?.turn?.status ?? "none"}`
1443
- ].join("\n") + "\n", { task, role, session: active, providerBinding: binding });
1754
+ `AgentRun: ${binding?.run?.status ?? "none"}`,
1755
+ `Run configuration: ${agentRunConfigurationLabel(runConfiguration)}`
1756
+ ].join("\n") + "\n"
1757
+ + `\n${renderRoleLaunchComparison(role, active.effective)}\n`
1758
+ + (runConfigurationDetail === "" ? "" : `\n${runConfigurationDetail}\n`), {
1759
+ task,
1760
+ role,
1761
+ session: active,
1762
+ providerBinding: binding,
1763
+ ...(runConfiguration === undefined ? {} : { runConfiguration })
1764
+ });
1765
+ }
1766
+ if (command === "new") {
1767
+ const usage = "Task Role Session new usage: yui task role session new <task> <role> --reason <text>.";
1768
+ const parsed = parseTail(rest, new Set(["--reason"]), usage);
1769
+ exactPositionals(parsed.positionals, 2, usage);
1770
+ const reason = requiredOption(parsed.options, "--reason");
1771
+ const now = clock(options);
1772
+ const result = store.transaction((tx) => {
1773
+ const task = requireTask(tx, parsed.positionals[0]);
1774
+ if (task.status === "archived")
1775
+ throw usageError("An archived Task cannot select a new Session.", usage);
1776
+ const actor = taskActor(tx, options, task.id);
1777
+ const role = requireRole(tx, task.id, parsed.positionals[1]);
1778
+ const current = tx.getTaskRoleSessionSet(task.id, role.name);
1779
+ const previous = current?.sessions[current.activeAgentId];
1780
+ const target = runtimeLifecycleTarget({ scope: "task", taskId: task.id, roleName: role.name });
1781
+ const mailbox = tx.getWorkMailbox(target);
1782
+ if ([...(mailbox?.pending?.reasons ?? []), ...(mailbox?.processing?.batch.reasons ?? [])]
1783
+ .includes(RUNTIME_SESSION_REPLACE_REQUIRED_REASON))
1784
+ return { taskId: task.id, roleName: role.name, target, alreadyRequested: true };
1785
+ recordTaskEvent(tx, task.id, "runtime.session-replacement-requested", {
1786
+ roleName: role.name, agentId: current?.activeAgentId ?? role.activeAgentId,
1787
+ ...(previous === undefined ? {} : { nativeSessionId: previous.nativeSessionId }),
1788
+ reason, requestedBy: actor
1789
+ }, now);
1790
+ enqueueWork(tx, target, RUNTIME_SESSION_REPLACE_REQUIRED_REASON, now, [{ type: "task", id: task.id }]);
1791
+ return { taskId: task.id, roleName: role.name, target, alreadyRequested: false };
1792
+ });
1793
+ notifyMailbox(options.runtime, result.target, result.taskId);
1794
+ return output(`Requested Session replacement for ${result.taskId}/${result.roleName}. `
1795
+ + "Yui will stop the old execution, retain its history and workspaces, and select a fresh Session. "
1796
+ + "If replacing your own Session, end this turn; the successor reads durable Task context.\n", result);
1444
1797
  }
1445
1798
  if (command === "stop") {
1446
1799
  const usage = "Task Role Session stop usage: yui task role session stop <task> <role> --reason <text>.";
@@ -1450,36 +1803,26 @@ function taskRoleSessionCommand(args, store, options) {
1450
1803
  const now = clock(options);
1451
1804
  const request = store.transaction((tx) => {
1452
1805
  const task = requireTask(tx, parsed.positionals[0]);
1453
- if (task.status !== "active" && task.status !== "completed") {
1454
- throw usageError(`Task Role Session stop requires an active or completed Task: ${task.id}.`, usage);
1806
+ if (!["draft", "active", "completed", "cancelled"].includes(task.status)) {
1807
+ throw usageError(`Task Role Session stop is unavailable for an archived Task: ${task.id}.`, usage);
1455
1808
  }
1456
1809
  const actor = taskActor(tx, options, task.id);
1457
1810
  const role = requireRole(tx, task.id, parsed.positionals[1]);
1458
1811
  if (actor === "leader" && role.name === LEADER_ROLE) {
1459
- throw usageError("A Leader cannot stop the Session executing its own current command.", usage);
1460
- }
1461
- if (tx.getActiveTurn(task.id, role.name) !== null) {
1462
- throw usageError(`Task Role has an active Turn; settle or retire it before stopping the Session: ${task.id}/${role.name}.`, usage);
1812
+ throw usageError("Use task role session new to request your own replacement and end this turn; synchronous self-stop cannot return.", usage);
1463
1813
  }
1464
1814
  const sessions = tx.getTaskRoleSessionSet(task.id, role.name);
1465
- const session = sessions?.sessions[sessions.activeAgentId];
1466
- if (session === undefined || session.status === "ended") {
1815
+ const session = taskRoleControlTarget(sessions);
1816
+ if (session === undefined || session.adapterId === undefined) {
1467
1817
  throw usageError(`Task Role has no active Session: ${task.id}/${role.name}.`, usage);
1468
1818
  }
1469
- const lifecycle = tx.getWorkMailbox(runtimeLifecycleTarget({
1470
- scope: "task",
1471
- taskId: task.id,
1472
- roleName: role.name
1473
- }));
1474
- if (lifecycle !== null && workMailboxHasWork(lifecycle)) {
1475
- throw usageError(`Task Role Session lifecycle is busy: ${task.id}/${role.name}.`, usage);
1476
- }
1819
+ // A repeated stop is a recovery request, not contention with its own
1820
+ // earlier cleanup obligation.
1477
1821
  recordTaskEvent(tx, task.id, "runtime.session-stop-requested", {
1478
1822
  roleName: role.name,
1479
1823
  agentId: session.agentId,
1480
1824
  adapterId: session.adapterId,
1481
1825
  nativeSessionId: session.nativeSessionId,
1482
- runtimeGenerationId: session.runtimeGenerationId ?? "",
1483
1826
  reason,
1484
1827
  requestedBy: actor
1485
1828
  }, now);
@@ -1489,7 +1832,6 @@ function taskRoleSessionCommand(args, store, options) {
1489
1832
  agentId: session.agentId,
1490
1833
  adapterId: session.adapterId,
1491
1834
  nativeSessionId: session.nativeSessionId,
1492
- ...(session.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: session.runtimeGenerationId }),
1493
1835
  sessionUpdatedAt: session.updatedAt
1494
1836
  };
1495
1837
  });
@@ -1607,7 +1949,7 @@ function listTaskRoles(args, store, options) {
1607
1949
  status.health,
1608
1950
  taskRoleOpenInputLabel(status),
1609
1951
  taskRoleActiveWorkLabel(status),
1610
- taskRoleLastTurnLabel(status),
1952
+ taskRoleLastRunLabel(status),
1611
1953
  taskRoleNativeSessionLabel(status),
1612
1954
  taskRoleTmuxLabel(status)
1613
1955
  ]), defaultTableWidth())}\n`, { roles: statuses });
@@ -1625,9 +1967,15 @@ function showTaskRole(args, store) {
1625
1967
  exactPositionals(args, 2, "Task role show usage: yui task role show <task> <role>.");
1626
1968
  const task = requireTask(store, args[0]);
1627
1969
  const role = requireRole(store, task.id, args[1]);
1628
- return renderRoleDetails(`Task Role: ${role.name}`, role, {
1970
+ const sessions = store.getTaskRoleSessionSet(task.id, role.name);
1971
+ return output(renderRoleDetails(`Task Role: ${role.name}`, role, {
1629
1972
  kind: "task",
1630
- sessions: store.getTaskRoleSessionSet(task.id, role.name)
1973
+ sessions
1974
+ }), {
1975
+ role,
1976
+ sessions,
1977
+ runs: store.listRuns(task.id).filter((run) => run.roleName === role.name)
1978
+ .map((run) => ({ id: run.id, status: run.status, effective: run.effective }))
1631
1979
  });
1632
1980
  }
1633
1981
  function updateTaskRole(args, store, options) {
@@ -1638,8 +1986,11 @@ function updateTaskRole(args, store, options) {
1638
1986
  }
1639
1987
  const parsed = parseRoleOptions(tail, new Map([
1640
1988
  ...roleOptionSpecs({ update: true, includeAgent: true }),
1641
- ["--profile", "value"]
1989
+ ["--profile", "value"],
1990
+ ["--environment", "value"],
1991
+ ["--managed-environment", "flag"]
1642
1992
  ]), usage);
1993
+ validateTaskRoleEnvironmentOptions(parsed, usage);
1643
1994
  if (parsed.has("--agent") && (parsed.one("--agent")?.trim().length ?? 0) === 0) {
1644
1995
  throw usageError("--agent is required.", usage);
1645
1996
  }
@@ -1652,7 +2003,9 @@ function updateTaskRole(args, store, options) {
1652
2003
  assertTaskOpen(task);
1653
2004
  taskActor(tx, options, task.id);
1654
2005
  const role = requireRole(tx, task.id, roleName);
1655
- const changesLaunchContext = hasRoleLaunchContextOptions(parsed) || parsed.has("--profile");
2006
+ const changesEnvironment = parsed.has("--environment") || parsed.has("--managed-environment");
2007
+ const changesLaunchContext = hasRoleLaunchContextOptions(parsed) || parsed.has("--profile")
2008
+ || changesEnvironment;
1656
2009
  const changesAgentConfig = hasAgentConfigOptions(parsed);
1657
2010
  if (changesLaunchContext || changesAgentConfig) {
1658
2011
  assertRoleRuntimeMutationAllowed(tx, {
@@ -1660,13 +2013,6 @@ function updateTaskRole(args, store, options) {
1660
2013
  taskId: task.id,
1661
2014
  roleName: role.name
1662
2015
  }, "desired launch configuration update");
1663
- assertLiveRoleSessionAcknowledged({
1664
- sessions: tx.getTaskRoleSessionSet(task.id, role.name),
1665
- roleName: role.name,
1666
- desiredRevision: role.launchRevision,
1667
- acknowledged: parsed.has(LIVE_SESSION_ACKNOWLEDGEMENT_OPTION),
1668
- stopCommand: `yui task role session stop ${task.id} ${role.name} --reason "<decision>"`
1669
- });
1670
2016
  }
1671
2017
  const profileId = parsed.one("--profile");
1672
2018
  const agentProfile = profileId === undefined
@@ -1684,7 +2030,7 @@ function updateTaskRole(args, store, options) {
1684
2030
  [bindingUpdate.agentId]: bindingUpdate.binding
1685
2031
  }
1686
2032
  }, now);
1687
- const next = updateRole(withBinding, {
2033
+ let next = updateRole(withBinding, {
1688
2034
  ...roleProfilePatch(parsed)
1689
2035
  }, now);
1690
2036
  if (bindingUpdate !== undefined) {
@@ -1693,16 +2039,32 @@ function updateTaskRole(args, store, options) {
1693
2039
  if (changesLaunchContext) {
1694
2040
  validateConfiguredRoleSkills(options.yuiHome, next.skills ?? []);
1695
2041
  }
1696
- tx.saveRole(task.id, next);
1697
- enqueueWork(tx, taskMailbox(task.id), "role-updated", now, [taskRef(task.id)]);
1698
- recordTaskEvent(tx, task.id, "role.updated", roleLaunchEventPayload(next, tx.getTaskRoleSessionSet(task.id, next.name)), now);
2042
+ if (changesEnvironment) {
2043
+ next = updateRole(next, {
2044
+ executionEnvironment: parsed.has("--managed-environment")
2045
+ ? null
2046
+ : createProjectResources(tx, () => now).resolveExecutionEnvironment(task.id, parsed.one("--environment"))
2047
+ }, now);
2048
+ }
2049
+ saveTaskRoleUpdate(tx, role, next, now, {
2050
+ source: "task.role.update", actor: taskActor(tx, options, task.id)
2051
+ });
1699
2052
  return next;
1700
2053
  });
1701
2054
  notifyMailbox(options.runtime, taskMailbox(updated.taskId), updated.taskId);
1702
- return renderRoleDetails(`Updated Task Role: ${updated.name}`, updated, {
2055
+ const sessions = store.getTaskRoleSessionSet(updated.taskId, updated.name);
2056
+ return output(renderRoleDetails(`Updated Task Role: ${updated.name}`, updated, {
1703
2057
  kind: "task",
1704
- sessions: store.getTaskRoleSessionSet(updated.taskId, updated.name)
1705
- });
2058
+ sessions
2059
+ }), { role: updated, sessions });
2060
+ }
2061
+ function validateTaskRoleEnvironmentOptions(parsed, usage) {
2062
+ if (parsed.has("--environment") && parsed.has("--managed-environment")) {
2063
+ throw usageError("--environment and --managed-environment are mutually exclusive.", usage);
2064
+ }
2065
+ if (parsed.has("--environment") && (parsed.one("--environment")?.trim().length ?? 0) === 0) {
2066
+ throw usageError("--environment requires an adopted preparation id.", usage);
2067
+ }
1706
2068
  }
1707
2069
  function removeTaskRole(args, store, options) {
1708
2070
  exactPositionals(args, 2, "Task role remove usage: yui task role remove <task> <role>.");
@@ -1719,8 +2081,8 @@ function removeTaskRole(args, store, options) {
1719
2081
  taskId: task.id,
1720
2082
  roleName: role.name
1721
2083
  }, "removal");
1722
- if (tx.getActiveTurn(task.id, role.name) !== null) {
1723
- throw usageError(`Task Role has an active Turn and cannot be removed: ${task.id}/${role.name}.`);
2084
+ if (tx.getActiveRun(task.id, role.name) !== null) {
2085
+ throw usageError(`Task Role has an active AgentRun and cannot be removed: ${task.id}/${role.name}.`);
1724
2086
  }
1725
2087
  const sessions = tx.getTaskRoleSessionSet(task.id, role.name);
1726
2088
  if (Object.values(sessions?.sessions ?? {}).some(({ status }) => status === "active")) {
@@ -1749,7 +2111,7 @@ function bindTaskRole(args, store, options) {
1749
2111
  }, "desired Agent binding update");
1750
2112
  const agent = requireAgent(tx, args[2]);
1751
2113
  const binding = role.agentBindings[agent.id]
1752
- ?? createRoleAgentBinding({ id: agent.id, adapterId: agent.adapterId });
2114
+ ?? createRoleAgentBinding(agent);
1753
2115
  const bound = updateRole(role, {
1754
2116
  agentBindings: { ...role.agentBindings, [agent.id]: binding }
1755
2117
  }, now);
@@ -1769,7 +2131,7 @@ function bindTaskRole(args, store, options) {
1769
2131
  const switched = (() => {
1770
2132
  try {
1771
2133
  return switchActiveRoleAgent(bound, existing, agent.id, {
1772
- activeTurn: tx.getActiveTurn(task.id, role.name) !== null,
2134
+ activeRun: tx.getActiveRun(task.id, role.name) !== null,
1773
2135
  nativeProcessRunning: currentSession !== undefined
1774
2136
  && currentSession.status === "active"
1775
2137
  }, now);
@@ -1782,6 +2144,10 @@ function bindTaskRole(args, store, options) {
1782
2144
  recordTaskEvent(tx, task.id, "role.agent-bound", {
1783
2145
  role: switched.role.name,
1784
2146
  agentId: agent.id,
2147
+ // Re-selecting an Agent must not revive its old management entrance.
2148
+ // This revokes authority, not the Session's independent execution fact.
2149
+ ...(role.name === LEADER_ROLE && currentSession?.nativeSessionId !== undefined
2150
+ ? { revokedNativeSessionId: currentSession.nativeSessionId } : {}),
1785
2151
  ...roleLaunchEventPayload(switched.role, switched.sessions)
1786
2152
  }, now);
1787
2153
  return { role: switched.role, mode: switched.mode };
@@ -1851,20 +2217,13 @@ function transferTaskRoleAuthority(args, store, options, action) {
1851
2217
  const sessions = tx.getTaskRoleSessionSet(task.id, role.name);
1852
2218
  const session = sessions?.sessions[role.activeAgentId];
1853
2219
  const binding = sessions?.providerBinding;
1854
- if (sessions === null || sessions === undefined || session === undefined
1855
- || binding === null || binding === undefined
1856
- || session.runtimeGenerationId === undefined
1857
- || session.status === "ended") {
2220
+ if (sessions === null || sessions === undefined || session === undefined || binding === null || binding === undefined || session.status === "ended") {
1858
2221
  throw new Error(`Task Role has no live managed Provider: ${task.id}/${role.name}.`);
1859
2222
  }
1860
- const activation = currentProviderActivation(binding);
1861
- if (activation === null) {
1862
- throw new Error(`Provider Activation is not live: ${task.id}/${role.name}.`);
1863
- }
1864
2223
  if (action === "takeover") {
1865
- const activeTurn = tx.getActiveTurn(task.id, role.name);
1866
- if (activeTurn === null) {
1867
- throw new Error(`Task Role has no active managed Turn for takeover: ${task.id}/${role.name}.`);
2224
+ const activeRun = tx.getActiveRun(task.id, role.name);
2225
+ if (activeRun === null) {
2226
+ throw new Error(`Task Role has no active managed AgentRun for takeover: ${task.id}/${role.name}.`);
1868
2227
  }
1869
2228
  }
1870
2229
  if (action === "takeover"
@@ -1885,7 +2244,7 @@ function transferTaskRoleAuthority(args, store, options, action) {
1885
2244
  expectedEpoch: binding.authority.epoch,
1886
2245
  expectedOwner: binding.authority.owner,
1887
2246
  owner: desiredOwner,
1888
- holderId: action === "takeover" ? `human:${randomUUID()}` : activation.activationId,
2247
+ holderId: action === "takeover" ? `human:${randomUUID()}` : "controller",
1889
2248
  changedAt: now.toISOString()
1890
2249
  });
1891
2250
  const authority = updatedBinding.authority;
@@ -1906,7 +2265,6 @@ function transferTaskRoleAuthority(args, store, options, action) {
1906
2265
  action,
1907
2266
  taskId: task.id,
1908
2267
  roleName: role.name,
1909
- runtimeGenerationId: session.runtimeGenerationId,
1910
2268
  nativeSessionId: session.nativeSessionId,
1911
2269
  authority: {
1912
2270
  epoch: authority.epoch,
@@ -1939,6 +2297,8 @@ function taskWorkCommand(args, store, options) {
1939
2297
  return output(updateWorkScope(rest, store, options));
1940
2298
  if (command === "dispatch")
1941
2299
  return output(dispatchWork(rest, store, options));
2300
+ if (command === "synthesize")
2301
+ return synthesizeRuns(rest, "workItem", store, options);
1942
2302
  if (command === "review") {
1943
2303
  return rest[0] === "retry"
1944
2304
  ? retryFailedTaskReviewRound(rest.slice(1), store, options)
@@ -1978,14 +2338,10 @@ function editWork(args, store, options) {
1978
2338
  const result = store.transaction((tx) => {
1979
2339
  const item = requireWorkItem(tx, parsed.positionals[0], options);
1980
2340
  const task = requireTask(tx, item.taskId);
1981
- if (task.status !== "draft") {
1982
- throw usageError(`Work Item definition edit is Draft-only: ${task.id}/${task.status}.`);
1983
- }
1984
- assertDraftTaskExecutionFree(tx, task);
2341
+ assertTaskOpen(task);
2342
+ if (task.status === "draft")
2343
+ assertDraftTaskExecutionFree(tx, task);
1985
2344
  const actor = taskActor(tx, options, task.id);
1986
- if (actor !== "user" && actor !== "operator") {
1987
- throw usageError("Only the user or Operator may edit a Draft Work Item.");
1988
- }
1989
2345
  if (item.status === "retired") {
1990
2346
  throw usageError(`Work Item is retired: ${item.id}.`);
1991
2347
  }
@@ -2006,7 +2362,7 @@ function editWork(args, store, options) {
2006
2362
  : parsed.options.has("--clear-role") ? null : undefined;
2007
2363
  if (typeof assignee === "string")
2008
2364
  requireRole(tx, task.id, assignee);
2009
- const updated = editDraftWorkItemDefinition(item, {
2365
+ const updated = editWorkItemDefinition(item, {
2010
2366
  ...(parsed.options.has("--title")
2011
2367
  ? { title: requiredOption(parsed.options, "--title") }
2012
2368
  : {}),
@@ -2023,7 +2379,21 @@ function editWork(args, store, options) {
2023
2379
  ...(baseRefs === undefined ? {} : { baseRefs }),
2024
2380
  ...(assignee === undefined ? {} : { assignee })
2025
2381
  }, now);
2026
- validateDraftWorkItemEdit(tx, task, updated);
2382
+ if (task.status === "draft")
2383
+ validateDraftWorkItemEdit(tx, task, updated);
2384
+ else {
2385
+ // Requirements can evolve while a frozen Assignment continues. Resource
2386
+ // scope and ownership changes still require an explicit idle boundary.
2387
+ if ((projects !== undefined || baseRefs !== undefined || assignee !== undefined)
2388
+ && tx.listRuns(task.id).some((run) => run.workItemId === item.id && run.status === "active")) {
2389
+ throw usageError(`Stop the active Work Item AgentRun before changing ownership or workspace scope: ${item.id}.`);
2390
+ }
2391
+ for (const dependencyId of updated.dependsOn) {
2392
+ if (tx.getWorkItem(task.id, dependencyId) === null) {
2393
+ throw usageError(`Work Item dependency not found: ${dependencyId}.`);
2394
+ }
2395
+ }
2396
+ }
2027
2397
  tx.saveWorkItem(task.id, updated);
2028
2398
  const changedFields = [
2029
2399
  parsed.options.has("--title") ? "title" : undefined,
@@ -2043,8 +2413,13 @@ function editWork(args, store, options) {
2043
2413
  workItemId: updated.id,
2044
2414
  revision: String(updated.revision),
2045
2415
  fields: changedFields.join(","),
2416
+ previous: editedFieldValues(item, changedFields),
2417
+ current: editedFieldValues(updated, changedFields),
2046
2418
  editedBy: actor
2047
2419
  }, now);
2420
+ if (actor !== "leader") {
2421
+ enqueueWork(tx, leaderMailbox(task.id), actor === "operator" ? "operator-input" : "user-message", now, [workItemRef(task.id, updated.id)], { source: actor });
2422
+ }
2048
2423
  return { task, item: updated };
2049
2424
  });
2050
2425
  options.runtime?.notifyStateChanged(result.task.id);
@@ -2121,8 +2496,8 @@ function updateWorkScope(args, store, options) {
2121
2496
  const item = requireWorkItem(tx, parsed.positionals[0], options);
2122
2497
  const task = requireTask(tx, item.taskId);
2123
2498
  taskActor(tx, options, task.id);
2124
- if (tx.getActiveTurn(task.id, item.assignee ?? "") !== null) {
2125
- throw usageError(`Stop the active Work Item Turn before changing scope: ${item.id}.`);
2499
+ if (tx.getActiveRun(task.id, item.assignee ?? "") !== null) {
2500
+ throw usageError(`Stop the active Work Item AgentRun before changing scope: ${item.id}.`);
2126
2501
  }
2127
2502
  const requestedProjectIds = (parsed.multiOptions.get("--project") ?? []).map((reference) => {
2128
2503
  const project = resolveProject(task.projectBindings.map(({ projectId }) => requireProject(tx, projectId)), reference);
@@ -2152,12 +2527,16 @@ function updateWorkScope(args, store, options) {
2152
2527
  return `${updated.changed ? "Updated" : "Unchanged"} Work Item Project scope ${updated.item.id}: ${updated.item.writeProjectIds.join(", ") || "read-only"}\n`;
2153
2528
  }
2154
2529
  function updateWork(args, store, options) {
2155
- const usage = "Task work update usage: yui task work update <task>/<work> <todo|running|done|failed> [--summary <text>].";
2156
- const parsed = parseTail(args, new Set(["--summary"]), usage);
2530
+ const usage = "Task work update usage: yui task work update <task>/<work> <todo|running|done|failed> [--summary <text>] [--artifact-ref <artifact-id> ...].";
2531
+ const parsed = parseMultiValueTail(args, new Set(["--summary"]), new Set(["--artifact-ref"]), usage);
2157
2532
  exactPositionals(parsed.positionals, 2, usage);
2158
2533
  const requested = parsed.positionals[1];
2159
2534
  const status = parseWorkStatus(requested);
2160
2535
  const summary = trimmed(parsed.options.get("--summary"));
2536
+ const artifactIds = parsed.multiOptions.get("--artifact-ref") ?? [];
2537
+ if (artifactIds.length > 0 && status !== "completed") {
2538
+ throw usageError("--artifact-ref is only valid when submitting a done Candidate.");
2539
+ }
2161
2540
  if (["completed", "failed"].includes(status)
2162
2541
  && summary === undefined) {
2163
2542
  throw usageError(`--summary is required when work becomes ${requested}.`);
@@ -2167,19 +2546,16 @@ function updateWork(args, store, options) {
2167
2546
  const current = requireWorkItem(tx, parsed.positionals[0], options);
2168
2547
  const task = requireTask(tx, current.taskId);
2169
2548
  assertTaskOpen(task);
2549
+ const artifactRefs = artifactIds.length === 0 ? undefined : fixedArtifactRefs(tx, task.id, artifactIds);
2170
2550
  if (current.assignee === undefined) {
2171
2551
  taskActor(tx, options, task.id);
2172
2552
  if (status === "running") {
2173
2553
  assertWorkItemDependenciesCompletedForCommand(tx, current);
2174
2554
  }
2175
- if (status === "completed" && current.status === "awaiting_acceptance") {
2176
- throw usageError(`Work Item ${current.id} is awaiting acceptance; use task work accept `
2177
- + "after the required ReviewRound and Integration evidence.");
2178
- }
2179
2555
  const configuredReview = status === "completed" && !isTerminalWorkItemStatus(current.status)
2180
2556
  ? tx.getReviewConfig()
2181
2557
  : null;
2182
- const taskFinalContract = status === "completed" && current.status === "running"
2558
+ const taskFinalContract = status === "completed" && current.status === "open"
2183
2559
  ? taskFinalReviewContractForMutation(tx, task.id, options)
2184
2560
  : undefined;
2185
2561
  const candidatePolicy = taskFinalContract === undefined
@@ -2189,9 +2565,7 @@ function updateWork(args, store, options) {
2189
2565
  && current.writeProjectIds.length > 0;
2190
2566
  const metadataOnlyTaskFinalDelivery = taskFinalContract !== undefined
2191
2567
  && current.assignee === undefined;
2192
- const candidateRequired = status === "completed"
2193
- && current.status === "running"
2194
- && (projectDelivery || candidatePolicy !== null);
2568
+ const candidateRequired = status === "completed" && current.status === "open";
2195
2569
  const developWorkspace = tx.getWorkItemWorkspace(task.id, current.id);
2196
2570
  if (status === "completed"
2197
2571
  && projectDelivery
@@ -2203,6 +2577,7 @@ function updateWork(args, store, options) {
2203
2577
  ? submitWorkItemCandidate(current, {
2204
2578
  summary: summary,
2205
2579
  source: { type: "direct" },
2580
+ ...(artifactRefs === undefined ? {} : { artifactRefs }),
2206
2581
  ...(candidatePolicy === null ? {} : { reviewPolicy: candidatePolicy }),
2207
2582
  ...(taskFinalContract === undefined
2208
2583
  ? {}
@@ -2217,9 +2592,7 @@ function updateWork(args, store, options) {
2217
2592
  ? {}
2218
2593
  : { taskMainSnapshot: options.directTaskMainSnapshot })
2219
2594
  }, now)
2220
- : current.status === "failed" && status === "running"
2221
- ? retryFailedWorkItem(current, now)
2222
- : updateWorkItemStatus(current, status, now, isTerminalWorkItemStatus(status) ? summary : undefined);
2595
+ : updateWorkItemStatus(current, "open", now);
2223
2596
  tx.saveWorkItem(task.id, updated);
2224
2597
  if (summary !== undefined) {
2225
2598
  recordTaskEvent(tx, task.id, "work.updated", {
@@ -2242,29 +2615,29 @@ function updateWork(args, store, options) {
2242
2615
  };
2243
2616
  }
2244
2617
  taskActor(tx, options, task.id);
2245
- if (status !== "completed" || current.status !== "running") {
2246
- throw usageError(`Assigned Work Item ${current.id} can only submit a completed direct Turn from running; `
2247
- + "use dispatch, task turn retry, or task work retire for other transitions.");
2618
+ if (status !== "completed" || current.status !== "open") {
2619
+ throw usageError(`Assigned Work Item ${current.id} can only submit a completed direct AgentRun from running; `
2620
+ + "use dispatch, task run retry, or task work retire for other transitions.");
2248
2621
  }
2249
- if (tx.getActiveTurn(task.id, current.assignee) !== null) {
2250
- throw usageError(`Work Item main Turn is still active: ${current.id}/${current.assignee}.`);
2622
+ if (tx.getActiveRun(task.id, current.assignee) !== null) {
2623
+ throw usageError(`Work Item main AgentRun is still active: ${current.id}/${current.assignee}.`);
2251
2624
  }
2252
2625
  const sourceGroup = currentWorkItemExecutionGroup(current);
2253
- const mainTurn = tx.listTurns(task.id).filter((turn) => (turn.purpose === "execution"
2254
- && turn.workItemId === current.id
2255
- && turn.roleName === current.assignee
2256
- && turn.executionGroupId === undefined
2257
- && turn.executionLaneId === undefined
2258
- && turn.sourceExecutionGroupId === sourceGroup?.id
2259
- && turn.status === "completed"
2260
- && turn.result !== undefined)).at(-1);
2261
- if (mainTurn === undefined) {
2626
+ const mainRun = tx.listRuns(task.id).filter((run) => (run.purpose === "execution"
2627
+ && run.workItemId === current.id
2628
+ && run.roleName === current.assignee
2629
+ && run.executionGroupId === undefined
2630
+ && run.executionLaneId === undefined
2631
+ && run.sourceExecutionGroupId === sourceGroup?.id
2632
+ && run.status === "completed"
2633
+ && run.result !== undefined)).at(-1);
2634
+ if (mainRun === undefined) {
2262
2635
  throw usageError(sourceGroup === undefined
2263
- ? `Work Item ${current.id} has no completed direct main Turn.`
2264
- : `Work Item ${current.id} has no completed main Turn for ExecutionGroup ${sourceGroup.id}.`);
2636
+ ? `Work Item ${current.id} has no completed direct main AgentRun.`
2637
+ : `Work Item ${current.id} has no completed main AgentRun for ExecutionGroup ${sourceGroup.id}.`);
2265
2638
  }
2266
- if (mainTurn.result === undefined) {
2267
- throw dataError(`Completed WorkItem main Turn has no result: ${mainTurn.id}.`);
2639
+ if (mainRun.result === undefined) {
2640
+ throw dataError(`Completed WorkItem main AgentRun has no result: ${mainRun.id}.`);
2268
2641
  }
2269
2642
  const configuredReview = tx.getReviewConfig();
2270
2643
  const taskFinalContract = taskFinalReviewContractForMutation(tx, task.id, options);
@@ -2280,8 +2653,9 @@ function updateWork(args, store, options) {
2280
2653
  throw usageError(`Project-backed Work Item ${current.id} must be isolated before Candidate submission.`);
2281
2654
  }
2282
2655
  const updated = submitWorkItemCandidate(current, {
2283
- summary: `Result from Turn ${mainTurn.id}.`,
2284
- source: { type: "turn", turnId: mainTurn.id },
2656
+ summary: `Result from AgentRun ${mainRun.id}.`,
2657
+ source: { type: "run", runId: mainRun.id },
2658
+ ...(artifactRefs === undefined ? {} : { artifactRefs }),
2285
2659
  ...(candidatePolicy === null ? {} : { reviewPolicy: candidatePolicy }),
2286
2660
  ...(taskFinalContract === undefined
2287
2661
  ? {}
@@ -2299,12 +2673,12 @@ function updateWork(args, store, options) {
2299
2673
  workItemId: updated.id,
2300
2674
  status: updated.status,
2301
2675
  summary: summary,
2302
- turnId: mainTurn.id,
2676
+ runId: mainRun.id,
2303
2677
  ...leaderActionEventPayload(tx, task.id, options)
2304
2678
  }, now);
2305
2679
  enqueueWork(tx, taskMailbox(task.id), "work-updated", now, [
2306
2680
  workItemRef(task.id, updated.id),
2307
- turnRef(task.id, mainTurn.id)
2681
+ runRef(task.id, mainRun.id)
2308
2682
  ]);
2309
2683
  const reviewDispatch = candidatePolicy?.trigger === "always"
2310
2684
  ? queueReviewRound(tx, updated, candidatePolicy, "policy", now)
@@ -2316,7 +2690,7 @@ function updateWork(args, store, options) {
2316
2690
  };
2317
2691
  });
2318
2692
  notifyMailbox(options.runtime, taskMailbox(result.item.taskId), result.item.taskId);
2319
- if (result.item.status === "awaiting_acceptance" && status === "completed") {
2693
+ if ((result.item.status === "open" && result.item.candidates.length > 0) && status === "completed") {
2320
2694
  const failure = result.reviewDispatch?.round.status === "failed"
2321
2695
  ? `Review could not start: ${result.reviewDispatch.round.failure?.message ?? result.reviewDispatch.round.id}\n`
2322
2696
  : "";
@@ -2351,13 +2725,13 @@ function dispatchWork(args, store, options) {
2351
2725
  throw usageError(`Work Item has no Task Role assignee: ${item.id}. `
2352
2726
  + `The Task Leader must run "yui task work update ${item.id} running" and execute it directly.`);
2353
2727
  }
2354
- const lanePlan = planReplicatedWorkItemLanes(item.assignee, requestedLaneRoles, `execution-group-${tx.peekNextTurnId(task.id)}`);
2728
+ const lanePlan = planReplicatedWorkItemLanes(item.assignee, requestedLaneRoles, `execution-group-${tx.peekNextRunId(task.id)}`);
2355
2729
  const currentGroup = currentWorkItemExecutionGroup(item);
2356
- if (item.status !== "pending" && item.status !== "failed") {
2730
+ if (item.status !== "open") {
2357
2731
  throw usageError(`Work item ${item.id} cannot be dispatched from ${item.status}.`);
2358
2732
  }
2359
2733
  if (currentGroup !== undefined && !workItemExecutionGroupSettled(currentGroup)) {
2360
- throw usageError(`Work Item ${item.id} retains open ExecutionGroup ${currentGroup.id}; retry or settle its exact Lane Turns.`);
2734
+ throw usageError(`Work Item ${item.id} retains open ExecutionGroup ${currentGroup.id}; retry or settle its exact Lane AgentRuns.`);
2361
2735
  }
2362
2736
  assertWorkItemDependenciesCompletedForCommand(tx, item);
2363
2737
  const leaderOwned = item.assignee === "leader";
@@ -2381,18 +2755,16 @@ function dispatchWork(args, store, options) {
2381
2755
  }
2382
2756
  const roles = lanePlan.roles.map((name) => requireRole(tx, task.id, name));
2383
2757
  for (const role of roles) {
2384
- if (tx.getActiveTurn(task.id, role.name) !== null) {
2385
- throw usageError(`${task.id}/${role.name} already has an active turn.`);
2758
+ if (tx.getActiveRun(task.id, role.name) !== null) {
2759
+ throw usageError(`${task.id}/${role.name} already has an active run.`);
2386
2760
  }
2387
2761
  }
2388
2762
  const rawInput = trimmed(parsed.options.get("--input")) ?? item.objective;
2389
- let workItemForDispatch = item.status === "failed"
2390
- ? retryFailedWorkItem(item, now)
2391
- : item;
2763
+ let workItemForDispatch = prepareWorkItemDispatch(item, now);
2392
2764
  if (lanePlan.roles.length === 0) {
2393
2765
  const role = requireRole(tx, task.id, item.assignee);
2394
- if (tx.getActiveTurn(task.id, role.name) !== null) {
2395
- throw usageError(`${task.id}/${role.name} already has an active turn.`);
2766
+ if (tx.getActiveRun(task.id, role.name) !== null) {
2767
+ throw usageError(`${task.id}/${role.name} already has an active run.`);
2396
2768
  }
2397
2769
  const effective = resolveEffectiveLaunch({
2398
2770
  role,
@@ -2400,8 +2772,8 @@ function dispatchWork(args, store, options) {
2400
2772
  workspace,
2401
2773
  workItemWriteProjectIds: item.writeProjectIds
2402
2774
  });
2403
- const turnId = tx.nextTurnId(task.id);
2404
- const turn = createTurn(turnId, task.id, role.name, roleAgentSessionResumeMode(tx.getTaskRoleSessionSet(task.id, role.name), effective.agentId, effective), createTurnInput({
2775
+ const runId = tx.nextRunId(task.id);
2776
+ const run = createRun(runId, task.id, role.name, roleAgentSessionResumeMode(tx.getTaskRoleSessionSet(task.id, role.name), effective.agentId, effective), createRunInput({
2405
2777
  source: { type: "yui", channel: "workitem-dispatch" },
2406
2778
  directive: rawInput,
2407
2779
  deltaRefIds: []
@@ -2410,30 +2782,30 @@ function dispatchWork(args, store, options) {
2410
2782
  workspace,
2411
2783
  effective
2412
2784
  });
2413
- const snapshot = freezeTurnContextSnapshot(tx, {
2785
+ const snapshot = freezeRunContextSnapshot(tx, {
2414
2786
  taskId: task.id,
2415
- roleName: turn.roleName,
2787
+ roleName: run.roleName,
2416
2788
  purpose: "execution",
2417
2789
  workItemId: item.id
2418
2790
  }, now, "controller");
2419
- const withContext = withTurnContextSnapshot(turn, contextSnapshotRef(snapshot), contextSnapshotDeltaRefIds(tx, snapshot));
2420
- if (workItemForDispatch.status !== "running") {
2421
- workItemForDispatch = updateWorkItemStatus(workItemForDispatch, "running", now);
2791
+ const withContext = withRunContextSnapshot(run, contextSnapshotRef(snapshot), contextSnapshotDeltaRefIds(tx, snapshot));
2792
+ if (workItemForDispatch.status !== "open") {
2793
+ workItemForDispatch = updateWorkItemStatus(workItemForDispatch, "open", now);
2422
2794
  }
2423
2795
  tx.saveWorkItem(task.id, workItemForDispatch);
2424
- tx.saveTurn(withContext);
2425
- tx.saveActiveTurn(withContext);
2426
- enqueueRoleTurnDispatch(tx, {
2796
+ tx.saveRun(withContext);
2797
+ tx.saveActiveRun(withContext);
2798
+ enqueueRoleRunDispatch(tx, {
2427
2799
  taskId: task.id,
2428
2800
  roleName: role.name,
2429
- turnId: withContext.id,
2801
+ runId: withContext.id,
2430
2802
  reason: "turn-dispatched",
2431
2803
  occurredAt: now
2432
2804
  });
2433
- recordTaskEvent(tx, task.id, "turn.dispatched", turnLaunchEventPayload(withContext), now);
2434
- return { kind: "direct", turns: [withContext] };
2805
+ recordTaskEvent(tx, task.id, "run.dispatched", runLaunchEventPayload(withContext), now);
2806
+ return { kind: "direct", runs: [withContext] };
2435
2807
  }
2436
- const groupId = `execution-group-${tx.peekNextTurnId(task.id)}`;
2808
+ const groupId = `execution-group-${tx.peekNextRunId(task.id)}`;
2437
2809
  if (workItemForDispatch !== item) {
2438
2810
  // Freeze the Assignment against the retried WorkItem revision. The
2439
2811
  // aggregate transaction rolls this back if a later precondition fails.
@@ -2462,7 +2834,7 @@ function dispatchWork(args, store, options) {
2462
2834
  laneId,
2463
2835
  managedWorkspace: laneWorkspace,
2464
2836
  effective,
2465
- turnId: tx.nextTurnId(task.id),
2837
+ runId: tx.nextRunId(task.id),
2466
2838
  dispatchMode: roleAgentSessionResumeMode(tx.getTaskRoleSessionSet(task.id, role.name), effective.agentId, effective)
2467
2839
  };
2468
2840
  });
@@ -2495,15 +2867,15 @@ function dispatchWork(args, store, options) {
2495
2867
  root: plan.managedWorkspace.root,
2496
2868
  writableProjectIds: [...item.writeProjectIds]
2497
2869
  },
2498
- currentTurnId: plan.turnId
2870
+ currentRunId: plan.runId
2499
2871
  })), now);
2500
2872
  workItemForDispatch = attachWorkItemExecutionGroup(workItemForDispatch, group, now);
2501
- if (workItemForDispatch.status !== "running") {
2502
- workItemForDispatch = updateWorkItemStatus(workItemForDispatch, "running", now);
2873
+ if (workItemForDispatch.status !== "open") {
2874
+ workItemForDispatch = updateWorkItemStatus(workItemForDispatch, "open", now);
2503
2875
  }
2504
2876
  tx.saveWorkItem(task.id, workItemForDispatch);
2505
- const turns = plans.map((plan, index) => {
2506
- const turn = createTurn(plan.turnId, task.id, plan.role.name, plan.dispatchMode, createTurnInput({
2877
+ const runs = plans.map((plan, index) => {
2878
+ const run = createRun(plan.runId, task.id, plan.role.name, plan.dispatchMode, createRunInput({
2507
2879
  source: { type: "yui", channel: "workitem-dispatch" },
2508
2880
  directive: assignment.input,
2509
2881
  deltaRefIds: []
@@ -2514,37 +2886,37 @@ function dispatchWork(args, store, options) {
2514
2886
  workspace: plan.managedWorkspace,
2515
2887
  effective: plan.effective
2516
2888
  });
2517
- const snapshot = freezeTurnContextSnapshot(tx, {
2889
+ const snapshot = freezeRunContextSnapshot(tx, {
2518
2890
  taskId: task.id,
2519
- roleName: turn.roleName,
2891
+ roleName: run.roleName,
2520
2892
  purpose: "execution",
2521
2893
  workItemId: item.id
2522
2894
  }, now, "controller", assignment.contextSnapshotRef);
2523
- const withContext = withTurnContextSnapshot(turn, contextSnapshotRef(snapshot), contextSnapshotDeltaRefIds(tx, snapshot));
2895
+ const withContext = withRunContextSnapshot(run, contextSnapshotRef(snapshot), contextSnapshotDeltaRefIds(tx, snapshot));
2524
2896
  const prepared = options.executionLaneWorkspaces?.get(group.lanes[index].id);
2525
2897
  if (prepared !== undefined && tx.getManagedWorkspace(prepared.owner) === null) {
2526
2898
  tx.saveManagedWorkspace(prepared);
2527
2899
  }
2528
- tx.saveTurn(withContext);
2529
- tx.saveActiveTurn(withContext);
2530
- enqueueRoleTurnDispatch(tx, {
2900
+ tx.saveRun(withContext);
2901
+ tx.saveActiveRun(withContext);
2902
+ enqueueRoleRunDispatch(tx, {
2531
2903
  taskId: task.id,
2532
2904
  roleName: plan.role.name,
2533
- turnId: withContext.id,
2905
+ runId: withContext.id,
2534
2906
  reason: "turn-dispatched",
2535
2907
  occurredAt: now
2536
2908
  });
2537
- recordTaskEvent(tx, task.id, "turn.dispatched", turnLaunchEventPayload(withContext), now);
2909
+ recordTaskEvent(tx, task.id, "run.dispatched", runLaunchEventPayload(withContext), now);
2538
2910
  return withContext;
2539
2911
  });
2540
- return { kind: "replicated", turns };
2912
+ return { kind: "replicated", runs };
2541
2913
  });
2542
- for (const turn of dispatch.turns) {
2543
- notifyMailbox(options.runtime, roleMailbox(turn.taskId, turn.roleName), turn.taskId);
2914
+ for (const run of dispatch.runs) {
2915
+ notifyMailbox(options.runtime, roleMailbox(run.taskId, run.roleName), run.taskId);
2544
2916
  }
2545
2917
  return dispatch.kind === "direct"
2546
- ? `Direct WorkItem Turn queued as ${dispatch.turns[0].id}\n`
2547
- : `Dispatch queued for ${dispatch.turns.length} replicated Lanes\n`;
2918
+ ? `Direct WorkItem AgentRun queued as ${dispatch.runs[0].id}\n`
2919
+ : `Dispatch queued for ${dispatch.runs.length} replicated Lanes\n`;
2548
2920
  }
2549
2921
  function replicatedProducerAssignmentInput(input, requiresCodeRef) {
2550
2922
  return [
@@ -2560,7 +2932,7 @@ function replicatedProducerAssignmentInput(input, requiresCodeRef) {
2560
2932
  }
2561
2933
  function acceptWork(args, store, options) {
2562
2934
  const usage = "Task work accept usage: yui task work accept <task>/<work> --summary <text>.";
2563
- const parsed = parseTail(args, new Set(["--summary"]), usage);
2935
+ const parsed = parseTail(args, new Set(["--summary", "--candidate"]), usage);
2564
2936
  exactPositionals(parsed.positionals, 1, usage);
2565
2937
  const summary = requiredOption(parsed.options, "--summary");
2566
2938
  const now = clock(options);
@@ -2571,17 +2943,20 @@ function acceptWork(args, store, options) {
2571
2943
  throw usageError(`Task is not active: ${task.id}/${task.status}.`);
2572
2944
  }
2573
2945
  const actor = taskActor(tx, options, task.id);
2574
- if (item.status !== "awaiting_acceptance") {
2946
+ if ((item.status !== "open" || item.candidates.length === 0)) {
2575
2947
  throw usageError(`Work Item is not awaiting acceptance: ${item.id}/${item.status}.`);
2576
2948
  }
2577
2949
  if (options.workItemIntegrationProof?.workspace.owner.type === "review-round") {
2578
2950
  throw usageError("A ReviewRound-owned workspace cannot be used for WorkItem acceptance.");
2579
2951
  }
2580
- const candidate = requireWorkItemCandidate(item);
2952
+ const candidateId = parsed.options.get("--candidate");
2953
+ const candidate = candidateId === undefined ? requireWorkItemCandidate(item)
2954
+ : item.candidates.find(({ id }) => id === candidateId);
2955
+ if (candidate === undefined)
2956
+ throw usageError(`Work Item Candidate not found: ${candidateId}.`);
2957
+ if (candidate.artifactRefs !== undefined && !isDeepStrictEqual(fixedArtifactRefs(tx, task.id, candidate.artifactRefs.map((ref) => ref.artifactId)), candidate.artifactRefs))
2958
+ throw usageError("Candidate Artifact references no longer match their saved immutable results.");
2581
2959
  const taskFinalContract = taskFinalReviewContractForMutation(tx, task.id, options);
2582
- if (!sameTaskFinalReviewContract(candidate.taskFinalReviewContract, taskFinalContract)) {
2583
- throw usageError(`Task final-review contract does not match Candidate ${candidate.id}.`);
2584
- }
2585
2960
  const latestReview = reviewRoundsByIdentity(tx.listReviewRounds(item.taskId)
2586
2961
  .filter((round) => round.workItemId === item.id
2587
2962
  && round.candidateId === candidate.id)).at(-1);
@@ -2609,17 +2984,25 @@ function acceptWork(args, store, options) {
2609
2984
  && isolatedWorkspace.owner.workItemId === item.id
2610
2985
  && isolatedWorkspace.entries.some(({ access }) => access === "write")) {
2611
2986
  assertWorkItemIntegrationProof(tx, item.id, item.assignee, isolatedWorkspace, options.workItemIntegrationProof);
2987
+ for (const proof of options.workItemIntegrationProof.projects) {
2988
+ const candidateCommit = candidate.gitSnapshot?.projects
2989
+ .find(({ projectId }) => projectId === proof.projectId)?.commit;
2990
+ if (candidateCommit === undefined || candidateCommit !== proof.headCommit) {
2991
+ throw usageError(`Selected Candidate ${candidate.id} does not match the integrated result for ${proof.projectId}.`);
2992
+ }
2993
+ }
2612
2994
  }
2613
- const completed = updateWorkItemStatus(item, "completed", now, summary);
2995
+ const completed = updateWorkItemStatus(item, "accepted", now, summary, candidate.id);
2614
2996
  tx.saveWorkItem(item.taskId, completed);
2615
2997
  recordTaskEvent(tx, item.taskId, "work.accepted", {
2616
2998
  workItemId: item.id,
2617
2999
  candidateId: candidate.id,
2618
- ...(candidate.source.type === "turn"
2619
- ? { turnId: candidate.source.turnId }
3000
+ ...(candidate.source.type === "run"
3001
+ ? { runId: candidate.source.runId }
2620
3002
  : { workItemRevision: String(candidate.workItemRevision) }),
2621
3003
  acceptedBy: actor,
2622
3004
  summary,
3005
+ ...(latestReview === undefined ? {} : { reviewRoundId: latestReview.id }),
2623
3006
  ...(actor === "leader" ? leaderActionEventPayload(tx, item.taskId, options) : {})
2624
3007
  }, now);
2625
3008
  return completed;
@@ -2665,7 +3048,7 @@ function rejectWork(args, store, options) {
2665
3048
  throw usageError(`Task is not active: ${task.id}/${task.status}.`);
2666
3049
  }
2667
3050
  const actor = taskActor(tx, options, task.id);
2668
- if (item.status !== "awaiting_acceptance") {
3051
+ if (item.status === "retired" || item.candidates.length === 0) {
2669
3052
  throw usageError(`Work Item is not awaiting acceptance: ${item.id}/${item.status}.`);
2670
3053
  }
2671
3054
  const candidate = requireWorkItemCandidate(item);
@@ -2673,7 +3056,7 @@ function rejectWork(args, store, options) {
2673
3056
  if (activeReview !== undefined) {
2674
3057
  throw usageError(`ReviewRound is still active: ${activeReview.id}/${activeReview.status}.`);
2675
3058
  }
2676
- const failed = updateWorkItemStatus(item, "failed", now, summary);
3059
+ const { currentCandidateId: _declinedCandidate, ...failed } = updateWorkItemStatus(item, "open", now);
2677
3060
  tx.saveWorkItem(item.taskId, failed);
2678
3061
  recordTaskEvent(tx, item.taskId, "work.rejected", {
2679
3062
  workItemId: item.id,
@@ -2727,12 +3110,12 @@ function retireWork(args, store, options) {
2727
3110
  + `${activeWorkItemJob.id}/${activeWorkItemJob.status}. `
2728
3111
  + "Cancel or acknowledge it before retiring.");
2729
3112
  }
2730
- for (const run of tx.listTurns(task.id).filter((candidate) => (candidate.status === "active" && candidate.workItemId === item.id))) {
2731
- const terminal = terminalizeExactTaskTurn(tx, {
3113
+ for (const run of tx.listRuns(task.id).filter((candidate) => (candidate.status === "active" && candidate.workItemId === item.id))) {
3114
+ const terminal = terminalizeExactTaskRun(tx, {
2732
3115
  taskId: task.id,
2733
3116
  roleName: run.roleName,
2734
3117
  agentId: run.effective.agentId,
2735
- turnId: run.id,
3118
+ runId: run.id,
2736
3119
  outcome: {
2737
3120
  status: "failed",
2738
3121
  diagnostic: `Work Item retired: ${summary}`,
@@ -2740,7 +3123,7 @@ function retireWork(args, store, options) {
2740
3123
  }
2741
3124
  }, now);
2742
3125
  if (terminal.disposition !== "applied") {
2743
- throw usageError(`Work Item Turn changed during retirement: ${run.id}/${terminal.reason ?? "obsolete"}.`);
3126
+ throw usageError(`Work Item AgentRun changed during retirement: ${run.id}/${terminal.reason ?? "obsolete"}.`);
2744
3127
  }
2745
3128
  }
2746
3129
  }
@@ -2777,9 +3160,9 @@ function listWork(args, store) {
2777
3160
  exactPositionals(args, 1, "Task work list usage: yui task work list <task>.");
2778
3161
  const task = requireTask(store, args[0]);
2779
3162
  const items = store.listWorkItems(task.id);
2780
- const turns = store.listTurns(task.id);
3163
+ const runs = store.listRuns(task.id);
2781
3164
  const sessionSets = store.listRoleSessionSets(task.id);
2782
- const executions = items.map((item) => projectWorkItemExecution(item, turns, sessionSets));
3165
+ const executions = items.map((item) => projectWorkItemExecution(item, runs, sessionSets, store));
2783
3166
  const rendered = items.length === 0
2784
3167
  ? "No work items found.\n"
2785
3168
  : `${renderTable(`Task work: ${task.id}`, [
@@ -2806,7 +3189,7 @@ function listWork(args, store) {
2806
3189
  function showWork(args, store, options) {
2807
3190
  exactPositionals(args, 1, "Task work show usage: yui task work show <work>.");
2808
3191
  const item = requireWorkItem(store, args[0], options);
2809
- const execution = projectWorkItemExecution(item, store.listTurns(item.taskId), store.listRoleSessionSets(item.taskId));
3192
+ const execution = projectWorkItemExecution(item, store.listRuns(item.taskId), store.listRoleSessionSets(item.taskId), store);
2810
3193
  const replacement = item.disposition?.replacementWorkItemId;
2811
3194
  const rendered = [
2812
3195
  `Work Item: ${item.id}`,
@@ -2827,7 +3210,7 @@ function showWork(args, store, options) {
2827
3210
  }
2828
3211
  function compactWorkItemExecution(projection) {
2829
3212
  if (projection.shape === "direct")
2830
- return `main=${projection.mainTurn.status}`;
3213
+ return `main=${projection.mainRun.status}`;
2831
3214
  const counts = projection.laneCounts;
2832
3215
  return `lanes ${counts.running}/${counts.succeeded}/${counts.needsAttention}/${counts.failed}/${counts.unknown}; ${projection.synthesis.status}`;
2833
3216
  }
@@ -2840,16 +3223,16 @@ function renderWorkItemExecutionProjection(projection) {
2840
3223
  : [
2841
3224
  `Lanes: running=${projection.laneCounts.running}, succeeded=${projection.laneCounts.succeeded}, needs-attention=${projection.laneCounts.needsAttention}, failed=${projection.laneCounts.failed}, unknown=${projection.laneCounts.unknown}`,
2842
3225
  ...projection.lanes.map((lane) => (` ${lane.laneId} (#${lane.ordinal}, ${lane.roleName}): ${lane.status}; `
2843
- + `turn=${lane.currentTurnId ?? "unknown"}; session=${lane.session}; `
2844
- + `retry=${lane.retryTurnId ?? "none"}; settle=${lane.settleTurnId ?? "none"}`))
3226
+ + `run=${lane.currentRunId ?? "unknown"}; session=${lane.session}; `
3227
+ + `retry=${lane.retryRunId ?? "none"}; settle=${lane.settleRunId ?? "none"}`))
2845
3228
  ]),
2846
- `Synthesis: ${projection.synthesis.status}; successful=${projection.synthesis.successfulLaneCount}/${projection.synthesis.requiredSuccessfulLaneCount}`,
2847
- `Main Turn: ${projection.mainTurn.turnId ?? "unobserved"} [${projection.mainTurn.status}]; role=${projection.mainTurn.roleName ?? "unobserved"}; session=${projection.mainTurn.session}; retry=${projection.mainTurn.retryTurnId ?? "none"}`,
2848
- `Candidate Source: ${projection.candidate.candidateId ?? "none"} [${projection.candidate.status}]; source=${projection.candidate.sourceType ?? "unobserved"}; main=${projection.candidate.mainTurnId ?? "unobserved"}`,
3229
+ `Synthesis: ${projection.synthesis.status}; successful=${projection.synthesis.successfulLaneCount}; sources selected by Leader`,
3230
+ `Main AgentRun: ${projection.mainRun.runId ?? "unobserved"} [${projection.mainRun.status}]; role=${projection.mainRun.roleName ?? "unobserved"}; session=${projection.mainRun.session}; retry=${projection.mainRun.retryRunId ?? "none"}`,
3231
+ `Candidate Source: ${projection.candidate.candidateId ?? "none"} [${projection.candidate.status}]; source=${projection.candidate.sourceType ?? "unobserved"}; main=${projection.candidate.mainRunId ?? "unobserved"}`,
2849
3232
  ...(projection.candidate.sourceExecutionGroupId === undefined
2850
3233
  ? []
2851
3234
  : [
2852
- `Candidate Provenance: main ${projection.candidate.mainTurnId ?? "unobserved"} -> group ${projection.candidate.sourceExecutionGroupId} -> ${projection.candidate.successfulLaneTurns.map(({ laneId, successfulTurnId }) => `${laneId} -> ${successfulTurnId}`).join(", ") || "unobserved"}`
3235
+ `Candidate Provenance: main ${projection.candidate.mainRunId ?? "unobserved"} -> group ${projection.candidate.sourceExecutionGroupId} -> ${projection.candidate.successfulLaneRuns.map(({ laneId, successfulRunId }) => `${laneId} -> ${successfulRunId}`).join(", ") || "unobserved"}`
2853
3236
  ]),
2854
3237
  `Next Action: ${projection.nextAction.kind}; owner=${projection.nextAction.owners.join(", ") || "none"}; target=${projection.nextAction.targetIds.join(", ") || "none"}`
2855
3238
  ];
@@ -2874,7 +3257,7 @@ function reviewWork(args, store, options) {
2874
3257
  throw usageError(`Task is not active: ${task.id}/${task.status}.`);
2875
3258
  }
2876
3259
  const requestedBy = taskActor(tx, options, task.id);
2877
- if (item.status !== "awaiting_acceptance") {
3260
+ if ((item.status !== "open" || item.candidates.length === 0)) {
2878
3261
  throw usageError(`Work Item is not awaiting acceptance: ${item.id}/${item.status}.`);
2879
3262
  }
2880
3263
  const candidate = requireWorkItemCandidate(item);
@@ -2892,9 +3275,9 @@ function reviewWork(args, store, options) {
2892
3275
  && (round.status === "pending" || round.status === "running")))).at(-1);
2893
3276
  if (activeRound !== undefined) {
2894
3277
  const producerDispatchPending = activeRound.executionGroup?.lanes.some((lane) => (lane.disposition === "open"
2895
- && (lane.currentTurnId === undefined
2896
- || tx.getTurn(task.id, lane.currentTurnId)?.status === "failed"))) === true;
2897
- if ((activeRound.status === "pending" && activeRound.reviewerTurnId === undefined)
3278
+ && (lane.currentRunId === undefined
3279
+ || tx.getRun(task.id, lane.currentRunId)?.status === "failed"))) === true;
3280
+ if ((activeRound.status === "pending" && activeRound.reviewerRunId === undefined)
2898
3281
  || (activeRound.status === "running" && producerDispatchPending)) {
2899
3282
  const persistedRoles = activeRound.executionGroup?.lanes
2900
3283
  .map(({ roleName }) => roleName) ?? [];
@@ -2927,7 +3310,7 @@ function reviewWork(args, store, options) {
2927
3310
  if (result.kind === "busy") {
2928
3311
  const busy = result.availability;
2929
3312
  return output(`Reviewer ${busy.reviewerRoleName} is busy (${busy.phase}`
2930
- + `${busy.activeTurnId === undefined ? "" : `; Turn ${busy.activeTurnId}`}); `
3313
+ + `${busy.activeRunId === undefined ? "" : `; AgentRun ${busy.activeRunId}`}); `
2931
3314
  + `${busy.activeReviewRoundId === undefined
2932
3315
  ? ""
2933
3316
  : `active ReviewRound ${busy.activeReviewRoundId}; `}`
@@ -2942,17 +3325,49 @@ function reviewWork(args, store, options) {
2942
3325
  }
2943
3326
  /**
2944
3327
  * Task-control recovery for a failed Task-final ReviewRound that never
2945
- * created a Reviewer Turn. This is deliberately separate from `task turn retry`:
2946
- * that command requires an exact failed Turn and remains the only retry
3328
+ * created a Reviewer AgentRun. This is deliberately separate from `task run retry`:
3329
+ * that command requires an exact failed AgentRun and remains the only retry
2947
3330
  * path for a failed provider execution. Here the old terminal Round is an
2948
3331
  * immutable anchor and one fresh Round is created only after the same frozen
2949
3332
  * committed Integration/ChangeSet provenance and Reviewer independence fences
2950
3333
  * pass again.
2951
3334
  */
3335
+ function synthesizeRuns(args, kind, store, options) {
3336
+ const subject = kind === "workItem" ? "work" : "review";
3337
+ const usage = `Usage: yui task ${subject} synthesize <task>/<${subject}> --source-run <task>/<run> ...`;
3338
+ const parsed = parseMultiValueTail(args, new Set(), new Set(["--source-run"]), usage);
3339
+ exactPositionals(parsed.positionals, 1, usage);
3340
+ const reference = taskRecordReference(parsed.positionals[0], kind, "Synthesis target", options);
3341
+ const sources = parsed.multiOptions.get("--source-run") ?? [];
3342
+ const sourceRunIds = sources.map((value) => {
3343
+ const source = taskRecordReference(value, "run", "Source AgentRun", options);
3344
+ if (source.taskId !== reference.taskId)
3345
+ throw usageError("Synthesis sources must belong to the same Task.");
3346
+ return source.localId;
3347
+ });
3348
+ const now = clock(options);
3349
+ const run = store.transaction((tx) => {
3350
+ const actor = taskActor(tx, options, reference.taskId);
3351
+ const created = kind === "workItem"
3352
+ ? dispatchWorkItemSynthesis(tx, reference.taskId, reference.localId, sourceRunIds, now)
3353
+ : dispatchReviewSynthesis(tx, reference.taskId, reference.localId, sourceRunIds, now);
3354
+ recordTaskEvent(tx, reference.taskId, "run.synthesis-requested", {
3355
+ runId: created.id,
3356
+ requestedBy: actor,
3357
+ sourceRunIds: sourceRunIds.join(","),
3358
+ ...(actor === "leader" ? leaderActionEventPayload(tx, reference.taskId, options) : {})
3359
+ }, now);
3360
+ return created;
3361
+ });
3362
+ notifyMailbox(options.runtime, roleMailbox(run.taskId, run.roleName), run.taskId);
3363
+ return output(`Dispatched synthesis AgentRun ${run.taskId}/${run.id}\n`, { run });
3364
+ }
2952
3365
  function taskReviewCommand(args, store, options) {
2953
3366
  const [command, ...rest] = args;
2954
3367
  if (command === "request")
2955
3368
  return requestTaskReviewRound(rest, store, options);
3369
+ if (command === "synthesize")
3370
+ return synthesizeRuns(rest, "reviewRound", store, options);
2956
3371
  if (command === "retry")
2957
3372
  return retryFailedTaskReviewRound(rest, store, options);
2958
3373
  throw usageError(command === undefined
@@ -2960,7 +3375,7 @@ function taskReviewCommand(args, store, options) {
2960
3375
  : `Unknown command: task review ${command}`);
2961
3376
  }
2962
3377
  function requestTaskReviewRound(args, store, options) {
2963
- const usage = "Task review request usage: yui task review request <task> --role <global-role> "
3378
+ const usage = "Task review request usage: yui task review request <task> --role <reviewer-role> "
2964
3379
  + "[--lane-role <producer-role> ...] [--delta-recheck].";
2965
3380
  const parsed = parseMultiValueTail(args, new Set(["--role"]), new Set(["--lane-role"]), usage, new Set(["--delta-recheck"]));
2966
3381
  exactPositionals(parsed.positionals, 1, usage);
@@ -2985,8 +3400,8 @@ function requestTaskReviewRound(args, store, options) {
2985
3400
  throw usageError("Delta-recheck is not supported with a Task-final review contract.");
2986
3401
  }
2987
3402
  }
2988
- if (tx.getGlobalRole(reviewerRoleName) === null) {
2989
- throw usageError(`Global Role not found: ${reviewerRoleName}.`);
3403
+ if (tx.getRole(task.id, reviewerRoleName) === null && tx.getGlobalRole(reviewerRoleName) === null) {
3404
+ throw usageError(`Reviewer Role not found in this Task or global templates: ${reviewerRoleName}.`);
2990
3405
  }
2991
3406
  const provenance = taskReviewProvenance(tx, task, options);
2992
3407
  const producerCollision = taskReviewProducerCollision(provenance, reviewerRoleName);
@@ -3087,7 +3502,7 @@ function requestTaskReviewRound(args, store, options) {
3087
3502
  });
3088
3503
  if ("kind" in round && round.kind === "busy") {
3089
3504
  return output(`Reviewer ${round.reviewerRoleName} is busy (${round.phase}`
3090
- + `${round.activeTurnId === undefined ? "" : `; Turn ${round.activeTurnId}`}); `
3505
+ + `${round.activeRunId === undefined ? "" : `; AgentRun ${round.activeRunId}`}); `
3091
3506
  + `${round.activeReviewRoundId === undefined
3092
3507
  ? ""
3093
3508
  : `active ReviewRound ${round.activeReviewRoundId}; `}`
@@ -3126,26 +3541,26 @@ function validateDeltaRecheckRequest(store, taskId, candidate, preflight) {
3126
3541
  return preflight.record;
3127
3542
  }
3128
3543
  function assertTaskReviewRequestLane(store, taskId, reviewerRoleName, reusableRound) {
3129
- const activePointer = store.getActiveTurn(taskId, reviewerRoleName);
3544
+ const activePointer = store.getActiveRun(taskId, reviewerRoleName);
3130
3545
  if (reusableRound === undefined || reusableRound.status === "completed") {
3131
3546
  assertReviewerAvailable(store, taskId, reviewerRoleName);
3132
3547
  return;
3133
3548
  }
3134
3549
  if (reusableRound.status === "pending") {
3135
3550
  if (activePointer !== null) {
3136
- throw usageError(`Reviewer Role already has an active Turn: ${reviewerRoleName}.`);
3551
+ throw usageError(`Reviewer Role already has an active AgentRun: ${reviewerRoleName}.`);
3137
3552
  }
3138
3553
  assertReviewerAvailable(store, taskId, reviewerRoleName, reusableRound);
3139
3554
  return;
3140
3555
  }
3141
- const reviewerTurnId = reusableRound.reviewerTurnId;
3142
- if (reviewerTurnId === undefined
3556
+ const reviewerRunId = reusableRound.reviewerRunId;
3557
+ if (reviewerRunId === undefined
3143
3558
  && reusableRound.executionGroup?.lanes.some(({ disposition }) => disposition === "open")) {
3144
3559
  assertReviewerAvailable(store, taskId, reviewerRoleName, reusableRound);
3145
3560
  return;
3146
3561
  }
3147
- const activeMatches = reviewerTurnId !== undefined
3148
- && activePointer?.id === reviewerTurnId
3562
+ const activeMatches = reviewerRunId !== undefined
3563
+ && activePointer?.id === reviewerRunId
3149
3564
  && activePointer.status === "active";
3150
3565
  if (!activeMatches) {
3151
3566
  throw usageError(`Existing Task-final ReviewRound ${reusableRound.id} is running without its exact Reviewer execution.`);
@@ -3160,7 +3575,7 @@ function assertReviewerAvailable(store, taskId, reviewerRoleName, reusableRound)
3160
3575
  return;
3161
3576
  }
3162
3577
  throw usageError(`Reviewer ${reviewerRoleName} is busy (${availability.phase}`
3163
- + `${availability.activeTurnId === undefined ? "" : `; Turn ${availability.activeTurnId}`}`
3578
+ + `${availability.activeRunId === undefined ? "" : `; AgentRun ${availability.activeRunId}`}`
3164
3579
  + `${availability.activeReviewRoundId === undefined
3165
3580
  ? ""
3166
3581
  : `; ReviewRound ${availability.activeReviewRoundId}`}).`);
@@ -3170,10 +3585,10 @@ function reviewerBusyBelongsToRound(busy, round) {
3170
3585
  return false;
3171
3586
  if (busy.phase === "review-slot")
3172
3587
  return true;
3173
- if (busy.phase !== "active-turn" || busy.activeTurnId === undefined)
3588
+ if (busy.phase !== "active-turn" || busy.activeRunId === undefined)
3174
3589
  return false;
3175
- return round.reviewerTurnId === busy.activeTurnId
3176
- || round.executionGroup?.lanes.some(({ currentTurnId }) => (currentTurnId === busy.activeTurnId)) === true;
3590
+ return round.reviewerRunId === busy.activeRunId
3591
+ || round.executionGroup?.lanes.some(({ currentRunId }) => (currentRunId === busy.activeRunId)) === true;
3177
3592
  }
3178
3593
  function retryFailedTaskReviewRound(args, store, options) {
3179
3594
  exactPositionals(args, 1, "Task review retry usage: yui task review retry <task>/<review-round>.");
@@ -3191,11 +3606,11 @@ function retryFailedTaskReviewRound(args, store, options) {
3191
3606
  if ((round.scope ?? "work-item") !== "task") {
3192
3607
  throw usageError(`ReviewRound ${round.id} is not a failed Task-final ReviewRound.`);
3193
3608
  }
3194
- if (round.reviewerTurnId !== undefined) {
3609
+ if (round.reviewerRunId !== undefined) {
3195
3610
  if (round.status === "completed") {
3196
3611
  throw usageError(`ReviewRound ${round.id} is not retryable from ${round.status}.`);
3197
3612
  }
3198
- throw usageError(`ReviewRound ${round.id} has Reviewer Turn ${round.reviewerTurnId}; use task turn retry instead.`);
3613
+ throw usageError(`ReviewRound ${round.id} has Reviewer AgentRun ${round.reviewerRunId}; use task run retry instead.`);
3199
3614
  }
3200
3615
  if (round.status !== "failed" && round.status !== "pending") {
3201
3616
  throw usageError(`ReviewRound ${round.id} is not retryable from ${round.status}.`);
@@ -3235,9 +3650,9 @@ function retryFailedTaskReviewRound(args, store, options) {
3235
3650
  reviewer = createTaskRole(tx, task, round.reviewerRoleName, undefined, now, round.reviewerRoleName);
3236
3651
  tx.saveRole(task.id, reviewer);
3237
3652
  }
3238
- const activePointer = tx.getActiveTurn(task.id, reviewer.name);
3653
+ const activePointer = tx.getActiveRun(task.id, reviewer.name);
3239
3654
  if (activePointer !== null) {
3240
- throw usageError(`Reviewer Role already has an active Turn: ${reviewer.name}.`);
3655
+ throw usageError(`Reviewer Role already has an active AgentRun: ${reviewer.name}.`);
3241
3656
  }
3242
3657
  assertReviewerAvailable(tx, task.id, reviewer.name, round);
3243
3658
  // Issue 06: an already-pending Round is the idempotent retry result.
@@ -3258,50 +3673,50 @@ function retryFailedTaskReviewRound(args, store, options) {
3258
3673
  ? `Task-final Review retry requested as ${result.round.id}\n`
3259
3674
  : `Task-final Review retry already requested as ${result.round.id} (${result.round.status})\n`, { reviewRound: result.round });
3260
3675
  }
3261
- function taskTurnCommand(args, store, options) {
3676
+ function taskRunCommand(args, store, options) {
3262
3677
  const [command, ...rest] = args;
3263
3678
  if (command === "list")
3264
- return output(listTurns(rest, store, options));
3679
+ return output(listRuns(rest, store, options));
3265
3680
  if (command === "show")
3266
- return showTurn(rest, store, options);
3681
+ return showRun(rest, store, options);
3267
3682
  if (command === "context")
3268
- return turnContextCommand(rest, store, options);
3683
+ return runContextCommand(rest, store, options);
3269
3684
  if (command === "retry")
3270
- return retryTurn(rest, store, options);
3685
+ return retryRun(rest, store, options);
3271
3686
  if (command === "settle")
3272
- return settleTurn(rest, store, options);
3687
+ return settleRun(rest, store, options);
3273
3688
  if (command === "checkpoint")
3274
- return output(checkpointTurn(rest, store, options));
3689
+ return output(checkpointRun(rest, store, options));
3275
3690
  if (command === "retire")
3276
- return retireTurn(rest, store, options);
3691
+ return retireRun(rest, store, options);
3277
3692
  throw usageError(command === undefined
3278
3693
  ? "Task turn command is required."
3279
- : `Unknown command: task turn ${command}`);
3694
+ : `Unknown command: task run ${command}`);
3280
3695
  }
3281
- function settleTurn(args, store, options) {
3282
- exactPositionals(args, 1, "Task turn settle usage: yui task turn settle <task>/<turn>.");
3283
- const previous = store.transaction((tx) => requireTurn(tx, args[0], options));
3696
+ function settleRun(args, store, options) {
3697
+ exactPositionals(args, 1, "Task turn settle usage: yui task run settle <task>/<run>.");
3698
+ const previous = store.transaction((tx) => requireRun(tx, args[0], options));
3284
3699
  if (previous.purpose === "review") {
3285
3700
  if (previous.executionGroupId !== undefined
3286
3701
  && previous.executionLaneId !== undefined) {
3287
- return settleFailedReviewExecutionLaneTurn(previous, store, options);
3702
+ return settleFailedReviewExecutionLaneRun(previous, store, options);
3288
3703
  }
3289
- return settleStaleFinalReviewTurn(args, store, options);
3704
+ return settleStaleFinalReviewRun(args, store, options);
3290
3705
  }
3291
- return settleFailedExecutionLaneTurn(previous, store, options);
3706
+ return settleFailedExecutionLaneRun(previous, store, options);
3292
3707
  }
3293
- function settleFailedReviewExecutionLaneTurn(previous, store, options) {
3708
+ function settleFailedReviewExecutionLaneRun(previous, store, options) {
3294
3709
  const now = clock(options);
3295
3710
  const result = store.transaction((tx) => {
3296
- const run = tx.getTurn(previous.taskId, previous.id);
3711
+ const run = tx.getRun(previous.taskId, previous.id);
3297
3712
  if (run === null || run.status !== "failed" || run.purpose !== "review") {
3298
- throw usageError(`Turn ${previous.id} is not a failed review Turn.`);
3713
+ throw usageError(`AgentRun ${previous.id} is not a failed review AgentRun.`);
3299
3714
  }
3300
3715
  if (run.reviewRoundId === undefined
3301
3716
  || run.executionGroupId === undefined
3302
3717
  || run.executionLaneId === undefined
3303
3718
  || run.sourceExecutionGroupId !== undefined) {
3304
- throw usageError(`Turn ${run.id} is not a failed Review Producer Lane Turn.`);
3719
+ throw usageError(`AgentRun ${run.id} is not a failed Review Producer Lane AgentRun.`);
3305
3720
  }
3306
3721
  const task = requireTask(tx, run.taskId);
3307
3722
  if (task.status !== "active")
@@ -3309,80 +3724,79 @@ function settleFailedReviewExecutionLaneTurn(previous, store, options) {
3309
3724
  const actor = taskActor(tx, options, task.id);
3310
3725
  const round = tx.getReviewRound(task.id, run.reviewRoundId);
3311
3726
  if (round === null) {
3312
- throw dataError(`ReviewRound not found for Turn ${run.id}: ${run.reviewRoundId}.`);
3727
+ throw dataError(`ReviewRound not found for AgentRun ${run.id}: ${run.reviewRoundId}.`);
3313
3728
  }
3314
3729
  const group = round.executionGroup;
3315
3730
  if (group === undefined || group.id !== run.executionGroupId) {
3316
- throw usageError(`Turn ${run.id} no longer belongs to the Review ExecutionGroup.`);
3731
+ throw usageError(`AgentRun ${run.id} no longer belongs to the Review ExecutionGroup.`);
3317
3732
  }
3318
3733
  const lane = group.lanes.find(({ id }) => id === run.executionLaneId);
3319
- if (lane === undefined || lane.currentTurnId !== run.id || lane.roleName !== run.roleName) {
3320
- throw usageError(`Turn ${run.id} no longer owns its Review Producer Lane.`);
3734
+ if (lane === undefined || lane.currentRunId !== run.id || lane.roleName !== run.roleName) {
3735
+ throw usageError(`AgentRun ${run.id} no longer owns its Review Producer Lane.`);
3321
3736
  }
3322
3737
  if (lane.disposition === "failed") {
3323
3738
  return {
3324
- turn: run,
3739
+ run: run,
3325
3740
  reviewRound: round,
3326
3741
  changed: false,
3327
- mainTurns: []
3742
+ mainRuns: []
3328
3743
  };
3329
3744
  }
3330
3745
  if (round.status !== "running" || lane.disposition !== "open") {
3331
- throw usageError(`Turn ${run.id} cannot settle ${round.id}/${group.id}/${lane.id} from `
3746
+ throw usageError(`AgentRun ${run.id} cannot settle ${round.id}/${group.id}/${lane.id} from `
3332
3747
  + `${round.status}/${lane.disposition}.`);
3333
3748
  }
3334
- const validation = validateExactTurnReviewRound(tx, run, { allowTerminal: true });
3749
+ const validation = validateExactRunReviewRound(tx, run, { allowTerminal: true });
3335
3750
  if (validation.disposition !== "applied") {
3336
- throw usageError(`Review Turn ${run.id} no longer matches its frozen Review Lane: `
3751
+ throw usageError(`Review AgentRun ${run.id} no longer matches its frozen Review Lane: `
3337
3752
  + `${validation.reason ?? "mismatch"}.`);
3338
3753
  }
3339
- if (tx.getActiveExecutionLaneTurn(task.id, group.id, lane.id) !== null) {
3340
- throw usageError(`Review Producer Lane still has an active Turn: ${group.id}/${lane.id}.`);
3754
+ if (tx.getActiveExecutionLaneRun(task.id, group.id, lane.id) !== null) {
3755
+ throw usageError(`Review Producer Lane still has an active AgentRun: ${group.id}/${lane.id}.`);
3341
3756
  }
3342
3757
  const settledGroup = updateUnifiedExecutionLane(group, lane.id, {
3343
- currentTurnId: run.id,
3758
+ currentRunId: run.id,
3344
3759
  disposition: "failed"
3345
3760
  }, now);
3346
3761
  tx.saveReviewRound(task.id, updateReviewExecutionGroup(round, settledGroup));
3347
- recordTaskEvent(tx, task.id, "turn.review-settled", {
3348
- turnId: run.id,
3762
+ recordTaskEvent(tx, task.id, "run.review-settled", {
3763
+ runId: run.id,
3349
3764
  reviewRoundId: round.id,
3350
3765
  executionGroupId: group.id,
3351
3766
  executionLaneId: lane.id,
3352
3767
  settledBy: actor,
3353
3768
  ...(actor === "leader" ? leaderActionEventPayload(tx, task.id, options) : {})
3354
3769
  }, now);
3355
- const reconciliation = reconcileReviewMainTurns(tx, task.id, now);
3356
3770
  return {
3357
- turn: run,
3771
+ run: run,
3358
3772
  reviewRound: tx.getReviewRound(task.id, round.id),
3359
3773
  changed: true,
3360
- mainTurns: reconciliation.createdTurns
3774
+ mainRuns: []
3361
3775
  };
3362
3776
  });
3363
- for (const turn of result.mainTurns) {
3364
- notifyMailbox(options.runtime, roleMailbox(turn.taskId, turn.roleName), turn.taskId);
3777
+ for (const run of result.mainRuns) {
3778
+ notifyMailbox(options.runtime, roleMailbox(run.taskId, run.roleName), run.taskId);
3365
3779
  }
3366
3780
  return output(result.changed
3367
- ? `Settled failed Review Producer Lane from Turn ${result.turn.id}\n`
3368
- : `Failed Review Producer Lane already settled from Turn ${result.turn.id}\n`, {
3369
- turn: result.turn,
3781
+ ? `Settled failed Review Producer Lane from AgentRun ${result.run.id}\n`
3782
+ : `Failed Review Producer Lane already settled from AgentRun ${result.run.id}\n`, {
3783
+ run: result.run,
3370
3784
  reviewRound: result.reviewRound,
3371
- ...(result.mainTurns.length === 0 ? {} : { mainTurns: result.mainTurns })
3785
+ ...(result.mainRuns.length === 0 ? {} : { mainRuns: result.mainRuns })
3372
3786
  });
3373
3787
  }
3374
- function settleFailedExecutionLaneTurn(previous, store, options) {
3788
+ function settleFailedExecutionLaneRun(previous, store, options) {
3375
3789
  const now = clock(options);
3376
3790
  const result = store.transaction((tx) => {
3377
- const run = tx.getTurn(previous.taskId, previous.id);
3791
+ const run = tx.getRun(previous.taskId, previous.id);
3378
3792
  if (run === null || run.status !== "failed" || run.purpose !== "execution") {
3379
- throw usageError(`Turn ${previous.id} is not a failed execution Turn.`);
3793
+ throw usageError(`AgentRun ${previous.id} is not a failed execution AgentRun.`);
3380
3794
  }
3381
3795
  if (run.workItemId === undefined
3382
3796
  || run.executionGroupId === undefined
3383
3797
  || run.executionLaneId === undefined
3384
3798
  || run.sourceExecutionGroupId !== undefined) {
3385
- throw usageError(`Turn ${run.id} is not a failed WorkItem Execution Lane Turn.`);
3799
+ throw usageError(`AgentRun ${run.id} is not a failed WorkItem Execution Lane AgentRun.`);
3386
3800
  }
3387
3801
  const task = requireTask(tx, run.taskId);
3388
3802
  if (task.status !== "active")
@@ -3390,94 +3804,88 @@ function settleFailedExecutionLaneTurn(previous, store, options) {
3390
3804
  const actor = taskActor(tx, options, task.id);
3391
3805
  const item = tx.getWorkItem(task.id, run.workItemId);
3392
3806
  if (item === null)
3393
- throw dataError(`Work item not found for Turn ${run.id}: ${run.workItemId}.`);
3807
+ throw dataError(`Work item not found for AgentRun ${run.id}: ${run.workItemId}.`);
3394
3808
  const group = currentWorkItemExecutionGroup(item);
3395
3809
  if (group === undefined || group.id !== run.executionGroupId) {
3396
- throw usageError(`Turn ${run.id} no longer belongs to the current ExecutionGroup.`);
3810
+ throw usageError(`AgentRun ${run.id} no longer belongs to the current ExecutionGroup.`);
3397
3811
  }
3398
3812
  const lane = group.lanes.find(({ id }) => id === run.executionLaneId);
3399
- if (lane === undefined || lane.currentTurnId !== run.id) {
3400
- throw usageError(`Turn ${run.id} no longer owns its Execution Lane.`);
3813
+ if (lane === undefined || lane.currentRunId !== run.id) {
3814
+ throw usageError(`AgentRun ${run.id} no longer owns its Execution Lane.`);
3401
3815
  }
3402
3816
  if (lane.disposition === "failed") {
3403
3817
  return {
3404
- turn: run,
3818
+ run: run,
3405
3819
  workItem: item,
3406
3820
  changed: false,
3407
- mainTurns: []
3821
+ mainRuns: []
3408
3822
  };
3409
3823
  }
3410
- if (item.status !== "running" || lane.disposition !== "open") {
3411
- throw usageError(`Turn ${run.id} cannot settle ${item.id}/${group.id}/${lane.id} from `
3824
+ if (item.status !== "open" || lane.disposition !== "open") {
3825
+ throw usageError(`AgentRun ${run.id} cannot settle ${item.id}/${group.id}/${lane.id} from `
3412
3826
  + `${item.status}/${lane.disposition}.`);
3413
3827
  }
3414
- if (tx.getActiveExecutionLaneTurn(task.id, group.id, lane.id) !== null) {
3415
- throw usageError(`Execution Lane still has an active Turn: ${group.id}/${lane.id}.`);
3828
+ if (tx.getActiveExecutionLaneRun(task.id, group.id, lane.id) !== null) {
3829
+ throw usageError(`Execution Lane still has an active AgentRun: ${group.id}/${lane.id}.`);
3416
3830
  }
3417
3831
  const settledGroup = updateWorkItemExecutionLane(group, lane.id, {
3418
- currentTurnId: run.id,
3832
+ currentRunId: run.id,
3419
3833
  disposition: "failed"
3420
3834
  }, now);
3421
3835
  const settledItem = updateWorkItemExecutionGroup(item, settledGroup, now);
3422
3836
  tx.saveWorkItem(task.id, settledItem);
3423
- recordTaskEvent(tx, task.id, "turn.execution-settled", {
3424
- turnId: run.id,
3837
+ recordTaskEvent(tx, task.id, "run.execution-settled", {
3838
+ runId: run.id,
3425
3839
  workItemId: item.id,
3426
3840
  executionGroupId: group.id,
3427
3841
  executionLaneId: lane.id,
3428
3842
  settledBy: actor,
3429
3843
  ...(actor === "leader" ? leaderActionEventPayload(tx, task.id, options) : {})
3430
3844
  }, now);
3431
- const reconciliation = reconcileWorkItemMainTurns(tx, task.id, now);
3432
3845
  return {
3433
- turn: run,
3846
+ run: run,
3434
3847
  workItem: tx.getWorkItem(task.id, item.id) ?? settledItem,
3435
3848
  changed: true,
3436
- mainTurns: reconciliation.createdTurns
3849
+ mainRuns: []
3437
3850
  };
3438
3851
  });
3439
- for (const turn of result.mainTurns) {
3440
- notifyMailbox(options.runtime, roleMailbox(turn.taskId, turn.roleName), turn.taskId);
3852
+ for (const run of result.mainRuns) {
3853
+ notifyMailbox(options.runtime, roleMailbox(run.taskId, run.roleName), run.taskId);
3441
3854
  }
3442
3855
  return output(result.changed
3443
- ? `Settled failed Execution Lane from Turn ${result.turn.id}\n`
3444
- : `Failed Execution Lane already settled from Turn ${result.turn.id}\n`, {
3445
- turn: result.turn,
3856
+ ? `Settled failed Execution Lane from AgentRun ${result.run.id}\n`
3857
+ : `Failed Execution Lane already settled from AgentRun ${result.run.id}\n`, {
3858
+ run: result.run,
3446
3859
  workItem: result.workItem,
3447
- ...(result.mainTurns.length === 0 ? {} : { mainTurns: result.mainTurns })
3860
+ ...(result.mainRuns.length === 0 ? {} : { mainRuns: result.mainRuns })
3448
3861
  });
3449
3862
  }
3450
- function retireTurn(args, store, options) {
3451
- const usage = "Task turn retire usage: yui task turn retire <task>/<turn> --reason <text> [--expected-progress-at <timestamp>] [--agent-id <id>] [--adapter-id <id>] [--native-session-id <id>] [--launch-id <id>].";
3863
+ function retireRun(args, store, options) {
3864
+ const usage = "Task turn retire usage: yui task run retire <task>/<run> --reason <text> [--expected-progress-at <timestamp>] [--agent-id <id>] [--adapter-id <id>] [--native-session-id <id>].";
3452
3865
  const parsed = parseTail(args, new Set([
3453
3866
  "--reason",
3454
3867
  "--expected-progress-at",
3455
3868
  "--progress-at",
3456
3869
  "--agent-id",
3457
3870
  "--adapter-id",
3458
- "--native-session-id",
3459
- "--launch-id"
3871
+ "--native-session-id"
3460
3872
  ]), usage);
3461
3873
  exactPositionals(parsed.positionals, 1, usage);
3462
3874
  const reason = requiredOption(parsed.options, "--reason");
3463
- const reference = taskRecordReference(parsed.positionals[0], "turn", "Turn reference", options);
3875
+ const reference = taskRecordReference(parsed.positionals[0], "run", "AgentRun reference", options);
3464
3876
  const now = clock(options);
3465
3877
  const result = store.transaction((tx) => {
3466
3878
  const task = requireTask(tx, reference.taskId);
3467
3879
  assertTaskOpen(task);
3468
3880
  const actor = taskActor(tx, options, task.id);
3469
- let run = tx.getTurn(task.id, reference.localId);
3881
+ let run = tx.getRun(task.id, reference.localId);
3470
3882
  if (run === null)
3471
- throw dataError(`Turn not found: ${task.id}/${reference.localId}.`);
3883
+ throw dataError(`AgentRun not found: ${task.id}/${reference.localId}.`);
3472
3884
  const events = tx.listEvents(task.id);
3473
- if (isTaskRecordRetired(events, "turn", run.id)) {
3474
- return { task, turn: run, changed: false };
3885
+ if (isTaskRecordRetired(events, "run", run.id)) {
3886
+ return { task, run: run, changed: false };
3475
3887
  }
3476
3888
  if (run.status === "active") {
3477
- if (actor === "leader"
3478
- && taskLeaderActionTurnId(tx, task.id, options.environment) === run.id) {
3479
- throw usageError("A Task Leader cannot retire its own current authority Turn.", usage);
3480
- }
3481
3889
  const expectedProgressAt = requiredOption(parsed.options, parsed.options.has("--expected-progress-at")
3482
3890
  ? "--expected-progress-at"
3483
3891
  : "--progress-at");
@@ -3487,35 +3895,30 @@ function retireTurn(args, store, options) {
3487
3895
  const agentId = requiredOption(parsed.options, "--agent-id");
3488
3896
  const adapterId = requiredOption(parsed.options, "--adapter-id");
3489
3897
  const nativeSessionId = parsed.options.get("--native-session-id");
3490
- const runtimeGenerationId = parsed.options.get("--launch-id");
3491
3898
  const sessions = tx.getTaskRoleSessionSet(task.id, run.roleName);
3492
3899
  const session = sessions?.sessions[run.effective.agentId];
3493
3900
  if (session?.nativeSessionId !== undefined && nativeSessionId === undefined) {
3494
- throw usageError("--native-session-id is required for this active Turn.", usage);
3901
+ throw usageError("--native-session-id is required for this active AgentRun.", usage);
3495
3902
  }
3496
- if (session?.nativeSessionId === undefined && runtimeGenerationId === undefined) {
3497
- throw usageError("--launch-id is required for an opaque active Turn.", usage);
3498
- }
3499
- const terminal = retireExactActiveTurn(tx, {
3903
+ const terminal = retireExactActiveRun(tx, {
3500
3904
  taskId: task.id,
3501
3905
  roleName: run.roleName,
3502
- turnId: run.id,
3906
+ runId: run.id,
3503
3907
  agentId,
3504
3908
  adapterId,
3505
3909
  ...(nativeSessionId === undefined ? {} : { nativeSessionId }),
3506
- ...(runtimeGenerationId === undefined ? {} : { runtimeGenerationId }),
3507
3910
  expectedProgressAt,
3508
- reason: `Turn retired: ${reason}`
3911
+ reason: `AgentRun retired: ${reason}`
3509
3912
  }, now);
3510
- if (terminal.disposition !== "applied" || terminal.turn === null) {
3913
+ if (terminal.disposition !== "applied" || terminal.run === null) {
3511
3914
  throw usageError(terminal.disposition === "blocked"
3512
- ? `Turn retirement is blocked: ${run.id}/${terminal.reason ?? "unsafe"}.`
3513
- : `Turn changed during retirement: ${run.id}/${terminal.reason ?? "obsolete"}.`);
3915
+ ? `AgentRun retirement is blocked: ${run.id}/${terminal.reason ?? "unsafe"}.`
3916
+ : `AgentRun changed during retirement: ${run.id}/${terminal.reason ?? "obsolete"}.`);
3514
3917
  }
3515
- run = terminal.turn;
3918
+ run = terminal.run;
3516
3919
  }
3517
- recordTaskEvent(tx, task.id, "turn.retired", {
3518
- turnId: run.id,
3920
+ recordTaskEvent(tx, task.id, "run.retired", {
3921
+ runId: run.id,
3519
3922
  reason,
3520
3923
  ...(parsed.options.get("--expected-progress-at") === undefined
3521
3924
  && parsed.options.get("--progress-at") === undefined
@@ -3527,9 +3930,6 @@ function retireTurn(args, store, options) {
3527
3930
  ...(parsed.options.get("--native-session-id") === undefined
3528
3931
  ? {}
3529
3932
  : { nativeSessionId: parsed.options.get("--native-session-id") }),
3530
- ...(parsed.options.get("--launch-id") === undefined
3531
- ? {}
3532
- : { runtimeGenerationId: parsed.options.get("--launch-id") }),
3533
3933
  ...(actor === "leader"
3534
3934
  ? leaderActionEventPayload(tx, task.id, options)
3535
3935
  : { retiredBy: actor })
@@ -3537,98 +3937,98 @@ function retireTurn(args, store, options) {
3537
3937
  tx.saveEvent(task.id, createTaskRecordRetirement({
3538
3938
  eventId: tx.nextEventId(task.id),
3539
3939
  taskId: task.id,
3540
- recordKind: "turn",
3940
+ recordKind: "run",
3541
3941
  recordId: run.id,
3542
3942
  reason,
3543
3943
  retiredBy: actor
3544
3944
  }, now));
3545
- return { task, turn: run, changed: true };
3945
+ return { task, run: run, changed: true };
3546
3946
  });
3547
3947
  if (result.changed)
3548
3948
  options.runtime?.notifyStateChanged(result.task.id);
3549
- return output(`Retired Turn ${result.task.id}/${result.turn.id}\n`, {
3550
- turn: result.turn,
3949
+ return output(`Retired AgentRun ${result.task.id}/${result.run.id}\n`, {
3950
+ run: result.run,
3551
3951
  retired: true
3552
3952
  });
3553
3953
  }
3554
- function turnContextCommand(args, store, options) {
3954
+ function runContextCommand(args, store, options) {
3555
3955
  const [first, ...rest] = args;
3556
3956
  if (first === "expand") {
3557
- const usage = "Task turn context expand usage: yui task turn context expand <task>/<turn> <ref-id> [--store <store>] [--mode full].";
3957
+ const usage = "Task turn context expand usage: yui task run context expand <task>/<run> <ref-id> [--store <store>] [--mode full].";
3558
3958
  const parsed = parseTail(rest, new Set(["--store", "--mode"]), usage);
3559
3959
  exactPositionals(parsed.positionals, 2, usage);
3560
3960
  const mode = parsed.options.get("--mode");
3561
3961
  if (mode !== undefined && mode !== "full") {
3562
- throw usageError("Turn Context expansion mode must be full.", usage);
3962
+ throw usageError("AgentRun Context expansion mode must be full.", usage);
3563
3963
  }
3564
- const { taskId, turnId } = parseTurnContextReference(parsed.positionals[0]);
3565
- authorizeTurnContext(store, taskId, turnId, options.environment);
3566
- const expanded = store.transaction((tx) => expandTurnContextRef(tx, taskId, turnId, parsed.positionals[1], optionalNonEmptyOption(parsed.options, "--store")));
3964
+ const { taskId, runId } = parseRunContextReference(parsed.positionals[0]);
3965
+ authorizeRunContext(store, taskId, runId, options.environment);
3966
+ const expanded = store.transaction((tx) => expandRunContextRef(tx, taskId, runId, parsed.positionals[1], optionalNonEmptyOption(parsed.options, "--store")));
3567
3967
  return output(`${JSON.stringify(expanded, null, 2)}\n`, { context: expanded });
3568
3968
  }
3569
3969
  if (first === "delta") {
3570
3970
  if (rest.length !== 3 || rest[1] !== "--after") {
3571
- throw usageError("Task turn context delta usage: yui task turn context delta <task>/<turn> --after <cursor>.");
3971
+ throw usageError("Task turn context delta usage: yui task run context delta <task>/<run> --after <cursor>.");
3572
3972
  }
3573
- const { taskId, turnId } = parseTurnContextReference(rest[0]);
3574
- authorizeTurnContext(store, taskId, turnId, options.environment);
3575
- const delta = store.transaction((tx) => (buildTurnContextDelta(tx, taskId, turnId, rest[2])));
3973
+ const { taskId, runId } = parseRunContextReference(rest[0]);
3974
+ authorizeRunContext(store, taskId, runId, options.environment);
3975
+ const delta = store.transaction((tx) => (buildRunContextDelta(tx, taskId, runId, rest[2])));
3576
3976
  return output(`${JSON.stringify(delta, null, 2)}\n`, { contextDelta: delta });
3577
3977
  }
3578
3978
  if (first === undefined || rest.length !== 0) {
3579
- throw usageError("Task turn context usage: yui task turn context <task>/<turn>.");
3979
+ throw usageError("Task turn context usage: yui task run context <task>/<run>.");
3580
3980
  }
3581
- const { taskId, turnId } = parseTurnContextReference(first);
3582
- authorizeTurnContext(store, taskId, turnId, options.environment);
3583
- const pack = store.transaction((tx) => buildTurnContextPack(tx, taskId, turnId));
3981
+ const { taskId, runId } = parseRunContextReference(first);
3982
+ authorizeRunContext(store, taskId, runId, options.environment);
3983
+ const pack = store.transaction((tx) => buildRunContextPack(tx, taskId, runId));
3584
3984
  return output(`${JSON.stringify(pack, null, 2)}\n`, { context: pack });
3585
3985
  }
3586
- function parseTurnContextReference(value) {
3587
- const [taskId, turnId, extra] = value.split("/");
3588
- if (taskId === undefined || taskId.length === 0 || turnId === undefined || turnId.length === 0
3986
+ function parseRunContextReference(value) {
3987
+ const [taskId, runId, extra] = value.split("/");
3988
+ if (taskId === undefined || taskId.length === 0 || runId === undefined || runId.length === 0
3589
3989
  || extra !== undefined) {
3590
- throw usageError(`Turn context reference is invalid: ${value}.`);
3990
+ throw usageError(`AgentRun context reference is invalid: ${value}.`);
3591
3991
  }
3592
- return { taskId, turnId };
3992
+ return { taskId, runId };
3593
3993
  }
3594
3994
  /**
3595
- * A Turn Context Pack is information the Role's own runtime reads in order to
3995
+ * A AgentRun Context Pack is information the Role's own runtime reads in order to
3596
3996
  * work. Authorization is therefore scope-shaped, not schedule-shaped: the
3597
- * caller must be the current runtime of the Task Role that owns the Turn,
3997
+ * caller must be the current runtime of the Task Role that owns the AgentRun,
3598
3998
  * proven by its per-Session caller key against durable state.
3599
3999
  *
3600
- * It deliberately does not require the Turn to still be the active one. An
3601
- * Agent whose Turn has advanced must still be able to read the context it was
4000
+ * It deliberately does not require the AgentRun to still be the active one. An
4001
+ * Agent whose AgentRun has advanced must still be able to read the context it was
3602
4002
  * given; losing read access to its own Role's history is what forces an
3603
4003
  * otherwise healthy Agent to stop and escalate to a human.
3604
4004
  */
3605
- function authorizeTurnContext(store, taskId, turnId, environment) {
4005
+ function authorizeRunContext(store, taskId, runId, environment) {
3606
4006
  const managed = environment?.YUI_SESSION_SCOPE !== undefined
3607
4007
  || environment?.YUI_TASK_ID !== undefined
3608
4008
  || environment?.YUI_ROLE !== undefined;
3609
4009
  if (!managed)
3610
4010
  return;
3611
- const run = store.getTurn(taskId, turnId);
4011
+ const run = store.getRun(taskId, runId);
3612
4012
  if (run === null) {
3613
- throw usageError(`Turn Context access is not authorized: ${taskId}/${turnId}.`);
4013
+ throw usageError(`AgentRun Context access is not authorized: ${taskId}/${runId}.`);
3614
4014
  }
3615
4015
  if (currentManagedRuntime(store, environment, taskId, run.roleName) === undefined) {
3616
- throw usageError(`Turn Context access requires the current runtime of ${taskId}/${run.roleName}.`);
4016
+ throw usageError(`AgentRun Context access requires the current runtime of ${taskId}/${run.roleName}.`);
3617
4017
  }
3618
4018
  }
3619
- function listTurns(args, store, options) {
3620
- const usage = "Task turn list usage: yui task turn list <task|task/work>.";
4019
+ function listRuns(args, store, options) {
4020
+ const usage = "Task turn list usage: yui task run list <task|task/work>.";
3621
4021
  exactPositionals(args, 1, usage);
3622
4022
  const reference = args[0];
3623
4023
  const task = store.getTask(reference);
3624
4024
  const item = task === null ? requireWorkItem(store, reference, options) : null;
3625
4025
  const taskId = task?.id ?? item.taskId;
3626
- const turns = store.listTurns(taskId).filter((turn) => (item === null || turn.workItemId === item.id));
3627
- if (turns.length === 0)
3628
- return "No Turns found.\n";
4026
+ const runs = store.listRuns(taskId).filter((run) => (item === null || run.workItemId === item.id));
4027
+ if (runs.length === 0)
4028
+ return "No AgentRuns found.\n";
3629
4029
  const events = store.listEvents(taskId);
3630
- return `${renderTable(`Turns: ${item?.id ?? taskId}`, [
3631
- { header: "Turn", minWidth: 6, maxWidth: 20 },
4030
+ return `${renderTable(`AgentRuns: ${item?.id ?? taskId}`, [
4031
+ { header: "AgentRun", minWidth: 6, maxWidth: 20 },
3632
4032
  { header: "Role", minWidth: 4, maxWidth: 22 },
3633
4033
  { header: "Subject", minWidth: 7, maxWidth: 24 },
3634
4034
  { header: "Purpose", minWidth: 6, maxWidth: 10 },
@@ -3639,7 +4039,7 @@ function listTurns(args, store, options) {
3639
4039
  { header: "Status", minWidth: 6, maxWidth: 12 },
3640
4040
  { header: "History", minWidth: 7, maxWidth: 9 },
3641
4041
  { header: "Summary", minWidth: 8, maxWidth: 58 }
3642
- ], turns.map((run) => [
4042
+ ], runs.map((run) => [
3643
4043
  run.id,
3644
4044
  run.roleName,
3645
4045
  run.workItemId ?? (run.reviewRoundId === undefined ? "task" : `review:${run.reviewRoundId}`),
@@ -3649,29 +4049,29 @@ function listTurns(args, store, options) {
3649
4049
  run.effective.profileAccess,
3650
4050
  run.effective.permission.strategy,
3651
4051
  run.status,
3652
- isTaskRecordRetired(events, "turn", run.id) ? "retired" : "active",
4052
+ isTaskRecordRetired(events, "run", run.id) ? "retired" : "active",
3653
4053
  run.result?.output ?? run.result?.diagnostic ?? "-"
3654
4054
  ]), defaultTableWidth())}\n`;
3655
4055
  }
3656
4056
  /**
3657
- * Settles only the known bootstrap split where a failed Task-final Turn
4057
+ * Settles only the known bootstrap split where a failed Task-final AgentRun
3658
4058
  * still owns a running ReviewRound, but the committed Task heads have moved
3659
4059
  * on. This is deliberately narrower than retry: it cannot manufacture a
3660
4060
  * review or fail an arbitrary Round, and every identity/mailbox fence is
3661
4061
  * checked before the old Round changes. The next normal Task completion then
3662
4062
  * creates one fresh Round over the newer frozen Task heads.
3663
4063
  */
3664
- function settleStaleFinalReviewTurn(args, store, options) {
3665
- exactPositionals(args, 1, "Task turn settle usage: yui task turn settle <task>/<turn>.");
4064
+ function settleStaleFinalReviewRun(args, store, options) {
4065
+ exactPositionals(args, 1, "Task turn settle usage: yui task run settle <task>/<run>.");
3666
4066
  const now = clock(options);
3667
- const previous = store.transaction((tx) => requireTurn(tx, args[0], options));
4067
+ const previous = store.transaction((tx) => requireRun(tx, args[0], options));
3668
4068
  const result = store.transaction((tx) => {
3669
- const run = tx.getTurn(previous.taskId, previous.id);
4069
+ const run = tx.getRun(previous.taskId, previous.id);
3670
4070
  if (run === null || run.status !== "failed" || run.purpose !== "review") {
3671
- throw usageError(`Turn ${previous.id} is not a failed review Turn.`);
4071
+ throw usageError(`AgentRun ${previous.id} is not a failed review AgentRun.`);
3672
4072
  }
3673
4073
  if (run.reviewRoundId === undefined) {
3674
- throw usageError(`Review Turn ${run.id} has no ReviewRound.`);
4074
+ throw usageError(`Review AgentRun ${run.id} has no ReviewRound.`);
3675
4075
  }
3676
4076
  const task = requireTask(tx, run.taskId);
3677
4077
  if (task.status !== "active")
@@ -3679,26 +4079,26 @@ function settleStaleFinalReviewTurn(args, store, options) {
3679
4079
  const actor = taskActor(tx, options, task.id);
3680
4080
  const round = tx.getReviewRound(task.id, run.reviewRoundId);
3681
4081
  if (round === null) {
3682
- throw dataError(`ReviewRound not found for Turn ${run.id}: ${run.reviewRoundId}.`);
4082
+ throw dataError(`ReviewRound not found for AgentRun ${run.id}: ${run.reviewRoundId}.`);
3683
4083
  }
3684
4084
  if ((round.scope ?? "work-item") !== "task") {
3685
- throw usageError(`Review Turn ${run.id} is not a Task-final review; request a new WorkItem review `
4085
+ throw usageError(`Review AgentRun ${run.id} is not a Task-final review; request a new WorkItem review `
3686
4086
  + "for a new Candidate.");
3687
4087
  }
3688
4088
  const taskFinalContract = taskFinalReviewContractForMutation(tx, task.id, options);
3689
4089
  if (!sameTaskFinalReviewContract(round.taskFinalReviewContract, taskFinalContract)) {
3690
4090
  throw usageError(`Task final-review contract does not match ReviewRound ${round.id}.`);
3691
4091
  }
3692
- // This read-only compare-and-swap fence covers the exact Turn/Round,
4092
+ // This read-only compare-and-swap fence covers the exact AgentRun/Round,
3693
4093
  // Candidate, stored Review workspace, frozen Project scope, and frozen
3694
4094
  // Project heads before any mailbox or Round write.
3695
- const validation = validateExactTurnReviewRound(tx, run, { allowTerminal: true });
4095
+ const validation = validateExactRunReviewRound(tx, run, { allowTerminal: true });
3696
4096
  if (validation.disposition !== "applied" || validation.round === null) {
3697
- throw usageError(`Review Turn ${run.id} identity does not match its ReviewRound or frozen Task state changed: ${validation.reason ?? "mismatch"}.`);
4097
+ throw usageError(`Review AgentRun ${run.id} identity does not match its ReviewRound or frozen Task state changed: ${validation.reason ?? "mismatch"}.`);
3698
4098
  }
3699
- const activeRoleTurn = tx.getActiveTurn(task.id, round.reviewerRoleName);
3700
- if (activeRoleTurn !== null) {
3701
- throw usageError(`${task.id}/${round.reviewerRoleName} already has active Turn ${activeRoleTurn.id}.`);
4099
+ const activeRoleRun = tx.getActiveRun(task.id, round.reviewerRoleName);
4100
+ if (activeRoleRun !== null) {
4101
+ throw usageError(`${task.id}/${round.reviewerRoleName} already has active AgentRun ${activeRoleRun.id}.`);
3702
4102
  }
3703
4103
  const taskRounds = reviewRoundsByIdentity(tx.listReviewRounds(task.id)
3704
4104
  .filter((entry) => (entry.scope ?? "work-item") === "task"));
@@ -3716,7 +4116,7 @@ function settleStaleFinalReviewTurn(args, store, options) {
3716
4116
  }
3717
4117
  if (round.status === "failed") {
3718
4118
  if (round.failure?.kind === "execution") {
3719
- return { turn: run, round, changed: false };
4119
+ return { run: run, round, changed: false };
3720
4120
  }
3721
4121
  throw usageError(`Final ReviewRound is already terminal: ${round.id}/${round.status}.`);
3722
4122
  }
@@ -3724,53 +4124,54 @@ function settleStaleFinalReviewTurn(args, store, options) {
3724
4124
  throw usageError(`Final ReviewRound is not stranded running: ${round.id}/${round.status}.`);
3725
4125
  }
3726
4126
  assertReviewerAvailable(tx, task.id, round.reviewerRoleName, round);
3727
- const summary = `Review Turn ${run.id} failed before delivery; committed Task heads changed.`;
4127
+ const summary = `Review AgentRun ${run.id} failed before delivery; committed Task heads changed.`;
3728
4128
  const terminal = finishReviewRound(round, "failed", now, { kind: "execution", message: summary });
3729
4129
  tx.saveReviewRound(task.id, terminal);
3730
- recordTaskEvent(tx, task.id, "turn.review-stale-settled", {
3731
- turnId: run.id,
4130
+ recordTaskEvent(tx, task.id, "run.review-stale-settled", {
4131
+ runId: run.id,
3732
4132
  reviewRoundId: round.id,
3733
4133
  previousTaskCandidate: JSON.stringify(round.taskCandidate),
3734
4134
  currentTaskCandidate: JSON.stringify(currentTaskCandidate),
3735
4135
  settledBy: actor,
3736
4136
  ...(actor === "leader" ? leaderActionEventPayload(tx, task.id, options) : {})
3737
4137
  }, now);
3738
- return { turn: run, round: terminal, changed: true };
4138
+ return { run: run, round: terminal, changed: true };
3739
4139
  });
3740
4140
  return output(result.changed
3741
- ? `Settled obsolete final Review ${result.round.id} from failed Turn ${result.turn.id}\n`
3742
- : `Obsolete final Review already settled: ${result.round.id}/${result.turn.id}\n`, { reviewRound: result.round, reviewTurn: result.turn });
4141
+ ? `Settled obsolete final Review ${result.round.id} from failed AgentRun ${result.run.id}\n`
4142
+ : `Obsolete final Review already settled: ${result.round.id}/${result.run.id}\n`, { reviewRound: result.round, reviewRun: result.run });
3743
4143
  }
3744
- function retryTurn(args, store, options) {
3745
- exactPositionals(args, 1, "Task turn retry usage: yui task turn retry <task>/<turn>.");
4144
+ function retryRun(args, store, options) {
4145
+ exactPositionals(args, 1, "Task turn retry usage: yui task run retry <task>/<run>.");
3746
4146
  const now = clock(options);
3747
- const previous = store.transaction((tx) => requireTurn(tx, args[0], options));
4147
+ const previous = store.transaction((tx) => requireRun(tx, args[0], options));
3748
4148
  if (previous.purpose === "review") {
3749
4149
  return retryFailedReviewRun(previous, store, options, now);
3750
4150
  }
3751
4151
  const retried = store.transaction((tx) => {
3752
4152
  if (previous.status !== "failed") {
3753
- throw usageError(`Turn ${previous.id} is not retryable from ${previous.status}.`);
4153
+ throw usageError(`AgentRun ${previous.id} is not retryable from ${previous.status}.`);
3754
4154
  }
3755
4155
  const task = requireTask(tx, previous.taskId);
3756
- if (task.status !== "active")
3757
- throw usageError(`Task is not active: ${task.id}.`);
3758
- assertTaskExecutionEnabled(task, "retrying a Turn");
4156
+ assertTaskExecutionEnabled(task, "retrying a AgentRun");
4157
+ if (!runPurposeAdmitsTaskState(previous.purpose, task)) {
4158
+ throw usageError(`Task does not admit ${previous.purpose} retry: ${task.id}/${task.status}.`);
4159
+ }
3759
4160
  const role = requireRole(tx, task.id, previous.roleName);
3760
- if (tx.getActiveTurn(task.id, role.name) !== null) {
3761
- throw usageError(`${task.id}/${role.name} already has an active turn.`);
4161
+ if (tx.getActiveRun(task.id, role.name) !== null) {
4162
+ throw usageError(`${task.id}/${role.name} already has an active run.`);
3762
4163
  }
3763
4164
  const sessions = tx.getTaskRoleSessionSet(task.id, role.name);
3764
4165
  const retryItem = previous.workItemId === undefined
3765
4166
  ? null
3766
4167
  : tx.getWorkItem(task.id, previous.workItemId);
3767
4168
  if (previous.workItemId !== undefined && retryItem === null) {
3768
- throw dataError(`Work item not found for Turn ${previous.id}: ${previous.workItemId}.`);
4169
+ throw dataError(`Work item not found for AgentRun ${previous.id}: ${previous.workItemId}.`);
3769
4170
  }
3770
4171
  const retriesSynthesisMain = previous.sourceExecutionGroupId !== undefined;
3771
4172
  if (retriesSynthesisMain
3772
4173
  && (previous.executionGroupId !== undefined || previous.executionLaneId !== undefined)) {
3773
- throw dataError(`Turn ${previous.id} has conflicting execution lineage.`);
4174
+ throw dataError(`AgentRun ${previous.id} has conflicting execution lineage.`);
3774
4175
  }
3775
4176
  const retryLaneBefore = previous.executionLaneId === undefined
3776
4177
  ? undefined
@@ -3788,57 +4189,54 @@ function retryTurn(args, store, options) {
3788
4189
  && retryLaneBefore !== undefined
3789
4190
  && retryLaneBefore.id === previous.executionLaneId
3790
4191
  && retryLaneBefore.disposition === "open"
3791
- && retryLaneBefore.currentTurnId === previous.id);
4192
+ && retryLaneBefore.currentRunId === previous.id);
3792
4193
  if (!exactCurrentLane) {
3793
- throw usageError(`Turn ${previous.id} no longer owns the current failed Execution Lane.`);
4194
+ throw usageError(`AgentRun ${previous.id} no longer owns the current failed Execution Lane.`);
3794
4195
  }
3795
4196
  const sourceGroup = !retriesSynthesisMain || retryItem === null
3796
4197
  ? undefined
3797
4198
  : workItemExecutionGroupById(retryItem, previous.sourceExecutionGroupId);
3798
- const sourceMainTurns = retriesSynthesisMain
3799
- ? chronologicalTurns(tx.listTurns(task.id).filter((turn) => (turn.purpose === "execution"
3800
- && turn.workItemId === previous.workItemId
3801
- && turn.sourceExecutionGroupId === previous.sourceExecutionGroupId)))
4199
+ const sourceMainRuns = retriesSynthesisMain
4200
+ ? chronologicalRuns(tx.listRuns(task.id).filter((run) => (run.purpose === "execution"
4201
+ && run.workItemId === previous.workItemId
4202
+ && run.sourceExecutionGroupId === previous.sourceExecutionGroupId)))
3802
4203
  : [];
3803
4204
  const exactSourceMain = !retriesSynthesisMain || (retryItem !== null
3804
- && retryItem.status === "running"
4205
+ && retryItem.status === "open"
3805
4206
  && retryItem.assignee === previous.roleName
3806
4207
  && sourceGroup !== undefined
3807
4208
  && currentRetryGroup?.id === sourceGroup.id
3808
- && workItemExecutionGroupSettled(sourceGroup)
3809
- && successfulWorkItemSynthesisProducers(tx, retryItem, sourceGroup).length
3810
- >= MINIMUM_WORK_ITEM_SYNTHESIS_RESULTS
3811
- && sourceMainTurns.at(-1)?.id === previous.id);
4209
+ && selectedWorkItemSynthesisProducers(tx, retryItem, sourceGroup, synthesisSourceRunIds(tx, previous)).length > 0
4210
+ && sourceMainRuns.at(-1)?.id === previous.id);
3812
4211
  if (!exactSourceMain) {
3813
- throw usageError(`Turn ${previous.id} no longer owns the current WorkItem main synthesis.`);
4212
+ throw usageError(`AgentRun ${previous.id} no longer owns the current WorkItem main synthesis.`);
3814
4213
  }
3815
- const directMainTurns = retryItem === null
4214
+ const directMainRuns = retryItem === null
3816
4215
  ? []
3817
- : chronologicalTurns(tx.listTurns(task.id).filter((turn) => (turn.purpose === "execution"
3818
- && turn.workItemId === retryItem.id
3819
- && turn.executionGroupId === undefined
3820
- && turn.executionLaneId === undefined
3821
- && turn.sourceExecutionGroupId === undefined)));
4216
+ : chronologicalRuns(tx.listRuns(task.id).filter((run) => (run.purpose === "execution"
4217
+ && run.workItemId === retryItem.id
4218
+ && run.executionGroupId === undefined
4219
+ && run.executionLaneId === undefined
4220
+ && run.sourceExecutionGroupId === undefined)));
3822
4221
  const retriesDirectMain = retryItem !== null
3823
4222
  && !retriesExecutionLane
3824
4223
  && !retriesSynthesisMain;
3825
- const exactDirectMain = !retriesDirectMain || ((retryItem.status === "running" || retryItem.status === "failed")
4224
+ const exactDirectMain = !retriesDirectMain || (retryItem.status === "open"
3826
4225
  && retryItem.assignee === previous.roleName
3827
- && directMainTurns.at(-1)?.id === previous.id);
4226
+ && directMainRuns.at(-1)?.id === previous.id);
3828
4227
  if (!exactDirectMain) {
3829
- throw usageError(`Turn ${previous.id} no longer owns the current direct WorkItem execution.`);
4228
+ throw usageError(`AgentRun ${previous.id} no longer owns the current direct WorkItem execution.`);
3830
4229
  }
3831
- const groupedRunningRetry = retryItem?.status === "running"
4230
+ const groupedRunningRetry = retryItem?.status === "open"
3832
4231
  && retriesExecutionLane
3833
4232
  && exactCurrentLane;
3834
- const synthesisRunningRetry = retryItem?.status === "running"
4233
+ const synthesisRunningRetry = retryItem?.status === "open"
3835
4234
  && retriesSynthesisMain
3836
4235
  && exactSourceMain;
3837
- const directRunningRetry = retryItem?.status === "running"
4236
+ const directRunningRetry = retryItem?.status === "open"
3838
4237
  && retriesDirectMain
3839
4238
  && exactDirectMain;
3840
4239
  if (retryItem !== null
3841
- && retryItem.status !== "failed"
3842
4240
  && !groupedRunningRetry
3843
4241
  && !synthesisRunningRetry
3844
4242
  && !directRunningRetry) {
@@ -3865,7 +4263,7 @@ function retryTurn(args, store, options) {
3865
4263
  && (retryGroup === undefined
3866
4264
  || retryGroup.id !== previous.executionGroupId
3867
4265
  || retryLane === undefined))) {
3868
- throw dataError(`Turn ${previous.id} execution lineage no longer matches its Work Item.`);
4266
+ throw dataError(`AgentRun ${previous.id} execution lineage no longer matches its Work Item.`);
3869
4267
  }
3870
4268
  const retryManagedWorkspace = retryLane === undefined ? runWorkspace : previous.workspace;
3871
4269
  if (retryLane !== undefined) {
@@ -3885,7 +4283,7 @@ function retryTurn(args, store, options) {
3885
4283
  || !isDeepStrictEqual(writableProjectIds, [...retryLane.workspace.writableProjectIds].sort())
3886
4284
  || storedLaneWorkspace === null
3887
4285
  || !isDeepStrictEqual(storedLaneWorkspace, previous.workspace)) {
3888
- throw dataError(`Turn ${previous.id} Lane workspace is missing or has drifted.`);
4286
+ throw dataError(`AgentRun ${previous.id} Lane workspace is missing or has drifted.`);
3889
4287
  }
3890
4288
  }
3891
4289
  if (retriesSynthesisMain) {
@@ -3902,7 +4300,7 @@ function retryTurn(args, store, options) {
3902
4300
  || currentMainWorkspace === null
3903
4301
  || !isDeepStrictEqual(storedMainWorkspace, previous.workspace)
3904
4302
  || !isDeepStrictEqual(currentMainWorkspace, previous.workspace)) {
3905
- throw dataError(`Turn ${previous.id} WorkItem main workspace is missing or has drifted.`);
4303
+ throw dataError(`AgentRun ${previous.id} WorkItem main workspace is missing or has drifted.`);
3906
4304
  }
3907
4305
  }
3908
4306
  if (retriesDirectMain) {
@@ -3919,20 +4317,20 @@ function retryTurn(args, store, options) {
3919
4317
  || currentDirectWorkspace === null
3920
4318
  || !isDeepStrictEqual(storedDirectWorkspace, previous.workspace)
3921
4319
  || !isDeepStrictEqual(currentDirectWorkspace, previous.workspace)) {
3922
- throw dataError(`Turn ${previous.id} direct WorkItem workspace is missing or has drifted.`);
4320
+ throw dataError(`AgentRun ${previous.id} direct WorkItem workspace is missing or has drifted.`);
3923
4321
  }
3924
4322
  }
3925
4323
  const effective = retryLane?.effective ?? resolveEffectiveLaunch({
3926
4324
  role,
3927
- purpose: "execution",
4325
+ purpose: previous.purpose,
3928
4326
  ...(retryManagedWorkspace === undefined ? {} : { workspace: retryManagedWorkspace }),
3929
4327
  ...(retryItem === null ? {} : { workItemWriteProjectIds: retryItem.writeProjectIds })
3930
4328
  });
3931
- const turnId = tx.nextTurnId(task.id);
4329
+ const runId = tx.nextRunId(task.id);
3932
4330
  const runningGroup = retryGroup === undefined || retryLane === undefined
3933
4331
  ? undefined
3934
4332
  : updateWorkItemExecutionLane(retryGroup, retryLane.id, {
3935
- currentTurnId: turnId
4333
+ currentRunId: runId
3936
4334
  }, now);
3937
4335
  // Restart the bound lane and reopen the failed WorkItem as two ordered
3938
4336
  // single-step record revisions, matching the dispatch path. Folding both
@@ -3941,11 +4339,7 @@ function retryTurn(args, store, options) {
3941
4339
  const laneRestartedItem = retryItem === null || runningGroup === undefined
3942
4340
  ? retryItem
3943
4341
  : updateWorkItemExecutionGroup(retryItem, runningGroup, now);
3944
- const retriedItemWithGroup = laneRestartedItem === null
3945
- ? null
3946
- : laneRestartedItem.status === "failed"
3947
- ? retryFailedWorkItem(laneRestartedItem, now)
3948
- : laneRestartedItem;
4342
+ const retriedItemWithGroup = laneRestartedItem;
3949
4343
  if (retriedItemWithGroup !== null) {
3950
4344
  if (laneRestartedItem !== null && laneRestartedItem !== retryItem) {
3951
4345
  tx.saveWorkItem(task.id, laneRestartedItem);
@@ -3957,13 +4351,13 @@ function retryTurn(args, store, options) {
3957
4351
  const input = retriesSynthesisMain
3958
4352
  ? previous.inputs[0].input
3959
4353
  : (() => {
3960
- const retrySnapshot = freezeTurnContextSnapshot(tx, {
4354
+ const retrySnapshot = freezeRunContextSnapshot(tx, {
3961
4355
  taskId: task.id,
3962
4356
  roleName: role.name,
3963
- purpose: "execution",
4357
+ purpose: previous.purpose,
3964
4358
  ...(previous.workItemId === undefined ? {} : { workItemId: previous.workItemId })
3965
4359
  }, now, "controller", retryGroup?.assignment.contextSnapshotRef);
3966
- return createTurnInput({
4360
+ return createRunInput({
3967
4361
  source: {
3968
4362
  type: "yui",
3969
4363
  channel: previous.workItemId === undefined ? "task-dispatch" : "workitem-dispatch"
@@ -3975,7 +4369,8 @@ function retryTurn(args, store, options) {
3975
4369
  deltaRefIds: contextSnapshotDeltaRefIds(tx, retrySnapshot)
3976
4370
  });
3977
4371
  })();
3978
- const created = createTurn(turnId, task.id, role.name, roleAgentSessionResumeMode(sessions, effective.agentId, effective), input, now, {
4372
+ const created = createRun(runId, task.id, role.name, roleAgentSessionResumeMode(sessions, effective.agentId, effective), input, now, {
4373
+ purpose: previous.purpose,
3979
4374
  ...(previous.workItemId === undefined ? {} : { workItemId: previous.workItemId }),
3980
4375
  ...(runningGroup === undefined ? {} : {
3981
4376
  executionGroupId: runningGroup.id,
@@ -3987,8 +4382,8 @@ function retryTurn(args, store, options) {
3987
4382
  ...(retryManagedWorkspace === undefined ? {} : { workspace: retryManagedWorkspace }),
3988
4383
  effective
3989
4384
  });
3990
- tx.saveTurn(created);
3991
- tx.saveActiveTurn(created);
4385
+ tx.saveRun(created);
4386
+ tx.saveActiveRun(created);
3992
4387
  if (previous.workItemId !== undefined && retriedItemWithGroup !== null) {
3993
4388
  const item = retriedItemWithGroup;
3994
4389
  const workspace = tx.getWorkItemWorkspace(task.id, item.id);
@@ -3998,21 +4393,21 @@ function retryTurn(args, store, options) {
3998
4393
  + `cannot retry ${item.id}.`);
3999
4394
  }
4000
4395
  }
4001
- enqueueRoleTurnDispatch(tx, {
4396
+ enqueueRoleRunDispatch(tx, {
4002
4397
  taskId: task.id,
4003
4398
  roleName: role.name,
4004
- turnId: created.id,
4399
+ runId: created.id,
4005
4400
  reason: "turn-retried",
4006
4401
  occurredAt: now
4007
4402
  });
4008
- recordTaskEvent(tx, task.id, "turn.retried", {
4009
- ...turnLaunchEventPayload(created),
4010
- previousTurnId: previous.id
4403
+ recordTaskEvent(tx, task.id, "run.retried", {
4404
+ ...runLaunchEventPayload(created),
4405
+ previousRunId: previous.id
4011
4406
  }, now);
4012
- return { kind: "turn", turn: created };
4407
+ return { kind: "run", run: created };
4013
4408
  });
4014
- notifyMailbox(options.runtime, roleMailbox(retried.turn.taskId, retried.turn.roleName), retried.turn.taskId);
4015
- return output(`Retry queued as ${retried.turn.id} for ${retried.turn.taskId}/${retried.turn.roleName}\n`);
4409
+ notifyMailbox(options.runtime, roleMailbox(retried.run.taskId, retried.run.roleName), retried.run.taskId);
4410
+ return output(`Retry queued as ${retried.run.id} for ${retried.run.taskId}/${retried.run.roleName}\n`);
4016
4411
  }
4017
4412
  function actualTaskReviewCandidateForMutation(store, task, options) {
4018
4413
  if (options.actualTaskReviewCandidate === undefined) {
@@ -4055,7 +4450,7 @@ function taskReviewProducerCollision(provenance, reviewerRoleName) {
4055
4450
  *
4056
4451
  * When `expected` is supplied this is also the final dispatch compare-and-swap
4057
4452
  * fence: every bound Project must still point at the exact frozen physical
4058
- * head. Drift fails closed before a Reviewer Turn is created.
4453
+ * head. Drift fails closed before a Reviewer AgentRun is created.
4059
4454
  */
4060
4455
  function taskReviewProvenance(store, task, options, expected) {
4061
4456
  const candidate = actualTaskReviewCandidateForMutation(store, task, options);
@@ -4110,13 +4505,13 @@ function taskReviewProvenance(store, task, options, expected) {
4110
4505
  recordProducer(LEADER_ROLE, item.id);
4111
4506
  continue;
4112
4507
  }
4113
- const sourceRun = store.getTurn(task.id, sourceCandidate.source.turnId);
4508
+ const sourceRun = store.getRun(task.id, sourceCandidate.source.runId);
4114
4509
  if (sourceRun === null
4115
4510
  || sourceRun.workItemId !== item.id
4116
4511
  || sourceRun.purpose !== "execution"
4117
4512
  || sourceRun.status !== "completed") {
4118
- throw dataError(`Committed producer Candidate Turn is unavailable: `
4119
- + `${item.id}/${sourceCandidate.source.turnId}.`);
4513
+ throw dataError(`Committed producer Candidate AgentRun is unavailable: `
4514
+ + `${item.id}/${sourceCandidate.source.runId}.`);
4120
4515
  }
4121
4516
  recordProducer(sourceRun.roleName, item.id);
4122
4517
  if (sourceRun.sourceExecutionGroupId !== undefined) {
@@ -4125,7 +4520,7 @@ function taskReviewProvenance(store, task, options, expected) {
4125
4520
  throw dataError(`Committed producer ExecutionGroup is unavailable: `
4126
4521
  + `${item.id}/${sourceRun.sourceExecutionGroupId}.`);
4127
4522
  }
4128
- for (const producer of successfulWorkItemSynthesisProducers(store, item, executionGroup)) {
4523
+ for (const producer of selectedWorkItemSynthesisProducers(store, item, executionGroup, synthesisSourceRunIds(store, sourceRun))) {
4129
4524
  recordProducer(producer.roleName, item.id);
4130
4525
  }
4131
4526
  }
@@ -4174,7 +4569,7 @@ function queueTaskReviewRound(store, task, config, taskCandidate, options, now,
4174
4569
  const availability = projectReviewerAvailability(store, task.id, config.roleName);
4175
4570
  if (availability.kind === "busy") {
4176
4571
  throw usageError(`Reviewer ${config.roleName} is busy (${availability.phase}`
4177
- + `${availability.activeTurnId === undefined ? "" : `; Turn ${availability.activeTurnId}`}); `
4572
+ + `${availability.activeRunId === undefined ? "" : `; AgentRun ${availability.activeRunId}`}); `
4178
4573
  + `retry after ${availability.retryAfterSeconds}s.`);
4179
4574
  }
4180
4575
  let reviewer = store.getRole(task.id, config.roleName);
@@ -4243,16 +4638,16 @@ function resumablePendingFinalTaskReview(store, task, round, config, taskCandida
4243
4638
  || round.reviewerRoleName !== taskFinalContract.reviewerRoleName) {
4244
4639
  throw usageError(`Pending final ReviewRound Reviewer identity changed: ${round.id}.`);
4245
4640
  }
4246
- if (round.reviewerTurnId !== undefined) {
4247
- throw usageError(`Pending final ReviewRound already records Reviewer Turn ${round.reviewerTurnId}: ${round.id}.`);
4641
+ if (round.reviewerRunId !== undefined) {
4642
+ throw usageError(`Pending final ReviewRound already records Reviewer AgentRun ${round.reviewerRunId}: ${round.id}.`);
4248
4643
  }
4249
4644
  assertNoConflictingTaskReviewRound(store.listReviewRounds(task.id), round.id, round.reviewerRoleName);
4250
4645
  const reviewer = store.getRole(task.id, round.reviewerRoleName);
4251
4646
  if (reviewer === null || reviewer.name !== round.reviewerRoleName) {
4252
4647
  throw usageError(`Pending final ReviewRound Reviewer identity changed: ${round.id}.`);
4253
4648
  }
4254
- if (store.getActiveTurn(task.id, reviewer.name) !== null) {
4255
- throw usageError(`Reviewer Role already has an active Turn: ${reviewer.name}.`);
4649
+ if (store.getActiveRun(task.id, reviewer.name) !== null) {
4650
+ throw usageError(`Reviewer Role already has an active AgentRun: ${reviewer.name}.`);
4256
4651
  }
4257
4652
  assertPendingFinalReviewWorkspaceEvidence(store, task, round);
4258
4653
  return round;
@@ -4288,7 +4683,7 @@ function assertPendingFinalReviewWorkspaceEvidence(store, task, round) {
4288
4683
  }
4289
4684
  }
4290
4685
  /**
4291
- * Task-control retry of an exact failed review Turn. The old failed Turn
4686
+ * Task-control retry of an exact failed review AgentRun. The old failed AgentRun
4292
4687
  * remains the attempt trail, while the ReviewRound is reset to pending under
4293
4688
  * its existing identity. Every identity and frozen-head fence is
4294
4689
  * checked inside one transaction so a partial fail-old-without-reset state can
@@ -4296,12 +4691,12 @@ function assertPendingFinalReviewWorkspaceEvidence(store, task, round) {
4296
4691
  */
4297
4692
  function retryFailedReviewRun(previous, store, options, now) {
4298
4693
  const result = store.transaction((tx) => {
4299
- const run = tx.getTurn(previous.taskId, previous.id);
4694
+ const run = tx.getRun(previous.taskId, previous.id);
4300
4695
  if (run === null || run.status !== "failed" || run.purpose !== "review") {
4301
- throw usageError(`Turn ${previous.id} is not a failed review Turn.`);
4696
+ throw usageError(`AgentRun ${previous.id} is not a failed review AgentRun.`);
4302
4697
  }
4303
4698
  if (run.reviewRoundId === undefined) {
4304
- throw usageError(`Review Turn ${run.id} has no ReviewRound.`);
4699
+ throw usageError(`Review AgentRun ${run.id} has no ReviewRound.`);
4305
4700
  }
4306
4701
  const task = requireTask(tx, run.taskId);
4307
4702
  if (task.status !== "active")
@@ -4317,16 +4712,16 @@ function retryFailedReviewRun(previous, store, options, now) {
4317
4712
  : round.executionGroup?.lanes.find(({ id }) => id === run.executionLaneId);
4318
4713
  if (run.executionGroupId !== undefined && (round.executionGroup?.id !== run.executionGroupId
4319
4714
  || retryLane === undefined
4320
- || retryLane.currentTurnId !== run.id
4715
+ || retryLane.currentRunId !== run.id
4321
4716
  || retryLane.roleName !== run.roleName)) {
4322
- throw usageError(`Review Turn ${run.id} no longer owns its exact Review Lane attempt.`);
4717
+ throw usageError(`Review AgentRun ${run.id} no longer owns its exact Review Lane attempt.`);
4323
4718
  }
4324
4719
  const panelGroup = round.executionGroup !== undefined;
4325
4720
  const runningPanelLaneRetry = round.status === "running"
4326
4721
  && panelGroup
4327
4722
  && retryLane?.disposition === "open";
4328
4723
  if (round.status === "running" && panelGroup && !runningPanelLaneRetry) {
4329
- throw usageError(`Review Turn ${run.id} is not the current failed Lane attempt in running Round ${round.id}.`);
4724
+ throw usageError(`Review AgentRun ${run.id} is not the current failed Lane attempt in running Round ${round.id}.`);
4330
4725
  }
4331
4726
  const retryReviewerRoleName = retryLane?.roleName ?? round.reviewerRoleName;
4332
4727
  if (round.status !== "failed"
@@ -4362,10 +4757,10 @@ function retryFailedReviewRun(previous, store, options, now) {
4362
4757
  throw dataError(`WorkItem ReviewRound has no Candidate anchor: ${round.id}.`);
4363
4758
  }
4364
4759
  if (run.workItemId !== round.workItemId) {
4365
- throw usageError(`Review Turn ${run.id} does not match WorkItem ${round.workItemId}.`);
4760
+ throw usageError(`Review AgentRun ${run.id} does not match WorkItem ${round.workItemId}.`);
4366
4761
  }
4367
4762
  const item = tx.getWorkItem(task.id, round.workItemId);
4368
- if (item === null || item.status !== "awaiting_acceptance") {
4763
+ if (item === null || (item.status !== "open" || item.candidates.length === 0)) {
4369
4764
  throw usageError(`WorkItem ReviewRound ${round.id} no longer has an awaiting Candidate.`);
4370
4765
  }
4371
4766
  const candidate = currentWorkItemCandidate(item);
@@ -4404,20 +4799,20 @@ function retryFailedReviewRun(previous, store, options, now) {
4404
4799
  throw usageError(`Reviewer already has an active review round for this candidate: ${activeRound.id}.`);
4405
4800
  }
4406
4801
  const reviewer = requireRole(tx, task.id, retryReviewerRoleName);
4407
- const activePointer = tx.getActiveTurn(task.id, reviewer.name);
4802
+ const activePointer = tx.getActiveRun(task.id, reviewer.name);
4408
4803
  // Issue 06: a completed same-Round retry is a no-write idempotent result.
4409
4804
  if (round.status === "completed") {
4410
4805
  assertReviewerAvailable(tx, task.id, reviewer.name);
4411
4806
  return { round, previousRun: run, created: false };
4412
4807
  }
4413
4808
  // Issue 06: a running same Round is reusable only with its exact active
4414
- // Turn. A stranded Turn (no active pointer) falls through and resets the
4809
+ // AgentRun. A stranded AgentRun (no active pointer) falls through and resets the
4415
4810
  // Round after the identity fences below.
4416
4811
  if (round.status === "running" && !runningPanelLaneRetry) {
4417
- const reviewerTurnId = round.reviewerTurnId;
4418
- const activeMatches = reviewerTurnId !== undefined
4812
+ const reviewerRunId = round.reviewerRunId;
4813
+ const activeMatches = reviewerRunId !== undefined
4419
4814
  && activePointer !== null
4420
- && activePointer.id === reviewerTurnId
4815
+ && activePointer.id === reviewerRunId
4421
4816
  && activePointer.status === "active";
4422
4817
  if (activePointer !== null && !activeMatches) {
4423
4818
  throw usageError(`Existing running ReviewRound ${round.id} lacks its exact active Reviewer execution.`);
@@ -4430,24 +4825,65 @@ function retryFailedReviewRun(previous, store, options, now) {
4430
4825
  // Issue 06: an already-pending Round is the idempotent retry result.
4431
4826
  if (round.status === "pending") {
4432
4827
  if (activePointer !== null) {
4433
- throw usageError(`Reviewer Role already has an active Turn: ${reviewer.name}.`);
4828
+ throw usageError(`Reviewer Role already has an active AgentRun: ${reviewer.name}.`);
4434
4829
  }
4435
4830
  assertReviewerAvailable(tx, task.id, reviewer.name, round);
4436
4831
  return { round, previousRun: run, created: false };
4437
4832
  }
4438
4833
  if (activePointer !== null) {
4439
- throw usageError(`Reviewer Role already has an active Turn: ${reviewer.name}.`);
4834
+ throw usageError(`Reviewer Role already has an active AgentRun: ${reviewer.name}.`);
4440
4835
  }
4441
4836
  assertReviewerAvailable(tx, task.id, reviewer.name, round);
4442
- const validation = validateExactTurnReviewRound(tx, run, { allowTerminal: true });
4837
+ const validation = validateExactRunReviewRound(tx, run, { allowTerminal: true });
4443
4838
  if (validation.disposition !== "applied" || validation.round === null) {
4444
- throw usageError(`Review Turn ${run.id} identity does not match its ReviewRound or frozen Task state changed: ${validation.reason ?? "mismatch"}.`);
4839
+ throw usageError(`Review AgentRun ${run.id} identity does not match its ReviewRound or frozen Task state changed: ${validation.reason ?? "mismatch"}.`);
4840
+ }
4841
+ if (run.sourceExecutionGroupId !== undefined) {
4842
+ assertTaskExecutionEnabled(task, "retrying Review synthesis");
4843
+ const group = round.executionGroup;
4844
+ if (round.status !== "failed" || round.reviewerRunId !== run.id
4845
+ || group?.id !== run.sourceExecutionGroupId
4846
+ || run.workspace === undefined) {
4847
+ throw usageError(`Review AgentRun ${run.id} no longer owns the current main synthesis.`);
4848
+ }
4849
+ selectedReviewSynthesisProducers(tx, round, group, synthesisSourceRunIds(tx, run));
4850
+ const effective = resolveEffectiveLaunch({
4851
+ role: reviewer,
4852
+ purpose: "review",
4853
+ workspace: run.workspace,
4854
+ reviewRoundId: round.id,
4855
+ reviewBaseCommit: round.reviewBaseCommit
4856
+ });
4857
+ const created = createRun(tx.nextRunId(task.id), task.id, reviewer.name, roleAgentSessionResumeMode(tx.getTaskRoleSessionSet(task.id, reviewer.name), effective.agentId, effective), run.inputs[0].input, now, {
4858
+ purpose: "review",
4859
+ ...(run.workItemId === undefined ? {} : { workItemId: run.workItemId }),
4860
+ reviewRoundId: round.id,
4861
+ sourceExecutionGroupId: group.id,
4862
+ workspace: run.workspace,
4863
+ effective
4864
+ });
4865
+ const restarted = startReviewRound(retryReviewRound(round, requestedBy, now), created.id);
4866
+ tx.saveReviewRound(task.id, restarted);
4867
+ tx.saveRun(created);
4868
+ tx.saveActiveRun(created);
4869
+ enqueueRoleRunDispatch(tx, {
4870
+ taskId: task.id,
4871
+ roleName: reviewer.name,
4872
+ runId: created.id,
4873
+ reason: "turn-retried",
4874
+ occurredAt: now
4875
+ });
4876
+ recordTaskEvent(tx, task.id, "run.review-retried", {
4877
+ ...runLaunchEventPayload(created),
4878
+ previousRunId: run.id
4879
+ }, now);
4880
+ return { round: restarted, previousRun: run, created: true, run: created };
4445
4881
  }
4446
4882
  if (runningPanelLaneRetry) {
4447
4883
  const resetRound = retryRunningReviewExecutionLane(round, retryLane.id, run.id);
4448
4884
  tx.saveReviewRound(task.id, resetRound);
4449
- recordTaskEvent(tx, task.id, "turn.review-retried", {
4450
- turnId: run.id,
4885
+ recordTaskEvent(tx, task.id, "run.review-retried", {
4886
+ runId: run.id,
4451
4887
  reviewRoundId: round.id,
4452
4888
  executionLaneId: retryLane.id
4453
4889
  }, now);
@@ -4457,7 +4893,7 @@ function retryFailedReviewRun(previous, store, options, now) {
4457
4893
  // fence has passed. The outer transaction rolls back if Round creation fails.
4458
4894
  let roundToReset = round;
4459
4895
  if (round.status !== "failed") {
4460
- const summary = `Review Turn ${run.id} failed before delivery.`;
4896
+ const summary = `Review AgentRun ${run.id} failed before delivery.`;
4461
4897
  roundToReset = finishReviewRound(round, "failed", now, { kind: "execution", message: summary });
4462
4898
  tx.saveReviewRound(task.id, roundToReset);
4463
4899
  }
@@ -4466,41 +4902,44 @@ function retryFailedReviewRun(previous, store, options, now) {
4466
4902
  // stable across execution-attempt failures.
4467
4903
  const resetRound = retryReviewRound(roundToReset, requestedBy, now);
4468
4904
  tx.saveReviewRound(task.id, resetRound);
4469
- recordTaskEvent(tx, task.id, "turn.review-retried", {
4470
- turnId: run.id,
4905
+ recordTaskEvent(tx, task.id, "run.review-retried", {
4906
+ runId: run.id,
4471
4907
  reviewRoundId: round.id
4472
4908
  }, now);
4473
4909
  return { round: resetRound, previousRun: run, created: true };
4474
4910
  });
4911
+ if ("run" in result && result.run !== undefined) {
4912
+ notifyMailbox(options.runtime, roleMailbox(result.run.taskId, result.run.roleName), result.run.taskId);
4913
+ }
4475
4914
  return output(result.created
4476
4915
  ? `Review retry requested as ${result.round.id}\n`
4477
- : `Review retry already requested as ${result.round.id} (${result.round.status})\n`, { reviewRound: result.round });
4916
+ : `Review retry already requested as ${result.round.id} (${result.round.status})\n`, { reviewRound: result.round, ...("run" in result ? { run: result.run } : {}) });
4478
4917
  }
4479
- /** Turn details are retained audit evidence; continuation uses durable Task state. */
4480
- function showTurn(args, store, options) {
4481
- const usage = "Task turn show usage: yui task turn show <task>/<turn> [--json].";
4918
+ /** AgentRun details are retained audit evidence; continuation uses durable Task state. */
4919
+ function showRun(args, store, options) {
4920
+ const usage = "Task turn show usage: yui task run show <task>/<run> [--json].";
4482
4921
  const asJson = args.includes("--json");
4483
4922
  const positionals = args.filter((arg) => arg !== "--json");
4484
4923
  exactPositionals(positionals, 1, usage);
4485
4924
  const data = store.transaction((tx) => {
4486
- const run = requireTurn(tx, positionals[0], options);
4925
+ const run = requireRun(tx, positionals[0], options);
4487
4926
  const retirement = tx.listEvents(run.taskId)
4488
4927
  .map(taskRecordRetirement)
4489
- .find((entry) => entry?.recordKind === "turn" && entry.recordId === run.id) ?? null;
4490
- return { turn: run, retirement };
4928
+ .find((entry) => entry?.recordKind === "run" && entry.recordId === run.id) ?? null;
4929
+ return { run: run, retirement, execution: runExecutionObservation(run, tx.getTaskRoleSessionSet(run.taskId, run.roleName)?.providerBinding, tx.listEvents(run.taskId)) };
4491
4930
  });
4492
4931
  if (asJson) {
4493
4932
  return { kind: "output", output: `${JSON.stringify(data, null, 2)}\n`, data };
4494
4933
  }
4495
4934
  return {
4496
4935
  kind: "output",
4497
- output: renderTurnShow(data.turn, data.retirement),
4936
+ output: `Delivery observation: ${data.execution.delivery}\n` + renderRunShow(data.run, data.retirement),
4498
4937
  data
4499
4938
  };
4500
4939
  }
4501
- function renderTurnShow(run, retirement) {
4940
+ function renderRunShow(run, retirement) {
4502
4941
  const lines = [
4503
- `Turn: ${run.id}`,
4942
+ `AgentRun: ${run.id}`,
4504
4943
  `Task: ${run.taskId}`,
4505
4944
  `Role: ${run.roleName}`,
4506
4945
  `Purpose: ${run.purpose}`,
@@ -4523,40 +4962,40 @@ function renderTurnShow(run, retirement) {
4523
4962
  return `${lines.join("\n")}\n`;
4524
4963
  }
4525
4964
  /**
4526
- * Records a structured progress checkpoint for an active Turn. This is a durable
4527
- * Turn fact, not a Task Message: it advances the Turn's durable-progress clock so
4528
- * a healthy but long-running Turn keeps proving it is alive without adding
4529
- * collaboration-narrative noise. It never completes, mutates the Turn, or wakes the
4965
+ * Records a structured progress checkpoint for an active AgentRun. This is a durable
4966
+ * AgentRun fact, not a Task Message: it advances the AgentRun's durable-progress clock so
4967
+ * a healthy but long-running AgentRun keeps proving it is alive without adding
4968
+ * collaboration-narrative noise. It never completes, mutates the AgentRun, or wakes the
4530
4969
  * Leader.
4531
4970
  */
4532
- function checkpointTurn(args, store, options) {
4533
- const usage = "Task turn checkpoint usage: yui task turn checkpoint <turn> (--note <text>|--note-file <path|->).";
4971
+ function checkpointRun(args, store, options) {
4972
+ const usage = "Task turn checkpoint usage: yui task run checkpoint <run> (--note <text>|--note-file <path|->).";
4534
4973
  const parsed = parseTail(args, new Set(["--note", "--note-file"]), usage);
4535
4974
  exactPositionals(parsed.positionals, 1, usage);
4536
4975
  const note = readCommandText(parsed.options.get("--note"), parsed.options.get("--note-file"), "--note", usage);
4537
4976
  const now = clock(options);
4538
4977
  const event = store.transaction((tx) => {
4539
- const run = requireTurn(tx, parsed.positionals[0], options);
4978
+ const run = requireRun(tx, parsed.positionals[0], options);
4540
4979
  if (run.status !== "active") {
4541
- throw usageError(`Turn ${run.id} is already terminal: ${run.status}.`);
4980
+ throw usageError(`AgentRun ${run.id} is already terminal: ${run.status}.`);
4542
4981
  }
4543
4982
  const task = requireTask(tx, run.taskId);
4544
4983
  if (task.status !== "active")
4545
- throw usageError(inactiveTaskMessage(task, "checkpointing a Turn"));
4546
- const pointer = activeTurnPointer(tx, run);
4984
+ throw usageError(inactiveTaskMessage(task, "checkpointing a AgentRun"));
4985
+ const pointer = activeRunPointer(tx, run);
4547
4986
  if (pointer?.id !== run.id) {
4548
- throw usageError(`Turn is not active for ${task.id}/${run.roleName}: ${run.id}.`);
4987
+ throw usageError(`AgentRun is not active for ${task.id}/${run.roleName}: ${run.id}.`);
4549
4988
  }
4550
4989
  const events = tx.listEvents(task.id);
4551
- const recovered = isRoleTurnStalled(events, run.id);
4552
- const progress = recordTaskEventRecord(tx, task.id, TURN_PROGRESS_EVENT, {
4553
- turnId: run.id,
4990
+ const recovered = isRoleRunStalled(events, run.id);
4991
+ const progress = recordTaskEventRecord(tx, task.id, RUN_PROGRESS_EVENT, {
4992
+ runId: run.id,
4554
4993
  note: truncateEventNote(note),
4555
4994
  ...(run.workItemId === undefined ? {} : { workItemId: run.workItemId })
4556
4995
  }, now);
4557
4996
  if (recovered) {
4558
- recordTaskEventRecord(tx, task.id, TURN_RECOVERED_EVENT, {
4559
- turnId: run.id,
4997
+ recordTaskEventRecord(tx, task.id, RUN_RECOVERED_EVENT, {
4998
+ runId: run.id,
4560
4999
  roleName: run.roleName,
4561
5000
  progressAt: now.toISOString(),
4562
5001
  kind: "checkpoint"
@@ -4604,12 +5043,12 @@ export function queueReviewRound(store, item, config, requestedBy, now, requeste
4604
5043
  reviewer = createTaskRole(store, task, roleName, undefined, now, roleName);
4605
5044
  store.saveRole(task.id, reviewer);
4606
5045
  }
4607
- if (store.getActiveTurn(item.taskId, reviewer.name) !== null) {
5046
+ if (store.getActiveRun(item.taskId, reviewer.name) !== null) {
4608
5047
  const pending = createPending();
4609
5048
  store.saveReviewRound(item.taskId, pending);
4610
5049
  const failed = finishReviewRound(pending, "failed", now, {
4611
5050
  kind: "dispatch",
4612
- message: `Reviewer Role already has an active Turn: ${reviewer.name}.`
5051
+ message: `Reviewer Role already has an active AgentRun: ${reviewer.name}.`
4613
5052
  });
4614
5053
  store.saveReviewRound(item.taskId, failed);
4615
5054
  return { round: failed };
@@ -4749,8 +5188,8 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
4749
5188
  && round.reviewerRoleName !== taskFinalContract.reviewerRoleName) {
4750
5189
  throw new TaskFinalReviewDispatchDriftError(`Task final-review Reviewer identity does not match ReviewRound ${round.id}.`);
4751
5190
  }
4752
- if (round.status === "pending" && round.reviewerTurnId !== undefined) {
4753
- throw new TaskFinalReviewDispatchDriftError(`Pending final ReviewRound already records Reviewer Turn ${round.reviewerTurnId}: `
5191
+ if (round.status === "pending" && round.reviewerRunId !== undefined) {
5192
+ throw new TaskFinalReviewDispatchDriftError(`Pending final ReviewRound already records Reviewer AgentRun ${round.reviewerRunId}: `
4754
5193
  + `${round.id}.`);
4755
5194
  }
4756
5195
  let currentTaskCandidate;
@@ -4802,8 +5241,8 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
4802
5241
  && (requestedReviewers.has(entry.reviewerRoleName)
4803
5242
  || entry.executionGroup?.lanes.some(({ roleName }) => (requestedReviewers.has(roleName))) === true)
4804
5243
  && !(entry.status === "running"
4805
- && entry.reviewerTurnId !== undefined
4806
- && tx.getTurn(task.id, entry.reviewerTurnId)?.status === "failed")));
5244
+ && entry.reviewerRunId !== undefined
5245
+ && tx.getRun(task.id, entry.reviewerRunId)?.status === "failed")));
4807
5246
  if (conflicting !== undefined) {
4808
5247
  throw new TaskFinalReviewDispatchDriftError(`Another active Task-final ReviewRound already exists: ${conflicting.id}/${conflicting.reviewerRoleName}.`);
4809
5248
  }
@@ -4830,8 +5269,8 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
4830
5269
  }
4831
5270
  const candidateLabel = taskScope
4832
5271
  ? "frozen Task candidate"
4833
- : candidate.source.type === "turn"
4834
- ? `candidate Turn ${candidate.source.turnId}`
5272
+ : candidate.source.type === "run"
5273
+ ? `candidate AgentRun ${candidate.source.runId}`
4835
5274
  : `revision ${candidate.workItemRevision}`;
4836
5275
  const frozenHeads = taskScope
4837
5276
  ? round.taskCandidate.projects
@@ -4846,7 +5285,7 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
4846
5285
  }
4847
5286
  const diffByProject = options.deltaRecheckDiff;
4848
5287
  if (diffByProject === undefined) {
4849
- throw new TaskFinalReviewDispatchDriftError(`Delta-recheck diff is missing for ${round.id}; the CLI preflight did not turn.`);
5288
+ throw new TaskFinalReviewDispatchDriftError(`Delta-recheck diff is missing for ${round.id}; the CLI preflight did not run.`);
4850
5289
  }
4851
5290
  try {
4852
5291
  verifyDeltaRecheckDiff(round.deltaRecheck, diffByProject);
@@ -4890,17 +5329,17 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
4890
5329
  "You may freely edit source/tests, run local build or test commands, and optionally commit diagnostic evidence only inside this stable Reviewer workspace at the exact ReviewRound snapshot.",
4891
5330
  "Do not push, integrate, mutate Task state, touch the Candidate or Worker workspace, another Task/workspace, a stable checkout, or the real Yui control-plane home.",
4892
5331
  "End the Provider turn with one complete original result in clear Markdown or JSON. Recommended sections are conclusion, material findings, checks actually run, uncertainty, and next actions. Yui preserves the text verbatim and does not parse or validate those sections.",
4893
- "Report reviewBaseCommit, exact checks/results, material findings, and uncertainty. This Turn result completes only the Round and creates no Candidate or ChangeSet.",
5332
+ "Report reviewBaseCommit, exact checks/results, material findings, and uncertainty. This AgentRun result completes only the Round and creates no Candidate or ChangeSet.",
4894
5333
  "The Leader alone interprets and routes evidence: original Worker when open, a small Repair WorkItem when needed, or Leader/Integration for merge and local fixes; never merge review evidence yourself."
4895
5334
  ].join("\n");
4896
- const createdTurns = [];
5335
+ const createdRuns = [];
4897
5336
  if (round.executionGroup === undefined) {
4898
5337
  if (round.status !== "pending")
4899
- return createdTurns;
4900
- if (tx.getActiveTurn(taskId, reviewer.name) !== null) {
4901
- throw usageError(`Reviewer Role already has an active Turn: ${reviewer.name}.`);
5338
+ return createdRuns;
5339
+ if (tx.getActiveRun(taskId, reviewer.name) !== null) {
5340
+ throw usageError(`Reviewer Role already has an active AgentRun: ${reviewer.name}.`);
4902
5341
  }
4903
- const turnId = tx.nextTurnId(taskId);
5342
+ const runId = tx.nextRunId(taskId);
4904
5343
  const effective = resolveEffectiveLaunch({
4905
5344
  role: reviewer,
4906
5345
  purpose: "review",
@@ -4908,14 +5347,14 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
4908
5347
  reviewRoundId: round.id,
4909
5348
  reviewBaseCommit: round.reviewBaseCommit
4910
5349
  });
4911
- const snapshot = freezeTurnContextSnapshot(tx, {
5350
+ const snapshot = freezeRunContextSnapshot(tx, {
4912
5351
  taskId,
4913
5352
  roleName: reviewer.name,
4914
5353
  purpose: "review",
4915
5354
  ...(item === undefined ? {} : { workItemId: item.id }),
4916
5355
  reviewRoundId: round.id
4917
5356
  }, now, "controller");
4918
- const created = createTurn(turnId, taskId, reviewer.name, roleAgentSessionResumeMode(tx.getTaskRoleSessionSet(taskId, reviewer.name), effective.agentId, effective), createTurnInput({
5357
+ const created = createRun(runId, taskId, reviewer.name, roleAgentSessionResumeMode(tx.getTaskRoleSessionSet(taskId, reviewer.name), effective.agentId, effective), createRunInput({
4919
5358
  source: {
4920
5359
  type: "yui",
4921
5360
  channel: item === undefined ? "task-dispatch" : "workitem-dispatch"
@@ -4930,27 +5369,27 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
4930
5369
  workspace: round.workspace,
4931
5370
  effective
4932
5371
  });
4933
- tx.saveTurn(created);
5372
+ tx.saveRun(created);
4934
5373
  tx.saveReviewRound(taskId, startReviewRound(round, created.id));
4935
- tx.saveActiveTurn(created);
4936
- enqueueRoleTurnDispatch(tx, {
5374
+ tx.saveActiveRun(created);
5375
+ enqueueRoleRunDispatch(tx, {
4937
5376
  taskId,
4938
5377
  roleName: reviewer.name,
4939
- turnId: created.id,
5378
+ runId: created.id,
4940
5379
  reason: "review-requested",
4941
5380
  occurredAt: now
4942
5381
  });
4943
- recordTaskEvent(tx, taskId, "turn.review-dispatched", turnLaunchEventPayload(created), now);
4944
- createdTurns.push(created);
4945
- return createdTurns;
5382
+ recordTaskEvent(tx, taskId, "run.review-dispatched", runLaunchEventPayload(created), now);
5383
+ createdRuns.push(created);
5384
+ return createdRuns;
4946
5385
  }
4947
5386
  let runningGroup = round.executionGroup;
4948
5387
  const dispatchLanes = runningGroup.lanes.filter((lane) => {
4949
5388
  if (lane.disposition !== "open")
4950
5389
  return false;
4951
- if (lane.currentTurnId === undefined)
5390
+ if (lane.currentRunId === undefined)
4952
5391
  return true;
4953
- return tx.getTurn(taskId, lane.currentTurnId)?.status === "failed";
5392
+ return tx.getRun(taskId, lane.currentRunId)?.status === "failed";
4954
5393
  });
4955
5394
  const preparedLaneWorkspaces = requireIsolatedReviewLaneWorkspaces(tx, round, dispatchLanes, options.executionLaneWorkspaces);
4956
5395
  for (const lane of dispatchLanes) {
@@ -4958,8 +5397,8 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
4958
5397
  if (laneReviewer === null) {
4959
5398
  throw usageError(`Review Producer Role not found: ${taskId}/${lane.roleName}.`);
4960
5399
  }
4961
- if (tx.getActiveTurn(taskId, lane.roleName) !== null) {
4962
- throw usageError(`Review Producer Role already has an active Turn: ${lane.roleName}.`);
5400
+ if (tx.getActiveRun(taskId, lane.roleName) !== null) {
5401
+ throw usageError(`Review Producer Role already has an active AgentRun: ${lane.roleName}.`);
4963
5402
  }
4964
5403
  const laneManagedWorkspace = preparedLaneWorkspaces.get(lane.id);
4965
5404
  const effective = lane.effective ?? resolveEffectiveLaunch({
@@ -4969,8 +5408,8 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
4969
5408
  reviewRoundId: round.id,
4970
5409
  reviewBaseCommit: round.reviewBaseCommit
4971
5410
  });
4972
- const turnId = tx.nextTurnId(taskId);
4973
- const input = createTurnInput({
5411
+ const runId = tx.nextRunId(taskId);
5412
+ const input = createRunInput({
4974
5413
  source: {
4975
5414
  type: "yui",
4976
5415
  channel: item === undefined ? "task-dispatch" : "workitem-dispatch"
@@ -4992,7 +5431,7 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
4992
5431
  deltaRefIds: []
4993
5432
  });
4994
5433
  runningGroup = updateUnifiedExecutionLane(runningGroup, lane.id, {
4995
- currentTurnId: turnId,
5434
+ currentRunId: runId,
4996
5435
  effective,
4997
5436
  workspace: {
4998
5437
  root: laneManagedWorkspace.root,
@@ -5001,7 +5440,7 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
5001
5440
  .map(({ projectId }) => projectId)
5002
5441
  }
5003
5442
  }, now);
5004
- createdTurns.push(createTurn(turnId, taskId, laneReviewer.name, roleAgentSessionResumeMode(tx.getTaskRoleSessionSet(taskId, laneReviewer.name), effective.agentId, effective), input, now, {
5443
+ createdRuns.push(createRun(runId, taskId, laneReviewer.name, roleAgentSessionResumeMode(tx.getTaskRoleSessionSet(taskId, laneReviewer.name), effective.agentId, effective), input, now, {
5005
5444
  ...(item === undefined ? {} : { workItemId: item.id }),
5006
5445
  purpose: "review",
5007
5446
  reviewRoundId: round.id,
@@ -5023,32 +5462,30 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
5023
5462
  tx.saveManagedWorkspace(prepared);
5024
5463
  }
5025
5464
  }
5026
- for (let index = 0; index < createdTurns.length; index += 1) {
5027
- const unboundTurn = createdTurns[index];
5028
- const snapshot = freezeTurnContextSnapshot(tx, {
5465
+ for (let index = 0; index < createdRuns.length; index += 1) {
5466
+ const unboundRun = createdRuns[index];
5467
+ const snapshot = freezeRunContextSnapshot(tx, {
5029
5468
  taskId,
5030
- roleName: unboundTurn.roleName,
5469
+ roleName: unboundRun.roleName,
5031
5470
  purpose: "review",
5032
5471
  ...(item === undefined ? {} : { workItemId: item.id }),
5033
5472
  reviewRoundId: round.id
5034
5473
  }, now, "controller", runningGroup.assignment.contextSnapshotRef);
5035
- const created = withTurnContextSnapshot(unboundTurn, contextSnapshotRef(snapshot), contextSnapshotDeltaRefIds(tx, snapshot));
5036
- createdTurns[index] = created;
5037
- const laneReviewer = requireRole(tx, taskId, unboundTurn.roleName);
5038
- tx.saveTurn(created);
5039
- tx.saveActiveTurn(created);
5040
- enqueueRoleTurnDispatch(tx, {
5474
+ const created = withRunContextSnapshot(unboundRun, contextSnapshotRef(snapshot), contextSnapshotDeltaRefIds(tx, snapshot));
5475
+ createdRuns[index] = created;
5476
+ const laneReviewer = requireRole(tx, taskId, unboundRun.roleName);
5477
+ tx.saveRun(created);
5478
+ tx.saveActiveRun(created);
5479
+ enqueueRoleRunDispatch(tx, {
5041
5480
  taskId,
5042
5481
  roleName: laneReviewer.name,
5043
- turnId: created.id,
5482
+ runId: created.id,
5044
5483
  reason: "review-requested",
5045
5484
  occurredAt: now
5046
5485
  });
5047
- recordTaskEvent(tx, taskId, "turn.review-dispatched", turnLaunchEventPayload(created), now);
5486
+ recordTaskEvent(tx, taskId, "run.review-dispatched", runLaunchEventPayload(created), now);
5048
5487
  }
5049
- const reconciliation = reconcileReviewMainTurns(tx, taskId, now);
5050
- createdTurns.push(...reconciliation.createdTurns);
5051
- return createdTurns;
5488
+ return createdRuns;
5052
5489
  });
5053
5490
  for (const run of runs) {
5054
5491
  notifyMailbox(options.runtime, roleMailbox(run.taskId, run.roleName), run.taskId);
@@ -5098,6 +5535,16 @@ function requireWorkItemCandidate(item) {
5098
5535
  }
5099
5536
  return candidate;
5100
5537
  }
5538
+ function fixedArtifactRefs(store, taskId, ids) {
5539
+ if (new Set(ids).size !== ids.length)
5540
+ throw usageError("Artifact references must be unique.");
5541
+ try {
5542
+ return createProjectResources(store).resultRefs(taskId, ids);
5543
+ }
5544
+ catch (error) {
5545
+ throw usageError(`Result Artifact is unavailable: ${messageOf(error)}`);
5546
+ }
5547
+ }
5101
5548
  /** ReviewRound ids are the durable Task-local creation order; wall time is not causal. */
5102
5549
  function reviewRoundsByIdentity(rounds) {
5103
5550
  return [...rounds].sort((left, right) => (left.id.localeCompare(right.id, undefined, { numeric: true })));
@@ -5110,7 +5557,9 @@ function activeReviewRoundForCandidate(store, item, candidate) {
5110
5557
  .at(-1);
5111
5558
  }
5112
5559
  function createTaskRole(store, task, roleName, explicitAgentId, now, sourceGlobalRoleName) {
5113
- const workspace = task.cwd ?? store.getConfig().defaultWorkspace ?? process.cwd();
5560
+ const workspace = task.status === "draft"
5561
+ ? join(`${store.rootDirectory()}.task-runtimes`, "planning", task.id)
5562
+ : task.cwd ?? store.getConfig().defaultWorkspace ?? process.cwd();
5114
5563
  if (explicitAgentId === undefined) {
5115
5564
  const sourceRoleName = sourceGlobalRoleName
5116
5565
  ?? (roleName === LEADER_ROLE ? LEADER_ROLE : "worker");
@@ -5128,7 +5577,7 @@ function createTaskRole(store, task, roleName, explicitAgentId, now, sourceGloba
5128
5577
  throw dataError(`No Agent is configured for Task role: ${roleName}.`);
5129
5578
  }
5130
5579
  const agent = requireAgent(store, agentId);
5131
- const binding = createRoleAgentBinding({ id: agent.id, adapterId: agent.adapterId });
5580
+ const binding = createRoleAgentBinding(agent);
5132
5581
  return createRole(task.id, roleName, [binding], agent.id, workspace, now);
5133
5582
  }
5134
5583
  function requireAgentProfile(store, id) {
@@ -5138,7 +5587,9 @@ function requireAgentProfile(store, id) {
5138
5587
  return profile;
5139
5588
  }
5140
5589
  function createTaskRoleFromAgentBinding(store, task, roleName, binding, now) {
5141
- const workspace = task.cwd ?? store.getConfig().defaultWorkspace ?? process.cwd();
5590
+ const workspace = task.status === "draft"
5591
+ ? join(`${store.rootDirectory()}.task-runtimes`, "planning", task.id)
5592
+ : task.cwd ?? store.getConfig().defaultWorkspace ?? process.cwd();
5142
5593
  return createRole(task.id, roleName, [binding], binding.agentId, workspace, now);
5143
5594
  }
5144
5595
  function resolvedAgentProfileRuntime(profile, store) {
@@ -5166,7 +5617,7 @@ function resolveTaskRoleAgentBindingAdd(parsed, profile, store) {
5166
5617
  if (explicitAgentId === undefined)
5167
5618
  return undefined;
5168
5619
  const agent = requireAgent(store, explicitAgentId);
5169
- binding = createRoleAgentBinding({ id: agent.id, adapterId: agent.adapterId });
5620
+ binding = createRoleAgentBinding(agent);
5170
5621
  }
5171
5622
  if (hasAgentConfigOptions(parsed)) {
5172
5623
  binding = patchRoleAgentBinding(binding, parsed);
@@ -5177,11 +5628,6 @@ function resolveTaskRoleAgentBindingUpdate(parsed, role, profile, store) {
5177
5628
  const changesAgentConfig = hasAgentConfigOptions(parsed);
5178
5629
  const explicitAgentId = parsed.one("--agent")?.trim();
5179
5630
  const targetAgentId = explicitAgentId || role.activeAgentId;
5180
- if (profile?.runtime.source === "global-worker"
5181
- && explicitAgentId === undefined
5182
- && !changesAgentConfig) {
5183
- return undefined;
5184
- }
5185
5631
  let binding;
5186
5632
  if (profile !== undefined) {
5187
5633
  const profileRuntime = resolvedAgentProfileRuntime(profile, store);
@@ -5200,7 +5646,7 @@ function resolveTaskRoleAgentBindingUpdate(parsed, role, profile, store) {
5200
5646
  return undefined;
5201
5647
  const agent = requireAgent(store, targetAgentId);
5202
5648
  binding = role.agentBindings[targetAgentId]
5203
- ?? createRoleAgentBinding({ id: agent.id, adapterId: agent.adapterId });
5649
+ ?? createRoleAgentBinding(agent);
5204
5650
  }
5205
5651
  if (changesAgentConfig) {
5206
5652
  binding = patchRoleAgentBinding(binding, parsed);
@@ -5237,7 +5683,7 @@ function appendMessage(store, taskId, body, kind, author, now, context = {}) {
5237
5683
  recordTaskEvent(store, taskId, "message.sent", {
5238
5684
  messageId: message.id,
5239
5685
  kind: message.kind,
5240
- ...(message.turnId === undefined ? {} : { turnId: message.turnId })
5686
+ ...(message.runId === undefined ? {} : { runId: message.runId })
5241
5687
  }, now);
5242
5688
  return message;
5243
5689
  }
@@ -5245,43 +5691,29 @@ function recordTaskEvent(store, taskId, type, payload, now) {
5245
5691
  return recordTaskEventRecord(store, taskId, type, payload, now);
5246
5692
  }
5247
5693
  function leaderActionEventPayload(store, taskId, options) {
5248
- const turnId = taskLeaderActionTurnId(store, taskId, options.environment);
5249
- return turnId === undefined ? {} : { leaderTurnId: turnId };
5694
+ const caller = currentManagedRuntime(store, options.environment, taskId, "leader");
5695
+ // A long-lived Session may be handling direct input while another request
5696
+ // awaits admission. The Role's active pointer cannot prove command origin.
5697
+ return caller === undefined ? {} : { leaderNativeSessionId: caller.nativeSessionId };
5250
5698
  }
5251
5699
  function recordTaskEventRecord(store, taskId, type, payload, now) {
5252
5700
  const event = createTaskEvent(store.nextEventId(taskId), taskId, type, payload, now);
5253
5701
  store.saveEvent(taskId, event);
5254
5702
  return event;
5255
5703
  }
5256
- /** Keeps a free-text Turn-fact note bounded so an event payload stays compact. */
5704
+ /** Keeps a free-text AgentRun-fact note bounded so an event payload stays compact. */
5257
5705
  function truncateEventNote(note) {
5258
5706
  const normalized = note.trim();
5259
5707
  return normalized.length <= 280 ? normalized : `${normalized.slice(0, 279)}…`;
5260
5708
  }
5261
- function roleLaunchEventPayload(role, sessions) {
5262
- const effective = sessions?.sessions[sessions.activeAgentId]?.effective;
5263
- return {
5264
- desiredRevision: String(role.launchRevision),
5265
- defaultAccess: role.defaultAccess,
5266
- effectiveRevision: effective === undefined
5267
- ? "none"
5268
- : String(effective.sourceDesiredRevision),
5269
- profileAccess: effective?.profileAccess ?? "none",
5270
- effectivePermission: effective?.permission.strategy ?? "none",
5271
- desiredDrift: effective === undefined
5272
- ? "not-started"
5273
- : effective.sourceDesiredRevision === role.launchRevision
5274
- ? "none"
5275
- : "pending-next-launch"
5276
- };
5277
- }
5278
- function turnLaunchEventPayload(run) {
5709
+ function runLaunchEventPayload(run) {
5279
5710
  return {
5280
- turnId: run.id,
5711
+ runId: run.id,
5281
5712
  role: run.roleName,
5282
5713
  purpose: run.purpose,
5283
5714
  mode: run.mode,
5284
5715
  agent: `${run.effective.agentId}/${run.effective.adapterId}`,
5716
+ component: run.effective.component,
5285
5717
  effectiveRevision: String(run.effective.sourceDesiredRevision),
5286
5718
  profileAccess: run.effective.profileAccess,
5287
5719
  effectivePermission: run.effective.permission.strategy,
@@ -5338,18 +5770,18 @@ function requireWorkItem(store, workItemId, options) {
5338
5770
  }
5339
5771
  return item;
5340
5772
  }
5341
- function requireTurn(store, turnId, options) {
5342
- const reference = taskRecordReference(turnId, "turn", "Turn reference", options);
5343
- const run = store.getTurn(reference.taskId, reference.localId);
5773
+ function requireRun(store, runId, options) {
5774
+ const reference = taskRecordReference(runId, "run", "AgentRun reference", options);
5775
+ const run = store.getRun(reference.taskId, reference.localId);
5344
5776
  if (run === null) {
5345
- throw usageError(`Turn not found: ${reference.taskId}/${reference.localId}.`);
5777
+ throw usageError(`AgentRun not found: ${reference.taskId}/${reference.localId}.`);
5346
5778
  }
5347
5779
  return run;
5348
5780
  }
5349
- function activeTurnPointer(store, run) {
5781
+ function activeRunPointer(store, run) {
5350
5782
  return run.executionGroupId !== undefined && run.executionLaneId !== undefined
5351
- ? store.getActiveExecutionLaneTurn(run.taskId, run.executionGroupId, run.executionLaneId)
5352
- : store.getActiveTurn(run.taskId, run.roleName);
5783
+ ? store.getActiveExecutionLaneRun(run.taskId, run.executionGroupId, run.executionLaneId)
5784
+ : store.getActiveRun(run.taskId, run.roleName);
5353
5785
  }
5354
5786
  function requireReviewRound(store, reviewRoundId, options) {
5355
5787
  const reference = taskRecordReference(reviewRoundId, "reviewRound", "ReviewRound reference", options);
@@ -5384,12 +5816,12 @@ export function assertWorkItemDependenciesCompletedForCommand(store, item) {
5384
5816
  throw error;
5385
5817
  }
5386
5818
  }
5387
- function chronologicalTurns(turns) {
5388
- return [...turns].sort((left, right) => (left.createdAt.localeCompare(right.createdAt)
5819
+ function chronologicalRuns(runs) {
5820
+ return [...runs].sort((left, right) => (left.createdAt.localeCompare(right.createdAt)
5389
5821
  || left.id.localeCompare(right.id)));
5390
5822
  }
5391
5823
  function isTerminalWorkItemStatus(status) {
5392
- return ["completed", "failed", "retired"].includes(status);
5824
+ return ["accepted", "retired"].includes(status);
5393
5825
  }
5394
5826
  function assertTaskOpen(task) {
5395
5827
  if (task.status === "completed") {
@@ -5397,7 +5829,7 @@ function assertTaskOpen(task) {
5397
5829
  }
5398
5830
  if (task.status === "archived")
5399
5831
  throw usageError(`Task is archived: ${task.id}.`);
5400
- if (task.status === "retired")
5832
+ if (task.status === "cancelled")
5401
5833
  throw usageError(`Task is retired: ${task.id}.`);
5402
5834
  }
5403
5835
  function assertTaskExecutionEnabled(task, action) {
@@ -5415,7 +5847,7 @@ function inactiveTaskMessage(task, action) {
5415
5847
  if (task.status === "completed") {
5416
5848
  return `Task ${task.id} is completed; reopen it before ${action}.`;
5417
5849
  }
5418
- if (task.status === "retired")
5850
+ if (task.status === "cancelled")
5419
5851
  return `Task ${task.id} is retired; it cannot resume ${action}.`;
5420
5852
  return `Task is archived: ${task.id}.`;
5421
5853
  }
@@ -5436,10 +5868,6 @@ function parseWorkStatus(value) {
5436
5868
  throw usageError(`Invalid work item status: ${value}.`);
5437
5869
  }
5438
5870
  function presentWorkStatus(status) {
5439
- if (status === "pending")
5440
- return "todo";
5441
- if (status === "completed")
5442
- return "done";
5443
5871
  return status;
5444
5872
  }
5445
5873
  function parseWorkCreateArgs(args, usage) {
@@ -5614,7 +6042,10 @@ function taskBriefCommand(args, store, options) {
5614
6042
  ...(hasSummary ? { leaderSummary: parsed.options.get("--leader-summary") } : {})
5615
6043
  }, updatedBy, now);
5616
6044
  tx.saveTaskBrief(task.id, brief);
5617
- recordTaskEvent(tx, task.id, "brief.updated", { updatedBy }, now);
6045
+ recordTaskEvent(tx, task.id, "brief.updated", {
6046
+ updatedBy,
6047
+ previous: JSON.stringify(existing), current: JSON.stringify(brief)
6048
+ }, now);
5618
6049
  enqueueWork(tx, taskMailbox(task.id), "brief-updated", now, [taskRef(task.id)]);
5619
6050
  if (task.status === "active" && updatedBy !== "leader") {
5620
6051
  enqueueWork(tx, leaderMailbox(task.id), "brief-updated", now, [taskRef(task.id)]);
@@ -5625,12 +6056,18 @@ function taskBriefCommand(args, store, options) {
5625
6056
  if (result.task.status === "active") {
5626
6057
  notifyMailbox(options.runtime, leaderMailbox(result.task.id), result.task.id);
5627
6058
  }
5628
- return output(`Updated brief for ${result.task.id}\n`);
6059
+ return output(`Updated brief for ${result.task.id}\n`, { taskId: result.task.id, brief: result.brief });
5629
6060
  }
5630
6061
  throw usageError(command === undefined
5631
6062
  ? "Task brief command is required."
5632
6063
  : `Unknown command: task brief ${command}`);
5633
6064
  }
6065
+ /** Persist only edited fields, not another copy of aggregate execution history.
6066
+ * Null records an absent optional field so clearing it remains observable. */
6067
+ function editedFieldValues(record, fields) {
6068
+ const values = record;
6069
+ return JSON.stringify(Object.fromEntries(fields.map((field) => [field, values[field] ?? null])));
6070
+ }
5634
6071
  function taskDecisionCommand(args, store, options) {
5635
6072
  const [command, ...rest] = args;
5636
6073
  if (command === "record") {
@@ -5881,14 +6318,11 @@ function taskContinuationCommand(args, store) {
5881
6318
  const report = [...continuation.reports].reverse()[0];
5882
6319
  const reportEvent = report === undefined
5883
6320
  ? undefined
5884
- : reportEvents.find((entry) => (entry.continuationId === identity.continuationId
5885
- && entry.continuationGeneration === identity.generation
5886
- && entry.reportId === report.reportId));
6321
+ : reportEvents.find((entry) => (entry.continuationId === identity.continuationId && entry.reportId === report.reportId));
5887
6322
  return Object.freeze({
5888
6323
  continuationId: identity.continuationId,
5889
- generation: identity.generation,
5890
6324
  driver: identity.providerNamespace,
5891
- turnId: continuation.turnId,
6325
+ runId: continuation.runId,
5892
6326
  execution: continuation.execution,
5893
6327
  outcome: continuation.outcome,
5894
6328
  attachment: continuation.attachment,
@@ -5934,12 +6368,9 @@ function continuationReportEvents(events) {
5934
6368
  const observation = runtimeObservationFromTaskEvent(event);
5935
6369
  if (observation !== null && observation.kind === "continuation.reported") {
5936
6370
  const continuationId = observation.fence.continuationId;
5937
- const continuationGeneration = observation.fence.continuationGeneration;
5938
6371
  const reportId = observation.payload?.reportId;
5939
- if (continuationId !== undefined
5940
- && continuationGeneration !== undefined
5941
- && reportId !== undefined) {
5942
- result.push({ event, continuationId, continuationGeneration, reportId });
6372
+ if (continuationId !== undefined && reportId !== undefined) {
6373
+ result.push({ event, continuationId, reportId });
5943
6374
  }
5944
6375
  }
5945
6376
  }
@@ -5953,6 +6384,48 @@ function continuationReportEvents(events) {
5953
6384
  */
5954
6385
  function taskWakeDispatch(args, store, options) {
5955
6386
  const [subcommand] = args;
6387
+ if (subcommand === "resolve") {
6388
+ const usage = "Task wake resolve usage: yui task wake resolve <task> <wake> --reason <quiescence-evidence>.";
6389
+ const parsed = parseTail(args.slice(1), new Set(["--reason"]), usage);
6390
+ exactPositionals(parsed.positionals, 2, usage);
6391
+ const reason = requiredOption(parsed.options, "--reason");
6392
+ const now = clock(options);
6393
+ const result = store.transaction((tx) => {
6394
+ const task = requireTask(tx, parsed.positionals[0]);
6395
+ taskActor(tx, options, task.id);
6396
+ const wakeId = parsed.positionals[1];
6397
+ const wake = tx.getTaskWake(task.id, wakeId);
6398
+ if (wake === null)
6399
+ throw usageError("Wake is unavailable.");
6400
+ const target = leaderMailbox(task.id);
6401
+ const mailbox = tx.getWorkMailbox(target);
6402
+ const claim = mailbox?.processing;
6403
+ if (claim === undefined || claim === null || claim.owner !== `leader-notification:${wake.id}`) {
6404
+ throw usageError("This wake has no unresolved notification claim.");
6405
+ }
6406
+ const previous = tx.listEvents(task.id).filter((event) => event.type === "notification.delivery" && event.payload.attemptId === claim.batchId).at(-1);
6407
+ const sessions = tx.getTaskRoleSessionSet(task.id, "leader");
6408
+ const provider = sessions?.providerBinding;
6409
+ if (previous?.payload.outcome !== "unknown")
6410
+ throw usageError("Only an unknown notification can be resolved.");
6411
+ if (tx.getActiveRun(task.id, "leader") !== null
6412
+ || provider?.authority.owner === "human" || provider?.authority.owner === "unknown"
6413
+ || (provider?.run !== undefined && provider.run !== null
6414
+ && ["submitting", "accepted", "delivery-unknown"].includes(provider.run.status))) {
6415
+ throw usageError("Shared native execution is not proven quiescent; resolve that exact runtime boundary first.");
6416
+ }
6417
+ // Preserve the original unknown outcome and the unconsumed fixed wake.
6418
+ // This releases only the scheduling claim; it never replays, claims
6419
+ // acceptance, or asserts that Message requirements were implemented.
6420
+ recordTaskEvent(tx, task.id, "notification.resolved", {
6421
+ wakeId: wake.id, attemptId: claim.batchId, reason, outcome: "released-without-replay"
6422
+ }, now);
6423
+ tx.saveWorkMailbox(completeProcessing(mailbox, claim.batchId));
6424
+ return { taskId: task.id, wakeId: wake.id, outcome: "released-without-replay" };
6425
+ });
6426
+ notifyMailbox(options.runtime, leaderMailbox(result.taskId), result.taskId);
6427
+ return output(`Released ${result.wakeId} without replay; original acceptance remains unknown.\n`, result);
6428
+ }
5956
6429
  if (subcommand === "list" || subcommand === "show") {
5957
6430
  return taskWakeInspectionCommand(args, store);
5958
6431
  }
@@ -5979,13 +6452,13 @@ function taskWakeInspectionCommand(args, store) {
5979
6452
  { header: "Wake", minWidth: 8, maxWidth: 18 },
5980
6453
  { header: "Status", minWidth: 8, maxWidth: 12 },
5981
6454
  { header: "Reasons", minWidth: 10, maxWidth: 40 },
5982
- { header: "Turn", minWidth: 10, maxWidth: 20 },
6455
+ { header: "AgentRun", minWidth: 10, maxWidth: 20 },
5983
6456
  { header: "Dispatched", minWidth: 10, maxWidth: 28 }
5984
6457
  ], wakes.map((wake) => [
5985
6458
  wake.id,
5986
6459
  wake.status,
5987
6460
  wake.reasons.map(renderWakeReason).join(", "),
5988
- wake.turnId ?? "-",
6461
+ wake.runId ?? "-",
5989
6462
  presentTime(wake.createdAt, timeZone)
5990
6463
  ]), defaultTableWidth())}\n`, { taskId: task.id, wakes });
5991
6464
  }
@@ -6004,18 +6477,30 @@ function taskWakeInspectionCommand(args, store) {
6004
6477
  return ms > fromMs && ms <= toMs;
6005
6478
  };
6006
6479
  const allEvents = store.listEvents(task.id);
6007
- const events = allEvents.filter((e) => inWindow(e.createdAt));
6008
- const messages = store.listMessages(task.id).filter((m) => inWindow(m.createdAt));
6009
- const allTurns = store.listTurns(task.id);
6010
- const referencedTurnIds = new Set(referencedWakeTurnIds(allTurns, allEvents, events));
6011
- const turns = operationalTaskRecords(allTurns, allEvents, "turn").filter((turn) => (inWindow(turn.createdAt) || referencedTurnIds.has(turn.id)));
6480
+ const deliveryEvents = allEvents.filter((event) => (event.type === "notification.delivery" || event.type === "notification.resolved")
6481
+ && (event.payload.wakeId === wake.id
6482
+ || event.payload.attemptId?.startsWith(`notification:${task.id}/${wake.id}/`)));
6483
+ const referenced = (type, id) => wake.refs?.some(ref => ref.type === type && ref.id === id
6484
+ && (!("taskId" in ref) || ref.taskId === task.id)) === true;
6485
+ const events = allEvents.filter((e) => inWindow(e.createdAt) || referenced("event", e.id));
6486
+ const messages = store.listMessages(task.id).filter((m) => inWindow(m.createdAt) || referenced("message", m.id));
6487
+ const allRuns = store.listRuns(task.id);
6488
+ const referencedRunIds = new Set(referencedWakeRunIds(allRuns, allEvents, events));
6489
+ for (const run of allRuns)
6490
+ if (referenced("run", run.id))
6491
+ referencedRunIds.add(run.id);
6492
+ for (const message of messages)
6493
+ if (message.resultRef?.type === "agent-run-result")
6494
+ referencedRunIds.add(message.resultRef.runId);
6495
+ const runs = operationalTaskRecords(allRuns, allEvents, "run").filter((run) => (inWindow(run.createdAt) || referencedRunIds.has(run.id)));
6012
6496
  const lines = [
6013
6497
  `Wake: ${wake.id}`,
6014
6498
  `Task: ${task.id}`,
6015
6499
  `Status: ${wake.status}`,
6500
+ `Notification: ${deliveryEvents.at(-1)?.payload.outcome ?? "unobserved"}`,
6016
6501
  `Reasons: ${wake.reasons.map(renderWakeReason).join(", ")}`,
6017
6502
  `Delta window: ${wake.fromCursor} → ${wake.toCursor}`,
6018
- ...(wake.turnId === undefined ? [] : [`Turn: ${wake.turnId}`]),
6503
+ ...(wake.runId === undefined ? [] : [`AgentRun: ${wake.runId}`]),
6019
6504
  `Dispatched: ${presentTime(wake.createdAt, timeZone)}`,
6020
6505
  ...(wake.consumedAt === undefined
6021
6506
  ? []
@@ -6024,19 +6509,20 @@ function taskWakeInspectionCommand(args, store) {
6024
6509
  ...events.map((e) => ` ${e.id} ${e.type} ${presentTime(e.createdAt, timeZone)}`),
6025
6510
  `Messages (${messages.length}):`,
6026
6511
  ...messages.map((m) => ` ${m.id} [${taskMessageAuthorLabel(m.author)}] ${presentTime(m.createdAt, timeZone)}`),
6027
- `Turns (${turns.length}):`,
6028
- ...turns.map((turn) => (` ${turn.id} [${turn.status}/${turn.purpose}] ${turn.roleName} `
6029
- + `${presentTime(turn.createdAt, timeZone)}`
6030
- + `${referencedTurnIds.has(turn.id)
6031
- ? ` → yui task turn show ${task.id}/${turn.id}`
6512
+ `AgentRuns (${runs.length}):`,
6513
+ ...runs.map((run) => (` ${run.id} [${run.status}/${run.purpose}] ${run.roleName} `
6514
+ + `${presentTime(run.createdAt, timeZone)}`
6515
+ + `${referencedRunIds.has(run.id)
6516
+ ? ` → yui task run show ${task.id}/${run.id}`
6032
6517
  : ""}`))
6033
6518
  ];
6034
6519
  return output(lines.join("\n").concat("\n"), {
6035
6520
  taskId: task.id,
6521
+ deliveryEvents,
6036
6522
  wake,
6037
6523
  events,
6038
6524
  messages,
6039
- turns
6525
+ runs
6040
6526
  });
6041
6527
  }
6042
6528
  throw usageError(command === undefined
@@ -6167,8 +6653,8 @@ function leaderMailbox(taskId) {
6167
6653
  function taskRef(id) {
6168
6654
  return { type: "task", id };
6169
6655
  }
6170
- function turnRef(taskId, id) {
6171
- return { type: "turn", taskId, id };
6656
+ function runRef(taskId, id) {
6657
+ return { type: "run", taskId, id };
6172
6658
  }
6173
6659
  function workItemRef(taskId, id) {
6174
6660
  return { type: "work-item", taskId, id };