@yielded/agent 0.1.0-beta.174 → 0.1.0-beta.175

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 (175) hide show
  1. package/dist/{Admin-BHmp2V9p.d.mts → Admin-Qa3I1oA5.d.mts} +38 -7
  2. package/dist/{AgentRuntime-BD3ZHAMq.d.mts → AgentRuntime-CHDyRbSf.d.mts} +3 -3
  3. package/dist/{AgentRuntime-B6qZlD3f.mjs → AgentRuntime-DaDKMasp.mjs} +123 -114
  4. package/dist/AgentRuntime-DaDKMasp.mjs.map +1 -0
  5. package/dist/{Digest-EpeqWEdv.mjs → Digest-C3EODOee.mjs} +2 -2
  6. package/dist/{Digest-EpeqWEdv.mjs.map → Digest-C3EODOee.mjs.map} +1 -1
  7. package/dist/{DurableAgentRuntime-CBBjRNJN.mjs → DurableAgentRuntime-CPlw5_7e.mjs} +161 -85
  8. package/dist/DurableAgentRuntime-CPlw5_7e.mjs.map +1 -0
  9. package/dist/{EventSource-C9aaJaXT.d.mts → EventSource-BTu68yDW.d.mts} +2 -2
  10. package/dist/InMemory.d.mts +2 -2
  11. package/dist/{MessageDelivery-BGDm-c46.d.mts → MessageDelivery-DjAZJ4Fd.d.mts} +4 -4
  12. package/dist/{MessagingHost-CRUK4JBi.mjs → MessagingHost-CZCyrnvJ.mjs} +27 -26
  13. package/dist/MessagingHost-CZCyrnvJ.mjs.map +1 -0
  14. package/dist/{PreparedInputAdmission-BvDqlgiP.d.mts → PreparedInputAdmission-wgY4JMZY.d.mts} +3 -3
  15. package/dist/{Records-BiuJDWNe.mjs → Records-DzL4Kbtw.mjs} +2 -2
  16. package/dist/{Records-BiuJDWNe.mjs.map → Records-DzL4Kbtw.mjs.map} +1 -1
  17. package/dist/{RunContinuation-CTJ1jGrO.d.mts → RunContinuation-3jB5pULC.d.mts} +10 -4
  18. package/dist/{RunContinuation-4ZATcVLB.mjs → RunContinuation-BiyKJK32.mjs} +74 -40
  19. package/dist/RunContinuation-BiyKJK32.mjs.map +1 -0
  20. package/dist/{RunJournal-CNZqA3Ge.mjs → RunJournal-CPMKcfd0.mjs} +84 -79
  21. package/dist/RunJournal-CPMKcfd0.mjs.map +1 -0
  22. package/dist/{RunStorage-BHjuuERI.mjs → RunStorage-CzutHJBE.mjs} +3 -2
  23. package/dist/RunStorage-CzutHJBE.mjs.map +1 -0
  24. package/dist/{Scheduling-NG8slKdI.mjs → Scheduling-07_dsGED.mjs} +3 -3
  25. package/dist/{Scheduling-NG8slKdI.mjs.map → Scheduling-07_dsGED.mjs.map} +1 -1
  26. package/dist/{Subagent-CTIpnzlB.mjs → Subagent-CexndZSp.mjs} +2 -2
  27. package/dist/{Subagent-CTIpnzlB.mjs.map → Subagent-CexndZSp.mjs.map} +1 -1
  28. package/dist/{Subagent-DKhZA72u.d.mts → Subagent-xhN_aD1r.d.mts} +3 -3
  29. package/dist/{Subscription-BNg9VCRl.d.mts → Subscription-DmIHlOJK.d.mts} +2 -2
  30. package/dist/{SubscriptionInput-BUoXv-_K.d.mts → SubscriptionInput-DgcQhvbe.d.mts} +2 -2
  31. package/dist/{Thread-dk-QiNml.d.mts → Thread-DzdJAY4J.d.mts} +4 -3
  32. package/dist/{ThreadHistory-ByxS8HU1.d.mts → ThreadHistory-KPPBJLJb.d.mts} +12 -2
  33. package/dist/{ThreadImport-CodYoJoa.mjs → ThreadImport-CjlG57vH.mjs} +8 -8
  34. package/dist/{ThreadImport-CodYoJoa.mjs.map → ThreadImport-CjlG57vH.mjs.map} +1 -1
  35. package/dist/{ThreadImport-CMgzttvr.d.mts → ThreadImport-DP-vOk2F.d.mts} +4 -4
  36. package/dist/{ThreadStore-59uDGXul.mjs → ThreadStore-EuUilthA.mjs} +5 -7
  37. package/dist/ThreadStore-EuUilthA.mjs.map +1 -0
  38. package/dist/{ThreadTransfer-3jigUl3g.mjs → ThreadTransfer-DB_zsrtJ.mjs} +4 -4
  39. package/dist/{ThreadTransfer-3jigUl3g.mjs.map → ThreadTransfer-DB_zsrtJ.mjs.map} +1 -1
  40. package/dist/{WorkerAdmission-DDYJdOsU.mjs → WorkerAdmission-eBeXt_mu.mjs} +5 -5
  41. package/dist/{WorkerAdmission-DDYJdOsU.mjs.map → WorkerAdmission-eBeXt_mu.mjs.map} +1 -1
  42. package/dist/{agent-registration-DRbyhAu-.mjs → agent-registration-a6QYaG3h.mjs} +4 -4
  43. package/dist/{agent-registration-DRbyhAu-.mjs.map → agent-registration-a6QYaG3h.mjs.map} +1 -1
  44. package/dist/capabilities/Messaging.mjs +1 -1
  45. package/dist/capabilities/ModelContext.d.mts +1 -1
  46. package/dist/capabilities/RunHooks.d.mts +1 -1
  47. package/dist/capabilities/Subagent.d.mts +1 -1
  48. package/dist/capabilities/Subagent.mjs +1 -1
  49. package/dist/core/Thread.d.mts +2 -2
  50. package/dist/core/Thread.mjs +5 -3
  51. package/dist/core/Thread.mjs.map +1 -1
  52. package/dist/durable/ActivityStore.mjs +1 -1
  53. package/dist/durable/Admin.d.mts +1 -1
  54. package/dist/durable/Admin.mjs +1 -1
  55. package/dist/durable/AgentRegistration.d.mts +1 -1
  56. package/dist/durable/AgentRegistration.mjs +1 -1
  57. package/dist/durable/Certification.d.mts +1 -1
  58. package/dist/durable/CommittedActivity.d.mts +1 -1
  59. package/dist/durable/CommittedActivity.mjs +3 -3
  60. package/dist/durable/Digest.mjs +1 -1
  61. package/dist/durable/DurableAgentRuntime.d.mts +1 -1
  62. package/dist/durable/DurableAgentRuntime.mjs +2 -2
  63. package/dist/durable/EventSource.d.mts +1 -1
  64. package/dist/durable/EventSource.mjs +1 -1
  65. package/dist/durable/GitHubWorkflowSource.d.mts +3 -3
  66. package/dist/durable/LifecyclePublication.d.mts +1 -1
  67. package/dist/durable/LifecyclePublication.mjs +2 -2
  68. package/dist/durable/MessageDelivery.d.mts +1 -1
  69. package/dist/durable/MessageDelivery.mjs +2 -2
  70. package/dist/durable/MessageDeliveryStoreConformance.d.mts +40 -40
  71. package/dist/durable/MessageDeliveryStoreConformance.mjs +2 -2
  72. package/dist/durable/PersistentHistory.d.mts +2 -2
  73. package/dist/durable/PersistentHistory.mjs +4 -4
  74. package/dist/durable/PreparedInputAdmission.d.mts +1 -1
  75. package/dist/durable/RecordFormat.mjs +1 -1
  76. package/dist/durable/Records.mjs +1 -1
  77. package/dist/durable/Recovery.d.mts +1 -1
  78. package/dist/durable/Recovery.mjs +1 -1
  79. package/dist/durable/RunContinuation.d.mts +2 -2
  80. package/dist/durable/RunContinuation.mjs +3 -3
  81. package/dist/durable/RunJournal.d.mts +2 -2
  82. package/dist/durable/RunJournal.mjs +2 -2
  83. package/dist/durable/RunStorage.d.mts +1 -1
  84. package/dist/durable/RunStorage.mjs +1 -1
  85. package/dist/durable/Schedule.mjs +1 -1
  86. package/dist/durable/ScheduleStoreConformance.mjs +1 -1
  87. package/dist/durable/ScheduleTransition.mjs +1 -1
  88. package/dist/durable/Scheduling.d.mts +1 -1
  89. package/dist/durable/Scheduling.mjs +1 -1
  90. package/dist/durable/SettlementPublisher.d.mts +1 -1
  91. package/dist/durable/SettlementPublisher.mjs +3 -3
  92. package/dist/durable/SqlMemoryStore.mjs +5 -3
  93. package/dist/durable/SqlMemoryStore.mjs.map +1 -1
  94. package/dist/durable/SubmissionLedger.d.mts +1 -1
  95. package/dist/durable/SubmissionLedger.mjs +7 -3
  96. package/dist/durable/SubmissionLedger.mjs.map +1 -1
  97. package/dist/durable/SubmissionLedgerConformance.d.mts +1 -1
  98. package/dist/durable/SubmissionLedgerConformance.mjs +6 -6
  99. package/dist/durable/SubmissionStatus.d.mts +1 -1
  100. package/dist/durable/Subscription.d.mts +1 -1
  101. package/dist/durable/Subscription.mjs +2 -2
  102. package/dist/durable/SubscriptionInput.d.mts +1 -1
  103. package/dist/durable/SubscriptionInput.mjs +2 -2
  104. package/dist/durable/SubscriptionStoreConformance.d.mts +1 -1
  105. package/dist/durable/SubscriptionStoreConformance.mjs +1 -1
  106. package/dist/durable/SubscriptionTools.d.mts +2 -2
  107. package/dist/durable/SubscriptionTransition.d.mts +1 -1
  108. package/dist/durable/SubscriptionTransition.mjs +1 -1
  109. package/dist/durable/Subscriptions.d.mts +5 -5
  110. package/dist/durable/Subscriptions.mjs +2 -2
  111. package/dist/durable/ThreadArchiveRange.d.mts +1 -1
  112. package/dist/durable/ThreadArchiveRange.mjs +1 -1
  113. package/dist/durable/ThreadContextHistory.d.mts +1 -1
  114. package/dist/durable/ThreadContextHistory.mjs +2 -2
  115. package/dist/durable/ThreadContextHistoryProjection.mjs +1 -1
  116. package/dist/durable/ThreadImport.d.mts +1 -1
  117. package/dist/durable/ThreadImport.mjs +1 -1
  118. package/dist/durable/ThreadInvariants.d.mts +3 -3
  119. package/dist/durable/ThreadInvariants.mjs +4 -4
  120. package/dist/durable/ThreadProjection.mjs +1 -1
  121. package/dist/durable/ThreadProjectionMaintenance.d.mts +1 -1
  122. package/dist/durable/ThreadReader.d.mts +1 -1
  123. package/dist/durable/ThreadReader.mjs +1 -1
  124. package/dist/durable/ThreadStore.d.mts +1 -1
  125. package/dist/durable/ThreadStore.mjs +2 -2
  126. package/dist/durable/ThreadStoreConformance.d.mts +1 -1
  127. package/dist/durable/ThreadStoreConformance.mjs +4 -4
  128. package/dist/durable/ThreadTransfer.d.mts +1 -1
  129. package/dist/durable/ThreadTransfer.mjs +1 -1
  130. package/dist/durable/ThreadWork.d.mts +2 -2
  131. package/dist/durable/ThreadWork.mjs +12 -6
  132. package/dist/durable/ThreadWork.mjs.map +1 -1
  133. package/dist/durable/ToolReconciler.mjs +1 -1
  134. package/dist/durable/WakeScheduler.mjs +23 -39
  135. package/dist/durable/WakeScheduler.mjs.map +1 -1
  136. package/dist/durable/WorkerAdmission.d.mts +1 -1
  137. package/dist/durable/WorkerAdmission.mjs +1 -1
  138. package/dist/durable/WorkerHost.d.mts +1 -1
  139. package/dist/engine/AgentRuntime.d.mts +1 -1
  140. package/dist/engine/AgentRuntime.mjs +1 -1
  141. package/dist/engine/MessagingHost.mjs +1 -1
  142. package/dist/engine/Output.mjs +1 -1
  143. package/dist/engine/ThreadHistory.d.mts +1 -1
  144. package/dist/engine/ThreadHistory.mjs +3 -2
  145. package/dist/engine/ThreadHistory.mjs.map +1 -1
  146. package/dist/index.d.mts +12 -12
  147. package/dist/index.mjs +13 -13
  148. package/dist/{output-contract-CMIhu4Cw.mjs → output-contract-DCnLYCdp.mjs} +12 -15
  149. package/dist/{output-contract-CMIhu4Cw.mjs.map → output-contract-DCnLYCdp.mjs.map} +1 -1
  150. package/dist/{run-context-reader-CC7ZD95F.d.mts → run-context-reader-DVhJZ1Qe.d.mts} +2 -2
  151. package/package.json +1 -1
  152. package/src/core/Thread.ts +143 -146
  153. package/src/durable/DurableAgentRuntime.ts +381 -206
  154. package/src/durable/RunContinuation.ts +113 -62
  155. package/src/durable/RunJournal.ts +99 -108
  156. package/src/durable/RunStorage.ts +7 -0
  157. package/src/durable/SqlMemoryStore.ts +8 -6
  158. package/src/durable/SubmissionLedger.ts +7 -0
  159. package/src/durable/ThreadStore.ts +9 -7
  160. package/src/durable/ThreadWork.ts +10 -0
  161. package/src/durable/WakeScheduler.ts +34 -66
  162. package/src/durable/internal/usage-totals.ts +28 -31
  163. package/src/engine/MessagingHost.ts +4 -2
  164. package/src/engine/ThreadHistory.ts +47 -48
  165. package/src/engine/internal/agent-runtime.ts +234 -202
  166. package/src/engine/internal/output-contract.ts +15 -15
  167. package/src/engine/internal/primitive-delta.ts +2 -3
  168. package/src/engine/internal/tool-telemetry.ts +3 -5
  169. package/dist/AgentRuntime-B6qZlD3f.mjs.map +0 -1
  170. package/dist/DurableAgentRuntime-CBBjRNJN.mjs.map +0 -1
  171. package/dist/MessagingHost-CRUK4JBi.mjs.map +0 -1
  172. package/dist/RunContinuation-4ZATcVLB.mjs.map +0 -1
  173. package/dist/RunJournal-CNZqA3Ge.mjs.map +0 -1
  174. package/dist/RunStorage-BHjuuERI.mjs.map +0 -1
  175. package/dist/ThreadStore-59uDGXul.mjs.map +0 -1
@@ -33,11 +33,13 @@ const EncodedMemoryChange = Schema.Struct({
33
33
  resultJson: StoredJson,
34
34
  });
35
35
 
36
- const equivalentContent = Schema.toEquivalence(
37
- Schema.toEncoded(MemoryWrite.Wire.members[0].fields.content),
38
- );
36
+ const makeEquivalentContent = () =>
37
+ Schema.toEquivalence(Schema.toEncoded(MemoryWrite.Wire.members[0].fields.content));
38
+
39
+ let equivalentContent: ReturnType<typeof makeEquivalentContent> | undefined;
39
40
 
40
- const equivalentScopes = Schema.toEquivalence(MemoryWrite.Wire.members[0].fields.scopes);
41
+ const makeEquivalentScopes = () => Schema.toEquivalence(MemoryWrite.Wire.members[0].fields.scopes);
42
+ let equivalentScopes: ReturnType<typeof makeEquivalentScopes> | undefined;
41
43
 
42
44
  /** Ordered commands committed atomically by the SQL memory adapter. */
43
45
  export const SqlMemoryWriteBatch = Schema.Array(MemoryWrite.Wire).check(Schema.isMaxLength(128));
@@ -388,8 +390,8 @@ const validateReceiptResult = Effect.fnUntraced(function* (
388
390
  return yield* storageError(operation, "corrupt");
389
391
  }
390
392
  if (
391
- !equivalentContent(command.content, result.content) ||
392
- !equivalentScopes(command.scopes, result.scopes)
393
+ !(equivalentContent ??= makeEquivalentContent())(command.content, result.content) ||
394
+ !(equivalentScopes ??= makeEquivalentScopes())(command.scopes, result.scopes)
393
395
  ) {
394
396
  return yield* storageError(operation, "corrupt");
395
397
  }
@@ -989,6 +989,10 @@ export type SubmissionLedgerFailure =
989
989
  * An optional `parentLinkage` is recorded immutably at first admission (SUB-016); a replay
990
990
  * whose linkage diverges (present vs. absent, or different identities) fails with
991
991
  * `AdmissionConflict` even when the input digest matches.
992
+ * With `readyIfInitialized`, an adapter may insert a fresh ordinary Submission as ready
993
+ * when it proves Thread initialization under the admission's owner transaction. Readiness,
994
+ * its lifecycle fact and source progress commit with the receipt. Otherwise it returns
995
+ * admitted and the caller completes materialization and `markReady` as usual.
992
996
  * - `resolveAdmission` — authoritative tri-state admission lookup by scoped idempotency key
993
997
  * (SUB-031): `NotAdmitted` proves absence, `Admitted` carries the full snapshot, and
994
998
  * `Indeterminate` states that the adapter cannot currently prove either. Only `NotAdmitted`
@@ -1093,6 +1097,7 @@ export class SubmissionLedger extends Context.Service<
1093
1097
  readonly capabilities: Effect.Effect<LedgerCapabilities>;
1094
1098
  readonly admit: (
1095
1099
  request: AdmissionRequest,
1100
+ options?: { readonly readyIfInitialized?: boolean },
1096
1101
  ) => Effect.Effect<AdmissionResult, AdmissionConflict | AdmissionPolicyError | LedgerError>;
1097
1102
  readonly markReady: (request: MarkReadyRequest) => Effect.Effect<void, LedgerError>;
1098
1103
  readonly lookup: (
@@ -1168,6 +1173,8 @@ export class SubmissionLedger extends Context.Service<
1168
1173
  request: ReleaseChildBudgetRequest,
1169
1174
  ) => Effect.Effect<ChildBudgetReservationSnapshot, ChildReservationConflict | LedgerError>;
1170
1175
  readonly scanNonterminal: Stream.Stream<SubmissionWorkItem, LedgerError>;
1176
+ /** Optional native snapshot for hosts that need the complete control worklist. */
1177
+ readonly listNonterminal?: Effect.Effect<ReadonlyArray<SubmissionWorkItem>, LedgerError>;
1171
1178
  readonly loadRecoverySnapshot: (
1172
1179
  request: RecoverySnapshotRequest,
1173
1180
  ) => Effect.Effect<RecoverySnapshot, LedgerError>;
@@ -415,10 +415,6 @@ const captureExecution = (input: ExecutionUpdate) => {
415
415
  facts.push(record.success);
416
416
  }
417
417
 
418
- // Keep the actual owned arrays within the persisted bounds as well.
419
- validateExecutionRecordCount(operational);
420
- validateExecutionRecordCount(facts);
421
-
422
418
  const checkpoint = Object.freeze(
423
419
  ExecutionCheckpoint.make(
424
420
  {
@@ -446,7 +442,13 @@ const captureExecution = (input: ExecutionUpdate) => {
446
442
  const json = `{"previousRevision":${update.previousRevision},"checkpoint":${checkpointJson},"facts":[${facts.map(({ json }) => json).join(",")}],"retire":${update.retire}}`;
447
443
 
448
444
  return Result.succeed(
449
- Object.freeze({ update, json, checkpointJson, bytes: utf8ByteLength(json) }),
445
+ Object.freeze({
446
+ update,
447
+ json,
448
+ checkpointJson,
449
+ factsJson: JSON.stringify(facts.map(({ wire }) => wire)),
450
+ bytes: utf8ByteLength(json),
451
+ }),
450
452
  );
451
453
  };
452
454
 
@@ -497,6 +499,8 @@ export interface PreparedAppend extends AppendRequest {
497
499
  readonly json: string;
498
500
  /** Checkpoint wire from the same detached, validated execution capture. */
499
501
  readonly checkpointJson: string;
502
+ /** Schema-encoded fact array from this same capture, for the stable retry digest. */
503
+ readonly factsJson: string;
500
504
  readonly bytes: number;
501
505
  }>;
502
506
  readonly records: ReadonlyArray<{
@@ -542,8 +546,6 @@ export const PreparedAppend = {
542
546
  executionWrites.push(yield* captureExecution(executions[index]!));
543
547
  Object.freeze(executionWrites);
544
548
 
545
- validateRecordCount(executionWrites);
546
-
547
549
  if (input.batch.records.length === 0 && executionWrites.length === 0)
548
550
  return yield* Result.fail(
549
551
  ThreadStoreError.make({
@@ -144,6 +144,10 @@ export const ThreadWorkCursor = Schema.Struct({
144
144
 
145
145
  export type ThreadWorkCursor = typeof ThreadWorkCursor.Type;
146
146
 
147
+ /** Local snapshot request; it grants no claim or dispatch authority. */
148
+ export const FreshAdmissionWork = Schema.Struct({ threadId: ThreadId, submissionId: SubmissionId });
149
+ export type FreshAdmissionWork = typeof FreshAdmissionWork.Type;
150
+
147
151
  export const WorkThreadsRequest = Schema.Struct({
148
152
  afterThreadId: Schema.optionalKey(ThreadId),
149
153
  limit: Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 32 })),
@@ -192,6 +196,12 @@ export class WorkDiscoveryUnavailable extends Schema.TaggedError<WorkDiscoveryUn
192
196
 
193
197
  /** Native owner pages and canonical-index maintenance, implemented at the storage owner. */
194
198
  export interface ThreadWorkStorage {
199
+ /** Native proof that this untouched root admission has no other recovery obligations.
200
+ * A caller must still claim and validate current controls before dispatch. */
201
+ readonly isOnlyFreshAdmission?: (
202
+ request: FreshAdmissionWork,
203
+ ) => Effect.Effect<boolean, ThreadStoreError>;
204
+
195
205
  readonly threads: (
196
206
  request: WorkThreadsRequest,
197
207
  ) => Effect.Effect<WorkThreadsPage, ThreadStoreError>;
@@ -1,5 +1,5 @@
1
1
  import type { Scope } from "effect";
2
- import { Context, Deferred, Effect, Layer, Ref, Stream } from "effect";
2
+ import { Context, Deferred, Effect, Layer, Stream } from "effect";
3
3
 
4
4
  import { type ThreadId } from "../core/Identifiers.ts";
5
5
 
@@ -17,79 +17,47 @@ export interface WakeSubscriptionHub {
17
17
  readonly notify: (threadId: ThreadId) => Effect.Effect<void>;
18
18
  }
19
19
 
20
- interface WakeRegistration {
21
- readonly id: number;
22
- readonly deferred: Deferred.Deferred<void>;
23
- }
24
-
25
- type WakeRegistrations = ReadonlyMap<ThreadId, ReadonlyMap<number, Deferred.Deferred<void>>>;
26
-
27
20
  /**
28
21
  * Build the shared per-incarnation registration primitive used by host WakeSchedulers. The hub
29
22
  * contains hints only: eviction may discard it, while a reconnecting caller re-subscribes and
30
23
  * rechecks canonical storage. Scope finalizers remove cancelled waiters, and notify broadcasts
31
24
  * instead of consuming a signal that belongs to another waiter.
32
25
  */
33
- export const makeWakeSubscriptionHub: Effect.Effect<WakeSubscriptionHub> = Effect.gen(function* () {
34
- const registrations = yield* Ref.make<WakeRegistrations>(new Map());
35
- const nextId = yield* Ref.make(0);
36
-
37
- const remove = (threadId: ThreadId, id: number) =>
38
- Ref.update(registrations, (current) => {
39
- const existing = current.get(threadId);
40
-
41
- if (existing === undefined || !existing.has(id)) return current;
42
- const next = new Map(current);
43
- const thread = new Map(existing);
44
-
45
- thread.delete(id);
46
- if (thread.size === 0) {
47
- next.delete(threadId);
48
- } else {
49
- next.set(threadId, thread);
50
- }
51
-
52
- return next;
26
+ export const makeWakeSubscriptionHub: Effect.Effect<WakeSubscriptionHub> = Effect.sync(() => {
27
+ const registrations = new Map<ThreadId, Set<Deferred.Deferred<void>>>();
28
+
29
+ const subscribe = (threadId: ThreadId) =>
30
+ Effect.acquireRelease(
31
+ Effect.sync(() => {
32
+ const deferred = Deferred.makeUnsafe<void>();
33
+ let waiters = registrations.get(threadId);
34
+
35
+ if (waiters === undefined) {
36
+ waiters = new Set();
37
+ registrations.set(threadId, waiters);
38
+ }
39
+ waiters.add(deferred);
40
+
41
+ return deferred;
42
+ }),
43
+ (deferred) =>
44
+ Effect.sync(() => {
45
+ const waiters = registrations.get(threadId);
46
+
47
+ if (waiters?.delete(deferred) && waiters.size === 0) registrations.delete(threadId);
48
+ }),
49
+ ).pipe(Effect.map(Deferred.await));
50
+
51
+ const notify = (threadId: ThreadId) =>
52
+ Effect.sync(() => {
53
+ const waiters = registrations.get(threadId);
54
+
55
+ if (waiters === undefined) return;
56
+ // Detach before resuming: a resumed waiter may register its next wait immediately.
57
+ registrations.delete(threadId);
58
+ for (const waiter of waiters) Deferred.doneUnsafe(waiter, Effect.void);
53
59
  });
54
60
 
55
- const subscribe = Effect.fnUntraced(function* (
56
- threadId: ThreadId,
57
- ): Effect.fn.Return<Effect.Effect<void>, never, Scope.Scope> {
58
- const deferred = yield* Deferred.make<void>();
59
- const id = yield* Ref.getAndUpdate(nextId, (current) => current + 1);
60
- const registration: WakeRegistration = { id, deferred };
61
-
62
- yield* Effect.addFinalizer(() => remove(threadId, registration.id));
63
- yield* Ref.update(registrations, (current) => {
64
- const next = new Map(current);
65
- const thread = new Map(current.get(threadId) ?? []);
66
-
67
- thread.set(registration.id, registration.deferred);
68
- next.set(threadId, thread);
69
-
70
- return next;
71
- });
72
-
73
- return Deferred.await(registration.deferred);
74
- });
75
-
76
- const notify = Effect.fnUntraced(function* (threadId: ThreadId) {
77
- const waiters = yield* Ref.modify(registrations, (current) => {
78
- const thread = current.get(threadId);
79
-
80
- if (thread === undefined) return [[], current] as const;
81
- const next = new Map(current);
82
-
83
- next.delete(threadId);
84
-
85
- return [[...thread.values()], next] as const;
86
- });
87
-
88
- yield* Effect.forEach(waiters, (waiter) => Deferred.succeed(waiter, undefined), {
89
- discard: true,
90
- });
91
- });
92
-
93
61
  return { subscribe, notify };
94
62
  });
95
63
 
@@ -3,36 +3,33 @@ import { Effect } from "effect";
3
3
  import { summarizeModelUsage, type ModelCallUsage } from "../../core/Usage.ts";
4
4
 
5
5
  /** Captured calls are additive and nonnegative; progress does not need attribution groups. */
6
- export const continuationUsage = Effect.fnUntraced(function* (
7
- calls: ReadonlyArray<ModelCallUsage>,
8
- ) {
9
- let inputTokens = 0;
10
- let outputTokens = 0;
11
- let costMicrousd = 0;
12
- let knownWebSearchCalls = 0;
6
+ export const continuationUsage = (calls: ReadonlyArray<ModelCallUsage>) =>
7
+ Effect.suspend(() => {
8
+ let inputTokens = 0;
9
+ let outputTokens = 0;
10
+ let costMicrousd = 0;
11
+ let knownWebSearchCalls = 0;
13
12
 
14
- for (const call of calls) {
15
- inputTokens += call.inputTokens.total;
16
- outputTokens += call.outputTokens.total;
17
- costMicrousd += call.costMicrousd;
18
- knownWebSearchCalls += call.webSearchCalls ?? 0;
19
- }
20
- if (
21
- Number.isSafeInteger(inputTokens) &&
22
- Number.isSafeInteger(outputTokens) &&
23
- Number.isSafeInteger(costMicrousd) &&
24
- Number.isSafeInteger(knownWebSearchCalls)
25
- )
26
- return { modelCalls: calls.length, inputTokens, outputTokens, costMicrousd };
13
+ for (const call of calls) {
14
+ inputTokens += call.inputTokens.total;
15
+ outputTokens += call.outputTokens.total;
16
+ costMicrousd += call.costMicrousd;
17
+ knownWebSearchCalls += call.webSearchCalls ?? 0;
18
+ }
19
+ if (
20
+ Number.isSafeInteger(inputTokens) &&
21
+ Number.isSafeInteger(outputTokens) &&
22
+ Number.isSafeInteger(costMicrousd) &&
23
+ Number.isSafeInteger(knownWebSearchCalls)
24
+ )
25
+ return Effect.succeed({ modelCalls: calls.length, inputTokens, outputTokens, costMicrousd });
27
26
 
28
- // Safe global sums bound every component/group. On overflow retain the full validator,
29
- // including groups whose hosted-search coverage differs from the aggregate's coverage.
30
- const summary = yield* summarizeModelUsage(calls);
31
-
32
- return {
33
- modelCalls: summary.modelCalls,
34
- inputTokens: summary.inputTokens.total,
35
- outputTokens: summary.outputTokens.total,
36
- costMicrousd: summary.costMicrousd,
37
- };
38
- });
27
+ // Safe global sums bound every component/group. On overflow retain the full validator,
28
+ // including groups whose hosted-search coverage differs from the aggregate's coverage.
29
+ return Effect.map(summarizeModelUsage(calls), (summary) => ({
30
+ modelCalls: summary.modelCalls,
31
+ inputTokens: summary.inputTokens.total,
32
+ outputTokens: summary.outputTokens.total,
33
+ costMicrousd: summary.costMicrousd,
34
+ }));
35
+ });
@@ -1,4 +1,4 @@
1
- import type { Effect } from "effect";
1
+ import { Effect } from "effect";
2
2
  import * as Context from "effect/Context";
3
3
 
4
4
  import type { AnyDefinition } from "../core/Agent.ts";
@@ -47,7 +47,9 @@ export class MessagingHost extends Context.Service<
47
47
  }
48
48
  >()("@effect-agent/engine/MessagingHost") {
49
49
  static readonly unavailable: MessagingHost["Service"] = {
50
- context: MessagingError.make({ operation: "context", reason: "unavailable" }),
50
+ context: Effect.suspend(() =>
51
+ MessagingError.make({ operation: "context", reason: "unavailable" }),
52
+ ),
51
53
  send: () => MessagingError.make({ operation: "send", reason: "unavailable" }),
52
54
  reply: () => MessagingError.make({ operation: "reply", reason: "unavailable" }),
53
55
  inbox: () => MessagingError.make({ operation: "inbox", reason: "unavailable" }),
@@ -75,57 +75,56 @@ export class ThreadHistory extends Context.Service<
75
75
  }
76
76
  >()("@effect-agent/engine/ThreadHistory") {
77
77
  /** Bind incremental history to the supplied Thread.Store, including scoped ownership views. */
78
- static readonly layerFromStore = Layer.effect(
79
- ThreadHistory,
80
- Effect.gen(function* () {
81
- const threads = yield* ConversationStore;
78
+ static readonly makeFromStore = Effect.gen(function* () {
79
+ const threads = yield* ConversationStore;
82
80
 
83
- const historyError = (cause: ThreadError): ThreadHistoryError =>
84
- ThreadHistoryError.make({
85
- threadId: cause.threadId,
86
- reason:
87
- cause._tag === "ThreadNotFound"
88
- ? "not-found"
89
- : cause._tag === "ThreadLimitExceeded"
90
- ? "limit"
91
- : cause._tag === "ThreadHistoryDiverged"
92
- ? "conflict"
93
- : cause._tag === "ThreadOwnershipError"
94
- ? cause.reason === "closed"
95
- ? "fenced"
96
- : "conflict"
97
- : "encoding",
98
- message:
99
- cause._tag === "ThreadNotFound"
100
- ? "Thread history is not present in this application Scope"
101
- : cause._tag === "ThreadLimitExceeded"
102
- ? `In-memory history exceeds the ${cause.limit} limit of ${cause.limitValue}`
103
- : cause.message,
104
- cause,
105
- });
81
+ const historyError = (cause: ThreadError): ThreadHistoryError =>
82
+ ThreadHistoryError.make({
83
+ threadId: cause.threadId,
84
+ reason:
85
+ cause._tag === "ThreadNotFound"
86
+ ? "not-found"
87
+ : cause._tag === "ThreadLimitExceeded"
88
+ ? "limit"
89
+ : cause._tag === "ThreadHistoryDiverged"
90
+ ? "conflict"
91
+ : cause._tag === "ThreadOwnershipError"
92
+ ? cause.reason === "closed"
93
+ ? "fenced"
94
+ : "conflict"
95
+ : "encoding",
96
+ message:
97
+ cause._tag === "ThreadNotFound"
98
+ ? "Thread history is not present in this application Scope"
99
+ : cause._tag === "ThreadLimitExceeded"
100
+ ? `In-memory history exceeds the ${cause.limit} limit of ${cause.limitValue}`
101
+ : cause.message,
102
+ cause,
103
+ });
106
104
 
107
- return ThreadHistory.of({
108
- retention: "incremental",
109
- memoryStore: threads,
110
- load: (threadId) =>
111
- threads.snapshot(threadId).pipe(Effect.map(toPrompt), Effect.mapError(historyError)),
112
- open: Effect.fnUntraced(function* ({ threadId, runId }) {
113
- const snapshot = yield* threads.create(threadId).pipe(Effect.mapError(historyError));
105
+ return ThreadHistory.of({
106
+ retention: "incremental",
107
+ memoryStore: threads,
108
+ load: (threadId) =>
109
+ threads.snapshot(threadId).pipe(Effect.map(toPrompt), Effect.mapError(historyError)),
110
+ open: Effect.fnUntraced(function* ({ threadId, runId }) {
111
+ const snapshot = yield* threads.create(threadId).pipe(Effect.mapError(historyError));
114
112
 
115
- return {
116
- prompt: toPrompt(snapshot),
117
- stageInput: () => Effect.void,
118
- stageHistory: ({ source }) =>
119
- threads
120
- .recordHistory(threadId, runId, source)
121
- .pipe(Effect.asVoid, Effect.mapError(historyError)),
122
- commit: () =>
123
- threads.snapshot(threadId).pipe(Effect.asVoid, Effect.mapError(historyError)),
124
- };
125
- }),
126
- });
127
- }),
128
- );
113
+ return {
114
+ prompt: toPrompt(snapshot),
115
+ stageInput: () => Effect.void,
116
+ stageHistory: ({ source }) =>
117
+ threads
118
+ .recordHistory(threadId, runId, source)
119
+ .pipe(Effect.asVoid, Effect.mapError(historyError)),
120
+ commit: () =>
121
+ threads.snapshot(threadId).pipe(Effect.asVoid, Effect.mapError(historyError)),
122
+ };
123
+ }),
124
+ });
125
+ });
126
+
127
+ static readonly layerFromStore = Layer.effect(ThreadHistory, ThreadHistory.makeFromStore);
129
128
 
130
129
  /** Retain history for the application Scope, within message and host capacity. Separate Layer builds own separate stores. */
131
130
  static readonly layer = ThreadHistory.layerFromStore.pipe(Layer.provideMerge(layerMemory));