effect-agent 0.1.0-beta.150 → 0.1.0-beta.151
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/{Admin-B8ISZyYf.d.mts → Admin-CKIdJoP8.d.mts} +6 -5
- package/dist/{DurableAgentRuntime-DLa4IwFi.mjs → DurableAgentRuntime-R2LUYnRl.mjs} +67 -81
- package/dist/DurableAgentRuntime-R2LUYnRl.mjs.map +1 -0
- package/dist/{EventSource-CkN05NT3.d.mts → EventSource-CMbXXmwe.d.mts} +2 -2
- package/dist/{PreparedInputAdmission-fctZOTnn.d.mts → PreparedInputAdmission-Bt6rgtVu.d.mts} +3 -3
- package/dist/{Subscription-B16A-48F.d.mts → Subscription-BJAzYNw1.d.mts} +2 -2
- package/dist/{SubscriptionInput-DIU7CiXE.d.mts → SubscriptionInput-CkBJt0AS.d.mts} +2 -2
- package/dist/WorkerAdmission-CJ_oWRuS.mjs +171 -0
- package/dist/WorkerAdmission-CJ_oWRuS.mjs.map +1 -0
- package/dist/durable/Admin.d.mts +1 -1
- package/dist/durable/AgentRegistration.d.mts +1 -1
- package/dist/durable/Certification.d.mts +1 -1
- package/dist/durable/CommittedActivity.d.mts +1 -1
- package/dist/durable/DurableAgentRuntime.d.mts +1 -1
- package/dist/durable/DurableAgentRuntime.mjs +2 -1
- package/dist/durable/EventSource.d.mts +1 -1
- package/dist/durable/GitHubWorkflowSource.d.mts +3 -3
- package/dist/durable/LifecyclePublication.d.mts +1 -1
- package/dist/durable/MessageDelivery.d.mts +41 -41
- package/dist/durable/MessageDeliveryStoreConformance.d.mts +39 -39
- package/dist/durable/PersistentHistory.d.mts +1 -1
- package/dist/durable/PreparedInputAdmission.d.mts +1 -1
- package/dist/durable/Recovery.d.mts +1 -1
- package/dist/durable/RunJournal.d.mts +1 -1
- package/dist/durable/Scheduling.d.mts +1 -1
- package/dist/durable/SubmissionLedger.d.mts +1 -1
- package/dist/durable/SubmissionLedgerConformance.d.mts +1 -1
- package/dist/durable/SubmissionStatus.d.mts +1 -1
- package/dist/durable/Subscription.d.mts +1 -1
- package/dist/durable/SubscriptionInput.d.mts +1 -1
- package/dist/durable/SubscriptionStoreConformance.d.mts +1 -1
- package/dist/durable/SubscriptionTools.d.mts +2 -2
- package/dist/durable/SubscriptionTransition.d.mts +1 -1
- package/dist/durable/Subscriptions.d.mts +5 -5
- package/dist/durable/ThreadContextHistory.d.mts +1 -1
- package/dist/durable/ThreadInvariants.d.mts +1 -1
- package/dist/durable/ThreadProjectionMaintenance.d.mts +1 -1
- package/dist/durable/ThreadStore.d.mts +1 -1
- package/dist/durable/ThreadStoreConformance.d.mts +1 -1
- package/dist/durable/WorkerAdmission.d.mts +172 -0
- package/dist/durable/WorkerAdmission.mjs +2 -0
- package/dist/durable/WorkerHost.d.mts +1 -1
- package/dist/index.d.mts +7 -6
- package/dist/index.mjs +3 -2
- package/package.json +1 -1
- package/src/durable/DurableAgentRuntime.ts +68 -108
- package/src/durable/WorkerAdmission.ts +104 -0
- package/src/durable/internal/thread-initialization.ts +195 -0
- package/src/durable/internal/worker-host.ts +121 -123
- package/src/index.ts +1 -0
- package/dist/DurableAgentRuntime-DLa4IwFi.mjs.map +0 -1
|
@@ -72,7 +72,6 @@ import {
|
|
|
72
72
|
WorkerStopRequested,
|
|
73
73
|
WorkerOrigin,
|
|
74
74
|
WorkerContinuation,
|
|
75
|
-
WorkerOriginRecorded,
|
|
76
75
|
WorkerInputCompleted,
|
|
77
76
|
WorkerReportPrepared,
|
|
78
77
|
WorkerReportRefused,
|
|
@@ -137,6 +136,7 @@ import {
|
|
|
137
136
|
} from "./agent-registration.ts";
|
|
138
137
|
import { lastWorkerReportMessageId } from "./agent-updates.ts";
|
|
139
138
|
import { messageStatus } from "./message-status.ts";
|
|
139
|
+
import { ensureWorkerOrigin } from "./thread-initialization.ts";
|
|
140
140
|
|
|
141
141
|
const failure = (
|
|
142
142
|
operation: WorkerError["operation"],
|
|
@@ -570,14 +570,13 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
|
|
|
570
570
|
payload:
|
|
571
571
|
| WorkerStopRequested
|
|
572
572
|
| WorkerInputRequested
|
|
573
|
-
| WorkerOriginRecorded
|
|
574
573
|
| WorkerInputCompleted
|
|
575
574
|
| WorkerReportPrepared
|
|
576
575
|
| WorkerReportRefused
|
|
577
576
|
| SubtreeBudgetReserved,
|
|
578
577
|
current: Pick<ThreadExport, "tailSequence" | "tailDigest" | "records"> &
|
|
579
578
|
Partial<Pick<ThreadTail, "producerEpoch">>,
|
|
580
|
-
phase: "source" | "
|
|
579
|
+
phase: "source" | "completion" | "subtree" | "report" | "stop",
|
|
581
580
|
acknowledgements: ReadonlyArray<WorkerInputCompleted> = [],
|
|
582
581
|
) {
|
|
583
582
|
const operation = "start";
|
|
@@ -612,11 +611,9 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
|
|
|
612
611
|
createdAt: DateTime.makeUnsafe(
|
|
613
612
|
payload._tag === "WorkerInputRequested"
|
|
614
613
|
? payload.admission.createdAtMillis
|
|
615
|
-
: payload._tag === "
|
|
616
|
-
? payload.
|
|
617
|
-
:
|
|
618
|
-
? payload.completedAtMillis
|
|
619
|
-
: yield* Clock.currentTimeMillis,
|
|
614
|
+
: payload._tag === "WorkerInputCompleted"
|
|
615
|
+
? payload.completedAtMillis
|
|
616
|
+
: yield* Clock.currentTimeMillis,
|
|
620
617
|
),
|
|
621
618
|
deploymentId: deps.deploymentId,
|
|
622
619
|
payload,
|
|
@@ -654,49 +651,55 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
|
|
|
654
651
|
record.payload._tag === "WorkerInputRequested" ? [record.payload] : [],
|
|
655
652
|
);
|
|
656
653
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
rows: ReadonlyArray<WorkerInputRequested>,
|
|
661
|
-
sourceRecords: ThreadExport["records"],
|
|
662
|
-
) {
|
|
663
|
-
const completed = new Set<string>(
|
|
664
|
-
sourceRecords.flatMap(({ record }) =>
|
|
654
|
+
const completedMessageIds = (records: ThreadExport["records"]) =>
|
|
655
|
+
new Set<string>(
|
|
656
|
+
records.flatMap(({ record }) =>
|
|
665
657
|
record.payload._tag === "WorkerInputCompleted" && record.payload.effectsResolved
|
|
666
658
|
? [record.payload.messageId]
|
|
667
659
|
: [],
|
|
668
660
|
),
|
|
669
661
|
);
|
|
670
662
|
|
|
671
|
-
|
|
663
|
+
// Read exact child receipts only under capacity pressure. Collect in source order so
|
|
664
|
+
// parallel foreign reads cannot change the canonical acknowledgement batch order.
|
|
665
|
+
const completedInputs = Effect.fn("WorkerHost.completedInputs")(function* (
|
|
666
|
+
rows: ReadonlyArray<WorkerInputRequested>,
|
|
667
|
+
sourceRecords: ThreadExport["records"],
|
|
668
|
+
) {
|
|
669
|
+
const completed = completedMessageIds(sourceRecords);
|
|
670
|
+
|
|
671
|
+
const receipts = yield* Effect.forEach(
|
|
672
|
+
rows.filter(({ admission }) => !completed.has(admission.messageId)),
|
|
673
|
+
Effect.fnUntraced(function* ({ admission }) {
|
|
674
|
+
const receipt = Option.getOrUndefined(
|
|
675
|
+
yield* getRecord({
|
|
676
|
+
threadId: admission.origin.worker.threadId,
|
|
677
|
+
recordId: Schema.decodeSync(RecordId)(`worker-effects-resolved:${admission.messageId}`),
|
|
678
|
+
}).pipe(
|
|
679
|
+
Effect.provideService(ThreadStore, deps.store),
|
|
680
|
+
// An unmaterialized reservation still consumes capacity.
|
|
681
|
+
Effect.catchTag("ThreadNotMaterialized", () => Effect.succeed(Option.none())),
|
|
682
|
+
Effect.mapError(storageFailure("start")),
|
|
683
|
+
),
|
|
684
|
+
)?.record.payload;
|
|
672
685
|
|
|
673
|
-
|
|
674
|
-
|
|
686
|
+
if (receipt === undefined) return;
|
|
687
|
+
if (
|
|
688
|
+
receipt._tag !== "WorkerInputCompleted" ||
|
|
689
|
+
!receipt.effectsResolved ||
|
|
690
|
+
receipt.messageId !== admission.messageId ||
|
|
691
|
+
receipt.workerThreadId !== admission.origin.worker.threadId
|
|
692
|
+
)
|
|
693
|
+
return yield* failure("start", "corrupt");
|
|
675
694
|
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
}).pipe(
|
|
681
|
-
Effect.provideService(ThreadStore, deps.store),
|
|
682
|
-
// A source reservation can precede child materialization. It still consumes
|
|
683
|
-
// capacity; absence of storage is never a completion acknowledgement.
|
|
684
|
-
Effect.catchTag("ThreadNotMaterialized", () => Effect.succeed(Option.none())),
|
|
685
|
-
Effect.mapError(storageFailure("start")),
|
|
686
|
-
),
|
|
687
|
-
)?.record.payload;
|
|
695
|
+
return receipt;
|
|
696
|
+
}),
|
|
697
|
+
{ concurrency: 8 },
|
|
698
|
+
);
|
|
688
699
|
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
!receipt.effectsResolved ||
|
|
693
|
-
receipt.messageId !== admission.messageId ||
|
|
694
|
-
receipt.workerThreadId !== admission.origin.worker.threadId
|
|
695
|
-
)
|
|
696
|
-
return yield* failure("start", "corrupt");
|
|
697
|
-
completed.add(admission.messageId);
|
|
698
|
-
acknowledgements.push(receipt);
|
|
699
|
-
}
|
|
700
|
+
const acknowledgements = receipts.filter((receipt) => receipt !== undefined);
|
|
701
|
+
|
|
702
|
+
for (const receipt of acknowledgements) completed.add(receipt.messageId);
|
|
700
703
|
|
|
701
704
|
return { completed, acknowledgements };
|
|
702
705
|
});
|
|
@@ -1047,33 +1050,40 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
|
|
|
1047
1050
|
|
|
1048
1051
|
// Include unreconciled reservations conservatively; only canonical completion frees an
|
|
1049
1052
|
// active slot. A worker Thread consumes one slot even when multiple inputs safely join it.
|
|
1050
|
-
const completedWorkers = yield* completedInputs(
|
|
1051
|
-
requests(source.current.records),
|
|
1052
|
-
source.current.records,
|
|
1053
|
-
);
|
|
1054
|
-
|
|
1055
1053
|
const completedAttached = new Set<string>(
|
|
1056
1054
|
source.current.records.flatMap(({ record }) =>
|
|
1057
1055
|
record.payload._tag === "SubagentJoined" ? [record.payload.reservationId] : [],
|
|
1058
1056
|
),
|
|
1059
1057
|
);
|
|
1060
1058
|
|
|
1061
|
-
const
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
row.lifetime === "background"
|
|
1065
|
-
? !completedWorkers.completed.has(row.reservationId)
|
|
1066
|
-
: !completedAttached.has(row.reservationId),
|
|
1067
|
-
)
|
|
1068
|
-
.map((row) => row.childThreadId),
|
|
1059
|
+
const activeLimit = Math.min(
|
|
1060
|
+
caps.maxConcurrentChildren ?? Infinity,
|
|
1061
|
+
source.policy.toolConcurrency,
|
|
1069
1062
|
);
|
|
1070
1063
|
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1064
|
+
const hasSlot = (completed: ReadonlySet<string>, conservative = false) => {
|
|
1065
|
+
const active = new Set(
|
|
1066
|
+
rows
|
|
1067
|
+
.filter((row) =>
|
|
1068
|
+
row.lifetime === "background"
|
|
1069
|
+
? !completed.has(row.reservationId)
|
|
1070
|
+
: !completedAttached.has(row.reservationId),
|
|
1071
|
+
)
|
|
1072
|
+
.map((row) => row.childThreadId),
|
|
1073
|
+
);
|
|
1074
|
+
|
|
1075
|
+
return active.has(payload.childThreadId)
|
|
1076
|
+
? !conservative || active.size <= activeLimit
|
|
1077
|
+
: active.size < activeLimit;
|
|
1078
|
+
};
|
|
1079
|
+
|
|
1080
|
+
const localCompletions = completedMessageIds(source.current.records);
|
|
1081
|
+
|
|
1082
|
+
const completedWorkers = hasSlot(localCompletions, true)
|
|
1083
|
+
? { completed: localCompletions, acknowledgements: [] }
|
|
1084
|
+
: yield* completedInputs(requests(source.current.records), source.current.records);
|
|
1085
|
+
|
|
1086
|
+
if (!hasSlot(completedWorkers.completed)) return yield* failure("start", "capacity");
|
|
1077
1087
|
if (
|
|
1078
1088
|
yield* append(
|
|
1079
1089
|
sourceThreadId,
|
|
@@ -1275,17 +1285,6 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
|
|
|
1275
1285
|
return yield* failure("start", "capacity");
|
|
1276
1286
|
}
|
|
1277
1287
|
|
|
1278
|
-
const completed = yield* completedInputs(rows, current.records);
|
|
1279
|
-
|
|
1280
|
-
const pendingRows = rows.filter((row) => !completed.completed.has(row.admission.messageId));
|
|
1281
|
-
const activeWorkers = new Set(pendingRows.map((row) => row.admission.origin.worker.threadId));
|
|
1282
|
-
|
|
1283
|
-
const pendingOwn = pendingRows.filter(
|
|
1284
|
-
(row) =>
|
|
1285
|
-
row.admission.origin.worker.threadId === origin.worker.threadId &&
|
|
1286
|
-
row.admission.reportKind === admission.reportKind,
|
|
1287
|
-
).length;
|
|
1288
|
-
|
|
1289
1288
|
// Resolve inside the source CAS loop: independently delivered starts must compete
|
|
1290
1289
|
// against one canonical prefix. A conflict repeats both authority and capacity reads.
|
|
1291
1290
|
const selectedConcurrency = yield* deps.concurrencyResolver.resolve({
|
|
@@ -1301,21 +1300,48 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
|
|
|
1301
1300
|
.maxActiveWorkersPerSource
|
|
1302
1301
|
: Infinity;
|
|
1303
1302
|
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1303
|
+
const activeLimit = Math.min(
|
|
1304
|
+
sourceConcurrency,
|
|
1305
|
+
deps.limits.maxActiveWorkersPerSource ?? source.policy.toolConcurrency,
|
|
1306
|
+
independent
|
|
1307
|
+
? Infinity
|
|
1308
|
+
: Math.min(caps.maxConcurrentChildren ?? Infinity, source.policy.toolConcurrency),
|
|
1309
|
+
);
|
|
1310
|
+
|
|
1311
|
+
const hasCapacity = (completed: ReadonlySet<string>, conservative = false) => {
|
|
1312
|
+
const pendingRows = rows.filter((row) => !completed.has(row.admission.messageId));
|
|
1313
|
+
|
|
1314
|
+
const activeWorkers = new Set(
|
|
1315
|
+
pendingRows.map((row) => row.admission.origin.worker.threadId),
|
|
1316
|
+
);
|
|
1317
|
+
|
|
1318
|
+
const pendingOwn = pendingRows.filter(
|
|
1319
|
+
(row) =>
|
|
1320
|
+
row.admission.origin.worker.threadId === origin.worker.threadId &&
|
|
1321
|
+
row.admission.reportKind === admission.reportKind,
|
|
1322
|
+
).length;
|
|
1323
|
+
|
|
1324
|
+
return (
|
|
1325
|
+
pendingOwn <
|
|
1326
|
+
(admission.reportKind === "update"
|
|
1327
|
+
? (deps.limits.maxPendingUpdateInputsPerWorker ?? 32)
|
|
1328
|
+
: deps.limits.maxPendingInputsPerWorker) &&
|
|
1329
|
+
(activeWorkers.has(origin.worker.threadId)
|
|
1330
|
+
? !conservative || activeWorkers.size <= activeLimit
|
|
1331
|
+
: activeWorkers.size < activeLimit)
|
|
1332
|
+
);
|
|
1333
|
+
};
|
|
1334
|
+
|
|
1335
|
+
// Unresolved inputs bound pending work from above. An apparently active destination
|
|
1336
|
+
// could already be idle: after a limit reduction, prove activity before exempting its
|
|
1337
|
+
// follow-up from acquiring a new slot.
|
|
1338
|
+
const localCompletions = completedMessageIds(current.records);
|
|
1339
|
+
|
|
1340
|
+
const completed = hasCapacity(localCompletions, true)
|
|
1341
|
+
? { completed: localCompletions, acknowledgements: [] }
|
|
1342
|
+
: yield* completedInputs(rows, current.records);
|
|
1343
|
+
|
|
1344
|
+
if (!hasCapacity(completed.completed))
|
|
1319
1345
|
return yield* WorkerError.make({ operation: "start", reason: "capacity", retryable: true });
|
|
1320
1346
|
if (!independent)
|
|
1321
1347
|
yield* reserveSubtree(
|
|
@@ -1351,42 +1377,14 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
|
|
|
1351
1377
|
return yield* failure("start", "storage");
|
|
1352
1378
|
});
|
|
1353
1379
|
|
|
1354
|
-
const ensureOrigin =
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
)
|
|
1361
|
-
|
|
1362
|
-
if (existing?._tag === "WorkerOriginRecorded") {
|
|
1363
|
-
if (!sameOrigin(existing.origin, origin)) return yield* failure("start", "worker-mismatch");
|
|
1364
|
-
|
|
1365
|
-
return;
|
|
1366
|
-
}
|
|
1367
|
-
const first = current.records[0]?.record.payload;
|
|
1368
|
-
|
|
1369
|
-
if (
|
|
1370
|
-
first?._tag !== "ThreadCreated" ||
|
|
1371
|
-
first.agentId !== origin.worker.targetAgentId ||
|
|
1372
|
-
!definitionDigestsEqual(first.definitions, origin.targetDigests) ||
|
|
1373
|
-
current.records.some(({ record }) => record.payload._tag === "SubagentLineageRecorded")
|
|
1374
|
-
)
|
|
1375
|
-
return yield* failure("start", "worker-mismatch");
|
|
1376
|
-
if (
|
|
1377
|
-
yield* append(
|
|
1378
|
-
origin.worker.threadId,
|
|
1379
|
-
workerOriginRecordId(origin.worker.threadId),
|
|
1380
|
-
WorkerOriginRecorded.make({ origin }),
|
|
1381
|
-
current,
|
|
1382
|
-
"origin",
|
|
1383
|
-
)
|
|
1384
|
-
)
|
|
1385
|
-
return;
|
|
1386
|
-
}
|
|
1387
|
-
|
|
1388
|
-
return yield* failure("start", "storage");
|
|
1389
|
-
});
|
|
1380
|
+
const ensureOrigin = (origin: WorkerOrigin) =>
|
|
1381
|
+
ensureWorkerOrigin(
|
|
1382
|
+
{ producerId: deps.producerId, deploymentId: deps.deploymentId },
|
|
1383
|
+
origin,
|
|
1384
|
+
).pipe(
|
|
1385
|
+
Effect.provideService(ThreadStore, deps.store),
|
|
1386
|
+
Effect.provideService(DurableRuntimeFailpoint, deps.failpoint),
|
|
1387
|
+
);
|
|
1390
1388
|
|
|
1391
1389
|
const validateAdmission = Effect.fn("WorkerHost.validateAdmission")(function* (
|
|
1392
1390
|
unvalidated: WorkerAdmission,
|
package/src/index.ts
CHANGED
|
@@ -96,3 +96,4 @@ export * as WorkerHost from "./durable/WorkerHost.ts";
|
|
|
96
96
|
export * as ThreadContextHistory from "./durable/ThreadContextHistory.ts";
|
|
97
97
|
export * as ThreadContextHistoryProjection from "./durable/ThreadContextHistoryProjection.ts";
|
|
98
98
|
export * as LifecyclePublication from "./durable/LifecyclePublication.ts";
|
|
99
|
+
export * as WorkerAdmission from "./durable/WorkerAdmission.ts";
|