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

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 (177) hide show
  1. package/dist/{Admin-BHmp2V9p.d.mts → Admin-DTDihBra.d.mts} +50 -13
  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-BJbuIqQq.mjs} +237 -114
  8. package/dist/DurableAgentRuntime-BJbuIqQq.mjs.map +1 -0
  9. package/dist/{EventSource-C9aaJaXT.d.mts → EventSource-Cc58uEso.d.mts} +2 -2
  10. package/dist/InMemory.d.mts +2 -2
  11. package/dist/{MessageDelivery-BGDm-c46.d.mts → MessageDelivery-ClHg7c0O.d.mts} +42 -42
  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-CjcX1OP0.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-4ZATcVLB.mjs → RunContinuation-CWHuciup.mjs} +74 -40
  18. package/dist/RunContinuation-CWHuciup.mjs.map +1 -0
  19. package/dist/{RunContinuation-CTJ1jGrO.d.mts → RunContinuation-DSnUTSSb.d.mts} +11 -5
  20. package/dist/{RunJournal-CNZqA3Ge.mjs → RunJournal-DGblbFas.mjs} +86 -81
  21. package/dist/RunJournal-DGblbFas.mjs.map +1 -0
  22. package/dist/{RunStorage-BHjuuERI.mjs → RunStorage-BF2YgWkg.mjs} +44 -33
  23. package/dist/RunStorage-BF2YgWkg.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-DuC_PcGx.d.mts} +2 -2
  30. package/dist/{SubscriptionInput-BUoXv-_K.d.mts → SubscriptionInput-B1IeAEDi.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-CMgzttvr.d.mts → ThreadImport-D_Biid6w.d.mts} +42 -42
  34. package/dist/{ThreadImport-CodYoJoa.mjs → ThreadImport-ZgWxf7bB.mjs} +8 -8
  35. package/dist/{ThreadImport-CodYoJoa.mjs.map → ThreadImport-ZgWxf7bB.mjs.map} +1 -1
  36. package/dist/{ThreadStore-59uDGXul.mjs → ThreadStore-BC4V-mY-.mjs} +42 -23
  37. package/dist/ThreadStore-BC4V-mY-.mjs.map +1 -0
  38. package/dist/{ThreadTransfer-3jigUl3g.mjs → ThreadTransfer-S7FnTRHt.mjs} +4 -4
  39. package/dist/{ThreadTransfer-3jigUl3g.mjs.map → ThreadTransfer-S7FnTRHt.mjs.map} +1 -1
  40. package/dist/{WorkerAdmission-DDYJdOsU.mjs → WorkerAdmission-C47Vy_cK.mjs} +5 -5
  41. package/dist/{WorkerAdmission-DDYJdOsU.mjs.map → WorkerAdmission-C47Vy_cK.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 +68 -68
  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-DvTEq2RC.d.mts} +2 -2
  151. package/package.json +1 -1
  152. package/src/core/Thread.ts +143 -146
  153. package/src/durable/DurableAgentRuntime.ts +549 -260
  154. package/src/durable/RunContinuation.ts +115 -64
  155. package/src/durable/RunJournal.ts +107 -112
  156. package/src/durable/RunStorage.ts +90 -61
  157. package/src/durable/SqlMemoryStore.ts +8 -6
  158. package/src/durable/SubmissionLedger.ts +15 -0
  159. package/src/durable/ThreadStore.ts +48 -26
  160. package/src/durable/ThreadWork.ts +10 -0
  161. package/src/durable/WakeScheduler.ts +34 -66
  162. package/src/durable/internal/initial-context.ts +17 -2
  163. package/src/durable/internal/prompt-history-cache.ts +13 -8
  164. package/src/durable/internal/usage-totals.ts +28 -31
  165. package/src/engine/MessagingHost.ts +4 -2
  166. package/src/engine/ThreadHistory.ts +47 -48
  167. package/src/engine/internal/agent-runtime.ts +234 -202
  168. package/src/engine/internal/output-contract.ts +15 -15
  169. package/src/engine/internal/primitive-delta.ts +2 -3
  170. package/src/engine/internal/tool-telemetry.ts +3 -5
  171. package/dist/AgentRuntime-B6qZlD3f.mjs.map +0 -1
  172. package/dist/DurableAgentRuntime-CBBjRNJN.mjs.map +0 -1
  173. package/dist/MessagingHost-CRUK4JBi.mjs.map +0 -1
  174. package/dist/RunContinuation-4ZATcVLB.mjs.map +0 -1
  175. package/dist/RunJournal-CNZqA3Ge.mjs.map +0 -1
  176. package/dist/RunStorage-BHjuuERI.mjs.map +0 -1
  177. package/dist/ThreadStore-59uDGXul.mjs.map +0 -1
@@ -343,19 +343,20 @@ export class AppendRequest extends Schema.Class<AppendRequest>(
343
343
  expectedTailSequence: CanonicalSequence,
344
344
  }) {}
345
345
 
346
+ const RecordCount = Schema.Array(Schema.Unknown).check(Schema.isMaxLength(256));
347
+ const ExecutionRecordCount = Schema.Array(Schema.Unknown).check(Schema.isMaxLength(8));
348
+
346
349
  const AppendHeader = Schema.Struct({
347
350
  threadId: ThreadId,
348
351
  expectedTailSequence: CanonicalSequence,
349
352
  batchId: BatchId,
350
353
  producerId: ProducerId,
354
+ records: RecordCount,
355
+ executions: RecordCount,
351
356
  });
352
357
 
353
358
  const validateHeader = Schema.decodeUnknownSync(AppendHeader);
354
359
 
355
- const validateRecordCount = Schema.decodeUnknownSync(
356
- Schema.Array(Schema.Unknown).check(Schema.isMaxLength(256)),
357
- );
358
-
359
360
  const validateExecutionHeader = Schema.decodeUnknownSync(
360
361
  Schema.Struct({
361
362
  previousRevision: ExecutionUpdate.fields.previousRevision,
@@ -366,24 +367,36 @@ const validateExecutionHeader = Schema.decodeUnknownSync(
366
367
  originalSequence: ExecutionCheckpoint.fields.originalSequence,
367
368
  throughSequence: ExecutionCheckpoint.fields.throughSequence,
368
369
  batchId: ExecutionCheckpoint.fields.batchId,
370
+ operational: ExecutionRecordCount,
369
371
  }),
372
+ facts: ExecutionRecordCount,
370
373
  }),
371
374
  );
372
375
 
373
- const validateExecutionRecordCount = Schema.decodeUnknownSync(
374
- Schema.Array(Schema.Unknown).check(Schema.isMaxLength(8)),
375
- );
376
-
377
376
  /** Capture records once; wrapper fields retain their owning Schema's constraints. */
378
377
  const captureExecution = (input: ExecutionUpdate) => {
379
- const header = validateExecutionHeader(input);
380
- const operationalInput = input.checkpoint.operational;
378
+ const checkpointInput = input.checkpoint;
379
+ const operationalInput = checkpointInput.operational;
381
380
  const factsInput = input.facts;
382
381
 
383
- const operationalLength = validateExecutionRecordCount(operationalInput).length;
384
- const factsLength = validateExecutionRecordCount(factsInput).length;
382
+ const header = validateExecutionHeader({
383
+ previousRevision: input.previousRevision,
384
+ retire: input.retire,
385
+ checkpoint: {
386
+ version: checkpointInput.version,
387
+ canonicalRecords: checkpointInput.canonicalRecords,
388
+ originalSequence: checkpointInput.originalSequence,
389
+ throughSequence: checkpointInput.throughSequence,
390
+ batchId: checkpointInput.batchId,
391
+ operational: operationalInput,
392
+ },
393
+ facts: factsInput,
394
+ });
395
+
396
+ const operationalLength = header.checkpoint.operational.length;
397
+ const factsLength = header.facts.length;
385
398
 
386
- const progress = captureRecord(input.checkpoint.progressRecord);
399
+ const progress = captureRecord(checkpointInput.progressRecord);
387
400
 
388
401
  if (Result.isFailure(progress)) return Result.fail(progress.failure);
389
402
  const progressRecord = progress.success.canonical;
@@ -415,10 +428,6 @@ const captureExecution = (input: ExecutionUpdate) => {
415
428
  facts.push(record.success);
416
429
  }
417
430
 
418
- // Keep the actual owned arrays within the persisted bounds as well.
419
- validateExecutionRecordCount(operational);
420
- validateExecutionRecordCount(facts);
421
-
422
431
  const checkpoint = Object.freeze(
423
432
  ExecutionCheckpoint.make(
424
433
  {
@@ -446,7 +455,13 @@ const captureExecution = (input: ExecutionUpdate) => {
446
455
  const json = `{"previousRevision":${update.previousRevision},"checkpoint":${checkpointJson},"facts":[${facts.map(({ json }) => json).join(",")}],"retire":${update.retire}}`;
447
456
 
448
457
  return Result.succeed(
449
- Object.freeze({ update, json, checkpointJson, bytes: utf8ByteLength(json) }),
458
+ Object.freeze({
459
+ update,
460
+ json,
461
+ checkpointJson,
462
+ factsJson: JSON.stringify(facts.map(({ wire }) => wire)),
463
+ bytes: utf8ByteLength(json),
464
+ }),
450
465
  );
451
466
  };
452
467
 
@@ -497,6 +512,8 @@ export interface PreparedAppend extends AppendRequest {
497
512
  readonly json: string;
498
513
  /** Checkpoint wire from the same detached, validated execution capture. */
499
514
  readonly checkpointJson: string;
515
+ /** Schema-encoded fact array from this same capture, for the stable retry digest. */
516
+ readonly factsJson: string;
500
517
  readonly bytes: number;
501
518
  }>;
502
519
  readonly records: ReadonlyArray<{
@@ -526,15 +543,19 @@ export const PreparedAppend = {
526
543
  return Effect.try({
527
544
  try: () =>
528
545
  Result.gen(function* () {
546
+ const executions = input.executions ?? [];
547
+ const recordInputs = input.batch.records;
548
+
529
549
  const header = validateHeader({
530
550
  ...input,
531
551
  batchId: input.batch.batchId,
532
552
  producerId: input.batch.producerId,
553
+ records: recordInputs,
554
+ executions,
533
555
  });
534
556
 
535
- validateRecordCount(input.batch.records);
536
- const executions = input.executions ?? [];
537
- const executionLength = validateRecordCount(executions).length;
557
+ const recordLength = header.records.length;
558
+ const executionLength = header.executions.length;
538
559
 
539
560
  const executionWrites: Array<PreparedAppend["executionWrites"][number]> = [];
540
561
 
@@ -542,9 +563,7 @@ export const PreparedAppend = {
542
563
  executionWrites.push(yield* captureExecution(executions[index]!));
543
564
  Object.freeze(executionWrites);
544
565
 
545
- validateRecordCount(executionWrites);
546
-
547
- if (input.batch.records.length === 0 && executionWrites.length === 0)
566
+ if (recordLength === 0 && executionWrites.length === 0)
548
567
  return yield* Result.fail(
549
568
  ThreadStoreError.make({
550
569
  operation: "prepare durable commit",
@@ -558,8 +577,11 @@ export const PreparedAppend = {
558
577
  message: "Active state exceeds its bounded transfer representation",
559
578
  }),
560
579
  );
561
- // Visit sparse slots too: capture owns each record's validation boundary.
562
- const encodings = yield* Result.all(Array.from(input.batch.records, captureRecord));
580
+ // Visit Schema-validated indexed slots, including sparse ones, never a caller's iterator.
581
+ const encodings = [];
582
+
583
+ for (let index = 0; index < recordLength; index++)
584
+ encodings.push(yield* captureRecord(recordInputs[index]!));
563
585
  const prefix = `{"batchId":${JSON.stringify(header.batchId)},"producerId":${JSON.stringify(header.producerId)},"records":[`;
564
586
  const batchJson = `${prefix}${encodings.map(({ json }) => json).join(",")}]}`;
565
587
 
@@ -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
 
@@ -1,17 +1,32 @@
1
1
  import { Effect, Option, Schema, Stream } from "effect";
2
2
  import { Prompt } from "effect/ai";
3
3
 
4
- import { CanonicalSequence, type CanonicalRecordEnvelope } from "../Records.ts";
4
+ import {
5
+ CanonicalSequence,
6
+ type CanonicalRecordEnvelope,
7
+ type RecordEnvelope,
8
+ type UserInputRecorded,
9
+ } from "../Records.ts";
5
10
  import { RunJournalError, toolCallSettledRecordId } from "../RunJournal.ts";
6
11
  import { submissionSettlementRecordId } from "../SubmissionLedger.ts";
7
12
  import { getRecord, getRunInput, PROMPT_EVIDENCE_TAGS, ThreadReader } from "../ThreadStore.ts";
8
13
  import type { JournalRecordEnvelope } from "./journal-metadata.ts";
9
14
 
15
+ /** A captured original input can prepare context before its atomic publication. It carries
16
+ * an expected position, never a committed observation cursor or dispatch authority. */
17
+ export type InitialContextInput =
18
+ | CanonicalRecordEnvelope
19
+ | {
20
+ readonly threadId: CanonicalRecordEnvelope["threadId"];
21
+ readonly sequence: CanonicalRecordEnvelope["sequence"];
22
+ readonly record: RecordEnvelope & { readonly payload: UserInputRecorded };
23
+ };
24
+
10
25
  const invalid = (message: string) => RunJournalError.make({ message });
11
26
  const promptTag = new Set<string>(PROMPT_EVIDENCE_TAGS);
12
27
 
13
28
  /** Fresh admission trusts validated immutable history; recovery verifies its saved Prompt digest. */
14
- export const initialContext = Effect.fnUntraced(function* (original: CanonicalRecordEnvelope) {
29
+ export const initialContext = Effect.fnUntraced(function* (original: InitialContextInput) {
15
30
  const reader = yield* ThreadReader;
16
31
  const threadId = original.threadId;
17
32
  const through = CanonicalSequence.make(original.sequence - 1);
@@ -6,9 +6,9 @@ import { boundedValueFootprint } from "../../engine/internal/bounded-value.ts";
6
6
  import { canonicalJson, digestCanonicalJson, utf8ByteLength } from "../Digest.ts";
7
7
  import {
8
8
  CanonicalSequence,
9
+ type CanonicalRecordEnvelope,
9
10
  CURRENT_RECORD_FORMAT,
10
11
  PromptRecord,
11
- type CanonicalRecordEnvelope,
12
12
  type Digest,
13
13
  type PromptRecordEnvelope,
14
14
  type RecordEnvelope,
@@ -23,6 +23,7 @@ import {
23
23
  ThreadStore,
24
24
  ThreadTailRequest,
25
25
  } from "../ThreadStore.ts";
26
+ import type { InitialContextInput } from "./initial-context.ts";
26
27
  import type { JournalRecordEnvelope } from "./journal-metadata.ts";
27
28
  import { promptJsonFootprint } from "./prompt-footprint.ts";
28
29
  import { captureRecord } from "./record-encoding.ts";
@@ -149,12 +150,14 @@ const retainedBytes = (value: unknown, json: string, allowance: number) => {
149
150
  return footprint === undefined ? undefined : encodedBytes + 2 * footprint;
150
151
  };
151
152
 
152
- const encodeHistory = Effect.fnUntraced(function* (prompt: Prompt.Prompt, allowance: number) {
153
- const value = yield* encodePrompt(prompt);
154
- const encoded = canonicalJson(value);
153
+ const encodeHistory = (prompt: Prompt.Prompt, allowance: number) =>
154
+ Effect.suspend(() =>
155
+ Effect.mapEager(encodePrompt(prompt), (value) => {
156
+ const encoded = canonicalJson(value);
155
157
 
156
- return { encoded, bytes: retainedBytes(value, encoded, allowance) };
157
- });
158
+ return { encoded, bytes: retainedBytes(value, encoded, allowance) };
159
+ }),
160
+ );
158
161
 
159
162
  // Finish parsing/decoding before suspending for suffix reads. Only the native prefix and its
160
163
  // canonical string leave this Effect; it does not keep a second parsed root in the outer Run.
@@ -252,7 +255,9 @@ const suffixRecordFootprint = (entry: PromptRecordEnvelope, allowance: number) =
252
255
  * acknowledged, consecutive appends. Never authority for writes or recovery; any uncertainty
253
256
  * discards this derivative and completion rereads canonical history.
254
257
  */
255
- export const makeCommittedPromptSuffix = (original: CanonicalRecordEnvelope) => {
258
+ export const makeCommittedPromptSuffix = (
259
+ original: Pick<CanonicalRecordEnvelope, "threadId" | "sequence" | "record">,
260
+ ) => {
256
261
  const records: Array<PromptRecordEnvelope> = [];
257
262
 
258
263
  const runId =
@@ -558,7 +563,7 @@ export const makePromptHistoryCache = Effect.fnUntraced(function* () {
558
563
  const complete = Effect.fnUntraced(
559
564
  function* (
560
565
  writer: WriterIdentity,
561
- original: CanonicalRecordEnvelope,
566
+ original: InitialContextInput,
562
567
  history: PromptHistory,
563
568
  committed?: CommittedPromptSuffix,
564
569
  ) {
@@ -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));