diff --git a/src/iai_mcp/graph.py b/src/iai_mcp/graph.py index 0bba61c..4becfb5 100644 --- a/src/iai_mcp/graph.py +++ b/src/iai_mcp/graph.py @@ -42,6 +42,7 @@ class MemoryGraph: def __init__(self) -> None: self._nx: nx.Graph = nx.Graph() self._ig: "ig.Graph | None" = None + self._ig_stale: bool = False self._attrs: dict[UUID, dict[str, Any]] = {} self._backend: str = "networkx" @@ -79,10 +80,11 @@ class MemoryGraph: self._nx.add_edge( str(src), str(dst), weight=weight, edge_type=edge_type ) - if self._ig is not None: - # igraph mirror is immutable by topology; rebuild after each edge - # write while in igraph backend. Cheap enough at Phase-1 scale. - self._rebuild_igraph() + self._maybe_switch_backend() + if self._backend == "igraph": + # Lazy rebuild: igraph mirror is stale after edge writes + # and will be rebuilt on next read. + self._ig_stale = True # ------------------------------------------------------ backend switching @@ -105,11 +107,17 @@ class MemoryGraph: weights = [ float(self._nx[u][v].get("weight", 1.0)) for u, v in self._nx.edges() ] - g = ig.Graph(n=len(nodes), edges=edges, directed=False) + g = ig.Graph(n=len(nodes), edges=edges, directed=False) # type: ignore[unbound] g.vs["name"] = nodes if weights: g.es["weight"] = weights self._ig = g + self._ig_stale = False + + def _ensure_igraph(self) -> None: + """Rebuild igraph mirror if stale from deferred edge writes.""" + if self._ig_stale and _HAS_IGRAPH: + self._rebuild_igraph() # ---------------------------------------------------------- graph metrics @@ -124,6 +132,7 @@ class MemoryGraph: bc = nx.betweenness_centrality(self._nx, weight="weight") return {UUID(n): float(c) for n, c in bc.items()} # igraph path + self._ensure_igraph() assert self._ig is not None has_weight = "weight" in self._ig.es.attributes() raw = self._ig.betweenness(weights="weight" if has_weight else None)