@mastra/memory 0.10.4-alpha.0 → 0.10.4-alpha.1

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.10.4-alpha.0 build /home/runner/work/mastra/mastra/packages/memory
2
+ > @mastra/memory@0.10.4-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.10.4-alpha.0 check /home/runner/work/mastra/mastra/packages/memory
6
+ > @mastra/memory@0.10.4-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,17 @@
1
1
  # @mastra/memory
2
2
 
3
+ ## 0.10.4-alpha.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 4051477: dependencies updates:
8
+ - Added dependency [`zod-to-json-schema@^3.24.5` ↗︎](https://www.npmjs.com/package/zod-to-json-schema/v/3.24.5) (to `dependencies`)
9
+ - d70c420: fix(core, memory): fix fetchMemory regression
10
+ - 2a16996: Working Memory Schema and Template
11
+ - Updated dependencies [d70c420]
12
+ - Updated dependencies [2a16996]
13
+ - @mastra/core@0.10.6-alpha.3
14
+
3
15
  ## 0.10.4-alpha.0
4
16
 
5
17
  ### Patch Changes
@@ -13,6 +13,8 @@ import type { StorageGetMessagesArg } from '@mastra/core/storage';
13
13
  import type { StorageThreadType } from '@mastra/core/memory';
14
14
  import type { TiktokenBPE } from 'js-tiktoken/lite';
15
15
  import type { UIMessage } from 'ai';
16
+ import type { WorkingMemoryFormat } from '@mastra/core/memory';
17
+ import type { WorkingMemoryTemplate } from '@mastra/core/memory';
16
18
 
17
19
  /**
18
20
  * Concrete implementation of MastraMemory that adds support for thread configuration
@@ -72,13 +74,18 @@ export declare class Memory extends MastraMemory {
72
74
  protected updateMessageToHideWorkingMemory(message: MastraMessageV1): MastraMessageV1 | null;
73
75
  protected updateMessageToHideWorkingMemoryV2(message: MastraMessageV2): MastraMessageV2 | null;
74
76
  protected parseWorkingMemory(text: string): string | null;
75
- getWorkingMemory({ threadId }: {
77
+ getWorkingMemory({ threadId, format, }: {
76
78
  threadId: string;
79
+ format?: WorkingMemoryFormat;
77
80
  }): Promise<string | null>;
81
+ getWorkingMemoryTemplate(): Promise<WorkingMemoryTemplate | null>;
78
82
  getSystemMessage({ threadId, memoryConfig, }: {
79
83
  threadId: string;
80
84
  memoryConfig?: MemoryConfig;
81
85
  }): Promise<string | null>;
86
+ getUserContextMessage({ threadId }: {
87
+ threadId: string;
88
+ }): Promise<string | null>;
82
89
  defaultWorkingMemoryTemplate: string;
83
90
  private getWorkingMemoryToolInstruction;
84
91
  getTools(config?: MemoryConfig): Record<string, CoreTool>;
@@ -134,6 +141,8 @@ declare class ToolCallFilter extends MemoryProcessor_2 {
134
141
  export { ToolCallFilter }
135
142
  export { ToolCallFilter as ToolCallFilter_alias_1 }
136
143
 
137
- export declare const updateWorkingMemoryTool: CoreTool;
144
+ export declare const updateWorkingMemoryTool: ({ format }: {
145
+ format: WorkingMemoryFormat;
146
+ }) => CoreTool;
138
147
 
139
148
  export { }
@@ -13,6 +13,8 @@ import type { StorageGetMessagesArg } from '@mastra/core/storage';
13
13
  import type { StorageThreadType } from '@mastra/core/memory';
14
14
  import type { TiktokenBPE } from 'js-tiktoken/lite';
15
15
  import type { UIMessage } from 'ai';
16
+ import type { WorkingMemoryFormat } from '@mastra/core/memory';
17
+ import type { WorkingMemoryTemplate } from '@mastra/core/memory';
16
18
 
17
19
  /**
18
20
  * Concrete implementation of MastraMemory that adds support for thread configuration
@@ -72,13 +74,18 @@ export declare class Memory extends MastraMemory {
72
74
  protected updateMessageToHideWorkingMemory(message: MastraMessageV1): MastraMessageV1 | null;
73
75
  protected updateMessageToHideWorkingMemoryV2(message: MastraMessageV2): MastraMessageV2 | null;
74
76
  protected parseWorkingMemory(text: string): string | null;
75
- getWorkingMemory({ threadId }: {
77
+ getWorkingMemory({ threadId, format, }: {
76
78
  threadId: string;
79
+ format?: WorkingMemoryFormat;
77
80
  }): Promise<string | null>;
81
+ getWorkingMemoryTemplate(): Promise<WorkingMemoryTemplate | null>;
78
82
  getSystemMessage({ threadId, memoryConfig, }: {
79
83
  threadId: string;
80
84
  memoryConfig?: MemoryConfig;
81
85
  }): Promise<string | null>;
86
+ getUserContextMessage({ threadId }: {
87
+ threadId: string;
88
+ }): Promise<string | null>;
82
89
  defaultWorkingMemoryTemplate: string;
83
90
  private getWorkingMemoryToolInstruction;
84
91
  getTools(config?: MemoryConfig): Record<string, CoreTool>;
@@ -134,6 +141,8 @@ declare class ToolCallFilter extends MemoryProcessor_2 {
134
141
  export { ToolCallFilter }
135
142
  export { ToolCallFilter as ToolCallFilter_alias_1 }
136
143
 
137
- export declare const updateWorkingMemoryTool: CoreTool;
144
+ export declare const updateWorkingMemoryTool: ({ format }: {
145
+ format: WorkingMemoryFormat;
146
+ }) => CoreTool;
138
147
 
139
148
  export { }
package/dist/index.cjs CHANGED
@@ -5,20 +5,22 @@ var agent = require('@mastra/core/agent');
5
5
  var memory = require('@mastra/core/memory');
6
6
  var ai = require('ai');
7
7
  var xxhash = require('xxhash-wasm');
8
+ var zodToJsonSchema = require('zod-to-json-schema');
8
9
  var zod = require('zod');
9
10
 
10
11
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
11
12
 
12
13
  var xxhash__default = /*#__PURE__*/_interopDefault(xxhash);
14
+ var zodToJsonSchema__default = /*#__PURE__*/_interopDefault(zodToJsonSchema);
13
15
 
14
16
  // src/index.ts
15
- var updateWorkingMemoryTool = {
17
+ var updateWorkingMemoryTool = ({ format }) => ({
16
18
  description: "Update the working memory with new information",
17
19
  parameters: zod.z.object({
18
- memory: zod.z.string().describe("The Markdown-formatted working memory content to store")
20
+ memory: zod.z.string().describe(`The ${format === "json" ? "JSON" : "Markdown"} formatted working memory content to store`)
19
21
  }),
20
22
  execute: async (params) => {
21
- const { context, threadId, memory } = params;
23
+ const { context, threadId, memory, resourceId } = params;
22
24
  if (!threadId || !memory) {
23
25
  throw new Error("Thread ID and Memory instance are required for working memory updates");
24
26
  }
@@ -26,18 +28,22 @@ var updateWorkingMemoryTool = {
26
28
  if (!thread) {
27
29
  throw new Error(`Thread ${threadId} not found`);
28
30
  }
31
+ if (thread.resourceId && thread.resourceId !== resourceId) {
32
+ throw new Error(`Thread with id ${threadId} resourceId does not match the current resourceId ${resourceId}`);
33
+ }
34
+ const workingMemory = context.memory;
29
35
  await memory.saveThread({
30
36
  thread: {
31
37
  ...thread,
32
38
  metadata: {
33
39
  ...thread.metadata,
34
- workingMemory: context.memory
40
+ workingMemory
35
41
  }
36
42
  }
37
43
  });
38
44
  return { success: true };
39
45
  }
40
- };
46
+ });
41
47
 
42
48
  // src/index.ts
43
49
  var CHARS_PER_TOKEN = 4;
@@ -200,10 +206,14 @@ var Memory = class extends memory.MastraMemory {
200
206
  }) {
201
207
  const config = this.getMergedThreadConfig(memoryConfig || {});
202
208
  if (config.workingMemory?.enabled && !thread?.metadata?.workingMemory) {
209
+ let workingMemory = config.workingMemory.template || this.defaultWorkingMemoryTemplate;
210
+ if (config.workingMemory.schema) {
211
+ workingMemory = JSON.stringify(zodToJsonSchema__default.default(config.workingMemory.schema));
212
+ }
203
213
  return this.storage.saveThread({
204
214
  thread: core.deepMerge(thread, {
205
215
  metadata: {
206
- workingMemory: config.workingMemory.template || this.defaultWorkingMemoryTemplate
216
+ workingMemory
207
217
  }
208
218
  })
209
219
  });
@@ -399,12 +409,41 @@ var Memory = class extends memory.MastraMemory {
399
409
  }
400
410
  return null;
401
411
  }
402
- async getWorkingMemory({ threadId }) {
403
- if (!this.threadConfig.workingMemory?.enabled) return null;
412
+ async getWorkingMemory({
413
+ threadId,
414
+ format
415
+ }) {
416
+ if (!this.threadConfig.workingMemory?.enabled) {
417
+ return null;
418
+ }
404
419
  const thread = await this.storage.getThreadById({ threadId });
405
- if (!thread) return this.threadConfig?.workingMemory?.template || this.defaultWorkingMemoryTemplate;
406
- const memory = thread.metadata?.workingMemory || this.threadConfig.workingMemory.template || this.defaultWorkingMemoryTemplate;
407
- return memory.trim();
420
+ if (format === "json") {
421
+ try {
422
+ return JSON.parse(thread?.metadata?.workingMemory) || null;
423
+ } catch (e) {
424
+ this.logger.error("Unable to parse working memory as JSON. Returning string.", e);
425
+ }
426
+ }
427
+ return thread?.metadata?.workingMemory ? JSON.stringify(thread?.metadata?.workingMemory) : null;
428
+ }
429
+ async getWorkingMemoryTemplate() {
430
+ if (!this.threadConfig.workingMemory?.enabled) {
431
+ return null;
432
+ }
433
+ if (this.threadConfig?.workingMemory?.schema) {
434
+ try {
435
+ const schema = this.threadConfig.workingMemory.schema;
436
+ const convertedSchema = zodToJsonSchema__default.default(schema, {
437
+ $refStrategy: "none"
438
+ });
439
+ return { format: "json", content: JSON.stringify(convertedSchema) };
440
+ } catch (error) {
441
+ this.logger.error("Error converting schema", error);
442
+ throw error;
443
+ }
444
+ }
445
+ const memory = this.threadConfig.workingMemory.template || this.defaultWorkingMemoryTemplate;
446
+ return { format: "markdown", content: memory.trim() };
408
447
  }
409
448
  async getSystemMessage({
410
449
  threadId,
@@ -414,11 +453,28 @@ var Memory = class extends memory.MastraMemory {
414
453
  if (!config.workingMemory?.enabled) {
415
454
  return null;
416
455
  }
456
+ const workingMemoryTemplate = await this.getWorkingMemoryTemplate();
457
+ const workingMemoryData = await this.getWorkingMemory({ threadId });
458
+ if (!workingMemoryTemplate) {
459
+ return null;
460
+ }
461
+ return this.getWorkingMemoryToolInstruction({
462
+ template: workingMemoryTemplate,
463
+ data: workingMemoryData
464
+ });
465
+ }
466
+ async getUserContextMessage({ threadId }) {
417
467
  const workingMemory = await this.getWorkingMemory({ threadId });
418
468
  if (!workingMemory) {
419
469
  return null;
420
470
  }
421
- return this.getWorkingMemoryToolInstruction(workingMemory);
471
+ return `The following is the most up-to-date information about the user's state and context:
472
+ ${JSON.stringify(workingMemory)}
473
+ Use this information as the source of truth when generating responses.
474
+ Do not reference or mention this memory directly to the user.
475
+ If conversation history shows information that is not in the working memory, use the working memory as the source of truth.
476
+ If there is a discrepancy between this information and conversation history, always rely on this information unless the user explicitly asks for an update.
477
+ `;
422
478
  }
423
479
  defaultWorkingMemoryTemplate = `
424
480
  # User Information
@@ -432,33 +488,44 @@ var Memory = class extends memory.MastraMemory {
432
488
  - **Facts**:
433
489
  - **Projects**:
434
490
  `;
435
- getWorkingMemoryToolInstruction(workingMemoryBlock) {
491
+ getWorkingMemoryToolInstruction({
492
+ template,
493
+ data
494
+ }) {
436
495
  return `WORKING_MEMORY_SYSTEM_INSTRUCTION:
437
496
  Store and update any conversation-relevant information by calling the updateWorkingMemory tool. If information might be referenced again - store it!
438
497
 
439
498
  Guidelines:
440
499
  1. Store anything that could be useful later in the conversation
441
500
  2. Update proactively when information changes, no matter how small
442
- 3. Use Markdown format for all data
501
+ 3. Use ${template.format === "json" ? "JSON" : "Markdown"} format for all data
443
502
  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"
444
503
 
445
- Memory Structure:
446
- ${workingMemoryBlock}
504
+ WORKING MEMORY TEMPLATE:
505
+ ${template.content}
506
+
507
+ WORKING MEMORY DATA:
508
+ ${data}
447
509
 
448
510
  Notes:
449
511
  - Update memory whenever referenced information changes
450
512
  - 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)
451
513
  - 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
452
514
  - Do not remove empty sections - you must include the empty sections along with the ones you're filling in
453
- - REMEMBER: the way you update your working memory is by calling the updateWorkingMemory tool with the entire Markdown content. The system will store it for you. The user will not see it.
515
+ - REMEMBER: the way you update your working memory is by calling the updateWorkingMemory tool with the entire ${template.format === "json" ? "JSON" : "Markdown"} content. The system will store it for you. The user will not see it.
454
516
  - IMPORTANT: You MUST call updateWorkingMemory in every response to a prompt where you received relevant information.
455
- - IMPORTANT: Preserve the Markdown formatting structure above while updating the content.`;
517
+ - IMPORTANT: Preserve the ${template.format === "json" ? "JSON" : "Markdown"} formatting structure above while updating the content.`;
456
518
  }
457
519
  getTools(config) {
458
520
  const mergedConfig = this.getMergedThreadConfig(config);
459
521
  if (mergedConfig.workingMemory?.enabled) {
522
+ if (mergedConfig.workingMemory.schema) {
523
+ return {
524
+ updateWorkingMemory: updateWorkingMemoryTool({ format: "json" })
525
+ };
526
+ }
460
527
  return {
461
- updateWorkingMemory: updateWorkingMemoryTool
528
+ updateWorkingMemory: updateWorkingMemoryTool({ format: "markdown" })
462
529
  };
463
530
  }
464
531
  return {};
package/dist/index.js CHANGED
@@ -3,16 +3,17 @@ import { MessageList } from '@mastra/core/agent';
3
3
  import { MastraMemory } from '@mastra/core/memory';
4
4
  import { embedMany } from 'ai';
5
5
  import xxhash from 'xxhash-wasm';
6
+ import zodToJsonSchema from 'zod-to-json-schema';
6
7
  import { z } from 'zod';
7
8
 
8
9
  // src/index.ts
9
- var updateWorkingMemoryTool = {
10
+ var updateWorkingMemoryTool = ({ format }) => ({
10
11
  description: "Update the working memory with new information",
11
12
  parameters: z.object({
12
- memory: z.string().describe("The Markdown-formatted working memory content to store")
13
+ memory: z.string().describe(`The ${format === "json" ? "JSON" : "Markdown"} formatted working memory content to store`)
13
14
  }),
14
15
  execute: async (params) => {
15
- const { context, threadId, memory } = params;
16
+ const { context, threadId, memory, resourceId } = params;
16
17
  if (!threadId || !memory) {
17
18
  throw new Error("Thread ID and Memory instance are required for working memory updates");
18
19
  }
@@ -20,18 +21,22 @@ var updateWorkingMemoryTool = {
20
21
  if (!thread) {
21
22
  throw new Error(`Thread ${threadId} not found`);
22
23
  }
24
+ if (thread.resourceId && thread.resourceId !== resourceId) {
25
+ throw new Error(`Thread with id ${threadId} resourceId does not match the current resourceId ${resourceId}`);
26
+ }
27
+ const workingMemory = context.memory;
23
28
  await memory.saveThread({
24
29
  thread: {
25
30
  ...thread,
26
31
  metadata: {
27
32
  ...thread.metadata,
28
- workingMemory: context.memory
33
+ workingMemory
29
34
  }
30
35
  }
31
36
  });
32
37
  return { success: true };
33
38
  }
34
- };
39
+ });
35
40
 
36
41
  // src/index.ts
37
42
  var CHARS_PER_TOKEN = 4;
@@ -194,10 +199,14 @@ var Memory = class extends MastraMemory {
194
199
  }) {
195
200
  const config = this.getMergedThreadConfig(memoryConfig || {});
196
201
  if (config.workingMemory?.enabled && !thread?.metadata?.workingMemory) {
202
+ let workingMemory = config.workingMemory.template || this.defaultWorkingMemoryTemplate;
203
+ if (config.workingMemory.schema) {
204
+ workingMemory = JSON.stringify(zodToJsonSchema(config.workingMemory.schema));
205
+ }
197
206
  return this.storage.saveThread({
198
207
  thread: deepMerge(thread, {
199
208
  metadata: {
200
- workingMemory: config.workingMemory.template || this.defaultWorkingMemoryTemplate
209
+ workingMemory
201
210
  }
202
211
  })
203
212
  });
@@ -393,12 +402,41 @@ var Memory = class extends MastraMemory {
393
402
  }
394
403
  return null;
395
404
  }
396
- async getWorkingMemory({ threadId }) {
397
- if (!this.threadConfig.workingMemory?.enabled) return null;
405
+ async getWorkingMemory({
406
+ threadId,
407
+ format
408
+ }) {
409
+ if (!this.threadConfig.workingMemory?.enabled) {
410
+ return null;
411
+ }
398
412
  const thread = await this.storage.getThreadById({ threadId });
399
- if (!thread) return this.threadConfig?.workingMemory?.template || this.defaultWorkingMemoryTemplate;
400
- const memory = thread.metadata?.workingMemory || this.threadConfig.workingMemory.template || this.defaultWorkingMemoryTemplate;
401
- return memory.trim();
413
+ if (format === "json") {
414
+ try {
415
+ return JSON.parse(thread?.metadata?.workingMemory) || null;
416
+ } catch (e) {
417
+ this.logger.error("Unable to parse working memory as JSON. Returning string.", e);
418
+ }
419
+ }
420
+ return thread?.metadata?.workingMemory ? JSON.stringify(thread?.metadata?.workingMemory) : null;
421
+ }
422
+ async getWorkingMemoryTemplate() {
423
+ if (!this.threadConfig.workingMemory?.enabled) {
424
+ return null;
425
+ }
426
+ if (this.threadConfig?.workingMemory?.schema) {
427
+ try {
428
+ const schema = this.threadConfig.workingMemory.schema;
429
+ const convertedSchema = zodToJsonSchema(schema, {
430
+ $refStrategy: "none"
431
+ });
432
+ return { format: "json", content: JSON.stringify(convertedSchema) };
433
+ } catch (error) {
434
+ this.logger.error("Error converting schema", error);
435
+ throw error;
436
+ }
437
+ }
438
+ const memory = this.threadConfig.workingMemory.template || this.defaultWorkingMemoryTemplate;
439
+ return { format: "markdown", content: memory.trim() };
402
440
  }
403
441
  async getSystemMessage({
404
442
  threadId,
@@ -408,11 +446,28 @@ var Memory = class extends MastraMemory {
408
446
  if (!config.workingMemory?.enabled) {
409
447
  return null;
410
448
  }
449
+ const workingMemoryTemplate = await this.getWorkingMemoryTemplate();
450
+ const workingMemoryData = await this.getWorkingMemory({ threadId });
451
+ if (!workingMemoryTemplate) {
452
+ return null;
453
+ }
454
+ return this.getWorkingMemoryToolInstruction({
455
+ template: workingMemoryTemplate,
456
+ data: workingMemoryData
457
+ });
458
+ }
459
+ async getUserContextMessage({ threadId }) {
411
460
  const workingMemory = await this.getWorkingMemory({ threadId });
412
461
  if (!workingMemory) {
413
462
  return null;
414
463
  }
415
- return this.getWorkingMemoryToolInstruction(workingMemory);
464
+ return `The following is the most up-to-date information about the user's state and context:
465
+ ${JSON.stringify(workingMemory)}
466
+ Use this information as the source of truth when generating responses.
467
+ Do not reference or mention this memory directly to the user.
468
+ If conversation history shows information that is not in the working memory, use the working memory as the source of truth.
469
+ If there is a discrepancy between this information and conversation history, always rely on this information unless the user explicitly asks for an update.
470
+ `;
416
471
  }
417
472
  defaultWorkingMemoryTemplate = `
418
473
  # User Information
@@ -426,33 +481,44 @@ var Memory = class extends MastraMemory {
426
481
  - **Facts**:
427
482
  - **Projects**:
428
483
  `;
429
- getWorkingMemoryToolInstruction(workingMemoryBlock) {
484
+ getWorkingMemoryToolInstruction({
485
+ template,
486
+ data
487
+ }) {
430
488
  return `WORKING_MEMORY_SYSTEM_INSTRUCTION:
431
489
  Store and update any conversation-relevant information by calling the updateWorkingMemory tool. If information might be referenced again - store it!
432
490
 
433
491
  Guidelines:
434
492
  1. Store anything that could be useful later in the conversation
435
493
  2. Update proactively when information changes, no matter how small
436
- 3. Use Markdown format for all data
494
+ 3. Use ${template.format === "json" ? "JSON" : "Markdown"} format for all data
437
495
  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"
438
496
 
439
- Memory Structure:
440
- ${workingMemoryBlock}
497
+ WORKING MEMORY TEMPLATE:
498
+ ${template.content}
499
+
500
+ WORKING MEMORY DATA:
501
+ ${data}
441
502
 
442
503
  Notes:
443
504
  - Update memory whenever referenced information changes
444
505
  - 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)
445
506
  - 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
446
507
  - Do not remove empty sections - you must include the empty sections along with the ones you're filling in
447
- - REMEMBER: the way you update your working memory is by calling the updateWorkingMemory tool with the entire Markdown content. The system will store it for you. The user will not see it.
508
+ - REMEMBER: the way you update your working memory is by calling the updateWorkingMemory tool with the entire ${template.format === "json" ? "JSON" : "Markdown"} content. The system will store it for you. The user will not see it.
448
509
  - IMPORTANT: You MUST call updateWorkingMemory in every response to a prompt where you received relevant information.
449
- - IMPORTANT: Preserve the Markdown formatting structure above while updating the content.`;
510
+ - IMPORTANT: Preserve the ${template.format === "json" ? "JSON" : "Markdown"} formatting structure above while updating the content.`;
450
511
  }
451
512
  getTools(config) {
452
513
  const mergedConfig = this.getMergedThreadConfig(config);
453
514
  if (mergedConfig.workingMemory?.enabled) {
515
+ if (mergedConfig.workingMemory.schema) {
516
+ return {
517
+ updateWorkingMemory: updateWorkingMemoryTool({ format: "json" })
518
+ };
519
+ }
454
520
  return {
455
- updateWorkingMemory: updateWorkingMemoryTool
521
+ updateWorkingMemory: updateWorkingMemoryTool({ format: "markdown" })
456
522
  };
457
523
  }
458
524
  return {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mastra/memory",
3
- "version": "0.10.4-alpha.0",
3
+ "version": "0.10.4-alpha.1",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -40,7 +40,8 @@
40
40
  "postgres": "^3.4.7",
41
41
  "redis": "^4.7.1",
42
42
  "xxhash-wasm": "^1.1.0",
43
- "zod": "^3.25.57"
43
+ "zod": "^3.25.57",
44
+ "zod-to-json-schema": "^3.24.5"
44
45
  },
45
46
  "devDependencies": {
46
47
  "@ai-sdk/openai": "^1.3.22",
@@ -53,7 +54,7 @@
53
54
  "typescript-eslint": "^8.34.0",
54
55
  "vitest": "^3.2.3",
55
56
  "@internal/lint": "0.0.12",
56
- "@mastra/core": "0.10.6-alpha.0"
57
+ "@mastra/core": "0.10.6-alpha.3"
57
58
  },
58
59
  "peerDependencies": {
59
60
  "@mastra/core": ">=0.10.4-0 <0.11.0"
package/src/index.ts CHANGED
@@ -3,12 +3,19 @@ import type { CoreTool, MastraMessageV1 } from '@mastra/core';
3
3
  import { MessageList } from '@mastra/core/agent';
4
4
  import type { MastraMessageV2 } from '@mastra/core/agent';
5
5
  import { MastraMemory } from '@mastra/core/memory';
6
- import type { MemoryConfig, SharedMemoryConfig, StorageThreadType } from '@mastra/core/memory';
6
+ import type {
7
+ MemoryConfig,
8
+ SharedMemoryConfig,
9
+ StorageThreadType,
10
+ WorkingMemoryFormat,
11
+ WorkingMemoryTemplate,
12
+ } from '@mastra/core/memory';
7
13
  import type { StorageGetMessagesArg } from '@mastra/core/storage';
8
14
  import { embedMany } from 'ai';
9
15
  import type { CoreMessage, TextPart, UIMessage } from 'ai';
10
16
 
11
17
  import xxhash from 'xxhash-wasm';
18
+ import zodToJsonSchema from 'zod-to-json-schema';
12
19
  import { updateWorkingMemoryTool } from './tools/working-memory';
13
20
 
14
21
  // Average characters per token based on OpenAI's tokenization
@@ -247,10 +254,16 @@ export class Memory extends MastraMemory {
247
254
 
248
255
  if (config.workingMemory?.enabled && !thread?.metadata?.workingMemory) {
249
256
  // if working memory is enabled but the thread doesn't have it, we need to set it
257
+ let workingMemory = config.workingMemory.template || this.defaultWorkingMemoryTemplate;
258
+
259
+ if (config.workingMemory.schema) {
260
+ workingMemory = JSON.stringify(zodToJsonSchema(config.workingMemory.schema));
261
+ }
262
+
250
263
  return this.storage.saveThread({
251
264
  thread: deepMerge(thread, {
252
265
  metadata: {
253
- workingMemory: config.workingMemory.template || this.defaultWorkingMemoryTemplate,
266
+ workingMemory,
254
267
  },
255
268
  }),
256
269
  });
@@ -540,20 +553,54 @@ export class Memory extends MastraMemory {
540
553
  return null;
541
554
  }
542
555
 
543
- public async getWorkingMemory({ threadId }: { threadId: string }): Promise<string | null> {
544
- if (!this.threadConfig.workingMemory?.enabled) return null;
556
+ public async getWorkingMemory({
557
+ threadId,
558
+ format,
559
+ }: {
560
+ threadId: string;
561
+ format?: WorkingMemoryFormat;
562
+ }): Promise<string | null> {
563
+ if (!this.threadConfig.workingMemory?.enabled) {
564
+ return null;
565
+ }
545
566
 
546
- // Get thread from storage
547
567
  const thread = await this.storage.getThreadById({ threadId });
548
- if (!thread) return this.threadConfig?.workingMemory?.template || this.defaultWorkingMemoryTemplate;
568
+
569
+ if (format === 'json') {
570
+ try {
571
+ return JSON.parse(thread?.metadata?.workingMemory as string) || null;
572
+ } catch (e) {
573
+ this.logger.error('Unable to parse working memory as JSON. Returning string.', e);
574
+ }
575
+ }
576
+
577
+ return thread?.metadata?.workingMemory ? JSON.stringify(thread?.metadata?.workingMemory) : null;
578
+ }
579
+
580
+ public async getWorkingMemoryTemplate(): Promise<WorkingMemoryTemplate | null> {
581
+ if (!this.threadConfig.workingMemory?.enabled) {
582
+ return null;
583
+ }
584
+
585
+ // Get thread from storage
586
+ if (this.threadConfig?.workingMemory?.schema) {
587
+ try {
588
+ const schema = this.threadConfig.workingMemory.schema;
589
+ const convertedSchema = zodToJsonSchema(schema, {
590
+ $refStrategy: 'none',
591
+ });
592
+
593
+ return { format: 'json', content: JSON.stringify(convertedSchema) };
594
+ } catch (error) {
595
+ this.logger.error('Error converting schema', error);
596
+ throw error;
597
+ }
598
+ }
549
599
 
550
600
  // Return working memory from metadata
551
- const memory =
552
- (thread.metadata?.workingMemory as string) ||
553
- this.threadConfig.workingMemory.template ||
554
- this.defaultWorkingMemoryTemplate;
601
+ const memory = this.threadConfig.workingMemory.template || this.defaultWorkingMemoryTemplate;
555
602
 
556
- return memory.trim();
603
+ return { format: 'markdown', content: memory.trim() };
557
604
  }
558
605
 
559
606
  public async getSystemMessage({
@@ -568,12 +615,32 @@ export class Memory extends MastraMemory {
568
615
  return null;
569
616
  }
570
617
 
618
+ const workingMemoryTemplate = await this.getWorkingMemoryTemplate();
619
+ const workingMemoryData = await this.getWorkingMemory({ threadId });
620
+
621
+ if (!workingMemoryTemplate) {
622
+ return null;
623
+ }
624
+
625
+ return this.getWorkingMemoryToolInstruction({
626
+ template: workingMemoryTemplate,
627
+ data: workingMemoryData,
628
+ });
629
+ }
630
+
631
+ public async getUserContextMessage({ threadId }: { threadId: string }) {
571
632
  const workingMemory = await this.getWorkingMemory({ threadId });
572
633
  if (!workingMemory) {
573
634
  return null;
574
635
  }
575
636
 
576
- return this.getWorkingMemoryToolInstruction(workingMemory);
637
+ return `The following is the most up-to-date information about the user's state and context:
638
+ ${JSON.stringify(workingMemory)}
639
+ Use this information as the source of truth when generating responses.
640
+ Do not reference or mention this memory directly to the user.
641
+ If conversation history shows information that is not in the working memory, use the working memory as the source of truth.
642
+ If there is a discrepancy between this information and conversation history, always rely on this information unless the user explicitly asks for an update.
643
+ `;
577
644
  }
578
645
 
579
646
  public defaultWorkingMemoryTemplate = `
@@ -589,34 +656,49 @@ export class Memory extends MastraMemory {
589
656
  - **Projects**:
590
657
  `;
591
658
 
592
- private getWorkingMemoryToolInstruction(workingMemoryBlock: string) {
659
+ private getWorkingMemoryToolInstruction({
660
+ template,
661
+ data,
662
+ }: {
663
+ template: WorkingMemoryTemplate;
664
+ data: string | null;
665
+ }) {
593
666
  return `WORKING_MEMORY_SYSTEM_INSTRUCTION:
594
667
  Store and update any conversation-relevant information by calling the updateWorkingMemory tool. If information might be referenced again - store it!
595
668
 
596
669
  Guidelines:
597
670
  1. Store anything that could be useful later in the conversation
598
671
  2. Update proactively when information changes, no matter how small
599
- 3. Use Markdown format for all data
672
+ 3. Use ${template.format === 'json' ? 'JSON' : 'Markdown'} format for all data
600
673
  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"
601
674
 
602
- Memory Structure:
603
- ${workingMemoryBlock}
675
+ WORKING MEMORY TEMPLATE:
676
+ ${template.content}
677
+
678
+ WORKING MEMORY DATA:
679
+ ${data}
604
680
 
605
681
  Notes:
606
682
  - Update memory whenever referenced information changes
607
683
  - 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)
608
684
  - 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
609
685
  - Do not remove empty sections - you must include the empty sections along with the ones you're filling in
610
- - REMEMBER: the way you update your working memory is by calling the updateWorkingMemory tool with the entire Markdown content. The system will store it for you. The user will not see it.
686
+ - REMEMBER: the way you update your working memory is by calling the updateWorkingMemory tool with the entire ${template.format === 'json' ? 'JSON' : 'Markdown'} content. The system will store it for you. The user will not see it.
611
687
  - IMPORTANT: You MUST call updateWorkingMemory in every response to a prompt where you received relevant information.
612
- - IMPORTANT: Preserve the Markdown formatting structure above while updating the content.`;
688
+ - IMPORTANT: Preserve the ${template.format === 'json' ? 'JSON' : 'Markdown'} formatting structure above while updating the content.`;
613
689
  }
614
690
 
615
691
  public getTools(config?: MemoryConfig): Record<string, CoreTool> {
616
692
  const mergedConfig = this.getMergedThreadConfig(config);
617
693
  if (mergedConfig.workingMemory?.enabled) {
694
+ if (mergedConfig.workingMemory.schema) {
695
+ return {
696
+ updateWorkingMemory: updateWorkingMemoryTool({ format: 'json' }),
697
+ };
698
+ }
699
+
618
700
  return {
619
- updateWorkingMemory: updateWorkingMemoryTool,
701
+ updateWorkingMemory: updateWorkingMemoryTool({ format: 'markdown' }),
620
702
  };
621
703
  }
622
704
  return {};
@@ -1,33 +1,43 @@
1
1
  import type { CoreTool } from '@mastra/core';
2
+ import type { WorkingMemoryFormat } from '@mastra/core/memory';
2
3
  import { z } from 'zod';
3
4
 
4
- export const updateWorkingMemoryTool: CoreTool = {
5
+ export const updateWorkingMemoryTool = ({ format }: { format: WorkingMemoryFormat }): CoreTool => ({
5
6
  description: 'Update the working memory with new information',
6
7
  parameters: z.object({
7
- memory: z.string().describe('The Markdown-formatted working memory content to store'),
8
+ memory: z
9
+ .string()
10
+ .describe(`The ${format === 'json' ? 'JSON' : 'Markdown'} formatted working memory content to store`),
8
11
  }),
9
12
  execute: async (params: any) => {
10
- const { context, threadId, memory } = params;
13
+ const { context, threadId, memory, resourceId } = params;
11
14
  if (!threadId || !memory) {
12
15
  throw new Error('Thread ID and Memory instance are required for working memory updates');
13
16
  }
14
17
 
15
18
  const thread = await memory.getThreadById({ threadId });
19
+
16
20
  if (!thread) {
17
21
  throw new Error(`Thread ${threadId} not found`);
18
22
  }
19
23
 
24
+ if (thread.resourceId && thread.resourceId !== resourceId) {
25
+ throw new Error(`Thread with id ${threadId} resourceId does not match the current resourceId ${resourceId}`);
26
+ }
27
+
28
+ const workingMemory = context.memory;
29
+
20
30
  // Update thread metadata with new working memory
21
31
  await memory.saveThread({
22
32
  thread: {
23
33
  ...thread,
24
34
  metadata: {
25
35
  ...thread.metadata,
26
- workingMemory: context.memory,
36
+ workingMemory: workingMemory,
27
37
  },
28
38
  },
29
39
  });
30
40
 
31
41
  return { success: true };
32
42
  },
33
- };
43
+ });