Compare commits

..

103 Commits

Author SHA1 Message Date
Saket Aryan af7dfc8f64 merge main into pr5 after #7322 was squash-merged
Same squash divergence as pr2 and pr4. Nine conflicts, three of them real and
six generated.

build.py: kept this branch's side, which carries the portable-bundle fix main
does not have. Regenerated all six _harness_id.py from it rather than resolving
them by hand, and verified the outcome: the portable bundle declares no
application and each native one still names its host.

deepseek-plugin/src/index.ts: kept this branch's side. Main has the comment
claiming the backend allowlist already recognizes DEEPSEEK_HARNESS, which is not
true until mem0ai/platform#3602 ships; this branch carries the correction.

test_uninitialised_identity.py: append-only, as on pr4. Our side kept whole.

Bundles clean for all six hosts, 318 passed 8 skipped, deepseek and pi-agent
suites green.
2026-09-18 13:47:56 +05:30
Saket Aryan 4e38b057fd fix(plugins): count installs once, and re-resolve the email when the key changes (#7325) 2026-09-18 13:46:25 +05:30
Saket Aryan 3362999095 fix(plugins): stop delivering telemetry events twice, and stop losing parked ones (#7324) 2026-09-18 13:43:33 +05:30
Saket Aryan 012cd32c3a fix(plugins): report the plugin that produced the event, not the one that sent it (#7323) 2026-09-18 13:42:34 +05:30
Saket Aryan e4e0307ae6 fix(plugins): say what telemetry actually sends, and salt the hashes (#7322) 2026-09-18 13:23:05 +05:30
Kartik 84bf468176 docs: add practical Mem0 Copilot guide (MEM-6344) (#7337) 2026-09-18 06:54:04 +05:30
Saket Aryan 6aa1f60503 fix(client): drop the unused import CI's lint caught
Left behind when the async construction test was removed. ruff check now passes
on mem0/ and tests/.

Claude-Session: https://claude.ai/code/session_01C7tEmH86HAr7GoAAKCEHZb
2026-09-17 19:55:17 +05:30
Saket Aryan 3d0521cad4 Merge branch 'pr4/install-marker-and-identity' into pr5/surface-headers 2026-09-17 19:44:14 +05:30
Saket Aryan d0799f1cb5 Merge branch 'pr3/spool-delivery' into pr4/install-marker-and-identity 2026-09-17 19:44:14 +05:30
Saket Aryan afc3d02a80 fix(plugins): sweep temp files a killed process left behind
Review finding, and the adjacent case to the one this PR already fixed.
_sweep_debris collects *.partial and *.corrupt; _write_identity and
_install_salt both create telemetry-*.<pid>.tmp and unlink it in a finally,
which a SIGKILL skips. Its own docstring reasoning, that no glob in the module
matches them so nothing else ever will, applies equally.

Collected on the stale window rather than the expiry window: unlike a quarantined
batch a temp file carries nothing worth keeping for diagnosis.

276 passed, 8 skipped.

Claude-Session: https://claude.ai/code/session_01C7tEmH86HAr7GoAAKCEHZb
2026-09-17 19:44:14 +05:30
Saket Aryan df4b88685b fix(client): repair the missed _bounded_stack call site, and test that a client constructs
Reported as a blocker by an independent re-review, and correctly: the previous
commit changed _bounded_stack to take the caller's entries and our own entry
separately, updated _apply_client_headers, and missed _client_stack. That runs on
every construction path, so every MemoryClient(...) raised TypeError. A total SDK
outage, introduced by the fix for a cosmetic truncation bug.

The whole suite stayed green because nothing constructed a client. That is the
actual defect here, so the test file exists as much for the gap as for the bug:
it builds a client, asserts the header reaches it, and covers the two bounding
rules directly. Confirmed it fails against the broken call site and passes
against the repaired one.

AsyncMemoryClient is deliberately not constructed: its validation path makes a
real request to /v1/ping/, and a unit test needing the network is worse than
none. It shares _client_headers with the sync client, which is what the
construction test guards.

tests/test_client_surface_headers.py 5 passed, plugin suites 312 passed 8
skipped.

Claude-Session: https://claude.ai/code/session_01C7tEmH86HAr7GoAAKCEHZb
2026-09-17 19:40:17 +05:30
mintlify[bot] f135cb9949 SEO & metadata audit: shorten hermes description (#7359)
Co-authored-by: mintlify[bot] <109931778+mintlify[bot]@users.noreply.github.com>
2026-09-17 19:32:03 +05:30
Saket Aryan 3a72dfdc52 fix(integrations): reserve our own slot in the client stack, and drop whole entries
Two review findings on this PR.

All three client-stack implementations appended our entry and then trimmed to
four, so whenever a caller already sent four entries the one dropped was exactly
the one the function exists to add. We vanished from our own stack while every
caller claim survived. The character cap was worse: slicing the joined string
severs an identifier, and the platform parses the fragment as a real client, so a
truncated tail arrives as a client literally named "me". Both caps now drop whole
entries and the reserved slot is ours, in the Python SDK, the TypeScript SDK and
pi-agent. mcp-server has the same fix on the platform branch.

The deepseek comment claimed the backend's allowlist recognizes DEEPSEEK_HARNESS.
This PR introduced that wording, replacing a neutral one. It is not true until
mem0ai/platform#3602 ships, so it now states the dependency.

Two pi-agent tests: our entry survives a full caller stack, and every surviving
entry is whole rather than a severed tail. Python side verified directly, a
4-entry caller stack keeps mem0-python and long entries are dropped whole.

312 passed 8 skipped, pi-agent 96, bundles clean, TS SDK builds.

Claude-Session: https://claude.ai/code/session_01C7tEmH86HAr7GoAAKCEHZb
2026-09-17 19:29:13 +05:30
Saket Aryan 8e59181edf Merge branch 'pr3/spool-delivery' into pr4/install-marker-and-identity 2026-09-17 19:28:11 +05:30
Saket Aryan a0bbf748c2 Merge branch 'pr4/install-marker-and-identity' into pr5/surface-headers 2026-09-17 19:28:11 +05:30
Saket Aryan 4edfaabc06 fix(plugins): collect quarantined batches instead of leaving them on disk forever
Review finding. _sweep_debris globbed only *.partial. The *.corrupt files this
PR writes when a batch cannot be decoded are matched by no glob in the module, so
they accumulated for the life of the install.

Collected on the expiry window rather than the stale window, deliberately: a
quarantined batch is the only remaining evidence of events that could not be
delivered, so someone chasing a report of missing telemetry has to be able to
find a recent one. Debris keeps the short window; it carries nothing.

One test, asserting both halves: a recent quarantine survives and an expired one
does not.

Claude-Session: https://claude.ai/code/session_01C7tEmH86HAr7GoAAKCEHZb
2026-09-17 19:28:08 +05:30
Saket Aryan 74ca467b28 Merge branch 'pr2/source-at-record-time' into pr3/spool-delivery 2026-09-17 19:27:43 +05:30
Saket Aryan 8abedca24a Merge branch 'pr1/telemetry-privacy-docs' into pr2/source-at-record-time 2026-09-17 19:27:43 +05:30
Saket Aryan c043e97673 fix(plugins): read the salt before minting one, and stop overclaiming backend support
Two findings from an independent review of this branch.

_install_salt went straight to create, fsync, link, unlink on every call. All but
the first process finds the salt already published, so each hook paid an fsync to
discover that, on a path documented as appending a line and returning. Hooks are
separate processes firing on every tool call inside a few-second budget.
Measured: cold process one fsync, warm process zero, same salt.

The deepseek README said the backend recognizes DEEPSEEK_HARNESS so usage
surfaces by name. It does not yet. That value, along with STRANDS, ZAPIER,
MEM0_PLUGIN, PI_AGENT and VERCEL_AI_SDK, buckets into OTHERS until
mem0ai/platform#3602 ships, so the README now states the dependency and links it.
The neighbouring comment in mem0-strands was already accurate and is unchanged:
it says recognized values live in the allowlist without claiming this one is in
it.

265 passed, 8 skipped.

Claude-Session: https://claude.ai/code/session_01C7tEmH86HAr7GoAAKCEHZb
2026-09-17 19:27:39 +05:30
ANIRUDDHA ADAK f5220ff8d4 fix(security): bump next to 15.5.24, patches GHSA-p293-qw3h-jr36 (#7320) 2026-09-17 19:23:45 +05:30
Saket Aryan 92aa8a1de1 fix(vercel-ai-sdk): inject the provider version at build instead of hardcoding it
Review finding from @karthik-indla on this PR. src/mem0-utils.ts carried
PROVIDER_VERSION = "3.0.2" as a literal. It matches package.json today and
misreports the client version from the next release bump onwards, which is the
one thing X-Mem0-Client exists to carry. Every other client in the repo injects
at build: mem0-ts via __MEM0_SDK_VERSION__, the Python side via
importlib.metadata, the CLIs via __CLI_VERSION__.

tsup now defines __MEM0_PROVIDER_VERSION__ from package.json, and the source
falls back to "dev" only when run unbundled, such as in tests. Verified in the
built bundle: PROVIDER_VERSION resolves to "3.0.2" and no placeholder survives.

resolveJsonModule is enabled alongside it, matching mem0-ts, because tsc
--noEmit covers tsup.config.ts and the package.json import fails without it.

Type check clean, build clean. The jest suite's failure is pre-existing and
unrelated: it requires a live MEM0_API_KEY, confirmed by running it on a stashed
tree. Python side 311 passed, 8 skipped; pi-agent 94 passed.

Claude-Session: https://claude.ai/code/session_01C7tEmH86HAr7GoAAKCEHZb
2026-09-16 22:16:20 +05:30
Saket Aryan 2266eccf07 fix(plugins): make the install marker durable before claim_install returns
Review nit from @karthik-indla on this PR. A hard kill between the O_EXCL open
and the buffered write reaching disk left a marker that exists but parses to
nothing: is_first_run reads it as claimed, so that install is never counted, and
claim_version_change cannot read a version out of it.

Not temp-and-rename, which is what the equivalent fixes in this stack use: the
O_EXCL open is what makes this claim exclusive across concurrently starting
sessions, and a rename would clobber rather than lose the race. The content is
the part that needed making safe, so it is flushed and fsynced before the call
returns.

The recovery path stays as the backstop: _repair_install_state already rewrites
an unparseable marker so version tracking resumes.

304 passed, 8 skipped.

Claude-Session: https://claude.ai/code/session_01C7tEmH86HAr7GoAAKCEHZb
2026-09-16 22:15:07 +05:30
Saket Aryan 75a2952004 Merge branch 'pr4/install-marker-and-identity' into pr5/surface-headers 2026-09-16 22:15:07 +05:30
Saket Aryan 8a014f299a Merge branch 'pr3/spool-delivery' into pr4/install-marker-and-identity 2026-09-16 22:14:45 +05:30
Saket Aryan 40287f6f04 fix(plugins): a batch that could not be read is not a delivered batch
Two review findings from @karthik-indla on this PR.

_drain returned (0, True) on any read failure, so a claim nothing was posted
from counted as fully delivered. flush() then carried on to the next claim as
though this one had arrived, and the single signal that says the run went badly
never fired. The two cases are now separated: undecodable content is still
quarantined and reported delivered, because there is nothing left to send and
the rest of the run should continue, while an OSError leaves the file exactly
where it is and reports undelivered. Quarantining there would discard events
over a transient filesystem error, and nothing ever re-globs .corrupt.

Retries had no time backoff. _release_claim backdated straight to
immediately-reclaimable, so two senders meeting one momentary failure could walk
a batch from attempt 0 to the limit within seconds and discard it, when a retry
a minute later would have delivered. Releases now carry a cooldown that grows
with the attempts already spent, clamped so the mtime never lands in the future
and reads as a live lease.

Four tests: an unreadable batch is neither delivered nor quarantined,
undecodable content still is quarantined so one torn file cannot block every
later claim, and attempts cannot be burned without waiting. The expiry test now
ages the file between flushes, which is the wall time a real retry waits.

Claude-Session: https://claude.ai/code/session_01C7tEmH86HAr7GoAAKCEHZb
2026-09-16 22:14:42 +05:30
Saket Aryan dddeb4572b Merge branch 'pr4/install-marker-and-identity' into pr5/surface-headers 2026-09-16 21:08:46 +05:30
Saket Aryan 9f8b106f8e Merge branch 'pr1/telemetry-privacy-docs' into pr2/source-at-record-time 2026-09-16 21:08:45 +05:30
Saket Aryan ed7b09884c Merge branch 'pr3/spool-delivery' into pr4/install-marker-and-identity 2026-09-16 21:08:45 +05:30
Saket Aryan 0377b9a85e Merge branch 'pr2/source-at-record-time' into pr3/spool-delivery 2026-09-16 21:08:45 +05:30
Saket Aryan 3fd4949040 fix(plugins): keep the salt working where hardlinks are not supported
Self-review of the atomic-publish fix. Some network mounts and container volumes
reject os.link, and the outer handler swallowed that into "no salt", which meant
repo_hash and session_hash were dropped on every run for that whole cohort. The
race being closed is narrow; losing the hashes for an entire filesystem is not a
fair trade.

Falls back to claiming the name with O_CREAT|O_EXCL and writing, which is what
this did before. The empty-file window reopens there, but it is benign now: a
reader landing in it gets "" and omits the hash for that process rather than
caching a guessable path digest, which was the actual defect.

266 passed, 8 skipped.

Claude-Session: https://claude.ai/code/session_01C7tEmH86HAr7GoAAKCEHZb
2026-09-16 21:08:43 +05:30
Saket Aryan 64a01ab31e fix(pi-agent): attribute the shared client, not two command call sites
Review finding from @kartik-mem0 on this PR.

Only the explicit slash commands set a source. Automatic recall at entry.ts:80,
the capture path, the memory tools and deletion all go through the same client
constructed at entry.ts:31 with no attribution, so everything except the
commands still reached the platform as generic SDK traffic. That is most of the
plugin's traffic.

Identity is now stamped once on the shared client. Deliberately by mutating
client.headers rather than through the SDK's MEM0_SOURCE environment support:
this package pins mem0ai ^3.0.7, the installed build has no such support, and
setting an environment variable it does not read would have looked like a fix
and changed nothing. Every request method in the published client sends
this.headers, so this covers all of them and keeps working when the SDK gains
the env path.

Set-once and append-only are preserved, so a wrapper that already named a
surface keeps it and the client stack accumulates rather than being replaced.
PLATFORM_SOURCE moves into the new module and commands.ts imports it; the body
source stays on those two calls because that is what the backend reads when the
header is absent.

Five tests for the header contract, plus the existing suite: 94 pi-agent tests
pass and the tsup DTS build is clean. Python side 307 passed, 8 skipped.

Claude-Session: https://claude.ai/code/session_01C7tEmH86HAr7GoAAKCEHZb
2026-09-16 20:37:57 +05:30
Saket Aryan 9524c238db Merge branch 'pr4/install-marker-and-identity' into pr5/surface-headers 2026-09-16 20:36:27 +05:30
Saket Aryan 6d89b3b33e fix(plugins): rotate the anonymous id when the account goes, and verify legacy rows
Three review findings from @kartik-mem0 on this PR.

Anonymous id reuse, reported twice and one defect. The id is offered to PostHog
as $anon_distinct_id on first sign-in and that merge is permanent, so keeping it
after a logout or a key change puts every later anonymous event on the account
that just left. It is now rotated on both routes, and 'aliased' is cleared with
it so the fresh id can be merged into whatever account comes next. Rotation is
deliberately not triggered by a plain lookup failure with no cached email: there
is no previous account to leak to, and churning ids there would fragment the
person for anyone offline on first run.

Legacy rows are verified instead of adopted. A row written before fingerprints
existed carries an email and no fingerprint; adopting the current key bound that
key to the previous account's email permanently, and every run after agreed with
itself. It now resolves once and takes the answer. If the lookup fails it keeps
the cached email and retries next flush rather than dropping a real attribution,
which is safe because the network that failed /v1/ping/ is about to fail the
PostHog POST too. My original comment justifying the shortcut claimed the check
would cost a request on every flush forever; that was wrong, the fingerprint is
stored after one success.

A failed upgrade claim is released. The sentinel was created before the marker
rewrite and left behind if the rewrite failed, so claim_version_change returned
early on every later run and that version's upgrade was never recorded again.

Five tests, covering both rotation routes, legacy verification, the firewalled
legacy case, and retrying a failed upgrade claim.

300 passed, 8 skipped.

Claude-Session: https://claude.ai/code/session_01C7tEmH86HAr7GoAAKCEHZb
2026-09-16 20:36:23 +05:30
Saket Aryan 88bd5f164f Merge branch 'pr3/spool-delivery' into pr4/install-marker-and-identity 2026-09-16 20:35:23 +05:30
Saket Aryan d8c99fb405 fix(plugins): check the lease before judging a claim exhausted
Review finding from @kartik-mem0 on this PR, and the most serious one: it loses
events, which is what this PR exists to prevent.

_claim_parked judged exhaustion before liveness. Claiming a parked file bumps
its attempt count and refreshes its mtime, so the moment a sender takes the
final attempt the file looks exhausted to every other sender while its owner is
actively draining it. The second sender unlinked it, and everything in that
batch was gone.

The liveness check now runs first, so a batch under a live lease is skipped
whatever its attempt count. The cleanup is deferred, not cancelled: once the
lease lapses, the same exhausted file is reaped on a later run.

Two tests. The first walks a batch to the final attempt and asserts a second
sender neither takes it nor deletes it, and that the events are still in it. The
second asserts an abandoned exhausted batch is still discarded once its lease
lapses, which is the over-correction to guard against. Confirmed the first fails
against the previous ordering.

288 passed, 8 skipped.

Claude-Session: https://claude.ai/code/session_01C7tEmH86HAr7GoAAKCEHZb
2026-09-16 20:35:13 +05:30
Saket Aryan 2ed501a43c Merge branch 'pr1/telemetry-privacy-docs' into pr2/source-at-record-time 2026-09-16 20:33:57 +05:30
Saket Aryan 26760b00b1 Merge branch 'pr2/source-at-record-time' into pr3/spool-delivery 2026-09-16 20:33:57 +05:30
Saket Aryan 7710a4e180 fix(plugins): publish the salt atomically, and omit the hash when there is none
Review finding from @kartik-mem0 on this PR.

O_CREAT|O_EXCL then write leaves a window where the salt file exists and is
empty. Hooks are short-lived processes firing on every tool call and people run
several agent windows, so a concurrent reader lands in that window, reads
nothing, and falls back to a digest of the salt file's own path, memoized for
its whole run. That path is guessable, so the race silently replaced the privacy
control with something an attacker can compute, and hashed the same repository
two ways depending on timing.

The value is now written to a private temp file, fsynced, and published with
os.link, which is atomic and fails if another process already published one.
Link rather than replace, so losing the race adopts their salt instead of
clobbering it. The temp file is removed either way.

The derived fallback is gone rather than fixed. _scoped_digest returns "" when
there is no salt and record() omits the property, because an unsalted digest
over a git remote or a home-directory path is close to plaintext, and shipping
one under a name that says hash is worse than sending nothing.

Three tests: the racing reader never sees the name half-written, a second writer
adopts the first's salt and leaves no temp file, and an unwritable data
directory drops the property instead of emitting a weak one. The old test
asserted the fallback behaviour and is replaced.

265 passed, 8 skipped.

Claude-Session: https://claude.ai/code/session_01C7tEmH86HAr7GoAAKCEHZb
2026-09-16 20:33:54 +05:30
Paurush Mittal 0df3e4b87d fix(docs): correct rendered titles and remaining SEO links (#7344) 2026-09-16 17:14:33 +05:30
Saket Aryan f80d21fa06 fix(plugins): do not claim a host application the portable bundle cannot know
Review point. The portable bundle is built with host "coding-agent", and the
build wrote that straight into PLATFORM_APPLICATION, so every portable install
sent X-Application: coding-agent.

That value is not in the platform's allowlist, so it was already being dropped
server-side. The effect was the worst of both: the wire claimed we knew the
editor, the stored event recorded that we did not, and nothing said which was
right. An absent header says the same thing honestly and costs a lookup.

HARNESS_ID stays "coding-agent". It is the PostHog-side label, it is true, and
grouping portable installs together there is useful.

Native bundles are unchanged apart from the regenerated comment. Covered by two
new build tests: portable declares no application, and each native names the
host it was generated for.

Claude-Session: https://claude.ai/code/session_01C7tEmH86HAr7GoAAKCEHZb
2026-09-16 02:49:11 +05:30
Saket Aryan 1304ffd4a1 Merge branch 'pr3/spool-delivery' into pr4/install-marker-and-identity 2026-09-15 22:44:20 +05:30
Saket Aryan b66b70c212 Merge branch 'pr1/telemetry-privacy-docs' into pr2/source-at-record-time 2026-09-15 22:44:20 +05:30
Saket Aryan f2f58b5a64 Merge branch 'pr2/source-at-record-time' into pr3/spool-delivery 2026-09-15 22:44:20 +05:30
Saket Aryan 422a9caf8d Merge branch 'pr4/install-marker-and-identity' into pr5/surface-headers 2026-09-15 22:44:20 +05:30
Saket Aryan f8a9d524be docs(openclaw): correct the anonymity claim to match how it identifies events
The sweep in aa770aa6 deliberately left this page alone, reasoning that
hashing the email is materially different from sending it. Reading
integrations/openclaw/telemetry.ts does not support that: distinctId() is an
unsalted sha256 of the account email, and Mem0 holds the emails it is derived
from, so recovering the account is a table join. resolveEmail() also rewrites
already-queued events onto that id, and identifyAnonymous() fires a PostHog
$identify that merges the prior random id into it for good.

That is pseudonymous, not anonymous, and it is the same mismatch between the
stated privacy posture and the wire format that this stack exists to close.
The opt-out is unchanged and still correct.

Claude-Session: https://claude.ai/code/session_01C7tEmH86HAr7GoAAKCEHZb
2026-09-15 22:44:17 +05:30
Saket Aryan 2097e29edb docs(plugins): restore the telemetry sweep this branch's merge reverted
The pr4 -> pr5 merge resolved eleven documentation files to the pre-sweep
side, undoing aa770aa6 in its entirety. Because the stack lands in order,
main would have taken the corrected wording in pr1 and then had it reverted
by pr5, leaving the shipped claim wrong again:

- all six hosts' pause skill back to "a minimal anonymous telemetry ping"
- docs/integrations/deepseek-plugin.mdx back to "Anonymous usage events"
- integrations/zapier-mem0/README.md back to advertising telemetry the app
  does not have, with an MEM0_TELEMETRY opt-out that controls nothing
- the README data-dir listing back to omitting telemetry-salt and
  install-state.json, both of which this stack creates
- the README and docs property lists back to the enumeration that drifts

Restored verbatim from pr4. No file here is in pr5's scope, and the full
pr4..pr5 diff is now surface headers only.

Claude-Session: https://claude.ai/code/session_01C7tEmH86HAr7GoAAKCEHZb
2026-09-15 22:37:16 +05:30
PowderXu b51f7692f0 docs(upstash): correct the default collection namespace (#7287) 2026-09-15 19:41:46 +05:30
jianyx1 dc7f88363f docs: fix Hermes integration page to match the current plugin (#7244) 2026-09-15 19:23:17 +05:30
Saket Aryan e9cbc626c0 fix(pi-agent): widen the search options by one property instead of to never
CI caught what I could not check locally: `pnpm exec tsc --noEmit` fails with

    error TS2353: Object literal may only specify known properties,
    and 'source' does not exist in type 'SearchMemoryOptions'.

Adding `source` to SearchMemoryOptions in mem0-ts does not help here. pi-agent
resolves `mem0ai` from npm, so it typechecks against the published 3.1.8 types,
not this repo's source. The declaration still belongs in mem0-ts for the next
release; this call site needs to compile today.

Widened by exactly that one property rather than restoring `as never`, which
was the original objection: a blanket cast also disabled checking of filters,
threshold, topK and rerank on the same literal. `source` reaches the wire
through the SDK's camelToSnakeKeys spread either way.

Verified by installing the package deps and running the real gates: tsc clean,
build clean. vercel-ai-sdk typechecks clean too. Also carries the spool-test
environment fix that had not been committed in this worktree.

Claude-Session: https://claude.ai/code/session_01C7tEmH86HAr7GoAAKCEHZb
2026-09-15 00:42:44 +05:30
Saket Aryan 3b43c78f7b Merge branch 'pr3/spool-delivery' into pr4/install-marker-and-identity 2026-09-15 00:37:46 +05:30
Saket Aryan e6179ac447 Merge branch 'pr2/source-at-record-time' into pr3/spool-delivery 2026-09-15 00:37:40 +05:30
Saket Aryan 8783c590a0 Merge branch 'pr1/telemetry-privacy-docs' into pr2/source-at-record-time 2026-09-15 00:37:38 +05:30
Saket Aryan aa770aa652 docs(plugins): finish the telemetry sweep across the remaining surfaces
The first pass fixed the plugin README and the module docstring but left the
same claim standing everywhere else.

- docs/integrations/deepseek-plugin.mdx still said "Anonymous usage events".
  The TS SDK's telemetryId is the raw account email, so it is not anonymous.
- The pause skill told users a "minimal anonymous telemetry ping" fires while
  paused. Same ping, same email. Corrected in the template, which regenerates
  into all six hosts.
- integrations/zapier-mem0/README.md advertised telemetry the app does not have:
  there is no telemetry code in it at all. It now says what is actually true,
  that its requests carry source="ZAPIER".
- The data directory listing is presented as exhaustive and had gone stale
  against this stack's two new files, telemetry-salt and install-state.json.

Also replaced the property enumeration in both the README and the docs page.
Review pointed out it omitted the configured model name among others — writing
a fresh exhaustive list in a PR whose whole purpose is making docs match code
reproduces the defect being fixed. It now describes the shape and points at
where the rule is actually enforced, so it cannot drift again.

Deliberately unchanged: docs/integrations/openclaw.mdx. OpenClaw hashes the
email rather than sending it, which is materially different from the plugin and
the SDK, so its claim is not wrong in the same way.

Claude-Session: https://claude.ai/code/session_01C7tEmH86HAr7GoAAKCEHZb
2026-09-15 00:37:33 +05:30
Saket Aryan 1282b46f9f Merge branch 'pr3/spool-delivery' into pr4/install-marker-and-identity 2026-09-15 00:36:29 +05:30
Saket Aryan 349f77e556 fix(plugins): stop the new spool tests depending on ambient state
CI runs agent-plugin-core/tests and claude-code-plugin/tests in one pytest
process. Two things only show up in that combined run, so the suites passed
locally and failed on every push.

claude-code-plugin/tests/conftest.py sets MEM0_TELEMETRY=false at import, which
is process-wide. record() then returns early and every assertion in
test_spool_delivery.py saw an empty spool — nine failures, all reported as
"recorded nothing" rather than as a disabled feature. The fixture now pins
MEM0_TELEMETRY rather than trusting whatever collected first.

The fixture also dropped telemetry/memory_core/_harness_id from sys.modules on
teardown. That conftest imports memory_core once at collection and calls
configure_harness() on it, so a later re-import got a fresh module with default
harness config and test_memory_core failed depending on collection order. The
fixture now saves and restores those entries instead of deleting them.

Verified with CI's exact command rather than the narrower path I had been
running: 266 passed, 8 skipped.

Claude-Session: https://claude.ai/code/session_01C7tEmH86HAr7GoAAKCEHZb
2026-09-15 00:36:23 +05:30
Saket Aryan 5104cc3276 fix(plugins): repair the merged test file and regenerate bundles
The keep-both conflict resolution split a function body. Rebuilt from both
merge parents so the header-contract test and the session-start tests are each
intact.

Claude-Session: https://claude.ai/code/session_01C7tEmH86HAr7GoAAKCEHZb
2026-09-15 00:33:49 +05:30
Saket Aryan c17d336fc0 Merge branch 'pr4/install-marker-and-identity' into pr5/surface-headers
# Conflicts:
#	integrations/agent-plugin-core/tests/test_uninitialised_identity.py
2026-09-15 00:33:21 +05:30
Saket Aryan faa3f029a1 Merge branch 'pr3/spool-delivery' into pr4/install-marker-and-identity 2026-09-15 00:33:09 +05:30
Saket Aryan 7bcb9bd124 Merge branch 'pr2/source-at-record-time' into pr3/spool-delivery 2026-09-15 00:32:58 +05:30
Saket Aryan 59627f2a6c Merge branch 'pr1/telemetry-privacy-docs' into pr2/source-at-record-time
# Conflicts:
#	integrations/agent-plugin-core/python/telemetry.py
#	integrations/antigravity-plugin/core/telemetry.py
#	integrations/claude-code-plugin/core/telemetry.py
#	integrations/codex-plugin/core/telemetry.py
#	integrations/cursor-plugin/core/telemetry.py
#	integrations/kimi-plugin/core/telemetry.py
#	integrations/mem0-agent-plugin/core/telemetry.py
2026-09-15 00:32:51 +05:30
Saket Aryan 47ce17c21b fix(integrations): apply the header contract the docs described
Review found the contract documented but not implemented, and one client path
missed entirely.

AsyncMemoryClient's custom-client branch still carried the old literal header
dict, so `AsyncMemoryClient(client=...)` sent no surface identity at all — the
exact asymmetry this work set out to remove.

Both custom-client branches also used a blanket headers.update(), which
overwrites. That is the one code path where an outer layer's identity can
physically be present, and it was the one path that erased it. They now
check-then-set the identity headers and append to an existing client stack,
which is what set-once and append-only were supposed to mean.

AGENTS.md claimed a plugin calling the Python SDK produces
`mem0-plugin/0.3.1, mem0-python/2.0.19`. Nothing in the repo sets the env vars
that would make that happen, so the concatenation was unreachable. Replaced with
the three ways an integration can actually declare itself, in preference order.

memory_core's comment said the backend reads X-Mem0-Source. That is only true
from the platform release shipping alongside this, and a reader would otherwise
trust it and build header-only attribution that silently does nothing — which is
how vercel-ai-sdk was written in the first cut. Corrected in all seven copies,
and the body value is what makes attribution work against either backend.

mem0-ts hardcoded SDK_VERSION = "3.1.8" while the repo already injects
__MEM0_SDK_VERSION__ via tsup, the same mechanism telemetry.ts uses. The
hardcode was correct only until the next release bump.

Dropped both `as never` casts in pi-agent. They suppressed an excess-property
error but also disabled checking of every other option at those call sites, so a
typo in filters or threshold would have compiled. SearchMemoryOptions now
declares `source` instead.

Stack truncation cut mid-identifier, leaving a fragment that parses as a real
client name. It now drops whole entries.

Claude-Session: https://claude.ai/code/session_01C7tEmH86HAr7GoAAKCEHZb
2026-09-15 00:32:39 +05:30
Saket Aryan 2c885fdcd7 fix(plugins): make code.install reachable, and stop pinging on every flush
Review found the headline fix inverted: code.install could never fire, so every
fresh install reported an upgrade and the two cohorts became indistinguishable —
strictly worse than the bug being fixed.

hook_runner reaches claim_install() only after cache_plugin_api_key() has
written `api-key` and EvidenceStore() has created `evidence.sqlite3` and its WAL
files. Asking "is the data directory empty" at that point always saw content.
The caller now snapshots emptiness at the top of the run, before anything
writes, and passes it in.

Also caught by review, all in the same file:

- claim_version_change was an unsynchronized read-modify-write, so several
  concurrently starting sessions each observed the old version and each recorded
  an upgrade. The first session after a version bump is exactly when a user's
  open agent windows all restart together. The transition is now claimed with an
  exclusive per-version sentinel.
- A crash between O_EXCL and the write left an empty marker, which disabled
  every future upgrade event on that machine: claim_install saw the file and
  claim_version_change could not parse it. An unparseable marker is now
  repaired.
- claim_install consumed the one-shot claim even under MEM0_TELEMETRY=false, so
  a user who opted out for their first sessions would never report install after
  opting in.
- Existing users have an email but no key fingerprint, so the fast path always
  missed and every flush paid an uncached /v1/ping/ — a 5s timeout each time for
  the offline users this stack keeps citing. Legacy rows now adopt the current
  key's fingerprint instead of re-resolving.
- A key that will not resolve (revoked, offline) kept attributing to the
  previous account's email, which is the bug this was meant to fix. It now falls
  back to the anonymous id.
- The anonymous id was never rotated, so once it had been merged into one
  account it was still offered as the alias for the next one. An alias naming an
  already-identified id is what could link two real people; it is now offered
  once.

The gap that let this ship was that no test drove hook_runner's session-start
path — the decision was only ever tested by calling claim_install() directly on
a directory nothing had touched. Adds subprocess tests that run the real
entrypoint: fresh install, exactly-once, and an existing data dir.

62 core tests, 203 host tests.

Claude-Session: https://claude.ai/code/session_01C7tEmH86HAr7GoAAKCEHZb
2026-09-15 00:31:52 +05:30
Saket Aryan bd17f2b8c9 fix(plugins): make the retry budget reachable and the rewrite durable
Review found that the first cut traded the duplicate-delivery bug for a worse
one, and disproved its own load-bearing safety claim by experiment.

Expiry was unreachable. _claim_parked touched the mtime on every re-claim and
_release_claim backdated to exactly now minus the stale threshold, so a file's
age hovered around 121 seconds and never approached the 7-day expiry. The
attempt count in the filename therefore bounded nothing: an undeliverable batch
(revoked key, proxy 403, oversized event) lived on disk forever, and because
spawn_flush starts a sender whenever a .sending file exists, it spawned a
detached Python process on every hook, MCP call and CLI invocation, forever.
The old code self-healed here, so this was a regression. Expiry now gates on the
attempt budget, which is the thing that actually accumulates; age stays only as
a backstop for files that never carried an attempt marker.

The attempt parser sniffed for a leading "a", which also matches a hex id like
a1234567, so a legacy telemetry-<pid>-<hex>.sending file parsed as attempt
1234567 and was deleted unsent on the first flush after upgrade — precisely the
population this PR is meant to protect. Anchored on field position instead.

The rewrite was not durable: no fsync before the rename, and _drain unlinked any
claim that parsed to zero events. A crash between write and rename left the
claim empty, and the next flush deleted it. Now fsynced, and a non-empty file
that parses to nothing is quarantined as .corrupt rather than destroyed.

read_text raises UnicodeDecodeError on a torn file, which `except OSError` does
not catch. flush() runs from a bare `finally:` in flush_worker, so the exception
also skipped the handoff cleanup and left it stuck in .running.

The per-batch rewrite's return value was discarded, so a failed rewrite let the
loop continue as though progress had been recorded — reintroducing the exact
duplicate delivery this PR exists to fix.

.partial files orphaned by a crash between write and rename matched no glob in
the module and were never cleaned up.

Also replaces the heartbeat test, which asserted `SEND_TIMEOUT * 4 <
CLAIM_STALE_SECONDS` — two constants, executing none of the code under test. It
now drives the real rewrite and watches the mtime move. New tests cover expiry
being reachable, legacy filename parsing, torn-claim quarantine, failed-rewrite
behaviour and debris sweeping.

64 core tests, 199 host tests.

Claude-Session: https://claude.ai/code/session_01C7tEmH86HAr7GoAAKCEHZb
2026-09-15 00:30:58 +05:30
Saket Aryan 95d4fc27e2 fix(plugins): make the telemetry salt stable, its own file, and memoized
Review found three ways the first cut produced worse data than no salt at all.
All three came from keeping the salt as a key in the identity dict and doing an
unlocked read-modify-write.

Hooks are short-lived separate processes firing on every tool call, and people
run more than one agent window, so several processes would read {}, each mint
its own uuid4, and each hash with it. One repository hashed several ways in the
window before a writer won.

resolve_distinct_id holds a copy of that same dict across a network call to
/v1/ping/ with a 5s timeout, so whichever write landed second erased the other's
key: losing the salt changes repo_hash mid-stream, losing the email fires a
second $identify and splits the person.

_write_identity swallows OSError, and nothing memoized, so on a read-only or
full data directory every single event got a brand-new random salt — unbounded
cardinality in PostHog, which is strictly worse than the unsalted value it
replaced.

The salt now lives in its own file claimed with O_CREAT|O_EXCL, so exactly one
process wins and the losers read the winner's value, and it is memoized per
process. When it cannot be persisted the fallback is derived from the data
directory path: stable for the machine rather than random per call.

Its own file also means record() no longer creates telemetry-identity.json as a
side effect. is_first_run keys off that file, so the first cut would have
silently suppressed the install event — a production metric change hidden in a
docs PR.

Claude-Session: https://claude.ai/code/session_01C7tEmH86HAr7GoAAKCEHZb
2026-09-15 00:25:12 +05:30
Saket Aryan bc5526f13d feat(integrations): declare which surface each client is, and its version
Nothing on the wire said which Mem0 surface made a call. Both SDKs sent only an
auth header, so the platform saw python-httpx and axios and attributed every
plugin, wrapper and direct API user to one undifferentiated bucket. Version was
unknowable, which is what gates every deprecation decision.

Three headers, and the rules on them are the point:

- X-Mem0-Source and X-Application are SET-ONCE. Whichever layer is outermost
  sets them; nothing below overwrites. A plugin wrapping the SDK keeps its own
  identity instead of being renamed by the transport underneath it.
- X-Mem0-Client is APPEND-ONLY. A plugin calling the Python SDK produces
  `mem0-plugin/0.3.1, mem0-python/2.0.19`, so neither layer can erase the other.

Deliberately not User-Agent: proxies rewrite it, and we have already met a WAF
that 403s on it.

The plugin core also hoists `source` out of metadata to the top level, which is
where the backend actually reads it. It sat in metadata, which get_event_source
never consults, so all six plugins arrived indistinguishable from a raw SDK call
no matter what they set. The harness tag stays in metadata as hook provenance.

pi-agent had PI_AGENT as a PostHog property only and never sent it on the wire.
vercel-ai-sdk sent nothing at all from its raw fetch calls.

Values must exist in the platform's EventSource enum or they bucket to OTHERS,
so integrations/AGENTS.md now states the contract and the "adding an
integration" checklist requires landing the platform value in the same week.

Pairs with mem0ai/platform#3602, which recognizes these values.

TypeScript changes are not typechecked locally — deps are not installed for
those packages. CI covers them.

Claude-Session: https://claude.ai/code/session_01C7tEmH86HAr7GoAAKCEHZb
2026-09-15 00:19:00 +05:30
Saket Aryan 0d2b20c03d fix(plugins): count installs once, and re-resolve the email when the key changes
code.install counted upgrades and repeat sessions. Session start records install
whenever is_first_run() is true, and that only checked whether
telemetry-identity.json exists. Recording install does not create that file —
only the first successful flush does. So install fired for every 0.2.x user on
their first 0.3.x session (0.2.x never wrote the file, and the data directory
survives the upgrade), again for any session starting before that first flush
finished, and — this is the part that makes it unbounded rather than a race —
on every single session, forever, for anyone whose flush never succeeds. An
offline or firewalled user reported a new install every time they opened an
editor, which is exactly the population hardest to see in the data.

A dedicated install-state.json is now claimed with O_CREAT|O_EXCL at the moment
install is recorded, so two sessions starting together cannot both win, and the
marker is not coupled to identity. Deliberately not the identity file: writing
that from a recording process would race the sender, which writes it during
resolve_distinct_id, and overloading it is what caused this.

Upgrade detection keys on the data directory already having content. A fresh
install has an empty one; anything else predates this session. That is a firmer
predicate than looking for 0.2.x's venv/ and requirements.txt, which is a guess
about files another part of the plugin may or may not have written and only ever
works for this one upgrade. The version is stored in the marker so later changes
record code.upgrade with a real from_version.

A cached email outlived an API key change. resolve_distinct_id kept the first
email it resolved and never looked again, so switching to a key from another
account kept attributing events to the previous one. It now stores a fingerprint
of the key the email came from and re-resolves when the current key differs, and
falls back to the anonymous id when no key is configured rather than continuing
to attribute to an account it cannot verify.

The dangerous part is the alias. resolve_distinct_id's second return value
becomes a PostHog $identify with $anon_distinct_id, and aliasing one account
email to another merges two real person profiles irreversibly. The re-resolve
path returns no alias; aliasing runs anonymous to email only, and never
email to email.

One existing test asserted that is_first_run flips when the identity file is
written, which is the defect itself. Rewritten, along with coverage for atomic
claiming, upgrade detection and version changes.

Claude-Session: https://claude.ai/code/session_01C7tEmH86HAr7GoAAKCEHZb
2026-09-15 00:18:38 +05:30
Saket Aryan cfdfe40e09 fix(plugins): stop delivering telemetry events twice, and stop losing parked ones
Two defects, one cause: the spool protocol infers ownership instead of holding
it, and never records progress.

Duplicate delivery after a partial failure. flush() posts the claim in batches of
100 and returns on the first failure, keeping the whole file. The retry then
posts every batch again, including the ones that already arrived — 150 recorded
events were delivered 250 times. Progress is now written back to the claim after
each successful batch, so a retry resumes where the send stopped and a crash
repeats at most one batch.

Duplicate delivery when two senders overlap. spool.replace(claim) is os.rename,
which preserves mtime, so a claim created after a quiet minute inherited the
spool's last-write time and looked abandoned the instant it existed. A second
sender starting while the first was still posting took it over and sent it too —
most likely at session end, when the MCP server's exit sender and the SessionEnd
flush worker both drain. Claims are now touched at claim time, and the per-batch
rewrite doubles as a lease heartbeat. _post makes one attempt with SEND_TIMEOUT
and no retry, so a heartbeat lands well inside the 120s lease; a test asserts
that margin so adding a retry loop to _post cannot silently break it.

Parked batches starved. _claim_spool only looked at parked .sending files when
no spool existed, and because sessions keep recording there usually was one — so
a batch parked by a failed send waited until the 7-day expiry deleted it unsent,
despite its own presence being what starts the sender in the first place.
flush() now drains the live spool and then parked claims in the same run, oldest
first, bounded. Expiry applies only after a genuine retry has failed, with the
attempt count carried in the filename.

A sender that gives up releases its lease rather than heartbeating on the way
out, so the next run picks the batch up promptly instead of waiting a full stale
window for a batch nobody is working on. A failing send stops the run, so one
broken connection cannot burn every parked batch's attempt budget at once.

Two existing tests asserted the old lifecycle and are updated in place, each
with a comment saying what changed.

Claude-Session: https://claude.ai/code/session_01C7tEmH86HAr7GoAAKCEHZb
2026-09-15 00:18:21 +05:30
Saket Aryan f9c566aa16 fix(plugins): report the plugin that produced the event, not the one that sent it
harness is set when an event is recorded; source was set when its batch was
sent. Both came from module globals that stay at "generic" and "MEM0_PLUGIN"
until telemetry.init() runs, and two processes in the pipeline never run it:

- `python3 telemetry.py`, the detached sender spawn_flush() starts at session
  start, after every skill command, and when the MCP server exits. Everything it
  delivered was labelled source=MEM0_PLUGIN. Only batches flush_worker.py
  happened to drain got the real host.
- mcp_server.py, which records every manual search as harness=generic.

All six Python plugins ship the same files, so source could not tell any of them
apart and MCP searches from every plugin landed in one generic bucket. The
portable plugin is worse: it has no flush_worker at all, so its only sender is
the uninitialised one and 100% of its events were mislabelled.

Two changes. record() stamps source beside harness, so the sending process stops
mattering — flush() already spreads per-event properties last, so a per-event
source wins over any sender default. And the build generates core/_harness_id.py
per host, seeding both modules at import, so identity no longer depends on an
entrypoint remembering to call init(). The build already computed HARNESS_ID and
spent it only on skill templating, and bundle_drift already diffs core/
byte-for-byte, so --check catches drift for free.

Deliberately not adding MEM0_PLUGIN_HARNESS to the six manifests: they sit
outside the --sync and --check boundary, which is the property that caused this.

Also unifies two defaults that disagreed. configure_harness derived
`<host>_plugin` while telemetry.init derived `MEM0_<HOST>_PLUGIN`, so a third
value existed. It was unreachable only because hook_runner never calls flush();
moving source into record() would have made it live.

Events now carry a uuid so a resend can be collapsed.

The suite stayed green through all of this because the only tests live under one
host, behind a conftest that calls init() at import. New tests run in real
subprocesses with no init, and cover the portable plugin, which would pass a
native-only test vacuously.

Claude-Session: https://claude.ai/code/session_01C7tEmH86HAr7GoAAKCEHZb
2026-09-15 00:17:53 +05:30
Saket Aryan 0d37619f24 fix(plugins): say what telemetry actually sends, and salt the hashes
The plugin README promises "anonymous usage events" and the telemetry module's
docstring says it sends only "salted hashes". Neither is true.

resolve_distinct_id() exchanges the API key for the account email and sends that
as the distinct_id on every event. Installing the plugin requires an API key, so
this is nearly every user. That is probably the behaviour we want — the Python
SDK and the CLI attribute the same way — but the description has to match it.

repo_hash and session_hash were unsalted SHA-256 cut to 16 hex characters.
repo.identity is a git remote URL, or `local:<absolute path>` when there is no
remote, which normally contains the account username. Sixteen unsalted hex
characters over that input space is enumerable, so the hash was not a privacy
control at all.

Salted per install, with the salt kept in the identity file. That preserves
every within-account join the analytics actually use and gives up only
cross-machine joins on the same repository, which nothing computes. Since the
distinct_id is already the email, the hash was never buying privacy from us —
only from whoever obtains the data later, which is exactly what the salt fixes.

Also corrects deepseek-plugin's README and source comment, which told readers
ZAPIER and STRANDS were already in the backend's KNOWN_EVENT_SOURCES allowlist.
Neither was.

Adds a Telemetry section to docs/integrations/claude-code.mdx, which had none.

Claude-Session: https://claude.ai/code/session_01C7tEmH86HAr7GoAAKCEHZb
2026-09-15 00:17:26 +05:30
Harsh Vardhan Gupta c7ee362aff fix(security): resolve 12 Vanta/Dependabot vulnerabilities across 6 pnpm workspaces + poetry.lock (#7280)
Co-authored-by: kartik-mem0 <kartik.labhshetwar@mem0.ai>
2026-09-11 16:07:57 +05:30
Kartik d873892dad feat(plugins)!: make Sidekick exclusive to Claude Code (#7278) 2026-09-10 20:51:50 +05:30
Kartik 02f7a9b2c4 docs: align agent plugin guides with shared runtime behavior (#7269) 2026-09-09 01:03:26 +05:30
Kartik 73e7b8763a refactor(integrations): shared agent plugin runtimes and native adapters (#7203) 2026-09-08 23:32:25 +05:30
Kartik dae67f74f5 fix(docs): SEO improvements for page titles, internal links, and URL structure (#7224) 2026-09-04 20:32:23 +05:30
Kartik 9a7924befd chore(release): bump Python and TypeScript SDK patch versions (#7210) 2026-09-02 18:44:55 +05:30
Kartik 3cf41878ea fix: replace PostHog evaluate_flags with static config for OSS notices (#7185) 2026-09-02 18:00:01 +05:30
Elif Sema Balcioglu c33ca27f5e docs: fix Oracle vector store setup and search examples (#7111) 2026-09-01 19:19:48 +05:30
Kartik 71fba8d464 feat(claude-code-plugin): move the Claude Code plugin to its own integration and ship it as 0.3.0 (#7106) 2026-09-01 02:34:45 +05:30
Kartik 19cb89aff4 docs: add 301 redirects for 49 legacy 404 pages (#7161) 2026-08-28 17:44:10 +05:30
Karthik fdfb763d6e docs(api-reference): add Dream (memory synthesis) endpoints (#7109)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-27 22:12:04 +05:30
Kartik 0070e08e01 feat(deepseek-plugin,mem0-strands): add usage telemetry (#7110) 2026-08-27 13:48:30 +05:30
Himanshu 39bc023305 docs(integrations): add Vercel Marketplace (managed) integration page (#7100) 2026-08-24 22:22:04 +05:30
krishna soni b1342a3408 refactor: replace custom validator with Pydantic extra="forbid" config (#7089) 2026-08-24 21:17:57 +05:30
Kartik b717e38785 refactor(integrations): rename dsh-mem0 to deepseek-plugin, strands-mem0 to mem0-strands (#7098) 2026-08-24 19:21:23 +05:30
Indian-boult dc82354e14 docs(skills): fix dead links in skills READMEs (#7092) 2026-08-24 18:25:33 +05:30
Kartik 4ddee9c51d chore(release): bump SDK, CLI, and plugin versions; add Strands, DeepSeek Harness, and Kimi changelogs (#7097) 2026-08-24 18:10:44 +05:30
Himanshu 7e09615571 feat(integrations): dsh-mem0 — Mem0 as a native DeepSeek Harness plugin (#7027)
Co-authored-by: kartik-mem0 <kartik.labhshetwar@mem0.ai>
2026-08-24 14:03:50 +05:30
Kartik d18e751dec docs: redirect five dead api-reference paths to their real pages (#7094) 2026-08-24 13:53:04 +05:30
Himanshu 8d5b7865bd feat(integrations): strands-mem0 | Mem0 as a native Strands MemoryStore (#7021) 2026-08-22 19:04:24 +05:30
Abhinav Singh 9b565da8e3 docs(embedders): document api_key on the Hugging Face Python config table (#7045) 2026-08-22 13:51:50 +05:30
Yiheng Zhao 48d0d0cd9c fix(docs): balance code fences in cookbook_template.mdx (#7054) 2026-08-22 13:50:49 +05:30
Kartik feb12852c0 fix(docs): redirect the eight 404 paths and repair dead wildcard rules (#7053) 2026-08-21 19:31:37 +05:30
Harsh Vardhan Gupta 5af797834c fix(security): resolve 17 Vanta/Dependabot HIGH+CRITICAL vulnerabilities across 5 pnpm workspaces (#7032) 2026-08-21 17:41:41 +05:30
Kartik 4fa4839077 fix(ci): make the vouch check speak, unblock list updates, widen the docs exemption (#6974) 2026-08-20 23:13:42 +05:30
Kartik 3599aa75ed docs: document the real search filter grammar (#6906) 2026-08-20 21:33:21 +05:30
Himanshu 1de6499b8a fix(integrations/zapier): address Zapier publishing review (#6985) 2026-08-20 18:53:06 +05:30
Kartik ed38ddf873 fix(python): huggingface TEI auth, procedural-memory content handling, and proxy pip auto-install (#6947) 2026-08-20 15:56:55 +05:30
Kartik 530d802b55 fix(plugins): bug-bash fixes for Cursor, Codex, Antigravity, and a Claude.ai docs page (#6948) 2026-08-20 15:56:17 +05:30
Kartik d3334fa5f1 docs: ground the platform/OSS comparison and memory-type status in reality (#6908) 2026-08-20 15:26:22 +05:30
Kartik 52b02c7cc1 docs: fix Claude Desktop MCP setup, CrewAI guide, and missing contributor docs (#6945) 2026-08-20 15:24:05 +05:30
mintlify[bot] 001c235229 Fix broken links: remove duplicate reranking redirect (#6975)
Co-authored-by: mintlify[bot] <109931778+mintlify[bot]@users.noreply.github.com>
2026-08-14 12:47:17 +00:00
Kartik bf2d591b27 docs: remove Controlling Memory Ingestion cookbook, redirect to Custom Instructions (#6955) 2026-08-14 18:16:36 +05:30
Kartik b4c50550bf docs: correct client call shape and stale v1 response examples (#6901) 2026-08-14 18:13:37 +05:30
610 changed files with 56164 additions and 21848 deletions
+1 -1
View File
@@ -8,7 +8,7 @@
"name": "mem0",
"source": {
"source": "local",
"path": "./integrations/mem0-plugin"
"path": "./integrations/codex-plugin"
},
"policy": {
"installation": "AVAILABLE",
+3 -3
View File
@@ -10,9 +10,9 @@
"plugins": [
{
"name": "mem0",
"source": "./integrations/mem0-plugin",
"description": "Mem0, the memory layer for AI agents. Add persistent memory, personalization, and semantic search to Claude workflows.",
"version": "0.2.14"
"source": "./integrations/claude-code-plugin",
"description": "Cross-session memory and token savings for coding agents.",
"version": "0.3.1"
}
]
}
+1 -1
View File
@@ -8,7 +8,7 @@
"name": "mem0",
"source": {
"source": "local",
"path": "./integrations/mem0-plugin"
"path": "./integrations/codex-plugin"
},
"policy": {
"installation": "AVAILABLE",
+3 -3
View File
@@ -10,9 +10,9 @@
"plugins": [
{
"name": "mem0",
"source": "./integrations/mem0-plugin",
"description": "Mem0, the memory layer for AI agents. Add persistent memory, personalization, and semantic search.",
"version": "0.2.14"
"source": "./integrations/cursor-plugin",
"description": "Cross-session memory and token savings for coding agents.",
"version": "0.3.1"
}
]
}
+61 -7
View File
@@ -18,15 +18,21 @@ Package workflows keep their own push-to-main and manual triggers. Their `pull_r
| 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 |
| Agent Plugins Python | `agent-plugins-python-checks.yml` | Push to main (shared Python core and native/portable plugin directories), manual | Runtime tests on Python 3.10; full pytest on 3.11, 3.12; ruff + generated-package drift on 3.12 |
| Agent Plugins TypeScript | `agent-plugins-typescript-checks.yml` | Push to main (`integrations/agent-plugin-core/typescript/`), manual | tsc + node:test on Node 22 |
| OpenCode Plugin | `opencode-plugin-checks.yml` | Push to main (`integrations/opencode-plugin/`), manual | Bun: tsc + build + dist artifact check |
| Pi Agent Plugin | `pi-agent-plugin-checks.yml` | Push to main (`integrations/pi-agent-plugin/`), manual | tsc + vitest + tsup on Node 20, 22 |
| DeepSeek Harness Plugin | `deepseek-plugin-checks.yml` | Push to main (`integrations/deepseek-plugin/`), manual | tsc + vitest + tsup on Node 20, 22 |
| n8n Node | `n8n-nodes-mem0-checks.yml` | Push to main (`integrations/n8n-nodes-mem0/`), manual | ESLint + tsc build on Node 20 |
| Zapier App | `zapier-mem0-checks.yml` | Push to main (`integrations/zapier-mem0/`), manual | tsc + `zapier validate` + offline unit tests on Node 22 |
| mem0-strands | `mem0-strands-checks.yml` | Push to main (`integrations/mem0-strands/`), manual | Ruff + mypy + pytest + hatch build on Python 3.10, 3.11, 3.12 |
| docs llms.txt | `docs-llms-txt-check.yml` | Manual | `docs/llms.txt` coverage |
| GitHub Scripts | inline in `ci-gate.yml` | none | `node` over every `.github/scripts/*.test.js` |
Adding a package CI workflow: give it `workflow_call` plus `push` / `workflow_dispatch` as needed but **no `pull_request` trigger**, then register it in `ci-gate.yml` with a path filter under the `changes` job, a call job, and an entry in the gate job's `needs` list.
`GitHub Scripts` is the one row that is a plain job inside `ci-gate.yml` rather than a called workflow, because a reusable workflow wrapping two `node` invocations would be more file than test. It runs on the `github_scripts` filter, which covers `.github/scripts/**` plus every file those tests read: `pr-gate.yml`, `vouch-check-pr.yml`, `issue-labeler.yml`, and `VOUCHED.td`. Add a new `.github/scripts/*.test.js` and it is picked up with no wiring; make a test read a new file and that file belongs in the filter.
## Branch protection on `main`
A repository ruleset named `Main Branch Rule`, id `11813754`. It enforces squash-only merges, linear history, no deletion, no force-push, and one approving review. Two status checks belong in its `required_status_checks` rule:
@@ -55,7 +61,9 @@ Requiring `CI Gate` also means fork PRs from first-time contributors cannot merg
| OpenClaw | `openclaw-cd.yml` | `openclaw-v*` | npm (`@mem0/openclaw-mem0`) |
| OpenCode Plugin | `opencode-plugin-cd.yml` | `opencode-v*` | npm (`@mem0/opencode-plugin`) |
| Pi Agent Plugin | `pi-agent-plugin-cd.yml` | `pi-agent-v*` | npm (`@mem0/pi-agent-plugin`) |
| DeepSeek Harness Plugin | `deepseek-plugin-cd.yml` | `deepseek-plugin-v*` | npm (`@mem0/deepseek-plugin`) |
| n8n Node | `n8n-nodes-mem0-cd.yml` | `n8n-nodes-mem0-v*` | npm (`@mem0/n8n-nodes-mem0`) |
| mem0-strands | `mem0-strands-cd.yml` | `mem0-strands-v*` | PyPI (`mem0-strands`) |
- Package CD workflows are `workflow_dispatch`-only, with `tag` and `prerelease` inputs. They check out and build the given tag.
- All publishing uses **OIDC trusted publishing**. No tokens, no secrets.
@@ -69,9 +77,9 @@ Requiring `CI Gate` also means fork PRs from first-time contributors cannot merg
| 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. |
| PR Gate | `pr-gate.yml` | Closes PRs that do not link an issue labeled `accepted`, and reopens them when that label arrives. Exempts members, bots, drafts, and docs-only changes. Never checks out PR code. |
| Vouch (check PR) | `vouch-check-pr.yml` | Closes PRs from authors denounced in `VOUCHED.td`. Comments once on PRs from authors merely absent from it, and blocks nothing in that case. |
| Vouch (manage list) | `vouch-manage-by-issue.yml` | Maintainers edit the trust list by commenting `!vouch @user`, `!denounce @user`, or `!unvouch @user` on any issue. Opens a PR against `VOUCHED.td` through a GitHub App token, for a maintainer to merge. |
| Issue Labeler | `issue-labeler.yml` | Labels issues from the `component` field in the issue forms |
| PR Labeler | `pr-labeler.yml` | Path-based labels, plus propagating labels from linked issues |
| Stale Bot | `stale.yml` | Marks stale issues and PRs |
@@ -79,7 +87,53 @@ Requiring `CI Gate` also means fork PRs from first-time contributors cannot merg
`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.
Both workflows exempt maintainers twice, and the second guard is the one that holds. `author_association` is rendered for the viewer, and a webhook payload has no privileged viewer: `MEMBER` needs the author's org membership to be **public**, `COLLABORATOR` needs a **direct** repository invite. An org member with private membership whose `maintain` comes through a team matches neither and arrives as `CONTRIBUTOR`, which is how PR #6948 was closed by its own author's gate. So the guard also skips any PR whose head branch lives in this repository (`head.repo.full_name == github.repository`). Pushing a branch here already requires write access and outside contributors always arrive from a fork, so that test means the same thing without depending on who is looking. Keep both: the `author_association` arm still covers members who work from their own fork.
`pr-gate.yml` carries two jobs whose `if:` conditions are deliberately disjoint. `gate` closes, and only ever runs on `opened`, `reopened`, and `ready_for_review`. `reopen` reopens, and only ever runs on `edited` or on `issues: labeled` with the `accepted` label. Nothing can both close and reopen on the same event, which is the property to preserve when editing either guard.
That split exists because the two halves of a gated PR's recovery arrive in either order. A maintainer usually labels the issue `accepted` at triage, before the author has linked it; sometimes the link lands first and the label follows. So `reopen` handles both directions. From `issues: labeled` it walks `closedByPullRequestsReferences` back to the pull requests that link the issue. From `edited` it takes the edited pull request directly. Both paths then apply the same four tests: the author is not denounced in `VOUCHED.td`, the PR is `CLOSED`, it links an issue labeled `accepted`, and it carries the `<!-- pr-gate -->` marker comment. Without the label path, a maintainer's label is inert. Without the `edited` path, an author who links the issue after it was labeled is stuck, since no other event fires.
The denounce test is what keeps the two gates from cancelling each other out. A denounced author whose PR also lacked an accepted issue was closed by both workflows, so it carries the `<!-- pr-gate -->` marker, and labeling the linked issue would otherwise reopen it. Vouch cannot undo that: reopening runs through `GITHUB_TOKEN`, which raises no events, so `vouch-check-pr.yml` never fires a second time. Reading the list here is the only place the check can live. It fails open like vouch does, warning and treating nobody as denounced if the file cannot be read, and it is the one piece of vouch semantics duplicated outside `vouch-check-pr.yml`, because `pr-gate.yml` never checks out the repository and so cannot import a shared parser. `.github/scripts/vouch-decision.test.js` covers the parsing and asserts `pr-gate.yml` still filters the list the same way.
`edited` must never reach the `gate` job. It fires on any title or description change, so when `gate` listened for it the gate re-judged pull requests that had been open for days and closed them the moment their author touched the description, which is what closed #6948. Rescuing on `edited` is safe for the same reason closing on it was not: the job can only move a PR from closed to open.
Reopening runs through `GITHUB_TOKEN`, which by design raises no further workflow events, so `gate` cannot bounce a freshly reopened PR straight back out.
The concurrency group is keyed on `github.event.action` as well as `github.event_name` and the number, and both keys carry weight. Without the event name, a maintainer applying `bug` right after `accepted` cancels the reopen mid-flight, since `cancel-in-progress` is on for `pull_request_target` and both label events would land in the same group. Without the action, `opened` and `edited` share a group on the same pull request, and an author who ticks a template checkbox in the seconds after opening cancels the run that was about to gate them: `gate` skips `edited` and `reopen` skips an open pull request, so the cancelled run is never replaced and the pull request stays ungated forever, since `opened` fires exactly once. Rapid successive edits still cancel each other, which is the dedup that was wanted.
The `edited` arm of `reopen` requires `github.event.pull_request.state == 'closed'`, so ordinary description edits on open pull requests do not start a runner.
Two known gaps, both mild. A PR that the gate closed, that someone reopened, and that a maintainer then closed deliberately still carries the marker, so labeling its issue reopens it again; a maintainer closes it once more. And an author who strips `Closes #<number>` out after passing keeps an open PR, which a reviewer sees anyway.
`GATE_EFFECTIVE_FROM` in `pr-gate.yml` is a `created_at` cutoff. `reopened` and `ready_for_review` still fire on PRs opened long before the gate existed, so without the cutoff part of the open backlog would be closed by a rule that did not exist when those PRs were filed. Set it to the actual merge date in UTC.
The gate's docs-only exemption covers `docs/` plus a named allowlist of four root files: `README.md`, `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, and `SECURITY.md`. It is an allowlist rather than a rule about top-level markdown because the repository root also holds `AGENTS.md`, `CLAUDE.md`, and `LLM.md`, which are the instructions coding agents read before touching this codebase. Those are functional files that happen to be written in prose, and rewriting them is a change to behaviour, so they stay gated. Markdown nested anywhere else stays gated for the same reason: `skills/**/*.md` and everything under `.github/` are functional too. Adding a genuinely prose root file means adding it to `rootDocs` in `pr-gate.yml`.
`.github/scripts/pr-gate-docs-exemption.test.js` covers that predicate. It pulls the `rootDocs` and `isDocs` lines out of `pr-gate.yml` and evaluates them, so it exercises the shipped rule rather than a copy that could drift from it, and it pins `AGENTS.md`, `CLAUDE.md`, and `LLM.md` on the gated side along with `skills/**/*.md`, nested `.github/` files, and the empty file list. It only accepts those two declarations in a literal one-line form, so keep `rootDocs` a `Set` of quoted names and `isDocs` a single arrow expression.
The two contribution gates answer different questions and neither covers for the other. `pr-gate.yml` judges the change, and the `accepted` label is how a maintainer says yes to it. `vouch-check-pr.yml` judges the author, and `VOUCHED.td` is how a maintainer says no to one. A vouched author with no accepted issue is still closed by the gate; a denounced author with an accepted issue is still closed by vouch. Read either one as a backstop for the other and both get weakened.
Vouch enforces on the denounce axis only, through `require-vouch: false` with `auto-close: true`. That pair is not the obvious reading of either input, so the decision table from v1.5.0 (`vouch/github.nu` at pinned SHA `d66fa29`) is worth stating outright:
| Author | `status` | Effect |
|---|---|---|
| ends in `[bot]` | `skipped` | nothing |
| collaborator with write or admin | `vouched` | nothing |
| listed in `VOUCHED.td` | `vouched` | nothing |
| listed as `-handle` | `closed` | action comments and closes |
| absent from the file | `allowed` | workflow comments, nothing closed |
`require-vouch: true` would close every first-time contributor, which is the opposite of what a trust list is for: the funnel has to stay open or nobody ever earns a vouch. `auto-close: false` is the setting that looked safe and did nothing at all, since in v1.5.0 both the unvouched and the denounced branch return before posting anything, leaving only a line in the run log. That is why `!denounce` was decorative until this pair landed.
Only the `allowed` arm is ours: a `github-script` step posts the soft comment, keyed on a `<!-- vouch-check -->` marker so a reopen does not comment twice. The `closed` arm belongs to the action, message and all. Keeping the two arms disjoint is what stops a denounced author getting two comments, so if that step is ever re-keyed off `allowed`, check the overlap first.
`.github/scripts/vouch-decision.test.js` holds that table as a `decide()` function and asserts the workflow's `require-vouch`, `auto-close`, and comment-step gating still produce it, comment counts included. Be clear about what that does and does not prove. `decide()` is a **hand transcription** of `gh-check-pr`, read from `vouch/github.nu` at the pinned SHA; the test cannot run the action, so it cannot notice the action changing underneath it. Left alone it would agree with itself forever, which makes bumping the pinned SHA the one edit it would otherwise sail through. So it also asserts `vouch-check-pr.yml` still pins `PINNED_VOUCH_SHA`, and a bump fails it on purpose: re-read `gh-check-pr` at the new revision, correct `decide()` and the table above, then move the constant. CI runs it through the `GitHub Scripts` job on any change to the scripts or the files they read.
Failure is open by design. If the action cannot read `VOUCHED.td` it falls back to an empty list, every author reads as absent, and nobody is closed by an API hiccup.
`vouch-manage-by-issue.yml` runs with `merge-immediately: "false"`. The `Main Branch Rule` ruleset requires one approving review and has no bypass actors, so the action's immediate `PUT /pulls/{n}/merge` would return 405 and leave `VOUCHED.td` unchanged on `main`. The bot opens the PR, a maintainer merges it. Setting `pull-request: "false"` is not an alternative: the same ruleset blocks direct pushes.
That workflow also needs `VOUCH_APP_ID` and `VOUCH_APP_PRIVATE_KEY` repository secrets. Without them it fails at the token step before doing anything. `vouch-check-pr.yml` needs neither.
## Issue forms and templates
@@ -99,4 +153,4 @@ Current field ids:
`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.
Vouch's only built-in exemptions are accounts ending in `[bot]` and repo collaborators with `write` or `admin`. **Organization membership alone is not one of them.** So `vouch-check-pr.yml` carries a job-level `if:` that skips the check for `OWNER`, `MEMBER`, and `COLLABORATOR` authors, the same exemption `pr-gate.yml` already applies. Org members are still listed in the file as a fallback, but the workflow guard is what actually holds.
+20 -6
View File
@@ -1,15 +1,29 @@
# 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.
# A denounced user (prefixed with a minus) is blocked outright: their pull
# requests are closed on sight, whatever they link. Being absent from this file
# blocks nothing. An unvouched author gets one comment saying so and their pull
# request is reviewed like anyone else's, because a first contribution has to
# start somewhere. Vouching is how that comment stops.
#
# This list is about who, and it is the only thing that judges who. Whether a
# change is wanted is a separate question, answered by the accepted label and
# enforced by pr-gate.yml. Neither gate substitutes for the other: a vouched
# author still needs an accepted issue, and a denounced author is turned away
# even holding one.
#
# Vouch automatically allows two kinds of account without consulting this file:
# accounts ending in [bot], and repo collaborators with write or admin
# permission. Org membership on its own is NOT one of them, so
# vouch-check-pr.yml skips the check 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.
# vouch-check-pr.yml skips the check for OWNER, MEMBER, and COLLABORATOR
# authors, and for any branch pushed to this repository.
#
# Keep every mem0ai member listed below anyway. The author_association arm of
# that guard is weaker than it looks: MEMBER needs the member's org membership
# to be public and COLLABORATOR needs a direct repo invite, so a member with
# private membership and a team-derived role reads as CONTRIBUTOR. Working from
# a branch here covers them, working from their own fork leaves this file as
# the only thing that does. A missing or miscased entry is a real gap.
#
# Syntax:
# - One handle per line (without @), sorted alphabetically.
@@ -0,0 +1,60 @@
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const gate = fs.readFileSync(path.join(__dirname, '..', 'workflows', 'pr-gate.yml'), 'utf8');
const rootDocsLine = gate.match(/^\s*(const rootDocs = new Set\(\['[\w.-]+'(?:, '[\w.-]+')*\]\);)\s*$/m);
const isDocsLine = gate.match(/^\s*(const isDocs = \(\w+\) => [\w.'"()[\]\/, |&!=><+-]+;)\s*$/m);
assert.ok(
rootDocsLine,
'pr-gate.yml no longer declares rootDocs as a single-line Set of quoted filenames. ' +
'This test evaluates that line to exercise the shipped predicate rather than a copy of it, ' +
'and only accepts a literal shape, so widen the pattern deliberately or keep the declaration literal.',
);
assert.ok(
isDocsLine,
'pr-gate.yml no longer declares isDocs as a single-line arrow expression. ' +
'This test evaluates that line to exercise the shipped predicate rather than a copy of it, ' +
'and refuses anything with a statement body, so keep it an expression.',
);
const isDocs = new Function(`${rootDocsLine[1]}\n${isDocsLine[1]}\nreturn isDocs;`)();
const exempt = (files) => files.length > 0 && files.every(isDocs);
const cases = [
[['docs/a.mdx'], true],
[['docs/platform/quickstart.mdx'], true],
[['README.md'], true],
[['CONTRIBUTING.md'], true],
[['CODE_OF_CONDUCT.md'], true],
[['SECURITY.md'], true],
[['README.md', 'CONTRIBUTING.md', 'docs/x.mdx'], true],
[['AGENTS.md'], false],
[['CLAUDE.md'], false],
[['LLM.md'], false],
[['README.md', 'AGENTS.md'], false],
[['README.md', 'mem0/memory/main.py'], false],
[['skills/mem0/SKILL.md'], false],
[['.github/AGENTS.md'], false],
[['.github/workflows/ci.yml'], false],
[['docs-site/index.md'], false],
[[], false],
];
let failures = 0;
for (const [files, expected] of cases) {
const actual = exempt(files);
const label = files.length ? files.join(', ') : '(no files)';
if (actual === expected) {
console.log(`ok ${label} -> ${actual ? 'exempt' : 'gated'}`);
} else {
failures += 1;
console.log(`FAIL ${label} -> ${actual ? 'exempt' : 'gated'}, expected ${expected ? 'exempt' : 'gated'}`);
}
}
console.log(failures === 0 ? '\nPASS' : `\nFAIL (${failures} cases)`);
process.exit(failures === 0 ? 0 : 1);
+122
View File
@@ -0,0 +1,122 @@
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const workflowPath = path.join(__dirname, '..', 'workflows', 'vouch-check-pr.yml');
const workflow = fs.readFileSync(workflowPath, 'utf8');
const PINNED_VOUCH_SHA = 'd66fa29a64600490892131ad87597c30c91fcac4';
assert.ok(
workflow.includes(`mitchellh/vouch/action/check-pr@${PINNED_VOUCH_SHA}`),
`decide() below is a hand transcription of gh-check-pr from vouch/github.nu at ${PINNED_VOUCH_SHA} (v1.5.0). ` +
'It reads the action, it does not run it, so on its own it agrees with itself whatever the action does. ' +
'vouch-check-pr.yml now pins a different revision: re-read gh-check-pr there, update decide() and the ' +
'decision table in .github/AGENTS.md to match it, then set PINNED_VOUCH_SHA to the new SHA.',
);
const actionDefaults = { 'require-vouch': true, 'auto-close': false };
const booleanInput = (name) => {
const match = workflow.match(new RegExp(`^\\s+${name}:\\s*"?(true|false)"?\\s*$`, 'm'));
return match ? match[1] === 'true' : actionDefaults[name];
};
const requireVouch = booleanInput('require-vouch');
const autoClose = booleanInput('auto-close');
const commentedStatus = (() => {
const match = workflow.match(/steps\.vouch\.outputs\.status == '(\w+)'/);
assert.ok(match, 'the follow-up comment step is not keyed on a vouch status');
return match[1];
})();
const decide = (author) => {
if (author === 'bot') return { status: 'skipped', closed: false, actionComments: false };
if (author === 'collaborator' || author === 'vouched') {
return { status: 'vouched', closed: false, actionComments: false };
}
if (author === 'denounced') {
if (!autoClose) return { status: 'closed', closed: false, actionComments: false };
return { status: 'closed', closed: true, actionComments: true };
}
if (!requireVouch) return { status: 'allowed', closed: false, actionComments: false };
if (!autoClose) return { status: 'closed', closed: false, actionComments: false };
return { status: 'closed', closed: true, actionComments: true };
};
const outcome = (author) => {
const result = decide(author);
return { ...result, workflowComments: result.status === commentedStatus };
};
const cases = [
{ author: 'bot', closed: false, comments: 0 },
{ author: 'collaborator', closed: false, comments: 0 },
{ author: 'vouched', closed: false, comments: 0 },
{ author: 'unvouched', closed: false, comments: 1 },
{ author: 'denounced', closed: true, comments: 1 },
];
let failures = 0;
for (const expected of cases) {
const actual = outcome(expected.author);
const comments = Number(actual.actionComments) + Number(actual.workflowComments);
try {
assert.strictEqual(actual.closed, expected.closed, `${expected.author}: closed`);
assert.strictEqual(comments, expected.comments, `${expected.author}: comment count`);
console.log(`ok ${expected.author} -> ${actual.status}, closed=${actual.closed}, comments=${comments}`);
} catch (error) {
failures += 1;
console.log(`FAIL ${expected.author} -> ${actual.status}, closed=${actual.closed}, comments=${comments}`);
console.log(` ${error.message}: expected ${JSON.stringify(expected)}`);
}
}
console.log(`\nvouch@${PINNED_VOUCH_SHA.slice(0, 7)} require-vouch=${requireVouch} auto-close=${autoClose} comment-on=${commentedStatus}`);
const parseDenounced = (contents) => new Set(contents
.split('\n')
.map((line) => line.trim())
.filter((line) => line.startsWith('-'))
.map((line) => line.slice(1).split(/\s+/)[0].split(':').pop().toLowerCase())
.filter(Boolean));
const gate = fs.readFileSync(path.join(__dirname, '..', 'workflows', 'pr-gate.yml'), 'utf8');
assert.ok(
gate.includes(".filter((line) => line.startsWith('-'))"),
'pr-gate.yml no longer parses the denounce list the way this test does',
);
const vouched = fs.readFileSync(path.join(__dirname, '..', 'VOUCHED.td'), 'utf8');
const denouncedNow = parseDenounced(vouched);
const sample = parseDenounced([
'# -notacomment is a comment line',
'-SpamBot seeded 2026-08-12',
'-github:OtherSpammer',
'realcontributor',
'',
].join('\n'));
let parseFailures = 0;
for (const [label, actual, expected] of [
['denounce entry, with note', sample.has('spambot'), true],
['denounce entry, platform prefixed', sample.has('otherspammer'), true],
['comment line is not an entry', sample.has('notacomment'), false],
['vouched entry is not denounced', sample.has('realcontributor'), false],
['live file parses without throwing', denouncedNow instanceof Set, true],
]) {
try {
assert.strictEqual(actual, expected, label);
console.log(`ok ${label}`);
} catch (error) {
parseFailures += 1;
console.log(`FAIL ${label}: ${error.message}`);
}
}
console.log(`denounced in VOUCHED.td: ${denouncedNow.size}`);
const total = failures + parseFailures;
console.log(total === 0 ? 'PASS' : `FAIL (${total} assertions)`);
process.exit(total === 0 ? 0 : 1);
@@ -0,0 +1,97 @@
name: Agent Plugins Python Checks
# Python runtime, adapters, generated bundles, and portable plugin validation.
# On PRs this is invoked by ci-gate.yml (the single required check);
# push-to-main and manual runs remain standalone.
on:
workflow_dispatch:
push:
branches: [main]
paths:
- 'integrations/agent-plugin-core/**'
- '!integrations/agent-plugin-core/typescript/**'
- 'integrations/mem0-agent-plugin/**'
- 'integrations/claude-code-plugin/**'
- 'integrations/cursor-plugin/**'
- 'integrations/codex-plugin/**'
- 'integrations/kimi-plugin/**'
- 'integrations/antigravity-plugin/**'
- 'marketplace.json'
- '.agents/plugins/marketplace.json'
- '.claude-plugin/marketplace.json'
- '.codex-plugin/marketplace.json'
- '.cursor-plugin/marketplace.json'
- '.kimi-plugin/marketplace.json'
- '.github/workflows/agent-plugins-python-checks.yml'
workflow_call:
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install runtime test tooling
if: matrix.python-version == '3.10'
run: pip install pytest
- name: Install build and test tooling
if: matrix.python-version != '3.10'
run: pip install pytest ruff -r integrations/agent-plugin-core/requirements-dev.txt
- name: Check Python runtime compatibility
run: >-
python3 -m compileall -q
integrations/agent-plugin-core/python
integrations/claude-code-plugin/adapters
integrations/cursor-plugin/hooks
integrations/codex-plugin/hooks
integrations/kimi-plugin/hooks
integrations/antigravity-plugin/hooks
- name: Lint
if: matrix.python-version == '3.12'
run: >-
python3 -m ruff check
integrations/agent-plugin-core
integrations/claude-code-plugin
integrations/cursor-plugin
integrations/codex-plugin
integrations/kimi-plugin
integrations/antigravity-plugin
- name: Verify installable plugins are current
if: matrix.python-version == '3.12'
run: |
for host in claude-code cursor codex kimi antigravity; do
python3 integrations/agent-plugin-core/build/build.py "$host" --kind native --check
done
python3 integrations/agent-plugin-core/build/build.py mem0-agent-plugin --kind portable --check
- name: Run Python 3.10 runtime tests
if: matrix.python-version == '3.10'
run: >-
python3 -m pytest -q
integrations/claude-code-plugin/tests/test_memory_core.py
integrations/claude-code-plugin/tests/test_telemetry.py
- name: Run full tests
if: matrix.python-version != '3.10'
run: >-
python3 -m pytest -q
integrations/agent-plugin-core/tests
integrations/claude-code-plugin/tests
integrations/cursor-plugin/tests
integrations/codex-plugin/tests
integrations/kimi-plugin/tests
integrations/antigravity-plugin/tests
--ignore=integrations/claude-code-plugin/tests/integration
@@ -0,0 +1,42 @@
name: Agent Plugins TypeScript Checks
# Shared TypeScript runtime checks. Each consuming integration keeps its own
# build workflow, which is also triggered when this shared core changes.
on:
workflow_dispatch:
push:
branches: [main]
paths:
- 'integrations/agent-plugin-core/typescript/**'
- '.github/workflows/agent-plugins-typescript-checks.yml'
workflow_call:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 10
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: 'pnpm'
cache-dependency-path: integrations/agent-plugin-core/typescript/pnpm-lock.yaml
- name: Install dependencies
working-directory: integrations/agent-plugin-core/typescript
run: pnpm install --frozen-lockfile
- name: Type check
working-directory: integrations/agent-plugin-core/typescript
run: pnpm typecheck
- name: Run tests
working-directory: integrations/agent-plugin-core/typescript
run: pnpm test
+92 -11
View File
@@ -38,12 +38,16 @@ jobs:
cli_python: ${{ steps.filter.outputs.cli_python }}
cli_node: ${{ steps.filter.outputs.cli_node }}
openclaw: ${{ steps.filter.outputs.openclaw }}
mem0_plugin: ${{ steps.filter.outputs.mem0_plugin }}
agent_plugins_python: ${{ steps.filter.outputs.agent_plugins_python }}
agent_plugins_typescript: ${{ steps.filter.outputs.agent_plugins_typescript }}
opencode_plugin: ${{ steps.filter.outputs.opencode_plugin }}
pi_agent_plugin: ${{ steps.filter.outputs.pi_agent_plugin }}
deepseek_plugin: ${{ steps.filter.outputs.deepseek_plugin }}
n8n_nodes_mem0: ${{ steps.filter.outputs.n8n_nodes_mem0 }}
zapier_mem0: ${{ steps.filter.outputs.zapier_mem0 }}
mem0_strands: ${{ steps.filter.outputs.mem0_strands }}
docs_llms_txt: ${{ steps.filter.outputs.docs_llms_txt }}
github_scripts: ${{ steps.filter.outputs.github_scripts }}
steps:
- uses: dorny/paths-filter@v3
id: filter
@@ -72,21 +76,45 @@ jobs:
- '.github/workflows/ci-gate.yml'
openclaw:
- 'integrations/openclaw/**'
- 'integrations/agent-plugin-core/typescript/**'
- '.github/workflows/openclaw-checks.yml'
- '.github/workflows/ci-gate.yml'
mem0_plugin:
- 'integrations/mem0-plugin/**'
- '!integrations/mem0-plugin/.opencode-plugin/**'
- '.github/workflows/mem0-plugin-checks.yml'
agent_plugins_python:
- 'integrations/agent-plugin-core/**'
- '!integrations/agent-plugin-core/typescript/**'
- 'integrations/mem0-agent-plugin/**'
- 'integrations/claude-code-plugin/**'
- 'integrations/cursor-plugin/**'
- 'integrations/codex-plugin/**'
- 'integrations/kimi-plugin/**'
- 'integrations/antigravity-plugin/**'
- 'marketplace.json'
- '.agents/plugins/marketplace.json'
- '.claude-plugin/marketplace.json'
- '.codex-plugin/marketplace.json'
- '.cursor-plugin/marketplace.json'
- '.kimi-plugin/marketplace.json'
- '.github/workflows/agent-plugins-python-checks.yml'
- '.github/workflows/ci-gate.yml'
agent_plugins_typescript:
- 'integrations/agent-plugin-core/typescript/**'
- '.github/workflows/agent-plugins-typescript-checks.yml'
- '.github/workflows/ci-gate.yml'
opencode_plugin:
- 'integrations/mem0-plugin/.opencode-plugin/**'
- 'integrations/opencode-plugin/**'
- 'integrations/agent-plugin-core/typescript/**'
- '.github/workflows/opencode-plugin-checks.yml'
- '.github/workflows/ci-gate.yml'
pi_agent_plugin:
- 'integrations/pi-agent-plugin/**'
- 'integrations/agent-plugin-core/typescript/**'
- '.github/workflows/pi-agent-plugin-checks.yml'
- '.github/workflows/ci-gate.yml'
deepseek_plugin:
- 'integrations/deepseek-plugin/**'
- 'integrations/agent-plugin-core/typescript/**'
- '.github/workflows/deepseek-plugin-checks.yml'
- '.github/workflows/ci-gate.yml'
n8n_nodes_mem0:
- 'integrations/n8n-nodes-mem0/**'
- '.github/workflows/n8n-nodes-mem0-checks.yml'
@@ -94,6 +122,10 @@ jobs:
- 'integrations/zapier-mem0/**'
- '.github/workflows/zapier-mem0-checks.yml'
- '.github/workflows/ci-gate.yml'
mem0_strands:
- 'integrations/mem0-strands/**'
- '.github/workflows/mem0-strands-checks.yml'
- '.github/workflows/ci-gate.yml'
docs_llms_txt:
- 'docs/**/*.mdx'
- 'docs/llms.txt'
@@ -101,6 +133,13 @@ jobs:
- 'scripts/llms-txt-ignore.txt'
- '.github/workflows/docs-llms-txt-check.yml'
- '.github/workflows/ci-gate.yml'
github_scripts:
- '.github/scripts/**'
- '.github/VOUCHED.td'
- '.github/workflows/pr-gate.yml'
- '.github/workflows/vouch-check-pr.yml'
- '.github/workflows/issue-labeler.yml'
- '.github/workflows/ci-gate.yml'
python-sdk:
name: Python SDK
@@ -137,11 +176,17 @@ jobs:
uses: ./.github/workflows/openclaw-checks.yml
secrets: inherit
mem0-plugin:
name: Mem0 Plugin
agent-plugins-python:
name: Agent Plugins Python
needs: changes
if: needs.changes.outputs.mem0_plugin == 'true'
uses: ./.github/workflows/mem0-plugin-checks.yml
if: needs.changes.outputs.agent_plugins_python == 'true'
uses: ./.github/workflows/agent-plugins-python-checks.yml
agent-plugins-typescript:
name: Agent Plugins TypeScript
needs: changes
if: needs.changes.outputs.agent_plugins_typescript == 'true'
uses: ./.github/workflows/agent-plugins-typescript-checks.yml
secrets: inherit
opencode-plugin:
@@ -158,6 +203,13 @@ jobs:
uses: ./.github/workflows/pi-agent-plugin-checks.yml
secrets: inherit
deepseek-plugin:
name: DeepSeek Harness Plugin
needs: changes
if: needs.changes.outputs.deepseek_plugin == 'true'
uses: ./.github/workflows/deepseek-plugin-checks.yml
secrets: inherit
n8n-nodes-mem0:
name: n8n Node
needs: changes
@@ -170,6 +222,13 @@ jobs:
uses: ./.github/workflows/zapier-mem0-checks.yml
secrets: inherit
mem0-strands:
name: mem0-strands
needs: changes
if: needs.changes.outputs.mem0_strands == 'true'
uses: ./.github/workflows/mem0-strands-checks.yml
secrets: inherit
docs-llms-txt:
name: docs llms.txt
needs: changes
@@ -177,6 +236,24 @@ jobs:
uses: ./.github/workflows/docs-llms-txt-check.yml
secrets: inherit
github-scripts:
name: GitHub Scripts
needs: changes
if: needs.changes.outputs.github_scripts == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Run .github/scripts tests
run: |
for test in .github/scripts/*.test.js; do
echo "::group::$test"
node "$test"
echo "::endgroup::"
done
gate:
name: CI Gate
needs:
@@ -186,12 +263,16 @@ jobs:
- cli-python
- cli-node
- openclaw
- mem0-plugin
- agent-plugins-python
- agent-plugins-typescript
- opencode-plugin
- pi-agent-plugin
- deepseek-plugin
- n8n-nodes-mem0
- zapier-mem0
- mem0-strands
- docs-llms-txt
- github-scripts
if: always()
runs-on: ubuntu-latest
steps:
+60
View File
@@ -0,0 +1,60 @@
name: Publish @mem0/deepseek-plugin 📦 to npm
# Dispatched by release.yml (Release Router) when a release tagged
# deepseek-plugin-v* is published. Can also be dispatched manually to re-publish
# a tag.
on:
workflow_dispatch:
inputs:
tag:
description: 'Release tag to build and publish (e.g. deepseek-plugin-v0.1.1)'
required: true
type: string
prerelease:
description: 'Publish under the version preid dist-tag instead of latest'
required: false
type: boolean
default: false
jobs:
build-n-publish:
name: Build and publish @mem0/deepseek-plugin 📦 to npm
if: startsWith(inputs.tag, 'deepseek-plugin-v')
runs-on: ubuntu-latest
permissions:
id-token: write
defaults:
run:
working-directory: integrations/deepseek-plugin
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.tag }}
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
registry-url: 'https://registry.npmjs.org'
cache: 'pnpm'
cache-dependency-path: integrations/deepseek-plugin/pnpm-lock.yaml
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm build
- name: Publish to npm
run: |
if [ "${{ inputs.prerelease }}" = "true" ]; then
PREID=$(node -p "require('./package.json').version.split('-')[1].split('.')[0]")
npx npm@latest publish --provenance --access public --tag "$PREID"
else
npx npm@latest publish --provenance --access public
fi
@@ -0,0 +1,89 @@
name: deepseek-plugin checks
# On PRs this is invoked by ci-gate.yml (the single required check);
# push-to-main and manual runs remain standalone.
on:
workflow_dispatch:
push:
branches: [main]
paths:
- 'integrations/deepseek-plugin/**'
- 'integrations/agent-plugin-core/typescript/**'
- '.github/workflows/deepseek-plugin-checks.yml'
workflow_call:
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
cache-dependency-path: integrations/deepseek-plugin/pnpm-lock.yaml
- name: Install dependencies
run: cd integrations/deepseek-plugin && pnpm install --frozen-lockfile
- name: Type check
run: cd integrations/deepseek-plugin && pnpm exec tsc --noEmit
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20, 22]
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
cache-dependency-path: integrations/deepseek-plugin/pnpm-lock.yaml
- name: Install dependencies
run: cd integrations/deepseek-plugin && pnpm install --frozen-lockfile
- name: Run tests
run: cd integrations/deepseek-plugin && pnpm exec vitest run
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
cache-dependency-path: integrations/deepseek-plugin/pnpm-lock.yaml
- name: Install dependencies
run: cd integrations/deepseek-plugin && pnpm install --frozen-lockfile
- name: Build
run: cd integrations/deepseek-plugin && pnpm build
- name: Verify package artifact
run: python3 integrations/agent-plugin-core/conformance/artifacts.py deepseek
-58
View File
@@ -1,58 +0,0 @@
name: Mem0 Plugin Checks
# On PRs this is invoked by ci-gate.yml (the single required check);
# push-to-main and manual runs remain standalone.
#
# Covers the Python plugin (scripts/ + tests/). The nested .opencode-plugin/
# is a separate package with its own workflow (opencode-plugin-checks.yml).
on:
workflow_dispatch:
push:
branches: [main]
paths:
- 'integrations/mem0-plugin/**'
- '!integrations/mem0-plugin/.opencode-plugin/**'
- '.github/workflows/mem0-plugin-checks.yml'
workflow_call:
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
working-directory: integrations/mem0-plugin
run: |
pip install -r requirements.txt
pip install pytest
- name: Verify hook entry points are executable
working-directory: integrations/mem0-plugin
run: |
missing=$(find scripts -name '*.sh' ! -name '_*' ! -perm -u+x -print)
if [ -n "$missing" ]; then
echo "Hook entry points must be executable:"
echo "$missing"
exit 1
fi
- name: Check hook manifests are valid JSON
working-directory: integrations/mem0-plugin
run: |
for f in plugin.json mcp_config.json hooks.json hooks/*.json; do
jq empty "$f" || (echo "Invalid JSON: $f" && exit 1)
done
- name: Run tests
working-directory: integrations/mem0-plugin
run: pytest -q
+50
View File
@@ -0,0 +1,50 @@
name: Publish mem0-strands 🐍 distribution 📦 to PyPI
# Dispatched by release.yml (Release Router) when a release tagged
# mem0-strands-v* is published. Can also be dispatched manually to re-publish
# a tag. Publishing uses PyPI Trusted Publishing (OIDC), so no API token is
# stored; the `mem0-strands` PyPI project must have a trusted publisher
# configured for mem0ai/mem0 + this workflow.
on:
workflow_dispatch:
inputs:
tag:
description: 'Release tag to build and publish (e.g. mem0-strands-v0.1.0)'
required: true
type: string
prerelease:
description: 'Unused for PyPI (pre-releases are expressed in the version itself); accepted for router uniformity'
required: false
type: boolean
default: false
jobs:
build-n-publish:
name: Build and publish mem0-strands 📦 to PyPI
if: startsWith(inputs.tag, 'mem0-strands-v')
runs-on: ubuntu-latest
permissions:
id-token: write
defaults:
run:
working-directory: integrations/mem0-strands/python
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.tag }}
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install Hatch
run: pip install hatch
- name: Build a binary wheel and a source tarball
run: hatch build --clean
- name: Publish distribution 📦 to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: integrations/mem0-strands/python/dist/
+82
View File
@@ -0,0 +1,82 @@
name: mem0-strands CI
# On PRs this is invoked by ci-gate.yml (the single required check);
# push-to-main and manual runs remain standalone.
on:
workflow_dispatch:
push:
branches: [main]
paths:
- 'integrations/mem0-strands/**'
- '.github/workflows/mem0-strands-checks.yml'
workflow_call:
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dev dependencies
working-directory: integrations/mem0-strands/python
run: pip install -e ".[dev]"
- name: Lint with ruff
working-directory: integrations/mem0-strands/python
run: ruff check .
- name: Check formatting
working-directory: integrations/mem0-strands/python
run: ruff format --check .
- name: Type-check with mypy
working-directory: integrations/mem0-strands/python
run: mypy src
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dev dependencies
working-directory: integrations/mem0-strands/python
run: pip install -e ".[dev]"
- name: Run tests
working-directory: integrations/mem0-strands/python
run: pytest
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install Hatch
run: pip install hatch
- name: Build
working-directory: integrations/mem0-strands/python
run: hatch build --clean
- name: Verify dist output
run: |
ls integrations/mem0-strands/python/dist/*.whl || (echo "Wheel file missing" && exit 1)
ls integrations/mem0-strands/python/dist/*.tar.gz || (echo "Source dist missing" && exit 1)
+3 -4
View File
@@ -8,6 +8,7 @@ on:
branches: [main]
paths:
- 'integrations/openclaw/**'
- 'integrations/agent-plugin-core/typescript/**'
- '.github/workflows/openclaw-checks.yml'
workflow_call:
@@ -93,7 +94,5 @@ jobs:
- name: Build
run: cd integrations/openclaw && pnpm build
- name: Verify dist output exists
run: |
test -f integrations/openclaw/dist/index.js || (echo "Build output missing: dist/index.js" && exit 1)
test -f integrations/openclaw/dist/index.d.ts || (echo "Build output missing: dist/index.d.ts" && exit 1)
- name: Verify package artifact
run: python3 integrations/agent-plugin-core/conformance/artifacts.py openclaw
+1 -1
View File
@@ -25,7 +25,7 @@ jobs:
id-token: write
defaults:
run:
working-directory: integrations/mem0-plugin/.opencode-plugin
working-directory: integrations/opencode-plugin
steps:
- uses: actions/checkout@v4
with:
+6 -5
View File
@@ -7,7 +7,8 @@ on:
push:
branches: [main]
paths:
- 'integrations/mem0-plugin/.opencode-plugin/**'
- 'integrations/opencode-plugin/**'
- 'integrations/agent-plugin-core/typescript/**'
- '.github/workflows/opencode-plugin-checks.yml'
workflow_call:
@@ -16,7 +17,7 @@ jobs:
runs-on: ubuntu-latest
defaults:
run:
working-directory: integrations/mem0-plugin/.opencode-plugin
working-directory: integrations/opencode-plugin
steps:
- uses: actions/checkout@v4
@@ -34,6 +35,6 @@ jobs:
- name: Build
run: bun run build
- name: Verify dist output exists
run: |
test -f dist/index.js || (echo "Build output missing: dist/index.js" && exit 1)
- name: Verify package artifact
working-directory: .
run: python3 integrations/agent-plugin-core/conformance/artifacts.py opencode
+3 -6
View File
@@ -8,6 +8,7 @@ on:
branches: [main]
paths:
- 'integrations/pi-agent-plugin/**'
- 'integrations/agent-plugin-core/typescript/**'
- '.github/workflows/pi-agent-plugin-checks.yml'
workflow_call:
@@ -84,9 +85,5 @@ jobs:
- name: Build
run: cd integrations/pi-agent-plugin && pnpm build
- name: Verify dist output exists
run: |
test -f integrations/pi-agent-plugin/dist/index.js || (echo "Build output missing: dist/index.js" && exit 1)
test -f integrations/pi-agent-plugin/dist/index.d.ts || (echo "Build output missing: dist/index.d.ts" && exit 1)
test -f integrations/pi-agent-plugin/dist/entry.js || (echo "Build output missing: dist/entry.js" && exit 1)
test -f integrations/pi-agent-plugin/dist/entry.d.ts || (echo "Build output missing: dist/entry.d.ts" && exit 1)
- name: Verify package artifact
run: python3 integrations/agent-plugin-core/conformance/artifacts.py pi-agent
+118 -7
View File
@@ -2,11 +2,13 @@ name: PR Gate
on:
pull_request_target:
types: [opened, reopened, edited, ready_for_review]
types: [opened, reopened, ready_for_review, edited]
issues:
types: [labeled]
concurrency:
group: pr-gate-${{ github.event.pull_request.number }}
cancel-in-progress: true
group: pr-gate-${{ github.event_name }}-${{ github.event.action }}-${{ github.event.pull_request.number || github.event.issue.number }}
cancel-in-progress: ${{ github.event_name == 'pull_request_target' }}
env:
GATE_EFFECTIVE_FROM: '2026-08-12T00:00:00Z'
@@ -19,8 +21,11 @@ permissions:
jobs:
gate:
if: >-
github.event_name == 'pull_request_target' &&
github.event.action != 'edited' &&
github.event.pull_request.draft == false &&
github.event.pull_request.user.type != 'Bot' &&
github.event.pull_request.head.repo.full_name != github.repository &&
!contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.pull_request.author_association)
runs-on: ubuntu-latest
steps:
@@ -47,7 +52,9 @@ jobs:
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/'))) {
const rootDocs = new Set(['README.md', 'CONTRIBUTING.md', 'CODE_OF_CONDUCT.md', 'SECURITY.md']);
const isDocs = (filename) => filename.startsWith('docs/') || rootDocs.has(filename);
if (files.length > 0 && files.every((file) => isDocs(file.filename))) {
core.info('Docs-only PR, gate skipped');
return;
}
@@ -75,6 +82,7 @@ jobs:
}
const body = [
'<!-- pr-gate -->',
'Thanks for taking the time to open this.',
'',
'We only review pull requests that fix an issue we have already agreed to take on, so this one is closed for now.',
@@ -84,10 +92,9 @@ jobs:
'',
'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.',
'3. Ask a maintainer to label that issue `accepted`. This pull request reopens by itself when they do.',
'',
'Already linked an accepted issue? Edit the description to include `Closes #<number>` and reopen. The check reruns automatically.',
'Issue already labeled `accepted`? Just add `Closes #<number>` to the description. That reopens this too.',
'',
'Documentation-only changes skip this gate entirely.',
'',
@@ -101,3 +108,107 @@ jobs:
owner, repo, pull_number: pr.number, state: 'closed',
});
core.info(`Closed #${pr.number}: no accepted issue linked`);
reopen:
if: >-
(github.event_name == 'issues' && github.event.label.name == 'accepted') ||
(github.event.action == 'edited' && github.event.pull_request.state == 'closed')
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v7
with:
script: |
const { owner, repo } = context.repo;
const marker = '<!-- pr-gate -->';
const denounced = await (async () => {
try {
const { data } = await github.rest.repos.getContent({
owner, repo, path: '.github/VOUCHED.td',
ref: context.payload.repository.default_branch,
});
return new Set(Buffer.from(data.content, 'base64').toString('utf8')
.split('\n')
.map((line) => line.trim())
.filter((line) => line.startsWith('-'))
.map((line) => line.slice(1).split(/\s+/)[0].split(':').pop().toLowerCase())
.filter(Boolean));
} catch (error) {
core.warning(`Could not read VOUCHED.td, treating nobody as denounced: ${error.message}`);
return new Set();
}
})();
const isReopenable = async (number) => {
const { repository } = await github.graphql(
`query ($owner: String!, $repo: String!, $number: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $number) {
state
author { login }
closingIssuesReferences(first: 20) {
nodes { labels(first: 50) { nodes { name } } }
}
}
}
}`,
{ owner, repo, number },
);
const pullRequest = repository.pullRequest;
if (denounced.has(pullRequest.author?.login?.toLowerCase())) {
core.info(`#${number} is from a denounced author. Vouch outranks this gate.`);
return false;
}
return pullRequest.state === 'CLOSED' &&
pullRequest.closingIssuesReferences.nodes.some((issue) =>
issue.labels.nodes.some((label) => label.name === 'accepted'));
};
let candidates;
if (context.eventName === 'issues') {
const { repository } = await github.graphql(
`query ($owner: String!, $repo: String!, $number: Int!) {
repository(owner: $owner, name: $repo) {
issue(number: $number) {
closedByPullRequestsReferences(first: 20, includeClosedPrs: true) {
nodes { number }
}
}
}
}`,
{ owner, repo, number: context.payload.issue.number },
);
candidates = repository.issue.closedByPullRequestsReferences.nodes.map((pr) => pr.number);
} else {
candidates = [context.payload.pull_request.number];
}
for (const number of candidates) {
if (!(await isReopenable(number))) {
core.info(`#${number} is not a closed pull request linking an accepted issue. Skipped.`);
continue;
}
const comments = await github.paginate(github.rest.issues.listComments, {
owner, repo, issue_number: number, per_page: 100,
});
if (!comments.some((comment) => comment.body?.startsWith(marker))) {
core.info(`#${number} was not closed by this gate. Left alone.`);
continue;
}
try {
await github.rest.pulls.update({
owner, repo, pull_number: number, state: 'open',
});
} catch (error) {
core.warning(`Could not reopen #${number}: ${error.message}`);
continue;
}
await github.rest.issues.createComment({
owner, repo, issue_number: number,
body: 'An `accepted` issue is linked now, so this is open again and ready for review.',
});
core.info(`Reopened #${number}`);
}
+2
View File
@@ -45,7 +45,9 @@ jobs:
openclaw-v*) workflow="openclaw-cd.yml" ;;
opencode-v*) workflow="opencode-plugin-cd.yml" ;;
pi-agent-v*) workflow="pi-agent-plugin-cd.yml" ;;
deepseek-plugin-v*) workflow="deepseek-plugin-cd.yml" ;;
n8n-nodes-mem0-v*) workflow="n8n-nodes-mem0-cd.yml" ;;
mem0-strands-v*) workflow="mem0-strands-cd.yml" ;;
v*) workflow="cd.yml" ;;
*)
echo "::error::Release tag '$TAG' does not match any known package prefix — nothing will be published. See the tag prefix table in AGENTS.md."
+33 -1
View File
@@ -16,12 +16,44 @@ jobs:
check:
if: >-
github.event.pull_request.user.type != 'Bot' &&
github.event.pull_request.head.repo.full_name != github.repository &&
!contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.pull_request.author_association)
runs-on: ubuntu-latest
steps:
- uses: mitchellh/vouch/action/check-pr@d66fa29a64600490892131ad87597c30c91fcac4 # v1.5.0
id: vouch
with:
pr-number: ${{ github.event.pull_request.number }}
auto-close: false
require-vouch: false
auto-close: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- if: steps.vouch.outputs.status == 'allowed'
uses: actions/github-script@v7
with:
script: |
const { owner, repo } = context.repo;
const pr = context.payload.pull_request;
const marker = '<!-- vouch-check -->';
const comments = await github.paginate(github.rest.issues.listComments, {
owner, repo, issue_number: pr.number, per_page: 100,
});
if (comments.some((comment) => comment.body?.startsWith(marker))) {
core.info('Vouch comment already posted, skipped.');
return;
}
const body = [
marker,
`Hi @${context.payload.pull_request.user.login}, thanks for opening this pull request.`,
'',
"This is just a soft check: you are not yet in this repo's vouched contributor list (`.github/VOUCHED.td`). Nothing is blocked and there is nothing you need to do.",
'',
`A maintainer can vouch for you by commenting \`!vouch @${context.payload.pull_request.user.login}\` on any issue.`,
].join('\n');
await github.rest.issues.createComment({
owner, repo, issue_number: pr.number, body,
});
+1 -1
View File
@@ -37,6 +37,6 @@ jobs:
denounce-keyword: "!denounce"
unvouch-keyword: "!unvouch"
pull-request: "true"
merge-immediately: "true"
merge-immediately: "false"
env:
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
+8
View File
@@ -14,6 +14,10 @@ server/.env
# Distribution / packaging
.Python
build/
!integrations/agent-plugin-core/build/
!integrations/agent-plugin-core/build/*.py
!integrations/agent-plugin-core/build/schemas/
!integrations/agent-plugin-core/build/schemas/*.json
develop-eggs/
dist/
downloads/
@@ -191,3 +195,7 @@ qdrant_storage/
testing.ipynb
.weave/
# TypeScript incremental build info and local, uncommitted e2e scripts (used by the integrations, e.g. integrations/deepseek-plugin)
*.tsbuildinfo
*.local.mjs
+3 -3
View File
@@ -5,11 +5,11 @@
{
"id": "mem0",
"displayName": "Mem0",
"version": "0.1.0",
"description": "Persistent memory for Kimi Code. Remembers decisions, patterns, and preferences across sessions.",
"version": "0.3.1",
"description": "Cross-session memory and token savings for coding agents.",
"homepage": "https://mem0.ai",
"keywords": ["memory", "personalization", "mcp", "semantic-search"],
"source": "https://github.com/mem0ai/mem0/tree/main/integrations/mem0-plugin"
"source": "https://github.com/mem0ai/mem0/tree/main/integrations/kimi-plugin"
}
]
}
+22 -2
View File
@@ -10,10 +10,11 @@ This is a polyglot monorepo and **every package sets its own rules**. Read the `
## Do NOT
- Open a pull request without a signed CLA. It will not be reviewed. See [The CLA is not optional](#the-cla-is-not-optional).
- Open a pull request that does not link an issue carrying the `accepted` label. A bot closes it within a minute. See [Two gates decide whether your pull request stays open](#two-gates-decide-whether-your-pull-request-stays-open).
- Modify anything in `.github/workflows/` without explicit maintainer approval. Publishing credentials are pinned to workflow filenames.
- Commit `.env` files, API keys, or credentials.
- Skip pre-commit hooks.
- Use npm or yarn in TypeScript packages. This repo is pnpm-only (Bun in `.opencode-plugin/`).
- Use npm or yarn in TypeScript packages. This repo is pnpm-only (Bun in `integrations/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.
@@ -121,6 +122,24 @@ Full guide: [`CONTRIBUTING.md`](CONTRIBUTING.md). Conduct: [`CODE_OF_CONDUCT.md`
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.**
### Two gates decide whether your pull request stays open
Two workflows run on every pull request from a fork. They judge different things and neither covers for the other, so a pull request has to get past both.
**The [PR Gate](.github/workflows/pr-gate.yml) judges the change.** It closes any pull request that does not link an issue carrying the `accepted` label. Closed is a queue decision, not a verdict: when a maintainer applies the label the pull request reopens by itself. Drafts, documentation-only changes, and branches pushed to this repository rather than a fork are all exempt.
**The [vouch check](.github/workflows/vouch-check-pr.yml) judges the account.** It reads [`.github/VOUCHED.td`](.github/VOUCHED.td), which has three possible answers about any given person:
| The list says | Meaning | Effect on the pull request |
|---|---|---|
| `-handle` | a maintainer ran `!denounce` after the code of conduct process | closed, even with an accepted issue |
| nothing at all | everybody who has not contributed here before | **none.** One comment saying nothing is blocked. |
| `handle` | a maintainer ran `!vouch` | none, and the comment stops appearing |
Being vouched grants nothing. It is a "we have seen this person before" flag that mutes the newcomer comment, not permission to skip the accepted-issue rule. Being absent from the list costs nothing.
If you are an agent opening a pull request on someone's behalf, the practical consequence is one rule: **get the linked issue labelled `accepted` before you open the pull request, or expect the pull request to be closed and to reopen later.** Do not work around either gate, do not reopen a gated pull request by hand, and do not re-file the same change under a new pull request when one is closed.
### The CLA is not optional
**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.
@@ -151,5 +170,6 @@ Beyond the CLA and the accepted-issue gate, the [Contribution Conduct](CODE_OF_C
| Documentation contributions | `docs/contributing/documentation.mdx` |
| PR template | `.github/PULL_REQUEST_TEMPLATE.md` |
| Issue forms | `.github/ISSUE_TEMPLATE/` |
| Trust list (vouch) | `.github/VOUCHED.td` |
| Contribution gates | [Two gates decide whether your pull request stays open](#two-gates-decide-whether-your-pull-request-stays-open) |
| Trust list (vouch) | [`.github/VOUCHED.td`](.github/VOUCHED.td) |
| CI/CD, gates, rulesets | [`.github/AGENTS.md`](.github/AGENTS.md) |
+31 -3
View File
@@ -40,9 +40,24 @@ 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.
means the change isn't in the queue yet. Once a maintainer labels the issue the
pull request reopens itself, and you don't have to do anything. Documentation-only
changes skip the gate entirely.
A second check looks at who opened the pull request rather than what it changes.
If you are not yet in this repo's contributor list
([`.github/VOUCHED.td`](./.github/VOUCHED.td)) you get one comment saying so.
**Nothing is blocked and there is nothing you need to do.** A maintainer can add
you by commenting `!vouch @you` on any issue, which only stops that comment from
appearing again. Being on the list is not permission to skip the accepted-issue
rule, and being absent from it costs you nothing.
The list has a negative side too. A maintainer can `!denounce` an account that
has been through the
[code of conduct](./CODE_OF_CONDUCT.md#contribution-conduct) enforcement process,
and pull requests from that account are closed whether or not they link an
accepted issue. This is rare, it is never where anyone starts, and it is
reversible.
Security fixes are the one exception, and they don't go through public pull
requests at all. Follow the [Security Policy](./SECURITY.md) instead, which uses
@@ -85,6 +100,19 @@ sign. Signing takes less than a minute and only needs to be done once. Pull
requests from contributors who have not signed the CLA will be blocked from
merging.
## First Contribution Fast Path
Fixing a typo or a small docs issue? You don't need the full workflow below.
1. **Pick something small.** Look for issues labeled `documentation` or `good first issue`, or a typo/broken link you noticed while reading the docs.
2. **Branch from `main`** with a name that says what you're fixing, e.g. `docs/fix-quickstart-typo` or `fix/broken-crewai-link`.
3. **Make the change, then run only what applies:**
- Docs-only change (`docs/**`): preview with `make docs`. If you added or removed an `.mdx` page, run `python scripts/check-llms-txt-coverage.py --write` so `docs/llms.txt` stays in sync.
- Code change: run the linter and tests for the package you touched, see [Development Workflow](#development-workflow) below.
4. **Open a PR** against `main` with `Closes #<issue-number>` and a one-line description of what you fixed.
For anything larger than a docs fix or a small bug, follow the full workflow below.
## Repository Layout
The two most common contribution targets are the SDKs:
+1 -1
View File
@@ -1,4 +1,4 @@
# Mem0 - The Memory Layer for AI Agents
# Mem0 - The Memory Layer for Personalized AI
## Overview
+1 -1
View File
@@ -11,7 +11,7 @@ install:
hatch env create
install_all:
pip install ruff==0.16.0 groq together boto3 litellm ollama chromadb weaviate weaviate-client sentence_transformers vertexai \
pip install ruff==0.16.0 groq together boto3 'litellm>=1.83.7,<1.98.0' ollama chromadb weaviate weaviate-client sentence_transformers vertexai \
google-generativeai elasticsearch opensearch-py vecs "pinecone<7.0.0" pinecone-text faiss-cpu langchain-community \
upstash-vector azure-search-documents langchain-memgraph langchain-neo4j langchain-aws rank-bm25 pymochow pymongo psycopg kuzu databricks-sdk valkey
+1 -1
View File
@@ -1,6 +1,6 @@
<p align="center">
<a href="https://github.com/mem0ai/mem0">
<img src="docs/images/banner-sm.png" width="800px" alt="Mem0 - The Memory Layer for AI Agents">
<img src="docs/images/banner-sm.png" width="800px" alt="Mem0 - The Memory Layer for Personalized AI">
</a>
</p>
<p align="center" style="display: flex; justify-content: center; gap: 20px; align-items: center;">
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@mem0/cli",
"version": "0.2.12",
"version": "0.2.13",
"description": "The official CLI for mem0 — the memory layer for AI agents",
"type": "module",
"bin": {
@@ -41,7 +41,7 @@
"tsup": "^8.0.0",
"tsx": "^4.7.0",
"vite": "^6.0.0",
"vitest": "^4.1.0",
"vitest": "^4.1.11",
"@biomejs/biome": "^1.7.0",
"@types/node": "^20.0.0"
},
+46 -46
View File
@@ -51,8 +51,8 @@ importers:
specifier: ^6.0.0
version: 6.4.3(@types/node@20.19.37)(tsx@4.21.0)
vitest:
specifier: ^4.1.0
version: 4.1.8(@types/node@20.19.37)(vite@6.4.3(@types/node@20.19.37)(tsx@4.21.0))
specifier: ^4.1.11
version: 4.1.11(@types/node@20.19.37)(vite@6.4.3(@types/node@20.19.37)(tsx@4.21.0))
packages:
@@ -439,11 +439,11 @@ packages:
'@types/node@20.19.37':
resolution: {integrity: sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==}
'@vitest/expect@4.1.8':
resolution: {integrity: sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==}
'@vitest/expect@4.1.11':
resolution: {integrity: sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==}
'@vitest/mocker@4.1.8':
resolution: {integrity: sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw==}
'@vitest/mocker@4.1.11':
resolution: {integrity: sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==}
peerDependencies:
msw: ^2.4.9
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
@@ -453,20 +453,20 @@ packages:
vite:
optional: true
'@vitest/pretty-format@4.1.8':
resolution: {integrity: sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==}
'@vitest/pretty-format@4.1.11':
resolution: {integrity: sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==}
'@vitest/runner@4.1.8':
resolution: {integrity: sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg==}
'@vitest/runner@4.1.11':
resolution: {integrity: sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==}
'@vitest/snapshot@4.1.8':
resolution: {integrity: sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==}
'@vitest/snapshot@4.1.11':
resolution: {integrity: sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==}
'@vitest/spy@4.1.8':
resolution: {integrity: sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA==}
'@vitest/spy@4.1.11':
resolution: {integrity: sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==}
'@vitest/utils@4.1.8':
resolution: {integrity: sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==}
'@vitest/utils@4.1.11':
resolution: {integrity: sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==}
acorn@8.16.0:
resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==}
@@ -910,20 +910,20 @@ packages:
yaml:
optional: true
vitest@4.1.8:
resolution: {integrity: sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig==}
vitest@4.1.11:
resolution: {integrity: sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==}
engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0}
hasBin: true
peerDependencies:
'@edge-runtime/vm': '*'
'@opentelemetry/api': ^1.9.0
'@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0
'@vitest/browser-playwright': 4.1.8
'@vitest/browser-preview': 4.1.8
'@vitest/browser-webdriverio': 4.1.8
'@vitest/coverage-istanbul': 4.1.8
'@vitest/coverage-v8': 4.1.8
'@vitest/ui': 4.1.8
'@vitest/browser-playwright': 4.1.11
'@vitest/browser-preview': 4.1.11
'@vitest/browser-webdriverio': 4.1.11
'@vitest/coverage-istanbul': 4.1.11
'@vitest/coverage-v8': 4.1.11
'@vitest/ui': 4.1.11
happy-dom: '*'
jsdom: '*'
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
@@ -1186,44 +1186,44 @@ snapshots:
dependencies:
undici-types: 6.21.0
'@vitest/expect@4.1.8':
'@vitest/expect@4.1.11':
dependencies:
'@standard-schema/spec': 1.1.0
'@types/chai': 5.2.3
'@vitest/spy': 4.1.8
'@vitest/utils': 4.1.8
'@vitest/spy': 4.1.11
'@vitest/utils': 4.1.11
chai: 6.2.2
tinyrainbow: 3.1.0
'@vitest/mocker@4.1.8(vite@6.4.3(@types/node@20.19.37)(tsx@4.21.0))':
'@vitest/mocker@4.1.11(vite@6.4.3(@types/node@20.19.37)(tsx@4.21.0))':
dependencies:
'@vitest/spy': 4.1.8
'@vitest/spy': 4.1.11
estree-walker: 3.0.3
magic-string: 0.30.21
optionalDependencies:
vite: 6.4.3(@types/node@20.19.37)(tsx@4.21.0)
'@vitest/pretty-format@4.1.8':
'@vitest/pretty-format@4.1.11':
dependencies:
tinyrainbow: 3.1.0
'@vitest/runner@4.1.8':
'@vitest/runner@4.1.11':
dependencies:
'@vitest/utils': 4.1.8
'@vitest/utils': 4.1.11
pathe: 2.0.3
'@vitest/snapshot@4.1.8':
'@vitest/snapshot@4.1.11':
dependencies:
'@vitest/pretty-format': 4.1.8
'@vitest/utils': 4.1.8
'@vitest/pretty-format': 4.1.11
'@vitest/utils': 4.1.11
magic-string: 0.30.21
pathe: 2.0.3
'@vitest/spy@4.1.8': {}
'@vitest/spy@4.1.11': {}
'@vitest/utils@4.1.8':
'@vitest/utils@4.1.11':
dependencies:
'@vitest/pretty-format': 4.1.8
'@vitest/pretty-format': 4.1.11
convert-source-map: 2.0.0
tinyrainbow: 3.1.0
@@ -1627,15 +1627,15 @@ snapshots:
fsevents: 2.3.3
tsx: 4.21.0
vitest@4.1.8(@types/node@20.19.37)(vite@6.4.3(@types/node@20.19.37)(tsx@4.21.0)):
vitest@4.1.11(@types/node@20.19.37)(vite@6.4.3(@types/node@20.19.37)(tsx@4.21.0)):
dependencies:
'@vitest/expect': 4.1.8
'@vitest/mocker': 4.1.8(vite@6.4.3(@types/node@20.19.37)(tsx@4.21.0))
'@vitest/pretty-format': 4.1.8
'@vitest/runner': 4.1.8
'@vitest/snapshot': 4.1.8
'@vitest/spy': 4.1.8
'@vitest/utils': 4.1.8
'@vitest/expect': 4.1.11
'@vitest/mocker': 4.1.11(vite@6.4.3(@types/node@20.19.37)(tsx@4.21.0))
'@vitest/pretty-format': 4.1.11
'@vitest/runner': 4.1.11
'@vitest/snapshot': 4.1.11
'@vitest/spy': 4.1.11
'@vitest/utils': 4.1.11
es-module-lexer: 2.1.0
expect-type: 1.3.0
magic-string: 0.30.21
+2 -1
View File
@@ -31,7 +31,8 @@ export class PlatformBackend implements Backend {
this.headers = {
Authorization: `Token ${config.apiKey}`,
"Content-Type": "application/json",
"X-Mem0-Source": "cli",
"X-Mem0-Source": "CLI",
"X-Mem0-Client": `mem0-cli-node/${CLI_VERSION}`,
"X-Mem0-Client-Language": "node",
"X-Mem0-Client-Version": CLI_VERSION,
};
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "mem0-cli"
version = "0.2.11"
version = "0.2.12"
description = "The official CLI for mem0 — the memory layer for AI agents"
readme = "README.md"
license = "Apache-2.0"
+1 -1
View File
@@ -1,3 +1,3 @@
"""mem0 CLI — the command-line interface for the mem0 memory layer."""
__version__ = "0.2.11"
__version__ = "0.2.12"
+2 -1
View File
@@ -27,7 +27,8 @@ class PlatformBackend(Backend):
headers={
"Authorization": f"Token {config.api_key}",
"Content-Type": "application/json",
"X-Mem0-Source": "cli",
"X-Mem0-Source": "CLI",
"X-Mem0-Client": f"mem0-cli-python/{__version__}",
"X-Mem0-Client-Language": "python",
"X-Mem0-Client-Version": __version__,
},
+4 -3
View File
@@ -1,5 +1,6 @@
---
title: "Overview"
title: "API Reference Overview"
sidebarTitle: "Overview"
icon: "terminal"
iconType: "solid"
description: "REST APIs for memory management, search, and entity operations"
@@ -10,7 +11,7 @@ description: "REST APIs for memory management, search, and entity operations"
Mem0 provides a comprehensive REST API for integrating advanced memory capabilities into your applications. Create, search, update, and manage memories across users, agents, and custom entities with simple HTTP requests.
<Info>
**Quick start:** Get your API key from the <a href="https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=api-reference" rel="nofollow">Mem0 Dashboard</a> and make your first memory operation in minutes.
**Quick start:** Get your API key from the <a href="https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=api-reference">Mem0 Dashboard</a> and make your first memory operation in minutes.
</Info>
---
@@ -87,7 +88,7 @@ All API requests require authentication using Token-based authentication. Includ
Authorization: Token <your-api-key>
```
Get your API key from the <a href="https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=api-reference" rel="nofollow">Mem0 Dashboard</a>.
Get your API key from the <a href="https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=api-reference">Mem0 Dashboard</a>.
<Warning>
**Keep your API key secure.** Never expose it in client-side code or public repositories. Use environment variables and server-side requests only.
@@ -0,0 +1,5 @@
---
title: "Preview Dream Scope"
description: "A no-write preview of the scope Dream synthesis would analyze for a project."
openapi: "post /api/v1/orgs/organizations/{org_id}/projects/{project_id}/dream/preview/"
---
@@ -0,0 +1,5 @@
---
title: "Get Dream Activity"
description: "Supersede/merge activity feed for a project, newest first (keyset-paginated)."
openapi: "get /api/v1/orgs/organizations/{org_id}/projects/{project_id}/dream/activity/"
---
@@ -0,0 +1,5 @@
---
title: "Get Dream Configuration"
description: "Retrieve a project's Dream (memory synthesis) configuration and plan entitlements."
openapi: "get /api/v1/orgs/organizations/{org_id}/projects/{project_id}/dream/config/"
---
@@ -0,0 +1,5 @@
---
title: "Get a Synthesized Memory's Sources"
description: "The source memories a synthesized (pattern) memory was distilled from."
openapi: "get /api/v1/orgs/organizations/{org_id}/projects/{project_id}/dream/memory/{memory_id}/sources/"
---
@@ -0,0 +1,5 @@
---
title: "Get Memories in a Dream Run"
description: "Keyset page of the synthesized memories within a single synthesis run."
openapi: "get /api/v1/orgs/organizations/{org_id}/projects/{project_id}/dream/runs/{run_id}/memories/"
---
@@ -0,0 +1,5 @@
---
title: "Get Dream Synthesis Runs"
description: "Synthesis activity grouped per run, newest first (keyset-paginated)."
openapi: "get /api/v1/orgs/organizations/{org_id}/projects/{project_id}/dream/runs/"
---
@@ -0,0 +1,5 @@
---
title: "Get Dream Stats"
description: "Lifecycle and synthesis counts for a project, plus reflection freshness."
openapi: "get /api/v1/orgs/organizations/{org_id}/projects/{project_id}/dream/stats/"
---
@@ -0,0 +1,5 @@
---
title: "Update Dream Configuration"
description: "Enable or disable Synthesis (reflection) for a project, or change the reflection mode."
openapi: "patch /api/v1/orgs/organizations/{org_id}/projects/{project_id}/dream/config/"
---
+2 -1
View File
@@ -1,5 +1,6 @@
---
title: 'Delete Memory'
title: "Delete Memory API Endpoint"
sidebarTitle: "Delete Memory"
description: "Delete a single memory by its unique memory ID from the Mem0 platform using the DELETE endpoint."
openapi: delete /v1/memories/{memory_id}/
---
+2 -1
View File
@@ -1,5 +1,6 @@
---
title: 'Update Memory'
title: "Update Memory API Endpoint"
sidebarTitle: "Update Memory"
description: "Update the content, metadata, timestamp, or expiration date of a single memory by its unique ID using the PUT endpoint."
openapi: put /v1/memories/{memory_id}/
---
@@ -1,5 +1,6 @@
---
title: 'Add Member'
title: "Add Organization Member API Endpoint"
sidebarTitle: "Add Member"
description: "Add a new member to an organization with a specified role such as READER or OWNER access level."
openapi: post /api/v1/orgs/organizations/{org_id}/members/
---
@@ -1,5 +1,6 @@
---
title: 'Get Members'
title: "Get Organization Members API Endpoint"
sidebarTitle: "Get Members"
description: "Retrieve a list of all members belonging to a specific organization on the Mem0 platform."
openapi: get /api/v1/orgs/organizations/{org_id}/members/
---
@@ -1,5 +1,6 @@
---
title: 'Add Member'
title: "Add Project Member API Endpoint"
sidebarTitle: "Add Member"
description: "Add a new member to a project with a specified role such as READER or OWNER access level."
openapi: post /api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/
---
@@ -1,5 +1,6 @@
---
title: 'Get Members'
title: "Get Project Members API Endpoint"
sidebarTitle: "Get Members"
description: "Retrieve a list of all members belonging to a specific project on the Mem0 platform."
openapi: get /api/v1/orgs/organizations/{org_id}/projects/{project_id}/members/
---
+49
View File
@@ -4,6 +4,55 @@ description: "Major product launches, headline features, and milestones for Mem0
mode: "wide"
---
<Update label="2026-08-24" description="DeepSeek Harness plugin">
**DeepSeek Harness: Mem0 as a Native Cordis Plugin**
The DeepSeek Harness agent forgets everything between sessions. [`@mem0/deepseek-plugin`](https://www.npmjs.com/package/@mem0/deepseek-plugin) gives it two Mem0-backed tools, so recall and writes persist across runs against the same memory bank you already use from Claude Code, Codex, and every other connected agent.
- **Two agent-callable tools:** `search_memory` recalls facts relevant to a query, `add_memory` stores a fact for future sessions. Both accept per-call `userId` / `agentId` / `runId` scope overrides.
- **Native Cordis lifecycle:** The plugin declares `inject = ['tools']` so it waits for the harness tool registry, then registers through `ctx.tools.register()`. Unmounting the plugin removes the tools automatically.
- **Managed backend, not a memory file:** Server-side extraction, semantic dedup, and conflict resolution, rather than a Markdown file the agent has to maintain itself.
- **Config:** `userId` is required, `apiKey` defaults to `$MEM0_API_KEY`, and `host` optionally targets a dedicated Mem0 Platform base URL.
See [DeepSeek Harness](/integrations/deepseek-plugin) for setup and [SDK & Tools](/changelog/sdk) for PR links.
<Note>
Developer preview. Auto-capture and auto-recall, where memory reaches the context with no explicit tool call, are planned but not yet built.
</Note>
</Update>
<Update label="2026-08-24" description="Strands Agents integration">
**Strands Agents: Mem0 as a Native MemoryStore**
[`mem0-strands`](https://pypi.org/project/mem0-strands/) plugs Mem0 into AWS's [Strands Agents](https://strandsagents.com/) SDK as a native `MemoryStore`, so recall and writes happen inside the agent loop rather than as tool calls the model has to remember to make.
- **Automatic recall:** The `MemoryManager` drives the store on every turn, searching Mem0 and injecting the results into the prompt with no tool call required.
- **Server-side extraction:** Because the store implements `add_messages`, enabling extraction routes raw conversation turns straight to Mem0's extraction pipeline, skipping the extra client-side model call needed to distill facts first.
- **Hosted or self-hosted:** An API key targets the hosted Mem0 Platform; a config dict targets self-hosted Mem0 OSS.
- **Entity scoping:** Accepts `user_id`, `agent_id`, `run_id`, and `app_id`, with at least one required and invalid combinations rejected at construction rather than on the first write.
See [Strands Agents](/integrations/strands) for setup and [SDK & Tools](/changelog/sdk) for PR links.
</Update>
<Update label="2026-08-13" description="Kimi Code plugin">
**Kimi Code: Mem0 Joins the Editor Plugin Family**
The shared Mem0 editor plugin now covers Kimi Code alongside Claude Code, Cursor, Codex, and Antigravity, running on the same scripts, skills, and memory bank, so context written in one editor is available in the others.
- **Hosted MCP server:** Registers `https://mcp.mem0.ai/mcp/`, authenticated with `MEM0_API_KEY` as a bearer token.
- **Automatic capture and recall:** SessionStart loads context through the `context-loader` skill, and hooks on prompt submit, file reads, Bash output, stop, and pre-compact capture and inject memory without an explicit tool call.
- **Guardrails:** Direct `Write` / `Edit` / `MultiEdit` to memory files is blocked, and metadata defaults are enforced on every Mem0 MCP tool call.
- **Kimi hook adapter:** A shim normalizes Kimi Code's hook contract to the shape the shared scripts already expect, resolving the real project directory and translating the differing prompt, tool-output, and MCP tool-name fields.
See [SDK & Tools](/changelog/sdk) for version details and PR links.
</Update>
<Update label="2026-07-30" description="n8n and Zapier integrations">
**Workflow Automation: Mem0 Memory in n8n and Zapier**
+429 -1
View File
@@ -7,6 +7,27 @@ mode: "wide"
<Tabs>
<Tab title="Python">
<Update label="2026-09-02" description="v2.0.20">
**Improvements:**
- **OSS notices:** Notice configuration now comes from a static, cacheable repository file with a bundled disabled fallback and deterministic rollout assignment, instead of calling PostHog's feature-flag evaluation API. This keeps notices fail-safe when the remote config is unavailable and removes the PostHog feature-flag request from notice evaluation ([#7185](https://github.com/mem0ai/mem0/pull/7185))
- **Vector Stores:** `RedisDBConfig` now uses Pydantic's native `extra="forbid"` handling for unknown fields instead of a custom model validator, preserving strict validation while returning standard Pydantic errors ([#7089](https://github.com/mem0ai/mem0/pull/7089))
</Update>
<Update label="2026-08-24" description="v2.0.19">
**Bug Fixes:**
- **Embeddings:** `HuggingFaceEmbedding` now falls back to the `HUGGINGFACE_API_KEY` env var, then a placeholder key, when `huggingface_base_url` is set and no `api_key` is configured. The OpenAI-compatible client used to talk to TEI endpoints raises at construction when no key resolves at all, so a TEI deployment that doesn't require a real key previously failed to initialize ([#6947](https://github.com/mem0ai/mem0/pull/6947))
- **Core:** `remove_code_blocks()` now accepts list-shaped content (a sequence of `{"text": ...}` blocks, as some agent frameworks pass) by joining each block's text before stripping code fences, instead of raising `AttributeError` from calling `.strip()` on a list ([#6947](https://github.com/mem0ai/mem0/pull/6947))
- **Core:** `create_procedural_memory()` (`Memory` and `AsyncMemory`) now raises a clear `ValueError` when the LLM returns no content for the summary, instead of continuing with empty content that surfaced as a confusing error further down the call ([#6947](https://github.com/mem0ai/mem0/pull/6947))
- **Proxy:** `mem0.proxy` no longer auto-installs `litellm` via a `pip install` subprocess when the import fails; it now raises `ImportError` with instructions to install it yourself. The auto-install could hang or fail silently in restricted environments and ran an unreviewed install on the caller's behalf ([#6947](https://github.com/mem0ai/mem0/pull/6947))
- **Client:** `get_all()` (sync and async) now sends `page` and `page_size` as independent query params instead of requiring both to be set before either was sent. Passing only `page_size` without `page` previously had it silently dropped, so results came back at the server's default page size ([#6900](https://github.com/mem0ai/mem0/pull/6900))
- **LLMs:** Add `provider_override` to `AWSBedrockConfig`, an explicit provider name (for example `"anthropic"`) for when `model` is an application inference profile ARN whose opaque ID has no provider substring for `extract_provider()` to detect. Without it, those ARNs raised `ValueError: Unable to determine provider` ([#6899](https://github.com/mem0ai/mem0/pull/6899))
- **LLMs:** `VllmConfig` now falls back to the `VLLM_BASE_URL` env var when `vllm_base_url` isn't passed explicitly. The default was filled in before the env var was ever checked, so `VLLM_BASE_URL` was silently ignored ([#6897](https://github.com/mem0ai/mem0/pull/6897))
</Update>
<Update label="2026-08-11" description="v2.0.18">
**Bug Fixes:**
@@ -1206,6 +1227,26 @@ See the [OSS v2 to v3 migration guide](https://docs.mem0.ai/migration/oss-v2-to-
<Tab title="TypeScript">
<Update label="2026-09-02" description="v3.1.8">
**Improvements:**
- **OSS notices:** Notice configuration now comes from a static, cacheable repository file with a bundled disabled fallback and deterministic rollout assignment, instead of calling PostHog's feature-flag evaluation API. This keeps notices fail-safe when the remote config is unavailable and removes the PostHog feature-flag request from notice evaluation ([#7185](https://github.com/mem0ai/mem0/pull/7185))
</Update>
<Update label="2026-08-24" description="v3.1.7">
**Bug Fixes:**
- **Vector Stores:** Redis and Valkey `search()` / `get()` / `list()` now preserve `agent_id`, `run_id`, and `user_id` as snake_case in the returned payload. The shared payload formatter camelCased every key including those three identity fields, so entity ids came back as `agentId` / `runId` / `userId`, inconsistent with every other vector store ([#6902](https://github.com/mem0ai/mem0/pull/6902))
- **Memory (OSS):** Embedding-cache lookups now use `Object.prototype.hasOwnProperty.call()` instead of the `in` operator or a falsy `||` check. Memory text matching an inherited `Object.prototype` property name (`constructor`, `toString`, and similar) previously short-circuited the lookup and resolved to that inherited value instead of computing a real embedding, silently corrupting the stored vector ([#6903](https://github.com/mem0ai/mem0/pull/6903))
- **Client:** `getAll()` now sends `page` and `pageSize` as independent query params instead of requiring both to be set before either was sent. Passing only `pageSize` without `page` previously had it silently dropped, so results came back at the server's default page size ([#6900](https://github.com/mem0ai/mem0/pull/6900))
- **LLMs:** Add `providerOverride` to the Bedrock `LLMConfig`, an explicit provider name for when `model` is an application inference profile ARN whose opaque ID has no provider substring for `extractProvider()` to detect. Without it, those ARNs threw before the provider-specific settings could be initialized ([#6899](https://github.com/mem0ai/mem0/pull/6899))
**Security:**
- **Dependencies:** Resolved 17 additional high and critical severity dependency vulnerabilities across 5 pnpm workspaces (`mem0-ts`, `vercel-ai-sdk`, `n8n-nodes-mem0`, `zapier-mem0`, `server/dashboard`) via `pnpm.overrides` and a `tar` patch ([#7032](https://github.com/mem0ai/mem0/pull/7032))
</Update>
<Update label="2026-08-11" description="v3.1.6">
**New Features:**
@@ -1502,7 +1543,7 @@ The largest provider release for the TypeScript OSS SDK so far: 17 new vector st
**Improvements:**
- **Telemetry:** Sample OSS hot-path events at 10% to reduce PostHog event volume ([#4771](https://github.com/mem0ai/mem0/pull/4771))
See the [TypeScript SDK migration guide](https://docs.mem0.ai/migration/ts-v2-to-v3) for upgrade instructions.
See the [OSS v2 to v3 migration guide](https://docs.mem0.ai/migration/oss-v2-to-v3) for upgrade instructions.
</Update>
@@ -1823,6 +1864,17 @@ See the [TypeScript SDK migration guide](https://docs.mem0.ai/migration/ts-v2-to
<Tab title="CLI">
<Update label="2026-08-24" description="Python v0.2.12 / Node v0.2.13">
**New Features:**
- **`version`:** New `mem0 version` subcommand, alongside the existing `--version` flag, so scripts and agent harnesses can read the CLI version as a regular subcommand instead of a root-level flag (Python and Node [#6907](https://github.com/mem0ai/mem0/pull/6907))
- **`add`:** New `--agent-custom-instructions` flag, threaded through to `agent_custom_instructions` on the `/v3/memories/add/` payload: a second extraction instruction set that applies only to agent-scoped memories, matching the SDKs' `agentCustomInstructions` / `agent_custom_instructions` support (Python and Node [#6910](https://github.com/mem0ai/mem0/pull/6910))
**Documentation:**
- **`search --filter`:** The `--filter` help text and `docs/platform/cli.mdx` now spell out the JSON shape (`{"AND": [...]}` / `{"OR": [...]}`) with a concrete example instead of just calling it "an advanced filter expression," and a matching example command was added to both the CLI help text and the docs page (Python and Node [#6907](https://github.com/mem0ai/mem0/pull/6907))
</Update>
<Update label="2026-08-04" description="Python v0.2.11 / Node v0.2.12">
**New Features:**
@@ -2008,6 +2060,45 @@ A full-featured command-line interface for Mem0, available in both Python and No
<Tabs>
<Tab title="Mem0 Plugin">
<Update label="2026-09-08" description="Shared agent plugin runtime">
**Changed:**
- Consolidated the coding-agent integrations into `integrations/agent-plugin-core/`: one Python runtime, one TypeScript utility library, and six canonical Python-plugin skill templates. Native adapters retain each host's event contracts and capabilities.
- Python plugins ship generated, self-contained `core/` and `skills/` directories. Builds validate portable schemas and skills, parse native JSON, and reject generated-file drift, missing files, stale generated files, and symlinks. TypeScript packages bundle the shared source into their distributable JavaScript and verify their entry points.
- Replaced the old `integrations/mem0-plugin/` layout with native host directories and one portable `integrations/mem0-agent-plugin/` package. Updated marketplace paths, installation guides, and integration-skill links. OpenCode now lives in `integrations/opencode-plugin/`.
- Native Python plugins expose one local, read-only `search_memories` MCP tool and six skills: search, remember, forget, status, pause, and resume. The shared search tool accepts optional `run_id` with every scope (`repo`, `dir`, and `mine`) to recall memories from a specific coding-agent session. Omitting it searches across sessions. This local tool is separate from the hosted Mem0 MCP server's tool set.
**Fixes:**
- Hooks, controls, MCP servers, and detached workers use the same host-specific data directory. Detached workers retain the host identity and telemetry source; `--plugin-data-dir` reaches the shared resolver.
- Session-end workers flush the conversation already captured by hooks. Repeated and concurrent response hooks no longer duplicate an answer, while identical answers after separate prompts are preserved.
- Shared prompts and responses are redacted without the previous 6,000-character cutoff. Python extraction splits oversized messages without dropping text to enforce each request's input budget. Flush event selection and claims share one write transaction, delayed handoffs are replaced atomically, and permanent HTTP polling errors fail promptly.
- Extraction instructions refer to the current coding agent. Python redaction covers JSON-shaped credentials; both telemetry runtimes recursively remove sensitive keys, including keys inside nested lists.
- New Git repository writes use a hash of the remote identity in `agent_id`. Search and explicit shared-memory deletion include both current and legacy repository IDs within the repository's `app_id`. Existing memories are not rewritten. Legacy IDs retain their original ambiguity for matching owner/repository names on different Git hosts.
**Packaging:**
- Claude Code, Cursor, Codex, Kimi, Antigravity, and the portable Python bundle are versioned at `0.3.1`. OpenCode, Pi Agent, and DeepSeek Harness are `0.3.0`; OpenClaw is `1.1.0`. Each host's changes and upgrade considerations are listed in its tab.
- Python and TypeScript CI run their respective runtime suites. Package checks build the installable artifacts, check generated-file consistency, and reject TypeScript output that still imports monorepo source.
[#7203](https://github.com/mem0ai/mem0/pull/7203)
</Update>
<Update label="2026-08-24" description="mem0-plugin v0.2.15">
**Fixes:**
- **Search:** A failed search request now prints `[mem0] search request failed: <error>` to stderr before returning no results. `search_memories()` swallowed every exception and returned `[]`, so an expired API key, a network failure, or a 500 from the backend was indistinguishable from a genuine "nothing stored yet" and the agent carried on with no context and no warning. Shared by Claude Code, Cursor, Codex, Antigravity, and Kimi ([#6898](https://github.com/mem0ai/mem0/pull/6898))
- **Cursor:** `mcpServers` in `.cursor-plugin/plugin.json` now points at `./.cursor-mcp.json` instead of `.cursor-mcp.json`. The un-prefixed path resolved inconsistently depending on Cursor's working directory when it loaded the plugin ([#6948](https://github.com/mem0ai/mem0/pull/6948))
- **Codex:** `install_codex_hooks.py` now prints all six registered events (`PreToolUse, SessionStart, UserPromptSubmit, PostToolUse, Stop, PreCompact`) after installing, instead of a stale four-event list left over from an earlier version of the installer. The README's hook table is corrected to match, documenting the three `PreToolUse` handlers and two `PostToolUse` handlers that were previously undocumented ([#6948](https://github.com/mem0ai/mem0/pull/6948))
**Documentation:**
- New [Claude.ai](/integrations/claude-ai) integration page ([#6948](https://github.com/mem0ai/mem0/pull/6948))
<Note>
The Claude Code, Cursor, and Codex per-editor manifests (`.claude-plugin/plugin.json`, `.cursor-plugin/plugin.json`, `.codex-plugin/plugin.json`) had drifted to `0.2.13` while the `.claude-plugin/marketplace.json` and `.cursor-plugin/marketplace.json` listings had already moved to `0.2.14`, so installs were pinned one release behind what the marketplace advertised. This release realigns every manifest and marketplace listing to `0.2.15`.
</Note>
</Update>
<Update label="2026-08-04" description="mem0-plugin v0.2.14">
**Fixes:**
@@ -2278,8 +2369,124 @@ Initial release of the Mem0 plugin for Claude Code and Cursor, followed by Codex
</Tab>
<Tab title="Claude Code">
<Update label="Unreleased" description="Sidekick availability">
Sidekick is now available only in Claude Code, with Sonnet, worktree isolation, and parent memories.
</Update>
<Update label="2026-09-08" description="Claude Code plugin v0.3.1">
**Changed:**
- Extracted hook orchestration and memory behavior into the shared Python core; Claude transcript parsing remains in its native adapter. The installed package contains the generated runtime rather than importing files outside its plugin directory.
- Preserves the public `mem0` name, hook declarations, MCP launch configuration, user configuration, and `mem0:sidekick` worktree behavior. The manifest and marketplace version advance from `0.3.0` to `0.3.1` so installations can identify the update.
**Fixes:**
- Session-end extraction no longer appends a final answer already captured from the transcript while an earlier extraction was running.
- Existing repository memories remain searchable after the shared-ID change; explicit shared-memory deletion also covers the legacy ID. Background workers and control skills consistently use Claude's data directory.
- Receives the shared JSON-secret redaction and nested telemetry filtering fixes. The local search tool exposes query, result count, category, scope, and optional `run_id` for session-specific recall across all scopes.
[#7203](https://github.com/mem0ai/mem0/pull/7203)
</Update>
</Tab>
<Tab title="Cursor">
<Update label="Unreleased" description="Sidekick availability">
Removes Sidekick and its start/stop hooks. Memory capture, search, and six skills remain available.
</Update>
<Update label="2026-09-08" description="Cursor plugin v0.3.1">
**Changed:**
- Moves from the legacy shared editor-plugin directory to a native `integrations/cursor-plugin/` package with generated Python core and skills, Cursor variables, local MCP configuration, and a native Sidekick.
- Translates Cursor conversation/workspace fields, response and summary fields, tool outcomes, and subagent lifecycle events into the shared runtime. The Sidekick searches memory itself because Cursor's subagent-start response cannot inject parent context.
**Fixes:**
- Adapter errors are logged and exit successfully so a memory failure does not terminate the host hook. Sidekick telemetry reports zero parent-injected context because this host uses self-search.
- Repeated response events and Stop/session-end capture do not duplicate the same answer.
- Receives shared data-directory handling, background-worker identity, redaction, and legacy-memory retrieval fixes.
[#7203](https://github.com/mem0ai/mem0/pull/7203)
</Update>
</Tab>
<Tab title="Codex">
<Update label="Unreleased" description="Sidekick availability">
Renames shared tracking to use subagent terminology. Native subagent memory support remains available.
</Update>
<Update label="2026-09-08" description="Codex plugin v0.3.1">
**Changed:**
- Moves from the legacy shared editor-plugin directory to a native `integrations/codex-plugin/` package, with generated Python core and six skills, a local search MCP server, and native lifecycle hooks.
- Resolves MCP repository searches from Codex workspace metadata when supplied. Control skills, hooks, and workers use the same plugin data directory.
- Native subagent start/stop hooks supply parent-retrieved memory context and record completions for every native subagent. Named custom agents remain project/user configuration; the plugin does not distribute a named Codex Sidekick.
**Fixes:**
- Receives shared credential redaction, legacy-memory retrieval, background-worker identity, and duplicate-response fixes. Codex tool outcomes use available structured failure indicators; missing outcome information is recorded as unknown.
[#7203](https://github.com/mem0ai/mem0/pull/7203)
</Update>
</Tab>
<Tab title="Agent Plugins v1">
<Update label="Unreleased" description="Sidekick availability">
Sidekick is available only in Claude Code, not in the portable package.
</Update>
<Update label="2026-09-08" description="Portable Mem0 plugin v0.3.1">
**Added:**
- One portable package at `integrations/mem0-agent-plugin/`, using the Agent Plugins 1.0.0 root `plugin.json`, `mcp.json`, and fixed `skills/` locations.
- Ships a local, read-only `search_memories` server and the six shared memory skills. Uses `PLUGIN_ROOT` for bundled files and `PLUGIN_DATA` for persistent plugin state; all package files remain inside the installable directory.
**Packaging:**
- Generated from the shared Python runtime and skill templates. Builds validate the manifest, MCP configuration, skills, and generated-file consistency.
- Host lifecycle hooks and native Sidekick declarations remain in the native plugin packages; the portable package does not provide automatic lifecycle capture or host-specific subagent isolation. Its bundled remember skill cannot persist a new memory on its own because the portable package has no capture hooks or write tool.
[#7203](https://github.com/mem0ai/mem0/pull/7203)
</Update>
</Tab>
<Tab title="OpenCode">
<Update label="2026-09-08" description="OpenCode plugin v0.3.0">
**Changed:**
- Moved the source from `integrations/mem0-plugin/.opencode-plugin/` to `integrations/opencode-plugin/`, retaining the `@mem0/opencode-plugin` package name and native OpenCode hooks.
- Reuses shared conversation preparation, redaction, scoping, and telemetry. Builds a self-contained Bun/ESM `dist/index.js` and publishes its TypeScript entry declaration.
- Global memory tool scope requires the user to enable it in plugin settings first; empty and wildcard identities are rejected.
- Retains the seven commands for context loading, search, remember, forget, scope, status, and tour; bundled skills continue loading through OpenCode's native configuration.
**Removed:**
- Removed auto-Dream consolidation, its gates and state handling, and the Dream and pin skills/commands. Existing configurations and workflows that use these features must be updated.
**Builds:**
- Updated build and publish paths for the relocated source directory; the existing release tag prefix and publishing workflow filename are unchanged.
[#7203](https://github.com/mem0ai/mem0/pull/7203)
</Update>
<Update label="2026-07-22" description="OpenCode plugin v0.2.2">
**Fixes:**
@@ -2360,6 +2567,35 @@ Initial release of the Mem0 plugin for Claude Code and Cursor, followed by Codex
<Tab title="Antigravity">
<Update label="Unreleased" description="Sidekick availability">
Removes Sidekick. Memory capture, search, and six skills remain available.
</Update>
<Update label="2026-09-08" description="Antigravity plugin v0.3.1">
**Changed:**
- Ships a native package with generated Python core and six skills, a local search MCP server, a Sidekick declaration, and an adapter for `PreInvocation`, `PostToolUse`, and `Stop`.
- Normalizes conversation IDs, transcript paths, workspace paths, tool calls, and errors. The adapter captures all completed user/assistant transcript messages incrementally, including later-turn intent, without replaying earlier messages.
- Sidekick searches Mem0 itself; this plugin does not provide Claude Code's worktree isolation.
**Fixes and host limitations:**
- Accepts `MEM0_CWD` as an explicit workspace fallback when the host omits `workspacePaths`. Skips capture when neither is available, rather than writing under an unrelated directory.
- Documents global MCP registration with `agy mcp add` when the host does not register the plugin-scoped server. Recall on the initial invocation depends on the host providing a prompt or readable transcript.
- Receives the shared data-directory, redaction, legacy-memory retrieval, and duplicate-response fixes.
[#7203](https://github.com/mem0ai/mem0/pull/7203)
</Update>
<Update label="2026-08-24" description="Antigravity plugin v0.1.7">
**Fixes:**
- **Hooks:** The `mem0-ensure-deps` and `mem0-session-start` hook commands in `hooks.json` no longer redirect stderr to `/dev/null`. Both commands still end in `|| true` so a failure can't block startup, but a broken dependency install or session bootstrap now shows up in the Antigravity hook log instead of failing invisibly ([#6948](https://github.com/mem0ai/mem0/pull/6948))
</Update>
<Update label="2026-08-04" description="Antigravity plugin v0.1.6">
**Fixes:**
@@ -2427,8 +2663,73 @@ Existing memories written by the previous versions are not rewritten. If your me
</Tab>
<Tab title="Kimi">
<Update label="Unreleased" description="Sidekick availability">
Removes Sidekick and its start/stop hooks. Memory capture, recall, and six skills remain available.
</Update>
<Update label="2026-09-08" description="Kimi Code plugin v0.3.1">
**Changed:**
- Ships a self-contained native package with six skills, nine lifecycle hooks, a local search MCP server, and a native Sidekick declaration. Added a dedicated installation and troubleshooting guide.
- Translates Kimi's session, prompt, tool, compaction, shutdown, and subagent events into the shared Python runtime. Sidekicks receive parent memory context through Kimi's native lifecycle.
**Fixes:**
- Recovers completed assistant output from Kimi's indexed v2 wire transcript when Stop events omit the response text. Repeated Sidekick invocations receive distinct run identifiers. Stops without a host ID are left uncorrelated when multiple matching runs are active, preserving their responses without assigning them to the wrong run.
- Keeps controls, hooks, MCP, and detached workers on the same Kimi data directory and preserves host identity in background workers.
- Receives the shared redaction, legacy-memory retrieval, and duplicate-response fixes.
**Host compatibility:**
- Documents `CHOKIDAR_USEPOLLING=1` for the observed macOS watcher issue. Filesystem isolation remains Kimi's responsibility.
[#7203](https://github.com/mem0ai/mem0/pull/7203)
</Update>
<Update label="2026-08-24" description="kimi-plugin v0.1.0">
**Initial release** of the Mem0 plugin for Kimi Code, sharing its scripts, skills, and marketplace listing with the Claude Code / Cursor / Codex / Antigravity plugin family ([#6919](https://github.com/mem0ai/mem0/pull/6919))
**New Features:**
- **MCP server:** Registers the hosted Mem0 MCP server at `https://mcp.mem0.ai/mcp/`, authenticated via the `MEM0_API_KEY` env var as a bearer token.
- **Lifecycle hooks:** Wires SessionStart (loads context through the `context-loader` skill), UserPromptSubmit, three PreToolUse hooks (blocks direct `Write`/`Edit`/`MultiEdit` to memory files, enforces metadata defaults on Mem0 MCP tool calls, and injects context on file reads), two PostToolUse hooks (post-tool tracking and Bash-output scanning), Stop, and PreCompact.
- **Hook adapter:** `kimi_hook_shim.sh` normalizes Kimi Code's hook contract to what the shared hook scripts expect: it resolves the real project directory from the payload's `cwd` (Kimi forces the plugin root as the working directory), converts the array-shaped `prompt` field and the `tool_output` / `tool_input.path` field names to the Claude-style shapes the scripts already handle, and translates the plugin-scoped MCP tool name prefix.
- **Shared policy skill:** Bundles the `/mem0:policy` skill for managing the `## Instructions` and `## Agent Instructions` sections of `mem0.md`.
</Update>
</Tab>
<Tab title="OpenClaw">
<Update label="2026-09-08" description="openclaw-mem0 v1.1.0">
**Changed:**
- Reuses shared conversation preparation, redaction, and telemetry while retaining OpenClaw's native memory backend, tools, CLI, and Platform/OSS modes.
- Continues to publish a self-contained ESM package under `@mem0/openclaw-mem0`; the plugin manifest and package version now agree.
**Removed:**
- Removed Dream consolidation: automatic scheduling and locking, `openclaw mem0 dream`, Dream configuration, the memory-dream skill, and Dream-state public artifacts. Triage, recall, and memory/entity artifacts remain available. Update configurations or integrations that use the removed Dream surface.
**Fixes:**
- `openclaw mem0 status` handles an unconfigured installation without crashing and directs users to setup.
- Removed OpenClaw's separate 2,000-character extraction cutoff. Selected user and assistant messages retain their full redacted text; recent-message selection, earlier summary selection, and noise filtering still apply.
- Telemetry removes sensitive properties recursively and uses the shared failure-safe delivery implementation.
[#7203](https://github.com/mem0ai/mem0/pull/7203)
</Update>
<Update label="2026-08-24" description="openclaw-mem0 v1.0.16">
**Security:**
- **Dependencies:** Tightened the `undici` pnpm override from `<6.27.0 → >=6.27.0 <8.0.0` to `<7.29.0 → >=7.29.0 <8.0.0`, closing a newer CVE range the previous floor didn't cover, as part of a wider dependency patch sweep across the pnpm workspaces ([#6847](https://github.com/mem0ai/mem0/pull/6847))
</Update>
<Update label="2026-08-01" description="openclaw-mem0 v1.0.15">
**Improvements:**
@@ -2692,6 +2993,31 @@ Existing memories written by the previous versions are not rewritten. If your me
<Tab title="Pi Agent">
<Update label="2026-09-08" description="Pi Agent plugin v0.3.0">
**Changed:**
- Reuses shared conversation preparation, memory formatting, project/session/global scope utilities, and telemetry while preserving Pi's native extension API and `@mem0/pi-agent-plugin` package name.
- Pi loads the built `dist/entry.js` extension instead of executing source TypeScript from an installed package. Builds also publish the library entry point and declarations.
**Removed:**
- Removed Dream consolidation and pin commands, skills, configuration, types, and exports. The remaining commands are remember, search, forget, tour, scope, and status. Update integrations that import removed APIs or invoke removed commands.
**Fixes:**
- Global memory tool scope requires the user to select `/mem0-scope global` or configure a global default first. Empty and wildcard identities are rejected.
- Memory update and delete accept the `mem0:<uuid>` and `[mem0:<uuid>]` citations displayed in tool results, as well as raw IDs.
- Shared capture preparation filters conversation roles and redacts content; telemetry removes sensitive keys inside nested structures.
[#7203](https://github.com/mem0ai/mem0/pull/7203)
</Update>
<Update label="2026-08-24" description="Pi Agent plugin v0.1.5">
**Security:**
- **Dependencies:** Tightened the `undici` pnpm override from `<6.27.0 → >=6.27.0 <8.0.0` / `>=8.0.0 <8.5.0 → >=8.5.0` to `<7.29.0 → >=7.29.0 <8.0.0` / `>=8.0.0 <8.9.0 → >=8.9.0 <9.0.0`, closing a newer CVE range the previous floors didn't cover, as part of a wider dependency patch sweep across the pnpm workspaces ([#6847](https://github.com/mem0ai/mem0/pull/6847))
</Update>
<Update label="2026-08-01" description="Pi Agent plugin v0.1.4">
**Security:**
@@ -2749,8 +3075,88 @@ Existing memories written by the previous versions are not rewritten. If your me
</Tab>
<Tab title="Strands">
<Update label="2026-08-25" description="mem0-strands v0.1.1">
**New Features:**
- **Usage telemetry:** Anonymous usage events (`strands.store.init`, `strands.store.search`, `strands.store.add`, `strands.store.add_messages`) ride the Mem0 SDK's existing PostHog client, unsampled, with no new dependency. Events carry only counts, durations, booleans, and coarse failure kinds: never queries, memory text, message content, entity ids, metadata, or API keys. Opt out with `MEM0_TELEMETRY=false` ([#7110](https://github.com/mem0ai/mem0/pull/7110))
</Update>
<Update label="2026-08-24" description="mem0-strands v0.1.0">
**Initial release** of [`mem0-strands`](https://pypi.org/project/mem0-strands/), a native `MemoryStore` that plugs Mem0 into the [Strands Agents](https://strandsagents.com/) `MemoryManager` ([#7021](https://github.com/mem0ai/mem0/pull/7021))
**New Features:**
- **Automatic recall and injection:** `Mem0MemoryStore.search()` runs every turn through the `MemoryManager`, so relevant memories are searched and prepended to the prompt with no explicit tool call required.
- **Server-side extraction:** `add_messages()` renders raw conversation turns to text and hands them to Mem0's own extraction pipeline (`infer=True`), so enabling extraction skips an extra client-side model call to distill facts first.
- **Verbatim writes:** `add()` stores a single fact exactly as given (`infer=False`), the sink used by the `add_memory` tool or a client-side extractor.
- **Entity scoping:** Accepts `user_id`, `agent_id`, `run_id`, and `app_id`; at least one is required, and mixing the platform-only `app_id` with a self-hosted `config` raises at construction instead of failing on the first write.
- **Hosted or self-hosted:** Defaults to the hosted Mem0 Platform via `api_key` (or `$MEM0_API_KEY`), or pass a `config` dict for a self-hosted Mem0 OSS backend.
- **Non-blocking construction:** The underlying Mem0 client is built lazily on first use inside `asyncio.to_thread`, so API-key validation and OSS embedder/vector-store setup never block the event loop.
<Note>
`Mem0MemoryStore` is the automatic-recall store for the `MemoryManager`. For a model-called tool instead, use the [`mem0_memory`](https://github.com/strands-agents/tools) tool from `strands-agents-tools`; both share the same Mem0 backend and namespace. See [Strands Agents](/integrations/strands) for setup.
</Note>
</Update>
</Tab>
<Tab title="DeepSeek Harness">
<Update label="2026-09-08" description="deepseek-plugin v0.3.0">
**Added:**
- Automatic recall during `system-prompt/assemble`, using the latest human prompt and avoiding repeated context injection within a session.
- Automatic capture from the durable `session/event` stream after a completed turn. Interrupted or incomplete turns are not sent through this automatic capture path. `autoRecall` and `autoCapture` default to `true` and can be disabled.
**Changed:**
- Reuses shared lifecycle, redaction, identity, and telemetry utilities while retaining the explicit `search_memory` and `add_memory` tools and their per-call agent/session scope. Cross-user `userId` overrides now require operator opt-in with `allowUserOverride: true`.
- Publishes a self-contained ESM artifact under `@mem0/deepseek-plugin`; native Harness services and the Mem0 SDK remain external dependencies. Plugin cleanup remains tied to the native Cordis lifecycle.
**Host compatibility:**
- Supports the declared Harness runtime dependencies and documents the macOS watcher workaround. The plugin does not bundle a named Sidekick or provide child filesystem isolation.
[#7203](https://github.com/mem0ai/mem0/pull/7203)
</Update>
<Update label="2026-08-25" description="deepseek-plugin v0.1.1">
**New Features:**
- **Usage telemetry:** Anonymous usage events (`deepseek.plugin.mounted`, `deepseek.tool.search_memory`, `deepseek.tool.add_memory`) are batched to PostHog over native fetch and flushed in the background. Events carry only tool names, durations, counts, and coarse failure kinds: never queries, memory text, filters, or API keys. Opt out with `MEM0_TELEMETRY=false` ([#7110](https://github.com/mem0ai/mem0/pull/7110))
</Update>
<Update label="2026-08-24" description="deepseek-plugin v0.1.0">
**Initial release** of [`deepseek-plugin`](https://www.npmjs.com/package/@mem0/deepseek-plugin), a native DeepSeek Harness (Cordis) plugin that registers Mem0 as two agent-callable tools ([#7027](https://github.com/mem0ai/mem0/pull/7027))
**New Features:**
- **`search_memory`:** Recalls facts relevant to a query, with an optional `limit` (default 10) and per-call `userId` / `agentId` / `runId` scope overrides.
- **`add_memory`:** Stores a fact for future sessions, tagged `source: "DEEPSEEK_HARNESS"` for backend attribution; extraction runs asynchronously server-side, so a stored fact may take a moment to become searchable.
- **Cordis lifecycle:** `apply(ctx, config)` declares `inject = ['tools']`, so the plugin waits for the harness tool registry to exist, and both tools are registered via `ctx.tools.register()` so they auto-unregister when the plugin unmounts.
- **Config:** `userId` is required; `apiKey` defaults to `$MEM0_API_KEY`; `host` optionally points at a dedicated Mem0 Platform base URL (not a switch to self-hosted Mem0 OSS).
<Note>
Developer preview: auto-capture and auto-recall (memory injected into context automatically, without an explicit tool call) are planned but not yet built. The backend's `KNOWN_EVENT_SOURCES` allowlist also needs `"DEEPSEEK_HARNESS"` added before usage surfaces by name in telemetry rather than bucketing into "OTHERS". See [DeepSeek Harness](/integrations/deepseek-plugin) for setup.
</Note>
</Update>
</Tab>
<Tab title="Vercel AI SDK">
<Update label="2026-08-24" description="Vercel AI SDK v3.0.2">
**Security:**
- **Dependencies:** Tightened the `js-yaml` pnpm overrides from `<3.15.0 → >=3.15.0 <4.0.0` / `>=4.0.0 <4.3.0 → >=4.3.0 <5.0.0` to `<3.15.1 → >=3.15.1 <4.0.0` / `>=4.0.0 <4.3.1 → >=4.3.1 <5.0.0`, closing a newer CVE range the previous floors didn't cover, as part of a wider dependency patch sweep across the pnpm workspaces ([#7032](https://github.com/mem0ai/mem0/pull/7032))
</Update>
<Update label="2026-08-01" description="Vercel AI SDK v3.0.1">
**Security:**
@@ -2843,6 +3249,13 @@ Existing memories written by the previous versions are not rewritten. If your me
<Tab title="n8n">
<Update label="2026-08-24" description="n8n-nodes-mem0 v0.1.4">
**Security:**
- **Dependencies:** Add `js-yaml` pnpm overrides (`<3.15.1 → >=3.15.1 <4.0.0`, `>=4.0.0 <4.3.1 → >=4.3.1 <5.0.0`) to close a HIGH/CRITICAL severity advisory, as part of a wider dependency patch sweep across the pnpm workspaces ([#7032](https://github.com/mem0ai/mem0/pull/7032))
</Update>
<Update label="2026-08-05" description="n8n-nodes-mem0 v0.1.3">
**Changes:**
@@ -2887,6 +3300,21 @@ Existing memories written by the previous versions are not rewritten. If your me
<Tab title="Zapier">
<Update label="2026-08-24" description="Zapier app v0.1.2">
**Changes:**
- **Connection label:** The saved connection now shows the account's email (`{{user_email}}`, read from the `/v1/ping/` test response) in the Zap editor instead of a static "Mem0" label, so a user with more than one Mem0 connection can tell them apart ([#6985](https://github.com/mem0ai/mem0/pull/6985))
- **Action and search copy:** Reworded labels and descriptions to address Zapier's publishing review: **Get Memories** is now **Find Memories by User**, **Search Memories** is now **Find Memories**, and every description now reads as a third-person statement of what the step does ([#6985](https://github.com/mem0ai/mem0/pull/6985))
- **Attribution:** Add Memory now tags writes with `source: "ZAPIER"` in the request body, so usage is attributed to this integration server-side ([#6985](https://github.com/mem0ai/mem0/pull/6985))
**Removed:**
- **Client-side telemetry:** Deleted the embedded PostHog telemetry client (`telemetry.ts`) and its call sites in Add Memory, Get Memories, and Search Memories. Usage attribution now happens server-side via the `source: "ZAPIER"` tag above instead of a separate fire-and-forget analytics call from inside the published app ([#6985](https://github.com/mem0ai/mem0/pull/6985))
**Security:**
- **Dependencies:** Tightened the `undici` pnpm override to `<7.29.0 → >=7.29.0 <8.0.0` / `>=8.0.0 <8.9.0 → >=8.9.0 <9.0.0` and added a `brace-expansion` override ([#6847](https://github.com/mem0ai/mem0/pull/6847)), then added a `js-yaml` override (`<3.15.1 → >=3.15.1 <4.0.0`, `>=4.0.0 <4.3.1 → >=4.3.1 <5.0.0`) closing a further HIGH/CRITICAL severity advisory ([#7032](https://github.com/mem0ai/mem0/pull/7032))
</Update>
<Update label="2026-08-04" description="Zapier app v0.1.1">
**Bug Fixes:**
+2 -1
View File
@@ -1,5 +1,6 @@
---
title: Configurations
title: "Embedder Configuration Reference"
sidebarTitle: "Configurations"
description: "Reference for embedder configuration options in Mem0, including provider selection and model settings."
---
@@ -1,5 +1,6 @@
---
title: AWS Bedrock
title: "AWS Bedrock as Embedding Provider"
sidebarTitle: "AWS Bedrock"
description: "Configure AWS Bedrock as an embedding provider in Mem0 with IAM credentials and boto3 authentication."
---
@@ -1,5 +1,6 @@
---
title: Azure OpenAI
title: "Azure OpenAI as Embedding Provider"
sidebarTitle: "Azure OpenAI"
description: "Configure Azure OpenAI as an embedding provider in Mem0 with API key, deployment, and endpoint settings."
---
@@ -1,5 +1,6 @@
---
title: Google AI
title: "Google AI as Embedding Provider"
sidebarTitle: "Google AI"
description: "Configure Google AI as an embedding provider in Mem0 using Gemini models and the GOOGLE_API_KEY variable."
---
@@ -99,6 +99,7 @@ Here are the parameters available for configuring the Hugging Face embedder:
| `embedding_dims` | Dimensions of the embedding model | `selected_model_dimensions` |
| `model_kwargs` | Additional arguments for the model | `None` |
| `huggingface_base_url` | URL to connect to Text Embeddings Inference (TEI) API | `None` |
| `api_key` | API key for the endpoint; falls back to the `HUGGINGFACE_API_KEY` env var. Only used on the `huggingface_base_url` path | `"hf"` |
</Tab>
<Tab title="TypeScript">
| Parameter | Description | Default Value |
@@ -1,5 +1,6 @@
---
title: LangChain
title: "LangChain as Embedding Provider"
sidebarTitle: "LangChain"
description: "Use LangChain as an embedding provider in Mem0 to access a wide range of models through a unified interface."
---
@@ -1,5 +1,6 @@
---
title: "LM Studio"
title: "LM Studio as Embedding Provider"
sidebarTitle: "LM Studio"
description: "Configure LM Studio as an embedding provider in Mem0 for local embedding generation with models like nomic-embed-text."
---
You can use embedding models from LM Studio to run Mem0 locally.
+2 -1
View File
@@ -1,5 +1,6 @@
---
title: "Ollama"
title: "Ollama as Embedding Provider"
sidebarTitle: "Ollama"
description: "Configure Ollama as an embedding provider in Mem0 to generate embeddings locally using open-source models."
---
You can use embedding models from Ollama to run Mem0 locally.
+2 -1
View File
@@ -1,5 +1,6 @@
---
title: OpenAI
title: "OpenAI as Embedding Provider"
sidebarTitle: "OpenAI"
description: "Configure OpenAI as an embedding provider in Mem0 using models like text-embedding-3-large for vector generation."
---
@@ -1,5 +1,6 @@
---
title: Together
title: "Together AI as Embedding Provider"
sidebarTitle: "Together"
description: "Configure Together AI as an embedding provider in Mem0 with support for 1024-dimensional embedding models."
---
+5 -4
View File
@@ -1,5 +1,6 @@
---
title: Overview
title: "Embedding Providers Overview"
sidebarTitle: Overview
description: "Overview of all supported embedding model providers in Mem0, including OpenAI, Azure, Ollama, and more."
---
@@ -15,15 +16,15 @@ See the list of supported embedders below.
<CardGroup cols={4}>
<Card title="OpenAI" icon="/images/provider-icons/openai.svg" href="/components/embedders/models/openai"></Card>
<Card title="Azure OpenAI" icon="/images/provider-icons/azure-color.svg" href="/components/embedders/models/azure_openai"></Card>
<Card title="Azure OpenAI" icon="/images/provider-icons/azure-color.svg" href="/components/embedders/models/azure-openai"></Card>
<Card title="Ollama" icon="/images/provider-icons/ollama.svg" href="/components/embedders/models/ollama"></Card>
<Card title="Hugging Face" icon="/images/provider-icons/huggingface.svg" href="/components/embedders/models/huggingface"></Card>
<Card title="Google AI" icon="/images/provider-icons/google-color.svg" href="/components/embedders/models/google_AI"></Card>
<Card title="Google AI" icon="/images/provider-icons/google-color.svg" href="/components/embedders/models/google-ai"></Card>
<Card title="Vertex AI" icon="/images/provider-icons/vertexai.svg" href="/components/embedders/models/vertexai"></Card>
<Card title="Together" icon="/images/provider-icons/together-color.svg" href="/components/embedders/models/together"></Card>
<Card title="LM Studio" icon="/images/provider-icons/lmstudio.svg" href="/components/embedders/models/lmstudio"></Card>
<Card title="Langchain" icon="/images/provider-icons/langchain-color.svg" href="/components/embedders/models/langchain"></Card>
<Card title="AWS Bedrock" icon="/images/provider-icons/bedrock-color.svg" href="/components/embedders/models/aws_bedrock"></Card>
<Card title="AWS Bedrock" icon="/images/provider-icons/bedrock-color.svg" href="/components/embedders/models/aws-bedrock"></Card>
<Card title="FastEmbed" icon="/images/provider-icons/qdrant.svg" href="/components/embedders/models/fastembed"></Card>
</CardGroup>
+2 -1
View File
@@ -1,5 +1,6 @@
---
title: Configurations
title: "LLM Configuration Reference"
sidebarTitle: "Configurations"
description: "Reference for LLM configuration options in Mem0 for Python and TypeScript, including value precedence rules."
---
@@ -1,5 +1,6 @@
---
title: AWS Bedrock
title: "AWS Bedrock as LLM Provider"
sidebarTitle: "AWS Bedrock"
description: "Configure AWS Bedrock as an LLM provider in Mem0 with IAM authentication and Claude model support."
---
@@ -1,5 +1,6 @@
---
title: Azure OpenAI
title: "Azure OpenAI as LLM Provider"
sidebarTitle: "Azure OpenAI"
description: "Configure Azure OpenAI as an LLM provider in Mem0 with Azure Identity authentication and deployment settings."
---
+1 -1
View File
@@ -3,7 +3,7 @@ title: DeepSeek
description: "Configure DeepSeek as an LLM provider in Mem0 with API key setup and optional custom endpoint configuration."
---
To use DeepSeek LLM models, you have to set the `DEEPSEEK_API_KEY` environment variable. You can also optionally set `DEEPSEEK_API_BASE` if you need to use a different API endpoint (defaults to "https://api.deepseek.com").
To use DeepSeek LLM models, you have to set the `DEEPSEEK_API_KEY` environment variable. You can also optionally set `DEEPSEEK_API_BASE` if you need to use a different API endpoint (defaults to `https://api.deepseek.com`).
## Usage
@@ -1,5 +1,6 @@
---
title: Google AI
title: "Google AI as LLM Provider"
sidebarTitle: "Google AI"
description: "Configure Google Gemini as an LLM provider in Mem0 using the google.genai SDK and GOOGLE_API_KEY variable."
---
+2 -1
View File
@@ -1,5 +1,6 @@
---
title: LangChain
title: "LangChain as LLM Provider"
sidebarTitle: "LangChain"
description: "Use LangChain as an LLM provider in Mem0 to integrate with various chat models through a unified interface."
---
+3 -2
View File
@@ -1,5 +1,6 @@
---
title: LM Studio
title: "LM Studio as LLM Provider"
sidebarTitle: "LM Studio"
description: "Configure LM Studio as an LLM provider in Mem0 for running local language models via an OpenAI-compatible API."
---
@@ -76,7 +77,7 @@ m.add(messages, user_id="alice123", metadata={"category": "movies"})
To use LM Studio, you need to:
1. Download and install [LM Studio](https://lmstudio.ai/)
2. Start a local server from the "Server" tab
3. Set the appropriate `lmstudio_base_url` in your configuration (default is usually http://localhost:1234/v1)
3. Set the appropriate `lmstudio_base_url` in your configuration (default is usually `http://localhost:1234/v1`)
</Note>
## Config
+1 -1
View File
@@ -3,7 +3,7 @@ title: MiniMax
description: "Configure MiniMax as an LLM provider in Mem0 with API key setup and optional custom endpoint configuration."
---
To use MiniMax LLM models, you have to set the `MINIMAX_API_KEY` environment variable. You can also optionally set `MINIMAX_API_BASE` if you need to use a different API endpoint (defaults to "https://api.minimax.io/v1").
To use MiniMax LLM models, you have to set the `MINIMAX_API_KEY` environment variable. You can also optionally set `MINIMAX_API_BASE` if you need to use a different API endpoint (defaults to `https://api.minimax.io/v1`).
## Usage
+2 -1
View File
@@ -1,5 +1,6 @@
---
title: Ollama
title: "Ollama as LLM Provider"
sidebarTitle: "Ollama"
description: "Configure Ollama as an LLM provider in Mem0 for running local language models with tool-calling support."
---
+2 -1
View File
@@ -1,5 +1,6 @@
---
title: OpenAI
title: "OpenAI as LLM Provider"
sidebarTitle: "OpenAI"
description: "Configure OpenAI as an LLM provider in Mem0 with support for GPT models and Openrouter compatibility."
---
+2 -1
View File
@@ -1,5 +1,6 @@
---
title: Together
title: "Together AI as LLM Provider"
sidebarTitle: "Together"
description: "Configure Together AI as an LLM provider in Mem0 with API key setup and optional custom endpoint configuration."
---
+2 -1
View File
@@ -1,5 +1,6 @@
---
title: xAI
title: "xAI Grok as LLM Provider"
sidebarTitle: "xAI"
description: "Configure xAI Grok models as an LLM provider in Mem0 with API key setup and usage examples."
---
+6 -5
View File
@@ -1,5 +1,6 @@
---
title: Overview
title: "LLM Providers Overview"
sidebarTitle: Overview
description: "Overview of all supported LLM providers in Mem0, including OpenAI, Anthropic, Groq, Ollama, and more."
---
@@ -22,14 +23,14 @@ See the list of supported LLMs below.
<CardGroup cols={4}>
<Card title="OpenAI" icon="/images/provider-icons/openai.svg" href="/components/llms/models/openai" />
<Card title="Ollama" icon="/images/provider-icons/ollama.svg" href="/components/llms/models/ollama" />
<Card title="Azure OpenAI" icon="/images/provider-icons/azure-color.svg" href="/components/llms/models/azure_openai" />
<Card title="Azure OpenAI" icon="/images/provider-icons/azure-color.svg" href="/components/llms/models/azure-openai" />
<Card title="Anthropic" icon="/images/provider-icons/anthropic.svg" href="/components/llms/models/anthropic" />
<Card title="Together" icon="/images/provider-icons/together-color.svg" href="/components/llms/models/together" />
<Card title="Groq" icon="/images/provider-icons/groq.svg" href="/components/llms/models/groq" />
<Card title="Litellm" icon="shuffle" href="/components/llms/models/litellm" />
<Card title="Mistral AI" icon="/images/provider-icons/mistral-color.svg" href="/components/llms/models/mistral_AI" />
<Card title="Google AI" icon="/images/provider-icons/google-color.svg" href="/components/llms/models/google_AI" />
<Card title="AWS bedrock" icon="/images/provider-icons/bedrock-color.svg" href="/components/llms/models/aws_bedrock" />
<Card title="Mistral AI" icon="/images/provider-icons/mistral-color.svg" href="/components/llms/models/mistral-ai" />
<Card title="Google AI" icon="/images/provider-icons/google-color.svg" href="/components/llms/models/google-ai" />
<Card title="AWS bedrock" icon="/images/provider-icons/bedrock-color.svg" href="/components/llms/models/aws-bedrock" />
<Card title="DeepSeek" icon="/images/provider-icons/deepseek-color.svg" href="/components/llms/models/deepseek" />
<Card title="MiniMax" icon="/images/provider-icons/minimax-color.svg" href="/components/llms/models/minimax" />
<Card title="xAI" icon="/images/provider-icons/xai.svg" href="/components/llms/models/xAI" />
+7 -6
View File
@@ -1,5 +1,6 @@
---
title: Overview
title: "Reranker Providers Overview"
sidebarTitle: "Overview"
description: 'Pick the right reranker path to boost Mem0 search relevance.'
---
@@ -13,10 +14,10 @@ Reranking trades extra latency for better precision. Start once you have baselin
<CardGroup cols={3}>
<Card title="Cohere" icon="/images/provider-icons/cohere.svg" href="/components/rerankers/models/cohere" />
<Card title="Sentence Transformers" icon="vector-square" href="/components/rerankers/models/sentence_transformer" />
<Card title="Sentence Transformers" icon="vector-square" href="/components/rerankers/models/sentence-transformer" />
<Card title="Hugging Face" icon="/images/provider-icons/huggingface.svg" href="/components/rerankers/models/huggingface" />
<Card title="LLM Reranker" icon="wand-magic-sparkles" href="/components/rerankers/models/llm_reranker" />
<Card title="Zero Entropy" icon="/images/provider-icons/zeroentropy.svg" href="/components/rerankers/models/zero_entropy" />
<Card title="LLM Reranker" icon="wand-magic-sparkles" href="/components/rerankers/models/llm-reranker" />
<Card title="Zero Entropy" icon="/images/provider-icons/zeroentropy.svg" href="/components/rerankers/models/zero-entropy" />
</CardGroup>
<Note>
@@ -54,13 +55,13 @@ All five rerankers are available in both the Python and the [TypeScript](/open-s
title="Zero Entropy Guide"
description="Adopt the managed neural reranker for production workloads."
icon="sparkles"
href="/components/rerankers/models/zero_entropy"
href="/components/rerankers/models/zero-entropy"
/>
<Card
title="Sentence Transformers"
description="Keep reranking on-device with cross-encoder models."
icon="microchip"
href="/components/rerankers/models/sentence_transformer"
href="/components/rerankers/models/sentence-transformer"
/>
</CardGroup>
+2 -1
View File
@@ -1,5 +1,6 @@
---
title: Configurations
title: "Vector Store Configuration Reference"
sidebarTitle: "Configurations"
description: "Reference for vector database configuration options in Mem0, including provider selection and connection settings."
---
+2 -1
View File
@@ -1,5 +1,6 @@
---
title: LangChain
title: "LangChain as Vector Store Provider"
sidebarTitle: "LangChain"
description: "Use LangChain as a unified vector store provider in Mem0 to access multiple vector databases through one interface."
---
+15 -7
View File
@@ -14,7 +14,7 @@ description: "Use Oracle Database AI Vector Search as a vector store in Mem0 for
<CodeGroup>
```bash Python
pip install oracledb
pip install mem0ai
```
```bash TypeScript
@@ -141,11 +141,13 @@ const config = {
Here are the parameters available for configuring Oracle AI Vector Search:
Provide either `connection_params`/`connectionParams` or an existing connection or pool as `client`.
| Python | TypeScript | Description | Default Value |
| --- | --- | --- | --- |
| `connection_params` | `connectionParams` | Connection settings passed to the Oracle driver, such as `user`, `password` and `dsn` (`connectString` in TypeScript). See the [Python](https://python-oracledb.readthedocs.io/en/latest/user_guide/connection_handling.html) or [Node.js](https://node-oracledb.readthedocs.io/en/latest/user_guide/connection_handling.html) connection handling guide. | `None` |
| `connection_params` | `connectionParams` | Connection settings passed to the Oracle driver, such as `user`, `password` and `dsn` (`connectString` in TypeScript). Required unless `client` is provided. See the [Python](https://python-oracledb.readthedocs.io/en/latest/user_guide/connection_handling.html) or [Node.js](https://node-oracledb.readthedocs.io/en/latest/user_guide/connection_handling.html) connection handling guide. | `None` |
| `use_connection_pool` | `useConnectionPool` | Create a connection pool from the connection parameters instead of a single connection | `True` |
| `client` | `client` | An existing Oracle connection or pool to use instead of building one from the connection parameters | `None` |
| `client` | `client` | An existing Oracle connection or pool to use instead of building one from the connection parameters. Required unless connection parameters are provided. | `None` |
| `collection_name` | `collectionName` | Name of the Oracle table that stores vectors and payloads | `mem0` |
| `embedding_model_dims` | `embeddingModelDims` | Dimension of your embedding vectors, must be greater than 0 | `1536` |
| `distance_metric` | `distanceMetric` | Distance function used for indexing and search: `COSINE`, `EUCLIDEAN`, `EUCLIDEAN_SQUARED`, `DOT`, `HAMMING` or `MANHATTAN` | `COSINE` |
@@ -222,15 +224,21 @@ Multiple fields at the top level are combined with `AND`:
```python Python
m.search(
"movie recommendations",
user_id="alice",
filters={"category": {"in": ["movies", "books"]}, "rating": {"gte": 4}},
filters={
"user_id": "alice",
"category": {"in": ["movies", "books"]},
"rating": {"gte": 4},
},
)
```
```typescript TypeScript
await memory.search("movie recommendations", {
userId: "alice",
filters: { category: { in: ["movies", "books"] }, rating: { gte: 4 } },
filters: {
user_id: "alice",
category: { in: ["movies", "books"] },
rating: { gte: 4 },
},
});
```
</CodeGroup>
@@ -102,7 +102,7 @@ Here are the parameters available for configuring Upstash Vector:
| `url` | URL for the Upstash Vector index | `None` |
| `token` | Token for the Upstash Vector index | `None` |
| `client` | An `upstash_vector.Index` instance | `None` |
| `collection_name` | The default namespace used | `""` |
| `collection_name` | The default namespace used | `"mem0"` |
| `enable_embeddings` | Whether to use Upstash embeddings | `False` |
<Note>
+5 -4
View File
@@ -1,5 +1,6 @@
---
title: Overview
title: "Vector Store Providers Overview"
sidebarTitle: "Overview"
description: "Overview of all supported vector databases in Mem0, including Qdrant, Chroma, PGVector, Pinecone, Oracle, and more."
---
@@ -28,12 +29,12 @@ See the list of supported vector databases below.
<Card title="Elasticsearch" icon="/images/provider-icons/elasticsearch.svg" href="/components/vectordbs/dbs/elasticsearch"></Card>
<Card title="OpenSearch" icon="/images/provider-icons/opensearch.svg" href="/components/vectordbs/dbs/opensearch"></Card>
<Card title="Supabase" icon="/images/provider-icons/supabase.svg" href="/components/vectordbs/dbs/supabase"></Card>
<Card title="Vertex AI" icon="/images/provider-icons/vertexai.svg" href="/components/vectordbs/dbs/vertex_ai"></Card>
<Card title="Vertex AI" icon="/images/provider-icons/vertexai.svg" href="/components/vectordbs/dbs/vertex-ai"></Card>
<Card title="Weaviate" icon="circle-nodes" href="/components/vectordbs/dbs/weaviate"></Card>
<Card title="FAISS" icon="layer-group" href="/components/vectordbs/dbs/faiss"></Card>
<Card title="LangChain" icon="/images/provider-icons/langchain-color.svg" href="/components/vectordbs/dbs/langchain"></Card>
<Card title="Amazon S3 Vectors" icon="/images/provider-icons/aws-color.svg" href="/components/vectordbs/dbs/s3_vectors"></Card>
<Card title="Neptune Analytics" icon="/images/provider-icons/aws-color.svg" href="/components/vectordbs/dbs/neptune_analytics"></Card>
<Card title="Amazon S3 Vectors" icon="/images/provider-icons/aws-color.svg" href="/components/vectordbs/dbs/s3-vectors"></Card>
<Card title="Neptune Analytics" icon="/images/provider-icons/aws-color.svg" href="/components/vectordbs/dbs/neptune-analytics"></Card>
<Card title="Databricks" icon="/images/provider-icons/databricks.svg" href="/components/vectordbs/dbs/databricks"></Card>
<Card title="Turbopuffer" icon="/images/provider-icons/turbopuffer.svg" href="/components/vectordbs/dbs/turbopuffer"></Card>
</CardGroup>
+27
View File
@@ -79,6 +79,33 @@ For detailed guidance on pull requests, refer to [GitHub's documentation](https:
---
## Installing from Source
If you just want to run the latest, unreleased SDK code instead of the published `mem0ai` package, for example to try out a fix before it ships, or to depend on a fork, install directly from a local clone rather than setting up the full contributor environment below.
### Python SDK
```bash
git clone https://github.com/mem0ai/mem0.git
cd mem0
pip install -e .
```
This installs `mem0ai` in editable mode, so edits under `mem0/` take effect immediately without reinstalling. Add an extra if you need one, e.g. `pip install -e ".[vector-stores]"` (see `pyproject.toml` for the full list). If you are contributing to the SDK itself and need every optional dependency for the test suite, use `hatch` instead, see [Dependency Management](#dependency-management).
### TypeScript SDK
```bash
git clone https://github.com/mem0ai/mem0.git
cd mem0/mem0-ts
pnpm install
pnpm run build
```
This builds `mem0-ts/dist` (CJS + ESM). To use it from another local project, add it as a `file:` dependency pointing at `mem0-ts`, or run `pnpm link --global` inside `mem0-ts` and `pnpm link --global mem0ai` in the consuming project.
---
## Python SDK (`mem0/`)
### Dependency Management
+1 -1
View File
@@ -38,7 +38,7 @@ Navigate to the `docs/` directory (where `docs.json` is located) and start the d
mintlify dev
```
The documentation website will be available at: [http://localhost:3000](http://localhost:3000).
The documentation website will be available at: `http://localhost:3000`.
---
@@ -48,7 +48,7 @@ Before you begin, follow these steps to set up the demo application:
OPENAI_API_KEY=your_openai_api_key
MEM0_API_KEY=your_mem0_api_key
```
You can obtain your `MEM0_API_KEY` by signing up at <a href="https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=cookbook-companions-quickstart" rel="nofollow">Mem0 API Dashboard</a>.
You can obtain your `MEM0_API_KEY` by signing up at <a href="https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=cookbook-companions-quickstart">Mem0 API Dashboard</a>.
5. Start the development server:
```bash
@@ -1,519 +0,0 @@
---
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.
Mem0 lets you control your memory ingestion pipeline. In this cookbook, we'll demonstrate these controls using a medical assistant example - showing how to filter unwanted data, enforce data formats, and implement confidence-based storage.
---
## Overview
Without controls, everything gets stored - speculation, low-confidence data, and information that shouldn't persist. This uncontrolled ingestion leads to cluttered memory and retrieval failures.
Mem0 provides **three tools to control** what gets stored:
1. **Custom instructions** define what to remember and what to ignore.
2. **Confidence thresholds** ensure only verified facts persist.
3. **Memory updates** let you change information without creating duplicates.
In this tutorial, we will:
- Filter speculative statements with custom instructions
- Configure confidence thresholds for fact verification
- Update stored information without duplication
- Build a complete ingestion pipeline
---
## Setup
```python
from mem0 import MemoryClient
client = MemoryClient(api_key="your-api-key")
```
<Note>
Replace `your-api-key` with your actual Mem0 API key from the <a href="https://app.mem0.ai?utm_source=oss&utm_medium=cookbook-memory-ingestion" rel="nofollow">dashboard</a>. Without proper API authentication, memory operations will fail.
</Note>
---
## The Problem
Uncontrolled ingestion stores everything, including speculation:
```python
# Patient mentions speculation
messages = [{"role": "user", "content": "I think I might be allergic to penicillin"}]
client.add(messages, user_id="patient_123")
# Check what got stored
results = client.search("patient allergies", filters={"user_id": "patient_123"})
print(results['results'][0]['memory'])
```
**Output:**
```
Patient is allergic to penicillin
```
<Warning>
Without custom instructions, AI assistants treat speculation as confirmed facts. "I think I might be allergic" becomes "Patient is allergic": a dangerous transformation in sensitive domains like healthcare, legal, or financial services.
</Warning>
The speculation became a confirmed fact. Let's add controls.
---
## Custom Instructions
Custom instructions tell Mem0 what to store and what to ignore.
```python
instructions = """
Only store CONFIRMED medical facts.
Store:
- Confirmed diagnoses from doctors
- Known allergies with documented reactions
- Current medications being taken
Ignore:
- Speculation (words like "might", "maybe", "I think")
- Unverified symptoms
- Casual mentions without confirmation
"""
client.project.update(custom_instructions=instructions)
# Same speculative statement
messages = [{"role": "user", "content": "I think I might be allergic to penicillin"}]
client.add(messages, user_id="patient_123")
# Check what got stored
results = client.get_all(filters={"user_id": "patient_123"})
print(f"Memories stored: {len(results['results'])}")
```
**Output:**
```
Memories stored: 0
```
<Info>
**Expected output:** Zero memories stored. The speculative statement "I think I might be allergic" was filtered out before reaching storage. Custom instructions are actively blocking unreliable data.
</Info>
The speculation was filtered out.
---
## Designing Custom Instructions
When designing instructions, consider the trade-off between precision and recall:
**Too restrictive:** You'll miss important information (false negatives)
```python
# Too strict - filters out useful context
"""
Only store information if explicitly stated by a doctor with full name,
date, time, and medical license number.
"""
```
**Too permissive:** You'll store unreliable data (false positives)
```python
# Too loose - stores speculation as fact
"""
Store any health-related information mentioned.
"""
```
**Balanced approach:**
```python
# Clear categories with examples
"""
Store CONFIRMED facts:
- Diagnoses: "Dr. Smith diagnosed hypertension on March 15th"
- Allergies: "Patient had hives reaction to penicillin"
- Medications: "Taking Lisinopril 10mg daily"
Ignore SPECULATION:
- "I think I might have..."
- "Maybe it's..."
- "Could be related to..."
"""
```
<Tip>
Start with strict instructions (only store confirmed facts), then relax them based on your use case. It's easier to allow more data than to clean up polluted memory. Test with sample conversations before deploying to production.
</Tip>
Start with clear categories and iterate based on retrieval quality.
---
## Confidence Thresholds
Mem0 assigns confidence scores to extracted memories. Use these to filter low-quality data.
### Setting Thresholds
Setting the right confidence threshold depends on your application:
- **High-stakes domains** (medical, legal): Require 0.8+ confidence
- **General assistants**: 0.6+ confidence is often sufficient
- **Exploratory systems**: Lower thresholds (0.4+) capture more data
Test your pipeline with multiple input examples and threshold combinations to find what works for your use case.
```python
# Configure stricter instructions
client.project.update(
custom_instructions="""
Only extract memories with HIGH confidence.
Require specific details (dates, dosages, doctor names) for medical facts.
Skip vague or uncertain statements.
"""
)
# Test with uncertain statement
messages = [{"role": "user", "content": "The doctor mentioned something about my blood pressure"}]
result1 = client.add(messages, user_id="patient_123")
# Test with confirmed fact
messages = [{"role": "user", "content": "Dr. Smith diagnosed me with hypertension on March 15th"}]
result2 = client.add(messages, user_id="patient_123")
print("Vague statement stored:", len(result1['results']) > 0)
print("Confirmed fact stored:", len(result2['results']) > 0)
```
**Output:**
```
Vague statement stored: False
Confirmed fact stored: True
```
<Info icon="check">
**Expected behavior:** Low-confidence extractions are now filtered out automatically. Only verified facts with specific details (names, dates, dosages) persist in memory. The confidence threshold is working.
</Info>
The vague statement was filtered for low confidence. The confirmed fact with specific details was stored.
---
## Filtering Sensitive Information
Custom instructions can prevent storing personal identifiers:
```python
client.project.update(
custom_instructions="""
Medical memory rules:
STORE:
- Confirmed diagnoses
- Verified allergies
- Current medications
NEVER STORE:
- Social Security Numbers
- Insurance policy numbers
- Credit card information
- Full addresses
- Phone numbers
Replace identifiers with generic references if mentioned.
"""
)
# Test with PII
messages = [
{"role": "user", "content": "My SSN is 123-45-6789 and I'm allergic to penicillin"}
]
client.add(messages, user_id="patient_123")
# Check what was stored
results = client.get_all(filters={"user_id": "patient_123"})
for result in results['results']:
print(result['memory'])
```
**Output:**
```
Patient is allergic to penicillin
```
The SSN was filtered out, but the allergy was stored.
---
## Updating Memories
When information changes, update existing memories instead of creating duplicates.
```python
# Initial allergy stored
result = client.add(
[{"role": "user", "content": "Patient confirmed allergy to penicillin with documented hives reaction"}],
user_id="patient_123"
)
memory_id = result['results'][0]['id']
print(f"Stored memory: {memory_id}")
# Later, patient gets retested - allergy was false positive
client.update(
memory_id=memory_id,
text="Patient tested negative for penicillin allergy on April 2nd, 2025. Previous allergy was false positive.",
metadata={"verified": True, "updated_date": "2025-04-02"}
)
# Retrieve the updated memory
updated = client.get(memory_id)
print(f"\\nUpdated memory: {updated['memory']}")
print(f"Metadata: {updated['metadata']}")
```
**Output:**
```
Stored memory: mem_abc123
Updated memory: Patient tested negative for penicillin allergy on April 2nd, 2025. Previous allergy was false positive.
Metadata: {'verified': True, 'updated_date': '2025-04-02'}
```
### Benefits of Updating
**Preserves history:**
- `created_at` shows when the memory was first stored
- `updated_at` shows when it was modified
- Audit trail for compliance
**Avoids conflicts:**
- No duplicate or contradicting memories
- Single source of truth for each fact
<Warning>
That “no duplicates” promise comes from the inference pipeline. Keep `infer=True` when you rely on automatic updates. Raw imports (`infer=False`) skip conflict checks, so mixing the two modes for the same fact will create duplicates.
</Warning>
### Pick the right inference mode
| Mode | What it does | Best for | Watch out for |
| --- | --- | --- | --- |
| `infer=True` *(default)* | Runs the LLM pipeline so Mem0 extracts structured facts and resolves conflicts automatically. | Daily conversations, preference tracking, anything you want deduped. | Slightly slower because inference runs on every write. |
| `infer=False` | Stores your payload exactly as-is: no inference, no dedupe. | Bulk imports, compliance snapshots, curated facts you already trust. | Later `infer=True` calls for the same fact will create duplicates you must clean manually. |
<Tip>
Stay consistent per data source. If you need both behaviors, keep them in separate scopes (e.g., different `app_id` or `run_id`) so you always know which memories are inferred vs direct imports.
</Tip>
---
## Update vs Delete
When should you update vs delete?
### Update when:
- Information changes but remains relevant
- You need audit history
- The memory has relationships to other data
```python
# Medication dosage changed
client.update(
memory_id=med_id,
text="Taking Lisinopril 20mg daily (increased from 10mg on March 1st)"
)
```
### Delete when:
- Information was completely wrong
- Memory is no longer relevant
- Duplicate entry
```python
# Duplicate entry
client.delete(memory_id)
```
---
## Putting It Together
Here's a complete ingestion pipeline with all controls:
```python
from mem0 import MemoryClient
import os
# Initialize client
client = MemoryClient(api_key=os.getenv("MEM0_API_KEY"))
# Configure custom instructions
client.project.update(
custom_instructions="""
Medical memory assistant rules:
STORE:
- Confirmed diagnoses (with doctor name and date)
- Verified allergies (with reaction details)
- Current medications (with dosage)
IGNORE:
- Speculation (might, maybe, possibly)
- Unverified symptoms
- Personal identifiers (SSN, insurance numbers)
CONFIDENCE:
Require high confidence. Reject vague or uncertain statements.
Require specific details: names, dates, dosages.
"""
)
# Helper function for safe ingestion
def add_medical_memory(content, user_id, metadata=None):
"""Add memory with automatic filtering."""
result = client.add(
[{"role": "user", "content": content}],
user_id=user_id,
metadata=metadata or {}
)
if result['results']:
print(f"✓ Stored: {result['results'][0]['memory']}")
else:
print(f"✗ Filtered: {content}")
return result
# Test cases
print("Testing ingestion pipeline:\\n")
test_cases = [
"I think I might be allergic to penicillin",
"Dr. Johnson confirmed penicillin allergy on Jan 15th with hives reaction",
"Patient SSN is 123-45-6789",
"Currently taking Lisinopril 10mg daily for hypertension",
"Feeling tired lately",
"Dr. Martinez diagnosed Type 2 diabetes on February 3rd, 2025"
]
for content in test_cases:
add_medical_memory(content, user_id="patient_123")
print()
```
**Output:**
```
Testing ingestion pipeline:
✗ Filtered: I think I might be allergic to penicillin
✓ Stored: Patient has confirmed penicillin allergy diagnosed by Dr. Johnson on January 15th with hives reaction
✗ Filtered: Patient SSN is 123-45-6789
✓ Stored: Patient is currently taking Lisinopril 10mg daily for hypertension
✗ Filtered: Feeling tired lately
✓ Stored: Patient diagnosed with Type 2 diabetes by Dr. Martinez on February 3rd, 2025
```
---
## Per-Call Instructions
You can override project-level instructions for specific conversations:
First define custom instructions
```python
custom_instructions="""Emergency intake mode:Store ALL symptoms and observations immediately.
Flag for later review and verification."""
```
```python
# Emergency intake - store everything temporarily
emergency_messages = [
{"role": "user", "content": "Patient arrived with chest pain and shortness of breath"}
]
client.add(
emergency_messages,
user_id="patient_456",
custom_instructions=custom_instructions,
metadata={"type": "emergency", "review_required": True}
)
```
This is useful for:
- Different conversation types (emergency vs routine)
- Channel-specific rules (phone vs in-person)
- Temporary data collection that needs review
---
## What You Built
You now have a medical assistant with production-grade memory controls:
- **Custom instructions** - Filter speculation and enforce confirmed facts only
- **Confidence thresholds** - Gate extractions below 0.7 confidence score
- **Memory updates** - Modify stored information without creating duplicates
- **Per-call instructions** - Apply temporary rules for specific conversations
- **PII filtering** - Block sensitive data (SSNs, insurance numbers) automatically
These controls prevent retrieval failures and ensure your AI assistant works with reliable, verified information.
---
## Summary
Start with conservative filters (only store confirmed facts) and iterate based on your application's needs. Combine custom instructions with confidence thresholds for the most reliable memory ingestion pipeline.
<Card title="Build a Mem0 Companion" icon="users" href="/cookbooks/essentials/building-ai-companion">
Learn core memory patterns including temporary vs permanent data handling.
</Card>
<Snippet file="star-on-github.mdx" />
@@ -21,7 +21,7 @@ from mem0 import MemoryClient
client = MemoryClient(api_key="m0-...")
```
Grab an API key from the <a href="https://app.mem0.ai/?utm_source=oss&utm_medium=cookbook-entity-partitioning" rel="nofollow">Mem0 dashboard</a> to get started.
Grab an API key from the <a href="https://app.mem0.ai/?utm_source=oss&utm_medium=cookbook-entity-partitioning">Mem0 dashboard</a> to get started.
## Store and Retrieve Scoped Memories
@@ -332,10 +332,10 @@ You learned how to:
href="/platform/features/v2-memory-filters"
/>
<Card
title="Control Memory Ingestion"
description="Pair scoped storage with rules that block low-quality facts."
title="Custom Instructions"
description="Pair scoped storage with instructions that steer what Mem0 extracts and stores."
icon="shield-check"
href="/cookbooks/essentials/controlling-memory-ingestion"
href="/platform/features/custom-instructions"
/>
</CardGroup>
@@ -23,7 +23,7 @@ client = MemoryClient(api_key="your-api-key")
```
<Note>
Your API key needs export permissions to download memory data. Check your project settings on the <a href="https://app.mem0.ai?utm_source=oss&utm_medium=cookbook-exporting-memories" rel="nofollow">dashboard</a> if export operations fail with authentication errors.
Your API key needs export permissions to download memory data. Check your project settings on the <a href="https://app.mem0.ai?utm_source=oss&utm_medium=cookbook-exporting-memories">dashboard</a> if export operations fail with authentication errors.
</Note>
Let's add some sample memories to work with:
@@ -287,8 +287,8 @@ Use **`get_all()`** for bulk retrieval, **`search()`** for specific questions, a
<Card title="Build a Mem0 Companion" icon="users" href="/cookbooks/essentials/building-ai-companion">
Learn core memory patterns including temporary vs permanent data handling.
</Card>
<Card title="Control Memory Ingestion" icon="filter" href="/cookbooks/essentials/controlling-memory-ingestion">
Ensure only verified insights make it into your export pipeline.
<Card title="Custom Instructions" icon="filter" href="/platform/features/custom-instructions">
Steer what Mem0 extracts so only verified insights make it into your export pipeline.
</Card>
</CardGroup>
@@ -249,8 +249,8 @@ Categories make retrieval faster and compliance easier. Define 3-5 clear categor
Instead of searching through everything, agents jump directly to the information type they need: billing issues, account details, or support tickets.
<CardGroup cols={2}>
<Card title="Control Memory Ingestion" icon="filter" href="/cookbooks/essentials/controlling-memory-ingestion">
Keep categories meaningful by filtering noise before it lands in storage.
<Card title="Custom Instructions" icon="filter" href="/platform/features/custom-instructions">
Keep categories meaningful by steering what Mem0 extracts before it lands in storage.
</Card>
<Card title="Export Tagged Memories" icon="download" href="/cookbooks/essentials/exporting-memories">
Use categories to drive audits, migrations, and compliance reports.
@@ -59,7 +59,7 @@ GEMINI_API_KEY=your-gemini-api-key-here
```
<Note>
Ensure you have your Mem0 API key from the <a href="https://app.mem0.ai?utm_source=oss&utm_medium=cookbook-gemini-3" rel="nofollow">Mem0 Dashboard</a> and your Gemini API key from the [Google AI Studio](https://ai.studio/app/api-keys).
Ensure you have your Mem0 API key from the <a href="https://app.mem0.ai?utm_source=oss&utm_medium=cookbook-gemini-3">Mem0 Dashboard</a> and your Gemini API key from the [Google AI Studio](https://ai.studio/app/api-keys).
</Note>
## Gemini Memory Agent

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