@effect-agent/storage-memory 0.1.0-beta.9 → 0.1.0-beta.91

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 (34) hide show
  1. package/dist/MemoryMessageDeliveryStore.d.mts +15 -0
  2. package/dist/MemoryMessageDeliveryStore.mjs +147 -0
  3. package/dist/MemoryMessageDeliveryStore.mjs.map +1 -0
  4. package/dist/MemoryScheduleStore.d.mts +10 -0
  5. package/dist/MemoryScheduleStore.mjs +169 -0
  6. package/dist/MemoryScheduleStore.mjs.map +1 -0
  7. package/dist/MemorySemanticIndex.d.mts +21 -0
  8. package/dist/MemorySemanticIndex.mjs +222 -0
  9. package/dist/MemorySemanticIndex.mjs.map +1 -0
  10. package/dist/MemorySubmissionLedger.d.mts +24 -0
  11. package/dist/MemorySubmissionLedger.mjs +1058 -0
  12. package/dist/MemorySubmissionLedger.mjs.map +1 -0
  13. package/dist/MemorySubscriptionStore.d.mts +9 -0
  14. package/dist/MemorySubscriptionStore.mjs +849 -0
  15. package/dist/MemorySubscriptionStore.mjs.map +1 -0
  16. package/dist/MemoryThreadStore.d.mts +17 -0
  17. package/dist/MemoryThreadStore.mjs +377 -0
  18. package/dist/MemoryThreadStore.mjs.map +1 -0
  19. package/dist/index.d.mts +7 -30
  20. package/dist/index.mjs +7 -1298
  21. package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
  22. package/package.json +1 -45
  23. package/src/MemoryMessageDeliveryStore.ts +293 -0
  24. package/src/MemoryScheduleStore.ts +328 -0
  25. package/src/MemorySemanticIndex.ts +357 -0
  26. package/src/{memory-ledger.ts → MemorySubmissionLedger.ts} +478 -86
  27. package/src/MemorySubscriptionStore.ts +1549 -0
  28. package/src/MemoryThreadStore.ts +766 -0
  29. package/src/index.ts +6 -2
  30. package/dist/index.mjs.map +0 -1
  31. package/dist/testing.d.mts +0 -2
  32. package/dist/testing.mjs +0 -2
  33. package/src/memory-storage.ts +0 -614
  34. package/src/testing.ts +0 -10
package/dist/index.mjs CHANGED
@@ -1,1298 +1,7 @@
1
- import { AttemptId, ConversationId, ReceiptId, SubmissionId } from "@effect-agent/core";
2
- import { AbortCommand, AbortIntent, AdmissionAdmitted, AdmissionConflict, AdmissionIndeterminate, AdmissionNotAdmitted, AdmissionRequest, AdmissionResult, AppendConflict, AppendResult, ApprovalConflict, ApprovalDecisionCommand, ApprovalDecisionIntent, AttachChildToReservationRequest, BeginChildBudgetReleaseRequest, CanonicalRecordEnvelope, CanonicalSequence, CheckpointRejected, ChildAttachmentSnapshot, ChildBudgetReservationRequest, ChildBudgetReservationSnapshot, ChildReservationConflict, ChildSettledNotification, Claim, ClaimJoiningRequest, ClaimRequest, ConversationExport, ConversationExportRequest, ConversationMaterialization, ConversationNotMaterialized, ConversationObservation, ConversationRead, ConversationStore, ConversationStoreError, ConversationTail, ConversationTailRequest, DEFAULT_OWNERSHIP_LEASE_DURATION, EMPTY_TAIL_DIGEST, FenceRejected, FencedAppendRequest, InputAppliedMarker, JoinSnapshot, JoinedToHost, JoiningClaim, LedgerCapabilities, LedgerError, LoadCheckpointRequest, MarkInputAppliedRequest, MarkJoinedRequest, MarkReadyRequest, MarkUnknownRequest, ObservationOffset, OwnershipLost, OwnershipRenewal, OwnershipSnapshot, OwnershipToken, ProducerEpoch, QueueSequence, RecoverySnapshot, RecoverySnapshotRequest, ReleaseChildBudgetRequest, ReleaseOwnershipRequest, RenewOwnershipRequest, ReservedChildBudget, ReservedSettlement, RevertJoiningRequest, SaveCheckpointRequest, Settlement, SettlementConflict, SettlementFinalization, SettlementReservation, SettlementReservationSnapshot, SubmissionLedger, SubmissionLookup, SubmissionLookupByKey, SubmissionSnapshot, SuspendRequest, SuspensionSnapshot, UnknownResolution, UnknownResolutionCommand, UnknownResolutionConflict, UnknownResolutionIntent, digestCanonicalBatch } from "@effect-agent/session";
3
- import { Clock, Crypto, DateTime, Duration, Effect, Encoding, Layer, Option, PubSub, Ref, Schema, Stream } from "effect";
4
- //#region src/memory-ledger.ts
5
- const MAX_SUBMISSIONS = 65536;
6
- /**
7
- * Lifecycle ordering used to advance-but-never-regress the operational state marker: a reclaimed
8
- * Attempt must not erase progress markers (input-applied, terminalizing) that an earlier Attempt
9
- * already committed.
10
- */
11
- const STATE_RANK = {
12
- admitted: 0,
13
- ready: 1,
14
- joining: 2,
15
- joined: 3,
16
- running: 4,
17
- "input-applied": 5,
18
- suspended: 6,
19
- unknown: 7,
20
- terminalizing: 8,
21
- settled: 9
22
- };
23
- /**
24
- * States in which `claim` never grants the head: the lane is host-owned (`joining`/`joined`)
25
- * or durably blocked (`suspended`/`unknown`) rather than worker-claimable.
26
- */
27
- const BLOCKED_HEAD_STATES = /* @__PURE__ */ new Set([
28
- "joining",
29
- "joined",
30
- "suspended",
31
- "unknown"
32
- ]);
33
- const failure = (error) => ({
34
- _tag: "failure",
35
- error
36
- });
37
- const success = (value) => ({
38
- _tag: "success",
39
- value
40
- });
41
- const ledgerError = (operation, message, cause) => cause === void 0 ? LedgerError.make({
42
- operation,
43
- message
44
- }) : LedgerError.make({
45
- operation,
46
- message,
47
- cause
48
- });
49
- const validate$1 = Effect.fn("MemorySubmissionLedger.validate")((schema, operation, value) => Schema.encodeUnknownEffect(schema)(value).pipe(Effect.flatMap(Schema.decodeUnknownEffect(schema)), Effect.mapError((error) => ledgerError(operation, `Invalid ${operation} request`, error))));
50
- const decodeSubmissionId = Schema.decodeSync(SubmissionId);
51
- const decodeReceiptId = Schema.decodeSync(ReceiptId);
52
- const decodeAttemptId = Schema.decodeSync(AttemptId);
53
- const decodeOwnershipToken = Schema.decodeSync(OwnershipToken);
54
- const decodeQueueSequence = Schema.decodeSync(QueueSequence);
55
- const decodeProducerEpoch = Schema.decodeSync(ProducerEpoch);
56
- const utc = (millis) => DateTime.toUtc(DateTime.makeUnsafe(millis));
57
- const admissionKey = (conversationId, principal, idempotencyKey) => `${conversationId}\u001f${principal}\u001f${idempotencyKey}`;
58
- const toSnapshot = (row) => SubmissionSnapshot.make({
59
- submissionId: row.submissionId,
60
- conversationId: row.conversationId,
61
- queueSequence: row.queueSequence,
62
- principal: row.principal,
63
- idempotencyKey: row.idempotencyKey,
64
- agentId: row.agentId,
65
- agentDigests: row.agentDigests,
66
- deploymentId: row.deploymentId,
67
- inputPayload: row.inputPayload,
68
- inputDigest: row.inputDigest,
69
- receiptId: row.receiptId,
70
- state: row.state,
71
- createdAt: utc(row.createdAtMillis),
72
- ...row.settledOutcome === void 0 ? {} : { settledOutcome: row.settledOutcome },
73
- ...row.readyAtMillis === void 0 ? {} : { readyAt: utc(row.readyAtMillis) },
74
- ...row.parentLinkage === void 0 ? {} : { parentLinkage: row.parentLinkage }
75
- });
76
- const toReservationSnapshot = (row) => ChildBudgetReservationSnapshot.make({
77
- reservationId: row.reservationId,
78
- parentSubmissionId: row.parentSubmissionId,
79
- parentToolCallId: row.parentToolCallId,
80
- status: row.status,
81
- allocation: row.allocation,
82
- allocationDigest: row.allocationDigest,
83
- reservedAt: utc(row.reservedAtMillis),
84
- ...row.childSubmissionId === void 0 ? {} : { childSubmissionId: row.childSubmissionId },
85
- ...row.accounting === void 0 ? {} : { accounting: row.accounting },
86
- ...row.releaseBeganAtMillis === void 0 ? {} : { releaseBeganAt: utc(row.releaseBeganAtMillis) },
87
- ...row.releasedAtMillis === void 0 ? {} : { releasedAt: utc(row.releasedAtMillis) }
88
- });
89
- /** Linkage equality: both absent, or both present naming the same parent Tool Call. */
90
- const sameParentLinkage = (left, right) => left === void 0 ? right === void 0 : right !== void 0 && left.parentSubmissionId === right.parentSubmissionId && left.parentToolCallId === right.parentToolCallId;
91
- const laneEpoch = (state, conversationId) => state.lanes.get(conversationId)?.producerEpoch ?? 0;
92
- const ownershipLost = (state, stored) => OwnershipLost.make({
93
- submissionId: stored.row.submissionId,
94
- actualEpoch: decodeProducerEpoch(laneEpoch(state, stored.row.conversationId))
95
- });
96
- /** The presented token owns the lane only while it matches the live ownership record. */
97
- const ownsLane = (stored, ownershipToken) => stored.ownership !== void 0 && stored.ownership.ownershipToken === ownershipToken;
98
- const withSubmission = (state, stored) => ({
99
- ...state,
100
- submissions: new Map(state.submissions).set(stored.row.submissionId, stored)
101
- });
102
- const withChildReservation = (state, reservation) => ({
103
- ...state,
104
- childReservations: new Map(state.childReservations).set(reservation.reservationId, reservation)
105
- });
106
- const findHead = (state, conversationId) => {
107
- let head;
108
- for (const stored of state.submissions.values()) {
109
- if (stored.row.conversationId !== conversationId || stored.row.state === "settled") continue;
110
- if (head === void 0 || stored.row.queueSequence < head.row.queueSequence) head = stored;
111
- }
112
- return head;
113
- };
114
- /**
115
- * Reference in-memory SubmissionLedger. It implements the full port contract — atomic idempotent
116
- * admission, FIFO-head claims, producer-epoch fencing, Clock-driven ownership leases, idempotent
117
- * settlement reservation/finalization, and durable abort intent — with every transition applied
118
- * as one atomic `Ref.modify`, but its state does not survive the process (`non-durable`).
119
- *
120
- * Adapter-specific semantics within the port's latitude:
121
- *
122
- * - Time comes exclusively from the Effect `Clock` service, so `TestClock` drives lease expiry
123
- * deterministically; no wall clock is consulted.
124
- * - The ownership lease is pinned to `DEFAULT_OWNERSHIP_LEASE_DURATION` (D5); durable adapters
125
- * own the configuration seam.
126
- * - A live lease blocks claims from other producers only: the same `producerId` may reclaim its
127
- * own live lease (restart recovery), which supersedes and fences the prior Attempt's token.
128
- * - Claiming advances `ready` to `running` and otherwise preserves the recorded state, so
129
- * progress markers from an earlier Attempt survive a reclaim.
130
- * - `renewOwnership` keeps the token stable (the port allows rotation); a replayed admission
131
- * reports the Submission's current state alongside the original identities.
132
- * - `claimJoining` walks the strictly-later queue: rows already `joining`/`joined` to the
133
- * SAME host extend the claimed prefix and are skipped, and an aborted-settled row is a
134
- * closed obligation that is also skipped (P7 §7(c)); any other non-`ready` row (an
135
- * `admitted` gap, a non-aborted settled row, foreign-host linkage) breaks the prefix
136
- * conservatively.
137
- * - `markJoined` verifies the token against the HOST's live ownership (the lane is
138
- * host-owned), so a later host Attempt can repair a lost marker from history (DUR-016). The
139
- * join marker reuses the input-applied marker: the joined input IS `input:{sid}`.
140
- * - `suspend` and `markUnknown` refuse when an exact settlement is already reserved
141
- * (`SettlementConflict` with the reserved outcome) — DUR-011's reservation wins.
142
- * - `resolveAdmission` derives its answer from the single strongly consistent store, so it
143
- * never answers `Indeterminate` on its own; the test-only `resolveAdmissionFault` option
144
- * injects the `Indeterminate` classification so SUB-031 callers can be conformance-tested.
145
- * - `recordChildSettled` and `suspend(WaitingForChild)` observe child settlement directly from
146
- * the child rows (single-store latitude); no separate notification marker is stored.
147
- */
148
- const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
149
- const state = yield* Ref.make({
150
- submissions: /* @__PURE__ */ new Map(),
151
- admissionIndex: /* @__PURE__ */ new Map(),
152
- lanes: /* @__PURE__ */ new Map(),
153
- childReservations: /* @__PURE__ */ new Map(),
154
- mintCounter: 0
155
- });
156
- const leaseMillis = Duration.toMillis(DEFAULT_OWNERSHIP_LEASE_DURATION);
157
- const capabilities = Effect.succeed(LedgerCapabilities.make({ durability: "non-durable" }));
158
- const admit = Effect.fn("MemorySubmissionLedger.admit")((unvalidated) => Effect.gen(function* () {
159
- const request = yield* validate$1(AdmissionRequest, "admit", unvalidated);
160
- const nowMillis = yield* Clock.currentTimeMillis;
161
- const decision = yield* Ref.modify(state, (current) => {
162
- const key = admissionKey(request.conversationId, request.principal, request.idempotencyKey);
163
- const existingId = current.admissionIndex.get(key);
164
- if (existingId !== void 0) {
165
- const existing = current.submissions.get(existingId);
166
- if (existing === void 0) return [failure(ledgerError("admit", "Admission index references a missing Submission")), current];
167
- if (existing.row.inputDigest !== request.inputDigest || !sameParentLinkage(existing.row.parentLinkage, request.parentLinkage)) return [failure(AdmissionConflict.make({
168
- conversationId: request.conversationId,
169
- principal: request.principal,
170
- idempotencyKey: request.idempotencyKey,
171
- existingInputDigest: existing.row.inputDigest,
172
- attemptedInputDigest: request.inputDigest
173
- })), current];
174
- return [success(AdmissionResult.make({
175
- submissionId: existing.row.submissionId,
176
- receiptId: existing.row.receiptId,
177
- queueSequence: existing.row.queueSequence,
178
- state: existing.row.state,
179
- replayed: true
180
- })), current];
181
- }
182
- if (current.submissions.size >= MAX_SUBMISSIONS) return [failure(ledgerError("admit", `In-memory submission limit ${MAX_SUBMISSIONS} exceeded`)), current];
183
- const lane = current.lanes.get(request.conversationId) ?? {
184
- nextQueueSequence: 1,
185
- producerEpoch: 0
186
- };
187
- const mintCounter = current.mintCounter + 1;
188
- const row = {
189
- submissionId: decodeSubmissionId(`submission-memory-${mintCounter}`),
190
- conversationId: request.conversationId,
191
- queueSequence: decodeQueueSequence(lane.nextQueueSequence),
192
- principal: request.principal,
193
- idempotencyKey: request.idempotencyKey,
194
- agentId: request.agentId,
195
- agentDigests: request.agentDigests,
196
- deploymentId: request.deploymentId,
197
- inputPayload: request.inputPayload,
198
- inputDigest: request.inputDigest,
199
- receiptId: decodeReceiptId(`receipt-memory-${mintCounter}`),
200
- state: "admitted",
201
- settledOutcome: void 0,
202
- createdAtMillis: nowMillis,
203
- readyAtMillis: void 0,
204
- parentLinkage: request.parentLinkage
205
- };
206
- const submissions = new Map(current.submissions).set(row.submissionId, {
207
- row,
208
- ownership: void 0,
209
- inputApplied: void 0,
210
- reservation: void 0,
211
- abortIntent: void 0,
212
- joinedHostSubmissionId: void 0,
213
- suspension: void 0,
214
- unknownMark: void 0,
215
- approvalDecisions: /* @__PURE__ */ new Map(),
216
- unknownResolutions: /* @__PURE__ */ new Map()
217
- });
218
- const admissionIndex = new Map(current.admissionIndex).set(key, row.submissionId);
219
- const lanes = new Map(current.lanes).set(request.conversationId, {
220
- nextQueueSequence: lane.nextQueueSequence + 1,
221
- producerEpoch: lane.producerEpoch
222
- });
223
- return [success(AdmissionResult.make({
224
- submissionId: row.submissionId,
225
- receiptId: row.receiptId,
226
- queueSequence: row.queueSequence,
227
- state: row.state,
228
- replayed: false
229
- })), {
230
- ...current,
231
- submissions,
232
- admissionIndex,
233
- lanes,
234
- mintCounter
235
- }];
236
- });
237
- if (decision._tag === "failure") return yield* decision.error;
238
- return decision.value;
239
- }));
240
- const markReady = Effect.fn("MemorySubmissionLedger.markReady")((unvalidated) => Effect.gen(function* () {
241
- const request = yield* validate$1(MarkReadyRequest, "markReady", unvalidated);
242
- const nowMillis = yield* Clock.currentTimeMillis;
243
- const decision = yield* Ref.modify(state, (current) => {
244
- const stored = current.submissions.get(request.submissionId);
245
- if (stored === void 0) return [failure(ledgerError("markReady", `Unknown Submission ${request.submissionId}`)), current];
246
- if (stored.row.state !== "admitted") return [success(void 0), current];
247
- return [success(void 0), withSubmission(current, {
248
- ...stored,
249
- row: {
250
- ...stored.row,
251
- state: "ready",
252
- readyAtMillis: nowMillis
253
- }
254
- })];
255
- });
256
- if (decision._tag === "failure") return yield* decision.error;
257
- }));
258
- const lookup = Effect.fn("MemorySubmissionLedger.lookup")((unvalidated) => Effect.gen(function* () {
259
- const request = yield* validate$1(SubmissionLookup, "lookup", unvalidated);
260
- const current = yield* Ref.get(state);
261
- const submissionId = request._tag === "SubmissionLookupById" ? request.submissionId : current.admissionIndex.get(admissionKey(request.conversationId, request.principal, request.idempotencyKey));
262
- const stored = submissionId === void 0 ? void 0 : current.submissions.get(submissionId);
263
- return stored === void 0 ? Option.none() : Option.some(toSnapshot(stored.row));
264
- }));
265
- const resolveAdmission = Effect.fn("MemorySubmissionLedger.resolveAdmission")((unvalidated) => Effect.gen(function* () {
266
- const request = yield* validate$1(SubmissionLookupByKey, "resolveAdmission", unvalidated);
267
- if (options.resolveAdmissionFault !== void 0) {
268
- const fault = yield* options.resolveAdmissionFault;
269
- if (Option.isSome(fault)) return AdmissionIndeterminate.make({ reason: fault.value });
270
- }
271
- const current = yield* Ref.get(state);
272
- const submissionId = current.admissionIndex.get(admissionKey(request.conversationId, request.principal, request.idempotencyKey));
273
- const stored = submissionId === void 0 ? void 0 : current.submissions.get(submissionId);
274
- return stored === void 0 ? AdmissionNotAdmitted.make() : AdmissionAdmitted.make({ submission: toSnapshot(stored.row) });
275
- }));
276
- const claim = Effect.fn("MemorySubmissionLedger.claim")((unvalidated) => Effect.gen(function* () {
277
- const request = yield* validate$1(ClaimRequest, "claim", unvalidated);
278
- const nowMillis = yield* Clock.currentTimeMillis;
279
- const decision = yield* Ref.modify(state, (current) => {
280
- const head = findHead(current, request.conversationId);
281
- if (head === void 0) return [success(Option.none()), current];
282
- if (BLOCKED_HEAD_STATES.has(head.row.state)) return [success(Option.none()), current];
283
- if (head.ownership !== void 0 && head.ownership.leaseExpiresAtMillis > nowMillis && head.ownership.ownerProducerId !== request.producerId) return [success(Option.none()), current];
284
- const lane = current.lanes.get(request.conversationId);
285
- if (lane === void 0) return [failure(ledgerError("claim", "Claimable head without a Conversation lane")), current];
286
- const producerEpoch = decodeProducerEpoch(lane.producerEpoch + 1);
287
- const mintCounter = current.mintCounter + 1;
288
- const ownership = {
289
- attemptId: decodeAttemptId(`attempt-memory-${mintCounter}`),
290
- ownershipToken: decodeOwnershipToken(`ownership-memory-${mintCounter}`),
291
- producerEpoch,
292
- ownerProducerId: request.producerId,
293
- leaseExpiresAtMillis: nowMillis + leaseMillis
294
- };
295
- const row = head.row.state === "ready" ? {
296
- ...head.row,
297
- state: "running"
298
- } : head.row;
299
- const next = withSubmission(current, {
300
- ...head,
301
- row,
302
- ownership
303
- });
304
- const lanes = new Map(next.lanes).set(request.conversationId, {
305
- nextQueueSequence: lane.nextQueueSequence,
306
- producerEpoch: lane.producerEpoch + 1
307
- });
308
- return [success(Option.some(Claim.make({
309
- submissionId: row.submissionId,
310
- attemptId: ownership.attemptId,
311
- ownershipToken: ownership.ownershipToken,
312
- producerEpoch,
313
- leaseExpiresAt: utc(ownership.leaseExpiresAtMillis),
314
- inputPayload: row.inputPayload
315
- }))), {
316
- ...next,
317
- lanes,
318
- mintCounter
319
- }];
320
- });
321
- if (decision._tag === "failure") return yield* decision.error;
322
- return decision.value;
323
- }));
324
- const renewOwnership = Effect.fn("MemorySubmissionLedger.renewOwnership")((unvalidated) => Effect.gen(function* () {
325
- const request = yield* validate$1(RenewOwnershipRequest, "renewOwnership", unvalidated);
326
- const nowMillis = yield* Clock.currentTimeMillis;
327
- const decision = yield* Ref.modify(state, (current) => {
328
- const stored = current.submissions.get(request.submissionId);
329
- if (stored === void 0) return [failure(ledgerError("renewOwnership", `Unknown Submission ${request.submissionId}`)), current];
330
- if (stored.ownership === void 0 || !ownsLane(stored, request.ownershipToken)) return [failure(ownershipLost(current, stored)), current];
331
- const ownership = {
332
- ...stored.ownership,
333
- leaseExpiresAtMillis: nowMillis + leaseMillis
334
- };
335
- return [success(OwnershipRenewal.make({
336
- ownershipToken: ownership.ownershipToken,
337
- leaseExpiresAt: utc(ownership.leaseExpiresAtMillis)
338
- })), withSubmission(current, {
339
- ...stored,
340
- ownership
341
- })];
342
- });
343
- if (decision._tag === "failure") return yield* decision.error;
344
- return decision.value;
345
- }));
346
- const releaseOwnership = Effect.fn("MemorySubmissionLedger.releaseOwnership")((unvalidated) => Effect.gen(function* () {
347
- const request = yield* validate$1(ReleaseOwnershipRequest, "releaseOwnership", unvalidated);
348
- const decision = yield* Ref.modify(state, (current) => {
349
- const stored = current.submissions.get(request.submissionId);
350
- if (stored === void 0) return [failure(ledgerError("releaseOwnership", `Unknown Submission ${request.submissionId}`)), current];
351
- if (!ownsLane(stored, request.ownershipToken)) return [failure(ownershipLost(current, stored)), current];
352
- return [success(void 0), withSubmission(current, {
353
- ...stored,
354
- ownership: void 0
355
- })];
356
- });
357
- if (decision._tag === "failure") return yield* decision.error;
358
- }));
359
- const markInputApplied = Effect.fn("MemorySubmissionLedger.markInputApplied")((unvalidated) => Effect.gen(function* () {
360
- const request = yield* validate$1(MarkInputAppliedRequest, "markInputApplied", unvalidated);
361
- const decision = yield* Ref.modify(state, (current) => {
362
- const stored = current.submissions.get(request.submissionId);
363
- if (stored === void 0) return [failure(ledgerError("markInputApplied", `Unknown Submission ${request.submissionId}`)), current];
364
- if (!ownsLane(stored, request.ownershipToken)) return [failure(ownershipLost(current, stored)), current];
365
- const marker = InputAppliedMarker.make({
366
- recordId: request.recordId,
367
- sequence: request.sequence
368
- });
369
- const row = STATE_RANK[stored.row.state] < STATE_RANK["input-applied"] ? {
370
- ...stored.row,
371
- state: "input-applied"
372
- } : stored.row;
373
- return [success(void 0), withSubmission(current, {
374
- ...stored,
375
- row,
376
- inputApplied: marker
377
- })];
378
- });
379
- if (decision._tag === "failure") return yield* decision.error;
380
- }));
381
- const reserveSettlement = Effect.fn("MemorySubmissionLedger.reserveSettlement")((unvalidated) => Effect.gen(function* () {
382
- const request = yield* validate$1(SettlementReservation, "reserveSettlement", unvalidated);
383
- const decision = yield* Ref.modify(state, (current) => {
384
- const stored = current.submissions.get(request.submissionId);
385
- if (stored === void 0) return [failure(ledgerError("reserveSettlement", `Unknown Submission ${request.submissionId}`)), current];
386
- const joinedSettlement = stored.row.state === "joined" && stored.joinedHostSubmissionId !== void 0;
387
- const queuedAbortSettlement = request.outcome === "aborted" && stored.abortIntent !== void 0 && stored.ownership === void 0 && (stored.row.state === "ready" || stored.row.state === "terminalizing");
388
- if (!joinedSettlement && !queuedAbortSettlement && !ownsLane(stored, request.ownershipToken)) return [failure(ownershipLost(current, stored)), current];
389
- const existing = stored.reservation;
390
- if (existing !== void 0) {
391
- if (existing.settlementId !== request.settlementId || existing.outcome !== request.outcome || existing.recordDigest !== request.recordDigest) return [failure(SettlementConflict.make({
392
- submissionId: request.submissionId,
393
- existingOutcome: existing.outcome
394
- })), current];
395
- return [success(ReservedSettlement.make({
396
- submissionId: request.submissionId,
397
- settlementId: existing.settlementId,
398
- outcome: existing.outcome,
399
- record: existing.record,
400
- recordDigest: existing.recordDigest,
401
- replayed: true
402
- })), current];
403
- }
404
- const reservation = {
405
- settlementId: request.settlementId,
406
- outcome: request.outcome,
407
- record: request.record,
408
- recordDigest: request.recordDigest,
409
- finalizedAtMillis: void 0
410
- };
411
- const row = STATE_RANK[stored.row.state] < STATE_RANK.terminalizing ? {
412
- ...stored.row,
413
- state: "terminalizing"
414
- } : stored.row;
415
- return [success(ReservedSettlement.make({
416
- submissionId: request.submissionId,
417
- settlementId: reservation.settlementId,
418
- outcome: reservation.outcome,
419
- record: reservation.record,
420
- recordDigest: reservation.recordDigest,
421
- replayed: false
422
- })), withSubmission(current, {
423
- ...stored,
424
- row,
425
- reservation
426
- })];
427
- });
428
- if (decision._tag === "failure") return yield* decision.error;
429
- return decision.value;
430
- }));
431
- const finalizeSettlement = Effect.fn("MemorySubmissionLedger.finalizeSettlement")((unvalidated) => Effect.gen(function* () {
432
- const request = yield* validate$1(SettlementFinalization, "finalizeSettlement", unvalidated);
433
- const nowMillis = yield* Clock.currentTimeMillis;
434
- const decision = yield* Ref.modify(state, (current) => {
435
- const stored = current.submissions.get(request.submissionId);
436
- if (stored === void 0) return [failure(ledgerError("finalizeSettlement", `Unknown Submission ${request.submissionId}`)), current];
437
- const reservation = stored.reservation;
438
- if (reservation === void 0) return [failure(ledgerError("finalizeSettlement", `No settlement reservation for Submission ${request.submissionId}`)), current];
439
- if (reservation.settlementId !== request.settlementId) return [failure(SettlementConflict.make({
440
- submissionId: request.submissionId,
441
- existingOutcome: reservation.outcome
442
- })), current];
443
- if (reservation.finalizedAtMillis !== void 0) return [success(Settlement.make({
444
- submissionId: stored.row.submissionId,
445
- settlementId: reservation.settlementId,
446
- receiptId: stored.row.receiptId,
447
- outcome: reservation.outcome,
448
- settledAt: utc(reservation.finalizedAtMillis)
449
- })), current];
450
- const next = withSubmission(current, {
451
- ...stored,
452
- row: {
453
- ...stored.row,
454
- state: "settled",
455
- settledOutcome: reservation.outcome
456
- },
457
- ownership: void 0,
458
- reservation: {
459
- ...reservation,
460
- finalizedAtMillis: nowMillis
461
- }
462
- });
463
- return [success(Settlement.make({
464
- submissionId: stored.row.submissionId,
465
- settlementId: reservation.settlementId,
466
- receiptId: stored.row.receiptId,
467
- outcome: reservation.outcome,
468
- settledAt: utc(nowMillis)
469
- })), next];
470
- });
471
- if (decision._tag === "failure") return yield* decision.error;
472
- return decision.value;
473
- }));
474
- const requestAbort = Effect.fn("MemorySubmissionLedger.requestAbort")((unvalidated) => Effect.gen(function* () {
475
- const request = yield* validate$1(AbortCommand, "requestAbort", unvalidated);
476
- const nowMillis = yield* Clock.currentTimeMillis;
477
- const decision = yield* Ref.modify(state, (current) => {
478
- const stored = current.submissions.get(request.submissionId);
479
- if (stored === void 0) return [failure(ledgerError("requestAbort", `Unknown Submission ${request.submissionId}`)), current];
480
- if (stored.row.state === "joined") {
481
- if (stored.joinedHostSubmissionId === void 0) return [failure(ledgerError("requestAbort", `Joined Submission ${request.submissionId} is missing its host linkage`)), current];
482
- return [failure(JoinedToHost.make({
483
- submissionId: request.submissionId,
484
- hostSubmissionId: stored.joinedHostSubmissionId
485
- })), current];
486
- }
487
- if (stored.row.state === "settled") {
488
- if (stored.row.settledOutcome === void 0) return [failure(ledgerError("requestAbort", `Settled Submission ${request.submissionId} is missing its outcome`)), current];
489
- return [failure(SettlementConflict.make({
490
- submissionId: request.submissionId,
491
- existingOutcome: stored.row.settledOutcome
492
- })), current];
493
- }
494
- if (stored.abortIntent !== void 0) return [success(stored.abortIntent), current];
495
- const intent = AbortIntent.make({
496
- submissionId: request.submissionId,
497
- author: request.author,
498
- reason: request.reason,
499
- requestedAt: utc(nowMillis)
500
- });
501
- return [success(intent), withSubmission(current, {
502
- ...stored,
503
- abortIntent: intent
504
- })];
505
- });
506
- if (decision._tag === "failure") return yield* decision.error;
507
- return decision.value;
508
- }));
509
- const claimJoining = Effect.fn("MemorySubmissionLedger.claimJoining")((unvalidated) => Effect.gen(function* () {
510
- const request = yield* validate$1(ClaimJoiningRequest, "claimJoining", unvalidated);
511
- const decision = yield* Ref.modify(state, (current) => {
512
- const host = current.submissions.get(request.hostSubmissionId);
513
- if (host === void 0) return [failure(ledgerError("claimJoining", `Unknown Submission ${request.hostSubmissionId}`)), current];
514
- if (host.row.conversationId !== request.conversationId) return [failure(ledgerError("claimJoining", `Host Submission ${request.hostSubmissionId} does not belong to Conversation ${request.conversationId}`)), current];
515
- if (!ownsLane(host, request.ownershipToken)) return [failure(ownershipLost(current, host)), current];
516
- const later = [...current.submissions.values()].filter((stored) => stored.row.conversationId === request.conversationId && stored.row.queueSequence > host.row.queueSequence).sort((left, right) => left.row.queueSequence - right.row.queueSequence);
517
- const claims = [];
518
- const submissions = new Map(current.submissions);
519
- for (const stored of later) {
520
- if (claims.length >= request.maxCount) break;
521
- if ((stored.row.state === "joining" || stored.row.state === "joined") && stored.joinedHostSubmissionId === request.hostSubmissionId) continue;
522
- if (stored.row.state === "settled" && stored.row.settledOutcome === "aborted") continue;
523
- if (stored.row.state !== "ready") break;
524
- submissions.set(stored.row.submissionId, {
525
- ...stored,
526
- row: {
527
- ...stored.row,
528
- state: "joining"
529
- },
530
- joinedHostSubmissionId: request.hostSubmissionId
531
- });
532
- claims.push(JoiningClaim.make({
533
- submissionId: stored.row.submissionId,
534
- queueSequence: stored.row.queueSequence,
535
- inputPayload: stored.row.inputPayload
536
- }));
537
- }
538
- return [success(claims), {
539
- ...current,
540
- submissions
541
- }];
542
- });
543
- if (decision._tag === "failure") return yield* decision.error;
544
- return decision.value;
545
- }));
546
- const markJoined = Effect.fn("MemorySubmissionLedger.markJoined")((unvalidated) => Effect.gen(function* () {
547
- const request = yield* validate$1(MarkJoinedRequest, "markJoined", unvalidated);
548
- const decision = yield* Ref.modify(state, (current) => {
549
- const stored = current.submissions.get(request.submissionId);
550
- if (stored === void 0) return [failure(ledgerError("markJoined", `Unknown Submission ${request.submissionId}`)), current];
551
- if (stored.joinedHostSubmissionId === void 0) return [failure(ledgerError("markJoined", `Submission ${request.submissionId} was never claimed for joining`)), current];
552
- const host = current.submissions.get(stored.joinedHostSubmissionId);
553
- if (host === void 0) return [failure(ledgerError("markJoined", `Host Submission ${stored.joinedHostSubmissionId} is missing`)), current];
554
- if (!ownsLane(host, request.ownershipToken)) return [failure(ownershipLost(current, host)), current];
555
- if (stored.inputApplied !== void 0) {
556
- if (stored.inputApplied.recordId === request.recordId && stored.inputApplied.sequence === request.sequence) return [success(void 0), current];
557
- return [failure(ledgerError("markJoined", `A different join marker is already recorded for Submission ${request.submissionId}`)), current];
558
- }
559
- if (stored.row.state !== "joining" && stored.row.state !== "joined") return [failure(ledgerError("markJoined", `Cannot mark Submission ${request.submissionId} joined from state ${stored.row.state}`)), current];
560
- const marker = InputAppliedMarker.make({
561
- recordId: request.recordId,
562
- sequence: request.sequence
563
- });
564
- return [success(void 0), withSubmission(current, {
565
- ...stored,
566
- row: {
567
- ...stored.row,
568
- state: "joined"
569
- },
570
- inputApplied: marker
571
- })];
572
- });
573
- if (decision._tag === "failure") return yield* decision.error;
574
- }));
575
- const revertJoining = Effect.fn("MemorySubmissionLedger.revertJoining")((unvalidated) => Effect.gen(function* () {
576
- const request = yield* validate$1(RevertJoiningRequest, "revertJoining", unvalidated);
577
- const decision = yield* Ref.modify(state, (current) => {
578
- const stored = current.submissions.get(request.submissionId);
579
- if (stored === void 0) return [failure(ledgerError("revertJoining", `Unknown Submission ${request.submissionId}`)), current];
580
- if (stored.row.state !== "joining") return [success(void 0), current];
581
- return [success(void 0), withSubmission(current, {
582
- ...stored,
583
- row: {
584
- ...stored.row,
585
- state: "ready"
586
- },
587
- joinedHostSubmissionId: void 0
588
- })];
589
- });
590
- if (decision._tag === "failure") return yield* decision.error;
591
- }));
592
- const suspend = Effect.fn("MemorySubmissionLedger.suspend")((unvalidated) => Effect.gen(function* () {
593
- const request = yield* validate$1(SuspendRequest, "suspend", unvalidated);
594
- const nowMillis = yield* Clock.currentTimeMillis;
595
- const decision = yield* Ref.modify(state, (current) => {
596
- const stored = current.submissions.get(request.submissionId);
597
- if (stored === void 0) return [failure(ledgerError("suspend", `Unknown Submission ${request.submissionId}`)), current];
598
- if (stored.row.state === "settled") {
599
- if (stored.row.settledOutcome === void 0) return [failure(ledgerError("suspend", `Settled Submission ${request.submissionId} is missing its outcome`)), current];
600
- return [failure(SettlementConflict.make({
601
- submissionId: request.submissionId,
602
- existingOutcome: stored.row.settledOutcome
603
- })), current];
604
- }
605
- if (stored.reservation !== void 0) return [failure(SettlementConflict.make({
606
- submissionId: request.submissionId,
607
- existingOutcome: stored.reservation.outcome
608
- })), current];
609
- if (!ownsLane(stored, request.ownershipToken)) return [failure(ownershipLost(current, stored)), current];
610
- if (request.reason._tag === "ApprovalPending" ? request.reason.toolCallIds.every((toolCallId) => stored.approvalDecisions.has(toolCallId)) : request.reason.children.every((child) => current.submissions.get(child.childSubmissionId)?.row.state === "settled")) return [success("resume-immediately"), current];
611
- return [success("suspended"), withSubmission(current, {
612
- ...stored,
613
- row: {
614
- ...stored.row,
615
- state: "suspended"
616
- },
617
- ownership: void 0,
618
- suspension: {
619
- reason: request.reason,
620
- suspendedAtMillis: nowMillis
621
- }
622
- })];
623
- });
624
- if (decision._tag === "failure") return yield* decision.error;
625
- return decision.value;
626
- }));
627
- const recordApprovalDecision = Effect.fn("MemorySubmissionLedger.recordApprovalDecision")((unvalidated) => Effect.gen(function* () {
628
- const command = yield* validate$1(ApprovalDecisionCommand, "recordApprovalDecision", unvalidated);
629
- const nowMillis = yield* Clock.currentTimeMillis;
630
- const decision = yield* Ref.modify(state, (current) => {
631
- const stored = current.submissions.get(command.submissionId);
632
- if (stored === void 0) return [failure(ledgerError("recordApprovalDecision", `Unknown Submission ${command.submissionId}`)), current];
633
- if (stored.row.state === "settled") {
634
- if (stored.row.settledOutcome === void 0) return [failure(ledgerError("recordApprovalDecision", `Settled Submission ${command.submissionId} is missing its outcome`)), current];
635
- return [failure(SettlementConflict.make({
636
- submissionId: command.submissionId,
637
- existingOutcome: stored.row.settledOutcome
638
- })), current];
639
- }
640
- const existing = stored.approvalDecisions.get(command.toolCallId);
641
- if (existing !== void 0) {
642
- if (existing.decision !== command.decision) return [failure(ApprovalConflict.make({
643
- submissionId: command.submissionId,
644
- toolCallId: command.toolCallId,
645
- existingDecision: existing.decision
646
- })), current];
647
- return [success(existing), current];
648
- }
649
- const intent = ApprovalDecisionIntent.make({
650
- submissionId: command.submissionId,
651
- toolCallId: command.toolCallId,
652
- decision: command.decision,
653
- resolver: command.resolver,
654
- reason: command.reason,
655
- decidedAt: utc(nowMillis)
656
- });
657
- const approvalDecisions = new Map(stored.approvalDecisions).set(command.toolCallId, intent);
658
- const wakes = stored.row.state === "suspended" && stored.suspension !== void 0 && stored.suspension.reason._tag === "ApprovalPending" && stored.suspension.reason.toolCallIds.every((toolCallId) => approvalDecisions.has(toolCallId));
659
- return [success(intent), withSubmission(current, {
660
- ...stored,
661
- row: wakes ? {
662
- ...stored.row,
663
- state: "input-applied"
664
- } : stored.row,
665
- suspension: wakes ? void 0 : stored.suspension,
666
- approvalDecisions
667
- })];
668
- });
669
- if (decision._tag === "failure") return yield* decision.error;
670
- return decision.value;
671
- }));
672
- const markUnknown = Effect.fn("MemorySubmissionLedger.markUnknown")((unvalidated) => Effect.gen(function* () {
673
- const request = yield* validate$1(MarkUnknownRequest, "markUnknown", unvalidated);
674
- const decision = yield* Ref.modify(state, (current) => {
675
- const stored = current.submissions.get(request.submissionId);
676
- if (stored === void 0) return [failure(ledgerError("markUnknown", `Unknown Submission ${request.submissionId}`)), current];
677
- if (stored.row.state === "settled") {
678
- if (stored.row.settledOutcome === void 0) return [failure(ledgerError("markUnknown", `Settled Submission ${request.submissionId} is missing its outcome`)), current];
679
- return [failure(SettlementConflict.make({
680
- submissionId: request.submissionId,
681
- existingOutcome: stored.row.settledOutcome
682
- })), current];
683
- }
684
- if (stored.reservation !== void 0) return [failure(SettlementConflict.make({
685
- submissionId: request.submissionId,
686
- existingOutcome: stored.reservation.outcome
687
- })), current];
688
- const existing = stored.unknownMark;
689
- const known = new Set(existing?.toolCallIds ?? []);
690
- const merged = [...existing?.toolCallIds ?? [], ...request.toolCallIds.filter((toolCallId) => !known.has(toolCallId))];
691
- return [success(void 0), withSubmission(current, {
692
- ...stored,
693
- row: stored.row.state === "unknown" ? stored.row : {
694
- ...stored.row,
695
- state: "unknown"
696
- },
697
- unknownMark: {
698
- reason: existing?.reason ?? request.reason,
699
- toolCallIds: merged
700
- }
701
- })];
702
- });
703
- if (decision._tag === "failure") return yield* decision.error;
704
- }));
705
- const recordUnknownResolution = Effect.fn("MemorySubmissionLedger.recordUnknownResolution")((unvalidated) => Effect.gen(function* () {
706
- const command = yield* validate$1(UnknownResolutionCommand, "recordUnknownResolution", unvalidated);
707
- const nowMillis = yield* Clock.currentTimeMillis;
708
- const encodedResolution = yield* Schema.encodeUnknownEffect(UnknownResolution)(command.resolution).pipe(Effect.mapError((error) => ledgerError("recordUnknownResolution", "Invalid unknown-outcome resolution", error)));
709
- const resolutionJson = JSON.stringify(encodedResolution);
710
- const decision = yield* Ref.modify(state, (current) => {
711
- const stored = current.submissions.get(command.submissionId);
712
- if (stored === void 0) return [failure(ledgerError("recordUnknownResolution", `Unknown Submission ${command.submissionId}`)), current];
713
- if (stored.row.state === "settled") {
714
- if (stored.row.settledOutcome === void 0) return [failure(ledgerError("recordUnknownResolution", `Settled Submission ${command.submissionId} is missing its outcome`)), current];
715
- return [failure(SettlementConflict.make({
716
- submissionId: command.submissionId,
717
- existingOutcome: stored.row.settledOutcome
718
- })), current];
719
- }
720
- const existing = stored.unknownResolutions.get(command.toolCallId);
721
- if (existing !== void 0 && existing.resolutionJson !== resolutionJson) return [failure(UnknownResolutionConflict.make({
722
- submissionId: command.submissionId,
723
- toolCallId: command.toolCallId
724
- })), current];
725
- const intent = existing?.intent ?? UnknownResolutionIntent.make({
726
- submissionId: command.submissionId,
727
- toolCallId: command.toolCallId,
728
- author: command.author,
729
- reason: command.reason,
730
- resolution: command.resolution,
731
- resolvedAt: utc(nowMillis)
732
- });
733
- const unknownResolutions = existing !== void 0 ? stored.unknownResolutions : new Map(stored.unknownResolutions).set(command.toolCallId, {
734
- intent,
735
- resolutionJson
736
- });
737
- const wakes = stored.row.state === "unknown" && stored.unknownMark !== void 0 && stored.unknownMark.toolCallIds.every((toolCallId) => unknownResolutions.has(toolCallId));
738
- return [success(intent), withSubmission(current, {
739
- ...stored,
740
- row: wakes ? {
741
- ...stored.row,
742
- state: "input-applied"
743
- } : stored.row,
744
- unknownMark: wakes ? void 0 : stored.unknownMark,
745
- unknownResolutions
746
- })];
747
- });
748
- if (decision._tag === "failure") return yield* decision.error;
749
- return decision.value;
750
- }));
751
- const recordChildSettled = Effect.fn("MemorySubmissionLedger.recordChildSettled")((unvalidated) => Effect.gen(function* () {
752
- const request = yield* validate$1(ChildSettledNotification, "recordChildSettled", unvalidated);
753
- const decision = yield* Ref.modify(state, (current) => {
754
- const parent = current.submissions.get(request.parentSubmissionId);
755
- if (parent === void 0) return [failure(ledgerError("recordChildSettled", `Unknown Submission ${request.parentSubmissionId}`)), current];
756
- const child = current.submissions.get(request.childSubmissionId);
757
- if (child === void 0 || child.row.state !== "settled") return [failure(ledgerError("recordChildSettled", `Child Submission ${request.childSubmissionId} has no recorded settlement`)), current];
758
- if (parent.row.state !== "suspended" || parent.suspension === void 0 || parent.suspension.reason._tag !== "WaitingForChild") return [success("not-waiting"), current];
759
- const children = parent.suspension.reason.children;
760
- if (!children.some((entry) => entry.childSubmissionId === request.childSubmissionId)) return [success("not-waiting"), current];
761
- if (!children.every((entry) => current.submissions.get(entry.childSubmissionId)?.row.state === "settled")) return [success("still-waiting"), current];
762
- return [success("woken"), withSubmission(current, {
763
- ...parent,
764
- row: {
765
- ...parent.row,
766
- state: "input-applied"
767
- },
768
- suspension: void 0
769
- })];
770
- });
771
- if (decision._tag === "failure") return yield* decision.error;
772
- return decision.value;
773
- }));
774
- const reserveChildBudget = Effect.fn("MemorySubmissionLedger.reserveChildBudget")((unvalidated) => Effect.gen(function* () {
775
- const request = yield* validate$1(ChildBudgetReservationRequest, "reserveChildBudget", unvalidated);
776
- const nowMillis = yield* Clock.currentTimeMillis;
777
- const allocationJson = JSON.stringify(request.allocation);
778
- const decision = yield* Ref.modify(state, (current) => {
779
- const existing = current.childReservations.get(request.reservationId);
780
- if (existing !== void 0) {
781
- if (!(existing.parentSubmissionId === request.parentSubmissionId && existing.parentToolCallId === request.parentToolCallId && existing.allocationDigest === request.allocationDigest && existing.allocationJson === allocationJson)) return [failure(ChildReservationConflict.make({
782
- reservationId: request.reservationId,
783
- status: existing.status,
784
- message: "A reservation with this identity exists with a different parent Tool Call or allocation."
785
- })), current];
786
- return [success(ReservedChildBudget.make({
787
- reservation: toReservationSnapshot(existing),
788
- replayed: true
789
- })), current];
790
- }
791
- for (const reservation of current.childReservations.values()) if (reservation.parentSubmissionId === request.parentSubmissionId && reservation.parentToolCallId === request.parentToolCallId) return [failure(ChildReservationConflict.make({
792
- reservationId: request.reservationId,
793
- status: reservation.status,
794
- message: `Parent Tool Call ${request.parentToolCallId} already owns reservation ${reservation.reservationId}.`
795
- })), current];
796
- const parent = current.submissions.get(request.parentSubmissionId);
797
- if (parent === void 0) return [failure(ledgerError("reserveChildBudget", `Unknown Submission ${request.parentSubmissionId}`)), current];
798
- if (!ownsLane(parent, request.ownershipToken)) return [failure(ownershipLost(current, parent)), current];
799
- const reservation = {
800
- reservationId: request.reservationId,
801
- parentSubmissionId: request.parentSubmissionId,
802
- parentToolCallId: request.parentToolCallId,
803
- childSubmissionId: void 0,
804
- status: "reserved",
805
- allocation: request.allocation,
806
- allocationJson,
807
- allocationDigest: request.allocationDigest,
808
- accounting: void 0,
809
- accountingJson: void 0,
810
- reservedAtMillis: nowMillis,
811
- releaseBeganAtMillis: void 0,
812
- releasedAtMillis: void 0
813
- };
814
- return [success(ReservedChildBudget.make({
815
- reservation: toReservationSnapshot(reservation),
816
- replayed: false
817
- })), withChildReservation(current, reservation)];
818
- });
819
- if (decision._tag === "failure") return yield* decision.error;
820
- return decision.value;
821
- }));
822
- const attachChildToReservation = Effect.fn("MemorySubmissionLedger.attachChildToReservation")((unvalidated) => Effect.gen(function* () {
823
- const request = yield* validate$1(AttachChildToReservationRequest, "attachChildToReservation", unvalidated);
824
- const decision = yield* Ref.modify(state, (current) => {
825
- const reservation = current.childReservations.get(request.reservationId);
826
- if (reservation === void 0) return [failure(ledgerError("attachChildToReservation", `Unknown child reservation ${request.reservationId}`)), current];
827
- if (reservation.childSubmissionId !== void 0) {
828
- if (reservation.childSubmissionId === request.childSubmissionId) return [success(toReservationSnapshot(reservation)), current];
829
- return [failure(ChildReservationConflict.make({
830
- reservationId: request.reservationId,
831
- status: reservation.status,
832
- message: `Reservation ${request.reservationId} already records child ${reservation.childSubmissionId}.`
833
- })), current];
834
- }
835
- const parent = current.submissions.get(reservation.parentSubmissionId);
836
- if (parent === void 0) return [failure(ledgerError("attachChildToReservation", `Unknown Submission ${reservation.parentSubmissionId}`)), current];
837
- if (!ownsLane(parent, request.ownershipToken)) return [failure(ownershipLost(current, parent)), current];
838
- if (reservation.status !== "reserved") return [failure(ChildReservationConflict.make({
839
- reservationId: request.reservationId,
840
- status: reservation.status,
841
- message: `Cannot attach a child to a ${reservation.status} reservation.`
842
- })), current];
843
- if (!current.submissions.has(request.childSubmissionId)) return [failure(ledgerError("attachChildToReservation", `Unknown child Submission ${request.childSubmissionId}`)), current];
844
- const attached = {
845
- ...reservation,
846
- childSubmissionId: request.childSubmissionId
847
- };
848
- return [success(toReservationSnapshot(attached)), withChildReservation(current, attached)];
849
- });
850
- if (decision._tag === "failure") return yield* decision.error;
851
- return decision.value;
852
- }));
853
- const beginChildBudgetRelease = Effect.fn("MemorySubmissionLedger.beginChildBudgetRelease")((unvalidated) => Effect.gen(function* () {
854
- const request = yield* validate$1(BeginChildBudgetReleaseRequest, "beginChildBudgetRelease", unvalidated);
855
- const nowMillis = yield* Clock.currentTimeMillis;
856
- const accountingJson = JSON.stringify(request.accounting);
857
- const decision = yield* Ref.modify(state, (current) => {
858
- const reservation = current.childReservations.get(request.reservationId);
859
- if (reservation === void 0) return [failure(ledgerError("beginChildBudgetRelease", `Unknown child reservation ${request.reservationId}`)), current];
860
- if (reservation.status !== "reserved") {
861
- if (reservation.accountingJson === accountingJson) return [success(toReservationSnapshot(reservation)), current];
862
- return [failure(ChildReservationConflict.make({
863
- reservationId: request.reservationId,
864
- status: reservation.status,
865
- message: "A different accounting decision is already frozen for this reservation."
866
- })), current];
867
- }
868
- const frozen = {
869
- ...reservation,
870
- status: "releasePending",
871
- accounting: request.accounting,
872
- accountingJson,
873
- releaseBeganAtMillis: nowMillis
874
- };
875
- return [success(toReservationSnapshot(frozen)), withChildReservation(current, frozen)];
876
- });
877
- if (decision._tag === "failure") return yield* decision.error;
878
- return decision.value;
879
- }));
880
- const releaseChildBudget = Effect.fn("MemorySubmissionLedger.releaseChildBudget")((unvalidated) => Effect.gen(function* () {
881
- const request = yield* validate$1(ReleaseChildBudgetRequest, "releaseChildBudget", unvalidated);
882
- const nowMillis = yield* Clock.currentTimeMillis;
883
- const decision = yield* Ref.modify(state, (current) => {
884
- const reservation = current.childReservations.get(request.reservationId);
885
- if (reservation === void 0) return [failure(ledgerError("releaseChildBudget", `Unknown child reservation ${request.reservationId}`)), current];
886
- if (reservation.status === "released") return [success(toReservationSnapshot(reservation)), current];
887
- if (reservation.status !== "releasePending") return [failure(ChildReservationConflict.make({
888
- reservationId: request.reservationId,
889
- status: reservation.status,
890
- message: "Cannot release a reservation whose accounting decision is not frozen."
891
- })), current];
892
- const released = {
893
- ...reservation,
894
- status: "released",
895
- releasedAtMillis: nowMillis
896
- };
897
- return [success(toReservationSnapshot(released)), withChildReservation(current, released)];
898
- });
899
- if (decision._tag === "failure") return yield* decision.error;
900
- return decision.value;
901
- }));
902
- const scanNonterminal = Stream.unwrap(Ref.get(state).pipe(Effect.map((current) => {
903
- const snapshots = [...current.submissions.values()].filter((stored) => stored.row.state !== "settled").sort((left, right) => left.row.conversationId < right.row.conversationId ? -1 : left.row.conversationId > right.row.conversationId ? 1 : left.row.queueSequence - right.row.queueSequence).map((stored) => toSnapshot(stored.row));
904
- return Stream.fromIterable(snapshots);
905
- })));
906
- const loadRecoverySnapshot = Effect.fn("MemorySubmissionLedger.loadRecoverySnapshot")((unvalidated) => Effect.gen(function* () {
907
- const request = yield* validate$1(RecoverySnapshotRequest, "loadRecoverySnapshot", unvalidated);
908
- const current = yield* Ref.get(state);
909
- const stored = current.submissions.get(request.submissionId);
910
- if (stored === void 0) return yield* ledgerError("loadRecoverySnapshot", `Unknown Submission ${request.submissionId}`);
911
- const joins = [...current.submissions.values()].filter((candidate) => candidate.joinedHostSubmissionId === request.submissionId).sort((left, right) => left.row.queueSequence - right.row.queueSequence).map((candidate) => JoinSnapshot.make({
912
- submissionId: candidate.row.submissionId,
913
- state: candidate.row.state,
914
- hostSubmissionId: request.submissionId
915
- }));
916
- const byToolCallId = (left, right) => left.toolCallId < right.toolCallId ? -1 : left.toolCallId > right.toolCallId ? 1 : 0;
917
- const childReservations = [...current.childReservations.values()].filter((reservation) => reservation.parentSubmissionId === request.submissionId).sort((left, right) => left.parentToolCallId < right.parentToolCallId ? -1 : left.parentToolCallId > right.parentToolCallId ? 1 : 0);
918
- const childAttachments = [];
919
- for (const reservation of childReservations) {
920
- if (reservation.childSubmissionId === void 0) continue;
921
- const child = current.submissions.get(reservation.childSubmissionId);
922
- if (child === void 0) continue;
923
- childAttachments.push(ChildAttachmentSnapshot.make({
924
- toolCallId: reservation.parentToolCallId,
925
- childSubmissionId: reservation.childSubmissionId,
926
- childState: child.row.state,
927
- ...child.row.settledOutcome === void 0 ? {} : { childOutcome: child.row.settledOutcome }
928
- }));
929
- }
930
- return RecoverySnapshot.make({
931
- submission: toSnapshot(stored.row),
932
- joins,
933
- approvalDecisions: [...stored.approvalDecisions.values()].sort(byToolCallId),
934
- unknownResolutions: [...stored.unknownResolutions.values()].map((resolution) => resolution.intent).sort(byToolCallId),
935
- childReservations: childReservations.map(toReservationSnapshot),
936
- childAttachments,
937
- ...stored.row.parentLinkage === void 0 ? {} : { parentLinkage: stored.row.parentLinkage },
938
- ...stored.joinedHostSubmissionId === void 0 ? {} : { hostSubmissionId: stored.joinedHostSubmissionId },
939
- ...stored.suspension === void 0 ? {} : { suspension: SuspensionSnapshot.make({
940
- reason: stored.suspension.reason,
941
- suspendedAt: utc(stored.suspension.suspendedAtMillis)
942
- }) },
943
- ...stored.ownership === void 0 ? {} : { ownership: OwnershipSnapshot.make({
944
- attemptId: stored.ownership.attemptId,
945
- ownerProducerId: stored.ownership.ownerProducerId,
946
- producerEpoch: stored.ownership.producerEpoch,
947
- leaseExpiresAt: utc(stored.ownership.leaseExpiresAtMillis)
948
- }) },
949
- ...stored.inputApplied === void 0 ? {} : { inputApplied: stored.inputApplied },
950
- ...stored.reservation === void 0 ? {} : { reservation: SettlementReservationSnapshot.make({
951
- settlementId: stored.reservation.settlementId,
952
- outcome: stored.reservation.outcome,
953
- record: stored.reservation.record,
954
- recordDigest: stored.reservation.recordDigest,
955
- finalized: stored.reservation.finalizedAtMillis !== void 0
956
- }) },
957
- ...stored.abortIntent === void 0 ? {} : { abortIntent: stored.abortIntent }
958
- });
959
- }));
960
- return SubmissionLedger.of({
961
- capabilities,
962
- admit,
963
- markReady,
964
- lookup,
965
- resolveAdmission,
966
- claim,
967
- renewOwnership,
968
- releaseOwnership,
969
- markInputApplied,
970
- reserveSettlement,
971
- finalizeSettlement,
972
- requestAbort,
973
- claimJoining,
974
- markJoined,
975
- revertJoining,
976
- suspend,
977
- recordApprovalDecision,
978
- markUnknown,
979
- recordUnknownResolution,
980
- recordChildSettled,
981
- reserveChildBudget,
982
- attachChildToReservation,
983
- beginChildBudgetRelease,
984
- releaseChildBudget,
985
- scanNonterminal,
986
- loadRecoverySnapshot
987
- });
988
- });
989
- /**
990
- * In-memory reference SubmissionLedger Layer (durability `non-durable`). All state lives in one
991
- * `Ref` owned by the Layer's Scope; no daemon fibers are spawned and no wall clock is consulted.
992
- */
993
- const memorySubmissionLedgerLayer = (options = {}) => Layer.effect(SubmissionLedger, makeSubmissionLedger(options));
994
- const MemorySubmissionLedgerLive = memorySubmissionLedgerLayer();
995
- //#endregion
996
- //#region src/memory-storage.ts
997
- const MAX_CONVERSATIONS = 256;
998
- const MAX_RECORDS_PER_CONVERSATION = 65536;
999
- const MAX_CHECKPOINTS_PER_CONVERSATION = 1024;
1000
- const storeError = (operation, message, cause) => cause === void 0 ? ConversationStoreError.make({
1001
- operation,
1002
- message
1003
- }) : ConversationStoreError.make({
1004
- operation,
1005
- message,
1006
- cause
1007
- });
1008
- const validate = Effect.fn("MemoryConversationStore.validate")((schema, operation, value) => Schema.encodeUnknownEffect(schema)(value).pipe(Effect.flatMap(Schema.decodeUnknownEffect(schema)), Effect.mapError((error) => storeError(operation, `Invalid ${operation} request`, error))));
1009
- const decodeCanonicalSequence = Schema.decodeSync(CanonicalSequence);
1010
- const ZERO_CANONICAL_SEQUENCE = decodeCanonicalSequence(0);
1011
- const offsetSequence = Effect.fn("MemoryConversationStore.offsetSequence")((conversationId, offset) => {
1012
- if (offset === void 0) return Effect.succeed(ZERO_CANONICAL_SEQUENCE);
1013
- const prefix = `memory:v1:${Encoding.encodeBase64(conversationId)}:`;
1014
- const encodedSequence = offset.startsWith(prefix) ? offset.slice(prefix.length) : "";
1015
- if (!/^\d+$/.test(encodedSequence)) return Effect.fail(storeError("observe", "Malformed observation offset"));
1016
- const sequence = Number(encodedSequence);
1017
- return Number.isSafeInteger(sequence) ? Schema.decodeUnknownEffect(CanonicalSequence)(sequence).pipe(Effect.mapError(() => storeError("observe", "Malformed observation offset"))) : Effect.fail(storeError("observe", "Malformed observation offset"));
1018
- });
1019
- const observationOffset = (conversationId, sequence) => Schema.decodeSync(ObservationOffset)(`memory:v1:${Encoding.encodeBase64(conversationId)}:${sequence}`);
1020
- const findConversation = Effect.fn("MemoryConversationStore.findConversation")((state, conversationId) => {
1021
- const conversation = state.conversations.get(conversationId);
1022
- return conversation === void 0 ? Effect.fail(ConversationNotMaterialized.make({ conversationId })) : Effect.succeed(conversation);
1023
- });
1024
- const CheckpointVersionEnvelope = Schema.Struct({ checkpoint: Schema.Struct({
1025
- conversationId: ConversationId,
1026
- schemaVersion: Schema.Natural
1027
- }) });
1028
- const validateCheckpointVersion = Effect.fn("MemoryConversationStore.validateCheckpointVersion")(function* (value) {
1029
- const envelope = yield* Schema.decodeUnknownEffect(CheckpointVersionEnvelope)(value).pipe(Effect.mapError(() => storeError("saveCheckpoint", "Invalid saveCheckpoint request")));
1030
- if (envelope.checkpoint.schemaVersion !== 1) return yield* CheckpointRejected.make({
1031
- conversationId: envelope.checkpoint.conversationId,
1032
- reason: "unsupported-version"
1033
- });
1034
- });
1035
- const makeConversationStore = Effect.gen(function* () {
1036
- const crypto = yield* Crypto.Crypto;
1037
- const state = yield* Ref.make({ conversations: /* @__PURE__ */ new Map() });
1038
- const updates = yield* PubSub.sliding(1);
1039
- yield* Effect.addFinalizer(() => PubSub.shutdown(updates));
1040
- const materialize = Effect.fn("MemoryConversationStore.materialize")((unvalidated) => Effect.gen(function* () {
1041
- const request = yield* validate(ConversationMaterialization, "materialize", unvalidated);
1042
- const decision = yield* Ref.modify(state, (current) => {
1043
- const existing = current.conversations.get(request.conversationId);
1044
- if (existing !== void 0) {
1045
- if (request.producerEpoch < existing.producerEpoch) return [{
1046
- _tag: "failure",
1047
- error: FenceRejected.make({
1048
- conversationId: request.conversationId,
1049
- actualEpoch: existing.producerEpoch,
1050
- attemptedEpoch: request.producerEpoch
1051
- })
1052
- }, current];
1053
- if (request.producerEpoch === existing.producerEpoch) return [{ _tag: "success" }, current];
1054
- const conversations = new Map(current.conversations);
1055
- conversations.set(request.conversationId, {
1056
- ...existing,
1057
- producerEpoch: request.producerEpoch
1058
- });
1059
- return [{ _tag: "success" }, { conversations }];
1060
- }
1061
- if (current.conversations.size >= MAX_CONVERSATIONS) return [{
1062
- _tag: "failure",
1063
- error: storeError("materialize", `In-memory conversation limit ${MAX_CONVERSATIONS} exceeded`)
1064
- }, current];
1065
- const conversations = new Map(current.conversations);
1066
- conversations.set(request.conversationId, {
1067
- producerEpoch: request.producerEpoch,
1068
- tailSequence: ZERO_CANONICAL_SEQUENCE,
1069
- tailDigest: EMPTY_TAIL_DIGEST,
1070
- records: [],
1071
- recordIds: /* @__PURE__ */ new Set(),
1072
- batches: /* @__PURE__ */ new Map(),
1073
- tailDigests: /* @__PURE__ */ new Map([[ZERO_CANONICAL_SEQUENCE, EMPTY_TAIL_DIGEST]]),
1074
- checkpoints: /* @__PURE__ */ new Map()
1075
- });
1076
- return [{ _tag: "success" }, { conversations }];
1077
- });
1078
- if (decision._tag === "failure") return yield* decision.error;
1079
- }));
1080
- const append = Effect.fn("MemoryConversationStore.append")((unvalidated) => Effect.gen(function* () {
1081
- const request = yield* validate(FencedAppendRequest, "append", unvalidated);
1082
- const digest = yield* digestCanonicalBatch(request.expectedTailDigest, request.batch).pipe(Effect.provideService(Crypto.Crypto, crypto), Effect.mapError((error) => storeError("append", error.message, error)));
1083
- const decision = yield* Effect.uninterruptible(Ref.modify(state, (current) => {
1084
- const conversation = current.conversations.get(request.conversationId);
1085
- if (conversation === void 0) return [{
1086
- _tag: "failure",
1087
- error: ConversationNotMaterialized.make({ conversationId: request.conversationId })
1088
- }, current];
1089
- if (request.producerEpoch !== conversation.producerEpoch) return [{
1090
- _tag: "failure",
1091
- error: FenceRejected.make({
1092
- conversationId: request.conversationId,
1093
- actualEpoch: conversation.producerEpoch,
1094
- attemptedEpoch: request.producerEpoch
1095
- })
1096
- }, current];
1097
- const previous = conversation.batches.get(request.batch.batchId);
1098
- if (previous !== void 0) {
1099
- if (previous.digest !== digest) return [{
1100
- _tag: "failure",
1101
- error: AppendConflict.make({
1102
- conversationId: request.conversationId,
1103
- batchId: request.batch.batchId,
1104
- reason: "batch-digest"
1105
- })
1106
- }, current];
1107
- return [{
1108
- _tag: "success",
1109
- result: AppendResult.make({
1110
- firstSequence: previous.result.firstSequence,
1111
- lastSequence: previous.result.lastSequence,
1112
- tailDigest: previous.result.tailDigest,
1113
- replayed: true
1114
- }),
1115
- records: []
1116
- }, current];
1117
- }
1118
- if (request.expectedTailSequence !== conversation.tailSequence || request.expectedTailDigest !== conversation.tailDigest) return [{
1119
- _tag: "failure",
1120
- error: AppendConflict.make({
1121
- conversationId: request.conversationId,
1122
- batchId: request.batch.batchId,
1123
- reason: "tail",
1124
- actualTailSequence: conversation.tailSequence,
1125
- actualTailDigest: conversation.tailDigest
1126
- })
1127
- }, current];
1128
- if (conversation.records.length + request.batch.records.length > MAX_RECORDS_PER_CONVERSATION) return [{
1129
- _tag: "failure",
1130
- error: storeError("append", `In-memory record limit ${MAX_RECORDS_PER_CONVERSATION} exceeded`)
1131
- }, current];
1132
- const batchRecordIds = /* @__PURE__ */ new Set();
1133
- for (const record of request.batch.records) {
1134
- if (conversation.recordIds.has(record.recordId) || batchRecordIds.has(record.recordId)) return [{
1135
- _tag: "failure",
1136
- error: AppendConflict.make({
1137
- conversationId: request.conversationId,
1138
- batchId: request.batch.batchId,
1139
- reason: "record-identity"
1140
- })
1141
- }, current];
1142
- batchRecordIds.add(record.recordId);
1143
- }
1144
- const records = request.batch.records.map((record, index) => {
1145
- const sequence = decodeCanonicalSequence(conversation.tailSequence + index + 1);
1146
- return CanonicalRecordEnvelope.make({
1147
- conversationId: request.conversationId,
1148
- batchId: request.batch.batchId,
1149
- sequence,
1150
- offset: observationOffset(request.conversationId, sequence),
1151
- record
1152
- });
1153
- });
1154
- const lastSequence = decodeCanonicalSequence(conversation.tailSequence + records.length);
1155
- const result = AppendResult.make({
1156
- firstSequence: decodeCanonicalSequence(conversation.tailSequence + 1),
1157
- lastSequence,
1158
- tailDigest: digest,
1159
- replayed: false
1160
- });
1161
- const batches = new Map(conversation.batches);
1162
- batches.set(request.batch.batchId, {
1163
- digest,
1164
- result
1165
- });
1166
- const recordIds = new Set(conversation.recordIds);
1167
- for (const recordId of batchRecordIds) recordIds.add(recordId);
1168
- const tailDigests = new Map(conversation.tailDigests);
1169
- tailDigests.set(lastSequence, digest);
1170
- const conversations = new Map(current.conversations);
1171
- conversations.set(request.conversationId, {
1172
- ...conversation,
1173
- tailSequence: lastSequence,
1174
- tailDigest: digest,
1175
- records: [...conversation.records, ...records],
1176
- recordIds,
1177
- batches,
1178
- tailDigests
1179
- });
1180
- return [{
1181
- _tag: "success",
1182
- result,
1183
- records
1184
- }, { conversations }];
1185
- }).pipe(Effect.tap((decision) => decision._tag === "success" && decision.records.length > 0 ? PubSub.publish(updates, void 0) : Effect.void)));
1186
- if (decision._tag === "failure") return yield* decision.error;
1187
- return decision.result;
1188
- }));
1189
- const readSnapshot = Effect.fn("MemoryConversationStore.readSnapshot")((conversationId, afterSequence, limit) => Ref.get(state).pipe(Effect.flatMap((current) => findConversation(current, conversationId)), Effect.map((conversation) => conversation.records.filter((record) => record.sequence > (afterSequence ?? ZERO_CANONICAL_SEQUENCE)).slice(0, limit))));
1190
- const read = (unvalidated) => Stream.unwrap(Effect.gen(function* () {
1191
- const request = yield* validate(ConversationRead, "read", unvalidated);
1192
- const records = yield* readSnapshot(request.conversationId, request.afterSequence, request.limit);
1193
- return Stream.fromIterable(records);
1194
- }));
1195
- const observe = (unvalidated) => Stream.unwrap(Effect.gen(function* () {
1196
- const request = yield* validate(ConversationObservation, "observe", unvalidated);
1197
- const afterSequence = yield* offsetSequence(request.conversationId, request.afterOffset);
1198
- return Stream.unwrap(Effect.gen(function* () {
1199
- const subscription = yield* PubSub.subscribe(updates);
1200
- const initial = yield* readSnapshot(request.conversationId, afterSequence, MAX_RECORDS_PER_CONVERSATION);
1201
- const highWater = initial.length === 0 ? afterSequence : initial.at(-1)?.sequence ?? afterSequence;
1202
- const live = Stream.fromEffectRepeat(PubSub.take(subscription)).pipe(Stream.mapAccumEffect(() => highWater, (lastSequence) => readSnapshot(request.conversationId, lastSequence, MAX_RECORDS_PER_CONVERSATION).pipe(Effect.map((records) => [records.at(-1)?.sequence ?? lastSequence, records]))));
1203
- return Stream.fromIterable(initial).pipe(Stream.concat(live));
1204
- }));
1205
- }));
1206
- const exportConversation = Effect.fn("MemoryConversationStore.export")((unvalidated) => Effect.gen(function* () {
1207
- const request = yield* validate(ConversationExportRequest, "export", unvalidated);
1208
- const conversation = yield* Ref.get(state).pipe(Effect.flatMap((current) => findConversation(current, request.conversationId)));
1209
- return ConversationExport.make({
1210
- format: "effect-agent/conversation@1",
1211
- conversationId: request.conversationId,
1212
- tailSequence: conversation.tailSequence,
1213
- tailDigest: conversation.tailDigest,
1214
- records: conversation.records
1215
- });
1216
- }));
1217
- const inspectTail = Effect.fn("MemoryConversationStore.inspectTail")((unvalidated) => Effect.gen(function* () {
1218
- const request = yield* validate(ConversationTailRequest, "inspectTail", unvalidated);
1219
- const conversation = yield* Ref.get(state).pipe(Effect.flatMap((current) => findConversation(current, request.conversationId)));
1220
- return ConversationTail.make({
1221
- conversationId: request.conversationId,
1222
- tailSequence: conversation.tailSequence,
1223
- tailDigest: conversation.tailDigest,
1224
- producerEpoch: conversation.producerEpoch
1225
- });
1226
- }));
1227
- const saveCheckpoint = Effect.fn("MemoryConversationStore.saveCheckpoint")((unvalidated) => Effect.gen(function* () {
1228
- yield* validateCheckpointVersion(unvalidated);
1229
- const request = yield* validate(SaveCheckpointRequest, "saveCheckpoint", unvalidated);
1230
- const decision = yield* Ref.modify(state, (current) => {
1231
- const checkpoint = request.checkpoint;
1232
- const conversation = current.conversations.get(checkpoint.conversationId);
1233
- if (conversation === void 0) return [{
1234
- _tag: "failure",
1235
- error: ConversationNotMaterialized.make({ conversationId: checkpoint.conversationId })
1236
- }, current];
1237
- if (checkpoint.throughSequence > conversation.tailSequence) return [{
1238
- _tag: "failure",
1239
- error: CheckpointRejected.make({
1240
- conversationId: checkpoint.conversationId,
1241
- reason: "ahead-of-tail"
1242
- })
1243
- }, current];
1244
- if (conversation.tailDigests.get(checkpoint.throughSequence) !== checkpoint.tailDigest) return [{
1245
- _tag: "failure",
1246
- error: CheckpointRejected.make({
1247
- conversationId: checkpoint.conversationId,
1248
- reason: "digest-mismatch"
1249
- })
1250
- }, current];
1251
- if (!conversation.checkpoints.has(checkpoint.throughSequence) && conversation.checkpoints.size >= MAX_CHECKPOINTS_PER_CONVERSATION) return [{
1252
- _tag: "failure",
1253
- error: storeError("saveCheckpoint", `In-memory checkpoint limit ${MAX_CHECKPOINTS_PER_CONVERSATION} exceeded`)
1254
- }, current];
1255
- const checkpoints = new Map(conversation.checkpoints);
1256
- checkpoints.set(checkpoint.throughSequence, checkpoint);
1257
- const conversations = new Map(current.conversations);
1258
- conversations.set(checkpoint.conversationId, {
1259
- ...conversation,
1260
- checkpoints
1261
- });
1262
- return [{ _tag: "success" }, { conversations }];
1263
- });
1264
- if (decision._tag === "failure") return yield* decision.error;
1265
- }));
1266
- const loadCheckpoint = Effect.fn("MemoryConversationStore.loadCheckpoint")((unvalidated) => Effect.gen(function* () {
1267
- const request = yield* validate(LoadCheckpointRequest, "loadCheckpoint", unvalidated);
1268
- const conversation = yield* Ref.get(state).pipe(Effect.flatMap((current) => findConversation(current, request.conversationId)));
1269
- const maximum = request.atOrBeforeSequence ?? conversation.tailSequence;
1270
- let selected;
1271
- for (const [sequence, checkpoint] of conversation.checkpoints) if (sequence <= maximum && (selected === void 0 || sequence > selected.throughSequence)) selected = checkpoint;
1272
- if (selected !== void 0 && conversation.tailDigests.get(selected.throughSequence) !== selected.tailDigest) return yield* CheckpointRejected.make({
1273
- conversationId: request.conversationId,
1274
- reason: "digest-mismatch"
1275
- });
1276
- return Option.fromNullishOr(selected);
1277
- }));
1278
- return ConversationStore.of({
1279
- materialize,
1280
- append,
1281
- read,
1282
- observe,
1283
- export: exportConversation,
1284
- inspectTail,
1285
- saveCheckpoint,
1286
- loadCheckpoint
1287
- });
1288
- });
1289
- const MemoryConversationStoreLive = Layer.effect(ConversationStore, makeConversationStore);
1290
- /**
1291
- * In-memory canonical Conversation persistence. Durable accepted work is served by the separate
1292
- * SubmissionLedger port; this Layer deliberately provides only the ConversationStore.
1293
- */
1294
- const MemoryStorageLive = MemoryConversationStoreLive;
1295
- //#endregion
1296
- export { MemoryConversationStoreLive, MemoryStorageLive, MemorySubmissionLedgerLive, memorySubmissionLedgerLayer };
1297
-
1298
- //# sourceMappingURL=index.mjs.map
1
+ import { t as MemoryScheduleStore_exports } from "./MemoryScheduleStore.mjs";
2
+ import { t as MemoryMessageDeliveryStore_exports } from "./MemoryMessageDeliveryStore.mjs";
3
+ import { t as MemorySemanticIndex_exports } from "./MemorySemanticIndex.mjs";
4
+ import { t as MemorySubmissionLedger_exports } from "./MemorySubmissionLedger.mjs";
5
+ import { t as MemorySubscriptionStore_exports } from "./MemorySubscriptionStore.mjs";
6
+ import { t as MemoryThreadStore_exports } from "./MemoryThreadStore.mjs";
7
+ export { MemoryMessageDeliveryStore_exports as MemoryMessageDeliveryStore, MemoryScheduleStore_exports as MemoryScheduleStore, MemorySemanticIndex_exports as MemorySemanticIndex, MemorySubmissionLedger_exports as MemorySubmissionLedger, MemorySubscriptionStore_exports as MemorySubscriptionStore, MemoryThreadStore_exports as MemoryThreadStore };