ltcmp
BK—404
08–25–24






long-term core memory profiles
filling in the gaps of Mem0’s episodic memory

The Problem: Episodic Memory Alone Isn't Enough

Mem0's search() function is great at answering "what happened to this person?", but it's less good at answering "who is this person?"

I believe that answering the second question is our next step to achieving AI assistants that will help users grow over time, rather than approaching each conversation as a singular issue.

Right now I'm building Mindme: a wellness companion aimed at entering the therapeutic space. When users start a new session, the system needs to understand: Who is this person? What are they working on? What matters to them?

With only search(), you'd need to either run multiple searches hoping to cover these bases, retrieve all memories and let the LLM synthesize, or hard-code some kind of user profile table outside Mem0. The first is brittle, the second doesn't scale, and the third defeats the purpose of having an integrated memory system.

Static Profiles: Who Is This Person?

Right now, Mem0's search() is largely episodic. A profile layer is semantic; a distilled, stable representation of who someone is, updated incrementally as new information arrives. Using both search and profiles together gives you comprehensive context without the overhead of processing hundreds of memories every session.

Profiles should cover demographics and role, communication preferences, key expertise, major interests, and current goals. The key is focusing on stable, high-level traits.

Our Implementation

We created a ProfileCompiler service that synthesizes user onboarding data into a cached, AI-generated narrative:
  1. User completes onboarding → wellness goals, career aspirations, favorite books
  2. Async compilation triggers → GPT-4o-mini generates a 60-100 word profile
  3. Profile cached → 15-minute TTL, invalidated on user updates
# Trigger is fire-and-forget so users don't wait
def trigger_profile_compilation(user_id: str):
 asyncio.create_task(compile_user_profile_async(user_id))
There’s a lot of room to be creative with this - and could be highly configurable for each developer’s use case. For example, we enrich favorite books via Google Books API before compilation. This gives GPT accurate author names and subject tags, preventing hallucination and enabling the AI to reference "James Clear's habit stacking" rather than generic book titles.

The compiled profile lives in PostgreSQL alongside the user's wellness preferences, served by a UserProfileService with in-memory caching. Total retrieval latency: <5ms when cached, ~50ms on cache miss.

Dynamic Profiles: What's Happening in Their Life?

Static profiles answer "who is this person?" but not "what's happening in their life?" For a wellness app, that second question is critical. For example, if a user expresses they had a stress attack at work, the AI should be aware that they’re dealing with a parent's cancer diagnosis. That context surfaced, even when discussing unrelated topics.

Sometimes you also need to force a refresh after a significant event or milestone: a promotion, a move to a new city, leaving a relationship they often spoke about.

So we extended the profile concept with episodes: significant life events extracted from conversations and tagged with themes. Think of episodes as the dynamic component of a user's profile; facts that a therapist would remember and reference sensitively.
EPISODE_EXTRACTION_PROMPT = """Detect if this message contains an "Episode" -
an important memory a therapist would remember.Episodes are:
- Major life events: diagnosis, job loss, breakup, death, promotion
- Ongoing situations: caregiving, workplace conflict, relationship issues
- Recurring patterns: panic attacks, insomnia, burnout cyclesNOT Episodes:
- Casual facts: "I work in tech", "I have a dog"
- Preferences: "I prefer mornings"
"""

Not every fact deserves episode status - most messages just go to Mem0's episodic store. Only significant life events become episodes, keeping the profile layer high-signal.

Cross-Domain Recall

The overhead of a graphing DB was a bit too expensive/laborious for our team so I built a system of weighted edges to improve the cross-domain retreival of our semantic memory system.

Episodes are tagged with themes (work, health, family, stress, anxiety, etc.) connected by weighted edges. When retrieving episodes, we expand the query themes:
THEME_EDGES = {
 'work': [('stress', 0.9)],
 'stress': [('sleep', 0.6), ('burnout', 0.6), ('anxiety', 0.6)],
 'anxiety': [('sleep', 0.5)],
 # ...
}

So when a user mentions "sleep problems," we surface episodes tagged with sleep, stress, anxiety, and work—cross-domain recall without LLM reasoning at query time.

Putting It Together

At request time, we fetch static profiles, dynamic episodes, and Mem0 memories in parallel:
profile_task = asyncio.create_task(self._get_user_profile(user_id))
episode_task = asyncio.create_task(self._get_episode_context(user_id, message))
memory_task = asyncio.create_task(self._get_memory_context(user_id, message))

Each has independent timeouts with graceful degradation. The AI always responds but personalization just degrades gracefully if a component fails.

The multi-layer approach reduced our effective context size (no more over-stuffing tokens into prompts) while improving recall of both stable identity and active life situations. Users report the AI "remembers" them in ways that feel natural rather than transactional.

If Mem0 added native profile support with auto-update triggers and version history, we'd happily migrate. Until then, this architecture bridges the gap between episodic memory and the semantic "who is this person?" layer that longitudinal AI relationships require.