Conversation history provides short-term continuity by storing and retrieving recent messages from the current thread. It’s the foundation of contextual conversations in Mastra.
How It Works
The MessageHistory processor handles both retrieval and persistence:
On Input : Fetches historical messages from storage and prepends them to the context
On Output : Persists new messages to storage (excluding system messages)
Basic Configuration
Enable conversation history by setting lastMessages:
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: {
lastMessages: 10 // Include last 10 messages
}
});
Configuration Options
lastMessages
number | false
default: "10"
Number of recent messages to include. Set to false to disable conversation history entirely.
const memory = new Memory ({
storage ,
options: {
lastMessages: 20 // Retrieve 20 most recent messages
}
});
// Disable conversation history
const memoryWithoutHistory = new Memory ({
storage ,
options: {
lastMessages: false
}
});
Message Storage
Messages are stored in the database with metadata:
type MastraDBMessage = {
id : string ;
role : 'user' | 'assistant' | 'tool' ;
content : UserContent | AssistantContent | ToolContent ;
threadId : string ;
resourceId : string ;
createdAt : Date ;
toolCallIds ?: string [];
toolCallArgs ?: Record < string , unknown >[];
toolNames ?: string [];
};
Working with Threads
Each conversation happens within a thread:
import { Agent , Memory } from '@mastra/core' ;
const agent = new Agent ({
name: 'Assistant' ,
model: 'openai/gpt-4o' ,
memory
});
// Create a new thread
const thread = await memory . createThread ({
resourceId: 'user-123' ,
title: 'Product inquiry'
});
// Generate with thread context
const result = await agent . generate ( 'What products do you have?' , {
threadId: thread . id ,
resourceId: 'user-123'
});
// Continue the conversation
const followUp = await agent . generate ( 'Tell me more about the first one' , {
threadId: thread . id ,
resourceId: 'user-123'
});
Listing Messages
Retrieve messages from a thread:
// Get recent messages
const { messages } = await memory . recall ({
threadId: 'thread-123' ,
resourceId: 'user-123'
});
console . log ( `Retrieved ${ messages . length } messages` );
messages . forEach ( msg => {
console . log ( ` ${ msg . role } : ${ msg . content } ` );
});
Message Filtering
The MessageHistory processor automatically filters messages before persistence:
Removes streaming tool calls : Filters partial-call states (intermediate streaming states)
Removes updateWorkingMemory calls : Hides working memory tool invocations from history
Strips working memory tags : Removes <working_memory> tags from text content
System messages are never stored in the database - they’re instructions, not conversation content.
Advanced: Manual Message Persistence
For custom use cases, you can manually persist messages:
import { MessageHistory } from '@mastra/core/processors' ;
const messageHistory = new MessageHistory ({
storage: memoryStorage ,
lastMessages: 10
});
// Persist messages manually
await messageHistory . persistMessages ({
messages: [
{
id: 'msg-1' ,
role: 'user' ,
content: { content: 'Hello' , parts: [] },
threadId: 'thread-123' ,
resourceId: 'user-123' ,
createdAt: new Date ()
}
],
threadId: 'thread-123' ,
resourceId: 'user-123'
});
Thread Management
List Threads
// List all threads for a resource
const { threads , total } = await memory . listThreads ({
filter: { resourceId: 'user-123' },
page: 0 ,
perPage: 20 ,
orderBy: { field: 'updatedAt' , direction: 'DESC' }
});
// Filter by metadata
const { threads : supportThreads } = await memory . listThreads ({
filter: {
resourceId: 'user-123' ,
metadata: {
category: 'support' ,
status: 'open'
}
}
});
Update Thread
// Update thread metadata
const thread = await memory . getThreadById ({ threadId: 'thread-123' });
if ( thread ) {
await memory . saveThread ({
thread: {
... thread ,
title: 'Resolved: Product inquiry' ,
metadata: {
... thread . metadata ,
status: 'resolved' ,
resolvedAt: new Date (). toISOString ()
}
}
});
}
Clone Thread
// Clone a thread with its messages
const { newThread , copiedMessages } = await memory . cloneThread ({
sourceThreadId: 'thread-123' ,
targetThreadId: 'thread-456' ,
resourceId: 'user-123'
});
console . log ( `Cloned ${ copiedMessages . length } messages to new thread` );
Delete Messages
// Delete specific messages
await memory . deleteMessages ([ 'msg-1' , 'msg-2' ]);
// Or using objects
await memory . deleteMessages ([
{ id: 'msg-1' },
{ id: 'msg-2' }
]);
Read-Only Mode
Prevent agents from modifying conversation history:
const routingAgent = new Agent ({
name: 'Router' ,
model: 'openai/gpt-4o-mini' ,
memory ,
memoryConfig: {
readOnly: true // Can read history but won't save messages
}
});
Implementation Details
The MessageHistory processor retrieves context from RequestContext or MessageList:
class MessageHistory implements Processor {
readonly id = 'message-history' ;
async processInput ( args ) {
const { messageList , requestContext } = args ;
// Get memory context
const context = this . getMemoryContext ( requestContext , messageList );
if ( ! context ) return messageList ;
const { threadId , resourceId } = context ;
// Fetch historical messages
const result = await this . storage . listMessages ({
threadId ,
resourceId ,
page: 0 ,
perPage: this . lastMessages ,
orderBy: { field: 'createdAt' , direction: 'DESC' }
});
// Filter and add to message list
const filtered = result . messages . filter ( msg => msg . role !== 'system' );
for ( const msg of filtered . reverse ()) {
messageList . add ( msg , 'memory' );
}
return messageList ;
}
async processOutputResult ( args ) {
const { messageList , requestContext } = args ;
// Get context and check readOnly
const context = this . getMemoryContext ( requestContext , messageList );
const memoryContext = parseMemoryRequestContext ( requestContext );
const readOnly = memoryContext ?. memoryConfig ?. readOnly ;
if ( ! context || readOnly ) return messageList ;
// Persist new messages
const newInput = messageList . get . input . db ();
const newOutput = messageList . get . response . db ();
await this . persistMessages ({
messages: [ ... newInput , ... newOutput ],
threadId: context . threadId ,
resourceId: context . resourceId
});
return messageList ;
}
}
Best Practices
Balance History Length Use 10-20 messages for most use cases. More history increases token usage.
Use Metadata Add metadata to threads for filtering and organization (category, priority, status).
Combine with Semantic Recall Use history for recent context, semantic recall for older relevant information.
Clean Up Old Threads Periodically delete old threads to manage storage and improve performance.
Next Steps
Semantic Recall Add RAG-based retrieval for older messages
Working Memory Store structured user information
Memory Overview Learn about all memory types