@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.
- package/dist/cli/commandCatalog.js +3 -7
- package/dist/cli.js +12 -33
- package/dist/commands/executionAuditCommands.js +19 -0
- package/dist/commands/globalRoleCommands.js +70 -0
- package/dist/commands/taskActor.js +3 -2
- package/dist/commands/taskCommands.js +160 -41
- package/dist/commands/taskContextCommand.js +1 -1
- package/dist/commands/taskInputCommands.js +3 -2
- package/dist/commands/taskRoleRuntimeStatus.js +3 -3
- package/dist/context/contextSnapshot.js +228 -0
- package/dist/context/roleSessionContext.js +3 -1
- package/dist/context/runContextContract.js +162 -0
- package/dist/context/runContextPack.js +322 -0
- package/dist/context/sessionBootstrapManifest.js +81 -0
- package/dist/context/sessionProtocolIdentity.js +23 -0
- package/dist/controller/agentRuntimeObserver.js +6 -1
- package/dist/controller/controller.js +4 -3
- package/dist/controller/fileSchedulerStoreAdapter.js +446 -145
- package/dist/controller/jobControl.js +2 -1
- package/dist/controller/runtime.js +83 -0
- package/dist/controller/runtimeHookRunFence.js +6 -2
- package/dist/controller/sessionOwnerReconciliation.js +5 -0
- package/dist/executor/agentAdapter.js +7 -2
- package/dist/executor/agentExecutor.js +23 -0
- package/dist/executor/effectiveLaunch.js +24 -0
- package/dist/executor/executorRegistry.js +7 -1
- package/dist/executor/fileRoleLaunchPlanner.js +73 -27
- package/dist/lifecycle/exactRunTerminalization.js +2 -3
- package/dist/lifecycle/providerErrorClass.js +8 -3
- package/dist/observability/executionAudit.js +87 -2
- package/dist/repository/taskWorkspacePreparer.js +2 -2
- package/dist/run/agentRun.js +101 -16
- package/dist/run/providerRetry.js +167 -56
- package/dist/run/providerRetryConfig.js +5 -1
- package/dist/run/runControlRequest.js +50 -0
- package/dist/runtime/agentDriver.js +47 -0
- package/dist/runtime/agentHost.js +327 -0
- package/dist/runtime/builtinAgentDrivers.js +23 -1
- package/dist/runtime/builtinTranscriptObserver.js +4 -0
- package/dist/runtime/builtinTranscriptUsage.js +2 -0
- package/dist/runtime/exactControlPlane.js +2 -2
- package/dist/runtime/globalProcessExitStore.js +38 -0
- package/dist/runtime/launchBroker.js +95 -0
- package/dist/runtime/processExitObservation.js +60 -0
- package/dist/runtime/runtimeBinding.js +6 -0
- package/dist/runtime/runtimeObservation.js +27 -6
- package/dist/runtime/runtimeProjection.js +6 -3
- package/dist/runtime/runtimeStopReceipt.js +42 -0
- package/dist/runtime/sessionTerminationGuard.js +13 -0
- package/dist/runtime/tmuxAdapters.js +203 -220
- package/dist/scheduler/activeRoleRunDelivery.js +24 -3
- package/dist/scheduler/leaderWakeupProcessor.js +18 -60
- package/dist/scheduler/roleRunLiveness.js +61 -27
- package/dist/storage/migration/productionRegistry.js +264 -0
- package/dist/storage/sqliteSchema.js +23 -2
- package/dist/storage/sqliteStore.js +39 -2
- package/dist/storage/taskStore.js +54 -5
- package/dist/storage/upgrade/recordVersions.js +3 -1
- package/dist/storage/upgrade/sqliteStateMigration.js +10 -0
- package/dist/task/taskRecordReference.js +1 -0
- package/dist/tmux/tmuxManager.js +15 -4
- package/dist/web/assets/client/components.js +1 -1
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +10 -5
- package/skills/yui-operator/SKILL.md +4 -0
- package/skills/yui-reviewer/SKILL.md +4 -0
- package/skills/yui-runtime/SKILL.md +61 -0
- package/skills/yui-worker/SKILL.md +82 -218
- 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 {
|
|
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 (
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
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,
|
|
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:
|
|
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 !==
|
|
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:
|
|
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 !==
|
|
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:
|
|
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
|
|
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
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
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
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2118
|
+
note: `episode-exhausted-${retryDecision.reason}`,
|
|
2119
|
+
consecutiveFailures: String(run.providerRetry?.consecutiveFailures ?? 0),
|
|
2120
|
+
dispatchedRetries: String(run.providerRetry?.dispatchedRetries ?? 0)
|
|
2000
2121
|
}, now);
|
|
2001
|
-
|
|
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 =
|
|
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:
|
|
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
|
-
|
|
2040
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
//
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
:
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
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
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
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
|
-
|
|
2217
|
-
|
|
2421
|
+
}
|
|
2422
|
+
store.saveAgentRun(withProviderRetry(run, deferred));
|
|
2218
2423
|
return;
|
|
2219
2424
|
}
|
|
2220
|
-
//
|
|
2221
|
-
//
|
|
2222
|
-
// re-pushes the exact same input
|
|
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
|
-
|
|
2455
|
-
|
|
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 !==
|
|
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
|
|
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 ===
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
3499
|
+
receiptId: agentRunDeliveryReceiptId(run)
|
|
3199
3500
|
}, now);
|
|
3200
3501
|
store.saveRoleSessionSet(updated);
|
|
3201
3502
|
}
|