Compare commits

..
117 changed files with 1330 additions and 11026 deletions
-18
View File
@@ -1,18 +0,0 @@
{
"name": "mem0-plugins",
"owner": {
"name": "Mem0",
"email": "support@mem0.ai"
},
"metadata": {
"description": "Official Mem0 plugins for Claude"
},
"plugins": [
{
"name": "mem0",
"source": "./mem0-plugin",
"description": "Mem0 memory layer for AI applications. Add persistent memory, personalization, and semantic search to Claude workflows.",
"version": "0.1.0"
}
]
}
-18
View File
@@ -1,18 +0,0 @@
{
"name": "mem0-plugins",
"owner": {
"name": "Mem0",
"email": "support@mem0.ai"
},
"metadata": {
"description": "Official Mem0 plugins for Cursor"
},
"plugins": [
{
"name": "mem0",
"source": "./mem0-plugin",
"description": "Mem0 memory layer for AI applications. Add persistent memory, personalization, and semantic search.",
"version": "0.1.0"
}
]
}
+32 -46
View File
@@ -1,55 +1,41 @@
name: Bug Report
description: Report a bug in mem0
labels: ["bug"]
name: 🐛 Bug Report
description: Create a report to help us reproduce and fix the bug
body:
- type: dropdown
id: component
attributes:
label: Component
description: Which part of mem0 is affected?
options:
- Core / Python SDK
- TypeScript SDK
- Vector Store (Qdrant, PGVector, Redis, Chroma, etc.)
- Graph Memory (Neo4j, Memgraph, etc.)
- Ollama / Local Models
- OpenClaw
- REST API
- Other
validations:
required: true
- type: markdown
attributes:
value: >
#### Before submitting a bug, please make sure the issue hasn't been already addressed by searching through [the existing and past issues](https://github.com/embedchain/embedchain/issues?q=is%3Aissue+sort%3Acreated-desc+).
- type: textarea
attributes:
label: 🐛 Describe the bug
description: |
Please provide a clear and concise description of what the bug is.
- type: textarea
id: description
attributes:
label: Description
value: |
### Summary
If relevant, add a minimal example so that we can reproduce the error by running the code. It is very important for the snippet to be as succinct (minimal) as possible, so please take time to trim down any irrelevant code to help us debug efficiently. We are going to copy-paste your code and we expect to get the same result as you did: avoid any external data, and include the relevant imports, etc. For example:
A clear summary of the bug.
```python
# All necessary imports at the beginning
import embedchain as ec
# Your code goes here
### Steps to Reproduce
```python
from mem0 import Memory
```
m = Memory()
# Your code here...
```
Please also paste or describe the results you observe instead of the expected results. If you observe an error, please paste the error message including the **full** traceback of the exception. It may be relevant to wrap error messages in ```` ```triple quotes blocks``` ````.
placeholder: |
A clear and concise description of what the bug is.
### Expected Behavior
```python
Sample code to reproduce the problem
```
What you expected to happen.
### Actual Behavior
What actually happened. Paste the full error traceback if applicable.
### Environment
- mem0 version:
- Python/Node version:
- OS:
validations:
required: true
```
The error message you got, with the full traceback.
````
validations:
required: true
- type: markdown
attributes:
value: >
Thanks for contributing 🎉!
+5 -5
View File
@@ -1,8 +1,8 @@
blank_issues_enabled: true
contact_links:
- name: Discord Community
- name: 1-on-1 Session
url: https://cal.com/taranjeetio/ec
about: Speak directly with Taranjeet, the founder, to discuss issues, share feedback, or explore improvements for Embedchain
- name: Discord
url: https://discord.gg/6PzXDgEjG5
about: Ask questions and discuss with the community
- name: Documentation
url: https://docs.mem0.ai
about: Read the official mem0 documentation
about: General community discussions
+9 -21
View File
@@ -1,23 +1,11 @@
name: Documentation Issue
description: Report an issue or suggest an improvement to the mem0 docs
labels: ["documentation"]
name: Documentation
description: Report an issue related to the Embedchain docs.
title: "DOC: <Please write a comprehensive title after the 'DOC: ' prefix>"
body:
- type: textarea
id: description
attributes:
label: Description
value: |
### Page
Link to the docs page: https://docs.mem0.ai/...
### What's Wrong or Missing
Describe what's incorrect, unclear, or missing.
### Suggested Fix
How should the docs be improved?
validations:
required: true
- type: textarea
attributes:
label: "Issue with current documentation:"
description: >
Please make sure to leave a reference to the document/code you're
referring to.
+21 -39
View File
@@ -1,41 +1,23 @@
name: Feature Request
description: Suggest a new feature or improvement for mem0
labels: ["enhancement"]
name: 🚀 Feature request
description: Submit a proposal/request for a new Embedchain feature
body:
- type: dropdown
id: component
attributes:
label: Component
description: Which part of mem0 does this relate to?
options:
- Core / Python SDK
- TypeScript SDK
- Vector Store (Qdrant, PGVector, Redis, Chroma, etc.)
- Graph Memory (Neo4j, Memgraph, etc.)
- Ollama / Local Models
- OpenClaw
- REST API
- Benchmarks / Evals
- Other
validations:
required: true
- type: textarea
id: description
attributes:
label: Description
value: |
### Use Case
What problem are you trying to solve?
### Proposed Solution
How should this work? Include API examples or pseudocode if helpful.
### Alternatives Considered
Any workarounds you've tried or other approaches considered.
validations:
required: true
- type: textarea
id: feature-request
attributes:
label: 🚀 The feature
description: >
A clear and concise description of the feature proposal
validations:
required: true
- type: textarea
attributes:
label: Motivation, pitch
description: >
Please outline the motivation for the proposal. Is your feature request related to a specific problem? e.g., *"I'm working on X and would like Y to be possible"*. If this is related to another GitHub issue, please link here too.
validations:
required: true
- type: markdown
attributes:
value: >
Thanks for contributing 🎉!
+28 -25
View File
@@ -1,38 +1,41 @@
## Linked Issue
Closes #<!-- issue number -->
## Description
<!-- What does this PR do? Why is it needed? -->
Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change.
## Type of Change
Fixes # (issue)
- [ ] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [ ] Refactor (no functional changes)
## Type of change
Please delete options that are not relevant.
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Refactor (does not change functionality, e.g. code style improvements, linting)
- [ ] Documentation update
## Breaking Changes
## How Has This Been Tested?
<!-- If this is a breaking change, describe what breaks and the migration path. Delete this section if not applicable. -->
Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration
N/A
Please delete options that are not relevant.
## Test Coverage
- [ ] Unit Test
- [ ] Test Script (please provide)
- [ ] I added/updated unit tests
- [ ] I added/updated integration tests
- [ ] I tested manually (describe below)
- [ ] No tests needed (explain why)
## Checklist:
<!-- Describe how you tested this, or link to CI results. -->
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream modules
- [ ] I have checked my code and corrected any misspellings
## Checklist
## Maintainer Checklist
- [ ] My code follows the project's style guidelines
- [ ] I have performed a self-review of my code
- [ ] I have added tests that prove my fix/feature works
- [ ] New and existing tests pass locally
- [ ] I have updated documentation if needed
- [ ] closes #xxxx (Replace xxxx with the GitHub issue number)
- [ ] Made sure Checks passed
-18
View File
@@ -1,18 +0,0 @@
# Maps dropdown selections to GitHub labels
# Used by the advanced-issue-labeler GitHub Action
component:
- label: "sdk-python"
matcher: "Core / Python SDK"
- label: "sdk-typescript"
matcher: "TypeScript SDK"
- label: "vector-store"
matcher: "Vector Store"
- label: "graph-memory"
matcher: "Graph Memory"
- label: "ollama"
matcher: "Ollama"
- label: "openclaw"
matcher: "OpenClaw"
- label: "rest-api"
matcher: "REST API"
-39
View File
@@ -1,39 +0,0 @@
name: Auto-label issues
on:
issues:
types: [opened]
permissions:
contents: read
issues: write
jobs:
label:
runs-on: ubuntu-latest
steps:
- uses: stefanbuck/github-issue-parser@v3
id: issue-parser
with:
template-path: .github/ISSUE_TEMPLATE/bug_report.yml
- uses: redhat-plumbers-in-action/advanced-issue-labeler@v3
with:
issue-form: ${{ steps.issue-parser.outputs.jsonString }}
section: component
token: ${{ secrets.GITHUB_TOKEN }}
config-path: .github/advanced-issue-labeler.yml
- uses: stefanbuck/github-issue-parser@v3
id: feature-parser
if: contains(github.event.issue.labels.*.name, 'enhancement')
with:
template-path: .github/ISSUE_TEMPLATE/feature_request.yml
- uses: redhat-plumbers-in-action/advanced-issue-labeler@v3
if: contains(github.event.issue.labels.*.name, 'enhancement')
with:
issue-form: ${{ steps.feature-parser.outputs.jsonString }}
section: component
token: ${{ secrets.GITHUB_TOKEN }}
config-path: .github/advanced-issue-labeler.yml
-48
View File
@@ -1,48 +0,0 @@
name: Close stale issues
on:
schedule:
- cron: '0 0 * * *'
workflow_dispatch:
permissions:
issues: write
pull-requests: write
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
with:
# Issue settings
days-before-issue-stale: 90
days-before-issue-close: 14
stale-issue-label: 'stale'
stale-issue-message: >
This issue has been automatically marked as stale because it has not
had any activity in 90 days. It will be closed in 14 days if no
further activity occurs. If this is still relevant, please leave a
comment or remove the `stale` label.
close-issue-message: >
This issue has been closed due to inactivity. If this is still
relevant, feel free to reopen it or create a new issue.
# PR settings — mark stale but never auto-close
days-before-pr-stale: 90
days-before-pr-close: -1
stale-pr-label: 'stale'
stale-pr-message: >
This pull request has been automatically marked as stale because it
has not had any activity in 90 days. Please update your branch and
address any review comments, or it may be closed in the future.
# Exempt these labels from stale processing
exempt-issue-labels: 'P0-critical,P1-high,good first issue,security'
exempt-pr-labels: 'P0-critical,P1-high'
# Remove stale label when there is new activity
remove-stale-when-updated: true
# Process up to 100 issues per run to stay within API limits
operations-per-run: 100
+2
View File
@@ -15,6 +15,8 @@
<a href="https://mem0.dev/DiG">Join Discord</a>
·
<a href="https://mem0.dev/demo">Demo</a>
·
<a href="https://mem0.dev/openmemory">OpenMemory</a>
</p>
<p align="center">
+9 -59
View File
@@ -8,56 +8,20 @@ mode: "wide"
<Tabs>
<Tab title="Python">
<Update label="2026-03-26" description="v1.0.8">
**New Features & Updates:**
- **Vector Stores:** Integrated Turbopuffer as a vector database provider ([#4428](https://github.com/mem0ai/mem0/pull/4428))
- **LLMs:** Added MiniMax LLM provider ([#4431](https://github.com/mem0ai/mem0/pull/4431))
**Bug Fixes:**
- **Core:** Fixed merging of multiple filter operators for the same key ([#4559](https://github.com/mem0ai/mem0/pull/4559))
- **Core:** Prevented in-place mutation of metadata in `_create_memory` ([#4529](https://github.com/mem0ai/mem0/pull/4529))
- **Core:** Preserved custom metadata when updating memory ([#4495](https://github.com/mem0ai/mem0/pull/4495))
- **Core:** Handled chatty LLM responses in JSON parsing ([#4525](https://github.com/mem0ai/mem0/pull/4525))
- **Core:** Prevented double embedding in `mem0.add` ([#3996](https://github.com/mem0ai/mem0/pull/3996))
- **Core:** Raised `ValueError` when deleting nonexistent memory ([#4455](https://github.com/mem0ai/mem0/pull/4455))
- **Core:** Cleaned up graph store data on `Memory.delete()` ([#4505](https://github.com/mem0ai/mem0/pull/4505))
- **Vector Stores:** Prevented SQL injection in Databricks vector store ([#4558](https://github.com/mem0ai/mem0/pull/4558))
- **Vector Stores:** Upgraded MongoDB vector store from deprecated `knnVector` to GA `vectorSearch` ([#3995](https://github.com/mem0ai/mem0/pull/3995))
- **Vector Stores:** Prevented embedding corruption in Valkey and Redis when vector is `None` ([#4362](https://github.com/mem0ai/mem0/pull/4362))
- **Vector Stores:** Accepted default `/tmp/chroma` path in `ChromaDbConfig` validator ([#4179](https://github.com/mem0ai/mem0/pull/4179))
- **Vector Stores:** Wrapped vector and payload in lists for `Langchain.update` ([#4446](https://github.com/mem0ai/mem0/pull/4446))
- **Graph:** Soft-delete graph relationships instead of hard `DELETE` ([#4188](https://github.com/mem0ai/mem0/pull/4188))
- **Graph:** Sanitized hyphens in Neo4j Cypher relationship names ([#4154](https://github.com/mem0ai/mem0/pull/4154))
- **Graph:** Used root LLM config as fallback for graph store instead of hardcoded OpenAI default ([#4466](https://github.com/mem0ai/mem0/pull/4466))
- **Qdrant:** Fixed `do not remove local path on init` ([#4475](https://github.com/mem0ai/mem0/pull/4475))
- **Qdrant:** Implemented enhanced metadata filtering operators ([#4127](https://github.com/mem0ai/mem0/pull/4127))
- **Embeddings:** Fixed OpenAI embedding dimensions ([#4481](https://github.com/mem0ai/mem0/pull/4481))
- **LLMs:** Omitted `topP` for Anthropic Converse in Bedrock; used `AWSBedrockConfig` in `LlmFactory` ([#4469](https://github.com/mem0ai/mem0/pull/4469))
- **LLMs:** Avoided sending both `temperature` and `top_p` to Anthropic API ([#4471](https://github.com/mem0ai/mem0/pull/4471))
- **LLMs:** Handled `None` content and empty candidates in `GeminiLLM` parsing ([#4462](https://github.com/mem0ai/mem0/pull/4462))
- **LLMs:** Added missing `_parse_response` to `AzureOpenAIStructuredLLM` ([#4434](https://github.com/mem0ai/mem0/pull/4434))
- **History:** Added timestamps for `DELETE` operations in history ([#4492](https://github.com/mem0ai/mem0/pull/4492))
**Improvements:**
- **Vector Stores:** Added vector validation to OpenSearchDB to ensure non-null, non-empty, and correct-dimension vectors ([#4472](https://github.com/mem0ai/mem0/pull/4472))
</Update>
<Update label="2026-03-19" description="v1.0.7">
**Bug Fixes:**
- **Core:** Fixed control characters in LLM JSON responses causing parse failures ([#4420](https://github.com/mem0ai/mem0/pull/4420))
- **Core:** Replaced hardcoded US/Pacific timezone references with `timezone.utc` ([#4404](https://github.com/mem0ai/mem0/pull/4404))
- **Core:** Preserved `http_auth` in `_safe_deepcopy_config` for OpenSearch ([#4418](https://github.com/mem0ai/mem0/pull/4418))
- **Core:** Normalized malformed LLM fact output before embedding ([#4224](https://github.com/mem0ai/mem0/pull/4224))
- **Embeddings:** Pass `encoding_format='float'` in OpenAI embeddings for proxy compatibility ([#4058](https://github.com/mem0ai/mem0/pull/4058))
- **LLMs:** Fixed Ollama to pass tools to `client.chat` and parse `tool_calls` from response ([#4176](https://github.com/mem0ai/mem0/pull/4176))
- **Reranker:** Support nested LLM config in `LLMReranker` for non-OpenAI providers ([#4405](https://github.com/mem0ai/mem0/pull/4405))
- **Vector Stores:** Cast `vector_distance` to float in Redis search ([#4377](https://github.com/mem0ai/mem0/pull/4377))
- **Core:** Fixed control characters in LLM JSON responses causing parse failures (#4420)
- **Core:** Replaced hardcoded US/Pacific timezone references with `timezone.utc` (#4404)
- **Core:** Preserved `http_auth` in `_safe_deepcopy_config` for OpenSearch (#4418)
- **Core:** Normalized malformed LLM fact output before embedding (#4224)
- **Embeddings:** Pass `encoding_format='float'` in OpenAI embeddings for proxy compatibility (#4058)
- **LLMs:** Fixed Ollama to pass tools to `client.chat` and parse `tool_calls` from response (#4176)
- **Reranker:** Support nested LLM config in `LLMReranker` for non-OpenAI providers (#4405)
- **Vector Stores:** Cast `vector_distance` to float in Redis search (#4377)
**Improvements:**
- **Embeddings:** Improved Ollama embedder with model name normalization and error handling ([#4403](https://github.com/mem0ai/mem0/pull/4403))
- **Embeddings:** Improved Ollama embedder with model name normalization and error handling (#4403)
</Update>
@@ -799,20 +763,6 @@ mode: "wide"
<Tab title="TypeScript">
<Update label="2026-03-26" description="v2.4.3">
**New Features & Updates:**
- **OSS:** Added pgvector support to NodeJS OSS `VectorStoreFactory` ([#3997](https://github.com/mem0ai/mem0/pull/3997))
**Bug Fixes:**
- **OSS:** Made pgvector `pg` import compatible with ESM ([#4544](https://github.com/mem0ai/mem0/pull/4544))
- **OSS:** Registered pgvector in `VectorStoreFactory` ([#4502](https://github.com/mem0ai/mem0/pull/4502))
- **OSS:** Used root LLM config as fallback for graph store instead of hardcoded OpenAI default ([#4466](https://github.com/mem0ai/mem0/pull/4466))
- **OSS:** Fixed `toCamelCase` in Redis `get` method for the payload ([#3172](https://github.com/mem0ai/mem0/pull/3172))
- **Client:** Fixed Zod Schema incompatibility with OpenAI Structured Outputs API ([#3462](https://github.com/mem0ai/mem0/pull/3462))
</Update>
<Update label="2026-03-19" description="v2.4.2">
**Bug Fixes:**
+87 -67
View File
@@ -7,51 +7,58 @@ When using LLM rerankers, you can customize the prompts used for ranking to bett
## Default Prompt
The default LLM reranker prompt scores each memory individually on a 0.0-1.0 scale:
The default LLM reranker prompt is designed to be general-purpose:
```
You are a relevance scoring assistant. Given a query and a document, you need to score how relevant the document is to the query.
Given a query and a list of memory entries, rank the memory entries based on their relevance to the query.
Rate each memory on a scale of 1-10 where 10 is most relevant.
Score the relevance on a scale from 0.0 to 1.0, where:
- 1.0 = Perfectly relevant and directly answers the query
- 0.8-0.9 = Highly relevant with good information
- 0.6-0.7 = Moderately relevant with some useful information
- 0.4-0.5 = Slightly relevant with limited useful information
- 0.0-0.3 = Not relevant or no useful information
Query: {query}
Query: "{query}"
Document: "{document}"
Memory entries:
{memories}
Provide only a single numerical score between 0.0 and 1.0. Do not include any explanation or additional text.
Provide your ranking as a JSON array with scores for each memory.
```
## Custom Prompt Configuration
You can provide a custom prompt template using the `scoring_prompt` parameter:
You can provide a custom prompt template when configuring the LLM reranker:
```python
from mem0 import Memory
custom_prompt = """
You are an expert at evaluating memories for a personal AI assistant.
Given a user query and a memory entry, score how relevant the memory is.
Consider direct relevance, temporal relevance, and actionability.
You are an expert at ranking memories for a personal AI assistant.
Given a user query and a list of memory entries, rank each memory based on:
1. Direct relevance to the query
2. Temporal relevance (recent memories may be more important)
3. Emotional significance
4. Actionability
Query: "{query}"
Memory: "{document}"
Query: {query}
User Context: {user_context}
Provide only a single numerical score between 0.0 and 1.0.
Memory entries:
{memories}
Rate each memory from 1-10 and provide reasoning.
Return as JSON: {{"rankings": [{{"index": 0, "score": 8, "reason": "..."}}]}}
"""
config = {
"reranker": {
"provider": "llm_reranker",
"config": {
"provider": "openai",
"model": "gpt-4o-mini",
"api_key": "your-openai-key",
"scoring_prompt": custom_prompt,
"top_k": 5
"llm": {
"provider": "openai",
"config": {
"model": "gpt-4.1-nano-2025-04-14",
"api_key": "your-openai-key"
}
},
"custom_prompt": custom_prompt,
"top_n": 5
}
}
}
@@ -63,14 +70,12 @@ memory = Memory.from_config(config)
Your custom prompt can use the following variables:
| Variable | Description |
| ------------ | ----------------------------- |
| `{query}` | The search query |
| `{document}` | The memory entry being scored |
<Note>
Both `{query}` and `{document}` are required in your custom prompt. The LLM reranker scores each memory individually against the query, so the prompt is called once per candidate memory.
</Note>
| Variable | Description |
| ---------------- | ------------------------------------- |
| `{query}` | The search query |
| `{memories}` | The list of memory entries to rank |
| `{user_id}` | The user ID (if available) |
| `{user_context}` | Additional user context (if provided) |
## Domain-Specific Examples
@@ -84,10 +89,13 @@ Prioritize memories that:
- Show previous resolution patterns
- Indicate customer preferences or constraints
Query: "{query}"
Memory: "{document}"
Query: {query}
Customer Context: Previous interactions with this customer
Score relevance from 0.0 to 1.0.
Memories:
{memories}
Rank each memory 1-10 based on support relevance.
"""
```
@@ -95,16 +103,19 @@ Score relevance from 0.0 to 1.0.
```python
educational_prompt = """
Score this learning memory for relevance to a student query.
Rank these learning memories for a student query.
Consider:
- Prerequisite knowledge requirements
- Learning progression and difficulty
- Relevance to current learning objectives
Student Query: "{query}"
Memory: "{document}"
Student Query: {query}
Learning Context: {user_context}
Score educational relevance from 0.0 to 1.0.
Available memories:
{memories}
Score each memory for educational value (1-10).
"""
```
@@ -112,64 +123,73 @@ Score educational relevance from 0.0 to 1.0.
```python
personal_assistant_prompt = """
Score this personal memory for relevance to the user's query.
Rank personal memories for relevance to the user's query.
Consider:
- Recent vs. historical importance
- Personal preferences and habits
- Contextual relationships
- Contextual relationships between memories
Query: "{query}"
Memory: "{document}"
Query: {query}
Personal context: {user_context}
Provide relevance score from 0.0 to 1.0.
Memories to rank:
{memories}
Provide relevance scores (1-10) with brief explanations.
"""
```
## Advanced Prompt Techniques
### Multi-Criteria Scoring
### Multi-Criteria Ranking
```python
multi_criteria_prompt = """
Evaluate this memory using multiple criteria:
Evaluate memories using multiple criteria:
1. RELEVANCE (40%): How directly related to the query
2. RECENCY (20%): How recent the memory appears to be
2. RECENCY (20%): How recent the memory is
3. IMPORTANCE (25%): Personal or business significance
4. ACTIONABILITY (15%): How useful for next steps
Query: "{query}"
Memory: "{document}"
Query: {query}
Context: {user_context}
Compute a weighted score from 0.0 to 1.0 based on these criteria.
Provide only the final numerical score.
Memories:
{memories}
For each memory, provide:
- Overall score (1-10)
- Breakdown by criteria
- Final ranking recommendation
Format: JSON with detailed scoring
"""
```
### Chain-of-Thought Scoring
### Contextual Ranking
```python
reasoning_prompt = """
Evaluate this memory's relevance step by step:
contextual_prompt = """
Consider the following context when ranking memories:
- Current user situation: {user_context}
- Time of day: {current_time}
- Recent activities: {recent_activities}
1. What is the main intent of the query?
2. What key information does the memory contain?
3. How directly does the memory address the query?
Query: {query}
Based on this analysis, provide a single relevance score from 0.0 to 1.0.
Rank these memories considering both direct relevance and contextual appropriateness:
{memories}
Query: "{query}"
Memory: "{document}"
Score:
Provide contextually-aware relevance scores (1-10).
"""
```
## Best Practices
1. **Be Specific**: Clearly define what makes a memory relevant for your use case
2. **Use 0.0-1.0 Scale**: The score extractor expects values between 0.0 and 1.0
3. **Request Only the Score**: Ask for just the numerical score to improve extraction reliability
2. **Use Examples**: Include examples in your prompt for better model understanding
3. **Structure Output**: Specify the exact JSON format you want returned
4. **Test Iteratively**: Refine your prompt based on actual ranking performance
5. **Consider Token Limits**: Keep prompts concise while being comprehensive
@@ -186,7 +206,7 @@ prompts = [
]
for i, prompt in enumerate(prompts):
config["reranker"]["config"]["scoring_prompt"] = prompt
config["reranker"]["config"]["custom_prompt"] = prompt
memory = Memory.from_config(config)
results = memory.search("test query", user_id="test_user")
@@ -196,6 +216,6 @@ for i, prompt in enumerate(prompts):
## Common Issues
- **Too Long**: Keep prompts under token limits for your chosen LLM
- **Too Vague**: Be specific about scoring criteria
- **Wrong Scale**: Use 0.0-1.0 scale to match the default score extractor
- **Extra Output**: Ask for only the numeric score — extra text can confuse score extraction
- **Too Vague**: Be specific about ranking criteria
- **Inconsistent Format**: Ensure JSON output format is clearly specified
- **Missing Context**: Include relevant variables for your use case
+132 -101
View File
@@ -18,9 +18,13 @@ config = {
"reranker": {
"provider": "llm_reranker",
"config": {
"provider": "openai",
"model": "gpt-4o-mini",
"api_key": "your-openai-api-key"
"llm": {
"provider": "openai",
"config": {
"model": "gpt-4",
"api_key": "your-openai-api-key"
}
}
}
}
}
@@ -32,14 +36,11 @@ m = Memory.from_config(config)
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `provider` | str | `"openai"` | LLM provider (openai, anthropic, etc.) |
| `model` | str | `"gpt-4o-mini"` | LLM model to use for reranking |
| `api_key` | str | None | API key for the LLM provider |
| `top_k` | int | None | Number of top documents to return after reranking |
| `llm` | dict | Required | LLM configuration object |
| `top_k` | int | 10 | Number of results to rerank |
| `temperature` | float | 0.0 | LLM temperature for consistency |
| `max_tokens` | int | 100 | Maximum tokens for LLM response |
| `scoring_prompt` | str | None | Custom prompt template for scoring documents |
| `llm` | dict | None | Optional nested LLM config for provider-specific fields (e.g., `ollama_base_url`, `azure_endpoint`). Overrides top-level `provider`/`model`/`api_key` when provided. |
| `custom_prompt` | str | None | Custom reranking prompt |
| `score_range` | tuple | (0, 10) | Score range for relevance |
### Advanced Configuration
@@ -48,19 +49,20 @@ config = {
"reranker": {
"provider": "llm_reranker",
"config": {
"provider": "anthropic",
"model": "claude-sonnet-4-20250514",
"api_key": "your-anthropic-api-key",
"llm": {
"provider": "anthropic",
"config": {
"model": "claude-3-sonnet-20240229",
"api_key": "your-anthropic-api-key"
}
},
"top_k": 15,
"temperature": 0.0,
"scoring_prompt": """
Rate the relevance of each memory to the query on a scale of 0.0-1.0.
"score_range": (1, 5),
"custom_prompt": """
Rate the relevance of each memory to the query on a scale of 1-5.
Consider semantic similarity, context, and practical utility.
Only provide the numeric score.
Query: "{query}"
Document: "{document}"
Score:
"""
}
}
@@ -76,10 +78,14 @@ config = {
"reranker": {
"provider": "llm_reranker",
"config": {
"provider": "openai",
"model": "gpt-4o-mini",
"api_key": "your-openai-api-key",
"temperature": 0.0
"llm": {
"provider": "openai",
"config": {
"model": "gpt-4",
"api_key": "your-openai-api-key",
"temperature": 0.0
}
}
}
}
}
@@ -92,9 +98,13 @@ config = {
"reranker": {
"provider": "llm_reranker",
"config": {
"provider": "anthropic",
"model": "claude-sonnet-4-20250514",
"api_key": "your-anthropic-api-key"
"llm": {
"provider": "anthropic",
"config": {
"model": "claude-3-sonnet-20240229",
"api_key": "your-anthropic-api-key"
}
}
}
}
}
@@ -107,12 +117,10 @@ config = {
"reranker": {
"provider": "llm_reranker",
"config": {
"provider": "ollama",
"model": "llama3.2",
"llm": {
"provider": "ollama",
"config": {
"model": "llama3.2",
"model": "llama2",
"ollama_base_url": "http://localhost:11434"
}
}
@@ -121,10 +129,6 @@ config = {
}
```
<Note>
For providers like Ollama that need extra fields (e.g., `ollama_base_url`), use the optional nested `llm` key to pass provider-specific configuration. The nested `llm` config overrides top-level `provider`/`model`/`api_key` when provided.
</Note>
### Azure OpenAI
```python
@@ -132,16 +136,13 @@ config = {
"reranker": {
"provider": "llm_reranker",
"config": {
"provider": "azure_openai",
"model": "gpt-4o-mini",
"api_key": "your-azure-api-key",
"llm": {
"provider": "azure_openai",
"config": {
"model": "gpt-4o-mini",
"model": "gpt-4",
"api_key": "your-azure-api-key",
"azure_endpoint": "https://your-resource.openai.azure.com/",
"azure_deployment": "gpt-4o-mini-deployment"
"azure_deployment": "gpt-4-deployment"
}
}
}
@@ -153,22 +154,16 @@ config = {
### Default Prompt Behavior
The default prompt asks the LLM to score relevance on a 0.0-1.0 scale:
The default prompt asks the LLM to score relevance on a 0-10 scale:
```
You are a relevance scoring assistant. Given a query and a document, you need to score how relevant the document is to the query.
Given a query and a memory, rate how relevant the memory is to answering the query.
Score from 0 (completely irrelevant) to 10 (perfectly relevant).
Only provide the numeric score.
Score the relevance on a scale from 0.0 to 1.0, where:
- 1.0 = Perfectly relevant and directly answers the query
- 0.8-0.9 = Highly relevant with good information
- 0.6-0.7 = Moderately relevant with some useful information
- 0.4-0.5 = Slightly relevant with limited useful information
- 0.0-0.3 = Not relevant or no useful information
Query: "{query}"
Document: "{document}"
Provide only a single numerical score between 0.0 and 1.0. Do not include any explanation or additional text.
Query: {query}
Memory: {memory}
Score:
```
### Custom Prompt Examples
@@ -179,14 +174,14 @@ Provide only a single numerical score between 0.0 and 1.0. Do not include any ex
custom_prompt = """
You are a medical information specialist. Rate how relevant each memory is for answering the medical query.
Consider clinical accuracy, specificity, and practical applicability.
Rate from 0.0 to 1.0 where:
- 0.0-0.3: Irrelevant or potentially harmful
- 0.4-0.6: Somewhat relevant but incomplete
- 0.7-0.8: Relevant and helpful
- 0.9-1.0: Highly relevant and clinically useful
Rate from 1-10 where:
- 1-3: Irrelevant or potentially harmful
- 4-6: Somewhat relevant but incomplete
- 7-8: Relevant and helpful
- 9-10: Highly relevant and clinically useful
Query: "{query}"
Document: "{document}"
Query: {query}
Memory: {memory}
Score:
"""
@@ -194,10 +189,14 @@ config = {
"reranker": {
"provider": "llm_reranker",
"config": {
"provider": "openai",
"model": "gpt-4o-mini",
"api_key": "your-api-key",
"scoring_prompt": custom_prompt
"llm": {
"provider": "openai",
"config": {
"model": "gpt-4",
"api_key": "your-api-key"
}
},
"custom_prompt": custom_prompt
}
}
}
@@ -214,15 +213,15 @@ Consider:
- Recency and accuracy
- Practical usefulness
Rate 0.0-1.0:
0.0 = Not relevant
0.25 = Slightly relevant
0.5 = Moderately relevant
0.75 = Very relevant
1.0 = Perfectly answers the question
Rate 1-5:
1 = Not relevant
2 = Slightly relevant
3 = Moderately relevant
4 = Very relevant
5 = Perfectly answers the question
Query: "{query}"
Document: "{document}"
Query: {query}
Memory: {memory}
Score:
"""
```
@@ -240,17 +239,13 @@ Consider:
- Factual accuracy
- Conversation flow
Rate 0.0-1.0:
Query: "{query}"
Document: "{document}"
Rate 0-10:
Query: {query}
Memory: {memory}
Score:
"""
```
<Note>
Custom prompts must include `{query}` and `{document}` placeholders. The LLM response should contain a numerical score which is automatically extracted.
</Note>
## Usage Examples
### Basic Usage
@@ -300,9 +295,9 @@ results = safe_llm_rerank_search("What are my preferences?", "alice")
| Model Type | Speed | Quality | Cost | Best For |
|------------|-------|---------|------|----------|
| GPT-4o mini | Fast | Good | Low | High-volume applications |
| GPT-4o | Medium | Excellent | Medium | Quality-critical applications |
| Claude Sonnet | Medium | Excellent | Medium | Balanced performance |
| GPT-3.5 Turbo | Fast | Good | Low | High-volume applications |
| GPT-4 | Medium | Excellent | Medium | Quality-critical applications |
| Claude 3 Sonnet | Medium | Excellent | Medium | Balanced performance |
| Ollama Local | Variable | Good | Free | Privacy-sensitive applications |
### Optimization Strategies
@@ -313,10 +308,14 @@ fast_config = {
"reranker": {
"provider": "llm_reranker",
"config": {
"provider": "openai",
"model": "gpt-4o-mini",
"api_key": "your-api-key",
"top_k": 5,
"llm": {
"provider": "openai",
"config": {
"model": "gpt-3.5-turbo",
"api_key": "your-api-key"
}
},
"top_k": 5, # Limit candidates
"temperature": 0.0
}
}
@@ -327,9 +326,13 @@ quality_config = {
"reranker": {
"provider": "llm_reranker",
"config": {
"provider": "openai",
"model": "gpt-4o",
"api_key": "your-api-key",
"llm": {
"provider": "openai",
"config": {
"model": "gpt-4",
"api_key": "your-api-key"
}
},
"top_k": 15,
"temperature": 0.0
}
@@ -350,10 +353,10 @@ Evaluate this memory's relevance using multi-step reasoning:
3. How directly does the memory address the query?
4. What additional context might be needed?
Based on this analysis, rate relevance 0.0-1.0:
Based on this analysis, rate relevance 1-10:
Query: "{query}"
Document: "{document}"
Query: {query}
Memory: {memory}
Analysis:
Step 1 (Intent):
@@ -364,6 +367,42 @@ Final Score:
"""
```
### Comparative Ranking
```python
comparative_prompt = """
You will see a query and multiple memories. Rank them in order of relevance.
Consider which memories best answer the question and would be most helpful.
Query: {query}
Memories to rank:
{memories}
Provide scores 1-10 for each memory, considering their relative usefulness.
"""
```
### Emotional Intelligence
```python
emotional_prompt = """
Consider both factual relevance and emotional appropriateness.
Rate how suitable this memory is for responding to the user's query.
Factors to consider:
- Factual accuracy and relevance
- Emotional tone and sensitivity
- User's likely emotional state
- Appropriateness of response
Query: {query}
Memory: {memory}
Emotional Context: {context}
Score (1-10):
"""
```
## Error Handling and Fallbacks
```python
@@ -394,22 +433,14 @@ class RobustLLMReranker:
primary_config = {
"reranker": {
"provider": "llm_reranker",
"config": {
"provider": "openai",
"model": "gpt-4o",
"api_key": "your-api-key"
}
"config": {"llm": {"provider": "openai", "config": {"model": "gpt-4"}}}
}
}
fallback_config = {
"reranker": {
"provider": "llm_reranker",
"config": {
"provider": "openai",
"model": "gpt-4o-mini",
"api_key": "your-api-key"
}
"config": {"llm": {"provider": "openai", "config": {"model": "gpt-3.5-turbo"}}}
}
}
@@ -455,4 +486,4 @@ results = reranker.search("What are my preferences?", "alice")
<Card title="Performance Optimization" icon="bolt" href="/components/rerankers/optimization">
Optimize LLM reranker performance
</Card>
</CardGroup>
</CardGroup>
+20 -29
View File
@@ -17,10 +17,8 @@ config = {
"workspace_url": "https://your-workspace.databricks.com",
"access_token": "your-access-token",
"endpoint_name": "your-vector-search-endpoint",
"catalog": "your_catalog",
"schema": "your_schema",
"table_name": "your_table",
"collection_name": "your_index_name",
"index_name": "catalog.schema.index_name",
"source_table_name": "catalog.schema.source_table",
"embedding_dimension": 1536
}
}
@@ -44,22 +42,17 @@ Here are the parameters available for configuring Databricks Vector Search:
| --- | --- | --- |
| `workspace_url` | The URL of your Databricks workspace | **Required** |
| `access_token` | Personal Access Token for authentication | `None` |
| `client_id` | Service principal client ID (alternative to access_token) | `None` |
| `client_secret` | Service principal client secret (required with client_id) | `None` |
| `azure_client_id` | Azure AD application client ID (for Azure Databricks) | `None` |
| `azure_client_secret` | Azure AD application client secret (for Azure Databricks) | `None` |
| `service_principal_client_id` | Service principal client ID (alternative to access_token) | `None` |
| `service_principal_client_secret` | Service principal client secret (required with client_id) | `None` |
| `endpoint_name` | Name of the Vector Search endpoint | **Required** |
| `catalog` | Unity Catalog catalog name | **Required** |
| `schema` | Unity Catalog schema name | **Required** |
| `table_name` | Source Delta table name | **Required** |
| `collection_name` | Vector search index name | `mem0` |
| `index_type` | Index type: `DELTA_SYNC` or `DIRECT_ACCESS` | `DELTA_SYNC` |
| `embedding_model_endpoint_name` | Databricks serving endpoint for embeddings | `None` |
| `index_name` | Name of the vector index (Unity Catalog format: catalog.schema.index) | **Required** |
| `source_table_name` | Name of the source Delta table (Unity Catalog format: catalog.schema.table) | **Required** |
| `embedding_dimension` | Dimension of self-managed embeddings | `1536` |
| `embedding_source_column` | Column name for text when using Databricks-computed embeddings | `None` |
| `embedding_model_endpoint_name` | Databricks serving endpoint for embeddings | `None` |
| `embedding_vector_column` | Column name for self-managed embedding vectors | `embedding` |
| `endpoint_type` | Type of endpoint (`STANDARD` or `STORAGE_OPTIMIZED`) | `STANDARD` |
| `pipeline_type` | Sync pipeline type: `TRIGGERED` or `CONTINUOUS` | `TRIGGERED` |
| `warehouse_name` | Databricks SQL warehouse name (if using SQL warehouse) | `None` |
| `query_type` | Query type: `ANN` or `HYBRID` | `ANN` |
| `sync_computed_embeddings` | Whether to sync computed embeddings automatically | `True` |
### Authentication
@@ -72,13 +65,11 @@ config = {
"provider": "databricks",
"config": {
"workspace_url": "https://your-workspace.databricks.com",
"client_id": "your-service-principal-id",
"client_secret": "your-service-principal-secret",
"service_principal_client_id": "your-service-principal-id",
"service_principal_client_secret": "your-service-principal-secret",
"endpoint_name": "your-endpoint",
"catalog": "your_catalog",
"schema": "your_schema",
"table_name": "your_table",
"collection_name": "your_index_name",
"index_name": "catalog.schema.index_name",
"source_table_name": "catalog.schema.source_table"
}
}
}
@@ -93,10 +84,8 @@ config = {
"workspace_url": "https://your-workspace.databricks.com",
"access_token": "your-personal-access-token",
"endpoint_name": "your-endpoint",
"catalog": "your_catalog",
"schema": "your_schema",
"table_name": "your_table",
"collection_name": "your_index_name",
"index_name": "catalog.schema.index_name",
"source_table_name": "catalog.schema.source_table"
}
}
}
@@ -114,6 +103,7 @@ config = {
"config": {
# ... authentication config ...
"embedding_dimension": 768, # Match your embedding model
"embedding_vector_column": "embedding"
}
}
}
@@ -128,6 +118,7 @@ config = {
"provider": "databricks",
"config": {
# ... authentication config ...
"embedding_source_column": "text",
"embedding_model_endpoint_name": "e5-small-v2"
}
}
@@ -136,8 +127,8 @@ config = {
### Important Notes
- **Index Types**: This implementation supports both `DELTA_SYNC` (auto-syncs with source Delta table) and `DIRECT_ACCESS` (manage vectors directly) index types.
- **Unity Catalog**: The source table and index are created under the specified `catalog.schema` namespace.
- **Delta Sync Index**: This implementation uses Delta Sync Index, which automatically syncs with your source Delta table. Direct vector insertion/deletion/update operations will log warnings as they're not supported with Delta Sync.
- **Unity Catalog**: Both the source table and index must be in Unity Catalog format (`catalog.schema.table_name`).
- **Endpoint Auto-Creation**: If the specified endpoint doesn't exist, it will be created automatically.
- **Index Auto-Creation**: If the specified index doesn't exist, it will be created automatically with the provided configuration.
- **Filter Support**: Supports filtering by metadata fields, with different syntax for STANDARD vs STORAGE_OPTIMIZED endpoints.
+1 -3
View File
@@ -48,7 +48,7 @@ const config = {
password: '123',
host: '127.0.0.1',
port: 5432,
dbname: 'vector_store', // Optional; TypeScript OSS defaults to `vector_store` when omitted
dbname: 'vector_store', // Optional, defaults to 'postgres'
diskann: false, // Optional, requires pgvectorscale extension
hnsw: false, // Optional, for HNSW indexing
},
@@ -85,8 +85,6 @@ Here are the parameters available for configuring pgvector:
| `connection_string` | PostgreSQL connection string (overrides individual connection parameters) | `None` |
| `connection_pool` | psycopg2 connection pool object (overrides connection string and individual parameters) | `None` |
**Note (TypeScript OSS):** If you omit `dbname`, the TypeScript client uses the database name `vector_store`. Python defaults to `postgres` for `dbname`, as in the table above.
**Note**: The connection parameters have the following priority:
1. `connection_pool` (highest priority)
2. `connection_string`
@@ -3,7 +3,7 @@ title: "Gemini 3 with Mem0 MCP"
description: "Create snappy, smart, memory-aware agents by pairing Gemini 3 with Mem0 MCP server."
---
Gemini 3, when paired with Mem0's cloud MCP server, works in synergy to create snappy, smart, memory-aware agents.
Gemini 3, when paired with mem0-mcp-server, works in synergy to create snappy, smart, memory-aware agents.
<Callout type="info" icon="sparkles" color="#8B5CF6">
This is the primary example of MCP integration - the same patterns work with Claude Desktop, Cursor, or any MCP-compatible client.
@@ -27,22 +27,10 @@ The Mem0 MCP server provides these tools to Gemini:
## Setup
### Configure Mem0 MCP
Add Mem0 MCP to your MCP client:
```bash
npx mcp-add \
--name mem0-mcp \
--type http \
--url "https://mcp.mem0.ai/mcp" \
--clients "claude,claude code,cursor,windsurf,vscode,opencode"
```
### Install dependencies
```bash
pip install pydantic-ai nest-asyncio python-dotenv google-genai
pip install pydantic-ai nest-asyncio python-dotenv uv google-genai
```
### Environment Setup
@@ -52,6 +40,7 @@ Create a file named `.env`:
```bash
MEM0_API_KEY=m0-xxxxxxxxxxxxxxxxx
GEMINI_API_KEY=your-gemini-api-key-here
MEM0_DEFAULT_USER_ID=demo-user
```
<Note>
@@ -71,7 +60,7 @@ import asyncio
import os
from dotenv import load_dotenv
from pydantic_ai import Agent
from pydantic_ai.mcp import MCPServerHTTP
from pydantic_ai.mcp import MCPServerStdio
# Load environment variables
load_dotenv()
@@ -86,9 +75,11 @@ class MemoryAgent:
def _setup(self):
"""Initialize the agent with MCP tools"""
# Connect to Mem0's cloud MCP server
self.server = MCPServerHTTP(
url="https://mcp.mem0.ai/mcp"
# Create MCP server directly
self.server = MCPServerStdio(
command="uvx",
args=["mem0-mcp-server"],
env=os.environ
)
# Create agent with Gemini and memory tools
+14 -14
View File
@@ -40,7 +40,6 @@
"icon": "rocket",
"pages": [
"platform/overview",
"vibecoding",
"platform/mem0-mcp",
"platform/platform-vs-oss",
"platform/quickstart"
@@ -143,7 +142,6 @@
"icon": "rocket",
"pages": [
"open-source/overview",
"vibecoding",
"open-source/python-quickstart",
"open-source/node-quickstart"
]
@@ -296,6 +294,20 @@
}
]
},
{
"tab": "OpenMemory",
"groups": [
{
"group": "Overview & Quickstart",
"icon": "square-terminal",
"pages": [
"openmemory/overview",
"openmemory/quickstart",
"openmemory/integrations"
]
}
]
},
{
"tab": "Cookbooks",
"groups": [
@@ -1041,18 +1053,6 @@
{
"source": "/cookbooks/customer-support-agent",
"destination": "/cookbooks/operations/support-inbox"
},
{
"source": "/openmemory/overview",
"destination": "/introduction"
},
{
"source": "/openmemory/quickstart",
"destination": "/introduction"
},
{
"source": "/openmemory/integrations",
"destination": "/introduction"
}
]
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 KiB

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 426 KiB

+26 -1
View File
@@ -31,7 +31,7 @@ mode: "custom"
</h2>
</div>
<div className="grid gap-6 sm:grid-cols-2">
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
<a
href="/platform/overview"
className="group flex h-full flex-col overflow-hidden rounded-2xl border border-gray-200 dark:border-zinc-800/40 bg-white dark:bg-zinc-900/40 transition hover:border-primary/60 hover:bg-gray-50 dark:hover:bg-zinc-900"
@@ -84,6 +84,31 @@ mode: "custom"
</div>
</a>
<a
href="/openmemory/overview"
className="group flex h-full flex-col overflow-hidden rounded-2xl border border-gray-200 dark:border-zinc-800/40 bg-white dark:bg-zinc-900/40 transition hover:border-primary/60 hover:bg-gray-50 dark:hover:bg-zinc-900"
>
<img
className="block dark:hidden aspect-[4/3] w-full object-cover"
src="/images/docs thumbnails/light/mem0_openmemory.png"
alt="OpenMemory thumbnail"
style={{pointerEvents: "none"}}
/>
<img
className="hidden dark:block aspect-[4/3] w-full object-cover"
src="/images/docs thumbnails/dark/mem0_openmemory.png"
alt="OpenMemory thumbnail"
style={{pointerEvents: "none"}}
/>
<div className="flex flex-1 flex-col gap-3 px-5 pb-6 pt-5 text-left">
<h3 className="text-base font-semibold text-gray-900 dark:text-zinc-100 group-hover:text-primary">
OpenMemory
</h3>
<p className="text-sm text-gray-600 dark:text-zinc-400">
Workspace-based memory for teams collaborating across agents and projects.
</p>
</div>
</a>
</div>
</section>
+1
View File
@@ -213,3 +213,4 @@ Key differentiators:
- [FAQs](https://docs.mem0.ai/platform/faqs): Frequently asked questions about Mem0's Platform capabilities and implementation details
- [Changelog](https://docs.mem0.ai/changelog): Detailed product updates and version history for tracking new features and improvements
- [Contributing Guide](https://docs.mem0.ai/contributing/development): Guidelines for contributing to Mem0's open-source development
- [OpenMemory](https://docs.mem0.ai/openmemory/overview): Open-source memory infrastructure for research and experimentation
@@ -123,9 +123,13 @@ config = {
"reranker": {
"provider": "llm_reranker",
"config": {
"provider": "openai",
"model": "gpt-4o-mini",
"api_key": "your-openai-api-key",
"llm": {
"provider": "openai",
"config": {
"model": "gpt-4",
"api_key": "your-openai-api-key"
}
},
"top_k": 5
}
}
+3 -31
View File
@@ -13,10 +13,6 @@ The Mem0 REST API server exposes every OSS memory operation over HTTP. Run it al
- You plan to explore or debug endpoints through the built-in OpenAPI page at `/docs`.
</Info>
<Warning>
**OSS vs Platform API paths:** The self-hosted OSS server does **not** use the `/v1/` prefix. For example, the endpoint is `POST /memories`, not `POST /v1/memories/`. The [API Reference](/api-reference) documents the hosted platform at `api.mem0.ai` which uses `/v1/` paths — those do not apply to the OSS server.
</Warning>
<Warning>
Enable API key authentication (see below) and HTTPS before exposing the server to anything beyond your internal network.
</Warning>
@@ -151,18 +147,13 @@ curl -X POST http://localhost:8000/memories \
</Info>
```bash
curl -X POST http://localhost:8000/search \
-H "Content-Type: application/json" \
-d '{
"query": "vegetable",
"user_id": "alice"
}'
curl "http://localhost:8000/memories/search?user_id=alice&query=vegetable"
```
### Explore with OpenAPI docs
1. Navigate to `http://localhost:8000/docs`.
2. Pick an endpoint (e.g., `POST /search`).
1. Navigate to `http://localhost:8000/docs`.
2. Pick an endpoint (e.g., `POST /memories/search`).
3. Fill in parameters and click **Execute** to try requests in-browser.
<Tip>
@@ -171,25 +162,6 @@ curl -X POST http://localhost:8000/search \
---
## Endpoint reference
The OSS REST server exposes the following endpoints. None use the `/v1/` prefix.
| Method | Path | Description |
|--------|------|-------------|
| `POST` | `/configure` | Set memory configuration |
| `POST` | `/memories` | Create memories |
| `GET` | `/memories` | Get all memories (filter by `user_id`, `agent_id`, or `run_id`) |
| `GET` | `/memories/{memory_id}` | Get a specific memory |
| `PUT` | `/memories/{memory_id}` | Update a memory |
| `DELETE` | `/memories/{memory_id}` | Delete a specific memory |
| `DELETE` | `/memories` | Delete all memories for an identifier |
| `GET` | `/memories/{memory_id}/history` | Get memory history |
| `POST` | `/search` | Search memories |
| `POST` | `/reset` | Reset all memories |
---
## Verify the feature is working
- Hit the root route and `/docs` to confirm the server is reachable.
+1 -1
View File
@@ -4,7 +4,7 @@
"title": "Mem0 API Docs",
"description": "mem0.ai API Docs",
"contact": {
"email": "support@mem0.ai"
"email": "deshraj@mem0.ai"
},
"license": {
"name": "Apache 2.0"
+56
View File
@@ -0,0 +1,56 @@
---
title: MCP Client Integration Guide
description: "Connect MCP-compatible clients to a locally running OpenMemory server for seamless memory integration."
icon: "plug"
iconType: "solid"
---
## Connecting an MCP Client
Once your OpenMemory server is running locally, you can connect any compatible MCP client to your personal memory stream. This enables a seamless memory layer integration for AI tools and agents.
Ensure the following environment variables are correctly set in your configuration files:
**In `/ui/.env`:**
```env
NEXT_PUBLIC_API_URL=http://localhost:8765
NEXT_PUBLIC_USER_ID=<user-id>
```
**In `/api/.env`:**
```env
OPENAI_API_KEY=sk-xxx
USER=<user-id>
```
These values define where your MCP server is running and which user's memory is accessed.
### MCP Client Setup
Use the following one-step command to configure OpenMemory Local MCP to a client. The general command format is as follows:
```bash
npx @openmemory/install local http://localhost:8765/mcp/<client-name>/sse/<user-id> --client <client-name>
```
Replace `<client-name>` with the desired client name and `<user-id>` with the value specified in your environment variables.
### Example Commands for Supported Clients
| Client | Command |
|-------------|---------|
| Claude | `npx install-mcp http://localhost:8765/mcp/claude/sse/<user-id> --client claude` |
| Cursor | `npx install-mcp http://localhost:8765/mcp/cursor/sse/<user-id> --client cursor` |
| Cline | `npx install-mcp http://localhost:8765/mcp/cline/sse/<user-id> --client cline` |
| RooCline | `npx install-mcp http://localhost:8765/mcp/roocline/sse/<user-id> --client roocline` |
| Windsurf | `npx install-mcp http://localhost:8765/mcp/windsurf/sse/<user-id> --client windsurf` |
| Witsy | `npx install-mcp http://localhost:8765/mcp/witsy/sse/<user-id> --client witsy` |
| Enconvo | `npx install-mcp http://localhost:8765/mcp/enconvo/sse/<user-id> --client enconvo` |
| Augment | `npx install-mcp http://localhost:8765/mcp/augment/sse/<user-id> --client augment` |
| Claude Code | `claude mcp add -s user -t sse OpenMemory http://localhost:8765/mcp/openmemory/sse/<user-id>` |
### What This Does
Running one of the above commands registers the specified MCP client and connects it to your OpenMemory server. This enables the client to stream and store contextual memory for the provided user ID.
The connection status and memory activity can be monitored via the OpenMemory UI at [http://localhost:3000](http://localhost:3000).
+124
View File
@@ -0,0 +1,124 @@
---
title: Overview
description: "Overview of OpenMemory, a local and hosted memory infrastructure powered by Mem0 with MCP server support."
icon: "info"
iconType: "solid"
---
## Hosted OpenMemory MCP Now Available
#### Sign Up Now - [app.openmemory.dev](https://app.openmemory.dev)
Everything you love about OpenMemory MCP but with zero setup.
- Works with all MCP-compatible tools (Claude Desktop, Cursor, etc.)
- Same standard memory operations: `add_memories`, `search_memory`, etc.
- One-click provisioning, no Docker required
- Powered by Mem0
Add shared, persistent, low-friction memory to your MCP-compatible clients in seconds.
### Get Started Now
Sign up and get your access key at [app.openmemory.dev](https://app.openmemory.dev).
Example installation: `npx @openmemory/install --client claude --env OPENMEMORY_API_KEY=your-key`
OpenMemory is a local memory infrastructure powered by Mem0 that lets you carry your memory across any AI app. It provides a unified memory layer that stays with you, enabling agents and assistants to remember what matters across applications.
<img src="https://github.com/user-attachments/assets/3c701757-ad82-4afa-bfbe-e049c2b4320b" alt="OpenMemory UI" />
## What is the OpenMemory MCP Server
The OpenMemory MCP Server is a private, local-first memory server that creates a shared, persistent memory layer for your MCP-compatible tools. It runs entirely on your machine, enabling seamless context handoff across tools. Whether you're switching between development, planning, or debugging environments, your AI assistants can access relevant memory without needing repeated instructions.
The OpenMemory MCP Server ensures all memory stays local, structured, and under your control with no cloud sync or external storage.
## OpenMemory Easy Setup
### Prerequisites
- Docker
- OpenAI API Key
You can quickly run OpenMemory by running the following command:
```bash
curl -sL https://raw.githubusercontent.com/mem0ai/mem0/main/openmemory/run.sh | bash
```
You should set the `OPENAI_API_KEY` as a global environment variable:
```bash
export OPENAI_API_KEY=your_api_key
```
You can also set the `OPENAI_API_KEY` as a parameter to the script:
```bash
curl -sL https://raw.githubusercontent.com/mem0ai/mem0/main/openmemory/run.sh | OPENAI_API_KEY=your_api_key bash
```
This will start the OpenMemory server and the OpenMemory UI. Deleting the container will lead to the deletion of the memory store. We suggest you follow the instructions [here](/openmemory/quickstart#setting-up-openmemory) to set up OpenMemory on your local machine with a more persistent memory store.
## How the OpenMemory MCP Server Works
Built around the Model Context Protocol (MCP), the OpenMemory MCP Server exposes a standardized set of memory tools:
- `add_memories`: Store new memory objects
- `search_memory`: Retrieve relevant memories
- `list_memories`: View all stored memory
- `delete_all_memories`: Clear memory entirely
Any MCP-compatible tool can connect to the server and use these APIs to persist and access memory.
## What It Enables
### Cross-Client Memory Access
Store context in Cursor and retrieve it later in Claude or Windsurf without repeating yourself.
### Fully Local Memory Store
All memory is stored on your machine. Nothing goes to the cloud. You maintain full ownership and control.
### Unified Memory UI
The built-in OpenMemory dashboard provides a central view of everything stored. Add, browse, delete, and control memory access to clients directly from the dashboard.
## Supported Clients
The OpenMemory MCP Server is compatible with any client that supports the Model Context Protocol. This includes:
- Cursor
- Claude Desktop
- Windsurf
- Cline
- And more
As more AI systems adopt MCP, your private memory becomes more valuable.
## Real-World Examples
### Scenario 1: Cross-Tool Project Flow
Define technical requirements of a project in Claude Desktop. Build in Cursor. Debug issues in Windsurf - all with shared context passed through OpenMemory.
### Scenario 2: Preferences That Persist
Set your preferred code style or tone in one tool. When you switch to another MCP client, it can access those same preferences without redefining them.
### Scenario 3: Project Knowledge
Save important project details once, then access them from any compatible AI tool - no more repetitive explanations.
## Conclusion
The OpenMemory MCP Server brings memory to MCP-compatible tools without giving up control or privacy. It solves a foundational limitation in modern LLM workflows: the loss of context across tools, sessions, and environments.
By standardizing memory operations and keeping all data local, it reduces token overhead, improves performance, and unlocks more intelligent interactions across the growing ecosystem of AI assistants.
This is just the beginning. The MCP server is the first core layer in the OpenMemory platform, a broader effort to make memory portable, private, and interoperable across AI systems.
## Getting Started Today
- Repository: [GitHub](https://github.com/mem0ai/mem0/tree/main/openmemory)
- Join our community: [Discord](https://discord.gg/6PzXDgEjG5)
With OpenMemory, your AI memories stay private, portable, and under your control, exactly where they belong.
OpenMemory: Your memories, your control.
## Contributing
OpenMemory is open source and we welcome contributions. Please see the [CONTRIBUTING.md](https://github.com/mem0ai/mem0/blob/main/openmemory/CONTRIBUTING.md) file for more information.
+197
View File
@@ -0,0 +1,197 @@
---
title: Quickstart
description: "Get started with hosted or self-hosted OpenMemory MCP, including API key setup and client installation."
icon: "terminal"
iconType: "solid"
---
## Hosted OpenMemory MCP Now Available
#### Sign Up Now - [app.openmemory.dev](https://app.openmemory.dev)
Everything you love about OpenMemory MCP but with zero setup.
- Works with all MCP-compatible tools (Claude Desktop, Cursor, etc.)
- Same standard memory operations: `add_memories`, `search_memory`, etc.
- One-click provisioning, no Docker required
- Powered by Mem0
Add shared, persistent, low-friction memory to your MCP-compatible clients in seconds.
### Get Started Now
Sign up and get your access key at [app.openmemory.dev](https://app.openmemory.dev).
Example installation: `npx @openmemory/install --client claude --env OPENMEMORY_API_KEY=your-key`
## Getting Started with Hosted OpenMemory
The fastest way to get started is with our hosted version - no setup required.
### 1. Get Your API Key
Visit [app.openmemory.dev](https://app.openmemory.dev) to sign up and get your `OPENMEMORY_API_KEY`.
### 2. Install and Connect to Your Preferred Client
Example commands (replace `your-key` with your actual API key):
**For Claude Desktop:**
```bash
npx @openmemory/install --client claude --env OPENMEMORY_API_KEY=your-key
```
**For Cursor:**
```bash
npx @openmemory/install --client cursor --env OPENMEMORY_API_KEY=your-key
```
**For Windsurf:**
```bash
npx @openmemory/install --client windsurf --env OPENMEMORY_API_KEY=your-key
```
That's it! Your AI client now has persistent memory across sessions.
## Local Setup (Self-Hosted)
Prefer to run OpenMemory locally? Follow the instructions below for a self-hosted setup.
## OpenMemory Easy Setup
### Prerequisites
- Docker
- OpenAI API Key
You can quickly run OpenMemory by running the following command:
```bash
curl -sL https://raw.githubusercontent.com/mem0ai/mem0/main/openmemory/run.sh | bash
```
You should set the `OPENAI_API_KEY` as a global environment variable:
```bash
export OPENAI_API_KEY=your_api_key
```
You can also set the `OPENAI_API_KEY` as a parameter to the script:
```bash
curl -sL https://raw.githubusercontent.com/mem0ai/mem0/main/openmemory/run.sh | OPENAI_API_KEY=your_api_key bash
```
This will start the OpenMemory server and the OpenMemory UI. Deleting the container will lead to the deletion of the memory store. We suggest you follow the instructions below to set up OpenMemory on your local machine with a more persistent memory store.
## Setting Up OpenMemory
Getting started with OpenMemory is straightforward and takes just a few minutes to set up on your local machine. Follow these steps:
### 1. Clone the Repository
```bash
# Clone the repository
git clone https://github.com/mem0ai/mem0.git
cd mem0/openmemory
```
### 2. Set Up Environment Variables
Before running the project, you need to configure environment variables for both the API and the UI.
You can do this in one of the following ways:
- **Manually:** Create a `.env` file in each of the following directories:
- `/api/.env`
- `/ui/.env`
- **Using `.env.example` files:** Copy and rename the example files:
```bash
cp api/.env.example api/.env
cp ui/.env.example ui/.env
```
- **Using Makefile** (if supported): Run:
```bash
make env
```
#### Example `/api/.env`
```bash
OPENAI_API_KEY=sk-xxx
USER=<user-id> # The User ID you want to associate the memories with
```
#### LLM Configuration (optional)
By default, OpenMemory uses OpenAI (`gpt-4o-mini`) for the LLM and embedder. You can configure a different provider by adding these variables to `/api/.env`:
| Variable | Description | Default |
|---|---|---|
| `LLM_PROVIDER` | LLM provider (`openai`, `ollama`, `anthropic`, `groq`, `together`, `deepseek`, etc.) | `openai` |
| `LLM_MODEL` | Model name for the LLM provider | `gpt-4o-mini` (OpenAI) / `llama3.1:latest` (Ollama) |
| `LLM_API_KEY` | API key for the LLM provider | `OPENAI_API_KEY` env var |
| `LLM_BASE_URL` | Custom base URL for the LLM API | Provider default |
| `OLLAMA_BASE_URL` | Ollama-specific base URL (takes precedence over `LLM_BASE_URL` for Ollama) | `http://localhost:11434` |
| `EMBEDDER_PROVIDER` | Embedder provider (defaults to `ollama` when LLM is Ollama, otherwise `openai`) | `openai` |
| `EMBEDDER_MODEL` | Model name for the embedder | `text-embedding-3-small` (OpenAI) / `nomic-embed-text` (Ollama) |
| `EMBEDDER_API_KEY` | API key for the embedder provider | `OPENAI_API_KEY` env var |
| `EMBEDDER_BASE_URL` | Custom base URL for the embedder API | Provider default |
**Example: Using Ollama (fully local)**
```bash
LLM_PROVIDER=ollama
LLM_MODEL=llama3.1:latest
EMBEDDER_PROVIDER=ollama
EMBEDDER_MODEL=nomic-embed-text
OLLAMA_BASE_URL=http://localhost:11434
```
**Example: Using Anthropic**
```bash
LLM_PROVIDER=anthropic
LLM_MODEL=claude-sonnet-4-20250514
LLM_API_KEY=sk-ant-xxx
```
#### Example `/ui/.env`
```bash
NEXT_PUBLIC_API_URL=http://localhost:8765
NEXT_PUBLIC_USER_ID=<user-id> # Same as the user ID for environment variable in api
```
### 3. Build and Run the Project
You can run the project using the following two commands:
```bash
make build # Builds the MCP server and UI
make up # Runs OpenMemory MCP server and UI
```
After running these commands, you will have:
- OpenMemory MCP server running at http://localhost:8765 (API documentation available at http://localhost:8765/docs)
- OpenMemory UI running at http://localhost:3000
#### UI Not Working on http://localhost:3000?
If the UI does not start properly on http://localhost:3000, try running it manually:
```bash
cd ui
pnpm install
pnpm dev
```
You can configure the MCP client using the following command (replace `username` with your username):
```bash
npx @openmemory/install local "http://localhost:8765/mcp/cursor/sse/username" --client cursor
```
**For Claude Code (CLI):**
```bash
claude mcp add -s user -t sse OpenMemory "http://localhost:8765/mcp/openmemory/sse/username"
```
The OpenMemory dashboard will be available at http://localhost:3000. From here, you can view and manage your memories and check connection status with your MCP clients.
Once set up, OpenMemory runs locally on your machine, ensuring all your AI memories remain private and secure while being accessible across any compatible MCP client.
## Getting Started Today
GitHub Repository: https://github.com/mem0ai/mem0/tree/main/openmemory
+139 -46
View File
@@ -9,48 +9,11 @@ description: "Connect any AI client to Mem0 using Model Context Protocol for uni
When building AI applications, memory management often requires manual integration. MCP eliminates this complexity by:
- **Universal compatibility**: Works with any MCP-compatible client (Claude, Claude Code, Cursor, Windsurf, VS Code, OpenCode)
- **Universal compatibility**: Works with any MCP-compatible client (Claude Desktop, Cursor, custom agents)
- **Agent autonomy**: AI agents decide when to save, search, or update memories
- **Zero infrastructure**: No servers to maintain - Mem0's cloud MCP handles everything
- **Zero infrastructure**: No servers to maintain - Mem0 handles everything
- **Standardized protocol**: One integration works across all your AI tools
## Setup
Add Mem0 MCP to all supported clients with a single command:
```bash
npx mcp-add \
--name mem0-mcp \
--type http \
--url "https://mcp.mem0.ai/mcp" \
--clients "claude,claude code,cursor,windsurf,vscode,opencode"
```
Or configure a specific client:
```bash
npx mcp-add \
--name mem0-mcp \
--type http \
--url "https://mcp.mem0.ai/mcp" \
--clients "cursor"
```
For manual configuration, add this to your MCP client config:
```json
{
"mcpServers": {
"mem0-mcp": {
"type": "http",
"url": "https://mcp.mem0.ai/mcp"
}
}
}
```
For detailed per-client instructions, see the [Mem0 MCP Quickstart](/platform/mem0-mcp).
## Available tools
The MCP server exposes 9 memory tools to your AI client:
@@ -67,12 +30,139 @@ The MCP server exposes 9 memory tools to your AI client:
| `get_memory` | Retrieve single memory by ID |
| `list_entities` | View stored entities |
## How it works
## Deployment options
1. **Configure the MCP server** - Add Mem0 MCP to your AI client using the setup command above
2. **Agent connects** - Your AI client connects to Mem0's cloud MCP server over HTTP
3. **Autonomous memory** - The agent decides when to store/retrieve memories as part of its reasoning
4. **No manual API calls** - The agent manages memory automatically through MCP tools
Choose the deployment method that fits your workflow:
<AccordionGroup>
<Accordion title="Python package (recommended)">
Install and run locally with uvx:
```bash
uv pip install mem0-mcp-server
```
Configure your client:
```json
{
"mcpServers": {
"mem0": {
"command": "uvx",
"args": ["mem0-mcp-server"],
"env": {
"MEM0_API_KEY": "m0-...",
"MEM0_DEFAULT_USER_ID": "your-handle"
}
}
}
}
```
</Accordion>
<Accordion title="Docker container">
Containerized deployment with HTTP endpoint:
```bash
docker build -t mem0-mcp-server https://github.com/mem0ai/mem0-mcp.git
docker run --rm -d -e MEM0_API_KEY="m0-..." -p 8080:8081 mem0-mcp-server
```
Configure for HTTP:
```json
{
"mcpServers": {
"mem0-docker": {
"command": "curl",
"args": ["-X", "POST", "http://localhost:8080/mcp", "--data-binary", "@"],
"env": {
"MEM0_API_KEY": "m0-..."
}
}
}
}
```
</Accordion>
<Accordion title="Smithery">
One-click setup with managed service:
Visit [smithery.ai/server/@mem0ai/mem0-memory-mcp](https://smithery.ai/server/@mem0ai/mem0-memory-mcp) and:
1. Select your AI client (Cursor, Claude Desktop, etc.)
2. Configure your Mem0 API key
3. Set your default user ID
4. Enable graph memory (optional)
5. Copy the generated configuration
Your client connects automatically - no installation required.
</Accordion>
</AccordionGroup>
## Configuration
### Required environment variables
```bash
MEM0_API_KEY="m0-..." # Your Mem0 API key
MEM0_DEFAULT_USER_ID="your-handle" # Default user ID
```
### Optional variables
```bash
MEM0_ENABLE_GRAPH_DEFAULT="true" # Enable graph memories
MEM0_MCP_AGENT_MODEL="gpt-4o-mini" # LLM for bundled examples
```
<AccordionGroup>
<Accordion title="Test your setup with the Python agent">
The included Pydantic AI agent provides an interactive REPL to test memory operations:
```bash
# Install the package
pip install mem0-mcp-server
# Set your API keys
export MEM0_API_KEY="m0-..."
export OPENAI_API_KEY="sk-openai-..."
# Clone and test with the agent
git clone https://github.com/mem0ai/mem0-mcp.git
cd mem0-mcp-server
python example/pydantic_ai_repl.py
```
**Testing different server configurations:**
- **Local server** (default): `python example/pydantic_ai_repl.py`
- **Docker container**:
```bash
export MEM0_MCP_CONFIG_PATH=example/docker-config.json
export MEM0_MCP_CONFIG_SERVER=mem0-docker
python example/pydantic_ai_repl.py
```
- **Smithery remote**:
```bash
export MEM0_MCP_CONFIG_PATH=example/config-smithery.json
export MEM0_MCP_CONFIG_SERVER=mem0-memory-mcp
python example/pydantic_ai_repl.py
```
Try these test prompts:
- "Remember that I love tiramisu"
- "Search for my food preferences"
- "Update my project: the mobile app is now 80% complete"
- "Show me all memories about project Phoenix"
- "Delete memories from 2023"
</Accordion>
</AccordionGroup>
## How the testing works
1. **Configuration loads** - Reads from `example/config.json` by default
2. **Server starts** - Launches or connects to the Mem0 MCP server
3. **Agent connects** - Pydantic AI agent (Mem0Guide) attaches to the server
4. **Interactive REPL** - You get a chat interface to test all memory operations
## Example interactions
@@ -135,11 +225,14 @@ The Mem0 MCP server enables powerful memory capabilities for your AI application
## Best practices
- **Use the cloud MCP**: The hosted MCP server at `https://mcp.mem0.ai/mcp` handles infrastructure for you
- **Start simple**: Use the Python package for development
- **Use wildcards**: `user_id: "*"` to search across all users
- **Test locally**: Use the bundled Python agent to verify setup
- **Monitor usage**: Track memory operations in the dashboard
- **Document patterns**: Share successful prompt patterns with your team
{/* DEBUG: verify CTA targets */}
<CardGroup cols={2}>
<Card
title="Memory Filters"
@@ -153,4 +246,4 @@ The Mem0 MCP server enables powerful memory capabilities for your AI application
icon="book-open"
href="/cookbooks/frameworks/gemini-3-with-mem0-mcp"
/>
</CardGroup>
</CardGroup>
+141 -108
View File
@@ -2,34 +2,28 @@
title: "Mem0 MCP"
description: "Connect any AI client to Mem0 using Model Context Protocol in minutes"
icon: "puzzle-piece"
estimatedTime: "~2 minutes"
estimatedTime: "~5 minutes"
---
<Info>
**Prerequisites**
- Mem0 Platform account ([Sign up here](https://app.mem0.ai))
- API key ([Get one from dashboard](https://app.mem0.ai/settings/api-keys))
- Node.js 14+ (for npx)
- An MCP-compatible client (Claude, Claude Code, Cursor, Windsurf, VS Code, OpenCode)
- Python 3.10+, Docker, or Node.js 14+
- An MCP-compatible client (Claude Desktop, Cursor, or custom agent)
</Info>
## What is Mem0 MCP?
Mem0 MCP Server exposes Mem0's memory capabilities as MCP tools, letting AI agents decide when to save, search, or update information. The cloud-hosted MCP server requires no local installation — just connect and start using memory.
Mem0 MCP Server exposes Mem0's memory capabilities as MCP tools, letting AI agents decide when to save, search, or update information.
## Quick Setup
## Deployment Options
Add Mem0 MCP to your preferred clients with a single command:
Choose from three deployment methods:
```bash
npx mcp-add \
--name mem0-mcp \
--type http \
--url "https://mcp.mem0.ai/mcp" \
--clients "claude,claude code,cursor,windsurf,vscode,opencode"
```
This automatically configures Mem0 MCP for all supported clients at once.
1. **Python Package (Recommended)** - Install locally with `uvx` for instant setup
2. **Docker Container** - Isolated deployment with HTTP endpoint
3. **Smithery** - Remote hosted service for managed deployments
## Available Tools
@@ -49,105 +43,54 @@ The MCP server exposes these memory tools to your AI client:
---
## Client-Specific Setup
## Quickstart with Python (UVX)
You can also configure individual clients:
<Steps>
<Step title="Install the MCP Server">
```bash
uv pip install mem0-mcp-server
```
</Step>
<AccordionGroup>
<Accordion title="Claude Desktop">
```bash
npx mcp-add \
--name mem0-mcp \
--type http \
--url "https://mcp.mem0.ai/mcp" \
--clients "claude"
```
<Step title="Configure your MCP client">
Add this to your MCP client (e.g., Claude Desktop):
Or manually add to your Claude Desktop configuration (`claude_desktop_config.json`):
```json
{
"mcpServers": {
"mem0-mcp": {
"type": "http",
"url": "https://mcp.mem0.ai/mcp"
}
```json
{
"mcpServers": {
"mem0": {
"command": "uvx",
"args": ["mem0-mcp-server"],
"env": {
"MEM0_API_KEY": "m0-...",
"MEM0_DEFAULT_USER_ID": "your-handle"
}
}
```
</Accordion>
}
}
```
<Accordion title="Claude Code">
```bash
npx mcp-add \
--name mem0-mcp \
--type http \
--url "https://mcp.mem0.ai/mcp" \
--clients "claude code"
```
</Accordion>
Set your environment variables:
<Accordion title="Cursor">
```bash
npx mcp-add \
--name mem0-mcp \
--type http \
--url "https://mcp.mem0.ai/mcp" \
--clients "cursor"
```
```bash
export MEM0_API_KEY="m0-..."
export MEM0_DEFAULT_USER_ID="your-handle"
```
</Step>
Or go to Cursor → Settings → MCP and add:
```json
{
"mcpServers": {
"mem0-mcp": {
"type": "http",
"url": "https://mcp.mem0.ai/mcp"
}
}
}
```
</Accordion>
<Step title="Test with the Python agent">
```bash
# Clone the mem0-mcp repository
git clone https://github.com/mem0ai/mem0-mcp.git
cd mem0-mcp
<Accordion title="Windsurf">
```bash
npx mcp-add \
--name mem0-mcp \
--type http \
--url "https://mcp.mem0.ai/mcp" \
--clients "windsurf"
```
</Accordion>
# Set your API keys
export MEM0_API_KEY="m0-..."
export OPENAI_API_KEY="sk-openai-..."
<Accordion title="VS Code">
```bash
npx mcp-add \
--name mem0-mcp \
--type http \
--url "https://mcp.mem0.ai/mcp" \
--clients "vscode"
```
</Accordion>
<Accordion title="OpenCode">
```bash
npx mcp-add \
--name mem0-mcp \
--type http \
--url "https://mcp.mem0.ai/mcp" \
--clients "opencode"
```
</Accordion>
</AccordionGroup>
---
## Verify Your Setup
Once configured, your AI client can:
- Automatically save information with `add_memory`
- Search memories with `search_memories`
- Update memories with `update_memory`
- Delete memories with `delete_memory`
# Run the interactive agent
python example/pydantic_ai_repl.py
```
**Sample Interactions:**
@@ -161,18 +104,107 @@ Agent: Based on your memories, you love tiramisu.
User: Update my project: the mobile app is now 80% complete
Agent: Updated your project status successfully.
```
</Step>
<Step title="Verify the setup">
Your AI client can now:
- Automatically save information with `add_memory`
- Search memories with `search_memories`
- Update memories with `update_memory`
- Delete memories with `delete_memory`
<Info icon="check">
If you get "Connection failed", ensure you have a valid API key from [Mem0 Dashboard](https://app.mem0.ai/settings/api-keys).
If you get "Connection failed", ensure your API key is valid and the server is running.
</Info>
</Step>
</Steps>
---
## Quickstart with Docker
<Steps>
<Step title="Build the Docker image">
```bash
docker build -t mem0-mcp-server https://github.com/mem0ai/mem0-mcp.git
```
</Step>
<Step title="Run the container">
```bash
docker run --rm -d \
--name mem0-mcp \
-e MEM0_API_KEY="m0-..." \
-p 8080:8081 \
mem0-mcp-server
```
</Step>
<Step title="Configure your client for HTTP">
For clients that connect via HTTP (instead of stdio):
```json
{
"mcpServers": {
"mem0-docker": {
"command": "curl",
"args": ["-X", "POST", "http://localhost:8080/mcp", "--data-binary", "@-"],
"env": {
"MEM0_API_KEY": "m0-..."
}
}
}
}
```
</Step>
<Step title="Verify the setup">
```bash
# Check container logs
docker logs mem0-mcp
# Test HTTP endpoint
curl http://localhost:8080/health
```
<Info icon="check">
The container should start successfully and respond to HTTP requests. If port 8080 is occupied, change it with `-p 8081:8081`.
</Info>
</Step>
</Steps>
---
## Quickstart with Smithery (Hosted)
For the simplest integration, use Smithery's hosted Mem0 MCP server - no installation required.
**Example: One-click setup in Cursor**
1. Visit [smithery.ai/server/@mem0ai/mem0-memory-mcp](https://smithery.ai/server/@mem0ai/mem0-memory-mcp) and select Cursor as your client
![Smithery Mem0 MCP Configuration](/images/smithery-mem0-mcp.png)
2. Open Cursor → Settings → MCP
3. Click `mem0-mcp` → Initiate authorization
4. Configure Smithery with your environment:
- `MEM0_API_KEY`: Your Mem0 API key
- `MEM0_DEFAULT_USER_ID`: Your user ID
- `MEM0_ENABLE_GRAPH_DEFAULT`: Optional, set to `true` for graph memories
5. Return to Cursor settings and wait for tools to load
6. Start chatting with Cursor and begin storing preferences
**For other clients:**
Visit [smithery.ai/server/@mem0ai/mem0-memory-mcp](https://smithery.ai/server/@mem0ai/mem0-memory-mcp) to connect any MCP-compatible client with your Mem0 credentials.
---
## Quick Recovery
- **"Connection refused"** → Check your internet connection and ensure the MCP client is correctly configured
- **"uvx command not found"** → Install with `pip install uv` or use `pip install mem0-mcp-server` instead. Make sure your Python environment has `uv` installed (or system-wide).
- **"Connection refused"** → Check that the server is running and the correct port is configured
- **"Invalid API key"** → Get a new key from [Mem0 Dashboard](https://app.mem0.ai/settings/api-keys)
- **"npx command not found"** → Install Node.js from [nodejs.org](https://nodejs.org)
- **"Permission denied"** → Ensure Docker has access to bind ports (try with `sudo` on Linux)
---
@@ -195,5 +227,6 @@ Agent: Updated your project status successfully.
## Additional Resources
- **[Mem0 MCP Repository](https://github.com/mem0ai/mem0-mcp)** - Source code and examples
- **[Platform Quickstart](/platform/quickstart)** - Direct API integration guide
- **[MCP Specification](https://modelcontextprotocol.io)** - Learn about MCP protocol
- **[MCP Specification](https://modelcontextprotocol.io)** - Learn about MCP protocol
+2 -2
View File
@@ -166,13 +166,13 @@ Call out the most common mistake or edge case for this layer.
title="[Related cookbook / deep dive]"
description="[Why this pairs well with the current guide]"
icon="arrow-right"
href="#related-link"
href="/[related-link]"
/>
<Card
title="[Next cookbook in journey]"
description="[Set expectation for the next step]"
icon="rocket"
href="#next-link"
href="/[next-link]"
/>
</CardGroup>
```
+1 -1
View File
@@ -1,6 +1,6 @@
---
title: Integration Guide Template
description: "A reusable template for writing integration guides that pair Mem0 with third-party tools and services."
description: "Pattern for pairing Mem0 with third-party tools."
icon: "plug"
---
+2 -2
View File
@@ -145,13 +145,13 @@ npm install mem0ai@[version]
title="[Deep dive reference]"
description="[Why this reference matters post-migration]"
icon="book"
href="#reference-link"
href="/[reference-link]"
/>
<Card
title="[Applied example or next step]"
description="[What readers can build now]"
icon="rocket"
href="#example-link"
href="/[example-link]"
/>
</CardGroup>
```
-124
View File
@@ -1,124 +0,0 @@
---
title: "Vibecoding with Mem0"
sidebarTitle: "Vibecoding"
description: "Agent skills, starter prompts, and setup for building with Mem0 using AI coding tools."
icon: "wand-magic-sparkles"
---
These docs are designed to be easily consumable by LLMs. Each page has a button that lets you copy the page as Markdown or paste directly into ChatGPT, Claude, or any AI coding tool.
We follow the llms.txt standard:
- [llms.txt](https://docs.mem0.ai/llms.txt)
<CardGroup cols={2}>
<Card title="Get an API Key" icon="key" href="https://app.mem0.ai">
Sign up for Mem0 Platform and start building
</Card>
<Card title="Quickstart" icon="rocket" href="/platform/quickstart">
Store your first memory in under 5 minutes
</Card>
</CardGroup>
## Agent Skills
Teach your coding assistant how to build with Mem0:
```bash
npx skills add https://github.com/mem0ai/mem0 --skill mem0
```
Works with Claude Code, Cursor, Windsurf, and any assistant that supports skills. Once installed, your assistant understands Mem0's full API, framework integrations, and common patterns.
## MCP Server Setup
Connect Claude, Claude Code, Cursor, Windsurf, VS Code, OpenCode, or any MCP-compatible client to Mem0.
Get your API key from [app.mem0.ai](https://app.mem0.ai), then add Mem0 MCP with a single command:
```bash
npx mcp-add \
--name mem0-mcp \
--type http \
--url "https://mcp.mem0.ai/mcp" \
--clients "claude,claude code,cursor,windsurf,vscode,opencode"
```
For per-client setup and advanced options, see [Mem0 MCP Setup](/platform/mem0-mcp).
## Universal Starter Prompt
Copy this into any AI tool to start building with Mem0:
```text
I want to start building with Mem0 — a self-improving memory layer for LLM
applications that gives agents persistent context across sessions.
## Mem0 Resources
**Documentation:**
- Main docs: https://docs.mem0.ai
- Platform Quickstart: https://docs.mem0.ai/platform/quickstart
- OSS Python Quickstart: https://docs.mem0.ai/open-source/python-quickstart
- OSS Node.js Quickstart: https://docs.mem0.ai/open-source/node-quickstart
- API Reference: https://docs.mem0.ai/api-reference
- Full LLM-friendly docs: https://docs.mem0.ai/llms.txt
**Code & Examples:**
- Core repo: https://github.com/mem0ai/mem0
- Python SDK: pip install mem0ai
- TypeScript SDK: npm install mem0ai
- Cookbooks: https://docs.mem0.ai/cookbooks/overview
**What Mem0 Does:**
Mem0 is a memory layer for AI apps — managed (Mem0 Platform) or self-hosted
(Open Source). It stores, retrieves, and manages user memories so agents
remember preferences, learn from interactions, and personalize over time.
Sub-50ms retrieval. Dual storage: vector embeddings + graph databases.
**Architecture Overview:**
- Memory is scoped by user_id, agent_id, or run_id
- Core operations: add, search, update, delete
- Memory types: factual (preferences, facts), episodic (past interactions),
semantic (concept relationships), working (session state)
- Integration pattern: retrieve relevant memories → generate response → store
new memories
**Quick Usage (Python Platform):**
from mem0 import MemoryClient
client = MemoryClient(api_key="m0-xxx")
client.add("I prefer dark mode and use VS Code.", user_id="user1")
results = client.search("What editor do they use?", user_id="user1")
**Quick Usage (JavaScript Platform):**
import MemoryClient from 'mem0ai';
const client = new MemoryClient({ apiKey: 'm0-xxx' });
await client.add([{ role: "user", content: "I prefer dark mode." }], { user_id: "user1" });
const results = await client.search("What editor?", { user_id: "user1" });
**Quick Usage (Python Open Source):**
from mem0 import Memory
m = Memory()
m.add("I prefer dark mode and use VS Code.", user_id="user1")
results = m.search("What editor do they use?", user_id="user1")
Help me integrate Mem0 into my project. Start by asking what I'm building,
what language/framework I'm using, and whether I want managed or self-hosted.
```
## Go Deeper
<CardGroup cols={2}>
<Card title="Platform Quickstart" icon="cloud" href="/platform/quickstart">
Get started with the managed API
</Card>
<Card title="Open Source" icon="code-branch" href="/open-source/overview">
Self-host with full control
</Card>
<Card title="Cookbooks" icon="book" href="/cookbooks/overview">
Production-ready tutorials and examples
</Card>
<Card title="API Reference" icon="code" href="/api-reference">
Explore every REST endpoint
</Card>
</CardGroup>
-13
View File
@@ -1,13 +0,0 @@
{
"name": "mem0",
"version": "0.1.0",
"description": "Mem0 memory layer for AI applications. Add persistent memory, personalization, and semantic search to Claude workflows using the Mem0 Platform MCP server.",
"author": {
"name": "Mem0",
"email": "support@mem0.ai"
},
"homepage": "https://mem0.ai",
"repository": "https://github.com/mem0ai/mem0",
"logo": "logo.svg",
"license": "Apache-2.0"
}
-10
View File
@@ -1,10 +0,0 @@
{
"mcpServers": {
"mem0": {
"url": "https://mcp.mem0.ai/mcp/",
"headers": {
"Authorization": "Token ${env:MEM0_API_KEY}"
}
}
}
}
-17
View File
@@ -1,17 +0,0 @@
{
"name": "mem0",
"version": "0.1.0",
"description": "Mem0 memory layer for AI applications. Add persistent memory, personalization, and semantic search using the Mem0 Platform MCP server.",
"author": {
"name": "Mem0",
"email": "support@mem0.ai"
},
"homepage": "https://mem0.ai",
"repository": "https://github.com/mem0ai/mem0",
"logo": "logo.svg",
"license": "Apache-2.0",
"keywords": ["mem0", "memory", "mcp", "personalization", "semantic-search"],
"skills": "./skills/",
"hooks": "./hooks/cursor-hooks.json",
"mcpServers": ".cursor-mcp.json"
}
-11
View File
@@ -1,11 +0,0 @@
{
"mcpServers": {
"mem0": {
"type": "http",
"url": "https://mcp.mem0.ai/mcp/",
"headers": {
"Authorization": "Token ${MEM0_API_KEY}"
}
}
}
}
-117
View File
@@ -1,117 +0,0 @@
# Mem0 Plugin for Claude Code, Claude Cowork & Cursor
Add persistent memory to your AI workflows. Store, retrieve, and manage memories across sessions using the Mem0 Platform. Works with **Claude Code** (CLI), **Claude Cowork** (desktop app), and **Cursor**.
## Step 1: Set your API key
> **You must complete this step before installing the plugin.**
1. Sign up at [app.mem0.ai](https://app.mem0.ai) if you haven't already
2. Go to [app.mem0.ai/dashboard/api-keys](https://app.mem0.ai/dashboard/api-keys)
3. Click **Create API Key** and copy the key (starts with `m0-`)
4. Add it to your shell profile:
```bash
# For zsh (default on macOS)
echo 'export MEM0_API_KEY="m0-your-api-key"' >> ~/.zshrc
source ~/.zshrc
# For bash
echo 'export MEM0_API_KEY="m0-your-api-key"' >> ~/.bashrc
source ~/.bashrc
```
5. Confirm it's set:
```bash
echo $MEM0_API_KEY
# Should print: m0-your-api-key
```
## Step 2: Install the plugin
Choose one of the options below. All require `MEM0_API_KEY` to be set first (see above).
### Claude Code (CLI) / Claude Cowork (Desktop)
Claude Code and Claude Cowork share the same plugin system.
**CLI:**
```
/plugin marketplace add mem0ai/mem0
/plugin install mem0@mem0-plugins
```
**Cowork desktop app:** Open the Cowork tab, click **Customize** in the sidebar, click **Browse plugins**, and install Mem0.
This installs the full plugin including the MCP server, lifecycle hooks (automatic memory capture), and the Mem0 SDK skill.
### Cursor
> **Already have `mem0` configured as an MCP server?** Remove the existing entry from your Cursor MCP settings before installing to avoid duplicate tools.
**Option A — One-click deeplink** (installs MCP server only):
[Install Mem0 MCP in Cursor](cursor://anysphere.cursor-deeplink/mcp/install?name=mem0&config=eyJtY3BTZXJ2ZXJzIjp7Im1lbTAiOnsidXJsIjoiaHR0cHM6Ly9tY3AubWVtMC5haS9tY3AvIiwiaGVhZGVycyI6eyJBdXRob3JpemF0aW9uIjoiVG9rZW4gJHtlbnY6TUVNMF9BUElfS0VZfSJ9fX19)
**Option B — Manual configuration** (MCP server only):
Add the following to your `.cursor/mcp.json`:
```json
{
"mcpServers": {
"mem0": {
"url": "https://mcp.mem0.ai/mcp/",
"headers": {
"Authorization": "Token ${env:MEM0_API_KEY}"
}
}
}
}
```
**Option C — Cursor Marketplace** (full plugin with hooks and skills):
Install from the [Cursor Marketplace](https://cursor.com/marketplace) for the complete experience including lifecycle hooks and the Mem0 SDK skill.
## Verify it works
After installing, confirm the MCP server is connected:
1. Start a new session (or restart your current one)
2. Ask: *"List my mem0 entities"* or *"Search my memories for hello"*
3. If the `mem0` tools appear and respond, you're all set
## What's included
| Component | Claude Code / Cowork | Cursor (Marketplace) | Cursor (Deeplink/Manual) |
|-----------|:--------------------:|:--------------------:|:------------------------:|
| MCP Server | Yes | Yes | Yes |
| Lifecycle Hooks | Yes | Yes | No |
| Mem0 SDK Skill | Yes | Yes | No |
- **MCP Server** — Connects to the Mem0 remote MCP server (`mcp.mem0.ai`), providing tools to add, search, update, and delete memories. No local dependencies required.
- **Lifecycle Hooks** — Automatic memory capture at key points: session start, context compaction, task completion, and session end.
- **Mem0 SDK Skill** — Guides the AI on how to integrate the Mem0 SDK (Python & TypeScript) into your applications.
## MCP Tools
Once installed, the following tools are available:
| Tool | Description |
|------|-------------|
| `add_memory` | Save text or conversation history for a user/agent |
| `search_memories` | Semantic search across memories with filters |
| `get_memories` | List memories with filters and pagination |
| `get_memory` | Retrieve a specific memory by ID |
| `update_memory` | Overwrite a memory's text by ID |
| `delete_memory` | Delete a single memory by ID |
| `delete_all_memories` | Bulk delete all memories in scope |
| `delete_entities` | Delete a user/agent/app/run entity and its memories |
| `list_entities` | List users/agents/apps/runs stored in Mem0 |
## License
Apache-2.0
-37
View File
@@ -1,37 +0,0 @@
{
"hooks": {
"sessionStart": [
{
"command": "${CURSOR_PLUGIN_ROOT}/scripts/on_session_start.sh",
"matcher": "startup|resume|compact"
}
],
"preToolUse": [
{
"command": "${CURSOR_PLUGIN_ROOT}/scripts/block_memory_write.sh",
"matcher": "Write|Edit"
}
],
"preCompact": [
{
"command": "${CURSOR_PLUGIN_ROOT}/scripts/on_pre_compact.sh"
},
{
"command": "python3 ${CURSOR_PLUGIN_ROOT}/scripts/on_pre_compact.py",
"timeout": 30
}
],
"stop": [
{
"command": "${CURSOR_PLUGIN_ROOT}/scripts/on_stop.sh",
"timeout": 10
}
],
"beforeSubmitPrompt": [
{
"command": "${CURSOR_PLUGIN_ROOT}/scripts/on_user_prompt.sh",
"timeout": 5
}
]
}
}
-79
View File
@@ -1,79 +0,0 @@
{
"description": "Mem0 memory capture hooks — automatic memory extraction at key lifecycle points",
"hooks": {
"SessionStart": [
{
"matcher": "startup|resume|compact",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/on_session_start.sh",
"statusMessage": "Loading mem0 context..."
}
]
}
],
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/block_memory_write.sh"
}
]
}
],
"PreCompact": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/on_pre_compact.sh",
"statusMessage": "Preparing pre-compaction summary..."
},
{
"type": "command",
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/scripts/on_pre_compact.py",
"statusMessage": "Saving session state to mem0...",
"timeout": 30
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/on_stop.sh",
"timeout": 10
}
]
}
],
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/on_user_prompt.sh",
"statusMessage": "Searching mem0 memories...",
"timeout": 5
}
]
}
],
"TaskCompleted": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/on_task_completed.sh",
"timeout": 10
}
]
}
]
}
}
-19
View File
@@ -1,19 +0,0 @@
<svg width="307" height="307" viewBox="0 0 307 307" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M162.496 25.3505C165.003 25.3505 167.453 24.6071 169.538 23.2144C171.622 21.8216 173.247 19.8419 174.206 17.5258C175.165 15.2097 175.416 12.6612 174.927 10.2024C174.438 7.74365 173.231 5.48516 171.458 3.71249C169.686 1.93983 167.427 0.73263 164.968 0.243552C162.51 -0.245525 159.961 0.00550576 157.645 0.964866C155.329 1.92423 153.349 3.54885 151.956 5.63328C150.564 7.71772 149.82 10.1683 149.82 12.6753C149.818 14.3404 150.145 15.9895 150.781 17.5283C151.417 19.0671 152.351 20.4653 153.528 21.6427C154.706 22.8201 156.104 23.7537 157.643 24.39C159.181 25.0262 160.83 25.3526 162.496 25.3505Z" fill="white"/>
<path d="M69.3342 56.559C71.1066 54.7862 72.3135 52.5277 72.8024 50.069C73.2913 47.6103 73.0401 45.0619 72.0807 42.7459C71.1213 40.43 69.4967 38.4505 67.4123 37.0579C65.3279 35.6652 62.8774 34.9219 60.3706 34.9219C57.8637 34.9219 55.4132 35.6652 53.3288 37.0579C51.2444 38.4505 49.6198 40.43 48.6604 42.7459C47.701 45.0619 47.4498 47.6103 47.9387 50.069C48.4276 52.5277 49.6345 54.7862 51.4069 56.559C52.5839 57.7363 53.9813 58.6701 55.5193 59.3073C57.0573 59.9444 58.7058 60.2724 60.3706 60.2724C62.0353 60.2724 63.6838 59.9444 65.2218 59.3073C66.7598 58.6701 68.1572 57.7363 69.3342 56.559Z" fill="white"/>
<path d="M25.3505 144.504C25.3505 141.997 24.6071 139.547 23.2143 137.462C21.8216 135.378 19.842 133.753 17.5259 132.794C15.2098 131.835 12.6612 131.584 10.2024 132.073C7.74368 132.562 5.48513 133.769 3.71247 135.542C1.9398 137.314 0.732655 139.573 0.243578 142.032C-0.2455 144.49 0.00543354 147.039 0.964793 149.355C1.92415 151.671 3.54877 153.651 5.63321 155.044C7.71764 156.436 10.1683 157.18 12.6752 157.18C16.0369 157.18 19.261 155.844 21.638 153.467C24.0151 151.09 25.3505 147.866 25.3505 144.504Z" fill="white"/>
<path d="M56.5589 237.749C54.7862 235.976 52.5277 234.769 50.069 234.28C47.6103 233.792 45.0619 234.043 42.7459 235.002C40.43 235.962 38.4505 237.586 37.0579 239.671C35.6652 241.755 34.9219 244.206 34.9219 246.712C34.9219 249.219 35.6652 251.67 37.0579 253.754C38.4505 255.838 40.43 257.463 42.7459 258.423C45.0619 259.382 47.6103 259.633 50.069 259.144C52.5277 258.655 54.7862 257.448 56.5589 255.676C57.7362 254.499 58.6701 253.102 59.3073 251.564C59.9444 250.026 60.2724 248.377 60.2724 246.712C60.2724 245.048 59.9444 243.399 59.3073 241.861C58.6701 240.323 57.7362 238.926 56.5589 237.749Z" fill="white"/>
<path d="M144.488 281.648C141.981 281.648 139.53 282.392 137.446 283.785C135.361 285.177 133.737 287.157 132.777 289.473C131.818 291.789 131.567 294.338 132.056 296.797C132.545 299.255 133.752 301.514 135.525 303.286C137.298 305.059 139.556 306.266 142.015 306.755C144.474 307.244 147.022 306.993 149.338 306.034C151.655 305.075 153.634 303.45 155.027 301.366C156.42 299.281 157.163 296.831 157.163 294.324C157.159 290.963 155.822 287.742 153.446 285.366C151.07 282.989 147.848 281.653 144.488 281.648Z" fill="white"/>
<path d="M237.751 250.487C235.978 252.26 234.771 254.518 234.282 256.977C233.794 259.435 234.045 261.984 235.004 264.3C235.964 266.616 237.588 268.595 239.673 269.988C241.757 271.381 244.207 272.124 246.714 272.124C249.221 272.124 251.672 271.381 253.756 269.988C255.84 268.595 257.465 266.616 258.424 264.3C259.384 261.984 259.635 259.435 259.146 256.977C258.657 254.518 257.45 252.26 255.678 250.487C254.501 249.31 253.104 248.376 251.566 247.739C250.028 247.101 248.379 246.773 246.714 246.773C245.05 246.773 243.401 247.101 241.863 247.739C240.325 248.376 238.928 249.31 237.751 250.487Z" fill="white"/>
<path d="M281.648 162.512C281.648 165.019 282.392 167.469 283.785 169.554C285.177 171.638 287.157 173.263 289.473 174.222C291.789 175.181 294.338 175.432 296.797 174.943C299.255 174.454 301.514 173.247 303.286 171.474C305.059 169.702 306.266 167.443 306.755 164.984C307.244 162.526 306.993 159.977 306.034 157.661C305.075 155.345 303.45 153.365 301.366 151.973C299.281 150.58 296.831 149.836 294.324 149.836C290.962 149.836 287.738 151.172 285.361 153.549C282.984 155.926 281.648 159.15 281.648 162.512Z" fill="white"/>
<path d="M250.471 69.3303C252.244 71.1027 254.503 72.3097 256.961 72.7985C259.42 73.2874 261.968 73.0363 264.284 72.0768C266.6 71.1174 268.58 69.4928 269.972 67.4084C271.365 65.324 272.108 62.8735 272.108 60.3667C272.108 57.8599 271.365 55.4093 269.972 53.3249C268.58 51.2406 266.6 49.616 264.284 48.6565C261.968 47.6971 259.42 47.4459 256.961 47.9348C254.503 48.4236 252.244 49.6306 250.471 51.403C249.294 52.58 248.36 53.9775 247.723 55.5155C247.086 57.0535 246.758 58.7019 246.758 60.3667C246.758 62.0314 247.086 63.6799 247.723 65.2179C248.36 66.7559 249.294 68.1533 250.471 69.3303Z" fill="white"/>
<path d="M184.782 60.8054C180.168 63.4713 178.3 69.0427 177.63 74.3267C177.047 78.9358 175.033 83.2457 171.87 86.6488C168.707 90.052 164.556 92.3766 160.002 93.2951C155.448 94.2136 150.721 93.6796 146.487 91.7683C142.252 89.857 138.724 86.6649 136.401 82.642C134.077 78.6192 133.075 73.9684 133.535 69.3455C133.995 64.7226 135.895 60.3607 138.966 56.8748C142.037 53.389 146.125 50.9549 150.653 49.9159C155.181 48.8768 159.921 49.2852 164.204 51.0834C169.121 53.1428 174.884 54.2762 179.514 51.6422C184.143 49.0082 185.995 43.4049 186.665 38.1208C187.245 33.5107 189.257 29.1987 192.418 25.7931C195.579 22.3876 199.73 20.0603 204.284 19.1394C208.838 18.2185 213.567 18.7505 217.803 20.6605C222.039 22.5704 225.568 25.7618 227.893 29.7846C230.218 33.8075 231.222 38.4587 230.763 43.0824C230.303 47.7062 228.404 52.0691 225.333 55.5559C222.262 59.0426 218.173 61.4773 213.645 62.5165C209.116 63.5557 204.375 63.147 200.091 61.3481C195.174 59.3048 189.411 58.1554 184.782 60.8054Z" fill="white"/>
<path d="M110.073 65.8178C108.7 70.9742 111.318 76.2422 114.575 80.4567C117.417 84.1261 119.036 88.595 119.204 93.2335C119.372 97.872 118.08 102.446 115.51 106.311C112.941 110.177 109.223 113.138 104.881 114.778C100.538 116.419 95.7912 116.655 91.3077 115.454C86.8242 114.253 82.8306 111.675 79.8898 108.084C76.9489 104.493 75.2091 100.07 74.9155 95.4379C74.6219 90.8057 75.7894 86.1981 78.2533 82.2645C80.7173 78.331 84.3534 75.2698 88.6494 73.5124C93.5822 71.485 98.4991 68.2444 99.8241 63.0881C101.149 57.9317 98.579 52.6637 95.3224 48.4493C92.4827 44.7781 90.8665 40.3083 90.7018 35.6699C90.537 31.0315 91.8319 26.4583 94.4039 22.5949C96.976 18.7314 100.695 15.7724 105.038 14.1349C109.381 12.4974 114.128 12.2639 118.611 13.4673C123.094 14.6708 127.085 17.2505 130.024 20.8429C132.963 24.4354 134.7 28.8594 134.991 33.4916C135.283 38.1237 134.113 42.7305 131.647 46.6627C129.182 50.5948 125.544 53.6541 121.248 55.4095C116.363 57.4209 111.462 60.6775 110.073 65.8178Z" fill="white"/>
<path d="M60.7892 122.218C63.4552 126.831 69.0425 128.699 74.3265 129.37C78.9361 129.955 83.2455 131.973 86.6471 135.138C90.0487 138.304 92.3707 142.457 93.2857 147.013C94.2006 151.569 93.6625 156.296 91.747 160.53C89.8314 164.763 86.6353 168.288 82.6093 170.608C78.5833 172.928 73.9305 173.926 69.3073 173.46C64.6841 172.995 60.3236 171.09 56.841 168.014C53.3583 164.938 50.9292 160.846 49.8962 156.316C48.8631 151.785 49.2783 147.045 51.0832 142.763C53.1426 137.846 54.2759 132.083 51.6419 127.454C49.0079 122.824 43.4046 120.973 38.1047 120.302C33.4951 119.717 29.1856 117.699 25.7841 114.533C22.3825 111.368 20.0604 107.214 19.1454 102.659C18.2304 98.1032 18.7687 93.3752 20.6842 89.1418C22.5997 84.9084 25.7959 81.3832 29.8219 79.0632C33.8479 76.7433 38.5006 75.7457 43.1238 76.2113C47.7471 76.6768 52.1075 78.582 55.5902 81.658C59.0728 84.7341 61.502 88.8258 62.535 93.3561C63.568 97.8865 63.1528 102.627 61.3479 106.908C59.2886 111.825 58.1552 117.588 60.7892 122.218Z" fill="white"/>
<path d="M65.8204 196.93C70.9767 198.303 76.2287 195.685 80.4592 192.428C84.1286 189.586 88.5975 187.967 93.236 187.799C97.8745 187.631 102.449 188.923 106.314 191.493C110.179 194.062 113.141 197.78 114.781 202.122C116.421 206.464 116.657 211.212 115.457 215.695C114.256 220.179 111.678 224.172 108.087 227.113C104.496 230.054 100.073 231.794 95.4404 232.087C90.8082 232.381 86.2006 231.214 82.2671 228.75C78.3335 226.286 75.2723 222.649 73.5149 218.353C71.4875 213.421 68.231 208.504 63.0906 207.179C57.9503 205.854 52.6662 208.424 48.4518 211.681C44.7804 214.528 40.308 216.151 35.6652 216.32C31.0224 216.49 26.4435 215.199 22.5738 212.628C18.7042 210.057 15.7391 206.336 14.0968 201.99C12.4544 197.644 12.2176 192.892 13.4197 188.404C14.6218 183.917 17.2021 179.919 20.7969 176.976C24.3917 174.033 28.8195 172.293 33.4562 172C38.0929 171.708 42.7045 172.878 46.6407 175.345C50.577 177.813 53.6394 181.454 55.3961 185.755C57.4235 190.656 60.6641 195.541 65.8204 196.93Z" fill="white"/>
<path d="M122.205 246.21C126.818 243.544 128.686 237.956 129.373 232.672C129.96 228.068 131.978 223.763 135.142 220.366C138.306 216.969 142.456 214.651 147.008 213.738C151.559 212.825 156.283 213.364 160.512 215.278C164.741 217.192 168.263 220.385 170.58 224.408C172.898 228.43 173.895 233.078 173.43 237.697C172.966 242.316 171.064 246.673 167.991 250.153C164.919 253.633 160.832 256.061 156.306 257.095C151.781 258.129 147.045 257.717 142.766 255.916C137.833 253.856 132.07 252.723 127.457 255.357C122.843 257.991 120.96 263.594 120.289 268.894C119.7 273.498 117.681 277.8 114.517 281.196C111.353 284.591 107.204 286.908 102.653 287.821C98.1027 288.733 93.3808 288.194 89.1525 286.281C84.9243 284.367 81.4031 281.175 79.085 277.154C76.767 273.134 75.7689 268.487 76.2316 263.869C76.6942 259.251 78.5942 254.895 81.6638 251.414C84.7334 247.933 88.8179 245.503 93.3417 244.466C97.8655 243.429 102.601 243.838 106.88 245.635C111.828 247.694 117.591 248.876 122.205 246.21Z" fill="white"/>
<path d="M196.915 241.18C198.304 236.024 195.686 230.756 192.414 226.542C189.567 222.87 187.944 218.398 187.774 213.755C187.604 209.112 188.896 204.533 191.467 200.664C194.038 196.794 197.759 193.829 202.104 192.187C206.45 190.544 211.202 190.307 215.69 191.509C220.178 192.712 224.175 195.292 227.118 198.887C230.061 202.481 231.802 206.909 232.094 211.546C232.387 216.183 231.217 220.794 228.749 224.731C226.281 228.667 222.64 231.729 218.339 233.486C213.406 235.513 208.505 238.77 207.164 243.91C205.823 249.051 208.393 254.335 211.666 258.549C214.513 262.22 216.136 266.693 216.306 271.335C216.476 275.978 215.184 280.557 212.613 284.427C210.042 288.297 206.321 291.262 201.975 292.904C197.629 294.546 192.877 294.783 188.39 293.581C183.902 292.379 179.905 289.799 176.962 286.204C174.019 282.609 172.278 278.181 171.985 273.545C171.693 268.908 172.863 264.296 175.331 260.36C177.799 256.424 181.44 253.361 185.741 251.605C190.658 249.577 195.543 246.337 196.915 241.18Z" fill="white"/>
<path d="M246.195 184.797C243.529 180.184 237.957 178.316 232.673 177.629C228.069 177.045 223.764 175.03 220.365 171.869C216.967 168.708 214.646 164.56 213.729 160.01C212.813 155.46 213.348 150.737 215.258 146.507C217.168 142.277 220.357 138.753 224.376 136.431C228.395 134.11 233.041 133.108 237.66 133.567C242.279 134.026 246.637 135.923 250.12 138.991C253.604 142.058 256.037 146.141 257.077 150.664C258.117 155.188 257.711 159.923 255.917 164.204C253.857 169.137 252.724 174.9 255.342 179.513C257.96 184.127 263.595 186.01 268.879 186.681C273.484 187.267 277.789 189.283 281.186 192.446C284.584 195.608 286.904 199.757 287.819 204.308C288.733 208.859 288.197 213.582 286.285 217.811C284.372 222.041 281.181 225.564 277.16 227.884C273.14 230.203 268.492 231.203 263.874 230.741C259.255 230.279 254.897 228.379 251.416 225.31C247.934 222.24 245.503 218.155 244.466 213.63C243.429 209.106 243.838 204.37 245.636 200.09C247.695 195.173 248.861 189.411 246.195 184.797Z" fill="white"/>
<path d="M241.18 110.07C236.024 108.697 230.756 111.315 226.542 114.588C222.87 117.435 218.398 119.058 213.755 119.228C209.112 119.398 204.533 118.106 200.664 115.535C196.794 112.964 193.829 109.243 192.187 104.897C190.544 100.551 190.307 95.7994 191.509 91.3117C192.712 86.824 195.292 82.8268 198.887 79.8837C202.481 76.9405 206.909 75.2 211.546 74.9073C216.183 74.6147 220.794 75.7849 224.731 78.2527C228.667 80.7206 231.729 84.3617 233.486 88.6627C235.513 93.5955 238.754 98.4964 243.91 99.8374C249.066 101.178 254.335 98.6082 258.549 95.3356C262.22 92.4959 266.69 90.8798 271.328 90.7151C275.967 90.5503 280.54 91.8452 284.403 94.4172C288.267 96.9892 291.226 100.709 292.863 105.052C294.501 109.394 294.734 114.142 293.531 118.624C292.327 123.107 289.748 127.099 286.155 130.037C282.563 132.976 278.139 134.714 273.507 135.005C268.875 135.296 264.268 134.126 260.336 131.661C256.403 129.195 253.344 125.557 251.589 121.261C249.577 116.36 246.321 111.459 241.18 110.07Z" fill="white"/>
<path d="M153.491 191.533C174.501 191.533 191.533 174.501 191.533 153.491C191.533 132.482 174.501 115.45 153.491 115.45C132.481 115.45 115.449 132.482 115.449 153.491C115.449 174.501 132.481 191.533 153.491 191.533Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 13 KiB

-32
View File
@@ -1,32 +0,0 @@
#!/usr/bin/env bash
# Hook: PreToolUse (matcher: Write|Edit)
#
# Blocks writes to MEMORY.md and auto-memory files, redirecting Claude
# to use the mem0 MCP add_memory tool instead.
#
# Input: JSON on stdin with tool_name, tool_input
# Output: stderr message (exit 2 = block)
#
# Exit codes:
# 0 = allow the tool call
# 2 = block the tool call (stderr is shown to Claude as feedback)
set -euo pipefail
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // ""' 2>/dev/null || echo "")
if [ -z "$FILE_PATH" ]; then
exit 0
fi
case "$FILE_PATH" in
*/MEMORY.md|*/memory/*.md|*/.claude/*/memory/*)
echo "BLOCKED: Do not write to $FILE_PATH. Use the mem0 MCP \`add_memory\` tool instead to persist memories. This project uses mem0 for all memory storage." >&2
exit 2
;;
*)
exit 0
;;
esac
-239
View File
@@ -1,239 +0,0 @@
#!/usr/bin/env python3
"""Capture session state via the Mem0 REST API.
Safety net for PreCompact and Stop hooks — reads the transcript JSONL,
extracts structured session state, and stores it in Mem0 directly.
Used by:
- PreCompact hook: Tags with "pre-compaction" (context about to be lost)
- Stop hook: Tags with "session-end" (session ending, Claude can't respond)
Input: JSON on stdin with transcript_path, session_id, cwd
Output: stderr logs only (exit 0 always — must not block)
"""
from __future__ import annotations
import json
import logging
import os
import sys
import urllib.request
import urllib.error
log = logging.getLogger("mem0-capture")
log.setLevel(logging.DEBUG)
_handler = logging.StreamHandler(sys.stderr)
_handler.setFormatter(logging.Formatter("[mem0-capture] %(message)s"))
log.addHandler(_handler)
API_URL = "https://api.mem0.ai"
MAX_TAIL_LINES = 500
MAX_USER_MESSAGES = 30
MAX_BASH_COMMANDS = 20
MAX_ASSISTANT_TEXT = 10000
def tail_lines(filepath: str, n: int) -> list[str]:
"""Read last n lines of a file efficiently."""
try:
with open(filepath, "rb") as f:
f.seek(0, 2)
file_size = f.tell()
if file_size == 0:
return []
chunk_size = min(file_size, n * 4096)
f.seek(max(0, file_size - chunk_size))
data = f.read().decode("utf-8", errors="replace")
return data.splitlines()[-n:]
except OSError:
return []
def parse_transcript(lines: list[str]) -> dict:
"""Parse transcript JSONL lines and extract session state."""
user_messages: list[str] = []
files_modified: set[str] = set()
bash_commands: list[str] = []
last_assistant_text = ""
for line in lines:
line = line.strip()
if not line:
continue
try:
entry = json.loads(line)
except json.JSONDecodeError:
continue
entry_type = entry.get("type")
if entry_type not in ("user", "assistant"):
continue
if entry.get("isSidechain"):
continue
message = entry.get("message", {})
content_blocks = message.get("content", [])
if entry_type == "user":
parts = []
if isinstance(content_blocks, str):
parts.append(content_blocks)
elif isinstance(content_blocks, list):
for block in content_blocks:
if isinstance(block, str):
parts.append(block)
elif isinstance(block, dict) and block.get("type") == "text":
parts.append(block.get("text", ""))
text = "\n".join(parts).strip()
if text and len(text) > 10 and not text.startswith("<"):
user_messages.append(text)
elif entry_type == "assistant":
for block in content_blocks:
if not isinstance(block, dict):
continue
if block.get("type") == "text":
text = block.get("text", "").strip()
if text:
last_assistant_text = text
if block.get("type") == "tool_use":
tool_name = block.get("name", "")
tool_input = block.get("input", {})
if tool_name in ("Write", "Edit"):
fp = tool_input.get("file_path", "")
if fp:
files_modified.add(fp)
elif tool_name == "Bash":
cmd = tool_input.get("command", "")
if cmd:
bash_commands.append(cmd)
return {
"user_messages": user_messages[-MAX_USER_MESSAGES:],
"files_modified": sorted(files_modified),
"bash_commands": bash_commands[-MAX_BASH_COMMANDS:],
"last_assistant_text": last_assistant_text[:MAX_ASSISTANT_TEXT],
}
def build_content(state: dict, source: str) -> str:
"""Build structured markdown from parsed state."""
parts = [f"## Session State ({source})\n"]
if state["user_messages"]:
parts.append("### What the user was working on")
for msg in state["user_messages"]:
truncated = msg[:5000] + "..." if len(msg) > 5000 else msg
parts.append(f"- {truncated}")
parts.append("")
if state["files_modified"]:
parts.append("### Files modified this session")
for fp in state["files_modified"]:
parts.append(f"- `{fp}`")
parts.append("")
if state["bash_commands"]:
parts.append("### Recent commands")
for cmd in state["bash_commands"]:
truncated = cmd[:1000] + "..." if len(cmd) > 1000 else cmd
parts.append(f"- `{truncated}`")
parts.append("")
if state["last_assistant_text"]:
parts.append("### Last context")
parts.append(state["last_assistant_text"])
parts.append("")
return "\n".join(parts)
def store_memory(api_key: str, content: str, user_id: str, source: str) -> bool:
"""Store session state as a memory via the Mem0 REST API."""
body = {
"messages": [
{"role": "user", "content": content}
],
"user_id": user_id,
"metadata": {
"type": "session_state",
"source": source,
},
}
data = json.dumps(body).encode("utf-8")
req = urllib.request.Request(
f"{API_URL}/v1/memories/",
data=data,
headers={
"Content-Type": "application/json",
"Authorization": f"Token {api_key}",
},
method="POST",
)
try:
with urllib.request.urlopen(req, timeout=15) as resp:
if resp.status in (200, 201):
log.info("Session state stored successfully")
return True
log.warning("API returned status %d", resp.status)
return False
except urllib.error.URLError as e:
log.warning("API call failed: %s", e)
return False
def main():
source = "pre-compaction"
for arg in sys.argv[1:]:
if arg.startswith("--source="):
source = arg.split("=", 1)[1]
api_key = os.environ.get("MEM0_API_KEY", "")
if not api_key:
log.debug("MEM0_API_KEY not set, skipping capture")
return
try:
hook_input = json.loads(sys.stdin.read())
except (json.JSONDecodeError, OSError):
log.debug("No valid JSON on stdin")
return
transcript_path = hook_input.get("transcript_path", "")
if not transcript_path:
log.debug("No transcript_path provided")
return
user_id = os.environ.get("MEM0_USER_ID", os.environ.get("USER", "default"))
lines = tail_lines(transcript_path, MAX_TAIL_LINES)
if not lines:
log.debug("Transcript empty or unreadable: %s", transcript_path)
return
state = parse_transcript(lines)
if not state["user_messages"] and not state["files_modified"]:
log.debug("No meaningful session state to capture")
return
content = build_content(state, source)
log.info(
"Capturing session state: %d user msgs, %d files, %d commands",
len(state["user_messages"]),
len(state["files_modified"]),
len(state["bash_commands"]),
)
store_memory(api_key, content, user_id, source)
if __name__ == "__main__":
try:
main()
except Exception as e:
log.error("Unexpected error: %s", e)
sys.exit(0)
-63
View File
@@ -1,63 +0,0 @@
#!/usr/bin/env bash
# Hook: PreCompact
#
# Fires BEFORE context compaction. This is the last chance to capture
# the full context before it gets compressed.
#
# Output: Text instructions injected into Claude's context.
# Claude still has the full conversation and can write an accurate summary.
# A companion Python script (on_pre_compact.py) also runs to capture
# transcript state directly via the Mem0 REST API as a safety net.
set -euo pipefail
cat <<'EOF'
## CRITICAL: Pre-Compaction Session Summary
Context compaction is about to happen. You are about to lose most of your conversation history. You MUST store a comprehensive session summary NOW using the mem0 `add_memory` tool.
### Step 1: Store session summary
Call `add_memory` with a thorough summary covering ALL of the following:
```
## Session Summary (Pre-Compaction)
### User's Goal
[What the user originally asked for and their intent]
### What Was Accomplished
[Numbered list of tasks completed, features built, bugs fixed]
### Key Decisions Made
[Architectural choices, design decisions, trade-offs discussed]
### Files Created or Modified
[List of important file paths with what changed in each]
### Current State
[What is in progress RIGHT NOW — the task you were in the middle of]
[Any pending items, blockers, or next steps]
### Important Context
[User preferences observed, coding patterns, anything that would help
the post-compaction agent continue without asking redundant questions]
```
Include metadata: `{"type": "session_state", "source": "pre-compaction"}`
### Step 2: Store any unstored learnings
If there are learnings from this session that you haven't stored yet, store them as separate memories:
- Failed approaches -> metadata `{"type": "anti_pattern"}`
- Successful strategies -> metadata `{"type": "task_learning"}`
- Architecture decisions -> metadata `{"type": "decision"}`
### Step 3: Acknowledge
After storing, briefly tell the user that session state has been saved and you're ready for compaction.
Do this NOW. Do not skip any section. The quality of this summary directly determines whether you can continue the user's task after compaction.
EOF
exit 0
-54
View File
@@ -1,54 +0,0 @@
#!/usr/bin/env bash
# Hook: SessionStart (matcher: startup|resume|compact)
#
# Bootstraps mem0 context at the start of every session.
# Output becomes part of Claude's context so it calls mem0 MCP tools.
#
# Input: JSON on stdin with session_id, source, transcript_path, model, cwd
# Output: Text injected into Claude's context (exit 0)
# Intentionally omit -e so the script always outputs a bootstrap prompt
# even if jq is missing or stdin is malformed.
set -uo pipefail
INPUT=$(cat)
SOURCE=$(echo "$INPUT" | jq -r '.source // "startup"' 2>/dev/null || echo "startup")
if [ "$SOURCE" = "startup" ]; then
cat <<'EOF'
## Mem0 Session Bootstrap
You have access to persistent memory via the mem0 MCP tools. Before doing anything else:
1. Call `search_memories` with a query related to the current project or user request to load relevant context.
2. Review the returned memories to understand what has been learned in prior sessions.
3. If appropriate, call `get_memories` to browse all stored memories for this user.
IMPORTANT: Do NOT skip this step. Always bootstrap context first.
EOF
elif [ "$SOURCE" = "resume" ]; then
cat <<'EOF'
## Mem0 Session Resumed
This is a resumed session. Your prior context is already loaded. Before continuing:
1. Call `search_memories` with a query related to the current task to refresh relevant memories.
2. If significant time has passed, search for recent project-wide updates.
Continue where you left off.
EOF
elif [ "$SOURCE" = "compact" ]; then
cat <<'EOF'
## Mem0 Post-Compaction Recovery
Context was just compacted. You may have lost important session context.
1. Call `search_memories` with queries related to what you were working on to reload relevant knowledge.
2. Check for any session state memories that were saved before compaction.
3. Continue working based on the recovered context.
EOF
fi
exit 0
-41
View File
@@ -1,41 +0,0 @@
#!/usr/bin/env bash
# Hook: Stop
#
# Fires when Claude finishes responding.
# Reminds Claude to store any unsaved learnings, then spawns a background
# process to capture transcript state via the Mem0 REST API directly.
#
# Input: JSON on stdin with stop_hook_active, transcript_path, cwd
# Output: Text that becomes Claude's context (exit 0), or nothing
#
# IMPORTANT: Check stop_hook_active to avoid infinite loops.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
INPUT=$(cat)
STOP_HOOK_ACTIVE=$(echo "$INPUT" | jq -r '.stop_hook_active // false' 2>/dev/null || echo "false")
if [ "$STOP_HOOK_ACTIVE" = "true" ]; then
exit 0
fi
cat <<'EOF'
Before finishing, check if there are important learnings from this interaction that should be persisted using the mem0 `add_memory` tool:
1. Were any significant decisions made? -> Store with metadata `{"type": "decision"}`
2. Were any new patterns or strategies discovered? -> Store with metadata `{"type": "task_learning"}`
3. Did any approach fail? -> Store with metadata `{"type": "anti_pattern"}`
4. Did you learn anything about the user's preferences? -> Store with metadata `{"type": "user_preference"}`
5. Were there environment/setup discoveries? -> Store with metadata `{"type": "environmental"}`
Memories can be as detailed as needed — include full context, reasoning, code snippets, file paths, and examples. Longer, searchable memories are more valuable than vague one-liners.
If nothing notable happened in this interaction, it's fine to skip. Only store genuinely useful learnings.
EOF
# Capture transcript state in the background via Mem0 REST API
echo "$INPUT" | python3 "$SCRIPT_DIR/on_pre_compact.py" --source=session-end 2>/dev/null &
exit 0
-29
View File
@@ -1,29 +0,0 @@
#!/usr/bin/env bash
# Hook: TaskCompleted
#
# Fires when a task is marked as completed. Reminds Claude to extract
# and store learnings via the mem0 MCP tools.
#
# Input: JSON on stdin with task_id, task_subject, task_description
# Output: Text that becomes feedback to the model (exit 0)
set -euo pipefail
INPUT=$(cat)
TASK_SUBJECT=$(echo "$INPUT" | jq -r '.task_subject // "unknown task"' 2>/dev/null || echo "unknown task")
cat <<EOF
Task completed: "$TASK_SUBJECT"
Extract key learnings from this completed task and store them using the mem0 \`add_memory\` tool:
1. What strategy worked well? -> Store with metadata \`{"type": "task_learning"}\`
2. Were there failed approaches before finding the solution? -> Store with metadata \`{"type": "anti_pattern"}\`
3. Were there architectural decisions? -> Store with metadata \`{"type": "decision"}\`
4. Any new conventions or patterns established? -> Store with metadata \`{"type": "convention"}\`
Memories can be as detailed as needed — include full context, reasoning, code snippets, and examples.
Only store genuinely useful learnings — skip if the task was trivial.
EOF
exit 0
-61
View File
@@ -1,61 +0,0 @@
#!/usr/bin/env bash
# Hook: UserPromptSubmit
#
# Fires on every user message. Searches mem0 for relevant memories
# and injects them into Claude's context before processing.
#
# Input: JSON on stdin with prompt, session_id, cwd, transcript_path
# Output: Matching memories as context text (exit 0)
#
# Skips search for very short prompts (< 20 chars) and when
# MEM0_API_KEY is not set. Uses a 3s timeout to minimize latency.
# Intentionally omit -e so the script always exits 0 even if
# curl or jq fail — must never block the user's prompt.
set -uo pipefail
INPUT=$(cat)
PROMPT=$(echo "$INPUT" | jq -r '.prompt // ""' 2>/dev/null || echo "")
# Skip trivial prompts — not worth a network call
if [ ${#PROMPT} -lt 20 ]; then
exit 0
fi
API_KEY="${MEM0_API_KEY:-}"
if [ -z "$API_KEY" ]; then
exit 0
fi
USER_ID="${MEM0_USER_ID:-${USER:-default}}"
# Build request body safely via jq to avoid injection
BODY=$(jq -n --arg query "$PROMPT" --arg user_id "$USER_ID" \
'{query: $query, filters: {user_id: $user_id}, top_k: 5}')
# Search mem0 for memories relevant to this prompt
RESPONSE=$(curl -s --max-time 3 \
-X POST "https://api.mem0.ai/v2/memories/search/" \
-H "Authorization: Token $API_KEY" \
-H "Content-Type: application/json" \
-d "$BODY" \
2>/dev/null || echo "")
if [ -z "$RESPONSE" ]; then
exit 0
fi
# Extract memories from response (API returns a flat array)
MEMORIES=$(echo "$RESPONSE" | jq -r '
if type == "array" then . else .results // [] end |
if length == 0 then empty else
"## Relevant memories from mem0\n\n" +
(map(select(.memory != null) | "- " + .memory) | join("\n"))
end
' 2>/dev/null || echo "")
if [ -n "$MEMORIES" ]; then
echo "$MEMORIES"
fi
exit 0
-189
View File
@@ -1,189 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but not
limited to compiled object code, generated documentation, and
conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work.
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to the Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by the Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding any notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
Copyright 2024 Mem0.ai
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-73
View File
@@ -1,73 +0,0 @@
# Mem0 Skill for Claude
Add persistent memory to any AI application in minutes using [Mem0 Platform](https://app.mem0.ai).
## What This Skill Does
When installed, Claude can:
- **Set up Mem0** in your Python or TypeScript project
- **Integrate memory** into your existing AI app (LangChain, CrewAI, Vercel AI, OpenAI Agents, LangGraph, LlamaIndex, etc.)
- **Generate working code** using real API references and tested patterns
- **Search live docs** on demand for the latest Mem0 documentation
## Installation
This skill is included automatically when you install the Mem0 plugin:
```
/plugin marketplace add mem0ai/mem0
/plugin install mem0@mem0-plugins
```
See the [plugin README](../../README.md) for full setup instructions.
### Prerequisites
- A Mem0 Platform API key ([Get one here](https://app.mem0.ai/dashboard/api-keys))
- Python 3.10+ or Node.js 18+
- Set the environment variable:
```bash
export MEM0_API_KEY="m0-your-api-key"
```
## Quick Start
After installing, just ask Claude:
- "Set up mem0 in my project"
- "Add memory to my chatbot"
- "Help me search user memories with filters"
- "Integrate mem0 with my LangChain app"
- "Add graph memory to track entity relationships"
## What's Inside
```text
skills/mem0/
├── SKILL.md # Skill definition and instructions
├── README.md # This file
├── LICENSE # Apache-2.0
├── scripts/
│ └── mem0_doc_search.py # Search live Mem0 docs on demand
└── references/ # Documentation (loaded on demand)
├── quickstart.md # Full quickstart (Python, TS, cURL)
├── sdk-guide.md # All SDK methods (Python + TypeScript)
├── api-reference.md # REST endpoints, filters, memory object
├── architecture.md # Processing pipeline, lifecycle, scoping, performance
├── features.md # Retrieval, graph, categories, MCP, webhooks, multimodal
├── integration-patterns.md # LangChain, CrewAI, Vercel AI, LangGraph, LlamaIndex, etc.
└── use-cases.md # 7 real-world patterns with Python + TypeScript code
```
## Links
- [Mem0 Platform Dashboard](https://app.mem0.ai)
- [Mem0 Documentation](https://docs.mem0.ai)
- [Mem0 GitHub](https://github.com/mem0ai/mem0)
- [API Reference](https://docs.mem0.ai/api-reference)
## License
Apache-2.0
-156
View File
@@ -1,156 +0,0 @@
---
name: mem0
description: >
Integrate Mem0 Platform into AI applications for persistent memory, personalization, and semantic search.
Use this skill when the user mentions "mem0", "memory layer", "remember user preferences",
"persistent context", "personalization", or needs to add long-term memory to chatbots, agents,
or AI apps. Covers Python and TypeScript SDKs, framework integrations (LangChain, CrewAI,
Vercel AI SDK, OpenAI Agents SDK, Pipecat), and the full Platform API. Use even when the user
doesn't explicitly say "mem0" but describes needing conversation memory, user context retention,
or knowledge retrieval across sessions.
license: Apache-2.0
metadata:
author: mem0ai
version: "0.1.0"
category: ai-memory
tags: "memory, personalization, ai, python, typescript, vector-search"
compatibility: Requires Python 3.10+ or Node.js 18+, pip install mem0ai or npm install mem0ai, MEM0_API_KEY env var, and internet access to api.mem0.ai
---
# Mem0 Platform Integration
Mem0 is a managed memory layer for AI applications. It stores, retrieves, and manages user memories via API — no infrastructure to deploy.
## Step 1: Install and authenticate
**Python:**
```bash
pip install mem0ai
export MEM0_API_KEY="m0-your-api-key"
```
**TypeScript/JavaScript:**
```bash
npm install mem0ai
export MEM0_API_KEY="m0-your-api-key"
```
Get an API key at: https://app.mem0.ai/dashboard/api-keys
## Step 2: Initialize the client
**Python:**
```python
from mem0 import MemoryClient
client = MemoryClient(api_key="m0-xxx")
```
**TypeScript:**
```typescript
import MemoryClient from 'mem0ai';
const client = new MemoryClient({ apiKey: 'm0-xxx' });
```
For async Python, use `AsyncMemoryClient`.
## Step 3: Core operations
Every Mem0 integration follows the same pattern: **retrieve → generate → store**.
### Add memories
```python
messages = [
{"role": "user", "content": "I'm a vegetarian and allergic to nuts."},
{"role": "assistant", "content": "Got it! I'll remember that."}
]
client.add(messages, user_id="alice")
```
### Search memories
```python
results = client.search("dietary preferences", user_id="alice")
for mem in results.get("results", []):
print(mem["memory"])
```
### Get all memories
```python
all_memories = client.get_all(user_id="alice")
```
### Update a memory
```python
client.update("memory-uuid", text="Updated: vegetarian, nut allergy, prefers organic")
```
### Delete a memory
```python
client.delete("memory-uuid")
client.delete_all(user_id="alice") # delete all for a user
```
## Common integration pattern
```python
from mem0 import MemoryClient
from openai import OpenAI
mem0 = MemoryClient()
openai = OpenAI()
def chat(user_input: str, user_id: str) -> str:
# 1. Retrieve relevant memories
memories = mem0.search(user_input, user_id=user_id)
context = "\n".join([m["memory"] for m in memories.get("results", [])])
# 2. Generate response with memory context
response = openai.chat.completions.create(
model="gpt-4.1-nano-2025-04-14",
messages=[
{"role": "system", "content": f"User context:\n{context}"},
{"role": "user", "content": user_input},
]
)
reply = response.choices[0].message.content
# 3. Store interaction for future context
mem0.add(
[{"role": "user", "content": user_input}, {"role": "assistant", "content": reply}],
user_id=user_id
)
return reply
```
## Common edge cases
- **Search returns empty:** Memories process asynchronously. Wait 2-3s after `add()` before searching. Also verify `user_id` matches exactly (case-sensitive).
- **AND filter with user_id + agent_id returns empty:** Entities are stored separately. Use `OR` instead, or query separately.
- **Duplicate memories:** Don't mix `infer=True` (default) and `infer=False` for the same data. Stick to one mode.
- **Wrong import:** Always use `from mem0 import MemoryClient` (or `AsyncMemoryClient` for async). Do not use `from mem0 import Memory`.
- **Immutable memories:** Cannot be updated or deleted once created. Use `client.history(memory_id)` to track changes over time.
## Live documentation search
For the latest docs beyond what's in the references, use the doc search tool:
```bash
python ${CLAUDE_SKILL_DIR}/scripts/mem0_doc_search.py --query "topic"
python ${CLAUDE_SKILL_DIR}/scripts/mem0_doc_search.py --page "/platform/features/graph-memory"
python ${CLAUDE_SKILL_DIR}/scripts/mem0_doc_search.py --index
```
No API key needed — searches docs.mem0.ai directly.
## References
Load these on demand for deeper detail:
| Topic | File |
|-------|------|
| Quickstart (Python, TS, cURL) | [references/quickstart.md](references/quickstart.md) |
| SDK guide (all methods, both languages) | [references/sdk-guide.md](references/sdk-guide.md) |
| API reference (endpoints, filters, object schema) | [references/api-reference.md](references/api-reference.md) |
| Architecture (pipeline, lifecycle, scoping, performance) | [references/architecture.md](references/architecture.md) |
| Platform features (retrieval, graph, categories, MCP, etc.) | [references/features.md](references/features.md) |
| Framework integrations (LangChain, CrewAI, Vercel AI, etc.) | [references/integration-patterns.md](references/integration-patterns.md) |
| Use cases & examples (real-world patterns with code) | [references/use-cases.md](references/use-cases.md) |
@@ -1,140 +0,0 @@
# Mem0 Platform API Reference
REST API endpoints for the Mem0 Platform. Base URL: `https://api.mem0.ai`
All endpoints require: `Authorization: Token <MEM0_API_KEY>`
## Endpoints
| Operation | Method | URL |
|-----------|--------|-----|
| Add Memories | `POST` | `/v1/memories/` |
| Search Memories | `POST` | `/v2/memories/search/` |
| Get All Memories | `POST` | `/v2/memories/` |
| Get Single Memory | `GET` | `/v1/memories/{memory_id}/` |
| Update Memory | `PUT` | `/v1/memories/{memory_id}/` |
| Delete Memory | `DELETE` | `/v1/memories/{memory_id}/` |
## Memory Object Structure
| Field | Type | Description |
|-------|------|-------------|
| `id` | string (UUID) | Unique memory identifier |
| `memory` | string | Text content of the memory |
| `user_id` | string | Associated user |
| `agent_id` | string (nullable) | Agent identifier |
| `app_id` | string (nullable) | Application identifier |
| `run_id` | string (nullable) | Run/session identifier |
| `metadata` | object | Custom key-value pairs |
| `categories` | array of strings | Auto-assigned category tags |
| `immutable` | boolean | If true, prevents modification |
| `expiration_date` | datetime (nullable) | Auto-expiry date |
| `hash` | string | Content hash |
| `created_at` | datetime | Creation timestamp |
| `updated_at` | datetime | Last modification timestamp |
Search results additionally include `score` (relevance metric).
## Scoping Identifiers
Memories can be scoped to different levels:
| Scope | Parameter | Use Case |
|-------|-----------|----------|
| User | `user_id` | Per-user memory isolation |
| Agent | `agent_id` | Per-agent memory partitioning |
| Application | `app_id` | Cross-agent app-level memory |
| Run/Session | `run_id` | Session-scoped temporary memory |
**Critical:** Combining `user_id` and `agent_id` in a single AND filter yields empty results. Entities are stored separately. Use `OR` logic or separate queries.
## Processing Model
- Memories are processed **asynchronously by default** (`async_mode=true`)
- Add responses return queued events (`ADD`, `UPDATE`, `DELETE`) for tracking
- Set `async_mode=false` for synchronous processing when needed
- Graph metadata is processed asynchronously -- use `get_all()` for complete graph data
## Filter System
Filters use nested JSON with a logical operator at the root:
```json
{
"AND": [
{"user_id": "alice"},
{"categories": {"contains": "finance"}},
{"created_at": {"gte": "2024-01-01"}}
]
}
```
Root must be `AND`, `OR`, or `NOT`. Simple shorthand `{"user_id": "alice"}` also works.
### Supported Operators
| Operator | Description |
|----------|-------------|
| `eq` | Equal to (default) |
| `ne` | Not equal to |
| `in` | Matches any value in array |
| `gt`, `gte` | Greater than / greater than or equal |
| `lt`, `lte` | Less than / less than or equal |
| `contains` | Case-sensitive containment |
| `icontains` | Case-insensitive containment |
| `*` | Wildcard -- matches any non-null value |
### Filterable Fields
| Field | Valid Operators |
|-------|-----------------|
| `user_id`, `agent_id`, `app_id`, `run_id` | `eq`, `ne`, `in`, `*` |
| `created_at`, `updated_at`, `timestamp` | `gt`, `gte`, `lt`, `lte`, `eq`, `ne` |
| `categories` | `eq`, `ne`, `in`, `contains` |
| `metadata` | `eq`, `ne`, `contains` (top-level keys only) |
| `keywords` | `contains`, `icontains` |
| `memory_ids` | `in` |
### Filter Constraints
1. **Entity scope partitioning:** `user_id` AND `agent_id` in one `AND` block yields empty results.
2. **Metadata limitations:** Only top-level keys. Only `eq`, `contains`, `ne`. No `in` or `gt`.
3. **Operator syntax:** Use `gte`, `lt`, `ne`. SQL-style (`>=`, `!=`) rejected.
4. **Entity filter required for get-all:** At least one of `user_id`, `agent_id`, `app_id`, or `run_id`.
5. **Wildcard excludes null:** `*` matches only non-null values.
6. **Date format:** ISO 8601 (`YYYY-MM-DDTHH:MM:SSZ`). Timezone-naive defaults to UTC.
## Response Formats
### Add Response
```json
[
{
"id": "mem_01JF8ZS4Y0R0SPM13R5R6H32CJ",
"event": "ADD",
"data": { "memory": "The user moved to Austin in 2025." }
}
]
```
Event types: `ADD`, `UPDATE`, `DELETE`. A single add can trigger multiple events.
### Search Response
```json
{
"results": [
{
"id": "ea925981-...",
"memory": "Is a vegetarian and allergic to nuts.",
"user_id": "user123",
"categories": ["food", "health"],
"score": 0.89,
"created_at": "2024-07-26T10:29:36.630547-07:00"
}
]
}
```
With `enable_graph=true`, includes additional `relations` array with entity relationships.
@@ -1,386 +0,0 @@
# Mem0 Platform Architecture
How Mem0 processes, stores, and retrieves memories under the hood.
## Table of Contents
- [Core Concept](#core-concept)
- [Memory Processing Pipeline](#memory-processing-pipeline)
- [Retrieval Pipeline](#retrieval-pipeline)
- [Memory Lifecycle](#memory-lifecycle)
- [Memory Object Structure](#memory-object-structure)
- [Scoping & Multi-Tenancy](#scoping--multi-tenancy)
- [Memory Layers](#memory-layers)
- [Performance Characteristics](#performance-characteristics)
---
## Core Concept
Mem0 is a managed memory layer that sits between your AI application and users. Every integration follows the same 3-step loop:
```
User Input → Retrieve relevant memories → Enrich LLM prompt → Generate response → Store new memories
```
Mem0 handles the complexity of extraction, deduplication, conflict resolution, and semantic retrieval so your application only needs to call `search()` and `add()`.
**Dual storage architecture:**
- **Vector store**: Embeddings for semantic similarity search
- **Graph store** (optional): Entity nodes and relationship edges for structured knowledge
---
## Memory Processing Pipeline
### What happens when you call `client.add()`
```
Messages In
│
▼
┌─────────────────────┐
│ 1. EXTRACTION │ LLM analyzes messages, extracts key facts
│ (infer=True) │ If infer=False, stores raw text as-is
└─────────┬───────────┘
│
▼
┌─────────────────────┐
│ 2. CONFLICT │ Checks existing memories for duplicates
│ RESOLUTION │ Latest truth wins (newer overrides older)
│ │ Only runs when infer=True
└─────────┬───────────┘
│
▼
┌─────────────────────┐
│ 3. STORAGE │ Generates embeddings → vector store
│ │ Optional: entity extraction → graph store
│ │ Indexes metadata, categories, timestamps
└─────────┬───────────┘
│
▼
Memory Object
(id, memory, categories, structured_attributes)
```
### Processing modes
**Async (default, `async_mode=True`):**
- API returns immediately: `{"status": "PENDING", "event_id": "..."}`
- Processing happens in background
- Use webhooks for completion notifications
- Best for: high-throughput, non-blocking workflows
**Sync (`async_mode=False`):**
- API waits for full processing
- Returns complete memory object with `id`, `event`, `memory`
- Best for: real-time access immediately after add
### Extraction modes
**Inferred (`infer=True`, default):**
- LLM extracts structured facts from conversation
- Conflict resolution deduplicates and resolves contradictions
- Best for: natural conversation → memory
**Raw (`infer=False`):**
- Stores text exactly as provided, no LLM processing
- Skips conflict resolution — same fact can be stored twice
- Only `user` role messages are stored; `assistant` messages ignored
- Best for: bulk imports, pre-structured data, migrations
**Warning:** Don't mix `infer=True` and `infer=False` for the same data — the same fact will be stored twice.
---
## Retrieval Pipeline
### What happens when you call `client.search()`
```
Query In
│
▼
┌─────────────────────┐
│ 1. QUERY EMBEDDING │ Convert query to vector representation
└─────────┬───────────┘
│
▼
┌─────────────────────┐
│ 2. VECTOR SEARCH │ Cosine similarity across stored embeddings
│ │ Scoped by filters (user_id, agent_id, etc.)
└─────────┬───────────┘
│
▼ (optional enhancements)
┌─────────────────────┐
│ 3a. KEYWORD SEARCH │ Expands results with specific terms (+10ms)
│ 3b. RERANKING │ Deep semantic reordering (+150-200ms)
│ 3c. FILTER MEMORIES │ Precision filtering, removes low-relevance (+200-300ms)
└─────────┬───────────┘
│
▼ (if enable_graph=True)
┌─────────────────────┐
│ 4. GRAPH LOOKUP │ Finds entity relationships
│ │ Appends relations WITHOUT reranking vector results
└─────────┬───────────┘
│
▼
Results + Relations
```
### Retrieval enhancement combinations
| Configuration | Latency | Best for |
|--------------|---------|----------|
| Base search only | ~100ms | Simple lookups |
| `keyword_search=True` | ~110ms | Entity-heavy queries, broad coverage |
| `rerank=True` | ~250-300ms | User-facing results, top-N precision |
| `keyword_search=True` + `rerank=True` | ~310ms | Balanced (recommended for most apps) |
| `rerank=True` + `filter_memories=True` | ~400-500ms | Safety-critical, production systems |
### Implicit null scoping
When you search with `user_id="alice"` only, Mem0 returns memories where `agent_id`, `app_id`, and `run_id` are all null. This prevents cross-scope leakage by default.
To include memories with non-null fields, use explicit filters:
```python
# Gets memories for alice regardless of agent/app/run
filters={"OR": [{"user_id": "alice"}]}
```
---
## Memory Lifecycle
```
CREATE ──→ ACTIVE ──→ UPDATE ──→ ACTIVE
│ │ │
│ ▼ ▼
│ EXPIRED EXPIRED
│ (still stored, (still stored,
│ not retrieved) not retrieved)
│ │ │
▼ ▼ ▼
DELETE DELETE DELETE
(permanent)
```
### Creation
- Triggered by `client.add(messages, user_id="...")`
- Messages processed through extraction → conflict resolution → storage
- Gets unique UUID, `created_at` timestamp
- Optional: custom `timestamp`, `expiration_date`, `metadata`, `immutable`
### Updates
- `client.update(memory_id, text="...")` replaces text and reindexes
- `client.batch_update([...])` for up to 1000 memories at once
- Immutable memories (`immutable=True`) cannot be updated — must delete and re-add
### Deduplication
- Automatic during `add()` with `infer=True`
- Conflict resolution merges duplicate facts
- Latest truth wins when contradictions detected
- Prevents memory bloat from repeated information
### Expiration
- Optional `expiration_date` parameter (ISO 8601 or `YYYY-MM-DD`)
- After expiration: memory NOT returned in searches but remains in storage
- Useful for time-sensitive info (events, temporary preferences, session state)
### Deletion
- Single: `client.delete(memory_id)` — permanent, no recovery
- Batch: `client.batch_delete([memory_ids])` — up to 1000
- Bulk: `client.delete_all(user_id="alice")` — all memories for entity
- `delete_all()` without filters raises error to prevent accidental data loss
### History tracking
- `client.history(memory_id)` returns version timeline
- Shows all changes: `{previous_value, new_value, action, timestamps}`
- Useful for audit trails and debugging
---
## Memory Object Structure
```json
{
"id": "uuid-string",
"memory": "Extracted memory text",
"user_id": "user-identifier",
"agent_id": null,
"app_id": null,
"run_id": null,
"metadata": { "source": "chat", "priority": "high" },
"categories": ["health", "preferences"],
"created_at": "2025-03-12T12:34:56Z",
"updated_at": "2025-03-12T12:34:56Z",
"expiration_date": null,
"immutable": false,
"structured_attributes": {
"day": 12, "month": 3, "year": 2025,
"hour": 12, "minute": 34,
"day_of_week": "wednesday",
"is_weekend": false,
"quarter": 1, "week_of_year": 11
},
"score": 0.85
}
```
| Field | Type | Description |
|-------|------|-------------|
| `id` | UUID | Unique identifier, used for update/delete |
| `memory` | string | Extracted or stored text content |
| `user_id` | string | Primary entity scope |
| `agent_id` | string | Agent scope |
| `app_id` | string | Application scope |
| `run_id` | string | Session/run scope |
| `metadata` | object | Custom key-value pairs for filtering |
| `categories` | array | Auto-assigned or custom category tags |
| `created_at` | datetime | Creation timestamp |
| `updated_at` | datetime | Last modification timestamp |
| `expiration_date` | datetime | Auto-expiry date (stops retrieval, data persists) |
| `immutable` | boolean | If true, prevents modification |
| `structured_attributes` | object | Temporal breakdown for time-based queries |
| `score` | float | Semantic similarity (search results only, 0-1) |
---
## Scoping & Multi-Tenancy
Mem0 separates memories across four dimensions to prevent data mixing:
| Dimension | Field | Purpose | Example |
|-----------|-------|---------|---------|
| User | `user_id` | Persistent persona or account | `"customer_6412"` |
| Agent | `agent_id` | Distinct agent or tool | `"meal_planner"` |
| App | `app_id` | Product surface or deployment | `"ios_retail_app"` |
| Session | `run_id` | Short-lived flow or thread | `"ticket-9241"` |
### Storage model
Each entity combination creates separate records. A memory with `user_id="alice"` is stored separately from one with `user_id="alice"` + `agent_id="bot"`.
### Critical: cross-entity queries
```python
# This returns NOTHING — user and agent memories are stored separately
filters={"AND": [{"user_id": "alice"}, {"agent_id": "bot"}]}
# Use OR to query multiple scopes
filters={"OR": [{"user_id": "alice"}, {"agent_id": "bot"}]}
# Use wildcard to include any non-null value
filters={"AND": [{"user_id": "*"}]} # All users (excludes null)
```
### Recommended scoping patterns
```python
# User-level: persistent preferences
client.add(messages, user_id="alice")
# Session-level: temporary context
client.add(messages, user_id="alice", run_id="session_123")
# Clean up when done: client.delete_all(run_id="session_123")
# Agent-level: agent-specific knowledge
client.add(messages, agent_id="support_bot", app_id="helpdesk")
# Multi-tenant: full isolation
client.add(messages, user_id="alice", agent_id="bot", app_id="acme_corp", run_id="ticket_42")
```
---
## Memory Layers
Mem0 supports three layers of memory, from shortest to longest lived:
### Conversation memory
- In-flight messages within a single turn
- Tool calls, chain-of-thought reasoning
- **Lifetime:** Single response — lost after turn finishes
- **Managed by:** Your application, not Mem0
### Session memory
- Short-lived facts for current task or channel
- Multi-step flows (onboarding, debugging, support tickets)
- **Lifetime:** Minutes to hours
- **Managed by:** Mem0 via `run_id` parameter
- Clean up with `client.delete_all(run_id="session_id")`
### User memory
- Long-lived knowledge tied to a person or account
- Personal preferences, account state, compliance details
- **Lifetime:** Weeks to forever
- **Managed by:** Mem0 via `user_id` parameter
- Persists across all sessions and interactions
### How layering works in practice
```python
def chat(user_input: str, user_id: str, session_id: str) -> str:
# 1. Retrieve user memories (long-term preferences)
user_mems = mem0.search(user_input, user_id=user_id)
# 2. Retrieve session memories (current task context)
session_mems = mem0.search(user_input, filters={
"AND": [{"user_id": user_id}, {"run_id": session_id}]
})
# 3. Combine both layers for LLM context
context = format_memories(user_mems) + format_memories(session_mems)
# 4. Generate response
response = llm.generate(context=context, input=user_input)
# 5. Store in session scope (temporary) + user scope (persistent)
messages = [{"role": "user", "content": user_input}, {"role": "assistant", "content": response}]
mem0.add(messages, user_id=user_id, run_id=session_id)
return response
```
---
## Performance Characteristics
### Latency
| Operation | Typical Latency |
|-----------|----------------|
| Base vector search | ~100ms |
| + keyword_search | +10ms |
| + reranking | +150-200ms |
| + filter_memories | +200-300ms |
| Add (async, default) | < 50ms response, background processing |
| Add (sync) | 500ms-2s depending on extraction complexity |
| Graph operations | Slight overhead for large stores |
### Processing
- **Async mode (default):** Returns immediately, processes in background
- **Sync mode:** Waits for full extraction + storage pipeline
- **Batch operations:** Up to 1000 memories per batch_update/batch_delete
- **Webhooks:** Real-time notifications when async processing completes
### Scoping strategy for performance
- Use `user_id` for all user-facing queries (most common, fastest)
- Add `run_id` for session isolation (narrows search space)
- Avoid wildcard `"*"` filters on large datasets (scans all non-null records)
- Use `top_k` to limit result count when you only need a few memories
---
## Comparison with Alternatives
| Approach | Pros | Cons |
|----------|------|------|
| **Raw vector DB** | Fast, full control | No extraction, no dedup, no conflict resolution |
| **In-memory chat history** | Zero latency | Lost on restart, no cross-session, grows unbounded |
| **RAG over documents** | Good for static knowledge | No personalization, no memory updates |
| **Mem0 Platform** | Managed extraction + dedup + graph + scoping | External dependency, async processing delay |
Mem0 combines the best of vector search (semantic retrieval) with automatic extraction (LLM-powered), conflict resolution (deduplication), and structured scoping (multi-tenancy) — in a single managed API.
@@ -1,496 +0,0 @@
# Platform Features -- Mem0 Platform
Additional platform capabilities beyond core CRUD operations.
## Table of Contents
- [Advanced Retrieval](#advanced-retrieval)
- [Graph Memory](#graph-memory)
- [Custom Categories](#custom-categories)
- [Custom Instructions](#custom-instructions)
- [Criteria Retrieval](#criteria-retrieval)
- [Feedback Mechanism](#feedback-mechanism)
- [Memory Export](#memory-export)
- [Group Chat](#group-chat)
- [MCP Integration](#mcp-integration)
- [Webhooks](#webhooks)
- [Multimodal Support](#multimodal-support)
## Advanced Retrieval
Three enhancement options for tuning search precision, recall, and latency.
### Keyword Search (`keyword_search=True`)
Expands results to include memories with specific terms, names, and technical keywords.
- Latency: +10ms
- Recall: Significantly increased
- Best for: entity-heavy queries, comprehensive coverage
### Reranking (`rerank=True`)
Deep semantic reordering of results — most relevant first.
- Latency: +150-200ms
- Accuracy: Significantly improved
- Best for: user-facing results, top-N precision
### Filter Memories (`filter_memories=True`)
Precision filtering — removes low-relevance results entirely.
- Latency: +200-300ms
- Precision: Maximized
- Best for: safety-critical applications, production systems
### Recommended Combinations
**Python:**
```python
# Fast & broad
results = client.search(query, keyword_search=True, user_id="user123")
# Balanced (recommended for most apps)
results = client.search(query, keyword_search=True, rerank=True, user_id="user123")
# High precision (critical apps)
results = client.search(query, rerank=True, filter_memories=True, user_id="user123")
```
**TypeScript:**
```typescript
const results = await client.search(query, {
user_id: 'user123',
keyword_search: true,
rerank: true,
});
```
---
## Graph Memory
Entity-level knowledge graph that creates relationships between memories.
### How It Works
1. **Extraction**: LLM analyzes conversation and identifies entities and relationships
2. **Storage**: Embeddings go to vector store; entity nodes and edges go to graph store
3. **Retrieval**: Vector search returns semantic matches; graph relations are appended to results
Graph relations **augment** vector results without reordering them. Vector similarity always determines hit sequence.
### Enabling Graph Memory
**Per request:**
```python
client.add(messages, user_id="alice", enable_graph=True)
client.search("query", user_id="alice", enable_graph=True)
client.get_all(filters={"AND": [{"user_id": "alice"}]}, enable_graph=True)
```
**Project-level (default for all operations):**
```python
client.project.update(enable_graph=True)
```
```javascript
await client.updateProject({ enable_graph: true });
```
### Relation Structure
Each relation in the response contains:
| Field | Type | Description |
|-------|------|-------------|
| `source` | string | Source entity name |
| `source_type` | string | Source entity type (e.g., "Person") |
| `relationship` | string | Relationship label (e.g., "lives_in") |
| `target` | string | Target entity name |
| `target_type` | string | Target entity type (e.g., "City") |
| `score` | number | Confidence score |
**Example:**
```json
{
"relations": [
{
"source": "Joseph",
"source_type": "Person",
"relationship": "lives_in",
"target": "Seattle",
"target_type": "City",
"score": 0.92
}
]
}
```
### Technical Notes
- Graph Memory adds processing time; see docs for current plan availability
- Works optimally with rich conversation histories containing entity relationships
- Best suited for long-running assistants tracking evolving information
- Graph writes and reads toggle independently per request
- Multi-agent context supported via `user_id`, `agent_id`, `run_id` scoping
- Add operations are asynchronous; graph metadata may not be immediately available
---
## Custom Categories
Replace Mem0's default 15 labels with domain-specific categories. The system automatically tags memories to the closest matching category.
### Default Categories (15)
`personal_details`, `family`, `professional_details`, `sports`, `travel`, `food`, `music`, `health`, `technology`, `hobbies`, `fashion`, `entertainment`, `milestones`, `user_preferences`, `misc`
### Configuration
**Set project-level categories:**
```python
new_categories = [
{"lifestyle_management": "Tracks daily routines, habits, wellness activities"},
{"seeking_structure": "Documents goals around creating routines and systems"},
{"personal_information": "Basic information about the user"}
]
client.project.update(custom_categories=new_categories)
```
```javascript
await client.updateProject({ custom_categories: new_categories });
```
**Retrieve active categories:**
```python
categories = client.project.get(fields=["custom_categories"])
```
### Key Constraint
Per-request overrides (`custom_categories=...` on `client.add`) are **not supported** on the managed API. Only project-level configuration works. Workaround: store ad-hoc labels in `metadata` field.
---
## Custom Instructions
Natural language filters that control what information Mem0 extracts when creating memories.
### Set Instructions
```python
client.project.update(custom_instructions="Your guidelines here...")
```
```javascript
await client.updateProject({ custom_instructions: "Your guidelines here..." });
```
### Template Structure
1. **Task Description** -- brief extraction overview
2. **Information Categories** -- numbered sections with specific details to capture
3. **Processing Guidelines** -- quality and handling rules
4. **Exclusion List** -- sensitive/irrelevant data to filter out
### Domain Examples
**E-commerce:** Capture product issues, preferences, service experience; exclude payment data.
**Education:** Extract learning progress, student preferences, performance patterns; exclude specific grades.
**Finance:** Track financial goals, life events, investment interests; exclude account numbers and SSNs.
### Best Practices
- Start simply, test with sample messages, iterate based on results
- Avoid overly lengthy instructions
- Be specific about what to include AND exclude
---
## Criteria Retrieval
Custom attribute-based memory ranking using LLM-evaluated criteria with weights. Goes beyond semantic similarity to prioritize memories based on domain-specific signals.
### Configuration
```python
# Define criteria at project level
retrieval_criteria = [
{"name": "joy", "description": "Positive emotions like happiness and excitement", "weight": 3},
{"name": "curiosity", "description": "Inquisitiveness and desire to learn", "weight": 2},
{"name": "urgency", "description": "Time-sensitive or high-priority items", "weight": 4},
]
client.project.update(retrieval_criteria=retrieval_criteria)
```
```typescript
await client.updateProject({
retrieval_criteria: [
{ name: 'joy', description: 'Positive emotions', weight: 3 },
{ name: 'urgency', description: 'Time-sensitive items', weight: 4 },
],
});
```
### Usage
Once configured, `client.search()` automatically applies criteria ranking:
```python
# Criteria-weighted results returned automatically
results = client.search("Why am I feeling happy?", filters={"user_id": "alice"})
```
**Best for:** Wellness assistants, tutoring platforms, productivity tools — any app needing intent-aware retrieval.
---
## Feedback Mechanism
Provide feedback on extracted memories to improve system quality over time.
### Feedback Types
| Type | Meaning |
|------|---------|
| `POSITIVE` | Memory is useful and accurate |
| `NEGATIVE` | Memory is not useful |
| `VERY_NEGATIVE` | Memory is harmful or completely wrong |
| `None` | Clear existing feedback |
### Usage
**Python:**
```python
client.feedback(
memory_id="mem-123",
feedback="POSITIVE",
feedback_reason="Accurately captured dietary preference"
)
# Bulk feedback
for item in feedback_data:
client.feedback(**item)
```
**TypeScript:**
```typescript
await client.feedback('mem-123', {
feedback: 'POSITIVE',
feedback_reason: 'Accurately captured dietary preference',
});
```
---
## Memory Export
Create structured exports of memories using customizable schemas with filters.
### Usage
```python
import json
# Define export schema
schema = {
"type": "object",
"properties": {
"name": {"type": "string"},
"preferences": {"type": "array", "items": {"type": "string"}},
"health_info": {"type": "string"},
}
}
# Create export
response = client.create_memory_export(
schema=json.dumps(schema),
filters={"user_id": "alice"},
export_instructions="Create comprehensive profile based on all memories"
)
# Retrieve export (may take a moment to process)
result = client.get_memory_export(memory_export_id=response["id"])
```
**Best for:** Data analytics, user profile generation, compliance audits, CRM sync.
---
## Group Chat
Process multi-participant conversations and automatically attribute memories to individual speakers.
### Usage
```python
messages = [
{"role": "user", "name": "Alice", "content": "I think we should use React for the frontend"},
{"role": "user", "name": "Bob", "content": "I prefer Vue.js, it's simpler for our use case"},
{"role": "assistant", "content": "Both are great choices. Let me note your preferences."},
]
# Mem0 automatically attributes memories to each speaker
response = client.add(messages, run_id="team_meeting_1")
# Retrieve Alice's memories from that session
alice_mems = client.get_all(
filters={"AND": [{"user_id": "alice"}, {"run_id": "team_meeting_1"}]}
)
```
Use the `name` field in messages to identify speakers. Mem0 maps names to entity scopes automatically.
---
## MCP Integration
Model Context Protocol integration enables AI clients (Claude Desktop, Cursor, custom agents) to manage Mem0 memory autonomously.
### Configuration
```json
{
"mcpServers": {
"mem0": {
"command": "uvx",
"args": ["mem0-mcp-server"],
"env": {
"MEM0_API_KEY": "m0-your-api-key",
"MEM0_DEFAULT_USER_ID": "your-user-id"
}
}
}
}
```
### Available MCP Tools
The MCP server exposes 9 memory tools that AI agents can use autonomously:
- Add, search, get, update, delete memories
- Get history, list users, delete users
- Search Mem0 documentation
### How It Works
1. Configure the MCP server in your AI client
2. The agent autonomously decides when to store/retrieve memories
3. No manual API calls needed — the agent manages memory as part of its reasoning
**Best for:** Universal AI client integration — one protocol works everywhere.
---
## Webhooks
Real-time event notifications for memory operations.
### Supported Events
| Event | Trigger |
|-------|---------|
| `memory_add` | Memory created |
| `memory_update` | Memory modified |
| `memory_delete` | Memory removed |
| `memory_categorize` | Memory tagged |
### Create Webhook
Note: `project_id` here refers to the Mem0 dashboard project scope for webhooks — not the deprecated client init parameter.
```python
webhook = client.create_webhook(
url="https://your-app.com/webhook",
name="Memory Logger",
project_id="proj_123",
event_types=["memory_add", "memory_categorize"]
)
```
### Manage Webhooks
```python
# Retrieve
webhooks = client.get_webhooks(project_id="proj_123")
# Update
client.update_webhook(
name="Updated Logger",
url="https://your-app.com/new-webhook",
event_types=["memory_update", "memory_add"],
webhook_id="wh_123"
)
# Delete
client.delete_webhook(webhook_id="wh_123")
```
### Payload Structure
Memory events contain: ID, data object with memory content, event type (`ADD`/`UPDATE`/`DELETE`).
Categorization events contain: memory ID, event type (`CATEGORIZE`), assigned category labels.
---
## Multimodal Support
Mem0 can process images and documents alongside text.
### Supported Media Types
- Images: JPG, PNG
- Documents: MDX, TXT, PDF
### Image via URL
```python
image_message = {
"role": "user",
"content": {
"type": "image_url",
"image_url": {"url": "https://example.com/image.jpg"}
}
}
client.add([image_message], user_id="alice")
```
### Image via Base64
```python
import base64
with open("photo.jpg", "rb") as f:
base64_image = base64.b64encode(f.read()).decode("utf-8")
image_message = {
"role": "user",
"content": {
"type": "image_url",
"image_url": {"url": f"data:image/jpeg;base64,{base64_image}"}
}
}
client.add([image_message], user_id="alice")
```
### Document (MDX/TXT)
```python
doc_message = {
"role": "user",
"content": {"type": "mdx_url", "mdx_url": {"url": document_url}}
}
client.add([doc_message], user_id="alice")
```
### PDF Document
```python
pdf_message = {
"role": "user",
"content": {"type": "pdf_url", "pdf_url": {"url": pdf_url}}
}
client.add([pdf_message], user_id="alice")
```
@@ -1,444 +0,0 @@
# Mem0 Integration Patterns
Working code examples for integrating Mem0 Platform with popular AI frameworks.
All examples use `MemoryClient` (Platform API key).
Code examples are sourced from official Mem0 integration docs at docs.mem0.ai, simplified for quick reference.
---
## Common Pattern
Every integration follows the same 3-step loop:
1. **Retrieve** -- search relevant memories before generating a response
2. **Generate** -- include memories as context in the LLM prompt
3. **Store** -- save the interaction back to Mem0 for future use
---
## LangChain
Source: [docs.mem0.ai/integrations/langchain](https://docs.mem0.ai/integrations/langchain)
```python
from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage, HumanMessage
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from mem0 import MemoryClient
llm = ChatOpenAI(model="gpt-4.1-nano-2025-04-14")
mem0 = MemoryClient()
prompt = ChatPromptTemplate.from_messages([
SystemMessage(content="You are a helpful travel agent AI. Use the provided context to personalize your responses."),
MessagesPlaceholder(variable_name="context"),
HumanMessage(content="{input}")
])
def retrieve_context(query: str, user_id: str):
"""Retrieve relevant memories from Mem0"""
memories = mem0.search(query, user_id=user_id)
memory_list = memories['results']
serialized = ' '.join([m["memory"] for m in memory_list])
return [
{"role": "system", "content": f"Relevant information: {serialized}"},
{"role": "user", "content": query}
]
def chat_turn(user_input: str, user_id: str) -> str:
# 1. Retrieve
context = retrieve_context(user_input, user_id)
# 2. Generate
chain = prompt | llm
response = chain.invoke({"context": context, "input": user_input})
# 3. Store
mem0.add(
[{"role": "user", "content": user_input}, {"role": "assistant", "content": response.content}],
user_id=user_id
)
return response.content
```
---
## CrewAI
Source: [docs.mem0.ai/integrations/crewai](https://docs.mem0.ai/integrations/crewai)
CrewAI has native Mem0 integration via `memory_config`:
```python
from crewai import Agent, Task, Crew, Process
from mem0 import MemoryClient
client = MemoryClient()
# Store user preferences first
messages = [
{"role": "user", "content": "I am more of a beach person than a mountain person."},
{"role": "assistant", "content": "Noted! I'll recommend beach destinations."},
{"role": "user", "content": "I like Airbnb more than hotels."},
]
client.add(messages, user_id="crew_user_1")
# Create agent
travel_agent = Agent(
role="Personalized Travel Planner",
goal="Plan personalized travel itineraries",
backstory="You are a seasoned travel planner.",
memory=True,
)
# Create task
task = Task(
description="Find places to live, eat, and visit in San Francisco.",
expected_output="A detailed list of places to live, eat, and visit.",
agent=travel_agent,
)
# Setup crew with Mem0 memory
crew = Crew(
agents=[travel_agent],
tasks=[task],
process=Process.sequential,
memory=True,
memory_config={
"provider": "mem0",
"config": {"user_id": "crew_user_1"},
}
)
result = crew.kickoff()
```
---
## Vercel AI SDK
Source: [docs.mem0.ai/integrations/vercel-ai-sdk](https://docs.mem0.ai/integrations/vercel-ai-sdk)
Install: `npm install @mem0/vercel-ai-provider`
### Basic Text Generation with Memory
```typescript
import { generateText } from "ai";
import { createMem0 } from "@mem0/vercel-ai-provider";
const mem0 = createMem0({
provider: "openai",
mem0ApiKey: "m0-xxx",
apiKey: "openai-api-key",
});
const { text } = await generateText({
model: mem0("gpt-4-turbo", { user_id: "borat" }),
prompt: "Suggest me a good car to buy!",
});
```
### Streaming with Memory
```typescript
import { streamText } from "ai";
import { createMem0 } from "@mem0/vercel-ai-provider";
const mem0 = createMem0();
const { textStream } = streamText({
model: mem0("gpt-4-turbo", { user_id: "borat" }),
prompt: "Suggest me a good car to buy!",
});
for await (const textPart of textStream) {
process.stdout.write(textPart);
}
```
### Using Memory Utilities Standalone
```typescript
import { openai } from "@ai-sdk/openai";
import { generateText } from "ai";
import { retrieveMemories, addMemories } from "@mem0/vercel-ai-provider";
// Retrieve memories and inject into any provider
const prompt = "Suggest me a good car to buy.";
const memories = await retrieveMemories(prompt, { user_id: "borat", mem0ApiKey: "m0-xxx" });
const { text } = await generateText({
model: openai("gpt-4-turbo"),
prompt: prompt,
system: memories,
});
// Store new memories
await addMemories(
[{ role: "user", content: [{ type: "text", text: "I love red cars." }] }],
{ user_id: "borat", mem0ApiKey: "m0-xxx" }
);
```
### Supported Providers
`openai`, `anthropic`, `google`, `groq`
---
## OpenAI Agents SDK
Source: [docs.mem0.ai/integrations/openai-agents-sdk](https://docs.mem0.ai/integrations/openai-agents-sdk)
```python
from agents import Agent, Runner, function_tool
from mem0 import MemoryClient
mem0 = MemoryClient()
@function_tool
def search_memory(query: str, user_id: str) -> str:
"""Search through past conversations and memories"""
memories = mem0.search(query, user_id=user_id, top_k=3)
if memories and memories.get('results'):
return "\n".join([f"- {mem['memory']}" for mem in memories['results']])
return "No relevant memories found."
@function_tool
def save_memory(content: str, user_id: str) -> str:
"""Save important information to memory"""
mem0.add([{"role": "user", "content": content}], user_id=user_id)
return "Information saved to memory."
agent = Agent(
name="Personal Assistant",
instructions="""You are a helpful personal assistant with memory capabilities.
Use search_memory to recall past conversations.
Use save_memory to store important information.""",
tools=[search_memory, save_memory],
model="gpt-4.1-nano-2025-04-14"
)
result = Runner.run_sync(agent, "I love Italian food and I'm planning a trip to Rome next month")
print(result.final_output)
```
### Multi-Agent with Handoffs
```python
from agents import Agent, Runner, function_tool
travel_agent = Agent(
name="Travel Planner",
instructions="You are a travel planning specialist. Use search_memory and save_memory tools.",
tools=[search_memory, save_memory],
model="gpt-4.1-nano-2025-04-14"
)
health_agent = Agent(
name="Health Advisor",
instructions="You are a health and wellness advisor. Use search_memory and save_memory tools.",
tools=[search_memory, save_memory],
model="gpt-4.1-nano-2025-04-14"
)
triage_agent = Agent(
name="Personal Assistant",
instructions="""Route travel questions to Travel Planner, health questions to Health Advisor.""",
handoffs=[travel_agent, health_agent],
model="gpt-4.1-nano-2025-04-14"
)
result = Runner.run_sync(triage_agent, "Plan a healthy meal for my Italy trip")
```
---
## Pipecat (Voice / Real-Time)
Source: [docs.mem0.ai/integrations/pipecat](https://docs.mem0.ai/integrations/pipecat)
```python
from pipecat.services.mem0 import Mem0MemoryService
memory = Mem0MemoryService(
api_key=os.getenv("MEM0_API_KEY"),
user_id="alice",
agent_id="voice_bot",
params={
"search_limit": 10,
"search_threshold": 0.1,
"system_prompt": "Here are your past memories:",
"add_as_system_message": True,
}
)
# Use in pipeline
pipeline = Pipeline([
transport.input(),
stt,
user_context,
memory, # Memory enhances context automatically
llm,
transport.output(),
assistant_context
])
```
---
## LangGraph
Source: [docs.mem0.ai/integrations/langgraph](https://docs.mem0.ai/integrations/langgraph)
State-based agent workflows with memory persistence. Best for complex conversation flows with branching logic.
```python
from typing import Annotated, TypedDict, List
from langgraph.graph import StateGraph, START
from langgraph.graph.message import add_messages
from langchain_openai import ChatOpenAI
from mem0 import MemoryClient
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
llm = ChatOpenAI(model="gpt-4")
mem0 = MemoryClient()
class State(TypedDict):
messages: Annotated[List[HumanMessage | AIMessage], add_messages]
mem0_user_id: str
def chatbot(state: State):
messages = state["messages"]
user_id = state["mem0_user_id"]
# Retrieve relevant memories
memories = mem0.search(messages[-1].content, user_id=user_id)
context = "Relevant context:\n"
for memory in memories["results"]:
context += f"- {memory['memory']}\n"
system_message = SystemMessage(content=f"""You are a helpful support assistant.
{context}""")
response = llm.invoke([system_message] + messages)
# Store the interaction
mem0.add(
[{"role": "user", "content": messages[-1].content},
{"role": "assistant", "content": response.content}],
user_id=user_id
)
return {"messages": [response]}
graph = StateGraph(State)
graph.add_node("chatbot", chatbot)
graph.add_edge(START, "chatbot")
app = graph.compile()
# Usage
result = app.invoke({
"messages": [HumanMessage(content="I need help with my order")],
"mem0_user_id": "customer_123"
})
```
---
## LlamaIndex
Source: [docs.mem0.ai/integrations/llama-index](https://docs.mem0.ai/integrations/llama-index)
Install: `pip install llama-index-core llama-index-memory-mem0`
LlamaIndex has native Mem0 support via `Mem0Memory`. Works with ReAct and FunctionCalling agents.
```python
from llama_index.memory.mem0 import Mem0Memory
context = {"user_id": "alice", "agent_id": "llama_agent_1"}
memory = Mem0Memory.from_client(
context=context,
search_msg_limit=4, # messages from chat history used for retrieval (default: 5)
)
# Use with LlamaIndex agent
from llama_index.core.agent import FunctionCallingAgent
from llama_index.llms.openai import OpenAI
llm = OpenAI(model="gpt-4")
agent = FunctionCallingAgent.from_tools(
tools=[],
llm=llm,
memory=memory,
verbose=True,
)
response = agent.chat("I prefer vegetarian restaurants")
# Memory automatically stores and retrieves context
response = agent.chat("What kind of food do I like?")
# Agent retrieves the vegetarian preference from Mem0
```
---
## AutoGen
Source: [docs.mem0.ai/integrations/autogen](https://docs.mem0.ai/integrations/autogen)
Install: `pip install autogen mem0ai`
Multi-agent conversational systems with memory persistence.
```python
from autogen import ConversableAgent
from mem0 import MemoryClient
memory_client = MemoryClient()
USER_ID = "alice"
agent = ConversableAgent(
"chatbot",
llm_config={"config_list": [{"model": "gpt-4", "api_key": os.environ["OPENAI_API_KEY"]}]},
code_execution_config=False,
human_input_mode="NEVER",
)
def get_context_aware_response(question: str) -> str:
# Retrieve memories for context
relevant_memories = memory_client.search(question, user_id=USER_ID)
context = "\n".join([m["memory"] for m in relevant_memories.get("results", [])])
prompt = f"""Answer considering previous interactions:
Previous context: {context}
Question: {question}"""
reply = agent.generate_reply(messages=[{"content": prompt, "role": "user"}])
# Store the new interaction
memory_client.add(
[{"role": "user", "content": question}, {"role": "assistant", "content": reply}],
user_id=USER_ID
)
return reply
```
---
## All Supported Frameworks
Beyond the examples above, Mem0 integrates with:
| Framework | Type | Install |
|-----------|------|---------|
| [Mastra](https://docs.mem0.ai/integrations/mastra) | TS agent framework | `npm install @mastra/mem0` |
| [ElevenLabs](https://docs.mem0.ai/integrations/elevenlabs) | Voice AI | `pip install elevenlabs mem0ai` |
| [LiveKit](https://docs.mem0.ai/integrations/livekit) | Real-time voice/video | `pip install livekit-agents mem0ai` |
| [Camel AI](https://docs.mem0.ai/integrations/camel-ai) | Multi-agent framework | `pip install camel-ai[all] mem0ai` |
| [AWS Bedrock](https://docs.mem0.ai/integrations/aws-bedrock) | Cloud LLM provider | `pip install boto3 mem0ai` |
| [Dify](https://docs.mem0.ai/integrations/dify) | Low-code AI platform | Plugin-based |
| [Google AI ADK](https://docs.mem0.ai/integrations/google-ai-adk) | Google agent framework | `pip install google-adk mem0ai` |
For the general Python pattern (no framework), see the "Common integration pattern" in [SKILL.md](../SKILL.md).
@@ -1,119 +0,0 @@
# Mem0 Platform Quickstart
Get running with Mem0 in 2 minutes. No infrastructure to deploy -- just an API key.
## Prerequisites
- Python 3.10+ or Node.js 18+
- A Mem0 Platform API key ([Get one here](https://app.mem0.ai/dashboard/api-keys))
## Python Setup
```bash
pip install mem0ai
export MEM0_API_KEY="m0-your-api-key"
```
```python
from mem0 import MemoryClient
client = MemoryClient(api_key="your-api-key")
# Add a memory
messages = [
{"role": "user", "content": "I'm a vegetarian and allergic to nuts."},
{"role": "assistant", "content": "Got it! I'll remember your dietary preferences."}
]
client.add(messages, user_id="user123")
# Search memories
results = client.search("What are my dietary restrictions?", user_id="user123")
print(results)
```
### Async Client
```python
from mem0 import AsyncMemoryClient
client = AsyncMemoryClient(api_key="your-api-key")
await client.add(messages, user_id="user123")
results = await client.search("query", user_id="user123")
```
## TypeScript / JavaScript Setup
```bash
npm install mem0ai
export MEM0_API_KEY="m0-your-api-key"
```
```javascript
import MemoryClient from 'mem0ai';
const client = new MemoryClient({ apiKey: 'your-api-key' });
// Add a memory
const messages = [
{"role": "user", "content": "I'm a vegetarian and allergic to nuts."},
{"role": "assistant", "content": "Got it! I'll remember your dietary preferences."}
];
await client.add(messages, { user_id: "user123" });
// Search memories
const results = await client.search("What are my dietary restrictions?", {
user_id: "user123"
});
console.log(results);
```
## cURL
```bash
export MEM0_API_KEY="m0-your-api-key"
# Add memory
curl -X POST https://api.mem0.ai/v1/memories/ \
-H "Authorization: Token $MEM0_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"messages": [
{"role": "user", "content": "I am a vegetarian and allergic to nuts."},
{"role": "assistant", "content": "Got it! I will remember your dietary preferences."}
],
"user_id": "user123"
}'
# Search memories
curl -X POST https://api.mem0.ai/v2/memories/search/ \
-H "Authorization: Token $MEM0_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "What are my dietary restrictions?",
"filters": {"user_id": "user123"}
}'
```
## Sample Response
```json
{
"results": [
{
"id": "14e1b28a-2014-40ad-ac42-69c9ef42193d",
"memory": "Allergic to nuts",
"user_id": "user123",
"categories": ["health"],
"created_at": "2025-10-22T04:40:22.864647-07:00",
"score": 0.30
}
]
}
```
## Next Steps
- [SDK Guide](sdk-guide.md) -- all methods for Python and TypeScript
- [API Reference](api-reference.md) -- REST endpoints and memory object structure
- [Integration Patterns](integration-patterns.md) -- LangChain, CrewAI, Vercel AI, etc.
@@ -1,308 +0,0 @@
# Mem0 SDK Guide
Complete SDK reference for Python and TypeScript. All methods use `MemoryClient` (Platform API).
## Initialization
**Python:**
```python
from mem0 import MemoryClient
client = MemoryClient(api_key="m0-your-api-key")
```
**Python (Async):**
```python
from mem0 import AsyncMemoryClient
client = AsyncMemoryClient(api_key="m0-your-api-key")
```
**TypeScript:**
```typescript
import MemoryClient from 'mem0ai';
const client = new MemoryClient({ apiKey: 'm0-your-api-key' });
```
Constructor accepts `apiKey` (required) and `host` (optional, default: `https://api.mem0.ai`).
---
## add() -- Store Memories
**Python:**
```python
messages = [
{"role": "user", "content": "I'm a vegetarian and allergic to nuts."},
{"role": "assistant", "content": "Got it! I'll remember that."}
]
client.add(messages, user_id="alice")
# With metadata
client.add(messages, user_id="alice", metadata={"source": "onboarding"})
# With graph memory
client.add(messages, user_id="alice", enable_graph=True)
```
**TypeScript:**
```typescript
await client.add(messages, { user_id: "alice" });
await client.add(messages, { user_id: "alice", metadata: { source: "onboarding" } });
await client.add(messages, { user_id: "alice", enable_graph: true });
```
### Parameters
| Name | Type | Description |
|------|------|-------------|
| `messages` | array | `[{"role": "user", "content": "..."}]` |
| `user_id` | string | User identifier (recommended) |
| `agent_id` | string | Agent identifier |
| `run_id` | string | Session identifier |
| `metadata` | object | Custom key-value pairs |
| `enable_graph` | boolean | Activate knowledge graph |
| `infer` | boolean | If `false`, store raw text without inference (default: `true`) |
| `immutable` | boolean | Prevents modification after creation |
| `expiration_date` | string | Auto-expiry date (`YYYY-MM-DD`) |
| `includes` | string | Preference filters for inclusion |
| `excludes` | string | Preference filters for exclusion |
| `async_mode` | boolean | Async processing (default: `true`). Set `false` to wait |
### Advanced Add Options
```python
# Immutable -- cannot be modified or overwritten
client.add(messages, user_id="alice", immutable=True)
# Expiring memory
client.add(messages, user_id="alice", expiration_date="2025-12-31")
# Selective extraction
client.add(messages, user_id="alice", includes="dietary preferences", excludes="payment info")
# Agent + session scoping
client.add(messages, user_id="alice", agent_id="nutrition-agent", run_id="session-456")
# Synchronous processing (wait for completion)
client.add(messages, user_id="alice", async_mode=False)
# Raw text -- skip LLM inference
client.add(
[{"role": "user", "content": "User prefers dark mode."}],
user_id="alice",
infer=False,
)
```
---
## search() -- Find Memories
**Python:**
```python
results = client.search("dietary preferences?", user_id="alice")
# With filters and reranking
results = client.search(
query="work experience",
filters={"AND": [{"user_id": "alice"}, {"categories": {"contains": "professional_details"}}]},
top_k=5,
rerank=True,
threshold=0.5
)
# With graph relations
results = client.search("colleagues", user_id="alice", enable_graph=True)
# Keyword search
results = client.search("vegetarian", user_id="alice", keyword_search=True)
```
**TypeScript:**
```typescript
const results = await client.search("dietary preferences", { user_id: "alice" });
const results = await client.search("work experience", {
filters: { AND: [{ user_id: "alice" }, { categories: { contains: "professional_details" } }] },
top_k: 5,
rerank: true,
});
```
### Parameters
| Name | Type | Description |
|------|------|-------------|
| `query` | string | Natural language search query |
| `user_id` | string | Filter by user |
| `filters` | object | V2 filter object (AND/OR operators) |
| `top_k` | number | Number of results (default: 10) |
| `rerank` | boolean | Enable reranking for better relevance |
| `threshold` | number | Minimum similarity score (default: 0.3) |
| `keyword_search` | boolean | Use keyword-based search |
| `enable_graph` | boolean | Include graph relations |
### Common Filter Patterns
```python
# Single user (shorthand)
client.search("query", user_id="alice")
# OR across agents
filters={"OR": [{"user_id": "alice"}, {"agent_id": {"in": ["travel-agent", "sports-agent"]}}]}
# Category filtering (partial match)
filters={"AND": [{"user_id": "alice"}, {"categories": {"contains": "finance"}}]}
# Category filtering (exact match)
filters={"AND": [{"user_id": "alice"}, {"categories": {"in": ["personal_information"]}}]}
# Wildcard (match any non-null run)
filters={"AND": [{"user_id": "alice"}, {"run_id": "*"}]}
# Date range
filters={"AND": [
{"user_id": "alice"},
{"created_at": {"gte": "2024-01-01T00:00:00Z"}},
{"created_at": {"lt": "2024-02-01T00:00:00Z"}}
]}
# Exclude categories with NOT
filters={"AND": [{"user_id": "user_123"}, {"NOT": {"categories": {"in": ["spam", "test"]}}}]}
# Multi-dimensional query
filters={"AND": [
{"user_id": "user_123"},
{"keywords": {"icontains": "invoice"}},
{"categories": {"in": ["finance"]}},
{"created_at": {"gte": "2024-01-01T00:00:00Z"}}
]}
```
---
## get() / getAll() -- Retrieve Memories
**Python:**
```python
# Single memory by ID
memory = client.get(memory_id="ea925981-...")
# All memories for a user
memories = client.get_all(filters={"AND": [{"user_id": "alice"}]})
# With date range
memories = client.get_all(
filters={"AND": [
{"user_id": "alex"},
{"created_at": {"gte": "2024-07-01", "lte": "2024-07-31"}}
]}
)
# With graph data
memories = client.get_all(filters={"AND": [{"user_id": "alice"}]}, enable_graph=True)
```
**TypeScript:**
```typescript
const memory = await client.get("ea925981-...");
const memories = await client.getAll({ filters: { AND: [{ user_id: "alice" }] } });
```
**Note:** `get_all` requires at least one of `user_id`, `agent_id`, `app_id`, or `run_id` in filters.
---
## update() -- Modify Memories
**Python:**
```python
client.update(memory_id="ea925981-...", text="Updated: vegan since 2024")
client.update(memory_id="ea925981-...", text="Updated", metadata={"verified": True})
```
**TypeScript:**
```typescript
await client.update("ea925981-...", { text: "Updated: vegan since 2024" });
```
Cannot update immutable memories.
---
## delete() / deleteAll() -- Remove Memories
**Python:**
```python
client.delete(memory_id="ea925981-...")
client.delete_all(user_id="alice") # Irreversible bulk delete
```
**TypeScript:**
```typescript
await client.delete("ea925981-...");
await client.deleteAll({ user_id: "alice" });
```
---
## history() -- Track Changes
**Python:**
```python
history = client.history(memory_id="ea925981-...")
# Returns: [{previous_value, new_value, action, timestamps}]
```
**TypeScript:**
```typescript
const history = await client.history("ea925981-...");
```
---
## Batch Operations (TypeScript)
```typescript
// Batch update
await client.batchUpdate([
{ memoryId: "uuid-1", text: "Updated text" },
{ memoryId: "uuid-2", text: "Another updated text" },
]);
// Batch delete
await client.batchDelete(["uuid-1", "uuid-2", "uuid-3"]);
```
---
## Additional Methods
```python
# List all users/agents/sessions with memories
users = client.users()
# Delete a user/agent entity
client.delete_users(user_id="alice")
# Submit feedback on a memory
client.feedback(memory_id="...", feedback="POSITIVE", feedback_reason="Accurate extraction")
# Export memories
export = client.create_memory_export(filters={"AND": [{"user_id": "alice"}]})
data = client.get_memory_export(memory_export_id=export["id"])
```
---
## Common Pitfalls
1. **Entity cross-filtering fails silently** -- `AND` with `user_id` + `agent_id` returns empty. Use `OR`.
2. **SQL operators rejected** -- use `gte`, `lt`, etc. Not `>=`, `<`.
3. **Metadata filtering is limited** -- only top-level keys with `eq`, `contains`, `ne`.
4. **Wildcard `*` excludes null** -- only matches non-null values.
5. **Default threshold is 0.3** -- increase for stricter matching.
6. **Async processing** -- memories process asynchronously. Wait 2-3s after `add()` before searching.
7. **Immutable memories** -- cannot be updated or deleted once created.
## Naming Conventions
Python uses `snake_case` (`user_id`, `memory_id`, `get_all`). TypeScript uses `camelCase` for methods (`getAll`, `deleteAll`, `batchUpdate`) but `snake_case` for API parameters (`user_id`, `agent_id`).
@@ -1,720 +0,0 @@
# Mem0 Use Cases & Examples
Real-world implementation patterns for Mem0 Platform. Each use case includes complete, runnable code in both Python and TypeScript.
## Table of Contents
- [Personalized AI Companion](#1-personalized-ai-companion)
- [Customer Support with Categories](#2-customer-support-with-categories)
- [Healthcare Coach](#3-healthcare-coach)
- [Content Creation Workflow](#4-content-creation-workflow)
- [Multi-Agent / Multi-Tenant](#5-multi-agent--multi-tenant)
- [Personalized Search](#6-personalized-search)
- [Email Intelligence](#7-email-intelligence)
- [Common Patterns Across Use Cases](#common-patterns-across-use-cases)
---
## 1. Personalized AI Companion
A fitness coach that remembers goals, preferences, and progress across sessions. Mem0 persists context across app restarts — no session state needed.
### Implementation (Python)
```python
from mem0 import MemoryClient
from openai import OpenAI
mem0 = MemoryClient()
openai_client = OpenAI()
def chat(user_input: str, user_id: str) -> str:
# 1. Retrieve relevant memories
memories = mem0.search(user_input, user_id=user_id)
context = "\n".join([f"- {m['memory']}" for m in memories.get("results", [])])
# 2. Generate response with memory context
system_prompt = f"""You are Ray, a personal fitness coach.
Use these known facts about the user to personalize your response:
{context if context else 'No prior context yet.'}"""
response = openai_client.chat.completions.create(
model="gpt-4.1-nano-2025-04-14",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_input},
]
)
reply = response.choices[0].message.content
# 3. Store interaction for future context
mem0.add(
[{"role": "user", "content": user_input}, {"role": "assistant", "content": reply}],
user_id=user_id
)
return reply
# Usage
chat("I want to run a marathon in under 4 hours", user_id="max")
# Next day, app restarted:
chat("What should I focus on today?", user_id="max")
# Ray remembers the sub-4 marathon goal
```
### Implementation (TypeScript)
```typescript
import MemoryClient from 'mem0ai';
import OpenAI from 'openai';
const mem0 = new MemoryClient({ apiKey: process.env.MEM0_API_KEY! });
const openai = new OpenAI();
async function chat(userInput: string, userId: string): Promise<string> {
// 1. Retrieve relevant memories
const memories = await mem0.search(userInput, { user_id: userId });
const context = memories.results
?.map((m: any) => `- ${m.memory}`)
.join('\n') || 'No prior context yet.';
// 2. Generate response with memory context
const response = await openai.chat.completions.create({
model: 'gpt-4.1-nano-2025-04-14',
messages: [
{ role: 'system', content: `You are Ray, a personal fitness coach.\nUser context:\n${context}` },
{ role: 'user', content: userInput },
],
});
const reply = response.choices[0].message.content!;
// 3. Store interaction
await mem0.add(
[{ role: 'user', content: userInput }, { role: 'assistant', content: reply }],
{ user_id: userId }
);
return reply;
}
```
### Key Benefits
- Context persists across app restarts — no session management needed
- Memories are automatically deduplicated and updated
- Works with any LLM provider (OpenAI, Anthropic, etc.)
**Best for:** Fitness coaches, tutors, therapists — any assistant that needs to remember goals across sessions.
---
## 2. Customer Support with Categories
Auto-categorize support data so teams retrieve the right facts fast. Uses custom categories for structured retrieval.
### Implementation (Python)
```python
from mem0 import MemoryClient
client = MemoryClient()
# 1. Define categories at the project level (one-time setup)
custom_categories = [
{"support_tickets": "Customer issues and resolutions"},
{"account_info": "Account details and preferences"},
{"billing": "Payment history and billing questions"},
{"product_feedback": "Feature requests and feedback"},
]
client.project.update(custom_categories=custom_categories)
# 2. Store interactions — auto-classified into categories
def log_support_interaction(user_id: str, message: str, priority: str = "normal"):
client.add(
[{"role": "user", "content": message}],
user_id=user_id,
metadata={"priority": priority, "source": "support_chat"}
)
# 3. Retrieve by category
def get_billing_issues(user_id: str):
return client.get_all(
filters={
"AND": [
{"user_id": user_id},
{"categories": {"in": ["billing"]}}
]
}
)
def search_support_history(user_id: str, query: str):
return client.search(
query,
filters={
"AND": [
{"user_id": user_id},
{"categories": {"contains": "support_tickets"}}
]
},
top_k=5
)
# Usage
log_support_interaction("maria", "I was charged twice for last month's subscription", priority="high")
log_support_interaction("maria", "The dashboard is loading slowly on mobile")
billing = get_billing_issues("maria") # Returns only billing-related memories
```
### Implementation (TypeScript)
```typescript
import MemoryClient from 'mem0ai';
const client = new MemoryClient({ apiKey: process.env.MEM0_API_KEY! });
// Setup categories (one-time)
await client.updateProject({
custom_categories: [
{ support_tickets: 'Customer issues and resolutions' },
{ billing: 'Payment history and billing questions' },
{ product_feedback: 'Feature requests and feedback' },
],
});
async function logInteraction(userId: string, message: string, priority = 'normal') {
await client.add(
[{ role: 'user', content: message }],
{ user_id: userId, metadata: { priority, source: 'support_chat' } }
);
}
async function getBillingIssues(userId: string) {
return client.getAll({
filters: { AND: [{ user_id: userId }, { categories: { in: ['billing'] } }] },
});
}
```
### Key Benefits
- Automatic categorization — no manual tagging
- Filter by category for structured retrieval
- Metadata (`priority`, `source`) enables multi-dimensional queries
**Best for:** Help desks, SaaS support, e-commerce — structured retrieval by category eliminates manual scanning.
---
## 3. Healthcare Coach
Guide patients with an assistant that remembers medical history. Uses high `threshold` for confident retrieval in safety-critical contexts.
### Implementation (Python)
```python
from mem0 import MemoryClient
from openai import OpenAI
mem0 = MemoryClient()
openai_client = OpenAI()
def save_patient_info(user_id: str, information: str):
mem0.add(
[{"role": "user", "content": information}],
user_id=user_id,
run_id="healthcare_session",
metadata={"type": "patient_information"}
)
def consult(user_id: str, question: str) -> str:
# High threshold for medical accuracy
memories = mem0.search(question, user_id=user_id, top_k=5, threshold=0.7)
context = "\n".join([f"- {m['memory']}" for m in memories.get("results", [])])
response = openai_client.chat.completions.create(
model="gpt-4.1-nano-2025-04-14",
messages=[
{"role": "system", "content": f"You are a health coach. Patient context:\n{context}"},
{"role": "user", "content": question},
]
)
reply = response.choices[0].message.content
# Store the interaction
mem0.add(
[{"role": "user", "content": question}, {"role": "assistant", "content": reply}],
user_id=user_id,
run_id="healthcare_session",
)
return reply
# Usage
save_patient_info("alex", "I'm allergic to penicillin and take metformin for type 2 diabetes")
consult("alex", "Can I take amoxicillin for my sore throat?")
# Remembers penicillin allergy — amoxicillin is a penicillin-type antibiotic
```
### Implementation (TypeScript)
```typescript
import MemoryClient from 'mem0ai';
import OpenAI from 'openai';
const mem0 = new MemoryClient({ apiKey: process.env.MEM0_API_KEY! });
const openai = new OpenAI();
async function savePatientInfo(userId: string, info: string) {
await mem0.add(
[{ role: 'user', content: info }],
{ user_id: userId, run_id: 'healthcare_session', metadata: { type: 'patient_information' } }
);
}
async function consult(userId: string, question: string): Promise<string> {
const memories = await mem0.search(question, {
user_id: userId,
top_k: 5,
threshold: 0.7,
});
const context = memories.results?.map((m: any) => `- ${m.memory}`).join('\n') || '';
const response = await openai.chat.completions.create({
model: 'gpt-4.1-nano-2025-04-14',
messages: [
{ role: 'system', content: `You are a health coach. Patient context:\n${context}` },
{ role: 'user', content: question },
],
});
const reply = response.choices[0].message.content!;
await mem0.add(
[{ role: 'user', content: question }, { role: 'assistant', content: reply }],
{ user_id: userId, run_id: 'healthcare_session' }
);
return reply;
}
```
### Key Benefits
- High threshold (0.7) ensures only confident matches for safety-critical retrieval
- Session scoping via `run_id` groups related health interactions
- Metadata tagging separates patient info from conversation history
**Best for:** Telehealth, wellness apps, patient management — persistent health context across visits.
---
## 4. Content Creation Workflow
Store voice guidelines once and apply them across every draft. Uses `run_id` and `metadata` to scope writing preferences per session.
### Implementation (Python)
```python
from mem0 import MemoryClient
from openai import OpenAI
mem0 = MemoryClient()
openai_client = OpenAI()
def store_writing_preferences(user_id: str, preferences: str):
mem0.add(
[{"role": "user", "content": preferences}],
user_id=user_id,
run_id="editing_session",
metadata={"type": "preferences", "category": "writing_style"}
)
def draft_content(user_id: str, topic: str) -> str:
# Retrieve writing preferences
prefs = mem0.search(
"writing style preferences",
filters={"AND": [{"user_id": user_id}, {"run_id": "editing_session"}]}
)
style_context = "\n".join([f"- {m['memory']}" for m in prefs.get("results", [])])
response = openai_client.chat.completions.create(
model="gpt-4.1-nano-2025-04-14",
messages=[
{"role": "system", "content": f"Write content matching these style preferences:\n{style_context}"},
{"role": "user", "content": f"Write a blog post about: {topic}"},
]
)
return response.choices[0].message.content
# Usage
store_writing_preferences("writer_01", "I prefer short sentences. Active voice. No jargon. Use analogies.")
draft_content("writer_01", "Why AI memory matters for chatbots")
# Drafts content matching the stored voice guidelines
```
### Implementation (TypeScript)
```typescript
import MemoryClient from 'mem0ai';
import OpenAI from 'openai';
const mem0 = new MemoryClient({ apiKey: process.env.MEM0_API_KEY! });
const openai = new OpenAI();
async function storePreferences(userId: string, preferences: string) {
await mem0.add(
[{ role: 'user', content: preferences }],
{ user_id: userId, run_id: 'editing_session', metadata: { type: 'preferences' } }
);
}
async function draftContent(userId: string, topic: string): Promise<string> {
const prefs = await mem0.search('writing style preferences', {
filters: { AND: [{ user_id: userId }, { run_id: 'editing_session' }] },
});
const styleContext = prefs.results?.map((m: any) => `- ${m.memory}`).join('\n') || '';
const response = await openai.chat.completions.create({
model: 'gpt-4.1-nano-2025-04-14',
messages: [
{ role: 'system', content: `Write content matching these preferences:\n${styleContext}` },
{ role: 'user', content: `Write a blog post about: ${topic}` },
],
});
return response.choices[0].message.content!;
}
```
### Key Benefits
- Voice consistency across all content without repeating guidelines
- Scoped sessions let you maintain different style profiles
- Preferences update automatically as you refine them
**Best for:** Marketing teams, technical writers, agencies — consistent voice across all content.
---
## 5. Multi-Agent / Multi-Tenant
Keep memories separate using `user_id`, `agent_id`, `app_id`, and `run_id` scoping. Critical for multi-agent workflows and multi-tenant apps.
### Implementation (Python)
```python
from mem0 import MemoryClient
client = MemoryClient()
# Store memories scoped to user + agent + session
def store_scoped_memory(messages: list, user_id: str, agent_id: str, run_id: str, app_id: str):
client.add(
messages,
user_id=user_id,
agent_id=agent_id,
run_id=run_id,
app_id=app_id
)
# Query within a specific scope
def search_user_session(query: str, user_id: str, app_id: str, run_id: str):
"""Search memories for a specific user within a specific session."""
return client.search(
query,
filters={
"AND": [
{"user_id": user_id},
{"app_id": app_id},
{"run_id": run_id}
]
}
)
def search_agent_knowledge(query: str, agent_id: str, app_id: str):
"""Search all memories an agent has across all users."""
return client.search(
query,
filters={
"AND": [
{"agent_id": agent_id},
{"app_id": app_id}
]
}
)
# Usage: Travel concierge app with multiple agents
store_scoped_memory(
[{"role": "user", "content": "I'm vegetarian and prefer window seats"}],
user_id="traveler_cam",
agent_id="travel_planner",
run_id="tokyo-2025",
app_id="concierge_app"
)
# User-scoped query: "What does Cam prefer?"
user_mems = search_user_session("dietary restrictions?", "traveler_cam", "concierge_app", "tokyo-2025")
# Agent-scoped query: "What do all travelers prefer?" (across users)
agent_mems = search_agent_knowledge("common dietary restrictions?", "travel_planner", "concierge_app")
```
### Implementation (TypeScript)
```typescript
import MemoryClient from 'mem0ai';
const client = new MemoryClient({ apiKey: process.env.MEM0_API_KEY! });
async function storeScopedMemory(
messages: Array<{ role: string; content: string }>,
userId: string, agentId: string, runId: string, appId: string
) {
await client.add(messages, {
user_id: userId,
agent_id: agentId,
run_id: runId,
app_id: appId,
});
}
async function searchUserSession(query: string, userId: string, appId: string, runId: string) {
return client.search(query, {
filters: { AND: [{ user_id: userId }, { app_id: appId }, { run_id: runId }] },
});
}
async function searchAgentKnowledge(query: string, agentId: string, appId: string) {
return client.search(query, {
filters: { AND: [{ agent_id: agentId }, { app_id: appId }] },
});
}
```
### Key Benefits
- Full isolation between users, agents, sessions, and apps
- Query at any scope level — user, agent, session, or app-wide
- No memory leakage between tenants
**Best for:** Multi-agent workflows, multi-tenant SaaS — proper isolation at every level.
---
## 6. Personalized Search
Blend real-time search results with personal context. Uses `custom_instructions` to infer preferences from queries.
### Implementation (Python)
```python
from mem0 import MemoryClient
from openai import OpenAI
mem0 = MemoryClient()
openai_client = OpenAI()
# One-time setup: configure Mem0 to infer from queries
mem0.project.update(
custom_instructions="""Infer user preferences and facts from their search queries.
Extract dietary preferences, location, interests, and purchase history."""
)
def personalized_search(user_id: str, query: str, search_results: list) -> str:
# Get user context from memory
memories = mem0.search(query, user_id=user_id, top_k=5)
user_context = "\n".join([f"- {m['memory']}" for m in memories.get("results", [])])
response = openai_client.chat.completions.create(
model="gpt-4.1-nano-2025-04-14",
messages=[
{"role": "system", "content": f"Personalize search results using user context:\n{user_context}"},
{"role": "user", "content": f"Query: {query}\n\nSearch results:\n{search_results}"},
]
)
reply = response.choices[0].message.content
# Store the query to learn preferences over time
mem0.add(
[{"role": "user", "content": query}],
user_id=user_id
)
return reply
# Usage
personalized_search("user_42", "best restaurants nearby", ["Restaurant A", "Restaurant B"])
# Over time, Mem0 learns: "user prefers vegetarian, lives in Austin"
# Future searches are automatically personalized
```
### Implementation (TypeScript)
```typescript
import MemoryClient from 'mem0ai';
import OpenAI from 'openai';
const mem0 = new MemoryClient({ apiKey: process.env.MEM0_API_KEY! });
const openai = new OpenAI();
async function personalizedSearch(userId: string, query: string, searchResults: string[]): Promise<string> {
const memories = await mem0.search(query, { user_id: userId, top_k: 5 });
const context = memories.results?.map((m: any) => `- ${m.memory}`).join('\n') || '';
const response = await openai.chat.completions.create({
model: 'gpt-4.1-nano-2025-04-14',
messages: [
{ role: 'system', content: `Personalize results using user context:\n${context}` },
{ role: 'user', content: `Query: ${query}\nResults: ${searchResults.join(', ')}` },
],
});
const reply = response.choices[0].message.content!;
await mem0.add([{ role: 'user', content: query }], { user_id: userId });
return reply;
}
```
### Key Benefits
- Learns preferences from queries automatically via `custom_instructions`
- Personalizes any search provider (Tavily, Google, Bing)
- Zero manual preference setup — improves over time
**Best for:** Personalized search engines, recommendation systems — search results tailored to individual users.
---
## 7. Email Intelligence
Capture, categorize, and recall inbox threads using persistent memories with rich metadata.
### Implementation (Python)
```python
from mem0 import MemoryClient
client = MemoryClient()
def store_email(user_id: str, sender: str, subject: str, body: str, date: str):
client.add(
[{"role": "user", "content": f"Email from {sender}: {subject}\n\n{body}"}],
user_id=user_id,
metadata={"email_type": "incoming", "sender": sender, "subject": subject, "date": date}
)
def search_emails(user_id: str, query: str):
return client.search(
query,
filters={"AND": [{"user_id": user_id}, {"categories": {"contains": "email"}}]},
top_k=10
)
def get_emails_from_sender(user_id: str, sender: str):
return client.get_all(
filters={
"AND": [
{"user_id": user_id},
{"metadata": {"contains": sender}}
]
}
)
# Usage
store_email("alice", "bob@acme.com", "Q3 Budget Review", "Attached is the Q3 budget...", "2025-01-15")
store_email("alice", "carol@acme.com", "Sprint Planning", "Here are the priorities...", "2025-01-16")
results = search_emails("alice", "budget discussions")
sender_emails = get_emails_from_sender("alice", "bob@acme.com")
```
### Implementation (TypeScript)
```typescript
import MemoryClient from 'mem0ai';
const client = new MemoryClient({ apiKey: process.env.MEM0_API_KEY! });
async function storeEmail(userId: string, sender: string, subject: string, body: string, date: string) {
await client.add(
[{ role: 'user', content: `Email from ${sender}: ${subject}\n\n${body}` }],
{ user_id: userId, metadata: { email_type: 'incoming', sender, subject, date } }
);
}
async function searchEmails(userId: string, query: string) {
return client.search(query, {
filters: { AND: [{ user_id: userId }, { categories: { contains: 'email' } }] },
top_k: 10,
});
}
```
### Key Benefits
- Rich metadata enables multi-dimensional queries (sender, date, subject)
- Category filtering separates emails from other memory types
- Semantic search across all email content
**Best for:** Inbox management, email automation — searchable email memories with metadata filtering.
---
## Common Patterns Across Use Cases
### Pattern 1: Retrieve → Generate → Store
Every use case follows the same 3-step loop:
```python
# 1. Retrieve relevant context
memories = mem0.search(user_input, user_id=user_id)
context = "\n".join([m["memory"] for m in memories.get("results", [])])
# 2. Generate with context
response = llm.generate(system_prompt=f"Context:\n{context}", user_input=user_input)
# 3. Store the interaction
mem0.add(
[{"role": "user", "content": user_input}, {"role": "assistant", "content": response}],
user_id=user_id
)
```
### Pattern 2: Scope with Entity Identifiers
Use `user_id`, `agent_id`, `app_id`, and `run_id` to isolate memories:
```python
# User-level: personal preferences
client.add(messages, user_id="alice")
# Session-level: conversation within one session
client.add(messages, user_id="alice", run_id="session_123")
# Agent-level: agent-specific knowledge
client.add(messages, agent_id="support_bot", app_id="helpdesk")
```
### Pattern 3: Rich Metadata for Filtering
Attach structured metadata for multi-dimensional queries:
```python
# Store with metadata
client.add(messages, user_id="alice", metadata={"priority": "high", "source": "phone_call"})
# Filter by category + metadata
client.search("billing issues", filters={
"AND": [{"user_id": "alice"}, {"categories": {"contains": "billing"}}]
})
```
### Pattern 4: Custom Instructions for Domain-Specific Extraction
Control what Mem0 extracts from conversations:
```python
client.project.update(
custom_instructions="Extract medical conditions, medications, and allergies. Exclude billing info."
)
```
---
## More Examples
For 30+ cookbooks with complete working code: [docs.mem0.ai/cookbooks](https://docs.mem0.ai/cookbooks)
@@ -1,224 +0,0 @@
#!/usr/bin/env python3
"""
Mem0 Documentation Search Agent (Mintlify-based)
On-demand search tool for querying Mem0 documentation without storing content locally.
This tool leverages Mintlify's documentation structure to perform just-in-time
retrieval of technical information from docs.mem0.ai.
Usage:
python mem0_doc_search.py --query "how to add graph memory"
python mem0_doc_search.py --query "filter syntax for categories"
python mem0_doc_search.py --page "/platform/features/graph-memory"
python mem0_doc_search.py --index
python mem0_doc_search.py --query "webhook events" --section platform
Purpose:
- Avoid bloating local context with full documentation
- Enable just-in-time retrieval of technical details
- Query specific documentation pages on demand
- Search across the full Mem0 documentation site
"""
import argparse
import json
import sys
import urllib.error
import urllib.parse
import urllib.request
DOCS_BASE = "https://docs.mem0.ai"
SEARCH_ENDPOINT = f"{DOCS_BASE}/api/search"
LLMS_INDEX = f"{DOCS_BASE}/llms.txt"
# Known documentation sections for targeted retrieval
SECTION_MAP = {
"platform": [
"/platform/overview",
"/platform/quickstart",
"/platform/features",
"/platform/features/graph-memory",
"/platform/features/selective-memory",
"/platform/features/custom-categories",
"/platform/features/v2-memory-filters",
"/platform/features/async-client",
"/platform/features/webhooks",
"/platform/features/multimodal-support",
],
"api": [
"/api-reference/memory/add-memories",
"/api-reference/memory/v2-search-memories",
"/api-reference/memory/v2-get-memories",
"/api-reference/memory/get-memory",
"/api-reference/memory/update-memory",
"/api-reference/memory/delete-memory",
],
"open-source": [
"/open-source/overview",
"/open-source/python-quickstart",
"/open-source/node-quickstart",
"/open-source/features",
"/open-source/features/graph-memory",
"/open-source/features/rest-api",
"/open-source/configure-components",
],
"openmemory": [
"/openmemory/overview",
"/openmemory/quickstart",
],
"sdks": [
"/sdks/python",
"/sdks/js",
],
"integrations": [
"/integrations",
],
}
def fetch_url(url: str) -> str:
"""Fetch content from a URL."""
req = urllib.request.Request(url, headers={"User-Agent": "Mem0DocSearchAgent/1.0"})
try:
with urllib.request.urlopen(req, timeout=15) as resp:
return resp.read().decode("utf-8")
except urllib.error.HTTPError as e:
return f"HTTP Error {e.code}: {e.reason}"
except urllib.error.URLError as e:
return f"URL Error: {e.reason}"
def search_docs(query: str, section: str | None = None) -> dict:
"""
Search Mem0 documentation using Mintlify's search API.
Falls back to the llms.txt index for keyword matching if the API is unavailable.
"""
# Try Mintlify search API first
params = urllib.parse.urlencode({"query": query})
search_url = f"{SEARCH_ENDPOINT}?{params}"
try:
result = fetch_url(search_url)
data = json.loads(result)
if isinstance(data, dict) and data.get("results"):
results = data["results"]
if section and section in SECTION_MAP:
section_paths = SECTION_MAP[section]
results = [r for r in results if any(r.get("url", "").startswith(p) for p in section_paths)]
return {"source": "mintlify_search", "results": results}
except (json.JSONDecodeError, Exception):
pass
# Fallback: search llms.txt index for matching URLs
index_content = fetch_url(LLMS_INDEX)
query_lower = query.lower()
matching_urls = []
for line in index_content.splitlines():
line = line.strip()
if not line or line.startswith("#"):
continue
if query_lower in line.lower():
matching_urls.append(line)
if section and section in SECTION_MAP:
section_paths = SECTION_MAP[section]
matching_urls = [u for u in matching_urls if any(p in u for p in section_paths)]
return {
"source": "llms_txt_index",
"query": query,
"matching_urls": matching_urls[:20],
"suggestion": "Fetch specific URLs for detailed content",
}
def fetch_page(page_path: str) -> dict:
"""Fetch a specific documentation page."""
url = f"{DOCS_BASE}{page_path}" if page_path.startswith("/") else page_path
content = fetch_url(url)
return {"url": url, "content": content[:10000], "truncated": len(content) > 10000}
def get_index() -> dict:
"""Fetch the full documentation index from llms.txt."""
content = fetch_url(LLMS_INDEX)
urls = [line.strip() for line in content.splitlines() if line.strip() and not line.startswith("#")]
return {"total_pages": len(urls), "urls": urls, "sections": list(SECTION_MAP.keys())}
def list_section(section: str) -> dict:
"""List all known pages in a documentation section."""
if section not in SECTION_MAP:
return {"error": f"Unknown section: {section}", "available": list(SECTION_MAP.keys())}
return {
"section": section,
"pages": [f"{DOCS_BASE}{p}" for p in SECTION_MAP[section]],
}
def main():
parser = argparse.ArgumentParser(description="Search Mem0 documentation on demand")
parser.add_argument("--query", help="Search query for documentation")
parser.add_argument("--page", help="Fetch a specific page path (e.g., /platform/features/graph-memory)")
parser.add_argument("--index", action="store_true", help="Show full documentation index")
parser.add_argument("--section", help="Filter by section or list section pages")
parser.add_argument("--json", action="store_true", help="Output as JSON")
args = parser.parse_args()
if args.index:
result = get_index()
elif args.section and not args.query:
result = list_section(args.section)
elif args.page:
result = fetch_page(args.page)
elif args.query:
result = search_docs(args.query, section=args.section)
else:
parser.print_help()
sys.exit(1)
if args.json:
print(json.dumps(result, indent=2))
else:
if isinstance(result, dict):
if "results" in result:
print(f"Source: {result.get('source', 'unknown')}")
for r in result["results"]:
print(f" - {r.get('title', 'N/A')}: {r.get('url', 'N/A')}")
if r.get("description"):
print(f" {r['description'][:200]}")
elif "matching_urls" in result:
print(f"Source: {result['source']}")
print(f"Query: {result['query']}")
for url in result["matching_urls"]:
print(f" - {url}")
if result.get("suggestion"):
print(f"\n{result['suggestion']}")
elif "urls" in result:
print(f"Total documentation pages: {result['total_pages']}")
print(f"Sections: {', '.join(result['sections'])}")
for url in result["urls"][:30]:
print(f" - {url}")
if result["total_pages"] > 30:
print(f" ... and {result['total_pages'] - 30} more")
elif "pages" in result:
print(f"Section: {result['section']}")
for page in result["pages"]:
print(f" - {page}")
elif "content" in result:
print(f"URL: {result['url']}")
if result.get("truncated"):
print("[Content truncated to 10000 chars]")
print(result["content"])
elif "error" in result:
print(f"Error: {result['error']}")
if result.get("available"):
print(f"Available sections: {', '.join(result['available'])}")
else:
print(json.dumps(result, indent=2))
if __name__ == "__main__":
main()
-1
View File
@@ -2,7 +2,6 @@
module.exports = {
...require("./jest.config"),
testMatch: ["**/integration/**/*.test.ts"],
globalSetup: "<rootDir>/src/client/tests/integration/global-setup.ts",
globalTeardown: "<rootDir>/src/client/tests/integration/global-teardown.ts",
// Run integration tests serially to avoid rate limiting and race conditions
maxWorkers: 1,
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "mem0ai",
"version": "2.4.3",
"version": "2.4.2",
"description": "The Memory Layer For Your AI Apps",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
-1
View File
@@ -31,7 +31,6 @@ export const MemoryUpdateSchema = z.object({
old_memory: z
.string()
.optional()
.nullable()
.describe(
"The previous content of the memory item if the event was UPDATE.",
),
@@ -1,6 +1,4 @@
import type { Client as ClientType } from "pg";
import pkg from "pg";
const { Client } = pkg;
import { Client, Pool } from "pg";
import { VectorStore } from "./base";
import { SearchFilters, VectorStoreConfig, VectorStoreResult } from "../types";
@@ -16,7 +14,7 @@ interface PGVectorConfig extends VectorStoreConfig {
}
export class PGVector implements VectorStore {
private client: ClientType;
private client: Client;
private collectionName: string;
private useDiskann: boolean;
private useHnsw: boolean;
@@ -37,7 +35,6 @@ export class PGVector implements VectorStore {
host: config.host,
port: config.port,
});
this.initialize().catch(console.error);
}
async initialize(): Promise<void> {
@@ -118,11 +118,6 @@ jest.mock("../src/vector_stores/azure_ai_search", () => ({
.fn()
.mockImplementation((config) => ({ type: "azure-ai-search", config })),
}));
jest.mock("../src/vector_stores/pgvector", () => ({
PGVector: jest
.fn()
.mockImplementation((config) => ({ type: "pgvector", config })),
}));
jest.mock("../src/storage/SupabaseHistoryManager", () => ({
SupabaseHistoryManager: jest
.fn()
@@ -241,7 +236,6 @@ describe("VectorStoreFactory", () => {
["langchain"],
["vectorize"],
["azure-ai-search"],
["pgvector"],
])("creates vector store for provider '%s'", (provider) => {
expect(() =>
VectorStoreFactory.create(provider, dummyVSConfig),
+2 -2
View File
@@ -16,7 +16,7 @@ class AnthropicConfig(BaseLlmConfig):
temperature: float = 0.1,
api_key: Optional[str] = None,
max_tokens: int = 2000,
top_p: Optional[float] = None,
top_p: float = 0.1,
top_k: int = 1,
enable_vision: bool = False,
vision_details: Optional[str] = "auto",
@@ -32,7 +32,7 @@ class AnthropicConfig(BaseLlmConfig):
temperature: Controls randomness, defaults to 0.1
api_key: Anthropic API key, defaults to None
max_tokens: Maximum tokens to generate, defaults to 2000
top_p: Nucleus sampling parameter, defaults to None (omitted to avoid conflict with temperature)
top_p: Nucleus sampling parameter, defaults to 0.1
top_k: Top-k sampling parameter, defaults to 1
enable_vision: Enable vision capabilities, defaults to False
vision_details: Vision detail level, defaults to "auto"
+4 -8
View File
@@ -16,7 +16,7 @@ class AWSBedrockConfig(BaseLlmConfig):
model: Optional[str] = None,
temperature: float = 0.1,
max_tokens: int = 2000,
top_p: Optional[float] = None,
top_p: float = 0.9,
top_k: int = 1,
aws_access_key_id: Optional[str] = None,
aws_secret_access_key: Optional[str] = None,
@@ -33,8 +33,7 @@ class AWSBedrockConfig(BaseLlmConfig):
model: Bedrock model identifier (e.g., "amazon.nova-3-mini-20241119-v1:0")
temperature: Controls randomness (0.0 to 2.0)
max_tokens: Maximum tokens to generate
top_p: Nucleus sampling (0.0–1.0). Default None omits topP on Converse
(required for Anthropic, which rejects temperature and topP together).
top_p: Nucleus sampling parameter (0.0 to 1.0)
top_k: Top-k sampling parameter (1 to 40)
aws_access_key_id: AWS access key (optional, uses env vars if not provided)
aws_secret_access_key: AWS secret key (optional, uses env vars if not provided)
@@ -76,16 +75,13 @@ class AWSBedrockConfig(BaseLlmConfig):
def get_model_config(self) -> Dict[str, Any]:
"""Get model-specific configuration parameters."""
base_config: Dict[str, Any] = {
base_config = {
"temperature": self.temperature,
"max_tokens": self.max_tokens,
"top_p": self.top_p,
"top_k": self.top_k,
}
# Only include top_p when explicitly set by the user.
if self.top_p is not None:
base_config["top_p"] = self.top_p
# Add custom model kwargs
base_config.update(self.model_kwargs)
-3
View File
@@ -21,7 +21,6 @@ class AzureOpenAIConfig(BaseLlmConfig):
top_k: int = 1,
enable_vision: bool = False,
vision_details: Optional[str] = "auto",
reasoning_effort: Optional[str] = None,
http_client_proxies: Optional[dict] = None,
# Azure OpenAI-specific parameters
azure_kwargs: Optional[Dict[str, Any]] = None,
@@ -38,7 +37,6 @@ class AzureOpenAIConfig(BaseLlmConfig):
top_k: Top-k sampling parameter, defaults to 1
enable_vision: Enable vision capabilities, defaults to False
vision_details: Vision detail level, defaults to "auto"
reasoning_effort: Effort level for reasoning models ("low", "medium", "high"), defaults to None
http_client_proxies: HTTP client proxy settings, defaults to None
azure_kwargs: Azure-specific configuration, defaults to None
"""
@@ -52,7 +50,6 @@ class AzureOpenAIConfig(BaseLlmConfig):
top_k=top_k,
enable_vision=enable_vision,
vision_details=vision_details,
reasoning_effort=reasoning_effort,
http_client_proxies=http_client_proxies,
)
-5
View File
@@ -23,7 +23,6 @@ class BaseLlmConfig(ABC):
top_k: int = 1,
enable_vision: bool = False,
vision_details: Optional[str] = "auto",
reasoning_effort: Optional[str] = None,
http_client_proxies: Optional[Union[Dict, str]] = None,
):
"""
@@ -49,9 +48,6 @@ class BaseLlmConfig(ABC):
Only applicable to vision-enabled models. Defaults to False
vision_details: Level of detail for vision processing.
Options: "low", "high", "auto". Defaults to "auto"
reasoning_effort: Effort level for reasoning models (e.g., o1, o3, gpt-5).
Options: "low", "medium", "high". Only applicable to reasoning models.
Defaults to None (uses the model's default reasoning effort)
http_client_proxies: Proxy settings for HTTP client.
Can be a dict or string. Defaults to None
"""
@@ -63,5 +59,4 @@ class BaseLlmConfig(ABC):
self.top_k = top_k
self.enable_vision = enable_vision
self.vision_details = vision_details
self.reasoning_effort = reasoning_effort
self.http_client = httpx.Client(proxies=http_client_proxies) if http_client_proxies else None
-3
View File
@@ -20,7 +20,6 @@ class OpenAIConfig(BaseLlmConfig):
top_k: int = 1,
enable_vision: bool = False,
vision_details: Optional[str] = "auto",
reasoning_effort: Optional[str] = None,
http_client_proxies: Optional[dict] = None,
# OpenAI-specific parameters
openai_base_url: Optional[str] = None,
@@ -45,7 +44,6 @@ class OpenAIConfig(BaseLlmConfig):
top_k: Top-k sampling parameter, defaults to 1
enable_vision: Enable vision capabilities, defaults to False
vision_details: Vision detail level, defaults to "auto"
reasoning_effort: Effort level for reasoning models ("low", "medium", "high"), defaults to None
http_client_proxies: HTTP client proxy settings, defaults to None
openai_base_url: OpenAI API base URL, defaults to None
models: List of models for OpenRouter, defaults to None
@@ -65,7 +63,6 @@ class OpenAIConfig(BaseLlmConfig):
top_k=top_k,
enable_vision=enable_vision,
vision_details=vision_details,
reasoning_effort=reasoning_effort,
http_client_proxies=http_client_proxies,
)
+2 -2
View File
@@ -32,8 +32,8 @@ class ChromaDbConfig(BaseModel):
values.pop("path", None)
return values
# Check if local/server configuration is provided
local_config = bool(path) or bool(host and port)
# Check if local/server configuration is provided (excluding default tmp path for cloud config)
local_config = bool(path and path != "/tmp/chroma") or bool(host and port)
if not cloud_config and not local_config:
raise ValueError("Either ChromaDB Cloud configuration (api_key, tenant) or local configuration (path or host/port) must be provided.")
+1 -1
View File
@@ -16,7 +16,7 @@ class QdrantConfig(BaseModel):
path: Optional[str] = Field("/tmp/qdrant", description="Path for local Qdrant database")
url: Optional[str] = Field(None, description="Full URL for Qdrant server")
api_key: Optional[str] = Field(None, description="API key for Qdrant server")
on_disk: Optional[bool] = Field(False,description="Enables persistent storage. Vectors are kept on disk (True) or in memory (False). Does not delete the local database path.")
on_disk: Optional[bool] = Field(False, description="Enables persistent storage")
@model_validator(mode="before")
@classmethod
+10 -11
View File
@@ -13,9 +13,6 @@ class OpenAIEmbedding(EmbeddingBase):
super().__init__(config)
self.config.model = self.config.model or "text-embedding-3-small"
# Only pass `dimensions` to the API when the user set embedding_dims; non-matryoshka
# OpenAI-compatible backends (vLLM, Voyage, etc.) reject the parameter
self._pass_dimensions_to_api = self.config.embedding_dims is not None
self.config.embedding_dims = self.config.embedding_dims or 1536
api_key = self.config.api_key or os.getenv("OPENAI_API_KEY")
@@ -45,11 +42,13 @@ class OpenAIEmbedding(EmbeddingBase):
list: The embedding vector.
"""
text = text.replace("\n", " ")
kwargs = {
"input": [text],
"model": self.config.model,
"encoding_format": "float",
}
if self._pass_dimensions_to_api:
kwargs["dimensions"] = self.config.embedding_dims
return self.client.embeddings.create(**kwargs).data[0].embedding
return (
self.client.embeddings.create(
input=[text],
model=self.config.model,
dimensions=self.config.embedding_dims,
encoding_format="float",
)
.data[0]
.embedding
)
-22
View File
@@ -409,28 +409,6 @@ class NeptuneBase(ABC):
"""
pass
def delete(self, data, filters):
"""
Delete graph entities associated with the given memory text.
Extracts entities and relationships from the memory text using the same
pipeline as add(), then deletes the matching relationships in the graph.
Args:
data (str): The memory text whose graph entities should be removed.
filters (dict): Scope filters (user_id, agent_id, run_id).
"""
try:
entity_type_map = self._retrieve_nodes_from_data(data, filters)
if not entity_type_map:
logger.debug("No entities found in memory text, skipping graph cleanup")
return
to_be_deleted = self._establish_nodes_relations_from_data(data, filters, entity_type_map)
if to_be_deleted:
self._delete_entities(to_be_deleted, filters["user_id"])
except Exception as e:
logger.error(f"Error during graph cleanup for memory delete: {e}")
def delete_all(self, filters):
cypher, params = self._delete_all_cypher(filters)
self.graph.query(cypher, params=params)
-25
View File
@@ -40,31 +40,6 @@ class AnthropicLLM(LLMBase):
api_key = self.config.api_key or os.getenv("ANTHROPIC_API_KEY")
self.client = anthropic.Anthropic(api_key=api_key)
def _get_common_params(self, **kwargs) -> Dict:
"""Get common parameters, avoiding sending both temperature and top_p together.
Anthropic rejects requests that include both temperature and top_p.
When both are set, we keep temperature and drop top_p.
"""
params = {}
if self.config.max_tokens is not None:
params["max_tokens"] = self.config.max_tokens
has_temperature = self.config.temperature is not None
has_top_p = self.config.top_p is not None
if has_temperature and has_top_p:
# Anthropic forbids both; prefer temperature
params["temperature"] = self.config.temperature
elif has_temperature:
params["temperature"] = self.config.temperature
elif has_top_p:
params["top_p"] = self.config.top_p
params.update(kwargs)
return params
def generate_response(
self,
messages: List[Dict[str, str]],
+41 -43
View File
@@ -228,12 +228,6 @@ class AWSBedrockLLM(LLMBase):
return "\n\nHuman: " + "".join(formatted_messages) + "\n\nAssistant:"
def _merge_optional_top_p(self, target: Dict[str, Any], *, key: str = "top_p") -> None:
"""Add nucleus sampling to ``target`` only when ``model_config`` has ``top_p`` set."""
top_p = self.model_config.get("top_p")
if top_p is not None:
target[key] = top_p
def _prepare_input(self, prompt: str) -> Dict[str, Any]:
"""
Prepare input for the current provider's model.
@@ -274,38 +268,44 @@ class AWSBedrockLLM(LLMBase):
"messages": [{"role": "user", "content": prompt}],
"max_tokens": self.model_config.get("max_tokens", 5000),
"temperature": self.model_config.get("temperature", 0.1),
"top_p": self.model_config.get("top_p", 0.9),
}
self._merge_optional_top_p(input_body)
else:
# Legacy Amazon models
text_gen_config: Dict[str, Any] = {
"maxTokenCount": self.model_config.get("max_tokens", 5000),
"temperature": self.model_config.get("temperature", 0.1),
input_body = {
"inputText": prompt,
"textGenerationConfig": {
"maxTokenCount": self.model_config.get("max_tokens", 5000),
"topP": self.model_config.get("top_p", 0.9),
"temperature": self.model_config.get("temperature", 0.1),
},
}
# Remove None values
input_body["textGenerationConfig"] = {
k: v for k, v in input_body["textGenerationConfig"].items() if v is not None
}
self._merge_optional_top_p(text_gen_config, key="topP")
input_body = {"inputText": prompt, "textGenerationConfig": text_gen_config}
elif self.provider == "anthropic":
input_body = {
"messages": [{"role": "user", "content": [{"type": "text", "text": prompt}]}],
"max_tokens": self.model_config.get("max_tokens", 2000),
"temperature": self.model_config.get("temperature", 0.1),
"top_p": self.model_config.get("top_p", 0.9),
"anthropic_version": "bedrock-2023-05-31",
}
self._merge_optional_top_p(input_body)
elif self.provider == "meta":
input_body = {
"prompt": prompt,
"max_gen_len": self.model_config.get("max_tokens", 5000),
"temperature": self.model_config.get("temperature", 0.1),
"top_p": self.model_config.get("top_p", 0.9),
}
self._merge_optional_top_p(input_body)
elif self.provider == "mistral":
input_body = {
"prompt": prompt,
"max_tokens": self.model_config.get("max_tokens", 5000),
"temperature": self.model_config.get("temperature", 0.1),
"top_p": self.model_config.get("top_p", 0.9),
}
self._merge_optional_top_p(input_body)
else:
# Generic case - add all model config parameters
input_body.update(self.model_config)
@@ -479,29 +479,6 @@ class AWSBedrockLLM(LLMBase):
return converse_tools
def _default_max_tokens_for_converse(self) -> int:
"""Default maxTokens if ``max_tokens`` is missing (Nova: 5000, else 2000)."""
model_id = (self.config.model or "").lower()
if self.provider == "amazon" and "nova" in model_id:
return 5000
return 2000
def _build_inference_config(self) -> Dict[str, Any]:
"""Build Converse ``inferenceConfig``. Anthropic allows only one of temperature or topP; we keep temperature and omit topP."""
inference_config: Dict[str, Any] = {
"maxTokens": self.model_config.get("max_tokens", self._default_max_tokens_for_converse()),
"temperature": self.model_config.get("temperature", 0.1),
}
top_p = self.model_config.get("top_p")
if top_p is not None:
if self.provider == "anthropic":
logger.debug("Omitting topP for Anthropic Converse (using temperature); top_p=%s", top_p)
else:
inference_config["topP"] = top_p
return inference_config
def _generate_with_tools(self, messages: List[Dict[str, str]], tools: List[Dict], stream: bool = False) -> Dict[str, Any]:
"""Generate response with tool calling support using correct message format."""
# Format messages for tool-enabled models
@@ -524,7 +501,11 @@ class AWSBedrockLLM(LLMBase):
converse_params = {
"modelId": self.config.model,
"messages": formatted_messages,
"inferenceConfig": self._build_inference_config(),
"inferenceConfig": {
"maxTokens": self.model_config.get("max_tokens", 2000),
"temperature": self.model_config.get("temperature", 0.1),
"topP": self.model_config.get("top_p", 0.9),
}
}
# Add system message if present (for Anthropic)
@@ -550,7 +531,11 @@ class AWSBedrockLLM(LLMBase):
converse_params = {
"modelId": self.config.model,
"messages": formatted_messages,
"inferenceConfig": self._build_inference_config(),
"inferenceConfig": {
"maxTokens": self.model_config.get("max_tokens", 2000),
"temperature": self.model_config.get("temperature", 0.1),
"topP": self.model_config.get("top_p", 0.9),
}
}
# Add system message if present
@@ -569,13 +554,26 @@ class AWSBedrockLLM(LLMBase):
return str(response)
elif self.provider == "amazon" and "nova" in self.config.model.lower():
# Nova models use the Converse API even without tools
# Nova models use converse API even without tools
formatted_messages = self._format_messages_amazon(messages)
input_body = {
"messages": formatted_messages,
"max_tokens": self.model_config.get("max_tokens", 5000),
"temperature": self.model_config.get("temperature", 0.1),
"top_p": self.model_config.get("top_p", 0.9),
}
# Use converse API for Nova models
response = self.client.converse(
modelId=self.config.model,
messages=formatted_messages,
inferenceConfig=self._build_inference_config(),
messages=input_body["messages"],
inferenceConfig={
"maxTokens": input_body["max_tokens"],
"temperature": input_body["temperature"],
"topP": input_body["top_p"],
}
)
return self._parse_response(response)
else:
# For other providers and legacy Amazon models (like Titan)
-1
View File
@@ -31,7 +31,6 @@ class AzureOpenAILLM(LLMBase):
top_k=config.top_k,
enable_vision=config.enable_vision,
vision_details=config.vision_details,
reasoning_effort=getattr(config, 'reasoning_effort', None),
http_client_proxies=config.http_client,
)
+1 -6
View File
@@ -88,12 +88,7 @@ class LLMBase(ABC):
supported_params["tools"] = kwargs["tools"]
if "tool_choice" in kwargs:
supported_params["tool_choice"] = kwargs["tool_choice"]
# Add reasoning_effort if configured
reasoning_effort = getattr(self.config, 'reasoning_effort', None)
if reasoning_effort:
supported_params["reasoning_effort"] = reasoning_effort
return supported_params
else:
# For regular models, include all common parameters
-1
View File
@@ -29,7 +29,6 @@ class OpenAILLM(LLMBase):
top_k=config.top_k,
enable_vision=config.enable_vision,
vision_details=config.vision_details,
reasoning_effort=getattr(config, 'reasoning_effort', None),
http_client_proxies=config.http_client,
)
-22
View File
@@ -246,28 +246,6 @@ class MemoryGraph:
logger.info(f"Returned {len(search_results)} search results")
return search_results
def delete(self, data, filters):
"""
Delete graph entities associated with the given memory text.
Extracts entities and relationships from the memory text using the same
pipeline as add(), then deletes the matching relationships in the graph.
Args:
data (str): The memory text whose graph entities should be removed.
filters (dict): Scope filters (user_id, agent_id, run_id).
"""
try:
entity_type_map = self._retrieve_nodes_from_data(data, filters)
if not entity_type_map:
logger.debug("No entities found in memory text, skipping graph cleanup")
return
to_be_deleted = self._establish_nodes_relations_from_data(data, filters, entity_type_map)
if to_be_deleted:
self._delete_entities(to_be_deleted, filters)
except Exception as e:
logger.error(f"Error during graph cleanup for memory delete: {e}")
def delete_all(self, filters):
"""Delete all nodes and relationships for a user or specific agent."""
where_parts = ["n.user_id = %s"]
-22
View File
@@ -129,28 +129,6 @@ class MemoryGraph:
return search_results
def delete(self, data, filters):
"""
Delete graph entities associated with the given memory text.
Extracts entities and relationships from the memory text using the same
pipeline as add(), then soft-deletes the matching relationships in the graph.
Args:
data (str): The memory text whose graph entities should be removed.
filters (dict): Scope filters (user_id, agent_id, run_id).
"""
try:
entity_type_map = self._retrieve_nodes_from_data(data, filters)
if not entity_type_map:
logger.debug("No entities found in memory text, skipping graph cleanup")
return
to_be_deleted = self._establish_nodes_relations_from_data(data, filters, entity_type_map)
if to_be_deleted:
self._delete_entities(to_be_deleted, filters)
except Exception as e:
logger.error(f"Error during graph cleanup for memory delete: {e}")
def delete_all(self, filters):
# Build node properties for filtering
node_props = ["user_id: $user_id"]
-22
View File
@@ -149,28 +149,6 @@ class MemoryGraph:
return search_results
def delete(self, data, filters):
"""
Delete graph entities associated with the given memory text.
Extracts entities and relationships from the memory text using the same
pipeline as add(), then deletes the matching relationships in the graph.
Args:
data (str): The memory text whose graph entities should be removed.
filters (dict): Scope filters (user_id, agent_id, run_id).
"""
try:
entity_type_map = self._retrieve_nodes_from_data(data, filters)
if not entity_type_map:
logger.debug("No entities found in memory text, skipping graph cleanup")
return
to_be_deleted = self._establish_nodes_relations_from_data(data, filters, entity_type_map)
if to_be_deleted:
self._delete_entities(to_be_deleted, filters)
except Exception as e:
logger.error(f"Error during graph cleanup for memory delete: {e}")
def delete_all(self, filters):
# Build node properties for filtering
node_props = ["user_id: $user_id"]
+60 -165
View File
@@ -9,7 +9,7 @@ import uuid
import warnings
from copy import deepcopy
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional, Union
from typing import Any, Dict, Optional
from pydantic import ValidationError
@@ -479,8 +479,7 @@ class Memory(MemoryBase):
msg_content = message_dict["content"]
msg_embeddings = self.embedding_model.embed(msg_content, "add")
# Pass embeddings as a dict so _create_memory can reuse the cached embedding
mem_id = self._create_memory(msg_content, {msg_content: msg_embeddings}, per_msg_meta)
mem_id = self._create_memory(msg_content, msg_embeddings, per_msg_meta)
returned_memories.append(
{
@@ -516,15 +515,15 @@ class Memory(MemoryBase):
)
try:
cleaned_response = remove_code_blocks(response)
if not cleaned_response.strip():
response = remove_code_blocks(response)
if not response.strip():
new_retrieved_facts = []
else:
try:
# First try direct JSON parsing
new_retrieved_facts = json.loads(cleaned_response, strict=False)["facts"]
new_retrieved_facts = json.loads(response, strict=False)["facts"]
except json.JSONDecodeError:
# Try extracting JSON from response (handles chatty LLM output)
# Try extracting JSON from response using built-in function
extracted_json = extract_json(response)
new_retrieved_facts = json.loads(extracted_json, strict=False)["facts"]
new_retrieved_facts = normalize_facts(new_retrieved_facts)
@@ -589,11 +588,8 @@ class Memory(MemoryBase):
logger.warning("Empty response from LLM, no memories to extract")
new_memories_with_actions = {}
else:
try:
new_memories_with_actions = json.loads(remove_code_blocks(response), strict=False)
except json.JSONDecodeError:
extracted_json = extract_json(response)
new_memories_with_actions = json.loads(extracted_json, strict=False)
response = remove_code_blocks(response)
new_memories_with_actions = json.loads(response, strict=False)
except Exception as e:
logger.error(f"Invalid JSON response: {e}")
new_memories_with_actions = {}
@@ -612,9 +608,6 @@ class Memory(MemoryBase):
event_type = resp.get("event")
if event_type == "ADD":
# Ensure action_text has an embedding cached to avoid redundant API calls
if action_text not in new_message_embeddings:
new_message_embeddings[action_text] = self.embedding_model.embed(action_text, "add")
memory_id = self._create_memory(
data=action_text,
existing_embeddings=new_message_embeddings,
@@ -622,9 +615,6 @@ class Memory(MemoryBase):
)
returned_memories.append({"id": memory_id, "memory": action_text, "event": event_type})
elif event_type == "UPDATE":
# Ensure action_text has an embedding cached to avoid redundant API calls
if action_text not in new_message_embeddings:
new_message_embeddings[action_text] = self.embedding_model.embed(action_text, "update")
self._update_memory(
memory_id=temp_uuid_mapping[resp.get("id")],
data=action_text,
@@ -977,11 +967,11 @@ class Memory(MemoryBase):
}
if operator in operator_map:
result.setdefault(key, {})[operator_map[operator]] = value
result[key] = {operator_map[operator]: value}
else:
raise ValueError(f"Unsupported metadata filter operator: {operator}")
return result
for key, value in metadata_filters.items():
if key == "AND":
# Logical AND: combine multiple conditions
@@ -1081,15 +1071,13 @@ class Memory(MemoryBase):
return original_memories
def update(self, memory_id, data, metadata: Optional[Dict[str, Any]] = None):
def update(self, memory_id, data):
"""
Update a memory by ID.
Args:
memory_id (str): ID of the memory to update.
data (str): New content to update the memory with.
metadata (dict, optional): Additional metadata to update. Existing metadata fields
not specified here will be preserved. Defaults to None.
Returns:
dict: Success message indicating the memory was updated.
@@ -1097,14 +1085,12 @@ class Memory(MemoryBase):
Example:
>>> m.update(memory_id="mem_123", data="Likes to play tennis on weekends")
{'message': 'Memory updated successfully!'}
>>> m.update(memory_id="mem_123", data="Likes tennis", metadata={"category": "sports"})
{'message': 'Memory updated successfully!'}
"""
capture_event("mem0.update", self, {"memory_id": memory_id, "sync_type": "sync"})
existing_embeddings = {data: self.embedding_model.embed(data, "update")}
self._update_memory(memory_id, data, existing_embeddings, metadata)
self._update_memory(memory_id, data, existing_embeddings)
return {"message": "Memory updated successfully!"}
def delete(self, memory_id):
@@ -1115,27 +1101,7 @@ class Memory(MemoryBase):
memory_id (str): ID of the memory to delete.
"""
capture_event("mem0.delete", self, {"memory_id": memory_id, "sync_type": "sync"})
existing_memory = self.vector_store.get(vector_id=memory_id)
if existing_memory is None:
raise ValueError(f"Memory with id {memory_id} not found")
# Clean up graph entities before deleting from vector store
if self.enable_graph:
try:
memory_text = existing_memory.payload.get("data", "")
if memory_text:
filters = {}
for key in ("user_id", "agent_id", "run_id"):
val = existing_memory.payload.get(key)
if val:
filters[key] = val
if filters.get("user_id"):
self.graph.delete(memory_text, filters)
except Exception as e:
logger.error(f"Error cleaning up graph for memory {memory_id}: {e}")
self._delete_memory(memory_id, existing_memory)
self._delete_memory(memory_id)
return {"message": "Memory deleted successfully!"}
def delete_all(self, user_id: Optional[str] = None, agent_id: Optional[str] = None, run_id: Optional[str] = None):
@@ -1187,37 +1153,31 @@ class Memory(MemoryBase):
capture_event("mem0.history", self, {"memory_id": memory_id, "sync_type": "sync"})
return self.db.get_history(memory_id)
def _create_memory(self, data: str, existing_embeddings: Union[Dict[str, List[float]], List[float]], metadata=None):
def _create_memory(self, data, existing_embeddings, metadata=None):
logger.debug(f"Creating memory with {data=}")
# existing_embeddings may be a dict (preferred) or a precomputed vector
if isinstance(existing_embeddings, dict) and data in existing_embeddings:
if data in existing_embeddings:
embeddings = existing_embeddings[data]
elif not isinstance(existing_embeddings, dict):
embeddings = existing_embeddings
else:
embeddings = self.embedding_model.embed(data, memory_action="add")
memory_id = str(uuid.uuid4())
new_metadata = deepcopy(metadata) if metadata is not None else {}
new_metadata["data"] = data
new_metadata["hash"] = hashlib.md5(data.encode()).hexdigest()
if "created_at" not in new_metadata:
new_metadata["created_at"] = datetime.now(timezone.utc).isoformat()
new_metadata["updated_at"] = new_metadata["created_at"]
metadata = metadata or {}
metadata["data"] = data
metadata["hash"] = hashlib.md5(data.encode()).hexdigest()
metadata["created_at"] = datetime.now(timezone.utc).isoformat()
self.vector_store.insert(
vectors=[embeddings],
ids=[memory_id],
payloads=[new_metadata],
payloads=[metadata],
)
self.db.add_history(
memory_id,
None,
data,
"ADD",
created_at=new_metadata.get("created_at"),
updated_at=new_metadata.get("updated_at"),
actor_id=new_metadata.get("actor_id"),
role=new_metadata.get("role"),
created_at=metadata.get("created_at"),
actor_id=metadata.get("actor_id"),
role=metadata.get("role"),
)
return memory_id
@@ -1251,17 +1211,16 @@ class Memory(MemoryBase):
if metadata is None:
raise ValueError("Metadata cannot be done for procedural memory.")
new_metadata = deepcopy(metadata)
new_metadata["memory_type"] = MemoryType.PROCEDURAL.value
metadata["memory_type"] = MemoryType.PROCEDURAL.value
embeddings = self.embedding_model.embed(procedural_memory, memory_action="add")
memory_id = self._create_memory(procedural_memory, {procedural_memory: embeddings}, metadata=new_metadata)
memory_id = self._create_memory(procedural_memory, {procedural_memory: embeddings}, metadata=metadata)
capture_event("mem0._create_procedural_memory", self, {"memory_id": memory_id, "sync_type": "sync"})
result = {"results": [{"id": memory_id, "memory": procedural_memory, "event": "ADD"}]}
return result
def _update_memory(self, memory_id, data: str, existing_embeddings: Union[Dict[str, List[float]], List[float]], metadata=None):
def _update_memory(self, memory_id, data, existing_embeddings, metadata=None):
logger.info(f"Updating memory with {data=}")
try:
@@ -1289,15 +1248,13 @@ class Memory(MemoryBase):
new_metadata["agent_id"] = existing_memory.payload["agent_id"]
if "run_id" not in new_metadata and "run_id" in existing_memory.payload:
new_metadata["run_id"] = existing_memory.payload["run_id"]
if "actor_id" in existing_memory.payload:
if "actor_id" not in new_metadata and "actor_id" in existing_memory.payload:
new_metadata["actor_id"] = existing_memory.payload["actor_id"]
if "role" not in new_metadata and "role" in existing_memory.payload:
new_metadata["role"] = existing_memory.payload["role"]
if isinstance(existing_embeddings, dict) and data in existing_embeddings:
if data in existing_embeddings:
embeddings = existing_embeddings[data]
elif not isinstance(existing_embeddings, dict):
embeddings = existing_embeddings
else:
embeddings = self.embedding_model.embed(data, "update")
@@ -1320,26 +1277,18 @@ class Memory(MemoryBase):
)
return memory_id
def _delete_memory(self, memory_id, existing_memory=None):
def _delete_memory(self, memory_id):
logger.info(f"Deleting memory with {memory_id=}")
existing_memory = self.vector_store.get(vector_id=memory_id)
if existing_memory is None:
existing_memory = self.vector_store.get(vector_id=memory_id)
if existing_memory is None:
raise ValueError(f"Memory with id {memory_id} not found")
raise ValueError(f"Memory with id {memory_id} not found")
prev_value = existing_memory.payload.get("data", "")
# Preserve original created_at and record deletion time
created_at = _normalize_iso_timestamp_to_utc(existing_memory.payload.get("created_at"))
updated_at = datetime.now(timezone.utc).isoformat()
self.vector_store.delete(vector_id=memory_id)
self.db.add_history(
memory_id,
prev_value,
None,
"DELETE",
created_at=created_at,
updated_at=updated_at,
actor_id=existing_memory.payload.get("actor_id"),
role=existing_memory.payload.get("role"),
is_deleted=1,
@@ -1574,8 +1523,7 @@ class AsyncMemory(MemoryBase):
msg_content = message_dict["content"]
msg_embeddings = await asyncio.to_thread(self.embedding_model.embed, msg_content, "add")
# Pass embeddings as a dict so _create_memory can reuse the cached embedding
mem_id = await self._create_memory(msg_content, {msg_content: msg_embeddings}, per_msg_meta)
mem_id = await self._create_memory(msg_content, msg_embeddings, per_msg_meta)
returned_memories.append(
{
@@ -1607,15 +1555,15 @@ class AsyncMemory(MemoryBase):
response_format={"type": "json_object"},
)
try:
cleaned_response = remove_code_blocks(response)
if not cleaned_response.strip():
response = remove_code_blocks(response)
if not response.strip():
new_retrieved_facts = []
else:
try:
# First try direct JSON parsing
new_retrieved_facts = json.loads(cleaned_response, strict=False)["facts"]
new_retrieved_facts = json.loads(response, strict=False)["facts"]
except json.JSONDecodeError:
# Try extracting JSON from response (handles chatty LLM output)
# Try extracting JSON from response using built-in function
extracted_json = extract_json(response)
new_retrieved_facts = json.loads(extracted_json, strict=False)["facts"]
new_retrieved_facts = normalize_facts(new_retrieved_facts)
@@ -1683,11 +1631,8 @@ class AsyncMemory(MemoryBase):
logger.warning("Empty response from LLM, no memories to extract")
new_memories_with_actions = {}
else:
try:
new_memories_with_actions = json.loads(remove_code_blocks(response), strict=False)
except json.JSONDecodeError:
extracted_json = extract_json(response)
new_memories_with_actions = json.loads(extracted_json, strict=False)
response = remove_code_blocks(response)
new_memories_with_actions = json.loads(response, strict=False)
except Exception as e:
logger.error(f"Invalid JSON response: {e}")
new_memories_with_actions = {}
@@ -1706,11 +1651,6 @@ class AsyncMemory(MemoryBase):
event_type = resp.get("event")
if event_type == "ADD":
# Ensure action_text has an embedding cached to avoid redundant API calls
if action_text not in new_message_embeddings:
new_message_embeddings[action_text] = await asyncio.to_thread(
self.embedding_model.embed, action_text, "add"
)
task = asyncio.create_task(
self._create_memory(
data=action_text,
@@ -1720,11 +1660,6 @@ class AsyncMemory(MemoryBase):
)
memory_tasks.append((task, resp, "ADD", None))
elif event_type == "UPDATE":
# Ensure action_text has an embedding cached to avoid redundant API calls
if action_text not in new_message_embeddings:
new_message_embeddings[action_text] = await asyncio.to_thread(
self.embedding_model.embed, action_text, "update"
)
task = asyncio.create_task(
self._update_memory(
memory_id=temp_uuid_mapping[resp["id"]],
@@ -2102,7 +2037,7 @@ class AsyncMemory(MemoryBase):
}
if operator in operator_map:
result.setdefault(key, {})[operator_map[operator]] = value
result[key] = {operator_map[operator]: value}
else:
raise ValueError(f"Unsupported metadata filter operator: {operator}")
return result
@@ -2208,15 +2143,13 @@ class AsyncMemory(MemoryBase):
return original_memories
async def update(self, memory_id, data, metadata: Optional[Dict[str, Any]] = None):
async def update(self, memory_id, data):
"""
Update a memory by ID asynchronously.
Args:
memory_id (str): ID of the memory to update.
data (str): New content to update the memory with.
metadata (dict, optional): Additional metadata to update. Existing metadata fields
not specified here will be preserved. Defaults to None.
Returns:
dict: Success message indicating the memory was updated.
@@ -2224,15 +2157,13 @@ class AsyncMemory(MemoryBase):
Example:
>>> await m.update(memory_id="mem_123", data="Likes to play tennis on weekends")
{'message': 'Memory updated successfully!'}
>>> await m.update(memory_id="mem_123", data="Likes tennis", metadata={"category": "sports"})
{'message': 'Memory updated successfully!'}
"""
capture_event("mem0.update", self, {"memory_id": memory_id, "sync_type": "async"})
embeddings = await asyncio.to_thread(self.embedding_model.embed, data, "update")
existing_embeddings = {data: embeddings}
await self._update_memory(memory_id, data, existing_embeddings, metadata)
await self._update_memory(memory_id, data, existing_embeddings)
return {"message": "Memory updated successfully!"}
async def delete(self, memory_id):
@@ -2243,27 +2174,7 @@ class AsyncMemory(MemoryBase):
memory_id (str): ID of the memory to delete.
"""
capture_event("mem0.delete", self, {"memory_id": memory_id, "sync_type": "async"})
existing_memory = await asyncio.to_thread(self.vector_store.get, vector_id=memory_id)
if existing_memory is None:
raise ValueError(f"Memory with id {memory_id} not found")
# Clean up graph entities before deleting from vector store
if self.enable_graph:
try:
memory_text = existing_memory.payload.get("data", "")
if memory_text:
filters = {}
for key in ("user_id", "agent_id", "run_id"):
val = existing_memory.payload.get(key)
if val:
filters[key] = val
if filters.get("user_id"):
await asyncio.to_thread(self.graph.delete, memory_text, filters)
except Exception as e:
logger.error(f"Error cleaning up graph for memory {memory_id}: {e}")
await self._delete_memory(memory_id, existing_memory)
await self._delete_memory(memory_id)
return {"message": "Memory deleted successfully!"}
async def delete_all(self, user_id=None, agent_id=None, run_id=None):
@@ -2318,29 +2229,24 @@ class AsyncMemory(MemoryBase):
capture_event("mem0.history", self, {"memory_id": memory_id, "sync_type": "async"})
return await asyncio.to_thread(self.db.get_history, memory_id)
async def _create_memory(self, data: str, existing_embeddings: Union[Dict[str, List[float]], List[float]], metadata=None):
async def _create_memory(self, data, existing_embeddings, metadata=None):
logger.debug(f"Creating memory with {data=}")
# existing_embeddings may be a dict (preferred) or a precomputed vector
if isinstance(existing_embeddings, dict) and data in existing_embeddings:
if data in existing_embeddings:
embeddings = existing_embeddings[data]
elif not isinstance(existing_embeddings, dict):
embeddings = existing_embeddings
else:
embeddings = await asyncio.to_thread(self.embedding_model.embed, data, memory_action="add")
memory_id = str(uuid.uuid4())
new_metadata = deepcopy(metadata) if metadata is not None else {}
new_metadata["data"] = data
new_metadata["hash"] = hashlib.md5(data.encode()).hexdigest()
if "created_at" not in new_metadata:
new_metadata["created_at"] = datetime.now(timezone.utc).isoformat()
new_metadata["updated_at"] = new_metadata["created_at"]
metadata = metadata or {}
metadata["data"] = data
metadata["hash"] = hashlib.md5(data.encode()).hexdigest()
metadata["created_at"] = datetime.now(timezone.utc).isoformat()
await asyncio.to_thread(
self.vector_store.insert,
vectors=[embeddings],
ids=[memory_id],
payloads=[new_metadata],
payloads=[metadata],
)
await asyncio.to_thread(
@@ -2349,10 +2255,9 @@ class AsyncMemory(MemoryBase):
None,
data,
"ADD",
created_at=new_metadata.get("created_at"),
updated_at=new_metadata.get("updated_at"),
actor_id=new_metadata.get("actor_id"),
role=new_metadata.get("role"),
created_at=metadata.get("created_at"),
actor_id=metadata.get("actor_id"),
role=metadata.get("role"),
)
return memory_id
@@ -2401,17 +2306,16 @@ class AsyncMemory(MemoryBase):
if metadata is None:
raise ValueError("Metadata cannot be done for procedural memory.")
new_metadata = deepcopy(metadata)
new_metadata["memory_type"] = MemoryType.PROCEDURAL.value
metadata["memory_type"] = MemoryType.PROCEDURAL.value
embeddings = await asyncio.to_thread(self.embedding_model.embed, procedural_memory, memory_action="add")
memory_id = await self._create_memory(procedural_memory, {procedural_memory: embeddings}, metadata=new_metadata)
memory_id = await self._create_memory(procedural_memory, {procedural_memory: embeddings}, metadata=metadata)
capture_event("mem0._create_procedural_memory", self, {"memory_id": memory_id, "sync_type": "async"})
result = {"results": [{"id": memory_id, "memory": procedural_memory, "event": "ADD"}]}
return result
async def _update_memory(self, memory_id, data: str, existing_embeddings: Union[Dict[str, List[float]], List[float]], metadata=None):
async def _update_memory(self, memory_id, data, existing_embeddings, metadata=None):
logger.info(f"Updating memory with {data=}")
try:
@@ -2440,15 +2344,13 @@ class AsyncMemory(MemoryBase):
if "run_id" not in new_metadata and "run_id" in existing_memory.payload:
new_metadata["run_id"] = existing_memory.payload["run_id"]
if "actor_id" in existing_memory.payload:
if "actor_id" not in new_metadata and "actor_id" in existing_memory.payload:
new_metadata["actor_id"] = existing_memory.payload["actor_id"]
if "role" not in new_metadata and "role" in existing_memory.payload:
new_metadata["role"] = existing_memory.payload["role"]
if isinstance(existing_embeddings, dict) and data in existing_embeddings:
if data in existing_embeddings:
embeddings = existing_embeddings[data]
elif not isinstance(existing_embeddings, dict):
embeddings = existing_embeddings
else:
embeddings = await asyncio.to_thread(self.embedding_model.embed, data, "update")
@@ -2473,18 +2375,13 @@ class AsyncMemory(MemoryBase):
)
return memory_id
async def _delete_memory(self, memory_id, existing_memory=None):
async def _delete_memory(self, memory_id):
logger.info(f"Deleting memory with {memory_id=}")
existing_memory = await asyncio.to_thread(self.vector_store.get, vector_id=memory_id)
if existing_memory is None:
existing_memory = await asyncio.to_thread(self.vector_store.get, vector_id=memory_id)
if existing_memory is None:
raise ValueError(f"Memory with id {memory_id} not found")
raise ValueError(f"Memory with id {memory_id} not found")
prev_value = existing_memory.payload.get("data", "")
# Preserve original created_at and record deletion time
created_at = _normalize_iso_timestamp_to_utc(existing_memory.payload.get("created_at"))
updated_at = datetime.now(timezone.utc).isoformat()
await asyncio.to_thread(self.vector_store.delete, vector_id=memory_id)
await asyncio.to_thread(
self.db.add_history,
@@ -2492,8 +2389,6 @@ class AsyncMemory(MemoryBase):
prev_value,
None,
"DELETE",
created_at=created_at,
updated_at=updated_at,
actor_id=existing_memory.payload.get("actor_id"),
role=existing_memory.payload.get("role"),
is_deleted=1,
-22
View File
@@ -134,28 +134,6 @@ class MemoryGraph:
return search_results
def delete(self, data, filters):
"""
Delete graph entities associated with the given memory text.
Extracts entities and relationships from the memory text using the same
pipeline as add(), then deletes the matching relationships in the graph.
Args:
data (str): The memory text whose graph entities should be removed.
filters (dict): Scope filters (user_id, agent_id).
"""
try:
entity_type_map = self._retrieve_nodes_from_data(data, filters)
if not entity_type_map:
logger.debug("No entities found in memory text, skipping graph cleanup")
return
to_be_deleted = self._establish_nodes_relations_from_data(data, filters, entity_type_map)
if to_be_deleted:
self._delete_entities(to_be_deleted, filters)
except Exception as e:
logger.error(f"Error during graph cleanup for memory delete: {e}")
def delete_all(self, filters):
"""Delete all nodes and relationships for a user or specific agent."""
if filters.get("agent_id"):
+2 -9
View File
@@ -124,20 +124,14 @@ def remove_code_blocks(content: str) -> str:
def extract_json(text):
"""
Extracts JSON content from a string, removing enclosing triple backticks and optional 'json' tag if present.
If no code block is found, attempts to locate JSON by finding the first '{' and last '}'.
If that also fails, returns the text as-is.
If no code block is found, returns the text as-is.
"""
text = text.strip()
match = re.search(r"```(?:json)?\s*(.*?)\s*```", text, re.DOTALL)
if match:
json_str = match.group(1)
else:
start_idx = text.find("{")
end_idx = text.rfind("}")
if start_idx != -1 and end_idx != -1 and end_idx > start_idx:
json_str = text[start_idx : end_idx + 1]
else:
json_str = text
json_str = text # assume it's raw JSON
return json_str
@@ -255,7 +249,6 @@ def sanitize_relationship_for_cypher(relationship) -> str:
"}": "_rbrace_",
"<": "_langle_",
">": "_rangle_",
"-": "_",
}
# Apply replacements and clean up
+1 -2
View File
@@ -3,7 +3,6 @@ from typing import Dict, Optional, Union
from mem0.configs.embeddings.base import BaseEmbedderConfig
from mem0.configs.llms.anthropic import AnthropicConfig
from mem0.configs.llms.aws_bedrock import AWSBedrockConfig
from mem0.configs.llms.azure import AzureOpenAIConfig
from mem0.configs.llms.base import BaseLlmConfig
from mem0.configs.llms.deepseek import DeepSeekConfig
@@ -39,7 +38,7 @@ class LlmFactory:
"openai": ("mem0.llms.openai.OpenAILLM", OpenAIConfig),
"groq": ("mem0.llms.groq.GroqLLM", BaseLlmConfig),
"together": ("mem0.llms.together.TogetherLLM", BaseLlmConfig),
"aws_bedrock": ("mem0.llms.aws_bedrock.AWSBedrockLLM", AWSBedrockConfig),
"aws_bedrock": ("mem0.llms.aws_bedrock.AWSBedrockLLM", BaseLlmConfig),
"litellm": ("mem0.llms.litellm.LiteLLM", BaseLlmConfig),
"azure_openai": ("mem0.llms.azure_openai.AzureOpenAILLM", AzureOpenAIConfig),
"openai_structured": ("mem0.llms.openai_structured.OpenAIStructuredLLM", OpenAIConfig),
+54 -111
View File
@@ -1,13 +1,11 @@
import json
import logging
import re
import uuid
from typing import Optional, List
from datetime import datetime, date
from databricks.sdk.service.catalog import ColumnInfo, ColumnTypeName, TableType, DataSourceFormat
from databricks.sdk.service.catalog import TableConstraint, PrimaryKeyConstraint
from databricks.sdk import WorkspaceClient
from databricks.sdk.service.sql import StatementParameterListItem
from databricks.sdk.service.vectorsearch import (
VectorIndexType,
DeltaSyncVectorIndexSpecRequest,
@@ -30,9 +28,6 @@ class MemoryResult(BaseModel):
excluded_keys = {"user_id", "agent_id", "run_id", "hash", "data", "created_at", "updated_at"}
# Pattern for valid SQL identifiers to prevent column name injection
_VALID_SQL_IDENTIFIER = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
class Databricks(VectorStoreBase):
def __init__(
@@ -70,7 +65,7 @@ class Databricks(VectorStoreBase):
catalog (str): Unity Catalog catalog name.
schema (str): Unity Catalog schema name.
table_name (str): Source Delta table name.
collection_name (str, optional): Vector search index name (default: "mem0").
index_name (str, optional): Vector search index name (default: "mem0").
index_type (str, optional): Index type, either "DELTA_SYNC" or "DIRECT_ACCESS" (default: "DELTA_SYNC").
embedding_model_endpoint_name (str, optional): Embedding model endpoint for Databricks-computed embeddings.
embedding_dimension (int, optional): Vector embedding dimensions (default: 1536).
@@ -90,7 +85,7 @@ class Databricks(VectorStoreBase):
self.fully_qualified_index_name = f"{self.catalog}.{self.schema}.{self.index_name}"
# Configuration
self.index_type = VectorIndexType(index_type) if isinstance(index_type, str) else index_type
self.index_type = index_type
self.embedding_model_endpoint_name = embedding_model_endpoint_name
self.embedding_dimension = embedding_dimension
self.endpoint_type = endpoint_type
@@ -266,11 +261,11 @@ class Databricks(VectorStoreBase):
)
logger.info(f"Successfully created source table '{self.fully_qualified_table_name}'")
self.client.table_constraints.create(
full_name_arg=self.fully_qualified_table_name,
full_name_arg="logistics_dev.ai.dev_memory",
constraint=TableConstraint(
primary_key_constraint=PrimaryKeyConstraint(
name=f"pk_{self.table_name}",
child_columns=["memory_id"],
name="pk_dev_memory", # Name of the primary key constraint
child_columns=["memory_id"], # Columns that make up the primary key
)
),
)
@@ -393,46 +388,28 @@ class Databricks(VectorStoreBase):
# Determine the number of items to process
num_items = len(payloads) if payloads else len(vectors) if vectors else 0
params = []
value_tuples = []
for i in range(num_items):
placeholders = []
values = []
for col in self.columns:
param_name = f"{col.name}_{i}"
if col.name == "memory_id":
val = ids[i] if ids and i < len(ids) else str(uuid.uuid4())
elif col.name == "embedding":
val = vectors[i] if vectors and i < len(vectors) else []
# Vectors are numeric arrays — ARRAY type not supported by StatementParameterListItem,
# so we inline using _format_sql_value (values are floats from the embedding model).
placeholders.append(self._format_sql_value(val))
continue
elif col.name == "memory":
val = payloads[i].get("data") if payloads and i < len(payloads) else None
else:
val = payloads[i].get(col.name) if payloads and i < len(payloads) else None
if val is None:
placeholders.append("NULL")
else:
placeholders.append(f":{param_name}")
if isinstance(val, dict):
val = json.dumps(val)
# Use explicit type for TIMESTAMP columns so Databricks doesn't
# rely on implicit STRING→TIMESTAMP casting.
param_type = "TIMESTAMP" if col.type_name == ColumnTypeName.TIMESTAMP else None
params.append(StatementParameterListItem(name=param_name, value=str(val), type=param_type))
value_tuples.append(f"({', '.join(placeholders)})")
values.append(val)
formatted = [self._format_sql_value(v) for v in values]
value_tuples.append(f"({', '.join(formatted)})")
insert_sql = f"INSERT INTO {self.fully_qualified_table_name} ({', '.join(self.column_names)}) VALUES {', '.join(value_tuples)}"
# Execute the insert
try:
response = self.client.statement_execution.execute_statement(
statement=insert_sql,
warehouse_id=self.warehouse_id,
wait_timeout="30s",
parameters=params,
statement=insert_sql, warehouse_id=self.warehouse_id, wait_timeout="30s"
)
if response.status.state.value == "SUCCEEDED":
logger.info(
@@ -462,29 +439,29 @@ class Databricks(VectorStoreBase):
try:
filters_json = json.dumps(filters) if filters else None
# Choose query mode per Databricks SDK contract:
# - query_text: for Delta Sync Index with model endpoint
# - query_vector: for Direct Access Index and Delta Sync Index with self-managed vectors
query_kwargs = {
"index_name": self.fully_qualified_index_name,
"columns": self.column_names,
"num_results": limit,
"query_type": self.query_type,
"filters_json": filters_json,
}
uses_model_endpoint = (
self.index_type == VectorIndexType.DELTA_SYNC and self.embedding_model_endpoint_name
)
if uses_model_endpoint:
if not query:
raise ValueError("Query text is required for Delta Sync Index with model endpoint.")
query_kwargs["query_text"] = query
elif vectors:
query_kwargs["query_vector"] = vectors
# Choose query type
if self.index_type == VectorIndexType.DELTA_SYNC and query:
# Text-based search
sdk_results = self.client.vector_search_indexes.query_index(
index_name=self.fully_qualified_index_name,
columns=self.column_names,
query_text=query,
num_results=limit,
query_type=self.query_type,
filters_json=filters_json,
)
elif self.index_type == VectorIndexType.DIRECT_ACCESS and vectors:
# Vector-based search
sdk_results = self.client.vector_search_indexes.query_index(
index_name=self.fully_qualified_index_name,
columns=self.column_names,
query_vector=vectors,
num_results=limit,
query_type=self.query_type,
filters_json=filters_json,
)
else:
raise ValueError("Must provide vectors for search.")
sdk_results = self.client.vector_search_indexes.query_index(**query_kwargs)
raise ValueError("Must provide query text for DELTA_SYNC or vectors for DIRECT_ACCESS.")
# Parse results
result_data = sdk_results.result if hasattr(sdk_results, "result") else sdk_results
@@ -517,13 +494,10 @@ class Databricks(VectorStoreBase):
try:
logger.info(f"Deleting vector with ID {vector_id} from Delta table {self.fully_qualified_table_name}")
delete_sql = f"DELETE FROM {self.fully_qualified_table_name} WHERE memory_id = :vector_id"
delete_sql = f"DELETE FROM {self.fully_qualified_table_name} WHERE memory_id = '{vector_id}'"
response = self.client.statement_execution.execute_statement(
statement=delete_sql,
warehouse_id=self.warehouse_id,
wait_timeout="30s",
parameters=[StatementParameterListItem(name="vector_id", value=str(vector_id))],
statement=delete_sql, warehouse_id=self.warehouse_id, wait_timeout="30s"
)
if response.status.state.value == "SUCCEEDED":
@@ -545,8 +519,8 @@ class Databricks(VectorStoreBase):
payload (dict, optional): New payload data.
"""
update_sql = f"UPDATE {self.fully_qualified_table_name} SET "
set_clauses = []
params = []
if not vector_id:
logger.error("vector_id is required for update operation")
return
@@ -554,38 +528,25 @@ class Databricks(VectorStoreBase):
if not isinstance(vector, list):
logger.error("vector must be a list of float values")
return
# Vectors are numeric arrays — safe to inline since StatementParameterListItem
# doesn't support ARRAY types, and values are validated as list of floats above.
# Use array() SQL syntax, not Python list repr which is invalid Databricks SQL.
set_clauses.append(f"embedding = {self._format_sql_value(vector)}")
set_clauses.append(f"embedding = {vector}")
if payload:
if not isinstance(payload, dict):
logger.error("payload must be a dictionary")
return
for key, value in payload.items():
if key not in excluded_keys:
if not _VALID_SQL_IDENTIFIER.match(key):
logger.warning(f"Skipping invalid column name in payload: {key!r}")
continue
param_name = f"payload_{key}"
set_clauses.append(f"{key} = :{param_name}")
params.append(StatementParameterListItem(name=param_name, value=str(value)))
set_clauses.append(f"{key} = '{value}'")
if not set_clauses:
logger.error("No fields to update")
return
update_sql = f"UPDATE {self.fully_qualified_table_name} SET "
update_sql += ", ".join(set_clauses)
update_sql += " WHERE memory_id = :vector_id"
params.append(StatementParameterListItem(name="vector_id", value=str(vector_id)))
update_sql += f" WHERE memory_id = '{vector_id}'"
try:
logger.info(f"Updating vector with ID {vector_id} in Delta table {self.fully_qualified_table_name}")
response = self.client.statement_execution.execute_statement(
statement=update_sql,
warehouse_id=self.warehouse_id,
wait_timeout="30s",
parameters=params,
statement=update_sql, warehouse_id=self.warehouse_id, wait_timeout="30s"
)
if response.status.state.value == "SUCCEEDED":
@@ -611,23 +572,14 @@ class Databricks(VectorStoreBase):
filters = {"memory_id": vector_id}
filters_json = json.dumps(filters)
# Use query_text for Delta Sync with model endpoint, query_vector otherwise
query_kwargs = {
"index_name": self.fully_qualified_index_name,
"columns": self.column_names,
"num_results": 1,
"query_type": self.query_type,
"filters_json": filters_json,
}
uses_model_endpoint = (
self.index_type == VectorIndexType.DELTA_SYNC and self.embedding_model_endpoint_name
results = self.client.vector_search_indexes.query_index(
index_name=self.fully_qualified_index_name,
columns=self.column_names,
query_text=" ", # Empty query, rely on filters
num_results=1,
query_type=self.query_type,
filters_json=filters_json,
)
if uses_model_endpoint:
query_kwargs["query_text"] = " "
else:
query_kwargs["query_vector"] = [0.0] * self.embedding_dimension
results = self.client.vector_search_indexes.query_index(**query_kwargs)
# Process results
result_data = results.result if hasattr(results, "result") else results
@@ -637,7 +589,7 @@ class Databricks(VectorStoreBase):
raise KeyError(f"Vector with ID {vector_id} not found")
result = data_array[0]
columns = [col.name for col in results.manifest.columns] if results.manifest and results.manifest.columns else []
columns = columns = [col.name for col in results.manifest.columns] if results.manifest and results.manifest.columns else []
row_data = dict(zip(columns, result))
# Build payload following the standard schema
@@ -734,23 +686,14 @@ class Databricks(VectorStoreBase):
filters_json = json.dumps(filters) if filters else None
num_results = limit or 100
columns = self.column_names
# Use query_text for Delta Sync with model endpoint, query_vector otherwise
query_kwargs = {
"index_name": self.fully_qualified_index_name,
"columns": columns,
"num_results": num_results,
"query_type": self.query_type,
"filters_json": filters_json,
}
uses_model_endpoint = (
self.index_type == VectorIndexType.DELTA_SYNC and self.embedding_model_endpoint_name
sdk_results = self.client.vector_search_indexes.query_index(
index_name=self.fully_qualified_index_name,
columns=columns,
query_text=" ",
num_results=num_results,
query_type=self.query_type,
filters_json=filters_json,
)
if uses_model_endpoint:
query_kwargs["query_text"] = " "
else:
query_kwargs["query_vector"] = [0.0] * self.embedding_dimension
sdk_results = self.client.vector_search_indexes.query_index(**query_kwargs)
result_data = sdk_results.result if hasattr(sdk_results, "result") else sdk_results
data_array = result_data.data_array if hasattr(result_data, "data_array") else []
+13 -13
View File
@@ -26,7 +26,7 @@ class OutputData(BaseModel):
class MongoDB(VectorStoreBase):
VECTOR_TYPE = "vector"
VECTOR_TYPE = "knnVector"
SIMILARITY_METRIC = "cosine"
def __init__(self, db_name: str, collection_name: str, embedding_model_dims: int, mongo_uri: str):
@@ -69,16 +69,17 @@ class MongoDB(VectorStoreBase):
else:
search_index_model = SearchIndexModel(
name=self.index_name,
type="vectorSearch",
definition={
"fields": [
{
"type": self.VECTOR_TYPE,
"path": "embedding",
"numDimensions": self.embedding_model_dims,
"similarity": self.SIMILARITY_METRIC,
}
]
"mappings": {
"dynamic": False,
"fields": {
"embedding": {
"type": self.VECTOR_TYPE,
"dimensions": self.embedding_model_dims,
"similarity": self.SIMILARITY_METRIC,
}
},
}
},
)
collection.create_search_index(search_index_model)
@@ -140,7 +141,7 @@ class MongoDB(VectorStoreBase):
"$vectorSearch": {
"index": self.index_name,
"limit": limit,
"numCandidates": min(limit * 20, 10000),
"numCandidates": limit,
"queryVector": vectors,
"path": "embedding",
}
@@ -197,8 +198,7 @@ class MongoDB(VectorStoreBase):
if vector is not None:
update_fields["embedding"] = vector
if payload is not None:
for key, value in payload.items():
update_fields[f"payload.{key}"] = value
update_fields["payload"] = payload
if update_fields:
try:
+6 -2
View File
@@ -1,4 +1,6 @@
import logging
import os
import shutil
from typing import Optional
from qdrant_client import QdrantClient
@@ -46,8 +48,7 @@ class Qdrant(VectorStoreBase):
path (str, optional): Path for local Qdrant database. Defaults to None.
url (str, optional): Full URL for Qdrant server. Defaults to None.
api_key (str, optional): API key for Qdrant server. Defaults to None.
on_disk (bool, optional): Enables persistent storage. Vectors are stored on disk (True) or in memory (False).
Does not delete the local database path. Defaults to False.
on_disk (bool, optional): Enables persistent storage. Defaults to False.
"""
if client:
self.client = client
@@ -65,6 +66,9 @@ class Qdrant(VectorStoreBase):
if not params:
params["path"] = path
self.is_local = True
if not on_disk:
if os.path.exists(path) and os.path.isdir(path):
shutil.rmtree(path)
else:
self.is_local = False
-9
View File
@@ -2,15 +2,6 @@
All notable changes to the `@mem0/openclaw-mem0` plugin will be documented in this file.
## [0.4.1] - 2026-03-26
### Added
- **Improved extraction quality**: Enhanced noise filtering, deduplication, and better extraction instructions for higher-quality memory capture (#4302)
### Fixed
- **Credential detection in extraction**: Improved detection of credentials, API keys, and secrets in extraction instructions to prevent them from being stored as memories (#4552)
- **Standalone timestamp extraction**: Prevented extraction of standalone timestamps as memories when no meaningful content accompanies them (#4550)
## [0.4.0] - 2026-03-16
### Added
+1 -4
View File
@@ -111,15 +111,12 @@ LANGUAGE:
- If the user speaks Spanish, store the memory in Spanish; do not translate
Exclude (NEVER store):
- Passwords, API keys, tokens, secrets, or any credentials — even when embedded in configuration blocks, setup logs, or tool output. This includes strings starting with sk-, m0-, ak_, ghp_, bot tokens (digits followed by colon and alphanumeric string), bearer tokens, webhook URLs containing tokens, pairing codes, and any long alphanumeric strings that appear in config/env contexts. Never include the actual secret value in a memory. Instead, record that the credential was configured:
WRONG: "User's API key is sk-abc123..." or "Bot token is 12345:AABcd..."
RIGHT: "API key was configured for the service (as of YYYY-MM-DD)" or "Telegram bot token was set up"
- Passwords, API keys, tokens, secrets, or any credentials — even if shared in conversation. Instead store: "Tavily API key was configured and saved to .env (as of 2026-02-20)"
- One-time commands or instructions ("stop the script", "continue where you left off")
- Acknowledgments or emotional reactions ("ok", "sounds good", "you're right", "sir")
- Transient UI/navigation states ("user is in the admin panel", "relay is attached")
- Ephemeral process status ("download at 50%", "daemon not running", "still syncing")
- Cron heartbeat outputs, NO_REPLY responses, compaction flush directives
- The current date/time as a standalone fact — timestamps are conversation context, not durable knowledge. "User indicates current time is 3:25 PM" is NEVER worth storing. However, DO use timestamps to anchor other facts: "User installed Ollama on 2026-03-21" is correct.
- System routing metadata (message IDs, sender IDs, channel routing info)
- Generic small talk with no informational content
- Raw code snippets (capture the intent/decision, not the code itself)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@mem0/openclaw-mem0",
"version": "0.4.1",
"version": "0.4.0",
"type": "module",
"description": "Mem0 memory backend for OpenClaw — platform or self-hosted open-source",
"license": "Apache-2.0",
+5 -87
View File
@@ -21,8 +21,6 @@ import json
import logging
import uuid
import anyio
from app.database import SessionLocal
from app.models import Memory, MemoryAccessLog, MemoryState, MemoryStatusHistory
from app.utils.db import get_user_and_app
@@ -33,8 +31,6 @@ from fastapi import FastAPI, Request
from fastapi.routing import APIRouter
from mcp.server.fastmcp import FastMCP
from mcp.server.sse import SseServerTransport
from mcp.server.streamable_http import StreamableHTTPServerTransport
from starlette.responses import Response
# Load environment variables
load_dotenv()
@@ -61,8 +57,8 @@ mcp_router = APIRouter(prefix="/mcp")
# Initialize SSE transport
sse = SseServerTransport("/mcp/messages/")
@mcp.tool(description="Add a new memory. This method is called everytime the user informs anything about themselves, their preferences, or anything that has any relevant information which can be useful in the future conversation. This can also be called when the user asks you to remember something. Set infer to False to store the memory verbatim without LLM fact extraction.")
async def add_memories(text: str, infer: bool = True) -> str:
@mcp.tool(description="Add a new memory. This method is called everytime the user informs anything about themselves, their preferences, or anything that has any relevant information which can be useful in the future conversation. This can also be called when the user asks you to remember something.")
async def add_memories(text: str) -> str:
uid = user_id_var.get(None)
client_name = client_name_var.get(None)
@@ -91,8 +87,7 @@ async def add_memories(text: str, infer: bool = True) -> str:
metadata={
"source_app": "openmemory",
"mcp_client": client_name,
},
infer=infer)
})
# Process the response and update database
if isinstance(response, dict) and 'results' in response:
@@ -189,7 +184,7 @@ async def search_memory(query: str) -> str:
for h in hits:
# All vector db search functions return OutputData class
id, score, payload = h.id, h.score, h.payload
if allowed and (h.id is None or h.id not in allowed):
if allowed and h.id is None or h.id not in allowed:
continue
results.append({
@@ -442,10 +437,7 @@ async def handle_sse(request: Request):
client_token = client_name_var.set(client_name or "")
try:
# NOTE: request._send is the raw ASGI `send` callable. Starlette does not
# expose it publicly, but the MCP SDK transports require the raw ASGI
# interface (scope, receive, send). This is the standard pattern from the
# MCP Python SDK examples.
# Handle SSE connection
async with sse.connect_sse(
request.scope,
request.receive,
@@ -492,80 +484,6 @@ async def handle_post_message(request: Request):
finally:
pass
@mcp_router.api_route("/{client_name}/http/{user_id}", methods=["POST", "GET", "DELETE"])
async def handle_streamable_http(request: Request):
"""Handle Streamable HTTP connections for a specific user and client.
Uses the Streamable HTTP transport (MCP spec 2025-03-26+) which replaces
the deprecated SSE transport. Runs in stateless mode — each request is
handled independently with no persistent session.
The transport writes its response directly to the ASGI ``send`` callable.
We intercept it via ``capture_send`` so we can return a proper ``Response``
to FastAPI — otherwise FastAPI would also try to send its own response,
causing a "double-response" bug.
"""
uid = request.path_params.get("user_id")
user_token = user_id_var.set(uid or "")
client_name = request.path_params.get("client_name")
client_token = client_name_var.set(client_name or "")
# Intercept the ASGI messages the transport sends so we can return them
# as a single Response to FastAPI. Without this, FastAPI would attempt to
# write its own response after the transport already wrote one.
response_started = False
response_status = 200
response_headers: list[tuple[bytes, bytes]] = []
response_body = bytearray()
async def capture_send(message):
nonlocal response_started, response_status
if message["type"] == "http.response.start":
response_started = True
response_status = message["status"]
response_headers.extend(message.get("headers", []))
elif message["type"] == "http.response.body":
response_body.extend(message.get("body", b""))
try:
transport = StreamableHTTPServerTransport(
mcp_session_id=None,
is_json_response_enabled=True,
)
async with anyio.create_task_group() as tg:
async def run_server(*, task_status=anyio.TASK_STATUS_IGNORED):
async with transport.connect() as (read_stream, write_stream):
task_status.started()
await mcp._mcp_server.run(
read_stream,
write_stream,
mcp._mcp_server.create_initialization_options(),
stateless=True,
)
await tg.start(run_server)
await transport.handle_request(request.scope, request.receive, capture_send)
await transport.terminate()
tg.cancel_scope.cancel()
finally:
user_id_var.reset(user_token)
client_name_var.reset(client_token)
if not response_started:
return Response(status_code=500, content=b"Transport did not produce a response")
# Header dict conversion is safe here: the MCP transport in stateless JSON
# mode only emits single-valued headers (Content-Type, Content-Length).
return Response(
content=bytes(response_body),
status_code=response_status,
headers={k.decode(): v.decode() for k, v in response_headers},
)
def setup_mcp_server(app: FastAPI):
"""Setup MCP server with the FastAPI application"""
mcp._mcp_server.name = "mem0-mcp-server"
View File
-396
View File
@@ -1,396 +0,0 @@
"""Tests for the MCP server endpoints (SSE and Streamable HTTP transports).
Covers the Streamable HTTP transport (MCP spec 2025-03-26+) and the legacy SSE
transport. Tests exercise the full JSON-RPC flow — initialize, tools/list,
tools/call — as well as error handling and context-variable isolation.
"""
import os
# Set dummy keys before any imports that trigger client initialization
os.environ.setdefault("OPENAI_API_KEY", "test-key")
import pytest
import pytest_asyncio
from httpx import ASGITransport, AsyncClient
from app.mcp_server import client_name_var, mcp, mcp_router, user_id_var
# MCP Streamable HTTP requires the Accept header to include application/json.
# Including text/event-stream as well satisfies GET (SSE) requests.
MCP_HEADERS = {"Accept": "application/json, text/event-stream"}
# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------
@pytest.fixture
def test_app():
"""Create a minimal FastAPI app with just the MCP router for testing."""
from fastapi import FastAPI
app = FastAPI()
app.include_router(mcp_router)
return app
@pytest_asyncio.fixture
async def client(test_app):
"""Async HTTP client wired to the test app via ASGI transport."""
transport = ASGITransport(app=test_app)
async with AsyncClient(transport=transport, base_url="http://test") as ac:
yield ac
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _jsonrpc(method: str, params: dict | None = None, req_id: int = 1) -> dict:
"""Build a JSON-RPC 2.0 request envelope."""
return {
"jsonrpc": "2.0",
"id": req_id,
"method": method,
"params": params or {},
}
def _initialize_payload(req_id: int = 1) -> dict:
return _jsonrpc(
"initialize",
{
"protocolVersion": "2025-03-26",
"capabilities": {},
"clientInfo": {"name": "test-client", "version": "0.1.0"},
},
req_id=req_id,
)
# ---------------------------------------------------------------------------
# Streamable HTTP — route existence & basic protocol
# ---------------------------------------------------------------------------
class TestStreamableHTTPBasic:
"""Verify the Streamable HTTP route is registered and responds."""
@pytest.mark.asyncio
async def test_post_initialize(self, client):
"""POST initialize should return a valid JSON-RPC result."""
resp = await client.post(
"/mcp/testclient/http/user1",
json=_initialize_payload(),
headers=MCP_HEADERS,
)
assert resp.status_code == 200
data = resp.json()
assert data["jsonrpc"] == "2.0"
assert data["id"] == 1
assert "result" in data
result = data["result"]
assert "serverInfo" in result
assert "capabilities" in result
assert result["protocolVersion"] == "2025-03-26"
@pytest.mark.asyncio
async def test_delete_returns_method_not_allowed(self, client):
"""DELETE in stateless mode should return 405 (no session to terminate)."""
resp = await client.delete(
"/mcp/testclient/http/user1",
headers=MCP_HEADERS,
)
assert resp.status_code == 405
@pytest.mark.asyncio
async def test_missing_accept_header_returns_406(self, client):
"""POST without the required Accept header should return 406."""
resp = await client.post(
"/mcp/testclient/http/user1",
json=_initialize_payload(),
)
assert resp.status_code == 406
@pytest.mark.asyncio
async def test_invalid_json_returns_400(self, client):
"""POST with unparseable body should return 400."""
resp = await client.post(
"/mcp/testclient/http/user1",
content=b"not json",
headers={**MCP_HEADERS, "Content-Type": "application/json"},
)
assert resp.status_code == 400
@pytest.mark.asyncio
async def test_route_not_found_for_wrong_path(self, client):
"""Requests to a non-existent path should 404."""
resp = await client.post(
"/mcp/testclient/nonexistent/user1",
json=_initialize_payload(),
headers=MCP_HEADERS,
)
assert resp.status_code == 404
# ---------------------------------------------------------------------------
# Streamable HTTP — full protocol flow
# ---------------------------------------------------------------------------
class TestStreamableHTTPProtocol:
"""End-to-end JSON-RPC flows over Streamable HTTP."""
@pytest.mark.asyncio
async def test_tools_list(self, client):
"""tools/list should return all registered MCP tools."""
init_resp = await client.post(
"/mcp/testclient/http/user1",
json=_initialize_payload(),
headers=MCP_HEADERS,
)
assert init_resp.status_code == 200
resp = await client.post(
"/mcp/testclient/http/user1",
json=_jsonrpc("tools/list", req_id=2),
headers=MCP_HEADERS,
)
assert resp.status_code == 200
data = resp.json()
assert "result" in data
tool_names = {t["name"] for t in data["result"]["tools"]}
expected = {"add_memories", "search_memory", "list_memories",
"delete_memories", "delete_all_memories"}
assert expected.issubset(tool_names), f"Missing tools: {expected - tool_names}"
@pytest.mark.asyncio
async def test_tools_list_has_descriptions(self, client):
"""Every tool returned by tools/list should have a non-empty description."""
await client.post(
"/mcp/testclient/http/user1",
json=_initialize_payload(),
headers=MCP_HEADERS,
)
resp = await client.post(
"/mcp/testclient/http/user1",
json=_jsonrpc("tools/list", req_id=2),
headers=MCP_HEADERS,
)
for tool in resp.json()["result"]["tools"]:
assert tool.get("description"), f"Tool {tool['name']} has no description"
@pytest.mark.asyncio
async def test_tools_list_has_input_schemas(self, client):
"""Every tool should declare an inputSchema."""
await client.post(
"/mcp/testclient/http/user1",
json=_initialize_payload(),
headers=MCP_HEADERS,
)
resp = await client.post(
"/mcp/testclient/http/user1",
json=_jsonrpc("tools/list", req_id=2),
headers=MCP_HEADERS,
)
for tool in resp.json()["result"]["tools"]:
assert "inputSchema" in tool, f"Tool {tool['name']} missing inputSchema"
@pytest.mark.asyncio
async def test_call_unknown_tool_returns_error(self, client):
"""Calling a non-existent tool should return a JSON-RPC error."""
await client.post(
"/mcp/testclient/http/user1",
json=_initialize_payload(),
headers=MCP_HEADERS,
)
resp = await client.post(
"/mcp/testclient/http/user1",
json=_jsonrpc("tools/call", {"name": "no_such_tool", "arguments": {}}, req_id=2),
headers=MCP_HEADERS,
)
assert resp.status_code == 200
data = resp.json()
assert "error" in data or (
"result" in data and data["result"].get("isError")
)
@pytest.mark.asyncio
async def test_unknown_jsonrpc_method(self, client):
"""An unknown JSON-RPC method should return an error."""
resp = await client.post(
"/mcp/testclient/http/user1",
json=_jsonrpc("nonexistent/method"),
headers=MCP_HEADERS,
)
assert resp.status_code in (200, 400)
@pytest.mark.asyncio
async def test_response_content_type_is_json(self, client):
"""Responses should have Content-Type: application/json."""
resp = await client.post(
"/mcp/testclient/http/user1",
json=_initialize_payload(),
headers=MCP_HEADERS,
)
ct = resp.headers.get("content-type", "")
assert "application/json" in ct
# ---------------------------------------------------------------------------
# Streamable HTTP — context variable handling
# ---------------------------------------------------------------------------
class TestStreamableHTTPContext:
"""Verify that user_id and client_name context variables are set correctly."""
@pytest.mark.asyncio
async def test_context_vars_set_during_tool_call(self, client):
"""Context vars should reflect the path parameters during tool execution."""
captured = {}
@mcp.tool(name="__test_ctx", description="test only")
async def _capture(query: str = "") -> str:
captured["user_id"] = user_id_var.get(None)
captured["client_name"] = client_name_var.get(None)
return "ok"
try:
await client.post(
"/mcp/my-app/http/alice",
json=_initialize_payload(),
headers=MCP_HEADERS,
)
resp = await client.post(
"/mcp/my-app/http/alice",
json=_jsonrpc("tools/call", {"name": "__test_ctx", "arguments": {}}, req_id=2),
headers=MCP_HEADERS,
)
assert resp.status_code == 200
assert captured.get("user_id") == "alice"
assert captured.get("client_name") == "my-app"
finally:
mcp._tool_manager._tools.pop("__test_ctx", None)
@pytest.mark.asyncio
async def test_different_users_are_isolated(self, client):
"""Sequential requests with different user_ids must not leak state."""
results = []
@mcp.tool(name="__test_uid_iso", description="test only")
async def _capture_uid(query: str = "") -> str:
results.append(user_id_var.get(None))
return "ok"
try:
for uid in ("userA", "userB", "userC"):
await client.post(
f"/mcp/app1/http/{uid}",
json=_initialize_payload(),
headers=MCP_HEADERS,
)
await client.post(
f"/mcp/app1/http/{uid}",
json=_jsonrpc("tools/call", {"name": "__test_uid_iso", "arguments": {}}, req_id=2),
headers=MCP_HEADERS,
)
assert results == ["userA", "userB", "userC"]
finally:
mcp._tool_manager._tools.pop("__test_uid_iso", None)
@pytest.mark.asyncio
async def test_different_clients_are_isolated(self, client):
"""Sequential requests with different client_names must not leak state."""
results = []
@mcp.tool(name="__test_cn_iso", description="test only")
async def _capture_cn(query: str = "") -> str:
results.append(client_name_var.get(None))
return "ok"
try:
for cn in ("cursor", "windsurf", "claude"):
await client.post(
f"/mcp/{cn}/http/user1",
json=_initialize_payload(),
headers=MCP_HEADERS,
)
await client.post(
f"/mcp/{cn}/http/user1",
json=_jsonrpc("tools/call", {"name": "__test_cn_iso", "arguments": {}}, req_id=2),
headers=MCP_HEADERS,
)
assert results == ["cursor", "windsurf", "claude"]
finally:
mcp._tool_manager._tools.pop("__test_cn_iso", None)
# ---------------------------------------------------------------------------
# Streamable HTTP — response correctness
# ---------------------------------------------------------------------------
class TestStreamableHTTPResponses:
"""Verify that captured responses are returned correctly to the caller."""
@pytest.mark.asyncio
async def test_error_status_codes_are_preserved(self, client):
"""Transport error codes (e.g. 406) must be forwarded, not masked as 200."""
resp = await client.post(
"/mcp/testclient/http/user1",
json=_initialize_payload(),
)
assert resp.status_code == 406
@pytest.mark.asyncio
async def test_delete_status_code_preserved(self, client):
"""DELETE 405 from stateless transport must not be masked."""
resp = await client.delete(
"/mcp/testclient/http/user1",
headers=MCP_HEADERS,
)
assert resp.status_code == 405
@pytest.mark.asyncio
async def test_multiple_sequential_requests(self, client):
"""Multiple requests in sequence should each get independent responses."""
for i in range(5):
resp = await client.post(
"/mcp/testclient/http/user1",
json=_initialize_payload(req_id=i + 1),
headers=MCP_HEADERS,
)
assert resp.status_code == 200
data = resp.json()
assert data["id"] == i + 1
assert "result" in data
@pytest.mark.asyncio
async def test_wrong_content_type_returns_error(self, client):
"""POST with wrong Content-Type should return an error status."""
resp = await client.post(
"/mcp/testclient/http/user1",
content=b'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}',
headers={**MCP_HEADERS, "Content-Type": "text/plain"},
)
assert resp.status_code in (400, 415)
# ---------------------------------------------------------------------------
# Route registration
# ---------------------------------------------------------------------------
class TestRouteRegistration:
"""Verify all expected routes are registered in the router."""
def test_sse_route_is_registered(self, test_app):
routes = [r.path for r in test_app.routes if hasattr(r, "path")]
assert "/mcp/{client_name}/sse/{user_id}" in routes
def test_sse_post_messages_route_is_registered(self, test_app):
routes = [r.path for r in test_app.routes if hasattr(r, "path")]
assert "/mcp/messages/" in routes or "/mcp/{client_name}/sse/{user_id}/messages/" in routes
def test_streamable_http_route_is_registered(self, test_app):
routes = [r.path for r in test_app.routes if hasattr(r, "path")]
assert "/mcp/{client_name}/http/{user_id}" in routes
+2 -2
View File
@@ -4,10 +4,10 @@ build-backend = "hatchling.build"
[project]
name = "mem0ai"
version = "1.0.8"
version = "1.0.7"
description = "Long-term memory for AI Agents"
authors = [
{ name = "Mem0", email = "support@mem0.ai" }
{ name = "Mem0", email = "founders@mem0.ai" }
]
readme = "README.md"
license = "Apache-2.0"
-5
View File
@@ -119,9 +119,6 @@ class MemoryCreate(BaseModel):
agent_id: Optional[str] = None
run_id: Optional[str] = None
metadata: Optional[Dict[str, Any]] = None
infer: Optional[bool] = Field(None, description="Whether to extract facts from messages. Defaults to True.")
memory_type: Optional[str] = Field(None, description="Type of memory to store (e.g. 'core').")
prompt: Optional[str] = Field(None, description="Custom prompt to use for fact extraction.")
class SearchRequest(BaseModel):
@@ -130,8 +127,6 @@ class SearchRequest(BaseModel):
run_id: Optional[str] = None
agent_id: Optional[str] = None
filters: Optional[Dict[str, Any]] = None
limit: Optional[int] = Field(None, description="Maximum number of results to return.")
threshold: Optional[float] = Field(None, description="Minimum similarity score for results.")
@app.post("/configure", summary="Configure Mem0")
+16 -11
View File
@@ -349,18 +349,23 @@ Use the `name` field in messages to identify speakers. Mem0 maps names to entity
## MCP Integration
Model Context Protocol integration enables AI clients (Claude, Claude Code, Cursor, Windsurf, VS Code, OpenCode) to manage Mem0 memory autonomously.
Model Context Protocol integration enables AI clients (Claude Desktop, Cursor, custom agents) to manage Mem0 memory autonomously.
### Setup
### Configuration
Add Mem0 MCP to your clients with a single command:
```bash
npx mcp-add \
--name mem0-mcp \
--type http \
--url "https://mcp.mem0.ai/mcp" \
--clients "claude,claude code,cursor,windsurf,vscode,opencode"
```json
{
"mcpServers": {
"mem0": {
"command": "uvx",
"args": ["mem0-mcp-server"],
"env": {
"MEM0_API_KEY": "m0-your-api-key",
"MEM0_DEFAULT_USER_ID": "your-user-id"
}
}
}
}
```
### Available MCP Tools
@@ -372,7 +377,7 @@ The MCP server exposes 9 memory tools that AI agents can use autonomously:
### How It Works
1. Add Mem0 MCP to your AI client using the setup command above
1. Configure the MCP server in your AI client
2. The agent autonomously decides when to store/retrieve memories
3. No manual API calls needed — the agent manages memory as part of its reasoning
+4
View File
@@ -62,6 +62,10 @@ SECTION_MAP = {
"/open-source/features/rest-api",
"/open-source/configure-components",
],
"openmemory": [
"/openmemory/overview",
"/openmemory/quickstart",
],
"sdks": [
"/sdks/python",
"/sdks/js",
+4 -19
View File
@@ -24,7 +24,7 @@ def test_embed_default_model(mock_openai_client):
result = embedder.embed("Hello world")
mock_openai_client.embeddings.create.assert_called_once_with(
input=["Hello world"], model="text-embedding-3-small", encoding_format="float"
input=["Hello world"], model="text-embedding-3-small", dimensions=1536, encoding_format="float"
)
assert result == [0.1, 0.2, 0.3]
@@ -54,7 +54,7 @@ def test_embed_removes_newlines(mock_openai_client):
result = embedder.embed("Hello\nworld")
mock_openai_client.embeddings.create.assert_called_once_with(
input=["Hello world"], model="text-embedding-3-small", encoding_format="float"
input=["Hello world"], model="text-embedding-3-small", dimensions=1536, encoding_format="float"
)
assert result == [0.7, 0.8, 0.9]
@@ -69,7 +69,7 @@ def test_embed_without_api_key_env_var(mock_openai_client):
result = embedder.embed("Testing API key")
mock_openai_client.embeddings.create.assert_called_once_with(
input=["Testing API key"], model="text-embedding-3-small", encoding_format="float"
input=["Testing API key"], model="text-embedding-3-small", dimensions=1536, encoding_format="float"
)
assert result == [1.0, 1.1, 1.2]
@@ -85,7 +85,7 @@ def test_embed_uses_environment_api_key(mock_openai_client, monkeypatch):
result = embedder.embed("Environment key test")
mock_openai_client.embeddings.create.assert_called_once_with(
input=["Environment key test"], model="text-embedding-3-small", encoding_format="float"
input=["Environment key test"], model="text-embedding-3-small", dimensions=1536, encoding_format="float"
)
assert result == [1.3, 1.4, 1.5]
@@ -107,18 +107,3 @@ def test_embed_passes_encoding_format_float(mock_openai_client):
call_kwargs = mock_openai_client.embeddings.create.call_args
assert call_kwargs.kwargs.get("encoding_format") == "float" or call_kwargs[1].get("encoding_format") == "float"
def test_embed_passes_dimensions_only_when_explicit(mock_openai_client):
"""Matryoshka / truncated embeddings: dimensions sent only if user sets embedding_dims (#4153)."""
config = BaseEmbedderConfig(embedding_dims=256)
embedder = OpenAIEmbedding(config)
mock_response = Mock()
mock_response.data = [Mock(embedding=[0.1] * 256)]
mock_openai_client.embeddings.create.return_value = mock_response
embedder.embed("truncate me")
mock_openai_client.embeddings.create.assert_called_once_with(
input=["truncate me"], model="text-embedding-3-small", dimensions=256, encoding_format="float"
)

Some files were not shown because too many files have changed in this diff Show More