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
@@ -1,7 +1,7 @@
1
1
  import { Clock, Context, Crypto, Effect, Layer, Result, Schema, Semaphore } from "effect";
2
2
 
3
3
  import * as FailureDiagnostic from "../core/FailureDiagnostic.ts";
4
- import { ThreadId } from "../core/Identifiers.ts";
4
+ import { AgentId, DelegationId, ThreadId } from "../core/Identifiers.ts";
5
5
  import { Receipt } from "../core/Receipt.ts";
6
6
  import { WorkerUpdate } from "../core/Worker.ts";
7
7
  import { digestJson } from "./Digest.ts";
@@ -181,13 +181,28 @@ export const MessageDeliveryChange = Schema.Union([
181
181
 
182
182
  export type MessageDeliveryChange = typeof MessageDeliveryChange.Type;
183
183
 
184
+ /** Select one native index per page; index filters cannot be combined. */
184
185
  export const MessageDeliveryPageRequest = Schema.Struct({
185
186
  ownerThreadId: ThreadId,
186
187
  limit: Positive.check(Schema.isLessThanOrEqualTo(4096)),
187
188
  after: Schema.optionalKey(IdempotencyKey),
188
189
  /** Native pending index, including accepted, future-due and parked obligations. */
189
190
  pendingOnly: Schema.optionalKey(Schema.Boolean),
190
- });
191
+ /** First retained envelopes, including starts not admitted at the destination. */
192
+ workerStarts: Schema.optionalKey(
193
+ Schema.Struct({ delegationId: DelegationId, targetAgentId: AgentId }),
194
+ ),
195
+ /** Retained inputs not yet acknowledged by one destination worker. */
196
+ pendingWorker: Schema.optionalKey(ThreadId),
197
+ }).check(
198
+ Schema.makeFilter(
199
+ (request) =>
200
+ Number(request.pendingOnly === true) +
201
+ Number(request.workerStarts !== undefined) +
202
+ Number(request.pendingWorker !== undefined) <=
203
+ 1,
204
+ ),
205
+ );
191
206
 
192
207
  export type MessageDeliveryPageRequest = typeof MessageDeliveryPageRequest.Type;
193
208
 
@@ -32,7 +32,7 @@ import {
32
32
  import { Selection, Snapshot } from "../core/ToolExposure.ts";
33
33
  import { ToolParameterRejection } from "../core/ToolResult.ts";
34
34
  import { ModelCallUsage, RunUsageSummary, RunTotals } from "../core/Usage.ts";
35
- import { WorkerBudgetScope, WorkerRef, WorkerSource } from "../core/Worker.ts";
35
+ import { WorkerBudgetScope, WorkerRef, WorkerSource, WorkerStop } from "../core/Worker.ts";
36
36
  import { ContextHandoff } from "../engine/ContextWindow.ts";
37
37
 
38
38
  const identifier = <const Name extends string>(name: Name) =>
@@ -805,9 +805,10 @@ export class SubagentLineageRecorded extends Schema.TaggedClass<SubagentLineageR
805
805
  }) {}
806
806
 
807
807
  export const WorkerReportingIntent = Schema.Struct({
808
- /** Standard framework reports or an application-mapped input projection. */
808
+ /** Absent only in historical custom-report origins; these remain readable as evidence. */
809
809
  mode: Schema.optionalKey(Schema.Literal("standard")),
810
810
  sourceDigests: DefinitionDigests,
811
+ /** Historical custom destination evidence. New worker origins never write this field. */
811
812
  destinationDelegationId: Schema.optionalKey(DelegationId),
812
813
  /** Frozen by the sender before acceptance. Standard emission never reads the source runtime. */
813
814
  returnAddress: Schema.optionalKey(
@@ -831,6 +832,8 @@ export const WorkerOrigin = Schema.Struct({
831
832
  source: WorkerSource,
832
833
  /** Omitted retains source-subtree funding; worker-run renews only for a new native Run. */
833
834
  budgetScope: Schema.optionalKey(WorkerBudgetScope),
835
+ /** Frozen at worker creation; older origins remain reusable. */
836
+ lifecycle: Schema.optionalKey(Schema.Literal("assignment")),
834
837
  targetDigests: DefinitionDigests,
835
838
  policy: Schema.toCodecJson(AgentPolicy),
836
839
  budget: SubagentBudgetReservation,
@@ -862,6 +865,12 @@ export const WorkerAdmission = Schema.Struct({
862
865
 
863
866
  export type WorkerAdmission = typeof WorkerAdmission.Type;
864
867
 
868
+ /** Owner command retained before the destination inbox is sealed. */
869
+ export class WorkerStopRequested extends Schema.TaggedClass<WorkerStopRequested>()(
870
+ "WorkerStopRequested",
871
+ { command: WorkerStop },
872
+ ) {}
873
+
865
874
  /** Source-log capacity reservation, retained independently of the source Run's settlement. */
866
875
  export class WorkerInputRequested extends Schema.TaggedClass<WorkerInputRequested>()(
867
876
  "WorkerInputRequested",
@@ -1009,6 +1018,7 @@ export const CanonicalRecordPayload = Schema.Union([
1009
1018
  SubagentJoined,
1010
1019
  SubagentLineageRecorded,
1011
1020
  WorkerInputRequested,
1021
+ WorkerStopRequested,
1012
1022
  WorkerOriginRecorded,
1013
1023
  WorkerInputCompleted,
1014
1024
  WorkerReportPrepared,
@@ -270,7 +270,21 @@ export const makeSqlMessageDeliveryStore = Effect.fn("SqlMessageDeliveryStore.ma
270
270
 
271
271
  const rows = yield* query(
272
272
  "list",
273
- sql`SELECT owner_thread_id, message_id, version, state, deadline_at_millis, record_json FROM effect_agent_message_deliveries WHERE owner_thread_id = ${input.ownerThreadId} ${input.after === undefined ? sql`` : sql`AND message_id > ${input.after}`} ${input.pendingOnly ? sql`AND state NOT IN ('processed', 'refused')` : sql``} ORDER BY message_id LIMIT ${input.limit + 1}`,
273
+ sql`SELECT owner_thread_id, message_id, version, state, deadline_at_millis, record_json FROM effect_agent_message_deliveries WHERE owner_thread_id = ${input.ownerThreadId} ${input.after === undefined ? sql`` : sql`AND message_id > ${input.after}`} ${input.pendingOnly ? sql`AND state NOT IN ('processed', 'refused')` : sql``}
274
+ ${
275
+ input.workerStarts === undefined
276
+ ? sql``
277
+ : sql`AND json_extract(record_json, '$.envelope.workerAdmission.origin.worker.delegationId') = ${input.workerStarts.delegationId}
278
+ AND json_extract(record_json, '$.envelope.workerAdmission.origin.worker.targetAgentId') = ${input.workerStarts.targetAgentId}
279
+ AND message_id = json_extract(record_json, '$.envelope.workerAdmission.origin.firstMessageId')`
280
+ }
281
+ ${
282
+ input.pendingWorker === undefined
283
+ ? sql``
284
+ : sql`AND json_extract(record_json, '$.envelope.workerAdmission.origin.worker.threadId') = ${input.pendingWorker}
285
+ AND state IN ('pending', 'parked') AND json_extract(record_json, '$.receipt') IS NULL`
286
+ }
287
+ ORDER BY message_id LIMIT ${input.limit + 1}`,
274
288
  );
275
289
 
276
290
  const records = yield* decodeRows(rows);
@@ -178,6 +178,20 @@ export const makeSelectedReads = Effect.fnUntraced(function* (
178
178
  rows =
179
179
  yield* sql`SELECT thread_id, sequence, record_id, batch_id, record_json, outstanding FROM effect_agent_canonical_records WHERE thread_id = ${request.threadId} AND outstanding <> 0 AND sequence > ${after} ORDER BY sequence LIMIT ${request.page.limit}`;
180
180
  break;
181
+ case "WorkerExecution":
182
+ rows = (yield* Effect.forEach(
183
+ ["UserInputRecorded", "RunStarted"],
184
+ (tag) =>
185
+ sql`SELECT thread_id, sequence, record_id, batch_id, record_json FROM effect_agent_canonical_records
186
+ WHERE thread_id = ${request.threadId} AND json_extract(record_json, '$.payload._tag') = ${tag}
187
+ AND json_extract(record_json, '$.payload.runId') IS NOT NULL
188
+ ORDER BY sequence DESC LIMIT 1`,
189
+ ))
190
+ .flat()
191
+ .filter((row) => Number(row.sequence) > after)
192
+ .sort((a, b) => Number(a.sequence) - Number(b.sequence))
193
+ .slice(0, request.page.limit);
194
+ break;
181
195
  case "WorkerState": {
182
196
  const runId =
183
197
  selection.sourceSubmissionId === undefined
@@ -15,6 +15,7 @@ import {
15
15
  import type { JoinedToHost } from "../core/Receipt.ts";
16
16
  import { IdempotencyKey, QueueSequence, Principal } from "../core/Receipt.ts";
17
17
  import { RunUsageSummary } from "../core/Usage.ts";
18
+ import { AssignmentTerminal } from "../core/Worker.ts";
18
19
  import {
19
20
  AbortRequested,
20
21
  ApprovalDecision,
@@ -241,6 +242,16 @@ export class SubmissionSnapshot extends Schema.Class<SubmissionSnapshot>(
241
242
  messageAdmission: Schema.optionalKey(InputMessage),
242
243
  }) {}
243
244
 
245
+ /** Bounded native control facts. Canonical input/Run records remain ThreadStore-owned. */
246
+ export const WorkerLedgerState = Schema.Struct({
247
+ latest: Schema.NullOr(SubmissionSnapshot),
248
+ active: Schema.NullOr(SubmissionSnapshot),
249
+ stopped: Schema.Boolean,
250
+ terminal: Schema.optionalKey(AssignmentTerminal),
251
+ }).check(Schema.makeFilter((state) => state.terminal === undefined || state.stopped));
252
+
253
+ export type WorkerLedgerState = typeof WorkerLedgerState.Type;
254
+
244
255
  /** The authoritative store proves the scoped idempotency key was never admitted (SUB-031). */
245
256
  export class AdmissionNotAdmitted extends Schema.TaggedClass<AdmissionNotAdmitted>(
246
257
  "@effect-agent/thread/AdmissionNotAdmitted",
@@ -418,6 +429,38 @@ export const settlementFailureFromRecord = (
418
429
  : undefined;
419
430
  };
420
431
 
432
+ /**
433
+ * Assignment intent derived only from frozen admission and exact settlement evidence.
434
+ * Joined receipts and cancellation before a Run starts do not finish an assignment.
435
+ * Adapters suppress completed intent when newer accepted input remains unapplied, then
436
+ * install the first seal and outstanding abort intents atomically before releasing the lane.
437
+ */
438
+ export const workerTerminalFromRecord = (
439
+ submission: SubmissionSnapshot,
440
+ record: RecordEnvelope,
441
+ ): AssignmentTerminal | undefined => {
442
+ const payload = record.payload;
443
+
444
+ if (
445
+ submission.workerAdmission?.origin.lifecycle !== "assignment" ||
446
+ payload._tag !== "SubmissionSettled" ||
447
+ (payload.runId !== undefined && payload.runId !== `run:${submission.submissionId}`)
448
+ )
449
+ return undefined;
450
+ if (payload.outcome === "failed" || payload.finishReason === "budget-exhausted") return "failed";
451
+ if (payload.outcome === "aborted") return payload.runId === undefined ? undefined : "cancelled";
452
+
453
+ return payload.runDisposition === "completed" ? "completed" : undefined;
454
+ };
455
+
456
+ /** Destination-owned permanent inbox seal; recorded atomically with all outstanding abort intents. */
457
+ export const WorkerStopCommand = Schema.Struct({
458
+ threadId: ThreadId,
459
+ author: Principal,
460
+ });
461
+
462
+ export type WorkerStopCommand = typeof WorkerStopCommand.Type;
463
+
421
464
  /**
422
465
  * A durable abort command (durability §13). Field bounds are exactly those of the canonical
423
466
  * `AbortRequested` payload so the intent can become canonical without re-validation.
@@ -995,7 +1038,8 @@ export type SubmissionLedgerFailure =
995
1038
  * recovery settles aborted never-claimed queued work immediately, without waiting for it to
996
1039
  * head the lane. Both exceptions are outcome- and state-narrow; everything else stays fenced.
997
1040
  * - `finalizeSettlement` — idempotent terminal transition (state → settled) after the reserved
998
- * record is canonical; releases the lane so the next `queueSequence` becomes claimable. It
1041
+ * record is canonical. Native adapters seal opted-in worker assignments atomically before
1042
+ * releasing the lane. Otherwise the next `queueSequence` becomes claimable. It
999
1043
  * requires no ownership token: canonical history authorizes finalization (DUR-015). A
1000
1044
  * finalization that disagrees with the recorded outcome fails with `SettlementConflict`.
1001
1045
  * - `requestAbort` — durable, idempotent by `submissionId`: repeating returns the recorded
@@ -1100,6 +1144,9 @@ export class SubmissionLedger extends Context.Service<
1100
1144
  readonly finalizeSettlement: (
1101
1145
  request: SettlementFinalization,
1102
1146
  ) => Effect.Effect<Settlement, SettlementConflict | LedgerError>;
1147
+ /** Optional for third-party adapters. Remaining ownership must reach zero before stop acknowledgement. */
1148
+ readonly inspectWorker?: (threadId: ThreadId) => Effect.Effect<WorkerLedgerState, LedgerError>;
1149
+ readonly stopWorker?: (request: WorkerStopCommand) => Effect.Effect<number, LedgerError>;
1103
1150
  readonly requestAbort: (
1104
1151
  request: AbortCommand,
1105
1152
  ) => Effect.Effect<AbortIntent, SettlementConflict | JoinedToHost | LedgerError>;
@@ -26,6 +26,7 @@ import {
26
26
  WorkerAdmission,
27
27
  type SettlementFailureDiagnostic,
28
28
  } from "./Records.ts";
29
+ import { runIdForSubmission } from "./RunJournal.ts";
29
30
  import {
30
31
  type AdmissionResult,
31
32
  AbortCommand,
@@ -64,6 +65,7 @@ import {
64
65
  ResolutionSafeToRetry,
65
66
  RevertJoiningRequest,
66
67
  SettlementConflict,
68
+ Settlement,
67
69
  SettlementFinalization,
68
70
  SettlementReservation,
69
71
  SubmissionLedger,
@@ -538,6 +540,50 @@ const admissionIdempotency = conformanceCase(
538
540
  }),
539
541
  );
540
542
 
543
+ const workerMetadata = (base: AdmissionRequest) =>
544
+ WorkerAdmission.make({
545
+ messageId: base.idempotencyKey,
546
+ sourceSubmissionId: Schema.decodeSync(SubmissionId)("source-input"),
547
+ deliveryPrincipal: base.principal,
548
+ parameters: { prompt: "original projection parameters" },
549
+ createdAtMillis: 1,
550
+ origin: {
551
+ worker: {
552
+ schemaVersion: 1,
553
+ threadId: base.threadId,
554
+ targetAgentId: CONFORMANCE_AGENT,
555
+ delegationId: Schema.decodeSync(DelegationId)("worker-research"),
556
+ },
557
+ source: {
558
+ _tag: "programmatic",
559
+ threadId: decodeThreadId("worker-source"),
560
+ agentId: CONFORMANCE_AGENT,
561
+ },
562
+ targetDigests: CONFORMANCE_DIGESTS,
563
+ policy: AgentPolicy.resolve(),
564
+ budget: {
565
+ caps: SubagentDelegationCaps.make({
566
+ maxConcurrentChildren: 1,
567
+ maxTotalChildInvocations: 2,
568
+ }),
569
+ allocation: SubagentReservationAmounts.make({
570
+ turns: 12,
571
+ toolCalls: 24,
572
+ durationMillis: 300_000,
573
+ inputTokens: 0,
574
+ outputTokens: 0,
575
+ costMicrousd: 0,
576
+ resultBytes: 1_000,
577
+ }),
578
+ },
579
+ grant: SubagentGrant.make({ maxDepth: 1, allowedToolNames: [] }),
580
+ depth: 1,
581
+ firstMessageId: base.idempotencyKey,
582
+ createdAtMillis: 1,
583
+ expiresAtMillis: 1_000_000,
584
+ },
585
+ });
586
+
541
587
  const workerAdmissionIdentity = conformanceCase(
542
588
  "retains immutable worker admission metadata and rejects same-key changes or omission",
543
589
  ({ ensure, expectFailure, expectSome }) =>
@@ -550,48 +596,7 @@ const workerAdmissionIdentity = conformanceCase(
550
596
  { text: "first" },
551
597
  );
552
598
 
553
- const metadata = WorkerAdmission.make({
554
- messageId: base.idempotencyKey,
555
- sourceSubmissionId: Schema.decodeSync(SubmissionId)("source-input"),
556
- deliveryPrincipal: base.principal,
557
- parameters: { prompt: "original projection parameters" },
558
- createdAtMillis: 1,
559
- origin: {
560
- worker: {
561
- schemaVersion: 1,
562
- threadId: base.threadId,
563
- targetAgentId: CONFORMANCE_AGENT,
564
- delegationId: Schema.decodeSync(DelegationId)("worker-research"),
565
- },
566
- source: {
567
- _tag: "programmatic",
568
- threadId: decodeThreadId("worker-source"),
569
- agentId: CONFORMANCE_AGENT,
570
- },
571
- targetDigests: CONFORMANCE_DIGESTS,
572
- policy: AgentPolicy.resolve(),
573
- budget: {
574
- caps: SubagentDelegationCaps.make({
575
- maxConcurrentChildren: 1,
576
- maxTotalChildInvocations: 2,
577
- }),
578
- allocation: SubagentReservationAmounts.make({
579
- turns: 12,
580
- toolCalls: 24,
581
- durationMillis: 300_000,
582
- inputTokens: 0,
583
- outputTokens: 0,
584
- costMicrousd: 0,
585
- resultBytes: 1_000,
586
- }),
587
- },
588
- grant: SubagentGrant.make({ maxDepth: 1, allowedToolNames: [] }),
589
- depth: 1,
590
- firstMessageId: base.idempotencyKey,
591
- createdAtMillis: 1,
592
- expiresAtMillis: 1_000_000,
593
- },
594
- });
599
+ const metadata = workerMetadata(base);
595
600
 
596
601
  const admitted = yield* ledger.admit(
597
602
  AdmissionRequest.make({ ...base, workerAdmission: metadata }),
@@ -4553,6 +4558,218 @@ const abortedSettledRowIsNotAJoiningGap = conformanceCase(
4553
4558
  }),
4554
4559
  );
4555
4560
 
4561
+ const assignmentSettlement = conformanceCase(
4562
+ "seals terminal assignments atomically while waiting and unapplied corrections remain steerable",
4563
+ ({ ensure, expectSome, expectFailure }) =>
4564
+ Effect.gen(function* () {
4565
+ const ledger = yield* SubmissionLedger;
4566
+
4567
+ if (ledger.inspectWorker === undefined) return;
4568
+
4569
+ // Each row isolates one ownership rule. The same cases run on every native adapter.
4570
+ const cases = [
4571
+ {
4572
+ name: "completed",
4573
+ outcome: "completed",
4574
+ disposition: "completed",
4575
+ terminal: "completed",
4576
+ },
4577
+ { name: "waiting", outcome: "completed", disposition: "waiting", terminal: undefined },
4578
+ { name: "failure", outcome: "failed", queued: true, terminal: "failed" },
4579
+ {
4580
+ name: "failure-joining",
4581
+ outcome: "failed",
4582
+ queued: true,
4583
+ joining: true,
4584
+ terminal: "failed",
4585
+ },
4586
+ { name: "exhaustion", outcome: "completed", exhausted: true, terminal: "failed" },
4587
+ { name: "active-abort", outcome: "aborted", terminal: "cancelled" },
4588
+ { name: "queued-abort", outcome: "aborted", noRun: true, terminal: undefined },
4589
+ {
4590
+ name: "reusable",
4591
+ outcome: "completed",
4592
+ disposition: "completed",
4593
+ reusable: true,
4594
+ terminal: undefined,
4595
+ },
4596
+ {
4597
+ name: "late-correction",
4598
+ outcome: "completed",
4599
+ disposition: "completed",
4600
+ queued: true,
4601
+ terminal: undefined,
4602
+ },
4603
+ {
4604
+ name: "applied-correction",
4605
+ outcome: "completed",
4606
+ disposition: "completed",
4607
+ queued: true,
4608
+ joined: true,
4609
+ terminal: "completed",
4610
+ },
4611
+ ] as const;
4612
+
4613
+ for (const scenario of cases) {
4614
+ const threadId = decodeThreadId(`assignment-${scenario.name}`);
4615
+ const base = yield* admissionRequest(threadId, "first", { text: "first" });
4616
+ const metadata = workerMetadata(base);
4617
+
4618
+ const workerAdmission = WorkerAdmission.make({
4619
+ ...metadata,
4620
+ origin: {
4621
+ ...metadata.origin,
4622
+ ...("reusable" in scenario ? {} : { lifecycle: "assignment" as const }),
4623
+ },
4624
+ });
4625
+
4626
+ const request = AdmissionRequest.make({ ...base, workerAdmission });
4627
+
4628
+ const first = yield* ledger.admit(request);
4629
+
4630
+ yield* ledger.markReady(MarkReadyRequest.make({ submissionId: first.submissionId }));
4631
+ const claim = yield* expectSome("assignment claim", yield* claimLane(threadId, PRODUCER_A));
4632
+ const runId = runIdForSubmission(first.submissionId);
4633
+
4634
+ const payload = yield* Schema.decodeEffect(SubmissionSettledRecord)(
4635
+ SubmissionSettled.make({
4636
+ submissionId: first.submissionId,
4637
+ settlementId: submissionSettlementId(first.submissionId),
4638
+ receiptId: first.receiptId,
4639
+ outcome: scenario.outcome,
4640
+ ...("noRun" in scenario ? {} : { runId }),
4641
+ ...(scenario.outcome === "aborted"
4642
+ ? {}
4643
+ : {
4644
+ result:
4645
+ scenario.outcome === "failed"
4646
+ ? { errorTag: "TestFailure", message: "Failed task" }
4647
+ : { answer: "done" },
4648
+ }),
4649
+ ...("disposition" in scenario ? { runDisposition: scenario.disposition } : {}),
4650
+ ...("exhausted" in scenario
4651
+ ? { finishReason: "budget-exhausted" as const, exhausted: "turns" as const }
4652
+ : {}),
4653
+ }),
4654
+ ).pipe(Effect.orDie);
4655
+
4656
+ const record = RecordEnvelope.make({
4657
+ recordId: submissionSettlementRecordId(first.submissionId),
4658
+ family: "thread",
4659
+ schemaVersion: 1,
4660
+ createdAt: CONFORMANCE_CREATED_AT,
4661
+ deploymentId: CONFORMANCE_DEPLOYMENT,
4662
+ payload,
4663
+ });
4664
+
4665
+ let queued: AdmissionResult | undefined;
4666
+
4667
+ if ("queued" in scenario) {
4668
+ const next = yield* admissionRequest(threadId, "correction", { text: "correction" });
4669
+
4670
+ queued = yield* ledger.admit(
4671
+ AdmissionRequest.make({
4672
+ ...next,
4673
+ workerAdmission: { ...workerAdmission, messageId: next.idempotencyKey },
4674
+ }),
4675
+ );
4676
+ yield* ledger.markReady(MarkReadyRequest.make({ submissionId: queued.submissionId }));
4677
+ if ("joined" in scenario || "joining" in scenario) {
4678
+ yield* ledger.claimJoining(
4679
+ ClaimJoiningRequest.make({
4680
+ threadId,
4681
+ hostSubmissionId: first.submissionId,
4682
+ ownershipToken: claim.ownershipToken,
4683
+ maxCount: 1,
4684
+ }),
4685
+ );
4686
+ }
4687
+ if ("joined" in scenario) {
4688
+ yield* ledger.markJoined(
4689
+ MarkJoinedRequest.make({
4690
+ submissionId: queued.submissionId,
4691
+ ownershipToken: claim.ownershipToken,
4692
+ recordId: submissionInputRecordId(queued.submissionId),
4693
+ sequence: Schema.decodeSync(CanonicalSequence)(2),
4694
+ }),
4695
+ );
4696
+ }
4697
+ }
4698
+ yield* ledger.reserveSettlement(
4699
+ SettlementReservation.make({
4700
+ submissionId: first.submissionId,
4701
+ ownershipToken: claim.ownershipToken,
4702
+ settlementId: payload.settlementId,
4703
+ outcome: scenario.outcome,
4704
+ record,
4705
+ recordDigest: yield* digestJson(
4706
+ yield* Schema.encodeEffect(RecordEnvelope)(record).pipe(Effect.orDie),
4707
+ ),
4708
+ }),
4709
+ );
4710
+
4711
+ const finalization = SettlementFinalization.make({
4712
+ submissionId: first.submissionId,
4713
+ settlementId: payload.settlementId,
4714
+ });
4715
+
4716
+ const settled = yield* ledger.finalizeSettlement(finalization);
4717
+
4718
+ yield* ensure(
4719
+ Schema.toEquivalence(Settlement)(settled, yield* ledger.finalizeSettlement(finalization)),
4720
+ "Finalization replay must preserve its exact outcome",
4721
+ );
4722
+ const control = yield* ledger.inspectWorker(threadId);
4723
+
4724
+ yield* ensure(
4725
+ control.terminal === scenario.terminal &&
4726
+ control.stopped === (scenario.terminal !== undefined),
4727
+ `Wrong assignment state for ${scenario.name}`,
4728
+ );
4729
+ yield* ensure(
4730
+ (yield* ledger.admit(request)).receiptId === first.receiptId,
4731
+ "A seal must preserve same-command receipt replay",
4732
+ );
4733
+ const next = yield* admissionRequest(threadId, "later", { text: "later" });
4734
+
4735
+ const later = AdmissionRequest.make({
4736
+ ...next,
4737
+ workerAdmission: { ...workerAdmission, messageId: next.idempotencyKey },
4738
+ });
4739
+
4740
+ if (scenario.terminal !== undefined) {
4741
+ const refusal = yield* expectFailure("terminal admission", ledger.admit(later));
4742
+
4743
+ yield* ensure(
4744
+ Schema.is(AdmissionPolicyError)(refusal) && refusal.code === "worker-stopped",
4745
+ "New instructions cannot reopen a terminal assignment",
4746
+ );
4747
+ } else {
4748
+ yield* ledger.admit(later);
4749
+ }
4750
+ if (queued !== undefined) {
4751
+ if ("joining" in scenario)
4752
+ yield* ledger.revertJoining(
4753
+ RevertJoiningRequest.make({ submissionId: queued.submissionId }),
4754
+ );
4755
+ const state = yield* recoverySnapshot(queued.submissionId);
4756
+
4757
+ yield* ensure(
4758
+ (state.abortIntent !== undefined) === (scenario.outcome === "failed"),
4759
+ "Failure aborts queued inputs; completion never cancels an accepted correction or joined member",
4760
+ );
4761
+ }
4762
+ if (scenario.terminal !== undefined && ledger.stopWorker !== undefined) {
4763
+ yield* ledger.stopWorker({ threadId, author: CONFORMANCE_PRINCIPAL });
4764
+ yield* ensure(
4765
+ (yield* ledger.inspectWorker(threadId)).terminal === scenario.terminal,
4766
+ "Explicit stop cannot replace an assignment's first terminal outcome",
4767
+ );
4768
+ }
4769
+ }
4770
+ }),
4771
+ );
4772
+
4556
4773
  /**
4557
4774
  * The shared, adapter-parameterized SubmissionLedger contract suite (STORE-010). Every durable
4558
4775
  * ledger adapter test suite must execute each case against its own ledger provisioning, inside
@@ -4561,6 +4778,7 @@ const abortedSettledRowIsNotAJoiningGap = conformanceCase(
4561
4778
  export const submissionLedgerConformanceCases: ReadonlyArray<SubmissionLedgerConformanceCase> = [
4562
4779
  admissionIdempotency,
4563
4780
  workerAdmissionIdentity,
4781
+ assignmentSettlement,
4564
4782
  messageAdmissionIdentity,
4565
4783
  workerCompletionIdentity,
4566
4784
  workerUpdateIdentity,
@@ -324,6 +324,11 @@ export const ThreadSelection = Schema.Union([
324
324
  expectedTailSequence: CanonicalSequence,
325
325
  expectedTailDigest: Digest,
326
326
  }),
327
+ Schema.Struct({
328
+ _tag: Schema.Literal("WorkerExecution"),
329
+ expectedTailSequence: CanonicalSequence,
330
+ expectedTailDigest: Digest,
331
+ }),
327
332
  Schema.Struct({
328
333
  _tag: Schema.Literal("WorkerState"),
329
334
  expectedTailSequence: CanonicalSequence,
@@ -230,15 +230,6 @@ interface CapturedBinding {
230
230
  ) => Effect.Effect<Option.Option<Settlement>, DurableWorkerFailure>;
231
231
  }
232
232
 
233
- type ReportRequirements<Reports> =
234
- Reports extends ReadonlyArray<WorkerReporting<unknown, unknown>>
235
- ? [Reports[number]] extends [never]
236
- ? never
237
- : Reports[number] extends WorkerReporting<unknown, infer R>
238
- ? Exclude<R, Scope.Scope>
239
- : never
240
- : never;
241
-
242
233
  // Registrations outlive their construction span. Dependencies remain captured,
243
234
  // while every attempt/report inherits the invoking fiber's tracing state.
244
235
  const omitTraceContext = Context.omit(
@@ -352,32 +343,16 @@ const capture = <A extends ExecutableAgentBinding, Provides = never, Requires =
352
343
  * may supply a previously computed digest triple directly.
353
344
  */
354
345
  export const DurableWorkerBinding = {
355
- make: <
356
- A extends ExecutableAgentBinding,
357
- const Reports extends ReadonlyArray<WorkerReporting<unknown, unknown>> = readonly [],
358
- >(
346
+ make: <A extends ExecutableAgentBinding>(
359
347
  agent: A,
360
348
  digests: DefinitionDigests,
361
- reporting?: Reports,
362
- ): Effect.Effect<
363
- ResolvedBinding,
364
- never,
365
- DurableWorkerRequirements<A> | ReportRequirements<Reports>
366
- > =>
349
+ ): Effect.Effect<ResolvedBinding, never, DurableWorkerRequirements<A>> =>
367
350
  Effect.gen(function* () {
368
351
  const binding = yield* capture(agent);
352
+ const reporting = yield* captureReporting(backgroundReports(agent.definition));
369
353
 
370
- const reports = yield* captureReporting([
371
- ...(reporting ?? []),
372
- ...backgroundReports(agent.definition),
373
- ]);
374
-
375
- return { ...binding, digests, reporting: reports };
376
- }) as Effect.Effect<
377
- ResolvedBinding,
378
- never,
379
- DurableWorkerRequirements<A> | ReportRequirements<Reports>
380
- >,
354
+ return { ...binding, digests, reporting };
355
+ }),
381
356
  } as const;
382
357
 
383
358
  /** INTERNAL identity-only capture retained for the legacy direct worker path. */
@@ -442,12 +417,6 @@ export type AgentRegistration<A extends ExecutableAgentBinding = ExecutableAgent
442
417
  readonly continuity?: {
443
418
  readonly versions: ReplayVersions;
444
419
  };
445
- /**
446
- * Source-owned reports capture services outside attemptLayer and open a fresh Scope per
447
- * preparation. Version changes with this registration. Missing exact code and preparation
448
- * failures become retained report refusals; durable delivery retries reuse the frozen input.
449
- */
450
- readonly reporting?: ReadonlyArray<WorkerReporting<unknown, unknown>>;
451
420
  /**
452
421
  * Build fresh services for exactly one fenced Attempt, across all its Tool/model turns.
453
422
  * Finalizes on completion, suspension, failure and interruption. Never reused after eviction.
@@ -477,12 +446,10 @@ type AttemptLayerRequirements<Requirements, AttemptLayer> = AttemptLayer extends
477
446
  // Conditional options retain every service they may consume. An absent attempt Layer
478
447
  // still needs the original worker services; only a definite Layer can remove them.
479
448
  type EntryRequirements<Entry> = Entry extends unknown
480
- ?
481
- | AttemptLayerRequirements<
482
- EntryWorkerRequirements<Entry>,
483
- "attemptLayer" extends keyof Entry ? Entry["attemptLayer"] : undefined
484
- >
485
- | ("reporting" extends keyof Entry ? ReportRequirements<Entry["reporting"]> : never)
449
+ ? AttemptLayerRequirements<
450
+ EntryWorkerRequirements<Entry>,
451
+ "attemptLayer" extends keyof Entry ? Entry["attemptLayer"] : undefined
452
+ >
486
453
  : never;
487
454
 
488
455
  type RegistrationRequirements<Entries extends ReadonlyArray<AgentRegistration>> = [
@@ -519,8 +486,9 @@ const registrationDefinitions = (entry: AgentRegistration): DefinitionDigestInpu
519
486
  backgroundReporting: automatic.map((report) => ({
520
487
  delegationId: report.delegationId,
521
488
  targetAgentId: report.target.id,
522
- mode: report.mode ?? "custom",
523
- destinationDelegationId: report.destination?.delegationId ?? null,
489
+ mode: "standard",
490
+ // Preserve the fingerprint of existing standard registrations.
491
+ destinationDelegationId: null,
524
492
  })),
525
493
  },
526
494
  };
@@ -579,10 +547,7 @@ const compileRegistration = <Entry extends AgentRegistration>(
579
547
  entry.attemptLayer,
580
548
  );
581
549
 
582
- const reporting = yield* captureReporting([
583
- ...(entry.reporting ?? []),
584
- ...backgroundReports(binding.definition),
585
- ]);
550
+ const reporting = yield* captureReporting(backgroundReports(binding.definition));
586
551
 
587
552
  return {
588
553
  ...binding,