Last updated

Memory Management Guide

This guide covers best practices for managing memories in your application using the Papr Memory API. We'll explore strategies for organizing, updating, and maintaining your memory store effectively.

Memory Structure

A memory in Papr consists of:

interface Memory {
  content: string;              // The main content
  type: "text" | "code_snippet" | "document";  // Content type
  metadata?: {
    topics?: string[];          // Topics as string array
    hierarchical_structures?: string;  // Navigation path (e.g., "Business/Planning/Product")
    createdAt?: string;        // ISO timestamp
    location?: string;         // Optional location context
    emoji_tags?: string[];     // Emoji representations
    emotion_tags?: string[];   // Emotional context
    conversationId?: string;   // For linking related memories
    sourceUrl?: string;        // Original source if applicable
    // Access control fields
    external_user_id?: string;
    external_user_read_access?: string[];
    external_user_write_access?: string[];
  };
  context?: Array<{
    role: "user" | "assistant";
    content: string;
  }>;
  relationships_json?: Array<{
    relation_type: string;
    related_item_id?: string;
    metadata?: Record<string, any>;
  }>;
}

Organizing Memories

Using Metadata Effectively

  1. Topics
const memory = {
  content: "Discussion about new feature requirements",
  type: "text",
  metadata: {
    topics: ["product development", "feature planning", "user feedback"],
    hierarchical_structures: "Product/Features/Requirements"
  }
};
  1. Emotional Context
const memory = {
  content: "Team celebration of successful launch",
  type: "text",
  metadata: {
    emoji_tags: ["🎉", "🚀", "🎯"],
    emotion_tags: ["excited", "accomplished", "proud"]
  }
};
  1. Conversation Threading
const memory = {
  content: "Follow-up on feature discussion",
  type: "text",
  metadata: {
    conversationId: "feat-123-discussion",
    sourceUrl: "https://meeting-notes.company.com/123"
  }
};
  1. Access Control
const memory = {
  content: "Confidential project update",
  type: "text",
  metadata: {
    external_user_id: "user_123",
    external_user_read_access: ["user_123", "user_456", "user_789"],
    external_user_write_access: ["user_123"]
  }
};

Memory Lifecycle Management

1. Creating Memories

Always validate and format your content before creation:

function prepareMemory(content: string, type: string) {
  return {
    content: content.trim(),
    type,
    metadata: {
      createdAt: new Date().toISOString(),
      topics: extractTopics(content),  // Your topic extraction logic
      hierarchical_structures: determineHierarchy(content)  // Your hierarchy logic
    }
  };
}

const memory = prepareMemory("Meeting notes...", "text");
const response = await fetch('https://memory.papr.ai/v1/memory', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-API-Key': '<your-api-key>',
    'X-Client-Type': 'your_app_name',
    'Accept-Encoding': 'gzip'
  },
  body: JSON.stringify(memory)
});

2. Updating Memories

Keep track of memory changes:

const updateMemory = async (memoryId: string, updates: Partial<Memory>) => {
  const response = await fetch(`https://memory.papr.ai/v1/memory/${memoryId}`, {
    method: 'PUT',
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key': '<your-api-key>',
      'X-Client-Type': 'your_app_name',
      'Accept-Encoding': 'gzip'
    },
    body: JSON.stringify({
      ...updates,
      metadata: {
        ...updates.metadata,
        updatedAt: new Date().toISOString()
      }
    })
  });
  
  return response.json();
};

3. Batch Processing

Group related memories for efficient processing:

You can set up webhooks to receive notifications when batch processing completes. This is especially useful for large batches that may take time to process asynchronously.

const batchAddMemories = async (memories: Memory[]) => {
  // Group memories by type
  const groupedMemories = memories.reduce((acc, memory) => {
    acc[memory.type] = acc[memory.type] || [];
    acc[memory.type].push(memory);
    return acc;
  }, {});

  // Process each group
  for (const [type, typeMemories] of Object.entries(groupedMemories)) {
    const response = await fetch('https://memory.papr.ai/v1/memory/batch', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-API-Key': '<your-api-key>',
        'X-Client-Type': 'your_app_name',
        'Accept-Encoding': 'gzip'
      },
      body: JSON.stringify({
        memories: typeMemories,
        batch_size: 10,
        webhook_url: "https://your-app.com/webhooks/batch-complete"  // Webhook for batch completion notification
      })
    });

    const result = await response.json();
    handleBatchResults(result);  // Your result handling logic
  }
};

// Example webhook handler implementation
const handleBatchWebhook = async (req, res) => {
  const batchData = req.body;
  
  console.log(`Batch ${batchData.batch_id} completed: ${batchData.total_successful} successful, ${batchData.total_failed} failed`);
  
  // Update your application state based on completion
  await updateBatchStatus(batchData.batch_id, batchData.status);
  
  res.status(200).send({ received: true });
};

Search and Retrieval Strategies

Write detailed queries for better results:

const searchMemories = async (topic: string, context: string) => {
  const response = await fetch('https://memory.papr.ai/v1/memory/search', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key': '<your-api-key>',
      'X-Client-Type': 'your_app_name',
      'Accept-Encoding': 'gzip'
    },
    body: JSON.stringify({
      query: `Find detailed discussions about ${topic} that include ${context}. Focus on items with specific examples or decisions made.`,
      enable_agentic_graph: true  // Enable intelligent context-aware search
    })
  });

  return response.json();
};

2. Result Processing

Handle search results effectively:

interface SearchResult {
  memories: Memory[];
  nodes: Node[];  // Graph nodes if available
}

function processSearchResults(results: SearchResult) {
  // Group by hierarchical structure
  const groupedResults = results.memories.reduce((acc, memory) => {
    const hierarchy = memory.metadata?.hierarchical_structures || 'Uncategorized';
    acc[hierarchy] = acc[hierarchy] || [];
    acc[hierarchy].push(memory);
    return acc;
  }, {});

  // Sort within each group by date
  for (const hierarchy in groupedResults) {
    groupedResults[hierarchy].sort((a, b) => {
      return new Date(b.metadata.createdAt) - new Date(a.metadata.createdAt);
    });
  }

  return groupedResults;
}

3. Providing Feedback

Improve future search results by providing feedback:

const submitFeedback = async (searchId: string, isHelpful: boolean, details?: string) => {
  const response = await fetch('https://memory.papr.ai/v1/feedback', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key': '<your-api-key>',
      'X-Client-Type': 'your_app_name'
    },
    body: JSON.stringify({
      search_id: searchId,
      feedbackData: {
        feedbackType: isHelpful ? 'thumbs_up' : 'thumbs_down',
        feedbackSource: 'inline',
        feedbackText: details
      }
    })
  });
  
  return response.json();
};

// After receiving search results
const searchResponse = await searchMemories('product roadmap', 'Q2 planning');
const searchId = searchResponse.search_id;

// Later, after user interaction
submitFeedback(searchId, true, "This was very helpful for our planning meeting");

Error Handling and Monitoring

Implement robust error handling:

async function safeMemoryOperation<T>(operation: () => Promise<T>): Promise<T> {
  const maxRetries = 3;
  let lastError: Error;

  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await operation();
    } catch (error) {
      lastError = error;
      
      if (error.status === 429) {  // Rate limit
        const retryAfter = parseInt(error.headers.get('Retry-After') || '5');
        await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
        continue;
      }
      
      if (error.status >= 500) {  // Server error
        await new Promise(resolve => setTimeout(resolve, attempt * 1000));
        continue;
      }
      
      throw error;  // Client error or other - don't retry
    }
  }

  throw lastError;
}

// Usage
try {
  const memory = await safeMemoryOperation(() => 
    client.memory.add({
      content: "Important content",
      type: "text"
    })
  );
  console.log(`Memory added: ${memory.data[0].memoryId}`);
} catch (error) {
  console.error("Failed to add memory", error);
  // Handle specific error codes
  if (error.code === 403) {
    console.log("Subscription limit reached. Please upgrade.");
  } else if (error.code === 413) {
    console.log("Content too large. Please reduce size.");
  }
}

Memory Management Best Practices

  1. Content Organization

    • Use consistent hierarchical structures
    • Apply meaningful topic tags
    • Add emotional context when relevant
  2. Batch Processing

    • Group related memories in batches
    • Use appropriate batch sizes (10-20 items)
    • Handle partial success scenarios
  3. Access Control

    • Use external_user_id for easy identification
    • Set appropriate read/write access lists
    • Review access periodically
  4. Performance Optimization

    • Use Accept-Encoding: gzip header
    • Implement client-side caching
    • Enable agentic graph for better search results

Next Steps