@zq-silk/yui 0.15.7 → 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 (281) 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 +157 -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 +178 -95
  25. package/dist/commands/agentCommands.js +72 -14
  26. package/dist/commands/capabilityCommands.js +9 -6
  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 +1188 -710
  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 +17 -6
  64. package/dist/controller/clientRuntime.js +65 -92
  65. package/dist/controller/controller.js +114 -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 +529 -479
  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 +247 -12
  106. package/dist/kernel/capabilityRegistry.js +64 -18
  107. package/dist/kernel/instanceHost.js +12 -1
  108. package/dist/kernel/kernelPorts.js +2 -2
  109. package/dist/lifecycle/canonicalLifecycleEvent.js +44 -49
  110. package/dist/lifecycle/exactRunTerminalization.js +449 -0
  111. package/dist/message/message.js +62 -6
  112. package/dist/message/messageContinuation.js +204 -0
  113. package/dist/observability/executionAudit.js +70 -72
  114. package/dist/observability/faultClassification.js +2 -2
  115. package/dist/observability/orchestrationMetrics.js +8 -8
  116. package/dist/operator/operatorSessionHistory.js +1 -7
  117. package/dist/output/agentConfigurationPresentation.js +8 -3
  118. package/dist/output/agentRunConfigurationPresentation.js +128 -0
  119. package/dist/output/rolePresentation.js +54 -3
  120. package/dist/plugins/pluginChild.js +104 -0
  121. package/dist/plugins/pluginIntent.js +26 -0
  122. package/dist/plugins/pluginInterpreter.js +43 -0
  123. package/dist/plugins/pluginPackage.js +101 -0
  124. package/dist/plugins/pluginProcess.js +112 -0
  125. package/dist/plugins/pluginService.js +380 -0
  126. package/dist/profile/agentProfile.js +1 -1
  127. package/dist/repository/gitWorkspace.js +26 -4
  128. package/dist/repository/project.js +19 -4
  129. package/dist/repository/taskBaseFreshness.js +13 -13
  130. package/dist/repository/taskWorkspaceCoordinator.js +20 -27
  131. package/dist/repository/taskWorkspacePreparer.js +344 -83
  132. package/dist/resources/autoResourceGc.js +3 -3
  133. package/dist/resources/liveReferences.js +3 -3
  134. package/dist/resources/projectResource.js +123 -0
  135. package/dist/resources/projectResourceService.js +421 -0
  136. package/dist/resources/resourceDiscovery.js +6 -6
  137. package/dist/resources/resourceGc.js +1 -1
  138. package/dist/resources/resourceRegistrar.js +1 -1
  139. package/dist/resources/resourceTypes.js +1 -1
  140. package/dist/review/deltaRecheck.js +3 -3
  141. package/dist/review/reviewAcceptance.js +16 -16
  142. package/dist/review/reviewDecision.js +7 -7
  143. package/dist/review/reviewRound.js +21 -20
  144. package/dist/review/reviewerAvailability.js +2 -2
  145. package/dist/role/role.js +51 -7
  146. package/dist/role/taskRoleUpdate.js +30 -0
  147. package/dist/runtime/acpProtocol.js +425 -0
  148. package/dist/runtime/acpSession.js +731 -0
  149. package/dist/runtime/acpSessionConfiguration.js +260 -0
  150. package/dist/runtime/agentDriver.js +30 -11
  151. package/dist/runtime/agentEndpoint.js +278 -0
  152. package/dist/runtime/agentEndpointIdentity.js +86 -0
  153. package/dist/runtime/agentEndpointOwnership.js +239 -0
  154. package/dist/runtime/agentError.js +2 -10
  155. package/dist/runtime/agentHost.js +565 -314
  156. package/dist/runtime/agentRunConfiguration.js +258 -0
  157. package/dist/runtime/builtinAgentDrivers.js +134 -18
  158. package/dist/runtime/builtinAgentErrorMappers.js +55 -3
  159. package/dist/runtime/builtinTranscriptUsage.js +1 -1
  160. package/dist/runtime/claude-process-owner +0 -0
  161. package/dist/runtime/codexAppServerRuntime.js +38 -30
  162. package/dist/runtime/codexInteractiveHost.js +41 -6
  163. package/dist/runtime/continuationManager.js +2 -6
  164. package/dist/runtime/executionEnvironment.js +30 -0
  165. package/dist/runtime/firstProgressAdvisory.js +11 -11
  166. package/dist/runtime/index.js +4 -3
  167. package/dist/runtime/jsonLineChannel.js +109 -0
  168. package/dist/runtime/launchBroker.js +91 -16
  169. package/dist/runtime/launchDiagnostics.js +2 -2
  170. package/dist/runtime/lifecycleReservation.js +10 -18
  171. package/dist/runtime/managedCaller.js +61 -17
  172. package/dist/runtime/nativeSessionControl.js +102 -0
  173. package/dist/runtime/ports.js +6 -21
  174. package/dist/runtime/processExitObservation.js +8 -7
  175. package/dist/runtime/promptEnvelope.js +17 -6
  176. package/dist/runtime/providerContinuation.js +3 -9
  177. package/dist/runtime/providerContinuationReconciliationService.js +4 -13
  178. package/dist/runtime/providerControl.js +2 -7
  179. package/dist/runtime/providerRuntimeIdentity.js +110 -222
  180. package/dist/runtime/providerRuntimeReconciler.js +5 -9
  181. package/dist/runtime/runtimeBinding.js +0 -1
  182. package/dist/runtime/runtimeContinuationProjection.js +4 -7
  183. package/dist/runtime/runtimeDeadlines.js +9 -0
  184. package/dist/runtime/runtimeHealthPolicy.js +1 -1
  185. package/dist/runtime/runtimeObservation.js +29 -65
  186. package/dist/runtime/runtimeProjection.js +43 -51
  187. package/dist/runtime/runtimeSessionCandidate.js +1 -3
  188. package/dist/runtime/sessionLaunchRequest.js +3 -7
  189. package/dist/runtime/sessionOwnerIdentity.js +7 -54
  190. package/dist/runtime/sessionOwnerRegistry.js +22 -17
  191. package/dist/runtime/sessionReconciliation.js +4 -8
  192. package/dist/runtime/sessionTerminationGuard.js +70 -259
  193. package/dist/runtime/sessionTokenMetrics.js +5 -16
  194. package/dist/runtime/structuredProviderHost.js +237 -117
  195. package/dist/runtime/taskRuntimeIsolation.js +39 -122
  196. package/dist/runtime/tmuxAdapters.js +39 -86
  197. package/dist/scheduler/activeRoleRunDelivery.js +354 -0
  198. package/dist/scheduler/leaderWakeupProcessor.js +75 -266
  199. package/dist/scheduler/operatorInputNotificationProcessor.js +1 -1
  200. package/dist/scheduler/ports.js +80 -9
  201. package/dist/scheduler/{roleTurnLiveness.js → roleRunLiveness.js} +26 -30
  202. package/dist/scheduler/{roleTurnStall.js → roleRunStall.js} +128 -139
  203. package/dist/scheduler/taskExecutionProjection.js +120 -124
  204. package/dist/scheduler/taskObservabilityProjection.js +29 -29
  205. package/dist/scheduler/taskWake.js +11 -4
  206. package/dist/scheduler/wakeReason.js +9 -1
  207. package/dist/setup/setupCommand.js +1 -0
  208. package/dist/storage/migrations/agentRunContract.js +159 -0
  209. package/dist/storage/migrations/removeRuntimeGeneration.js +207 -0
  210. package/dist/storage/sqliteSchema.js +431 -5
  211. package/dist/storage/sqliteStore.js +375 -220
  212. package/dist/storage/storageVersions.js +1 -1
  213. package/dist/storage/storeRpc.js +10 -5
  214. package/dist/storage/taskStore.js +13 -11
  215. package/dist/storage/upgrade/upgradeOrchestrator.js +5 -7
  216. package/dist/surface/surfaceContributions.js +102 -0
  217. package/dist/task/completionReadiness.js +32 -6
  218. package/dist/task/deliveryGuard.js +16 -16
  219. package/dist/task/draftPlan.js +72 -12
  220. package/dist/task/nextAction.js +144 -128
  221. package/dist/task/remoteDelivery.js +6 -6
  222. package/dist/task/task.js +184 -18
  223. package/dist/task/taskActivation.js +301 -0
  224. package/dist/task/taskActivationService.js +392 -0
  225. package/dist/task/taskRecordReference.js +5 -4
  226. package/dist/task/taskRecordRetirement.js +1 -1
  227. package/dist/telemetry/sqliteTelemetryStore.js +55 -68
  228. package/dist/telemetry/telemetryConfig.js +14 -14
  229. package/dist/telemetry/telemetryWiring.js +2 -2
  230. package/dist/web/assets/assetManifest.js +2 -0
  231. package/dist/web/assets/client/app.js +120 -20
  232. package/dist/web/assets/client/components.js +87 -54
  233. package/dist/web/assets/client/i18n.js +83 -41
  234. package/dist/web/assets/client/markdown.js +1 -1
  235. package/dist/web/assets/client/taskSurface.js +353 -0
  236. package/dist/web/assets/client/view.js +49 -44
  237. package/dist/web/assets/shell.js +1 -1
  238. package/dist/web/assets/styles/cards.js +22 -4
  239. package/dist/web/controllerWeb.js +60 -0
  240. package/dist/web/webMutation.js +28 -0
  241. package/dist/web/webServer.js +118 -8
  242. package/dist/web/webSnapshot.js +81 -74
  243. package/dist/web/webTaskSurface.js +64 -0
  244. package/dist/workItem/dependencyGate.js +1 -1
  245. package/dist/workItem/workItem.js +80 -48
  246. package/dist/workspace/workItemChangeSetManager.js +16 -9
  247. package/docs/agent-result-consumption.md +94 -0
  248. package/docs/agent-runtime-drivers.md +91 -0
  249. package/docs/architecture/README.md +38 -0
  250. package/docs/architecture/capabilities-and-resources.md +79 -0
  251. package/docs/managed-turn-and-session-runtime.md +222 -0
  252. package/docs/observability/README.md +81 -0
  253. package/docs/plugin-sdk.md +290 -0
  254. package/docs/provider-runtime.md +163 -0
  255. package/docs/release-workflow.md +303 -0
  256. package/docs/roles-and-configuration.md +113 -0
  257. package/docs/sqlite-control-plane-design.md +76 -0
  258. package/docs/task-dag-semantics.md +57 -0
  259. package/docs/task-delivery.md +103 -0
  260. package/docs/task-local-identity.md +6 -6
  261. package/docs/testing/verification-levels.md +86 -0
  262. package/i18n/README.zh-CN.md +98 -739
  263. package/package.json +2 -2
  264. package/skills/yui-leader/SKILL.md +130 -103
  265. package/skills/yui-leader/references/integration.md +39 -0
  266. package/skills/yui-leader/references/replicated-execution.md +42 -0
  267. package/skills/yui-leader/references/task-plugins.md +33 -0
  268. package/skills/yui-operator/SKILL.md +30 -59
  269. package/skills/yui-reviewer/SKILL.md +35 -36
  270. package/skills/yui-runtime/SKILL.md +88 -24
  271. package/skills/yui-runtime/references/publication.md +22 -0
  272. package/skills/yui-runtime/references/recovery.md +64 -0
  273. package/skills/yui-worker/SKILL.md +37 -39
  274. package/dist/cli/roleOptionCatalog.js +0 -68
  275. package/dist/context/sourceTurnContext.js +0 -30
  276. package/dist/execution/reviewMainTurn.js +0 -161
  277. package/dist/execution/workItemMainTurn.js +0 -164
  278. package/dist/lifecycle/exactTurnTerminalization.js +0 -407
  279. package/dist/runtime/preallocatedNativeSession.js +0 -13
  280. package/dist/runtime/runtimeStopReceipt.js +0 -42
  281. 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
  }
@@ -506,6 +570,8 @@ export function updateTaskMetadataCommand(store, taskId, patch, options = {}) {
506
570
  tx.saveTask(updated);
507
571
  recordTaskEvent(tx, updated.id, "task.updated", {
508
572
  status: updated.status,
573
+ previous: editedFieldValues(current, Object.keys(patch)),
574
+ current: editedFieldValues(updated, Object.keys(patch)),
509
575
  ...(updated.type === undefined ? {} : { taskType: updated.type })
510
576
  }, now);
511
577
  enqueueWork(tx, taskMailbox(updated.id), "task-updated", now, [taskRef(updated.id)]);
@@ -521,16 +587,17 @@ export function submitOperatorMessage(body, taskId, store, options = {}) {
521
587
  const task = requireTask(tx, taskId);
522
588
  assertTaskOpen(task);
523
589
  const message = appendMessage(tx, task.id, body, "operator", { type: "operator" }, now);
524
- if (task.status === "active") {
590
+ if (leaderWakingTaskStatus(task.status)) {
525
591
  enqueueWork(tx, leaderMailbox(task.id), "operator-input", now, [messageRef(task.id, message.id)]);
526
592
  }
527
593
  return { task, message, created: false };
528
594
  }
529
595
  const created = createTaskAggregate(tx, titleFrom(body), {}, now);
530
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)]);
531
598
  return { ...created, message, created: true };
532
599
  });
533
- 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);
534
601
  return result.created
535
602
  ? `Created Draft task ${result.task.id}: ${result.task.title}\nSubmitted message ${result.message.id}\n`
536
603
  : `Submitted message ${result.message.id} to ${result.task.id}\n`;
@@ -657,7 +724,7 @@ function showTaskCommand(args, store, currentTaskCandidate) {
657
724
  events: events.length,
658
725
  workItems: work.length,
659
726
  currentWorkItems: currentWorkItemCount,
660
- turns: store.listTurns(task.id).length,
727
+ runs: store.listRuns(task.id).length,
661
728
  changeSets: changeSets.length,
662
729
  integrations: integrations.length,
663
730
  publications: publications.length,
@@ -698,7 +765,7 @@ function showTaskCommand(args, store, currentTaskCandidate) {
698
765
  `Events: ${counts.events}`,
699
766
  `Work items: ${counts.workItems}`,
700
767
  `Current work items: ${currentWorkItemCount}`,
701
- `Turns: ${counts.turns}`,
768
+ `AgentRuns: ${counts.runs}`,
702
769
  `ChangeSets: ${counts.changeSets}`,
703
770
  `Integration Attempts: ${counts.integrations}`,
704
771
  `Publication references: ${counts.publications} (${verifiedMergedPublications} verified merged)`,
@@ -721,7 +788,7 @@ function activateTaskCommand(args, store, options) {
721
788
  const task = requireTask(tx, args[0]);
722
789
  if (task.status === "archived")
723
790
  throw usageError(`Task is archived: ${task.id}.`);
724
- if (task.status === "retired")
791
+ if (task.status === "cancelled")
725
792
  throw usageError(`Task is retired: ${task.id}.`);
726
793
  if (task.status === "completed") {
727
794
  throw usageError(`Task ${task.id} is completed; use task reopen before activating it.`);
@@ -734,13 +801,26 @@ function activateTaskCommand(args, store, options) {
734
801
  || activation.task.id !== task.id
735
802
  || activation.task.status !== "active"
736
803
  || !isDeepStrictEqual(activation.task.workspaceIdentity, task.workspaceIdentity)
737
- || activation.path !== task.cwd
738
- || workspace === null
739
- || workspace.owner.type !== "task"
740
- || workspace.owner.taskId !== task.id
741
- || workspace.root !== task.cwd) {
804
+ || activation.path !== task.cwd) {
742
805
  throw usageError(`Task workspace activation proof does not match ${task.id}.`);
743
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
+ }
744
824
  return { task, changed: activation.changed };
745
825
  }
746
826
  throw usageError(`Task ${task.id} activation requires atomic workspace adoption through the CLI preflight.`);
@@ -790,7 +870,7 @@ function completeTaskCommand(args, store, options) {
790
870
  };
791
871
  }
792
872
  // Completion is a Task decision only. The current Provider Turn remains
793
- // responsible for closing its own Turn, and the reusable Session keeps
873
+ // responsible for closing its own AgentRun, and the reusable Session keeps
794
874
  // its independent lifecycle.
795
875
  // Issue 06: re-validate the full completion readiness inside the
796
876
  // transaction (the CAS fence) after final-review preparation. This is the
@@ -803,7 +883,21 @@ function completeTaskCommand(args, store, options) {
803
883
  if (!readiness.ready) {
804
884
  throw usageError(formatCompletionBlockers(task.id, readiness.blockers));
805
885
  }
806
- 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 });
807
901
  tx.saveTask(completed);
808
902
  tx.clearPendingWakeup(task.id);
809
903
  tx.clearLeaderFailure(task.id);
@@ -834,6 +928,10 @@ function completeTaskCommand(args, store, options) {
834
928
  const terminalEvent = recordTaskEvent(tx, task.id, "task.completed", {
835
929
  by: actor,
836
930
  summary,
931
+ ...(completed.completionArtifactRefs === undefined ? {} : {
932
+ artifactRefs: JSON.stringify(completed.completionArtifactRefs)
933
+ }),
934
+ dispatchHistory: JSON.stringify(taskDispatchMailboxes(tx, task.id)),
837
935
  ...(completedProjectHeads === undefined
838
936
  ? {}
839
937
  : { projectHeads: completedProjectHeads }),
@@ -850,7 +948,7 @@ function completeTaskCommand(args, store, options) {
850
948
  // discarded at this lifecycle boundary.
851
949
  tx.removeWorkMailbox(taskMailbox(task.id));
852
950
  // Role mailboxes are also derived wake state. A Worker result or runtime
853
- // 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
854
952
  // completed Task and become actionable after a later explicit reopen.
855
953
  for (const role of roles) {
856
954
  tx.removeWorkMailbox(roleMailbox(task.id, role.name));
@@ -895,19 +993,39 @@ function reopenTaskCommand(args, store, options) {
895
993
  return { task, changed: false };
896
994
  if (task.status === "archived")
897
995
  throw usageError(`Task is archived: ${task.id}.`);
898
- if (task.status !== "completed")
899
- 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.
900
1007
  const active = reopenTask(task, now);
901
1008
  tx.saveTask(active);
902
1009
  const reopenedReason = wakeReason("task-reopened");
903
- 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
+ }
904
1013
  enqueueWork(tx, taskMailbox(task.id), reopenedReason, now, [taskRef(task.id)]);
905
- recordTaskEvent(tx, task.id, "task.reopened", { status: active.status }, now);
906
- 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" };
907
1023
  });
908
1024
  if (result.changed) {
909
1025
  notifyMailbox(options.runtime, taskMailbox(result.task.id), result.task.id);
910
- 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
+ }
911
1029
  }
912
1030
  return result.changed
913
1031
  ? `Reopened task ${result.task.id}\n`
@@ -922,13 +1040,17 @@ function archiveTaskCommand(args, store, options) {
922
1040
  if (task.status === "archived")
923
1041
  return { task, changed: false };
924
1042
  if (task.status !== "completed"
925
- && task.status !== "retired") {
1043
+ && task.status !== "cancelled") {
926
1044
  throw usageError(`Task ${task.id} must be completed or retired before it can be archived.`);
927
1045
  }
928
1046
  const remoteDelivery = request.disposition === "integrated"
929
1047
  ? assertTaskRemoteDeliveryProof(tx, task, options.archiveRemoteDeliveryProof, { forceUnverified: request.forceUnverified })
930
1048
  : undefined;
931
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
+ }
932
1054
  const unresolvedIntegration = tx.listIntegrationAttempts(task.id).find((integration) => (integration.status === "running"
933
1055
  || integration.status === "blocked"
934
1056
  || integration.status === "validating"));
@@ -945,9 +1067,9 @@ function archiveTaskCommand(args, store, options) {
945
1067
  throw usageError(`Task ${task.id} still has managed worktrees; clean them before archiving.`);
946
1068
  }
947
1069
  const activeRole = tx.listRoles(task.id)
948
- .find((role) => tx.getActiveTurn(task.id, role.name) !== null);
1070
+ .find((role) => tx.getActiveRun(task.id, role.name) !== null);
949
1071
  if (activeRole !== undefined) {
950
- 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}; `
951
1073
  + "stop its runtime before archiving.");
952
1074
  }
953
1075
  const liveSessionRole = tx.listRoles(task.id).find((role) => {
@@ -1003,6 +1125,41 @@ function archiveTaskCommand(args, store, options) {
1003
1125
  ? `Archived task ${result.task.id}\n`
1004
1126
  : `Task ${result.task.id} is already archived\n`;
1005
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
+ }
1006
1163
  function retireTaskCommand(args, store, options) {
1007
1164
  const usage = "Task retire usage: yui task retire <task> (--summary <text>|--summary-file <path|->) [--replacement <task>].";
1008
1165
  const parsed = parseTail(args, new Set(["--summary", "--summary-file", "--replacement"]), usage);
@@ -1014,7 +1171,7 @@ function retireTaskCommand(args, store, options) {
1014
1171
  const result = store.transaction((tx) => {
1015
1172
  const task = requireTask(tx, taskId);
1016
1173
  const actor = taskActor(tx, options, task.id);
1017
- if (task.status === "retired") {
1174
+ if (task.status === "cancelled") {
1018
1175
  const same = task.retirementSummary === summary
1019
1176
  && task.replacementTaskId === replacementTaskId;
1020
1177
  if (!same)
@@ -1043,12 +1200,12 @@ function retireTaskCommand(args, store, options) {
1043
1200
  throw usageError(`Task ${task.id} has an active DurableJob: ${activeRetireJob.id}/${activeRetireJob.status}.`);
1044
1201
  }
1045
1202
  assertTaskRetirementProof(tx, task, options.taskRetirementProof);
1046
- for (const run of tx.listTurns(task.id).filter(({ status: runStatus }) => (runStatus === "active"))) {
1047
- const terminal = terminalizeExactTaskTurn(tx, {
1203
+ for (const run of tx.listRuns(task.id).filter(({ status: runStatus }) => (runStatus === "active"))) {
1204
+ const terminal = terminalizeExactTaskRun(tx, {
1048
1205
  taskId: task.id,
1049
1206
  roleName: run.roleName,
1050
1207
  agentId: run.effective.agentId,
1051
- turnId: run.id,
1208
+ runId: run.id,
1052
1209
  mailboxDisposition: "discard",
1053
1210
  outcome: {
1054
1211
  status: "failed",
@@ -1057,11 +1214,11 @@ function retireTaskCommand(args, store, options) {
1057
1214
  }
1058
1215
  }, now);
1059
1216
  if (terminal.disposition !== "applied") {
1060
- 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"}.`);
1061
1218
  }
1062
1219
  }
1063
1220
  for (const item of tx.listWorkItems(task.id)) {
1064
- if (item.status === "completed" || item.status === "retired")
1221
+ if (item.status === "accepted" || item.status === "retired")
1065
1222
  continue;
1066
1223
  tx.saveWorkItem(task.id, updateWorkItemStatus(item, "retired", now, `Task retired: ${summary}`));
1067
1224
  }
@@ -1082,12 +1239,14 @@ function retireTaskCommand(args, store, options) {
1082
1239
  const retired = retireTask(task, {
1083
1240
  by: actor,
1084
1241
  summary,
1242
+ isolated: true,
1085
1243
  ...(replacementTaskId === undefined ? {} : { replacementTaskId })
1086
1244
  }, now);
1087
1245
  tx.saveTask(retired);
1088
1246
  const terminalEvent = recordTaskEvent(tx, task.id, "task.retired", {
1089
1247
  by: actor,
1090
1248
  summary,
1249
+ isolationEstablished: "true",
1091
1250
  ...(replacementTaskId === undefined ? {} : { replacementTaskId })
1092
1251
  }, now);
1093
1252
  enqueueOperatorEvent(tx, terminalEvent, "task-terminal", now);
@@ -1153,10 +1312,13 @@ function formatProjectCommits(projects) {
1153
1312
  export function validateTaskArchiveRequest(args, store, options = {}) {
1154
1313
  const request = parseTaskArchiveArguments(args);
1155
1314
  const task = requireTask(store, request.taskId);
1156
- 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
+ }
1157
1319
  if (task.status !== "archived"
1158
1320
  && task.status !== "completed"
1159
- && task.status !== "retired") {
1321
+ && task.status !== "cancelled") {
1160
1322
  throw usageError(`Task ${task.id} must be completed or retired before it can be archived.`);
1161
1323
  }
1162
1324
  if (task.status !== "archived") {
@@ -1179,9 +1341,55 @@ function reconcileTaskCommand(args, store, options) {
1179
1341
  }
1180
1342
  function taskMessageCommand(args, store, options) {
1181
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
+ }
1182
1390
  if (command === "send") {
1183
- const usage = "Task message send usage: yui task message send <id> (<body>|--body-file <path|->) [--wake-policy leader|none].";
1184
- 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);
1185
1393
  if (parsed.positionals.length < 1 || parsed.positionals.length > 2)
1186
1394
  throw usageError(usage);
1187
1395
  const body = readCommandText(parsed.positionals[1], parsed.options.get("--body-file"), "--body", usage);
@@ -1196,42 +1404,24 @@ function taskMessageCommand(args, store, options) {
1196
1404
  else {
1197
1405
  throw usageError(`--wake-policy must be 'leader' or 'none': ${wakePolicyRaw}.`);
1198
1406
  }
1199
- const now = clock(options);
1200
- const result = store.transaction((tx) => {
1201
- const task = requireTask(tx, parsed.positionals[0]);
1202
- assertTaskOpen(task);
1203
- const actor = taskActor(tx, options, task.id);
1204
- const message = actor === "leader"
1205
- ? appendMessage(tx, task.id, body, "role-result", { type: "role", roleName: LEADER_ROLE }, now)
1206
- : actor === "operator"
1207
- ? appendMessage(tx, task.id, body, "operator", { type: "operator" }, now, { wakePolicy })
1208
- : appendMessage(tx, task.id, body, "user", { type: "user" }, now, { wakePolicy });
1209
- // Issue 05: only `wakePolicy=leader` (the default for backward
1210
- // compatibility) enqueues Leader work. `wakePolicy=none` persists the
1211
- // message as context without waking the Leader.
1212
- if (task.status === "active"
1213
- && actor !== "leader"
1214
- && wakePolicy !== "none") {
1215
- enqueueWork(tx, leaderMailbox(task.id), actor === "operator" ? "operator-input" : "user-message", now, [messageRef(task.id, message.id)], {
1216
- source: actor,
1217
- dedupeKey: `message:${task.id}:${message.id}`
1218
- });
1219
- }
1220
- return { task, message, actor };
1221
- });
1222
- if (result.actor !== "leader") {
1223
- notifyMailbox(options.runtime, result.task.status === "active"
1224
- ? leaderMailbox(result.task.id)
1225
- : taskMailbox(result.task.id), result.task.id);
1226
- }
1227
- 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 });
1228
1418
  }
1229
1419
  if (command === "list") {
1230
1420
  const messageListUsage = "Task message list usage: yui task message list <id> [--after <timestamp>] [--limit <n>].";
1231
1421
  const parsed = parseTail(rest, new Set(["--after", "--limit"]), messageListUsage);
1232
1422
  exactPositionals(parsed.positionals, 1, messageListUsage);
1233
1423
  const task = requireTask(store, parsed.positionals[0]);
1234
- let messages = store.listMessages(task.id);
1424
+ let messages = listContextMessages(store, task.id, options.environment);
1235
1425
  const after = optionalNonEmptyOption(parsed.options, "--after");
1236
1426
  if (after !== undefined) {
1237
1427
  const afterMs = Date.parse(after);
@@ -1279,6 +1469,100 @@ function taskMessageCommand(args, store, options) {
1279
1469
  ? "Task message command is required."
1280
1470
  : `Unknown command: task message ${command}`);
1281
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
+ }
1282
1566
  function updateMessage(args, store, options) {
1283
1567
  const usage = "Task message update usage: yui task message update <task>/<message> (<body>|--body-file <path|->) [--wake-policy leader|none].";
1284
1568
  const parsed = parseTail(args, new Set(["--body-file", "--wake-policy"]), usage);
@@ -1322,9 +1606,14 @@ function updateMessage(args, store, options) {
1322
1606
  updatedBy: actor,
1323
1607
  ...(wakePolicy === undefined ? {} : { wakePolicy })
1324
1608
  }, now);
1325
- 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 };
1326
1614
  });
1327
- options.runtime?.notifyStateChanged(result.task.id);
1615
+ notifyMailbox(options.runtime, result.queuedForLeader
1616
+ ? leaderMailbox(result.task.id) : taskMailbox(result.task.id), result.task.id);
1328
1617
  return output(`Updated Task Message ${result.task.id}/${result.message.id}\n`, {
1329
1618
  message: result.message
1330
1619
  });
@@ -1353,7 +1642,7 @@ function retireMessage(args, store, options) {
1353
1642
  // Remove an isolated pending wake for this exact directive. A merged batch
1354
1643
  // is retained because its other signals remain actionable; context and
1355
1644
  // actionability projections still filter the retired message below.
1356
- if (task.status === "active") {
1645
+ if (leaderWakingTaskStatus(task.status)) {
1357
1646
  try {
1358
1647
  settleExactWorkExecution(tx, leaderMailbox(task.id), messageRef(task.id, message.id));
1359
1648
  }
@@ -1408,9 +1697,9 @@ function taskRoleCommand(args, store, options) {
1408
1697
  if (command === "status")
1409
1698
  return taskRoleStatus(rest, store, options);
1410
1699
  if (command === "show")
1411
- return output(showTaskRole(rest, store));
1700
+ return showTaskRole(rest, store);
1412
1701
  if (command === "update")
1413
- return output(updateTaskRole(rest, store, options));
1702
+ return updateTaskRole(rest, store, options);
1414
1703
  if (command === "remove")
1415
1704
  return output(removeTaskRole(rest, store, options));
1416
1705
  if (command === "bind")
@@ -1435,20 +1724,76 @@ function taskRoleSessionCommand(args, store, options) {
1435
1724
  exactPositionals(rest, 2, "Task Role Session inspect usage: yui task role session inspect <task> <role>.");
1436
1725
  const task = requireTask(store, rest[0]);
1437
1726
  const role = requireRole(store, task.id, rest[1]);
1438
- 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
+ }
1439
1733
  const sessions = store.getTaskRoleSessionSet(task.id, role.name);
1440
1734
  const active = sessions?.sessions[sessions.activeAgentId] ?? null;
1441
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);
1442
1743
  return output(active === null
1443
- ? `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`)
1444
1746
  : [
1445
1747
  `Session ${task.id}/${role.name}`,
1446
- `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)`,
1447
1752
  `Native id: ${active.nativeSessionId}`,
1448
- `Host activation: ${active.runtimeGenerationId ?? "none"}`,
1449
1753
  `Session: ${active.status}${active.endReason === undefined ? "" : `/${active.endReason}`}`,
1450
- `Turn: ${binding?.turn?.status ?? "none"}`
1451
- ].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);
1452
1797
  }
1453
1798
  if (command === "stop") {
1454
1799
  const usage = "Task Role Session stop usage: yui task role session stop <task> <role> --reason <text>.";
@@ -1458,36 +1803,26 @@ function taskRoleSessionCommand(args, store, options) {
1458
1803
  const now = clock(options);
1459
1804
  const request = store.transaction((tx) => {
1460
1805
  const task = requireTask(tx, parsed.positionals[0]);
1461
- if (task.status !== "active" && task.status !== "completed") {
1462
- 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);
1463
1808
  }
1464
1809
  const actor = taskActor(tx, options, task.id);
1465
1810
  const role = requireRole(tx, task.id, parsed.positionals[1]);
1466
1811
  if (actor === "leader" && role.name === LEADER_ROLE) {
1467
- throw usageError("A Leader cannot stop the Session executing its own current command.", usage);
1468
- }
1469
- if (tx.getActiveTurn(task.id, role.name) !== null) {
1470
- 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);
1471
1813
  }
1472
1814
  const sessions = tx.getTaskRoleSessionSet(task.id, role.name);
1473
- const session = sessions?.sessions[sessions.activeAgentId];
1474
- if (session === undefined || session.status === "ended") {
1815
+ const session = taskRoleControlTarget(sessions);
1816
+ if (session === undefined || session.adapterId === undefined) {
1475
1817
  throw usageError(`Task Role has no active Session: ${task.id}/${role.name}.`, usage);
1476
1818
  }
1477
- const lifecycle = tx.getWorkMailbox(runtimeLifecycleTarget({
1478
- scope: "task",
1479
- taskId: task.id,
1480
- roleName: role.name
1481
- }));
1482
- if (lifecycle !== null && workMailboxHasWork(lifecycle)) {
1483
- throw usageError(`Task Role Session lifecycle is busy: ${task.id}/${role.name}.`, usage);
1484
- }
1819
+ // A repeated stop is a recovery request, not contention with its own
1820
+ // earlier cleanup obligation.
1485
1821
  recordTaskEvent(tx, task.id, "runtime.session-stop-requested", {
1486
1822
  roleName: role.name,
1487
1823
  agentId: session.agentId,
1488
1824
  adapterId: session.adapterId,
1489
1825
  nativeSessionId: session.nativeSessionId,
1490
- runtimeGenerationId: session.runtimeGenerationId ?? "",
1491
1826
  reason,
1492
1827
  requestedBy: actor
1493
1828
  }, now);
@@ -1497,7 +1832,6 @@ function taskRoleSessionCommand(args, store, options) {
1497
1832
  agentId: session.agentId,
1498
1833
  adapterId: session.adapterId,
1499
1834
  nativeSessionId: session.nativeSessionId,
1500
- ...(session.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: session.runtimeGenerationId }),
1501
1835
  sessionUpdatedAt: session.updatedAt
1502
1836
  };
1503
1837
  });
@@ -1615,7 +1949,7 @@ function listTaskRoles(args, store, options) {
1615
1949
  status.health,
1616
1950
  taskRoleOpenInputLabel(status),
1617
1951
  taskRoleActiveWorkLabel(status),
1618
- taskRoleLastTurnLabel(status),
1952
+ taskRoleLastRunLabel(status),
1619
1953
  taskRoleNativeSessionLabel(status),
1620
1954
  taskRoleTmuxLabel(status)
1621
1955
  ]), defaultTableWidth())}\n`, { roles: statuses });
@@ -1633,9 +1967,15 @@ function showTaskRole(args, store) {
1633
1967
  exactPositionals(args, 2, "Task role show usage: yui task role show <task> <role>.");
1634
1968
  const task = requireTask(store, args[0]);
1635
1969
  const role = requireRole(store, task.id, args[1]);
1636
- 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, {
1637
1972
  kind: "task",
1638
- 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 }))
1639
1979
  });
1640
1980
  }
1641
1981
  function updateTaskRole(args, store, options) {
@@ -1646,8 +1986,11 @@ function updateTaskRole(args, store, options) {
1646
1986
  }
1647
1987
  const parsed = parseRoleOptions(tail, new Map([
1648
1988
  ...roleOptionSpecs({ update: true, includeAgent: true }),
1649
- ["--profile", "value"]
1989
+ ["--profile", "value"],
1990
+ ["--environment", "value"],
1991
+ ["--managed-environment", "flag"]
1650
1992
  ]), usage);
1993
+ validateTaskRoleEnvironmentOptions(parsed, usage);
1651
1994
  if (parsed.has("--agent") && (parsed.one("--agent")?.trim().length ?? 0) === 0) {
1652
1995
  throw usageError("--agent is required.", usage);
1653
1996
  }
@@ -1660,7 +2003,9 @@ function updateTaskRole(args, store, options) {
1660
2003
  assertTaskOpen(task);
1661
2004
  taskActor(tx, options, task.id);
1662
2005
  const role = requireRole(tx, task.id, roleName);
1663
- 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;
1664
2009
  const changesAgentConfig = hasAgentConfigOptions(parsed);
1665
2010
  if (changesLaunchContext || changesAgentConfig) {
1666
2011
  assertRoleRuntimeMutationAllowed(tx, {
@@ -1668,13 +2013,6 @@ function updateTaskRole(args, store, options) {
1668
2013
  taskId: task.id,
1669
2014
  roleName: role.name
1670
2015
  }, "desired launch configuration update");
1671
- assertLiveRoleSessionAcknowledged({
1672
- sessions: tx.getTaskRoleSessionSet(task.id, role.name),
1673
- roleName: role.name,
1674
- desiredRevision: role.launchRevision,
1675
- acknowledged: parsed.has(LIVE_SESSION_ACKNOWLEDGEMENT_OPTION),
1676
- stopCommand: `yui task role session stop ${task.id} ${role.name} --reason "<decision>"`
1677
- });
1678
2016
  }
1679
2017
  const profileId = parsed.one("--profile");
1680
2018
  const agentProfile = profileId === undefined
@@ -1692,7 +2030,7 @@ function updateTaskRole(args, store, options) {
1692
2030
  [bindingUpdate.agentId]: bindingUpdate.binding
1693
2031
  }
1694
2032
  }, now);
1695
- const next = updateRole(withBinding, {
2033
+ let next = updateRole(withBinding, {
1696
2034
  ...roleProfilePatch(parsed)
1697
2035
  }, now);
1698
2036
  if (bindingUpdate !== undefined) {
@@ -1701,16 +2039,32 @@ function updateTaskRole(args, store, options) {
1701
2039
  if (changesLaunchContext) {
1702
2040
  validateConfiguredRoleSkills(options.yuiHome, next.skills ?? []);
1703
2041
  }
1704
- tx.saveRole(task.id, next);
1705
- enqueueWork(tx, taskMailbox(task.id), "role-updated", now, [taskRef(task.id)]);
1706
- 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
+ });
1707
2052
  return next;
1708
2053
  });
1709
2054
  notifyMailbox(options.runtime, taskMailbox(updated.taskId), updated.taskId);
1710
- 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, {
1711
2057
  kind: "task",
1712
- sessions: store.getTaskRoleSessionSet(updated.taskId, updated.name)
1713
- });
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
+ }
1714
2068
  }
1715
2069
  function removeTaskRole(args, store, options) {
1716
2070
  exactPositionals(args, 2, "Task role remove usage: yui task role remove <task> <role>.");
@@ -1727,8 +2081,8 @@ function removeTaskRole(args, store, options) {
1727
2081
  taskId: task.id,
1728
2082
  roleName: role.name
1729
2083
  }, "removal");
1730
- if (tx.getActiveTurn(task.id, role.name) !== null) {
1731
- 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}.`);
1732
2086
  }
1733
2087
  const sessions = tx.getTaskRoleSessionSet(task.id, role.name);
1734
2088
  if (Object.values(sessions?.sessions ?? {}).some(({ status }) => status === "active")) {
@@ -1757,7 +2111,7 @@ function bindTaskRole(args, store, options) {
1757
2111
  }, "desired Agent binding update");
1758
2112
  const agent = requireAgent(tx, args[2]);
1759
2113
  const binding = role.agentBindings[agent.id]
1760
- ?? createRoleAgentBinding({ id: agent.id, adapterId: agent.adapterId });
2114
+ ?? createRoleAgentBinding(agent);
1761
2115
  const bound = updateRole(role, {
1762
2116
  agentBindings: { ...role.agentBindings, [agent.id]: binding }
1763
2117
  }, now);
@@ -1777,7 +2131,7 @@ function bindTaskRole(args, store, options) {
1777
2131
  const switched = (() => {
1778
2132
  try {
1779
2133
  return switchActiveRoleAgent(bound, existing, agent.id, {
1780
- activeTurn: tx.getActiveTurn(task.id, role.name) !== null,
2134
+ activeRun: tx.getActiveRun(task.id, role.name) !== null,
1781
2135
  nativeProcessRunning: currentSession !== undefined
1782
2136
  && currentSession.status === "active"
1783
2137
  }, now);
@@ -1790,6 +2144,10 @@ function bindTaskRole(args, store, options) {
1790
2144
  recordTaskEvent(tx, task.id, "role.agent-bound", {
1791
2145
  role: switched.role.name,
1792
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 } : {}),
1793
2151
  ...roleLaunchEventPayload(switched.role, switched.sessions)
1794
2152
  }, now);
1795
2153
  return { role: switched.role, mode: switched.mode };
@@ -1859,20 +2217,13 @@ function transferTaskRoleAuthority(args, store, options, action) {
1859
2217
  const sessions = tx.getTaskRoleSessionSet(task.id, role.name);
1860
2218
  const session = sessions?.sessions[role.activeAgentId];
1861
2219
  const binding = sessions?.providerBinding;
1862
- if (sessions === null || sessions === undefined || session === undefined
1863
- || binding === null || binding === undefined
1864
- || session.runtimeGenerationId === undefined
1865
- || session.status === "ended") {
2220
+ if (sessions === null || sessions === undefined || session === undefined || binding === null || binding === undefined || session.status === "ended") {
1866
2221
  throw new Error(`Task Role has no live managed Provider: ${task.id}/${role.name}.`);
1867
2222
  }
1868
- const activation = currentProviderActivation(binding);
1869
- if (activation === null) {
1870
- throw new Error(`Provider Activation is not live: ${task.id}/${role.name}.`);
1871
- }
1872
2223
  if (action === "takeover") {
1873
- const activeTurn = tx.getActiveTurn(task.id, role.name);
1874
- if (activeTurn === null) {
1875
- 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}.`);
1876
2227
  }
1877
2228
  }
1878
2229
  if (action === "takeover"
@@ -1893,7 +2244,7 @@ function transferTaskRoleAuthority(args, store, options, action) {
1893
2244
  expectedEpoch: binding.authority.epoch,
1894
2245
  expectedOwner: binding.authority.owner,
1895
2246
  owner: desiredOwner,
1896
- holderId: action === "takeover" ? `human:${randomUUID()}` : activation.activationId,
2247
+ holderId: action === "takeover" ? `human:${randomUUID()}` : "controller",
1897
2248
  changedAt: now.toISOString()
1898
2249
  });
1899
2250
  const authority = updatedBinding.authority;
@@ -1914,7 +2265,6 @@ function transferTaskRoleAuthority(args, store, options, action) {
1914
2265
  action,
1915
2266
  taskId: task.id,
1916
2267
  roleName: role.name,
1917
- runtimeGenerationId: session.runtimeGenerationId,
1918
2268
  nativeSessionId: session.nativeSessionId,
1919
2269
  authority: {
1920
2270
  epoch: authority.epoch,
@@ -1947,6 +2297,8 @@ function taskWorkCommand(args, store, options) {
1947
2297
  return output(updateWorkScope(rest, store, options));
1948
2298
  if (command === "dispatch")
1949
2299
  return output(dispatchWork(rest, store, options));
2300
+ if (command === "synthesize")
2301
+ return synthesizeRuns(rest, "workItem", store, options);
1950
2302
  if (command === "review") {
1951
2303
  return rest[0] === "retry"
1952
2304
  ? retryFailedTaskReviewRound(rest.slice(1), store, options)
@@ -1986,14 +2338,10 @@ function editWork(args, store, options) {
1986
2338
  const result = store.transaction((tx) => {
1987
2339
  const item = requireWorkItem(tx, parsed.positionals[0], options);
1988
2340
  const task = requireTask(tx, item.taskId);
1989
- if (task.status !== "draft") {
1990
- throw usageError(`Work Item definition edit is Draft-only: ${task.id}/${task.status}.`);
1991
- }
1992
- assertDraftTaskExecutionFree(tx, task);
2341
+ assertTaskOpen(task);
2342
+ if (task.status === "draft")
2343
+ assertDraftTaskExecutionFree(tx, task);
1993
2344
  const actor = taskActor(tx, options, task.id);
1994
- if (actor !== "user" && actor !== "operator") {
1995
- throw usageError("Only the user or Operator may edit a Draft Work Item.");
1996
- }
1997
2345
  if (item.status === "retired") {
1998
2346
  throw usageError(`Work Item is retired: ${item.id}.`);
1999
2347
  }
@@ -2014,7 +2362,7 @@ function editWork(args, store, options) {
2014
2362
  : parsed.options.has("--clear-role") ? null : undefined;
2015
2363
  if (typeof assignee === "string")
2016
2364
  requireRole(tx, task.id, assignee);
2017
- const updated = editDraftWorkItemDefinition(item, {
2365
+ const updated = editWorkItemDefinition(item, {
2018
2366
  ...(parsed.options.has("--title")
2019
2367
  ? { title: requiredOption(parsed.options, "--title") }
2020
2368
  : {}),
@@ -2031,7 +2379,21 @@ function editWork(args, store, options) {
2031
2379
  ...(baseRefs === undefined ? {} : { baseRefs }),
2032
2380
  ...(assignee === undefined ? {} : { assignee })
2033
2381
  }, now);
2034
- 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
+ }
2035
2397
  tx.saveWorkItem(task.id, updated);
2036
2398
  const changedFields = [
2037
2399
  parsed.options.has("--title") ? "title" : undefined,
@@ -2051,8 +2413,13 @@ function editWork(args, store, options) {
2051
2413
  workItemId: updated.id,
2052
2414
  revision: String(updated.revision),
2053
2415
  fields: changedFields.join(","),
2416
+ previous: editedFieldValues(item, changedFields),
2417
+ current: editedFieldValues(updated, changedFields),
2054
2418
  editedBy: actor
2055
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
+ }
2056
2423
  return { task, item: updated };
2057
2424
  });
2058
2425
  options.runtime?.notifyStateChanged(result.task.id);
@@ -2129,8 +2496,8 @@ function updateWorkScope(args, store, options) {
2129
2496
  const item = requireWorkItem(tx, parsed.positionals[0], options);
2130
2497
  const task = requireTask(tx, item.taskId);
2131
2498
  taskActor(tx, options, task.id);
2132
- if (tx.getActiveTurn(task.id, item.assignee ?? "") !== null) {
2133
- 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}.`);
2134
2501
  }
2135
2502
  const requestedProjectIds = (parsed.multiOptions.get("--project") ?? []).map((reference) => {
2136
2503
  const project = resolveProject(task.projectBindings.map(({ projectId }) => requireProject(tx, projectId)), reference);
@@ -2160,12 +2527,16 @@ function updateWorkScope(args, store, options) {
2160
2527
  return `${updated.changed ? "Updated" : "Unchanged"} Work Item Project scope ${updated.item.id}: ${updated.item.writeProjectIds.join(", ") || "read-only"}\n`;
2161
2528
  }
2162
2529
  function updateWork(args, store, options) {
2163
- const usage = "Task work update usage: yui task work update <task>/<work> <todo|running|done|failed> [--summary <text>].";
2164
- 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);
2165
2532
  exactPositionals(parsed.positionals, 2, usage);
2166
2533
  const requested = parsed.positionals[1];
2167
2534
  const status = parseWorkStatus(requested);
2168
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
+ }
2169
2540
  if (["completed", "failed"].includes(status)
2170
2541
  && summary === undefined) {
2171
2542
  throw usageError(`--summary is required when work becomes ${requested}.`);
@@ -2175,19 +2546,16 @@ function updateWork(args, store, options) {
2175
2546
  const current = requireWorkItem(tx, parsed.positionals[0], options);
2176
2547
  const task = requireTask(tx, current.taskId);
2177
2548
  assertTaskOpen(task);
2549
+ const artifactRefs = artifactIds.length === 0 ? undefined : fixedArtifactRefs(tx, task.id, artifactIds);
2178
2550
  if (current.assignee === undefined) {
2179
2551
  taskActor(tx, options, task.id);
2180
2552
  if (status === "running") {
2181
2553
  assertWorkItemDependenciesCompletedForCommand(tx, current);
2182
2554
  }
2183
- if (status === "completed" && current.status === "awaiting_acceptance") {
2184
- throw usageError(`Work Item ${current.id} is awaiting acceptance; use task work accept `
2185
- + "after the required ReviewRound and Integration evidence.");
2186
- }
2187
2555
  const configuredReview = status === "completed" && !isTerminalWorkItemStatus(current.status)
2188
2556
  ? tx.getReviewConfig()
2189
2557
  : null;
2190
- const taskFinalContract = status === "completed" && current.status === "running"
2558
+ const taskFinalContract = status === "completed" && current.status === "open"
2191
2559
  ? taskFinalReviewContractForMutation(tx, task.id, options)
2192
2560
  : undefined;
2193
2561
  const candidatePolicy = taskFinalContract === undefined
@@ -2197,9 +2565,7 @@ function updateWork(args, store, options) {
2197
2565
  && current.writeProjectIds.length > 0;
2198
2566
  const metadataOnlyTaskFinalDelivery = taskFinalContract !== undefined
2199
2567
  && current.assignee === undefined;
2200
- const candidateRequired = status === "completed"
2201
- && current.status === "running"
2202
- && (projectDelivery || candidatePolicy !== null);
2568
+ const candidateRequired = status === "completed" && current.status === "open";
2203
2569
  const developWorkspace = tx.getWorkItemWorkspace(task.id, current.id);
2204
2570
  if (status === "completed"
2205
2571
  && projectDelivery
@@ -2211,6 +2577,7 @@ function updateWork(args, store, options) {
2211
2577
  ? submitWorkItemCandidate(current, {
2212
2578
  summary: summary,
2213
2579
  source: { type: "direct" },
2580
+ ...(artifactRefs === undefined ? {} : { artifactRefs }),
2214
2581
  ...(candidatePolicy === null ? {} : { reviewPolicy: candidatePolicy }),
2215
2582
  ...(taskFinalContract === undefined
2216
2583
  ? {}
@@ -2225,9 +2592,7 @@ function updateWork(args, store, options) {
2225
2592
  ? {}
2226
2593
  : { taskMainSnapshot: options.directTaskMainSnapshot })
2227
2594
  }, now)
2228
- : current.status === "failed" && status === "running"
2229
- ? retryFailedWorkItem(current, now)
2230
- : updateWorkItemStatus(current, status, now, isTerminalWorkItemStatus(status) ? summary : undefined);
2595
+ : updateWorkItemStatus(current, "open", now);
2231
2596
  tx.saveWorkItem(task.id, updated);
2232
2597
  if (summary !== undefined) {
2233
2598
  recordTaskEvent(tx, task.id, "work.updated", {
@@ -2250,29 +2615,29 @@ function updateWork(args, store, options) {
2250
2615
  };
2251
2616
  }
2252
2617
  taskActor(tx, options, task.id);
2253
- if (status !== "completed" || current.status !== "running") {
2254
- throw usageError(`Assigned Work Item ${current.id} can only submit a completed direct Turn from running; `
2255
- + "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.");
2256
2621
  }
2257
- if (tx.getActiveTurn(task.id, current.assignee) !== null) {
2258
- 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}.`);
2259
2624
  }
2260
2625
  const sourceGroup = currentWorkItemExecutionGroup(current);
2261
- const mainTurn = tx.listTurns(task.id).filter((turn) => (turn.purpose === "execution"
2262
- && turn.workItemId === current.id
2263
- && turn.roleName === current.assignee
2264
- && turn.executionGroupId === undefined
2265
- && turn.executionLaneId === undefined
2266
- && turn.sourceExecutionGroupId === sourceGroup?.id
2267
- && turn.status === "completed"
2268
- && turn.result !== undefined)).at(-1);
2269
- 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) {
2270
2635
  throw usageError(sourceGroup === undefined
2271
- ? `Work Item ${current.id} has no completed direct main Turn.`
2272
- : `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}.`);
2273
2638
  }
2274
- if (mainTurn.result === undefined) {
2275
- 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}.`);
2276
2641
  }
2277
2642
  const configuredReview = tx.getReviewConfig();
2278
2643
  const taskFinalContract = taskFinalReviewContractForMutation(tx, task.id, options);
@@ -2288,8 +2653,9 @@ function updateWork(args, store, options) {
2288
2653
  throw usageError(`Project-backed Work Item ${current.id} must be isolated before Candidate submission.`);
2289
2654
  }
2290
2655
  const updated = submitWorkItemCandidate(current, {
2291
- summary: `Result from Turn ${mainTurn.id}.`,
2292
- source: { type: "turn", turnId: mainTurn.id },
2656
+ summary: `Result from AgentRun ${mainRun.id}.`,
2657
+ source: { type: "run", runId: mainRun.id },
2658
+ ...(artifactRefs === undefined ? {} : { artifactRefs }),
2293
2659
  ...(candidatePolicy === null ? {} : { reviewPolicy: candidatePolicy }),
2294
2660
  ...(taskFinalContract === undefined
2295
2661
  ? {}
@@ -2307,12 +2673,12 @@ function updateWork(args, store, options) {
2307
2673
  workItemId: updated.id,
2308
2674
  status: updated.status,
2309
2675
  summary: summary,
2310
- turnId: mainTurn.id,
2676
+ runId: mainRun.id,
2311
2677
  ...leaderActionEventPayload(tx, task.id, options)
2312
2678
  }, now);
2313
2679
  enqueueWork(tx, taskMailbox(task.id), "work-updated", now, [
2314
2680
  workItemRef(task.id, updated.id),
2315
- turnRef(task.id, mainTurn.id)
2681
+ runRef(task.id, mainRun.id)
2316
2682
  ]);
2317
2683
  const reviewDispatch = candidatePolicy?.trigger === "always"
2318
2684
  ? queueReviewRound(tx, updated, candidatePolicy, "policy", now)
@@ -2324,7 +2690,7 @@ function updateWork(args, store, options) {
2324
2690
  };
2325
2691
  });
2326
2692
  notifyMailbox(options.runtime, taskMailbox(result.item.taskId), result.item.taskId);
2327
- if (result.item.status === "awaiting_acceptance" && status === "completed") {
2693
+ if ((result.item.status === "open" && result.item.candidates.length > 0) && status === "completed") {
2328
2694
  const failure = result.reviewDispatch?.round.status === "failed"
2329
2695
  ? `Review could not start: ${result.reviewDispatch.round.failure?.message ?? result.reviewDispatch.round.id}\n`
2330
2696
  : "";
@@ -2359,13 +2725,13 @@ function dispatchWork(args, store, options) {
2359
2725
  throw usageError(`Work Item has no Task Role assignee: ${item.id}. `
2360
2726
  + `The Task Leader must run "yui task work update ${item.id} running" and execute it directly.`);
2361
2727
  }
2362
- 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)}`);
2363
2729
  const currentGroup = currentWorkItemExecutionGroup(item);
2364
- if (item.status !== "pending" && item.status !== "failed") {
2730
+ if (item.status !== "open") {
2365
2731
  throw usageError(`Work item ${item.id} cannot be dispatched from ${item.status}.`);
2366
2732
  }
2367
2733
  if (currentGroup !== undefined && !workItemExecutionGroupSettled(currentGroup)) {
2368
- 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.`);
2369
2735
  }
2370
2736
  assertWorkItemDependenciesCompletedForCommand(tx, item);
2371
2737
  const leaderOwned = item.assignee === "leader";
@@ -2389,18 +2755,16 @@ function dispatchWork(args, store, options) {
2389
2755
  }
2390
2756
  const roles = lanePlan.roles.map((name) => requireRole(tx, task.id, name));
2391
2757
  for (const role of roles) {
2392
- if (tx.getActiveTurn(task.id, role.name) !== null) {
2393
- 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.`);
2394
2760
  }
2395
2761
  }
2396
2762
  const rawInput = trimmed(parsed.options.get("--input")) ?? item.objective;
2397
- let workItemForDispatch = item.status === "failed"
2398
- ? retryFailedWorkItem(item, now)
2399
- : item;
2763
+ let workItemForDispatch = prepareWorkItemDispatch(item, now);
2400
2764
  if (lanePlan.roles.length === 0) {
2401
2765
  const role = requireRole(tx, task.id, item.assignee);
2402
- if (tx.getActiveTurn(task.id, role.name) !== null) {
2403
- 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.`);
2404
2768
  }
2405
2769
  const effective = resolveEffectiveLaunch({
2406
2770
  role,
@@ -2408,8 +2772,8 @@ function dispatchWork(args, store, options) {
2408
2772
  workspace,
2409
2773
  workItemWriteProjectIds: item.writeProjectIds
2410
2774
  });
2411
- const turnId = tx.nextTurnId(task.id);
2412
- 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({
2413
2777
  source: { type: "yui", channel: "workitem-dispatch" },
2414
2778
  directive: rawInput,
2415
2779
  deltaRefIds: []
@@ -2418,30 +2782,30 @@ function dispatchWork(args, store, options) {
2418
2782
  workspace,
2419
2783
  effective
2420
2784
  });
2421
- const snapshot = freezeTurnContextSnapshot(tx, {
2785
+ const snapshot = freezeRunContextSnapshot(tx, {
2422
2786
  taskId: task.id,
2423
- roleName: turn.roleName,
2787
+ roleName: run.roleName,
2424
2788
  purpose: "execution",
2425
2789
  workItemId: item.id
2426
2790
  }, now, "controller");
2427
- const withContext = withTurnContextSnapshot(turn, contextSnapshotRef(snapshot), contextSnapshotDeltaRefIds(tx, snapshot));
2428
- if (workItemForDispatch.status !== "running") {
2429
- 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);
2430
2794
  }
2431
2795
  tx.saveWorkItem(task.id, workItemForDispatch);
2432
- tx.saveTurn(withContext);
2433
- tx.saveActiveTurn(withContext);
2434
- enqueueRoleTurnDispatch(tx, {
2796
+ tx.saveRun(withContext);
2797
+ tx.saveActiveRun(withContext);
2798
+ enqueueRoleRunDispatch(tx, {
2435
2799
  taskId: task.id,
2436
2800
  roleName: role.name,
2437
- turnId: withContext.id,
2801
+ runId: withContext.id,
2438
2802
  reason: "turn-dispatched",
2439
2803
  occurredAt: now
2440
2804
  });
2441
- recordTaskEvent(tx, task.id, "turn.dispatched", turnLaunchEventPayload(withContext), now);
2442
- return { kind: "direct", turns: [withContext] };
2805
+ recordTaskEvent(tx, task.id, "run.dispatched", runLaunchEventPayload(withContext), now);
2806
+ return { kind: "direct", runs: [withContext] };
2443
2807
  }
2444
- const groupId = `execution-group-${tx.peekNextTurnId(task.id)}`;
2808
+ const groupId = `execution-group-${tx.peekNextRunId(task.id)}`;
2445
2809
  if (workItemForDispatch !== item) {
2446
2810
  // Freeze the Assignment against the retried WorkItem revision. The
2447
2811
  // aggregate transaction rolls this back if a later precondition fails.
@@ -2470,7 +2834,7 @@ function dispatchWork(args, store, options) {
2470
2834
  laneId,
2471
2835
  managedWorkspace: laneWorkspace,
2472
2836
  effective,
2473
- turnId: tx.nextTurnId(task.id),
2837
+ runId: tx.nextRunId(task.id),
2474
2838
  dispatchMode: roleAgentSessionResumeMode(tx.getTaskRoleSessionSet(task.id, role.name), effective.agentId, effective)
2475
2839
  };
2476
2840
  });
@@ -2503,15 +2867,15 @@ function dispatchWork(args, store, options) {
2503
2867
  root: plan.managedWorkspace.root,
2504
2868
  writableProjectIds: [...item.writeProjectIds]
2505
2869
  },
2506
- currentTurnId: plan.turnId
2870
+ currentRunId: plan.runId
2507
2871
  })), now);
2508
2872
  workItemForDispatch = attachWorkItemExecutionGroup(workItemForDispatch, group, now);
2509
- if (workItemForDispatch.status !== "running") {
2510
- workItemForDispatch = updateWorkItemStatus(workItemForDispatch, "running", now);
2873
+ if (workItemForDispatch.status !== "open") {
2874
+ workItemForDispatch = updateWorkItemStatus(workItemForDispatch, "open", now);
2511
2875
  }
2512
2876
  tx.saveWorkItem(task.id, workItemForDispatch);
2513
- const turns = plans.map((plan, index) => {
2514
- 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({
2515
2879
  source: { type: "yui", channel: "workitem-dispatch" },
2516
2880
  directive: assignment.input,
2517
2881
  deltaRefIds: []
@@ -2522,37 +2886,37 @@ function dispatchWork(args, store, options) {
2522
2886
  workspace: plan.managedWorkspace,
2523
2887
  effective: plan.effective
2524
2888
  });
2525
- const snapshot = freezeTurnContextSnapshot(tx, {
2889
+ const snapshot = freezeRunContextSnapshot(tx, {
2526
2890
  taskId: task.id,
2527
- roleName: turn.roleName,
2891
+ roleName: run.roleName,
2528
2892
  purpose: "execution",
2529
2893
  workItemId: item.id
2530
2894
  }, now, "controller", assignment.contextSnapshotRef);
2531
- const withContext = withTurnContextSnapshot(turn, contextSnapshotRef(snapshot), contextSnapshotDeltaRefIds(tx, snapshot));
2895
+ const withContext = withRunContextSnapshot(run, contextSnapshotRef(snapshot), contextSnapshotDeltaRefIds(tx, snapshot));
2532
2896
  const prepared = options.executionLaneWorkspaces?.get(group.lanes[index].id);
2533
2897
  if (prepared !== undefined && tx.getManagedWorkspace(prepared.owner) === null) {
2534
2898
  tx.saveManagedWorkspace(prepared);
2535
2899
  }
2536
- tx.saveTurn(withContext);
2537
- tx.saveActiveTurn(withContext);
2538
- enqueueRoleTurnDispatch(tx, {
2900
+ tx.saveRun(withContext);
2901
+ tx.saveActiveRun(withContext);
2902
+ enqueueRoleRunDispatch(tx, {
2539
2903
  taskId: task.id,
2540
2904
  roleName: plan.role.name,
2541
- turnId: withContext.id,
2905
+ runId: withContext.id,
2542
2906
  reason: "turn-dispatched",
2543
2907
  occurredAt: now
2544
2908
  });
2545
- recordTaskEvent(tx, task.id, "turn.dispatched", turnLaunchEventPayload(withContext), now);
2909
+ recordTaskEvent(tx, task.id, "run.dispatched", runLaunchEventPayload(withContext), now);
2546
2910
  return withContext;
2547
2911
  });
2548
- return { kind: "replicated", turns };
2912
+ return { kind: "replicated", runs };
2549
2913
  });
2550
- for (const turn of dispatch.turns) {
2551
- 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);
2552
2916
  }
2553
2917
  return dispatch.kind === "direct"
2554
- ? `Direct WorkItem Turn queued as ${dispatch.turns[0].id}\n`
2555
- : `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`;
2556
2920
  }
2557
2921
  function replicatedProducerAssignmentInput(input, requiresCodeRef) {
2558
2922
  return [
@@ -2568,7 +2932,7 @@ function replicatedProducerAssignmentInput(input, requiresCodeRef) {
2568
2932
  }
2569
2933
  function acceptWork(args, store, options) {
2570
2934
  const usage = "Task work accept usage: yui task work accept <task>/<work> --summary <text>.";
2571
- const parsed = parseTail(args, new Set(["--summary"]), usage);
2935
+ const parsed = parseTail(args, new Set(["--summary", "--candidate"]), usage);
2572
2936
  exactPositionals(parsed.positionals, 1, usage);
2573
2937
  const summary = requiredOption(parsed.options, "--summary");
2574
2938
  const now = clock(options);
@@ -2579,17 +2943,20 @@ function acceptWork(args, store, options) {
2579
2943
  throw usageError(`Task is not active: ${task.id}/${task.status}.`);
2580
2944
  }
2581
2945
  const actor = taskActor(tx, options, task.id);
2582
- if (item.status !== "awaiting_acceptance") {
2946
+ if ((item.status !== "open" || item.candidates.length === 0)) {
2583
2947
  throw usageError(`Work Item is not awaiting acceptance: ${item.id}/${item.status}.`);
2584
2948
  }
2585
2949
  if (options.workItemIntegrationProof?.workspace.owner.type === "review-round") {
2586
2950
  throw usageError("A ReviewRound-owned workspace cannot be used for WorkItem acceptance.");
2587
2951
  }
2588
- 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.");
2589
2959
  const taskFinalContract = taskFinalReviewContractForMutation(tx, task.id, options);
2590
- if (!sameTaskFinalReviewContract(candidate.taskFinalReviewContract, taskFinalContract)) {
2591
- throw usageError(`Task final-review contract does not match Candidate ${candidate.id}.`);
2592
- }
2593
2960
  const latestReview = reviewRoundsByIdentity(tx.listReviewRounds(item.taskId)
2594
2961
  .filter((round) => round.workItemId === item.id
2595
2962
  && round.candidateId === candidate.id)).at(-1);
@@ -2617,17 +2984,25 @@ function acceptWork(args, store, options) {
2617
2984
  && isolatedWorkspace.owner.workItemId === item.id
2618
2985
  && isolatedWorkspace.entries.some(({ access }) => access === "write")) {
2619
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
+ }
2620
2994
  }
2621
- const completed = updateWorkItemStatus(item, "completed", now, summary);
2995
+ const completed = updateWorkItemStatus(item, "accepted", now, summary, candidate.id);
2622
2996
  tx.saveWorkItem(item.taskId, completed);
2623
2997
  recordTaskEvent(tx, item.taskId, "work.accepted", {
2624
2998
  workItemId: item.id,
2625
2999
  candidateId: candidate.id,
2626
- ...(candidate.source.type === "turn"
2627
- ? { turnId: candidate.source.turnId }
3000
+ ...(candidate.source.type === "run"
3001
+ ? { runId: candidate.source.runId }
2628
3002
  : { workItemRevision: String(candidate.workItemRevision) }),
2629
3003
  acceptedBy: actor,
2630
3004
  summary,
3005
+ ...(latestReview === undefined ? {} : { reviewRoundId: latestReview.id }),
2631
3006
  ...(actor === "leader" ? leaderActionEventPayload(tx, item.taskId, options) : {})
2632
3007
  }, now);
2633
3008
  return completed;
@@ -2673,7 +3048,7 @@ function rejectWork(args, store, options) {
2673
3048
  throw usageError(`Task is not active: ${task.id}/${task.status}.`);
2674
3049
  }
2675
3050
  const actor = taskActor(tx, options, task.id);
2676
- if (item.status !== "awaiting_acceptance") {
3051
+ if (item.status === "retired" || item.candidates.length === 0) {
2677
3052
  throw usageError(`Work Item is not awaiting acceptance: ${item.id}/${item.status}.`);
2678
3053
  }
2679
3054
  const candidate = requireWorkItemCandidate(item);
@@ -2681,7 +3056,7 @@ function rejectWork(args, store, options) {
2681
3056
  if (activeReview !== undefined) {
2682
3057
  throw usageError(`ReviewRound is still active: ${activeReview.id}/${activeReview.status}.`);
2683
3058
  }
2684
- const failed = updateWorkItemStatus(item, "failed", now, summary);
3059
+ const { currentCandidateId: _declinedCandidate, ...failed } = updateWorkItemStatus(item, "open", now);
2685
3060
  tx.saveWorkItem(item.taskId, failed);
2686
3061
  recordTaskEvent(tx, item.taskId, "work.rejected", {
2687
3062
  workItemId: item.id,
@@ -2735,12 +3110,12 @@ function retireWork(args, store, options) {
2735
3110
  + `${activeWorkItemJob.id}/${activeWorkItemJob.status}. `
2736
3111
  + "Cancel or acknowledge it before retiring.");
2737
3112
  }
2738
- for (const run of tx.listTurns(task.id).filter((candidate) => (candidate.status === "active" && candidate.workItemId === item.id))) {
2739
- 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, {
2740
3115
  taskId: task.id,
2741
3116
  roleName: run.roleName,
2742
3117
  agentId: run.effective.agentId,
2743
- turnId: run.id,
3118
+ runId: run.id,
2744
3119
  outcome: {
2745
3120
  status: "failed",
2746
3121
  diagnostic: `Work Item retired: ${summary}`,
@@ -2748,7 +3123,7 @@ function retireWork(args, store, options) {
2748
3123
  }
2749
3124
  }, now);
2750
3125
  if (terminal.disposition !== "applied") {
2751
- 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"}.`);
2752
3127
  }
2753
3128
  }
2754
3129
  }
@@ -2785,9 +3160,9 @@ function listWork(args, store) {
2785
3160
  exactPositionals(args, 1, "Task work list usage: yui task work list <task>.");
2786
3161
  const task = requireTask(store, args[0]);
2787
3162
  const items = store.listWorkItems(task.id);
2788
- const turns = store.listTurns(task.id);
3163
+ const runs = store.listRuns(task.id);
2789
3164
  const sessionSets = store.listRoleSessionSets(task.id);
2790
- const executions = items.map((item) => projectWorkItemExecution(item, turns, sessionSets));
3165
+ const executions = items.map((item) => projectWorkItemExecution(item, runs, sessionSets, store));
2791
3166
  const rendered = items.length === 0
2792
3167
  ? "No work items found.\n"
2793
3168
  : `${renderTable(`Task work: ${task.id}`, [
@@ -2814,7 +3189,7 @@ function listWork(args, store) {
2814
3189
  function showWork(args, store, options) {
2815
3190
  exactPositionals(args, 1, "Task work show usage: yui task work show <work>.");
2816
3191
  const item = requireWorkItem(store, args[0], options);
2817
- 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);
2818
3193
  const replacement = item.disposition?.replacementWorkItemId;
2819
3194
  const rendered = [
2820
3195
  `Work Item: ${item.id}`,
@@ -2835,7 +3210,7 @@ function showWork(args, store, options) {
2835
3210
  }
2836
3211
  function compactWorkItemExecution(projection) {
2837
3212
  if (projection.shape === "direct")
2838
- return `main=${projection.mainTurn.status}`;
3213
+ return `main=${projection.mainRun.status}`;
2839
3214
  const counts = projection.laneCounts;
2840
3215
  return `lanes ${counts.running}/${counts.succeeded}/${counts.needsAttention}/${counts.failed}/${counts.unknown}; ${projection.synthesis.status}`;
2841
3216
  }
@@ -2848,16 +3223,16 @@ function renderWorkItemExecutionProjection(projection) {
2848
3223
  : [
2849
3224
  `Lanes: running=${projection.laneCounts.running}, succeeded=${projection.laneCounts.succeeded}, needs-attention=${projection.laneCounts.needsAttention}, failed=${projection.laneCounts.failed}, unknown=${projection.laneCounts.unknown}`,
2850
3225
  ...projection.lanes.map((lane) => (` ${lane.laneId} (#${lane.ordinal}, ${lane.roleName}): ${lane.status}; `
2851
- + `turn=${lane.currentTurnId ?? "unknown"}; session=${lane.session}; `
2852
- + `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"}`))
2853
3228
  ]),
2854
- `Synthesis: ${projection.synthesis.status}; successful=${projection.synthesis.successfulLaneCount}/${projection.synthesis.requiredSuccessfulLaneCount}`,
2855
- `Main Turn: ${projection.mainTurn.turnId ?? "unobserved"} [${projection.mainTurn.status}]; role=${projection.mainTurn.roleName ?? "unobserved"}; session=${projection.mainTurn.session}; retry=${projection.mainTurn.retryTurnId ?? "none"}`,
2856
- `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"}`,
2857
3232
  ...(projection.candidate.sourceExecutionGroupId === undefined
2858
3233
  ? []
2859
3234
  : [
2860
- `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"}`
2861
3236
  ]),
2862
3237
  `Next Action: ${projection.nextAction.kind}; owner=${projection.nextAction.owners.join(", ") || "none"}; target=${projection.nextAction.targetIds.join(", ") || "none"}`
2863
3238
  ];
@@ -2882,7 +3257,7 @@ function reviewWork(args, store, options) {
2882
3257
  throw usageError(`Task is not active: ${task.id}/${task.status}.`);
2883
3258
  }
2884
3259
  const requestedBy = taskActor(tx, options, task.id);
2885
- if (item.status !== "awaiting_acceptance") {
3260
+ if ((item.status !== "open" || item.candidates.length === 0)) {
2886
3261
  throw usageError(`Work Item is not awaiting acceptance: ${item.id}/${item.status}.`);
2887
3262
  }
2888
3263
  const candidate = requireWorkItemCandidate(item);
@@ -2900,9 +3275,9 @@ function reviewWork(args, store, options) {
2900
3275
  && (round.status === "pending" || round.status === "running")))).at(-1);
2901
3276
  if (activeRound !== undefined) {
2902
3277
  const producerDispatchPending = activeRound.executionGroup?.lanes.some((lane) => (lane.disposition === "open"
2903
- && (lane.currentTurnId === undefined
2904
- || tx.getTurn(task.id, lane.currentTurnId)?.status === "failed"))) === true;
2905
- 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)
2906
3281
  || (activeRound.status === "running" && producerDispatchPending)) {
2907
3282
  const persistedRoles = activeRound.executionGroup?.lanes
2908
3283
  .map(({ roleName }) => roleName) ?? [];
@@ -2935,7 +3310,7 @@ function reviewWork(args, store, options) {
2935
3310
  if (result.kind === "busy") {
2936
3311
  const busy = result.availability;
2937
3312
  return output(`Reviewer ${busy.reviewerRoleName} is busy (${busy.phase}`
2938
- + `${busy.activeTurnId === undefined ? "" : `; Turn ${busy.activeTurnId}`}); `
3313
+ + `${busy.activeRunId === undefined ? "" : `; AgentRun ${busy.activeRunId}`}); `
2939
3314
  + `${busy.activeReviewRoundId === undefined
2940
3315
  ? ""
2941
3316
  : `active ReviewRound ${busy.activeReviewRoundId}; `}`
@@ -2950,17 +3325,49 @@ function reviewWork(args, store, options) {
2950
3325
  }
2951
3326
  /**
2952
3327
  * Task-control recovery for a failed Task-final ReviewRound that never
2953
- * created a Reviewer Turn. This is deliberately separate from `task turn retry`:
2954
- * 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
2955
3330
  * path for a failed provider execution. Here the old terminal Round is an
2956
3331
  * immutable anchor and one fresh Round is created only after the same frozen
2957
3332
  * committed Integration/ChangeSet provenance and Reviewer independence fences
2958
3333
  * pass again.
2959
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
+ }
2960
3365
  function taskReviewCommand(args, store, options) {
2961
3366
  const [command, ...rest] = args;
2962
3367
  if (command === "request")
2963
3368
  return requestTaskReviewRound(rest, store, options);
3369
+ if (command === "synthesize")
3370
+ return synthesizeRuns(rest, "reviewRound", store, options);
2964
3371
  if (command === "retry")
2965
3372
  return retryFailedTaskReviewRound(rest, store, options);
2966
3373
  throw usageError(command === undefined
@@ -2968,7 +3375,7 @@ function taskReviewCommand(args, store, options) {
2968
3375
  : `Unknown command: task review ${command}`);
2969
3376
  }
2970
3377
  function requestTaskReviewRound(args, store, options) {
2971
- 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> "
2972
3379
  + "[--lane-role <producer-role> ...] [--delta-recheck].";
2973
3380
  const parsed = parseMultiValueTail(args, new Set(["--role"]), new Set(["--lane-role"]), usage, new Set(["--delta-recheck"]));
2974
3381
  exactPositionals(parsed.positionals, 1, usage);
@@ -2993,8 +3400,8 @@ function requestTaskReviewRound(args, store, options) {
2993
3400
  throw usageError("Delta-recheck is not supported with a Task-final review contract.");
2994
3401
  }
2995
3402
  }
2996
- if (tx.getGlobalRole(reviewerRoleName) === null) {
2997
- 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}.`);
2998
3405
  }
2999
3406
  const provenance = taskReviewProvenance(tx, task, options);
3000
3407
  const producerCollision = taskReviewProducerCollision(provenance, reviewerRoleName);
@@ -3095,7 +3502,7 @@ function requestTaskReviewRound(args, store, options) {
3095
3502
  });
3096
3503
  if ("kind" in round && round.kind === "busy") {
3097
3504
  return output(`Reviewer ${round.reviewerRoleName} is busy (${round.phase}`
3098
- + `${round.activeTurnId === undefined ? "" : `; Turn ${round.activeTurnId}`}); `
3505
+ + `${round.activeRunId === undefined ? "" : `; AgentRun ${round.activeRunId}`}); `
3099
3506
  + `${round.activeReviewRoundId === undefined
3100
3507
  ? ""
3101
3508
  : `active ReviewRound ${round.activeReviewRoundId}; `}`
@@ -3134,26 +3541,26 @@ function validateDeltaRecheckRequest(store, taskId, candidate, preflight) {
3134
3541
  return preflight.record;
3135
3542
  }
3136
3543
  function assertTaskReviewRequestLane(store, taskId, reviewerRoleName, reusableRound) {
3137
- const activePointer = store.getActiveTurn(taskId, reviewerRoleName);
3544
+ const activePointer = store.getActiveRun(taskId, reviewerRoleName);
3138
3545
  if (reusableRound === undefined || reusableRound.status === "completed") {
3139
3546
  assertReviewerAvailable(store, taskId, reviewerRoleName);
3140
3547
  return;
3141
3548
  }
3142
3549
  if (reusableRound.status === "pending") {
3143
3550
  if (activePointer !== null) {
3144
- throw usageError(`Reviewer Role already has an active Turn: ${reviewerRoleName}.`);
3551
+ throw usageError(`Reviewer Role already has an active AgentRun: ${reviewerRoleName}.`);
3145
3552
  }
3146
3553
  assertReviewerAvailable(store, taskId, reviewerRoleName, reusableRound);
3147
3554
  return;
3148
3555
  }
3149
- const reviewerTurnId = reusableRound.reviewerTurnId;
3150
- if (reviewerTurnId === undefined
3556
+ const reviewerRunId = reusableRound.reviewerRunId;
3557
+ if (reviewerRunId === undefined
3151
3558
  && reusableRound.executionGroup?.lanes.some(({ disposition }) => disposition === "open")) {
3152
3559
  assertReviewerAvailable(store, taskId, reviewerRoleName, reusableRound);
3153
3560
  return;
3154
3561
  }
3155
- const activeMatches = reviewerTurnId !== undefined
3156
- && activePointer?.id === reviewerTurnId
3562
+ const activeMatches = reviewerRunId !== undefined
3563
+ && activePointer?.id === reviewerRunId
3157
3564
  && activePointer.status === "active";
3158
3565
  if (!activeMatches) {
3159
3566
  throw usageError(`Existing Task-final ReviewRound ${reusableRound.id} is running without its exact Reviewer execution.`);
@@ -3168,7 +3575,7 @@ function assertReviewerAvailable(store, taskId, reviewerRoleName, reusableRound)
3168
3575
  return;
3169
3576
  }
3170
3577
  throw usageError(`Reviewer ${reviewerRoleName} is busy (${availability.phase}`
3171
- + `${availability.activeTurnId === undefined ? "" : `; Turn ${availability.activeTurnId}`}`
3578
+ + `${availability.activeRunId === undefined ? "" : `; AgentRun ${availability.activeRunId}`}`
3172
3579
  + `${availability.activeReviewRoundId === undefined
3173
3580
  ? ""
3174
3581
  : `; ReviewRound ${availability.activeReviewRoundId}`}).`);
@@ -3178,10 +3585,10 @@ function reviewerBusyBelongsToRound(busy, round) {
3178
3585
  return false;
3179
3586
  if (busy.phase === "review-slot")
3180
3587
  return true;
3181
- if (busy.phase !== "active-turn" || busy.activeTurnId === undefined)
3588
+ if (busy.phase !== "active-turn" || busy.activeRunId === undefined)
3182
3589
  return false;
3183
- return round.reviewerTurnId === busy.activeTurnId
3184
- || 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;
3185
3592
  }
3186
3593
  function retryFailedTaskReviewRound(args, store, options) {
3187
3594
  exactPositionals(args, 1, "Task review retry usage: yui task review retry <task>/<review-round>.");
@@ -3199,11 +3606,11 @@ function retryFailedTaskReviewRound(args, store, options) {
3199
3606
  if ((round.scope ?? "work-item") !== "task") {
3200
3607
  throw usageError(`ReviewRound ${round.id} is not a failed Task-final ReviewRound.`);
3201
3608
  }
3202
- if (round.reviewerTurnId !== undefined) {
3609
+ if (round.reviewerRunId !== undefined) {
3203
3610
  if (round.status === "completed") {
3204
3611
  throw usageError(`ReviewRound ${round.id} is not retryable from ${round.status}.`);
3205
3612
  }
3206
- 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.`);
3207
3614
  }
3208
3615
  if (round.status !== "failed" && round.status !== "pending") {
3209
3616
  throw usageError(`ReviewRound ${round.id} is not retryable from ${round.status}.`);
@@ -3243,9 +3650,9 @@ function retryFailedTaskReviewRound(args, store, options) {
3243
3650
  reviewer = createTaskRole(tx, task, round.reviewerRoleName, undefined, now, round.reviewerRoleName);
3244
3651
  tx.saveRole(task.id, reviewer);
3245
3652
  }
3246
- const activePointer = tx.getActiveTurn(task.id, reviewer.name);
3653
+ const activePointer = tx.getActiveRun(task.id, reviewer.name);
3247
3654
  if (activePointer !== null) {
3248
- throw usageError(`Reviewer Role already has an active Turn: ${reviewer.name}.`);
3655
+ throw usageError(`Reviewer Role already has an active AgentRun: ${reviewer.name}.`);
3249
3656
  }
3250
3657
  assertReviewerAvailable(tx, task.id, reviewer.name, round);
3251
3658
  // Issue 06: an already-pending Round is the idempotent retry result.
@@ -3266,50 +3673,50 @@ function retryFailedTaskReviewRound(args, store, options) {
3266
3673
  ? `Task-final Review retry requested as ${result.round.id}\n`
3267
3674
  : `Task-final Review retry already requested as ${result.round.id} (${result.round.status})\n`, { reviewRound: result.round });
3268
3675
  }
3269
- function taskTurnCommand(args, store, options) {
3676
+ function taskRunCommand(args, store, options) {
3270
3677
  const [command, ...rest] = args;
3271
3678
  if (command === "list")
3272
- return output(listTurns(rest, store, options));
3679
+ return output(listRuns(rest, store, options));
3273
3680
  if (command === "show")
3274
- return showTurn(rest, store, options);
3681
+ return showRun(rest, store, options);
3275
3682
  if (command === "context")
3276
- return turnContextCommand(rest, store, options);
3683
+ return runContextCommand(rest, store, options);
3277
3684
  if (command === "retry")
3278
- return retryTurn(rest, store, options);
3685
+ return retryRun(rest, store, options);
3279
3686
  if (command === "settle")
3280
- return settleTurn(rest, store, options);
3687
+ return settleRun(rest, store, options);
3281
3688
  if (command === "checkpoint")
3282
- return output(checkpointTurn(rest, store, options));
3689
+ return output(checkpointRun(rest, store, options));
3283
3690
  if (command === "retire")
3284
- return retireTurn(rest, store, options);
3691
+ return retireRun(rest, store, options);
3285
3692
  throw usageError(command === undefined
3286
3693
  ? "Task turn command is required."
3287
- : `Unknown command: task turn ${command}`);
3694
+ : `Unknown command: task run ${command}`);
3288
3695
  }
3289
- function settleTurn(args, store, options) {
3290
- exactPositionals(args, 1, "Task turn settle usage: yui task turn settle <task>/<turn>.");
3291
- 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));
3292
3699
  if (previous.purpose === "review") {
3293
3700
  if (previous.executionGroupId !== undefined
3294
3701
  && previous.executionLaneId !== undefined) {
3295
- return settleFailedReviewExecutionLaneTurn(previous, store, options);
3702
+ return settleFailedReviewExecutionLaneRun(previous, store, options);
3296
3703
  }
3297
- return settleStaleFinalReviewTurn(args, store, options);
3704
+ return settleStaleFinalReviewRun(args, store, options);
3298
3705
  }
3299
- return settleFailedExecutionLaneTurn(previous, store, options);
3706
+ return settleFailedExecutionLaneRun(previous, store, options);
3300
3707
  }
3301
- function settleFailedReviewExecutionLaneTurn(previous, store, options) {
3708
+ function settleFailedReviewExecutionLaneRun(previous, store, options) {
3302
3709
  const now = clock(options);
3303
3710
  const result = store.transaction((tx) => {
3304
- const run = tx.getTurn(previous.taskId, previous.id);
3711
+ const run = tx.getRun(previous.taskId, previous.id);
3305
3712
  if (run === null || run.status !== "failed" || run.purpose !== "review") {
3306
- throw usageError(`Turn ${previous.id} is not a failed review Turn.`);
3713
+ throw usageError(`AgentRun ${previous.id} is not a failed review AgentRun.`);
3307
3714
  }
3308
3715
  if (run.reviewRoundId === undefined
3309
3716
  || run.executionGroupId === undefined
3310
3717
  || run.executionLaneId === undefined
3311
3718
  || run.sourceExecutionGroupId !== undefined) {
3312
- 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.`);
3313
3720
  }
3314
3721
  const task = requireTask(tx, run.taskId);
3315
3722
  if (task.status !== "active")
@@ -3317,80 +3724,79 @@ function settleFailedReviewExecutionLaneTurn(previous, store, options) {
3317
3724
  const actor = taskActor(tx, options, task.id);
3318
3725
  const round = tx.getReviewRound(task.id, run.reviewRoundId);
3319
3726
  if (round === null) {
3320
- throw dataError(`ReviewRound not found for Turn ${run.id}: ${run.reviewRoundId}.`);
3727
+ throw dataError(`ReviewRound not found for AgentRun ${run.id}: ${run.reviewRoundId}.`);
3321
3728
  }
3322
3729
  const group = round.executionGroup;
3323
3730
  if (group === undefined || group.id !== run.executionGroupId) {
3324
- 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.`);
3325
3732
  }
3326
3733
  const lane = group.lanes.find(({ id }) => id === run.executionLaneId);
3327
- if (lane === undefined || lane.currentTurnId !== run.id || lane.roleName !== run.roleName) {
3328
- 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.`);
3329
3736
  }
3330
3737
  if (lane.disposition === "failed") {
3331
3738
  return {
3332
- turn: run,
3739
+ run: run,
3333
3740
  reviewRound: round,
3334
3741
  changed: false,
3335
- mainTurns: []
3742
+ mainRuns: []
3336
3743
  };
3337
3744
  }
3338
3745
  if (round.status !== "running" || lane.disposition !== "open") {
3339
- 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 `
3340
3747
  + `${round.status}/${lane.disposition}.`);
3341
3748
  }
3342
- const validation = validateExactTurnReviewRound(tx, run, { allowTerminal: true });
3749
+ const validation = validateExactRunReviewRound(tx, run, { allowTerminal: true });
3343
3750
  if (validation.disposition !== "applied") {
3344
- 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: `
3345
3752
  + `${validation.reason ?? "mismatch"}.`);
3346
3753
  }
3347
- if (tx.getActiveExecutionLaneTurn(task.id, group.id, lane.id) !== null) {
3348
- 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}.`);
3349
3756
  }
3350
3757
  const settledGroup = updateUnifiedExecutionLane(group, lane.id, {
3351
- currentTurnId: run.id,
3758
+ currentRunId: run.id,
3352
3759
  disposition: "failed"
3353
3760
  }, now);
3354
3761
  tx.saveReviewRound(task.id, updateReviewExecutionGroup(round, settledGroup));
3355
- recordTaskEvent(tx, task.id, "turn.review-settled", {
3356
- turnId: run.id,
3762
+ recordTaskEvent(tx, task.id, "run.review-settled", {
3763
+ runId: run.id,
3357
3764
  reviewRoundId: round.id,
3358
3765
  executionGroupId: group.id,
3359
3766
  executionLaneId: lane.id,
3360
3767
  settledBy: actor,
3361
3768
  ...(actor === "leader" ? leaderActionEventPayload(tx, task.id, options) : {})
3362
3769
  }, now);
3363
- const reconciliation = reconcileReviewMainTurns(tx, task.id, now);
3364
3770
  return {
3365
- turn: run,
3771
+ run: run,
3366
3772
  reviewRound: tx.getReviewRound(task.id, round.id),
3367
3773
  changed: true,
3368
- mainTurns: reconciliation.createdTurns
3774
+ mainRuns: []
3369
3775
  };
3370
3776
  });
3371
- for (const turn of result.mainTurns) {
3372
- 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);
3373
3779
  }
3374
3780
  return output(result.changed
3375
- ? `Settled failed Review Producer Lane from Turn ${result.turn.id}\n`
3376
- : `Failed Review Producer Lane already settled from Turn ${result.turn.id}\n`, {
3377
- 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,
3378
3784
  reviewRound: result.reviewRound,
3379
- ...(result.mainTurns.length === 0 ? {} : { mainTurns: result.mainTurns })
3785
+ ...(result.mainRuns.length === 0 ? {} : { mainRuns: result.mainRuns })
3380
3786
  });
3381
3787
  }
3382
- function settleFailedExecutionLaneTurn(previous, store, options) {
3788
+ function settleFailedExecutionLaneRun(previous, store, options) {
3383
3789
  const now = clock(options);
3384
3790
  const result = store.transaction((tx) => {
3385
- const run = tx.getTurn(previous.taskId, previous.id);
3791
+ const run = tx.getRun(previous.taskId, previous.id);
3386
3792
  if (run === null || run.status !== "failed" || run.purpose !== "execution") {
3387
- throw usageError(`Turn ${previous.id} is not a failed execution Turn.`);
3793
+ throw usageError(`AgentRun ${previous.id} is not a failed execution AgentRun.`);
3388
3794
  }
3389
3795
  if (run.workItemId === undefined
3390
3796
  || run.executionGroupId === undefined
3391
3797
  || run.executionLaneId === undefined
3392
3798
  || run.sourceExecutionGroupId !== undefined) {
3393
- 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.`);
3394
3800
  }
3395
3801
  const task = requireTask(tx, run.taskId);
3396
3802
  if (task.status !== "active")
@@ -3398,94 +3804,88 @@ function settleFailedExecutionLaneTurn(previous, store, options) {
3398
3804
  const actor = taskActor(tx, options, task.id);
3399
3805
  const item = tx.getWorkItem(task.id, run.workItemId);
3400
3806
  if (item === null)
3401
- 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}.`);
3402
3808
  const group = currentWorkItemExecutionGroup(item);
3403
3809
  if (group === undefined || group.id !== run.executionGroupId) {
3404
- 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.`);
3405
3811
  }
3406
3812
  const lane = group.lanes.find(({ id }) => id === run.executionLaneId);
3407
- if (lane === undefined || lane.currentTurnId !== run.id) {
3408
- 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.`);
3409
3815
  }
3410
3816
  if (lane.disposition === "failed") {
3411
3817
  return {
3412
- turn: run,
3818
+ run: run,
3413
3819
  workItem: item,
3414
3820
  changed: false,
3415
- mainTurns: []
3821
+ mainRuns: []
3416
3822
  };
3417
3823
  }
3418
- if (item.status !== "running" || lane.disposition !== "open") {
3419
- 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 `
3420
3826
  + `${item.status}/${lane.disposition}.`);
3421
3827
  }
3422
- if (tx.getActiveExecutionLaneTurn(task.id, group.id, lane.id) !== null) {
3423
- 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}.`);
3424
3830
  }
3425
3831
  const settledGroup = updateWorkItemExecutionLane(group, lane.id, {
3426
- currentTurnId: run.id,
3832
+ currentRunId: run.id,
3427
3833
  disposition: "failed"
3428
3834
  }, now);
3429
3835
  const settledItem = updateWorkItemExecutionGroup(item, settledGroup, now);
3430
3836
  tx.saveWorkItem(task.id, settledItem);
3431
- recordTaskEvent(tx, task.id, "turn.execution-settled", {
3432
- turnId: run.id,
3837
+ recordTaskEvent(tx, task.id, "run.execution-settled", {
3838
+ runId: run.id,
3433
3839
  workItemId: item.id,
3434
3840
  executionGroupId: group.id,
3435
3841
  executionLaneId: lane.id,
3436
3842
  settledBy: actor,
3437
3843
  ...(actor === "leader" ? leaderActionEventPayload(tx, task.id, options) : {})
3438
3844
  }, now);
3439
- const reconciliation = reconcileWorkItemMainTurns(tx, task.id, now);
3440
3845
  return {
3441
- turn: run,
3846
+ run: run,
3442
3847
  workItem: tx.getWorkItem(task.id, item.id) ?? settledItem,
3443
3848
  changed: true,
3444
- mainTurns: reconciliation.createdTurns
3849
+ mainRuns: []
3445
3850
  };
3446
3851
  });
3447
- for (const turn of result.mainTurns) {
3448
- 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);
3449
3854
  }
3450
3855
  return output(result.changed
3451
- ? `Settled failed Execution Lane from Turn ${result.turn.id}\n`
3452
- : `Failed Execution Lane already settled from Turn ${result.turn.id}\n`, {
3453
- 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,
3454
3859
  workItem: result.workItem,
3455
- ...(result.mainTurns.length === 0 ? {} : { mainTurns: result.mainTurns })
3860
+ ...(result.mainRuns.length === 0 ? {} : { mainRuns: result.mainRuns })
3456
3861
  });
3457
3862
  }
3458
- function retireTurn(args, store, options) {
3459
- 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>].";
3460
3865
  const parsed = parseTail(args, new Set([
3461
3866
  "--reason",
3462
3867
  "--expected-progress-at",
3463
3868
  "--progress-at",
3464
3869
  "--agent-id",
3465
3870
  "--adapter-id",
3466
- "--native-session-id",
3467
- "--launch-id"
3871
+ "--native-session-id"
3468
3872
  ]), usage);
3469
3873
  exactPositionals(parsed.positionals, 1, usage);
3470
3874
  const reason = requiredOption(parsed.options, "--reason");
3471
- const reference = taskRecordReference(parsed.positionals[0], "turn", "Turn reference", options);
3875
+ const reference = taskRecordReference(parsed.positionals[0], "run", "AgentRun reference", options);
3472
3876
  const now = clock(options);
3473
3877
  const result = store.transaction((tx) => {
3474
3878
  const task = requireTask(tx, reference.taskId);
3475
3879
  assertTaskOpen(task);
3476
3880
  const actor = taskActor(tx, options, task.id);
3477
- let run = tx.getTurn(task.id, reference.localId);
3881
+ let run = tx.getRun(task.id, reference.localId);
3478
3882
  if (run === null)
3479
- throw dataError(`Turn not found: ${task.id}/${reference.localId}.`);
3883
+ throw dataError(`AgentRun not found: ${task.id}/${reference.localId}.`);
3480
3884
  const events = tx.listEvents(task.id);
3481
- if (isTaskRecordRetired(events, "turn", run.id)) {
3482
- return { task, turn: run, changed: false };
3885
+ if (isTaskRecordRetired(events, "run", run.id)) {
3886
+ return { task, run: run, changed: false };
3483
3887
  }
3484
3888
  if (run.status === "active") {
3485
- if (actor === "leader"
3486
- && taskLeaderActionTurnId(tx, task.id, options.environment) === run.id) {
3487
- throw usageError("A Task Leader cannot retire its own current authority Turn.", usage);
3488
- }
3489
3889
  const expectedProgressAt = requiredOption(parsed.options, parsed.options.has("--expected-progress-at")
3490
3890
  ? "--expected-progress-at"
3491
3891
  : "--progress-at");
@@ -3495,35 +3895,30 @@ function retireTurn(args, store, options) {
3495
3895
  const agentId = requiredOption(parsed.options, "--agent-id");
3496
3896
  const adapterId = requiredOption(parsed.options, "--adapter-id");
3497
3897
  const nativeSessionId = parsed.options.get("--native-session-id");
3498
- const runtimeGenerationId = parsed.options.get("--launch-id");
3499
3898
  const sessions = tx.getTaskRoleSessionSet(task.id, run.roleName);
3500
3899
  const session = sessions?.sessions[run.effective.agentId];
3501
3900
  if (session?.nativeSessionId !== undefined && nativeSessionId === undefined) {
3502
- throw usageError("--native-session-id is required for this active Turn.", usage);
3503
- }
3504
- if (session?.nativeSessionId === undefined && runtimeGenerationId === undefined) {
3505
- throw usageError("--launch-id is required for an opaque active Turn.", usage);
3901
+ throw usageError("--native-session-id is required for this active AgentRun.", usage);
3506
3902
  }
3507
- const terminal = retireExactActiveTurn(tx, {
3903
+ const terminal = retireExactActiveRun(tx, {
3508
3904
  taskId: task.id,
3509
3905
  roleName: run.roleName,
3510
- turnId: run.id,
3906
+ runId: run.id,
3511
3907
  agentId,
3512
3908
  adapterId,
3513
3909
  ...(nativeSessionId === undefined ? {} : { nativeSessionId }),
3514
- ...(runtimeGenerationId === undefined ? {} : { runtimeGenerationId }),
3515
3910
  expectedProgressAt,
3516
- reason: `Turn retired: ${reason}`
3911
+ reason: `AgentRun retired: ${reason}`
3517
3912
  }, now);
3518
- if (terminal.disposition !== "applied" || terminal.turn === null) {
3913
+ if (terminal.disposition !== "applied" || terminal.run === null) {
3519
3914
  throw usageError(terminal.disposition === "blocked"
3520
- ? `Turn retirement is blocked: ${run.id}/${terminal.reason ?? "unsafe"}.`
3521
- : `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"}.`);
3522
3917
  }
3523
- run = terminal.turn;
3918
+ run = terminal.run;
3524
3919
  }
3525
- recordTaskEvent(tx, task.id, "turn.retired", {
3526
- turnId: run.id,
3920
+ recordTaskEvent(tx, task.id, "run.retired", {
3921
+ runId: run.id,
3527
3922
  reason,
3528
3923
  ...(parsed.options.get("--expected-progress-at") === undefined
3529
3924
  && parsed.options.get("--progress-at") === undefined
@@ -3535,9 +3930,6 @@ function retireTurn(args, store, options) {
3535
3930
  ...(parsed.options.get("--native-session-id") === undefined
3536
3931
  ? {}
3537
3932
  : { nativeSessionId: parsed.options.get("--native-session-id") }),
3538
- ...(parsed.options.get("--launch-id") === undefined
3539
- ? {}
3540
- : { runtimeGenerationId: parsed.options.get("--launch-id") }),
3541
3933
  ...(actor === "leader"
3542
3934
  ? leaderActionEventPayload(tx, task.id, options)
3543
3935
  : { retiredBy: actor })
@@ -3545,98 +3937,98 @@ function retireTurn(args, store, options) {
3545
3937
  tx.saveEvent(task.id, createTaskRecordRetirement({
3546
3938
  eventId: tx.nextEventId(task.id),
3547
3939
  taskId: task.id,
3548
- recordKind: "turn",
3940
+ recordKind: "run",
3549
3941
  recordId: run.id,
3550
3942
  reason,
3551
3943
  retiredBy: actor
3552
3944
  }, now));
3553
- return { task, turn: run, changed: true };
3945
+ return { task, run: run, changed: true };
3554
3946
  });
3555
3947
  if (result.changed)
3556
3948
  options.runtime?.notifyStateChanged(result.task.id);
3557
- return output(`Retired Turn ${result.task.id}/${result.turn.id}\n`, {
3558
- turn: result.turn,
3949
+ return output(`Retired AgentRun ${result.task.id}/${result.run.id}\n`, {
3950
+ run: result.run,
3559
3951
  retired: true
3560
3952
  });
3561
3953
  }
3562
- function turnContextCommand(args, store, options) {
3954
+ function runContextCommand(args, store, options) {
3563
3955
  const [first, ...rest] = args;
3564
3956
  if (first === "expand") {
3565
- 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].";
3566
3958
  const parsed = parseTail(rest, new Set(["--store", "--mode"]), usage);
3567
3959
  exactPositionals(parsed.positionals, 2, usage);
3568
3960
  const mode = parsed.options.get("--mode");
3569
3961
  if (mode !== undefined && mode !== "full") {
3570
- throw usageError("Turn Context expansion mode must be full.", usage);
3962
+ throw usageError("AgentRun Context expansion mode must be full.", usage);
3571
3963
  }
3572
- const { taskId, turnId } = parseTurnContextReference(parsed.positionals[0]);
3573
- authorizeTurnContext(store, taskId, turnId, options.environment);
3574
- 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")));
3575
3967
  return output(`${JSON.stringify(expanded, null, 2)}\n`, { context: expanded });
3576
3968
  }
3577
3969
  if (first === "delta") {
3578
3970
  if (rest.length !== 3 || rest[1] !== "--after") {
3579
- 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>.");
3580
3972
  }
3581
- const { taskId, turnId } = parseTurnContextReference(rest[0]);
3582
- authorizeTurnContext(store, taskId, turnId, options.environment);
3583
- 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])));
3584
3976
  return output(`${JSON.stringify(delta, null, 2)}\n`, { contextDelta: delta });
3585
3977
  }
3586
3978
  if (first === undefined || rest.length !== 0) {
3587
- 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>.");
3588
3980
  }
3589
- const { taskId, turnId } = parseTurnContextReference(first);
3590
- authorizeTurnContext(store, taskId, turnId, options.environment);
3591
- 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));
3592
3984
  return output(`${JSON.stringify(pack, null, 2)}\n`, { context: pack });
3593
3985
  }
3594
- function parseTurnContextReference(value) {
3595
- const [taskId, turnId, extra] = value.split("/");
3596
- 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
3597
3989
  || extra !== undefined) {
3598
- throw usageError(`Turn context reference is invalid: ${value}.`);
3990
+ throw usageError(`AgentRun context reference is invalid: ${value}.`);
3599
3991
  }
3600
- return { taskId, turnId };
3992
+ return { taskId, runId };
3601
3993
  }
3602
3994
  /**
3603
- * 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
3604
3996
  * work. Authorization is therefore scope-shaped, not schedule-shaped: the
3605
- * 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,
3606
3998
  * proven by its per-Session caller key against durable state.
3607
3999
  *
3608
- * It deliberately does not require the Turn to still be the active one. An
3609
- * 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
3610
4002
  * given; losing read access to its own Role's history is what forces an
3611
4003
  * otherwise healthy Agent to stop and escalate to a human.
3612
4004
  */
3613
- function authorizeTurnContext(store, taskId, turnId, environment) {
4005
+ function authorizeRunContext(store, taskId, runId, environment) {
3614
4006
  const managed = environment?.YUI_SESSION_SCOPE !== undefined
3615
4007
  || environment?.YUI_TASK_ID !== undefined
3616
4008
  || environment?.YUI_ROLE !== undefined;
3617
4009
  if (!managed)
3618
4010
  return;
3619
- const run = store.getTurn(taskId, turnId);
4011
+ const run = store.getRun(taskId, runId);
3620
4012
  if (run === null) {
3621
- throw usageError(`Turn Context access is not authorized: ${taskId}/${turnId}.`);
4013
+ throw usageError(`AgentRun Context access is not authorized: ${taskId}/${runId}.`);
3622
4014
  }
3623
4015
  if (currentManagedRuntime(store, environment, taskId, run.roleName) === undefined) {
3624
- 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}.`);
3625
4017
  }
3626
4018
  }
3627
- function listTurns(args, store, options) {
3628
- 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>.";
3629
4021
  exactPositionals(args, 1, usage);
3630
4022
  const reference = args[0];
3631
4023
  const task = store.getTask(reference);
3632
4024
  const item = task === null ? requireWorkItem(store, reference, options) : null;
3633
4025
  const taskId = task?.id ?? item.taskId;
3634
- const turns = store.listTurns(taskId).filter((turn) => (item === null || turn.workItemId === item.id));
3635
- if (turns.length === 0)
3636
- 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";
3637
4029
  const events = store.listEvents(taskId);
3638
- return `${renderTable(`Turns: ${item?.id ?? taskId}`, [
3639
- { header: "Turn", minWidth: 6, maxWidth: 20 },
4030
+ return `${renderTable(`AgentRuns: ${item?.id ?? taskId}`, [
4031
+ { header: "AgentRun", minWidth: 6, maxWidth: 20 },
3640
4032
  { header: "Role", minWidth: 4, maxWidth: 22 },
3641
4033
  { header: "Subject", minWidth: 7, maxWidth: 24 },
3642
4034
  { header: "Purpose", minWidth: 6, maxWidth: 10 },
@@ -3647,7 +4039,7 @@ function listTurns(args, store, options) {
3647
4039
  { header: "Status", minWidth: 6, maxWidth: 12 },
3648
4040
  { header: "History", minWidth: 7, maxWidth: 9 },
3649
4041
  { header: "Summary", minWidth: 8, maxWidth: 58 }
3650
- ], turns.map((run) => [
4042
+ ], runs.map((run) => [
3651
4043
  run.id,
3652
4044
  run.roleName,
3653
4045
  run.workItemId ?? (run.reviewRoundId === undefined ? "task" : `review:${run.reviewRoundId}`),
@@ -3657,29 +4049,29 @@ function listTurns(args, store, options) {
3657
4049
  run.effective.profileAccess,
3658
4050
  run.effective.permission.strategy,
3659
4051
  run.status,
3660
- isTaskRecordRetired(events, "turn", run.id) ? "retired" : "active",
4052
+ isTaskRecordRetired(events, "run", run.id) ? "retired" : "active",
3661
4053
  run.result?.output ?? run.result?.diagnostic ?? "-"
3662
4054
  ]), defaultTableWidth())}\n`;
3663
4055
  }
3664
4056
  /**
3665
- * 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
3666
4058
  * still owns a running ReviewRound, but the committed Task heads have moved
3667
4059
  * on. This is deliberately narrower than retry: it cannot manufacture a
3668
4060
  * review or fail an arbitrary Round, and every identity/mailbox fence is
3669
4061
  * checked before the old Round changes. The next normal Task completion then
3670
4062
  * creates one fresh Round over the newer frozen Task heads.
3671
4063
  */
3672
- function settleStaleFinalReviewTurn(args, store, options) {
3673
- 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>.");
3674
4066
  const now = clock(options);
3675
- const previous = store.transaction((tx) => requireTurn(tx, args[0], options));
4067
+ const previous = store.transaction((tx) => requireRun(tx, args[0], options));
3676
4068
  const result = store.transaction((tx) => {
3677
- const run = tx.getTurn(previous.taskId, previous.id);
4069
+ const run = tx.getRun(previous.taskId, previous.id);
3678
4070
  if (run === null || run.status !== "failed" || run.purpose !== "review") {
3679
- throw usageError(`Turn ${previous.id} is not a failed review Turn.`);
4071
+ throw usageError(`AgentRun ${previous.id} is not a failed review AgentRun.`);
3680
4072
  }
3681
4073
  if (run.reviewRoundId === undefined) {
3682
- throw usageError(`Review Turn ${run.id} has no ReviewRound.`);
4074
+ throw usageError(`Review AgentRun ${run.id} has no ReviewRound.`);
3683
4075
  }
3684
4076
  const task = requireTask(tx, run.taskId);
3685
4077
  if (task.status !== "active")
@@ -3687,26 +4079,26 @@ function settleStaleFinalReviewTurn(args, store, options) {
3687
4079
  const actor = taskActor(tx, options, task.id);
3688
4080
  const round = tx.getReviewRound(task.id, run.reviewRoundId);
3689
4081
  if (round === null) {
3690
- throw dataError(`ReviewRound not found for Turn ${run.id}: ${run.reviewRoundId}.`);
4082
+ throw dataError(`ReviewRound not found for AgentRun ${run.id}: ${run.reviewRoundId}.`);
3691
4083
  }
3692
4084
  if ((round.scope ?? "work-item") !== "task") {
3693
- 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 `
3694
4086
  + "for a new Candidate.");
3695
4087
  }
3696
4088
  const taskFinalContract = taskFinalReviewContractForMutation(tx, task.id, options);
3697
4089
  if (!sameTaskFinalReviewContract(round.taskFinalReviewContract, taskFinalContract)) {
3698
4090
  throw usageError(`Task final-review contract does not match ReviewRound ${round.id}.`);
3699
4091
  }
3700
- // 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,
3701
4093
  // Candidate, stored Review workspace, frozen Project scope, and frozen
3702
4094
  // Project heads before any mailbox or Round write.
3703
- const validation = validateExactTurnReviewRound(tx, run, { allowTerminal: true });
4095
+ const validation = validateExactRunReviewRound(tx, run, { allowTerminal: true });
3704
4096
  if (validation.disposition !== "applied" || validation.round === null) {
3705
- 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"}.`);
3706
4098
  }
3707
- const activeRoleTurn = tx.getActiveTurn(task.id, round.reviewerRoleName);
3708
- if (activeRoleTurn !== null) {
3709
- 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}.`);
3710
4102
  }
3711
4103
  const taskRounds = reviewRoundsByIdentity(tx.listReviewRounds(task.id)
3712
4104
  .filter((entry) => (entry.scope ?? "work-item") === "task"));
@@ -3724,7 +4116,7 @@ function settleStaleFinalReviewTurn(args, store, options) {
3724
4116
  }
3725
4117
  if (round.status === "failed") {
3726
4118
  if (round.failure?.kind === "execution") {
3727
- return { turn: run, round, changed: false };
4119
+ return { run: run, round, changed: false };
3728
4120
  }
3729
4121
  throw usageError(`Final ReviewRound is already terminal: ${round.id}/${round.status}.`);
3730
4122
  }
@@ -3732,53 +4124,54 @@ function settleStaleFinalReviewTurn(args, store, options) {
3732
4124
  throw usageError(`Final ReviewRound is not stranded running: ${round.id}/${round.status}.`);
3733
4125
  }
3734
4126
  assertReviewerAvailable(tx, task.id, round.reviewerRoleName, round);
3735
- 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.`;
3736
4128
  const terminal = finishReviewRound(round, "failed", now, { kind: "execution", message: summary });
3737
4129
  tx.saveReviewRound(task.id, terminal);
3738
- recordTaskEvent(tx, task.id, "turn.review-stale-settled", {
3739
- turnId: run.id,
4130
+ recordTaskEvent(tx, task.id, "run.review-stale-settled", {
4131
+ runId: run.id,
3740
4132
  reviewRoundId: round.id,
3741
4133
  previousTaskCandidate: JSON.stringify(round.taskCandidate),
3742
4134
  currentTaskCandidate: JSON.stringify(currentTaskCandidate),
3743
4135
  settledBy: actor,
3744
4136
  ...(actor === "leader" ? leaderActionEventPayload(tx, task.id, options) : {})
3745
4137
  }, now);
3746
- return { turn: run, round: terminal, changed: true };
4138
+ return { run: run, round: terminal, changed: true };
3747
4139
  });
3748
4140
  return output(result.changed
3749
- ? `Settled obsolete final Review ${result.round.id} from failed Turn ${result.turn.id}\n`
3750
- : `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 });
3751
4143
  }
3752
- function retryTurn(args, store, options) {
3753
- 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>.");
3754
4146
  const now = clock(options);
3755
- const previous = store.transaction((tx) => requireTurn(tx, args[0], options));
4147
+ const previous = store.transaction((tx) => requireRun(tx, args[0], options));
3756
4148
  if (previous.purpose === "review") {
3757
4149
  return retryFailedReviewRun(previous, store, options, now);
3758
4150
  }
3759
4151
  const retried = store.transaction((tx) => {
3760
4152
  if (previous.status !== "failed") {
3761
- throw usageError(`Turn ${previous.id} is not retryable from ${previous.status}.`);
4153
+ throw usageError(`AgentRun ${previous.id} is not retryable from ${previous.status}.`);
3762
4154
  }
3763
4155
  const task = requireTask(tx, previous.taskId);
3764
- if (task.status !== "active")
3765
- throw usageError(`Task is not active: ${task.id}.`);
3766
- 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
+ }
3767
4160
  const role = requireRole(tx, task.id, previous.roleName);
3768
- if (tx.getActiveTurn(task.id, role.name) !== null) {
3769
- 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.`);
3770
4163
  }
3771
4164
  const sessions = tx.getTaskRoleSessionSet(task.id, role.name);
3772
4165
  const retryItem = previous.workItemId === undefined
3773
4166
  ? null
3774
4167
  : tx.getWorkItem(task.id, previous.workItemId);
3775
4168
  if (previous.workItemId !== undefined && retryItem === null) {
3776
- 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}.`);
3777
4170
  }
3778
4171
  const retriesSynthesisMain = previous.sourceExecutionGroupId !== undefined;
3779
4172
  if (retriesSynthesisMain
3780
4173
  && (previous.executionGroupId !== undefined || previous.executionLaneId !== undefined)) {
3781
- throw dataError(`Turn ${previous.id} has conflicting execution lineage.`);
4174
+ throw dataError(`AgentRun ${previous.id} has conflicting execution lineage.`);
3782
4175
  }
3783
4176
  const retryLaneBefore = previous.executionLaneId === undefined
3784
4177
  ? undefined
@@ -3796,57 +4189,54 @@ function retryTurn(args, store, options) {
3796
4189
  && retryLaneBefore !== undefined
3797
4190
  && retryLaneBefore.id === previous.executionLaneId
3798
4191
  && retryLaneBefore.disposition === "open"
3799
- && retryLaneBefore.currentTurnId === previous.id);
4192
+ && retryLaneBefore.currentRunId === previous.id);
3800
4193
  if (!exactCurrentLane) {
3801
- 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.`);
3802
4195
  }
3803
4196
  const sourceGroup = !retriesSynthesisMain || retryItem === null
3804
4197
  ? undefined
3805
4198
  : workItemExecutionGroupById(retryItem, previous.sourceExecutionGroupId);
3806
- const sourceMainTurns = retriesSynthesisMain
3807
- ? chronologicalTurns(tx.listTurns(task.id).filter((turn) => (turn.purpose === "execution"
3808
- && turn.workItemId === previous.workItemId
3809
- && 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)))
3810
4203
  : [];
3811
4204
  const exactSourceMain = !retriesSynthesisMain || (retryItem !== null
3812
- && retryItem.status === "running"
4205
+ && retryItem.status === "open"
3813
4206
  && retryItem.assignee === previous.roleName
3814
4207
  && sourceGroup !== undefined
3815
4208
  && currentRetryGroup?.id === sourceGroup.id
3816
- && workItemExecutionGroupSettled(sourceGroup)
3817
- && successfulWorkItemSynthesisProducers(tx, retryItem, sourceGroup).length
3818
- >= MINIMUM_WORK_ITEM_SYNTHESIS_RESULTS
3819
- && sourceMainTurns.at(-1)?.id === previous.id);
4209
+ && selectedWorkItemSynthesisProducers(tx, retryItem, sourceGroup, synthesisSourceRunIds(tx, previous)).length > 0
4210
+ && sourceMainRuns.at(-1)?.id === previous.id);
3820
4211
  if (!exactSourceMain) {
3821
- 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.`);
3822
4213
  }
3823
- const directMainTurns = retryItem === null
4214
+ const directMainRuns = retryItem === null
3824
4215
  ? []
3825
- : chronologicalTurns(tx.listTurns(task.id).filter((turn) => (turn.purpose === "execution"
3826
- && turn.workItemId === retryItem.id
3827
- && turn.executionGroupId === undefined
3828
- && turn.executionLaneId === undefined
3829
- && 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)));
3830
4221
  const retriesDirectMain = retryItem !== null
3831
4222
  && !retriesExecutionLane
3832
4223
  && !retriesSynthesisMain;
3833
- const exactDirectMain = !retriesDirectMain || ((retryItem.status === "running" || retryItem.status === "failed")
4224
+ const exactDirectMain = !retriesDirectMain || (retryItem.status === "open"
3834
4225
  && retryItem.assignee === previous.roleName
3835
- && directMainTurns.at(-1)?.id === previous.id);
4226
+ && directMainRuns.at(-1)?.id === previous.id);
3836
4227
  if (!exactDirectMain) {
3837
- 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.`);
3838
4229
  }
3839
- const groupedRunningRetry = retryItem?.status === "running"
4230
+ const groupedRunningRetry = retryItem?.status === "open"
3840
4231
  && retriesExecutionLane
3841
4232
  && exactCurrentLane;
3842
- const synthesisRunningRetry = retryItem?.status === "running"
4233
+ const synthesisRunningRetry = retryItem?.status === "open"
3843
4234
  && retriesSynthesisMain
3844
4235
  && exactSourceMain;
3845
- const directRunningRetry = retryItem?.status === "running"
4236
+ const directRunningRetry = retryItem?.status === "open"
3846
4237
  && retriesDirectMain
3847
4238
  && exactDirectMain;
3848
4239
  if (retryItem !== null
3849
- && retryItem.status !== "failed"
3850
4240
  && !groupedRunningRetry
3851
4241
  && !synthesisRunningRetry
3852
4242
  && !directRunningRetry) {
@@ -3873,7 +4263,7 @@ function retryTurn(args, store, options) {
3873
4263
  && (retryGroup === undefined
3874
4264
  || retryGroup.id !== previous.executionGroupId
3875
4265
  || retryLane === undefined))) {
3876
- 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.`);
3877
4267
  }
3878
4268
  const retryManagedWorkspace = retryLane === undefined ? runWorkspace : previous.workspace;
3879
4269
  if (retryLane !== undefined) {
@@ -3893,7 +4283,7 @@ function retryTurn(args, store, options) {
3893
4283
  || !isDeepStrictEqual(writableProjectIds, [...retryLane.workspace.writableProjectIds].sort())
3894
4284
  || storedLaneWorkspace === null
3895
4285
  || !isDeepStrictEqual(storedLaneWorkspace, previous.workspace)) {
3896
- 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.`);
3897
4287
  }
3898
4288
  }
3899
4289
  if (retriesSynthesisMain) {
@@ -3910,7 +4300,7 @@ function retryTurn(args, store, options) {
3910
4300
  || currentMainWorkspace === null
3911
4301
  || !isDeepStrictEqual(storedMainWorkspace, previous.workspace)
3912
4302
  || !isDeepStrictEqual(currentMainWorkspace, previous.workspace)) {
3913
- 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.`);
3914
4304
  }
3915
4305
  }
3916
4306
  if (retriesDirectMain) {
@@ -3927,20 +4317,20 @@ function retryTurn(args, store, options) {
3927
4317
  || currentDirectWorkspace === null
3928
4318
  || !isDeepStrictEqual(storedDirectWorkspace, previous.workspace)
3929
4319
  || !isDeepStrictEqual(currentDirectWorkspace, previous.workspace)) {
3930
- 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.`);
3931
4321
  }
3932
4322
  }
3933
4323
  const effective = retryLane?.effective ?? resolveEffectiveLaunch({
3934
4324
  role,
3935
- purpose: "execution",
4325
+ purpose: previous.purpose,
3936
4326
  ...(retryManagedWorkspace === undefined ? {} : { workspace: retryManagedWorkspace }),
3937
4327
  ...(retryItem === null ? {} : { workItemWriteProjectIds: retryItem.writeProjectIds })
3938
4328
  });
3939
- const turnId = tx.nextTurnId(task.id);
4329
+ const runId = tx.nextRunId(task.id);
3940
4330
  const runningGroup = retryGroup === undefined || retryLane === undefined
3941
4331
  ? undefined
3942
4332
  : updateWorkItemExecutionLane(retryGroup, retryLane.id, {
3943
- currentTurnId: turnId
4333
+ currentRunId: runId
3944
4334
  }, now);
3945
4335
  // Restart the bound lane and reopen the failed WorkItem as two ordered
3946
4336
  // single-step record revisions, matching the dispatch path. Folding both
@@ -3949,11 +4339,7 @@ function retryTurn(args, store, options) {
3949
4339
  const laneRestartedItem = retryItem === null || runningGroup === undefined
3950
4340
  ? retryItem
3951
4341
  : updateWorkItemExecutionGroup(retryItem, runningGroup, now);
3952
- const retriedItemWithGroup = laneRestartedItem === null
3953
- ? null
3954
- : laneRestartedItem.status === "failed"
3955
- ? retryFailedWorkItem(laneRestartedItem, now)
3956
- : laneRestartedItem;
4342
+ const retriedItemWithGroup = laneRestartedItem;
3957
4343
  if (retriedItemWithGroup !== null) {
3958
4344
  if (laneRestartedItem !== null && laneRestartedItem !== retryItem) {
3959
4345
  tx.saveWorkItem(task.id, laneRestartedItem);
@@ -3965,13 +4351,13 @@ function retryTurn(args, store, options) {
3965
4351
  const input = retriesSynthesisMain
3966
4352
  ? previous.inputs[0].input
3967
4353
  : (() => {
3968
- const retrySnapshot = freezeTurnContextSnapshot(tx, {
4354
+ const retrySnapshot = freezeRunContextSnapshot(tx, {
3969
4355
  taskId: task.id,
3970
4356
  roleName: role.name,
3971
- purpose: "execution",
4357
+ purpose: previous.purpose,
3972
4358
  ...(previous.workItemId === undefined ? {} : { workItemId: previous.workItemId })
3973
4359
  }, now, "controller", retryGroup?.assignment.contextSnapshotRef);
3974
- return createTurnInput({
4360
+ return createRunInput({
3975
4361
  source: {
3976
4362
  type: "yui",
3977
4363
  channel: previous.workItemId === undefined ? "task-dispatch" : "workitem-dispatch"
@@ -3983,7 +4369,8 @@ function retryTurn(args, store, options) {
3983
4369
  deltaRefIds: contextSnapshotDeltaRefIds(tx, retrySnapshot)
3984
4370
  });
3985
4371
  })();
3986
- 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,
3987
4374
  ...(previous.workItemId === undefined ? {} : { workItemId: previous.workItemId }),
3988
4375
  ...(runningGroup === undefined ? {} : {
3989
4376
  executionGroupId: runningGroup.id,
@@ -3995,8 +4382,8 @@ function retryTurn(args, store, options) {
3995
4382
  ...(retryManagedWorkspace === undefined ? {} : { workspace: retryManagedWorkspace }),
3996
4383
  effective
3997
4384
  });
3998
- tx.saveTurn(created);
3999
- tx.saveActiveTurn(created);
4385
+ tx.saveRun(created);
4386
+ tx.saveActiveRun(created);
4000
4387
  if (previous.workItemId !== undefined && retriedItemWithGroup !== null) {
4001
4388
  const item = retriedItemWithGroup;
4002
4389
  const workspace = tx.getWorkItemWorkspace(task.id, item.id);
@@ -4006,21 +4393,21 @@ function retryTurn(args, store, options) {
4006
4393
  + `cannot retry ${item.id}.`);
4007
4394
  }
4008
4395
  }
4009
- enqueueRoleTurnDispatch(tx, {
4396
+ enqueueRoleRunDispatch(tx, {
4010
4397
  taskId: task.id,
4011
4398
  roleName: role.name,
4012
- turnId: created.id,
4399
+ runId: created.id,
4013
4400
  reason: "turn-retried",
4014
4401
  occurredAt: now
4015
4402
  });
4016
- recordTaskEvent(tx, task.id, "turn.retried", {
4017
- ...turnLaunchEventPayload(created),
4018
- previousTurnId: previous.id
4403
+ recordTaskEvent(tx, task.id, "run.retried", {
4404
+ ...runLaunchEventPayload(created),
4405
+ previousRunId: previous.id
4019
4406
  }, now);
4020
- return { kind: "turn", turn: created };
4407
+ return { kind: "run", run: created };
4021
4408
  });
4022
- notifyMailbox(options.runtime, roleMailbox(retried.turn.taskId, retried.turn.roleName), retried.turn.taskId);
4023
- 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`);
4024
4411
  }
4025
4412
  function actualTaskReviewCandidateForMutation(store, task, options) {
4026
4413
  if (options.actualTaskReviewCandidate === undefined) {
@@ -4063,7 +4450,7 @@ function taskReviewProducerCollision(provenance, reviewerRoleName) {
4063
4450
  *
4064
4451
  * When `expected` is supplied this is also the final dispatch compare-and-swap
4065
4452
  * fence: every bound Project must still point at the exact frozen physical
4066
- * head. Drift fails closed before a Reviewer Turn is created.
4453
+ * head. Drift fails closed before a Reviewer AgentRun is created.
4067
4454
  */
4068
4455
  function taskReviewProvenance(store, task, options, expected) {
4069
4456
  const candidate = actualTaskReviewCandidateForMutation(store, task, options);
@@ -4118,13 +4505,13 @@ function taskReviewProvenance(store, task, options, expected) {
4118
4505
  recordProducer(LEADER_ROLE, item.id);
4119
4506
  continue;
4120
4507
  }
4121
- const sourceRun = store.getTurn(task.id, sourceCandidate.source.turnId);
4508
+ const sourceRun = store.getRun(task.id, sourceCandidate.source.runId);
4122
4509
  if (sourceRun === null
4123
4510
  || sourceRun.workItemId !== item.id
4124
4511
  || sourceRun.purpose !== "execution"
4125
4512
  || sourceRun.status !== "completed") {
4126
- throw dataError(`Committed producer Candidate Turn is unavailable: `
4127
- + `${item.id}/${sourceCandidate.source.turnId}.`);
4513
+ throw dataError(`Committed producer Candidate AgentRun is unavailable: `
4514
+ + `${item.id}/${sourceCandidate.source.runId}.`);
4128
4515
  }
4129
4516
  recordProducer(sourceRun.roleName, item.id);
4130
4517
  if (sourceRun.sourceExecutionGroupId !== undefined) {
@@ -4133,7 +4520,7 @@ function taskReviewProvenance(store, task, options, expected) {
4133
4520
  throw dataError(`Committed producer ExecutionGroup is unavailable: `
4134
4521
  + `${item.id}/${sourceRun.sourceExecutionGroupId}.`);
4135
4522
  }
4136
- for (const producer of successfulWorkItemSynthesisProducers(store, item, executionGroup)) {
4523
+ for (const producer of selectedWorkItemSynthesisProducers(store, item, executionGroup, synthesisSourceRunIds(store, sourceRun))) {
4137
4524
  recordProducer(producer.roleName, item.id);
4138
4525
  }
4139
4526
  }
@@ -4182,7 +4569,7 @@ function queueTaskReviewRound(store, task, config, taskCandidate, options, now,
4182
4569
  const availability = projectReviewerAvailability(store, task.id, config.roleName);
4183
4570
  if (availability.kind === "busy") {
4184
4571
  throw usageError(`Reviewer ${config.roleName} is busy (${availability.phase}`
4185
- + `${availability.activeTurnId === undefined ? "" : `; Turn ${availability.activeTurnId}`}); `
4572
+ + `${availability.activeRunId === undefined ? "" : `; AgentRun ${availability.activeRunId}`}); `
4186
4573
  + `retry after ${availability.retryAfterSeconds}s.`);
4187
4574
  }
4188
4575
  let reviewer = store.getRole(task.id, config.roleName);
@@ -4251,16 +4638,16 @@ function resumablePendingFinalTaskReview(store, task, round, config, taskCandida
4251
4638
  || round.reviewerRoleName !== taskFinalContract.reviewerRoleName) {
4252
4639
  throw usageError(`Pending final ReviewRound Reviewer identity changed: ${round.id}.`);
4253
4640
  }
4254
- if (round.reviewerTurnId !== undefined) {
4255
- 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}.`);
4256
4643
  }
4257
4644
  assertNoConflictingTaskReviewRound(store.listReviewRounds(task.id), round.id, round.reviewerRoleName);
4258
4645
  const reviewer = store.getRole(task.id, round.reviewerRoleName);
4259
4646
  if (reviewer === null || reviewer.name !== round.reviewerRoleName) {
4260
4647
  throw usageError(`Pending final ReviewRound Reviewer identity changed: ${round.id}.`);
4261
4648
  }
4262
- if (store.getActiveTurn(task.id, reviewer.name) !== null) {
4263
- 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}.`);
4264
4651
  }
4265
4652
  assertPendingFinalReviewWorkspaceEvidence(store, task, round);
4266
4653
  return round;
@@ -4296,7 +4683,7 @@ function assertPendingFinalReviewWorkspaceEvidence(store, task, round) {
4296
4683
  }
4297
4684
  }
4298
4685
  /**
4299
- * 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
4300
4687
  * remains the attempt trail, while the ReviewRound is reset to pending under
4301
4688
  * its existing identity. Every identity and frozen-head fence is
4302
4689
  * checked inside one transaction so a partial fail-old-without-reset state can
@@ -4304,12 +4691,12 @@ function assertPendingFinalReviewWorkspaceEvidence(store, task, round) {
4304
4691
  */
4305
4692
  function retryFailedReviewRun(previous, store, options, now) {
4306
4693
  const result = store.transaction((tx) => {
4307
- const run = tx.getTurn(previous.taskId, previous.id);
4694
+ const run = tx.getRun(previous.taskId, previous.id);
4308
4695
  if (run === null || run.status !== "failed" || run.purpose !== "review") {
4309
- throw usageError(`Turn ${previous.id} is not a failed review Turn.`);
4696
+ throw usageError(`AgentRun ${previous.id} is not a failed review AgentRun.`);
4310
4697
  }
4311
4698
  if (run.reviewRoundId === undefined) {
4312
- throw usageError(`Review Turn ${run.id} has no ReviewRound.`);
4699
+ throw usageError(`Review AgentRun ${run.id} has no ReviewRound.`);
4313
4700
  }
4314
4701
  const task = requireTask(tx, run.taskId);
4315
4702
  if (task.status !== "active")
@@ -4325,16 +4712,16 @@ function retryFailedReviewRun(previous, store, options, now) {
4325
4712
  : round.executionGroup?.lanes.find(({ id }) => id === run.executionLaneId);
4326
4713
  if (run.executionGroupId !== undefined && (round.executionGroup?.id !== run.executionGroupId
4327
4714
  || retryLane === undefined
4328
- || retryLane.currentTurnId !== run.id
4715
+ || retryLane.currentRunId !== run.id
4329
4716
  || retryLane.roleName !== run.roleName)) {
4330
- 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.`);
4331
4718
  }
4332
4719
  const panelGroup = round.executionGroup !== undefined;
4333
4720
  const runningPanelLaneRetry = round.status === "running"
4334
4721
  && panelGroup
4335
4722
  && retryLane?.disposition === "open";
4336
4723
  if (round.status === "running" && panelGroup && !runningPanelLaneRetry) {
4337
- 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}.`);
4338
4725
  }
4339
4726
  const retryReviewerRoleName = retryLane?.roleName ?? round.reviewerRoleName;
4340
4727
  if (round.status !== "failed"
@@ -4370,10 +4757,10 @@ function retryFailedReviewRun(previous, store, options, now) {
4370
4757
  throw dataError(`WorkItem ReviewRound has no Candidate anchor: ${round.id}.`);
4371
4758
  }
4372
4759
  if (run.workItemId !== round.workItemId) {
4373
- 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}.`);
4374
4761
  }
4375
4762
  const item = tx.getWorkItem(task.id, round.workItemId);
4376
- if (item === null || item.status !== "awaiting_acceptance") {
4763
+ if (item === null || (item.status !== "open" || item.candidates.length === 0)) {
4377
4764
  throw usageError(`WorkItem ReviewRound ${round.id} no longer has an awaiting Candidate.`);
4378
4765
  }
4379
4766
  const candidate = currentWorkItemCandidate(item);
@@ -4412,20 +4799,20 @@ function retryFailedReviewRun(previous, store, options, now) {
4412
4799
  throw usageError(`Reviewer already has an active review round for this candidate: ${activeRound.id}.`);
4413
4800
  }
4414
4801
  const reviewer = requireRole(tx, task.id, retryReviewerRoleName);
4415
- const activePointer = tx.getActiveTurn(task.id, reviewer.name);
4802
+ const activePointer = tx.getActiveRun(task.id, reviewer.name);
4416
4803
  // Issue 06: a completed same-Round retry is a no-write idempotent result.
4417
4804
  if (round.status === "completed") {
4418
4805
  assertReviewerAvailable(tx, task.id, reviewer.name);
4419
4806
  return { round, previousRun: run, created: false };
4420
4807
  }
4421
4808
  // Issue 06: a running same Round is reusable only with its exact active
4422
- // 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
4423
4810
  // Round after the identity fences below.
4424
4811
  if (round.status === "running" && !runningPanelLaneRetry) {
4425
- const reviewerTurnId = round.reviewerTurnId;
4426
- const activeMatches = reviewerTurnId !== undefined
4812
+ const reviewerRunId = round.reviewerRunId;
4813
+ const activeMatches = reviewerRunId !== undefined
4427
4814
  && activePointer !== null
4428
- && activePointer.id === reviewerTurnId
4815
+ && activePointer.id === reviewerRunId
4429
4816
  && activePointer.status === "active";
4430
4817
  if (activePointer !== null && !activeMatches) {
4431
4818
  throw usageError(`Existing running ReviewRound ${round.id} lacks its exact active Reviewer execution.`);
@@ -4438,24 +4825,65 @@ function retryFailedReviewRun(previous, store, options, now) {
4438
4825
  // Issue 06: an already-pending Round is the idempotent retry result.
4439
4826
  if (round.status === "pending") {
4440
4827
  if (activePointer !== null) {
4441
- throw usageError(`Reviewer Role already has an active Turn: ${reviewer.name}.`);
4828
+ throw usageError(`Reviewer Role already has an active AgentRun: ${reviewer.name}.`);
4442
4829
  }
4443
4830
  assertReviewerAvailable(tx, task.id, reviewer.name, round);
4444
4831
  return { round, previousRun: run, created: false };
4445
4832
  }
4446
4833
  if (activePointer !== null) {
4447
- throw usageError(`Reviewer Role already has an active Turn: ${reviewer.name}.`);
4834
+ throw usageError(`Reviewer Role already has an active AgentRun: ${reviewer.name}.`);
4448
4835
  }
4449
4836
  assertReviewerAvailable(tx, task.id, reviewer.name, round);
4450
- const validation = validateExactTurnReviewRound(tx, run, { allowTerminal: true });
4837
+ const validation = validateExactRunReviewRound(tx, run, { allowTerminal: true });
4451
4838
  if (validation.disposition !== "applied" || validation.round === null) {
4452
- 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 };
4453
4881
  }
4454
4882
  if (runningPanelLaneRetry) {
4455
4883
  const resetRound = retryRunningReviewExecutionLane(round, retryLane.id, run.id);
4456
4884
  tx.saveReviewRound(task.id, resetRound);
4457
- recordTaskEvent(tx, task.id, "turn.review-retried", {
4458
- turnId: run.id,
4885
+ recordTaskEvent(tx, task.id, "run.review-retried", {
4886
+ runId: run.id,
4459
4887
  reviewRoundId: round.id,
4460
4888
  executionLaneId: retryLane.id
4461
4889
  }, now);
@@ -4465,7 +4893,7 @@ function retryFailedReviewRun(previous, store, options, now) {
4465
4893
  // fence has passed. The outer transaction rolls back if Round creation fails.
4466
4894
  let roundToReset = round;
4467
4895
  if (round.status !== "failed") {
4468
- const summary = `Review Turn ${run.id} failed before delivery.`;
4896
+ const summary = `Review AgentRun ${run.id} failed before delivery.`;
4469
4897
  roundToReset = finishReviewRound(round, "failed", now, { kind: "execution", message: summary });
4470
4898
  tx.saveReviewRound(task.id, roundToReset);
4471
4899
  }
@@ -4474,41 +4902,44 @@ function retryFailedReviewRun(previous, store, options, now) {
4474
4902
  // stable across execution-attempt failures.
4475
4903
  const resetRound = retryReviewRound(roundToReset, requestedBy, now);
4476
4904
  tx.saveReviewRound(task.id, resetRound);
4477
- recordTaskEvent(tx, task.id, "turn.review-retried", {
4478
- turnId: run.id,
4905
+ recordTaskEvent(tx, task.id, "run.review-retried", {
4906
+ runId: run.id,
4479
4907
  reviewRoundId: round.id
4480
4908
  }, now);
4481
4909
  return { round: resetRound, previousRun: run, created: true };
4482
4910
  });
4911
+ if ("run" in result && result.run !== undefined) {
4912
+ notifyMailbox(options.runtime, roleMailbox(result.run.taskId, result.run.roleName), result.run.taskId);
4913
+ }
4483
4914
  return output(result.created
4484
4915
  ? `Review retry requested as ${result.round.id}\n`
4485
- : `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 } : {}) });
4486
4917
  }
4487
- /** Turn details are retained audit evidence; continuation uses durable Task state. */
4488
- function showTurn(args, store, options) {
4489
- 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].";
4490
4921
  const asJson = args.includes("--json");
4491
4922
  const positionals = args.filter((arg) => arg !== "--json");
4492
4923
  exactPositionals(positionals, 1, usage);
4493
4924
  const data = store.transaction((tx) => {
4494
- const run = requireTurn(tx, positionals[0], options);
4925
+ const run = requireRun(tx, positionals[0], options);
4495
4926
  const retirement = tx.listEvents(run.taskId)
4496
4927
  .map(taskRecordRetirement)
4497
- .find((entry) => entry?.recordKind === "turn" && entry.recordId === run.id) ?? null;
4498
- 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)) };
4499
4930
  });
4500
4931
  if (asJson) {
4501
4932
  return { kind: "output", output: `${JSON.stringify(data, null, 2)}\n`, data };
4502
4933
  }
4503
4934
  return {
4504
4935
  kind: "output",
4505
- output: renderTurnShow(data.turn, data.retirement),
4936
+ output: `Delivery observation: ${data.execution.delivery}\n` + renderRunShow(data.run, data.retirement),
4506
4937
  data
4507
4938
  };
4508
4939
  }
4509
- function renderTurnShow(run, retirement) {
4940
+ function renderRunShow(run, retirement) {
4510
4941
  const lines = [
4511
- `Turn: ${run.id}`,
4942
+ `AgentRun: ${run.id}`,
4512
4943
  `Task: ${run.taskId}`,
4513
4944
  `Role: ${run.roleName}`,
4514
4945
  `Purpose: ${run.purpose}`,
@@ -4531,40 +4962,40 @@ function renderTurnShow(run, retirement) {
4531
4962
  return `${lines.join("\n")}\n`;
4532
4963
  }
4533
4964
  /**
4534
- * Records a structured progress checkpoint for an active Turn. This is a durable
4535
- * Turn fact, not a Task Message: it advances the Turn's durable-progress clock so
4536
- * a healthy but long-running Turn keeps proving it is alive without adding
4537
- * 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
4538
4969
  * Leader.
4539
4970
  */
4540
- function checkpointTurn(args, store, options) {
4541
- 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|->).";
4542
4973
  const parsed = parseTail(args, new Set(["--note", "--note-file"]), usage);
4543
4974
  exactPositionals(parsed.positionals, 1, usage);
4544
4975
  const note = readCommandText(parsed.options.get("--note"), parsed.options.get("--note-file"), "--note", usage);
4545
4976
  const now = clock(options);
4546
4977
  const event = store.transaction((tx) => {
4547
- const run = requireTurn(tx, parsed.positionals[0], options);
4978
+ const run = requireRun(tx, parsed.positionals[0], options);
4548
4979
  if (run.status !== "active") {
4549
- throw usageError(`Turn ${run.id} is already terminal: ${run.status}.`);
4980
+ throw usageError(`AgentRun ${run.id} is already terminal: ${run.status}.`);
4550
4981
  }
4551
4982
  const task = requireTask(tx, run.taskId);
4552
4983
  if (task.status !== "active")
4553
- throw usageError(inactiveTaskMessage(task, "checkpointing a Turn"));
4554
- const pointer = activeTurnPointer(tx, run);
4984
+ throw usageError(inactiveTaskMessage(task, "checkpointing a AgentRun"));
4985
+ const pointer = activeRunPointer(tx, run);
4555
4986
  if (pointer?.id !== run.id) {
4556
- 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}.`);
4557
4988
  }
4558
4989
  const events = tx.listEvents(task.id);
4559
- const recovered = isRoleTurnStalled(events, run.id);
4560
- const progress = recordTaskEventRecord(tx, task.id, TURN_PROGRESS_EVENT, {
4561
- turnId: run.id,
4990
+ const recovered = isRoleRunStalled(events, run.id);
4991
+ const progress = recordTaskEventRecord(tx, task.id, RUN_PROGRESS_EVENT, {
4992
+ runId: run.id,
4562
4993
  note: truncateEventNote(note),
4563
4994
  ...(run.workItemId === undefined ? {} : { workItemId: run.workItemId })
4564
4995
  }, now);
4565
4996
  if (recovered) {
4566
- recordTaskEventRecord(tx, task.id, TURN_RECOVERED_EVENT, {
4567
- turnId: run.id,
4997
+ recordTaskEventRecord(tx, task.id, RUN_RECOVERED_EVENT, {
4998
+ runId: run.id,
4568
4999
  roleName: run.roleName,
4569
5000
  progressAt: now.toISOString(),
4570
5001
  kind: "checkpoint"
@@ -4612,12 +5043,12 @@ export function queueReviewRound(store, item, config, requestedBy, now, requeste
4612
5043
  reviewer = createTaskRole(store, task, roleName, undefined, now, roleName);
4613
5044
  store.saveRole(task.id, reviewer);
4614
5045
  }
4615
- if (store.getActiveTurn(item.taskId, reviewer.name) !== null) {
5046
+ if (store.getActiveRun(item.taskId, reviewer.name) !== null) {
4616
5047
  const pending = createPending();
4617
5048
  store.saveReviewRound(item.taskId, pending);
4618
5049
  const failed = finishReviewRound(pending, "failed", now, {
4619
5050
  kind: "dispatch",
4620
- message: `Reviewer Role already has an active Turn: ${reviewer.name}.`
5051
+ message: `Reviewer Role already has an active AgentRun: ${reviewer.name}.`
4621
5052
  });
4622
5053
  store.saveReviewRound(item.taskId, failed);
4623
5054
  return { round: failed };
@@ -4757,8 +5188,8 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
4757
5188
  && round.reviewerRoleName !== taskFinalContract.reviewerRoleName) {
4758
5189
  throw new TaskFinalReviewDispatchDriftError(`Task final-review Reviewer identity does not match ReviewRound ${round.id}.`);
4759
5190
  }
4760
- if (round.status === "pending" && round.reviewerTurnId !== undefined) {
4761
- 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}: `
4762
5193
  + `${round.id}.`);
4763
5194
  }
4764
5195
  let currentTaskCandidate;
@@ -4810,8 +5241,8 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
4810
5241
  && (requestedReviewers.has(entry.reviewerRoleName)
4811
5242
  || entry.executionGroup?.lanes.some(({ roleName }) => (requestedReviewers.has(roleName))) === true)
4812
5243
  && !(entry.status === "running"
4813
- && entry.reviewerTurnId !== undefined
4814
- && tx.getTurn(task.id, entry.reviewerTurnId)?.status === "failed")));
5244
+ && entry.reviewerRunId !== undefined
5245
+ && tx.getRun(task.id, entry.reviewerRunId)?.status === "failed")));
4815
5246
  if (conflicting !== undefined) {
4816
5247
  throw new TaskFinalReviewDispatchDriftError(`Another active Task-final ReviewRound already exists: ${conflicting.id}/${conflicting.reviewerRoleName}.`);
4817
5248
  }
@@ -4838,8 +5269,8 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
4838
5269
  }
4839
5270
  const candidateLabel = taskScope
4840
5271
  ? "frozen Task candidate"
4841
- : candidate.source.type === "turn"
4842
- ? `candidate Turn ${candidate.source.turnId}`
5272
+ : candidate.source.type === "run"
5273
+ ? `candidate AgentRun ${candidate.source.runId}`
4843
5274
  : `revision ${candidate.workItemRevision}`;
4844
5275
  const frozenHeads = taskScope
4845
5276
  ? round.taskCandidate.projects
@@ -4854,7 +5285,7 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
4854
5285
  }
4855
5286
  const diffByProject = options.deltaRecheckDiff;
4856
5287
  if (diffByProject === undefined) {
4857
- 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.`);
4858
5289
  }
4859
5290
  try {
4860
5291
  verifyDeltaRecheckDiff(round.deltaRecheck, diffByProject);
@@ -4898,17 +5329,17 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
4898
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.",
4899
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.",
4900
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.",
4901
- "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.",
4902
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."
4903
5334
  ].join("\n");
4904
- const createdTurns = [];
5335
+ const createdRuns = [];
4905
5336
  if (round.executionGroup === undefined) {
4906
5337
  if (round.status !== "pending")
4907
- return createdTurns;
4908
- if (tx.getActiveTurn(taskId, reviewer.name) !== null) {
4909
- 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}.`);
4910
5341
  }
4911
- const turnId = tx.nextTurnId(taskId);
5342
+ const runId = tx.nextRunId(taskId);
4912
5343
  const effective = resolveEffectiveLaunch({
4913
5344
  role: reviewer,
4914
5345
  purpose: "review",
@@ -4916,14 +5347,14 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
4916
5347
  reviewRoundId: round.id,
4917
5348
  reviewBaseCommit: round.reviewBaseCommit
4918
5349
  });
4919
- const snapshot = freezeTurnContextSnapshot(tx, {
5350
+ const snapshot = freezeRunContextSnapshot(tx, {
4920
5351
  taskId,
4921
5352
  roleName: reviewer.name,
4922
5353
  purpose: "review",
4923
5354
  ...(item === undefined ? {} : { workItemId: item.id }),
4924
5355
  reviewRoundId: round.id
4925
5356
  }, now, "controller");
4926
- 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({
4927
5358
  source: {
4928
5359
  type: "yui",
4929
5360
  channel: item === undefined ? "task-dispatch" : "workitem-dispatch"
@@ -4938,27 +5369,27 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
4938
5369
  workspace: round.workspace,
4939
5370
  effective
4940
5371
  });
4941
- tx.saveTurn(created);
5372
+ tx.saveRun(created);
4942
5373
  tx.saveReviewRound(taskId, startReviewRound(round, created.id));
4943
- tx.saveActiveTurn(created);
4944
- enqueueRoleTurnDispatch(tx, {
5374
+ tx.saveActiveRun(created);
5375
+ enqueueRoleRunDispatch(tx, {
4945
5376
  taskId,
4946
5377
  roleName: reviewer.name,
4947
- turnId: created.id,
5378
+ runId: created.id,
4948
5379
  reason: "review-requested",
4949
5380
  occurredAt: now
4950
5381
  });
4951
- recordTaskEvent(tx, taskId, "turn.review-dispatched", turnLaunchEventPayload(created), now);
4952
- createdTurns.push(created);
4953
- return createdTurns;
5382
+ recordTaskEvent(tx, taskId, "run.review-dispatched", runLaunchEventPayload(created), now);
5383
+ createdRuns.push(created);
5384
+ return createdRuns;
4954
5385
  }
4955
5386
  let runningGroup = round.executionGroup;
4956
5387
  const dispatchLanes = runningGroup.lanes.filter((lane) => {
4957
5388
  if (lane.disposition !== "open")
4958
5389
  return false;
4959
- if (lane.currentTurnId === undefined)
5390
+ if (lane.currentRunId === undefined)
4960
5391
  return true;
4961
- return tx.getTurn(taskId, lane.currentTurnId)?.status === "failed";
5392
+ return tx.getRun(taskId, lane.currentRunId)?.status === "failed";
4962
5393
  });
4963
5394
  const preparedLaneWorkspaces = requireIsolatedReviewLaneWorkspaces(tx, round, dispatchLanes, options.executionLaneWorkspaces);
4964
5395
  for (const lane of dispatchLanes) {
@@ -4966,8 +5397,8 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
4966
5397
  if (laneReviewer === null) {
4967
5398
  throw usageError(`Review Producer Role not found: ${taskId}/${lane.roleName}.`);
4968
5399
  }
4969
- if (tx.getActiveTurn(taskId, lane.roleName) !== null) {
4970
- 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}.`);
4971
5402
  }
4972
5403
  const laneManagedWorkspace = preparedLaneWorkspaces.get(lane.id);
4973
5404
  const effective = lane.effective ?? resolveEffectiveLaunch({
@@ -4977,8 +5408,8 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
4977
5408
  reviewRoundId: round.id,
4978
5409
  reviewBaseCommit: round.reviewBaseCommit
4979
5410
  });
4980
- const turnId = tx.nextTurnId(taskId);
4981
- const input = createTurnInput({
5411
+ const runId = tx.nextRunId(taskId);
5412
+ const input = createRunInput({
4982
5413
  source: {
4983
5414
  type: "yui",
4984
5415
  channel: item === undefined ? "task-dispatch" : "workitem-dispatch"
@@ -5000,7 +5431,7 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
5000
5431
  deltaRefIds: []
5001
5432
  });
5002
5433
  runningGroup = updateUnifiedExecutionLane(runningGroup, lane.id, {
5003
- currentTurnId: turnId,
5434
+ currentRunId: runId,
5004
5435
  effective,
5005
5436
  workspace: {
5006
5437
  root: laneManagedWorkspace.root,
@@ -5009,7 +5440,7 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
5009
5440
  .map(({ projectId }) => projectId)
5010
5441
  }
5011
5442
  }, now);
5012
- 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, {
5013
5444
  ...(item === undefined ? {} : { workItemId: item.id }),
5014
5445
  purpose: "review",
5015
5446
  reviewRoundId: round.id,
@@ -5031,32 +5462,30 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
5031
5462
  tx.saveManagedWorkspace(prepared);
5032
5463
  }
5033
5464
  }
5034
- for (let index = 0; index < createdTurns.length; index += 1) {
5035
- const unboundTurn = createdTurns[index];
5036
- const snapshot = freezeTurnContextSnapshot(tx, {
5465
+ for (let index = 0; index < createdRuns.length; index += 1) {
5466
+ const unboundRun = createdRuns[index];
5467
+ const snapshot = freezeRunContextSnapshot(tx, {
5037
5468
  taskId,
5038
- roleName: unboundTurn.roleName,
5469
+ roleName: unboundRun.roleName,
5039
5470
  purpose: "review",
5040
5471
  ...(item === undefined ? {} : { workItemId: item.id }),
5041
5472
  reviewRoundId: round.id
5042
5473
  }, now, "controller", runningGroup.assignment.contextSnapshotRef);
5043
- const created = withTurnContextSnapshot(unboundTurn, contextSnapshotRef(snapshot), contextSnapshotDeltaRefIds(tx, snapshot));
5044
- createdTurns[index] = created;
5045
- const laneReviewer = requireRole(tx, taskId, unboundTurn.roleName);
5046
- tx.saveTurn(created);
5047
- tx.saveActiveTurn(created);
5048
- 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, {
5049
5480
  taskId,
5050
5481
  roleName: laneReviewer.name,
5051
- turnId: created.id,
5482
+ runId: created.id,
5052
5483
  reason: "review-requested",
5053
5484
  occurredAt: now
5054
5485
  });
5055
- recordTaskEvent(tx, taskId, "turn.review-dispatched", turnLaunchEventPayload(created), now);
5486
+ recordTaskEvent(tx, taskId, "run.review-dispatched", runLaunchEventPayload(created), now);
5056
5487
  }
5057
- const reconciliation = reconcileReviewMainTurns(tx, taskId, now);
5058
- createdTurns.push(...reconciliation.createdTurns);
5059
- return createdTurns;
5488
+ return createdRuns;
5060
5489
  });
5061
5490
  for (const run of runs) {
5062
5491
  notifyMailbox(options.runtime, roleMailbox(run.taskId, run.roleName), run.taskId);
@@ -5106,6 +5535,16 @@ function requireWorkItemCandidate(item) {
5106
5535
  }
5107
5536
  return candidate;
5108
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
+ }
5109
5548
  /** ReviewRound ids are the durable Task-local creation order; wall time is not causal. */
5110
5549
  function reviewRoundsByIdentity(rounds) {
5111
5550
  return [...rounds].sort((left, right) => (left.id.localeCompare(right.id, undefined, { numeric: true })));
@@ -5118,7 +5557,9 @@ function activeReviewRoundForCandidate(store, item, candidate) {
5118
5557
  .at(-1);
5119
5558
  }
5120
5559
  function createTaskRole(store, task, roleName, explicitAgentId, now, sourceGlobalRoleName) {
5121
- 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();
5122
5563
  if (explicitAgentId === undefined) {
5123
5564
  const sourceRoleName = sourceGlobalRoleName
5124
5565
  ?? (roleName === LEADER_ROLE ? LEADER_ROLE : "worker");
@@ -5136,7 +5577,7 @@ function createTaskRole(store, task, roleName, explicitAgentId, now, sourceGloba
5136
5577
  throw dataError(`No Agent is configured for Task role: ${roleName}.`);
5137
5578
  }
5138
5579
  const agent = requireAgent(store, agentId);
5139
- const binding = createRoleAgentBinding({ id: agent.id, adapterId: agent.adapterId });
5580
+ const binding = createRoleAgentBinding(agent);
5140
5581
  return createRole(task.id, roleName, [binding], agent.id, workspace, now);
5141
5582
  }
5142
5583
  function requireAgentProfile(store, id) {
@@ -5146,7 +5587,9 @@ function requireAgentProfile(store, id) {
5146
5587
  return profile;
5147
5588
  }
5148
5589
  function createTaskRoleFromAgentBinding(store, task, roleName, binding, now) {
5149
- 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();
5150
5593
  return createRole(task.id, roleName, [binding], binding.agentId, workspace, now);
5151
5594
  }
5152
5595
  function resolvedAgentProfileRuntime(profile, store) {
@@ -5174,7 +5617,7 @@ function resolveTaskRoleAgentBindingAdd(parsed, profile, store) {
5174
5617
  if (explicitAgentId === undefined)
5175
5618
  return undefined;
5176
5619
  const agent = requireAgent(store, explicitAgentId);
5177
- binding = createRoleAgentBinding({ id: agent.id, adapterId: agent.adapterId });
5620
+ binding = createRoleAgentBinding(agent);
5178
5621
  }
5179
5622
  if (hasAgentConfigOptions(parsed)) {
5180
5623
  binding = patchRoleAgentBinding(binding, parsed);
@@ -5185,11 +5628,6 @@ function resolveTaskRoleAgentBindingUpdate(parsed, role, profile, store) {
5185
5628
  const changesAgentConfig = hasAgentConfigOptions(parsed);
5186
5629
  const explicitAgentId = parsed.one("--agent")?.trim();
5187
5630
  const targetAgentId = explicitAgentId || role.activeAgentId;
5188
- if (profile?.runtime.source === "global-worker"
5189
- && explicitAgentId === undefined
5190
- && !changesAgentConfig) {
5191
- return undefined;
5192
- }
5193
5631
  let binding;
5194
5632
  if (profile !== undefined) {
5195
5633
  const profileRuntime = resolvedAgentProfileRuntime(profile, store);
@@ -5208,7 +5646,7 @@ function resolveTaskRoleAgentBindingUpdate(parsed, role, profile, store) {
5208
5646
  return undefined;
5209
5647
  const agent = requireAgent(store, targetAgentId);
5210
5648
  binding = role.agentBindings[targetAgentId]
5211
- ?? createRoleAgentBinding({ id: agent.id, adapterId: agent.adapterId });
5649
+ ?? createRoleAgentBinding(agent);
5212
5650
  }
5213
5651
  if (changesAgentConfig) {
5214
5652
  binding = patchRoleAgentBinding(binding, parsed);
@@ -5245,7 +5683,7 @@ function appendMessage(store, taskId, body, kind, author, now, context = {}) {
5245
5683
  recordTaskEvent(store, taskId, "message.sent", {
5246
5684
  messageId: message.id,
5247
5685
  kind: message.kind,
5248
- ...(message.turnId === undefined ? {} : { turnId: message.turnId })
5686
+ ...(message.runId === undefined ? {} : { runId: message.runId })
5249
5687
  }, now);
5250
5688
  return message;
5251
5689
  }
@@ -5253,43 +5691,29 @@ function recordTaskEvent(store, taskId, type, payload, now) {
5253
5691
  return recordTaskEventRecord(store, taskId, type, payload, now);
5254
5692
  }
5255
5693
  function leaderActionEventPayload(store, taskId, options) {
5256
- const turnId = taskLeaderActionTurnId(store, taskId, options.environment);
5257
- 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 };
5258
5698
  }
5259
5699
  function recordTaskEventRecord(store, taskId, type, payload, now) {
5260
5700
  const event = createTaskEvent(store.nextEventId(taskId), taskId, type, payload, now);
5261
5701
  store.saveEvent(taskId, event);
5262
5702
  return event;
5263
5703
  }
5264
- /** 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. */
5265
5705
  function truncateEventNote(note) {
5266
5706
  const normalized = note.trim();
5267
5707
  return normalized.length <= 280 ? normalized : `${normalized.slice(0, 279)}…`;
5268
5708
  }
5269
- function roleLaunchEventPayload(role, sessions) {
5270
- const effective = sessions?.sessions[sessions.activeAgentId]?.effective;
5271
- return {
5272
- desiredRevision: String(role.launchRevision),
5273
- defaultAccess: role.defaultAccess,
5274
- effectiveRevision: effective === undefined
5275
- ? "none"
5276
- : String(effective.sourceDesiredRevision),
5277
- profileAccess: effective?.profileAccess ?? "none",
5278
- effectivePermission: effective?.permission.strategy ?? "none",
5279
- desiredDrift: effective === undefined
5280
- ? "not-started"
5281
- : effective.sourceDesiredRevision === role.launchRevision
5282
- ? "none"
5283
- : "pending-next-launch"
5284
- };
5285
- }
5286
- function turnLaunchEventPayload(run) {
5709
+ function runLaunchEventPayload(run) {
5287
5710
  return {
5288
- turnId: run.id,
5711
+ runId: run.id,
5289
5712
  role: run.roleName,
5290
5713
  purpose: run.purpose,
5291
5714
  mode: run.mode,
5292
5715
  agent: `${run.effective.agentId}/${run.effective.adapterId}`,
5716
+ component: run.effective.component,
5293
5717
  effectiveRevision: String(run.effective.sourceDesiredRevision),
5294
5718
  profileAccess: run.effective.profileAccess,
5295
5719
  effectivePermission: run.effective.permission.strategy,
@@ -5346,18 +5770,18 @@ function requireWorkItem(store, workItemId, options) {
5346
5770
  }
5347
5771
  return item;
5348
5772
  }
5349
- function requireTurn(store, turnId, options) {
5350
- const reference = taskRecordReference(turnId, "turn", "Turn reference", options);
5351
- 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);
5352
5776
  if (run === null) {
5353
- throw usageError(`Turn not found: ${reference.taskId}/${reference.localId}.`);
5777
+ throw usageError(`AgentRun not found: ${reference.taskId}/${reference.localId}.`);
5354
5778
  }
5355
5779
  return run;
5356
5780
  }
5357
- function activeTurnPointer(store, run) {
5781
+ function activeRunPointer(store, run) {
5358
5782
  return run.executionGroupId !== undefined && run.executionLaneId !== undefined
5359
- ? store.getActiveExecutionLaneTurn(run.taskId, run.executionGroupId, run.executionLaneId)
5360
- : store.getActiveTurn(run.taskId, run.roleName);
5783
+ ? store.getActiveExecutionLaneRun(run.taskId, run.executionGroupId, run.executionLaneId)
5784
+ : store.getActiveRun(run.taskId, run.roleName);
5361
5785
  }
5362
5786
  function requireReviewRound(store, reviewRoundId, options) {
5363
5787
  const reference = taskRecordReference(reviewRoundId, "reviewRound", "ReviewRound reference", options);
@@ -5392,12 +5816,12 @@ export function assertWorkItemDependenciesCompletedForCommand(store, item) {
5392
5816
  throw error;
5393
5817
  }
5394
5818
  }
5395
- function chronologicalTurns(turns) {
5396
- 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)
5397
5821
  || left.id.localeCompare(right.id)));
5398
5822
  }
5399
5823
  function isTerminalWorkItemStatus(status) {
5400
- return ["completed", "failed", "retired"].includes(status);
5824
+ return ["accepted", "retired"].includes(status);
5401
5825
  }
5402
5826
  function assertTaskOpen(task) {
5403
5827
  if (task.status === "completed") {
@@ -5405,7 +5829,7 @@ function assertTaskOpen(task) {
5405
5829
  }
5406
5830
  if (task.status === "archived")
5407
5831
  throw usageError(`Task is archived: ${task.id}.`);
5408
- if (task.status === "retired")
5832
+ if (task.status === "cancelled")
5409
5833
  throw usageError(`Task is retired: ${task.id}.`);
5410
5834
  }
5411
5835
  function assertTaskExecutionEnabled(task, action) {
@@ -5423,7 +5847,7 @@ function inactiveTaskMessage(task, action) {
5423
5847
  if (task.status === "completed") {
5424
5848
  return `Task ${task.id} is completed; reopen it before ${action}.`;
5425
5849
  }
5426
- if (task.status === "retired")
5850
+ if (task.status === "cancelled")
5427
5851
  return `Task ${task.id} is retired; it cannot resume ${action}.`;
5428
5852
  return `Task is archived: ${task.id}.`;
5429
5853
  }
@@ -5444,10 +5868,6 @@ function parseWorkStatus(value) {
5444
5868
  throw usageError(`Invalid work item status: ${value}.`);
5445
5869
  }
5446
5870
  function presentWorkStatus(status) {
5447
- if (status === "pending")
5448
- return "todo";
5449
- if (status === "completed")
5450
- return "done";
5451
5871
  return status;
5452
5872
  }
5453
5873
  function parseWorkCreateArgs(args, usage) {
@@ -5622,7 +6042,10 @@ function taskBriefCommand(args, store, options) {
5622
6042
  ...(hasSummary ? { leaderSummary: parsed.options.get("--leader-summary") } : {})
5623
6043
  }, updatedBy, now);
5624
6044
  tx.saveTaskBrief(task.id, brief);
5625
- 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);
5626
6049
  enqueueWork(tx, taskMailbox(task.id), "brief-updated", now, [taskRef(task.id)]);
5627
6050
  if (task.status === "active" && updatedBy !== "leader") {
5628
6051
  enqueueWork(tx, leaderMailbox(task.id), "brief-updated", now, [taskRef(task.id)]);
@@ -5633,12 +6056,18 @@ function taskBriefCommand(args, store, options) {
5633
6056
  if (result.task.status === "active") {
5634
6057
  notifyMailbox(options.runtime, leaderMailbox(result.task.id), result.task.id);
5635
6058
  }
5636
- 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 });
5637
6060
  }
5638
6061
  throw usageError(command === undefined
5639
6062
  ? "Task brief command is required."
5640
6063
  : `Unknown command: task brief ${command}`);
5641
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
+ }
5642
6071
  function taskDecisionCommand(args, store, options) {
5643
6072
  const [command, ...rest] = args;
5644
6073
  if (command === "record") {
@@ -5889,14 +6318,11 @@ function taskContinuationCommand(args, store) {
5889
6318
  const report = [...continuation.reports].reverse()[0];
5890
6319
  const reportEvent = report === undefined
5891
6320
  ? undefined
5892
- : reportEvents.find((entry) => (entry.continuationId === identity.continuationId
5893
- && entry.continuationGeneration === identity.generation
5894
- && entry.reportId === report.reportId));
6321
+ : reportEvents.find((entry) => (entry.continuationId === identity.continuationId && entry.reportId === report.reportId));
5895
6322
  return Object.freeze({
5896
6323
  continuationId: identity.continuationId,
5897
- generation: identity.generation,
5898
6324
  driver: identity.providerNamespace,
5899
- turnId: continuation.turnId,
6325
+ runId: continuation.runId,
5900
6326
  execution: continuation.execution,
5901
6327
  outcome: continuation.outcome,
5902
6328
  attachment: continuation.attachment,
@@ -5942,12 +6368,9 @@ function continuationReportEvents(events) {
5942
6368
  const observation = runtimeObservationFromTaskEvent(event);
5943
6369
  if (observation !== null && observation.kind === "continuation.reported") {
5944
6370
  const continuationId = observation.fence.continuationId;
5945
- const continuationGeneration = observation.fence.continuationGeneration;
5946
6371
  const reportId = observation.payload?.reportId;
5947
- if (continuationId !== undefined
5948
- && continuationGeneration !== undefined
5949
- && reportId !== undefined) {
5950
- result.push({ event, continuationId, continuationGeneration, reportId });
6372
+ if (continuationId !== undefined && reportId !== undefined) {
6373
+ result.push({ event, continuationId, reportId });
5951
6374
  }
5952
6375
  }
5953
6376
  }
@@ -5961,6 +6384,48 @@ function continuationReportEvents(events) {
5961
6384
  */
5962
6385
  function taskWakeDispatch(args, store, options) {
5963
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
+ }
5964
6429
  if (subcommand === "list" || subcommand === "show") {
5965
6430
  return taskWakeInspectionCommand(args, store);
5966
6431
  }
@@ -5987,13 +6452,13 @@ function taskWakeInspectionCommand(args, store) {
5987
6452
  { header: "Wake", minWidth: 8, maxWidth: 18 },
5988
6453
  { header: "Status", minWidth: 8, maxWidth: 12 },
5989
6454
  { header: "Reasons", minWidth: 10, maxWidth: 40 },
5990
- { header: "Turn", minWidth: 10, maxWidth: 20 },
6455
+ { header: "AgentRun", minWidth: 10, maxWidth: 20 },
5991
6456
  { header: "Dispatched", minWidth: 10, maxWidth: 28 }
5992
6457
  ], wakes.map((wake) => [
5993
6458
  wake.id,
5994
6459
  wake.status,
5995
6460
  wake.reasons.map(renderWakeReason).join(", "),
5996
- wake.turnId ?? "-",
6461
+ wake.runId ?? "-",
5997
6462
  presentTime(wake.createdAt, timeZone)
5998
6463
  ]), defaultTableWidth())}\n`, { taskId: task.id, wakes });
5999
6464
  }
@@ -6012,18 +6477,30 @@ function taskWakeInspectionCommand(args, store) {
6012
6477
  return ms > fromMs && ms <= toMs;
6013
6478
  };
6014
6479
  const allEvents = store.listEvents(task.id);
6015
- const events = allEvents.filter((e) => inWindow(e.createdAt));
6016
- const messages = store.listMessages(task.id).filter((m) => inWindow(m.createdAt));
6017
- const allTurns = store.listTurns(task.id);
6018
- const referencedTurnIds = new Set(referencedWakeTurnIds(allTurns, allEvents, events));
6019
- 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)));
6020
6496
  const lines = [
6021
6497
  `Wake: ${wake.id}`,
6022
6498
  `Task: ${task.id}`,
6023
6499
  `Status: ${wake.status}`,
6500
+ `Notification: ${deliveryEvents.at(-1)?.payload.outcome ?? "unobserved"}`,
6024
6501
  `Reasons: ${wake.reasons.map(renderWakeReason).join(", ")}`,
6025
6502
  `Delta window: ${wake.fromCursor} → ${wake.toCursor}`,
6026
- ...(wake.turnId === undefined ? [] : [`Turn: ${wake.turnId}`]),
6503
+ ...(wake.runId === undefined ? [] : [`AgentRun: ${wake.runId}`]),
6027
6504
  `Dispatched: ${presentTime(wake.createdAt, timeZone)}`,
6028
6505
  ...(wake.consumedAt === undefined
6029
6506
  ? []
@@ -6032,19 +6509,20 @@ function taskWakeInspectionCommand(args, store) {
6032
6509
  ...events.map((e) => ` ${e.id} ${e.type} ${presentTime(e.createdAt, timeZone)}`),
6033
6510
  `Messages (${messages.length}):`,
6034
6511
  ...messages.map((m) => ` ${m.id} [${taskMessageAuthorLabel(m.author)}] ${presentTime(m.createdAt, timeZone)}`),
6035
- `Turns (${turns.length}):`,
6036
- ...turns.map((turn) => (` ${turn.id} [${turn.status}/${turn.purpose}] ${turn.roleName} `
6037
- + `${presentTime(turn.createdAt, timeZone)}`
6038
- + `${referencedTurnIds.has(turn.id)
6039
- ? ` → 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}`
6040
6517
  : ""}`))
6041
6518
  ];
6042
6519
  return output(lines.join("\n").concat("\n"), {
6043
6520
  taskId: task.id,
6521
+ deliveryEvents,
6044
6522
  wake,
6045
6523
  events,
6046
6524
  messages,
6047
- turns
6525
+ runs
6048
6526
  });
6049
6527
  }
6050
6528
  throw usageError(command === undefined
@@ -6175,8 +6653,8 @@ function leaderMailbox(taskId) {
6175
6653
  function taskRef(id) {
6176
6654
  return { type: "task", id };
6177
6655
  }
6178
- function turnRef(taskId, id) {
6179
- return { type: "turn", taskId, id };
6656
+ function runRef(taskId, id) {
6657
+ return { type: "run", taskId, id };
6180
6658
  }
6181
6659
  function workItemRef(taskId, id) {
6182
6660
  return { type: "work-item", taskId, id };