Compare commits

...

38 Commits

Author SHA1 Message Date
karthik 1c448b3eec docs(llms): add Dream API reference pages to llms.txt
The check-llms-txt CI (and the CI Gate that aggregates it) failed because the 8
new Dream API-reference pages weren't listed in docs/llms.txt. Add a Dream
subsection [Platform] between Projects and Webhooks. `check-llms-txt-coverage.py`
now reports in sync.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-27 18:48:29 +05:30
karthik 806e15e927 fix(api-reference): use nullable array for dream activity sources
`type: null` is not valid OpenAPI 3.0.1, so the spec failed validation and
Mintlify rendered the Dream pages blank. Represent the always-null `sources`
field on the activity feed as a nullable array (parity with the runs feed).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-25 20:47:15 +05:30
karthik 6f804a8a20 docs(api-reference): add Dream (memory synthesis) endpoints
Dream shipped to prod but its API endpoints weren't in the API reference. Add
the org/project-scoped Dream endpoints (OpenAPI spec + per-endpoint pages + nav):

- GET/PATCH dream/config/  — get / update Synthesis config + plan entitlements
- GET  dream/stats/        — lifecycle + synthesis counts
- GET  dream/activity/     — supersede/merge feed (keyset-paginated)
- GET  dream/runs/         — synthesis runs feed
- GET  dream/runs/{run_id}/memories/       — memories within a run
- GET  dream/memory/{memory_id}/sources/   — a synthesized memory's sources
- POST dream/preview/      — no-write scope preview

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-25 19:58:25 +05:30
Himanshu 39bc023305 docs(integrations): add Vercel Marketplace (managed) integration page (#7100) 2026-08-24 22:22:04 +05:30
krishna soni b1342a3408 refactor: replace custom validator with Pydantic extra="forbid" config (#7089) 2026-08-24 21:17:57 +05:30
Kartik b717e38785 refactor(integrations): rename dsh-mem0 to deepseek-plugin, strands-mem0 to mem0-strands (#7098) 2026-08-24 19:21:23 +05:30
Indian-boult dc82354e14 docs(skills): fix dead links in skills READMEs (#7092) 2026-08-24 18:25:33 +05:30
Kartik 4ddee9c51d chore(release): bump SDK, CLI, and plugin versions; add Strands, DeepSeek Harness, and Kimi changelogs (#7097) 2026-08-24 18:10:44 +05:30
Himanshu 7e09615571 feat(integrations): dsh-mem0 — Mem0 as a native DeepSeek Harness plugin (#7027)
Co-authored-by: kartik-mem0 <kartik.labhshetwar@mem0.ai>
2026-08-24 14:03:50 +05:30
Kartik d18e751dec docs: redirect five dead api-reference paths to their real pages (#7094) 2026-08-24 13:53:04 +05:30
Himanshu 8d5b7865bd feat(integrations): strands-mem0 | Mem0 as a native Strands MemoryStore (#7021) 2026-08-22 19:04:24 +05:30
Abhinav Singh 9b565da8e3 docs(embedders): document api_key on the Hugging Face Python config table (#7045) 2026-08-22 13:51:50 +05:30
Yiheng Zhao 48d0d0cd9c fix(docs): balance code fences in cookbook_template.mdx (#7054) 2026-08-22 13:50:49 +05:30
Kartik feb12852c0 fix(docs): redirect the eight 404 paths and repair dead wildcard rules (#7053) 2026-08-21 19:31:37 +05:30
Harsh Vardhan Gupta 5af797834c fix(security): resolve 17 Vanta/Dependabot HIGH+CRITICAL vulnerabilities across 5 pnpm workspaces (#7032) 2026-08-21 17:41:41 +05:30
Kartik 4fa4839077 fix(ci): make the vouch check speak, unblock list updates, widen the docs exemption (#6974) 2026-08-20 23:13:42 +05:30
Kartik 3599aa75ed docs: document the real search filter grammar (#6906) 2026-08-20 21:33:21 +05:30
Himanshu 1de6499b8a fix(integrations/zapier): address Zapier publishing review (#6985) 2026-08-20 18:53:06 +05:30
Kartik ed38ddf873 fix(python): huggingface TEI auth, procedural-memory content handling, and proxy pip auto-install (#6947) 2026-08-20 15:56:55 +05:30
Kartik 530d802b55 fix(plugins): bug-bash fixes for Cursor, Codex, Antigravity, and a Claude.ai docs page (#6948) 2026-08-20 15:56:17 +05:30
Kartik d3334fa5f1 docs: ground the platform/OSS comparison and memory-type status in reality (#6908) 2026-08-20 15:26:22 +05:30
Kartik 52b02c7cc1 docs: fix Claude Desktop MCP setup, CrewAI guide, and missing contributor docs (#6945) 2026-08-20 15:24:05 +05:30
mintlify[bot] 001c235229 Fix broken links: remove duplicate reranking redirect (#6975)
Co-authored-by: mintlify[bot] <109931778+mintlify[bot]@users.noreply.github.com>
2026-08-14 12:47:17 +00:00
Kartik bf2d591b27 docs: remove Controlling Memory Ingestion cookbook, redirect to Custom Instructions (#6955) 2026-08-14 18:16:36 +05:30
Kartik b4c50550bf docs: correct client call shape and stale v1 response examples (#6901) 2026-08-14 18:13:37 +05:30
Kartik 290de24bb8 feat(ci): gate pull requests on an accepted issue (#6894) 2026-08-14 17:05:27 +05:30
Ratish jain ef6f51d977 fix(tests): check for RediSearch module availability in Redis tests (#6687) 2026-08-14 17:00:56 +05:30
Kartik a10c0cd030 fix(mem0-plugin): stop search errors from looking like empty results (#6898) 2026-08-14 16:57:01 +05:30
Kartik 956bf4f88e fix(ts-oss): return snake_case entity ids from the redis and valkey stores (#6902) 2026-08-14 16:56:27 +05:30
Kartik 0f172c2890 fix(ts-oss): stop prototype keys from short-circuiting embedding lookup (#6903) 2026-08-14 16:56:03 +05:30
Kartik 696455fd62 docs: contrast the advanced retrieval modes with distinct examples (#6904) 2026-08-14 16:55:44 +05:30
Kartik 9e99eaadbc docs: correct memory decay claims that contradict the SDK (#6905) 2026-08-14 16:55:26 +05:30
Kartik 02ff6c5595 feat(cli): add a version subcommand and document the --filter JSON shape (#6907) 2026-08-14 16:55:12 +05:30
Kartik a0329f047b docs(cookbooks): repair dead references and label OSS vs Platform support (#6909) 2026-08-14 16:50:46 +05:30
Kartik c50a2bfb8f docs(llms): fix wrong read snippets, dead reranking link, and 24 mis-scoped integration tags (#6950) 2026-08-14 16:50:23 +05:30
Kartik bfb51c6b93 fix(client): honor page_size in get_all when page is not passed (#6900) 2026-08-14 16:49:58 +05:30
Kartik a133287015 fix(llms/aws_bedrock): resolve provider for application inference profile ARNs (#6899) 2026-08-14 16:49:37 +05:30
Kartik c883f52130 fix(llms/vllm): honor VLLM_BASE_URL instead of always using localhost (#6897) 2026-08-14 16:49:18 +05:30
222 changed files with 11297 additions and 2730 deletions
+1 -1
View File
@@ -12,7 +12,7 @@
"name": "mem0",
"source": "./integrations/mem0-plugin",
"description": "Mem0 memory layer for AI applications. Add persistent memory, personalization, and semantic search to Claude workflows.",
"version": "0.2.14"
"version": "0.2.15"
}
]
}
+1 -1
View File
@@ -12,7 +12,7 @@
"name": "mem0",
"source": "./integrations/mem0-plugin",
"description": "Mem0 memory layer for AI applications. Add persistent memory, personalization, and semantic search.",
"version": "0.2.14"
"version": "0.2.15"
}
]
}
+155
View File
@@ -0,0 +1,155 @@
# CI/CD and repository automation (`.github/`)
> **Do not modify any workflow without explicit approval from a maintainer.** Publishing
> credentials are bound to workflow filenames, and the gate workflows decide whether
> contributions are accepted. Read this file before proposing any change here.
## CI: one gate, many pipelines
`ci-gate.yml` (**CI Gate**) is the single entry point. It runs on every PR, detects which packages changed, and calls only the relevant package workflows as reusable workflows (`workflow_call`). Its final `CI Gate` job aggregates the results: skipped pipelines pass, failed or cancelled ones fail. It is the **only CI 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 live in the gate's path filters instead.
| Workflow | File | Standalone triggers | Runs |
|----------|------|---------------------|------|
| CI Gate | `ci-gate.yml` | All PRs | Routes to and aggregates everything below |
| Python SDK | `ci.yml` | Push to main | Ruff + pytest on Python 3.10, 3.11, 3.12 |
| TypeScript SDK | `ts-sdk-ci.yml` | Push to main (`mem0-ts/`) | Prettier + build + jest on Node 20, 22 |
| Python CLI | `cli-python-ci.yml` | Push to main (`cli/python/`), manual | Ruff + pytest + hatch build on Python 3.10, 3.11, 3.12 |
| Node CLI | `cli-node-ci.yml` | Push to main (`cli/node/`), manual | Biome + tsc + vitest + tsup on Node 20, 22 |
| OpenClaw | `openclaw-checks.yml` | Push to main (`integrations/openclaw/`), manual | tsc + vitest (Codecov) + tsup on Node 20, 22 |
| Mem0 Plugin | `mem0-plugin-checks.yml` | Push to main (`integrations/mem0-plugin/`, excluding `.opencode-plugin/`), manual | pytest + hook exec bits + JSON manifest validation on Python 3.10, 3.11, 3.12 |
| OpenCode Plugin | `opencode-plugin-checks.yml` | Push to main (`.opencode-plugin/`), manual | Bun: tsc + build + dist artifact check |
| Pi Agent Plugin | `pi-agent-plugin-checks.yml` | Push to main (`integrations/pi-agent-plugin/`), manual | tsc + vitest + tsup on Node 20, 22 |
| DeepSeek Harness Plugin | `deepseek-plugin-checks.yml` | Push to main (`integrations/deepseek-plugin/`), manual | tsc + vitest + tsup on Node 20, 22 |
| n8n Node | `n8n-nodes-mem0-checks.yml` | Push to main (`integrations/n8n-nodes-mem0/`), manual | ESLint + tsc build on Node 20 |
| Zapier App | `zapier-mem0-checks.yml` | Push to main (`integrations/zapier-mem0/`), manual | tsc + `zapier validate` + offline unit tests on Node 22 |
| mem0-strands | `mem0-strands-checks.yml` | Push to main (`integrations/mem0-strands/`), manual | Ruff + mypy + pytest + hatch build on Python 3.10, 3.11, 3.12 |
| docs llms.txt | `docs-llms-txt-check.yml` | Manual | `docs/llms.txt` coverage |
| GitHub Scripts | inline in `ci-gate.yml` | none | `node` over every `.github/scripts/*.test.js` |
Adding a 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` with a path filter under the `changes` job, a call job, and an entry in the gate job's `needs` list.
`GitHub Scripts` is the one row that is a plain job inside `ci-gate.yml` rather than a called workflow, because a reusable workflow wrapping two `node` invocations would be more file than test. It runs on the `github_scripts` filter, which covers `.github/scripts/**` plus every file those tests read: `pr-gate.yml`, `vouch-check-pr.yml`, `issue-labeler.yml`, and `VOUCHED.td`. Add a new `.github/scripts/*.test.js` and it is picked up with no wiring; make a test read a new file and that file belongs in the filter.
## Branch protection on `main`
A repository ruleset named `Main Branch Rule`, id `11813754`. It enforces squash-only merges, linear history, no deletion, no force-push, and one approving review. Two status checks belong in its `required_status_checks` rule:
| Context | Posted by | Why |
|---------|-----------|-----|
| `CI Gate` | `ci-gate.yml` | Aggregates every package pipeline |
| `license/cla` | CLA Assistant | Proves the CLA is signed, not merely requested |
Editing the ruleset requires repo **admin**. `maintain` is not enough, and the API returns 404 rather than 403 in that case. Until `license/cla` is required, the claim in `CONTRIBUTING.md` that unsigned PRs are blocked from merging holds by convention only.
Requiring `CI Gate` also means fork PRs from first-time contributors cannot merge until a maintainer approves the workflow run. Those sit at `action_required`, which is intended behavior.
## CD: one router, many publishers
`release.yml` (**Release Router**) is the only workflow listening to `release: published`. It matches the tag prefix and dispatches the matching package workflow through `workflow_dispatch`, so one release produces exactly one routed run.
| Workflow | File | Tag prefix | Target |
|----------|------|------------|--------|
| Release Router | `release.yml` | all releases | dispatches the rows 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`) |
| Node CLI | `cli-node-cd.yml` | `cli-node-v*` | npm (`@mem0/cli`) |
| 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`) |
| DeepSeek Harness Plugin | `deepseek-plugin-cd.yml` | `deepseek-plugin-v*` | npm (`@mem0/deepseek-plugin`) |
| n8n Node | `n8n-nodes-mem0-cd.yml` | `n8n-nodes-mem0-v*` | npm (`@mem0/n8n-nodes-mem0`) |
| mem0-strands | `mem0-strands-cd.yml` | `mem0-strands-v*` | PyPI (`mem0-strands`) |
- Package CD workflows are `workflow_dispatch`-only, with `tag` and `prerelease` inputs. They check out and build the given tag.
- All publishing uses **OIDC trusted publishing**. No tokens, no secrets.
- Registry trusted-publisher settings are pinned to each package's own workflow **filename**. Renaming a CD workflow breaks publishing for that package.
- First publish of a new npm package must be done manually. OIDC works from the second version onward.
- To re-publish a release, do **not** delete and recreate the GitHub release. Dispatch the workflow directly: `gh workflow run <package>-cd.yml --ref refs/tags/<tag> -f tag=<tag>`.
- The Zapier app deploys to Zapier's platform, not npm, so it is not in the router. Deploy with `gh workflow run zapier-mem0-cd.yml --ref main`.
- Adding a package: add its CD workflow, then register its tag prefix in the `case` block in `release.yml`, keeping the bare `v*` arm last.
## Contribution gates
| Workflow | File | Purpose |
|----------|------|---------|
| PR Gate | `pr-gate.yml` | Closes PRs that do not link an issue labeled `accepted`, and reopens them when that label arrives. Exempts members, bots, drafts, and docs-only changes. Never checks out PR code. |
| Vouch (check PR) | `vouch-check-pr.yml` | Closes PRs from authors denounced in `VOUCHED.td`. Comments once on PRs from authors merely absent from it, and blocks nothing in that case. |
| Vouch (manage list) | `vouch-manage-by-issue.yml` | Maintainers edit the trust list by commenting `!vouch @user`, `!denounce @user`, or `!unvouch @user` on any issue. Opens a PR against `VOUCHED.td` through a GitHub App token, for a maintainer to merge. |
| Issue Labeler | `issue-labeler.yml` | Labels issues from the `component` field in the issue forms |
| PR Labeler | `pr-labeler.yml` | Path-based labels, plus propagating labels from linked issues |
| Stale Bot | `stale.yml` | Marks stale issues and PRs |
| llms.txt Check | `docs-llms-txt-check.yml` | Blocks PRs touching `docs/**/*.mdx` when `docs/llms.txt` is out of sync |
`pr-gate.yml` and `vouch-check-pr.yml` use `pull_request_target`, which is required to label and close fork PRs. Neither checks out PR code and neither has a `run:` step, so there is no pwn-request or script-injection surface. Keep it that way: any future `run:` step in these files must never interpolate `github.event.*` text.
Both workflows exempt maintainers twice, and the second guard is the one that holds. `author_association` is rendered for the viewer, and a webhook payload has no privileged viewer: `MEMBER` needs the author's org membership to be **public**, `COLLABORATOR` needs a **direct** repository invite. An org member with private membership whose `maintain` comes through a team matches neither and arrives as `CONTRIBUTOR`, which is how PR #6948 was closed by its own author's gate. So the guard also skips any PR whose head branch lives in this repository (`head.repo.full_name == github.repository`). Pushing a branch here already requires write access and outside contributors always arrive from a fork, so that test means the same thing without depending on who is looking. Keep both: the `author_association` arm still covers members who work from their own fork.
`pr-gate.yml` carries two jobs whose `if:` conditions are deliberately disjoint. `gate` closes, and only ever runs on `opened`, `reopened`, and `ready_for_review`. `reopen` reopens, and only ever runs on `edited` or on `issues: labeled` with the `accepted` label. Nothing can both close and reopen on the same event, which is the property to preserve when editing either guard.
That split exists because the two halves of a gated PR's recovery arrive in either order. A maintainer usually labels the issue `accepted` at triage, before the author has linked it; sometimes the link lands first and the label follows. So `reopen` handles both directions. From `issues: labeled` it walks `closedByPullRequestsReferences` back to the pull requests that link the issue. From `edited` it takes the edited pull request directly. Both paths then apply the same four tests: the author is not denounced in `VOUCHED.td`, the PR is `CLOSED`, it links an issue labeled `accepted`, and it carries the `<!-- pr-gate -->` marker comment. Without the label path, a maintainer's label is inert. Without the `edited` path, an author who links the issue after it was labeled is stuck, since no other event fires.
The denounce test is what keeps the two gates from cancelling each other out. A denounced author whose PR also lacked an accepted issue was closed by both workflows, so it carries the `<!-- pr-gate -->` marker, and labeling the linked issue would otherwise reopen it. Vouch cannot undo that: reopening runs through `GITHUB_TOKEN`, which raises no events, so `vouch-check-pr.yml` never fires a second time. Reading the list here is the only place the check can live. It fails open like vouch does, warning and treating nobody as denounced if the file cannot be read, and it is the one piece of vouch semantics duplicated outside `vouch-check-pr.yml`, because `pr-gate.yml` never checks out the repository and so cannot import a shared parser. `.github/scripts/vouch-decision.test.js` covers the parsing and asserts `pr-gate.yml` still filters the list the same way.
`edited` must never reach the `gate` job. It fires on any title or description change, so when `gate` listened for it the gate re-judged pull requests that had been open for days and closed them the moment their author touched the description, which is what closed #6948. Rescuing on `edited` is safe for the same reason closing on it was not: the job can only move a PR from closed to open.
Reopening runs through `GITHUB_TOKEN`, which by design raises no further workflow events, so `gate` cannot bounce a freshly reopened PR straight back out.
The concurrency group is keyed on `github.event.action` as well as `github.event_name` and the number, and both keys carry weight. Without the event name, a maintainer applying `bug` right after `accepted` cancels the reopen mid-flight, since `cancel-in-progress` is on for `pull_request_target` and both label events would land in the same group. Without the action, `opened` and `edited` share a group on the same pull request, and an author who ticks a template checkbox in the seconds after opening cancels the run that was about to gate them: `gate` skips `edited` and `reopen` skips an open pull request, so the cancelled run is never replaced and the pull request stays ungated forever, since `opened` fires exactly once. Rapid successive edits still cancel each other, which is the dedup that was wanted.
The `edited` arm of `reopen` requires `github.event.pull_request.state == 'closed'`, so ordinary description edits on open pull requests do not start a runner.
Two known gaps, both mild. A PR that the gate closed, that someone reopened, and that a maintainer then closed deliberately still carries the marker, so labeling its issue reopens it again; a maintainer closes it once more. And an author who strips `Closes #<number>` out after passing keeps an open PR, which a reviewer sees anyway.
`GATE_EFFECTIVE_FROM` in `pr-gate.yml` is a `created_at` cutoff. `reopened` and `ready_for_review` still fire on PRs opened long before the gate existed, so without the cutoff part of the open backlog would be closed by a rule that did not exist when those PRs were filed. Set it to the actual merge date in UTC.
The gate's docs-only exemption covers `docs/` plus a named allowlist of four root files: `README.md`, `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, and `SECURITY.md`. It is an allowlist rather than a rule about top-level markdown because the repository root also holds `AGENTS.md`, `CLAUDE.md`, and `LLM.md`, which are the instructions coding agents read before touching this codebase. Those are functional files that happen to be written in prose, and rewriting them is a change to behaviour, so they stay gated. Markdown nested anywhere else stays gated for the same reason: `skills/**/*.md` and everything under `.github/` are functional too. Adding a genuinely prose root file means adding it to `rootDocs` in `pr-gate.yml`.
`.github/scripts/pr-gate-docs-exemption.test.js` covers that predicate. It pulls the `rootDocs` and `isDocs` lines out of `pr-gate.yml` and evaluates them, so it exercises the shipped rule rather than a copy that could drift from it, and it pins `AGENTS.md`, `CLAUDE.md`, and `LLM.md` on the gated side along with `skills/**/*.md`, nested `.github/` files, and the empty file list. It only accepts those two declarations in a literal one-line form, so keep `rootDocs` a `Set` of quoted names and `isDocs` a single arrow expression.
The two contribution gates answer different questions and neither covers for the other. `pr-gate.yml` judges the change, and the `accepted` label is how a maintainer says yes to it. `vouch-check-pr.yml` judges the author, and `VOUCHED.td` is how a maintainer says no to one. A vouched author with no accepted issue is still closed by the gate; a denounced author with an accepted issue is still closed by vouch. Read either one as a backstop for the other and both get weakened.
Vouch enforces on the denounce axis only, through `require-vouch: false` with `auto-close: true`. That pair is not the obvious reading of either input, so the decision table from v1.5.0 (`vouch/github.nu` at pinned SHA `d66fa29`) is worth stating outright:
| Author | `status` | Effect |
|---|---|---|
| ends in `[bot]` | `skipped` | nothing |
| collaborator with write or admin | `vouched` | nothing |
| listed in `VOUCHED.td` | `vouched` | nothing |
| listed as `-handle` | `closed` | action comments and closes |
| absent from the file | `allowed` | workflow comments, nothing closed |
`require-vouch: true` would close every first-time contributor, which is the opposite of what a trust list is for: the funnel has to stay open or nobody ever earns a vouch. `auto-close: false` is the setting that looked safe and did nothing at all, since in v1.5.0 both the unvouched and the denounced branch return before posting anything, leaving only a line in the run log. That is why `!denounce` was decorative until this pair landed.
Only the `allowed` arm is ours: a `github-script` step posts the soft comment, keyed on a `<!-- vouch-check -->` marker so a reopen does not comment twice. The `closed` arm belongs to the action, message and all. Keeping the two arms disjoint is what stops a denounced author getting two comments, so if that step is ever re-keyed off `allowed`, check the overlap first.
`.github/scripts/vouch-decision.test.js` holds that table as a `decide()` function and asserts the workflow's `require-vouch`, `auto-close`, and comment-step gating still produce it, comment counts included. Be clear about what that does and does not prove. `decide()` is a **hand transcription** of `gh-check-pr`, read from `vouch/github.nu` at the pinned SHA; the test cannot run the action, so it cannot notice the action changing underneath it. Left alone it would agree with itself forever, which makes bumping the pinned SHA the one edit it would otherwise sail through. So it also asserts `vouch-check-pr.yml` still pins `PINNED_VOUCH_SHA`, and a bump fails it on purpose: re-read `gh-check-pr` at the new revision, correct `decide()` and the table above, then move the constant. CI runs it through the `GitHub Scripts` job on any change to the scripts or the files they read.
Failure is open by design. If the action cannot read `VOUCHED.td` it falls back to an empty list, every author reads as absent, and nobody is closed by an API hiccup.
`vouch-manage-by-issue.yml` runs with `merge-immediately: "false"`. The `Main Branch Rule` ruleset requires one approving review and has no bypass actors, so the action's immediate `PUT /pulls/{n}/merge` would return 405 and leave `VOUCHED.td` unchanged on `main`. The bot opens the PR, a maintainer merges it. Setting `pull-request: "false"` is not an alternative: the same ruleset blocks direct pushes.
That workflow also needs `VOUCH_APP_ID` and `VOUCH_APP_PRIVATE_KEY` repository secrets. Without them it fails at the token step before doing anything. `vouch-check-pr.yml` needs neither.
## Issue forms and templates
`ISSUE_TEMPLATE/*.yml` are GitHub issue **forms**, not markdown templates. Only forms support `required: true` and machine-parseable field ids. Blank issues are disabled in `config.yml`.
`issue-labeler.yml` reads only the `component` field id through `stefanbuck/github-issue-parser` and `redhat-plumbers-in-action/advanced-issue-labeler`, so adding new field ids is safe. Renaming `component` is not.
Current field ids:
| Form | Ids |
|------|-----|
| `bug_report.yml` | `component`, `description`, `verification`, `ai_assistance` |
| `feature_request.yml` | `component`, `description`, `ai_assistance` |
| `documentation_issue.yml` | `description`, `ai_assistance` |
## Trust list
`VOUCHED.td` is one GitHub username per line, `#` for comments. Seeded from every author with at least one merged PR in this repository, then filtered: accounts at or below a 16% merge rate across five or more attempts were dropped, since landing one change out of many is the signature of automated submission rather than contribution.
Vouch's only built-in exemptions are accounts ending in `[bot]` and repo collaborators with `write` or `admin`. **Organization membership alone is not one of them.** So `vouch-check-pr.yml` carries a job-level `if:` that skips the check for `OWNER`, `MEMBER`, and `COLLABORATOR` authors, the same exemption `pr-gate.yml` already applies. Org members are still listed in the file as a fallback, but the workflow guard is what actually holds.
+1
View File
@@ -0,0 +1 @@
AGENTS.md
+40
View File
@@ -51,3 +51,43 @@ body:
- OS:
validations:
required: true
- type: textarea
id: verification
attributes:
label: How You Verified This
description: We only take on bugs someone has actually reproduced. Show your work.
value: |
### What I Ran
The exact command or script, and where it ran.
### What I Saw
The real output, log line, or traceback. Paste it, do not describe it.
### Why This Is a Bug
What should have happened instead, and what says so: a docs link, a
docstring, a test, or the code itself.
### What I Ruled Out
Anything you checked that turned out not to be the cause.
validations:
required: true
- type: dropdown
id: ai_assistance
attributes:
label: AI Assistance
description: >-
This asks how the bug was found and confirmed, not how the text was
written. Drafting the write-up with AI is fine. We ask because it tells
us how much to trust the reproduction, not because it counts against you.
options:
- No AI involved
- AI helped me find it, and I reproduced it myself afterwards
- AI found and wrote this, and I have not reproduced it myself
validations:
required: true
+6 -3
View File
@@ -1,8 +1,11 @@
blank_issues_enabled: true
blank_issues_enabled: false
contact_links:
- name: Discord Community
- name: Question or general help
url: https://discord.gg/6PzXDgEjG5
about: Ask questions and discuss with the community
about: Ask on Discord. The issue tracker is for bugs and accepted work only.
- name: Documentation
url: https://docs.mem0.ai
about: Read the official mem0 documentation
- name: Report a security vulnerability
url: https://github.com/mem0ai/mem0/security/advisories/new
about: Report privately through a security advisory. Never open a public issue.
@@ -21,3 +21,17 @@ body:
How should the docs be improved?
validations:
required: true
- type: dropdown
id: ai_assistance
attributes:
label: AI Assistance
description: >-
This asks how the problem was found, not how the text was written.
Drafting the write-up with AI is fine.
options:
- No AI involved, I hit this reading the docs
- AI-assisted, but I checked the page myself
- AI found this, and I have not opened the page
validations:
required: true
@@ -36,3 +36,18 @@ body:
Any workarounds you've tried or other approaches considered.
validations:
required: true
- type: dropdown
id: ai_assistance
attributes:
label: AI Assistance
description: >-
This asks where the idea came from, not how the text was written.
Drafting the write-up with AI is fine. A request you hit yourself while
building something carries more weight than one a model suggested.
options:
- No AI involved, this is a need I hit myself
- AI-assisted, but the need is mine
- AI suggested this feature
validations:
required: true
+12
View File
@@ -14,6 +14,18 @@ Closes #<!-- issue number -->
- [ ] Refactor (no functional changes)
- [ ] Documentation update
## AI Assistance
<!-- This is about the code, not this description. Writing the description with AI is fine. -->
- [ ] No AI assistance
- [ ] AI-assisted (autocomplete, or I asked a model questions while writing this)
- [ ] AI-generated (an agent wrote most or all of this diff)
<!-- If you ticked either AI box, name the tool and what you checked yourself. -->
- [ ] **I can explain every line of this diff and how it interacts with the rest of the codebase, without asking an AI tool.**
## Breaking Changes
<!-- If this is a breaking change, describe what breaks and the migration path. Delete this section if not applicable. -->
+427
View File
@@ -0,0 +1,427 @@
# The list of vouched (or denounced) users for this repository.
#
# A denounced user (prefixed with a minus) is blocked outright: their pull
# requests are closed on sight, whatever they link. Being absent from this file
# blocks nothing. An unvouched author gets one comment saying so and their pull
# request is reviewed like anyone else's, because a first contribution has to
# start somewhere. Vouching is how that comment stops.
#
# This list is about who, and it is the only thing that judges who. Whether a
# change is wanted is a separate question, answered by the accepted label and
# enforced by pr-gate.yml. Neither gate substitutes for the other: a vouched
# author still needs an accepted issue, and a denounced author is turned away
# even holding one.
#
# Vouch automatically allows two kinds of account without consulting this file:
# accounts ending in [bot], and repo collaborators with write or admin
# permission. Org membership on its own is NOT one of them, so
# vouch-check-pr.yml skips the check for OWNER, MEMBER, and COLLABORATOR
# authors, and for any branch pushed to this repository.
#
# Keep every mem0ai member listed below anyway. The author_association arm of
# that guard is weaker than it looks: MEMBER needs the member's org membership
# to be public and COLLABORATOR needs a direct repo invite, so a member with
# private membership and a team-derived role reads as CONTRIBUTOR. Working from
# a branch here covers them, working from their own fork leaves this file as
# the only thing that does. A missing or miscased entry is a real gap.
#
# Syntax:
# - One handle per line (without @), sorted alphabetically.
# - Optionally specify platform: `platform:username` (e.g. `github:mitchellh`).
# - To denounce a user, prefix with minus: `-username`.
# - Optionally add a note after a space following the handle.
#
# Maintainers vouch by commenting "!vouch @username" on any issue, and denounce
# with "!denounce @username". The bot commits the change back to this file.
#
# Seeded on 2026-08-12 from every author with at least one merged pull request,
# then filtered: accounts with a merge rate at or below 16% across six or more
# attempts were dropped, since landing one change out of many is the signature
# of automated submission rather than contribution. Removal is not a ban. Any
# maintainer can !vouch these accounts back in.
1MikeMakuch
aaishikdutta
Aarkin7
abdullahirfann
AbdurNawaz
abhay-codes07
Abhineshhh
ac12644
acarbonetto
adh-wonolo
Aditya-Tripuraneni
aesher9o1
agumpandey
ahnedeee
ajmalmohad
AkisAya
akshat1423
akshseh
alessandropanzieri
alohays
aloktripathi1
amahuli03
amanagarwal042
Ameysr
amjadraza
anantoj
anchit-nishant
andrewghlee
andy-k-improving
anifort
anishesg
AnkushMalaker
AnnaSuSu
anujshandillya
ArchishmanSengupta
Arsh-mem0
ArthurHoward1
aryankhanna475
Ashu463
atahanyild
AtharvaJaiswal005
atkinsh
avp1598
axelray-dev
ayaangazali
aymenkrifa
barry166
being-abhi
berwinjoule
BillionClaw
bioshazard
bisla
bkidd1
blino
bmsvinci1729
boss-mao
Br1an67
brucewkz
cachho
caifeizhi
candidosales
cclauss
chaithanyak42
chinnuabey
ChiragArora31
ChrisFloofyKitsune
chrisqu777
clementantonyk
codexvn
Colsrch
CrepuscularIRIS
ctxlong
danielsiwiec
darkhaniop
davidatorres
deshraj
Dev-Khant
Devan019
deven298
devYRPauli
DhanushNehru
DhilipBinny
Dhravya
dimigerontaki
Diveyam-Mishra
divyansh-1009
Divyanshu9822
dog-last
DrJsPBs
dtee1
DumoeDss
e-biswas
Echo3ToEcho7
eldar702
eltociear
EnzoFanAccount
Esparon1
Fahmid-Arman
Failfail2603
FarukhS52
farzad528
felipeavilis
femto
fengjikui
fenilfaldu
fileames
Flyfoxs
fmercurio
FoliageOwO
fran3cc
frank-zsy
frederikb96
Freshield
freya0926
G26karthik
gabe-l-hart
gabrielstein-mem0
gajazlikovac
gasolin
gaurav0107
gauravagerwala
Genarojrsanchez
ghdcksgml1
GingerMoon
gmdorf
golemus
GongRzhe
GopalGB
Gyubin
haarishmk26
hackice20
halanm
hardik1408
Harin329
harshgupta-mem0
harshpandit007
hayescode
hcsum
he-yufeng
heng-ah
Hexecu
Himanshu-Sangshetti
hjlarry
HowieG
HrushiYadav
HScarb
huveewomg
Hybirdss
ianupamsingh
IgnazioDS
immuhammadfurqan
into-the-night
invincible04
Itz-Antaripa
ixchio
Jaco-Ren
Jai0401
JainamShah-22
Jainish-S
jarediaz
jeanibarz
Jerry-Terrasse
jessai2026
jesse-c
jfeng18
jferrettiboke
jjjojoj
joaomdmoura
JoeSL
johnwlockwood
jonasiwnl
josephchancey
juananpe
juaneloDev
junmo1215
Jupiter363
KapilM26
karthik-indla
KarthikeyaKollu
kartik-mem0
katarinasupe
ketangangal
kimnamu
kindertheo
kirex0
kirklin
kk2211
kmitul
koi646
kratos0718
krescent
Krishnachaitanyakc
kriszlazar
KushagraB424
l1anch1
lamost423
lan17
LeonieFreisinger
lh0x00
limboinf
liviaellen
longway-code
lsvishaal
LuciAkirami
lucifertrj
lvpx
ly-wang19
maamalama
maccuryj
mae5357
mahone3297
Malhis
maljazaery
manganeseheptoxide
manthanguptaa
mark-watson
Mark-Zeng
markmbain
matanco1
mauricioalarcon
maxvonhippel
me-tusharchandra
mezotv
MgeeeeK
mggger
mgoulart
microbluey
mikejgray
Mingxiangyu
Mini256
misrasaurabh1
mjzcng
mogith-pn
morgoth9808
moyueheng
mrbusche
Mrinank-Bhowmick
muhammed-mamun
MUZAMMILPERVAIZ
mvanhorn
naman09
NavyaAlapati13
neilbhutada
NightClover-code
nikhilsharma26500
NILAY1556
niv-hertz
NoahStapp
norrishuang
OfficialAbhinavSingh
officialasishkumar
OjusWiZard
okaditya84
omahs
OsamaNabih
oskarrough
p-tirth
Padarn
paipeline
ParseDark
parshvadaftari
Parteeksachdeva
parthshr370
parzival418
Paulie-Aditya
paurushmittal
pc9
Pecunia201
peterj
pragnyanramtha
PranavPuranik
PrashantDixit0
prateekchhikara
prathameshagrawal
pratikgajjar
PratikRai0101
Prikshit7766
Prithvi1994
QunBB
rafid001
raghavtyagii
rahulsharmavishwakarma
rajib76
rakheesingh
ranjithkumar8352
Rayhanpatel
reachAnushaKondam
Real5K
Rhythm-08
richawo
Rishiraj2594
RitwijParmar
RobinALG87
rocke2020
rodboev
rohitgr7
ron-42
roshan-shaik-ml
rst0070
rudra717
rudrajmehta-mem0
rupamoraczen
rupeshbansal
ryanrozich
SaharshPatel24
sahilyadav902
sahithreddy05
SakshiSrivastava2024
SamuelDevdas
sarkarsaurabh27
sdht0
seetharam-rajagopal
sergio-toro
SerSamgy
shafdev
shashank42
ShauryaaSharma
Sheharyar570
shenxiangzhuang
ShivamMenda
shlokkhemani
shraderdm
shrivastavanolo
shubhampal123
shuoli84
sidmohanty11
siroa
slobodaapl
soapun
soumil-rathi
spike-spiegel-21
srishti-git1110
SSDWGG
sssserrano
subhadip001
subhajit20
SudoAnirudh
sukkritsharmaofficial
sw8fbar
swarnaprakash
sxu75374
SZemse
taranjeet
techcontributor
tgabi333
theagenticguy
thomasgtaylor
tomasonjo
TommyZihao
TruptiAgrawal
turtletongue
Tushar-kalsi
Ukong0324
umran666
utkarsh240799
UzairNaeem3
V-Silpin
vatsalrathod16
vedant381
veeceey
vgvoleg
VictorECDSA
VikramIyer125
Vir-8
vsatyamuralikrishna
vuonghuuhung
WayneCao
whysosaket
wobushixiaoj
xiangpingjiang
XiaojuCH
xu-xiang
xyb
yashikabadaya
yashs33244
ygorth
youneshima
ytkimirti
YuriyTW
YusukeJustinNakajima
zaiddkhan
zegerhoogeboom
zinyando
Zlo7
Zncl2222
zzaym
@@ -0,0 +1,60 @@
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const gate = fs.readFileSync(path.join(__dirname, '..', 'workflows', 'pr-gate.yml'), 'utf8');
const rootDocsLine = gate.match(/^\s*(const rootDocs = new Set\(\['[\w.-]+'(?:, '[\w.-]+')*\]\);)\s*$/m);
const isDocsLine = gate.match(/^\s*(const isDocs = \(\w+\) => [\w.'"()[\]\/, |&!=><+-]+;)\s*$/m);
assert.ok(
rootDocsLine,
'pr-gate.yml no longer declares rootDocs as a single-line Set of quoted filenames. ' +
'This test evaluates that line to exercise the shipped predicate rather than a copy of it, ' +
'and only accepts a literal shape, so widen the pattern deliberately or keep the declaration literal.',
);
assert.ok(
isDocsLine,
'pr-gate.yml no longer declares isDocs as a single-line arrow expression. ' +
'This test evaluates that line to exercise the shipped predicate rather than a copy of it, ' +
'and refuses anything with a statement body, so keep it an expression.',
);
const isDocs = new Function(`${rootDocsLine[1]}\n${isDocsLine[1]}\nreturn isDocs;`)();
const exempt = (files) => files.length > 0 && files.every(isDocs);
const cases = [
[['docs/a.mdx'], true],
[['docs/platform/quickstart.mdx'], true],
[['README.md'], true],
[['CONTRIBUTING.md'], true],
[['CODE_OF_CONDUCT.md'], true],
[['SECURITY.md'], true],
[['README.md', 'CONTRIBUTING.md', 'docs/x.mdx'], true],
[['AGENTS.md'], false],
[['CLAUDE.md'], false],
[['LLM.md'], false],
[['README.md', 'AGENTS.md'], false],
[['README.md', 'mem0/memory/main.py'], false],
[['skills/mem0/SKILL.md'], false],
[['.github/AGENTS.md'], false],
[['.github/workflows/ci.yml'], false],
[['docs-site/index.md'], false],
[[], false],
];
let failures = 0;
for (const [files, expected] of cases) {
const actual = exempt(files);
const label = files.length ? files.join(', ') : '(no files)';
if (actual === expected) {
console.log(`ok ${label} -> ${actual ? 'exempt' : 'gated'}`);
} else {
failures += 1;
console.log(`FAIL ${label} -> ${actual ? 'exempt' : 'gated'}, expected ${expected ? 'exempt' : 'gated'}`);
}
}
console.log(failures === 0 ? '\nPASS' : `\nFAIL (${failures} cases)`);
process.exit(failures === 0 ? 0 : 1);
+122
View File
@@ -0,0 +1,122 @@
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const workflowPath = path.join(__dirname, '..', 'workflows', 'vouch-check-pr.yml');
const workflow = fs.readFileSync(workflowPath, 'utf8');
const PINNED_VOUCH_SHA = 'd66fa29a64600490892131ad87597c30c91fcac4';
assert.ok(
workflow.includes(`mitchellh/vouch/action/check-pr@${PINNED_VOUCH_SHA}`),
`decide() below is a hand transcription of gh-check-pr from vouch/github.nu at ${PINNED_VOUCH_SHA} (v1.5.0). ` +
'It reads the action, it does not run it, so on its own it agrees with itself whatever the action does. ' +
'vouch-check-pr.yml now pins a different revision: re-read gh-check-pr there, update decide() and the ' +
'decision table in .github/AGENTS.md to match it, then set PINNED_VOUCH_SHA to the new SHA.',
);
const actionDefaults = { 'require-vouch': true, 'auto-close': false };
const booleanInput = (name) => {
const match = workflow.match(new RegExp(`^\\s+${name}:\\s*"?(true|false)"?\\s*$`, 'm'));
return match ? match[1] === 'true' : actionDefaults[name];
};
const requireVouch = booleanInput('require-vouch');
const autoClose = booleanInput('auto-close');
const commentedStatus = (() => {
const match = workflow.match(/steps\.vouch\.outputs\.status == '(\w+)'/);
assert.ok(match, 'the follow-up comment step is not keyed on a vouch status');
return match[1];
})();
const decide = (author) => {
if (author === 'bot') return { status: 'skipped', closed: false, actionComments: false };
if (author === 'collaborator' || author === 'vouched') {
return { status: 'vouched', closed: false, actionComments: false };
}
if (author === 'denounced') {
if (!autoClose) return { status: 'closed', closed: false, actionComments: false };
return { status: 'closed', closed: true, actionComments: true };
}
if (!requireVouch) return { status: 'allowed', closed: false, actionComments: false };
if (!autoClose) return { status: 'closed', closed: false, actionComments: false };
return { status: 'closed', closed: true, actionComments: true };
};
const outcome = (author) => {
const result = decide(author);
return { ...result, workflowComments: result.status === commentedStatus };
};
const cases = [
{ author: 'bot', closed: false, comments: 0 },
{ author: 'collaborator', closed: false, comments: 0 },
{ author: 'vouched', closed: false, comments: 0 },
{ author: 'unvouched', closed: false, comments: 1 },
{ author: 'denounced', closed: true, comments: 1 },
];
let failures = 0;
for (const expected of cases) {
const actual = outcome(expected.author);
const comments = Number(actual.actionComments) + Number(actual.workflowComments);
try {
assert.strictEqual(actual.closed, expected.closed, `${expected.author}: closed`);
assert.strictEqual(comments, expected.comments, `${expected.author}: comment count`);
console.log(`ok ${expected.author} -> ${actual.status}, closed=${actual.closed}, comments=${comments}`);
} catch (error) {
failures += 1;
console.log(`FAIL ${expected.author} -> ${actual.status}, closed=${actual.closed}, comments=${comments}`);
console.log(` ${error.message}: expected ${JSON.stringify(expected)}`);
}
}
console.log(`\nvouch@${PINNED_VOUCH_SHA.slice(0, 7)} require-vouch=${requireVouch} auto-close=${autoClose} comment-on=${commentedStatus}`);
const parseDenounced = (contents) => new Set(contents
.split('\n')
.map((line) => line.trim())
.filter((line) => line.startsWith('-'))
.map((line) => line.slice(1).split(/\s+/)[0].split(':').pop().toLowerCase())
.filter(Boolean));
const gate = fs.readFileSync(path.join(__dirname, '..', 'workflows', 'pr-gate.yml'), 'utf8');
assert.ok(
gate.includes(".filter((line) => line.startsWith('-'))"),
'pr-gate.yml no longer parses the denounce list the way this test does',
);
const vouched = fs.readFileSync(path.join(__dirname, '..', 'VOUCHED.td'), 'utf8');
const denouncedNow = parseDenounced(vouched);
const sample = parseDenounced([
'# -notacomment is a comment line',
'-SpamBot seeded 2026-08-12',
'-github:OtherSpammer',
'realcontributor',
'',
].join('\n'));
let parseFailures = 0;
for (const [label, actual, expected] of [
['denounce entry, with note', sample.has('spambot'), true],
['denounce entry, platform prefixed', sample.has('otherspammer'), true],
['comment line is not an entry', sample.has('notacomment'), false],
['vouched entry is not denounced', sample.has('realcontributor'), false],
['live file parses without throwing', denouncedNow instanceof Set, true],
]) {
try {
assert.strictEqual(actual, expected, label);
console.log(`ok ${label}`);
} catch (error) {
parseFailures += 1;
console.log(`FAIL ${label}: ${error.message}`);
}
}
console.log(`denounced in VOUCHED.td: ${denouncedNow.size}`);
const total = failures + parseFailures;
console.log(total === 0 ? 'PASS' : `FAIL (${total} assertions)`);
process.exit(total === 0 ? 0 : 1);
+53
View File
@@ -41,9 +41,12 @@ jobs:
mem0_plugin: ${{ steps.filter.outputs.mem0_plugin }}
opencode_plugin: ${{ steps.filter.outputs.opencode_plugin }}
pi_agent_plugin: ${{ steps.filter.outputs.pi_agent_plugin }}
deepseek_plugin: ${{ steps.filter.outputs.deepseek_plugin }}
n8n_nodes_mem0: ${{ steps.filter.outputs.n8n_nodes_mem0 }}
zapier_mem0: ${{ steps.filter.outputs.zapier_mem0 }}
mem0_strands: ${{ steps.filter.outputs.mem0_strands }}
docs_llms_txt: ${{ steps.filter.outputs.docs_llms_txt }}
github_scripts: ${{ steps.filter.outputs.github_scripts }}
steps:
- uses: dorny/paths-filter@v3
id: filter
@@ -87,6 +90,10 @@ jobs:
- 'integrations/pi-agent-plugin/**'
- '.github/workflows/pi-agent-plugin-checks.yml'
- '.github/workflows/ci-gate.yml'
deepseek_plugin:
- 'integrations/deepseek-plugin/**'
- '.github/workflows/deepseek-plugin-checks.yml'
- '.github/workflows/ci-gate.yml'
n8n_nodes_mem0:
- 'integrations/n8n-nodes-mem0/**'
- '.github/workflows/n8n-nodes-mem0-checks.yml'
@@ -94,6 +101,10 @@ jobs:
- 'integrations/zapier-mem0/**'
- '.github/workflows/zapier-mem0-checks.yml'
- '.github/workflows/ci-gate.yml'
mem0_strands:
- 'integrations/mem0-strands/**'
- '.github/workflows/mem0-strands-checks.yml'
- '.github/workflows/ci-gate.yml'
docs_llms_txt:
- 'docs/**/*.mdx'
- 'docs/llms.txt'
@@ -101,6 +112,13 @@ jobs:
- 'scripts/llms-txt-ignore.txt'
- '.github/workflows/docs-llms-txt-check.yml'
- '.github/workflows/ci-gate.yml'
github_scripts:
- '.github/scripts/**'
- '.github/VOUCHED.td'
- '.github/workflows/pr-gate.yml'
- '.github/workflows/vouch-check-pr.yml'
- '.github/workflows/issue-labeler.yml'
- '.github/workflows/ci-gate.yml'
python-sdk:
name: Python SDK
@@ -158,6 +176,13 @@ jobs:
uses: ./.github/workflows/pi-agent-plugin-checks.yml
secrets: inherit
deepseek-plugin:
name: DeepSeek Harness Plugin
needs: changes
if: needs.changes.outputs.deepseek_plugin == 'true'
uses: ./.github/workflows/deepseek-plugin-checks.yml
secrets: inherit
n8n-nodes-mem0:
name: n8n Node
needs: changes
@@ -170,6 +195,13 @@ jobs:
uses: ./.github/workflows/zapier-mem0-checks.yml
secrets: inherit
mem0-strands:
name: mem0-strands
needs: changes
if: needs.changes.outputs.mem0_strands == 'true'
uses: ./.github/workflows/mem0-strands-checks.yml
secrets: inherit
docs-llms-txt:
name: docs llms.txt
needs: changes
@@ -177,6 +209,24 @@ jobs:
uses: ./.github/workflows/docs-llms-txt-check.yml
secrets: inherit
github-scripts:
name: GitHub Scripts
needs: changes
if: needs.changes.outputs.github_scripts == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Run .github/scripts tests
run: |
for test in .github/scripts/*.test.js; do
echo "::group::$test"
node "$test"
echo "::endgroup::"
done
gate:
name: CI Gate
needs:
@@ -189,9 +239,12 @@ jobs:
- mem0-plugin
- opencode-plugin
- pi-agent-plugin
- deepseek-plugin
- n8n-nodes-mem0
- zapier-mem0
- mem0-strands
- docs-llms-txt
- github-scripts
if: always()
runs-on: ubuntu-latest
steps:
+1 -1
View File
@@ -28,7 +28,7 @@ jobs:
}
base_version=$(git show "$BASE_SHA:pyproject.toml" 2>/dev/null | extract_version || echo "")
head_version=$(extract_version < pyproject.toml)
head_version=$(git show "$HEAD_SHA:pyproject.toml" | extract_version)
echo "Base version: ${base_version:-<unknown>}"
echo "Head version: $head_version"
+60
View File
@@ -0,0 +1,60 @@
name: Publish @mem0/deepseek-plugin 📦 to npm
# Dispatched by release.yml (Release Router) when a release tagged
# deepseek-plugin-v* is published. Can also be dispatched manually to re-publish
# a tag.
on:
workflow_dispatch:
inputs:
tag:
description: 'Release tag to build and publish (e.g. deepseek-plugin-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/deepseek-plugin 📦 to npm
if: startsWith(inputs.tag, 'deepseek-plugin-v')
runs-on: ubuntu-latest
permissions:
id-token: write
defaults:
run:
working-directory: integrations/deepseek-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/deepseek-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,90 @@
name: deepseek-plugin checks
# On PRs this is invoked by ci-gate.yml (the single required check);
# push-to-main and manual runs remain standalone.
on:
workflow_dispatch:
push:
branches: [main]
paths:
- 'integrations/deepseek-plugin/**'
- '.github/workflows/deepseek-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/deepseek-plugin/pnpm-lock.yaml
- name: Install dependencies
run: cd integrations/deepseek-plugin && pnpm install --frozen-lockfile
- name: Type check
run: cd integrations/deepseek-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/deepseek-plugin/pnpm-lock.yaml
- name: Install dependencies
run: cd integrations/deepseek-plugin && pnpm install --frozen-lockfile
- name: Run tests
run: cd integrations/deepseek-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/deepseek-plugin/pnpm-lock.yaml
- name: Install dependencies
run: cd integrations/deepseek-plugin && pnpm install --frozen-lockfile
- name: Build
run: cd integrations/deepseek-plugin && pnpm build
- name: Verify dist output exists
run: |
test -f integrations/deepseek-plugin/dist/index.js || (echo "Build output missing: dist/index.js" && exit 1)
test -f integrations/deepseek-plugin/dist/index.d.ts || (echo "Build output missing: dist/index.d.ts" && exit 1)
+50
View File
@@ -0,0 +1,50 @@
name: Publish mem0-strands 🐍 distribution 📦 to PyPI
# Dispatched by release.yml (Release Router) when a release tagged
# mem0-strands-v* is published. Can also be dispatched manually to re-publish
# a tag. Publishing uses PyPI Trusted Publishing (OIDC), so no API token is
# stored; the `mem0-strands` PyPI project must have a trusted publisher
# configured for mem0ai/mem0 + this workflow.
on:
workflow_dispatch:
inputs:
tag:
description: 'Release tag to build and publish (e.g. mem0-strands-v0.1.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-strands 📦 to PyPI
if: startsWith(inputs.tag, 'mem0-strands-v')
runs-on: ubuntu-latest
permissions:
id-token: write
defaults:
run:
working-directory: integrations/mem0-strands/python
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.tag }}
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install Hatch
run: pip install hatch
- name: Build a binary wheel and a source tarball
run: hatch build --clean
- name: Publish distribution 📦 to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: integrations/mem0-strands/python/dist/
+82
View File
@@ -0,0 +1,82 @@
name: mem0-strands CI
# On PRs this is invoked by ci-gate.yml (the single required check);
# push-to-main and manual runs remain standalone.
on:
workflow_dispatch:
push:
branches: [main]
paths:
- 'integrations/mem0-strands/**'
- '.github/workflows/mem0-strands-checks.yml'
workflow_call:
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dev dependencies
working-directory: integrations/mem0-strands/python
run: pip install -e ".[dev]"
- name: Lint with ruff
working-directory: integrations/mem0-strands/python
run: ruff check .
- name: Check formatting
working-directory: integrations/mem0-strands/python
run: ruff format --check .
- name: Type-check with mypy
working-directory: integrations/mem0-strands/python
run: mypy src
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dev dependencies
working-directory: integrations/mem0-strands/python
run: pip install -e ".[dev]"
- name: Run tests
working-directory: integrations/mem0-strands/python
run: pytest
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install Hatch
run: pip install hatch
- name: Build
working-directory: integrations/mem0-strands/python
run: hatch build --clean
- name: Verify dist output
run: |
ls integrations/mem0-strands/python/dist/*.whl || (echo "Wheel file missing" && exit 1)
ls integrations/mem0-strands/python/dist/*.tar.gz || (echo "Source dist missing" && exit 1)
+214
View File
@@ -0,0 +1,214 @@
name: PR Gate
on:
pull_request_target:
types: [opened, reopened, ready_for_review, edited]
issues:
types: [labeled]
concurrency:
group: pr-gate-${{ github.event_name }}-${{ github.event.action }}-${{ github.event.pull_request.number || github.event.issue.number }}
cancel-in-progress: ${{ github.event_name == 'pull_request_target' }}
env:
GATE_EFFECTIVE_FROM: '2026-08-12T00:00:00Z'
permissions:
contents: read
pull-requests: write
issues: read
jobs:
gate:
if: >-
github.event_name == 'pull_request_target' &&
github.event.action != 'edited' &&
github.event.pull_request.draft == false &&
github.event.pull_request.user.type != 'Bot' &&
github.event.pull_request.head.repo.full_name != github.repository &&
!contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.pull_request.author_association)
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request;
const { owner, repo } = context.repo;
const effectiveFrom = process.env.GATE_EFFECTIVE_FROM;
if (effectiveFrom && Date.parse(pr.created_at) < Date.parse(effectiveFrom)) {
core.info(`Opened ${pr.created_at}, before the gate took effect ${effectiveFrom}. Skipped.`);
return;
}
const { data: current } = await github.rest.pulls.get({
owner, repo, pull_number: pr.number,
});
if (current.state !== 'open') {
core.info(`#${pr.number} is already ${current.state}. Skipped.`);
return;
}
const files = await github.paginate(github.rest.pulls.listFiles, {
owner, repo, pull_number: pr.number, per_page: 100,
});
const rootDocs = new Set(['README.md', 'CONTRIBUTING.md', 'CODE_OF_CONDUCT.md', 'SECURITY.md']);
const isDocs = (filename) => filename.startsWith('docs/') || rootDocs.has(filename);
if (files.length > 0 && files.every((file) => isDocs(file.filename))) {
core.info('Docs-only PR, gate skipped');
return;
}
const { repository } = await github.graphql(
`query ($owner: String!, $repo: String!, $number: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $number) {
closingIssuesReferences(first: 20) {
nodes { number labels(first: 50) { nodes { name } } }
}
}
}
}`,
{ owner, repo, number: pr.number },
);
const accepted = repository.pullRequest.closingIssuesReferences.nodes
.filter((issue) => issue.labels.nodes.some((label) => label.name === 'accepted'))
.map((issue) => issue.number);
if (accepted.length > 0) {
core.info(`Accepted issue linked: #${accepted.join(', #')}`);
return;
}
const body = [
'<!-- pr-gate -->',
'Thanks for taking the time to open this.',
'',
'We only review pull requests that fix an issue we have already agreed to take on, so this one is closed for now.',
'**Closed does not mean rejected.** It means it is not in the queue yet, and reopening takes about a minute.',
'',
'To get it reviewed:',
'',
'1. Make sure an issue describes the problem, with the version you are on, a runnable reproduction, and the real output or traceback you saw.',
'2. Link it from this pull request description with `Closes #<number>`.',
'3. Ask a maintainer to label that issue `accepted`. This pull request reopens by itself when they do.',
'',
'Issue already labeled `accepted`? Just add `Closes #<number>` to the description. That reopens this too.',
'',
'Documentation-only changes skip this gate entirely.',
'',
'See [CONTRIBUTING.md](https://github.com/mem0ai/mem0/blob/main/CONTRIBUTING.md) for the full policy.',
].join('\n');
await github.rest.issues.createComment({
owner, repo, issue_number: pr.number, body,
});
await github.rest.pulls.update({
owner, repo, pull_number: pr.number, state: 'closed',
});
core.info(`Closed #${pr.number}: no accepted issue linked`);
reopen:
if: >-
(github.event_name == 'issues' && github.event.label.name == 'accepted') ||
(github.event.action == 'edited' && github.event.pull_request.state == 'closed')
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v7
with:
script: |
const { owner, repo } = context.repo;
const marker = '<!-- pr-gate -->';
const denounced = await (async () => {
try {
const { data } = await github.rest.repos.getContent({
owner, repo, path: '.github/VOUCHED.td',
ref: context.payload.repository.default_branch,
});
return new Set(Buffer.from(data.content, 'base64').toString('utf8')
.split('\n')
.map((line) => line.trim())
.filter((line) => line.startsWith('-'))
.map((line) => line.slice(1).split(/\s+/)[0].split(':').pop().toLowerCase())
.filter(Boolean));
} catch (error) {
core.warning(`Could not read VOUCHED.td, treating nobody as denounced: ${error.message}`);
return new Set();
}
})();
const isReopenable = async (number) => {
const { repository } = await github.graphql(
`query ($owner: String!, $repo: String!, $number: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $number) {
state
author { login }
closingIssuesReferences(first: 20) {
nodes { labels(first: 50) { nodes { name } } }
}
}
}
}`,
{ owner, repo, number },
);
const pullRequest = repository.pullRequest;
if (denounced.has(pullRequest.author?.login?.toLowerCase())) {
core.info(`#${number} is from a denounced author. Vouch outranks this gate.`);
return false;
}
return pullRequest.state === 'CLOSED' &&
pullRequest.closingIssuesReferences.nodes.some((issue) =>
issue.labels.nodes.some((label) => label.name === 'accepted'));
};
let candidates;
if (context.eventName === 'issues') {
const { repository } = await github.graphql(
`query ($owner: String!, $repo: String!, $number: Int!) {
repository(owner: $owner, name: $repo) {
issue(number: $number) {
closedByPullRequestsReferences(first: 20, includeClosedPrs: true) {
nodes { number }
}
}
}
}`,
{ owner, repo, number: context.payload.issue.number },
);
candidates = repository.issue.closedByPullRequestsReferences.nodes.map((pr) => pr.number);
} else {
candidates = [context.payload.pull_request.number];
}
for (const number of candidates) {
if (!(await isReopenable(number))) {
core.info(`#${number} is not a closed pull request linking an accepted issue. Skipped.`);
continue;
}
const comments = await github.paginate(github.rest.issues.listComments, {
owner, repo, issue_number: number, per_page: 100,
});
if (!comments.some((comment) => comment.body?.startsWith(marker))) {
core.info(`#${number} was not closed by this gate. Left alone.`);
continue;
}
try {
await github.rest.pulls.update({
owner, repo, pull_number: number, state: 'open',
});
} catch (error) {
core.warning(`Could not reopen #${number}: ${error.message}`);
continue;
}
await github.rest.issues.createComment({
owner, repo, issue_number: number,
body: 'An `accepted` issue is linked now, so this is open again and ready for review.',
});
core.info(`Reopened #${number}`);
}
+2
View File
@@ -45,7 +45,9 @@ jobs:
openclaw-v*) workflow="openclaw-cd.yml" ;;
opencode-v*) workflow="opencode-plugin-cd.yml" ;;
pi-agent-v*) workflow="pi-agent-plugin-cd.yml" ;;
deepseek-plugin-v*) workflow="deepseek-plugin-cd.yml" ;;
n8n-nodes-mem0-v*) workflow="n8n-nodes-mem0-cd.yml" ;;
mem0-strands-v*) workflow="mem0-strands-cd.yml" ;;
v*) workflow="cd.yml" ;;
*)
echo "::error::Release tag '$TAG' does not match any known package prefix — nothing will be published. See the tag prefix table in AGENTS.md."
+1 -1
View File
@@ -41,7 +41,7 @@ jobs:
set -euo pipefail
base_version=$(git show "$BASE_SHA:mem0-ts/package.json" 2>/dev/null | jq -r .version || echo "")
head_version=$(jq -r .version mem0-ts/package.json)
head_version=$(git show "$HEAD_SHA:mem0-ts/package.json" | jq -r .version)
echo "Base version: ${base_version:-<unknown>}"
echo "Head version: $head_version"
+59
View File
@@ -0,0 +1,59 @@
name: Vouch - Check PR
on:
pull_request_target:
types: [opened, reopened]
concurrency:
group: vouch-check-pr-${{ github.event.pull_request.number }}
cancel-in-progress: true
permissions:
contents: read
pull-requests: write
jobs:
check:
if: >-
github.event.pull_request.user.type != 'Bot' &&
github.event.pull_request.head.repo.full_name != github.repository &&
!contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.pull_request.author_association)
runs-on: ubuntu-latest
steps:
- uses: mitchellh/vouch/action/check-pr@d66fa29a64600490892131ad87597c30c91fcac4 # v1.5.0
id: vouch
with:
pr-number: ${{ github.event.pull_request.number }}
require-vouch: false
auto-close: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- if: steps.vouch.outputs.status == 'allowed'
uses: actions/github-script@v7
with:
script: |
const { owner, repo } = context.repo;
const pr = context.payload.pull_request;
const marker = '<!-- vouch-check -->';
const comments = await github.paginate(github.rest.issues.listComments, {
owner, repo, issue_number: pr.number, per_page: 100,
});
if (comments.some((comment) => comment.body?.startsWith(marker))) {
core.info('Vouch comment already posted, skipped.');
return;
}
const body = [
marker,
`Hi @${context.payload.pull_request.user.login}, thanks for opening this pull request.`,
'',
"This is just a soft check: you are not yet in this repo's vouched contributor list (`.github/VOUCHED.td`). Nothing is blocked and there is nothing you need to do.",
'',
`A maintainer can vouch for you by commenting \`!vouch @${context.payload.pull_request.user.login}\` on any issue.`,
].join('\n');
await github.rest.issues.createComment({
owner, repo, issue_number: pr.number, body,
});
@@ -0,0 +1,42 @@
name: Vouch - Manage by Issue
on:
issue_comment:
types: [created]
concurrency:
group: vouch-manage
cancel-in-progress: false
permissions:
contents: write
issues: write
pull-requests: write
jobs:
manage:
if: contains(github.event.comment.body, '!vouch') || contains(github.event.comment.body, '!denounce') || contains(github.event.comment.body, '!unvouch')
runs-on: ubuntu-latest
steps:
- uses: actions/create-github-app-token@v3
id: app-token
with:
app-id: ${{ secrets.VOUCH_APP_ID }}
private-key: ${{ secrets.VOUCH_APP_PRIVATE_KEY }}
- uses: actions/checkout@v4
with:
token: ${{ steps.app-token.outputs.token }}
- uses: mitchellh/vouch/action/manage-by-issue@d66fa29a64600490892131ad87597c30c91fcac4 # v1.5.0
with:
repo: ${{ github.repository }}
issue-id: ${{ github.event.issue.number }}
comment-id: ${{ github.event.comment.id }}
vouch-keyword: "!vouch"
denounce-keyword: "!denounce"
unvouch-keyword: "!unvouch"
pull-request: "true"
merge-immediately: "false"
env:
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
+4
View File
@@ -191,3 +191,7 @@ qdrant_storage/
testing.ipynb
.weave/
# TypeScript incremental build info and local, uncommitted e2e scripts (used by the integrations, e.g. integrations/deepseek-plugin)
*.tsbuildinfo
*.local.mjs
+119 -537
View File
@@ -1,593 +1,175 @@
# AGENTS.md
This file provides context for AI coding assistants (Claude Code, Cursor, GitHub Copilot, Codex, etc.) working with the Mem0 repository.
Context for AI coding assistants (Claude Code, Cursor, Copilot, Codex) working in the Mem0 repository.
## Project Overview
**Mem0** ("mem-zero") is a memory layer for AI agents: persistent, personalized memory through a hosted platform API and self-hosted open-source SDKs. Apache-2.0.
[Repository](https://github.com/mem0ai/mem0) · [Documentation](https://docs.mem0.ai)
**Mem0** ("mem-zero") is an intelligent memory layer for AI agents and assistants. It provides persistent, personalized memory via both a hosted platform API and self-hosted open-source SDKs.
This is a polyglot monorepo and **every package sets its own rules**. Read the `AGENTS.md` nearest the files you are editing before running any command. The linters, formatters, test runners, and line lengths genuinely differ per package, and using the wrong one fails CI or produces a diff full of noise.
- **Repository**: https://github.com/mem0ai/mem0
- **Documentation**: https://docs.mem0.ai
- **License**: Apache-2.0
## Do NOT
## Repository Structure
- Open a pull request without a signed CLA. It will not be reviewed. See [The CLA is not optional](#the-cla-is-not-optional).
- Open a pull request that does not link an issue carrying the `accepted` label. A bot closes it within a minute. See [Two gates decide whether your pull request stays open](#two-gates-decide-whether-your-pull-request-stays-open).
- Modify anything in `.github/workflows/` without explicit maintainer approval. Publishing credentials are pinned to workflow filenames.
- Commit `.env` files, API keys, or credentials.
- Skip pre-commit hooks.
- Use npm or yarn in TypeScript packages. This repo is pnpm-only (Bun in `.opencode-plugin/`).
- Use `require()` in TypeScript. ES module `import` syntax only.
- Mix up linter configs. Root Python is ruff at line length **120**, `cli/python/` is ruff at **100**, `cli/node/` is Biome, `mem0-ts/` is Prettier, `integrations/vercel-ai-sdk/` is ESLint.
- Add Python dependencies to the core `dependencies` list in `pyproject.toml`. Use an optional group.
- Change a public API without updating `docs/` in the same pull request.
- Introduce a new framework or abstraction without discussion. Follow the patterns already in the file you are editing.
This is a **polyglot monorepo** containing Python and TypeScript packages, CLIs, servers, plugins, and documentation.
## Where to look
### Key Directories
| Editing | Read | Toolchain |
|---------|------|-----------|
| `mem0/` | [`mem0/AGENTS.md`](mem0/AGENTS.md) | hatch, ruff 120, pytest |
| `tests/` | [`tests/AGENTS.md`](tests/AGENTS.md) | pytest |
| `mem0-ts/` | [`mem0-ts/AGENTS.md`](mem0-ts/AGENTS.md) | pnpm, tsup, Prettier, jest |
| `cli/python/` | [`cli/python/AGENTS.md`](cli/python/AGENTS.md) | ruff **100**, pytest |
| `cli/node/` | [`cli/node/AGENTS.md`](cli/node/AGENTS.md) | pnpm, tsup, Biome, vitest |
| `integrations/` | [`integrations/AGENTS.md`](integrations/AGENTS.md) | varies per integration |
| `server/` | [`server/AGENTS.md`](server/AGENTS.md) | Docker Compose, FastAPI |
| `docs/` | [`docs/AGENTS.md`](docs/AGENTS.md) | Mintlify |
| `skills/` | [`skills/AGENTS.md`](skills/AGENTS.md) | markdown, size-budgeted |
| `.github/` | [`.github/AGENTS.md`](.github/AGENTS.md) | GitHub Actions |
| Directory | Description |
|-----------|-------------|
| `mem0/` | Core Python SDK (`mem0ai` on PyPI) — memory, LLMs, embeddings, vector stores, graphs, rerankers |
| `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` |
| `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 |
| `integrations/n8n-nodes-mem0/` | `@mem0/n8n-nodes-mem0` — n8n community node; add / search / get / update / delete memories |
| `integrations/zapier-mem0/` | `@mem0/zapier` — Zapier Platform CLI app (deploys to Zapier, not npm); add / search / get / delete memories |
| `server/` | FastAPI REST server for self-hosted Mem0 (Docker: FastAPI + PostgreSQL/pgvector + Neo4j) |
| `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/` |
## Repository map
| Directory | What it is |
|-----------|------------|
| `mem0/` | Core Python SDK (`mem0ai` on PyPI): memory, LLMs, embeddings, vector stores, graphs, rerankers |
| `mem0-ts/` | TypeScript SDK (`mem0ai` on npm): hosted 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` |
| `integrations/` | Agent and editor integrations, one self-contained directory each |
| `server/` | FastAPI REST server for self-hosted Mem0 (Docker: FastAPI + pgvector + Neo4j) |
| `skills/` | Claude Code skill definitions, published by raw URL |
| `docs/` | Documentation site (Mintlify) |
| `tests/` | Python SDK tests (pytest) |
| `evaluation/` | Submodule → [`mem0ai/memory-benchmarks`](https://github.com/mem0ai/memory-benchmarks) — benchmarking (LOCOMO, LongMemEval, BEAM) lives in that repo |
| `examples/` | Sample projects & runnable demos — apps, Chrome extension, multi-agent patterns, and Jupyter notebooks (`notebooks/`) |
| `examples/` | Sample apps, Chrome extension, multi-agent patterns, notebooks |
| `scripts/` | Repo-wide utilities, e.g. `check-llms-txt-coverage.py` |
| `evaluation/` | Submodule pinned to [`mem0ai/memory-benchmarks`](https://github.com/mem0ai/memory-benchmarks) |
| `pr-reviews/` | Pull request review materials |
| `scripts/` | Repo-wide utility scripts (e.g., `check-llms-txt-coverage.py` for docs/llms.txt sync) |
### Core Package Dependencies
```
mem0 (Python SDK) mem0-ts (TypeScript SDK)
├── mem0/memory/ ├── src/client/ (MemoryClient — hosted)
├── mem0/llms/ └── src/oss/ (Memory — self-hosted)
├── mem0/memory/ ├── src/client/ MemoryClient (hosted)
├── mem0/llms/ └── src/oss/ Memory (self-hosted)
├── mem0/embeddings/ ├── src/llms/
├── mem0/vector_stores/ ├── src/embeddings/
├── mem0/graphs/ ├── src/vector_stores/
└── mem0/reranker/ └── src/graphs/
cli/python/ ──▶ mem0ai (optional, for OSS mode)
cli/node/ ──▶ mem0ai (npm, for API calls)
integrations/vercel-ai-sdk/ ──▶ ai, @ai-sdk/* providers
integrations/openclaw/ ──▶ mem0ai (npm)
cli/python/ ──▶ mem0ai (optional, OSS mode)
cli/node/ ──▶ mem0ai (npm)
integrations/vercel-ai-sdk/ ──▶ ai, @ai-sdk/*
integrations/openclaw/ ──▶ mem0ai (npm)
```
## Development Setup
### Requirements
- **Python**: 3.9+ (3.10+ for CLI)
- **Node.js**: v18+ (v20 or v22 recommended)
- **pnpm**: v10+ (`npm install -g pnpm@10`) — used for all TypeScript packages
- **Hatch**: Python build/environment tool (`pip install hatch`)
- **Docker**: Required for `server/` development
### Initial Setup
## Setup
```bash
# Python SDK
hatch shell dev_py_3_11 # creates environment with all deps
pre-commit install # install git hooks
hatch shell dev_py_3_11 # Python: creates the env with all deps
pre-commit install # ruff + isort on commit
# TypeScript packages
cd mem0-ts && pnpm install # TS SDK
cd cli/node && pnpm install # Node CLI
cd integrations/vercel-ai-sdk && pnpm install # Vercel AI provider
cd integrations/openclaw && pnpm install # OpenClaw plugin
cd <ts-package> && pnpm install
```
## Build, Lint, and Test Commands
Requirements: Python 3.9+ (3.10+ for the CLI), Node 18+ (20 or 22 preferred), pnpm 10+, hatch, Docker for `server/`.
### Python SDK (`mem0/`)
## Conventions everywhere
- **Naming:** `snake_case.py`, `test_<module>.py`, `snake_case.ts`, `<module>.test.ts`, `kebab-case` for config and manifest files.
- **Python:** Pydantic v2 for models and config. Providers inherit a `base.py` abstract class; config lives in `configs.py`.
- **TypeScript:** strict mode, tsup builds, ES module imports.
- **Commits:** [Conventional Commits](https://www.conventionalcommits.org/) (`feat:`, `fix:`, `docs:`, `refactor:`, `test:`).
- **Versions:** bump in `pyproject.toml` or `package.json`. Releases are cut by tag prefix; see [`.github/AGENTS.md`](.github/AGENTS.md).
## Benchmarking
Benchmarks (LOCOMO, LongMemEval, BEAM) live in [`mem0ai/memory-benchmarks`](https://github.com/mem0ai/memory-benchmarks). The in-repo `evaluation/` path is a submodule pinned to that repo's `main`:
```bash
# Environment setup (uses Hatch)
hatch shell dev_py_3_11 # or dev_py_3_9, dev_py_3_10, dev_py_3_12
# Linting and formatting
make lint # ruff check
make format # ruff format
make sort # isort mem0/
# Tests
make test # pytest tests/
make test-py-3.9 # test specific Python version (3.9–3.12)
# Build and publish
make build # hatch build
make publish # hatch publish
git submodule update --init evaluation
```
- **Python:** 3.9, 3.10, 3.11, 3.12
- **Linter/formatter:** Ruff (line length **120**)
- **Import sorting:** isort (`profile = "black"`)
- **Test framework:** pytest (with pytest-mock, pytest-asyncio)
- **Pre-commit hooks:** ruff + isort — run `pre-commit install` before committing
## What to ship with a change
### TypeScript SDK (`mem0-ts/`)
Guidelines, not rules. Trivial fixes need less; anything user-facing needs more.
```bash
cd mem0-ts
pnpm install
pnpm run build # tsup
pnpm run test # jest (all tests)
pnpm run test:unit # jest --coverage (unit tests only)
pnpm run test:integration # jest (integration tests, needs MEM0_API_KEY)
pnpm run test:ci # jest --coverage --ci (CI mode)
pnpm run test:watch # jest watch mode
```
| Change | Expect |
|--------|--------|
| **Bug fix** | A regression test that fails without the fix, written first. The fix. The relevant suite passing. The package's linter run. |
| **New feature** | Implementation following existing patterns, test coverage, `docs/` updates for public APIs, an example if the behavior is user-facing, and an `llms.txt` entry for any new `.mdx` page. |
| **New provider** | See [Adding a provider](mem0/AGENTS.md#adding-a-provider). |
| **New integration** | See [Adding an integration](integrations/AGENTS.md#adding-an-integration). |
| **Refactor** | Tests for changed behavior, existing tests still green. No docs needed for internal-only changes. |
- **Node:** 20, 22 (CI-tested)
- **Build:** tsup (CJS + ESM)
- **Test:** jest
- **Formatter:** prettier
Fix bugs at the root, not at the symptom. If a guard belongs in a shared function, put it there rather than in each caller.
### Python CLI (`cli/python/`)
## Contributing
```bash
cd cli/python
pip install -e ".[dev]" # dev install with ruff + pytest
ruff check . # lint
ruff format . # format
pytest # test
hatch build # build
```
Full guide: [`CONTRIBUTING.md`](CONTRIBUTING.md). Conduct: [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md).
- **Python:** 3.10+ (not 3.9)
- **Linter/formatter:** Ruff (line length **100** — different from root SDK)
- **Ruff rules:** E, F, I, W, UP, B, SIM, RUF (ignores E501, B008 for Typer patterns, SIM108)
- **Framework:** Typer + Rich + httpx
- **Entry point:** `mem0 = "mem0_cli.app:main"`
- **Source layout:** `src/mem0_cli/`
- **Optional dependency:** `mem0ai` (for OSS mode, via `[oss]` extra)
1. Open an issue **first** and wait for a maintainer to apply the `accepted` label. Every PR must link it with `Closes #<number>`. PRs without an accepted linked issue are closed automatically by the [PR Gate](.github/workflows/pr-gate.yml), with a reopen path. Documentation-only changes are exempt.
2. Fork, then branch from `main` (`feature/...`, `fix/...`).
3. Make the change: code, tests, docs, examples.
4. Run lint and tests for **every** package you touched.
5. Commit with Conventional Commits.
6. Open the PR against `main` and fill in [the template](.github/PULL_REQUEST_TEMPLATE.md). Do not paraphrase it; GitHub prefills it.
7. **Sign the CLA.**
### Node CLI (`cli/node/`)
### Two gates decide whether your pull request stays open
```bash
cd cli/node
pnpm install
pnpm run build # tsup
pnpm run lint # biome check src/
pnpm run lint:fix # biome check --write src/
pnpm run typecheck # tsc --noEmit
pnpm run test # vitest run
pnpm run test:watch # vitest (watch mode)
pnpm run dev # tsx src/index.ts (development)
```
Two workflows run on every pull request from a fork. They judge different things and neither covers for the other, so a pull request has to get past both.
- **Node:** 18+ required
- **Build:** tsup (ESM)
- **Linter:** Biome (not ESLint, not Ruff)
- **Test:** vitest (not jest)
- **Framework:** Commander + Chalk + ora + cli-table3
**The [PR Gate](.github/workflows/pr-gate.yml) judges the change.** It closes any pull request that does not link an issue carrying the `accepted` label. Closed is a queue decision, not a verdict: when a maintainer applies the label the pull request reopens by itself. Drafts, documentation-only changes, and branches pushed to this repository rather than a fork are all exempt.
### Vercel AI SDK Provider (`integrations/vercel-ai-sdk/`)
**The [vouch check](.github/workflows/vouch-check-pr.yml) judges the account.** It reads [`.github/VOUCHED.td`](.github/VOUCHED.td), which has three possible answers about any given person:
```bash
cd integrations/vercel-ai-sdk
pnpm install
pnpm run build # tsup
pnpm run lint # eslint
pnpm run type-check # tsc --noEmit
pnpm run prettier-check # prettier --check
pnpm run test # jest
pnpm run test:edge # vitest (edge runtime)
pnpm run test:node # vitest (node runtime)
```
| The list says | Meaning | Effect on the pull request |
|---|---|---|
| `-handle` | a maintainer ran `!denounce` after the code of conduct process | closed, even with an accepted issue |
| nothing at all | everybody who has not contributed here before | **none.** One comment saying nothing is blocked. |
| `handle` | a maintainer ran `!vouch` | none, and the comment stops appearing |
- **Build:** tsup (CJS + ESM)
- **Lint:** ESLint + Prettier
- **Test:** jest + vitest (edge/node configs)
Being vouched grants nothing. It is a "we have seen this person before" flag that mutes the newcomer comment, not permission to skip the accepted-issue rule. Being absent from the list costs nothing.
### OpenClaw Plugin (`integrations/openclaw/`)
If you are an agent opening a pull request on someone's behalf, the practical consequence is one rule: **get the linked issue labelled `accepted` before you open the pull request, or expect the pull request to be closed and to reopen later.** Do not work around either gate, do not reopen a gated pull request by hand, and do not re-file the same change under a new pull request when one is closed.
```bash
cd integrations/openclaw
pnpm install
pnpm run build # tsup
pnpm run test # vitest run
```
### The CLA is not optional
- **Build:** tsup (ESM)
- **Test:** vitest (with Codecov in CI)
- **Plugin manifest:** `openclaw.plugin.json`
**A pull request from a contributor who has not signed the Contributor License Agreement is not accepted, not reviewed, and not merged.** This is not a formality applied at merge time. An unsigned pull request does not enter the review queue at all: maintainers do not read the diff, do not leave feedback, and do not discuss the approach. It sits until the CLA is signed, and it is closed if it goes stale.
### Server (`server/`)
The `CLAassistant` bot comments on your first pull request with a link. Signing takes under a minute, is done once per GitHub account, and covers every contribution you make afterwards. Until it is signed the `license/cla` check stays red.
```bash
# Docker production build
cd server
make build # docker build -t mem0-api-server .
make run_local # docker run -p 8000:8000 with .env
If you are an agent opening a pull request on someone's behalf, tell them they must sign it themselves. Nobody else can sign for them, and the pull request goes nowhere until they do.
# Docker Compose development (FastAPI + PostgreSQL/pgvector + Neo4j)
cd server
docker-compose up # starts all 3 services
# mem0 API: localhost:8888
# PostgreSQL: localhost:8432
# Neo4j HTTP: localhost:8474, Bolt: localhost:8687
```
### What gets a pull request closed
- **Framework:** FastAPI with uvicorn (auto-reload in dev)
- **Services:** PostgreSQL with pgvector, Neo4j 5.x with APOC plugin
- **Hot reload:** Dev Dockerfile mounts `server/` and `mem0/` for live changes
Beyond the CLA and the accepted-issue gate, the [Contribution Conduct](CODE_OF_CONDUCT.md#contribution-conduct) section of the code of conduct is the enforceable form of this repo's anti-slop policy:
### Documentation (`docs/`)
- **Disclose AI use.** The PR template asks how the *code* was written; drafting the description with a model is fine. The disclosure is never held against you, it tells a reviewer where to look. Silence followed by a review comment you cannot answer is what costs everyone the afternoon.
- **Do not submit work you have not run.** A bug report means you reproduced it. A PR means you ran the tests.
- **Do not fabricate evidence.** Invented tracebacks, unmeasured benchmarks, tests that assert the implementation back at itself, descriptions that describe a different change than the diff makes.
- **Match your volume to your engagement.** Open changes at the rate you can discuss them.
- **Do not press for merges.** One polite follow-up after a reasonable wait is fine.
- **You must be able to explain every line of your diff** and how it interacts with the rest of the codebase, without asking an AI tool. This is the one rule that does not bend.
```bash
make docs # or: cd docs && mintlify dev
```
### Reference
- **Framework:** Mintlify
- **API spec:** `docs/openapi.json`
- **Structure:** `api-reference/`, `open-source/`, `platform/`, `integrations/`, `cookbooks/`, `core-concepts/`
### Evaluation / Benchmarking
Benchmarking lives in the external [`mem0ai/memory-benchmarks`](https://github.com/mem0ai/memory-benchmarks) repo (LOCOMO + LongMemEval + BEAM). The in-repo `evaluation/` path is a **git submodule** pinned to that repo's `main` — populate it with `git submodule update --init evaluation` (or clone mem0 with `--recurse-submodules`), or clone the benchmarks repo standalone:
```bash
git clone https://github.com/mem0ai/memory-benchmarks.git
cd memory-benchmarks
pip install -r requirements.txt
# Run a benchmark (Mem0 Cloud; use docker compose for OSS)
python -m benchmarks.locomo.run --project-name my-test --backend cloud --mem0-api-key $MEM0_API_KEY
python -m benchmarks.longmemeval.run --project-name my-test --backend cloud --mem0-api-key $MEM0_API_KEY --all-questions
python -m benchmarks.beam.run --project-name my-test --backend cloud --mem0-api-key $MEM0_API_KEY --chat-sizes 100K --conversations 0-9
```
## Core APIs
### Python
| Function / Class | Purpose | Import |
|-----------------|---------|--------|
| `Memory` | Self-hosted memory (sync) | `from mem0 import Memory` |
| `AsyncMemory` | Self-hosted memory (async) | `from mem0 import AsyncMemory` |
| `MemoryClient` | Hosted platform client (sync) | `from mem0 import MemoryClient` |
| `AsyncMemoryClient` | Hosted platform client (async) | `from mem0 import AsyncMemoryClient` |
**Key `Memory` / `MemoryClient` methods:**
| Method | Purpose |
|--------|---------|
| `add(messages, *, user_id, agent_id, run_id, metadata)` | Store a new memory |
| `search(query, *, user_id, agent_id, run_id, limit, filters)` | Search memories |
| `get(memory_id)` | Retrieve a single memory by ID |
| `get_all(*, user_id, agent_id, run_id, limit)` | List all memories |
| `update(memory_id, data)` | Update a memory |
| `delete(memory_id)` | Delete a memory |
| `delete_all(*, user_id, agent_id, run_id)` | Delete all memories |
| `history(memory_id)` | Get change history for a memory |
### TypeScript
| Export | Purpose | Import |
|--------|---------|--------|
| `MemoryClient` | Hosted platform client | `import { MemoryClient } from 'mem0ai'` |
| `Memory` | Self-hosted OSS memory | `import { Memory } from 'mem0ai/oss'` |
## Import Patterns
### Python
| What | Import |
|------|--------|
| Core memory classes | `from mem0 import Memory, AsyncMemory` |
| Platform client | `from mem0 import MemoryClient, AsyncMemoryClient` |
| Configuration | `from mem0.configs.base import MemoryConfig` |
| LLM providers | `from mem0.llms.<provider> import <ProviderLLM>` |
| Embedding providers | `from mem0.embeddings.<provider> import <ProviderEmbedding>` |
| Vector store providers | `from mem0.vector_stores.<provider> import <ProviderVectorStore>` |
### TypeScript
| What | Import |
|------|--------|
| Hosted client | `import { MemoryClient } from 'mem0ai'` |
| OSS memory | `import { Memory } from 'mem0ai/oss'` |
| Specific providers (OSS) | `import { OpenAIEmbedding } from 'mem0ai/oss'` |
## Coding Standards
### File Naming Conventions
- **Python source files:** `snake_case.py` (e.g., `azure_openai.py`, `cohere_reranker.py`)
- **Python test files:** `test_<module>.py` (e.g., `test_memory.py`, `test_main.py`)
- **TypeScript source files:** `snake_case.ts` (e.g., `azure_ai_search.ts`)
- **TypeScript test files:** `<module>.test.ts` (e.g., `memory.test.ts`)
- **Config/manifest files:** `kebab-case` (e.g., `openclaw.plugin.json`, `jest.config.js`)
### Python Conventions
- **Provider pattern:** All providers (LLMs, embeddings, vector stores, graphs, rerankers) inherit from a `base.py` abstract class in their directory. Config classes live in `configs.py`.
- **Pydantic v2** for all data models and configuration.
- **Ruff** is the single linting and formatting tool — no black, no flake8.
- Root SDK: line length **120**
- Python CLI: line length **100** with extended rule set (UP, B, SIM, RUF)
- **isort** with `profile = "black"` for import sorting.
### TypeScript Conventions
- **Build:** tsup across all packages.
- **Package manager:** pnpm everywhere (no npm, no yarn).
- **TypeScript strict mode** across all packages.
- **Linting varies by package:**
| Package | Linter | Formatter | Test Framework |
|---------|--------|-----------|---------------|
| `mem0-ts/` | — | Prettier | jest |
| `cli/node/` | Biome | Biome | vitest |
| `integrations/vercel-ai-sdk/` | ESLint | Prettier | jest + vitest |
| `integrations/openclaw/` | — | — | vitest |
### Type Checking
Always run type checking after modifying TypeScript code:
```bash
cd <package> && pnpm run typecheck # or: tsc --noEmit
```
## Architecture
### Provider Pattern
The SDK uses a consistent plugin architecture across 5 categories. Each category has a `base.py` abstract class and concrete provider implementations:
| Category | Count | Examples |
|----------|-------|---------|
| **LLMs** | 24 | OpenAI, Anthropic, AWS Bedrock, Azure OpenAI, Gemini, Groq, Ollama, Together, DeepSeek, vLLM, LiteLLM, LM Studio, xAI |
| **Vector Stores** | 30 | Qdrant, Pinecone, Chroma, Weaviate, Milvus, MongoDB, Redis, Elasticsearch, pgvector, Supabase, Faiss, S3 Vectors |
| **Embeddings** | 15 | OpenAI, Azure OpenAI, Gemini, HuggingFace, FastEmbed, Together, AWS Bedrock, Ollama, Vertex AI |
| **Graph Stores** | 4 | Neo4j, Memgraph, Kuzu, Apache AGE |
| **Rerankers** | 5 | Cohere, HuggingFace, LLM-based, Sentence Transformer, Zero Entropy |
### Two Usage Modes
Self-hosted `Memory` / `AsyncMemory` classes and hosted-platform `MemoryClient` — both in Python and TypeScript.
### Graph Memory
Optional layer on top of vector memory for relationship-aware retrieval. Configured via the `graph` section of `MemoryConfig`.
### MCP Integration
Model Context Protocol support in multiple places:
- **Remote:** MCP server at `mcp.mem0.ai`
- **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
- `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); `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
To add a new LLM, embedding, vector store, or reranker provider:
1. Create `mem0/<category>/<provider_name>.py`
2. Inherit from the abstract base class in `mem0/<category>/base.py`
3. Add configuration to `mem0/<category>/configs.py` (if the category uses one)
4. Register the provider in `mem0/<category>/__init__.py`
5. Add tests in `tests/<category>/<provider_name>/`
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)
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 |
| Mem0 Plugin | `mem0-plugin-checks.yml` | Push to main (on `integrations/mem0-plugin/`, excluding `.opencode-plugin/`), manual | pytest + hook entry-point exec bits + JSON manifest validation on Python 3.10, 3.11, 3.12 |
| 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 |
| n8n Node | `n8n-nodes-mem0-checks.yml` | Push to main (on `integrations/n8n-nodes-mem0/`), manual | ESLint (n8n-nodes-base) + tsc build (dist artifact check) on Node 20 |
| Zapier App | `zapier-mem0-checks.yml` | Push to main (on `integrations/zapier-mem0/`), manual | build (tsc) + `zapier validate` + offline unit tests on Node 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`) |
| Node CLI | `cli-node-cd.yml` | `cli-node-v*` | npm (`@mem0/cli`) |
| 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`) |
| n8n Node | `n8n-nodes-mem0-cd.yml` | `n8n-nodes-mem0-v*` | npm (`@mem0/n8n-nodes-mem0`) |
- 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>`.
- The **Zapier app** (`integrations/zapier-mem0`) deploys to Zapier's own platform, not npm, so it is **not** in the release router. Deploy it manually: `gh workflow run zapier-mem0-cd.yml --ref main` (requires the `ZAPIER_DEPLOY_KEY` secret).
- 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
| Workflow | File | Purpose |
|----------|------|---------|
| Issue Labeler | `issue-labeler.yml` | Automatic issue labeling |
| PR Labeler | `pr-labeler.yml` | Path-based PR labeling plus propagating labels from linked issues |
| Stale Bot | `stale.yml` | Marks stale issues and PRs |
| llms.txt Check | `docs-llms-txt-check.yml` | Blocks PRs touching `docs/**/*.mdx` when `docs/llms.txt` is out of sync. Fix locally with `python scripts/check-llms-txt-coverage.py --write`. |
## Task Completion Guidelines
These guidelines outline typical artifacts for different task types. Use judgment to adapt based on scope and context.
### Bug Fixes
1. **Unit tests**: Add tests that would fail without the fix (regression tests)
2. **Implementation**: Fix the bug
3. **Manual verification**: Run the relevant test suite to confirm the fix
4. **Lint**: Run the appropriate linter for the package you modified
### New Features
1. **Implementation**: Build the feature following existing patterns
2. **Unit tests**: Comprehensive test coverage for new functionality
3. **Documentation**: Update relevant docs in `docs/` for public APIs
4. **Examples**: Add usage examples if the feature introduces new user-facing behavior
5. **llms.txt**: Any new `.mdx` page under `docs/` must be linked in `docs/llms.txt` with a scope tag (`[Platform]` / `[OSS]` / `[Both]`) and a `Use when ...` description. The `docs-llms-txt-check.yml` workflow runs on every PR that touches docs and **fails the check** if the index is out of sync. To fix: run `python scripts/check-llms-txt-coverage.py --write` locally to scaffold placeholders under `## Unclassified - needs triage`, then replace the `[TODO: ...]` tags, rewrite descriptions as `Use when ...`, move entries into the right section, and delete the triage heading when empty.
### New Provider (LLM / Embedding / Vector Store / Reranker)
1. **Implementation**: Follow the "Adding a New Provider" steps above
2. **Tests**: Add unit tests matching the pattern of existing providers
3. **Configuration**: Add to the appropriate `configs.py` and `__init__.py`
4. **Dependencies**: Add to the correct optional group in `pyproject.toml`
5. **Documentation**: Add an integration guide in `docs/integrations/`
### Refactoring / Internal Changes
- Unit tests for any changed behavior
- No documentation needed for internal-only changes
- Ensure all existing tests still pass
### When to Deviate
These are guidelines, not rigid rules. Adjust based on:
- **Scope**: Trivial fixes (typos, comments) may not need tests
- **Visibility**: Internal changes may not need documentation
- **Context**: Some changes span multiple categories — use judgment
When uncertain about expected artifacts, ask for clarification.
## Contributing Guidelines
### Workflow
1. Fork and clone the repository.
2. Create a feature branch from `main` (e.g., `feature/my-new-feature`).
3. Make your changes — add tests, docs, and examples as appropriate.
4. Run linting and tests for every package you modified (see commands above).
5. Run `pre-commit install` on first setup — hooks run ruff + isort automatically.
6. Commit with a clear message following [Conventional Commits](https://www.conventionalcommits.org/) (e.g., `feat:`, `fix:`, `docs:`, `refactor:`).
7. Push and open a Pull Request against `main`.
### Pull Request Requirements
Every PR must follow the repo's PR template (`.github/PULL_REQUEST_TEMPLATE.md`):
1. **Linked Issue** — Reference the issue with `Closes #<number>`. If no issue exists, create one first or explain why in the description.
2. **Description** — Explain what the PR does and why it's needed.
3. **Type of Change** — Check the appropriate box:
- Bug fix / New feature / Breaking change / Refactor / Documentation update
4. **Breaking Changes** — If applicable, describe what breaks and the migration path.
5. **Test Coverage** — Check what applies:
- Added/updated unit tests
- Added/updated integration tests
- Tested manually (describe how)
- No tests needed (explain why)
6. **Checklist** — All must be checked before merge:
- [ ] Code follows the project's style guidelines
- [ ] Self-review performed
- [ ] Tests added that prove the fix/feature works
- [ ] New and existing tests pass locally
- [ ] Documentation updated if needed
### PR Description Template
```markdown
## Linked Issue
Closes #<!-- issue number -->
## Description
<!-- What does this PR do? Why is it needed? -->
## Type of Change
- [ ] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [ ] Refactor (no functional changes)
- [ ] Documentation update
## Breaking Changes
N/A
## Test Coverage
- [ ] I added/updated unit tests
- [ ] I added/updated integration tests
- [ ] I tested manually (describe below)
- [ ] No tests needed (explain why)
## Checklist
- [ ] My code follows the project's style guidelines
- [ ] I have performed a self-review of my code
- [ ] I have added tests that prove my fix/feature works
- [ ] New and existing tests pass locally
- [ ] I have updated documentation if needed
```
### General Rules
- Follow existing code patterns — don't introduce new frameworks or abstractions without discussion.
- Version bumps go in `pyproject.toml` (Python) or `package.json` (TypeScript).
- For `server/` work, use Docker Compose for local development.
- Do NOT use `pip` or `conda` for dependency management — use `hatch` (see `docs/contributing/development.mdx`).
### Contributing Guides
| Task | Guide |
|------|-------|
| Code contributions | `docs/contributing/development.mdx` |
| Topic | File |
|-------|------|
| Contributor guide | [`CONTRIBUTING.md`](CONTRIBUTING.md) |
| Code of conduct | [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md) |
| Security reports | [`SECURITY.md`](SECURITY.md) |
| Development setup | `docs/contributing/development.mdx` |
| Documentation contributions | `docs/contributing/documentation.mdx` |
| PR template | `.github/PULL_REQUEST_TEMPLATE.md` |
| Bug reports | `.github/ISSUE_TEMPLATE/bug_report.yml` |
| Feature requests | `.github/ISSUE_TEMPLATE/feature_request.yml` |
| Documentation issues | `.github/ISSUE_TEMPLATE/documentation_issue.yml` |
## Do NOT
- Modify CI/CD workflows without explicit approval.
- Add new Python dependencies to the core `dependencies` list in `pyproject.toml` without discussion — use optional dependency groups instead.
- Commit `.env` files, API keys, or credentials.
- Skip pre-commit hooks.
- Use npm or yarn in TypeScript packages — this repo uses pnpm exclusively.
- Use `require()` for imports in TypeScript — use ES module `import` syntax.
- Mix up linter configs: root Python SDK uses line-length 120, Python CLI uses 100, Node CLI uses Biome (not ESLint/Ruff).
- Change public APIs without updating documentation in `docs/`.
| Issue forms | `.github/ISSUE_TEMPLATE/` |
| Contribution gates | [Two gates decide whether your pull request stays open](#two-gates-decide-whether-your-pull-request-stays-open) |
| Trust list (vouch) | [`.github/VOUCHED.td`](.github/VOUCHED.td) |
| CI/CD, gates, rulesets | [`.github/AGENTS.md`](.github/AGENTS.md) |
+181
View File
@@ -0,0 +1,181 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
- Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or advances of
any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email address,
without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Contribution Conduct
Mem0 receives more contributions than any maintainer can read line by line. The
rules below exist so that the time we do have goes to people who are actually
trying to improve the project. They apply to issues, pull requests, discussions,
and reviews.
**Be honest about how the work was produced.** Using an AI tool to find a bug,
write a patch, or draft a description is fine and welcome. Not saying so is not.
Every issue form and the pull request template ask about AI, and the answer is
never held against you. It tells a reviewer where to look. An unanswered review
comment on code the author cannot explain is what costs us the afternoon.
**Do not submit work you have not verified.** A reported bug means you ran it and
saw it. A pull request means you ran the tests. Pasting a model's output, a
scanner result, or a plausible-looking patch and letting maintainers find out
whether it is real moves your work onto someone else's desk. Reports and patches
that turn out to be unverified are closed without a detailed response.
**Do not fabricate evidence.** Invented tracebacks, benchmark numbers you did not
measure, reproductions that were never run, tests that assert the implementation
back at itself, and descriptions that describe a different change than the diff
makes are all treated the same way, regardless of whether a person or a tool
produced them.
**Match your volume to your engagement.** Open changes at the rate you can
discuss them. A queue of open pull requests from one author, none of them
answered when questioned, is treated as automated submission and handled under
enforcement below, whatever the individual diffs look like.
**Do not press for merges.** Bumping a thread, tagging maintainers repeatedly,
asking in Discord or by direct message for a review, and reopening a closed pull
request without addressing why it was closed all take attention away from the
queue rather than moving your change through it. One polite follow-up after a
reasonable wait is fine.
**Do not contribute for a badge.** Changes made to raise a contribution count,
qualify for an event, or pad a profile, whitespace edits, README churn, and
mechanical reformatting bundled with nothing else, are closed on sight.
**Disagreement is fine, and closing is not a verdict.** Our pull request gate
closes changes that do not yet link an accepted issue. That is a queue decision,
not a judgment of you or your code, and reopening takes about a minute. Argue for
your change on its merits; that is a normal and welcome part of contributing.
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, including this
repository, our Discord, and our documentation, and also applies when an
individual is officially representing the community in public spaces. Examples of
representing our community include using an official email address, posting via
an official social media account, or acting as an appointed representative at an
online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the maintainers at **support@mem0.ai**. All complaints will be
reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community, or a first contribution that
breaches the Contribution Conduct rules above.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of
actions, including a repeated pattern of unverified or automated submissions
after a first warning.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent
ban. At this stage the account may be denounced in `.github/VOUCHED.td`, which
means new pull requests are flagged automatically.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the
community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
The Contribution Conduct section is specific to this repository.
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][mozilla].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][faq]. Translations are available at
[https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[mozilla]: https://github.com/mozilla/inclusion
[faq]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations
+80 -2
View File
@@ -7,6 +7,12 @@ new features, documentation, examples, and integrations.
Mem0 is a polyglot monorepo, and this guide covers contributing to both the
**Python SDK** and the **TypeScript SDK** (and the rest of the repository).
By participating you agree to our [Code of Conduct](./CODE_OF_CONDUCT.md). Its
**Contribution Conduct** section is the enforceable form of the rules on this
page: disclose AI use, don't submit work you haven't run, don't fabricate
reproductions or benchmarks, keep your volume matched to your engagement, and
don't press for merges.
## Before You Start
### 1. Open an Issue First
@@ -23,9 +29,68 @@ in code.
- For anything beyond a trivial fix, wait for a maintainer to confirm the approach
before starting significant work.
Every pull request must link to an issue using `Closes #<issue-number>`.
A bug report needs a reproduction we can run, the version you are on, and the
real output or traceback you saw. Reports without those cannot be acted on and
get closed. A feature request needs the problem you hit and the workaround you
are living with, not just the API you would like.
### 2. Sign the Contributor License Agreement (CLA)
Every pull request must link to an issue using `Closes #<issue-number>`, and that
issue must carry the `accepted` label. A maintainer applies `accepted` once we
agree the change is one we want.
Pull requests that don't link an accepted issue are closed automatically by the
[PR Gate](./.github/workflows/pr-gate.yml). **Closed does not mean rejected.** It
means the change isn't in the queue yet. Once a maintainer labels the issue the
pull request reopens itself, and you don't have to do anything. Documentation-only
changes skip the gate entirely.
A second check looks at who opened the pull request rather than what it changes.
If you are not yet in this repo's contributor list
([`.github/VOUCHED.td`](./.github/VOUCHED.td)) you get one comment saying so.
**Nothing is blocked and there is nothing you need to do.** A maintainer can add
you by commenting `!vouch @you` on any issue, which only stops that comment from
appearing again. Being on the list is not permission to skip the accepted-issue
rule, and being absent from it costs you nothing.
The list has a negative side too. A maintainer can `!denounce` an account that
has been through the
[code of conduct](./CODE_OF_CONDUCT.md#contribution-conduct) enforcement process,
and pull requests from that account are closed whether or not they link an
accepted issue. This is rare, it is never where anyone starts, and it is
reversible.
Security fixes are the one exception, and they don't go through public pull
requests at all. Follow the [Security Policy](./SECURITY.md) instead, which uses
a private advisory and a private fork so the vulnerability isn't disclosed before
the fix ships.
### 2. Understand Your Code
**You must be able to explain what your changes do and how they interact with
the rest of the codebase without the help of an AI tool.** This is the one rule
we will not bend on.
Using AI to write code is fine. Most of us do. You can build real understanding
by interrogating an agent about this codebase until you grasp the edge cases and
the blast radius of your change. What is not fine is opening a pull request for
a diff you cannot defend in review.
Disclose it in the pull request template and say what you checked yourself.
We ask about the code, not the write-up: using AI to draft the pull request
description is fine. We ask because it tells reviewers where to look, not
because it counts against you. An honest "an agent wrote this, here is what I
verified" is welcome. Silence, followed by a review comment you cannot answer,
is what wastes everyone's time.
Signs your pull request will be closed:
- Invented APIs, config keys, or providers that don't exist in this repo.
- Tests that assert the implementation back at itself rather than the behaviour.
- A description that describes a different change than the diff makes.
- Sweeping unrelated reformatting bundled with a small fix.
- You cannot answer a direct question about your own diff.
### 3. Sign the Contributor License Agreement (CLA)
**We cannot accept or merge any pull request until you have signed our Contributor
License Agreement (CLA).**
@@ -35,6 +100,19 @@ sign. Signing takes less than a minute and only needs to be done once. Pull
requests from contributors who have not signed the CLA will be blocked from
merging.
## First Contribution Fast Path
Fixing a typo or a small docs issue? You don't need the full workflow below.
1. **Pick something small.** Look for issues labeled `documentation` or `good first issue`, or a typo/broken link you noticed while reading the docs.
2. **Branch from `main`** with a name that says what you're fixing, e.g. `docs/fix-quickstart-typo` or `fix/broken-crewai-link`.
3. **Make the change, then run only what applies:**
- Docs-only change (`docs/**`): preview with `make docs`. If you added or removed an `.mdx` page, run `python scripts/check-llms-txt-coverage.py --write` so `docs/llms.txt` stays in sync.
- Code change: run the linter and tests for the package you touched, see [Development Workflow](#development-workflow) below.
4. **Open a PR** against `main` with `Closes #<issue-number>` and a one-line description of what you fixed.
For anything larger than a docs fix or a small bug, follow the full workflow below.
## Repository Layout
The two most common contribution targets are the SDKs:
+1 -1
View File
@@ -11,7 +11,7 @@ install:
hatch env create
install_all:
pip install ruff==0.16.0 groq together boto3 litellm ollama chromadb weaviate weaviate-client sentence_transformers vertexai \
pip install ruff==0.16.0 groq together boto3 'litellm>=1.83.7,<1.98.0' ollama chromadb weaviate weaviate-client sentence_transformers vertexai \
google-generativeai elasticsearch opensearch-py vecs "pinecone<7.0.0" pinecone-text faiss-cpu langchain-community \
upstash-vector azure-search-documents langchain-memgraph langchain-neo4j langchain-aws rank-bm25 pymochow pymongo psycopg kuzu databricks-sdk valkey
+6
View File
@@ -26,6 +26,12 @@ following as you can:
- Clear, step-by-step reproduction instructions
- The security impact and a proof of concept, if available
- Any suggested fix or mitigation
- Whether an AI tool was involved in finding or writing up the report
Reports generated by an AI tool are welcome, but only once you have run the
reproduction yourself and confirmed the impact is real. A scan result or model
output pasted in without that step is not a vulnerability report, and we close
those without a detailed response so we can spend the time on real ones.
## Response Process
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@mem0/cli",
"version": "0.2.12",
"version": "0.2.13",
"description": "The official CLI for mem0 — the memory layer for AI agents",
"type": "module",
"bin": {
+10 -1
View File
@@ -36,7 +36,16 @@ const COMMAND_GROUPS: { panel: string; commands: string[] }[] = [
},
{
panel: "Management",
commands: ["init", "status", "import", "help", "entity", "event", "config"],
commands: [
"init",
"status",
"version",
"import",
"help",
"entity",
"event",
"config",
],
},
];
+17 -3
View File
@@ -95,6 +95,10 @@ async function getBackendOnly(
return (await getBackendAndConfig(apiKey, baseUrl)).backend;
}
function printVersion(): void {
console.log(` ${colors.brand("◆ Mem0")} CLI v${CLI_VERSION}`);
}
function checkAgentMode(): boolean {
const rootOpts = program.opts();
const isAgent = !!(rootOpts.json || rootOpts.agent);
@@ -154,7 +158,7 @@ program
.enablePositionalOptions()
.option("--version", "Show version and exit.")
.on("option:version", () => {
console.log(` ${colors.brand("◆ Mem0")} CLI v${CLI_VERSION}`);
printVersion();
process.exit(0);
})
.option("--json", "Output as JSON for agent/programmatic use.")
@@ -387,7 +391,10 @@ program
)
.option("--rerank", "Enable reranking (Platform only).", false)
.option("--keyword", "Use keyword search.", false)
.option("--filter <json>", "Advanced filter expression (JSON).")
.option(
"--filter <json>",
'Advanced filter as JSON: {"AND": [...]} or {"OR": [...]}, e.g. {"AND": [{"categories": {"in": ["work"]}}]}.',
)
.option("--fields <list>", "Specific fields to return (comma-separated).")
.option("--show-expired", "Include expired memories.", false)
.option(
@@ -404,7 +411,7 @@ program
.option("--base-url <url>", "Override API base URL.")
.addHelpText(
"after",
'\nExamples:\n $ mem0 search "preferences" --user-id alice\n $ mem0 search "tools" -u alice -o json -k 5\n $ echo "preferences" | mem0 search -u alice',
'\nExamples:\n $ mem0 search "preferences" --user-id alice\n $ mem0 search "tools" -u alice -o json -k 5\n $ echo "preferences" | mem0 search -u alice\n $ mem0 search "invoices" -u alice --filter \'{"AND": [{"categories": {"in": ["work"]}}]}\'',
)
.action(async (query, opts) => {
let resolvedQuery = query;
@@ -823,6 +830,13 @@ program
});
});
program
.command("version")
.description("Show version and exit.")
.action(() => {
printVersion();
});
program
.command("import <filePath>")
.description("Import memories from a JSON file.")
+15 -2
View File
@@ -44,11 +44,17 @@ describe("CLI Integration — help and version", () => {
expect(result.stdout).toContain("search");
});
it("prints the version with --version, and has no version subcommand", () => {
it("prints the version with --version", () => {
const flag = run(["--version"]);
expect(flag.exitCode).toBe(0);
expect(flag.stdout).toContain("Mem0");
expect(run(["version"]).exitCode).not.toBe(0);
});
it("version subcommand output matches --version output byte-for-byte", () => {
const flag = run(["--version"]);
const cmd = run(["version"]);
expect(cmd.exitCode).toBe(0);
expect(cmd.stdout).toBe(flag.stdout);
});
it.each([["help", "--json"], ["--json", "help"], ["--agent", "help"]])(
@@ -129,6 +135,13 @@ describe("CLI Integration — help and version", () => {
expect(result.stdout).toContain("--rerank");
});
it("search help documents the --filter JSON shape with an example", () => {
const result = run(["search", "--help"]);
expect(result.exitCode).toBe(0);
expect(result.stdout).toContain("AND");
expect(result.stdout).toContain("categories");
});
it("list help has --category flag", () => {
const result = run(["list", "--help"]);
expect(result.exitCode).toBe(0);
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "mem0-cli"
version = "0.2.11"
version = "0.2.12"
description = "The official CLI for mem0 — the memory layer for AI agents"
readme = "README.md"
license = "Apache-2.0"
+1 -1
View File
@@ -1,3 +1,3 @@
"""mem0 CLI — the command-line interface for the mem0 memory layer."""
__version__ = "0.2.11"
__version__ = "0.2.12"
+22 -2
View File
@@ -371,7 +371,11 @@ def search(
False, "--keyword", help="Use keyword search.", rich_help_panel="Search"
),
filter_json: str | None = typer.Option(
None, "--filter", help="Advanced filter expression (JSON).", rich_help_panel="Search"
None,
"--filter",
help='Advanced filter as JSON: {"AND": [...]} or {"OR": [...]}, '
'e.g. {"AND": [{"categories": {"in": ["work"]}}]}.',
rich_help_panel="Search",
),
fields: str | None = typer.Option(
None,
@@ -414,6 +418,7 @@ def search(
mem0 search "preferences" --user-id alice
mem0 search "tools" -u alice -o json -k 5
echo "preferences" | mem0 search -u alice
mem0 search "invoices" -u alice --filter '{"AND": [{"categories": {"in": ["work"]}}]}'
"""
from mem0_cli.commands.memory import cmd_search
@@ -1084,6 +1089,18 @@ def status(
)
@app.command(rich_help_panel="Management")
def version() -> None:
"""Show version and exit.
Example:
mem0 version
"""
from mem0_cli.commands.utils import cmd_version
cmd_version()
@app.command("import", rich_help_panel="Management")
def import_cmd(
file_path: str = typer.Argument(..., help="JSON file to import."),
@@ -1165,7 +1182,10 @@ def _build_help_json() -> dict:
"--threshold": "Minimum similarity score (default: 0.3).",
"--rerank": "Enable reranking (Platform only).",
"--keyword": "Use keyword search instead of semantic.",
"--filter": "Advanced filter expression (JSON).",
"--filter": (
'Advanced filter as JSON: {"AND": [...]} or {"OR": [...]}, '
'e.g. {"AND": [{"categories": {"in": ["work"]}}]}.'
),
"--fields": "Specific fields to return (comma-separated).",
"--show-expired": "Include expired memories.",
"--reference-date": "Reference date for relative queries (YYYY-MM-DD or unix timestamp).",
+12 -1
View File
@@ -91,7 +91,12 @@ class TestCLIIntegration:
flag = _run(["--version"])
assert flag.returncode == 0
assert __version__ in flag.stdout
assert _run(["version"]).returncode != 0
def test_version_subcommand_matches_flag_byte_for_byte(self):
flag = _run(["--version"])
cmd = _run(["version"])
assert cmd.returncode == 0
assert cmd.stdout == flag.stdout
@pytest.mark.parametrize(
"args",
@@ -127,6 +132,12 @@ class TestCLIIntegration:
assert result.returncode == 0
assert "top-k" in result.stdout
def test_search_help_documents_filter_json_shape(self):
result = _run(["search", "--help"])
assert result.returncode == 0
assert "AND" in result.stdout
assert "categories" in result.stdout
def test_list_help(self):
result = _run(["list", "--help"])
assert result.returncode == 0
+49
View File
@@ -0,0 +1,49 @@
# Documentation (`docs/`)
Mintlify site published at https://docs.mem0.ai.
## Commands
```bash
make docs # from repo root
cd docs && mintlify dev
```
## Structure
| Path | Contents |
|------|----------|
| `api-reference/` | Platform REST endpoints |
| `open-source/` | Self-hosted SDK guides |
| `platform/` | Hosted platform guides |
| `integrations/` | One page per integration |
| `core-concepts/` | Memory model, graph memory, scoping |
| `cookbooks/` | End-to-end recipes |
| `contributing/` | Contributor guides |
| `docs.json` | Navigation tree |
| `openapi.json` | Platform API spec |
| `llms.txt` | Scope-tagged index for agents |
## Adding a page
Every new `.mdx` page needs three things, or CI fails:
1. The page itself under the right section.
2. A navigation entry in `docs.json`.
3. A line in `llms.txt` with a scope tag (`[Platform]`, `[OSS]`, or `[Both]`) and a description that starts with `Use when ...`.
`docs-llms-txt-check.yml` runs on every PR touching `docs/**/*.mdx` and **blocks the merge** when `llms.txt` is out of sync. To fix:
```bash
python scripts/check-llms-txt-coverage.py --write
```
That scaffolds placeholders under `## Unclassified - needs triage`. Then replace each `[TODO: ...]` tag, rewrite the descriptions as `Use when ...`, move entries into the correct section, and delete the triage heading once it is empty.
## Conventions
- Frontmatter needs `title`, `description`, and usually `icon`.
- Mintlify components (`<Note>`, `<Card>`, `<Tabs>`, `<CodeGroup>`) are available; prefer them over raw HTML.
- Code samples must be runnable. If a sample calls a public SDK method, it has to match the real signature.
- Documentation-only PRs are exempt from the `accepted`-issue requirement in the PR gate, but not from the CLA.
- Any change to a public SDK signature has to update the matching page here in the same PR.
+1
View File
@@ -0,0 +1 @@
AGENTS.md
@@ -0,0 +1,5 @@
---
title: "Preview Dream Scope"
description: "A no-write preview of the scope Dream synthesis would analyze for a project."
openapi: "post /api/v1/orgs/organizations/{org_id}/projects/{project_id}/dream/preview/"
---
@@ -0,0 +1,5 @@
---
title: "Get Dream Activity"
description: "Supersede/merge activity feed for a project, newest first (keyset-paginated)."
openapi: "get /api/v1/orgs/organizations/{org_id}/projects/{project_id}/dream/activity/"
---
@@ -0,0 +1,5 @@
---
title: "Get Dream Configuration"
description: "Retrieve a project's Dream (memory synthesis) configuration and plan entitlements."
openapi: "get /api/v1/orgs/organizations/{org_id}/projects/{project_id}/dream/config/"
---
@@ -0,0 +1,5 @@
---
title: "Get a Synthesized Memory's Sources"
description: "The source memories a synthesized (pattern) memory was distilled from."
openapi: "get /api/v1/orgs/organizations/{org_id}/projects/{project_id}/dream/memory/{memory_id}/sources/"
---
@@ -0,0 +1,5 @@
---
title: "Get Memories in a Dream Run"
description: "Keyset page of the synthesized memories within a single synthesis run."
openapi: "get /api/v1/orgs/organizations/{org_id}/projects/{project_id}/dream/runs/{run_id}/memories/"
---
@@ -0,0 +1,5 @@
---
title: "Get Dream Synthesis Runs"
description: "Synthesis activity grouped per run, newest first (keyset-paginated)."
openapi: "get /api/v1/orgs/organizations/{org_id}/projects/{project_id}/dream/runs/"
---
@@ -0,0 +1,5 @@
---
title: "Get Dream Stats"
description: "Lifecycle and synthesis counts for a project, plus reflection freshness."
openapi: "get /api/v1/orgs/organizations/{org_id}/projects/{project_id}/dream/stats/"
---
@@ -0,0 +1,5 @@
---
title: "Update Dream Configuration"
description: "Enable or disable Synthesis (reflection) for a project, or change the reflection mode."
openapi: "patch /api/v1/orgs/organizations/{org_id}/projects/{project_id}/dream/config/"
---
+49
View File
@@ -4,6 +4,55 @@ description: "Major product launches, headline features, and milestones for Mem0
mode: "wide"
---
<Update label="2026-08-24" description="DeepSeek Harness plugin">
**DeepSeek Harness: Mem0 as a Native Cordis Plugin**
The DeepSeek Harness agent forgets everything between sessions. [`@mem0/deepseek-plugin`](https://www.npmjs.com/package/@mem0/deepseek-plugin) gives it two Mem0-backed tools, so recall and writes persist across runs against the same memory bank you already use from Claude Code, Codex, and every other connected agent.
- **Two agent-callable tools:** `search_memory` recalls facts relevant to a query, `add_memory` stores a fact for future sessions. Both accept per-call `userId` / `agentId` / `runId` scope overrides.
- **Native Cordis lifecycle:** The plugin declares `inject = ['tools']` so it waits for the harness tool registry, then registers through `ctx.tools.register()`. Unmounting the plugin removes the tools automatically.
- **Managed backend, not a memory file:** Server-side extraction, semantic dedup, and conflict resolution, rather than a Markdown file the agent has to maintain itself.
- **Config:** `userId` is required, `apiKey` defaults to `$MEM0_API_KEY`, and `host` optionally targets a dedicated Mem0 Platform base URL.
See [DeepSeek Harness](/integrations/deepseek-plugin) for setup and [SDK & Tools](/changelog/sdk) for PR links.
<Note>
Developer preview. Auto-capture and auto-recall, where memory reaches the context with no explicit tool call, are planned but not yet built.
</Note>
</Update>
<Update label="2026-08-24" description="Strands Agents integration">
**Strands Agents: Mem0 as a Native MemoryStore**
[`mem0-strands`](https://pypi.org/project/mem0-strands/) plugs Mem0 into AWS's [Strands Agents](https://strandsagents.com/) SDK as a native `MemoryStore`, so recall and writes happen inside the agent loop rather than as tool calls the model has to remember to make.
- **Automatic recall:** The `MemoryManager` drives the store on every turn, searching Mem0 and injecting the results into the prompt with no tool call required.
- **Server-side extraction:** Because the store implements `add_messages`, enabling extraction routes raw conversation turns straight to Mem0's extraction pipeline, skipping the extra client-side model call needed to distill facts first.
- **Hosted or self-hosted:** An API key targets the hosted Mem0 Platform; a config dict targets self-hosted Mem0 OSS.
- **Entity scoping:** Accepts `user_id`, `agent_id`, `run_id`, and `app_id`, with at least one required and invalid combinations rejected at construction rather than on the first write.
See [Strands Agents](/integrations/strands) for setup and [SDK & Tools](/changelog/sdk) for PR links.
</Update>
<Update label="2026-08-13" description="Kimi Code plugin">
**Kimi Code: Mem0 Joins the Editor Plugin Family**
The shared Mem0 editor plugin now covers Kimi Code alongside Claude Code, Cursor, Codex, and Antigravity, running on the same scripts, skills, and memory bank, so context written in one editor is available in the others.
- **Hosted MCP server:** Registers `https://mcp.mem0.ai/mcp/`, authenticated with `MEM0_API_KEY` as a bearer token.
- **Automatic capture and recall:** SessionStart loads context through the `context-loader` skill, and hooks on prompt submit, file reads, Bash output, stop, and pre-compact capture and inject memory without an explicit tool call.
- **Guardrails:** Direct `Write` / `Edit` / `MultiEdit` to memory files is blocked, and metadata defaults are enforced on every Mem0 MCP tool call.
- **Kimi hook adapter:** A shim normalizes Kimi Code's hook contract to the shape the shared scripts already expect, resolving the real project directory and translating the differing prompt, tool-output, and MCP tool-name fields.
See [SDK & Tools](/changelog/sdk) for version details and PR links.
</Update>
<Update label="2026-07-30" description="n8n and Zapier integrations">
**Workflow Automation: Mem0 Memory in n8n and Zapier**
+161
View File
@@ -7,6 +7,19 @@ mode: "wide"
<Tabs>
<Tab title="Python">
<Update label="2026-08-24" description="v2.0.19">
**Bug Fixes:**
- **Embeddings:** `HuggingFaceEmbedding` now falls back to the `HUGGINGFACE_API_KEY` env var, then a placeholder key, when `huggingface_base_url` is set and no `api_key` is configured. The OpenAI-compatible client used to talk to TEI endpoints raises at construction when no key resolves at all, so a TEI deployment that doesn't require a real key previously failed to initialize ([#6947](https://github.com/mem0ai/mem0/pull/6947))
- **Core:** `remove_code_blocks()` now accepts list-shaped content (a sequence of `{"text": ...}` blocks, as some agent frameworks pass) by joining each block's text before stripping code fences, instead of raising `AttributeError` from calling `.strip()` on a list ([#6947](https://github.com/mem0ai/mem0/pull/6947))
- **Core:** `create_procedural_memory()` (`Memory` and `AsyncMemory`) now raises a clear `ValueError` when the LLM returns no content for the summary, instead of continuing with empty content that surfaced as a confusing error further down the call ([#6947](https://github.com/mem0ai/mem0/pull/6947))
- **Proxy:** `mem0.proxy` no longer auto-installs `litellm` via a `pip install` subprocess when the import fails; it now raises `ImportError` with instructions to install it yourself. The auto-install could hang or fail silently in restricted environments and ran an unreviewed install on the caller's behalf ([#6947](https://github.com/mem0ai/mem0/pull/6947))
- **Client:** `get_all()` (sync and async) now sends `page` and `page_size` as independent query params instead of requiring both to be set before either was sent. Passing only `page_size` without `page` previously had it silently dropped, so results came back at the server's default page size ([#6900](https://github.com/mem0ai/mem0/pull/6900))
- **LLMs:** Add `provider_override` to `AWSBedrockConfig`, an explicit provider name (for example `"anthropic"`) for when `model` is an application inference profile ARN whose opaque ID has no provider substring for `extract_provider()` to detect. Without it, those ARNs raised `ValueError: Unable to determine provider` ([#6899](https://github.com/mem0ai/mem0/pull/6899))
- **LLMs:** `VllmConfig` now falls back to the `VLLM_BASE_URL` env var when `vllm_base_url` isn't passed explicitly. The default was filled in before the env var was ever checked, so `VLLM_BASE_URL` was silently ignored ([#6897](https://github.com/mem0ai/mem0/pull/6897))
</Update>
<Update label="2026-08-11" description="v2.0.18">
**Bug Fixes:**
@@ -1206,6 +1219,19 @@ See the [OSS v2 to v3 migration guide](https://docs.mem0.ai/migration/oss-v2-to-
<Tab title="TypeScript">
<Update label="2026-08-24" description="v3.1.7">
**Bug Fixes:**
- **Vector Stores:** Redis and Valkey `search()` / `get()` / `list()` now preserve `agent_id`, `run_id`, and `user_id` as snake_case in the returned payload. The shared payload formatter camelCased every key including those three identity fields, so entity ids came back as `agentId` / `runId` / `userId`, inconsistent with every other vector store ([#6902](https://github.com/mem0ai/mem0/pull/6902))
- **Memory (OSS):** Embedding-cache lookups now use `Object.prototype.hasOwnProperty.call()` instead of the `in` operator or a falsy `||` check. Memory text matching an inherited `Object.prototype` property name (`constructor`, `toString`, and similar) previously short-circuited the lookup and resolved to that inherited value instead of computing a real embedding, silently corrupting the stored vector ([#6903](https://github.com/mem0ai/mem0/pull/6903))
- **Client:** `getAll()` now sends `page` and `pageSize` as independent query params instead of requiring both to be set before either was sent. Passing only `pageSize` without `page` previously had it silently dropped, so results came back at the server's default page size ([#6900](https://github.com/mem0ai/mem0/pull/6900))
- **LLMs:** Add `providerOverride` to the Bedrock `LLMConfig`, an explicit provider name for when `model` is an application inference profile ARN whose opaque ID has no provider substring for `extractProvider()` to detect. Without it, those ARNs threw before the provider-specific settings could be initialized ([#6899](https://github.com/mem0ai/mem0/pull/6899))
**Security:**
- **Dependencies:** Resolved 17 additional high and critical severity dependency vulnerabilities across 5 pnpm workspaces (`mem0-ts`, `vercel-ai-sdk`, `n8n-nodes-mem0`, `zapier-mem0`, `server/dashboard`) via `pnpm.overrides` and a `tar` patch ([#7032](https://github.com/mem0ai/mem0/pull/7032))
</Update>
<Update label="2026-08-11" description="v3.1.6">
**New Features:**
@@ -1823,6 +1849,17 @@ See the [TypeScript SDK migration guide](https://docs.mem0.ai/migration/ts-v2-to
<Tab title="CLI">
<Update label="2026-08-24" description="Python v0.2.12 / Node v0.2.13">
**New Features:**
- **`version`:** New `mem0 version` subcommand, alongside the existing `--version` flag, so scripts and agent harnesses can read the CLI version as a regular subcommand instead of a root-level flag (Python and Node [#6907](https://github.com/mem0ai/mem0/pull/6907))
- **`add`:** New `--agent-custom-instructions` flag, threaded through to `agent_custom_instructions` on the `/v3/memories/add/` payload: a second extraction instruction set that applies only to agent-scoped memories, matching the SDKs' `agentCustomInstructions` / `agent_custom_instructions` support (Python and Node [#6910](https://github.com/mem0ai/mem0/pull/6910))
**Documentation:**
- **`search --filter`:** The `--filter` help text and `docs/platform/cli.mdx` now spell out the JSON shape (`{"AND": [...]}` / `{"OR": [...]}`) with a concrete example instead of just calling it "an advanced filter expression," and a matching example command was added to both the CLI help text and the docs page (Python and Node [#6907](https://github.com/mem0ai/mem0/pull/6907))
</Update>
<Update label="2026-08-04" description="Python v0.2.11 / Node v0.2.12">
**New Features:**
@@ -2008,6 +2045,22 @@ A full-featured command-line interface for Mem0, available in both Python and No
<Tabs>
<Tab title="Mem0 Plugin">
<Update label="2026-08-24" description="mem0-plugin v0.2.15">
**Fixes:**
- **Search:** A failed search request now prints `[mem0] search request failed: <error>` to stderr before returning no results. `search_memories()` swallowed every exception and returned `[]`, so an expired API key, a network failure, or a 500 from the backend was indistinguishable from a genuine "nothing stored yet" and the agent carried on with no context and no warning. Shared by Claude Code, Cursor, Codex, Antigravity, and Kimi ([#6898](https://github.com/mem0ai/mem0/pull/6898))
- **Cursor:** `mcpServers` in `.cursor-plugin/plugin.json` now points at `./.cursor-mcp.json` instead of `.cursor-mcp.json`. The un-prefixed path resolved inconsistently depending on Cursor's working directory when it loaded the plugin ([#6948](https://github.com/mem0ai/mem0/pull/6948))
- **Codex:** `install_codex_hooks.py` now prints all six registered events (`PreToolUse, SessionStart, UserPromptSubmit, PostToolUse, Stop, PreCompact`) after installing, instead of a stale four-event list left over from an earlier version of the installer. The README's hook table is corrected to match, documenting the three `PreToolUse` handlers and two `PostToolUse` handlers that were previously undocumented ([#6948](https://github.com/mem0ai/mem0/pull/6948))
**Documentation:**
- New [Claude.ai](/integrations/claude-ai) integration page ([#6948](https://github.com/mem0ai/mem0/pull/6948))
<Note>
The Claude Code, Cursor, and Codex per-editor manifests (`.claude-plugin/plugin.json`, `.cursor-plugin/plugin.json`, `.codex-plugin/plugin.json`) had drifted to `0.2.13` while the `.claude-plugin/marketplace.json` and `.cursor-plugin/marketplace.json` listings had already moved to `0.2.14`, so installs were pinned one release behind what the marketplace advertised. This release realigns every manifest and marketplace listing to `0.2.15`.
</Note>
</Update>
<Update label="2026-08-04" description="mem0-plugin v0.2.14">
**Fixes:**
@@ -2360,6 +2413,13 @@ Initial release of the Mem0 plugin for Claude Code and Cursor, followed by Codex
<Tab title="Antigravity">
<Update label="2026-08-24" description="Antigravity plugin v0.1.7">
**Fixes:**
- **Hooks:** The `mem0-ensure-deps` and `mem0-session-start` hook commands in `hooks.json` no longer redirect stderr to `/dev/null`. Both commands still end in `|| true` so a failure can't block startup, but a broken dependency install or session bootstrap now shows up in the Antigravity hook log instead of failing invisibly ([#6948](https://github.com/mem0ai/mem0/pull/6948))
</Update>
<Update label="2026-08-04" description="Antigravity plugin v0.1.6">
**Fixes:**
@@ -2427,8 +2487,31 @@ Existing memories written by the previous versions are not rewritten. If your me
</Tab>
<Tab title="Kimi">
<Update label="2026-08-24" description="kimi-plugin v0.1.0">
**Initial release** of the Mem0 plugin for Kimi Code, sharing its scripts, skills, and marketplace listing with the Claude Code / Cursor / Codex / Antigravity plugin family ([#6919](https://github.com/mem0ai/mem0/pull/6919))
**New Features:**
- **MCP server:** Registers the hosted Mem0 MCP server at `https://mcp.mem0.ai/mcp/`, authenticated via the `MEM0_API_KEY` env var as a bearer token.
- **Lifecycle hooks:** Wires SessionStart (loads context through the `context-loader` skill), UserPromptSubmit, three PreToolUse hooks (blocks direct `Write`/`Edit`/`MultiEdit` to memory files, enforces metadata defaults on Mem0 MCP tool calls, and injects context on file reads), two PostToolUse hooks (post-tool tracking and Bash-output scanning), Stop, and PreCompact.
- **Hook adapter:** `kimi_hook_shim.sh` normalizes Kimi Code's hook contract to what the shared hook scripts expect: it resolves the real project directory from the payload's `cwd` (Kimi forces the plugin root as the working directory), converts the array-shaped `prompt` field and the `tool_output` / `tool_input.path` field names to the Claude-style shapes the scripts already handle, and translates the plugin-scoped MCP tool name prefix.
- **Shared policy skill:** Bundles the `/mem0:policy` skill for managing the `## Instructions` and `## Agent Instructions` sections of `mem0.md`.
</Update>
</Tab>
<Tab title="OpenClaw">
<Update label="2026-08-24" description="openclaw-mem0 v1.0.16">
**Security:**
- **Dependencies:** Tightened the `undici` pnpm override from `<6.27.0 → >=6.27.0 <8.0.0` to `<7.29.0 → >=7.29.0 <8.0.0`, closing a newer CVE range the previous floor didn't cover, as part of a wider dependency patch sweep across the pnpm workspaces ([#6847](https://github.com/mem0ai/mem0/pull/6847))
</Update>
<Update label="2026-08-01" description="openclaw-mem0 v1.0.15">
**Improvements:**
@@ -2692,6 +2775,13 @@ Existing memories written by the previous versions are not rewritten. If your me
<Tab title="Pi Agent">
<Update label="2026-08-24" description="Pi Agent plugin v0.1.5">
**Security:**
- **Dependencies:** Tightened the `undici` pnpm override from `<6.27.0 → >=6.27.0 <8.0.0` / `>=8.0.0 <8.5.0 → >=8.5.0` to `<7.29.0 → >=7.29.0 <8.0.0` / `>=8.0.0 <8.9.0 → >=8.9.0 <9.0.0`, closing a newer CVE range the previous floors didn't cover, as part of a wider dependency patch sweep across the pnpm workspaces ([#6847](https://github.com/mem0ai/mem0/pull/6847))
</Update>
<Update label="2026-08-01" description="Pi Agent plugin v0.1.4">
**Security:**
@@ -2749,8 +2839,57 @@ Existing memories written by the previous versions are not rewritten. If your me
</Tab>
<Tab title="Strands">
<Update label="2026-08-24" description="mem0-strands v0.1.0">
**Initial release** of [`mem0-strands`](https://pypi.org/project/mem0-strands/), a native `MemoryStore` that plugs Mem0 into the [Strands Agents](https://strandsagents.com/) `MemoryManager` ([#7021](https://github.com/mem0ai/mem0/pull/7021))
**New Features:**
- **Automatic recall and injection:** `Mem0MemoryStore.search()` runs every turn through the `MemoryManager`, so relevant memories are searched and prepended to the prompt with no explicit tool call required.
- **Server-side extraction:** `add_messages()` renders raw conversation turns to text and hands them to Mem0's own extraction pipeline (`infer=True`), so enabling extraction skips an extra client-side model call to distill facts first.
- **Verbatim writes:** `add()` stores a single fact exactly as given (`infer=False`), the sink used by the `add_memory` tool or a client-side extractor.
- **Entity scoping:** Accepts `user_id`, `agent_id`, `run_id`, and `app_id`; at least one is required, and mixing the platform-only `app_id` with a self-hosted `config` raises at construction instead of failing on the first write.
- **Hosted or self-hosted:** Defaults to the hosted Mem0 Platform via `api_key` (or `$MEM0_API_KEY`), or pass a `config` dict for a self-hosted Mem0 OSS backend.
- **Non-blocking construction:** The underlying Mem0 client is built lazily on first use inside `asyncio.to_thread`, so API-key validation and OSS embedder/vector-store setup never block the event loop.
<Note>
`Mem0MemoryStore` is the automatic-recall store for the `MemoryManager`. For a model-called tool instead, use the [`mem0_memory`](https://github.com/strands-agents/tools) tool from `strands-agents-tools`; both share the same Mem0 backend and namespace. See [Strands Agents](/integrations/strands) for setup.
</Note>
</Update>
</Tab>
<Tab title="DeepSeek Harness">
<Update label="2026-08-24" description="deepseek-plugin v0.1.0">
**Initial release** of [`deepseek-plugin`](https://www.npmjs.com/package/@mem0/deepseek-plugin), a native DeepSeek Harness (Cordis) plugin that registers Mem0 as two agent-callable tools ([#7027](https://github.com/mem0ai/mem0/pull/7027))
**New Features:**
- **`search_memory`:** Recalls facts relevant to a query, with an optional `limit` (default 10) and per-call `userId` / `agentId` / `runId` scope overrides.
- **`add_memory`:** Stores a fact for future sessions, tagged `source: "DEEPSEEK_HARNESS"` for backend attribution; extraction runs asynchronously server-side, so a stored fact may take a moment to become searchable.
- **Cordis lifecycle:** `apply(ctx, config)` declares `inject = ['tools']`, so the plugin waits for the harness tool registry to exist, and both tools are registered via `ctx.tools.register()` so they auto-unregister when the plugin unmounts.
- **Config:** `userId` is required; `apiKey` defaults to `$MEM0_API_KEY`; `host` optionally points at a dedicated Mem0 Platform base URL (not a switch to self-hosted Mem0 OSS).
<Note>
Developer preview: auto-capture and auto-recall (memory injected into context automatically, without an explicit tool call) are planned but not yet built. The backend's `KNOWN_EVENT_SOURCES` allowlist also needs `"DEEPSEEK_HARNESS"` added before usage surfaces by name in telemetry rather than bucketing into "OTHERS". See [DeepSeek Harness](/integrations/deepseek-plugin) for setup.
</Note>
</Update>
</Tab>
<Tab title="Vercel AI SDK">
<Update label="2026-08-24" description="Vercel AI SDK v3.0.2">
**Security:**
- **Dependencies:** Tightened the `js-yaml` pnpm overrides from `<3.15.0 → >=3.15.0 <4.0.0` / `>=4.0.0 <4.3.0 → >=4.3.0 <5.0.0` to `<3.15.1 → >=3.15.1 <4.0.0` / `>=4.0.0 <4.3.1 → >=4.3.1 <5.0.0`, closing a newer CVE range the previous floors didn't cover, as part of a wider dependency patch sweep across the pnpm workspaces ([#7032](https://github.com/mem0ai/mem0/pull/7032))
</Update>
<Update label="2026-08-01" description="Vercel AI SDK v3.0.1">
**Security:**
@@ -2843,6 +2982,13 @@ Existing memories written by the previous versions are not rewritten. If your me
<Tab title="n8n">
<Update label="2026-08-24" description="n8n-nodes-mem0 v0.1.4">
**Security:**
- **Dependencies:** Add `js-yaml` pnpm overrides (`<3.15.1 → >=3.15.1 <4.0.0`, `>=4.0.0 <4.3.1 → >=4.3.1 <5.0.0`) to close a HIGH/CRITICAL severity advisory, as part of a wider dependency patch sweep across the pnpm workspaces ([#7032](https://github.com/mem0ai/mem0/pull/7032))
</Update>
<Update label="2026-08-05" description="n8n-nodes-mem0 v0.1.3">
**Changes:**
@@ -2887,6 +3033,21 @@ Existing memories written by the previous versions are not rewritten. If your me
<Tab title="Zapier">
<Update label="2026-08-24" description="Zapier app v0.1.2">
**Changes:**
- **Connection label:** The saved connection now shows the account's email (`{{user_email}}`, read from the `/v1/ping/` test response) in the Zap editor instead of a static "Mem0" label, so a user with more than one Mem0 connection can tell them apart ([#6985](https://github.com/mem0ai/mem0/pull/6985))
- **Action and search copy:** Reworded labels and descriptions to address Zapier's publishing review: **Get Memories** is now **Find Memories by User**, **Search Memories** is now **Find Memories**, and every description now reads as a third-person statement of what the step does ([#6985](https://github.com/mem0ai/mem0/pull/6985))
- **Attribution:** Add Memory now tags writes with `source: "ZAPIER"` in the request body, so usage is attributed to this integration server-side ([#6985](https://github.com/mem0ai/mem0/pull/6985))
**Removed:**
- **Client-side telemetry:** Deleted the embedded PostHog telemetry client (`telemetry.ts`) and its call sites in Add Memory, Get Memories, and Search Memories. Usage attribution now happens server-side via the `source: "ZAPIER"` tag above instead of a separate fire-and-forget analytics call from inside the published app ([#6985](https://github.com/mem0ai/mem0/pull/6985))
**Security:**
- **Dependencies:** Tightened the `undici` pnpm override to `<7.29.0 → >=7.29.0 <8.0.0` / `>=8.0.0 <8.9.0 → >=8.9.0 <9.0.0` and added a `brace-expansion` override ([#6847](https://github.com/mem0ai/mem0/pull/6847)), then added a `js-yaml` override (`<3.15.1 → >=3.15.1 <4.0.0`, `>=4.0.0 <4.3.1 → >=4.3.1 <5.0.0`) closing a further HIGH/CRITICAL severity advisory ([#7032](https://github.com/mem0ai/mem0/pull/7032))
</Update>
<Update label="2026-08-04" description="Zapier app v0.1.1">
**Bug Fixes:**
@@ -99,6 +99,7 @@ Here are the parameters available for configuring the Hugging Face embedder:
| `embedding_dims` | Dimensions of the embedding model | `selected_model_dimensions` |
| `model_kwargs` | Additional arguments for the model | `None` |
| `huggingface_base_url` | URL to connect to Text Embeddings Inference (TEI) API | `None` |
| `api_key` | API key for the endpoint; falls back to the `HUGGINGFACE_API_KEY` env var. Only used on the `huggingface_base_url` path | `"hf"` |
</Tab>
<Tab title="TypeScript">
| Parameter | Description | Default Value |
@@ -78,6 +78,38 @@ await memory.add(messages, { userId: 'alice', metadata: { category: 'movies' } }
The TypeScript provider calls the Bedrock [Converse API](https://docs.aws.amazon.com/bedrock/latest/userguide/conversation-inference.html), a single uniform interface across the current Bedrock model families. Streaming and `InvokeModel`-only models are not supported yet.
</Note>
### Application inference profiles
Bedrock resolves the model family from the model identifier. An application inference profile ARN ends in an opaque ID, so there is nothing to resolve from. Set `provider_override` (Python) / `providerOverride` (TypeScript) when your model is one:
<CodeGroup>
```python Python
config = {
"llm": {
"provider": "aws_bedrock",
"config": {
"model": "arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/abc123xyz",
"provider_override": "anthropic",
}
}
}
```
```typescript TypeScript
const config = {
llm: {
provider: 'aws_bedrock',
config: {
model: 'arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/abc123xyz',
providerOverride: 'anthropic',
},
},
};
```
</CodeGroup>
Without it, initialization raises `Unknown provider in model` (Python: `ValueError`; TypeScript: `Error`). Plain model IDs and cross-region inference profiles such as `us.anthropic.claude-sonnet-4-20250514-v1:0` still resolve automatically and need no override.
### Config
All available parameters for the `aws_bedrock` config are present in [Master List of All Params in Config](../config).
+51 -2
View File
@@ -14,7 +14,8 @@ Follow the steps below for a smooth contribution process.
<Note>
For the complete contributor checklist, see
[CONTRIBUTING.md](https://github.com/mem0ai/mem0/blob/main/CONTRIBUTING.md) in
the repository root.
the repository root. By participating you agree to our
[Code of Conduct](https://github.com/mem0ai/mem0/blob/main/CODE_OF_CONDUCT.md).
</Note>
## Before You Start
@@ -30,7 +31,28 @@ change, avoid duplicate work, and agree on the approach before you write code.
or [feature request](https://github.com/mem0ai/mem0/issues/new?template=feature_request.yml).
- For anything beyond a trivial fix, wait for a maintainer to confirm the approach.
Every pull request must link to an issue using `Closes #<issue-number>`.
Every pull request must link to an issue using `Closes #<issue-number>`, and that
issue must carry the `accepted` label. A maintainer applies `accepted` once we
agree the change is worth making. Pull requests that do not link an accepted
issue are closed automatically, with instructions to reopen once the label is
applied. Documentation-only changes are exempt.
<Note>
Closed does not mean rejected. Getting the label and reopening takes about a
minute, and the check reruns on reopen.
</Note>
### Show Your Work
Both issue forms ask how you verified the problem: what you ran, the real output
you saw, and why it is a bug rather than expected behavior. Reports without that
are hard to act on and usually sit unanswered.
They also ask whether AI was involved. That question is about how the problem was
found and confirmed, not about how the text was written: drafting the write-up
with a model is fine. The same applies to pull requests, where the AI disclosure
covers the code in the diff. We ask because it tells reviewers where to look, not
because it counts against you.
### 2. Sign the Contributor License Agreement (CLA)
@@ -57,6 +79,33 @@ For detailed guidance on pull requests, refer to [GitHub's documentation](https:
---
## Installing from Source
If you just want to run the latest, unreleased SDK code instead of the published `mem0ai` package, for example to try out a fix before it ships, or to depend on a fork, install directly from a local clone rather than setting up the full contributor environment below.
### Python SDK
```bash
git clone https://github.com/mem0ai/mem0.git
cd mem0
pip install -e .
```
This installs `mem0ai` in editable mode, so edits under `mem0/` take effect immediately without reinstalling. Add an extra if you need one, e.g. `pip install -e ".[vector-stores]"` (see `pyproject.toml` for the full list). If you are contributing to the SDK itself and need every optional dependency for the test suite, use `hatch` instead, see [Dependency Management](#dependency-management).
### TypeScript SDK
```bash
git clone https://github.com/mem0ai/mem0.git
cd mem0/mem0-ts
pnpm install
pnpm run build
```
This builds `mem0-ts/dist` (CJS + ESM). To use it from another local project, add it as a `file:` dependency pointing at `mem0-ts`, or run `pnpm link --global` inside `mem0-ts` and `pnpm link --global mem0ai` in the consuming project.
---
## Python SDK (`mem0/`)
### Dependency Management
+3
View File
@@ -3,6 +3,9 @@ title: Personalized AI Tutor
description: "Keep student progress and preferences persistent across tutoring sessions."
---
<Info icon="server">
**Works with:** Mem0 OSS (`Memory`)
</Info>
You can create a personalized AI Tutor using Mem0. This guide will walk you through the necessary steps and provide the complete code to get you started.
@@ -3,6 +3,9 @@ title: Self-Hosted AI Companion
description: "Run Mem0 end-to-end on your machine using Ollama-powered LLMs and embedders."
---
<Info icon="server">
**Works with:** Mem0 OSS (`Memory`)
</Info>
Mem0 can be utilized entirely locally by leveraging Ollama for both the embedding model and the language model (LLM). This guide will walk you through the necessary steps and provide the complete code to get you started.
@@ -3,6 +3,9 @@ title: Build a Node.js Companion
description: "Build a JavaScript fitness coach that remembers user goals run after run."
---
<Info icon="server">
**Works with:** Mem0 OSS (`Memory`)
</Info>
You can create a personalized AI Companion using Mem0. This guide will walk you through the necessary steps and provide the complete code to get you started.
@@ -3,6 +3,9 @@ title: Interactive Memory Demo
description: "Spin up the showcase companion app to see Mem0 memories in action."
---
<Info icon="cloud">
**Works with:** Mem0 Platform
</Info>
You can create a personalized AI Companion using Mem0. This guide will walk you through the necessary steps and provide the complete setup instructions to get you started.
@@ -3,6 +3,9 @@ title: Smart Travel Assistant
description: "Plan itineraries that remember traveler preferences across trips."
---
<Info icon="server">
**Works with:** Mem0 OSS (`Memory`)
</Info>
Create a personalized AI Travel Assistant using Mem0. This guide provides step-by-step instructions and the complete code to get you started.
@@ -3,6 +3,9 @@ title: Voice-First AI Companion
description: "Pair the OpenAI Agents SDK with Mem0 to build a voice assistant that remembers."
---
<Info icon="cloud">
**Works with:** Mem0 Platform (`MemoryClient`)
</Info>
This guide demonstrates how to combine OpenAI's Agents SDK for voice applications with Mem0's memory capabilities to create a voice assistant that remembers user preferences and past interactions.
@@ -3,6 +3,9 @@ title: Research Assistant for YouTube
description: "Layer personalized context over any video using the Mem0 YouTube assistant."
---
<Info icon="cloud">
**Works with:** Mem0 Platform
</Info>
Enhance your YouTube experience with Mem0's YouTube Assistant, a Chrome extension that brings AI-powered chat directly to your YouTube videos. Get instant, personalized answers about video content while leveraging your own knowledge and memories, all without leaving the page.
@@ -3,6 +3,9 @@ title: Build a Companion with Mem0
description: "Spin up a fitness coach that remembers goals, adapts tone, and keeps sessions personal."
---
<Info icon="layer-group">
**Works with:** Mem0 OSS (`Memory`) and Mem0 Platform (`MemoryClient`)
</Info>
Essentially, creating a companion out of LLMs is as simple as a loop. But these loops work great for one type of character without personalization and fall short as soon as you restart the chat.
@@ -1,516 +0,0 @@
---
title: Control Memory Ingestion
description: "Filter speculation, enforce formats, and gate low-confidence data before it persists."
---
AI assistants plugged with memory systems face a problem - they often store everything. Not every conversation needs to be remembered, and not every detail should go to the memory store. Without proper controls, memory systems accumulate unreliable data.
Mem0 lets you control your memory ingestion pipeline. In this cookbook, we'll demonstrate these controls using a medical assistant example - showing how to filter unwanted data, enforce data formats, and implement confidence-based storage.
---
## Overview
Without controls, everything gets stored - speculation, low-confidence data, and information that shouldn't persist. This uncontrolled ingestion leads to cluttered memory and retrieval failures.
Mem0 provides **three tools to control** what gets stored:
1. **Custom instructions** define what to remember and what to ignore.
2. **Confidence thresholds** ensure only verified facts persist.
3. **Memory updates** let you change information without creating duplicates.
In this tutorial, we will:
- Filter speculative statements with custom instructions
- Configure confidence thresholds for fact verification
- Update stored information without duplication
- Build a complete ingestion pipeline
---
## Setup
```python
from mem0 import MemoryClient
client = MemoryClient(api_key="your-api-key")
```
<Note>
Replace `your-api-key` with your actual Mem0 API key from the <a href="https://app.mem0.ai?utm_source=oss&utm_medium=cookbook-memory-ingestion" rel="nofollow">dashboard</a>. Without proper API authentication, memory operations will fail.
</Note>
---
## The Problem
Uncontrolled ingestion stores everything, including speculation:
```python
# Patient mentions speculation
messages = [{"role": "user", "content": "I think I might be allergic to penicillin"}]
client.add(messages, user_id="patient_123")
# Check what got stored
results = client.search("patient allergies", filters={"user_id": "patient_123"})
print(results['results'][0]['memory'])
```
**Output:**
```
Patient is allergic to penicillin
```
<Warning>
Without custom instructions, AI assistants treat speculation as confirmed facts. "I think I might be allergic" becomes "Patient is allergic": a dangerous transformation in sensitive domains like healthcare, legal, or financial services.
</Warning>
The speculation became a confirmed fact. Let's add controls.
---
## Custom Instructions
Custom instructions tell Mem0 what to store and what to ignore.
```python
instructions = """
Only store CONFIRMED medical facts.
Store:
- Confirmed diagnoses from doctors
- Known allergies with documented reactions
- Current medications being taken
Ignore:
- Speculation (words like "might", "maybe", "I think")
- Unverified symptoms
- Casual mentions without confirmation
"""
client.project.update(custom_instructions=instructions)
# Same speculative statement
messages = [{"role": "user", "content": "I think I might be allergic to penicillin"}]
client.add(messages, user_id="patient_123")
# Check what got stored
results = client.get_all(filters={"user_id": "patient_123"})
print(f"Memories stored: {len(results['results'])}")
```
**Output:**
```
Memories stored: 0
```
<Info>
**Expected output:** Zero memories stored. The speculative statement "I think I might be allergic" was filtered out before reaching storage. Custom instructions are actively blocking unreliable data.
</Info>
The speculation was filtered out.
---
## Designing Custom Instructions
When designing instructions, consider the trade-off between precision and recall:
**Too restrictive:** You'll miss important information (false negatives)
```python
# Too strict - filters out useful context
"""
Only store information if explicitly stated by a doctor with full name,
date, time, and medical license number.
"""
```
**Too permissive:** You'll store unreliable data (false positives)
```python
# Too loose - stores speculation as fact
"""
Store any health-related information mentioned.
"""
```
**Balanced approach:**
```python
# Clear categories with examples
"""
Store CONFIRMED facts:
- Diagnoses: "Dr. Smith diagnosed hypertension on March 15th"
- Allergies: "Patient had hives reaction to penicillin"
- Medications: "Taking Lisinopril 10mg daily"
Ignore SPECULATION:
- "I think I might have..."
- "Maybe it's..."
- "Could be related to..."
"""
```
<Tip>
Start with strict instructions (only store confirmed facts), then relax them based on your use case. It's easier to allow more data than to clean up polluted memory. Test with sample conversations before deploying to production.
</Tip>
Start with clear categories and iterate based on retrieval quality.
---
## Confidence Thresholds
Mem0 assigns confidence scores to extracted memories. Use these to filter low-quality data.
### Setting Thresholds
Setting the right confidence threshold depends on your application:
- **High-stakes domains** (medical, legal): Require 0.8+ confidence
- **General assistants**: 0.6+ confidence is often sufficient
- **Exploratory systems**: Lower thresholds (0.4+) capture more data
Test your pipeline with multiple input examples and threshold combinations to find what works for your use case.
```python
# Configure stricter instructions
client.project.update(
custom_instructions="""
Only extract memories with HIGH confidence.
Require specific details (dates, dosages, doctor names) for medical facts.
Skip vague or uncertain statements.
"""
)
# Test with uncertain statement
messages = [{"role": "user", "content": "The doctor mentioned something about my blood pressure"}]
result1 = client.add(messages, user_id="patient_123")
# Test with confirmed fact
messages = [{"role": "user", "content": "Dr. Smith diagnosed me with hypertension on March 15th"}]
result2 = client.add(messages, user_id="patient_123")
print("Vague statement stored:", len(result1['results']) > 0)
print("Confirmed fact stored:", len(result2['results']) > 0)
```
**Output:**
```
Vague statement stored: False
Confirmed fact stored: True
```
<Info icon="check">
**Expected behavior:** Low-confidence extractions are now filtered out automatically. Only verified facts with specific details (names, dates, dosages) persist in memory. The confidence threshold is working.
</Info>
The vague statement was filtered for low confidence. The confirmed fact with specific details was stored.
---
## Filtering Sensitive Information
Custom instructions can prevent storing personal identifiers:
```python
client.project.update(
custom_instructions="""
Medical memory rules:
STORE:
- Confirmed diagnoses
- Verified allergies
- Current medications
NEVER STORE:
- Social Security Numbers
- Insurance policy numbers
- Credit card information
- Full addresses
- Phone numbers
Replace identifiers with generic references if mentioned.
"""
)
# Test with PII
messages = [
{"role": "user", "content": "My SSN is 123-45-6789 and I'm allergic to penicillin"}
]
client.add(messages, user_id="patient_123")
# Check what was stored
results = client.get_all(filters={"user_id": "patient_123"})
for result in results['results']:
print(result['memory'])
```
**Output:**
```
Patient is allergic to penicillin
```
The SSN was filtered out, but the allergy was stored.
---
## Updating Memories
When information changes, update existing memories instead of creating duplicates.
```python
# Initial allergy stored
result = client.add(
[{"role": "user", "content": "Patient confirmed allergy to penicillin with documented hives reaction"}],
user_id="patient_123"
)
memory_id = result['results'][0]['id']
print(f"Stored memory: {memory_id}")
# Later, patient gets retested - allergy was false positive
client.update(
memory_id=memory_id,
text="Patient tested negative for penicillin allergy on April 2nd, 2025. Previous allergy was false positive.",
metadata={"verified": True, "updated_date": "2025-04-02"}
)
# Retrieve the updated memory
updated = client.get(memory_id)
print(f"\\nUpdated memory: {updated['memory']}")
print(f"Metadata: {updated['metadata']}")
```
**Output:**
```
Stored memory: mem_abc123
Updated memory: Patient tested negative for penicillin allergy on April 2nd, 2025. Previous allergy was false positive.
Metadata: {'verified': True, 'updated_date': '2025-04-02'}
```
### Benefits of Updating
**Preserves history:**
- `created_at` shows when the memory was first stored
- `updated_at` shows when it was modified
- Audit trail for compliance
**Avoids conflicts:**
- No duplicate or contradicting memories
- Single source of truth for each fact
<Warning>
That “no duplicates” promise comes from the inference pipeline. Keep `infer=True` when you rely on automatic updates. Raw imports (`infer=False`) skip conflict checks, so mixing the two modes for the same fact will create duplicates.
</Warning>
### Pick the right inference mode
| Mode | What it does | Best for | Watch out for |
| --- | --- | --- | --- |
| `infer=True` *(default)* | Runs the LLM pipeline so Mem0 extracts structured facts and resolves conflicts automatically. | Daily conversations, preference tracking, anything you want deduped. | Slightly slower because inference runs on every write. |
| `infer=False` | Stores your payload exactly as-is: no inference, no dedupe. | Bulk imports, compliance snapshots, curated facts you already trust. | Later `infer=True` calls for the same fact will create duplicates you must clean manually. |
<Tip>
Stay consistent per data source. If you need both behaviors, keep them in separate scopes (e.g., different `app_id` or `run_id`) so you always know which memories are inferred vs direct imports.
</Tip>
---
## Update vs Delete
When should you update vs delete?
### Update when:
- Information changes but remains relevant
- You need audit history
- The memory has relationships to other data
```python
# Medication dosage changed
client.update(
memory_id=med_id,
text="Taking Lisinopril 20mg daily (increased from 10mg on March 1st)"
)
```
### Delete when:
- Information was completely wrong
- Memory is no longer relevant
- Duplicate entry
```python
# Duplicate entry
client.delete(memory_id)
```
---
## Putting It Together
Here's a complete ingestion pipeline with all controls:
```python
from mem0 import MemoryClient
import os
# Initialize client
client = MemoryClient(api_key=os.getenv("MEM0_API_KEY"))
# Configure custom instructions
client.project.update(
custom_instructions="""
Medical memory assistant rules:
STORE:
- Confirmed diagnoses (with doctor name and date)
- Verified allergies (with reaction details)
- Current medications (with dosage)
IGNORE:
- Speculation (might, maybe, possibly)
- Unverified symptoms
- Personal identifiers (SSN, insurance numbers)
CONFIDENCE:
Require high confidence. Reject vague or uncertain statements.
Require specific details: names, dates, dosages.
"""
)
# Helper function for safe ingestion
def add_medical_memory(content, user_id, metadata=None):
"""Add memory with automatic filtering."""
result = client.add(
[{"role": "user", "content": content}],
user_id=user_id,
metadata=metadata or {}
)
if result['results']:
print(f"✓ Stored: {result['results'][0]['memory']}")
else:
print(f"✗ Filtered: {content}")
return result
# Test cases
print("Testing ingestion pipeline:\\n")
test_cases = [
"I think I might be allergic to penicillin",
"Dr. Johnson confirmed penicillin allergy on Jan 15th with hives reaction",
"Patient SSN is 123-45-6789",
"Currently taking Lisinopril 10mg daily for hypertension",
"Feeling tired lately",
"Dr. Martinez diagnosed Type 2 diabetes on February 3rd, 2025"
]
for content in test_cases:
add_medical_memory(content, user_id="patient_123")
print()
```
**Output:**
```
Testing ingestion pipeline:
✗ Filtered: I think I might be allergic to penicillin
✓ Stored: Patient has confirmed penicillin allergy diagnosed by Dr. Johnson on January 15th with hives reaction
✗ Filtered: Patient SSN is 123-45-6789
✓ Stored: Patient is currently taking Lisinopril 10mg daily for hypertension
✗ Filtered: Feeling tired lately
✓ Stored: Patient diagnosed with Type 2 diabetes by Dr. Martinez on February 3rd, 2025
```
---
## Per-Call Instructions
You can override project-level instructions for specific conversations:
First define custom instructions
```python
custom_instructions="""Emergency intake mode:Store ALL symptoms and observations immediately.
Flag for later review and verification."""
```
```python
# Emergency intake - store everything temporarily
emergency_messages = [
{"role": "user", "content": "Patient arrived with chest pain and shortness of breath"}
]
client.add(
emergency_messages,
user_id="patient_456",
custom_instructions=custom_instructions,
metadata={"type": "emergency", "review_required": True}
)
```
This is useful for:
- Different conversation types (emergency vs routine)
- Channel-specific rules (phone vs in-person)
- Temporary data collection that needs review
---
## What You Built
You now have a medical assistant with production-grade memory controls:
- **Custom instructions** - Filter speculation and enforce confirmed facts only
- **Confidence thresholds** - Gate extractions below 0.7 confidence score
- **Memory updates** - Modify stored information without creating duplicates
- **Per-call instructions** - Apply temporary rules for specific conversations
- **PII filtering** - Block sensitive data (SSNs, insurance numbers) automatically
These controls prevent retrieval failures and ensure your AI assistant works with reliable, verified information.
---
## Summary
Start with conservative filters (only store confirmed facts) and iterate based on your application's needs. Combine custom instructions with confidence thresholds for the most reliable memory ingestion pipeline.
<Card title="Build a Mem0 Companion" icon="users" href="/cookbooks/essentials/building-ai-companion">
Learn core memory patterns including temporary vs permanent data handling.
</Card>
<Snippet file="star-on-github.mdx" />
@@ -3,6 +3,10 @@ title: Partition Memories by Entity
description: Keep memories separate by tagging each write and query with user, agent, app, and session identifiers.
---
<Info icon="cloud">
**Works with:** Mem0 Platform (`MemoryClient`)
</Info>
Nora runs a travel service. When she stored all memories in one bucket, a recruiter's nut allergy accidentally appeared in a traveler's dinner reservation. Let's fix this by properly separating memories for different users, agents, and applications.
<Info icon="clock">
@@ -328,10 +332,10 @@ You learned how to:
href="/platform/features/v2-memory-filters"
/>
<Card
title="Control Memory Ingestion"
description="Pair scoped storage with rules that block low-quality facts."
title="Custom Instructions"
description="Pair scoped storage with instructions that steer what Mem0 extracts and stores."
icon="shield-check"
href="/cookbooks/essentials/controlling-memory-ingestion"
href="/platform/features/custom-instructions"
/>
</CardGroup>
@@ -3,6 +3,9 @@ title: Export Stored Memories
description: "Retrieve, review, and migrate user memories with structured exports."
---
<Info icon="cloud">
**Works with:** Mem0 Platform (`MemoryClient`)
</Info>
Mem0 is a dynamic memory store that gives you full control over your data. Along with storing memories, it gives you the ability to retrieve, export, and migrate your data whenever you need.
@@ -169,20 +172,20 @@ export_job = client.create_memory_export(
)
print(f"Export ID: {export_job['id']}")
print(f"Status: {export_job['status']}")
print(f"Message: {export_job['message']}")
```
**Output:**
```
Export ID: exp_abc123
Status: processing
Export ID: 550e8400-e29b-41d4-a716-446655440000
Message: Memory export request received. The export will be ready in a few seconds.
```
<Info>
**Export initiated:** Status is "processing". Large exports may take a few seconds. Poll with `get_memory_export()` until status changes to "completed" before downloading data.
**Export initiated:** The export runs asynchronously and is usually ready within a few seconds. Retry `get_memory_export()` with the returned ID until it stops returning a "no export found" error.
</Info>
### Step 3: Download the export
@@ -193,7 +196,7 @@ export_data = client.get_memory_export(
memory_export_id=export_job['id']
)
print(export_data['data'])
print(export_data)
```
@@ -216,7 +219,7 @@ export_by_filters = client.get_memory_export(
filters={"user_id": "dev"}
)
print(export_by_filters['data'])
print(export_by_filters)
```
@@ -240,7 +243,7 @@ export_with_instructions = client.create_memory_export(
```
<Tip>
Always check export status before downloading. Call `get_memory_export()` in a loop with a short delay until `status == "completed"`. Attempting to download while still processing returns incomplete data.
If the export is still processing, `get_memory_export()` returns a 404 with `{"error": "No memory export request found"}`. Retry after a short delay until the call succeeds instead of polling a status field.
</Tip>
---
@@ -284,8 +287,8 @@ Use **`get_all()`** for bulk retrieval, **`search()`** for specific questions, a
<Card title="Build a Mem0 Companion" icon="users" href="/cookbooks/essentials/building-ai-companion">
Learn core memory patterns including temporary vs permanent data handling.
</Card>
<Card title="Control Memory Ingestion" icon="filter" href="/cookbooks/essentials/controlling-memory-ingestion">
Ensure only verified insights make it into your export pipeline.
<Card title="Custom Instructions" icon="filter" href="/platform/features/custom-instructions">
Steer what Mem0 extracts so only verified insights make it into your export pipeline.
</Card>
</CardGroup>
@@ -3,6 +3,9 @@ title: Tag and Organize Memories
description: "Let Mem0 auto-categorize support data so teams retrieve the right facts fast."
---
<Info icon="cloud">
**Works with:** Mem0 Platform (`MemoryClient`)
</Info>
When you have large volumes of memory data, sorting it during post-processing becomes difficult. What if your memory store understood the importance of creating tags and buckets without a lot of effort?
@@ -246,8 +249,8 @@ Categories make retrieval faster and compliance easier. Define 3-5 clear categor
Instead of searching through everything, agents jump directly to the information type they need: billing issues, account details, or support tickets.
<CardGroup cols={2}>
<Card title="Control Memory Ingestion" icon="filter" href="/cookbooks/essentials/controlling-memory-ingestion">
Keep categories meaningful by filtering noise before it lands in storage.
<Card title="Custom Instructions" icon="filter" href="/platform/features/custom-instructions">
Keep categories meaningful by steering what Mem0 extracts before it lands in storage.
</Card>
<Card title="Export Tagged Memories" icon="download" href="/cookbooks/essentials/exporting-memories">
Use categories to drive audits, migrations, and compliance reports.
@@ -3,6 +3,9 @@ title: Persistent Eliza Characters
description: "Bring persistent personality to Eliza OS agents using Mem0."
---
<Info icon="cloud">
**Works with:** Mem0 Platform
</Info>
You can create a personalized Eliza OS Character using Mem0. This guide will walk you through the necessary steps and provide the complete code to get you started.
@@ -3,6 +3,10 @@ title: "Gemini 3 with Mem0 MCP"
description: "Create snappy, smart, memory-aware agents by pairing Gemini 3 with Mem0 MCP server."
---
<Info icon="cloud">
**Works with:** Mem0 Platform (MCP server)
</Info>
Gemini 3, when paired with Mem0's cloud MCP server, works in synergy to create snappy, smart, memory-aware agents.
<Callout type="info" icon="sparkles" color="#8B5CF6">
@@ -3,6 +3,9 @@ title: Multi-Agent Collaboration
description: "Share a persistent memory layer across collaborating LlamaIndex agents."
---
<Info icon="cloud">
**Works with:** Mem0 Platform (`Mem0Memory.from_client`)
</Info>
<Snippet file="blank-notif.mdx" />
@@ -3,6 +3,9 @@ title: ReAct Agents with Memory
description: "Teach a ReAct agent to store and recall context via Mem0."
---
<Info icon="cloud">
**Works with:** Mem0 Platform (`Mem0Memory.from_client`)
</Info>
Create a ReAct Agent with LlamaIndex which uses Mem0 as the memory store.
@@ -78,7 +81,7 @@ from llama_index.core.agent import FunctionCallingAgent
agent = FunctionCallingAgent.from_tools(
[call_tool, email_tool, order_food_tool],
llm=llm,
memory=memory_from_client, # or memory_from_config
memory=memory_from_client,
verbose=True,
)
```
@@ -161,7 +164,7 @@ agent = FunctionCallingAgent.from_tools(
[call_tool, email_tool, order_food_tool],
llm=llm,
# memory is provided
memory=memory_from_client, # or memory_from_config
memory=memory_from_client,
verbose=True,
)
response = agent.chat("I am feeling hungry, order me something and send me the bill")
@@ -3,6 +3,9 @@ title: Visual Memory Retrieval
description: "Store and recall visual context alongside text conversations."
---
<Info icon="cloud">
**Works with:** Mem0 Platform
</Info>
Enhance your AI interactions with Mem0's multimodal capabilities. Mem0 now supports image understanding, allowing for richer context and more natural interactions across supported AI platforms.
@@ -3,6 +3,9 @@ title: Memory-Powered Agent SDK
description: "Expose Mem0 memories as callable tools inside OpenAI agent workflows."
---
<Info icon="cloud">
**Works with:** Mem0 Platform (`MemoryClient`)
</Info>
Integrate Mem0's memory capabilities with OpenAI's Agents SDK to create AI agents with persistent memory. You can create agents that remember past conversations and use that context to provide better responses.
@@ -3,6 +3,9 @@ title: Bedrock with Persistent Memory
description: "Pair Mem0 with AWS Bedrock and OpenSearch for a managed stack."
---
<Info icon="server">
**Works with:** Mem0 OSS (`Memory`)
</Info>
This example demonstrates how to configure and use the `mem0ai` SDK with **AWS Bedrock** and **OpenSearch Service (AOSS)** for persistent memory capabilities in Python.
@@ -3,6 +3,9 @@ title: Healthcare Coach with ADK
description: "Guide patients with an assistant that remembers history across ADK sessions."
---
<Info icon="cloud">
**Works with:** Mem0 Platform (`MemoryClient`)
</Info>
This example demonstrates how to build a healthcare assistant that remembers patient information across conversations using Google ADK and Mem0.
@@ -123,7 +126,7 @@ Now we'll create our main agent with all the tools:
# Create the agent
healthcare_agent = Agent(
name="healthcare_assistant",
model="gemini-1.5-flash", # Using Gemini for healthcare assistant
model="gemini-2.0-flash", # Using Gemini for healthcare assistant
description="Healthcare assistant that helps patients with health information and appointment scheduling.",
instruction="""You are a helpful Healthcare Assistant with memory capabilities.
+4 -1
View File
@@ -3,10 +3,13 @@ title: Persistent Mastra Agents
description: "Extend Mastra agents with persistent memories powered by Mem0."
---
<Info icon="cloud">
**Works with:** Mem0 Platform (`@mastra/mem0`)
</Info>
In this example you'll learn how to use Mem0 to add long-term memory capabilities to [Mastra's agent](https://mastra.ai/) via tool-use. This memory integration can work alongside Mastra's [agent memory features](https://mastra.ai/docs/agents/01-agent-memory).
You can find the complete example code in the [Mastra repository](https://github.com/mastra-ai/mastra/tree/main/examples/memory-with-mem0).
The complete example code, from installing the integration to wiring it into a Mastra agent, is shown below. Mem0's integration is published on npm as [`@mastra/mem0`](https://www.npmjs.com/package/@mastra/mem0).
## Overview
@@ -3,6 +3,9 @@ title: Memory as OpenAI Tool
description: "Wire Mem0 memories into OpenAI's inbuilt function-calling flow."
---
<Info icon="cloud">
**Works with:** Mem0 Platform (`MemoryClient`)
</Info>
Integrate Mem0’s memory capabilities with OpenAI’s Inbuilt Tools to create AI agents with persistent memory.
@@ -290,8 +293,8 @@ run().catch(console.error);
<Card title="Agents SDK Tool with Mem0" icon="robot" href="/cookbooks/integrations/agents-sdk-tool">
Extend the OpenAI Agents SDK with Mem0 integration capabilities.
</Card>
<Card title="Control Memory Ingestion" icon="filter" href="/cookbooks/essentials/controlling-memory-ingestion">
Fine-tune what memories get stored during tool calls.
<Card title="Custom Instructions" icon="filter" href="/platform/features/custom-instructions">
Steer what memories get stored during tool calls.
</Card>
</CardGroup>
@@ -3,12 +3,14 @@ title: Search with Personal Context
description: "Blend Tavily's realtime results with personal context stored in Mem0."
---
<Info icon="cloud">
**Works with:** Mem0 Platform (`MemoryClient`)
</Info>
Imagine asking a search assistant for "coffee shops nearby" and instead of generic results, it shows remote-work-friendly cafes with great WiFi in your city because it remembers you mentioned working remotely before. Or when you search for "lunchbox ideas for kids" it knows you have a 7-year-old daughter and recommends peanut-free options that align with her allergy.
That's what we are going to build today, a Personalized Search Assistant powered by Mem0 for memory and [Tavily](https://tavily.com) for real-time search.
## Why Personalized Search
Most assistants treat every query like they've never seen you before. That means repeating yourself about your location, diet, or preferences, and getting results that feel generic.
@@ -3,6 +3,9 @@ title: Content Creation Workflow
description: "Store voice guidelines once and apply them across every draft."
---
<Info icon="layer-group">
**Works with:** Mem0 OSS (`Memory`) and Mem0 Platform (`MemoryClient`)
</Info>
This guide demonstrates how to leverage **Mem0** to streamline content writing by applying your unique writing style and preferences using persistent memory.
@@ -357,8 +360,8 @@ Mem0 enables a seamless, intelligent content-writing workflow, perfect for conte
---
<CardGroup cols={2}>
<Card title="Control Memory Ingestion" icon="filter" href="/cookbooks/essentials/controlling-memory-ingestion">
Filter and curate content examples to maintain consistent writing style.
<Card title="Custom Instructions" icon="filter" href="/platform/features/custom-instructions">
Steer what Mem0 extracts and stores to maintain a consistent writing style.
</Card>
<Card title="Email Automation with Mem0" icon="envelope" href="/cookbooks/operations/email-automation">
Automate email drafting with memory-powered context and tone matching.
+3 -8
View File
@@ -3,11 +3,12 @@ title: Multi-Session Research Agent
description: "Run multi-session investigations that remember past findings and preferences."
---
<Info icon="cloud">
**Works with:** Mem0 Platform
</Info>
Deep Research is an intelligent agent that synthesizes large amounts of online data and completes complex research tasks, customized to your unique preferences and insights. Built on Mem0's technology, it enhances AI-driven online exploration with personalized memories.
You can check out the GitHub repository here: [Personalized Deep Research](https://github.com/mem0ai/personalized-deep-research/tree/mem0)
## Overview
Deep Research leverages Mem0's memory capabilities to:
@@ -61,12 +62,6 @@ Watch Deep Research in action:
- **Technical Research**: Technology evaluation, solution comparison
- **Business Research**: Strategic planning, opportunity analysis
## Try It Out
> To try it yourself, clone the repository and follow the instructions in the README to run it locally or deploy it.
- [Personalized Deep Research GitHub](https://github.com/mem0ai/personalized-deep-research/tree/mem0)
---
<CardGroup cols={2}>
@@ -3,6 +3,9 @@ title: Automated Email Intelligence
description: "Capture, categorize, and recall inbox threads using persistent memories."
---
<Info icon="layer-group">
**Works with:** Mem0 OSS (`Memory`) and Mem0 Platform (`MemoryClient`)
</Info>
This guide demonstrates how to build an intelligent email processing system using Mem0's memory capabilities. You'll learn how to store, categorize, retrieve, and analyze emails to create a smart email management solution.
@@ -3,6 +3,9 @@ title: Memory-Powered Support Agent
description: "Build a support assistant that keeps past tickets and resolutions at its fingertips."
---
<Info icon="server">
**Works with:** Mem0 OSS (`Memory`)
</Info>
You can create a personalized Customer Support AI Agent using Mem0. This guide will walk you through the necessary steps and provide the complete code to get you started.
@@ -3,6 +3,9 @@ title: Collaborative Task Assistant
description: "Coordinate multi-user projects with shared memories and roles."
---
<Info icon="server">
**Works with:** Mem0 OSS (`Memory`)
</Info>
## Overview
+60 -4
View File
@@ -13,6 +13,55 @@ With Mem0, you can create stateful LLM-based applications such as chatbots, virt
Here are some examples of how Mem0 can be integrated into various applications:
## Pick by compatibility
Every cookbook opens with a **Works with** badge naming the SDK surface it uses. Pick your setup below to see only the cookbooks that run on it. Three cookbooks work on both and appear under either tab.
<Tabs>
<Tab title="Self-hosted OSS">
Ten cookbooks run on the open-source `Memory` class, with no Mem0 Platform account.
| Cookbook | Category | Works with |
| --- | --- | --- |
| [Personalized AI Tutor](/cookbooks/companions/ai-tutor) | Companions | OSS only |
| [Build a Node.js Companion](/cookbooks/companions/nodejs-companion) | Companions | OSS only |
| [Self-Hosted AI Companion](/cookbooks/companions/local-companion-ollama) | Companions | OSS only |
| [Smart Travel Assistant](/cookbooks/companions/travel-assistant) | Companions | OSS only |
| [Build a Companion with Mem0](/cookbooks/essentials/building-ai-companion) | Essentials | OSS and Platform |
| [Bedrock with Persistent Memory](/cookbooks/integrations/aws-bedrock) | Integrations | OSS only |
| [Automated Email Intelligence](/cookbooks/operations/email-automation) | Operations | OSS and Platform |
| [Collaborative Task Assistant](/cookbooks/operations/team-task-agent) | Operations | OSS only |
| [Content Creation Workflow](/cookbooks/operations/content-writing) | Operations | OSS and Platform |
| [Memory-Powered Support Agent](/cookbooks/operations/support-inbox) | Operations | OSS only |
</Tab>
<Tab title="Hosted Platform">
Twenty cookbooks run on the hosted Platform, using `MemoryClient` or the Mem0 MCP server with a `MEM0_API_KEY`.
| Cookbook | Category | Works with |
| --- | --- | --- |
| [Interactive Memory Demo](/cookbooks/companions/quickstart-demo) | Companions | Platform only |
| [Research Assistant for YouTube](/cookbooks/companions/youtube-research) | Companions | Platform only |
| [Voice-First AI Companion](/cookbooks/companions/voice-companion-openai) | Companions | Platform only |
| [Build a Companion with Mem0](/cookbooks/essentials/building-ai-companion) | Essentials | OSS and Platform |
| [Export Stored Memories](/cookbooks/essentials/exporting-memories) | Essentials | Platform only |
| [Partition Memories by Entity](/cookbooks/essentials/entity-partitioning-playbook) | Essentials | Platform only |
| [Tag and Organize Memories](/cookbooks/essentials/tagging-and-organizing-memories) | Essentials | Platform only |
| [Gemini 3 with Mem0 MCP](/cookbooks/frameworks/gemini-3-with-mem0-mcp) | Frameworks | Platform only |
| [Multi-Agent Collaboration](/cookbooks/frameworks/llamaindex-multiagent) | Frameworks | Platform only |
| [Persistent Eliza Characters](/cookbooks/frameworks/eliza-os-character) | Frameworks | Platform only |
| [ReAct Agents with Memory](/cookbooks/frameworks/llamaindex-react) | Frameworks | Platform only |
| [Visual Memory Retrieval](/cookbooks/frameworks/multimodal-retrieval) | Frameworks | Platform only |
| [Healthcare Coach with ADK](/cookbooks/integrations/healthcare-google-adk) | Integrations | Platform only |
| [Memory as OpenAI Tool](/cookbooks/integrations/openai-tool-calls) | Integrations | Platform only |
| [Memory-Powered Agent SDK](/cookbooks/integrations/agents-sdk-tool) | Integrations | Platform only |
| [Persistent Mastra Agents](/cookbooks/integrations/mastra-agent) | Integrations | Platform only |
| [Search with Personal Context](/cookbooks/integrations/tavily-search) | Integrations | Platform only |
| [Automated Email Intelligence](/cookbooks/operations/email-automation) | Operations | OSS and Platform |
| [Content Creation Workflow](/cookbooks/operations/content-writing) | Operations | OSS and Platform |
| [Multi-Session Research Agent](/cookbooks/operations/deep-research) | Operations | Platform only |
</Tab>
</Tabs>
## Start here
The most popular cookbooks to get going fast:
@@ -47,11 +96,18 @@ The most popular cookbooks to get going fast:
Balance personalization with consistent behavior across users, agents, and apps.
</Card>
<Card
title="Control Memory Ingestion"
icon="filter"
href="/cookbooks/essentials/controlling-memory-ingestion"
title="Tag and Organize Memories"
icon="tags"
href="/cookbooks/essentials/tagging-and-organizing-memories"
>
Filter speculation and low-confidence data.
Use categories to keep retrieval fast and audits simple.
</Card>
<Card
title="Export Memories"
icon="download"
href="/cookbooks/essentials/exporting-memories"
>
Back up, migrate, and audit stored memory data.
</Card>
</CardGroup>
+66 -69
View File
@@ -1,68 +1,69 @@
---
title: Memory Types
description: "See how Mem0 layers conversation, session, and user memories to keep agents contextual."
description: "What memory_type actually does in Mem0: procedural memory is implemented, semantic and episodic are not."
icon: "tag"
iconType: "solid"
---
# How Mem0 Organizes Memory
# Memory Types
Mem0 separates memory into layers so agents remember the right detail at the right time. Think of it like a notebook: a sticky note for the current task, a daily journal for the session, and an archive for everything a user has shared.
Mem0's Python SDK exposes a `memory_type` parameter on `add()`. The underlying `MemoryType` enum defines three values, but only one of them is wired up. This page states plainly which is which so you don't build against a type that doesn't exist yet.
## Key terms
## Status
- **Conversation memory**: In-flight messages inside a single turn (what was just said).
- **Session memory**: Short-lived facts that apply for the current task or channel.
- **User memory**: Long-lived knowledge tied to a person, account, or workspace.
- **Organizational memory**: Shared context available to multiple agents or teams.
| Type | Enum value | Status | Notes |
| --- | --- | --- | --- |
| Procedural memory | `procedural_memory` | **Implemented** | Python OSS only (`Memory`/`AsyncMemory`). Pass `memory_type="procedural_memory"` and `agent_id` to `add()`. Not available on the Platform `MemoryClient`, and not available in the TypeScript SDK (OSS or Platform). |
| Semantic memory | `semantic_memory` | **Not implemented** | Defined in the `MemoryType` enum but never read anywhere else in the codebase. Passing it to `add()` raises a validation error. There is no evidence in this repo of a roadmap date for this. |
| Episodic memory | `episodic_memory` | **Not implemented** | Same as above: defined, never wired into the extraction pipeline, rejected by validation, no documented roadmap. |
```mermaid
graph LR
A[Conversation turn] --> B[Session memory]
B --> C[User memory]
C --> D[Org memory]
C --> E[Mem0 retrieval layer]
```
<Warning>
Only `procedural_memory` is a real, working value. Calling `memory.add(messages, memory_type="semantic_memory")` (or `episodic_memory`) is rejected and tells you to pass `procedural_memory` instead. Sync `Memory.add()` raises `Mem0ValidationError`; `AsyncMemory.add()` raises a plain `ValueError`.
</Warning>
## Short-term vs long-term memory
## Procedural memory
Short-term memory keeps the current conversation coherent. It includes:
- **Conversation history**: recent turns in order so the agent remembers what was just said.
- **Working memory**: temporary state such as tool outputs or intermediate calculations.
- **Attention context**: the immediate focus of the assistant, similar to what a person holds in mind mid-sentence.
Long-term memory preserves knowledge across sessions. It captures:
- **Factual memory**: user preferences, account details, and domain facts.
- **Episodic memory**: summaries of past interactions or completed tasks.
- **Semantic memory**: relationships between concepts so agents can reason about them later.
Mem0 maps these classic categories onto its layered storage so you can decide what should fade quickly versus what should last for months.
## How does it work?
Mem0 stores each layer separately and merges them when you query:
1. **Capture**: Messages enter the conversation layer while the turn is active.
2. **Promote**: Relevant details persist to session or user memory based on your `user_id`, `run_id`, and metadata.
3. **Retrieve**: The search pipeline pulls from all layers, ranking user memories first, then session notes, then raw history.
Procedural memory stores step-by-step task knowledge (how an agent performs a workflow) rather than facts about a user. It requires `agent_id`:
```python
import os
from mem0 import Memory
memory = Memory()
# Sticky note: conversation memory
memory.add(
["I'm Alex and I prefer boutique hotels."],
[
{"role": "user", "content": "Book a flight from SFO to NYC"},
{"role": "assistant", "content": "1. Search flights. 2. Filter by price. 3. Confirm booking."},
],
agent_id="travel-agent",
memory_type="procedural_memory",
)
```
Omit `memory_type` entirely and Mem0 stores the messages as an ordinary memory: there is no semantic/episodic pathway for it to fall into. Any other explicit value is rejected by validation rather than quietly falling back to an ordinary memory.
## How every other memory is scoped
Outside of the `procedural_memory` special case, Mem0 does not sort memories into named types. Every memory is scoped by the identifiers you pass in, and the same identifiers are used to retrieve it later:
- **`user_id`**: ties a memory to a specific person or account.
- **`agent_id`**: ties a memory to a specific agent or assistant persona.
- **`run_id`**: ties a memory to a specific session, task, or conversation thread.
- **`app_id`** (Platform only): ties a memory to a specific application or tenant, in addition to the three above. See <Link href="/platform/features/entity-scoped-memory">Entity-Scoped Memory</Link>.
At least one identifier is required on `add()`. Passing more than one narrows the scope further (for example, `user_id` + `run_id` together).
```python
from mem0 import Memory
memory = Memory()
memory.add(
"I'm Alex and I prefer boutique hotels.",
user_id="alex",
run_id="trip-planning-2025",
)
# Later in the session, pull long-term + session context
results = memory.search(
"Any hotel preferences?",
filters={"user_id": "alex", "run_id": "trip-planning-2025"},
@@ -70,52 +71,48 @@ results = memory.search(
```
<Tip>
Use `run_id` when you want short-term context to expire automatically; rely on `user_id` for lasting personalization.
Use `run_id` when you want a set of memories to stay tied to one session or task; use `user_id` alone for anything that should persist across every session for that person.
</Tip>
## When should you use each layer?
## How memories are extracted and updated
- **Conversation memory**: Tool calls or chain-of-thought that only matter within the current turn.
- **Session memory**: Multi-step tasks (onboarding flows, debugging sessions) that should reset once complete.
- **User memory**: Personal preferences, account state, or compliance details that must persist across interactions.
- **Organizational memory**: Shared FAQs, product catalogs, or policies that every agent should recall.
When `infer=True` (the default) on `add()`, Mem0 runs a single pipeline rather than routing through separate type-specific paths:
## How it compares
1. **Context gathering**: pulls the most recent messages already stored for the same `user_id`/`agent_id`/`run_id` scope.
2. **Existing memory retrieval**: embeds the new messages and runs a vector search against memories already in that same scope, to find candidates that might need to change.
3. **Extraction**: a single LLM call compares the new messages against the retrieved candidates and decides, per fact, whether to `ADD`, `UPDATE`, `DELETE`, or leave a memory alone.
| Layer | Lifetime | Short or long term | Best for | Trade-offs |
| --- | --- | --- | --- | --- |
| Conversation | Single response | Short-term | Tool execution detail | Lost after the turn finishes |
| Session | Minutes to hours | Short-term | Multi-step flows | Clear it manually when done |
| User | Weeks to forever | Long-term | Personalization | Requires consent/governance |
| Org | Configured globally | Long-term | Shared knowledge | Needs owner to keep current |
Alongside this, both OSS and Platform extract named entities (people, places, organizations) from memory text and use shared entities between memories to boost related results at search time. On Platform, that entity graph is also queryable directly; see <Link href="/platform/features/graph-memory">Graph Memory</Link>. In OSS, entities only affect ranking, there is no separate graph to query.
<Warning>
Avoid storing secrets or unredacted PII in user or org memories: Mem0 is retrievable by design. Encrypt or hash sensitive values first.
Avoid storing secrets or unredacted PII in memories: they are retrievable by design. Encrypt or hash sensitive values before calling `add()`.
</Warning>
## Put it into practice
- Use the <Link href="/core-concepts/memory-operations/add">Add Memory</Link> guide to persist user preferences.
- Follow <Link href="/platform/advanced-memory-operations">Advanced Memory Operations</Link> to tune metadata and retrieval.
## See it live
- <Link href="/cookbooks/companions/ai-tutor">AI Tutor with Mem0</Link> shows session vs user memories in action.
- <Link href="/cookbooks/operations/support-inbox">Support Inbox with Mem0</Link> demonstrates shared org memory.
{/* DEBUG: verify CTA targets */}
<CardGroup cols={2}>
<Card
title="Explore Memory Operations"
description="Dive into the add/search/update/delete concepts next."
description="Dive into the add/search/update/delete operations next."
icon="circle-check"
href="/core-concepts/memory-operations/add"
/>
<Card
title="See a Cookbook"
description="Apply layered memories inside a customer support agent."
title="Advanced Memory Operations"
description="Tune metadata, filters, and retrieval on Platform."
icon="sliders"
href="/platform/advanced-memory-operations"
/>
<Card
title="AI Tutor Cookbook"
description="See user_id-scoped memory used in a real tutoring agent."
icon="rocket"
href="/cookbooks/companions/ai-tutor"
/>
<Card
title="Support Inbox Cookbook"
description="See user_id-scoped memory used in a support workflow."
icon="inbox"
href="/cookbooks/operations/support-inbox"
/>
</CardGroup>
+93 -24
View File
@@ -325,7 +325,9 @@
"integrations/google-ai-adk",
"integrations/mastra",
"integrations/vercel-ai-sdk",
"integrations/chatdev"
"integrations/vercel",
"integrations/chatdev",
"integrations/strands"
]
},
{
@@ -368,6 +370,7 @@
"icon": "terminal",
"pages": [
"integrations/claude-code",
"integrations/claude-ai",
"integrations/cursor",
"integrations/codex",
"integrations/opencode",
@@ -380,7 +383,8 @@
"pages": [
"integrations/openclaw",
"integrations/hermes",
"integrations/pi-agent"
"integrations/pi-agent",
"integrations/deepseek-plugin"
]
}
]
@@ -401,7 +405,6 @@
"pages": [
"cookbooks/essentials/building-ai-companion",
"cookbooks/essentials/entity-partitioning-playbook",
"cookbooks/essentials/controlling-memory-ingestion",
"cookbooks/essentials/tagging-and-organizing-memories",
"cookbooks/essentials/exporting-memories"
]
@@ -536,6 +539,20 @@
"api-reference/project/delete-project"
]
},
{
"group": "Dream",
"icon": "sparkles",
"pages": [
"api-reference/dream/get-dream-config",
"api-reference/dream/update-dream-config",
"api-reference/dream/get-dream-stats",
"api-reference/dream/get-dream-activity",
"api-reference/dream/get-dream-runs",
"api-reference/dream/get-dream-run-memories",
"api-reference/dream/get-dream-memory-sources",
"api-reference/dream/dream-preview"
]
},
{
"group": "Webhooks",
"icon": "webhook",
@@ -611,6 +628,10 @@
]
},
"redirects": [
{
"source": "/open-source/features/reranking",
"destination": "/open-source/features/reranker-search"
},
{
"source": "/platform/features/contextual-add",
"destination": "/core-concepts/memory-operations/add"
@@ -948,24 +969,20 @@
"destination": "/platform/features/memory-export"
},
{
"source": "/v0x/components/:a/:b/:c",
"destination": "/components/:a/:b/:c"
"source": "/v0x/components/:slug*",
"destination": "/components/:slug*"
},
{
"source": "/v0x/components/:a/:b",
"destination": "/components/:a/:b"
"source": "/v0x/core-concepts/:slug*",
"destination": "/core-concepts/:slug*"
},
{
"source": "/v0x/core-concepts/:a/:b",
"destination": "/core-concepts/:a/:b"
"source": "/v0x/integrations/:slug*",
"destination": "/integrations/:slug*"
},
{
"source": "/v0x/integrations/:slug",
"destination": "/integrations/:slug"
},
{
"source": "/v0x/open-source/:slug",
"destination": "/open-source/:slug"
"source": "/v0x/open-source/:slug*",
"destination": "/open-source/:slug*"
},
{
"source": "/v0x/introduction",
@@ -1040,8 +1057,8 @@
"destination": "/platform/features/graph-memory"
},
{
"source": "/features/:slug",
"destination": "/platform/features/:slug"
"source": "/features/:slug*",
"destination": "/platform/features/:slug*"
},
{
"source": "/platform/features/online-memory",
@@ -1168,7 +1185,7 @@
"destination": "/open-source/overview"
},
{
"source": "/self-hosting/:slug",
"source": "/self-hosting/:slug*",
"destination": "/open-source/overview"
},
{
@@ -1176,7 +1193,7 @@
"destination": "/open-source/overview"
},
{
"source": "/self-hosted/:slug",
"source": "/self-hosted/:slug*",
"destination": "/open-source/overview"
},
{
@@ -1184,7 +1201,7 @@
"destination": "/platform/quickstart"
},
{
"source": "/getting-started/:slug",
"source": "/getting-started/:slug*",
"destination": "/platform/quickstart"
},
{
@@ -1192,7 +1209,7 @@
"destination": "/open-source/setup"
},
{
"source": "/deployment/:slug",
"source": "/deployment/:slug*",
"destination": "/open-source/setup"
},
{
@@ -1204,12 +1221,12 @@
"destination": "/open-source/overview"
},
{
"source": "/oss/:slug",
"source": "/oss/:slug*",
"destination": "/open-source/overview"
},
{
"source": "/concepts/:slug",
"destination": "/core-concepts/:slug"
"source": "/concepts/:slug*",
"destination": "/core-concepts/:slug*"
},
{
"source": "/pricing",
@@ -1246,6 +1263,58 @@
{
"source": "/integrations/keywords",
"destination": "/integrations/respan"
},
{
"source": "/cookbooks/essentials/controlling-memory-ingestion",
"destination": "/platform/features/custom-instructions"
},
{
"source": "/open-source/quickstart",
"destination": "/open-source/overview"
},
{
"source": "/open-source/graph-memory/overview",
"destination": "/open-source/overview"
},
{
"source": "/what-is-mem0",
"destination": "https://mem0.ai/"
},
{
"source": "/platform-vs-oss",
"destination": "/platform/platform-vs-oss"
},
{
"source": "/components/overview",
"destination": "/components/llms/overview"
},
{
"source": "/v0x/core-concepts/memory-operations/add",
"destination": "/core-concepts/memory-operations/add"
},
{
"source": "/v0x/integrations/llama-index",
"destination": "/integrations/llama-index"
},
{
"source": "/api-reference/event/get-event",
"destination": "/api-reference/events/get-event"
},
{
"source": "/api-reference/webhook/get-webhooks",
"destination": "/api-reference/webhook/get-webhook"
},
{
"source": "/api-reference/organization/update-organization-members",
"destination": "/api-reference/organization/update-org-member"
},
{
"source": "/api-reference/organization/delete-project",
"destination": "/api-reference/project/delete-project"
},
{
"source": "/api-reference/entities/get-entities",
"destination": "/api-reference/entities/get-users"
}
]
}
+85
View File
@@ -0,0 +1,85 @@
---
title: Claude.ai
description: "Add persistent memory to Claude.ai with the Mem0 remote MCP server: custom connector setup and native-memory troubleshooting."
---
Add persistent memory to [**Claude.ai**](https://claude.ai) (the hosted web app, not Claude Code) using Mem0's remote MCP server. Claude.ai connects to MCP servers over the internet as **custom connectors**; there is no local plugin or hook system here, since chats run in Anthropic's cloud, not on your machine.
## Prerequisites
1. A Mem0 Platform account: <a href="https://app.mem0.ai?utm_source=oss&utm_medium=integration-claude-ai" rel="nofollow">sign up at app.mem0.ai</a>
2. A Claude.ai account on any plan (free, Pro, Max, Team, or Enterprise)
You don't need an API key up front: the connector uses browser-based sign-in the first time Claude calls a Mem0 tool (see [Signing in](#signing-in)).
## Installation
### Individual accounts (Pro, Max, or free)
1. Go to **Customize > Connectors**
2. Click **+**, then **Add custom connector**
3. Enter the server URL: `https://mcp.mem0.ai/mcp/`
4. Click **Add**
<Note>
Free-tier accounts are limited to one custom connector. Pro, Max, Team, and Enterprise accounts can add multiple.
</Note>
### Team and Enterprise organizations
An organization owner registers the connector once for everyone:
1. Go to **Organization Settings > Connectors**
2. Click **Add**, hover **Custom**, then select **Web**
3. Enter the server URL: `https://mcp.mem0.ai/mcp/`
4. Click **Add**
Members then connect individually: **Customize > Connectors**, find **mem0**, and click **Connect**.
### Enabling in a chat
Connectors are opt-in per conversation. Click the **+** button next to the chat prompt, open **Connectors**, and toggle **mem0** on before you start.
## Signing in
The first time Claude calls a Mem0 tool, your browser opens a sign-in prompt to authorize the connector against your Mem0 account. Approve it once; Claude.ai stores and refreshes the resulting token for you. There's no API key to paste into the connector UI itself.
## What's Included
| Component | Included |
|-----------|:--------:|
| MCP Server (9 memory tools) | Yes |
| Lifecycle Hooks | No (Claude.ai has no local hook system) |
| Mem0 SDK Skill | No (Claude.ai has no local skills directory) |
## Available MCP Tools
| Tool | Description |
|------|-------------|
| `add_memory` | Save text or conversation history for a user/agent |
| `search_memories` | Semantic search across memories with filters |
| `get_memories` | List memories with filters and pagination |
| `get_memory` | Retrieve a specific memory by ID |
| `update_memory` | Overwrite a memory's text by ID |
| `delete_memory` | Delete a single memory by ID |
| `delete_all_memories` | Bulk delete all memories in scope |
| `delete_entities` | Delete a user/agent/app/run entity and its memories |
| `list_entities` | List users/agents/apps/runs stored in Mem0 |
## Troubleshooting
- **Claude never calls the mem0 tools, even though the connector shows as connected**: Claude.ai ships its own native memory (Settings > Capabilities > Memory, on by default), which synthesizes a running summary from your chat history automatically. When both are active, Claude's system prompt tends to favor its built-in memory and rarely reaches for a third-party memory tool on its own. Ask explicitly ("search my mem0 memories for...", "save this to mem0") to force the tool call, or pause Claude's native memory (Settings > Capabilities > Memory > Pause) if you want Mem0 to be the primary memory store for that account.
- **"Connection failed" or the connector won't add**: Confirm the URL is exactly `https://mcp.mem0.ai/mcp/`. Custom connectors reach your MCP server from Anthropic's cloud, not your device, so a localhost URL will never work here.
- **No tools appearing after adding the connector**: Make sure you toggled **mem0** on for the current conversation under the **+ > Connectors** menu; adding a connector doesn't enable it in every chat automatically.
- **Can't add a second connector**: Free-tier accounts are capped at one custom connector; upgrade to Pro/Max/Team/Enterprise for more.
<CardGroup cols={2}>
<Card title="Mem0 MCP Setup" icon="puzzle-piece" href="/platform/mem0-mcp">
Detailed MCP configuration for all clients
</Card>
<Card title="Claude Code Integration" icon="/images/provider-icons/anthropic.svg" href="/integrations/claude-code">
Add Mem0 memory to Claude Code workflows
</Card>
</CardGroup>
<Snippet file="star-on-github.mdx" />
+29 -3
View File
@@ -91,12 +91,23 @@ To update, run `codex plugin marketplace upgrade` to pull the latest from the Me
After either option, start a new Codex task and ask: *"List my mem0 entities"* or *"Search my memories for hello"*. If the `mem0` tools appear and respond, you're all set.
</Info>
## Codex Cloud
[Codex Cloud](https://developers.openai.com/codex/cloud/environments) tasks run setup scripts and the agent in separate phases with different variable scoping:
- **Environment Variables** persist for the full duration of the task, through both the setup script and the agent phase.
- **Secrets** are only available to the setup script; they are wiped before the agent phase starts, so the agent itself cannot read them.
Because the `mem0` MCP server authenticates on every tool call the agent makes (not just during setup), set `MEM0_API_KEY` as an **Environment Variable** in your Codex Cloud environment configuration, not as a Secret. A Secret will let a setup script authenticate but the agent will lose access to `MEM0_API_KEY` once the task phase begins, breaking Mem0 MCP calls.
Lifecycle hooks that shell out to local scripts (Option A) are not applicable in Codex Cloud's ephemeral containers; use Option B (Direct MCP) with `MEM0_API_KEY` set as above.
## What's Included
| Component | Plugin Install | MCP Only |
|-----------|:--------------:|:--------:|
| MCP Server (9 memory tools) | Yes | Yes |
| Lifecycle Hooks | Yes | No |
| Lifecycle Hooks | Opt-in (see below) | No |
| Mem0 SDK Skill | Yes | No |
## Available MCP Tools
@@ -117,7 +128,22 @@ Once installed, the following tools are available in every Codex session:
## Lifecycle Hooks
When installed via the plugin marketplace, Mem0 hooks into Codex's lifecycle to automatically manage memory:
Unlike Claude Code, Codex has no plugin-host mechanism for auto-wiring hooks from an installed plugin: it only reads hooks from `~/.codex/hooks.json` (or `<repo>/.codex/hooks.json`). Installing the plugin (Option A) does **not** turn hooks on by itself. To enable them, run the bundled installer once against your local clone:
```bash
python3 <path-to-your-clone>/integrations/mem0-plugin/scripts/install_codex_hooks.py
```
This merges Mem0's entries into `~/.codex/hooks.json` and is idempotent (safe to re-run after upgrading). It also requires the `codex_hooks` feature flag in `~/.codex/config.toml`:
```toml
[features]
codex_hooks = true
```
The installer prints a reminder if the flag isn't set. Restart Codex after installing hooks or editing the config. To remove: `python3 .../install_codex_hooks.py --uninstall`.
Once enabled, Mem0 hooks into Codex's lifecycle to automatically manage memory:
| Hook | Event | What it does |
|------|-------|-------------|
@@ -155,7 +181,7 @@ You: Add WebSocket support for real-time notification delivery.
- **"Connection failed"**: Verify `MEM0_API_KEY` is set: `echo $MEM0_API_KEY`
- **No tools appearing**: Restart your Codex session after installation
- **Duplicate `mem0` MCP / "tool collision" errors**: You combined Option A with Option B. Remove the `[mcp_servers.mem0]` block from `~/.codex/config.toml`; the plugin registers it automatically
- **Hooks not firing**: Ensure the plugin is installed via the marketplace (Option A). MCP-only installs do not include hooks
- **Hooks not firing**: Hooks are opt-in and are not installed by the marketplace install itself. Run `scripts/install_codex_hooks.py` (see [Lifecycle Hooks](#lifecycle-hooks)), confirm `codex_hooks = true` is set under `[features]` in `~/.codex/config.toml`, and restart Codex. MCP-only installs (Option B) never include hooks
<CardGroup cols={2}>
<Card title="Mem0 MCP Setup" icon="puzzle-piece" href="/platform/mem0-mcp">
+38 -23
View File
@@ -39,6 +39,10 @@ os.environ["SERPER_API_KEY"] = "your-serper-api-key"
client = MemoryClient()
```
<Note>
Newer versions of CrewAI removed the `memory_config={"provider": "mem0"}` shortcut on `Crew(...)` that older guides referenced. CrewAI still offers a native Mem0 path through its `ExternalMemory` API, so that option remains open; check [CrewAI's memory documentation](https://docs.crewai.com/en/concepts/memory) for the shape your version expects. This guide wires Mem0 in explicitly through `MemoryClient` instead, which keeps retrieval under your control and stays valid as CrewAI's memory API changes.
</Note>
## Store User Preferences
Set up initial conversation and preferences storage:
@@ -69,9 +73,21 @@ messages = [
store_user_preferences("crew_user_1", messages)
```
## Retrieve Relevant Memories
Look up what Mem0 already knows about the user before planning a trip, so the crew's output reflects their actual preferences:
```python
def get_user_context(user_id: str, query: str) -> str:
"""Fetch relevant memories and format them for a task description"""
relevant_memories = client.search(query, filters={"user_id": user_id})
memories = [m["memory"] for m in relevant_memories.get("results", [])]
return "\n".join(f"- {memory}" for memory in memories)
```
## Create CrewAI Agent
Define an agent with memory capabilities:
Define an agent with search capabilities:
```python
def create_travel_agent():
@@ -83,61 +99,60 @@ def create_travel_agent():
goal="Plan personalized travel itineraries",
backstory="""You are a seasoned travel planner, known for your meticulous attention to detail.""",
allow_delegation=False,
memory=True,
tools=[search_tool],
)
```
## Define Tasks
Create tasks for your agent:
Create a task that folds the retrieved memories into its description, so the agent plans around the user's known preferences:
```python
def create_planning_task(agent, destination: str):
"""Create a travel planning task"""
def create_planning_task(agent, destination: str, user_context: str):
"""Create a travel planning task personalized with the user's stored preferences"""
return Task(
description=f"""Find places to live, eat, and visit in {destination}.""",
expected_output=f"A detailed list of places to live, eat, and visit in {destination}.",
description=f"""Find places to live, eat, and visit in {destination}.
Known preferences for this user:
{user_context or "No stored preferences yet."}
""",
expected_output=f"A detailed list of places to live, eat, and visit in {destination}, tailored to the user's preferences.",
agent=agent,
)
```
## Set Up Crew
Configure the crew with memory integration:
Configure the crew. Mem0 handles persistence outside of CrewAI, so the crew itself does not need `memory=True` or a `memory_config`:
```python
def setup_crew(agents: list, tasks: list):
"""Set up a crew with Mem0 memory integration"""
"""Set up a crew; memory is managed through Mem0, not CrewAI's memory_config"""
return Crew(
agents=agents,
tasks=tasks,
process=Process.sequential,
memory=True,
memory_config={
"provider": "mem0",
"config": {"user_id": "crew_user_1"},
}
)
```
## Main Execution Function
Implement the main function to run the travel planning system:
Implement the main function to run the travel planning system: retrieve context from Mem0, run the crew, then store the new conversation back:
```python
def plan_trip(destination: str, user_id: str):
# Create agent
travel_agent = create_travel_agent()
# Create task
planning_task = create_planning_task(travel_agent, destination)
# Setup crew
user_context = get_user_context(user_id, f"travel preferences for {destination}")
planning_task = create_planning_task(travel_agent, destination, user_context)
crew = setup_crew([travel_agent], [planning_task])
result = crew.kickoff()
# Execute and return results
return crew.kickoff()
client.add(
[{"role": "user", "content": f"Planned a trip to {destination}."}],
user_id=user_id,
)
return result
# Example usage
if __name__ == "__main__":
+89
View File
@@ -0,0 +1,89 @@
---
title: DeepSeek Harness
description: "Add persistent Mem0 memory to the DeepSeek Harness (Cordis) agent with two native tools: search and add."
---
Add persistent memory to the [**DeepSeek Harness**](https://github.com/deepseek-ai/deepseek-harness) with `@mem0/deepseek-plugin`. The Harness agent forgets everything between sessions. This plugin gives it two Mem0-backed tools so recall and writes persist across runs, sharing the same memory bank you already use from Claude Code, Codex, and other agents.
## Overview
The plugin registers two agent-callable tools:
| Tool | Does |
|---|---|
| `search_memory` | Recall facts from Mem0 relevant to a query |
| `add_memory` | Store a fact in Mem0 for future sessions |
Unlike file-based memory plugins, Mem0 is a managed backend: server-side extraction, semantic dedup, and conflict resolution, with the same memory reusable across every agent you connect.
## How it works
A Cordis plugin is a module exporting `apply(ctx, config)`. This one declares `inject = ['tools']` so it waits for the harness tool registry, then registers the two tools via `ctx.tools.register(...)`. When the plugin unmounts, the tools are removed automatically (Cordis revertible effects).
## Prerequisites
1. A Mem0 Platform account and API key:
- <a href="https://app.mem0.ai?utm_source=oss&utm_medium=integration-deepseek-plugin" rel="nofollow">Sign up at app.mem0.ai</a>
- <a href="https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=integration-deepseek-plugin" rel="nofollow">Get your API key</a> (starts with `m0-`)
2. The DeepSeek Harness installed.
3. Your API key exported in your shell:
<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>
## Try it locally
1. Build the plugin:
```sh
cd integrations/deepseek-plugin
pnpm install
pnpm build
```
2. Point the Harness at it. Copy `cordis.example.yml`, set the absolute path to `dist/index.js` and your `userId`, then load it:
```sh
pnpm dsh web --patch ./integrations/deepseek-plugin/cordis.example.yml
```
3. Open the web UI and ask the agent to remember something, then recall it in a later turn.
The `cordis.yml` entry looks like this:
```yaml
- name: "@deepseek-ai/dsh-system-prompt"
- name: "@deepseek-ai/dsh-tools"
- insert:
- id: mem0
name: "/absolute/path/to/integrations/deepseek-plugin/dist/index.js"
config:
# apiKey is read from MEM0_API_KEY when omitted here.
userId: "your-user-id"
# host: "https://your-onprem.mem0.ai" # optional: Platform on-prem / dedicated base URL
```
For a Mem0 Platform on-prem or dedicated deployment, point `config.host` at that base URL (defaults to `api.mem0.ai`). `host` is a Platform base-URL override, not a switch to self-hosted Mem0 OSS.
## Configuration
| Field | Required | Default | Notes |
|---|---|---|---|
| `apiKey` | no | `$MEM0_API_KEY` | Mem0 platform API key |
| `userId` | yes | | Default entity that owns the memories |
| `host` | no | `api.mem0.ai` | Platform base URL (on-prem / dedicated) |
Both tools also accept optional per-call `userId`, `agentId`, and `runId` params so a single install can partition memory by entity, agent, or session; when omitted they fall back to the configured `userId`.
## Telemetry
Writes are tagged `source="DEEPSEEK_HARNESS"` so Mem0's backend can attribute usage to this integration.
-17
View File
@@ -34,15 +34,11 @@ npx flowise start
2. In this example, we use the **Conversation Chain** template.
3. Replace the default **Buffer Memory** with **Mem0 Memory**.
![Flowise Memory Integration](https://raw.githubusercontent.com/FlowiseAI/FlowiseDocs/main/en/.gitbook/assets/mem0/flowise-flow.png)
### 2. Obtain Your Mem0 API Key
1. Navigate to the <a href="https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=integration-flowise" rel="nofollow">Mem0 API Key dashboard</a>.
2. Generate or copy your existing Mem0 API Key.
![Mem0 API Key](https://raw.githubusercontent.com/FlowiseAI/FlowiseDocs/main/en/.gitbook/assets/mem0/api-key.png)
### 3. Configure Mem0 Credentials
1. Enter the **Mem0 API Key** in the Mem0 Credentials section.
@@ -57,11 +53,6 @@ npx flowise start
}
```
<figure>
<img src="https://raw.githubusercontent.com/FlowiseAI/FlowiseDocs/main/en/.gitbook/assets/mem0/creds.png" alt="Mem0 Credentials" />
<figcaption>Configure API Credentials</figcaption>
</figure>
## Memory Features
### 1. Basic Memory Storage
@@ -72,8 +63,6 @@ Test your memory configuration:
2. Run a test chat and store some information
3. Verify the stored memories in the <a href="https://app.mem0.ai/dashboard/requests?utm_source=oss&utm_medium=integration-flowise" rel="nofollow">Mem0 Dashboard</a>
![Flowise Test Chat](https://raw.githubusercontent.com/FlowiseAI/FlowiseDocs/main/en/.gitbook/assets/mem0/flowise-chat-1.png)
### 2. Memory Retention
Validate memory persistence:
@@ -82,14 +71,10 @@ Validate memory persistence:
2. Ask a question about previously stored information
3. Confirm that the AI remembers the context
![Testing Memory Retention](https://raw.githubusercontent.com/FlowiseAI/FlowiseDocs/main/en/.gitbook/assets/mem0/flowise-chat-2.png)
## Advanced Configuration
### Memory Settings
![Mem0 Settings](https://raw.githubusercontent.com/FlowiseAI/FlowiseDocs/main/en/.gitbook/assets/mem0/settings.png)
Available settings include:
1. **Search Only Mode**: Enable memory retrieval without creating new memories
@@ -108,8 +93,6 @@ Additional settings available in <a href="https://app.mem0.ai/dashboard/project-
1. **Custom Instructions**: Define memory extraction rules
2. **Expiration Date**: Set automatic memory cleanup periods
![Mem0 Project Settings](https://raw.githubusercontent.com/FlowiseAI/FlowiseDocs/main/en/.gitbook/assets/mem0/mem0-settings.png)
## Best Practices
1. **User Identification**: Use consistent `user_id` values for reliable memory retrieval
+1 -1
View File
@@ -6,7 +6,7 @@ description: "Use Mem0 as a memory store in LlamaIndex with support for ReAct an
LlamaIndex supports Mem0 as a [memory store](https://llamahub.ai/l/memory/llama-index-memory-mem0). In this guide, we'll show you how to use it.
<Note type="info">
[**Mem0Memory**](https://docs.llamaindex.ai/en/stable/examples/memory/Mem0Memory/) now supports **ReAct** and **FunctionCalling** agents.
[**Mem0Memory**](https://developers.llamaindex.ai/python/examples/memory/mem0memory/) now supports **ReAct** and **FunctionCalling** agents.
</Note>
### Installation
+69
View File
@@ -0,0 +1,69 @@
---
title: Strands Agents
description: "Add persistent long-term memory to AWS Strands agents with Mem0, as a native MemoryStore that plugs into the agent loop."
---
Integrate [**Mem0**](https://github.com/mem0ai/mem0) with [Strands Agents](https://github.com/strands-agents/sdk-python), AWS's open-source SDK for building AI agents. The [`mem0-strands`](https://github.com/mem0ai/mem0/tree/main/integrations/mem0-strands) package ships a native `MemoryStore`, so recall and writes happen automatically inside the agent loop, not as tool calls the model has to remember.
## Overview
1. A `MemoryStore` the `MemoryManager` drives on every turn: it searches Mem0 and injects the results into the prompt, and writes memory back when extraction is enabled.
2. Server-side extraction: because the store implements `add_messages`, enabling `extraction` routes raw conversation turns to Mem0's own extraction pipeline, with no extra client-side model call.
3. Works with the hosted Mem0 Platform (an API key) or self-hosted Mem0 OSS (a config dict).
## Prerequisites
Before setting up Mem0 with Strands, ensure you have:
1. Installed the required packages:
```bash
pip install mem0-strands
```
2. A valid API key:
- <a href="https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=integration-strands" rel="nofollow">Mem0 API Key</a> (set as `MEM0_API_KEY`)
## Basic Integration Example
Hand a `Mem0MemoryStore` to a `MemoryManager`, and the agent gets automatic recall and memory writes:
```python
import os
from strands import Agent
from strands.memory import MemoryManager
from mem0_strands import Mem0MemoryStore
os.environ["MEM0_API_KEY"] = "your-mem0-api-key"
# extraction=True routes conversation turns to Mem0's server-side extraction.
store = Mem0MemoryStore(user_id="alex", extraction=True)
agent = Agent(memory_manager=MemoryManager(stores=[store]))
agent("Remember I use Neovim and deploy on Fridays.") # writes memory
print(agent("What editor do I use?")) # recalls it, injected automatically
```
Scope memories with any of `user_id`, `agent_id`, `run_id`, or `app_id` (`app_id` is platform-only). Pass `max_search_results` to bound how many memories are injected per turn.
## Self-hosted Mem0 (OSS)
To run against self-hosted Mem0 instead of the platform, pass a `config` dict:
```python
store = Mem0MemoryStore(
user_id="alex",
extraction=True,
config={
"vector_store": {"provider": "qdrant", "config": {"host": "localhost", "port": 6333}},
},
)
```
## Explicit memory tool
If you want the model to call memory explicitly instead of (or alongside) the automatic store, use the `mem0_memory` tool from `strands-agents-tools`. A store and the tool can share the same Mem0 backend and namespace.
## Learn more
- [mem0-strands on GitHub](https://github.com/mem0ai/mem0/tree/main/integrations/mem0-strands)
- [Strands Agents documentation](https://strandsagents.com)
+130
View File
@@ -0,0 +1,130 @@
---
title: Vercel
description: "Add persistent memory to your Vercel apps with the Mem0 Vercel Marketplace integration. Install from the Marketplace, get an API key, and start building."
---
The **Mem0 Vercel integration** provisions managed Mem0 access directly from the [Vercel Marketplace](https://vercel.com/marketplace). Installing it creates a Mem0 account for you and injects a `MEM0_API_KEY` into your Vercel project, so you can add long-term memory to your AI apps and agents in minutes, with no separate signup and billing handled through Vercel.
<Note type="info">
This is the managed Marketplace integration. If you instead want to use Mem0 with the Vercel AI SDK in code, see [Vercel AI SDK](/integrations/vercel-ai-sdk).
</Note>
## Overview
When you install Mem0 from the Vercel Marketplace:
- A Mem0 account and project are created for you (Vercel Native).
- Mem0 environment variables (`MEM0_API_KEY`, `MEM0_ORG_ID`, `MEM0_PROJECT_ID`, `MEM0_BASE_URL`) are added to your connected Vercel project.
- Billing is handled through your Vercel account, with no separate Mem0 subscription.
You call Mem0 from your app with the injected key. There is nothing else to wire up.
## Install
<Steps>
<Step title="Add the integration">
From the [Vercel Marketplace](https://vercel.com/marketplace), find **Mem0** and click **Install**. Choose **Create New Mem0 Account** (Vercel Native).
</Step>
<Step title="Pick a plan">
Select **Hobby** (free) or **Starter** ($20/month). You can change this anytime from the installation settings in your Vercel dashboard.
</Step>
<Step title="Connect a project">
Connect the Mem0 resource to a Vercel project. Mem0 injects its environment variables into that project.
</Step>
<Step title="Redeploy">
Redeploy so the variables are available at runtime, or run `vercel env pull` to use them locally.
</Step>
</Steps>
## Environment variables
The integration sets the following on your connected project:
| Variable | Description |
| --- | --- |
| `MEM0_API_KEY` | Your Mem0 API key, scoped to the org and project created for this installation. This is what you authenticate with. |
| `MEM0_ORG_ID` | The Mem0 organization id for this installation. |
| `MEM0_PROJECT_ID` | The Mem0 project id for this installation. |
| `MEM0_BASE_URL` | The Mem0 API base URL (`https://api.mem0.ai`). |
The API key is scoped to your org and project, so the SDK needs only `MEM0_API_KEY` to get started.
## Quickstart
Install the SDK:
<CodeGroup>
```bash npm
npm install mem0ai
```
```bash pip
pip install mem0ai
```
</CodeGroup>
Add and search memories using the injected key:
<CodeGroup>
```typescript JavaScript
import MemoryClient from "mem0ai";
const memory = new MemoryClient({ apiKey: process.env.MEM0_API_KEY });
// Store a memory
await memory.add(
[{ role: "user", content: "I'm a vegetarian and allergic to nuts." }],
{ userId: "user123" },
);
// Retrieve relevant memories
const results = await memory.search("What are my dietary restrictions?", {
filters: { user_id: "user123" },
});
console.log(results);
```
```python Python
import os
from mem0 import MemoryClient
memory = MemoryClient(api_key=os.environ["MEM0_API_KEY"])
# Store a memory
memory.add(
[{"role": "user", "content": "I'm a vegetarian and allergic to nuts."}],
user_id="user123",
)
# Retrieve relevant memories
results = memory.search("What are my dietary restrictions?", filters={"user_id": "user123"})
print(results)
```
</CodeGroup>
See the [platform quickstart](/platform/quickstart) for the full API.
## Plans and billing
| Plan | Price | Memories | Retrieval calls | Projects | Support |
| --- | --- | --- | --- | --- | --- |
| Hobby | Free | 10,000 | 1,000 / month | 1 | Community |
| Starter | $20 / month | 50,000 | 5,000 / month | 1 | Community |
Billing runs through your Vercel account; Mem0 never charges your card directly. You can upgrade or downgrade anytime from the installation's settings in the Vercel dashboard.
## Managing the integration
- **Change plan:** open the installation in your Vercel dashboard and update the billing plan.
- **Open in Mem0:** use the **Open in Mem0** link to sign in to the Mem0 dashboard for this installation.
- **Uninstall:** removing the integration deactivates the API key and tears down the resource. Any usage for the current period is invoiced before removal.
## Notes
- The integration creates a **new** Mem0 account for the installation. Linking an existing Mem0 account is not supported yet.
- The injected `MEM0_API_KEY` is scoped to the org and project created for this installation, so you do not manage keys by hand.
## Related
- [Platform quickstart](/platform/quickstart)
- [Vercel AI SDK integration](/integrations/vercel-ai-sdk)
+54 -42
View File
@@ -56,8 +56,8 @@ client.add(
)
# Read
client.search("What does Alice like to do?", user_id="alice")
client.get_all(user_id="alice")
client.search("What does Alice like to do?", filters={"user_id": "alice"})
client.get_all(filters={"user_id": "alice"})
client.get(memory_id="<id>")
# Update
@@ -86,8 +86,8 @@ await client.add(
);
// Read
await client.search("What does Alice like to do?", { user_id: "alice" });
await client.getAll({ user_id: "alice" });
await client.search("What does Alice like to do?", { filters: { user_id: "alice" } });
await client.getAll({ filters: { user_id: "alice" } });
await client.get("<memory_id>");
// Update
@@ -113,8 +113,8 @@ m = Memory() # needs OPENAI_API_KEY; see components/ for custom providers
m.add("I love hiking on weekends", user_id="alice")
# Read
m.search("What does Alice like to do?", user_id="alice")
m.get_all(user_id="alice")
m.search("What does Alice like to do?", filters={"user_id": "alice"})
m.get_all(filters={"user_id": "alice"})
m.get(memory_id="<id>")
# Update
@@ -140,8 +140,8 @@ const memory = new Memory();
await memory.add("I love hiking on weekends", { userId: "alice" });
// Read
await memory.search("What does Alice like to do?", { userId: "alice" });
await memory.getAll({ userId: "alice" });
await memory.search("What does Alice like to do?", { filters: { user_id: "alice" } });
await memory.getAll({ filters: { user_id: "alice" } });
await memory.get("<memory_id>");
// Update
@@ -164,7 +164,7 @@ npm list mem0ai --depth 0 2>/dev/null | grep mem0ai
mem0 --version # Python or Node CLI, whichever is on PATH
```
If the user is on a pre-current major (Python < 2, TS < 3, or Platform `output_format: "v1.1"`), route them through the matching migration guide in the Platform section before quoting current docs. If no Mem0 package is installed, recommend `pip install mem0ai` or `npm install mem0ai` and the corresponding quickstart above.
If the user is on a pre-current major (Python < 2, TS < 3, or a Platform call still passing `output_format`, `api_version`, `async_mode`, or `enable_graph`, all removed in the current major), route them through the matching migration guide in the Platform section before quoting current docs. If no Mem0 package is installed, recommend `pip install mem0ai` or `npm install mem0ai` and the corresponding quickstart above.
## Getting Started
@@ -185,7 +185,7 @@ If the user is on a pre-current major (Python < 2, TS < 3, or Platform `output_f
## Core Concepts
- [How Mem0 Works](https://docs.mem0.ai/core-concepts/how-it-works) [Both]: Use when explaining the end-to-end pipeline: extraction (ADD-only distillation), storage across vector/entity/history stores, and multi-signal retrieval.
- [Memory Types](https://docs.mem0.ai/core-concepts/memory-types) [Both]: Use when explaining working, factual, episodic, and semantic memory distinctions.
- [Memory Types](https://docs.mem0.ai/core-concepts/memory-types) [Both]: Use when checking which `memory_type` values actually work: `procedural_memory` is implemented, `semantic_memory` and `episodic_memory` are defined in the enum but rejected by validation.
- [Memory Operations - Add](https://docs.mem0.ai/core-concepts/memory-operations/add) [Both]: Use when explaining how `add()` extracts facts, resolves conflicts, and writes to both stores.
- [Memory Operations - Search](https://docs.mem0.ai/core-concepts/memory-operations/search) [Both]: Use when explaining how queries are processed and ranked.
- [Memory Operations - Update](https://docs.mem0.ai/core-concepts/memory-operations/update) [Both]: Use when memories need to be edited in place or reconciled against new info.
@@ -235,7 +235,6 @@ If the user is on a pre-current major (Python < 2, TS < 3, or Platform `output_f
- [Open Source Features Overview](https://docs.mem0.ai/open-source/features/overview) [OSS]: Use when surveying OSS-only capabilities.
- [Metadata Filtering](https://docs.mem0.ai/open-source/features/metadata-filtering) [OSS]: Use when filtering by custom metadata fields in self-hosted.
- [Reranker Search](https://docs.mem0.ai/open-source/features/reranker-search) [OSS]: Use when improving OSS search quality with a reranker.
- [Reranking](https://docs.mem0.ai/open-source/features/reranking) [OSS]: Use when configuring reranking end-to-end in OSS.
- [Async Memory](https://docs.mem0.ai/open-source/features/async-memory) [OSS]: Use when the self-hosted app needs `AsyncMemory`.
- [OSS Multimodal Support (features)](https://docs.mem0.ai/open-source/features/multimodal-support) [OSS]: Use when handling images and PDFs self-hosted (feature guide).
- [Custom Instructions (OSS)](https://docs.mem0.ai/open-source/features/custom-instructions) [OSS]: Use when tailoring extraction prompts in OSS.
@@ -247,46 +246,50 @@ If the user is on a pre-current major (Python < 2, TS < 3, or Platform `output_f
- [Integrations Overview](https://docs.mem0.ai/integrations) [Both]: Use when surveying every available integration.
### Agent Frameworks
- [LangChain](https://docs.mem0.ai/integrations/langchain) [Both]: Use when the user is on LangChain.
- [LangGraph](https://docs.mem0.ai/integrations/langgraph) [Both]: Use when building stateful multi-actor LangGraph apps.
- [LangChain Tools](https://docs.mem0.ai/integrations/langchain-tools) [Both]: Use when Mem0 should be exposed as a LangChain tool.
- [LangChain](https://docs.mem0.ai/integrations/langchain) [Platform]: Use when the user is on LangChain.
- [LangGraph](https://docs.mem0.ai/integrations/langgraph) [Platform]: Use when building stateful multi-actor LangGraph apps.
- [LangChain Tools](https://docs.mem0.ai/integrations/langchain-tools) [Platform]: Use when Mem0 should be exposed as a LangChain tool.
- [LlamaIndex](https://docs.mem0.ai/integrations/llama-index) [Both]: Use when layering memory on a LlamaIndex RAG app.
- [CrewAI](https://docs.mem0.ai/integrations/crewai) [Both]: Use when building CrewAI multi-agent systems.
- [AutoGen](https://docs.mem0.ai/integrations/autogen) [Both]: Use when the user is on Microsoft AutoGen.
- [Agno](https://docs.mem0.ai/integrations/agno) [Both]: Use when the user is on Agno.
- [CrewAI](https://docs.mem0.ai/integrations/crewai) [Platform]: Use when building CrewAI multi-agent systems.
- [AutoGen](https://docs.mem0.ai/integrations/autogen) [Platform]: Use when the user is on Microsoft AutoGen.
- [Agno](https://docs.mem0.ai/integrations/agno) [Platform]: Use when the user is on Agno.
- [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.
- [ChatDev](https://docs.mem0.ai/integrations/chatdev) [Platform]: Use when the user is on ChatDev.
- [Hermes](https://docs.mem0.ai/integrations/hermes) [Both]: Use when the user is on Hermes.
- [Pi Agent](https://docs.mem0.ai/integrations/pi-agent) [Platform]: Use when adding persistent memory to Pi Agent with the Mem0 plugin.
- [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).
- [DeepSeek Harness](https://docs.mem0.ai/integrations/deepseek-plugin) [Platform]: Use when adding persistent memory to the DeepSeek Harness (Cordis) agent via the Mem0 plugin.
- [OpenAI Agents SDK](https://docs.mem0.ai/integrations/openai-agents-sdk) [Platform]: Use when the user is on the OpenAI Agents SDK.
- [Google AI ADK](https://docs.mem0.ai/integrations/google-ai-adk) [Platform]: Use when the user is on Google's Agent Development Kit.
- [Mastra](https://docs.mem0.ai/integrations/mastra) [Platform]: Use when the user is on Mastra (TypeScript).
- [OpenClaw](https://docs.mem0.ai/integrations/openclaw) [Both]: Use when wiring Mem0 into Claude Code or editors via OpenClaw.
- [Vercel AI SDK](https://docs.mem0.ai/integrations/vercel-ai-sdk) [Both]: Use when the user is on the Vercel AI SDK.
- [Vercel](https://docs.mem0.ai/integrations/vercel) [Platform]: Use when deploying on Vercel and installing Mem0 from the Vercel Marketplace.
- [Strands Agents](https://docs.mem0.ai/integrations/strands) [Both]: Use when the user is on AWS Strands and wants a native MemoryStore.
### AI Coding Tools
- [Claude Code](https://docs.mem0.ai/integrations/claude-code) [Both]: Use when wiring memory into Claude Code.
- [Cursor](https://docs.mem0.ai/integrations/cursor) [Both]: Use when wiring memory into Cursor.
- [Codex](https://docs.mem0.ai/integrations/codex) [Both]: Use when wiring memory into Codex / other editor assistants.
- [OpenCode](https://docs.mem0.ai/integrations/opencode) [Both]: Use when wiring memory into OpenCode.
- [Antigravity](https://docs.mem0.ai/integrations/antigravity) [Both]: Use when wiring memory into Google Antigravity.
- [Claude.ai](https://docs.mem0.ai/integrations/claude-ai) [Platform]: Use when connecting Mem0 to Claude.ai (the hosted web app) via a custom remote MCP connector, or when Claude's native memory seems to be crowding out mem0 tool calls.
- [Cursor](https://docs.mem0.ai/integrations/cursor) [Platform]: Use when wiring memory into Cursor.
- [Codex](https://docs.mem0.ai/integrations/codex) [Platform]: Use when wiring memory into Codex / other editor assistants.
- [OpenCode](https://docs.mem0.ai/integrations/opencode) [Platform]: Use when wiring memory into OpenCode.
- [Antigravity](https://docs.mem0.ai/integrations/antigravity) [Platform]: Use when wiring memory into Google Antigravity.
### Voice & Real-time
- [LiveKit](https://docs.mem0.ai/integrations/livekit) [Both]: Use when building real-time voice/video with memory.
- [Pipecat](https://docs.mem0.ai/integrations/pipecat) [Both]: Use when the voice pipeline is Pipecat.
- [ElevenLabs](https://docs.mem0.ai/integrations/elevenlabs) [Both]: Use when voice synthesis uses ElevenLabs.
- [LiveKit](https://docs.mem0.ai/integrations/livekit) [Platform]: Use when building real-time voice/video with memory.
- [Pipecat](https://docs.mem0.ai/integrations/pipecat) [Platform]: Use when the voice pipeline is Pipecat.
- [ElevenLabs](https://docs.mem0.ai/integrations/elevenlabs) [Platform]: Use when voice synthesis uses ElevenLabs.
### Cloud & Infrastructure
- [AWS Bedrock](https://docs.mem0.ai/integrations/aws-bedrock) [Both]: Use when the user is on AWS Bedrock managed AI services.
- [AWS Bedrock](https://docs.mem0.ai/integrations/aws-bedrock) [OSS]: Use when the user is on AWS Bedrock managed AI services.
### Developer Tools
- [Dify](https://docs.mem0.ai/integrations/dify) [Both]: Use when the user is on Dify LLMOps.
- [Flowise](https://docs.mem0.ai/integrations/flowise) [Both]: Use when the user is on Flowise no-code.
- [Dify](https://docs.mem0.ai/integrations/dify) [Platform]: Use when the user is on Dify LLMOps.
- [Flowise](https://docs.mem0.ai/integrations/flowise) [Platform]: Use when the user is on Flowise no-code.
- [n8n](https://docs.mem0.ai/integrations/n8n) [Both]: Use when the user builds workflows or AI agents in n8n.
- [Zapier](https://docs.mem0.ai/integrations/zapier) [Both]: Use when the user automates workflows with Zapier.
- [AgentOps](https://docs.mem0.ai/integrations/agentops) [Both]: Use when tracking agent observability with memory metadata.
- [Respan](https://docs.mem0.ai/integrations/respan) [Both]: Use when monitoring Mem0 with Respan (formerly Keywords AI) LLM observability.
- [Raycast](https://docs.mem0.ai/integrations/raycast) [Both]: Use when the user wants quick memory access via Raycast.
- [Respan](https://docs.mem0.ai/integrations/respan) [OSS]: Use when monitoring Mem0 with Respan (formerly Keywords AI) LLM observability.
- [Raycast](https://docs.mem0.ai/integrations/raycast) [Platform]: Use when the user wants quick memory access via Raycast.
## Cookbooks
@@ -295,7 +298,6 @@ If the user is on a pre-current major (Python < 2, TS < 3, or Platform `output_f
### Essentials
- [Building an AI Companion](https://docs.mem0.ai/cookbooks/essentials/building-ai-companion) [Both]: Use when starting a companion app from scratch.
- [Partition Memories by Entity](https://docs.mem0.ai/cookbooks/essentials/entity-partitioning-playbook) [Both]: Use when isolating multi-tenant memories.
- [Controlling Memory Ingestion](https://docs.mem0.ai/cookbooks/essentials/controlling-memory-ingestion) [Both]: Use when deciding what to store and what to skip.
- [Tagging and Organizing Memories](https://docs.mem0.ai/cookbooks/essentials/tagging-and-organizing-memories) [Both]: Use when memory taxonomy matters.
- [Exporting Memories](https://docs.mem0.ai/cookbooks/essentials/exporting-memories) [Both]: Use when backing up or migrating memory data.
@@ -318,10 +320,10 @@ If the user is on a pre-current major (Python < 2, TS < 3, or Platform `output_f
### Integration Examples
- [Agents SDK Tool](https://docs.mem0.ai/cookbooks/integrations/agents-sdk-tool) [Platform]: Use when exposing Mem0 as a tool in OpenAI Agents SDK.
- [OpenAI Tool Calls](https://docs.mem0.ai/cookbooks/integrations/openai-tool-calls) [Platform]: Use when hooking Mem0 into OpenAI function calling.
- [Mastra Agent](https://docs.mem0.ai/cookbooks/integrations/mastra-agent) [Both]: Use when the agent is built in Mastra.
- [Healthcare Google ADK](https://docs.mem0.ai/cookbooks/integrations/healthcare-google-adk) [Both]: Use when the domain is medical and the framework is Google ADK.
- [AWS Bedrock](https://docs.mem0.ai/cookbooks/integrations/aws-bedrock) [Both]: Use when deploying with AWS managed model services.
- [Tavily Search](https://docs.mem0.ai/cookbooks/integrations/tavily-search) [Both]: Use when the agent layers web search on memory.
- [Mastra Agent](https://docs.mem0.ai/cookbooks/integrations/mastra-agent) [Platform]: Use when the agent is built in Mastra.
- [Healthcare Google ADK](https://docs.mem0.ai/cookbooks/integrations/healthcare-google-adk) [Platform]: Use when the domain is medical and the framework is Google ADK.
- [AWS Bedrock](https://docs.mem0.ai/cookbooks/integrations/aws-bedrock) [OSS]: Use when deploying with AWS managed model services.
- [Tavily Search](https://docs.mem0.ai/cookbooks/integrations/tavily-search) [Platform]: Use when the agent layers web search on memory.
### Framework Examples
- [LlamaIndex React](https://docs.mem0.ai/cookbooks/frameworks/llamaindex-react) [Both]: Use when building a React UI with LlamaIndex and memory.
@@ -381,6 +383,16 @@ All API Reference docs describe Mem0 Platform REST endpoints (requires API key).
- [Remove Project Member](https://docs.mem0.ai/api-reference/project/remove-project-member) [Platform]: Use when removing a member from a project.
- [Delete Project](https://docs.mem0.ai/api-reference/project/delete-project) [Platform]: Use when removing a project.
### Dream
- [Get Dream Configuration](https://docs.mem0.ai/api-reference/dream/get-dream-config) [Platform]: Use when reading a project's Dream (memory synthesis) config and plan entitlements.
- [Update Dream Configuration](https://docs.mem0.ai/api-reference/dream/update-dream-config) [Platform]: Use when enabling/disabling Synthesis or changing its mode for a project.
- [Get Dream Stats](https://docs.mem0.ai/api-reference/dream/get-dream-stats) [Platform]: Use when fetching lifecycle and synthesis counts for the Dream dashboard.
- [Get Dream Activity](https://docs.mem0.ai/api-reference/dream/get-dream-activity) [Platform]: Use when listing recent supersede/merge activity for a project.
- [Get Dream Synthesis Runs](https://docs.mem0.ai/api-reference/dream/get-dream-runs) [Platform]: Use when listing synthesis runs and the memories they generated.
- [Get Memories in a Dream Run](https://docs.mem0.ai/api-reference/dream/get-dream-run-memories) [Platform]: Use when paging the synthesized memories within a single run.
- [Get a Synthesized Memory's Sources](https://docs.mem0.ai/api-reference/dream/get-dream-memory-sources) [Platform]: Use when tracing the source memories a synthesized memory was distilled from.
- [Preview Dream Scope](https://docs.mem0.ai/api-reference/dream/dream-preview) [Platform]: Use when previewing what Synthesis would analyze for a project (no writes).
### Webhooks
- [Create Webhook](https://docs.mem0.ai/api-reference/webhook/create-webhook) [Platform]: Use when registering a webhook endpoint.
- [Get Webhook](https://docs.mem0.ai/api-reference/webhook/get-webhook) [Platform]: Use when fetching webhook config.
@@ -410,10 +422,10 @@ The `integrations/mem0-plugin/` directory provides MCP server connection, lifecy
Editor-specific setup docs (already listed above under `## Integrations > AI Coding Tools`):
- `integrations/claude-code` [Both]
- `integrations/cursor` [Both]
- `integrations/codex` [Both]
- `integrations/opencode` [Both]
- `integrations/antigravity` [Both]
- `integrations/cursor` [Platform]
- `integrations/codex` [Platform]
- `integrations/opencode` [Platform]
- `integrations/antigravity` [Platform]
- `integrations/openclaw` [Both]
### MCP Endpoints
@@ -6,6 +6,10 @@ icon: "filter"
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.
<Info>
This page covers the self-hosted `Memory` / `AsyncMemory` grammar. Sibling top-level keys are implicitly ANDed on both self-hosted and the hosted Platform API, so a flat filter like `{"user_id": "alice", "category": "work"}` works without wrapping it in `AND` on either. Two real differences remain: the `nin` operator documented below is not part of the Platform contract, and Platform validates every top-level key against a fixed allow-list, rejecting anything else with a 400. See [Memory Filters (Platform)](/platform/features/v2-memory-filters) for the hosted grammar.
</Info>
---
## Feature anatomy
@@ -19,7 +23,7 @@ Enhanced metadata filtering in Mem0 lets you run complex queries across memory m
| `lt` / `lte` | Less than / less than or equal | Cap numeric values (e.g., ratings, timestamps). |
| `in` / `nin` | In list / not in list | Pre-approve or block sets of values without chaining multiple filters. |
| `contains` / `icontains` | Case-sensitive / case-insensitive substring match | Scan text fields for keywords. |
| `*` | Wildcard | Require that a field exists, regardless of value. |
| `*` | Wildcard | Match regardless of value. Exact semantics (field-must-exist vs. no-op) vary by vector store, see [Wildcard matching](#wildcard-matching). |
| `AND` / `OR` / `NOT` | Combine filters | Build logic trees so multiple conditions work together. |
</Accordion>
</AccordionGroup>
@@ -111,7 +115,7 @@ results = m.search(
### Wildcard matching
Allow any value for a field while still requiring the field to exist: handy when the mere presence of a field matters.
Match any value for a field, handy when the mere presence of a field matters.
```python
# Match any value for a field
@@ -124,10 +128,18 @@ results = m.search(
)
```
<Warning>
Wildcard semantics differ by vector store. pgvector requires the key to exist in the payload (a real "field exists" check). Qdrant and Chroma have no native "field exists" filter, so `*` is a no-op there: the field condition is dropped and every record passes, including ones where the field is missing entirely. Do not rely on `*` to exclude records with a missing field unless you have confirmed your store's behavior in `mem0/vector_stores/<provider>.py`.
</Warning>
### Logical combinations
Combine filters with `AND`, `OR`, and `NOT` to express complex decision trees. Nest logical operators to encode multi-branch workflows.
<Warning>
The examples on this page use `search()`. `get_all()` accepts the same entity and comparison-operator filters, but the `AND` / `OR` / `NOT` wrapper is only translated at the `search()` layer before it reaches the vector store. Whether it also works on `get_all()` depends on your vector store: Qdrant recognizes raw `AND` / `OR` / `NOT` keys natively, but pgvector does not, so a logical wrapper passed to `get_all()` on pgvector silently matches nothing. Stick to `search()` for logical trees, or use plain sibling keys (implicitly ANDed) with `get_all()`.
</Warning>
```python
# Logical AND
results = m.search(
@@ -245,25 +257,18 @@ avoid_filters = {
When you reorder filters so indexed fields come first (`good_filters` example), queries typically return faster than the `avoid_filters` pattern where expensive text searches run before simple checks.
</Info>
Vector store support varies. Confirm operator coverage before shipping:
Vector store support varies widely. This table reflects what each provider's filter-translation code (`mem0/vector_stores/<provider>.py`) actually implements, confirm before shipping if you use a store not listed:
<AccordionGroup>
<Accordion title="Qdrant">
Full comparison, list, and logical support. Handles deeply nested boolean logic efficiently.
</Accordion>
<Accordion title="Chroma">
Equality and basic comparisons only. Limited nesting: break large trees into smaller calls.
</Accordion>
<Accordion title="Pinecone">
Comparisons plus `in`/`nin`. Text operators are constrained; rely on tags where possible.
</Accordion>
<Accordion title="Weaviate">
Full operator coverage with advanced text filters. Best option when you need hybrid text + metadata queries.
</Accordion>
</AccordionGroup>
| Store | `eq`/`ne`/`gt`/`gte`/`lt`/`lte`/`in`/`nin` | `contains`/`icontains` | `AND`/`OR`/`NOT` | `*` wildcard |
| --- | --- | --- | --- | --- |
| Qdrant | Full support (`in`/`nin` must be a list, or the Qdrant client raises a validation error) | Yes | Yes, nested | No-op: matches every record regardless of whether the field is present |
| pgvector | Full support (`in`/`nin` must be a list, or the call raises `ValueError`) | Yes, via SQL `LIKE`/`ILIKE` | Yes, nested | Requires the key to exist in the payload |
| Chroma | Full support | No: silently falls back to equality | Yes, one level of nesting | No-op: the filter is dropped |
| Pinecone | Full support | Not implemented | Not implemented: filters are only ANDed field-by-field | Not implemented: `"*"` is matched as the literal string `"*"`, not a wildcard |
| Weaviate | Not implemented: only `user_id`, `agent_id` and `run_id` are filtered on, as exact equality. Every other key, including all metadata, is silently dropped | Not implemented | Not implemented: only an implicit AND across the three supported keys | Not implemented |
<Warning>
If an operator is unsupported, most stores silently ignore that branch. Add validation before execution so you can fall back to simpler queries instead of returning empty results.
If an operator is unsupported, most stores silently ignore it or fall back to equality rather than raising an error. Test filters against your actual store instead of assuming operator parity with Qdrant.
</Warning>
### Migrate from earlier filters
@@ -427,4 +432,7 @@ except ValueError as e:
<Card title="Tag and Organize Memories" icon="tag" href="/cookbooks/essentials/tagging-and-organizing-memories">
Practice building workflows that label and retrieve memories with clear metadata filters.
</Card>
<Card title="Memory Filters (Platform)" icon="cloud" href="/platform/features/v2-memory-filters">
Using the hosted API instead? See the allow-listed top-level fields, the missing `nin` operator, and Platform-only fields like `created_at` and `memory_ids`.
</Card>
</CardGroup>
@@ -53,7 +53,7 @@ const memory = new Memory({
});
const results = await memory.search("What are my food preferences?", {
filters: { userId: "alice" },
filters: { user_id: "alice" },
rerank: true,
});
```
@@ -86,7 +86,7 @@ const memory = new Memory({
});
const results = await memory.search("What movies do I like?", {
filters: { userId: "alice" },
filters: { user_id: "alice" },
rerank: true,
});
```
@@ -108,7 +108,7 @@ const memory = new Memory({
});
const results = await memory.search("What movies do I like?", {
filters: { userId: "alice" },
filters: { user_id: "alice" },
rerank: true,
});
```
-6
View File
@@ -1,6 +0,0 @@
---
title: Reranking
description: 'Redirect to the canonical reranker-enhanced search guide.'
---
<Redirect href="/open-source/features/reranker-search" />
+1 -1
View File
@@ -43,7 +43,7 @@ await memory.add(messages, { userId: "alice", metadata: { category: "movie_recom
<Step title="Search memories">
```ts
const results = await memory.search("What do you know about me?", { filters: { userId: "alice" } });
const results = await memory.search("What do you know about me?", { filters: { user_id: "alice" } });
console.log(results);
```
+863
View File
@@ -7207,6 +7207,869 @@
}
]
}
},
"/api/v1/orgs/organizations/{org_id}/projects/{project_id}/dream/config/": {
"get": {
"tags": [
"dream"
],
"summary": "Get Dream configuration",
"description": "Retrieve the project's Dream (memory synthesis) configuration together with the plan entitlement snapshot. Dream automatically supersedes/merges memories on the add path; **Synthesis** (reflection) is the opt-in part controlled by `reflection_enabled`.",
"operationId": "get_dream_config",
"parameters": [
{
"name": "org_id",
"in": "path",
"required": true,
"description": "Unique identifier of the organization.",
"schema": {
"type": "string"
}
},
{
"name": "project_id",
"in": "path",
"required": true,
"description": "Unique identifier of the project.",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Successful response.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"config": {
"type": "object",
"properties": {
"reflection_enabled": {
"type": "boolean",
"description": "Whether background synthesis (reflection) is enabled for the project."
},
"reflection_enabled_at": {
"type": "string",
"description": "When synthesis was last enabled. Synthesis only considers memories created on or after this time (forward-only).",
"format": "date-time",
"nullable": true
},
"reflection_mode": {
"type": "string",
"description": "Synthesis execution mode.",
"enum": [
"batch",
"direct"
]
}
}
},
"entitlements": {
"type": "object",
"properties": {
"plan": {
"type": "string",
"description": "Resolved billing tier for the project (e.g. `free`, `pro`, `custom`)."
},
"reflection_interval_days": {
"type": "integer",
"description": "How often synthesis runs for this plan, in days (Pro weekly, Enterprise daily)."
},
"features": {
"type": "object",
"properties": {},
"description": "Per-feature entitlement snapshot, keyed by Dream feature (e.g. `dream_reflection`, `dream_tab`, `dream_preview`).",
"additionalProperties": {
"type": "object",
"properties": {
"entitled": {
"type": "boolean",
"description": "Whether the plan is entitled to the feature."
},
"killed": {
"type": "boolean",
"description": "Whether the feature is globally disabled by a kill switch."
},
"enabled": {
"type": "boolean",
"description": "Effective state: entitled AND not killed AND turned on."
}
}
}
}
}
}
}
}
}
}
}
}
},
"patch": {
"tags": [
"dream"
],
"summary": "Update Dream configuration",
"description": "Enable or disable Synthesis (reflection) for the project, or change the reflection mode. Requires a Pro or Enterprise (CUSTOM) plan and organization-owner permission. Synthesis is forward-only: after enabling, it only synthesizes memories added from that point on.",
"operationId": "update_dream_config",
"parameters": [
{
"name": "org_id",
"in": "path",
"required": true,
"description": "Unique identifier of the organization.",
"schema": {
"type": "string"
}
},
{
"name": "project_id",
"in": "path",
"required": true,
"description": "Unique identifier of the project.",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"reflection_enabled": {
"type": "boolean",
"description": "Turn background synthesis on or off."
},
"reflection_mode": {
"type": "string",
"description": "Synthesis execution mode.",
"enum": [
"batch",
"direct"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "Updated configuration.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"config": {
"type": "object",
"properties": {
"reflection_enabled": {
"type": "boolean",
"description": "Whether background synthesis (reflection) is enabled for the project."
},
"reflection_enabled_at": {
"type": "string",
"description": "When synthesis was last enabled. Synthesis only considers memories created on or after this time (forward-only).",
"format": "date-time",
"nullable": true
},
"reflection_mode": {
"type": "string",
"description": "Synthesis execution mode.",
"enum": [
"batch",
"direct"
]
}
}
},
"entitlements": {
"type": "object",
"properties": {
"plan": {
"type": "string",
"description": "Resolved billing tier for the project (e.g. `free`, `pro`, `custom`)."
},
"reflection_interval_days": {
"type": "integer",
"description": "How often synthesis runs for this plan, in days (Pro weekly, Enterprise daily)."
},
"features": {
"type": "object",
"properties": {},
"description": "Per-feature entitlement snapshot, keyed by Dream feature (e.g. `dream_reflection`, `dream_tab`, `dream_preview`).",
"additionalProperties": {
"type": "object",
"properties": {
"entitled": {
"type": "boolean",
"description": "Whether the plan is entitled to the feature."
},
"killed": {
"type": "boolean",
"description": "Whether the feature is globally disabled by a kill switch."
},
"enabled": {
"type": "boolean",
"description": "Effective state: entitled AND not killed AND turned on."
}
}
}
}
}
}
}
}
}
}
},
"400": {
"description": "Validation error (e.g. a field the plan is not entitled to change)."
},
"403": {
"description": "Not entitled on the current plan (`upgrade_required: true`), or the caller is not an organization owner."
}
}
}
},
"/api/v1/orgs/organizations/{org_id}/projects/{project_id}/dream/stats/": {
"get": {
"tags": [
"dream"
],
"summary": "Get Dream stats",
"description": "Lifecycle + synthesis counts for the project's Dream dashboard, plus reflection freshness. Requires a Pro or Enterprise (CUSTOM) plan.",
"operationId": "get_dream_stats",
"parameters": [
{
"name": "org_id",
"in": "path",
"required": true,
"description": "Unique identifier of the organization.",
"schema": {
"type": "string"
}
},
{
"name": "project_id",
"in": "path",
"required": true,
"description": "Unique identifier of the project.",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Successful response.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"active": {
"type": "integer",
"description": "Active (current) memory count."
},
"merged": {
"type": "integer",
"description": "Count of memories merged into a canonical one."
},
"superseded": {
"type": "integer",
"description": "Count of memories superseded by a newer one."
},
"synthesized": {
"type": "integer",
"description": "Count of higher-order pattern memories created by synthesis."
},
"last_run_at": {
"type": "string",
"description": "When synthesis last completed.",
"format": "date-time",
"nullable": true
},
"processed_through": {
"type": "string",
"description": "Timestamp of the newest source memory synthesis has processed.",
"format": "date-time",
"nullable": true
},
"running": {
"type": "boolean",
"description": "Whether a synthesis run is currently in flight."
},
"next_run_at": {
"type": "string",
"description": "Projected next synthesis run.",
"format": "date-time",
"nullable": true
}
}
}
}
}
},
"403": {
"description": "Not entitled on the current plan (`upgrade_required: true`)."
}
}
}
},
"/api/v1/orgs/organizations/{org_id}/projects/{project_id}/dream/activity/": {
"get": {
"tags": [
"dream"
],
"summary": "Get Dream activity",
"description": "Supersede/merge activity feed (newest first), keyset-paginated. Synthesis output is not included here — see the runs endpoint.",
"operationId": "get_dream_activity",
"parameters": [
{
"name": "org_id",
"in": "path",
"required": true,
"description": "Unique identifier of the organization.",
"schema": {
"type": "string"
}
},
{
"name": "project_id",
"in": "path",
"required": true,
"description": "Unique identifier of the project.",
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"required": false,
"description": "Page size (default 50, max 200).",
"schema": {
"type": "integer",
"default": 50
}
},
{
"name": "cursor",
"in": "query",
"required": false,
"description": "Opaque keyset cursor from a previous page's `next_cursor`.",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Successful response.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"type": "object",
"properties": {
"memory_id": {
"type": "string",
"description": "The superseded/merged memory's ID."
},
"text": {
"type": "string",
"description": "The memory's text (truncated)."
},
"transition": {
"type": "string",
"description": "Lifecycle transition.",
"enum": [
"merged",
"superseded"
]
},
"at": {
"type": "string",
"description": "When the transition happened.",
"format": "date-time"
},
"replaced_by": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "ID of the newer memory."
},
"text": {
"type": "string",
"description": "Text of the newer memory (truncated).",
"nullable": true
}
},
"nullable": true,
"description": "The newer memory that replaced this one (null for a merge with no single replacement)."
},
"sources": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Source memory ID."
},
"text": {
"type": "string",
"description": "Source memory text (truncated)."
}
}
},
"nullable": true,
"description": "Always null on this feed; synthesis provenance is shown on the runs feed."
}
}
}
},
"next_cursor": {
"type": "string",
"description": "Cursor for the next page, or null when there are no more.",
"nullable": true
}
}
}
}
}
}
}
}
},
"/api/v1/orgs/organizations/{org_id}/projects/{project_id}/dream/runs/": {
"get": {
"tags": [
"dream"
],
"summary": "Get Dream synthesis runs",
"description": "Synthesis activity grouped per run (newest first), keyset-paginated. Each run inlines its first page of synthesized memories with their sources; larger runs page the rest via the run-memories endpoint.",
"operationId": "get_dream_runs",
"parameters": [
{
"name": "org_id",
"in": "path",
"required": true,
"description": "Unique identifier of the organization.",
"schema": {
"type": "string"
}
},
{
"name": "project_id",
"in": "path",
"required": true,
"description": "Unique identifier of the project.",
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"required": false,
"description": "Number of runs per page (default 20, max 100).",
"schema": {
"type": "integer",
"default": 20
}
},
{
"name": "cursor",
"in": "query",
"required": false,
"description": "Opaque keyset cursor from a previous page's `next_cursor`.",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Successful response.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"type": "object",
"properties": {
"run_id": {
"type": "string",
"description": "Identifier of the synthesis run."
},
"at": {
"type": "string",
"description": "When the run produced memories.",
"format": "date-time"
},
"count": {
"type": "integer",
"description": "Total synthesized memories in this run."
},
"synthesized": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Synthesized memory ID."
},
"text": {
"type": "string",
"description": "Synthesized (pattern) memory text."
},
"sources": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Source memory ID."
},
"text": {
"type": "string",
"description": "Source memory text (truncated)."
}
}
},
"description": "Source memories this pattern was distilled from."
}
}
},
"description": "First page of synthesized memories (each with its sources)."
},
"has_more": {
"type": "boolean",
"description": "Whether the run has more memories than are inlined here."
},
"mem_cursor": {
"type": "string",
"description": "Cursor to page this run's remaining memories via the run-memories endpoint.",
"nullable": true
},
"sources": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Source memory ID."
},
"text": {
"type": "string",
"description": "Source memory text (truncated)."
}
}
},
"description": "De-duplicated source memories across the inlined page."
}
}
}
},
"next_cursor": {
"type": "string",
"description": "Cursor for the next page, or null when there are no more.",
"nullable": true
}
}
}
}
}
}
}
}
},
"/api/v1/orgs/organizations/{org_id}/projects/{project_id}/dream/runs/{run_id}/memories/": {
"get": {
"tags": [
"dream"
],
"summary": "Get memories in a Dream run",
"description": "Keyset page of the synthesized memories within a single run (for runs whose `has_more` is true).",
"operationId": "get_dream_run_memories",
"parameters": [
{
"name": "org_id",
"in": "path",
"required": true,
"description": "Unique identifier of the organization.",
"schema": {
"type": "string"
}
},
{
"name": "project_id",
"in": "path",
"required": true,
"description": "Unique identifier of the project.",
"schema": {
"type": "string"
}
},
{
"name": "run_id",
"in": "path",
"required": true,
"description": "Identifier of the synthesis run.",
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"required": false,
"description": "Page size (default 50, max 200).",
"schema": {
"type": "integer",
"default": 50
}
},
{
"name": "cursor",
"in": "query",
"required": false,
"description": "Opaque keyset cursor (use the run's `mem_cursor` or a previous `next_cursor`).",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Successful response.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Synthesized memory ID."
},
"text": {
"type": "string",
"description": "Synthesized (pattern) memory text."
},
"sources": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Source memory ID."
},
"text": {
"type": "string",
"description": "Source memory text (truncated)."
}
}
},
"description": "Source memories this pattern was distilled from."
}
}
}
},
"next_cursor": {
"type": "string",
"description": "Cursor for the next page, or null when there are no more.",
"nullable": true
}
}
}
}
}
}
}
}
},
"/api/v1/orgs/organizations/{org_id}/projects/{project_id}/dream/memory/{memory_id}/sources/": {
"get": {
"tags": [
"dream"
],
"summary": "Get a synthesized memory's sources",
"description": "The source memories a synthesized (pattern) memory was distilled from, for the memory drawer's provenance panel.",
"operationId": "get_dream_memory_sources",
"parameters": [
{
"name": "org_id",
"in": "path",
"required": true,
"description": "Unique identifier of the organization.",
"schema": {
"type": "string"
}
},
{
"name": "project_id",
"in": "path",
"required": true,
"description": "Unique identifier of the project.",
"schema": {
"type": "string"
}
},
{
"name": "memory_id",
"in": "path",
"required": true,
"description": "ID of the (synthesized) memory.",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Successful response.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"memory_id": {
"type": "string",
"description": "The memory's ID."
},
"synthesized": {
"type": "boolean",
"description": "Whether this memory was produced by synthesis."
},
"sources": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Source memory ID."
},
"text": {
"type": "string",
"description": "Source memory text."
},
"lifecycle_state": {
"type": "string",
"description": "Source memory lifecycle state.",
"enum": [
"active",
"merged",
"superseded"
]
}
}
}
}
}
}
}
}
},
"404": {
"description": "Memory not found in this project."
}
}
}
},
"/api/v1/orgs/organizations/{org_id}/projects/{project_id}/dream/preview/": {
"post": {
"tags": [
"dream"
],
"summary": "Preview Dream scope",
"description": "A no-write preview of the scope Dream synthesis would analyze for the project (a capped sample plus the count of users with enough memories to benefit). Never runs the LLM and never mutates anything.",
"operationId": "dream_preview",
"parameters": [
{
"name": "org_id",
"in": "path",
"required": true,
"description": "Unique identifier of the organization.",
"schema": {
"type": "string"
}
},
{
"name": "project_id",
"in": "path",
"required": true,
"description": "Unique identifier of the project.",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Successful response.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"active_total": {
"type": "integer",
"description": "Total active memories in the project."
},
"scanned": {
"type": "integer",
"description": "How many recent memories the preview aggregated over (capped)."
},
"cap": {
"type": "integer",
"description": "The scan cap applied."
},
"eligible_users": {
"type": "integer",
"description": "Users with enough memories to benefit from synthesis."
},
"note": {
"type": "string",
"description": "Human-readable note about the preview scope."
}
}
}
}
}
},
"403": {
"description": "Preview not available on the current plan (`upgrade_required: true`)."
}
}
}
}
},
"components": {
+3 -2
View File
@@ -143,6 +143,7 @@ Search memories using natural language.
```bash
mem0 search "dietary restrictions" --user-id alice
mem0 search "preferred tools" --user-id alice --output json --top-k 5
mem0 search "invoices" --user-id alice --filter '{"AND": [{"categories": {"in": ["work"]}}]}'
```
| Flag | Description |
@@ -155,7 +156,7 @@ mem0 search "preferred tools" --user-id alice --output json --top-k 5
| `--threshold` | Minimum similarity score (default: 0.3) |
| `--rerank` | Enable reranking |
| `--keyword` | Use keyword search instead of semantic |
| `--filter` | Advanced filter expression (JSON) |
| `--filter` | Advanced filter as JSON: `{"AND": [...]}` or `{"OR": [...]}`, e.g. `{"AND": [{"categories": {"in": ["work"]}}]}` |
| `--fields` | Return only the named fields |
| `--show-expired` | Include expired memories |
| `--reference-date` | Reference date for relative queries (`YYYY-MM-DD` or Unix timestamp) |
@@ -471,7 +472,7 @@ These two flags belong to `mem0` itself, so they go **before** the command name:
|------|-------------|
| `--json` | Enable agent mode: structured JSON envelope output, no colors or spinners |
| `--agent` | Alias for `--json` |
| `--version` | Print the CLI version and exit |
| `--version` | Print the CLI version and exit. `mem0 version` does the same thing as a regular subcommand |
<Warning>
On `init` only, `--agent` means something different. `mem0 init --agent` creates an Agent Mode account (see [Sign up as an agent](/platform/agent-signup)); it does not switch the output to JSON. To get JSON from `init`, put the flag first: `mem0 --json init`.
+18 -4
View File
@@ -99,16 +99,23 @@ results = client.search(
### Recommended Configurations
`rerank` is the only lever here that changes result *order*. `filters`, `top_k`, and `threshold` change *which* memories come back, not how they're ordered. The two functions below send the same query and filters; the only difference is the `rerank` flag.
<CodeGroup>
```python Python
# Basic search - good for exploration
# Fast path - use for exploratory search, or anywhere the user scans a list
# of results instead of trusting result #1 (dashboards, "show me everything
# about X" style queries). No reranking overhead.
def quick_search(query, user_id):
return client.search(
query=query,
filters={"user_id": user_id},
)
# Reranked search - good when result order matters
# Precision path - use when only the top result reaches the user, e.g. an
# agent that injects a single fact into a prompt. Reranking (see above)
# re-scores every match and moves the closest one to position 1, at the
# cost of ~150-200ms added latency.
def standard_search(query, user_id):
return client.search(
query=query,
@@ -118,14 +125,19 @@ def standard_search(query, user_id):
```
```javascript JavaScript
// Basic search - good for exploration
// Fast path - use for exploratory search, or anywhere the user scans a list
// of results instead of trusting result #1 (dashboards, "show me everything
// about X" style queries). No reranking overhead.
function quickSearch(query, userId) {
return client.search(query, {
filters: { user_id: userId },
});
}
// Reranked search - good when result order matters
// Precision path - use when only the top result reaches the user, e.g. an
// agent that injects a single fact into a prompt. Reranking (see above)
// re-scores every match and moves the closest one to position 1, at the
// cost of ~150-200ms added latency.
function standardSearch(query, userId) {
return client.search(query, {
filters: { user_id: userId },
@@ -135,6 +147,8 @@ function standardSearch(query, userId) {
```
</CodeGroup>
**What changes in the response:** both calls return the same fields on each memory (see the [Search Memories API reference](/api-reference/memory/search-memories) for the full response shape). The only difference is the *order* of the `results` array, the same effect shown in the [Reranking example above](#reranking): `quick_search` returns results ranked by raw similarity, `standard_search` returns the reranked order.
## Best Practices
### Do
+1 -1
View File
@@ -75,7 +75,7 @@ print(response)
```
</CodeGroup>
This "Updated custom categories" message is specific to a PATCH-style partial update, which is what `client.project.update()` sends. Calling the raw API with a full PUT instead returns a generic `{"message": "Project updated successfully."}`, regardless of which fields changed.
Treat the `message` string as informational. The project endpoint accepts `PATCH` only, and its documented response is the generic `{"message": "Project updated successfully"}`. Confirm an update by reading the field back, as in the next step, rather than by matching on the message.
### 2. Confirm the active catalog

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