Message history
Message history is the most basic and important form of memory. It gives the LLM a view of recent messages in the context window, enabling your agent to reference earlier exchanges and respond coherently.
You can also retrieve message history to display past conversations in your UI.
Each message belongs to a thread (the conversation) and a resource (the user or entity it's associated with). See Threads and resources for more detail.
When you use memory with a client application, send only the new message from the client instead of the full conversation history.
Sending the full history is redundant because Mastra loads messages from storage, and it can cause message ordering bugs when client-side timestamps conflict with stored timestamps.
For an AI SDK example, see Using Mastra Memory.
Threads and resourcesDirect link to Threads and resources
Mastra organizes conversations using two identifiers:
- Thread: A conversation session containing a sequence of messages.
- Resource: The entity that owns the thread, such as a user, organization, project, or another domain entity in your application.
Studio automatically generates a thread and resource ID for you. When calling stream() or generate() yourself, provide these identifiers explicitly.
Getting startedDirect link to Getting started
Install the Mastra memory module along with a storage adapter for your database. The examples below use @mastra/libsql, which stores data locally in a mastra.db file.
- npm
- pnpm
- Yarn
- Bun
npm install @mastra/memory@latest @mastra/libsql@latest
pnpm add @mastra/memory@latest @mastra/libsql@latest
yarn add @mastra/memory@latest @mastra/libsql@latest
bun add @mastra/memory@latest @mastra/libsql@latest
Message history requires a storage adapter to persist conversations. Configure storage on your Mastra instance if you haven't already:
import { Mastra } from '@mastra/core'
import { LibSQLStore } from '@mastra/libsql'
export const mastra = new Mastra({
storage: new LibSQLStore({
id: 'mastra-storage',
url: 'file:./mastra.db',
}),
})
Instantiate a Memory instance in your agent:
import { Memory } from '@mastra/memory'
import { Agent } from '@mastra/core/agent'
export const agent = new Agent({
id: 'test-agent',
memory: new Memory({
options: {
lastMessages: 10,
},
}),
})
When you call the agent, messages are automatically saved to the database. You can specify a threadId, resourceId, and optional metadata:
- .generate()
- .stream()
await agent.generate('Hello', {
memory: {
thread: {
id: 'thread-123',
title: 'Support conversation',
metadata: { category: 'billing' },
},
resource: 'user-456',
},
})
await agent.stream('Hello', {
memory: {
thread: {
id: 'thread-123',
title: 'Support conversation',
metadata: { category: 'billing' },
},
resource: 'user-456',
},
})
Threads and messages are created automatically when you call agent.generate() or agent.stream(), but you can also create them manually with createThread() and saveMessages().
You can use this history in two ways:
- Automatic inclusion: Mastra automatically includes recent messages in the context window. The default of 10 messages keeps agents grounded in the conversation. Adjust it with
lastMessageswhen needed. - Manual querying: For more control, query threads and messages directly with
recall(). Use the results to choose which memories enter the context window or to render conversation history in your UI.
lastMessages counts every stored message, including tool calls, tool results, and signals of any kind, so a single turn can add several messages to the count. The window also slides forward on every request: once a thread grows past the limit, the oldest message leaves context on each turn, which changes the start of the prompt and invalidates the provider prompt cache. For long-running conversations, use Observational Memory, which keeps the prompt prefix stable.
When memory is enabled, Studio uses message history to display past conversations in the chat sidebar.
Thread title generationDirect link to Thread title generation
Mastra can automatically generate descriptive thread titles from the conversation transcript when generateTitle is enabled. Use this option when you build a chat interface that renders conversation titles in a thread list or sidebar.
import { Agent } from '@mastra/core/agent'
import { Memory } from '@mastra/memory'
export const supportAgent = new Agent({
id: 'support-agent',
name: 'Support agent',
instructions: 'Answer customer support questions.',
model: 'openai/gpt-5.6-sol',
memory: new Memory({
options: {
generateTitle: true,
},
}),
})
Title generation runs asynchronously after the agent responds and doesn't affect response time.
To optimize cost or behavior, provide a smaller model and custom instructions:
import { Agent } from '@mastra/core/agent'
import { Memory } from '@mastra/memory'
export const supportAgent = new Agent({
id: 'support-agent',
name: 'Support agent',
instructions: 'Answer customer support questions.',
model: 'openai/gpt-5.6-sol',
memory: new Memory({
options: {
generateTitle: {
model: 'openai/gpt-5-mini',
instructions: 'Generate a one-word title.',
},
},
}),
})
Accessing memoryDirect link to Accessing memory
To access memory functions for querying, cloning, or deleting threads and messages, call getMemory() on an agent:
const agent = mastra.getAgentById('test-agent')
const memory = await agent.getMemory()
Use the Memory instance to query stored threads and messages or clone a conversation.
QueryingDirect link to Querying
Use these methods to fetch threads and messages for displaying conversation history in your UI or for custom memory retrieval logic.
The memory system doesn't enforce access control. Before running any query, verify in your application logic that the current user is authorized to access the resourceId being queried.
ThreadsDirect link to Threads
Use listThreads() to retrieve threads for a resource:
const result = await memory.listThreads({
filter: { resourceId: 'user-123' },
perPage: false,
})
Paginate through threads:
const result = await memory.listThreads({
filter: { resourceId: 'user-123' },
page: 0,
perPage: 10,
})
console.log(result.threads) // thread objects
console.log(result.hasMore) // more pages available?
You can also filter by metadata and control sort order:
const result = await memory.listThreads({
filter: {
resourceId: 'user-123',
metadata: { status: 'active' },
},
orderBy: { field: 'createdAt', direction: 'DESC' },
})
To fetch a single thread by ID, use getThreadById():
const thread = await memory.getThreadById({ threadId: 'thread-123' })
MessagesDirect link to Messages
Once you have a thread, use recall() to retrieve its messages. It supports pagination and semantic search, with optional date filtering.
Fetch a thread's history without pagination. Recall hides reminder signals by default; pass hideSignals: false to include them, true to hide all recognized signals, or an array to omit selected types. See signal visibility and compatibility for matching rules and precedence.
const { messages } = await memory.recall({
threadId: 'thread-123',
perPage: false,
})
Paginate through messages:
const { messages } = await memory.recall({
threadId: 'thread-123',
page: 0,
perPage: 50,
})
Filter by date range:
const { messages } = await memory.recall({
threadId: 'thread-123',
filter: {
dateRange: {
start: new Date('2025-01-01'),
end: new Date('2025-06-01'),
},
},
})
Filter by shallow message metadata:
const { messages } = await memory.recall({
threadId: 'thread-123',
filter: {
metadata: {
category: 'billing',
escalated: true,
priority: 2,
archivedAt: null,
},
},
})
Metadata filters match shallow scalar values only: string, finite number, boolean, and null.
All specified metadata keys use AND semantics. A null filter matches only an explicit null value. A missing metadata key doesn't match.
Metadata keys must start with a letter or underscore and contain only alphanumeric characters. They must be 128 characters or fewer and can't use reserved prototype keys such as __proto__, constructor, or prototype.
Performance depends on the storage backend. Some backends can push parts of the filter into the database, while others scan candidate messages after thread, resource, and date constraints are applied but before pagination.
Fetch a single message by ID:
const { messages } = await memory.recall({
threadId: 'thread-123',
include: [{ id: 'msg-123' }],
})
Fetch multiple messages by ID with surrounding context:
const { messages } = await memory.recall({
threadId: 'thread-123',
include: [
{ id: 'msg-123' },
{
id: 'msg-456',
withPreviousMessages: 3,
withNextMessages: 1,
},
],
})
Search by meaning (see Semantic recall for setup):
const { messages } = await memory.recall({
threadId: 'thread-123',
vectorSearchString: 'project deadline discussion',
threadConfig: {
semanticRecall: true,
},
})
UI formatDirect link to UI format
Message queries return MastraDBMessage[] format. To display messages in a frontend, you may need to convert them to a format your UI library expects. For example, toAISdkV5Messages converts messages to AI SDK UI format.
Thread cloningDirect link to Thread cloning
Thread cloning creates a copy of an existing thread with its messages. This is useful for branching conversations or creating checkpoints before a potentially destructive operation, or alternatively testing variations of a conversation.
const { thread, clonedMessages } = await memory.cloneThread({
sourceThreadId: 'thread-123',
title: 'Branched conversation',
})
You can filter cloned messages by count or date range and specify custom thread IDs. Utility methods are also available to inspect clone relationships.
If you don't need the copied messages returned, for example when forking a long thread, use copyThread(). It never returns message payloads, and on LibSQL and PostgreSQL the rows are copied inside the database. When semantic recall is enabled, the copied messages are still read back in batches to generate embeddings.
See cloneThread(), copyThread(), and clone utilities for the full API.
Deleting messagesDirect link to Deleting messages
To remove messages from a thread, use deleteMessages(). You can delete by message ID or clear all messages from a thread.