effect-agent 0.1.0-beta.124 → 0.1.0-beta.126

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 (80) hide show
  1. package/dist/{Records-D88mgvyS.d.mts → Admin-C34cVKlT.d.mts} +2908 -3941
  2. package/dist/{AgentRuntime-BUxDdLpw.d.mts → AgentRuntime-DEDvMkAR.d.mts} +2 -2
  3. package/dist/Digest-U69rDHuO.d.mts +22 -0
  4. package/dist/{DurableAgentRuntime-B-9w7jDt.mjs → DurableAgentRuntime-BydyN_n-.mjs} +89 -15
  5. package/dist/DurableAgentRuntime-BydyN_n-.mjs.map +1 -0
  6. package/dist/{EventSource-CnlZ3Kma.d.mts → EventSource-Bac0SxKL.d.mts} +3 -3
  7. package/dist/{PreparedInputAdmission-BQLGldl-.d.mts → PreparedInputAdmission-DcQGQ0Yp.d.mts} +4 -4
  8. package/dist/Records-B5GFWCpn.d.mts +1104 -0
  9. package/dist/{Schedule-BIOHA6RR.d.mts → Schedule-CtYREFnz.d.mts} +2 -2
  10. package/dist/{Subagent-DCrB7an7.mjs → Subagent-DJeZDrbD.mjs} +6 -2
  11. package/dist/Subagent-DJeZDrbD.mjs.map +1 -0
  12. package/dist/{Subagent-CNik9RiA.d.mts → Subagent-DRFyQxdb.d.mts} +21 -4
  13. package/dist/{SubagentHost-4WB8Hj0C.d.mts → SubagentHost-DQjHFGqH.d.mts} +10 -2
  14. package/dist/{Subscription-CRteOKRf.d.mts → Subscription-Bk2Smmqc.d.mts} +34 -3
  15. package/dist/{SubscriptionInput-oNcwTPti.d.mts → SubscriptionInput-ekbJz3pF.d.mts} +3 -3
  16. package/dist/capabilities/Subagent.d.mts +1 -1
  17. package/dist/capabilities/Subagent.mjs +1 -1
  18. package/dist/durable/ActivityStore.d.mts +1 -1
  19. package/dist/durable/Admin.d.mts +1 -1
  20. package/dist/durable/AgentRegistration.d.mts +1 -1
  21. package/dist/durable/Certification.d.mts +1 -1
  22. package/dist/durable/CommittedActivity.d.mts +3 -1
  23. package/dist/durable/Digest.d.mts +1 -1
  24. package/dist/durable/DurableAgentRuntime.d.mts +1 -1
  25. package/dist/durable/DurableAgentRuntime.mjs +1 -1
  26. package/dist/durable/EventSource.d.mts +1 -1
  27. package/dist/durable/GitHubWorkflowSource.d.mts +3 -3
  28. package/dist/durable/MessageDelivery.d.mts +82 -41
  29. package/dist/durable/MessageDeliveryStoreConformance.d.mts +23 -1
  30. package/dist/durable/PersistentHistory.d.mts +1 -1
  31. package/dist/durable/PreparedInputAdmission.d.mts +1 -1
  32. package/dist/durable/Records.d.mts +2 -2
  33. package/dist/durable/Records.mjs +12 -2
  34. package/dist/durable/Records.mjs.map +1 -1
  35. package/dist/durable/Recovery.d.mts +1 -1
  36. package/dist/durable/RunJournal.d.mts +1 -1
  37. package/dist/durable/Schedule.d.mts +1 -1
  38. package/dist/durable/ScheduleStoreConformance.d.mts +1 -1
  39. package/dist/durable/ScheduleTransition.d.mts +1 -1
  40. package/dist/durable/Scheduling.d.mts +3 -2
  41. package/dist/durable/SqlStorageV2Upgrade.d.mts +2 -2
  42. package/dist/durable/SqlSubscriptionStore.d.mts +2 -2
  43. package/dist/durable/SqlThreadNativeReads.d.mts +2 -1
  44. package/dist/durable/SqlThreadNativeReads.mjs +1 -1
  45. package/dist/durable/SqlThreadNativeReads.mjs.map +1 -1
  46. package/dist/durable/SubmissionLedger.d.mts +1 -1
  47. package/dist/durable/SubmissionLedgerConformance.d.mts +2 -1
  48. package/dist/durable/SubmissionStatus.d.mts +1 -1
  49. package/dist/durable/Subscription.d.mts +1 -1
  50. package/dist/durable/SubscriptionInput.d.mts +1 -1
  51. package/dist/durable/SubscriptionStoreConformance.d.mts +1 -1
  52. package/dist/durable/SubscriptionTools.d.mts +4 -3
  53. package/dist/durable/SubscriptionTransition.d.mts +1 -1
  54. package/dist/durable/Subscriptions.d.mts +6 -5
  55. package/dist/durable/ThreadContextHistory.d.mts +1 -1
  56. package/dist/durable/ThreadContextHistoryProjection.d.mts +1 -1
  57. package/dist/durable/ThreadInvariants.d.mts +2 -1
  58. package/dist/durable/ThreadProjection.d.mts +1 -1
  59. package/dist/durable/ThreadProjectionMaintenance.d.mts +1 -1
  60. package/dist/durable/ThreadStore.d.mts +1 -1
  61. package/dist/durable/ThreadStoreConformance.d.mts +1 -1
  62. package/dist/durable/ThreadStoreConformance.mjs +10 -1
  63. package/dist/durable/ThreadStoreConformance.mjs.map +1 -1
  64. package/dist/durable/WorkerHost.d.mts +9 -2
  65. package/dist/durable/WorkerHost.mjs.map +1 -1
  66. package/dist/engine/AgentRuntime.d.mts +1 -1
  67. package/dist/engine/SubagentHost.d.mts +1 -1
  68. package/dist/engine/SubagentHost.mjs.map +1 -1
  69. package/dist/index.d.mts +12 -10
  70. package/dist/index.mjs +2 -2
  71. package/package.json +1 -1
  72. package/src/capabilities/internal/subagent-background.ts +37 -3
  73. package/src/durable/Records.ts +13 -1
  74. package/src/durable/SqlThreadNativeReads.ts +1 -1
  75. package/src/durable/ThreadStoreConformance.ts +28 -0
  76. package/src/durable/WorkerHost.ts +14 -2
  77. package/src/durable/internal/worker-host.ts +284 -9
  78. package/src/engine/SubagentHost.ts +9 -0
  79. package/dist/DurableAgentRuntime-B-9w7jDt.mjs.map +0 -1
  80. package/dist/Subagent-DCrB7an7.mjs.map +0 -1
@@ -71,6 +71,7 @@ import {
71
71
  WorkerInputRequested,
72
72
  WorkerStopRequested,
73
73
  WorkerOrigin,
74
+ WorkerContinuation,
74
75
  WorkerOriginRecorded,
75
76
  WorkerInputCompleted,
76
77
  WorkerReportPrepared,
@@ -292,6 +293,7 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
292
293
  origin: WorkerOrigin,
293
294
  principal: Principal,
294
295
  ) {
296
+ yield* verifyContinuation(origin);
295
297
  if (origin.budgetScope !== "worker-run") return;
296
298
  // Independence changes accounting ownership, never delegation generations.
297
299
  if (origin.depth !== 1) return yield* failure("start", "denied");
@@ -300,6 +302,7 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
300
302
  source: origin.source,
301
303
  principal,
302
304
  worker: origin.worker,
305
+ ...(origin.continuationOf === undefined ? {} : { continuationOf: origin.continuationOf }),
303
306
  policy: origin.policy,
304
307
  budget: origin.budget,
305
308
  });
@@ -321,6 +324,170 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
321
324
  );
322
325
  });
323
326
 
327
+ const completedContinuation = Effect.fn("WorkerHost.completedContinuation")(function* (
328
+ source: WorkerContext["source"],
329
+ worker: WorkerRef,
330
+ targetAgentId: AgentId,
331
+ delegationId: WorkerRef["delegationId"],
332
+ expected?: WorkerContinuation,
333
+ ) {
334
+ const selected = yield* decode(WorkerRef, worker, "start");
335
+
336
+ if (
337
+ selected.targetAgentId !== targetAgentId ||
338
+ selected.delegationId !== delegationId ||
339
+ selected.threadId === source.threadId ||
340
+ deps.ledger.inspectWorker === undefined
341
+ )
342
+ return yield* failure("start", "worker-mismatch");
343
+ const sourceState = yield* readWorkerState(source.threadId, "start");
344
+
345
+ if (
346
+ sourceState.records.some(
347
+ ({ record }) =>
348
+ record.payload._tag === "WorkerStopRequested" &&
349
+ record.payload.command.worker.threadId === selected.threadId,
350
+ )
351
+ )
352
+ return yield* failure("start", "denied");
353
+
354
+ const first = sourceState.records.find(
355
+ ({ record }) => record.recordId === firstWorkerInputRecordId(selected),
356
+ )?.record.payload;
357
+
358
+ const origin = first?._tag === "WorkerInputRequested" ? first.admission.origin : undefined;
359
+
360
+ if (
361
+ origin === undefined ||
362
+ origin.lifecycle !== "assignment" ||
363
+ !Schema.toEquivalence(WorkerRef)(origin.worker, selected) ||
364
+ origin.source.threadId !== source.threadId ||
365
+ origin.source.agentId !== source.agentId ||
366
+ first?._tag !== "WorkerInputRequested" ||
367
+ first.admission.messageId !== origin.firstMessageId
368
+ )
369
+ return yield* failure("start", "worker-mismatch");
370
+
371
+ const recorded = Option.getOrUndefined(
372
+ yield* exactRecord(selected.threadId, workerOriginRecordId(selected.threadId), "start"),
373
+ )?.record.payload;
374
+
375
+ if (recorded?._tag !== "WorkerOriginRecorded" || !sameOrigin(recorded.origin, origin))
376
+ return yield* failure("start", "worker-mismatch");
377
+
378
+ const control = yield* deps.ledger
379
+ .inspectWorker(selected.threadId)
380
+ .pipe(Effect.mapError(storageFailure("start")));
381
+
382
+ if (control.terminal !== "completed" || !control.stopped || control.active !== null)
383
+ return yield* failure("start", "denied");
384
+
385
+ const tail = yield* deps.store
386
+ .inspectTail(ThreadTailRequest.make({ threadId: selected.threadId }))
387
+ .pipe(Effect.mapError(storageFailure("start")));
388
+
389
+ const execution = yield* deps.store
390
+ .read({
391
+ threadId: selected.threadId,
392
+ selection: {
393
+ _tag: "WorkerExecution",
394
+ expectedTailSequence: tail.tailSequence,
395
+ expectedTailDigest: tail.tailDigest,
396
+ },
397
+ page: { limit: 2 },
398
+ })
399
+ .pipe(Stream.runCollect, Effect.mapError(storageFailure("start")));
400
+
401
+ const run = execution.find(({ record }) => record.payload._tag === "RunStarted")?.record
402
+ .payload;
403
+
404
+ if (run?._tag !== "RunStarted") return yield* failure("start", "corrupt");
405
+
406
+ const input = Option.getOrUndefined(
407
+ yield* getRunInput({ threadId: selected.threadId, runId: run.runId }).pipe(
408
+ Effect.provideService(ThreadStore, deps.store),
409
+ Effect.mapError(storageFailure("start")),
410
+ ),
411
+ )?.record.payload;
412
+
413
+ if (input?._tag !== "UserInputRecorded" || input.submissionId === undefined)
414
+ return yield* failure("start", "corrupt");
415
+
416
+ const found = yield* deps.ledger
417
+ .lookup(SubmissionLookupById.make({ submissionId: input.submissionId }))
418
+ .pipe(Effect.mapError(storageFailure("start")));
419
+
420
+ const row = Option.getOrUndefined(found);
421
+
422
+ const settlement = Option.getOrUndefined(
423
+ yield* exactRecord(
424
+ selected.threadId,
425
+ submissionSettlementRecordId(input.submissionId),
426
+ "start",
427
+ ),
428
+ )?.record.payload;
429
+
430
+ if (
431
+ row === undefined ||
432
+ row.threadId !== selected.threadId ||
433
+ row.state !== "settled" ||
434
+ row.settledOutcome !== "completed" ||
435
+ row.workerAdmission === undefined ||
436
+ !sameOrigin(row.workerAdmission.origin, origin) ||
437
+ settlement?._tag !== "SubmissionSettled" ||
438
+ settlement.outcome !== "completed" ||
439
+ settlement.runDisposition !== "completed" ||
440
+ settlement.finishReason !== undefined ||
441
+ settlement.runId !== run.runId ||
442
+ settlement.submissionId !== row.submissionId ||
443
+ settlement.receiptId !== row.receiptId
444
+ )
445
+ return yield* failure("start", "denied");
446
+
447
+ const evidence = WorkerContinuation.make({
448
+ worker: selected,
449
+ receipt: Receipt.make({
450
+ threadId: row.threadId,
451
+ submissionId: row.submissionId,
452
+ receiptId: row.receiptId,
453
+ queueSequence: row.queueSequence,
454
+ }),
455
+ settlementId: settlement.settlementId,
456
+ });
457
+
458
+ if (expected !== undefined && !Schema.toEquivalence(WorkerContinuation)(expected, evidence))
459
+ return yield* failure("start", "worker-mismatch");
460
+
461
+ return { evidence, origin };
462
+ });
463
+
464
+ const verifyContinuation = Effect.fn("WorkerHost.verifyContinuation")(function* (
465
+ origin: WorkerOrigin,
466
+ ) {
467
+ if (origin.continuationOf === undefined) return;
468
+
469
+ const previous = yield* completedContinuation(
470
+ origin.source,
471
+ origin.continuationOf.worker,
472
+ origin.worker.targetAgentId,
473
+ origin.worker.delegationId,
474
+ origin.continuationOf,
475
+ );
476
+
477
+ if (
478
+ origin.worker.threadId === previous.origin.worker.threadId ||
479
+ origin.lifecycle !== "assignment" ||
480
+ !samePolicy(origin.policy, previous.origin.policy) ||
481
+ !Schema.toEquivalence(WorkerOrigin.fields.budget)(origin.budget, previous.origin.budget) ||
482
+ !Schema.toEquivalence(SubagentGrant)(origin.grant, previous.origin.grant) ||
483
+ origin.budgetScope !== previous.origin.budgetScope ||
484
+ origin.depth !== previous.origin.depth ||
485
+ origin.toolCallAllowance !== previous.origin.toolCallAllowance ||
486
+ origin.expiresAtMillis > previous.origin.expiresAtMillis
487
+ )
488
+ return yield* failure("start", "denied");
489
+ });
490
+
324
491
  const readIdentity = Effect.fn("WorkerHost.readIdentity")(function* (
325
492
  threadId: ThreadId,
326
493
  operation: WorkerError["operation"],
@@ -885,6 +1052,8 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
885
1052
  retainedFrameworkInput: boolean,
886
1053
  ): Effect.fn.Return<void, WorkerError> {
887
1054
  const origin = admission.origin;
1055
+
1056
+ yield* verifyContinuation(origin);
888
1057
  const id = workerInputRecordId(admission.messageId);
889
1058
 
890
1059
  for (let attempt = 0; attempt < 16; attempt++) {
@@ -895,6 +1064,16 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
895
1064
  );
896
1065
 
897
1066
  const current = source.current;
1067
+
1068
+ if (
1069
+ origin.continuationOf !== undefined &&
1070
+ current.records.some(
1071
+ ({ record }) =>
1072
+ record.payload._tag === "WorkerStopRequested" &&
1073
+ record.payload.command.worker.threadId === origin.continuationOf?.worker.threadId,
1074
+ )
1075
+ )
1076
+ return yield* failure("start", "denied");
898
1077
  const rows = requests(current.records);
899
1078
  const existing = current.records.find(({ record }) => record.recordId === id)?.record.payload;
900
1079
 
@@ -955,6 +1134,9 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
955
1134
  ? {
956
1135
  ...targetRequest,
957
1136
  _tag: "InitialInput",
1137
+ ...(origin.continuationOf === undefined
1138
+ ? {}
1139
+ : { continuationOf: origin.continuationOf }),
958
1140
  input,
959
1141
  inputDigest,
960
1142
  ...(source.submission === undefined ? {} : { sourceSubmission: source.submission }),
@@ -1057,6 +1239,7 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
1057
1239
  const selectedConcurrency = yield* deps.concurrencyResolver.resolve({
1058
1240
  source: origin.source,
1059
1241
  worker: origin.worker,
1242
+ ...(origin.continuationOf === undefined ? {} : { continuationOf: origin.continuationOf }),
1060
1243
  principal,
1061
1244
  ...(source.submission === undefined ? {} : { sourceSubmission: source.submission }),
1062
1245
  });
@@ -1168,6 +1351,7 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
1168
1351
  yield* validateCompletion(options.messageAdmission, options, agentId, inputDigest);
1169
1352
  }
1170
1353
 
1354
+ yield* verifyContinuation(admission.origin);
1171
1355
  yield* deps.authorizer.authorize({
1172
1356
  sourceThreadId: admission.origin.source.threadId,
1173
1357
  ...(admission.sourceSubmissionId === undefined
@@ -1177,6 +1361,9 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
1177
1361
  operation: "start",
1178
1362
  access: "send",
1179
1363
  worker: admission.origin.worker,
1364
+ ...(admission.origin.continuationOf === undefined
1365
+ ? {}
1366
+ : { continuationOf: admission.origin.continuationOf }),
1180
1367
  });
1181
1368
  yield* authorizeBudget(admission.origin, options.principal);
1182
1369
  if (
@@ -1247,6 +1434,8 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
1247
1434
  );
1248
1435
 
1249
1436
  if (now >= deadlineAtMillis) return yield* failure("followUp", "denied");
1437
+ if (reports[0].reportUpdate !== undefined && !reports[0].reportUpdate(update))
1438
+ return undefined;
1250
1439
  const input = destination.input;
1251
1440
 
1252
1441
  let workerAdmission: WorkerAdmission | undefined;
@@ -1438,7 +1627,11 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
1438
1627
  },
1439
1628
  };
1440
1629
 
1441
- const projection = yield* Effect.suspend(() => descriptor.prepare(report)).pipe(
1630
+ const projection = yield* Effect.suspend(() =>
1631
+ descriptor.reportCompletion?.(report) === false
1632
+ ? Effect.succeed(undefined)
1633
+ : descriptor.prepare(report),
1634
+ ).pipe(
1442
1635
  Effect.timeout(
1443
1636
  Math.min(deps.limits.reportPreparationTimeoutMillis ?? 5_000, deadlineAtMillis - now),
1444
1637
  ),
@@ -1455,6 +1648,8 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
1455
1648
 
1456
1649
  if (projection._tag === "WorkerReportRefused") return projection;
1457
1650
 
1651
+ if (projection.value === undefined) return refused("filtered");
1652
+
1458
1653
  const message = projection.value.message;
1459
1654
 
1460
1655
  if (!Schema.is(WorkerCompletion)(message)) return refused("input");
@@ -1676,6 +1871,7 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
1676
1871
  operation: WorkerError["operation"],
1677
1872
  access: "context" | "read" | "send" | "control",
1678
1873
  worker?: WorkerRef,
1874
+ continuationOf?: WorkerContinuation,
1679
1875
  ) =>
1680
1876
  deps.authorizer.authorize({
1681
1877
  sourceThreadId: context.source.threadId,
@@ -1684,6 +1880,7 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
1684
1880
  operation,
1685
1881
  access,
1686
1882
  ...(worker === undefined ? {} : { worker }),
1883
+ ...(continuationOf === undefined ? {} : { continuationOf }),
1687
1884
  });
1688
1885
 
1689
1886
  const binding = (target: Agent.AnyDefinition, operation: WorkerError["operation"]) =>
@@ -1697,6 +1894,7 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
1697
1894
  const preparedTarget = Effect.fn("WorkerHost.preparedTarget")(function* (
1698
1895
  target: Agent.AnyDefinition,
1699
1896
  encodedInput: unknown,
1897
+ continuationOf?: WorkerContinuation,
1700
1898
  ) {
1701
1899
  const resolved = yield* binding(target, "start");
1702
1900
 
@@ -1713,6 +1911,7 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
1713
1911
 
1714
1912
  const policy = yield* resolveTargetPolicy({
1715
1913
  _tag: "InitialInput",
1914
+ ...(continuationOf === undefined ? {} : { continuationOf }),
1716
1915
  definition: resolved.definition,
1717
1916
  definitions: resolved.digests,
1718
1917
  source: context.source,
@@ -2185,14 +2384,35 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
2185
2384
  };
2186
2385
  }),
2187
2386
  resolveTargetPolicy: Effect.fn("WorkerHost.resolvePreparedTargetPolicy")(function* (request) {
2188
- yield* authorize("start", "send");
2189
-
2190
- return (yield* preparedTarget(request.target, request.encodedInput)).policy;
2387
+ const previous =
2388
+ request.continuationOf === undefined
2389
+ ? undefined
2390
+ : yield* completedContinuation(
2391
+ context.source,
2392
+ request.continuationOf,
2393
+ request.target.id,
2394
+ request.continuationOf.delegationId,
2395
+ );
2396
+
2397
+ yield* authorize("start", "send", undefined, previous?.evidence);
2398
+
2399
+ return (yield* preparedTarget(request.target, request.encodedInput, previous?.evidence))
2400
+ .policy;
2191
2401
  }),
2192
2402
  start: Effect.fn("WorkerHost.start")(function* <E = never, R = never>(
2193
2403
  command: StartWorkerRequest | DeferredStartWorkerRequest<E, R>,
2194
2404
  ): Effect.fn.Return<WorkerStarted, WorkerError | E, R> {
2195
- const principal = yield* authorize("start", "send");
2405
+ const previous =
2406
+ command.continuationOf === undefined
2407
+ ? undefined
2408
+ : yield* completedContinuation(
2409
+ context.source,
2410
+ command.continuationOf,
2411
+ command.target.id,
2412
+ command.delegationId,
2413
+ );
2414
+
2415
+ const principal = yield* authorize("start", "send", undefined, previous?.evidence);
2196
2416
 
2197
2417
  if (context.depth !== 0 && sourceSubmissionId === undefined)
2198
2418
  return yield* failure("start", "denied");
@@ -2219,7 +2439,23 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
2219
2439
 
2220
2440
  const replay = (saved: MessageDeliveryRecord) =>
2221
2441
  Effect.gen(function* () {
2222
- const replayPrincipal = yield* authorize("start", "send");
2442
+ const currentPrevious =
2443
+ command.continuationOf === undefined
2444
+ ? undefined
2445
+ : yield* completedContinuation(
2446
+ context.source,
2447
+ command.continuationOf,
2448
+ command.target.id,
2449
+ command.delegationId,
2450
+ previous?.evidence,
2451
+ );
2452
+
2453
+ const replayPrincipal = yield* authorize(
2454
+ "start",
2455
+ "send",
2456
+ undefined,
2457
+ currentPrevious?.evidence,
2458
+ );
2223
2459
 
2224
2460
  yield* binding(command.target, "start");
2225
2461
  const metadata = saved.envelope.workerAdmission;
@@ -2228,6 +2464,10 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
2228
2464
  const origin = metadata.origin;
2229
2465
 
2230
2466
  if (
2467
+ !Schema.toEquivalence(Schema.UndefinedOr(WorkerContinuation))(
2468
+ origin.continuationOf,
2469
+ currentPrevious?.evidence,
2470
+ ) ||
2231
2471
  origin.firstMessageId !== messageId ||
2232
2472
  metadata.messageId !== messageId ||
2233
2473
  origin.source.threadId !== context.source.threadId ||
@@ -2247,6 +2487,8 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
2247
2487
  )
2248
2488
  return yield* failure("start", "idempotency-conflict");
2249
2489
 
2490
+ yield* verifyContinuation(origin);
2491
+
2250
2492
  return {
2251
2493
  worker: origin.worker,
2252
2494
  delivery: yield* send(
@@ -2268,7 +2510,34 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
2268
2510
  const request =
2269
2511
  "prepare" in command ? { ...(yield* command.prepare), ...command } : command;
2270
2512
 
2271
- const prepared = yield* preparedTarget(request.target, request.encodedInput);
2513
+ const currentPrevious =
2514
+ command.continuationOf === undefined
2515
+ ? undefined
2516
+ : yield* completedContinuation(
2517
+ context.source,
2518
+ command.continuationOf,
2519
+ command.target.id,
2520
+ command.delegationId,
2521
+ previous?.evidence,
2522
+ );
2523
+
2524
+ if (currentPrevious !== undefined) {
2525
+ const preparedPrincipal = yield* authorize(
2526
+ "start",
2527
+ "send",
2528
+ undefined,
2529
+ currentPrevious.evidence,
2530
+ );
2531
+
2532
+ if (preparedPrincipal !== principal) return yield* failure("start", "denied");
2533
+ }
2534
+
2535
+ const prepared = yield* preparedTarget(
2536
+ request.target,
2537
+ request.encodedInput,
2538
+ currentPrevious?.evidence,
2539
+ );
2540
+
2272
2541
  const resolved = prepared.resolved;
2273
2542
 
2274
2543
  const sourcePolicy = Option.getOrElse(
@@ -2302,6 +2571,9 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
2302
2571
  threadId: Schema.decodeSync(ThreadId)(messageId),
2303
2572
  },
2304
2573
  source: context.source,
2574
+ ...(currentPrevious === undefined
2575
+ ? {}
2576
+ : { continuationOf: currentPrevious.evidence }),
2305
2577
  ...(resolved.definition.runDisposition?.workerLifecycle === "assignment"
2306
2578
  ? { lifecycle: "assignment" }
2307
2579
  : {}),
@@ -2313,7 +2585,10 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
2313
2585
  depth: context.depth + 1,
2314
2586
  firstMessageId: messageId,
2315
2587
  createdAtMillis: now,
2316
- expiresAtMillis: now + deps.limits.lifetimeMillis,
2588
+ expiresAtMillis: Math.min(
2589
+ now + deps.limits.lifetimeMillis,
2590
+ currentPrevious?.origin.expiresAtMillis ?? Infinity,
2591
+ ),
2317
2592
  ...(reporting === undefined ? {} : { reporting }),
2318
2593
  ...(request.toolCallAllowance === undefined
2319
2594
  ? {}
@@ -2796,7 +3071,7 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
2796
3071
  ...(sourceSubmissionId === undefined ? {} : { sourceSubmissionId }),
2797
3072
  principal: options.principal,
2798
3073
  operation: "followUp",
2799
- access: "send",
3074
+ access: "report",
2800
3075
  worker: receiving?.origin.worker ?? origin.worker,
2801
3076
  });
2802
3077
 
@@ -3,6 +3,7 @@ import { Context, Schema, Stream } from "effect";
3
3
 
4
4
  import type * as Agent from "../core/Agent.ts";
5
5
  import type { AgentPolicy } from "../core/AgentPolicy.ts";
6
+ import type { Update } from "../core/AgentUpdates.ts";
6
7
  import type * as FailureDiagnostic from "../core/FailureDiagnostic.ts";
7
8
  import type { DelegationId, RunId, SettlementId, ThreadId } from "../core/Identifiers.ts";
8
9
  import type { MessageRef, MessageStatus } from "../core/Messaging.ts";
@@ -26,6 +27,8 @@ import {
26
27
  /** Prepared values cross this port only to be Schema-decoded before durable storage. */
27
28
  export interface StartWorkerRequest {
28
29
  readonly delegationId: DelegationId;
30
+ /** Start a distinct assignment using verified completed predecessor authority. */
31
+ readonly continuationOf?: WorkerRef;
29
32
  readonly target: Agent.AnyDefinition;
30
33
  readonly idempotencyKey: IdempotencyKey;
31
34
  readonly encodedInput: unknown;
@@ -49,6 +52,7 @@ export interface StartWorkerRequest {
49
52
  export interface DeferredStartWorkerRequest<E = never, R = never> extends Pick<
50
53
  StartWorkerRequest,
51
54
  | "delegationId"
55
+ | "continuationOf"
52
56
  | "target"
53
57
  | "idempotencyKey"
54
58
  | "encodedParameters"
@@ -137,6 +141,10 @@ export class WorkerReportPreparationFailure extends Schema.TaggedError<WorkerRep
137
141
  export interface WorkerReporting<E = never, R = never> {
138
142
  readonly delegationId: DelegationId;
139
143
  readonly target: Agent.AnyDefinition;
144
+ /** Pure first-emission selection. False retains the canonical update without parent delivery. */
145
+ readonly reportUpdate?: (update: Update) => boolean;
146
+ /** False retains a filtered report receipt without waking the parent. */
147
+ readonly reportCompletion?: (report: WorkerRunReport) => boolean;
140
148
  readonly prepare: (report: WorkerRunReport) => Effect.Effect<
141
149
  {
142
150
  readonly message: WorkerCompletion;
@@ -162,6 +170,7 @@ export class SubagentHost extends Context.Service<
162
170
  readonly context: Effect.Effect<WorkerContext, WorkerError>;
163
171
  /** Resolve prepared input through host authority; None retains legacy target inheritance. */
164
172
  readonly resolveTargetPolicy: (request: {
173
+ readonly continuationOf?: WorkerRef;
165
174
  readonly target: Agent.AnyDefinition;
166
175
  readonly encodedInput: unknown;
167
176
  }) => Effect.Effect<Option.Option<AgentPolicy>, WorkerError>;