@mastra/pg 1.31.0-alpha.4 → 1.31.1-alpha.0

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
@@ -11611,6 +11611,92 @@ var MCPServersPG = class MCPServersPG extends _mastra_core_storage.MCPServersSto
11611
11611
  }
11612
11612
  };
11613
11613
  //#endregion
11614
+ //#region ../../packages/_internal-core/dist/memory/index.js
11615
+ /**
11616
+ * Shared rules for observational memory lifecycle writes. Every storage adapter applies
11617
+ * these inside its own atomic section (lock, transaction, or conditional update), so the
11618
+ * decisions are identical across adapters.
11619
+ */
11620
+ /**
11621
+ * Observational memory columns that lifecycle writes depend on. Storage adapters merge these
11622
+ * into the table schema they get from `@mastra/core`, so the columns exist even when the
11623
+ * installed core predates them.
11624
+ */
11625
+ const OBSERVATIONAL_MEMORY_LIFECYCLE_COLUMNS = { supersededBy: {
11626
+ type: "text",
11627
+ nullable: true
11628
+ } };
11629
+ /** `schema` plus {@link OBSERVATIONAL_MEMORY_LIFECYCLE_COLUMNS}; `undefined` when `schema` is. */
11630
+ function withObservationalMemoryLifecycleColumns(schema) {
11631
+ return schema ? {
11632
+ ...schema,
11633
+ ...OBSERVATIONAL_MEMORY_LIFECYCLE_COLUMNS
11634
+ } : void 0;
11635
+ }
11636
+ /**
11637
+ * A buffered chunk stores `lastObservedAt = max message time + 1ms`. It is wholly covered
11638
+ * (every message it observed is already at or before the cursor) iff
11639
+ * `cursor >= chunk.lastObservedAt - 1ms`.
11640
+ */
11641
+ function isBufferedChunkCoveredByCursor(chunkLastObservedAt, cursor) {
11642
+ if (!cursor) return false;
11643
+ return new Date(cursor).getTime() >= new Date(chunkLastObservedAt).getTime() - 1;
11644
+ }
11645
+ /** The later of two cursors; `undefined` only when both are absent. */
11646
+ function maxObservationCursor(a, b) {
11647
+ if (!a) return b ? new Date(b) : void 0;
11648
+ if (!b) return new Date(a);
11649
+ return new Date(Math.max(new Date(a).getTime(), new Date(b).getTime()));
11650
+ }
11651
+ /**
11652
+ * Decide the new generation's text when a reflection built from `snapshot` commits against
11653
+ * the stored record.
11654
+ *
11655
+ * - Stored text equals the snapshot: the reflection replaces it.
11656
+ * - Stored text extends the snapshot (activation only appends): the appended tail is kept
11657
+ * after the reflection, and its tokens are added.
11658
+ * - Anything else (a non-append rewrite): `null` — the reflection must not be applied.
11659
+ */
11660
+ function planReflectionGenerationText(input) {
11661
+ const stored = input.storedObservations ?? "";
11662
+ const snapshot = input.snapshotObservations ?? "";
11663
+ if (stored === snapshot) return {
11664
+ mode: "equal",
11665
+ observations: input.reflection,
11666
+ tokenCount: input.tokenCount
11667
+ };
11668
+ if (!stored.startsWith(snapshot)) return null;
11669
+ const tail = stored.slice(snapshot.length).trimStart();
11670
+ if (!tail.trim()) return {
11671
+ mode: "equal",
11672
+ observations: input.reflection,
11673
+ tokenCount: input.tokenCount
11674
+ };
11675
+ return {
11676
+ mode: "append",
11677
+ observations: input.reflection ? `${input.reflection}\n\n${tail}` : tail,
11678
+ tokenCount: input.tokenCount + Math.max(0, (input.storedObservationTokenCount ?? 0) - (input.snapshotObservationTokenCount ?? 0))
11679
+ };
11680
+ }
11681
+ /**
11682
+ * Backs `updateActiveObservations` in adapters that implement `commitActiveObservations`.
11683
+ * Callers of `updateActiveObservations` (which resolves `void`) can't see a commit result, so a
11684
+ * write that was not applied throws instead of resolving as if it had committed.
11685
+ */
11686
+ function assertActiveObservationsApplied(result, id) {
11687
+ if (result.applied) return;
11688
+ 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`);
11689
+ }
11690
+ /**
11691
+ * Whether the stored text can still accept a reflection built from `snapshot`
11692
+ * (equal, or only appended to since).
11693
+ */
11694
+ function isAppendOnlySince(storedObservations, snapshotObservations) {
11695
+ const stored = storedObservations ?? "";
11696
+ const snapshot = snapshotObservations ?? "";
11697
+ return stored === snapshot || stored.startsWith(snapshot);
11698
+ }
11699
+ //#endregion
11614
11700
  //#region src/storage/db/run-fencing.ts
11615
11701
  /**
11616
11702
  * Epoch milliseconds on the database clock. `clock_timestamp()` rather than
@@ -11656,6 +11742,8 @@ const OM_TABLE = "mastra_observational_memory";
11656
11742
  * one wins so the active record stays stable across reads.
11657
11743
  */
11658
11744
  const OM_GENERATION_ORDER = `"generationCount" DESC, "createdAt" ASC, id ASC`;
11745
+ /** Lifecycle writes aimed at a retired record follow it to the head at most this many times. */
11746
+ const OM_MAX_HEAD_HOPS = 3;
11659
11747
  const POSTGRES_MAX_BIND_PARAMETERS = 65535;
11660
11748
  const MESSAGE_INSERT_BIND_PARAMETERS = 8;
11661
11749
  const MAX_MESSAGES_PER_INSERT = Math.floor(POSTGRES_MAX_BIND_PARAMETERS / MESSAGE_INSERT_BIND_PARAMETERS);
@@ -11683,7 +11771,8 @@ const OM_MIGRATION_COLUMNS = [
11683
11771
  "isBufferingReflection",
11684
11772
  "lastBufferedAtTokens",
11685
11773
  "lastBufferedAtTime",
11686
- "metadata"
11774
+ "metadata",
11775
+ "supersededBy"
11687
11776
  ];
11688
11777
  /**
11689
11778
  * The OM schema is imported statically above: the peer dependency range
@@ -11693,7 +11782,10 @@ const OM_MIGRATION_COLUMNS = [
11693
11782
  * bundle to a shim that always throws, and the silent catch meant the
11694
11783
  * published ESM build skipped creating the OM table entirely (#18954).
11695
11784
  */
11696
- const _omTableSchema = _mastra_core_storage.OBSERVATIONAL_MEMORY_TABLE_SCHEMA;
11785
+ const _omTableSchema = {
11786
+ ..._mastra_core_storage.OBSERVATIONAL_MEMORY_TABLE_SCHEMA,
11787
+ [OM_TABLE]: withObservationalMemoryLifecycleColumns(_mastra_core_storage.OBSERVATIONAL_MEMORY_TABLE_SCHEMA?.[OM_TABLE])
11788
+ };
11697
11789
  function getSchemaName$3(schema) {
11698
11790
  return schema ? `"${schema}"` : "\"public\"";
11699
11791
  }
@@ -11717,6 +11809,14 @@ function inPlaceholders(count, startIndex = 1) {
11717
11809
  function toUtcISOString(date) {
11718
11810
  return date.toISOString();
11719
11811
  }
11812
+ /**
11813
+ * Read a `timestamp without time zone` column written from a UTC ISO string. Postgres drops the
11814
+ * zone on input, so the stored wall time is UTC; node-postgres parses it as local time.
11815
+ */
11816
+ function utcFromTimestampWithoutTimeZone(value) {
11817
+ if (value instanceof Date) return new Date(Date.UTC(value.getFullYear(), value.getMonth(), value.getDate(), value.getHours(), value.getMinutes(), value.getSeconds(), value.getMilliseconds()));
11818
+ return new Date(/[zZ]|[+-]\d{2}:?\d{2}$/.test(value) ? value : `${value.replace(" ", "T")}Z`);
11819
+ }
11720
11820
  function dedupeMessagesForSave(messages) {
11721
11821
  const deduped = /* @__PURE__ */ new Map();
11722
11822
  for (const message of messages) {
@@ -11831,6 +11931,7 @@ var MemoryPG = class MemoryPG extends _mastra_core_storage.MemoryStorage {
11831
11931
  schemaName: getSchemaName$3(this.#schema)
11832
11932
  });
11833
11933
  await this.#db.createIndexFromStatement("idx_om_lookup_key", `CREATE INDEX IF NOT EXISTS idx_om_lookup_key ON ${omTableName} ("lookupKey")`);
11934
+ await this.#backfillSupersededBy(omTableName);
11834
11935
  }
11835
11936
  await this.#db.createTable({
11836
11937
  tableName: _mastra_core_storage.TABLE_MEMORY_RUN_FENCES,
@@ -13428,6 +13529,115 @@ var MemoryPG = class MemoryPG extends _mastra_core_storage.MemoryStorage {
13428
13529
  async #getLatestOMRow(client, tableName, lookupKey) {
13429
13530
  return client.oneOrNone(`SELECT * FROM ${tableName} WHERE "lookupKey" = $1 ORDER BY ${OM_GENERATION_ORDER} LIMIT 1`, [lookupKey]);
13430
13531
  }
13532
+ #omTableName() {
13533
+ return getTableName$3({
13534
+ indexName: OM_TABLE,
13535
+ schemaName: getSchemaName$3(this.#schema)
13536
+ });
13537
+ }
13538
+ /** Row-locks one OM record by primary key for the rest of the transaction. */
13539
+ async #lockOMRow(t, tableName, id) {
13540
+ return t.oneOrNone(`SELECT * FROM ${tableName} WHERE id = $1 FOR UPDATE`, [id]);
13541
+ }
13542
+ /**
13543
+ * The live, row-locked record a lifecycle write aimed at `row` lands on: `row` itself while
13544
+ * live, otherwise the head of its lookup key (locked, then re-checked — a rollover may retire
13545
+ * it between the head read and the lock).
13546
+ */
13547
+ async #resolveLiveOMRow(t, tableName, row) {
13548
+ let current = row;
13549
+ for (let hop = 0; current.supersededBy; hop++) {
13550
+ const head = hop < OM_MAX_HEAD_HOPS ? await this.#getLatestOMRow(t, tableName, current.lookupKey) : null;
13551
+ const locked = head && head.id !== current.id ? await this.#lockOMRow(t, tableName, head.id) : null;
13552
+ if (!locked) throw new _mastra_core_error.MastraError({
13553
+ id: (0, _mastra_core_storage.createStorageErrorId)("PG", "RESOLVE_OBSERVATIONAL_MEMORY_HEAD", "FAILED"),
13554
+ text: `Observational memory record ${row.id} is superseded but no live head was found`,
13555
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
13556
+ category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
13557
+ details: { id: row.id }
13558
+ });
13559
+ current = locked;
13560
+ }
13561
+ return current;
13562
+ }
13563
+ /**
13564
+ * Retire every live row that sorts after its key's canonical head, marking it superseded by
13565
+ * the head. Rows retired by older adapter versions (which never set `supersededBy`) become
13566
+ * frozen. One statement: the head's ordering key is part of the condition, and a row the
13567
+ * statement waits on (a head being retired by a concurrent rollover) is re-checked against
13568
+ * `"supersededBy" IS NULL` after that rollover commits.
13569
+ */
13570
+ async #backfillSupersededBy(tableName) {
13571
+ await this.#db.client.none(`UPDATE ${tableName} AS r SET "supersededBy" = h.id
13572
+ FROM (
13573
+ SELECT DISTINCT ON ("lookupKey") "lookupKey", id, "generationCount", "createdAt"
13574
+ FROM ${tableName}
13575
+ WHERE "lookupKey" IN (
13576
+ SELECT "lookupKey" FROM ${tableName} WHERE "supersededBy" IS NULL GROUP BY "lookupKey" HAVING COUNT(*) > 1
13577
+ )
13578
+ ORDER BY "lookupKey", ${OM_GENERATION_ORDER}
13579
+ ) AS h
13580
+ WHERE r."lookupKey" = h."lookupKey"
13581
+ AND r."supersededBy" IS NULL
13582
+ AND (
13583
+ r."generationCount" < h."generationCount"
13584
+ OR (r."generationCount" = h."generationCount" AND r."createdAt" > h."createdAt")
13585
+ OR (r."generationCount" = h."generationCount" AND r."createdAt" = h."createdAt" AND r.id > h.id)
13586
+ )`);
13587
+ }
13588
+ /**
13589
+ * Create the next generation from the stored (live, row-locked) record and retire the stored
13590
+ * record in the same transaction. Carried columns are copied in SQL from the stored row, so
13591
+ * nothing round-trips through a client-side timezone conversion. Buffered chunks move to the
13592
+ * new generation; the cursor, buffering markers, flags, and counters carry over; buffered
13593
+ * reflection state does not.
13594
+ */
13595
+ async #rollOverOMRow(t, tableName, storedId, observations, tokenCount, newRecordId) {
13596
+ const id = newRecordId ?? crypto.randomUUID();
13597
+ const nowStr = (/* @__PURE__ */ new Date()).toISOString();
13598
+ const inserted = await t.one(`INSERT INTO ${tableName} (
13599
+ id, "lookupKey", scope, "resourceId", "threadId",
13600
+ "activeObservations", "activeObservationsPendingUpdate",
13601
+ "originType", config, "generationCount", "lastObservedAt", "lastObservedAtZ", "lastReflectionAt", "lastReflectionAtZ",
13602
+ "pendingMessageTokens", "totalTokensObserved", "observationTokenCount", "bufferedObservationChunks",
13603
+ "isObserving", "isReflecting", "isBufferingObservation", "isBufferingReflection", "lastBufferedAtTokens", "lastBufferedAtTime",
13604
+ "observedTimezone", metadata, "supersededBy", "createdAt", "createdAtZ", "updatedAt", "updatedAtZ"
13605
+ )
13606
+ SELECT
13607
+ $1, "lookupKey", scope, "resourceId", "threadId",
13608
+ $2, NULL,
13609
+ 'reflection', config, "generationCount" + 1, "lastObservedAt", "lastObservedAtZ", $4, $5,
13610
+ "pendingMessageTokens", "totalTokensObserved", $3, "bufferedObservationChunks",
13611
+ false, false, "isBufferingObservation", false, "lastBufferedAtTokens", "lastBufferedAtTime",
13612
+ "observedTimezone", metadata, NULL, $6, $7, $8, $9
13613
+ FROM ${tableName} WHERE id = $10
13614
+ RETURNING *`, [
13615
+ id,
13616
+ observations,
13617
+ Math.round(tokenCount),
13618
+ nowStr,
13619
+ nowStr,
13620
+ nowStr,
13621
+ nowStr,
13622
+ nowStr,
13623
+ nowStr,
13624
+ storedId
13625
+ ]);
13626
+ if ((await t.query(`UPDATE ${tableName} SET "supersededBy" = $1, "bufferedObservationChunks" = NULL, "updatedAt" = $2, "updatedAtZ" = $3
13627
+ WHERE id = $4 AND "supersededBy" IS NULL`, [
13628
+ id,
13629
+ nowStr,
13630
+ nowStr,
13631
+ storedId
13632
+ ])).rowCount !== 1) throw new _mastra_core_error.MastraError({
13633
+ id: (0, _mastra_core_storage.createStorageErrorId)("PG", "CREATE_REFLECTION_GENERATION", "RETIRE_FAILED"),
13634
+ text: `Failed to retire observational memory record ${storedId}`,
13635
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
13636
+ category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
13637
+ details: { id: storedId }
13638
+ });
13639
+ return this.parseOMRow(inserted);
13640
+ }
13431
13641
  parseOMRow(row) {
13432
13642
  return {
13433
13643
  id: row.id,
@@ -13456,11 +13666,12 @@ var MemoryPG = class MemoryPG extends _mastra_core_storage.MemoryStorage {
13456
13666
  isBufferingObservation: row.isBufferingObservation === true || row.isBufferingObservation === "true",
13457
13667
  isBufferingReflection: row.isBufferingReflection === true || row.isBufferingReflection === "true",
13458
13668
  lastBufferedAtTokens: typeof row.lastBufferedAtTokens === "number" ? row.lastBufferedAtTokens : parseInt(String(row.lastBufferedAtTokens ?? "0"), 10) || 0,
13459
- lastBufferedAtTime: row.lastBufferedAtTime ? new Date(String(row.lastBufferedAtTime)) : null,
13669
+ lastBufferedAtTime: row.lastBufferedAtTime ? utcFromTimestampWithoutTimeZone(row.lastBufferedAtTime) : null,
13460
13670
  config: row.config ? typeof row.config === "string" ? JSON.parse(row.config) : row.config : {},
13461
13671
  metadata: row.metadata ? typeof row.metadata === "string" ? JSON.parse(row.metadata) : row.metadata : void 0,
13462
13672
  observedMessageIds: row.observedMessageIds ? typeof row.observedMessageIds === "string" ? JSON.parse(row.observedMessageIds) : row.observedMessageIds : void 0,
13463
- observedTimezone: row.observedTimezone || void 0
13673
+ observedTimezone: row.observedTimezone || void 0,
13674
+ supersededBy: row.supersededBy ?? null
13464
13675
  };
13465
13676
  }
13466
13677
  async getObservationalMemory(threadId, resourceId) {
@@ -13470,7 +13681,7 @@ var MemoryPG = class MemoryPG extends _mastra_core_storage.MemoryStorage {
13470
13681
  indexName: OM_TABLE,
13471
13682
  schemaName: getSchemaName$3(this.#schema)
13472
13683
  });
13473
- const result = await this.#getLatestOMRow(this.#db.readClient, tableName, lookupKey);
13684
+ const result = await this.#getLatestOMRow(this.#db.client, tableName, lookupKey);
13474
13685
  if (!result) return null;
13475
13686
  return this.parseOMRow(result);
13476
13687
  } catch (error) {
@@ -13553,9 +13764,9 @@ var MemoryPG = class MemoryPG extends _mastra_core_storage.MemoryStorage {
13553
13764
  }
13554
13765
  async initializeObservationalMemory(input) {
13555
13766
  try {
13767
+ const lookupKey = this.getOMKey(input.threadId, input.resourceId);
13556
13768
  const id = crypto.randomUUID();
13557
13769
  const now = /* @__PURE__ */ new Date();
13558
- const lookupKey = this.getOMKey(input.threadId, input.resourceId);
13559
13770
  const record = {
13560
13771
  id,
13561
13772
  scope: input.scope,
@@ -13577,7 +13788,8 @@ var MemoryPG = class MemoryPG extends _mastra_core_storage.MemoryStorage {
13577
13788
  lastBufferedAtTokens: 0,
13578
13789
  lastBufferedAtTime: null,
13579
13790
  config: input.config,
13580
- observedTimezone: input.observedTimezone
13791
+ observedTimezone: input.observedTimezone,
13792
+ supersededBy: null
13581
13793
  };
13582
13794
  const tableName = getTableName$3({
13583
13795
  indexName: OM_TABLE,
@@ -13657,8 +13869,8 @@ var MemoryPG = class MemoryPG extends _mastra_core_storage.MemoryStorage {
13657
13869
  "reflectedObservationLineCount",
13658
13870
  "isObserving", "isReflecting", "isBufferingObservation", "isBufferingReflection",
13659
13871
  "lastBufferedAtTokens", "lastBufferedAtTime",
13660
- "observedTimezone", metadata, "createdAt", "createdAtZ", "updatedAt", "updatedAtZ"
13661
- ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32, $33, $34, $35)`, [
13872
+ "observedTimezone", metadata, "createdAt", "createdAtZ", "updatedAt", "updatedAtZ", "supersededBy"
13873
+ ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32, $33, $34, $35, $36)`, [
13662
13874
  record.id,
13663
13875
  lookupKey,
13664
13876
  record.scope,
@@ -13693,7 +13905,8 @@ var MemoryPG = class MemoryPG extends _mastra_core_storage.MemoryStorage {
13693
13905
  record.createdAt.toISOString(),
13694
13906
  record.createdAt.toISOString(),
13695
13907
  record.updatedAt.toISOString(),
13696
- record.updatedAt.toISOString()
13908
+ record.updatedAt.toISOString(),
13909
+ record.supersededBy ?? null
13697
13910
  ]);
13698
13911
  } catch (error) {
13699
13912
  throw new _mastra_core_error.MastraError({
@@ -13709,41 +13922,55 @@ var MemoryPG = class MemoryPG extends _mastra_core_storage.MemoryStorage {
13709
13922
  }
13710
13923
  }
13711
13924
  async updateActiveObservations(input) {
13925
+ assertActiveObservationsApplied(await this.commitActiveObservations(input), input.id);
13926
+ }
13927
+ async commitActiveObservations(input) {
13712
13928
  try {
13713
- const now = /* @__PURE__ */ new Date();
13714
- const tableName = getTableName$3({
13715
- indexName: OM_TABLE,
13716
- schemaName: getSchemaName$3(this.#schema)
13717
- });
13718
- const lastObservedAtStr = input.lastObservedAt.toISOString();
13719
- const nowStr = now.toISOString();
13929
+ const tableName = this.#omTableName();
13930
+ const nowStr = (/* @__PURE__ */ new Date()).toISOString();
13720
13931
  const observedMessageIdsJson = input.observedMessageIds ? toPgJson(input.observedMessageIds) : null;
13721
- if ((await this.#fenced(void 0, "updateActiveObservations", (q) => q.query(`UPDATE ${tableName} SET
13722
- "activeObservations" = $1,
13723
- "lastObservedAt" = $2,
13724
- "lastObservedAtZ" = $3,
13725
- "pendingMessageTokens" = 0,
13726
- "observationTokenCount" = $4,
13727
- "totalTokensObserved" = "totalTokensObserved" + $5,
13728
- "observedMessageIds" = $6,
13729
- "updatedAt" = $7,
13730
- "updatedAtZ" = $8
13731
- WHERE id = $9`, [
13732
- input.observations,
13733
- lastObservedAtStr,
13734
- lastObservedAtStr,
13735
- Math.round(input.tokenCount),
13736
- Math.round(input.tokenCount),
13737
- observedMessageIdsJson,
13738
- nowStr,
13739
- nowStr,
13740
- input.id
13741
- ]))).rowCount === 0) throw new _mastra_core_error.MastraError({
13742
- id: (0, _mastra_core_storage.createStorageErrorId)("PG", "UPDATE_ACTIVE_OBSERVATIONS", "NOT_FOUND"),
13743
- text: `Observational memory record not found: ${input.id}`,
13744
- domain: _mastra_core_error.ErrorDomain.STORAGE,
13745
- category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
13746
- details: { id: input.id }
13932
+ const fence = (0, _mastra_core_storage.resolveRunFence)(this, void 0);
13933
+ return await this.#db.client.tx(async (t) => {
13934
+ if (fence) await assertRunFence(t, this.#runFencesTable(), fence, "commitActiveObservations");
13935
+ const row = await this.#lockOMRow(t, tableName, input.id);
13936
+ if (!row) throw new _mastra_core_error.MastraError({
13937
+ id: (0, _mastra_core_storage.createStorageErrorId)("PG", "UPDATE_ACTIVE_OBSERVATIONS", "NOT_FOUND"),
13938
+ text: `Observational memory record not found: ${input.id}`,
13939
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
13940
+ category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
13941
+ details: { id: input.id }
13942
+ });
13943
+ if (row.supersededBy) return {
13944
+ applied: false,
13945
+ reason: "retired"
13946
+ };
13947
+ if (input.expectedActiveObservations !== void 0 && input.expectedActiveObservations !== (row.activeObservations || "")) return {
13948
+ applied: false,
13949
+ reason: "conflict"
13950
+ };
13951
+ const lastObservedAtStr = maxObservationCursor(row.lastObservedAtZ, input.lastObservedAt).toISOString();
13952
+ await t.none(`UPDATE ${tableName} SET
13953
+ "activeObservations" = $1,
13954
+ "lastObservedAt" = $2,
13955
+ "lastObservedAtZ" = $3,
13956
+ "pendingMessageTokens" = 0,
13957
+ "observationTokenCount" = $4,
13958
+ "totalTokensObserved" = "totalTokensObserved" + $5,
13959
+ "observedMessageIds" = $6,
13960
+ "updatedAt" = $7,
13961
+ "updatedAtZ" = $8
13962
+ WHERE id = $9`, [
13963
+ input.observations,
13964
+ lastObservedAtStr,
13965
+ lastObservedAtStr,
13966
+ Math.round(input.tokenCount),
13967
+ Math.round(input.tokenCount),
13968
+ observedMessageIdsJson,
13969
+ nowStr,
13970
+ nowStr,
13971
+ input.id
13972
+ ]);
13973
+ return { applied: true };
13747
13974
  });
13748
13975
  } catch (error) {
13749
13976
  if (error instanceof _mastra_core_error.MastraError || (0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
@@ -13757,18 +13984,27 @@ var MemoryPG = class MemoryPG extends _mastra_core_storage.MemoryStorage {
13757
13984
  }
13758
13985
  async createReflectionGeneration(input) {
13759
13986
  try {
13760
- const lookupKey = this.getOMKey(input.currentRecord.threadId, input.currentRecord.resourceId);
13761
- const tableName = getTableName$3({
13762
- indexName: OM_TABLE,
13763
- schemaName: getSchemaName$3(this.#schema)
13764
- });
13987
+ const { currentRecord } = input;
13988
+ const lookupKey = this.getOMKey(currentRecord.threadId, currentRecord.resourceId);
13989
+ const tableName = this.#omTableName();
13765
13990
  return await this.#withOMLookupKeyLock(tableName, lookupKey, async (t) => {
13766
- const newer = await this.#getNewerOMGeneration(t, tableName, lookupKey, input.currentRecord.generationCount);
13767
- if (newer) return newer;
13768
- return this.#insertReflectionGeneration(t, tableName, lookupKey, input);
13991
+ const row = await this.#lockOMRow(t, tableName, currentRecord.id);
13992
+ if (!row) return currentRecord;
13993
+ if (row.supersededBy) return this.parseOMRow(await this.#resolveLiveOMRow(t, tableName, row));
13994
+ const stored = this.parseOMRow(row);
13995
+ const plan = planReflectionGenerationText({
13996
+ storedObservations: stored.activeObservations,
13997
+ storedObservationTokenCount: stored.observationTokenCount,
13998
+ snapshotObservations: currentRecord.activeObservations,
13999
+ snapshotObservationTokenCount: currentRecord.observationTokenCount,
14000
+ reflection: input.reflection,
14001
+ tokenCount: input.tokenCount
14002
+ });
14003
+ if (!plan) return stored;
14004
+ return this.#rollOverOMRow(t, tableName, stored.id, plan.observations, plan.tokenCount, input.newRecordId);
13769
14005
  }, "createReflectionGeneration");
13770
14006
  } catch (error) {
13771
- if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
14007
+ if (error instanceof _mastra_core_error.MastraError) throw error;
13772
14008
  throw new _mastra_core_error.MastraError({
13773
14009
  id: (0, _mastra_core_storage.createStorageErrorId)("PG", "CREATE_REFLECTION_GENERATION", "FAILED"),
13774
14010
  domain: _mastra_core_error.ErrorDomain.STORAGE,
@@ -13777,84 +14013,6 @@ var MemoryPG = class MemoryPG extends _mastra_core_storage.MemoryStorage {
13777
14013
  }, error);
13778
14014
  }
13779
14015
  }
13780
- /**
13781
- * Returns the active record when another writer already created a generation
13782
- * after `generationCount`. Reflecting from the older generation again would
13783
- * add a second record with the same generation.
13784
- */
13785
- async #getNewerOMGeneration(t, tableName, lookupKey, generationCount) {
13786
- const latest = await this.#getLatestOMRow(t, tableName, lookupKey);
13787
- return latest && Number(latest.generationCount) > generationCount ? this.parseOMRow(latest) : null;
13788
- }
13789
- async #insertReflectionGeneration(t, tableName, lookupKey, input) {
13790
- const id = crypto.randomUUID();
13791
- const now = /* @__PURE__ */ new Date();
13792
- const record = {
13793
- id,
13794
- scope: input.currentRecord.scope,
13795
- threadId: input.currentRecord.threadId,
13796
- resourceId: input.currentRecord.resourceId,
13797
- createdAt: now,
13798
- updatedAt: now,
13799
- lastObservedAt: input.currentRecord.lastObservedAt,
13800
- originType: "reflection",
13801
- generationCount: input.currentRecord.generationCount + 1,
13802
- activeObservations: input.reflection,
13803
- totalTokensObserved: input.currentRecord.totalTokensObserved,
13804
- observationTokenCount: input.tokenCount,
13805
- pendingMessageTokens: 0,
13806
- isReflecting: false,
13807
- isObserving: false,
13808
- isBufferingObservation: false,
13809
- isBufferingReflection: false,
13810
- lastBufferedAtTokens: 0,
13811
- lastBufferedAtTime: null,
13812
- config: input.currentRecord.config,
13813
- metadata: input.currentRecord.metadata,
13814
- observedTimezone: input.currentRecord.observedTimezone
13815
- };
13816
- const nowStr = now.toISOString();
13817
- const lastObservedAtStr = record.lastObservedAt?.toISOString() || null;
13818
- await t.none(`INSERT INTO ${tableName} (
13819
- id, "lookupKey", scope, "resourceId", "threadId",
13820
- "activeObservations", "activeObservationsPendingUpdate",
13821
- "originType", config, "generationCount", "lastObservedAt", "lastObservedAtZ", "lastReflectionAt", "lastReflectionAtZ",
13822
- "pendingMessageTokens", "totalTokensObserved", "observationTokenCount",
13823
- "isObserving", "isReflecting", "isBufferingObservation", "isBufferingReflection", "lastBufferedAtTokens", "lastBufferedAtTime",
13824
- "observedTimezone", metadata, "createdAt", "createdAtZ", "updatedAt", "updatedAtZ"
13825
- ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29)`, [
13826
- id,
13827
- lookupKey,
13828
- record.scope,
13829
- record.resourceId,
13830
- record.threadId || null,
13831
- input.reflection,
13832
- null,
13833
- "reflection",
13834
- toPgJson(record.config),
13835
- input.currentRecord.generationCount + 1,
13836
- lastObservedAtStr,
13837
- lastObservedAtStr,
13838
- nowStr,
13839
- nowStr,
13840
- record.pendingMessageTokens,
13841
- Math.round(record.totalTokensObserved),
13842
- Math.round(record.observationTokenCount),
13843
- false,
13844
- false,
13845
- false,
13846
- false,
13847
- 0,
13848
- null,
13849
- record.observedTimezone || null,
13850
- record.metadata ? toPgJson(record.metadata) : null,
13851
- nowStr,
13852
- nowStr,
13853
- nowStr,
13854
- nowStr
13855
- ]);
13856
- return record;
13857
- }
13858
14016
  async setReflectingFlag(id, isReflecting) {
13859
14017
  try {
13860
14018
  const tableName = getTableName$3({
@@ -13927,41 +14085,35 @@ var MemoryPG = class MemoryPG extends _mastra_core_storage.MemoryStorage {
13927
14085
  }
13928
14086
  async setBufferingObservationFlag(id, isBuffering, lastBufferedAtTokens) {
13929
14087
  try {
13930
- const tableName = getTableName$3({
13931
- indexName: OM_TABLE,
13932
- schemaName: getSchemaName$3(this.#schema)
13933
- });
14088
+ const tableName = this.#omTableName();
13934
14089
  const nowStr = (/* @__PURE__ */ new Date()).toISOString();
13935
- let query;
13936
- let values;
13937
- if (lastBufferedAtTokens !== void 0) {
13938
- query = `UPDATE ${tableName} SET "isBufferingObservation" = $1, "lastBufferedAtTokens" = $2, "updatedAt" = $3, "updatedAtZ" = $4 WHERE id = $5`;
13939
- values = [
14090
+ await this.#db.client.tx(async (t) => {
14091
+ const row = await this.#lockOMRow(t, tableName, id);
14092
+ if (!row) throw new _mastra_core_error.MastraError({
14093
+ id: (0, _mastra_core_storage.createStorageErrorId)("PG", "SET_BUFFERING_OBSERVATION_FLAG", "NOT_FOUND"),
14094
+ text: `Observational memory record not found: ${id}`,
14095
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
14096
+ category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
14097
+ details: {
14098
+ id,
14099
+ isBuffering,
14100
+ lastBufferedAtTokens: lastBufferedAtTokens ?? null
14101
+ }
14102
+ });
14103
+ const target = await this.#resolveLiveOMRow(t, tableName, row);
14104
+ if (lastBufferedAtTokens !== void 0) await t.none(`UPDATE ${tableName} SET "isBufferingObservation" = $1, "lastBufferedAtTokens" = $2, "updatedAt" = $3, "updatedAtZ" = $4 WHERE id = $5`, [
13940
14105
  isBuffering,
13941
14106
  Math.round(lastBufferedAtTokens),
13942
14107
  nowStr,
13943
14108
  nowStr,
13944
- id
13945
- ];
13946
- } else {
13947
- query = `UPDATE ${tableName} SET "isBufferingObservation" = $1, "updatedAt" = $2, "updatedAtZ" = $3 WHERE id = $4`;
13948
- values = [
14109
+ target.id
14110
+ ]);
14111
+ else await t.none(`UPDATE ${tableName} SET "isBufferingObservation" = $1, "updatedAt" = $2, "updatedAtZ" = $3 WHERE id = $4`, [
13949
14112
  isBuffering,
13950
14113
  nowStr,
13951
14114
  nowStr,
13952
- id
13953
- ];
13954
- }
13955
- if ((await this.#db.client.query(query, values)).rowCount === 0) throw new _mastra_core_error.MastraError({
13956
- id: (0, _mastra_core_storage.createStorageErrorId)("PG", "SET_BUFFERING_OBSERVATION_FLAG", "NOT_FOUND"),
13957
- text: `Observational memory record not found: ${id}`,
13958
- domain: _mastra_core_error.ErrorDomain.STORAGE,
13959
- category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
13960
- details: {
13961
- id,
13962
- isBuffering,
13963
- lastBufferedAtTokens: lastBufferedAtTokens ?? null
13964
- }
14115
+ target.id
14116
+ ]);
13965
14117
  });
13966
14118
  } catch (error) {
13967
14119
  if (error instanceof _mastra_core_error.MastraError) throw error;
@@ -14034,29 +14186,27 @@ var MemoryPG = class MemoryPG extends _mastra_core_storage.MemoryStorage {
14034
14186
  }
14035
14187
  async setPendingMessageTokens(id, tokenCount) {
14036
14188
  try {
14037
- const tableName = getTableName$3({
14038
- indexName: OM_TABLE,
14039
- schemaName: getSchemaName$3(this.#schema)
14040
- });
14189
+ const tableName = this.#omTableName();
14041
14190
  const nowStr = (/* @__PURE__ */ new Date()).toISOString();
14042
- if ((await this.#db.client.query(`UPDATE ${tableName} SET
14043
- "pendingMessageTokens" = $1,
14044
- "updatedAt" = $2,
14045
- "updatedAtZ" = $3
14046
- WHERE id = $4`, [
14047
- Math.round(tokenCount),
14048
- nowStr,
14049
- nowStr,
14050
- id
14051
- ])).rowCount === 0) throw new _mastra_core_error.MastraError({
14052
- id: (0, _mastra_core_storage.createStorageErrorId)("PG", "SET_PENDING_MESSAGE_TOKENS", "NOT_FOUND"),
14053
- text: `Observational memory record not found: ${id}`,
14054
- domain: _mastra_core_error.ErrorDomain.STORAGE,
14055
- category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
14056
- details: {
14057
- id,
14058
- tokenCount
14059
- }
14191
+ await this.#db.client.tx(async (t) => {
14192
+ const row = await this.#lockOMRow(t, tableName, id);
14193
+ if (!row) throw new _mastra_core_error.MastraError({
14194
+ id: (0, _mastra_core_storage.createStorageErrorId)("PG", "SET_PENDING_MESSAGE_TOKENS", "NOT_FOUND"),
14195
+ text: `Observational memory record not found: ${id}`,
14196
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
14197
+ category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
14198
+ details: {
14199
+ id,
14200
+ tokenCount
14201
+ }
14202
+ });
14203
+ const target = await this.#resolveLiveOMRow(t, tableName, row);
14204
+ await t.none(`UPDATE ${tableName} SET "pendingMessageTokens" = $1, "updatedAt" = $2, "updatedAtZ" = $3 WHERE id = $4`, [
14205
+ Math.round(tokenCount),
14206
+ nowStr,
14207
+ nowStr,
14208
+ target.id
14209
+ ]);
14060
14210
  });
14061
14211
  } catch (error) {
14062
14212
  if (error instanceof _mastra_core_error.MastraError) throw error;
@@ -14106,11 +14256,11 @@ var MemoryPG = class MemoryPG extends _mastra_core_storage.MemoryStorage {
14106
14256
  }
14107
14257
  }
14108
14258
  async updateBufferedObservations(input) {
14259
+ await this.appendBufferedObservations(input);
14260
+ }
14261
+ async appendBufferedObservations(input) {
14109
14262
  try {
14110
- const tableName = getTableName$3({
14111
- indexName: OM_TABLE,
14112
- schemaName: getSchemaName$3(this.#schema)
14113
- });
14263
+ const tableName = this.#omTableName();
14114
14264
  const nowStr = (/* @__PURE__ */ new Date()).toISOString();
14115
14265
  const newChunk = {
14116
14266
  id: `ombuf-${globalThis.crypto.randomUUID()}`,
@@ -14127,32 +14277,42 @@ var MemoryPG = class MemoryPG extends _mastra_core_storage.MemoryStorage {
14127
14277
  extractedValues: input.chunk.extractedValues,
14128
14278
  extractionFailures: input.chunk.extractionFailures
14129
14279
  };
14130
- const lastBufferedAtTime = input.lastBufferedAtTime ? input.lastBufferedAtTime.toISOString() : null;
14131
- if ((await this.#fenced(void 0, "updateBufferedObservations", (q) => q.query(`UPDATE ${tableName} SET
14132
- "bufferedObservationChunks" = CASE
14133
- WHEN EXISTS (
14134
- SELECT 1
14135
- FROM jsonb_array_elements(COALESCE("bufferedObservationChunks", '[]'::jsonb)) AS chunk
14136
- WHERE chunk->>'cycleId' = $2
14137
- ) THEN COALESCE("bufferedObservationChunks", '[]'::jsonb)
14138
- ELSE COALESCE("bufferedObservationChunks", '[]'::jsonb) || $1::jsonb
14139
- END,
14140
- "lastBufferedAtTime" = COALESCE($3, "lastBufferedAtTime"),
14141
- "updatedAt" = $4,
14142
- "updatedAtZ" = $5
14143
- WHERE id = $6`, [
14144
- toPgJson([newChunk]),
14145
- input.chunk.cycleId,
14146
- lastBufferedAtTime,
14147
- nowStr,
14148
- nowStr,
14149
- input.id
14150
- ]))).rowCount === 0) throw new _mastra_core_error.MastraError({
14151
- id: (0, _mastra_core_storage.createStorageErrorId)("PG", "UPDATE_BUFFERED_OBSERVATIONS", "NOT_FOUND"),
14152
- text: `Observational memory record not found: ${input.id}`,
14153
- domain: _mastra_core_error.ErrorDomain.STORAGE,
14154
- category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
14155
- details: { id: input.id }
14280
+ const fence = (0, _mastra_core_storage.resolveRunFence)(this, void 0);
14281
+ return await this.#db.client.tx(async (t) => {
14282
+ if (fence) await assertRunFence(t, this.#runFencesTable(), fence, "appendBufferedObservations");
14283
+ const row = await this.#lockOMRow(t, tableName, input.id);
14284
+ if (!row) throw new _mastra_core_error.MastraError({
14285
+ id: (0, _mastra_core_storage.createStorageErrorId)("PG", "UPDATE_BUFFERED_OBSERVATIONS", "NOT_FOUND"),
14286
+ text: `Observational memory record not found: ${input.id}`,
14287
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
14288
+ category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
14289
+ details: { id: input.id }
14290
+ });
14291
+ const target = this.parseOMRow(await this.#resolveLiveOMRow(t, tableName, row));
14292
+ if ((Array.isArray(target.bufferedObservationChunks) ? target.bufferedObservationChunks : []).some((existing) => existing.cycleId === input.chunk.cycleId) || isBufferedChunkCoveredByCursor(input.chunk.lastObservedAt, target.lastObservedAt)) return {
14293
+ persisted: false,
14294
+ recordId: target.id
14295
+ };
14296
+ const lastBufferedAtTime = input.lastBufferedAtTime ? input.lastBufferedAtTime.toISOString() : null;
14297
+ await t.none(`UPDATE ${tableName} SET
14298
+ "bufferedObservationChunks" = CASE
14299
+ WHEN jsonb_typeof("bufferedObservationChunks") = 'array' THEN "bufferedObservationChunks" || $1::jsonb
14300
+ ELSE $1::jsonb
14301
+ END,
14302
+ "lastBufferedAtTime" = GREATEST("lastBufferedAtTime", $2::timestamp),
14303
+ "updatedAt" = $3,
14304
+ "updatedAtZ" = $4
14305
+ WHERE id = $5`, [
14306
+ toPgJson([newChunk]),
14307
+ lastBufferedAtTime,
14308
+ nowStr,
14309
+ nowStr,
14310
+ target.id
14311
+ ]);
14312
+ return {
14313
+ persisted: true,
14314
+ recordId: target.id
14315
+ };
14156
14316
  });
14157
14317
  } catch (error) {
14158
14318
  if (error instanceof _mastra_core_error.MastraError || (0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
@@ -14171,73 +14331,86 @@ var MemoryPG = class MemoryPG extends _mastra_core_storage.MemoryStorage {
14171
14331
  schemaName: getSchemaName$3(this.#schema)
14172
14332
  });
14173
14333
  const nowStr = (/* @__PURE__ */ new Date()).toISOString();
14174
- const record = await this.#db.client.oneOrNone(`SELECT * FROM ${tableName} WHERE id = $1`, [input.id]);
14175
- if (!record) throw new _mastra_core_error.MastraError({
14176
- id: (0, _mastra_core_storage.createStorageErrorId)("PG", "SWAP_BUFFERED_TO_ACTIVE", "NOT_FOUND"),
14177
- text: `Observational memory record not found: ${input.id}`,
14178
- domain: _mastra_core_error.ErrorDomain.STORAGE,
14179
- category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
14180
- details: { id: input.id }
14181
- });
14182
- let chunks = [];
14183
- if (record.bufferedObservationChunks) try {
14184
- const parsed = typeof record.bufferedObservationChunks === "string" ? JSON.parse(record.bufferedObservationChunks) : record.bufferedObservationChunks;
14185
- chunks = Array.isArray(parsed) ? parsed : [];
14186
- } catch {
14187
- chunks = [];
14188
- }
14189
- if (chunks.length === 0) return {
14190
- chunksActivated: 0,
14191
- messageTokensActivated: 0,
14192
- observationTokensActivated: 0,
14193
- messagesActivated: 0,
14194
- activatedCycleIds: [],
14195
- activatedMessageIds: []
14196
- };
14197
- const retentionFloor = input.messageTokensThreshold * (1 - input.activationRatio);
14198
- const targetMessageTokens = Math.max(0, input.currentPendingTokens - retentionFloor);
14199
- let cumulativeMessageTokens = 0;
14200
- let chunksToActivate = 0;
14201
- let bestOverBoundary = 0;
14202
- let bestOverTokens = 0;
14203
- let bestUnderBoundary = 0;
14204
- let bestUnderTokens = 0;
14205
- for (let i = 0; i < chunks.length; i++) {
14206
- cumulativeMessageTokens += chunks[i].messageTokens ?? 0;
14207
- const boundary = i + 1;
14208
- if (cumulativeMessageTokens >= targetMessageTokens) {
14209
- if (bestOverBoundary === 0 || cumulativeMessageTokens < bestOverTokens) {
14210
- bestOverBoundary = boundary;
14211
- bestOverTokens = cumulativeMessageTokens;
14334
+ const fence = (0, _mastra_core_storage.resolveRunFence)(this, void 0);
14335
+ return await this.#db.client.tx(async (t) => {
14336
+ if (fence) await assertRunFence(t, this.#runFencesTable(), fence, "swapBufferedToActive");
14337
+ const record = await this.#lockOMRow(t, tableName, input.id);
14338
+ if (!record) throw new _mastra_core_error.MastraError({
14339
+ id: (0, _mastra_core_storage.createStorageErrorId)("PG", "SWAP_BUFFERED_TO_ACTIVE", "NOT_FOUND"),
14340
+ text: `Observational memory record not found: ${input.id}`,
14341
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
14342
+ category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
14343
+ details: { id: input.id }
14344
+ });
14345
+ const emptyResult = {
14346
+ chunksActivated: 0,
14347
+ messageTokensActivated: 0,
14348
+ observationTokensActivated: 0,
14349
+ messagesActivated: 0,
14350
+ activatedCycleIds: [],
14351
+ activatedMessageIds: []
14352
+ };
14353
+ if (record.supersededBy) return {
14354
+ ...emptyResult,
14355
+ retired: true
14356
+ };
14357
+ let persistedChunks = [];
14358
+ if (record.bufferedObservationChunks) try {
14359
+ const parsed = typeof record.bufferedObservationChunks === "string" ? JSON.parse(record.bufferedObservationChunks) : record.bufferedObservationChunks;
14360
+ persistedChunks = Array.isArray(parsed) ? parsed : [];
14361
+ } catch {
14362
+ persistedChunks = [];
14363
+ }
14364
+ const refreshedWeights = new Map((Array.isArray(input.bufferedChunks) ? input.bufferedChunks : []).map((c) => [c.id, c.messageTokens]));
14365
+ const chunks = persistedChunks.map((c) => refreshedWeights.has(c.id) ? {
14366
+ ...c,
14367
+ messageTokens: refreshedWeights.get(c.id)
14368
+ } : c);
14369
+ if (chunks.length === 0) return emptyResult;
14370
+ const retentionFloor = input.messageTokensThreshold * (1 - input.activationRatio);
14371
+ const targetMessageTokens = Math.max(0, input.currentPendingTokens - retentionFloor);
14372
+ let cumulativeMessageTokens = 0;
14373
+ let chunksToActivate = 0;
14374
+ let bestOverBoundary = 0;
14375
+ let bestOverTokens = 0;
14376
+ let bestUnderBoundary = 0;
14377
+ let bestUnderTokens = 0;
14378
+ for (let i = 0; i < chunks.length; i++) {
14379
+ cumulativeMessageTokens += chunks[i].messageTokens ?? 0;
14380
+ const boundary = i + 1;
14381
+ if (cumulativeMessageTokens >= targetMessageTokens) {
14382
+ if (bestOverBoundary === 0 || cumulativeMessageTokens < bestOverTokens) {
14383
+ bestOverBoundary = boundary;
14384
+ bestOverTokens = cumulativeMessageTokens;
14385
+ }
14386
+ } else if (cumulativeMessageTokens > bestUnderTokens) {
14387
+ bestUnderBoundary = boundary;
14388
+ bestUnderTokens = cumulativeMessageTokens;
14212
14389
  }
14213
- } else if (cumulativeMessageTokens > bestUnderTokens) {
14214
- bestUnderBoundary = boundary;
14215
- bestUnderTokens = cumulativeMessageTokens;
14216
14390
  }
14217
- }
14218
- const maxOvershoot = retentionFloor * .95;
14219
- const overshoot = bestOverTokens - targetMessageTokens;
14220
- const remainingAfterOver = input.currentPendingTokens - bestOverTokens;
14221
- const remainingAfterUnder = input.currentPendingTokens - bestUnderTokens;
14222
- const minRemaining = Math.min(1e3, retentionFloor);
14223
- if (input.forceMaxActivation && bestOverBoundary > 0 && remainingAfterOver >= minRemaining) chunksToActivate = bestOverBoundary;
14224
- else if (bestOverBoundary > 0 && overshoot <= maxOvershoot && remainingAfterOver >= minRemaining) chunksToActivate = bestOverBoundary;
14225
- else if (bestUnderBoundary > 0 && remainingAfterUnder >= minRemaining) chunksToActivate = bestUnderBoundary;
14226
- else if (bestOverBoundary > 0) chunksToActivate = bestOverBoundary;
14227
- else chunksToActivate = 1;
14228
- const activatedChunks = chunks.slice(0, chunksToActivate);
14229
- const remainingChunks = chunks.slice(chunksToActivate);
14230
- const activatedContent = activatedChunks.map((c) => c.observations).join("\n\n");
14231
- const activatedTokens = Math.round(activatedChunks.reduce((sum, c) => sum + c.tokenCount, 0));
14232
- const activatedMessageTokens = Math.round(activatedChunks.reduce((sum, c) => sum + (c.messageTokens ?? 0), 0));
14233
- const activatedMessageCount = activatedChunks.reduce((sum, c) => sum + c.messageIds.length, 0);
14234
- const activatedCycleIds = activatedChunks.map((c) => c.cycleId).filter((id) => !!id);
14235
- const activatedMessageIds = activatedChunks.flatMap((c) => c.messageIds ?? []);
14236
- const latestChunk = activatedChunks[activatedChunks.length - 1];
14237
- const lastObservedAt = input.lastObservedAt ?? (latestChunk?.lastObservedAt ? new Date(latestChunk.lastObservedAt) : /* @__PURE__ */ new Date());
14238
- const lastObservedAtStr = lastObservedAt.toISOString();
14239
- const boundary = `\n\n--- message boundary (${lastObservedAt.toISOString()}) ---\n\n`;
14240
- if ((await this.#fenced(void 0, "swapBufferedToActive", (q) => q.query(`UPDATE ${tableName} SET
14391
+ const maxOvershoot = retentionFloor * .95;
14392
+ const overshoot = bestOverTokens - targetMessageTokens;
14393
+ const remainingAfterOver = input.currentPendingTokens - bestOverTokens;
14394
+ const remainingAfterUnder = input.currentPendingTokens - bestUnderTokens;
14395
+ const minRemaining = Math.min(1e3, retentionFloor);
14396
+ if (input.forceMaxActivation && bestOverBoundary > 0 && remainingAfterOver >= minRemaining) chunksToActivate = bestOverBoundary;
14397
+ else if (bestOverBoundary > 0 && overshoot <= maxOvershoot && remainingAfterOver >= minRemaining) chunksToActivate = bestOverBoundary;
14398
+ else if (bestUnderBoundary > 0 && remainingAfterUnder >= minRemaining) chunksToActivate = bestUnderBoundary;
14399
+ else if (bestOverBoundary > 0) chunksToActivate = bestOverBoundary;
14400
+ else chunksToActivate = 1;
14401
+ const activatedChunks = chunks.slice(0, chunksToActivate);
14402
+ const remainingChunks = persistedChunks.slice(chunksToActivate);
14403
+ const activatedContent = activatedChunks.map((c) => c.observations).join("\n\n");
14404
+ const activatedTokens = Math.round(activatedChunks.reduce((sum, c) => sum + c.tokenCount, 0));
14405
+ const activatedMessageTokens = Math.round(activatedChunks.reduce((sum, c) => sum + (c.messageTokens ?? 0), 0));
14406
+ const activatedMessageCount = activatedChunks.reduce((sum, c) => sum + c.messageIds.length, 0);
14407
+ const activatedCycleIds = activatedChunks.map((c) => c.cycleId).filter((id) => !!id);
14408
+ const activatedMessageIds = activatedChunks.flatMap((c) => c.messageIds ?? []);
14409
+ const latestChunk = activatedChunks[activatedChunks.length - 1];
14410
+ const lastObservedAt = input.lastObservedAt ?? (latestChunk?.lastObservedAt ? new Date(latestChunk.lastObservedAt) : /* @__PURE__ */ new Date());
14411
+ const lastObservedAtStr = maxObservationCursor(record.lastObservedAtZ, lastObservedAt).toISOString();
14412
+ const boundary = `\n\n--- message boundary (${lastObservedAt.toISOString()}) ---\n\n`;
14413
+ if ((await t.query(`UPDATE ${tableName} SET
14241
14414
  "activeObservations" = CASE
14242
14415
  WHEN "activeObservations" IS NOT NULL AND "activeObservations" != ''
14243
14416
  THEN "activeObservations" || $10 || $1
@@ -14251,45 +14424,40 @@ var MemoryPG = class MemoryPG extends _mastra_core_storage.MemoryStorage {
14251
14424
  "updatedAt" = $7,
14252
14425
  "updatedAtZ" = $8
14253
14426
  WHERE id = $9
14427
+ AND "supersededBy" IS NULL
14254
14428
  AND "bufferedObservationChunks" IS NOT NULL
14255
14429
  AND "bufferedObservationChunks"::text != '[]'`, [
14256
- activatedContent,
14257
- activatedTokens,
14258
- activatedMessageTokens,
14259
- remainingChunks.length > 0 ? toPgJson(remainingChunks) : null,
14260
- lastObservedAtStr,
14261
- lastObservedAtStr,
14262
- nowStr,
14263
- nowStr,
14264
- input.id,
14265
- boundary
14266
- ]))).rowCount === 0) return {
14267
- chunksActivated: 0,
14268
- messageTokensActivated: 0,
14269
- observationTokensActivated: 0,
14270
- messagesActivated: 0,
14271
- activatedCycleIds: [],
14272
- activatedMessageIds: []
14273
- };
14274
- const latestChunkHints = activatedChunks[activatedChunks.length - 1];
14275
- return {
14276
- chunksActivated: activatedChunks.length,
14277
- messageTokensActivated: activatedMessageTokens,
14278
- observationTokensActivated: activatedTokens,
14279
- messagesActivated: activatedMessageCount,
14280
- activatedCycleIds,
14281
- activatedMessageIds,
14282
- observations: activatedContent,
14283
- perChunk: activatedChunks.map((c) => ({
14284
- cycleId: c.cycleId ?? "",
14285
- messageTokens: c.messageTokens ?? 0,
14286
- observationTokens: c.tokenCount,
14287
- messageCount: c.messageIds.length,
14288
- observations: c.observations
14289
- })),
14290
- suggestedContinuation: latestChunkHints?.suggestedContinuation ?? void 0,
14291
- currentTask: latestChunkHints?.currentTask ?? void 0
14292
- };
14430
+ activatedContent,
14431
+ activatedTokens,
14432
+ activatedMessageTokens,
14433
+ remainingChunks.length > 0 ? toPgJson(remainingChunks) : null,
14434
+ lastObservedAtStr,
14435
+ lastObservedAtStr,
14436
+ nowStr,
14437
+ nowStr,
14438
+ input.id,
14439
+ boundary
14440
+ ])).rowCount === 0) return emptyResult;
14441
+ const latestChunkHints = activatedChunks[activatedChunks.length - 1];
14442
+ return {
14443
+ chunksActivated: activatedChunks.length,
14444
+ messageTokensActivated: activatedMessageTokens,
14445
+ observationTokensActivated: activatedTokens,
14446
+ messagesActivated: activatedMessageCount,
14447
+ activatedCycleIds,
14448
+ activatedMessageIds,
14449
+ observations: activatedContent,
14450
+ perChunk: activatedChunks.map((c) => ({
14451
+ cycleId: c.cycleId ?? "",
14452
+ messageTokens: c.messageTokens ?? 0,
14453
+ observationTokens: c.tokenCount,
14454
+ messageCount: c.messageIds.length,
14455
+ observations: c.observations
14456
+ })),
14457
+ suggestedContinuation: latestChunkHints?.suggestedContinuation ?? void 0,
14458
+ currentTask: latestChunkHints?.currentTask ?? void 0
14459
+ };
14460
+ });
14293
14461
  } catch (error) {
14294
14462
  if (error instanceof _mastra_core_error.MastraError || (0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
14295
14463
  throw new _mastra_core_error.MastraError({
@@ -14345,50 +14513,40 @@ var MemoryPG = class MemoryPG extends _mastra_core_storage.MemoryStorage {
14345
14513
  }
14346
14514
  async swapBufferedReflectionToActive(input) {
14347
14515
  try {
14348
- const tableName = getTableName$3({
14349
- indexName: OM_TABLE,
14350
- schemaName: getSchemaName$3(this.#schema)
14351
- });
14352
- const lookupKey = this.getOMKey(input.currentRecord.threadId, input.currentRecord.resourceId);
14516
+ const { currentRecord } = input;
14517
+ const tableName = this.#omTableName();
14518
+ const lookupKey = this.getOMKey(currentRecord.threadId, currentRecord.resourceId);
14353
14519
  return await this.#withOMLookupKeyLock(tableName, lookupKey, async (t) => {
14354
- const newer = await this.#getNewerOMGeneration(t, tableName, lookupKey, input.currentRecord.generationCount);
14355
- if (newer) return newer;
14356
- const record = await t.oneOrNone(`SELECT * FROM ${tableName} WHERE id = $1`, [input.currentRecord.id]);
14357
- if (!record) throw new _mastra_core_error.MastraError({
14358
- id: (0, _mastra_core_storage.createStorageErrorId)("PG", "SWAP_BUFFERED_REFLECTION_TO_ACTIVE", "NOT_FOUND"),
14359
- text: `Observational memory record not found: ${input.currentRecord.id}`,
14360
- domain: _mastra_core_error.ErrorDomain.STORAGE,
14361
- category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
14362
- details: { id: input.currentRecord.id }
14363
- });
14364
- const bufferedReflection = record.bufferedReflection || "";
14365
- const reflectedLineCount = Number(record.reflectedObservationLineCount || 0);
14520
+ const row = await this.#lockOMRow(t, tableName, currentRecord.id);
14521
+ if (!row) return currentRecord;
14522
+ if (row.supersededBy) return this.parseOMRow(await this.#resolveLiveOMRow(t, tableName, row));
14523
+ const stored = this.parseOMRow(row);
14524
+ const bufferedReflection = stored.bufferedReflection || "";
14366
14525
  if (!bufferedReflection) throw new _mastra_core_error.MastraError({
14367
14526
  id: (0, _mastra_core_storage.createStorageErrorId)("PG", "SWAP_BUFFERED_REFLECTION_TO_ACTIVE", "NO_CONTENT"),
14368
14527
  text: "No buffered reflection to swap",
14369
14528
  domain: _mastra_core_error.ErrorDomain.STORAGE,
14370
14529
  category: _mastra_core_error.ErrorCategory.USER,
14371
- details: { id: input.currentRecord.id }
14530
+ details: { id: currentRecord.id }
14372
14531
  });
14373
- const unreflectedContent = (record.activeObservations || "").split("\n").slice(reflectedLineCount).join("\n").trim();
14532
+ if (!isAppendOnlySince(stored.activeObservations, currentRecord.activeObservations)) return stored;
14533
+ const reflectedLineCount = stored.reflectedObservationLineCount ?? 0;
14534
+ const unreflectedContent = (stored.activeObservations || "").split("\n").slice(reflectedLineCount).join("\n").trim();
14374
14535
  const newObservations = unreflectedContent ? `${bufferedReflection}\n\n${unreflectedContent}` : bufferedReflection;
14375
- const newRecord = await this.#insertReflectionGeneration(t, tableName, lookupKey, {
14376
- currentRecord: input.currentRecord,
14377
- reflection: newObservations,
14378
- tokenCount: input.tokenCount
14379
- });
14536
+ const tokenCount = input.tokenCount + Math.max(0, (stored.observationTokenCount ?? 0) - (currentRecord.observationTokenCount ?? 0));
14537
+ const newRecord = await this.#rollOverOMRow(t, tableName, stored.id, newObservations, tokenCount, input.newRecordId);
14380
14538
  const nowStr = (/* @__PURE__ */ new Date()).toISOString();
14381
- await t.query(`UPDATE ${tableName} SET
14382
- "bufferedReflection" = NULL,
14383
- "bufferedReflectionTokens" = NULL,
14384
- "bufferedReflectionInputTokens" = NULL,
14385
- "reflectedObservationLineCount" = NULL,
14386
- "updatedAt" = $1,
14387
- "updatedAtZ" = $2
14388
- WHERE id = $3`, [
14539
+ await t.none(`UPDATE ${tableName} SET
14540
+ "bufferedReflection" = NULL,
14541
+ "bufferedReflectionTokens" = NULL,
14542
+ "bufferedReflectionInputTokens" = NULL,
14543
+ "reflectedObservationLineCount" = NULL,
14544
+ "updatedAt" = $1,
14545
+ "updatedAtZ" = $2
14546
+ WHERE id = $3`, [
14389
14547
  nowStr,
14390
14548
  nowStr,
14391
- input.currentRecord.id
14549
+ stored.id
14392
14550
  ]);
14393
14551
  return newRecord;
14394
14552
  }, "swapBufferedReflectionToActive");