@mastra/memory 1.30.0-alpha.3 → 1.30.0-alpha.4

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 (26) hide show
  1. package/dist/docs/SKILL.md +1 -1
  2. package/dist/docs/assets/SOURCE_MAP.json +1 -1
  3. package/dist/docs/references/docs-guides-context-engineering.md +1 -1
  4. package/dist/docs/references/docs-memory-observational-memory.md +2 -2
  5. package/dist/docs/references/reference-memory-observational-memory.md +3 -2
  6. package/dist/index.cjs +1 -1
  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/observation-turn/load-memory-context.d.ts.map +1 -1
  12. package/dist/processors/observational-memory/observation-turn/safe-buffer-prefix.d.ts +35 -0
  13. package/dist/processors/observational-memory/observation-turn/safe-buffer-prefix.d.ts.map +1 -0
  14. package/dist/processors/observational-memory/observation-turn/step.d.ts.map +1 -1
  15. package/dist/processors/observational-memory/observation-turn/turn.d.ts.map +1 -1
  16. package/dist/processors/observational-memory/observational-memory.d.ts.map +1 -1
  17. package/dist/processors/observational-memory/processor.d.ts.map +1 -1
  18. package/dist/processors/observational-memory/types.d.ts +6 -0
  19. package/dist/processors/observational-memory/types.d.ts.map +1 -1
  20. package/dist/{src-t3Mhge0m.js → src-Dt8oPiQN.js} +101 -33
  21. package/dist/src-Dt8oPiQN.js.map +1 -0
  22. package/dist/{src-CJgJb_MC.cjs → src-oL_dDHN9.cjs} +101 -33
  23. package/dist/src-oL_dDHN9.cjs.map +1 -0
  24. package/package.json +3 -3
  25. package/dist/src-CJgJb_MC.cjs.map +0 -1
  26. package/dist/src-t3Mhge0m.js.map +0 -1
@@ -25288,10 +25288,61 @@ async function loadMemoryContextMessages({ memory, messageList, threadId, resour
25288
25288
  resourceId,
25289
25289
  runState
25290
25290
  });
25291
- for (const msg of ctx.messages) if (msg.role !== "system") messageList.add(msg, "memory");
25291
+ const existingIds = new Set(messageList.get.all.db().map((message) => message.id));
25292
+ for (const msg of ctx.messages) if (msg.role !== "system" && !existingIds.has(msg.id)) messageList.add(msg, "memory");
25292
25293
  return ctx;
25293
25294
  }
25294
25295
  //#endregion
25296
+ //#region src/processors/observational-memory/observation-turn/safe-buffer-prefix.ts
25297
+ function hasPendingToolCall(message) {
25298
+ return message.content.parts?.some((part) => part.type === "tool-invocation" && part.toolInvocation.state === "call") ?? false;
25299
+ }
25300
+ /**
25301
+ * Selects the unobserved messages that are safe to buffer while a tool call
25302
+ * may still be in flight.
25303
+ *
25304
+ * Only the chronologically last candidate can hold a live pending call: a
25305
+ * `tool-invocation` still in state `call` (client- or provider-executed) on the
25306
+ * newest message means the request ended waiting for its result. Any earlier
25307
+ * `call` is an orphan — the conversation already continued past it, and core's
25308
+ * output converter drops or placeholder-pairs it before the prompt reaches a
25309
+ * provider — so it is buffered like any other message.
25310
+ *
25311
+ * When the last candidate is pending, the prefix before it is buffered, but only
25312
+ * when the cut is clean. The cut is unsafe — and the whole attempt is deferred —
25313
+ * when:
25314
+ *
25315
+ * - Cursor collision: buffering advances the persisted cursor to
25316
+ * `max(buffered.createdAt) + 1ms`, and later candidate selection requires
25317
+ * `createdAt > cursor`. If the retained message sits at the same or +1ms
25318
+ * timestamp, the cursor would permanently hide it even after its result
25319
+ * arrives.
25320
+ * - Split tool exchange: a `toolCallId` appears on both sides of the cut, so
25321
+ * the observer would see half of a tool exchange.
25322
+ * - Ambiguous tail: several candidates share the newest timestamp and one of
25323
+ * them holds a pending call. `createdAt` alone cannot say which is really
25324
+ * last, so the pending call is treated as the tail and the attempt deferred.
25325
+ *
25326
+ * The result is always in chronological order. When the tail has no pending
25327
+ * call, every message is returned. An empty result means "defer this buffering
25328
+ * attempt" — callers must skip buffering entirely. Raw message persistence is
25329
+ * unaffected and happens elsewhere; the retained message becomes eligible again
25330
+ * once its tool call completes.
25331
+ */
25332
+ function selectSafeBufferPrefix(messages) {
25333
+ const chronological = [...messages].sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
25334
+ const last = chronological[chronological.length - 1];
25335
+ if (!last) return chronological;
25336
+ const newestTime = new Date(last.createdAt).getTime();
25337
+ const newestGroup = chronological.filter((message) => new Date(message.createdAt).getTime() === newestTime);
25338
+ if (!newestGroup.some(hasPendingToolCall)) return chronological;
25339
+ if (newestGroup.length > 1) return [];
25340
+ const prefix = chronological.slice(0, -1);
25341
+ const retainedTime = new Date(last.createdAt).getTime();
25342
+ const retainedToolIds = new Set((last.content.parts ?? []).flatMap((part) => part.type === "tool-invocation" ? [part.toolInvocation.toolCallId] : []));
25343
+ return prefix.some((message) => new Date(message.createdAt).getTime() + 1 >= retainedTime || message.content.parts?.some((part) => part.type === "tool-invocation" && retainedToolIds.has(part.toolInvocation.toolCallId))) ? [] : prefix;
25344
+ }
25345
+ //#endregion
25295
25346
  //#region src/processors/observational-memory/observation-turn/step.ts
25296
25347
  /**
25297
25348
  * Represents a single step in the agentic loop within an observation turn.
@@ -25414,37 +25465,41 @@ var ObservationStep = class {
25414
25465
  record: this.turn.record,
25415
25466
  messages: getObservableMessages(messageList)
25416
25467
  });
25417
- if (statusSnapshot.shouldBuffer && !hasIncompleteToolCalls) {
25468
+ if (statusSnapshot.shouldBuffer) {
25418
25469
  const allMessages = getObservableMessages(messageList);
25419
25470
  const unobservedMessages = om.getUnobservedMessages(allMessages, statusSnapshot.record);
25420
25471
  const candidates = om.getUnobservedMessages(unobservedMessages, statusSnapshot.record, { excludeBuffered: true });
25421
- if (candidates.length > 0) {
25422
- om.sealMessagesForBuffering(candidates);
25472
+ const safeCandidates = selectSafeBufferPrefix(candidates);
25473
+ const deferred = candidates.length > 0 && safeCandidates.length === 0;
25474
+ if (safeCandidates.length > 0) {
25475
+ om.sealMessagesForBuffering(safeCandidates);
25423
25476
  try {
25424
25477
  await this.turn.hooks?.onBufferChunkSealed?.();
25425
25478
  } catch (error) {
25426
25479
  omDebug(`[OM:buffer] onBufferChunkSealed hook failed: ${error instanceof Error ? error.message : String(error)}`);
25427
25480
  }
25428
- if (this.turn.memory) await this.turn.memory.persistMessages(candidates);
25429
- messageList.removeByIds(candidates.map((msg) => msg.id));
25430
- for (const msg of candidates) messageList.add(msg, "memory");
25481
+ if (this.turn.memory) await this.turn.memory.persistMessages(safeCandidates);
25482
+ messageList.removeByIds(safeCandidates.map((msg) => msg.id));
25483
+ for (const msg of safeCandidates) messageList.add(msg, "memory");
25484
+ }
25485
+ if (!deferred) {
25486
+ om.trackBackgroundWork(om.buffer({
25487
+ threadId,
25488
+ resourceId,
25489
+ messages: safeCandidates,
25490
+ pendingTokens: statusSnapshot.pendingTokens,
25491
+ record: statusSnapshot.record,
25492
+ writer: this.turn.writer,
25493
+ agent: this.turn.agent,
25494
+ sendSignal: this.turn.sendSignal,
25495
+ sendStateSignal: this.turn.sendStateSignal,
25496
+ requestContext: this.turn.requestContext,
25497
+ observabilityContext: this.turn.observabilityContext
25498
+ }).catch((err) => {
25499
+ omDebug(`[OM:buffer] fire-and-forget buffer failed: ${err?.message}`);
25500
+ }));
25501
+ buffered = true;
25431
25502
  }
25432
- om.trackBackgroundWork(om.buffer({
25433
- threadId,
25434
- resourceId,
25435
- messages: unobservedMessages,
25436
- pendingTokens: statusSnapshot.pendingTokens,
25437
- record: statusSnapshot.record,
25438
- writer: this.turn.writer,
25439
- agent: this.turn.agent,
25440
- sendSignal: this.turn.sendSignal,
25441
- sendStateSignal: this.turn.sendStateSignal,
25442
- requestContext: this.turn.requestContext,
25443
- observabilityContext: this.turn.observabilityContext
25444
- }).catch((err) => {
25445
- omDebug(`[OM:buffer] fire-and-forget buffer failed: ${err?.message}`);
25446
- }));
25447
- buffered = true;
25448
25503
  }
25449
25504
  const willObserveNow = statusSnapshot.shouldObserve && !hasIncompleteToolCalls;
25450
25505
  /** In-flight message ids the step-0 cleanup must never remove from live context. */
@@ -25819,11 +25874,11 @@ var ObservationTurn = class {
25819
25874
  if (asyncObservationEnabled && bufferOnIdle) {
25820
25875
  const allMessages = getObservableMessages(this.messageList);
25821
25876
  const record = this._record;
25822
- const unobservedMessages = this.om.getUnobservedMessages(allMessages, record);
25823
- if (unobservedMessages.length > 0) this.om.trackBackgroundWork(this.om.buffer({
25877
+ const idleMessages = selectSafeBufferPrefix(this.om.getUnobservedMessages(allMessages, record));
25878
+ if (idleMessages.length > 0) this.om.trackBackgroundWork(this.om.buffer({
25824
25879
  threadId: this.threadId,
25825
25880
  resourceId: this.resourceId,
25826
- messages: unobservedMessages,
25881
+ messages: idleMessages,
25827
25882
  record,
25828
25883
  writer: this.writer,
25829
25884
  agent: this.agent,
@@ -27551,15 +27606,18 @@ var ObservationalMemory = class ObservationalMemory {
27551
27606
  this.hookExecution = config.hookExecution ?? "non-blocking";
27552
27607
  this.mastra = config.mastra;
27553
27608
  this.memory = config.memory;
27609
+ const topLevelModel = config.model;
27610
+ const observationConfigModel = config.observation?.model;
27611
+ const reflectionConfigModel = config.reflection?.model;
27554
27612
  const resolveModel = (model, defaultModel) => model === "default" ? defaultModel : model;
27555
- const observationModel = resolveModel(config.model, OBSERVATIONAL_MEMORY_DEFAULTS.observation.model) ?? resolveModel(config.observation?.model, OBSERVATIONAL_MEMORY_DEFAULTS.observation.model) ?? resolveModel(config.reflection?.model, OBSERVATIONAL_MEMORY_DEFAULTS.observation.model) ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.model;
27556
- const reflectionModel = resolveModel(config.model, OBSERVATIONAL_MEMORY_DEFAULTS.reflection.model) ?? resolveModel(config.reflection?.model, OBSERVATIONAL_MEMORY_DEFAULTS.reflection.model) ?? resolveModel(config.observation?.model, OBSERVATIONAL_MEMORY_DEFAULTS.reflection.model) ?? OBSERVATIONAL_MEMORY_DEFAULTS.reflection.model;
27613
+ const observationModel = resolveModel(topLevelModel, OBSERVATIONAL_MEMORY_DEFAULTS.observation.model) ?? resolveModel(observationConfigModel, OBSERVATIONAL_MEMORY_DEFAULTS.observation.model) ?? resolveModel(reflectionConfigModel, OBSERVATIONAL_MEMORY_DEFAULTS.observation.model) ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.model;
27614
+ const reflectionModel = resolveModel(topLevelModel, OBSERVATIONAL_MEMORY_DEFAULTS.reflection.model) ?? resolveModel(reflectionConfigModel, OBSERVATIONAL_MEMORY_DEFAULTS.reflection.model) ?? resolveModel(observationConfigModel, OBSERVATIONAL_MEMORY_DEFAULTS.reflection.model) ?? OBSERVATIONAL_MEMORY_DEFAULTS.reflection.model;
27557
27615
  const messageTokens = config.observation?.messageTokens ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.messageTokens;
27558
27616
  const observationTokens = config.reflection?.observationTokens ?? OBSERVATIONAL_MEMORY_DEFAULTS.reflection.observationTokens;
27559
27617
  const isSharedBudget = config.shareTokenBudget ?? false;
27560
27618
  const isDefaultModelSelection = (model) => model === void 0 || model === "default" || model instanceof ModelByInputTokens;
27561
- const observationSelectedModel = config.model ?? config.observation?.model ?? config.reflection?.model;
27562
- const reflectionSelectedModel = config.model ?? config.reflection?.model ?? config.observation?.model;
27619
+ const observationSelectedModel = topLevelModel ?? observationConfigModel ?? reflectionConfigModel;
27620
+ const reflectionSelectedModel = topLevelModel ?? reflectionConfigModel ?? observationConfigModel;
27563
27621
  const observationDefaultMaxOutputTokens = config.observation?.modelSettings?.maxOutputTokens ?? (isDefaultModelSelection(observationSelectedModel) ? OBSERVATIONAL_MEMORY_DEFAULTS.observation.modelSettings.maxOutputTokens : void 0);
27564
27622
  const reflectionDefaultMaxOutputTokens = config.reflection?.modelSettings?.maxOutputTokens ?? (isDefaultModelSelection(reflectionSelectedModel) ? OBSERVATIONAL_MEMORY_DEFAULTS.reflection.modelSettings.maxOutputTokens : void 0);
27565
27623
  const totalBudget = messageTokens + observationTokens;
@@ -30279,6 +30337,16 @@ function normalizeObservationalMemoryConfig(config) {
30279
30337
  if (typeof config === "object" && config.enabled === false) return void 0;
30280
30338
  return config;
30281
30339
  }
30340
+ /**
30341
+ * Observer model selection (`observation.model`, else top-level `model`), read into the widened
30342
+ * model type first: combining values of the public type makes TS subtype-reduce the model-id
30343
+ * literal union, which fails with TS2590 once the provider registry is large enough.
30344
+ */
30345
+ function selectObserverModel(omConfig) {
30346
+ const observationModel = omConfig.observation?.model;
30347
+ const topLevelModel = omConfig.model;
30348
+ return observationModel ?? topLevelModel;
30349
+ }
30282
30350
  function hasWorkingMemoryExtractor(extractors) {
30283
30351
  return !!extractors?.some((extractor) => extractor.slug === "working-memory");
30284
30352
  }
@@ -30396,7 +30464,7 @@ var Memory = class Memory extends _mastra_core_memory.MastraMemory {
30396
30464
  const extract = observation.extract ?? [];
30397
30465
  const existingSlugs = new Set(extract.map((extractor) => extractor.slug));
30398
30466
  let curatorMemory;
30399
- const subconsciousExtractors = omConfig.experimental_subconscious.createObservationExtractors(observation.model ?? omConfig.model, () => curatorMemory ??= new Memory({
30467
+ const subconsciousExtractors = omConfig.experimental_subconscious.createObservationExtractors(selectObserverModel(omConfig), () => curatorMemory ??= new Memory({
30400
30468
  storage: this.storage,
30401
30469
  options: { observationalMemory: false }
30402
30470
  })).filter((extractor) => !existingSlugs.has(extractor.slug));
@@ -31702,7 +31770,7 @@ Notes:
31702
31770
  if (remind && "builtIn" in remind) tools.ask_memory = createAskMemoryTool({
31703
31771
  memory: this,
31704
31772
  config: remind,
31705
- omModel: omConfig.observation?.model ?? omConfig.model,
31773
+ omModel: selectObserverModel(omConfig),
31706
31774
  getParentAgent: (agentId) => this._mastraInstance?.getAgentById(agentId)
31707
31775
  });
31708
31776
  }
@@ -32662,4 +32730,4 @@ Object.defineProperty(exports, "wrapInObservationGroup", {
32662
32730
  }
32663
32731
  });
32664
32732
 
32665
- //# sourceMappingURL=src-CJgJb_MC.cjs.map
32733
+ //# sourceMappingURL=src-oL_dDHN9.cjs.map