effect-agent 0.1.0-beta.113 → 0.1.0-beta.115

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 (137) hide show
  1. package/dist/Agent-1DMSO_Fl.mjs.map +1 -1
  2. package/dist/{Agent-DriBcXBN.d.mts → Agent-D9cXqfF3.d.mts} +9 -2
  3. package/dist/{AgentRuntime-DLwPoEVH.mjs → AgentRuntime-Cp1hAb-W.mjs} +8 -4
  4. package/dist/AgentRuntime-Cp1hAb-W.mjs.map +1 -0
  5. package/dist/{AgentRuntime-CWkHuzyt.d.mts → AgentRuntime-DyZv8uu2.d.mts} +6 -6
  6. package/dist/{DurableAgentRuntime-BIrgxwRl.mjs → DurableAgentRuntime-tNrkJZNo.mjs} +254 -162
  7. package/dist/DurableAgentRuntime-tNrkJZNo.mjs.map +1 -0
  8. package/dist/{DurableFailpoint-mx3-nsxU.d.mts → DurableFailpoint-Cnr3wx1L.d.mts} +3 -3
  9. package/dist/{EventSource-ARs56eBq.d.mts → EventSource-DPRdlvhV.d.mts} +3 -3
  10. package/dist/{Messaging-DaZDlBqh.d.mts → Messaging-JYYN3oYS.d.mts} +3 -3
  11. package/dist/{MessagingHost-e1Sbzfao.d.mts → MessagingHost-CqzpxbGz.d.mts} +3 -3
  12. package/dist/{MessagingHost-D89X7ma8.mjs → MessagingHost-DfsbMPvq.mjs} +2 -2
  13. package/dist/{MessagingHost-D89X7ma8.mjs.map → MessagingHost-DfsbMPvq.mjs.map} +1 -1
  14. package/dist/{Output-CAFJTAbP.d.mts → Output-V-6YL1_Z.d.mts} +2 -2
  15. package/dist/{PreparedInputAdmission-_WYg-D0f.d.mts → PreparedInputAdmission-DqQUjA9k.d.mts} +4 -4
  16. package/dist/{Records-BtxMy8xD.d.mts → Records-Byeb70tt.d.mts} +89 -31
  17. package/dist/{RunOptions-BoESJrxw.d.mts → RunOptions-CSBsSj0X.d.mts} +3 -3
  18. package/dist/{Schedule-DGPyXBzK.d.mts → Schedule-krSnBcbU.d.mts} +2 -2
  19. package/dist/{Subagent-D9eYdGTl.mjs → Subagent-Di8EEjWZ.mjs} +67 -105
  20. package/dist/Subagent-Di8EEjWZ.mjs.map +1 -0
  21. package/dist/{Subagent-F72tuaxy.d.mts → Subagent-gw9KD6eb.d.mts} +310 -35
  22. package/dist/{SubagentHost-CmiNTOBZ.d.mts → SubagentHost-P9zB_TfV.d.mts} +24 -18
  23. package/dist/{Subscription-B9VszlF9.d.mts → Subscription-Xnwd42Sm.d.mts} +6 -3
  24. package/dist/{SubscriptionInput-BJWZqCWP.d.mts → SubscriptionInput-CqtJ58vl.d.mts} +3 -3
  25. package/dist/{Worker-T_3RbaKc.mjs → Worker-CfwzMZEK.mjs} +66 -5
  26. package/dist/Worker-CfwzMZEK.mjs.map +1 -0
  27. package/dist/{Worker-Du9dhVnp.d.mts → Worker-fwWkBg12.d.mts} +199 -6
  28. package/dist/{agent-registration-B5CI-Ygk.mjs → agent-registration-CKNsrpwo.mjs} +7 -7
  29. package/dist/agent-registration-CKNsrpwo.mjs.map +1 -0
  30. package/dist/capabilities/Commands.d.mts +1 -1
  31. package/dist/capabilities/Messaging.d.mts +1 -1
  32. package/dist/capabilities/Messaging.mjs +2 -2
  33. package/dist/capabilities/RunHooks.d.mts +1 -1
  34. package/dist/capabilities/Subagent.d.mts +3 -3
  35. package/dist/capabilities/Subagent.mjs +3 -3
  36. package/dist/core/Agent.d.mts +1 -1
  37. package/dist/core/Worker.d.mts +2 -2
  38. package/dist/core/Worker.mjs +2 -2
  39. package/dist/durable/ActivityStore.d.mts +1 -1
  40. package/dist/durable/Admin.d.mts +1 -1
  41. package/dist/durable/AgentRegistration.d.mts +1 -1
  42. package/dist/durable/AgentRegistration.mjs +1 -1
  43. package/dist/durable/Certification.d.mts +2 -2
  44. package/dist/durable/CommittedActivity.d.mts +1 -1
  45. package/dist/durable/Digest.d.mts +1 -1
  46. package/dist/durable/DurableAgentRuntime.d.mts +1 -1
  47. package/dist/durable/DurableAgentRuntime.mjs +1 -1
  48. package/dist/durable/DurableFailpoint.d.mts +1 -1
  49. package/dist/durable/DurableFailpoint.mjs +4 -0
  50. package/dist/durable/DurableFailpoint.mjs.map +1 -1
  51. package/dist/durable/DurableFailpointTestControl.d.mts +1 -1
  52. package/dist/durable/EventSource.d.mts +1 -1
  53. package/dist/durable/GitHubWorkflowSource.d.mts +3 -3
  54. package/dist/durable/MessageDelivery.d.mts +20 -3
  55. package/dist/durable/MessageDelivery.mjs +12 -4
  56. package/dist/durable/MessageDelivery.mjs.map +1 -1
  57. package/dist/durable/MessageDeliveryStoreConformance.d.mts +41 -39
  58. package/dist/durable/MessageDeliveryStoreConformance.mjs +1 -1
  59. package/dist/durable/PersistentHistory.d.mts +1 -1
  60. package/dist/durable/PreparedInputAdmission.d.mts +1 -1
  61. package/dist/durable/Records.d.mts +2 -2
  62. package/dist/durable/Records.mjs +12 -5
  63. package/dist/durable/Records.mjs.map +1 -1
  64. package/dist/durable/Recovery.d.mts +1 -1
  65. package/dist/durable/RunJournal.d.mts +1 -1
  66. package/dist/durable/Schedule.d.mts +1 -1
  67. package/dist/durable/ScheduleStoreConformance.d.mts +1 -1
  68. package/dist/durable/ScheduleTransition.d.mts +1 -1
  69. package/dist/durable/Scheduling.d.mts +2 -2
  70. package/dist/durable/SqlMessageDeliveryStore.mjs +7 -1
  71. package/dist/durable/SqlMessageDeliveryStore.mjs.map +1 -1
  72. package/dist/durable/SqlStorageV2Upgrade.d.mts +2 -2
  73. package/dist/durable/SqlSubscriptionStore.d.mts +2 -2
  74. package/dist/durable/SqlThreadNativeReads.d.mts +5 -1
  75. package/dist/durable/SqlThreadNativeReads.mjs +6 -0
  76. package/dist/durable/SqlThreadNativeReads.mjs.map +1 -1
  77. package/dist/durable/SubmissionLedger.d.mts +2 -2
  78. package/dist/durable/SubmissionLedger.mjs +34 -4
  79. package/dist/durable/SubmissionLedger.mjs.map +1 -1
  80. package/dist/durable/SubmissionLedgerConformance.d.mts +1 -1
  81. package/dist/durable/SubmissionLedgerConformance.mjs +899 -706
  82. package/dist/durable/SubmissionLedgerConformance.mjs.map +1 -1
  83. package/dist/durable/SubmissionStatus.d.mts +1 -1
  84. package/dist/durable/Subscription.d.mts +1 -1
  85. package/dist/durable/Subscription.mjs +1 -1
  86. package/dist/durable/SubscriptionInput.d.mts +1 -1
  87. package/dist/durable/SubscriptionInput.mjs +1 -1
  88. package/dist/durable/SubscriptionStoreConformance.d.mts +1 -1
  89. package/dist/durable/SubscriptionTools.d.mts +3 -3
  90. package/dist/durable/SubscriptionTransition.d.mts +1 -1
  91. package/dist/durable/Subscriptions.d.mts +5 -5
  92. package/dist/durable/ThreadContextHistory.d.mts +1 -1
  93. package/dist/durable/ThreadContextHistoryProjection.d.mts +1 -1
  94. package/dist/durable/ThreadInvariants.d.mts +1 -1
  95. package/dist/durable/ThreadProjection.d.mts +1 -1
  96. package/dist/durable/ThreadProjectionMaintenance.d.mts +1 -1
  97. package/dist/durable/ThreadStore.d.mts +1 -1
  98. package/dist/durable/ThreadStore.mjs +5 -0
  99. package/dist/durable/ThreadStore.mjs.map +1 -1
  100. package/dist/durable/ThreadStoreConformance.d.mts +1 -1
  101. package/dist/durable/WorkerHost.d.mts +3 -3
  102. package/dist/durable/WorkerHost.mjs +1 -1
  103. package/dist/engine/AgentRuntime.d.mts +1 -1
  104. package/dist/engine/AgentRuntime.mjs +1 -1
  105. package/dist/engine/MessagingHost.d.mts +1 -1
  106. package/dist/engine/MessagingHost.mjs +1 -1
  107. package/dist/engine/Output.d.mts +1 -1
  108. package/dist/engine/RunOptions.d.mts +1 -1
  109. package/dist/engine/SubagentHost.d.mts +2 -2
  110. package/dist/engine/SubagentHost.mjs +5 -1
  111. package/dist/engine/SubagentHost.mjs.map +1 -1
  112. package/dist/index.d.mts +16 -16
  113. package/dist/index.mjs +5 -5
  114. package/package.json +1 -1
  115. package/src/capabilities/Subagent.ts +2 -1
  116. package/src/capabilities/internal/subagent-background.ts +86 -57
  117. package/src/capabilities/internal/subagent-reporting.ts +3 -128
  118. package/src/core/Agent.ts +8 -1
  119. package/src/core/Worker.ts +58 -2
  120. package/src/durable/DurableAgentRuntime.ts +2 -0
  121. package/src/durable/DurableFailpoint.ts +4 -0
  122. package/src/durable/MessageDelivery.ts +17 -2
  123. package/src/durable/Records.ts +12 -2
  124. package/src/durable/SqlMessageDeliveryStore.ts +15 -1
  125. package/src/durable/SqlThreadNativeReads.ts +14 -0
  126. package/src/durable/SubmissionLedger.ts +48 -1
  127. package/src/durable/SubmissionLedgerConformance.ts +260 -42
  128. package/src/durable/ThreadStore.ts +5 -0
  129. package/src/durable/internal/agent-registration.ts +13 -48
  130. package/src/durable/internal/worker-host.ts +490 -341
  131. package/src/engine/SubagentHost.ts +37 -13
  132. package/src/engine/internal/agent-runtime.ts +17 -2
  133. package/dist/AgentRuntime-DLwPoEVH.mjs.map +0 -1
  134. package/dist/DurableAgentRuntime-BIrgxwRl.mjs.map +0 -1
  135. package/dist/Subagent-D9eYdGTl.mjs.map +0 -1
  136. package/dist/Worker-T_3RbaKc.mjs.map +0 -1
  137. package/dist/agent-registration-B5CI-Ygk.mjs.map +0 -1
@@ -19,6 +19,8 @@ import { MessageRef } from "../../core/Messaging.ts";
19
19
  import { IdempotencyKey, Receipt } from "../../core/Receipt.ts";
20
20
  import { SubagentDelegationCaps, SubagentGrant } from "../../core/SubagentContract.ts";
21
21
  import {
22
+ type WorkerSummary,
23
+ type WorkerStarted,
22
24
  WorkerCompletion,
23
25
  FrameworkMessage,
24
26
  WorkerUpdate,
@@ -26,9 +28,12 @@ import {
26
28
  WorkerHistoryEntry,
27
29
  type WorkerContext,
28
30
  WorkerRef,
31
+ WorkerStop,
29
32
  } from "../../core/Worker.ts";
30
33
  import {
31
34
  type SubagentHost,
35
+ type DeferredStartWorkerRequest,
36
+ type StartWorkerRequest,
32
37
  type WorkerObservation,
33
38
  type WorkerReceiptRequest,
34
39
  type WorkerRunReport,
@@ -62,6 +67,7 @@ import {
62
67
  RecordId,
63
68
  WorkerAdmission,
64
69
  WorkerInputRequested,
70
+ WorkerStopRequested,
65
71
  WorkerOrigin,
66
72
  WorkerOriginRecorded,
67
73
  WorkerInputCompleted,
@@ -86,13 +92,14 @@ import {
86
92
  } from "../Schedule.ts";
87
93
  import {
88
94
  SubmissionLedger,
95
+ WorkerLedgerState,
89
96
  LedgerError,
90
97
  AbortCommand,
91
98
  type AbortIntent,
92
99
  type Principal,
93
100
  type Settlement,
94
101
  SubmissionLookupById,
95
- SubmissionLookupByKey,
102
+ submissionSettlementRecordId,
96
103
  type SubmissionSnapshot,
97
104
  } from "../SubmissionLedger.ts";
98
105
  import type { SubmissionStatus } from "../SubmissionStatus.ts";
@@ -100,6 +107,7 @@ import { PreparedInput } from "../Subscription.ts";
100
107
  import {
101
108
  ThreadStore,
102
109
  getRecord,
110
+ getRunInput,
103
111
  readWorkerState as readNativeWorkerState,
104
112
  FencedAppendRequest,
105
113
  ThreadExportRequest,
@@ -352,6 +360,7 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
352
360
  threadId: ThreadId,
353
361
  id: string,
354
362
  payload:
363
+ | WorkerStopRequested
355
364
  | WorkerInputRequested
356
365
  | WorkerOriginRecorded
357
366
  | WorkerInputCompleted
@@ -359,7 +368,7 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
359
368
  | WorkerReportRefused
360
369
  | SubtreeBudgetReserved,
361
370
  current: Pick<ThreadExport, "tailSequence" | "tailDigest" | "records">,
362
- phase: "source" | "origin" | "completion" | "subtree" | "report",
371
+ phase: "source" | "origin" | "completion" | "subtree" | "report" | "stop",
363
372
  acknowledgements: ReadonlyArray<WorkerInputCompleted> = [],
364
373
  ) {
365
374
  const operation = "start";
@@ -487,50 +496,36 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
487
496
  const report = reports[0];
488
497
 
489
498
  if (report === undefined) return undefined;
490
- if (
491
- reports.length !== 1 ||
492
- !Object.is(report.target, target.definition) ||
493
- (report.mode !== "standard" && !Object.is(report.input, source.definition.input)) ||
494
- (report.destination !== undefined && !Object.is(report.destination.target, source.definition))
495
- )
499
+ if (reports.length !== 1 || !Object.is(report.target, target.definition))
496
500
  return yield* failure("start", "declaration-unavailable");
497
501
 
498
- let returnAddress: NonNullable<WorkerOrigin["reporting"]>["returnAddress"];
502
+ const submission = authority.submission;
499
503
 
500
- if (report.mode === "standard") {
501
- const submission = authority.submission;
502
-
503
- if (
504
- submission === undefined ||
505
- (authority.depth !== 0 && submission.workerAdmission === undefined)
506
- )
507
- return yield* failure("start", "denied");
504
+ if (
505
+ submission === undefined ||
506
+ (authority.depth !== 0 && submission.workerAdmission === undefined)
507
+ )
508
+ return yield* failure("start", "denied");
508
509
 
509
- const retained =
510
- submission.workerAdmission === undefined
511
- ? undefined
512
- : yield* Schema.encodeEffect(WorkerAdmission)(submission.workerAdmission).pipe(
513
- Effect.flatMap(Schema.decodeEffect(PersistedJson)),
514
- Effect.mapError(storageFailure("start")),
515
- );
510
+ const retained =
511
+ submission.workerAdmission === undefined
512
+ ? undefined
513
+ : yield* Schema.encodeEffect(WorkerAdmission)(submission.workerAdmission).pipe(
514
+ Effect.flatMap(Schema.decodeEffect(PersistedJson)),
515
+ Effect.mapError(storageFailure("start")),
516
+ );
516
517
 
517
- returnAddress = {
518
+ return {
519
+ sourceDigests: source.digests,
520
+ mode: "standard",
521
+ returnAddress: {
518
522
  input: submission.inputPayload,
519
523
  principal: submission.principal,
520
524
  policy: authority.policy,
521
525
  depth: authority.depth,
522
526
  ...(authority.grant === undefined ? {} : { grant: authority.grant }),
523
527
  ...(retained === undefined ? {} : { workerAdmission: retained }),
524
- };
525
- }
526
-
527
- return {
528
- sourceDigests: source.digests,
529
- ...(report.mode === undefined ? {} : { mode: report.mode }),
530
- ...(returnAddress === undefined ? {} : { returnAddress }),
531
- ...(report.destination === undefined
532
- ? {}
533
- : { destinationDelegationId: report.destination.delegationId }),
528
+ },
534
529
  };
535
530
  });
536
531
 
@@ -1232,7 +1227,7 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
1232
1227
  sourceBinding === undefined ||
1233
1228
  targetBinding === undefined ||
1234
1229
  reports.length !== 1 ||
1235
- reports[0]?.mode !== "standard" ||
1230
+ reports[0] === undefined ||
1236
1231
  reports[0].target !== targetBinding.definition ||
1237
1232
  !definitionDigestsEqual(submission.agentDigests, origin.targetDigests)
1238
1233
  )
@@ -1351,6 +1346,9 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
1351
1346
  const refused = (reason: WorkerReportRefused["reason"]) =>
1352
1347
  WorkerReportRefused.make({ runId, messageId, reason });
1353
1348
 
1349
+ // Retired custom origins remain readable; already prepared envelopes bypass preparation.
1350
+ if (intent.mode !== "standard") return refused("declaration-unavailable");
1351
+
1354
1352
  const selected = yield* deps.ledger
1355
1353
  .lookup(SubmissionLookupById.make({ submissionId: host.submissionId }))
1356
1354
  .pipe(Effect.mapError(storageFailure("inspect")));
@@ -1366,84 +1364,18 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
1366
1364
  const hostSubmission = selected.value;
1367
1365
  const hostAdmission = selected.value.workerAdmission;
1368
1366
 
1369
- // Standard reports use only the accepted return address. Application-mapped
1370
- // projections retain their explicit source-context contract.
1371
- const source = yield* Effect.gen(function* () {
1372
- if (intent.mode === "standard") {
1373
- const address = intent.returnAddress;
1367
+ const address = intent.returnAddress;
1374
1368
 
1375
- if (address === undefined) return yield* failure("inspect", "corrupt");
1369
+ if (address === undefined) return yield* failure("inspect", "corrupt");
1376
1370
 
1377
- const retained =
1378
- address.workerAdmission === undefined
1379
- ? undefined
1380
- : yield* Schema.decodeUnknownEffect(WorkerAdmission)(address.workerAdmission).pipe(
1381
- Effect.mapError((cause) => failure("inspect", "corrupt", cause)),
1382
- );
1383
-
1384
- return { ...address, admission: retained, sourceSubmissionId: undefined };
1385
- }
1386
-
1387
- const first = Option.getOrUndefined(
1388
- yield* exactRecord(
1389
- origin.source.threadId,
1390
- workerInputRecordId(origin.firstMessageId),
1391
- "inspect",
1392
- ),
1393
- )?.record.payload;
1394
-
1395
- if (
1396
- first?._tag !== "WorkerInputRequested" ||
1397
- first.admission.messageId !== origin.firstMessageId ||
1398
- !sameOrigin(first.admission.origin, origin)
1399
- )
1400
- return yield* failure("inspect", "corrupt");
1401
-
1402
- const authority = yield* sourceAuthority(
1403
- origin.source.threadId,
1404
- first.admission.sourceSubmissionId,
1405
- );
1406
-
1407
- const recorded = authority.current.records.find(
1408
- ({ record }) => record.payload._tag === "WorkerOriginRecorded",
1409
- )?.record.payload;
1410
-
1411
- let retained: WorkerAdmission | undefined;
1412
- let principal = hostSubmission.principal;
1413
-
1414
- if (recorded?._tag === "WorkerOriginRecorded") {
1415
- if (hostAdmission.sourceSubmissionId === undefined)
1416
- return yield* failure("inspect", "denied");
1417
-
1418
- const snapshot = yield* deps.ledger
1419
- .lookup(
1420
- SubmissionLookupById.make({
1421
- submissionId: hostAdmission.sourceSubmissionId,
1422
- }),
1423
- )
1424
- .pipe(Effect.mapError(storageFailure("inspect")));
1425
-
1426
- if (
1427
- Option.isNone(snapshot) ||
1428
- snapshot.value.threadId !== origin.source.threadId ||
1429
- snapshot.value.workerAdmission === undefined ||
1430
- !sameOrigin(snapshot.value.workerAdmission.origin, recorded.origin)
1431
- )
1432
- return yield* failure("inspect", "denied");
1433
- retained = snapshot.value.workerAdmission;
1434
- principal = snapshot.value.principal;
1435
- }
1371
+ const retainedAdmission =
1372
+ address.workerAdmission === undefined
1373
+ ? undefined
1374
+ : yield* Schema.decodeUnknownEffect(WorkerAdmission)(address.workerAdmission).pipe(
1375
+ Effect.mapError((cause) => failure("inspect", "corrupt", cause)),
1376
+ );
1436
1377
 
1437
- return {
1438
- policy: authority.policy,
1439
- depth: authority.depth,
1440
- grant: authority.grant,
1441
- input: authority.submission?.inputPayload,
1442
- admission: retained,
1443
- principal,
1444
- sourceSubmissionId: first.admission.sourceSubmissionId,
1445
- };
1446
- });
1378
+ const source = { ...address, admission: retainedAdmission };
1447
1379
 
1448
1380
  const sourceBinding = currentBinding(origin.source.agentId);
1449
1381
  const targetBinding = currentBinding(origin.worker.targetAgentId);
@@ -1460,29 +1392,13 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
1460
1392
  targetBinding === undefined ||
1461
1393
  descriptor === undefined ||
1462
1394
  reports.length !== 1 ||
1463
- !Object.is(descriptor.target, targetBinding.definition) ||
1464
- (descriptor.mode !== "standard" &&
1465
- !Object.is(descriptor.input, sourceBinding.definition.input)) ||
1466
- descriptor.mode !== intent.mode ||
1467
- descriptor.destination?.delegationId !== intent.destinationDelegationId ||
1468
- (descriptor.destination !== undefined &&
1469
- !Object.is(descriptor.destination.target, sourceBinding.definition))
1395
+ !Object.is(descriptor.target, targetBinding.definition)
1470
1396
  )
1471
1397
  return refused("declaration-unavailable");
1472
1398
 
1473
1399
  const sourceOrigin = source.admission?.origin;
1474
1400
 
1475
1401
  if (source.depth !== 0 && sourceOrigin === undefined) return refused("destination");
1476
- if (
1477
- descriptor.mode !== "standard" &&
1478
- sourceOrigin !== undefined &&
1479
- (descriptor.destination === undefined ||
1480
- descriptor.destination.delegationId !== sourceOrigin.worker.delegationId ||
1481
- descriptor.destination.target.id !== sourceOrigin.worker.targetAgentId)
1482
- )
1483
- return refused("destination");
1484
- if (sourceOrigin === undefined && descriptor.destination !== undefined)
1485
- return refused("destination");
1486
1402
  const now = yield* Clock.currentTimeMillis;
1487
1403
 
1488
1404
  const deadlineAtMillis = Math.min(
@@ -1537,32 +1453,20 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
1537
1453
 
1538
1454
  if (projection._tag === "WorkerReportRefused") return projection;
1539
1455
 
1540
- if (descriptor.mode === "standard") {
1541
- const message = projection.value.message;
1456
+ const message = projection.value.message;
1542
1457
 
1543
- if (source.input === undefined || !Schema.is(WorkerCompletion)(message))
1544
- return refused("input");
1545
- if (
1546
- !Schema.toEquivalence(WorkerRef)(message.report.worker, origin.worker) ||
1547
- !Schema.toEquivalence(Receipt)(message.report.receipt, report.observation.receipt) ||
1548
- message.report.runId !== runId ||
1549
- message.report.settlementId !== host.settlementId ||
1550
- message.report.outcome !== host.outcome ||
1551
- message.budgetExhausted !== report.observation.budgetExhausted
1552
- )
1553
- return refused("input");
1554
- } else if (projection.value.message !== undefined) return refused("input");
1458
+ if (!Schema.is(WorkerCompletion)(message)) return refused("input");
1459
+ if (
1460
+ !Schema.toEquivalence(WorkerRef)(message.report.worker, origin.worker) ||
1461
+ !Schema.toEquivalence(Receipt)(message.report.receipt, report.observation.receipt) ||
1462
+ message.report.runId !== runId ||
1463
+ message.report.settlementId !== host.settlementId ||
1464
+ message.report.outcome !== host.outcome ||
1465
+ message.budgetExhausted !== report.observation.budgetExhausted
1466
+ )
1467
+ return refused("input");
1555
1468
 
1556
- const reportInput =
1557
- descriptor.mode === "standard" ? source.input : projection.value.encodedInput;
1558
-
1559
- const validated = yield* (
1560
- descriptor.mode === "standard"
1561
- ? Schema.decodeUnknownEffect(PersistedJson)(reportInput)
1562
- : Schema.decodeEffect(Schema.toEncoded(sourceBinding.definition.input))(reportInput).pipe(
1563
- Effect.flatMap(() => Schema.decodeUnknownEffect(PersistedJson)(reportInput)),
1564
- )
1565
- ).pipe(Effect.option);
1469
+ const validated = yield* Schema.decodeEffect(PersistedJson)(source.input).pipe(Effect.option);
1566
1470
 
1567
1471
  if (Option.isNone(validated)) return refused("input");
1568
1472
  const input = validated.value;
@@ -1579,9 +1483,9 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
1579
1483
 
1580
1484
  if (retained === undefined) return refused("destination");
1581
1485
 
1582
- const parameters = yield* Schema.decodeUnknownEffect(PersistedJson)(
1583
- descriptor.mode === "standard" ? retained.parameters : projection.value.encodedParameters,
1584
- ).pipe(Effect.option);
1486
+ const parameters = yield* Schema.decodeEffect(PersistedJson)(retained.parameters).pipe(
1487
+ Effect.option,
1488
+ );
1585
1489
 
1586
1490
  if (Option.isNone(parameters)) return refused("destination");
1587
1491
  workerAdmission = {
@@ -1595,56 +1499,21 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
1595
1499
  };
1596
1500
  }
1597
1501
 
1598
- // Reporting remains owned by the original allocator, even when a later input joins
1599
- // or starts another Run. Nested reports authorize the enclosing worker's source owner.
1600
- const authorizationSourceSubmissionId =
1601
- sourceOrigin === undefined
1602
- ? source.sourceSubmissionId
1603
- : workerAdmission?.sourceSubmissionId;
1604
-
1605
- // Standard framework messages are reauthorized by validateCompletion at
1606
- // their destination. Persisting an outbound report does not admit input there.
1607
- const authorized =
1608
- intent.mode === "standard"
1609
- ? Option.some(principal)
1610
- : yield* deps.authorizer
1611
- .authorize({
1612
- sourceThreadId: sourceOrigin?.source.threadId ?? origin.source.threadId,
1613
- ...(authorizationSourceSubmissionId === undefined
1614
- ? {}
1615
- : { sourceSubmissionId: authorizationSourceSubmissionId }),
1616
- principal,
1617
- operation: "followUp",
1618
- access: "send",
1619
- worker: sourceOrigin?.worker ?? origin.worker,
1620
- })
1621
- .pipe(
1622
- Effect.map(Option.some),
1623
- Effect.catchTag("WorkerError", (error) =>
1624
- error.reason === "storage" || error.reason === "unavailable"
1625
- ? Effect.fail(error)
1626
- : Effect.succeed(Option.none<Principal>()),
1627
- ),
1628
- );
1629
-
1630
- if (Option.isNone(authorized)) return refused("denied");
1631
-
1502
+ // The destination reauthorizes standard messages through validateCompletion.
1632
1503
  const envelope: PreparedInput = {
1633
1504
  schemaVersion: 1,
1634
1505
  threadId: origin.source.threadId,
1635
- deliveryPrincipal: authorized.value,
1506
+ deliveryPrincipal: principal,
1636
1507
  agentId: origin.source.agentId,
1637
1508
  definitions: intent.sourceDigests,
1638
1509
  input,
1639
1510
  inputDigest,
1640
1511
  admissionKey: messageId,
1641
1512
  authorization: { policyId: "worker-report", decisionId: messageId },
1642
- ...(projection.value.message === undefined
1643
- ? {}
1644
- : { messageAdmission: projection.value.message }),
1513
+ messageAdmission: message,
1645
1514
  ...(workerAdmission === undefined
1646
1515
  ? {}
1647
- : { workerAdmission: { ...workerAdmission, deliveryPrincipal: authorized.value } }),
1516
+ : { workerAdmission: { ...workerAdmission, deliveryPrincipal: principal } }),
1648
1517
  };
1649
1518
 
1650
1519
  const encoded = yield* Schema.encodeEffect(PreparedInput)(envelope).pipe(
@@ -1868,7 +1737,28 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
1868
1737
 
1869
1738
  const origin = first?._tag === "WorkerInputRequested" ? first.admission.origin : undefined;
1870
1739
 
1871
- if (origin === undefined) return yield* failure(operation, "not-found");
1740
+ if (origin === undefined) {
1741
+ const retained = Option.isNone(deps.deliveries)
1742
+ ? null
1743
+ : yield* deps.deliveries.value
1744
+ .get({
1745
+ ownerThreadId: context.source.threadId,
1746
+ messageId: Schema.decodeSync(IdempotencyKey)(worker.threadId),
1747
+ })
1748
+ .pipe(Effect.mapError(storageFailure(operation)));
1749
+
1750
+ const pendingOrigin = retained?.envelope.workerAdmission?.origin;
1751
+
1752
+ if (pendingOrigin === undefined) return yield* failure(operation, "not-found");
1753
+ if (
1754
+ !Schema.toEquivalence(WorkerRef)(pendingOrigin.worker, worker) ||
1755
+ pendingOrigin.firstMessageId !== retained?.key.messageId ||
1756
+ pendingOrigin.source.threadId !== context.source.threadId
1757
+ )
1758
+ return yield* failure(operation, "worker-mismatch");
1759
+
1760
+ return pendingOrigin;
1761
+ }
1872
1762
  if (
1873
1763
  !Schema.toEquivalence(WorkerRef)(origin.worker, worker) ||
1874
1764
  first?._tag !== "WorkerInputRequested" ||
@@ -2091,65 +1981,194 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
2091
1981
 
2092
1982
  const summarize = Effect.fn("WorkerHost.summarize")(function* (
2093
1983
  origin: WorkerOrigin,
2094
- all: ReadonlyArray<WorkerInputRequested>,
2095
1984
  operation: "list" | "inspect",
2096
- ) {
1985
+ ): Effect.fn.Return<WorkerSummary, WorkerError> {
2097
1986
  yield* authorize(operation, "read", origin.worker);
2098
- let latestReceipt: Receipt | null = null;
2099
- let active = false;
2100
- let starting = false;
1987
+ if (deps.ledger.inspectWorker === undefined || Option.isNone(deps.deliveries))
1988
+ return yield* failure(operation, "unavailable");
1989
+ const threadId = origin.worker.threadId;
1990
+ const deliveries = deps.deliveries.value;
2101
1991
 
2102
- for (const row of all) {
2103
- if (row.admission.origin.worker.threadId !== origin.worker.threadId) continue;
1992
+ const pending = () =>
1993
+ deliveries
1994
+ .list({ ownerThreadId: context.source.threadId, pendingWorker: threadId, limit: 1 })
1995
+ .pipe(
1996
+ Effect.mapError(storageFailure(operation)),
1997
+ Effect.map((page) => page.items[0]),
1998
+ );
2104
1999
 
2105
- const delivery = Option.isNone(deps.deliveries)
2106
- ? null
2107
- : yield* deps.deliveries.value
2108
- .get({
2109
- ownerThreadId: context.source.threadId,
2110
- messageId: row.admission.messageId,
2111
- })
2112
- .pipe(Effect.mapError(storageFailure(operation)));
2000
+ const tail = () =>
2001
+ deps.store.inspectTail(ThreadTailRequest.make({ threadId })).pipe(
2002
+ Effect.catchTag("ThreadNotMaterialized", () => Effect.succeed(undefined)),
2003
+ Effect.mapError(storageFailure(operation)),
2004
+ );
2113
2005
 
2114
- const principal = row.admission.deliveryPrincipal ?? delivery?.envelope.deliveryPrincipal;
2115
-
2116
- const found =
2117
- principal === undefined
2118
- ? Option.none()
2119
- : yield* deps.ledger
2120
- .lookup(
2121
- SubmissionLookupByKey.make({
2122
- threadId: origin.worker.threadId,
2123
- principal,
2124
- idempotencyKey: row.admission.messageId,
2125
- }),
2126
- )
2127
- .pipe(Effect.mapError(storageFailure(operation)));
2006
+ const receipt = (row: SubmissionSnapshot): Receipt => ({
2007
+ threadId: row.threadId,
2008
+ submissionId: row.submissionId,
2009
+ receiptId: row.receiptId,
2010
+ queueSequence: row.queueSequence,
2011
+ });
2128
2012
 
2129
- if (Option.isNone(found)) {
2130
- if (delivery?.status !== "refused") starting = true;
2131
- continue;
2013
+ const input = (row: SubmissionSnapshot) => {
2014
+ if (row.workerAdmission === undefined || !sameOrigin(row.workerAdmission.origin, origin))
2015
+ return failure(operation, "corrupt");
2016
+
2017
+ return Effect.succeed({ receipt: receipt(row), messageId: row.workerAdmission.messageId });
2018
+ };
2019
+
2020
+ const submission = (submissionId: SubmissionId) =>
2021
+ deps.ledger.lookup(SubmissionLookupById.make({ submissionId })).pipe(
2022
+ Effect.mapError(storageFailure(operation)),
2023
+ Effect.flatMap((row) =>
2024
+ Option.isNone(row) ? failure(operation, "corrupt") : Effect.succeed(row.value),
2025
+ ),
2026
+ );
2027
+
2028
+ // Native pages and exact lookups only. Retry changing watermarks instead of combining
2029
+ // acceptance, application and completion facts that never coexisted.
2030
+ for (let attempt = 0; attempt < 16; attempt++) {
2031
+ const delivery = yield* pending();
2032
+
2033
+ const control = yield* deps.ledger
2034
+ .inspectWorker(threadId)
2035
+ .pipe(Effect.mapError(storageFailure(operation)));
2036
+
2037
+ const current = yield* tail();
2038
+
2039
+ const records =
2040
+ current === undefined
2041
+ ? []
2042
+ : yield* deps.store
2043
+ .read({
2044
+ threadId,
2045
+ selection: {
2046
+ _tag: "WorkerExecution",
2047
+ expectedTailSequence: current.tailSequence,
2048
+ expectedTailDigest: current.tailDigest,
2049
+ },
2050
+ page: { limit: 2 },
2051
+ })
2052
+ .pipe(
2053
+ Stream.runCollect,
2054
+ Effect.mapError(storageFailure(operation)),
2055
+ Effect.catch((error) =>
2056
+ Effect.gen(function* () {
2057
+ // An unavailable recheck cannot justify discarding the original error.
2058
+ const after = yield* tail().pipe(Effect.orElseSucceed(() => current));
2059
+
2060
+ if (
2061
+ after !== undefined &&
2062
+ (current.tailSequence !== after.tailSequence ||
2063
+ current.tailDigest !== after.tailDigest)
2064
+ )
2065
+ return undefined;
2066
+
2067
+ return yield* error;
2068
+ }),
2069
+ ),
2070
+ );
2071
+
2072
+ if (records === undefined) continue;
2073
+
2074
+ const applied = records.find((entry) => entry.record.payload._tag === "UserInputRecorded");
2075
+
2076
+ const started = records.find((entry) => entry.record.payload._tag === "RunStarted")?.record
2077
+ .payload;
2078
+
2079
+ const acceptedInput = control.latest === null ? null : yield* input(control.latest);
2080
+ let appliedInput: WorkerSummary["appliedInput"] = null;
2081
+ let run: WorkerSummary["run"] = null;
2082
+
2083
+ if (applied?.record.payload._tag === "UserInputRecorded") {
2084
+ const value = applied.record.payload;
2085
+
2086
+ if (value.submissionId === undefined || value.runId === undefined)
2087
+ return yield* failure(operation, "corrupt");
2088
+ appliedInput = {
2089
+ ...(yield* input(yield* submission(value.submissionId))),
2090
+ runId: value.runId,
2091
+ sequence: applied.sequence,
2092
+ };
2132
2093
  }
2133
- active ||= found.value.state !== "settled";
2134
- if (latestReceipt === null || found.value.queueSequence > latestReceipt.queueSequence) {
2135
- latestReceipt = {
2136
- threadId: found.value.threadId,
2137
- submissionId: found.value.submissionId,
2138
- receiptId: found.value.receiptId,
2139
- queueSequence: found.value.queueSequence,
2094
+ if (started?._tag === "RunStarted") {
2095
+ const initial = Option.getOrUndefined(
2096
+ yield* getRunInput({ threadId, runId: started.runId }).pipe(
2097
+ Effect.provideService(ThreadStore, deps.store),
2098
+ Effect.mapError(storageFailure(operation)),
2099
+ ),
2100
+ )?.record.payload;
2101
+
2102
+ if (initial?._tag !== "UserInputRecorded" || initial.submissionId === undefined)
2103
+ return yield* failure(operation, "corrupt");
2104
+ const host = yield* input(yield* submission(initial.submissionId));
2105
+
2106
+ const terminal = Option.getOrUndefined(
2107
+ yield* exactRecord(
2108
+ threadId,
2109
+ submissionSettlementRecordId(initial.submissionId),
2110
+ operation,
2111
+ ),
2112
+ )?.record.payload;
2113
+
2114
+ if (
2115
+ terminal !== undefined &&
2116
+ (terminal._tag !== "SubmissionSettled" || terminal.runId !== started.runId)
2117
+ )
2118
+ return yield* failure(operation, "corrupt");
2119
+
2120
+ run = {
2121
+ runId: started.runId,
2122
+ hostReceipt: host.receipt,
2123
+ outcome: terminal?.outcome ?? null,
2124
+ disposition: terminal?.runDisposition ?? null,
2140
2125
  };
2141
2126
  }
2127
+
2128
+ const after = yield* deps.ledger
2129
+ .inspectWorker(threadId)
2130
+ .pipe(Effect.mapError(storageFailure(operation)));
2131
+
2132
+ const afterTail = yield* tail();
2133
+ const afterDelivery = yield* pending();
2134
+
2135
+ if (
2136
+ !Schema.toEquivalence(WorkerLedgerState)(control, after) ||
2137
+ current?.tailSequence !== afterTail?.tailSequence ||
2138
+ current?.tailDigest !== afterTail?.tailDigest ||
2139
+ delivery?.key.messageId !== afterDelivery?.key.messageId ||
2140
+ delivery?.version !== afterDelivery?.version
2141
+ )
2142
+ continue;
2143
+
2144
+ return {
2145
+ worker: origin.worker,
2146
+ latestReceipt: acceptedInput?.receipt ?? null,
2147
+ state:
2148
+ control.terminal ??
2149
+ (control.stopped
2150
+ ? control.active === null
2151
+ ? "stopped"
2152
+ : "stopping"
2153
+ : control.active !== null
2154
+ ? "active"
2155
+ : delivery !== undefined
2156
+ ? "starting"
2157
+ : "idle"),
2158
+ acceptedInput,
2159
+ appliedInput,
2160
+ run,
2161
+ pendingDelivery:
2162
+ delivery === undefined ? null : yield* deliveryStatus(delivery, operation),
2163
+ watermark: {
2164
+ canonicalSequence: current?.tailSequence ?? 0,
2165
+ acceptedQueueSequence: control.latest?.queueSequence ?? 0,
2166
+ pendingDeliveryVersion: delivery?.version ?? null,
2167
+ },
2168
+ };
2142
2169
  }
2143
2170
 
2144
- return {
2145
- worker: origin.worker,
2146
- latestReceipt,
2147
- state: active
2148
- ? ("active" as const)
2149
- : starting || latestReceipt === null
2150
- ? ("starting" as const)
2151
- : ("idle" as const),
2152
- };
2171
+ return yield* failure(operation, "storage");
2153
2172
  });
2154
2173
 
2155
2174
  return {
@@ -2168,104 +2187,181 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
2168
2187
 
2169
2188
  return (yield* preparedTarget(request.target, request.encodedInput)).policy;
2170
2189
  }),
2171
- start: Effect.fn("WorkerHost.start")(function* (request) {
2190
+ start: Effect.fn("WorkerHost.start")(function* <E = never, R = never>(
2191
+ command: StartWorkerRequest | DeferredStartWorkerRequest<E, R>,
2192
+ ): Effect.fn.Return<WorkerStarted, WorkerError | E, R> {
2172
2193
  const principal = yield* authorize("start", "send");
2173
2194
 
2174
2195
  if (context.depth !== 0 && sourceSubmissionId === undefined)
2175
2196
  return yield* failure("start", "denied");
2176
- const prepared = yield* preparedTarget(request.target, request.encodedInput);
2177
- const resolved = prepared.resolved;
2178
- const sourcePolicy = Option.getOrElse(prepared.source.policyOverride, () => context.policy);
2179
2197
 
2180
- const grant = yield* Schema.decodeUnknownEffect(SubagentGrant)(request.encodedGrant).pipe(
2198
+ const grant = yield* Schema.decodeUnknownEffect(SubagentGrant)(command.encodedGrant).pipe(
2181
2199
  Effect.mapError((cause) => failure("start", "corrupt", cause)),
2182
2200
  );
2183
2201
 
2202
+ const parameters = yield* decode(PersistedJson, command.encodedParameters, "start");
2203
+
2184
2204
  const messageId = yield* messageIdFor([
2185
2205
  context.source.threadId,
2186
- request.delegationId,
2187
- request.idempotencyKey,
2206
+ command.delegationId,
2207
+ command.idempotencyKey,
2188
2208
  ]);
2189
2209
 
2190
2210
  if (Option.isNone(deps.deliveries)) return yield* failure("start", "unavailable");
2211
+ const deliveries = deps.deliveries.value;
2191
2212
 
2192
- const existing = yield* deps.deliveries.value
2193
- .get({ ownerThreadId: context.source.threadId, messageId })
2194
- .pipe(Effect.mapError(storageFailure("start")));
2213
+ const retained = () =>
2214
+ deliveries
2215
+ .get({ ownerThreadId: context.source.threadId, messageId })
2216
+ .pipe(Effect.mapError(storageFailure("start")));
2195
2217
 
2196
- const now = yield* Clock.currentTimeMillis;
2197
- const previousOrigin = existing?.envelope.workerAdmission?.origin;
2198
- const sourceBinding = prepared.source.binding;
2218
+ const replay = (saved: MessageDeliveryRecord) =>
2219
+ Effect.gen(function* () {
2220
+ const replayPrincipal = yield* authorize("start", "send");
2221
+
2222
+ yield* binding(command.target, "start");
2223
+ const metadata = saved.envelope.workerAdmission;
2224
+
2225
+ if (metadata === undefined) return yield* failure("start", "idempotency-conflict");
2226
+ const origin = metadata.origin;
2227
+
2228
+ if (
2229
+ origin.firstMessageId !== messageId ||
2230
+ metadata.messageId !== messageId ||
2231
+ origin.source.threadId !== context.source.threadId ||
2232
+ origin.worker.delegationId !== command.delegationId ||
2233
+ origin.worker.targetAgentId !== command.target.id ||
2234
+ !sameJson(metadata.parameters, parameters) ||
2235
+ !Schema.toEquivalence(SubagentGrant)(origin.grant, grant) ||
2236
+ origin.budgetScope !== command.budgetScope ||
2237
+ origin.depth !== context.depth + 1 ||
2238
+ (!("prepare" in command) &&
2239
+ (!samePolicy(origin.policy, command.policy) ||
2240
+ !Schema.toEquivalence(WorkerOrigin.fields.budget)(
2241
+ origin.budget,
2242
+ command.budget,
2243
+ ) ||
2244
+ origin.toolCallAllowance !== command.toolCallAllowance))
2245
+ )
2246
+ return yield* failure("start", "idempotency-conflict");
2247
+
2248
+ return {
2249
+ worker: origin.worker,
2250
+ delivery: yield* send(
2251
+ origin,
2252
+ messageId,
2253
+ "prepare" in command ? saved.envelope.input : command.encodedInput,
2254
+ parameters,
2255
+ replayPrincipal,
2256
+ "start",
2257
+ ),
2258
+ };
2259
+ });
2199
2260
 
2200
- if (sourceBinding === undefined) return yield* failure("start", "declaration-unavailable");
2261
+ const existing = yield* retained();
2201
2262
 
2202
- const reporting =
2203
- previousOrigin === undefined
2204
- ? yield* reportIntent(sourceBinding, resolved, request.delegationId, prepared.source)
2205
- : previousOrigin.reporting;
2263
+ if (existing !== null) return yield* replay(existing);
2206
2264
 
2207
- if (reporting?.mode === "standard" && sourceSubmissionId === undefined)
2208
- return yield* failure("start", "denied");
2265
+ return yield* Effect.gen(function* () {
2266
+ const request =
2267
+ "prepare" in command ? { ...(yield* command.prepare), ...command } : command;
2268
+
2269
+ const prepared = yield* preparedTarget(request.target, request.encodedInput);
2270
+ const resolved = prepared.resolved;
2271
+
2272
+ const sourcePolicy = Option.getOrElse(
2273
+ prepared.source.policyOverride,
2274
+ () => context.policy,
2275
+ );
2276
+
2277
+ const now = yield* Clock.currentTimeMillis;
2278
+ const sourceBinding = prepared.source.binding;
2279
+
2280
+ if (sourceBinding === undefined)
2281
+ return yield* failure("start", "declaration-unavailable");
2209
2282
 
2210
- const origin = yield* decode(
2211
- WorkerOrigin,
2212
- {
2213
- worker: {
2214
- schemaVersion: 1,
2215
- delegationId: request.delegationId,
2216
- targetAgentId: request.target.id,
2217
- threadId: Schema.decodeSync(ThreadId)(messageId),
2283
+ const reporting = yield* reportIntent(
2284
+ sourceBinding,
2285
+ resolved,
2286
+ request.delegationId,
2287
+ prepared.source,
2288
+ );
2289
+
2290
+ if (reporting?.mode === "standard" && sourceSubmissionId === undefined)
2291
+ return yield* failure("start", "denied");
2292
+
2293
+ const origin = yield* decode(
2294
+ WorkerOrigin,
2295
+ {
2296
+ worker: {
2297
+ schemaVersion: 1,
2298
+ delegationId: request.delegationId,
2299
+ targetAgentId: request.target.id,
2300
+ threadId: Schema.decodeSync(ThreadId)(messageId),
2301
+ },
2302
+ source: context.source,
2303
+ ...(resolved.definition.runDisposition?.workerLifecycle === "assignment"
2304
+ ? { lifecycle: "assignment" }
2305
+ : {}),
2306
+ targetDigests: resolved.digests,
2307
+ policy: request.policy,
2308
+ budget: request.budget,
2309
+ ...(request.budgetScope === undefined ? {} : { budgetScope: request.budgetScope }),
2310
+ grant,
2311
+ depth: context.depth + 1,
2312
+ firstMessageId: messageId,
2313
+ createdAtMillis: now,
2314
+ expiresAtMillis: now + deps.limits.lifetimeMillis,
2315
+ ...(reporting === undefined ? {} : { reporting }),
2316
+ ...(request.toolCallAllowance === undefined
2317
+ ? {}
2318
+ : { toolCallAllowance: request.toolCallAllowance }),
2218
2319
  },
2219
- source: existing?.envelope.workerAdmission?.origin.source ?? context.source,
2220
- targetDigests: previousOrigin?.targetDigests ?? resolved.digests,
2221
- policy: request.policy,
2222
- budget: request.budget,
2223
- ...(request.budgetScope === undefined ? {} : { budgetScope: request.budgetScope }),
2224
- grant,
2225
- depth: context.depth + 1,
2226
- firstMessageId: messageId,
2227
- createdAtMillis: existing?.envelope.workerAdmission?.origin.createdAtMillis ?? now,
2228
- expiresAtMillis:
2229
- existing?.envelope.workerAdmission?.origin.expiresAtMillis ??
2230
- now + deps.limits.lifetimeMillis,
2231
- ...(reporting === undefined ? {} : { reporting }),
2232
- ...(request.toolCallAllowance === undefined
2233
- ? {}
2234
- : { toolCallAllowance: request.toolCallAllowance }),
2235
- },
2236
- "start",
2237
- );
2320
+ "start",
2321
+ );
2238
2322
 
2239
- yield* authorizeBudget(origin, principal);
2323
+ yield* authorizeBudget(origin, principal);
2240
2324
 
2241
- const targetPolicy = Option.isSome(prepared.policy)
2242
- ? prepared.policy.value
2243
- : origin.budgetScope === "worker-run"
2244
- ? resolved.definition.policy
2245
- : AgentPolicy.resolve(
2246
- resolved.definition.policyOverrides ?? resolved.definition.policy,
2247
- sourcePolicy,
2248
- );
2325
+ const targetPolicy = Option.isSome(prepared.policy)
2326
+ ? prepared.policy.value
2327
+ : origin.budgetScope === "worker-run"
2328
+ ? resolved.definition.policy
2329
+ : AgentPolicy.resolve(
2330
+ resolved.definition.policyOverrides ?? resolved.definition.policy,
2331
+ sourcePolicy,
2332
+ );
2249
2333
 
2250
- if (
2251
- !withinPolicy(
2252
- origin,
2253
- origin.budgetScope === "worker-run" ? targetPolicy : sourcePolicy,
2254
- targetPolicy,
2334
+ if (
2335
+ !withinPolicy(
2336
+ origin,
2337
+ origin.budgetScope === "worker-run" ? targetPolicy : sourcePolicy,
2338
+ targetPolicy,
2339
+ )
2255
2340
  )
2256
- )
2257
- return yield* failure("start", "capacity");
2341
+ return yield* failure("start", "capacity");
2258
2342
 
2259
- const delivery = yield* send(
2260
- origin,
2261
- messageId,
2262
- request.encodedInput,
2263
- request.encodedParameters,
2264
- principal,
2265
- "start",
2266
- );
2343
+ const delivery = yield* send(
2344
+ origin,
2345
+ messageId,
2346
+ request.encodedInput,
2347
+ request.encodedParameters,
2348
+ principal,
2349
+ "start",
2350
+ );
2267
2351
 
2268
- return { worker: origin.worker, delivery };
2352
+ return { worker: origin.worker, delivery };
2353
+ }).pipe(
2354
+ Effect.catch((error) =>
2355
+ Effect.gen(function* () {
2356
+ if (!("prepare" in command)) return yield* Effect.fail(error);
2357
+ // A concurrent first writer may have retained the command during preparation or insert.
2358
+ // Only its exact declared arguments permit replay; absent proof preserves the failure.
2359
+ const saved = yield* retained();
2360
+
2361
+ return saved === null ? yield* Effect.fail(error) : yield* replay(saved);
2362
+ }),
2363
+ ),
2364
+ );
2269
2365
  }),
2270
2366
  followUp: Effect.fn("WorkerHost.followUp")(function* (request) {
2271
2367
  const principal = yield* authorize("followUp", "send", request.worker);
@@ -2351,9 +2447,8 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
2351
2447
  summary: Effect.fn("WorkerHost.inspectWorker")(function* (request) {
2352
2448
  yield* authorize("inspect", "read", request.worker);
2353
2449
  const origin = yield* findOrigin(request.worker, request.target, "inspect");
2354
- const source = yield* readWorkerState(context.source.threadId, "inspect");
2355
2450
 
2356
- return yield* summarize(origin, requests(source.records), "inspect");
2451
+ return yield* summarize(origin, "inspect");
2357
2452
  }),
2358
2453
  observe: (request) =>
2359
2454
  Stream.unwrap(
@@ -2438,33 +2533,87 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
2438
2533
  yield* binding(request.target, "list");
2439
2534
  if (!Number.isSafeInteger(request.limit) || request.limit < 1 || request.limit > 100)
2440
2535
  return yield* failure("list", "capacity");
2441
- const source = yield* readWorkerState(context.source.threadId, "list");
2442
- const all = requests(source.records);
2443
-
2444
- const origins = [
2445
- ...new Map(
2446
- all.map((row) => [row.admission.origin.worker.threadId, row.admission.origin]),
2447
- ).values(),
2448
- ]
2449
- .filter(
2450
- (origin) =>
2451
- origin.worker.delegationId === request.delegationId &&
2452
- origin.worker.targetAgentId === request.target.id &&
2453
- (request.after === undefined || origin.worker.threadId > request.after),
2454
- )
2455
- .sort((left, right) => left.worker.threadId.localeCompare(right.worker.threadId));
2536
+ if (Option.isNone(deps.deliveries)) return yield* failure("list", "unavailable");
2537
+
2538
+ const page = yield* deps.deliveries.value
2539
+ .list({
2540
+ ownerThreadId: context.source.threadId,
2541
+ limit: request.limit,
2542
+ workerStarts: { delegationId: request.delegationId, targetAgentId: request.target.id },
2543
+ ...(request.after === undefined
2544
+ ? {}
2545
+ : { after: Schema.decodeSync(IdempotencyKey)(request.after) }),
2546
+ })
2547
+ .pipe(Effect.mapError(storageFailure("list")));
2456
2548
 
2457
- const selected = origins.slice(0, request.limit);
2549
+ const items = yield* Effect.forEach(page.items, (row) => {
2550
+ const origin = row.envelope.workerAdmission?.origin;
2458
2551
 
2459
- const items = yield* Effect.forEach(selected, (origin) => summarize(origin, all, "list"));
2552
+ return origin === undefined || origin.firstMessageId !== row.key.messageId
2553
+ ? failure("list", "corrupt")
2554
+ : summarize(origin, "list");
2555
+ });
2460
2556
 
2461
- return {
2462
- items,
2463
- next:
2464
- origins.length > selected.length
2465
- ? (selected[selected.length - 1]?.worker.threadId ?? null)
2466
- : null,
2467
- };
2557
+ return { items, next: page.next === null ? null : Schema.decodeSync(ThreadId)(page.next) };
2558
+ }),
2559
+ stop: Effect.fn("WorkerHost.stop")(function* (request) {
2560
+ const principal = yield* authorize("stop", "control", request.worker);
2561
+
2562
+ yield* findOrigin(request.worker, request.target, "stop");
2563
+ if (deps.ledger.stopWorker === undefined) return yield* failure("stop", "unavailable");
2564
+ const command = yield* decode(WorkerStop, request, "stop");
2565
+
2566
+ const id = Schema.decodeSync(RecordId)(
2567
+ `worker-stop:${yield* messageIdFor([context.source.threadId, request.idempotencyKey])}`,
2568
+ );
2569
+
2570
+ let retained = false;
2571
+
2572
+ for (let attempt = 0; attempt < 16; attempt++) {
2573
+ const current = yield* deps.store
2574
+ .inspectTail(ThreadTailRequest.make({ threadId: context.source.threadId }))
2575
+ .pipe(Effect.mapError(storageFailure("stop")));
2576
+
2577
+ const prior = Option.getOrUndefined(
2578
+ yield* exactRecord(context.source.threadId, id, "stop"),
2579
+ )?.record.payload;
2580
+
2581
+ if (prior !== undefined) {
2582
+ if (
2583
+ prior._tag !== "WorkerStopRequested" ||
2584
+ !Schema.toEquivalence(WorkerStop)(prior.command, command)
2585
+ )
2586
+ return yield* failure("stop", "idempotency-conflict");
2587
+ retained = true;
2588
+ break;
2589
+ }
2590
+ if (
2591
+ yield* append(
2592
+ context.source.threadId,
2593
+ id,
2594
+ WorkerStopRequested.make({ command }),
2595
+ { ...current, records: [] },
2596
+ "stop",
2597
+ )
2598
+ ) {
2599
+ retained = true;
2600
+ break;
2601
+ }
2602
+ }
2603
+ if (!retained) return yield* failure("stop", "storage");
2604
+ // The destination serializes this seal with admission. Replays retain every earlier
2605
+ // abort intent and wait for active handlers/finalizers, rather than acknowledging a hint.
2606
+ while (true) {
2607
+ yield* hit("worker:before-stop-seal", "stop");
2608
+
2609
+ const owned = yield* deps.ledger
2610
+ .stopWorker({ threadId: request.worker.threadId, author: principal })
2611
+ .pipe(Effect.mapError(storageFailure("stop")));
2612
+
2613
+ yield* hit("worker:after-stop-seal", "stop");
2614
+ if (owned === 0) return command;
2615
+ yield* Effect.sleep(deps.settlementPollInterval);
2616
+ }
2468
2617
  }),
2469
2618
  cancel: Effect.fn("WorkerHost.cancel")(function* (request) {
2470
2619
  const principal = yield* authorize("cancel", "control", request.worker);