Features
Episodic Memory
Give your AI application persistent, per-user memory that evolves over time with natural decay and consolidation.
What is Episodic Memory?
Think about how your own memory works. You remember yesterday's lunch clearly, but last Tuesday's lunch is fuzzy. A conversation you had three times sticks better than one you had once. Important moments are vivid; routine ones fade.
Episodic memory in Memcity works the same way. It gives each user of your application their own personal memory space that:
- Stores important information — user preferences, past decisions, conversation highlights
- Decays over time — memories naturally fade if they're not accessed
- Consolidates with use — memories that are accessed frequently get stronger
- Enhances search — relevant memories are mixed into search results for personalization
Without episodic memory, every interaction with your AI starts from scratch. With it, the AI remembers that "this user prefers Python over JavaScript" or "this customer had a billing issue last week."
Why Does Your AI App Need It?
| Without Episodic Memory | With Episodic Memory |
|---|---|
| User: "Use the same framework as last time" → AI: "What framework?" | AI remembers you chose Next.js and suggests it again |
| Returning customer explains their issue from scratch every session | AI recalls the customer's history and picks up where they left off |
| AI gives generic recommendations to everyone | AI tailors responses based on known preferences |
| Previous conversations are lost forever | Key context from past conversations persists and strengthens |
How It Works
Per-User Memory Storage
Each user in your application gets their own isolated memory space. One user's memories never leak into another's:
// Create a user (or retrieve if they already exist)
const userId = await memory.createUser(ctx, {
orgId,
externalId: "user_123", // Your app's user ID
name: "Alice",
});Adding Memories
Memories are pieces of information about a user that you want the AI to remember. They can be added explicitly (your code decides to store something) or automatically (extracted from conversations).
// Explicitly add a memory
await memory.addMemory(ctx, {
orgId,
userId,
content: "Prefers TypeScript over JavaScript",
type: "preference",
});
await memory.addMemory(ctx, {
orgId,
userId,
content: "Working on a Next.js e-commerce project",
type: "context",
});
await memory.addMemory(ctx, {
orgId,
userId,
content: "Had a billing issue on 2024-01-15 that was resolved",
type: "fact",
});Memory types:
| Type | Description | Example |
|---|---|---|
preference | User likes or dislikes | "Prefers dark mode", "Likes concise answers" |
fact | Factual information about the user | "Works at Acme Corp", "Uses PostgreSQL" |
context | Current situation or project | "Building a mobile app", "Preparing for a demo" |
summary | Summary of a past conversation | "Discussed React vs Vue, chose React" |
Memory Decay
Memories naturally fade over time. Memcity uses an exponential decay function based on time since last access:
strength = initial_strength × e^(-decay_rate × hours_since_access)In practical terms:
| Time Since Last Access | Memory Strength |
|---|---|
| 0 hours (just accessed) | 100% |
| 24 hours (1 day) | ~85% |
| 72 hours (3 days) | ~60% |
| 168 hours (1 week) | ~35% |
| 720 hours (30 days) | ~5% |
Weak memories (below ~5% strength) are eventually pruned. This ensures that your memory store doesn't grow unbounded and that stale information naturally ages out.
Memory Consolidation
When a memory is accessed during a search, its strength is boosted back up. Memories that are accessed frequently become stronger and more resistant to decay — just like how studying something repeatedly makes it stick.
// When a memory is retrieved during search:
new_strength = max(current_strength + boost, 1.0)
last_accessed = nowThis creates a natural lifecycle:
- New memory — full strength
- Not accessed — gradually decays
- Accessed during search — strength boosted, decay clock resets
- Accessed repeatedly — becomes a long-term memory
- Never accessed again — eventually fades and is pruned
Memory Search at Query Time
When a user searches, Memcity automatically retrieves their relevant memories and includes them in the results (Step 15 of the pipeline):
const results = await memory.getContext(ctx, {
orgId,
knowledgeBaseId: kbId,
query: "What framework should I use?",
userId, // Pass the user ID to enable memory search
});
// Results now include:
// 1. Chunks from the knowledge base (normal search results)
// 2. Relevant user memories (e.g., "Prefers TypeScript", "Building a Next.js project")
//
// Your LLM can use both to generate a personalized answerConversation Tracking
Episodic memory also includes conversation tracking — storing the message history of each conversation a user has.
Creating a Conversation
// Start a new conversation
const conversationId = await memory.createConversation(ctx, {
orgId,
userId,
title: "Help with deployment",
});Adding Messages
// User sends a message
await memory.addMessage(ctx, {
orgId,
conversationId,
role: "user",
content: "How do I deploy to production?",
});
// Your AI responds
await memory.addMessage(ctx, {
orgId,
conversationId,
role: "assistant",
content: "To deploy to production, run `npx convex deploy`...",
});Using Conversation Context
Previous messages in the current conversation can inform the search. If the user asked about "React" three messages ago and now asks "how do I add routing?", the context from previous messages helps Memcity understand they mean React routing, not Express routing.
const results = await memory.getContext(ctx, {
orgId,
knowledgeBaseId: kbId,
query: "how do I add routing?",
userId,
conversationId, // Include conversation context
});Listing Memories
// Get all memories for a user
const memories = await memory.listMemories(ctx, {
orgId,
userId,
});
for (const mem of memories) {
console.log(`[${mem.type}] ${mem.content} (strength: ${mem.strength})`);
}
// [preference] Prefers TypeScript over JavaScript (strength: 0.92)
// [context] Working on a Next.js e-commerce project (strength: 0.75)
// [fact] Had a billing issue on 2024-01-15 (strength: 0.31)Memory vs Knowledge Base
A common question: when should I store something as a user memory vs ingesting it into a knowledge base?
| Episodic Memory | Knowledge Base | |
|---|---|---|
| Scope | Per-user, personal | Shared, organizational |
| Lifetime | Temporary, decays over time | Permanent until deleted |
| Content | Preferences, context, conversation summaries | Documents, policies, reference material |
| Size | Short snippets (1-2 sentences) | Full documents (pages to hundreds of pages) |
| Example | "Alice prefers Python" | "Python Style Guide for Acme Corp" |
Rule of thumb: If it's about a specific user, use episodic memory. If it's information everyone should be able to search, ingest it into a knowledge base.
Use Cases
Customer Support Bot
The bot remembers each customer's history — past issues, product version, communication preferences:
// After resolving a support ticket
await memory.addMemory(ctx, {
orgId, userId,
content: "Had issues with API rate limiting on 2024-03-15. Resolved by upgrading to Pro tier.",
type: "fact",
});
// Next time the same customer contacts support:
// Memory is retrieved → bot knows their history → faster resolutionCoding Assistant
The assistant remembers the developer's tech stack, coding style, and current project:
await memory.addMemory(ctx, {
orgId, userId,
content: "Uses TypeScript, Next.js, Tailwind, and Convex for their current project",
type: "context",
});
await memory.addMemory(ctx, {
orgId, userId,
content: "Prefers functional components over class components",
type: "preference",
});Tutoring App
The tutor remembers what the student has learned, what they struggle with, and adapts accordingly:
await memory.addMemory(ctx, {
orgId, userId,
content: "Completed Module 3 (Arrays). Struggled with nested iteration — took 3 attempts.",
type: "fact",
});
await memory.addMemory(ctx, {
orgId, userId,
content: "Learns best with visual examples and step-by-step walkthroughs",
type: "preference",
});Memory decay means the system naturally knows when to review old topics — if the student hasn't practiced arrays in 2 weeks, that memory has decayed, signaling it might be time for a review.
Availability
Episodic memory is available on Pro and Team tiers. Community tier does not include user management or memory features.