@mastra/memory 1.30.0-alpha.1 → 1.30.0-alpha.2

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.
@@ -30282,8 +30282,35 @@ const DEFAULT_TOP_K = 4;
30282
30282
  const VECTOR_DELETE_BATCH_SIZE = 100;
30283
30283
  const DEFAULT_EMBEDDING_CACHE_MAX_SIZE = 1e3;
30284
30284
  /**
30285
- * Concrete implementation of MastraMemory that adds support for thread configuration
30286
- * and message injection.
30285
+ * Gives Mastra agents conversation history, with optional working memory,
30286
+ * semantic recall, and observational memory.
30287
+ *
30288
+ * @remarks
30289
+ * Configure storage on this instance or its Mastra instance before use.
30290
+ * See the bundled docs for setup and conversation identifiers.
30291
+ *
30292
+ * @example
30293
+ * Attach memory to an agent; `yourModel` is your configured model.
30294
+ * ```typescript
30295
+ * import { Agent } from '@mastra/core/agent';
30296
+ * import { Memory } from '@mastra/memory';
30297
+ *
30298
+ * const agent = new Agent({
30299
+ * id: 'assistant',
30300
+ * name: 'Assistant',
30301
+ * instructions: 'You are a helpful assistant.',
30302
+ * model: yourModel,
30303
+ * memory: new Memory(),
30304
+ * });
30305
+ * ```
30306
+ *
30307
+ * @see For documentation bundled with your installed package, locate
30308
+ * `@mastra/memory/package.json` with your project's resolver or package-manager
30309
+ * tooling, then read `dist/docs/SKILL.md` from that package root and follow its
30310
+ * reference links. Use package-manager tools for virtual or archived packages.
30311
+ *
30312
+ * @see [Memory documentation](https://mastra.ai/docs/memory/overview)
30313
+ * if packaged docs are unavailable.
30287
30314
  */
30288
30315
  var Memory = class Memory extends _mastra_core_memory.MastraMemory {
30289
30316
  _omEngine;
@@ -31882,18 +31909,40 @@ Notes:
31882
31909
  * ```
31883
31910
  */
31884
31911
  async cloneThread(args, memoryConfig) {
31885
- const memoryStore = await this.getMemoryStore();
31886
- const config = this.getMergedThreadConfig(memoryConfig);
31887
- const requestedHydrate = args.options?.hydrateMessages ?? true;
31888
- const effectiveHydrate = requestedHydrate || Boolean(this.vector && this.embedder && config.semanticRecall);
31889
- const result = await memoryStore.cloneThread(effectiveHydrate === requestedHydrate ? args : {
31890
- ...args,
31891
- options: {
31892
- ...args.options,
31893
- hydrateMessages: effectiveHydrate
31912
+ const result = await this.copyThread(args, memoryConfig);
31913
+ const { messages } = await (await this.getMemoryStore()).listMessages({
31914
+ threadId: result.thread.id,
31915
+ resourceId: result.thread.resourceId,
31916
+ perPage: false,
31917
+ orderBy: {
31918
+ field: "createdAt",
31919
+ direction: "ASC"
31894
31920
  }
31895
31921
  });
31922
+ return {
31923
+ ...result,
31924
+ clonedMessages: messages
31925
+ };
31926
+ }
31927
+ /**
31928
+ * Copies a thread with all its messages to a new thread without returning the
31929
+ * message payloads. Working memory, observational memory, and semantic-recall
31930
+ * embeddings are carried over exactly as with `cloneThread`; the only difference
31931
+ * is that message content never has to be held in the Node heap at once.
31932
+ *
31933
+ * Use this instead of `cloneThread` when only the new thread id is needed
31934
+ * (e.g. forking a conversation for a subagent).
31935
+ *
31936
+ * @param args - Clone parameters, same as `cloneThread`
31937
+ * @param memoryConfig - Optional memory configuration override
31938
+ * @returns The newly created thread and the source→new message id map
31939
+ */
31940
+ async copyThread(args, memoryConfig) {
31941
+ const memoryStore = await this.getMemoryStore();
31942
+ const config = this.getMergedThreadConfig(memoryConfig);
31943
+ const result = await memoryStore.copyThread(args);
31896
31944
  const sourceResourceId = (await this.getThreadById({ threadId: args.sourceThreadId }))?.resourceId;
31945
+ let priorDestinationResourceWm;
31897
31946
  if (config.workingMemory?.enabled) {
31898
31947
  const scope = config.workingMemory.scope || "resource";
31899
31948
  if (scope === "thread" || scope === "resource" && args.resourceId && args.resourceId !== sourceResourceId) {
@@ -31902,28 +31951,125 @@ Notes:
31902
31951
  resourceId: sourceResourceId,
31903
31952
  memoryConfig
31904
31953
  });
31905
- if (sourceWm) await this.updateWorkingMemory({
31906
- threadId: result.thread.id,
31907
- resourceId: result.thread.resourceId,
31908
- workingMemory: sourceWm,
31909
- memoryConfig
31910
- });
31954
+ if (sourceWm) {
31955
+ if (scope === "resource") priorDestinationResourceWm = (await memoryStore.getResourceById({ resourceId: result.thread.resourceId }))?.workingMemory ?? null;
31956
+ await this.updateWorkingMemory({
31957
+ threadId: result.thread.id,
31958
+ resourceId: result.thread.resourceId,
31959
+ workingMemory: sourceWm,
31960
+ memoryConfig
31961
+ });
31962
+ }
31911
31963
  }
31912
31964
  }
31913
31965
  if (memoryStore.supportsObservationalMemory && sourceResourceId) try {
31914
31966
  await this.cloneObservationalMemory(memoryStore, args.sourceThreadId, sourceResourceId, result);
31915
31967
  } catch (error) {
31916
- try {
31917
- await memoryStore.deleteThread({ threadId: result.thread.id });
31918
- } catch (rollbackError) {
31919
- this.logger.error("Failed to rollback cloned thread after OM clone failure", rollbackError);
31920
- }
31968
+ await this.rollbackCopiedThread(memoryStore, result.thread, "OM clone", false, priorDestinationResourceWm);
31969
+ throw error;
31970
+ }
31971
+ if (this.vector && this.embedder && config.semanticRecall) try {
31972
+ await this.embedCopiedMessagesInBatches(memoryStore, result, config);
31973
+ } catch (error) {
31974
+ await this.rollbackCopiedThread(memoryStore, result.thread, "embedding", true, priorDestinationResourceWm);
31921
31975
  throw error;
31922
31976
  }
31923
- if (this.vector && config.semanticRecall && result.clonedMessages.length > 0) await this.embedClonedMessages(result.clonedMessages, config);
31924
31977
  return result;
31925
31978
  }
31926
31979
  /**
31980
+ * Best-effort compensation when a later step of copyThread fails after the destination thread
31981
+ * was persisted. deleteThread removes the thread, its messages and thread-scoped working memory.
31982
+ * `priorDestinationResourceWm` is the destination resource's working memory before the copy
31983
+ * overwrote it (`null` = none existed); when defined it is restored. When `afterOmClone` is set the OM step already succeeded, so the thread-scoped OM record and
31984
+ * any vectors written so far are dropped too. Resource-scoped OM is left alone: it may be
31985
+ * shared with a pre-existing resource and isn't safe to clear blindly.
31986
+ */
31987
+ async rollbackCopiedThread(memoryStore, thread, failedStep, afterOmClone, priorDestinationResourceWm) {
31988
+ const threadId = thread.id;
31989
+ try {
31990
+ await memoryStore.deleteThread({ threadId });
31991
+ } catch (rollbackError) {
31992
+ this.logger.error(`Failed to rollback copied thread after ${failedStep} failure`, rollbackError);
31993
+ }
31994
+ if (priorDestinationResourceWm !== void 0) try {
31995
+ await memoryStore.updateResource({
31996
+ resourceId: thread.resourceId,
31997
+ workingMemory: priorDestinationResourceWm ?? ""
31998
+ });
31999
+ } catch (rollbackError) {
32000
+ this.logger.error(`Failed to restore resource working memory after ${failedStep} failure`, rollbackError);
32001
+ }
32002
+ if (!afterOmClone) return;
32003
+ if (memoryStore.supportsObservationalMemory) try {
32004
+ await memoryStore.clearObservationalMemory(threadId, thread.resourceId);
32005
+ } catch (rollbackError) {
32006
+ this.logger.error(`Failed to rollback copied thread OM after ${failedStep} failure`, rollbackError);
32007
+ }
32008
+ try {
32009
+ const messageIndexes = await this.getMemoryVectorIndexes([this.messageIndexPrefix]);
32010
+ await Promise.all(messageIndexes.map((indexName) => this.vector.deleteVectors({
32011
+ indexName,
32012
+ filter: { thread_id: threadId }
32013
+ })));
32014
+ } catch (rollbackError) {
32015
+ this.logger.error(`Failed to rollback copied thread vectors after ${failedStep} failure`, rollbackError);
32016
+ }
32017
+ }
32018
+ static CLONE_EMBED_PAGE_SIZE = 100;
32019
+ async embedCopiedMessagesInBatches(memoryStore, copied, config) {
32020
+ const size = Memory.CLONE_EMBED_PAGE_SIZE;
32021
+ const copiedIds = Object.values(copied.messageIdMap ?? {});
32022
+ if (copiedIds.length > 0) {
32023
+ for (let i = 0; i < copiedIds.length; i += size) {
32024
+ const { messages } = await memoryStore.listMessagesById({ messageIds: copiedIds.slice(i, i + size) });
32025
+ if (messages.length > 0) await this.embedClonedMessages(messages, config);
32026
+ }
32027
+ return;
32028
+ }
32029
+ const { messages } = await memoryStore.listMessages({
32030
+ threadId: copied.thread.id,
32031
+ resourceId: copied.thread.resourceId,
32032
+ perPage: false,
32033
+ includeTotal: false
32034
+ });
32035
+ for (let i = 0; i < messages.length; i += size) await this.embedClonedMessages(messages.slice(i, i + size), config);
32036
+ }
32037
+ async updateThreadResourceId({ threadId, resourceId, memoryConfig }) {
32038
+ const memoryStore = await this.getMemoryStore();
32039
+ const config = this.getMergedThreadConfig(memoryConfig);
32040
+ const migratesVectors = Boolean(this.vector && this.embedder && config.semanticRecall);
32041
+ if (!migratesVectors) {
32042
+ const existing = await memoryStore.getThreadById({ threadId });
32043
+ if (existing && existing.resourceId === resourceId) return existing;
32044
+ }
32045
+ const thread = await memoryStore.updateThreadResourceId({
32046
+ threadId,
32047
+ resourceId
32048
+ });
32049
+ if (migratesVectors) try {
32050
+ const { messages } = await memoryStore.listMessages({
32051
+ threadId,
32052
+ perPage: false
32053
+ });
32054
+ const messageIndexes = await this.getMemoryVectorIndexes([this.messageIndexPrefix]);
32055
+ await Promise.all(messageIndexes.map(async (indexName) => {
32056
+ await this.vector.deleteVectors({
32057
+ indexName,
32058
+ filter: { thread_id: threadId }
32059
+ });
32060
+ }));
32061
+ if (messages.length > 0) await this.embedClonedMessages(messages, config);
32062
+ } catch (error) {
32063
+ this.logger.error("Failed to migrate semantic-recall vectors during thread transfer", {
32064
+ threadId,
32065
+ resourceId,
32066
+ error: error instanceof Error ? error.message : String(error)
32067
+ });
32068
+ throw new Error(`Thread "${threadId}" was transferred to resource "${resourceId}", but migrating its semantic-recall vectors failed. The thread's messages may not surface under resource-scoped recall until the vectors are re-indexed. Cause: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
32069
+ }
32070
+ return thread;
32071
+ }
32072
+ /**
31927
32073
  * Clone observational memory records when cloning a thread.
31928
32074
  * Thread-scoped: always cloned to the new thread.
31929
32075
  * Resource-scoped: cloned only when the resourceId changes (same resourceId shares OM naturally).
@@ -32507,4 +32653,4 @@ Object.defineProperty(exports, "wrapInObservationGroup", {
32507
32653
  }
32508
32654
  });
32509
32655
 
32510
- //# sourceMappingURL=src-Bze5SVTm.cjs.map
32656
+ //# sourceMappingURL=src-Bs9_1MCb.cjs.map