Compare commits
62 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b3ede5b7c0 | |||
| 3553fc79dd | |||
| f322cf82b9 | |||
| 73c975ba68 | |||
| 931d579ba5 | |||
| f4773a0baf | |||
| 06d33f6cc4 | |||
| 8f3b60f3e1 | |||
| a6e27dcc9c | |||
| 4f10c986b5 | |||
| 821152bd14 | |||
| f48b133101 | |||
| 1d56f85705 | |||
| ced852033b | |||
| b9ad8fa8b2 | |||
| e3f5ce7b41 | |||
| f681889b14 | |||
| b5ec46be5b | |||
| 168ad358d5 | |||
| 2c796d144f | |||
| c676c2c458 | |||
| b36847622d | |||
| 32c8849044 | |||
| 2dd2872c08 | |||
| cf268da19d | |||
| 7a5df64746 | |||
| 4c41f6deeb | |||
| f84aa1eb31 | |||
| 9226ee2229 | |||
| 8399b088a5 | |||
| 437f0b5495 | |||
| 0ffaffa88c | |||
| 433ff494f1 | |||
| de03c52ed3 | |||
| b4a50e3dc8 | |||
| b819d95d18 | |||
| 3ac1c9452c | |||
| d6347f6660 | |||
| e769502baa | |||
| 652193d599 | |||
| a86c87236d | |||
| 2274b5acad | |||
| 9b0705c345 | |||
| d31fa168eb | |||
| f32eb4406b | |||
| 366945965d | |||
| 6702fa3e3e | |||
| a44855af9e | |||
| d817aa9c12 | |||
| 7ac8ab154b | |||
| b00a1a1065 | |||
| 2e90ed4f78 | |||
| 069ea0887c | |||
| ae7f406265 | |||
| 90f2d24e83 | |||
| 64b9646e7d | |||
| 866888df41 | |||
| 95b6f95f7b | |||
| 74771b4e76 | |||
| 8e65ce915d | |||
| a3154d59e5 | |||
| 1019f0e17c |
@@ -8,7 +8,7 @@
|
||||
"name": "mem0",
|
||||
"source": {
|
||||
"source": "local",
|
||||
"path": "./mem0-plugin"
|
||||
"path": "./integrations/mem0-plugin"
|
||||
},
|
||||
"policy": {
|
||||
"installation": "AVAILABLE",
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
"plugins": [
|
||||
{
|
||||
"name": "mem0",
|
||||
"source": "./mem0-plugin",
|
||||
"source": "./integrations/mem0-plugin",
|
||||
"description": "Mem0 memory layer for AI applications. Add persistent memory, personalization, and semantic search to Claude workflows.",
|
||||
"version": "0.2.7"
|
||||
"version": "0.2.10"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"name": "mem0",
|
||||
"source": {
|
||||
"source": "local",
|
||||
"path": "./mem0-plugin"
|
||||
"path": "./integrations/mem0-plugin"
|
||||
},
|
||||
"policy": {
|
||||
"installation": "AVAILABLE",
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
"plugins": [
|
||||
{
|
||||
"name": "mem0",
|
||||
"source": "./mem0-plugin",
|
||||
"source": "./integrations/mem0-plugin",
|
||||
"description": "Mem0 memory layer for AI applications. Add persistent memory, personalization, and semantic search.",
|
||||
"version": "0.2.7"
|
||||
"version": "0.2.10"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,18 +1,33 @@
|
||||
name: Publish Python 🐍 distributions 📦 to PyPI and TestPyPI
|
||||
|
||||
# Dispatched by release.yml (Release Router) when a release tagged v* is
|
||||
# published. Can also be dispatched manually to re-publish a tag.
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Release tag to build and publish (e.g. v1.2.3)'
|
||||
required: true
|
||||
type: string
|
||||
prerelease:
|
||||
description: 'Unused for PyPI (pre-releases are expressed in the version itself); accepted for router uniformity'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
jobs:
|
||||
build-n-publish:
|
||||
name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI
|
||||
if: startsWith(github.event.release.tag_name, 'v')
|
||||
# Pure SDK version tags only (v1.2.3) — excludes package-prefixed tags
|
||||
# like vercel-ai-v* that also start with 'v'
|
||||
if: startsWith(inputs.tag, 'v') && !contains(inputs.tag, '-v')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ inputs.tag }}
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
@@ -39,7 +54,6 @@ jobs:
|
||||
# packages_dir: dist/
|
||||
|
||||
- name: Publish distribution 📦 to PyPI
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
packages_dir: dist/
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
name: CI Gate
|
||||
|
||||
# Single required status check for all PRs.
|
||||
#
|
||||
# Path-filtered CI workflows can't be marked as required in branch
|
||||
# protection: on a PR that doesn't touch their paths they never report, and
|
||||
# the required check hangs at "Expected" forever. This gate solves that. It
|
||||
# runs on every PR, detects which packages changed, calls only the relevant
|
||||
# package CI workflows (as reusable workflows), and the final "CI Gate" job
|
||||
# reports the aggregate result — success when every invoked pipeline passed
|
||||
# (skipped pipelines are fine), failure when any failed.
|
||||
#
|
||||
# Branch protection should require exactly one status check: "CI Gate".
|
||||
#
|
||||
# Package CI workflows keep their own push-to-main and workflow_dispatch
|
||||
# triggers; only their pull_request triggers moved here. To wire in a new
|
||||
# package: add a filter under the `changes` job, a call job that `uses:` the
|
||||
# package workflow, and list the call job in the gate's `needs`.
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
group: ci-gate-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
changes:
|
||||
name: Detect changed packages
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
python_sdk: ${{ steps.filter.outputs.python_sdk }}
|
||||
ts_sdk: ${{ steps.filter.outputs.ts_sdk }}
|
||||
cli_python: ${{ steps.filter.outputs.cli_python }}
|
||||
cli_node: ${{ steps.filter.outputs.cli_node }}
|
||||
openclaw: ${{ steps.filter.outputs.openclaw }}
|
||||
opencode_plugin: ${{ steps.filter.outputs.opencode_plugin }}
|
||||
pi_agent_plugin: ${{ steps.filter.outputs.pi_agent_plugin }}
|
||||
docs_llms_txt: ${{ steps.filter.outputs.docs_llms_txt }}
|
||||
steps:
|
||||
- uses: dorny/paths-filter@v3
|
||||
id: filter
|
||||
with:
|
||||
# Each filter mirrors the package workflow's old pull_request
|
||||
# paths, plus the package workflow file itself and this gate file
|
||||
# (changing either must re-exercise the pipeline).
|
||||
filters: |
|
||||
python_sdk:
|
||||
- 'mem0/**'
|
||||
- 'tests/**'
|
||||
- 'pyproject.toml'
|
||||
- '.github/workflows/ci.yml'
|
||||
- '.github/workflows/ci-gate.yml'
|
||||
ts_sdk:
|
||||
- 'mem0-ts/**'
|
||||
- '.github/workflows/ts-sdk-ci.yml'
|
||||
- '.github/workflows/ci-gate.yml'
|
||||
cli_python:
|
||||
- 'cli/python/**'
|
||||
- '.github/workflows/cli-python-ci.yml'
|
||||
- '.github/workflows/ci-gate.yml'
|
||||
cli_node:
|
||||
- 'cli/node/**'
|
||||
- '.github/workflows/cli-node-ci.yml'
|
||||
- '.github/workflows/ci-gate.yml'
|
||||
openclaw:
|
||||
- 'integrations/openclaw/**'
|
||||
- '.github/workflows/openclaw-checks.yml'
|
||||
- '.github/workflows/ci-gate.yml'
|
||||
opencode_plugin:
|
||||
- 'integrations/mem0-plugin/.opencode-plugin/**'
|
||||
- '.github/workflows/opencode-plugin-checks.yml'
|
||||
- '.github/workflows/ci-gate.yml'
|
||||
pi_agent_plugin:
|
||||
- 'integrations/pi-agent-plugin/**'
|
||||
- '.github/workflows/pi-agent-plugin-checks.yml'
|
||||
- '.github/workflows/ci-gate.yml'
|
||||
docs_llms_txt:
|
||||
- 'docs/**/*.mdx'
|
||||
- 'docs/llms.txt'
|
||||
- 'scripts/check-llms-txt-coverage.py'
|
||||
- 'scripts/llms-txt-ignore.txt'
|
||||
- '.github/workflows/docs-llms-txt-check.yml'
|
||||
- '.github/workflows/ci-gate.yml'
|
||||
|
||||
python-sdk:
|
||||
name: Python SDK
|
||||
needs: changes
|
||||
if: needs.changes.outputs.python_sdk == 'true'
|
||||
uses: ./.github/workflows/ci.yml
|
||||
secrets: inherit
|
||||
|
||||
ts-sdk:
|
||||
name: TypeScript SDK
|
||||
needs: changes
|
||||
if: needs.changes.outputs.ts_sdk == 'true'
|
||||
uses: ./.github/workflows/ts-sdk-ci.yml
|
||||
secrets: inherit
|
||||
|
||||
cli-python:
|
||||
name: Python CLI
|
||||
needs: changes
|
||||
if: needs.changes.outputs.cli_python == 'true'
|
||||
uses: ./.github/workflows/cli-python-ci.yml
|
||||
secrets: inherit
|
||||
|
||||
cli-node:
|
||||
name: Node CLI
|
||||
needs: changes
|
||||
if: needs.changes.outputs.cli_node == 'true'
|
||||
uses: ./.github/workflows/cli-node-ci.yml
|
||||
secrets: inherit
|
||||
|
||||
openclaw:
|
||||
name: OpenClaw
|
||||
needs: changes
|
||||
if: needs.changes.outputs.openclaw == 'true'
|
||||
uses: ./.github/workflows/openclaw-checks.yml
|
||||
secrets: inherit
|
||||
|
||||
opencode-plugin:
|
||||
name: OpenCode Plugin
|
||||
needs: changes
|
||||
if: needs.changes.outputs.opencode_plugin == 'true'
|
||||
uses: ./.github/workflows/opencode-plugin-checks.yml
|
||||
secrets: inherit
|
||||
|
||||
pi-agent-plugin:
|
||||
name: Pi Agent Plugin
|
||||
needs: changes
|
||||
if: needs.changes.outputs.pi_agent_plugin == 'true'
|
||||
uses: ./.github/workflows/pi-agent-plugin-checks.yml
|
||||
secrets: inherit
|
||||
|
||||
docs-llms-txt:
|
||||
name: docs llms.txt
|
||||
needs: changes
|
||||
if: needs.changes.outputs.docs_llms_txt == 'true'
|
||||
uses: ./.github/workflows/docs-llms-txt-check.yml
|
||||
secrets: inherit
|
||||
|
||||
gate:
|
||||
name: CI Gate
|
||||
needs:
|
||||
- changes
|
||||
- python-sdk
|
||||
- ts-sdk
|
||||
- cli-python
|
||||
- cli-node
|
||||
- openclaw
|
||||
- opencode-plugin
|
||||
- pi-agent-plugin
|
||||
- docs-llms-txt
|
||||
if: always()
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Evaluate pipeline results
|
||||
env:
|
||||
NEEDS: ${{ toJSON(needs) }}
|
||||
run: |
|
||||
echo "$NEEDS" | jq -r 'to_entries[] | "\(.key): \(.value.result)"'
|
||||
failed=$(echo "$NEEDS" | jq -r '[to_entries[] | select(.value.result == "failure" or .value.result == "cancelled") | .key] | join(", ")')
|
||||
if [ -n "$failed" ]; then
|
||||
echo "::error::Failing pipelines: $failed"
|
||||
exit 1
|
||||
fi
|
||||
echo "All pipelines relevant to this change passed."
|
||||
@@ -1,9 +1,11 @@
|
||||
name: ci
|
||||
|
||||
# On PRs this is invoked by ci-gate.yml (the single required check);
|
||||
# push-to-main runs remain standalone.
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
changelog_check:
|
||||
|
||||
@@ -1,13 +1,25 @@
|
||||
name: Publish @mem0/cli 📦 to npm
|
||||
|
||||
# Dispatched by release.yml (Release Router) when a release tagged
|
||||
# cli-node-v* is published. Can also be dispatched manually to re-publish
|
||||
# a tag.
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Release tag to build and publish (e.g. cli-node-v0.2.0)'
|
||||
required: true
|
||||
type: string
|
||||
prerelease:
|
||||
description: 'Publish under the version preid dist-tag instead of latest'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
jobs:
|
||||
build-n-publish:
|
||||
name: Build and publish @mem0/cli 📦 to npm
|
||||
if: startsWith(github.event.release.tag_name, 'cli-node-v')
|
||||
if: startsWith(inputs.tag, 'cli-node-v')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
@@ -16,6 +28,8 @@ jobs:
|
||||
working-directory: cli/node
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.tag }}
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
@@ -38,7 +52,7 @@ jobs:
|
||||
|
||||
- name: Publish to npm
|
||||
run: |
|
||||
if [ "${{ github.event.release.prerelease }}" = "true" ]; then
|
||||
if [ "${{ inputs.prerelease }}" = "true" ]; then
|
||||
PREID=$(node -p "require('./package.json').version.split('-')[1].split('.')[0]")
|
||||
npx npm@latest publish --provenance --access public --tag "$PREID"
|
||||
else
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
name: CLI Node CI
|
||||
|
||||
# On PRs this is invoked by ci-gate.yml (the single required check);
|
||||
# push-to-main and manual runs remain standalone.
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
@@ -7,10 +9,7 @@ on:
|
||||
paths:
|
||||
- 'cli/node/**'
|
||||
- '.github/workflows/cli-node-ci.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'cli/node/**'
|
||||
- '.github/workflows/cli-node-ci.yml'
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
|
||||
@@ -1,13 +1,24 @@
|
||||
name: Publish mem0-cli 🐍 distributions 📦 to PyPI
|
||||
|
||||
# Dispatched by release.yml (Release Router) when a release tagged cli-v* is
|
||||
# published. Can also be dispatched manually to re-publish a tag.
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Release tag to build and publish (e.g. cli-v0.2.0)'
|
||||
required: true
|
||||
type: string
|
||||
prerelease:
|
||||
description: 'Unused for PyPI (pre-releases are expressed in the version itself); accepted for router uniformity'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
jobs:
|
||||
build-n-publish:
|
||||
name: Build and publish mem0-cli 📦 to PyPI
|
||||
if: startsWith(github.event.release.tag_name, 'cli-v')
|
||||
if: startsWith(inputs.tag, 'cli-v')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
@@ -16,6 +27,8 @@ jobs:
|
||||
working-directory: cli/python
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.tag }}
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
name: CLI Python CI
|
||||
|
||||
# On PRs this is invoked by ci-gate.yml (the single required check);
|
||||
# push-to-main and manual runs remain standalone.
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
@@ -7,10 +9,7 @@ on:
|
||||
paths:
|
||||
- 'cli/python/**'
|
||||
- '.github/workflows/cli-python-ci.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'cli/python/**'
|
||||
- '.github/workflows/cli-python-ci.yml'
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
|
||||
@@ -6,13 +6,10 @@ name: docs - llms.txt check
|
||||
# python scripts/check-llms-txt-coverage.py # read-only
|
||||
# python scripts/check-llms-txt-coverage.py --write # scaffold placeholders
|
||||
|
||||
# On PRs this is invoked by ci-gate.yml (the single required check);
|
||||
# manual runs remain standalone.
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'docs/**/*.mdx'
|
||||
- 'docs/llms.txt'
|
||||
- 'scripts/check-llms-txt-coverage.py'
|
||||
- 'scripts/llms-txt-ignore.txt'
|
||||
workflow_call:
|
||||
workflow_dispatch: {}
|
||||
|
||||
permissions:
|
||||
|
||||
@@ -1,21 +1,35 @@
|
||||
name: Publish @mem0/openclaw-mem0 📦 to npm
|
||||
|
||||
# Dispatched by release.yml (Release Router) when a release tagged
|
||||
# openclaw-v* is published. Can also be dispatched manually to re-publish
|
||||
# a tag.
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Release tag to build and publish (e.g. openclaw-v0.5.0)'
|
||||
required: true
|
||||
type: string
|
||||
prerelease:
|
||||
description: 'Publish under the version preid dist-tag instead of latest'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
jobs:
|
||||
build-n-publish:
|
||||
name: Build and publish @mem0/openclaw-mem0 📦 to npm
|
||||
if: startsWith(github.event.release.tag_name, 'openclaw-v')
|
||||
if: startsWith(inputs.tag, 'openclaw-v')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
defaults:
|
||||
run:
|
||||
working-directory: openclaw
|
||||
working-directory: integrations/openclaw
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.tag }}
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
@@ -28,7 +42,7 @@ jobs:
|
||||
node-version: '22'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: openclaw/pnpm-lock.yaml
|
||||
cache-dependency-path: integrations/openclaw/pnpm-lock.yaml
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
@@ -38,7 +52,7 @@ jobs:
|
||||
|
||||
- name: Publish to npm
|
||||
run: |
|
||||
if [ "${{ github.event.release.prerelease }}" = "true" ]; then
|
||||
if [ "${{ inputs.prerelease }}" = "true" ]; then
|
||||
PREID=$(node -p "require('./package.json').version.split('-')[1].split('.')[0]")
|
||||
npx npm@latest publish --provenance --access public --tag "$PREID"
|
||||
else
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
name: openclaw checks
|
||||
|
||||
# On PRs this is invoked by ci-gate.yml (the single required check);
|
||||
# push-to-main and manual runs remain standalone.
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'openclaw/**'
|
||||
- '.github/workflows/openclaw-checks.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'openclaw/**'
|
||||
- 'integrations/openclaw/**'
|
||||
- '.github/workflows/openclaw-checks.yml'
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
@@ -28,13 +27,13 @@ jobs:
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: openclaw/pnpm-lock.yaml
|
||||
cache-dependency-path: integrations/openclaw/pnpm-lock.yaml
|
||||
|
||||
- name: Install dependencies
|
||||
run: cd openclaw && pnpm install --frozen-lockfile
|
||||
run: cd integrations/openclaw && pnpm install --frozen-lockfile
|
||||
|
||||
- name: Type check
|
||||
run: cd openclaw && pnpm exec tsc --noEmit
|
||||
run: cd integrations/openclaw && pnpm exec tsc --noEmit
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -54,20 +53,20 @@ jobs:
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: openclaw/pnpm-lock.yaml
|
||||
cache-dependency-path: integrations/openclaw/pnpm-lock.yaml
|
||||
|
||||
- name: Install dependencies
|
||||
run: cd openclaw && pnpm install --frozen-lockfile
|
||||
run: cd integrations/openclaw && pnpm install --frozen-lockfile
|
||||
|
||||
- name: Run tests with coverage
|
||||
run: cd openclaw && pnpm exec vitest run --coverage
|
||||
run: cd integrations/openclaw && pnpm exec vitest run --coverage
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
if: matrix.node-version == 20
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
flags: openclaw
|
||||
directory: openclaw/coverage
|
||||
directory: integrations/openclaw/coverage
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
@@ -86,15 +85,15 @@ jobs:
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: openclaw/pnpm-lock.yaml
|
||||
cache-dependency-path: integrations/openclaw/pnpm-lock.yaml
|
||||
|
||||
- name: Install dependencies
|
||||
run: cd openclaw && pnpm install --frozen-lockfile
|
||||
run: cd integrations/openclaw && pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: cd openclaw && pnpm build
|
||||
run: cd integrations/openclaw && pnpm build
|
||||
|
||||
- name: Verify dist output exists
|
||||
run: |
|
||||
test -f openclaw/dist/index.js || (echo "Build output missing: dist/index.js" && exit 1)
|
||||
test -f openclaw/dist/index.d.ts || (echo "Build output missing: dist/index.d.ts" && exit 1)
|
||||
test -f integrations/openclaw/dist/index.js || (echo "Build output missing: dist/index.js" && exit 1)
|
||||
test -f integrations/openclaw/dist/index.d.ts || (echo "Build output missing: dist/index.d.ts" && exit 1)
|
||||
|
||||
@@ -1,21 +1,35 @@
|
||||
name: Publish @mem0/opencode-plugin 📦 to npm
|
||||
|
||||
# Dispatched by release.yml (Release Router) when a release tagged
|
||||
# opencode-v* is published. Can also be dispatched manually to re-publish
|
||||
# a tag.
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Release tag to build and publish (e.g. opencode-v0.2.0)'
|
||||
required: true
|
||||
type: string
|
||||
prerelease:
|
||||
description: 'Publish under the version preid dist-tag instead of latest'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
jobs:
|
||||
build-n-publish:
|
||||
name: Build and publish @mem0/opencode-plugin 📦 to npm
|
||||
if: startsWith(github.event.release.tag_name, 'opencode-v')
|
||||
if: startsWith(inputs.tag, 'opencode-v')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
defaults:
|
||||
run:
|
||||
working-directory: mem0-plugin/.opencode-plugin
|
||||
working-directory: integrations/mem0-plugin/.opencode-plugin
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.tag }}
|
||||
|
||||
- name: Install Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
@@ -36,7 +50,7 @@ jobs:
|
||||
|
||||
- name: Publish to npm
|
||||
run: |
|
||||
if [ "${{ github.event.release.prerelease }}" = "true" ]; then
|
||||
if [ "${{ inputs.prerelease }}" = "true" ]; then
|
||||
PREID=$(node -p "require('./package.json').version.split('-')[1].split('.')[0]")
|
||||
npx npm@latest publish --provenance --access public --tag "$PREID"
|
||||
else
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
name: opencode-plugin checks
|
||||
|
||||
# On PRs this is invoked by ci-gate.yml (the single required check);
|
||||
# push-to-main and manual runs remain standalone.
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'mem0-plugin/.opencode-plugin/**'
|
||||
- '.github/workflows/opencode-plugin-checks.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'mem0-plugin/.opencode-plugin/**'
|
||||
- 'integrations/mem0-plugin/.opencode-plugin/**'
|
||||
- '.github/workflows/opencode-plugin-checks.yml'
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: mem0-plugin/.opencode-plugin
|
||||
working-directory: integrations/mem0-plugin/.opencode-plugin
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
name: Publish @mem0/pi-agent-plugin 📦 to npm
|
||||
|
||||
# Dispatched by release.yml (Release Router) when a release tagged
|
||||
# pi-agent-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. pi-agent-v0.1.1)'
|
||||
required: true
|
||||
type: string
|
||||
prerelease:
|
||||
description: 'Publish under the version preid dist-tag instead of latest'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
jobs:
|
||||
build-n-publish:
|
||||
name: Build and publish @mem0/pi-agent-plugin 📦 to npm
|
||||
if: startsWith(inputs.tag, 'pi-agent-v')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
defaults:
|
||||
run:
|
||||
working-directory: integrations/pi-agent-plugin
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.tag }}
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: integrations/pi-agent-plugin/pnpm-lock.yaml
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: pnpm build
|
||||
|
||||
- name: Publish to npm
|
||||
run: |
|
||||
if [ "${{ inputs.prerelease }}" = "true" ]; then
|
||||
PREID=$(node -p "require('./package.json').version.split('-')[1].split('.')[0]")
|
||||
npx npm@latest publish --provenance --access public --tag "$PREID"
|
||||
else
|
||||
npx npm@latest publish --provenance --access public
|
||||
fi
|
||||
@@ -0,0 +1,92 @@
|
||||
name: pi-agent-plugin checks
|
||||
|
||||
# On PRs this is invoked by ci-gate.yml (the single required check);
|
||||
# push-to-main and manual runs remain standalone.
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'integrations/pi-agent-plugin/**'
|
||||
- '.github/workflows/pi-agent-plugin-checks.yml'
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: integrations/pi-agent-plugin/pnpm-lock.yaml
|
||||
|
||||
- name: Install dependencies
|
||||
run: cd integrations/pi-agent-plugin && pnpm install --frozen-lockfile
|
||||
|
||||
- name: Type check
|
||||
run: cd integrations/pi-agent-plugin && pnpm exec tsc --noEmit
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20, 22]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- name: Setup Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: integrations/pi-agent-plugin/pnpm-lock.yaml
|
||||
|
||||
- name: Install dependencies
|
||||
run: cd integrations/pi-agent-plugin && pnpm install --frozen-lockfile
|
||||
|
||||
- name: Run tests
|
||||
run: cd integrations/pi-agent-plugin && pnpm exec vitest run
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: integrations/pi-agent-plugin/pnpm-lock.yaml
|
||||
|
||||
- name: Install dependencies
|
||||
run: cd integrations/pi-agent-plugin && pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: cd integrations/pi-agent-plugin && pnpm build
|
||||
|
||||
- name: Verify dist output exists
|
||||
run: |
|
||||
test -f integrations/pi-agent-plugin/dist/index.js || (echo "Build output missing: dist/index.js" && exit 1)
|
||||
test -f integrations/pi-agent-plugin/dist/index.d.ts || (echo "Build output missing: dist/index.d.ts" && exit 1)
|
||||
test -f integrations/pi-agent-plugin/dist/entry.js || (echo "Build output missing: dist/entry.js" && exit 1)
|
||||
test -f integrations/pi-agent-plugin/dist/entry.d.ts || (echo "Build output missing: dist/entry.d.ts" && exit 1)
|
||||
@@ -0,0 +1,68 @@
|
||||
name: Release Router 🚦
|
||||
|
||||
# Single entry point for all release publishing.
|
||||
#
|
||||
# Package CD workflows no longer listen to release events themselves — this
|
||||
# router inspects the release tag and dispatches only the matching pipeline,
|
||||
# so each release produces one routed run instead of one real run plus seven
|
||||
# skipped ones.
|
||||
#
|
||||
# Re-publishing a release (e.g. after fixing registry settings) does NOT
|
||||
# require deleting and recreating it anymore — manually dispatch the
|
||||
# package's CD workflow from the tag instead:
|
||||
#
|
||||
# gh workflow run <package>-cd.yml --ref refs/tags/<tag> -f tag=<tag>
|
||||
#
|
||||
# Note: dispatching runs the workflow file as it exists at the given ref, so
|
||||
# this router can only dispatch tags created after the workflow_dispatch
|
||||
# conversion landed on main. For older tags, dispatch manually from main.
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
permissions:
|
||||
actions: write
|
||||
|
||||
jobs:
|
||||
route:
|
||||
name: Route ${{ github.event.release.tag_name }} to its CD pipeline
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Match tag prefix to CD workflow
|
||||
id: match
|
||||
env:
|
||||
TAG: ${{ github.event.release.tag_name }}
|
||||
run: |
|
||||
# Specific package prefixes first; the bare v* (Python SDK) arm
|
||||
# must stay last so prefixed tags that also start with 'v'
|
||||
# (vercel-ai-v*) can never be routed to the Python pipeline.
|
||||
case "$TAG" in
|
||||
ts-v*) workflow="ts-sdk-cd.yml" ;;
|
||||
cli-node-v*) workflow="cli-node-cd.yml" ;;
|
||||
cli-v*) workflow="cli-python-cd.yml" ;;
|
||||
vercel-ai-v*) workflow="vercel-ai-cd.yml" ;;
|
||||
openclaw-v*) workflow="openclaw-cd.yml" ;;
|
||||
opencode-v*) workflow="opencode-plugin-cd.yml" ;;
|
||||
pi-agent-v*) workflow="pi-agent-plugin-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."
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
echo "workflow=$workflow" >> "$GITHUB_OUTPUT"
|
||||
echo ":outbox_tray: Routed \`$TAG\` → \`$workflow\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Dispatch ${{ steps.match.outputs.workflow }}
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAG: ${{ github.event.release.tag_name }}
|
||||
run: |
|
||||
# --ref points at the tag so the dispatched run builds (and signs
|
||||
# provenance for) the exact tagged commit.
|
||||
gh workflow run "${{ steps.match.outputs.workflow }}" \
|
||||
--repo "$GITHUB_REPOSITORY" \
|
||||
--ref "refs/tags/$TAG" \
|
||||
-f tag="$TAG" \
|
||||
-f prerelease="${{ github.event.release.prerelease }}"
|
||||
@@ -1,13 +1,24 @@
|
||||
name: Publish mem0ai 📦 to npm
|
||||
|
||||
# Dispatched by release.yml (Release Router) when a release tagged ts-v* is
|
||||
# published. Can also be dispatched manually to re-publish a tag.
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Release tag to build and publish (e.g. ts-v2.1.0)'
|
||||
required: true
|
||||
type: string
|
||||
prerelease:
|
||||
description: 'Publish under the version preid dist-tag instead of latest'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
jobs:
|
||||
build-n-publish:
|
||||
name: Build and publish mem0ai 📦 to npm
|
||||
if: startsWith(github.event.release.tag_name, 'ts-v')
|
||||
if: startsWith(inputs.tag, 'ts-v')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
@@ -16,6 +27,8 @@ jobs:
|
||||
working-directory: mem0-ts
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.tag }}
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
@@ -38,7 +51,7 @@ jobs:
|
||||
|
||||
- name: Publish to npm
|
||||
run: |
|
||||
if [ "${{ github.event.release.prerelease }}" = "true" ]; then
|
||||
if [ "${{ inputs.prerelease }}" = "true" ]; then
|
||||
PREID=$(node -p "require('./package.json').version.split('-')[1].split('.')[0]")
|
||||
npx npm@latest publish --provenance --access public --tag "$PREID"
|
||||
else
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
name: TypeScript SDK CI
|
||||
|
||||
# On PRs this is invoked by ci-gate.yml (the single required check);
|
||||
# push-to-main runs remain standalone.
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'mem0-ts/**'
|
||||
- '.github/workflows/ts-sdk-ci.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'mem0-ts/**'
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
check_changes:
|
||||
|
||||
@@ -1,21 +1,35 @@
|
||||
name: Publish @mem0/vercel-ai-provider 📦 to npm
|
||||
|
||||
# Dispatched by release.yml (Release Router) when a release tagged
|
||||
# vercel-ai-v* is published. Can also be dispatched manually to re-publish
|
||||
# a tag.
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Release tag to build and publish (e.g. vercel-ai-v2.0.7)'
|
||||
required: true
|
||||
type: string
|
||||
prerelease:
|
||||
description: 'Publish under the version preid dist-tag instead of latest'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
jobs:
|
||||
build-n-publish:
|
||||
name: Build and publish @mem0/vercel-ai-provider 📦 to npm
|
||||
if: startsWith(github.event.release.tag_name, 'vercel-ai-v')
|
||||
if: startsWith(inputs.tag, 'vercel-ai-v')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
defaults:
|
||||
run:
|
||||
working-directory: vercel-ai-sdk
|
||||
working-directory: integrations/vercel-ai-sdk
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.tag }}
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
@@ -28,7 +42,7 @@ jobs:
|
||||
node-version: '22'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: vercel-ai-sdk/pnpm-lock.yaml
|
||||
cache-dependency-path: integrations/vercel-ai-sdk/pnpm-lock.yaml
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
@@ -38,7 +52,7 @@ jobs:
|
||||
|
||||
- name: Publish to npm
|
||||
run: |
|
||||
if [ "${{ github.event.release.prerelease }}" = "true" ]; then
|
||||
if [ "${{ inputs.prerelease }}" = "true" ]; then
|
||||
PREID=$(node -p "require('./package.json').version.split('-')[1].split('.')[0]")
|
||||
npx npm@latest publish --provenance --access public --tag "$PREID"
|
||||
else
|
||||
|
||||
@@ -22,17 +22,18 @@ This is a **polyglot monorepo** containing Python and TypeScript packages, CLIs,
|
||||
| `mem0-ts/` | TypeScript SDK (`mem0ai` on npm) — client + OSS memory |
|
||||
| `cli/python/` | Python CLI (`mem0-cli` on PyPI) — Typer-based, entry point `mem0` |
|
||||
| `cli/node/` | Node CLI (`@mem0/cli` on npm) — Commander-based, entry point `mem0` |
|
||||
| `vercel-ai-sdk/` | `@mem0/vercel-ai-provider` — Vercel AI SDK memory provider |
|
||||
| `openclaw/` | `@mem0/openclaw-mem0` — OpenClaw plugin for Claude Code / AI editors |
|
||||
| `integrations/` | **Agent & editor integrations**, one directory per integration (see "Adding a New Integration") |
|
||||
| `integrations/mem0-plugin/` | AI editor plugins (Claude Code, Cursor, Codex) — MCP server connection, lifecycle hooks, skills. Contains nested `.opencode-plugin/` (`@mem0/opencode-plugin`) |
|
||||
| `integrations/openclaw/` | `@mem0/openclaw-mem0` — OpenClaw plugin for Claude Code / AI editors |
|
||||
| `integrations/pi-agent-plugin/` | `@mem0/pi-agent-plugin` — Pi Agent plugin |
|
||||
| `integrations/vercel-ai-sdk/` | `@mem0/vercel-ai-provider` — Vercel AI SDK memory provider |
|
||||
| `server/` | FastAPI REST server for self-hosted Mem0 (Docker: FastAPI + PostgreSQL/pgvector + Neo4j) |
|
||||
| `openmemory/` | Self-hosted memory platform — `api/` (FastAPI + Alembic + MCP server) and `ui/` (Next.js 15 + React 19) |
|
||||
| `mem0-plugin/` | AI editor plugins (Claude Code, Cursor, Codex) — MCP server connection, lifecycle hooks, skills |
|
||||
| `skills/` | Claude Code skill definitions. Reference skills (SDK knowledge, always-on): `mem0/`, `mem0-cli/`, `mem0-vercel-ai-sdk/`. Pipeline skills (run on demand): `mem0-integrate/`, `mem0-test-integration/` |
|
||||
| `skills/` | Claude Code skill definitions. Reference skills (SDK knowledge, always-on): `mem0/`, `mem0-cli/`, `mem0-vercel-ai-sdk/`. Pipeline skills (run on demand): `mem0-integrate/`, `mem0-test-integration/`, `mem0-oss-to-platform/` |
|
||||
| `docs/` | Documentation site (Mintlify) |
|
||||
| `tests/` | Python SDK tests (pytest) |
|
||||
| `evaluation/` | Benchmarking framework — LOCOMO evals, experiment runner, score generation |
|
||||
| `examples/` | Sample projects — demo apps, Chrome extension, multi-agent patterns |
|
||||
| `cookbooks/` | Jupyter notebooks — customer support chatbot, AutoGen integration |
|
||||
| `examples/` | Sample projects & runnable demos — apps, Chrome extension, multi-agent patterns, and Jupyter notebooks (`notebooks/`) |
|
||||
| `pr-reviews/` | Pull request review materials |
|
||||
| `scripts/` | Repo-wide utility scripts (e.g., `check-llms-txt-coverage.py` for docs/llms.txt sync) |
|
||||
|
||||
@@ -49,8 +50,8 @@ mem0 (Python SDK) mem0-ts (TypeScript SDK)
|
||||
|
||||
cli/python/ ──▶ mem0ai (optional, for OSS mode)
|
||||
cli/node/ ──▶ mem0ai (npm, for API calls)
|
||||
vercel-ai-sdk/ ──▶ ai, @ai-sdk/* providers
|
||||
openclaw/ ──▶ mem0ai (npm)
|
||||
integrations/vercel-ai-sdk/ ──▶ ai, @ai-sdk/* providers
|
||||
integrations/openclaw/ ──▶ mem0ai (npm)
|
||||
```
|
||||
|
||||
## Development Setup
|
||||
@@ -73,8 +74,8 @@ pre-commit install # install git hooks
|
||||
# TypeScript packages
|
||||
cd mem0-ts && pnpm install # TS SDK
|
||||
cd cli/node && pnpm install # Node CLI
|
||||
cd vercel-ai-sdk && pnpm install # Vercel AI provider
|
||||
cd openclaw && pnpm install # OpenClaw plugin
|
||||
cd integrations/vercel-ai-sdk && pnpm install # Vercel AI provider
|
||||
cd integrations/openclaw && pnpm install # OpenClaw plugin
|
||||
```
|
||||
|
||||
## Build, Lint, and Test Commands
|
||||
@@ -162,10 +163,10 @@ pnpm run dev # tsx src/index.ts (development)
|
||||
- **Test:** vitest (not jest)
|
||||
- **Framework:** Commander + Chalk + ora + cli-table3
|
||||
|
||||
### Vercel AI SDK Provider (`vercel-ai-sdk/`)
|
||||
### Vercel AI SDK Provider (`integrations/vercel-ai-sdk/`)
|
||||
|
||||
```bash
|
||||
cd vercel-ai-sdk
|
||||
cd integrations/vercel-ai-sdk
|
||||
pnpm install
|
||||
pnpm run build # tsup
|
||||
pnpm run lint # eslint
|
||||
@@ -180,10 +181,10 @@ pnpm run test:node # vitest (node runtime)
|
||||
- **Lint:** ESLint + Prettier
|
||||
- **Test:** jest + vitest (edge/node configs)
|
||||
|
||||
### OpenClaw Plugin (`openclaw/`)
|
||||
### OpenClaw Plugin (`integrations/openclaw/`)
|
||||
|
||||
```bash
|
||||
cd openclaw
|
||||
cd integrations/openclaw
|
||||
pnpm install
|
||||
pnpm run build # tsup
|
||||
pnpm run test # vitest run
|
||||
@@ -342,8 +343,8 @@ make run-openai # OpenAI comparison
|
||||
|---------|--------|-----------|---------------|
|
||||
| `mem0-ts/` | — | Prettier | jest |
|
||||
| `cli/node/` | Biome | Biome | vitest |
|
||||
| `vercel-ai-sdk/` | ESLint | Prettier | jest + vitest |
|
||||
| `openclaw/` | — | — | vitest |
|
||||
| `integrations/vercel-ai-sdk/` | ESLint | Prettier | jest + vitest |
|
||||
| `integrations/openclaw/` | — | — | vitest |
|
||||
|
||||
### Type Checking
|
||||
|
||||
@@ -381,14 +382,14 @@ Model Context Protocol support in multiple places:
|
||||
|
||||
- **Remote:** MCP server at `mcp.mem0.ai`
|
||||
- **Local:** MCP server in `openmemory/api/` (FastAPI-based)
|
||||
- **Plugin:** MCP tools in `mem0-plugin/` — 9 tools: `add_memory`, `search_memories`, `get_memories`, `get_memory`, `update_memory`, `delete_memory`, `delete_all_memories`, `delete_entities`, `list_entities`
|
||||
- **Plugin:** MCP tools in `integrations/mem0-plugin/` — 9 tools: `add_memory`, `search_memories`, `get_memories`, `get_memory`, `update_memory`, `delete_memory`, `delete_all_memories`, `delete_entities`, `list_entities`
|
||||
|
||||
### Plugin & Skills System
|
||||
|
||||
- `mem0-plugin/` provides integrations for Claude Code, Cursor, and Codex via MCP server connections and lifecycle hooks for automatic memory capture.
|
||||
- `integrations/mem0-plugin/` provides integrations for Claude Code, Cursor, and Codex via MCP server connections and lifecycle hooks for automatic memory capture.
|
||||
- `skills/` contains structured skill definitions for AI agents, split into two categories:
|
||||
- **Reference skills** (always-on SDK knowledge): `mem0` (Python + TS SDKs, framework integrations), `mem0-cli` (terminal workflows), `mem0-vercel-ai-sdk` (Vercel AI provider).
|
||||
- **Pipeline skills** (run on demand): `mem0-integrate` wires Mem0 into an existing repo via a TDD pipeline; `mem0-test-integration` verifies what the integrator produced on the same branch. The two are loosely coupled via `.mem0-integration/` artifacts.
|
||||
- **Pipeline skills** (run on demand): `mem0-integrate` wires Mem0 into an existing repo via a TDD pipeline; `mem0-test-integration` verifies what the integrator produced on the same branch (the two are loosely coupled via `.mem0-integration/` artifacts); `mem0-oss-to-platform` migrates an existing project from Mem0 OSS to the hosted Platform SDK (plan, then execute on approval).
|
||||
|
||||
### Adding a New Provider
|
||||
|
||||
@@ -402,23 +403,44 @@ To add a new LLM, embedding, vector store, or reranker provider:
|
||||
6. Add any new dependencies to the appropriate optional group in `pyproject.toml` (never to core `dependencies`)
|
||||
7. Follow the exact pattern of existing providers in the same category — match method signatures, error handling, and config structure
|
||||
|
||||
### Adding a New Integration
|
||||
|
||||
Agent/editor integrations live under `integrations/`. Each is a self-contained directory (its own `package.json`/lockfile, build, and tests). To add one:
|
||||
|
||||
1. Create `integrations/<name>/` and build the integration there.
|
||||
2. If it publishes to a registry, set `repository.directory: "integrations/<name>"` in its `package.json` so npm provenance links to the correct subdirectory.
|
||||
3. Add CI/CD under `.github/workflows/` (`<name>-checks.yml`, `<name>-cd.yml`). Use `integrations/<name>` in `paths:` triggers, `working-directory`, and `cache-dependency-path`. Register the release tag prefix in the `case` block in `release.yml` (keep the bare `v*` arm last). Keep workflow **filenames** stable — npm OIDC trusted publishing is pinned to repo + workflow filename.
|
||||
4. If it is a Claude Code / editor marketplace plugin, register its path in the five `marketplace.json` files (root + `.claude-plugin/`, `.cursor-plugin/`, `.codex-plugin/`, `.agents/plugins/`).
|
||||
5. Document it under `docs/integrations/` and add the page to `docs/docs.json` and `docs/llms.txt`.
|
||||
6. Add rows to the "Key Directories" table and the CI/CD tables in this file.
|
||||
|
||||
## CI/CD
|
||||
|
||||
### CI Workflows (automated testing)
|
||||
|
||||
| Workflow | File | Triggers | Tests |
|
||||
|----------|------|----------|-------|
|
||||
| Python SDK | `ci.yml` | Push to main, PRs on `mem0/`, `tests/`, `pyproject.toml` | Ruff lint + pytest on Python 3.10, 3.11, 3.12 |
|
||||
| TypeScript SDK | `ts-sdk-ci.yml` | Push to main, PRs on `mem0-ts/` | Prettier + build + jest on Node 20, 22 |
|
||||
| Python CLI | `cli-python-ci.yml` | Push to `cli/python/`, PRs, manual | Ruff lint + pytest + hatch build on Python 3.10, 3.11, 3.12 |
|
||||
| Node CLI | `cli-node-ci.yml` | Push to `cli/node/`, PRs, manual | Biome lint + tsc + vitest + tsup build on Node 20, 22 |
|
||||
| OpenClaw | `openclaw-checks.yml` | Push to `openclaw/`, PRs, manual | tsc + vitest (with Codecov) + tsup build on Node 20, 22 |
|
||||
| OpenCode Plugin | `opencode-plugin-checks.yml` | Push to `mem0-plugin/.opencode-plugin/`, PRs, manual | Bun: tsc type-check + build + dist artifact check |
|
||||
PR testing is orchestrated by a single entry point: **`ci-gate.yml` (CI Gate)** runs on every PR, detects which packages changed, and invokes only the relevant package workflows below as reusable workflows (`workflow_call`). Its final **`CI Gate`** job aggregates the results (skipped pipelines pass; failed or cancelled ones fail) and is the **only status check that needs to be required** in branch protection. Package workflows keep their own push-to-main and manual triggers; their `pull_request` triggers moved into the gate's path filters.
|
||||
|
||||
| Workflow | File | Standalone Triggers | Tests |
|
||||
|----------|------|---------------------|-------|
|
||||
| CI Gate | `ci-gate.yml` | All PRs | Routes to and aggregates the workflows below |
|
||||
| Python SDK | `ci.yml` | Push to main | Ruff lint + pytest on Python 3.10, 3.11, 3.12 |
|
||||
| TypeScript SDK | `ts-sdk-ci.yml` | Push to main (on `mem0-ts/`) | Prettier + build + jest on Node 20, 22 |
|
||||
| Python CLI | `cli-python-ci.yml` | Push to main (on `cli/python/`), manual | Ruff lint + pytest + hatch build on Python 3.10, 3.11, 3.12 |
|
||||
| Node CLI | `cli-node-ci.yml` | Push to main (on `cli/node/`), manual | Biome lint + tsc + vitest + tsup build on Node 20, 22 |
|
||||
| OpenClaw | `openclaw-checks.yml` | Push to main (on `integrations/openclaw/`), manual | tsc + vitest (with Codecov) + tsup build on Node 20, 22 |
|
||||
| OpenCode Plugin | `opencode-plugin-checks.yml` | Push to main (on `integrations/mem0-plugin/.opencode-plugin/`), manual | Bun: tsc type-check + build + dist artifact check |
|
||||
| Pi Agent Plugin | `pi-agent-plugin-checks.yml` | Push to main (on `integrations/pi-agent-plugin/`), manual | tsc + vitest + tsup build (dist artifact check) on Node 20, 22 |
|
||||
| docs llms.txt | `docs-llms-txt-check.yml` | Manual | `docs/llms.txt` coverage check |
|
||||
|
||||
When adding a new package CI workflow: give it `workflow_call` (plus `push`/`workflow_dispatch` as needed, but no `pull_request` trigger), then register it in `ci-gate.yml` — a path filter under the `changes` job, a call job, and an entry in the gate job's `needs` list.
|
||||
|
||||
### CD Workflows (automated publishing)
|
||||
|
||||
Publishing is routed through a single entry point: **`release.yml` (Release Router)** is the only workflow that listens to `release: published` events. It matches the release tag prefix and dispatches the corresponding package workflow via `workflow_dispatch`, so each release produces exactly one routed run (no skipped runs from the other pipelines).
|
||||
|
||||
| Workflow | File | Tag Prefix | Target |
|
||||
|----------|------|------------|--------|
|
||||
| Release Router | `release.yml` | (all releases) | dispatches the matching workflow below |
|
||||
| Python SDK | `cd.yml` | `v*` | PyPI (`mem0ai`) |
|
||||
| TypeScript SDK | `ts-sdk-cd.yml` | `ts-v*` | npm (`mem0ai`) |
|
||||
| Python CLI | `cli-python-cd.yml` | `cli-v*` | PyPI (`mem0-cli`) |
|
||||
@@ -426,9 +448,13 @@ To add a new LLM, embedding, vector store, or reranker provider:
|
||||
| Vercel AI SDK | `vercel-ai-cd.yml` | `vercel-ai-v*` | npm (`@mem0/vercel-ai-provider`) |
|
||||
| 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`) |
|
||||
|
||||
- Package CD workflows are `workflow_dispatch`-only (inputs: `tag`, `prerelease`); they check out and build the given tag. Registry trusted-publisher settings stay pinned to each package's own workflow filename.
|
||||
- All publishing uses **OIDC trusted publishing** — no tokens or secrets required.
|
||||
- First publish of a new npm package must be done manually; OIDC works for subsequent versions.
|
||||
- To re-publish a release (e.g. after a registry settings fix), do **not** delete/recreate the GitHub release — manually dispatch the package workflow instead: `gh workflow run <package>-cd.yml --ref refs/tags/<tag> -f tag=<tag>`.
|
||||
- When adding a new package: add its CD workflow (`workflow_dispatch` with `tag`/`prerelease` inputs), then register its tag prefix in the `case` block in `release.yml`. Keep the bare `v*` arm last.
|
||||
|
||||
### Utility Workflows
|
||||
|
||||
|
||||
@@ -1026,7 +1026,8 @@ def get_user_preferences(user_id: str):
|
||||
### AutoGen Integration
|
||||
|
||||
```python
|
||||
from cookbooks.helper.mem0_teachability import Mem0Teachability
|
||||
# Mem0Teachability lives in examples/notebooks/helper/ — see examples/notebooks/mem0-autogen.ipynb
|
||||
from helper.mem0_teachability import Mem0Teachability
|
||||
from mem0 import Memory
|
||||
|
||||
# Add memory capability to AutoGen agents
|
||||
|
||||
@@ -186,9 +186,10 @@ npx skills add https://github.com/mem0ai/mem0 --skill mem0-vercel-ai-sdk
|
||||
```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
|
||||
```
|
||||
|
||||
Use `/mem0-integrate` to wire Mem0 into an existing repo via a test-first pipeline, then `/mem0-test-integration` to verify. See the [skills catalog](./skills/) or [Vibecoding with Mem0](https://docs.mem0.ai/vibecoding) for the full picture.
|
||||
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.
|
||||
|
||||
### Basic Usage
|
||||
|
||||
|
||||
@@ -5,6 +5,21 @@ All notable changes to `@mem0/cli` are documented here.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.2.8] — 2026-06-01
|
||||
|
||||
### Security
|
||||
|
||||
- Pinned transitive dependencies via pnpm overrides to remediate high-severity CVEs:
|
||||
- `jws` → 4.0.1 (CVE-2025-65945)
|
||||
- `langsmith` → ^0.6.0 (CVE-2026-45134)
|
||||
- `tar-fs` → ^2.1.4 (CVE-2025-48387, CVE-2025-59343)
|
||||
- `picomatch` → ^2.3.2 (CVE-2026-33671)
|
||||
- `minimatch` → ^3.1.3 / ^5.1.8 / ^9.0.7 (CVE-2026-27903, CVE-2026-27904, CVE-2026-26996)
|
||||
- `path-to-regexp` → ^8.4.0 (CVE-2026-4926)
|
||||
- `rollup` → ^4.59.0 (CVE-2026-27606)
|
||||
- `glob` → ^10.5.0 (CVE-2025-64756)
|
||||
- `@modelcontextprotocol/sdk` → ^1.25.4 (CVE-2025-66414, CVE-2026-0621)
|
||||
|
||||
## [0.2.7] — 2026-05-20
|
||||
|
||||
### Added
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@mem0/cli",
|
||||
"version": "0.2.7",
|
||||
"version": "0.2.8",
|
||||
"description": "The official CLI for mem0 — the memory layer for AI agents",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
@@ -40,7 +40,8 @@
|
||||
"typescript": "^5.4.0",
|
||||
"tsup": "^8.0.0",
|
||||
"tsx": "^4.7.0",
|
||||
"vitest": "^1.5.0",
|
||||
"vite": "^6.0.0",
|
||||
"vitest": "^4.1.0",
|
||||
"@biomejs/biome": "^1.7.0",
|
||||
"@types/node": "^20.0.0"
|
||||
}
|
||||
|
||||
Generated
+364
-493
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,13 @@
|
||||
packages:
|
||||
- '.'
|
||||
|
||||
onlyBuiltDependencies:
|
||||
- "@biomejs/biome"
|
||||
- esbuild
|
||||
|
||||
overrides:
|
||||
jws@4.0.0: 4.0.1
|
||||
langsmith@<0.6.0: ^0.6.0
|
||||
tar-fs@>=2.0.0 <2.1.4: ^2.1.4
|
||||
picomatch@<2.3.2: ^2.3.2
|
||||
"postcss@<8.5.10": ">=8.5.10"
|
||||
@@ -8,4 +8,10 @@ export default defineConfig({
|
||||
define: {
|
||||
__CLI_VERSION__: JSON.stringify(pkg.version),
|
||||
},
|
||||
test: {
|
||||
// Integration tests spawn the CLI via `npx tsx` (15s subprocess
|
||||
// timeout); the first spawn in a file pays a cold-start cost that can
|
||||
// exceed vitest's 5s default on CI runners.
|
||||
testTimeout: 30_000,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -32,12 +32,13 @@ def _run(args: list[str], home_dir: str | None = None) -> subprocess.CompletedPr
|
||||
if key.startswith("MEM0_"):
|
||||
del env[key]
|
||||
env.pop("FORCE_COLOR", None)
|
||||
env["PYTHONIOENCODING"] = "utf-8"
|
||||
if home_dir:
|
||||
env["HOME"] = home_dir
|
||||
result = subprocess.run(
|
||||
[sys.executable, "-m", "mem0_cli", *args],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
env=env,
|
||||
timeout=15,
|
||||
)
|
||||
@@ -99,12 +100,13 @@ class TestArgvPreprocessing:
|
||||
result = subprocess.run(
|
||||
[sys.executable, "-m", "mem0_cli", "init", "--agent"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
env={
|
||||
**{k: v for k, v in os.environ.items() if not k.startswith("MEM0_")},
|
||||
"HOME": clean_home,
|
||||
"MEM0_BASE_URL": "http://127.0.0.1:1", # blackhole
|
||||
"FORCE_COLOR": "0",
|
||||
"PYTHONIOENCODING": "utf-8",
|
||||
},
|
||||
timeout=15,
|
||||
)
|
||||
@@ -133,12 +135,13 @@ class TestJsonEnvelopeParity:
|
||||
result = subprocess.run(
|
||||
[sys.executable, "-m", "mem0_cli", "init", "--agent", "--json"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
env={
|
||||
**{k: v for k, v in os.environ.items() if not k.startswith("MEM0_")},
|
||||
"HOME": clean_home,
|
||||
"MEM0_BASE_URL": "http://127.0.0.1:1",
|
||||
"FORCE_COLOR": "0",
|
||||
"PYTHONIOENCODING": "utf-8",
|
||||
},
|
||||
timeout=15,
|
||||
)
|
||||
|
||||
@@ -49,6 +49,7 @@ def _run(
|
||||
if key.startswith("MEM0_"):
|
||||
del env[key]
|
||||
env.pop("FORCE_COLOR", None)
|
||||
env["PYTHONIOENCODING"] = "utf-8"
|
||||
if home_dir:
|
||||
env["HOME"] = home_dir
|
||||
if env_override:
|
||||
@@ -56,7 +57,7 @@ def _run(
|
||||
result = subprocess.run(
|
||||
[sys.executable, "-m", "mem0_cli", *args],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
env=env,
|
||||
)
|
||||
return subprocess.CompletedProcess(
|
||||
|
||||
@@ -8,8 +8,8 @@ from io import StringIO
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from click.exceptions import Exit as ClickExit
|
||||
from rich.console import Console
|
||||
from typer import Exit as TyperExit
|
||||
|
||||
from mem0_cli.commands.config_cmd import (
|
||||
cmd_config_get,
|
||||
@@ -181,7 +181,7 @@ class TestAddCommand:
|
||||
patch("mem0_cli.commands.memory.console", console),
|
||||
patch("mem0_cli.commands.memory.err_console", err_console),
|
||||
patch("mem0_cli.commands.memory._stdin_is_piped", return_value=False),
|
||||
pytest.raises((SystemExit, ClickExit)),
|
||||
pytest.raises((SystemExit, TyperExit)),
|
||||
):
|
||||
cmd_add(
|
||||
mock_backend,
|
||||
@@ -206,7 +206,7 @@ class TestAddCommand:
|
||||
with (
|
||||
patch("mem0_cli.commands.memory.console", console),
|
||||
patch("mem0_cli.commands.memory.err_console", err_console),
|
||||
pytest.raises((SystemExit, ClickExit)),
|
||||
pytest.raises((SystemExit, TyperExit)),
|
||||
):
|
||||
cmd_add(
|
||||
mock_backend,
|
||||
@@ -764,7 +764,7 @@ class TestImportCommand:
|
||||
with (
|
||||
patch("mem0_cli.commands.utils.console", console),
|
||||
patch("mem0_cli.commands.utils.err_console", err_console),
|
||||
pytest.raises((SystemExit, ClickExit)),
|
||||
pytest.raises((SystemExit, TyperExit)),
|
||||
):
|
||||
cmd_import(mock_backend, "/nonexistent/file.json", user_id=None, agent_id=None)
|
||||
|
||||
@@ -801,7 +801,7 @@ class TestEntitiesListCommand:
|
||||
with (
|
||||
patch("mem0_cli.commands.entities.console", console),
|
||||
patch("mem0_cli.commands.entities.err_console", err_console),
|
||||
pytest.raises((SystemExit, ClickExit)),
|
||||
pytest.raises((SystemExit, TyperExit)),
|
||||
):
|
||||
cmd_entities_list(mock_backend, "invalid", output="table")
|
||||
|
||||
@@ -944,7 +944,7 @@ class TestEntitiesDeleteCommand:
|
||||
with (
|
||||
patch("mem0_cli.commands.entities.console", console),
|
||||
patch("mem0_cli.commands.entities.err_console", err_console),
|
||||
pytest.raises((SystemExit, ClickExit)),
|
||||
pytest.raises((SystemExit, TyperExit)),
|
||||
):
|
||||
cmd_entities_delete(
|
||||
mock_backend,
|
||||
@@ -1308,7 +1308,7 @@ class TestAgentMode:
|
||||
patch("mem0_cli.commands.memory.console", console),
|
||||
patch("mem0_cli.commands.memory.err_console", err_console),
|
||||
patch("sys.stdout", captured_stdout),
|
||||
pytest.raises((SystemExit, ClickExit)),
|
||||
pytest.raises((SystemExit, TyperExit)),
|
||||
):
|
||||
cmd_get(mock_backend, "bad-id", output="text")
|
||||
|
||||
|
||||
@@ -67,7 +67,8 @@ class TestConfig:
|
||||
from mem0_cli.config import CONFIG_FILE
|
||||
|
||||
mode = os.stat(CONFIG_FILE).st_mode & 0o777
|
||||
assert mode == 0o600
|
||||
if os.name != "nt":
|
||||
assert mode == 0o600
|
||||
|
||||
def test_defaults_save_and_load(self, isolate_config):
|
||||
config = Mem0Config()
|
||||
|
||||
@@ -4,6 +4,39 @@ description: "Release notes for the OpenClaw plugin and agent harness."
|
||||
mode: "wide"
|
||||
---
|
||||
|
||||
<Update label="2026-06-12" description="v1.0.13">
|
||||
|
||||
**Fixes:**
|
||||
- **Custom categories payload:** `customCategories` (a `Record<string, string>` map) is now converted via the new `customCategoryMapToList()` helper into the `Array<Record<string, string>>` shape the Mem0 SDK expects on `add` calls — previously the raw object was passed as `custom_categories` and silently ignored ([#5345](https://github.com/mem0ai/mem0/pull/5345))
|
||||
- **Skip runtime setup during metadata registration:** `register()` now detects `registrationMode === "cli-metadata"`, registers only the CLI commands, and returns early — avoiding backend initialization, service/tool registration, and hook installation during OpenClaw's metadata-only registration pass ([#5383](https://github.com/mem0ai/mem0/pull/5383))
|
||||
|
||||
**Security:**
|
||||
- Bumped `mem0ai` from `3.0.3` to `3.0.7` (latest Node SDK) — includes the transitive axios CVE remediation shipped in `3.0.6` ([#5460](https://github.com/mem0ai/mem0/pull/5460))
|
||||
- Added pnpm override `uuid@<11.1.1` → `>=11.1.1` to resolve an open MEDIUM Dependabot alert ([#5489](https://github.com/mem0ai/mem0/pull/5489))
|
||||
|
||||
**Improvements:**
|
||||
- **Repo consolidation:** Plugin moved from repo-root `openclaw/` to `integrations/openclaw/`; `package.json` `repository.directory` updated to match so npm provenance links to the correct subdirectory ([#5491](https://github.com/mem0ai/mem0/pull/5491))
|
||||
|
||||
**Tests:**
|
||||
- Added `customCategoryMapToList` unit tests and a `PlatformProvider` test asserting `custom_categories` is passed to the Mem0 SDK as a list ([#5345](https://github.com/mem0ai/mem0/pull/5345))
|
||||
- Added a regression test asserting `cli-metadata` registration registers only CLI commands and triggers no runtime side effects ([#5383](https://github.com/mem0ai/mem0/pull/5383))
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-06-02" description="v1.0.12">
|
||||
|
||||
**Docs:**
|
||||
- **Agent Mode onboarding:** README now documents an autonomous setup path for AI agents — `mem0 init --agent --json` mints an evaluation Mem0 API key with no email, OTP, or browser and exports it as `MEM0_API_KEY` for `openclaw mem0 init`; a human owner can later run `mem0 init --email <email>` to claim ownership without disrupting the agent ([#5123](https://github.com/mem0ai/mem0/pull/5123))
|
||||
|
||||
**Security:**
|
||||
- Added pnpm overrides to remediate advisories in transitive dependencies: `langsmith@<0.6.0` → `^0.6.0`, `picomatch@<2.3.2` → `^2.3.2`, `vite` → `^8.0.5`, and `@qdrant/js-client-rest` → `^1.18.0` ([#5294](https://github.com/mem0ai/mem0/pull/5294))
|
||||
|
||||
**Dependencies:**
|
||||
- Bumped `mem0ai` from `3.0.2` to `3.0.3` ([#5212](https://github.com/mem0ai/mem0/pull/5212))
|
||||
- Bumped dev dependencies `@vitest/coverage-v8` and `vitest` from `^4.0.18` to `^4.1.7`; added `vite@^8.0.5` and `@qdrant/js-client-rest@^1.18.0` ([#5294](https://github.com/mem0ai/mem0/pull/5294))
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-04-29" description="v1.0.11">
|
||||
|
||||
**New Features:**
|
||||
|
||||
@@ -7,6 +7,42 @@ mode: "wide"
|
||||
<Tabs>
|
||||
<Tab title="Python">
|
||||
|
||||
<Update label="2026-06-13" description="v2.0.6">
|
||||
|
||||
**New Features:**
|
||||
- **Memory:** Add a contextual OSS-to-Platform notices system that surfaces occasional, situation-aware messages (first run, scale/performance thresholds, slow queries, and when temporal/decay features are relevant) pointing to the corresponding Mem0 Platform capabilities; disable via `MEM0_TELEMETRY=false` ([#5494](https://github.com/mem0ai/mem0/pull/5494))
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Memory:** Prevent a crash in `parse_vision_messages` when vision support is disabled ([#5487](https://github.com/mem0ai/mem0/pull/5487))
|
||||
- **Vector Stores:** Expose the `https` option on the Qdrant vector store configuration so TLS endpoints can be targeted explicitly ([#5380](https://github.com/mem0ai/mem0/pull/5380))
|
||||
- **Vector Stores:** Use valid S3 Vectors entity index names, fixing index operations that failed on invalid names ([#5416](https://github.com/mem0ai/mem0/pull/5416))
|
||||
- **Vector Stores:** Fix `search()` crashing with a `TypeError` in the LangChain vector store when a result score is `None` ([#5072](https://github.com/mem0ai/mem0/pull/5072))
|
||||
- **Vector Stores:** Use `is not None` instead of a truthiness check for vector/payload in the PGVector `update()` path, so valid empty/zero values are no longer skipped ([#5488](https://github.com/mem0ai/mem0/pull/5488))
|
||||
- **Vector Stores:** Index the Valkey `memory` field as `TEXT` rather than `TAG` so full-text search behaves correctly ([#5443](https://github.com/mem0ai/mem0/pull/5443))
|
||||
- **Vector Stores:** Implement `$not` filter support in the ChromaDB vector store ([#5485](https://github.com/mem0ai/mem0/pull/5485))
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-06-10" description="v2.0.5">
|
||||
|
||||
**New Features:**
|
||||
- **Memory:** Warn at init time when hybrid/BM25 search silently degrades to semantic-only because the configured vector store does not implement `keyword_search`. Affected stores: Chroma, FAISS, Cassandra, LangChain, Neptune Analytics, S3 Vectors, Supabase, TurboPuffer, Valkey ([#5444](https://github.com/mem0ai/mem0/pull/5444))
|
||||
- **Memory:** Add opt-in `explain=True` parameter to `Memory.search()` and `AsyncMemory.search()`. When enabled, each result includes a `score_breakdown` dict with `semantic`, `keyword` (normalized BM25), `entity_boost`, and `temporal_boost` signals so callers can understand and tune retrieval ranking ([#5102](https://github.com/mem0ai/mem0/pull/5102))
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Vector Stores:** Normalize similarity scores to `[0, 1]` (higher = better) consistently across all backends. 11 adapters previously returned raw distance metrics (lower = better) — FAISS, Chroma, Milvus, Redis, Cassandra, PGVector, S3 Vectors, Supabase, Valkey, Azure MySQL, and Vertex AI Vector Search — causing incorrect ranking in multi-store setups ([#5391](https://github.com/mem0ai/mem0/pull/5391))
|
||||
- **Memory:** Parallelize entity boost searches in `Memory.search()` and `AsyncMemory.search()`. Previously up to 8 entities were embedded and queried sequentially (16 serial round-trips with remote embedders); all entity lookups now run concurrently, eliminating multi-second latency on entity-rich queries ([#5377](https://github.com/mem0ai/mem0/pull/5377))
|
||||
- **Memory:** Reject empty or whitespace-only queries in `Memory.search()`, `AsyncMemory.search()`, `MemoryClient.search()`, and `AsyncMemoryClient.search()` before any embedding or API call is made. Also strips leading/trailing whitespace from valid queries ([#5258](https://github.com/mem0ai/mem0/pull/5258))
|
||||
- **LLMs:** Add `is_reasoning_model: Optional[bool]` override to `BaseLlmConfig` (surfaced on `OpenAILlmConfig` and `AzureOpenAILlmConfig`). Fixes silent zero-extraction when using Azure deployments with versioned `gpt-5.x` names that the automatic name-based heuristic cannot recognize ([#5327](https://github.com/mem0ai/mem0/pull/5327))
|
||||
- **LLMs:** Fix xAI LLM provider: add `XAIConfig` with `xai_base_url`, forward `tools`/`tool_choice` in `generate_response()`, and parse `tool_calls` in the response. Previously the provider raised `AttributeError` at init and silently dropped tool results ([#5190](https://github.com/mem0ai/mem0/pull/5190))
|
||||
- **Vector Stores:** Fix PGVector `ConnectionPool` hang in Docker Compose environments where the app container starts before Postgres is DNS-resolvable — switched to `open=False` to avoid blocking constructor or silent zombie pool ([#5155](https://github.com/mem0ai/mem0/pull/5155))
|
||||
- **Vector Stores:** Fix PGVector `sslmode` handling for PostgreSQL URIs — the `sslmode` query parameter is now correctly extracted and forwarded when building the async connection pool ([#5308](https://github.com/mem0ai/mem0/pull/5308))
|
||||
- **Vector Stores:** Fix S3 Vectors `list()` not applying metadata filters — filtering is now done client-side after fetching, with pagination preserved and `top_k` applied after filtering to prevent pre-truncation of matching rows ([#5018](https://github.com/mem0ai/mem0/pull/5018))
|
||||
- **Vector Stores:** Fix Upstash Vector `search()` routing all queries to the default namespace — `namespace` is now passed as a top-level keyword argument to `query_many()` instead of inside the per-query dict where it was silently ignored ([#5202](https://github.com/mem0ai/mem0/pull/5202))
|
||||
- **Core:** Replace mutable default arguments with `None` sentinels in embedder configs and the proxy module, preventing cross-request state contamination ([#5302](https://github.com/mem0ai/mem0/pull/5302))
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-05-27" description="v2.0.4">
|
||||
|
||||
**New Features:**
|
||||
@@ -939,6 +975,38 @@ See the [OSS v1 to v2 migration guide](https://docs.mem0.ai/migration/oss-v1-to-
|
||||
</Tab>
|
||||
|
||||
<Tab title="TypeScript">
|
||||
|
||||
<Update label="2026-06-13" description="v3.0.8">
|
||||
|
||||
**New Features:**
|
||||
- **Memory:** Add a contextual OSS-to-Platform notices system that surfaces occasional, situation-aware messages (first run, scale/performance thresholds, slow queries, and when temporal/decay features are relevant) pointing to the corresponding Mem0 Platform capabilities; disable via `MEM0_TELEMETRY=false` ([#5494](https://github.com/mem0ai/mem0/pull/5494))
|
||||
|
||||
**Security:**
|
||||
- **Dependencies:** Upgrade `@langchain/community` to `^1.1.18` to remediate CVE-2026-27795 and CVE-2026-26019 ([#5510](https://github.com/mem0ai/mem0/pull/5510))
|
||||
- **Dependencies:** Resolve all open MEDIUM Dependabot alerts via pnpm overrides ([#5489](https://github.com/mem0ai/mem0/pull/5489))
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-06-10" description="v3.0.7">
|
||||
|
||||
**New Features:**
|
||||
- **Embeddings:** Add `LMStudioEmbedding` provider for local embeddings via the LM Studio server ([#5377](https://github.com/mem0ai/mem0/pull/5377))
|
||||
- **Memory:** Add opt-in `explain: true` option to `Memory.search()`. When enabled, each result includes a `scoreBreakdown` object with `semantic`, `keyword`, `entityBoost`, and `temporalBoost` fields so callers can inspect and tune retrieval ranking ([#5102](https://github.com/mem0ai/mem0/pull/5102))
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Memory:** Parallelize entity boost searches in `Memory.search()`. All entity embed + store lookups now run concurrently instead of sequentially, eliminating multi-second latency on entity-rich queries with remote embedding providers ([#5377](https://github.com/mem0ai/mem0/pull/5377))
|
||||
- **Vector Stores:** Normalize similarity scores to `[0, 1]` (higher = better) — fixed score inversion in the Redis vector store adapter ([#5391](https://github.com/mem0ai/mem0/pull/5391))
|
||||
- **Embeddings:** Request `encoding_format: "float"` from the OpenAI embedder in both `embed()` and `embedBatch()`. Fixes incorrect vector dimensions when using OpenAI-compatible proxies that default to base64 encoding ([#5170](https://github.com/mem0ai/mem0/pull/5170))
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-06-01" description="v3.0.6">
|
||||
|
||||
**Security:**
|
||||
- **Dependencies:** Bumped `axios` to `^1.16.0` to remediate high-severity prototype-pollution CVEs (credential theft, MITM, DoS). Pinned transitive dependencies via pnpm overrides: `jws` → 4.0.1 (CVE-2025-65945), `langsmith` → ^0.6.0 (CVE-2026-45134), `tar-fs` → ^2.1.4 (CVE-2025-48387, CVE-2025-59343), `picomatch` → ^2.3.2 (CVE-2026-33671), `minimatch` → ^3.1.3 / ^5.1.8 / ^9.0.7 (CVE-2026-27903, CVE-2026-27904, CVE-2026-26996), `path-to-regexp` → ^8.4.0 (CVE-2026-4926), `rollup` → ^4.59.0 (CVE-2026-27606), `glob` → ^10.5.0 (CVE-2025-64756), `@modelcontextprotocol/sdk` → ^1.25.4 (CVE-2025-66414, CVE-2026-0621)
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-05-27" description="v3.0.5">
|
||||
|
||||
**New Features:**
|
||||
@@ -1352,6 +1420,13 @@ See the [TypeScript SDK migration guide](https://docs.mem0.ai/migration/ts-v2-to
|
||||
|
||||
<Tab title="CLI">
|
||||
|
||||
<Update label="2026-06-01" description="Node v0.2.8">
|
||||
|
||||
**Security:**
|
||||
- **Dependencies:** Pinned transitive dependencies via pnpm overrides to remediate high-severity CVEs: `jws` → 4.0.1 (CVE-2025-65945), `langsmith` → ^0.6.0 (CVE-2026-45134), `tar-fs` → ^2.1.4 (CVE-2025-48387, CVE-2025-59343), `picomatch` → ^2.3.2 (CVE-2026-33671), `minimatch` → ^3.1.3 / ^5.1.8 / ^9.0.7 (CVE-2026-27903, CVE-2026-27904, CVE-2026-26996), `path-to-regexp` → ^8.4.0 (CVE-2026-4926), `rollup` → ^4.59.0 (CVE-2026-27606), `glob` → ^10.5.0 (CVE-2025-64756), `@modelcontextprotocol/sdk` → ^1.25.4 (CVE-2025-66414, CVE-2026-0621)
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-05-16" description="Python v0.2.6 / Node v0.2.6">
|
||||
|
||||
**Bug Fixes:**
|
||||
@@ -1468,6 +1543,43 @@ A full-featured command-line interface for Mem0, available in both Python and No
|
||||
|
||||
<Tab title="Plugins">
|
||||
|
||||
<Update label="2026-06-01" description="openclaw-mem0 v1.0.12">
|
||||
|
||||
**Security:**
|
||||
- **Dependencies:** Pinned transitive dependencies via pnpm overrides to remediate high-severity CVEs: `protobufjs` → ^7.5.5, `vite` → ^8.0.5, `langsmith` → ^0.6.0 (CVE-2026-45134), `picomatch` → ^2.3.2 (CVE-2026-33671), `@qdrant/js-client-rest` → ^1.18.0
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-06-10" description="Vercel AI SDK v3.0.0">
|
||||
|
||||
**Major Release** — Migrated to Vercel AI SDK v6 (`LanguageModelV3` / `ProviderV3`) and Mem0 v3 API.
|
||||
|
||||
**Breaking Changes:**
|
||||
- **AI SDK v6:** Upgraded from AI SDK v5 (`LanguageModelV2`) to v6 (`LanguageModelV3`). Users must upgrade `ai` to `^6.0.199` and all `@ai-sdk/*` provider packages to `^3.x` ([#4741](https://github.com/mem0ai/mem0/pull/4741))
|
||||
- **Mem0 v3 API:** Memory endpoints migrated from `/v1/memories/` and `/v2/memories/search/` to `/v3/memories/add/` and `/v3/memories/search/`. Entity IDs (`user_id`, `agent_id`, `run_id`) now go inside the `filters` object for search requests ([#4741](https://github.com/mem0ai/mem0/pull/4741))
|
||||
- **Graph memory removed:** All `enable_graph`, graph prompts, and relation-extraction code removed. Graph memory is now a project-level setting on the Platform ([#4741](https://github.com/mem0ai/mem0/pull/4741))
|
||||
- **Deprecated params removed:** `org_id`, `project_id`, `org_name`, `project_name`, `output_format`, `filter_memories`, `async_mode`, `enable_graph`, `version`, `api_version` removed from `Mem0ConfigSettings` ([#4741](https://github.com/mem0ai/mem0/pull/4741))
|
||||
|
||||
**New Features:**
|
||||
- **V3 provider contract:** `specificationVersion: 'v3'`, `supportedUrls` property, V3 content array in `doGenerate`, V3 stream lifecycle events in `doStream` ([#4741](https://github.com/mem0ai/mem0/pull/4741))
|
||||
- **Mem0 source in responses:** Memories are attached as a `source` in `generateText`/`streamText` responses with `providerMetadata.mem0.memories` for programmatic access ([#4741](https://github.com/mem0ai/mem0/pull/4741))
|
||||
|
||||
**Bug Fixes:**
|
||||
- **Async memory storage:** `addMemories` is now properly `await`ed — memories no longer silently fail to store ([#4741](https://github.com/mem0ai/mem0/pull/4741))
|
||||
- **Prompt mutation:** Prompt array is now cloned before injecting memory context, preventing side effects on the caller's array ([#4741](https://github.com/mem0ai/mem0/pull/4741))
|
||||
- **Null guard on content:** `doGenerate` guards against null `content` from upstream providers ([#4741](https://github.com/mem0ai/mem0/pull/4741))
|
||||
- **Stream response:** `doStream` now returns the full `LanguageModelV3StreamResult` object preserving all V3 fields ([#4741](https://github.com/mem0ai/mem0/pull/4741))
|
||||
- **Response normalization:** `getMemories` and `retrieveMemories` now handle both array and `{results: [...]}` envelope responses from the v3 API ([#4741](https://github.com/mem0ai/mem0/pull/4741))
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-06-01" description="Vercel AI SDK v2.0.6">
|
||||
|
||||
**Security:**
|
||||
- **Dependencies:** Pinned transitive dependencies via pnpm overrides to remediate high-severity CVEs: `glob` → ^10.5.0 (CVE-2025-64756), `minimatch` → ^3.1.3 / ^5.1.8 / ^9.0.7 (CVE-2026-27903, CVE-2026-27904, CVE-2026-26996), `picomatch` → ^2.3.2 (CVE-2026-33671), `rollup` → ^4.59.0 (CVE-2026-27606)
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-04-02" description="mem0-plugin v1.0.0">
|
||||
|
||||
**Mem0 Plugin for Claude Code, Cursor, and Codex**
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
---
|
||||
title: "Neon"
|
||||
description: "Use Neon as a vector store in Mem0, powered by PostgreSQL and pgvector."
|
||||
---
|
||||
|
||||
Use [Neon](https://neon.com/) as a vector store in Mem0, powered by PostgreSQL and the
|
||||
[pgvector extension](https://neon.com/docs/extensions/pgvector).
|
||||
|
||||
Neon is a serverless Postgres platform. Since Mem0 supports Postgres through the
|
||||
`pgvector` provider, Neon can be used with a standard Postgres connection string.
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeGroup>
|
||||
```python Python
|
||||
import os
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from mem0 import Memory
|
||||
|
||||
load_dotenv()
|
||||
|
||||
config = {
|
||||
"vector_store": {
|
||||
"provider": "pgvector",
|
||||
"config": {
|
||||
"connection_string": os.environ["DATABASE_URL"],
|
||||
"collection_name": "memories",
|
||||
"embedding_model_dims": 1536,
|
||||
"hnsw": True,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
m = Memory.from_config(config)
|
||||
messages = [
|
||||
{"role": "user", "content": "I'm planning to watch a movie tonight. Any recommendations?"},
|
||||
{"role": "assistant", "content": "How about thriller movies? They can be quite engaging."},
|
||||
{"role": "user", "content": "I'm not a big fan of thriller movies but I love sci-fi movies."},
|
||||
{"role": "assistant", "content": "Got it! I'll avoid thriller recommendations and suggest sci-fi movies in the future."},
|
||||
]
|
||||
m.add(messages, user_id="alice", metadata={"category": "movies"})
|
||||
|
||||
results = m.search(
|
||||
"What movies should I recommend?",
|
||||
filters={"user_id": "alice"},
|
||||
)
|
||||
|
||||
print(results)
|
||||
```
|
||||
|
||||
```typescript TypeScript
|
||||
import "dotenv/config";
|
||||
import { Memory } from "mem0ai/oss";
|
||||
|
||||
const databaseUrl = new URL(process.env.DATABASE_URL!);
|
||||
|
||||
const m = new Memory({
|
||||
vectorStore: {
|
||||
provider: "pgvector",
|
||||
config: {
|
||||
user: decodeURIComponent(databaseUrl.username),
|
||||
password: decodeURIComponent(databaseUrl.password),
|
||||
host: databaseUrl.hostname,
|
||||
port: Number(databaseUrl.port || 5432),
|
||||
dbname: databaseUrl.pathname.slice(1) || "neondb",
|
||||
collectionName: "memories",
|
||||
dimension: 1536,
|
||||
embeddingModelDims: 1536,
|
||||
hnsw: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const messages = [
|
||||
{ role: "user" as const, content: "I'm planning to watch a movie tonight. Any recommendations?" },
|
||||
{ role: "assistant" as const, content: "How about thriller movies? They can be quite engaging." },
|
||||
{ role: "user" as const, content: "I'm not a big fan of thriller movies but I love sci-fi movies." },
|
||||
{ role: "assistant" as const, content: "Got it! I'll avoid thriller recommendations and suggest sci-fi movies in the future." },
|
||||
];
|
||||
|
||||
await m.add(messages, {
|
||||
userId: "alice",
|
||||
metadata: { category: "movies" },
|
||||
});
|
||||
|
||||
const results = await m.search("What movies should I recommend?", {
|
||||
filters: { user_id: "alice" },
|
||||
});
|
||||
|
||||
console.log(results);
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## SQL Migration
|
||||
|
||||
You don't need to run any SQL migrations. Mem0 creates the collection table when it initializes the `pgvector` store.
|
||||
|
||||
## Environment
|
||||
|
||||
```env
|
||||
OPENAI_API_KEY=sk-xx...
|
||||
DATABASE_URL=postgresql://user:password@ep-example.us-east-2.aws.neon.tech/neondb?sslmode=require
|
||||
```
|
||||
|
||||
## Config
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Python">
|
||||
| Parameter | Description | Default Value |
|
||||
| --- | --- | --- |
|
||||
| `connection_string` | Neon Postgres connection string. | Required |
|
||||
| `collection_name` | Name for the vector collection. | `mem0` |
|
||||
| `embedding_model_dims` | Embedding model dimensions. | `1536` |
|
||||
| `hnsw` | Enables HNSW indexing. | `False` |
|
||||
| `sslmode` | PostgreSQL SSL mode. Use `require` for Neon. | Driver default |
|
||||
</Tab>
|
||||
<Tab title="TypeScript">
|
||||
The current Mem0 TypeScript `pgvector` adapter takes individual Postgres fields,
|
||||
so parse `DATABASE_URL` before creating `Memory`.
|
||||
|
||||
| Parameter | Description | Default |
|
||||
| --- | --- | --- |
|
||||
| `user` | Database user. | Required |
|
||||
| `password` | Database password. | Required |
|
||||
| `host` | Database host. | Required |
|
||||
| `port` | Database port. | `5432` |
|
||||
| `dbname` | Database name. | `vector_store` |
|
||||
| `collectionName` | Name for the vector collection. | `memories` |
|
||||
| `dimension` | Vector dimension for Mem0 config. | Auto-detected |
|
||||
| `embeddingModelDims` | Embedding model dimensions for table creation. | Required |
|
||||
| `hnsw` | Enables HNSW indexing. | `false` |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
### Indexing
|
||||
|
||||
The `pgvector` provider can create an HNSW index for faster vector search.
|
||||
|
||||
- Set `hnsw` to `true` to enable a Hierarchical Navigable Small World index.
|
||||
- Leave `hnsw` as `false` if you want to create or manage indexes yourself.
|
||||
|
||||
### Similarity Search
|
||||
|
||||
The `pgvector` provider uses cosine similarity for vector search. Make sure your
|
||||
embedding dimensions match the configured `embedding_model_dims` value.
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Index Selection**:
|
||||
- Use `hnsw` for faster search performance when memory usage is not a constraint
|
||||
- Manage indexes manually if you need a different pgvector index strategy
|
||||
|
||||
2. **Connection String**:
|
||||
- Always use environment variables or even better, a secret manager for sensitive information in the connection string
|
||||
- Format: `postgresql://user:password@host:port/database`
|
||||
@@ -76,6 +76,7 @@ Let's see the available parameters for the `qdrant` config:
|
||||
| `path` | Path for the qdrant database | `/tmp/qdrant` |
|
||||
| `url` | Full URL for the qdrant server | `None` |
|
||||
| `api_key` | API key for the qdrant server | `None` |
|
||||
| `https` | Whether to force HTTPS on or off. `None` lets the client decide; set `False` for plain HTTP Qdrant with API key authentication. | `None` |
|
||||
| `on_disk` | For enabling persistent storage | `False` |
|
||||
</Tab>
|
||||
<Tab title="TypeScript">
|
||||
@@ -90,4 +91,4 @@ Let's see the available parameters for the `qdrant` config:
|
||||
| `apiKey` | API key for the Qdrant server | `None` |
|
||||
| `onDisk` | For enabling persistent storage | `False` |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</Tabs>
|
||||
|
||||
@@ -156,6 +156,33 @@ const memories = memory.search("food preferences", {
|
||||
On Mem0 Platform v3, time-aware queries use Temporal Reasoning internally while preserving the normal search response shape. See <Link href="/platform/features/temporal-reasoning">Temporal Reasoning</Link>.
|
||||
</Note>
|
||||
|
||||
### Explain OSS search scores
|
||||
|
||||
OSS search combines semantic similarity with optional keyword and entity signals. Pass `explain=True` when tuning retrieval quality or debugging why a memory ranked where it did:
|
||||
|
||||
<CodeGroup>
|
||||
```python Python
|
||||
results = m.search(
|
||||
"food preferences",
|
||||
filters={"user_id": "alice"},
|
||||
explain=True,
|
||||
)
|
||||
|
||||
print(results["results"][0]["score_details"])
|
||||
```
|
||||
|
||||
```javascript JavaScript
|
||||
const results = await memory.search("food preferences", {
|
||||
filters: { user_id: "alice" },
|
||||
explain: true,
|
||||
});
|
||||
|
||||
console.log(results.results[0].score_details);
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
Each result includes `score_details` with the semantic score, normalized BM25 score, entity boost, raw combined score, maximum possible score, final score, and threshold used for filtering. The field is omitted unless `explain` is enabled, so existing response shapes stay unchanged.
|
||||
|
||||
## Filter patterns
|
||||
|
||||
Filters help narrow down search results. Common use cases:
|
||||
|
||||
+7
-3
@@ -143,7 +143,8 @@
|
||||
"icon": "robot",
|
||||
"pages": [
|
||||
"integrations/openclaw",
|
||||
"integrations/hermes"
|
||||
"integrations/hermes",
|
||||
"integrations/pi-agent"
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -245,6 +246,7 @@
|
||||
"components/vectordbs/dbs/cassandra",
|
||||
"components/vectordbs/dbs/s3_vectors",
|
||||
"components/vectordbs/dbs/databricks",
|
||||
"components/vectordbs/dbs/neon",
|
||||
"components/vectordbs/dbs/neptune_analytics",
|
||||
"components/vectordbs/dbs/turbopuffer"
|
||||
]
|
||||
@@ -302,7 +304,8 @@
|
||||
"group": "Migration",
|
||||
"icon": "arrow-right",
|
||||
"pages": [
|
||||
"migration/oss-v2-to-v3"
|
||||
"migration/oss-v2-to-v3",
|
||||
"migration/server-pgvector-upgrade"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -462,7 +465,8 @@
|
||||
"icon": "robot",
|
||||
"pages": [
|
||||
"integrations/openclaw",
|
||||
"integrations/hermes"
|
||||
"integrations/hermes",
|
||||
"integrations/pi-agent"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -28,7 +28,7 @@ echo 'export MEM0_API_KEY="m0-your-api-key"' >> ~/.bashrc && source ~/.bashrc
|
||||
|
||||
```bash
|
||||
# Install the plugin (MCP server, hooks, scripts)
|
||||
npx degit mem0ai/mem0/mem0-plugin ~/.gemini/config/plugins/mem0
|
||||
npx degit mem0ai/mem0/integrations/mem0-plugin ~/.gemini/config/plugins/mem0
|
||||
```
|
||||
|
||||
This installs the MCP server, lifecycle hooks, and shared scripts.
|
||||
|
||||
+243
-191
@@ -7,285 +7,338 @@ Integrate [**Mem0**](https://github.com/mem0ai/mem0) with [Google ADK (Agent Dev
|
||||
|
||||
## Overview
|
||||
|
||||
1. Store and retrieve memories from Mem0 within Google ADK agents
|
||||
2. Multi-agent workflows with shared memory across hierarchies
|
||||
3. Retrieve relevant memories from past conversations
|
||||
4. Personalized responses based on user history
|
||||
In this guide, we'll create a Google ADK agent that:
|
||||
1. Uses ADK's native `MemoryService` interface to connect Mem0
|
||||
2. Automatically injects relevant memories using ADK's built-in `load_memory` tool
|
||||
3. Persists session history to Mem0 after each turn via an after-agent callback
|
||||
4. Shares memory seamlessly across multi-agent hierarchies
|
||||
|
||||
## Prerequisites
|
||||
## Setup and Configuration
|
||||
|
||||
Before setting up Mem0 with Google ADK, ensure you have:
|
||||
Install the necessary libraries:
|
||||
|
||||
1. Installed the required packages:
|
||||
```bash
|
||||
pip install google-adk mem0ai python-dotenv
|
||||
```
|
||||
|
||||
2. Valid API keys:
|
||||
Set up your API keys:
|
||||
- <a href="https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=integration-google-ai-adk" rel="nofollow">Mem0 API Key</a>
|
||||
- Google AI Studio API Key
|
||||
|
||||
## Basic Integration Example
|
||||
|
||||
The following example demonstrates how to create a Google ADK agent with Mem0 memory integration:
|
||||
<Note>Remember to get your API key from <a href="https://app.mem0.ai" rel="nofollow">Mem0 Platform</a> and set up a [Google AI Studio API Key](https://aistudio.google.com/apikey).</Note>
|
||||
|
||||
```python
|
||||
import os
|
||||
import asyncio
|
||||
from google.adk.agents import Agent
|
||||
from google.adk.runners import Runner
|
||||
from google.adk.sessions import InMemorySessionService
|
||||
from google.genai import types
|
||||
from mem0 import MemoryClient
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
# Set up environment variables
|
||||
# os.environ["GOOGLE_API_KEY"] = "your-google-api-key"
|
||||
# os.environ["MEM0_API_KEY"] = "your-mem0-api-key"
|
||||
```
|
||||
|
||||
# Initialize Mem0 client
|
||||
mem0 = MemoryClient()
|
||||
## Implement Mem0MemoryService
|
||||
|
||||
# Define memory function tools
|
||||
def search_memory(query: str, user_id: str) -> dict:
|
||||
"""Search through past conversations and memories"""
|
||||
# For Platform API, user_id goes in filters
|
||||
filters = {"user_id": user_id}
|
||||
memories = mem0.search(query, filters=filters)
|
||||
if memories.get('results', []):
|
||||
memory_list = memories['results']
|
||||
memory_context = "\n".join([f"- {mem['memory']}" for mem in memory_list])
|
||||
return {"status": "success", "memories": memory_context}
|
||||
return {"status": "no_memories", "message": "No relevant memories found"}
|
||||
Create a custom `MemoryService` by implementing ADK's `BaseMemoryService`. Save the following as **`mem0_memory_service.py`**:
|
||||
|
||||
def save_memory(content: str, user_id: str) -> dict:
|
||||
"""Save important information to memory"""
|
||||
```python
|
||||
import asyncio
|
||||
import os
|
||||
from typing import Optional
|
||||
from typing_extensions import override
|
||||
|
||||
from google.adk.memory.base_memory_service import BaseMemoryService, SearchMemoryResponse
|
||||
from google.adk.memory.memory_entry import MemoryEntry
|
||||
from google.adk.sessions import Session
|
||||
from google.genai.types import Content, Part
|
||||
from mem0 import MemoryClient
|
||||
|
||||
|
||||
class Mem0MemoryService(BaseMemoryService):
|
||||
"""MemoryService implementation backed by the Mem0 Platform."""
|
||||
|
||||
def __init__(self, api_key: Optional[str] = None):
|
||||
super().__init__()
|
||||
api_key = api_key or os.environ.get("MEM0_API_KEY")
|
||||
self._client: Optional[MemoryClient] = MemoryClient(api_key=api_key) if api_key else None
|
||||
|
||||
@override
|
||||
async def search_memory(
|
||||
self, *, app_name: str, user_id: str, query: str
|
||||
) -> SearchMemoryResponse:
|
||||
"""Search for memories relevant to the current user and query."""
|
||||
if not self._client:
|
||||
return SearchMemoryResponse(memories=[])
|
||||
|
||||
try:
|
||||
results = await asyncio.to_thread(
|
||||
self._client.search,
|
||||
query,
|
||||
filters={"AND": [{"user_id": user_id}, {"app_id": app_name}]},
|
||||
top_k=5,
|
||||
)
|
||||
|
||||
entries = []
|
||||
for mem in results.get("results", []):
|
||||
text = mem.get("memory", "")
|
||||
if not text:
|
||||
continue
|
||||
|
||||
raw_ts = mem.get("created_at") or mem.get("updated_at")
|
||||
entries.append(
|
||||
MemoryEntry(
|
||||
content=Content(parts=[Part(text=text)]),
|
||||
author=mem.get("metadata", {}).get("author", "user"),
|
||||
timestamp=str(raw_ts) if raw_ts else None,
|
||||
)
|
||||
)
|
||||
|
||||
return SearchMemoryResponse(memories=entries)
|
||||
|
||||
except Exception as e:
|
||||
print(f"[Mem0MemoryService] search_memory error: {e}")
|
||||
return SearchMemoryResponse(memories=[])
|
||||
|
||||
@override
|
||||
async def add_session_to_memory(self, session: Session) -> None:
|
||||
"""Persist a completed ADK session into Mem0."""
|
||||
if not self._client:
|
||||
return
|
||||
|
||||
user_id = session.user_id
|
||||
if not user_id:
|
||||
return
|
||||
|
||||
app_name = getattr(session, "app_name", None)
|
||||
|
||||
try:
|
||||
messages = []
|
||||
for event in session.events:
|
||||
if not (event.content and event.content.parts):
|
||||
continue
|
||||
role = getattr(event.content, "role", None) or "user"
|
||||
if role == "model":
|
||||
role = "assistant"
|
||||
elif role not in ("user", "assistant"):
|
||||
continue
|
||||
text_parts = [
|
||||
p.text for p in event.content.parts if hasattr(p, "text") and p.text
|
||||
]
|
||||
if text_parts:
|
||||
messages.append({"role": role, "content": " ".join(text_parts)})
|
||||
|
||||
if messages:
|
||||
metadata = {"app_id": app_name} if app_name else {}
|
||||
await asyncio.to_thread(
|
||||
self._client.add, messages, user_id=user_id, metadata=metadata
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
print(f"[Mem0MemoryService] add_session_to_memory error: {e}")
|
||||
```
|
||||
|
||||
## Add Auto-Save Callback
|
||||
|
||||
This after-agent callback fires at the end of every turn and saves the session to Mem0. Save as **`memory_callbacks.py`**:
|
||||
|
||||
```python
|
||||
async def save_session_to_memory(callback_context) -> None:
|
||||
"""Persist the completed session to Mem0 after each agent turn."""
|
||||
try:
|
||||
result = mem0.add([{"role": "user", "content": content}], user_id=user_id)
|
||||
return {"status": "success", "message": "Information saved to memory", "result": result}
|
||||
await callback_context.add_session_to_memory()
|
||||
except ValueError:
|
||||
pass
|
||||
except Exception as e:
|
||||
return {"status": "error", "message": f"Failed to save memory: {str(e)}"}
|
||||
print(f"[save_session_to_memory] error: {e}")
|
||||
```
|
||||
|
||||
# Create agent with memory capabilities
|
||||
personal_assistant = Agent(
|
||||
## Basic Integration Example
|
||||
|
||||
The following example demonstrates creating an ADK agent with automatic Mem0 memory:
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from google.adk.agents import LlmAgent
|
||||
from google.adk.runners import Runner
|
||||
from google.adk.sessions import InMemorySessionService
|
||||
from google.adk.tools import load_memory
|
||||
from google.genai.types import Content, Part
|
||||
|
||||
from mem0_memory_service import Mem0MemoryService
|
||||
from memory_callbacks import save_session_to_memory
|
||||
|
||||
memory_service = Mem0MemoryService()
|
||||
session_service = InMemorySessionService()
|
||||
|
||||
agent = LlmAgent(
|
||||
name="personal_assistant",
|
||||
model="gemini-2.0-flash",
|
||||
instruction="""You are a helpful personal assistant with memory capabilities.
|
||||
Use the search_memory function to recall past conversations and user preferences.
|
||||
Use the save_memory function to store important information about the user.
|
||||
Always personalize your responses based on available memory.""",
|
||||
instruction="""You are a helpful personal assistant.
|
||||
Relevant memories from past conversations are provided to you automatically.
|
||||
Use them to personalize your responses.""",
|
||||
description="A personal assistant that remembers user preferences and past interactions",
|
||||
tools=[search_memory, save_memory]
|
||||
tools=[load_memory],
|
||||
after_agent_callback=save_session_to_memory,
|
||||
)
|
||||
|
||||
async def chat_with_agent(user_input: str, user_id: str) -> str:
|
||||
"""
|
||||
Handle user input with automatic memory integration.
|
||||
runner = Runner(
|
||||
agent=agent,
|
||||
session_service=session_service,
|
||||
memory_service=memory_service,
|
||||
app_name="memory_assistant",
|
||||
)
|
||||
|
||||
Args:
|
||||
user_input: The user's message
|
||||
user_id: Unique identifier for the user
|
||||
|
||||
Returns:
|
||||
The agent's response
|
||||
"""
|
||||
# Set up session and runner
|
||||
session_service = InMemorySessionService()
|
||||
async def chat(user_input: str, user_id: str) -> str:
|
||||
session = await session_service.create_session(
|
||||
app_name="memory_assistant",
|
||||
user_id=user_id,
|
||||
session_id=f"session_{user_id}"
|
||||
)
|
||||
runner = Runner(agent=personal_assistant, app_name="memory_assistant", session_service=session_service)
|
||||
|
||||
# Create content and run agent
|
||||
content = types.Content(role='user', parts=[types.Part(text=user_input)])
|
||||
events = runner.run(user_id=user_id, session_id=session.id, new_message=content)
|
||||
|
||||
# Extract final response
|
||||
for event in events:
|
||||
if event.is_final_response():
|
||||
response = event.content.parts[0].text
|
||||
|
||||
return response
|
||||
|
||||
content = Content(role="user", parts=[Part(text=user_input)])
|
||||
async for event in runner.run_async(user_id=user_id, session_id=session.id, new_message=content):
|
||||
if event.is_final_response() and event.content and event.content.parts:
|
||||
return event.content.parts[0].text
|
||||
return "No response generated"
|
||||
|
||||
# Example usage
|
||||
|
||||
if __name__ == "__main__":
|
||||
response = asyncio.run(chat_with_agent(
|
||||
print(asyncio.run(chat(
|
||||
"I love Italian food and I'm planning a trip to Rome next month",
|
||||
user_id="alice"
|
||||
))
|
||||
print(response)
|
||||
user_id="alice",
|
||||
)))
|
||||
|
||||
print(asyncio.run(chat(
|
||||
"Any food recommendations for my trip?",
|
||||
user_id="alice",
|
||||
)))
|
||||
```
|
||||
|
||||
## Multi-Agent Hierarchy with Shared Memory
|
||||
|
||||
Create specialized agents in a hierarchy that share memory:
|
||||
Because `memory_service` is passed to the `Runner`, every agent in the hierarchy shares the same memory automatically. Only the root coordinator needs the auto-save callback — ADK fires it once when the full turn completes:
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from google.adk.agents import LlmAgent
|
||||
from google.adk.runners import Runner
|
||||
from google.adk.sessions import InMemorySessionService
|
||||
from google.adk.tools.agent_tool import AgentTool
|
||||
from google.adk.tools import load_memory
|
||||
from google.genai.types import Content, Part
|
||||
|
||||
# Travel specialist agent
|
||||
travel_agent = Agent(
|
||||
from mem0_memory_service import Mem0MemoryService
|
||||
from memory_callbacks import save_session_to_memory
|
||||
|
||||
memory_service = Mem0MemoryService()
|
||||
session_service = InMemorySessionService()
|
||||
|
||||
travel_agent = LlmAgent(
|
||||
name="travel_specialist",
|
||||
model="gemini-2.0-flash",
|
||||
instruction="""You are a travel planning specialist. Use search_memory to
|
||||
understand the user's travel preferences and history before making recommendations.
|
||||
After providing advice, use save_memory to save travel-related information.""",
|
||||
instruction="""You are a travel planning specialist.
|
||||
Relevant memories about the user's travel preferences are provided automatically.
|
||||
Use them to make personalized recommendations.""",
|
||||
description="Specialist in travel planning and recommendations",
|
||||
tools=[search_memory, save_memory]
|
||||
tools=[load_memory],
|
||||
)
|
||||
|
||||
# Health advisor agent
|
||||
health_agent = Agent(
|
||||
health_agent = LlmAgent(
|
||||
name="health_advisor",
|
||||
model="gemini-2.0-flash",
|
||||
instruction="""You are a health and wellness advisor. Use search_memory to
|
||||
understand the user's health goals and dietary preferences.
|
||||
After providing advice, use save_memory to save health-related information.""",
|
||||
instruction="""You are a health and wellness advisor.
|
||||
Relevant memories about the user's health goals are provided automatically.
|
||||
Use them to give personalized advice.""",
|
||||
description="Specialist in health and wellness advice",
|
||||
tools=[search_memory, save_memory]
|
||||
tools=[load_memory],
|
||||
)
|
||||
|
||||
# Coordinator agent that delegates to specialists
|
||||
coordinator_agent = Agent(
|
||||
coordinator = LlmAgent(
|
||||
name="coordinator",
|
||||
model="gemini-2.0-flash",
|
||||
instruction="""You are a coordinator that delegates requests to specialist agents.
|
||||
For travel-related questions (trips, hotels, flights, destinations), delegate to the travel specialist.
|
||||
For health-related questions (fitness, diet, wellness, exercise), delegate to the health advisor.
|
||||
Use search_memory to understand the user before delegation.""",
|
||||
For travel-related questions, delegate to the travel specialist.
|
||||
For health-related questions, delegate to the health advisor.
|
||||
Relevant memories about the user are provided automatically.""",
|
||||
description="Coordinates requests between specialist agents",
|
||||
tools=[
|
||||
load_memory,
|
||||
AgentTool(agent=travel_agent, skip_summarization=False),
|
||||
AgentTool(agent=health_agent, skip_summarization=False)
|
||||
]
|
||||
AgentTool(agent=health_agent, skip_summarization=False),
|
||||
],
|
||||
after_agent_callback=save_session_to_memory,
|
||||
)
|
||||
|
||||
def chat_with_specialists(user_input: str, user_id: str) -> str:
|
||||
"""
|
||||
Handle user input with specialist agent delegation and memory.
|
||||
runner = Runner(
|
||||
agent=coordinator,
|
||||
session_service=session_service,
|
||||
memory_service=memory_service,
|
||||
app_name="specialist_system",
|
||||
)
|
||||
|
||||
Args:
|
||||
user_input: The user's message
|
||||
user_id: Unique identifier for the user
|
||||
|
||||
Returns:
|
||||
The specialist agent's response
|
||||
"""
|
||||
session_service = InMemorySessionService()
|
||||
session = session_service.create_session(
|
||||
async def chat_with_specialists(user_input: str, user_id: str) -> str:
|
||||
session = await session_service.create_session(
|
||||
app_name="specialist_system",
|
||||
user_id=user_id,
|
||||
session_id=f"session_{user_id}"
|
||||
)
|
||||
runner = Runner(agent=coordinator_agent, app_name="specialist_system", session_service=session_service)
|
||||
|
||||
content = types.Content(role='user', parts=[types.Part(text=user_input)])
|
||||
events = runner.run(user_id=user_id, session_id=session.id, new_message=content)
|
||||
|
||||
for event in events:
|
||||
if event.is_final_response():
|
||||
response = event.content.parts[0].text
|
||||
|
||||
# Store the conversation in shared memory
|
||||
conversation = [
|
||||
{"role": "user", "content": user_input},
|
||||
{"role": "assistant", "content": response}
|
||||
]
|
||||
mem0.add(conversation, user_id=user_id)
|
||||
|
||||
return response
|
||||
|
||||
content = Content(role="user", parts=[Part(text=user_input)])
|
||||
async for event in runner.run_async(user_id=user_id, session_id=session.id, new_message=content):
|
||||
if event.is_final_response() and event.content and event.content.parts:
|
||||
return event.content.parts[0].text
|
||||
return "No response generated"
|
||||
|
||||
# Example usage
|
||||
response = chat_with_specialists("Plan a healthy meal for my Italy trip", user_id="alice")
|
||||
print(response)
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Quick Start Chat Interface
|
||||
|
||||
Simple interactive chat with memory and Google ADK:
|
||||
|
||||
```python
|
||||
def interactive_chat():
|
||||
"""Interactive chat interface with memory and ADK"""
|
||||
user_id = input("Enter your user ID: ") or "demo_user"
|
||||
print(f"Chat started for user: {user_id}")
|
||||
print("Type 'quit' to exit")
|
||||
print("=" * 50)
|
||||
|
||||
while True:
|
||||
user_input = input("\nYou: ")
|
||||
|
||||
if user_input.lower() == 'quit':
|
||||
print("Goodbye! Your conversation has been saved to memory.")
|
||||
break
|
||||
else:
|
||||
response = chat_with_specialists(user_input, user_id)
|
||||
print(f"Assistant: {response}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
interactive_chat()
|
||||
response = asyncio.run(chat_with_specialists("Plan a healthy meal for my Italy trip", user_id="alice"))
|
||||
print(response)
|
||||
```
|
||||
|
||||
## Key Features
|
||||
|
||||
### 1. Memory-Enhanced Function Tools
|
||||
- **Function Tools**: Standard Python functions that can search and save memories
|
||||
- **Tool Context**: Access to session state and memory through function parameters
|
||||
- **Structured Returns**: Dictionary-based returns with status indicators for better LLM understanding
|
||||
|
||||
### 2. Multi-Agent Memory Sharing
|
||||
- **Agent-as-a-Tool**: Specialists can be called as tools while maintaining shared memory
|
||||
- **Hierarchical Delegation**: Coordinator agents route to specialists based on context
|
||||
- **Memory Categories**: Store interactions with metadata for better organization
|
||||
|
||||
### 3. Flexible Memory Operations
|
||||
- **Search Capabilities**: Retrieve relevant memories through conversation history
|
||||
- **User Segmentation**: Organize memories by user ID
|
||||
- **Memory Management**: Built-in tools for saving and retrieving information
|
||||
1. **Automatic Memory Injection**: ADK's built-in `load_memory` tool searches Mem0 at the start of each turn and injects relevant memories directly into the agent context — no prompt instructions needed.
|
||||
2. **Automatic Session Saving**: The `save_session_to_memory` callback persists every completed turn to Mem0 without any manual calls.
|
||||
3. **Native ADK Integration**: `Mem0MemoryService` implements ADK's `BaseMemoryService` and integrates via the `Runner` — works natively across the entire agent hierarchy.
|
||||
4. **User Scoping**: `user_id` is passed automatically from the ADK session context, ensuring memories are always scoped to the correct user.
|
||||
5. **Multi-Agent Support**: A single `Mem0MemoryService` instance shared through the `Runner` gives all agents — coordinators and specialists — access to the same user memory.
|
||||
|
||||
## Configuration Options
|
||||
|
||||
Customize memory behavior and agent setup:
|
||||
### Using Vertex AI
|
||||
|
||||
To use Google Cloud Vertex AI instead of AI Studio, set the following environment variables before creating agents:
|
||||
|
||||
```python
|
||||
# Configure memory search with filters
|
||||
# For Platform API, all filters including user_id go in filters object
|
||||
memories = mem0.search(
|
||||
query="travel preferences",
|
||||
filters={
|
||||
"AND": [
|
||||
{"user_id": "alice"},
|
||||
{"categories": {"contains": "travel"}}
|
||||
]
|
||||
},
|
||||
top_k=5
|
||||
)
|
||||
|
||||
# Configure agent with custom model settings
|
||||
agent = Agent(
|
||||
name="custom_agent",
|
||||
model="gemini-2.0-flash", # or use LiteLLM for other models
|
||||
instruction="Custom agent behavior",
|
||||
tools=[memory_tools],
|
||||
# Additional ADK configurations
|
||||
)
|
||||
|
||||
# Use Google Cloud Vertex AI instead of AI Studio
|
||||
import os
|
||||
os.environ["GOOGLE_GENAI_USE_VERTEXAI"] = "True"
|
||||
os.environ["GOOGLE_CLOUD_PROJECT"] = "your-project-id"
|
||||
os.environ["GOOGLE_CLOUD_LOCATION"] = "us-central1"
|
||||
```
|
||||
|
||||
### Advanced Memory Filtering
|
||||
|
||||
You can customize how memories are searched by modifying `Mem0MemoryService.search_memory`. For example, to filter by category:
|
||||
|
||||
```python
|
||||
results = await asyncio.to_thread(
|
||||
self._client.search,
|
||||
query,
|
||||
filters={
|
||||
"AND": [
|
||||
{"user_id": user_id},
|
||||
{"app_id": app_name},
|
||||
{"categories": {"contains": "travel"}}
|
||||
]
|
||||
},
|
||||
top_k=10,
|
||||
)
|
||||
```
|
||||
|
||||
<Note>`InMemorySessionService` stores sessions in memory and is intended for prototyping. For production, use a persistent session service and clean up sessions when they are no longer needed.</Note>
|
||||
|
||||
## Conclusion
|
||||
|
||||
By implementing `Mem0MemoryService` as an ADK `BaseMemoryService`, you get persistent, user-scoped memory across single agents and complex multi-agent hierarchies with minimal code. Memory injection and session saving happen automatically, keeping your agent prompts clean and your token usage efficient.
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Healthcare Agent Cookbook" icon="heart-pulse" href="/cookbooks/integrations/healthcare-google-adk">
|
||||
Build HIPAA-compliant healthcare agents with Google ADK
|
||||
@@ -294,4 +347,3 @@ os.environ["GOOGLE_CLOUD_LOCATION"] = "us-central1"
|
||||
Compare with OpenAI's agent framework
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ bunx @mem0/opencode-plugin@latest install
|
||||
**Or let your agent do it** — paste this into OpenCode:
|
||||
|
||||
```
|
||||
Install @mem0/opencode-plugin by following https://raw.githubusercontent.com/mem0ai/mem0/main/mem0-plugin/.opencode-plugin/README.md
|
||||
Install @mem0/opencode-plugin by following https://raw.githubusercontent.com/mem0ai/mem0/main/integrations/mem0-plugin/.opencode-plugin/README.md
|
||||
```
|
||||
|
||||
All commands auto-add the plugin and MCP server to your `~/.config/opencode/opencode.json`. Restart OpenCode — you get the MCP server, lifecycle hooks, and all `/mem0:` slash commands.
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
---
|
||||
title: Pi Agent
|
||||
description: "Add persistent memory to Pi Agent with the Mem0 plugin semantic search, auto-capture, and dream consolidation."
|
||||
---
|
||||
|
||||
Add persistent memory to [**Pi Agent**](https://pi.dev) with `@mem0/pi-agent-plugin`. Your agent forgets everything between sessions — this plugin fixes that by automatically capturing knowledge from conversations, storing it in Mem0's cloud memory layer, and retrieving relevant context before every response.
|
||||
|
||||
## Overview
|
||||
|
||||
The plugin provides:
|
||||
1. **Auto-capture** — Extracts durable facts from both user and assistant messages automatically
|
||||
2. **Semantic recall** — Retrieves relevant memories via the `mem0_memory` tool before each response
|
||||
3. **Dream consolidation** — Periodic maintenance: merges duplicates, resolves contradictions, prunes stale entries
|
||||
4. **Monorepo-aware scoping** — Uses git root for project detection, consistent across subdirectories
|
||||
5. **Confirmation dialogs** — Destructive commands ask before acting via Pi's built-in UI
|
||||
6. **8 skills + 8 commands** — Essential memory management from slash commands and agent-guided workflows
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. A Mem0 Platform account and API key:
|
||||
- <a href="https://app.mem0.ai?utm_source=oss&utm_medium=integration-pi-agent" rel="nofollow">Sign up at app.mem0.ai</a>
|
||||
- <a href="https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=integration-pi-agent" rel="nofollow">Get your API key</a> (starts with `m0-`)
|
||||
|
||||
2. Pi Agent installed ([pi.dev](https://pi.dev))
|
||||
|
||||
3. Your API key added to your shell profile:
|
||||
|
||||
<CodeGroup>
|
||||
```bash zsh
|
||||
echo 'export MEM0_API_KEY="m0-your-api-key"' >> ~/.zshrc
|
||||
source ~/.zshrc
|
||||
```
|
||||
|
||||
```bash bash
|
||||
echo 'export MEM0_API_KEY="m0-your-api-key"' >> ~/.bashrc
|
||||
source ~/.bashrc
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pi install npm:@mem0/pi-agent-plugin
|
||||
```
|
||||
|
||||
That's it. The extension loads automatically on every Pi session. No config files needed — `MEM0_API_KEY` from your environment is picked up automatically.
|
||||
|
||||
<Info>
|
||||
Start a new Pi session and run `/mem0-status` to verify the connection. You should see your user ID, detected project, and memory count.
|
||||
</Info>
|
||||
|
||||
### Optional Configuration
|
||||
|
||||
For advanced settings, create `~/.pi/agent/mem0-config.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"apiKey": "m0-your-key-here",
|
||||
"userId": "your-username",
|
||||
"autoCapture": true,
|
||||
"defaultScope": "project",
|
||||
"searchThreshold": 0.3,
|
||||
"dream": {
|
||||
"enabled": true,
|
||||
"auto": true,
|
||||
"minHours": 24,
|
||||
"minSessions": 5,
|
||||
"minMemories": 20
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
|-----|------|---------|-------------|
|
||||
| `apiKey` | `string` | `$MEM0_API_KEY` | Mem0 API key. Environment variable takes precedence. |
|
||||
| `userId` | `string` | `$MEM0_USER_ID` or `"default"` | User identity for memory scoping |
|
||||
| `autoCapture` | `boolean` | `true` | Store facts from conversations automatically |
|
||||
| `defaultScope` | `string` | `"project"` | Default memory scope: `project`, `session`, or `global` |
|
||||
| `searchThreshold` | `number` | `0.3` | Minimum similarity score (0–1) a memory must reach to count as a match for `/mem0-search`, `/mem0-forget`, and `/mem0-pin`, enforced on each result's relevance score. Raise it to be stricter; lower it if relevant results are missed. |
|
||||
| `dream.enabled` | `boolean` | `true` | Enable dream consolidation |
|
||||
| `dream.auto` | `boolean` | `true` | Auto-trigger dreams when thresholds are met |
|
||||
| `dream.minHours` | `number` | `24` | Minimum hours between auto-dreams |
|
||||
| `dream.minSessions` | `number` | `5` | Minimum sessions before first auto-dream |
|
||||
| `dream.minMemories` | `number` | `20` | Minimum memories before auto-dream triggers |
|
||||
|
||||
|
||||
## What's Included
|
||||
|
||||
| Component | Description |
|
||||
|-----------|-------------|
|
||||
| `mem0_memory` tool | Agent-callable tool for search, add, get_all, delete, delete_all |
|
||||
| 8 slash commands | Essential memory management from the command line |
|
||||
| 8 skills | Guide the agent on how to use each capability |
|
||||
| Auto-capture | Extracts and stores facts on every `agent_end` event |
|
||||
| System prompt | Appends memory policy to every agent turn |
|
||||
| Dream consolidation | Automated memory maintenance with session/time/count gates |
|
||||
|
||||
## Agent Tool
|
||||
|
||||
The `mem0_memory` tool is registered with Pi and callable by the agent during conversations:
|
||||
|
||||
| Action | Required Params | Description |
|
||||
|--------|----------------|-------------|
|
||||
| `search` | `query` | Semantic search across memories |
|
||||
| `add` | `content` | Store a new memory |
|
||||
| `get_all` | — | List all memories in scope |
|
||||
| `delete` | `memory_id` | Delete a specific memory |
|
||||
| `delete_all` | — | Delete all memories in scope |
|
||||
|
||||
All actions accept an optional `scope` parameter: `project` (default), `session`, or `global`.
|
||||
|
||||
Tool output is truncated to 200 lines / 50KB to prevent context overflow.
|
||||
|
||||
## Commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `/mem0-remember <text>` | Store a memory verbatim (no inference) |
|
||||
| `/mem0-forget <query>` | Search and delete memories (with confirmation dialog) |
|
||||
| `/mem0-search <query>` | Semantic search across memories |
|
||||
| `/mem0-tour [scope]` | Browse all memories grouped by category |
|
||||
| `/mem0-dream` | Consolidate — merge duplicates, prune stale, resolve contradictions |
|
||||
| `/mem0-pin <query>` | Pin a memory to protect from dream pruning (preserves memory ID) |
|
||||
| `/mem0-scope <scope>` | Change default scope for this session (project, session, global) |
|
||||
| `/mem0-status` | Connection health, identity, and memory count |
|
||||
|
||||
## Memory Scopes
|
||||
|
||||
Memories are scoped using Mem0's `user_id`, `app_id`, and `run_id` parameters:
|
||||
|
||||
| Scope | Filters | Use Case |
|
||||
|-------|---------|----------|
|
||||
| `project` | user_id + app_id (git root) | **Default.** Project-specific knowledge — decisions, architecture, config |
|
||||
| `session` | user_id + app_id + run_id | Ephemeral context for the current session only |
|
||||
| `global` | user_id only | All memories across all your projects |
|
||||
|
||||
The `app_id` is auto-detected from the git repository root (`git rev-parse --show-toplevel`), so all subdirectories within a monorepo share the same memory pool. Falls back to the working directory name for non-git directories. The `run_id` is derived from Pi's session file path.
|
||||
|
||||
## Dream Consolidation
|
||||
|
||||
### Confirmation Dialogs
|
||||
|
||||
Destructive and mutating commands use Pi's built-in `ctx.ui.confirm()` dialog before acting:
|
||||
|
||||
- `/mem0-forget` asks "Delete this memory?" before deleting a single match
|
||||
- `/mem0-pin` asks "Pin this memory?" before modifying it
|
||||
- Cancelling either operation is always safe — no changes are made
|
||||
|
||||
### Pin
|
||||
|
||||
`/mem0-pin` uses Mem0's `update()` API to prepend `[PINNED]` to the memory text. This preserves the original memory ID — no add+delete cycle that would lose history or change the UUID.
|
||||
|
||||
### Dream Consolidation
|
||||
|
||||
The plugin includes automated memory maintenance ("dream") that merges duplicates, resolves contradictions, and prunes stale entries. When enabled, dreams auto-trigger after enough sessions, time, and memories accumulate (configurable via `dream.*` settings). Run `/mem0-dream` to trigger consolidation manually at any time. Pinned memories (via `/mem0-pin`) are protected from pruning.
|
||||
|
||||
## Example Workflow
|
||||
|
||||
```text
|
||||
# Session 1
|
||||
You: I prefer dark mode and concise answers.
|
||||
# Mem0 auto-captures preferences
|
||||
|
||||
# Session 2 (days later)
|
||||
You: What do you know about my preferences?
|
||||
# Pi retrieves stored memories — no re-explaining needed
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **"No API key found"** — Verify `MEM0_API_KEY` is set: `echo $MEM0_API_KEY`. If empty, add it to your shell profile (see Prerequisites)
|
||||
- **Extension not loading** — Check Pi startup output for errors. Run `pi -e ./src/entry.ts` from the plugin directory for verbose output
|
||||
- **Memories not capturing** — Verify `autoCapture` is `true` (default). Check `/mem0-status` for connection health
|
||||
- **Wrong project detected** — The plugin uses the git repository root as `app_id`. If not in a git repo, it falls back to the working directory name. Run `/mem0-status` to see the detected project
|
||||
- **Dream not triggering** — All three gates must pass (time, sessions, memories). Use `/mem0-dream` to force it manually
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Claude Code Integration" icon="terminal" href="/integrations/claude-code">
|
||||
Add Mem0 memory to Claude Code
|
||||
</Card>
|
||||
<Card title="OpenClaw Integration" icon="plug" href="/integrations/openclaw">
|
||||
Add Mem0 memory to OpenClaw agents
|
||||
</Card>
|
||||
</CardGroup>
|
||||
+151
-116
@@ -6,25 +6,33 @@ description: "Use the Mem0 AI SDK Provider with Vercel AI SDK for persistent mem
|
||||
The [**Mem0 AI SDK Provider**](https://www.npmjs.com/package/@mem0/vercel-ai-provider) is a library developed by **Mem0** to integrate with the Vercel AI SDK. This library brings enhanced AI interaction capabilities to your applications by introducing persistent memory functionality.
|
||||
|
||||
<Note type="info">
|
||||
Mem0 AI SDK now supports <strong>Vercel AI SDK V5</strong>.
|
||||
Mem0 AI SDK Provider v3.0.0 supports <strong>Vercel AI SDK v6</strong> (<code>LanguageModelV3</code> / <code>ProviderV3</code>). If you are upgrading from v2.x, see the <a href="https://ai-sdk.dev/docs/migration-guides/migration-guide-6-0">AI SDK v6 migration guide</a>.
|
||||
</Note>
|
||||
|
||||
## Overview
|
||||
|
||||
1. Offers persistent memory storage for conversational AI
|
||||
2. Enables smooth integration with the Vercel AI SDK
|
||||
3. Ensures compatibility with multiple LLM providers
|
||||
2. Enables smooth integration with the Vercel AI SDK v6
|
||||
3. Ensures compatibility with multiple LLM providers (OpenAI, Anthropic, Google, Groq, Cohere)
|
||||
4. Supports structured message formats for clarity
|
||||
5. Facilitates streaming response capabilities
|
||||
6. Attaches Mem0 memories as sources in responses for programmatic access
|
||||
|
||||
## Setup and Configuration
|
||||
|
||||
Install the SDK provider using npm:
|
||||
Install the SDK provider and AI SDK:
|
||||
|
||||
```bash
|
||||
npm install @mem0/vercel-ai-provider
|
||||
npm install @mem0/vercel-ai-provider ai@^6
|
||||
```
|
||||
|
||||
### Peer Dependencies
|
||||
|
||||
`@mem0/vercel-ai-provider` v3.0.0 requires:
|
||||
- `ai` v6+ (`^6.0.199`)
|
||||
- `@ai-sdk/provider` v3+ (`^3.0.10`)
|
||||
- Provider packages at v3+: `@ai-sdk/openai@^3`, `@ai-sdk/anthropic@^3`, `@ai-sdk/google@^3`, `@ai-sdk/groq@^3`, `@ai-sdk/cohere@^3`
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Setting Up Mem0
|
||||
@@ -41,7 +49,7 @@ npm install @mem0/vercel-ai-provider
|
||||
mem0ApiKey: "m0-xxx",
|
||||
apiKey: "provider-api-key",
|
||||
config: {
|
||||
// Options for LLM Provider
|
||||
// Options for the upstream LLM provider (e.g. baseURL)
|
||||
},
|
||||
// Optional Mem0 Global Config
|
||||
mem0Config: {
|
||||
@@ -57,154 +65,153 @@ npm install @mem0/vercel-ai-provider
|
||||
3. Add Memories to Enhance Context:
|
||||
|
||||
```typescript
|
||||
import { LanguageModelV2Prompt } from "@ai-sdk/provider";
|
||||
import { addMemories } from "@mem0/vercel-ai-provider";
|
||||
|
||||
const messages: LanguageModelV2Prompt = [
|
||||
const messages = [
|
||||
{ role: "user", content: [{ type: "text", text: "I love red cars." }] },
|
||||
];
|
||||
|
||||
await addMemories(messages, { user_id: "borat" });
|
||||
```
|
||||
|
||||
### Standalone Features:
|
||||
### Standalone Features
|
||||
|
||||
```typescript
|
||||
await addMemories(messages, { user_id: "borat", mem0ApiKey: "m0-xxx" });
|
||||
await retrieveMemories(prompt, { user_id: "borat", mem0ApiKey: "m0-xxx" });
|
||||
await getMemories(prompt, { user_id: "borat", mem0ApiKey: "m0-xxx" });
|
||||
```
|
||||
> For standalone features, such as `addMemories`, `retrieveMemories`, and `getMemories`, you must either set `MEM0_API_KEY` as an environment variable or pass it directly in the function call.
|
||||
```typescript
|
||||
await addMemories(messages, { user_id: "borat", mem0ApiKey: "m0-xxx" });
|
||||
await retrieveMemories(prompt, { user_id: "borat", mem0ApiKey: "m0-xxx" });
|
||||
await getMemories(prompt, { user_id: "borat", mem0ApiKey: "m0-xxx" });
|
||||
```
|
||||
|
||||
> `getMemories` will return raw memories in the form of an array of objects, while `retrieveMemories` will return a response in string format with a system prompt ingested with the retrieved memories.
|
||||
> For standalone features, such as `addMemories`, `retrieveMemories`, and `getMemories`, you must either set `MEM0_API_KEY` as an environment variable or pass it directly in the function call.
|
||||
|
||||
> `getMemories` returns an array of memory objects.
|
||||
> `getMemories` will return raw memories in the form of an array of objects, while `retrieveMemories` will return a response in string format with a system prompt ingested with the retrieved memories.
|
||||
|
||||
### 1. Basic Text Generation with Memory Context
|
||||
|
||||
```typescript
|
||||
import { generateText } from "ai";
|
||||
import { createMem0 } from "@mem0/vercel-ai-provider";
|
||||
```typescript
|
||||
import { generateText } from "ai";
|
||||
import { createMem0 } from "@mem0/vercel-ai-provider";
|
||||
|
||||
const mem0 = createMem0();
|
||||
const mem0 = createMem0();
|
||||
|
||||
const { text } = await generateText({
|
||||
model: mem0("gpt-4-turbo", { user_id: "borat" }),
|
||||
prompt: "Suggest me a good car to buy!",
|
||||
});
|
||||
```
|
||||
const { text } = await generateText({
|
||||
model: mem0("gpt-5-mini", { user_id: "borat" }),
|
||||
prompt: "Suggest me a good car to buy!",
|
||||
});
|
||||
```
|
||||
|
||||
### 2. Combining OpenAI Provider with Memory Utils
|
||||
|
||||
```typescript
|
||||
import { generateText } from "ai";
|
||||
import { openai } from "@ai-sdk/openai";
|
||||
import { retrieveMemories } from "@mem0/vercel-ai-provider";
|
||||
```typescript
|
||||
import { generateText } from "ai";
|
||||
import { openai } from "@ai-sdk/openai";
|
||||
import { retrieveMemories } from "@mem0/vercel-ai-provider";
|
||||
|
||||
const prompt = "Suggest me a good car to buy.";
|
||||
const memories = await retrieveMemories(prompt, { user_id: "borat" });
|
||||
const prompt = "Suggest me a good car to buy.";
|
||||
const memories = await retrieveMemories(prompt, { user_id: "borat" });
|
||||
|
||||
const { text } = await generateText({
|
||||
model: openai("gpt-4-turbo"),
|
||||
prompt: prompt,
|
||||
system: memories,
|
||||
});
|
||||
```
|
||||
const { text } = await generateText({
|
||||
model: openai("gpt-5-mini"),
|
||||
prompt: prompt,
|
||||
system: memories,
|
||||
});
|
||||
```
|
||||
|
||||
### 3. Structured Message Format with Memory
|
||||
|
||||
```typescript
|
||||
import { generateText } from "ai";
|
||||
import { createMem0 } from "@mem0/vercel-ai-provider";
|
||||
```typescript
|
||||
import { generateText } from "ai";
|
||||
import { createMem0 } from "@mem0/vercel-ai-provider";
|
||||
|
||||
const mem0 = createMem0();
|
||||
const mem0 = createMem0();
|
||||
|
||||
const { text } = await generateText({
|
||||
model: mem0("gpt-4-turbo", { user_id: "borat" }),
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "Suggest me a good car to buy." },
|
||||
{ type: "text", text: "Why is it better than the other cars for me?" },
|
||||
],
|
||||
},
|
||||
const { text } = await generateText({
|
||||
model: mem0("gpt-5-mini", { user_id: "borat" }),
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "Suggest me a good car to buy." },
|
||||
{ type: "text", text: "Why is it better than the other cars for me?" },
|
||||
],
|
||||
});
|
||||
```
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
### 3. Streaming Responses with Memory Context
|
||||
### 4. Streaming Responses with Memory Context
|
||||
|
||||
```typescript
|
||||
import { streamText } from "ai";
|
||||
import { createMem0 } from "@mem0/vercel-ai-provider";
|
||||
```typescript
|
||||
import { streamText } from "ai";
|
||||
import { createMem0 } from "@mem0/vercel-ai-provider";
|
||||
|
||||
const mem0 = createMem0();
|
||||
const mem0 = createMem0();
|
||||
|
||||
const { textStream } = streamText({
|
||||
model: mem0("gpt-4-turbo", {
|
||||
user_id: "borat",
|
||||
}),
|
||||
prompt: "Suggest me a good car to buy! Why is it better than the other cars for me? Give options for every price range.",
|
||||
});
|
||||
const { textStream } = streamText({
|
||||
model: mem0("gpt-5-mini", {
|
||||
user_id: "borat",
|
||||
}),
|
||||
prompt: "Suggest me a good car to buy! Why is it better than the other cars for me? Give options for every price range.",
|
||||
});
|
||||
|
||||
for await (const textPart of textStream) {
|
||||
process.stdout.write(textPart);
|
||||
}
|
||||
```
|
||||
for await (const textPart of textStream) {
|
||||
process.stdout.write(textPart);
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Generate Responses with Tools Call
|
||||
### 5. Generate Responses with Tools Call
|
||||
|
||||
```typescript
|
||||
import { generateText } from "ai";
|
||||
import { createMem0 } from "@mem0/vercel-ai-provider";
|
||||
import { z } from "zod";
|
||||
```typescript
|
||||
import { generateText, tool } from "ai";
|
||||
import { createMem0 } from "@mem0/vercel-ai-provider";
|
||||
import { z } from "zod";
|
||||
|
||||
const mem0 = createMem0({
|
||||
provider: "anthropic",
|
||||
apiKey: "anthropic-api-key",
|
||||
mem0Config: {
|
||||
// Global User ID
|
||||
user_id: "borat"
|
||||
}
|
||||
});
|
||||
const mem0 = createMem0({
|
||||
provider: "anthropic",
|
||||
apiKey: "anthropic-api-key",
|
||||
mem0Config: {
|
||||
user_id: "borat"
|
||||
}
|
||||
});
|
||||
|
||||
const prompt = "What the temperature in the city that I live in?"
|
||||
const result = await generateText({
|
||||
model: mem0('claude-sonnet-4-20250514'),
|
||||
tools: {
|
||||
weather: tool({
|
||||
description: 'Get the weather in a location',
|
||||
parameters: z.object({
|
||||
location: z.string().describe('The location to get the weather for'),
|
||||
}),
|
||||
execute: async ({ location }) => ({
|
||||
location,
|
||||
temperature: 72 + Math.floor(Math.random() * 21) - 10,
|
||||
}),
|
||||
}),
|
||||
},
|
||||
prompt: "What the temperature in the city that I live in?",
|
||||
});
|
||||
|
||||
const result = await generateText({
|
||||
model: mem0('claude-3-5-sonnet-20240620'),
|
||||
tools: {
|
||||
weather: tool({
|
||||
description: 'Get the weather in a location',
|
||||
parameters: z.object({
|
||||
location: z.string().describe('The location to get the weather for'),
|
||||
}),
|
||||
execute: async ({ location }) => ({
|
||||
location,
|
||||
temperature: 72 + Math.floor(Math.random() * 21) - 10,
|
||||
}),
|
||||
}),
|
||||
},
|
||||
prompt: prompt,
|
||||
});
|
||||
console.log(result);
|
||||
```
|
||||
|
||||
console.log(result);
|
||||
```
|
||||
### 6. Get Sources from Memory
|
||||
|
||||
### 5. Get sources from memory
|
||||
`generateText` and `streamText` responses include Mem0 memories as a source, giving you programmatic access to the memories that influenced the response:
|
||||
|
||||
```typescript
|
||||
const { text, sources } = await generateText({
|
||||
model: mem0("gpt-4-turbo"),
|
||||
prompt: "Suggest me a good car to buy!",
|
||||
model: mem0("gpt-5-mini", { user_id: "borat" }),
|
||||
prompt: "Suggest me a good car to buy!",
|
||||
});
|
||||
|
||||
// sources[0].title === "Mem0 Memories"
|
||||
// sources[0].providerMetadata.mem0.memories — array of memory objects
|
||||
console.log(sources);
|
||||
```
|
||||
|
||||
The same can be done for `streamText` as well.
|
||||
|
||||
### 6. File Support with Memory Context
|
||||
### 7. File Support with Memory Context
|
||||
|
||||
Mem0 AI SDK supports file processing with memory context. Here's an example of analyzing a PDF file:
|
||||
|
||||
@@ -226,15 +233,11 @@ const mem0 = createMem0({
|
||||
});
|
||||
|
||||
async function main() {
|
||||
// Read the PDF file
|
||||
const filePath = join(process.cwd(), 'my_pdf.pdf');
|
||||
const fileBuffer = readFileSync(filePath);
|
||||
|
||||
// Convert the file's arrayBuffer to a Base64 data URL
|
||||
const arrayBuffer = fileBuffer.buffer.slice(fileBuffer.byteOffset, fileBuffer.byteOffset + fileBuffer.byteLength);
|
||||
const uint8Array = new Uint8Array(arrayBuffer);
|
||||
|
||||
// Convert Uint8Array to an array of characters
|
||||
const charArray = Array.from(uint8Array, byte => String.fromCharCode(byte));
|
||||
const binaryString = charArray.join('');
|
||||
const base64Data = Buffer.from(binaryString, 'binary').toString('base64');
|
||||
@@ -274,24 +277,56 @@ main();
|
||||
|
||||
| Provider | Configuration Value |
|
||||
|----------|-------------------|
|
||||
| OpenAI | openai |
|
||||
| Anthropic | anthropic |
|
||||
| Google | google |
|
||||
| Groq | groq |
|
||||
| OpenAI | `openai` |
|
||||
| Anthropic | `anthropic` |
|
||||
| Google / Gemini | `google` or `gemini` |
|
||||
| Groq | `groq` |
|
||||
| Cohere | `cohere` |
|
||||
|
||||
> **Note**: You can use `google` as provider for Gemini (Google) models. They are same and internally they use `@ai-sdk/google` package.
|
||||
> **Note**: You can use either `google` or `gemini` as the provider value for Google Gemini models. Both map to the `@ai-sdk/google` package internally.
|
||||
|
||||
## Configuration Options
|
||||
|
||||
### Mem0ConfigSettings
|
||||
|
||||
These options can be passed per-request when creating a model instance:
|
||||
|
||||
| Option | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `user_id` | `string` | User identifier for memory scoping |
|
||||
| `agent_id` | `string` | Agent identifier |
|
||||
| `app_id` | `string` | Application identifier |
|
||||
| `run_id` | `string` | Run/session identifier |
|
||||
| `metadata` | `object` | Custom metadata for memories |
|
||||
| `filters` | `object` | Filters for memory search |
|
||||
| `infer` | `boolean` | Enable inference-based retrieval |
|
||||
| `top_k` | `number` | Number of memories to retrieve (default: 10) |
|
||||
| `threshold` | `number` | Relevance threshold for search |
|
||||
| `rerank` | `boolean` | Enable reranking of results |
|
||||
| `page` | `number` | Page number for pagination |
|
||||
| `page_size` | `number` | Results per page |
|
||||
|
||||
## Key Features
|
||||
|
||||
- `createMem0()`: Initializes a new Mem0 provider instance.
|
||||
- `retrieveMemories()`: Retrieves memory context for prompts.
|
||||
- `createMem0()`: Initializes a new Mem0 provider instance implementing `ProviderV3`.
|
||||
- `retrieveMemories()`: Retrieves memory context for prompts as a formatted system prompt string.
|
||||
- `getMemories()`: Get memories from your profile in array format.
|
||||
- `addMemories()`: Adds user memories to enhance contextual responses.
|
||||
|
||||
## Migrating from v2.x
|
||||
|
||||
If you're upgrading from `@mem0/vercel-ai-provider` v2.x:
|
||||
|
||||
1. **Upgrade AI SDK**: `npm install ai@^6` and update all `@ai-sdk/*` provider packages to `^3.x`
|
||||
2. **Remove deprecated params**: Remove `org_id`, `project_id`, `output_format`, `filter_memories`, `async_mode`, `enable_graph` from your config
|
||||
3. **Remove graph memory**: All graph-related options (`enable_graph`, graph prompts) have been removed. Graph memory is now a project-level setting on the Mem0 Platform
|
||||
4. **Update imports**: `LanguageModelV2Prompt` is now `LanguageModelV3Prompt` if you import types directly
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **User Identification**: Use a unique `user_id` for consistent memory retrieval.
|
||||
2. **Memory Cleanup**: Regularly clean up unused memory data.
|
||||
3. **Sources**: Access `result.sources` to inspect which memories influenced the response.
|
||||
|
||||
> **Note**: We also have support for `agent_id`, `app_id`, and `run_id`. Refer [Docs](/api-reference/memory/add-memories).
|
||||
|
||||
|
||||
+5
-2
@@ -228,6 +228,7 @@ If the user is on a pre-current major (Python < 2, TS < 3, or Platform `output_f
|
||||
- [OSS v2 to v3 Migration](https://docs.mem0.ai/migration/oss-v2-to-v3) [OSS]: Use when upgrading a self-hosted deployment across major versions.
|
||||
- [Platform v2 to v3 Migration](https://docs.mem0.ai/migration/platform-v2-to-v3) [Platform]: Use when upgrading a Platform integration across major versions.
|
||||
- [API Changes](https://docs.mem0.ai/migration/api-changes) [Both]: Use when the upgrade involves API surface changes.
|
||||
- [Server pgvector Image Upgrade](https://docs.mem0.ai/migration/server-pgvector-upgrade) [OSS]: Use when upgrading the self-hosted server Docker image from ankane/pgvector to pgvector/pgvector.
|
||||
- [Changelog](https://docs.mem0.ai/changelog/highlights) [Both]: Use when the user asks what shipped recently.
|
||||
|
||||
## Open Source
|
||||
@@ -258,6 +259,7 @@ If the user is on a pre-current major (Python < 2, TS < 3, or Platform `output_f
|
||||
- [Camel AI](https://docs.mem0.ai/integrations/camel-ai) [Both]: Use when the user is on Camel AI.
|
||||
- [ChatDev](https://docs.mem0.ai/integrations/chatdev) [Both]: 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.
|
||||
- [OpenAI Agents SDK](https://docs.mem0.ai/integrations/openai-agents-sdk) [Both]: Use when the user is on the OpenAI Agents SDK.
|
||||
- [Google AI ADK](https://docs.mem0.ai/integrations/google-ai-adk) [Both]: Use when the user is on Google's Agent Development Kit.
|
||||
- [Mastra](https://docs.mem0.ai/integrations/mastra) [Both]: Use when the user is on Mastra (TypeScript).
|
||||
@@ -396,9 +398,9 @@ Each subdirectory is a Claude Code Skill (`SKILL.md` + supporting assets). Load
|
||||
|
||||
### Editor Plugin (shared glue)
|
||||
|
||||
Source: https://github.com/mem0ai/mem0/tree/main/mem0-plugin
|
||||
Source: https://github.com/mem0ai/mem0/tree/main/integrations/mem0-plugin
|
||||
|
||||
The `mem0-plugin/` directory provides MCP server connection, lifecycle hooks, and skill bundling for Claude Code, Cursor, Codex, OpenCode, and Antigravity. It exposes 9 MCP tools: `add_memory`, `search_memories`, `get_memories`, `get_memory`, `update_memory`, `delete_memory`, `delete_all_memories`, `delete_entities`, `list_entities`.
|
||||
The `integrations/mem0-plugin/` directory provides MCP server connection, lifecycle hooks, and skill bundling for Claude Code, Cursor, Codex, OpenCode, and Antigravity. It exposes 9 MCP tools: `add_memory`, `search_memories`, `get_memories`, `get_memory`, `update_memory`, `delete_memory`, `delete_all_memories`, `delete_entities`, `list_entities`.
|
||||
|
||||
Editor-specific setup docs (already listed above under `## Integrations > AI Coding Tools`):
|
||||
|
||||
@@ -474,6 +476,7 @@ Everything below is OSS-only provider configuration. Skip this entire section wh
|
||||
- [Elasticsearch](https://docs.mem0.ai/components/vectordbs/dbs/elasticsearch) [OSS]: Use when Elasticsearch is the backing store.
|
||||
- [OpenSearch](https://docs.mem0.ai/components/vectordbs/dbs/opensearch) [OSS]: Use when OpenSearch is the backing store.
|
||||
- [Supabase](https://docs.mem0.ai/components/vectordbs/dbs/supabase) [OSS]: Use when Supabase with pgvector is the backing store.
|
||||
- [Neon](https://docs.mem0.ai/components/vectordbs/dbs/neon) [OSS]: Use when Neon Postgres with pgvector is the backing store.
|
||||
- [Upstash Vector](https://docs.mem0.ai/components/vectordbs/dbs/upstash-vector) [OSS]: Use for serverless Upstash Vector.
|
||||
- [Vectorize](https://docs.mem0.ai/components/vectordbs/dbs/vectorize) [OSS]: Use when the store is Cloudflare Vectorize.
|
||||
- [Vertex AI Vector Search](https://docs.mem0.ai/components/vectordbs/dbs/vertex_ai) [OSS]: Use when the store is Google Cloud Vertex Vector Search.
|
||||
|
||||
@@ -6,17 +6,15 @@ versionFrom: "Open Source"
|
||||
versionTo: "Platform"
|
||||
---
|
||||
|
||||
# Migrate from Open Source to Platform
|
||||
|
||||
Move your Mem0 implementation to managed infrastructure with enterprise features.
|
||||
## Overview
|
||||
|
||||
| Scope | Effort | Downtime |
|
||||
| --------------------- | -------------- | ---------------------------- |
|
||||
| Infrastructure & Code | Low (~30 mins) | None (Parallel run possible) |
|
||||
|
||||
<Info>
|
||||
<Note>
|
||||
Using Mem0 Open Source with **hosted Qdrant**? You can migrate your existing memories to Mem0 Platform with a one-line script below.
|
||||
</Info>
|
||||
</Note>
|
||||
|
||||
<Info>
|
||||
**Why migrate to Platform?**
|
||||
@@ -30,12 +28,29 @@ Move your Mem0 implementation to managed infrastructure with enterprise features
|
||||
- **Production Grade**: Auto-scaling, high availability, dedicated support
|
||||
</Info>
|
||||
|
||||
## Plan
|
||||
### Plan
|
||||
|
||||
1. **Sign up**: Create an account on <a href="https://app.mem0.ai?utm_source=oss&utm_medium=migration-oss-to-platform" rel="nofollow">Mem0 Platform</a>.
|
||||
2. **Get API Key**: Navigate to **Settings > API Keys** and generate a new key.
|
||||
3. **Review Usage**: Identify where you instantiate `Memory` and where you call `search` or `get_all`.
|
||||
|
||||
## Migrate with Agent Skill
|
||||
|
||||
Paste this prompt into your coding agent. It uses a migration skill to produce a plan; once you review and approve it, the agent implements the changes.
|
||||
|
||||
```text
|
||||
Migrate my project from Mem0 OSS to the Mem0 Platform SDK using the
|
||||
mem0-oss-to-platform skill in the mem0ai/mem0 repo, at
|
||||
skills/mem0-oss-to-platform/
|
||||
|
||||
Get the skill whichever way is easiest:
|
||||
- install it: npx skills add https://github.com/mem0ai/mem0 --skill mem0-oss-to-platform
|
||||
- if the mem0 repo is cloned locally, read it from skills/mem0-oss-to-platform/
|
||||
- otherwise fetch that folder from github.com/mem0ai/mem0 (SKILL.md + references/)
|
||||
|
||||
Then read SKILL.md and begin the migration.
|
||||
```
|
||||
|
||||
## Migrate
|
||||
|
||||
### 1. Import Memories Into Platform
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
---
|
||||
title: "Server: Upgrading the pgvector Docker Image"
|
||||
description: "Migrate your self-hosted Mem0 server from the archived ankane/pgvector image to the official pgvector/pgvector image."
|
||||
icon: "arrow-right"
|
||||
iconType: "solid"
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The self-hosted Mem0 server has upgraded its PostgreSQL Docker image:
|
||||
|
||||
| | Before | After |
|
||||
| --- | --- | --- |
|
||||
| Docker image | `ankane/pgvector:v0.5.1` | `pgvector/pgvector:pg17` |
|
||||
| PostgreSQL | 15 | 17 |
|
||||
| pgvector | 0.5.1 | 0.8.0 |
|
||||
| Credentials | Hardcoded `postgres` / `postgres` | Set via `POSTGRES_USER` / `POSTGRES_PASSWORD` env vars |
|
||||
|
||||
<Warning>
|
||||
The `ankane/pgvector` image is **archived and no longer maintained**. The new `pgvector/pgvector` image is the official distribution maintained by the pgvector project.
|
||||
</Warning>
|
||||
|
||||
<Info>
|
||||
**Should you migrate?**
|
||||
- You are running the Mem0 server via `docker-compose.yaml` in the `server/` directory.
|
||||
- You want to stay on a maintained, actively-patched PostgreSQL + pgvector image.
|
||||
- You want pgvector 0.8.0 features (improved HNSW performance, parallel index builds).
|
||||
</Info>
|
||||
|
||||
## Fresh Installs
|
||||
|
||||
No migration is needed. Copy the example env file, set your password, and start the stack:
|
||||
|
||||
```bash
|
||||
cd server
|
||||
cp .env.example .env
|
||||
# Edit .env — set POSTGRES_PASSWORD (required) and OPENAI_API_KEY at minimum
|
||||
make up
|
||||
```
|
||||
|
||||
## Migrating an Existing Install
|
||||
|
||||
PostgreSQL 17 cannot read data files created by PostgreSQL 15 directly. You need to export your data from the old container and import it into the new one.
|
||||
|
||||
### 1. Back Up Your Data
|
||||
|
||||
With the **old** stack still running:
|
||||
|
||||
```bash
|
||||
cd server
|
||||
docker compose exec -T postgres pg_dumpall -U postgres > mem0_backup.sql
|
||||
```
|
||||
|
||||
Verify the dump is non-empty:
|
||||
|
||||
```bash
|
||||
ls -lh mem0_backup.sql
|
||||
```
|
||||
|
||||
<Warning>
|
||||
Do not skip this step. The next step permanently deletes your Postgres data volume.
|
||||
</Warning>
|
||||
|
||||
### 2. Stop the Old Stack and Remove the Volume
|
||||
|
||||
```bash
|
||||
docker compose down
|
||||
docker compose down -v
|
||||
```
|
||||
|
||||
### 3. Update Your `.env`
|
||||
|
||||
Postgres credentials are no longer hardcoded in `docker-compose.yaml`. Add them to your `.env`:
|
||||
|
||||
```bash
|
||||
POSTGRES_HOST=postgres
|
||||
POSTGRES_PORT=5432
|
||||
POSTGRES_DB=postgres
|
||||
POSTGRES_USER=postgres
|
||||
POSTGRES_PASSWORD=<your-password> # required — compose will refuse to start without it
|
||||
POSTGRES_COLLECTION_NAME=memories
|
||||
```
|
||||
|
||||
<Info>
|
||||
`POSTGRES_PASSWORD` is **required** — `docker compose up` will refuse to start without it. If you previously relied on the hardcoded default, set `POSTGRES_PASSWORD=postgres`.
|
||||
</Info>
|
||||
|
||||
### 4. Start Only Postgres
|
||||
|
||||
Start **only** the Postgres container first — do **not** start the mem0 API yet.
|
||||
The API runs `alembic upgrade head` on startup, which creates empty tables that
|
||||
would conflict with the restore.
|
||||
|
||||
```bash
|
||||
docker compose up -d postgres
|
||||
```
|
||||
|
||||
Wait for Postgres to become healthy:
|
||||
|
||||
```bash
|
||||
docker compose exec -T postgres pg_isready -q && echo "ready" || echo "not ready"
|
||||
```
|
||||
|
||||
### 5. Restore Your Data
|
||||
|
||||
```bash
|
||||
docker compose exec -T postgres psql -U postgres < mem0_backup.sql
|
||||
```
|
||||
|
||||
You may see notices like `role "postgres" already exists` — these are safe to ignore.
|
||||
|
||||
<Warning>
|
||||
You must restore **before** starting the mem0 API container. The API runs
|
||||
database migrations on startup which create empty tables — restoring after
|
||||
that would fail with duplicate-key errors and lose your API keys and settings.
|
||||
</Warning>
|
||||
|
||||
### 6. Start the API
|
||||
|
||||
Now start the mem0 API container. Alembic will detect the existing tables and
|
||||
only apply any new migrations:
|
||||
|
||||
```bash
|
||||
docker compose up -d mem0
|
||||
```
|
||||
|
||||
### 7. Verify
|
||||
|
||||
```bash
|
||||
# Check service health
|
||||
cd server && make health
|
||||
|
||||
# Confirm memories are accessible
|
||||
curl -s http://localhost:8888/memories?user_id=<your-user-id> \
|
||||
-H "X-API-Key: <your-api-key>"
|
||||
```
|
||||
|
||||
## Rollback
|
||||
|
||||
If something goes wrong, revert the image tag in `docker-compose.yaml`:
|
||||
|
||||
```yaml
|
||||
postgres:
|
||||
image: ankane/pgvector:v0.5.1
|
||||
```
|
||||
|
||||
Then destroy the new volume, start the old image, and restore from your backup:
|
||||
|
||||
```bash
|
||||
docker compose down -v
|
||||
docker compose up -d --build
|
||||
docker compose exec -T postgres psql -U postgres < mem0_backup.sql
|
||||
```
|
||||
|
||||
## Need Help?
|
||||
|
||||
- Join our [Discord community](https://mem0.ai/discord) for real-time support
|
||||
- Open an issue on [GitHub](https://github.com/mem0ai/mem0/issues)
|
||||
@@ -4,22 +4,7 @@ description: Fine-grained metadata queries for precise OSS memory retrieval.
|
||||
icon: "filter"
|
||||
---
|
||||
|
||||
Enhanced metadata filtering in Mem0 1.0.0 lets you run complex queries across memory metadata. Combine comparisons, logical operators, and wildcard matches to zero in on the exact memories your agent needs.
|
||||
|
||||
<Info>
|
||||
**You’ll use this when…**
|
||||
- Retrieval must respect multiple metadata conditions before returning context.
|
||||
- You need to mix numeric, boolean, and string filters in a single query.
|
||||
- Agents rely on deterministic filtering instead of broad semantic search alone.
|
||||
</Info>
|
||||
|
||||
<Warning>
|
||||
Enhanced filtering requires Mem0 1.0.0 or later and a vector store that supports the operators you enable. Unsupported operators fall back to simple equality filters.
|
||||
</Warning>
|
||||
|
||||
<Note>
|
||||
The TypeScript SDK accepts the same filter shape shown here—transpose the dictionaries to objects and reuse the keys unchanged.
|
||||
</Note>
|
||||
Enhanced metadata filtering in Mem0 lets you run complex queries across memory metadata. Combine comparisons, logical operators, and wildcard matches to zero in on the exact memories your agent needs.
|
||||
|
||||
---
|
||||
|
||||
@@ -148,8 +133,8 @@ Combine filters with `AND`, `OR`, and `NOT` to express complex decision trees. N
|
||||
results = m.search(
|
||||
"complex query",
|
||||
filters={
|
||||
"user_id": "alice",
|
||||
"AND": [
|
||||
{"user_id": "alice"},
|
||||
{"category": "work"},
|
||||
{"priority": {"gte": 7}},
|
||||
{"status": {"ne": "completed"}}
|
||||
@@ -161,15 +146,11 @@ results = m.search(
|
||||
results = m.search(
|
||||
"flexible query",
|
||||
filters={
|
||||
"AND": [
|
||||
{"user_id": "alice"},
|
||||
{
|
||||
"OR": [
|
||||
{"category": "urgent"},
|
||||
{"priority": {"gte": 9}},
|
||||
{"deadline": {"contains": "today"}}
|
||||
]
|
||||
}
|
||||
"user_id": "alice",
|
||||
"OR": [
|
||||
{"category": "urgent"},
|
||||
{"priority": {"gte": 9}},
|
||||
{"deadline": {"contains": "today"}}
|
||||
]
|
||||
}
|
||||
)
|
||||
@@ -178,14 +159,10 @@ results = m.search(
|
||||
results = m.search(
|
||||
"exclusion query",
|
||||
filters={
|
||||
"AND": [
|
||||
{"user_id": "alice"},
|
||||
{
|
||||
"NOT": [
|
||||
{"category": "archived"},
|
||||
{"status": "deleted"}
|
||||
]
|
||||
}
|
||||
"user_id": "alice",
|
||||
"NOT": [
|
||||
{"category": "archived"},
|
||||
{"status": "deleted"}
|
||||
]
|
||||
}
|
||||
)
|
||||
@@ -194,8 +171,8 @@ results = m.search(
|
||||
results = m.search(
|
||||
"advanced query",
|
||||
filters={
|
||||
"user_id": "alice",
|
||||
"AND": [
|
||||
{"user_id": "alice"},
|
||||
{
|
||||
"OR": [
|
||||
{"category": "work"},
|
||||
@@ -248,8 +225,8 @@ config = {
|
||||
```python
|
||||
# More efficient: Filter on indexed fields first
|
||||
good_filters = {
|
||||
"user_id": "alice",
|
||||
"AND": [
|
||||
{"user_id": "alice"},
|
||||
{"category": "work"},
|
||||
{"content": {"contains": "meeting"}}
|
||||
]
|
||||
@@ -257,9 +234,9 @@ good_filters = {
|
||||
|
||||
# Less efficient: Complex operations first
|
||||
avoid_filters = {
|
||||
"user_id": "alice",
|
||||
"AND": [
|
||||
{"description": {"icontains": "complex text search"}},
|
||||
{"user_id": "alice"}
|
||||
{"description": {"icontains": "complex text search"}}
|
||||
]
|
||||
}
|
||||
```
|
||||
@@ -302,8 +279,8 @@ results = m.search(
|
||||
results = m.search(
|
||||
"query",
|
||||
filters={
|
||||
"user_id": "alice",
|
||||
"AND": [
|
||||
{"user_id": "alice"},
|
||||
{"category": "work"},
|
||||
{"status": {"ne": "archived"}},
|
||||
{"priority": {"gte": 5}}
|
||||
@@ -327,8 +304,8 @@ results = m.search(
|
||||
results = m.search(
|
||||
"What tasks need attention?",
|
||||
filters={
|
||||
"user_id": "project_manager",
|
||||
"AND": [
|
||||
{"user_id": "project_manager"},
|
||||
{"project": {"in": ["alpha", ""]}},
|
||||
{"priority": {"gte": 8}},
|
||||
{"status": {"ne": "completed"}},
|
||||
@@ -354,8 +331,8 @@ results = m.search(
|
||||
results = m.search(
|
||||
"pending support issues",
|
||||
filters={
|
||||
"agent_id": "support_bot",
|
||||
"AND": [
|
||||
{"agent_id": "support_bot"},
|
||||
{"ticket_status": {"ne": "resolved"}},
|
||||
{"priority": {"in": ["high", "critical"]}},
|
||||
{"created_date": {"gte": "2024-01-01"}},
|
||||
@@ -380,8 +357,8 @@ results = m.search(
|
||||
results = m.search(
|
||||
"recommend content",
|
||||
filters={
|
||||
"user_id": "reader123",
|
||||
"AND": [
|
||||
{"user_id": "reader123"},
|
||||
{
|
||||
"OR": [
|
||||
{"genre": {"in": ["sci-fi", "fantasy"]}},
|
||||
|
||||
@@ -226,6 +226,20 @@ curl -X POST http://localhost:8888/search \
|
||||
}'
|
||||
```
|
||||
|
||||
Set `explain` to inspect the scoring signals used by OSS hybrid search:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8888/search \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"query": "vegetable pizza",
|
||||
"user_id": "alice",
|
||||
"explain": true
|
||||
}'
|
||||
```
|
||||
|
||||
Each returned memory includes `score_details` only when explanation mode is enabled.
|
||||
|
||||
### Explore with OpenAPI docs
|
||||
|
||||
1. Navigate to `http://localhost:8888/docs` (Compose) or `http://localhost:8000/docs` (raw Docker / uvicorn).
|
||||
|
||||
@@ -45,10 +45,12 @@ Let your assistant execute an end-to-end workflow in an existing repo. Invoked a
|
||||
```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
|
||||
```
|
||||
|
||||
- `/mem0-integrate` — wire Mem0 into an existing repository using a goal-driven, test-first pipeline. Detects the stack, asks whether to use Platform or OSS, writes failing tests first, and keeps the integration additive and feature-flagged.
|
||||
- `/mem0-test-integration` — verify what `/mem0-integrate` produced. Runs the repo's native test suite and a real end-to-end smoke flow against your API key, then produces a scorecard.
|
||||
- `/mem0-oss-to-platform` — migrate an existing project from Mem0 OSS to the hosted Platform SDK. Audits where Mem0 is used, writes a reviewable migration plan, then executes it on approval.
|
||||
|
||||
See the [skills index](https://github.com/mem0ai/mem0/tree/main/skills) for the full catalog.
|
||||
|
||||
|
||||
@@ -16,10 +16,10 @@ type RetrievedMemory = {
|
||||
|
||||
type NewMemory = {
|
||||
id: string;
|
||||
data: {
|
||||
data?: {
|
||||
memory: string;
|
||||
};
|
||||
event: "ADD" | "DELETE";
|
||||
event: "ADD" | "UPDATE" | "DELETE" | "GET";
|
||||
};
|
||||
|
||||
type NewMemoryAnnotation = {
|
||||
@@ -47,14 +47,16 @@ const useMemories = (): Memory[] => {
|
||||
() =>
|
||||
annotations?.filter(isMemoryAnnotation).flatMap((a) => {
|
||||
if (a.type === "mem0-update") {
|
||||
return a.memories.map(
|
||||
(m): Memory => ({
|
||||
event: m.event,
|
||||
id: m.id,
|
||||
memory: m.data.memory,
|
||||
score: 1,
|
||||
})
|
||||
);
|
||||
return a.memories
|
||||
.filter((m): m is NewMemory & { data: { memory: string } } => m.data != null)
|
||||
.map(
|
||||
(m): Memory => ({
|
||||
event: m.event,
|
||||
id: m.id,
|
||||
memory: m.data.memory,
|
||||
score: 1,
|
||||
})
|
||||
);
|
||||
} else if (a.type === "mem0-get") {
|
||||
return a.memories.map((m) => ({
|
||||
event: "GET",
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"ai": "^4.1.46",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"js-cookie": "^3.0.5",
|
||||
"js-cookie": "^3.0.6",
|
||||
"lucide-react": "^0.477.0",
|
||||
"next": "15.5.18",
|
||||
"react": "^19.0.0",
|
||||
|
||||
@@ -555,7 +555,7 @@
|
||||
"# - Enables creation of AI agents with long-term memory and learning abilities.\n",
|
||||
"# - Improves consistency and reduces repetition in user-agent interactions.\n",
|
||||
"\n",
|
||||
"from cookbooks.helper.mem0_teachability import Mem0Teachability\n",
|
||||
"from helper.mem0_teachability import Mem0Teachability\n",
|
||||
"\n",
|
||||
"teachability = Mem0Teachability(\n",
|
||||
" verbosity=2, # for visibility of what's happening\n",
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mem0",
|
||||
"version": "0.2.7",
|
||||
"version": "0.2.10",
|
||||
"description": "Persistent memory for Claude Code. Remembers decisions, patterns, and preferences across sessions.",
|
||||
"author": {
|
||||
"name": "Mem0",
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mem0",
|
||||
"version": "0.2.7",
|
||||
"version": "0.2.10",
|
||||
"description": "Persistent memory for Codex. Remembers decisions, patterns, and preferences across sessions.",
|
||||
"author": {
|
||||
"name": "Mem0",
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mem0",
|
||||
"version": "0.2.7",
|
||||
"version": "0.2.10",
|
||||
"description": "Mem0 memory layer for AI applications. Add persistent memory, personalization, and semantic search using the Mem0 Platform MCP server.",
|
||||
"author": {
|
||||
"name": "Mem0",
|
||||
@@ -0,0 +1,48 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to the `@mem0/opencode-plugin` will be documented in this file.
|
||||
|
||||
## 0.1.3 — File-context injection, session summaries & activity timeline, anonymous telemetry
|
||||
|
||||
### Added
|
||||
|
||||
- **File-context injection (`tool.execute.before` / Read):** Before the agent reads a file, the plugin searches mem0 for memories referencing that file path and injects prior work as system context. Gates on file size (>= 1,500 bytes). Gives the agent "I've worked on this file before" awareness automatically.
|
||||
- **Stop hook session summary (`experimental.session.compacting`):** Enhanced session compaction to store a structured `session_summary` memory with `infer=True`, letting the mem0 backend AI extract key facts (request, decisions, learnings, next steps). Previously only stored a raw stats string.
|
||||
- **SessionStart activity timeline:** The initial memory loading now formats recent memories with type icons (⚖️ decision, 🔴 bug_fix, 🔵 task_learning, etc.) and relative age indicators (2h ago, 1d ago) instead of bare text. Provides a visual "Recent Activity" timeline on first message.
|
||||
- **PostHog telemetry (`telemetry.ts`):** Anonymous, fire-and-forget usage events. Opt out with `MEM0_TELEMETRY=false`. Only fires when an API key is present; never sends memory content, prompts, or the API key — only an anonymized `sha256(apiKey)[:32]` identity plus event type, platform, and plugin version. Emits the same schema as the Mem0 editor plugin (`plugin.*` events, `source: "plugin"`, `platform: "opencode"`) so OpenCode appears as a `platform` in the shared plugin dashboard. Events: `plugin.session_start` (with memory count) and `plugin.tool_use` (`add` / `search` / `update` / `delete`).
|
||||
|
||||
### Changed
|
||||
|
||||
- **`experimental.session.compacting` handler:** Now stores `metadata.type=session_summary` with `metadata.source=opencode-stop` instead of `metadata.type=session_state` with `metadata.source=pre-compaction`. Includes a structured prompt that instructs mem0's AI to extract request, decisions, learnings, and next steps.
|
||||
- **Initial context formatting:** Memories shown on first message now include type icons and age labels for quick scanning.
|
||||
|
||||
## 0.1.2 — Automatic coding categories & global search
|
||||
|
||||
### Added
|
||||
|
||||
- **Auto-configured coding categories:** The plugin now automatically sets up 17 coding categories (e.g. `architecture_decisions`, `api_design`, `security`, `debugging_notes`) on the Mem0 project at startup. Runs in the background on every session start via `autoSetupCategories()`, is fully idempotent, and never blocks initialization. Uses SHA-256 fingerprints of the category list and API key — stored in `~/.mem0/categories_setup.json` — to skip redundant API calls on subsequent sessions.
|
||||
- **Global search mode (`global_search` setting):** New `global_search` toggle in `~/.mem0/settings.json` (default: `false`). When enabled, all `search_memories` and `get_memories` calls use `{"OR": [{"user_id": "*"}]}` instead of the per-user per-project `AND` filter — returning all memories across all users and all `app_id` scopes. Writes (`add_memory`) still tag with the current `user_id` and `app_id`. Applies to all plugin search paths: initial load, per-message recall, resume detection, error-pattern lookup, and compaction context.
|
||||
- **`/mem0:switch-project --global` / `--no-global`:** Enables or disables global search via the switch-project skill. Persists to `~/.mem0/settings.json`. No manual config editing needed.
|
||||
- **`MEM0_GLOBAL_SEARCH` environment variable:** Exported to child shells via the `shell.env` hook (`"true"` or `"false"`).
|
||||
|
||||
### Changed
|
||||
|
||||
- **Search filters are now dynamic:** All search paths throughout the plugin construct filters based on the `global_search` setting instead of always using `AND [user_id, app_id]`.
|
||||
- **Resume-context searches broadened:** Resume and error-pattern searches no longer include `metadata.type` sub-filters (`session_state`, `decision`, `anti_pattern`, `bug_fix`), broadening recall.
|
||||
- **System context message updated:** Informs the model when global search is active (`"Global search is ON — searches return all memories across all users and projects. Writes still use user_id=..., app_id=..."`).
|
||||
- **`/mem0:onboard` Step 5 is no longer interactive:** Removed the manual category installation prompt. Categories now configure automatically in the background; the onboarding step only verifies status and stores a fallback `project_profile` memory if the background run hasn't finished yet.
|
||||
- **`/mem0:switch-project` skill expanded:** Description and execution updated to document the `--global` and `--no-global` flags alongside the existing project-name argument.
|
||||
|
||||
## 0.1.1
|
||||
|
||||
- CI/CD publish flow test (`#5288`).
|
||||
- Fixed tsconfig, added `publishConfig` and bun lockfile (`#5273`).
|
||||
- Renamed package to `@mem0/opencode-plugin` (`#5272`).
|
||||
- Added plugin array to bundled `opencode.json` (`#5271`).
|
||||
|
||||
## 0.1.0 — Initial release
|
||||
|
||||
- **OpenCode plugin** (`@mem0/opencode-plugin` on npm): Pure TypeScript plugin using the `mem0ai` TS SDK — no Python, no shell scripts. Hooks into all 6 OpenCode events (`chat.message`, `tool.execute.before`, `tool.execute.after`, `experimental.chat.system.transform`, `experimental.session.compacting`, `shell.env`). Features: session start memory loading, per-prompt semantic search, error pattern detection with memory lookup, resume/remember intent detection, auto-capture every 3rd message, periodic save nudges, full metadata defaults injection (confidence, source, type, session_id, files, branch), identity injection for search/get/delete filters, type-filtered error pre-fetch (anti_pattern + bug_fix), pre-compaction memory capture, MEMORY.md write blocking, and secret redaction.
|
||||
- **16 OpenCode-native skills** bundled in `opencode-skills/`: `context-loader`, `dream`, `export`, `forget`, `health`, `import`, `list-projects`, `mem0` (SDK reference), `memory-reviewer`, `onboard`, `peek`, `pin`, `remember`, `stats`, `switch-project`, `tour`. All skills are pure MCP-tool-based — no Python scripts, no shell scripts, no Claude Code dependencies.
|
||||
- **Auto-install skills and commands (`installSkills()`):** On plugin load, copies all 16 skills to `.opencode/skills/` and creates command wrapper files in `.opencode/commands/` so they appear in the OpenCode `/` palette.
|
||||
- **CLI installer (`cli.ts`):** `bunx @mem0/opencode-plugin install` auto-configures plugin and MCP server in `~/.config/opencode/opencode.json`.
|
||||
+1
-1
@@ -17,7 +17,7 @@ opencode plugin @mem0/opencode-plugin
|
||||
**Or let your agent do it** — paste this into OpenCode:
|
||||
|
||||
```
|
||||
Install @mem0/opencode-plugin by following https://raw.githubusercontent.com/mem0ai/mem0/main/mem0-plugin/.opencode-plugin/README.md
|
||||
Install @mem0/opencode-plugin by following https://raw.githubusercontent.com/mem0ai/mem0/main/integrations/mem0-plugin/.opencode-plugin/README.md
|
||||
```
|
||||
|
||||
All commands auto-add the plugin and MCP server to your `~/.config/opencode/opencode.json`. No manual config needed.
|
||||
+2
-2
@@ -6,7 +6,7 @@
|
||||
"name": "@mem0/opencode-plugin",
|
||||
"dependencies": {
|
||||
"@opencode-ai/plugin": "^1.0.162",
|
||||
"mem0ai": "^3.0.5",
|
||||
"mem0ai": "^3.0.7",
|
||||
},
|
||||
"devDependencies": {
|
||||
"bun-types": ">=1.3.14",
|
||||
@@ -462,7 +462,7 @@
|
||||
|
||||
"md5": ["md5@2.3.0", "", { "dependencies": { "charenc": "0.0.2", "crypt": "0.0.2", "is-buffer": "~1.1.6" } }, "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g=="],
|
||||
|
||||
"mem0ai": ["mem0ai@3.0.5", "", { "dependencies": { "axios": "^1.15.2", "openai": "^4.93.0", "uuid": "9.0.1", "zod": "^3.24.1" }, "peerDependencies": { "@anthropic-ai/sdk": "^0.40.1", "@azure/identity": "^4.0.0", "@azure/search-documents": "^12.0.0", "@cloudflare/workers-types": "^4.20250504.0", "@google/genai": "^1.2.0", "@langchain/core": "^1.1.47", "@mistralai/mistralai": "^1.5.2", "@qdrant/js-client-rest": "1.13.0", "@supabase/supabase-js": "^2.49.1", "@types/jest": "29.5.14", "@types/pg": "8.11.0", "better-sqlite3": "^12.6.2", "cloudflare": "^4.2.0", "compromise": "^14.0.0", "groq-sdk": "0.3.0", "natural": "^8.0.1", "ollama": "^0.5.14", "pg": "8.11.3", "redis": "^4.6.13" } }, "sha512-W/R59d5fMpUGHhPEnyoo36GSz5NFJbAs+vS4BxoIvE+t19mIJfoz/2FJSKIw80mT8AkeNYpDfzc/DYRu2IzYIw=="],
|
||||
"mem0ai": ["mem0ai@3.0.7", "", { "dependencies": { "axios": "^1.16.0", "openai": "^4.93.0", "uuid": "9.0.1", "zod": "^3.24.1" }, "peerDependencies": { "@anthropic-ai/sdk": "^0.40.1", "@azure/identity": "^4.0.0", "@azure/search-documents": "^12.0.0", "@cloudflare/workers-types": "^4.20250504.0", "@google/genai": "^1.40.0", "@langchain/core": "^1.1.47", "@mistralai/mistralai": "^1.5.2", "@qdrant/js-client-rest": "^1.18.0", "@supabase/supabase-js": "^2.49.1", "@types/jest": "29.5.14", "@types/pg": "8.11.0", "better-sqlite3": "^12.6.2", "cloudflare": "^4.2.0", "compromise": "^14.0.0", "groq-sdk": "0.3.0", "natural": "^8.0.1", "ollama": "^0.5.14", "pg": "8.11.3", "redis": "^4.6.13" } }, "sha512-CUHzX7DyeKTHcI3aDsSqY9LXTD7GcFxf988796TuOa4yJgGuF2Xd2NROcBhVFRo3r9y8fVmbo3c5TF9jv1KlKw=="],
|
||||
|
||||
"memjs": ["memjs@1.3.2", "", {}, "sha512-qUEg2g8vxPe+zPn09KidjIStHPtoBO8Cttm8bgJFWWabbsjQ9Av9Ky+6UcvKx6ue0LLb/LEhtcyQpRyKfzeXcg=="],
|
||||
|
||||
+239
-66
@@ -6,6 +6,10 @@ import { userInfo } from "os";
|
||||
import { basename, resolve, dirname } from "path";
|
||||
import { randomBytes } from "crypto";
|
||||
import { existsSync, readdirSync, cpSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
||||
import { homedir } from "os";
|
||||
import { join } from "path";
|
||||
import { createHash } from "crypto";
|
||||
import { captureEvent } from "./telemetry";
|
||||
|
||||
async function getUserId(): Promise<string> {
|
||||
if (process.env.MEM0_USER_ID) return process.env.MEM0_USER_ID;
|
||||
@@ -63,6 +67,100 @@ function redact(text: string): string {
|
||||
return out;
|
||||
}
|
||||
|
||||
function formatAge(createdAt: string): string {
|
||||
try {
|
||||
const dt = new Date(createdAt);
|
||||
const now = Date.now();
|
||||
const seconds = Math.floor((now - dt.getTime()) / 1000);
|
||||
if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;
|
||||
if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`;
|
||||
const days = Math.floor(seconds / 86400);
|
||||
if (days === 1) return "1d ago";
|
||||
if (days < 30) return `${days}d ago`;
|
||||
return `${Math.floor(days / 30)}mo ago`;
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
const TYPE_ICONS: Record<string, string> = {
|
||||
decision: "⚖️",
|
||||
anti_pattern: "🔴",
|
||||
bug_fix: "🔴",
|
||||
convention: "🔄",
|
||||
task_learning: "🔵",
|
||||
user_preference: "🟣",
|
||||
session_summary: "📋",
|
||||
session_state: "📋",
|
||||
project_profile: "📖",
|
||||
compact_summary: "📋",
|
||||
auto_capture: "✅",
|
||||
};
|
||||
|
||||
const FILE_READ_GATE_MIN_BYTES = 1500;
|
||||
|
||||
function loadGlobalSearch(): boolean {
|
||||
try {
|
||||
const settingsPath = join(homedir(), ".mem0", "settings.json");
|
||||
if (!existsSync(settingsPath)) return false;
|
||||
const settings = JSON.parse(readFileSync(settingsPath, "utf8"));
|
||||
return settings.global_search === true;
|
||||
} catch {}
|
||||
return false;
|
||||
}
|
||||
|
||||
const CODING_CATEGORIES = [
|
||||
"architecture_decisions", "api_design", "data_models", "algorithms",
|
||||
"dependencies", "environment_setup", "testing_strategy", "debugging_notes",
|
||||
"performance", "security", "deployment", "code_conventions",
|
||||
"error_handling", "refactoring_history", "integrations", "onboarding",
|
||||
"project_meta",
|
||||
];
|
||||
|
||||
function categoriesFingerprint(): string {
|
||||
const sorted = [...CODING_CATEGORIES].sort();
|
||||
return createHash("sha256").update(sorted.join("\n")).digest("hex").slice(0, 16);
|
||||
}
|
||||
|
||||
function apiKeyFingerprint(apiKey: string): string {
|
||||
return createHash("sha256").update(apiKey).digest("hex").slice(0, 16);
|
||||
}
|
||||
|
||||
async function autoSetupCategories(mem0: MemoryClient, apiKey: string): Promise<void> {
|
||||
const stateDir = join(homedir(), ".mem0");
|
||||
const stateFile = join(stateDir, "categories_setup.json");
|
||||
const keyFp = apiKeyFingerprint(apiKey);
|
||||
const catFp = categoriesFingerprint();
|
||||
|
||||
let state: Record<string, string> = {};
|
||||
try {
|
||||
if (existsSync(stateFile)) {
|
||||
state = JSON.parse(readFileSync(stateFile, "utf8"));
|
||||
}
|
||||
} catch {}
|
||||
|
||||
if (state[keyFp] === catFp) return;
|
||||
|
||||
try {
|
||||
const project = await mem0.getProject({ fields: ["customCategories"] });
|
||||
const existing: string[] = (project as any)?.custom_categories ?? (project as any)?.customCategories ?? [];
|
||||
const sortedExisting = [...existing].sort();
|
||||
const sortedTarget = [...CODING_CATEGORIES].sort();
|
||||
if (JSON.stringify(sortedExisting) === JSON.stringify(sortedTarget)) {
|
||||
state[keyFp] = catFp;
|
||||
mkdirSync(stateDir, { recursive: true });
|
||||
writeFileSync(stateFile, JSON.stringify(state, null, 2) + "\n");
|
||||
return;
|
||||
}
|
||||
|
||||
await mem0.updateProject({ customCategories: CODING_CATEGORIES as any });
|
||||
|
||||
state[keyFp] = catFp;
|
||||
mkdirSync(stateDir, { recursive: true });
|
||||
writeFileSync(stateFile, JSON.stringify(state, null, 2) + "\n");
|
||||
} catch {}
|
||||
}
|
||||
|
||||
const NUDGE_RE =
|
||||
/\b(remember\s+(this|that)|memorize|save\s+this|note\s+(this|that)|don'?t\s+forget|always\s+remember|never\s+forget|keep\s+(this|that)\s+in\s+(mind|memory)|store\s+(this|that))\b/i;
|
||||
|
||||
@@ -173,6 +271,7 @@ const Mem0Plugin: Plugin = async (ctx) => {
|
||||
const branch = await getBranch($);
|
||||
const stats = { adds: 0, searches: 0, messages: 0 };
|
||||
const sessionId = generateSessionId();
|
||||
const globalSearch = loadGlobalSearch();
|
||||
|
||||
let initialized = false;
|
||||
let memoryCount = 0;
|
||||
@@ -180,6 +279,9 @@ const Mem0Plugin: Plugin = async (ctx) => {
|
||||
|
||||
const systemContext: string[] = [];
|
||||
|
||||
// Auto-configure coding categories in background (idempotent, never blocks)
|
||||
Promise.resolve().then(() => autoSetupCategories(mem0, apiKey)).catch(() => {});
|
||||
|
||||
return {
|
||||
"chat.message": async (input: any, output: any) => {
|
||||
const userText = extractUserText(input, output);
|
||||
@@ -192,11 +294,13 @@ const Mem0Plugin: Plugin = async (ctx) => {
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
|
||||
const searchFilters = globalSearch
|
||||
? { OR: [{ user_id: "*" }] }
|
||||
: { AND: [{ user_id: userId }, { app_id: appId }] };
|
||||
|
||||
try {
|
||||
const all = await mem0.getAll({
|
||||
filters: {
|
||||
AND: [{ user_id: userId }, { app_id: appId }],
|
||||
},
|
||||
filters: searchFilters,
|
||||
page: 1,
|
||||
pageSize: 1,
|
||||
});
|
||||
@@ -205,9 +309,15 @@ const Mem0Plugin: Plugin = async (ctx) => {
|
||||
(all as any)?.results?.length ??
|
||||
0;
|
||||
|
||||
systemContext.push(
|
||||
`Always include user_id="${userId}" and app_id="${appId}" in every search_memories filter and add_memory call.`,
|
||||
);
|
||||
if (globalSearch) {
|
||||
systemContext.push(
|
||||
`Global search is ON — searches return all memories across all users and projects. Writes still use user_id="${userId}", app_id="${appId}".`,
|
||||
);
|
||||
} else {
|
||||
systemContext.push(
|
||||
`Always include user_id="${userId}" and app_id="${appId}" in every search_memories filter and add_memory call.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (memoryCount === 0) {
|
||||
systemContext.push(
|
||||
@@ -223,9 +333,7 @@ const Mem0Plugin: Plugin = async (ctx) => {
|
||||
const res = await mem0.search(
|
||||
"recent session state decisions and learnings",
|
||||
{
|
||||
filters: {
|
||||
AND: [{ user_id: userId }, { app_id: appId }],
|
||||
},
|
||||
filters: searchFilters,
|
||||
topK: 5,
|
||||
},
|
||||
);
|
||||
@@ -233,9 +341,16 @@ const Mem0Plugin: Plugin = async (ctx) => {
|
||||
const memories = extractMemories(res);
|
||||
if (memories.length > 0) {
|
||||
const memLines = memories
|
||||
.map((m) => `- ${m.memory}`)
|
||||
.map((m) => {
|
||||
const meta = (m as any).metadata ?? {};
|
||||
const cat = meta.type ?? "unknown";
|
||||
const icon = TYPE_ICONS[cat] ?? "❓";
|
||||
const age = (m as any).created_at ? formatAge((m as any).created_at) : "";
|
||||
const ageStr = age ? ` (${age})` : "";
|
||||
return `- ${icon} [${cat}]${ageStr} ${m.memory.slice(0, 120)}`;
|
||||
})
|
||||
.join("\n");
|
||||
systemContext.push(`Prior context from mem0:\n${memLines}`);
|
||||
systemContext.push(`### Recent Activity\n\n${memLines}`);
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
@@ -254,6 +369,8 @@ const Mem0Plugin: Plugin = async (ctx) => {
|
||||
});
|
||||
} catch {}
|
||||
}
|
||||
|
||||
captureEvent("session_start", { memory_count: memoryCount }, apiKey);
|
||||
}
|
||||
|
||||
if (NUDGE_RE.test(safeText)) {
|
||||
@@ -265,25 +382,21 @@ const Mem0Plugin: Plugin = async (ctx) => {
|
||||
const hasResume = RESUME_RE.test(safeText);
|
||||
if (hasResume) {
|
||||
try {
|
||||
const [stateRes, decisionsRes] = await Promise.all([
|
||||
mem0.search("session state current task", {
|
||||
filters: {
|
||||
const resumeFilters = globalSearch
|
||||
? { OR: [{ user_id: "*" }] }
|
||||
: {
|
||||
AND: [
|
||||
{ user_id: userId },
|
||||
{ app_id: appId },
|
||||
{ metadata: { type: "session_state" } },
|
||||
],
|
||||
},
|
||||
};
|
||||
const [stateRes, decisionsRes] = await Promise.all([
|
||||
mem0.search("session state current task", {
|
||||
filters: resumeFilters,
|
||||
topK: 3,
|
||||
}),
|
||||
mem0.search("recent decisions and learnings", {
|
||||
filters: {
|
||||
AND: [
|
||||
{ user_id: userId },
|
||||
{ app_id: appId },
|
||||
{ metadata: { type: "decision" } },
|
||||
],
|
||||
},
|
||||
filters: resumeFilters,
|
||||
topK: 3,
|
||||
}),
|
||||
]);
|
||||
@@ -309,8 +422,11 @@ const Mem0Plugin: Plugin = async (ctx) => {
|
||||
|
||||
if (!hasResume && memoryCount > 0) {
|
||||
try {
|
||||
const msgFilters = globalSearch
|
||||
? { OR: [{ user_id: "*" }] }
|
||||
: { AND: [{ user_id: userId }, { app_id: appId }] };
|
||||
const res = await mem0.search(safeText, {
|
||||
filters: { AND: [{ user_id: userId }, { app_id: appId }] },
|
||||
filters: msgFilters,
|
||||
topK: 5,
|
||||
});
|
||||
stats.searches++;
|
||||
@@ -371,6 +487,41 @@ const Mem0Plugin: Plugin = async (ctx) => {
|
||||
"tool.execute.before": async (input: any, output: any) => {
|
||||
const toolName: string = input?.tool ?? "";
|
||||
|
||||
// File-context injection: before reading a file, search mem0 for prior work on it
|
||||
if (toolName === "read" || toolName === "Read") {
|
||||
const filePath = String(output?.args?.file_path ?? output?.args?.filePath ?? "");
|
||||
if (filePath && filePath.length > 0) {
|
||||
try {
|
||||
const absPath = filePath.startsWith("/") ? filePath : resolve(process.cwd(), filePath);
|
||||
const { statSync } = await import("fs");
|
||||
const stat = statSync(absPath);
|
||||
if (stat.isFile() && stat.size >= FILE_READ_GATE_MIN_BYTES) {
|
||||
const searchFilters = globalSearch
|
||||
? { OR: [{ user_id: "*" }] }
|
||||
: { AND: [{ user_id: userId }, { app_id: appId }] };
|
||||
const relPath = filePath.startsWith("/")
|
||||
? filePath.replace(process.cwd() + "/", "")
|
||||
: filePath;
|
||||
const res = await mem0.search(relPath, {
|
||||
filters: searchFilters,
|
||||
topK: 5,
|
||||
});
|
||||
stats.searches++;
|
||||
const memories = extractMemories(res);
|
||||
if (memories.length > 0) {
|
||||
const lines = memories.map((m) => {
|
||||
const text = m.memory.slice(0, 150).replace(/\n/g, " ");
|
||||
return `- ${text} [mem0:${m.id.slice(0, 8)}]`;
|
||||
});
|
||||
systemContext.push(
|
||||
`Prior work on \`${relPath}\`:\n${lines.join("\n")}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
if (WRITE_TOOLS.has(toolName)) {
|
||||
const fp = String(
|
||||
output?.args?.file_path ?? output?.args?.filePath ?? "",
|
||||
@@ -401,32 +552,36 @@ const Mem0Plugin: Plugin = async (ctx) => {
|
||||
}
|
||||
|
||||
if (isMem0SearchOrGet(toolName)) {
|
||||
const existingFilters = output.args.filters;
|
||||
if (existingFilters === undefined || existingFilters === null) {
|
||||
output.args.filters = {
|
||||
AND: [{ user_id: userId }, { app_id: appId }],
|
||||
};
|
||||
} else if (typeof existingFilters === "object") {
|
||||
const andClauses: any[] = existingFilters.AND;
|
||||
if (Array.isArray(andClauses)) {
|
||||
const hasUid = andClauses.some(
|
||||
(c: any) => c && typeof c === "object" && "user_id" in c,
|
||||
);
|
||||
const hasAid = andClauses.some(
|
||||
(c: any) => c && typeof c === "object" && "app_id" in c,
|
||||
);
|
||||
if (!hasUid) andClauses.push({ user_id: userId });
|
||||
if (!hasAid) andClauses.push({ app_id: appId });
|
||||
} else if (andClauses === undefined) {
|
||||
const hasUid = "user_id" in existingFilters;
|
||||
const hasAid = "app_id" in existingFilters;
|
||||
if (!hasUid || !hasAid) {
|
||||
const existing = Object.entries(existingFilters).map(
|
||||
([k, v]) => ({ [k]: v }),
|
||||
if (globalSearch) {
|
||||
output.args.filters = { OR: [{ user_id: "*" }] };
|
||||
} else {
|
||||
const existingFilters = output.args.filters;
|
||||
if (existingFilters === undefined || existingFilters === null) {
|
||||
output.args.filters = {
|
||||
AND: [{ user_id: userId }, { app_id: appId }],
|
||||
};
|
||||
} else if (typeof existingFilters === "object") {
|
||||
const andClauses: any[] = existingFilters.AND;
|
||||
if (Array.isArray(andClauses)) {
|
||||
const hasUid = andClauses.some(
|
||||
(c: any) => c && typeof c === "object" && "user_id" in c,
|
||||
);
|
||||
if (!hasUid) existing.push({ user_id: userId });
|
||||
if (!hasAid) existing.push({ app_id: appId });
|
||||
output.args.filters = { AND: existing };
|
||||
const hasAid = andClauses.some(
|
||||
(c: any) => c && typeof c === "object" && "app_id" in c,
|
||||
);
|
||||
if (!hasUid) andClauses.push({ user_id: userId });
|
||||
if (!hasAid) andClauses.push({ app_id: appId });
|
||||
} else if (andClauses === undefined) {
|
||||
const hasUid = "user_id" in existingFilters;
|
||||
const hasAid = "app_id" in existingFilters;
|
||||
if (!hasUid || !hasAid) {
|
||||
const existing = Object.entries(existingFilters).map(
|
||||
([k, v]) => ({ [k]: v }),
|
||||
);
|
||||
if (!hasUid) existing.push({ user_id: userId });
|
||||
if (!hasAid) existing.push({ app_id: appId });
|
||||
output.args.filters = { AND: existing };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -450,6 +605,17 @@ const Mem0Plugin: Plugin = async (ctx) => {
|
||||
if (MEM0_MCP_RE.test(toolName)) {
|
||||
if (toolName.includes("add_memory")) stats.adds++;
|
||||
if (toolName.includes("search")) stats.searches++;
|
||||
|
||||
const tool = toolName.includes("add_memory")
|
||||
? "add_memory"
|
||||
: toolName.includes("search")
|
||||
? "search_memories"
|
||||
: toolName.includes("delete")
|
||||
? "delete_memory"
|
||||
: toolName.includes("update")
|
||||
? "update_memory"
|
||||
: "other";
|
||||
captureEvent("tool_use", { tool }, apiKey);
|
||||
}
|
||||
|
||||
if (toolName === "bash" && toolOutput.length >= 50) {
|
||||
@@ -481,25 +647,21 @@ const Mem0Plugin: Plugin = async (ctx) => {
|
||||
const errorQuery = errorLine.slice(0, 80);
|
||||
if (errorQuery.length < 10) return;
|
||||
|
||||
const [antiPatternRes, bugFixRes] = await Promise.all([
|
||||
mem0.search(`error: ${errorQuery}`, {
|
||||
filters: {
|
||||
const errorFilters = globalSearch
|
||||
? { OR: [{ user_id: "*" }] }
|
||||
: {
|
||||
AND: [
|
||||
{ user_id: userId },
|
||||
{ app_id: appId },
|
||||
{ metadata: { type: "anti_pattern" } },
|
||||
],
|
||||
},
|
||||
};
|
||||
const [antiPatternRes, bugFixRes] = await Promise.all([
|
||||
mem0.search(`error: ${errorQuery}`, {
|
||||
filters: errorFilters,
|
||||
topK: 3,
|
||||
}),
|
||||
mem0.search(`error: ${errorQuery}`, {
|
||||
filters: {
|
||||
AND: [
|
||||
{ user_id: userId },
|
||||
{ app_id: appId },
|
||||
{ metadata: { type: "bug_fix" } },
|
||||
],
|
||||
},
|
||||
filters: errorFilters,
|
||||
topK: 3,
|
||||
}),
|
||||
]);
|
||||
@@ -537,15 +699,22 @@ const Mem0Plugin: Plugin = async (ctx) => {
|
||||
) => {
|
||||
try {
|
||||
const compactSessionId = input?.sessionID ?? sessionId;
|
||||
const summaryContent = `Session compacting. Project: ${appId}. Branch: ${branch}. Session: ${compactSessionId}. Stats: ${stats.adds} memories stored, ${stats.searches} searches, ${stats.messages} messages.`;
|
||||
|
||||
// Session summary capture: store a structured summary of the session
|
||||
const summaryPrompt = [
|
||||
`Session summary for project ${appId} (branch: ${branch}).`,
|
||||
`Session: ${compactSessionId}.`,
|
||||
`Stats: ${stats.adds} memories stored, ${stats.searches} searches, ${stats.messages} messages.`,
|
||||
`Extract and remember: what was requested, what was investigated, key decisions made, what was completed, and what needs to happen next.`,
|
||||
].join(" ");
|
||||
Promise.resolve().then(async () => {
|
||||
try {
|
||||
await mem0.add([{ role: "user", content: summaryContent }], {
|
||||
await mem0.add([{ role: "user", content: summaryPrompt }], {
|
||||
user_id: userId,
|
||||
app_id: appId,
|
||||
metadata: {
|
||||
type: "session_state",
|
||||
source: "pre-compaction",
|
||||
type: "session_summary",
|
||||
source: "opencode-stop",
|
||||
session_id: compactSessionId,
|
||||
branch,
|
||||
},
|
||||
@@ -554,8 +723,11 @@ const Mem0Plugin: Plugin = async (ctx) => {
|
||||
} catch {}
|
||||
});
|
||||
|
||||
const compactFilters = globalSearch
|
||||
? { OR: [{ user_id: "*" }] }
|
||||
: { AND: [{ user_id: userId }, { app_id: appId }] };
|
||||
const res = await mem0.search("session state decisions learnings", {
|
||||
filters: { AND: [{ user_id: userId }, { app_id: appId }] },
|
||||
filters: compactFilters,
|
||||
topK: 10,
|
||||
});
|
||||
const memories = extractMemories(res);
|
||||
@@ -577,6 +749,7 @@ const Mem0Plugin: Plugin = async (ctx) => {
|
||||
output.env.MEM0_APP_ID = appId;
|
||||
output.env.MEM0_SESSION_ID = sessionId;
|
||||
output.env.MEM0_BRANCH = branch;
|
||||
output.env.MEM0_GLOBAL_SEARCH = globalSearch ? "true" : "false";
|
||||
}
|
||||
},
|
||||
};
|
||||
+19
-39
@@ -128,58 +128,38 @@ If `add_memory` calls fail, print:
|
||||
- Project file import failed. Check API key and MCP connection, then retry with: /mem0:onboard
|
||||
```
|
||||
|
||||
## Step 5: Set up coding categories
|
||||
## Step 5: Verify coding categories
|
||||
|
||||
Ask: "Install coding categories optimized for development workflows? [Y/n]"
|
||||
Coding categories are now configured automatically in the background when the plugin starts. This step only verifies they are set up.
|
||||
|
||||
If the user says no or skips, print `- Coding categories skipped.` and proceed to Step 6.
|
||||
Check if the categories are already configured by searching for a project_profile memory:
|
||||
|
||||
If yes, store a project profile memory that records the project and its active coding categories. Call `add_memory` once with:
|
||||
Call `search_memories` with `query="coding categories project profile"`, `filters={"AND": [{"user_id": "<active_user_id>"}, {"app_id": "<active_project_id>"}]}`, `top_k=1`.
|
||||
|
||||
- `data`: a plain-text description of the project profile and the list of active coding categories (see below)
|
||||
If a project_profile memory is found, print:
|
||||
```
|
||||
- Coding categories already configured (17 categories, auto-installed).
|
||||
```
|
||||
|
||||
If no project_profile memory is found, store one as a fallback. Call `add_memory` once with:
|
||||
|
||||
- `data`: a plain-text description of the project profile and the list of active coding categories:
|
||||
```
|
||||
Project profile for <project_id>.
|
||||
Active coding categories: architecture_decisions, api_design, data_models,
|
||||
algorithms, dependencies, environment_setup, testing_strategy, debugging_notes,
|
||||
performance, security, deployment, code_conventions, error_handling,
|
||||
refactoring_history, integrations, onboarding, project_meta.
|
||||
```
|
||||
- `user_id`: the active user id
|
||||
- `app_id`: the active project id
|
||||
- `metadata`: `{"type": "project_profile", "source": "onboard"}`
|
||||
|
||||
The standard set of 17 coding categories to include in the `data` field:
|
||||
|
||||
```
|
||||
architecture_decisions - High-level design choices and system structure
|
||||
api_design - Interface contracts, REST/GraphQL/RPC conventions
|
||||
data_models - Schemas, entity definitions, relationships
|
||||
algorithms - Non-trivial logic, performance-sensitive routines
|
||||
dependencies - Libraries, versions, upgrade notes
|
||||
environment_setup - Dev environment, tooling, build system
|
||||
testing_strategy - Test patterns, coverage targets, mocking approach
|
||||
debugging_notes - Known issues, workarounds, gotchas
|
||||
performance - Bottlenecks, profiling results, optimizations
|
||||
security - Auth patterns, secret handling, threat notes
|
||||
deployment - CI/CD pipelines, infra config, release process
|
||||
code_conventions - Naming, formatting, style rules beyond the linter
|
||||
error_handling - Error taxonomy, recovery patterns, logging approach
|
||||
refactoring_history - Past rewrites, why changes were made
|
||||
integrations - Third-party services, webhooks, external APIs
|
||||
onboarding - New-contributor notes, repo orientation
|
||||
project_meta - Goals, non-goals, stakeholder context
|
||||
```
|
||||
|
||||
Example `data` value:
|
||||
|
||||
```
|
||||
Project profile for <project_id>.
|
||||
Active coding categories: architecture_decisions, api_design, data_models,
|
||||
algorithms, dependencies, environment_setup, testing_strategy, debugging_notes,
|
||||
performance, security, deployment, code_conventions, error_handling,
|
||||
refactoring_history, integrations, onboarding, project_meta.
|
||||
```
|
||||
|
||||
After the `add_memory` call succeeds, print:
|
||||
```
|
||||
- Coding categories installed (17 categories).
|
||||
```
|
||||
|
||||
If `add_memory` fails, print the error and suggest re-running `/mem0:onboard`.
|
||||
|
||||
## Step 6: Summary
|
||||
|
||||
Print a summary:
|
||||
@@ -0,0 +1,107 @@
|
||||
---
|
||||
name: switch-project
|
||||
description: Overrides the auto-detected project scope to read and write memories under a different project ID, or enables global search to access all memories across all users and projects. Use when working across multiple projects, accessing memories from another repo, enabling team-wide memory access, or when auto-detection resolves to the wrong project.
|
||||
---
|
||||
|
||||
# Mem0 Switch Project
|
||||
|
||||
Override the automatic project_id detection for the current directory, or enable global search mode.
|
||||
|
||||
## Usage
|
||||
|
||||
- `/mem0:switch-project <project-name>` — switch to a specific project scope
|
||||
- `/mem0:switch-project --global` — enable global search (all memories, all users, all projects)
|
||||
- `/mem0:switch-project --no-global` — disable global search and return to per-project scoping
|
||||
|
||||
## Execution
|
||||
|
||||
### If `--global` flag is provided:
|
||||
|
||||
1. Set `global_search: true` in `~/.mem0/settings.json` using the Bash tool:
|
||||
|
||||
```bash
|
||||
python3 -c "
|
||||
import json, os
|
||||
settings_file = os.path.expanduser('~/.mem0/settings.json')
|
||||
settings = {}
|
||||
if os.path.isfile(settings_file):
|
||||
with open(settings_file) as f:
|
||||
settings = json.load(f)
|
||||
settings['global_search'] = True
|
||||
with open(settings_file, 'w') as f:
|
||||
json.dump(settings, f, indent=2)
|
||||
print('Global search enabled')
|
||||
"
|
||||
```
|
||||
|
||||
2. Print:
|
||||
```
|
||||
Global search enabled.
|
||||
Searches now return all memories across all users and projects.
|
||||
Writes still use the current user_id and app_id.
|
||||
Restart the session for the change to take effect.
|
||||
```
|
||||
|
||||
### If `--no-global` flag is provided:
|
||||
|
||||
1. Set `global_search: false` in `~/.mem0/settings.json` using the Bash tool:
|
||||
|
||||
```bash
|
||||
python3 -c "
|
||||
import json, os
|
||||
settings_file = os.path.expanduser('~/.mem0/settings.json')
|
||||
settings = {}
|
||||
if os.path.isfile(settings_file):
|
||||
with open(settings_file) as f:
|
||||
settings = json.load(f)
|
||||
settings['global_search'] = False
|
||||
with open(settings_file, 'w') as f:
|
||||
json.dump(settings, f, indent=2)
|
||||
print('Global search disabled')
|
||||
"
|
||||
```
|
||||
|
||||
2. Print:
|
||||
```
|
||||
Global search disabled.
|
||||
Searches now return only memories scoped to the current project.
|
||||
Restart the session for the change to take effect.
|
||||
```
|
||||
|
||||
### If a project name is provided (no flags):
|
||||
|
||||
1. If no project name was given, ask: "What project_id should this directory use?"
|
||||
|
||||
2. Write the mapping to `~/.mem0/project_map.json` using the Bash tool:
|
||||
|
||||
```bash
|
||||
python3 -c "
|
||||
import json, os
|
||||
map_file = os.path.expanduser('~/.mem0/project_map.json')
|
||||
mapping = {}
|
||||
if os.path.isfile(map_file):
|
||||
with open(map_file) as f:
|
||||
mapping = json.load(f)
|
||||
mapping[os.getcwd()] = '<PROJECT_NAME>'
|
||||
os.makedirs(os.path.dirname(map_file), exist_ok=True)
|
||||
with open(map_file, 'w') as f:
|
||||
json.dump(mapping, f, indent=2)
|
||||
print(f'Mapped {os.getcwd()} -> <PROJECT_NAME>')
|
||||
"
|
||||
```
|
||||
|
||||
(Replace `<PROJECT_NAME>` with the user's chosen project name.)
|
||||
|
||||
3. Verify by searching for existing memories:
|
||||
- Call `search_memories` with `query="project"`, `filters={"AND": [{"user_id": "<active_user_id>"}, {"app_id": "<PROJECT_NAME>"}]}`, `top_k=1`
|
||||
|
||||
4. Print:
|
||||
```
|
||||
Switched to project <PROJECT_NAME>.
|
||||
<N> memories found for this project.
|
||||
Note: This override persists across sessions for this directory.
|
||||
```
|
||||
|
||||
## Output formatting
|
||||
|
||||
IMPORTANT: Do NOT use markdown in your output. OpenCode TUI renders text verbatim — markdown like **bold**, ## headers, and | table | syntax appears as raw characters. Use plain text with indentation for structure. Use dashes for lists. Use spaces to align columns instead of markdown tables.
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@mem0/opencode-plugin",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.3",
|
||||
"type": "module",
|
||||
"description": "Mem0 persistent memory plugin for OpenCode — add, search, and manage memories across sessions",
|
||||
"main": "dist/index.js",
|
||||
@@ -30,7 +30,7 @@
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mem0ai/mem0",
|
||||
"directory": "mem0-plugin/.opencode-plugin"
|
||||
"directory": "integrations/mem0-plugin/.opencode-plugin"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
@@ -59,7 +59,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@opencode-ai/plugin": "^1.0.162",
|
||||
"mem0ai": "^3.0.5"
|
||||
"mem0ai": "^3.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"bun-types": ">=1.3.14",
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user