@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
@@ -14835,6 +14835,7 @@ var Extractor = class Extractor {
14835
14835
  includePreviousExtraction;
14836
14836
  metadataKeyPath;
14837
14837
  onExtracted;
14838
+ retryStructuredExtractionOnEmptyObject;
14838
14839
  /** @internal */
14839
14840
  internal;
14840
14841
  instructionsConfig;
@@ -14857,6 +14858,7 @@ var Extractor = class Extractor {
14857
14858
  this.includePreviousExtraction = config.includePreviousExtraction ?? true;
14858
14859
  this.metadataKeyPath = config.metadataKeyPath ?? `extracted.${slug}`;
14859
14860
  this.onExtracted = config.onExtracted;
14861
+ this.retryStructuredExtractionOnEmptyObject = config.retryStructuredExtractionOnEmptyObject ?? false;
14860
14862
  this.internal = internal;
14861
14863
  }
14862
14864
  async resolve(context) {
@@ -14869,7 +14871,8 @@ var Extractor = class Extractor {
14869
14871
  ...schema ? { schema } : {},
14870
14872
  includePreviousExtraction: this.includePreviousExtraction,
14871
14873
  metadataKeyPath: this.metadataKeyPath,
14872
- onExtracted: this.onExtracted
14874
+ onExtracted: this.onExtracted,
14875
+ retryStructuredExtractionOnEmptyObject: this.retryStructuredExtractionOnEmptyObject
14873
14876
  }, this.internal);
14874
14877
  }
14875
14878
  };
@@ -15194,6 +15197,9 @@ if (OM_DEBUG_LOG) {
15194
15197
  function isAbortError$1(error, abortSignal) {
15195
15198
  return abortSignal?.aborted === true || error instanceof DOMException && error.name === "AbortError" || error instanceof Error && error.name === "AbortError";
15196
15199
  }
15200
+ function shouldRetryEmptyStructuredObject(object, extractors) {
15201
+ return Object.keys(object).length === 0 && extractors.some((extractor) => extractor.retryStructuredExtractionOnEmptyObject);
15202
+ }
15197
15203
  async function extractStructuredValues(opts) {
15198
15204
  const structuredExtractors = (opts.extractors ?? []).filter((extractor) => extractor.mode === "structured");
15199
15205
  if (structuredExtractors.length === 0) return {
@@ -15230,8 +15236,10 @@ ${extractorInstructions}${priorLines.length > 0 ? `\n\n## Prior Extracted Values
15230
15236
  return output.object;
15231
15237
  };
15232
15238
  let object;
15239
+ let retryEmptyObject = false;
15233
15240
  try {
15234
15241
  object = await generateWithStructuredOutput();
15242
+ retryEmptyObject = shouldRetryEmptyStructuredObject(object, structuredExtractors);
15235
15243
  } catch (error) {
15236
15244
  if (isAbortError$1(error, opts.abortSignal)) throw error;
15237
15245
  try {
@@ -15248,6 +15256,19 @@ ${extractorInstructions}${priorLines.length > 0 ? `\n\n## Prior Extracted Values
15248
15256
  };
15249
15257
  }
15250
15258
  }
15259
+ if (retryEmptyObject) try {
15260
+ object = await generateWithStructuredOutput(coreFeatures.has("json-prompt-injection:inline") ? "inline" : true);
15261
+ } catch (fallbackError) {
15262
+ if (isAbortError$1(fallbackError, opts.abortSignal)) throw fallbackError;
15263
+ const message = fallbackError instanceof Error ? fallbackError.message : String(fallbackError);
15264
+ return {
15265
+ values,
15266
+ failures: structuredExtractors.map((extractor) => ({
15267
+ slug: extractor.slug,
15268
+ error: message
15269
+ }))
15270
+ };
15271
+ }
15251
15272
  for (const extractor of structuredExtractors) {
15252
15273
  const value = object[extractor.slug];
15253
15274
  if (value === void 0 || value === null || value === "") continue;
@@ -18553,6 +18574,17 @@ var TokenCounter = class TokenCounter {
18553
18574
  toolResultDelta
18554
18575
  };
18555
18576
  }
18577
+ if (invocation.state === "output-error") {
18578
+ toolResultDelta++;
18579
+ const errorText = invocation.errorText;
18580
+ const errorMessage = typeof errorText === "string" ? errorText : "Tool execution failed";
18581
+ tokens += this.readOrPersistPartEstimate(part, "tool-result-error", errorMessage);
18582
+ return {
18583
+ tokens,
18584
+ overheadDelta,
18585
+ toolResultDelta
18586
+ };
18587
+ }
18556
18588
  throw new Error(`Unhandled tool-invocation state '${part.toolInvocation?.state}' in token counting for part type '${part.type}'`);
18557
18589
  }
18558
18590
  if (typeof part.type === "string" && part.type.startsWith("data-")) return {
@@ -18778,6 +18810,7 @@ var WorkingMemoryExtractor = class extends Extractor {
18778
18810
  name: "Working Memory",
18779
18811
  includePreviousExtraction: false,
18780
18812
  metadataKeyPath: false,
18813
+ retryStructuredExtractionOnEmptyObject: true,
18781
18814
  instructions: async (context) => buildWorkingMemoryInstructions(await getWorkingMemoryDetails(context)),
18782
18815
  schema: async (context) => {
18783
18816
  return (await getWorkingMemoryDetails(context)).usesSchema ? z.union([z.record(z.string(), z.unknown()), z.null()]) : void 0;
@@ -18926,9 +18959,10 @@ async function listThreadsForResource({ memory, resourceId, currentThreadId, pag
18926
18959
  hasMore
18927
18960
  };
18928
18961
  }
18962
+ 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.";
18929
18963
  async function searchMessagesForResource({ memory, resourceId, currentThreadId, query, topK = 10, maxTokens = DEFAULT_MAX_RESULT_TOKENS, before, after, threadScope }) {
18930
18964
  if (!memory.searchMessages) return {
18931
- results: "Search is not configured. Enable it with `retrieval: { vector: true }` and configure a vector store and embedder on your Memory instance.",
18965
+ results: SEARCH_NOT_CONFIGURED_MESSAGE,
18932
18966
  count: 0
18933
18967
  };
18934
18968
  const MAX_TOPK = 20;
@@ -19474,9 +19508,16 @@ async function recallThreadFromStart({ memory, threadId, resourceId, page = 1, l
19474
19508
  }
19475
19509
  const recallTool = (_memoryConfig, options) => {
19476
19510
  const isResourceScope = (options?.retrievalScope ?? "thread") === "resource";
19511
+ const searchEnabled = options?.searchEnabled ?? true;
19512
+ 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.`;
19513
+ const modeEnum = searchEnabled ? [
19514
+ "messages",
19515
+ "threads",
19516
+ "search"
19517
+ ] : ["messages", "threads"];
19477
19518
  return createTool({
19478
19519
  id: "recall",
19479
- 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.",
19520
+ description,
19480
19521
  inputSchema: {
19481
19522
  $schema: "http://json-schema.org/draft-07/schema#",
19482
19523
  type: "object",
@@ -19484,12 +19525,8 @@ const recallTool = (_memoryConfig, options) => {
19484
19525
  ...isResourceScope ? {
19485
19526
  mode: {
19486
19527
  type: "string",
19487
- enum: [
19488
- "messages",
19489
- "threads",
19490
- "search"
19491
- ],
19492
- 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."
19528
+ enum: modeEnum,
19529
+ 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." : ""}`
19493
19530
  },
19494
19531
  threadId: {
19495
19532
  type: "string",
@@ -19506,18 +19543,14 @@ const recallTool = (_memoryConfig, options) => {
19506
19543
  }
19507
19544
  } : { mode: {
19508
19545
  type: "string",
19509
- enum: [
19510
- "messages",
19511
- "threads",
19512
- "search"
19513
- ],
19514
- 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."
19546
+ enum: modeEnum,
19547
+ 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." : ""}`
19515
19548
  } },
19516
- query: {
19549
+ ...searchEnabled ? { query: {
19517
19550
  type: "string",
19518
19551
  minLength: 1,
19519
19552
  description: "Search query for mode=\"search\". Finds messages semantically similar to this text."
19520
- },
19553
+ } } : {},
19521
19554
  cursor: {
19522
19555
  type: "string",
19523
19556
  minLength: 1,
@@ -19578,6 +19611,10 @@ const recallTool = (_memoryConfig, options) => {
19578
19611
  if (!memory) throw new Error("Memory instance is required for recall");
19579
19612
  if (explicitThreadId === "current" && !currentThreadId) throw new Error("Could not resolve current thread.");
19580
19613
  if (mode === "search") {
19614
+ if (!searchEnabled) return {
19615
+ results: SEARCH_NOT_CONFIGURED_MESSAGE,
19616
+ count: 0
19617
+ };
19581
19618
  if (!query) throw new Error("query is required for mode=\"search\"");
19582
19619
  if (!resourceId) throw new Error("Resource ID is required for recall");
19583
19620
  return searchMessagesForResource({
@@ -20539,6 +20576,24 @@ function getUnobservedPartsPreservingToolCallPairs(message) {
20539
20576
  return preservedCalls.length > 0 ? [...preservedCalls, ...unobservedParts] : unobservedParts;
20540
20577
  }
20541
20578
  /**
20579
+ * Get the messages Observational Memory is allowed to work with.
20580
+ *
20581
+ * Messages supplied through the `context` option are per-run ephemeral input. Core's
20582
+ * persistence contract already treats them as never-persist: `MessageStateManager` routes
20583
+ * them into `userContextMessages`, and `drainUnsavedMessages` only drains input/response.
20584
+ *
20585
+ * OM builds its windows from `get.all.db()`, which includes context messages, and then
20586
+ * seals and persists candidates directly — turning ephemeral context into durable user
20587
+ * messages. Excluding them here keeps OM's window, sealing, persistence and token
20588
+ * accounting consistent with that contract.
20589
+ */
20590
+ function getObservableMessages(messageList) {
20591
+ const allMessages = messageList.get.all.db();
20592
+ const contextMessageIds = messageList.makeMessageSourceChecker().context;
20593
+ if (contextMessageIds.size === 0) return allMessages;
20594
+ return allMessages.filter((message) => !contextMessageIds.has(message.id));
20595
+ }
20596
+ /**
20542
20597
  * Safely extract buffered observation chunks from a record.
20543
20598
  * Handles both array and JSON-string formats, returning empty array if malformed.
20544
20599
  */
@@ -20551,7 +20606,7 @@ function getUnobservedPartsPreservingToolCallPairs(message) {
20551
20606
  */
20552
20607
  function filterObservedMessages(opts) {
20553
20608
  const { messageList, record } = opts;
20554
- const allMessages = messageList.get.all.db();
20609
+ const allMessages = getObservableMessages(messageList);
20555
20610
  const useMarkerBoundaryPruning = opts.useMarkerBoundaryPruning ?? true;
20556
20611
  const preserveMessageIds = opts.preserveMessageIds ?? /* @__PURE__ */ new Set();
20557
20612
  const observedIds = new Set(Array.isArray(record?.observedMessageIds) ? record.observedMessageIds : []);
@@ -20969,7 +21024,7 @@ var ObservationStrategy = class ObservationStrategy {
20969
21024
  transient: true
20970
21025
  }).catch(() => {});
20971
21026
  const markerThreadId = marker.data?.threadId ?? this.opts.threadId;
20972
- await this.persistMarkerToStorage(marker, markerThreadId, this.opts.resourceId);
21027
+ if (!await this.persistMarkerToMessage(marker, this.opts.messageList, markerThreadId, this.opts.resourceId)) await this.persistMarkerToStorage(marker, markerThreadId, this.opts.resourceId);
20973
21028
  }
20974
21029
  getObservationMarkerConfig() {
20975
21030
  return {
@@ -21097,10 +21152,14 @@ var ObservationStrategy = class ObservationStrategy {
21097
21152
  /**
21098
21153
  * Persist a marker part on the last assistant message in a MessageList
21099
21154
  * AND save the updated message to the DB.
21155
+ *
21156
+ * @returns true when a marker was placed on an assistant message, false when
21157
+ * no list was provided or the list contains no assistant message (caller
21158
+ * should fall back to `persistMarkerToStorage`).
21100
21159
  */
21101
21160
  async persistMarkerToMessage(marker, messageList, threadId, resourceId) {
21102
- if (!messageList) return;
21103
- const allMsgs = messageList.get.all.db();
21161
+ if (!messageList) return false;
21162
+ const allMsgs = getObservableMessages(messageList);
21104
21163
  for (let i = allMsgs.length - 1; i >= 0; i--) {
21105
21164
  const msg = allMsgs[i];
21106
21165
  if (msg?.role === "assistant" && msg.content?.parts && Array.isArray(msg.content.parts)) {
@@ -21115,9 +21174,10 @@ var ObservationStrategy = class ObservationStrategy {
21115
21174
  } catch (e) {
21116
21175
  omDebug(`[OM:persistMarker] failed to save marker to DB: ${e}`);
21117
21176
  }
21118
- return;
21177
+ return true;
21119
21178
  }
21120
21179
  }
21180
+ return false;
21121
21181
  }
21122
21182
  };
21123
21183
  //#endregion
@@ -21926,6 +21986,12 @@ var ObservationStep = class {
21926
21986
  stepNumber;
21927
21987
  _prepared = false;
21928
21988
  _context;
21989
+ /**
21990
+ * True when this step seeded an empty assistant response message for a step-0
21991
+ * observation. While set, the response-id rotation hook must NOT run — rotating
21992
+ * would orphan the seed (markers would sit on a message the agent never streams into).
21993
+ */
21994
+ seededResponseMessage = false;
21929
21995
  constructor(turn, stepNumber) {
21930
21996
  this.turn = turn;
21931
21997
  this.stepNumber = stepNumber;
@@ -21972,7 +22038,7 @@ var ObservationStep = class {
21972
22038
  let didThresholdCleanup = false;
21973
22039
  let observerExchange;
21974
22040
  if (this.stepNumber === 0) {
21975
- const step0Messages = messageList.get.all.db();
22041
+ const step0Messages = getObservableMessages(messageList);
21976
22042
  const activation = await om.activate({
21977
22043
  threadId,
21978
22044
  resourceId,
@@ -22004,7 +22070,7 @@ var ObservationStep = class {
22004
22070
  currentModel: this.turn.actorModelContext,
22005
22071
  requestContext: this.turn.requestContext,
22006
22072
  observabilityContext: this.turn.observabilityContext,
22007
- lastActivityAt: getLastActivityFromMessages(messageList.get.all.db()),
22073
+ lastActivityAt: getLastActivityFromMessages(getObservableMessages(messageList)),
22008
22074
  reflectionHooks: om.composeHooks(void 0, {
22009
22075
  threadId,
22010
22076
  resourceId,
@@ -22014,7 +22080,7 @@ var ObservationStep = class {
22014
22080
  await this.turn.refreshRecord();
22015
22081
  if (this.turn.record.generationCount > preReflectGeneration) reflected = true;
22016
22082
  }
22017
- const allMsgsForToolCheck = messageList.get.all.db();
22083
+ const allMsgsForToolCheck = getObservableMessages(messageList);
22018
22084
  const lastMessage = allMsgsForToolCheck[allMsgsForToolCheck.length - 1];
22019
22085
  const pendingStepMessages = [...messageList.get.input.db(), ...messageList.get.response.db()];
22020
22086
  const latestStepParts = [...getLatestStepParts(lastMessage?.content?.parts ?? []), ...pendingStepMessages.flatMap((msg) => getLatestStepParts(msg.content?.parts ?? []))];
@@ -22023,10 +22089,10 @@ var ObservationStep = class {
22023
22089
  let statusSnapshot = await om.getStatus({
22024
22090
  threadId,
22025
22091
  resourceId,
22026
- messages: messageList.get.all.db()
22092
+ messages: getObservableMessages(messageList)
22027
22093
  });
22028
22094
  if (statusSnapshot.shouldBuffer && !hasIncompleteToolCalls) {
22029
- const allMessages = messageList.get.all.db();
22095
+ const allMessages = getObservableMessages(messageList);
22030
22096
  const unobservedMessages = om.getUnobservedMessages(allMessages, statusSnapshot.record);
22031
22097
  const candidates = om.getUnobservedMessages(unobservedMessages, statusSnapshot.record, { excludeBuffered: true });
22032
22098
  if (candidates.length > 0) {
@@ -22054,15 +22120,41 @@ var ObservationStep = class {
22054
22120
  });
22055
22121
  buffered = true;
22056
22122
  }
22057
- if (this.stepNumber > 0) {
22058
- const newInput = messageList.clear.input.db();
22059
- const newOutput = messageList.clear.response.db();
22060
- const messagesToSave = [...newInput, ...newOutput];
22061
- if (messagesToSave.length > 0) {
22062
- await om.persistMessages(messagesToSave, threadId, resourceId);
22063
- for (const msg of messagesToSave) messageList.add(msg, "memory");
22123
+ const willObserveNow = statusSnapshot.shouldObserve && !hasIncompleteToolCalls;
22124
+ /** In-flight message ids the step-0 cleanup must never remove from live context. */
22125
+ let step0PreserveIds;
22126
+ if (this.stepNumber > 0 || willObserveNow) {
22127
+ if (this.stepNumber > 0) {
22128
+ const newInput = messageList.clear.input.db();
22129
+ const newOutput = messageList.clear.response.db();
22130
+ const messagesToSave = [...newInput, ...newOutput];
22131
+ if (messagesToSave.length > 0) {
22132
+ await om.persistMessages(messagesToSave, threadId, resourceId);
22133
+ for (const msg of messagesToSave) messageList.add(msg, "memory");
22134
+ }
22135
+ } else {
22136
+ const pending = [...messageList.get.input.db(), ...messageList.get.response.db()];
22137
+ if (pending.length > 0) await om.persistMessages(pending, threadId, resourceId);
22138
+ step0PreserveIds = pending.map((msg) => msg.id);
22064
22139
  }
22065
- if (statusSnapshot.shouldObserve && !hasIncompleteToolCalls) {
22140
+ if (this.stepNumber === 0 && willObserveNow && this.turn.responseMessageId) {
22141
+ const seed = {
22142
+ id: this.turn.responseMessageId,
22143
+ role: "assistant",
22144
+ content: {
22145
+ format: 2,
22146
+ parts: []
22147
+ },
22148
+ type: "text",
22149
+ createdAt: /* @__PURE__ */ new Date(),
22150
+ threadId,
22151
+ resourceId
22152
+ };
22153
+ messageList.add(seed, "response");
22154
+ this.seededResponseMessage = true;
22155
+ omDebug(`[OM:step0] seeded response message ${seed.id} for step-0 observation markers`);
22156
+ }
22157
+ if (willObserveNow) {
22066
22158
  const preObsGeneration = this.turn.record.generationCount;
22067
22159
  const obsResult = await this.runThresholdObservation();
22068
22160
  observerExchange = obsResult.observerExchange;
@@ -22076,7 +22168,8 @@ var ObservationStep = class {
22076
22168
  resourceId,
22077
22169
  messages: messageList,
22078
22170
  observedMessageIds: observedIds,
22079
- retentionFloor: minRemaining
22171
+ retentionFloor: minRemaining,
22172
+ preserveMessageIds: step0PreserveIds
22080
22173
  });
22081
22174
  if (statusSnapshot.asyncObservationEnabled) await om.resetBufferingState({
22082
22175
  threadId,
@@ -22091,7 +22184,7 @@ var ObservationStep = class {
22091
22184
  statusSnapshot = await om.getStatus({
22092
22185
  threadId,
22093
22186
  resourceId,
22094
- messages: messageList.get.all.db()
22187
+ messages: getObservableMessages(messageList)
22095
22188
  });
22096
22189
  }
22097
22190
  const otherThreadsContext = await this.turn.refreshOtherThreadsContext();
@@ -22140,10 +22233,11 @@ var ObservationStep = class {
22140
22233
  const { threadId, resourceId, messageList } = this.turn;
22141
22234
  const om = this.turn.om;
22142
22235
  await om.waitForBuffering(threadId, resourceId);
22236
+ const observableMessages = this.seededResponseMessage ? getObservableMessages(messageList).filter((msg) => msg.id !== this.turn.responseMessageId) : getObservableMessages(messageList);
22143
22237
  const freshStatus = await om.getStatus({
22144
22238
  threadId,
22145
22239
  resourceId,
22146
- messages: messageList.get.all.db()
22240
+ messages: observableMessages
22147
22241
  });
22148
22242
  if (!freshStatus.shouldObserve) return {
22149
22243
  succeeded: false,
@@ -22153,7 +22247,7 @@ var ObservationStep = class {
22153
22247
  const activation = await om.activate({
22154
22248
  threadId,
22155
22249
  resourceId,
22156
- messages: messageList.get.all.db(),
22250
+ messages: observableMessages,
22157
22251
  currentModel: this.turn.actorModelContext,
22158
22252
  writer: this.turn.writer,
22159
22253
  messageList
@@ -22169,7 +22263,7 @@ var ObservationStep = class {
22169
22263
  currentModel: this.turn.actorModelContext,
22170
22264
  requestContext: this.turn.requestContext,
22171
22265
  observabilityContext: this.turn.observabilityContext,
22172
- lastActivityAt: getLastActivityFromMessages(messageList.get.all.db()),
22266
+ lastActivityAt: getLastActivityFromMessages(getObservableMessages(messageList)),
22173
22267
  reflectionHooks: om.composeHooks(void 0, {
22174
22268
  threadId,
22175
22269
  resourceId,
@@ -22186,7 +22280,8 @@ var ObservationStep = class {
22186
22280
  const obsResult = await om.observe({
22187
22281
  threadId,
22188
22282
  resourceId,
22189
- messages: messageList.get.all.db(),
22283
+ messages: observableMessages,
22284
+ messageList,
22190
22285
  trigger: "turn-sync",
22191
22286
  requestContext: this.turn.requestContext,
22192
22287
  writer: this.turn.writer,
@@ -22194,7 +22289,7 @@ var ObservationStep = class {
22194
22289
  });
22195
22290
  if (obsResult.observed) {
22196
22291
  const observedMessageIds = new Set(obsResult.record.observedMessageIds ?? []);
22197
- const liveMessages = messageList.get.all.db();
22292
+ const liveMessages = getObservableMessages(messageList);
22198
22293
  let latestObservedIndex = -1;
22199
22294
  for (let i = liveMessages.length - 1; i >= 0; i--) {
22200
22295
  const message = liveMessages[i];
@@ -22203,10 +22298,12 @@ var ObservationStep = class {
22203
22298
  break;
22204
22299
  }
22205
22300
  }
22206
- const messageToSeal = latestObservedIndex >= 0 ? liveMessages[latestObservedIndex] : void 0;
22301
+ let messageToSeal = latestObservedIndex >= 0 ? liveMessages[latestObservedIndex] : void 0;
22302
+ if (this.stepNumber === 0 && messageToSeal?.role !== "assistant") messageToSeal = void 0;
22207
22303
  const messagesToSeal = messageToSeal ? [messageToSeal] : [];
22208
22304
  om.sealMessagesForBuffering(messagesToSeal);
22209
- try {
22305
+ if (this.seededResponseMessage) omDebug("[OM:observe] skipping response-id rotation — step-0 seeded response message holds the active id");
22306
+ else try {
22210
22307
  await this.turn.hooks?.onSyncObservationComplete?.();
22211
22308
  } catch (error) {
22212
22309
  omDebug(`[OM:observe] onSyncObservationComplete hook failed: ${error instanceof Error ? error.message : String(error)}`);
@@ -22267,6 +22364,8 @@ var ObservationTurn = class {
22267
22364
  sendSignal;
22268
22365
  /** Current actor model for this step. Updated by the processor before prepare(). */
22269
22366
  actorModelContext;
22367
+ /** The active assistant response message ID for this step. Updated by the processor before prepare(). */
22368
+ responseMessageId;
22270
22369
  /** Processor-provided hooks for turn/step lifecycle integration. */
22271
22370
  hooks;
22272
22371
  constructor(opts) {
@@ -22365,7 +22464,7 @@ var ObservationTurn = class {
22365
22464
  const asyncObservationEnabled = this.om.buffering.isAsyncObservationEnabled();
22366
22465
  const bufferOnIdle = this.om.getObservationConfig().bufferOnIdle;
22367
22466
  if (asyncObservationEnabled && bufferOnIdle) {
22368
- const allMessages = this.messageList.get.all.db();
22467
+ const allMessages = getObservableMessages(this.messageList);
22369
22468
  const record = this._record;
22370
22469
  const unobservedMessages = this.om.getUnobservedMessages(allMessages, record);
22371
22470
  if (unobservedMessages.length > 0) this.om.buffer({
@@ -22735,7 +22834,7 @@ function getCurrentModel$1(model) {
22735
22834
  return formatModelContext$1(model?.provider, model?.modelId);
22736
22835
  }
22737
22836
  function getLastModelFromMessageList(messageList) {
22738
- const messages = messageList?.get.all.db();
22837
+ const messages = messageList ? getObservableMessages(messageList) : void 0;
22739
22838
  if (!messages) return void 0;
22740
22839
  for (let i = messages.length - 1; i >= 0; i--) {
22741
22840
  const message = messages[i];
@@ -24319,7 +24418,7 @@ var ObservationalMemory = class ObservationalMemory {
24319
24418
  */
24320
24419
  async persistMarkerToMessage(marker, messageList, threadId, resourceId) {
24321
24420
  if (!messageList) return;
24322
- const allMsgs = messageList.get.all.db();
24421
+ const allMsgs = getObservableMessages(messageList);
24323
24422
  for (let i = allMsgs.length - 1; i >= 0; i--) {
24324
24423
  const msg = allMsgs[i];
24325
24424
  if (msg?.role === "assistant" && msg.content?.parts && Array.isArray(msg.content.parts)) {
@@ -24935,6 +25034,7 @@ ${formattedMessages}
24935
25034
  */
24936
25035
  async getObservedMessageIdsForCleanup(opts) {
24937
25036
  const { threadId, resourceId, messages, observedMessageIds, retentionFloor } = opts;
25037
+ const preserveSet = opts.preserveMessageIds?.length ? new Set(opts.preserveMessageIds) : null;
24938
25038
  const record = await this.getOrCreateRecord(threadId, resourceId);
24939
25039
  const effectiveObservedIds = observedMessageIds && observedMessageIds.length > 0 ? observedMessageIds : Array.isArray(record.observedMessageIds) ? record.observedMessageIds : [];
24940
25040
  if (effectiveObservedIds.length === 0) return [];
@@ -24946,6 +25046,10 @@ ${formattedMessages}
24946
25046
  const retentionCounter = typeof retentionFloor === "number" ? new TokenCounter() : null;
24947
25047
  for (const msg of messages) {
24948
25048
  if (!msg?.id || msg.id === "om-continuation" || !observedSet.has(msg.id)) continue;
25049
+ if (preserveSet?.has(msg.id)) {
25050
+ skipped += 1;
25051
+ continue;
25052
+ }
24949
25053
  const unobservedParts = getUnobservedParts(msg);
24950
25054
  const totalParts = msg.content?.parts?.length ?? 0;
24951
25055
  if (unobservedParts.length > 0 && unobservedParts.length < totalParts) {
@@ -24990,9 +25094,9 @@ ${formattedMessages}
24990
25094
  */
24991
25095
  /** @internal Used by ObservationStep. */
24992
25096
  async cleanupMessages(opts) {
24993
- const { threadId, resourceId, observedMessageIds, retentionFloor } = opts;
25097
+ const { threadId, resourceId, observedMessageIds, retentionFloor, preserveMessageIds } = opts;
24994
25098
  const messageList = this.isMessageList(opts.messages) ? opts.messages : void 0;
24995
- const allMsgs = messageList ? messageList.get.all.db() : opts.messages;
25099
+ const allMsgs = messageList ? getObservableMessages(messageList) : opts.messages;
24996
25100
  let markerIdx = -1;
24997
25101
  let markerMsg = null;
24998
25102
  for (let i = allMsgs.length - 1; i >= 0; i--) {
@@ -25011,7 +25115,8 @@ ${formattedMessages}
25011
25115
  resourceId,
25012
25116
  messages: allMsgs,
25013
25117
  observedMessageIds,
25014
- retentionFloor
25118
+ retentionFloor,
25119
+ preserveMessageIds
25015
25120
  });
25016
25121
  if (messageList) {
25017
25122
  if (idsToRemoveList.length > 0) messageList.removeByIds(idsToRemoveList);
@@ -25023,18 +25128,21 @@ ${formattedMessages}
25023
25128
  if (markerMsg && markerIdx !== -1) {
25024
25129
  const idsToRemove = [];
25025
25130
  const messagesToSave = [];
25131
+ const preserveSet = preserveMessageIds?.length ? new Set(preserveMessageIds) : null;
25026
25132
  for (let i = 0; i < markerIdx; i++) {
25027
25133
  const msg = allMsgs[i];
25028
- if (msg?.id && msg.id !== "om-continuation") {
25134
+ if (msg?.id && msg.id !== "om-continuation" && !preserveSet?.has(msg.id)) {
25029
25135
  idsToRemove.push(msg.id);
25030
25136
  messagesToSave.push(msg);
25031
25137
  }
25032
25138
  }
25033
25139
  messagesToSave.push(markerMsg);
25034
- const unobservedParts = getUnobservedParts(markerMsg);
25035
- if (unobservedParts.length === 0) {
25036
- if (markerMsg.id) idsToRemove.push(markerMsg.id);
25037
- } else if (unobservedParts.length < (markerMsg.content?.parts?.length ?? 0)) markerMsg.content.parts = unobservedParts;
25140
+ if (!Boolean(markerMsg.id && preserveSet?.has(markerMsg.id))) {
25141
+ const unobservedParts = getUnobservedParts(markerMsg);
25142
+ if (unobservedParts.length === 0) {
25143
+ if (markerMsg.id) idsToRemove.push(markerMsg.id);
25144
+ } else if (unobservedParts.length < (markerMsg.content?.parts?.length ?? 0)) markerMsg.content.parts = unobservedParts;
25145
+ }
25038
25146
  if (messageList) {
25039
25147
  if (idsToRemove.length > 0) messageList.removeByIds(idsToRemove);
25040
25148
  if (messagesToSave.length > 0) await this.persistMessages(messagesToSave, threadId, resourceId);
@@ -25821,6 +25929,7 @@ ${formattedMessages}
25821
25929
  threadId,
25822
25930
  resourceId,
25823
25931
  messages: unobservedMessages,
25932
+ messageList: opts.messageList,
25824
25933
  reflectionHooks,
25825
25934
  agent: opts.agent,
25826
25935
  requestContext,
@@ -26103,7 +26212,7 @@ function isTemporalGapMarkerForMessage(message, targetMessageId) {
26103
26212
  async function insertTemporalGapMarkers({ messageList, sendSignal }) {
26104
26213
  const latestInputMessage = messageList.get.input.db().filter((message) => Boolean(message)).at(-1);
26105
26214
  if (!latestInputMessage || isTemporalGapMarker(latestInputMessage)) return;
26106
- const allMessages = messageList.get.all.db().filter((message) => Boolean(message));
26215
+ const allMessages = getObservableMessages(messageList).filter((message) => Boolean(message));
26107
26216
  const latestInputIndex = allMessages.findIndex((message) => message.id === latestInputMessage.id);
26108
26217
  if (latestInputIndex <= 0) return;
26109
26218
  if (allMessages.some((message) => isTemporalGapMarkerForMessage(message, latestInputMessage.id))) return;
@@ -26210,7 +26319,7 @@ var ObservationalMemoryProcessor = class {
26210
26319
  this.temporalMarkers = options?.temporalMarkers ?? false;
26211
26320
  }
26212
26321
  async processInputStep(args) {
26213
- const { messageList, requestContext, stepNumber, state: _state, writer, model, abortSignal, abort, rotateResponseMessageId } = args;
26322
+ const { messageList, requestContext, stepNumber, state: _state, writer, model, abortSignal, abort, messageId, rotateResponseMessageId } = args;
26214
26323
  const state = _state ?? {};
26215
26324
  omDebug(`[OM:processInputStep:ENTER] step=${stepNumber}, hasMastraMemory=${!!requestContext?.get("MastraMemory")}, hasMemoryInfo=${!!messageList?.serialize()?.memoryInfo?.threadId}`);
26216
26325
  const context = this.engine.getThreadContext(requestContext, messageList);
@@ -26296,6 +26405,7 @@ var ObservationalMemoryProcessor = class {
26296
26405
  state.__omObservabilityContext = observabilityContext;
26297
26406
  this.turn.observabilityContext = observabilityContext;
26298
26407
  this.turn.actorModelContext = actorModelContext;
26408
+ this.turn.responseMessageId = messageId;
26299
26409
  {
26300
26410
  const step = this.turn.step(stepNumber);
26301
26411
  let ctx;
@@ -26326,7 +26436,7 @@ var ObservationalMemoryProcessor = class {
26326
26436
  threadId,
26327
26437
  resourceId
26328
26438
  });
26329
- const allDbMsgs = messageList.get.all.db();
26439
+ const allDbMsgs = getObservableMessages(messageList);
26330
26440
  const tokenCounter = this.engine.getTokenCounter();
26331
26441
  const contextTokens = await tokenCounter.countMessagesAsync(allDbMsgs);
26332
26442
  const otherThreadsContext = this.turn.context.otherThreadsContext;
@@ -26696,6 +26806,18 @@ var Memory = class extends MastraMemory {
26696
26806
  _omEngine;
26697
26807
  _omEngineInstance;
26698
26808
  _mastraInstance;
26809
+ /**
26810
+ * Every vector cleanup that deleteThread or deleteMessages started in the background.
26811
+ * Callers do not wait for the cleanup, so this handle is the only join point.
26812
+ */
26813
+ pendingVectorCleanup = Promise.resolve();
26814
+ /**
26815
+ * Adds a background vector cleanup to the join handle.
26816
+ * The handle keeps the earlier cleanups, so it settles only after all of them end.
26817
+ */
26818
+ trackVectorCleanup(cleanup) {
26819
+ this.pendingVectorCleanup = Promise.allSettled([this.pendingVectorCleanup, cleanup]).then(() => void 0);
26820
+ }
26699
26821
  /** The shared ObservationalMemory engine. Lazily created on first access. */
26700
26822
  get omEngine() {
26701
26823
  if (!this._omEngine) this._omEngine = this._initOMEngine().then((engine) => {
@@ -26960,26 +27082,40 @@ var Memory = class extends MastraMemory {
26960
27082
  const thread = await memoryStore.getThreadById({ threadId });
26961
27083
  await memoryStore.deleteThread({ threadId });
26962
27084
  if (thread?.resourceId && memoryStore.supportsObservationalMemory) await memoryStore.clearObservationalMemory(threadId, thread.resourceId);
26963
- if (this.vector) this.deleteThreadVectors(threadId);
27085
+ if (this.vector) this.trackVectorCleanup(this.deleteThreadVectors(threadId));
26964
27086
  }
26965
27087
  /**
26966
- * Lists all vector indexes that match the memory messages prefix.
26967
- * Handles separator differences across vector store backends (e.g. '_' vs '-').
27088
+ * Prefix shared by every message index. The index for the default embedding
27089
+ * dimension is named with the bare prefix; other dimensions add a suffix.
26968
27090
  */
26969
- async getMemoryVectorIndexes() {
27091
+ get messageIndexPrefix() {
27092
+ return this.getEmbeddingIndexName();
27093
+ }
27094
+ /**
27095
+ * Prefix shared by every observation index. Each observation index adds a dimension suffix.
27096
+ */
27097
+ get observationIndexPrefix() {
27098
+ return `memory${this.vector?.indexSeparator ?? "_"}observations`;
27099
+ }
27100
+ /**
27101
+ * Lists the vector indexes whose name starts with one of the given prefixes.
27102
+ * Index names can carry a dimension suffix, so discovery matches on the prefix.
27103
+ */
27104
+ async getMemoryVectorIndexes(prefixes) {
26970
27105
  if (!this.vector) return [];
26971
- const prefix = `memory${this.vector.indexSeparator ?? "_"}messages`;
26972
- return (await this.vector.listIndexes()).filter((name) => name.startsWith(prefix));
27106
+ return (await this.vector.listIndexes()).filter((name) => prefixes.some((prefix) => name.startsWith(prefix)));
26973
27107
  }
26974
27108
  /**
26975
27109
  * Deletes all vector embeddings associated with a thread.
26976
27110
  * This is called internally by deleteThread to clean up orphaned vectors.
27111
+ * Both message and observation vectors are removed, so no text of the deleted
27112
+ * thread stays reachable through resource-scoped retrieval.
26977
27113
  *
26978
27114
  * @param threadId - The ID of the thread whose vectors should be deleted
26979
27115
  */
26980
27116
  async deleteThreadVectors(threadId) {
26981
27117
  try {
26982
- const memoryIndexes = await this.getMemoryVectorIndexes();
27118
+ const memoryIndexes = await this.getMemoryVectorIndexes([this.messageIndexPrefix, this.observationIndexPrefix]);
26983
27119
  await Promise.all(memoryIndexes.map(async (indexName) => {
26984
27120
  try {
26985
27121
  await this.vector.deleteVectors({
@@ -26987,14 +27123,14 @@ var Memory = class extends MastraMemory {
26987
27123
  filter: { thread_id: threadId }
26988
27124
  });
26989
27125
  } catch {
26990
- this.logger.debug("Failed to delete vectors for thread, skipping", {
27126
+ this.logger.warn("Failed to delete vectors of the deleted thread from index", {
26991
27127
  threadId,
26992
27128
  indexName
26993
27129
  });
26994
27130
  }
26995
27131
  }));
26996
27132
  } catch {
26997
- this.logger.debug("Failed to clean up vectors for thread", { threadId });
27133
+ this.logger.warn("Failed to clean up vectors of the deleted thread", { threadId });
26998
27134
  }
26999
27135
  }
27000
27136
  async updateWorkingMemory({ threadId, resourceId, workingMemory, memoryConfig, observabilityContext }) {
@@ -27667,7 +27803,7 @@ Notes:
27667
27803
  getObservationEmbeddingIndexName(dimensions) {
27668
27804
  const usedDimensions = dimensions ?? 384;
27669
27805
  const separator = this.vector?.indexSeparator ?? "_";
27670
- return `memory${separator}observations${separator}${usedDimensions}`;
27806
+ return `${this.observationIndexPrefix}${separator}${usedDimensions}`;
27671
27807
  }
27672
27808
  async createObservationEmbeddingIndex(dimensions) {
27673
27809
  const usedDimensions = dimensions ?? 384;
@@ -27913,7 +28049,10 @@ Notes:
27913
28049
  tools[name] = tool;
27914
28050
  }
27915
28051
  const omConfig = normalizeObservationalMemoryConfig(mergedConfig.observationalMemory);
27916
- if (omConfig?.retrieval) tools.recall = recallTool(mergedConfig, { retrievalScope: typeof omConfig.retrieval === "object" ? omConfig.retrieval.scope ?? "resource" : "resource" });
28052
+ if (omConfig?.retrieval) tools.recall = recallTool(mergedConfig, {
28053
+ retrievalScope: typeof omConfig.retrieval === "object" ? omConfig.retrieval.scope ?? "resource" : "resource",
28054
+ searchEnabled: this.hasRetrievalSearch(omConfig.retrieval)
28055
+ });
27917
28056
  return tools;
27918
28057
  }
27919
28058
  /**
@@ -27969,7 +28108,7 @@ Notes:
27969
28108
  }));
27970
28109
  const messageIdsNeedingDeletion = /* @__PURE__ */ new Set([...messageIdsWithClearedContent, ...messageIdsWithNewEmbeddings]);
27971
28110
  if (messageIdsNeedingDeletion.size > 0) try {
27972
- const memoryIndexes = await this.getMemoryVectorIndexes();
28111
+ const memoryIndexes = await this.getMemoryVectorIndexes([this.messageIndexPrefix]);
27973
28112
  const idsToDelete = [...messageIdsNeedingDeletion];
27974
28113
  await Promise.all(memoryIndexes.map(async (indexName) => {
27975
28114
  for (let i = 0; i < idsToDelete.length; i += VECTOR_DELETE_BATCH_SIZE) {
@@ -28028,7 +28167,7 @@ Notes:
28028
28167
  const span = this.createMemorySpan("delete", observabilityContext, void 0, { messageCount: messageIds.length });
28029
28168
  try {
28030
28169
  await (await this.getMemoryStore()).deleteMessages(messageIds);
28031
- if (this.vector) this.deleteMessageVectors(messageIds);
28170
+ if (this.vector) this.trackVectorCleanup(this.deleteMessageVectors(messageIds));
28032
28171
  span?.end({
28033
28172
  output: { success: true },
28034
28173
  attributes: { messageCount: messageIds.length }
@@ -28044,12 +28183,14 @@ Notes:
28044
28183
  /**
28045
28184
  * Deletes vector embeddings for specific messages.
28046
28185
  * This is called internally by deleteMessages to clean up orphaned vectors.
28186
+ * Only the message indexes are touched, because observation vectors can hold
28187
+ * text of other messages of the thread.
28047
28188
  *
28048
28189
  * @param messageIds - The IDs of the messages whose vectors should be deleted
28049
28190
  */
28050
28191
  async deleteMessageVectors(messageIds) {
28051
28192
  try {
28052
- const memoryIndexes = await this.getMemoryVectorIndexes();
28193
+ const memoryIndexes = await this.getMemoryVectorIndexes([this.messageIndexPrefix]);
28053
28194
  await Promise.all(memoryIndexes.map(async (indexName) => {
28054
28195
  for (let i = 0; i < messageIds.length; i += VECTOR_DELETE_BATCH_SIZE) {
28055
28196
  const batch = messageIds.slice(i, i + VECTOR_DELETE_BATCH_SIZE);
@@ -28443,4 +28584,4 @@ Notes:
28443
28584
  //#endregion
28444
28585
  export { extractCurrentTask as A, OBSERVATION_CONTEXT_INSTRUCTIONS as B, WorkingMemoryExtractor as C, OBSERVER_SYSTEM_PROMPT as D, TokenCounter as E, injectAnchorIds as F, OBSERVATION_CONTINUATION_HINT as H, parseAnchorId as I, stripEphemeralAnchorIds as L, hasCurrentTaskSection as M, optimizeObservationsForContext as N, buildObserverPrompt as O, parseObserverOutput as P, Extractor as R, deepMergeWorkingMemory as S, summarizeConversation as T, OBSERVATION_CONTEXT_PROMPT as V, reconcileObservationGroupsFromReflection as _, extractWorkingMemoryContent as a, wrapInObservationGroup as b, WORKING_MEMORY_STATE_ID as c, getObservationsAsOf as d, ObservationalMemoryProcessor as f, parseObservationGroups as g, deriveObservationGroupProvenance as h, WorkingMemory as i, formatMessagesForObserver as j, buildObserverSystemPrompt as k, WORKING_MEMORY_STATE_PROCESSOR_ID as l, combineObservationGroupRanges as m, MessageHistory$1 as n, extractWorkingMemoryTags as o, ObservationalMemory as p, SemanticRecall as r, removeWorkingMemoryTags as s, Memory as t, WorkingMemoryStateProcessor as u, renderObservationGroupsForReflection as v, SUMMARIZE_THREAD_DEFAULTS as w, ModelByInputTokens as x, stripObservationGroups as y, OBSERVATIONAL_MEMORY_DEFAULTS as z };
28445
28586
 
28446
- //# sourceMappingURL=src-Cwt9gefz.js.map
28587
+ //# sourceMappingURL=src-CdqJP57F.js.map