repositories
loading repo index
repositories
loading repo index
repository
loading code, commits, and activity
public Clawd ADK gateway launch mirror
stars
latest
clone command
git clone gitlawb://did:key:z6Mkq5mY...iFZ5/my-project-publ...git clone gitlawb://did:key:z6Mkq5mY.../my-project-publ...2fa351d6docs: add automaton and perps launch sources16d ago| #1 | --- |
| #2 | title: Build a Companion with Mem0 |
| #3 | description: "Spin up a fitness coach that remembers goals, adapts tone, and keeps sessions personal." |
| #4 | --- |
| #5 | |
| #6 | |
| #7 | Essentially, creating a companion out of LLMs is as simple as a loop. But these loops work great for one type of character without personalization and fall short as soon as you restart the chat. |
| #8 | |
| #9 | Problem: LLMs are stateless. GPT doesn't remember conversations. You could stuff everything inside the context window, but that becomes slow, expensive, and breaks at scale. |
| #10 | |
| #11 | The solution: Mem0. It extracts and stores what matters from conversations, then retrieves it when needed. Your companion remembers user preferences, past events, and history. |
| #12 | |
| #13 | In this cookbook we'll build a **fitness companion** that: |
| #14 | |
| #15 | - Remembers user goals across sessions |
| #16 | - Recalls past workouts and progress |
| #17 | - Adapts its personality based on user preferences |
| #18 | - Handles both short-term context (today's chat) and long-term memory (months of history) |
| #19 | |
| #20 | By the end, you'll have a working fitness companion and know how to handle common production challenges. |
| #21 | |
| #22 | --- |
| #23 | |
| #24 | ## The Basic Loop with Memory |
| #25 | |
| #26 | Max wants to train for a marathon. He starts chatting with Ray, an AI running coach. |
| #27 | |
| #28 | ```python |
| #29 | from openai import OpenAI |
| #30 | from mem0 import MemoryClient |
| #31 | |
| #32 | openai_client = OpenAI(api_key="your-openai-key") |
| #33 | mem0_client = MemoryClient(api_key="your-mem0-key") |
| #34 | |
| #35 | def chat(user_input, user_id): |
| #36 | # Retrieve relevant memories |
| #37 | memories = mem0_client.search(user_input, user_id=user_id, limit=5) |
| #38 | context = "\\n".join(m["memory"] for m in memories["results"]) |
| #39 | |
| #40 | # Call LLM with memory context |
| #41 | response = openai_client.chat.completions.create( |
| #42 | model="gpt-4o-mini", |
| #43 | messages=[ |
| #44 | {"role": "system", "content": f"You're Ray, a running coach. Memories:\\n{context}"}, |
| #45 | {"role": "user", "content": user_input} |
| #46 | ] |
| #47 | ).choices[0].message.content |
| #48 | |
| #49 | # Store the exchange |
| #50 | mem0_client.add([ |
| #51 | {"role": "user", "content": user_input}, |
| #52 | {"role": "assistant", "content": response} |
| #53 | ], user_id=user_id) |
| #54 | |
| #55 | return response |
| #56 | |
| #57 | ``` |
| #58 | |
| #59 | **Session 1:** |
| #60 | |
| #61 | ```python |
| #62 | chat("I want to run a marathon in under 4 hours", user_id="max") |
| #63 | # Output: "That's a solid goal. What's your current weekly mileage?" |
| #64 | # Stored in Mem0: "Max wants to run sub-4 marathon" |
| #65 | |
| #66 | ``` |
| #67 | |
| #68 | **Session 2 (next day, app restarted):** |
| #69 | |
| #70 | ```python |
| #71 | chat("What should I focus on today?", user_id="max") |
| #72 | # Output: "Based on your sub-4 marathon goal, let's work on building your aerobic base..." |
| #73 | |
| #74 | ``` |
| #75 | |
| #76 | <Info> |
| #77 | Ray remembers Max's goal across sessions. The app restarted, but the memory persisted. This is the core pattern: retrieve memories, pass them as context, store new exchanges. |
| #78 | </Info> |
| #79 | |
| #80 | Ray remembers. Restart the app, and the goal persists. From here on, we'll focus on just the Mem0 API calls. |
| #81 | |
| #82 | --- |
| #83 | |
| #84 | ## Organizing Memory by Type |
| #85 | |
| #86 | ### Separating Temporary from Permanent |
| #87 | |
| #88 | Max mentions his knee hurts. That's different from his marathon goal - one is temporary, the other is long-term. |
| #89 | |
| #90 | **Categories vs Metadata:** |
| #91 | |
| #92 | - **Categories**: AI-assigned by Mem0 based on content (you can't force them) |
| #93 | - **Metadata**: Manually set by you for forced tagging |
| #94 | |
| #95 | Define custom categories at the project level. Mem0 will automatically tag memories with relevant categories based on content: |
| #96 | |
| #97 | ```python |
| #98 | mem0_client.project.update(custom_categories=[ |
| #99 | {"goals": "Race targets and training objectives"}, |
| #100 | {"constraints": "Injuries, limitations, recovery needs"}, |
| #101 | {"preferences": "Training style, surfaces, schedules"} |
| #102 | ]) |
| #103 | |
| #104 | ``` |
| #105 | |
| #106 | <Note> |
| #107 | **Categories vs Metadata:** Categories are AI-assigned by Mem0 based on content semantics. You define the palette, Mem0 picks which ones apply. If you need guaranteed tagging, use `metadata` instead. |
| #108 | </Note> |
| #109 | |
| #110 | Now when you add memories, Mem0 automatically assigns the appropriate categories: |
| #111 | |
| #112 | ```python |
| #113 | # Add goal - Mem0 automatically tags it as "goals" |
| #114 | mem0_client.add( |
| #115 | [{"role": "user", "content": "Sub-4 marathon is my A-race"}], |
| #116 | user_id="max" |
| #117 | ) |
| #118 | |
| #119 | # Add constraint - Mem0 automatically tags it as "constraints" |
| #120 | mem0_client.add( |
| #121 | [{"role": "user", "content": "My right knee flares up on downhills"}], |
| #122 | user_id="max" |
| #123 | ) |
| #124 | |
| #125 | ``` |
| #126 | |
| #127 | Mem0 reads the content and intelligently picks which categories apply. You define the palette, it handles the tagging. |
| #128 | |
| #129 | **Important:** You cannot force specific categories. Mem0's platform decides which categories are relevant based on content. If you need to force-tag something, use `metadata` instead: |
| #130 | |
| #131 | ```python |
| #132 | # Force tag using metadata (not categories) |
| #133 | mem0_client.add( |
| #134 | [{"role": "user", "content": "Some workout note"}], |
| #135 | user_id="max", |
| #136 | metadata={"workout_type": "speed", "forced_tag": "custom_label"} |
| #137 | ) |
| #138 | |
| #139 | ``` |
| #140 | |
| #141 | ### Filtering by Category |
| #142 | |
| #143 | Retrieve just constraints for workout planning: |
| #144 | |
| #145 | ```python |
| #146 | constraints = mem0_client.search( |
| #147 | query="injury concerns", |
| #148 | filters={ |
| #149 | "AND": [ |
| #150 | {"user_id": "max"}, |
| #151 | {"categories": {"in": ["constraints"]}} |
| #152 | ] |
| #153 | }, |
| #154 | threshold=0.0 # optional: widen recall for short phrases |
| #155 | ) |
| #156 | print([m["memory"] for m in constraints["results"]]) |
| #157 | # Output: ["Max's right knee flares up on downhills"] |
| #158 | |
| #159 | ``` |
| #160 | |
| #161 | Ray can plan workouts that avoid aggravating Max's knee, without pulling in race goals or other unrelated memories. |
| #162 | |
| #163 | --- |
| #164 | |
| #165 | ## Filtering What Gets Stored |
| #166 | |
| #167 | ### The Problem |
| #168 | |
| #169 | Run the basic loop for a week and check what's stored: |
| #170 | |
| #171 | ```python |
| #172 | memories = mem0_client.get_all(filters={"AND": [{"user_id": "max"}]}) |
| #173 | print([m["memory"] for m in memories["results"]]) |
| #174 | # Output: ["Max wants to run marathon under 4 hours", "hey", "lol ok", "cool thanks", "gtg bye"] |
| #175 | |
| #176 | ``` |
| #177 | |
| #178 | <Warning> |
| #179 | Without filters, Mem0 stores everything—greetings, filler, and casual chat. This pollutes retrieval: instead of pulling "marathon goal," you get "lol ok." Set custom instructions to keep memory clean. |
| #180 | </Warning> |
| #181 | |
| #182 | Noise. Greetings and filler clutter the memory. |
| #183 | |
| #184 | ### Custom Instructions |
| #185 | |
| #186 | Tell Mem0 what matters: |
| #187 | |
| #188 | ```python |
| #189 | mem0_client.project.update(custom_instructions=""" |
| #190 | Extract from running coach conversations: |
| #191 | - Training goals and race targets |
| #192 | - Physical constraints or injuries |
| #193 | - Training preferences (time of day, surfaces, weather) |
| #194 | - Progress milestones |
| #195 | |
| #196 | Exclude: |
| #197 | - Greetings and filler |
| #198 | - Casual chatter |
| #199 | - Hypotheticals unless planning related |
| #200 | """) |
| #201 | |
| #202 | ``` |
| #203 | |
| #204 | Now chat again: |
| #205 | |
| #206 | ```python |
| #207 | chat("hey how's it going", user_id="max") |
| #208 | chat("I prefer trail running over roads", user_id="max") |
| #209 | |
| #210 | memories = mem0_client.get_all(filters={"AND": [{"user_id": "max"}]}) |
| #211 | print([m["memory"] for m in memories["results"]]) |
| #212 | # Output: ["Max wants to run marathon under 4 hours", "Max prefers trail running over roads"] |
| #213 | |
| #214 | ``` |
| #215 | |
| #216 | <Info> |
| #217 | **Expected output:** Only 2 memories stored—the marathon goal and trail preference. The greeting "hey how's it going" was filtered out automatically. Custom instructions are working. |
| #218 | </Info> |
| #219 | |
| #220 | Only meaningful facts. Filler gets dropped automatically. |
| #221 | |
| #222 | --- |
| #223 | |
| #224 | --- |
| #225 | |
| #226 | ## Agent Memory for Personality |
| #227 | |
| #228 | ### Why Agents Need Memory Too |
| #229 | |
| #230 | Max prefers direct feedback, not motivational fluff. Ray needs to remember how to communicate - that's agent memory, separate from user memory. |
| #231 | |
| #232 | Store agent personality: |
| #233 | |
| #234 | ```python |
| #235 | mem0_client.add( |
| #236 | [{"role": "system", "content": "Max wants direct, data-driven feedback. Skip motivational language."}], |
| #237 | agent_id="ray_coach" |
| #238 | ) |
| #239 | |
| #240 | ``` |
| #241 | |
| #242 | Retrieve agent style alongside user memories: |
| #243 | |
| #244 | ```python |
| #245 | # Get coach personality |
| #246 | agent_memories = mem0_client.search("coaching style", agent_id="ray_coach") |
| #247 | # Output: ["Max wants direct, data-driven feedback. Skip motivational language."] |
| #248 | |
| #249 | # Store conversations with agent_id |
| #250 | mem0_client.add([ |
| #251 | {"role": "user", "content": "How'd my run look today?"}, |
| #252 | {"role": "assistant", "content": "Pace was 8:15/mile. Heart rate 152, zone 2."} |
| #253 | ], user_id="max", agent_id="ray_coach") |
| #254 | |
| #255 | ``` |
| #256 | |
| #257 | <Info> |
| #258 | **Expected behavior:** Ray's responses are now data-driven and direct. The agent memory stored the coaching style preference, so future responses adapt automatically without Max having to repeat his preference. |
| #259 | </Info> |
| #260 | |
| #261 | No "Great job!" or "Keep it up!" - just data. Ray adapts to Max's preference. |
| #262 | |
| #263 | --- |
| #264 | |
| #265 | ## Managing Short-Term Context |
| #266 | |
| #267 | ### When to Store in Mem0 |
| #268 | |
| #269 | Don't send every single message to Mem0. Keep recent context in memory, let Mem0 handle the important long-term facts. |
| #270 | |
| #271 | ```python |
| #272 | # Store only meaningful exchanges in Mem0 |
| #273 | mem0_client.add([ |
| #274 | {"role": "user", "content": "I want to run a marathon"}, |
| #275 | {"role": "assistant", "content": "Let's build a training plan"} |
| #276 | ], user_id="max") |
| #277 | |
| #278 | # Skip storing filler |
| #279 | # "hey" → don't store |
| #280 | # "cool thanks" → don't store |
| #281 | |
| #282 | # Or rely on custom_instructions to filter automatically |
| #283 | |
| #284 | ``` |
| #285 | |
| #286 | Last 10 messages in your app's buffer. Important facts in Mem0. Faster, cheaper, still works. |
| #287 | |
| #288 | --- |
| #289 | |
| #290 | ## Time-Bound Memories |
| #291 | |
| #292 | ### Auto-Expiring Facts |
| #293 | |
| #294 | Max tweaks his ankle. It'll heal in two weeks - the memory should expire too. |
| #295 | |
| #296 | ```python |
| #297 | from datetime import datetime, timedelta |
| #298 | |
| #299 | expiration = (datetime.now() + timedelta(days=14)).strftime("%Y-%m-%d") |
| #300 | |
| #301 | mem0_client.add( |
| #302 | [{"role": "user", "content": "Rolled my left ankle, needs rest"}], |
| #303 | user_id="max", |
| #304 | expiration_date=expiration |
| #305 | ) |
| #306 | |
| #307 | ``` |
| #308 | |
| #309 | In 14 days, this memory disappears automatically. Ray stops asking about the ankle. |
| #310 | |
| #311 | --- |
| #312 | |
| #313 | ## Putting It All Together |
| #314 | |
| #315 | Here's the Mem0 setup combining everything: |
| #316 | |
| #317 | ```python |
| #318 | from mem0 import MemoryClient |
| #319 | from datetime import datetime, timedelta |
| #320 | |
| #321 | mem0_client = MemoryClient(api_key="your-mem0-key") |
| #322 | |
| #323 | # Configure memory filtering and categories |
| #324 | mem0_client.project.update( |
| #325 | custom_instructions=""" |
| #326 | Extract: goals, constraints, preferences, progress |
| #327 | Exclude: greetings, filler, casual chat |
| #328 | """, |
| #329 | custom_categories=[ |
| #330 | {"name": "goals", "description": "Training targets"}, |
| #331 | {"name": "constraints", "description": "Injuries and limitations"}, |
| #332 | {"name": "preferences", "description": "Training style"} |
| #333 | ] |
| #334 | ) |
| #335 | |
| #336 | ``` |
| #337 | |
| #338 | **Week 1 - Store goals and preferences:** |
| #339 | |
| #340 | ```python |
| #341 | mem0_client.add([ |
| #342 | {"role": "user", "content": "I want to run a sub-4 marathon"}, |
| #343 | {"role": "assistant", "content": "Got it. Let's build a training plan."} |
| #344 | ], user_id="max", agent_id="ray", categories=["goals"]) |
| #345 | |
| #346 | mem0_client.add([ |
| #347 | {"role": "user", "content": "I prefer trail running over roads"} |
| #348 | ], user_id="max", categories=["preferences"]) |
| #349 | |
| #350 | ``` |
| #351 | |
| #352 | **Week 3 - Temporary injury with expiration:** |
| #353 | |
| #354 | ```python |
| #355 | expiration = (datetime.now() + timedelta(days=14)).strftime("%Y-%m-%d") |
| #356 | mem0_client.add( |
| #357 | [{"role": "user", "content": "Rolled ankle, need light workouts"}], |
| #358 | user_id="max", |
| #359 | categories=["constraints"], |
| #360 | expiration_date=expiration |
| #361 | ) |
| #362 | |
| #363 | ``` |
| #364 | |
| #365 | **Retrieve for context:** |
| #366 | |
| #367 | ```python |
| #368 | memories = mem0_client.search("training plan", user_id="max", limit=5) |
| #369 | # Gets: marathon goal, trail preference, ankle injury (if still valid) |
| #370 | |
| #371 | ``` |
| #372 | |
| #373 | Ray remembers goals, preferences, and personality. Handles temporary injuries. Works across sessions. |
| #374 | |
| #375 | --- |
| #376 | |
| #377 | ## Common Production Patterns |
| #378 | |
| #379 | ### Episodic Stories with run_id |
| #380 | |
| #381 | Training for Boston is different from training for New York. Separate the memory threads: |
| #382 | |
| #383 | ```python |
| #384 | mem0_client.add(messages, user_id="max", run_id="boston-2025") |
| #385 | mem0_client.add(messages, user_id="max", run_id="nyc-2025") |
| #386 | |
| #387 | # Retrieve only Boston memories |
| #388 | boston_memories = mem0_client.search( |
| #389 | "training plan", |
| #390 | user_id="max", |
| #391 | run_id="boston-2025" |
| #392 | ) |
| #393 | |
| #394 | ``` |
| #395 | |
| #396 | Each race gets its own episodic boundary. No cross-contamination. |
| #397 | |
| #398 | ### Importing Historical Data |
| #399 | |
| #400 | Max has 6 months of training logs to backfill: |
| #401 | |
| #402 | ```python |
| #403 | old_logs = [ |
| #404 | [{"role": "user", "content": "Completed 20-mile long run"}], |
| #405 | [{"role": "user", "content": "Hit 8:00 pace on tempo run"}], |
| #406 | ] |
| #407 | |
| #408 | for log in old_logs: |
| #409 | mem0_client.add(log, user_id="max") |
| #410 | |
| #411 | ``` |
| #412 | |
| #413 | ### Handling Contradictions |
| #414 | |
| #415 | Max changes his goal from sub-4 to sub-3:45: |
| #416 | |
| #417 | ```python |
| #418 | # Find the old memory |
| #419 | memories = mem0_client.get_all(filters={"AND": [{"user_id": "max"}]}) |
| #420 | goal_memory = [m for m in memories["results"] if "sub-4" in m["memory"]][0] |
| #421 | |
| #422 | # Update it |
| #423 | mem0_client.update(goal_memory["id"], "Max wants to run sub-3:45 marathon") |
| #424 | |
| #425 | ``` |
| #426 | |
| #427 | Update instead of creating duplicates. |
| #428 | |
| #429 | ### Multiple Agents |
| #430 | |
| #431 | Max works with Ray for running and Jordan for strength training: |
| #432 | |
| #433 | ```python |
| #434 | chat("easy run today", user_id="max", agent_id="ray") |
| #435 | chat("leg day workout", user_id="max", agent_id="jordan") |
| #436 | |
| #437 | ``` |
| #438 | |
| #439 | Each coach maintains separate personality memory while sharing user context. |
| #440 | |
| #441 | ### Filtering by Date |
| #442 | |
| #443 | Prioritize recent training over old data: |
| #444 | |
| #445 | ```python |
| #446 | recent = mem0_client.search( |
| #447 | "training progress", |
| #448 | user_id="max", |
| #449 | filters={"created_at": {"gte": "2025-10-01"}} |
| #450 | ) |
| #451 | |
| #452 | ``` |
| #453 | |
| #454 | ### Metadata Tagging |
| #455 | |
| #456 | Tag workouts by type: |
| #457 | |
| #458 | ```python |
| #459 | mem0_client.add( |
| #460 | [{"role": "user", "content": "10x400m intervals"}], |
| #461 | user_id="max", |
| #462 | metadata={"workout_type": "speed", "intensity": "high"} |
| #463 | ) |
| #464 | |
| #465 | # Later, find all speed workouts |
| #466 | speed_sessions = mem0_client.search( |
| #467 | "speed work", |
| #468 | user_id="max", |
| #469 | filters={"metadata": {"workout_type": "speed"}} |
| #470 | ) |
| #471 | |
| #472 | ``` |
| #473 | |
| #474 | ### Pruning Old Memories |
| #475 | |
| #476 | Delete irrelevant memories: |
| #477 | |
| #478 | ```python |
| #479 | mem0_client.delete(memory_id="mem_xyz") |
| #480 | |
| #481 | # Or clear an entire run_id |
| #482 | mem0_client.delete_all(user_id="max", run_id="old-training-cycle") |
| #483 | |
| #484 | ``` |
| #485 | |
| #486 | --- |
| #487 | |
| #488 | ## What You Built |
| #489 | |
| #490 | A companion that: |
| #491 | |
| #492 | - **Persists across sessions** - Mem0 storage |
| #493 | - **Filters noise** - custom instructions |
| #494 | - **Organizes by type** - categories |
| #495 | - **Adapts personality** - **`agent_id`** |
| #496 | - **Stays fast** - short-term buffer |
| #497 | - **Handles temporal facts** - expiration |
| #498 | - **Scales to production** - batching, metadata, pruning |
| #499 | |
| #500 | This pattern works for any companion: fitness coaches, tutors, roleplay characters, therapy bots, creative writing partners. |
| #501 | |
| #502 | --- |
| #503 | |
| #504 | <Tip> |
| #505 | Start with 2-3 categories max (e.g., goals, constraints, preferences). More categories dilute tagging accuracy. You can always add more later after seeing what Mem0 extracts. |
| #506 | </Tip> |
| #507 | |
| #508 | --- |
| #509 | |
| #510 | ## Production Checklist |
| #511 | |
| #512 | Before launching: |
| #513 | |
| #514 | - Set custom instructions for your domain |
| #515 | - Define 2-3 categories (goals, constraints, preferences) |
| #516 | - Add expiration strategy for time-bound facts |
| #517 | - Implement error handling for API calls |
| #518 | - Monitor memory quality in Mem0 dashboard |
| #519 | - Clear test data from production project |
| #520 | |
| #521 | --- |
| #522 | |
| #523 | <CardGroup cols={2}> |
| #524 | <Card title="Partition Memories by Entity" icon="layers" href="/cookbooks/essentials/entity-partitioning-playbook"> |
| #525 | Keep companions from leaking context by combining user, agent, and session scopes. |
| #526 | </Card> |
| #527 | <Card title="Tag Support Memories" icon="tag" href="/cookbooks/essentials/tagging-and-organizing-memories"> |
| #528 | Organize customer context to keep assistants responsive at scale. |
| #529 | </Card> |
| #530 | </CardGroup> |
| #531 |