Skip to main content
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:
  1. On Input: Fetches historical messages from storage and prepends them to the context
  2. 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:
  1. Removes streaming tool calls: Filters partial-call states (intermediate streaming states)
  2. Removes updateWorkingMemory calls: Hides working memory tool invocations from history
  3. 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

Build docs developers (and LLMs) love