@mastra/mongodb 1.23.0 → 1.23.1-alpha.1

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.
package/dist/index.cjs CHANGED
@@ -5,11 +5,12 @@ let _mastra_core_storage = require("@mastra/core/storage");
5
5
  let _mastra_core_vector = require("@mastra/core/vector");
6
6
  let mongodb = require("mongodb");
7
7
  let _mastra_core_vector_filter = require("@mastra/core/vector/filter");
8
+ let crypto$1 = require("crypto");
8
9
  let _mastra_core_agent = require("@mastra/core/agent");
9
10
  let _mastra_core_evals = require("@mastra/core/evals");
10
11
  let _mastra_core_storage_domains_skills = require("@mastra/core/storage/domains/skills");
11
12
  //#region package.json
12
- var version = "1.23.0";
13
+ var version = "1.23.1-alpha.1";
13
14
  //#endregion
14
15
  //#region src/vector/filter.ts
15
16
  /**
@@ -1618,20 +1619,29 @@ var MongoDBConnector = class MongoDBConnector {
1618
1619
  return (await this.getConnection()).listCollections({ name: collectionName }).hasNext();
1619
1620
  }
1620
1621
  /**
1621
- * Returns true when the deployment supports multi-document transactions
1622
- * (replica set or sharded cluster). Standalone servers and custom connector
1623
- * handlers return false. Probed once and cached.
1622
+ * Whether the deployment supports multi-document transactions (replica set
1623
+ * or sharded cluster). Standalone servers and custom connector handlers
1624
+ * don't. The answer is cached once a probe completes; a probe that fails
1625
+ * rejects and is retried on the next call, so the answer never changes.
1624
1626
  */
1625
- async supportsTransactions() {
1627
+ async probeTransactions() {
1626
1628
  if (this.#supportsTransactions !== void 0) return this.#supportsTransactions;
1627
1629
  if (!this.#client) {
1628
1630
  this.#supportsTransactions = false;
1629
1631
  return false;
1630
1632
  }
1633
+ const hello = await (await this.getConnection()).admin().command({ hello: 1 });
1634
+ this.#supportsTransactions = Boolean(hello.setName) || hello.msg === "isdbgrid";
1635
+ return this.#supportsTransactions;
1636
+ }
1637
+ /**
1638
+ * Like {@link probeTransactions}, but a failed probe reads as no
1639
+ * transaction support (and is retried on the next call), so a momentary
1640
+ * outage degrades writes to non-atomic instead of failing them.
1641
+ */
1642
+ async supportsTransactions() {
1631
1643
  try {
1632
- const hello = await (await this.getConnection()).admin().command({ hello: 1 });
1633
- this.#supportsTransactions = Boolean(hello.setName) || hello.msg === "isdbgrid";
1634
- return this.#supportsTransactions;
1644
+ return await this.probeTransactions();
1635
1645
  } catch {
1636
1646
  return false;
1637
1647
  }
@@ -2278,14 +2288,19 @@ function cutoffFor(policy, encoding, now = Date.now()) {
2278
2288
  * Delete up to `limit` documents whose anchor field is older than `cutoff`.
2279
2289
  *
2280
2290
  * `deleteMany` has no limit clause, so each batch first collects `_id`s with a
2281
- * bounded `find`, then deletes exactly that set — the Mongo equivalent of the
2282
- * SQL stores' `DELETE ... WHERE rowid/ctid IN (SELECT ... LIMIT n)` pattern.
2291
+ * bounded `find`, then deletes that set — the Mongo equivalent of the SQL
2292
+ * stores' `DELETE ... WHERE rowid/ctid IN (SELECT ... LIMIT n)` pattern. The
2293
+ * delete re-checks the anchor, so a document updated after the `find` (a
2294
+ * renewed run ownership, a touched snapshot) survives.
2283
2295
  */
2284
2296
  async function pruneCollectionBatch({ connector, table, column, cutoff, limit }) {
2285
2297
  const collection = await connector.getCollection(table);
2286
2298
  const docs = await collection.find({ [column]: { $lt: cutoff } }).project({ _id: 1 }).limit(limit).toArray();
2287
2299
  if (docs.length === 0) return 0;
2288
- return (await collection.deleteMany({ _id: { $in: docs.map((doc) => doc._id) } })).deletedCount;
2300
+ return (await collection.deleteMany({
2301
+ _id: { $in: docs.map((doc) => doc._id) },
2302
+ [column]: { $lt: cutoff }
2303
+ })).deletedCount;
2289
2304
  }
2290
2305
  /**
2291
2306
  * Run the bounded/cancellable batched-delete loop for a single logical target,
@@ -6495,6 +6510,105 @@ var MongoDBMCPServersStorage = class MongoDBMCPServersStorage extends _mastra_co
6495
6510
  }
6496
6511
  };
6497
6512
  //#endregion
6513
+ //#region ../../packages/_internal-core/dist/memory/index.js
6514
+ /**
6515
+ * A buffered chunk stores `lastObservedAt = max message time + 1ms`. It is wholly covered
6516
+ * (every message it observed is already at or before the cursor) iff
6517
+ * `cursor >= chunk.lastObservedAt - 1ms`.
6518
+ */
6519
+ function isBufferedChunkCoveredByCursor(chunkLastObservedAt, cursor) {
6520
+ if (!cursor) return false;
6521
+ return new Date(cursor).getTime() >= new Date(chunkLastObservedAt).getTime() - 1;
6522
+ }
6523
+ /** The later of two cursors; `undefined` only when both are absent. */
6524
+ function maxObservationCursor(a, b) {
6525
+ if (!a) return b ? new Date(b) : void 0;
6526
+ if (!b) return new Date(a);
6527
+ return new Date(Math.max(new Date(a).getTime(), new Date(b).getTime()));
6528
+ }
6529
+ /**
6530
+ * Decide the new generation's text when a reflection built from `snapshot` commits against
6531
+ * the stored record.
6532
+ *
6533
+ * - Stored text equals the snapshot: the reflection replaces it.
6534
+ * - Stored text extends the snapshot (activation only appends): the appended tail is kept
6535
+ * after the reflection, and its tokens are added.
6536
+ * - Anything else (a non-append rewrite): `null` — the reflection must not be applied.
6537
+ */
6538
+ function planReflectionGenerationText(input) {
6539
+ const stored = input.storedObservations ?? "";
6540
+ const snapshot = input.snapshotObservations ?? "";
6541
+ if (stored === snapshot) return {
6542
+ mode: "equal",
6543
+ observations: input.reflection,
6544
+ tokenCount: input.tokenCount
6545
+ };
6546
+ if (!stored.startsWith(snapshot)) return null;
6547
+ const tail = stored.slice(snapshot.length).trimStart();
6548
+ if (!tail.trim()) return {
6549
+ mode: "equal",
6550
+ observations: input.reflection,
6551
+ tokenCount: input.tokenCount
6552
+ };
6553
+ return {
6554
+ mode: "append",
6555
+ observations: input.reflection ? `${input.reflection}\n\n${tail}` : tail,
6556
+ tokenCount: input.tokenCount + Math.max(0, (input.storedObservationTokenCount ?? 0) - (input.snapshotObservationTokenCount ?? 0))
6557
+ };
6558
+ }
6559
+ /**
6560
+ * Backs `updateActiveObservations` in adapters that implement `commitActiveObservations`.
6561
+ * Callers of `updateActiveObservations` (which resolves `void`) can't see a commit result, so a
6562
+ * write that was not applied throws instead of resolving as if it had committed.
6563
+ */
6564
+ function assertActiveObservationsApplied(result, id) {
6565
+ if (result.applied) return;
6566
+ throw new Error(result.reason === "retired" ? `Observational memory record ${id} was superseded by a newer generation; observations were not written` : `Observational memory record ${id} changed since the observations were composed; observations were not written`);
6567
+ }
6568
+ /**
6569
+ * Whether the stored text can still accept a reflection built from `snapshot`
6570
+ * (equal, or only appended to since).
6571
+ */
6572
+ function isAppendOnlySince(storedObservations, snapshotObservations) {
6573
+ const stored = storedObservations ?? "";
6574
+ const snapshot = snapshotObservations ?? "";
6575
+ return stored === snapshot || stored.startsWith(snapshot);
6576
+ }
6577
+ //#endregion
6578
+ //#region src/storage/domains/run-fencing.ts
6579
+ async function getRunClaims(connector, name) {
6580
+ return await connector.getCollection(name);
6581
+ }
6582
+ /** Duplicate-key error: an upsert lost the race to insert the document. */
6583
+ function isDuplicateKeyError(error) {
6584
+ return error?.code === 11e3;
6585
+ }
6586
+ /**
6587
+ * Runs a write, fenced when `check` is set. A fenced write runs in a
6588
+ * transaction that first updates the run's claim document, matching it on
6589
+ * the fence. A fence that is no longer current matches nothing, and the write
6590
+ * throws `RunFenceConflictError` before anything is written. A claim that
6591
+ * changes while the transaction runs makes the update conflict, so the
6592
+ * transaction retries and sees the new claim; a claim attempted after the
6593
+ * update waits until the transaction commits. Updating the claim document,
6594
+ * rather than reading it, is what makes concurrent claims conflict.
6595
+ *
6596
+ * Fencing is declared only on deployments that support transactions.
6597
+ */
6598
+ async function withRunFence(connector, check, write) {
6599
+ if (!check) return write();
6600
+ const { claims, fence, operation } = check;
6601
+ const collection = await getRunClaims(connector, claims);
6602
+ return connector.withTransaction(async (session) => {
6603
+ if ((await collection.updateOne({
6604
+ _id: fence.runId,
6605
+ generation: fence.generation,
6606
+ ownerId: fence.ownerId
6607
+ }, { $inc: { fencedWrites: 1 } }, { session })).matchedCount === 0) throw new _mastra_core_storage.RunFenceConflictError(fence, operation);
6608
+ return write(session);
6609
+ });
6610
+ }
6611
+ //#endregion
6498
6612
  //#region src/storage/domains/memory/index.ts
6499
6613
  /**
6500
6614
  * Local constant for the observational memory table name.
@@ -6502,6 +6616,95 @@ var MongoDBMCPServersStorage = class MongoDBMCPServersStorage extends _mastra_co
6502
6616
  * versions that don't export TABLE_OBSERVATIONAL_MEMORY.
6503
6617
  */
6504
6618
  const OM_TABLE = "mastra_observational_memory";
6619
+ /** Canonical head order: newest generation, then earliest createdAt, then lowest id. */
6620
+ const OM_HEAD_SORT = {
6621
+ generationCount: -1,
6622
+ createdAt: 1,
6623
+ id: 1
6624
+ };
6625
+ /** Max redirects when a write aimed at a retired record follows the head. */
6626
+ const OM_MAX_HEAD_HOPS = 3;
6627
+ /** Max read-decide-conditional-update rounds before giving up on a contended record. */
6628
+ const OM_MAX_CONDITIONAL_ATTEMPTS = 3;
6629
+ /** Document `_id` of a key's generation-0 record; see `initializeObservationalMemory`. */
6630
+ function getGeneration0DocumentId(lookupKey) {
6631
+ return `om0_${(0, crypto$1.createHash)("sha256").update(lookupKey).digest("hex").slice(0, 32)}`;
6632
+ }
6633
+ /**
6634
+ * The successor document of a fenced (frozen) document: a pure function of the old document's
6635
+ * final state and the rollover inputs, so concurrent roll-forwards insert identical documents.
6636
+ * Buffered chunks, the cursor, buffering markers, flags, and counters carry over; buffered
6637
+ * reflection state does not.
6638
+ */
6639
+ function buildSuccessorDocument(old, pending) {
6640
+ let activeObservations = pending.reflection;
6641
+ let observationTokenCount = pending.tokenCount;
6642
+ if (pending.mode === "append") {
6643
+ const tail = String(old.activeObservations ?? "").slice(pending.snapTextLength).trimStart();
6644
+ activeObservations = pending.reflection ? `${pending.reflection}\n\n${tail}` : tail;
6645
+ observationTokenCount = pending.tokenCount + Math.max(0, Number(old.observationTokenCount || 0) - pending.snapObservationTokenCount);
6646
+ }
6647
+ return {
6648
+ id: pending.newId,
6649
+ lookupKey: old.lookupKey,
6650
+ scope: old.scope,
6651
+ resourceId: old.resourceId,
6652
+ threadId: old.threadId ?? null,
6653
+ activeObservations,
6654
+ activeObservationsPendingUpdate: null,
6655
+ originType: "reflection",
6656
+ config: old.config ?? null,
6657
+ generationCount: Number(old.generationCount || 0) + 1,
6658
+ lastObservedAt: old.lastObservedAt ?? null,
6659
+ lastReflectionAt: pending.createdAt,
6660
+ pendingMessageTokens: old.pendingMessageTokens ?? 0,
6661
+ totalTokensObserved: old.totalTokensObserved ?? 0,
6662
+ observationTokenCount,
6663
+ bufferedObservationChunks: Array.isArray(old.bufferedObservationChunks) ? old.bufferedObservationChunks : [],
6664
+ isObserving: false,
6665
+ isReflecting: false,
6666
+ isBufferingObservation: Boolean(old.isBufferingObservation),
6667
+ isBufferingReflection: false,
6668
+ lastBufferedAtTokens: old.lastBufferedAtTokens ?? 0,
6669
+ lastBufferedAtTime: old.lastBufferedAtTime ?? null,
6670
+ observedTimezone: old.observedTimezone ?? null,
6671
+ metadata: old.metadata ?? null,
6672
+ supersededBy: null,
6673
+ createdAt: pending.createdAt,
6674
+ updatedAt: pending.createdAt
6675
+ };
6676
+ }
6677
+ /** Equality filters for `fields` as stored in `doc` (`$exists: false` for absent fields). */
6678
+ function matchStoredFields(doc, fields) {
6679
+ return Object.fromEntries(fields.map((f) => [f, doc[f] === void 0 ? { $exists: false } : doc[f]]));
6680
+ }
6681
+ function omNotFound(operation, id) {
6682
+ return new _mastra_core_error.MastraError({
6683
+ id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", operation, "NOT_FOUND"),
6684
+ text: `Observational memory record not found: ${id}`,
6685
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
6686
+ category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
6687
+ details: { id }
6688
+ });
6689
+ }
6690
+ function omConflict(operation, id) {
6691
+ return new _mastra_core_error.MastraError({
6692
+ id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", operation, "CONFLICT"),
6693
+ text: `Observational memory record ${id} kept changing during ${operation}; giving up after ${OM_MAX_CONDITIONAL_ATTEMPTS} attempts`,
6694
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
6695
+ category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
6696
+ details: { id }
6697
+ });
6698
+ }
6699
+ function omNoLiveHead(id) {
6700
+ return new _mastra_core_error.MastraError({
6701
+ id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "RESOLVE_OBSERVATIONAL_MEMORY_HEAD", "FAILED"),
6702
+ text: `Observational memory record ${id} is superseded but no live head was found`,
6703
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
6704
+ category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
6705
+ details: { id }
6706
+ });
6707
+ }
6505
6708
  var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_storage.MemoryStorage {
6506
6709
  supportsPartialThreadUpdate = true;
6507
6710
  supportsObservationalMemory = true;
@@ -6514,11 +6717,14 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
6514
6717
  _mastra_core_storage.TABLE_THREADS,
6515
6718
  _mastra_core_storage.TABLE_MESSAGES,
6516
6719
  _mastra_core_storage.TABLE_RESOURCES,
6517
- OM_TABLE
6720
+ OM_TABLE,
6721
+ _mastra_core_storage.TABLE_MEMORY_RUN_FENCES
6518
6722
  ];
6519
6723
  /**
6520
6724
  * Retention-eligible collections. The observational-memory collection is
6521
6725
  * excluded: it has no timestamp anchor to age on. All anchors are BSON dates.
6726
+ * A run fence's `retiredAt` stays null until the execution holding it
6727
+ * settles, and null is never pruned.
6522
6728
  */
6523
6729
  static retentionTables = {
6524
6730
  messages: {
@@ -6535,6 +6741,11 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
6535
6741
  table: _mastra_core_storage.TABLE_THREADS,
6536
6742
  column: "createdAt",
6537
6743
  indexed: true
6744
+ },
6745
+ runFences: {
6746
+ table: _mastra_core_storage.TABLE_MEMORY_RUN_FENCES,
6747
+ column: "retiredAt",
6748
+ indexed: true
6538
6749
  }
6539
6750
  };
6540
6751
  constructor(config) {
@@ -6549,13 +6760,80 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
6549
6760
  async init() {
6550
6761
  await this.createDefaultIndexes();
6551
6762
  await this.createCustomIndexes();
6763
+ await this.#warnIfOMIdIndexMissing();
6764
+ await this.#maintainSupersededBy();
6765
+ }
6766
+ /**
6767
+ * Fenced writes need multi-document transactions, so only replica sets and
6768
+ * sharded clusters fence. Rejects while the deployment can't be probed.
6769
+ */
6770
+ supportsRunFencing() {
6771
+ return this.#connector.probeTransactions();
6772
+ }
6773
+ async raiseRunFence(fence) {
6774
+ try {
6775
+ const fences = await getRunClaims(this.#connector, _mastra_core_storage.TABLE_MEMORY_RUN_FENCES);
6776
+ for (;;) {
6777
+ try {
6778
+ await fences.updateOne({
6779
+ _id: fence.runId,
6780
+ $or: [{ generation: { $lt: fence.generation } }, {
6781
+ generation: fence.generation,
6782
+ ownerId: fence.ownerId
6783
+ }]
6784
+ }, { $set: {
6785
+ generation: fence.generation,
6786
+ ownerId: fence.ownerId,
6787
+ retiredAt: null
6788
+ } }, { upsert: true });
6789
+ return true;
6790
+ } catch (error) {
6791
+ if (!isDuplicateKeyError(error)) throw error;
6792
+ }
6793
+ const current = await fences.findOne({ _id: fence.runId });
6794
+ if (current && current.generation >= fence.generation) return current.generation === fence.generation && current.ownerId === fence.ownerId;
6795
+ }
6796
+ } catch (error) {
6797
+ throw new _mastra_core_error.MastraError({
6798
+ id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "RAISE_RUN_FENCE", "FAILED"),
6799
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
6800
+ category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
6801
+ details: { runId: fence.runId }
6802
+ }, error);
6803
+ }
6804
+ }
6805
+ async retireRunFence(fence) {
6806
+ try {
6807
+ return (await (await getRunClaims(this.#connector, _mastra_core_storage.TABLE_MEMORY_RUN_FENCES)).updateOne({
6808
+ _id: fence.runId,
6809
+ generation: fence.generation,
6810
+ ownerId: fence.ownerId
6811
+ }, [{ $set: { retiredAt: "$$NOW" } }])).matchedCount > 0;
6812
+ } catch (error) {
6813
+ throw new _mastra_core_error.MastraError({
6814
+ id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "RETIRE_RUN_FENCE", "FAILED"),
6815
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
6816
+ category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
6817
+ details: { runId: fence.runId }
6818
+ }, error);
6819
+ }
6820
+ }
6821
+ /** The check a write must pass: its own fence, otherwise the one in scope. */
6822
+ #runFenceCheck(fence, operation) {
6823
+ const resolved = (0, _mastra_core_storage.resolveRunFence)(this, fence);
6824
+ return resolved && {
6825
+ claims: _mastra_core_storage.TABLE_MEMORY_RUN_FENCES,
6826
+ fence: resolved,
6827
+ operation
6828
+ };
6552
6829
  }
6553
6830
  /**
6554
6831
  * Delete memory rows older than each table's `maxAge`, batched. Order is
6555
6832
  * messages → resources → threads so child rows never outlive the delete of
6556
- * their thread. Like `deleteThread()`, this does not sweep vector-store
6557
- * embeddings — semantic-recall vectors live in a separate vector store the
6558
- * memory domain cannot reach; cleaning those up is the operator's concern.
6833
+ * their thread; run fences are independent and go last. Like
6834
+ * `deleteThread()`, this does not sweep vector-store embeddings —
6835
+ * semantic-recall vectors live in a separate vector store the memory domain
6836
+ * cannot reach; cleaning those up is the operator's concern.
6559
6837
  */
6560
6838
  async prune(policies, options) {
6561
6839
  const targets = resolveTargets({
@@ -6564,7 +6842,8 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
6564
6842
  order: [
6565
6843
  "messages",
6566
6844
  "resources",
6567
- "threads"
6845
+ "threads",
6846
+ "runFences"
6568
6847
  ]
6569
6848
  });
6570
6849
  return runPrune({
@@ -6670,17 +6949,19 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
6670
6949
  }
6671
6950
  }
6672
6951
  async dangerouslyClearAll() {
6673
- const [threadsCollection, messagesCollection, resourcesCollection, omCollection] = await Promise.all([
6952
+ const [threadsCollection, messagesCollection, resourcesCollection, omCollection, fencesCollection] = await Promise.all([
6674
6953
  this.getCollection(_mastra_core_storage.TABLE_THREADS),
6675
6954
  this.getCollection(_mastra_core_storage.TABLE_MESSAGES),
6676
6955
  this.getCollection(_mastra_core_storage.TABLE_RESOURCES),
6677
- this.getCollection(OM_TABLE)
6956
+ this.getCollection(OM_TABLE),
6957
+ this.getCollection(_mastra_core_storage.TABLE_MEMORY_RUN_FENCES)
6678
6958
  ]);
6679
6959
  await Promise.all([
6680
6960
  threadsCollection.deleteMany({}),
6681
6961
  messagesCollection.deleteMany({}),
6682
6962
  resourcesCollection.deleteMany({}),
6683
- omCollection.deleteMany({})
6963
+ omCollection.deleteMany({}),
6964
+ fencesCollection.deleteMany({})
6684
6965
  ]);
6685
6966
  }
6686
6967
  parseRow(row) {
@@ -7036,7 +7317,7 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
7036
7317
  throw mastraError;
7037
7318
  }
7038
7319
  }
7039
- async saveMessages({ messages }) {
7320
+ async saveMessages({ messages, fence }) {
7040
7321
  if (messages.length === 0) return { messages: [] };
7041
7322
  try {
7042
7323
  if (!messages[0]?.threadId) throw new Error("Thread ID is required");
@@ -7064,12 +7345,16 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
7064
7345
  });
7065
7346
  const allThreadIds = new Set(messages.map((m) => m.threadId));
7066
7347
  const now = /* @__PURE__ */ new Date();
7067
- await this.#connector.withTransaction(async (session) => {
7348
+ const writeMessages = async (session) => {
7068
7349
  await collection.bulkWrite(messagesToInsert, { session });
7069
7350
  for (const tid of allThreadIds) await threadsCollection.updateOne({ id: tid }, { $set: { updatedAt: now } }, { session });
7070
- });
7351
+ };
7352
+ const check = this.#runFenceCheck(fence, "saveMessages");
7353
+ if (check) await withRunFence(this.#connector, check, writeMessages);
7354
+ else await this.#connector.withTransaction(writeMessages);
7071
7355
  return { messages: new _mastra_core_agent.MessageList().add(messages, "memory").get.all.db() };
7072
7356
  } catch (error) {
7357
+ if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
7073
7358
  throw new _mastra_core_error.MastraError({
7074
7359
  id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "SAVE_MESSAGES", "FAILED"),
7075
7360
  domain: _mastra_core_error.ErrorDomain.STORAGE,
@@ -7077,49 +7362,51 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
7077
7362
  }, error);
7078
7363
  }
7079
7364
  }
7080
- async updateMessages({ messages }) {
7365
+ async updateMessages({ messages, fence }) {
7081
7366
  if (messages.length === 0) return [];
7082
7367
  const messageIds = messages.map((m) => m.id);
7083
7368
  const collection = await this.getCollection(_mastra_core_storage.TABLE_MESSAGES);
7084
- const existingMessagesParsed = (await collection.find({ id: { $in: messageIds } }).toArray()).map((msg) => this.parseRow(msg));
7085
- if (existingMessagesParsed.length === 0) return [];
7086
- const threadIdsToUpdate = /* @__PURE__ */ new Set();
7087
- const bulkOps = [];
7088
- for (const existingMessage of existingMessagesParsed) {
7089
- const updatePayload = messages.find((m) => m.id === existingMessage.id);
7090
- if (!updatePayload) continue;
7091
- const { id, ...fieldsToUpdate } = updatePayload;
7092
- if (Object.keys(fieldsToUpdate).length === 0) continue;
7093
- threadIdsToUpdate.add(existingMessage.threadId);
7094
- if (updatePayload.threadId && updatePayload.threadId !== existingMessage.threadId) threadIdsToUpdate.add(updatePayload.threadId);
7095
- const updateDoc = {};
7096
- const updatableFields = { ...fieldsToUpdate };
7097
- if (updatableFields.content) {
7098
- const newContent = {
7099
- ...existingMessage.content,
7100
- ...updatableFields.content,
7101
- ...existingMessage.content?.metadata && updatableFields.content.metadata ? { metadata: {
7102
- ...existingMessage.content.metadata,
7103
- ...updatableFields.content.metadata
7104
- } } : {}
7105
- };
7106
- updateDoc.content = JSON.stringify(newContent);
7107
- delete updatableFields.content;
7108
- }
7109
- for (const key in updatableFields) if (Object.prototype.hasOwnProperty.call(updatableFields, key)) {
7110
- const dbKey = key === "threadId" ? "thread_id" : key;
7111
- let value = updatableFields[key];
7112
- if (typeof value === "object" && value !== null) value = JSON.stringify(value);
7113
- updateDoc[dbKey] = value;
7369
+ return withRunFence(this.#connector, this.#runFenceCheck(fence, "updateMessages"), async (session) => {
7370
+ const existingMessagesParsed = (await collection.find({ id: { $in: messageIds } }, { session }).toArray()).map((msg) => this.parseRow(msg));
7371
+ if (existingMessagesParsed.length === 0) return [];
7372
+ const threadIdsToUpdate = /* @__PURE__ */ new Set();
7373
+ const bulkOps = [];
7374
+ for (const existingMessage of existingMessagesParsed) {
7375
+ const updatePayload = messages.find((m) => m.id === existingMessage.id);
7376
+ if (!updatePayload) continue;
7377
+ const { id, ...fieldsToUpdate } = updatePayload;
7378
+ if (Object.keys(fieldsToUpdate).length === 0) continue;
7379
+ threadIdsToUpdate.add(existingMessage.threadId);
7380
+ if (updatePayload.threadId && updatePayload.threadId !== existingMessage.threadId) threadIdsToUpdate.add(updatePayload.threadId);
7381
+ const updateDoc = {};
7382
+ const updatableFields = { ...fieldsToUpdate };
7383
+ if (updatableFields.content) {
7384
+ const newContent = {
7385
+ ...existingMessage.content,
7386
+ ...updatableFields.content,
7387
+ ...existingMessage.content?.metadata && updatableFields.content.metadata ? { metadata: {
7388
+ ...existingMessage.content.metadata,
7389
+ ...updatableFields.content.metadata
7390
+ } } : {}
7391
+ };
7392
+ updateDoc.content = JSON.stringify(newContent);
7393
+ delete updatableFields.content;
7394
+ }
7395
+ for (const key in updatableFields) if (Object.prototype.hasOwnProperty.call(updatableFields, key)) {
7396
+ const dbKey = key === "threadId" ? "thread_id" : key;
7397
+ let value = updatableFields[key];
7398
+ if (typeof value === "object" && value !== null) value = JSON.stringify(value);
7399
+ updateDoc[dbKey] = value;
7400
+ }
7401
+ if (Object.keys(updateDoc).length > 0) bulkOps.push({ updateOne: {
7402
+ filter: { id },
7403
+ update: { $set: updateDoc }
7404
+ } });
7114
7405
  }
7115
- if (Object.keys(updateDoc).length > 0) bulkOps.push({ updateOne: {
7116
- filter: { id },
7117
- update: { $set: updateDoc }
7118
- } });
7119
- }
7120
- if (bulkOps.length > 0) await collection.bulkWrite(bulkOps);
7121
- if (threadIdsToUpdate.size > 0) await (await this.getCollection(_mastra_core_storage.TABLE_THREADS)).updateMany({ id: { $in: Array.from(threadIdsToUpdate) } }, { $set: { updatedAt: /* @__PURE__ */ new Date() } });
7122
- return (await collection.find({ id: { $in: messageIds } }).toArray()).map((row) => this.parseRow(row));
7406
+ if (bulkOps.length > 0) await collection.bulkWrite(bulkOps, { session });
7407
+ if (threadIdsToUpdate.size > 0) await (await this.getCollection(_mastra_core_storage.TABLE_THREADS)).updateMany({ id: { $in: Array.from(threadIdsToUpdate) } }, { $set: { updatedAt: /* @__PURE__ */ new Date() } }, { session });
7408
+ return (await collection.find({ id: { $in: messageIds } }, { session }).toArray()).map((row) => this.parseRow(row));
7409
+ });
7123
7410
  }
7124
7411
  async getResourceById({ resourceId }) {
7125
7412
  try {
@@ -7154,9 +7441,11 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
7154
7441
  }, error);
7155
7442
  }
7156
7443
  }
7157
- async updateResource({ resourceId, workingMemory, metadata }) {
7444
+ async updateResource({ resourceId, workingMemory, metadata, fence }) {
7158
7445
  try {
7446
+ const check = this.#runFenceCheck(fence, "updateResource");
7159
7447
  const existingResource = await this.getResourceById({ resourceId });
7448
+ const collection = await this.getCollection(_mastra_core_storage.TABLE_RESOURCES);
7160
7449
  if (!existingResource) {
7161
7450
  const newResource = {
7162
7451
  id: resourceId,
@@ -7165,7 +7454,11 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
7165
7454
  createdAt: /* @__PURE__ */ new Date(),
7166
7455
  updatedAt: /* @__PURE__ */ new Date()
7167
7456
  };
7168
- return this.saveResource({ resource: newResource });
7457
+ await withRunFence(this.#connector, check, (session) => collection.updateOne({ id: resourceId }, { $set: { ...newResource } }, {
7458
+ upsert: true,
7459
+ session
7460
+ }));
7461
+ return newResource;
7169
7462
  }
7170
7463
  const updatedResource = {
7171
7464
  ...existingResource,
@@ -7176,13 +7469,13 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
7176
7469
  } : existingResource.metadata,
7177
7470
  updatedAt: /* @__PURE__ */ new Date()
7178
7471
  };
7179
- const collection = await this.getCollection(_mastra_core_storage.TABLE_RESOURCES);
7180
7472
  const updateDoc = { updatedAt: updatedResource.updatedAt };
7181
7473
  if (workingMemory !== void 0) updateDoc.workingMemory = workingMemory;
7182
7474
  if (metadata) updateDoc.metadata = updatedResource.metadata;
7183
- await collection.updateOne({ id: resourceId }, { $set: updateDoc });
7475
+ await withRunFence(this.#connector, check, (session) => collection.updateOne({ id: resourceId }, { $set: updateDoc }, { session }));
7184
7476
  return updatedResource;
7185
7477
  } catch (error) {
7478
+ if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
7186
7479
  throw new _mastra_core_error.MastraError({
7187
7480
  id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "UPDATE_RESOURCE", "FAILED"),
7188
7481
  domain: _mastra_core_error.ErrorDomain.STORAGE,
@@ -7282,14 +7575,19 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
7282
7575
  throw mastraError;
7283
7576
  }
7284
7577
  }
7285
- async saveThread({ thread }) {
7578
+ async saveThread({ thread, fence }) {
7286
7579
  try {
7287
- await (await this.getCollection(_mastra_core_storage.TABLE_THREADS)).updateOne({ id: thread.id }, { $set: {
7580
+ const collection = await this.getCollection(_mastra_core_storage.TABLE_THREADS);
7581
+ await withRunFence(this.#connector, this.#runFenceCheck(fence, "saveThread"), (session) => collection.updateOne({ id: thread.id }, { $set: {
7288
7582
  ...thread,
7289
7583
  metadata: thread.metadata
7290
- } }, { upsert: true });
7584
+ } }, {
7585
+ upsert: true,
7586
+ session
7587
+ }));
7291
7588
  return thread;
7292
7589
  } catch (error) {
7590
+ if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
7293
7591
  throw new _mastra_core_error.MastraError({
7294
7592
  id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "SAVE_THREAD", "FAILED"),
7295
7593
  domain: _mastra_core_error.ErrorDomain.STORAGE,
@@ -7298,7 +7596,8 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
7298
7596
  }, error);
7299
7597
  }
7300
7598
  }
7301
- async updateThread({ id, title, metadata }) {
7599
+ async updateThread({ id, title, metadata, fence }) {
7600
+ const check = this.#runFenceCheck(fence, "updateThread");
7302
7601
  const thread = await this.getThreadById({ threadId: id });
7303
7602
  if (!thread) throw new _mastra_core_error.MastraError({
7304
7603
  id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "UPDATE_THREAD", "NOT_FOUND"),
@@ -7321,12 +7620,14 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
7321
7620
  updatedAt: now
7322
7621
  };
7323
7622
  try {
7324
- await (await this.getCollection(_mastra_core_storage.TABLE_THREADS)).updateOne({ id }, { $set: {
7623
+ const collection = await this.getCollection(_mastra_core_storage.TABLE_THREADS);
7624
+ await withRunFence(this.#connector, check, (session) => collection.updateOne({ id }, { $set: {
7325
7625
  title: updatedThread.title,
7326
7626
  metadata: updatedThread.metadata,
7327
7627
  updatedAt: now
7328
- } });
7628
+ } }, { session }));
7329
7629
  } catch (error) {
7630
+ if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
7330
7631
  throw new _mastra_core_error.MastraError({
7331
7632
  id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "UPDATE_THREAD", "FAILED"),
7332
7633
  domain: _mastra_core_error.ErrorDomain.STORAGE,
@@ -7349,16 +7650,19 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
7349
7650
  }, error);
7350
7651
  }
7351
7652
  }
7352
- async deleteMessages(messageIds) {
7653
+ async deleteMessages(messageIds, options) {
7353
7654
  if (messageIds.length === 0) return;
7354
7655
  try {
7355
7656
  const messagesCollection = await this.getCollection(_mastra_core_storage.TABLE_MESSAGES);
7356
7657
  const threadsCollection = await this.getCollection(_mastra_core_storage.TABLE_THREADS);
7357
- const messagesToDelete = await messagesCollection.find({ id: { $in: messageIds } }).toArray();
7358
- const threadIds = [...new Set(messagesToDelete.map((m) => m.thread_id))];
7359
- await messagesCollection.deleteMany({ id: { $in: messageIds } });
7360
- if (threadIds.length > 0) await threadsCollection.updateMany({ id: { $in: threadIds } }, { $set: { updatedAt: /* @__PURE__ */ new Date() } });
7658
+ await withRunFence(this.#connector, this.#runFenceCheck(options?.fence, "deleteMessages"), async (session) => {
7659
+ const messagesToDelete = await messagesCollection.find({ id: { $in: messageIds } }, { session }).toArray();
7660
+ const threadIds = [...new Set(messagesToDelete.map((m) => m.thread_id))];
7661
+ await messagesCollection.deleteMany({ id: { $in: messageIds } }, { session });
7662
+ if (threadIds.length > 0) await threadsCollection.updateMany({ id: { $in: threadIds } }, { $set: { updatedAt: /* @__PURE__ */ new Date() } }, { session });
7663
+ });
7361
7664
  } catch (error) {
7665
+ if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
7362
7666
  throw new _mastra_core_error.MastraError({
7363
7667
  id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "DELETE_MESSAGES", "FAILED"),
7364
7668
  domain: _mastra_core_error.ErrorDomain.STORAGE,
@@ -7526,15 +7830,15 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
7526
7830
  config: doc.config || {},
7527
7831
  metadata: doc.metadata || void 0,
7528
7832
  observedMessageIds: doc.observedMessageIds || void 0,
7529
- observedTimezone: doc.observedTimezone || void 0
7833
+ observedTimezone: doc.observedTimezone || void 0,
7834
+ supersededBy: doc.supersededBy ?? null
7530
7835
  };
7531
7836
  }
7532
7837
  async getObservationalMemory(threadId, resourceId) {
7533
7838
  try {
7534
- const lookupKey = this.getOMKey(threadId, resourceId);
7535
- const doc = await (await this.getCollection(OM_TABLE)).findOne({ lookupKey }, { sort: { generationCount: -1 } });
7536
- if (!doc) return null;
7537
- return this.parseOMDocument(doc);
7839
+ const collection = await this.getCollection(OM_TABLE);
7840
+ const doc = await this.#getHeadDoc(collection, this.getOMKey(threadId, resourceId));
7841
+ return doc ? this.parseOMDocument(doc) : null;
7538
7842
  } catch (error) {
7539
7843
  throw new _mastra_core_error.MastraError({
7540
7844
  id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "GET_OBSERVATIONAL_MEMORY", "FAILED"),
@@ -7597,60 +7901,55 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
7597
7901
  }
7598
7902
  async initializeObservationalMemory(input) {
7599
7903
  try {
7904
+ const lookupKey = this.getOMKey(input.threadId, input.resourceId);
7905
+ const collection = await this.getCollection(OM_TABLE);
7906
+ const existing = await this.#getHeadDoc(collection, lookupKey);
7907
+ if (existing) return this.parseOMDocument(existing);
7600
7908
  const id = globalThis.crypto.randomUUID();
7601
7909
  const now = /* @__PURE__ */ new Date();
7602
- const lookupKey = this.getOMKey(input.threadId, input.resourceId);
7603
- const record = {
7604
- id,
7605
- scope: input.scope,
7606
- threadId: input.threadId,
7607
- resourceId: input.resourceId,
7608
- createdAt: now,
7609
- updatedAt: now,
7610
- lastObservedAt: void 0,
7611
- originType: "initial",
7612
- generationCount: 0,
7613
- activeObservations: "",
7614
- totalTokensObserved: 0,
7615
- observationTokenCount: 0,
7616
- pendingMessageTokens: 0,
7617
- isReflecting: false,
7618
- isObserving: false,
7619
- isBufferingObservation: false,
7620
- isBufferingReflection: false,
7621
- lastBufferedAtTokens: 0,
7622
- lastBufferedAtTime: null,
7623
- config: input.config,
7624
- observedTimezone: input.observedTimezone
7625
- };
7626
- await (await this.getCollection(OM_TABLE)).insertOne({
7627
- id,
7628
- lookupKey,
7629
- scope: input.scope,
7630
- resourceId: input.resourceId,
7631
- threadId: input.threadId || null,
7632
- activeObservations: "",
7633
- activeObservationsPendingUpdate: null,
7634
- originType: "initial",
7635
- config: input.config,
7636
- generationCount: 0,
7637
- lastObservedAt: null,
7638
- lastReflectionAt: null,
7639
- pendingMessageTokens: 0,
7640
- totalTokensObserved: 0,
7641
- observationTokenCount: 0,
7642
- isObserving: false,
7643
- isReflecting: false,
7644
- isBufferingObservation: false,
7645
- isBufferingReflection: false,
7646
- lastBufferedAtTokens: 0,
7647
- lastBufferedAtTime: null,
7648
- observedTimezone: input.observedTimezone || null,
7649
- createdAt: now,
7650
- updatedAt: now
7910
+ try {
7911
+ await collection.insertOne({
7912
+ _id: getGeneration0DocumentId(lookupKey),
7913
+ id,
7914
+ lookupKey,
7915
+ scope: input.scope,
7916
+ resourceId: input.resourceId,
7917
+ threadId: input.threadId || null,
7918
+ activeObservations: "",
7919
+ activeObservationsPendingUpdate: null,
7920
+ originType: "initial",
7921
+ config: input.config,
7922
+ generationCount: 0,
7923
+ lastObservedAt: null,
7924
+ lastReflectionAt: null,
7925
+ pendingMessageTokens: 0,
7926
+ totalTokensObserved: 0,
7927
+ observationTokenCount: 0,
7928
+ isObserving: false,
7929
+ isReflecting: false,
7930
+ isBufferingObservation: false,
7931
+ isBufferingReflection: false,
7932
+ lastBufferedAtTokens: 0,
7933
+ lastBufferedAtTime: null,
7934
+ observedTimezone: input.observedTimezone || null,
7935
+ supersededBy: null,
7936
+ createdAt: now,
7937
+ updatedAt: now
7938
+ });
7939
+ } catch (error) {
7940
+ if (!isDuplicateKeyError(error)) throw error;
7941
+ }
7942
+ const head = await this.#getHeadDoc(collection, lookupKey);
7943
+ if (!head) throw new _mastra_core_error.MastraError({
7944
+ id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "INITIALIZE_OBSERVATIONAL_MEMORY", "NOT_FOUND"),
7945
+ text: `Observational memory record not found after initialization: ${id}`,
7946
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
7947
+ category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
7948
+ details: { id }
7651
7949
  });
7652
- return record;
7950
+ return this.parseOMDocument(head);
7653
7951
  } catch (error) {
7952
+ if (error instanceof _mastra_core_error.MastraError) throw error;
7654
7953
  throw new _mastra_core_error.MastraError({
7655
7954
  id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "INITIALIZE_OBSERVATIONAL_MEMORY", "FAILED"),
7656
7955
  domain: _mastra_core_error.ErrorDomain.STORAGE,
@@ -7695,6 +7994,7 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
7695
7994
  lastBufferedAtTime: record.lastBufferedAtTime || null,
7696
7995
  observedTimezone: record.observedTimezone || null,
7697
7996
  metadata: record.metadata || null,
7997
+ supersededBy: record.supersededBy ?? null,
7698
7998
  createdAt: record.createdAt,
7699
7999
  updatedAt: record.updatedAt
7700
8000
  });
@@ -7712,28 +8012,40 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
7712
8012
  }
7713
8013
  }
7714
8014
  async updateActiveObservations(input) {
8015
+ assertActiveObservationsApplied(await this.commitActiveObservations(input), input.id);
8016
+ }
8017
+ async commitActiveObservations(input) {
7715
8018
  try {
7716
- const now = /* @__PURE__ */ new Date();
7717
8019
  const collection = await this.getCollection(OM_TABLE);
7718
8020
  const safeTokenCount = Number.isFinite(input.tokenCount) && input.tokenCount >= 0 ? input.tokenCount : 0;
7719
- const updateDoc = {
7720
- activeObservations: input.observations,
7721
- lastObservedAt: input.lastObservedAt,
7722
- pendingMessageTokens: 0,
7723
- observationTokenCount: safeTokenCount,
7724
- observedMessageIds: input.observedMessageIds ?? null,
7725
- updatedAt: now
7726
- };
7727
- if ((await collection.updateOne({ id: input.id }, {
7728
- $set: updateDoc,
7729
- $inc: { totalTokensObserved: safeTokenCount }
7730
- })).matchedCount === 0) throw new _mastra_core_error.MastraError({
7731
- id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "UPDATE_ACTIVE_OBSERVATIONS", "NOT_FOUND"),
7732
- text: `Observational memory record not found: ${input.id}`,
7733
- domain: _mastra_core_error.ErrorDomain.STORAGE,
7734
- category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
7735
- details: { id: input.id }
7736
- });
8021
+ for (let attempt = 1; attempt <= OM_MAX_CONDITIONAL_ATTEMPTS; attempt++) {
8022
+ const doc = await collection.findOne({ id: input.id });
8023
+ if (!doc) throw omNotFound("UPDATE_ACTIVE_OBSERVATIONS", input.id);
8024
+ if (doc.supersededBy) return {
8025
+ applied: false,
8026
+ reason: "retired"
8027
+ };
8028
+ if (input.expectedActiveObservations !== void 0 && input.expectedActiveObservations !== (doc.activeObservations || "")) return {
8029
+ applied: false,
8030
+ reason: "conflict"
8031
+ };
8032
+ if ((await withRunFence(this.#connector, this.#runFenceCheck(void 0, "updateActiveObservations"), (session) => collection.updateOne({
8033
+ id: input.id,
8034
+ supersededBy: null,
8035
+ ...matchStoredFields(doc, ["activeObservations", "lastObservedAt"])
8036
+ }, {
8037
+ $set: {
8038
+ activeObservations: input.observations,
8039
+ lastObservedAt: maxObservationCursor(doc.lastObservedAt, input.lastObservedAt),
8040
+ pendingMessageTokens: 0,
8041
+ observationTokenCount: safeTokenCount,
8042
+ observedMessageIds: input.observedMessageIds ?? null,
8043
+ updatedAt: /* @__PURE__ */ new Date()
8044
+ },
8045
+ $inc: { totalTokensObserved: safeTokenCount }
8046
+ }, { session }))).matchedCount === 1) return { applied: true };
8047
+ }
8048
+ throw omConflict("UPDATE_ACTIVE_OBSERVATIONS", input.id);
7737
8049
  } catch (error) {
7738
8050
  if (error instanceof _mastra_core_error.MastraError) throw error;
7739
8051
  throw new _mastra_core_error.MastraError({
@@ -7746,62 +8058,31 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
7746
8058
  }
7747
8059
  async createReflectionGeneration(input) {
7748
8060
  try {
7749
- const id = globalThis.crypto.randomUUID();
7750
- const now = /* @__PURE__ */ new Date();
7751
- const lookupKey = this.getOMKey(input.currentRecord.threadId, input.currentRecord.resourceId);
7752
- const record = {
7753
- id,
7754
- scope: input.currentRecord.scope,
7755
- threadId: input.currentRecord.threadId,
7756
- resourceId: input.currentRecord.resourceId,
7757
- createdAt: now,
7758
- updatedAt: now,
7759
- lastObservedAt: input.currentRecord.lastObservedAt,
7760
- originType: "reflection",
7761
- generationCount: input.currentRecord.generationCount + 1,
7762
- activeObservations: input.reflection,
7763
- totalTokensObserved: input.currentRecord.totalTokensObserved,
7764
- observationTokenCount: input.tokenCount,
7765
- pendingMessageTokens: 0,
7766
- isReflecting: false,
7767
- isObserving: false,
7768
- isBufferingObservation: false,
7769
- isBufferingReflection: false,
7770
- lastBufferedAtTokens: 0,
7771
- lastBufferedAtTime: null,
7772
- config: input.currentRecord.config,
7773
- metadata: input.currentRecord.metadata,
7774
- observedTimezone: input.currentRecord.observedTimezone
7775
- };
7776
- await (await this.getCollection(OM_TABLE)).insertOne({
7777
- id,
7778
- lookupKey,
7779
- scope: record.scope,
7780
- resourceId: record.resourceId,
7781
- threadId: record.threadId || null,
7782
- activeObservations: input.reflection,
7783
- activeObservationsPendingUpdate: null,
7784
- originType: "reflection",
7785
- config: record.config,
7786
- generationCount: input.currentRecord.generationCount + 1,
7787
- lastObservedAt: record.lastObservedAt || null,
7788
- lastReflectionAt: now,
7789
- pendingMessageTokens: record.pendingMessageTokens,
7790
- totalTokensObserved: record.totalTokensObserved,
7791
- observationTokenCount: record.observationTokenCount,
7792
- isObserving: false,
7793
- isReflecting: false,
7794
- isBufferingObservation: false,
7795
- isBufferingReflection: false,
7796
- lastBufferedAtTokens: 0,
7797
- lastBufferedAtTime: null,
7798
- observedTimezone: record.observedTimezone || null,
7799
- createdAt: now,
7800
- updatedAt: now,
7801
- metadata: record.metadata || null
8061
+ const { currentRecord } = input;
8062
+ return await this.#rollOver(currentRecord, "createReflectionGeneration", (doc) => {
8063
+ const stored = this.parseOMDocument(doc);
8064
+ const plan = planReflectionGenerationText({
8065
+ storedObservations: stored.activeObservations,
8066
+ storedObservationTokenCount: stored.observationTokenCount,
8067
+ snapshotObservations: currentRecord.activeObservations,
8068
+ snapshotObservationTokenCount: currentRecord.observationTokenCount,
8069
+ reflection: input.reflection,
8070
+ tokenCount: input.tokenCount
8071
+ });
8072
+ if (!plan) return null;
8073
+ return {
8074
+ newId: input.newRecordId ?? globalThis.crypto.randomUUID(),
8075
+ mode: plan.mode,
8076
+ reflection: input.reflection,
8077
+ tokenCount: input.tokenCount,
8078
+ snapTextLength: (currentRecord.activeObservations ?? "").length,
8079
+ snapObservationTokenCount: currentRecord.observationTokenCount ?? 0,
8080
+ clearBufferedReflection: false,
8081
+ createdAt: /* @__PURE__ */ new Date()
8082
+ };
7802
8083
  });
7803
- return record;
7804
8084
  } catch (error) {
8085
+ if (error instanceof _mastra_core_error.MastraError) throw error;
7805
8086
  throw new _mastra_core_error.MastraError({
7806
8087
  id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "CREATE_REFLECTION_GENERATION", "FAILED"),
7807
8088
  domain: _mastra_core_error.ErrorDomain.STORAGE,
@@ -7868,23 +8149,12 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
7868
8149
  }
7869
8150
  async setBufferingObservationFlag(id, isBuffering, lastBufferedAtTokens) {
7870
8151
  try {
7871
- const collection = await this.getCollection(OM_TABLE);
7872
8152
  const updateDoc = {
7873
8153
  isBufferingObservation: isBuffering,
7874
8154
  updatedAt: /* @__PURE__ */ new Date()
7875
8155
  };
7876
8156
  if (lastBufferedAtTokens !== void 0) updateDoc.lastBufferedAtTokens = lastBufferedAtTokens;
7877
- if ((await collection.updateOne({ id }, { $set: updateDoc })).matchedCount === 0) throw new _mastra_core_error.MastraError({
7878
- id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "SET_BUFFERING_OBSERVATION_FLAG", "NOT_FOUND"),
7879
- text: `Observational memory record not found: ${id}`,
7880
- domain: _mastra_core_error.ErrorDomain.STORAGE,
7881
- category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
7882
- details: {
7883
- id,
7884
- isBuffering,
7885
- lastBufferedAtTokens: lastBufferedAtTokens ?? null
7886
- }
7887
- });
8157
+ await this.#updateLiveDoc(id, "SET_BUFFERING_OBSERVATION_FLAG", { $set: updateDoc });
7888
8158
  } catch (error) {
7889
8159
  if (error instanceof _mastra_core_error.MastraError) throw error;
7890
8160
  throw new _mastra_core_error.MastraError({
@@ -7955,19 +8225,10 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
7955
8225
  }
7956
8226
  });
7957
8227
  try {
7958
- if ((await (await this.getCollection(OM_TABLE)).updateOne({ id }, { $set: {
8228
+ await this.#updateLiveDoc(id, "SET_PENDING_MESSAGE_TOKENS", { $set: {
7959
8229
  pendingMessageTokens: tokenCount,
7960
8230
  updatedAt: /* @__PURE__ */ new Date()
7961
- } })).matchedCount === 0) throw new _mastra_core_error.MastraError({
7962
- id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "SET_PENDING_MESSAGE_TOKENS", "NOT_FOUND"),
7963
- text: `Observational memory record not found: ${id}`,
7964
- domain: _mastra_core_error.ErrorDomain.STORAGE,
7965
- category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
7966
- details: {
7967
- id,
7968
- tokenCount
7969
- }
7970
- });
8231
+ } });
7971
8232
  } catch (error) {
7972
8233
  if (error instanceof _mastra_core_error.MastraError) throw error;
7973
8234
  throw new _mastra_core_error.MastraError({
@@ -8009,6 +8270,9 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
8009
8270
  }
8010
8271
  }
8011
8272
  async updateBufferedObservations(input) {
8273
+ await this.appendBufferedObservations(input);
8274
+ }
8275
+ async appendBufferedObservations(input) {
8012
8276
  try {
8013
8277
  const collection = await this.getCollection(OM_TABLE);
8014
8278
  const newChunk = {
@@ -8026,18 +8290,65 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
8026
8290
  extractedValues: input.chunk.extractedValues,
8027
8291
  extractionFailures: input.chunk.extractionFailures
8028
8292
  };
8029
- const setStage = {
8030
- updatedAt: /* @__PURE__ */ new Date(),
8031
- bufferedObservationChunks: { $concatArrays: [{ $ifNull: ["$bufferedObservationChunks", []] }, [newChunk]] }
8032
- };
8033
- if (input.lastBufferedAtTime) setStage.lastBufferedAtTime = input.lastBufferedAtTime;
8034
- if ((await collection.updateOne({ id: input.id }, [{ $set: setStage }])).matchedCount === 0) throw new _mastra_core_error.MastraError({
8035
- id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "UPDATE_BUFFERED_OBSERVATIONS", "NOT_FOUND"),
8036
- text: `Observational memory record not found: ${input.id}`,
8037
- domain: _mastra_core_error.ErrorDomain.STORAGE,
8038
- category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
8039
- details: { id: input.id }
8040
- });
8293
+ const notCovered = { $or: [{ lastObservedAt: null }, { lastObservedAt: { $lt: /* @__PURE__ */ new Date(new Date(input.chunk.lastObservedAt).getTime() - 1) } }] };
8294
+ const lastBufferedAtTime = input.lastBufferedAtTime ? new Date(input.lastBufferedAtTime) : void 0;
8295
+ let targetId = input.id;
8296
+ let hops = 0;
8297
+ let retries = 0;
8298
+ for (;;) {
8299
+ if ((await withRunFence(this.#connector, this.#runFenceCheck(void 0, "updateBufferedObservations"), (session) => collection.updateOne({
8300
+ id: targetId,
8301
+ supersededBy: null,
8302
+ "bufferedObservationChunks.cycleId": { $ne: input.chunk.cycleId },
8303
+ ...notCovered,
8304
+ $and: [{ $or: [
8305
+ { bufferedObservationChunks: { $exists: false } },
8306
+ { bufferedObservationChunks: null },
8307
+ { bufferedObservationChunks: { $type: "array" } }
8308
+ ] }]
8309
+ }, [{ $set: {
8310
+ bufferedObservationChunks: { $concatArrays: [{ $ifNull: ["$bufferedObservationChunks", []] }, { $literal: [newChunk] }] },
8311
+ ...lastBufferedAtTime ? { lastBufferedAtTime: { $max: ["$lastBufferedAtTime", { $literal: lastBufferedAtTime }] } } : {},
8312
+ updatedAt: { $literal: /* @__PURE__ */ new Date() }
8313
+ } }], { session }))).matchedCount === 1) return {
8314
+ persisted: true,
8315
+ recordId: targetId
8316
+ };
8317
+ const doc = await collection.findOne({ id: targetId });
8318
+ if (!doc) throw omNotFound("UPDATE_BUFFERED_OBSERVATIONS", targetId);
8319
+ if (doc.supersededBy) {
8320
+ const head = hops < OM_MAX_HEAD_HOPS ? await this.#getHeadDoc(collection, doc.lookupKey) : null;
8321
+ if (!head || head.supersededBy || head.id === targetId) throw omNoLiveHead(input.id);
8322
+ targetId = head.id;
8323
+ hops++;
8324
+ continue;
8325
+ }
8326
+ const stored = doc.bufferedObservationChunks;
8327
+ if (Array.isArray(stored) && stored.some((c) => c?.cycleId === input.chunk.cycleId)) return {
8328
+ persisted: false,
8329
+ recordId: targetId
8330
+ };
8331
+ if (isBufferedChunkCoveredByCursor(input.chunk.lastObservedAt, doc.lastObservedAt)) return {
8332
+ persisted: false,
8333
+ recordId: targetId
8334
+ };
8335
+ if (stored !== void 0 && stored !== null && !Array.isArray(stored)) {
8336
+ if ((await withRunFence(this.#connector, this.#runFenceCheck(void 0, "updateBufferedObservations"), (session) => collection.updateOne({
8337
+ id: targetId,
8338
+ supersededBy: null,
8339
+ bufferedObservationChunks: stored,
8340
+ ...notCovered
8341
+ }, { $set: {
8342
+ bufferedObservationChunks: [newChunk],
8343
+ ...lastBufferedAtTime ? { lastBufferedAtTime: maxObservationCursor(doc.lastBufferedAtTime, lastBufferedAtTime) } : {},
8344
+ updatedAt: /* @__PURE__ */ new Date()
8345
+ } }, { session }))).matchedCount === 1) return {
8346
+ persisted: true,
8347
+ recordId: targetId
8348
+ };
8349
+ }
8350
+ if (++retries >= OM_MAX_CONDITIONAL_ATTEMPTS) throw omConflict("UPDATE_BUFFERED_OBSERVATIONS", targetId);
8351
+ }
8041
8352
  } catch (error) {
8042
8353
  if (error instanceof _mastra_core_error.MastraError) throw error;
8043
8354
  throw new _mastra_core_error.MastraError({
@@ -8051,16 +8362,7 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
8051
8362
  async swapBufferedToActive(input) {
8052
8363
  try {
8053
8364
  const collection = await this.getCollection(OM_TABLE);
8054
- const doc = await collection.findOne({ id: input.id });
8055
- if (!doc) throw new _mastra_core_error.MastraError({
8056
- id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "SWAP_BUFFERED_TO_ACTIVE", "NOT_FOUND"),
8057
- text: `Observational memory record not found: ${input.id}`,
8058
- domain: _mastra_core_error.ErrorDomain.STORAGE,
8059
- category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
8060
- details: { id: input.id }
8061
- });
8062
- const chunks = Array.isArray(doc.bufferedObservationChunks) ? doc.bufferedObservationChunks : [];
8063
- if (chunks.length === 0) return {
8365
+ const emptyResult = {
8064
8366
  chunksActivated: 0,
8065
8367
  messageTokensActivated: 0,
8066
8368
  observationTokensActivated: 0,
@@ -8068,95 +8370,107 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
8068
8370
  activatedCycleIds: [],
8069
8371
  activatedMessageIds: []
8070
8372
  };
8071
- const retentionFloor = input.messageTokensThreshold * (1 - input.activationRatio);
8072
- const targetMessageTokens = Math.max(0, input.currentPendingTokens - retentionFloor);
8073
- let cumulativeMessageTokens = 0;
8074
- let bestOverBoundary = 0;
8075
- let bestOverTokens = 0;
8076
- let bestUnderBoundary = 0;
8077
- let bestUnderTokens = 0;
8078
- for (let i = 0; i < chunks.length; i++) {
8079
- cumulativeMessageTokens += chunks[i].messageTokens ?? 0;
8080
- const boundary = i + 1;
8081
- if (cumulativeMessageTokens >= targetMessageTokens) {
8082
- if (bestOverBoundary === 0 || cumulativeMessageTokens < bestOverTokens) {
8083
- bestOverBoundary = boundary;
8084
- bestOverTokens = cumulativeMessageTokens;
8373
+ for (let attempt = 1; attempt <= OM_MAX_CONDITIONAL_ATTEMPTS; attempt++) {
8374
+ const doc = await collection.findOne({ id: input.id });
8375
+ if (!doc) throw omNotFound("SWAP_BUFFERED_TO_ACTIVE", input.id);
8376
+ if (doc.supersededBy) return {
8377
+ ...emptyResult,
8378
+ retired: true
8379
+ };
8380
+ const persistedChunks = Array.isArray(doc.bufferedObservationChunks) ? doc.bufferedObservationChunks : [];
8381
+ const refreshedWeights = new Map((Array.isArray(input.bufferedChunks) ? input.bufferedChunks : []).map((c) => [c.id, c.messageTokens]));
8382
+ const chunks = persistedChunks.map((c) => refreshedWeights.has(c.id) ? {
8383
+ ...c,
8384
+ messageTokens: refreshedWeights.get(c.id)
8385
+ } : c);
8386
+ if (chunks.length === 0) return emptyResult;
8387
+ const retentionFloor = input.messageTokensThreshold * (1 - input.activationRatio);
8388
+ const targetMessageTokens = Math.max(0, input.currentPendingTokens - retentionFloor);
8389
+ let cumulativeMessageTokens = 0;
8390
+ let bestOverBoundary = 0;
8391
+ let bestOverTokens = 0;
8392
+ let bestUnderBoundary = 0;
8393
+ let bestUnderTokens = 0;
8394
+ for (let i = 0; i < chunks.length; i++) {
8395
+ cumulativeMessageTokens += chunks[i].messageTokens ?? 0;
8396
+ const boundary = i + 1;
8397
+ if (cumulativeMessageTokens >= targetMessageTokens) {
8398
+ if (bestOverBoundary === 0 || cumulativeMessageTokens < bestOverTokens) {
8399
+ bestOverBoundary = boundary;
8400
+ bestOverTokens = cumulativeMessageTokens;
8401
+ }
8402
+ } else if (cumulativeMessageTokens > bestUnderTokens) {
8403
+ bestUnderBoundary = boundary;
8404
+ bestUnderTokens = cumulativeMessageTokens;
8085
8405
  }
8086
- } else if (cumulativeMessageTokens > bestUnderTokens) {
8087
- bestUnderBoundary = boundary;
8088
- bestUnderTokens = cumulativeMessageTokens;
8089
8406
  }
8407
+ const maxOvershoot = retentionFloor * .95;
8408
+ const overshoot = bestOverTokens - targetMessageTokens;
8409
+ const remainingAfterOver = input.currentPendingTokens - bestOverTokens;
8410
+ const remainingAfterUnder = input.currentPendingTokens - bestUnderTokens;
8411
+ const minRemaining = Math.min(1e3, retentionFloor);
8412
+ let chunksToActivate;
8413
+ if (input.forceMaxActivation && bestOverBoundary > 0 && remainingAfterOver >= minRemaining) chunksToActivate = bestOverBoundary;
8414
+ else if (bestOverBoundary > 0 && overshoot <= maxOvershoot && remainingAfterOver >= minRemaining) chunksToActivate = bestOverBoundary;
8415
+ else if (bestUnderBoundary > 0 && remainingAfterUnder >= minRemaining) chunksToActivate = bestUnderBoundary;
8416
+ else if (bestOverBoundary > 0) chunksToActivate = bestOverBoundary;
8417
+ else chunksToActivate = 1;
8418
+ const activatedChunks = chunks.slice(0, chunksToActivate);
8419
+ const remainingChunks = persistedChunks.slice(chunksToActivate);
8420
+ const activatedContent = activatedChunks.map((c) => c.observations).join("\n\n");
8421
+ const activatedTokens = activatedChunks.reduce((sum, c) => sum + c.tokenCount, 0);
8422
+ const activatedMessageTokens = activatedChunks.reduce((sum, c) => sum + (c.messageTokens ?? 0), 0);
8423
+ const activatedMessageCount = activatedChunks.reduce((sum, c) => sum + c.messageIds.length, 0);
8424
+ const activatedCycleIds = activatedChunks.map((c) => c.cycleId).filter((id) => !!id);
8425
+ const activatedMessageIds = activatedChunks.flatMap((c) => c.messageIds ?? []);
8426
+ const latestChunk = activatedChunks[activatedChunks.length - 1];
8427
+ const lastObservedAt = input.lastObservedAt ?? (latestChunk?.lastObservedAt ? new Date(latestChunk.lastObservedAt) : /* @__PURE__ */ new Date());
8428
+ const existingActive = doc.activeObservations || "";
8429
+ const existingTokenCount = Number(doc.observationTokenCount || 0);
8430
+ const boundary = `\n\n--- message boundary (${lastObservedAt.toISOString()}) ---\n\n`;
8431
+ const newActive = existingActive ? `${existingActive}${boundary}${activatedContent}` : activatedContent;
8432
+ const newTokenCount = existingTokenCount + activatedTokens;
8433
+ const existingPending = Number(doc.pendingMessageTokens || 0);
8434
+ const newPending = Math.max(0, existingPending - activatedMessageTokens);
8435
+ if ((await withRunFence(this.#connector, this.#runFenceCheck(void 0, "swapBufferedToActive"), (session) => collection.updateOne({
8436
+ id: input.id,
8437
+ supersededBy: null,
8438
+ ...matchStoredFields(doc, [
8439
+ "bufferedObservationChunks",
8440
+ "activeObservations",
8441
+ "observationTokenCount",
8442
+ "pendingMessageTokens",
8443
+ "lastObservedAt"
8444
+ ])
8445
+ }, { $set: {
8446
+ activeObservations: newActive,
8447
+ observationTokenCount: newTokenCount,
8448
+ pendingMessageTokens: newPending,
8449
+ bufferedObservationChunks: remainingChunks,
8450
+ lastObservedAt: maxObservationCursor(doc.lastObservedAt, lastObservedAt),
8451
+ updatedAt: /* @__PURE__ */ new Date()
8452
+ } }, { session }))).matchedCount === 0) continue;
8453
+ const latestChunkHints = activatedChunks[activatedChunks.length - 1];
8454
+ return {
8455
+ chunksActivated: activatedChunks.length,
8456
+ messageTokensActivated: activatedMessageTokens,
8457
+ observationTokensActivated: activatedTokens,
8458
+ messagesActivated: activatedMessageCount,
8459
+ activatedCycleIds,
8460
+ activatedMessageIds,
8461
+ observations: activatedContent,
8462
+ perChunk: activatedChunks.map((c) => ({
8463
+ cycleId: c.cycleId ?? "",
8464
+ messageTokens: c.messageTokens ?? 0,
8465
+ observationTokens: c.tokenCount,
8466
+ messageCount: c.messageIds.length,
8467
+ observations: c.observations
8468
+ })),
8469
+ suggestedContinuation: latestChunkHints?.suggestedContinuation ?? void 0,
8470
+ currentTask: latestChunkHints?.currentTask ?? void 0
8471
+ };
8090
8472
  }
8091
- const maxOvershoot = retentionFloor * .95;
8092
- const overshoot = bestOverTokens - targetMessageTokens;
8093
- const remainingAfterOver = input.currentPendingTokens - bestOverTokens;
8094
- const remainingAfterUnder = input.currentPendingTokens - bestUnderTokens;
8095
- const minRemaining = Math.min(1e3, retentionFloor);
8096
- let chunksToActivate;
8097
- if (input.forceMaxActivation && bestOverBoundary > 0 && remainingAfterOver >= minRemaining) chunksToActivate = bestOverBoundary;
8098
- else if (bestOverBoundary > 0 && overshoot <= maxOvershoot && remainingAfterOver >= minRemaining) chunksToActivate = bestOverBoundary;
8099
- else if (bestUnderBoundary > 0 && remainingAfterUnder >= minRemaining) chunksToActivate = bestUnderBoundary;
8100
- else if (bestOverBoundary > 0) chunksToActivate = bestOverBoundary;
8101
- else chunksToActivate = 1;
8102
- const activatedChunks = chunks.slice(0, chunksToActivate);
8103
- const remainingChunks = chunks.slice(chunksToActivate);
8104
- const activatedContent = activatedChunks.map((c) => c.observations).join("\n\n");
8105
- const activatedTokens = activatedChunks.reduce((sum, c) => sum + c.tokenCount, 0);
8106
- const activatedMessageTokens = activatedChunks.reduce((sum, c) => sum + (c.messageTokens ?? 0), 0);
8107
- const activatedMessageCount = activatedChunks.reduce((sum, c) => sum + c.messageIds.length, 0);
8108
- const activatedCycleIds = activatedChunks.map((c) => c.cycleId).filter((id) => !!id);
8109
- const activatedMessageIds = activatedChunks.flatMap((c) => c.messageIds ?? []);
8110
- const latestChunk = activatedChunks[activatedChunks.length - 1];
8111
- const lastObservedAt = input.lastObservedAt ?? (latestChunk?.lastObservedAt ? new Date(latestChunk.lastObservedAt) : /* @__PURE__ */ new Date());
8112
- const existingActive = doc.activeObservations || "";
8113
- const existingTokenCount = Number(doc.observationTokenCount || 0);
8114
- const boundary = `\n\n--- message boundary (${lastObservedAt.toISOString()}) ---\n\n`;
8115
- const newActive = existingActive ? `${existingActive}${boundary}${activatedContent}` : activatedContent;
8116
- const newTokenCount = existingTokenCount + activatedTokens;
8117
- const existingPending = Number(doc.pendingMessageTokens || 0);
8118
- const newPending = Math.max(0, existingPending - activatedMessageTokens);
8119
- if ((await collection.updateOne({
8120
- id: input.id,
8121
- bufferedObservationChunks: {
8122
- $exists: true,
8123
- $ne: null,
8124
- $not: { $size: 0 }
8125
- }
8126
- }, { $set: {
8127
- activeObservations: newActive,
8128
- observationTokenCount: newTokenCount,
8129
- pendingMessageTokens: newPending,
8130
- bufferedObservationChunks: remainingChunks,
8131
- lastObservedAt,
8132
- updatedAt: /* @__PURE__ */ new Date()
8133
- } })).modifiedCount === 0) return {
8134
- chunksActivated: 0,
8135
- messageTokensActivated: 0,
8136
- observationTokensActivated: 0,
8137
- messagesActivated: 0,
8138
- activatedCycleIds: [],
8139
- activatedMessageIds: []
8140
- };
8141
- const latestChunkHints = activatedChunks[activatedChunks.length - 1];
8142
- return {
8143
- chunksActivated: activatedChunks.length,
8144
- messageTokensActivated: activatedMessageTokens,
8145
- observationTokensActivated: activatedTokens,
8146
- messagesActivated: activatedMessageCount,
8147
- activatedCycleIds,
8148
- activatedMessageIds,
8149
- observations: activatedContent,
8150
- perChunk: activatedChunks.map((c) => ({
8151
- cycleId: c.cycleId ?? "",
8152
- messageTokens: c.messageTokens ?? 0,
8153
- observationTokens: c.tokenCount,
8154
- messageCount: c.messageIds.length,
8155
- observations: c.observations
8156
- })),
8157
- suggestedContinuation: latestChunkHints?.suggestedContinuation ?? void 0,
8158
- currentTask: latestChunkHints?.currentTask ?? void 0
8159
- };
8473
+ throw omConflict("SWAP_BUFFERED_TO_ACTIVE", input.id);
8160
8474
  } catch (error) {
8161
8475
  if (error instanceof _mastra_core_error.MastraError) throw error;
8162
8476
  throw new _mastra_core_error.MastraError({
@@ -8184,13 +8498,13 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
8184
8498
  const newContent = existingContent ? `${existingContent}\n\n${input.reflection}` : input.reflection;
8185
8499
  const newTokens = existingTokens + input.tokenCount;
8186
8500
  const newInputTokens = existingInputTokens + input.inputTokenCount;
8187
- if ((await collection.updateOne({ id: input.id }, { $set: {
8501
+ if ((await withRunFence(this.#connector, this.#runFenceCheck(void 0, "updateBufferedReflection"), (session) => collection.updateOne({ id: input.id }, { $set: {
8188
8502
  bufferedReflection: newContent,
8189
8503
  bufferedReflectionTokens: newTokens,
8190
8504
  bufferedReflectionInputTokens: newInputTokens,
8191
8505
  reflectedObservationLineCount: input.reflectedObservationLineCount,
8192
8506
  updatedAt: /* @__PURE__ */ new Date()
8193
- } })).matchedCount === 0) throw new _mastra_core_error.MastraError({
8507
+ } }, { session }))).matchedCount === 0) throw new _mastra_core_error.MastraError({
8194
8508
  id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "UPDATE_BUFFERED_REFLECTION", "NOT_FOUND"),
8195
8509
  text: `Observational memory record not found: ${input.id}`,
8196
8510
  domain: _mastra_core_error.ErrorDomain.STORAGE,
@@ -8209,39 +8523,33 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
8209
8523
  }
8210
8524
  async swapBufferedReflectionToActive(input) {
8211
8525
  try {
8212
- const collection = await this.getCollection(OM_TABLE);
8213
- const doc = await collection.findOne({ id: input.currentRecord.id });
8214
- if (!doc) throw new _mastra_core_error.MastraError({
8215
- id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "SWAP_BUFFERED_REFLECTION_TO_ACTIVE", "NOT_FOUND"),
8216
- text: `Observational memory record not found: ${input.currentRecord.id}`,
8217
- domain: _mastra_core_error.ErrorDomain.STORAGE,
8218
- category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
8219
- details: { id: input.currentRecord.id }
8220
- });
8221
- const bufferedReflection = doc.bufferedReflection || "";
8222
- const reflectedLineCount = Number(doc.reflectedObservationLineCount || 0);
8223
- if (!bufferedReflection) throw new _mastra_core_error.MastraError({
8224
- id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "SWAP_BUFFERED_REFLECTION_TO_ACTIVE", "NO_CONTENT"),
8225
- text: "No buffered reflection to swap",
8226
- domain: _mastra_core_error.ErrorDomain.STORAGE,
8227
- category: _mastra_core_error.ErrorCategory.USER,
8228
- details: { id: input.currentRecord.id }
8229
- });
8230
- const unreflectedContent = (doc.activeObservations || "").split("\n").slice(reflectedLineCount).join("\n").trim();
8231
- const newObservations = unreflectedContent ? `${bufferedReflection}\n\n${unreflectedContent}` : bufferedReflection;
8232
- const newRecord = await this.createReflectionGeneration({
8233
- currentRecord: input.currentRecord,
8234
- reflection: newObservations,
8235
- tokenCount: input.tokenCount
8526
+ const { currentRecord } = input;
8527
+ return await this.#rollOver(currentRecord, "swapBufferedReflectionToActive", (doc) => {
8528
+ const stored = this.parseOMDocument(doc);
8529
+ const bufferedReflection = stored.bufferedReflection || "";
8530
+ if (!bufferedReflection) throw new _mastra_core_error.MastraError({
8531
+ id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "SWAP_BUFFERED_REFLECTION_TO_ACTIVE", "NO_CONTENT"),
8532
+ text: "No buffered reflection to swap",
8533
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
8534
+ category: _mastra_core_error.ErrorCategory.USER,
8535
+ details: { id: currentRecord.id }
8536
+ });
8537
+ if (!isAppendOnlySince(stored.activeObservations, currentRecord.activeObservations)) return null;
8538
+ const reflectedLineCount = stored.reflectedObservationLineCount ?? 0;
8539
+ const unreflectedContent = (stored.activeObservations || "").split("\n").slice(reflectedLineCount).join("\n").trim();
8540
+ const newObservations = unreflectedContent ? `${bufferedReflection}\n\n${unreflectedContent}` : bufferedReflection;
8541
+ const tokenCount = input.tokenCount + Math.max(0, (stored.observationTokenCount ?? 0) - (currentRecord.observationTokenCount ?? 0));
8542
+ return {
8543
+ newId: input.newRecordId ?? globalThis.crypto.randomUUID(),
8544
+ mode: "equal",
8545
+ reflection: newObservations,
8546
+ tokenCount,
8547
+ snapTextLength: (stored.activeObservations ?? "").length,
8548
+ snapObservationTokenCount: stored.observationTokenCount ?? 0,
8549
+ clearBufferedReflection: true,
8550
+ createdAt: /* @__PURE__ */ new Date()
8551
+ };
8236
8552
  });
8237
- await collection.updateOne({ id: input.currentRecord.id }, { $set: {
8238
- bufferedReflection: null,
8239
- bufferedReflectionTokens: null,
8240
- bufferedReflectionInputTokens: null,
8241
- reflectedObservationLineCount: null,
8242
- updatedAt: /* @__PURE__ */ new Date()
8243
- } });
8244
- return newRecord;
8245
8553
  } catch (error) {
8246
8554
  if (error instanceof _mastra_core_error.MastraError) throw error;
8247
8555
  throw new _mastra_core_error.MastraError({
@@ -8252,6 +8560,171 @@ var MemoryStorageMongoDB = class MemoryStorageMongoDB extends _mastra_core_stora
8252
8560
  }, error);
8253
8561
  }
8254
8562
  }
8563
+ /**
8564
+ * The head of a lookup key by the canonical order (`generationCount DESC, createdAt ASC, id ASC`).
8565
+ * A head that was fenced for rollover but whose successor insert never ran (crash, or still in
8566
+ * flight in another process) is rolled forward here, so every head read reaches the successor.
8567
+ */
8568
+ async #getHeadDoc(collection, lookupKey) {
8569
+ for (let hop = 0; hop <= OM_MAX_HEAD_HOPS; hop++) {
8570
+ const head = await collection.findOne({ lookupKey }, { sort: OM_HEAD_SORT });
8571
+ if (!head || !head.supersededBy || !head.pendingSuccessor) return head;
8572
+ await this.#completeRollover(collection, head, head.pendingSuccessor);
8573
+ }
8574
+ throw omNoLiveHead(lookupKey);
8575
+ }
8576
+ /**
8577
+ * A single-document update aimed at a live record. A retired (or concurrently fenced) id is
8578
+ * redirected to the head, at most `OM_MAX_HEAD_HOPS` times.
8579
+ */
8580
+ async #updateLiveDoc(id, operation, update) {
8581
+ const collection = await this.getCollection(OM_TABLE);
8582
+ let targetId = id;
8583
+ for (let hop = 0; hop <= OM_MAX_HEAD_HOPS; hop++) {
8584
+ if ((await collection.updateOne({
8585
+ id: targetId,
8586
+ supersededBy: null
8587
+ }, update)).matchedCount === 1) return targetId;
8588
+ const doc = await collection.findOne({ id: targetId }, { projection: {
8589
+ lookupKey: 1,
8590
+ supersededBy: 1
8591
+ } });
8592
+ if (!doc) throw omNotFound(operation, targetId);
8593
+ if (!doc.supersededBy) continue;
8594
+ const head = await this.#getHeadDoc(collection, doc.lookupKey);
8595
+ if (!head || head.supersededBy || head.id === targetId) throw omNoLiveHead(id);
8596
+ targetId = head.id;
8597
+ }
8598
+ throw omNoLiveHead(id);
8599
+ }
8600
+ /**
8601
+ * Roll the live record `currentRecord.id` over to a new generation.
8602
+ *
8603
+ * 1. Read the stored document and decide (`decide` returns the successor inputs, or `null`
8604
+ * when the snapshot is stale).
8605
+ * 2. Fence: one conditional update sets `supersededBy` and the small `pendingSuccessor`
8606
+ * payload, matching only if the fields the decision read are unchanged. From here the old
8607
+ * document is frozen — no lifecycle write matches it — so its pre-image is its final state.
8608
+ * 3. Insert the successor built purely from that pre-image, then clear the old document's
8609
+ * chunks (they moved) and `pendingSuccessor`.
8610
+ *
8611
+ * A crash between 2 and 3 is finished by the next head read (`#getHeadDoc`) or startup.
8612
+ */
8613
+ async #rollOver(currentRecord, operation, decide) {
8614
+ const collection = await this.getCollection(OM_TABLE);
8615
+ for (let attempt = 1; attempt <= OM_MAX_CONDITIONAL_ATTEMPTS; attempt++) {
8616
+ const doc = await collection.findOne({ id: currentRecord.id });
8617
+ if (!doc) return currentRecord;
8618
+ if (doc.supersededBy) {
8619
+ const head = await this.#getHeadDoc(collection, doc.lookupKey);
8620
+ return head ? this.parseOMDocument(head) : currentRecord;
8621
+ }
8622
+ const pending = decide(doc);
8623
+ if (!pending) return this.parseOMDocument(doc);
8624
+ const frozen = await withRunFence(this.#connector, this.#runFenceCheck(void 0, operation), (session) => collection.findOneAndUpdate({
8625
+ id: doc.id,
8626
+ supersededBy: null,
8627
+ ...matchStoredFields(doc, [
8628
+ "activeObservations",
8629
+ "observationTokenCount",
8630
+ "bufferedReflection",
8631
+ "reflectedObservationLineCount"
8632
+ ])
8633
+ }, { $set: {
8634
+ supersededBy: pending.newId,
8635
+ pendingSuccessor: pending
8636
+ } }, {
8637
+ returnDocument: "before",
8638
+ session
8639
+ }));
8640
+ if (!frozen) continue;
8641
+ return this.parseOMDocument(await this.#completeRollover(collection, frozen, pending));
8642
+ }
8643
+ throw omConflict("CREATE_REFLECTION_GENERATION", currentRecord.id);
8644
+ }
8645
+ /** Insert the successor of a fenced document (idempotent) and clean up the old document. */
8646
+ async #completeRollover(collection, frozen, pending) {
8647
+ try {
8648
+ await collection.insertOne(buildSuccessorDocument(frozen, pending));
8649
+ } catch (error) {
8650
+ if (!isDuplicateKeyError(error)) throw error;
8651
+ }
8652
+ await collection.updateOne({
8653
+ id: frozen.id,
8654
+ supersededBy: pending.newId
8655
+ }, {
8656
+ $set: {
8657
+ bufferedObservationChunks: [],
8658
+ ...pending.clearBufferedReflection ? {
8659
+ bufferedReflection: null,
8660
+ bufferedReflectionTokens: null,
8661
+ bufferedReflectionInputTokens: null,
8662
+ reflectedObservationLineCount: null
8663
+ } : {},
8664
+ updatedAt: /* @__PURE__ */ new Date()
8665
+ },
8666
+ $unset: { pendingSuccessor: "" }
8667
+ });
8668
+ const successor = await collection.findOne({ id: pending.newId });
8669
+ if (!successor) throw omNotFound("CREATE_REFLECTION_GENERATION", pending.newId);
8670
+ return successor;
8671
+ }
8672
+ /**
8673
+ * Startup maintenance for the `supersededBy` marker:
8674
+ * - finish rollovers interrupted after the fence (insert the successor if missing, clean up);
8675
+ * - for keys with more than one live document (left by older adapter versions, which never set
8676
+ * `supersededBy`), retire every live document that sorts after the canonical head. The head's
8677
+ * ordering key is embedded in the filter, so a document created by a concurrent rollover
8678
+ * (a newer generation) never matches.
8679
+ */
8680
+ async #maintainSupersededBy() {
8681
+ const collection = await this.getCollection(OM_TABLE);
8682
+ const interrupted = await collection.find({
8683
+ supersededBy: { $nin: [null] },
8684
+ pendingSuccessor: { $exists: true }
8685
+ }).toArray();
8686
+ for (const doc of interrupted) await this.#completeRollover(collection, doc, doc.pendingSuccessor);
8687
+ const keys = await collection.aggregate([
8688
+ { $match: { supersededBy: null } },
8689
+ { $group: {
8690
+ _id: "$lookupKey",
8691
+ live: { $sum: 1 }
8692
+ } },
8693
+ { $match: { live: { $gt: 1 } } }
8694
+ ]).toArray();
8695
+ for (const { _id: lookupKey } of keys) {
8696
+ const head = await this.#getHeadDoc(collection, lookupKey);
8697
+ if (!head || head.supersededBy) continue;
8698
+ await collection.updateMany({
8699
+ lookupKey,
8700
+ supersededBy: null,
8701
+ $or: [
8702
+ { generationCount: { $lt: head.generationCount } },
8703
+ {
8704
+ generationCount: head.generationCount,
8705
+ createdAt: { $gt: head.createdAt }
8706
+ },
8707
+ {
8708
+ generationCount: head.generationCount,
8709
+ createdAt: head.createdAt,
8710
+ id: { $gt: head.id }
8711
+ }
8712
+ ]
8713
+ }, { $set: { supersededBy: head.id } });
8714
+ }
8715
+ }
8716
+ /**
8717
+ * Cross-process rollover and initialization rely on the unique `id` index (a duplicate insert
8718
+ * of the same successor or generation-0 id must fail). Warn once if it is missing, e.g. under
8719
+ * `skipDefaultIndexes`.
8720
+ */
8721
+ async #warnIfOMIdIndexMissing() {
8722
+ try {
8723
+ if (!(await (await this.getCollection(OM_TABLE)).listIndexes().toArray().catch((error) => error?.code === 26 ? [] : Promise.reject(error))).some((index) => index.unique && Object.keys(index.key ?? {}).length === 1 && index.key.id === 1)) this.logger?.warn?.(`MongoDB collection "${OM_TABLE}" has no unique index on { id: 1 }. Observational memory needs it to stay consistent when several processes write the same thread or resource. Create it with db.${OM_TABLE}.createIndex({ id: 1 }, { unique: true }).`);
8724
+ } catch (error) {
8725
+ this.logger?.warn?.(`Could not list indexes on MongoDB collection "${OM_TABLE}":`, error);
8726
+ }
8727
+ }
8255
8728
  };
8256
8729
  //#endregion
8257
8730
  //#region src/storage/domains/notifications/index.ts
@@ -11478,33 +11951,58 @@ var MongoDBWorkflowDefinitionsStore = class MongoDBWorkflowDefinitionsStore exte
11478
11951
  };
11479
11952
  //#endregion
11480
11953
  //#region src/storage/domains/workflows/index.ts
11954
+ const RUN_OWNER_PROJECTION = {
11955
+ generation: 1,
11956
+ ownerId: 1,
11957
+ leaseExpiresAt: 1,
11958
+ live: { $gt: ["$leaseExpiresAt", "$$NOW"] }
11959
+ };
11960
+ const NOT_LIVE = { $or: [{ leaseExpiresAt: null }, { $expr: { $lte: ["$leaseExpiresAt", "$$NOW"] } }] };
11961
+ function toRunOwnershipRecord(doc) {
11962
+ return {
11963
+ runId: doc._id,
11964
+ generation: Number(doc.generation),
11965
+ ownerId: doc.ownerId,
11966
+ leaseExpiresAt: doc.leaseExpiresAt ?? null,
11967
+ live: Boolean(doc.live)
11968
+ };
11969
+ }
11481
11970
  var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends _mastra_core_storage.WorkflowsStorage {
11482
11971
  #connector;
11483
11972
  #skipDefaultIndexes;
11484
11973
  #indexes;
11485
11974
  /** Collections managed by this domain */
11486
- static MANAGED_COLLECTIONS = [_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT];
11975
+ static MANAGED_COLLECTIONS = [_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT, _mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS];
11487
11976
  /**
11488
- * Anchor is `updatedAt` (BSON date), so the policy reads as inactivity:
11489
- * a run is pruned only after its snapshot has not been touched for `maxAge`.
11977
+ * Both anchors are `updatedAt` (BSON date), so the policies read as
11978
+ * inactivity: a run is pruned only after its snapshot has not been touched
11979
+ * for `maxAge`, and an ownership document only after no claim, renewal or
11980
+ * release has touched it for `maxAge`.
11490
11981
  */
11491
- static retentionTables = { workflowSnapshot: {
11492
- table: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
11493
- column: "updatedAt",
11494
- indexed: true
11495
- } };
11982
+ static retentionTables = {
11983
+ workflowSnapshot: {
11984
+ table: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
11985
+ column: "updatedAt",
11986
+ indexed: true
11987
+ },
11988
+ runOwnership: {
11989
+ table: _mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS,
11990
+ column: "updatedAt",
11991
+ indexed: true
11992
+ }
11993
+ };
11496
11994
  constructor(config) {
11497
11995
  super();
11498
11996
  this.#connector = resolveMongoDBConfig(config);
11499
11997
  this.#skipDefaultIndexes = config.skipDefaultIndexes;
11500
11998
  this.#indexes = config.indexes?.filter((idx) => WorkflowsStorageMongoDB.MANAGED_COLLECTIONS.includes(idx.collection));
11501
11999
  }
11502
- /** Delete workflow snapshots idle for longer than the policy's `maxAge`, batched. */
12000
+ /** Delete workflow snapshots and ownership documents idle for longer than each policy's `maxAge`, batched. */
11503
12001
  async prune(policies, options) {
11504
12002
  const targets = resolveTargets({
11505
12003
  policies,
11506
12004
  descriptor: WorkflowsStorageMongoDB.retentionTables,
11507
- order: ["workflowSnapshot"]
12005
+ order: ["workflowSnapshot", "runOwnership"]
11508
12006
  });
11509
12007
  return runPrune({
11510
12008
  connector: this.#connector,
@@ -11517,6 +12015,13 @@ var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends _mastra_core
11517
12015
  supportsConcurrentUpdates() {
11518
12016
  return true;
11519
12017
  }
12018
+ /**
12019
+ * Fenced writes need multi-document transactions, so only replica sets and
12020
+ * sharded clusters fence. Rejects while the deployment can't be probed.
12021
+ */
12022
+ supportsRunFencing() {
12023
+ return this.#connector.probeTransactions();
12024
+ }
11520
12025
  async getCollection(name) {
11521
12026
  return this.#connector.getCollection(name);
11522
12027
  }
@@ -11524,6 +12029,124 @@ var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends _mastra_core
11524
12029
  await this.createDefaultIndexes();
11525
12030
  await this.createCustomIndexes();
11526
12031
  }
12032
+ #runFenceCheck(fence, runId, operation) {
12033
+ const resolved = (0, _mastra_core_storage.resolveRunFence)(this, fence, runId);
12034
+ return resolved && {
12035
+ claims: _mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS,
12036
+ fence: resolved,
12037
+ operation
12038
+ };
12039
+ }
12040
+ async #readRunOwner(runId) {
12041
+ const doc = await (await getRunClaims(this.#connector, _mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS)).findOne({ _id: runId }, { projection: RUN_OWNER_PROJECTION });
12042
+ return doc ? toRunOwnershipRecord(doc) : null;
12043
+ }
12044
+ #ownershipError(operation, runId, error) {
12045
+ return new _mastra_core_error.MastraError({
12046
+ id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", operation, "FAILED"),
12047
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
12048
+ category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
12049
+ details: { runId }
12050
+ }, error);
12051
+ }
12052
+ async claimRunOwnership({ runId, ownerId, leaseMs, force, expectedGeneration }) {
12053
+ try {
12054
+ const owners = await getRunClaims(this.#connector, _mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS);
12055
+ const claim = (generation) => [{ $set: {
12056
+ generation,
12057
+ ownerId: { $literal: ownerId },
12058
+ leaseExpiresAt: { $add: ["$$NOW", leaseMs] },
12059
+ updatedAt: "$$NOW"
12060
+ } }];
12061
+ if (!expectedGeneration) {
12062
+ try {
12063
+ const created = await owners.findOneAndUpdate({
12064
+ _id: runId,
12065
+ generation: { $exists: false }
12066
+ }, claim({ $max: [1, { $toLong: "$$NOW" }] }), {
12067
+ upsert: true,
12068
+ returnDocument: "after",
12069
+ projection: RUN_OWNER_PROJECTION
12070
+ });
12071
+ if (created) return {
12072
+ acquired: true,
12073
+ record: toRunOwnershipRecord(created)
12074
+ };
12075
+ } catch (error) {
12076
+ if (!isDuplicateKeyError(error)) throw error;
12077
+ }
12078
+ if (expectedGeneration === 0) return {
12079
+ acquired: false,
12080
+ record: await this.#readRunOwner(runId)
12081
+ };
12082
+ }
12083
+ const advanced = await owners.findOneAndUpdate({
12084
+ _id: runId,
12085
+ ...expectedGeneration !== void 0 ? { generation: expectedGeneration } : {},
12086
+ ...force ? {} : NOT_LIVE
12087
+ }, claim({ $add: ["$generation", 1] }), {
12088
+ returnDocument: "after",
12089
+ projection: RUN_OWNER_PROJECTION
12090
+ });
12091
+ if (advanced) return {
12092
+ acquired: true,
12093
+ record: toRunOwnershipRecord(advanced)
12094
+ };
12095
+ return {
12096
+ acquired: false,
12097
+ record: await this.#readRunOwner(runId)
12098
+ };
12099
+ } catch (error) {
12100
+ throw this.#ownershipError("CLAIM_RUN_OWNERSHIP", runId, error);
12101
+ }
12102
+ }
12103
+ async renewRunOwnership({ leaseMs, ...fence }) {
12104
+ try {
12105
+ const renewed = await (await getRunClaims(this.#connector, _mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS)).findOneAndUpdate({
12106
+ _id: fence.runId,
12107
+ generation: fence.generation,
12108
+ ownerId: fence.ownerId,
12109
+ leaseExpiresAt: { $ne: null }
12110
+ }, [{ $set: {
12111
+ leaseExpiresAt: { $add: ["$$NOW", leaseMs] },
12112
+ updatedAt: "$$NOW"
12113
+ } }], {
12114
+ returnDocument: "after",
12115
+ projection: RUN_OWNER_PROJECTION
12116
+ });
12117
+ if (renewed) return {
12118
+ renewed: true,
12119
+ record: toRunOwnershipRecord(renewed)
12120
+ };
12121
+ return {
12122
+ renewed: false,
12123
+ record: await this.#readRunOwner(fence.runId)
12124
+ };
12125
+ } catch (error) {
12126
+ throw this.#ownershipError("RENEW_RUN_OWNERSHIP", fence.runId, error);
12127
+ }
12128
+ }
12129
+ async releaseRunOwnership(fence) {
12130
+ try {
12131
+ return (await (await getRunClaims(this.#connector, _mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS)).updateOne({
12132
+ _id: fence.runId,
12133
+ generation: fence.generation,
12134
+ ownerId: fence.ownerId
12135
+ }, [{ $set: {
12136
+ leaseExpiresAt: null,
12137
+ updatedAt: "$$NOW"
12138
+ } }])).matchedCount > 0;
12139
+ } catch (error) {
12140
+ throw this.#ownershipError("RELEASE_RUN_OWNERSHIP", fence.runId, error);
12141
+ }
12142
+ }
12143
+ async getRunOwnership({ runId }) {
12144
+ try {
12145
+ return await this.#readRunOwner(runId);
12146
+ } catch (error) {
12147
+ throw this.#ownershipError("GET_RUN_OWNERSHIP", runId, error);
12148
+ }
12149
+ }
11527
12150
  /**
11528
12151
  * Returns default index definitions for the workflows domain collections.
11529
12152
  */
@@ -11583,8 +12206,10 @@ var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends _mastra_core
11583
12206
  }
11584
12207
  async dangerouslyClearAll() {
11585
12208
  await (await this.getCollection(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT)).deleteMany({});
12209
+ await (await this.getCollection(_mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS)).deleteMany({});
11586
12210
  }
11587
- async updateWorkflowResults({ workflowName, runId, stepId, result, requestContext, state }) {
12211
+ async updateWorkflowResults({ workflowName, runId, stepId, result, requestContext, state, fence }) {
12212
+ const check = this.#runFenceCheck(fence, runId, "updateWorkflowResults");
11588
12213
  try {
11589
12214
  const collection = await this.getCollection(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT);
11590
12215
  const now = /* @__PURE__ */ new Date();
@@ -11604,7 +12229,7 @@ var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends _mastra_core
11604
12229
  runId,
11605
12230
  requestContext: {}
11606
12231
  };
11607
- const updatedDoc = await collection.findOneAndUpdate({
12232
+ const updatedDoc = await withRunFence(this.#connector, check, (session) => collection.findOneAndUpdate({
11608
12233
  workflow_name: workflowName,
11609
12234
  run_id: runId
11610
12235
  }, [{ $set: {
@@ -11619,10 +12244,12 @@ var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends _mastra_core
11619
12244
  createdAt: { $ifNull: ["$createdAt", now] }
11620
12245
  } }], {
11621
12246
  upsert: true,
11622
- returnDocument: "after"
11623
- });
12247
+ returnDocument: "after",
12248
+ session
12249
+ }));
11624
12250
  return (typeof updatedDoc?.snapshot === "string" ? JSON.parse(updatedDoc.snapshot) : updatedDoc?.snapshot)?.context || {};
11625
12251
  } catch (error) {
12252
+ if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
11626
12253
  throw new _mastra_core_error.MastraError({
11627
12254
  id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "UPDATE_WORKFLOW_RESULTS", "FAILED"),
11628
12255
  domain: _mastra_core_error.ErrorDomain.STORAGE,
@@ -11635,7 +12262,8 @@ var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends _mastra_core
11635
12262
  }, error);
11636
12263
  }
11637
12264
  }
11638
- async updateWorkflowState({ workflowName, runId, opts }) {
12265
+ async updateWorkflowState({ workflowName, runId, opts, fence }) {
12266
+ const check = this.#runFenceCheck(fence, runId, "updateWorkflowState");
11639
12267
  try {
11640
12268
  const collection = await this.getCollection(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT);
11641
12269
  const { expectedStatus, ...state } = opts;
@@ -11644,10 +12272,13 @@ var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends _mastra_core
11644
12272
  run_id: runId
11645
12273
  };
11646
12274
  if (expectedStatus !== void 0) filter["snapshot.status"] = { $in: Array.isArray(expectedStatus) ? expectedStatus : [expectedStatus] };
11647
- const updatedDoc = await collection.findOneAndUpdate(filter, [{ $set: {
12275
+ const updatedDoc = await withRunFence(this.#connector, check, (session) => collection.findOneAndUpdate(filter, [{ $set: {
11648
12276
  snapshot: { $mergeObjects: ["$snapshot", state] },
11649
12277
  updatedAt: /* @__PURE__ */ new Date()
11650
- } }], { returnDocument: "after" });
12278
+ } }], {
12279
+ returnDocument: "after",
12280
+ session
12281
+ }));
11651
12282
  if (!updatedDoc) return;
11652
12283
  const snapshot = typeof updatedDoc.snapshot === "string" ? JSON.parse(updatedDoc.snapshot) : updatedDoc.snapshot;
11653
12284
  if (!snapshot?.context) throw new _mastra_core_error.MastraError({
@@ -11662,7 +12293,7 @@ var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends _mastra_core
11662
12293
  });
11663
12294
  return snapshot;
11664
12295
  } catch (error) {
11665
- if (error instanceof _mastra_core_error.MastraError) throw error;
12296
+ if (error instanceof _mastra_core_error.MastraError || (0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
11666
12297
  throw new _mastra_core_error.MastraError({
11667
12298
  id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "UPDATE_WORKFLOW_STATE", "FAILED"),
11668
12299
  domain: _mastra_core_error.ErrorDomain.STORAGE,
@@ -11674,10 +12305,12 @@ var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends _mastra_core
11674
12305
  }, error);
11675
12306
  }
11676
12307
  }
11677
- async persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot, createdAt, updatedAt }) {
12308
+ async persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot, createdAt, updatedAt, fence }) {
12309
+ const check = this.#runFenceCheck(fence, runId, "persistWorkflowSnapshot");
11678
12310
  try {
11679
12311
  const now = /* @__PURE__ */ new Date();
11680
- await (await this.getCollection(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT)).updateOne({
12312
+ const collection = await this.getCollection(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT);
12313
+ await withRunFence(this.#connector, check, (session) => collection.updateOne({
11681
12314
  workflow_name: workflowName,
11682
12315
  run_id: runId
11683
12316
  }, {
@@ -11689,8 +12322,12 @@ var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends _mastra_core
11689
12322
  updatedAt: updatedAt ?? now
11690
12323
  },
11691
12324
  $setOnInsert: { createdAt: createdAt ?? now }
11692
- }, { upsert: true });
12325
+ }, {
12326
+ upsert: true,
12327
+ session
12328
+ }));
11693
12329
  } catch (error) {
12330
+ if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
11694
12331
  throw new _mastra_core_error.MastraError({
11695
12332
  id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "PERSIST_WORKFLOW_SNAPSHOT", "FAILED"),
11696
12333
  domain: _mastra_core_error.ErrorDomain.STORAGE,
@@ -11783,13 +12420,16 @@ var WorkflowsStorageMongoDB = class WorkflowsStorageMongoDB extends _mastra_core
11783
12420
  }, error);
11784
12421
  }
11785
12422
  }
11786
- async deleteWorkflowRunById({ runId, workflowName }) {
12423
+ async deleteWorkflowRunById({ runId, workflowName, fence }) {
12424
+ const check = this.#runFenceCheck(fence, runId, "deleteWorkflowRunById");
11787
12425
  try {
11788
- await (await this.getCollection(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT)).deleteOne({
12426
+ const collection = await this.getCollection(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT);
12427
+ await withRunFence(this.#connector, check, (session) => collection.deleteOne({
11789
12428
  workflow_name: workflowName,
11790
12429
  run_id: runId
11791
- });
12430
+ }, { session }));
11792
12431
  } catch (error) {
12432
+ if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
11793
12433
  throw new _mastra_core_error.MastraError({
11794
12434
  id: (0, _mastra_core_storage.createStorageErrorId)("MONGODB", "DELETE_WORKFLOW_RUN_BY_ID", "FAILED"),
11795
12435
  domain: _mastra_core_error.ErrorDomain.STORAGE,