@mastra/pg 1.31.0 → 1.31.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.js CHANGED
@@ -11587,6 +11587,92 @@ var MCPServersPG = class MCPServersPG extends MCPServersStorage {
11587
11587
  }
11588
11588
  };
11589
11589
  //#endregion
11590
+ //#region ../../packages/_internal-core/dist/memory/index.js
11591
+ /**
11592
+ * Shared rules for observational memory lifecycle writes. Every storage adapter applies
11593
+ * these inside its own atomic section (lock, transaction, or conditional update), so the
11594
+ * decisions are identical across adapters.
11595
+ */
11596
+ /**
11597
+ * Observational memory columns that lifecycle writes depend on. Storage adapters merge these
11598
+ * into the table schema they get from `@mastra/core`, so the columns exist even when the
11599
+ * installed core predates them.
11600
+ */
11601
+ const OBSERVATIONAL_MEMORY_LIFECYCLE_COLUMNS = { supersededBy: {
11602
+ type: "text",
11603
+ nullable: true
11604
+ } };
11605
+ /** `schema` plus {@link OBSERVATIONAL_MEMORY_LIFECYCLE_COLUMNS}; `undefined` when `schema` is. */
11606
+ function withObservationalMemoryLifecycleColumns(schema) {
11607
+ return schema ? {
11608
+ ...schema,
11609
+ ...OBSERVATIONAL_MEMORY_LIFECYCLE_COLUMNS
11610
+ } : void 0;
11611
+ }
11612
+ /**
11613
+ * A buffered chunk stores `lastObservedAt = max message time + 1ms`. It is wholly covered
11614
+ * (every message it observed is already at or before the cursor) iff
11615
+ * `cursor >= chunk.lastObservedAt - 1ms`.
11616
+ */
11617
+ function isBufferedChunkCoveredByCursor(chunkLastObservedAt, cursor) {
11618
+ if (!cursor) return false;
11619
+ return new Date(cursor).getTime() >= new Date(chunkLastObservedAt).getTime() - 1;
11620
+ }
11621
+ /** The later of two cursors; `undefined` only when both are absent. */
11622
+ function maxObservationCursor(a, b) {
11623
+ if (!a) return b ? new Date(b) : void 0;
11624
+ if (!b) return new Date(a);
11625
+ return new Date(Math.max(new Date(a).getTime(), new Date(b).getTime()));
11626
+ }
11627
+ /**
11628
+ * Decide the new generation's text when a reflection built from `snapshot` commits against
11629
+ * the stored record.
11630
+ *
11631
+ * - Stored text equals the snapshot: the reflection replaces it.
11632
+ * - Stored text extends the snapshot (activation only appends): the appended tail is kept
11633
+ * after the reflection, and its tokens are added.
11634
+ * - Anything else (a non-append rewrite): `null` — the reflection must not be applied.
11635
+ */
11636
+ function planReflectionGenerationText(input) {
11637
+ const stored = input.storedObservations ?? "";
11638
+ const snapshot = input.snapshotObservations ?? "";
11639
+ if (stored === snapshot) return {
11640
+ mode: "equal",
11641
+ observations: input.reflection,
11642
+ tokenCount: input.tokenCount
11643
+ };
11644
+ if (!stored.startsWith(snapshot)) return null;
11645
+ const tail = stored.slice(snapshot.length).trimStart();
11646
+ if (!tail.trim()) return {
11647
+ mode: "equal",
11648
+ observations: input.reflection,
11649
+ tokenCount: input.tokenCount
11650
+ };
11651
+ return {
11652
+ mode: "append",
11653
+ observations: input.reflection ? `${input.reflection}\n\n${tail}` : tail,
11654
+ tokenCount: input.tokenCount + Math.max(0, (input.storedObservationTokenCount ?? 0) - (input.snapshotObservationTokenCount ?? 0))
11655
+ };
11656
+ }
11657
+ /**
11658
+ * Backs `updateActiveObservations` in adapters that implement `commitActiveObservations`.
11659
+ * Callers of `updateActiveObservations` (which resolves `void`) can't see a commit result, so a
11660
+ * write that was not applied throws instead of resolving as if it had committed.
11661
+ */
11662
+ function assertActiveObservationsApplied(result, id) {
11663
+ if (result.applied) return;
11664
+ 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`);
11665
+ }
11666
+ /**
11667
+ * Whether the stored text can still accept a reflection built from `snapshot`
11668
+ * (equal, or only appended to since).
11669
+ */
11670
+ function isAppendOnlySince(storedObservations, snapshotObservations) {
11671
+ const stored = storedObservations ?? "";
11672
+ const snapshot = snapshotObservations ?? "";
11673
+ return stored === snapshot || stored.startsWith(snapshot);
11674
+ }
11675
+ //#endregion
11590
11676
  //#region src/storage/db/run-fencing.ts
11591
11677
  /**
11592
11678
  * Epoch milliseconds on the database clock. `clock_timestamp()` rather than
@@ -11632,6 +11718,8 @@ const OM_TABLE = "mastra_observational_memory";
11632
11718
  * one wins so the active record stays stable across reads.
11633
11719
  */
11634
11720
  const OM_GENERATION_ORDER = `"generationCount" DESC, "createdAt" ASC, id ASC`;
11721
+ /** Lifecycle writes aimed at a retired record follow it to the head at most this many times. */
11722
+ const OM_MAX_HEAD_HOPS = 3;
11635
11723
  const POSTGRES_MAX_BIND_PARAMETERS = 65535;
11636
11724
  const MESSAGE_INSERT_BIND_PARAMETERS = 8;
11637
11725
  const MAX_MESSAGES_PER_INSERT = Math.floor(POSTGRES_MAX_BIND_PARAMETERS / MESSAGE_INSERT_BIND_PARAMETERS);
@@ -11659,7 +11747,8 @@ const OM_MIGRATION_COLUMNS = [
11659
11747
  "isBufferingReflection",
11660
11748
  "lastBufferedAtTokens",
11661
11749
  "lastBufferedAtTime",
11662
- "metadata"
11750
+ "metadata",
11751
+ "supersededBy"
11663
11752
  ];
11664
11753
  /**
11665
11754
  * The OM schema is imported statically above: the peer dependency range
@@ -11669,7 +11758,10 @@ const OM_MIGRATION_COLUMNS = [
11669
11758
  * bundle to a shim that always throws, and the silent catch meant the
11670
11759
  * published ESM build skipped creating the OM table entirely (#18954).
11671
11760
  */
11672
- const _omTableSchema = OBSERVATIONAL_MEMORY_TABLE_SCHEMA;
11761
+ const _omTableSchema = {
11762
+ ...OBSERVATIONAL_MEMORY_TABLE_SCHEMA,
11763
+ [OM_TABLE]: withObservationalMemoryLifecycleColumns(OBSERVATIONAL_MEMORY_TABLE_SCHEMA?.[OM_TABLE])
11764
+ };
11673
11765
  function getSchemaName$3(schema) {
11674
11766
  return schema ? `"${schema}"` : "\"public\"";
11675
11767
  }
@@ -11693,6 +11785,14 @@ function inPlaceholders(count, startIndex = 1) {
11693
11785
  function toUtcISOString(date) {
11694
11786
  return date.toISOString();
11695
11787
  }
11788
+ /**
11789
+ * Read a `timestamp without time zone` column written from a UTC ISO string. Postgres drops the
11790
+ * zone on input, so the stored wall time is UTC; node-postgres parses it as local time.
11791
+ */
11792
+ function utcFromTimestampWithoutTimeZone(value) {
11793
+ if (value instanceof Date) return new Date(Date.UTC(value.getFullYear(), value.getMonth(), value.getDate(), value.getHours(), value.getMinutes(), value.getSeconds(), value.getMilliseconds()));
11794
+ return new Date(/[zZ]|[+-]\d{2}:?\d{2}$/.test(value) ? value : `${value.replace(" ", "T")}Z`);
11795
+ }
11696
11796
  function dedupeMessagesForSave(messages) {
11697
11797
  const deduped = /* @__PURE__ */ new Map();
11698
11798
  for (const message of messages) {
@@ -11807,6 +11907,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
11807
11907
  schemaName: getSchemaName$3(this.#schema)
11808
11908
  });
11809
11909
  await this.#db.createIndexFromStatement("idx_om_lookup_key", `CREATE INDEX IF NOT EXISTS idx_om_lookup_key ON ${omTableName} ("lookupKey")`);
11910
+ await this.#backfillSupersededBy(omTableName);
11810
11911
  }
11811
11912
  await this.#db.createTable({
11812
11913
  tableName: TABLE_MEMORY_RUN_FENCES,
@@ -13404,6 +13505,115 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
13404
13505
  async #getLatestOMRow(client, tableName, lookupKey) {
13405
13506
  return client.oneOrNone(`SELECT * FROM ${tableName} WHERE "lookupKey" = $1 ORDER BY ${OM_GENERATION_ORDER} LIMIT 1`, [lookupKey]);
13406
13507
  }
13508
+ #omTableName() {
13509
+ return getTableName$3({
13510
+ indexName: OM_TABLE,
13511
+ schemaName: getSchemaName$3(this.#schema)
13512
+ });
13513
+ }
13514
+ /** Row-locks one OM record by primary key for the rest of the transaction. */
13515
+ async #lockOMRow(t, tableName, id) {
13516
+ return t.oneOrNone(`SELECT * FROM ${tableName} WHERE id = $1 FOR UPDATE`, [id]);
13517
+ }
13518
+ /**
13519
+ * The live, row-locked record a lifecycle write aimed at `row` lands on: `row` itself while
13520
+ * live, otherwise the head of its lookup key (locked, then re-checked — a rollover may retire
13521
+ * it between the head read and the lock).
13522
+ */
13523
+ async #resolveLiveOMRow(t, tableName, row) {
13524
+ let current = row;
13525
+ for (let hop = 0; current.supersededBy; hop++) {
13526
+ const head = hop < OM_MAX_HEAD_HOPS ? await this.#getLatestOMRow(t, tableName, current.lookupKey) : null;
13527
+ const locked = head && head.id !== current.id ? await this.#lockOMRow(t, tableName, head.id) : null;
13528
+ if (!locked) throw new MastraError({
13529
+ id: createStorageErrorId("PG", "RESOLVE_OBSERVATIONAL_MEMORY_HEAD", "FAILED"),
13530
+ text: `Observational memory record ${row.id} is superseded but no live head was found`,
13531
+ domain: ErrorDomain.STORAGE,
13532
+ category: ErrorCategory.THIRD_PARTY,
13533
+ details: { id: row.id }
13534
+ });
13535
+ current = locked;
13536
+ }
13537
+ return current;
13538
+ }
13539
+ /**
13540
+ * Retire every live row that sorts after its key's canonical head, marking it superseded by
13541
+ * the head. Rows retired by older adapter versions (which never set `supersededBy`) become
13542
+ * frozen. One statement: the head's ordering key is part of the condition, and a row the
13543
+ * statement waits on (a head being retired by a concurrent rollover) is re-checked against
13544
+ * `"supersededBy" IS NULL` after that rollover commits.
13545
+ */
13546
+ async #backfillSupersededBy(tableName) {
13547
+ await this.#db.client.none(`UPDATE ${tableName} AS r SET "supersededBy" = h.id
13548
+ FROM (
13549
+ SELECT DISTINCT ON ("lookupKey") "lookupKey", id, "generationCount", "createdAt"
13550
+ FROM ${tableName}
13551
+ WHERE "lookupKey" IN (
13552
+ SELECT "lookupKey" FROM ${tableName} WHERE "supersededBy" IS NULL GROUP BY "lookupKey" HAVING COUNT(*) > 1
13553
+ )
13554
+ ORDER BY "lookupKey", ${OM_GENERATION_ORDER}
13555
+ ) AS h
13556
+ WHERE r."lookupKey" = h."lookupKey"
13557
+ AND r."supersededBy" IS NULL
13558
+ AND (
13559
+ r."generationCount" < h."generationCount"
13560
+ OR (r."generationCount" = h."generationCount" AND r."createdAt" > h."createdAt")
13561
+ OR (r."generationCount" = h."generationCount" AND r."createdAt" = h."createdAt" AND r.id > h.id)
13562
+ )`);
13563
+ }
13564
+ /**
13565
+ * Create the next generation from the stored (live, row-locked) record and retire the stored
13566
+ * record in the same transaction. Carried columns are copied in SQL from the stored row, so
13567
+ * nothing round-trips through a client-side timezone conversion. Buffered chunks move to the
13568
+ * new generation; the cursor, buffering markers, flags, and counters carry over; buffered
13569
+ * reflection state does not.
13570
+ */
13571
+ async #rollOverOMRow(t, tableName, storedId, observations, tokenCount, newRecordId) {
13572
+ const id = newRecordId ?? crypto.randomUUID();
13573
+ const nowStr = (/* @__PURE__ */ new Date()).toISOString();
13574
+ const inserted = await t.one(`INSERT INTO ${tableName} (
13575
+ id, "lookupKey", scope, "resourceId", "threadId",
13576
+ "activeObservations", "activeObservationsPendingUpdate",
13577
+ "originType", config, "generationCount", "lastObservedAt", "lastObservedAtZ", "lastReflectionAt", "lastReflectionAtZ",
13578
+ "pendingMessageTokens", "totalTokensObserved", "observationTokenCount", "bufferedObservationChunks",
13579
+ "isObserving", "isReflecting", "isBufferingObservation", "isBufferingReflection", "lastBufferedAtTokens", "lastBufferedAtTime",
13580
+ "observedTimezone", metadata, "supersededBy", "createdAt", "createdAtZ", "updatedAt", "updatedAtZ"
13581
+ )
13582
+ SELECT
13583
+ $1, "lookupKey", scope, "resourceId", "threadId",
13584
+ $2, NULL,
13585
+ 'reflection', config, "generationCount" + 1, "lastObservedAt", "lastObservedAtZ", $4, $5,
13586
+ "pendingMessageTokens", "totalTokensObserved", $3, "bufferedObservationChunks",
13587
+ false, false, "isBufferingObservation", false, "lastBufferedAtTokens", "lastBufferedAtTime",
13588
+ "observedTimezone", metadata, NULL, $6, $7, $8, $9
13589
+ FROM ${tableName} WHERE id = $10
13590
+ RETURNING *`, [
13591
+ id,
13592
+ observations,
13593
+ Math.round(tokenCount),
13594
+ nowStr,
13595
+ nowStr,
13596
+ nowStr,
13597
+ nowStr,
13598
+ nowStr,
13599
+ nowStr,
13600
+ storedId
13601
+ ]);
13602
+ if ((await t.query(`UPDATE ${tableName} SET "supersededBy" = $1, "bufferedObservationChunks" = NULL, "updatedAt" = $2, "updatedAtZ" = $3
13603
+ WHERE id = $4 AND "supersededBy" IS NULL`, [
13604
+ id,
13605
+ nowStr,
13606
+ nowStr,
13607
+ storedId
13608
+ ])).rowCount !== 1) throw new MastraError({
13609
+ id: createStorageErrorId("PG", "CREATE_REFLECTION_GENERATION", "RETIRE_FAILED"),
13610
+ text: `Failed to retire observational memory record ${storedId}`,
13611
+ domain: ErrorDomain.STORAGE,
13612
+ category: ErrorCategory.THIRD_PARTY,
13613
+ details: { id: storedId }
13614
+ });
13615
+ return this.parseOMRow(inserted);
13616
+ }
13407
13617
  parseOMRow(row) {
13408
13618
  return {
13409
13619
  id: row.id,
@@ -13432,11 +13642,12 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
13432
13642
  isBufferingObservation: row.isBufferingObservation === true || row.isBufferingObservation === "true",
13433
13643
  isBufferingReflection: row.isBufferingReflection === true || row.isBufferingReflection === "true",
13434
13644
  lastBufferedAtTokens: typeof row.lastBufferedAtTokens === "number" ? row.lastBufferedAtTokens : parseInt(String(row.lastBufferedAtTokens ?? "0"), 10) || 0,
13435
- lastBufferedAtTime: row.lastBufferedAtTime ? new Date(String(row.lastBufferedAtTime)) : null,
13645
+ lastBufferedAtTime: row.lastBufferedAtTime ? utcFromTimestampWithoutTimeZone(row.lastBufferedAtTime) : null,
13436
13646
  config: row.config ? typeof row.config === "string" ? JSON.parse(row.config) : row.config : {},
13437
13647
  metadata: row.metadata ? typeof row.metadata === "string" ? JSON.parse(row.metadata) : row.metadata : void 0,
13438
13648
  observedMessageIds: row.observedMessageIds ? typeof row.observedMessageIds === "string" ? JSON.parse(row.observedMessageIds) : row.observedMessageIds : void 0,
13439
- observedTimezone: row.observedTimezone || void 0
13649
+ observedTimezone: row.observedTimezone || void 0,
13650
+ supersededBy: row.supersededBy ?? null
13440
13651
  };
13441
13652
  }
13442
13653
  async getObservationalMemory(threadId, resourceId) {
@@ -13446,7 +13657,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
13446
13657
  indexName: OM_TABLE,
13447
13658
  schemaName: getSchemaName$3(this.#schema)
13448
13659
  });
13449
- const result = await this.#getLatestOMRow(this.#db.readClient, tableName, lookupKey);
13660
+ const result = await this.#getLatestOMRow(this.#db.client, tableName, lookupKey);
13450
13661
  if (!result) return null;
13451
13662
  return this.parseOMRow(result);
13452
13663
  } catch (error) {
@@ -13529,9 +13740,9 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
13529
13740
  }
13530
13741
  async initializeObservationalMemory(input) {
13531
13742
  try {
13743
+ const lookupKey = this.getOMKey(input.threadId, input.resourceId);
13532
13744
  const id = crypto.randomUUID();
13533
13745
  const now = /* @__PURE__ */ new Date();
13534
- const lookupKey = this.getOMKey(input.threadId, input.resourceId);
13535
13746
  const record = {
13536
13747
  id,
13537
13748
  scope: input.scope,
@@ -13553,7 +13764,8 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
13553
13764
  lastBufferedAtTokens: 0,
13554
13765
  lastBufferedAtTime: null,
13555
13766
  config: input.config,
13556
- observedTimezone: input.observedTimezone
13767
+ observedTimezone: input.observedTimezone,
13768
+ supersededBy: null
13557
13769
  };
13558
13770
  const tableName = getTableName$3({
13559
13771
  indexName: OM_TABLE,
@@ -13633,8 +13845,8 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
13633
13845
  "reflectedObservationLineCount",
13634
13846
  "isObserving", "isReflecting", "isBufferingObservation", "isBufferingReflection",
13635
13847
  "lastBufferedAtTokens", "lastBufferedAtTime",
13636
- "observedTimezone", metadata, "createdAt", "createdAtZ", "updatedAt", "updatedAtZ"
13637
- ) 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)`, [
13848
+ "observedTimezone", metadata, "createdAt", "createdAtZ", "updatedAt", "updatedAtZ", "supersededBy"
13849
+ ) 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)`, [
13638
13850
  record.id,
13639
13851
  lookupKey,
13640
13852
  record.scope,
@@ -13669,7 +13881,8 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
13669
13881
  record.createdAt.toISOString(),
13670
13882
  record.createdAt.toISOString(),
13671
13883
  record.updatedAt.toISOString(),
13672
- record.updatedAt.toISOString()
13884
+ record.updatedAt.toISOString(),
13885
+ record.supersededBy ?? null
13673
13886
  ]);
13674
13887
  } catch (error) {
13675
13888
  throw new MastraError({
@@ -13685,41 +13898,55 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
13685
13898
  }
13686
13899
  }
13687
13900
  async updateActiveObservations(input) {
13901
+ assertActiveObservationsApplied(await this.commitActiveObservations(input), input.id);
13902
+ }
13903
+ async commitActiveObservations(input) {
13688
13904
  try {
13689
- const now = /* @__PURE__ */ new Date();
13690
- const tableName = getTableName$3({
13691
- indexName: OM_TABLE,
13692
- schemaName: getSchemaName$3(this.#schema)
13693
- });
13694
- const lastObservedAtStr = input.lastObservedAt.toISOString();
13695
- const nowStr = now.toISOString();
13905
+ const tableName = this.#omTableName();
13906
+ const nowStr = (/* @__PURE__ */ new Date()).toISOString();
13696
13907
  const observedMessageIdsJson = input.observedMessageIds ? toPgJson(input.observedMessageIds) : null;
13697
- if ((await this.#fenced(void 0, "updateActiveObservations", (q) => q.query(`UPDATE ${tableName} SET
13698
- "activeObservations" = $1,
13699
- "lastObservedAt" = $2,
13700
- "lastObservedAtZ" = $3,
13701
- "pendingMessageTokens" = 0,
13702
- "observationTokenCount" = $4,
13703
- "totalTokensObserved" = "totalTokensObserved" + $5,
13704
- "observedMessageIds" = $6,
13705
- "updatedAt" = $7,
13706
- "updatedAtZ" = $8
13707
- WHERE id = $9`, [
13708
- input.observations,
13709
- lastObservedAtStr,
13710
- lastObservedAtStr,
13711
- Math.round(input.tokenCount),
13712
- Math.round(input.tokenCount),
13713
- observedMessageIdsJson,
13714
- nowStr,
13715
- nowStr,
13716
- input.id
13717
- ]))).rowCount === 0) throw new MastraError({
13718
- id: createStorageErrorId("PG", "UPDATE_ACTIVE_OBSERVATIONS", "NOT_FOUND"),
13719
- text: `Observational memory record not found: ${input.id}`,
13720
- domain: ErrorDomain.STORAGE,
13721
- category: ErrorCategory.THIRD_PARTY,
13722
- details: { id: input.id }
13908
+ const fence = resolveRunFence(this, void 0);
13909
+ return await this.#db.client.tx(async (t) => {
13910
+ if (fence) await assertRunFence(t, this.#runFencesTable(), fence, "commitActiveObservations");
13911
+ const row = await this.#lockOMRow(t, tableName, input.id);
13912
+ if (!row) throw new MastraError({
13913
+ id: createStorageErrorId("PG", "UPDATE_ACTIVE_OBSERVATIONS", "NOT_FOUND"),
13914
+ text: `Observational memory record not found: ${input.id}`,
13915
+ domain: ErrorDomain.STORAGE,
13916
+ category: ErrorCategory.THIRD_PARTY,
13917
+ details: { id: input.id }
13918
+ });
13919
+ if (row.supersededBy) return {
13920
+ applied: false,
13921
+ reason: "retired"
13922
+ };
13923
+ if (input.expectedActiveObservations !== void 0 && input.expectedActiveObservations !== (row.activeObservations || "")) return {
13924
+ applied: false,
13925
+ reason: "conflict"
13926
+ };
13927
+ const lastObservedAtStr = maxObservationCursor(row.lastObservedAtZ, input.lastObservedAt).toISOString();
13928
+ await t.none(`UPDATE ${tableName} SET
13929
+ "activeObservations" = $1,
13930
+ "lastObservedAt" = $2,
13931
+ "lastObservedAtZ" = $3,
13932
+ "pendingMessageTokens" = 0,
13933
+ "observationTokenCount" = $4,
13934
+ "totalTokensObserved" = "totalTokensObserved" + $5,
13935
+ "observedMessageIds" = $6,
13936
+ "updatedAt" = $7,
13937
+ "updatedAtZ" = $8
13938
+ WHERE id = $9`, [
13939
+ input.observations,
13940
+ lastObservedAtStr,
13941
+ lastObservedAtStr,
13942
+ Math.round(input.tokenCount),
13943
+ Math.round(input.tokenCount),
13944
+ observedMessageIdsJson,
13945
+ nowStr,
13946
+ nowStr,
13947
+ input.id
13948
+ ]);
13949
+ return { applied: true };
13723
13950
  });
13724
13951
  } catch (error) {
13725
13952
  if (error instanceof MastraError || isRunFenceConflictError(error)) throw error;
@@ -13733,18 +13960,27 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
13733
13960
  }
13734
13961
  async createReflectionGeneration(input) {
13735
13962
  try {
13736
- const lookupKey = this.getOMKey(input.currentRecord.threadId, input.currentRecord.resourceId);
13737
- const tableName = getTableName$3({
13738
- indexName: OM_TABLE,
13739
- schemaName: getSchemaName$3(this.#schema)
13740
- });
13963
+ const { currentRecord } = input;
13964
+ const lookupKey = this.getOMKey(currentRecord.threadId, currentRecord.resourceId);
13965
+ const tableName = this.#omTableName();
13741
13966
  return await this.#withOMLookupKeyLock(tableName, lookupKey, async (t) => {
13742
- const newer = await this.#getNewerOMGeneration(t, tableName, lookupKey, input.currentRecord.generationCount);
13743
- if (newer) return newer;
13744
- return this.#insertReflectionGeneration(t, tableName, lookupKey, input);
13967
+ const row = await this.#lockOMRow(t, tableName, currentRecord.id);
13968
+ if (!row) return currentRecord;
13969
+ if (row.supersededBy) return this.parseOMRow(await this.#resolveLiveOMRow(t, tableName, row));
13970
+ const stored = this.parseOMRow(row);
13971
+ const plan = planReflectionGenerationText({
13972
+ storedObservations: stored.activeObservations,
13973
+ storedObservationTokenCount: stored.observationTokenCount,
13974
+ snapshotObservations: currentRecord.activeObservations,
13975
+ snapshotObservationTokenCount: currentRecord.observationTokenCount,
13976
+ reflection: input.reflection,
13977
+ tokenCount: input.tokenCount
13978
+ });
13979
+ if (!plan) return stored;
13980
+ return this.#rollOverOMRow(t, tableName, stored.id, plan.observations, plan.tokenCount, input.newRecordId);
13745
13981
  }, "createReflectionGeneration");
13746
13982
  } catch (error) {
13747
- if (isRunFenceConflictError(error)) throw error;
13983
+ if (error instanceof MastraError) throw error;
13748
13984
  throw new MastraError({
13749
13985
  id: createStorageErrorId("PG", "CREATE_REFLECTION_GENERATION", "FAILED"),
13750
13986
  domain: ErrorDomain.STORAGE,
@@ -13753,84 +13989,6 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
13753
13989
  }, error);
13754
13990
  }
13755
13991
  }
13756
- /**
13757
- * Returns the active record when another writer already created a generation
13758
- * after `generationCount`. Reflecting from the older generation again would
13759
- * add a second record with the same generation.
13760
- */
13761
- async #getNewerOMGeneration(t, tableName, lookupKey, generationCount) {
13762
- const latest = await this.#getLatestOMRow(t, tableName, lookupKey);
13763
- return latest && Number(latest.generationCount) > generationCount ? this.parseOMRow(latest) : null;
13764
- }
13765
- async #insertReflectionGeneration(t, tableName, lookupKey, input) {
13766
- const id = crypto.randomUUID();
13767
- const now = /* @__PURE__ */ new Date();
13768
- const record = {
13769
- id,
13770
- scope: input.currentRecord.scope,
13771
- threadId: input.currentRecord.threadId,
13772
- resourceId: input.currentRecord.resourceId,
13773
- createdAt: now,
13774
- updatedAt: now,
13775
- lastObservedAt: input.currentRecord.lastObservedAt,
13776
- originType: "reflection",
13777
- generationCount: input.currentRecord.generationCount + 1,
13778
- activeObservations: input.reflection,
13779
- totalTokensObserved: input.currentRecord.totalTokensObserved,
13780
- observationTokenCount: input.tokenCount,
13781
- pendingMessageTokens: 0,
13782
- isReflecting: false,
13783
- isObserving: false,
13784
- isBufferingObservation: false,
13785
- isBufferingReflection: false,
13786
- lastBufferedAtTokens: 0,
13787
- lastBufferedAtTime: null,
13788
- config: input.currentRecord.config,
13789
- metadata: input.currentRecord.metadata,
13790
- observedTimezone: input.currentRecord.observedTimezone
13791
- };
13792
- const nowStr = now.toISOString();
13793
- const lastObservedAtStr = record.lastObservedAt?.toISOString() || null;
13794
- await t.none(`INSERT INTO ${tableName} (
13795
- id, "lookupKey", scope, "resourceId", "threadId",
13796
- "activeObservations", "activeObservationsPendingUpdate",
13797
- "originType", config, "generationCount", "lastObservedAt", "lastObservedAtZ", "lastReflectionAt", "lastReflectionAtZ",
13798
- "pendingMessageTokens", "totalTokensObserved", "observationTokenCount",
13799
- "isObserving", "isReflecting", "isBufferingObservation", "isBufferingReflection", "lastBufferedAtTokens", "lastBufferedAtTime",
13800
- "observedTimezone", metadata, "createdAt", "createdAtZ", "updatedAt", "updatedAtZ"
13801
- ) 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)`, [
13802
- id,
13803
- lookupKey,
13804
- record.scope,
13805
- record.resourceId,
13806
- record.threadId || null,
13807
- input.reflection,
13808
- null,
13809
- "reflection",
13810
- toPgJson(record.config),
13811
- input.currentRecord.generationCount + 1,
13812
- lastObservedAtStr,
13813
- lastObservedAtStr,
13814
- nowStr,
13815
- nowStr,
13816
- record.pendingMessageTokens,
13817
- Math.round(record.totalTokensObserved),
13818
- Math.round(record.observationTokenCount),
13819
- false,
13820
- false,
13821
- false,
13822
- false,
13823
- 0,
13824
- null,
13825
- record.observedTimezone || null,
13826
- record.metadata ? toPgJson(record.metadata) : null,
13827
- nowStr,
13828
- nowStr,
13829
- nowStr,
13830
- nowStr
13831
- ]);
13832
- return record;
13833
- }
13834
13992
  async setReflectingFlag(id, isReflecting) {
13835
13993
  try {
13836
13994
  const tableName = getTableName$3({
@@ -13903,41 +14061,35 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
13903
14061
  }
13904
14062
  async setBufferingObservationFlag(id, isBuffering, lastBufferedAtTokens) {
13905
14063
  try {
13906
- const tableName = getTableName$3({
13907
- indexName: OM_TABLE,
13908
- schemaName: getSchemaName$3(this.#schema)
13909
- });
14064
+ const tableName = this.#omTableName();
13910
14065
  const nowStr = (/* @__PURE__ */ new Date()).toISOString();
13911
- let query;
13912
- let values;
13913
- if (lastBufferedAtTokens !== void 0) {
13914
- query = `UPDATE ${tableName} SET "isBufferingObservation" = $1, "lastBufferedAtTokens" = $2, "updatedAt" = $3, "updatedAtZ" = $4 WHERE id = $5`;
13915
- values = [
14066
+ await this.#db.client.tx(async (t) => {
14067
+ const row = await this.#lockOMRow(t, tableName, id);
14068
+ if (!row) throw new MastraError({
14069
+ id: createStorageErrorId("PG", "SET_BUFFERING_OBSERVATION_FLAG", "NOT_FOUND"),
14070
+ text: `Observational memory record not found: ${id}`,
14071
+ domain: ErrorDomain.STORAGE,
14072
+ category: ErrorCategory.THIRD_PARTY,
14073
+ details: {
14074
+ id,
14075
+ isBuffering,
14076
+ lastBufferedAtTokens: lastBufferedAtTokens ?? null
14077
+ }
14078
+ });
14079
+ const target = await this.#resolveLiveOMRow(t, tableName, row);
14080
+ if (lastBufferedAtTokens !== void 0) await t.none(`UPDATE ${tableName} SET "isBufferingObservation" = $1, "lastBufferedAtTokens" = $2, "updatedAt" = $3, "updatedAtZ" = $4 WHERE id = $5`, [
13916
14081
  isBuffering,
13917
14082
  Math.round(lastBufferedAtTokens),
13918
14083
  nowStr,
13919
14084
  nowStr,
13920
- id
13921
- ];
13922
- } else {
13923
- query = `UPDATE ${tableName} SET "isBufferingObservation" = $1, "updatedAt" = $2, "updatedAtZ" = $3 WHERE id = $4`;
13924
- values = [
14085
+ target.id
14086
+ ]);
14087
+ else await t.none(`UPDATE ${tableName} SET "isBufferingObservation" = $1, "updatedAt" = $2, "updatedAtZ" = $3 WHERE id = $4`, [
13925
14088
  isBuffering,
13926
14089
  nowStr,
13927
14090
  nowStr,
13928
- id
13929
- ];
13930
- }
13931
- if ((await this.#db.client.query(query, values)).rowCount === 0) throw new MastraError({
13932
- id: createStorageErrorId("PG", "SET_BUFFERING_OBSERVATION_FLAG", "NOT_FOUND"),
13933
- text: `Observational memory record not found: ${id}`,
13934
- domain: ErrorDomain.STORAGE,
13935
- category: ErrorCategory.THIRD_PARTY,
13936
- details: {
13937
- id,
13938
- isBuffering,
13939
- lastBufferedAtTokens: lastBufferedAtTokens ?? null
13940
- }
14091
+ target.id
14092
+ ]);
13941
14093
  });
13942
14094
  } catch (error) {
13943
14095
  if (error instanceof MastraError) throw error;
@@ -14010,29 +14162,27 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
14010
14162
  }
14011
14163
  async setPendingMessageTokens(id, tokenCount) {
14012
14164
  try {
14013
- const tableName = getTableName$3({
14014
- indexName: OM_TABLE,
14015
- schemaName: getSchemaName$3(this.#schema)
14016
- });
14165
+ const tableName = this.#omTableName();
14017
14166
  const nowStr = (/* @__PURE__ */ new Date()).toISOString();
14018
- if ((await this.#db.client.query(`UPDATE ${tableName} SET
14019
- "pendingMessageTokens" = $1,
14020
- "updatedAt" = $2,
14021
- "updatedAtZ" = $3
14022
- WHERE id = $4`, [
14023
- Math.round(tokenCount),
14024
- nowStr,
14025
- nowStr,
14026
- id
14027
- ])).rowCount === 0) throw new MastraError({
14028
- id: createStorageErrorId("PG", "SET_PENDING_MESSAGE_TOKENS", "NOT_FOUND"),
14029
- text: `Observational memory record not found: ${id}`,
14030
- domain: ErrorDomain.STORAGE,
14031
- category: ErrorCategory.THIRD_PARTY,
14032
- details: {
14033
- id,
14034
- tokenCount
14035
- }
14167
+ await this.#db.client.tx(async (t) => {
14168
+ const row = await this.#lockOMRow(t, tableName, id);
14169
+ if (!row) throw new MastraError({
14170
+ id: createStorageErrorId("PG", "SET_PENDING_MESSAGE_TOKENS", "NOT_FOUND"),
14171
+ text: `Observational memory record not found: ${id}`,
14172
+ domain: ErrorDomain.STORAGE,
14173
+ category: ErrorCategory.THIRD_PARTY,
14174
+ details: {
14175
+ id,
14176
+ tokenCount
14177
+ }
14178
+ });
14179
+ const target = await this.#resolveLiveOMRow(t, tableName, row);
14180
+ await t.none(`UPDATE ${tableName} SET "pendingMessageTokens" = $1, "updatedAt" = $2, "updatedAtZ" = $3 WHERE id = $4`, [
14181
+ Math.round(tokenCount),
14182
+ nowStr,
14183
+ nowStr,
14184
+ target.id
14185
+ ]);
14036
14186
  });
14037
14187
  } catch (error) {
14038
14188
  if (error instanceof MastraError) throw error;
@@ -14082,11 +14232,11 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
14082
14232
  }
14083
14233
  }
14084
14234
  async updateBufferedObservations(input) {
14235
+ await this.appendBufferedObservations(input);
14236
+ }
14237
+ async appendBufferedObservations(input) {
14085
14238
  try {
14086
- const tableName = getTableName$3({
14087
- indexName: OM_TABLE,
14088
- schemaName: getSchemaName$3(this.#schema)
14089
- });
14239
+ const tableName = this.#omTableName();
14090
14240
  const nowStr = (/* @__PURE__ */ new Date()).toISOString();
14091
14241
  const newChunk = {
14092
14242
  id: `ombuf-${globalThis.crypto.randomUUID()}`,
@@ -14103,32 +14253,42 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
14103
14253
  extractedValues: input.chunk.extractedValues,
14104
14254
  extractionFailures: input.chunk.extractionFailures
14105
14255
  };
14106
- const lastBufferedAtTime = input.lastBufferedAtTime ? input.lastBufferedAtTime.toISOString() : null;
14107
- if ((await this.#fenced(void 0, "updateBufferedObservations", (q) => q.query(`UPDATE ${tableName} SET
14108
- "bufferedObservationChunks" = CASE
14109
- WHEN EXISTS (
14110
- SELECT 1
14111
- FROM jsonb_array_elements(COALESCE("bufferedObservationChunks", '[]'::jsonb)) AS chunk
14112
- WHERE chunk->>'cycleId' = $2
14113
- ) THEN COALESCE("bufferedObservationChunks", '[]'::jsonb)
14114
- ELSE COALESCE("bufferedObservationChunks", '[]'::jsonb) || $1::jsonb
14115
- END,
14116
- "lastBufferedAtTime" = COALESCE($3, "lastBufferedAtTime"),
14117
- "updatedAt" = $4,
14118
- "updatedAtZ" = $5
14119
- WHERE id = $6`, [
14120
- toPgJson([newChunk]),
14121
- input.chunk.cycleId,
14122
- lastBufferedAtTime,
14123
- nowStr,
14124
- nowStr,
14125
- input.id
14126
- ]))).rowCount === 0) throw new MastraError({
14127
- id: createStorageErrorId("PG", "UPDATE_BUFFERED_OBSERVATIONS", "NOT_FOUND"),
14128
- text: `Observational memory record not found: ${input.id}`,
14129
- domain: ErrorDomain.STORAGE,
14130
- category: ErrorCategory.THIRD_PARTY,
14131
- details: { id: input.id }
14256
+ const fence = resolveRunFence(this, void 0);
14257
+ return await this.#db.client.tx(async (t) => {
14258
+ if (fence) await assertRunFence(t, this.#runFencesTable(), fence, "appendBufferedObservations");
14259
+ const row = await this.#lockOMRow(t, tableName, input.id);
14260
+ if (!row) throw new MastraError({
14261
+ id: createStorageErrorId("PG", "UPDATE_BUFFERED_OBSERVATIONS", "NOT_FOUND"),
14262
+ text: `Observational memory record not found: ${input.id}`,
14263
+ domain: ErrorDomain.STORAGE,
14264
+ category: ErrorCategory.THIRD_PARTY,
14265
+ details: { id: input.id }
14266
+ });
14267
+ const target = this.parseOMRow(await this.#resolveLiveOMRow(t, tableName, row));
14268
+ if ((Array.isArray(target.bufferedObservationChunks) ? target.bufferedObservationChunks : []).some((existing) => existing.cycleId === input.chunk.cycleId) || isBufferedChunkCoveredByCursor(input.chunk.lastObservedAt, target.lastObservedAt)) return {
14269
+ persisted: false,
14270
+ recordId: target.id
14271
+ };
14272
+ const lastBufferedAtTime = input.lastBufferedAtTime ? input.lastBufferedAtTime.toISOString() : null;
14273
+ await t.none(`UPDATE ${tableName} SET
14274
+ "bufferedObservationChunks" = CASE
14275
+ WHEN jsonb_typeof("bufferedObservationChunks") = 'array' THEN "bufferedObservationChunks" || $1::jsonb
14276
+ ELSE $1::jsonb
14277
+ END,
14278
+ "lastBufferedAtTime" = GREATEST("lastBufferedAtTime", $2::timestamp),
14279
+ "updatedAt" = $3,
14280
+ "updatedAtZ" = $4
14281
+ WHERE id = $5`, [
14282
+ toPgJson([newChunk]),
14283
+ lastBufferedAtTime,
14284
+ nowStr,
14285
+ nowStr,
14286
+ target.id
14287
+ ]);
14288
+ return {
14289
+ persisted: true,
14290
+ recordId: target.id
14291
+ };
14132
14292
  });
14133
14293
  } catch (error) {
14134
14294
  if (error instanceof MastraError || isRunFenceConflictError(error)) throw error;
@@ -14147,73 +14307,86 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
14147
14307
  schemaName: getSchemaName$3(this.#schema)
14148
14308
  });
14149
14309
  const nowStr = (/* @__PURE__ */ new Date()).toISOString();
14150
- const record = await this.#db.client.oneOrNone(`SELECT * FROM ${tableName} WHERE id = $1`, [input.id]);
14151
- if (!record) throw new MastraError({
14152
- id: createStorageErrorId("PG", "SWAP_BUFFERED_TO_ACTIVE", "NOT_FOUND"),
14153
- text: `Observational memory record not found: ${input.id}`,
14154
- domain: ErrorDomain.STORAGE,
14155
- category: ErrorCategory.THIRD_PARTY,
14156
- details: { id: input.id }
14157
- });
14158
- let chunks = [];
14159
- if (record.bufferedObservationChunks) try {
14160
- const parsed = typeof record.bufferedObservationChunks === "string" ? JSON.parse(record.bufferedObservationChunks) : record.bufferedObservationChunks;
14161
- chunks = Array.isArray(parsed) ? parsed : [];
14162
- } catch {
14163
- chunks = [];
14164
- }
14165
- if (chunks.length === 0) return {
14166
- chunksActivated: 0,
14167
- messageTokensActivated: 0,
14168
- observationTokensActivated: 0,
14169
- messagesActivated: 0,
14170
- activatedCycleIds: [],
14171
- activatedMessageIds: []
14172
- };
14173
- const retentionFloor = input.messageTokensThreshold * (1 - input.activationRatio);
14174
- const targetMessageTokens = Math.max(0, input.currentPendingTokens - retentionFloor);
14175
- let cumulativeMessageTokens = 0;
14176
- let chunksToActivate = 0;
14177
- let bestOverBoundary = 0;
14178
- let bestOverTokens = 0;
14179
- let bestUnderBoundary = 0;
14180
- let bestUnderTokens = 0;
14181
- for (let i = 0; i < chunks.length; i++) {
14182
- cumulativeMessageTokens += chunks[i].messageTokens ?? 0;
14183
- const boundary = i + 1;
14184
- if (cumulativeMessageTokens >= targetMessageTokens) {
14185
- if (bestOverBoundary === 0 || cumulativeMessageTokens < bestOverTokens) {
14186
- bestOverBoundary = boundary;
14187
- bestOverTokens = cumulativeMessageTokens;
14310
+ const fence = resolveRunFence(this, void 0);
14311
+ return await this.#db.client.tx(async (t) => {
14312
+ if (fence) await assertRunFence(t, this.#runFencesTable(), fence, "swapBufferedToActive");
14313
+ const record = await this.#lockOMRow(t, tableName, input.id);
14314
+ if (!record) throw new MastraError({
14315
+ id: createStorageErrorId("PG", "SWAP_BUFFERED_TO_ACTIVE", "NOT_FOUND"),
14316
+ text: `Observational memory record not found: ${input.id}`,
14317
+ domain: ErrorDomain.STORAGE,
14318
+ category: ErrorCategory.THIRD_PARTY,
14319
+ details: { id: input.id }
14320
+ });
14321
+ const emptyResult = {
14322
+ chunksActivated: 0,
14323
+ messageTokensActivated: 0,
14324
+ observationTokensActivated: 0,
14325
+ messagesActivated: 0,
14326
+ activatedCycleIds: [],
14327
+ activatedMessageIds: []
14328
+ };
14329
+ if (record.supersededBy) return {
14330
+ ...emptyResult,
14331
+ retired: true
14332
+ };
14333
+ let persistedChunks = [];
14334
+ if (record.bufferedObservationChunks) try {
14335
+ const parsed = typeof record.bufferedObservationChunks === "string" ? JSON.parse(record.bufferedObservationChunks) : record.bufferedObservationChunks;
14336
+ persistedChunks = Array.isArray(parsed) ? parsed : [];
14337
+ } catch {
14338
+ persistedChunks = [];
14339
+ }
14340
+ const refreshedWeights = new Map((Array.isArray(input.bufferedChunks) ? input.bufferedChunks : []).map((c) => [c.id, c.messageTokens]));
14341
+ const chunks = persistedChunks.map((c) => refreshedWeights.has(c.id) ? {
14342
+ ...c,
14343
+ messageTokens: refreshedWeights.get(c.id)
14344
+ } : c);
14345
+ if (chunks.length === 0) return emptyResult;
14346
+ const retentionFloor = input.messageTokensThreshold * (1 - input.activationRatio);
14347
+ const targetMessageTokens = Math.max(0, input.currentPendingTokens - retentionFloor);
14348
+ let cumulativeMessageTokens = 0;
14349
+ let chunksToActivate = 0;
14350
+ let bestOverBoundary = 0;
14351
+ let bestOverTokens = 0;
14352
+ let bestUnderBoundary = 0;
14353
+ let bestUnderTokens = 0;
14354
+ for (let i = 0; i < chunks.length; i++) {
14355
+ cumulativeMessageTokens += chunks[i].messageTokens ?? 0;
14356
+ const boundary = i + 1;
14357
+ if (cumulativeMessageTokens >= targetMessageTokens) {
14358
+ if (bestOverBoundary === 0 || cumulativeMessageTokens < bestOverTokens) {
14359
+ bestOverBoundary = boundary;
14360
+ bestOverTokens = cumulativeMessageTokens;
14361
+ }
14362
+ } else if (cumulativeMessageTokens > bestUnderTokens) {
14363
+ bestUnderBoundary = boundary;
14364
+ bestUnderTokens = cumulativeMessageTokens;
14188
14365
  }
14189
- } else if (cumulativeMessageTokens > bestUnderTokens) {
14190
- bestUnderBoundary = boundary;
14191
- bestUnderTokens = cumulativeMessageTokens;
14192
14366
  }
14193
- }
14194
- const maxOvershoot = retentionFloor * .95;
14195
- const overshoot = bestOverTokens - targetMessageTokens;
14196
- const remainingAfterOver = input.currentPendingTokens - bestOverTokens;
14197
- const remainingAfterUnder = input.currentPendingTokens - bestUnderTokens;
14198
- const minRemaining = Math.min(1e3, retentionFloor);
14199
- if (input.forceMaxActivation && bestOverBoundary > 0 && remainingAfterOver >= minRemaining) chunksToActivate = bestOverBoundary;
14200
- else if (bestOverBoundary > 0 && overshoot <= maxOvershoot && remainingAfterOver >= minRemaining) chunksToActivate = bestOverBoundary;
14201
- else if (bestUnderBoundary > 0 && remainingAfterUnder >= minRemaining) chunksToActivate = bestUnderBoundary;
14202
- else if (bestOverBoundary > 0) chunksToActivate = bestOverBoundary;
14203
- else chunksToActivate = 1;
14204
- const activatedChunks = chunks.slice(0, chunksToActivate);
14205
- const remainingChunks = chunks.slice(chunksToActivate);
14206
- const activatedContent = activatedChunks.map((c) => c.observations).join("\n\n");
14207
- const activatedTokens = Math.round(activatedChunks.reduce((sum, c) => sum + c.tokenCount, 0));
14208
- const activatedMessageTokens = Math.round(activatedChunks.reduce((sum, c) => sum + (c.messageTokens ?? 0), 0));
14209
- const activatedMessageCount = activatedChunks.reduce((sum, c) => sum + c.messageIds.length, 0);
14210
- const activatedCycleIds = activatedChunks.map((c) => c.cycleId).filter((id) => !!id);
14211
- const activatedMessageIds = activatedChunks.flatMap((c) => c.messageIds ?? []);
14212
- const latestChunk = activatedChunks[activatedChunks.length - 1];
14213
- const lastObservedAt = input.lastObservedAt ?? (latestChunk?.lastObservedAt ? new Date(latestChunk.lastObservedAt) : /* @__PURE__ */ new Date());
14214
- const lastObservedAtStr = lastObservedAt.toISOString();
14215
- const boundary = `\n\n--- message boundary (${lastObservedAt.toISOString()}) ---\n\n`;
14216
- if ((await this.#fenced(void 0, "swapBufferedToActive", (q) => q.query(`UPDATE ${tableName} SET
14367
+ const maxOvershoot = retentionFloor * .95;
14368
+ const overshoot = bestOverTokens - targetMessageTokens;
14369
+ const remainingAfterOver = input.currentPendingTokens - bestOverTokens;
14370
+ const remainingAfterUnder = input.currentPendingTokens - bestUnderTokens;
14371
+ const minRemaining = Math.min(1e3, retentionFloor);
14372
+ if (input.forceMaxActivation && bestOverBoundary > 0 && remainingAfterOver >= minRemaining) chunksToActivate = bestOverBoundary;
14373
+ else if (bestOverBoundary > 0 && overshoot <= maxOvershoot && remainingAfterOver >= minRemaining) chunksToActivate = bestOverBoundary;
14374
+ else if (bestUnderBoundary > 0 && remainingAfterUnder >= minRemaining) chunksToActivate = bestUnderBoundary;
14375
+ else if (bestOverBoundary > 0) chunksToActivate = bestOverBoundary;
14376
+ else chunksToActivate = 1;
14377
+ const activatedChunks = chunks.slice(0, chunksToActivate);
14378
+ const remainingChunks = persistedChunks.slice(chunksToActivate);
14379
+ const activatedContent = activatedChunks.map((c) => c.observations).join("\n\n");
14380
+ const activatedTokens = Math.round(activatedChunks.reduce((sum, c) => sum + c.tokenCount, 0));
14381
+ const activatedMessageTokens = Math.round(activatedChunks.reduce((sum, c) => sum + (c.messageTokens ?? 0), 0));
14382
+ const activatedMessageCount = activatedChunks.reduce((sum, c) => sum + c.messageIds.length, 0);
14383
+ const activatedCycleIds = activatedChunks.map((c) => c.cycleId).filter((id) => !!id);
14384
+ const activatedMessageIds = activatedChunks.flatMap((c) => c.messageIds ?? []);
14385
+ const latestChunk = activatedChunks[activatedChunks.length - 1];
14386
+ const lastObservedAt = input.lastObservedAt ?? (latestChunk?.lastObservedAt ? new Date(latestChunk.lastObservedAt) : /* @__PURE__ */ new Date());
14387
+ const lastObservedAtStr = maxObservationCursor(record.lastObservedAtZ, lastObservedAt).toISOString();
14388
+ const boundary = `\n\n--- message boundary (${lastObservedAt.toISOString()}) ---\n\n`;
14389
+ if ((await t.query(`UPDATE ${tableName} SET
14217
14390
  "activeObservations" = CASE
14218
14391
  WHEN "activeObservations" IS NOT NULL AND "activeObservations" != ''
14219
14392
  THEN "activeObservations" || $10 || $1
@@ -14227,45 +14400,40 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
14227
14400
  "updatedAt" = $7,
14228
14401
  "updatedAtZ" = $8
14229
14402
  WHERE id = $9
14403
+ AND "supersededBy" IS NULL
14230
14404
  AND "bufferedObservationChunks" IS NOT NULL
14231
14405
  AND "bufferedObservationChunks"::text != '[]'`, [
14232
- activatedContent,
14233
- activatedTokens,
14234
- activatedMessageTokens,
14235
- remainingChunks.length > 0 ? toPgJson(remainingChunks) : null,
14236
- lastObservedAtStr,
14237
- lastObservedAtStr,
14238
- nowStr,
14239
- nowStr,
14240
- input.id,
14241
- boundary
14242
- ]))).rowCount === 0) return {
14243
- chunksActivated: 0,
14244
- messageTokensActivated: 0,
14245
- observationTokensActivated: 0,
14246
- messagesActivated: 0,
14247
- activatedCycleIds: [],
14248
- activatedMessageIds: []
14249
- };
14250
- const latestChunkHints = activatedChunks[activatedChunks.length - 1];
14251
- return {
14252
- chunksActivated: activatedChunks.length,
14253
- messageTokensActivated: activatedMessageTokens,
14254
- observationTokensActivated: activatedTokens,
14255
- messagesActivated: activatedMessageCount,
14256
- activatedCycleIds,
14257
- activatedMessageIds,
14258
- observations: activatedContent,
14259
- perChunk: activatedChunks.map((c) => ({
14260
- cycleId: c.cycleId ?? "",
14261
- messageTokens: c.messageTokens ?? 0,
14262
- observationTokens: c.tokenCount,
14263
- messageCount: c.messageIds.length,
14264
- observations: c.observations
14265
- })),
14266
- suggestedContinuation: latestChunkHints?.suggestedContinuation ?? void 0,
14267
- currentTask: latestChunkHints?.currentTask ?? void 0
14268
- };
14406
+ activatedContent,
14407
+ activatedTokens,
14408
+ activatedMessageTokens,
14409
+ remainingChunks.length > 0 ? toPgJson(remainingChunks) : null,
14410
+ lastObservedAtStr,
14411
+ lastObservedAtStr,
14412
+ nowStr,
14413
+ nowStr,
14414
+ input.id,
14415
+ boundary
14416
+ ])).rowCount === 0) return emptyResult;
14417
+ const latestChunkHints = activatedChunks[activatedChunks.length - 1];
14418
+ return {
14419
+ chunksActivated: activatedChunks.length,
14420
+ messageTokensActivated: activatedMessageTokens,
14421
+ observationTokensActivated: activatedTokens,
14422
+ messagesActivated: activatedMessageCount,
14423
+ activatedCycleIds,
14424
+ activatedMessageIds,
14425
+ observations: activatedContent,
14426
+ perChunk: activatedChunks.map((c) => ({
14427
+ cycleId: c.cycleId ?? "",
14428
+ messageTokens: c.messageTokens ?? 0,
14429
+ observationTokens: c.tokenCount,
14430
+ messageCount: c.messageIds.length,
14431
+ observations: c.observations
14432
+ })),
14433
+ suggestedContinuation: latestChunkHints?.suggestedContinuation ?? void 0,
14434
+ currentTask: latestChunkHints?.currentTask ?? void 0
14435
+ };
14436
+ });
14269
14437
  } catch (error) {
14270
14438
  if (error instanceof MastraError || isRunFenceConflictError(error)) throw error;
14271
14439
  throw new MastraError({
@@ -14321,50 +14489,40 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
14321
14489
  }
14322
14490
  async swapBufferedReflectionToActive(input) {
14323
14491
  try {
14324
- const tableName = getTableName$3({
14325
- indexName: OM_TABLE,
14326
- schemaName: getSchemaName$3(this.#schema)
14327
- });
14328
- const lookupKey = this.getOMKey(input.currentRecord.threadId, input.currentRecord.resourceId);
14492
+ const { currentRecord } = input;
14493
+ const tableName = this.#omTableName();
14494
+ const lookupKey = this.getOMKey(currentRecord.threadId, currentRecord.resourceId);
14329
14495
  return await this.#withOMLookupKeyLock(tableName, lookupKey, async (t) => {
14330
- const newer = await this.#getNewerOMGeneration(t, tableName, lookupKey, input.currentRecord.generationCount);
14331
- if (newer) return newer;
14332
- const record = await t.oneOrNone(`SELECT * FROM ${tableName} WHERE id = $1`, [input.currentRecord.id]);
14333
- if (!record) throw new MastraError({
14334
- id: createStorageErrorId("PG", "SWAP_BUFFERED_REFLECTION_TO_ACTIVE", "NOT_FOUND"),
14335
- text: `Observational memory record not found: ${input.currentRecord.id}`,
14336
- domain: ErrorDomain.STORAGE,
14337
- category: ErrorCategory.THIRD_PARTY,
14338
- details: { id: input.currentRecord.id }
14339
- });
14340
- const bufferedReflection = record.bufferedReflection || "";
14341
- const reflectedLineCount = Number(record.reflectedObservationLineCount || 0);
14496
+ const row = await this.#lockOMRow(t, tableName, currentRecord.id);
14497
+ if (!row) return currentRecord;
14498
+ if (row.supersededBy) return this.parseOMRow(await this.#resolveLiveOMRow(t, tableName, row));
14499
+ const stored = this.parseOMRow(row);
14500
+ const bufferedReflection = stored.bufferedReflection || "";
14342
14501
  if (!bufferedReflection) throw new MastraError({
14343
14502
  id: createStorageErrorId("PG", "SWAP_BUFFERED_REFLECTION_TO_ACTIVE", "NO_CONTENT"),
14344
14503
  text: "No buffered reflection to swap",
14345
14504
  domain: ErrorDomain.STORAGE,
14346
14505
  category: ErrorCategory.USER,
14347
- details: { id: input.currentRecord.id }
14506
+ details: { id: currentRecord.id }
14348
14507
  });
14349
- const unreflectedContent = (record.activeObservations || "").split("\n").slice(reflectedLineCount).join("\n").trim();
14508
+ if (!isAppendOnlySince(stored.activeObservations, currentRecord.activeObservations)) return stored;
14509
+ const reflectedLineCount = stored.reflectedObservationLineCount ?? 0;
14510
+ const unreflectedContent = (stored.activeObservations || "").split("\n").slice(reflectedLineCount).join("\n").trim();
14350
14511
  const newObservations = unreflectedContent ? `${bufferedReflection}\n\n${unreflectedContent}` : bufferedReflection;
14351
- const newRecord = await this.#insertReflectionGeneration(t, tableName, lookupKey, {
14352
- currentRecord: input.currentRecord,
14353
- reflection: newObservations,
14354
- tokenCount: input.tokenCount
14355
- });
14512
+ const tokenCount = input.tokenCount + Math.max(0, (stored.observationTokenCount ?? 0) - (currentRecord.observationTokenCount ?? 0));
14513
+ const newRecord = await this.#rollOverOMRow(t, tableName, stored.id, newObservations, tokenCount, input.newRecordId);
14356
14514
  const nowStr = (/* @__PURE__ */ new Date()).toISOString();
14357
- await t.query(`UPDATE ${tableName} SET
14358
- "bufferedReflection" = NULL,
14359
- "bufferedReflectionTokens" = NULL,
14360
- "bufferedReflectionInputTokens" = NULL,
14361
- "reflectedObservationLineCount" = NULL,
14362
- "updatedAt" = $1,
14363
- "updatedAtZ" = $2
14364
- WHERE id = $3`, [
14515
+ await t.none(`UPDATE ${tableName} SET
14516
+ "bufferedReflection" = NULL,
14517
+ "bufferedReflectionTokens" = NULL,
14518
+ "bufferedReflectionInputTokens" = NULL,
14519
+ "reflectedObservationLineCount" = NULL,
14520
+ "updatedAt" = $1,
14521
+ "updatedAtZ" = $2
14522
+ WHERE id = $3`, [
14365
14523
  nowStr,
14366
14524
  nowStr,
14367
- input.currentRecord.id
14525
+ stored.id
14368
14526
  ]);
14369
14527
  return newRecord;
14370
14528
  }, "swapBufferedReflectionToActive");
@@ -24969,25 +25127,9 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
24969
25127
  schemaName: getSchemaName(this.#schema)
24970
25128
  });
24971
25129
  const existingSnapshotResult = await t.oneOrNone(`SELECT snapshot FROM ${tableName} WHERE workflow_name = $1 AND run_id = $2 FOR UPDATE`, [workflowName, runId]);
24972
- let snapshot;
24973
- if (!existingSnapshotResult) snapshot = {
24974
- context: {},
24975
- activePaths: [],
24976
- timestamp: Date.now(),
24977
- suspendedPaths: {},
24978
- activeStepsPath: {},
24979
- resumeLabels: {},
24980
- serializedStepGraph: [],
24981
- status: "pending",
24982
- value: {},
24983
- waitingPaths: {},
24984
- runId,
24985
- requestContext: {}
24986
- };
24987
- else {
24988
- const existingSnapshot = existingSnapshotResult.snapshot;
24989
- snapshot = typeof existingSnapshot === "string" ? JSON.parse(existingSnapshot) : existingSnapshot;
24990
- }
25130
+ if (!existingSnapshotResult) return {};
25131
+ const existingSnapshot = existingSnapshotResult.snapshot;
25132
+ const snapshot = typeof existingSnapshot === "string" ? JSON.parse(existingSnapshot) : existingSnapshot;
24991
25133
  mergeWorkflowStepResult({
24992
25134
  snapshot,
24993
25135
  stepId,