Skip to content

FalkorDB: writes mutate the shared driver (self.driver = self.driver.clone(database=group_id)) — reads then answer from the *last-written* group's graph, silently returning empty for other groups #1795

Description

@drcollect

Summary

On the FalkorDB backend, each group gets its own graph, and add_episode /
add_triplet permanently switch the shared Graphiti client's driver to the
group they just wrote:

  • graphiti_core/graphiti.py:1081self.driver = self.driver.clone(database=group_id)
  • graphiti_core/graphiti.py:1309 — same in the triplet path
    (line numbers from main @ 993e081a; behavior identical in 0.29.2)

Any long-lived multi-group client — the MCP server is exactly that — then runs
all subsequent reads inside whichever group wrote last. get_episodes /
searches for another group execute in the wrong graph with a
group_id property filter that matches nothing, and return empty with no
error. Scoped memory appears to work until a second group writes; after that,
cross-group reads silently return nothing.

Environment

  • graphiti-core 0.29.2 (code path unchanged on current main @ 993e081a)
  • MCP server (streamable HTTP), FalkorDB v4.20.4, falkordb-py 1.2.0

Reproduction

  1. One MCP server / one Graphiti client, FalkorDB backend.
  2. add_memory(..., group_id="a") and wait for it to land.
  3. get_episodes(group_ids="a") → returns the episode (driver sits on a).
  4. add_memory(..., group_id="b") (or just let queued group-b work run).
  5. get_episodes(group_ids="a")empty, though the data exists.

Evidence

Captured live with redis-cli MONITOR while calling
get_episodes(group_ids="quarantine") right after group helpscout-sync had
been written:

GRAPH.QUERY "helpscout-sync" "MATCH (e:Episodic) WHERE e.group_id IN $group_ids ..."

— the Episodic query for group quarantine executed against the
helpscout-sync graph. Direct inspection confirmed the data exists where it
was written:

GRAPH.QUERY quarantine "MATCH (e:Episodic) RETURN e.name"  → smoke-temp-fix

Two related design problems

  1. Mutating shared state: clone() returning a rerouted driver is fine;
    assigning it to self.driver on a shared client is the bug. Reads have no
    corresponding per-call routing.
  2. Per-group graphs make group_ids lists unsatisfiable: a search with
    group_ids=["a","b"] can only ever execute inside one graph, so
    cross-group search silently degrades to single-group even when routing is
    "correct".

Suggested fix

Route per operation instead of mutating: use a local
driver = self.driver.clone(database=group_id) for writes, and route reads by
their requested group_ids. For the list-search case, either fan out across
graphs and merge, or offer a single-graph mode for FalkorDB in which all
groups live in the configured graph and scoping relies on the existing
group_id property filters (this is how the Neo4j path already behaves).

We deployed the single-graph variant locally (make FalkorDriver.clone() a
no-op so _database stays fixed) and all semantics — scalar reads after
interleaved writes, list searches, isolation between groups — behave correctly
since. The 0.29.3 "namespace operations routing" fixes appear related but do
not cover this.

Workaround

Pin all groups to one graph by neutering FalkorDriver.clone() (return
self), accepting the loss of graph-level isolation; scoping via the
group_id property filters already present in every query.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions