Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea94ffa63b | ||
|
|
290de24bb8 | ||
|
|
ef6f51d977 | ||
|
|
a10c0cd030 | ||
|
|
956bf4f88e | ||
|
|
0f172c2890 | ||
|
|
696455fd62 | ||
|
|
9e99eaadbc | ||
|
|
02ff6c5595 | ||
|
|
a0329f047b | ||
|
|
c50a2bfb8f | ||
|
|
bfb51c6b93 | ||
|
|
a133287015 | ||
|
|
c883f52130 | ||
|
|
a3d1a1068b | ||
|
|
227c02da9f | ||
|
|
d64d7b525b |
@@ -0,0 +1,102 @@
|
||||
# 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 |
|
||||
| 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 |
|
||||
| docs llms.txt | `docs-llms-txt-check.yml` | Manual | `docs/llms.txt` coverage |
|
||||
|
||||
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.
|
||||
|
||||
## 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`) |
|
||||
| n8n Node | `n8n-nodes-mem0-cd.yml` | `n8n-nodes-mem0-v*` | npm (`@mem0/n8n-nodes-mem0`) |
|
||||
|
||||
- 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`, with a reopen path. Exempts members, bots, drafts, and docs-only changes. Never checks out PR code. |
|
||||
| Vouch (check PR) | `vouch-check-pr.yml` | Comments on PRs from authors absent from `VOUCHED.td`. Comment-only mode (`auto-close: false`). |
|
||||
| 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. Commits back to `VOUCHED.td` through a GitHub App token. |
|
||||
| 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.
|
||||
|
||||
`GATE_EFFECTIVE_FROM` in `pr-gate.yml` is a `created_at` cutoff. `edited`, `reopened`, and `ready_for_review` fire on PRs opened long before the gate existed, so without the cutoff the whole 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.
|
||||
|
||||
## 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. `author_association` is `MEMBER` for every org member regardless of repository permission, so no member can be flagged even if their `VOUCHED.td` entry is missing, misspelled, or miscased. Org members are still listed in the file as a fallback, but the workflow guard is what actually holds.
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
AGENTS.md
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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. -->
|
||||
|
||||
@@ -0,0 +1,413 @@
|
||||
# The list of vouched (or denounced) users for this repository.
|
||||
#
|
||||
# Only vouched users can open pull requests here. A denounced user (prefixed
|
||||
# with a minus) is blocked outright.
|
||||
#
|
||||
# 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 entirely for OWNER, MEMBER, and
|
||||
# COLLABORATOR authors. mem0ai org members are listed below as well, but that
|
||||
# workflow guard is what actually protects them: a missing, misspelled, or
|
||||
# miscased entry here can never cause a member to be flagged.
|
||||
#
|
||||
# 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
|
||||
@@ -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"
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
name: PR Gate
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, reopened, edited, ready_for_review]
|
||||
|
||||
concurrency:
|
||||
group: pr-gate-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
GATE_EFFECTIVE_FROM: '2026-08-12T00:00:00Z'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
issues: read
|
||||
|
||||
jobs:
|
||||
gate:
|
||||
if: >-
|
||||
github.event.pull_request.draft == false &&
|
||||
github.event.pull_request.user.type != 'Bot' &&
|
||||
!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,
|
||||
});
|
||||
if (files.length > 0 && files.every((file) => file.filename.startsWith('docs/'))) {
|
||||
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 = [
|
||||
'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`.',
|
||||
'4. Reopen this pull request. The check runs again and it stays open.',
|
||||
'',
|
||||
'Already linked an accepted issue? Edit the description to include `Closes #<number>` and reopen. The check reruns automatically.',
|
||||
'',
|
||||
'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`);
|
||||
@@ -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"
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
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' &&
|
||||
!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
|
||||
with:
|
||||
pr-number: ${{ github.event.pull_request.number }}
|
||||
auto-close: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -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: "true"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
@@ -1,593 +1,155 @@
|
||||
# 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).
|
||||
- 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/`)
|
||||
### The CLA is not optional
|
||||
|
||||
```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)
|
||||
```
|
||||
**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.
|
||||
|
||||
- **Node:** 18+ required
|
||||
- **Build:** tsup (ESM)
|
||||
- **Linter:** Biome (not ESLint, not Ruff)
|
||||
- **Test:** vitest (not jest)
|
||||
- **Framework:** Commander + Chalk + ora + cli-table3
|
||||
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.
|
||||
|
||||
### Vercel AI SDK Provider (`integrations/vercel-ai-sdk/`)
|
||||
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.
|
||||
|
||||
```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)
|
||||
```
|
||||
### What gets a pull request closed
|
||||
|
||||
- **Build:** tsup (CJS + ESM)
|
||||
- **Lint:** ESLint + Prettier
|
||||
- **Test:** jest + vitest (edge/node configs)
|
||||
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:
|
||||
|
||||
### OpenClaw Plugin (`integrations/openclaw/`)
|
||||
- **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
|
||||
cd integrations/openclaw
|
||||
pnpm install
|
||||
pnpm run build # tsup
|
||||
pnpm run test # vitest run
|
||||
```
|
||||
### Reference
|
||||
|
||||
- **Build:** tsup (ESM)
|
||||
- **Test:** vitest (with Codecov in CI)
|
||||
- **Plugin manifest:** `openclaw.plugin.json`
|
||||
|
||||
### Server (`server/`)
|
||||
|
||||
```bash
|
||||
# Docker production build
|
||||
cd server
|
||||
make build # docker build -t mem0-api-server .
|
||||
make run_local # docker run -p 8000:8000 with .env
|
||||
|
||||
# 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
|
||||
```
|
||||
|
||||
- **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
|
||||
|
||||
### Documentation (`docs/`)
|
||||
|
||||
```bash
|
||||
make docs # or: cd docs && mintlify dev
|
||||
```
|
||||
|
||||
- **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/` |
|
||||
| Trust list (vouch) | `.github/VOUCHED.td` |
|
||||
| CI/CD, gates, rulesets | [`.github/AGENTS.md`](.github/AGENTS.md) |
|
||||
|
||||
@@ -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
|
||||
+52
-2
@@ -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,53 @@ 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,
|
||||
reopen the pull request and it stays open. Documentation-only changes skip the
|
||||
gate entirely.
|
||||
|
||||
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).**
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+10
-1
@@ -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
@@ -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.")
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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).",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
AGENTS.md
|
||||
@@ -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).
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -3,6 +3,9 @@ title: Control Memory Ingestion
|
||||
description: "Filter speculation, enforce formats, and gate low-confidence data before it persists."
|
||||
---
|
||||
|
||||
<Info icon="cloud">
|
||||
**Works with:** Mem0 Platform (`MemoryClient`)
|
||||
</Info>
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -3,15 +3,14 @@ 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 lets you retrieve memories as raw records or transform them into a structured export for downstream use.
|
||||
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.
|
||||
|
||||
This cookbook shows you how to retrieve and export your data for inspection, migration, or compliance.
|
||||
|
||||
<Warning>
|
||||
The structured export job is not a raw backup. It uses the schema and instructions you provide to transform matching memories. Use paginated `get_all()` calls when you need the original memory records. For an account-level data access or compliance request that the API cannot complete, contact support.
|
||||
</Warning>
|
||||
|
||||
---
|
||||
|
||||
## Setup
|
||||
@@ -173,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
|
||||
@@ -197,7 +196,7 @@ export_data = client.get_memory_export(
|
||||
memory_export_id=export_job['id']
|
||||
)
|
||||
|
||||
print(export_data['data'])
|
||||
print(export_data)
|
||||
|
||||
```
|
||||
|
||||
@@ -220,7 +219,7 @@ export_by_filters = client.get_memory_export(
|
||||
filters={"user_id": "dev"}
|
||||
)
|
||||
|
||||
print(export_by_filters['data'])
|
||||
print(export_by_filters)
|
||||
|
||||
```
|
||||
|
||||
@@ -244,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>
|
||||
|
||||
---
|
||||
@@ -258,7 +257,7 @@ You can also export memories directly from the Mem0 platform UI:
|
||||
3. Select your filters and schema
|
||||
4. Download the completed export as JSON
|
||||
|
||||
This is useful for one-off structured exports or manual data reviews. It does not create a raw backup of every memory record; use paginated `get_all()` calls for raw retrieval.
|
||||
This is useful for one-off exports or manual data reviews.
|
||||
|
||||
<Warning>
|
||||
Exported data expires after 7 days. Download and store exports locally if you need long-term archives. After expiration, you'll need to recreate the export job.
|
||||
@@ -276,13 +275,13 @@ A complete memory export system with multiple retrieval methods:
|
||||
- **Export instructions** - Guide conflict resolution and data formatting
|
||||
- **Platform UI exports** - One-off manual downloads via dashboard
|
||||
|
||||
These approaches support data inspection, portability workflows, system migrations, and manual reviews. Use raw retrieval or contact support when a compliance request requires the original records rather than a schema-transformed export.
|
||||
This covers data portability, GDPR compliance, system migrations, and manual reviews.
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Use **`get_all()`** for paginated raw retrieval, **`search()`** for specific questions, and **`create_memory_export()`** for schema-transformed data. Structured exports expire after 7 days, so download completed results promptly. Do not treat them as raw backups.
|
||||
Use **`get_all()`** for bulk retrieval, **`search()`** for specific questions, and **`create_memory_export()`** for structured data exports with custom schemas. Remember exports expire after 7 days: download them locally for long-term archives.
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Build a Mem0 Companion" icon="users" href="/cookbooks/essentials/building-ai-companion">
|
||||
|
||||
@@ -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?
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -13,6 +13,56 @@ 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-one 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 |
|
||||
| [Control Memory Ingestion](/cookbooks/essentials/controlling-memory-ingestion) | Essentials | Platform only |
|
||||
| [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:
|
||||
|
||||
@@ -611,6 +611,10 @@
|
||||
]
|
||||
},
|
||||
"redirects": [
|
||||
{
|
||||
"source": "/open-source/features/reranking",
|
||||
"destination": "/open-source/features/reranker-search"
|
||||
},
|
||||
{
|
||||
"source": "/platform/features/contextual-add",
|
||||
"destination": "/core-concepts/memory-operations/add"
|
||||
|
||||
@@ -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**.
|
||||
|
||||

|
||||
|
||||
### 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.
|
||||
|
||||

|
||||
|
||||
### 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>
|
||||
|
||||

|
||||
|
||||
### 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
|
||||
|
||||

|
||||
|
||||
## Advanced Configuration
|
||||
|
||||
### Memory Settings
|
||||
|
||||

|
||||
|
||||
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
|
||||
|
||||

|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **User Identification**: Use consistent `user_id` values for reliable memory retrieval
|
||||
|
||||
@@ -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
|
||||
|
||||
+39
-40
@@ -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
|
||||
|
||||
@@ -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,46 @@ 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).
|
||||
- [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.
|
||||
|
||||
### 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.
|
||||
- [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
|
||||
|
||||
@@ -318,10 +317,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.
|
||||
@@ -410,10 +409,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
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
```
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
title: Reranking
|
||||
description: 'Redirect to the canonical reranker-enhanced search guide.'
|
||||
---
|
||||
|
||||
<Redirect href="/open-source/features/reranker-search" />
|
||||
@@ -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);
|
||||
```
|
||||
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ client.project.update(decay=True)
|
||||
```
|
||||
|
||||
```javascript JavaScript
|
||||
await client.project.update({ decay: true });
|
||||
await client.updateProject({ decay: true });
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
@@ -75,32 +75,32 @@ curl -X PATCH https://api.mem0.ai/api/v1/orgs/organizations/$ORG_ID/projects/$PR
|
||||
```
|
||||
|
||||
```json Response
|
||||
{ "message": "Updated decay" }
|
||||
{ "message": "Project updated successfully" }
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
### 2. Confirm the state
|
||||
|
||||
`decay` is returned on every project read; there is currently no way to narrow the response to just this field, so read the full project object and pick out `decay`.
|
||||
`decay` is returned on every project read. To fetch only this field, use `?fields=decay`.
|
||||
|
||||
<CodeGroup>
|
||||
```python Python
|
||||
response = client.project.get()
|
||||
response = client.project.get(fields=["decay"])
|
||||
print(response["decay"])
|
||||
```
|
||||
|
||||
```javascript JavaScript
|
||||
const response = await client.project.get();
|
||||
const response = await client.getProject({ fields: ["decay"] });
|
||||
console.log(response.decay);
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl "https://api.mem0.ai/api/v1/orgs/organizations/$ORG_ID/projects/$PROJECT_ID/" \
|
||||
curl "https://api.mem0.ai/api/v1/orgs/organizations/$ORG_ID/projects/$PROJECT_ID/?fields=decay" \
|
||||
-H "Authorization: Token $MEM0_API_KEY"
|
||||
```
|
||||
|
||||
```json Response
|
||||
{ "decay": true, "...": "full project object" }
|
||||
{ "decay": true }
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
@@ -114,7 +114,7 @@ client.project.update(decay=False)
|
||||
```
|
||||
|
||||
```javascript JavaScript
|
||||
await client.project.update({ decay: false });
|
||||
await client.updateProject({ decay: false });
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
|
||||
@@ -7,10 +7,6 @@ description: 'Export memories in a structured format using customizable Pydantic
|
||||
|
||||
The Memory Export feature allows you to create structured exports of memories using customizable Pydantic schemas. This process enables you to transform your stored memories into specific data formats that match your needs. You can apply various filters to narrow down which memories to export and define exactly how the data should be structured.
|
||||
|
||||
<Warning>
|
||||
Memory Export transforms matching memories into the schema you provide. It is not a raw backup or a byte-for-byte download of every stored memory record. If you need the original memory records, retrieve them with the paginated `get_all()` API instead. For an account-level data access or compliance request that cannot be completed through the API, contact support.
|
||||
</Warning>
|
||||
|
||||
## Creating a Memory Export
|
||||
|
||||
To create a memory export, you'll need to:
|
||||
@@ -282,8 +278,6 @@ You can apply various filters to customize which memories are included in the ex
|
||||
The export process may take some time to complete, especially when dealing with a large number of memories or complex schemas.
|
||||
</Note>
|
||||
|
||||
If an export remains in progress unexpectedly, do not submit duplicate jobs. Retrieve the job by its export ID to check its status, then contact support with that ID if it does not complete.
|
||||
|
||||
If you have any questions, please feel free to reach out to us using one of the following methods:
|
||||
|
||||
<Snippet file="get-help.mdx" />
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
# Integrations (`integrations/`)
|
||||
|
||||
Agent and editor integrations. Each subdirectory is self-contained: its own `package.json`, lockfile, build, and tests. **There is no shared toolchain.** Check the table before running anything.
|
||||
|
||||
| Directory | Package | Build | Lint | Test |
|
||||
|-----------|---------|-------|------|------|
|
||||
| `vercel-ai-sdk/` | `@mem0/vercel-ai-provider` | tsup (CJS+ESM) | ESLint + Prettier | jest + vitest (edge/node) |
|
||||
| `openclaw/` | `@mem0/openclaw-mem0` | tsup (ESM) | none | vitest |
|
||||
| `mem0-plugin/` | Claude Code / Cursor / Codex plugin | none | none | pytest |
|
||||
| `mem0-plugin/.opencode-plugin/` | `@mem0/opencode-plugin` | Bun | none | tsc type-check |
|
||||
| `pi-agent-plugin/` | `@mem0/pi-agent-plugin` | tsup | none | vitest |
|
||||
| `n8n-nodes-mem0/` | `@mem0/n8n-nodes-mem0` | tsc | ESLint (n8n-nodes-base) | none |
|
||||
| `zapier-mem0/` | `@mem0/zapier` | tsc | none | offline unit tests + `zapier validate` |
|
||||
|
||||
pnpm everywhere except `.opencode-plugin/`, which uses Bun. Never npm, never yarn.
|
||||
|
||||
## Commands
|
||||
|
||||
```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
|
||||
pnpm run test # jest
|
||||
pnpm run test:edge # vitest, edge runtime
|
||||
pnpm run test:node # vitest, node runtime
|
||||
|
||||
cd integrations/openclaw
|
||||
pnpm install
|
||||
pnpm run build # tsup
|
||||
pnpm run test # vitest
|
||||
```
|
||||
|
||||
Run the type check after every TypeScript change: `pnpm run typecheck` or `tsc --noEmit`, whichever the package defines.
|
||||
|
||||
## What each one is
|
||||
|
||||
- **`vercel-ai-sdk/`** wraps the Vercel AI SDK through a `createMem0` provider. Integrations for AI-SDK repos go through this wrapper, not raw `MemoryClient`.
|
||||
- **`mem0-plugin/`** connects Claude Code, Cursor, and Codex to the MCP server at `mcp.mem0.ai` and installs lifecycle hooks for automatic memory capture. Exposes 9 MCP tools: `add_memory`, `search_memories`, `get_memories`, `get_memory`, `update_memory`, `delete_memory`, `delete_all_memories`, `delete_entities`, `list_entities`.
|
||||
- **`openclaw/`**, **`pi-agent-plugin/`** are editor and agent plugins with the same shape.
|
||||
- **`n8n-nodes-mem0/`** is an n8n community node: add, search, get, update, delete.
|
||||
- **`zapier-mem0/`** is a Zapier Platform CLI app: add, search, get, delete. It deploys to Zapier, not npm, so it is **not** in the release router. Deploy it with `gh workflow run zapier-mem0-cd.yml --ref main` (needs the `ZAPIER_DEPLOY_KEY` secret).
|
||||
|
||||
## Adding an integration
|
||||
|
||||
1. Create `integrations/<name>/` and build it there, self-contained.
|
||||
2. If it publishes to a registry, set `repository.directory: "integrations/<name>"` in `package.json` so npm provenance links to the right subdirectory.
|
||||
3. Add `.github/workflows/<name>-checks.yml` and `<name>-cd.yml`. Use `integrations/<name>` in the `paths:` trigger, `working-directory`, and `cache-dependency-path`. Register the release tag prefix in the `case` block in `release.yml`, keeping the bare `v*` arm last.
|
||||
**Workflow filenames are load-bearing:** npm OIDC trusted publishing is pinned to repository plus workflow filename. Renaming one breaks publishing.
|
||||
4. Register the CI workflow in `ci-gate.yml`: a path filter under the `changes` job, a call job, and an entry in the gate job's `needs` list.
|
||||
5. If it is a Claude Code or editor marketplace plugin, register its path in all five `marketplace.json` files: root, `.claude-plugin/`, `.cursor-plugin/`, `.codex-plugin/`, and `.agents/plugins/`.
|
||||
6. Document it under `docs/integrations/` and add the page to `docs/docs.json` and `docs/llms.txt`.
|
||||
7. Add rows to the table above and to the CI/CD tables in [`../.github/AGENTS.md`](../.github/AGENTS.md).
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
AGENTS.md
|
||||
@@ -8,6 +8,7 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import urllib.request
|
||||
|
||||
SEARCH_URL = "https://api.mem0.ai/v3/memories/search/"
|
||||
@@ -83,7 +84,8 @@ def search_memories(
|
||||
if min_score > 0:
|
||||
results = [m for m in results if m.get("score", 0) >= min_score]
|
||||
return results
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
print(f"[mem0] search request failed: {e}", file=sys.stderr)
|
||||
return []
|
||||
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import urllib.error
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
|
||||
@@ -101,6 +102,39 @@ def test_search_memories_no_api_key_returns_empty():
|
||||
assert results == []
|
||||
|
||||
|
||||
def test_search_memories_logs_rate_limit_error(capsys):
|
||||
"""Bug bash #22: a 429 must not look identical to a genuine empty result."""
|
||||
from _search import search_memories
|
||||
|
||||
def mock_urlopen(req, timeout=None):
|
||||
raise urllib.error.HTTPError("http://x", 429, "Too Many Requests", {}, None)
|
||||
|
||||
with patch("urllib.request.urlopen", side_effect=mock_urlopen):
|
||||
results = search_memories("key", "user", "proj", "query")
|
||||
|
||||
assert results == []
|
||||
err = capsys.readouterr().err
|
||||
assert "429" in err
|
||||
assert "Too Many Requests" in err
|
||||
|
||||
|
||||
def test_search_memories_happy_path_is_silent(capsys):
|
||||
from _search import search_memories
|
||||
|
||||
def mock_urlopen(req, timeout=None):
|
||||
resp = MagicMock()
|
||||
resp.read.return_value = json.dumps({"results": []}).encode()
|
||||
resp.__enter__ = lambda s: s
|
||||
resp.__exit__ = MagicMock(return_value=False)
|
||||
return resp
|
||||
|
||||
with patch("urllib.request.urlopen", side_effect=mock_urlopen):
|
||||
results = search_memories("key", "user", "proj", "query")
|
||||
|
||||
assert results == []
|
||||
assert capsys.readouterr().err == ""
|
||||
|
||||
|
||||
def test_search_memories_omits_rerank_by_default():
|
||||
"""Regression for #5684: rerank must not be sent unless requested."""
|
||||
from _search import search_memories
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
# TypeScript SDK (`mem0-ts/`)
|
||||
|
||||
The `mem0ai` package on npm. Hosted client plus self-hosted OSS memory.
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
pnpm run build # tsup (CJS + ESM)
|
||||
pnpm run test # jest, all tests
|
||||
pnpm run test:unit # jest --coverage
|
||||
pnpm run test:integration # jest, needs MEM0_API_KEY
|
||||
pnpm run test:ci # jest --coverage --ci
|
||||
pnpm run test:watch
|
||||
pnpm run typecheck # tsc --noEmit
|
||||
```
|
||||
|
||||
pnpm only. Never npm, never yarn.
|
||||
|
||||
## Conventions
|
||||
|
||||
- **Node 20 and 22** are the CI-tested versions.
|
||||
- **Build:** tsup, dual CJS + ESM output.
|
||||
- **Formatter:** Prettier. No linter is configured here; `cli/node/` uses Biome and `integrations/vercel-ai-sdk/` uses ESLint, so do not assume a shared setup.
|
||||
- **Tests:** jest. `cli/node/` and `integrations/openclaw/` use vitest instead.
|
||||
- **TypeScript strict mode.**
|
||||
- ES module `import` syntax only. Never `require()`.
|
||||
- Source files are `snake_case.ts`, tests are `<module>.test.ts`.
|
||||
|
||||
Run `pnpm run typecheck` after every change.
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
mem0-ts/src/
|
||||
├── client/ MemoryClient (hosted platform)
|
||||
└── oss/ Memory (self-hosted)
|
||||
├── llms/
|
||||
├── embeddings/
|
||||
├── vector_stores/
|
||||
└── graphs/
|
||||
```
|
||||
|
||||
## Public API
|
||||
|
||||
| Export | Purpose | Import |
|
||||
| -------------- | ---------------------- | ---------------------------------------------- |
|
||||
| `MemoryClient` | Hosted platform client | `import { MemoryClient } from 'mem0ai'` |
|
||||
| `Memory` | Self-hosted OSS memory | `import { Memory } from 'mem0ai/oss'` |
|
||||
| Providers | OSS building blocks | `import { OpenAIEmbedding } from 'mem0ai/oss'` |
|
||||
|
||||
The method surface mirrors the Python SDK: `add`, `search`, `get`, `getAll`, `update`, `delete`, `deleteAll`, `history`. Changing any public signature means updating `docs/` in the same PR.
|
||||
|
||||
## Releasing
|
||||
|
||||
Tag prefix `ts-v*` triggers `ts-sdk-cd.yml`, which publishes to npm over OIDC. Bump the version in `package.json` first.
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
AGENTS.md
|
||||
@@ -2,8 +2,6 @@
|
||||
module.exports = {
|
||||
...require("./jest.config"),
|
||||
testMatch: ["**/integration/**/*.test.ts"],
|
||||
globalSetup: "<rootDir>/src/client/tests/integration/global-setup.ts",
|
||||
globalTeardown: "<rootDir>/src/client/tests/integration/global-teardown.ts",
|
||||
// Run integration tests serially to avoid rate limiting and race conditions
|
||||
maxWorkers: 1,
|
||||
};
|
||||
|
||||
@@ -404,10 +404,10 @@ export default class MemoryClient {
|
||||
...(filters && { filters }),
|
||||
};
|
||||
|
||||
let url = `${this.host}/v3/memories/`;
|
||||
if (page && pageSize) {
|
||||
url += `?page=${page}&page_size=${pageSize}`;
|
||||
}
|
||||
const queryParams: string[] = [];
|
||||
if (page !== undefined) queryParams.push(`page=${page}`);
|
||||
if (pageSize !== undefined) queryParams.push(`page_size=${pageSize}`);
|
||||
const url = `${this.host}/v3/memories/${queryParams.length ? `?${queryParams.join("&")}` : ""}`;
|
||||
|
||||
const response = await this._fetchWithErrorHandling(url, {
|
||||
method: "POST",
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
/**
|
||||
* Jest global setup for integration tests.
|
||||
*
|
||||
* Runs a full project cleanup before any integration test starts,
|
||||
* then waits 10 seconds for the async cleanup to propagate.
|
||||
*/
|
||||
import { MemoryClient } from "../../mem0";
|
||||
|
||||
export default async function globalSetup() {
|
||||
const apiKey = process.env.MEM0_API_KEY;
|
||||
if (!apiKey) return; // skip if no key — tests will be skipped too
|
||||
|
||||
const client = new MemoryClient({ apiKey });
|
||||
await client.ping();
|
||||
|
||||
console.log("[integration] Running pre-test cleanup...");
|
||||
|
||||
// Full project wipe — all four filters set explicitly
|
||||
try {
|
||||
await client.deleteAll({
|
||||
userId: "*",
|
||||
agentId: "*",
|
||||
appId: "*",
|
||||
runId: "*",
|
||||
});
|
||||
} catch {
|
||||
// ignore — may 404 if no data exists
|
||||
}
|
||||
|
||||
try {
|
||||
await client.deleteUsers();
|
||||
} catch {
|
||||
// ignore — may throw "No entities to delete"
|
||||
}
|
||||
|
||||
// Wait 10 seconds for async cleanup to propagate
|
||||
console.log("[integration] Waiting 10s for cleanup to propagate...");
|
||||
await new Promise((r) => setTimeout(r, 10_000));
|
||||
console.log("[integration] Pre-test cleanup done.");
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/**
|
||||
* Jest global teardown for integration tests.
|
||||
*
|
||||
* Runs a full project cleanup after all integration tests complete
|
||||
* so no test data is left behind.
|
||||
*/
|
||||
import { MemoryClient } from "../../mem0";
|
||||
|
||||
export default async function globalTeardown() {
|
||||
const apiKey = process.env.MEM0_API_KEY;
|
||||
if (!apiKey) return;
|
||||
|
||||
const client = new MemoryClient({ apiKey });
|
||||
await client.ping();
|
||||
|
||||
console.log("[integration] Running post-test cleanup...");
|
||||
|
||||
try {
|
||||
await client.deleteAll({
|
||||
userId: "*",
|
||||
agentId: "*",
|
||||
appId: "*",
|
||||
runId: "*",
|
||||
});
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
try {
|
||||
await client.deleteUsers();
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
console.log("[integration] Post-test cleanup done.");
|
||||
}
|
||||
@@ -52,13 +52,17 @@ export async function withRetry<T>(
|
||||
}
|
||||
|
||||
/**
|
||||
* Poll getAll until memories appear for a user.
|
||||
* The Mem0 API processes memories asynchronously — after add()
|
||||
* we need to wait for them to be available.
|
||||
* Poll getAll until a user's memories reach minCount and the ID set
|
||||
* stops changing between polls.
|
||||
*
|
||||
* The Mem0 API processes memories asynchronously, and consolidating a
|
||||
* new fact replaces a memory (delete + add) instead of editing it in
|
||||
* place. Returning on the first non-empty read hands callers IDs the
|
||||
* pipeline then invalidates, so compare IDs rather than length: a
|
||||
* replacement leaves the count unchanged.
|
||||
*
|
||||
* Polls every 15 seconds with a maximum of 4 retries to avoid
|
||||
* hitting rate limits. Throws if results aren't available after
|
||||
* all retries.
|
||||
* hitting rate limits. Throws if minCount is never reached.
|
||||
*/
|
||||
export async function waitForMemories(
|
||||
client: MemoryClient,
|
||||
@@ -66,18 +70,32 @@ export async function waitForMemories(
|
||||
minCount: number,
|
||||
maxRetries = 4,
|
||||
): Promise<Memory[]> {
|
||||
let memories: Memory[] = [];
|
||||
let previousIds = "";
|
||||
|
||||
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||
const response = await withRetry(() =>
|
||||
client.getAll({ filters: { user_id: userId } }),
|
||||
);
|
||||
const memories = response.results ?? [];
|
||||
if (memories.length >= minCount) {
|
||||
memories = response.results ?? [];
|
||||
|
||||
const ids = memories
|
||||
.map((m) => m.id)
|
||||
.sort()
|
||||
.join(",");
|
||||
if (memories.length >= minCount && ids === previousIds) {
|
||||
return memories;
|
||||
}
|
||||
previousIds = ids;
|
||||
|
||||
if (attempt < maxRetries) {
|
||||
await new Promise((r) => setTimeout(r, 15_000));
|
||||
}
|
||||
}
|
||||
|
||||
if (memories.length >= minCount) {
|
||||
return memories;
|
||||
}
|
||||
throw new Error(
|
||||
`waitForMemories: expected at least ${minCount} memories for user "${userId}" but did not get them after ${maxRetries} attempts`,
|
||||
);
|
||||
@@ -202,32 +220,3 @@ export async function cleanupTestUser(
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Full project wipe — deletes all memories and all entities.
|
||||
* Equivalent to Python SDK's:
|
||||
* client.delete_all(user_id="*", agent_id="*", app_id="*", run_id="*")
|
||||
*
|
||||
* Used as cleanup before and after integration test runs so tests
|
||||
* start from a clean slate and don't leave data behind.
|
||||
*/
|
||||
export async function fullProjectCleanup(client: MemoryClient): Promise<void> {
|
||||
// Delete all memories — all four filters set explicitly
|
||||
try {
|
||||
await client.deleteAll({
|
||||
userId: "*",
|
||||
agentId: "*",
|
||||
appId: "*",
|
||||
runId: "*",
|
||||
});
|
||||
} catch {
|
||||
// ignore — may 404 if no data exists
|
||||
}
|
||||
|
||||
// Delete all entities (users, agents, apps, runs)
|
||||
try {
|
||||
await client.deleteUsers();
|
||||
} catch {
|
||||
// ignore — may throw "No entities to delete"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,6 +100,7 @@ describeIntegration("MemoryClient Integration — Users & Project", () => {
|
||||
const hookName = `test-hook-${randomUUID().slice(0, 8)}`;
|
||||
const hookUrl = `https://example.com/webhook/${randomUUID().slice(0, 8)}`;
|
||||
const updatedName = `updated-hook-${randomUUID().slice(0, 8)}`;
|
||||
const updatedUrl = `https://example.com/updated/${randomUUID().slice(0, 8)}`;
|
||||
|
||||
afterAll(async () => {
|
||||
if (createdWebhookId) {
|
||||
@@ -168,7 +169,7 @@ describeIntegration("MemoryClient Integration — Users & Project", () => {
|
||||
client.updateWebhook({
|
||||
webhookId: createdWebhookId,
|
||||
name: updatedName,
|
||||
url: "https://example.com/updated",
|
||||
url: updatedUrl,
|
||||
eventTypes: [WebhookEvent.MEMORY_DELETED],
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -373,3 +373,56 @@ describe("MemoryClient - getAll() entity param rejection", () => {
|
||||
expect(getFetchBody(call!).show_expired).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("MemoryClient - getAll() page/pageSize query params", () => {
|
||||
test("sends no query params when neither page nor pageSize is provided", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v3/memories/", { status: 200, body: { results: [] } });
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.getAll({ filters: { user_id: "u1" } });
|
||||
|
||||
const call = findFetchCall(mock, "/v3/memories/", "POST");
|
||||
expect(call![0]).not.toContain("?");
|
||||
});
|
||||
|
||||
test("sends page alone as a query param", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v3/memories/", { status: 200, body: { results: [] } });
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.getAll({ filters: { user_id: "u1" }, page: 2 });
|
||||
|
||||
const call = findFetchCall(mock, "/v3/memories/", "POST");
|
||||
expect(call![0]).toContain("?page=2");
|
||||
expect(call![0]).not.toContain("page_size");
|
||||
});
|
||||
|
||||
test("sends pageSize alone as a query param", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v3/memories/", { status: 200, body: { results: [] } });
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.getAll({ filters: { user_id: "u1" }, pageSize: 50 });
|
||||
|
||||
const call = findFetchCall(mock, "/v3/memories/", "POST");
|
||||
expect(call![0]).toContain("?page_size=50");
|
||||
expect(call![0]).not.toContain("?page=50");
|
||||
});
|
||||
|
||||
test("sends both page and pageSize when provided, including falsy 0 values", async () => {
|
||||
const extra = new Map<string, { status: number; body: unknown }>();
|
||||
extra.set("/v3/memories/", { status: 200, body: { results: [] } });
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.getAll({ filters: { user_id: "u1" }, page: 0, pageSize: 0 });
|
||||
|
||||
const call = findFetchCall(mock, "/v3/memories/", "POST");
|
||||
expect(call![0]).toContain("page=0");
|
||||
expect(call![0]).toContain("page_size=0");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import { waitForMemories } from "./integration/helpers";
|
||||
|
||||
const clientReturning = (polls: Array<Array<{ id: string }>>) => {
|
||||
let call = 0;
|
||||
return {
|
||||
getAll: jest.fn(async () => ({
|
||||
results: polls[Math.min(call++, polls.length - 1)],
|
||||
})),
|
||||
} as any;
|
||||
};
|
||||
|
||||
const settle = async <T>(pending: Promise<T>): Promise<T> => {
|
||||
pending.catch(() => {});
|
||||
for (let i = 0; i < 4; i++) {
|
||||
await jest.advanceTimersByTimeAsync(15_000);
|
||||
}
|
||||
return pending;
|
||||
};
|
||||
|
||||
describe("waitForMemories", () => {
|
||||
beforeEach(() => jest.useFakeTimers());
|
||||
afterEach(() => jest.useRealTimers());
|
||||
|
||||
test("waits for the id set to stop changing", async () => {
|
||||
const client = clientReturning([
|
||||
[{ id: "a" }],
|
||||
[{ id: "a" }, { id: "b" }],
|
||||
[{ id: "a" }, { id: "b" }],
|
||||
]);
|
||||
|
||||
const memories = await settle(waitForMemories(client, "user", 1));
|
||||
|
||||
expect(memories.map((m) => m.id)).toEqual(["a", "b"]);
|
||||
expect(client.getAll).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
test("keeps polling when consolidation replaces a memory at a constant count", async () => {
|
||||
const client = clientReturning([
|
||||
[{ id: "a" }, { id: "b" }],
|
||||
[{ id: "a" }, { id: "c" }],
|
||||
[{ id: "a" }, { id: "c" }],
|
||||
]);
|
||||
|
||||
const memories = await settle(waitForMemories(client, "user", 1));
|
||||
|
||||
expect(memories.map((m) => m.id)).toEqual(["a", "c"]);
|
||||
});
|
||||
|
||||
test("returns the last read when the set never settles but minCount is met", async () => {
|
||||
const client = clientReturning([
|
||||
[{ id: "a" }],
|
||||
[{ id: "b" }],
|
||||
[{ id: "c" }],
|
||||
[{ id: "d" }],
|
||||
]);
|
||||
|
||||
const memories = await settle(waitForMemories(client, "user", 1));
|
||||
|
||||
expect(memories.map((m) => m.id)).toEqual(["d"]);
|
||||
});
|
||||
|
||||
test("throws when minCount is never reached", async () => {
|
||||
const client = clientReturning([[]]);
|
||||
|
||||
await expect(settle(waitForMemories(client, "user", 1))).rejects.toThrow(
|
||||
/expected at least 1 memories/,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -29,7 +29,18 @@ const PROVIDERS = [
|
||||
* Extract the model-family provider from a Bedrock model id
|
||||
* (e.g. `anthropic.claude-3-sonnet-...` -> `anthropic`).
|
||||
*/
|
||||
export function extractProvider(model: string): string {
|
||||
export function extractProvider(
|
||||
model: string,
|
||||
providerOverride?: string,
|
||||
): string {
|
||||
if (providerOverride) {
|
||||
if (!PROVIDERS.includes(providerOverride)) {
|
||||
throw new Error(
|
||||
`Unknown providerOverride '${providerOverride}'. Valid providers: ${PROVIDERS.join(", ")}`,
|
||||
);
|
||||
}
|
||||
return providerOverride;
|
||||
}
|
||||
for (const provider of PROVIDERS) {
|
||||
const re = new RegExp(
|
||||
`\\b${provider.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`,
|
||||
@@ -79,7 +90,7 @@ export class AWSBedrockLLM implements LLM {
|
||||
this.model =
|
||||
(typeof config.model === "string" && config.model) ||
|
||||
"anthropic.claude-3-5-sonnet-20240620-v1:0";
|
||||
this.provider = extractProvider(this.model);
|
||||
this.provider = extractProvider(this.model, config.providerOverride);
|
||||
this.temperature = config.temperature ?? 0.1;
|
||||
this.maxTokens = config.maxTokens ?? 2000;
|
||||
this.topP = config.topP;
|
||||
|
||||
@@ -105,6 +105,18 @@ const ENTITY_PARAMS = [
|
||||
// actor_id has no camelCase alias.
|
||||
const IDENTITY_KEYS = [...ENTITY_PARAMS, "actor_id"];
|
||||
|
||||
const PAYLOAD_METADATA_EXCLUDED_KEYS = new Set([
|
||||
"user_id",
|
||||
"agent_id",
|
||||
"run_id",
|
||||
"hash",
|
||||
"data",
|
||||
"createdAt",
|
||||
"updatedAt",
|
||||
"textLemmatized",
|
||||
"attributedTo",
|
||||
]);
|
||||
|
||||
// Caller metadata must not overwrite or inject an identity scope (#6342 / #6367 / #6371).
|
||||
function stripIdentityKeys(
|
||||
metadata: Record<string, any> = {},
|
||||
@@ -1000,7 +1012,8 @@ export class Memory {
|
||||
|
||||
for (const mem of extractedMemories) {
|
||||
const text = mem.text;
|
||||
if (!text || !(text in embedMap)) continue;
|
||||
if (!text || !Object.prototype.hasOwnProperty.call(embedMap, text))
|
||||
continue;
|
||||
|
||||
const memHash = createHash("md5").update(text).digest("hex");
|
||||
if (existingHashes.has(memHash) || seenHashes.has(memHash)) {
|
||||
@@ -1297,20 +1310,8 @@ export class Memory {
|
||||
metadata: {},
|
||||
};
|
||||
|
||||
// Add additional metadata
|
||||
const excludedKeys = new Set([
|
||||
"userId",
|
||||
"agentId",
|
||||
"runId",
|
||||
"hash",
|
||||
"data",
|
||||
"createdAt",
|
||||
"updatedAt",
|
||||
"textLemmatized",
|
||||
"attributedTo",
|
||||
]);
|
||||
for (const [key, value] of Object.entries(memory.payload)) {
|
||||
if (!excludedKeys.has(key)) {
|
||||
if (!PAYLOAD_METADATA_EXCLUDED_KEYS.has(key)) {
|
||||
memoryItem.metadata![key] = value;
|
||||
}
|
||||
}
|
||||
@@ -1566,18 +1567,6 @@ export class Memory {
|
||||
);
|
||||
|
||||
// Step 9: Format results
|
||||
const excludedKeys = new Set([
|
||||
"user_id",
|
||||
"agent_id",
|
||||
"run_id",
|
||||
"hash",
|
||||
"data",
|
||||
"createdAt",
|
||||
"updatedAt",
|
||||
"textLemmatized",
|
||||
"attributedTo",
|
||||
]);
|
||||
|
||||
const results = scoredResults
|
||||
.filter((scored) => scored.payload?.data)
|
||||
.map((scored) => {
|
||||
@@ -1590,7 +1579,7 @@ export class Memory {
|
||||
updatedAt: payload.updatedAt,
|
||||
score: scored.score,
|
||||
metadata: Object.entries(payload)
|
||||
.filter(([key]) => !excludedKeys.has(key))
|
||||
.filter(([key]) => !PAYLOAD_METADATA_EXCLUDED_KEYS.has(key))
|
||||
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
|
||||
...(payload.user_id && { user_id: payload.user_id }),
|
||||
...(payload.agent_id && { agent_id: payload.agent_id }),
|
||||
@@ -1896,17 +1885,6 @@ export class Memory {
|
||||
? memories
|
||||
: memories.filter((mem) => !payloadIsExpired(mem.payload));
|
||||
|
||||
const excludedKeys = new Set([
|
||||
"user_id",
|
||||
"agent_id",
|
||||
"run_id",
|
||||
"hash",
|
||||
"data",
|
||||
"createdAt",
|
||||
"updatedAt",
|
||||
"textLemmatized",
|
||||
"attributedTo",
|
||||
]);
|
||||
const results = visibleMemories.slice(0, topK).map((mem) => ({
|
||||
id: mem.id,
|
||||
memory: mem.payload.data,
|
||||
@@ -1914,7 +1892,7 @@ export class Memory {
|
||||
createdAt: mem.payload.createdAt,
|
||||
updatedAt: mem.payload.updatedAt,
|
||||
metadata: Object.entries(mem.payload)
|
||||
.filter(([key]) => !excludedKeys.has(key))
|
||||
.filter(([key]) => !PAYLOAD_METADATA_EXCLUDED_KEYS.has(key))
|
||||
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
|
||||
...(mem.payload.user_id && { user_id: mem.payload.user_id }),
|
||||
...(mem.payload.agent_id && { agent_id: mem.payload.agent_id }),
|
||||
@@ -1943,8 +1921,12 @@ export class Memory {
|
||||
metadata: Record<string, any>,
|
||||
): Promise<string> {
|
||||
const memoryId = uuidv4();
|
||||
const embedding =
|
||||
existingEmbeddings[data] || (await this.embedder.embed(data, "add"));
|
||||
const embedding = Object.prototype.hasOwnProperty.call(
|
||||
existingEmbeddings,
|
||||
data,
|
||||
)
|
||||
? existingEmbeddings[data]
|
||||
: await this.embedder.embed(data, "add");
|
||||
|
||||
const memoryMetadata = {
|
||||
...metadata,
|
||||
@@ -1987,9 +1969,12 @@ export class Memory {
|
||||
}
|
||||
const textChanged = newData !== prevValue;
|
||||
|
||||
const embedding =
|
||||
existingEmbeddings[newData] ||
|
||||
(await this.embedder.embed(newData, "update"));
|
||||
const embedding = Object.prototype.hasOwnProperty.call(
|
||||
existingEmbeddings,
|
||||
newData,
|
||||
)
|
||||
? existingEmbeddings[newData]
|
||||
: await this.embedder.embed(newData, "update");
|
||||
|
||||
const sanitizedMetadata = stripIdentityKeys(metadata);
|
||||
|
||||
|
||||
@@ -68,6 +68,29 @@ describe("extractProvider", () => {
|
||||
/Unknown provider/,
|
||||
);
|
||||
});
|
||||
|
||||
const ARN =
|
||||
"arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/abc123xyz";
|
||||
|
||||
it("throws for an application inference profile ARN without providerOverride", () => {
|
||||
expect(() => extractProvider(ARN)).toThrow(/Unknown provider in model/);
|
||||
});
|
||||
|
||||
it("resolves an application inference profile ARN via providerOverride", () => {
|
||||
expect(extractProvider(ARN, "anthropic")).toBe("anthropic");
|
||||
});
|
||||
|
||||
it("lets providerOverride take precedence over regex detection", () => {
|
||||
expect(
|
||||
extractProvider("anthropic.claude-3-5-sonnet-20240620-v1:0", "amazon"),
|
||||
).toBe("amazon");
|
||||
});
|
||||
|
||||
it("throws on a misspelled providerOverride", () => {
|
||||
expect(() => extractProvider(ARN, "anthorpic")).toThrow(
|
||||
/Unknown providerOverride 'anthorpic'/,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("AWSBedrockLLM", () => {
|
||||
@@ -176,6 +199,28 @@ describe("AWSBedrockLLM", () => {
|
||||
expect(res).toEqual({ content: "hello from bedrock", role: "assistant" });
|
||||
});
|
||||
|
||||
it("uses providerOverride to resolve inference profile ARNs (topP omitted like anthropic)", async () => {
|
||||
const arn =
|
||||
"arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/abc123xyz";
|
||||
const client = new FakeBedrockClient(textResponse);
|
||||
const llm = makeLLM(client, {
|
||||
model: arn,
|
||||
providerOverride: "anthropic",
|
||||
topP: 0.9,
|
||||
});
|
||||
await llm.generateResponse([{ role: "user", content: "hi" }]);
|
||||
expect(client.lastInput.modelId).toBe(arn);
|
||||
expect(client.lastInput.inferenceConfig.topP).toBeUndefined();
|
||||
});
|
||||
|
||||
it("throws when constructed with an inference profile ARN and no providerOverride", () => {
|
||||
const arn =
|
||||
"arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/abc123xyz";
|
||||
expect(() => new AWSBedrockLLM({ model: arn })).toThrow(
|
||||
/Unknown provider in model/,
|
||||
);
|
||||
});
|
||||
|
||||
it("does not construct the Bedrock client until the first request", () => {
|
||||
const before = mockClientConstructions;
|
||||
// No injected client: the old constructor eagerly built a real one.
|
||||
|
||||
@@ -78,6 +78,7 @@ export interface LLMConfig {
|
||||
awsAccessKeyId?: string;
|
||||
awsSecretAccessKey?: string;
|
||||
awsSessionToken?: string;
|
||||
providerOverride?: string;
|
||||
// Optional pre-constructed client (e.g. BedrockRuntimeClient) for DI/testing.
|
||||
client?: any;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
function toCamelCase(obj: Record<string, any>): Record<string, any> {
|
||||
if (typeof obj !== "object" || obj === null) return obj;
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(obj).map(([key, value]) => [
|
||||
key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()),
|
||||
value,
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
export function toCamelCasePreservingIds(
|
||||
payload: Record<string, any>,
|
||||
): Record<string, any> {
|
||||
const { agent_id, run_id, user_id, ...rest } = payload;
|
||||
return {
|
||||
...toCamelCase(rest),
|
||||
...(agent_id !== undefined && { agent_id }),
|
||||
...(run_id !== undefined && { run_id }),
|
||||
...(user_id !== undefined && { user_id }),
|
||||
};
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import type {
|
||||
import { VectorStore } from "./base";
|
||||
import { SearchFilters, VectorStoreConfig, VectorStoreResult } from "../types";
|
||||
import { loadPeer } from "../utils/load_peer";
|
||||
import { toCamelCasePreservingIds } from "../utils/casing";
|
||||
|
||||
/**
|
||||
* Escape RediSearch TAG filter special characters. Any punctuation in the
|
||||
@@ -145,18 +146,6 @@ export function buildRedisFilterExpr(filters?: SearchFilters): string {
|
||||
return conditions.length > 0 ? conditions.join(" ") : "*";
|
||||
}
|
||||
|
||||
// Utility function to convert object keys to camelCase
|
||||
function toCamelCase(obj: Record<string, any>): Record<string, any> {
|
||||
if (typeof obj !== "object" || obj === null) return obj;
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(obj).map(([key, value]) => [
|
||||
key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()),
|
||||
value,
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
export class RedisDB implements VectorStore {
|
||||
private client!: RedisClientType<
|
||||
RedisDefaultModules & RedisModules & RedisFunctions & RedisScripts
|
||||
@@ -468,7 +457,7 @@ export class RedisDB implements VectorStore {
|
||||
|
||||
return {
|
||||
id: doc.value.memory_id,
|
||||
payload: toCamelCase(resultPayload),
|
||||
payload: toCamelCasePreservingIds(resultPayload),
|
||||
score: Math.max(0, 1 - (Number(doc.value.__vector_score) ?? 0)),
|
||||
};
|
||||
});
|
||||
@@ -573,7 +562,7 @@ export class RedisDB implements VectorStore {
|
||||
|
||||
return {
|
||||
id: vectorId,
|
||||
payload: toCamelCase(payload),
|
||||
payload: toCamelCasePreservingIds(payload),
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error getting vector:", error);
|
||||
@@ -680,7 +669,7 @@ export class RedisDB implements VectorStore {
|
||||
|
||||
const items = results.documents.map((doc) => ({
|
||||
id: doc.value.memory_id,
|
||||
payload: toCamelCase({
|
||||
payload: toCamelCasePreservingIds({
|
||||
hash: doc.value.hash,
|
||||
data: doc.value.memory,
|
||||
created_at: new Date(parseInt(doc.value.created_at)).toISOString(),
|
||||
|
||||
@@ -2,6 +2,7 @@ import { VectorStore } from "./base";
|
||||
import { SearchFilters, VectorStoreResult } from "../types";
|
||||
import { ValkeyConfig } from "../types/valkey";
|
||||
import { loadPeer } from "../utils/load_peer";
|
||||
import { toCamelCasePreservingIds } from "../utils/casing";
|
||||
|
||||
interface ValkeyClient {
|
||||
call: (...args: (string | number | Buffer)[]) => Promise<unknown>;
|
||||
@@ -45,16 +46,6 @@ function toSnakeCase(obj: Record<string, any>): Record<string, any> {
|
||||
);
|
||||
}
|
||||
|
||||
function toCamelCase(obj: Record<string, any>): Record<string, any> {
|
||||
if (typeof obj !== "object" || obj === null) return obj;
|
||||
return Object.fromEntries(
|
||||
Object.entries(obj).map(([key, value]) => [
|
||||
key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()),
|
||||
value,
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
interface ValkeySearchDoc {
|
||||
memory_id?: string;
|
||||
hash?: string;
|
||||
@@ -388,7 +379,7 @@ export class ValkeyDB implements VectorStore {
|
||||
|
||||
return {
|
||||
id: doc.memory_id ?? "",
|
||||
payload: toCamelCase(resultPayload),
|
||||
payload: toCamelCasePreservingIds(resultPayload),
|
||||
score,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -269,9 +269,7 @@ describe("Memory - add()", () => {
|
||||
"runId",
|
||||
"actor_id",
|
||||
]) {
|
||||
if (key !== canonicalKey) {
|
||||
expect(stored!.metadata).not.toHaveProperty(key);
|
||||
}
|
||||
expect(stored!.metadata).not.toHaveProperty(key);
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -307,10 +305,7 @@ describe("Memory - add()", () => {
|
||||
}
|
||||
}
|
||||
expect(stored!.metadata).toEqual(
|
||||
expect.objectContaining({
|
||||
[filterKey]: filterValue,
|
||||
ordinary: "preserved",
|
||||
}),
|
||||
expect.objectContaining({ ordinary: "preserved" }),
|
||||
);
|
||||
for (const key of [
|
||||
"user_id",
|
||||
@@ -321,7 +316,6 @@ describe("Memory - add()", () => {
|
||||
"runId",
|
||||
"actor_id",
|
||||
]) {
|
||||
if (key === filterKey) continue;
|
||||
expect(stored!.metadata).not.toHaveProperty(key);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -128,6 +128,16 @@ describe("Memory - get()", () => {
|
||||
expect(item!.createdAt).toBeDefined();
|
||||
expect(new Date(item!.createdAt!).toString()).not.toBe("Invalid Date");
|
||||
});
|
||||
|
||||
test("does not duplicate the entity id inside metadata", async () => {
|
||||
const addResult: SearchResult = await memory.add("Metadata leak test", {
|
||||
userId,
|
||||
});
|
||||
const item: any = await memory.get(addResult.results[0].id);
|
||||
expect(item.user_id).toBe(userId);
|
||||
expect(item.metadata).not.toHaveProperty("user_id");
|
||||
expect(item.metadata).not.toHaveProperty("userId");
|
||||
});
|
||||
});
|
||||
|
||||
// ─── update() ────────────────────────────────────────────
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
/// <reference types="jest" />
|
||||
/** add() with infer=false must embed Object.prototype-colliding text instead of resolving it off the prototype chain. */
|
||||
import { Memory } from "../src/memory";
|
||||
import { MemoryVectorStore } from "../src/vector_stores/memory";
|
||||
import type { SearchResult } from "../src/types";
|
||||
|
||||
const mockEmbedding = new Array(1536).fill(0.1);
|
||||
const mockEmbed = jest.fn().mockResolvedValue(mockEmbedding);
|
||||
|
||||
jest.mock("../src/embeddings/openai", () => ({
|
||||
OpenAIEmbedder: jest.fn().mockImplementation(() => ({
|
||||
embed: mockEmbed,
|
||||
embedBatch: jest.fn(),
|
||||
embeddingDims: 1536,
|
||||
})),
|
||||
}));
|
||||
|
||||
function createMemory(): Memory {
|
||||
return new Memory({
|
||||
version: "v1.1",
|
||||
embedder: {
|
||||
provider: "openai",
|
||||
config: { apiKey: "test-key", model: "text-embedding-3-small" },
|
||||
},
|
||||
vectorStore: {
|
||||
provider: "memory",
|
||||
config: {
|
||||
collectionName: `test-proto-${Date.now()}-${Math.random()}`,
|
||||
dimension: 1536,
|
||||
dbPath: ":memory:",
|
||||
},
|
||||
},
|
||||
llm: {
|
||||
provider: "openai",
|
||||
config: { apiKey: "test-key", model: "gpt-5-mini" },
|
||||
},
|
||||
historyDbPath: ":memory:",
|
||||
});
|
||||
}
|
||||
|
||||
describe("add() with infer=false and Object.prototype-colliding text", () => {
|
||||
let memory: Memory;
|
||||
let insertSpy: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
memory = createMemory();
|
||||
mockEmbed.mockClear();
|
||||
insertSpy = jest.spyOn(MemoryVectorStore.prototype, "insert");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
insertSpy.mockRestore();
|
||||
await memory.reset();
|
||||
});
|
||||
|
||||
test.each([
|
||||
"constructor",
|
||||
"toString",
|
||||
"valueOf",
|
||||
"hasOwnProperty",
|
||||
"__proto__",
|
||||
])(
|
||||
'embeds "%s" instead of resolving it off Object.prototype',
|
||||
async (text) => {
|
||||
const result: SearchResult = await memory.add(text, {
|
||||
userId: "u1",
|
||||
infer: false,
|
||||
});
|
||||
|
||||
expect(mockEmbed).toHaveBeenCalledWith(text, "add");
|
||||
|
||||
const [storedVectors] =
|
||||
insertSpy.mock.calls[insertSpy.mock.calls.length - 1];
|
||||
expect(Array.isArray(storedVectors[0])).toBe(true);
|
||||
expect(storedVectors[0]).toEqual(mockEmbedding);
|
||||
expect(
|
||||
storedVectors[0].every((n: unknown) => typeof n === "number"),
|
||||
).toBe(true);
|
||||
|
||||
expect(result.results[0].memory).toBe(text);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("update() metadata-only on Object.prototype-colliding text (#6323)", () => {
|
||||
let memory: Memory;
|
||||
let updateSpy: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
memory = createMemory();
|
||||
updateSpy = jest.spyOn(MemoryVectorStore.prototype, "update");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
updateSpy.mockRestore();
|
||||
await memory.reset();
|
||||
});
|
||||
|
||||
test.each([
|
||||
"constructor",
|
||||
"toString",
|
||||
"valueOf",
|
||||
"hasOwnProperty",
|
||||
"__proto__",
|
||||
])(
|
||||
're-embeds "%s" on a metadata-only update instead of resolving it off Object.prototype',
|
||||
async (text) => {
|
||||
const added: SearchResult = await memory.add(text, {
|
||||
userId: "u1",
|
||||
infer: false,
|
||||
});
|
||||
const memoryId = added.results[0].id!;
|
||||
mockEmbed.mockClear();
|
||||
|
||||
await memory.update(memoryId, { metadata: { pinned: true } });
|
||||
|
||||
expect(mockEmbed).toHaveBeenCalledWith(text, "update");
|
||||
|
||||
const [, storedVector] =
|
||||
updateSpy.mock.calls[updateSpy.mock.calls.length - 1];
|
||||
expect(Array.isArray(storedVector)).toBe(true);
|
||||
expect(storedVector).toEqual(mockEmbedding);
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -125,4 +125,88 @@ describe("RedisDB – entity payload handling", () => {
|
||||
expect(entry.created_at).toBeGreaterThan(0);
|
||||
expect(Number.isNaN(entry.created_at)).toBe(false);
|
||||
});
|
||||
|
||||
test("search returns entity ids as snake_case", async () => {
|
||||
mockClient.ft.search.mockResolvedValue({
|
||||
total: 1,
|
||||
documents: [
|
||||
{
|
||||
id: "mem0:test:mem-1",
|
||||
value: {
|
||||
memory_id: "mem-1",
|
||||
hash: "h1",
|
||||
memory: "likes coffee",
|
||||
created_at: "1700000000000",
|
||||
agent_id: "agent-1",
|
||||
run_id: "run-1",
|
||||
user_id: "user-1",
|
||||
metadata: "{}",
|
||||
__vector_score: 0.1,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const results = await store.search([0.1, 0.2, 0.3, 0.4], 5);
|
||||
|
||||
expect(results[0].payload.user_id).toBe("user-1");
|
||||
expect(results[0].payload.agent_id).toBe("agent-1");
|
||||
expect(results[0].payload.run_id).toBe("run-1");
|
||||
expect(results[0].payload).not.toHaveProperty("userId");
|
||||
expect(results[0].payload).not.toHaveProperty("agentId");
|
||||
expect(results[0].payload).not.toHaveProperty("runId");
|
||||
});
|
||||
|
||||
test("get returns entity ids as snake_case", async () => {
|
||||
mockClient.exists.mockResolvedValue(1);
|
||||
mockClient.hGetAll.mockResolvedValue({
|
||||
memory_id: "mem-1",
|
||||
hash: "h1",
|
||||
memory: "likes coffee",
|
||||
created_at: "1700000000000",
|
||||
agent_id: "agent-1",
|
||||
run_id: "run-1",
|
||||
user_id: "user-1",
|
||||
metadata: "{}",
|
||||
});
|
||||
|
||||
const result = await store.get("mem-1");
|
||||
|
||||
expect(result?.payload.user_id).toBe("user-1");
|
||||
expect(result?.payload.agent_id).toBe("agent-1");
|
||||
expect(result?.payload.run_id).toBe("run-1");
|
||||
expect(result?.payload).not.toHaveProperty("userId");
|
||||
expect(result?.payload).not.toHaveProperty("agentId");
|
||||
expect(result?.payload).not.toHaveProperty("runId");
|
||||
});
|
||||
|
||||
test("list returns entity ids as snake_case", async () => {
|
||||
mockClient.ft.search.mockResolvedValue({
|
||||
total: 1,
|
||||
documents: [
|
||||
{
|
||||
id: "mem0:test:mem-1",
|
||||
value: {
|
||||
memory_id: "mem-1",
|
||||
hash: "h1",
|
||||
memory: "likes coffee",
|
||||
created_at: "1700000000000",
|
||||
agent_id: "agent-1",
|
||||
run_id: "run-1",
|
||||
user_id: "user-1",
|
||||
metadata: "{}",
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const [items] = await store.list();
|
||||
|
||||
expect(items[0].payload.user_id).toBe("user-1");
|
||||
expect(items[0].payload.agent_id).toBe("agent-1");
|
||||
expect(items[0].payload.run_id).toBe("run-1");
|
||||
expect(items[0].payload).not.toHaveProperty("userId");
|
||||
expect(items[0].payload).not.toHaveProperty("agentId");
|
||||
expect(items[0].payload).not.toHaveProperty("runId");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -166,7 +166,7 @@ describe("Valkey – mocked iovalkey client", () => {
|
||||
const result = await store.get("mem-1");
|
||||
expect(result?.id).toBe("mem-1");
|
||||
expect(result?.payload.data).toBe("hello valkey");
|
||||
expect(result?.payload.userId).toBe("alice");
|
||||
expect(result?.payload.user_id).toBe("alice");
|
||||
// created_at is persisted as unix seconds and rendered back to its ISO instant.
|
||||
expect(result?.payload.createdAt).toBe("2024-01-01T00:00:00.000Z");
|
||||
});
|
||||
@@ -244,6 +244,91 @@ describe("Valkey – mocked iovalkey client", () => {
|
||||
expect(searchCall[2]).toContain("@user_id:{a\\|b\\ c}");
|
||||
});
|
||||
|
||||
it("get returns entity ids as snake_case", async () => {
|
||||
const store = new ValkeyDB({
|
||||
collectionName: "test",
|
||||
embeddingModelDims: 4,
|
||||
valkeyUrl: "valkey://localhost:6379",
|
||||
});
|
||||
await store.initialize();
|
||||
|
||||
await store.insert(
|
||||
[[0.1, 0.2, 0.3, 0.4]],
|
||||
["mem-ids"],
|
||||
[
|
||||
{
|
||||
data: "likes tea",
|
||||
user_id: "user-1",
|
||||
agent_id: "agent-1",
|
||||
run_id: "run-1",
|
||||
},
|
||||
],
|
||||
);
|
||||
|
||||
const result = await store.get("mem-ids");
|
||||
expect(result?.payload.user_id).toBe("user-1");
|
||||
expect(result?.payload.agent_id).toBe("agent-1");
|
||||
expect(result?.payload.run_id).toBe("run-1");
|
||||
expect(result?.payload).not.toHaveProperty("userId");
|
||||
expect(result?.payload).not.toHaveProperty("agentId");
|
||||
expect(result?.payload).not.toHaveProperty("runId");
|
||||
});
|
||||
|
||||
it("search and list return entity ids as snake_case", async () => {
|
||||
const store = new ValkeyDB({
|
||||
collectionName: "test",
|
||||
embeddingModelDims: 4,
|
||||
valkeyUrl: "valkey://localhost:6379",
|
||||
});
|
||||
await store.initialize();
|
||||
|
||||
const iovalkey = require("iovalkey");
|
||||
const mockClient = iovalkey.__mockClient;
|
||||
mockClient.call.mockImplementation(async (...args: any[]) => {
|
||||
if (args[0] === "FT.SEARCH") {
|
||||
return [
|
||||
1,
|
||||
"mem0:test:mem-search",
|
||||
[
|
||||
"memory_id",
|
||||
"mem-search",
|
||||
"hash",
|
||||
"h1",
|
||||
"memory",
|
||||
"likes tea",
|
||||
"created_at",
|
||||
"1700000000",
|
||||
"agent_id",
|
||||
"agent-1",
|
||||
"run_id",
|
||||
"run-1",
|
||||
"user_id",
|
||||
"user-1",
|
||||
"metadata",
|
||||
"{}",
|
||||
"vector_score",
|
||||
"0.1",
|
||||
],
|
||||
];
|
||||
}
|
||||
return "OK";
|
||||
});
|
||||
|
||||
const results = await store.search([0.1, 0.2, 0.3, 0.4], 5);
|
||||
expect(results[0].payload.user_id).toBe("user-1");
|
||||
expect(results[0].payload.agent_id).toBe("agent-1");
|
||||
expect(results[0].payload.run_id).toBe("run-1");
|
||||
expect(results[0].payload).not.toHaveProperty("userId");
|
||||
expect(results[0].payload).not.toHaveProperty("agentId");
|
||||
expect(results[0].payload).not.toHaveProperty("runId");
|
||||
|
||||
const [listed] = await store.list();
|
||||
expect(listed[0].payload.user_id).toBe("user-1");
|
||||
expect(listed[0].payload.agent_id).toBe("agent-1");
|
||||
expect(listed[0].payload.run_id).toBe("run-1");
|
||||
expect(listed[0].payload).not.toHaveProperty("userId");
|
||||
});
|
||||
|
||||
it("does not raise an unhandled rejection when initialization fails", async () => {
|
||||
const iovalkey = require("iovalkey");
|
||||
iovalkey.__mockClient.call.mockImplementationOnce(async () => {
|
||||
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
# Python SDK (`mem0/`)
|
||||
|
||||
The `mem0ai` package on PyPI. Memory core plus five pluggable provider categories.
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
hatch shell dev_py_3_11 # or dev_py_3_9 / dev_py_3_10 / dev_py_3_12
|
||||
pre-commit install # first time only; runs ruff + isort on commit
|
||||
|
||||
make lint # ruff check
|
||||
make format # ruff format
|
||||
make sort # isort mem0/
|
||||
make test # pytest tests/
|
||||
make test-py-3.9 # pin a Python version (3.9 through 3.12)
|
||||
make install_all # optional deps; run before the full test suite
|
||||
make build # hatch build
|
||||
```
|
||||
|
||||
Use `hatch` for environments and dependencies. Do not use `pip` or `conda`.
|
||||
|
||||
## Conventions
|
||||
|
||||
- **Python 3.9 through 3.12.** Code must run on 3.9.
|
||||
- **Ruff**, line length **120**. `cli/python/` uses 100; do not carry that config across.
|
||||
- **isort**, `profile = "black"`, first-party `mem0` and `mem0_cli`.
|
||||
- **Pydantic v2** for every data model and config class.
|
||||
- **pytest** with pytest-mock and pytest-asyncio. Tests live in `../tests/`.
|
||||
- Source files are `snake_case.py`.
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
mem0/
|
||||
├── memory/ Memory, AsyncMemory
|
||||
├── client/ MemoryClient, AsyncMemoryClient
|
||||
├── configs/ MemoryConfig and per-category config models
|
||||
├── llms/ 24 providers
|
||||
├── embeddings/ 15 providers
|
||||
├── vector_stores/ 30 providers
|
||||
├── graphs/ 4 providers
|
||||
└── reranker/ 5 providers
|
||||
```
|
||||
|
||||
## Provider pattern
|
||||
|
||||
Every category follows the same shape: a `base.py` with the abstract class, one module per provider, config models in `configs.py`, registration in `__init__.py`.
|
||||
|
||||
| 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 |
|
||||
|
||||
### Adding a provider
|
||||
|
||||
1. Create `mem0/<category>/<provider_name>.py`.
|
||||
2. Inherit the abstract base class from `mem0/<category>/base.py`.
|
||||
3. Add its config to `mem0/<category>/configs.py` if the category uses one.
|
||||
4. Register it in `mem0/<category>/__init__.py`.
|
||||
5. Add tests under `tests/<category>/<provider_name>/`.
|
||||
6. Put new dependencies in an **optional** group in `pyproject.toml`, never in core `dependencies`.
|
||||
7. Match an existing provider in the same category exactly: method signatures, error handling, config structure.
|
||||
8. Add an integration guide under `docs/integrations/`.
|
||||
|
||||
## Public API
|
||||
|
||||
| Class | Purpose | Import |
|
||||
|-------|---------|--------|
|
||||
| `Memory` | Self-hosted, sync | `from mem0 import Memory` |
|
||||
| `AsyncMemory` | Self-hosted, async | `from mem0 import AsyncMemory` |
|
||||
| `MemoryClient` | Hosted platform, sync | `from mem0 import MemoryClient` |
|
||||
| `AsyncMemoryClient` | Hosted platform, async | `from mem0 import AsyncMemoryClient` |
|
||||
|
||||
Both `Memory` and `MemoryClient` expose the same surface:
|
||||
|
||||
| Method | Purpose |
|
||||
|--------|---------|
|
||||
| `add(messages, *, user_id, agent_id, run_id, metadata)` | Store a memory |
|
||||
| `search(query, *, user_id, agent_id, run_id, limit, filters)` | Search memories |
|
||||
| `get(memory_id)` | Fetch one memory |
|
||||
| `get_all(*, user_id, agent_id, run_id, limit)` | List memories |
|
||||
| `update(memory_id, data)` | Update a memory |
|
||||
| `delete(memory_id)` | Delete a memory |
|
||||
| `delete_all(*, user_id, agent_id, run_id)` | Delete a scope |
|
||||
| `history(memory_id)` | Change history for a memory |
|
||||
|
||||
Changing any of these signatures means updating `docs/` in the same PR.
|
||||
|
||||
### Import paths
|
||||
|
||||
| What | Import |
|
||||
|------|--------|
|
||||
| Memory classes | `from mem0 import Memory, AsyncMemory` |
|
||||
| Platform client | `from mem0 import MemoryClient, AsyncMemoryClient` |
|
||||
| Configuration | `from mem0.configs.base import MemoryConfig` |
|
||||
| LLM provider | `from mem0.llms.<provider> import <ProviderLLM>` |
|
||||
| Embedding provider | `from mem0.embeddings.<provider> import <ProviderEmbedding>` |
|
||||
| Vector store provider | `from mem0.vector_stores.<provider> import <ProviderVectorStore>` |
|
||||
|
||||
## Graph memory
|
||||
|
||||
An optional layer on top of vector memory for relationship-aware retrieval, configured through the `graph` section of `MemoryConfig`. It supplements vector search rather than replacing it.
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
AGENTS.md
|
||||
+4
-10
@@ -275,11 +275,8 @@ class MemoryClient:
|
||||
kwargs = {**(options.model_dump(exclude_unset=True) if options else {}), **kwargs}
|
||||
params = self._prepare_params(kwargs)
|
||||
|
||||
if "page" in params and "page_size" in params:
|
||||
query_params = {
|
||||
"page": params.pop("page"),
|
||||
"page_size": params.pop("page_size"),
|
||||
}
|
||||
query_params = {key: params.pop(key) for key in ("page", "page_size") if key in params}
|
||||
if query_params:
|
||||
response = self.client.post("/v3/memories/", json=params, params=query_params)
|
||||
else:
|
||||
response = self.client.post("/v3/memories/", json=params)
|
||||
@@ -1198,11 +1195,8 @@ class AsyncMemoryClient:
|
||||
kwargs = {**(options.model_dump(exclude_unset=True) if options else {}), **kwargs}
|
||||
params = self._prepare_params(kwargs)
|
||||
|
||||
if "page" in params and "page_size" in params:
|
||||
query_params = {
|
||||
"page": params.pop("page"),
|
||||
"page_size": params.pop("page_size"),
|
||||
}
|
||||
query_params = {key: params.pop(key) for key in ("page", "page_size") if key in params}
|
||||
if query_params:
|
||||
response = await self.async_client.post("/v3/memories/", json=params, params=query_params)
|
||||
else:
|
||||
response = await self.async_client.post("/v3/memories/", json=params)
|
||||
|
||||
@@ -24,6 +24,7 @@ class AWSBedrockConfig(BaseLlmConfig):
|
||||
aws_session_token: Optional[str] = None,
|
||||
aws_profile: Optional[str] = None,
|
||||
model_kwargs: Optional[Dict[str, Any]] = None,
|
||||
provider_override: Optional[str] = None,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
@@ -42,6 +43,10 @@ class AWSBedrockConfig(BaseLlmConfig):
|
||||
aws_session_token: AWS session token for temporary credentials
|
||||
aws_profile: AWS profile name for credentials
|
||||
model_kwargs: Additional model-specific parameters
|
||||
provider_override: Explicit provider name (e.g. "anthropic"), required when
|
||||
model is an application inference profile ARN whose opaque ID has no
|
||||
provider substring for automatic detection. Defaults to None (uses
|
||||
automatic detection from the model identifier).
|
||||
**kwargs: Additional arguments passed to base class
|
||||
"""
|
||||
super().__init__(
|
||||
@@ -59,6 +64,7 @@ class AWSBedrockConfig(BaseLlmConfig):
|
||||
self.aws_session_token = aws_session_token
|
||||
self.aws_profile = aws_profile
|
||||
self.model_kwargs = model_kwargs or {}
|
||||
self.provider_override = provider_override
|
||||
|
||||
@property
|
||||
def provider(self) -> str:
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
from mem0.configs.llms.base import BaseLlmConfig
|
||||
@@ -53,4 +54,4 @@ class VllmConfig(BaseLlmConfig):
|
||||
)
|
||||
|
||||
# vLLM-specific parameters
|
||||
self.vllm_base_url = vllm_base_url or "http://localhost:8000/v1"
|
||||
self.vllm_base_url = vllm_base_url or os.getenv("VLLM_BASE_URL") or "http://localhost:8000/v1"
|
||||
|
||||
@@ -9,8 +9,8 @@ try:
|
||||
except ImportError:
|
||||
raise ImportError("The 'boto3' library is required. Please install it using 'pip install boto3'.")
|
||||
|
||||
from mem0.configs.llms.base import BaseLlmConfig
|
||||
from mem0.configs.llms.aws_bedrock import AWSBedrockConfig
|
||||
from mem0.configs.llms.base import BaseLlmConfig
|
||||
from mem0.llms.base import LLMBase
|
||||
from mem0.memory.utils import extract_json
|
||||
|
||||
@@ -23,8 +23,14 @@ PROVIDERS = [
|
||||
]
|
||||
|
||||
|
||||
def extract_provider(model: str) -> str:
|
||||
"""Extract provider from model identifier."""
|
||||
def extract_provider(model: str, explicit_provider: Optional[str] = None) -> str:
|
||||
"""Extract provider from model identifier, or return explicit_provider when set."""
|
||||
if explicit_provider:
|
||||
if explicit_provider not in PROVIDERS:
|
||||
raise ValueError(
|
||||
f"Unknown provider_override '{explicit_provider}'. Valid providers: {', '.join(PROVIDERS)}"
|
||||
)
|
||||
return explicit_provider
|
||||
for provider in PROVIDERS:
|
||||
if re.search(rf"\b{re.escape(provider)}\b", model):
|
||||
return provider
|
||||
@@ -69,7 +75,7 @@ class AWSBedrockLLM(LLMBase):
|
||||
|
||||
# Get model configuration
|
||||
self.model_config = self.config.get_model_config()
|
||||
self.provider = extract_provider(self.config.model)
|
||||
self.provider = extract_provider(self.config.model, self.config.provider_override)
|
||||
|
||||
# Initialize provider-specific settings
|
||||
self._initialize_provider_settings()
|
||||
|
||||
+1
-2
@@ -37,8 +37,7 @@ class VllmLLM(LLMBase):
|
||||
self.config.model = "Qwen/Qwen2.5-32B-Instruct"
|
||||
|
||||
self.config.api_key = self.config.api_key or os.getenv("VLLM_API_KEY") or "vllm-api-key"
|
||||
base_url = self.config.vllm_base_url or os.getenv("VLLM_BASE_URL")
|
||||
self.client = OpenAI(api_key=self.config.api_key, base_url=base_url)
|
||||
self.client = OpenAI(api_key=self.config.api_key, base_url=self.config.vllm_base_url)
|
||||
|
||||
def _parse_response(self, response, tools):
|
||||
"""
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user