@zq-silk/yui 0.15.7 → 0.15.9

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 (308) hide show
  1. package/ARCHITECTURE.md +194 -399
  2. package/ARCHITECTURE.zh-CN.md +151 -0
  3. package/README.md +306 -1131
  4. package/dist/agent/adapterCatalog.js +15 -2
  5. package/dist/agent/agent.js +23 -3
  6. package/dist/agent/argumentPolicy.js +7 -1
  7. package/dist/agent/connectionPlan.js +62 -0
  8. package/dist/agent/executionComponents.js +158 -0
  9. package/dist/agent/launchEnvironment.js +31 -3
  10. package/dist/agent/managedRuntimeEnvironment.js +3 -5
  11. package/dist/{turn/turn.js → agentRun/agentRun.js} +166 -109
  12. package/dist/{turn/turnIdentity.js → agentRun/runIdentity.js} +4 -4
  13. package/dist/artifacts/artifactCapability.js +74 -0
  14. package/dist/artifacts/artifactCommitLock.js +249 -0
  15. package/dist/artifacts/artifactPaths.js +151 -0
  16. package/dist/artifacts/gitArtifactRef.js +146 -0
  17. package/dist/artifacts/managedGit.js +332 -0
  18. package/dist/artifacts/taskArtifactRepository.js +277 -0
  19. package/dist/brief/taskBrief.js +12 -0
  20. package/dist/cli/agentConfigurationPicker.js +13 -0
  21. package/dist/cli/commandCatalog.js +165 -74
  22. package/dist/cli/interactionCandidates.js +5 -5
  23. package/dist/cli/interactionPolicy.js +38 -8
  24. package/dist/cli/invocationRouter.js +1 -1
  25. package/dist/cli/managedDiagnostics.js +28 -0
  26. package/dist/cli/operatorWizard.js +1 -7
  27. package/dist/cli/roleOptionOrder.js +27 -0
  28. package/dist/cli/roleWizard.js +50 -14
  29. package/dist/cli/updateOrchestrator.js +1 -1
  30. package/dist/cli/updatePorts.js +3 -4
  31. package/dist/cli.js +245 -95
  32. package/dist/commands/agentCommands.js +72 -14
  33. package/dist/commands/capabilityCommands.js +9 -6
  34. package/dist/commands/configCommands.js +20 -20
  35. package/dist/commands/deliveryGuardPreflight.js +2 -2
  36. package/dist/commands/executionAuditCommands.js +24 -24
  37. package/dist/commands/globalRoleCommands.js +1 -1
  38. package/dist/commands/grantCommands.js +4 -4
  39. package/dist/commands/operatorCommands.js +34 -9
  40. package/dist/commands/projectCommands.js +4 -4
  41. package/dist/commands/resourcesCommands.js +2 -2
  42. package/dist/commands/roleConfiguration.js +25 -5
  43. package/dist/commands/roleRuntimeGuard.js +4 -5
  44. package/dist/commands/sessionCommands.js +3 -7
  45. package/dist/commands/taskActivationCommands.js +281 -0
  46. package/dist/commands/taskActor.js +28 -49
  47. package/dist/commands/taskCommands.js +1461 -720
  48. package/dist/commands/taskContextCommand.js +39 -583
  49. package/dist/commands/taskExecutionCommands.js +32 -32
  50. package/dist/commands/taskInputCommands.js +40 -104
  51. package/dist/commands/taskIntegrationCommands.js +3 -2
  52. package/dist/commands/taskIntegrationQueueCommands.js +1 -1
  53. package/dist/commands/taskNextActionCommand.js +8 -8
  54. package/dist/commands/taskOverviewCommand.js +33 -45
  55. package/dist/commands/taskRemoteDeliveryCommand.js +2 -2
  56. package/dist/commands/taskRoleRuntimeStatus.js +133 -102
  57. package/dist/commands/telemetryCommands.js +36 -38
  58. package/dist/config/configCatalog.js +4 -4
  59. package/dist/config/yuiConfig.js +8 -8
  60. package/dist/context/contextSnapshot.js +10 -10
  61. package/dist/context/dispatchContext.js +11 -11
  62. package/dist/context/roleSessionContext.js +6 -3
  63. package/dist/context/{turnContextPack.js → runContextPack.js} +158 -81
  64. package/dist/context/{turnInputContract.js → runInputContract.js} +73 -60
  65. package/dist/context/sessionBootstrapManifest.js +21 -2
  66. package/dist/context/sourceRunContext.js +30 -0
  67. package/dist/context/taskContext.js +481 -0
  68. package/dist/context/wakeNotification.js +27 -27
  69. package/dist/controller/agentRuntimeObserver.js +21 -24
  70. package/dist/controller/capabilityBridge.js +17 -6
  71. package/dist/controller/clientRuntime.js +65 -92
  72. package/dist/controller/controller.js +120 -188
  73. package/dist/controller/fileSchedulerStoreAdapter.js +787 -887
  74. package/dist/controller/jobControl.js +54 -85
  75. package/dist/controller/resourceInventory.js +8 -27
  76. package/dist/controller/resourceInventoryLinux.js +12 -13
  77. package/dist/controller/runtime.js +529 -479
  78. package/dist/controller/runtimeEventInbox.js +55 -25
  79. package/dist/controller/runtimeEventProcessor.js +22 -31
  80. package/dist/controller/{runtimeHookTurnFence.js → runtimeHookRunFence.js} +91 -115
  81. package/dist/controller/runtimeLaunchCoordinator.js +80 -426
  82. package/dist/controller/runtimeObservationHook.js +14 -18
  83. package/dist/controller/sessionNotify.js +16 -24
  84. package/dist/controller/sessionOwnerReconciliation.js +168 -50
  85. package/dist/controller/structuredProviderObservation.js +138 -99
  86. package/dist/coordination/workMailbox.js +3 -3
  87. package/dist/coordination/workMailboxQueue.js +36 -33
  88. package/dist/core/boundedRpc.js +8 -1
  89. package/dist/core/controllerClient.js +20 -1
  90. package/dist/core/controllerServer.js +4 -4
  91. package/dist/doctor/doctor.js +13 -2
  92. package/dist/domain/agentResultTransport.js +9 -9
  93. package/dist/execution/codexThreadNaming.js +2 -8
  94. package/dist/execution/executionHealth.js +51 -63
  95. package/dist/execution/reviewMainRun.js +137 -0
  96. package/dist/execution/workItemExecution.js +28 -29
  97. package/dist/execution/workItemExecutionProjection.js +99 -107
  98. package/dist/execution/workItemMainRun.js +141 -0
  99. package/dist/executor/agentAdapter.js +227 -20
  100. package/dist/executor/agentConfigurationCatalog.js +126 -4
  101. package/dist/executor/agentConfigurationProbe.js +162 -4
  102. package/dist/executor/agentExecutor.js +79 -78
  103. package/dist/executor/effectiveLaunch.js +105 -18
  104. package/dist/executor/executorRegistry.js +29 -44
  105. package/dist/executor/fileRoleLaunchPlanner.js +229 -154
  106. package/dist/executor/workspacePreflightClassification.js +16 -16
  107. package/dist/grant/capabilityGrant.js +6 -3
  108. package/dist/input/inputRequest.js +12 -10
  109. package/dist/integration/gitIntegrationService.js +4 -11
  110. package/dist/integration/integrationQueueService.js +4 -4
  111. package/dist/interaction/operatorPresentation.js +1 -1
  112. package/dist/kernel/builtinCapabilities.js +255 -12
  113. package/dist/kernel/capabilityRegistry.js +64 -18
  114. package/dist/kernel/instanceHost.js +12 -1
  115. package/dist/kernel/kernelPorts.js +2 -2
  116. package/dist/lifecycle/canonicalLifecycleEvent.js +44 -49
  117. package/dist/lifecycle/exactRunTerminalization.js +449 -0
  118. package/dist/message/message.js +118 -6
  119. package/dist/message/messageContinuation.js +204 -0
  120. package/dist/observability/executionAudit.js +70 -72
  121. package/dist/observability/faultClassification.js +2 -2
  122. package/dist/observability/orchestrationMetrics.js +8 -8
  123. package/dist/operator/operatorSessionHistory.js +1 -7
  124. package/dist/output/agentConfigurationPresentation.js +8 -3
  125. package/dist/output/agentRunConfigurationPresentation.js +128 -0
  126. package/dist/output/rolePresentation.js +54 -3
  127. package/dist/plugins/pluginChild.js +104 -0
  128. package/dist/plugins/pluginIntent.js +26 -0
  129. package/dist/plugins/pluginInterpreter.js +43 -0
  130. package/dist/plugins/pluginPackage.js +101 -0
  131. package/dist/plugins/pluginProcess.js +112 -0
  132. package/dist/plugins/pluginService.js +388 -0
  133. package/dist/profile/agentProfile.js +1 -1
  134. package/dist/repository/gitWorkspace.js +26 -4
  135. package/dist/repository/project.js +19 -4
  136. package/dist/repository/taskBaseFreshness.js +13 -13
  137. package/dist/repository/taskWorkspaceCoordinator.js +20 -27
  138. package/dist/repository/taskWorkspacePreparer.js +344 -83
  139. package/dist/resources/autoResourceGc.js +3 -3
  140. package/dist/resources/liveReferences.js +3 -3
  141. package/dist/resources/projectResource.js +75 -0
  142. package/dist/resources/projectResourceService.js +343 -0
  143. package/dist/resources/resourceDiscovery.js +6 -6
  144. package/dist/resources/resourceGc.js +1 -1
  145. package/dist/resources/resourceRegistrar.js +1 -1
  146. package/dist/resources/resourceTypes.js +1 -1
  147. package/dist/review/deltaRecheck.js +3 -3
  148. package/dist/review/reviewAcceptance.js +16 -16
  149. package/dist/review/reviewDecision.js +7 -7
  150. package/dist/review/reviewRound.js +21 -20
  151. package/dist/review/reviewerAvailability.js +2 -2
  152. package/dist/role/role.js +51 -7
  153. package/dist/role/taskRoleUpdate.js +30 -0
  154. package/dist/runtime/acpProtocol.js +425 -0
  155. package/dist/runtime/acpSession.js +731 -0
  156. package/dist/runtime/acpSessionConfiguration.js +260 -0
  157. package/dist/runtime/agentDriver.js +30 -11
  158. package/dist/runtime/agentEndpoint.js +278 -0
  159. package/dist/runtime/agentEndpointIdentity.js +86 -0
  160. package/dist/runtime/agentEndpointOwnership.js +239 -0
  161. package/dist/runtime/agentError.js +2 -10
  162. package/dist/runtime/agentHost.js +565 -314
  163. package/dist/runtime/agentRunConfiguration.js +258 -0
  164. package/dist/runtime/builtinAgentDrivers.js +134 -18
  165. package/dist/runtime/builtinAgentErrorMappers.js +55 -3
  166. package/dist/runtime/builtinTranscriptUsage.js +1 -1
  167. package/dist/runtime/claude-process-owner +0 -0
  168. package/dist/runtime/codexAppServerRuntime.js +38 -30
  169. package/dist/runtime/codexInteractiveHost.js +41 -6
  170. package/dist/runtime/continuationManager.js +2 -6
  171. package/dist/runtime/executionEnvironment.js +30 -0
  172. package/dist/runtime/firstProgressAdvisory.js +11 -11
  173. package/dist/runtime/index.js +4 -3
  174. package/dist/runtime/jsonLineChannel.js +109 -0
  175. package/dist/runtime/launchBroker.js +91 -16
  176. package/dist/runtime/launchDiagnostics.js +2 -2
  177. package/dist/runtime/lifecycleReservation.js +10 -18
  178. package/dist/runtime/managedCaller.js +61 -17
  179. package/dist/runtime/nativeSessionControl.js +102 -0
  180. package/dist/runtime/ports.js +6 -21
  181. package/dist/runtime/processExitObservation.js +8 -7
  182. package/dist/runtime/promptEnvelope.js +17 -6
  183. package/dist/runtime/providerContinuation.js +3 -9
  184. package/dist/runtime/providerContinuationReconciliationService.js +4 -13
  185. package/dist/runtime/providerControl.js +2 -7
  186. package/dist/runtime/providerRuntimeIdentity.js +110 -222
  187. package/dist/runtime/providerRuntimeReconciler.js +5 -9
  188. package/dist/runtime/runtimeBinding.js +0 -1
  189. package/dist/runtime/runtimeContinuationProjection.js +4 -7
  190. package/dist/runtime/runtimeDeadlines.js +9 -0
  191. package/dist/runtime/runtimeHealthPolicy.js +1 -1
  192. package/dist/runtime/runtimeObservation.js +29 -65
  193. package/dist/runtime/runtimeProjection.js +43 -51
  194. package/dist/runtime/runtimeSessionCandidate.js +1 -3
  195. package/dist/runtime/sessionLaunchRequest.js +3 -7
  196. package/dist/runtime/sessionOwnerIdentity.js +7 -54
  197. package/dist/runtime/sessionOwnerRegistry.js +22 -17
  198. package/dist/runtime/sessionReconciliation.js +4 -8
  199. package/dist/runtime/sessionTerminationGuard.js +70 -259
  200. package/dist/runtime/sessionTokenMetrics.js +5 -16
  201. package/dist/runtime/structuredProviderHost.js +237 -117
  202. package/dist/runtime/taskRuntimeIsolation.js +39 -122
  203. package/dist/runtime/tmuxAdapters.js +39 -86
  204. package/dist/scheduler/activeRoleRunDelivery.js +354 -0
  205. package/dist/scheduler/leaderWakeupProcessor.js +75 -266
  206. package/dist/scheduler/operatorInputNotificationProcessor.js +1 -1
  207. package/dist/scheduler/ports.js +80 -9
  208. package/dist/scheduler/{roleTurnLiveness.js → roleRunLiveness.js} +26 -30
  209. package/dist/scheduler/{roleTurnStall.js → roleRunStall.js} +128 -139
  210. package/dist/scheduler/taskExecutionProjection.js +120 -124
  211. package/dist/scheduler/taskObservabilityProjection.js +29 -29
  212. package/dist/scheduler/taskWake.js +11 -4
  213. package/dist/scheduler/wakeReason.js +9 -1
  214. package/dist/setup/setupCommand.js +3 -7
  215. package/dist/storage/migrations/agentRunContract.js +159 -0
  216. package/dist/storage/migrations/artifactsToGit.js +338 -0
  217. package/dist/storage/migrations/removeRuntimeGeneration.js +207 -0
  218. package/dist/storage/migrations/submitIntent.js +126 -0
  219. package/dist/storage/sqliteSchema.js +467 -7
  220. package/dist/storage/sqliteStore.js +355 -220
  221. package/dist/storage/storageVersions.js +1 -1
  222. package/dist/storage/storeRpc.js +10 -5
  223. package/dist/storage/taskStore.js +13 -11
  224. package/dist/storage/upgrade/upgradeOrchestrator.js +5 -7
  225. package/dist/surface/surfaceContributions.js +102 -0
  226. package/dist/task/completionReadiness.js +32 -6
  227. package/dist/task/deliveryGuard.js +16 -16
  228. package/dist/task/draftPlan.js +72 -12
  229. package/dist/task/nextAction.js +144 -128
  230. package/dist/task/remoteDelivery.js +6 -6
  231. package/dist/task/task.js +184 -18
  232. package/dist/task/taskActivation.js +327 -0
  233. package/dist/task/taskActivationService.js +408 -0
  234. package/dist/task/taskRecordReference.js +5 -4
  235. package/dist/task/taskRecordRetirement.js +1 -1
  236. package/dist/task/taskSubmission.js +236 -0
  237. package/dist/telemetry/sqliteTelemetryStore.js +55 -68
  238. package/dist/telemetry/telemetryConfig.js +14 -14
  239. package/dist/telemetry/telemetryWiring.js +2 -2
  240. package/dist/web/assets/assetManifest.js +2 -0
  241. package/dist/web/assets/client/app.js +121 -20
  242. package/dist/web/assets/client/components.js +87 -54
  243. package/dist/web/assets/client/i18n.js +83 -41
  244. package/dist/web/assets/client/markdown.js +1 -1
  245. package/dist/web/assets/client/taskSurface.js +442 -0
  246. package/dist/web/assets/client/view.js +49 -44
  247. package/dist/web/assets/shell.js +1 -1
  248. package/dist/web/assets/styles/cards.js +22 -4
  249. package/dist/web/controllerWeb.js +60 -0
  250. package/dist/web/webMutation.js +28 -0
  251. package/dist/web/webServer.js +133 -8
  252. package/dist/web/webSnapshot.js +81 -74
  253. package/dist/web/webTaskSurface.js +64 -0
  254. package/dist/workItem/dependencyGate.js +1 -1
  255. package/dist/workItem/workItem.js +84 -48
  256. package/dist/workspace/workItemChangeSetManager.js +16 -9
  257. package/docs/agent-result-consumption.md +96 -0
  258. package/docs/agent-result-consumption.zh-CN.md +81 -0
  259. package/docs/agent-runtime-drivers.md +93 -0
  260. package/docs/agent-runtime-drivers.zh-CN.md +77 -0
  261. package/docs/architecture/README.md +50 -0
  262. package/docs/architecture/README.zh-CN.md +43 -0
  263. package/docs/architecture/capabilities-and-resources.md +118 -0
  264. package/docs/architecture/capabilities-and-resources.zh-CN.md +83 -0
  265. package/docs/managed-turn-and-session-runtime.md +224 -0
  266. package/docs/managed-turn-and-session-runtime.zh-CN.md +180 -0
  267. package/docs/observability/README.md +83 -0
  268. package/docs/observability/README.zh-CN.md +71 -0
  269. package/docs/plugin-sdk.md +393 -0
  270. package/docs/plugin-sdk.zh-CN.md +293 -0
  271. package/docs/provider-runtime.md +165 -0
  272. package/docs/provider-runtime.zh-CN.md +132 -0
  273. package/docs/release-workflow.md +305 -0
  274. package/docs/release-workflow.zh-CN.md +237 -0
  275. package/docs/roles-and-configuration.md +115 -0
  276. package/docs/roles-and-configuration.zh-CN.md +96 -0
  277. package/docs/sqlite-control-plane-design.md +78 -0
  278. package/docs/sqlite-control-plane-design.zh-CN.md +62 -0
  279. package/docs/task-dag-semantics.md +80 -0
  280. package/docs/task-dag-semantics.zh-CN.md +59 -0
  281. package/docs/task-delivery.md +105 -0
  282. package/docs/task-delivery.zh-CN.md +82 -0
  283. package/docs/task-local-identity.md +8 -6
  284. package/docs/task-local-identity.zh-CN.md +58 -0
  285. package/docs/testing/verification-levels.md +88 -0
  286. package/docs/testing/verification-levels.zh-CN.md +69 -0
  287. package/i18n/README.zh-CN.md +270 -722
  288. package/package.json +3 -2
  289. package/skills/yui-leader/SKILL.md +88 -304
  290. package/skills/yui-leader/references/execution.md +303 -0
  291. package/skills/yui-leader/references/integration.md +39 -0
  292. package/skills/yui-leader/references/planning.md +109 -0
  293. package/skills/yui-leader/references/replicated-execution.md +42 -0
  294. package/skills/yui-leader/references/task-plugins.md +37 -0
  295. package/skills/yui-operator/SKILL.md +46 -62
  296. package/skills/yui-reviewer/SKILL.md +35 -36
  297. package/skills/yui-runtime/SKILL.md +88 -24
  298. package/skills/yui-runtime/references/publication.md +22 -0
  299. package/skills/yui-runtime/references/recovery.md +64 -0
  300. package/skills/yui-worker/SKILL.md +37 -39
  301. package/dist/cli/roleOptionCatalog.js +0 -68
  302. package/dist/context/sourceTurnContext.js +0 -30
  303. package/dist/execution/reviewMainTurn.js +0 -161
  304. package/dist/execution/workItemMainTurn.js +0 -164
  305. package/dist/lifecycle/exactTurnTerminalization.js +0 -407
  306. package/dist/runtime/preallocatedNativeSession.js +0 -13
  307. package/dist/runtime/runtimeStopReceipt.js +0 -42
  308. package/dist/scheduler/activeRoleTurnDelivery.js +0 -315
@@ -1,27 +1,33 @@
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, withSubmissionReceipt, TASK_SUBMISSION_INTENTS } from "../message/message.js";
20
24
  import { assertDraftTaskExecutionFree, validateDraftWorkItemEdit } from "../task/draftPlan.js";
25
+ import { TASK_PLANNING_ENTERED_EVENT, decideSubmissionRouting, describeSubmissionFeedback, draftActivationState, draftHasEnteredPlanning, normalizeSubmissionIntent, sameSubmissionTarget } from "../task/taskSubmission.js";
26
+ import { recordTaskActivationRequestInTransaction, taskActivationOperationRef } from "../task/taskActivationService.js";
21
27
  import { cancelInputRequest } from "../input/inputRequest.js";
22
- import { retireExactActiveTurn, terminalizeExactTaskTurn, validateExactTurnReviewRound } from "../lifecycle/exactTurnTerminalization.js";
28
+ import { retireExactActiveRun, terminalizeExactTaskRun, validateExactRunReviewRound } from "../lifecycle/exactRunTerminalization.js";
23
29
  import { copyGlobalRoleToTaskRole, createRole, createRoleAgentBinding, switchActiveRoleAgent, unbindRoleAgent, updateRole } from "../role/role.js";
24
- import { createTurn, withTurnContextSnapshot } from "../turn/turn.js";
30
+ import { createRun, runPurposeAdmitsTaskState, runExecutionObservation, withRunContextSnapshot } from "../agentRun/agentRun.js";
25
31
  import { createReviewRound, createTaskReviewRound, createTaskDeltaReviewRound, attachReviewExecutionGroup, finishReviewRound, recordReviewWorkspaceDisposition, retryReviewRound, retryRunningReviewExecutionLane, retryTaskReviewRound, startReplicatedReviewRound, startReviewRound, updateReviewExecutionGroup, validateTaskReviewCandidate } from "../review/reviewRound.js";
26
32
  import { buildDeltaRecheckDispatchContext, verifyDeltaRecheckDiff } from "../review/deltaRecheck.js";
27
33
  import { isCompletedTaskReviewEvidence } from "../review/reviewAcceptance.js";
@@ -30,38 +36,45 @@ import { createTaskBrief, updateTaskBrief } from "../brief/taskBrief.js";
30
36
  import { createDecision, supersedeDecision } from "../decision/decision.js";
31
37
  import { createMilestone } from "../milestone/milestone.js";
32
38
  import { runPublicationCommand } from "./taskPublicationCommands.js";
39
+ import { runTaskActivationCommand, nonLeaderActivationIdentity } from "./taskActivationCommands.js";
33
40
  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";
41
+ import { enqueueRoleRunDispatch, enqueueWork, settleExactWorkExecution } from "../coordination/workMailboxQueue.js";
42
+ import { completeProcessing } from "../coordination/workMailbox.js";
43
+ import { runtimeLifecycleTarget, RUNTIME_SESSION_REPLACE_REQUIRED_REASON } from "../runtime/lifecycleReservation.js";
37
44
  import { projectProviderContinuations } from "../runtime/runtimeContinuationProjection.js";
38
45
  import { runtimeObservationFromTaskEvent } from "../runtime/runtimeObservation.js";
39
- import { addTaskProjectBinding, archiveTask, completeTask, createTask, retireTask, reopenTask, updateTaskMetadata } from "../task/task.js";
46
+ import { addTaskProjectBinding, archiveTask, completeTask, createTask, retireTask, reopenTask, taskOwnsManagedWorkspace, updateTaskMetadata } from "../task/task.js";
40
47
  import { resolveTaskRecordReference } from "../task/taskRecordReference.js";
41
48
  import { projectCompletionReadiness } from "../task/completionReadiness.js";
49
+ import { StorageConflictError } from "../storage/taskStore.js";
42
50
  import { requireResolvedAgentProfileRuntime } from "../profile/agentProfileRuntime.js";
43
51
  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";
52
+ import { currentWorkItemCandidate, currentWorkItemExecutionGroup, workItemExecutionGroupById, createWorkItem, editWorkItemDefinition, attachWorkItemExecutionGroup, updateWorkItemExecutionGroup, retireWorkItem, prepareWorkItemDispatch, submitWorkItemCandidate, updateWorkItemWriteProjects, updateWorkItemStatus } from "../workItem/workItem.js";
45
53
  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";
54
+ import { createExecutionGroup, createReviewExecutionAssignment, createWorkItemExecutionAssignment, createWorkItemExecutionGroup, updateExecutionLane as updateUnifiedExecutionLane, updateWorkItemExecutionLane, workItemExecutionGroupSettled } from "../execution/workItemExecution.js";
55
+ import { dispatchWorkItemSynthesis, selectedWorkItemSynthesisProducers } from "../execution/workItemMainRun.js";
56
+ import { dispatchReviewSynthesis, selectedReviewSynthesisProducers } from "../execution/reviewMainRun.js";
57
+ import { synthesisSourceRunIds } from "../context/runContextPack.js";
49
58
  import { projectWorkItemExecution } from "../execution/workItemExecutionProjection.js";
50
59
  import { sameTaskFinalReviewContract, taskFinalReviewConfig, validateTaskFinalReviewContract } from "../review/taskFinalReviewContract.js";
51
60
  import { resolveRecordedTaskFinalReviewContract } from "../review/taskFinalReviewContractResolution.js";
52
61
  import { managedWorkspaceKey } from "../worktree/managedWorkspace.js";
53
62
  import { hasAgentConfigOptions, hasNoRoleMutation, parseRoleOptions, patchRoleAgentBinding, roleOptionSpecs, roleProfilePatch } from "./roleConfiguration.js";
54
63
  import { hasRoleLaunchContextOptions, validateConfiguredRoleSkills } from "./roleSkillValidation.js";
55
- import { assertLiveRoleSessionAcknowledged, assertRoleRuntimeMutationAllowed, LIVE_SESSION_ACKNOWLEDGEMENT_OPTION } from "./roleRuntimeGuard.js";
64
+ import { assertRoleRuntimeMutationAllowed } from "./roleRuntimeGuard.js";
56
65
  import { runTaskContextCommand } from "./taskContextCommand.js";
66
+ import { listContextMessages } from "../context/taskContext.js";
67
+ import { createProjectResources } from "../resources/projectResourceService.js";
68
+ import { isGitArtifactRefString, parseGitArtifactRef } from "../artifacts/gitArtifactRef.js";
57
69
  import { runTaskNextActionCommand } from "./taskNextActionCommand.js";
58
70
  import { runDeliveryGuardPreflight, withGuardWarnings } from "./deliveryGuardPreflight.js";
59
- import { inspectTaskRoleRuntimeStatuses, renderTaskRoleRuntimeStatus, taskRoleActiveWorkLabel, taskRoleLastTurnLabel, taskRoleNativeSessionLabel, taskRoleOpenInputLabel, taskRoleTmuxLabel } from "./taskRoleRuntimeStatus.js";
71
+ import { inspectTaskRoleRuntimeStatuses, renderTaskRoleRuntimeStatus, taskRoleActiveWorkLabel, taskRoleLastRunLabel, taskRoleNativeSessionLabel, taskRoleOpenInputLabel, taskRoleTmuxLabel } from "./taskRoleRuntimeStatus.js";
60
72
  import { assertNoOpenInputRequests, openInputRequestCount, runTaskInputCommand } from "./taskInputCommands.js";
61
73
  import { runGrantCommand } from "./grantCommands.js";
62
74
  import { runWorkflowCommand } from "./workflowCommands.js";
63
- import { taskLocalActor as resolveTaskLocalActor, taskLeaderActionTurnId } from "./taskActor.js";
64
- import { currentManagedRuntime } from "../runtime/managedCaller.js";
75
+ import { taskLocalActor as resolveTaskLocalActor, assertTaskDeliveryAuthority } from "./taskActor.js";
76
+ import { currentManagedRuntime, resolveManagedTaskReader } from "../runtime/managedCaller.js";
77
+ import { resolveMessageRecipient, messageContinuationBlocker } from "../message/messageContinuation.js";
65
78
  import { enqueueOperatorEvent } from "../scheduler/operatorEvent.js";
66
79
  import { queueLeaderWakeup } from "../scheduler/wakeupQueue.js";
67
80
  import { renderWakeReason, wakeReason } from "../scheduler/wakeReason.js";
@@ -181,8 +194,11 @@ export function previewTaskRoleAgentConfigurationMutation(args, store) {
181
194
  }
182
195
  const parsed = parseRoleOptions(tail, new Map([
183
196
  ...roleOptionSpecs({ update: true, includeAgent: true }),
184
- ["--profile", "value"]
197
+ ["--profile", "value"],
198
+ ["--environment", "value"],
199
+ ["--managed-environment", "flag"]
185
200
  ]), usage);
201
+ validateTaskRoleEnvironmentOptions(parsed, usage);
186
202
  if (parsed.has("--agent") && (parsed.one("--agent")?.trim().length ?? 0) === 0) {
187
203
  throw usageError("--agent is required.", usage);
188
204
  }
@@ -201,7 +217,7 @@ export function previewTaskRoleAgentConfigurationMutation(args, store) {
201
217
  }
202
218
  export function parseTaskCompletionRequest(args, summaryOverride) {
203
219
  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"]));
220
+ const parsed = parseMultiValueTail(args, new Set(["--summary", "--summary-file", "--accept-published-tree"]), new Set(["--artifact-ref"]), usage, new Set(["--refresh-remote"]));
205
221
  exactPositionals(parsed.positionals, 1, usage);
206
222
  const inlineSummary = parsed.options.get("--summary");
207
223
  const summaryFile = parsed.options.get("--summary-file");
@@ -213,6 +229,7 @@ export function parseTaskCompletionRequest(args, summaryOverride) {
213
229
  return {
214
230
  taskId: parsed.positionals[0],
215
231
  summary,
232
+ artifactRefs: parsed.multiOptions.get("--artifact-ref") ?? [],
216
233
  ...(acceptedPublishedTreePublicationId === undefined
217
234
  ? {}
218
235
  : { acceptedPublishedTreePublicationId })
@@ -233,6 +250,7 @@ export function parseTaskCompletionRequest(args, summaryOverride) {
233
250
  export function preflightTaskCompletion(taskId, store, options = {}, request = {}) {
234
251
  const task = requireTask(store, taskId);
235
252
  const actor = taskActor(store, options, task.id);
253
+ assertTaskDeliveryAuthority(store, options.environment, task.id);
236
254
  if (task.status === "completed") {
237
255
  return { task, actor, completed: true, activeTaskReview: false };
238
256
  }
@@ -279,19 +297,29 @@ function formatCompletionBlockers(taskId, blockers) {
279
297
  }
280
298
  export function runTaskCommand(args, store, options = {}) {
281
299
  const [command, ...rest] = args;
300
+ const delivery = command === "complete"
301
+ || (command === "work" && ["dispatch", "synthesize", "review", "accept"].includes(rest[0] ?? ""))
302
+ || (command === "review" && !["list", "show"].includes(rest[0] ?? ""))
303
+ || ((command === "run" || command === "run") && rest[0] === "retry");
304
+ if (delivery && options.environment?.YUI_SESSION_SCOPE === "task"
305
+ && options.environment.YUI_TASK_ID !== undefined) {
306
+ assertTaskDeliveryAuthority(store, options.environment, options.environment.YUI_TASK_ID);
307
+ }
282
308
  switch (command) {
283
309
  case "create": return createTaskCommand(rest, store, options);
284
310
  case "update": return output(updateTaskCommand(rest, store, options));
285
311
  case "list": return listTaskCommand(rest, store);
286
312
  case "show": return showTaskCommand(rest, store, options.actualTaskReviewCandidate ?? null);
287
- case "context": return runTaskContextCommand(rest, store, options.actualTaskReviewCandidate ?? null);
313
+ case "context": return runTaskContextCommand(rest, store, options.environment);
288
314
  case "next-action": return runTaskNextActionCommand(rest, store, options.actualTaskReviewCandidate ?? null);
289
315
  case "remote-delivery": return runTaskRemoteDeliveryCommand(rest, store, options.actualTaskReviewCandidate ?? null);
290
316
  case "activate": return output(activateTaskCommand(rest, store, options));
317
+ case "activation": return runTaskActivationCommand(rest, store, options);
291
318
  case "complete": return completeTaskCommand(rest, store, options);
292
319
  case "reopen": return output(reopenTaskCommand(rest, store, options));
293
320
  case "archive": return output(archiveTaskCommand(rest, store, options));
294
321
  case "retire": return retireTaskCommand(rest, store, options);
322
+ case "cancel": return cancelTaskCommand(rest, store, options);
295
323
  case "reconcile": return output(reconcileTaskCommand(rest, store, options));
296
324
  case "message": {
297
325
  const execution = taskMessageCommand(rest, store, options);
@@ -306,7 +334,8 @@ export function runTaskCommand(args, store, options = {}) {
306
334
  case "role": return taskRoleCommand(rest, store, options);
307
335
  case "work": return taskWorkCommand(rest, store, options);
308
336
  case "review": return taskReviewCommand(rest, store, options);
309
- case "turn": return taskTurnCommand(rest, store, options);
337
+ case "run":
338
+ case "run": return taskRunCommand(rest, store, options);
310
339
  case "brief": return taskBriefCommand(rest, store, options);
311
340
  case "decision": return taskDecisionCommand(rest, store, options);
312
341
  case "milestone": return taskMilestoneCommand(rest, store, options);
@@ -356,21 +385,21 @@ function workItemCandidateProducerRoles(store, item, candidate) {
356
385
  roles.add(LEADER_ROLE);
357
386
  return roles;
358
387
  }
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}.`);
388
+ const sourceRun = store.getRun(item.taskId, candidate.source.runId);
389
+ if (sourceRun === null
390
+ || sourceRun.workItemId !== item.id
391
+ || sourceRun.purpose !== "execution"
392
+ || sourceRun.status !== "completed") {
393
+ throw dataError(`WorkItem Candidate producer AgentRun is unavailable: ${item.id}/${candidate.source.runId}.`);
365
394
  }
366
- roles.add(sourceTurn.roleName);
367
- if (sourceTurn.sourceExecutionGroupId !== undefined) {
368
- const group = workItemExecutionGroupById(item, sourceTurn.sourceExecutionGroupId);
395
+ roles.add(sourceRun.roleName);
396
+ if (sourceRun.sourceExecutionGroupId !== undefined) {
397
+ const group = workItemExecutionGroupById(item, sourceRun.sourceExecutionGroupId);
369
398
  if (group === undefined) {
370
399
  throw dataError(`WorkItem Candidate ExecutionGroup is unavailable: `
371
- + `${item.id}/${sourceTurn.sourceExecutionGroupId}.`);
400
+ + `${item.id}/${sourceRun.sourceExecutionGroupId}.`);
372
401
  }
373
- for (const producer of successfulWorkItemSynthesisProducers(store, item, group)) {
402
+ for (const producer of selectedWorkItemSynthesisProducers(store, item, group, synthesisSourceRunIds(store, sourceRun))) {
374
403
  roles.add(producer.roleName);
375
404
  }
376
405
  }
@@ -506,6 +535,8 @@ export function updateTaskMetadataCommand(store, taskId, patch, options = {}) {
506
535
  tx.saveTask(updated);
507
536
  recordTaskEvent(tx, updated.id, "task.updated", {
508
537
  status: updated.status,
538
+ previous: editedFieldValues(current, Object.keys(patch)),
539
+ current: editedFieldValues(updated, Object.keys(patch)),
509
540
  ...(updated.type === undefined ? {} : { taskType: updated.type })
510
541
  }, now);
511
542
  enqueueWork(tx, taskMailbox(updated.id), "task-updated", now, [taskRef(updated.id)]);
@@ -514,26 +545,273 @@ export function updateTaskMetadataCommand(store, taskId, patch, options = {}) {
514
545
  notifyMailbox(options.runtime, taskMailbox(result.id), result.id);
515
546
  return result;
516
547
  }
517
- export function submitOperatorMessage(body, taskId, store, options = {}) {
548
+ function routeUserSubmission(tx, task, actor, body, intent, now, submissionKey, target) {
549
+ const kind = actor;
550
+ const author = actor === "operator"
551
+ ? { type: "operator" }
552
+ : { type: "user" };
553
+ // §2.3 keyed idempotency: a retry is detected by reading an existing keyed
554
+ // Message, and its original disposition is reproduced from the receipt that
555
+ // Message carries — never recomputed from current state. A matching key with
556
+ // matching input and target replays; any mismatch conflicts.
557
+ if (submissionKey !== undefined) {
558
+ const prior = tx.listMessages(task.id).find((message) => message.submissionKey === submissionKey);
559
+ if (prior !== undefined) {
560
+ return replayKeyedSubmission(task, prior, kind, body, intent, target);
561
+ }
562
+ }
563
+ // Save first, recording intent and key, so the "saved" facet holds regardless of
564
+ // how routing then resolves (§2.5). Intent is stored, never re-read from the body.
565
+ const message = appendMessage(tx, task.id, body, kind, author, now, {
566
+ intent,
567
+ ...(submissionKey === undefined ? {} : { submissionKey })
568
+ });
569
+ // Re-read phase and activation inside this same transaction: this is the point
570
+ // the race is decided at (§2.3).
571
+ const enteredPlanning = draftHasEnteredPlanning(tx, task);
572
+ const activation = draftActivationState(task.activationRequest);
573
+ const routing = decideSubmissionRouting({
574
+ intent,
575
+ status: task.status,
576
+ enteredPlanning,
577
+ activation,
578
+ executionEnabled: task.executionGate.state === "enabled",
579
+ // develop adopts no extra resource by guessing: the workspace is still built
580
+ // from the Task's own Project bindings when it activates (§2.3).
581
+ developEnvironmentPlan: { kind: "empty" }
582
+ });
583
+ let queuedForLeader = false;
584
+ let activationRef;
585
+ let activationFailure;
586
+ switch (routing.kind) {
587
+ case "record":
588
+ case "planned-needs-manual-activation":
589
+ case "activation-blocked-execution-stopped":
590
+ // Save only. No Leader wake, no planning, no activation.
591
+ break;
592
+ case "await-activation":
593
+ // The submission is a post-activation input or a report against an existing
594
+ // request; it acts on nothing itself but surfaces the exact reference.
595
+ activationRef = task.activationRequest === undefined
596
+ ? undefined
597
+ : taskActivationOperationRef(task.id, task.activationRequest.operation.requestId);
598
+ if (routing.state === "failed") {
599
+ activationFailure = task.activationRequest?.outcome;
600
+ }
601
+ break;
602
+ case "enter-planning":
603
+ // The one place the shared service itself writes the planning-entered fact,
604
+ // in the same transaction as the message (§2.2 derivation source (a)).
605
+ recordTaskEvent(tx, task.id, TASK_PLANNING_ENTERED_EVENT, {
606
+ messageId: message.id,
607
+ intent
608
+ }, now);
609
+ enqueueWork(tx, leaderMailbox(task.id), submissionEnqueueReason(actor), now, [messageRef(task.id, message.id)], { source: actor, dedupeKey: `message:${task.id}:${message.id}` });
610
+ queuedForLeader = true;
611
+ break;
612
+ case "continue-planning":
613
+ case "active-context":
614
+ enqueueWork(tx, leaderMailbox(task.id), submissionEnqueueReason(actor), now, [messageRef(task.id, message.id)], { source: actor, dedupeKey: `message:${task.id}:${message.id}` });
615
+ queuedForLeader = true;
616
+ break;
617
+ case "activate": {
618
+ // Record the activation in-transaction, then queue only activation
619
+ // processing. The requestId is derived from the message so a retried
620
+ // submission cannot mint a second request.
621
+ const identity = nonLeaderActivationIdentity(tx, actor);
622
+ recordTaskActivationRequestInTransaction(tx, {
623
+ taskId: task.id,
624
+ requestId: `submit-${message.id}`,
625
+ actorId: identity.actorId,
626
+ authorityRef: identity.authorityRef,
627
+ environmentPlan: routing.environmentPlan,
628
+ origin: "submit-develop"
629
+ }, now);
630
+ activationRef = taskActivationOperationRef(task.id, `submit-${message.id}`);
631
+ enqueueWork(tx, taskMailbox(task.id), "activation-requested", now, [taskRef(task.id)]);
632
+ break;
633
+ }
634
+ default: {
635
+ const exhaustive = routing;
636
+ throw new Error(`Unhandled submission routing: ${JSON.stringify(exhaustive)}`);
637
+ }
638
+ }
639
+ const feedback = describeSubmissionFeedback({
640
+ taskId: task.id,
641
+ messageId: message.id,
642
+ status: task.status,
643
+ enteredPlanning,
644
+ activationState: activation,
645
+ routing,
646
+ ...(activationRef === undefined ? {} : { activationRef }),
647
+ ...(activationFailure === undefined ? {} : { activationFailure })
648
+ });
649
+ // §2.3 receipt: a keyed submission records the disposition it actually received
650
+ // (routing + feedback) and the target its key bound to, in this same
651
+ // transaction, so a later retry reproduces exactly this outcome rather than
652
+ // re-deriving one from a phase or activation that has since changed.
653
+ if (submissionKey !== undefined && target !== undefined) {
654
+ const receipted = withSubmissionReceipt(message, { target, routing, feedback });
655
+ tx.updateMessage(task.id, receipted);
656
+ return { task, message: receipted, routing, feedback, queuedForLeader };
657
+ }
658
+ return { task, message, routing, feedback, queuedForLeader };
659
+ }
660
+ /**
661
+ * Reproduce a keyed submission's original outcome from the receipt it recorded
662
+ * (task-32 §2.3), never by recomputing from the Task's current state. The prior
663
+ * Message's frozen {@link SubmissionReceipt} names the routing it received and the
664
+ * §2.5 feedback it returned, so a gate enabled or an activation cancelled after
665
+ * the fact can no longer fabricate a different disposition. The replay writes
666
+ * nothing — any Leader wake or activation request the original made already
667
+ * exists — and preserves the original Task/Message/routing/request references
668
+ * exactly.
669
+ *
670
+ * Same key with a different normalized input (kind, body or intent) or a
671
+ * different target is a conflict, never a silent overwrite or a cross-target
672
+ * replay. A prior keyed Message that carries no receipt (an old client, before
673
+ * receipts existed) also conflicts rather than being replayed from a rebuilt
674
+ * disposition.
675
+ */
676
+ function replayKeyedSubmission(task, prior, kind, body, intent, target) {
677
+ const receipt = prior.submissionReceipt;
678
+ if (prior.kind !== kind
679
+ || prior.body !== body
680
+ || normalizeSubmissionIntent(prior.intent) !== intent
681
+ || receipt === undefined
682
+ || (target !== undefined && !sameSubmissionTarget(receipt.target, target))) {
683
+ throw new StorageConflictError(`Submission key ${prior.submissionKey} was already used for a different submission on ${task.id}/${prior.id}.`);
684
+ }
685
+ // Reproduce the recorded disposition verbatim; a retry acts on nothing itself.
686
+ return {
687
+ task,
688
+ message: prior,
689
+ routing: receipt.routing,
690
+ feedback: receipt.feedback,
691
+ queuedForLeader: false
692
+ };
693
+ }
694
+ /** The mailbox reason a submission wake carries, preserving the existing
695
+ * operator-input / user-message vocabulary the Controller already understands. */
696
+ function submissionEnqueueReason(actor) {
697
+ return actor === "operator" ? "operator-input" : "user-message";
698
+ }
699
+ /**
700
+ * Render the §2.5 feedback as CLI text, one facet per line, so the user always
701
+ * sees the save, the phase, planning, activation, delivery and next step as
702
+ * separate statements — never a single "started". Web and capability callers
703
+ * return {@link SubmissionFeedback} structurally instead of this text.
704
+ */
705
+ function renderSubmissionFeedback(feedback) {
706
+ const lines = [
707
+ `Saved message ${feedback.saved.messageId} to ${feedback.saved.taskId}.`,
708
+ `Phase: ${SUBMISSION_PHASE_LABEL[feedback.phase]}.`
709
+ ];
710
+ if (feedback.planning !== "none") {
711
+ lines.push(`Planning: ${feedback.planning === "entered"
712
+ ? "started; the Leader is queued to plan"
713
+ : "continued; the Leader is queued"}.`);
714
+ }
715
+ if (feedback.activation !== "none") {
716
+ lines.push(`Activation: ${SUBMISSION_ACTIVATION_LABEL[feedback.activation]}.`);
717
+ }
718
+ if (feedback.delivery === "queued" && feedback.planning === "none") {
719
+ lines.push("Delivery: queued to the Leader.");
720
+ }
721
+ const nextStep = feedback.nextStep;
722
+ if (nextStep !== undefined)
723
+ lines.push(`Next: ${renderSubmissionNextStep(nextStep)}`);
724
+ return `${lines.join("\n")}\n`;
725
+ }
726
+ const SUBMISSION_PHASE_LABEL = {
727
+ active: "active",
728
+ "draft-planning": "Draft, in planning",
729
+ "draft-unplanned": "Draft, not yet in planning"
730
+ };
731
+ const SUBMISSION_ACTIVATION_LABEL = {
732
+ none: "none",
733
+ requested: "requested; activation is queued",
734
+ pending: "already pending; this input waits for it",
735
+ failed: "the previous request failed",
736
+ "manual-required": "already planned; activate explicitly to develop",
737
+ "execution-stopped": "not requested; execution is stopped"
738
+ };
739
+ function renderSubmissionNextStep(step) {
740
+ switch (step.kind) {
741
+ case "activate-manually":
742
+ return `activate it explicitly with "yui task activate ${step.taskId}".`;
743
+ case "start-execution":
744
+ return `start execution with "yui task execution start ${step.taskId}", then submit develop again.`;
745
+ case "await-pending-activation":
746
+ return `wait for the pending activation ${step.activationRef}.`;
747
+ case "resolve-failed-activation":
748
+ return `retry with a new activation request or cancel ${step.activationRef} (failure: ${step.failure}).`;
749
+ default: {
750
+ const exhaustive = step;
751
+ throw new Error(`Unhandled submission next step: ${JSON.stringify(exhaustive)}`);
752
+ }
753
+ }
754
+ }
755
+ /** Parse and validate the CLI `--intent` option. Absent leaves it undefined so
756
+ * the shared service applies the discuss default (task-32 §2.5). */
757
+ function parseSubmissionIntentOption(raw, usage) {
758
+ if (raw === undefined)
759
+ return undefined;
760
+ if (TASK_SUBMISSION_INTENTS.includes(raw)) {
761
+ return raw;
762
+ }
763
+ throw usageError(`--intent must be one of ${TASK_SUBMISSION_INTENTS.join(", ")}: ${raw}.`, usage);
764
+ }
765
+ export function submitOperatorMessage(body, taskId, store, options = {}, intent, submissionKey) {
518
766
  const now = clock(options);
767
+ const effectiveIntent = normalizeSubmissionIntent(intent);
519
768
  const result = store.transaction((tx) => {
520
769
  if (taskId !== undefined) {
770
+ // Addressed submit: the key is scoped to this Task and dedups within it, so
771
+ // no other Task is read (§2.3). Its target is this Task.
521
772
  const task = requireTask(tx, taskId);
522
773
  assertTaskOpen(task);
523
- const message = appendMessage(tx, task.id, body, "operator", { type: "operator" }, now);
524
- if (task.status === "active") {
525
- enqueueWork(tx, leaderMailbox(task.id), "operator-input", now, [messageRef(task.id, message.id)]);
774
+ const routed = routeUserSubmission(tx, task, "operator", body, effectiveIntent, now, submissionKey, { kind: "task", taskId: task.id });
775
+ return { ...routed, created: false };
776
+ }
777
+ // Task-less submit: the key is scoped to "create a new Task". A retry must
778
+ // locate the Draft the original create produced — not any same-key Message on
779
+ // an addressed Task — and a key already bound to a specific Task cannot be
780
+ // reused to create (§2.3 "同 key 不同目标冲突").
781
+ if (submissionKey !== undefined) {
782
+ const lookup = findSubmissionKeyCreate(tx, submissionKey);
783
+ if (lookup.kind === "create") {
784
+ const task = requireTask(tx, lookup.taskId);
785
+ const routed = routeUserSubmission(tx, task, "operator", body, effectiveIntent, now, submissionKey, { kind: "create" });
786
+ return { ...routed, created: false };
787
+ }
788
+ if (lookup.kind === "other-target") {
789
+ throw new StorageConflictError(`Submission key ${submissionKey} is already bound to a specific Task; a task-less create cannot reuse it.`);
526
790
  }
527
- return { task, message, created: false };
528
791
  }
529
- const created = createTaskAggregate(tx, titleFrom(body), {}, now);
530
- const message = appendMessage(tx, created.task.id, body, "operator", { type: "operator" }, now);
531
- return { ...created, message, created: true };
792
+ const createdAgg = createTaskAggregate(tx, titleFrom(body), {}, now);
793
+ const routed = routeUserSubmission(tx, createdAgg.task, "operator", body, effectiveIntent, now, submissionKey, { kind: "create" });
794
+ return { ...routed, task: createdAgg.task, created: true };
532
795
  });
533
- notifyMailbox(options.runtime, result.task.status === "active" ? leaderMailbox(result.task.id) : taskMailbox(result.task.id), result.task.id);
534
- return result.created
535
- ? `Created Draft task ${result.task.id}: ${result.task.title}\nSubmitted message ${result.message.id}\n`
536
- : `Submitted message ${result.message.id} to ${result.task.id}\n`;
796
+ notifyMailbox(options.runtime, result.queuedForLeader ? leaderMailbox(result.task.id) : taskMailbox(result.task.id), result.task.id);
797
+ const header = result.created
798
+ ? `Created Draft task ${result.task.id}: ${result.task.title}\n`
799
+ : "";
800
+ return `${header}${renderSubmissionFeedback(result.feedback)}`;
801
+ }
802
+ function findSubmissionKeyCreate(store, submissionKey) {
803
+ let otherTarget = false;
804
+ for (const task of store.listTasks()) {
805
+ for (const message of store.listMessages(task.id)) {
806
+ if (message.submissionKey !== submissionKey)
807
+ continue;
808
+ if (message.submissionReceipt?.target.kind === "create") {
809
+ return { kind: "create", taskId: task.id };
810
+ }
811
+ otherTarget = true;
812
+ }
813
+ }
814
+ return otherTarget ? { kind: "other-target" } : { kind: "unused" };
537
815
  }
538
816
  function createTaskCommand(args, store, options) {
539
817
  const parsed = parseTaskCreation(args, store);
@@ -657,7 +935,7 @@ function showTaskCommand(args, store, currentTaskCandidate) {
657
935
  events: events.length,
658
936
  workItems: work.length,
659
937
  currentWorkItems: currentWorkItemCount,
660
- turns: store.listTurns(task.id).length,
938
+ runs: store.listRuns(task.id).length,
661
939
  changeSets: changeSets.length,
662
940
  integrations: integrations.length,
663
941
  publications: publications.length,
@@ -698,7 +976,7 @@ function showTaskCommand(args, store, currentTaskCandidate) {
698
976
  `Events: ${counts.events}`,
699
977
  `Work items: ${counts.workItems}`,
700
978
  `Current work items: ${currentWorkItemCount}`,
701
- `Turns: ${counts.turns}`,
979
+ `AgentRuns: ${counts.runs}`,
702
980
  `ChangeSets: ${counts.changeSets}`,
703
981
  `Integration Attempts: ${counts.integrations}`,
704
982
  `Publication references: ${counts.publications} (${verifiedMergedPublications} verified merged)`,
@@ -721,7 +999,7 @@ function activateTaskCommand(args, store, options) {
721
999
  const task = requireTask(tx, args[0]);
722
1000
  if (task.status === "archived")
723
1001
  throw usageError(`Task is archived: ${task.id}.`);
724
- if (task.status === "retired")
1002
+ if (task.status === "cancelled")
725
1003
  throw usageError(`Task is retired: ${task.id}.`);
726
1004
  if (task.status === "completed") {
727
1005
  throw usageError(`Task ${task.id} is completed; use task reopen before activating it.`);
@@ -734,13 +1012,26 @@ function activateTaskCommand(args, store, options) {
734
1012
  || activation.task.id !== task.id
735
1013
  || activation.task.status !== "active"
736
1014
  || !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) {
1015
+ || activation.path !== task.cwd) {
742
1016
  throw usageError(`Task workspace activation proof does not match ${task.id}.`);
743
1017
  }
1018
+ // An empty environment plan over no bound Project is a legal Task shape,
1019
+ // so the proof of adoption is the *absence* of a workspace rather than a
1020
+ // ManagedWorkspace record. Demanding one here would have forced every
1021
+ // such activation to create a worktree purely to satisfy this check.
1022
+ if (taskOwnsManagedWorkspace(task)) {
1023
+ if (workspace === null
1024
+ || workspace.owner.type !== "task"
1025
+ || workspace.owner.taskId !== task.id
1026
+ || workspace.root !== task.cwd) {
1027
+ throw usageError(`Task workspace activation proof does not match ${task.id}.`);
1028
+ }
1029
+ }
1030
+ else if (workspace !== null
1031
+ || task.workspaceIdentity !== undefined
1032
+ || activation.path !== undefined) {
1033
+ throw usageError(`Workspace-free Task activation proof claims a workspace: ${task.id}.`);
1034
+ }
744
1035
  return { task, changed: activation.changed };
745
1036
  }
746
1037
  throw usageError(`Task ${task.id} activation requires atomic workspace adoption through the CLI preflight.`);
@@ -790,7 +1081,7 @@ function completeTaskCommand(args, store, options) {
790
1081
  };
791
1082
  }
792
1083
  // Completion is a Task decision only. The current Provider Turn remains
793
- // responsible for closing its own Turn, and the reusable Session keeps
1084
+ // responsible for closing its own AgentRun, and the reusable Session keeps
794
1085
  // its independent lifecycle.
795
1086
  // Issue 06: re-validate the full completion readiness inside the
796
1087
  // transaction (the CAS fence) after final-review preparation. This is the
@@ -803,7 +1094,21 @@ function completeTaskCommand(args, store, options) {
803
1094
  if (!readiness.ready) {
804
1095
  throw usageError(formatCompletionBlockers(task.id, readiness.blockers));
805
1096
  }
806
- const completed = completeTask(task, now, { by: actor, summary });
1097
+ for (const ref of request.artifactRefs) {
1098
+ if (isGitArtifactRefString(ref)) {
1099
+ fixedArtifactRefs(task.id, [ref]);
1100
+ }
1101
+ else if (ref.startsWith("turn:")) {
1102
+ const run = tx.getRun(task.id, ref.slice("turn:".length));
1103
+ if (run === null || run.result === undefined) {
1104
+ throw usageError(`Task completion result ref is not readable: ${ref}.`);
1105
+ }
1106
+ }
1107
+ else if (!/^https?:\/\/[^\s]+$/u.test(ref)) {
1108
+ throw usageError("Completion --artifact-ref must be a commit-pinned git:<commit>:<relativePath> reference, turn:<local-turn-id>, or an explicit HTTP(S) reference URL.");
1109
+ }
1110
+ }
1111
+ const completed = completeTask(task, now, { by: actor, summary, artifactRefs: request.artifactRefs });
807
1112
  tx.saveTask(completed);
808
1113
  tx.clearPendingWakeup(task.id);
809
1114
  tx.clearLeaderFailure(task.id);
@@ -834,6 +1139,10 @@ function completeTaskCommand(args, store, options) {
834
1139
  const terminalEvent = recordTaskEvent(tx, task.id, "task.completed", {
835
1140
  by: actor,
836
1141
  summary,
1142
+ ...(completed.completionArtifactRefs === undefined ? {} : {
1143
+ artifactRefs: JSON.stringify(completed.completionArtifactRefs)
1144
+ }),
1145
+ dispatchHistory: JSON.stringify(taskDispatchMailboxes(tx, task.id)),
837
1146
  ...(completedProjectHeads === undefined
838
1147
  ? {}
839
1148
  : { projectHeads: completedProjectHeads }),
@@ -850,7 +1159,7 @@ function completeTaskCommand(args, store, options) {
850
1159
  // discarded at this lifecycle boundary.
851
1160
  tx.removeWorkMailbox(taskMailbox(task.id));
852
1161
  // 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
1162
+ // signal queued while the final AgentRun was being settled must not survive a
854
1163
  // completed Task and become actionable after a later explicit reopen.
855
1164
  for (const role of roles) {
856
1165
  tx.removeWorkMailbox(roleMailbox(task.id, role.name));
@@ -895,19 +1204,39 @@ function reopenTaskCommand(args, store, options) {
895
1204
  return { task, changed: false };
896
1205
  if (task.status === "archived")
897
1206
  throw usageError(`Task is archived: ${task.id}.`);
898
- if (task.status !== "completed")
899
- throw usageError(`Task is not completed: ${task.id}.`);
1207
+ if (task.status !== "completed" && task.status !== "cancelled") {
1208
+ throw usageError(`Task is not completed or cancelled: ${task.id}.`);
1209
+ }
1210
+ const actor = taskActor(tx, options, task.id);
1211
+ if (task.status === "cancelled" && actor === "leader") {
1212
+ throw usageError("Restoring a cancelled Task requires user or Operator authority.");
1213
+ }
1214
+ const dispatchHistory = JSON.stringify(taskDispatchMailboxes(tx, task.id));
1215
+ // Reopening changes Task intent, not the disposition of existing inputs.
1216
+ // In particular, external messages and unknown deliveries remain owned by
1217
+ // their existing mailbox fences.
900
1218
  const active = reopenTask(task, now);
901
1219
  tx.saveTask(active);
902
1220
  const reopenedReason = wakeReason("task-reopened");
903
- enqueueWork(tx, leaderMailbox(task.id), reopenedReason, now, [taskRef(task.id)]);
1221
+ if (actor !== "leader") {
1222
+ enqueueWork(tx, leaderMailbox(task.id), reopenedReason, now, [taskRef(task.id)]);
1223
+ }
904
1224
  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 };
1225
+ recordTaskEvent(tx, task.id, "task.reopened", {
1226
+ status: active.status, by: actor, historicalInputs: "preserved", dispatchHistory,
1227
+ ...(actor === "leader" ? leaderActionEventPayload(tx, task.id, options) : {}),
1228
+ previous: editedFieldValues(task, [
1229
+ "status", "completedAt", "completedBy", "completionSummary", "completionArtifactRefs",
1230
+ "retiredAt", "retiredBy", "retirementSummary", "replacementTaskId", "retirementIsolation"
1231
+ ])
1232
+ }, now);
1233
+ return { task: active, changed: true, wakeLeader: actor !== "leader" };
907
1234
  });
908
1235
  if (result.changed) {
909
1236
  notifyMailbox(options.runtime, taskMailbox(result.task.id), result.task.id);
910
- notifyMailbox(options.runtime, leaderMailbox(result.task.id), result.task.id);
1237
+ if (result.wakeLeader) {
1238
+ notifyMailbox(options.runtime, leaderMailbox(result.task.id), result.task.id);
1239
+ }
911
1240
  }
912
1241
  return result.changed
913
1242
  ? `Reopened task ${result.task.id}\n`
@@ -922,13 +1251,17 @@ function archiveTaskCommand(args, store, options) {
922
1251
  if (task.status === "archived")
923
1252
  return { task, changed: false };
924
1253
  if (task.status !== "completed"
925
- && task.status !== "retired") {
1254
+ && task.status !== "cancelled") {
926
1255
  throw usageError(`Task ${task.id} must be completed or retired before it can be archived.`);
927
1256
  }
928
1257
  const remoteDelivery = request.disposition === "integrated"
929
1258
  ? assertTaskRemoteDeliveryProof(tx, task, options.archiveRemoteDeliveryProof, { forceUnverified: request.forceUnverified })
930
1259
  : undefined;
931
1260
  assertNoOpenInputRequests(tx, task.id, "archiving the Task");
1261
+ const unsettledWork = tx.listWorkItems(task.id).find((item) => item.status === "open");
1262
+ if (unsettledWork !== undefined) {
1263
+ throw usageError(`Work Item ${unsettledWork.id} must be accepted or explicitly retired before archive.`);
1264
+ }
932
1265
  const unresolvedIntegration = tx.listIntegrationAttempts(task.id).find((integration) => (integration.status === "running"
933
1266
  || integration.status === "blocked"
934
1267
  || integration.status === "validating"));
@@ -945,9 +1278,9 @@ function archiveTaskCommand(args, store, options) {
945
1278
  throw usageError(`Task ${task.id} still has managed worktrees; clean them before archiving.`);
946
1279
  }
947
1280
  const activeRole = tx.listRoles(task.id)
948
- .find((role) => tx.getActiveTurn(task.id, role.name) !== null);
1281
+ .find((role) => tx.getActiveRun(task.id, role.name) !== null);
949
1282
  if (activeRole !== undefined) {
950
- throw usageError(`Task ${task.id} still has an active Turn for Role ${activeRole.name}; `
1283
+ throw usageError(`Task ${task.id} still has an active AgentRun for Role ${activeRole.name}; `
951
1284
  + "stop its runtime before archiving.");
952
1285
  }
953
1286
  const liveSessionRole = tx.listRoles(task.id).find((role) => {
@@ -1003,6 +1336,41 @@ function archiveTaskCommand(args, store, options) {
1003
1336
  ? `Archived task ${result.task.id}\n`
1004
1337
  : `Task ${result.task.id} is already archived\n`;
1005
1338
  }
1339
+ function cancelTaskCommand(args, store, options) {
1340
+ const usage = "Task cancel usage: yui task cancel <task> (--summary <text>|--summary-file <path|->).";
1341
+ const parsed = parseTail(args, new Set(["--summary", "--summary-file"]), usage);
1342
+ exactPositionals(parsed.positionals, 1, usage);
1343
+ const summary = readCommandText(parsed.options.get("--summary"), parsed.options.get("--summary-file"), "--summary", usage);
1344
+ const now = clock(options);
1345
+ const result = store.transaction((tx) => {
1346
+ const task = requireTask(tx, parsed.positionals[0]);
1347
+ const actor = taskActor(tx, options, task.id);
1348
+ const cancelled = retireTask(task, { by: actor, summary }, now);
1349
+ if (cancelled === task)
1350
+ return task;
1351
+ const dispatchHistory = JSON.stringify(taskDispatchMailboxes(tx, task.id));
1352
+ tx.saveTask(cancelled);
1353
+ tx.clearPendingWakeup(task.id);
1354
+ tx.clearLeaderFailure(task.id);
1355
+ for (const mailbox of tx.listWorkMailboxes()) {
1356
+ if ((mailbox.target.kind === "task" || mailbox.target.kind === "role")
1357
+ && mailbox.target.taskId === task.id)
1358
+ tx.removeWorkMailbox(mailbox.target);
1359
+ }
1360
+ const event = recordTaskEvent(tx, task.id, "task.cancelled", { by: actor, summary, dispatchHistory }, now);
1361
+ enqueueOperatorEvent(tx, event, "task-terminal", now);
1362
+ // Cancellation is intent, not fabricated proof that old processes stopped.
1363
+ // AgentRuns, inputs, WorkItems and their results remain independently readable.
1364
+ return cancelled;
1365
+ });
1366
+ options.runtime?.notifyStateChanged(result.id);
1367
+ notifyMailbox(options.runtime, { kind: "operator" }, result.id);
1368
+ return output(`Cancelled task ${result.id}\n`, { task: result });
1369
+ }
1370
+ /** Historical evidence only; these snapshots are never dispatch input. */
1371
+ function taskDispatchMailboxes(store, taskId) {
1372
+ return store.listWorkMailboxes().filter(({ target }) => (target.kind === "task" || target.kind === "role") && target.taskId === taskId);
1373
+ }
1006
1374
  function retireTaskCommand(args, store, options) {
1007
1375
  const usage = "Task retire usage: yui task retire <task> (--summary <text>|--summary-file <path|->) [--replacement <task>].";
1008
1376
  const parsed = parseTail(args, new Set(["--summary", "--summary-file", "--replacement"]), usage);
@@ -1014,7 +1382,7 @@ function retireTaskCommand(args, store, options) {
1014
1382
  const result = store.transaction((tx) => {
1015
1383
  const task = requireTask(tx, taskId);
1016
1384
  const actor = taskActor(tx, options, task.id);
1017
- if (task.status === "retired") {
1385
+ if (task.status === "cancelled") {
1018
1386
  const same = task.retirementSummary === summary
1019
1387
  && task.replacementTaskId === replacementTaskId;
1020
1388
  if (!same)
@@ -1043,12 +1411,12 @@ function retireTaskCommand(args, store, options) {
1043
1411
  throw usageError(`Task ${task.id} has an active DurableJob: ${activeRetireJob.id}/${activeRetireJob.status}.`);
1044
1412
  }
1045
1413
  assertTaskRetirementProof(tx, task, options.taskRetirementProof);
1046
- for (const run of tx.listTurns(task.id).filter(({ status: runStatus }) => (runStatus === "active"))) {
1047
- const terminal = terminalizeExactTaskTurn(tx, {
1414
+ for (const run of tx.listRuns(task.id).filter(({ status: runStatus }) => (runStatus === "active"))) {
1415
+ const terminal = terminalizeExactTaskRun(tx, {
1048
1416
  taskId: task.id,
1049
1417
  roleName: run.roleName,
1050
1418
  agentId: run.effective.agentId,
1051
- turnId: run.id,
1419
+ runId: run.id,
1052
1420
  mailboxDisposition: "discard",
1053
1421
  outcome: {
1054
1422
  status: "failed",
@@ -1057,11 +1425,11 @@ function retireTaskCommand(args, store, options) {
1057
1425
  }
1058
1426
  }, now);
1059
1427
  if (terminal.disposition !== "applied") {
1060
- throw usageError(`Task Turn changed during retirement: ${run.id}/${terminal.reason ?? "obsolete"}.`);
1428
+ throw usageError(`Task AgentRun changed during retirement: ${run.id}/${terminal.reason ?? "obsolete"}.`);
1061
1429
  }
1062
1430
  }
1063
1431
  for (const item of tx.listWorkItems(task.id)) {
1064
- if (item.status === "completed" || item.status === "retired")
1432
+ if (item.status === "accepted" || item.status === "retired")
1065
1433
  continue;
1066
1434
  tx.saveWorkItem(task.id, updateWorkItemStatus(item, "retired", now, `Task retired: ${summary}`));
1067
1435
  }
@@ -1082,12 +1450,14 @@ function retireTaskCommand(args, store, options) {
1082
1450
  const retired = retireTask(task, {
1083
1451
  by: actor,
1084
1452
  summary,
1453
+ isolated: true,
1085
1454
  ...(replacementTaskId === undefined ? {} : { replacementTaskId })
1086
1455
  }, now);
1087
1456
  tx.saveTask(retired);
1088
1457
  const terminalEvent = recordTaskEvent(tx, task.id, "task.retired", {
1089
1458
  by: actor,
1090
1459
  summary,
1460
+ isolationEstablished: "true",
1091
1461
  ...(replacementTaskId === undefined ? {} : { replacementTaskId })
1092
1462
  }, now);
1093
1463
  enqueueOperatorEvent(tx, terminalEvent, "task-terminal", now);
@@ -1153,10 +1523,13 @@ function formatProjectCommits(projects) {
1153
1523
  export function validateTaskArchiveRequest(args, store, options = {}) {
1154
1524
  const request = parseTaskArchiveArguments(args);
1155
1525
  const task = requireTask(store, request.taskId);
1156
- taskActor(store, options, task.id);
1526
+ const archiveActor = taskActor(store, options, task.id);
1527
+ if (archiveActor === "leader") {
1528
+ throw usageError("Task archive requires independent user or Operator authorization.");
1529
+ }
1157
1530
  if (task.status !== "archived"
1158
1531
  && task.status !== "completed"
1159
- && task.status !== "retired") {
1532
+ && task.status !== "cancelled") {
1160
1533
  throw usageError(`Task ${task.id} must be completed or retired before it can be archived.`);
1161
1534
  }
1162
1535
  if (task.status !== "archived") {
@@ -1179,9 +1552,55 @@ function reconcileTaskCommand(args, store, options) {
1179
1552
  }
1180
1553
  function taskMessageCommand(args, store, options) {
1181
1554
  const [command, ...rest] = args;
1555
+ if (command === "handoff") {
1556
+ const usage = "Task message handoff usage: yui task message handoff <task/message> --to <role>.";
1557
+ const parsed = parseTail(rest, new Set(["--to"]), usage);
1558
+ exactPositionals(parsed.positionals, 1, usage);
1559
+ const ref = taskRecordReference(parsed.positionals[0], "message", "Message reference", options);
1560
+ const now = clock(options);
1561
+ const message = store.transaction((tx) => {
1562
+ assertTaskDeliveryAuthority(tx, options.environment, ref.taskId);
1563
+ const current = tx.listMessages(ref.taskId).find((entry) => entry.id === ref.localId);
1564
+ if (current?.recipient?.ownerRunId === undefined || current.continuation?.runId !== undefined) {
1565
+ throw usageError("Only an unassigned pending Message can be explicitly handed off.");
1566
+ }
1567
+ const recipient = resolveMessageRecipient(tx, ref.taskId, requiredOption(parsed.options, "--to"), {
1568
+ ...(current.recipient.workItemId === undefined ? {} : { workItemId: current.recipient.workItemId }),
1569
+ ...(current.recipient.reviewRoundId === undefined ? {} : { reviewRoundId: current.recipient.reviewRoundId })
1570
+ });
1571
+ const updated = { ...current, recipient, continuation: {},
1572
+ handovers: [...(current.handovers ?? []), { from: current.recipient, at: now.toISOString() }] };
1573
+ const blocker = messageContinuationBlocker(tx, updated);
1574
+ if (blocker !== undefined)
1575
+ throw usageError(`Message handoff cannot proceed: ${blocker}.`);
1576
+ tx.updateMessage(ref.taskId, updated);
1577
+ recordTaskEvent(tx, ref.taskId, "message.handed-off", {
1578
+ messageId: current.id, fromRole: current.recipient.roleName, toRole: recipient.roleName,
1579
+ ownerRunId: recipient.ownerRunId
1580
+ }, now);
1581
+ enqueueWork(tx, taskMailbox(ref.taskId), "message-continuation", now, [messageRef(ref.taskId, current.id)]);
1582
+ return updated;
1583
+ });
1584
+ notifyMailbox(options.runtime, taskMailbox(ref.taskId), ref.taskId);
1585
+ return output(`Handed off Message ${ref.localId} to ${message.recipient.roleName}.\n`, message);
1586
+ }
1587
+ if (command === "show") {
1588
+ const usage = "Task message show usage: yui task message show <task/message>.";
1589
+ exactPositionals(rest, 1, usage);
1590
+ const ref = taskRecordReference(rest[0], "message", "Message reference", options);
1591
+ const message = listContextMessages(store, ref.taskId, options.environment)
1592
+ .find((entry) => entry.id === ref.localId);
1593
+ if (message === undefined)
1594
+ throw dataError("Message is unavailable in the caller's scope.");
1595
+ const continuationRun = message.continuation?.runId === undefined
1596
+ ? null : store.getRun(ref.taskId, message.continuation.runId);
1597
+ const expanded = { ...expandTaskMessageResult(message, (task, run) => store.getRun(task, run)),
1598
+ ...(continuationRun === null ? {} : { execution: runExecutionObservation(continuationRun, store.getTaskRoleSessionSet(ref.taskId, continuationRun.roleName)?.providerBinding, store.listEvents(ref.taskId)) }) };
1599
+ return { kind: "output", output: `${JSON.stringify(expanded, null, 2)}\n`, data: expanded };
1600
+ }
1182
1601
  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);
1602
+ const usage = "Task message send usage: yui task message send <id> (<body>|--body-file <path|->) [--intent record|discuss|develop] [--request-id <key>] [--wake-policy leader|none] [--to <role> --work-item <id>|--review-round <id>].";
1603
+ const parsed = parseTail(rest, new Set(["--body-file", "--intent", "--request-id", "--wake-policy", "--to", "--work-item", "--review-round"]), usage);
1185
1604
  if (parsed.positionals.length < 1 || parsed.positionals.length > 2)
1186
1605
  throw usageError(usage);
1187
1606
  const body = readCommandText(parsed.positionals[1], parsed.options.get("--body-file"), "--body", usage);
@@ -1196,42 +1615,34 @@ function taskMessageCommand(args, store, options) {
1196
1615
  else {
1197
1616
  throw usageError(`--wake-policy must be 'leader' or 'none': ${wakePolicyRaw}.`);
1198
1617
  }
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`;
1618
+ const intent = parseSubmissionIntentOption(parsed.options.get("--intent"), usage);
1619
+ const submissionKey = parsed.options.get("--request-id");
1620
+ if (submissionKey !== undefined && submissionKey.trim().length === 0) {
1621
+ throw usageError("--request-id is required.", usage);
1622
+ }
1623
+ const recipientRole = parsed.options.get("--to");
1624
+ const workItemId = parsed.options.get("--work-item");
1625
+ const reviewRoundId = parsed.options.get("--review-round");
1626
+ if (recipientRole === undefined && (workItemId !== undefined || reviewRoundId !== undefined))
1627
+ throw usageError("--to is required for scoped Message delivery.");
1628
+ const result = sendTaskMessageCommand(store, parsed.positionals[0], body, wakePolicy, options, recipientRole === undefined ? undefined : { roleName: recipientRole, workItemId, reviewRoundId }, intent, submissionKey);
1629
+ // A user/operator submission returns the unified §2.5 feedback; render each
1630
+ // facet on its own line and expose the structure to non-text callers.
1631
+ if (result.feedback !== undefined) {
1632
+ return output(renderSubmissionFeedback(result.feedback), { taskId: result.task.id, message: result.message, submission: result.feedback });
1633
+ }
1634
+ const reason = result.message.continuation?.notDeliveredReason;
1635
+ const delivery = reason !== undefined ? { state: "not-delivered", reason }
1636
+ : recipientRole !== undefined || result.queuedForLeader
1637
+ ? { state: "queued" } : { state: "saved" };
1638
+ 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
1639
  }
1229
1640
  if (command === "list") {
1230
1641
  const messageListUsage = "Task message list usage: yui task message list <id> [--after <timestamp>] [--limit <n>].";
1231
1642
  const parsed = parseTail(rest, new Set(["--after", "--limit"]), messageListUsage);
1232
1643
  exactPositionals(parsed.positionals, 1, messageListUsage);
1233
1644
  const task = requireTask(store, parsed.positionals[0]);
1234
- let messages = store.listMessages(task.id);
1645
+ let messages = listContextMessages(store, task.id, options.environment);
1235
1646
  const after = optionalNonEmptyOption(parsed.options, "--after");
1236
1647
  if (after !== undefined) {
1237
1648
  const afterMs = Date.parse(after);
@@ -1279,6 +1690,127 @@ function taskMessageCommand(args, store, options) {
1279
1690
  ? "Task message command is required."
1280
1691
  : `Unknown command: task message ${command}`);
1281
1692
  }
1693
+ /** CLI and authenticated user Surface share the same message and mailbox
1694
+ * transaction. Talking to Leader does not impersonate Leader authority. */
1695
+ /**
1696
+ * The one transaction that turns an inbound Task Message into durable facts and,
1697
+ * when the Task's own lifecycle calls for it, Leader work.
1698
+ *
1699
+ * CLI `task message send` and the Web Task surface both call this, so neither
1700
+ * owns a private notion of what "sending a message" means. The Draft case is the
1701
+ * reason that matters here: a Draft's Leader conversation is its planning Turn,
1702
+ * so a Draft must wake its Leader exactly like an active Task does. Gating the
1703
+ * wake on `active` would leave the Draft entry point saving text that no Leader
1704
+ * ever reads — the message would be persisted and silently go nowhere, which is
1705
+ * indistinguishable to the user from a Provider that never answered.
1706
+ *
1707
+ * The wake carries intent only. Whether the resulting Turn is planning or
1708
+ * execution is decided by the Controller from the Task's own status, so this
1709
+ * function never names a purpose and no second planning path exists.
1710
+ */
1711
+ export function sendTaskMessageCommand(store, taskId, body, wakePolicy, options = {}, recipient, intent, submissionKey) {
1712
+ if (!body.trim())
1713
+ throw usageError("Message body is required.");
1714
+ if (recipient !== undefined && wakePolicy !== undefined) {
1715
+ throw usageError("--wake-policy applies only to unaddressed Leader Messages; an owner-directed Message uses its exact continuation boundary.");
1716
+ }
1717
+ if (recipient !== undefined && intent !== undefined) {
1718
+ throw usageError("A submission intent applies only to unaddressed Leader Messages; an owner-directed Message uses its exact continuation boundary.");
1719
+ }
1720
+ if (recipient !== undefined && submissionKey !== undefined) {
1721
+ throw usageError("A submission key applies only to unaddressed Leader Messages; an owner-directed Message uses its exact continuation boundary.");
1722
+ }
1723
+ const now = clock(options);
1724
+ const result = store.transaction((tx) => {
1725
+ const task = requireTask(tx, taskId);
1726
+ if (recipient === undefined)
1727
+ assertTaskOpen(task);
1728
+ const caller = currentManagedRuntime(tx, options.environment, task.id);
1729
+ const roleCaller = caller !== undefined && caller.roleName !== "leader" ? caller : undefined;
1730
+ if (roleCaller !== undefined) {
1731
+ const run = roleCaller.currentRunId === undefined ? null : tx.getRun(task.id, roleCaller.currentRunId);
1732
+ if (run === null || recipient?.roleName !== "leader"
1733
+ || recipient.workItemId !== run.workItemId || recipient.reviewRoundId !== run.reviewRoundId) {
1734
+ throw usageError("A Worker or Reviewer may send only to Leader within its current Assignment.");
1735
+ }
1736
+ }
1737
+ const actor = roleCaller === undefined ? taskActor(tx, options, task.id) : "role";
1738
+ // A user/operator Message with no explicit recipient is the single path that
1739
+ // carries submission intent (record | discuss | develop). It routes through
1740
+ // the one shared submission service so intent means exactly the same thing
1741
+ // here as on every other surface. Internal role/leader result messages and
1742
+ // owner-directed continuations keep their exact existing boundary and never
1743
+ // gain develop authority (task-32 §2.5).
1744
+ if (recipient === undefined && (actor === "user" || actor === "operator")) {
1745
+ const effectiveIntent = normalizeSubmissionIntent(intent, wakePolicy);
1746
+ const routed = routeUserSubmission(tx, task, actor, body, effectiveIntent, now, submissionKey, { kind: "task", taskId: task.id });
1747
+ return {
1748
+ task: routed.task, message: routed.message, actor,
1749
+ queuedForLeader: routed.queuedForLeader, feedback: routed.feedback
1750
+ };
1751
+ }
1752
+ if (intent !== undefined) {
1753
+ throw usageError("Submission intent applies only to an unaddressed user or Operator Message.");
1754
+ }
1755
+ if (recipient !== undefined && actor === "leader")
1756
+ assertTaskDeliveryAuthority(tx, options.environment, task.id);
1757
+ const target = recipient === undefined ? undefined
1758
+ : recipient.roleName === "leader" && roleCaller !== undefined ? {
1759
+ roleName: "leader", ...(recipient.workItemId === undefined ? {} : { workItemId: recipient.workItemId }),
1760
+ ...(recipient.reviewRoundId === undefined ? {} : { reviewRoundId: recipient.reviewRoundId })
1761
+ } : resolveMessageRecipient(tx, task.id, recipient.roleName, {
1762
+ ...(recipient.workItemId === undefined ? {} : { workItemId: recipient.workItemId }),
1763
+ ...(recipient.reviewRoundId === undefined ? {} : { reviewRoundId: recipient.reviewRoundId })
1764
+ });
1765
+ const context = {
1766
+ ...(target === undefined ? {} : { recipient: target }),
1767
+ ...(recipient?.workItemId === undefined ? {} : { workItemId: recipient.workItemId })
1768
+ };
1769
+ const message = actor === "leader"
1770
+ ? appendMessage(tx, task.id, body, "role-result", { type: "role", roleName: LEADER_ROLE }, now, context)
1771
+ : actor === "role"
1772
+ ? appendMessage(tx, task.id, body, "role-result", { type: "role", roleName: roleCaller.roleName }, now, context)
1773
+ : actor === "operator"
1774
+ ? appendMessage(tx, task.id, body, "operator", { type: "operator" }, now, context)
1775
+ : appendMessage(tx, task.id, body, "user", { type: "user" }, now, context);
1776
+ // Reached only by addressed Messages and by an unaddressed Leader result now:
1777
+ // unaddressed user/operator submissions are handled above by the shared
1778
+ // service. A Worker/Reviewer message addressed to its Leader still wakes the
1779
+ // Leader exactly as before; an owner-directed continuation posts to the Task
1780
+ // mailbox for its exact owner Run.
1781
+ const queuedForLeader = target?.ownerRunId === undefined
1782
+ && leaderWakingTaskStatus(task.status) && actor !== "leader";
1783
+ if (target?.ownerRunId !== undefined) {
1784
+ const reason = messageContinuationBlocker(tx, message);
1785
+ if (reason !== undefined) {
1786
+ message.continuation = { notDeliveredReason: reason };
1787
+ tx.updateMessage(task.id, message);
1788
+ }
1789
+ enqueueWork(tx, taskMailbox(task.id), "message-continuation", now, [messageRef(task.id, message.id)], { dedupeKey: `message:${task.id}:${message.id}` });
1790
+ }
1791
+ else if (queuedForLeader) {
1792
+ 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}` });
1793
+ }
1794
+ return { task, message, actor, queuedForLeader, feedback: undefined };
1795
+ });
1796
+ if (recipient !== undefined) {
1797
+ notifyMailbox(options.runtime, taskMailbox(result.task.id), result.task.id);
1798
+ }
1799
+ else if (result.actor !== "leader") {
1800
+ notifyMailbox(options.runtime, result.queuedForLeader
1801
+ ? leaderMailbox(result.task.id) : taskMailbox(result.task.id), result.task.id);
1802
+ }
1803
+ return result;
1804
+ }
1805
+ /**
1806
+ * Whether an inbound Message on a Task in this status is delivered to its
1807
+ * Leader. Draft qualifies because planning is a Leader conversation that
1808
+ * deliberately precedes any delivery environment or Activation (S01); a
1809
+ * terminal Task has no Leader lane and keeps the message as context only.
1810
+ */
1811
+ export function leaderWakingTaskStatus(status) {
1812
+ return status === "active" || status === "draft";
1813
+ }
1282
1814
  function updateMessage(args, store, options) {
1283
1815
  const usage = "Task message update usage: yui task message update <task>/<message> (<body>|--body-file <path|->) [--wake-policy leader|none].";
1284
1816
  const parsed = parseTail(args, new Set(["--body-file", "--wake-policy"]), usage);
@@ -1322,9 +1854,14 @@ function updateMessage(args, store, options) {
1322
1854
  updatedBy: actor,
1323
1855
  ...(wakePolicy === undefined ? {} : { wakePolicy })
1324
1856
  }, now);
1325
- return { task, message: updated };
1857
+ const queuedForLeader = updated.wakePolicy !== "none";
1858
+ if (queuedForLeader) {
1859
+ enqueueWork(tx, leaderMailbox(task.id), actor === "operator" ? "operator-input" : "user-message", now, [messageRef(task.id, updated.id)], { source: actor });
1860
+ }
1861
+ return { task, message: updated, queuedForLeader };
1326
1862
  });
1327
- options.runtime?.notifyStateChanged(result.task.id);
1863
+ notifyMailbox(options.runtime, result.queuedForLeader
1864
+ ? leaderMailbox(result.task.id) : taskMailbox(result.task.id), result.task.id);
1328
1865
  return output(`Updated Task Message ${result.task.id}/${result.message.id}\n`, {
1329
1866
  message: result.message
1330
1867
  });
@@ -1353,7 +1890,7 @@ function retireMessage(args, store, options) {
1353
1890
  // Remove an isolated pending wake for this exact directive. A merged batch
1354
1891
  // is retained because its other signals remain actionable; context and
1355
1892
  // actionability projections still filter the retired message below.
1356
- if (task.status === "active") {
1893
+ if (leaderWakingTaskStatus(task.status)) {
1357
1894
  try {
1358
1895
  settleExactWorkExecution(tx, leaderMailbox(task.id), messageRef(task.id, message.id));
1359
1896
  }
@@ -1408,9 +1945,9 @@ function taskRoleCommand(args, store, options) {
1408
1945
  if (command === "status")
1409
1946
  return taskRoleStatus(rest, store, options);
1410
1947
  if (command === "show")
1411
- return output(showTaskRole(rest, store));
1948
+ return showTaskRole(rest, store);
1412
1949
  if (command === "update")
1413
- return output(updateTaskRole(rest, store, options));
1950
+ return updateTaskRole(rest, store, options);
1414
1951
  if (command === "remove")
1415
1952
  return output(removeTaskRole(rest, store, options));
1416
1953
  if (command === "bind")
@@ -1435,20 +1972,76 @@ function taskRoleSessionCommand(args, store, options) {
1435
1972
  exactPositionals(rest, 2, "Task Role Session inspect usage: yui task role session inspect <task> <role>.");
1436
1973
  const task = requireTask(store, rest[0]);
1437
1974
  const role = requireRole(store, task.id, rest[1]);
1438
- taskActor(store, options, task.id);
1975
+ const reader = resolveManagedTaskReader(store, options.environment);
1976
+ if (reader === undefined)
1977
+ taskActor(store, options, task.id);
1978
+ else if (reader.taskId !== task.id || (reader.roleName !== "leader" && reader.roleName !== role.name)) {
1979
+ throw usageError("Session inspection is outside the caller's Task/Role.");
1980
+ }
1439
1981
  const sessions = store.getTaskRoleSessionSet(task.id, role.name);
1440
1982
  const active = sessions?.sessions[sessions.activeAgentId] ?? null;
1441
1983
  const binding = sessions?.providerBinding ?? null;
1984
+ // The live reading, when the CLI took one. Rendered under the persisted
1985
+ // facts rather than merged into them: the Session's own record is what Yui
1986
+ // launched, and this is what the Agent says about it now. An empty render
1987
+ // means there was nothing an Agent reported, and the one-line label above
1988
+ // still states which of the "no value" cases applies.
1989
+ const runConfiguration = active === null ? undefined : options.liveRunConfiguration;
1990
+ const runConfigurationDetail = renderAgentRunConfiguration(runConfiguration);
1442
1991
  return output(active === null
1443
- ? `No Session exists for ${task.id}/${role.name}.\n`
1992
+ ? `No attached Session exists for ${task.id}/${role.name}.\n`
1993
+ + (binding === null ? "" : `Retained native execution: ${currentProviderConversation(binding).conversationId}; input=${binding.run?.status ?? "none"}. Use session new to replace from durable context.\n`)
1444
1994
  : [
1445
1995
  `Session ${task.id}/${role.name}`,
1446
- `Agent: ${active.agentId}/${active.adapterId}`,
1996
+ // The component, not just the connection plan: several products are
1997
+ // reached over `acp`, and the plan alone cannot say which one ran.
1998
+ `Agent: ${active.agentId}/${agentExecutionComponentLabel(active.effective.component)}`
1999
+ + ` (${active.adapterId} plan)`,
1447
2000
  `Native id: ${active.nativeSessionId}`,
1448
- `Host activation: ${active.runtimeGenerationId ?? "none"}`,
1449
2001
  `Session: ${active.status}${active.endReason === undefined ? "" : `/${active.endReason}`}`,
1450
- `Turn: ${binding?.turn?.status ?? "none"}`
1451
- ].join("\n") + "\n", { task, role, session: active, providerBinding: binding });
2002
+ `AgentRun: ${binding?.run?.status ?? "none"}`,
2003
+ `Run configuration: ${agentRunConfigurationLabel(runConfiguration)}`
2004
+ ].join("\n") + "\n"
2005
+ + `\n${renderRoleLaunchComparison(role, active.effective)}\n`
2006
+ + (runConfigurationDetail === "" ? "" : `\n${runConfigurationDetail}\n`), {
2007
+ task,
2008
+ role,
2009
+ session: active,
2010
+ providerBinding: binding,
2011
+ ...(runConfiguration === undefined ? {} : { runConfiguration })
2012
+ });
2013
+ }
2014
+ if (command === "new") {
2015
+ const usage = "Task Role Session new usage: yui task role session new <task> <role> --reason <text>.";
2016
+ const parsed = parseTail(rest, new Set(["--reason"]), usage);
2017
+ exactPositionals(parsed.positionals, 2, usage);
2018
+ const reason = requiredOption(parsed.options, "--reason");
2019
+ const now = clock(options);
2020
+ const result = store.transaction((tx) => {
2021
+ const task = requireTask(tx, parsed.positionals[0]);
2022
+ if (task.status === "archived")
2023
+ throw usageError("An archived Task cannot select a new Session.", usage);
2024
+ const actor = taskActor(tx, options, task.id);
2025
+ const role = requireRole(tx, task.id, parsed.positionals[1]);
2026
+ const current = tx.getTaskRoleSessionSet(task.id, role.name);
2027
+ const previous = current?.sessions[current.activeAgentId];
2028
+ const target = runtimeLifecycleTarget({ scope: "task", taskId: task.id, roleName: role.name });
2029
+ const mailbox = tx.getWorkMailbox(target);
2030
+ if ([...(mailbox?.pending?.reasons ?? []), ...(mailbox?.processing?.batch.reasons ?? [])]
2031
+ .includes(RUNTIME_SESSION_REPLACE_REQUIRED_REASON))
2032
+ return { taskId: task.id, roleName: role.name, target, alreadyRequested: true };
2033
+ recordTaskEvent(tx, task.id, "runtime.session-replacement-requested", {
2034
+ roleName: role.name, agentId: current?.activeAgentId ?? role.activeAgentId,
2035
+ ...(previous === undefined ? {} : { nativeSessionId: previous.nativeSessionId }),
2036
+ reason, requestedBy: actor
2037
+ }, now);
2038
+ enqueueWork(tx, target, RUNTIME_SESSION_REPLACE_REQUIRED_REASON, now, [{ type: "task", id: task.id }]);
2039
+ return { taskId: task.id, roleName: role.name, target, alreadyRequested: false };
2040
+ });
2041
+ notifyMailbox(options.runtime, result.target, result.taskId);
2042
+ return output(`Requested Session replacement for ${result.taskId}/${result.roleName}. `
2043
+ + "Yui will stop the old execution, retain its history and workspaces, and select a fresh Session. "
2044
+ + "If replacing your own Session, end this turn; the successor reads durable Task context.\n", result);
1452
2045
  }
1453
2046
  if (command === "stop") {
1454
2047
  const usage = "Task Role Session stop usage: yui task role session stop <task> <role> --reason <text>.";
@@ -1458,36 +2051,26 @@ function taskRoleSessionCommand(args, store, options) {
1458
2051
  const now = clock(options);
1459
2052
  const request = store.transaction((tx) => {
1460
2053
  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);
2054
+ if (!["draft", "active", "completed", "cancelled"].includes(task.status)) {
2055
+ throw usageError(`Task Role Session stop is unavailable for an archived Task: ${task.id}.`, usage);
1463
2056
  }
1464
2057
  const actor = taskActor(tx, options, task.id);
1465
2058
  const role = requireRole(tx, task.id, parsed.positionals[1]);
1466
2059
  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);
2060
+ throw usageError("Use task role session new to request your own replacement and end this turn; synchronous self-stop cannot return.", usage);
1471
2061
  }
1472
2062
  const sessions = tx.getTaskRoleSessionSet(task.id, role.name);
1473
- const session = sessions?.sessions[sessions.activeAgentId];
1474
- if (session === undefined || session.status === "ended") {
2063
+ const session = taskRoleControlTarget(sessions);
2064
+ if (session === undefined || session.adapterId === undefined) {
1475
2065
  throw usageError(`Task Role has no active Session: ${task.id}/${role.name}.`, usage);
1476
2066
  }
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
- }
2067
+ // A repeated stop is a recovery request, not contention with its own
2068
+ // earlier cleanup obligation.
1485
2069
  recordTaskEvent(tx, task.id, "runtime.session-stop-requested", {
1486
2070
  roleName: role.name,
1487
2071
  agentId: session.agentId,
1488
2072
  adapterId: session.adapterId,
1489
2073
  nativeSessionId: session.nativeSessionId,
1490
- runtimeGenerationId: session.runtimeGenerationId ?? "",
1491
2074
  reason,
1492
2075
  requestedBy: actor
1493
2076
  }, now);
@@ -1497,7 +2080,6 @@ function taskRoleSessionCommand(args, store, options) {
1497
2080
  agentId: session.agentId,
1498
2081
  adapterId: session.adapterId,
1499
2082
  nativeSessionId: session.nativeSessionId,
1500
- ...(session.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: session.runtimeGenerationId }),
1501
2083
  sessionUpdatedAt: session.updatedAt
1502
2084
  };
1503
2085
  });
@@ -1615,7 +2197,7 @@ function listTaskRoles(args, store, options) {
1615
2197
  status.health,
1616
2198
  taskRoleOpenInputLabel(status),
1617
2199
  taskRoleActiveWorkLabel(status),
1618
- taskRoleLastTurnLabel(status),
2200
+ taskRoleLastRunLabel(status),
1619
2201
  taskRoleNativeSessionLabel(status),
1620
2202
  taskRoleTmuxLabel(status)
1621
2203
  ]), defaultTableWidth())}\n`, { roles: statuses });
@@ -1633,9 +2215,15 @@ function showTaskRole(args, store) {
1633
2215
  exactPositionals(args, 2, "Task role show usage: yui task role show <task> <role>.");
1634
2216
  const task = requireTask(store, args[0]);
1635
2217
  const role = requireRole(store, task.id, args[1]);
1636
- return renderRoleDetails(`Task Role: ${role.name}`, role, {
2218
+ const sessions = store.getTaskRoleSessionSet(task.id, role.name);
2219
+ return output(renderRoleDetails(`Task Role: ${role.name}`, role, {
1637
2220
  kind: "task",
1638
- sessions: store.getTaskRoleSessionSet(task.id, role.name)
2221
+ sessions
2222
+ }), {
2223
+ role,
2224
+ sessions,
2225
+ runs: store.listRuns(task.id).filter((run) => run.roleName === role.name)
2226
+ .map((run) => ({ id: run.id, status: run.status, effective: run.effective }))
1639
2227
  });
1640
2228
  }
1641
2229
  function updateTaskRole(args, store, options) {
@@ -1646,8 +2234,11 @@ function updateTaskRole(args, store, options) {
1646
2234
  }
1647
2235
  const parsed = parseRoleOptions(tail, new Map([
1648
2236
  ...roleOptionSpecs({ update: true, includeAgent: true }),
1649
- ["--profile", "value"]
2237
+ ["--profile", "value"],
2238
+ ["--environment", "value"],
2239
+ ["--managed-environment", "flag"]
1650
2240
  ]), usage);
2241
+ validateTaskRoleEnvironmentOptions(parsed, usage);
1651
2242
  if (parsed.has("--agent") && (parsed.one("--agent")?.trim().length ?? 0) === 0) {
1652
2243
  throw usageError("--agent is required.", usage);
1653
2244
  }
@@ -1660,7 +2251,9 @@ function updateTaskRole(args, store, options) {
1660
2251
  assertTaskOpen(task);
1661
2252
  taskActor(tx, options, task.id);
1662
2253
  const role = requireRole(tx, task.id, roleName);
1663
- const changesLaunchContext = hasRoleLaunchContextOptions(parsed) || parsed.has("--profile");
2254
+ const changesEnvironment = parsed.has("--environment") || parsed.has("--managed-environment");
2255
+ const changesLaunchContext = hasRoleLaunchContextOptions(parsed) || parsed.has("--profile")
2256
+ || changesEnvironment;
1664
2257
  const changesAgentConfig = hasAgentConfigOptions(parsed);
1665
2258
  if (changesLaunchContext || changesAgentConfig) {
1666
2259
  assertRoleRuntimeMutationAllowed(tx, {
@@ -1668,13 +2261,6 @@ function updateTaskRole(args, store, options) {
1668
2261
  taskId: task.id,
1669
2262
  roleName: role.name
1670
2263
  }, "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
2264
  }
1679
2265
  const profileId = parsed.one("--profile");
1680
2266
  const agentProfile = profileId === undefined
@@ -1692,7 +2278,7 @@ function updateTaskRole(args, store, options) {
1692
2278
  [bindingUpdate.agentId]: bindingUpdate.binding
1693
2279
  }
1694
2280
  }, now);
1695
- const next = updateRole(withBinding, {
2281
+ let next = updateRole(withBinding, {
1696
2282
  ...roleProfilePatch(parsed)
1697
2283
  }, now);
1698
2284
  if (bindingUpdate !== undefined) {
@@ -1701,16 +2287,32 @@ function updateTaskRole(args, store, options) {
1701
2287
  if (changesLaunchContext) {
1702
2288
  validateConfiguredRoleSkills(options.yuiHome, next.skills ?? []);
1703
2289
  }
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);
2290
+ if (changesEnvironment) {
2291
+ next = updateRole(next, {
2292
+ executionEnvironment: parsed.has("--managed-environment")
2293
+ ? null
2294
+ : createProjectResources(tx, () => now).resolveExecutionEnvironment(task.id, parsed.one("--environment"))
2295
+ }, now);
2296
+ }
2297
+ saveTaskRoleUpdate(tx, role, next, now, {
2298
+ source: "task.role.update", actor: taskActor(tx, options, task.id)
2299
+ });
1707
2300
  return next;
1708
2301
  });
1709
2302
  notifyMailbox(options.runtime, taskMailbox(updated.taskId), updated.taskId);
1710
- return renderRoleDetails(`Updated Task Role: ${updated.name}`, updated, {
2303
+ const sessions = store.getTaskRoleSessionSet(updated.taskId, updated.name);
2304
+ return output(renderRoleDetails(`Updated Task Role: ${updated.name}`, updated, {
1711
2305
  kind: "task",
1712
- sessions: store.getTaskRoleSessionSet(updated.taskId, updated.name)
1713
- });
2306
+ sessions
2307
+ }), { role: updated, sessions });
2308
+ }
2309
+ function validateTaskRoleEnvironmentOptions(parsed, usage) {
2310
+ if (parsed.has("--environment") && parsed.has("--managed-environment")) {
2311
+ throw usageError("--environment and --managed-environment are mutually exclusive.", usage);
2312
+ }
2313
+ if (parsed.has("--environment") && (parsed.one("--environment")?.trim().length ?? 0) === 0) {
2314
+ throw usageError("--environment requires an adopted preparation id.", usage);
2315
+ }
1714
2316
  }
1715
2317
  function removeTaskRole(args, store, options) {
1716
2318
  exactPositionals(args, 2, "Task role remove usage: yui task role remove <task> <role>.");
@@ -1727,8 +2329,8 @@ function removeTaskRole(args, store, options) {
1727
2329
  taskId: task.id,
1728
2330
  roleName: role.name
1729
2331
  }, "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}.`);
2332
+ if (tx.getActiveRun(task.id, role.name) !== null) {
2333
+ throw usageError(`Task Role has an active AgentRun and cannot be removed: ${task.id}/${role.name}.`);
1732
2334
  }
1733
2335
  const sessions = tx.getTaskRoleSessionSet(task.id, role.name);
1734
2336
  if (Object.values(sessions?.sessions ?? {}).some(({ status }) => status === "active")) {
@@ -1757,7 +2359,7 @@ function bindTaskRole(args, store, options) {
1757
2359
  }, "desired Agent binding update");
1758
2360
  const agent = requireAgent(tx, args[2]);
1759
2361
  const binding = role.agentBindings[agent.id]
1760
- ?? createRoleAgentBinding({ id: agent.id, adapterId: agent.adapterId });
2362
+ ?? createRoleAgentBinding(agent);
1761
2363
  const bound = updateRole(role, {
1762
2364
  agentBindings: { ...role.agentBindings, [agent.id]: binding }
1763
2365
  }, now);
@@ -1777,7 +2379,7 @@ function bindTaskRole(args, store, options) {
1777
2379
  const switched = (() => {
1778
2380
  try {
1779
2381
  return switchActiveRoleAgent(bound, existing, agent.id, {
1780
- activeTurn: tx.getActiveTurn(task.id, role.name) !== null,
2382
+ activeRun: tx.getActiveRun(task.id, role.name) !== null,
1781
2383
  nativeProcessRunning: currentSession !== undefined
1782
2384
  && currentSession.status === "active"
1783
2385
  }, now);
@@ -1790,6 +2392,10 @@ function bindTaskRole(args, store, options) {
1790
2392
  recordTaskEvent(tx, task.id, "role.agent-bound", {
1791
2393
  role: switched.role.name,
1792
2394
  agentId: agent.id,
2395
+ // Re-selecting an Agent must not revive its old management entrance.
2396
+ // This revokes authority, not the Session's independent execution fact.
2397
+ ...(role.name === LEADER_ROLE && currentSession?.nativeSessionId !== undefined
2398
+ ? { revokedNativeSessionId: currentSession.nativeSessionId } : {}),
1793
2399
  ...roleLaunchEventPayload(switched.role, switched.sessions)
1794
2400
  }, now);
1795
2401
  return { role: switched.role, mode: switched.mode };
@@ -1859,20 +2465,13 @@ function transferTaskRoleAuthority(args, store, options, action) {
1859
2465
  const sessions = tx.getTaskRoleSessionSet(task.id, role.name);
1860
2466
  const session = sessions?.sessions[role.activeAgentId];
1861
2467
  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") {
2468
+ if (sessions === null || sessions === undefined || session === undefined || binding === null || binding === undefined || session.status === "ended") {
1866
2469
  throw new Error(`Task Role has no live managed Provider: ${task.id}/${role.name}.`);
1867
2470
  }
1868
- const activation = currentProviderActivation(binding);
1869
- if (activation === null) {
1870
- throw new Error(`Provider Activation is not live: ${task.id}/${role.name}.`);
1871
- }
1872
2471
  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}.`);
2472
+ const activeRun = tx.getActiveRun(task.id, role.name);
2473
+ if (activeRun === null) {
2474
+ throw new Error(`Task Role has no active managed AgentRun for takeover: ${task.id}/${role.name}.`);
1876
2475
  }
1877
2476
  }
1878
2477
  if (action === "takeover"
@@ -1893,7 +2492,7 @@ function transferTaskRoleAuthority(args, store, options, action) {
1893
2492
  expectedEpoch: binding.authority.epoch,
1894
2493
  expectedOwner: binding.authority.owner,
1895
2494
  owner: desiredOwner,
1896
- holderId: action === "takeover" ? `human:${randomUUID()}` : activation.activationId,
2495
+ holderId: action === "takeover" ? `human:${randomUUID()}` : "controller",
1897
2496
  changedAt: now.toISOString()
1898
2497
  });
1899
2498
  const authority = updatedBinding.authority;
@@ -1914,7 +2513,6 @@ function transferTaskRoleAuthority(args, store, options, action) {
1914
2513
  action,
1915
2514
  taskId: task.id,
1916
2515
  roleName: role.name,
1917
- runtimeGenerationId: session.runtimeGenerationId,
1918
2516
  nativeSessionId: session.nativeSessionId,
1919
2517
  authority: {
1920
2518
  epoch: authority.epoch,
@@ -1947,6 +2545,8 @@ function taskWorkCommand(args, store, options) {
1947
2545
  return output(updateWorkScope(rest, store, options));
1948
2546
  if (command === "dispatch")
1949
2547
  return output(dispatchWork(rest, store, options));
2548
+ if (command === "synthesize")
2549
+ return synthesizeRuns(rest, "workItem", store, options);
1950
2550
  if (command === "review") {
1951
2551
  return rest[0] === "retry"
1952
2552
  ? retryFailedTaskReviewRound(rest.slice(1), store, options)
@@ -1986,14 +2586,10 @@ function editWork(args, store, options) {
1986
2586
  const result = store.transaction((tx) => {
1987
2587
  const item = requireWorkItem(tx, parsed.positionals[0], options);
1988
2588
  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);
2589
+ assertTaskOpen(task);
2590
+ if (task.status === "draft")
2591
+ assertDraftTaskExecutionFree(tx, task);
1993
2592
  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
2593
  if (item.status === "retired") {
1998
2594
  throw usageError(`Work Item is retired: ${item.id}.`);
1999
2595
  }
@@ -2014,7 +2610,7 @@ function editWork(args, store, options) {
2014
2610
  : parsed.options.has("--clear-role") ? null : undefined;
2015
2611
  if (typeof assignee === "string")
2016
2612
  requireRole(tx, task.id, assignee);
2017
- const updated = editDraftWorkItemDefinition(item, {
2613
+ const updated = editWorkItemDefinition(item, {
2018
2614
  ...(parsed.options.has("--title")
2019
2615
  ? { title: requiredOption(parsed.options, "--title") }
2020
2616
  : {}),
@@ -2031,7 +2627,21 @@ function editWork(args, store, options) {
2031
2627
  ...(baseRefs === undefined ? {} : { baseRefs }),
2032
2628
  ...(assignee === undefined ? {} : { assignee })
2033
2629
  }, now);
2034
- validateDraftWorkItemEdit(tx, task, updated);
2630
+ if (task.status === "draft")
2631
+ validateDraftWorkItemEdit(tx, task, updated);
2632
+ else {
2633
+ // Requirements can evolve while a frozen Assignment continues. Resource
2634
+ // scope and ownership changes still require an explicit idle boundary.
2635
+ if ((projects !== undefined || baseRefs !== undefined || assignee !== undefined)
2636
+ && tx.listRuns(task.id).some((run) => run.workItemId === item.id && run.status === "active")) {
2637
+ throw usageError(`Stop the active Work Item AgentRun before changing ownership or workspace scope: ${item.id}.`);
2638
+ }
2639
+ for (const dependencyId of updated.dependsOn) {
2640
+ if (tx.getWorkItem(task.id, dependencyId) === null) {
2641
+ throw usageError(`Work Item dependency not found: ${dependencyId}.`);
2642
+ }
2643
+ }
2644
+ }
2035
2645
  tx.saveWorkItem(task.id, updated);
2036
2646
  const changedFields = [
2037
2647
  parsed.options.has("--title") ? "title" : undefined,
@@ -2051,8 +2661,13 @@ function editWork(args, store, options) {
2051
2661
  workItemId: updated.id,
2052
2662
  revision: String(updated.revision),
2053
2663
  fields: changedFields.join(","),
2664
+ previous: editedFieldValues(item, changedFields),
2665
+ current: editedFieldValues(updated, changedFields),
2054
2666
  editedBy: actor
2055
2667
  }, now);
2668
+ if (actor !== "leader") {
2669
+ enqueueWork(tx, leaderMailbox(task.id), actor === "operator" ? "operator-input" : "user-message", now, [workItemRef(task.id, updated.id)], { source: actor });
2670
+ }
2056
2671
  return { task, item: updated };
2057
2672
  });
2058
2673
  options.runtime?.notifyStateChanged(result.task.id);
@@ -2129,8 +2744,8 @@ function updateWorkScope(args, store, options) {
2129
2744
  const item = requireWorkItem(tx, parsed.positionals[0], options);
2130
2745
  const task = requireTask(tx, item.taskId);
2131
2746
  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}.`);
2747
+ if (tx.getActiveRun(task.id, item.assignee ?? "") !== null) {
2748
+ throw usageError(`Stop the active Work Item AgentRun before changing scope: ${item.id}.`);
2134
2749
  }
2135
2750
  const requestedProjectIds = (parsed.multiOptions.get("--project") ?? []).map((reference) => {
2136
2751
  const project = resolveProject(task.projectBindings.map(({ projectId }) => requireProject(tx, projectId)), reference);
@@ -2160,12 +2775,16 @@ function updateWorkScope(args, store, options) {
2160
2775
  return `${updated.changed ? "Updated" : "Unchanged"} Work Item Project scope ${updated.item.id}: ${updated.item.writeProjectIds.join(", ") || "read-only"}\n`;
2161
2776
  }
2162
2777
  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);
2778
+ const usage = "Task work update usage: yui task work update <task>/<work> <todo|running|done|failed> [--summary <text>] [--artifact-ref git:<commit>:<relative-path> ...].";
2779
+ const parsed = parseMultiValueTail(args, new Set(["--summary"]), new Set(["--artifact-ref"]), usage);
2165
2780
  exactPositionals(parsed.positionals, 2, usage);
2166
2781
  const requested = parsed.positionals[1];
2167
2782
  const status = parseWorkStatus(requested);
2168
2783
  const summary = trimmed(parsed.options.get("--summary"));
2784
+ const artifactIds = parsed.multiOptions.get("--artifact-ref") ?? [];
2785
+ if (artifactIds.length > 0 && status !== "completed") {
2786
+ throw usageError("--artifact-ref is only valid when submitting a done Candidate.");
2787
+ }
2169
2788
  if (["completed", "failed"].includes(status)
2170
2789
  && summary === undefined) {
2171
2790
  throw usageError(`--summary is required when work becomes ${requested}.`);
@@ -2175,19 +2794,16 @@ function updateWork(args, store, options) {
2175
2794
  const current = requireWorkItem(tx, parsed.positionals[0], options);
2176
2795
  const task = requireTask(tx, current.taskId);
2177
2796
  assertTaskOpen(task);
2797
+ const artifactRefs = artifactIds.length === 0 ? undefined : fixedArtifactRefs(task.id, artifactIds);
2178
2798
  if (current.assignee === undefined) {
2179
2799
  taskActor(tx, options, task.id);
2180
2800
  if (status === "running") {
2181
2801
  assertWorkItemDependenciesCompletedForCommand(tx, current);
2182
2802
  }
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
2803
  const configuredReview = status === "completed" && !isTerminalWorkItemStatus(current.status)
2188
2804
  ? tx.getReviewConfig()
2189
2805
  : null;
2190
- const taskFinalContract = status === "completed" && current.status === "running"
2806
+ const taskFinalContract = status === "completed" && current.status === "open"
2191
2807
  ? taskFinalReviewContractForMutation(tx, task.id, options)
2192
2808
  : undefined;
2193
2809
  const candidatePolicy = taskFinalContract === undefined
@@ -2197,9 +2813,7 @@ function updateWork(args, store, options) {
2197
2813
  && current.writeProjectIds.length > 0;
2198
2814
  const metadataOnlyTaskFinalDelivery = taskFinalContract !== undefined
2199
2815
  && current.assignee === undefined;
2200
- const candidateRequired = status === "completed"
2201
- && current.status === "running"
2202
- && (projectDelivery || candidatePolicy !== null);
2816
+ const candidateRequired = status === "completed" && current.status === "open";
2203
2817
  const developWorkspace = tx.getWorkItemWorkspace(task.id, current.id);
2204
2818
  if (status === "completed"
2205
2819
  && projectDelivery
@@ -2211,6 +2825,7 @@ function updateWork(args, store, options) {
2211
2825
  ? submitWorkItemCandidate(current, {
2212
2826
  summary: summary,
2213
2827
  source: { type: "direct" },
2828
+ ...(artifactRefs === undefined ? {} : { artifactRefs }),
2214
2829
  ...(candidatePolicy === null ? {} : { reviewPolicy: candidatePolicy }),
2215
2830
  ...(taskFinalContract === undefined
2216
2831
  ? {}
@@ -2225,9 +2840,7 @@ function updateWork(args, store, options) {
2225
2840
  ? {}
2226
2841
  : { taskMainSnapshot: options.directTaskMainSnapshot })
2227
2842
  }, now)
2228
- : current.status === "failed" && status === "running"
2229
- ? retryFailedWorkItem(current, now)
2230
- : updateWorkItemStatus(current, status, now, isTerminalWorkItemStatus(status) ? summary : undefined);
2843
+ : updateWorkItemStatus(current, "open", now);
2231
2844
  tx.saveWorkItem(task.id, updated);
2232
2845
  if (summary !== undefined) {
2233
2846
  recordTaskEvent(tx, task.id, "work.updated", {
@@ -2250,29 +2863,29 @@ function updateWork(args, store, options) {
2250
2863
  };
2251
2864
  }
2252
2865
  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.");
2866
+ if (status !== "completed" || current.status !== "open") {
2867
+ throw usageError(`Assigned Work Item ${current.id} can only submit a completed direct AgentRun from running; `
2868
+ + "use dispatch, task run retry, or task work retire for other transitions.");
2256
2869
  }
2257
- if (tx.getActiveTurn(task.id, current.assignee) !== null) {
2258
- throw usageError(`Work Item main Turn is still active: ${current.id}/${current.assignee}.`);
2870
+ if (tx.getActiveRun(task.id, current.assignee) !== null) {
2871
+ throw usageError(`Work Item main AgentRun is still active: ${current.id}/${current.assignee}.`);
2259
2872
  }
2260
2873
  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) {
2874
+ const mainRun = tx.listRuns(task.id).filter((run) => (run.purpose === "execution"
2875
+ && run.workItemId === current.id
2876
+ && run.roleName === current.assignee
2877
+ && run.executionGroupId === undefined
2878
+ && run.executionLaneId === undefined
2879
+ && run.sourceExecutionGroupId === sourceGroup?.id
2880
+ && run.status === "completed"
2881
+ && run.result !== undefined)).at(-1);
2882
+ if (mainRun === undefined) {
2270
2883
  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}.`);
2884
+ ? `Work Item ${current.id} has no completed direct main AgentRun.`
2885
+ : `Work Item ${current.id} has no completed main AgentRun for ExecutionGroup ${sourceGroup.id}.`);
2273
2886
  }
2274
- if (mainTurn.result === undefined) {
2275
- throw dataError(`Completed WorkItem main Turn has no result: ${mainTurn.id}.`);
2887
+ if (mainRun.result === undefined) {
2888
+ throw dataError(`Completed WorkItem main AgentRun has no result: ${mainRun.id}.`);
2276
2889
  }
2277
2890
  const configuredReview = tx.getReviewConfig();
2278
2891
  const taskFinalContract = taskFinalReviewContractForMutation(tx, task.id, options);
@@ -2288,8 +2901,9 @@ function updateWork(args, store, options) {
2288
2901
  throw usageError(`Project-backed Work Item ${current.id} must be isolated before Candidate submission.`);
2289
2902
  }
2290
2903
  const updated = submitWorkItemCandidate(current, {
2291
- summary: `Result from Turn ${mainTurn.id}.`,
2292
- source: { type: "turn", turnId: mainTurn.id },
2904
+ summary: `Result from AgentRun ${mainRun.id}.`,
2905
+ source: { type: "run", runId: mainRun.id },
2906
+ ...(artifactRefs === undefined ? {} : { artifactRefs }),
2293
2907
  ...(candidatePolicy === null ? {} : { reviewPolicy: candidatePolicy }),
2294
2908
  ...(taskFinalContract === undefined
2295
2909
  ? {}
@@ -2307,12 +2921,12 @@ function updateWork(args, store, options) {
2307
2921
  workItemId: updated.id,
2308
2922
  status: updated.status,
2309
2923
  summary: summary,
2310
- turnId: mainTurn.id,
2924
+ runId: mainRun.id,
2311
2925
  ...leaderActionEventPayload(tx, task.id, options)
2312
2926
  }, now);
2313
2927
  enqueueWork(tx, taskMailbox(task.id), "work-updated", now, [
2314
2928
  workItemRef(task.id, updated.id),
2315
- turnRef(task.id, mainTurn.id)
2929
+ runRef(task.id, mainRun.id)
2316
2930
  ]);
2317
2931
  const reviewDispatch = candidatePolicy?.trigger === "always"
2318
2932
  ? queueReviewRound(tx, updated, candidatePolicy, "policy", now)
@@ -2324,7 +2938,7 @@ function updateWork(args, store, options) {
2324
2938
  };
2325
2939
  });
2326
2940
  notifyMailbox(options.runtime, taskMailbox(result.item.taskId), result.item.taskId);
2327
- if (result.item.status === "awaiting_acceptance" && status === "completed") {
2941
+ if ((result.item.status === "open" && result.item.candidates.length > 0) && status === "completed") {
2328
2942
  const failure = result.reviewDispatch?.round.status === "failed"
2329
2943
  ? `Review could not start: ${result.reviewDispatch.round.failure?.message ?? result.reviewDispatch.round.id}\n`
2330
2944
  : "";
@@ -2359,13 +2973,13 @@ function dispatchWork(args, store, options) {
2359
2973
  throw usageError(`Work Item has no Task Role assignee: ${item.id}. `
2360
2974
  + `The Task Leader must run "yui task work update ${item.id} running" and execute it directly.`);
2361
2975
  }
2362
- const lanePlan = planReplicatedWorkItemLanes(item.assignee, requestedLaneRoles, `execution-group-${tx.peekNextTurnId(task.id)}`);
2976
+ const lanePlan = planReplicatedWorkItemLanes(item.assignee, requestedLaneRoles, `execution-group-${tx.peekNextRunId(task.id)}`);
2363
2977
  const currentGroup = currentWorkItemExecutionGroup(item);
2364
- if (item.status !== "pending" && item.status !== "failed") {
2978
+ if (item.status !== "open") {
2365
2979
  throw usageError(`Work item ${item.id} cannot be dispatched from ${item.status}.`);
2366
2980
  }
2367
2981
  if (currentGroup !== undefined && !workItemExecutionGroupSettled(currentGroup)) {
2368
- throw usageError(`Work Item ${item.id} retains open ExecutionGroup ${currentGroup.id}; retry or settle its exact Lane Turns.`);
2982
+ throw usageError(`Work Item ${item.id} retains open ExecutionGroup ${currentGroup.id}; retry or settle its exact Lane AgentRuns.`);
2369
2983
  }
2370
2984
  assertWorkItemDependenciesCompletedForCommand(tx, item);
2371
2985
  const leaderOwned = item.assignee === "leader";
@@ -2389,18 +3003,16 @@ function dispatchWork(args, store, options) {
2389
3003
  }
2390
3004
  const roles = lanePlan.roles.map((name) => requireRole(tx, task.id, name));
2391
3005
  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.`);
3006
+ if (tx.getActiveRun(task.id, role.name) !== null) {
3007
+ throw usageError(`${task.id}/${role.name} already has an active run.`);
2394
3008
  }
2395
3009
  }
2396
3010
  const rawInput = trimmed(parsed.options.get("--input")) ?? item.objective;
2397
- let workItemForDispatch = item.status === "failed"
2398
- ? retryFailedWorkItem(item, now)
2399
- : item;
3011
+ let workItemForDispatch = prepareWorkItemDispatch(item, now);
2400
3012
  if (lanePlan.roles.length === 0) {
2401
3013
  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.`);
3014
+ if (tx.getActiveRun(task.id, role.name) !== null) {
3015
+ throw usageError(`${task.id}/${role.name} already has an active run.`);
2404
3016
  }
2405
3017
  const effective = resolveEffectiveLaunch({
2406
3018
  role,
@@ -2408,8 +3020,8 @@ function dispatchWork(args, store, options) {
2408
3020
  workspace,
2409
3021
  workItemWriteProjectIds: item.writeProjectIds
2410
3022
  });
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({
3023
+ const runId = tx.nextRunId(task.id);
3024
+ const run = createRun(runId, task.id, role.name, roleAgentSessionResumeMode(tx.getTaskRoleSessionSet(task.id, role.name), effective.agentId, effective), createRunInput({
2413
3025
  source: { type: "yui", channel: "workitem-dispatch" },
2414
3026
  directive: rawInput,
2415
3027
  deltaRefIds: []
@@ -2418,30 +3030,30 @@ function dispatchWork(args, store, options) {
2418
3030
  workspace,
2419
3031
  effective
2420
3032
  });
2421
- const snapshot = freezeTurnContextSnapshot(tx, {
3033
+ const snapshot = freezeRunContextSnapshot(tx, {
2422
3034
  taskId: task.id,
2423
- roleName: turn.roleName,
3035
+ roleName: run.roleName,
2424
3036
  purpose: "execution",
2425
3037
  workItemId: item.id
2426
3038
  }, now, "controller");
2427
- const withContext = withTurnContextSnapshot(turn, contextSnapshotRef(snapshot), contextSnapshotDeltaRefIds(tx, snapshot));
2428
- if (workItemForDispatch.status !== "running") {
2429
- workItemForDispatch = updateWorkItemStatus(workItemForDispatch, "running", now);
3039
+ const withContext = withRunContextSnapshot(run, contextSnapshotRef(snapshot), contextSnapshotDeltaRefIds(tx, snapshot));
3040
+ if (workItemForDispatch.status !== "open") {
3041
+ workItemForDispatch = updateWorkItemStatus(workItemForDispatch, "open", now);
2430
3042
  }
2431
3043
  tx.saveWorkItem(task.id, workItemForDispatch);
2432
- tx.saveTurn(withContext);
2433
- tx.saveActiveTurn(withContext);
2434
- enqueueRoleTurnDispatch(tx, {
3044
+ tx.saveRun(withContext);
3045
+ tx.saveActiveRun(withContext);
3046
+ enqueueRoleRunDispatch(tx, {
2435
3047
  taskId: task.id,
2436
3048
  roleName: role.name,
2437
- turnId: withContext.id,
3049
+ runId: withContext.id,
2438
3050
  reason: "turn-dispatched",
2439
3051
  occurredAt: now
2440
3052
  });
2441
- recordTaskEvent(tx, task.id, "turn.dispatched", turnLaunchEventPayload(withContext), now);
2442
- return { kind: "direct", turns: [withContext] };
3053
+ recordTaskEvent(tx, task.id, "run.dispatched", runLaunchEventPayload(withContext), now);
3054
+ return { kind: "direct", runs: [withContext] };
2443
3055
  }
2444
- const groupId = `execution-group-${tx.peekNextTurnId(task.id)}`;
3056
+ const groupId = `execution-group-${tx.peekNextRunId(task.id)}`;
2445
3057
  if (workItemForDispatch !== item) {
2446
3058
  // Freeze the Assignment against the retried WorkItem revision. The
2447
3059
  // aggregate transaction rolls this back if a later precondition fails.
@@ -2470,7 +3082,7 @@ function dispatchWork(args, store, options) {
2470
3082
  laneId,
2471
3083
  managedWorkspace: laneWorkspace,
2472
3084
  effective,
2473
- turnId: tx.nextTurnId(task.id),
3085
+ runId: tx.nextRunId(task.id),
2474
3086
  dispatchMode: roleAgentSessionResumeMode(tx.getTaskRoleSessionSet(task.id, role.name), effective.agentId, effective)
2475
3087
  };
2476
3088
  });
@@ -2503,15 +3115,15 @@ function dispatchWork(args, store, options) {
2503
3115
  root: plan.managedWorkspace.root,
2504
3116
  writableProjectIds: [...item.writeProjectIds]
2505
3117
  },
2506
- currentTurnId: plan.turnId
3118
+ currentRunId: plan.runId
2507
3119
  })), now);
2508
3120
  workItemForDispatch = attachWorkItemExecutionGroup(workItemForDispatch, group, now);
2509
- if (workItemForDispatch.status !== "running") {
2510
- workItemForDispatch = updateWorkItemStatus(workItemForDispatch, "running", now);
3121
+ if (workItemForDispatch.status !== "open") {
3122
+ workItemForDispatch = updateWorkItemStatus(workItemForDispatch, "open", now);
2511
3123
  }
2512
3124
  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({
3125
+ const runs = plans.map((plan, index) => {
3126
+ const run = createRun(plan.runId, task.id, plan.role.name, plan.dispatchMode, createRunInput({
2515
3127
  source: { type: "yui", channel: "workitem-dispatch" },
2516
3128
  directive: assignment.input,
2517
3129
  deltaRefIds: []
@@ -2522,37 +3134,37 @@ function dispatchWork(args, store, options) {
2522
3134
  workspace: plan.managedWorkspace,
2523
3135
  effective: plan.effective
2524
3136
  });
2525
- const snapshot = freezeTurnContextSnapshot(tx, {
3137
+ const snapshot = freezeRunContextSnapshot(tx, {
2526
3138
  taskId: task.id,
2527
- roleName: turn.roleName,
3139
+ roleName: run.roleName,
2528
3140
  purpose: "execution",
2529
3141
  workItemId: item.id
2530
3142
  }, now, "controller", assignment.contextSnapshotRef);
2531
- const withContext = withTurnContextSnapshot(turn, contextSnapshotRef(snapshot), contextSnapshotDeltaRefIds(tx, snapshot));
3143
+ const withContext = withRunContextSnapshot(run, contextSnapshotRef(snapshot), contextSnapshotDeltaRefIds(tx, snapshot));
2532
3144
  const prepared = options.executionLaneWorkspaces?.get(group.lanes[index].id);
2533
3145
  if (prepared !== undefined && tx.getManagedWorkspace(prepared.owner) === null) {
2534
3146
  tx.saveManagedWorkspace(prepared);
2535
3147
  }
2536
- tx.saveTurn(withContext);
2537
- tx.saveActiveTurn(withContext);
2538
- enqueueRoleTurnDispatch(tx, {
3148
+ tx.saveRun(withContext);
3149
+ tx.saveActiveRun(withContext);
3150
+ enqueueRoleRunDispatch(tx, {
2539
3151
  taskId: task.id,
2540
3152
  roleName: plan.role.name,
2541
- turnId: withContext.id,
3153
+ runId: withContext.id,
2542
3154
  reason: "turn-dispatched",
2543
3155
  occurredAt: now
2544
3156
  });
2545
- recordTaskEvent(tx, task.id, "turn.dispatched", turnLaunchEventPayload(withContext), now);
3157
+ recordTaskEvent(tx, task.id, "run.dispatched", runLaunchEventPayload(withContext), now);
2546
3158
  return withContext;
2547
3159
  });
2548
- return { kind: "replicated", turns };
3160
+ return { kind: "replicated", runs };
2549
3161
  });
2550
- for (const turn of dispatch.turns) {
2551
- notifyMailbox(options.runtime, roleMailbox(turn.taskId, turn.roleName), turn.taskId);
3162
+ for (const run of dispatch.runs) {
3163
+ notifyMailbox(options.runtime, roleMailbox(run.taskId, run.roleName), run.taskId);
2552
3164
  }
2553
3165
  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`;
3166
+ ? `Direct WorkItem AgentRun queued as ${dispatch.runs[0].id}\n`
3167
+ : `Dispatch queued for ${dispatch.runs.length} replicated Lanes\n`;
2556
3168
  }
2557
3169
  function replicatedProducerAssignmentInput(input, requiresCodeRef) {
2558
3170
  return [
@@ -2568,7 +3180,7 @@ function replicatedProducerAssignmentInput(input, requiresCodeRef) {
2568
3180
  }
2569
3181
  function acceptWork(args, store, options) {
2570
3182
  const usage = "Task work accept usage: yui task work accept <task>/<work> --summary <text>.";
2571
- const parsed = parseTail(args, new Set(["--summary"]), usage);
3183
+ const parsed = parseTail(args, new Set(["--summary", "--candidate"]), usage);
2572
3184
  exactPositionals(parsed.positionals, 1, usage);
2573
3185
  const summary = requiredOption(parsed.options, "--summary");
2574
3186
  const now = clock(options);
@@ -2579,17 +3191,23 @@ function acceptWork(args, store, options) {
2579
3191
  throw usageError(`Task is not active: ${task.id}/${task.status}.`);
2580
3192
  }
2581
3193
  const actor = taskActor(tx, options, task.id);
2582
- if (item.status !== "awaiting_acceptance") {
3194
+ if ((item.status !== "open" || item.candidates.length === 0)) {
2583
3195
  throw usageError(`Work Item is not awaiting acceptance: ${item.id}/${item.status}.`);
2584
3196
  }
2585
3197
  if (options.workItemIntegrationProof?.workspace.owner.type === "review-round") {
2586
3198
  throw usageError("A ReviewRound-owned workspace cannot be used for WorkItem acceptance.");
2587
3199
  }
2588
- const candidate = requireWorkItemCandidate(item);
3200
+ const candidateId = parsed.options.get("--candidate");
3201
+ const candidate = candidateId === undefined ? requireWorkItemCandidate(item)
3202
+ : item.candidates.find(({ id }) => id === candidateId);
3203
+ if (candidate === undefined)
3204
+ throw usageError(`Work Item Candidate not found: ${candidateId}.`);
3205
+ // A Candidate's artifact references are commit-pinned (git:<commit>:<path>):
3206
+ // the commit self-certifies the frozen bytes, so they cannot drift and are
3207
+ // re-validated for shape whenever the Candidate is loaded. Their bytes are
3208
+ // resolved lazily on the async read/context path, never re-derived from a DB
3209
+ // mirror here.
2589
3210
  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
3211
  const latestReview = reviewRoundsByIdentity(tx.listReviewRounds(item.taskId)
2594
3212
  .filter((round) => round.workItemId === item.id
2595
3213
  && round.candidateId === candidate.id)).at(-1);
@@ -2617,17 +3235,25 @@ function acceptWork(args, store, options) {
2617
3235
  && isolatedWorkspace.owner.workItemId === item.id
2618
3236
  && isolatedWorkspace.entries.some(({ access }) => access === "write")) {
2619
3237
  assertWorkItemIntegrationProof(tx, item.id, item.assignee, isolatedWorkspace, options.workItemIntegrationProof);
3238
+ for (const proof of options.workItemIntegrationProof.projects) {
3239
+ const candidateCommit = candidate.gitSnapshot?.projects
3240
+ .find(({ projectId }) => projectId === proof.projectId)?.commit;
3241
+ if (candidateCommit === undefined || candidateCommit !== proof.headCommit) {
3242
+ throw usageError(`Selected Candidate ${candidate.id} does not match the integrated result for ${proof.projectId}.`);
3243
+ }
3244
+ }
2620
3245
  }
2621
- const completed = updateWorkItemStatus(item, "completed", now, summary);
3246
+ const completed = updateWorkItemStatus(item, "accepted", now, summary, candidate.id);
2622
3247
  tx.saveWorkItem(item.taskId, completed);
2623
3248
  recordTaskEvent(tx, item.taskId, "work.accepted", {
2624
3249
  workItemId: item.id,
2625
3250
  candidateId: candidate.id,
2626
- ...(candidate.source.type === "turn"
2627
- ? { turnId: candidate.source.turnId }
3251
+ ...(candidate.source.type === "run"
3252
+ ? { runId: candidate.source.runId }
2628
3253
  : { workItemRevision: String(candidate.workItemRevision) }),
2629
3254
  acceptedBy: actor,
2630
3255
  summary,
3256
+ ...(latestReview === undefined ? {} : { reviewRoundId: latestReview.id }),
2631
3257
  ...(actor === "leader" ? leaderActionEventPayload(tx, item.taskId, options) : {})
2632
3258
  }, now);
2633
3259
  return completed;
@@ -2673,7 +3299,7 @@ function rejectWork(args, store, options) {
2673
3299
  throw usageError(`Task is not active: ${task.id}/${task.status}.`);
2674
3300
  }
2675
3301
  const actor = taskActor(tx, options, task.id);
2676
- if (item.status !== "awaiting_acceptance") {
3302
+ if (item.status === "retired" || item.candidates.length === 0) {
2677
3303
  throw usageError(`Work Item is not awaiting acceptance: ${item.id}/${item.status}.`);
2678
3304
  }
2679
3305
  const candidate = requireWorkItemCandidate(item);
@@ -2681,7 +3307,7 @@ function rejectWork(args, store, options) {
2681
3307
  if (activeReview !== undefined) {
2682
3308
  throw usageError(`ReviewRound is still active: ${activeReview.id}/${activeReview.status}.`);
2683
3309
  }
2684
- const failed = updateWorkItemStatus(item, "failed", now, summary);
3310
+ const { currentCandidateId: _declinedCandidate, ...failed } = updateWorkItemStatus(item, "open", now);
2685
3311
  tx.saveWorkItem(item.taskId, failed);
2686
3312
  recordTaskEvent(tx, item.taskId, "work.rejected", {
2687
3313
  workItemId: item.id,
@@ -2735,12 +3361,12 @@ function retireWork(args, store, options) {
2735
3361
  + `${activeWorkItemJob.id}/${activeWorkItemJob.status}. `
2736
3362
  + "Cancel or acknowledge it before retiring.");
2737
3363
  }
2738
- for (const run of tx.listTurns(task.id).filter((candidate) => (candidate.status === "active" && candidate.workItemId === item.id))) {
2739
- const terminal = terminalizeExactTaskTurn(tx, {
3364
+ for (const run of tx.listRuns(task.id).filter((candidate) => (candidate.status === "active" && candidate.workItemId === item.id))) {
3365
+ const terminal = terminalizeExactTaskRun(tx, {
2740
3366
  taskId: task.id,
2741
3367
  roleName: run.roleName,
2742
3368
  agentId: run.effective.agentId,
2743
- turnId: run.id,
3369
+ runId: run.id,
2744
3370
  outcome: {
2745
3371
  status: "failed",
2746
3372
  diagnostic: `Work Item retired: ${summary}`,
@@ -2748,7 +3374,7 @@ function retireWork(args, store, options) {
2748
3374
  }
2749
3375
  }, now);
2750
3376
  if (terminal.disposition !== "applied") {
2751
- throw usageError(`Work Item Turn changed during retirement: ${run.id}/${terminal.reason ?? "obsolete"}.`);
3377
+ throw usageError(`Work Item AgentRun changed during retirement: ${run.id}/${terminal.reason ?? "obsolete"}.`);
2752
3378
  }
2753
3379
  }
2754
3380
  }
@@ -2785,9 +3411,9 @@ function listWork(args, store) {
2785
3411
  exactPositionals(args, 1, "Task work list usage: yui task work list <task>.");
2786
3412
  const task = requireTask(store, args[0]);
2787
3413
  const items = store.listWorkItems(task.id);
2788
- const turns = store.listTurns(task.id);
3414
+ const runs = store.listRuns(task.id);
2789
3415
  const sessionSets = store.listRoleSessionSets(task.id);
2790
- const executions = items.map((item) => projectWorkItemExecution(item, turns, sessionSets));
3416
+ const executions = items.map((item) => projectWorkItemExecution(item, runs, sessionSets, store));
2791
3417
  const rendered = items.length === 0
2792
3418
  ? "No work items found.\n"
2793
3419
  : `${renderTable(`Task work: ${task.id}`, [
@@ -2814,7 +3440,7 @@ function listWork(args, store) {
2814
3440
  function showWork(args, store, options) {
2815
3441
  exactPositionals(args, 1, "Task work show usage: yui task work show <work>.");
2816
3442
  const item = requireWorkItem(store, args[0], options);
2817
- const execution = projectWorkItemExecution(item, store.listTurns(item.taskId), store.listRoleSessionSets(item.taskId));
3443
+ const execution = projectWorkItemExecution(item, store.listRuns(item.taskId), store.listRoleSessionSets(item.taskId), store);
2818
3444
  const replacement = item.disposition?.replacementWorkItemId;
2819
3445
  const rendered = [
2820
3446
  `Work Item: ${item.id}`,
@@ -2835,7 +3461,7 @@ function showWork(args, store, options) {
2835
3461
  }
2836
3462
  function compactWorkItemExecution(projection) {
2837
3463
  if (projection.shape === "direct")
2838
- return `main=${projection.mainTurn.status}`;
3464
+ return `main=${projection.mainRun.status}`;
2839
3465
  const counts = projection.laneCounts;
2840
3466
  return `lanes ${counts.running}/${counts.succeeded}/${counts.needsAttention}/${counts.failed}/${counts.unknown}; ${projection.synthesis.status}`;
2841
3467
  }
@@ -2848,16 +3474,16 @@ function renderWorkItemExecutionProjection(projection) {
2848
3474
  : [
2849
3475
  `Lanes: running=${projection.laneCounts.running}, succeeded=${projection.laneCounts.succeeded}, needs-attention=${projection.laneCounts.needsAttention}, failed=${projection.laneCounts.failed}, unknown=${projection.laneCounts.unknown}`,
2850
3476
  ...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"}`))
3477
+ + `run=${lane.currentRunId ?? "unknown"}; session=${lane.session}; `
3478
+ + `retry=${lane.retryRunId ?? "none"}; settle=${lane.settleRunId ?? "none"}`))
2853
3479
  ]),
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"}`,
3480
+ `Synthesis: ${projection.synthesis.status}; successful=${projection.synthesis.successfulLaneCount}; sources selected by Leader`,
3481
+ `Main AgentRun: ${projection.mainRun.runId ?? "unobserved"} [${projection.mainRun.status}]; role=${projection.mainRun.roleName ?? "unobserved"}; session=${projection.mainRun.session}; retry=${projection.mainRun.retryRunId ?? "none"}`,
3482
+ `Candidate Source: ${projection.candidate.candidateId ?? "none"} [${projection.candidate.status}]; source=${projection.candidate.sourceType ?? "unobserved"}; main=${projection.candidate.mainRunId ?? "unobserved"}`,
2857
3483
  ...(projection.candidate.sourceExecutionGroupId === undefined
2858
3484
  ? []
2859
3485
  : [
2860
- `Candidate Provenance: main ${projection.candidate.mainTurnId ?? "unobserved"} -> group ${projection.candidate.sourceExecutionGroupId} -> ${projection.candidate.successfulLaneTurns.map(({ laneId, successfulTurnId }) => `${laneId} -> ${successfulTurnId}`).join(", ") || "unobserved"}`
3486
+ `Candidate Provenance: main ${projection.candidate.mainRunId ?? "unobserved"} -> group ${projection.candidate.sourceExecutionGroupId} -> ${projection.candidate.successfulLaneRuns.map(({ laneId, successfulRunId }) => `${laneId} -> ${successfulRunId}`).join(", ") || "unobserved"}`
2861
3487
  ]),
2862
3488
  `Next Action: ${projection.nextAction.kind}; owner=${projection.nextAction.owners.join(", ") || "none"}; target=${projection.nextAction.targetIds.join(", ") || "none"}`
2863
3489
  ];
@@ -2882,7 +3508,7 @@ function reviewWork(args, store, options) {
2882
3508
  throw usageError(`Task is not active: ${task.id}/${task.status}.`);
2883
3509
  }
2884
3510
  const requestedBy = taskActor(tx, options, task.id);
2885
- if (item.status !== "awaiting_acceptance") {
3511
+ if ((item.status !== "open" || item.candidates.length === 0)) {
2886
3512
  throw usageError(`Work Item is not awaiting acceptance: ${item.id}/${item.status}.`);
2887
3513
  }
2888
3514
  const candidate = requireWorkItemCandidate(item);
@@ -2900,9 +3526,9 @@ function reviewWork(args, store, options) {
2900
3526
  && (round.status === "pending" || round.status === "running")))).at(-1);
2901
3527
  if (activeRound !== undefined) {
2902
3528
  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)
3529
+ && (lane.currentRunId === undefined
3530
+ || tx.getRun(task.id, lane.currentRunId)?.status === "failed"))) === true;
3531
+ if ((activeRound.status === "pending" && activeRound.reviewerRunId === undefined)
2906
3532
  || (activeRound.status === "running" && producerDispatchPending)) {
2907
3533
  const persistedRoles = activeRound.executionGroup?.lanes
2908
3534
  .map(({ roleName }) => roleName) ?? [];
@@ -2935,7 +3561,7 @@ function reviewWork(args, store, options) {
2935
3561
  if (result.kind === "busy") {
2936
3562
  const busy = result.availability;
2937
3563
  return output(`Reviewer ${busy.reviewerRoleName} is busy (${busy.phase}`
2938
- + `${busy.activeTurnId === undefined ? "" : `; Turn ${busy.activeTurnId}`}); `
3564
+ + `${busy.activeRunId === undefined ? "" : `; AgentRun ${busy.activeRunId}`}); `
2939
3565
  + `${busy.activeReviewRoundId === undefined
2940
3566
  ? ""
2941
3567
  : `active ReviewRound ${busy.activeReviewRoundId}; `}`
@@ -2950,17 +3576,49 @@ function reviewWork(args, store, options) {
2950
3576
  }
2951
3577
  /**
2952
3578
  * 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
3579
+ * created a Reviewer AgentRun. This is deliberately separate from `task run retry`:
3580
+ * that command requires an exact failed AgentRun and remains the only retry
2955
3581
  * path for a failed provider execution. Here the old terminal Round is an
2956
3582
  * immutable anchor and one fresh Round is created only after the same frozen
2957
3583
  * committed Integration/ChangeSet provenance and Reviewer independence fences
2958
3584
  * pass again.
2959
3585
  */
3586
+ function synthesizeRuns(args, kind, store, options) {
3587
+ const subject = kind === "workItem" ? "work" : "review";
3588
+ const usage = `Usage: yui task ${subject} synthesize <task>/<${subject}> --source-run <task>/<run> ...`;
3589
+ const parsed = parseMultiValueTail(args, new Set(), new Set(["--source-run"]), usage);
3590
+ exactPositionals(parsed.positionals, 1, usage);
3591
+ const reference = taskRecordReference(parsed.positionals[0], kind, "Synthesis target", options);
3592
+ const sources = parsed.multiOptions.get("--source-run") ?? [];
3593
+ const sourceRunIds = sources.map((value) => {
3594
+ const source = taskRecordReference(value, "run", "Source AgentRun", options);
3595
+ if (source.taskId !== reference.taskId)
3596
+ throw usageError("Synthesis sources must belong to the same Task.");
3597
+ return source.localId;
3598
+ });
3599
+ const now = clock(options);
3600
+ const run = store.transaction((tx) => {
3601
+ const actor = taskActor(tx, options, reference.taskId);
3602
+ const created = kind === "workItem"
3603
+ ? dispatchWorkItemSynthesis(tx, reference.taskId, reference.localId, sourceRunIds, now)
3604
+ : dispatchReviewSynthesis(tx, reference.taskId, reference.localId, sourceRunIds, now);
3605
+ recordTaskEvent(tx, reference.taskId, "run.synthesis-requested", {
3606
+ runId: created.id,
3607
+ requestedBy: actor,
3608
+ sourceRunIds: sourceRunIds.join(","),
3609
+ ...(actor === "leader" ? leaderActionEventPayload(tx, reference.taskId, options) : {})
3610
+ }, now);
3611
+ return created;
3612
+ });
3613
+ notifyMailbox(options.runtime, roleMailbox(run.taskId, run.roleName), run.taskId);
3614
+ return output(`Dispatched synthesis AgentRun ${run.taskId}/${run.id}\n`, { run });
3615
+ }
2960
3616
  function taskReviewCommand(args, store, options) {
2961
3617
  const [command, ...rest] = args;
2962
3618
  if (command === "request")
2963
3619
  return requestTaskReviewRound(rest, store, options);
3620
+ if (command === "synthesize")
3621
+ return synthesizeRuns(rest, "reviewRound", store, options);
2964
3622
  if (command === "retry")
2965
3623
  return retryFailedTaskReviewRound(rest, store, options);
2966
3624
  throw usageError(command === undefined
@@ -2968,7 +3626,7 @@ function taskReviewCommand(args, store, options) {
2968
3626
  : `Unknown command: task review ${command}`);
2969
3627
  }
2970
3628
  function requestTaskReviewRound(args, store, options) {
2971
- const usage = "Task review request usage: yui task review request <task> --role <global-role> "
3629
+ const usage = "Task review request usage: yui task review request <task> --role <reviewer-role> "
2972
3630
  + "[--lane-role <producer-role> ...] [--delta-recheck].";
2973
3631
  const parsed = parseMultiValueTail(args, new Set(["--role"]), new Set(["--lane-role"]), usage, new Set(["--delta-recheck"]));
2974
3632
  exactPositionals(parsed.positionals, 1, usage);
@@ -2993,8 +3651,8 @@ function requestTaskReviewRound(args, store, options) {
2993
3651
  throw usageError("Delta-recheck is not supported with a Task-final review contract.");
2994
3652
  }
2995
3653
  }
2996
- if (tx.getGlobalRole(reviewerRoleName) === null) {
2997
- throw usageError(`Global Role not found: ${reviewerRoleName}.`);
3654
+ if (tx.getRole(task.id, reviewerRoleName) === null && tx.getGlobalRole(reviewerRoleName) === null) {
3655
+ throw usageError(`Reviewer Role not found in this Task or global templates: ${reviewerRoleName}.`);
2998
3656
  }
2999
3657
  const provenance = taskReviewProvenance(tx, task, options);
3000
3658
  const producerCollision = taskReviewProducerCollision(provenance, reviewerRoleName);
@@ -3095,7 +3753,7 @@ function requestTaskReviewRound(args, store, options) {
3095
3753
  });
3096
3754
  if ("kind" in round && round.kind === "busy") {
3097
3755
  return output(`Reviewer ${round.reviewerRoleName} is busy (${round.phase}`
3098
- + `${round.activeTurnId === undefined ? "" : `; Turn ${round.activeTurnId}`}); `
3756
+ + `${round.activeRunId === undefined ? "" : `; AgentRun ${round.activeRunId}`}); `
3099
3757
  + `${round.activeReviewRoundId === undefined
3100
3758
  ? ""
3101
3759
  : `active ReviewRound ${round.activeReviewRoundId}; `}`
@@ -3134,26 +3792,26 @@ function validateDeltaRecheckRequest(store, taskId, candidate, preflight) {
3134
3792
  return preflight.record;
3135
3793
  }
3136
3794
  function assertTaskReviewRequestLane(store, taskId, reviewerRoleName, reusableRound) {
3137
- const activePointer = store.getActiveTurn(taskId, reviewerRoleName);
3795
+ const activePointer = store.getActiveRun(taskId, reviewerRoleName);
3138
3796
  if (reusableRound === undefined || reusableRound.status === "completed") {
3139
3797
  assertReviewerAvailable(store, taskId, reviewerRoleName);
3140
3798
  return;
3141
3799
  }
3142
3800
  if (reusableRound.status === "pending") {
3143
3801
  if (activePointer !== null) {
3144
- throw usageError(`Reviewer Role already has an active Turn: ${reviewerRoleName}.`);
3802
+ throw usageError(`Reviewer Role already has an active AgentRun: ${reviewerRoleName}.`);
3145
3803
  }
3146
3804
  assertReviewerAvailable(store, taskId, reviewerRoleName, reusableRound);
3147
3805
  return;
3148
3806
  }
3149
- const reviewerTurnId = reusableRound.reviewerTurnId;
3150
- if (reviewerTurnId === undefined
3807
+ const reviewerRunId = reusableRound.reviewerRunId;
3808
+ if (reviewerRunId === undefined
3151
3809
  && reusableRound.executionGroup?.lanes.some(({ disposition }) => disposition === "open")) {
3152
3810
  assertReviewerAvailable(store, taskId, reviewerRoleName, reusableRound);
3153
3811
  return;
3154
3812
  }
3155
- const activeMatches = reviewerTurnId !== undefined
3156
- && activePointer?.id === reviewerTurnId
3813
+ const activeMatches = reviewerRunId !== undefined
3814
+ && activePointer?.id === reviewerRunId
3157
3815
  && activePointer.status === "active";
3158
3816
  if (!activeMatches) {
3159
3817
  throw usageError(`Existing Task-final ReviewRound ${reusableRound.id} is running without its exact Reviewer execution.`);
@@ -3168,7 +3826,7 @@ function assertReviewerAvailable(store, taskId, reviewerRoleName, reusableRound)
3168
3826
  return;
3169
3827
  }
3170
3828
  throw usageError(`Reviewer ${reviewerRoleName} is busy (${availability.phase}`
3171
- + `${availability.activeTurnId === undefined ? "" : `; Turn ${availability.activeTurnId}`}`
3829
+ + `${availability.activeRunId === undefined ? "" : `; AgentRun ${availability.activeRunId}`}`
3172
3830
  + `${availability.activeReviewRoundId === undefined
3173
3831
  ? ""
3174
3832
  : `; ReviewRound ${availability.activeReviewRoundId}`}).`);
@@ -3178,10 +3836,10 @@ function reviewerBusyBelongsToRound(busy, round) {
3178
3836
  return false;
3179
3837
  if (busy.phase === "review-slot")
3180
3838
  return true;
3181
- if (busy.phase !== "active-turn" || busy.activeTurnId === undefined)
3839
+ if (busy.phase !== "active-turn" || busy.activeRunId === undefined)
3182
3840
  return false;
3183
- return round.reviewerTurnId === busy.activeTurnId
3184
- || round.executionGroup?.lanes.some(({ currentTurnId }) => (currentTurnId === busy.activeTurnId)) === true;
3841
+ return round.reviewerRunId === busy.activeRunId
3842
+ || round.executionGroup?.lanes.some(({ currentRunId }) => (currentRunId === busy.activeRunId)) === true;
3185
3843
  }
3186
3844
  function retryFailedTaskReviewRound(args, store, options) {
3187
3845
  exactPositionals(args, 1, "Task review retry usage: yui task review retry <task>/<review-round>.");
@@ -3199,11 +3857,11 @@ function retryFailedTaskReviewRound(args, store, options) {
3199
3857
  if ((round.scope ?? "work-item") !== "task") {
3200
3858
  throw usageError(`ReviewRound ${round.id} is not a failed Task-final ReviewRound.`);
3201
3859
  }
3202
- if (round.reviewerTurnId !== undefined) {
3860
+ if (round.reviewerRunId !== undefined) {
3203
3861
  if (round.status === "completed") {
3204
3862
  throw usageError(`ReviewRound ${round.id} is not retryable from ${round.status}.`);
3205
3863
  }
3206
- throw usageError(`ReviewRound ${round.id} has Reviewer Turn ${round.reviewerTurnId}; use task turn retry instead.`);
3864
+ throw usageError(`ReviewRound ${round.id} has Reviewer AgentRun ${round.reviewerRunId}; use task run retry instead.`);
3207
3865
  }
3208
3866
  if (round.status !== "failed" && round.status !== "pending") {
3209
3867
  throw usageError(`ReviewRound ${round.id} is not retryable from ${round.status}.`);
@@ -3243,9 +3901,9 @@ function retryFailedTaskReviewRound(args, store, options) {
3243
3901
  reviewer = createTaskRole(tx, task, round.reviewerRoleName, undefined, now, round.reviewerRoleName);
3244
3902
  tx.saveRole(task.id, reviewer);
3245
3903
  }
3246
- const activePointer = tx.getActiveTurn(task.id, reviewer.name);
3904
+ const activePointer = tx.getActiveRun(task.id, reviewer.name);
3247
3905
  if (activePointer !== null) {
3248
- throw usageError(`Reviewer Role already has an active Turn: ${reviewer.name}.`);
3906
+ throw usageError(`Reviewer Role already has an active AgentRun: ${reviewer.name}.`);
3249
3907
  }
3250
3908
  assertReviewerAvailable(tx, task.id, reviewer.name, round);
3251
3909
  // Issue 06: an already-pending Round is the idempotent retry result.
@@ -3266,50 +3924,50 @@ function retryFailedTaskReviewRound(args, store, options) {
3266
3924
  ? `Task-final Review retry requested as ${result.round.id}\n`
3267
3925
  : `Task-final Review retry already requested as ${result.round.id} (${result.round.status})\n`, { reviewRound: result.round });
3268
3926
  }
3269
- function taskTurnCommand(args, store, options) {
3927
+ function taskRunCommand(args, store, options) {
3270
3928
  const [command, ...rest] = args;
3271
3929
  if (command === "list")
3272
- return output(listTurns(rest, store, options));
3930
+ return output(listRuns(rest, store, options));
3273
3931
  if (command === "show")
3274
- return showTurn(rest, store, options);
3932
+ return showRun(rest, store, options);
3275
3933
  if (command === "context")
3276
- return turnContextCommand(rest, store, options);
3934
+ return runContextCommand(rest, store, options);
3277
3935
  if (command === "retry")
3278
- return retryTurn(rest, store, options);
3936
+ return retryRun(rest, store, options);
3279
3937
  if (command === "settle")
3280
- return settleTurn(rest, store, options);
3938
+ return settleRun(rest, store, options);
3281
3939
  if (command === "checkpoint")
3282
- return output(checkpointTurn(rest, store, options));
3940
+ return output(checkpointRun(rest, store, options));
3283
3941
  if (command === "retire")
3284
- return retireTurn(rest, store, options);
3942
+ return retireRun(rest, store, options);
3285
3943
  throw usageError(command === undefined
3286
3944
  ? "Task turn command is required."
3287
- : `Unknown command: task turn ${command}`);
3945
+ : `Unknown command: task run ${command}`);
3288
3946
  }
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));
3947
+ function settleRun(args, store, options) {
3948
+ exactPositionals(args, 1, "Task turn settle usage: yui task run settle <task>/<run>.");
3949
+ const previous = store.transaction((tx) => requireRun(tx, args[0], options));
3292
3950
  if (previous.purpose === "review") {
3293
3951
  if (previous.executionGroupId !== undefined
3294
3952
  && previous.executionLaneId !== undefined) {
3295
- return settleFailedReviewExecutionLaneTurn(previous, store, options);
3953
+ return settleFailedReviewExecutionLaneRun(previous, store, options);
3296
3954
  }
3297
- return settleStaleFinalReviewTurn(args, store, options);
3955
+ return settleStaleFinalReviewRun(args, store, options);
3298
3956
  }
3299
- return settleFailedExecutionLaneTurn(previous, store, options);
3957
+ return settleFailedExecutionLaneRun(previous, store, options);
3300
3958
  }
3301
- function settleFailedReviewExecutionLaneTurn(previous, store, options) {
3959
+ function settleFailedReviewExecutionLaneRun(previous, store, options) {
3302
3960
  const now = clock(options);
3303
3961
  const result = store.transaction((tx) => {
3304
- const run = tx.getTurn(previous.taskId, previous.id);
3962
+ const run = tx.getRun(previous.taskId, previous.id);
3305
3963
  if (run === null || run.status !== "failed" || run.purpose !== "review") {
3306
- throw usageError(`Turn ${previous.id} is not a failed review Turn.`);
3964
+ throw usageError(`AgentRun ${previous.id} is not a failed review AgentRun.`);
3307
3965
  }
3308
3966
  if (run.reviewRoundId === undefined
3309
3967
  || run.executionGroupId === undefined
3310
3968
  || run.executionLaneId === undefined
3311
3969
  || run.sourceExecutionGroupId !== undefined) {
3312
- throw usageError(`Turn ${run.id} is not a failed Review Producer Lane Turn.`);
3970
+ throw usageError(`AgentRun ${run.id} is not a failed Review Producer Lane AgentRun.`);
3313
3971
  }
3314
3972
  const task = requireTask(tx, run.taskId);
3315
3973
  if (task.status !== "active")
@@ -3317,80 +3975,79 @@ function settleFailedReviewExecutionLaneTurn(previous, store, options) {
3317
3975
  const actor = taskActor(tx, options, task.id);
3318
3976
  const round = tx.getReviewRound(task.id, run.reviewRoundId);
3319
3977
  if (round === null) {
3320
- throw dataError(`ReviewRound not found for Turn ${run.id}: ${run.reviewRoundId}.`);
3978
+ throw dataError(`ReviewRound not found for AgentRun ${run.id}: ${run.reviewRoundId}.`);
3321
3979
  }
3322
3980
  const group = round.executionGroup;
3323
3981
  if (group === undefined || group.id !== run.executionGroupId) {
3324
- throw usageError(`Turn ${run.id} no longer belongs to the Review ExecutionGroup.`);
3982
+ throw usageError(`AgentRun ${run.id} no longer belongs to the Review ExecutionGroup.`);
3325
3983
  }
3326
3984
  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.`);
3985
+ if (lane === undefined || lane.currentRunId !== run.id || lane.roleName !== run.roleName) {
3986
+ throw usageError(`AgentRun ${run.id} no longer owns its Review Producer Lane.`);
3329
3987
  }
3330
3988
  if (lane.disposition === "failed") {
3331
3989
  return {
3332
- turn: run,
3990
+ run: run,
3333
3991
  reviewRound: round,
3334
3992
  changed: false,
3335
- mainTurns: []
3993
+ mainRuns: []
3336
3994
  };
3337
3995
  }
3338
3996
  if (round.status !== "running" || lane.disposition !== "open") {
3339
- throw usageError(`Turn ${run.id} cannot settle ${round.id}/${group.id}/${lane.id} from `
3997
+ throw usageError(`AgentRun ${run.id} cannot settle ${round.id}/${group.id}/${lane.id} from `
3340
3998
  + `${round.status}/${lane.disposition}.`);
3341
3999
  }
3342
- const validation = validateExactTurnReviewRound(tx, run, { allowTerminal: true });
4000
+ const validation = validateExactRunReviewRound(tx, run, { allowTerminal: true });
3343
4001
  if (validation.disposition !== "applied") {
3344
- throw usageError(`Review Turn ${run.id} no longer matches its frozen Review Lane: `
4002
+ throw usageError(`Review AgentRun ${run.id} no longer matches its frozen Review Lane: `
3345
4003
  + `${validation.reason ?? "mismatch"}.`);
3346
4004
  }
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}.`);
4005
+ if (tx.getActiveExecutionLaneRun(task.id, group.id, lane.id) !== null) {
4006
+ throw usageError(`Review Producer Lane still has an active AgentRun: ${group.id}/${lane.id}.`);
3349
4007
  }
3350
4008
  const settledGroup = updateUnifiedExecutionLane(group, lane.id, {
3351
- currentTurnId: run.id,
4009
+ currentRunId: run.id,
3352
4010
  disposition: "failed"
3353
4011
  }, now);
3354
4012
  tx.saveReviewRound(task.id, updateReviewExecutionGroup(round, settledGroup));
3355
- recordTaskEvent(tx, task.id, "turn.review-settled", {
3356
- turnId: run.id,
4013
+ recordTaskEvent(tx, task.id, "run.review-settled", {
4014
+ runId: run.id,
3357
4015
  reviewRoundId: round.id,
3358
4016
  executionGroupId: group.id,
3359
4017
  executionLaneId: lane.id,
3360
4018
  settledBy: actor,
3361
4019
  ...(actor === "leader" ? leaderActionEventPayload(tx, task.id, options) : {})
3362
4020
  }, now);
3363
- const reconciliation = reconcileReviewMainTurns(tx, task.id, now);
3364
4021
  return {
3365
- turn: run,
4022
+ run: run,
3366
4023
  reviewRound: tx.getReviewRound(task.id, round.id),
3367
4024
  changed: true,
3368
- mainTurns: reconciliation.createdTurns
4025
+ mainRuns: []
3369
4026
  };
3370
4027
  });
3371
- for (const turn of result.mainTurns) {
3372
- notifyMailbox(options.runtime, roleMailbox(turn.taskId, turn.roleName), turn.taskId);
4028
+ for (const run of result.mainRuns) {
4029
+ notifyMailbox(options.runtime, roleMailbox(run.taskId, run.roleName), run.taskId);
3373
4030
  }
3374
4031
  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,
4032
+ ? `Settled failed Review Producer Lane from AgentRun ${result.run.id}\n`
4033
+ : `Failed Review Producer Lane already settled from AgentRun ${result.run.id}\n`, {
4034
+ run: result.run,
3378
4035
  reviewRound: result.reviewRound,
3379
- ...(result.mainTurns.length === 0 ? {} : { mainTurns: result.mainTurns })
4036
+ ...(result.mainRuns.length === 0 ? {} : { mainRuns: result.mainRuns })
3380
4037
  });
3381
4038
  }
3382
- function settleFailedExecutionLaneTurn(previous, store, options) {
4039
+ function settleFailedExecutionLaneRun(previous, store, options) {
3383
4040
  const now = clock(options);
3384
4041
  const result = store.transaction((tx) => {
3385
- const run = tx.getTurn(previous.taskId, previous.id);
4042
+ const run = tx.getRun(previous.taskId, previous.id);
3386
4043
  if (run === null || run.status !== "failed" || run.purpose !== "execution") {
3387
- throw usageError(`Turn ${previous.id} is not a failed execution Turn.`);
4044
+ throw usageError(`AgentRun ${previous.id} is not a failed execution AgentRun.`);
3388
4045
  }
3389
4046
  if (run.workItemId === undefined
3390
4047
  || run.executionGroupId === undefined
3391
4048
  || run.executionLaneId === undefined
3392
4049
  || run.sourceExecutionGroupId !== undefined) {
3393
- throw usageError(`Turn ${run.id} is not a failed WorkItem Execution Lane Turn.`);
4050
+ throw usageError(`AgentRun ${run.id} is not a failed WorkItem Execution Lane AgentRun.`);
3394
4051
  }
3395
4052
  const task = requireTask(tx, run.taskId);
3396
4053
  if (task.status !== "active")
@@ -3398,94 +4055,88 @@ function settleFailedExecutionLaneTurn(previous, store, options) {
3398
4055
  const actor = taskActor(tx, options, task.id);
3399
4056
  const item = tx.getWorkItem(task.id, run.workItemId);
3400
4057
  if (item === null)
3401
- throw dataError(`Work item not found for Turn ${run.id}: ${run.workItemId}.`);
4058
+ throw dataError(`Work item not found for AgentRun ${run.id}: ${run.workItemId}.`);
3402
4059
  const group = currentWorkItemExecutionGroup(item);
3403
4060
  if (group === undefined || group.id !== run.executionGroupId) {
3404
- throw usageError(`Turn ${run.id} no longer belongs to the current ExecutionGroup.`);
4061
+ throw usageError(`AgentRun ${run.id} no longer belongs to the current ExecutionGroup.`);
3405
4062
  }
3406
4063
  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.`);
4064
+ if (lane === undefined || lane.currentRunId !== run.id) {
4065
+ throw usageError(`AgentRun ${run.id} no longer owns its Execution Lane.`);
3409
4066
  }
3410
4067
  if (lane.disposition === "failed") {
3411
4068
  return {
3412
- turn: run,
4069
+ run: run,
3413
4070
  workItem: item,
3414
4071
  changed: false,
3415
- mainTurns: []
4072
+ mainRuns: []
3416
4073
  };
3417
4074
  }
3418
- if (item.status !== "running" || lane.disposition !== "open") {
3419
- throw usageError(`Turn ${run.id} cannot settle ${item.id}/${group.id}/${lane.id} from `
4075
+ if (item.status !== "open" || lane.disposition !== "open") {
4076
+ throw usageError(`AgentRun ${run.id} cannot settle ${item.id}/${group.id}/${lane.id} from `
3420
4077
  + `${item.status}/${lane.disposition}.`);
3421
4078
  }
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}.`);
4079
+ if (tx.getActiveExecutionLaneRun(task.id, group.id, lane.id) !== null) {
4080
+ throw usageError(`Execution Lane still has an active AgentRun: ${group.id}/${lane.id}.`);
3424
4081
  }
3425
4082
  const settledGroup = updateWorkItemExecutionLane(group, lane.id, {
3426
- currentTurnId: run.id,
4083
+ currentRunId: run.id,
3427
4084
  disposition: "failed"
3428
4085
  }, now);
3429
4086
  const settledItem = updateWorkItemExecutionGroup(item, settledGroup, now);
3430
4087
  tx.saveWorkItem(task.id, settledItem);
3431
- recordTaskEvent(tx, task.id, "turn.execution-settled", {
3432
- turnId: run.id,
4088
+ recordTaskEvent(tx, task.id, "run.execution-settled", {
4089
+ runId: run.id,
3433
4090
  workItemId: item.id,
3434
4091
  executionGroupId: group.id,
3435
4092
  executionLaneId: lane.id,
3436
4093
  settledBy: actor,
3437
4094
  ...(actor === "leader" ? leaderActionEventPayload(tx, task.id, options) : {})
3438
4095
  }, now);
3439
- const reconciliation = reconcileWorkItemMainTurns(tx, task.id, now);
3440
4096
  return {
3441
- turn: run,
4097
+ run: run,
3442
4098
  workItem: tx.getWorkItem(task.id, item.id) ?? settledItem,
3443
4099
  changed: true,
3444
- mainTurns: reconciliation.createdTurns
4100
+ mainRuns: []
3445
4101
  };
3446
4102
  });
3447
- for (const turn of result.mainTurns) {
3448
- notifyMailbox(options.runtime, roleMailbox(turn.taskId, turn.roleName), turn.taskId);
4103
+ for (const run of result.mainRuns) {
4104
+ notifyMailbox(options.runtime, roleMailbox(run.taskId, run.roleName), run.taskId);
3449
4105
  }
3450
4106
  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,
4107
+ ? `Settled failed Execution Lane from AgentRun ${result.run.id}\n`
4108
+ : `Failed Execution Lane already settled from AgentRun ${result.run.id}\n`, {
4109
+ run: result.run,
3454
4110
  workItem: result.workItem,
3455
- ...(result.mainTurns.length === 0 ? {} : { mainTurns: result.mainTurns })
4111
+ ...(result.mainRuns.length === 0 ? {} : { mainRuns: result.mainRuns })
3456
4112
  });
3457
4113
  }
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>].";
4114
+ function retireRun(args, store, options) {
4115
+ 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
4116
  const parsed = parseTail(args, new Set([
3461
4117
  "--reason",
3462
4118
  "--expected-progress-at",
3463
4119
  "--progress-at",
3464
4120
  "--agent-id",
3465
4121
  "--adapter-id",
3466
- "--native-session-id",
3467
- "--launch-id"
4122
+ "--native-session-id"
3468
4123
  ]), usage);
3469
4124
  exactPositionals(parsed.positionals, 1, usage);
3470
4125
  const reason = requiredOption(parsed.options, "--reason");
3471
- const reference = taskRecordReference(parsed.positionals[0], "turn", "Turn reference", options);
4126
+ const reference = taskRecordReference(parsed.positionals[0], "run", "AgentRun reference", options);
3472
4127
  const now = clock(options);
3473
4128
  const result = store.transaction((tx) => {
3474
4129
  const task = requireTask(tx, reference.taskId);
3475
4130
  assertTaskOpen(task);
3476
4131
  const actor = taskActor(tx, options, task.id);
3477
- let run = tx.getTurn(task.id, reference.localId);
4132
+ let run = tx.getRun(task.id, reference.localId);
3478
4133
  if (run === null)
3479
- throw dataError(`Turn not found: ${task.id}/${reference.localId}.`);
4134
+ throw dataError(`AgentRun not found: ${task.id}/${reference.localId}.`);
3480
4135
  const events = tx.listEvents(task.id);
3481
- if (isTaskRecordRetired(events, "turn", run.id)) {
3482
- return { task, turn: run, changed: false };
4136
+ if (isTaskRecordRetired(events, "run", run.id)) {
4137
+ return { task, run: run, changed: false };
3483
4138
  }
3484
4139
  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
4140
  const expectedProgressAt = requiredOption(parsed.options, parsed.options.has("--expected-progress-at")
3490
4141
  ? "--expected-progress-at"
3491
4142
  : "--progress-at");
@@ -3495,35 +4146,30 @@ function retireTurn(args, store, options) {
3495
4146
  const agentId = requiredOption(parsed.options, "--agent-id");
3496
4147
  const adapterId = requiredOption(parsed.options, "--adapter-id");
3497
4148
  const nativeSessionId = parsed.options.get("--native-session-id");
3498
- const runtimeGenerationId = parsed.options.get("--launch-id");
3499
4149
  const sessions = tx.getTaskRoleSessionSet(task.id, run.roleName);
3500
4150
  const session = sessions?.sessions[run.effective.agentId];
3501
4151
  if (session?.nativeSessionId !== undefined && nativeSessionId === undefined) {
3502
- throw usageError("--native-session-id is required for this active Turn.", usage);
4152
+ throw usageError("--native-session-id is required for this active AgentRun.", usage);
3503
4153
  }
3504
- if (session?.nativeSessionId === undefined && runtimeGenerationId === undefined) {
3505
- throw usageError("--launch-id is required for an opaque active Turn.", usage);
3506
- }
3507
- const terminal = retireExactActiveTurn(tx, {
4154
+ const terminal = retireExactActiveRun(tx, {
3508
4155
  taskId: task.id,
3509
4156
  roleName: run.roleName,
3510
- turnId: run.id,
4157
+ runId: run.id,
3511
4158
  agentId,
3512
4159
  adapterId,
3513
4160
  ...(nativeSessionId === undefined ? {} : { nativeSessionId }),
3514
- ...(runtimeGenerationId === undefined ? {} : { runtimeGenerationId }),
3515
4161
  expectedProgressAt,
3516
- reason: `Turn retired: ${reason}`
4162
+ reason: `AgentRun retired: ${reason}`
3517
4163
  }, now);
3518
- if (terminal.disposition !== "applied" || terminal.turn === null) {
4164
+ if (terminal.disposition !== "applied" || terminal.run === null) {
3519
4165
  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"}.`);
4166
+ ? `AgentRun retirement is blocked: ${run.id}/${terminal.reason ?? "unsafe"}.`
4167
+ : `AgentRun changed during retirement: ${run.id}/${terminal.reason ?? "obsolete"}.`);
3522
4168
  }
3523
- run = terminal.turn;
4169
+ run = terminal.run;
3524
4170
  }
3525
- recordTaskEvent(tx, task.id, "turn.retired", {
3526
- turnId: run.id,
4171
+ recordTaskEvent(tx, task.id, "run.retired", {
4172
+ runId: run.id,
3527
4173
  reason,
3528
4174
  ...(parsed.options.get("--expected-progress-at") === undefined
3529
4175
  && parsed.options.get("--progress-at") === undefined
@@ -3535,9 +4181,6 @@ function retireTurn(args, store, options) {
3535
4181
  ...(parsed.options.get("--native-session-id") === undefined
3536
4182
  ? {}
3537
4183
  : { nativeSessionId: parsed.options.get("--native-session-id") }),
3538
- ...(parsed.options.get("--launch-id") === undefined
3539
- ? {}
3540
- : { runtimeGenerationId: parsed.options.get("--launch-id") }),
3541
4184
  ...(actor === "leader"
3542
4185
  ? leaderActionEventPayload(tx, task.id, options)
3543
4186
  : { retiredBy: actor })
@@ -3545,98 +4188,98 @@ function retireTurn(args, store, options) {
3545
4188
  tx.saveEvent(task.id, createTaskRecordRetirement({
3546
4189
  eventId: tx.nextEventId(task.id),
3547
4190
  taskId: task.id,
3548
- recordKind: "turn",
4191
+ recordKind: "run",
3549
4192
  recordId: run.id,
3550
4193
  reason,
3551
4194
  retiredBy: actor
3552
4195
  }, now));
3553
- return { task, turn: run, changed: true };
4196
+ return { task, run: run, changed: true };
3554
4197
  });
3555
4198
  if (result.changed)
3556
4199
  options.runtime?.notifyStateChanged(result.task.id);
3557
- return output(`Retired Turn ${result.task.id}/${result.turn.id}\n`, {
3558
- turn: result.turn,
4200
+ return output(`Retired AgentRun ${result.task.id}/${result.run.id}\n`, {
4201
+ run: result.run,
3559
4202
  retired: true
3560
4203
  });
3561
4204
  }
3562
- function turnContextCommand(args, store, options) {
4205
+ function runContextCommand(args, store, options) {
3563
4206
  const [first, ...rest] = args;
3564
4207
  if (first === "expand") {
3565
- const usage = "Task turn context expand usage: yui task turn context expand <task>/<turn> <ref-id> [--store <store>] [--mode full].";
4208
+ const usage = "Task turn context expand usage: yui task run context expand <task>/<run> <ref-id> [--store <store>] [--mode full].";
3566
4209
  const parsed = parseTail(rest, new Set(["--store", "--mode"]), usage);
3567
4210
  exactPositionals(parsed.positionals, 2, usage);
3568
4211
  const mode = parsed.options.get("--mode");
3569
4212
  if (mode !== undefined && mode !== "full") {
3570
- throw usageError("Turn Context expansion mode must be full.", usage);
4213
+ throw usageError("AgentRun Context expansion mode must be full.", usage);
3571
4214
  }
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")));
4215
+ const { taskId, runId } = parseRunContextReference(parsed.positionals[0]);
4216
+ authorizeRunContext(store, taskId, runId, options.environment);
4217
+ const expanded = store.transaction((tx) => expandRunContextRef(tx, taskId, runId, parsed.positionals[1], optionalNonEmptyOption(parsed.options, "--store")));
3575
4218
  return output(`${JSON.stringify(expanded, null, 2)}\n`, { context: expanded });
3576
4219
  }
3577
4220
  if (first === "delta") {
3578
4221
  if (rest.length !== 3 || rest[1] !== "--after") {
3579
- throw usageError("Task turn context delta usage: yui task turn context delta <task>/<turn> --after <cursor>.");
4222
+ throw usageError("Task turn context delta usage: yui task run context delta <task>/<run> --after <cursor>.");
3580
4223
  }
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])));
4224
+ const { taskId, runId } = parseRunContextReference(rest[0]);
4225
+ authorizeRunContext(store, taskId, runId, options.environment);
4226
+ const delta = store.transaction((tx) => (buildRunContextDelta(tx, taskId, runId, rest[2])));
3584
4227
  return output(`${JSON.stringify(delta, null, 2)}\n`, { contextDelta: delta });
3585
4228
  }
3586
4229
  if (first === undefined || rest.length !== 0) {
3587
- throw usageError("Task turn context usage: yui task turn context <task>/<turn>.");
4230
+ throw usageError("Task turn context usage: yui task run context <task>/<run>.");
3588
4231
  }
3589
- const { taskId, turnId } = parseTurnContextReference(first);
3590
- authorizeTurnContext(store, taskId, turnId, options.environment);
3591
- const pack = store.transaction((tx) => buildTurnContextPack(tx, taskId, turnId));
4232
+ const { taskId, runId } = parseRunContextReference(first);
4233
+ authorizeRunContext(store, taskId, runId, options.environment);
4234
+ const pack = store.transaction((tx) => buildRunContextPack(tx, taskId, runId));
3592
4235
  return output(`${JSON.stringify(pack, null, 2)}\n`, { context: pack });
3593
4236
  }
3594
- function parseTurnContextReference(value) {
3595
- const [taskId, turnId, extra] = value.split("/");
3596
- if (taskId === undefined || taskId.length === 0 || turnId === undefined || turnId.length === 0
4237
+ function parseRunContextReference(value) {
4238
+ const [taskId, runId, extra] = value.split("/");
4239
+ if (taskId === undefined || taskId.length === 0 || runId === undefined || runId.length === 0
3597
4240
  || extra !== undefined) {
3598
- throw usageError(`Turn context reference is invalid: ${value}.`);
4241
+ throw usageError(`AgentRun context reference is invalid: ${value}.`);
3599
4242
  }
3600
- return { taskId, turnId };
4243
+ return { taskId, runId };
3601
4244
  }
3602
4245
  /**
3603
- * A Turn Context Pack is information the Role's own runtime reads in order to
4246
+ * A AgentRun Context Pack is information the Role's own runtime reads in order to
3604
4247
  * 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,
4248
+ * caller must be the current runtime of the Task Role that owns the AgentRun,
3606
4249
  * proven by its per-Session caller key against durable state.
3607
4250
  *
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
4251
+ * It deliberately does not require the AgentRun to still be the active one. An
4252
+ * Agent whose AgentRun has advanced must still be able to read the context it was
3610
4253
  * given; losing read access to its own Role's history is what forces an
3611
4254
  * otherwise healthy Agent to stop and escalate to a human.
3612
4255
  */
3613
- function authorizeTurnContext(store, taskId, turnId, environment) {
4256
+ function authorizeRunContext(store, taskId, runId, environment) {
3614
4257
  const managed = environment?.YUI_SESSION_SCOPE !== undefined
3615
4258
  || environment?.YUI_TASK_ID !== undefined
3616
4259
  || environment?.YUI_ROLE !== undefined;
3617
4260
  if (!managed)
3618
4261
  return;
3619
- const run = store.getTurn(taskId, turnId);
4262
+ const run = store.getRun(taskId, runId);
3620
4263
  if (run === null) {
3621
- throw usageError(`Turn Context access is not authorized: ${taskId}/${turnId}.`);
4264
+ throw usageError(`AgentRun Context access is not authorized: ${taskId}/${runId}.`);
3622
4265
  }
3623
4266
  if (currentManagedRuntime(store, environment, taskId, run.roleName) === undefined) {
3624
- throw usageError(`Turn Context access requires the current runtime of ${taskId}/${run.roleName}.`);
4267
+ throw usageError(`AgentRun Context access requires the current runtime of ${taskId}/${run.roleName}.`);
3625
4268
  }
3626
4269
  }
3627
- function listTurns(args, store, options) {
3628
- const usage = "Task turn list usage: yui task turn list <task|task/work>.";
4270
+ function listRuns(args, store, options) {
4271
+ const usage = "Task turn list usage: yui task run list <task|task/work>.";
3629
4272
  exactPositionals(args, 1, usage);
3630
4273
  const reference = args[0];
3631
4274
  const task = store.getTask(reference);
3632
4275
  const item = task === null ? requireWorkItem(store, reference, options) : null;
3633
4276
  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";
4277
+ const runs = store.listRuns(taskId).filter((run) => (item === null || run.workItemId === item.id));
4278
+ if (runs.length === 0)
4279
+ return "No AgentRuns found.\n";
3637
4280
  const events = store.listEvents(taskId);
3638
- return `${renderTable(`Turns: ${item?.id ?? taskId}`, [
3639
- { header: "Turn", minWidth: 6, maxWidth: 20 },
4281
+ return `${renderTable(`AgentRuns: ${item?.id ?? taskId}`, [
4282
+ { header: "AgentRun", minWidth: 6, maxWidth: 20 },
3640
4283
  { header: "Role", minWidth: 4, maxWidth: 22 },
3641
4284
  { header: "Subject", minWidth: 7, maxWidth: 24 },
3642
4285
  { header: "Purpose", minWidth: 6, maxWidth: 10 },
@@ -3647,7 +4290,7 @@ function listTurns(args, store, options) {
3647
4290
  { header: "Status", minWidth: 6, maxWidth: 12 },
3648
4291
  { header: "History", minWidth: 7, maxWidth: 9 },
3649
4292
  { header: "Summary", minWidth: 8, maxWidth: 58 }
3650
- ], turns.map((run) => [
4293
+ ], runs.map((run) => [
3651
4294
  run.id,
3652
4295
  run.roleName,
3653
4296
  run.workItemId ?? (run.reviewRoundId === undefined ? "task" : `review:${run.reviewRoundId}`),
@@ -3657,29 +4300,29 @@ function listTurns(args, store, options) {
3657
4300
  run.effective.profileAccess,
3658
4301
  run.effective.permission.strategy,
3659
4302
  run.status,
3660
- isTaskRecordRetired(events, "turn", run.id) ? "retired" : "active",
4303
+ isTaskRecordRetired(events, "run", run.id) ? "retired" : "active",
3661
4304
  run.result?.output ?? run.result?.diagnostic ?? "-"
3662
4305
  ]), defaultTableWidth())}\n`;
3663
4306
  }
3664
4307
  /**
3665
- * Settles only the known bootstrap split where a failed Task-final Turn
4308
+ * Settles only the known bootstrap split where a failed Task-final AgentRun
3666
4309
  * still owns a running ReviewRound, but the committed Task heads have moved
3667
4310
  * on. This is deliberately narrower than retry: it cannot manufacture a
3668
4311
  * review or fail an arbitrary Round, and every identity/mailbox fence is
3669
4312
  * checked before the old Round changes. The next normal Task completion then
3670
4313
  * creates one fresh Round over the newer frozen Task heads.
3671
4314
  */
3672
- function settleStaleFinalReviewTurn(args, store, options) {
3673
- exactPositionals(args, 1, "Task turn settle usage: yui task turn settle <task>/<turn>.");
4315
+ function settleStaleFinalReviewRun(args, store, options) {
4316
+ exactPositionals(args, 1, "Task turn settle usage: yui task run settle <task>/<run>.");
3674
4317
  const now = clock(options);
3675
- const previous = store.transaction((tx) => requireTurn(tx, args[0], options));
4318
+ const previous = store.transaction((tx) => requireRun(tx, args[0], options));
3676
4319
  const result = store.transaction((tx) => {
3677
- const run = tx.getTurn(previous.taskId, previous.id);
4320
+ const run = tx.getRun(previous.taskId, previous.id);
3678
4321
  if (run === null || run.status !== "failed" || run.purpose !== "review") {
3679
- throw usageError(`Turn ${previous.id} is not a failed review Turn.`);
4322
+ throw usageError(`AgentRun ${previous.id} is not a failed review AgentRun.`);
3680
4323
  }
3681
4324
  if (run.reviewRoundId === undefined) {
3682
- throw usageError(`Review Turn ${run.id} has no ReviewRound.`);
4325
+ throw usageError(`Review AgentRun ${run.id} has no ReviewRound.`);
3683
4326
  }
3684
4327
  const task = requireTask(tx, run.taskId);
3685
4328
  if (task.status !== "active")
@@ -3687,26 +4330,26 @@ function settleStaleFinalReviewTurn(args, store, options) {
3687
4330
  const actor = taskActor(tx, options, task.id);
3688
4331
  const round = tx.getReviewRound(task.id, run.reviewRoundId);
3689
4332
  if (round === null) {
3690
- throw dataError(`ReviewRound not found for Turn ${run.id}: ${run.reviewRoundId}.`);
4333
+ throw dataError(`ReviewRound not found for AgentRun ${run.id}: ${run.reviewRoundId}.`);
3691
4334
  }
3692
4335
  if ((round.scope ?? "work-item") !== "task") {
3693
- throw usageError(`Review Turn ${run.id} is not a Task-final review; request a new WorkItem review `
4336
+ throw usageError(`Review AgentRun ${run.id} is not a Task-final review; request a new WorkItem review `
3694
4337
  + "for a new Candidate.");
3695
4338
  }
3696
4339
  const taskFinalContract = taskFinalReviewContractForMutation(tx, task.id, options);
3697
4340
  if (!sameTaskFinalReviewContract(round.taskFinalReviewContract, taskFinalContract)) {
3698
4341
  throw usageError(`Task final-review contract does not match ReviewRound ${round.id}.`);
3699
4342
  }
3700
- // This read-only compare-and-swap fence covers the exact Turn/Round,
4343
+ // This read-only compare-and-swap fence covers the exact AgentRun/Round,
3701
4344
  // Candidate, stored Review workspace, frozen Project scope, and frozen
3702
4345
  // Project heads before any mailbox or Round write.
3703
- const validation = validateExactTurnReviewRound(tx, run, { allowTerminal: true });
4346
+ const validation = validateExactRunReviewRound(tx, run, { allowTerminal: true });
3704
4347
  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"}.`);
4348
+ throw usageError(`Review AgentRun ${run.id} identity does not match its ReviewRound or frozen Task state changed: ${validation.reason ?? "mismatch"}.`);
3706
4349
  }
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}.`);
4350
+ const activeRoleRun = tx.getActiveRun(task.id, round.reviewerRoleName);
4351
+ if (activeRoleRun !== null) {
4352
+ throw usageError(`${task.id}/${round.reviewerRoleName} already has active AgentRun ${activeRoleRun.id}.`);
3710
4353
  }
3711
4354
  const taskRounds = reviewRoundsByIdentity(tx.listReviewRounds(task.id)
3712
4355
  .filter((entry) => (entry.scope ?? "work-item") === "task"));
@@ -3724,7 +4367,7 @@ function settleStaleFinalReviewTurn(args, store, options) {
3724
4367
  }
3725
4368
  if (round.status === "failed") {
3726
4369
  if (round.failure?.kind === "execution") {
3727
- return { turn: run, round, changed: false };
4370
+ return { run: run, round, changed: false };
3728
4371
  }
3729
4372
  throw usageError(`Final ReviewRound is already terminal: ${round.id}/${round.status}.`);
3730
4373
  }
@@ -3732,53 +4375,54 @@ function settleStaleFinalReviewTurn(args, store, options) {
3732
4375
  throw usageError(`Final ReviewRound is not stranded running: ${round.id}/${round.status}.`);
3733
4376
  }
3734
4377
  assertReviewerAvailable(tx, task.id, round.reviewerRoleName, round);
3735
- const summary = `Review Turn ${run.id} failed before delivery; committed Task heads changed.`;
4378
+ const summary = `Review AgentRun ${run.id} failed before delivery; committed Task heads changed.`;
3736
4379
  const terminal = finishReviewRound(round, "failed", now, { kind: "execution", message: summary });
3737
4380
  tx.saveReviewRound(task.id, terminal);
3738
- recordTaskEvent(tx, task.id, "turn.review-stale-settled", {
3739
- turnId: run.id,
4381
+ recordTaskEvent(tx, task.id, "run.review-stale-settled", {
4382
+ runId: run.id,
3740
4383
  reviewRoundId: round.id,
3741
4384
  previousTaskCandidate: JSON.stringify(round.taskCandidate),
3742
4385
  currentTaskCandidate: JSON.stringify(currentTaskCandidate),
3743
4386
  settledBy: actor,
3744
4387
  ...(actor === "leader" ? leaderActionEventPayload(tx, task.id, options) : {})
3745
4388
  }, now);
3746
- return { turn: run, round: terminal, changed: true };
4389
+ return { run: run, round: terminal, changed: true };
3747
4390
  });
3748
4391
  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 });
4392
+ ? `Settled obsolete final Review ${result.round.id} from failed AgentRun ${result.run.id}\n`
4393
+ : `Obsolete final Review already settled: ${result.round.id}/${result.run.id}\n`, { reviewRound: result.round, reviewRun: result.run });
3751
4394
  }
3752
- function retryTurn(args, store, options) {
3753
- exactPositionals(args, 1, "Task turn retry usage: yui task turn retry <task>/<turn>.");
4395
+ function retryRun(args, store, options) {
4396
+ exactPositionals(args, 1, "Task turn retry usage: yui task run retry <task>/<run>.");
3754
4397
  const now = clock(options);
3755
- const previous = store.transaction((tx) => requireTurn(tx, args[0], options));
4398
+ const previous = store.transaction((tx) => requireRun(tx, args[0], options));
3756
4399
  if (previous.purpose === "review") {
3757
4400
  return retryFailedReviewRun(previous, store, options, now);
3758
4401
  }
3759
4402
  const retried = store.transaction((tx) => {
3760
4403
  if (previous.status !== "failed") {
3761
- throw usageError(`Turn ${previous.id} is not retryable from ${previous.status}.`);
4404
+ throw usageError(`AgentRun ${previous.id} is not retryable from ${previous.status}.`);
3762
4405
  }
3763
4406
  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");
4407
+ assertTaskExecutionEnabled(task, "retrying a AgentRun");
4408
+ if (!runPurposeAdmitsTaskState(previous.purpose, task)) {
4409
+ throw usageError(`Task does not admit ${previous.purpose} retry: ${task.id}/${task.status}.`);
4410
+ }
3767
4411
  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.`);
4412
+ if (tx.getActiveRun(task.id, role.name) !== null) {
4413
+ throw usageError(`${task.id}/${role.name} already has an active run.`);
3770
4414
  }
3771
4415
  const sessions = tx.getTaskRoleSessionSet(task.id, role.name);
3772
4416
  const retryItem = previous.workItemId === undefined
3773
4417
  ? null
3774
4418
  : tx.getWorkItem(task.id, previous.workItemId);
3775
4419
  if (previous.workItemId !== undefined && retryItem === null) {
3776
- throw dataError(`Work item not found for Turn ${previous.id}: ${previous.workItemId}.`);
4420
+ throw dataError(`Work item not found for AgentRun ${previous.id}: ${previous.workItemId}.`);
3777
4421
  }
3778
4422
  const retriesSynthesisMain = previous.sourceExecutionGroupId !== undefined;
3779
4423
  if (retriesSynthesisMain
3780
4424
  && (previous.executionGroupId !== undefined || previous.executionLaneId !== undefined)) {
3781
- throw dataError(`Turn ${previous.id} has conflicting execution lineage.`);
4425
+ throw dataError(`AgentRun ${previous.id} has conflicting execution lineage.`);
3782
4426
  }
3783
4427
  const retryLaneBefore = previous.executionLaneId === undefined
3784
4428
  ? undefined
@@ -3796,57 +4440,54 @@ function retryTurn(args, store, options) {
3796
4440
  && retryLaneBefore !== undefined
3797
4441
  && retryLaneBefore.id === previous.executionLaneId
3798
4442
  && retryLaneBefore.disposition === "open"
3799
- && retryLaneBefore.currentTurnId === previous.id);
4443
+ && retryLaneBefore.currentRunId === previous.id);
3800
4444
  if (!exactCurrentLane) {
3801
- throw usageError(`Turn ${previous.id} no longer owns the current failed Execution Lane.`);
4445
+ throw usageError(`AgentRun ${previous.id} no longer owns the current failed Execution Lane.`);
3802
4446
  }
3803
4447
  const sourceGroup = !retriesSynthesisMain || retryItem === null
3804
4448
  ? undefined
3805
4449
  : 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)))
4450
+ const sourceMainRuns = retriesSynthesisMain
4451
+ ? chronologicalRuns(tx.listRuns(task.id).filter((run) => (run.purpose === "execution"
4452
+ && run.workItemId === previous.workItemId
4453
+ && run.sourceExecutionGroupId === previous.sourceExecutionGroupId)))
3810
4454
  : [];
3811
4455
  const exactSourceMain = !retriesSynthesisMain || (retryItem !== null
3812
- && retryItem.status === "running"
4456
+ && retryItem.status === "open"
3813
4457
  && retryItem.assignee === previous.roleName
3814
4458
  && sourceGroup !== undefined
3815
4459
  && 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);
4460
+ && selectedWorkItemSynthesisProducers(tx, retryItem, sourceGroup, synthesisSourceRunIds(tx, previous)).length > 0
4461
+ && sourceMainRuns.at(-1)?.id === previous.id);
3820
4462
  if (!exactSourceMain) {
3821
- throw usageError(`Turn ${previous.id} no longer owns the current WorkItem main synthesis.`);
4463
+ throw usageError(`AgentRun ${previous.id} no longer owns the current WorkItem main synthesis.`);
3822
4464
  }
3823
- const directMainTurns = retryItem === null
4465
+ const directMainRuns = retryItem === null
3824
4466
  ? []
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)));
4467
+ : chronologicalRuns(tx.listRuns(task.id).filter((run) => (run.purpose === "execution"
4468
+ && run.workItemId === retryItem.id
4469
+ && run.executionGroupId === undefined
4470
+ && run.executionLaneId === undefined
4471
+ && run.sourceExecutionGroupId === undefined)));
3830
4472
  const retriesDirectMain = retryItem !== null
3831
4473
  && !retriesExecutionLane
3832
4474
  && !retriesSynthesisMain;
3833
- const exactDirectMain = !retriesDirectMain || ((retryItem.status === "running" || retryItem.status === "failed")
4475
+ const exactDirectMain = !retriesDirectMain || (retryItem.status === "open"
3834
4476
  && retryItem.assignee === previous.roleName
3835
- && directMainTurns.at(-1)?.id === previous.id);
4477
+ && directMainRuns.at(-1)?.id === previous.id);
3836
4478
  if (!exactDirectMain) {
3837
- throw usageError(`Turn ${previous.id} no longer owns the current direct WorkItem execution.`);
4479
+ throw usageError(`AgentRun ${previous.id} no longer owns the current direct WorkItem execution.`);
3838
4480
  }
3839
- const groupedRunningRetry = retryItem?.status === "running"
4481
+ const groupedRunningRetry = retryItem?.status === "open"
3840
4482
  && retriesExecutionLane
3841
4483
  && exactCurrentLane;
3842
- const synthesisRunningRetry = retryItem?.status === "running"
4484
+ const synthesisRunningRetry = retryItem?.status === "open"
3843
4485
  && retriesSynthesisMain
3844
4486
  && exactSourceMain;
3845
- const directRunningRetry = retryItem?.status === "running"
4487
+ const directRunningRetry = retryItem?.status === "open"
3846
4488
  && retriesDirectMain
3847
4489
  && exactDirectMain;
3848
4490
  if (retryItem !== null
3849
- && retryItem.status !== "failed"
3850
4491
  && !groupedRunningRetry
3851
4492
  && !synthesisRunningRetry
3852
4493
  && !directRunningRetry) {
@@ -3873,7 +4514,7 @@ function retryTurn(args, store, options) {
3873
4514
  && (retryGroup === undefined
3874
4515
  || retryGroup.id !== previous.executionGroupId
3875
4516
  || retryLane === undefined))) {
3876
- throw dataError(`Turn ${previous.id} execution lineage no longer matches its Work Item.`);
4517
+ throw dataError(`AgentRun ${previous.id} execution lineage no longer matches its Work Item.`);
3877
4518
  }
3878
4519
  const retryManagedWorkspace = retryLane === undefined ? runWorkspace : previous.workspace;
3879
4520
  if (retryLane !== undefined) {
@@ -3893,7 +4534,7 @@ function retryTurn(args, store, options) {
3893
4534
  || !isDeepStrictEqual(writableProjectIds, [...retryLane.workspace.writableProjectIds].sort())
3894
4535
  || storedLaneWorkspace === null
3895
4536
  || !isDeepStrictEqual(storedLaneWorkspace, previous.workspace)) {
3896
- throw dataError(`Turn ${previous.id} Lane workspace is missing or has drifted.`);
4537
+ throw dataError(`AgentRun ${previous.id} Lane workspace is missing or has drifted.`);
3897
4538
  }
3898
4539
  }
3899
4540
  if (retriesSynthesisMain) {
@@ -3910,7 +4551,7 @@ function retryTurn(args, store, options) {
3910
4551
  || currentMainWorkspace === null
3911
4552
  || !isDeepStrictEqual(storedMainWorkspace, previous.workspace)
3912
4553
  || !isDeepStrictEqual(currentMainWorkspace, previous.workspace)) {
3913
- throw dataError(`Turn ${previous.id} WorkItem main workspace is missing or has drifted.`);
4554
+ throw dataError(`AgentRun ${previous.id} WorkItem main workspace is missing or has drifted.`);
3914
4555
  }
3915
4556
  }
3916
4557
  if (retriesDirectMain) {
@@ -3927,20 +4568,20 @@ function retryTurn(args, store, options) {
3927
4568
  || currentDirectWorkspace === null
3928
4569
  || !isDeepStrictEqual(storedDirectWorkspace, previous.workspace)
3929
4570
  || !isDeepStrictEqual(currentDirectWorkspace, previous.workspace)) {
3930
- throw dataError(`Turn ${previous.id} direct WorkItem workspace is missing or has drifted.`);
4571
+ throw dataError(`AgentRun ${previous.id} direct WorkItem workspace is missing or has drifted.`);
3931
4572
  }
3932
4573
  }
3933
4574
  const effective = retryLane?.effective ?? resolveEffectiveLaunch({
3934
4575
  role,
3935
- purpose: "execution",
4576
+ purpose: previous.purpose,
3936
4577
  ...(retryManagedWorkspace === undefined ? {} : { workspace: retryManagedWorkspace }),
3937
4578
  ...(retryItem === null ? {} : { workItemWriteProjectIds: retryItem.writeProjectIds })
3938
4579
  });
3939
- const turnId = tx.nextTurnId(task.id);
4580
+ const runId = tx.nextRunId(task.id);
3940
4581
  const runningGroup = retryGroup === undefined || retryLane === undefined
3941
4582
  ? undefined
3942
4583
  : updateWorkItemExecutionLane(retryGroup, retryLane.id, {
3943
- currentTurnId: turnId
4584
+ currentRunId: runId
3944
4585
  }, now);
3945
4586
  // Restart the bound lane and reopen the failed WorkItem as two ordered
3946
4587
  // single-step record revisions, matching the dispatch path. Folding both
@@ -3949,11 +4590,7 @@ function retryTurn(args, store, options) {
3949
4590
  const laneRestartedItem = retryItem === null || runningGroup === undefined
3950
4591
  ? retryItem
3951
4592
  : updateWorkItemExecutionGroup(retryItem, runningGroup, now);
3952
- const retriedItemWithGroup = laneRestartedItem === null
3953
- ? null
3954
- : laneRestartedItem.status === "failed"
3955
- ? retryFailedWorkItem(laneRestartedItem, now)
3956
- : laneRestartedItem;
4593
+ const retriedItemWithGroup = laneRestartedItem;
3957
4594
  if (retriedItemWithGroup !== null) {
3958
4595
  if (laneRestartedItem !== null && laneRestartedItem !== retryItem) {
3959
4596
  tx.saveWorkItem(task.id, laneRestartedItem);
@@ -3965,13 +4602,13 @@ function retryTurn(args, store, options) {
3965
4602
  const input = retriesSynthesisMain
3966
4603
  ? previous.inputs[0].input
3967
4604
  : (() => {
3968
- const retrySnapshot = freezeTurnContextSnapshot(tx, {
4605
+ const retrySnapshot = freezeRunContextSnapshot(tx, {
3969
4606
  taskId: task.id,
3970
4607
  roleName: role.name,
3971
- purpose: "execution",
4608
+ purpose: previous.purpose,
3972
4609
  ...(previous.workItemId === undefined ? {} : { workItemId: previous.workItemId })
3973
4610
  }, now, "controller", retryGroup?.assignment.contextSnapshotRef);
3974
- return createTurnInput({
4611
+ return createRunInput({
3975
4612
  source: {
3976
4613
  type: "yui",
3977
4614
  channel: previous.workItemId === undefined ? "task-dispatch" : "workitem-dispatch"
@@ -3983,7 +4620,8 @@ function retryTurn(args, store, options) {
3983
4620
  deltaRefIds: contextSnapshotDeltaRefIds(tx, retrySnapshot)
3984
4621
  });
3985
4622
  })();
3986
- const created = createTurn(turnId, task.id, role.name, roleAgentSessionResumeMode(sessions, effective.agentId, effective), input, now, {
4623
+ const created = createRun(runId, task.id, role.name, roleAgentSessionResumeMode(sessions, effective.agentId, effective), input, now, {
4624
+ purpose: previous.purpose,
3987
4625
  ...(previous.workItemId === undefined ? {} : { workItemId: previous.workItemId }),
3988
4626
  ...(runningGroup === undefined ? {} : {
3989
4627
  executionGroupId: runningGroup.id,
@@ -3995,8 +4633,8 @@ function retryTurn(args, store, options) {
3995
4633
  ...(retryManagedWorkspace === undefined ? {} : { workspace: retryManagedWorkspace }),
3996
4634
  effective
3997
4635
  });
3998
- tx.saveTurn(created);
3999
- tx.saveActiveTurn(created);
4636
+ tx.saveRun(created);
4637
+ tx.saveActiveRun(created);
4000
4638
  if (previous.workItemId !== undefined && retriedItemWithGroup !== null) {
4001
4639
  const item = retriedItemWithGroup;
4002
4640
  const workspace = tx.getWorkItemWorkspace(task.id, item.id);
@@ -4006,21 +4644,21 @@ function retryTurn(args, store, options) {
4006
4644
  + `cannot retry ${item.id}.`);
4007
4645
  }
4008
4646
  }
4009
- enqueueRoleTurnDispatch(tx, {
4647
+ enqueueRoleRunDispatch(tx, {
4010
4648
  taskId: task.id,
4011
4649
  roleName: role.name,
4012
- turnId: created.id,
4650
+ runId: created.id,
4013
4651
  reason: "turn-retried",
4014
4652
  occurredAt: now
4015
4653
  });
4016
- recordTaskEvent(tx, task.id, "turn.retried", {
4017
- ...turnLaunchEventPayload(created),
4018
- previousTurnId: previous.id
4654
+ recordTaskEvent(tx, task.id, "run.retried", {
4655
+ ...runLaunchEventPayload(created),
4656
+ previousRunId: previous.id
4019
4657
  }, now);
4020
- return { kind: "turn", turn: created };
4658
+ return { kind: "run", run: created };
4021
4659
  });
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`);
4660
+ notifyMailbox(options.runtime, roleMailbox(retried.run.taskId, retried.run.roleName), retried.run.taskId);
4661
+ return output(`Retry queued as ${retried.run.id} for ${retried.run.taskId}/${retried.run.roleName}\n`);
4024
4662
  }
4025
4663
  function actualTaskReviewCandidateForMutation(store, task, options) {
4026
4664
  if (options.actualTaskReviewCandidate === undefined) {
@@ -4063,7 +4701,7 @@ function taskReviewProducerCollision(provenance, reviewerRoleName) {
4063
4701
  *
4064
4702
  * When `expected` is supplied this is also the final dispatch compare-and-swap
4065
4703
  * fence: every bound Project must still point at the exact frozen physical
4066
- * head. Drift fails closed before a Reviewer Turn is created.
4704
+ * head. Drift fails closed before a Reviewer AgentRun is created.
4067
4705
  */
4068
4706
  function taskReviewProvenance(store, task, options, expected) {
4069
4707
  const candidate = actualTaskReviewCandidateForMutation(store, task, options);
@@ -4118,13 +4756,13 @@ function taskReviewProvenance(store, task, options, expected) {
4118
4756
  recordProducer(LEADER_ROLE, item.id);
4119
4757
  continue;
4120
4758
  }
4121
- const sourceRun = store.getTurn(task.id, sourceCandidate.source.turnId);
4759
+ const sourceRun = store.getRun(task.id, sourceCandidate.source.runId);
4122
4760
  if (sourceRun === null
4123
4761
  || sourceRun.workItemId !== item.id
4124
4762
  || sourceRun.purpose !== "execution"
4125
4763
  || sourceRun.status !== "completed") {
4126
- throw dataError(`Committed producer Candidate Turn is unavailable: `
4127
- + `${item.id}/${sourceCandidate.source.turnId}.`);
4764
+ throw dataError(`Committed producer Candidate AgentRun is unavailable: `
4765
+ + `${item.id}/${sourceCandidate.source.runId}.`);
4128
4766
  }
4129
4767
  recordProducer(sourceRun.roleName, item.id);
4130
4768
  if (sourceRun.sourceExecutionGroupId !== undefined) {
@@ -4133,7 +4771,7 @@ function taskReviewProvenance(store, task, options, expected) {
4133
4771
  throw dataError(`Committed producer ExecutionGroup is unavailable: `
4134
4772
  + `${item.id}/${sourceRun.sourceExecutionGroupId}.`);
4135
4773
  }
4136
- for (const producer of successfulWorkItemSynthesisProducers(store, item, executionGroup)) {
4774
+ for (const producer of selectedWorkItemSynthesisProducers(store, item, executionGroup, synthesisSourceRunIds(store, sourceRun))) {
4137
4775
  recordProducer(producer.roleName, item.id);
4138
4776
  }
4139
4777
  }
@@ -4182,7 +4820,7 @@ function queueTaskReviewRound(store, task, config, taskCandidate, options, now,
4182
4820
  const availability = projectReviewerAvailability(store, task.id, config.roleName);
4183
4821
  if (availability.kind === "busy") {
4184
4822
  throw usageError(`Reviewer ${config.roleName} is busy (${availability.phase}`
4185
- + `${availability.activeTurnId === undefined ? "" : `; Turn ${availability.activeTurnId}`}); `
4823
+ + `${availability.activeRunId === undefined ? "" : `; AgentRun ${availability.activeRunId}`}); `
4186
4824
  + `retry after ${availability.retryAfterSeconds}s.`);
4187
4825
  }
4188
4826
  let reviewer = store.getRole(task.id, config.roleName);
@@ -4251,16 +4889,16 @@ function resumablePendingFinalTaskReview(store, task, round, config, taskCandida
4251
4889
  || round.reviewerRoleName !== taskFinalContract.reviewerRoleName) {
4252
4890
  throw usageError(`Pending final ReviewRound Reviewer identity changed: ${round.id}.`);
4253
4891
  }
4254
- if (round.reviewerTurnId !== undefined) {
4255
- throw usageError(`Pending final ReviewRound already records Reviewer Turn ${round.reviewerTurnId}: ${round.id}.`);
4892
+ if (round.reviewerRunId !== undefined) {
4893
+ throw usageError(`Pending final ReviewRound already records Reviewer AgentRun ${round.reviewerRunId}: ${round.id}.`);
4256
4894
  }
4257
4895
  assertNoConflictingTaskReviewRound(store.listReviewRounds(task.id), round.id, round.reviewerRoleName);
4258
4896
  const reviewer = store.getRole(task.id, round.reviewerRoleName);
4259
4897
  if (reviewer === null || reviewer.name !== round.reviewerRoleName) {
4260
4898
  throw usageError(`Pending final ReviewRound Reviewer identity changed: ${round.id}.`);
4261
4899
  }
4262
- if (store.getActiveTurn(task.id, reviewer.name) !== null) {
4263
- throw usageError(`Reviewer Role already has an active Turn: ${reviewer.name}.`);
4900
+ if (store.getActiveRun(task.id, reviewer.name) !== null) {
4901
+ throw usageError(`Reviewer Role already has an active AgentRun: ${reviewer.name}.`);
4264
4902
  }
4265
4903
  assertPendingFinalReviewWorkspaceEvidence(store, task, round);
4266
4904
  return round;
@@ -4296,7 +4934,7 @@ function assertPendingFinalReviewWorkspaceEvidence(store, task, round) {
4296
4934
  }
4297
4935
  }
4298
4936
  /**
4299
- * Task-control retry of an exact failed review Turn. The old failed Turn
4937
+ * Task-control retry of an exact failed review AgentRun. The old failed AgentRun
4300
4938
  * remains the attempt trail, while the ReviewRound is reset to pending under
4301
4939
  * its existing identity. Every identity and frozen-head fence is
4302
4940
  * checked inside one transaction so a partial fail-old-without-reset state can
@@ -4304,12 +4942,12 @@ function assertPendingFinalReviewWorkspaceEvidence(store, task, round) {
4304
4942
  */
4305
4943
  function retryFailedReviewRun(previous, store, options, now) {
4306
4944
  const result = store.transaction((tx) => {
4307
- const run = tx.getTurn(previous.taskId, previous.id);
4945
+ const run = tx.getRun(previous.taskId, previous.id);
4308
4946
  if (run === null || run.status !== "failed" || run.purpose !== "review") {
4309
- throw usageError(`Turn ${previous.id} is not a failed review Turn.`);
4947
+ throw usageError(`AgentRun ${previous.id} is not a failed review AgentRun.`);
4310
4948
  }
4311
4949
  if (run.reviewRoundId === undefined) {
4312
- throw usageError(`Review Turn ${run.id} has no ReviewRound.`);
4950
+ throw usageError(`Review AgentRun ${run.id} has no ReviewRound.`);
4313
4951
  }
4314
4952
  const task = requireTask(tx, run.taskId);
4315
4953
  if (task.status !== "active")
@@ -4325,16 +4963,16 @@ function retryFailedReviewRun(previous, store, options, now) {
4325
4963
  : round.executionGroup?.lanes.find(({ id }) => id === run.executionLaneId);
4326
4964
  if (run.executionGroupId !== undefined && (round.executionGroup?.id !== run.executionGroupId
4327
4965
  || retryLane === undefined
4328
- || retryLane.currentTurnId !== run.id
4966
+ || retryLane.currentRunId !== run.id
4329
4967
  || retryLane.roleName !== run.roleName)) {
4330
- throw usageError(`Review Turn ${run.id} no longer owns its exact Review Lane attempt.`);
4968
+ throw usageError(`Review AgentRun ${run.id} no longer owns its exact Review Lane attempt.`);
4331
4969
  }
4332
4970
  const panelGroup = round.executionGroup !== undefined;
4333
4971
  const runningPanelLaneRetry = round.status === "running"
4334
4972
  && panelGroup
4335
4973
  && retryLane?.disposition === "open";
4336
4974
  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}.`);
4975
+ throw usageError(`Review AgentRun ${run.id} is not the current failed Lane attempt in running Round ${round.id}.`);
4338
4976
  }
4339
4977
  const retryReviewerRoleName = retryLane?.roleName ?? round.reviewerRoleName;
4340
4978
  if (round.status !== "failed"
@@ -4370,10 +5008,10 @@ function retryFailedReviewRun(previous, store, options, now) {
4370
5008
  throw dataError(`WorkItem ReviewRound has no Candidate anchor: ${round.id}.`);
4371
5009
  }
4372
5010
  if (run.workItemId !== round.workItemId) {
4373
- throw usageError(`Review Turn ${run.id} does not match WorkItem ${round.workItemId}.`);
5011
+ throw usageError(`Review AgentRun ${run.id} does not match WorkItem ${round.workItemId}.`);
4374
5012
  }
4375
5013
  const item = tx.getWorkItem(task.id, round.workItemId);
4376
- if (item === null || item.status !== "awaiting_acceptance") {
5014
+ if (item === null || (item.status !== "open" || item.candidates.length === 0)) {
4377
5015
  throw usageError(`WorkItem ReviewRound ${round.id} no longer has an awaiting Candidate.`);
4378
5016
  }
4379
5017
  const candidate = currentWorkItemCandidate(item);
@@ -4412,20 +5050,20 @@ function retryFailedReviewRun(previous, store, options, now) {
4412
5050
  throw usageError(`Reviewer already has an active review round for this candidate: ${activeRound.id}.`);
4413
5051
  }
4414
5052
  const reviewer = requireRole(tx, task.id, retryReviewerRoleName);
4415
- const activePointer = tx.getActiveTurn(task.id, reviewer.name);
5053
+ const activePointer = tx.getActiveRun(task.id, reviewer.name);
4416
5054
  // Issue 06: a completed same-Round retry is a no-write idempotent result.
4417
5055
  if (round.status === "completed") {
4418
5056
  assertReviewerAvailable(tx, task.id, reviewer.name);
4419
5057
  return { round, previousRun: run, created: false };
4420
5058
  }
4421
5059
  // 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
5060
+ // AgentRun. A stranded AgentRun (no active pointer) falls through and resets the
4423
5061
  // Round after the identity fences below.
4424
5062
  if (round.status === "running" && !runningPanelLaneRetry) {
4425
- const reviewerTurnId = round.reviewerTurnId;
4426
- const activeMatches = reviewerTurnId !== undefined
5063
+ const reviewerRunId = round.reviewerRunId;
5064
+ const activeMatches = reviewerRunId !== undefined
4427
5065
  && activePointer !== null
4428
- && activePointer.id === reviewerTurnId
5066
+ && activePointer.id === reviewerRunId
4429
5067
  && activePointer.status === "active";
4430
5068
  if (activePointer !== null && !activeMatches) {
4431
5069
  throw usageError(`Existing running ReviewRound ${round.id} lacks its exact active Reviewer execution.`);
@@ -4438,24 +5076,65 @@ function retryFailedReviewRun(previous, store, options, now) {
4438
5076
  // Issue 06: an already-pending Round is the idempotent retry result.
4439
5077
  if (round.status === "pending") {
4440
5078
  if (activePointer !== null) {
4441
- throw usageError(`Reviewer Role already has an active Turn: ${reviewer.name}.`);
5079
+ throw usageError(`Reviewer Role already has an active AgentRun: ${reviewer.name}.`);
4442
5080
  }
4443
5081
  assertReviewerAvailable(tx, task.id, reviewer.name, round);
4444
5082
  return { round, previousRun: run, created: false };
4445
5083
  }
4446
5084
  if (activePointer !== null) {
4447
- throw usageError(`Reviewer Role already has an active Turn: ${reviewer.name}.`);
5085
+ throw usageError(`Reviewer Role already has an active AgentRun: ${reviewer.name}.`);
4448
5086
  }
4449
5087
  assertReviewerAvailable(tx, task.id, reviewer.name, round);
4450
- const validation = validateExactTurnReviewRound(tx, run, { allowTerminal: true });
5088
+ const validation = validateExactRunReviewRound(tx, run, { allowTerminal: true });
4451
5089
  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"}.`);
5090
+ throw usageError(`Review AgentRun ${run.id} identity does not match its ReviewRound or frozen Task state changed: ${validation.reason ?? "mismatch"}.`);
5091
+ }
5092
+ if (run.sourceExecutionGroupId !== undefined) {
5093
+ assertTaskExecutionEnabled(task, "retrying Review synthesis");
5094
+ const group = round.executionGroup;
5095
+ if (round.status !== "failed" || round.reviewerRunId !== run.id
5096
+ || group?.id !== run.sourceExecutionGroupId
5097
+ || run.workspace === undefined) {
5098
+ throw usageError(`Review AgentRun ${run.id} no longer owns the current main synthesis.`);
5099
+ }
5100
+ selectedReviewSynthesisProducers(tx, round, group, synthesisSourceRunIds(tx, run));
5101
+ const effective = resolveEffectiveLaunch({
5102
+ role: reviewer,
5103
+ purpose: "review",
5104
+ workspace: run.workspace,
5105
+ reviewRoundId: round.id,
5106
+ reviewBaseCommit: round.reviewBaseCommit
5107
+ });
5108
+ 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, {
5109
+ purpose: "review",
5110
+ ...(run.workItemId === undefined ? {} : { workItemId: run.workItemId }),
5111
+ reviewRoundId: round.id,
5112
+ sourceExecutionGroupId: group.id,
5113
+ workspace: run.workspace,
5114
+ effective
5115
+ });
5116
+ const restarted = startReviewRound(retryReviewRound(round, requestedBy, now), created.id);
5117
+ tx.saveReviewRound(task.id, restarted);
5118
+ tx.saveRun(created);
5119
+ tx.saveActiveRun(created);
5120
+ enqueueRoleRunDispatch(tx, {
5121
+ taskId: task.id,
5122
+ roleName: reviewer.name,
5123
+ runId: created.id,
5124
+ reason: "turn-retried",
5125
+ occurredAt: now
5126
+ });
5127
+ recordTaskEvent(tx, task.id, "run.review-retried", {
5128
+ ...runLaunchEventPayload(created),
5129
+ previousRunId: run.id
5130
+ }, now);
5131
+ return { round: restarted, previousRun: run, created: true, run: created };
4453
5132
  }
4454
5133
  if (runningPanelLaneRetry) {
4455
5134
  const resetRound = retryRunningReviewExecutionLane(round, retryLane.id, run.id);
4456
5135
  tx.saveReviewRound(task.id, resetRound);
4457
- recordTaskEvent(tx, task.id, "turn.review-retried", {
4458
- turnId: run.id,
5136
+ recordTaskEvent(tx, task.id, "run.review-retried", {
5137
+ runId: run.id,
4459
5138
  reviewRoundId: round.id,
4460
5139
  executionLaneId: retryLane.id
4461
5140
  }, now);
@@ -4465,7 +5144,7 @@ function retryFailedReviewRun(previous, store, options, now) {
4465
5144
  // fence has passed. The outer transaction rolls back if Round creation fails.
4466
5145
  let roundToReset = round;
4467
5146
  if (round.status !== "failed") {
4468
- const summary = `Review Turn ${run.id} failed before delivery.`;
5147
+ const summary = `Review AgentRun ${run.id} failed before delivery.`;
4469
5148
  roundToReset = finishReviewRound(round, "failed", now, { kind: "execution", message: summary });
4470
5149
  tx.saveReviewRound(task.id, roundToReset);
4471
5150
  }
@@ -4474,41 +5153,44 @@ function retryFailedReviewRun(previous, store, options, now) {
4474
5153
  // stable across execution-attempt failures.
4475
5154
  const resetRound = retryReviewRound(roundToReset, requestedBy, now);
4476
5155
  tx.saveReviewRound(task.id, resetRound);
4477
- recordTaskEvent(tx, task.id, "turn.review-retried", {
4478
- turnId: run.id,
5156
+ recordTaskEvent(tx, task.id, "run.review-retried", {
5157
+ runId: run.id,
4479
5158
  reviewRoundId: round.id
4480
5159
  }, now);
4481
5160
  return { round: resetRound, previousRun: run, created: true };
4482
5161
  });
5162
+ if ("run" in result && result.run !== undefined) {
5163
+ notifyMailbox(options.runtime, roleMailbox(result.run.taskId, result.run.roleName), result.run.taskId);
5164
+ }
4483
5165
  return output(result.created
4484
5166
  ? `Review retry requested as ${result.round.id}\n`
4485
- : `Review retry already requested as ${result.round.id} (${result.round.status})\n`, { reviewRound: result.round });
5167
+ : `Review retry already requested as ${result.round.id} (${result.round.status})\n`, { reviewRound: result.round, ...("run" in result ? { run: result.run } : {}) });
4486
5168
  }
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].";
5169
+ /** AgentRun details are retained audit evidence; continuation uses durable Task state. */
5170
+ function showRun(args, store, options) {
5171
+ const usage = "Task turn show usage: yui task run show <task>/<run> [--json].";
4490
5172
  const asJson = args.includes("--json");
4491
5173
  const positionals = args.filter((arg) => arg !== "--json");
4492
5174
  exactPositionals(positionals, 1, usage);
4493
5175
  const data = store.transaction((tx) => {
4494
- const run = requireTurn(tx, positionals[0], options);
5176
+ const run = requireRun(tx, positionals[0], options);
4495
5177
  const retirement = tx.listEvents(run.taskId)
4496
5178
  .map(taskRecordRetirement)
4497
- .find((entry) => entry?.recordKind === "turn" && entry.recordId === run.id) ?? null;
4498
- return { turn: run, retirement };
5179
+ .find((entry) => entry?.recordKind === "run" && entry.recordId === run.id) ?? null;
5180
+ return { run: run, retirement, execution: runExecutionObservation(run, tx.getTaskRoleSessionSet(run.taskId, run.roleName)?.providerBinding, tx.listEvents(run.taskId)) };
4499
5181
  });
4500
5182
  if (asJson) {
4501
5183
  return { kind: "output", output: `${JSON.stringify(data, null, 2)}\n`, data };
4502
5184
  }
4503
5185
  return {
4504
5186
  kind: "output",
4505
- output: renderTurnShow(data.turn, data.retirement),
5187
+ output: `Delivery observation: ${data.execution.delivery}\n` + renderRunShow(data.run, data.retirement),
4506
5188
  data
4507
5189
  };
4508
5190
  }
4509
- function renderTurnShow(run, retirement) {
5191
+ function renderRunShow(run, retirement) {
4510
5192
  const lines = [
4511
- `Turn: ${run.id}`,
5193
+ `AgentRun: ${run.id}`,
4512
5194
  `Task: ${run.taskId}`,
4513
5195
  `Role: ${run.roleName}`,
4514
5196
  `Purpose: ${run.purpose}`,
@@ -4531,40 +5213,40 @@ function renderTurnShow(run, retirement) {
4531
5213
  return `${lines.join("\n")}\n`;
4532
5214
  }
4533
5215
  /**
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
5216
+ * Records a structured progress checkpoint for an active AgentRun. This is a durable
5217
+ * AgentRun fact, not a Task Message: it advances the AgentRun's durable-progress clock so
5218
+ * a healthy but long-running AgentRun keeps proving it is alive without adding
5219
+ * collaboration-narrative noise. It never completes, mutates the AgentRun, or wakes the
4538
5220
  * Leader.
4539
5221
  */
4540
- function checkpointTurn(args, store, options) {
4541
- const usage = "Task turn checkpoint usage: yui task turn checkpoint <turn> (--note <text>|--note-file <path|->).";
5222
+ function checkpointRun(args, store, options) {
5223
+ const usage = "Task turn checkpoint usage: yui task run checkpoint <run> (--note <text>|--note-file <path|->).";
4542
5224
  const parsed = parseTail(args, new Set(["--note", "--note-file"]), usage);
4543
5225
  exactPositionals(parsed.positionals, 1, usage);
4544
5226
  const note = readCommandText(parsed.options.get("--note"), parsed.options.get("--note-file"), "--note", usage);
4545
5227
  const now = clock(options);
4546
5228
  const event = store.transaction((tx) => {
4547
- const run = requireTurn(tx, parsed.positionals[0], options);
5229
+ const run = requireRun(tx, parsed.positionals[0], options);
4548
5230
  if (run.status !== "active") {
4549
- throw usageError(`Turn ${run.id} is already terminal: ${run.status}.`);
5231
+ throw usageError(`AgentRun ${run.id} is already terminal: ${run.status}.`);
4550
5232
  }
4551
5233
  const task = requireTask(tx, run.taskId);
4552
5234
  if (task.status !== "active")
4553
- throw usageError(inactiveTaskMessage(task, "checkpointing a Turn"));
4554
- const pointer = activeTurnPointer(tx, run);
5235
+ throw usageError(inactiveTaskMessage(task, "checkpointing a AgentRun"));
5236
+ const pointer = activeRunPointer(tx, run);
4555
5237
  if (pointer?.id !== run.id) {
4556
- throw usageError(`Turn is not active for ${task.id}/${run.roleName}: ${run.id}.`);
5238
+ throw usageError(`AgentRun is not active for ${task.id}/${run.roleName}: ${run.id}.`);
4557
5239
  }
4558
5240
  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,
5241
+ const recovered = isRoleRunStalled(events, run.id);
5242
+ const progress = recordTaskEventRecord(tx, task.id, RUN_PROGRESS_EVENT, {
5243
+ runId: run.id,
4562
5244
  note: truncateEventNote(note),
4563
5245
  ...(run.workItemId === undefined ? {} : { workItemId: run.workItemId })
4564
5246
  }, now);
4565
5247
  if (recovered) {
4566
- recordTaskEventRecord(tx, task.id, TURN_RECOVERED_EVENT, {
4567
- turnId: run.id,
5248
+ recordTaskEventRecord(tx, task.id, RUN_RECOVERED_EVENT, {
5249
+ runId: run.id,
4568
5250
  roleName: run.roleName,
4569
5251
  progressAt: now.toISOString(),
4570
5252
  kind: "checkpoint"
@@ -4612,12 +5294,12 @@ export function queueReviewRound(store, item, config, requestedBy, now, requeste
4612
5294
  reviewer = createTaskRole(store, task, roleName, undefined, now, roleName);
4613
5295
  store.saveRole(task.id, reviewer);
4614
5296
  }
4615
- if (store.getActiveTurn(item.taskId, reviewer.name) !== null) {
5297
+ if (store.getActiveRun(item.taskId, reviewer.name) !== null) {
4616
5298
  const pending = createPending();
4617
5299
  store.saveReviewRound(item.taskId, pending);
4618
5300
  const failed = finishReviewRound(pending, "failed", now, {
4619
5301
  kind: "dispatch",
4620
- message: `Reviewer Role already has an active Turn: ${reviewer.name}.`
5302
+ message: `Reviewer Role already has an active AgentRun: ${reviewer.name}.`
4621
5303
  });
4622
5304
  store.saveReviewRound(item.taskId, failed);
4623
5305
  return { round: failed };
@@ -4757,8 +5439,8 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
4757
5439
  && round.reviewerRoleName !== taskFinalContract.reviewerRoleName) {
4758
5440
  throw new TaskFinalReviewDispatchDriftError(`Task final-review Reviewer identity does not match ReviewRound ${round.id}.`);
4759
5441
  }
4760
- if (round.status === "pending" && round.reviewerTurnId !== undefined) {
4761
- throw new TaskFinalReviewDispatchDriftError(`Pending final ReviewRound already records Reviewer Turn ${round.reviewerTurnId}: `
5442
+ if (round.status === "pending" && round.reviewerRunId !== undefined) {
5443
+ throw new TaskFinalReviewDispatchDriftError(`Pending final ReviewRound already records Reviewer AgentRun ${round.reviewerRunId}: `
4762
5444
  + `${round.id}.`);
4763
5445
  }
4764
5446
  let currentTaskCandidate;
@@ -4810,8 +5492,8 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
4810
5492
  && (requestedReviewers.has(entry.reviewerRoleName)
4811
5493
  || entry.executionGroup?.lanes.some(({ roleName }) => (requestedReviewers.has(roleName))) === true)
4812
5494
  && !(entry.status === "running"
4813
- && entry.reviewerTurnId !== undefined
4814
- && tx.getTurn(task.id, entry.reviewerTurnId)?.status === "failed")));
5495
+ && entry.reviewerRunId !== undefined
5496
+ && tx.getRun(task.id, entry.reviewerRunId)?.status === "failed")));
4815
5497
  if (conflicting !== undefined) {
4816
5498
  throw new TaskFinalReviewDispatchDriftError(`Another active Task-final ReviewRound already exists: ${conflicting.id}/${conflicting.reviewerRoleName}.`);
4817
5499
  }
@@ -4838,8 +5520,8 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
4838
5520
  }
4839
5521
  const candidateLabel = taskScope
4840
5522
  ? "frozen Task candidate"
4841
- : candidate.source.type === "turn"
4842
- ? `candidate Turn ${candidate.source.turnId}`
5523
+ : candidate.source.type === "run"
5524
+ ? `candidate AgentRun ${candidate.source.runId}`
4843
5525
  : `revision ${candidate.workItemRevision}`;
4844
5526
  const frozenHeads = taskScope
4845
5527
  ? round.taskCandidate.projects
@@ -4854,7 +5536,7 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
4854
5536
  }
4855
5537
  const diffByProject = options.deltaRecheckDiff;
4856
5538
  if (diffByProject === undefined) {
4857
- throw new TaskFinalReviewDispatchDriftError(`Delta-recheck diff is missing for ${round.id}; the CLI preflight did not turn.`);
5539
+ throw new TaskFinalReviewDispatchDriftError(`Delta-recheck diff is missing for ${round.id}; the CLI preflight did not run.`);
4858
5540
  }
4859
5541
  try {
4860
5542
  verifyDeltaRecheckDiff(round.deltaRecheck, diffByProject);
@@ -4898,17 +5580,17 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
4898
5580
  "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
5581
  "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
5582
  "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.",
5583
+ "Report reviewBaseCommit, exact checks/results, material findings, and uncertainty. This AgentRun result completes only the Round and creates no Candidate or ChangeSet.",
4902
5584
  "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
5585
  ].join("\n");
4904
- const createdTurns = [];
5586
+ const createdRuns = [];
4905
5587
  if (round.executionGroup === undefined) {
4906
5588
  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}.`);
5589
+ return createdRuns;
5590
+ if (tx.getActiveRun(taskId, reviewer.name) !== null) {
5591
+ throw usageError(`Reviewer Role already has an active AgentRun: ${reviewer.name}.`);
4910
5592
  }
4911
- const turnId = tx.nextTurnId(taskId);
5593
+ const runId = tx.nextRunId(taskId);
4912
5594
  const effective = resolveEffectiveLaunch({
4913
5595
  role: reviewer,
4914
5596
  purpose: "review",
@@ -4916,14 +5598,14 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
4916
5598
  reviewRoundId: round.id,
4917
5599
  reviewBaseCommit: round.reviewBaseCommit
4918
5600
  });
4919
- const snapshot = freezeTurnContextSnapshot(tx, {
5601
+ const snapshot = freezeRunContextSnapshot(tx, {
4920
5602
  taskId,
4921
5603
  roleName: reviewer.name,
4922
5604
  purpose: "review",
4923
5605
  ...(item === undefined ? {} : { workItemId: item.id }),
4924
5606
  reviewRoundId: round.id
4925
5607
  }, now, "controller");
4926
- const created = createTurn(turnId, taskId, reviewer.name, roleAgentSessionResumeMode(tx.getTaskRoleSessionSet(taskId, reviewer.name), effective.agentId, effective), createTurnInput({
5608
+ const created = createRun(runId, taskId, reviewer.name, roleAgentSessionResumeMode(tx.getTaskRoleSessionSet(taskId, reviewer.name), effective.agentId, effective), createRunInput({
4927
5609
  source: {
4928
5610
  type: "yui",
4929
5611
  channel: item === undefined ? "task-dispatch" : "workitem-dispatch"
@@ -4938,27 +5620,27 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
4938
5620
  workspace: round.workspace,
4939
5621
  effective
4940
5622
  });
4941
- tx.saveTurn(created);
5623
+ tx.saveRun(created);
4942
5624
  tx.saveReviewRound(taskId, startReviewRound(round, created.id));
4943
- tx.saveActiveTurn(created);
4944
- enqueueRoleTurnDispatch(tx, {
5625
+ tx.saveActiveRun(created);
5626
+ enqueueRoleRunDispatch(tx, {
4945
5627
  taskId,
4946
5628
  roleName: reviewer.name,
4947
- turnId: created.id,
5629
+ runId: created.id,
4948
5630
  reason: "review-requested",
4949
5631
  occurredAt: now
4950
5632
  });
4951
- recordTaskEvent(tx, taskId, "turn.review-dispatched", turnLaunchEventPayload(created), now);
4952
- createdTurns.push(created);
4953
- return createdTurns;
5633
+ recordTaskEvent(tx, taskId, "run.review-dispatched", runLaunchEventPayload(created), now);
5634
+ createdRuns.push(created);
5635
+ return createdRuns;
4954
5636
  }
4955
5637
  let runningGroup = round.executionGroup;
4956
5638
  const dispatchLanes = runningGroup.lanes.filter((lane) => {
4957
5639
  if (lane.disposition !== "open")
4958
5640
  return false;
4959
- if (lane.currentTurnId === undefined)
5641
+ if (lane.currentRunId === undefined)
4960
5642
  return true;
4961
- return tx.getTurn(taskId, lane.currentTurnId)?.status === "failed";
5643
+ return tx.getRun(taskId, lane.currentRunId)?.status === "failed";
4962
5644
  });
4963
5645
  const preparedLaneWorkspaces = requireIsolatedReviewLaneWorkspaces(tx, round, dispatchLanes, options.executionLaneWorkspaces);
4964
5646
  for (const lane of dispatchLanes) {
@@ -4966,8 +5648,8 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
4966
5648
  if (laneReviewer === null) {
4967
5649
  throw usageError(`Review Producer Role not found: ${taskId}/${lane.roleName}.`);
4968
5650
  }
4969
- if (tx.getActiveTurn(taskId, lane.roleName) !== null) {
4970
- throw usageError(`Review Producer Role already has an active Turn: ${lane.roleName}.`);
5651
+ if (tx.getActiveRun(taskId, lane.roleName) !== null) {
5652
+ throw usageError(`Review Producer Role already has an active AgentRun: ${lane.roleName}.`);
4971
5653
  }
4972
5654
  const laneManagedWorkspace = preparedLaneWorkspaces.get(lane.id);
4973
5655
  const effective = lane.effective ?? resolveEffectiveLaunch({
@@ -4977,8 +5659,8 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
4977
5659
  reviewRoundId: round.id,
4978
5660
  reviewBaseCommit: round.reviewBaseCommit
4979
5661
  });
4980
- const turnId = tx.nextTurnId(taskId);
4981
- const input = createTurnInput({
5662
+ const runId = tx.nextRunId(taskId);
5663
+ const input = createRunInput({
4982
5664
  source: {
4983
5665
  type: "yui",
4984
5666
  channel: item === undefined ? "task-dispatch" : "workitem-dispatch"
@@ -5000,7 +5682,7 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
5000
5682
  deltaRefIds: []
5001
5683
  });
5002
5684
  runningGroup = updateUnifiedExecutionLane(runningGroup, lane.id, {
5003
- currentTurnId: turnId,
5685
+ currentRunId: runId,
5004
5686
  effective,
5005
5687
  workspace: {
5006
5688
  root: laneManagedWorkspace.root,
@@ -5009,7 +5691,7 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
5009
5691
  .map(({ projectId }) => projectId)
5010
5692
  }
5011
5693
  }, now);
5012
- createdTurns.push(createTurn(turnId, taskId, laneReviewer.name, roleAgentSessionResumeMode(tx.getTaskRoleSessionSet(taskId, laneReviewer.name), effective.agentId, effective), input, now, {
5694
+ createdRuns.push(createRun(runId, taskId, laneReviewer.name, roleAgentSessionResumeMode(tx.getTaskRoleSessionSet(taskId, laneReviewer.name), effective.agentId, effective), input, now, {
5013
5695
  ...(item === undefined ? {} : { workItemId: item.id }),
5014
5696
  purpose: "review",
5015
5697
  reviewRoundId: round.id,
@@ -5031,32 +5713,30 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
5031
5713
  tx.saveManagedWorkspace(prepared);
5032
5714
  }
5033
5715
  }
5034
- for (let index = 0; index < createdTurns.length; index += 1) {
5035
- const unboundTurn = createdTurns[index];
5036
- const snapshot = freezeTurnContextSnapshot(tx, {
5716
+ for (let index = 0; index < createdRuns.length; index += 1) {
5717
+ const unboundRun = createdRuns[index];
5718
+ const snapshot = freezeRunContextSnapshot(tx, {
5037
5719
  taskId,
5038
- roleName: unboundTurn.roleName,
5720
+ roleName: unboundRun.roleName,
5039
5721
  purpose: "review",
5040
5722
  ...(item === undefined ? {} : { workItemId: item.id }),
5041
5723
  reviewRoundId: round.id
5042
5724
  }, 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, {
5725
+ const created = withRunContextSnapshot(unboundRun, contextSnapshotRef(snapshot), contextSnapshotDeltaRefIds(tx, snapshot));
5726
+ createdRuns[index] = created;
5727
+ const laneReviewer = requireRole(tx, taskId, unboundRun.roleName);
5728
+ tx.saveRun(created);
5729
+ tx.saveActiveRun(created);
5730
+ enqueueRoleRunDispatch(tx, {
5049
5731
  taskId,
5050
5732
  roleName: laneReviewer.name,
5051
- turnId: created.id,
5733
+ runId: created.id,
5052
5734
  reason: "review-requested",
5053
5735
  occurredAt: now
5054
5736
  });
5055
- recordTaskEvent(tx, taskId, "turn.review-dispatched", turnLaunchEventPayload(created), now);
5737
+ recordTaskEvent(tx, taskId, "run.review-dispatched", runLaunchEventPayload(created), now);
5056
5738
  }
5057
- const reconciliation = reconcileReviewMainTurns(tx, taskId, now);
5058
- createdTurns.push(...reconciliation.createdTurns);
5059
- return createdTurns;
5739
+ return createdRuns;
5060
5740
  });
5061
5741
  for (const run of runs) {
5062
5742
  notifyMailbox(options.runtime, roleMailbox(run.taskId, run.roleName), run.taskId);
@@ -5106,6 +5786,28 @@ function requireWorkItemCandidate(item) {
5106
5786
  }
5107
5787
  return candidate;
5108
5788
  }
5789
+ /**
5790
+ * Parse `--artifact-ref git:<commit>:<relativePath>` selectors into canonical
5791
+ * commit-pinned references for the given Task. This is a PURE shape check with
5792
+ * no Git or DB I/O: the commit self-certifies the frozen bytes, so a valid
5793
+ * pinned reference is complete evidence. The bytes are proven to exist lazily
5794
+ * when they are resolved on the async read/context path.
5795
+ */
5796
+ function fixedArtifactRefs(taskId, refs) {
5797
+ if (new Set(refs).size !== refs.length)
5798
+ throw usageError("Artifact references must be unique.");
5799
+ return refs.map((ref) => {
5800
+ if (!isGitArtifactRefString(ref)) {
5801
+ throw usageError("Artifact reference must be a commit-pinned git:<commit>:<relativePath> reference.");
5802
+ }
5803
+ try {
5804
+ return parseGitArtifactRef(ref, taskId);
5805
+ }
5806
+ catch (error) {
5807
+ throw usageError(`Artifact reference is invalid: ${messageOf(error)}`);
5808
+ }
5809
+ });
5810
+ }
5109
5811
  /** ReviewRound ids are the durable Task-local creation order; wall time is not causal. */
5110
5812
  function reviewRoundsByIdentity(rounds) {
5111
5813
  return [...rounds].sort((left, right) => (left.id.localeCompare(right.id, undefined, { numeric: true })));
@@ -5118,7 +5820,9 @@ function activeReviewRoundForCandidate(store, item, candidate) {
5118
5820
  .at(-1);
5119
5821
  }
5120
5822
  function createTaskRole(store, task, roleName, explicitAgentId, now, sourceGlobalRoleName) {
5121
- const workspace = task.cwd ?? store.getConfig().defaultWorkspace ?? process.cwd();
5823
+ const workspace = task.status === "draft"
5824
+ ? join(`${store.rootDirectory()}.task-runtimes`, "planning", task.id)
5825
+ : task.cwd ?? store.getConfig().defaultWorkspace ?? process.cwd();
5122
5826
  if (explicitAgentId === undefined) {
5123
5827
  const sourceRoleName = sourceGlobalRoleName
5124
5828
  ?? (roleName === LEADER_ROLE ? LEADER_ROLE : "worker");
@@ -5136,7 +5840,7 @@ function createTaskRole(store, task, roleName, explicitAgentId, now, sourceGloba
5136
5840
  throw dataError(`No Agent is configured for Task role: ${roleName}.`);
5137
5841
  }
5138
5842
  const agent = requireAgent(store, agentId);
5139
- const binding = createRoleAgentBinding({ id: agent.id, adapterId: agent.adapterId });
5843
+ const binding = createRoleAgentBinding(agent);
5140
5844
  return createRole(task.id, roleName, [binding], agent.id, workspace, now);
5141
5845
  }
5142
5846
  function requireAgentProfile(store, id) {
@@ -5146,7 +5850,9 @@ function requireAgentProfile(store, id) {
5146
5850
  return profile;
5147
5851
  }
5148
5852
  function createTaskRoleFromAgentBinding(store, task, roleName, binding, now) {
5149
- const workspace = task.cwd ?? store.getConfig().defaultWorkspace ?? process.cwd();
5853
+ const workspace = task.status === "draft"
5854
+ ? join(`${store.rootDirectory()}.task-runtimes`, "planning", task.id)
5855
+ : task.cwd ?? store.getConfig().defaultWorkspace ?? process.cwd();
5150
5856
  return createRole(task.id, roleName, [binding], binding.agentId, workspace, now);
5151
5857
  }
5152
5858
  function resolvedAgentProfileRuntime(profile, store) {
@@ -5174,7 +5880,7 @@ function resolveTaskRoleAgentBindingAdd(parsed, profile, store) {
5174
5880
  if (explicitAgentId === undefined)
5175
5881
  return undefined;
5176
5882
  const agent = requireAgent(store, explicitAgentId);
5177
- binding = createRoleAgentBinding({ id: agent.id, adapterId: agent.adapterId });
5883
+ binding = createRoleAgentBinding(agent);
5178
5884
  }
5179
5885
  if (hasAgentConfigOptions(parsed)) {
5180
5886
  binding = patchRoleAgentBinding(binding, parsed);
@@ -5185,11 +5891,6 @@ function resolveTaskRoleAgentBindingUpdate(parsed, role, profile, store) {
5185
5891
  const changesAgentConfig = hasAgentConfigOptions(parsed);
5186
5892
  const explicitAgentId = parsed.one("--agent")?.trim();
5187
5893
  const targetAgentId = explicitAgentId || role.activeAgentId;
5188
- if (profile?.runtime.source === "global-worker"
5189
- && explicitAgentId === undefined
5190
- && !changesAgentConfig) {
5191
- return undefined;
5192
- }
5193
5894
  let binding;
5194
5895
  if (profile !== undefined) {
5195
5896
  const profileRuntime = resolvedAgentProfileRuntime(profile, store);
@@ -5208,7 +5909,7 @@ function resolveTaskRoleAgentBindingUpdate(parsed, role, profile, store) {
5208
5909
  return undefined;
5209
5910
  const agent = requireAgent(store, targetAgentId);
5210
5911
  binding = role.agentBindings[targetAgentId]
5211
- ?? createRoleAgentBinding({ id: agent.id, adapterId: agent.adapterId });
5912
+ ?? createRoleAgentBinding(agent);
5212
5913
  }
5213
5914
  if (changesAgentConfig) {
5214
5915
  binding = patchRoleAgentBinding(binding, parsed);
@@ -5245,7 +5946,7 @@ function appendMessage(store, taskId, body, kind, author, now, context = {}) {
5245
5946
  recordTaskEvent(store, taskId, "message.sent", {
5246
5947
  messageId: message.id,
5247
5948
  kind: message.kind,
5248
- ...(message.turnId === undefined ? {} : { turnId: message.turnId })
5949
+ ...(message.runId === undefined ? {} : { runId: message.runId })
5249
5950
  }, now);
5250
5951
  return message;
5251
5952
  }
@@ -5253,43 +5954,29 @@ function recordTaskEvent(store, taskId, type, payload, now) {
5253
5954
  return recordTaskEventRecord(store, taskId, type, payload, now);
5254
5955
  }
5255
5956
  function leaderActionEventPayload(store, taskId, options) {
5256
- const turnId = taskLeaderActionTurnId(store, taskId, options.environment);
5257
- return turnId === undefined ? {} : { leaderTurnId: turnId };
5957
+ const caller = currentManagedRuntime(store, options.environment, taskId, "leader");
5958
+ // A long-lived Session may be handling direct input while another request
5959
+ // awaits admission. The Role's active pointer cannot prove command origin.
5960
+ return caller === undefined ? {} : { leaderNativeSessionId: caller.nativeSessionId };
5258
5961
  }
5259
5962
  function recordTaskEventRecord(store, taskId, type, payload, now) {
5260
5963
  const event = createTaskEvent(store.nextEventId(taskId), taskId, type, payload, now);
5261
5964
  store.saveEvent(taskId, event);
5262
5965
  return event;
5263
5966
  }
5264
- /** Keeps a free-text Turn-fact note bounded so an event payload stays compact. */
5967
+ /** Keeps a free-text AgentRun-fact note bounded so an event payload stays compact. */
5265
5968
  function truncateEventNote(note) {
5266
5969
  const normalized = note.trim();
5267
5970
  return normalized.length <= 280 ? normalized : `${normalized.slice(0, 279)}…`;
5268
5971
  }
5269
- function roleLaunchEventPayload(role, sessions) {
5270
- const effective = sessions?.sessions[sessions.activeAgentId]?.effective;
5972
+ function runLaunchEventPayload(run) {
5271
5973
  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) {
5287
- return {
5288
- turnId: run.id,
5974
+ runId: run.id,
5289
5975
  role: run.roleName,
5290
5976
  purpose: run.purpose,
5291
5977
  mode: run.mode,
5292
5978
  agent: `${run.effective.agentId}/${run.effective.adapterId}`,
5979
+ component: run.effective.component,
5293
5980
  effectiveRevision: String(run.effective.sourceDesiredRevision),
5294
5981
  profileAccess: run.effective.profileAccess,
5295
5982
  effectivePermission: run.effective.permission.strategy,
@@ -5346,18 +6033,18 @@ function requireWorkItem(store, workItemId, options) {
5346
6033
  }
5347
6034
  return item;
5348
6035
  }
5349
- function requireTurn(store, turnId, options) {
5350
- const reference = taskRecordReference(turnId, "turn", "Turn reference", options);
5351
- const run = store.getTurn(reference.taskId, reference.localId);
6036
+ function requireRun(store, runId, options) {
6037
+ const reference = taskRecordReference(runId, "run", "AgentRun reference", options);
6038
+ const run = store.getRun(reference.taskId, reference.localId);
5352
6039
  if (run === null) {
5353
- throw usageError(`Turn not found: ${reference.taskId}/${reference.localId}.`);
6040
+ throw usageError(`AgentRun not found: ${reference.taskId}/${reference.localId}.`);
5354
6041
  }
5355
6042
  return run;
5356
6043
  }
5357
- function activeTurnPointer(store, run) {
6044
+ function activeRunPointer(store, run) {
5358
6045
  return run.executionGroupId !== undefined && run.executionLaneId !== undefined
5359
- ? store.getActiveExecutionLaneTurn(run.taskId, run.executionGroupId, run.executionLaneId)
5360
- : store.getActiveTurn(run.taskId, run.roleName);
6046
+ ? store.getActiveExecutionLaneRun(run.taskId, run.executionGroupId, run.executionLaneId)
6047
+ : store.getActiveRun(run.taskId, run.roleName);
5361
6048
  }
5362
6049
  function requireReviewRound(store, reviewRoundId, options) {
5363
6050
  const reference = taskRecordReference(reviewRoundId, "reviewRound", "ReviewRound reference", options);
@@ -5392,12 +6079,12 @@ export function assertWorkItemDependenciesCompletedForCommand(store, item) {
5392
6079
  throw error;
5393
6080
  }
5394
6081
  }
5395
- function chronologicalTurns(turns) {
5396
- return [...turns].sort((left, right) => (left.createdAt.localeCompare(right.createdAt)
6082
+ function chronologicalRuns(runs) {
6083
+ return [...runs].sort((left, right) => (left.createdAt.localeCompare(right.createdAt)
5397
6084
  || left.id.localeCompare(right.id)));
5398
6085
  }
5399
6086
  function isTerminalWorkItemStatus(status) {
5400
- return ["completed", "failed", "retired"].includes(status);
6087
+ return ["accepted", "retired"].includes(status);
5401
6088
  }
5402
6089
  function assertTaskOpen(task) {
5403
6090
  if (task.status === "completed") {
@@ -5405,7 +6092,7 @@ function assertTaskOpen(task) {
5405
6092
  }
5406
6093
  if (task.status === "archived")
5407
6094
  throw usageError(`Task is archived: ${task.id}.`);
5408
- if (task.status === "retired")
6095
+ if (task.status === "cancelled")
5409
6096
  throw usageError(`Task is retired: ${task.id}.`);
5410
6097
  }
5411
6098
  function assertTaskExecutionEnabled(task, action) {
@@ -5423,7 +6110,7 @@ function inactiveTaskMessage(task, action) {
5423
6110
  if (task.status === "completed") {
5424
6111
  return `Task ${task.id} is completed; reopen it before ${action}.`;
5425
6112
  }
5426
- if (task.status === "retired")
6113
+ if (task.status === "cancelled")
5427
6114
  return `Task ${task.id} is retired; it cannot resume ${action}.`;
5428
6115
  return `Task is archived: ${task.id}.`;
5429
6116
  }
@@ -5444,10 +6131,6 @@ function parseWorkStatus(value) {
5444
6131
  throw usageError(`Invalid work item status: ${value}.`);
5445
6132
  }
5446
6133
  function presentWorkStatus(status) {
5447
- if (status === "pending")
5448
- return "todo";
5449
- if (status === "completed")
5450
- return "done";
5451
6134
  return status;
5452
6135
  }
5453
6136
  function parseWorkCreateArgs(args, usage) {
@@ -5622,7 +6305,10 @@ function taskBriefCommand(args, store, options) {
5622
6305
  ...(hasSummary ? { leaderSummary: parsed.options.get("--leader-summary") } : {})
5623
6306
  }, updatedBy, now);
5624
6307
  tx.saveTaskBrief(task.id, brief);
5625
- recordTaskEvent(tx, task.id, "brief.updated", { updatedBy }, now);
6308
+ recordTaskEvent(tx, task.id, "brief.updated", {
6309
+ updatedBy,
6310
+ previous: JSON.stringify(existing), current: JSON.stringify(brief)
6311
+ }, now);
5626
6312
  enqueueWork(tx, taskMailbox(task.id), "brief-updated", now, [taskRef(task.id)]);
5627
6313
  if (task.status === "active" && updatedBy !== "leader") {
5628
6314
  enqueueWork(tx, leaderMailbox(task.id), "brief-updated", now, [taskRef(task.id)]);
@@ -5633,12 +6319,18 @@ function taskBriefCommand(args, store, options) {
5633
6319
  if (result.task.status === "active") {
5634
6320
  notifyMailbox(options.runtime, leaderMailbox(result.task.id), result.task.id);
5635
6321
  }
5636
- return output(`Updated brief for ${result.task.id}\n`);
6322
+ return output(`Updated brief for ${result.task.id}\n`, { taskId: result.task.id, brief: result.brief });
5637
6323
  }
5638
6324
  throw usageError(command === undefined
5639
6325
  ? "Task brief command is required."
5640
6326
  : `Unknown command: task brief ${command}`);
5641
6327
  }
6328
+ /** Persist only edited fields, not another copy of aggregate execution history.
6329
+ * Null records an absent optional field so clearing it remains observable. */
6330
+ function editedFieldValues(record, fields) {
6331
+ const values = record;
6332
+ return JSON.stringify(Object.fromEntries(fields.map((field) => [field, values[field] ?? null])));
6333
+ }
5642
6334
  function taskDecisionCommand(args, store, options) {
5643
6335
  const [command, ...rest] = args;
5644
6336
  if (command === "record") {
@@ -5889,14 +6581,11 @@ function taskContinuationCommand(args, store) {
5889
6581
  const report = [...continuation.reports].reverse()[0];
5890
6582
  const reportEvent = report === undefined
5891
6583
  ? undefined
5892
- : reportEvents.find((entry) => (entry.continuationId === identity.continuationId
5893
- && entry.continuationGeneration === identity.generation
5894
- && entry.reportId === report.reportId));
6584
+ : reportEvents.find((entry) => (entry.continuationId === identity.continuationId && entry.reportId === report.reportId));
5895
6585
  return Object.freeze({
5896
6586
  continuationId: identity.continuationId,
5897
- generation: identity.generation,
5898
6587
  driver: identity.providerNamespace,
5899
- turnId: continuation.turnId,
6588
+ runId: continuation.runId,
5900
6589
  execution: continuation.execution,
5901
6590
  outcome: continuation.outcome,
5902
6591
  attachment: continuation.attachment,
@@ -5942,12 +6631,9 @@ function continuationReportEvents(events) {
5942
6631
  const observation = runtimeObservationFromTaskEvent(event);
5943
6632
  if (observation !== null && observation.kind === "continuation.reported") {
5944
6633
  const continuationId = observation.fence.continuationId;
5945
- const continuationGeneration = observation.fence.continuationGeneration;
5946
6634
  const reportId = observation.payload?.reportId;
5947
- if (continuationId !== undefined
5948
- && continuationGeneration !== undefined
5949
- && reportId !== undefined) {
5950
- result.push({ event, continuationId, continuationGeneration, reportId });
6635
+ if (continuationId !== undefined && reportId !== undefined) {
6636
+ result.push({ event, continuationId, reportId });
5951
6637
  }
5952
6638
  }
5953
6639
  }
@@ -5961,6 +6647,48 @@ function continuationReportEvents(events) {
5961
6647
  */
5962
6648
  function taskWakeDispatch(args, store, options) {
5963
6649
  const [subcommand] = args;
6650
+ if (subcommand === "resolve") {
6651
+ const usage = "Task wake resolve usage: yui task wake resolve <task> <wake> --reason <quiescence-evidence>.";
6652
+ const parsed = parseTail(args.slice(1), new Set(["--reason"]), usage);
6653
+ exactPositionals(parsed.positionals, 2, usage);
6654
+ const reason = requiredOption(parsed.options, "--reason");
6655
+ const now = clock(options);
6656
+ const result = store.transaction((tx) => {
6657
+ const task = requireTask(tx, parsed.positionals[0]);
6658
+ taskActor(tx, options, task.id);
6659
+ const wakeId = parsed.positionals[1];
6660
+ const wake = tx.getTaskWake(task.id, wakeId);
6661
+ if (wake === null)
6662
+ throw usageError("Wake is unavailable.");
6663
+ const target = leaderMailbox(task.id);
6664
+ const mailbox = tx.getWorkMailbox(target);
6665
+ const claim = mailbox?.processing;
6666
+ if (claim === undefined || claim === null || claim.owner !== `leader-notification:${wake.id}`) {
6667
+ throw usageError("This wake has no unresolved notification claim.");
6668
+ }
6669
+ const previous = tx.listEvents(task.id).filter((event) => event.type === "notification.delivery" && event.payload.attemptId === claim.batchId).at(-1);
6670
+ const sessions = tx.getTaskRoleSessionSet(task.id, "leader");
6671
+ const provider = sessions?.providerBinding;
6672
+ if (previous?.payload.outcome !== "unknown")
6673
+ throw usageError("Only an unknown notification can be resolved.");
6674
+ if (tx.getActiveRun(task.id, "leader") !== null
6675
+ || provider?.authority.owner === "human" || provider?.authority.owner === "unknown"
6676
+ || (provider?.run !== undefined && provider.run !== null
6677
+ && ["submitting", "accepted", "delivery-unknown"].includes(provider.run.status))) {
6678
+ throw usageError("Shared native execution is not proven quiescent; resolve that exact runtime boundary first.");
6679
+ }
6680
+ // Preserve the original unknown outcome and the unconsumed fixed wake.
6681
+ // This releases only the scheduling claim; it never replays, claims
6682
+ // acceptance, or asserts that Message requirements were implemented.
6683
+ recordTaskEvent(tx, task.id, "notification.resolved", {
6684
+ wakeId: wake.id, attemptId: claim.batchId, reason, outcome: "released-without-replay"
6685
+ }, now);
6686
+ tx.saveWorkMailbox(completeProcessing(mailbox, claim.batchId));
6687
+ return { taskId: task.id, wakeId: wake.id, outcome: "released-without-replay" };
6688
+ });
6689
+ notifyMailbox(options.runtime, leaderMailbox(result.taskId), result.taskId);
6690
+ return output(`Released ${result.wakeId} without replay; original acceptance remains unknown.\n`, result);
6691
+ }
5964
6692
  if (subcommand === "list" || subcommand === "show") {
5965
6693
  return taskWakeInspectionCommand(args, store);
5966
6694
  }
@@ -5987,13 +6715,13 @@ function taskWakeInspectionCommand(args, store) {
5987
6715
  { header: "Wake", minWidth: 8, maxWidth: 18 },
5988
6716
  { header: "Status", minWidth: 8, maxWidth: 12 },
5989
6717
  { header: "Reasons", minWidth: 10, maxWidth: 40 },
5990
- { header: "Turn", minWidth: 10, maxWidth: 20 },
6718
+ { header: "AgentRun", minWidth: 10, maxWidth: 20 },
5991
6719
  { header: "Dispatched", minWidth: 10, maxWidth: 28 }
5992
6720
  ], wakes.map((wake) => [
5993
6721
  wake.id,
5994
6722
  wake.status,
5995
6723
  wake.reasons.map(renderWakeReason).join(", "),
5996
- wake.turnId ?? "-",
6724
+ wake.runId ?? "-",
5997
6725
  presentTime(wake.createdAt, timeZone)
5998
6726
  ]), defaultTableWidth())}\n`, { taskId: task.id, wakes });
5999
6727
  }
@@ -6012,18 +6740,30 @@ function taskWakeInspectionCommand(args, store) {
6012
6740
  return ms > fromMs && ms <= toMs;
6013
6741
  };
6014
6742
  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)));
6743
+ const deliveryEvents = allEvents.filter((event) => (event.type === "notification.delivery" || event.type === "notification.resolved")
6744
+ && (event.payload.wakeId === wake.id
6745
+ || event.payload.attemptId?.startsWith(`notification:${task.id}/${wake.id}/`)));
6746
+ const referenced = (type, id) => wake.refs?.some(ref => ref.type === type && ref.id === id
6747
+ && (!("taskId" in ref) || ref.taskId === task.id)) === true;
6748
+ const events = allEvents.filter((e) => inWindow(e.createdAt) || referenced("event", e.id));
6749
+ const messages = store.listMessages(task.id).filter((m) => inWindow(m.createdAt) || referenced("message", m.id));
6750
+ const allRuns = store.listRuns(task.id);
6751
+ const referencedRunIds = new Set(referencedWakeRunIds(allRuns, allEvents, events));
6752
+ for (const run of allRuns)
6753
+ if (referenced("run", run.id))
6754
+ referencedRunIds.add(run.id);
6755
+ for (const message of messages)
6756
+ if (message.resultRef?.type === "agent-run-result")
6757
+ referencedRunIds.add(message.resultRef.runId);
6758
+ const runs = operationalTaskRecords(allRuns, allEvents, "run").filter((run) => (inWindow(run.createdAt) || referencedRunIds.has(run.id)));
6020
6759
  const lines = [
6021
6760
  `Wake: ${wake.id}`,
6022
6761
  `Task: ${task.id}`,
6023
6762
  `Status: ${wake.status}`,
6763
+ `Notification: ${deliveryEvents.at(-1)?.payload.outcome ?? "unobserved"}`,
6024
6764
  `Reasons: ${wake.reasons.map(renderWakeReason).join(", ")}`,
6025
6765
  `Delta window: ${wake.fromCursor} → ${wake.toCursor}`,
6026
- ...(wake.turnId === undefined ? [] : [`Turn: ${wake.turnId}`]),
6766
+ ...(wake.runId === undefined ? [] : [`AgentRun: ${wake.runId}`]),
6027
6767
  `Dispatched: ${presentTime(wake.createdAt, timeZone)}`,
6028
6768
  ...(wake.consumedAt === undefined
6029
6769
  ? []
@@ -6032,19 +6772,20 @@ function taskWakeInspectionCommand(args, store) {
6032
6772
  ...events.map((e) => ` ${e.id} ${e.type} ${presentTime(e.createdAt, timeZone)}`),
6033
6773
  `Messages (${messages.length}):`,
6034
6774
  ...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}`
6775
+ `AgentRuns (${runs.length}):`,
6776
+ ...runs.map((run) => (` ${run.id} [${run.status}/${run.purpose}] ${run.roleName} `
6777
+ + `${presentTime(run.createdAt, timeZone)}`
6778
+ + `${referencedRunIds.has(run.id)
6779
+ ? ` → yui task run show ${task.id}/${run.id}`
6040
6780
  : ""}`))
6041
6781
  ];
6042
6782
  return output(lines.join("\n").concat("\n"), {
6043
6783
  taskId: task.id,
6784
+ deliveryEvents,
6044
6785
  wake,
6045
6786
  events,
6046
6787
  messages,
6047
- turns
6788
+ runs
6048
6789
  });
6049
6790
  }
6050
6791
  throw usageError(command === undefined
@@ -6175,8 +6916,8 @@ function leaderMailbox(taskId) {
6175
6916
  function taskRef(id) {
6176
6917
  return { type: "task", id };
6177
6918
  }
6178
- function turnRef(taskId, id) {
6179
- return { type: "turn", taskId, id };
6919
+ function runRef(taskId, id) {
6920
+ return { type: "run", taskId, id };
6180
6921
  }
6181
6922
  function workItemRef(taskId, id) {
6182
6923
  return { type: "work-item", taskId, id };