Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bd9d27ff50 | |||
| 08b746c9be | |||
| 693e709389 | |||
| 553e275112 | |||
| 43dde3b186 | |||
| cca7551192 | |||
| 5be2630f5b | |||
| 2549a84e5c | |||
| 34ed122ef3 | |||
| db8ac61713 | |||
| 15feaa8ac4 | |||
| 282feaebf2 | |||
| f5dc825d47 |
+6
-2
@@ -4,6 +4,10 @@ __pycache__/
|
||||
*$py.class
|
||||
**/node_modules/
|
||||
|
||||
# Self-hosted server local runtime state
|
||||
server/history/
|
||||
server/.env
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
@@ -15,8 +19,8 @@ dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
/lib/
|
||||
/lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
|
||||
@@ -1313,7 +1313,7 @@ async def delete_memory(memory_id: str):
|
||||
- **Documentation**: https://docs.mem0.ai
|
||||
- **GitHub Repository**: https://github.com/mem0ai/mem0
|
||||
- **Discord Community**: https://mem0.dev/DiG
|
||||
- **Platform**: https://app.mem0.ai
|
||||
- **Platform**: https://app.mem0.ai?utm_source=oss&utm_medium=llm
|
||||
- **Research Paper**: https://mem0.ai/research
|
||||
- **Examples**: https://github.com/mem0ai/mem0/tree/main/examples
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://mem0.ai/research"><strong>📄 Building Production-Ready AI Agents with Scalable Long-Term Memory →</strong></a>
|
||||
<a href="https://mem0.ai/research"><strong>📄 Benchmarking Mem0's token-efficient memory algorithm →</strong></a>
|
||||
</p>
|
||||
|
||||
## New Memory Algorithm (April 2026)
|
||||
@@ -85,18 +85,17 @@ See the [migration guide](https://docs.mem0.ai/migration/oss-v2-to-v3) for upgra
|
||||
|
||||
## 🚀 Quickstart Guide <a name="quickstart"></a>
|
||||
|
||||
Choose between our hosted platform or self-hosted package:
|
||||
| | Library | Self-Hosted Server | Cloud Platform |
|
||||
|---|---------|-------------------|----------------|
|
||||
| **Best for** | Testing, prototyping | Teams running on their own infrastructure | Zero-ops production use |
|
||||
| **Setup** | `pip install mem0ai` | `docker compose up` | Sign up at [app.mem0.ai](https://app.mem0.ai?utm_source=oss&utm_medium=readme) |
|
||||
| **Dashboard** | -- | [Yes](https://docs.mem0.ai/open-source/setup) | Yes |
|
||||
| **Auth & API Keys** | -- | Yes | Yes |
|
||||
| **Advanced Features** | -- | Teasers | All included |
|
||||
|
||||
### Hosted Platform
|
||||
Just testing? Use the library. Building for a team? Self-hosted. Want zero ops? Cloud.
|
||||
|
||||
Get up and running in minutes with automatic updates, analytics, and enterprise security.
|
||||
|
||||
1. Sign up on [Mem0 Platform](https://app.mem0.ai)
|
||||
2. Embed the memory layer via SDK or API keys
|
||||
|
||||
### Self-Hosted (Open Source)
|
||||
|
||||
Install the sdk via pip:
|
||||
### Library (pip / npm)
|
||||
|
||||
```bash
|
||||
pip install mem0ai
|
||||
@@ -110,10 +109,30 @@ python -m spacy download en_core_web_sm
|
||||
```
|
||||
|
||||
Install sdk via npm:
|
||||
|
||||
```bash
|
||||
npm install mem0ai
|
||||
```
|
||||
|
||||
### Self-Hosted Server
|
||||
|
||||
> **Note:** Self-hosted auth is on by default. Upgrading from a pre-auth build? Set `ADMIN_API_KEY`, register an admin through the wizard, or `AUTH_DISABLED=true` for local dev only. See [upgrade notes](https://docs.mem0.ai/open-source/setup#upgrade-notes).
|
||||
|
||||
```bash
|
||||
# Recommended: one command — start the stack, create an admin, issue the first API key.
|
||||
cd server && make bootstrap
|
||||
|
||||
# Manual: start the stack and finish setup via the browser wizard.
|
||||
cd server && docker compose up -d # http://localhost:3000
|
||||
```
|
||||
|
||||
See the [self-hosted docs](https://docs.mem0.ai/open-source/overview) for configuration.
|
||||
|
||||
### Cloud Platform
|
||||
|
||||
1. Sign up on [Mem0 Platform](https://app.mem0.ai?utm_source=oss&utm_medium=readme)
|
||||
2. Embed the memory layer via SDK or API keys
|
||||
|
||||
### CLI
|
||||
|
||||
Manage memories from your terminal:
|
||||
|
||||
@@ -96,7 +96,7 @@ export function printError(message: string, hint?: string): void {
|
||||
const resolvedHint =
|
||||
hint ??
|
||||
(message.includes("Authentication failed")
|
||||
? `Run ${brand("mem0 init")} to reconfigure your API key · https://app.mem0.ai/dashboard/api-keys`
|
||||
? `Run ${brand("mem0 init")} to reconfigure your API key · https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=cli-node`
|
||||
: undefined);
|
||||
if (resolvedHint) {
|
||||
console.error(` ${dim(resolvedHint)}`);
|
||||
|
||||
@@ -185,7 +185,7 @@ function promptLine(label: string, defaultValue?: string): Promise<string> {
|
||||
async function setupPlatform(config: Mem0Config): Promise<void> {
|
||||
console.log();
|
||||
console.log(
|
||||
` ${dim("Get your API key at https://app.mem0.ai/dashboard/api-keys")}`,
|
||||
` ${dim("Get your API key at https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=cli-node")}`,
|
||||
);
|
||||
console.log();
|
||||
|
||||
@@ -234,7 +234,7 @@ async function validatePlatform(config: Mem0Config): Promise<void> {
|
||||
} else {
|
||||
printError(
|
||||
`Could not connect: ${status.error ?? "Unknown error"}`,
|
||||
"Visit https://app.mem0.ai/dashboard/api-keys to get a new key, or run mem0 init again.",
|
||||
"Visit https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=cli-node to get a new key, or run mem0 init again.",
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
@@ -63,7 +63,7 @@ export async function cmdStatus(
|
||||
` ${dim("Run")} ${brand("mem0 init")} ${dim("to reconfigure your API key")}`,
|
||||
);
|
||||
lines.push(
|
||||
` ${dim("Get a key at")} ${brand("https://app.mem0.ai/dashboard/api-keys")}`,
|
||||
` ${dim("Get a key at")} ${brand("https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=cli-node")}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@ def timed_status(console: Console, message: str):
|
||||
if "Authentication failed" in ctx.error_msg:
|
||||
_err.print(
|
||||
f" [{DIM_COLOR}]Run [bold]mem0 init[/bold] to reconfigure your API key"
|
||||
f" · [bold]https://app.mem0.ai/dashboard/api-keys[/bold][/]"
|
||||
f" · [bold]https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=cli-python[/bold][/]"
|
||||
)
|
||||
raise
|
||||
else:
|
||||
|
||||
@@ -19,7 +19,13 @@ from mem0_cli.branding import (
|
||||
print_info,
|
||||
print_success,
|
||||
)
|
||||
from mem0_cli.config import CONFIG_FILE, DEFAULT_BASE_URL, Mem0Config, load_config, save_config
|
||||
from mem0_cli.config import (
|
||||
CONFIG_FILE,
|
||||
DEFAULT_BASE_URL,
|
||||
Mem0Config,
|
||||
load_config,
|
||||
save_config,
|
||||
)
|
||||
|
||||
console = Console()
|
||||
err_console = Console(stderr=True)
|
||||
@@ -352,7 +358,9 @@ def run_init(
|
||||
def _setup_platform(config: Mem0Config) -> None:
|
||||
"""Platform setup flow."""
|
||||
console.print()
|
||||
console.print(f" [{DIM_COLOR}]Get your API key at https://app.mem0.ai/dashboard/api-keys[/]")
|
||||
console.print(
|
||||
f" [{DIM_COLOR}]Get your API key at https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=cli-python[/]"
|
||||
)
|
||||
console.print()
|
||||
|
||||
console.print(f" [{BRAND_COLOR}]API Key[/]: ", end="")
|
||||
@@ -404,7 +412,7 @@ def _validate_platform(config: Mem0Config) -> None:
|
||||
print_error(
|
||||
err_console,
|
||||
f"Could not connect: {status.get('error', 'Unknown error')}",
|
||||
hint="Visit https://app.mem0.ai/dashboard/api-keys to get a new key, then run mem0 init again.",
|
||||
hint="Visit https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=cli-python to get a new key, then run mem0 init again.",
|
||||
)
|
||||
except Exception as e:
|
||||
print_error(err_console, f"Connection test failed: {e}")
|
||||
|
||||
@@ -77,7 +77,7 @@ def cmd_status(
|
||||
f" [{DIM_COLOR}]Run [bold]mem0 init[/bold] to reconfigure your API key[/]"
|
||||
)
|
||||
lines.append(
|
||||
f" [{DIM_COLOR}]Get a key at [bold]https://app.mem0.ai/dashboard/api-keys[/bold][/]"
|
||||
f" [{DIM_COLOR}]Get a key at [bold]https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=cli-python[/bold][/]"
|
||||
)
|
||||
lines.append(f" [{DIM_COLOR}]Latency:[/] {_elapsed:.2f}s")
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ description: "REST APIs for memory management, search, and entity operations"
|
||||
Mem0 provides a comprehensive REST API for integrating advanced memory capabilities into your applications. Create, search, update, and manage memories across users, agents, and custom entities with simple HTTP requests.
|
||||
|
||||
<Info>
|
||||
**Quick start:** Get your API key from the <a href="https://app.mem0.ai/dashboard/api-keys" rel="nofollow">Mem0 Dashboard</a> and make your first memory operation in minutes.
|
||||
**Quick start:** Get your API key from the <a href="https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=api-reference" rel="nofollow">Mem0 Dashboard</a> and make your first memory operation in minutes.
|
||||
</Info>
|
||||
|
||||
---
|
||||
@@ -87,7 +87,7 @@ All API requests require authentication using Token-based authentication. Includ
|
||||
Authorization: Token <your-api-key>
|
||||
```
|
||||
|
||||
Get your API key from the <a href="https://app.mem0.ai/dashboard/api-keys" rel="nofollow">Mem0 Dashboard</a>.
|
||||
Get your API key from the <a href="https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=api-reference" rel="nofollow">Mem0 Dashboard</a>.
|
||||
|
||||
<Warning>
|
||||
**Keep your API key secure.** Never expose it in client-side code or public repositories. Use environment variables and server-side requests only.
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
---
|
||||
title: 'Add Memories'
|
||||
description: "Add facts, messages, or metadata to a user memory store with support for async processing and event tracking."
|
||||
openapi: post /v1/memories/
|
||||
title: Add Memories
|
||||
description: "Add facts, messages, or metadata to a user memory store with async processing and event tracking via the V3 additive pipeline."
|
||||
openapi: post /v3/memories/add/
|
||||
---
|
||||
|
||||
Add new facts, messages, or metadata to a user’s memory store. The Add Memories endpoint accepts either raw text or conversational turns and commits them asynchronously so the memory is ready for later search, retrieval, and graph queries.
|
||||
Extract and store memories from a conversation using the V3 additive pipeline. The endpoint uses single-pass ADD-only extraction — one LLM call, no UPDATE/DELETE. Memories accumulate over time; nothing is overwritten.
|
||||
|
||||
## Endpoint
|
||||
|
||||
- **Method**: `POST`
|
||||
- **URL**: `/v1/memories/`
|
||||
- **URL**: `/v3/memories/add/`
|
||||
- **Content-Type**: `application/json`
|
||||
|
||||
Memories are processed asynchronously by default. The response contains queued events you can track while the platform finalizes enrichment.
|
||||
Processing is asynchronous. The response returns an `event_id` you can poll via `GET /v1/event/{event_id}/`.
|
||||
|
||||
## Required headers
|
||||
|
||||
@@ -23,7 +23,7 @@ Memories are processed asynchronously by default. The response contains queued e
|
||||
|
||||
## Request body
|
||||
|
||||
Provide at least one message or direct memory string. Most callers supply `messages` so Mem0 can infer structured memories as part of ingestion.
|
||||
Provide conversation messages for Mem0 to extract memories from. At least one entity ID (`user_id`, `agent_id`, `app_id`, or `run_id`) is required so the memory is scoped to a session. Entity IDs are accepted at the top level.
|
||||
|
||||
<CodeGroup>
|
||||
```json Basic request
|
||||
@@ -43,12 +43,15 @@ Provide at least one message or direct memory string. Most callers supply `messa
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `user_id` | string | No* | Associates the memory with a user. Provide when you want the memory scoped to a specific identity. |
|
||||
| `messages` | array | No* | Conversation turns for Mem0 to infer memories from. Each object should include `role` and `content`. |
|
||||
| `messages` | array | Yes | Conversation turns for Mem0 to extract memories from. Each object should include `role` and `content`. |
|
||||
| `user_id` | string | No* | Associates the memory with a user. |
|
||||
| `agent_id` | string | No* | Associates the memory with an agent. |
|
||||
| `run_id` | string | No* | Associates the memory with a run. |
|
||||
| `app_id` | string | No* | Associates the memory with an app. |
|
||||
| `metadata` | object | Optional | Custom key/value metadata (e.g., `{"topic": "preferences"}`). |
|
||||
| `infer` | boolean (default `true`) | Optional | Set to `false` to skip inference and store the provided text as-is. |
|
||||
|
||||
> \* Provide at least one `messages` entry to describe what you are storing. For scoped memories, include `user_id`. You can also attach `agent_id`, `app_id`, `run_id`, `project_id`, or `org_id` to refine ownership.
|
||||
> \* At least one entity ID (`user_id`, `agent_id`, `app_id`, or `run_id`) is required.
|
||||
|
||||
<Tip>
|
||||
Need more details? See [all request parameters](#body-messages) below for complete field descriptions, types, and constraints.
|
||||
@@ -56,19 +59,15 @@ Provide at least one message or direct memory string. Most callers supply `messa
|
||||
|
||||
## Response
|
||||
|
||||
Successful requests return an array of events queued for processing. Each event includes the generated memory text and an identifier you can persist for auditing.
|
||||
The request is queued for background processing. The response contains an `event_id` for tracking status.
|
||||
|
||||
<CodeGroup>
|
||||
```json 200 response
|
||||
[
|
||||
{
|
||||
"id": "mem_01JF8ZS4Y0R0SPM13R5R6H32CJ",
|
||||
"event": "ADD",
|
||||
"data": {
|
||||
"memory": "The user moved to Austin in 2025."
|
||||
}
|
||||
}
|
||||
]
|
||||
{
|
||||
"message": "Memory processing has been queued for background execution",
|
||||
"status": "PENDING",
|
||||
"event_id": "evt-uuid"
|
||||
}
|
||||
```
|
||||
|
||||
```json 400 response
|
||||
@@ -81,3 +80,7 @@ Successful requests return an array of events queued for processing. Each event
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
<Info>
|
||||
Poll the event status via `GET /v1/event/{event_id}/`. Status will be `SUCCEEDED` or `FAILED` once processing completes.
|
||||
</Info>
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
---
|
||||
title: "Get Memories"
|
||||
description: "Retrieve memories with advanced filtering using logical operators like AND, OR, NOT, and comparison queries."
|
||||
openapi: post /v2/memories/
|
||||
description: "Retrieve memories with paginated results and advanced filtering using logical operators like AND, OR, NOT, and comparison queries."
|
||||
openapi: post /v3/memories/
|
||||
---
|
||||
|
||||
The v2 get memories API is powerful and flexible, allowing for more precise memory listing without the need for a search query. It supports complex logical operations (AND, OR, NOT) and comparison operators for advanced filtering capabilities. The comparison operators include:
|
||||
List memories scoped by filters with paginated results. Entity IDs (`user_id`, `agent_id`, `app_id`, `run_id`) **must** be passed inside the `filters` object — top-level entity IDs are rejected with 400.
|
||||
|
||||
The `filters` object supports complex logical operations (AND, OR, NOT) and comparison operators:
|
||||
|
||||
- `in`: Matches any of the values specified
|
||||
- `gte`: Greater than or equal to
|
||||
@@ -15,6 +17,8 @@ The v2 get memories API is powerful and flexible, allowing for more precise memo
|
||||
- `icontains`: Case-insensitive containment check
|
||||
- `*`: Wildcard character that matches everything
|
||||
|
||||
Pass `page` and `page_size` as query parameters to paginate through results.
|
||||
|
||||
<CodeGroup>
|
||||
```python Code
|
||||
memories = client.get_all(
|
||||
@@ -27,12 +31,17 @@ memories = client.get_all(
|
||||
"created_at": {"gte": "2024-07-01", "lte": "2024-07-31"}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
page=1,
|
||||
page_size=50
|
||||
)
|
||||
```
|
||||
|
||||
```python Output
|
||||
{
|
||||
"count": 2,
|
||||
"next": null,
|
||||
"previous": null,
|
||||
"results": [
|
||||
{
|
||||
"id": "f4cbdb08-7062-4f3e-8eb2-9f5c80dfe64c",
|
||||
@@ -46,10 +55,13 @@ memories = client.get_all(
|
||||
"created_at": "2024-07-05T15:30:00Z",
|
||||
"updated_at": "2024-07-05T15:30:00Z"
|
||||
}
|
||||
],
|
||||
"total": 2
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
<Info>
|
||||
The response is a paginated envelope with `count`, `next`, `previous`, and `results`. Use `page` and `page_size` query params to step through results.
|
||||
</Info>
|
||||
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
---
|
||||
title: 'Search Memories'
|
||||
description: "Search memories with semantic queries and advanced filtering using logical and comparison operators."
|
||||
openapi: post /v2/memories/search/
|
||||
description: "Search memories with hybrid retrieval (semantic + BM25 + entity matching) and advanced filtering using logical and comparison operators."
|
||||
openapi: post /v3/memories/search/
|
||||
---
|
||||
|
||||
The v2 search API is powerful and flexible, allowing for more precise memory retrieval. It supports complex logical operations (AND, OR, NOT) and comparison operators for advanced filtering capabilities. The comparison operators include:
|
||||
Relevance-ranked hybrid search across stored memories. V3 uses multi-signal retrieval — semantic, BM25 keyword, and entity matching scored in parallel and fused. The returned `score` is a combined `[0, 1]` value.
|
||||
|
||||
Entity IDs (`user_id`, `agent_id`, `app_id`, `run_id`) **must** be passed inside the `filters` object — top-level entity IDs are rejected with 400. At least one entity ID is required.
|
||||
|
||||
The `filters` object supports complex logical operations (AND, OR, NOT) and comparison operators:
|
||||
- `in`: Matches any of the values specified
|
||||
- `gte`: Greater than or equal to
|
||||
- `lte`: Less than or equal to
|
||||
@@ -14,6 +18,14 @@ The v2 search API is powerful and flexible, allowing for more precise memory ret
|
||||
- `icontains`: Case-insensitive containment check
|
||||
- `*`: Wildcard character that matches everything
|
||||
|
||||
### Search parameter defaults
|
||||
|
||||
| Parameter | V1/V2 | V3 |
|
||||
| --- | --- | --- |
|
||||
| `top_k` | Supported (default 10) | Supported (1-1000, default 10) |
|
||||
| `threshold` | No default | Default `0.1` (pass `0.0` to disable) |
|
||||
| `rerank` | Default `true` | Default `false` (pass `true` to enable) |
|
||||
|
||||
<CodeGroup>
|
||||
```python Platform API Example
|
||||
related_memories = client.search(
|
||||
@@ -33,20 +45,19 @@ related_memories = client.search(
|
||||
|
||||
```json Output
|
||||
{
|
||||
"memories": [
|
||||
"results": [
|
||||
{
|
||||
"id": "ea925981-272f-40dd-b576-be64e4871429",
|
||||
"memory": "Likes to play cricket and plays cricket on weekends.",
|
||||
"metadata": {
|
||||
"category": "hobbies"
|
||||
},
|
||||
"score": 0.32116443111457704,
|
||||
"score": 0.82,
|
||||
"created_at": "2024-07-26T10:29:36.630547-07:00",
|
||||
"updated_at": null,
|
||||
"user_id": "alice",
|
||||
"agent_id": "sports-agent"
|
||||
"categories": ["hobbies"]
|
||||
}
|
||||
],
|
||||
]
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
@@ -4,6 +4,67 @@ description: "Release notes for the OpenClaw plugin and agent harness."
|
||||
mode: "wide"
|
||||
---
|
||||
|
||||
<Update label="2026-04-23" description="v1.0.10">
|
||||
|
||||
**Security:**
|
||||
- Telemetry `distinct_id` now uses SHA-256 instead of MD5 — prevents rainbow-table reversal of API key hashes
|
||||
- User email is now SHA-256 hashed before sending as `distinct_id` — no PII in telemetry payloads
|
||||
- Declared PostHog telemetry endpoint (`us.i.posthog.com`) in `providerEndpoints`
|
||||
|
||||
**Fixes:**
|
||||
- Fixed version-pinned install records preventing plugin updates. `ensureInstallRecord()` now detects semver-pinned specs (e.g. `@mem0/openclaw-mem0@1.0.7`) and rewrites them to `@latest` or `clawhub:` prefix so `openclaw plugins update` resolves to the newest release
|
||||
- Fixed `searchThreshold` default inconsistency: standardized to `0.3` across docs, README, and manifest
|
||||
- `PLUGIN_VERSION` now injected at build time via tsup `define` from `package.json` — no more hardcoded version strings
|
||||
|
||||
**Manifest Compliance:**
|
||||
- Removed non-spec fields: `requiredEnvVars`, `dataLocations`, `privacy`, `setup` (with `externalEndpoints`, `providers`, `requiresRuntime`, `postInstallHint`)
|
||||
- Replaced `setup.externalEndpoints` with spec-compliant `providerEndpoints` using `endpointClass` + `hosts` format
|
||||
- Env var declarations now rely solely on `providerAuthEnvVars` (already spec-compliant)
|
||||
|
||||
**Docs:**
|
||||
- Fixed `openclaw plugins update` command: uses plugin ID (`openclaw-mem0`), not npm package name (`@mem0/openclaw-mem0`)
|
||||
- Added update section to README
|
||||
- Removed redundant "Key Features" and "Conclusion" sections from integration docs
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-04-22" description="v1.0.9">
|
||||
|
||||
**Security & Compliance:**
|
||||
- Added top-level `requiredEnvVars` to plugin manifest, declaring env vars per mode (platform, OSS OpenAI, OSS Anthropic, OSS Ollama). Fixes ClaHub scanner "required env vars: none" mismatch
|
||||
- Added `sensitive: true` and descriptions to `apiKey` and `userEmail` in `configSchema` — previously only declared in `uiHints`
|
||||
- Added `default: false` with descriptions to `autoCapture` and `autoRecall` in `configSchema` so scanner can confirm opt-in defaults
|
||||
- Added `dataLocations` field to manifest declaring all persistence paths (config, vectorStore, historyDb, dreamState)
|
||||
- Added `privacy` field to manifest documenting data flow for platform vs open-source mode and credential storage guidance
|
||||
- Added `externalEndpoints` to `setup` section declaring api.mem0.ai and app.mem0.ai with purpose and requirement context
|
||||
|
||||
**Tests:**
|
||||
- Replaced direct `process.env` access in `tests/cli-commands.test.ts` and `tests/fs-safe.test.ts` with `vi.stubEnv`/`vi.unstubAllEnvs`. Fixes ClaHub static analysis flag for "environment variable access combined with network send"
|
||||
- 421 tests across 15 test files
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-04-21" description="v1.0.8">
|
||||
|
||||
**New Features:**
|
||||
- **OSS Onboarding Wizard:** New guided 4-step interactive setup for open-source mode — walks through LLM provider, embedding provider, vector store, and user ID selection with prefilled defaults
|
||||
- **Agent-Friendly CLI:** Added `--json` flag to all 16 CLI commands for machine-readable output. Agents can call `openclaw mem0 help --json` to discover every command and flag
|
||||
- **Non-Interactive OSS Setup:** Added `--mode open-source` with `--oss-llm`, `--oss-embedder`, `--oss-vector` flags for fully automated OSS configuration without prompts
|
||||
- **JSON Helpers Module:** New `cli/json-helpers.ts` with `jsonOut`, `jsonErr`, and `redactSecrets` utilities for consistent structured output
|
||||
|
||||
**Improvements:**
|
||||
- **Init Flow Redesigned:** Replaced 3-option flat menu with 2-level structure: Platform (email login or API key) and Open Source (guided wizard)
|
||||
- **Provider Selection:** LLM providers: OpenAI, Ollama, Anthropic. Embedding providers: OpenAI, Ollama. Vector stores: Qdrant, PGVector
|
||||
- **Input Prefill:** All prompts with defaults (base URL, user ID) now prefill the input field instead of showing defaults in brackets
|
||||
- **Smart Reuse:** When LLM and embedder use the same provider, API key and base URL are automatically reused from the LLM step
|
||||
- **Default Model:** Updated default LLM model to `gpt-5-mini`
|
||||
- **Manifest Compliance:** Removed undocumented fields, aligned env var declarations between SKILL.md and manifest, fixed `configSchema.required` for clean installs
|
||||
|
||||
**Tests:**
|
||||
- 404 tests across 15 test files (+3 new: `json-helpers.test.ts`, `oss-wizard.test.ts`, `cli-commands.test.ts`)
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-04-20" description="v1.0.7">
|
||||
|
||||
**New Features:**
|
||||
|
||||
@@ -7,6 +7,23 @@ mode: "wide"
|
||||
<Tabs>
|
||||
<Tab title="Python">
|
||||
|
||||
<Update label="2026-04-25" description="v2.0.1">
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Client:** Map `user_id`, `agent_id`, `run_id` entity params to filters in `GET /memories` ([#4960](https://github.com/mem0ai/mem0/pull/4960))
|
||||
- **Memory:** Honor `prompt` param in vector store extraction pipeline ([#4914](https://github.com/mem0ai/mem0/pull/4914))
|
||||
- **Memory:** Add missing `text_lemmatized` field in `AsyncMemory._create_memory` ([#4886](https://github.com/mem0ai/mem0/pull/4886))
|
||||
- **Memory:** Merge same-key operator dicts in AND metadata filters ([#4853](https://github.com/mem0ai/mem0/pull/4853))
|
||||
- **LLMs:** Narrow `_is_reasoning_model` check to not match `gpt-5.x` variants ([#4746](https://github.com/mem0ai/mem0/pull/4746))
|
||||
- **Vector Stores:** Add `ca_certs` config option for Elasticsearch vector store ([#3993](https://github.com/mem0ai/mem0/pull/3993))
|
||||
- **Vector Stores:** Add `agent_id` and `run_id` to Elasticsearch/OpenSearch default mappings ([#4906](https://github.com/mem0ai/mem0/pull/4906))
|
||||
- **Embeddings:** Set FastEmbed `embedding_dims` from model metadata at init ([#4711](https://github.com/mem0ai/mem0/pull/4711))
|
||||
|
||||
**Security:**
|
||||
- Bump vulnerable dependencies to patched versions ([#4835](https://github.com/mem0ai/mem0/pull/4835))
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-04-14" description="v2.0.0">
|
||||
|
||||
**Major Release** — Python SDK with V3 memory pipeline, ADD-only extraction, and cleaned-up API surface.
|
||||
@@ -893,6 +910,17 @@ See the [OSS v1 to v2 migration guide](https://docs.mem0.ai/migration/oss-v1-to-
|
||||
</Tab>
|
||||
|
||||
<Tab title="TypeScript">
|
||||
<Update label="2026-04-25" description="v3.0.2">
|
||||
|
||||
**Bug Fixes:**
|
||||
- **LLMs:** Forward `timeout` config to OpenAI client in JS OSS LLM providers ([#4770](https://github.com/mem0ai/mem0/pull/4770))
|
||||
|
||||
**Improvements:**
|
||||
- **Telemetry:** Harden TS telemetry version injection and require changelog entry on version bump ([#4900](https://github.com/mem0ai/mem0/pull/4900))
|
||||
- **Docs:** Update memory tool list, CLI usage, and config file reading logic ([#4861](https://github.com/mem0ai/mem0/pull/4861))
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-04-20" description="v3.0.1">
|
||||
|
||||
**Bug Fixes:**
|
||||
|
||||
@@ -45,7 +45,7 @@ Before you begin, follow these steps to set up the demo application:
|
||||
OPENAI_API_KEY=your_openai_api_key
|
||||
MEM0_API_KEY=your_mem0_api_key
|
||||
```
|
||||
You can obtain your `MEM0_API_KEY` by signing up at <a href="https://app.mem0.ai/dashboard/api-keys" rel="nofollow">Mem0 API Dashboard</a>.
|
||||
You can obtain your `MEM0_API_KEY` by signing up at <a href="https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=cookbook-companions-quickstart" rel="nofollow">Mem0 API Dashboard</a>.
|
||||
|
||||
5. Start the development server:
|
||||
```bash
|
||||
|
||||
@@ -38,7 +38,7 @@ client = MemoryClient(api_key="your-api-key")
|
||||
```
|
||||
|
||||
<Note>
|
||||
Replace `your-api-key` with your actual Mem0 API key from the <a href="https://app.mem0.ai" rel="nofollow">dashboard</a>. Without proper API authentication, memory operations will fail.
|
||||
Replace `your-api-key` with your actual Mem0 API key from the <a href="https://app.mem0.ai?utm_source=oss&utm_medium=cookbook-memory-ingestion" rel="nofollow">dashboard</a>. Without proper API authentication, memory operations will fail.
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
@@ -17,7 +17,7 @@ from mem0 import MemoryClient
|
||||
client = MemoryClient(api_key="m0-...")
|
||||
```
|
||||
|
||||
Grab an API key from the <a href="https://app.mem0.ai/" rel="nofollow">Mem0 dashboard</a> to get started.
|
||||
Grab an API key from the <a href="https://app.mem0.ai/?utm_source=oss&utm_medium=cookbook-entity-partitioning" rel="nofollow">Mem0 dashboard</a> to get started.
|
||||
|
||||
## Store and Retrieve Scoped Memories
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ client = MemoryClient(api_key="your-api-key")
|
||||
```
|
||||
|
||||
<Note>
|
||||
Your API key needs export permissions to download memory data. Check your project settings on the <a href="https://app.mem0.ai" rel="nofollow">dashboard</a> if export operations fail with authentication errors.
|
||||
Your API key needs export permissions to download memory data. Check your project settings on the <a href="https://app.mem0.ai?utm_source=oss&utm_medium=cookbook-exporting-memories" rel="nofollow">dashboard</a> if export operations fail with authentication errors.
|
||||
</Note>
|
||||
|
||||
Let's add some sample memories to work with:
|
||||
|
||||
@@ -42,7 +42,7 @@ Create a `.env` file in the root of the project and add the following (you can u
|
||||
|
||||
```bash
|
||||
# Mem0 Configuration
|
||||
MEM0_API_KEY= # Mem0 API Key (get from https://app.mem0.ai/dashboard/api-keys)
|
||||
MEM0_API_KEY= # Mem0 API Key (get from https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=cookbook-eliza-os)
|
||||
MEM0_USER_ID= # Default: eliza-os-user
|
||||
MEM0_PROVIDER= # Default: openai
|
||||
MEM0_PROVIDER_API_KEY= # API Key for the provider (OpenAI, Anthropic, etc.)
|
||||
|
||||
@@ -55,7 +55,7 @@ GEMINI_API_KEY=your-gemini-api-key-here
|
||||
```
|
||||
|
||||
<Note>
|
||||
Ensure you have your Mem0 API key from the <a href="https://app.mem0.ai" rel="nofollow">Mem0 Dashboard</a> and your Gemini API key from the [Google AI Studio](https://ai.studio/app/api-keys).
|
||||
Ensure you have your Mem0 API key from the <a href="https://app.mem0.ai?utm_source=oss&utm_medium=cookbook-gemini-3" rel="nofollow">Mem0 Dashboard</a> and your Gemini API key from the [Google AI Studio](https://ai.studio/app/api-keys).
|
||||
</Note>
|
||||
|
||||
## Gemini Memory Agent
|
||||
|
||||
@@ -41,7 +41,7 @@ Set up your environment variables:
|
||||
- `MEM0_API_KEY`: Your Mem0 Platform API key
|
||||
- `OPENAI_API_KEY`: Your OpenAI API key
|
||||
|
||||
You can obtain your Mem0 Platform API key from the <a href="https://app.mem0.ai" rel="nofollow">Mem0 Platform</a>.
|
||||
You can obtain your Mem0 Platform API key from the <a href="https://app.mem0.ai?utm_source=oss&utm_medium=cookbook-llamaindex-multiagent" rel="nofollow">Mem0 Platform</a>.
|
||||
|
||||
## Complete Implementation
|
||||
|
||||
@@ -357,7 +357,7 @@ Based on our previous session, I remember we covered Vision Language Models and
|
||||
## Help & Resources
|
||||
|
||||
- [LlamaIndex Agent Workflows](https://docs.llamaindex.ai/en/stable/use_cases/agents/)
|
||||
- <a href="https://app.mem0.ai/" rel="nofollow">Mem0 Platform</a>
|
||||
- <a href="https://app.mem0.ai/?utm_source=oss&utm_medium=cookbook-llamaindex-multiagent" rel="nofollow">Mem0 Platform</a>
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ os.environ["OPENAI_API_KEY"] = "<your-openai-api-key>"
|
||||
llm = OpenAI(model="gpt-5-mini")
|
||||
```
|
||||
|
||||
Initialize the Mem0 client. You can find your API key <a href="https://app.mem0.ai/dashboard/api-keys" rel="nofollow">here</a>. Read about Mem0 [Open Source](https://docs.mem0.ai/open-source/overview).
|
||||
Initialize the Mem0 client. You can find your API key <a href="https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=cookbook-llamaindex-react" rel="nofollow">here</a>. Read about Mem0 [Open Source](https://docs.mem0.ai/open-source/overview).
|
||||
```python
|
||||
os.environ["MEM0_API_KEY"] = "<your-mem0-api-key>"
|
||||
|
||||
|
||||
@@ -223,7 +223,7 @@ context = Mem0Context(user_id="user123")
|
||||
## Resources
|
||||
|
||||
- [Mem0 Documentation](https://docs.mem0.ai/introduction)
|
||||
- <a href="https://app.mem0.ai/dashboard" rel="nofollow">Mem0 Dashboard</a>
|
||||
- <a href="https://app.mem0.ai/dashboard?utm_source=oss&utm_medium=cookbook-agents-sdk-tool" rel="nofollow">Mem0 Dashboard</a>
|
||||
- [API Reference](https://docs.mem0.ai/api-reference)
|
||||
|
||||
---
|
||||
|
||||
@@ -23,7 +23,7 @@ MEM0_API_KEY=your_mem0_api_key
|
||||
OPENAI_API_KEY=your_openai_api_key
|
||||
```
|
||||
|
||||
Get your Mem0 API key from the <a href="https://app.mem0.ai/dashboard/api-keys" rel="nofollow">Mem0 Dashboard</a>.
|
||||
Get your Mem0 API key from the <a href="https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=cookbook-openai-tool-calls" rel="nofollow">Mem0 Dashboard</a>.
|
||||
|
||||
### Configuration
|
||||
|
||||
@@ -303,7 +303,7 @@ run().catch(console.error);
|
||||
## Resources
|
||||
|
||||
- [Mem0 Documentation](https://docs.mem0.ai/introduction)
|
||||
- <a href="https://app.mem0.ai/dashboard" rel="nofollow">Mem0 Dashboard</a>
|
||||
- <a href="https://app.mem0.ai/dashboard?utm_source=oss&utm_medium=cookbook-openai-tool-calls" rel="nofollow">Mem0 Dashboard</a>
|
||||
- [API Reference](https://docs.mem0.ai/api-reference)
|
||||
- [OpenAI Documentation](https://platform.openai.com/docs)
|
||||
|
||||
|
||||
+3
-2
@@ -153,6 +153,7 @@
|
||||
"icon": "rocket",
|
||||
"pages": [
|
||||
"open-source/overview",
|
||||
"open-source/setup",
|
||||
"vibecoding",
|
||||
"open-source/python-quickstart",
|
||||
"open-source/node-quickstart"
|
||||
@@ -578,7 +579,7 @@
|
||||
"primary": {
|
||||
"type": "button",
|
||||
"label": "Your Dashboard",
|
||||
"href": "https://app.mem0.ai"
|
||||
"href": "https://app.mem0.ai?utm_source=oss&utm_medium=docs-nav"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
@@ -608,7 +609,7 @@
|
||||
"title": "Try in Playground",
|
||||
"description": "Open this example in the interactive Mem0 playground",
|
||||
"icon": "play",
|
||||
"href": "https://app.mem0.ai/playground"
|
||||
"href": "https://app.mem0.ai/playground?utm_source=oss&utm_medium=docs-nav"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 293 KiB After Width: | Height: | Size: 139 KiB |
@@ -24,7 +24,7 @@ pip install mem0ai agentops python-dotenv
|
||||
2. Valid API keys:
|
||||
- [AgentOps API Key](https://app.agentops.ai/dashboard/api-keys)
|
||||
- OpenAI API Key (for LLM operations)
|
||||
- <a href="https://app.mem0.ai/dashboard/api-keys" rel="nofollow">Mem0 API Key</a> (optional, for cloud operations)
|
||||
- <a href="https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=integration-agentops" rel="nofollow">Mem0 API Key</a> (optional, for cloud operations)
|
||||
|
||||
## Basic Integration Example
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ pip install agno mem0ai python-dotenv
|
||||
```
|
||||
|
||||
2. Valid API keys:
|
||||
- <a href="https://app.mem0.ai/dashboard/api-keys" rel="nofollow">Mem0 API Key</a>
|
||||
- <a href="https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=integration-agno" rel="nofollow">Mem0 API Key</a>
|
||||
- OpenAI API Key (for the agent model)
|
||||
|
||||
## Quick Integration (Using `Mem0Tools`)
|
||||
|
||||
@@ -19,7 +19,7 @@ pip install autogen mem0ai openai python-dotenv
|
||||
|
||||
First, we'll import the necessary libraries and set up our configurations.
|
||||
|
||||
<Note>Remember to get the Mem0 API key from <a href="https://app.mem0.ai" rel="nofollow">Mem0 Platform</a>.</Note>
|
||||
<Note>Remember to get the Mem0 API key from <a href="https://app.mem0.ai?utm_source=oss&utm_medium=integration-autogen" rel="nofollow">Mem0 Platform</a>.</Note>
|
||||
|
||||
```python
|
||||
import os
|
||||
@@ -32,7 +32,7 @@ load_dotenv()
|
||||
|
||||
# Configuration
|
||||
# OPENAI_API_KEY = 'sk-xxx' # Replace with your actual OpenAI API key
|
||||
# MEM0_API_KEY = 'your-mem0-key' # Replace with your actual Mem0 API key from https://app.mem0.ai
|
||||
# MEM0_API_KEY = 'your-mem0-key' # Replace with your actual Mem0 API key from https://app.mem0.ai?utm_source=oss&utm_medium=integration-autogen
|
||||
USER_ID = "alice"
|
||||
|
||||
# Set up OpenAI API key
|
||||
|
||||
@@ -18,7 +18,7 @@ In this guide, you'll:
|
||||
- **Python 3.12+**
|
||||
- **[uv](https://docs.astral.sh/uv/)** — Python package manager
|
||||
- **Node.js 18+** and **npm** — only needed if using the web console
|
||||
- A **Mem0 API key** from <a href="https://app.mem0.ai" rel="nofollow">app.mem0.ai</a>
|
||||
- A **Mem0 API key** from <a href="https://app.mem0.ai?utm_source=oss&utm_medium=integration-chatdev" rel="nofollow">app.mem0.ai</a>
|
||||
- An **OpenAI API key** (or another LLM provider supported by ChatDev)
|
||||
|
||||
## Setup and Configuration
|
||||
@@ -39,7 +39,7 @@ cd frontend && npm install && cd ..
|
||||
|
||||
Set up your environment variables in a `.env` file:
|
||||
|
||||
<Note>Get your Mem0 API key from <a href="https://app.mem0.ai" rel="nofollow">Mem0 Platform</a>.</Note>
|
||||
<Note>Get your Mem0 API key from <a href="https://app.mem0.ai?utm_source=oss&utm_medium=integration-chatdev" rel="nofollow">Mem0 Platform</a>.</Note>
|
||||
|
||||
```bash
|
||||
MEM0_API_KEY=your-mem0-api-key
|
||||
@@ -194,7 +194,7 @@ This means retrieval returns memories from **both** the user's scope and the age
|
||||
|
||||
| Field | Required | Description |
|
||||
|-------|----------|-------------|
|
||||
| `api_key` | Yes | Mem0 API key from <a href="https://app.mem0.ai" rel="nofollow">app.mem0.ai</a> |
|
||||
| `api_key` | Yes | Mem0 API key from <a href="https://app.mem0.ai?utm_source=oss&utm_medium=integration-chatdev" rel="nofollow">app.mem0.ai</a> |
|
||||
| `user_id` | No | Scope memories to a specific user |
|
||||
| `agent_id` | No | Scope memories to a specific agent |
|
||||
|
||||
@@ -216,9 +216,9 @@ This means retrieval returns memories from **both** the user's scope and the age
|
||||
|
||||
- **No memories returned on first run** — This is expected. Memories are stored *after* the agent responds, so the first interaction has no prior context. Memories appear starting from the second interaction onward.
|
||||
- **`mem0ai` not installed** — If you see `ImportError: mem0ai is required for Mem0Memory`, run `uv add mem0ai` or `pip install mem0ai` to add the dependency.
|
||||
- **Invalid API key** — A wrong or expired `MEM0_API_KEY` will log errors like `Mem0 search failed` or `Mem0 add failed` but won't crash the agent. Check your key at <a href="https://app.mem0.ai" rel="nofollow">app.mem0.ai</a>.
|
||||
- **Invalid API key** — A wrong or expired `MEM0_API_KEY` will log errors like `Mem0 search failed` or `Mem0 add failed` but won't crash the agent. Check your key at <a href="https://app.mem0.ai?utm_source=oss&utm_medium=integration-chatdev" rel="nofollow">app.mem0.ai</a>.
|
||||
- **Pipeline headers in memories** — ChatDev automatically strips internal pipeline headers (e.g., `=== INPUT FROM TASK (user) ===`) before sending text to Mem0, so your memories stay clean.
|
||||
- **Clearing test memories** — To delete memories created during testing, use the Mem0 dashboard at <a href="https://app.mem0.ai" rel="nofollow">app.mem0.ai</a> or the Python SDK: `MemoryClient().delete_all(user_id="your-test-user")`.
|
||||
- **Clearing test memories** — To delete memories created during testing, use the Mem0 dashboard at <a href="https://app.mem0.ai?utm_source=oss&utm_medium=integration-chatdev" rel="nofollow">app.mem0.ai</a> or the Python SDK: `MemoryClient().delete_all(user_id="your-test-user")`.
|
||||
|
||||
## Key Features
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@ Add persistent memory to [**Claude Code**](https://docs.anthropic.com/en/docs/cl
|
||||
Before setting up Mem0 with Claude Code, ensure you have:
|
||||
|
||||
1. A Mem0 Platform account and API key:
|
||||
- <a href="https://app.mem0.ai" rel="nofollow">Sign up at app.mem0.ai</a>
|
||||
- <a href="https://app.mem0.ai/dashboard/api-keys" rel="nofollow">Get your API key</a> (starts with `m0-`)
|
||||
- <a href="https://app.mem0.ai?utm_source=oss&utm_medium=integration-claude-code" rel="nofollow">Sign up at app.mem0.ai</a>
|
||||
- <a href="https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=integration-claude-code" rel="nofollow">Get your API key</a> (starts with `m0-`)
|
||||
|
||||
2. Claude Code CLI or Claude Cowork desktop app installed
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@ Add persistent memory to [**OpenAI Codex**](https://openai.com/index/codex/) wit
|
||||
Before setting up Mem0 with Codex, ensure you have:
|
||||
|
||||
1. A Mem0 Platform account and API key:
|
||||
- <a href="https://app.mem0.ai" rel="nofollow">Sign up at app.mem0.ai</a>
|
||||
- <a href="https://app.mem0.ai/dashboard/api-keys" rel="nofollow">Get your API key</a> (starts with `m0-`)
|
||||
- <a href="https://app.mem0.ai?utm_source=oss&utm_medium=integration-codex" rel="nofollow">Sign up at app.mem0.ai</a>
|
||||
- <a href="https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=integration-codex" rel="nofollow">Get your API key</a> (starts with `m0-`)
|
||||
|
||||
2. OpenAI Codex access
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ pip install crewai crewai-tools mem0ai
|
||||
|
||||
Import required modules and set up configurations:
|
||||
|
||||
<Note>Remember to get your API keys from <a href="https://app.mem0.ai" rel="nofollow">Mem0 Platform</a>, [OpenAI](https://platform.openai.com) and [Serper Dev](https://serper.dev) for search capabilities.</Note>
|
||||
<Note>Remember to get your API keys from <a href="https://app.mem0.ai?utm_source=oss&utm_medium=integration-crewai" rel="nofollow">Mem0 Platform</a>, [OpenAI](https://platform.openai.com) and [Serper Dev](https://serper.dev) for search capabilities.</Note>
|
||||
|
||||
```python
|
||||
import os
|
||||
|
||||
@@ -17,8 +17,8 @@ Add persistent memory to [**Cursor**](https://cursor.com) with the Mem0 plugin.
|
||||
Before setting up Mem0 with Cursor, ensure you have:
|
||||
|
||||
1. A Mem0 Platform account and API key:
|
||||
- <a href="https://app.mem0.ai" rel="nofollow">Sign up at app.mem0.ai</a>
|
||||
- <a href="https://app.mem0.ai/dashboard/api-keys" rel="nofollow">Get your API key</a> (starts with `m0-`)
|
||||
- <a href="https://app.mem0.ai?utm_source=oss&utm_medium=integration-cursor" rel="nofollow">Sign up at app.mem0.ai</a>
|
||||
- <a href="https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=integration-cursor" rel="nofollow">Get your API key</a> (starts with `m0-`)
|
||||
|
||||
2. Cursor installed ([cursor.com](https://cursor.com))
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ npx flowise start
|
||||
|
||||
### 2. Obtain Your Mem0 API Key
|
||||
|
||||
1. Navigate to the <a href="https://app.mem0.ai/dashboard/api-keys" rel="nofollow">Mem0 API Key dashboard</a>.
|
||||
1. Navigate to the <a href="https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=integration-flowise" rel="nofollow">Mem0 API Key dashboard</a>.
|
||||
2. Generate or copy your existing Mem0 API Key.
|
||||
|
||||

|
||||
@@ -70,7 +70,7 @@ Test your memory configuration:
|
||||
|
||||
1. Save your Flowise configuration
|
||||
2. Run a test chat and store some information
|
||||
3. Verify the stored memories in the <a href="https://app.mem0.ai/dashboard/requests" rel="nofollow">Mem0 Dashboard</a>
|
||||
3. Verify the stored memories in the <a href="https://app.mem0.ai/dashboard/requests?utm_source=oss&utm_medium=integration-flowise" rel="nofollow">Mem0 Dashboard</a>
|
||||
|
||||

|
||||
|
||||
@@ -103,7 +103,7 @@ Available settings include:
|
||||
|
||||
### Platform Configuration
|
||||
|
||||
Additional settings available in <a href="https://app.mem0.ai/dashboard/project-settings" rel="nofollow">Mem0 Project Settings</a>:
|
||||
Additional settings available in <a href="https://app.mem0.ai/dashboard/project-settings?utm_source=oss&utm_medium=integration-flowise" rel="nofollow">Mem0 Project Settings</a>:
|
||||
|
||||
1. **Custom Instructions**: Define memory extraction rules
|
||||
2. **Expiration Date**: Set automatic memory cleanup periods
|
||||
|
||||
@@ -22,7 +22,7 @@ pip install google-adk mem0ai python-dotenv
|
||||
```
|
||||
|
||||
2. Valid API keys:
|
||||
- <a href="https://app.mem0.ai/dashboard/api-keys" rel="nofollow">Mem0 API Key</a>
|
||||
- <a href="https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=integration-google-ai-adk" rel="nofollow">Mem0 API Key</a>
|
||||
- Google AI Studio API Key
|
||||
|
||||
## Basic Integration Example
|
||||
|
||||
@@ -52,7 +52,7 @@ hermes memory setup
|
||||
|
||||
Select **mem0** as the provider and enter your Mem0 API key when prompted. The wizard writes your config to `~/.hermes/mem0.json`.
|
||||
|
||||
<Note>Get your API key from <a href="https://app.mem0.ai" rel="nofollow">app.mem0.ai</a>.</Note>
|
||||
<Note>Get your API key from <a href="https://app.mem0.ai?utm_source=oss&utm_medium=integration-hermes" rel="nofollow">app.mem0.ai</a>.</Note>
|
||||
|
||||
### Option 2: Manual Configuration
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ Combining Mem0 with Keywords AI allows you to:
|
||||
4. Optimize token usage and reduce costs
|
||||
|
||||
<Note>
|
||||
You can get your Mem0 API key from the <a href="https://app.mem0.ai/" rel="nofollow">Mem0 dashboard</a>.
|
||||
You can get your Mem0 API key from the <a href="https://app.mem0.ai/?utm_source=oss&utm_medium=integration-keywords" rel="nofollow">Mem0 dashboard</a>.
|
||||
</Note>
|
||||
|
||||
## Setup and Configuration
|
||||
|
||||
@@ -22,7 +22,7 @@ pip install langchain langchain_openai mem0ai python-dotenv
|
||||
|
||||
Import required modules and set up configurations:
|
||||
|
||||
<Note>Remember to get the Mem0 API key from <a href="https://app.mem0.ai" rel="nofollow">Mem0 Platform</a>.</Note>
|
||||
<Note>Remember to get the Mem0 API key from <a href="https://app.mem0.ai?utm_source=oss&utm_medium=integration-langchain" rel="nofollow">Mem0 Platform</a>.</Note>
|
||||
|
||||
```python
|
||||
import os
|
||||
|
||||
@@ -23,7 +23,7 @@ pip install langgraph langchain-openai mem0ai python-dotenv
|
||||
|
||||
Import required modules and set up configurations:
|
||||
|
||||
<Note>Remember to get the Mem0 API key from <a href="https://app.mem0.ai" rel="nofollow">Mem0 Platform</a>.</Note>
|
||||
<Note>Remember to get the Mem0 API key from <a href="https://app.mem0.ai?utm_source=oss&utm_medium=integration-langgraph" rel="nofollow">Mem0 Platform</a>.</Note>
|
||||
|
||||
```python
|
||||
from typing import Annotated, TypedDict, List
|
||||
|
||||
@@ -22,7 +22,7 @@ pip install llama-index-core llama-index-memory-mem0 python-dotenv
|
||||
Set your Mem0 Platform API key as an environment variable. You can replace `<your-mem0-api-key>` with your actual API key:
|
||||
|
||||
<Note type="info">
|
||||
You can obtain your Mem0 Platform API key from the <a href="https://app.mem0.ai/login" rel="nofollow">Mem0 Platform</a>.
|
||||
You can obtain your Mem0 Platform API key from the <a href="https://app.mem0.ai/login?utm_source=oss&utm_medium=integration-llama-index" rel="nofollow">Mem0 Platform</a>.
|
||||
</Note>
|
||||
|
||||
```python
|
||||
|
||||
@@ -23,7 +23,7 @@ npm install @mastra/core @mastra/mem0 @ai-sdk/openai zod
|
||||
|
||||
Set up your environment variables:
|
||||
|
||||
<Note>Remember to get the Mem0 API key from <a href="https://app.mem0.ai" rel="nofollow">Mem0 Platform</a>.</Note>
|
||||
<Note>Remember to get the Mem0 API key from <a href="https://app.mem0.ai?utm_source=oss&utm_medium=integration-mastra" rel="nofollow">Mem0 Platform</a>.</Note>
|
||||
|
||||
```bash
|
||||
MEM0_API_KEY=your-mem0-api-key
|
||||
|
||||
@@ -22,7 +22,7 @@ pip install openai-agents mem0ai
|
||||
```
|
||||
|
||||
2. Valid API keys:
|
||||
- <a href="https://app.mem0.ai/dashboard/api-keys" rel="nofollow">Mem0 API Key</a>
|
||||
- <a href="https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=integration-openai-agents-sdk" rel="nofollow">Mem0 API Key</a>
|
||||
- [OpenAI API Key](https://platform.openai.com/api-keys)
|
||||
|
||||
## Basic Integration Example
|
||||
|
||||
+147
-21
@@ -16,7 +16,7 @@ The plugin provides:
|
||||
2. **Auto-Capture** — After the agent responds, the exchange is sent to Mem0 which decides what's worth keeping
|
||||
3. **Agent Tools** — Eight tools for explicit memory operations during conversations
|
||||
|
||||
Both auto-recall and auto-capture run silently with no manual configuration required.
|
||||
Both auto-recall and auto-capture are opt-in (`autoRecall: true`, `autoCapture: true` in config). Once enabled, they run silently with no manual intervention required.
|
||||
|
||||
## Requirements
|
||||
|
||||
@@ -114,7 +114,7 @@ That's it. No API key, no config file editing, no environment variables. The plu
|
||||
</Step>
|
||||
|
||||
<Step title="Get your API key">
|
||||
Get your API key from <a href="https://app.mem0.ai?utm_source=mem0-docs" rel="nofollow">app.mem0.ai</a>.
|
||||
Get your API key from <a href="https://app.mem0.ai?utm_source=oss&utm_medium=integration-openclaw" rel="nofollow">app.mem0.ai</a>.
|
||||
</Step>
|
||||
|
||||
<Step title="Select the plugin as your memory backend in `openclaw.json`">
|
||||
@@ -147,7 +147,81 @@ OpenClaw treats memory plugins as an exclusive slot. Installing the plugin alone
|
||||
|
||||
### Open-Source Mode (Self-hosted)
|
||||
|
||||
No Mem0 key needed. Requires `OPENAI_API_KEY` for default embeddings/LLM.
|
||||
No Mem0 key needed. Defaults use OpenAI (`gpt-5-mini` for LLM, `text-embedding-3-small` for embeddings) — requires `OPENAI_API_KEY`. For a fully local setup, use Ollama for both.
|
||||
|
||||
#### Option 1: Interactive Wizard (Recommended)
|
||||
|
||||
Run the guided 4-step wizard:
|
||||
|
||||
```bash
|
||||
openclaw mem0 init --mode open-source
|
||||
```
|
||||
|
||||
The wizard walks you through:
|
||||
|
||||
<Steps>
|
||||
<Step title="LLM provider">
|
||||
Choose OpenAI (`gpt-5-mini`), Ollama (`llama3.1:8b`, fully local), or Anthropic (`claude-sonnet-4-5-20250514`). Provide an API key or base URL as needed.
|
||||
</Step>
|
||||
<Step title="Embedding provider">
|
||||
Choose OpenAI (`text-embedding-3-small`) or Ollama (`nomic-embed-text`, local). If the same provider was chosen for LLM, the API key and URL are reused automatically.
|
||||
</Step>
|
||||
<Step title="Vector store">
|
||||
Choose Qdrant (`http://localhost:6333`) or PGVector (PostgreSQL). Connectivity is verified before proceeding.
|
||||
</Step>
|
||||
<Step title="User ID">
|
||||
Set your memory namespace identifier.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
#### Option 2: Non-Interactive Setup
|
||||
|
||||
For CI/CD, scripts, or agent-driven setup — pass all options as flags:
|
||||
|
||||
```bash
|
||||
# Fully local with Ollama + Qdrant
|
||||
openclaw mem0 init --mode open-source \
|
||||
--oss-llm ollama --oss-embedder ollama --oss-vector qdrant
|
||||
|
||||
# OpenAI + Qdrant
|
||||
openclaw mem0 init --mode open-source \
|
||||
--oss-llm openai --oss-llm-key <key> \
|
||||
--oss-embedder openai --oss-embedder-key <key> \
|
||||
--oss-vector qdrant
|
||||
|
||||
# Anthropic LLM + OpenAI embeddings + PGVector
|
||||
openclaw mem0 init --mode open-source \
|
||||
--oss-llm anthropic --oss-llm-key <key> \
|
||||
--oss-embedder openai --oss-embedder-key <key> \
|
||||
--oss-vector pgvector --oss-vector-user postgres --oss-vector-password secret
|
||||
```
|
||||
|
||||
Add `--json` for machine-readable output (useful when an LLM agent is driving the setup).
|
||||
|
||||
<Accordion title="All --oss-* flags">
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `--oss-llm <provider>` | `openai`, `ollama`, or `anthropic` |
|
||||
| `--oss-llm-key <key>` | API key for LLM provider |
|
||||
| `--oss-llm-model <model>` | Override default LLM model |
|
||||
| `--oss-llm-url <url>` | Base URL (Ollama only) |
|
||||
| `--oss-embedder <provider>` | `openai` or `ollama` |
|
||||
| `--oss-embedder-key <key>` | API key for embedder |
|
||||
| `--oss-embedder-model <model>` | Override default embedder model |
|
||||
| `--oss-embedder-url <url>` | Base URL (Ollama only) |
|
||||
| `--oss-vector <provider>` | `qdrant` or `pgvector` |
|
||||
| `--oss-vector-url <url>` | Qdrant server URL (default: `http://localhost:6333`) |
|
||||
| `--oss-vector-host <host>` | PGVector host |
|
||||
| `--oss-vector-port <port>` | PGVector port |
|
||||
| `--oss-vector-user <user>` | PGVector user |
|
||||
| `--oss-vector-password <pw>` | PGVector password |
|
||||
| `--oss-vector-dbname <db>` | PGVector database name |
|
||||
| `--oss-vector-dims <n>` | Override embedding dimensions |
|
||||
</Accordion>
|
||||
|
||||
#### Option 3: Manual Config
|
||||
|
||||
Minimal config — uses OpenAI defaults:
|
||||
|
||||
```json5
|
||||
{
|
||||
@@ -168,7 +242,7 @@ No Mem0 key needed. Requires `OPENAI_API_KEY` for default embeddings/LLM.
|
||||
}
|
||||
```
|
||||
|
||||
Sensible defaults work out of the box. To customize the embedder, vector store, or LLM:
|
||||
To customize providers:
|
||||
|
||||
```json5
|
||||
{
|
||||
@@ -184,8 +258,8 @@ Sensible defaults work out of the box. To customize the embedder, vector store,
|
||||
"userId": "your-user-id",
|
||||
"oss": {
|
||||
"embedder": { "provider": "openai", "config": { "model": "text-embedding-3-small" } },
|
||||
"vectorStore": { "provider": "qdrant", "config": { "host": "localhost", "port": 6333 } },
|
||||
"llm": { "provider": "openai", "config": { "model": "gpt-4o" } }
|
||||
"vectorStore": { "provider": "qdrant", "config": { "url": "http://localhost:6333" } },
|
||||
"llm": { "provider": "openai", "config": { "model": "gpt-5-mini" } }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -225,6 +299,8 @@ The `memory_search` and `memory_list` tools accept a `scope` parameter (`"sessio
|
||||
|
||||
## CLI Commands
|
||||
|
||||
All commands support `--json` for machine-readable output — useful when an LLM agent drives the CLI programmatically. Run `openclaw mem0 help --json` to discover every command and flag.
|
||||
|
||||
```bash
|
||||
# Search all memories (long-term + session)
|
||||
openclaw mem0 search "what languages does the user know"
|
||||
@@ -238,6 +314,10 @@ openclaw mem0 search "what languages does the user know" --scope session
|
||||
# List all memories
|
||||
openclaw mem0 list
|
||||
openclaw mem0 list --user-id alice --top-k 20
|
||||
|
||||
# JSON output (any command)
|
||||
openclaw mem0 search "preferences" --json
|
||||
openclaw mem0 status --json
|
||||
```
|
||||
|
||||
## Configuration Options
|
||||
@@ -248,8 +328,8 @@ openclaw mem0 list --user-id alice --top-k 20
|
||||
|-----|------|---------|-------------|
|
||||
| `mode` | `"platform"` \| `"open-source"` | `"platform"` | Which backend to use |
|
||||
| `userId` | `string` | OS username | Scope memories per user |
|
||||
| `autoRecall` | `boolean` | `true` | Inject memories before each turn |
|
||||
| `autoCapture` | `boolean` | `true` | Store facts after each turn |
|
||||
| `autoRecall` | `boolean` | `false` | Inject memories before each turn (opt-in) |
|
||||
| `autoCapture` | `boolean` | `false` | Store facts after each turn (opt-in) |
|
||||
| `topK` | `number` | `5` | Max memories per recall |
|
||||
| `searchThreshold` | `number` | `0.3` | Min similarity (0–1) |
|
||||
|
||||
@@ -275,18 +355,16 @@ openclaw mem0 list --user-id alice --top-k 20
|
||||
| `oss.historyDbPath` | `string` | — | SQLite path for memory edit history |
|
||||
| `oss.disableHistory` | `boolean` | `false` | Disable memory edit history tracking |
|
||||
|
||||
Everything inside `oss` is optional — defaults use OpenAI embeddings (`text-embedding-3-small`), in-memory vector store, and OpenAI LLM.
|
||||
Everything inside `oss` is optional — defaults use OpenAI embeddings (`text-embedding-3-small`), in-memory vector store, and OpenAI LLM (`gpt-5-mini`).
|
||||
|
||||
## Plugin Management
|
||||
|
||||
### Updating the Plugin
|
||||
|
||||
```bash
|
||||
openclaw plugins update @mem0/openclaw-mem0
|
||||
openclaw plugins update openclaw-mem0
|
||||
```
|
||||
|
||||
<Note>Use the npm package name (`@mem0/openclaw-mem0`) for plugin management commands, not the plugin ID (`openclaw-mem0`).</Note>
|
||||
|
||||
### Checking Plugin Status
|
||||
|
||||
```bash
|
||||
@@ -331,23 +409,71 @@ If the plugin installs but doesn't work:
|
||||
|
||||
If `openclaw plugins update` fails:
|
||||
|
||||
1. Use the full npm package name: `openclaw plugins update @mem0/openclaw-mem0`
|
||||
2. If that fails, uninstall and reinstall:
|
||||
1. Use the plugin ID: `openclaw plugins update openclaw-mem0`
|
||||
2. Update all plugins at once: `openclaw plugins update --all`
|
||||
3. If that fails, uninstall and reinstall:
|
||||
```bash
|
||||
openclaw plugins uninstall openclaw-mem0
|
||||
openclaw plugins install @mem0/openclaw-mem0
|
||||
```
|
||||
|
||||
## Key Features
|
||||
## Privacy & Security
|
||||
|
||||
1. **Zero Configuration** — Auto-recall and auto-capture work out of the box with no prompting required
|
||||
2. **Dual Memory Scopes** — Session-scoped short-term and user-scoped long-term memories
|
||||
3. **Flexible Backend** — Use Mem0 Cloud for managed service or self-host with open-source mode
|
||||
4. **Rich Tool Suite** — Eight agent tools for explicit memory operations when needed
|
||||
### Data Flow
|
||||
|
||||
## Conclusion
|
||||
| Mode | Where data goes | Storage |
|
||||
|------|----------------|---------|
|
||||
| **Platform** | Conversations sent to `api.mem0.ai` for extraction and storage | Mem0 cloud |
|
||||
| **Open-source** | Embeddings generated via configured provider (default: OpenAI API). Vectors stored locally. | `~/.mem0/vector_store.db` (SQLite) |
|
||||
|
||||
The `@mem0/openclaw-mem0` plugin gives OpenClaw agents persistent memory with minimal setup. Whether using Mem0 Cloud or self-hosting, your agents can now remember user preferences, facts, and context across sessions automatically.
|
||||
### Enabling Auto-Capture and Auto-Recall
|
||||
|
||||
Auto-capture and auto-recall are disabled by default (opt-in). To enable either or both:
|
||||
|
||||
```json5
|
||||
{
|
||||
"plugins": {
|
||||
"entries": {
|
||||
"openclaw-mem0": {
|
||||
"config": {
|
||||
"autoCapture": true, // send conversations to Mem0 for extraction
|
||||
"autoRecall": true // inject relevant memories into context
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Without these enabled, the agent can still use memory tools (`memory_add`, `memory_search`, etc.) explicitly — only the automatic background behavior is off.
|
||||
|
||||
### Credential Protection
|
||||
|
||||
The plugin never stores API keys, tokens, or secrets as memories. Five independent layers enforce this:
|
||||
|
||||
1. **Triage gate** — The extraction prompt rejects values matching known credential patterns (`sk-`, `m0-`, `ghp_`, `AKIA`, `Bearer`, `password=`, `token=`, `secret=`)
|
||||
2. **Dream cleanup** — Periodic memory consolidation deletes any memories that slipped through containing credential patterns
|
||||
3. **Extraction instructions** — Default extraction rules explicitly instruct the model to store only that a credential was configured, never the value
|
||||
4. **Configurable patterns** — Add custom credential patterns via `skills.triage.credentialPatterns`
|
||||
5. **CLI redaction** — `openclaw mem0 config show` redacts sensitive fields (`apiKey`, `oss.*.config.apiKey`)
|
||||
|
||||
### API Key Storage
|
||||
|
||||
Plugin config is stored in `~/.openclaw/openclaw.json` with file permissions `0o600` (owner-read-only). For production deployments, use environment variable references (`${MEM0_API_KEY}`) or SecretRef objects instead of plaintext keys.
|
||||
|
||||
### Telemetry
|
||||
|
||||
Anonymous usage telemetry (PostHog) is enabled by default to help improve the plugin. No conversation content or memory values are included — only event counts (recall, capture, tool usage, CLI commands).
|
||||
|
||||
To opt out, set the environment variable:
|
||||
|
||||
```bash
|
||||
export MEM0_TELEMETRY=false
|
||||
```
|
||||
|
||||
### System Prompt Context
|
||||
|
||||
The plugin injects memory-related instructions into the agent's system context via OpenClaw's `prependSystemContext` mechanism. This includes the memory triage protocol and recalled memories. This is the standard OpenClaw plugin SDK pattern for memory backends — no user-facing prompts are modified.
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="OpenAI Agents SDK" icon="robot" href="/integrations/openai-agents-sdk">
|
||||
|
||||
@@ -9,7 +9,7 @@ Mem0 is a self-improving memory layer for LLM applications, enabling personalize
|
||||
|
||||
**Get your API Key**: You'll need a Mem0 API key to use this extension:
|
||||
|
||||
a. Sign up at <a href="https://app.mem0.ai" rel="nofollow">app.mem0.ai</a>
|
||||
a. Sign up at <a href="https://app.mem0.ai?utm_source=oss&utm_medium=integration-raycast" rel="nofollow">app.mem0.ai</a>
|
||||
|
||||
b. Navigate to your API Keys page
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ npm install @mem0/vercel-ai-provider
|
||||
|
||||
### Setting Up Mem0
|
||||
|
||||
1. Get your **Mem0 API Key** from the <a href="https://app.mem0.ai/dashboard/api-keys" rel="nofollow">Mem0 Dashboard</a>.
|
||||
1. Get your **Mem0 API Key** from the <a href="https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=integration-vercel-ai-sdk" rel="nofollow">Mem0 Dashboard</a>.
|
||||
|
||||
2. Initialize the Mem0 Client in your application:
|
||||
|
||||
|
||||
@@ -161,6 +161,7 @@ If the user is on a pre-current major (Python < 2, TS < 3, or Platform `output_f
|
||||
- [Open Source Configuration](https://docs.mem0.ai/open-source/configuration) [OSS]: Use when configuring `Memory` - LLM, embedder, vector store, graph store.
|
||||
- [Open Source Python Quickstart](https://docs.mem0.ai/open-source/python-quickstart) [OSS]: Use for the first self-hosted Python integration.
|
||||
- [Open Source Node.js Quickstart](https://docs.mem0.ai/open-source/node-quickstart) [OSS]: Use for the first self-hosted Node integration.
|
||||
- [Self-Hosted Setup](https://docs.mem0.ai/open-source/setup) [OSS]: Use when standing up the bundled REST server and dashboard via Docker Compose, including auth, API keys, and the setup wizard.
|
||||
|
||||
## Core Concepts
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ Move your Mem0 implementation to managed infrastructure with enterprise features
|
||||
|
||||
## Plan
|
||||
|
||||
1. **Sign up**: Create an account on <a href="https://app.mem0.ai" rel="nofollow">Mem0 Platform</a>.
|
||||
1. **Sign up**: Create an account on <a href="https://app.mem0.ai?utm_source=oss&utm_medium=migration-oss-to-platform" rel="nofollow">Mem0 Platform</a>.
|
||||
2. **Get API Key**: Navigate to **Settings > API Keys** and generate a new key.
|
||||
3. **Review Usage**: Identify where you instantiate `Memory` and where you call `search` or `get_all`.
|
||||
|
||||
@@ -372,7 +372,7 @@ If you encounter issues, you can revert immediately by switching your import bac
|
||||
|
||||
## Next Steps
|
||||
|
||||
- <a href="https://app.mem0.ai" rel="nofollow">Platform Dashboard</a> - Monitor usage and manage settings.
|
||||
- <a href="https://app.mem0.ai?utm_source=oss&utm_medium=migration-oss-to-platform" rel="nofollow">Platform Dashboard</a> - Monitor usage and manage settings.
|
||||
- [Webhooks Setup](/platform/features/webhooks) - Configure real-time event notifications.
|
||||
- [Organizations & Projects](/api-reference/organizations-projects) - Set up multi-tenancy for your team.
|
||||
|
||||
|
||||
@@ -13,6 +13,10 @@ 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>
|
||||
**First time self-hosting, or upgrading from a pre-1.x build?** Start at [Self-Hosted Setup](/open-source/setup). It walks through the stack, the setup wizard, and the upgrade path for deployments that relied on open endpoints or `ADMIN_API_KEY`. This page covers the API surface and auth modes only.
|
||||
</Warning>
|
||||
|
||||
<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>
|
||||
@@ -26,7 +30,7 @@ The Mem0 REST API server exposes every OSS memory operation over HTTP. Run it al
|
||||
## Feature
|
||||
|
||||
- **CRUD endpoints:** Create, retrieve, search, update, delete, and reset memories by `user_id`, `agent_id`, or `run_id`.
|
||||
- **API key authentication:** Optionally secure all endpoints with a shared API key via the `X-API-Key` header.
|
||||
- **Authentication:** On by default. Dashboard sessions use JWTs; programmatic clients use per-user `X-API-Key` headers. Legacy `ADMIN_API_KEY` is still supported.
|
||||
- **Status health check:** Access base routes to confirm the server is online.
|
||||
- **OpenAPI explorer:** Visit `/docs` for interactive testing and schema reference.
|
||||
|
||||
@@ -40,51 +44,75 @@ The Mem0 REST API server exposes every OSS memory operation over HTTP. Run it al
|
||||
<Tab title="Steps">
|
||||
1. Create `server/.env` with your keys:
|
||||
|
||||
```bash
|
||||
OPENAI_API_KEY=your-openai-api-key
|
||||
```
|
||||
```bash
|
||||
OPENAI_API_KEY=your-openai-api-key
|
||||
JWT_SECRET=$(openssl rand -base64 48)
|
||||
```
|
||||
|
||||
2. Start the stack:
|
||||
2. Bootstrap the stack in one command:
|
||||
|
||||
```bash
|
||||
cd server
|
||||
docker compose up
|
||||
```
|
||||
```bash
|
||||
cd server
|
||||
make bootstrap # starts Compose, creates an admin, issues the first API key
|
||||
```
|
||||
|
||||
3. Reach the API at `http://localhost:8888`. Edits to the server or library auto-reload.
|
||||
Or to start the stack only and finish setup via the browser wizard at http://localhost:3000:
|
||||
|
||||
```bash
|
||||
cd server
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
3. API is at `http://localhost:8888`. Code edits auto-reload.
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
### Run with Docker
|
||||
<AccordionGroup>
|
||||
<Accordion title="Other install paths">
|
||||
**Run with Docker**
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Pull image">
|
||||
<Tabs>
|
||||
<Tab title="Pull image">
|
||||
```bash
|
||||
docker pull mem0/mem0-api-server
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Build locally">
|
||||
</Tab>
|
||||
<Tab title="Build locally">
|
||||
```bash
|
||||
docker build -t mem0-api-server .
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
1. Create a `.env` file with `OPENAI_API_KEY`.
|
||||
2. Run the container:
|
||||
1. Create a `.env` file with `OPENAI_API_KEY` and `JWT_SECRET`.
|
||||
2. Run the container:
|
||||
|
||||
```bash
|
||||
docker run -p 8000:8000 --env-file .env mem0-api-server
|
||||
```
|
||||
```bash
|
||||
docker run -p 8000:8000 --env-file .env mem0-api-server
|
||||
```
|
||||
|
||||
3. Visit `http://localhost:8000`.
|
||||
3. Visit `http://localhost:8000`.
|
||||
|
||||
### Run directly (no Docker)
|
||||
**Run directly (no Docker)**
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
uvicorn main:app --reload
|
||||
```
|
||||
<Warning>
|
||||
This path skips Docker and assumes Postgres is already running and reachable at `POSTGRES_HOST:POSTGRES_PORT`. For a single-command local setup with Postgres included, use Docker Compose above.
|
||||
</Warning>
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
uvicorn main:app --reload
|
||||
```
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
<Note>
|
||||
Compose publishes internal port 8000 as 8888 on the host. Raw Docker and raw uvicorn listen on 8000 unless remapped.
|
||||
</Note>
|
||||
|
||||
<Note>
|
||||
`JWT_SECRET` is required once auth is enabled — the server returns `500` on auth endpoints if it's unset. Generate one with `openssl rand -base64 48`. See [Self-Hosted Setup](/open-source/setup#configure-the-environment) for the full env var table.
|
||||
</Note>
|
||||
|
||||
<Tip>
|
||||
Use a process manager such as `systemd`, Supervisor, or PM2 when deploying the FastAPI server for production resilience.
|
||||
@@ -98,35 +126,74 @@ uvicorn main:app --reload
|
||||
|
||||
## Authentication
|
||||
|
||||
The server supports optional API key authentication. When the `ADMIN_API_KEY` environment variable is set, every endpoint requires a valid `X-API-Key` header. The `/` redirect, `/docs`, and `/openapi.json` routes remain open so you can always reach the interactive API explorer.
|
||||
Auth is on by default. Protected endpoints require either a JWT (from the dashboard login flow) or an `X-API-Key` header. The `/` redirect, `/docs`, and `/openapi.json` routes stay open so you can reach the OpenAPI explorer.
|
||||
|
||||
| `ADMIN_API_KEY` value | Behavior |
|
||||
|---|---|
|
||||
| Not set / empty | All endpoints are open (no auth) |
|
||||
| Any non-empty string | Requests must include `X-API-Key: <your-key>` |
|
||||
| Mode | How to send it | When to use it |
|
||||
|---|---|---|
|
||||
| Bearer JWT | `Authorization: Bearer <access_token>` | Dashboard sessions; tokens come from `POST /auth/login` and refresh via `POST /auth/refresh` |
|
||||
| Per-user API key | `X-API-Key: m0sk_...` | Programmatic access scoped to a single dashboard user |
|
||||
| Legacy `ADMIN_API_KEY` | `X-API-Key: <env value>` | Back-compat for deployments that set the `ADMIN_API_KEY` env var |
|
||||
| `AUTH_DISABLED=true` | — | Local development only; bypasses auth entirely |
|
||||
|
||||
### Enable authentication
|
||||
The `/docs` OpenAPI explorer supports both auth modes. Click **Authorize** at the top of the page and paste either `Bearer <access_token>` (JWT) or your `X-API-Key` value. Protected endpoints return `401` until you authorize.
|
||||
|
||||
Add the key to your `.env` file:
|
||||
### Log in and use a JWT
|
||||
|
||||
Register the first admin (only works when no user exists yet), then log in:
|
||||
|
||||
```bash
|
||||
ADMIN_API_KEY=your-secret-api-key
|
||||
# First admin only — returns 403 after the first admin is registered
|
||||
curl -X POST http://localhost:8888/auth/register \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name": "Admin", "email": "admin@example.com", "password": "strong-password"}'
|
||||
```
|
||||
|
||||
Then include the header in every request:
|
||||
```bash
|
||||
curl -X POST http://localhost:8888/auth/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"email": "admin@example.com", "password": "your-password"}'
|
||||
```
|
||||
|
||||
Use the returned `access_token` as a bearer token:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/memories \
|
||||
curl -X POST http://localhost:8888/memories \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-API-Key: your-secret-api-key" \
|
||||
-H "Authorization: Bearer <access_token>" \
|
||||
-d '{
|
||||
"messages": [{"role": "user", "content": "I love pizza."}],
|
||||
"user_id": "alice"
|
||||
}'
|
||||
```
|
||||
|
||||
When the access token expires, exchange the refresh token at `POST /auth/refresh`.
|
||||
|
||||
### Create and use a per-user API key
|
||||
|
||||
Create a key from the dashboard **API Keys** page, or call `POST /api-keys` with a JWT. The full `m0sk_...` value is returned **once** at creation time — store it securely.
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8888/memories \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-API-Key: m0sk_your_key_here" \
|
||||
-d '{
|
||||
"messages": [{"role": "user", "content": "I love pizza."}],
|
||||
"user_id": "alice"
|
||||
}'
|
||||
```
|
||||
|
||||
Per-user keys inherit the creating user's scope. List or revoke them via `GET /api-keys` and `DELETE /api-keys/{id}`.
|
||||
|
||||
### Legacy `ADMIN_API_KEY`
|
||||
|
||||
Set the `ADMIN_API_KEY` environment variable and send it as `X-API-Key`. The request is treated as admin-level and is not tied to a dashboard user. This mode is kept for back-compat with older self-hosted deployments — prefer JWT or per-user keys for new setups.
|
||||
|
||||
```bash
|
||||
ADMIN_API_KEY=your-long-admin-key
|
||||
```
|
||||
|
||||
<Warning>
|
||||
The server logs a warning at startup when `ADMIN_API_KEY` is not set. Always set it in production.
|
||||
Setting `AUTH_DISABLED=true` makes every protected endpoint open — the server logs a warning at startup when it's enabled. The server also warns when `ADMIN_API_KEY` is shorter than 16 characters. Never enable `AUTH_DISABLED` in production, and always use a long `ADMIN_API_KEY` if you rely on the legacy fallback.
|
||||
</Warning>
|
||||
|
||||
---
|
||||
@@ -136,7 +203,7 @@ curl -X POST http://localhost:8000/memories \
|
||||
### Create and search memories via HTTP
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/memories \
|
||||
curl -X POST http://localhost:8888/memories \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"messages": [
|
||||
@@ -151,7 +218,7 @@ curl -X POST http://localhost:8000/memories \
|
||||
</Info>
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/search \
|
||||
curl -X POST http://localhost:8888/search \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"query": "vegetable",
|
||||
@@ -161,7 +228,7 @@ curl -X POST http://localhost:8000/search \
|
||||
|
||||
### Explore with OpenAPI docs
|
||||
|
||||
1. Navigate to `http://localhost:8000/docs`.
|
||||
1. Navigate to `http://localhost:8888/docs` (Compose) or `http://localhost:8000/docs` (raw Docker / uvicorn).
|
||||
2. Pick an endpoint (e.g., `POST /search`).
|
||||
3. Fill in parameters and click **Execute** to try requests in-browser.
|
||||
|
||||
@@ -175,9 +242,13 @@ curl -X POST http://localhost:8000/search \
|
||||
|
||||
The OSS REST server exposes the following endpoints. None use the `/v1/` prefix.
|
||||
|
||||
### Memory operations
|
||||
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| `POST` | `/configure` | Set memory configuration |
|
||||
| `POST` | `/configure` | Set memory configuration. Rejects unbundled providers with a 400 |
|
||||
| `GET` | `/configure` | Get the current memory configuration |
|
||||
| `GET` | `/configure/providers` | List the LLM and embedder providers bundled in the container |
|
||||
| `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 |
|
||||
@@ -188,6 +259,43 @@ The OSS REST server exposes the following endpoints. None use the `/v1/` prefix.
|
||||
| `POST` | `/search` | Search memories |
|
||||
| `POST` | `/reset` | Reset all memories |
|
||||
|
||||
### Authentication
|
||||
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| `GET` | `/auth/setup-status` | Returns `{needsSetup: bool}`. Open, no auth required |
|
||||
| `POST` | `/auth/register` | Register the first admin. Registration closes after the first admin is created; additional accounts are provisioned by the existing admin. |
|
||||
| `POST` | `/auth/login` | Exchange email and password for access and refresh JWTs |
|
||||
| `POST` | `/auth/refresh` | Exchange a refresh token for a new access token |
|
||||
| `GET` | `/auth/me` | Get the current authenticated user (JWT required) |
|
||||
| `PATCH` | `/auth/me` | Update the caller's name or email. 409 if the new email is already in use |
|
||||
| `POST` | `/auth/change-password` | Change the caller's password. 401 if the current password is wrong; new password must be at least 8 characters |
|
||||
|
||||
### API keys
|
||||
|
||||
All `/api-keys` endpoints require a JWT.
|
||||
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| `GET` | `/api-keys` | List the caller's API keys |
|
||||
| `POST` | `/api-keys` | Create a new key; the full `m0sk_...` value is returned once |
|
||||
| `DELETE` | `/api-keys/{id}` | Revoke an API key |
|
||||
|
||||
### Request logs
|
||||
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| `GET` | `/requests?limit=N` | Recent API call log (JWT or admin key) |
|
||||
|
||||
### Entities
|
||||
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| `GET` | `/entities` | Distinct `user_id` / `agent_id` / `run_id` values with memory counts |
|
||||
| `DELETE` | `/entities/{entity_type}/{entity_id}` | Cascade-delete all memories for an entity; `entity_type` is `user`, `agent`, or `run` |
|
||||
|
||||
The `/auth/*`, `/api-keys`, `/requests`, and `/entities` routes are new to the self-hosted server and primarily back the dashboard, but you can call them directly from your own tooling.
|
||||
|
||||
---
|
||||
|
||||
## Verify the feature is working
|
||||
@@ -201,7 +309,7 @@ The OSS REST server exposes the following endpoints. None use the `/v1/` prefix.
|
||||
|
||||
## Best practices
|
||||
|
||||
1. **Enable authentication:** Set `ADMIN_API_KEY` to secure all endpoints, or use an API gateway for more advanced schemes.
|
||||
1. **Keep auth on:** Auth is enabled by default. Never set `AUTH_DISABLED=true` in production. If you rely on `ADMIN_API_KEY`, use a long value (16+ chars) or prefer per-user API keys.
|
||||
2. **Use HTTPS:** Terminate TLS at your load balancer or reverse proxy.
|
||||
3. **Monitor uptime:** Track request rates, latency, and error codes per endpoint.
|
||||
4. **Version configs:** Keep environment files and Docker Compose definitions in source control.
|
||||
|
||||
@@ -15,12 +15,15 @@ Mem0 Open Source delivers the same adaptive memory engine as the platform, but p
|
||||
- **Extendable codebase**: Fork the repo, add providers, and ship custom automations.
|
||||
|
||||
<Info>
|
||||
Begin with the <Link href="/open-source/python-quickstart">Python quickstart</Link> (or the Node.js variant) to clone the repo, configure dependencies, and validate memory reads/writes locally.
|
||||
Two ways to run Mem0 OSS: as a **library** inside your app (Python or Node), or as a **self-hosted server** with a dashboard, per-user API keys, and a request audit log.
|
||||
</Info>
|
||||
|
||||
## Choose your path
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<CardGroup cols={3}>
|
||||
<Card title="Self-hosted setup" icon="rocket-launch" href="/open-source/setup">
|
||||
Run `make bootstrap` to launch the server + dashboard, create an admin, and issue your first API key.
|
||||
</Card>
|
||||
<Card title="Python Quickstart" icon="python" href="/open-source/python-quickstart">
|
||||
Bootstrap CLI and verify add/search loop.
|
||||
</Card>
|
||||
@@ -41,15 +44,6 @@ Mem0 Open Source delivers the same adaptive memory engine as the platform, but p
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Deploy with Docker Compose" icon="server" href="/open-source/features/rest-api">
|
||||
Reference deployment with REST endpoints.
|
||||
</Card>
|
||||
<Card title="Use the REST API" icon="code" href="/open-source/features/rest-api">
|
||||
Async add/search flows and automation.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
<Tip>
|
||||
Need a managed alternative? Compare hosting models in the <Link href="/platform/platform-vs-oss">Platform vs OSS guide</Link> or switch tabs to the Platform documentation.
|
||||
</Tip>
|
||||
@@ -70,19 +64,27 @@ Mem0 Open Source delivers the same adaptive memory engine as the platform, but p
|
||||
## Default components
|
||||
|
||||
<Note>
|
||||
Mem0 OSS works out of the box with sensible defaults:
|
||||
**Library defaults** (when you `import` Mem0 and call `Memory()` directly):
|
||||
- LLM: OpenAI `gpt-5-mini` (via `OPENAI_API_KEY`)
|
||||
- Embeddings: OpenAI `text-embedding-3-small`
|
||||
- Vector store: Local Qdrant instance storing data at `/tmp/qdrant`
|
||||
- History store: SQLite database at `~/.mem0/history.db`
|
||||
- Reranker: Disabled until you configure a provider
|
||||
- Vector store: Local Qdrant at `/tmp/qdrant`
|
||||
- History store: SQLite at `~/.mem0/history.db`
|
||||
- Reranker: Disabled until configured
|
||||
|
||||
Override any component with <Link href="/open-source/configuration">`Memory.from_config`</Link>.
|
||||
</Note>
|
||||
|
||||
## Keep going
|
||||
<Note>
|
||||
**Self-hosted server defaults** (the `server/` Docker Compose stack):
|
||||
- LLM: OpenAI `gpt-4.1-nano-2025-04-14` (override with `MEM0_DEFAULT_LLM_MODEL`)
|
||||
- Embeddings: OpenAI `text-embedding-3-small` (override with `MEM0_DEFAULT_EMBEDDER_MODEL`)
|
||||
- Vector store: Postgres + pgvector
|
||||
- Bundled providers: `openai`, `anthropic`, `gemini` — switch from the Configuration page
|
||||
|
||||
{/* DEBUG: verify CTA targets */}
|
||||
See <Link href="/open-source/setup#supported-providers">Self-Hosted Setup</Link> for the full provider list and how to extend it.
|
||||
</Note>
|
||||
|
||||
## Keep going
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card
|
||||
@@ -98,7 +100,3 @@ Mem0 Open Source delivers the same adaptive memory engine as the platform, but p
|
||||
href="/open-source/python-quickstart"
|
||||
/>
|
||||
</CardGroup>
|
||||
|
||||
<Tip>
|
||||
Need a managed alternative? Compare hosting models in the <Link href="/platform/platform-vs-oss">Platform vs OSS guide</Link> or switch tabs to the Platform documentation.
|
||||
</Tip>
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
---
|
||||
title: "Self-Hosted Setup"
|
||||
description: "Stand up the Mem0 REST server and dashboard in a few minutes — admin account, API keys, and a live audit log included."
|
||||
icon: "rocket-launch"
|
||||
---
|
||||
|
||||
The self-hosted bundle ships the REST API and a web dashboard together. Configure your LLM provider and secrets in a `.env` file, start the containers, then choose how to create your admin account: through the browser-based setup wizard, or from the command line.
|
||||
|
||||
<Info>
|
||||
**Use this page when…**
|
||||
- You want a self-hosted Mem0 with a dashboard, not just the Python or Node library.
|
||||
- You need per-user API keys and a request audit log for your team.
|
||||
- You're upgrading from a pre-1.x server that relied on `ADMIN_API_KEY` or open endpoints.
|
||||
</Info>
|
||||
|
||||
<Warning>
|
||||
**Upgrading from 1.x?** Auth is now on by default. Deployments that ran with an empty `ADMIN_API_KEY` will return `401` on every protected endpoint until you either set `ADMIN_API_KEY`, register an admin through the wizard, or set `AUTH_DISABLED=true` for local development. See [Upgrade notes](#upgrade-notes) below.
|
||||
</Warning>
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Docker and Docker Compose (the reference path).
|
||||
- An `OPENAI_API_KEY` (or equivalent — the server reads the same component config as the library).
|
||||
- A free port `8888` for the API and `3000` for the dashboard.
|
||||
|
||||
---
|
||||
|
||||
## Configure the environment
|
||||
|
||||
Copy `server/.env.example` to `server/.env` and fill in the required values. The server refuses to start if `JWT_SECRET` is unset once auth is enabled.
|
||||
|
||||
| Variable | Required | Purpose |
|
||||
|---|---|---|
|
||||
| `OPENAI_API_KEY` | Yes | Default LLM and embedder provider. |
|
||||
| `JWT_SECRET` | Yes | Signs access and refresh tokens. Use a long random value. A missing secret causes auth endpoints to return `500`. |
|
||||
| `ADMIN_API_KEY` | Optional | Legacy shared admin key. Kept for back-compat; prefer per-user keys for new setups. |
|
||||
| `AUTH_DISABLED` | Optional | `true` turns off auth for local development only. Never enable in production. |
|
||||
| `DASHBOARD_URL` | Optional | Origin the API accepts for CORS. Defaults to `http://localhost:3000`. Set this when you front the dashboard on a custom domain. |
|
||||
| `POSTGRES_*` | Optional | Override the bundled Postgres / pgvector connection. |
|
||||
|
||||
<Tip>
|
||||
Generate a `JWT_SECRET` with `openssl rand -base64 48` or `python -c "import secrets; print(secrets.token_urlsafe(48))"`.
|
||||
</Tip>
|
||||
|
||||
---
|
||||
|
||||
## Start the stack
|
||||
|
||||
Pick the path that fits your workflow.
|
||||
|
||||
### Browser-first (setup wizard)
|
||||
|
||||
```bash
|
||||
cd server
|
||||
make up
|
||||
```
|
||||
|
||||
This starts the containers and runs database migrations. The REST API listens on `http://localhost:8888` and the dashboard on `http://localhost:3000`.
|
||||
|
||||
Open `http://localhost:3000` — since no admin account exists yet, the dashboard redirects to the one-time setup wizard at `/setup`. See [Run the setup wizard](#run-the-setup-wizard) below.
|
||||
|
||||
### Agent-first (command line)
|
||||
|
||||
First, set `OPENAI_API_KEY` (or `ANTHROPIC_API_KEY` / `GOOGLE_API_KEY`) in `server/.env`. `make bootstrap` does not prompt for it, and the runtime test will fail without a valid provider key.
|
||||
|
||||
```bash
|
||||
cd server
|
||||
make bootstrap
|
||||
```
|
||||
|
||||
`make bootstrap` starts the same containers, then automatically creates the admin account and generates the first API key via the CLI. The admin credentials and API key are printed to your terminal — no browser required.
|
||||
|
||||
You can override the generated credentials:
|
||||
|
||||
```bash
|
||||
make bootstrap EMAIL=admin@company.com PASSWORD='strong-password' NAME='Admin'
|
||||
```
|
||||
|
||||
Because `make bootstrap` already creates the admin, the setup wizard is skipped. Opening `http://localhost:3000` takes you straight to the login page.
|
||||
|
||||
<Tip>
|
||||
For machine-readable output (useful in CI), run `OUTPUT=json make seed` after `make up`.
|
||||
</Tip>
|
||||
|
||||
---
|
||||
|
||||
## Run the setup wizard
|
||||
|
||||
<Info>
|
||||
This section applies to the **browser-first** path (`make up`). If you used `make bootstrap`, the admin and API key were already created — skip ahead to [What the dashboard gives you](#what-the-dashboard-gives-you).
|
||||
</Info>
|
||||
|
||||
On a fresh install the dashboard redirects to `/setup`. Each step submits on Enter.
|
||||
|
||||
**1. Create the admin account.** Name, email, password. This account becomes the first admin. Registration closes after the first admin is created; additional accounts are provisioned by the existing admin.
|
||||
|
||||
**2. Review the effective config.** Read-only display of the LLM and embedder the server is running with, sourced from your environment. If anything is wrong here, stop the stack, fix the `.env`, and restart — the dashboard intentionally does not let you change provider secrets at runtime.
|
||||
|
||||
**3. Generate your first API key.** The full `m0sk_...` value is shown **once**. Copy it immediately — the server only stores the prefix and a bcrypt hash.
|
||||
|
||||
**4. Tell us your use case.** Pick a preset or describe your use case in a few words. Mem0 generates custom instructions that tell the memory system what to prioritize. You can edit the instructions before saving, or skip this step entirely.
|
||||
|
||||
**5. Test the key.** A ready-to-paste `curl` exercises `POST /memories` against your new key. Click "Run Test" to fire it from the browser. Success lands you in the dashboard at `/dashboard/requests`, where you'll see the test call in the live audit log.
|
||||
|
||||
---
|
||||
|
||||
## What the dashboard gives you
|
||||
|
||||
| Page | What it does |
|
||||
|---|---|
|
||||
| **Requests** | Default landing page. Live audit log of every API call, with status, latency, and auth mode. |
|
||||
| **Memories** | Browse and search the memories your server has stored. |
|
||||
| **Entities** | Distinct `user_id` / `agent_id` / `run_id` values with memory counts and cascade-delete. |
|
||||
| **API Keys** | Issue per-user keys, label them, and revoke. |
|
||||
| **Configuration** | Runtime override for LLM and embedder. Changes persist to the app database and reapply on restart, layered over the values from your `.env`. |
|
||||
| **Settings** | Account and session controls. |
|
||||
|
||||
For the underlying endpoints (including `/auth/*`, `/api-keys`, `/requests`, `/entities`), see the [REST API reference](/open-source/features/rest-api).
|
||||
|
||||
---
|
||||
|
||||
## Supported providers
|
||||
|
||||
The shipped container bundles the Python packages for:
|
||||
|
||||
- **LLMs** — `openai`, `anthropic`, `gemini`
|
||||
- **Embedders** — `openai`, `gemini`
|
||||
|
||||
The Configuration page and `POST /configure` only accept providers from these lists. Anything else returns a 400 up front instead of failing at the first memory write.
|
||||
|
||||
**To add another provider**, for example to run embeddings locally with `sentence-transformers`:
|
||||
|
||||
1. Add the package to `server/requirements.txt` (e.g. `sentence-transformers>=2.0`).
|
||||
2. Extend `BUNDLED_LLM_PROVIDERS` or `BUNDLED_EMBEDDER_PROVIDERS` in `server/main.py`.
|
||||
3. Rebuild the image (`make up` or `docker compose build`).
|
||||
|
||||
Heavy providers (`sentence-transformers` pulls in PyTorch, ~2 GB) are intentionally kept out of the default image.
|
||||
|
||||
---
|
||||
|
||||
## Upgrade notes
|
||||
|
||||
### Upgrading from a pre-auth build
|
||||
|
||||
Previous self-hosted builds allowed open access when `ADMIN_API_KEY` was unset. This build enables auth by default. After pulling the new image, pick **one**:
|
||||
|
||||
1. **Fastest, zero client changes** — set `ADMIN_API_KEY` to a long random value (16+ characters). Existing clients that send `X-API-Key: <your-key>` keep working unchanged.
|
||||
2. **Recommended for teams** — visit `http://<host>:3000`, run the setup wizard, and switch clients to per-user API keys. You get the audit log and revocation for free.
|
||||
3. **Local development only** — set `AUTH_DISABLED=true`. The server logs a warning on every boot. Never use this in production.
|
||||
|
||||
The server prints an unmissable startup banner when it detects the "upgraded but not configured" state so you know exactly which option to pick.
|
||||
|
||||
### Other changes in this release
|
||||
|
||||
- Dashboard ships as a second container in the reference Compose stack, wired to the API over the internal Docker network.
|
||||
- New tables: `users`, `api_keys`, `request_logs`. Alembic handles the migration automatically on first boot.
|
||||
|
||||
If `alembic upgrade head` fails on first boot, see the [Troubleshooting](#troubleshooting) section below.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Port 3000 or 8888 is already in use">
|
||||
Find the owning process on either port:
|
||||
```bash
|
||||
lsof -iTCP:3000 -sTCP:LISTEN
|
||||
lsof -iTCP:8888 -sTCP:LISTEN
|
||||
```
|
||||
Kill it (`kill <PID>`) or change the host port in `server/docker-compose.yaml`.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="JWT_SECRET is required">
|
||||
The server refuses to start without one. Generate a secret and add it to `server/.env`:
|
||||
```bash
|
||||
echo "JWT_SECRET=$(openssl rand -base64 48)" >> server/.env
|
||||
```
|
||||
`AUTH_DISABLED=true` is valid for local dev only, never production.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title=".env changes aren't applied after editing">
|
||||
`docker compose restart` does not re-read `env_file`. To pick up changes:
|
||||
```bash
|
||||
cd server && docker compose up -d --force-recreate mem0
|
||||
# or
|
||||
cd server && make up
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Provider returns 401 (bad API key)">
|
||||
Provider credential errors surface as `502 Upstream provider error.`. Check `docker compose logs mem0` for the full trace, then fix the key on the Configuration page and hit **Save**.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Alembic migrations fail on startup">
|
||||
Inspect the logs:
|
||||
```bash
|
||||
docker compose logs mem0 | grep -i alembic
|
||||
```
|
||||
If the database is unrecoverable, reset the volume (**this destroys all memories and users**):
|
||||
```bash
|
||||
docker compose down -v
|
||||
```
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
---
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="REST API reference" icon="code" href="/open-source/features/rest-api">
|
||||
Endpoint tables, auth modes, and example requests.
|
||||
</Card>
|
||||
<Card title="Configure components" icon="sliders" href="/open-source/configuration">
|
||||
Swap LLMs, embedders, vector stores, and rerankers.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
@@ -7,8 +7,8 @@ estimatedTime: "~2 minutes"
|
||||
|
||||
<Info>
|
||||
**Prerequisites**
|
||||
- Mem0 Platform account (<a href="https://app.mem0.ai" rel="nofollow">Sign up here</a>)
|
||||
- API key (<a href="https://app.mem0.ai/settings/api-keys" rel="nofollow">Get one from dashboard</a>)
|
||||
- Mem0 Platform account (<a href="https://app.mem0.ai?utm_source=oss&utm_medium=platform-mem0-mcp" rel="nofollow">Sign up here</a>)
|
||||
- API key (<a href="https://app.mem0.ai/settings/api-keys?utm_source=oss&utm_medium=platform-mem0-mcp" rel="nofollow">Get one from dashboard</a>)
|
||||
- Node.js 14+ (for npx)
|
||||
- An MCP-compatible client (Claude, Claude Code, Cursor, Windsurf, VS Code, OpenCode)
|
||||
</Info>
|
||||
@@ -163,7 +163,7 @@ Agent: Updated your project status successfully.
|
||||
```
|
||||
|
||||
<Info icon="check">
|
||||
If you get "Connection failed", ensure you have a valid API key from <a href="https://app.mem0.ai/settings/api-keys" rel="nofollow">Mem0 Dashboard</a>.
|
||||
If you get "Connection failed", ensure you have a valid API key from <a href="https://app.mem0.ai/settings/api-keys?utm_source=oss&utm_medium=platform-mem0-mcp" rel="nofollow">Mem0 Dashboard</a>.
|
||||
</Info>
|
||||
|
||||
---
|
||||
@@ -171,7 +171,7 @@ Agent: Updated your project status successfully.
|
||||
## Quick Recovery
|
||||
|
||||
- **"Connection refused"** → Check your internet connection and ensure the MCP client is correctly configured
|
||||
- **"Invalid API key"** → Get a new key from <a href="https://app.mem0.ai/settings/api-keys" rel="nofollow">Mem0 Dashboard</a>
|
||||
- **"Invalid API key"** → Get a new key from <a href="https://app.mem0.ai/settings/api-keys?utm_source=oss&utm_medium=platform-mem0-mcp" rel="nofollow">Mem0 Dashboard</a>
|
||||
- **"npx command not found"** → Install Node.js from [nodejs.org](https://nodejs.org)
|
||||
|
||||
---
|
||||
|
||||
@@ -60,7 +60,7 @@ Mem0 is the memory engine that keeps conversations contextual so users never rep
|
||||
<Card title="Connect Integrations" icon="plug" href="/integrations">
|
||||
LangChain, CrewAI, Vercel AI SDK.
|
||||
</Card>
|
||||
<Card title="Monitor in the Dashboard" icon="presentation" href="https://app.mem0.ai/login">
|
||||
<Card title="Monitor in the Dashboard" icon="presentation" href="https://app.mem0.ai/login?utm_source=oss&utm_medium=platform-overview">
|
||||
Track activity and manage workspaces.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
@@ -150,7 +150,7 @@ Mem0 offers two powerful ways to add memory to your AI applications. Choose base
|
||||
<Card
|
||||
title="Try Platform Free"
|
||||
icon="rocket"
|
||||
href="https://app.mem0.ai/login"
|
||||
href="https://app.mem0.ai/login?utm_source=oss&utm_medium=platform-vs-oss"
|
||||
>
|
||||
Sign up and test the Platform with our free tier. No credit card required.
|
||||
</Card>
|
||||
|
||||
@@ -9,7 +9,7 @@ Get started with Mem0 Platform's hosted API in under 5 minutes. This guide shows
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Mem0 Platform account (<a href="https://app.mem0.ai" rel="nofollow">Sign up here</a>)
|
||||
- Mem0 Platform account (<a href="https://app.mem0.ai?utm_source=oss&utm_medium=platform-quickstart" rel="nofollow">Sign up here</a>)
|
||||
- API key (<a href="https://app.mem0.ai/dashboard/settings?tab=api-keys&subtab=configuration" rel="nofollow">Get one from dashboard</a>)
|
||||
- Python 3.10+, Node.js 14+, or cURL
|
||||
|
||||
|
||||
+2
-2
@@ -12,7 +12,7 @@ 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/login">
|
||||
<Card title="Get an API Key" icon="key" href="https://app.mem0.ai/login?utm_source=oss&utm_medium=vibecoding">
|
||||
Sign up for Mem0 Platform and start building
|
||||
</Card>
|
||||
<Card title="Quickstart" icon="rocket" href="/platform/quickstart">
|
||||
@@ -34,7 +34,7 @@ Works with Claude Code, Cursor, Windsurf, and any assistant that supports skills
|
||||
|
||||
Connect Claude, Claude Code, Cursor, Windsurf, VS Code, OpenCode, or any MCP-compatible client to Mem0.
|
||||
|
||||
Get your API key from <a href="https://app.mem0.ai" rel="nofollow">app.mem0.ai</a>, then add Mem0 MCP with a single command:
|
||||
Get your API key from <a href="https://app.mem0.ai?utm_source=oss&utm_medium=vibecoding" rel="nofollow">app.mem0.ai</a>, then add Mem0 MCP with a single command:
|
||||
|
||||
```bash
|
||||
npx mcp-add \
|
||||
|
||||
@@ -91,7 +91,7 @@ export const Assistant = () => {
|
||||
</button>
|
||||
<GithubButton url="https://github.com/mem0ai/mem0/tree/main/examples" />
|
||||
|
||||
<Link href={"https://app.mem0.ai/"} target="_blank" className="py-1 ml-2 px-4 font-semibold dark:bg-zinc-100 dark:hover:bg-zinc-200 bg-zinc-800 text-white rounded-full hover:bg-zinc-900 dark:text-[#475569]">
|
||||
<Link href={"https://app.mem0.ai/?utm_source=oss&utm_medium=example-mem0-demo"} target="_blank" className="py-1 ml-2 px-4 font-semibold dark:bg-zinc-100 dark:hover:bg-zinc-200 bg-zinc-800 text-white rounded-full hover:bg-zinc-900 dark:text-[#475569]">
|
||||
Playground
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -174,7 +174,7 @@ export const Thread: FC<ThreadProps> = ({
|
||||
<GithubButton url="https://github.com/mem0ai/mem0/tree/main/examples" className="w-full rounded-lg h-9 pl-2 text-sm font-semibold bg-zinc-800 dark:border-zinc-800 dark:text-white text-white hover:bg-zinc-900" text="View on Github" />
|
||||
|
||||
<Link
|
||||
href={"https://app.mem0.ai/"}
|
||||
href={"https://app.mem0.ai/?utm_source=oss&utm_medium=example-mem0-demo"}
|
||||
target="_blank"
|
||||
className="py-2 px-4 w-full rounded-lg h-9 pl-3 text-sm font-semibold dark:bg-zinc-800 dark:hover:bg-zinc-700 bg-zinc-800 text-white hover:bg-zinc-900 dark:text-white"
|
||||
>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Add persistent long-term memory to your [NemoClaw](https://docs.nvidia.com/nemoclaw/latest/get-started/quickstart.html) OpenClaw agent using the `@mem0/openclaw-mem0` plugin.
|
||||
|
||||
> **Note:** This plugin requires **Mem0 Platform mode** (i.e., a Mem0 API key from [app.mem0.ai](https://app.mem0.ai)). Open-source mode is not supported in NemoClaw sandboxes because the sandbox proxy blocks `/v1/embeddings` requests required by the open-source backend. See [Known Limitations](#known-limitations) for details.
|
||||
> **Note:** This plugin requires **Mem0 Platform mode** (i.e., a Mem0 API key from [app.mem0.ai](https://app.mem0.ai?utm_source=oss&utm_medium=example-nemoclaw)). Open-source mode is not supported in NemoClaw sandboxes because the sandbox proxy blocks `/v1/embeddings` requests required by the open-source backend. See [Known Limitations](#known-limitations) for details.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
@@ -15,7 +15,7 @@ Add persistent long-term memory to your [NemoClaw](https://docs.nvidia.com/nemoc
|
||||
**Accounts required:**
|
||||
|
||||
- **NVIDIA** — sign up at [build.nvidia.com](https://build.nvidia.com), generate an API key at [build.nvidia.com/settings/api-keys](https://build.nvidia.com/settings/api-keys) (starts with `nvapi-`)
|
||||
- **Mem0** — sign up at [app.mem0.ai](https://app.mem0.ai), generate an API key from the dashboard (starts with `m0-`)
|
||||
- **Mem0** — sign up at [app.mem0.ai](https://app.mem0.ai?utm_source=oss&utm_medium=example-nemoclaw), generate an API key from the dashboard (starts with `m0-`)
|
||||
|
||||
**Supported platforms:** Ubuntu 22.04+, macOS (via Docker), Windows (WSL 2 + Docker)
|
||||
|
||||
@@ -269,4 +269,4 @@ Then re-run `nemoclaw onboard`.
|
||||
- [Mem0 Documentation](https://docs.mem0.ai)
|
||||
- [NemoClaw Documentation](https://docs.nvidia.com/nemoclaw/latest/get-started/quickstart.html)
|
||||
- [`@mem0/openclaw-mem0` on npm](https://www.npmjs.com/package/@mem0/openclaw-mem0)
|
||||
- [Mem0 Dashboard](https://app.mem0.ai)
|
||||
- [Mem0 Dashboard](https://app.mem0.ai?utm_source=oss&utm_medium=example-nemoclaw)
|
||||
|
||||
@@ -4,7 +4,7 @@ import { zodResponsesFunction } from "openai/helpers/zod";
|
||||
import { z } from "zod";
|
||||
|
||||
const mem0Config = {
|
||||
apiKey: process.env.MEM0_API_KEY, // GET THIS API KEY FROM MEM0 (https://app.mem0.ai/dashboard/api-keys)
|
||||
apiKey: process.env.MEM0_API_KEY, // GET THIS API KEY FROM MEM0 (https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=example-openai-inbuilt-tools)
|
||||
user_id: "sample-user",
|
||||
};
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ Add persistent memory to your AI workflows. Store, retrieve, and manage memories
|
||||
|
||||
> **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)
|
||||
1. Sign up at [app.mem0.ai](https://app.mem0.ai?utm_source=oss&utm_medium=mem0-plugin-readme) if you haven't already
|
||||
2. Go to [app.mem0.ai/dashboard/api-keys](https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=mem0-plugin-readme)
|
||||
3. Click **Create API Key** and copy the key (starts with `m0-`)
|
||||
4. Add it to your shell profile:
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Mem0 Skill for Claude
|
||||
|
||||
Add persistent memory to any AI application in minutes using [Mem0 Platform](https://app.mem0.ai).
|
||||
Add persistent memory to any AI application in minutes using [Mem0 Platform](https://app.mem0.ai?utm_source=oss&utm_medium=mem0-plugin-skill-readme).
|
||||
|
||||
## What This Skill Does
|
||||
|
||||
@@ -24,7 +24,7 @@ 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))
|
||||
- A Mem0 Platform API key ([Get one here](https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=mem0-plugin-skill-readme))
|
||||
- Python 3.10+ or Node.js 18+
|
||||
- Set the environment variable:
|
||||
|
||||
@@ -63,7 +63,7 @@ skills/mem0/
|
||||
|
||||
## Links
|
||||
|
||||
- [Mem0 Platform Dashboard](https://app.mem0.ai)
|
||||
- [Mem0 Platform Dashboard](https://app.mem0.ai?utm_source=oss&utm_medium=mem0-plugin-skill-readme)
|
||||
- [Mem0 Documentation](https://docs.mem0.ai)
|
||||
- [Mem0 GitHub](https://github.com/mem0ai/mem0)
|
||||
- [API Reference](https://docs.mem0.ai/api-reference)
|
||||
|
||||
@@ -35,7 +35,7 @@ npm install mem0ai
|
||||
export MEM0_API_KEY="m0-your-api-key"
|
||||
```
|
||||
|
||||
Get an API key at: https://app.mem0.ai/dashboard/api-keys
|
||||
Get an API key at: https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=mem0-plugin-skill
|
||||
|
||||
## Step 2: Initialize the client
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ Get running with Mem0 in 2 minutes. No infrastructure to deploy -- just an API k
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.10+ or Node.js 18+
|
||||
- A Mem0 Platform API key ([Get one here](https://app.mem0.ai/dashboard/api-keys))
|
||||
- A Mem0 Platform API key ([Get one here](https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=mem0-plugin-skill-quickstart))
|
||||
|
||||
## Python Setup
|
||||
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ npm i mem0ai
|
||||
|
||||
## 2. API Key Setup
|
||||
|
||||
For the cloud offering, sign in to [Mem0 Platform](https://app.mem0.ai/dashboard/api-keys) to obtain your API Key.
|
||||
For the cloud offering, sign in to [Mem0 Platform](https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=mem0-ts-readme) to obtain your API Key.
|
||||
|
||||
## 3. Client Features
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mem0ai",
|
||||
"version": "3.0.1",
|
||||
"version": "3.0.2",
|
||||
"description": "The Memory Layer For Your AI Apps",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.mjs",
|
||||
|
||||
@@ -10,6 +10,7 @@ export class OpenAILLM implements LLM {
|
||||
this.openai = new OpenAI({
|
||||
apiKey: config.apiKey,
|
||||
baseURL: config.baseURL,
|
||||
...(config.timeout != null && { timeout: config.timeout }),
|
||||
});
|
||||
this.model = config.model || "gpt-5-mini";
|
||||
}
|
||||
|
||||
@@ -7,7 +7,11 @@ export class OpenAIStructuredLLM implements LLM {
|
||||
private model: string;
|
||||
|
||||
constructor(config: LLMConfig) {
|
||||
this.openai = new OpenAI({ apiKey: config.apiKey });
|
||||
this.openai = new OpenAI({
|
||||
apiKey: config.apiKey,
|
||||
baseURL: config.baseURL,
|
||||
...(config.timeout != null && { timeout: config.timeout }),
|
||||
});
|
||||
this.model = config.model || "gpt-5-mini";
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ export interface LLMConfig {
|
||||
apiKey?: string;
|
||||
model?: string | any;
|
||||
modelProperties?: Record<string, any>;
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
export interface MemoryConfig {
|
||||
@@ -129,6 +130,7 @@ export const MemoryConfigSchema = z.object({
|
||||
modelProperties: z.record(z.string(), z.any()).optional(),
|
||||
baseURL: z.string().optional(),
|
||||
url: z.string().optional(),
|
||||
timeout: z.number().optional(),
|
||||
}),
|
||||
}),
|
||||
historyDbPath: z.string().optional(),
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
/// <reference types="jest" />
|
||||
/**
|
||||
* OpenAI LLM — unit tests (mocked openai).
|
||||
*
|
||||
* Regression tests for #4707: timeout config was silently ignored,
|
||||
* causing add() to hang indefinitely on slow LLM responses.
|
||||
*/
|
||||
|
||||
let capturedConstructorArgs: any;
|
||||
const mockCreate = jest.fn();
|
||||
|
||||
jest.mock("openai", () => {
|
||||
return jest.fn().mockImplementation((args: any) => {
|
||||
capturedConstructorArgs = args;
|
||||
return {
|
||||
chat: { completions: { create: mockCreate } },
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
import { OpenAILLM } from "../src/llms/openai";
|
||||
|
||||
describe("OpenAILLM (unit)", () => {
|
||||
beforeEach(() => {
|
||||
capturedConstructorArgs = undefined;
|
||||
mockCreate.mockClear();
|
||||
});
|
||||
|
||||
it("forwards timeout to the OpenAI client constructor", () => {
|
||||
new OpenAILLM({
|
||||
apiKey: "test-key",
|
||||
baseURL: "http://localhost:8080/v1",
|
||||
timeout: 5000,
|
||||
});
|
||||
|
||||
expect(capturedConstructorArgs).toMatchObject({
|
||||
apiKey: "test-key",
|
||||
baseURL: "http://localhost:8080/v1",
|
||||
timeout: 5000,
|
||||
});
|
||||
});
|
||||
|
||||
it("forwards timeout: 0 to the OpenAI client (explicit zero is valid)", () => {
|
||||
new OpenAILLM({
|
||||
apiKey: "test-key",
|
||||
baseURL: "http://localhost:8080/v1",
|
||||
timeout: 0,
|
||||
});
|
||||
|
||||
expect(capturedConstructorArgs.timeout).toBe(0);
|
||||
});
|
||||
|
||||
it("omits timeout from the OpenAI client when not configured", () => {
|
||||
new OpenAILLM({
|
||||
apiKey: "test-key",
|
||||
baseURL: "http://localhost:8080/v1",
|
||||
});
|
||||
|
||||
expect(capturedConstructorArgs).toMatchObject({
|
||||
apiKey: "test-key",
|
||||
baseURL: "http://localhost:8080/v1",
|
||||
});
|
||||
expect(capturedConstructorArgs).not.toHaveProperty("timeout");
|
||||
});
|
||||
|
||||
it("generateResponse() returns text content", async () => {
|
||||
mockCreate.mockResolvedValueOnce({
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: '{"facts": ["hello"]}',
|
||||
role: "assistant",
|
||||
tool_calls: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const llm = new OpenAILLM({ apiKey: "test-key" });
|
||||
const result = await llm.generateResponse([
|
||||
{ role: "user", content: "Hi" },
|
||||
]);
|
||||
|
||||
expect(mockCreate).toHaveBeenCalledTimes(1);
|
||||
expect(result).toBe('{"facts": ["hello"]}');
|
||||
});
|
||||
|
||||
it("generateResponse() handles tool calls", async () => {
|
||||
mockCreate.mockResolvedValueOnce({
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: "",
|
||||
role: "assistant",
|
||||
tool_calls: [
|
||||
{
|
||||
function: {
|
||||
name: "get_weather",
|
||||
arguments: '{"city": "London"}',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const llm = new OpenAILLM({ apiKey: "test-key" });
|
||||
const result = await llm.generateResponse(
|
||||
[{ role: "user", content: "What is the weather?" }],
|
||||
undefined,
|
||||
[{ type: "function", function: { name: "get_weather" } }],
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
content: "",
|
||||
role: "assistant",
|
||||
toolCalls: [{ name: "get_weather", arguments: '{"city": "London"}' }],
|
||||
});
|
||||
});
|
||||
|
||||
it("generateChat() returns LLMResponse shape", async () => {
|
||||
mockCreate.mockResolvedValueOnce({
|
||||
choices: [
|
||||
{
|
||||
message: { content: "I can help.", role: "assistant" },
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const llm = new OpenAILLM({ apiKey: "test-key" });
|
||||
const result = await llm.generateChat([
|
||||
{ role: "user", content: "Help me" },
|
||||
]);
|
||||
|
||||
expect(result).toEqual({
|
||||
content: "I can help.",
|
||||
role: "assistant",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,59 @@
|
||||
/// <reference types="jest" />
|
||||
/**
|
||||
* OpenAI Structured LLM — unit tests (mocked openai).
|
||||
*
|
||||
* Sibling fix for #4707: OpenAIStructuredLLM had the same timeout gap.
|
||||
*/
|
||||
|
||||
let capturedConstructorArgs: any;
|
||||
const mockCreate = jest.fn();
|
||||
|
||||
jest.mock("openai", () => {
|
||||
return jest.fn().mockImplementation((args: any) => {
|
||||
capturedConstructorArgs = args;
|
||||
return {
|
||||
chat: { completions: { create: mockCreate } },
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
import { OpenAIStructuredLLM } from "../src/llms/openai_structured";
|
||||
|
||||
describe("OpenAIStructuredLLM (unit)", () => {
|
||||
beforeEach(() => {
|
||||
capturedConstructorArgs = undefined;
|
||||
mockCreate.mockClear();
|
||||
});
|
||||
|
||||
it("forwards timeout to the OpenAI client constructor", () => {
|
||||
new OpenAIStructuredLLM({
|
||||
apiKey: "test-key",
|
||||
timeout: 15000,
|
||||
});
|
||||
|
||||
expect(capturedConstructorArgs).toMatchObject({
|
||||
apiKey: "test-key",
|
||||
timeout: 15000,
|
||||
});
|
||||
});
|
||||
|
||||
it("forwards timeout: 0 to the OpenAI client (explicit zero is valid)", () => {
|
||||
new OpenAIStructuredLLM({
|
||||
apiKey: "test-key",
|
||||
timeout: 0,
|
||||
});
|
||||
|
||||
expect(capturedConstructorArgs.timeout).toBe(0);
|
||||
});
|
||||
|
||||
it("omits timeout from the OpenAI client when not configured", () => {
|
||||
new OpenAIStructuredLLM({
|
||||
apiKey: "test-key",
|
||||
});
|
||||
|
||||
expect(capturedConstructorArgs).toMatchObject({
|
||||
apiKey: "test-key",
|
||||
});
|
||||
expect(capturedConstructorArgs).not.toHaveProperty("timeout");
|
||||
});
|
||||
});
|
||||
@@ -14,6 +14,7 @@ class ElasticsearchConfig(BaseModel):
|
||||
api_key: Optional[str] = Field(None, description="API key for authentication")
|
||||
embedding_model_dims: int = Field(1536, description="Dimension of the embedding vector")
|
||||
verify_certs: bool = Field(True, description="Verify SSL certificates")
|
||||
ca_certs: Optional[str] = Field(None, description="Path to CA bundle for SSL certificate verification")
|
||||
use_ssl: bool = Field(True, description="Use SSL for connection")
|
||||
auto_create_index: bool = Field(True, description="Automatically create index during initialization")
|
||||
custom_search_query: Optional[Callable[[List[float], int, Optional[Dict]], Dict]] = Field(
|
||||
|
||||
+11
-6
@@ -54,14 +54,19 @@ class LLMBase(ABC):
|
||||
"o1", "o1-preview", "o3-mini", "o3",
|
||||
"gpt-5", "gpt-5o", "gpt-5o-mini", "gpt-5o-micro",
|
||||
}
|
||||
|
||||
if model.lower() in reasoning_models:
|
||||
return True
|
||||
|
||||
|
||||
model_lower = model.lower()
|
||||
if any(reasoning_model in model_lower for reasoning_model in ["gpt-5", "o1", "o3"]):
|
||||
# Strip provider prefixes (e.g. "openai/o3-mini" -> "o3-mini")
|
||||
base_model = model_lower.rsplit("/", 1)[-1]
|
||||
|
||||
if base_model in reasoning_models:
|
||||
return True
|
||||
|
||||
|
||||
# Match o1/o3 family with prefixes (o1-2024-12-17, o3-2025-04-16)
|
||||
# but NOT gpt-5.x variants (gpt-5.4-mini supports temperature)
|
||||
if any(base_model.startswith(prefix) for prefix in ["o1-", "o1.", "o3-", "o3."]):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _get_supported_params(self, **kwargs) -> Dict:
|
||||
|
||||
+7
-5
@@ -656,10 +656,10 @@ class Memory(MemoryBase):
|
||||
else:
|
||||
messages = parse_vision_messages(messages)
|
||||
|
||||
vector_store_result = self._add_to_vector_store(messages, processed_metadata, effective_filters, infer)
|
||||
vector_store_result = self._add_to_vector_store(messages, processed_metadata, effective_filters, infer, prompt=prompt)
|
||||
return {"results": vector_store_result}
|
||||
|
||||
def _add_to_vector_store(self, messages, metadata, filters, infer):
|
||||
def _add_to_vector_store(self, messages, metadata, filters, infer, prompt=None):
|
||||
if not infer:
|
||||
returned_memories = []
|
||||
for message_dict in messages:
|
||||
@@ -726,7 +726,7 @@ class Memory(MemoryBase):
|
||||
if is_agent_scoped:
|
||||
system_prompt += AGENT_CONTEXT_SUFFIX
|
||||
|
||||
custom_instr = self.custom_instructions
|
||||
custom_instr = prompt or self.custom_instructions
|
||||
|
||||
user_prompt = generate_additive_extraction_prompt(
|
||||
existing_memories=existing_memories,
|
||||
@@ -2064,7 +2064,7 @@ class AsyncMemory(MemoryBase):
|
||||
else:
|
||||
messages = parse_vision_messages(messages)
|
||||
|
||||
vector_store_result = await self._add_to_vector_store(messages, processed_metadata, effective_filters, infer)
|
||||
vector_store_result = await self._add_to_vector_store(messages, processed_metadata, effective_filters, infer, prompt=prompt)
|
||||
return {"results": vector_store_result}
|
||||
|
||||
async def _add_to_vector_store(
|
||||
@@ -2073,6 +2073,7 @@ class AsyncMemory(MemoryBase):
|
||||
metadata: dict,
|
||||
effective_filters: dict,
|
||||
infer: bool,
|
||||
prompt: Optional[str] = None,
|
||||
):
|
||||
if not infer:
|
||||
returned_memories = []
|
||||
@@ -2141,7 +2142,7 @@ class AsyncMemory(MemoryBase):
|
||||
if is_agent_scoped:
|
||||
system_prompt += AGENT_CONTEXT_SUFFIX
|
||||
|
||||
custom_instr = self.custom_instructions
|
||||
custom_instr = prompt or self.custom_instructions
|
||||
|
||||
user_prompt = generate_additive_extraction_prompt(
|
||||
existing_memories=existing_memories,
|
||||
@@ -3008,6 +3009,7 @@ class AsyncMemory(MemoryBase):
|
||||
if "created_at" not in new_metadata:
|
||||
new_metadata["created_at"] = datetime.now(timezone.utc).isoformat()
|
||||
new_metadata["updated_at"] = new_metadata["created_at"]
|
||||
new_metadata["text_lemmatized"] = lemmatize_for_bm25(data)
|
||||
|
||||
await asyncio.to_thread(
|
||||
self.vector_store.insert,
|
||||
|
||||
@@ -31,6 +31,7 @@ class ElasticsearchDB(VectorStoreBase):
|
||||
cloud_id=config.cloud_id,
|
||||
api_key=config.api_key,
|
||||
verify_certs=config.verify_certs,
|
||||
ca_certs=config.ca_certs,
|
||||
headers= config.headers or {},
|
||||
)
|
||||
else:
|
||||
@@ -38,6 +39,7 @@ class ElasticsearchDB(VectorStoreBase):
|
||||
hosts=[f"{config.host}" if config.port is None else f"{config.host}:{config.port}"],
|
||||
basic_auth=(config.user, config.password) if (config.user and config.password) else None,
|
||||
verify_certs=config.verify_certs,
|
||||
ca_certs=config.ca_certs,
|
||||
headers= config.headers or {},
|
||||
)
|
||||
|
||||
|
||||
+134
-54
@@ -2,7 +2,7 @@
|
||||
|
||||
Long-term memory for [OpenClaw](https://github.com/openclaw/openclaw) agents, powered by [Mem0](https://mem0.ai).
|
||||
|
||||
Your agent forgets everything between sessions. This plugin fixes that — it watches conversations, extracts what matters, and brings it back when relevant. Automatically.
|
||||
Your agent forgets everything between sessions. This plugin fixes that — it stores conversations, extracts what matters, and brings it back when relevant. Enable `autoRecall` and `autoCapture` in config to run this automatically, or use agent tools for explicit control.
|
||||
|
||||
## Requirements
|
||||
|
||||
@@ -19,46 +19,9 @@ openclaw --version
|
||||
|
||||
## Quick Start
|
||||
|
||||
The fastest way is to install directly from your OpenClaw chat — no CLI or config editing needed.
|
||||
|
||||
Copy and paste this into your OpenClaw chat (Telegram, WhatsApp, default chat, or any channel where your agent lives):
|
||||
|
||||
```
|
||||
Setup Mem0 from mem0.ai/claw-setup
|
||||
```
|
||||
|
||||
OpenClaw installs the plugin, prompts you for your email, and connects your Mem0 account with OTP verification. See [Chat Setup](#chat-setup-recommended) below for the full walkthrough.
|
||||
|
||||
If you prefer the OpenClaw CLI, or are setting up self-hosted / open-source mode, see [Manual Config](#manual-config) and [Open-Source (Self-hosted)](#open-source-self-hosted) below.
|
||||
|
||||
### Platform (Mem0 Cloud)
|
||||
|
||||
There are two ways to set up `@mem0/openclaw-mem0` on the Mem0 platform:
|
||||
|
||||
- **Chat setup (recommended)** — run the setup inside any OpenClaw chat. No config editing, no API key handling.
|
||||
- **Manual config** — edit `openclaw.json` directly.
|
||||
|
||||
#### Chat Setup (Recommended)
|
||||
|
||||
You no longer need manual config editing to get started. Everything happens inside the OpenClaw chat itself.
|
||||
|
||||
1. **Send the setup command to your OpenClaw agent.** Open any OpenClaw channel and paste:
|
||||
|
||||
```
|
||||
Setup Mem0 from mem0.ai/claw-setup
|
||||
```
|
||||
|
||||
OpenClaw responds with a Mem0 setup card and asks: *"What's your email address? I'll send you a verification code to connect your Mem0 account."*
|
||||
|
||||
2. **Enter your email.** Type your email address and send it. Mem0 replies: *"Check your email for a 6-digit code and paste it here."*
|
||||
|
||||
3. **Paste the OTP.** Copy the 6-digit code from your email inbox and paste it into the chat. You'll see: *"Connected to Mem0."*
|
||||
|
||||
That's it. No API key, no config file editing, no environment variables. The plugin is now active and auto-capture and auto-recall are running on every turn.
|
||||
|
||||
> The chat flow uses the same underlying config as manual setup — it writes `apiKey` and `userId` into `openclaw.json` for you. You can still open the file to inspect or override values afterward.
|
||||
|
||||
#### Manual Config
|
||||
#### Install and Configure
|
||||
|
||||
1. **Install the plugin via the OpenClaw CLI:**
|
||||
|
||||
@@ -66,7 +29,7 @@ That's it. No API key, no config file editing, no environment variables. The plu
|
||||
openclaw plugins install @mem0/openclaw-mem0
|
||||
```
|
||||
|
||||
2. **Get your API key** from [app.mem0.ai](https://app.mem0.ai/dashboard/api-keys).
|
||||
2. **Get your API key** from [app.mem0.ai](https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=openclaw-readme).
|
||||
|
||||
3. **Select the plugin as your memory backend in `openclaw.json`.** Either initialize via the CLI:
|
||||
|
||||
@@ -97,11 +60,86 @@ That's it. No API key, no config file editing, no environment variables. The plu
|
||||
|
||||
> **Note:** OpenClaw memory plugins load through an exclusive slot, so install alone does not activate the plugin. You must set `plugins.slots.memory` as shown above.
|
||||
|
||||
### Updating the plugin to get the latest features and fixes:
|
||||
|
||||
```bash
|
||||
openclaw plugins update openclaw-mem0
|
||||
```
|
||||
|
||||
### Open-Source (Self-hosted)
|
||||
|
||||
No Mem0 key needed. Requires `OPENAI_API_KEY` for default embeddings and LLM. Vectors are stored locally in SQLite at `~/.mem0/vector_store.db` — no external database required.
|
||||
No Mem0 key needed. Vectors are stored locally in SQLite at `~/.mem0/vector_store.db` — no external database required.
|
||||
|
||||
Defaults: `text-embedding-3-small` for embeddings, `gpt-5.4` for fact extraction.
|
||||
Defaults: `text-embedding-3-small` (OpenAI) for embeddings, `gpt-5-mini` (OpenAI) for fact extraction — requires `OPENAI_API_KEY`. For a fully local setup, use Ollama for both LLM and embeddings.
|
||||
|
||||
#### Interactive Setup (Recommended)
|
||||
|
||||
Run the guided 4-step wizard:
|
||||
|
||||
```bash
|
||||
openclaw mem0 init --mode open-source
|
||||
```
|
||||
|
||||
The wizard walks you through:
|
||||
1. **LLM provider** — OpenAI (`gpt-5-mini`), Ollama (`llama3.1:8b`, local), or Anthropic (`claude-sonnet-4-5-20250514`)
|
||||
2. **Embedding provider** — OpenAI (`text-embedding-3-small`) or Ollama (`nomic-embed-text`, local)
|
||||
3. **Vector store** — Qdrant (`http://localhost:6333`) or PGVector (PostgreSQL)
|
||||
4. **User ID** — your memory namespace identifier
|
||||
|
||||
Each step tests connectivity (Ollama, Qdrant, PGVector) before proceeding.
|
||||
|
||||
#### Non-Interactive Setup
|
||||
|
||||
For CI/CD, scripts, or agent-driven setup — pass all options as flags:
|
||||
|
||||
```bash
|
||||
# Fully local with Ollama + Qdrant
|
||||
openclaw mem0 init --mode open-source \
|
||||
--oss-llm ollama --oss-embedder ollama --oss-vector qdrant
|
||||
|
||||
# OpenAI + Qdrant
|
||||
openclaw mem0 init --mode open-source \
|
||||
--oss-llm openai --oss-llm-key <key> \
|
||||
--oss-embedder openai --oss-embedder-key <key> \
|
||||
--oss-vector qdrant
|
||||
|
||||
# Anthropic LLM + OpenAI embeddings + PGVector
|
||||
openclaw mem0 init --mode open-source \
|
||||
--oss-llm anthropic --oss-llm-key <key> \
|
||||
--oss-embedder openai --oss-embedder-key <key> \
|
||||
--oss-vector pgvector --oss-vector-user postgres --oss-vector-password secret
|
||||
|
||||
# JSON output (for LLM agents)
|
||||
openclaw mem0 init --mode open-source --oss-llm ollama --oss-embedder ollama --oss-vector qdrant --json
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>All <code>--oss-*</code> flags</summary>
|
||||
|
||||
| Flag | Description |
|
||||
| ---- | ----------- |
|
||||
| `--oss-llm <provider>` | `openai`, `ollama`, or `anthropic` |
|
||||
| `--oss-llm-key <key>` | API key for LLM provider |
|
||||
| `--oss-llm-model <model>` | Override default LLM model |
|
||||
| `--oss-llm-url <url>` | Base URL (Ollama only) |
|
||||
| `--oss-embedder <provider>` | `openai` or `ollama` |
|
||||
| `--oss-embedder-key <key>` | API key for embedder |
|
||||
| `--oss-embedder-model <model>` | Override default embedder model |
|
||||
| `--oss-embedder-url <url>` | Base URL (Ollama only) |
|
||||
| `--oss-vector <provider>` | `qdrant` or `pgvector` |
|
||||
| `--oss-vector-url <url>` | Qdrant server URL (default: `http://localhost:6333`) |
|
||||
| `--oss-vector-host <host>` | PGVector host |
|
||||
| `--oss-vector-port <port>` | PGVector port |
|
||||
| `--oss-vector-user <user>` | PGVector user |
|
||||
| `--oss-vector-password <pw>` | PGVector password |
|
||||
| `--oss-vector-dbname <db>` | PGVector database name |
|
||||
| `--oss-vector-dims <n>` | Override embedding dimensions |
|
||||
|
||||
</details>
|
||||
|
||||
#### Manual Config
|
||||
|
||||
Minimal config — uses OpenAI defaults:
|
||||
|
||||
```json5
|
||||
{
|
||||
@@ -130,8 +168,8 @@ Customize the embedder, vector store, or LLM via the `oss` block:
|
||||
"userId": "alice",
|
||||
"oss": {
|
||||
"embedder": { "provider": "openai", "config": { "model": "text-embedding-3-small" } },
|
||||
"vectorStore": { "provider": "qdrant", "config": { "host": "localhost", "port": 6333 } },
|
||||
"llm": { "provider": "openai", "config": { "model": "gpt-5.4" } }
|
||||
"vectorStore": { "provider": "qdrant", "config": { "url": "http://localhost:6333" } },
|
||||
"llm": { "provider": "openai", "config": { "model": "gpt-5-mini" } }
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -144,11 +182,11 @@ All `oss` fields are optional. See the [Mem0 OSS docs](https://docs.mem0.ai/open
|
||||
<img src="https://raw.githubusercontent.com/mem0ai/mem0/main/docs/images/openclaw-architecture.png" alt="Architecture" width="800" />
|
||||
</p>
|
||||
|
||||
**Auto-Recall** — Before the agent responds, the plugin searches Mem0 for relevant memories and injects them into context.
|
||||
**Auto-Recall** (`autoRecall: true`) — Before the agent responds, the plugin searches Mem0 for relevant memories and injects them into context.
|
||||
|
||||
**Auto-Capture** — After the agent responds, the conversation is filtered through a noise-removal pipeline and sent to Mem0. New facts get stored, stale ones updated, duplicates merged.
|
||||
**Auto-Capture** (`autoCapture: true`) — After the agent responds, the conversation is filtered through a noise-removal pipeline and sent to Mem0. New facts get stored, stale ones updated, duplicates merged.
|
||||
|
||||
Both run silently. No prompting, no manual calls required.
|
||||
Both are opt-in. Once enabled, they run silently — no prompting, no manual calls required. Without them, the agent can still use memory tools (`memory_add`, `memory_search`, etc.) explicitly.
|
||||
|
||||
### Memory Scopes
|
||||
|
||||
@@ -176,7 +214,7 @@ Eight tools are registered for agent use:
|
||||
|
||||
## CLI
|
||||
|
||||
All commands: `openclaw mem0 <command>`.
|
||||
All commands: `openclaw mem0 <command>`. All commands support `--json` for machine-readable output (for LLM agents).
|
||||
|
||||
```bash
|
||||
# Memory operations
|
||||
@@ -191,8 +229,9 @@ openclaw mem0 delete --all --user-id alice --confirm
|
||||
openclaw mem0 import memories.json
|
||||
|
||||
# Management
|
||||
openclaw mem0 init
|
||||
openclaw mem0 init --api-key <key> --user-id alice
|
||||
openclaw mem0 init # interactive setup
|
||||
openclaw mem0 init --mode open-source --oss-llm ollama # non-interactive OSS
|
||||
openclaw mem0 init --api-key <key> --user-id alice # non-interactive platform
|
||||
openclaw mem0 status
|
||||
openclaw mem0 config show
|
||||
openclaw mem0 config get api_key
|
||||
@@ -205,6 +244,12 @@ openclaw mem0 event status <event_id>
|
||||
# Memory consolidation
|
||||
openclaw mem0 dream
|
||||
openclaw mem0 dream --dry-run
|
||||
|
||||
# JSON output (any command)
|
||||
openclaw mem0 search "preferences" --json
|
||||
openclaw mem0 list --json
|
||||
openclaw mem0 status --json
|
||||
openclaw mem0 help --json # discover all commands + flags
|
||||
```
|
||||
|
||||
## Configuration Reference
|
||||
@@ -215,10 +260,10 @@ openclaw mem0 dream --dry-run
|
||||
| --- | ---- | ------- | ----------- |
|
||||
| `mode` | `"platform"` \| `"open-source"` | `"platform"` | Backend mode |
|
||||
| `userId` | `string` | OS username | User identifier. All memories scoped to this value. |
|
||||
| `autoRecall` | `boolean` | `true` | Inject relevant memories before each turn |
|
||||
| `autoCapture` | `boolean` | `true` | Extract and store facts after each turn |
|
||||
| `autoRecall` | `boolean` | `false` | Inject relevant memories before each turn |
|
||||
| `autoCapture` | `boolean` | `false` | Extract and store facts after each turn |
|
||||
| `topK` | `number` | `5` | Max memories returned per recall |
|
||||
| `searchThreshold` | `number` | `0.5` | Minimum similarity score (0-1) |
|
||||
| `searchThreshold` | `number` | `0.3` | Minimum similarity score (0-1) |
|
||||
|
||||
### Platform Mode
|
||||
|
||||
@@ -230,7 +275,7 @@ openclaw mem0 dream --dry-run
|
||||
|
||||
### Open-Source Mode
|
||||
|
||||
All fields optional. Defaults: `text-embedding-3-small` embeddings, local SQLite vector store (`~/.mem0/vector_store.db`), `gpt-5.4` LLM.
|
||||
All fields optional. Defaults: `text-embedding-3-small` embeddings, local SQLite vector store (`~/.mem0/vector_store.db`), `gpt-5-mini` LLM.
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
| --- | ---- | ------- | ----------- |
|
||||
@@ -243,6 +288,41 @@ All fields optional. Defaults: `text-embedding-3-small` embeddings, local SQLite
|
||||
| `oss.llm.config` | `object` | — | Provider config (`apiKey`, `model`, `baseURL`) |
|
||||
| `oss.historyDbPath` | `string` | — | SQLite path for edit history |
|
||||
|
||||
## Privacy & Security
|
||||
|
||||
### Data Flow
|
||||
|
||||
| Mode | Where data goes | Credentials needed |
|
||||
|------|----------------|-------------------|
|
||||
| **Platform** | Conversations sent to `api.mem0.ai` for memory extraction and retrieval | `MEM0_API_KEY` |
|
||||
| **Open-Source (OpenAI)** | LLM/embedding calls to OpenAI API; vectors stored locally at `~/.mem0/vector_store.db` | `OPENAI_API_KEY` |
|
||||
| **Open-Source (Ollama)** | Fully local — LLM, embeddings, and vectors all on your machine | None |
|
||||
|
||||
### Credential Storage
|
||||
|
||||
The plugin stores configuration in `~/.openclaw/openclaw.json`. If you use the chat setup flow or `openclaw mem0 init`, your API key and user ID are written to this file.
|
||||
|
||||
To avoid plaintext credentials:
|
||||
- Use env var references: `"apiKey": "${MEM0_API_KEY}"`
|
||||
- Use SecretRef: `"apiKey": {"source": "env", "provider": "default", "id": "MEM0_API_KEY"}`
|
||||
|
||||
### Auto-Capture & Auto-Recall
|
||||
|
||||
Both are **disabled by default** (`false`). When enabled:
|
||||
- `autoCapture`: sends conversation content to your configured backend (cloud or local) after each agent turn
|
||||
- `autoRecall`: queries your memory store before each agent turn and injects results into agent context
|
||||
|
||||
Do not enable `autoCapture` in platform mode if your conversations contain sensitive data you do not want stored on Mem0 cloud.
|
||||
|
||||
### Persistence Locations
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `~/.openclaw/openclaw.json` | Plugin configuration (API keys, user ID, settings) |
|
||||
| `~/.mem0/vector_store.db` | Local vector store (open-source mode only) |
|
||||
| `~/.mem0/history.db` | Memory edit history (open-source mode only) |
|
||||
| `<pluginStateDir>/dream-state.json` | Memory consolidation state |
|
||||
|
||||
## License
|
||||
|
||||
[Apache 2.0](LICENSE)
|
||||
|
||||
+507
-126
File diff suppressed because it is too large
Load Diff
@@ -141,10 +141,12 @@ export function ensureInstallRecord(): void {
|
||||
const entry = full?.plugins?.entries?.[PLUGIN_ID];
|
||||
const record = full?.plugins?.installs?.[PLUGIN_ID];
|
||||
const allow = full?.plugins?.allow;
|
||||
const specPinned = record?.spec && /\d+\.\d+\.\d+/.test(record.spec);
|
||||
if (
|
||||
entry?.enabled === true &&
|
||||
record?.source &&
|
||||
record?.spec &&
|
||||
!specPinned &&
|
||||
Array.isArray(allow) &&
|
||||
allow.includes(PLUGIN_ID)
|
||||
) {
|
||||
@@ -171,8 +173,10 @@ export function ensureInstallRecord(): void {
|
||||
record.source = "npm";
|
||||
changed = true;
|
||||
}
|
||||
if (!record.spec) {
|
||||
record.spec = `${NPM_PACKAGE}@latest`;
|
||||
if (!record.spec || /\d+\.\d+\.\d+/.test(record.spec)) {
|
||||
record.spec = record.source === "clawhub"
|
||||
? `clawhub:${NPM_PACKAGE}`
|
||||
: `${NPM_PACKAGE}@latest`;
|
||||
changed = true;
|
||||
}
|
||||
if (!record.resolvedName) {
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* JSON output helpers for agent-friendly CLI commands.
|
||||
*/
|
||||
|
||||
function writeStdout(data: Record<string, unknown>): void {
|
||||
process.stdout.write(JSON.stringify(data, null, 2) + "\n");
|
||||
}
|
||||
|
||||
export function jsonOut(
|
||||
opts: { json?: boolean },
|
||||
data: Record<string, unknown>,
|
||||
): boolean {
|
||||
if (!opts.json) return false;
|
||||
writeStdout(data);
|
||||
return true;
|
||||
}
|
||||
|
||||
export function jsonErr(
|
||||
opts: { json?: boolean },
|
||||
error: string,
|
||||
): boolean {
|
||||
if (!opts.json) return false;
|
||||
writeStdout({ ok: false, error });
|
||||
return true;
|
||||
}
|
||||
|
||||
export function redactSecrets(
|
||||
obj: Record<string, unknown>,
|
||||
secretKeys: Set<string>,
|
||||
): Record<string, unknown> {
|
||||
const result = { ...obj };
|
||||
for (const key of secretKeys) {
|
||||
const val = result[key];
|
||||
if (typeof val !== "string") continue;
|
||||
result[key] = val.length <= 8
|
||||
? val.slice(0, 2) + "***"
|
||||
: val.slice(0, 4) + "..." + val.slice(-4);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
/**
|
||||
* OSS provider wizard — provider definitions, config builders, and validation.
|
||||
*
|
||||
* Used by the init command for both interactive wizard and non-interactive
|
||||
* --oss-* flag paths.
|
||||
*/
|
||||
|
||||
import { join } from "node:path";
|
||||
import { homedir } from "node:os";
|
||||
|
||||
// ============================================================================
|
||||
// Provider definitions
|
||||
// ============================================================================
|
||||
|
||||
export interface ProviderDef {
|
||||
id: string;
|
||||
label: string;
|
||||
needsApiKey: boolean;
|
||||
needsUrl: boolean;
|
||||
envVar?: string;
|
||||
defaultModel: string;
|
||||
defaultUrl?: string;
|
||||
}
|
||||
|
||||
export const LLM_PROVIDERS: ProviderDef[] = [
|
||||
{ id: "openai", label: "OpenAI (requires API key)", needsApiKey: true, needsUrl: false, envVar: "OPENAI_API_KEY", defaultModel: "gpt-5-mini" },
|
||||
{ id: "ollama", label: "Ollama (local, no API key)", needsApiKey: false, needsUrl: true, defaultModel: "llama3.1:8b", defaultUrl: "http://localhost:11434" },
|
||||
{ id: "anthropic", label: "Anthropic (requires API key)", needsApiKey: true, needsUrl: false, envVar: "ANTHROPIC_API_KEY", defaultModel: "claude-sonnet-4-5-20250514" },
|
||||
];
|
||||
|
||||
export interface EmbedderDef extends ProviderDef {
|
||||
defaultDims: number;
|
||||
}
|
||||
|
||||
export const EMBEDDER_PROVIDERS: EmbedderDef[] = [
|
||||
{ id: "openai", label: "OpenAI (requires API key)", needsApiKey: true, needsUrl: false, envVar: "OPENAI_API_KEY", defaultModel: "text-embedding-3-small", defaultDims: 1536 },
|
||||
{ id: "ollama", label: "Ollama (local, no API key)", needsApiKey: false, needsUrl: true, defaultModel: "nomic-embed-text", defaultUrl: "http://localhost:11434", defaultDims: 768 },
|
||||
];
|
||||
|
||||
export interface VectorDef {
|
||||
id: string;
|
||||
label: string;
|
||||
needsConnection: boolean;
|
||||
defaultUrl?: string;
|
||||
defaultPort?: number;
|
||||
setupHint?: string;
|
||||
}
|
||||
|
||||
export const VECTOR_PROVIDERS: VectorDef[] = [
|
||||
{ id: "qdrant", label: "Qdrant (requires server — Docker or cloud)", needsConnection: true, defaultUrl: "http://localhost:6333", defaultPort: 6333, setupHint: "docker run -d -p 6333:6333 qdrant/qdrant" },
|
||||
{ id: "pgvector", label: "PGVector (requires PostgreSQL + pgvector extension)", needsConnection: true, defaultPort: 5432, setupHint: "docker run -d -p 5432:5432 -e POSTGRES_PASSWORD=postgres pgvector/pgvector:pg17" },
|
||||
];
|
||||
|
||||
export const KNOWN_EMBEDDER_DIMS: Record<string, number> = {
|
||||
"text-embedding-3-small": 1536,
|
||||
"text-embedding-3-large": 3072,
|
||||
"text-embedding-ada-002": 1536,
|
||||
"nomic-embed-text": 768,
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Config builders
|
||||
// ============================================================================
|
||||
|
||||
export interface LlmConfigInput {
|
||||
apiKey?: string;
|
||||
model?: string;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
export function buildOssLlmConfig(
|
||||
providerId: string,
|
||||
input: LlmConfigInput,
|
||||
): { provider: string; config: Record<string, unknown> } {
|
||||
const def = LLM_PROVIDERS.find((p) => p.id === providerId);
|
||||
if (!def) throw new Error(`Unknown LLM provider: ${providerId}`);
|
||||
|
||||
const config: Record<string, unknown> = {
|
||||
model: input.model || def.defaultModel,
|
||||
};
|
||||
if (input.apiKey) config.apiKey = input.apiKey;
|
||||
if (providerId === "ollama") {
|
||||
config.url = input.url || def.defaultUrl;
|
||||
}
|
||||
return { provider: providerId, config };
|
||||
}
|
||||
|
||||
export interface EmbedderConfigInput {
|
||||
apiKey?: string;
|
||||
model?: string;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
export function buildOssEmbedderConfig(
|
||||
providerId: string,
|
||||
input: EmbedderConfigInput,
|
||||
): { provider: string; config: Record<string, unknown>; dims: number | undefined } {
|
||||
const def = EMBEDDER_PROVIDERS.find((p) => p.id === providerId);
|
||||
if (!def) throw new Error(`Unknown embedder provider: ${providerId}`);
|
||||
|
||||
const model = input.model || def.defaultModel;
|
||||
const config: Record<string, unknown> = { model };
|
||||
if (input.apiKey) config.apiKey = input.apiKey;
|
||||
if (providerId === "ollama") {
|
||||
config.url = input.url || def.defaultUrl;
|
||||
}
|
||||
|
||||
const dims = KNOWN_EMBEDDER_DIMS[model] ?? undefined;
|
||||
return { provider: providerId, config, dims };
|
||||
}
|
||||
|
||||
export interface VectorConfigInput {
|
||||
url?: string;
|
||||
host?: string;
|
||||
port?: string;
|
||||
user?: string;
|
||||
password?: string;
|
||||
dbname?: string;
|
||||
apiKey?: string;
|
||||
dims?: number;
|
||||
}
|
||||
|
||||
export function buildOssVectorConfig(
|
||||
providerId: string,
|
||||
input: VectorConfigInput,
|
||||
): { provider: string; config: Record<string, unknown> } {
|
||||
const config: Record<string, unknown> = {};
|
||||
|
||||
if (providerId === "qdrant") {
|
||||
config.url = input.url || "http://localhost:6333";
|
||||
config.onDisk = true;
|
||||
if (input.apiKey) config.apiKey = input.apiKey;
|
||||
} else if (providerId === "pgvector") {
|
||||
config.host = input.host || "localhost";
|
||||
config.port = parseInt(input.port || "5432", 10);
|
||||
if (input.user) config.user = input.user;
|
||||
if (input.password) config.password = input.password;
|
||||
config.dbname = input.dbname || "postgres";
|
||||
}
|
||||
|
||||
if (input.dims) config.dimension = input.dims;
|
||||
return { provider: providerId, config };
|
||||
}
|
||||
|
||||
export async function checkOllamaConnectivity(url: string): Promise<{ ok: boolean; error?: string }> {
|
||||
try {
|
||||
const resp = await fetch(`${url.replace(/\/+$/, "")}/api/tags`, { signal: AbortSignal.timeout(3000) });
|
||||
if (resp.ok) return { ok: true };
|
||||
return { ok: false, error: `Ollama returned HTTP ${resp.status}` };
|
||||
} catch {
|
||||
return { ok: false, error: `Cannot reach Ollama at ${url}. Install: https://ollama.com/download` };
|
||||
}
|
||||
}
|
||||
|
||||
export async function checkPgConnectivity(host: string, port: number): Promise<{ ok: boolean; error?: string }> {
|
||||
return new Promise((resolve) => {
|
||||
import("node:net").then(({ createConnection }) => {
|
||||
const sock = createConnection({ host, port, timeout: 3000 });
|
||||
sock.once("connect", () => { sock.destroy(); resolve({ ok: true }); });
|
||||
sock.once("timeout", () => { sock.destroy(); resolve({ ok: false, error: `PostgreSQL not reachable at ${host}:${port}` }); });
|
||||
sock.once("error", () => { sock.destroy(); resolve({ ok: false, error: `PostgreSQL not reachable at ${host}:${port}. Ensure PostgreSQL with pgvector extension is running.` }); });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function checkQdrantConnectivity(url: string): Promise<{ ok: boolean; error?: string }> {
|
||||
try {
|
||||
const resp = await fetch(`${url.replace(/\/+$/, "")}/healthz`, { signal: AbortSignal.timeout(3000) });
|
||||
if (resp.ok) return { ok: true };
|
||||
return { ok: false, error: `Qdrant returned HTTP ${resp.status}` };
|
||||
} catch (err) {
|
||||
return { ok: false, error: `Cannot reach Qdrant at ${url}. Start it with: docker run -d -p 6333:6333 qdrant/qdrant` };
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Non-interactive flag validation
|
||||
// ============================================================================
|
||||
|
||||
export interface OssFlags {
|
||||
ossLlm?: string;
|
||||
ossLlmKey?: string;
|
||||
ossLlmModel?: string;
|
||||
ossLlmUrl?: string;
|
||||
ossEmbedder?: string;
|
||||
ossEmbedderKey?: string;
|
||||
ossEmbedderModel?: string;
|
||||
ossEmbedderUrl?: string;
|
||||
ossVector?: string;
|
||||
ossVectorUrl?: string;
|
||||
ossVectorHost?: string;
|
||||
ossVectorPort?: string;
|
||||
ossVectorUser?: string;
|
||||
ossVectorPassword?: string;
|
||||
ossVectorDbname?: string;
|
||||
ossVectorDims?: string;
|
||||
}
|
||||
|
||||
export function validateOssFlags(
|
||||
flags: OssFlags,
|
||||
): { error?: string } {
|
||||
const llmId = flags.ossLlm || "openai";
|
||||
const llmDef = LLM_PROVIDERS.find((p) => p.id === llmId);
|
||||
if (!llmDef) return { error: `Unknown LLM provider: ${llmId}. Valid: ${LLM_PROVIDERS.map((p) => p.id).join(", ")}` };
|
||||
|
||||
if (llmDef.needsApiKey && !flags.ossLlmKey) {
|
||||
return { error: `--oss-llm-key required when --oss-llm is ${llmId}` };
|
||||
}
|
||||
|
||||
const embId = flags.ossEmbedder || "openai";
|
||||
const embDef = EMBEDDER_PROVIDERS.find((p) => p.id === embId);
|
||||
if (!embDef) return { error: `Unknown embedder provider: ${embId}. Valid: ${EMBEDDER_PROVIDERS.map((p) => p.id).join(", ")}` };
|
||||
|
||||
if (embDef.needsApiKey && !flags.ossEmbedderKey && !flags.ossLlmKey) {
|
||||
return { error: `--oss-embedder-key required when --oss-embedder is ${embId}` };
|
||||
}
|
||||
|
||||
const vecId = flags.ossVector || "qdrant";
|
||||
const vecDef = VECTOR_PROVIDERS.find((p) => p.id === vecId);
|
||||
if (!vecDef) return { error: `Unknown vector store provider: ${vecId}. Valid: ${VECTOR_PROVIDERS.map((p) => p.id).join(", ")}` };
|
||||
|
||||
if (vecId === "pgvector" && !flags.ossVectorUser) {
|
||||
return { error: "--oss-vector-user required when --oss-vector is pgvector" };
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
+2
-2
@@ -231,8 +231,8 @@ export const mem0ConfigSchema = {
|
||||
return "default";
|
||||
}
|
||||
})(),
|
||||
autoCapture: cfg.autoCapture !== false,
|
||||
autoRecall: cfg.autoRecall !== false,
|
||||
autoCapture: cfg.autoCapture === true,
|
||||
autoRecall: cfg.autoRecall === true,
|
||||
// v3.0.0: customPrompt renamed to customInstructions (backwards-compat: accept either)
|
||||
customInstructions:
|
||||
typeof cfg.customInstructions === "string"
|
||||
|
||||
+1
-1
@@ -128,7 +128,7 @@ const memoryPlugin = definePluginEntry({
|
||||
"openclaw-mem0: API key not configured. Memory features are disabled.\n" +
|
||||
" To set up, run:\n" +
|
||||
" openclaw mem0 init\n" +
|
||||
" Get your key at: https://app.mem0.ai/dashboard/api-keys",
|
||||
" Get your key at: https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=openclaw-src",
|
||||
);
|
||||
|
||||
// Register CLI even without API key — init command must be available
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
{
|
||||
"id": "openclaw-mem0",
|
||||
"name": "Memory (Mem0)",
|
||||
"description": "Mem0 memory backend for OpenClaw — platform or self-hosted open-source. PLATFORM MODE: Sends conversation data to mem0.ai cloud (requires MEM0_API_KEY). OPEN-SOURCE MODE: Stores vectors locally (~/.mem0/history.db) but uses external APIs for embeddings/LLM (default: OpenAI, requires OPENAI_API_KEY). Auto-recall injects memories before agent turns; auto-capture extracts facts after turns. Both configurable via autoRecall/autoCapture settings. Config stored in ~/.openclaw/openclaw.json.",
|
||||
"version": "1.0.7",
|
||||
"description": "Mem0 memory backend for OpenClaw — platform (mem0.ai cloud) or self-hosted open-source. Auto-recall and auto-capture are opt-in (disabled by default). Supports OpenAI, Anthropic, Ollama (fully local), Qdrant, and PGVector providers.",
|
||||
"version": "1.0.10",
|
||||
"kind": "memory",
|
||||
"skills": ["skills"],
|
||||
"commandAliases": [
|
||||
{
|
||||
"name": "mem0",
|
||||
"cliCommand": "mem0",
|
||||
"description": "Mem0 memory plugin commands"
|
||||
"cliCommand": "mem0"
|
||||
}
|
||||
],
|
||||
"contracts": {
|
||||
@@ -20,7 +19,7 @@
|
||||
},
|
||||
"providerAuthEnvVars": {
|
||||
"mem0": ["MEM0_API_KEY"],
|
||||
"openclaw-mem0-oss": ["OPENAI_API_KEY", "ANTHROPIC_API_KEY", "AZURE_OPENAI_API_KEY", "COHERE_API_KEY"]
|
||||
"openclaw-mem0-oss": ["OPENAI_API_KEY", "ANTHROPIC_API_KEY"]
|
||||
},
|
||||
"providerAuthChoices": [
|
||||
{
|
||||
@@ -28,13 +27,39 @@
|
||||
"method": "api-key",
|
||||
"choiceId": "mem0-api-key",
|
||||
"choiceLabel": "Mem0 API key",
|
||||
"choiceHint": "Required for platform mode. Get your key at https://app.mem0.ai/dashboard/api-keys",
|
||||
"choiceHint": "Required for platform mode. Get your key at https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=openclaw-plugin",
|
||||
"groupId": "mem0",
|
||||
"groupLabel": "Mem0",
|
||||
"optionKey": "apiKey",
|
||||
"cliFlag": "--mem0-api-key",
|
||||
"cliOption": "--mem0-api-key <key>",
|
||||
"cliDescription": "Mem0 platform API key"
|
||||
},
|
||||
{
|
||||
"provider": "openclaw-mem0-oss",
|
||||
"method": "config",
|
||||
"choiceId": "oss-openai",
|
||||
"choiceLabel": "Open Source with OpenAI",
|
||||
"choiceHint": "Self-hosted mode using OpenAI for LLM and embeddings",
|
||||
"groupId": "oss",
|
||||
"groupLabel": "Open Source (self-hosted)",
|
||||
"optionKey": "oss.llm.config.apiKey",
|
||||
"cliFlag": "--oss-llm-key",
|
||||
"cliOption": "--oss-llm-key <key>",
|
||||
"cliDescription": "OpenAI API key for OSS LLM"
|
||||
},
|
||||
{
|
||||
"provider": "openclaw-mem0-oss",
|
||||
"method": "config",
|
||||
"choiceId": "oss-ollama",
|
||||
"choiceLabel": "Open Source with Ollama (local)",
|
||||
"choiceHint": "Fully local mode, no API keys needed",
|
||||
"groupId": "oss",
|
||||
"groupLabel": "Open Source (self-hosted)",
|
||||
"optionKey": "oss.llm.config.ollama_base_url",
|
||||
"cliFlag": "--oss-llm-url",
|
||||
"cliOption": "--oss-llm-url <url>",
|
||||
"cliDescription": "Ollama base URL for local LLM"
|
||||
}
|
||||
],
|
||||
"uiHints": {
|
||||
@@ -78,19 +103,37 @@
|
||||
},
|
||||
"searchThreshold": {
|
||||
"label": "Search Threshold",
|
||||
"placeholder": "0.5",
|
||||
"help": "Minimum similarity score for search results (0-1). Default: 0.5"
|
||||
"placeholder": "0.3",
|
||||
"help": "Minimum similarity score for search results (0-1). Default: 0.3"
|
||||
},
|
||||
"topK": {
|
||||
"label": "Top K Results",
|
||||
"placeholder": "5",
|
||||
"help": "Maximum number of memories to retrieve"
|
||||
},
|
||||
"userEmail": {
|
||||
"label": "User Email",
|
||||
"sensitive": true,
|
||||
"advanced": true,
|
||||
"help": "Email address associated with the Mem0 account. Set automatically during platform login."
|
||||
},
|
||||
"oss": {
|
||||
"label": "Open-Source Configuration",
|
||||
"advanced": true,
|
||||
"help": "Optional. Configure custom embedder, vector store, LLM, or history DB for open-source mode. For API keys in sub-provider configs, use SecretRef objects or ${VAR} syntax instead of plaintext values."
|
||||
},
|
||||
"oss.llm.config.apiKey": {
|
||||
"label": "OSS LLM API Key",
|
||||
"sensitive": true,
|
||||
"advanced": true,
|
||||
"help": "API key for open-source LLM provider. Use SecretRef or ${VAR} syntax."
|
||||
},
|
||||
"oss.embedder.config.apiKey": {
|
||||
"label": "OSS Embedder API Key",
|
||||
"sensitive": true,
|
||||
"advanced": true,
|
||||
"help": "API key for open-source embedder provider. Use SecretRef or ${VAR} syntax."
|
||||
},
|
||||
"skills": {
|
||||
"label": "Agentic Memory Skills",
|
||||
"advanced": true,
|
||||
@@ -106,22 +149,35 @@
|
||||
"enum": [
|
||||
"platform",
|
||||
"open-source"
|
||||
]
|
||||
],
|
||||
"description": "Required. 'platform' requires MEM0_API_KEY. 'open-source' requires OPENAI_API_KEY (default) or no keys with Ollama."
|
||||
},
|
||||
"apiKey": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"sensitive": true,
|
||||
"description": "Platform API key. Prefer SecretRef or ${MEM0_API_KEY} env var over plaintext."
|
||||
},
|
||||
"userId": {
|
||||
"type": "string"
|
||||
},
|
||||
"baseUrl": {
|
||||
"type": "string",
|
||||
"description": "API base URL override (default: https://api.mem0.ai)"
|
||||
},
|
||||
"userEmail": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"sensitive": true,
|
||||
"description": "Email associated with Mem0 account. Set automatically during platform login."
|
||||
},
|
||||
"autoCapture": {
|
||||
"type": "boolean"
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Opt-in. When true, extracts durable facts after each agent turn. Disabled by default."
|
||||
},
|
||||
"autoRecall": {
|
||||
"type": "boolean"
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Opt-in. When true, injects relevant memories before each agent turn. Disabled by default."
|
||||
},
|
||||
"customInstructions": {
|
||||
"type": "string"
|
||||
@@ -238,5 +294,19 @@
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
}
|
||||
},
|
||||
"providerEndpoints": [
|
||||
{
|
||||
"endpointClass": "api",
|
||||
"hosts": ["api.mem0.ai"]
|
||||
},
|
||||
{
|
||||
"endpointClass": "dashboard",
|
||||
"hosts": ["app.mem0.ai"]
|
||||
},
|
||||
{
|
||||
"endpointClass": "telemetry",
|
||||
"hosts": ["us.i.posthog.com"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@mem0/openclaw-mem0",
|
||||
"version": "1.0.7",
|
||||
"version": "1.0.10",
|
||||
"type": "module",
|
||||
"description": "Mem0 memory backend for OpenClaw — platform or self-hosted open-source",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
+28
-5
@@ -214,6 +214,7 @@ class PlatformProvider implements Mem0Provider {
|
||||
// ============================================================================
|
||||
|
||||
class OSSProvider implements Mem0Provider {
|
||||
private static _warnPatched = false;
|
||||
private memory: any; // Memory from mem0ai/oss
|
||||
private initPromise: Promise<void> | null = null;
|
||||
|
||||
@@ -241,7 +242,7 @@ class OSSProvider implements Mem0Provider {
|
||||
provider: "openai",
|
||||
config: { model: "text-embedding-3-small" },
|
||||
};
|
||||
const defaultLlm = { provider: "openai", config: { model: "gpt-5.4" } };
|
||||
const defaultLlm = { provider: "openai", config: { model: "gpt-5-mini" } };
|
||||
|
||||
const stripEmpty = (obj: Record<string, unknown>) => {
|
||||
const out = { ...obj };
|
||||
@@ -325,13 +326,35 @@ class OSSProvider implements Mem0Provider {
|
||||
VectorCls.prototype.__patched = true;
|
||||
}
|
||||
|
||||
// Proactively detect broken better-sqlite3 native binding (e.g. Node
|
||||
// version mismatch) and skip history to avoid noisy constructor failures.
|
||||
let sqliteOk = true;
|
||||
if (!this.ossConfig?.disableHistory) {
|
||||
try {
|
||||
// @ts-ignore — better-sqlite3 is a transitive dep; no types in this package
|
||||
const bs3Mod = await import("better-sqlite3");
|
||||
const BS3 = bs3Mod.default ?? bs3Mod;
|
||||
const testDb = new (BS3 as any)(":memory:");
|
||||
(testDb as any).close();
|
||||
} catch {
|
||||
sqliteOk = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!OSSProvider._warnPatched) {
|
||||
const origWarn = console.warn;
|
||||
console.warn = (...args: unknown[]) => {
|
||||
if (typeof args[0] === "string" && args[0].includes("checkCompatibility")) return;
|
||||
origWarn.apply(console, args);
|
||||
};
|
||||
OSSProvider._warnPatched = true;
|
||||
}
|
||||
|
||||
let mem: any;
|
||||
try {
|
||||
mem = new Memory(this._buildConfig());
|
||||
mem = new Memory(this._buildConfig(!sqliteOk));
|
||||
} catch (err) {
|
||||
// If constructor fails (e.g. native SQLite binding under jiti/Docker),
|
||||
// retry with a FRESH config that has history disabled.
|
||||
if (!this.ossConfig?.disableHistory) {
|
||||
if (!this.ossConfig?.disableHistory && sqliteOk) {
|
||||
console.warn(
|
||||
"[mem0] Memory initialization failed, retrying with history disabled:",
|
||||
err instanceof Error ? err.message : err,
|
||||
|
||||
@@ -192,21 +192,16 @@ function renderCategoriesBlock(
|
||||
}
|
||||
|
||||
function renderTriageKnobs(config: SkillsConfig): string {
|
||||
const triage = config.triage;
|
||||
if (!triage) return "";
|
||||
|
||||
const lines: string[] = [];
|
||||
|
||||
if (triage.importanceThreshold !== undefined) {
|
||||
if (config.triage?.importanceThreshold !== undefined) {
|
||||
lines.push(
|
||||
`- Only store facts with importance >= ${triage.importanceThreshold}`,
|
||||
`- Only store facts with importance >= ${config.triage.importanceThreshold}`,
|
||||
);
|
||||
}
|
||||
|
||||
const patterns = resolveCredentialPatterns(config);
|
||||
if (config.triage?.credentialPatterns) {
|
||||
lines.push(`- Credential patterns to scan: ${patterns.join(", ")}`);
|
||||
}
|
||||
lines.push(`- Credential patterns to scan: ${patterns.map((p) => `\`${p}\``).join(", ")}`);
|
||||
|
||||
if (lines.length === 0) return "";
|
||||
return "\n## Active Configuration Overrides\n\n" + lines.join("\n");
|
||||
@@ -265,8 +260,8 @@ export function loadSkill(
|
||||
parts.push(renderCategoriesBlock(mergedCats));
|
||||
}
|
||||
|
||||
// Inject triage knobs (maxFactsPerTurn, importanceThreshold, credentialPatterns)
|
||||
if (skillName === "memory-triage") {
|
||||
// Inject triage knobs (importanceThreshold, credentialPatterns)
|
||||
if (skillName === "memory-triage" || skillName === "memory-dream") {
|
||||
const knobs = renderTriageKnobs(config);
|
||||
if (knobs) parts.push(knobs);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ description: >
|
||||
Also triggers automatically after sufficient activity (configurable).
|
||||
user-invocable: true
|
||||
metadata:
|
||||
{"openclaw": {"emoji": "💤", "requires": {"env": ["MEM0_API_KEY"], "bins": []}}}
|
||||
{"openclaw": {"injected": true, "emoji": "💤", "requires": {"env": ["MEM0_API_KEY", "OPENAI_API_KEY", "ANTHROPIC_API_KEY"], "bins": []}}}
|
||||
---
|
||||
|
||||
# Memory Consolidation
|
||||
@@ -46,7 +46,7 @@ Execute the actions identified in Phase 2. Work in this priority order:
|
||||
### 3a. Delete dangerous and expired entries
|
||||
|
||||
Delete immediately using `memory_delete`:
|
||||
- Credentials, API keys, tokens, passwords, secrets (patterns: sk-, m0-, ghp_, AKIA, Bearer, password=, token=, secret=)
|
||||
- Credentials, API keys, tokens, passwords, secrets (matching known credential prefixes and auth patterns injected by the plugin at runtime)
|
||||
- Pure timestamps with no context
|
||||
- Raw tool output stored as memory
|
||||
- Heartbeat or cron execution records
|
||||
|
||||
@@ -7,7 +7,7 @@ description: >
|
||||
projects, and relationships. Loaded by the openclaw-mem0 plugin when skills mode is active.
|
||||
user-invocable: false
|
||||
metadata:
|
||||
{"openclaw": {"always": false, "emoji": "🧠", "requires": {"env": ["MEM0_API_KEY"], "bins": []}}}
|
||||
{"openclaw": {"always": false, "injected": true, "emoji": "🧠", "requires": {"env": ["MEM0_API_KEY", "OPENAI_API_KEY", "ANTHROPIC_API_KEY"], "bins": []}}}
|
||||
---
|
||||
|
||||
# Memory Protocol
|
||||
@@ -37,9 +37,9 @@ Every candidate fact must pass ALL four gates:
|
||||
- Fail: vague impressions, questions, small talk, acknowledgments, generic assistant responses ("Sure, I can help") → SKIP
|
||||
|
||||
**Gate 4 — SAFE**: Does this contain ANY credential, secret, or token?
|
||||
- Scan for: `sk-`, `m0-`, `ghp_`, `AKIA`, `ak_`, `Bearer `, bot tokens (digits:alphanumeric), webhook URLs with tokens, pairing codes, long alphanumeric strings in config/env context, `password=`, `token=`, `secret=`, `.env` values
|
||||
- Scan for known credential prefixes, auth tokens, webhook URLs with tokens, pairing codes, long alphanumeric strings in config/env context, and key-value assignment patterns. The plugin injects the full pattern list at runtime.
|
||||
- ANY match → NEVER STORE the value. Instead, store that the credential was configured:
|
||||
- WRONG: "User's API key is sk-abc123..."
|
||||
- WRONG: "User's API key is [redacted]"
|
||||
- RIGHT: "API key was configured for the service (as of 2026-03-30)"
|
||||
- When in doubt → SKIP. No exceptions.
|
||||
|
||||
@@ -218,7 +218,7 @@ When a recalled memory needs updating (fact changed, status changed, new detail
|
||||
|
||||
## What NEVER to Store
|
||||
|
||||
- **Credentials and secrets** — even embedded in config blocks, setup logs, or tool output. Includes sk-, m0-, ak_, ghp_, bot tokens, bearer tokens, webhook URLs with tokens, pairing codes, long alphanumeric strings in config/env contexts. Record that the credential was configured, never the value itself.
|
||||
- **Credentials and secrets** — even embedded in config blocks, setup logs, or tool output. Includes any known credential prefixes, auth tokens, bearer tokens, webhook URLs with tokens, pairing codes, and long alphanumeric strings in config/env contexts. Record that the credential was configured, never the value itself.
|
||||
- **Raw tool output** — bash results, file contents, API responses, logs, diffs, test output. Extract only the durable OUTCOME or ROOT CAUSE.
|
||||
- **One-time commands** — "stop the script", "continue where you left off", "run this"
|
||||
- **Acknowledgments and emotional reactions** — "ok", "sure", "sounds good", "sir", "got it", "thanks", "you're right"
|
||||
@@ -280,7 +280,7 @@ Agent: [updates the sheet successfully]
|
||||
|
||||
### Example 7: Credential — store the fact, not the value
|
||||
```
|
||||
User: "Use this API key for the new service: sk-proj-abc123def456"
|
||||
User: "Use this API key for the new service: [credential value]"
|
||||
Agent: [configures the service]
|
||||
→ memory_add(facts: ["API key was configured for the new service (as of 2026-03-30)"], category: "configuration")
|
||||
```
|
||||
|
||||
@@ -119,7 +119,12 @@ describe("OSSProvider — disableHistory passthrough to Memory", () => {
|
||||
expect(capturedConfig!.disableHistory).toBe(true);
|
||||
});
|
||||
|
||||
it("does not set disableHistory when not configured", async () => {
|
||||
it("does not set disableHistory when not configured and sqlite works", async () => {
|
||||
// Mock better-sqlite3 so the proactive probe succeeds
|
||||
vi.doMock("better-sqlite3", () => {
|
||||
return { default: class { close() {} } };
|
||||
});
|
||||
|
||||
const { createProvider } = await import("./index.ts");
|
||||
const cfg = mem0ConfigSchema.parse({
|
||||
mode: "open-source",
|
||||
@@ -248,6 +253,12 @@ describe("OSSProvider — graceful SQLite fallback", () => {
|
||||
});
|
||||
|
||||
it("retries with disableHistory: true when initial construction fails", async () => {
|
||||
// Mock better-sqlite3 so the proactive probe succeeds — tests the
|
||||
// catch-retry fallback path for other constructor errors.
|
||||
vi.doMock("better-sqlite3", () => {
|
||||
return { default: class { close() {} } };
|
||||
});
|
||||
|
||||
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||
const { createProvider } = await import("./index.ts");
|
||||
const cfg = mem0ConfigSchema.parse({
|
||||
@@ -274,6 +285,29 @@ describe("OSSProvider — graceful SQLite fallback", () => {
|
||||
warnSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("proactively disables history when better-sqlite3 binary is broken", async () => {
|
||||
// Do NOT mock better-sqlite3 — let probe detect the real version mismatch
|
||||
// (or force it to fail if native binary happens to work on this Node).
|
||||
vi.doMock("better-sqlite3", () => {
|
||||
return { default: class { constructor() { throw new Error("NODE_MODULE_VERSION mismatch"); } } };
|
||||
});
|
||||
|
||||
const { createProvider } = await import("./index.ts");
|
||||
const cfg = mem0ConfigSchema.parse({
|
||||
mode: "open-source",
|
||||
oss: {},
|
||||
});
|
||||
const api = { resolvePath: (p: string) => p } as any;
|
||||
const provider = createProvider(cfg, api);
|
||||
|
||||
const results = await provider.search("test", { user_id: "u1" });
|
||||
expect(results).toBeDefined();
|
||||
|
||||
// Only ONE constructor call — probe detected broken sqlite, skipped retry
|
||||
expect(capturedConfigs).toHaveLength(1);
|
||||
expect(capturedConfigs[0].disableHistory).toBe(true);
|
||||
});
|
||||
|
||||
it("does not retry when disableHistory is already true", async () => {
|
||||
// Force the constructor to always throw, regardless of disableHistory
|
||||
forceConstructorError = "vector store connection refused";
|
||||
|
||||
+12
-18
@@ -11,7 +11,8 @@
|
||||
import { createHash, randomUUID } from "node:crypto";
|
||||
import { readPluginAuth, writePluginAuth, getBaseUrl, clearAnonymousTelemetryId } from "./cli/config-file.ts";
|
||||
|
||||
export const PLUGIN_VERSION = "1.0.7";
|
||||
declare const __OPENCLAW_PLUGIN_VERSION__: string;
|
||||
export const PLUGIN_VERSION: string = __OPENCLAW_PLUGIN_VERSION__;
|
||||
|
||||
const POSTHOG_API_KEY = "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX";
|
||||
const POSTHOG_HOST = "https://us.i.posthog.com/i/v0/e/";
|
||||
@@ -129,18 +130,11 @@ function maybeResolveEmail(apiKey: string): void {
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
// Upgrade any already-queued events from md5(apiKey) to email
|
||||
const oldId = createHash("md5").update(apiKey).digest("hex");
|
||||
const oldId = createHash("sha256").update(apiKey).digest("hex");
|
||||
const newId = createHash("sha256").update(email).digest("hex");
|
||||
for (const ev of eventQueue) {
|
||||
if (ev.distinct_id === oldId) {
|
||||
ev.distinct_id = email;
|
||||
}
|
||||
// Also upgrade $identify's distinct_id if present
|
||||
if (
|
||||
ev.event === "$identify" &&
|
||||
ev.distinct_id === oldId
|
||||
) {
|
||||
ev.distinct_id = email;
|
||||
ev.distinct_id = newId;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -176,12 +170,14 @@ function isTelemetryEnabled(): boolean {
|
||||
function getDistinctId(apiKey?: string): string {
|
||||
try {
|
||||
const auth = readPluginAuth();
|
||||
if (auth.userEmail) return auth.userEmail;
|
||||
if (auth.userEmail) {
|
||||
return createHash("sha256").update(auth.userEmail).digest("hex");
|
||||
}
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
if (apiKey) {
|
||||
return createHash("md5").update(apiKey).digest("hex");
|
||||
return createHash("sha256").update(apiKey).digest("hex");
|
||||
}
|
||||
return getOrCreateAnonymousId();
|
||||
}
|
||||
@@ -262,11 +258,9 @@ export function captureEvent(
|
||||
try {
|
||||
const distinctId = getDistinctId(ctx?.apiKey);
|
||||
|
||||
// If we resolved to md5(apiKey) instead of email, kick off a background
|
||||
// /v1/ping/ to resolve and cache the email. The current event ships with
|
||||
// the hash, but the async resolution upgrades any still-queued events
|
||||
// (including this one) before the beforeExit flush fires.
|
||||
if (ctx?.apiKey && distinctId && !distinctId.includes("@") && !distinctId.startsWith("openclaw-anon-")) {
|
||||
let hasEmail = false;
|
||||
try { hasEmail = !!readPluginAuth().userEmail; } catch { /* ignore */ }
|
||||
if (ctx?.apiKey && !hasEmail && !distinctId.startsWith("openclaw-anon-")) {
|
||||
maybeResolveEmail(ctx.apiKey);
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ vi.mock("../skill-loader.ts", () => ({
|
||||
loadDreamPrompt: vi.fn().mockReturnValue("dream prompt"),
|
||||
}));
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Imports (after mocks)
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -251,6 +252,7 @@ describe("registerCliCommands", () => {
|
||||
warn: ReturnType<typeof vi.spyOn>;
|
||||
};
|
||||
let stderrSpy: ReturnType<typeof vi.spyOn>;
|
||||
let stdoutSpy: ReturnType<typeof vi.spyOn>;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
@@ -267,6 +269,7 @@ describe("registerCliCommands", () => {
|
||||
warn: vi.spyOn(console, "warn").mockImplementation(() => {}),
|
||||
};
|
||||
stderrSpy = vi.spyOn(process.stderr, "write").mockImplementation(() => true);
|
||||
stdoutSpy = vi.spyOn(process.stdout, "write").mockImplementation(() => true);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -274,6 +277,7 @@ describe("registerCliCommands", () => {
|
||||
consoleSpy.error.mockRestore();
|
||||
consoleSpy.warn.mockRestore();
|
||||
stderrSpy.mockRestore();
|
||||
stdoutSpy.mockRestore();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
@@ -527,6 +531,158 @@ describe("registerCliCommands", () => {
|
||||
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it("outputs JSON for --api-key flow when --json is set", async () => {
|
||||
const { mem0 } = setup();
|
||||
const initCmd = findCommand(mem0, "init")!;
|
||||
|
||||
vi.stubGlobal("fetch", vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: vi.fn().mockResolvedValue({}),
|
||||
}));
|
||||
|
||||
await initCmd._action!({ apiKey: "m0-key", json: true });
|
||||
|
||||
const jsonCall = stdoutSpy.mock.calls.find((c) => {
|
||||
try {
|
||||
const p = JSON.parse(c[0] as string);
|
||||
return typeof p.ok === "boolean";
|
||||
} catch { return false; }
|
||||
});
|
||||
expect(jsonCall).toBeDefined();
|
||||
const parsed = JSON.parse(jsonCall![0] as string);
|
||||
expect(parsed.ok).toBe(true);
|
||||
expect(parsed.mode).toBe("platform");
|
||||
expect(parsed.validated).toBe(true);
|
||||
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it("outputs JSON for --api-key flow with failed validation when --json is set", async () => {
|
||||
const { mem0 } = setup();
|
||||
const initCmd = findCommand(mem0, "init")!;
|
||||
|
||||
vi.stubGlobal("fetch", vi.fn().mockResolvedValue({
|
||||
ok: false,
|
||||
status: 401,
|
||||
json: vi.fn().mockResolvedValue({}),
|
||||
}));
|
||||
|
||||
await initCmd._action!({ apiKey: "bad-key", json: true });
|
||||
|
||||
const jsonCall = stdoutSpy.mock.calls.find((c) => {
|
||||
try {
|
||||
const p = JSON.parse(c[0] as string);
|
||||
return typeof p.ok === "boolean";
|
||||
} catch { return false; }
|
||||
});
|
||||
expect(jsonCall).toBeDefined();
|
||||
const parsed = JSON.parse(jsonCall![0] as string);
|
||||
expect(parsed.ok).toBe(false);
|
||||
expect(parsed.mode).toBe("platform");
|
||||
expect(parsed.validated).toBe(false);
|
||||
expect(parsed.httpStatus).toBe(401);
|
||||
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it("outputs JSON for --api-key + --email conflict when --json is set", async () => {
|
||||
const { mem0 } = setup();
|
||||
const initCmd = findCommand(mem0, "init")!;
|
||||
|
||||
await initCmd._action!({ apiKey: "key", email: "a@b.com", json: true });
|
||||
|
||||
const jsonCall = stdoutSpy.mock.calls.find((c) => {
|
||||
try {
|
||||
const p = JSON.parse(c[0] as string);
|
||||
return p.ok === false;
|
||||
} catch { return false; }
|
||||
});
|
||||
expect(jsonCall).toBeDefined();
|
||||
const parsed = JSON.parse(jsonCall![0] as string);
|
||||
expect(parsed.error).toContain("Cannot use both");
|
||||
expect(writePluginAuth).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("outputs JSON for email send-code flow when --json is set", async () => {
|
||||
const { mem0 } = setup();
|
||||
const initCmd = findCommand(mem0, "init")!;
|
||||
|
||||
vi.stubGlobal("fetch", vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: vi.fn().mockResolvedValue({}),
|
||||
}));
|
||||
|
||||
await initCmd._action!({ email: "user@example.com", json: true });
|
||||
|
||||
const jsonCall = stdoutSpy.mock.calls.find((c) => {
|
||||
try {
|
||||
const p = JSON.parse(c[0] as string);
|
||||
return p.codeSent === true;
|
||||
} catch { return false; }
|
||||
});
|
||||
expect(jsonCall).toBeDefined();
|
||||
const parsed = JSON.parse(jsonCall![0] as string);
|
||||
expect(parsed.ok).toBe(true);
|
||||
expect(parsed.email).toBe("user@example.com");
|
||||
expect(parsed.nextCommand).toContain("--code");
|
||||
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it("outputs JSON for email verify flow when --json is set", async () => {
|
||||
const { mem0 } = setup();
|
||||
const initCmd = findCommand(mem0, "init")!;
|
||||
|
||||
vi.stubGlobal("fetch", vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: vi.fn().mockResolvedValue({ api_key: "m0-verified" }),
|
||||
}));
|
||||
|
||||
await initCmd._action!({ email: "u@b.com", code: "123456", json: true });
|
||||
|
||||
const jsonCall = stdoutSpy.mock.calls.find((c) => {
|
||||
try {
|
||||
const p = JSON.parse(c[0] as string);
|
||||
return p.ok === true && p.mode === "platform";
|
||||
} catch { return false; }
|
||||
});
|
||||
expect(jsonCall).toBeDefined();
|
||||
const parsed = JSON.parse(jsonCall![0] as string);
|
||||
expect(parsed.email).toBe("u@b.com");
|
||||
expect(parsed.message).toContain("Authenticated");
|
||||
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it("clears stale apiKey when switching to OSS mode", async () => {
|
||||
vi.stubGlobal("fetch", vi.fn().mockResolvedValue({ ok: true, json: async () => ({}) }));
|
||||
|
||||
const { mem0 } = setup();
|
||||
const initCmd = findCommand(mem0, "init")!;
|
||||
|
||||
(readPluginAuth as ReturnType<typeof vi.fn>).mockReturnValue({
|
||||
apiKey: "m0-old-platform-key",
|
||||
mode: "platform",
|
||||
userId: "testuser",
|
||||
});
|
||||
|
||||
await initCmd._action!({
|
||||
mode: "open-source",
|
||||
ossLlm: "ollama",
|
||||
ossEmbedder: "ollama",
|
||||
ossVector: "qdrant",
|
||||
});
|
||||
|
||||
expect(writePluginAuth).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
apiKey: "",
|
||||
mode: "open-source",
|
||||
}),
|
||||
);
|
||||
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
});
|
||||
|
||||
// ========================================================================
|
||||
@@ -1456,4 +1612,141 @@ describe("registerCliCommands", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// ========================================================================
|
||||
// Restructured init menu flags
|
||||
// ========================================================================
|
||||
|
||||
describe("init — restructured menu", () => {
|
||||
it("registers --mode flag", () => {
|
||||
const { mem0 } = setup();
|
||||
const initCmd = findCommand(mem0, "init")!;
|
||||
const modeOpt = initCmd._options.find((o) => o.flags.includes("--mode"));
|
||||
expect(modeOpt).toBeDefined();
|
||||
});
|
||||
|
||||
it("registers --oss-llm flag", () => {
|
||||
const { mem0 } = setup();
|
||||
const initCmd = findCommand(mem0, "init")!;
|
||||
const opt = initCmd._options.find((o) => o.flags.includes("--oss-llm "));
|
||||
expect(opt).toBeDefined();
|
||||
});
|
||||
|
||||
it("registers --json flag on init", () => {
|
||||
const { mem0 } = setup();
|
||||
const initCmd = findCommand(mem0, "init")!;
|
||||
const opt = initCmd._options.find((o) => o.flags.includes("--json"));
|
||||
expect(opt).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ========================================================================
|
||||
// --json flag registration on all commands
|
||||
// ========================================================================
|
||||
|
||||
describe("--json flag registration", () => {
|
||||
for (const name of ["search", "add", "get", "list", "update", "delete", "status", "import", "dream"]) {
|
||||
it(`registers --json on ${name}`, () => {
|
||||
const { mem0 } = setup();
|
||||
const cmd = findCommand(mem0, name)!;
|
||||
const opt = cmd._options.find((o) => o.flags.includes("--json"));
|
||||
expect(opt).toBeDefined();
|
||||
});
|
||||
}
|
||||
|
||||
it("registers --json on config show", () => {
|
||||
const { mem0 } = setup();
|
||||
const configCmd = findCommand(mem0, "config")!;
|
||||
const showCmd = findCommand(configCmd, "show")!;
|
||||
expect(showCmd).toBeDefined();
|
||||
const opt = showCmd._options.find((o) => o.flags.includes("--json"));
|
||||
expect(opt).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ========================================================================
|
||||
// Non-interactive OSS init
|
||||
// ========================================================================
|
||||
|
||||
describe("init --mode open-source (non-interactive)", () => {
|
||||
it("writes LLM, embedder, and vector config for ollama + qdrant", async () => {
|
||||
vi.stubGlobal("fetch", vi.fn().mockResolvedValue({ ok: true, json: async () => ({}) }));
|
||||
|
||||
const { mem0 } = setup();
|
||||
const initCmd = findCommand(mem0, "init")!;
|
||||
|
||||
await initCmd._action!({
|
||||
mode: "open-source",
|
||||
ossLlm: "ollama",
|
||||
ossEmbedder: "ollama",
|
||||
ossVector: "qdrant",
|
||||
userId: "test-user",
|
||||
});
|
||||
|
||||
expect(writePluginConfigField).toHaveBeenCalledWith(
|
||||
["oss", "llm"],
|
||||
expect.objectContaining({ provider: "ollama" }),
|
||||
);
|
||||
expect(writePluginConfigField).toHaveBeenCalledWith(
|
||||
["oss", "embedder"],
|
||||
expect.objectContaining({ provider: "ollama" }),
|
||||
);
|
||||
expect(writePluginConfigField).toHaveBeenCalledWith(
|
||||
["oss", "vectorStore"],
|
||||
expect.objectContaining({ provider: "qdrant" }),
|
||||
);
|
||||
expect(writePluginAuth).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ mode: "open-source", userId: "test-user" }),
|
||||
);
|
||||
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it("outputs JSON when --json is passed", async () => {
|
||||
vi.stubGlobal("fetch", vi.fn().mockResolvedValue({ ok: true, json: async () => ({}) }));
|
||||
|
||||
const { mem0 } = setup();
|
||||
const initCmd = findCommand(mem0, "init")!;
|
||||
|
||||
await initCmd._action!({
|
||||
mode: "open-source",
|
||||
ossLlm: "ollama",
|
||||
ossEmbedder: "ollama",
|
||||
ossVector: "qdrant",
|
||||
json: true,
|
||||
});
|
||||
|
||||
const jsonCall = stdoutSpy.mock.calls.find((c) => {
|
||||
try {
|
||||
const p = JSON.parse(c[0] as string);
|
||||
return p.ok === true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
expect(jsonCall).toBeDefined();
|
||||
if (jsonCall) {
|
||||
const parsed = JSON.parse(jsonCall[0] as string);
|
||||
expect(parsed.mode).toBe("open-source");
|
||||
expect(parsed.config.llm.provider).toBe("ollama");
|
||||
}
|
||||
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it("errors when openai LLM has no key", async () => {
|
||||
const { mem0 } = setup();
|
||||
const initCmd = findCommand(mem0, "init")!;
|
||||
|
||||
vi.stubEnv("OPENAI_API_KEY", "");
|
||||
|
||||
await initCmd._action!({ mode: "open-source", ossLlm: "openai" });
|
||||
|
||||
expect(consoleSpy.error).toHaveBeenCalledWith(
|
||||
expect.stringContaining("--oss-llm-key"),
|
||||
);
|
||||
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -44,14 +44,14 @@ describe("mem0ConfigSchema.parse() — defaults", () => {
|
||||
expect(cfg.userId.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("autoCapture defaults to true", () => {
|
||||
it("autoCapture defaults to false", () => {
|
||||
const cfg = mem0ConfigSchema.parse({ apiKey: "test-key" });
|
||||
expect(cfg.autoCapture).toBe(true);
|
||||
expect(cfg.autoCapture).toBe(false);
|
||||
});
|
||||
|
||||
it("autoRecall defaults to true", () => {
|
||||
it("autoRecall defaults to false", () => {
|
||||
const cfg = mem0ConfigSchema.parse({ apiKey: "test-key" });
|
||||
expect(cfg.autoRecall).toBe(true);
|
||||
expect(cfg.autoRecall).toBe(false);
|
||||
});
|
||||
|
||||
it("topK defaults to 5", () => {
|
||||
|
||||
@@ -1,36 +1,30 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
||||
import { bootstrapTelemetryFlag } from "../fs-safe.ts";
|
||||
|
||||
describe("bootstrapTelemetryFlag", () => {
|
||||
const originalEnv = process.env.MEM0_TELEMETRY;
|
||||
|
||||
beforeEach(() => {
|
||||
delete (globalThis as any).__mem0_telemetry_override;
|
||||
delete process.env.MEM0_TELEMETRY;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
delete (globalThis as any).__mem0_telemetry_override;
|
||||
if (originalEnv !== undefined) {
|
||||
process.env.MEM0_TELEMETRY = originalEnv;
|
||||
} else {
|
||||
delete process.env.MEM0_TELEMETRY;
|
||||
}
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
|
||||
it("sets globalThis override when MEM0_TELEMETRY is set", () => {
|
||||
process.env.MEM0_TELEMETRY = "false";
|
||||
vi.stubEnv("MEM0_TELEMETRY", "false");
|
||||
bootstrapTelemetryFlag();
|
||||
expect((globalThis as any).__mem0_telemetry_override).toBe("false");
|
||||
});
|
||||
|
||||
it("does not set globalThis override when MEM0_TELEMETRY is unset", () => {
|
||||
vi.stubEnv("MEM0_TELEMETRY", undefined as unknown as string);
|
||||
bootstrapTelemetryFlag();
|
||||
expect((globalThis as any).__mem0_telemetry_override).toBeUndefined();
|
||||
});
|
||||
|
||||
it("passes through truthy values", () => {
|
||||
process.env.MEM0_TELEMETRY = "true";
|
||||
vi.stubEnv("MEM0_TELEMETRY", "true");
|
||||
bootstrapTelemetryFlag();
|
||||
expect((globalThis as any).__mem0_telemetry_override).toBe("true");
|
||||
});
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import { jsonOut, jsonErr, redactSecrets } from "../cli/json-helpers.ts";
|
||||
|
||||
describe("jsonOut", () => {
|
||||
let writeSpy: ReturnType<typeof vi.spyOn>;
|
||||
beforeEach(() => { writeSpy = vi.spyOn(process.stdout, "write").mockImplementation(() => true); });
|
||||
afterEach(() => { writeSpy.mockRestore(); });
|
||||
|
||||
it("returns false and prints nothing when json is falsy", () => {
|
||||
expect(jsonOut({}, { ok: true })).toBe(false);
|
||||
expect(writeSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns true and prints JSON to stdout when json is true", () => {
|
||||
expect(jsonOut({ json: true }, { ok: true, count: 3 })).toBe(true);
|
||||
expect(writeSpy).toHaveBeenCalledOnce();
|
||||
const parsed = JSON.parse(writeSpy.mock.calls[0][0] as string);
|
||||
expect(parsed).toEqual({ ok: true, count: 3 });
|
||||
});
|
||||
});
|
||||
|
||||
describe("jsonErr", () => {
|
||||
let writeSpy: ReturnType<typeof vi.spyOn>;
|
||||
beforeEach(() => { writeSpy = vi.spyOn(process.stdout, "write").mockImplementation(() => true); });
|
||||
afterEach(() => { writeSpy.mockRestore(); });
|
||||
|
||||
it("returns false when json is falsy", () => {
|
||||
expect(jsonErr({}, "bad")).toBe(false);
|
||||
});
|
||||
|
||||
it("returns true and prints error JSON to stdout", () => {
|
||||
expect(jsonErr({ json: true }, "Something broke")).toBe(true);
|
||||
const parsed = JSON.parse(writeSpy.mock.calls[0][0] as string);
|
||||
expect(parsed).toEqual({ ok: false, error: "Something broke" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("redactSecrets", () => {
|
||||
it("redacts string values for known secret keys", () => {
|
||||
const input = { apiKey: "m0-abcdefghijklmnop", name: "test" };
|
||||
const result = redactSecrets(input, new Set(["apiKey"]));
|
||||
expect(result.apiKey).toBe("m0-a...mnop");
|
||||
expect(result.name).toBe("test");
|
||||
});
|
||||
|
||||
it("handles short keys", () => {
|
||||
const result = redactSecrets({ apiKey: "ab" }, new Set(["apiKey"]));
|
||||
expect(result.apiKey).toBe("ab***");
|
||||
});
|
||||
|
||||
it("skips non-string values", () => {
|
||||
const result = redactSecrets({ count: 5 }, new Set(["count"]));
|
||||
expect(result.count).toBe(5);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,196 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import {
|
||||
LLM_PROVIDERS,
|
||||
EMBEDDER_PROVIDERS,
|
||||
VECTOR_PROVIDERS,
|
||||
KNOWN_EMBEDDER_DIMS,
|
||||
buildOssLlmConfig,
|
||||
buildOssEmbedderConfig,
|
||||
buildOssVectorConfig,
|
||||
validateOssFlags,
|
||||
checkQdrantConnectivity,
|
||||
checkOllamaConnectivity,
|
||||
checkPgConnectivity,
|
||||
} from "../cli/oss-wizard.ts";
|
||||
|
||||
describe("LLM_PROVIDERS", () => {
|
||||
it("has 3 providers", () => {
|
||||
expect(LLM_PROVIDERS).toHaveLength(3);
|
||||
expect(LLM_PROVIDERS.map((p) => p.id)).toEqual(["openai", "ollama", "anthropic"]);
|
||||
});
|
||||
|
||||
it("openai requires API key", () => {
|
||||
const openai = LLM_PROVIDERS.find((p) => p.id === "openai")!;
|
||||
expect(openai.needsApiKey).toBe(true);
|
||||
expect(openai.defaultModel).toBe("gpt-5-mini");
|
||||
});
|
||||
|
||||
it("ollama needs no API key but needs URL", () => {
|
||||
const ollama = LLM_PROVIDERS.find((p) => p.id === "ollama")!;
|
||||
expect(ollama.needsApiKey).toBe(false);
|
||||
expect(ollama.needsUrl).toBe(true);
|
||||
expect(ollama.defaultUrl).toBe("http://localhost:11434");
|
||||
});
|
||||
});
|
||||
|
||||
describe("EMBEDDER_PROVIDERS", () => {
|
||||
it("has 2 providers", () => {
|
||||
expect(EMBEDDER_PROVIDERS).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("KNOWN_EMBEDDER_DIMS", () => {
|
||||
it("maps default models to dims", () => {
|
||||
expect(KNOWN_EMBEDDER_DIMS["text-embedding-3-small"]).toBe(1536);
|
||||
expect(KNOWN_EMBEDDER_DIMS["nomic-embed-text"]).toBe(768);
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildOssLlmConfig", () => {
|
||||
it("builds openai config with API key", () => {
|
||||
const result = buildOssLlmConfig("openai", { apiKey: "sk-test" });
|
||||
expect(result).toEqual({
|
||||
provider: "openai",
|
||||
config: { model: "gpt-5-mini", apiKey: "sk-test" },
|
||||
});
|
||||
});
|
||||
|
||||
it("builds ollama config with custom URL and model", () => {
|
||||
const result = buildOssLlmConfig("ollama", { url: "http://myhost:11434", model: "mistral" });
|
||||
expect(result).toEqual({
|
||||
provider: "ollama",
|
||||
config: { model: "mistral", url: "http://myhost:11434" },
|
||||
});
|
||||
});
|
||||
|
||||
it("builds ollama config with default URL", () => {
|
||||
const result = buildOssLlmConfig("ollama", {});
|
||||
expect(result.config.url).toBe("http://localhost:11434");
|
||||
});
|
||||
|
||||
it("ignores url for non-ollama providers", () => {
|
||||
const result = buildOssLlmConfig("anthropic", { apiKey: "sk-ant", url: "http://ignored" });
|
||||
expect(result.config).not.toHaveProperty("url");
|
||||
expect(result.config).toHaveProperty("apiKey", "sk-ant");
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildOssEmbedderConfig", () => {
|
||||
it("builds openai embedder", () => {
|
||||
const result = buildOssEmbedderConfig("openai", { apiKey: "sk-test" });
|
||||
expect(result.config.model).toBe("text-embedding-3-small");
|
||||
expect(result.dims).toBe(1536);
|
||||
});
|
||||
|
||||
it("builds ollama embedder with url field", () => {
|
||||
const result = buildOssEmbedderConfig("ollama", { url: "http://myhost:11434" });
|
||||
expect(result.config.url).toBe("http://myhost:11434");
|
||||
expect(result.config).not.toHaveProperty("ollama_base_url");
|
||||
expect(result.config.model).toBe("nomic-embed-text");
|
||||
expect(result.dims).toBe(768);
|
||||
});
|
||||
|
||||
it("returns unknown dims for custom model", () => {
|
||||
const result = buildOssEmbedderConfig("ollama", { model: "custom-embed" });
|
||||
expect(result.dims).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("VECTOR_PROVIDERS", () => {
|
||||
it("has 2 providers", () => {
|
||||
expect(VECTOR_PROVIDERS).toHaveLength(2);
|
||||
expect(VECTOR_PROVIDERS.map((p) => p.id)).toEqual(["qdrant", "pgvector"]);
|
||||
});
|
||||
|
||||
it("qdrant requires server connection", () => {
|
||||
const qdrant = VECTOR_PROVIDERS.find((p) => p.id === "qdrant")!;
|
||||
expect(qdrant.needsConnection).toBe(true);
|
||||
expect(qdrant.defaultUrl).toBe("http://localhost:6333");
|
||||
expect(qdrant.setupHint).toContain("docker");
|
||||
});
|
||||
|
||||
it("pgvector requires connection and has setup hint", () => {
|
||||
const pg = VECTOR_PROVIDERS.find((p) => p.id === "pgvector")!;
|
||||
expect(pg.needsConnection).toBe(true);
|
||||
expect(pg.defaultPort).toBe(5432);
|
||||
expect(pg.setupHint).toContain("pgvector");
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildOssVectorConfig", () => {
|
||||
it("builds qdrant with default url and dims", () => {
|
||||
const result = buildOssVectorConfig("qdrant", { dims: 1536 });
|
||||
expect(result.config.url).toBe("http://localhost:6333");
|
||||
expect(result.config.onDisk).toBe(true);
|
||||
expect(result.config.dimension).toBe(1536);
|
||||
});
|
||||
|
||||
it("builds qdrant with custom url", () => {
|
||||
const result = buildOssVectorConfig("qdrant", { url: "http://qdrant.local:6333", dims: 768 });
|
||||
expect(result.config.url).toBe("http://qdrant.local:6333");
|
||||
expect(result.config.onDisk).toBe(true);
|
||||
expect(result.config.dimension).toBe(768);
|
||||
});
|
||||
|
||||
it("builds qdrant with api key for cloud", () => {
|
||||
const result = buildOssVectorConfig("qdrant", { url: "https://cloud.qdrant.io", apiKey: "qd-key", dims: 1536 });
|
||||
expect(result.config.apiKey).toBe("qd-key");
|
||||
expect(result.config.url).toBe("https://cloud.qdrant.io");
|
||||
});
|
||||
|
||||
it("builds pgvector with connection details", () => {
|
||||
const result = buildOssVectorConfig("pgvector", {
|
||||
host: "db.local", port: "5432", user: "me", password: "pw", dbname: "mydb", dims: 512,
|
||||
});
|
||||
expect(result.config.host).toBe("db.local");
|
||||
expect(result.config.dimension).toBe(512);
|
||||
});
|
||||
});
|
||||
|
||||
describe("checkQdrantConnectivity", () => {
|
||||
it("returns error for unreachable host", async () => {
|
||||
const result = await checkQdrantConnectivity("http://localhost:19999");
|
||||
expect(result.ok).toBe(false);
|
||||
expect(result.error).toContain("Cannot reach Qdrant");
|
||||
});
|
||||
});
|
||||
|
||||
describe("checkOllamaConnectivity", () => {
|
||||
it("returns error for unreachable host", async () => {
|
||||
const result = await checkOllamaConnectivity("http://localhost:19998");
|
||||
expect(result.ok).toBe(false);
|
||||
expect(result.error).toContain("Cannot reach Ollama");
|
||||
});
|
||||
});
|
||||
|
||||
describe("checkPgConnectivity", () => {
|
||||
it("returns error for unreachable host", async () => {
|
||||
const result = await checkPgConnectivity("localhost", 19997);
|
||||
expect(result.ok).toBe(false);
|
||||
expect(result.error).toContain("PostgreSQL not reachable");
|
||||
});
|
||||
});
|
||||
|
||||
describe("validateOssFlags", () => {
|
||||
it("returns error when openai LLM has no key", () => {
|
||||
const result = validateOssFlags({ ossLlm: "openai" });
|
||||
expect(result.error).toContain("--oss-llm-key");
|
||||
});
|
||||
|
||||
it("passes for ollama with no key", () => {
|
||||
const result = validateOssFlags({ ossLlm: "ollama", ossEmbedder: "ollama", ossVector: "qdrant" });
|
||||
expect(result.error).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns error for unknown provider", () => {
|
||||
const result = validateOssFlags({ ossLlm: "bogus" });
|
||||
expect(result.error).toContain("Unknown LLM provider");
|
||||
});
|
||||
|
||||
it("returns error when pgvector missing user", () => {
|
||||
const result = validateOssFlags({
|
||||
ossLlm: "ollama", ossEmbedder: "ollama", ossVector: "pgvector",
|
||||
});
|
||||
expect(result.error).toContain("--oss-vector-user");
|
||||
});
|
||||
});
|
||||
@@ -5,7 +5,7 @@ vi.mock("../cli/config-file.ts", () => ({
|
||||
readPluginAuth: vi.fn().mockReturnValue({}),
|
||||
}));
|
||||
|
||||
import { captureEvent, PLUGIN_VERSION } from "../telemetry.ts";
|
||||
import { captureEvent } from "../telemetry.ts";
|
||||
import { readPluginAuth } from "../cli/config-file.ts";
|
||||
|
||||
describe("telemetry", () => {
|
||||
@@ -23,10 +23,6 @@ describe("telemetry", () => {
|
||||
delete (globalThis as any).__mem0_telemetry_override;
|
||||
});
|
||||
|
||||
it("exports PLUGIN_VERSION", () => {
|
||||
expect(PLUGIN_VERSION).toBe("1.0.7");
|
||||
});
|
||||
|
||||
it("captureEvent does not throw", () => {
|
||||
expect(() => captureEvent("test_event")).not.toThrow();
|
||||
});
|
||||
|
||||
@@ -263,10 +263,11 @@ describe("memory_search execute", () => {
|
||||
scope: "session",
|
||||
});
|
||||
|
||||
// Should call buildSearchOptions with session ID
|
||||
// Should call buildSearchOptions with session ID as 4th arg (sessionKey)
|
||||
expect(ctx.buildSearchOptions).toHaveBeenCalledWith(
|
||||
"testuser",
|
||||
undefined,
|
||||
undefined,
|
||||
"session-abc",
|
||||
);
|
||||
expect(result.details.count).toBe(1);
|
||||
@@ -446,8 +447,8 @@ describe("memory_add execute", () => {
|
||||
|
||||
await tool.execute("call-7", { text: "new fact" });
|
||||
|
||||
// Search should be called for dedup before add
|
||||
expect(searchMock).toHaveBeenCalledOnce();
|
||||
// Mem0 backend handles dedup internally — no separate search call
|
||||
expect(searchMock).not.toHaveBeenCalled();
|
||||
expect(addMock).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -82,9 +82,6 @@ export function createMemoryAddTool(deps: ToolDeps) {
|
||||
}
|
||||
|
||||
const combinedText = allFacts.join("\n");
|
||||
const dedupOpts = buildSearchOptions(uid, 3);
|
||||
dedupOpts.threshold = 0.85;
|
||||
await provider.search(combinedText.slice(0, 200), dedupOpts);
|
||||
|
||||
const result = await provider.add([{ role: "user", content: combinedText }], buildAddOptions(uid, runId, currentSessionId));
|
||||
const added = result.results?.filter((r) => r.event === "ADD") ?? [];
|
||||
|
||||
@@ -47,7 +47,7 @@ export function createMemorySearchTool(deps: ToolDeps) {
|
||||
|
||||
if (scope === "session") {
|
||||
if (currentSessionId) {
|
||||
results = await provider.search(query, applyFilters(buildSearchOptions(uid, limit, currentSessionId)));
|
||||
results = await provider.search(query, applyFilters(buildSearchOptions(uid, limit, undefined, currentSessionId)));
|
||||
}
|
||||
} else if (scope === "long-term") {
|
||||
results = await provider.search(query, applyFilters(buildSearchOptions(uid, limit)));
|
||||
@@ -55,7 +55,7 @@ export function createMemorySearchTool(deps: ToolDeps) {
|
||||
const longTerm = await provider.search(query, applyFilters(buildSearchOptions(uid, limit)));
|
||||
let session: MemoryItem[] = [];
|
||||
if (currentSessionId) {
|
||||
session = await provider.search(query, applyFilters(buildSearchOptions(uid, limit, currentSessionId)));
|
||||
session = await provider.search(query, applyFilters(buildSearchOptions(uid, limit, undefined, currentSessionId)));
|
||||
}
|
||||
const seen = new Set(longTerm.map((r) => r.id));
|
||||
results = [...longTerm, ...session.filter((r) => !seen.has(r.id))];
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user