@mastra/memory 1.26.0 → 1.26.1-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.
Files changed (37) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/dist/docs/SKILL.md +1 -1
  3. package/dist/docs/assets/SOURCE_MAP.json +1 -1
  4. package/dist/docs/references/docs-capabilities-subagents.md +23 -5
  5. package/dist/index.cjs +1 -1
  6. package/dist/index.d.ts +25 -2
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +1 -1
  9. package/dist/processors/index.cjs +1 -1
  10. package/dist/processors/index.js +1 -1
  11. package/dist/processors/observational-memory/extraction-runner.d.ts.map +1 -1
  12. package/dist/processors/observational-memory/extractor.d.ts +3 -0
  13. package/dist/processors/observational-memory/extractor.d.ts.map +1 -1
  14. package/dist/processors/observational-memory/message-utils.d.ts +13 -0
  15. package/dist/processors/observational-memory/message-utils.d.ts.map +1 -1
  16. package/dist/processors/observational-memory/observation-strategies/base.d.ts +5 -1
  17. package/dist/processors/observational-memory/observation-strategies/base.d.ts.map +1 -1
  18. package/dist/processors/observational-memory/observation-strategies/types.d.ts +7 -1
  19. package/dist/processors/observational-memory/observation-strategies/types.d.ts.map +1 -1
  20. package/dist/processors/observational-memory/observation-turn/step.d.ts +6 -0
  21. package/dist/processors/observational-memory/observation-turn/step.d.ts.map +1 -1
  22. package/dist/processors/observational-memory/observation-turn/turn.d.ts +2 -0
  23. package/dist/processors/observational-memory/observation-turn/turn.d.ts.map +1 -1
  24. package/dist/processors/observational-memory/observational-memory.d.ts +6 -0
  25. package/dist/processors/observational-memory/observational-memory.d.ts.map +1 -1
  26. package/dist/processors/observational-memory/processor.d.ts.map +1 -1
  27. package/dist/processors/observational-memory/reflector-runner.d.ts.map +1 -1
  28. package/dist/processors/observational-memory/temporal-markers.d.ts.map +1 -1
  29. package/dist/processors/observational-memory/token-counter.d.ts.map +1 -1
  30. package/dist/processors/observational-memory/working-memory-extractor.d.ts.map +1 -1
  31. package/dist/{src-Cwt9gefz.js → src-CdqJP57F.js} +214 -73
  32. package/dist/{src-Cwt9gefz.js.map → src-CdqJP57F.js.map} +1 -1
  33. package/dist/{src-CjTEWCUF.cjs → src-DY9-_lul.cjs} +214 -73
  34. package/dist/{src-CjTEWCUF.cjs.map → src-DY9-_lul.cjs.map} +1 -1
  35. package/dist/tools/om-tools.d.ts +2 -0
  36. package/dist/tools/om-tools.d.ts.map +1 -1
  37. package/package.json +5 -5
@@ -14856,6 +14856,7 @@ var Extractor = class Extractor {
14856
14856
  includePreviousExtraction;
14857
14857
  metadataKeyPath;
14858
14858
  onExtracted;
14859
+ retryStructuredExtractionOnEmptyObject;
14859
14860
  /** @internal */
14860
14861
  internal;
14861
14862
  instructionsConfig;
@@ -14878,6 +14879,7 @@ var Extractor = class Extractor {
14878
14879
  this.includePreviousExtraction = config.includePreviousExtraction ?? true;
14879
14880
  this.metadataKeyPath = config.metadataKeyPath ?? `extracted.${slug}`;
14880
14881
  this.onExtracted = config.onExtracted;
14882
+ this.retryStructuredExtractionOnEmptyObject = config.retryStructuredExtractionOnEmptyObject ?? false;
14881
14883
  this.internal = internal;
14882
14884
  }
14883
14885
  async resolve(context) {
@@ -14890,7 +14892,8 @@ var Extractor = class Extractor {
14890
14892
  ...schema ? { schema } : {},
14891
14893
  includePreviousExtraction: this.includePreviousExtraction,
14892
14894
  metadataKeyPath: this.metadataKeyPath,
14893
- onExtracted: this.onExtracted
14895
+ onExtracted: this.onExtracted,
14896
+ retryStructuredExtractionOnEmptyObject: this.retryStructuredExtractionOnEmptyObject
14894
14897
  }, this.internal);
14895
14898
  }
14896
14899
  };
@@ -15215,6 +15218,9 @@ if (OM_DEBUG_LOG) {
15215
15218
  function isAbortError$1(error, abortSignal) {
15216
15219
  return abortSignal?.aborted === true || error instanceof DOMException && error.name === "AbortError" || error instanceof Error && error.name === "AbortError";
15217
15220
  }
15221
+ function shouldRetryEmptyStructuredObject(object, extractors) {
15222
+ return Object.keys(object).length === 0 && extractors.some((extractor) => extractor.retryStructuredExtractionOnEmptyObject);
15223
+ }
15218
15224
  async function extractStructuredValues(opts) {
15219
15225
  const structuredExtractors = (opts.extractors ?? []).filter((extractor) => extractor.mode === "structured");
15220
15226
  if (structuredExtractors.length === 0) return {
@@ -15251,8 +15257,10 @@ ${extractorInstructions}${priorLines.length > 0 ? `\n\n## Prior Extracted Values
15251
15257
  return output.object;
15252
15258
  };
15253
15259
  let object;
15260
+ let retryEmptyObject = false;
15254
15261
  try {
15255
15262
  object = await generateWithStructuredOutput();
15263
+ retryEmptyObject = shouldRetryEmptyStructuredObject(object, structuredExtractors);
15256
15264
  } catch (error) {
15257
15265
  if (isAbortError$1(error, opts.abortSignal)) throw error;
15258
15266
  try {
@@ -15269,6 +15277,19 @@ ${extractorInstructions}${priorLines.length > 0 ? `\n\n## Prior Extracted Values
15269
15277
  };
15270
15278
  }
15271
15279
  }
15280
+ if (retryEmptyObject) try {
15281
+ object = await generateWithStructuredOutput(_mastra_core_features.coreFeatures.has("json-prompt-injection:inline") ? "inline" : true);
15282
+ } catch (fallbackError) {
15283
+ if (isAbortError$1(fallbackError, opts.abortSignal)) throw fallbackError;
15284
+ const message = fallbackError instanceof Error ? fallbackError.message : String(fallbackError);
15285
+ return {
15286
+ values,
15287
+ failures: structuredExtractors.map((extractor) => ({
15288
+ slug: extractor.slug,
15289
+ error: message
15290
+ }))
15291
+ };
15292
+ }
15272
15293
  for (const extractor of structuredExtractors) {
15273
15294
  const value = object[extractor.slug];
15274
15295
  if (value === void 0 || value === null || value === "") continue;
@@ -18574,6 +18595,17 @@ var TokenCounter = class TokenCounter {
18574
18595
  toolResultDelta
18575
18596
  };
18576
18597
  }
18598
+ if (invocation.state === "output-error") {
18599
+ toolResultDelta++;
18600
+ const errorText = invocation.errorText;
18601
+ const errorMessage = typeof errorText === "string" ? errorText : "Tool execution failed";
18602
+ tokens += this.readOrPersistPartEstimate(part, "tool-result-error", errorMessage);
18603
+ return {
18604
+ tokens,
18605
+ overheadDelta,
18606
+ toolResultDelta
18607
+ };
18608
+ }
18577
18609
  throw new Error(`Unhandled tool-invocation state '${part.toolInvocation?.state}' in token counting for part type '${part.type}'`);
18578
18610
  }
18579
18611
  if (typeof part.type === "string" && part.type.startsWith("data-")) return {
@@ -18799,6 +18831,7 @@ var WorkingMemoryExtractor = class extends Extractor {
18799
18831
  name: "Working Memory",
18800
18832
  includePreviousExtraction: false,
18801
18833
  metadataKeyPath: false,
18834
+ retryStructuredExtractionOnEmptyObject: true,
18802
18835
  instructions: async (context) => buildWorkingMemoryInstructions(await getWorkingMemoryDetails(context)),
18803
18836
  schema: async (context) => {
18804
18837
  return (await getWorkingMemoryDetails(context)).usesSchema ? zod.z.union([zod.z.record(zod.z.string(), zod.z.unknown()), zod.z.null()]) : void 0;
@@ -18947,9 +18980,10 @@ async function listThreadsForResource({ memory, resourceId, currentThreadId, pag
18947
18980
  hasMore
18948
18981
  };
18949
18982
  }
18983
+ const SEARCH_NOT_CONFIGURED_MESSAGE = "Search is not configured. Enable it with `retrieval: { vector: true }` and configure a vector store and embedder on your Memory instance.";
18950
18984
  async function searchMessagesForResource({ memory, resourceId, currentThreadId, query, topK = 10, maxTokens = DEFAULT_MAX_RESULT_TOKENS, before, after, threadScope }) {
18951
18985
  if (!memory.searchMessages) return {
18952
- results: "Search is not configured. Enable it with `retrieval: { vector: true }` and configure a vector store and embedder on your Memory instance.",
18986
+ results: SEARCH_NOT_CONFIGURED_MESSAGE,
18953
18987
  count: 0
18954
18988
  };
18955
18989
  const MAX_TOPK = 20;
@@ -19495,9 +19529,16 @@ async function recallThreadFromStart({ memory, threadId, resourceId, page = 1, l
19495
19529
  }
19496
19530
  const recallTool = (_memoryConfig, options) => {
19497
19531
  const isResourceScope = (options?.retrievalScope ?? "thread") === "resource";
19532
+ const searchEnabled = options?.searchEnabled ?? true;
19533
+ const description = isResourceScope ? `Browse conversation history. Use mode="threads" to list all threads for the current user. Use mode="messages" (default) to browse messages in the current thread or pass threadId to browse another thread in the active resource. When mode="messages" has no cursor or threadId, it defaults to the current thread and says so at the top of the result. If you pass only a cursor, it must belong to the current thread.${searchEnabled ? " Use mode=\"search\" to find messages by content across all threads." : ""}` : `Browse conversation history in the current thread. Use mode="messages" (default) to page through messages near a cursor.${searchEnabled ? " Use mode=\"search\" to find messages by content in this thread." : ""} Use mode="threads" to get the current thread's ID and title.`;
19534
+ const modeEnum = searchEnabled ? [
19535
+ "messages",
19536
+ "threads",
19537
+ "search"
19538
+ ] : ["messages", "threads"];
19498
19539
  return (0, _mastra_core_tools.createTool)({
19499
19540
  id: "recall",
19500
- description: isResourceScope ? "Browse conversation history. Use mode=\"threads\" to list all threads for the current user. Use mode=\"messages\" (default) to browse messages in the current thread or pass threadId to browse another thread in the active resource. When mode=\"messages\" has no cursor or threadId, it defaults to the current thread and says so at the top of the result. If you pass only a cursor, it must belong to the current thread. Use mode=\"search\" to find messages by content across all threads." : "Browse conversation history in the current thread. Use mode=\"messages\" (default) to page through messages near a cursor. Use mode=\"search\" to find messages by content in this thread. Use mode=\"threads\" to get the current thread's ID and title.",
19541
+ description,
19501
19542
  inputSchema: {
19502
19543
  $schema: "http://json-schema.org/draft-07/schema#",
19503
19544
  type: "object",
@@ -19505,12 +19546,8 @@ const recallTool = (_memoryConfig, options) => {
19505
19546
  ...isResourceScope ? {
19506
19547
  mode: {
19507
19548
  type: "string",
19508
- enum: [
19509
- "messages",
19510
- "threads",
19511
- "search"
19512
- ],
19513
- description: "What to retrieve. \"messages\" (default) pages through message history. \"threads\" lists all threads for the current user. \"search\" finds messages by semantic similarity across all threads."
19549
+ enum: modeEnum,
19550
+ description: `What to retrieve. "messages" (default) pages through message history. "threads" lists all threads for the current user.${searchEnabled ? " \"search\" finds messages by semantic similarity across all threads." : ""}`
19514
19551
  },
19515
19552
  threadId: {
19516
19553
  type: "string",
@@ -19527,18 +19564,14 @@ const recallTool = (_memoryConfig, options) => {
19527
19564
  }
19528
19565
  } : { mode: {
19529
19566
  type: "string",
19530
- enum: [
19531
- "messages",
19532
- "threads",
19533
- "search"
19534
- ],
19535
- description: "What to retrieve. \"messages\" (default) pages through message history. \"threads\" returns info about the current thread. \"search\" finds messages by semantic similarity in this thread."
19567
+ enum: modeEnum,
19568
+ description: `What to retrieve. "messages" (default) pages through message history. "threads" returns info about the current thread.${searchEnabled ? " \"search\" finds messages by semantic similarity in this thread." : ""}`
19536
19569
  } },
19537
- query: {
19570
+ ...searchEnabled ? { query: {
19538
19571
  type: "string",
19539
19572
  minLength: 1,
19540
19573
  description: "Search query for mode=\"search\". Finds messages semantically similar to this text."
19541
- },
19574
+ } } : {},
19542
19575
  cursor: {
19543
19576
  type: "string",
19544
19577
  minLength: 1,
@@ -19599,6 +19632,10 @@ const recallTool = (_memoryConfig, options) => {
19599
19632
  if (!memory) throw new Error("Memory instance is required for recall");
19600
19633
  if (explicitThreadId === "current" && !currentThreadId) throw new Error("Could not resolve current thread.");
19601
19634
  if (mode === "search") {
19635
+ if (!searchEnabled) return {
19636
+ results: SEARCH_NOT_CONFIGURED_MESSAGE,
19637
+ count: 0
19638
+ };
19602
19639
  if (!query) throw new Error("query is required for mode=\"search\"");
19603
19640
  if (!resourceId) throw new Error("Resource ID is required for recall");
19604
19641
  return searchMessagesForResource({
@@ -20560,6 +20597,24 @@ function getUnobservedPartsPreservingToolCallPairs(message) {
20560
20597
  return preservedCalls.length > 0 ? [...preservedCalls, ...unobservedParts] : unobservedParts;
20561
20598
  }
20562
20599
  /**
20600
+ * Get the messages Observational Memory is allowed to work with.
20601
+ *
20602
+ * Messages supplied through the `context` option are per-run ephemeral input. Core's
20603
+ * persistence contract already treats them as never-persist: `MessageStateManager` routes
20604
+ * them into `userContextMessages`, and `drainUnsavedMessages` only drains input/response.
20605
+ *
20606
+ * OM builds its windows from `get.all.db()`, which includes context messages, and then
20607
+ * seals and persists candidates directly — turning ephemeral context into durable user
20608
+ * messages. Excluding them here keeps OM's window, sealing, persistence and token
20609
+ * accounting consistent with that contract.
20610
+ */
20611
+ function getObservableMessages(messageList) {
20612
+ const allMessages = messageList.get.all.db();
20613
+ const contextMessageIds = messageList.makeMessageSourceChecker().context;
20614
+ if (contextMessageIds.size === 0) return allMessages;
20615
+ return allMessages.filter((message) => !contextMessageIds.has(message.id));
20616
+ }
20617
+ /**
20563
20618
  * Safely extract buffered observation chunks from a record.
20564
20619
  * Handles both array and JSON-string formats, returning empty array if malformed.
20565
20620
  */
@@ -20572,7 +20627,7 @@ function getUnobservedPartsPreservingToolCallPairs(message) {
20572
20627
  */
20573
20628
  function filterObservedMessages(opts) {
20574
20629
  const { messageList, record } = opts;
20575
- const allMessages = messageList.get.all.db();
20630
+ const allMessages = getObservableMessages(messageList);
20576
20631
  const useMarkerBoundaryPruning = opts.useMarkerBoundaryPruning ?? true;
20577
20632
  const preserveMessageIds = opts.preserveMessageIds ?? /* @__PURE__ */ new Set();
20578
20633
  const observedIds = new Set(Array.isArray(record?.observedMessageIds) ? record.observedMessageIds : []);
@@ -20990,7 +21045,7 @@ var ObservationStrategy = class ObservationStrategy {
20990
21045
  transient: true
20991
21046
  }).catch(() => {});
20992
21047
  const markerThreadId = marker.data?.threadId ?? this.opts.threadId;
20993
- await this.persistMarkerToStorage(marker, markerThreadId, this.opts.resourceId);
21048
+ if (!await this.persistMarkerToMessage(marker, this.opts.messageList, markerThreadId, this.opts.resourceId)) await this.persistMarkerToStorage(marker, markerThreadId, this.opts.resourceId);
20994
21049
  }
20995
21050
  getObservationMarkerConfig() {
20996
21051
  return {
@@ -21118,10 +21173,14 @@ var ObservationStrategy = class ObservationStrategy {
21118
21173
  /**
21119
21174
  * Persist a marker part on the last assistant message in a MessageList
21120
21175
  * AND save the updated message to the DB.
21176
+ *
21177
+ * @returns true when a marker was placed on an assistant message, false when
21178
+ * no list was provided or the list contains no assistant message (caller
21179
+ * should fall back to `persistMarkerToStorage`).
21121
21180
  */
21122
21181
  async persistMarkerToMessage(marker, messageList, threadId, resourceId) {
21123
- if (!messageList) return;
21124
- const allMsgs = messageList.get.all.db();
21182
+ if (!messageList) return false;
21183
+ const allMsgs = getObservableMessages(messageList);
21125
21184
  for (let i = allMsgs.length - 1; i >= 0; i--) {
21126
21185
  const msg = allMsgs[i];
21127
21186
  if (msg?.role === "assistant" && msg.content?.parts && Array.isArray(msg.content.parts)) {
@@ -21136,9 +21195,10 @@ var ObservationStrategy = class ObservationStrategy {
21136
21195
  } catch (e) {
21137
21196
  omDebug(`[OM:persistMarker] failed to save marker to DB: ${e}`);
21138
21197
  }
21139
- return;
21198
+ return true;
21140
21199
  }
21141
21200
  }
21201
+ return false;
21142
21202
  }
21143
21203
  };
21144
21204
  //#endregion
@@ -21947,6 +22007,12 @@ var ObservationStep = class {
21947
22007
  stepNumber;
21948
22008
  _prepared = false;
21949
22009
  _context;
22010
+ /**
22011
+ * True when this step seeded an empty assistant response message for a step-0
22012
+ * observation. While set, the response-id rotation hook must NOT run — rotating
22013
+ * would orphan the seed (markers would sit on a message the agent never streams into).
22014
+ */
22015
+ seededResponseMessage = false;
21950
22016
  constructor(turn, stepNumber) {
21951
22017
  this.turn = turn;
21952
22018
  this.stepNumber = stepNumber;
@@ -21993,7 +22059,7 @@ var ObservationStep = class {
21993
22059
  let didThresholdCleanup = false;
21994
22060
  let observerExchange;
21995
22061
  if (this.stepNumber === 0) {
21996
- const step0Messages = messageList.get.all.db();
22062
+ const step0Messages = getObservableMessages(messageList);
21997
22063
  const activation = await om.activate({
21998
22064
  threadId,
21999
22065
  resourceId,
@@ -22025,7 +22091,7 @@ var ObservationStep = class {
22025
22091
  currentModel: this.turn.actorModelContext,
22026
22092
  requestContext: this.turn.requestContext,
22027
22093
  observabilityContext: this.turn.observabilityContext,
22028
- lastActivityAt: getLastActivityFromMessages(messageList.get.all.db()),
22094
+ lastActivityAt: getLastActivityFromMessages(getObservableMessages(messageList)),
22029
22095
  reflectionHooks: om.composeHooks(void 0, {
22030
22096
  threadId,
22031
22097
  resourceId,
@@ -22035,7 +22101,7 @@ var ObservationStep = class {
22035
22101
  await this.turn.refreshRecord();
22036
22102
  if (this.turn.record.generationCount > preReflectGeneration) reflected = true;
22037
22103
  }
22038
- const allMsgsForToolCheck = messageList.get.all.db();
22104
+ const allMsgsForToolCheck = getObservableMessages(messageList);
22039
22105
  const lastMessage = allMsgsForToolCheck[allMsgsForToolCheck.length - 1];
22040
22106
  const pendingStepMessages = [...messageList.get.input.db(), ...messageList.get.response.db()];
22041
22107
  const latestStepParts = [...getLatestStepParts(lastMessage?.content?.parts ?? []), ...pendingStepMessages.flatMap((msg) => getLatestStepParts(msg.content?.parts ?? []))];
@@ -22044,10 +22110,10 @@ var ObservationStep = class {
22044
22110
  let statusSnapshot = await om.getStatus({
22045
22111
  threadId,
22046
22112
  resourceId,
22047
- messages: messageList.get.all.db()
22113
+ messages: getObservableMessages(messageList)
22048
22114
  });
22049
22115
  if (statusSnapshot.shouldBuffer && !hasIncompleteToolCalls) {
22050
- const allMessages = messageList.get.all.db();
22116
+ const allMessages = getObservableMessages(messageList);
22051
22117
  const unobservedMessages = om.getUnobservedMessages(allMessages, statusSnapshot.record);
22052
22118
  const candidates = om.getUnobservedMessages(unobservedMessages, statusSnapshot.record, { excludeBuffered: true });
22053
22119
  if (candidates.length > 0) {
@@ -22075,15 +22141,41 @@ var ObservationStep = class {
22075
22141
  });
22076
22142
  buffered = true;
22077
22143
  }
22078
- if (this.stepNumber > 0) {
22079
- const newInput = messageList.clear.input.db();
22080
- const newOutput = messageList.clear.response.db();
22081
- const messagesToSave = [...newInput, ...newOutput];
22082
- if (messagesToSave.length > 0) {
22083
- await om.persistMessages(messagesToSave, threadId, resourceId);
22084
- for (const msg of messagesToSave) messageList.add(msg, "memory");
22144
+ const willObserveNow = statusSnapshot.shouldObserve && !hasIncompleteToolCalls;
22145
+ /** In-flight message ids the step-0 cleanup must never remove from live context. */
22146
+ let step0PreserveIds;
22147
+ if (this.stepNumber > 0 || willObserveNow) {
22148
+ if (this.stepNumber > 0) {
22149
+ const newInput = messageList.clear.input.db();
22150
+ const newOutput = messageList.clear.response.db();
22151
+ const messagesToSave = [...newInput, ...newOutput];
22152
+ if (messagesToSave.length > 0) {
22153
+ await om.persistMessages(messagesToSave, threadId, resourceId);
22154
+ for (const msg of messagesToSave) messageList.add(msg, "memory");
22155
+ }
22156
+ } else {
22157
+ const pending = [...messageList.get.input.db(), ...messageList.get.response.db()];
22158
+ if (pending.length > 0) await om.persistMessages(pending, threadId, resourceId);
22159
+ step0PreserveIds = pending.map((msg) => msg.id);
22085
22160
  }
22086
- if (statusSnapshot.shouldObserve && !hasIncompleteToolCalls) {
22161
+ if (this.stepNumber === 0 && willObserveNow && this.turn.responseMessageId) {
22162
+ const seed = {
22163
+ id: this.turn.responseMessageId,
22164
+ role: "assistant",
22165
+ content: {
22166
+ format: 2,
22167
+ parts: []
22168
+ },
22169
+ type: "text",
22170
+ createdAt: /* @__PURE__ */ new Date(),
22171
+ threadId,
22172
+ resourceId
22173
+ };
22174
+ messageList.add(seed, "response");
22175
+ this.seededResponseMessage = true;
22176
+ omDebug(`[OM:step0] seeded response message ${seed.id} for step-0 observation markers`);
22177
+ }
22178
+ if (willObserveNow) {
22087
22179
  const preObsGeneration = this.turn.record.generationCount;
22088
22180
  const obsResult = await this.runThresholdObservation();
22089
22181
  observerExchange = obsResult.observerExchange;
@@ -22097,7 +22189,8 @@ var ObservationStep = class {
22097
22189
  resourceId,
22098
22190
  messages: messageList,
22099
22191
  observedMessageIds: observedIds,
22100
- retentionFloor: minRemaining
22192
+ retentionFloor: minRemaining,
22193
+ preserveMessageIds: step0PreserveIds
22101
22194
  });
22102
22195
  if (statusSnapshot.asyncObservationEnabled) await om.resetBufferingState({
22103
22196
  threadId,
@@ -22112,7 +22205,7 @@ var ObservationStep = class {
22112
22205
  statusSnapshot = await om.getStatus({
22113
22206
  threadId,
22114
22207
  resourceId,
22115
- messages: messageList.get.all.db()
22208
+ messages: getObservableMessages(messageList)
22116
22209
  });
22117
22210
  }
22118
22211
  const otherThreadsContext = await this.turn.refreshOtherThreadsContext();
@@ -22161,10 +22254,11 @@ var ObservationStep = class {
22161
22254
  const { threadId, resourceId, messageList } = this.turn;
22162
22255
  const om = this.turn.om;
22163
22256
  await om.waitForBuffering(threadId, resourceId);
22257
+ const observableMessages = this.seededResponseMessage ? getObservableMessages(messageList).filter((msg) => msg.id !== this.turn.responseMessageId) : getObservableMessages(messageList);
22164
22258
  const freshStatus = await om.getStatus({
22165
22259
  threadId,
22166
22260
  resourceId,
22167
- messages: messageList.get.all.db()
22261
+ messages: observableMessages
22168
22262
  });
22169
22263
  if (!freshStatus.shouldObserve) return {
22170
22264
  succeeded: false,
@@ -22174,7 +22268,7 @@ var ObservationStep = class {
22174
22268
  const activation = await om.activate({
22175
22269
  threadId,
22176
22270
  resourceId,
22177
- messages: messageList.get.all.db(),
22271
+ messages: observableMessages,
22178
22272
  currentModel: this.turn.actorModelContext,
22179
22273
  writer: this.turn.writer,
22180
22274
  messageList
@@ -22190,7 +22284,7 @@ var ObservationStep = class {
22190
22284
  currentModel: this.turn.actorModelContext,
22191
22285
  requestContext: this.turn.requestContext,
22192
22286
  observabilityContext: this.turn.observabilityContext,
22193
- lastActivityAt: getLastActivityFromMessages(messageList.get.all.db()),
22287
+ lastActivityAt: getLastActivityFromMessages(getObservableMessages(messageList)),
22194
22288
  reflectionHooks: om.composeHooks(void 0, {
22195
22289
  threadId,
22196
22290
  resourceId,
@@ -22207,7 +22301,8 @@ var ObservationStep = class {
22207
22301
  const obsResult = await om.observe({
22208
22302
  threadId,
22209
22303
  resourceId,
22210
- messages: messageList.get.all.db(),
22304
+ messages: observableMessages,
22305
+ messageList,
22211
22306
  trigger: "turn-sync",
22212
22307
  requestContext: this.turn.requestContext,
22213
22308
  writer: this.turn.writer,
@@ -22215,7 +22310,7 @@ var ObservationStep = class {
22215
22310
  });
22216
22311
  if (obsResult.observed) {
22217
22312
  const observedMessageIds = new Set(obsResult.record.observedMessageIds ?? []);
22218
- const liveMessages = messageList.get.all.db();
22313
+ const liveMessages = getObservableMessages(messageList);
22219
22314
  let latestObservedIndex = -1;
22220
22315
  for (let i = liveMessages.length - 1; i >= 0; i--) {
22221
22316
  const message = liveMessages[i];
@@ -22224,10 +22319,12 @@ var ObservationStep = class {
22224
22319
  break;
22225
22320
  }
22226
22321
  }
22227
- const messageToSeal = latestObservedIndex >= 0 ? liveMessages[latestObservedIndex] : void 0;
22322
+ let messageToSeal = latestObservedIndex >= 0 ? liveMessages[latestObservedIndex] : void 0;
22323
+ if (this.stepNumber === 0 && messageToSeal?.role !== "assistant") messageToSeal = void 0;
22228
22324
  const messagesToSeal = messageToSeal ? [messageToSeal] : [];
22229
22325
  om.sealMessagesForBuffering(messagesToSeal);
22230
- try {
22326
+ if (this.seededResponseMessage) omDebug("[OM:observe] skipping response-id rotation — step-0 seeded response message holds the active id");
22327
+ else try {
22231
22328
  await this.turn.hooks?.onSyncObservationComplete?.();
22232
22329
  } catch (error) {
22233
22330
  omDebug(`[OM:observe] onSyncObservationComplete hook failed: ${error instanceof Error ? error.message : String(error)}`);
@@ -22288,6 +22385,8 @@ var ObservationTurn = class {
22288
22385
  sendSignal;
22289
22386
  /** Current actor model for this step. Updated by the processor before prepare(). */
22290
22387
  actorModelContext;
22388
+ /** The active assistant response message ID for this step. Updated by the processor before prepare(). */
22389
+ responseMessageId;
22291
22390
  /** Processor-provided hooks for turn/step lifecycle integration. */
22292
22391
  hooks;
22293
22392
  constructor(opts) {
@@ -22386,7 +22485,7 @@ var ObservationTurn = class {
22386
22485
  const asyncObservationEnabled = this.om.buffering.isAsyncObservationEnabled();
22387
22486
  const bufferOnIdle = this.om.getObservationConfig().bufferOnIdle;
22388
22487
  if (asyncObservationEnabled && bufferOnIdle) {
22389
- const allMessages = this.messageList.get.all.db();
22488
+ const allMessages = getObservableMessages(this.messageList);
22390
22489
  const record = this._record;
22391
22490
  const unobservedMessages = this.om.getUnobservedMessages(allMessages, record);
22392
22491
  if (unobservedMessages.length > 0) this.om.buffer({
@@ -22756,7 +22855,7 @@ function getCurrentModel$1(model) {
22756
22855
  return formatModelContext$1(model?.provider, model?.modelId);
22757
22856
  }
22758
22857
  function getLastModelFromMessageList(messageList) {
22759
- const messages = messageList?.get.all.db();
22858
+ const messages = messageList ? getObservableMessages(messageList) : void 0;
22760
22859
  if (!messages) return void 0;
22761
22860
  for (let i = messages.length - 1; i >= 0; i--) {
22762
22861
  const message = messages[i];
@@ -24340,7 +24439,7 @@ var ObservationalMemory = class ObservationalMemory {
24340
24439
  */
24341
24440
  async persistMarkerToMessage(marker, messageList, threadId, resourceId) {
24342
24441
  if (!messageList) return;
24343
- const allMsgs = messageList.get.all.db();
24442
+ const allMsgs = getObservableMessages(messageList);
24344
24443
  for (let i = allMsgs.length - 1; i >= 0; i--) {
24345
24444
  const msg = allMsgs[i];
24346
24445
  if (msg?.role === "assistant" && msg.content?.parts && Array.isArray(msg.content.parts)) {
@@ -24956,6 +25055,7 @@ ${formattedMessages}
24956
25055
  */
24957
25056
  async getObservedMessageIdsForCleanup(opts) {
24958
25057
  const { threadId, resourceId, messages, observedMessageIds, retentionFloor } = opts;
25058
+ const preserveSet = opts.preserveMessageIds?.length ? new Set(opts.preserveMessageIds) : null;
24959
25059
  const record = await this.getOrCreateRecord(threadId, resourceId);
24960
25060
  const effectiveObservedIds = observedMessageIds && observedMessageIds.length > 0 ? observedMessageIds : Array.isArray(record.observedMessageIds) ? record.observedMessageIds : [];
24961
25061
  if (effectiveObservedIds.length === 0) return [];
@@ -24967,6 +25067,10 @@ ${formattedMessages}
24967
25067
  const retentionCounter = typeof retentionFloor === "number" ? new TokenCounter() : null;
24968
25068
  for (const msg of messages) {
24969
25069
  if (!msg?.id || msg.id === "om-continuation" || !observedSet.has(msg.id)) continue;
25070
+ if (preserveSet?.has(msg.id)) {
25071
+ skipped += 1;
25072
+ continue;
25073
+ }
24970
25074
  const unobservedParts = getUnobservedParts(msg);
24971
25075
  const totalParts = msg.content?.parts?.length ?? 0;
24972
25076
  if (unobservedParts.length > 0 && unobservedParts.length < totalParts) {
@@ -25011,9 +25115,9 @@ ${formattedMessages}
25011
25115
  */
25012
25116
  /** @internal Used by ObservationStep. */
25013
25117
  async cleanupMessages(opts) {
25014
- const { threadId, resourceId, observedMessageIds, retentionFloor } = opts;
25118
+ const { threadId, resourceId, observedMessageIds, retentionFloor, preserveMessageIds } = opts;
25015
25119
  const messageList = this.isMessageList(opts.messages) ? opts.messages : void 0;
25016
- const allMsgs = messageList ? messageList.get.all.db() : opts.messages;
25120
+ const allMsgs = messageList ? getObservableMessages(messageList) : opts.messages;
25017
25121
  let markerIdx = -1;
25018
25122
  let markerMsg = null;
25019
25123
  for (let i = allMsgs.length - 1; i >= 0; i--) {
@@ -25032,7 +25136,8 @@ ${formattedMessages}
25032
25136
  resourceId,
25033
25137
  messages: allMsgs,
25034
25138
  observedMessageIds,
25035
- retentionFloor
25139
+ retentionFloor,
25140
+ preserveMessageIds
25036
25141
  });
25037
25142
  if (messageList) {
25038
25143
  if (idsToRemoveList.length > 0) messageList.removeByIds(idsToRemoveList);
@@ -25044,18 +25149,21 @@ ${formattedMessages}
25044
25149
  if (markerMsg && markerIdx !== -1) {
25045
25150
  const idsToRemove = [];
25046
25151
  const messagesToSave = [];
25152
+ const preserveSet = preserveMessageIds?.length ? new Set(preserveMessageIds) : null;
25047
25153
  for (let i = 0; i < markerIdx; i++) {
25048
25154
  const msg = allMsgs[i];
25049
- if (msg?.id && msg.id !== "om-continuation") {
25155
+ if (msg?.id && msg.id !== "om-continuation" && !preserveSet?.has(msg.id)) {
25050
25156
  idsToRemove.push(msg.id);
25051
25157
  messagesToSave.push(msg);
25052
25158
  }
25053
25159
  }
25054
25160
  messagesToSave.push(markerMsg);
25055
- const unobservedParts = getUnobservedParts(markerMsg);
25056
- if (unobservedParts.length === 0) {
25057
- if (markerMsg.id) idsToRemove.push(markerMsg.id);
25058
- } else if (unobservedParts.length < (markerMsg.content?.parts?.length ?? 0)) markerMsg.content.parts = unobservedParts;
25161
+ if (!Boolean(markerMsg.id && preserveSet?.has(markerMsg.id))) {
25162
+ const unobservedParts = getUnobservedParts(markerMsg);
25163
+ if (unobservedParts.length === 0) {
25164
+ if (markerMsg.id) idsToRemove.push(markerMsg.id);
25165
+ } else if (unobservedParts.length < (markerMsg.content?.parts?.length ?? 0)) markerMsg.content.parts = unobservedParts;
25166
+ }
25059
25167
  if (messageList) {
25060
25168
  if (idsToRemove.length > 0) messageList.removeByIds(idsToRemove);
25061
25169
  if (messagesToSave.length > 0) await this.persistMessages(messagesToSave, threadId, resourceId);
@@ -25842,6 +25950,7 @@ ${formattedMessages}
25842
25950
  threadId,
25843
25951
  resourceId,
25844
25952
  messages: unobservedMessages,
25953
+ messageList: opts.messageList,
25845
25954
  reflectionHooks,
25846
25955
  agent: opts.agent,
25847
25956
  requestContext,
@@ -26124,7 +26233,7 @@ function isTemporalGapMarkerForMessage(message, targetMessageId) {
26124
26233
  async function insertTemporalGapMarkers({ messageList, sendSignal }) {
26125
26234
  const latestInputMessage = messageList.get.input.db().filter((message) => Boolean(message)).at(-1);
26126
26235
  if (!latestInputMessage || isTemporalGapMarker(latestInputMessage)) return;
26127
- const allMessages = messageList.get.all.db().filter((message) => Boolean(message));
26236
+ const allMessages = getObservableMessages(messageList).filter((message) => Boolean(message));
26128
26237
  const latestInputIndex = allMessages.findIndex((message) => message.id === latestInputMessage.id);
26129
26238
  if (latestInputIndex <= 0) return;
26130
26239
  if (allMessages.some((message) => isTemporalGapMarkerForMessage(message, latestInputMessage.id))) return;
@@ -26231,7 +26340,7 @@ var ObservationalMemoryProcessor = class {
26231
26340
  this.temporalMarkers = options?.temporalMarkers ?? false;
26232
26341
  }
26233
26342
  async processInputStep(args) {
26234
- const { messageList, requestContext, stepNumber, state: _state, writer, model, abortSignal, abort, rotateResponseMessageId } = args;
26343
+ const { messageList, requestContext, stepNumber, state: _state, writer, model, abortSignal, abort, messageId, rotateResponseMessageId } = args;
26235
26344
  const state = _state ?? {};
26236
26345
  omDebug(`[OM:processInputStep:ENTER] step=${stepNumber}, hasMastraMemory=${!!requestContext?.get("MastraMemory")}, hasMemoryInfo=${!!messageList?.serialize()?.memoryInfo?.threadId}`);
26237
26346
  const context = this.engine.getThreadContext(requestContext, messageList);
@@ -26317,6 +26426,7 @@ var ObservationalMemoryProcessor = class {
26317
26426
  state.__omObservabilityContext = observabilityContext;
26318
26427
  this.turn.observabilityContext = observabilityContext;
26319
26428
  this.turn.actorModelContext = actorModelContext;
26429
+ this.turn.responseMessageId = messageId;
26320
26430
  {
26321
26431
  const step = this.turn.step(stepNumber);
26322
26432
  let ctx;
@@ -26347,7 +26457,7 @@ var ObservationalMemoryProcessor = class {
26347
26457
  threadId,
26348
26458
  resourceId
26349
26459
  });
26350
- const allDbMsgs = messageList.get.all.db();
26460
+ const allDbMsgs = getObservableMessages(messageList);
26351
26461
  const tokenCounter = this.engine.getTokenCounter();
26352
26462
  const contextTokens = await tokenCounter.countMessagesAsync(allDbMsgs);
26353
26463
  const otherThreadsContext = this.turn.context.otherThreadsContext;
@@ -26717,6 +26827,18 @@ var Memory = class extends _mastra_core_memory.MastraMemory {
26717
26827
  _omEngine;
26718
26828
  _omEngineInstance;
26719
26829
  _mastraInstance;
26830
+ /**
26831
+ * Every vector cleanup that deleteThread or deleteMessages started in the background.
26832
+ * Callers do not wait for the cleanup, so this handle is the only join point.
26833
+ */
26834
+ pendingVectorCleanup = Promise.resolve();
26835
+ /**
26836
+ * Adds a background vector cleanup to the join handle.
26837
+ * The handle keeps the earlier cleanups, so it settles only after all of them end.
26838
+ */
26839
+ trackVectorCleanup(cleanup) {
26840
+ this.pendingVectorCleanup = Promise.allSettled([this.pendingVectorCleanup, cleanup]).then(() => void 0);
26841
+ }
26720
26842
  /** The shared ObservationalMemory engine. Lazily created on first access. */
26721
26843
  get omEngine() {
26722
26844
  if (!this._omEngine) this._omEngine = this._initOMEngine().then((engine) => {
@@ -26981,26 +27103,40 @@ var Memory = class extends _mastra_core_memory.MastraMemory {
26981
27103
  const thread = await memoryStore.getThreadById({ threadId });
26982
27104
  await memoryStore.deleteThread({ threadId });
26983
27105
  if (thread?.resourceId && memoryStore.supportsObservationalMemory) await memoryStore.clearObservationalMemory(threadId, thread.resourceId);
26984
- if (this.vector) this.deleteThreadVectors(threadId);
27106
+ if (this.vector) this.trackVectorCleanup(this.deleteThreadVectors(threadId));
26985
27107
  }
26986
27108
  /**
26987
- * Lists all vector indexes that match the memory messages prefix.
26988
- * Handles separator differences across vector store backends (e.g. '_' vs '-').
27109
+ * Prefix shared by every message index. The index for the default embedding
27110
+ * dimension is named with the bare prefix; other dimensions add a suffix.
26989
27111
  */
26990
- async getMemoryVectorIndexes() {
27112
+ get messageIndexPrefix() {
27113
+ return this.getEmbeddingIndexName();
27114
+ }
27115
+ /**
27116
+ * Prefix shared by every observation index. Each observation index adds a dimension suffix.
27117
+ */
27118
+ get observationIndexPrefix() {
27119
+ return `memory${this.vector?.indexSeparator ?? "_"}observations`;
27120
+ }
27121
+ /**
27122
+ * Lists the vector indexes whose name starts with one of the given prefixes.
27123
+ * Index names can carry a dimension suffix, so discovery matches on the prefix.
27124
+ */
27125
+ async getMemoryVectorIndexes(prefixes) {
26991
27126
  if (!this.vector) return [];
26992
- const prefix = `memory${this.vector.indexSeparator ?? "_"}messages`;
26993
- return (await this.vector.listIndexes()).filter((name) => name.startsWith(prefix));
27127
+ return (await this.vector.listIndexes()).filter((name) => prefixes.some((prefix) => name.startsWith(prefix)));
26994
27128
  }
26995
27129
  /**
26996
27130
  * Deletes all vector embeddings associated with a thread.
26997
27131
  * This is called internally by deleteThread to clean up orphaned vectors.
27132
+ * Both message and observation vectors are removed, so no text of the deleted
27133
+ * thread stays reachable through resource-scoped retrieval.
26998
27134
  *
26999
27135
  * @param threadId - The ID of the thread whose vectors should be deleted
27000
27136
  */
27001
27137
  async deleteThreadVectors(threadId) {
27002
27138
  try {
27003
- const memoryIndexes = await this.getMemoryVectorIndexes();
27139
+ const memoryIndexes = await this.getMemoryVectorIndexes([this.messageIndexPrefix, this.observationIndexPrefix]);
27004
27140
  await Promise.all(memoryIndexes.map(async (indexName) => {
27005
27141
  try {
27006
27142
  await this.vector.deleteVectors({
@@ -27008,14 +27144,14 @@ var Memory = class extends _mastra_core_memory.MastraMemory {
27008
27144
  filter: { thread_id: threadId }
27009
27145
  });
27010
27146
  } catch {
27011
- this.logger.debug("Failed to delete vectors for thread, skipping", {
27147
+ this.logger.warn("Failed to delete vectors of the deleted thread from index", {
27012
27148
  threadId,
27013
27149
  indexName
27014
27150
  });
27015
27151
  }
27016
27152
  }));
27017
27153
  } catch {
27018
- this.logger.debug("Failed to clean up vectors for thread", { threadId });
27154
+ this.logger.warn("Failed to clean up vectors of the deleted thread", { threadId });
27019
27155
  }
27020
27156
  }
27021
27157
  async updateWorkingMemory({ threadId, resourceId, workingMemory, memoryConfig, observabilityContext }) {
@@ -27688,7 +27824,7 @@ Notes:
27688
27824
  getObservationEmbeddingIndexName(dimensions) {
27689
27825
  const usedDimensions = dimensions ?? 384;
27690
27826
  const separator = this.vector?.indexSeparator ?? "_";
27691
- return `memory${separator}observations${separator}${usedDimensions}`;
27827
+ return `${this.observationIndexPrefix}${separator}${usedDimensions}`;
27692
27828
  }
27693
27829
  async createObservationEmbeddingIndex(dimensions) {
27694
27830
  const usedDimensions = dimensions ?? 384;
@@ -27934,7 +28070,10 @@ Notes:
27934
28070
  tools[name] = tool;
27935
28071
  }
27936
28072
  const omConfig = normalizeObservationalMemoryConfig(mergedConfig.observationalMemory);
27937
- if (omConfig?.retrieval) tools.recall = recallTool(mergedConfig, { retrievalScope: typeof omConfig.retrieval === "object" ? omConfig.retrieval.scope ?? "resource" : "resource" });
28073
+ if (omConfig?.retrieval) tools.recall = recallTool(mergedConfig, {
28074
+ retrievalScope: typeof omConfig.retrieval === "object" ? omConfig.retrieval.scope ?? "resource" : "resource",
28075
+ searchEnabled: this.hasRetrievalSearch(omConfig.retrieval)
28076
+ });
27938
28077
  return tools;
27939
28078
  }
27940
28079
  /**
@@ -27990,7 +28129,7 @@ Notes:
27990
28129
  }));
27991
28130
  const messageIdsNeedingDeletion = /* @__PURE__ */ new Set([...messageIdsWithClearedContent, ...messageIdsWithNewEmbeddings]);
27992
28131
  if (messageIdsNeedingDeletion.size > 0) try {
27993
- const memoryIndexes = await this.getMemoryVectorIndexes();
28132
+ const memoryIndexes = await this.getMemoryVectorIndexes([this.messageIndexPrefix]);
27994
28133
  const idsToDelete = [...messageIdsNeedingDeletion];
27995
28134
  await Promise.all(memoryIndexes.map(async (indexName) => {
27996
28135
  for (let i = 0; i < idsToDelete.length; i += VECTOR_DELETE_BATCH_SIZE) {
@@ -28049,7 +28188,7 @@ Notes:
28049
28188
  const span = this.createMemorySpan("delete", observabilityContext, void 0, { messageCount: messageIds.length });
28050
28189
  try {
28051
28190
  await (await this.getMemoryStore()).deleteMessages(messageIds);
28052
- if (this.vector) this.deleteMessageVectors(messageIds);
28191
+ if (this.vector) this.trackVectorCleanup(this.deleteMessageVectors(messageIds));
28053
28192
  span?.end({
28054
28193
  output: { success: true },
28055
28194
  attributes: { messageCount: messageIds.length }
@@ -28065,12 +28204,14 @@ Notes:
28065
28204
  /**
28066
28205
  * Deletes vector embeddings for specific messages.
28067
28206
  * This is called internally by deleteMessages to clean up orphaned vectors.
28207
+ * Only the message indexes are touched, because observation vectors can hold
28208
+ * text of other messages of the thread.
28068
28209
  *
28069
28210
  * @param messageIds - The IDs of the messages whose vectors should be deleted
28070
28211
  */
28071
28212
  async deleteMessageVectors(messageIds) {
28072
28213
  try {
28073
- const memoryIndexes = await this.getMemoryVectorIndexes();
28214
+ const memoryIndexes = await this.getMemoryVectorIndexes([this.messageIndexPrefix]);
28074
28215
  await Promise.all(memoryIndexes.map(async (indexName) => {
28075
28216
  for (let i = 0; i < messageIds.length; i += VECTOR_DELETE_BATCH_SIZE) {
28076
28217
  const batch = messageIds.slice(i, i + VECTOR_DELETE_BATCH_SIZE);
@@ -28697,4 +28838,4 @@ Object.defineProperty(exports, "wrapInObservationGroup", {
28697
28838
  }
28698
28839
  });
28699
28840
 
28700
- //# sourceMappingURL=src-CjTEWCUF.cjs.map
28841
+ //# sourceMappingURL=src-DY9-_lul.cjs.map