Skip to content

Conversation History Management

This example demonstrates how to manage conversation history, implement memory limits, and handle multi-turn conversations effectively.

Overview

The conversation history management example shows how to:

  • Maintain conversation context across multiple interactions
  • Implement memory limits and history pruning
  • Test conversation history persistence
  • Handle conversation serialization and deserialization
  • Optimize memory usage for long conversations

Source Code

Location: Package-owned examples under packages/agents/examples/

Key Concepts

1. Basic History Management

typescript
import { Robota } from '@robota-sdk/agent-core';
import { OpenAIProvider } from '@robota-sdk/agent-provider-openai';

const robota = new Robota({
  aiProviders: [openaiProvider],
  defaultModel: {
    provider: 'openai',
    model: 'gpt-3.5-turbo',
    systemMessage: 'You are a helpful assistant with memory of our conversation.',
  },
  maxHistoryLength: 10, // Keep last 10 messages
});

// Sequential conversation with memory
await robota.run('Hello, my name is Alice');
await robota.run('I work as a software engineer');
const response = await robota.run('What do you remember about me?');
// AI should remember the name and profession

2. History Persistence Testing

typescript
async function testHistoryPersistence() {
  console.log('Testing conversation history persistence...');

  // Build conversation context
  await robota.run('I am planning a trip to Japan');
  await robota.run('I want to visit Tokyo and Kyoto');
  await robota.run('My budget is $3000');

  // Test memory retention
  const response = await robota.run('Can you summarize my travel plans?');

  // Verify AI remembers all the details
  const remembersDestination = response.toLowerCase().includes('japan');
  const remembersCities =
    response.toLowerCase().includes('tokyo') && response.toLowerCase().includes('kyoto');
  const remembersBudget = response.includes('3000');

  console.log('Memory test results:', {
    remembersDestination,
    remembersCities,
    remembersBudget,
    overallScore: [remembersDestination, remembersCities, remembersBudget].filter(Boolean).length,
  });

  return response;
}

3. History Limits and Memory Management

typescript
async function testHistoryLimits() {
  console.log('Testing history limits...');

  // Create instance with small history limit
  const limitedRobota = new Robota({
    aiProviders: [openaiProvider],
    defaultModel: {
      provider: 'openai',
      model: 'gpt-3.5-turbo',
      systemMessage: 'Remember our conversation within your memory limits.',
    },
    maxHistoryLength: 3, // Very small limit for testing
  });

  // Add many messages to exceed limit
  const topics = ['dogs', 'cats', 'birds', 'fish', 'hamsters'];

  for (let i = 0; i < topics.length; i++) {
    await limitedRobota.run(`I love ${topics[i]}. They are my favorite pet #${i + 1}.`);
  }

  // Test what AI remembers (should only remember recent items)
  const response = await limitedRobota.run('What pets did I mention?');

  console.log('History limit test response:', response);

  // Should remember recent topics but not the earliest ones
  return response;
}

Running the Example

  1. Ensure setup is complete (see Setup Guide)

  2. Run a package-owned example (from repo root):

    bash
    npx tsx packages/agents/examples/basic-conversation.ts

Expected Output

===== Conversation History Management Test =====

----- Basic Memory Test -----
User: Hello, my name is Alice
AI: Hello Alice! Nice to meet you. How can I help you today?

User: I work as a software engineer
AI: That's great, Alice! Software engineering is an exciting field...

User: What do you remember about me?
AI: I remember that your name is Alice and you work as a software engineer...

✓ Basic memory test passed

----- Travel Planning Memory Test -----
Testing conversation history persistence...

User: I am planning a trip to Japan
AI: How exciting! Japan is a wonderful destination...

User: I want to visit Tokyo and Kyoto
AI: Excellent choices! Tokyo and Kyoto offer very different experiences...

User: My budget is $3000
AI: With a $3000 budget, you should be able to have a great trip to Tokyo and Kyoto...

User: Can you summarize my travel plans?
AI: Based on our conversation, you're planning a trip to Japan with a $3000 budget, specifically wanting to visit Tokyo and Kyoto...

Memory test results: {
  remembersDestination: true,
  remembersCities: true,
  remembersBudget: true,
  overallScore: 3
}

✓ Travel planning memory test passed

----- History Limits Test -----
Testing history limits...

User: I love dogs. They are my favorite pet #1.
User: I love cats. They are my favorite pet #2.
User: I love birds. They are my favorite pet #3.
User: I love fish. They are my favorite pet #4.
User: I love hamsters. They are my favorite pet #5.

User: What pets did I mention?
AI: From our recent conversation, I can see you mentioned birds, fish, and hamsters as pets you love...

✓ History limits working correctly (forgot earlier mentions)

===== Conversation History Tests Completed =====

Advanced History Management

1. Custom History Strategies

typescript
interface HistoryStrategy {
  shouldKeepMessage(message: any, index: number, allMessages: any[]): boolean;
  pruneHistory(messages: any[]): any[];
}

class SlidingWindowStrategy implements HistoryStrategy {
  constructor(private maxMessages: number) {}

  shouldKeepMessage(message: any, index: number, allMessages: any[]): boolean {
    return index >= allMessages.length - this.maxMessages;
  }

  pruneHistory(messages: any[]): any[] {
    return messages.slice(-this.maxMessages);
  }
}

class ImportanceBasedStrategy implements HistoryStrategy {
  constructor(private maxMessages: number) {}

  shouldKeepMessage(message: any, index: number, allMessages: any[]): boolean {
    // Keep system messages and recent messages
    if (message.role === 'system') return true;
    if (index >= allMessages.length - Math.floor(this.maxMessages / 2)) return true;

    // Keep messages with important keywords
    const importantKeywords = ['name', 'important', 'remember', 'budget', 'deadline'];
    const hasImportantKeyword = importantKeywords.some((keyword) =>
      message.content?.toLowerCase().includes(keyword),
    );

    return hasImportantKeyword;
  }

  pruneHistory(messages: any[]): any[] {
    const kept = messages.filter((msg, index) => this.shouldKeepMessage(msg, index, messages));

    // If still too many, keep most recent
    return kept.length > this.maxMessages ? kept.slice(-this.maxMessages) : kept;
  }
}

class SmartHistoryManager {
  private strategy: HistoryStrategy;

  constructor(strategy: HistoryStrategy) {
    this.strategy = strategy;
  }

  manageHistory(currentHistory: any[]): any[] {
    return this.strategy.pruneHistory(currentHistory);
  }

  setStrategy(newStrategy: HistoryStrategy) {
    this.strategy = newStrategy;
  }
}

// Usage
const historyManager = new SmartHistoryManager(new ImportanceBasedStrategy(10));

const robota = new Robota({
  aiProviders: { openai: openaiProvider },
  currentModel: 'gpt-3.5-turbo',
  historyManager: historyManager,
  systemPrompt: 'You are an assistant with smart memory management.',
});

2. Conversation Summarization

typescript
class ConversationSummarizer {
  private robota: Robota;

  constructor(robota: Robota) {
    this.robota = robota;
  }

  async summarizeConversation(messages: any[]): Promise<string> {
    // Extract key conversation points
    const conversationText = messages
      .filter((msg) => msg.role !== 'system')
      .map((msg) => `${msg.role}: ${msg.content}`)
      .join('\n');

    const summaryPrompt = `
Please provide a concise summary of this conversation, focusing on:
1. Key facts mentioned by the user
2. Important decisions or preferences
3. Action items or next steps

Conversation:
${conversationText}

Summary:`;

    return await this.robota.run(summaryPrompt);
  }

  async createContextualSummary(messages: any[], maxSummaryLength: number = 200): Promise<string> {
    const summary = await this.summarizeConversation(messages);

    // Truncate if too long
    if (summary.length > maxSummaryLength) {
      return summary.substring(0, maxSummaryLength - 3) + '...';
    }

    return summary;
  }
}

class SummarizingHistoryStrategy implements HistoryStrategy {
  private summarizer: ConversationSummarizer;
  private maxMessages: number;
  private summaryInterval: number;

  constructor(
    summarizer: ConversationSummarizer,
    maxMessages: number = 10,
    summaryInterval: number = 20,
  ) {
    this.summarizer = summarizer;
    this.maxMessages = maxMessages;
    this.summaryInterval = summaryInterval;
  }

  shouldKeepMessage(message: any, index: number, allMessages: any[]): boolean {
    // Always keep recent messages
    return index >= allMessages.length - this.maxMessages;
  }

  async pruneHistory(messages: any[]): Promise<any[]> {
    if (messages.length <= this.maxMessages) {
      return messages;
    }

    // Get older messages to summarize
    const oldMessages = messages.slice(0, messages.length - this.maxMessages);
    const recentMessages = messages.slice(-this.maxMessages);

    // Create summary of old messages
    if (oldMessages.length >= this.summaryInterval) {
      const summary = await this.summarizer.createContextualSummary(oldMessages);

      // Replace old messages with summary
      const summaryMessage = {
        role: 'system',
        content: `Previous conversation summary: ${summary}`,
        timestamp: new Date().toISOString(),
        type: 'summary',
      };

      return [summaryMessage, ...recentMessages];
    }

    return messages;
  }
}
typescript
class ContextualMemory {
  private memories: Array<{
    content: string;
    timestamp: Date;
    importance: number;
    keywords: string[];
    context: string;
  }> = [];

  addMemory(content: string, importance: number = 1, context: string = 'general') {
    const keywords = this.extractKeywords(content);

    this.memories.push({
      content,
      timestamp: new Date(),
      importance,
      keywords,
      context,
    });

    // Keep memories sorted by importance and recency
    this.memories.sort((a, b) => {
      const importanceScore = b.importance - a.importance;
      if (Math.abs(importanceScore) < 0.1) {
        return b.timestamp.getTime() - a.timestamp.getTime();
      }
      return importanceScore;
    });

    // Limit memory size
    if (this.memories.length > 100) {
      this.memories = this.memories.slice(0, 100);
    }
  }

  searchMemories(query: string, context?: string, limit: number = 5): Array<any> {
    const queryKeywords = this.extractKeywords(query);

    return this.memories
      .filter((memory) => {
        // Filter by context if specified
        if (context && memory.context !== context) return false;

        // Check keyword overlap
        const keywordOverlap = memory.keywords.filter((keyword) =>
          queryKeywords.some((qk) => qk.includes(keyword) || keyword.includes(qk)),
        ).length;

        return keywordOverlap > 0;
      })
      .sort((a, b) => {
        // Score based on keyword relevance and importance
        const aScore = this.calculateRelevanceScore(a, queryKeywords);
        const bScore = this.calculateRelevanceScore(b, queryKeywords);
        return bScore - aScore;
      })
      .slice(0, limit);
  }

  private extractKeywords(text: string): string[] {
    // Simple keyword extraction (in production, use NLP library)
    return text
      .toLowerCase()
      .replace(/[^\w\s]/g, '')
      .split(/\s+/)
      .filter((word) => word.length > 3)
      .filter((word) => !['this', 'that', 'with', 'have', 'will', 'been', 'from'].includes(word));
  }

  private calculateRelevanceScore(memory: any, queryKeywords: string[]): number {
    const keywordOverlap = memory.keywords.filter((keyword) =>
      queryKeywords.some((qk) => qk.includes(keyword) || keyword.includes(qk)),
    ).length;

    const keywordScore = keywordOverlap / Math.max(memory.keywords.length, queryKeywords.length);
    const importanceScore = memory.importance;
    const recencyScore = (Date.now() - memory.timestamp.getTime()) / (1000 * 60 * 60 * 24); // Days old

    return keywordScore * 0.5 + importanceScore * 0.3 + (1 / (1 + recencyScore)) * 0.2;
  }

  getMemoryStats() {
    const contexts = [...new Set(this.memories.map((m) => m.context))];
    const avgImportance =
      this.memories.reduce((sum, m) => sum + m.importance, 0) / this.memories.length;

    return {
      totalMemories: this.memories.length,
      contexts: contexts,
      averageImportance: avgImportance,
      oldestMemory:
        this.memories.length > 0
          ? Math.min(...this.memories.map((m) => m.timestamp.getTime()))
          : null,
      newestMemory:
        this.memories.length > 0
          ? Math.max(...this.memories.map((m) => m.timestamp.getTime()))
          : null,
    };
  }
}

// Integration with Robota
class MemoryEnhancedRobota extends Robota {
  private contextualMemory: ContextualMemory;

  constructor(config: any) {
    super(config);
    this.contextualMemory = new ContextualMemory();
  }

  async run(prompt: string, context?: string): Promise<string> {
    // Search for relevant memories
    const relevantMemories = this.contextualMemory.searchMemories(prompt, context);

    // Enhance prompt with relevant context
    let enhancedPrompt = prompt;
    if (relevantMemories.length > 0) {
      const memoryContext = relevantMemories.map((m) => m.content).join('\n');

      enhancedPrompt = `Context from previous conversations:\n${memoryContext}\n\nCurrent request: ${prompt}`;
    }

    // Get AI response
    const response = await super.run(enhancedPrompt);

    // Store interaction in memory
    this.contextualMemory.addMemory(
      `User asked: ${prompt}\nAssistant responded: ${response}`,
      this.calculateImportance(prompt, response),
      context || 'general',
    );

    return response;
  }

  private calculateImportance(prompt: string, response: string): number {
    // Simple importance calculation
    const importantKeywords = [
      'important',
      'remember',
      'name',
      'password',
      'meeting',
      'deadline',
      'budget',
    ];
    const hasImportantKeywords = importantKeywords.some(
      (keyword) =>
        prompt.toLowerCase().includes(keyword) || response.toLowerCase().includes(keyword),
    );

    const isQuestion = prompt.includes('?');
    const isLongResponse = response.length > 200;

    let importance = 1.0;
    if (hasImportantKeywords) importance += 0.5;
    if (isQuestion) importance += 0.2;
    if (isLongResponse) importance += 0.3;

    return Math.min(importance, 3.0); // Cap at 3.0
  }

  getMemoryStats() {
    return this.contextualMemory.getMemoryStats();
  }

  searchMemory(query: string, context?: string) {
    return this.contextualMemory.searchMemories(query, context);
  }
}

Performance Optimization

1. Efficient History Storage

typescript
class OptimizedHistoryManager {
  private compressionThreshold: number = 1000; // chars

  compressMessage(message: any): any {
    if (message.content.length < this.compressionThreshold) {
      return message;
    }

    // Simple compression - extract key information
    const keyPhrases = this.extractKeyPhrases(message.content);

    return {
      ...message,
      content: keyPhrases.join('. '),
      originalLength: message.content.length,
      compressed: true,
    };
  }

  private extractKeyPhrases(text: string): string[] {
    // Extract sentences with important keywords
    const sentences = text.split(/[.!?]+/);
    const importantKeywords = ['name', 'important', 'remember', 'budget', 'deadline', 'meeting'];

    return sentences
      .filter(
        (sentence) =>
          importantKeywords.some((keyword) => sentence.toLowerCase().includes(keyword)) ||
          sentence.length < 100,
      )
      .slice(0, 3); // Keep max 3 key phrases
  }

  estimateTokenCount(messages: any[]): number {
    // Rough estimation: 1 token ≈ 4 characters
    const totalChars = messages.reduce((sum, msg) => sum + (msg.content?.length || 0), 0);
    return Math.ceil(totalChars / 4);
  }

  optimizeForTokenLimit(messages: any[], maxTokens: number): any[] {
    let currentTokens = this.estimateTokenCount(messages);

    if (currentTokens <= maxTokens) {
      return messages;
    }

    // Start removing oldest non-system messages
    const optimized = [...messages];

    while (currentTokens > maxTokens && optimized.length > 1) {
      // Find oldest non-system message
      const oldestIndex = optimized.findIndex((msg, idx) => idx > 0 && msg.role !== 'system');

      if (oldestIndex !== -1) {
        optimized.splice(oldestIndex, 1);
        currentTokens = this.estimateTokenCount(optimized);
      } else {
        break;
      }
    }

    return optimized;
  }
}

2. Lazy Loading and Pagination

typescript
class PaginatedHistoryManager {
  private pageSize: number = 10;
  private currentPage: number = 0;
  private totalMessages: any[] = [];

  addMessage(message: any) {
    this.totalMessages.push(message);
  }

  getCurrentPage(): any[] {
    const start = this.currentPage * this.pageSize;
    const end = start + this.pageSize;
    return this.totalMessages.slice(start, end);
  }

  getRecentMessages(count: number = 10): any[] {
    return this.totalMessages.slice(-count);
  }

  searchMessages(query: string): any[] {
    return this.totalMessages.filter((msg) =>
      msg.content?.toLowerCase().includes(query.toLowerCase()),
    );
  }

  nextPage(): boolean {
    const maxPage = Math.ceil(this.totalMessages.length / this.pageSize) - 1;
    if (this.currentPage < maxPage) {
      this.currentPage++;
      return true;
    }
    return false;
  }

  previousPage(): boolean {
    if (this.currentPage > 0) {
      this.currentPage--;
      return true;
    }
    return false;
  }

  getPageInfo() {
    const totalPages = Math.ceil(this.totalMessages.length / this.pageSize);
    return {
      currentPage: this.currentPage,
      totalPages,
      totalMessages: this.totalMessages.length,
      hasNext: this.currentPage < totalPages - 1,
      hasPrevious: this.currentPage > 0,
    };
  }
}

Best Practices

1. Memory-Efficient Conversation Management

typescript
// Configure appropriate history limits
const robota = new Robota({
  aiProviders: [openaiProvider],
  defaultModel: {
    provider: 'openai',
    model: 'gpt-3.5-turbo',
    systemMessage: 'You are a helpful assistant with good memory management.',
  },
  maxHistoryLength: 20, // Keep reasonable history
  maxHistoryTokens: 4000, // Respect token limits
  historyPruningStrategy: 'smart', // Use intelligent pruning
});

2. Context Preservation

typescript
async function preserveImportantContext(robota: Robota, userMessage: string) {
  // Check if message contains important information
  const importantPatterns = [
    /my name is (\w+)/i,
    /i work as (.+)/i,
    /my budget is (.+)/i,
    /remember that (.+)/i,
  ];

  const hasImportantInfo = importantPatterns.some((pattern) => pattern.test(userMessage));

  if (hasImportantInfo) {
    // Mark this message as high importance
    await robota.run(userMessage, { importance: 'high' });
  } else {
    await robota.run(userMessage);
  }
}

3. History Serialization

typescript
class SerializableHistoryManager {
  serializeHistory(messages: any[]): string {
    const serializable = messages.map((msg) => ({
      role: msg.role,
      content: msg.content,
      timestamp: msg.timestamp,
      importance: msg.importance || 1,
    }));

    return JSON.stringify(serializable);
  }

  deserializeHistory(serialized: string): any[] {
    try {
      return JSON.parse(serialized);
    } catch (error) {
      console.error('Failed to deserialize history:', error);
      return [];
    }
  }

  saveToStorage(messages: any[], key: string = 'conversation_history') {
    const serialized = this.serializeHistory(messages);
    localStorage.setItem(key, serialized);
  }

  loadFromStorage(key: string = 'conversation_history'): any[] {
    const serialized = localStorage.getItem(key);
    return serialized ? this.deserializeHistory(serialized) : [];
  }
}

Next Steps

After mastering conversation history management, explore:

  1. Token and Request Limits - Managing usage and costs
  2. Session Management - Multi-session conversation management
  3. Provider Switching - History preservation across providers

Troubleshooting

Memory Issues

  • Monitor conversation length and token usage
  • Implement appropriate history limits
  • Use compression for long conversations
  • Consider conversation summarization

Context Loss

  • Verify history retention settings
  • Check for proper message serialization
  • Test with different conversation lengths
  • Implement importance-based retention

Performance Problems

  • Optimize history storage and retrieval
  • Use efficient data structures
  • Implement lazy loading for large histories
  • Monitor memory usage patterns

Released under the MIT License.