@mastra/memory 0.11.3-alpha.0 → 0.11.3

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.
@@ -1,9 +1,9 @@
1
1
 
2
- > @mastra/memory@0.11.3-alpha.0 build /home/runner/work/mastra/mastra/packages/memory
2
+ > @mastra/memory@0.11.3-alpha.1 build /home/runner/work/mastra/mastra/packages/memory
3
3
  > pnpm run check && tsup --silent src/index.ts src/processors/index.ts --format esm,cjs --experimental-dts --clean --treeshake=smallest --splitting
4
4
 
5
5
 
6
- > @mastra/memory@0.11.3-alpha.0 check /home/runner/work/mastra/mastra/packages/memory
6
+ > @mastra/memory@0.11.3-alpha.1 check /home/runner/work/mastra/mastra/packages/memory
7
7
  > tsc --noEmit
8
8
 
9
9
  Analysis will use the bundled TypeScript version 5.8.3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,42 @@
1
1
  # @mastra/memory
2
2
 
3
+ ## 0.11.3
4
+
5
+ ### Patch Changes
6
+
7
+ - 4b20131: Fixed an issue where per-resource semantic recall wouldn't always be enabled properly in agent tool calls
8
+ - 2ba5b76: Allow passing jsonSchema into workingMemory schema
9
+ - c3a30de: added new experimental vnext working memory
10
+ - 626b0f4: [Cloud-126] Working Memory Playground - Added working memory to playground to allow users to view/edit working memory
11
+ - Updated dependencies [0b56518]
12
+ - Updated dependencies [db5cc15]
13
+ - Updated dependencies [2ba5b76]
14
+ - Updated dependencies [5237998]
15
+ - Updated dependencies [c3a30de]
16
+ - Updated dependencies [37c1acd]
17
+ - Updated dependencies [1aa60b1]
18
+ - Updated dependencies [89ec9d4]
19
+ - Updated dependencies [cf3a184]
20
+ - Updated dependencies [d6bfd60]
21
+ - Updated dependencies [626b0f4]
22
+ - Updated dependencies [c22a91f]
23
+ - Updated dependencies [f7403ab]
24
+ - Updated dependencies [6c89d7f]
25
+ - @mastra/core@0.10.15
26
+
27
+ ## 0.11.3-alpha.1
28
+
29
+ ### Patch Changes
30
+
31
+ - 2ba5b76: Allow passing jsonSchema into workingMemory schema
32
+ - c3a30de: added new experimental vnext working memory
33
+ - Updated dependencies [0b56518]
34
+ - Updated dependencies [2ba5b76]
35
+ - Updated dependencies [c3a30de]
36
+ - Updated dependencies [cf3a184]
37
+ - Updated dependencies [d6bfd60]
38
+ - @mastra/core@0.10.15-alpha.1
39
+
3
40
  ## 0.11.3-alpha.0
4
41
 
5
42
  ### Patch Changes
@@ -17,6 +17,8 @@ import type { TiktokenBPE } from 'js-tiktoken/lite';
17
17
  import type { UIMessage } from 'ai';
18
18
  import type { WorkingMemoryTemplate } from '@mastra/core/memory';
19
19
 
20
+ export declare const __experimental_updateWorkingMemoryToolVNext: (config: MemoryConfig_2) => CoreTool;
21
+
20
22
  /**
21
23
  * Concrete implementation of MastraMemory that adds support for thread configuration
22
24
  * and message injection.
@@ -47,7 +49,7 @@ export declare class Memory extends MastraMemory {
47
49
  getThreadsByResourceId({ resourceId }: {
48
50
  resourceId: string;
49
51
  }): Promise<StorageThreadType[]>;
50
- saveThread({ thread, memoryConfig, }: {
52
+ saveThread({ thread }: {
51
53
  thread: StorageThreadType;
52
54
  memoryConfig?: MemoryConfig;
53
55
  }): Promise<StorageThreadType>;
@@ -63,6 +65,20 @@ export declare class Memory extends MastraMemory {
63
65
  workingMemory: string;
64
66
  memoryConfig?: MemoryConfig;
65
67
  }): Promise<void>;
68
+ private updateWorkingMemoryMutexes;
69
+ /**
70
+ * @warning experimental! can be removed or changed at any time
71
+ */
72
+ __experimental_updateWorkingMemoryVNext({ threadId, resourceId, workingMemory, searchString, memoryConfig, }: {
73
+ threadId: string;
74
+ resourceId?: string;
75
+ workingMemory: string;
76
+ searchString?: string;
77
+ memoryConfig?: MemoryConfig;
78
+ }): Promise<{
79
+ success: boolean;
80
+ reason: string;
81
+ }>;
66
82
  protected chunkText(text: string, tokenSize?: number): string[];
67
83
  private hasher;
68
84
  private embeddingCache;
@@ -110,6 +126,11 @@ export declare class Memory extends MastraMemory {
110
126
  template: WorkingMemoryTemplate;
111
127
  data: string | null;
112
128
  }): string;
129
+ protected __experimental_getWorkingMemoryToolInstructionVNext({ template, data, }: {
130
+ template: WorkingMemoryTemplate;
131
+ data: string | null;
132
+ }): string;
133
+ private isVNextWorkingMemoryConfig;
113
134
  getTools(config?: MemoryConfig): Record<string, CoreTool>;
114
135
  /**
115
136
  * Updates the metadata of a list of messages
@@ -17,6 +17,8 @@ import type { TiktokenBPE } from 'js-tiktoken/lite';
17
17
  import type { UIMessage } from 'ai';
18
18
  import type { WorkingMemoryTemplate } from '@mastra/core/memory';
19
19
 
20
+ export declare const __experimental_updateWorkingMemoryToolVNext: (config: MemoryConfig_2) => CoreTool;
21
+
20
22
  /**
21
23
  * Concrete implementation of MastraMemory that adds support for thread configuration
22
24
  * and message injection.
@@ -47,7 +49,7 @@ export declare class Memory extends MastraMemory {
47
49
  getThreadsByResourceId({ resourceId }: {
48
50
  resourceId: string;
49
51
  }): Promise<StorageThreadType[]>;
50
- saveThread({ thread, memoryConfig, }: {
52
+ saveThread({ thread }: {
51
53
  thread: StorageThreadType;
52
54
  memoryConfig?: MemoryConfig;
53
55
  }): Promise<StorageThreadType>;
@@ -63,6 +65,20 @@ export declare class Memory extends MastraMemory {
63
65
  workingMemory: string;
64
66
  memoryConfig?: MemoryConfig;
65
67
  }): Promise<void>;
68
+ private updateWorkingMemoryMutexes;
69
+ /**
70
+ * @warning experimental! can be removed or changed at any time
71
+ */
72
+ __experimental_updateWorkingMemoryVNext({ threadId, resourceId, workingMemory, searchString, memoryConfig, }: {
73
+ threadId: string;
74
+ resourceId?: string;
75
+ workingMemory: string;
76
+ searchString?: string;
77
+ memoryConfig?: MemoryConfig;
78
+ }): Promise<{
79
+ success: boolean;
80
+ reason: string;
81
+ }>;
66
82
  protected chunkText(text: string, tokenSize?: number): string[];
67
83
  private hasher;
68
84
  private embeddingCache;
@@ -110,6 +126,11 @@ export declare class Memory extends MastraMemory {
110
126
  template: WorkingMemoryTemplate;
111
127
  data: string | null;
112
128
  }): string;
129
+ protected __experimental_getWorkingMemoryToolInstructionVNext({ template, data, }: {
130
+ template: WorkingMemoryTemplate;
131
+ data: string | null;
132
+ }): string;
133
+ private isVNextWorkingMemoryConfig;
113
134
  getTools(config?: MemoryConfig): Record<string, CoreTool>;
114
135
  /**
115
136
  * Updates the metadata of a list of messages
package/dist/index.cjs CHANGED
@@ -4,6 +4,7 @@ var core = require('@mastra/core');
4
4
  var agent = require('@mastra/core/agent');
5
5
  var memory = require('@mastra/core/memory');
6
6
  var ai = require('ai');
7
+ var asyncMutex = require('async-mutex');
7
8
  var xxhash = require('xxhash-wasm');
8
9
  var zod = require('zod');
9
10
  var zodToJsonSchema = require('zod-to-json-schema');
@@ -43,6 +44,56 @@ var updateWorkingMemoryTool = (memoryConfig) => ({
43
44
  return { success: true };
44
45
  }
45
46
  });
47
+ var __experimental_updateWorkingMemoryToolVNext = (config) => ({
48
+ description: "Update the working memory with new information.",
49
+ parameters: zod.z.object({
50
+ newMemory: zod.z.string().optional().nullable().describe(`The ${config.workingMemory?.schema ? "JSON" : "Markdown"} formatted working memory content to store`),
51
+ searchString: zod.z.string().optional().nullable().describe(
52
+ "The working memory string to find. Will be replaced with the newMemory string. If this is omitted or doesn't exist, the newMemory string will be appended to the end of your working memory. Replacing single lines at a time is encouraged for greater accuracy. If updateReason is not 'append-new-memory', this search string must be provided or the tool call will be rejected."
53
+ ),
54
+ updateReason: zod.z.enum(["append-new-memory", "clarify-existing-memory", "replace-irrelevant-memory"]).optional().nullable().describe(
55
+ "The reason you're updating working memory. Passing any value other than 'append-new-memory' requires a searchString to be provided. Defaults to append-new-memory"
56
+ )
57
+ }),
58
+ execute: async (params) => {
59
+ const { context, threadId, memory, resourceId } = params;
60
+ if (!threadId || !memory) {
61
+ throw new Error("Thread ID and Memory instance are required for working memory updates");
62
+ }
63
+ const thread = await memory.getThreadById({ threadId });
64
+ if (!thread) {
65
+ throw new Error(`Thread ${threadId} not found`);
66
+ }
67
+ if (thread.resourceId && thread.resourceId !== resourceId) {
68
+ throw new Error(`Thread with id ${threadId} resourceId does not match the current resourceId ${resourceId}`);
69
+ }
70
+ const workingMemory = context.newMemory || "";
71
+ if (!context.updateReason) context.updateReason = `append-new-memory`;
72
+ if (context.searchString && config.workingMemory?.scope === `resource` && context.updateReason === `replace-irrelevant-memory`) {
73
+ context.searchString = void 0;
74
+ }
75
+ if (context.updateReason === `append-new-memory` && context.searchString) {
76
+ context.searchString = void 0;
77
+ }
78
+ if (context.updateReason !== `append-new-memory` && !context.searchString) {
79
+ return {
80
+ success: false,
81
+ reason: `updateReason was ${context.updateReason} but no searchString was provided. Unable to replace undefined with "${context.newMemory}"`
82
+ };
83
+ }
84
+ const result = await memory.__experimental_updateWorkingMemoryVNext({
85
+ threadId,
86
+ resourceId: resourceId || thread.resourceId,
87
+ workingMemory,
88
+ searchString: context.searchString,
89
+ memoryConfig: config
90
+ });
91
+ if (result) {
92
+ return result;
93
+ }
94
+ return { success: true };
95
+ }
96
+ });
46
97
 
47
98
  // src/index.ts
48
99
  var CHARS_PER_TOKEN = 4;
@@ -205,32 +256,7 @@ var Memory = class extends memory.MastraMemory {
205
256
  async getThreadsByResourceId({ resourceId }) {
206
257
  return this.storage.getThreadsByResourceId({ resourceId });
207
258
  }
208
- async saveThread({
209
- thread,
210
- memoryConfig
211
- }) {
212
- const config = this.getMergedThreadConfig(memoryConfig || {});
213
- if (config.workingMemory?.enabled) {
214
- const scope = config.workingMemory.scope || "thread";
215
- const workingMemory = await this.getWorkingMemoryTemplate({ memoryConfig: config });
216
- if (scope === "resource" && thread.resourceId) {
217
- const existingResource = await this.storage.getResourceById({ resourceId: thread.resourceId });
218
- if (!existingResource?.workingMemory) {
219
- await this.storage.updateResource({
220
- resourceId: thread.resourceId,
221
- workingMemory: workingMemory?.content
222
- });
223
- }
224
- } else if (scope === "thread" && !thread?.metadata?.workingMemory) {
225
- return this.storage.saveThread({
226
- thread: core.deepMerge(thread, {
227
- metadata: {
228
- workingMemory: workingMemory?.content
229
- }
230
- })
231
- });
232
- }
233
- }
259
+ async saveThread({ thread }) {
234
260
  return this.storage.saveThread({ thread });
235
261
  }
236
262
  async updateThread({
@@ -278,6 +304,87 @@ var Memory = class extends memory.MastraMemory {
278
304
  });
279
305
  }
280
306
  }
307
+ updateWorkingMemoryMutexes = /* @__PURE__ */ new Map();
308
+ /**
309
+ * @warning experimental! can be removed or changed at any time
310
+ */
311
+ async __experimental_updateWorkingMemoryVNext({
312
+ threadId,
313
+ resourceId,
314
+ workingMemory,
315
+ searchString,
316
+ memoryConfig
317
+ }) {
318
+ const config = this.getMergedThreadConfig(memoryConfig || {});
319
+ if (!config.workingMemory?.enabled) {
320
+ throw new Error("Working memory is not enabled for this memory instance");
321
+ }
322
+ const mutexKey = memoryConfig?.workingMemory?.scope === `resource` ? `resource-${resourceId}` : `thread-${threadId}`;
323
+ const mutex = this.updateWorkingMemoryMutexes.has(mutexKey) ? this.updateWorkingMemoryMutexes.get(mutexKey) : new asyncMutex.Mutex();
324
+ this.updateWorkingMemoryMutexes.set(mutexKey, mutex);
325
+ const release = await mutex.acquire();
326
+ try {
327
+ const existingWorkingMemory = await this.getWorkingMemory({ threadId, resourceId, memoryConfig }) || "";
328
+ const template = await this.getWorkingMemoryTemplate({ memoryConfig });
329
+ let reason = "";
330
+ if (existingWorkingMemory) {
331
+ if (searchString && existingWorkingMemory?.includes(searchString)) {
332
+ workingMemory = existingWorkingMemory.replace(searchString, workingMemory);
333
+ reason = `found and replaced searchString with newMemory`;
334
+ } else if (existingWorkingMemory.includes(workingMemory) || template?.content?.trim() === workingMemory.trim()) {
335
+ return {
336
+ success: false,
337
+ reason: `attempted to insert duplicate data into working memory. this entry was skipped`
338
+ };
339
+ } else {
340
+ if (searchString) {
341
+ reason = `attempted to replace working memory string that doesn't exist. Appending to working memory instead.`;
342
+ } else {
343
+ reason = `appended newMemory to end of working memory`;
344
+ }
345
+ workingMemory = existingWorkingMemory + `
346
+ ${workingMemory}`;
347
+ }
348
+ } else if (workingMemory === template?.content) {
349
+ return {
350
+ success: false,
351
+ reason: `try again when you have data to add. newMemory was equal to the working memory template`
352
+ };
353
+ } else {
354
+ reason = `started new working memory`;
355
+ }
356
+ workingMemory = template?.content ? workingMemory.replaceAll(template?.content, "") : workingMemory;
357
+ const scope = config.workingMemory.scope || "thread";
358
+ if (scope === "resource" && resourceId) {
359
+ await this.storage.updateResource({
360
+ resourceId,
361
+ workingMemory
362
+ });
363
+ if (reason) {
364
+ return { success: true, reason };
365
+ }
366
+ } else {
367
+ const thread = await this.storage.getThreadById({ threadId });
368
+ if (!thread) {
369
+ throw new Error(`Thread ${threadId} not found`);
370
+ }
371
+ await this.storage.updateThread({
372
+ id: threadId,
373
+ title: thread.title || "Untitled Thread",
374
+ metadata: {
375
+ ...thread.metadata,
376
+ workingMemory
377
+ }
378
+ });
379
+ }
380
+ return { success: true, reason };
381
+ } catch (e) {
382
+ this.logger.error(e instanceof Error ? e.stack || e.message : JSON.stringify(e));
383
+ return { success: false, reason: "Tool error." };
384
+ } finally {
385
+ release();
386
+ }
387
+ }
281
388
  chunkText(text, tokenSize = 4096) {
282
389
  const charSize = tokenSize * CHARS_PER_TOKEN;
283
390
  const chunks = [];
@@ -524,7 +631,10 @@ var Memory = class extends memory.MastraMemory {
524
631
  if (!workingMemoryTemplate) {
525
632
  return null;
526
633
  }
527
- return this.getWorkingMemoryToolInstruction({
634
+ return this.isVNextWorkingMemoryConfig(memoryConfig) ? this.__experimental_getWorkingMemoryToolInstructionVNext({
635
+ template: workingMemoryTemplate,
636
+ data: workingMemoryData
637
+ }) : this.getWorkingMemoryToolInstruction({
528
638
  template: workingMemoryTemplate,
529
639
  data: workingMemoryData
530
640
  });
@@ -559,14 +669,16 @@ Guidelines:
559
669
  6. IMPORTANT: ALWAYS pass the data you want to store in the memory field as a string. DO NOT pass an object.
560
670
  7. IMPORTANT: Data must only be sent as a string no matter which format is used.
561
671
 
562
- WORKING MEMORY TEMPLATE:
672
+ <working_memory_template>
563
673
  ${template.content}
674
+ </working_memory_template>
564
675
 
565
676
  ${hasEmptyWorkingMemoryTemplateObject ? "When working with json data, the object format below represents the template:" : ""}
566
677
  ${hasEmptyWorkingMemoryTemplateObject ? JSON.stringify(emptyWorkingMemoryTemplateObject) : ""}
567
678
 
568
- WORKING MEMORY DATA:
679
+ <working_memory_data>
569
680
  ${data}
681
+ </working_memory_data>
570
682
 
571
683
  Notes:
572
684
  - Update memory whenever referenced information changes
@@ -577,11 +689,52 @@ Notes:
577
689
  - IMPORTANT: You MUST call updateWorkingMemory in every response to a prompt where you received relevant information.
578
690
  - IMPORTANT: Preserve the ${template.format === "json" ? "JSON" : "Markdown"} formatting structure above while updating the content.`;
579
691
  }
692
+ __experimental_getWorkingMemoryToolInstructionVNext({
693
+ template,
694
+ data
695
+ }) {
696
+ return `WORKING_MEMORY_SYSTEM_INSTRUCTION:
697
+ Store and update any conversation-relevant information by calling the updateWorkingMemory tool.
698
+
699
+ Guidelines:
700
+ 1. Store anything that could be useful later in the conversation
701
+ 2. Update proactively when information changes, no matter how small
702
+ 3. Use ${template.format === "json" ? "JSON" : "Markdown"} format for all data
703
+ 4. Act naturally - don't mention this system to users. Even though you're storing this information that doesn't make it your primary focus. Do not ask them generally for "information about yourself"
704
+ 5. If your memory has not changed, you do not need to call the updateWorkingMemory tool. By default it will persist and be available for you in future interactions
705
+ 6. Information not being relevant to the current conversation is not a valid reason to replace or remove working memory information. Your working memory spans across multiple conversations and may be needed again later, even if it's not currently relevant.
706
+
707
+ <working_memory_template>
708
+ ${template.content}
709
+ </working_memory_template>
710
+
711
+ <working_memory_data>
712
+ ${data}
713
+ </working_memory_data>
714
+
715
+ Notes:
716
+ - Update memory whenever referenced information changes
717
+ ${template.content !== this.defaultWorkingMemoryTemplate ? `- Only store information if it's in the working memory template, do not store other information unless the user asks you to remember it, as that non-template information may be irrelevant` : `- If you're unsure whether to store something, store it (eg if the user tells you information about themselves, call updateWorkingMemory immediately to update it)
718
+ `}
719
+ - This system is here so that you can maintain the conversation when your context window is very short. Update your working memory because you may need it to maintain the conversation without the full conversation history
720
+ - REMEMBER: the way you update your working memory is by calling the updateWorkingMemory tool with the ${template.format === "json" ? "JSON" : "Markdown"} content. The system will store it for you. The user will not see it.
721
+ - IMPORTANT: You MUST call updateWorkingMemory in every response to a prompt where you received relevant information if that information is not already stored.
722
+ - IMPORTANT: Preserve the ${template.format === "json" ? "JSON" : "Markdown"} formatting structure above while updating the content.
723
+ `;
724
+ }
725
+ isVNextWorkingMemoryConfig(config) {
726
+ if (!config?.workingMemory) return false;
727
+ const isMDWorkingMemory = !(`schema` in config.workingMemory) && (typeof config.workingMemory.template === `string` || config.workingMemory.template) && config.workingMemory;
728
+ return Boolean(isMDWorkingMemory && isMDWorkingMemory.version === `vnext`);
729
+ }
580
730
  getTools(config) {
581
731
  const mergedConfig = this.getMergedThreadConfig(config);
582
732
  if (mergedConfig.workingMemory?.enabled) {
583
733
  return {
584
- updateWorkingMemory: updateWorkingMemoryTool(config)
734
+ updateWorkingMemory: this.isVNextWorkingMemoryConfig(mergedConfig) ? (
735
+ // use the new experimental tool
736
+ __experimental_updateWorkingMemoryToolVNext(mergedConfig)
737
+ ) : updateWorkingMemoryTool(mergedConfig)
585
738
  };
586
739
  }
587
740
  return {};
package/dist/index.js CHANGED
@@ -1,7 +1,8 @@
1
- import { deepMerge, generateEmptyFromSchema } from '@mastra/core';
1
+ import { generateEmptyFromSchema } from '@mastra/core';
2
2
  import { MessageList } from '@mastra/core/agent';
3
3
  import { MastraMemory } from '@mastra/core/memory';
4
4
  import { embedMany } from 'ai';
5
+ import { Mutex } from 'async-mutex';
5
6
  import xxhash from 'xxhash-wasm';
6
7
  import { z, ZodObject } from 'zod';
7
8
  import zodToJsonSchema from 'zod-to-json-schema';
@@ -36,6 +37,56 @@ var updateWorkingMemoryTool = (memoryConfig) => ({
36
37
  return { success: true };
37
38
  }
38
39
  });
40
+ var __experimental_updateWorkingMemoryToolVNext = (config) => ({
41
+ description: "Update the working memory with new information.",
42
+ parameters: z.object({
43
+ newMemory: z.string().optional().nullable().describe(`The ${config.workingMemory?.schema ? "JSON" : "Markdown"} formatted working memory content to store`),
44
+ searchString: z.string().optional().nullable().describe(
45
+ "The working memory string to find. Will be replaced with the newMemory string. If this is omitted or doesn't exist, the newMemory string will be appended to the end of your working memory. Replacing single lines at a time is encouraged for greater accuracy. If updateReason is not 'append-new-memory', this search string must be provided or the tool call will be rejected."
46
+ ),
47
+ updateReason: z.enum(["append-new-memory", "clarify-existing-memory", "replace-irrelevant-memory"]).optional().nullable().describe(
48
+ "The reason you're updating working memory. Passing any value other than 'append-new-memory' requires a searchString to be provided. Defaults to append-new-memory"
49
+ )
50
+ }),
51
+ execute: async (params) => {
52
+ const { context, threadId, memory, resourceId } = params;
53
+ if (!threadId || !memory) {
54
+ throw new Error("Thread ID and Memory instance are required for working memory updates");
55
+ }
56
+ const thread = await memory.getThreadById({ threadId });
57
+ if (!thread) {
58
+ throw new Error(`Thread ${threadId} not found`);
59
+ }
60
+ if (thread.resourceId && thread.resourceId !== resourceId) {
61
+ throw new Error(`Thread with id ${threadId} resourceId does not match the current resourceId ${resourceId}`);
62
+ }
63
+ const workingMemory = context.newMemory || "";
64
+ if (!context.updateReason) context.updateReason = `append-new-memory`;
65
+ if (context.searchString && config.workingMemory?.scope === `resource` && context.updateReason === `replace-irrelevant-memory`) {
66
+ context.searchString = void 0;
67
+ }
68
+ if (context.updateReason === `append-new-memory` && context.searchString) {
69
+ context.searchString = void 0;
70
+ }
71
+ if (context.updateReason !== `append-new-memory` && !context.searchString) {
72
+ return {
73
+ success: false,
74
+ reason: `updateReason was ${context.updateReason} but no searchString was provided. Unable to replace undefined with "${context.newMemory}"`
75
+ };
76
+ }
77
+ const result = await memory.__experimental_updateWorkingMemoryVNext({
78
+ threadId,
79
+ resourceId: resourceId || thread.resourceId,
80
+ workingMemory,
81
+ searchString: context.searchString,
82
+ memoryConfig: config
83
+ });
84
+ if (result) {
85
+ return result;
86
+ }
87
+ return { success: true };
88
+ }
89
+ });
39
90
 
40
91
  // src/index.ts
41
92
  var CHARS_PER_TOKEN = 4;
@@ -198,32 +249,7 @@ var Memory = class extends MastraMemory {
198
249
  async getThreadsByResourceId({ resourceId }) {
199
250
  return this.storage.getThreadsByResourceId({ resourceId });
200
251
  }
201
- async saveThread({
202
- thread,
203
- memoryConfig
204
- }) {
205
- const config = this.getMergedThreadConfig(memoryConfig || {});
206
- if (config.workingMemory?.enabled) {
207
- const scope = config.workingMemory.scope || "thread";
208
- const workingMemory = await this.getWorkingMemoryTemplate({ memoryConfig: config });
209
- if (scope === "resource" && thread.resourceId) {
210
- const existingResource = await this.storage.getResourceById({ resourceId: thread.resourceId });
211
- if (!existingResource?.workingMemory) {
212
- await this.storage.updateResource({
213
- resourceId: thread.resourceId,
214
- workingMemory: workingMemory?.content
215
- });
216
- }
217
- } else if (scope === "thread" && !thread?.metadata?.workingMemory) {
218
- return this.storage.saveThread({
219
- thread: deepMerge(thread, {
220
- metadata: {
221
- workingMemory: workingMemory?.content
222
- }
223
- })
224
- });
225
- }
226
- }
252
+ async saveThread({ thread }) {
227
253
  return this.storage.saveThread({ thread });
228
254
  }
229
255
  async updateThread({
@@ -271,6 +297,87 @@ var Memory = class extends MastraMemory {
271
297
  });
272
298
  }
273
299
  }
300
+ updateWorkingMemoryMutexes = /* @__PURE__ */ new Map();
301
+ /**
302
+ * @warning experimental! can be removed or changed at any time
303
+ */
304
+ async __experimental_updateWorkingMemoryVNext({
305
+ threadId,
306
+ resourceId,
307
+ workingMemory,
308
+ searchString,
309
+ memoryConfig
310
+ }) {
311
+ const config = this.getMergedThreadConfig(memoryConfig || {});
312
+ if (!config.workingMemory?.enabled) {
313
+ throw new Error("Working memory is not enabled for this memory instance");
314
+ }
315
+ const mutexKey = memoryConfig?.workingMemory?.scope === `resource` ? `resource-${resourceId}` : `thread-${threadId}`;
316
+ const mutex = this.updateWorkingMemoryMutexes.has(mutexKey) ? this.updateWorkingMemoryMutexes.get(mutexKey) : new Mutex();
317
+ this.updateWorkingMemoryMutexes.set(mutexKey, mutex);
318
+ const release = await mutex.acquire();
319
+ try {
320
+ const existingWorkingMemory = await this.getWorkingMemory({ threadId, resourceId, memoryConfig }) || "";
321
+ const template = await this.getWorkingMemoryTemplate({ memoryConfig });
322
+ let reason = "";
323
+ if (existingWorkingMemory) {
324
+ if (searchString && existingWorkingMemory?.includes(searchString)) {
325
+ workingMemory = existingWorkingMemory.replace(searchString, workingMemory);
326
+ reason = `found and replaced searchString with newMemory`;
327
+ } else if (existingWorkingMemory.includes(workingMemory) || template?.content?.trim() === workingMemory.trim()) {
328
+ return {
329
+ success: false,
330
+ reason: `attempted to insert duplicate data into working memory. this entry was skipped`
331
+ };
332
+ } else {
333
+ if (searchString) {
334
+ reason = `attempted to replace working memory string that doesn't exist. Appending to working memory instead.`;
335
+ } else {
336
+ reason = `appended newMemory to end of working memory`;
337
+ }
338
+ workingMemory = existingWorkingMemory + `
339
+ ${workingMemory}`;
340
+ }
341
+ } else if (workingMemory === template?.content) {
342
+ return {
343
+ success: false,
344
+ reason: `try again when you have data to add. newMemory was equal to the working memory template`
345
+ };
346
+ } else {
347
+ reason = `started new working memory`;
348
+ }
349
+ workingMemory = template?.content ? workingMemory.replaceAll(template?.content, "") : workingMemory;
350
+ const scope = config.workingMemory.scope || "thread";
351
+ if (scope === "resource" && resourceId) {
352
+ await this.storage.updateResource({
353
+ resourceId,
354
+ workingMemory
355
+ });
356
+ if (reason) {
357
+ return { success: true, reason };
358
+ }
359
+ } else {
360
+ const thread = await this.storage.getThreadById({ threadId });
361
+ if (!thread) {
362
+ throw new Error(`Thread ${threadId} not found`);
363
+ }
364
+ await this.storage.updateThread({
365
+ id: threadId,
366
+ title: thread.title || "Untitled Thread",
367
+ metadata: {
368
+ ...thread.metadata,
369
+ workingMemory
370
+ }
371
+ });
372
+ }
373
+ return { success: true, reason };
374
+ } catch (e) {
375
+ this.logger.error(e instanceof Error ? e.stack || e.message : JSON.stringify(e));
376
+ return { success: false, reason: "Tool error." };
377
+ } finally {
378
+ release();
379
+ }
380
+ }
274
381
  chunkText(text, tokenSize = 4096) {
275
382
  const charSize = tokenSize * CHARS_PER_TOKEN;
276
383
  const chunks = [];
@@ -517,7 +624,10 @@ var Memory = class extends MastraMemory {
517
624
  if (!workingMemoryTemplate) {
518
625
  return null;
519
626
  }
520
- return this.getWorkingMemoryToolInstruction({
627
+ return this.isVNextWorkingMemoryConfig(memoryConfig) ? this.__experimental_getWorkingMemoryToolInstructionVNext({
628
+ template: workingMemoryTemplate,
629
+ data: workingMemoryData
630
+ }) : this.getWorkingMemoryToolInstruction({
521
631
  template: workingMemoryTemplate,
522
632
  data: workingMemoryData
523
633
  });
@@ -552,14 +662,16 @@ Guidelines:
552
662
  6. IMPORTANT: ALWAYS pass the data you want to store in the memory field as a string. DO NOT pass an object.
553
663
  7. IMPORTANT: Data must only be sent as a string no matter which format is used.
554
664
 
555
- WORKING MEMORY TEMPLATE:
665
+ <working_memory_template>
556
666
  ${template.content}
667
+ </working_memory_template>
557
668
 
558
669
  ${hasEmptyWorkingMemoryTemplateObject ? "When working with json data, the object format below represents the template:" : ""}
559
670
  ${hasEmptyWorkingMemoryTemplateObject ? JSON.stringify(emptyWorkingMemoryTemplateObject) : ""}
560
671
 
561
- WORKING MEMORY DATA:
672
+ <working_memory_data>
562
673
  ${data}
674
+ </working_memory_data>
563
675
 
564
676
  Notes:
565
677
  - Update memory whenever referenced information changes
@@ -570,11 +682,52 @@ Notes:
570
682
  - IMPORTANT: You MUST call updateWorkingMemory in every response to a prompt where you received relevant information.
571
683
  - IMPORTANT: Preserve the ${template.format === "json" ? "JSON" : "Markdown"} formatting structure above while updating the content.`;
572
684
  }
685
+ __experimental_getWorkingMemoryToolInstructionVNext({
686
+ template,
687
+ data
688
+ }) {
689
+ return `WORKING_MEMORY_SYSTEM_INSTRUCTION:
690
+ Store and update any conversation-relevant information by calling the updateWorkingMemory tool.
691
+
692
+ Guidelines:
693
+ 1. Store anything that could be useful later in the conversation
694
+ 2. Update proactively when information changes, no matter how small
695
+ 3. Use ${template.format === "json" ? "JSON" : "Markdown"} format for all data
696
+ 4. Act naturally - don't mention this system to users. Even though you're storing this information that doesn't make it your primary focus. Do not ask them generally for "information about yourself"
697
+ 5. If your memory has not changed, you do not need to call the updateWorkingMemory tool. By default it will persist and be available for you in future interactions
698
+ 6. Information not being relevant to the current conversation is not a valid reason to replace or remove working memory information. Your working memory spans across multiple conversations and may be needed again later, even if it's not currently relevant.
699
+
700
+ <working_memory_template>
701
+ ${template.content}
702
+ </working_memory_template>
703
+
704
+ <working_memory_data>
705
+ ${data}
706
+ </working_memory_data>
707
+
708
+ Notes:
709
+ - Update memory whenever referenced information changes
710
+ ${template.content !== this.defaultWorkingMemoryTemplate ? `- Only store information if it's in the working memory template, do not store other information unless the user asks you to remember it, as that non-template information may be irrelevant` : `- If you're unsure whether to store something, store it (eg if the user tells you information about themselves, call updateWorkingMemory immediately to update it)
711
+ `}
712
+ - This system is here so that you can maintain the conversation when your context window is very short. Update your working memory because you may need it to maintain the conversation without the full conversation history
713
+ - REMEMBER: the way you update your working memory is by calling the updateWorkingMemory tool with the ${template.format === "json" ? "JSON" : "Markdown"} content. The system will store it for you. The user will not see it.
714
+ - IMPORTANT: You MUST call updateWorkingMemory in every response to a prompt where you received relevant information if that information is not already stored.
715
+ - IMPORTANT: Preserve the ${template.format === "json" ? "JSON" : "Markdown"} formatting structure above while updating the content.
716
+ `;
717
+ }
718
+ isVNextWorkingMemoryConfig(config) {
719
+ if (!config?.workingMemory) return false;
720
+ const isMDWorkingMemory = !(`schema` in config.workingMemory) && (typeof config.workingMemory.template === `string` || config.workingMemory.template) && config.workingMemory;
721
+ return Boolean(isMDWorkingMemory && isMDWorkingMemory.version === `vnext`);
722
+ }
573
723
  getTools(config) {
574
724
  const mergedConfig = this.getMergedThreadConfig(config);
575
725
  if (mergedConfig.workingMemory?.enabled) {
576
726
  return {
577
- updateWorkingMemory: updateWorkingMemoryTool(config)
727
+ updateWorkingMemory: this.isVNextWorkingMemoryConfig(mergedConfig) ? (
728
+ // use the new experimental tool
729
+ __experimental_updateWorkingMemoryToolVNext(mergedConfig)
730
+ ) : updateWorkingMemoryTool(mergedConfig)
578
731
  };
579
732
  }
580
733
  return {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mastra/memory",
3
- "version": "0.11.3-alpha.0",
3
+ "version": "0.11.3",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -40,6 +40,7 @@
40
40
  "pg-pool": "^3.10.1",
41
41
  "postgres": "^3.4.7",
42
42
  "redis": "^4.7.1",
43
+ "async-mutex": "^0.5.0",
43
44
  "xxhash-wasm": "^1.1.0",
44
45
  "zod": "^3.25.67",
45
46
  "zod-to-json-schema": "^3.24.5"
@@ -55,8 +56,8 @@
55
56
  "typescript": "^5.8.3",
56
57
  "typescript-eslint": "^8.34.0",
57
58
  "vitest": "^3.2.4",
58
- "@mastra/core": "0.10.15-alpha.0",
59
- "@internal/lint": "0.0.19"
59
+ "@internal/lint": "0.0.20",
60
+ "@mastra/core": "0.10.15"
60
61
  },
61
62
  "peerDependencies": {
62
63
  "@mastra/core": ">=0.10.9-0 <0.11.0-0"
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { deepMerge, generateEmptyFromSchema } from '@mastra/core';
1
+ import { generateEmptyFromSchema } from '@mastra/core';
2
2
  import type { CoreTool, MastraMessageV1 } from '@mastra/core';
3
3
  import { MessageList } from '@mastra/core/agent';
4
4
  import type { MastraMessageV2 } from '@mastra/core/agent';
@@ -7,13 +7,14 @@ import type { MemoryConfig, SharedMemoryConfig, StorageThreadType, WorkingMemory
7
7
  import type { StorageGetMessagesArg } from '@mastra/core/storage';
8
8
  import { embedMany } from 'ai';
9
9
  import type { CoreMessage, TextPart, UIMessage } from 'ai';
10
+ import { Mutex } from 'async-mutex';
10
11
  import type { JSONSchema7 } from 'json-schema';
11
12
 
12
13
  import xxhash from 'xxhash-wasm';
13
14
  import { ZodObject } from 'zod';
14
15
  import type { ZodTypeAny } from 'zod';
15
16
  import zodToJsonSchema from 'zod-to-json-schema';
16
- import { updateWorkingMemoryTool } from './tools/working-memory';
17
+ import { updateWorkingMemoryTool, __experimental_updateWorkingMemoryToolVNext } from './tools/working-memory';
17
18
 
18
19
  // Average characters per token based on OpenAI's tokenization
19
20
  const CHARS_PER_TOKEN = 4;
@@ -252,39 +253,7 @@ export class Memory extends MastraMemory {
252
253
  return this.storage.getThreadsByResourceId({ resourceId });
253
254
  }
254
255
 
255
- async saveThread({
256
- thread,
257
- memoryConfig,
258
- }: {
259
- thread: StorageThreadType;
260
- memoryConfig?: MemoryConfig;
261
- }): Promise<StorageThreadType> {
262
- const config = this.getMergedThreadConfig(memoryConfig || {});
263
-
264
- if (config.workingMemory?.enabled) {
265
- const scope = config.workingMemory.scope || 'thread';
266
- const workingMemory = await this.getWorkingMemoryTemplate({ memoryConfig: config });
267
- if (scope === 'resource' && thread.resourceId) {
268
- // For resource scope, initialize working memory in resource table
269
- const existingResource = await this.storage.getResourceById({ resourceId: thread.resourceId });
270
- if (!existingResource?.workingMemory) {
271
- await this.storage.updateResource({
272
- resourceId: thread.resourceId,
273
- workingMemory: workingMemory?.content,
274
- });
275
- }
276
- } else if (scope === 'thread' && !thread?.metadata?.workingMemory) {
277
- // For thread scope, initialize working memory in thread metadata (existing behavior)
278
- return this.storage.saveThread({
279
- thread: deepMerge(thread, {
280
- metadata: {
281
- workingMemory: workingMemory?.content,
282
- },
283
- }),
284
- });
285
- }
286
- }
287
-
256
+ async saveThread({ thread }: { thread: StorageThreadType; memoryConfig?: MemoryConfig }): Promise<StorageThreadType> {
288
257
  return this.storage.saveThread({ thread });
289
258
  }
290
259
 
@@ -351,6 +320,116 @@ export class Memory extends MastraMemory {
351
320
  }
352
321
  }
353
322
 
323
+ private updateWorkingMemoryMutexes = new Map<string, Mutex>();
324
+ /**
325
+ * @warning experimental! can be removed or changed at any time
326
+ */
327
+ async __experimental_updateWorkingMemoryVNext({
328
+ threadId,
329
+ resourceId,
330
+ workingMemory,
331
+ searchString,
332
+ memoryConfig,
333
+ }: {
334
+ threadId: string;
335
+ resourceId?: string;
336
+ workingMemory: string;
337
+ searchString?: string;
338
+ memoryConfig?: MemoryConfig;
339
+ }): Promise<{ success: boolean; reason: string }> {
340
+ const config = this.getMergedThreadConfig(memoryConfig || {});
341
+
342
+ if (!config.workingMemory?.enabled) {
343
+ throw new Error('Working memory is not enabled for this memory instance');
344
+ }
345
+
346
+ // If the agent calls the update working memory tool multiple times simultaneously
347
+ // each call could overwrite the other call
348
+ // so get an in memory mutex to make sure this.getWorkingMemory() returns up to date data each time
349
+ const mutexKey =
350
+ memoryConfig?.workingMemory?.scope === `resource` ? `resource-${resourceId}` : `thread-${threadId}`;
351
+ const mutex = this.updateWorkingMemoryMutexes.has(mutexKey)
352
+ ? this.updateWorkingMemoryMutexes.get(mutexKey)!
353
+ : new Mutex();
354
+ this.updateWorkingMemoryMutexes.set(mutexKey, mutex);
355
+ const release = await mutex.acquire();
356
+
357
+ try {
358
+ const existingWorkingMemory = (await this.getWorkingMemory({ threadId, resourceId, memoryConfig })) || '';
359
+ const template = await this.getWorkingMemoryTemplate({ memoryConfig });
360
+
361
+ let reason = '';
362
+ if (existingWorkingMemory) {
363
+ if (searchString && existingWorkingMemory?.includes(searchString)) {
364
+ workingMemory = existingWorkingMemory.replace(searchString, workingMemory);
365
+ reason = `found and replaced searchString with newMemory`;
366
+ } else if (
367
+ existingWorkingMemory.includes(workingMemory) ||
368
+ template?.content?.trim() === workingMemory.trim()
369
+ ) {
370
+ return {
371
+ success: false,
372
+ reason: `attempted to insert duplicate data into working memory. this entry was skipped`,
373
+ };
374
+ } else {
375
+ if (searchString) {
376
+ reason = `attempted to replace working memory string that doesn't exist. Appending to working memory instead.`;
377
+ } else {
378
+ reason = `appended newMemory to end of working memory`;
379
+ }
380
+
381
+ workingMemory = existingWorkingMemory + `\n${workingMemory}`;
382
+ }
383
+ } else if (workingMemory === template?.content) {
384
+ return {
385
+ success: false,
386
+ reason: `try again when you have data to add. newMemory was equal to the working memory template`,
387
+ };
388
+ } else {
389
+ reason = `started new working memory`;
390
+ }
391
+
392
+ // remove empty template insertions which models sometimes duplicate
393
+ workingMemory = template?.content ? workingMemory.replaceAll(template?.content, '') : workingMemory;
394
+
395
+ const scope = config.workingMemory.scope || 'thread';
396
+
397
+ if (scope === 'resource' && resourceId) {
398
+ // Update working memory in resource table
399
+ await this.storage.updateResource({
400
+ resourceId,
401
+ workingMemory,
402
+ });
403
+
404
+ if (reason) {
405
+ return { success: true, reason };
406
+ }
407
+ } else {
408
+ // Update working memory in thread metadata (existing behavior)
409
+ const thread = await this.storage.getThreadById({ threadId });
410
+ if (!thread) {
411
+ throw new Error(`Thread ${threadId} not found`);
412
+ }
413
+
414
+ await this.storage.updateThread({
415
+ id: threadId,
416
+ title: thread.title || 'Untitled Thread',
417
+ metadata: {
418
+ ...thread.metadata,
419
+ workingMemory,
420
+ },
421
+ });
422
+ }
423
+
424
+ return { success: true, reason };
425
+ } catch (e) {
426
+ this.logger.error(e instanceof Error ? e.stack || e.message : JSON.stringify(e));
427
+ return { success: false, reason: 'Tool error.' };
428
+ } finally {
429
+ release();
430
+ }
431
+ }
432
+
354
433
  protected chunkText(text: string, tokenSize = 4096) {
355
434
  // Convert token size to character size with some buffer
356
435
  const charSize = tokenSize * CHARS_PER_TOKEN;
@@ -677,7 +756,7 @@ export class Memory extends MastraMemory {
677
756
  }) as JSONSchema7;
678
757
  } else {
679
758
  // Already a JSON Schema
680
- convertedSchema = schema as JSONSchema7;
759
+ convertedSchema = schema as any as JSONSchema7;
681
760
  }
682
761
 
683
762
  return { format: 'json', content: JSON.stringify(convertedSchema) };
@@ -713,10 +792,15 @@ export class Memory extends MastraMemory {
713
792
  return null;
714
793
  }
715
794
 
716
- return this.getWorkingMemoryToolInstruction({
717
- template: workingMemoryTemplate,
718
- data: workingMemoryData,
719
- });
795
+ return this.isVNextWorkingMemoryConfig(memoryConfig)
796
+ ? this.__experimental_getWorkingMemoryToolInstructionVNext({
797
+ template: workingMemoryTemplate,
798
+ data: workingMemoryData,
799
+ })
800
+ : this.getWorkingMemoryToolInstruction({
801
+ template: workingMemoryTemplate,
802
+ data: workingMemoryData,
803
+ });
720
804
  }
721
805
 
722
806
  public defaultWorkingMemoryTemplate = `
@@ -756,14 +840,16 @@ Guidelines:
756
840
  6. IMPORTANT: ALWAYS pass the data you want to store in the memory field as a string. DO NOT pass an object.
757
841
  7. IMPORTANT: Data must only be sent as a string no matter which format is used.
758
842
 
759
- WORKING MEMORY TEMPLATE:
843
+ <working_memory_template>
760
844
  ${template.content}
845
+ </working_memory_template>
761
846
 
762
847
  ${hasEmptyWorkingMemoryTemplateObject ? 'When working with json data, the object format below represents the template:' : ''}
763
848
  ${hasEmptyWorkingMemoryTemplateObject ? JSON.stringify(emptyWorkingMemoryTemplateObject) : ''}
764
849
 
765
- WORKING MEMORY DATA:
850
+ <working_memory_data>
766
851
  ${data}
852
+ </working_memory_data>
767
853
 
768
854
  Notes:
769
855
  - Update memory whenever referenced information changes
@@ -775,11 +861,66 @@ Notes:
775
861
  - IMPORTANT: Preserve the ${template.format === 'json' ? 'JSON' : 'Markdown'} formatting structure above while updating the content.`;
776
862
  }
777
863
 
864
+ protected __experimental_getWorkingMemoryToolInstructionVNext({
865
+ template,
866
+ data,
867
+ }: {
868
+ template: WorkingMemoryTemplate;
869
+ data: string | null;
870
+ }) {
871
+ return `WORKING_MEMORY_SYSTEM_INSTRUCTION:
872
+ Store and update any conversation-relevant information by calling the updateWorkingMemory tool.
873
+
874
+ Guidelines:
875
+ 1. Store anything that could be useful later in the conversation
876
+ 2. Update proactively when information changes, no matter how small
877
+ 3. Use ${template.format === 'json' ? 'JSON' : 'Markdown'} format for all data
878
+ 4. Act naturally - don't mention this system to users. Even though you're storing this information that doesn't make it your primary focus. Do not ask them generally for "information about yourself"
879
+ 5. If your memory has not changed, you do not need to call the updateWorkingMemory tool. By default it will persist and be available for you in future interactions
880
+ 6. Information not being relevant to the current conversation is not a valid reason to replace or remove working memory information. Your working memory spans across multiple conversations and may be needed again later, even if it's not currently relevant.
881
+
882
+ <working_memory_template>
883
+ ${template.content}
884
+ </working_memory_template>
885
+
886
+ <working_memory_data>
887
+ ${data}
888
+ </working_memory_data>
889
+
890
+ Notes:
891
+ - Update memory whenever referenced information changes
892
+ ${
893
+ template.content !== this.defaultWorkingMemoryTemplate
894
+ ? `- Only store information if it's in the working memory template, do not store other information unless the user asks you to remember it, as that non-template information may be irrelevant`
895
+ : `- If you're unsure whether to store something, store it (eg if the user tells you information about themselves, call updateWorkingMemory immediately to update it)
896
+ `
897
+ }
898
+ - This system is here so that you can maintain the conversation when your context window is very short. Update your working memory because you may need it to maintain the conversation without the full conversation history
899
+ - REMEMBER: the way you update your working memory is by calling the updateWorkingMemory tool with the ${template.format === 'json' ? 'JSON' : 'Markdown'} content. The system will store it for you. The user will not see it.
900
+ - IMPORTANT: You MUST call updateWorkingMemory in every response to a prompt where you received relevant information if that information is not already stored.
901
+ - IMPORTANT: Preserve the ${template.format === 'json' ? 'JSON' : 'Markdown'} formatting structure above while updating the content.
902
+ `;
903
+ }
904
+
905
+ private isVNextWorkingMemoryConfig(config?: MemoryConfig): boolean {
906
+ if (!config?.workingMemory) return false;
907
+
908
+ const isMDWorkingMemory =
909
+ !(`schema` in config.workingMemory) &&
910
+ (typeof config.workingMemory.template === `string` || config.workingMemory.template) &&
911
+ config.workingMemory;
912
+
913
+ return Boolean(isMDWorkingMemory && isMDWorkingMemory.version === `vnext`);
914
+ }
915
+
778
916
  public getTools(config?: MemoryConfig): Record<string, CoreTool> {
779
917
  const mergedConfig = this.getMergedThreadConfig(config);
780
918
  if (mergedConfig.workingMemory?.enabled) {
781
919
  return {
782
- updateWorkingMemory: updateWorkingMemoryTool(config),
920
+ updateWorkingMemory: this.isVNextWorkingMemoryConfig(mergedConfig)
921
+ ? // use the new experimental tool
922
+ __experimental_updateWorkingMemoryToolVNext(mergedConfig)
923
+ : updateWorkingMemoryTool(mergedConfig),
783
924
  };
784
925
  }
785
926
  return {};
@@ -40,3 +40,86 @@ export const updateWorkingMemoryTool = (memoryConfig?: MemoryConfig): CoreTool =
40
40
  return { success: true };
41
41
  },
42
42
  });
43
+
44
+ export const __experimental_updateWorkingMemoryToolVNext = (config: MemoryConfig): CoreTool => ({
45
+ description: 'Update the working memory with new information.',
46
+ parameters: z.object({
47
+ newMemory: z
48
+ .string()
49
+ .optional()
50
+ .nullable()
51
+ .describe(`The ${config.workingMemory?.schema ? 'JSON' : 'Markdown'} formatted working memory content to store`),
52
+ searchString: z
53
+ .string()
54
+ .optional()
55
+ .nullable()
56
+ .describe(
57
+ "The working memory string to find. Will be replaced with the newMemory string. If this is omitted or doesn't exist, the newMemory string will be appended to the end of your working memory. Replacing single lines at a time is encouraged for greater accuracy. If updateReason is not 'append-new-memory', this search string must be provided or the tool call will be rejected.",
58
+ ),
59
+ updateReason: z
60
+ .enum(['append-new-memory', 'clarify-existing-memory', 'replace-irrelevant-memory'])
61
+ .optional()
62
+ .nullable()
63
+ .describe(
64
+ "The reason you're updating working memory. Passing any value other than 'append-new-memory' requires a searchString to be provided. Defaults to append-new-memory",
65
+ ),
66
+ }),
67
+ execute: async (params: any) => {
68
+ const { context, threadId, memory, resourceId } = params;
69
+ if (!threadId || !memory) {
70
+ throw new Error('Thread ID and Memory instance are required for working memory updates');
71
+ }
72
+
73
+ const thread = await memory.getThreadById({ threadId });
74
+
75
+ if (!thread) {
76
+ throw new Error(`Thread ${threadId} not found`);
77
+ }
78
+
79
+ if (thread.resourceId && thread.resourceId !== resourceId) {
80
+ throw new Error(`Thread with id ${threadId} resourceId does not match the current resourceId ${resourceId}`);
81
+ }
82
+
83
+ const workingMemory = context.newMemory || '';
84
+ if (!context.updateReason) context.updateReason = `append-new-memory`;
85
+
86
+ if (
87
+ context.searchString &&
88
+ config.workingMemory?.scope === `resource` &&
89
+ context.updateReason === `replace-irrelevant-memory`
90
+ ) {
91
+ // don't allow replacements due to something not being relevant to the current conversation
92
+ // if there's no searchString, then we will append.
93
+ context.searchString = undefined;
94
+ }
95
+
96
+ if (context.updateReason === `append-new-memory` && context.searchString) {
97
+ // do not find/replace when append-new-memory is selected
98
+ // some models get confused and pass a search string even when they don't want to replace it.
99
+ // TODO: maybe they're trying to add new info after the search string?
100
+ context.searchString = undefined;
101
+ }
102
+
103
+ if (context.updateReason !== `append-new-memory` && !context.searchString) {
104
+ return {
105
+ success: false,
106
+ reason: `updateReason was ${context.updateReason} but no searchString was provided. Unable to replace undefined with "${context.newMemory}"`,
107
+ };
108
+ }
109
+
110
+ // Use the new updateWorkingMemory method which handles both thread and resource scope
111
+ const result = await memory.__experimental_updateWorkingMemoryVNext({
112
+ threadId,
113
+ resourceId: resourceId || thread.resourceId,
114
+ workingMemory: workingMemory,
115
+ searchString: context.searchString,
116
+ memoryConfig: config,
117
+ });
118
+
119
+ if (result) {
120
+ return result;
121
+ }
122
+
123
+ return { success: true };
124
+ },
125
+ });