@zq-silk/yui 0.13.7 → 0.13.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,7 +9,7 @@ import { operationalTaskRecords } from "../task/taskRecordRetirement.js";
9
9
  import { buildTaskWakeEnvelope } from "../context/wakeNotification.js";
10
10
  import { createTaskWake, fallbackWakeCursor, latestTaskWake } from "../scheduler/taskWake.js";
11
11
  import { answerInputRequest } from "../input/inputRequest.js";
12
- import { activeRoleAgentBinding, updateRoleStatus } from "../role/role.js";
12
+ import { activeRoleAgentBinding } from "../role/role.js";
13
13
  import { effectiveLaunchWithTaskMainWorkspace, effectiveLaunchSnapshotsCompatible, effectiveLaunchSnapshotsCompatibleForTaskSession, resolveEffectiveLaunch, validateEffectiveLaunchSnapshot } from "../executor/effectiveLaunch.js";
14
14
  import { SYSTEM_OPERATOR_ROLE } from "../role/systemRoles.js";
15
15
  import { failAgentRun, markAgentRunDelivered, markAgentRunPushed, reopenRunForProviderRetry, clearProviderRetryOnProgress, agentRunDeliveryReceiptId, withProviderRetry } from "../run/agentRun.js";
@@ -101,7 +101,7 @@ export class FileSchedulerStoreAdapter {
101
101
  outcome = this.observeRuntimePromptAccepted(input, adapterId, now);
102
102
  break;
103
103
  case "turn.completed": {
104
- const completed = {
104
+ const classification = this.classifyRuntimeTurnCompleted({
105
105
  taskId,
106
106
  roleName: input.fence.roleName,
107
107
  agentId: input.fence.agentId,
@@ -110,10 +110,8 @@ export class FileSchedulerStoreAdapter {
110
110
  nativeSessionId: input.fence.nativeSessionId,
111
111
  turnId: input.fence.nativeTurnId,
112
112
  attemptId: input.fence.receiptId,
113
- runId: input.fence.runId,
114
- summary: input.payload.summary ?? "Agent turn completed without a workflow outcome."
115
- };
116
- const classification = this.classifyRuntimeTurnCompleted(completed);
113
+ runId: input.fence.runId
114
+ });
117
115
  if (classification !== "apply")
118
116
  return classification;
119
117
  if (this.runtimeTurnHasActiveNativeSubagents(input)) {
@@ -123,39 +121,24 @@ export class FileSchedulerStoreAdapter {
123
121
  outcome = this.validateCanonicalRunObservation(input, now);
124
122
  break;
125
123
  }
126
- const result = this.observeRuntimeTurnCompleted(completed, now);
127
- outcome = result.disposition === "obsolete" ? "obsolete" : "applied";
124
+ outcome = this.foldProviderTurnBoundary(input, adapterId, "completed", input.payload.summary ?? "Agent turn completed without a workflow outcome.", now);
128
125
  break;
129
126
  }
130
127
  case "turn.failed": {
131
128
  const failureEvidence = input.payload.failure;
132
129
  if (failureEvidence.runTerminal !== true) {
133
- const classification = this.classifyRuntimeTurnCompleted({
134
- taskId,
135
- roleName: input.fence.roleName,
136
- agentId: input.fence.agentId,
137
- adapterId,
138
- launchId: input.fence.launchId,
139
- nativeSessionId: input.fence.nativeSessionId,
140
- turnId: input.fence.nativeTurnId,
141
- runId: input.fence.runId
142
- });
143
- if (classification !== "apply")
144
- return classification;
145
- const boundary = this.observeRuntimeTurnCompleted({
146
- taskId,
147
- roleName: input.fence.roleName,
148
- agentId: input.fence.agentId,
149
- adapterId,
150
- launchId: input.fence.launchId,
151
- nativeSessionId: input.fence.nativeSessionId,
152
- turnId: input.fence.nativeTurnId,
153
- attemptId: input.fence.receiptId,
154
- runId: input.fence.runId,
155
- summary: input.payload.summary ?? `Provider Turn failed: ${failureEvidence.code}.`,
156
- providerStatus: "failed"
157
- }, now);
158
- outcome = boundary.disposition === "obsolete" ? "obsolete" : "applied";
130
+ outcome = this.foldProviderTurnBoundary(input, adapterId, "failed", input.payload.summary ?? `Provider Turn failed: ${failureEvidence.code}.`, now);
131
+ break;
132
+ }
133
+ const active = this.store.getActiveAgentRun(taskId, input.fence.roleName);
134
+ const recordedTurn = this.store.getTaskRoleSessionSet(taskId, input.fence.roleName)?.providerBinding?.turn;
135
+ if (input.fence.runId !== undefined
136
+ && active?.id !== input.fence.runId
137
+ && recordedTurn !== null
138
+ && recordedTurn !== undefined
139
+ && recordedTurn.runId === input.fence.runId
140
+ && recordedTurn.turnId === input.fence.nativeTurnId) {
141
+ outcome = this.foldProviderTurnBoundary(input, adapterId, "failed", input.payload.summary ?? `Provider Turn failed: ${failureEvidence.code}.`, now);
159
142
  break;
160
143
  }
161
144
  const failure = {
@@ -194,7 +177,6 @@ export class FileSchedulerStoreAdapter {
194
177
  case "operation.completed":
195
178
  case "operation.failed":
196
179
  case "turn.waiting":
197
- case "turn.cancelled":
198
180
  case "activity.observed":
199
181
  case "observer.health":
200
182
  case "native-work.snapshot":
@@ -204,6 +186,10 @@ export class FileSchedulerStoreAdapter {
204
186
  case "input.delivery-unknown":
205
187
  outcome = this.validateCanonicalRunObservation(input, now);
206
188
  break;
189
+ case "turn.cancelled": {
190
+ outcome = this.foldProviderTurnBoundary(input, adapterId, "cancelled", input.payload.summary ?? "Provider Turn was cancelled.", now);
191
+ break;
192
+ }
207
193
  case "conversation.observed":
208
194
  case "activation.started":
209
195
  case "activation.ended":
@@ -292,6 +278,26 @@ export class FileSchedulerStoreAdapter {
292
278
  const projection = projectRuntimeTaskEvents(input.fence, run.createdAt, this.store.listEvents(taskId));
293
279
  return Object.values(projection.operations).some(({ kind }) => kind === "subagent");
294
280
  }
281
+ foldProviderTurnBoundary(input, adapterId, providerStatus, summary, now) {
282
+ const completed = {
283
+ taskId: input.fence.taskId,
284
+ roleName: input.fence.roleName,
285
+ agentId: input.fence.agentId,
286
+ adapterId,
287
+ launchId: input.fence.launchId,
288
+ nativeSessionId: input.fence.nativeSessionId,
289
+ turnId: input.fence.nativeTurnId,
290
+ attemptId: input.fence.receiptId,
291
+ runId: input.fence.runId,
292
+ summary,
293
+ providerStatus
294
+ };
295
+ const classification = this.classifyRuntimeTurnCompleted(completed);
296
+ if (classification !== "apply")
297
+ return classification;
298
+ const result = this.observeRuntimeTurnCompleted(completed, now);
299
+ return result.disposition === "obsolete" ? "obsolete" : "applied";
300
+ }
295
301
  validateCanonicalSessionObservation(input, now) {
296
302
  return this.store.transaction((store) => {
297
303
  const run = store.getAgentRun(input.fence.taskId, input.fence.runId);
@@ -381,7 +387,6 @@ export class FileSchedulerStoreAdapter {
381
387
  && active.status === "active"
382
388
  && active.deliveredAt !== undefined
383
389
  && role !== null) {
384
- store.saveRole(input.fence.taskId, updateRoleStatus(role, "detached", now));
385
390
  if (role.name === "leader") {
386
391
  const message = [
387
392
  `Leader native Session became unavailable while Run ${active.id} remains active.`,
@@ -827,10 +832,6 @@ export class FileSchedulerStoreAdapter {
827
832
  return existing && !recovered ? "already-recorded" : "recorded";
828
833
  });
829
834
  }
830
- hasInFlightTurn(taskId, roleName) {
831
- const sessions = this.store.getTaskRoleSessionSet(taskId, roleName);
832
- return sessions !== null && sessions.inFlight !== null;
833
- }
834
835
  beginAgentHostProviderTurn(input) {
835
836
  this.store.transaction((store) => {
836
837
  const sessions = store.getTaskRoleSessionSet(input.taskId, input.roleName);
@@ -844,7 +845,6 @@ export class FileSchedulerStoreAdapter {
844
845
  if (sessions === null || sessions === undefined
845
846
  || binding === null || binding === undefined
846
847
  || sessions.inFlight?.runId !== input.runId
847
- || binding.runId !== input.runId
848
848
  || session?.launchId !== input.launchId
849
849
  || session.nativeSessionId !== input.nativeSessionId
850
850
  || currentProviderConversation(binding).conversationId !== input.nativeSessionId
@@ -854,7 +854,8 @@ export class FileSchedulerStoreAdapter {
854
854
  || !processingOwnsExactInitialDelivery(mailbox, input.taskId, input.runId, input.attemptId)) {
855
855
  throw new AgentHostProviderTurnFenceError("Agent Host Provider Turn carries a stale durable writer fence. Release and reacquire Provider authority before retrying input.");
856
856
  }
857
- const exactReplay = binding.turn?.attemptId === input.attemptId
857
+ const exactReplay = binding.turn?.runId === input.runId
858
+ && binding.turn.attemptId === input.attemptId
858
859
  && binding.turn.authorityEpoch === input.authorityEpoch
859
860
  && binding.turn.status === "submitting";
860
861
  if (!exactReplay && binding.turn !== null
@@ -863,6 +864,7 @@ export class FileSchedulerStoreAdapter {
863
864
  throw new AgentHostProviderTurnFenceError("Provider Conversation already has an unsettled Turn.");
864
865
  }
865
866
  store.saveTaskRoleSessionSet(updateTaskRoleProviderRuntime(sessions, beginProviderTurn(binding, {
867
+ runId: input.runId,
866
868
  attemptId: input.attemptId,
867
869
  authorityEpoch: input.authorityEpoch,
868
870
  submittedAt: input.now.toISOString()
@@ -875,7 +877,7 @@ export class FileSchedulerStoreAdapter {
875
877
  const binding = sessions?.providerBinding;
876
878
  if (sessions === null || sessions === undefined
877
879
  || binding === null || binding === undefined
878
- || binding.runId !== input.runId
880
+ || binding.turn?.runId !== input.runId
879
881
  || binding.turn?.attemptId !== input.attemptId) {
880
882
  throw new Error("Agent Host Provider Turn submission is no longer current.");
881
883
  }
@@ -893,7 +895,6 @@ export class FileSchedulerStoreAdapter {
893
895
  const session = sessions?.sessions[input.agentId];
894
896
  const binding = sessions?.providerBinding;
895
897
  if (binding === null || binding === undefined
896
- || binding.runId !== input.runId
897
898
  || session?.launchId !== input.launchId
898
899
  || session.nativeSessionId !== input.nativeSessionId
899
900
  || currentProviderConversation(binding).conversationId !== input.nativeSessionId)
@@ -1022,7 +1023,6 @@ export class FileSchedulerStoreAdapter {
1022
1023
  const binding = sessions?.providerBinding;
1023
1024
  if (sessions === null || sessions === undefined
1024
1025
  || binding === null || binding === undefined
1025
- || binding.runId !== input.executionRef.id
1026
1026
  || binding.authority.owner !== "controller") {
1027
1027
  throw new Error("Controller does not own the Provider writer authority.");
1028
1028
  }
@@ -1034,6 +1034,7 @@ export class FileSchedulerStoreAdapter {
1034
1034
  throw new Error("Provider input claim carries a stale Conversation/Activation fence.");
1035
1035
  }
1036
1036
  const next = beginProviderTurn(binding, {
1037
+ runId: input.executionRef.id,
1037
1038
  attemptId: input.attemptId,
1038
1039
  authorityEpoch: binding.authority.epoch,
1039
1040
  submittedAt: input.now.toISOString()
@@ -1282,7 +1283,6 @@ export class FileSchedulerStoreAdapter {
1282
1283
  store.saveAgentRun(input.run);
1283
1284
  store.saveActiveAgentRun(input.run);
1284
1285
  store.saveWorkMailbox(claimed);
1285
- store.saveRole(input.task.id, updateRoleStatus(role, "running", input.now));
1286
1286
  bindTaskRoleRunInFlight(store, role, input.run, input.now);
1287
1287
  store.saveEvent(input.task.id, createTaskEvent(store.nextEventId(input.task.id), input.task.id, "run.dispatched", runLaunchEventPayload(input.run), input.now));
1288
1288
  if (input.wakeId !== undefined && input.wakeFromCursor !== undefined) {
@@ -1368,9 +1368,6 @@ export class FileSchedulerStoreAdapter {
1368
1368
  markTaskRoleRunPushedInFlight(store, role, deliveryRun, input.now);
1369
1369
  }
1370
1370
  }
1371
- if (role.status !== "running") {
1372
- store.saveRole(input.task.id, updateRoleStatus(role, "running", input.now));
1373
- }
1374
1371
  if (input.session !== null && input.session.nativeSessionId !== undefined) {
1375
1372
  const existing = store.getRoleSession(input.task.id, input.role.name);
1376
1373
  const reservation = matchingPreparedRuntimeReservation(store, input);
@@ -1494,10 +1491,6 @@ export class FileSchedulerStoreAdapter {
1494
1491
  routeRoleEvent(store, deliveryFailureEvent, input.roleName, terminal.purpose === "review" ? "review-failed" : "role-run-failed", input.now);
1495
1492
  }
1496
1493
  else {
1497
- const failedRole = store.getRole(input.taskId, input.roleName);
1498
- if (failedRole !== null) {
1499
- store.saveRole(input.taskId, updateRoleStatus(failedRole, "failed", input.now));
1500
- }
1501
1494
  store.saveLeaderFailure(recordLeaderFailure(input.taskId, session?.nativeSessionId ?? "(unregistered)", summary, input.now, store.getLeaderFailure(input.taskId)));
1502
1495
  routeRoleEvent(store, deliveryFailureEvent, input.roleName, "leader-run-failed", input.now);
1503
1496
  }
@@ -1534,7 +1527,6 @@ export class FileSchedulerStoreAdapter {
1534
1527
  // Consume this failed delivery attempt. Releasing it would immediately
1535
1528
  // redispatch the same wake and manufacture another Leader Session.
1536
1529
  store.saveWorkMailbox(completeProcessing(mailbox, mailbox.processing.batchId));
1537
- store.saveRole(input.task.id, updateRoleStatus(role, "failed", input.now));
1538
1530
  breakTaskSessionIfPresent(store, input.task.id, role.name, active.effective.agentId, input.now);
1539
1531
  const failure = recordLeaderFailure(input.task.id, input.failure.nativeSessionId, input.failure.message, input.now, store.getLeaderFailure(input.task.id));
1540
1532
  store.saveLeaderFailure(failure);
@@ -1599,7 +1591,6 @@ export class FileSchedulerStoreAdapter {
1599
1591
  }, input.now);
1600
1592
  if (terminal.disposition !== "applied")
1601
1593
  return "state-changed";
1602
- store.saveRole(task.id, updateRoleStatus(role, "exited", input.now));
1603
1594
  stopTaskSessionIfPresent(store, task.id, role.name, currentRun.effective.agentId, input.now);
1604
1595
  queueLeaderWakeup(store, task.id, wakeReason("review-failed"), input.now);
1605
1596
  return "failed";
@@ -1639,7 +1630,6 @@ export class FileSchedulerStoreAdapter {
1639
1630
  }
1640
1631
  }
1641
1632
  const leaderFailed = role.name === "leader";
1642
- store.saveRole(task.id, updateRoleStatus(role, leaderFailed ? "failed" : "exited", input.now));
1643
1633
  stopTaskSessionIfPresent(store, task.id, role.name, currentRun.effective.agentId, input.now);
1644
1634
  if (leaderFailed) {
1645
1635
  const failure = recordLeaderFailure(task.id, input.session?.nativeSessionId ?? "(unregistered)", input.summary, input.now, store.getLeaderFailure(task.id));
@@ -1860,6 +1850,19 @@ export class FileSchedulerStoreAdapter {
1860
1850
  catch {
1861
1851
  return "obsolete";
1862
1852
  }
1853
+ const providerTurn = sessions?.providerBinding?.turn;
1854
+ const canonicalTurnId = sessions === null || sessions === undefined
1855
+ ? input.turnId
1856
+ : canonicalStructuredProviderTurnId(sessions, input.turnId, input.attemptId);
1857
+ const recordedProviderTurn = input.runId !== undefined
1858
+ && providerTurn?.runId === input.runId
1859
+ && providerTurn.turnId === canonicalTurnId;
1860
+ if (recordedProviderTurn) {
1861
+ const run = this.store.getAgentRun(input.taskId, input.runId);
1862
+ return run === null
1863
+ ? "obsolete"
1864
+ : run.pushedAt === undefined ? "deferred" : "apply";
1865
+ }
1863
1866
  // A normal explicit CLI yield may precede its native Hook; that Hook still
1864
1867
  // owns the turn fact. A forced cleanup boundary or stopped process instead
1865
1868
  // makes the old generation obsolete.
@@ -1893,6 +1896,13 @@ export class FileSchedulerStoreAdapter {
1893
1896
  let sessions = store.getTaskRoleSessionSet(input.taskId, input.roleName)
1894
1897
  ?? createRoleSessionSet({ scope: "task", taskId: input.taskId, roleName: input.roleName }, input.agentId, now);
1895
1898
  const canonicalTurnId = canonicalStructuredProviderTurnId(sessions, input.turnId, input.attemptId);
1899
+ const providerTurn = sessions.providerBinding?.turn;
1900
+ const recordedProviderTurn = input.runId !== undefined
1901
+ && providerTurn?.runId === input.runId
1902
+ && providerTurn.turnId === canonicalTurnId;
1903
+ const observedRun = input.runId === undefined
1904
+ ? null
1905
+ : store.getAgentRun(input.taskId, input.runId);
1896
1906
  const existing = sessions.sessions[input.agentId];
1897
1907
  const owner = {
1898
1908
  scope: "task",
@@ -1912,7 +1922,8 @@ export class FileSchedulerStoreAdapter {
1912
1922
  && hasRecentTurnId(effectiveExisting.recentCompletedTurnIds, canonicalTurnId)) {
1913
1923
  return { session: effectiveExisting, duplicate: true };
1914
1924
  }
1915
- if (sessions.inFlight === null
1925
+ if (!recordedProviderTurn
1926
+ && sessions.inFlight === null
1916
1927
  && existing !== undefined
1917
1928
  && (hasRuntimeCleanupObligation(store.getWorkMailbox(runtimeLifecycleTarget(owner)))
1918
1929
  || isObsoleteTerminalRuntimeRun(store, input))) {
@@ -1920,7 +1931,8 @@ export class FileSchedulerStoreAdapter {
1920
1931
  }
1921
1932
  // Classification is only an optimization. Revalidate the Run inside the
1922
1933
  // authoritative transaction before a Hook may claim a native identity.
1923
- if (sessions.inFlight !== null
1934
+ if (!recordedProviderTurn
1935
+ && sessions.inFlight !== null
1924
1936
  && (input.runId === undefined
1925
1937
  || sessions.inFlight.runId !== input.runId)) {
1926
1938
  if (effectiveExisting === undefined) {
@@ -1928,7 +1940,9 @@ export class FileSchedulerStoreAdapter {
1928
1940
  }
1929
1941
  return { session: effectiveExisting, duplicate: false };
1930
1942
  }
1931
- if (sessions.inFlight !== null && sessions.inFlight.pushedAt === undefined) {
1943
+ if (recordedProviderTurn
1944
+ ? observedRun?.pushedAt === undefined
1945
+ : sessions.inFlight !== null && sessions.inFlight.pushedAt === undefined) {
1932
1946
  throw new Error("Runtime turn completion requires an independently committed transport receipt.");
1933
1947
  }
1934
1948
  const idleStatus = effectiveExisting?.status === "stopped"
@@ -2016,6 +2030,11 @@ export class FileSchedulerStoreAdapter {
2016
2030
  recordObsoleteRuntimeEvent(store, input, "run-missing", now);
2017
2031
  return { disposition: "obsolete", runId: input.runId };
2018
2032
  }
2033
+ const sessions = store.getTaskRoleSessionSet(input.taskId, input.roleName);
2034
+ if (sessions?.providerBinding?.turn?.runId === input.runId
2035
+ && sessions.providerBinding.turn.turnId === input.nativeTurnId) {
2036
+ store.saveTaskRoleSessionSet(settleStructuredProviderTurn(sessions, input.nativeTurnId, "failed", now));
2037
+ }
2019
2038
  const summary = runtimeTurnFailureSummary(input);
2020
2039
  // Issue 04: a classified transient Provider failure keeps the exact Run
2021
2040
  // and Native Session and is retried in place instead of terminalizing.
@@ -2045,10 +2064,6 @@ export class FileSchedulerStoreAdapter {
2045
2064
  roleName: input.roleName
2046
2065
  }), RUNTIME_CLEANUP_REQUIRED_REASON, now, [{ type: "task", id: input.taskId }]);
2047
2066
  const terminal = result.run;
2048
- const role = store.getRole(input.taskId, input.roleName);
2049
- if (role !== null) {
2050
- store.saveRole(input.taskId, updateRoleStatus(role, input.roleName === "leader" ? "failed" : "idle", now));
2051
- }
2052
2067
  const failureEvent = createTaskEvent(store.nextEventId(input.taskId), input.taskId, "runtime.turn-failed", {
2053
2068
  eventId: input.eventId,
2054
2069
  runId: input.runId,
@@ -2555,7 +2570,6 @@ export class FileSchedulerStoreAdapter {
2555
2570
  const sessions = store.getTaskRoleSessionSet(taskId, input.fence.roleName);
2556
2571
  const run = store.getAgentRun(taskId, input.fence.runId);
2557
2572
  if (sessions === null || sessions.providerBinding === null || run === null
2558
- || sessions.providerBinding.runId !== input.fence.runId
2559
2573
  || input.fence.conversationId === undefined) {
2560
2574
  recordCanonicalObservationObsolete(store, input, "provider-binding-missing", now);
2561
2575
  return "obsolete";
@@ -2747,10 +2761,6 @@ export class FileSchedulerStoreAdapter {
2747
2761
  return "obsolete";
2748
2762
  }
2749
2763
  store.saveTaskRoleSessionSet(recordStructuredProviderAcceptance(updateRoleAgentSessionStatus(sessions, input.fence.agentId, "running", now), input, now));
2750
- const role = store.getRole(input.fence.taskId, input.fence.roleName);
2751
- if (role !== null && role.status !== "running") {
2752
- store.saveRole(input.fence.taskId, updateRoleStatus(role, "running", now));
2753
- }
2754
2764
  store.saveEvent(input.fence.taskId, createTaskEvent(store.nextEventId(input.fence.taskId), input.fence.taskId, "run.input-delivered", {
2755
2765
  attemptId: inputDelivery.attemptId,
2756
2766
  runId: active.id,
@@ -3339,10 +3349,6 @@ function finalizeProviderRetryDeadline(store, run, summary, reason, now) {
3339
3349
  elapsedMs: String(Math.max(0, now.getTime() - Date.parse(retry.firstFailureAt)))
3340
3350
  }, now);
3341
3351
  store.saveEvent(run.taskId, exhaustionEvent);
3342
- const role = store.getRole(run.taskId, run.roleName);
3343
- if (role !== null) {
3344
- store.saveRole(run.taskId, updateRoleStatus(role, run.roleName === "leader" ? "failed" : "idle", now));
3345
- }
3346
3352
  if (run.roleName === "leader") {
3347
3353
  store.saveLeaderFailure(recordLeaderFailure(run.taskId, retry.nativeSessionId ?? "(unproven)", summary, now, store.getLeaderFailure(run.taskId)));
3348
3354
  }
@@ -3527,8 +3533,7 @@ function mapRole(store, role) {
3527
3533
  ...(binding.config.effort === undefined ? {} : { effort: binding.config.effort }),
3528
3534
  effective,
3529
3535
  workspace: role.workspace,
3530
- ...(workspace === undefined ? {} : { managedWorkspace: workspace }),
3531
- status: role.status
3536
+ ...(workspace === undefined ? {} : { managedWorkspace: workspace })
3532
3537
  };
3533
3538
  }
3534
3539
  function taskSessionEffective(store, taskId, roleName, agentId, existing) {
@@ -3822,7 +3827,9 @@ function recordStructuredProviderAcceptance(sessions, input, now) {
3822
3827
  const turnId = input.fence.nativeTurnId;
3823
3828
  if (current === null || attemptId === undefined || turnId === undefined)
3824
3829
  return sessions;
3825
- if (current.turn?.attemptId === attemptId
3830
+ if (current.turn === null || current.turn.runId !== input.fence.runId)
3831
+ return sessions;
3832
+ if (current.turn.attemptId === attemptId
3826
3833
  && ["accepted", "running", "completed", "failed", "cancelled"].includes(current.turn.status))
3827
3834
  return sessions;
3828
3835
  const binding = acceptProviderTurn(current, {
@@ -3855,7 +3862,6 @@ function bindOrSupersedeProviderRuntime(sessions, input, now, replacementBasis)
3855
3862
  return bindTaskRoleProviderRuntime(sessions, createProviderRuntimeBinding({
3856
3863
  providerNamespace: input.fence.driverId,
3857
3864
  accountScope: input.fence.agentId,
3858
- runId: sessions.inFlight.runId,
3859
3865
  conversationId,
3860
3866
  activationId,
3861
3867
  startedAt: sessions.inFlight.preparedAt
@@ -3893,7 +3899,7 @@ function rejectSubmittingProviderTurn(store, target, delivery, now, reason) {
3893
3899
  const binding = sessions?.providerBinding;
3894
3900
  if (sessions === null || sessions === undefined
3895
3901
  || binding === null || binding === undefined
3896
- || binding.runId !== delivery.executionRef.id
3902
+ || binding.turn?.runId !== delivery.executionRef.id
3897
3903
  || binding.turn?.attemptId !== delivery.attemptId
3898
3904
  || (binding.turn.status !== "submitting"
3899
3905
  && binding.turn.status !== "delivery-unknown"))
@@ -3931,7 +3937,7 @@ function markSubmittingProviderTurnUnknown(store, target, delivery, now, reason)
3931
3937
  const binding = sessions?.providerBinding;
3932
3938
  if (sessions === null || sessions === undefined
3933
3939
  || binding === null || binding === undefined
3934
- || binding.runId !== delivery.executionRef.id
3940
+ || binding.turn?.runId !== delivery.executionRef.id
3935
3941
  || binding.turn?.attemptId !== delivery.attemptId
3936
3942
  || binding.turn.status !== "submitting")
3937
3943
  return;
@@ -1,7 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { hasRecentTurnId, rememberRecentTurnId, validatePendingTurnCompletion, validateRecentTurnIds } from "./turnCompletion.js";
3
3
  import { effectiveLaunchSnapshotsCompatible, effectiveLaunchSnapshotsCompatibleForTaskSession, validateEffectiveLaunchSnapshot } from "./effectiveLaunch.js";
4
- import { currentProviderActivation, endProviderActivation, rebindProviderRuntimeRun, validateProviderRuntimeBinding } from "../runtime/providerRuntimeIdentity.js";
4
+ import { currentProviderActivation, endProviderActivation, validateProviderRuntimeBinding } from "../runtime/providerRuntimeIdentity.js";
5
5
  import { builtinDriverIdForAdapter } from "../runtime/builtinAgentDrivers.js";
6
6
  export function createRoleSessionSet(owner, activeAgentId, now) {
7
7
  const base = {
@@ -14,7 +14,7 @@ export function createRoleSessionSet(owner, activeAgentId, now) {
14
14
  ? { ...base, schemaVersion: 3 }
15
15
  : {
16
16
  ...base,
17
- schemaVersion: 6,
17
+ schemaVersion: 7,
18
18
  inFlight: null,
19
19
  providerBinding: null
20
20
  };
@@ -298,15 +298,14 @@ export function bindTaskRoleRun(set, fence, preparedAt, mode) {
298
298
  throw new Error("Task Role session set already has an in-flight Run.");
299
299
  }
300
300
  const timestamp = requireDate(preparedAt, "Task Role Run preparedAt");
301
- // A fresh Session follows physical cleanup and does not inherit execution
302
- // fences from the disposable old Conversation. Runtime events retain its
303
- // audit evidence; the new Provider binds a fresh control identity when it is
304
- // observed. Resume keeps the existing exact Conversation fence.
301
+ // A fresh Session follows physical cleanup and does not inherit runtime
302
+ // evidence from the disposable old Conversation. A resumed Session keeps
303
+ // that evidence unchanged here: claiming a durable Run is a workflow
304
+ // mutation, not a Provider operation. AgentHost binds the Conversation to
305
+ // this Run only when it can actually submit the next serialized Turn.
305
306
  const providerBinding = mode === "new"
306
307
  ? null
307
- : set.providerBinding !== null
308
- ? rebindProviderRuntimeRun(set.providerBinding, normalized.runId)
309
- : null;
308
+ : set.providerBinding;
310
309
  const updated = {
311
310
  ...set,
312
311
  inFlight: { ...normalized, preparedAt: timestamp },
@@ -340,9 +339,6 @@ export function prepareTaskRoleRunRedispatch(set, fence, preparedAt) {
340
339
  export function bindTaskRoleProviderRuntime(set, binding, updatedAt) {
341
340
  validateRoleSessionSet(set);
342
341
  const normalized = validateProviderRuntimeBinding(binding);
343
- if (set.inFlight === null || set.inFlight.runId !== normalized.runId) {
344
- throw new Error("Provider Runtime Binding does not match the in-flight Run.");
345
- }
346
342
  if (set.providerBinding !== null) {
347
343
  if (JSON.stringify(set.providerBinding) === JSON.stringify(normalized))
348
344
  return set;
@@ -358,7 +354,6 @@ export function updateTaskRoleProviderRuntime(set, binding, updatedAt) {
358
354
  validateRoleSessionSet(set);
359
355
  const normalized = validateProviderRuntimeBinding(binding);
360
356
  if (set.providerBinding === null
361
- || normalized.runId !== set.providerBinding.runId
362
357
  || normalized.providerNamespace !== set.providerBinding.providerNamespace
363
358
  || normalized.accountScope !== set.providerBinding.accountScope) {
364
359
  throw new Error("Provider Runtime Binding identity cannot change in place.");
@@ -525,21 +520,15 @@ export function clearTaskRoleRun(set, fence, clearedAt) {
525
520
  return validateRoleSessionSet(updated);
526
521
  }
527
522
  /**
528
- * Applies an authoritative application-level Run terminal fact to the native
529
- * session fence. A later native Hook is only advisory and must not be required
530
- * to make the next Run dispatchable.
523
+ * Applies an authoritative application-level Run terminal fact to its exact
524
+ * delivery receipt. Provider lifecycle remains independent: only the matching
525
+ * native Turn boundary may move the Session from running to ready. AgentHost
526
+ * serializes a retained next delivery while that Turn is still active.
531
527
  */
532
528
  export function terminalizeTaskRoleRunSession(set, fence, terminalAt) {
533
529
  validateRoleSessionSet(set);
534
530
  const inFlight = set.inFlight;
535
- let updated = inFlight === null
536
- ? set
537
- : clearTaskRoleRun(set, fence, terminalAt);
538
- const session = updated.sessions[updated.activeAgentId];
539
- if (session?.status === "running") {
540
- updated = updateRoleAgentSessionStatus(updated, updated.activeAgentId, "ready", terminalAt);
541
- }
542
- return updated;
531
+ return inFlight === null ? set : clearTaskRoleRun(set, fence, terminalAt);
543
532
  }
544
533
  export function settleTaskRoleCompletion(set, expected, settledAt) {
545
534
  validateRoleSessionSet(set);
@@ -597,7 +586,7 @@ export function validateRoleSessionSet(set) {
597
586
  }
598
587
  }
599
588
  else {
600
- if (set.schemaVersion !== 6) {
589
+ if (set.schemaVersion !== 7) {
601
590
  throw new Error("Task Role session set schema version is invalid.");
602
591
  }
603
592
  if (!Object.hasOwn(set, "inFlight")
@@ -637,9 +626,6 @@ export function validateRoleSessionSet(set) {
637
626
  throw new Error("Task Role in-flight Run Agent must be active.");
638
627
  }
639
628
  if (providerBinding !== null) {
640
- if (inFlight !== null && providerBinding.runId !== inFlight.runId) {
641
- throw new Error("Provider Runtime Binding must match the in-flight Run.");
642
- }
643
629
  const session = taskSet.sessions[inFlight?.agentId ?? set.activeAgentId];
644
630
  if (session === undefined) {
645
631
  throw new Error("Provider Runtime Binding has no active Role Agent session.");
@@ -608,8 +608,7 @@ export class FileRoleLaunchPlanner {
608
608
  role: {
609
609
  name: role.name,
610
610
  workspace: effectiveWorkspace,
611
- ...(agentWorkspace === effectiveWorkspace ? {} : { cwd: agentWorkspace }),
612
- ...(owner.scope === "task" ? { status: role.status } : {})
611
+ ...(agentWorkspace === effectiveWorkspace ? {} : { cwd: agentWorkspace })
613
612
  },
614
613
  launch: ordinaryConversationLaunch,
615
614
  session,
@@ -659,7 +658,7 @@ export class FileRoleLaunchPlanner {
659
658
  }
660
659
  #providerOwnedTurnForLaunch(taskId, roleName, runId) {
661
660
  const binding = this.store.getTaskRoleSessionSet(taskId, roleName)?.providerBinding;
662
- if (binding === null || binding === undefined || binding.runId !== runId)
661
+ if (binding === null || binding === undefined || binding.turn?.runId !== runId)
663
662
  return undefined;
664
663
  const turn = binding.turn;
665
664
  if (turn === null || !["accepted", "running"].includes(turn.status))
@@ -2,7 +2,6 @@ import { isDeepStrictEqual } from "node:util";
2
2
  import { completeProcessing } from "../coordination/workMailbox.js";
3
3
  import { settleExactWorkExecution } from "../coordination/workMailboxQueue.js";
4
4
  import { terminalizeTaskRoleRunSession } from "../executor/agentExecutor.js";
5
- import { updateRoleStatus } from "../role/role.js";
6
5
  import { finishReviewRound, updateReviewExecutionGroup } from "../review/reviewRound.js";
7
6
  import { reconcileReviewFindingsAfterReview } from "../review/reviewFindingLedger.js";
8
7
  import { agentRunDeliveryReceiptId, failAgentRun, withYieldReceipt, yieldAgentRun } from "../run/agentRun.js";
@@ -265,7 +264,7 @@ export function retireExactActiveAgentRun(store, input, now) {
265
264
  const session = sessions?.sessions[input.agentId];
266
265
  const providerBinding = sessions?.providerBinding;
267
266
  const providerTurn = providerBinding?.turn;
268
- const providerSettled = providerBinding?.runId === current.id
267
+ const providerSettled = providerTurn?.runId === current.id
269
268
  && (providerTurn?.status === "completed"
270
269
  || providerTurn?.status === "failed"
271
270
  || providerTurn?.status === "cancelled"
@@ -450,7 +449,6 @@ export function terminalizeExactTaskRun(store, input, now) {
450
449
  else {
451
450
  store.clearActiveAgentRun(input.taskId, input.roleName);
452
451
  }
453
- store.saveRole(input.taskId, updateRoleStatus(role, "idle", now));
454
452
  if (sessions !== null) {
455
453
  store.saveTaskRoleSessionSet(terminalizeTaskRoleRunSession(sessions, {
456
454
  agentId: input.agentId,
@@ -23,7 +23,6 @@ export function renderRoleDetails(title, role, input) {
23
23
  const overview = [
24
24
  ` Kind ${input.kind}`,
25
25
  ` Active Agent ${role.activeAgentId}`,
26
- ...("status" in role ? [` Status ${role.status}`] : []),
27
26
  ` Workspace ${role.workspace}`,
28
27
  ` Desired launch r${role.launchRevision}; Profile intent=${role.defaultAccess}`,
29
28
  ` Effective launch ${effective === undefined
@@ -2764,7 +2764,6 @@ function retireWorkspaceBoundSession(store, taskId, roleName, now) {
2764
2764
  }
2765
2765
  function canCorrectActiveWorkItemRoleWorkspaceHint(store, taskId, role, item, workspace) {
2766
2766
  if (role.taskId !== taskId
2767
- || role.status !== "running"
2768
2767
  || item.taskId !== taskId
2769
2768
  || item.assignee !== role.name
2770
2769
  || ["completed", "failed", "retired"].includes(item.status)