Skip to content

feat(adapters): Mem0 memory adapter with push/pull/reconcile protocol (Phase 3)#79

Merged
Gradata merged 3 commits into
mainfrom
wt-phase3-adapters-mem0
Apr 15, 2026
Merged

feat(adapters): Mem0 memory adapter with push/pull/reconcile protocol (Phase 3)#79
Gradata merged 3 commits into
mainfrom
wt-phase3-adapters-mem0

Conversation

@Gradata

@Gradata Gradata commented Apr 15, 2026

Copy link
Copy Markdown
Owner

Summary

First external-memory adapter. Greenfield src/gradata/adapters/ module with MemoryAdapter Protocol + concrete Mem0Adapter. Lays the pattern for Letta/EverMind/Hermes follow-ups.

API

from gradata.adapters import Mem0Adapter
adapter = Mem0Adapter(api_key=..., user_id=..., client=None)  # client= for DI/tests
adapter.push_correction(*, draft, final, summary="", tags=None, metadata=None) -> str | None
adapter.pull_memory_for_context(query, *, k=5, filters=None) -> list[dict]
adapter.reconcile(*, gradata_memory_ids=None) -> dict

All three methods swallow backend exceptions, log at WARNING, return sentinel (None / [] / {}).

Optional dep

Added adapters-mem0 = ["mem0ai>=0.1.0"] extra in pyproject.toml and folded into all. Lazy-imported inside __init__, so from gradata.adapters import Mem0Adapter works without the extra installed.

Wiring

NOT wired into the event pipeline. Users call adapter.push_correction() themselves. Auto-mirror via Brain(...) constructor option is a follow-up.

Divergences from plan (intentional)

  1. Added client= DI kwarg — only clean way to hit "fake-client tests pass without network" without monkeypatching.
  2. Added response-shape tolerance (_extract_memory_id, _normalise_search_results, _extract_all_ids) — mem0ai shipped 3 different envelope shapes across versions.
  3. Added SDK-version fallback test.

Tests

2419 pass (+18 fake-client + 1 gated integration test on MEM0_API_KEY). Ruff clean.

Commits

  • 0ede7e7 feat(adapters): Mem0 memory adapter with push/pull/reconcile protocol

Co-Authored-By: Gradata noreply@gradata.ai

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gradata has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@coderabbitai

coderabbitai Bot commented Apr 15, 2026

Copy link
Copy Markdown
📝 Walkthrough
  • New gradata.adapters module with MemoryAdapter protocol and Mem0Adapter concrete implementation for mirroring corrections to external memory backends
  • Three public API methods: push_correction() (mirror draft/final to Mem0), pull_memory_for_context() (retrieve context from Mem0), reconcile() (diff local vs remote memory IDs)
  • Optional dependency: Added adapters-mem0 = ["mem0ai>=1.0.11"] extra; mem0ai is lazy-imported so no hard dependency required
  • Resilient error handling: All methods swallow backend exceptions, log at WARNING level, and return sentinel values (None/[]/{}); no exceptions propagated to callers
  • Response shape tolerance: Helper functions handle varying Mem0 SDK response envelopes and include SDK-version compatibility fallback (retries without filters on TypeError)
  • Opt-in integration: Adapters are not auto-wired into the event pipeline; users must explicitly call adapter.push_correction() from their correction call sites
  • Test coverage: 18 offline fake-client tests validating construction, method contracts, error handling, and response normalization; 1 integration test (requires MEM0_API_KEY) for roundtrip verification

Walkthrough

Adds an opt-in external memory adapter system: a MemoryAdapter protocol, a Mem0Adapter implementation using the mem0ai SDK, package exports, a new adapters-mem0 optional dependency in pyproject, and tests including an integration smoke test. (50 words)

Changes

Cohort / File(s) Summary
Configuration
pyproject.toml
Added optional extra adapters-mem0 = ["mem0ai>=1.0.11"]; reformatted and extended the all extra to include mem0ai>=1.0.11 alongside existing extras.
Adapter package exports
src/gradata/adapters/__init__.py
New module exposing MemoryAdapter (from .base) and Mem0Adapter (from .mem0) via __all__.
Adapter protocol
src/gradata/adapters/base.py
New @runtime_checkable MemoryAdapter Protocol defining sync methods push_correction, pull_memory_for_context, and reconcile with typed signatures and specified failure-return semantics.
Mem0 integration
src/gradata/adapters/mem0.py
New Mem0Adapter implementing the protocol using mem0ai (supports injected client or lazy SDK import with api_key), robust error handling, SDK-compatibility retries (e.g., omit filters), and helpers to normalize/extract IDs and search results across varied response shapes.
Tests
tests/test_mem0_adapter.py
New offline test suite with _FakeMem0Client covering constructor rules, payload shape, metadata merging/protection, response-shape normalization, error handling and logging, retry behavior, reconciliation diffs, and an integration smoke test gated on MEM0_API_KEY.

Sequence Diagram(s)

sequenceDiagram
    participant Gradata as Gradata Code
    participant Adapter as Mem0Adapter
    participant MemClient as Mem0MemoryClient
    participant Mem0Backend as Mem0 Backend

    rect rgba(100, 150, 200, 0.5)
        Note over Gradata,Mem0Backend: push_correction Flow
        Gradata->>Adapter: push_correction(draft, final, summary, tags, metadata)
        Adapter->>Adapter: Enrich metadata with source="gradata" and kind="correction"
        Adapter->>MemClient: add(messages=[...], metadata={...}, user_id=...)
        MemClient->>Mem0Backend: POST /add
        Mem0Backend-->>MemClient: Return response (various shapes)
        MemClient-->>Adapter: Response object
        Adapter->>Adapter: Extract memory ID or return None on failure
        Adapter-->>Gradata: str | None
    end

    rect rgba(150, 200, 100, 0.5)
        Note over Gradata,Mem0Backend: pull_memory_for_context Flow
        Gradata->>Adapter: pull_memory_for_context(query, k=5, filters=...)
        Adapter->>MemClient: search(query, limit=k, filters=...) 
        MemClient->>Mem0Backend: POST /search
        Mem0Backend-->>MemClient: Return results
        MemClient-->>Adapter: Search response (varied shapes)
        Adapter->>Adapter: Normalize to list[{text, metadata, score}]
        Adapter-->>Gradata: list[dict[str, Any]]
    end

    rect rgba(200, 150, 100, 0.5)
        Note over Gradata,Mem0Backend: reconcile Flow
        Gradata->>Adapter: reconcile(gradata_memory_ids=[...])
        Adapter->>MemClient: get_all(user_id=...)
        MemClient->>Mem0Backend: GET /memories
        Mem0Backend-->>MemClient: Return all memories
        MemClient-->>Adapter: Response with memory list
        Adapter->>Adapter: Extract remote IDs and compute diff (only_local, only_remote)
        Adapter-->>Gradata: dict[str, Any] with reconciliation results
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 32.26% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and specifically summarizes the main change: introducing a Mem0 memory adapter with the core protocol methods (push/pull/reconcile).
Description check ✅ Passed The PR description provides detailed context about the adapter implementation, API surface, optional dependencies, testing approach, and intentional divergences from the original plan.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch wt-phase3-adapters-mem0

Comment @coderabbitai help to get the list of available commands and usage tips.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Apr 15, 2026

Copy link
Copy Markdown

Deploying gradata-dashboard with  Cloudflare Pages  Cloudflare Pages

Latest commit: bc4b9f9
Status:🚫  Build failed.

View logs

@coderabbitai coderabbitai Bot added the feature label Apr 15, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pyproject.toml`:
- Around line 33-39: The pyproject dependency entries reference a non-existent
mem0ai version; update both occurrences of "mem0ai>=0.1.0" (in the adapters-mem0
extra and the all extras list) to a valid release such as "mem0ai>=1.0.7" so the
package can be resolved during installation and keep both entries consistent.

In `@src/gradata/adapters/mem0.py`:
- Around line 118-128: The current meta construction lets caller-provided
metadata overwrite internal keys because of meta.update(metadata) after setting
base keys; to preserve base values change the update order or sanitize the
incoming dict: either apply metadata first and then set meta =
{"source":"gradata","kind":"correction", ...} (ensuring source/kind override
caller keys), or keep the current order but remove reserved keys from the
incoming `metadata` before calling `meta.update(metadata)` (e.g., pop or ignore
"source" and "kind"); this change should be made around the `meta` creation
where `meta: dict[str, Any] = {...}` and `meta.update(metadata)` are used.
🪄 Autofix (Beta)

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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 451290b2-ee57-4d8d-a433-404aed7fac51

📥 Commits

Reviewing files that changed from the base of the PR and between a100a3a and 0ede7e7.

📒 Files selected for processing (5)
  • pyproject.toml
  • src/gradata/adapters/__init__.py
  • src/gradata/adapters/base.py
  • src/gradata/adapters/mem0.py
  • tests/test_mem0_adapter.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.13)
  • GitHub Check: test (3.11)
  • GitHub Check: Cloudflare Pages
🧰 Additional context used
📓 Path-based instructions (2)
src/gradata/**/*.py

⚙️ CodeRabbit configuration file

src/gradata/**/*.py: This is the core SDK. Check for: type safety (from future import annotations required), no print()
statements (use logging), all functions accepting BrainContext where DB access occurs, no hardcoded paths. Severity
scoring must clamp to [0,1]. Confidence values must be in [0.0, 1.0].

Files:

  • src/gradata/adapters/__init__.py
  • src/gradata/adapters/base.py
  • src/gradata/adapters/mem0.py
tests/**

⚙️ CodeRabbit configuration file

tests/**: Test files. Verify: no hardcoded paths, assertions check specific values not just truthiness,
parametrized tests preferred for boundary conditions, floating point comparisons use pytest.approx.

Files:

  • tests/test_mem0_adapter.py
🔇 Additional comments (14)
src/gradata/adapters/mem0.py (6)

264-269: Minor: or chaining treats empty string as falsy.

The expression item.get("memory") or item.get("text") or ... will skip an explicit empty string "" and try the next key. This is likely fine since empty memory text isn't meaningful, but be aware if the SDK ever returns "" intentionally.


1-40: LGTM!

Module header follows coding guidelines: includes from __future__ import annotations, uses logging module instead of print(), and provides clear usage documentation.


64-92: LGTM!

Constructor validation is correct: enforces non-empty user_id, supports dependency injection for testing via client=, and provides a helpful ImportError message when mem0ai is missing. The lazy import pattern keeps the module importable without the optional dependency.


153-177: LGTM!

The dual-layer exception handling for SDK backward compatibility is well-implemented. The TypeError catch for older SDKs that don't accept filters triggers a retry without that kwarg, and any other exception correctly logs and returns [] per the protocol contract.


179-205: LGTM!

The reconcile implementation correctly computes the set difference between local and remote memory IDs. Using sorted() ensures deterministic output, and the exception handling follows the protocol contract.


214-297: LGTM!

The response-shape helper functions handle multiple Mem0 SDK envelope formats robustly. The type guards (isinstance checks) prevent runtime errors on unexpected shapes, and returning empty sentinels on parse failure aligns with the adapter's fail-safe design.

src/gradata/adapters/base.py (1)

1-93: LGTM!

The MemoryAdapter protocol is well-designed with clear failure-mode contracts (return sentinels instead of raising). The @runtime_checkable decorator enables isinstance() checks, and all methods have proper type annotations with comprehensive docstrings.

src/gradata/adapters/__init__.py (1)

1-22: LGTM!

Clean package interface with explicit __all__. The eager import of Mem0Adapter is fine since the actual mem0ai import inside Mem0Adapter.__init__ is lazy—users can import the class without having the optional dependency installed.

tests/test_mem0_adapter.py (6)

22-63: LGTM!

The _FakeMem0Client test double is well-designed: it records all calls for assertion, supports configurable responses and failure modes, and simulates the accepts_filters=False behavior for SDK version compatibility testing.


71-83: LGTM!

Constructor tests properly verify: empty user_id raises ValueError, dependency injection works without api_key, and the adapter satisfies the MemoryAdapter protocol (runtime-checkable isinstance).


91-172: LGTM!

Comprehensive test coverage for push_correction: verifies all four response shape variants (results envelope, flat id, list, string), unknown shape handling, exception logging, and metadata merging. Assertions check specific values rather than just truthiness.


180-259: LGTM!

Thorough coverage of pull_memory_for_context: result normalization, bare-list fallback, SDK backward-compat retry, exception handling with log verification, and None response handling. The floating-point score comparisons (0.91, 0.77, 0.5) are fine since they're canned fixture values, not computed results.


267-301: LGTM!

Reconcile tests verify the set-difference logic correctly: only_local, only_remote, and remote_count are all checked with specific expected values. Exception handling with empty dict return and warning log is properly verified.


309-327: LGTM!

Integration smoke test is properly gated with @pytest.mark.integration and @pytest.mark.skipif on missing MEM0_API_KEY. The minimal assertions (non-None ID, list return type) are appropriate for a smoke test that validates connectivity without over-specifying API response details.

Comment thread pyproject.toml Outdated
Comment thread src/gradata/adapters/mem0.py Outdated
Gradata added a commit that referenced this pull request Apr 15, 2026
…erved-keys protection)

- Bump mem0ai extra from >=0.1.0 (non-existent on PyPI) to >=1.0.11 in both
  the adapters-mem0 extra and the all extras list.
- Mem0Adapter.push_correction: apply caller metadata first, then set internal
  source/kind/summary/tags so callers cannot overwrite reserved keys.
- Add regression test proving metadata={"source":"evil","kind":"evil"} does
  not override source="gradata" / kind="correction".

Co-Authored-By: Gradata <noreply@gradata.ai>

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gradata has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/test_mem0_adapter.py`:
- Around line 123-144: Consolidate the four nearly identical tests
(test_push_correction_handles_flat_id_response,
test_push_correction_handles_list_response,
test_push_correction_handles_string_response,
test_push_correction_returns_none_on_unknown_shape) into a single
pytest.mark.parametrize test that iterates over different add_response values
passed to _FakeMem0Client and checks Mem0Adapter(user_id="oliver",
client=fake).push_correction(draft="a", final="b") against the expected id
(including None for the unknown shape); reference the _FakeMem0Client,
Mem0Adapter, and push_correction symbols when locating the code to replace.
🪄 Autofix (Beta)

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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 91f8b207-8fd9-40f2-9674-e87ec36f3a45

📥 Commits

Reviewing files that changed from the base of the PR and between 0ede7e7 and 1bacd7f.

📒 Files selected for processing (3)
  • pyproject.toml
  • src/gradata/adapters/mem0.py
  • tests/test_mem0_adapter.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.12)
  • GitHub Check: Cloudflare Pages
🧰 Additional context used
📓 Path-based instructions (2)
tests/**

⚙️ CodeRabbit configuration file

tests/**: Test files. Verify: no hardcoded paths, assertions check specific values not just truthiness,
parametrized tests preferred for boundary conditions, floating point comparisons use pytest.approx.

Files:

  • tests/test_mem0_adapter.py
src/gradata/**/*.py

⚙️ CodeRabbit configuration file

src/gradata/**/*.py: This is the core SDK. Check for: type safety (from future import annotations required), no print()
statements (use logging), all functions accepting BrainContext where DB access occurs, no hardcoded paths. Severity
scoring must clamp to [0,1]. Confidence values must be in [0.0, 1.0].

Files:

  • src/gradata/adapters/mem0.py
🔇 Additional comments (11)
pyproject.toml (1)

33-39: LGTM!

The mem0ai>=1.0.11 version constraint is consistent between the adapters-mem0 extra and the all extra. The previous review concern about the non-existent >=0.1.0 version has been addressed.

src/gradata/adapters/mem0.py (5)

64-92: LGTM!

The constructor properly validates user_id, supports dependency injection for testing, provides lazy importing of mem0ai, and includes helpful error messages for missing dependencies.


118-127: LGTM!

The metadata handling correctly applies caller-provided metadata first, then sets internal reserved keys (source, kind, summary, tags) afterward to prevent callers from overwriting them. This addresses the previous review concern.


141-177: LGTM!

The method correctly handles SDK version compatibility by catching TypeError for the filters parameter and retrying without it. Exception handling follows the protocol contract (returns [], logs warning, doesn't raise).


179-205: LGTM!

The reconcile method correctly computes set differences between local and remote memory IDs, returns sorted results for determinism, and follows the protocol contract (returns {} on failure, logs warning, doesn't raise).


214-297: LGTM!

The response-shape helper functions are robust, handling None, various envelope structures ({"id": ...}, {"results": [...]}, bare lists, strings), and defensive type checking throughout. This isolates SDK version differences cleanly.

tests/test_mem0_adapter.py (5)

22-63: LGTM!

The _FakeMem0Client is a well-designed test double that records calls for assertion, supports configurable responses, can simulate exceptions, and can emulate older SDK behavior (no filters support). This enables comprehensive offline testing.


71-83: LGTM!

Construction tests properly verify validation (empty user_id), dependency injection, and protocol conformance with specific assertions.


213-292: LGTM!

The pull_memory_for_context tests comprehensively cover result normalization, bare list handling, SDK version compatibility (TypeError retry), exception handling, and None response scenarios. All assertions check specific values.


300-334: LGTM!

The reconcile tests properly verify diff computation (set differences), handle both envelope and bare list response formats, and test exception handling. Assertions check specific values.


342-361: LGTM with minor observation.

The integration smoke test is appropriately gated by MEM0_API_KEY and marked for CI skip. The assertions verify basic contract compliance. For a smoke test, the isinstance(hits, list) check is acceptable, though you could optionally verify the list is non-empty or that returned items have the expected structure (text, metadata, score keys).

Comment thread tests/test_mem0_adapter.py Outdated
Comment on lines +123 to +144
def test_push_correction_handles_flat_id_response() -> None:
fake = _FakeMem0Client(add_response={"id": "mem-flat"})
adapter = Mem0Adapter(user_id="oliver", client=fake)
assert adapter.push_correction(draft="a", final="b") == "mem-flat"


def test_push_correction_handles_list_response() -> None:
fake = _FakeMem0Client(add_response=[{"id": "mem-list"}])
adapter = Mem0Adapter(user_id="oliver", client=fake)
assert adapter.push_correction(draft="a", final="b") == "mem-list"


def test_push_correction_handles_string_response() -> None:
fake = _FakeMem0Client(add_response="mem-str")
adapter = Mem0Adapter(user_id="oliver", client=fake)
assert adapter.push_correction(draft="a", final="b") == "mem-str"


def test_push_correction_returns_none_on_unknown_shape() -> None:
fake = _FakeMem0Client(add_response={"no": "id"})
adapter = Mem0Adapter(user_id="oliver", client=fake)
assert adapter.push_correction(draft="a", final="b") is None

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider parametrizing response shape tests.

The tests for different response shapes (flat_id, list, string, unknown) follow the same pattern. They could be consolidated into a parametrized test for improved maintainability, though the current form is acceptable.

♻️ Optional: Parametrize response shape tests
`@pytest.mark.parametrize`(
    "add_response,expected_id",
    [
        ({"id": "mem-flat"}, "mem-flat"),
        ([{"id": "mem-list"}], "mem-list"),
        ("mem-str", "mem-str"),
        ({"no": "id"}, None),
    ],
    ids=["flat_dict", "list", "string", "unknown_shape"],
)
def test_push_correction_handles_response_shapes(
    add_response: Any, expected_id: str | None
) -> None:
    fake = _FakeMem0Client(add_response=add_response)
    adapter = Mem0Adapter(user_id="oliver", client=fake)
    assert adapter.push_correction(draft="a", final="b") == expected_id
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_mem0_adapter.py` around lines 123 - 144, Consolidate the four
nearly identical tests (test_push_correction_handles_flat_id_response,
test_push_correction_handles_list_response,
test_push_correction_handles_string_response,
test_push_correction_returns_none_on_unknown_shape) into a single
pytest.mark.parametrize test that iterates over different add_response values
passed to _FakeMem0Client and checks Mem0Adapter(user_id="oliver",
client=fake).push_correction(draft="a", final="b") against the expected id
(including None for the unknown shape); reference the _FakeMem0Client,
Mem0Adapter, and push_correction symbols when locating the code to replace.

Gradata added a commit that referenced this pull request Apr 15, 2026
…erved-keys protection)

- Bump mem0ai extra from >=0.1.0 (non-existent on PyPI) to >=1.0.11 in both
  the adapters-mem0 extra and the all extras list.
- Mem0Adapter.push_correction: apply caller metadata first, then set internal
  source/kind/summary/tags so callers cannot overwrite reserved keys.
- Add regression test proving metadata={"source":"evil","kind":"evil"} does
  not override source="gradata" / kind="correction".

Co-Authored-By: Gradata <noreply@gradata.ai>
@Gradata Gradata force-pushed the wt-phase3-adapters-mem0 branch from 1bacd7f to b23c574 Compare April 15, 2026 09:49

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gradata has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

Gradata and others added 3 commits April 15, 2026 03:27
First external-memory adapter. Ships the MemoryAdapter protocol
(runtime_checkable) plus a Mem0 concrete implementation with
push_correction, pull_memory_for_context, and reconcile. Opt-in
via the new adapters-mem0 extra in pyproject.toml. Not wired into
the event pipeline; users call adapter methods themselves.

Adapter never raises on backend failure: writes return None, reads
return []. Full fake-client test coverage (18 tests) plus a gated
integration smoke test that runs only when MEM0_API_KEY is set.

Co-Authored-By: Gradata <noreply@gradata.ai>
…erved-keys protection)

- Bump mem0ai extra from >=0.1.0 (non-existent on PyPI) to >=1.0.11 in both
  the adapters-mem0 extra and the all extras list.
- Mem0Adapter.push_correction: apply caller metadata first, then set internal
  source/kind/summary/tags so callers cannot overwrite reserved keys.
- Add regression test proving metadata={"source":"evil","kind":"evil"} does
  not override source="gradata" / kind="correction".

Co-Authored-By: Gradata <noreply@gradata.ai>
…it; rebase onto main

Co-Authored-By: Gradata <noreply@gradata.ai>
@Gradata Gradata force-pushed the wt-phase3-adapters-mem0 branch from b23c574 to bc4b9f9 Compare April 15, 2026 10:32

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gradata has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/test_mem0_adapter.py`:
- Around line 229-233: The test currently asserts an exact float match for
hits[0]["score"] (and similarly at the other assertion) which should use
approximate comparison; update the assertion so the score is checked with
pytest.approx (e.g., assert hits[0]["score"] == pytest.approx(0.91)) or replace
the whole dict comparison with matching for text and metadata but compare the
numeric score using pytest.approx; ensure you import pytest if not already
present and apply the same change to the second float assertion referenced in
the comment.
- Around line 354-355: The current test only asserts that hits is a list, which
is too weak; update the test around adapter.pull_memory_for_context("greeting
style", k=3) to assert concrete expectations from the roundtrip: check that hits
is non-empty (len(hits) > 0), that its length is <= 3, and that each hit has the
expected structure/fields (e.g., contains a "text" or "content" attribute or
that the first item's text contains "greeting" or matches the known sample
inserted earlier). Use adapter.pull_memory_for_context and the hits variable
names to locate the test and replace the loose isinstance check with these
stronger assertions.
🪄 Autofix (Beta)

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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: cab50929-8e85-46eb-9be7-243b81f3374f

📥 Commits

Reviewing files that changed from the base of the PR and between 1bacd7f and bc4b9f9.

📒 Files selected for processing (5)
  • pyproject.toml
  • src/gradata/adapters/__init__.py
  • src/gradata/adapters/base.py
  • src/gradata/adapters/mem0.py
  • tests/test_mem0_adapter.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
  • GitHub Check: Test (Python 3.12)
  • GitHub Check: test (3.13)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.12)
  • GitHub Check: Test (Python 3.11)
  • GitHub Check: Test (Python 3.13)
  • GitHub Check: Python 3.11
  • GitHub Check: Python 3.12
  • GitHub Check: Python 3.12
  • GitHub Check: Python 3.11
  • GitHub Check: Test (Python 3.11)
  • GitHub Check: Test (Python 3.13)
  • GitHub Check: Test (Python 3.12)
  • GitHub Check: Cloudflare Pages
🧰 Additional context used
📓 Path-based instructions (2)
src/gradata/**/*.py

⚙️ CodeRabbit configuration file

src/gradata/**/*.py: This is the core SDK. Check for: type safety (from future import annotations required), no print()
statements (use logging), all functions accepting BrainContext where DB access occurs, no hardcoded paths. Severity
scoring must clamp to [0,1]. Confidence values must be in [0.0, 1.0].

Files:

  • src/gradata/adapters/__init__.py
  • src/gradata/adapters/base.py
  • src/gradata/adapters/mem0.py
tests/**

⚙️ CodeRabbit configuration file

tests/**: Test files. Verify: no hardcoded paths, assertions check specific values not just truthiness,
parametrized tests preferred for boundary conditions, floating point comparisons use pytest.approx.

Files:

  • tests/test_mem0_adapter.py
🔇 Additional comments (5)
pyproject.toml (1)

33-39: Optional dependency wiring looks correct.

adapters-mem0 and the all extra are consistent, and the version constraint now points to a valid mem0ai release line.

src/gradata/adapters/__init__.py (1)

17-22: Clean public export surface for adapters.

The module keeps imports minimal and exposes a clear public API through __all__.

src/gradata/adapters/base.py (1)

22-93: Protocol contract is clear and implementation-ready.

@runtime_checkable plus explicit sentinel return contracts provides a strong adapter boundary.

tests/test_mem0_adapter.py (1)

123-139: Great consolidation with parametrized response-shape coverage.

This keeps the ID-shape matrix easy to extend and maintain.

src/gradata/adapters/mem0.py (1)

118-177: Good defensive behavior on metadata and backend failures.

The reserved metadata key enforcement (Line 121-127) and sentinel-return failure handling in push/pull are solid.

Comment on lines +229 to +233
assert hits[0] == {
"text": "user prefers full names",
"metadata": {"tags": ["greeting"]},
"score": 0.91,
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use pytest.approx for float score checks.

At Line 232 and Line 246, float comparisons are exact; these should use pytest.approx per test policy.

🎯 Suggested update for float assertions
-    assert hits[0] == {
-        "text": "user prefers full names",
-        "metadata": {"tags": ["greeting"]},
-        "score": 0.91,
-    }
+    assert hits[0]["text"] == "user prefers full names"
+    assert hits[0]["metadata"] == {"tags": ["greeting"]}
+    assert hits[0]["score"] == pytest.approx(0.91)

@@
-    assert hits == [{"text": "plain text memory", "metadata": {}, "score": 0.5}]
+    assert hits[0]["text"] == "plain text memory"
+    assert hits[0]["metadata"] == {}
+    assert hits[0]["score"] == pytest.approx(0.5)

As per coding guidelines, tests/**: floating point comparisons use pytest.approx.

Also applies to: 246-246

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_mem0_adapter.py` around lines 229 - 233, The test currently
asserts an exact float match for hits[0]["score"] (and similarly at the other
assertion) which should use approximate comparison; update the assertion so the
score is checked with pytest.approx (e.g., assert hits[0]["score"] ==
pytest.approx(0.91)) or replace the whole dict comparison with matching for text
and metadata but compare the numeric score using pytest.approx; ensure you
import pytest if not already present and apply the same change to the second
float assertion referenced in the comment.

Comment on lines +354 to +355
hits = adapter.pull_memory_for_context("greeting style", k=3)
assert isinstance(hits, list)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Roundtrip smoke test assertion is too weak to validate retrieval.

At Line 355, assert isinstance(hits, list) passes even when retrieval returns unrelated/empty results, so the “roundtrip” can silently regress.

💡 Suggested tightening of the integration assertion
 def test_real_mem0_roundtrip() -> None:
@@
     memory_id = adapter.push_correction(
@@
     assert memory_id is not None

     hits = adapter.pull_memory_for_context("greeting style", k=3)
-    assert isinstance(hits, list)
+    assert any(
+        isinstance(hit, dict) and "greeting" in str(hit.get("text", "")).lower()
+        for hit in hits
+    )

As per coding guidelines, tests/**: assertions check specific values not just truthiness.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
hits = adapter.pull_memory_for_context("greeting style", k=3)
assert isinstance(hits, list)
hits = adapter.pull_memory_for_context("greeting style", k=3)
assert any(
isinstance(hit, dict) and "greeting" in str(hit.get("text", "")).lower()
for hit in hits
)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_mem0_adapter.py` around lines 354 - 355, The current test only
asserts that hits is a list, which is too weak; update the test around
adapter.pull_memory_for_context("greeting style", k=3) to assert concrete
expectations from the roundtrip: check that hits is non-empty (len(hits) > 0),
that its length is <= 3, and that each hit has the expected structure/fields
(e.g., contains a "text" or "content" attribute or that the first item's text
contains "greeting" or matches the known sample inserted earlier). Use
adapter.pull_memory_for_context and the hits variable names to locate the test
and replace the loose isinstance check with these stronger assertions.

@Gradata Gradata merged commit b7092e7 into main Apr 15, 2026
17 of 18 checks passed
@Gradata Gradata deleted the wt-phase3-adapters-mem0 branch April 17, 2026 19:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant