@zq-silk/yui 0.6.16 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/dist/cli/commandCatalog.js +3 -7
  2. package/dist/cli.js +12 -33
  3. package/dist/commands/executionAuditCommands.js +19 -0
  4. package/dist/commands/globalRoleCommands.js +70 -0
  5. package/dist/commands/taskActor.js +3 -2
  6. package/dist/commands/taskCommands.js +160 -41
  7. package/dist/commands/taskContextCommand.js +1 -1
  8. package/dist/commands/taskInputCommands.js +3 -2
  9. package/dist/commands/taskRoleRuntimeStatus.js +3 -3
  10. package/dist/context/contextSnapshot.js +228 -0
  11. package/dist/context/roleSessionContext.js +3 -1
  12. package/dist/context/runContextContract.js +162 -0
  13. package/dist/context/runContextPack.js +322 -0
  14. package/dist/context/sessionBootstrapManifest.js +81 -0
  15. package/dist/context/sessionProtocolIdentity.js +23 -0
  16. package/dist/controller/agentRuntimeObserver.js +6 -1
  17. package/dist/controller/controller.js +4 -3
  18. package/dist/controller/fileSchedulerStoreAdapter.js +446 -145
  19. package/dist/controller/jobControl.js +2 -1
  20. package/dist/controller/runtime.js +83 -0
  21. package/dist/controller/runtimeHookRunFence.js +6 -2
  22. package/dist/controller/sessionOwnerReconciliation.js +5 -0
  23. package/dist/executor/agentAdapter.js +7 -2
  24. package/dist/executor/agentExecutor.js +23 -0
  25. package/dist/executor/effectiveLaunch.js +24 -0
  26. package/dist/executor/executorRegistry.js +7 -1
  27. package/dist/executor/fileRoleLaunchPlanner.js +73 -27
  28. package/dist/lifecycle/exactRunTerminalization.js +2 -3
  29. package/dist/lifecycle/providerErrorClass.js +8 -3
  30. package/dist/observability/executionAudit.js +87 -2
  31. package/dist/repository/taskWorkspacePreparer.js +2 -2
  32. package/dist/run/agentRun.js +101 -16
  33. package/dist/run/providerRetry.js +167 -56
  34. package/dist/run/providerRetryConfig.js +5 -1
  35. package/dist/run/runControlRequest.js +50 -0
  36. package/dist/runtime/agentDriver.js +47 -0
  37. package/dist/runtime/agentHost.js +327 -0
  38. package/dist/runtime/builtinAgentDrivers.js +23 -1
  39. package/dist/runtime/builtinTranscriptObserver.js +4 -0
  40. package/dist/runtime/builtinTranscriptUsage.js +2 -0
  41. package/dist/runtime/exactControlPlane.js +2 -2
  42. package/dist/runtime/globalProcessExitStore.js +38 -0
  43. package/dist/runtime/launchBroker.js +95 -0
  44. package/dist/runtime/processExitObservation.js +60 -0
  45. package/dist/runtime/runtimeBinding.js +6 -0
  46. package/dist/runtime/runtimeObservation.js +27 -6
  47. package/dist/runtime/runtimeProjection.js +6 -3
  48. package/dist/runtime/runtimeStopReceipt.js +42 -0
  49. package/dist/runtime/sessionTerminationGuard.js +13 -0
  50. package/dist/runtime/tmuxAdapters.js +203 -220
  51. package/dist/scheduler/activeRoleRunDelivery.js +24 -3
  52. package/dist/scheduler/leaderWakeupProcessor.js +18 -60
  53. package/dist/scheduler/roleRunLiveness.js +61 -27
  54. package/dist/storage/migration/productionRegistry.js +264 -0
  55. package/dist/storage/sqliteSchema.js +23 -2
  56. package/dist/storage/sqliteStore.js +39 -2
  57. package/dist/storage/taskStore.js +54 -5
  58. package/dist/storage/upgrade/recordVersions.js +3 -1
  59. package/dist/storage/upgrade/sqliteStateMigration.js +10 -0
  60. package/dist/task/taskRecordReference.js +1 -0
  61. package/dist/tmux/tmuxManager.js +15 -4
  62. package/dist/web/assets/client/components.js +1 -1
  63. package/package.json +1 -1
  64. package/skills/yui-leader/SKILL.md +10 -5
  65. package/skills/yui-operator/SKILL.md +4 -0
  66. package/skills/yui-reviewer/SKILL.md +4 -0
  67. package/skills/yui-runtime/SKILL.md +61 -0
  68. package/skills/yui-worker/SKILL.md +82 -218
  69. package/dist/executor/managedClaudeRunner.js +0 -121
@@ -12,10 +12,12 @@ import { answerInputRequest } from "../input/inputRequest.js";
12
12
  import { activeRoleAgentBinding, updateRoleStatus } from "../role/role.js";
13
13
  import { effectiveLaunchWithTaskMainWorkspace, effectiveLaunchSnapshotsCompatible, effectiveLaunchSnapshotsCompatibleForTaskMain, resolveEffectiveLaunch, validateEffectiveLaunchSnapshot } from "../executor/effectiveLaunch.js";
14
14
  import { SYSTEM_OPERATOR_ROLE } from "../role/systemRoles.js";
15
- import { failAgentRun, markAgentRunDelivered, markAgentRunPushed, reopenRunForProviderRetry, withProviderRetry } from "../run/agentRun.js";
15
+ import { failAgentRun, markAgentRunDelivered, markAgentRunPushed, reopenRunForProviderRetry, clearProviderRetryOnProgress, agentRunDeliveryReceiptId, withProviderRetry } from "../run/agentRun.js";
16
+ import { markWorkflowOutcomeRequestDispatched } from "../run/runControlRequest.js";
16
17
  import { classifyProviderError, isRetryableProviderErrorClass } from "../lifecycle/providerErrorClass.js";
17
- import { providerRetryBudgetExhausted, providerRetryIsDue, scheduleProviderRetry } from "../run/providerRetry.js";
18
- import { providerRetryConfig } from "../run/providerRetryConfig.js";
18
+ import { deferProviderRetry, markProviderRetryDispatched, providerRetryIsDue, scheduleProviderRetry } from "../run/providerRetry.js";
19
+ import { providerRetryAdapterEnabled, providerRetryConfig } from "../run/providerRetryConfig.js";
20
+ import { classifyRuntimeProcessExit, validateRuntimeProcessExitObservation } from "../runtime/processExitObservation.js";
19
21
  import { terminalizeExactTaskRun } from "../lifecycle/exactRunTerminalization.js";
20
22
  import { createCanonicalLifecycleEvent, foldCanonicalLifecycleEvent } from "../lifecycle/canonicalLifecycleEvent.js";
21
23
  import { recordLeaderFailure } from "../scheduler/leaderFailure.js";
@@ -36,6 +38,8 @@ import { nativeSessionIdForLaunch } from "../runtime/preallocatedNativeSession.j
36
38
  import { builtinAgentDriverRegistry } from "../runtime/builtinAgentDrivers.js";
37
39
  import { RUNTIME_OBSERVATION_TASK_EVENT, createRuntimeObservation, runtimeObservationFenceMatches, runtimeObservationFromTaskEvent, runtimeObservationRunFenceMatches, runtimeObservationTaskEventPayload } from "../runtime/runtimeObservation.js";
38
40
  import { projectRuntimeTaskEvents } from "../runtime/runtimeProjection.js";
41
+ import { contextSnapshotRef } from "../context/contextSnapshot.js";
42
+ import { contextSnapshotDeltaRefIds, freezeRunContextSnapshot } from "../context/runContextPack.js";
39
43
  /** Maps the authoritative FileTaskStore records to the scheduler's narrow port. */
40
44
  export class FileSchedulerStoreAdapter {
41
45
  store;
@@ -47,6 +51,19 @@ export class FileSchedulerStoreAdapter {
47
51
  this.telemetry = telemetry;
48
52
  this.drivers = drivers;
49
53
  }
54
+ freezeLeaderContextSnapshot(taskId, roleName, now) {
55
+ return this.store.transaction((tx) => {
56
+ const snapshot = freezeRunContextSnapshot(tx, {
57
+ taskId,
58
+ roleName,
59
+ purpose: "execution"
60
+ }, now);
61
+ return Object.freeze({
62
+ ref: contextSnapshotRef(snapshot),
63
+ deltaRefIds: contextSnapshotDeltaRefIds(tx, snapshot)
64
+ });
65
+ });
66
+ }
50
67
  /**
51
68
  * Sole provider-independent ingress for structured runtime state. Driver
52
69
  * mapping has already happened before this boundary; this method validates
@@ -142,6 +159,7 @@ export class FileSchedulerStoreAdapter {
142
159
  adapterId,
143
160
  launchId: input.fence.launchId,
144
161
  nativeSessionId: input.fence.nativeSessionId,
162
+ nativeTurnId: input.fence.nativeTurnId,
145
163
  runId: input.fence.runId,
146
164
  ...(failureEvidence.errorCode === undefined
147
165
  ? {}
@@ -152,7 +170,10 @@ export class FileSchedulerStoreAdapter {
152
170
  : { errorDetails: failureEvidence.details }),
153
171
  ...(failureEvidence.lastOutput === undefined
154
172
  ? {}
155
- : { lastAssistantMessage: failureEvidence.lastOutput })
173
+ : { lastAssistantMessage: failureEvidence.lastOutput }),
174
+ ...(failureEvidence.retryAfterMs === undefined
175
+ ? {}
176
+ : { retryAfterMs: failureEvidence.retryAfterMs })
156
177
  };
157
178
  if (this.classifyRuntimeTurnFailed(failure) !== "apply")
158
179
  return "obsolete";
@@ -191,6 +212,11 @@ export class FileSchedulerStoreAdapter {
191
212
  default:
192
213
  outcome = "obsolete";
193
214
  }
215
+ if (outcome === "applied"
216
+ && input.kind === "activity.observed"
217
+ && input.payload.activityId !== undefined) {
218
+ this.clearProviderRetryForProgress(input, "correlated-activity", now);
219
+ }
194
220
  if (outcome === "applied")
195
221
  this.persistRuntimeObservation(input, now);
196
222
  return outcome;
@@ -1226,19 +1252,46 @@ export class FileSchedulerStoreAdapter {
1226
1252
  if (active === null || active.id !== input.run.id) {
1227
1253
  throw new Error(`Active Agent run changed before delivery was persisted: ${input.run.id}.`);
1228
1254
  }
1255
+ let deliveryRun = active;
1256
+ if (active.providerRetry?.state === "dispatching") {
1257
+ deliveryRun = withProviderRetry(active, markProviderRetryDispatched(active.providerRetry));
1258
+ store.saveAgentRun(deliveryRun);
1259
+ store.saveEvent(task.id, createTaskEvent(store.nextEventId(task.id), task.id, "runtime.provider-retry-dispatched", {
1260
+ runId: active.id,
1261
+ roleName: active.roleName,
1262
+ episodeId: active.providerRetry.episodeId,
1263
+ receiptId: active.providerRetry.lastRetryReceiptId ?? "",
1264
+ dispatchedRetries: String(active.providerRetry.dispatchedRetries + 1)
1265
+ }, input.now));
1266
+ }
1267
+ if (active.controlRequest?.state === "dispatching") {
1268
+ deliveryRun = {
1269
+ ...active,
1270
+ controlRequest: markWorkflowOutcomeRequestDispatched(active.controlRequest)
1271
+ };
1272
+ store.saveAgentRun(deliveryRun);
1273
+ store.saveEvent(task.id, createTaskEvent(store.nextEventId(task.id), task.id, "runtime.workflow-outcome-request-dispatched", {
1274
+ runId: active.id,
1275
+ roleName: active.roleName,
1276
+ requestId: active.controlRequest.requestId,
1277
+ receiptId: active.controlRequest.receiptId,
1278
+ nativeTurnId: active.controlRequest.nativeTurnId
1279
+ }, input.now));
1280
+ }
1229
1281
  // Transport success records prompt-pushed ONLY. Acceptance (deliveredAt)
1230
1282
  // is written exclusively by an exact provider-accepted fold, never by the
1231
1283
  // transport receipt — that removes the transport-to-delivered false path.
1232
- if (active.pushedAt === undefined) {
1233
- store.saveAgentRun(markAgentRunPushed(active, input.now));
1234
- markTaskRoleRunPushedInFlight(store, role, input.run, input.now);
1235
- store.saveEvent(task.id, createTaskEvent(store.nextEventId(task.id), task.id, "run.pushed", runLaunchEventPayload(active), input.now));
1284
+ if (deliveryRun.pushedAt === undefined) {
1285
+ const pushed = markAgentRunPushed(deliveryRun, input.now);
1286
+ store.saveAgentRun(pushed);
1287
+ markTaskRoleRunPushedInFlight(store, role, pushed, input.now);
1288
+ store.saveEvent(task.id, createTaskEvent(store.nextEventId(task.id), task.id, "run.pushed", runLaunchEventPayload(pushed), input.now));
1236
1289
  }
1237
1290
  else {
1238
1291
  const sessions = store.getTaskRoleSessionSet(input.task.id, input.role.name);
1239
1292
  if (sessions?.inFlight?.runId === input.run.id
1240
1293
  && sessions.inFlight.pushedAt === undefined) {
1241
- markTaskRoleRunPushedInFlight(store, role, input.run, input.now);
1294
+ markTaskRoleRunPushedInFlight(store, role, deliveryRun, input.now);
1242
1295
  }
1243
1296
  }
1244
1297
  if (role.status !== "running") {
@@ -1317,7 +1370,7 @@ export class FileSchedulerStoreAdapter {
1317
1370
  || mailbox.processing.executionRef?.type !== "run"
1318
1371
  || mailbox.processing.executionRef.taskId !== input.taskId
1319
1372
  || mailbox.processing.executionRef.id !== input.runId
1320
- || !preparedDeliveryFailureSessionFenceMatches(sessions, session, input)
1373
+ || !preparedDeliveryFailureSessionFenceMatches(sessions, session, input, agentRunDeliveryReceiptId(active))
1321
1374
  || !preparedReservationMatches(runtimeMailbox, input.taskId, input.runId, input.launchId)) {
1322
1375
  return "state-changed";
1323
1376
  }
@@ -1327,7 +1380,7 @@ export class FileSchedulerStoreAdapter {
1327
1380
  roleName: input.roleName,
1328
1381
  agentId: input.agentId,
1329
1382
  runId: input.runId,
1330
- receiptId: formatAgentRunReceiptId(input.taskId, input.runId),
1383
+ receiptId: agentRunDeliveryReceiptId(active),
1331
1384
  ...(session?.nativeSessionId === undefined
1332
1385
  ? {}
1333
1386
  : { nativeSessionId: session.nativeSessionId }),
@@ -1441,7 +1494,7 @@ export class FileSchedulerStoreAdapter {
1441
1494
  && sessions !== null
1442
1495
  && (sessions.inFlight.agentId !== currentRun.effective.agentId
1443
1496
  || sessions.inFlight.runId !== currentRun.id
1444
- || sessions.inFlight.receiptId !== formatAgentRunReceiptId(task.id, currentRun.id))) {
1497
+ || sessions.inFlight.receiptId !== agentRunDeliveryReceiptId(currentRun))) {
1445
1498
  return "state-changed";
1446
1499
  }
1447
1500
  // Review launch failure uses the aggregate exact-terminalization path.
@@ -1463,7 +1516,7 @@ export class FileSchedulerStoreAdapter {
1463
1516
  roleName: role.name,
1464
1517
  agentId: currentRun.effective.agentId,
1465
1518
  runId: currentRun.id,
1466
- receiptId: formatAgentRunReceiptId(task.id, currentRun.id),
1519
+ receiptId: agentRunDeliveryReceiptId(currentRun),
1467
1520
  ...(input.session?.nativeSessionId === undefined
1468
1521
  ? {}
1469
1522
  : { nativeSessionId: input.session.nativeSessionId }),
@@ -1870,7 +1923,7 @@ export class FileSchedulerStoreAdapter {
1870
1923
  || session.launchId !== input.launchId
1871
1924
  || sessions.inFlight?.agentId !== input.agentId
1872
1925
  || sessions.inFlight.runId !== input.runId
1873
- || sessions.inFlight.receiptId !== `agent-run:${input.taskId}/${input.runId}`)
1926
+ || sessions.inFlight.receiptId !== agentRunDeliveryReceiptId(run))
1874
1927
  return "obsolete";
1875
1928
  return "apply";
1876
1929
  }
@@ -1892,7 +1945,7 @@ export class FileSchedulerStoreAdapter {
1892
1945
  roleName: input.roleName,
1893
1946
  agentId: input.agentId,
1894
1947
  runId: input.runId,
1895
- receiptId: `agent-run:${input.taskId}/${input.runId}`,
1948
+ receiptId: agentRunDeliveryReceiptId(before),
1896
1949
  nativeSessionId: input.nativeSessionId,
1897
1950
  launchId: input.launchId,
1898
1951
  outcome: {
@@ -1962,7 +2015,7 @@ export class FileSchedulerStoreAdapter {
1962
2015
  errorDetails: input.errorDetails,
1963
2016
  summary: input.lastAssistantMessage
1964
2017
  });
1965
- const adapterEnabled = config.adapters.includes(input.adapterId);
2018
+ const adapterEnabled = providerRetryAdapterEnabled(config, input.adapterId);
1966
2019
  const retryable = adapterEnabled
1967
2020
  && isRetryableProviderErrorClass(classification.errorClass);
1968
2021
  const shadow = config.mode === "shadow";
@@ -1981,31 +2034,101 @@ export class FileSchedulerStoreAdapter {
1981
2034
  if (blocked !== null)
1982
2035
  return blocked;
1983
2036
  }
2037
+ if (!shadow && classification.errorClass === "context-capacity") {
2038
+ const blocked = this.applyContextCapacityBlock(store, input, summary, now);
2039
+ if (blocked !== null)
2040
+ return blocked;
2041
+ }
2042
+ if (!shadow && classification.errorClass === "session-dead") {
2043
+ const run = store.getAgentRun(input.taskId, input.runId);
2044
+ if (run !== null && run.status === "active" && run.mode === "resume") {
2045
+ // A structured failure on the exact resume launch is the only Core
2046
+ // evidence that native continuation is unrecoverable. The normal
2047
+ // terminal path below may now request a fresh managed generation.
2048
+ store.saveEvent(input.taskId, createTaskEvent(store.nextEventId(input.taskId), input.taskId, "runtime.native-resume-unrecoverable", {
2049
+ eventId: input.eventId,
2050
+ runId: input.runId,
2051
+ roleName: input.roleName,
2052
+ launchId: input.launchId,
2053
+ nativeSessionId: input.nativeSessionId,
2054
+ ...(run.assignment.contextSnapshotRef === undefined
2055
+ ? {}
2056
+ : {
2057
+ contextSnapshotId: run.assignment.contextSnapshotRef.id,
2058
+ contextSnapshotDigest: run.assignment.contextSnapshotRef.digest
2059
+ })
2060
+ }, now));
2061
+ }
2062
+ else {
2063
+ const blocked = this.applyNativeSessionRecoverabilityBlock(store, input, summary, now);
2064
+ if (blocked !== null)
2065
+ return blocked;
2066
+ }
2067
+ }
1984
2068
  return null;
1985
2069
  }
2070
+ // transport-uncertain: the Provider may have accepted the turn. If a
2071
+ // native completion is already durable, the completion path owns the
2072
+ // yield; do not resend.
2073
+ if (classification.errorClass === "transport-uncertain") {
2074
+ const driver = this.drivers.requireByAdapterId(input.adapterId);
2075
+ if (driver.capabilities.lifecycle.deliveryDeduplication !== "exact") {
2076
+ const run = store.getAgentRun(input.taskId, input.runId);
2077
+ if (run === null || run.status !== "active")
2078
+ return null;
2079
+ const blocked = scheduleProviderRetry(run.providerRetry, {
2080
+ failureEventId: input.eventId,
2081
+ errorClass: classification.errorClass,
2082
+ launchId: input.launchId,
2083
+ nativeSessionId: input.nativeSessionId,
2084
+ failedNativeTurnId: input.nativeTurnId,
2085
+ lastErrorSummary: summary,
2086
+ scheduleNextAttempt: false
2087
+ }, now);
2088
+ if (blocked.outcome === "exhausted")
2089
+ return null;
2090
+ store.saveAgentRun(withProviderRetry(run, blocked.retry));
2091
+ this.recordProviderRetryClassified(store, input, classification.errorClass, {
2092
+ wouldRetry: "false",
2093
+ shadow: "false",
2094
+ note: "delivery-deduplication-unproven"
2095
+ }, now);
2096
+ enqueueWork(store, input.roleName === "leader"
2097
+ ? { kind: "operator" }
2098
+ : { kind: "role", taskId: input.taskId, roleName: "leader" }, "provider-delivery-uncertain", now, [{ type: "run", taskId: input.taskId, id: input.runId }]);
2099
+ return { disposition: "applied", runId: input.runId };
2100
+ }
2101
+ }
1986
2102
  const run = store.getAgentRun(input.taskId, input.runId);
1987
2103
  if (run === null || run.status !== "active")
1988
2104
  return null;
1989
- // A retry lineage that has used its total budget terminalizes with one
1990
- // structured failure instead of looping. The Run falls through to the
1991
- // normal terminalization path below.
1992
- if (run.providerRetry !== undefined
1993
- && providerRetryBudgetExhausted(run.providerRetry, now, config.maxWindowMs)) {
2105
+ const retryDecision = scheduleProviderRetry(run.providerRetry, {
2106
+ failureEventId: input.eventId,
2107
+ errorClass: classification.errorClass,
2108
+ launchId: input.launchId,
2109
+ nativeSessionId: input.nativeSessionId,
2110
+ failedNativeTurnId: input.nativeTurnId,
2111
+ lastErrorSummary: summary,
2112
+ ...(input.retryAfterMs === undefined ? {} : { retryAfterMs: input.retryAfterMs })
2113
+ }, now);
2114
+ if (retryDecision.outcome === "exhausted") {
1994
2115
  this.recordProviderRetryClassified(store, input, classification.errorClass, {
1995
2116
  wouldRetry: "false",
1996
2117
  shadow: "false",
1997
- basis: classification.basis,
1998
- note: "retry-budget-exhausted",
1999
- attempt: String(run.providerRetry.attempt)
2118
+ note: `episode-exhausted-${retryDecision.reason}`,
2119
+ consecutiveFailures: String(run.providerRetry?.consecutiveFailures ?? 0),
2120
+ dispatchedRetries: String(run.providerRetry?.dispatchedRetries ?? 0)
2000
2121
  }, now);
2001
- return null;
2122
+ if (run.providerRetry === undefined)
2123
+ return null;
2124
+ const finalized = finalizeProviderRetryDeadline(store, run, retryDecision.reason === "attempts"
2125
+ ? "Provider retry failed after all three in-Session continuation attempts."
2126
+ : retryDecision.reason === "retry-after-window"
2127
+ ? "Provider Retry-After exceeded the bounded 600-second episode window."
2128
+ : "Provider retry did not recover within the bounded 600-second episode window.", retryDecision.reason === "attempts" ? "attempts-exhausted" : "episode-window-exhausted", now);
2129
+ return finalized ? { disposition: "applied", runId: input.runId } : null;
2002
2130
  }
2003
- const retry = scheduleProviderRetry(run.providerRetry, {
2004
- errorClass: classification.errorClass,
2005
- launchId: input.launchId,
2006
- nativeSessionId: input.nativeSessionId,
2007
- lastErrorSummary: summary
2008
- }, now);
2131
+ const retry = retryDecision.retry;
2009
2132
  store.saveAgentRun(withProviderRetry(run, retry));
2010
2133
  // Clear the in-flight fence so the original Session can be re-bound and
2011
2134
  const sessions = store.getTaskRoleSessionSet(input.taskId, input.roleName);
@@ -2014,7 +2137,7 @@ export class FileSchedulerStoreAdapter {
2014
2137
  store.saveTaskRoleSessionSet(clearTaskRoleRun(sessions, {
2015
2138
  agentId: input.agentId,
2016
2139
  runId: input.runId,
2017
- receiptId: `agent-run:${input.taskId}/${input.runId}`
2140
+ receiptId: agentRunDeliveryReceiptId(run)
2018
2141
  }, now));
2019
2142
  }
2020
2143
  // Settle the delivery claim so the retry push can re-claim the mailbox.
@@ -2036,8 +2159,8 @@ export class FileSchedulerStoreAdapter {
2036
2159
  this.recordProviderRetryClassified(store, input, classification.errorClass, {
2037
2160
  wouldRetry: "true",
2038
2161
  shadow: "false",
2039
- basis: classification.basis,
2040
- attempt: String(retry.attempt),
2162
+ consecutiveFailures: String(retry.consecutiveFailures),
2163
+ dispatchedRetries: String(retry.dispatchedRetries),
2041
2164
  nextAttemptAt: retry.nextAttemptAt
2042
2165
  }, now);
2043
2166
  return { disposition: "applied", runId: input.runId };
@@ -2052,14 +2175,18 @@ export class FileSchedulerStoreAdapter {
2052
2175
  const run = store.getAgentRun(input.taskId, input.runId);
2053
2176
  if (run === null || run.status !== "active")
2054
2177
  return null;
2055
- const retry = scheduleProviderRetry(run.providerRetry, {
2178
+ const retryDecision = scheduleProviderRetry(run.providerRetry, {
2179
+ failureEventId: input.eventId,
2056
2180
  errorClass: "policy-denied",
2057
2181
  launchId: input.launchId,
2058
2182
  nativeSessionId: input.nativeSessionId,
2183
+ failedNativeTurnId: input.nativeTurnId,
2059
2184
  lastErrorSummary: summary,
2060
2185
  scheduleNextAttempt: false
2061
2186
  }, now);
2062
- store.saveAgentRun(withProviderRetry(run, retry));
2187
+ if (retryDecision.outcome === "exhausted")
2188
+ return null;
2189
+ store.saveAgentRun(withProviderRetry(run, retryDecision.retry));
2063
2190
  this.recordProviderRetryClassified(store, input, "policy-denied", {
2064
2191
  wouldRetry: "false",
2065
2192
  shadow: "false",
@@ -2078,6 +2205,80 @@ export class FileSchedulerStoreAdapter {
2078
2205
  }
2079
2206
  return { disposition: "applied", runId: input.runId };
2080
2207
  }
2208
+ /**
2209
+ * Exact Provider capacity failures never trigger a token-threshold rollover.
2210
+ * Keep the native lineage intact and surface the Driver's explicit native
2211
+ * compaction/resume capability; unsupported/unknown capabilities fail safe
2212
+ * for Leader/Operator action instead of forcing a fresh generation.
2213
+ */
2214
+ applyContextCapacityBlock(store, input, summary, now) {
2215
+ const run = store.getAgentRun(input.taskId, input.runId);
2216
+ if (run === null || run.status !== "active")
2217
+ return null;
2218
+ const decision = scheduleProviderRetry(run.providerRetry, {
2219
+ failureEventId: input.eventId,
2220
+ errorClass: "context-capacity",
2221
+ launchId: input.launchId,
2222
+ nativeSessionId: input.nativeSessionId,
2223
+ failedNativeTurnId: input.nativeTurnId,
2224
+ lastErrorSummary: summary,
2225
+ scheduleNextAttempt: false
2226
+ }, now);
2227
+ if (decision.outcome === "exhausted")
2228
+ return null;
2229
+ store.saveAgentRun(withProviderRetry(run, decision.retry));
2230
+ const driver = this.drivers.requireByAdapterId(input.adapterId);
2231
+ store.saveEvent(input.taskId, createTaskEvent(store.nextEventId(input.taskId), input.taskId, "runtime.context-capacity-failure", {
2232
+ eventId: input.eventId,
2233
+ runId: input.runId,
2234
+ roleName: input.roleName,
2235
+ nativeSessionId: input.nativeSessionId,
2236
+ launchId: input.launchId,
2237
+ compactionCapability: driver.capabilities.lifecycle.compaction,
2238
+ nativeResumeCapability: driver.capabilities.lifecycle.nativeConversationResume,
2239
+ action: driver.capabilities.lifecycle.compaction === "native-explicit"
2240
+ ? "native-explicit-compaction-required"
2241
+ : "await-provider-native-recovery"
2242
+ }, now));
2243
+ this.recordProviderRetryClassified(store, input, "context-capacity", {
2244
+ wouldRetry: "false",
2245
+ shadow: "false",
2246
+ note: "provider-native-capacity-recovery-only"
2247
+ }, now);
2248
+ enqueueWork(store, input.roleName === "leader"
2249
+ ? { kind: "operator" }
2250
+ : { kind: "role", taskId: input.taskId, roleName: "leader" }, "provider-context-capacity-recovery-required", now, [{ type: "run", taskId: input.taskId, id: input.runId }]);
2251
+ return { disposition: "applied", runId: input.runId };
2252
+ }
2253
+ applyNativeSessionRecoverabilityBlock(store, input, summary, now) {
2254
+ const run = store.getAgentRun(input.taskId, input.runId);
2255
+ if (run === null || run.status !== "active")
2256
+ return null;
2257
+ const decision = scheduleProviderRetry(run.providerRetry, {
2258
+ failureEventId: input.eventId,
2259
+ errorClass: "session-dead",
2260
+ launchId: input.launchId,
2261
+ nativeSessionId: input.nativeSessionId,
2262
+ failedNativeTurnId: input.nativeTurnId,
2263
+ lastErrorSummary: summary,
2264
+ scheduleNextAttempt: false
2265
+ }, now);
2266
+ if (decision.outcome === "exhausted")
2267
+ return null;
2268
+ store.saveAgentRun(withProviderRetry(run, decision.retry));
2269
+ store.saveEvent(input.taskId, createTaskEvent(store.nextEventId(input.taskId), input.taskId, "runtime.native-session-recoverability-unproven", {
2270
+ eventId: input.eventId,
2271
+ runId: input.runId,
2272
+ roleName: input.roleName,
2273
+ launchId: input.launchId,
2274
+ nativeSessionId: input.nativeSessionId,
2275
+ requiredEvidence: "exact-native-resume-outcome"
2276
+ }, now));
2277
+ enqueueWork(store, input.roleName === "leader"
2278
+ ? { kind: "operator" }
2279
+ : { kind: "role", taskId: input.taskId, roleName: "leader" }, "native-session-recoverability-unproven", now, [{ type: "run", taskId: input.taskId, id: input.runId }]);
2280
+ return { disposition: "applied", runId: input.runId };
2281
+ }
2081
2282
  recordProviderRetryClassified(store, input, errorClass, extra, now) {
2082
2283
  store.saveEvent(input.taskId, createTaskEvent(store.nextEventId(input.taskId), input.taskId, "runtime.provider-retry-classified", {
2083
2284
  eventId: input.eventId,
@@ -2095,6 +2296,54 @@ export class FileSchedulerStoreAdapter {
2095
2296
  listPendingProviderRetries(taskIds) {
2096
2297
  return this.store.listPendingProviderRetries(taskIds);
2097
2298
  }
2299
+ saveRoleHostExitObservation(input) {
2300
+ this.store.transaction((store) => {
2301
+ const identity = [
2302
+ input.taskId,
2303
+ input.roleName,
2304
+ input.runId,
2305
+ input.launchId ?? "unknown-launch",
2306
+ String(input.deadStatus ?? "unknown-status")
2307
+ ].join("\0");
2308
+ const observationId = `tmux-host-exit-${createHash("sha256").update(identity).digest("hex")}`;
2309
+ const events = store.listEvents(input.taskId);
2310
+ if (events.some((event) => (event.type === "runtime.process-exit-observed"
2311
+ && event.payload.observationId === observationId)))
2312
+ return;
2313
+ const stopReceipt = [...events].reverse().find((event) => (event.type === "runtime.session-termination"
2314
+ && event.payload.roleName === input.roleName
2315
+ && (input.launchId === undefined || event.payload.launchId === input.launchId)
2316
+ && ["stop-requested", "graceful-stop", "forced-stop", "stop-confirmed"]
2317
+ .includes(event.payload.outcome ?? "")));
2318
+ const observation = validateRuntimeProcessExitObservation({
2319
+ schemaVersion: 1,
2320
+ observationId,
2321
+ hostSequence: 1,
2322
+ hostInstanceId: `tmux-${input.launchId ?? input.roleName}`,
2323
+ taskId: input.taskId,
2324
+ roleName: input.roleName,
2325
+ runId: input.runId,
2326
+ launchId: input.launchId ?? `unknown-${input.runId}`,
2327
+ ...(input.nativeSessionId === undefined
2328
+ ? {}
2329
+ : { nativeSessionId: input.nativeSessionId }),
2330
+ processKind: "agent-host",
2331
+ ...(input.deadStatus === undefined ? {} : { exitCode: input.deadStatus }),
2332
+ ...(stopReceipt === undefined ? {} : { stopReceiptId: stopReceipt.id }),
2333
+ observedAt: input.observedAt.toISOString()
2334
+ });
2335
+ const classification = classifyRuntimeProcessExit(observation, {});
2336
+ store.saveEvent(input.taskId, createTaskEvent(store.nextEventId(input.taskId), input.taskId, "runtime.process-exit-observed", {
2337
+ observationId,
2338
+ processKind: "agent-host",
2339
+ roleName: input.roleName,
2340
+ launchId: observation.launchId,
2341
+ observedAt: observation.observedAt,
2342
+ classification,
2343
+ observation: JSON.stringify(observation)
2344
+ }, input.observedAt));
2345
+ });
2346
+ }
2098
2347
  /**
2099
2348
  * Issue 04: reopens each due retry on its original Native Session. A Run
2100
2349
  * whose Session is proven dead terminalizes with an exact replacement
@@ -2105,129 +2354,78 @@ export class FileSchedulerStoreAdapter {
2105
2354
  const selectedTaskIds = taskIds === undefined
2106
2355
  ? undefined
2107
2356
  : [...taskIds].sort((left, right) => (left.localeCompare(right, undefined, { numeric: true })));
2108
- const due = this.listPendingProviderRetries(selectedTaskIds).filter((entry) => Date.parse(entry.nextAttemptAt) <= now.getTime());
2357
+ const due = this.listPendingProviderRetries(selectedTaskIds).filter((entry) => Date.parse(entry.dueAt) <= now.getTime());
2109
2358
  const reopened = [];
2110
- const config = providerRetryConfig(this.store.getConfig());
2111
2359
  for (const entry of due) {
2112
2360
  this.store.transaction((store) => {
2113
2361
  const run = store.getAgentRun(entry.taskId, entry.runId);
2114
2362
  if (run === null
2115
2363
  || run.status !== "active"
2116
- || run.providerRetry === undefined
2364
+ || run.providerRetry === undefined) {
2365
+ return;
2366
+ }
2367
+ if (run.providerRetry.state === "dispatching"
2368
+ || run.providerRetry.state === "awaiting-progress") {
2369
+ if (now.getTime() < Date.parse(run.providerRetry.episodeDeadlineAt))
2370
+ return;
2371
+ finalizeProviderRetryDeadline(store, run, run.providerRetry.state === "dispatching"
2372
+ ? "Provider retry delivery outcome remained uncertain through the bounded episode deadline; no correlated Provider progress was observed."
2373
+ : "Provider retry produced no correlated Provider input/output progress before the bounded episode deadline.", run.providerRetry.state === "dispatching"
2374
+ ? "retry-delivery-outcome-uncertain"
2375
+ : "provider-progress-timeout", now);
2376
+ return;
2377
+ }
2378
+ if (run.providerRetry.state !== "scheduled"
2117
2379
  || run.providerRetry.nextAttemptAt === undefined
2118
2380
  || !providerRetryIsDue(run.providerRetry, now)) {
2119
2381
  return;
2120
2382
  }
2121
2383
  const sessions = store.getTaskRoleSessionSet(entry.taskId, entry.roleName);
2122
2384
  const session = sessions?.sessions[run.effective.agentId];
2123
- // Budget exhausted: one structured terminal failure, never a loop.
2124
- if (providerRetryBudgetExhausted(run.providerRetry, now, config.maxWindowMs)) {
2125
- const summary = `Provider retry budget exhausted after ${run.providerRetry.attempt} attempt(s) `
2126
- + `(run ${run.id}, error class ${run.providerRetry.errorClass}, `
2127
- + `last error: ${run.providerRetry.lastErrorSummary}). `
2128
- + `The original Session requires an explicit Leader recovery decision.`;
2129
- const result = terminalizeExactTaskRun(store, {
2130
- taskId: entry.taskId,
2131
- roleName: entry.roleName,
2132
- agentId: run.effective.agentId,
2133
- runId: run.id,
2134
- receiptId: formatAgentRunReceiptId(entry.taskId, run.id),
2135
- ...(run.providerRetry.nativeSessionId === undefined
2136
- ? {}
2137
- : { nativeSessionId: run.providerRetry.nativeSessionId }),
2138
- ...(run.providerRetry.launchId === undefined
2139
- ? {}
2140
- : { launchId: run.providerRetry.launchId }),
2141
- outcome: { status: "failed", summary }
2142
- }, now);
2143
- if (result.disposition !== "applied")
2144
- return;
2145
- store.saveEvent(entry.taskId, createTaskEvent(store.nextEventId(entry.taskId), entry.taskId, "runtime.provider-retry-budget-exhausted", {
2146
- runId: run.id,
2147
- attempt: String(run.providerRetry.attempt),
2148
- errorClass: run.providerRetry.errorClass
2149
- }, now));
2150
- enqueueWork(store, { kind: "role", taskId: entry.taskId, roleName: "leader" }, "role-run-failed", now, [{ type: "run", taskId: entry.taskId, id: run.id }]);
2151
- return;
2152
- }
2153
2385
  const identityMatches = session !== undefined
2154
2386
  && session.adapterId === run.effective.adapterId
2155
2387
  && (run.providerRetry.nativeSessionId === undefined
2156
2388
  || session.nativeSessionId === run.providerRetry.nativeSessionId)
2157
2389
  && (run.providerRetry.launchId === undefined
2158
2390
  || session.launchId === run.providerRetry.launchId);
2159
- const sessionAlive = identityMatches
2160
- && session.status !== "stopped"
2161
- && session.status !== "broken";
2162
- if (sessionAlive) {
2163
- // The CLI process is still alive. Enter a grace period: let the
2164
- // Provider reconnect and finish the turn itself instead of sending
2165
- // a duplicate prompt. Reschedule with the next backoff; a late
2166
- // completion is attributed to this Run by observeRuntimeTurnCompleted.
2167
- const retry = scheduleProviderRetry(run.providerRetry, {
2168
- errorClass: run.providerRetry.errorClass,
2169
- ...(run.providerRetry.launchId === undefined
2170
- ? {}
2171
- : { launchId: run.providerRetry.launchId }),
2172
- ...(run.providerRetry.nativeSessionId === undefined
2173
- ? {}
2174
- : { nativeSessionId: run.providerRetry.nativeSessionId }),
2175
- lastErrorSummary: run.providerRetry.lastErrorSummary
2176
- }, now);
2177
- store.saveAgentRun(withProviderRetry(run, retry));
2178
- store.saveEvent(entry.taskId, createTaskEvent(store.nextEventId(entry.taskId), entry.taskId, "runtime.provider-retry-waiting", {
2179
- runId: run.id,
2180
- attempt: String(retry.attempt),
2181
- ...(retry.nextAttemptAt === undefined
2182
- ? {}
2183
- : { nextAttemptAt: retry.nextAttemptAt })
2184
- }, now));
2391
+ if (!identityMatches || session?.nativeSessionId === undefined) {
2392
+ // Local Session projection loss is not native-session-dead evidence.
2393
+ // Preserve the Run and episode for explicit same-generation recovery;
2394
+ // only an exact Provider-native resume response may terminalize it as
2395
+ // unrecoverable.
2396
+ const deferred = deferProviderRetry(run.providerRetry, now);
2397
+ if (deferred === null) {
2398
+ finalizeProviderRetryDeadline(store, run, "Provider retry safety could not be re-established before the bounded episode deadline; native Session recoverability remains unproven.", "native-resume-unproven", now);
2399
+ return;
2400
+ }
2401
+ store.saveAgentRun(withProviderRetry(run, deferred));
2402
+ if (!store.listEvents(entry.taskId).some((event) => (event.type === "runtime.provider-retry-native-resume-unproven"
2403
+ && event.payload.runId === run.id))) {
2404
+ store.saveEvent(entry.taskId, createTaskEvent(store.nextEventId(entry.taskId), entry.taskId, "runtime.provider-retry-native-resume-unproven", {
2405
+ runId: run.id,
2406
+ consecutiveFailures: String(run.providerRetry.consecutiveFailures),
2407
+ dispatchedRetries: String(run.providerRetry.dispatchedRetries)
2408
+ }, now));
2409
+ }
2410
+ enqueueWork(store, { kind: "operator" }, "provider-retry-native-resume-unproven", now, [
2411
+ { type: "task", id: entry.taskId },
2412
+ { type: "run", taskId: entry.taskId, id: run.id }
2413
+ ]);
2185
2414
  return;
2186
2415
  }
2187
- // The process is gone. If the adapter supports resume and the
2188
- // projection carries a native Session id, relaunch with resume: the
2189
- // conversation identity stays the same, only the launch generation
2190
- // changes. Otherwise terminalize with a precise replacement blocker.
2191
- const canResume = run.providerRetry.nativeSessionId !== undefined
2192
- && this.drivers.requireByAdapterId(run.effective.adapterId)
2193
- .capabilities.control.resume;
2194
- if (!canResume) {
2195
- const summary = `Provider retry stopped: the original Session is gone `
2196
- + `and the adapter cannot resume it `
2197
- + `(run ${run.id}, attempt ${run.providerRetry.attempt}, `
2198
- + `last error: ${run.providerRetry.lastErrorSummary}). `
2199
- + `A replacement Session requires an explicit Leader recovery decision.`;
2200
- const result = terminalizeExactTaskRun(store, {
2201
- taskId: entry.taskId,
2202
- roleName: entry.roleName,
2203
- agentId: run.effective.agentId,
2204
- runId: run.id,
2205
- receiptId: formatAgentRunReceiptId(entry.taskId, run.id),
2206
- ...(run.providerRetry.nativeSessionId === undefined
2207
- ? {}
2208
- : { nativeSessionId: run.providerRetry.nativeSessionId }),
2209
- ...(run.providerRetry.launchId === undefined
2210
- ? {}
2211
- : { launchId: run.providerRetry.launchId }),
2212
- outcome: { status: "failed", summary }
2213
- }, now);
2214
- if (result.disposition !== "applied")
2416
+ if (hasUnsettledRuntimeOperations(store, entry.taskId, run.id)) {
2417
+ const deferred = deferProviderRetry(run.providerRetry, now);
2418
+ if (deferred === null) {
2419
+ finalizeProviderRetryDeadline(store, run, "Provider retry stopped because tool/subagent outcome remained uncertain through the bounded episode deadline.", "operation-outcome-uncertain", now);
2215
2420
  return;
2216
- store.saveEvent(entry.taskId, createTaskEvent(store.nextEventId(entry.taskId), entry.taskId, "runtime.provider-retry-session-dead", { runId: run.id, attempt: String(run.providerRetry.attempt) }, now));
2217
- enqueueWork(store, { kind: "role", taskId: entry.taskId, roleName: "leader" }, "role-run-failed", now, [{ type: "run", taskId: entry.taskId, id: run.id }]);
2421
+ }
2422
+ store.saveAgentRun(withProviderRetry(run, deferred));
2218
2423
  return;
2219
2424
  }
2220
- // Resume relaunch: reopen the Run in resume mode on the same native
2221
- // Session. The delivery pass relaunches the process with resume and
2222
- // re-pushes the exact same input; only the launch generation changes.
2223
- store.saveAgentRun(reopenRunForProviderRetry(run, now));
2224
- store.saveEvent(entry.taskId, createTaskEvent(store.nextEventId(entry.taskId), entry.taskId, "runtime.provider-retry-resume", {
2225
- runId: run.id,
2226
- attempt: String(run.providerRetry.attempt),
2227
- ...(run.providerRetry.nativeSessionId === undefined
2228
- ? {}
2229
- : { nativeSessionId: run.providerRetry.nativeSessionId })
2230
- }, now));
2425
+ // Reopen the Run on its original Session: reset transport markers,
2426
+ // switch to resume, and mark the projection in-flight. The delivery
2427
+ // pass re-pushes the exact same input in this same pass.
2428
+ store.saveAgentRun(reopenRunForProviderRetry(run, `provider-retry-${run.providerRetry.episodeId}-${run.providerRetry.dispatchedRetries + 1}`, now));
2231
2429
  // The retry decision settled the original delivery claim; re-queue the
2232
2430
  // Role mailbox so the delivery pass can claim and re-push the Run.
2233
2431
  enqueueWork(store, { kind: "role", taskId: entry.taskId, roleName: entry.roleName }, "provider-retry-repush", now, [{ type: "run", taskId: entry.taskId, id: run.id }]);
@@ -2422,6 +2620,9 @@ export class FileSchedulerStoreAdapter {
2422
2620
  : { nativeTurnId: input.fence.nativeTurnId })
2423
2621
  }, now));
2424
2622
  store.saveWorkMailbox(completeMailboxInputDelivery(mailbox, inputDelivery.attemptId, now));
2623
+ if (active.providerRetry !== undefined) {
2624
+ this.clearProviderRetryProjection(store, active, input, "provider-accepted", now);
2625
+ }
2425
2626
  return "applied";
2426
2627
  }
2427
2628
  const event = createCanonicalLifecycleEvent({
@@ -2451,8 +2652,24 @@ export class FileSchedulerStoreAdapter {
2451
2652
  recordCanonicalObservationObsolete(store, input, "role-missing", now);
2452
2653
  return "obsolete";
2453
2654
  }
2454
- if (active.deliveredAt === undefined) {
2455
- const delivered = markAgentRunDelivered(active, now);
2655
+ const progressed = clearProviderRetryOnProgress(active);
2656
+ if (progressed !== active) {
2657
+ store.saveAgentRun(progressed);
2658
+ store.saveEvent(input.fence.taskId, createTaskEvent(store.nextEventId(input.fence.taskId), input.fence.taskId, "runtime.provider-retry-recovered", {
2659
+ runId: active.id,
2660
+ roleName: active.roleName,
2661
+ evidence: "provider-accepted",
2662
+ nativeSessionId: input.fence.nativeSessionId ?? "",
2663
+ episodeId: active.providerRetry?.episodeId ?? "",
2664
+ consecutiveFailures: String(active.providerRetry?.consecutiveFailures ?? 0),
2665
+ dispatchedRetries: String(active.providerRetry?.dispatchedRetries ?? 0),
2666
+ recoveryLatencyMs: String(active.providerRetry === undefined
2667
+ ? 0
2668
+ : Math.max(0, now.getTime() - Date.parse(active.providerRetry.firstFailureAt)))
2669
+ }, now));
2670
+ }
2671
+ if (progressed.deliveredAt === undefined) {
2672
+ const delivered = markAgentRunDelivered(progressed, now);
2456
2673
  store.saveAgentRun(delivered);
2457
2674
  markTaskRoleRunDeliveredInFlight(store, role, delivered, now);
2458
2675
  store.saveEvent(input.fence.taskId, createTaskEvent(store.nextEventId(input.fence.taskId), input.fence.taskId, "run.delivered", runLaunchEventPayload(delivered), now));
@@ -2467,6 +2684,35 @@ export class FileSchedulerStoreAdapter {
2467
2684
  return "applied";
2468
2685
  });
2469
2686
  }
2687
+ clearProviderRetryForProgress(input, evidence, now) {
2688
+ this.store.transaction((store) => {
2689
+ const taskId = input.fence.taskId;
2690
+ const runId = input.fence.runId;
2691
+ if (taskId === undefined || runId === undefined)
2692
+ return;
2693
+ const run = store.getAgentRun(taskId, runId);
2694
+ if (run === null || run.status !== "active" || run.providerRetry === undefined)
2695
+ return;
2696
+ this.clearProviderRetryProjection(store, run, input, evidence, now);
2697
+ });
2698
+ }
2699
+ clearProviderRetryProjection(store, run, input, evidence, now) {
2700
+ const retry = run.providerRetry;
2701
+ if (retry === undefined)
2702
+ return;
2703
+ store.saveAgentRun(clearProviderRetryOnProgress(run));
2704
+ store.saveEvent(run.taskId, createTaskEvent(store.nextEventId(run.taskId), run.taskId, "runtime.provider-retry-recovered", {
2705
+ runId: run.id,
2706
+ roleName: input.fence.roleName,
2707
+ evidence,
2708
+ episodeId: retry.episodeId,
2709
+ consecutiveFailures: String(retry.consecutiveFailures),
2710
+ dispatchedRetries: String(retry.dispatchedRetries),
2711
+ recoveryLatencyMs: String(Math.max(0, now.getTime() - Date.parse(retry.firstFailureAt))),
2712
+ nativeSessionId: input.fence.nativeSessionId ?? "",
2713
+ activityId: input.payload.activityId ?? ""
2714
+ }, now));
2715
+ }
2470
2716
  foldRuntimeLifecycleEvent(store, event, observation) {
2471
2717
  const expectation = this.projectRunExpectation(store, event.fence, observation.fence.runId);
2472
2718
  if (expectation === null) {
@@ -2632,6 +2878,14 @@ export class FileSchedulerStoreAdapter {
2632
2878
  return "apply";
2633
2879
  }
2634
2880
  }
2881
+ function hasUnsettledRuntimeOperations(store, taskId, runId) {
2882
+ return store.listEvents(taskId).some((event) => {
2883
+ const observation = runtimeObservationFromTaskEvent(event);
2884
+ return observation !== null
2885
+ && observation.fence.runId === runId
2886
+ && observation.kind === "operation.started";
2887
+ });
2888
+ }
2635
2889
  function runtimeTurnFailureSummary(input) {
2636
2890
  return [
2637
2891
  "Agent runtime turn failed.",
@@ -2781,7 +3035,7 @@ function preallocatedRuntimeReadyAwaitingProjection(store, observation, drivers)
2781
3035
  || run.status !== "active"
2782
3036
  || run.effective.agentId !== observation.fence.agentId
2783
3037
  || run.effective.adapterId !== driver.adapterId
2784
- || observation.fence.receiptId !== formatAgentRunReceiptId(taskId, runId)
3038
+ || observation.fence.receiptId !== agentRunDeliveryReceiptId(run)
2785
3039
  || (sessions?.inFlight !== null && sessions?.inFlight !== undefined)
2786
3040
  || sessions?.sessions[observation.fence.agentId] !== undefined)
2787
3041
  return false;
@@ -2898,7 +3152,54 @@ function preparedSessionMatches(session, agentId, adapterId, nativeSessionId, la
2898
3152
  ? session.launchId === undefined
2899
3153
  : session.launchId === launchId;
2900
3154
  }
2901
- function preparedDeliveryFailureSessionFenceMatches(sessions, session, input) {
3155
+ function finalizeProviderRetryDeadline(store, run, summary, reason, now) {
3156
+ const retry = run.providerRetry;
3157
+ if (retry === undefined)
3158
+ return false;
3159
+ const terminalization = terminalizeExactTaskRun(store, {
3160
+ taskId: run.taskId,
3161
+ roleName: run.roleName,
3162
+ agentId: run.effective.agentId,
3163
+ runId: run.id,
3164
+ receiptId: agentRunDeliveryReceiptId(run),
3165
+ outcome: { status: "failed", summary }
3166
+ }, now);
3167
+ if (terminalization.disposition !== "applied" || terminalization.run === null)
3168
+ return false;
3169
+ const terminal = terminalization.run;
3170
+ store.saveEvent(run.taskId, createTaskEvent(store.nextEventId(run.taskId), run.taskId, "runtime.provider-retry-exhausted", {
3171
+ runId: run.id,
3172
+ roleName: run.roleName,
3173
+ episodeId: retry.episodeId,
3174
+ reason,
3175
+ consecutiveFailures: String(retry.consecutiveFailures),
3176
+ dispatchedRetries: String(retry.dispatchedRetries),
3177
+ elapsedMs: String(Math.max(0, now.getTime() - Date.parse(retry.firstFailureAt)))
3178
+ }, now));
3179
+ if (terminal.purpose === "execution" && terminal.workItemId !== undefined) {
3180
+ const item = store.getWorkItem(run.taskId, terminal.workItemId);
3181
+ if (item !== null && !["completed", "failed", "retired"].includes(item.status)) {
3182
+ store.saveWorkItem(run.taskId, updateWorkItemStatus(item, "failed", now, summary));
3183
+ }
3184
+ }
3185
+ const role = store.getRole(run.taskId, run.roleName);
3186
+ if (role !== null) {
3187
+ store.saveRole(run.taskId, updateRoleStatus(role, run.roleName === "leader" ? "failed" : "idle", now));
3188
+ }
3189
+ if (run.roleName === "leader") {
3190
+ store.saveLeaderFailure(recordLeaderFailure(run.taskId, retry.nativeSessionId ?? "(unproven)", summary, now, store.getLeaderFailure(run.taskId)));
3191
+ store.saveOperatorNotification(createLeaderRecoveryNotification(run.taskId, summary, now, store.getOperatorNotification(run.taskId)));
3192
+ enqueueWork(store, { kind: "operator" }, "provider-retry-exhausted", now, [
3193
+ { type: "task", id: run.taskId },
3194
+ { type: "run", taskId: run.taskId, id: run.id }
3195
+ ]);
3196
+ }
3197
+ else {
3198
+ enqueueWork(store, { kind: "role", taskId: run.taskId, roleName: "leader" }, "provider-retry-exhausted", now, [{ type: "run", taskId: run.taskId, id: run.id }]);
3199
+ }
3200
+ return true;
3201
+ }
3202
+ function preparedDeliveryFailureSessionFenceMatches(sessions, session, input, expectedReceiptId) {
2902
3203
  if (sessions === null)
2903
3204
  return input.nativeSessionId === undefined;
2904
3205
  if (sessions.activeAgentId !== input.agentId)
@@ -2909,7 +3210,7 @@ function preparedDeliveryFailureSessionFenceMatches(sessions, session, input) {
2909
3210
  }
2910
3211
  return sessions.inFlight.agentId === input.agentId
2911
3212
  && sessions.inFlight.runId === input.runId
2912
- && sessions.inFlight.receiptId === formatAgentRunReceiptId(input.taskId, input.runId)
3213
+ && sessions.inFlight.receiptId === expectedReceiptId
2913
3214
  && sessions.inFlight.pushedAt === undefined
2914
3215
  && preparedSessionMatches(session, input.agentId, input.adapterId, input.nativeSessionId, input.launchId);
2915
3216
  }
@@ -3158,7 +3459,7 @@ function bindTaskRoleRunInFlight(store, role, run, now) {
3158
3459
  const updated = bindTaskRoleRun(current, {
3159
3460
  agentId,
3160
3461
  runId: run.id,
3161
- receiptId: formatAgentRunReceiptId(role.taskId, run.id)
3462
+ receiptId: agentRunDeliveryReceiptId(run)
3162
3463
  }, now);
3163
3464
  store.saveRoleSessionSet(updated);
3164
3465
  }
@@ -3170,7 +3471,7 @@ function markTaskRoleRunPushedInFlight(store, role, run, now) {
3170
3471
  const updated = markTaskRoleRunPushed(current, {
3171
3472
  agentId: run.effective.agentId,
3172
3473
  runId: run.id,
3173
- receiptId: formatAgentRunReceiptId(role.taskId, run.id)
3474
+ receiptId: agentRunDeliveryReceiptId(run)
3174
3475
  }, now);
3175
3476
  store.saveRoleSessionSet(updated);
3176
3477
  }
@@ -3182,7 +3483,7 @@ function markTaskRoleRunDeliveredInFlight(store, role, run, now) {
3182
3483
  const updated = markTaskRoleRunDelivered(current, {
3183
3484
  agentId: run.effective.agentId,
3184
3485
  runId: run.id,
3185
- receiptId: formatAgentRunReceiptId(role.taskId, run.id)
3486
+ receiptId: agentRunDeliveryReceiptId(run)
3186
3487
  }, now);
3187
3488
  store.saveRoleSessionSet(updated);
3188
3489
  }
@@ -3195,7 +3496,7 @@ function clearTaskRoleRunInFlight(store, role, run, now) {
3195
3496
  const updated = clearTaskRoleRun(current, {
3196
3497
  agentId: run.effective.agentId,
3197
3498
  runId: run.id,
3198
- receiptId: formatAgentRunReceiptId(role.taskId, run.id)
3499
+ receiptId: agentRunDeliveryReceiptId(run)
3199
3500
  }, now);
3200
3501
  store.saveRoleSessionSet(updated);
3201
3502
  }