feat: add seamless Reflexio integration for hosted mem0 clients - #419
feat: add seamless Reflexio integration for hosted mem0 clients#419yyiilluu wants to merge 9 commits into
Conversation
📝 WalkthroughWalkthroughAdds an optional Changesmem0 drop-in wrapper
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Application
participant MemoryClient
participant mem0
participant ReflexioClient
Application->>MemoryClient: add(messages, options)
MemoryClient->>mem0: forward add operation
mem0-->>MemoryClient: mem0 result
MemoryClient->>ReflexioClient: publish normalized interactions
ReflexioClient-->>MemoryClient: publish result or error
MemoryClient-->>Application: mem0 result
Application->>MemoryClient: search(query, filters, include_reflexio)
MemoryClient->>mem0: forward search operation
mem0-->>MemoryClient: mem0 results
MemoryClient->>ReflexioClient: search profiles and playbooks
ReflexioClient-->>MemoryClient: Reflexio result data or error
MemoryClient-->>Application: combined search result
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@notebooks/07_mem0_dropin_wrapper.ipynb`:
- Around line 721-729: Extend the cleanup cell after the mem0 deletion request
to delete the Reflexio records associated with USER_ID, including any supported
derived profiles or playbooks, before calling show_success. Use the existing
Reflexio client/API symbols from the notebook and ensure cleanup is completed
for this test user before reporting success.
- Line 72: Update the notebook cleanup flow to remove Reflexio interactions,
profiles, and playbooks created during the current session, including records
identified by derived IDs, in addition to Mem0 memories. Scope deletion to those
session and derived record IDs, avoid organization-wide delete_all_* methods,
and place the logic near the existing client.delete_all() cleanup using the
imported display helpers as needed.
In `@README.md`:
- Around line 322-328: Update the README description of search() to state that
Reflexio sibling keys are included only when Reflexio is configured and used;
otherwise the original mem0 payload is returned unchanged. Clarify that “never
raises” applies only to best-effort Reflexio publishing/enrichment, while mem0
failures continue to propagate.
In `@reflexio/mem0/_wrapper.py`:
- Around line 179-199: Update the identity resolution in the mem0 add() publish
path before the user_id guard and _reflexio.publish_interaction call to read
user_id, agent_id, and run_id from effective options.filters, while preserving
the documented precedence between typed/stubbed options and kwargs. Ensure
filtered identity values enable publishing and are passed through for
attribution, then add regression coverage for options and kwargs precedence.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: b2b718bb-7339-4e07-b67c-f0c66848f43d
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (15)
README.mdclient_dist/pyproject.tomlnotebooks/07_mem0_dropin_wrapper.ipynbnotebooks/README.mdpyproject.tomlreflexio/mem0/__init__.pyreflexio/mem0/_wrapper.pytests/mem0/__init__.pytests/mem0/conftest.pytests/mem0/test_add_publish.pytests/mem0/test_delegation.pytests/mem0/test_import_guard.pytests/mem0/test_real_mem0_smoke.pytests/mem0/test_search_augment.pytypings/mem0/__init__.pyi
Customers using mem0's managed platform change one import (from reflexio.mem0 import MemoryClient) and keep every call site: - add() forwards to mem0, then best-effort publishes the trace to Reflexio (publish_interaction, wait_for_response=False) mapping user_id->user_id, run_id->session_id, agent_id->agent_version - search() returns mem0's payload untouched plus reflexio_profiles / reflexio_user_playbooks / reflexio_agent_playbooks sibling keys from unified search; keys are absent on any Reflexio failure - Reflexio errors are always logged and swallowed; mem0 errors propagate unchanged - mem0ai ships as an optional extra (reflexio-ai[mem0] and reflexio-client[mem0], pinned >=2.0,<3.0); import reflexio works without it and reflexio.mem0 raises a helpful ImportError - typings/mem0 stub keeps pyright from resolving the top-level name mem0 to the wrapper package itself when mem0ai is not installed
notebooks/07_mem0_dropin_wrapper.ipynb validates the reflexio.mem0 wrapper end to end against the real mem0 platform and a live local Reflexio backend: one add() stores memories in mem0 AND publishes the trace to Reflexio; one search() returns mem0 memories plus reflexio_profiles/user_playbooks/agent_playbooks sibling keys; a dead Reflexio endpoint degrades to pure-mem0 behavior. The conversation is 8 turns because the default extraction gate (stride_size=8) skips shorter test conversations. MEM0_API_KEY is read from the environment, never stored in the notebook.
- Pass-through mode when unconfigured: with neither REFLEXIO_API_KEY nor REFLEXIO_URL set (and no reflexio_client), the wrapper makes no Reflexio calls instead of firing doomed unauthenticated requests at the client's production default URL on every add()/search() - Skip augmentation for empty/blank queries instead of failing the Reflexio call and warning every time - Treat an explicit None role as 'user' rather than publishing 'None' - Debounce failure logs: first Reflexio failure warns, subsequent ones log at DEBUG so an outage doesn't spam the host app's logs - Tests for all of the above plus kwargs-over-options precedence for timestamp and filters
0a75736 to
5a6c4c8
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@reflexio/mem0/_wrapper.py`:
- Around line 110-119: The identity extraction logic around the filter helper
must reject conflicting values instead of returning the first match. Collect all
non-empty plain values for name from the top-level filters and every AND clause,
return the identity only when all collected values agree, and otherwise return
None; add regression coverage for conflicting user_id filters in both add() and
search().
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 931d46c3-0a42-4de2-bf1d-289f8e9f80d9
📒 Files selected for processing (5)
README.mdnotebooks/07_mem0_dropin_wrapper.ipynbreflexio/mem0/_wrapper.pytests/mem0/test_add_publish.pytests/mem0/test_search_augment.py
🚧 Files skipped from review as they are similar to previous changes (1)
- README.md
Mirror successful hosted sync and async adds while preserving exact mem0 search by default. Add opt-in namespaced retrieval, scoped lifecycle facades, composite identity isolation, native async Reflexio calls, and real-wheel compatibility CI for mem0 2.0.x.
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (8)
scripts/verify_mem0_artifact.py (1)
89-99: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winCheck inherited parameter defaults.
The verifier checks only parameter names and kinds. A wrapper can change a base constructor or
search()default and still pass. This can break existing call sites that omit optional arguments.Proposed verification improvement
- for name in base_init.parameters: + for name, base_parameter in base_init.parameters.items(): assert name in wrapper_init.parameters - assert wrapper_init.parameters[name].kind == base_init.parameters[name].kind + wrapper_parameter = wrapper_init.parameters[name] + assert wrapper_parameter.kind == base_parameter.kind + assert wrapper_parameter.default == base_parameter.default signature = inspect.signature(wrapper.search) + base_search = inspect.signature(base.search) + for name, base_parameter in base_search.parameters.items(): + wrapper_parameter = signature.parameters[name] + assert wrapper_parameter.kind == base_parameter.kind + assert wrapper_parameter.default == base_parameter.default🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/verify_mem0_artifact.py` around lines 89 - 99, Extend the checks in the wrapper signature verification loop to compare each inherited constructor parameter’s default between base_init and wrapper_init, preserving the existing name and kind checks. Also compare the corresponding defaults for wrapper.search parameters, including include_reflexio, so omitted optional arguments retain the base API behavior.reflexio/client/client.py (2)
179-188: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueUpdate the docstring type for
timeout.The parameter type is now
float. The docstring still documentstimeout (int).📝 Proposed docstring fix
- timeout (int): Default request timeout in seconds (default 300) + timeout (float): Default request timeout in seconds (default 300)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@reflexio/client/client.py` around lines 179 - 188, Update the timeout entry in the Reflexio client initializer docstring to document a float type, matching the timeout annotation in the initializer signature; leave the default and surrounding documentation unchanged.
2878-2924: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider sharing the request construction between
searchandsearch_async.
search_asyncrepeats the full 17-parameter signature and the identical_build_requestcall. A private_build_search_request(...)helper would keep the two entry points from drifting when a field is added toUnifiedSearchRequest.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@reflexio/client/client.py` around lines 2878 - 2924, Extract the shared request construction from search and search_async into a private _build_search_request helper, preserving all 17 parameters and their forwarding to _build_request with UnifiedSearchRequest. Update both entry points to use this helper while leaving their synchronous and asynchronous request execution paths unchanged.tests/mem0/test_add_publish.py (1)
311-314: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd a boolean case to the invalid-timeout parameters.
_validate_timeouthas an explicitisinstance(timeout, bool)guard, but no test exercises it.Truewould otherwise pass the numeric check becauseboolsubclassesint.💚 Proposed test addition
-@pytest.mark.parametrize("timeout", [0, -1, float("inf"), float("nan"), "5"]) +@pytest.mark.parametrize("timeout", [0, -1, float("inf"), float("nan"), "5", True]) def test_invalid_timeout_rejected(wrapped_cls, timeout):🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/mem0/test_add_publish.py` around lines 311 - 314, Extend the timeout values in test_invalid_timeout_rejected to include a boolean case such as True, ensuring _validate_timeout’s explicit bool rejection is covered while preserving the existing ValueError assertion.tests/client/test_publish_interaction.py (1)
132-157: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueMove the response import to module scope.
The
PublishUserInteractionResponseimport sits insidefake_publish, so it re-executes on every call and hides the dependency from readers. Import it with the other module-level imports.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/client/test_publish_interaction.py` around lines 132 - 157, Move the PublishUserInteractionResponse import out of the local fake_publish function in test_publish_interaction_async_uses_shared_validation_and_warnings and place it with the module-level imports. Remove the nested import while preserving the fake publisher’s return behavior.reflexio/mem0/_wrapper.py (2)
388-391: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winInclude the exception cause in the best-effort failure log.
_log_reflexio_failurerecords only the operation name. Every call site catches a broadExceptionand discards it, so an operator seesBest-effort Reflexio publish failedwith no cause. Diagnosing a misconfigured URL, an auth failure, or a timeout then requires a code change.Pass the exception through and log it with
exc_infoat the first (warning) occurrence.🔧 Proposed change
- def _log_reflexio_failure(self, operation: str) -> None: - level = logging.DEBUG if self._reflexio_failure_logged else logging.WARNING - self._reflexio_failure_logged = True - logger.log(level, "Best-effort Reflexio %s failed", operation) + def _log_reflexio_failure( + self, operation: str, exc: BaseException | None = None + ) -> None: + first = not self._reflexio_failure_logged + level = logging.WARNING if first else logging.DEBUG + self._reflexio_failure_logged = True + logger.log( + level, + "Best-effort Reflexio %s failed", + operation, + exc_info=exc if first else None, + )Then update each call site, for example:
- except Exception: # noqa: BLE001 - best-effort by contract. - self._log_reflexio_failure("publish") + except Exception as exc: # noqa: BLE001 - best-effort by contract. + self._log_reflexio_failure("publish", exc)The existing message text is unchanged, so
tests/mem0/test_add_publish.py::test_repeat_failures_warn_once_then_debugstill matches.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@reflexio/mem0/_wrapper.py` around lines 388 - 391, Update _log_reflexio_failure to accept the caught exception, preserve the existing operation message, and pass exc_info when logging the first warning occurrence while retaining debug-level behavior afterward. Update every broad-Exception call site that invokes _log_reflexio_failure to pass the caught exception through instead of discarding it.
205-215: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueSimplify the resolver selection.
resolver = _resolve_add_identity if add else Noneforces a second conditional inside the comprehension and computesfilterseven when it is unused. A direct branch reads more plainly.♻️ Proposed refactor
- resolver = _resolve_add_identity if add else None - filters = _opt(options, kwargs, "filters") - identities = { - name: resolver(options, kwargs, name) - if resolver - else _extract_identity(filters, name) - for name in _IDENTITY_NAMES - } + filters = _opt(options, kwargs, "filters") + identities = { + name: ( + _resolve_add_identity(options, kwargs, name) + if add + else _extract_identity(filters, name) + ) + for name in _IDENTITY_NAMES + }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@reflexio/mem0/_wrapper.py` around lines 205 - 215, Update _resolve_identities to use separate add and non-add branches instead of assigning resolver and conditionally invoking it inside the comprehension. In the add branch, build identities with _resolve_add_identity and avoid retrieving filters; in the non-add branch, retrieve filters and build identities with _extract_identity.tests/mem0/test_async_wrapper.py (1)
76-97: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winThis test passes even if the wrapper blocks the event loop.
asyncio.gatherawaits both coroutines, somark_heartbeatruns and setsheartbeat = Truebeforegatherreturns regardless of whether the Reflexio call yields. The assertion cannot fail for a blocking implementation.Assert ordering instead: record that the heartbeat completes while the search is still pending.
Two smaller points on the same block:
delayed_searchreturnsreflexio_mock.search.return_value, but the wrapper callssearch_async. Usereflexio_mock.search_async.return_valueso the fixture wiring matches the code path under test.asyncio.sleep(0.01)adds wall-clock time to the suite. A shorter sleep or anasyncio.Eventis sufficient.💚 Proposed test rewrite
- reflexio_mock.search_async.side_effect = delayed_search - client = async_wrapped_cls(api_key="mk", reflexio_client=reflexio_mock) - heartbeat = False - - async def mark_heartbeat(): - nonlocal heartbeat - await asyncio.sleep(0) - heartbeat = True - - await asyncio.gather( - client.search("q", filters={"user_id": "u1"}, include_reflexio=True), - mark_heartbeat(), - ) - assert heartbeat is True + order: list[str] = [] + + async def delayed_search(**_kwargs): + await asyncio.sleep(0.01) + order.append("search") + return reflexio_mock.search_async.return_value + + reflexio_mock.search_async.side_effect = delayed_search + client = async_wrapped_cls(api_key="mk", reflexio_client=reflexio_mock) + + async def mark_heartbeat(): + await asyncio.sleep(0) + order.append("heartbeat") + + await asyncio.gather( + client.search("q", filters={"user_id": "u1"}, include_reflexio=True), + mark_heartbeat(), + ) + assert order == ["heartbeat", "search"]🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/mem0/test_async_wrapper.py` around lines 76 - 97, Rewrite test_async_wrapper_yields_while_reflexio_search_waits to verify ordering: record when mark_heartbeat completes and assert it occurs before the pending client.search task finishes, rather than only asserting heartbeat after gather. Make delayed_search return reflexio_mock.search_async.return_value, and replace the fixed 0.01-second delay with a shorter or event-based synchronization.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/mem0-compat.yml:
- Around line 42-45: Update the non-minimum branch of the mem0 compatibility
matrix installation command to test the full supported mem0ai range through
versions below 3.0, matching the mem0 package extra instead of restricting it
below 2.1; leave the minimum branch pinned to mem0ai 2.0.0.
- Line 18: Update the workflow’s actions/checkout step to disable credential
persistence, and configure the workflow permissions as read-only. Ensure uv
build cannot access a persisted repository token while preserving the existing
checkout behavior.
In `@reflexio/mem0/__init__.py`:
- Around line 13-19: Update the import guard around AsyncMemory and Memory to
catch only ModuleNotFoundError where exc.name equals "mem0", preserving the
existing optional-dependency message for that case. Re-raise all other import
failures unchanged, including missing internal dependencies and missing exports.
In `@tests/client/test_request_headers.py`:
- Around line 50-52: Update the FakeResponse class by importing ClassVar at
module scope and annotating its mutable headers class attribute as ClassVar with
the existing dictionary type, resolving RUF012 without changing its value or
behavior.
In `@typings/mem0/__init__.pyi`:
- Around line 10-26: Extend both MemoryClient and AsyncMemoryClient stubs with
get, delete_all, delete, delete_users, and reset methods. Use synchronous return
signatures for MemoryClient and async coroutine signatures for
AsyncMemoryClient, matching the existing method parameter and return-type
conventions in each class.
---
Nitpick comments:
In `@reflexio/client/client.py`:
- Around line 179-188: Update the timeout entry in the Reflexio client
initializer docstring to document a float type, matching the timeout annotation
in the initializer signature; leave the default and surrounding documentation
unchanged.
- Around line 2878-2924: Extract the shared request construction from search and
search_async into a private _build_search_request helper, preserving all 17
parameters and their forwarding to _build_request with UnifiedSearchRequest.
Update both entry points to use this helper while leaving their synchronous and
asynchronous request execution paths unchanged.
In `@reflexio/mem0/_wrapper.py`:
- Around line 388-391: Update _log_reflexio_failure to accept the caught
exception, preserve the existing operation message, and pass exc_info when
logging the first warning occurrence while retaining debug-level behavior
afterward. Update every broad-Exception call site that invokes
_log_reflexio_failure to pass the caught exception through instead of discarding
it.
- Around line 205-215: Update _resolve_identities to use separate add and
non-add branches instead of assigning resolver and conditionally invoking it
inside the comprehension. In the add branch, build identities with
_resolve_add_identity and avoid retrieving filters; in the non-add branch,
retrieve filters and build identities with _extract_identity.
In `@scripts/verify_mem0_artifact.py`:
- Around line 89-99: Extend the checks in the wrapper signature verification
loop to compare each inherited constructor parameter’s default between base_init
and wrapper_init, preserving the existing name and kind checks. Also compare the
corresponding defaults for wrapper.search parameters, including
include_reflexio, so omitted optional arguments retain the base API behavior.
In `@tests/client/test_publish_interaction.py`:
- Around line 132-157: Move the PublishUserInteractionResponse import out of the
local fake_publish function in
test_publish_interaction_async_uses_shared_validation_and_warnings and place it
with the module-level imports. Remove the nested import while preserving the
fake publisher’s return behavior.
In `@tests/mem0/test_add_publish.py`:
- Around line 311-314: Extend the timeout values in
test_invalid_timeout_rejected to include a boolean case such as True, ensuring
_validate_timeout’s explicit bool rejection is covered while preserving the
existing ValueError assertion.
In `@tests/mem0/test_async_wrapper.py`:
- Around line 76-97: Rewrite
test_async_wrapper_yields_while_reflexio_search_waits to verify ordering: record
when mark_heartbeat completes and assert it occurs before the pending
client.search task finishes, rather than only asserting heartbeat after gather.
Make delayed_search return reflexio_mock.search_async.return_value, and replace
the fixed 0.01-second delay with a shorter or event-based synchronization.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 7ad394f9-73a5-496f-9989-405585eeccf4
⛔ Files ignored due to path filters (2)
client_dist/uv.lockis excluded by!**/*.lockuv.lockis excluded by!**/*.lock
📒 Files selected for processing (22)
.github/workflows/mem0-compat.ymlREADME.mdclient_dist/pyproject.tomlnotebooks/07_mem0_dropin_wrapper.ipynbpyproject.tomlreflexio/client/client.pyreflexio/mem0/__init__.pyreflexio/mem0/_facade.pyreflexio/mem0/_wrapper.pyscripts/verify_mem0_artifact.pytests/client/test_publish_interaction.pytests/client/test_request_headers.pytests/client/test_search.pytests/mem0/conftest.pytests/mem0/test_add_publish.pytests/mem0/test_async_wrapper.pytests/mem0/test_delegation.pytests/mem0/test_facade.pytests/mem0/test_import_guard.pytests/mem0/test_real_mem0_smoke.pytests/mem0/test_search_augment.pytypings/mem0/__init__.pyi
🚧 Files skipped from review as they are similar to previous changes (7)
- client_dist/pyproject.toml
- tests/mem0/test_import_guard.py
- README.md
- tests/mem0/test_real_mem0_smoke.py
- pyproject.toml
- tests/mem0/test_delegation.py
- notebooks/07_mem0_dropin_wrapper.ipynb
What
Makes Reflexio a compatibility-first extension for mem0's hosted clients. Existing users switch one import; successful
add()calls are mirrored to Reflexio, while ordinarysearch()remains exactly mem0 unless retrieval is explicitly requested.The wrapper never rewrites a prompt or injects retrieved text. Applications keep deciding how to validate and format both mem0 memories and Reflexio learnings as prompt context.
Behavior
MemoryClientandAsyncMemoryClient; localMemoryandAsyncMemoryremain exact mem0 aliases.add(), propagates mem0 failures, then attempts one bounded Reflexio publish. The exact mem0 result is returned regardless of the best-effort mirror outcome.search()behavior exact: no configuration inspection, Reflexio call, copy, or result mutation unlessinclude_reflexio=True.reflexioenvelope withok,skipped, orerrorstatus and sanitized reason codes.ReflexioNamespaceCollisionErrorbefore retrieval if mem0 already owns the reserved top-level key.client.reflexio, with scoped user/session/record deletion and no organization-wide deletion methods. Inherited mem0 deletes remain mem0-only.publish_interaction_async()andsearch_async()methods toReflexioClient, including an aiohttp total timeout.Compatibility and delivery contract
mem0ai>=2.0,<2.1for bothreflexio-ai[mem0]andreflexio-client[mem0].reflexio_timeoutis rejected.delete_session_recordsremoves stored requests/interactions but not derived learnings.clear_user_dataremoves encoded-user requests, interactions, profiles, and user playbooks, but shared agent playbooks require explicit deletion by ID. These are not transactional barriers against queued work.Packaging and CI
Verification
reflexio-ai[mem0]against mem0 2.0.0 andreflexio-client[mem0]against mem0 2.0.18; both artifact contract verifiers passed.Documentation
Summary by CodeRabbit
New Features
Documentation
Bug Fixes