From b7739a479444bcfb498ecfd5d60917086f3f9ec2 Mon Sep 17 00:00:00 2001 From: AndyMik90 Date: Sat, 14 Feb 2026 20:48:20 +0100 Subject: [PATCH 1/6] =?UTF-8?q?fix(memory):=20complete=20FalkorDB=20?= =?UTF-8?q?=E2=86=92=20LadybugDB=20migration=20in=20memory=20system?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem The memory system was still checking for FalkorDB imports in `config.py`, causing it to always report as unavailable and fall back to file-based storage, despite LadybugDB being the configured and installed database. Error in logs: ``` Graphiti packages not installed: falkordb is required for FalkorDri... ``` ## Root Cause In `get_graphiti_status()` at line 638, the code tried to import: ```python from graphiti_core.driver.falkordb_driver import FalkorDriver ``` This import failed because FalkorDB was removed when migrating to LadybugDB. ## Changes Made ### Priority 1 — Critical Bug Fix **File**: `apps/backend/integrations/graphiti/config.py` (lines 634-646) - Replaced FalkorDB import check with LadybugDB/kuzu import check - Now tries `real_ladybug` first (Python 3.12+), falls back to `kuzu` - Removed unreachable pragma: no cover comment (line now executes) ### Priority 2 — Test Infrastructure Updates 1. **`conftest.py`** (lines 84-103) - Renamed fixture: `mock_falkor_driver` → `mock_kuzu_driver` - Updated docstring and patch path to reference KuzuDriver 2. **`test_config.py`** (lines 1056-1070, 1092-1094) - Updated test to reflect new behavior: `available=True` when packages installed, even with embedder validation errors (embedder is optional) - Updated comment from "falkordb" to "LadybugDB/kuzu" 3. **`test_memory.py`** (lines 267, 278) - Updated variable name: `mock_falkordb_driver` → `mock_kuzu_driver` - Updated sys.modules patch path to use kuzu_driver instead of falkordb_driver ### Priority 3 — Documentation Updates 4. **`test_memory_facade.py`** (line 163) - Updated comment: "remote FalkorDB" → "remote database" 5. **`spec_runner.py`** (line 139) - Updated example: "FalkorDB" → "LadybugDB" ## Testing All 670 graphiti tests pass: ``` apps/backend/.venv/bin/pytest apps/backend/integrations/graphiti/tests/ -v ========== 670 passed, 6 skipped, 112 deselected, 4 warnings in 2.10s ========== ``` ## Impact - Memory system now correctly detects LadybugDB as available - No more false negatives causing fallback to file-based storage - All existing functionality preserved - No breaking changes Co-Authored-By: Claude Opus 4.6 --- apps/backend/integrations/graphiti/config.py | 9 ++++++--- .../integrations/graphiti/tests/conftest.py | 8 ++++---- .../graphiti/tests/test_config.py | 19 +++++++++++-------- .../graphiti/tests/test_memory.py | 4 ++-- .../graphiti/tests/test_memory_facade.py | 2 +- apps/backend/runners/spec_runner.py | 2 +- 6 files changed, 25 insertions(+), 19 deletions(-) diff --git a/apps/backend/integrations/graphiti/config.py b/apps/backend/integrations/graphiti/config.py index 903800cb..31e6dd9b 100644 --- a/apps/backend/integrations/graphiti/config.py +++ b/apps/backend/integrations/graphiti/config.py @@ -635,10 +635,13 @@ def get_graphiti_status() -> dict: try: # Attempt to import the main graphiti_memory module import graphiti_core # noqa: F401 - from graphiti_core.driver.falkordb_driver import FalkorDriver # noqa: F401 - # If we got here, packages are importable - status["available"] = True # pragma: no cover + # Try LadybugDB first (preferred for Python 3.12+), fall back to kuzu + try: + import real_ladybug # noqa: F401 + except ImportError: + import kuzu # noqa: F401 + status["available"] = True except ImportError as e: status["available"] = False status["reason"] = f"Graphiti packages not installed: {e}" diff --git a/apps/backend/integrations/graphiti/tests/conftest.py b/apps/backend/integrations/graphiti/tests/conftest.py index 852312e2..470b9ade 100644 --- a/apps/backend/integrations/graphiti/tests/conftest.py +++ b/apps/backend/integrations/graphiti/tests/conftest.py @@ -81,16 +81,16 @@ def mock_graphiti_core(): @pytest.fixture -def mock_falkor_driver(): - """Mock graphiti_core.driver.falkordb_driver.FalkorDriver. +def mock_kuzu_driver(): + """Mock graphiti_core.driver.kuzu_driver.KuzuDriver. - Prevents actual FalkorDB connections during tests. + Prevents actual LadybugDB/kuzu connections during tests. Yields: tuple: (mock_driver_class, mock_driver_instance) """ with patch( - "integrations.graphiti.queries_pkg.graphiti.graphiti_core.driver.falkordb_driver.FalkorDriver" + "integrations.graphiti.queries_pkg.graphiti.graphiti_core.driver.kuzu_driver.KuzuDriver" ) as mock_driver: mock_instance = MagicMock() mock_driver.return_value = mock_instance diff --git a/apps/backend/integrations/graphiti/tests/test_config.py b/apps/backend/integrations/graphiti/tests/test_config.py index 9deecd47..5802a058 100644 --- a/apps/backend/integrations/graphiti/tests/test_config.py +++ b/apps/backend/integrations/graphiti/tests/test_config.py @@ -1054,9 +1054,11 @@ class TestModuleLevelFunctions: assert "OPENAI_API_KEY" in status["errors"][0] def test_get_graphiti_status_invalid_config_sets_reason(self, clean_env): - """Test get_graphiti_status sets reason when config is invalid. + """Test get_graphiti_status with validation errors (embedder misconfigured). - This tests lines 628-629 where the reason is set from validation errors. + When packages are installed but embedder config has errors, available should + still be True (embedder is optional - keyword search fallback exists). + Validation errors are reported in the errors list for informational purposes. """ os.environ["GRAPHITI_ENABLED"] = "true" os.environ["GRAPHITI_EMBEDDER_PROVIDER"] = "voyage" @@ -1064,10 +1066,11 @@ class TestModuleLevelFunctions: status = get_graphiti_status() assert status["enabled"] is True - assert status["available"] is False - # When config is invalid, reason should be set from errors - assert status["reason"] != "" + # With LadybugDB/kuzu installed, available should be True + assert status["available"] is True + # Validation errors are informational (embedder is optional) assert len(status["errors"]) > 0 + assert "VOYAGE_API_KEY" in status["errors"][0] @pytest.mark.slow def test_get_graphiti_status_with_graphiti_installed(self, clean_env): @@ -1089,9 +1092,9 @@ class TestModuleLevelFunctions: assert "reason" in status assert "errors" in status - # Note: Line 641 (status["available"] = True) requires falkordb to be installed. - # Since falkordb is not installed in the test environment, that line is marked - # with pragma: no cover. The except clause (lines 642-644) is tested here. + # Note: Line 641 (status["available"] = True) requires LadybugDB/kuzu to be installed. + # Since LadybugDB/kuzu may not be installed in all test environments, that line + # may be marked with pragma: no cover. The except clause is tested here. def test_get_available_providers_empty(self, clean_env): """Test get_available_providers with no credentials.""" diff --git a/apps/backend/integrations/graphiti/tests/test_memory.py b/apps/backend/integrations/graphiti/tests/test_memory.py index 3e50bc1e..460c23da 100644 --- a/apps/backend/integrations/graphiti/tests/test_memory.py +++ b/apps/backend/integrations/graphiti/tests/test_memory.py @@ -264,7 +264,7 @@ class TestTestGraphitiConnection: # Mock graphiti_core imports to succeed mock_graphiti = MagicMock() - mock_falkordb_driver = MagicMock() + mock_kuzu_driver = MagicMock() # Mock provider creation to raise ProviderError with patch("graphiti_providers.create_llm_client") as mock_create_llm: @@ -275,7 +275,7 @@ class TestTestGraphitiConnection: { "graphiti_core": MagicMock(Graphiti=mock_graphiti), "graphiti_core.driver": MagicMock(), - "graphiti_core.driver.falkordb_driver": mock_falkordb_driver, + "graphiti_core.driver.kuzu_driver": mock_kuzu_driver, "graphiti_providers": MagicMock( ProviderError=ProviderError, create_embedder=MagicMock(), diff --git a/apps/backend/integrations/graphiti/tests/test_memory_facade.py b/apps/backend/integrations/graphiti/tests/test_memory_facade.py index 9c7763c8..05af4078 100644 --- a/apps/backend/integrations/graphiti/tests/test_memory_facade.py +++ b/apps/backend/integrations/graphiti/tests/test_memory_facade.py @@ -160,7 +160,7 @@ class TestTestGraphitiConnection: """Tests for the test_graphiti_connection async function. Note: The function now uses embedded LadybugDB via patched KuzuDriver - instead of remote FalkorDB with host/port credentials. + instead of remote database with host/port credentials. """ @pytest.mark.asyncio diff --git a/apps/backend/runners/spec_runner.py b/apps/backend/runners/spec_runner.py index 70d6e755..1db2f8db 100644 --- a/apps/backend/runners/spec_runner.py +++ b/apps/backend/runners/spec_runner.py @@ -136,7 +136,7 @@ Examples: python spec_runner.py --task "Update text" --complexity simple # Complex integration (auto-detected) - python spec_runner.py --task "Add Graphiti memory integration with FalkorDB" + python spec_runner.py --task "Add Graphiti memory integration with LadybugDB" # Interactive mode python spec_runner.py --interactive From aaa3b7f5885cc2ce5e246efce2bf5a184008e1f9 Mon Sep 17 00:00:00 2001 From: AndyMik90 Date: Sat, 14 Feb 2026 20:57:29 +0100 Subject: [PATCH 2/6] fix: remove stale FalkorDB references from migration cleanup - Remove FalkorDB docker service reference from project_index.json (docker-compose.yml no longer exists) - Correct line number reference in test_config.py comment (line 644 not 641) Code review findings - no functional changes, just metadata cleanup. Co-Authored-By: Claude Opus 4.6 --- apps/backend/integrations/graphiti/tests/test_config.py | 2 +- apps/backend/runners/roadmap/project_index.json | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/apps/backend/integrations/graphiti/tests/test_config.py b/apps/backend/integrations/graphiti/tests/test_config.py index 5802a058..a164181e 100644 --- a/apps/backend/integrations/graphiti/tests/test_config.py +++ b/apps/backend/integrations/graphiti/tests/test_config.py @@ -1092,7 +1092,7 @@ class TestModuleLevelFunctions: assert "reason" in status assert "errors" in status - # Note: Line 641 (status["available"] = True) requires LadybugDB/kuzu to be installed. + # Note: Line 644 (status["available"] = True) requires LadybugDB/kuzu to be installed. # Since LadybugDB/kuzu may not be installed in all test environments, that line # may be marked with pragma: no cover. The except clause is tested here. diff --git a/apps/backend/runners/roadmap/project_index.json b/apps/backend/runners/roadmap/project_index.json index 965f9b9f..e3462a17 100644 --- a/apps/backend/runners/roadmap/project_index.json +++ b/apps/backend/runners/roadmap/project_index.json @@ -2,11 +2,6 @@ "project_root": "/Users/andremikalsen/Documents/Coding/autonomous-coding", "project_type": "single", "services": {}, - "infrastructure": { - "docker_compose": "docker-compose.yml", - "docker_services": [ - "falkordb" - ] - }, + "infrastructure": {}, "conventions": {} } From e61f4370a8137316c9de8dc7c6994adad36caab8 Mon Sep 17 00:00:00 2001 From: AndyMik90 Date: Sun, 15 Feb 2026 17:38:06 +0100 Subject: [PATCH 3/6] fix: address review findings for PR #1834 - Add nested try-except in config.py for clearer error messages when graph DB backend is missing - Mock imports in test_config.py to make test environment-independent - Ensure test passes regardless of whether graphiti_core/real_ladybug/kuzu are installed Resolves CodeRabbit and Gemini review comments on PR #1834. Co-Authored-By: Claude Opus 4.6 --- apps/backend/integrations/graphiti/config.py | 9 ++++++++- apps/backend/integrations/graphiti/tests/test_config.py | 9 +++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/apps/backend/integrations/graphiti/config.py b/apps/backend/integrations/graphiti/config.py index 31e6dd9b..b8078e67 100644 --- a/apps/backend/integrations/graphiti/config.py +++ b/apps/backend/integrations/graphiti/config.py @@ -640,7 +640,14 @@ def get_graphiti_status() -> dict: try: import real_ladybug # noqa: F401 except ImportError: - import kuzu # noqa: F401 + try: + import kuzu # noqa: F401 + except ImportError: + status["available"] = False + status["reason"] = ( + "Graph database backend not installed (need real_ladybug or kuzu)" + ) + return status status["available"] = True except ImportError as e: status["available"] = False diff --git a/apps/backend/integrations/graphiti/tests/test_config.py b/apps/backend/integrations/graphiti/tests/test_config.py index a164181e..f3305d15 100644 --- a/apps/backend/integrations/graphiti/tests/test_config.py +++ b/apps/backend/integrations/graphiti/tests/test_config.py @@ -15,7 +15,7 @@ Tests cover: import json import os from pathlib import Path -from unittest.mock import patch +from unittest.mock import MagicMock, patch import pytest from integrations.graphiti.config import ( @@ -1063,7 +1063,12 @@ class TestModuleLevelFunctions: os.environ["GRAPHITI_ENABLED"] = "true" os.environ["GRAPHITI_EMBEDDER_PROVIDER"] = "voyage" - status = get_graphiti_status() + # Mock imports to ensure test is independent of environment + with patch.dict( + "sys.modules", + {"graphiti_core": MagicMock(), "real_ladybug": MagicMock()}, + ): + status = get_graphiti_status() assert status["enabled"] is True # With LadybugDB/kuzu installed, available should be True From 36b3b29d9ec641243b5d7e0e94bf6f890460cadc Mon Sep 17 00:00:00 2001 From: AndyMik90 Date: Sun, 15 Feb 2026 20:28:22 +0100 Subject: [PATCH 4/6] test: add coverage for missing graph backend scenario Add test_get_graphiti_status_no_graph_backend to verify error handling when graphiti_core imports successfully but neither real_ladybug nor kuzu are available. This addresses CodeRabbit's recommendation to test the error path in config.py lines 645-650. Addresses CodeRabbit review comment on PR #1834. Co-Authored-By: Claude Opus 4.6 --- .../graphiti/tests/test_config.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/apps/backend/integrations/graphiti/tests/test_config.py b/apps/backend/integrations/graphiti/tests/test_config.py index f3305d15..edf514bd 100644 --- a/apps/backend/integrations/graphiti/tests/test_config.py +++ b/apps/backend/integrations/graphiti/tests/test_config.py @@ -1077,6 +1077,25 @@ class TestModuleLevelFunctions: assert len(status["errors"]) > 0 assert "VOYAGE_API_KEY" in status["errors"][0] + def test_get_graphiti_status_no_graph_backend(self, clean_env): + """Test get_graphiti_status when graphiti_core exists but no graph DB backend. + + This tests the error path in config.py lines 645-650 where graphiti_core + imports successfully but neither real_ladybug nor kuzu is available. + """ + os.environ["GRAPHITI_ENABLED"] = "true" + + # Mock graphiti_core as present, but ensure real_ladybug and kuzu are absent + with patch.dict( + "sys.modules", + {"graphiti_core": MagicMock(), "real_ladybug": None, "kuzu": None}, + ): + status = get_graphiti_status() + + assert status["enabled"] is True + assert status["available"] is False + assert "real_ladybug or kuzu" in status["reason"] + @pytest.mark.slow def test_get_graphiti_status_with_graphiti_installed(self, clean_env): """Test get_graphiti_status when Graphiti packages are installed. From 04d141f6be2555711f0dfbbcd46a880524eacf19 Mon Sep 17 00:00:00 2001 From: AndyMik90 Date: Tue, 17 Feb 2026 15:34:24 +0100 Subject: [PATCH 5/6] fix: make test_get_graphiti_status_invalid_config_sets_reason environment-independent Avoid hard-asserting status['available'] is True, which depends on sys.modules patching behavior. Instead check the key exists and branch on its value, consistent with neighboring tests in the same class. Co-Authored-By: Claude Opus 4.6 --- .../integrations/graphiti/tests/test_config.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/apps/backend/integrations/graphiti/tests/test_config.py b/apps/backend/integrations/graphiti/tests/test_config.py index edf514bd..8e7d12be 100644 --- a/apps/backend/integrations/graphiti/tests/test_config.py +++ b/apps/backend/integrations/graphiti/tests/test_config.py @@ -1071,11 +1071,17 @@ class TestModuleLevelFunctions: status = get_graphiti_status() assert status["enabled"] is True - # With LadybugDB/kuzu installed, available should be True - assert status["available"] is True - # Validation errors are informational (embedder is optional) - assert len(status["errors"]) > 0 - assert "VOYAGE_API_KEY" in status["errors"][0] + # available depends on whether mocked packages are resolved correctly; + # sys.modules patching should make imports succeed, but guard against + # environment quirks (consistent with test_get_graphiti_status_enabled) + assert "available" in status + if status["available"]: + # Mocked packages resolved - validation errors are informational + assert len(status["errors"]) > 0 + assert "VOYAGE_API_KEY" in status["errors"][0] + else: + # If mocking didn't take effect, just verify structure is correct + assert "reason" in status def test_get_graphiti_status_no_graph_backend(self, clean_env): """Test get_graphiti_status when graphiti_core exists but no graph DB backend. From 6f9a24176d4ed080087ade7c271085bf7c3e7ca9 Mon Sep 17 00:00:00 2001 From: AndyMik90 Date: Wed, 18 Feb 2026 09:48:29 +0100 Subject: [PATCH 6/6] fix: remove vacuous if/else guard in graphiti status test Replace conditional assertions with direct assertions since sys.modules patching deterministically makes available=True. The else-branch was dead code with a trivially-passing assertion. Co-Authored-By: Claude Opus 4.6 --- .../integrations/graphiti/tests/test_config.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/apps/backend/integrations/graphiti/tests/test_config.py b/apps/backend/integrations/graphiti/tests/test_config.py index 8e7d12be..88aa9631 100644 --- a/apps/backend/integrations/graphiti/tests/test_config.py +++ b/apps/backend/integrations/graphiti/tests/test_config.py @@ -1074,14 +1074,9 @@ class TestModuleLevelFunctions: # available depends on whether mocked packages are resolved correctly; # sys.modules patching should make imports succeed, but guard against # environment quirks (consistent with test_get_graphiti_status_enabled) - assert "available" in status - if status["available"]: - # Mocked packages resolved - validation errors are informational - assert len(status["errors"]) > 0 - assert "VOYAGE_API_KEY" in status["errors"][0] - else: - # If mocking didn't take effect, just verify structure is correct - assert "reason" in status + assert status["available"] is True + assert len(status["errors"]) > 0 + assert "VOYAGE_API_KEY" in status["errors"][0] def test_get_graphiti_status_no_graph_backend(self, clean_env): """Test get_graphiti_status when graphiti_core exists but no graph DB backend.