Working memory provides agents with a persistent, structured record of user information that evolves throughout conversations. It enables personalization and context continuity across sessions.
How It Works
Working memory operates through two components:
WorkingMemory Processor : Injects memory data as a system message and provides instructions
updateWorkingMemory Tool : Allows agents to update the memory structure
The processor is input-only - it retrieves and injects memory, while updates happen via tool calls.
Basic Configuration
Enable working memory with a template:
import { Memory } from '@mastra/core' ;
import { LibSQLStore } from '@mastra/store-libsql' ;
const memory = new Memory ({
storage: new LibSQLStore ({
id: 'agent-memory' ,
url: 'file:./memory.db'
}),
options: {
workingMemory: {
enabled: true ,
scope: 'resource' , // Shared across all user threads
template: `
# User Profile
- **First Name**:
- **Last Name**:
- **Location**:
- **Occupation**:
- **Interests**:
- **Goals**:
- **Preferences**:
`
}
}
});
Configuration Options
Enable or disable working memory
scope
'thread' | 'resource'
default: "'resource'"
Memory scope:
resource: Shared across all threads for a user (recommended)
thread: Isolated per conversation thread
Markdown template defining the memory structure
JSON schema for structured memory (alternative to template)
version
'stable' | 'vnext'
default: "'stable'"
Memory update behavior version:
stable: Updates on every relevant message
vnext: Only updates when information changes
Template-Based Memory
Define memory structure with a Markdown template:
const memory = new Memory ({
storage ,
options: {
workingMemory: {
enabled: true ,
template: `
# Customer Profile
## Personal Information
- **Name**:
- **Email**:
- **Phone**:
## Preferences
- **Communication Style**:
- **Preferred Contact Time**:
- **Topics of Interest**:
## Interaction History
- **Last Issue**:
- **Resolution Status**:
- **Follow-up Required**:
`
}
}
});
The agent will automatically update this structure as it learns information:
const agent = new Agent ({
name: 'Support Agent' ,
model: 'openai/gpt-4o' ,
memory
});
// User provides information
const result = await agent . generate (
"Hi, I'm John Smith. I prefer email communication and I'm interested in your enterprise plans." ,
{
threadId: 'thread-123' ,
resourceId: 'user-123'
}
);
// Agent automatically updates working memory:
/*
# Customer Profile
## Personal Information
- **Name**: John Smith
- **Email**:
- **Phone**:
## Preferences
- **Communication Style**: Email preferred
- **Preferred Contact Time**:
- **Topics of Interest**: Enterprise plans
## Interaction History
- **Last Issue**:
- **Resolution Status**:
- **Follow-up Required**:
*/
Schema-Based Memory
Use a Zod schema for type-safe structured memory:
import { z } from 'zod' ;
const memory = new Memory ({
storage ,
options: {
workingMemory: {
enabled: true ,
schema: z . object ({
personalInfo: z . object ({
name: z . string (),
email: z . string (). email (). optional (),
phone: z . string (). optional ()
}),
preferences: z . object ({
communicationStyle: z . string (),
topics: z . array ( z . string ())
}),
context: z . object ({
lastIssue: z . string (). optional (),
resolutionStatus: z . enum ([ 'open' , 'in-progress' , 'resolved' ]). optional ()
})
})
}
}
});
Memory Scopes
Resource Scope (Recommended)
Memory persists across all threads for a user:
const memory = new Memory ({
storage ,
options: {
workingMemory: {
enabled: true ,
scope: 'resource' // Shared across all threads
}
}
});
// Thread 1: User provides preferences
await agent . generate ( "I prefer morning meetings" , {
threadId: 'thread-1' ,
resourceId: 'user-123'
});
// Thread 2: Agent remembers from resource memory
await agent . generate ( "When can we schedule a call?" , {
threadId: 'thread-2' , // Different thread
resourceId: 'user-123' // Same resource
});
// Agent: "Based on your preference for morning meetings, how about 10 AM?"
Thread Scope
Memory is isolated to a single thread:
const memory = new Memory ({
storage ,
options: {
workingMemory: {
enabled: true ,
scope: 'thread' // Per-thread memory
}
}
});
// Each thread maintains its own working memory
Update Behavior Versions
Stable (Default)
Agent updates memory on every relevant message:
const memory = new Memory ({
storage ,
options: {
workingMemory: {
enabled: true ,
// version: 'stable' (default)
}
}
});
VNext
Agent only updates when information actually changes:
const memory = new Memory ({
storage ,
options: {
workingMemory: {
enabled: true ,
version: 'vnext'
}
}
});
VNext reduces unnecessary updates and tool calls, improving performance for long conversations.
Read-Only Mode
Provide memory context without allowing updates:
const routingAgent = new Agent ({
name: 'Router' ,
model: 'openai/gpt-4o-mini' ,
memory ,
memoryConfig: {
readOnly: true // Can read working memory but not update
}
});
In read-only mode:
Memory data is still injected as context
No updateWorkingMemory tool is provided
No update instructions are included
Retrieving Working Memory
Access working memory programmatically:
// Get working memory for a thread
const threadMemory = await memory . getWorkingMemory ({
threadId: 'thread-123' ,
resourceId: 'user-123'
});
console . log ( threadMemory );
/*
# User Profile
- **First Name**: John
- **Last Name**: Smith
- **Location**: San Francisco
- **Occupation**: Software Engineer
*/
// Get working memory template
const template = await memory . getWorkingMemoryTemplate ({
memoryConfig: {
workingMemory: {
enabled: true
}
}
});
Manual Updates
Update working memory directly:
await memory . updateWorkingMemory ({
threadId: 'thread-123' ,
resourceId: 'user-123' ,
workingMemory: `
# User Profile
- **First Name**: John
- **Last Name**: Smith
- **Location**: San Francisco
- **Occupation**: Software Engineer
- **Interests**: AI, Machine Learning
`
});
Custom Instructions
Provide domain-specific guidance for memory updates:
const memory = new Memory ({
storage ,
options: {
workingMemory: {
enabled: true ,
template: `
# Sales Lead Profile
- **Company**:
- **Industry**:
- **Budget Range**:
- **Decision Timeline**:
- **Pain Points**:
`
}
}
});
const agent = new Agent ({
name: 'Sales Agent' ,
model: 'openai/gpt-4o' ,
instructions: `You are a sales agent helping qualify leads.
Focus on understanding:
1. Company background and size
2. Budget constraints
3. Decision-making timeline
4. Key pain points and challenges
Update working memory as you gather this information.` ,
memory
});
Implementation Details
The WorkingMemory processor injects memory as a system message:
class WorkingMemory implements Processor {
readonly id = 'working-memory' ;
async processInput ( args ) {
const { messageList , requestContext } = args ;
// Get memory context
const memoryContext = parseMemoryRequestContext ( requestContext );
const threadId = memoryContext ?. thread ?. id ;
const resourceId = memoryContext ?. resourceId ;
if ( ! threadId && ! resourceId ) return messageList ;
// Determine scope
const scope = this . options . scope || 'resource' ;
// Retrieve working memory
let workingMemoryData : string | null = null ;
if ( scope === 'thread' && threadId ) {
const thread = await this . options . storage . getThreadById ({ threadId });
workingMemoryData = thread ?. metadata ?. workingMemory || null ;
} else if ( scope === 'resource' && resourceId ) {
const resource = await this . options . storage . getResourceById ({ resourceId });
workingMemoryData = resource ?. workingMemory || null ;
}
// Get template
const template = this . options . template || {
format: 'markdown' ,
content: this . defaultWorkingMemoryTemplate
};
// Check if read-only
const isReadOnly = this . options . readOnly ||
memoryContext . memoryConfig ?. readOnly ;
// Format instruction
let instruction : string ;
if ( isReadOnly ) {
instruction = this . getReadOnlyWorkingMemoryInstruction ({
template ,
data: workingMemoryData
});
} else if ( this . options . useVNext ) {
instruction = this . getWorkingMemoryToolInstructionVNext ({
template ,
data: workingMemoryData
});
} else {
instruction = this . getWorkingMemoryToolInstruction ({
template ,
data: workingMemoryData
});
}
// Add to message list
if ( instruction ) {
messageList . addSystem ( instruction , 'memory' );
}
return messageList ;
}
}
Best Practices
Use Resource Scope Default to resource scope for user-level information that should persist across threads.
Keep Templates Focused Only include information relevant to the agent’s purpose. Avoid bloated templates.
Use VNext for Long Conversations Enable version: 'vnext' to reduce unnecessary updates in extended interactions.
Read-Only for Routing Use read-only mode for orchestration agents to prevent memory pollution.
Use Cases
workingMemory : {
enabled : true ,
template : `
# Support Ticket Context
- **Issue Category**:
- **Priority Level**:
- **Previous Issues**:
- **Resolution History**:
- **Customer Sentiment**:
`
}
workingMemory : {
enabled : true ,
template : `
# User Context
- **Name**:
- **Timezone**:
- **Work Schedule**:
- **Current Projects**:
- **Important Dates**:
- **Communication Preferences**:
`
}
workingMemory : {
enabled : true ,
template : `
# Lead Information
- **Company Name**:
- **Industry**:
- **Company Size**:
- **Budget Range**:
- **Decision Timeline**:
- **Key Pain Points**:
- **Decision Makers**:
`
}
Next Steps
Conversation History Manage recent message persistence
Semantic Recall Add RAG-based retrieval for older messages
Memory Overview Learn about all memory types in Mastra