Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d6bac1af6 | ||
|
|
443895110b | ||
|
|
8af9cc5793 | ||
|
|
74570ead11 | ||
|
|
5020fa1a0d | ||
|
|
3ea7cc82cd | ||
|
|
fdfb763d6e | ||
|
|
0070e08e01 | ||
|
|
39bc023305 | ||
|
|
b1342a3408 | ||
|
|
b717e38785 |
+4
-4
@@ -21,10 +21,10 @@ Package workflows keep their own push-to-main and manual triggers. Their `pull_r
|
||||
| Mem0 Plugin | `mem0-plugin-checks.yml` | Push to main (`integrations/mem0-plugin/`, excluding `.opencode-plugin/`), manual | pytest + hook exec bits + JSON manifest validation on Python 3.10, 3.11, 3.12 |
|
||||
| OpenCode Plugin | `opencode-plugin-checks.yml` | Push to main (`.opencode-plugin/`), manual | Bun: tsc + build + dist artifact check |
|
||||
| Pi Agent Plugin | `pi-agent-plugin-checks.yml` | Push to main (`integrations/pi-agent-plugin/`), manual | tsc + vitest + tsup on Node 20, 22 |
|
||||
| DeepSeek Harness Plugin | `dsh-mem0-checks.yml` | Push to main (`integrations/dsh-mem0/`), manual | tsc + vitest + tsup on Node 20, 22 |
|
||||
| DeepSeek Harness Plugin | `deepseek-plugin-checks.yml` | Push to main (`integrations/deepseek-plugin/`), manual | tsc + vitest + tsup on Node 20, 22 |
|
||||
| n8n Node | `n8n-nodes-mem0-checks.yml` | Push to main (`integrations/n8n-nodes-mem0/`), manual | ESLint + tsc build on Node 20 |
|
||||
| Zapier App | `zapier-mem0-checks.yml` | Push to main (`integrations/zapier-mem0/`), manual | tsc + `zapier validate` + offline unit tests on Node 22 |
|
||||
| strands-mem0 | `strands-mem0-checks.yml` | Push to main (`integrations/strands-mem0/`), manual | Ruff + mypy + pytest + hatch build on Python 3.10, 3.11, 3.12 |
|
||||
| mem0-strands | `mem0-strands-checks.yml` | Push to main (`integrations/mem0-strands/`), manual | Ruff + mypy + pytest + hatch build on Python 3.10, 3.11, 3.12 |
|
||||
| docs llms.txt | `docs-llms-txt-check.yml` | Manual | `docs/llms.txt` coverage |
|
||||
| GitHub Scripts | inline in `ci-gate.yml` | none | `node` over every `.github/scripts/*.test.js` |
|
||||
|
||||
@@ -60,9 +60,9 @@ Requiring `CI Gate` also means fork PRs from first-time contributors cannot merg
|
||||
| OpenClaw | `openclaw-cd.yml` | `openclaw-v*` | npm (`@mem0/openclaw-mem0`) |
|
||||
| OpenCode Plugin | `opencode-plugin-cd.yml` | `opencode-v*` | npm (`@mem0/opencode-plugin`) |
|
||||
| Pi Agent Plugin | `pi-agent-plugin-cd.yml` | `pi-agent-v*` | npm (`@mem0/pi-agent-plugin`) |
|
||||
| DeepSeek Harness Plugin | `dsh-mem0-cd.yml` | `dsh-mem0-v*` | npm (`@mem0/dsh-mem0`) |
|
||||
| DeepSeek Harness Plugin | `deepseek-plugin-cd.yml` | `deepseek-plugin-v*` | npm (`@mem0/deepseek-plugin`) |
|
||||
| n8n Node | `n8n-nodes-mem0-cd.yml` | `n8n-nodes-mem0-v*` | npm (`@mem0/n8n-nodes-mem0`) |
|
||||
| strands-mem0 | `strands-mem0-cd.yml` | `strands-mem0-v*` | PyPI (`strands-mem0`) |
|
||||
| mem0-strands | `mem0-strands-cd.yml` | `mem0-strands-v*` | PyPI (`mem0-strands`) |
|
||||
|
||||
- Package CD workflows are `workflow_dispatch`-only, with `tag` and `prerelease` inputs. They check out and build the given tag.
|
||||
- All publishing uses **OIDC trusted publishing**. No tokens, no secrets.
|
||||
|
||||
@@ -41,10 +41,10 @@ jobs:
|
||||
mem0_plugin: ${{ steps.filter.outputs.mem0_plugin }}
|
||||
opencode_plugin: ${{ steps.filter.outputs.opencode_plugin }}
|
||||
pi_agent_plugin: ${{ steps.filter.outputs.pi_agent_plugin }}
|
||||
dsh_mem0: ${{ steps.filter.outputs.dsh_mem0 }}
|
||||
deepseek_plugin: ${{ steps.filter.outputs.deepseek_plugin }}
|
||||
n8n_nodes_mem0: ${{ steps.filter.outputs.n8n_nodes_mem0 }}
|
||||
zapier_mem0: ${{ steps.filter.outputs.zapier_mem0 }}
|
||||
strands_mem0: ${{ steps.filter.outputs.strands_mem0 }}
|
||||
mem0_strands: ${{ steps.filter.outputs.mem0_strands }}
|
||||
docs_llms_txt: ${{ steps.filter.outputs.docs_llms_txt }}
|
||||
github_scripts: ${{ steps.filter.outputs.github_scripts }}
|
||||
steps:
|
||||
@@ -90,9 +90,9 @@ jobs:
|
||||
- 'integrations/pi-agent-plugin/**'
|
||||
- '.github/workflows/pi-agent-plugin-checks.yml'
|
||||
- '.github/workflows/ci-gate.yml'
|
||||
dsh_mem0:
|
||||
- 'integrations/dsh-mem0/**'
|
||||
- '.github/workflows/dsh-mem0-checks.yml'
|
||||
deepseek_plugin:
|
||||
- 'integrations/deepseek-plugin/**'
|
||||
- '.github/workflows/deepseek-plugin-checks.yml'
|
||||
- '.github/workflows/ci-gate.yml'
|
||||
n8n_nodes_mem0:
|
||||
- 'integrations/n8n-nodes-mem0/**'
|
||||
@@ -101,9 +101,9 @@ jobs:
|
||||
- 'integrations/zapier-mem0/**'
|
||||
- '.github/workflows/zapier-mem0-checks.yml'
|
||||
- '.github/workflows/ci-gate.yml'
|
||||
strands_mem0:
|
||||
- 'integrations/strands-mem0/**'
|
||||
- '.github/workflows/strands-mem0-checks.yml'
|
||||
mem0_strands:
|
||||
- 'integrations/mem0-strands/**'
|
||||
- '.github/workflows/mem0-strands-checks.yml'
|
||||
- '.github/workflows/ci-gate.yml'
|
||||
docs_llms_txt:
|
||||
- 'docs/**/*.mdx'
|
||||
@@ -176,11 +176,11 @@ jobs:
|
||||
uses: ./.github/workflows/pi-agent-plugin-checks.yml
|
||||
secrets: inherit
|
||||
|
||||
dsh-mem0:
|
||||
deepseek-plugin:
|
||||
name: DeepSeek Harness Plugin
|
||||
needs: changes
|
||||
if: needs.changes.outputs.dsh_mem0 == 'true'
|
||||
uses: ./.github/workflows/dsh-mem0-checks.yml
|
||||
if: needs.changes.outputs.deepseek_plugin == 'true'
|
||||
uses: ./.github/workflows/deepseek-plugin-checks.yml
|
||||
secrets: inherit
|
||||
|
||||
n8n-nodes-mem0:
|
||||
@@ -195,11 +195,11 @@ jobs:
|
||||
uses: ./.github/workflows/zapier-mem0-checks.yml
|
||||
secrets: inherit
|
||||
|
||||
strands-mem0:
|
||||
name: strands-mem0
|
||||
mem0-strands:
|
||||
name: mem0-strands
|
||||
needs: changes
|
||||
if: needs.changes.outputs.strands_mem0 == 'true'
|
||||
uses: ./.github/workflows/strands-mem0-checks.yml
|
||||
if: needs.changes.outputs.mem0_strands == 'true'
|
||||
uses: ./.github/workflows/mem0-strands-checks.yml
|
||||
secrets: inherit
|
||||
|
||||
docs-llms-txt:
|
||||
@@ -239,10 +239,10 @@ jobs:
|
||||
- mem0-plugin
|
||||
- opencode-plugin
|
||||
- pi-agent-plugin
|
||||
- dsh-mem0
|
||||
- deepseek-plugin
|
||||
- n8n-nodes-mem0
|
||||
- zapier-mem0
|
||||
- strands-mem0
|
||||
- mem0-strands
|
||||
- docs-llms-txt
|
||||
- github-scripts
|
||||
if: always()
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
name: Publish @mem0/dsh-mem0 📦 to npm
|
||||
name: Publish @mem0/deepseek-plugin 📦 to npm
|
||||
|
||||
# Dispatched by release.yml (Release Router) when a release tagged
|
||||
# dsh-mem0-v* is published. Can also be dispatched manually to re-publish
|
||||
# deepseek-plugin-v* is published. Can also be dispatched manually to re-publish
|
||||
# a tag.
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Release tag to build and publish (e.g. dsh-mem0-v0.1.1)'
|
||||
description: 'Release tag to build and publish (e.g. deepseek-plugin-v0.1.1)'
|
||||
required: true
|
||||
type: string
|
||||
prerelease:
|
||||
@@ -18,14 +18,14 @@ on:
|
||||
|
||||
jobs:
|
||||
build-n-publish:
|
||||
name: Build and publish @mem0/dsh-mem0 📦 to npm
|
||||
if: startsWith(inputs.tag, 'dsh-mem0-v')
|
||||
name: Build and publish @mem0/deepseek-plugin 📦 to npm
|
||||
if: startsWith(inputs.tag, 'deepseek-plugin-v')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
defaults:
|
||||
run:
|
||||
working-directory: integrations/dsh-mem0
|
||||
working-directory: integrations/deepseek-plugin
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
node-version: '22'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: integrations/dsh-mem0/pnpm-lock.yaml
|
||||
cache-dependency-path: integrations/deepseek-plugin/pnpm-lock.yaml
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
+14
-14
@@ -1,4 +1,4 @@
|
||||
name: dsh-mem0 checks
|
||||
name: deepseek-plugin checks
|
||||
|
||||
# On PRs this is invoked by ci-gate.yml (the single required check);
|
||||
# push-to-main and manual runs remain standalone.
|
||||
@@ -7,8 +7,8 @@ on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'integrations/dsh-mem0/**'
|
||||
- '.github/workflows/dsh-mem0-checks.yml'
|
||||
- 'integrations/deepseek-plugin/**'
|
||||
- '.github/workflows/deepseek-plugin-checks.yml'
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
@@ -27,13 +27,13 @@ jobs:
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: integrations/dsh-mem0/pnpm-lock.yaml
|
||||
cache-dependency-path: integrations/deepseek-plugin/pnpm-lock.yaml
|
||||
|
||||
- name: Install dependencies
|
||||
run: cd integrations/dsh-mem0 && pnpm install --frozen-lockfile
|
||||
run: cd integrations/deepseek-plugin && pnpm install --frozen-lockfile
|
||||
|
||||
- name: Type check
|
||||
run: cd integrations/dsh-mem0 && pnpm exec tsc --noEmit
|
||||
run: cd integrations/deepseek-plugin && pnpm exec tsc --noEmit
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -53,13 +53,13 @@ jobs:
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: integrations/dsh-mem0/pnpm-lock.yaml
|
||||
cache-dependency-path: integrations/deepseek-plugin/pnpm-lock.yaml
|
||||
|
||||
- name: Install dependencies
|
||||
run: cd integrations/dsh-mem0 && pnpm install --frozen-lockfile
|
||||
run: cd integrations/deepseek-plugin && pnpm install --frozen-lockfile
|
||||
|
||||
- name: Run tests
|
||||
run: cd integrations/dsh-mem0 && pnpm exec vitest run
|
||||
run: cd integrations/deepseek-plugin && pnpm exec vitest run
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -76,15 +76,15 @@ jobs:
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: integrations/dsh-mem0/pnpm-lock.yaml
|
||||
cache-dependency-path: integrations/deepseek-plugin/pnpm-lock.yaml
|
||||
|
||||
- name: Install dependencies
|
||||
run: cd integrations/dsh-mem0 && pnpm install --frozen-lockfile
|
||||
run: cd integrations/deepseek-plugin && pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: cd integrations/dsh-mem0 && pnpm build
|
||||
run: cd integrations/deepseek-plugin && pnpm build
|
||||
|
||||
- name: Verify dist output exists
|
||||
run: |
|
||||
test -f integrations/dsh-mem0/dist/index.js || (echo "Build output missing: dist/index.js" && exit 1)
|
||||
test -f integrations/dsh-mem0/dist/index.d.ts || (echo "Build output missing: dist/index.d.ts" && exit 1)
|
||||
test -f integrations/deepseek-plugin/dist/index.js || (echo "Build output missing: dist/index.js" && exit 1)
|
||||
test -f integrations/deepseek-plugin/dist/index.d.ts || (echo "Build output missing: dist/index.d.ts" && exit 1)
|
||||
@@ -1,15 +1,15 @@
|
||||
name: Publish strands-mem0 🐍 distribution 📦 to PyPI
|
||||
name: Publish mem0-strands 🐍 distribution 📦 to PyPI
|
||||
|
||||
# Dispatched by release.yml (Release Router) when a release tagged
|
||||
# strands-mem0-v* is published. Can also be dispatched manually to re-publish
|
||||
# mem0-strands-v* is published. Can also be dispatched manually to re-publish
|
||||
# a tag. Publishing uses PyPI Trusted Publishing (OIDC), so no API token is
|
||||
# stored; the `strands-mem0` PyPI project must have a trusted publisher
|
||||
# stored; the `mem0-strands` PyPI project must have a trusted publisher
|
||||
# configured for mem0ai/mem0 + this workflow.
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Release tag to build and publish (e.g. strands-mem0-v0.1.0)'
|
||||
description: 'Release tag to build and publish (e.g. mem0-strands-v0.1.0)'
|
||||
required: true
|
||||
type: string
|
||||
prerelease:
|
||||
@@ -20,14 +20,14 @@ on:
|
||||
|
||||
jobs:
|
||||
build-n-publish:
|
||||
name: Build and publish strands-mem0 📦 to PyPI
|
||||
if: startsWith(inputs.tag, 'strands-mem0-v')
|
||||
name: Build and publish mem0-strands 📦 to PyPI
|
||||
if: startsWith(inputs.tag, 'mem0-strands-v')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
defaults:
|
||||
run:
|
||||
working-directory: integrations/strands-mem0/python
|
||||
working-directory: integrations/mem0-strands/python
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -47,4 +47,4 @@ jobs:
|
||||
- name: Publish distribution 📦 to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
packages-dir: integrations/strands-mem0/python/dist/
|
||||
packages-dir: integrations/mem0-strands/python/dist/
|
||||
+12
-12
@@ -1,4 +1,4 @@
|
||||
name: strands-mem0 CI
|
||||
name: mem0-strands CI
|
||||
|
||||
# On PRs this is invoked by ci-gate.yml (the single required check);
|
||||
# push-to-main and manual runs remain standalone.
|
||||
@@ -7,8 +7,8 @@ on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'integrations/strands-mem0/**'
|
||||
- '.github/workflows/strands-mem0-checks.yml'
|
||||
- 'integrations/mem0-strands/**'
|
||||
- '.github/workflows/mem0-strands-checks.yml'
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
@@ -23,19 +23,19 @@ jobs:
|
||||
python-version: '3.12'
|
||||
|
||||
- name: Install dev dependencies
|
||||
working-directory: integrations/strands-mem0/python
|
||||
working-directory: integrations/mem0-strands/python
|
||||
run: pip install -e ".[dev]"
|
||||
|
||||
- name: Lint with ruff
|
||||
working-directory: integrations/strands-mem0/python
|
||||
working-directory: integrations/mem0-strands/python
|
||||
run: ruff check .
|
||||
|
||||
- name: Check formatting
|
||||
working-directory: integrations/strands-mem0/python
|
||||
working-directory: integrations/mem0-strands/python
|
||||
run: ruff format --check .
|
||||
|
||||
- name: Type-check with mypy
|
||||
working-directory: integrations/strands-mem0/python
|
||||
working-directory: integrations/mem0-strands/python
|
||||
run: mypy src
|
||||
|
||||
test:
|
||||
@@ -52,11 +52,11 @@ jobs:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Install dev dependencies
|
||||
working-directory: integrations/strands-mem0/python
|
||||
working-directory: integrations/mem0-strands/python
|
||||
run: pip install -e ".[dev]"
|
||||
|
||||
- name: Run tests
|
||||
working-directory: integrations/strands-mem0/python
|
||||
working-directory: integrations/mem0-strands/python
|
||||
run: pytest
|
||||
|
||||
build:
|
||||
@@ -73,10 +73,10 @@ jobs:
|
||||
run: pip install hatch
|
||||
|
||||
- name: Build
|
||||
working-directory: integrations/strands-mem0/python
|
||||
working-directory: integrations/mem0-strands/python
|
||||
run: hatch build --clean
|
||||
|
||||
- name: Verify dist output
|
||||
run: |
|
||||
ls integrations/strands-mem0/python/dist/*.whl || (echo "Wheel file missing" && exit 1)
|
||||
ls integrations/strands-mem0/python/dist/*.tar.gz || (echo "Source dist missing" && exit 1)
|
||||
ls integrations/mem0-strands/python/dist/*.whl || (echo "Wheel file missing" && exit 1)
|
||||
ls integrations/mem0-strands/python/dist/*.tar.gz || (echo "Source dist missing" && exit 1)
|
||||
@@ -45,9 +45,9 @@ jobs:
|
||||
openclaw-v*) workflow="openclaw-cd.yml" ;;
|
||||
opencode-v*) workflow="opencode-plugin-cd.yml" ;;
|
||||
pi-agent-v*) workflow="pi-agent-plugin-cd.yml" ;;
|
||||
dsh-mem0-v*) workflow="dsh-mem0-cd.yml" ;;
|
||||
deepseek-plugin-v*) workflow="deepseek-plugin-cd.yml" ;;
|
||||
n8n-nodes-mem0-v*) workflow="n8n-nodes-mem0-cd.yml" ;;
|
||||
strands-mem0-v*) workflow="strands-mem0-cd.yml" ;;
|
||||
mem0-strands-v*) workflow="mem0-strands-cd.yml" ;;
|
||||
v*) workflow="cd.yml" ;;
|
||||
*)
|
||||
echo "::error::Release tag '$TAG' does not match any known package prefix — nothing will be published. See the tag prefix table in AGENTS.md."
|
||||
|
||||
+1
-1
@@ -192,6 +192,6 @@ testing.ipynb
|
||||
.weave/
|
||||
|
||||
|
||||
# TypeScript incremental build info and local, uncommitted e2e scripts (used by the integrations, e.g. integrations/dsh-mem0)
|
||||
# TypeScript incremental build info and local, uncommitted e2e scripts (used by the integrations, e.g. integrations/deepseek-plugin)
|
||||
*.tsbuildinfo
|
||||
*.local.mjs
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# Mem0 Python SDK
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/mem0ai/mem0">
|
||||
<img src="docs/images/banner-sm.png" width="800px" alt="Mem0 - The Memory Layer for Personalized AI">
|
||||
<img src="docs/images/banner-sm.png" width="800px" alt="Mem0, the memory layer for personalized AI">
|
||||
</a>
|
||||
</p>
|
||||
<p align="center" style="display: flex; justify-content: center; gap: 20px; align-items: center;">
|
||||
@@ -22,166 +24,187 @@
|
||||
<img src="https://img.shields.io/badge/Discord-%235865F2.svg?&logo=discord&logoColor=white" alt="Mem0 Discord">
|
||||
</a>
|
||||
<a href="https://pepy.tech/project/mem0ai">
|
||||
<img src="https://img.shields.io/pypi/dm/mem0ai" alt="Mem0 PyPI - Downloads">
|
||||
<img src="https://img.shields.io/pypi/dm/mem0ai" alt="Mem0 PyPI downloads">
|
||||
</a>
|
||||
<a href="https://github.com/mem0ai/mem0">
|
||||
<img src="https://img.shields.io/github/commit-activity/m/mem0ai/mem0?style=flat-square" alt="GitHub commit activity">
|
||||
</a>
|
||||
<a href="https://pypi.org/project/mem0ai" target="blank">
|
||||
<img src="https://img.shields.io/pypi/v/mem0ai?color=%2334D058&label=pypi%20package" alt="Package version">
|
||||
</a>
|
||||
<a href="https://www.npmjs.com/package/mem0ai" target="blank">
|
||||
<img src="https://img.shields.io/npm/v/mem0ai" alt="Npm package">
|
||||
<img src="https://img.shields.io/pypi/v/mem0ai?color=%2334D058&label=pypi%20package" alt="PyPI package version">
|
||||
</a>
|
||||
<a href="https://www.ycombinator.com/companies/mem0">
|
||||
<img src="https://img.shields.io/badge/Y%20Combinator-S24-orange?style=flat-square" alt="Y Combinator S24">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://mem0.ai/research"><strong>📄 Benchmarking Mem0's token-efficient memory algorithm →</strong></a>
|
||||
</p>
|
||||
Mem0 gives AI assistants and agents persistent memory. It extracts useful facts from conversations, scopes them to a user, agent, or run, and retrieves the relevant facts for later interactions. The Python package includes `MemoryClient` for the hosted Mem0 Platform and `Memory` for open-source, in-process memory.
|
||||
|
||||
## New Memory Algorithm (April 2026)
|
||||
## Requirements
|
||||
|
||||
| Benchmark | Old | New | Tokens | Latency p50 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| **LoCoMo** | 71.4 | **92.5** | 7.0K | 0.88s |
|
||||
| **LongMemEval** | 67.8 | **94.4** | 6.8K | 1.09s |
|
||||
| **BEAM (1M)** | — | **64.1** | 6.7K | 1.00s |
|
||||
| **BEAM (10M)** | — | **48.6** | 6.9K | 1.05s |
|
||||
- Python 3.10 or later
|
||||
- Hosted Platform: `MEM0_API_KEY` from the [Mem0 dashboard](https://app.mem0.ai/dashboard/api-keys)
|
||||
- Open source with the default providers: `OPENAI_API_KEY`
|
||||
|
||||
All benchmarks run on the same production-representative model stack. Single-pass retrieval (one call, no agentic loops) at a top_200 retrieval budget. Scores reflect Mem0's managed platform, which includes proprietary optimizations not available in the open-source SDK; open-source users should expect directionally similar gains but not identical numbers.
|
||||
|
||||
**What changed:**
|
||||
- **Single-pass ADD-only extraction** -- one LLM call, no UPDATE/DELETE. Memories accumulate; nothing is overwritten.
|
||||
- **Agent-generated facts are first-class** -- when an agent confirms an action, that information is now stored with equal weight.
|
||||
- **Entity linking** -- entities are extracted, embedded, and linked across memories for retrieval boosting.
|
||||
- **Multi-signal retrieval** -- semantic, BM25 keyword, and entity matching scored in parallel and fused.
|
||||
- **Temporal Reasoning** -- time-aware retrieval that ranks the right dated instance for queries about current state, past events, and upcoming plans.
|
||||
|
||||
See the [migration guide](https://docs.mem0.ai/migration/oss-v2-to-v3) for upgrade instructions. The [evaluation framework](https://github.com/mem0ai/memory-benchmarks) is open-sourced so anyone can reproduce the numbers.
|
||||
|
||||
## Research Highlights
|
||||
- **92.5 on LoCoMo** -- +21 points over the previous algorithm
|
||||
- **94.4 on LongMemEval** -- +27 points, with 98.2 on assistant memory recall
|
||||
- **64.1 on BEAM (1M)** -- production-scale memory evaluation at 1M tokens
|
||||
- [Read the full paper](https://mem0.ai/research)
|
||||
|
||||
# Introduction
|
||||
|
||||
[Mem0](https://mem0.ai) ("mem-zero") enhances AI assistants and agents with an intelligent memory layer, enabling personalized AI interactions. It remembers user preferences, adapts to individual needs, and continuously learns over time—ideal for customer support chatbots, AI assistants, and autonomous systems.
|
||||
|
||||
### Key Features & Use Cases
|
||||
|
||||
**Core Capabilities:**
|
||||
- **Multi-Level Memory**: Seamlessly retains User, Session, and Agent state with adaptive personalization
|
||||
- **Developer-Friendly**: Intuitive API, cross-platform SDKs, and a fully managed service option
|
||||
|
||||
**Applications:**
|
||||
- **AI Assistants**: Consistent, context-rich conversations
|
||||
- **Customer Support**: Recall past tickets and user history for tailored help
|
||||
- **Healthcare**: Track patient preferences and history for personalized care
|
||||
- **Productivity & Gaming**: Adaptive workflows and environments based on user behavior
|
||||
|
||||
## 🚀 Quickstart Guide <a name="quickstart"></a>
|
||||
|
||||
### Sign up as an agent
|
||||
|
||||
AI agents can mint a working Mem0 API key in under five seconds — no email, no dashboard, no OTP. Four commands end-to-end:
|
||||
|
||||
```bash
|
||||
# 1. Install
|
||||
npm install -g @mem0/cli # or: pip install mem0-cli
|
||||
|
||||
# 2. Sign up as an agent (replace `claude-code` with your name)
|
||||
mem0 init --agent --agent-caller claude-code
|
||||
|
||||
# 3. Add a memory
|
||||
mem0 add "I am using mem0"
|
||||
|
||||
# 4. Search
|
||||
mem0 search "am I using mem0"
|
||||
```
|
||||
|
||||
The human owner can claim the account later with `mem0 init --email <their-email>` — same key, memories preserved. Full guide: [Sign up as an agent](https://docs.mem0.ai/platform/agent-signup).
|
||||
|
||||
| | 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 |
|
||||
|
||||
Just testing? Use the library. Building for a team? Self-hosted. Want zero ops? Cloud.
|
||||
|
||||
### Library (pip / npm)
|
||||
## Install
|
||||
|
||||
```bash
|
||||
pip install mem0ai
|
||||
```
|
||||
|
||||
For enhanced hybrid search with BM25 keyword matching and entity extraction, install with NLP support:
|
||||
For enhanced hybrid search with BM25 keyword matching and entity extraction:
|
||||
|
||||
```bash
|
||||
pip install mem0ai[nlp]
|
||||
pip install "mem0ai[nlp]"
|
||||
python -m spacy download en_core_web_sm
|
||||
```
|
||||
|
||||
Install sdk via npm:
|
||||
## Platform or open source
|
||||
|
||||
```bash
|
||||
npm install mem0ai
|
||||
| | Platform (`MemoryClient`) | Open source (`Memory`) |
|
||||
|---|---|---|
|
||||
| Import | `from mem0 import MemoryClient` | `from mem0 import Memory` |
|
||||
| Where memories live | Mem0's hosted API | Your configured vector store |
|
||||
| Required key | `MEM0_API_KEY` | `OPENAI_API_KEY` with the defaults, or keys for your chosen providers |
|
||||
| Extraction | Managed and asynchronous | Runs synchronously against your configured LLM |
|
||||
| Best for | Zero-ops production use | Local development and custom infrastructure |
|
||||
|
||||
## Platform quickstart
|
||||
|
||||
Set `MEM0_API_KEY`, then add a conversation:
|
||||
|
||||
```python
|
||||
import os
|
||||
|
||||
from mem0 import MemoryClient
|
||||
|
||||
client = MemoryClient(api_key=os.environ["MEM0_API_KEY"])
|
||||
|
||||
messages = [
|
||||
{"role": "user", "content": "I am vegetarian and allergic to nuts."},
|
||||
{"role": "assistant", "content": "I will remember that."},
|
||||
]
|
||||
result = client.add(messages, user_id="alex")
|
||||
print(result)
|
||||
```
|
||||
|
||||
### Self-Hosted Server
|
||||
Hosted `add()` queues extraction and usually returns an `event_id` with `status: "PENDING"`. Do not search immediately after `add()`. Wait for processing to finish in the dashboard, or use a [`memory_add` webhook](https://docs.mem0.ai/platform/features/webhooks), then search:
|
||||
|
||||
> **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).
|
||||
```python
|
||||
import os
|
||||
|
||||
from mem0 import MemoryClient
|
||||
|
||||
client = MemoryClient(api_key=os.environ["MEM0_API_KEY"])
|
||||
results = client.search(
|
||||
"What does Alex eat?",
|
||||
filters={"user_id": "alex"},
|
||||
top_k=5,
|
||||
)
|
||||
print(results["results"])
|
||||
```
|
||||
|
||||
`search()` and `get_all()` take entity IDs inside `filters`. `add()` and `delete_all()` take `user_id`, `agent_id`, or `run_id` as top-level keyword arguments.
|
||||
|
||||
## Open-source quickstart
|
||||
|
||||
Set `OPENAI_API_KEY` before using the default OpenAI LLM and embedder:
|
||||
|
||||
```python
|
||||
from mem0 import Memory
|
||||
|
||||
memory = Memory()
|
||||
|
||||
messages = [
|
||||
{"role": "user", "content": "I am vegetarian and allergic to nuts."},
|
||||
{"role": "assistant", "content": "I will remember that."},
|
||||
]
|
||||
memory.add(messages, user_id="alex")
|
||||
|
||||
results = memory.search(
|
||||
"What does Alex eat?",
|
||||
filters={"user_id": "alex"},
|
||||
top_k=5,
|
||||
)
|
||||
print(results["results"])
|
||||
```
|
||||
|
||||
The default `Memory` configuration uses OpenAI `gpt-5-mini`, OpenAI `text-embedding-3-small`, local Qdrant storage, and a SQLite history database. Pass a `MemoryConfig` or use `Memory.from_config()` to change the LLM, embedder, vector store, history path, or reranker.
|
||||
|
||||
## Configuration and features
|
||||
|
||||
| Feature | Documentation |
|
||||
|---|---|
|
||||
| Memory operations: `add`, `search`, `get`, `get_all`, `update`, `delete`, `delete_all`, `history` | [Python quickstart](https://docs.mem0.ai/open-source/python-quickstart) |
|
||||
| Entity scoping with `user_id`, `agent_id`, and `run_id` | [Entity-scoped memory](https://docs.mem0.ai/platform/features/entity-scoped-memory) |
|
||||
| Metadata and filters | [Metadata filtering](https://docs.mem0.ai/open-source/features/metadata-filtering) |
|
||||
| Async clients: `AsyncMemory` and `AsyncMemoryClient` | [Async memory](https://docs.mem0.ai/open-source/features/async-memory) |
|
||||
| LLMs, embedders, vector stores, and rerankers | [Components](https://docs.mem0.ai/components/llms/overview) |
|
||||
| Graph memory | [Graph memory](https://docs.mem0.ai/platform/features/graph-memory) |
|
||||
| Custom instructions | [Custom instructions](https://docs.mem0.ai/open-source/features/custom-instructions) |
|
||||
| Multimodal input | [Multimodal support](https://docs.mem0.ai/open-source/features/multimodal-support) |
|
||||
| Platform webhooks, export, feedback, expiration, and custom categories | [Platform features](https://docs.mem0.ai/platform/features) |
|
||||
|
||||
## Benchmarks
|
||||
|
||||
<p align="center">
|
||||
<a href="https://mem0.ai/research"><strong>Benchmarking Mem0's token-efficient memory algorithm</strong></a>
|
||||
</p>
|
||||
|
||||
| Benchmark | Old | New | Tokens | Latency p50 |
|
||||
|---|---:|---:|---:|---:|
|
||||
| **LoCoMo** | 71.4 | **92.5** | 7.0K | 0.88s |
|
||||
| **LongMemEval** | 67.8 | **94.4** | 6.8K | 1.09s |
|
||||
| **BEAM (1M)** | n/a | **64.1** | 6.7K | 1.00s |
|
||||
| **BEAM (10M)** | n/a | **48.6** | 6.9K | 1.05s |
|
||||
|
||||
All benchmarks use the same production-representative model stack, single-pass retrieval, and a top-200 retrieval budget. Scores reflect the managed Platform, which includes proprietary optimizations not available in the open-source SDK. Open-source results should show similar directional gains, but may not match these scores.
|
||||
|
||||
The current algorithm uses single-pass ADD-only extraction, first-class agent facts, entity linking, multi-signal retrieval, and temporal reasoning. Read the [research paper](https://mem0.ai/research), the [migration guide](https://docs.mem0.ai/migration/oss-v2-to-v3), or the open-source [evaluation framework](https://github.com/mem0ai/memory-benchmarks).
|
||||
|
||||
## Self-hosted server
|
||||
|
||||
Run Mem0 as a FastAPI service with PostgreSQL, pgvector, and Neo4j:
|
||||
|
||||
```bash
|
||||
# Recommended: one command — start the stack, create an admin, issue the first API key.
|
||||
# Recommended: start the stack, create an admin, and 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
|
||||
# Manual: start the stack, then finish setup in the browser wizard.
|
||||
cd server && docker compose up -d
|
||||
```
|
||||
|
||||
See the [self-hosted docs](https://docs.mem0.ai/open-source/overview) for configuration.
|
||||
Self-hosted authentication is enabled by default. See the [self-hosted documentation](https://docs.mem0.ai/open-source/overview) and [upgrade notes](https://docs.mem0.ai/open-source/setup#upgrade-notes).
|
||||
|
||||
### Cloud Platform
|
||||
## CLI
|
||||
|
||||
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
|
||||
3. Using hosted Qdrant vectors? See the [Platform migration guide](https://docs.mem0.ai/migration/oss-to-platform) to import them into Mem0 Platform.
|
||||
|
||||
### CLI
|
||||
|
||||
Manage memories from your terminal:
|
||||
Manage hosted memories from your terminal:
|
||||
|
||||
```bash
|
||||
npm install -g @mem0/cli # or: pip install mem0-cli
|
||||
pip install mem0-cli
|
||||
|
||||
mem0 init
|
||||
mem0 add "Prefers dark mode and vim keybindings" --user-id alice
|
||||
mem0 search "What does Alice prefer?" --user-id alice
|
||||
```
|
||||
|
||||
See the [CLI documentation](https://docs.mem0.ai/platform/cli) for the full command reference.
|
||||
AI agents can create an account without email or a dashboard:
|
||||
|
||||
### Agent Skills
|
||||
```bash
|
||||
mem0 init --agent --agent-caller claude-code
|
||||
```
|
||||
|
||||
Teach your AI coding assistant (Claude Code, Codex, Cursor, Windsurf, OpenCode, OpenClaw, and any tool that supports the skills standard) how to build with Mem0. Two categories:
|
||||
The human owner can claim the account later with `mem0 init --email <their-email>`. The API key and memories remain unchanged. See the [CLI documentation](https://docs.mem0.ai/platform/cli) and [agent signup guide](https://docs.mem0.ai/platform/agent-signup).
|
||||
|
||||
**Reference skills — always on** (SDK knowledge loaded into the assistant's context):
|
||||
## Agent skills
|
||||
|
||||
Install reference skills to give compatible coding assistants Mem0 context:
|
||||
|
||||
```bash
|
||||
npx skills add https://github.com/mem0ai/mem0 --skill mem0
|
||||
npx skills add https://github.com/mem0ai/mem0 --skill mem0-cli
|
||||
npx skills add https://github.com/mem0ai/mem0 --skill mem0-vercel-ai-sdk
|
||||
```
|
||||
|
||||
**Pipeline skills — run on demand** (execute an end-to-end workflow in an existing repo):
|
||||
Install pipeline skills for end-to-end workflows:
|
||||
|
||||
```bash
|
||||
npx skills add https://github.com/mem0ai/mem0 --skill mem0-integrate
|
||||
@@ -189,72 +212,30 @@ npx skills add https://github.com/mem0ai/mem0 --skill mem0-test-integration
|
||||
npx skills add https://github.com/mem0ai/mem0 --skill mem0-oss-to-platform
|
||||
```
|
||||
|
||||
Use `/mem0-integrate` to wire Mem0 into an existing repo via a test-first pipeline, then `/mem0-test-integration` to verify. Use `/mem0-oss-to-platform` to migrate an existing project from Mem0 OSS to the hosted Platform SDK. See the [skills catalog](./skills/) or [Vibecoding with Mem0](https://docs.mem0.ai/vibecoding) for the full picture.
|
||||
See the [skills catalog](./skills/) or [Vibecoding with Mem0](https://docs.mem0.ai/vibecoding).
|
||||
|
||||
### Basic Usage
|
||||
## Integrations and demos
|
||||
|
||||
Mem0 requires an LLM to function, with `gpt-5-mini` from OpenAI as the default. However, it supports a variety of LLMs; for details, refer to our [Supported LLMs documentation](https://docs.mem0.ai/components/llms/overview).
|
||||
- [ChatGPT with Memory demo](https://mem0.dev/demo)
|
||||
- [Browser extension](https://chromewebstore.google.com/detail/onihkkbipkfeijkadecaafbgagkhglop?utm_source=item-share-cb)
|
||||
- [LangGraph integration](https://docs.mem0.ai/integrations/langgraph)
|
||||
- [CrewAI integration](https://docs.mem0.ai/integrations/crewai)
|
||||
|
||||
Mem0 uses `text-embedding-3-small` from OpenAI as the default embedding model. For best results with hybrid search (semantic + keyword + entity boosting), we recommend using at least [Qwen 600M](https://huggingface.co/Alibaba-NLP/gte-Qwen2-1.5B-instruct) or a comparable embedding model. See [Supported Embeddings](https://docs.mem0.ai/components/embedders/overview) for configuration details.
|
||||
## Documentation and help
|
||||
|
||||
First step is to instantiate the memory:
|
||||
- [Python quickstart](https://docs.mem0.ai/open-source/python-quickstart)
|
||||
- [Platform quickstart](https://docs.mem0.ai/platform/quickstart)
|
||||
- [API reference](https://docs.mem0.ai/api-reference)
|
||||
- [Discord](https://mem0.dev/DiG)
|
||||
- [GitHub issues](https://github.com/mem0ai/mem0/issues)
|
||||
- Email: founders@mem0.ai
|
||||
|
||||
```python
|
||||
from openai import OpenAI
|
||||
from mem0 import Memory
|
||||
## Contributing
|
||||
|
||||
openai_client = OpenAI()
|
||||
memory = Memory()
|
||||
|
||||
def chat_with_memories(message: str, user_id: str = "default_user") -> str:
|
||||
# Retrieve relevant memories
|
||||
relevant_memories = memory.search(query=message, filters={"user_id": user_id}, top_k=3)
|
||||
memories_str = "\n".join(f"- {entry['memory']}" for entry in relevant_memories["results"])
|
||||
|
||||
# Generate Assistant response
|
||||
system_prompt = f"You are a helpful AI. Answer the question based on query and memories.\nUser Memories:\n{memories_str}"
|
||||
messages = [{"role": "system", "content": system_prompt}, {"role": "user", "content": message}]
|
||||
response = openai_client.chat.completions.create(model="gpt-5-mini", messages=messages)
|
||||
assistant_response = response.choices[0].message.content
|
||||
|
||||
# Create new memories from the conversation
|
||||
messages.append({"role": "assistant", "content": assistant_response})
|
||||
memory.add(messages, user_id=user_id)
|
||||
|
||||
return assistant_response
|
||||
|
||||
def main():
|
||||
print("Chat with AI (type 'exit' to quit)")
|
||||
while True:
|
||||
user_input = input("You: ").strip()
|
||||
if user_input.lower() == 'exit':
|
||||
print("Goodbye!")
|
||||
break
|
||||
print(f"AI: {chat_with_memories(user_input)}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
|
||||
For detailed integration steps, see the [Quickstart](https://docs.mem0.ai/quickstart) and [API Reference](https://docs.mem0.ai/api-reference).
|
||||
|
||||
## 🔗 Integrations & Demos
|
||||
|
||||
- **ChatGPT with Memory**: Personalized chat powered by Mem0 ([Live Demo](https://mem0.dev/demo))
|
||||
- **Browser Extension**: Store memories across ChatGPT, Perplexity, and Claude ([Chrome Extension](https://chromewebstore.google.com/detail/onihkkbipkfeijkadecaafbgagkhglop?utm_source=item-share-cb))
|
||||
- **Langgraph Support**: Build a customer bot with Langgraph + Mem0 ([Guide](https://docs.mem0.ai/integrations/langgraph))
|
||||
- **CrewAI Integration**: Tailor CrewAI outputs with Mem0 ([Example](https://docs.mem0.ai/integrations/crewai))
|
||||
|
||||
## 📚 Documentation & Support
|
||||
|
||||
- Full docs: https://docs.mem0.ai
|
||||
- Community: [Discord](https://mem0.dev/DiG) · [X (formerly Twitter)](https://x.com/mem0ai)
|
||||
- Contact: founders@mem0.ai
|
||||
Read [CONTRIBUTING.md](https://github.com/mem0ai/mem0/blob/main/CONTRIBUTING.md) before opening an issue or pull request.
|
||||
|
||||
## Citation
|
||||
|
||||
We now have a paper you can cite:
|
||||
|
||||
```bibtex
|
||||
@article{mem0,
|
||||
title={Mem0: Building Production-Ready AI Agents with Scalable Long-Term Memory},
|
||||
@@ -264,6 +245,6 @@ We now have a paper you can cite:
|
||||
}
|
||||
```
|
||||
|
||||
## ⚖️ License
|
||||
## License
|
||||
|
||||
Apache 2.0 — see the [LICENSE](https://github.com/mem0ai/mem0/blob/main/LICENSE) file for details.
|
||||
Apache 2.0. See [LICENSE](https://github.com/mem0ai/mem0/blob/main/LICENSE).
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: "Preview Dream Scope"
|
||||
description: "A no-write preview of the scope Dream synthesis would analyze for a project."
|
||||
openapi: "post /api/v1/orgs/organizations/{org_id}/projects/{project_id}/dream/preview/"
|
||||
---
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: "Get Dream Activity"
|
||||
description: "Supersede/merge activity feed for a project, newest first (keyset-paginated)."
|
||||
openapi: "get /api/v1/orgs/organizations/{org_id}/projects/{project_id}/dream/activity/"
|
||||
---
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: "Get Dream Configuration"
|
||||
description: "Retrieve a project's Dream (memory synthesis) configuration and plan entitlements."
|
||||
openapi: "get /api/v1/orgs/organizations/{org_id}/projects/{project_id}/dream/config/"
|
||||
---
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: "Get a Synthesized Memory's Sources"
|
||||
description: "The source memories a synthesized (pattern) memory was distilled from."
|
||||
openapi: "get /api/v1/orgs/organizations/{org_id}/projects/{project_id}/dream/memory/{memory_id}/sources/"
|
||||
---
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: "Get Memories in a Dream Run"
|
||||
description: "Keyset page of the synthesized memories within a single synthesis run."
|
||||
openapi: "get /api/v1/orgs/organizations/{org_id}/projects/{project_id}/dream/runs/{run_id}/memories/"
|
||||
---
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: "Get Dream Synthesis Runs"
|
||||
description: "Synthesis activity grouped per run, newest first (keyset-paginated)."
|
||||
openapi: "get /api/v1/orgs/organizations/{org_id}/projects/{project_id}/dream/runs/"
|
||||
---
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: "Get Dream Stats"
|
||||
description: "Lifecycle and synthesis counts for a project, plus reflection freshness."
|
||||
openapi: "get /api/v1/orgs/organizations/{org_id}/projects/{project_id}/dream/stats/"
|
||||
---
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: "Update Dream Configuration"
|
||||
description: "Enable or disable Synthesis (reflection) for a project, or change the reflection mode."
|
||||
openapi: "patch /api/v1/orgs/organizations/{org_id}/projects/{project_id}/dream/config/"
|
||||
---
|
||||
@@ -8,14 +8,14 @@ mode: "wide"
|
||||
|
||||
**DeepSeek Harness: Mem0 as a Native Cordis Plugin**
|
||||
|
||||
The DeepSeek Harness agent forgets everything between sessions. [`@mem0/dsh-mem0`](https://www.npmjs.com/package/@mem0/dsh-mem0) gives it two Mem0-backed tools, so recall and writes persist across runs against the same memory bank you already use from Claude Code, Codex, and every other connected agent.
|
||||
The DeepSeek Harness agent forgets everything between sessions. [`@mem0/deepseek-plugin`](https://www.npmjs.com/package/@mem0/deepseek-plugin) gives it two Mem0-backed tools, so recall and writes persist across runs against the same memory bank you already use from Claude Code, Codex, and every other connected agent.
|
||||
|
||||
- **Two agent-callable tools:** `search_memory` recalls facts relevant to a query, `add_memory` stores a fact for future sessions. Both accept per-call `userId` / `agentId` / `runId` scope overrides.
|
||||
- **Native Cordis lifecycle:** The plugin declares `inject = ['tools']` so it waits for the harness tool registry, then registers through `ctx.tools.register()`. Unmounting the plugin removes the tools automatically.
|
||||
- **Managed backend, not a memory file:** Server-side extraction, semantic dedup, and conflict resolution, rather than a Markdown file the agent has to maintain itself.
|
||||
- **Config:** `userId` is required, `apiKey` defaults to `$MEM0_API_KEY`, and `host` optionally targets a dedicated Mem0 Platform base URL.
|
||||
|
||||
See [DeepSeek Harness](/integrations/dsh-mem0) for setup and [SDK & Tools](/changelog/sdk) for PR links.
|
||||
See [DeepSeek Harness](/integrations/deepseek-plugin) for setup and [SDK & Tools](/changelog/sdk) for PR links.
|
||||
|
||||
<Note>
|
||||
Developer preview. Auto-capture and auto-recall, where memory reaches the context with no explicit tool call, are planned but not yet built.
|
||||
@@ -27,7 +27,7 @@ See [DeepSeek Harness](/integrations/dsh-mem0) for setup and [SDK & Tools](/chan
|
||||
|
||||
**Strands Agents: Mem0 as a Native MemoryStore**
|
||||
|
||||
[`strands-mem0`](https://pypi.org/project/strands-mem0/) plugs Mem0 into AWS's [Strands Agents](https://strandsagents.com/) SDK as a native `MemoryStore`, so recall and writes happen inside the agent loop rather than as tool calls the model has to remember to make.
|
||||
[`mem0-strands`](https://pypi.org/project/mem0-strands/) plugs Mem0 into AWS's [Strands Agents](https://strandsagents.com/) SDK as a native `MemoryStore`, so recall and writes happen inside the agent loop rather than as tool calls the model has to remember to make.
|
||||
|
||||
- **Automatic recall:** The `MemoryManager` drives the store on every turn, searching Mem0 and injecting the results into the prompt with no tool call required.
|
||||
- **Server-side extraction:** Because the store implements `add_messages`, enabling extraction routes raw conversation turns straight to Mem0's extraction pipeline, skipping the extra client-side model call needed to distill facts first.
|
||||
|
||||
+19
-5
@@ -2841,9 +2841,16 @@ Existing memories written by the previous versions are not rewritten. If your me
|
||||
|
||||
<Tab title="Strands">
|
||||
|
||||
<Update label="2026-08-24" description="strands-mem0 v0.1.0">
|
||||
<Update label="2026-08-25" description="mem0-strands v0.1.1">
|
||||
|
||||
**Initial release** of [`strands-mem0`](https://pypi.org/project/strands-mem0/), a native `MemoryStore` that plugs Mem0 into the [Strands Agents](https://strandsagents.com/) `MemoryManager` ([#7021](https://github.com/mem0ai/mem0/pull/7021))
|
||||
**New Features:**
|
||||
- **Usage telemetry:** Anonymous usage events (`strands.store.init`, `strands.store.search`, `strands.store.add`, `strands.store.add_messages`) ride the Mem0 SDK's existing PostHog client, unsampled, with no new dependency. Events carry only counts, durations, booleans, and coarse failure kinds: never queries, memory text, message content, entity ids, metadata, or API keys. Opt out with `MEM0_TELEMETRY=false` ([#7110](https://github.com/mem0ai/mem0/pull/7110))
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-08-24" description="mem0-strands v0.1.0">
|
||||
|
||||
**Initial release** of [`mem0-strands`](https://pypi.org/project/mem0-strands/), a native `MemoryStore` that plugs Mem0 into the [Strands Agents](https://strandsagents.com/) `MemoryManager` ([#7021](https://github.com/mem0ai/mem0/pull/7021))
|
||||
|
||||
**New Features:**
|
||||
- **Automatic recall and injection:** `Mem0MemoryStore.search()` runs every turn through the `MemoryManager`, so relevant memories are searched and prepended to the prompt with no explicit tool call required.
|
||||
@@ -2863,9 +2870,16 @@ Existing memories written by the previous versions are not rewritten. If your me
|
||||
|
||||
<Tab title="DeepSeek Harness">
|
||||
|
||||
<Update label="2026-08-24" description="dsh-mem0 v0.1.0">
|
||||
<Update label="2026-08-25" description="deepseek-plugin v0.1.1">
|
||||
|
||||
**Initial release** of [`dsh-mem0`](https://www.npmjs.com/package/@mem0/dsh-mem0), a native DeepSeek Harness (Cordis) plugin that registers Mem0 as two agent-callable tools ([#7027](https://github.com/mem0ai/mem0/pull/7027))
|
||||
**New Features:**
|
||||
- **Usage telemetry:** Anonymous usage events (`deepseek.plugin.mounted`, `deepseek.tool.search_memory`, `deepseek.tool.add_memory`) are batched to PostHog over native fetch and flushed in the background. Events carry only tool names, durations, counts, and coarse failure kinds: never queries, memory text, filters, or API keys. Opt out with `MEM0_TELEMETRY=false` ([#7110](https://github.com/mem0ai/mem0/pull/7110))
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-08-24" description="deepseek-plugin v0.1.0">
|
||||
|
||||
**Initial release** of [`deepseek-plugin`](https://www.npmjs.com/package/@mem0/deepseek-plugin), a native DeepSeek Harness (Cordis) plugin that registers Mem0 as two agent-callable tools ([#7027](https://github.com/mem0ai/mem0/pull/7027))
|
||||
|
||||
**New Features:**
|
||||
- **`search_memory`:** Recalls facts relevant to a query, with an optional `limit` (default 10) and per-call `userId` / `agentId` / `runId` scope overrides.
|
||||
@@ -2874,7 +2888,7 @@ Existing memories written by the previous versions are not rewritten. If your me
|
||||
- **Config:** `userId` is required; `apiKey` defaults to `$MEM0_API_KEY`; `host` optionally points at a dedicated Mem0 Platform base URL (not a switch to self-hosted Mem0 OSS).
|
||||
|
||||
<Note>
|
||||
Developer preview: auto-capture and auto-recall (memory injected into context automatically, without an explicit tool call) are planned but not yet built. The backend's `KNOWN_EVENT_SOURCES` allowlist also needs `"DEEPSEEK_HARNESS"` added before usage surfaces by name in telemetry rather than bucketing into "OTHERS". See [DeepSeek Harness](/integrations/dsh-mem0) for setup.
|
||||
Developer preview: auto-capture and auto-recall (memory injected into context automatically, without an explicit tool call) are planned but not yet built. The backend's `KNOWN_EVENT_SOURCES` allowlist also needs `"DEEPSEEK_HARNESS"` added before usage surfaces by name in telemetry rather than bucketing into "OTHERS". See [DeepSeek Harness](/integrations/deepseek-plugin) for setup.
|
||||
</Note>
|
||||
|
||||
</Update>
|
||||
|
||||
+16
-1
@@ -325,6 +325,7 @@
|
||||
"integrations/google-ai-adk",
|
||||
"integrations/mastra",
|
||||
"integrations/vercel-ai-sdk",
|
||||
"integrations/vercel",
|
||||
"integrations/chatdev",
|
||||
"integrations/strands"
|
||||
]
|
||||
@@ -383,7 +384,7 @@
|
||||
"integrations/openclaw",
|
||||
"integrations/hermes",
|
||||
"integrations/pi-agent",
|
||||
"integrations/dsh-mem0"
|
||||
"integrations/deepseek-plugin"
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -538,6 +539,20 @@
|
||||
"api-reference/project/delete-project"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Dream",
|
||||
"icon": "sparkles",
|
||||
"pages": [
|
||||
"api-reference/dream/get-dream-config",
|
||||
"api-reference/dream/update-dream-config",
|
||||
"api-reference/dream/get-dream-stats",
|
||||
"api-reference/dream/get-dream-activity",
|
||||
"api-reference/dream/get-dream-runs",
|
||||
"api-reference/dream/get-dream-run-memories",
|
||||
"api-reference/dream/get-dream-memory-sources",
|
||||
"api-reference/dream/dream-preview"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Webhooks",
|
||||
"icon": "webhook",
|
||||
|
||||
@@ -3,7 +3,7 @@ title: DeepSeek Harness
|
||||
description: "Add persistent Mem0 memory to the DeepSeek Harness (Cordis) agent with two native tools: search and add."
|
||||
---
|
||||
|
||||
Add persistent memory to the [**DeepSeek Harness**](https://github.com/deepseek-ai/deepseek-harness) with `@mem0/dsh-mem0`. The Harness agent forgets everything between sessions. This plugin gives it two Mem0-backed tools so recall and writes persist across runs, sharing the same memory bank you already use from Claude Code, Codex, and other agents.
|
||||
Add persistent memory to the [**DeepSeek Harness**](https://github.com/deepseek-ai/deepseek-harness) with `@mem0/deepseek-plugin`. The Harness agent forgets everything between sessions. This plugin gives it two Mem0-backed tools so recall and writes persist across runs, sharing the same memory bank you already use from Claude Code, Codex, and other agents.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -23,8 +23,8 @@ A Cordis plugin is a module exporting `apply(ctx, config)`. This one declares `i
|
||||
## Prerequisites
|
||||
|
||||
1. A Mem0 Platform account and API key:
|
||||
- <a href="https://app.mem0.ai?utm_source=oss&utm_medium=integration-dsh-mem0" rel="nofollow">Sign up at app.mem0.ai</a>
|
||||
- <a href="https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=integration-dsh-mem0" rel="nofollow">Get your API key</a> (starts with `m0-`)
|
||||
- <a href="https://app.mem0.ai?utm_source=oss&utm_medium=integration-deepseek-plugin" rel="nofollow">Sign up at app.mem0.ai</a>
|
||||
- <a href="https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=integration-deepseek-plugin" rel="nofollow">Get your API key</a> (starts with `m0-`)
|
||||
|
||||
2. The DeepSeek Harness installed.
|
||||
|
||||
@@ -46,14 +46,14 @@ source ~/.bashrc
|
||||
|
||||
1. Build the plugin:
|
||||
```sh
|
||||
cd integrations/dsh-mem0
|
||||
cd integrations/deepseek-plugin
|
||||
pnpm install
|
||||
pnpm build
|
||||
```
|
||||
|
||||
2. Point the Harness at it. Copy `cordis.example.yml`, set the absolute path to `dist/index.js` and your `userId`, then load it:
|
||||
```sh
|
||||
pnpm dsh web --patch ./integrations/dsh-mem0/cordis.example.yml
|
||||
pnpm dsh web --patch ./integrations/deepseek-plugin/cordis.example.yml
|
||||
```
|
||||
|
||||
3. Open the web UI and ask the agent to remember something, then recall it in a later turn.
|
||||
@@ -65,7 +65,7 @@ The `cordis.yml` entry looks like this:
|
||||
- name: "@deepseek-ai/dsh-tools"
|
||||
- insert:
|
||||
- id: mem0
|
||||
name: "/absolute/path/to/integrations/dsh-mem0/dist/index.js"
|
||||
name: "/absolute/path/to/integrations/deepseek-plugin/dist/index.js"
|
||||
config:
|
||||
# apiKey is read from MEM0_API_KEY when omitted here.
|
||||
userId: "your-user-id"
|
||||
@@ -3,7 +3,7 @@ title: Strands Agents
|
||||
description: "Add persistent long-term memory to AWS Strands agents with Mem0, as a native MemoryStore that plugs into the agent loop."
|
||||
---
|
||||
|
||||
Integrate [**Mem0**](https://github.com/mem0ai/mem0) with [Strands Agents](https://github.com/strands-agents/sdk-python), AWS's open-source SDK for building AI agents. The [`strands-mem0`](https://github.com/mem0ai/mem0/tree/main/integrations/strands-mem0) package ships a native `MemoryStore`, so recall and writes happen automatically inside the agent loop, not as tool calls the model has to remember.
|
||||
Integrate [**Mem0**](https://github.com/mem0ai/mem0) with [Strands Agents](https://github.com/strands-agents/sdk-python), AWS's open-source SDK for building AI agents. The [`mem0-strands`](https://github.com/mem0ai/mem0/tree/main/integrations/mem0-strands) package ships a native `MemoryStore`, so recall and writes happen automatically inside the agent loop, not as tool calls the model has to remember.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -17,7 +17,7 @@ Before setting up Mem0 with Strands, ensure you have:
|
||||
|
||||
1. Installed the required packages:
|
||||
```bash
|
||||
pip install strands-mem0
|
||||
pip install mem0-strands
|
||||
```
|
||||
|
||||
2. A valid API key:
|
||||
@@ -31,7 +31,7 @@ Hand a `Mem0MemoryStore` to a `MemoryManager`, and the agent gets automatic reca
|
||||
import os
|
||||
from strands import Agent
|
||||
from strands.memory import MemoryManager
|
||||
from strands_mem0 import Mem0MemoryStore
|
||||
from mem0_strands import Mem0MemoryStore
|
||||
|
||||
os.environ["MEM0_API_KEY"] = "your-mem0-api-key"
|
||||
|
||||
@@ -65,5 +65,5 @@ If you want the model to call memory explicitly instead of (or alongside) the au
|
||||
|
||||
## Learn more
|
||||
|
||||
- [strands-mem0 on GitHub](https://github.com/mem0ai/mem0/tree/main/integrations/strands-mem0)
|
||||
- [mem0-strands on GitHub](https://github.com/mem0ai/mem0/tree/main/integrations/mem0-strands)
|
||||
- [Strands Agents documentation](https://strandsagents.com)
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
---
|
||||
title: Vercel
|
||||
description: "Add persistent memory to your Vercel apps with the Mem0 Vercel Marketplace integration. Install from the Marketplace, get an API key, and start building."
|
||||
---
|
||||
|
||||
The **Mem0 Vercel integration** provisions managed Mem0 access directly from the [Vercel Marketplace](https://vercel.com/marketplace). Installing it creates a Mem0 account for you and injects a `MEM0_API_KEY` into your Vercel project, so you can add long-term memory to your AI apps and agents in minutes, with no separate signup and billing handled through Vercel.
|
||||
|
||||
<Note type="info">
|
||||
This is the managed Marketplace integration. If you instead want to use Mem0 with the Vercel AI SDK in code, see [Vercel AI SDK](/integrations/vercel-ai-sdk).
|
||||
</Note>
|
||||
|
||||
## Overview
|
||||
|
||||
When you install Mem0 from the Vercel Marketplace:
|
||||
|
||||
- A Mem0 account and project are created for you (Vercel Native).
|
||||
- Mem0 environment variables (`MEM0_API_KEY`, `MEM0_ORG_ID`, `MEM0_PROJECT_ID`, `MEM0_BASE_URL`) are added to your connected Vercel project.
|
||||
- Billing is handled through your Vercel account, with no separate Mem0 subscription.
|
||||
|
||||
You call Mem0 from your app with the injected key. There is nothing else to wire up.
|
||||
|
||||
## Install
|
||||
|
||||
<Steps>
|
||||
<Step title="Add the integration">
|
||||
From the [Vercel Marketplace](https://vercel.com/marketplace), find **Mem0** and click **Install**. Choose **Create New Mem0 Account** (Vercel Native).
|
||||
</Step>
|
||||
<Step title="Pick a plan">
|
||||
Select **Hobby** (free) or **Starter** ($20/month). You can change this anytime from the installation settings in your Vercel dashboard.
|
||||
</Step>
|
||||
<Step title="Connect a project">
|
||||
Connect the Mem0 resource to a Vercel project. Mem0 injects its environment variables into that project.
|
||||
</Step>
|
||||
<Step title="Redeploy">
|
||||
Redeploy so the variables are available at runtime, or run `vercel env pull` to use them locally.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Environment variables
|
||||
|
||||
The integration sets the following on your connected project:
|
||||
|
||||
| Variable | Description |
|
||||
| --- | --- |
|
||||
| `MEM0_API_KEY` | Your Mem0 API key, scoped to the org and project created for this installation. This is what you authenticate with. |
|
||||
| `MEM0_ORG_ID` | The Mem0 organization id for this installation. |
|
||||
| `MEM0_PROJECT_ID` | The Mem0 project id for this installation. |
|
||||
| `MEM0_BASE_URL` | The Mem0 API base URL (`https://api.mem0.ai`). |
|
||||
|
||||
The API key is scoped to your org and project, so the SDK needs only `MEM0_API_KEY` to get started.
|
||||
|
||||
## Quickstart
|
||||
|
||||
Install the SDK:
|
||||
|
||||
<CodeGroup>
|
||||
```bash npm
|
||||
npm install mem0ai
|
||||
```
|
||||
|
||||
```bash pip
|
||||
pip install mem0ai
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
Add and search memories using the injected key:
|
||||
|
||||
<CodeGroup>
|
||||
```typescript JavaScript
|
||||
import MemoryClient from "mem0ai";
|
||||
|
||||
const memory = new MemoryClient({ apiKey: process.env.MEM0_API_KEY });
|
||||
|
||||
// Store a memory
|
||||
await memory.add(
|
||||
[{ role: "user", content: "I'm a vegetarian and allergic to nuts." }],
|
||||
{ userId: "user123" },
|
||||
);
|
||||
|
||||
// Retrieve relevant memories
|
||||
const results = await memory.search("What are my dietary restrictions?", {
|
||||
filters: { user_id: "user123" },
|
||||
});
|
||||
console.log(results);
|
||||
```
|
||||
|
||||
```python Python
|
||||
import os
|
||||
from mem0 import MemoryClient
|
||||
|
||||
memory = MemoryClient(api_key=os.environ["MEM0_API_KEY"])
|
||||
|
||||
# Store a memory
|
||||
memory.add(
|
||||
[{"role": "user", "content": "I'm a vegetarian and allergic to nuts."}],
|
||||
user_id="user123",
|
||||
)
|
||||
|
||||
# Retrieve relevant memories
|
||||
results = memory.search("What are my dietary restrictions?", filters={"user_id": "user123"})
|
||||
print(results)
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
See the [platform quickstart](/platform/quickstart) for the full API.
|
||||
|
||||
## Plans and billing
|
||||
|
||||
| Plan | Price | Memories | Retrieval calls | Projects | Support |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| Hobby | Free | 10,000 | 1,000 / month | 1 | Community |
|
||||
| Starter | $20 / month | 50,000 | 5,000 / month | 1 | Community |
|
||||
|
||||
Billing runs through your Vercel account; Mem0 never charges your card directly. You can upgrade or downgrade anytime from the installation's settings in the Vercel dashboard.
|
||||
|
||||
## Managing the integration
|
||||
|
||||
- **Change plan:** open the installation in your Vercel dashboard and update the billing plan.
|
||||
- **Open in Mem0:** use the **Open in Mem0** link to sign in to the Mem0 dashboard for this installation.
|
||||
- **Uninstall:** removing the integration deactivates the API key and tears down the resource. Any usage for the current period is invoiced before removal.
|
||||
|
||||
## Notes
|
||||
|
||||
- The integration creates a **new** Mem0 account for the installation. Linking an existing Mem0 account is not supported yet.
|
||||
- The injected `MEM0_API_KEY` is scoped to the org and project created for this installation, so you do not manage keys by hand.
|
||||
|
||||
## Related
|
||||
|
||||
- [Platform quickstart](/platform/quickstart)
|
||||
- [Vercel AI SDK integration](/integrations/vercel-ai-sdk)
|
||||
+12
-1
@@ -257,12 +257,13 @@ If the user is on a pre-current major (Python < 2, TS < 3, or a Platform call st
|
||||
- [ChatDev](https://docs.mem0.ai/integrations/chatdev) [Platform]: Use when the user is on ChatDev.
|
||||
- [Hermes](https://docs.mem0.ai/integrations/hermes) [Both]: Use when the user is on Hermes.
|
||||
- [Pi Agent](https://docs.mem0.ai/integrations/pi-agent) [Platform]: Use when adding persistent memory to Pi Agent with the Mem0 plugin.
|
||||
- [DeepSeek Harness](https://docs.mem0.ai/integrations/dsh-mem0) [Platform]: Use when adding persistent memory to the DeepSeek Harness (Cordis) agent via the Mem0 plugin.
|
||||
- [DeepSeek Harness](https://docs.mem0.ai/integrations/deepseek-plugin) [Platform]: Use when adding persistent memory to the DeepSeek Harness (Cordis) agent via the Mem0 plugin.
|
||||
- [OpenAI Agents SDK](https://docs.mem0.ai/integrations/openai-agents-sdk) [Platform]: Use when the user is on the OpenAI Agents SDK.
|
||||
- [Google AI ADK](https://docs.mem0.ai/integrations/google-ai-adk) [Platform]: Use when the user is on Google's Agent Development Kit.
|
||||
- [Mastra](https://docs.mem0.ai/integrations/mastra) [Platform]: Use when the user is on Mastra (TypeScript).
|
||||
- [OpenClaw](https://docs.mem0.ai/integrations/openclaw) [Both]: Use when wiring Mem0 into Claude Code or editors via OpenClaw.
|
||||
- [Vercel AI SDK](https://docs.mem0.ai/integrations/vercel-ai-sdk) [Both]: Use when the user is on the Vercel AI SDK.
|
||||
- [Vercel](https://docs.mem0.ai/integrations/vercel) [Platform]: Use when deploying on Vercel and installing Mem0 from the Vercel Marketplace.
|
||||
- [Strands Agents](https://docs.mem0.ai/integrations/strands) [Both]: Use when the user is on AWS Strands and wants a native MemoryStore.
|
||||
|
||||
### AI Coding Tools
|
||||
@@ -382,6 +383,16 @@ All API Reference docs describe Mem0 Platform REST endpoints (requires API key).
|
||||
- [Remove Project Member](https://docs.mem0.ai/api-reference/project/remove-project-member) [Platform]: Use when removing a member from a project.
|
||||
- [Delete Project](https://docs.mem0.ai/api-reference/project/delete-project) [Platform]: Use when removing a project.
|
||||
|
||||
### Dream
|
||||
- [Get Dream Configuration](https://docs.mem0.ai/api-reference/dream/get-dream-config) [Platform]: Use when reading a project's Dream (memory synthesis) config and plan entitlements.
|
||||
- [Update Dream Configuration](https://docs.mem0.ai/api-reference/dream/update-dream-config) [Platform]: Use when enabling/disabling Synthesis or changing its mode for a project.
|
||||
- [Get Dream Stats](https://docs.mem0.ai/api-reference/dream/get-dream-stats) [Platform]: Use when fetching lifecycle and synthesis counts for the Dream dashboard.
|
||||
- [Get Dream Activity](https://docs.mem0.ai/api-reference/dream/get-dream-activity) [Platform]: Use when listing recent supersede/merge activity for a project.
|
||||
- [Get Dream Synthesis Runs](https://docs.mem0.ai/api-reference/dream/get-dream-runs) [Platform]: Use when listing synthesis runs and the memories they generated.
|
||||
- [Get Memories in a Dream Run](https://docs.mem0.ai/api-reference/dream/get-dream-run-memories) [Platform]: Use when paging the synthesized memories within a single run.
|
||||
- [Get a Synthesized Memory's Sources](https://docs.mem0.ai/api-reference/dream/get-dream-memory-sources) [Platform]: Use when tracing the source memories a synthesized memory was distilled from.
|
||||
- [Preview Dream Scope](https://docs.mem0.ai/api-reference/dream/dream-preview) [Platform]: Use when previewing what Synthesis would analyze for a project (no writes).
|
||||
|
||||
### Webhooks
|
||||
- [Create Webhook](https://docs.mem0.ai/api-reference/webhook/create-webhook) [Platform]: Use when registering a webhook endpoint.
|
||||
- [Get Webhook](https://docs.mem0.ai/api-reference/webhook/get-webhook) [Platform]: Use when fetching webhook config.
|
||||
|
||||
@@ -7207,6 +7207,869 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/orgs/organizations/{org_id}/projects/{project_id}/dream/config/": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"dream"
|
||||
],
|
||||
"summary": "Get Dream configuration",
|
||||
"description": "Retrieve the project's Dream (memory synthesis) configuration together with the plan entitlement snapshot. Dream automatically supersedes/merges memories on the add path; **Synthesis** (reflection) is the opt-in part controlled by `reflection_enabled`.",
|
||||
"operationId": "get_dream_config",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "org_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "Unique identifier of the organization.",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "project_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "Unique identifier of the project.",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful response.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"config": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"reflection_enabled": {
|
||||
"type": "boolean",
|
||||
"description": "Whether background synthesis (reflection) is enabled for the project."
|
||||
},
|
||||
"reflection_enabled_at": {
|
||||
"type": "string",
|
||||
"description": "When synthesis was last enabled. Synthesis only considers memories created on or after this time (forward-only).",
|
||||
"format": "date-time",
|
||||
"nullable": true
|
||||
},
|
||||
"reflection_mode": {
|
||||
"type": "string",
|
||||
"description": "Synthesis execution mode.",
|
||||
"enum": [
|
||||
"batch",
|
||||
"direct"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"entitlements": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"plan": {
|
||||
"type": "string",
|
||||
"description": "Resolved billing tier for the project (e.g. `free`, `pro`, `custom`)."
|
||||
},
|
||||
"reflection_interval_days": {
|
||||
"type": "integer",
|
||||
"description": "How often synthesis runs for this plan, in days (Pro weekly, Enterprise daily)."
|
||||
},
|
||||
"features": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"description": "Per-feature entitlement snapshot, keyed by Dream feature (e.g. `dream_reflection`, `dream_tab`, `dream_preview`).",
|
||||
"additionalProperties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"entitled": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the plan is entitled to the feature."
|
||||
},
|
||||
"killed": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the feature is globally disabled by a kill switch."
|
||||
},
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"description": "Effective state: entitled AND not killed AND turned on."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"patch": {
|
||||
"tags": [
|
||||
"dream"
|
||||
],
|
||||
"summary": "Update Dream configuration",
|
||||
"description": "Enable or disable Synthesis (reflection) for the project, or change the reflection mode. Requires a Pro or Enterprise (CUSTOM) plan and organization-owner permission. Synthesis is forward-only: after enabling, it only synthesizes memories added from that point on.",
|
||||
"operationId": "update_dream_config",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "org_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "Unique identifier of the organization.",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "project_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "Unique identifier of the project.",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"reflection_enabled": {
|
||||
"type": "boolean",
|
||||
"description": "Turn background synthesis on or off."
|
||||
},
|
||||
"reflection_mode": {
|
||||
"type": "string",
|
||||
"description": "Synthesis execution mode.",
|
||||
"enum": [
|
||||
"batch",
|
||||
"direct"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Updated configuration.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"config": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"reflection_enabled": {
|
||||
"type": "boolean",
|
||||
"description": "Whether background synthesis (reflection) is enabled for the project."
|
||||
},
|
||||
"reflection_enabled_at": {
|
||||
"type": "string",
|
||||
"description": "When synthesis was last enabled. Synthesis only considers memories created on or after this time (forward-only).",
|
||||
"format": "date-time",
|
||||
"nullable": true
|
||||
},
|
||||
"reflection_mode": {
|
||||
"type": "string",
|
||||
"description": "Synthesis execution mode.",
|
||||
"enum": [
|
||||
"batch",
|
||||
"direct"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"entitlements": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"plan": {
|
||||
"type": "string",
|
||||
"description": "Resolved billing tier for the project (e.g. `free`, `pro`, `custom`)."
|
||||
},
|
||||
"reflection_interval_days": {
|
||||
"type": "integer",
|
||||
"description": "How often synthesis runs for this plan, in days (Pro weekly, Enterprise daily)."
|
||||
},
|
||||
"features": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"description": "Per-feature entitlement snapshot, keyed by Dream feature (e.g. `dream_reflection`, `dream_tab`, `dream_preview`).",
|
||||
"additionalProperties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"entitled": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the plan is entitled to the feature."
|
||||
},
|
||||
"killed": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the feature is globally disabled by a kill switch."
|
||||
},
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"description": "Effective state: entitled AND not killed AND turned on."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Validation error (e.g. a field the plan is not entitled to change)."
|
||||
},
|
||||
"403": {
|
||||
"description": "Not entitled on the current plan (`upgrade_required: true`), or the caller is not an organization owner."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/orgs/organizations/{org_id}/projects/{project_id}/dream/stats/": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"dream"
|
||||
],
|
||||
"summary": "Get Dream stats",
|
||||
"description": "Lifecycle + synthesis counts for the project's Dream dashboard, plus reflection freshness. Requires a Pro or Enterprise (CUSTOM) plan.",
|
||||
"operationId": "get_dream_stats",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "org_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "Unique identifier of the organization.",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "project_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "Unique identifier of the project.",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful response.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"active": {
|
||||
"type": "integer",
|
||||
"description": "Active (current) memory count."
|
||||
},
|
||||
"merged": {
|
||||
"type": "integer",
|
||||
"description": "Count of memories merged into a canonical one."
|
||||
},
|
||||
"superseded": {
|
||||
"type": "integer",
|
||||
"description": "Count of memories superseded by a newer one."
|
||||
},
|
||||
"synthesized": {
|
||||
"type": "integer",
|
||||
"description": "Count of higher-order pattern memories created by synthesis."
|
||||
},
|
||||
"last_run_at": {
|
||||
"type": "string",
|
||||
"description": "When synthesis last completed.",
|
||||
"format": "date-time",
|
||||
"nullable": true
|
||||
},
|
||||
"processed_through": {
|
||||
"type": "string",
|
||||
"description": "Timestamp of the newest source memory synthesis has processed.",
|
||||
"format": "date-time",
|
||||
"nullable": true
|
||||
},
|
||||
"running": {
|
||||
"type": "boolean",
|
||||
"description": "Whether a synthesis run is currently in flight."
|
||||
},
|
||||
"next_run_at": {
|
||||
"type": "string",
|
||||
"description": "Projected next synthesis run.",
|
||||
"format": "date-time",
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Not entitled on the current plan (`upgrade_required: true`)."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/orgs/organizations/{org_id}/projects/{project_id}/dream/activity/": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"dream"
|
||||
],
|
||||
"summary": "Get Dream activity",
|
||||
"description": "Supersede/merge activity feed (newest first), keyset-paginated. Synthesis output is not included here — see the runs endpoint.",
|
||||
"operationId": "get_dream_activity",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "org_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "Unique identifier of the organization.",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "project_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "Unique identifier of the project.",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "limit",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"description": "Page size (default 50, max 200).",
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"default": 50
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "cursor",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"description": "Opaque keyset cursor from a previous page's `next_cursor`.",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful response.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"results": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"memory_id": {
|
||||
"type": "string",
|
||||
"description": "The superseded/merged memory's ID."
|
||||
},
|
||||
"text": {
|
||||
"type": "string",
|
||||
"description": "The memory's text (truncated)."
|
||||
},
|
||||
"transition": {
|
||||
"type": "string",
|
||||
"description": "Lifecycle transition.",
|
||||
"enum": [
|
||||
"merged",
|
||||
"superseded"
|
||||
]
|
||||
},
|
||||
"at": {
|
||||
"type": "string",
|
||||
"description": "When the transition happened.",
|
||||
"format": "date-time"
|
||||
},
|
||||
"replaced_by": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "ID of the newer memory."
|
||||
},
|
||||
"text": {
|
||||
"type": "string",
|
||||
"description": "Text of the newer memory (truncated).",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
"nullable": true,
|
||||
"description": "The newer memory that replaced this one (null for a merge with no single replacement)."
|
||||
},
|
||||
"sources": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Source memory ID."
|
||||
},
|
||||
"text": {
|
||||
"type": "string",
|
||||
"description": "Source memory text (truncated)."
|
||||
}
|
||||
}
|
||||
},
|
||||
"nullable": true,
|
||||
"description": "Always null on this feed; synthesis provenance is shown on the runs feed."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"next_cursor": {
|
||||
"type": "string",
|
||||
"description": "Cursor for the next page, or null when there are no more.",
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/orgs/organizations/{org_id}/projects/{project_id}/dream/runs/": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"dream"
|
||||
],
|
||||
"summary": "Get Dream synthesis runs",
|
||||
"description": "Synthesis activity grouped per run (newest first), keyset-paginated. Each run inlines its first page of synthesized memories with their sources; larger runs page the rest via the run-memories endpoint.",
|
||||
"operationId": "get_dream_runs",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "org_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "Unique identifier of the organization.",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "project_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "Unique identifier of the project.",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "limit",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"description": "Number of runs per page (default 20, max 100).",
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"default": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "cursor",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"description": "Opaque keyset cursor from a previous page's `next_cursor`.",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful response.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"results": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"run_id": {
|
||||
"type": "string",
|
||||
"description": "Identifier of the synthesis run."
|
||||
},
|
||||
"at": {
|
||||
"type": "string",
|
||||
"description": "When the run produced memories.",
|
||||
"format": "date-time"
|
||||
},
|
||||
"count": {
|
||||
"type": "integer",
|
||||
"description": "Total synthesized memories in this run."
|
||||
},
|
||||
"synthesized": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Synthesized memory ID."
|
||||
},
|
||||
"text": {
|
||||
"type": "string",
|
||||
"description": "Synthesized (pattern) memory text."
|
||||
},
|
||||
"sources": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Source memory ID."
|
||||
},
|
||||
"text": {
|
||||
"type": "string",
|
||||
"description": "Source memory text (truncated)."
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Source memories this pattern was distilled from."
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "First page of synthesized memories (each with its sources)."
|
||||
},
|
||||
"has_more": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the run has more memories than are inlined here."
|
||||
},
|
||||
"mem_cursor": {
|
||||
"type": "string",
|
||||
"description": "Cursor to page this run's remaining memories via the run-memories endpoint.",
|
||||
"nullable": true
|
||||
},
|
||||
"sources": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Source memory ID."
|
||||
},
|
||||
"text": {
|
||||
"type": "string",
|
||||
"description": "Source memory text (truncated)."
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "De-duplicated source memories across the inlined page."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"next_cursor": {
|
||||
"type": "string",
|
||||
"description": "Cursor for the next page, or null when there are no more.",
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/orgs/organizations/{org_id}/projects/{project_id}/dream/runs/{run_id}/memories/": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"dream"
|
||||
],
|
||||
"summary": "Get memories in a Dream run",
|
||||
"description": "Keyset page of the synthesized memories within a single run (for runs whose `has_more` is true).",
|
||||
"operationId": "get_dream_run_memories",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "org_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "Unique identifier of the organization.",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "project_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "Unique identifier of the project.",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "run_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "Identifier of the synthesis run.",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "limit",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"description": "Page size (default 50, max 200).",
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"default": 50
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "cursor",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"description": "Opaque keyset cursor (use the run's `mem_cursor` or a previous `next_cursor`).",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful response.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"results": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Synthesized memory ID."
|
||||
},
|
||||
"text": {
|
||||
"type": "string",
|
||||
"description": "Synthesized (pattern) memory text."
|
||||
},
|
||||
"sources": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Source memory ID."
|
||||
},
|
||||
"text": {
|
||||
"type": "string",
|
||||
"description": "Source memory text (truncated)."
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Source memories this pattern was distilled from."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"next_cursor": {
|
||||
"type": "string",
|
||||
"description": "Cursor for the next page, or null when there are no more.",
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/orgs/organizations/{org_id}/projects/{project_id}/dream/memory/{memory_id}/sources/": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"dream"
|
||||
],
|
||||
"summary": "Get a synthesized memory's sources",
|
||||
"description": "The source memories a synthesized (pattern) memory was distilled from, for the memory drawer's provenance panel.",
|
||||
"operationId": "get_dream_memory_sources",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "org_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "Unique identifier of the organization.",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "project_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "Unique identifier of the project.",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "memory_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "ID of the (synthesized) memory.",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful response.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"memory_id": {
|
||||
"type": "string",
|
||||
"description": "The memory's ID."
|
||||
},
|
||||
"synthesized": {
|
||||
"type": "boolean",
|
||||
"description": "Whether this memory was produced by synthesis."
|
||||
},
|
||||
"sources": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Source memory ID."
|
||||
},
|
||||
"text": {
|
||||
"type": "string",
|
||||
"description": "Source memory text."
|
||||
},
|
||||
"lifecycle_state": {
|
||||
"type": "string",
|
||||
"description": "Source memory lifecycle state.",
|
||||
"enum": [
|
||||
"active",
|
||||
"merged",
|
||||
"superseded"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Memory not found in this project."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/orgs/organizations/{org_id}/projects/{project_id}/dream/preview/": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"dream"
|
||||
],
|
||||
"summary": "Preview Dream scope",
|
||||
"description": "A no-write preview of the scope Dream synthesis would analyze for the project (a capped sample plus the count of users with enough memories to benefit). Never runs the LLM and never mutates anything.",
|
||||
"operationId": "dream_preview",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "org_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "Unique identifier of the organization.",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "project_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "Unique identifier of the project.",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful response.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"active_total": {
|
||||
"type": "integer",
|
||||
"description": "Total active memories in the project."
|
||||
},
|
||||
"scanned": {
|
||||
"type": "integer",
|
||||
"description": "How many recent memories the preview aggregated over (capped)."
|
||||
},
|
||||
"cap": {
|
||||
"type": "integer",
|
||||
"description": "The scan cap applied."
|
||||
},
|
||||
"eligible_users": {
|
||||
"type": "integer",
|
||||
"description": "Users with enough memories to benefit from synthesis."
|
||||
},
|
||||
"note": {
|
||||
"type": "string",
|
||||
"description": "Human-readable note about the preview scope."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Preview not available on the current plan (`upgrade_required: true`)."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
|
||||
@@ -9,12 +9,12 @@ Agent and editor integrations. Each subdirectory is self-contained: its own `pac
|
||||
| `mem0-plugin/` | Claude Code / Cursor / Codex plugin | none | none | pytest |
|
||||
| `mem0-plugin/.opencode-plugin/` | `@mem0/opencode-plugin` | Bun | none | tsc type-check |
|
||||
| `pi-agent-plugin/` | `@mem0/pi-agent-plugin` | tsup | none | vitest |
|
||||
| `dsh-mem0/` | `@mem0/dsh-mem0` | tsup (ESM) | none | vitest |
|
||||
| `deepseek-plugin/` | `@mem0/deepseek-plugin` | tsup (ESM) | none | vitest |
|
||||
| `n8n-nodes-mem0/` | `@mem0/n8n-nodes-mem0` | tsc | ESLint (n8n-nodes-base) | none |
|
||||
| `zapier-mem0/` | `@mem0/zapier` | tsc | none | offline unit tests + `zapier validate` |
|
||||
| `strands-mem0/` | `strands-mem0` (PyPI) | hatch | Ruff + mypy | pytest |
|
||||
| `mem0-strands/` | `mem0-strands` (PyPI) | hatch | Ruff + mypy | pytest |
|
||||
|
||||
pnpm everywhere except `.opencode-plugin/` (Bun) and `strands-mem0/` (Python: pip / hatch). Never npm, never yarn.
|
||||
pnpm everywhere except `.opencode-plugin/` (Bun) and `mem0-strands/` (Python: pip / hatch). Never npm, never yarn.
|
||||
|
||||
## Commands
|
||||
|
||||
@@ -41,10 +41,10 @@ Run the type check after every TypeScript change: `pnpm run typecheck` or `tsc -
|
||||
|
||||
- **`vercel-ai-sdk/`** wraps the Vercel AI SDK through a `createMem0` provider. Integrations for AI-SDK repos go through this wrapper, not raw `MemoryClient`.
|
||||
- **`mem0-plugin/`** connects Claude Code, Cursor, and Codex to the MCP server at `mcp.mem0.ai` and installs lifecycle hooks for automatic memory capture. Exposes 9 MCP tools: `add_memory`, `search_memories`, `get_memories`, `get_memory`, `update_memory`, `delete_memory`, `delete_all_memories`, `delete_entities`, `list_entities`.
|
||||
- **`openclaw/`**, **`pi-agent-plugin/`**, **`dsh-mem0/`** are editor and agent plugins with the same shape. `dsh-mem0/` registers Mem0 search/add tools as a native DeepSeek Harness (Cordis) plugin.
|
||||
- **`openclaw/`**, **`pi-agent-plugin/`**, **`deepseek-plugin/`** are editor and agent plugins with the same shape. `deepseek-plugin/` registers Mem0 search/add tools as a native DeepSeek Harness (Cordis) plugin.
|
||||
- **`n8n-nodes-mem0/`** is an n8n community node: add, search, get, update, delete.
|
||||
- **`zapier-mem0/`** is a Zapier Platform CLI app: add, search, get, delete. It deploys to Zapier, not npm, so it is **not** in the release router. Deploy it with `gh workflow run zapier-mem0-cd.yml --ref main` (needs the `ZAPIER_DEPLOY_KEY` secret).
|
||||
- **`strands-mem0/`** is a native Strands `MemoryStore` (Python, published to PyPI as `strands-mem0`). It plugs into the Strands `MemoryManager` for automatic recall and server-side extraction, over the hosted Mem0 platform or self-hosted Mem0 OSS. The package lives under `strands-mem0/python/`.
|
||||
- **`mem0-strands/`** is a native Strands `MemoryStore` (Python, published to PyPI as `mem0-strands`). It plugs into the Strands `MemoryManager` for automatic recall and server-side extraction, over the hosted Mem0 platform or self-hosted Mem0 OSS. The package lives under `mem0-strands/python/`.
|
||||
|
||||
## Adding an integration
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# dsh-mem0
|
||||
# deepseek-plugin
|
||||
|
||||
[Mem0](https://mem0.ai) long-term memory as a native [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (Cordis) plugin.
|
||||
|
||||
@@ -18,7 +18,7 @@ A Cordis plugin is a module exporting `apply(ctx, config)`. This one declares `i
|
||||
```
|
||||
[ mem0ai SDK ] <-- managed memory, owned by Mem0
|
||||
|
|
||||
[ dsh-mem0: apply(ctx) -> ctx.tools.register(...) ] <-- this package
|
||||
[ deepseek-plugin: apply(ctx) -> ctx.tools.register(...) ] <-- this package
|
||||
|
|
||||
[ DeepSeek Harness ] <-- the agent, loaded via cordis.yml
|
||||
```
|
||||
@@ -27,7 +27,7 @@ A Cordis plugin is a module exporting `apply(ctx, config)`. This one declares `i
|
||||
|
||||
1. Build the plugin:
|
||||
```sh
|
||||
cd integrations/dsh-mem0
|
||||
cd integrations/deepseek-plugin
|
||||
pnpm install
|
||||
pnpm build
|
||||
```
|
||||
@@ -37,7 +37,7 @@ A Cordis plugin is a module exporting `apply(ctx, config)`. This one declares `i
|
||||
```
|
||||
3. Point Harness at it. Copy `cordis.example.yml`, set the absolute path to `dist/index.js` and your `userId`, then:
|
||||
```sh
|
||||
pnpm dsh web --patch ./integrations/dsh-mem0/cordis.example.yml
|
||||
pnpm dsh web --patch ./integrations/deepseek-plugin/cordis.example.yml
|
||||
```
|
||||
4. Open http://127.0.0.1:3080 and ask the agent to remember something, then recall it in a later turn.
|
||||
|
||||
@@ -55,6 +55,8 @@ For a Mem0 Platform on-prem or dedicated deployment, point `config.host` at that
|
||||
|
||||
Writes are tagged `source="DEEPSEEK_HARNESS"` so Mem0's backend can attribute usage to this integration. For it to surface by name (rather than bucketing into `OTHERS`), `DEEPSEEK_HARNESS` must be present in the backend's `KNOWN_EVENT_SOURCES` allowlist, a one-line platform change matching the existing `ZAPIER` / `STRANDS` sources.
|
||||
|
||||
The plugin also sends anonymous usage events (which tool ran, duration, result counts, coarse failure kind) so Mem0 can tell how the plugin is used and where it breaks. Queries, memory text, and entity ids are never sent. Turn it off with `MEM0_TELEMETRY=false`.
|
||||
|
||||
## Status
|
||||
|
||||
Developer preview. Tracks the DeepSeek Harness v0.1 plugin API (`@deepseek-ai/cordis`, `@deepseek-ai/dsh-tools`), which is young and moving; pin versions once it stabilizes. Auto-capture (store turns without an explicit tool call) and auto-recall (inject memory into the prompt at assembly) are planned once the harness session/assembly event API is confirmed.
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
# Example DeepSeek Harness config that loads dsh-mem0 alongside the built-in
|
||||
# Example DeepSeek Harness config that loads deepseek-plugin alongside the built-in
|
||||
# tools plugin. Load with:
|
||||
#
|
||||
# pnpm dsh web --patch ./integrations/dsh-mem0/cordis.example.yml
|
||||
# pnpm dsh web --patch ./integrations/deepseek-plugin/cordis.example.yml
|
||||
#
|
||||
# The tools plugin (and its system-prompt dependency) must be present, because
|
||||
# the memory tools contribute schemas the system prompt renders.
|
||||
@@ -12,7 +12,7 @@
|
||||
- id: mem0
|
||||
# Absolute path to the built plugin (run `pnpm build` first), or point
|
||||
# at src/index.ts when running through tsx during development.
|
||||
name: "/absolute/path/to/mem0/integrations/dsh-mem0/dist/index.js"
|
||||
name: "/absolute/path/to/mem0/integrations/deepseek-plugin/dist/index.js"
|
||||
config:
|
||||
# apiKey is read from the MEM0_API_KEY env var when omitted here.
|
||||
userId: "your-user-id"
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "@mem0/dsh-mem0",
|
||||
"version": "0.1.0",
|
||||
"name": "@mem0/deepseek-plugin",
|
||||
"version": "0.1.1",
|
||||
"description": "Mem0 long-term memory as a native DeepSeek Harness (Cordis) plugin.",
|
||||
"type": "module",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mem0ai/mem0",
|
||||
"directory": "integrations/dsh-mem0"
|
||||
"directory": "integrations/deepseek-plugin"
|
||||
},
|
||||
"keywords": [
|
||||
"deepseek-harness",
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* dsh-mem0: Mem0 long-term memory as a native DeepSeek Harness (Cordis) plugin.
|
||||
* deepseek-plugin: Mem0 long-term memory as a native DeepSeek Harness (Cordis) plugin.
|
||||
*
|
||||
* Registers two agent-callable tools backed by the Mem0 SDK:
|
||||
* - `search_memory` recalls facts relevant to a query
|
||||
@@ -16,6 +16,7 @@ import { MemoryClient } from "mem0ai";
|
||||
import { formatMemoryList, formatAddResult } from "./formatting.ts";
|
||||
import { truncateOutput } from "./output.ts";
|
||||
import { resolveSearchFilters, resolveAddParams } from "./scoping.ts";
|
||||
import { captureEvent, errorKind } from "./telemetry.ts";
|
||||
|
||||
export const name = "mem0";
|
||||
export const inject = ["tools"];
|
||||
@@ -69,11 +70,11 @@ const scopeParams = {
|
||||
export function apply(ctx: Context, config: Config): void {
|
||||
const apiKey = config.apiKey ?? process.env.MEM0_API_KEY;
|
||||
if (!apiKey) {
|
||||
throw new Error("dsh-mem0: set config.apiKey or the MEM0_API_KEY env var");
|
||||
throw new Error("deepseek-plugin: set config.apiKey or the MEM0_API_KEY env var");
|
||||
}
|
||||
const userId = config.userId;
|
||||
if (!userId) {
|
||||
throw new Error("dsh-mem0: config.userId is required");
|
||||
throw new Error("deepseek-plugin: config.userId is required");
|
||||
}
|
||||
|
||||
const client = new MemoryClient({
|
||||
@@ -81,6 +82,8 @@ export function apply(ctx: Context, config: Config): void {
|
||||
...(config.host ? { host: config.host } : {}),
|
||||
});
|
||||
|
||||
captureEvent("deepseek.plugin.mounted", { has_host: Boolean(config.host) }, client);
|
||||
|
||||
// Recall. The platform rejects top-level entity params on search, so scope
|
||||
// goes inside `filters` (unlike add below, which takes them top-level).
|
||||
ctx.tools.register(
|
||||
@@ -99,11 +102,36 @@ export function apply(ctx: Context, config: Config): void {
|
||||
output: textOutput,
|
||||
async execute({ query, limit, userId: u, agentId, runId }) {
|
||||
const filters = resolveSearchFilters({ userId: u, agentId, runId }, userId);
|
||||
const topK = limit && limit > 0 ? limit : DEFAULT_SEARCH_LIMIT;
|
||||
const started = Date.now();
|
||||
try {
|
||||
const topK = limit && limit > 0 ? limit : DEFAULT_SEARCH_LIMIT;
|
||||
const { results } = await client.search(query, { filters, topK });
|
||||
captureEvent(
|
||||
"deepseek.tool.search_memory",
|
||||
{
|
||||
success: true,
|
||||
duration_ms: Date.now() - started,
|
||||
top_k: topK,
|
||||
result_count: results?.length ?? 0,
|
||||
query_chars: query.length,
|
||||
scope_overridden: Boolean(u && u !== userId),
|
||||
has_agent_id: Boolean(agentId),
|
||||
has_run_id: Boolean(runId),
|
||||
},
|
||||
client,
|
||||
);
|
||||
return truncateOutput(formatMemoryList(results ?? []));
|
||||
} catch (err) {
|
||||
captureEvent(
|
||||
"deepseek.tool.search_memory",
|
||||
{
|
||||
success: false,
|
||||
duration_ms: Date.now() - started,
|
||||
top_k: topK,
|
||||
error_kind: errorKind(err),
|
||||
},
|
||||
client,
|
||||
);
|
||||
return `search_memory failed: ${err instanceof Error ? err.message : String(err)}`;
|
||||
}
|
||||
},
|
||||
@@ -123,13 +151,37 @@ export function apply(ctx: Context, config: Config): void {
|
||||
output: textOutput,
|
||||
async execute({ text, userId: u, agentId, runId }) {
|
||||
const addParams = resolveAddParams({ userId: u, agentId, runId }, userId);
|
||||
const started = Date.now();
|
||||
try {
|
||||
const result = await client.add([{ role: "user", content: text }], {
|
||||
...addParams,
|
||||
source: SOURCE,
|
||||
});
|
||||
captureEvent(
|
||||
"deepseek.tool.add_memory",
|
||||
{
|
||||
success: true,
|
||||
duration_ms: Date.now() - started,
|
||||
text_chars: text.length,
|
||||
memory_count: Array.isArray(result) ? result.length : 0,
|
||||
scope_overridden: Boolean(u && u !== userId),
|
||||
has_agent_id: Boolean(agentId),
|
||||
has_run_id: Boolean(runId),
|
||||
},
|
||||
client,
|
||||
);
|
||||
return truncateOutput(formatAddResult(result));
|
||||
} catch (err) {
|
||||
captureEvent(
|
||||
"deepseek.tool.add_memory",
|
||||
{
|
||||
success: false,
|
||||
duration_ms: Date.now() - started,
|
||||
text_chars: text.length,
|
||||
error_kind: errorKind(err),
|
||||
},
|
||||
client,
|
||||
);
|
||||
return `add_memory failed: ${err instanceof Error ? err.message : String(err)}`;
|
||||
}
|
||||
},
|
||||
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* Anonymous usage telemetry for the DeepSeek Harness plugin.
|
||||
*
|
||||
* Fire-and-forget PostHog events over native fetch, batched and flushed every
|
||||
* 5 seconds, at 10 queued events, or on process exit. Never throws, never logs.
|
||||
*
|
||||
* Events carry only tool names, durations, counts, and coarse failure kinds:
|
||||
* never queries, memory text, filters, or API keys.
|
||||
*
|
||||
* Disable with MEM0_TELEMETRY=false.
|
||||
*/
|
||||
import { randomUUID } from "node:crypto";
|
||||
import * as fs from "node:fs";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
|
||||
const POSTHOG_API_KEY = "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX";
|
||||
const POSTHOG_BATCH_URL = "https://us.i.posthog.com/batch/";
|
||||
const FLUSH_INTERVAL_MS = 5_000;
|
||||
const FLUSH_THRESHOLD = 10;
|
||||
const SEND_TIMEOUT_MS = 3_000;
|
||||
|
||||
const PLUGIN_VERSION = ((): string => {
|
||||
try {
|
||||
return JSON.parse(
|
||||
fs.readFileSync(new URL("../package.json", import.meta.url), "utf-8"),
|
||||
).version;
|
||||
} catch {
|
||||
return "unknown";
|
||||
}
|
||||
})();
|
||||
|
||||
/** The Mem0 SDK resolves this to the account email once ping() lands, so events join other Mem0 surfaces. */
|
||||
export interface TelemetryIdentity {
|
||||
telemetryId?: string;
|
||||
}
|
||||
|
||||
let queue: Record<string, unknown>[] = [];
|
||||
let flushTimer: ReturnType<typeof setInterval> | undefined;
|
||||
let exitHandlerInstalled = false;
|
||||
let cachedAnonymousId: string | undefined;
|
||||
let identified = false;
|
||||
|
||||
function identityPath(): string {
|
||||
return path.join(os.homedir(), ".mem0", "deepseek-plugin-telemetry.json");
|
||||
}
|
||||
|
||||
export function isTelemetryEnabled(): boolean {
|
||||
const value = process.env.MEM0_TELEMETRY?.toLowerCase();
|
||||
return value !== "false" && value !== "0" && value !== "no" && value !== "off";
|
||||
}
|
||||
|
||||
function anonymousId(): string {
|
||||
if (cachedAnonymousId) return cachedAnonymousId;
|
||||
try {
|
||||
const stored = JSON.parse(fs.readFileSync(identityPath(), "utf-8"));
|
||||
if (typeof stored.anonymousId === "string" && stored.anonymousId) {
|
||||
cachedAnonymousId = stored.anonymousId;
|
||||
return cachedAnonymousId!;
|
||||
}
|
||||
} catch {
|
||||
/* first run, or an unreadable identity file */
|
||||
}
|
||||
const created = `deepseek-anon-${randomUUID().replace(/-/g, "")}`;
|
||||
try {
|
||||
const target = identityPath();
|
||||
fs.mkdirSync(path.dirname(target), { recursive: true });
|
||||
fs.writeFileSync(target, JSON.stringify({ anonymousId: created }), "utf-8");
|
||||
} catch {
|
||||
/* an unwritable home directory must not break the plugin */
|
||||
}
|
||||
cachedAnonymousId = created;
|
||||
return created;
|
||||
}
|
||||
|
||||
/** Merge the pre-ping anonymous history into the account once the email resolves. */
|
||||
function identifyEvent(distinctId: string): Record<string, unknown> | undefined {
|
||||
if (identified || distinctId.startsWith("deepseek-anon-")) return undefined;
|
||||
identified = true;
|
||||
let storedAnonymousId: string | undefined;
|
||||
try {
|
||||
storedAnonymousId = JSON.parse(fs.readFileSync(identityPath(), "utf-8")).anonymousId;
|
||||
fs.unlinkSync(identityPath());
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
cachedAnonymousId = undefined;
|
||||
if (!storedAnonymousId) return undefined;
|
||||
return {
|
||||
event: "$identify",
|
||||
distinct_id: distinctId,
|
||||
properties: { $anon_distinct_id: storedAnonymousId, $lib: "posthog-node" },
|
||||
};
|
||||
}
|
||||
|
||||
export function flushEvents(): void {
|
||||
if (queue.length === 0) return;
|
||||
const batch = queue;
|
||||
queue = [];
|
||||
fetch(POSTHOG_BATCH_URL, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ api_key: POSTHOG_API_KEY, batch }),
|
||||
signal: AbortSignal.timeout(SEND_TIMEOUT_MS),
|
||||
}).catch(() => {
|
||||
/* telemetry never surfaces its own failures */
|
||||
});
|
||||
}
|
||||
|
||||
function scheduleFlush(): void {
|
||||
if (!flushTimer) {
|
||||
flushTimer = setInterval(flushEvents, FLUSH_INTERVAL_MS);
|
||||
flushTimer.unref?.();
|
||||
}
|
||||
if (!exitHandlerInstalled) {
|
||||
exitHandlerInstalled = true;
|
||||
process.on("beforeExit", flushEvents);
|
||||
}
|
||||
}
|
||||
|
||||
export function errorKind(error: unknown): string {
|
||||
const text = (error instanceof Error ? error.message : String(error)).toLowerCase();
|
||||
if (text.includes("timeout") || text.includes("aborted")) return "timeout";
|
||||
if (text.includes("401") || text.includes("403") || text.includes("unauthor")) return "auth";
|
||||
if (text.includes("429") || text.includes("rate limit")) return "rate-limited";
|
||||
if (/50[0234]/.test(text)) return "server-error";
|
||||
if (text.includes("400") || text.includes("422")) return "bad-request";
|
||||
if (text.includes("fetch failed") || text.includes("enotfound")) return "network";
|
||||
return error instanceof Error ? error.constructor.name : "other";
|
||||
}
|
||||
|
||||
export function captureEvent(
|
||||
event: string,
|
||||
properties: Record<string, unknown>,
|
||||
client: TelemetryIdentity,
|
||||
): void {
|
||||
if (!isTelemetryEnabled()) return;
|
||||
try {
|
||||
const distinctId = client.telemetryId || anonymousId();
|
||||
const identify = identifyEvent(distinctId);
|
||||
if (identify) queue.push(identify);
|
||||
queue.push({
|
||||
event,
|
||||
distinct_id: distinctId,
|
||||
properties: {
|
||||
source: "DEEPSEEK_HARNESS",
|
||||
language: "node",
|
||||
plugin_version: PLUGIN_VERSION,
|
||||
node_version: process.version,
|
||||
os: process.platform,
|
||||
$process_person_profile: false,
|
||||
$lib: "posthog-node",
|
||||
...properties,
|
||||
},
|
||||
});
|
||||
scheduleFlush();
|
||||
if (queue.length >= FLUSH_THRESHOLD) flushEvents();
|
||||
} catch {
|
||||
/* telemetry never breaks a tool call */
|
||||
}
|
||||
}
|
||||
|
||||
export function _queueForTesting(): Record<string, unknown>[] {
|
||||
return queue;
|
||||
}
|
||||
|
||||
export function _resetForTesting(): void {
|
||||
queue = [];
|
||||
clearInterval(flushTimer);
|
||||
flushTimer = undefined;
|
||||
cachedAnonymousId = undefined;
|
||||
identified = false;
|
||||
}
|
||||
+5
@@ -35,9 +35,12 @@ function applyAndCollect(config: Config): Map<string, RegisteredTool> {
|
||||
}
|
||||
|
||||
let savedKey: string | undefined;
|
||||
let savedTelemetry: string | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
savedKey = process.env.MEM0_API_KEY;
|
||||
savedTelemetry = process.env.MEM0_TELEMETRY;
|
||||
process.env.MEM0_TELEMETRY = "false";
|
||||
mockSearch.mockReset();
|
||||
mockAdd.mockReset();
|
||||
});
|
||||
@@ -45,6 +48,8 @@ beforeEach(() => {
|
||||
afterEach(() => {
|
||||
if (savedKey === undefined) delete process.env.MEM0_API_KEY;
|
||||
else process.env.MEM0_API_KEY = savedKey;
|
||||
if (savedTelemetry === undefined) delete process.env.MEM0_TELEMETRY;
|
||||
else process.env.MEM0_TELEMETRY = savedTelemetry;
|
||||
});
|
||||
|
||||
describe("apply() config validation", () => {
|
||||
@@ -0,0 +1,218 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import * as fs from "node:fs";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
|
||||
const mockSearch = vi.fn();
|
||||
const mockAdd = vi.fn();
|
||||
vi.mock("mem0ai", () => ({
|
||||
MemoryClient: class {
|
||||
telemetryId = "dev@example.com";
|
||||
search = mockSearch;
|
||||
add = mockAdd;
|
||||
},
|
||||
}));
|
||||
vi.mock("@deepseek-ai/dsh-tools", () => ({ defineTool: (options: unknown) => options }));
|
||||
|
||||
import { apply, type Config } from "../src/index.ts";
|
||||
import {
|
||||
captureEvent,
|
||||
errorKind,
|
||||
flushEvents,
|
||||
isTelemetryEnabled,
|
||||
_queueForTesting,
|
||||
_resetForTesting,
|
||||
} from "../src/telemetry.ts";
|
||||
|
||||
interface RegisteredTool {
|
||||
name: string;
|
||||
execute(args: unknown, exec: unknown): Promise<unknown>;
|
||||
}
|
||||
|
||||
function applyAndCollect(config: Config): Map<string, RegisteredTool> {
|
||||
const tools = new Map<string, RegisteredTool>();
|
||||
apply({ tools: { register: (t: RegisteredTool) => tools.set(t.name, t) } } as never, config);
|
||||
return tools;
|
||||
}
|
||||
|
||||
function queued(): Record<string, any>[] {
|
||||
return _queueForTesting() as Record<string, any>[];
|
||||
}
|
||||
|
||||
let home: string;
|
||||
let savedHome: string | undefined;
|
||||
let savedTelemetry: string | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
savedHome = process.env.HOME;
|
||||
savedTelemetry = process.env.MEM0_TELEMETRY;
|
||||
delete process.env.MEM0_TELEMETRY;
|
||||
home = fs.mkdtempSync(path.join(os.tmpdir(), "mem0-deepseek-"));
|
||||
process.env.HOME = home;
|
||||
vi.stubGlobal("fetch", vi.fn().mockResolvedValue({ ok: true }));
|
||||
mockSearch.mockReset();
|
||||
mockAdd.mockReset();
|
||||
_resetForTesting();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
_resetForTesting();
|
||||
vi.unstubAllGlobals();
|
||||
fs.rmSync(home, { recursive: true, force: true });
|
||||
if (savedHome === undefined) delete process.env.HOME;
|
||||
else process.env.HOME = savedHome;
|
||||
if (savedTelemetry === undefined) delete process.env.MEM0_TELEMETRY;
|
||||
else process.env.MEM0_TELEMETRY = savedTelemetry;
|
||||
});
|
||||
|
||||
describe("opt-out", () => {
|
||||
it("queues nothing for every documented off value", () => {
|
||||
for (const value of ["false", "0", "no", "OFF"]) {
|
||||
process.env.MEM0_TELEMETRY = value;
|
||||
expect(isTelemetryEnabled()).toBe(false);
|
||||
captureEvent("deepseek.tool.search_memory", {}, { telemetryId: "dev@example.com" });
|
||||
}
|
||||
expect(queued()).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("is on by default", () => {
|
||||
expect(isTelemetryEnabled()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("identity", () => {
|
||||
it("keys events on the account email the SDK resolved", () => {
|
||||
captureEvent("deepseek.tool.add_memory", {}, { telemetryId: "dev@example.com" });
|
||||
expect(queued()[0].distinct_id).toBe("dev@example.com");
|
||||
});
|
||||
|
||||
it("falls back to a persisted anonymous id before the SDK has pinged", () => {
|
||||
captureEvent("deepseek.tool.add_memory", {}, {});
|
||||
const first = queued()[0].distinct_id as string;
|
||||
expect(first).toMatch(/^deepseek-anon-/);
|
||||
|
||||
_resetForTesting();
|
||||
captureEvent("deepseek.tool.add_memory", {}, {});
|
||||
expect(queued()[0].distinct_id).toBe(first);
|
||||
});
|
||||
|
||||
it("aliases the anonymous history onto the email exactly once", () => {
|
||||
captureEvent("deepseek.tool.add_memory", {}, {});
|
||||
const anonymous = queued()[0].distinct_id;
|
||||
_resetForTesting();
|
||||
|
||||
captureEvent("deepseek.tool.add_memory", {}, { telemetryId: "dev@example.com" });
|
||||
const [identify, event] = queued();
|
||||
expect(identify.event).toBe("$identify");
|
||||
expect(identify.distinct_id).toBe("dev@example.com");
|
||||
expect(identify.properties.$anon_distinct_id).toBe(anonymous);
|
||||
expect(event.distinct_id).toBe("dev@example.com");
|
||||
|
||||
captureEvent("deepseek.tool.add_memory", {}, { telemetryId: "dev@example.com" });
|
||||
expect(queued().filter((e) => e.event === "$identify")).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("event shape", () => {
|
||||
it("stamps every event with the integration source and runtime baseline", () => {
|
||||
captureEvent("deepseek.tool.search_memory", { success: true }, { telemetryId: "d@e.com" });
|
||||
const { properties } = queued()[0];
|
||||
expect(properties.source).toBe("DEEPSEEK_HARNESS");
|
||||
expect(properties.language).toBe("node");
|
||||
expect(properties.$process_person_profile).toBe(false);
|
||||
expect(properties.os).toBe(process.platform);
|
||||
expect(properties.plugin_version).not.toBe("unknown");
|
||||
expect(properties.success).toBe(true);
|
||||
});
|
||||
|
||||
it("flushes one batch to PostHog and empties the queue", async () => {
|
||||
captureEvent("deepseek.tool.search_memory", {}, { telemetryId: "d@e.com" });
|
||||
flushEvents();
|
||||
|
||||
const [url, init] = (fetch as unknown as ReturnType<typeof vi.fn>).mock.calls[0];
|
||||
expect(url).toContain("posthog.com");
|
||||
expect(JSON.parse(init.body).batch).toHaveLength(1);
|
||||
expect(queued()).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("never throws when the network is gone", () => {
|
||||
vi.stubGlobal("fetch", vi.fn().mockRejectedValue(new Error("fetch failed")));
|
||||
captureEvent("deepseek.tool.search_memory", {}, { telemetryId: "d@e.com" });
|
||||
expect(() => flushEvents()).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe("errorKind", () => {
|
||||
it("buckets failures without leaking the message", () => {
|
||||
expect(errorKind(new Error("Request failed with status 429"))).toBe("rate-limited");
|
||||
expect(errorKind(new Error("401 Unauthorized"))).toBe("auth");
|
||||
expect(errorKind(new Error("503 Service Unavailable"))).toBe("server-error");
|
||||
expect(errorKind(new Error("The operation was aborted due to timeout"))).toBe("timeout");
|
||||
expect(errorKind(new Error("fetch failed"))).toBe("network");
|
||||
expect(errorKind(new Error("token sk-abcdef is invalid"))).toBe("Error");
|
||||
});
|
||||
});
|
||||
|
||||
describe("tool instrumentation", () => {
|
||||
it("records a mount, then a successful search with its shape but not its query", async () => {
|
||||
mockSearch.mockResolvedValue({ results: [{ id: "m1", memory: "Likes tea" }] });
|
||||
const tools = applyAndCollect({ apiKey: "k", userId: "u" });
|
||||
|
||||
await tools.get("search_memory")!.execute({ query: "what drink", limit: 3 }, {});
|
||||
|
||||
const [mounted, search] = queued();
|
||||
expect(mounted.event).toBe("deepseek.plugin.mounted");
|
||||
expect(mounted.properties.has_host).toBe(false);
|
||||
expect(search.event).toBe("deepseek.tool.search_memory");
|
||||
expect(search.properties).toMatchObject({
|
||||
success: true,
|
||||
top_k: 3,
|
||||
result_count: 1,
|
||||
query_chars: 10,
|
||||
scope_overridden: false,
|
||||
has_agent_id: false,
|
||||
has_run_id: false,
|
||||
});
|
||||
expect(typeof search.properties.duration_ms).toBe("number");
|
||||
expect(JSON.stringify(search)).not.toContain("what drink");
|
||||
expect(JSON.stringify(search)).not.toContain("Likes tea");
|
||||
});
|
||||
|
||||
it("records a failed search with a coarse error kind", async () => {
|
||||
mockSearch.mockRejectedValue(new Error("429 rate limit exceeded"));
|
||||
const tools = applyAndCollect({ apiKey: "k", userId: "u" });
|
||||
|
||||
await tools.get("search_memory")!.execute({ query: "x" }, {});
|
||||
|
||||
const search = queued().at(-1)!;
|
||||
expect(search.properties).toMatchObject({ success: false, error_kind: "rate-limited" });
|
||||
});
|
||||
|
||||
it("records a write with its size but not its text", async () => {
|
||||
mockAdd.mockResolvedValue([{ id: "m1", memory: "Fact" }]);
|
||||
const tools = applyAndCollect({ apiKey: "k", userId: "u" });
|
||||
|
||||
await tools.get("add_memory")!.execute({ text: "secret fact", userId: "alice" }, {});
|
||||
|
||||
const add = queued().at(-1)!;
|
||||
expect(add.event).toBe("deepseek.tool.add_memory");
|
||||
expect(add.properties).toMatchObject({
|
||||
success: true,
|
||||
text_chars: 11,
|
||||
memory_count: 1,
|
||||
scope_overridden: true,
|
||||
});
|
||||
expect(JSON.stringify(add)).not.toContain("secret fact");
|
||||
expect(JSON.stringify(add)).not.toContain("alice");
|
||||
});
|
||||
|
||||
it("records a failed write and still returns the graceful failure line", async () => {
|
||||
mockAdd.mockRejectedValue(new Error("boom"));
|
||||
const tools = applyAndCollect({ apiKey: "k", userId: "u" });
|
||||
|
||||
const out = await tools.get("add_memory")!.execute({ text: "x" }, {});
|
||||
|
||||
expect(out).toContain("add_memory failed");
|
||||
expect(queued().at(-1)!.properties).toMatchObject({ success: false, error_kind: "Error" });
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
strands-mem0
|
||||
mem0-strands
|
||||
Copyright 2026 Mem0
|
||||
|
||||
This product includes software developed at Mem0 (https://mem0.ai).
|
||||
@@ -1,5 +1,5 @@
|
||||
<div align="center">
|
||||
<h1>strands-mem0</h1>
|
||||
<h1>mem0-strands</h1>
|
||||
<h3>Persistent long-term memory for Strands Agents, backed by Mem0</h3>
|
||||
<p>
|
||||
A community <a href="https://strandsagents.com/">Strands Agents</a> integration that plugs
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
---
|
||||
|
||||
`strands-mem0` gives [Strands](https://github.com/strands-agents/sdk-python) agents durable memory
|
||||
`mem0-strands` gives [Strands](https://github.com/strands-agents/sdk-python) agents durable memory
|
||||
that survives across sessions, backed by [Mem0](https://mem0.ai). Where the
|
||||
`mem0_memory` tool is called explicitly by the model, `Mem0MemoryStore` plugs into the **agent loop**
|
||||
directly: the manager recalls context and injects it automatically, and writes new memories, either
|
||||
@@ -22,7 +22,7 @@ verbatim or by extracting facts from the conversation.
|
||||
## Install
|
||||
|
||||
```bash
|
||||
pip install strands-mem0
|
||||
pip install mem0-strands
|
||||
```
|
||||
|
||||
## Usage
|
||||
@@ -30,7 +30,7 @@ pip install strands-mem0
|
||||
```python
|
||||
from strands import Agent
|
||||
from strands.memory import MemoryManager
|
||||
from strands_mem0 import Mem0MemoryStore
|
||||
from mem0_strands import Mem0MemoryStore
|
||||
|
||||
# Recall + write, distilling facts from the conversation via Mem0's server-side extraction.
|
||||
store = Mem0MemoryStore(user_id="alex", writable=True, extraction=True)
|
||||
@@ -77,6 +77,13 @@ For the model-called tool (`store` / `retrieve` / `get` / `delete`), use the
|
||||
[`mem0_memory`](https://github.com/strands-agents/tools) tool from `strands-agents-tools`. The store and
|
||||
the tool share one Mem0 backend and namespace.
|
||||
|
||||
## Telemetry
|
||||
|
||||
The store sends anonymous usage events (store configuration, operation, duration,
|
||||
result counts, coarse failure kind) over the Mem0 SDK's existing telemetry client,
|
||||
tagged `source="STRANDS"`. Queries, memory text, message content, entity ids, and
|
||||
metadata are never sent. Turn it off with `MEM0_TELEMETRY=false`.
|
||||
|
||||
## Development
|
||||
|
||||
The package lives under [`python/`](python/) (monorepo-style layout matching the
|
||||
+5
-5
@@ -1,4 +1,4 @@
|
||||
# strands-mem0 (Python)
|
||||
# mem0-strands (Python)
|
||||
|
||||
Persistent long-term memory for [Strands Agents](https://github.com/strands-agents/sdk-python),
|
||||
backed by [Mem0](https://mem0.ai).
|
||||
@@ -6,7 +6,7 @@ backed by [Mem0](https://mem0.ai).
|
||||
See the [repository README](../README.md) for full usage. Quick start:
|
||||
|
||||
```bash
|
||||
pip install strands-mem0
|
||||
pip install mem0-strands
|
||||
```
|
||||
|
||||
As a `MemoryStore` that plugs into the agent loop (Strands >= 1.45):
|
||||
@@ -14,7 +14,7 @@ As a `MemoryStore` that plugs into the agent loop (Strands >= 1.45):
|
||||
```python
|
||||
from strands import Agent
|
||||
from strands.memory import MemoryManager
|
||||
from strands_mem0 import Mem0MemoryStore
|
||||
from mem0_strands import Mem0MemoryStore
|
||||
|
||||
store = Mem0MemoryStore(user_id="alex", writable=True, extraction=True)
|
||||
agent = Agent(memory_manager=MemoryManager(stores=[store]))
|
||||
@@ -32,6 +32,6 @@ hatch run prepare # format + lint + typecheck + test
|
||||
|
||||
## Release
|
||||
|
||||
Publish a GitHub release tagged `strands-mem0-v*` (e.g. `strands-mem0-v0.1.0`). The
|
||||
release router (`.github/workflows/release.yml`) dispatches `strands-mem0-cd.yml`,
|
||||
Publish a GitHub release tagged `mem0-strands-v*` (e.g. `mem0-strands-v0.1.0`). The
|
||||
release router (`.github/workflows/release.yml`) dispatches `mem0-strands-cd.yml`,
|
||||
which builds the wheel and publishes it to PyPI via trusted publishing (OIDC).
|
||||
+4
-4
@@ -3,8 +3,8 @@ requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "strands-mem0"
|
||||
version = "0.1.0"
|
||||
name = "mem0-strands"
|
||||
version = "0.1.1"
|
||||
description = "Persistent long-term memory for Strands agents, backed by Mem0."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
@@ -33,7 +33,7 @@ dependencies = [
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://mem0.ai"
|
||||
Documentation = "https://github.com/mem0ai/mem0/tree/main/integrations/strands-mem0#readme"
|
||||
Documentation = "https://github.com/mem0ai/mem0/tree/main/integrations/mem0-strands#readme"
|
||||
Repository = "https://github.com/mem0ai/mem0"
|
||||
Issues = "https://github.com/mem0ai/mem0/issues"
|
||||
|
||||
@@ -47,7 +47,7 @@ dev = [
|
||||
]
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["src/strands_mem0"]
|
||||
packages = ["src/mem0_strands"]
|
||||
|
||||
[tool.hatch.envs.default]
|
||||
dependencies = [
|
||||
+3
-3
@@ -12,8 +12,8 @@ share one Mem0 backend and namespace.
|
||||
|
||||
from importlib.metadata import PackageNotFoundError, version
|
||||
|
||||
from strands_mem0.client import Mem0ServiceClient
|
||||
from strands_mem0.store import Mem0MemoryStore
|
||||
from mem0_strands.client import Mem0ServiceClient
|
||||
from mem0_strands.store import Mem0MemoryStore
|
||||
|
||||
__all__ = [
|
||||
"Mem0MemoryStore",
|
||||
@@ -21,6 +21,6 @@ __all__ = [
|
||||
]
|
||||
|
||||
try:
|
||||
__version__ = version("strands-mem0")
|
||||
__version__ = version("mem0-strands")
|
||||
except PackageNotFoundError: # pragma: no cover - only when running from a source tree
|
||||
__version__ = "0.0.0+unknown"
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
"""A thin wrapper around the Mem0 SDK used by :class:`~strands_mem0.store.Mem0MemoryStore`.
|
||||
"""A thin wrapper around the Mem0 SDK used by :class:`~mem0_strands.store.Mem0MemoryStore`.
|
||||
|
||||
Both Mem0 backends -- the hosted platform (:class:`mem0.MemoryClient`) and
|
||||
self-hosted OSS (:class:`mem0.Memory`) -- expose the same call shape to the store:
|
||||
@@ -95,7 +95,7 @@ class Mem0ServiceClient:
|
||||
from mem0 import Memory
|
||||
except ImportError as err: # pragma: no cover - exercised via install docs
|
||||
raise ImportError(
|
||||
"The mem0ai package is required. Install it with: pip install 'strands-mem0'"
|
||||
"The mem0ai package is required. Install it with: pip install 'mem0-strands'"
|
||||
) from err
|
||||
self.mem0 = Memory.from_config(config)
|
||||
self.is_platform = False
|
||||
@@ -104,7 +104,7 @@ class Mem0ServiceClient:
|
||||
try:
|
||||
from mem0 import MemoryClient
|
||||
except ImportError as err: # pragma: no cover - exercised via install docs
|
||||
raise ImportError("The mem0ai package is required. Install it with: pip install 'strands-mem0'") from err
|
||||
raise ImportError("The mem0ai package is required. Install it with: pip install 'mem0-strands'") from err
|
||||
api_key = api_key or os.environ.get("MEM0_API_KEY")
|
||||
# MemoryClient(host=None) would override the SDK default with None, so only
|
||||
# pass host when the caller actually set one.
|
||||
+62
-6
@@ -21,7 +21,7 @@ Example:
|
||||
```python
|
||||
from strands import Agent
|
||||
from strands.memory import MemoryManager
|
||||
from strands_mem0 import Mem0MemoryStore
|
||||
from mem0_strands import Mem0MemoryStore
|
||||
|
||||
# Recall + write, with Mem0 extracting facts from the conversation server-side.
|
||||
store = Mem0MemoryStore(user_id="alex", writable=True, extraction=True)
|
||||
@@ -36,12 +36,14 @@ environment variable, or pass a Mem0 OSS ``config`` dict for a self-hosted backe
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
from strands.memory import AddMessagesContext, MemoryEntry, MemoryStore, SearchOptions
|
||||
from strands.types.content import Message
|
||||
|
||||
from strands_mem0.client import Mem0ServiceClient
|
||||
from mem0_strands import telemetry
|
||||
from mem0_strands.client import Mem0ServiceClient
|
||||
|
||||
DEFAULT_MAX_SEARCH_RESULTS = 5
|
||||
# Entity fields that scope a memory in Mem0. At least one must be set.
|
||||
@@ -92,7 +94,7 @@ class Mem0MemoryStore(MemoryStore):
|
||||
api_key: Mem0 platform API key (defaults to ``$MEM0_API_KEY``).
|
||||
host: Mem0 platform base URL.
|
||||
config: Mem0 OSS config dict for a self-hosted backend.
|
||||
client: A pre-built :class:`~strands_mem0.client.Mem0ServiceClient`
|
||||
client: A pre-built :class:`~mem0_strands.client.Mem0ServiceClient`
|
||||
(for testing, or to wrap your own raw Mem0 client via
|
||||
``Mem0ServiceClient(client=...)``); when omitted, one is
|
||||
constructed lazily on first use from ``api_key`` / ``config``.
|
||||
@@ -134,6 +136,7 @@ class Mem0MemoryStore(MemoryStore):
|
||||
self._host = host
|
||||
self._config = config
|
||||
self._client = client
|
||||
self._recorded_init = False
|
||||
|
||||
@property
|
||||
def client(self) -> Mem0ServiceClient:
|
||||
@@ -144,8 +147,22 @@ class Mem0MemoryStore(MemoryStore):
|
||||
stores), so first use is deferred and always happens inside a worker thread
|
||||
via :func:`asyncio.to_thread`, never on the event loop.
|
||||
"""
|
||||
client_injected = self._client is not None
|
||||
if self._client is None:
|
||||
self._client = Mem0ServiceClient(api_key=self._api_key, host=self._host, config=self._config)
|
||||
if not self._recorded_init:
|
||||
self._recorded_init = True
|
||||
telemetry.record(
|
||||
"store.init",
|
||||
self._client,
|
||||
scopes=sorted(self.scope),
|
||||
writable=self.writable,
|
||||
extraction_enabled=bool(self.extraction),
|
||||
has_default_metadata=bool(self.metadata),
|
||||
max_search_results=self.max_search_results,
|
||||
host_overridden=bool(self._host),
|
||||
client_injected=client_injected,
|
||||
)
|
||||
return self._client
|
||||
|
||||
async def search(self, query: str, options: SearchOptions | None = None) -> list[MemoryEntry]:
|
||||
@@ -158,7 +175,13 @@ class Mem0MemoryStore(MemoryStore):
|
||||
|
||||
# ``self.client`` is resolved inside the thread so lazy construction (a
|
||||
# blocking call) does not run on the event loop.
|
||||
memories = await asyncio.to_thread(lambda: self.client.search_memories(query, self.scope, top_k))
|
||||
started = time.perf_counter()
|
||||
try:
|
||||
memories = await asyncio.to_thread(lambda: self.client.search_memories(query, self.scope, top_k))
|
||||
except Exception as exc:
|
||||
self._record("store.search", started, False, top_k=top_k, error_kind=telemetry.error_kind(exc))
|
||||
raise
|
||||
self._record("store.search", started, True, top_k=top_k, result_count=len(memories))
|
||||
return [self._to_entry(memory) for memory in memories]
|
||||
|
||||
async def add(self, content: str, metadata: dict[str, Any] | None = None) -> Any:
|
||||
@@ -168,7 +191,14 @@ class Mem0MemoryStore(MemoryStore):
|
||||
Mem0 de-duplicates on the server.
|
||||
"""
|
||||
merged = self._merge_metadata(metadata)
|
||||
return await asyncio.to_thread(lambda: self.client.store_memory(content, self.scope, merged))
|
||||
started = time.perf_counter()
|
||||
try:
|
||||
result = await asyncio.to_thread(lambda: self.client.store_memory(content, self.scope, merged))
|
||||
except Exception as exc:
|
||||
self._record("store.add", started, False, error_kind=telemetry.error_kind(exc))
|
||||
raise
|
||||
self._record("store.add", started, True, content_chars=len(content), has_metadata=bool(merged))
|
||||
return result
|
||||
|
||||
async def add_messages(self, messages: list[Message], context: AddMessagesContext | None = None) -> Any:
|
||||
"""Ingest raw conversation turns for Mem0 server-side extraction (``infer=True``).
|
||||
@@ -186,7 +216,33 @@ class Mem0MemoryStore(MemoryStore):
|
||||
payload.append({"role": message["role"], "content": text})
|
||||
if not payload:
|
||||
return None
|
||||
return await asyncio.to_thread(lambda: self.client.store_messages(payload, self.scope))
|
||||
started = time.perf_counter()
|
||||
try:
|
||||
result = await asyncio.to_thread(lambda: self.client.store_messages(payload, self.scope))
|
||||
except Exception as exc:
|
||||
self._record("store.add_messages", started, False, error_kind=telemetry.error_kind(exc))
|
||||
raise
|
||||
self._record(
|
||||
"store.add_messages",
|
||||
started,
|
||||
True,
|
||||
message_count=len(messages),
|
||||
rendered_count=len(payload),
|
||||
total_chars=sum(len(turn["content"]) for turn in payload),
|
||||
)
|
||||
return result
|
||||
|
||||
def _record(self, event: str, started: float, success: bool, **properties: Any) -> None:
|
||||
"""Send one store telemetry event, timed from ``started``."""
|
||||
if self._client is None:
|
||||
return
|
||||
telemetry.record(
|
||||
event,
|
||||
self._client,
|
||||
success=success,
|
||||
duration_ms=round((time.perf_counter() - started) * 1000, 2),
|
||||
**properties,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _render_content(content: Any) -> str:
|
||||
@@ -0,0 +1,70 @@
|
||||
"""Anonymous usage telemetry for the Strands memory store.
|
||||
|
||||
Events ride the Mem0 SDK's own PostHog client, so they need no extra dependency
|
||||
and join the account's other Mem0 usage: on the hosted platform the distinct id
|
||||
is the account email, and on a self-hosted OSS backend it is the machine-local
|
||||
anonymous id the SDK already keeps. Unlike the SDK's OSS hot-path events these
|
||||
are never sampled, because a store call is an agent-level action, not a loop.
|
||||
|
||||
Only names, counts, durations, and coarse failure kinds are sent: never queries,
|
||||
memory text, message content, entity ids, metadata, or API keys.
|
||||
|
||||
Opt out with MEM0_TELEMETRY=false.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from importlib.metadata import PackageNotFoundError, version
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from mem0.memory.telemetry import client_telemetry
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from mem0_strands.client import Mem0ServiceClient
|
||||
|
||||
SOURCE = "STRANDS"
|
||||
|
||||
try:
|
||||
_VERSION = version("mem0-strands")
|
||||
except PackageNotFoundError: # pragma: no cover - only when running from a source tree
|
||||
_VERSION = "0.0.0+unknown"
|
||||
|
||||
|
||||
def error_kind(exc: BaseException) -> str:
|
||||
"""Coarse, content-free label for a failure, safe to send."""
|
||||
text = f"{type(exc).__name__}: {exc}".lower()
|
||||
if "timeout" in text or "timed out" in text:
|
||||
return "timeout"
|
||||
if "401" in text or "403" in text or "unauthor" in text or "forbidden" in text:
|
||||
return "auth"
|
||||
if "429" in text or "rate limit" in text:
|
||||
return "rate-limited"
|
||||
if any(code in text for code in ("500", "502", "503", "504")):
|
||||
return "server-error"
|
||||
if "400" in text or "422" in text:
|
||||
return "bad-request"
|
||||
return type(exc).__name__
|
||||
|
||||
|
||||
def _distinct_id(client: Mem0ServiceClient) -> str | None:
|
||||
"""The account email to attribute events to, or None to fall back to the SDK's anonymous id."""
|
||||
email = getattr(client.mem0, "user_email", None)
|
||||
return email if isinstance(email, str) and email else None
|
||||
|
||||
|
||||
def record(event: str, client: Mem0ServiceClient, **properties: Any) -> None:
|
||||
"""Send one strands.* usage event. Never raises."""
|
||||
try:
|
||||
client_telemetry.capture_event(
|
||||
f"strands.{event}",
|
||||
{
|
||||
"source": SOURCE,
|
||||
"language": "python",
|
||||
"strands_store_version": _VERSION,
|
||||
"backend": "platform" if client.is_platform else "oss",
|
||||
**properties,
|
||||
},
|
||||
_distinct_id(client),
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
@@ -0,0 +1,10 @@
|
||||
"""Keep the test suite from sending usage telemetry to the live PostHog project.
|
||||
|
||||
Set before ``mem0`` is imported: the SDK reads ``MEM0_TELEMETRY`` once, at import.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
os.environ["MEM0_TELEMETRY"] = "false"
|
||||
+1
-1
@@ -8,7 +8,7 @@ what the earlier permissive fakes did not do.
|
||||
|
||||
import pytest
|
||||
|
||||
from strands_mem0.client import Mem0ServiceClient, _extract_results, _is_platform_client
|
||||
from mem0_strands.client import Mem0ServiceClient, _extract_results, _is_platform_client
|
||||
|
||||
|
||||
class FakeMemoryClient:
|
||||
+2
-2
@@ -10,7 +10,7 @@ import pytest
|
||||
from strands.memory import MemoryEntry, MemoryStore
|
||||
from strands.memory.types import _has_method, _has_write_sink
|
||||
|
||||
from strands_mem0 import Mem0MemoryStore
|
||||
from mem0_strands import Mem0MemoryStore
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -241,7 +241,7 @@ def test_client_constructed_lazily(monkeypatch):
|
||||
calls["n"] += 1
|
||||
self.api_key = api_key
|
||||
|
||||
monkeypatch.setattr("strands_mem0.store.Mem0ServiceClient", FakeClient)
|
||||
monkeypatch.setattr("mem0_strands.store.Mem0ServiceClient", FakeClient)
|
||||
|
||||
store = Mem0MemoryStore(user_id="alex", api_key="m0-x")
|
||||
assert calls["n"] == 0 # not built yet
|
||||
@@ -0,0 +1,172 @@
|
||||
"""Tests for the store's anonymous usage telemetry."""
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from mem0_strands import Mem0MemoryStore, telemetry
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_client():
|
||||
"""A mocked Mem0ServiceClient that looks like the hosted platform."""
|
||||
client = MagicMock()
|
||||
client.is_platform = True
|
||||
client.mem0.user_email = "dev@example.com"
|
||||
return client
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def captured():
|
||||
"""Intercept the SDK PostHog client and collect (event, properties, distinct_id)."""
|
||||
with patch.object(telemetry, "client_telemetry") as posthog:
|
||||
events = []
|
||||
posthog.capture_event.side_effect = lambda event, properties, distinct_id=None: events.append(
|
||||
(event, properties, distinct_id)
|
||||
)
|
||||
yield events
|
||||
|
||||
|
||||
def make_store(mock_client, **kwargs):
|
||||
kwargs.setdefault("user_id", "alex")
|
||||
return Mem0MemoryStore(client=mock_client, **kwargs)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Event shape
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_every_event_carries_source_and_backend(mock_client, captured):
|
||||
assert make_store(mock_client).client is mock_client
|
||||
|
||||
event, properties, distinct_id = captured[0]
|
||||
assert event == "strands.store.init"
|
||||
assert properties["source"] == "STRANDS"
|
||||
assert properties["language"] == "python"
|
||||
assert properties["backend"] == "platform"
|
||||
assert properties["strands_store_version"]
|
||||
assert distinct_id == "dev@example.com"
|
||||
|
||||
|
||||
def test_oss_backend_is_labelled_and_falls_back_to_the_sdk_anonymous_id(captured):
|
||||
client = MagicMock()
|
||||
client.is_platform = False
|
||||
del client.mem0.user_email
|
||||
assert make_store(client).client is client
|
||||
|
||||
_, properties, distinct_id = captured[0]
|
||||
assert properties["backend"] == "oss"
|
||||
assert distinct_id is None
|
||||
|
||||
|
||||
def test_init_is_recorded_once_per_store(mock_client, captured):
|
||||
store = make_store(mock_client)
|
||||
assert store.client is store.client
|
||||
|
||||
assert [event for event, _, _ in captured] == ["strands.store.init"]
|
||||
|
||||
|
||||
def test_init_describes_configuration_without_scope_values(mock_client, captured):
|
||||
store = make_store(mock_client, agent_id="assistant", writable=False, extraction=True, metadata={"team": "core"})
|
||||
assert store.client is mock_client
|
||||
|
||||
_, properties, _ = captured[0]
|
||||
assert properties["scopes"] == ["agent_id", "user_id"]
|
||||
assert properties["writable"] is False
|
||||
assert properties["extraction_enabled"] is True
|
||||
assert properties["has_default_metadata"] is True
|
||||
assert "alex" not in str(properties)
|
||||
assert "assistant" not in str(properties)
|
||||
assert "core" not in str(properties)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Per-operation events
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
async def test_search_records_counts_but_never_the_query(mock_client, captured):
|
||||
mock_client.search_memories.return_value = [{"id": "m1", "memory": "Likes tea"}]
|
||||
|
||||
await make_store(mock_client).search("what does alex drink")
|
||||
|
||||
event, properties, _ = captured[-1]
|
||||
assert event == "strands.store.search"
|
||||
assert properties["success"] is True
|
||||
assert properties["result_count"] == 1
|
||||
assert properties["top_k"] == 5
|
||||
assert properties["duration_ms"] >= 0
|
||||
assert "drink" not in str(properties)
|
||||
assert "tea" not in str(properties)
|
||||
|
||||
|
||||
async def test_add_records_size_but_never_the_content(mock_client, captured):
|
||||
await make_store(mock_client).add("Alex prefers dark roast", metadata={"team": "core"})
|
||||
|
||||
event, properties, _ = captured[-1]
|
||||
assert event == "strands.store.add"
|
||||
assert properties["success"] is True
|
||||
assert properties["content_chars"] == len("Alex prefers dark roast")
|
||||
assert properties["has_metadata"] is True
|
||||
assert "roast" not in str(properties)
|
||||
|
||||
|
||||
async def test_add_messages_records_turn_counts_but_never_the_turns(mock_client, captured):
|
||||
messages = [
|
||||
{"role": "user", "content": [{"text": "hello"}]},
|
||||
{"role": "assistant", "content": [{"text": "hi there"}]},
|
||||
]
|
||||
|
||||
await make_store(mock_client).add_messages(messages)
|
||||
|
||||
event, properties, _ = captured[-1]
|
||||
assert event == "strands.store.add_messages"
|
||||
assert properties["message_count"] == 2
|
||||
assert properties["rendered_count"] == 2
|
||||
assert properties["total_chars"] == len("hello") + len("hi there")
|
||||
assert "hello" not in str(properties)
|
||||
|
||||
|
||||
async def test_empty_add_messages_records_nothing(mock_client, captured):
|
||||
assert await make_store(mock_client).add_messages([]) is None
|
||||
assert [event for event, _, _ in captured] == []
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Failures
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
async def test_a_failed_search_records_a_coarse_error_kind_and_still_raises(mock_client, captured):
|
||||
mock_client.search_memories.side_effect = RuntimeError("HTTP 429 rate limit exceeded for key sk-secret")
|
||||
|
||||
with pytest.raises(RuntimeError):
|
||||
await make_store(mock_client).search("anything")
|
||||
|
||||
event, properties, _ = captured[-1]
|
||||
assert event == "strands.store.search"
|
||||
assert properties["success"] is False
|
||||
assert properties["error_kind"] == "rate-limited"
|
||||
assert "sk-secret" not in str(properties)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("message", "expected"),
|
||||
[
|
||||
("request timed out", "timeout"),
|
||||
("401 Unauthorized", "auth"),
|
||||
("429 Too Many Requests", "rate-limited"),
|
||||
("503 Service Unavailable", "server-error"),
|
||||
("422 Unprocessable Entity", "bad-request"),
|
||||
("something else entirely", "ValueError"),
|
||||
],
|
||||
)
|
||||
def test_error_kind_buckets_failures(message, expected):
|
||||
assert telemetry.error_kind(ValueError(message)) == expected
|
||||
|
||||
|
||||
def test_a_broken_telemetry_backend_never_breaks_the_store(mock_client):
|
||||
with patch.object(telemetry, "client_telemetry") as posthog:
|
||||
posthog.capture_event.side_effect = RuntimeError("posthog is down")
|
||||
assert make_store(mock_client).client is mock_client
|
||||
+207
-36
@@ -1,64 +1,235 @@
|
||||
# Mem0 - The Memory Layer for Your AI Apps
|
||||
# Mem0 TypeScript SDK
|
||||
|
||||
Mem0 is a self-improving memory layer for LLM applications, enabling personalized AI experiences that save costs and delight users. We offer both cloud and open-source solutions to cater to different needs.
|
||||
<p align="center">
|
||||
<a href="https://github.com/mem0ai/mem0">
|
||||
<img src="../docs/images/banner-sm.png" width="800px" alt="Mem0, the memory layer for personalized AI">
|
||||
</a>
|
||||
</p>
|
||||
<p align="center" style="display: flex; justify-content: center; gap: 20px; align-items: center;">
|
||||
<a href="https://trendshift.io/repositories/11194" target="blank">
|
||||
<img src="https://trendshift.io/api/badge/repositories/11194" alt="mem0ai%2Fmem0 | Trendshift" width="250" height="55"/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
See the complete [OSS Docs](https://docs.mem0.ai/open-source/node-quickstart).
|
||||
See the complete [Platform API Reference](https://docs.mem0.ai/api-reference).
|
||||
<p align="center">
|
||||
<a href="https://mem0.ai">Learn more</a>
|
||||
·
|
||||
<a href="https://mem0.dev/DiG">Join Discord</a>
|
||||
·
|
||||
<a href="https://mem0.dev/demo">Demo</a>
|
||||
</p>
|
||||
|
||||
## 1. Installation
|
||||
<p align="center">
|
||||
<a href="https://mem0.dev/DiG">
|
||||
<img src="https://img.shields.io/badge/Discord-%235865F2.svg?&logo=discord&logoColor=white" alt="Mem0 Discord">
|
||||
</a>
|
||||
<a href="https://www.npmjs.com/package/mem0ai">
|
||||
<img src="https://img.shields.io/npm/dm/mem0ai" alt="Mem0 npm downloads">
|
||||
</a>
|
||||
<a href="https://github.com/mem0ai/mem0">
|
||||
<img src="https://img.shields.io/github/commit-activity/m/mem0ai/mem0?style=flat-square" alt="GitHub commit activity">
|
||||
</a>
|
||||
<a href="https://www.npmjs.com/package/mem0ai" target="blank">
|
||||
<img src="https://img.shields.io/npm/v/mem0ai?color=%2334D058&label=npm%20package" alt="npm package version">
|
||||
</a>
|
||||
<a href="https://www.ycombinator.com/companies/mem0">
|
||||
<img src="https://img.shields.io/badge/Y%20Combinator-S24-orange?style=flat-square" alt="Y Combinator S24">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
For the open-source version, you can install the Mem0 package using npm:
|
||||
Mem0 gives AI assistants and agents persistent memory. It extracts useful facts from conversations, scopes them to a user, agent, or run, and retrieves the relevant facts for later interactions. The TypeScript package, `mem0ai` on npm, includes `MemoryClient` for the hosted Mem0 Platform, imported from `mem0ai`, and `Memory` for open-source, in-process memory, imported from `mem0ai/oss`.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Node.js 20 or later
|
||||
- Hosted Platform: `MEM0_API_KEY` from the [Mem0 dashboard](https://app.mem0.ai/dashboard/api-keys)
|
||||
- Open source with the default providers: `OPENAI_API_KEY`
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
npm i mem0ai
|
||||
npm install mem0ai
|
||||
```
|
||||
|
||||
## 2. API Key Setup
|
||||
## Platform or open source
|
||||
|
||||
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.
|
||||
| | Platform (`MemoryClient`) | Open source (`Memory`) |
|
||||
| ------------------- | --------------------------------------- | --------------------------------------------------------------------- |
|
||||
| Import | `import { MemoryClient } from "mem0ai"` | `import { Memory } from "mem0ai/oss"` |
|
||||
| Where memories live | Mem0's hosted API | Your configured vector store |
|
||||
| Required key | `MEM0_API_KEY` | `OPENAI_API_KEY` with the defaults, or keys for your chosen providers |
|
||||
| Extraction | Managed and asynchronous | Runs against your configured LLM |
|
||||
| Best for | Zero-ops production use | Local development and custom infrastructure |
|
||||
|
||||
## 3. Client Features
|
||||
## Platform quickstart
|
||||
|
||||
### Cloud Offering
|
||||
Set `MEM0_API_KEY`, then add a conversation:
|
||||
|
||||
The cloud version provides a comprehensive set of features, including:
|
||||
```ts
|
||||
import { MemoryClient, type Message } from "mem0ai";
|
||||
|
||||
- **Memory Operations**: Perform CRUD operations on memories.
|
||||
- **Search Capabilities**: Search for relevant memories using advanced filters.
|
||||
- **Memory History**: Track changes to memories over time.
|
||||
- **Error Handling**: Robust error handling for API-related issues.
|
||||
- **Async/Await Support**: All methods return promises for easy integration.
|
||||
const client = new MemoryClient({ apiKey: process.env.MEM0_API_KEY! });
|
||||
|
||||
### Open-Source Offering
|
||||
const messages: Message[] = [
|
||||
{ role: "user", content: "I am vegetarian and allergic to nuts." },
|
||||
{ role: "assistant", content: "I will remember that." },
|
||||
];
|
||||
await client.add(messages, { userId: "alex" });
|
||||
```
|
||||
|
||||
The open-source version includes the following top features:
|
||||
Hosted `add()` queues extraction and usually returns an `eventId` with `status: "PENDING"`. Do not search immediately after `add()`. Wait for processing to finish in the dashboard, or use a [`memory_add` webhook](https://docs.mem0.ai/platform/features/webhooks), then search:
|
||||
|
||||
- **Memory Management**: Add, update, delete, and retrieve memories.
|
||||
- **Vector Store Integration**: Supports various vector store providers for efficient memory retrieval.
|
||||
- **LLM Support**: Integrates with multiple LLM providers for generating responses.
|
||||
- **Customizable Configuration**: Easily configure memory settings and providers.
|
||||
- **SQLite Storage**: Use SQLite for memory history management.
|
||||
```ts
|
||||
import { MemoryClient } from "mem0ai";
|
||||
|
||||
## 4. Memory Operations
|
||||
const client = new MemoryClient({ apiKey: process.env.MEM0_API_KEY! });
|
||||
const results = await client.search("What does Alex eat?", {
|
||||
filters: { user_id: "alex" },
|
||||
topK: 5,
|
||||
});
|
||||
console.log(results.results);
|
||||
```
|
||||
|
||||
Mem0 provides a simple and customizable interface for performing memory operations. You can create long-term and short-term memories, search for relevant memories, and manage memory history.
|
||||
`search()` and `getAll()` take entity IDs inside `filters` with snake_case keys. `add()` and `deleteAll()` take `userId`, `agentId`, or `runId` as top-level camelCase options.
|
||||
|
||||
## 5. Error Handling
|
||||
## Open-source quickstart
|
||||
|
||||
The MemoryClient throws errors for any API-related issues. You can catch and handle these errors effectively.
|
||||
Set `OPENAI_API_KEY` before using the default OpenAI LLM and embedder:
|
||||
|
||||
## 6. Using with async/await
|
||||
```ts
|
||||
import { Memory } from "mem0ai/oss";
|
||||
|
||||
All methods of the MemoryClient return promises, allowing for seamless integration with async/await syntax.
|
||||
const memory = new Memory();
|
||||
|
||||
## 7. Testing the Client
|
||||
const messages = [
|
||||
{ role: "user", content: "I am vegetarian and allergic to nuts." },
|
||||
{ role: "assistant", content: "I will remember that." },
|
||||
];
|
||||
await memory.add(messages, { userId: "alex" });
|
||||
|
||||
To test the MemoryClient in a Node.js environment, you can create a simple script to verify the functionality of memory operations.
|
||||
const results = await memory.search("What does Alex eat?", {
|
||||
filters: { user_id: "alex" },
|
||||
topK: 5,
|
||||
});
|
||||
console.log(results.results);
|
||||
```
|
||||
|
||||
## Getting Help
|
||||
The default `Memory` configuration uses OpenAI `gpt-5-mini`, OpenAI `text-embedding-3-small`, a SQLite-backed vector store at `~/.mem0/vector_store.db`, and a SQLite history database at `memory.db`. Pass a config object to `Memory` to change the LLM, embedder, vector store, history path, or reranker.
|
||||
|
||||
If you have any questions or need assistance, please reach out to us:
|
||||
## Configuration and features
|
||||
|
||||
| Feature | Documentation |
|
||||
| ---------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
|
||||
| Memory operations: `add`, `search`, `get`, `getAll`, `update`, `delete`, `deleteAll`, `history`, all async and Promise-based | [Node quickstart](https://docs.mem0.ai/open-source/node-quickstart) |
|
||||
| Entity scoping with `userId`, `agentId`, and `runId` | [Entity-scoped memory](https://docs.mem0.ai/platform/features/entity-scoped-memory) |
|
||||
| Metadata and filters | [Metadata filtering](https://docs.mem0.ai/open-source/features/metadata-filtering) |
|
||||
| LLMs, embedders, vector stores, and rerankers | [Components](https://docs.mem0.ai/components/llms/overview) |
|
||||
| Graph memory | [Graph memory](https://docs.mem0.ai/platform/features/graph-memory) |
|
||||
| Custom instructions | [Custom instructions](https://docs.mem0.ai/open-source/features/custom-instructions) |
|
||||
| Multimodal input | [Multimodal support](https://docs.mem0.ai/open-source/features/multimodal-support) |
|
||||
| Platform webhooks, export, feedback, expiration, and custom categories | [Platform features](https://docs.mem0.ai/platform/features) |
|
||||
|
||||
## Benchmarks
|
||||
|
||||
<p align="center">
|
||||
<a href="https://mem0.ai/research"><strong>Benchmarking Mem0's token-efficient memory algorithm</strong></a>
|
||||
</p>
|
||||
|
||||
| Benchmark | Old | New | Tokens | Latency p50 |
|
||||
| --------------- | ---: | -------: | -----: | ----------: |
|
||||
| **LoCoMo** | 71.4 | **92.5** | 7.0K | 0.88s |
|
||||
| **LongMemEval** | 67.8 | **94.4** | 6.8K | 1.09s |
|
||||
| **BEAM (1M)** | n/a | **64.1** | 6.7K | 1.00s |
|
||||
| **BEAM (10M)** | n/a | **48.6** | 6.9K | 1.05s |
|
||||
|
||||
All benchmarks use the same production-representative model stack, single-pass retrieval, and a top-200 retrieval budget. Scores reflect the managed Platform, which includes proprietary optimizations not available in the open-source SDK. Open-source results should show similar directional gains, but may not match these scores.
|
||||
|
||||
The current algorithm uses single-pass ADD-only extraction, first-class agent facts, entity linking, multi-signal retrieval, and temporal reasoning. Read the [research paper](https://mem0.ai/research), the [migration guide](https://docs.mem0.ai/migration/oss-v2-to-v3), or the open-source [evaluation framework](https://github.com/mem0ai/memory-benchmarks).
|
||||
|
||||
## Self-hosted server
|
||||
|
||||
Run Mem0 as a FastAPI service with PostgreSQL, pgvector, and Neo4j:
|
||||
|
||||
```bash
|
||||
# Recommended: start the stack, create an admin, and issue the first API key.
|
||||
cd server && make bootstrap
|
||||
|
||||
# Manual: start the stack, then finish setup in the browser wizard.
|
||||
cd server && docker compose up -d
|
||||
```
|
||||
|
||||
Self-hosted authentication is enabled by default. See the [self-hosted documentation](https://docs.mem0.ai/open-source/overview) and [upgrade notes](https://docs.mem0.ai/open-source/setup#upgrade-notes).
|
||||
|
||||
## CLI
|
||||
|
||||
Manage hosted memories from your terminal:
|
||||
|
||||
```bash
|
||||
npm install -g @mem0/cli
|
||||
|
||||
mem0 init
|
||||
mem0 add "Prefers dark mode and vim keybindings" --user-id alice
|
||||
mem0 search "What does Alice prefer?" --user-id alice
|
||||
```
|
||||
|
||||
AI agents can create an account without email or a dashboard:
|
||||
|
||||
```bash
|
||||
mem0 init --agent --agent-caller claude-code
|
||||
```
|
||||
|
||||
The human owner can claim the account later with `mem0 init --email <their-email>`. The API key and memories remain unchanged. See the [CLI documentation](https://docs.mem0.ai/platform/cli) and [agent signup guide](https://docs.mem0.ai/platform/agent-signup).
|
||||
|
||||
## Agent skills
|
||||
|
||||
Install reference skills to give compatible coding assistants Mem0 context:
|
||||
|
||||
```bash
|
||||
npx skills add https://github.com/mem0ai/mem0 --skill mem0
|
||||
npx skills add https://github.com/mem0ai/mem0 --skill mem0-cli
|
||||
```
|
||||
|
||||
Install pipeline skills for end-to-end workflows:
|
||||
|
||||
```bash
|
||||
npx skills add https://github.com/mem0ai/mem0 --skill mem0-integrate
|
||||
npx skills add https://github.com/mem0ai/mem0 --skill mem0-test-integration
|
||||
npx skills add https://github.com/mem0ai/mem0 --skill mem0-oss-to-platform
|
||||
```
|
||||
|
||||
See the [skills catalog](../skills/) or [Vibecoding with Mem0](https://docs.mem0.ai/vibecoding).
|
||||
|
||||
## Integrations and demos
|
||||
|
||||
- [ChatGPT with Memory demo](https://mem0.dev/demo)
|
||||
- [Browser extension](https://chromewebstore.google.com/detail/onihkkbipkfeijkadecaafbgagkhglop?utm_source=item-share-cb)
|
||||
- [LangGraph integration](https://docs.mem0.ai/integrations/langgraph)
|
||||
- [CrewAI integration](https://docs.mem0.ai/integrations/crewai)
|
||||
|
||||
## Documentation and help
|
||||
|
||||
- [Node quickstart](https://docs.mem0.ai/open-source/node-quickstart)
|
||||
- [Platform quickstart](https://docs.mem0.ai/platform/quickstart)
|
||||
- [API reference](https://docs.mem0.ai/api-reference)
|
||||
- [Discord](https://mem0.dev/DiG)
|
||||
- [GitHub issues](https://github.com/mem0ai/mem0/issues)
|
||||
- Email: founders@mem0.ai
|
||||
- [Join our discord community](https://mem0.ai/discord)
|
||||
- GitHub Issues: [Report bugs or request features](https://github.com/mem0ai/mem0/issues)
|
||||
|
||||
## Contributing
|
||||
|
||||
Read [CONTRIBUTING.md](https://github.com/mem0ai/mem0/blob/main/CONTRIBUTING.md) before opening an issue or pull request.
|
||||
|
||||
## Citation
|
||||
|
||||
```bibtex
|
||||
@article{mem0,
|
||||
title={Mem0: Building Production-Ready AI Agents with Scalable Long-Term Memory},
|
||||
author={Chhikara, Prateek and Khant, Dev and Aryan, Saket and Singh, Taranjeet and Yadav, Deshraj},
|
||||
journal={arXiv preprint arXiv:2504.19413},
|
||||
year={2025}
|
||||
}
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Apache 2.0. See [LICENSE](https://github.com/mem0ai/mem0/blob/main/LICENSE).
|
||||
|
||||
@@ -1,24 +1,9 @@
|
||||
from typing import Any, Dict
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
|
||||
# TODO: Upgrade to latest pydantic version
|
||||
class RedisDBConfig(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid", arbitrary_types_allowed=True)
|
||||
|
||||
redis_url: str = Field(..., description="Redis URL")
|
||||
collection_name: str = Field("mem0", description="Collection name")
|
||||
embedding_model_dims: int = Field(1536, description="Embedding model dimensions")
|
||||
|
||||
@model_validator(mode="before")
|
||||
@classmethod
|
||||
def validate_extra_fields(cls, values: Dict[str, Any]) -> Dict[str, Any]:
|
||||
allowed_fields = set(cls.model_fields.keys())
|
||||
input_fields = set(values.keys())
|
||||
extra_fields = input_fields - allowed_fields
|
||||
if extra_fields:
|
||||
raise ValueError(
|
||||
f"Extra fields not allowed: {', '.join(extra_fields)}. Please input only the following fields: {', '.join(allowed_fields)}"
|
||||
)
|
||||
return values
|
||||
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
|
||||
Reference in New Issue
Block a user