memcity

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 MemoryWith 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 sessionAI recalls the customer's history and picks up where they left off
AI gives generic recommendations to everyoneAI tailors responses based on known preferences
Previous conversations are lost foreverKey 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:

ts
// 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).

ts
// 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:

TypeDescriptionExample
preferenceUser likes or dislikes"Prefers dark mode", "Likes concise answers"
factFactual information about the user"Works at Acme Corp", "Uses PostgreSQL"
contextCurrent situation or project"Building a mobile app", "Preparing for a demo"
summarySummary 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:

typescript
strength = initial_strength × e^(-decay_rate × hours_since_access)

In practical terms:

Time Since Last AccessMemory 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.

typescript
// When a memory is retrieved during search:
new_strength = max(current_strength + boost, 1.0)
last_accessed = now

This creates a natural lifecycle:

  1. New memory — full strength
  2. Not accessed — gradually decays
  3. Accessed during search — strength boosted, decay clock resets
  4. Accessed repeatedly — becomes a long-term memory
  5. 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):

ts
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 answer

Conversation Tracking

Episodic memory also includes conversation tracking — storing the message history of each conversation a user has.

Creating a Conversation

ts
// Start a new conversation
const conversationId = await memory.createConversation(ctx, {
  orgId,
  userId,
  title: "Help with deployment",
});

Adding Messages

ts
// 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.

ts
const results = await memory.getContext(ctx, {
  orgId,
  knowledgeBaseId: kbId,
  query: "how do I add routing?",
  userId,
  conversationId,  // Include conversation context
});

Listing Memories

ts
// 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 MemoryKnowledge Base
ScopePer-user, personalShared, organizational
LifetimeTemporary, decays over timePermanent until deleted
ContentPreferences, context, conversation summariesDocuments, policies, reference material
SizeShort 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:

ts
// 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 resolution

Coding Assistant

The assistant remembers the developer's tech stack, coding style, and current project:

ts
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:

ts
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.