Debug Kanban Memory & Add Sentry Monitoring (#1380)
* auto-claude: subtask-1-1 - Add debug logging to capture memory initialization state Added comprehensive debug logging to memory_manager.py to reveal if _ensure_initialized() is failing silently. This captures: - PRE-INIT STATE: Memory instance details before any save attempt - is_enabled, is_initialized, group_id - Internal component states (client, queries, search) - Config validation state (is_valid, providers, database) - State object details (initialized, episode_count, errors) - PRE-SAVE CHECK: Initialization state right before save method - Logs whether _ensure_initialized() will be called - POST-SAVE CHECK: State after save method returns - Confirms if initialization actually happened - Shows save result and component states This logging will reveal the root cause of kanban task memory save failures by showing exactly what state the memory system is in during the save flow. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * auto-claude: subtask-2-1 - Update graphiti_helpers.py to add explicit initialize() call - Made get_graphiti_memory() async function - Added await memory.initialize() call following GitHub pattern - Updated save_to_graphiti_async() to await the async helper - Added proper error handling for initialization failures Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * auto-claude: subtask-2-2 - Update memory_manager.py to use async get_graphiti_memory Updated both get_graphiti_context() and save_session_memory() to properly await the now-async get_graphiti_memory() helper, which initializes the GraphitiMemory instance internally before returning. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * auto-claude: subtask-3-1 - Add Sentry capture_exception to GraphitiClient ini * auto-claude: subtask-3-2 - Add Sentry capture_exception to GraphitiMemory class - Import capture_exception from core.sentry - Add Sentry tracking to initialize() method for initialization failures - Add try/except with capture_exception to all save_* methods: - save_session_insights - save_codebase_discoveries - save_pattern - save_gotcha - save_task_outcome - save_structured_insights - Add try/except with capture_exception to all get_* methods: - get_relevant_context - get_session_history - get_similar_task_outcomes - get_patterns_and_gotchas - Include relevant context (component, operation, etc.) with each capture Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * auto-claude: subtask-3-3 - Add Sentry capture_exception to GraphitiQueries class Track all episode save failures with operation type and content summary: - add_session_insight: tracks group_id, spec_id, session_number - add_codebase_discoveries: tracks group_id, spec_id, discovery_count - add_pattern: tracks group_id, spec_id, content_summary - add_gotcha: tracks group_id, spec_id, content_summary - add_task_outcome: tracks group_id, spec_id, task_id, success, content_summary - add_structured_insights: tracks group_id, spec_id, content_summary (insight types) * auto-claude: subtask-3-4 - Add Sentry capture_exception to GraphitiSearch class - Import capture_exception from core.sentry - Add Sentry tracking to get_relevant_context with query_summary and group_id - Add Sentry tracking to get_session_history with group_id context - Add Sentry tracking to get_similar_task_outcomes with query_summary and group_id - Add Sentry tracking to get_patterns_and_gotchas with query_summary and group_id - All exception handlers now include operation name for better error grouping Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * auto-claude: subtask-4-1 - Add Sentry capture_exception to graphiti_helpers.py Add Sentry error tracking to Graphiti helper functions: - Import capture_exception from core.sentry - Track get_graphiti_memory failures with spec_dir and project_dir context - Track save_to_graphiti_async failures with spec_dir, session_num context - Track connection close failures with context information Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * auto-claude: subtask-4-2 - Add Sentry capture_exception to memory_manager.py. * auto-claude: subtask-5-1 - Create test script for memory save verification * fix: resolve ruff lint and format errors - Fix import block sorting (I001) in graphiti.py and test_memory_save.py - Fix f-string without placeholders (F541) in test_memory_save.py - Apply ruff formatting to 4 files Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(memory): await async get_graphiti_memory calls to prevent AttributeError The get_graphiti_memory function was changed to async but call sites in patterns.py, codebase_map.py, and tools/memory.py were not updated. This caused graphiti variables to be coroutine objects instead of GraphitiMemory instances, resulting in AttributeError when calling methods like save_gotcha() or save_pattern(). - Wrap sync callers with run_async() helper - Add await for async caller in _save_to_graphiti_async - Add graphiti.close() calls to prevent connection leaks Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(memory): fix run_async returning Future in async context - Fix ASYNC-004: run_async() now returns None when called from async context instead of returning a Future. This prevents AttributeError when callers try to use the Future as the actual result. - This fixes ASYNC-001, ASYNC-002, ASYNC-003 in codebase_map.py and patterns.py since they already check `if graphiti:` which will be False for None. - Close the coroutine when in async context to avoid "coroutine was never awaited" warning. - Remove investigation debug logging from memory_manager.py (CMT-001) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: StillKnotKnown <192589389+StillKnotKnown@users.noreply.github.com>
This commit is contained in:
@@ -10,6 +10,7 @@ Handles session memory storage using dual-layer approach:
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
from core.sentry import capture_exception
|
||||
from debug import (
|
||||
debug,
|
||||
debug_detailed,
|
||||
@@ -116,12 +117,15 @@ async def get_graphiti_context(
|
||||
|
||||
memory = None
|
||||
try:
|
||||
# Use centralized helper for GraphitiMemory instantiation
|
||||
memory = get_graphiti_memory(spec_dir, project_dir)
|
||||
# Use centralized helper for GraphitiMemory instantiation (async)
|
||||
memory = await get_graphiti_memory(spec_dir, project_dir)
|
||||
if memory is None:
|
||||
if is_debug_enabled():
|
||||
debug_warning("memory", "GraphitiMemory not available")
|
||||
debug_warning(
|
||||
"memory", "GraphitiMemory not available for context retrieval"
|
||||
)
|
||||
return None
|
||||
|
||||
# Build search query from subtask description
|
||||
subtask_desc = subtask.get("description", "")
|
||||
subtask_id = subtask.get("id", "")
|
||||
@@ -228,6 +232,15 @@ async def get_graphiti_context(
|
||||
logger.warning(f"Failed to get Graphiti context: {e}")
|
||||
if is_debug_enabled():
|
||||
debug_error("memory", "Graphiti context retrieval failed", error=str(e))
|
||||
# Capture exception to Sentry with full context
|
||||
capture_exception(
|
||||
e,
|
||||
operation="get_graphiti_context",
|
||||
subtask_id=subtask.get("id", "unknown"),
|
||||
subtask_desc=subtask.get("description", "")[:200],
|
||||
spec_dir=str(spec_dir),
|
||||
project_dir=str(project_dir),
|
||||
)
|
||||
return None
|
||||
finally:
|
||||
# Always close the memory connection (swallow exceptions to avoid overriding)
|
||||
@@ -324,21 +337,16 @@ async def save_session_memory(
|
||||
|
||||
memory = None
|
||||
try:
|
||||
# Use centralized helper for GraphitiMemory instantiation
|
||||
memory = get_graphiti_memory(spec_dir, project_dir)
|
||||
# Use centralized helper for GraphitiMemory instantiation (async)
|
||||
memory = await get_graphiti_memory(spec_dir, project_dir)
|
||||
if memory is None:
|
||||
if is_debug_enabled():
|
||||
debug_warning("memory", "GraphitiMemory not available")
|
||||
# Continue to file-based fallback
|
||||
else:
|
||||
if is_debug_enabled():
|
||||
debug_detailed(
|
||||
debug(
|
||||
"memory",
|
||||
"GraphitiMemory instance created",
|
||||
is_enabled=memory.is_enabled,
|
||||
group_id=getattr(memory, "group_id", "unknown"),
|
||||
"get_graphiti_memory() returned None - this usually means Graphiti is disabled or provider config is invalid",
|
||||
)
|
||||
|
||||
# Continue to file-based fallback
|
||||
if memory is not None and memory.is_enabled:
|
||||
if is_debug_enabled():
|
||||
debug("memory", "Saving to Graphiti...")
|
||||
@@ -393,6 +401,17 @@ async def save_session_memory(
|
||||
logger.warning(f"Graphiti save failed: {e}, falling back to file-based")
|
||||
if is_debug_enabled():
|
||||
debug_error("memory", "Graphiti save failed", error=str(e))
|
||||
# Capture exception to Sentry with full context
|
||||
capture_exception(
|
||||
e,
|
||||
operation="save_session_memory_graphiti",
|
||||
subtask_id=subtask_id,
|
||||
session_num=session_num,
|
||||
success=success,
|
||||
subtasks_completed=subtasks_completed,
|
||||
spec_dir=str(spec_dir),
|
||||
project_dir=str(project_dir),
|
||||
)
|
||||
finally:
|
||||
# Always close the memory connection (swallow exceptions to avoid overriding)
|
||||
if memory is not None:
|
||||
@@ -438,6 +457,17 @@ async def save_session_memory(
|
||||
logger.error(f"File-based memory save also failed: {e}")
|
||||
if is_debug_enabled():
|
||||
debug_error("memory", "File-based memory save FAILED", error=str(e))
|
||||
# Capture exception to Sentry with full context
|
||||
capture_exception(
|
||||
e,
|
||||
operation="save_session_memory_file",
|
||||
subtask_id=subtask_id,
|
||||
session_num=session_num,
|
||||
success=success,
|
||||
subtasks_completed=subtasks_completed,
|
||||
spec_dir=str(spec_dir),
|
||||
project_dir=str(project_dir),
|
||||
)
|
||||
return False, "none"
|
||||
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ async def _save_to_graphiti_async(
|
||||
# The helper handles enablement checks internally
|
||||
from memory.graphiti_helpers import get_graphiti_memory
|
||||
|
||||
memory = get_graphiti_memory(spec_dir, project_dir)
|
||||
memory = await get_graphiti_memory(spec_dir, project_dir)
|
||||
if memory is None:
|
||||
return False
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import logging
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from core.sentry import capture_exception
|
||||
from graphiti_config import GraphitiConfig, GraphitiState
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -133,9 +134,23 @@ class GraphitiClient:
|
||||
)
|
||||
except ProviderNotInstalled as e:
|
||||
logger.warning(f"LLM provider packages not installed: {e}")
|
||||
capture_exception(
|
||||
e,
|
||||
error_type="ProviderNotInstalled",
|
||||
provider_type="llm",
|
||||
llm_provider=self.config.llm_provider,
|
||||
embedder_provider=self.config.embedder_provider,
|
||||
)
|
||||
return False
|
||||
except ProviderError as e:
|
||||
logger.warning(f"LLM provider configuration error: {e}")
|
||||
capture_exception(
|
||||
e,
|
||||
error_type="ProviderError",
|
||||
provider_type="llm",
|
||||
llm_provider=self.config.llm_provider,
|
||||
embedder_provider=self.config.embedder_provider,
|
||||
)
|
||||
return False
|
||||
|
||||
try:
|
||||
@@ -145,9 +160,23 @@ class GraphitiClient:
|
||||
)
|
||||
except ProviderNotInstalled as e:
|
||||
logger.warning(f"Embedder provider packages not installed: {e}")
|
||||
capture_exception(
|
||||
e,
|
||||
error_type="ProviderNotInstalled",
|
||||
provider_type="embedder",
|
||||
llm_provider=self.config.llm_provider,
|
||||
embedder_provider=self.config.embedder_provider,
|
||||
)
|
||||
return False
|
||||
except ProviderError as e:
|
||||
logger.warning(f"Embedder provider configuration error: {e}")
|
||||
capture_exception(
|
||||
e,
|
||||
error_type="ProviderError",
|
||||
provider_type="embedder",
|
||||
llm_provider=self.config.llm_provider,
|
||||
embedder_provider=self.config.embedder_provider,
|
||||
)
|
||||
return False
|
||||
|
||||
# Apply LadybugDB monkeypatch to use it via graphiti's KuzuDriver
|
||||
@@ -173,15 +202,36 @@ class GraphitiClient:
|
||||
logger.warning(
|
||||
f"Failed to initialize LadybugDB driver at {db_path}: {e}"
|
||||
)
|
||||
capture_exception(
|
||||
e,
|
||||
error_type=type(e).__name__,
|
||||
db_path=str(db_path),
|
||||
llm_provider=self.config.llm_provider,
|
||||
embedder_provider=self.config.embedder_provider,
|
||||
)
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"Unexpected error initializing LadybugDB driver at {db_path}: {e}"
|
||||
)
|
||||
capture_exception(
|
||||
e,
|
||||
error_type=type(e).__name__,
|
||||
db_path=str(db_path),
|
||||
llm_provider=self.config.llm_provider,
|
||||
embedder_provider=self.config.embedder_provider,
|
||||
)
|
||||
return False
|
||||
logger.info(f"Initialized LadybugDB driver (patched) at: {db_path}")
|
||||
except ImportError as e:
|
||||
logger.warning(f"KuzuDriver not available: {e}")
|
||||
capture_exception(
|
||||
e,
|
||||
error_type="ImportError",
|
||||
component="kuzu_driver_patched",
|
||||
llm_provider=self.config.llm_provider,
|
||||
embedder_provider=self.config.embedder_provider,
|
||||
)
|
||||
return False
|
||||
|
||||
# Initialize Graphiti with the custom providers
|
||||
@@ -216,10 +266,23 @@ class GraphitiClient:
|
||||
f"Graphiti packages not installed: {e}. "
|
||||
"Install with: pip install real_ladybug graphiti-core"
|
||||
)
|
||||
capture_exception(
|
||||
e,
|
||||
error_type="ImportError",
|
||||
component="graphiti_core",
|
||||
llm_provider=self.config.llm_provider,
|
||||
embedder_provider=self.config.embedder_provider,
|
||||
)
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to initialize Graphiti client: {e}")
|
||||
capture_exception(
|
||||
e,
|
||||
error_type=type(e).__name__,
|
||||
llm_provider=self.config.llm_provider,
|
||||
embedder_provider=self.config.embedder_provider,
|
||||
)
|
||||
return False
|
||||
|
||||
async def close(self) -> None:
|
||||
|
||||
@@ -13,6 +13,7 @@ import logging
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
from core.sentry import capture_exception
|
||||
from graphiti_config import GraphitiConfig, GraphitiState
|
||||
|
||||
from .client import GraphitiClient
|
||||
@@ -196,6 +197,13 @@ class GraphitiMemory:
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to initialize Graphiti: {e}")
|
||||
self._record_error(f"Initialization failed: {e}")
|
||||
capture_exception(
|
||||
e,
|
||||
component="graphiti",
|
||||
operation="initialize",
|
||||
group_id=self.group_id,
|
||||
group_id_mode=self.group_id_mode,
|
||||
)
|
||||
self._available = False
|
||||
return False
|
||||
|
||||
@@ -220,14 +228,25 @@ class GraphitiMemory:
|
||||
if not await self._ensure_initialized():
|
||||
return False
|
||||
|
||||
result = await self._queries.add_session_insight(session_num, insights)
|
||||
try:
|
||||
result = await self._queries.add_session_insight(session_num, insights)
|
||||
|
||||
if result and self.state:
|
||||
self.state.last_session = session_num
|
||||
self.state.episode_count += 1
|
||||
self.state.save(self.spec_dir)
|
||||
if result and self.state:
|
||||
self.state.last_session = session_num
|
||||
self.state.episode_count += 1
|
||||
self.state.save(self.spec_dir)
|
||||
|
||||
return result
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to save session insights: {e}")
|
||||
self._record_error(f"save_session_insights failed: {e}")
|
||||
capture_exception(
|
||||
e,
|
||||
component="graphiti",
|
||||
operation="save_session_insights",
|
||||
session_num=session_num,
|
||||
)
|
||||
return False
|
||||
|
||||
async def save_codebase_discoveries(
|
||||
self,
|
||||
@@ -237,39 +256,69 @@ class GraphitiMemory:
|
||||
if not await self._ensure_initialized():
|
||||
return False
|
||||
|
||||
result = await self._queries.add_codebase_discoveries(discoveries)
|
||||
try:
|
||||
result = await self._queries.add_codebase_discoveries(discoveries)
|
||||
|
||||
if result and self.state:
|
||||
self.state.episode_count += 1
|
||||
self.state.save(self.spec_dir)
|
||||
if result and self.state:
|
||||
self.state.episode_count += 1
|
||||
self.state.save(self.spec_dir)
|
||||
|
||||
return result
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to save codebase discoveries: {e}")
|
||||
self._record_error(f"save_codebase_discoveries failed: {e}")
|
||||
capture_exception(
|
||||
e,
|
||||
component="graphiti",
|
||||
operation="save_codebase_discoveries",
|
||||
)
|
||||
return False
|
||||
|
||||
async def save_pattern(self, pattern: str) -> bool:
|
||||
"""Save a code pattern to the knowledge graph."""
|
||||
if not await self._ensure_initialized():
|
||||
return False
|
||||
|
||||
result = await self._queries.add_pattern(pattern)
|
||||
try:
|
||||
result = await self._queries.add_pattern(pattern)
|
||||
|
||||
if result and self.state:
|
||||
self.state.episode_count += 1
|
||||
self.state.save(self.spec_dir)
|
||||
if result and self.state:
|
||||
self.state.episode_count += 1
|
||||
self.state.save(self.spec_dir)
|
||||
|
||||
return result
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to save pattern: {e}")
|
||||
self._record_error(f"save_pattern failed: {e}")
|
||||
capture_exception(
|
||||
e,
|
||||
component="graphiti",
|
||||
operation="save_pattern",
|
||||
)
|
||||
return False
|
||||
|
||||
async def save_gotcha(self, gotcha: str) -> bool:
|
||||
"""Save a gotcha (pitfall) to the knowledge graph."""
|
||||
if not await self._ensure_initialized():
|
||||
return False
|
||||
|
||||
result = await self._queries.add_gotcha(gotcha)
|
||||
try:
|
||||
result = await self._queries.add_gotcha(gotcha)
|
||||
|
||||
if result and self.state:
|
||||
self.state.episode_count += 1
|
||||
self.state.save(self.spec_dir)
|
||||
if result and self.state:
|
||||
self.state.episode_count += 1
|
||||
self.state.save(self.spec_dir)
|
||||
|
||||
return result
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to save gotcha: {e}")
|
||||
self._record_error(f"save_gotcha failed: {e}")
|
||||
capture_exception(
|
||||
e,
|
||||
component="graphiti",
|
||||
operation="save_gotcha",
|
||||
)
|
||||
return False
|
||||
|
||||
async def save_task_outcome(
|
||||
self,
|
||||
@@ -282,28 +331,49 @@ class GraphitiMemory:
|
||||
if not await self._ensure_initialized():
|
||||
return False
|
||||
|
||||
result = await self._queries.add_task_outcome(
|
||||
task_id, success, outcome, metadata
|
||||
)
|
||||
try:
|
||||
result = await self._queries.add_task_outcome(
|
||||
task_id, success, outcome, metadata
|
||||
)
|
||||
|
||||
if result and self.state:
|
||||
self.state.episode_count += 1
|
||||
self.state.save(self.spec_dir)
|
||||
if result and self.state:
|
||||
self.state.episode_count += 1
|
||||
self.state.save(self.spec_dir)
|
||||
|
||||
return result
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to save task outcome: {e}")
|
||||
self._record_error(f"save_task_outcome failed: {e}")
|
||||
capture_exception(
|
||||
e,
|
||||
component="graphiti",
|
||||
operation="save_task_outcome",
|
||||
task_id=task_id,
|
||||
)
|
||||
return False
|
||||
|
||||
async def save_structured_insights(self, insights: dict) -> bool:
|
||||
"""Save extracted insights as multiple focused episodes."""
|
||||
if not await self._ensure_initialized():
|
||||
return False
|
||||
|
||||
result = await self._queries.add_structured_insights(insights)
|
||||
try:
|
||||
result = await self._queries.add_structured_insights(insights)
|
||||
|
||||
if result and self.state:
|
||||
# Episode count updated in queries module
|
||||
pass
|
||||
if result and self.state:
|
||||
# Episode count updated in queries module
|
||||
pass
|
||||
|
||||
return result
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to save structured insights: {e}")
|
||||
self._record_error(f"save_structured_insights failed: {e}")
|
||||
capture_exception(
|
||||
e,
|
||||
component="graphiti",
|
||||
operation="save_structured_insights",
|
||||
)
|
||||
return False
|
||||
|
||||
# Delegate methods to search module
|
||||
|
||||
@@ -317,9 +387,19 @@ class GraphitiMemory:
|
||||
if not await self._ensure_initialized():
|
||||
return []
|
||||
|
||||
return await self._search.get_relevant_context(
|
||||
query, num_results, include_project_context
|
||||
)
|
||||
try:
|
||||
return await self._search.get_relevant_context(
|
||||
query, num_results, include_project_context
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to get relevant context: {e}")
|
||||
self._record_error(f"get_relevant_context failed: {e}")
|
||||
capture_exception(
|
||||
e,
|
||||
component="graphiti",
|
||||
operation="get_relevant_context",
|
||||
)
|
||||
return []
|
||||
|
||||
async def get_session_history(
|
||||
self,
|
||||
@@ -330,7 +410,17 @@ class GraphitiMemory:
|
||||
if not await self._ensure_initialized():
|
||||
return []
|
||||
|
||||
return await self._search.get_session_history(limit, spec_only)
|
||||
try:
|
||||
return await self._search.get_session_history(limit, spec_only)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to get session history: {e}")
|
||||
self._record_error(f"get_session_history failed: {e}")
|
||||
capture_exception(
|
||||
e,
|
||||
component="graphiti",
|
||||
operation="get_session_history",
|
||||
)
|
||||
return []
|
||||
|
||||
async def get_similar_task_outcomes(
|
||||
self,
|
||||
@@ -341,7 +431,17 @@ class GraphitiMemory:
|
||||
if not await self._ensure_initialized():
|
||||
return []
|
||||
|
||||
return await self._search.get_similar_task_outcomes(task_description, limit)
|
||||
try:
|
||||
return await self._search.get_similar_task_outcomes(task_description, limit)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to get similar task outcomes: {e}")
|
||||
self._record_error(f"get_similar_task_outcomes failed: {e}")
|
||||
capture_exception(
|
||||
e,
|
||||
component="graphiti",
|
||||
operation="get_similar_task_outcomes",
|
||||
)
|
||||
return []
|
||||
|
||||
async def get_patterns_and_gotchas(
|
||||
self,
|
||||
@@ -367,9 +467,19 @@ class GraphitiMemory:
|
||||
if not await self._ensure_initialized():
|
||||
return [], []
|
||||
|
||||
return await self._search.get_patterns_and_gotchas(
|
||||
query, num_results, min_score
|
||||
)
|
||||
try:
|
||||
return await self._search.get_patterns_and_gotchas(
|
||||
query, num_results, min_score
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to get patterns and gotchas: {e}")
|
||||
self._record_error(f"get_patterns_and_gotchas failed: {e}")
|
||||
capture_exception(
|
||||
e,
|
||||
component="graphiti",
|
||||
operation="get_patterns_and_gotchas",
|
||||
)
|
||||
return [], []
|
||||
|
||||
# Status and utility methods
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import json
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from core.sentry import capture_exception
|
||||
|
||||
from .schema import (
|
||||
EPISODE_TYPE_CODEBASE_DISCOVERY,
|
||||
EPISODE_TYPE_GOTCHA,
|
||||
@@ -82,6 +84,13 @@ class GraphitiQueries:
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to save session insights: {e}")
|
||||
capture_exception(
|
||||
e,
|
||||
operation="add_session_insight",
|
||||
group_id=self.group_id,
|
||||
spec_id=self.spec_context_id,
|
||||
session_number=session_num,
|
||||
)
|
||||
return False
|
||||
|
||||
async def add_codebase_discoveries(
|
||||
@@ -124,6 +133,13 @@ class GraphitiQueries:
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to save codebase discoveries: {e}")
|
||||
capture_exception(
|
||||
e,
|
||||
operation="add_codebase_discoveries",
|
||||
group_id=self.group_id,
|
||||
spec_id=self.spec_context_id,
|
||||
discovery_count=len(discoveries),
|
||||
)
|
||||
return False
|
||||
|
||||
async def add_pattern(self, pattern: str) -> bool:
|
||||
@@ -160,6 +176,13 @@ class GraphitiQueries:
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to save pattern: {e}")
|
||||
capture_exception(
|
||||
e,
|
||||
operation="add_pattern",
|
||||
group_id=self.group_id,
|
||||
spec_id=self.spec_context_id,
|
||||
content_summary=pattern[:100] if pattern else "",
|
||||
)
|
||||
return False
|
||||
|
||||
async def add_gotcha(self, gotcha: str) -> bool:
|
||||
@@ -196,6 +219,13 @@ class GraphitiQueries:
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to save gotcha: {e}")
|
||||
capture_exception(
|
||||
e,
|
||||
operation="add_gotcha",
|
||||
group_id=self.group_id,
|
||||
spec_id=self.spec_context_id,
|
||||
content_summary=gotcha[:100] if gotcha else "",
|
||||
)
|
||||
return False
|
||||
|
||||
async def add_task_outcome(
|
||||
@@ -245,6 +275,15 @@ class GraphitiQueries:
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to save task outcome: {e}")
|
||||
capture_exception(
|
||||
e,
|
||||
operation="add_task_outcome",
|
||||
group_id=self.group_id,
|
||||
spec_id=self.spec_context_id,
|
||||
task_id=task_id,
|
||||
success=success,
|
||||
content_summary=outcome[:100] if outcome else "",
|
||||
)
|
||||
return False
|
||||
|
||||
async def add_structured_insights(self, insights: dict) -> bool:
|
||||
@@ -459,4 +498,26 @@ class GraphitiQueries:
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to save structured insights: {e}")
|
||||
# Build content summary of insight types
|
||||
insight_types = []
|
||||
if insights.get("file_insights"):
|
||||
insight_types.append(f"files:{len(insights['file_insights'])}")
|
||||
if insights.get("patterns_discovered"):
|
||||
insight_types.append(f"patterns:{len(insights['patterns_discovered'])}")
|
||||
if insights.get("gotchas_discovered"):
|
||||
insight_types.append(f"gotchas:{len(insights['gotchas_discovered'])}")
|
||||
if insights.get("approach_outcome"):
|
||||
insight_types.append("outcome:1")
|
||||
if insights.get("recommendations"):
|
||||
insight_types.append(
|
||||
f"recommendations:{len(insights['recommendations'])}"
|
||||
)
|
||||
|
||||
capture_exception(
|
||||
e,
|
||||
operation="add_structured_insights",
|
||||
group_id=self.group_id,
|
||||
spec_id=self.spec_context_id,
|
||||
content_summary=", ".join(insight_types) if insight_types else "empty",
|
||||
)
|
||||
return False
|
||||
|
||||
@@ -9,6 +9,8 @@ import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
from core.sentry import capture_exception
|
||||
|
||||
from .schema import (
|
||||
EPISODE_TYPE_GOTCHA,
|
||||
EPISODE_TYPE_PATTERN,
|
||||
@@ -120,6 +122,12 @@ class GraphitiSearch:
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to search context: {e}")
|
||||
capture_exception(
|
||||
e,
|
||||
query_summary=query[:100] if query else "",
|
||||
group_id=self.group_id,
|
||||
operation="get_relevant_context",
|
||||
)
|
||||
return []
|
||||
|
||||
async def get_session_history(
|
||||
@@ -174,6 +182,11 @@ class GraphitiSearch:
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to get session history: {e}")
|
||||
capture_exception(
|
||||
e,
|
||||
group_id=self.group_id,
|
||||
operation="get_session_history",
|
||||
)
|
||||
return []
|
||||
|
||||
async def get_similar_task_outcomes(
|
||||
@@ -227,6 +240,12 @@ class GraphitiSearch:
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to get similar task outcomes: {e}")
|
||||
capture_exception(
|
||||
e,
|
||||
query_summary=task_description[:100] if task_description else "",
|
||||
group_id=self.group_id,
|
||||
operation="get_similar_task_outcomes",
|
||||
)
|
||||
return []
|
||||
|
||||
async def get_patterns_and_gotchas(
|
||||
@@ -337,4 +356,10 @@ class GraphitiSearch:
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to get patterns/gotchas: {e}")
|
||||
capture_exception(
|
||||
e,
|
||||
query_summary=query[:100] if query else "",
|
||||
group_id=self.group_id,
|
||||
operation="get_patterns_and_gotchas",
|
||||
)
|
||||
return [], []
|
||||
|
||||
@@ -64,9 +64,10 @@ def update_codebase_map(spec_dir: Path, discoveries: dict[str, str]) -> None:
|
||||
# Also save to Graphiti if enabled
|
||||
if is_graphiti_memory_enabled() and discoveries:
|
||||
try:
|
||||
graphiti = get_graphiti_memory(spec_dir)
|
||||
graphiti = run_async(get_graphiti_memory(spec_dir))
|
||||
if graphiti:
|
||||
run_async(graphiti.save_codebase_discoveries(discoveries))
|
||||
run_async(graphiti.close())
|
||||
logger.info("Codebase discoveries also saved to Graphiti")
|
||||
except Exception as e:
|
||||
logger.warning(f"Graphiti codebase save failed: {e}")
|
||||
|
||||
@@ -12,6 +12,8 @@ import logging
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from core.sentry import capture_exception
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -37,18 +39,22 @@ def is_graphiti_memory_enabled() -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def get_graphiti_memory(
|
||||
async def get_graphiti_memory(
|
||||
spec_dir: Path, project_dir: Path | None = None
|
||||
) -> "GraphitiMemory | None":
|
||||
"""
|
||||
Get a GraphitiMemory instance if available.
|
||||
Get an initialized GraphitiMemory instance if available.
|
||||
|
||||
Args:
|
||||
spec_dir: Spec directory
|
||||
project_dir: Project root directory (defaults to spec_dir.parent.parent)
|
||||
|
||||
Returns:
|
||||
GraphitiMemory instance or None if not available
|
||||
Initialized GraphitiMemory instance or None if not available
|
||||
|
||||
Note:
|
||||
This function is async and calls initialize() on the memory instance
|
||||
before returning, following the GitHub pattern for proper initialization.
|
||||
"""
|
||||
if not is_graphiti_memory_enabled():
|
||||
return None
|
||||
@@ -59,29 +65,54 @@ def get_graphiti_memory(
|
||||
if project_dir is None:
|
||||
project_dir = spec_dir.parent.parent
|
||||
# Use project-wide shared memory for cross-spec learning
|
||||
return GraphitiMemory(spec_dir, project_dir, group_id_mode=GroupIdMode.PROJECT)
|
||||
memory = GraphitiMemory(
|
||||
spec_dir, project_dir, group_id_mode=GroupIdMode.PROJECT
|
||||
)
|
||||
|
||||
# Initialize the memory instance (following GitHub pattern)
|
||||
await memory.initialize()
|
||||
|
||||
return memory
|
||||
except ImportError:
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to initialize Graphiti memory: {e}")
|
||||
capture_exception(
|
||||
e,
|
||||
function="get_graphiti_memory",
|
||||
spec_dir=str(spec_dir),
|
||||
project_dir=str(project_dir) if project_dir else None,
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
def run_async(coro):
|
||||
"""
|
||||
Run an async coroutine synchronously.
|
||||
|
||||
Handles the case where we're already in an event loop.
|
||||
NOTE: This should only be called from synchronous code. For async callers,
|
||||
use the async function directly with await to ensure proper execution.
|
||||
|
||||
Args:
|
||||
coro: Async coroutine to run
|
||||
|
||||
Returns:
|
||||
Result of the coroutine or a Future if already in event loop
|
||||
Result of the coroutine, or None if already in an async context
|
||||
"""
|
||||
try:
|
||||
loop = asyncio.get_running_loop()
|
||||
# Already in an event loop - create a task
|
||||
return asyncio.ensure_future(coro)
|
||||
asyncio.get_running_loop()
|
||||
# Already in an async context - caller should use await directly
|
||||
# Log a warning and return None to avoid returning a Future that
|
||||
# callers would incorrectly try to use as the actual result
|
||||
logger.warning(
|
||||
"run_async called from async context. "
|
||||
"Use await directly for proper execution."
|
||||
)
|
||||
# Close the coroutine to avoid "coroutine was never awaited" warning
|
||||
coro.close()
|
||||
return None
|
||||
except RuntimeError:
|
||||
# No event loop running - create one
|
||||
# No event loop running - safe to create one
|
||||
return asyncio.run(coro)
|
||||
|
||||
|
||||
@@ -105,7 +136,7 @@ async def save_to_graphiti_async(
|
||||
Returns:
|
||||
True if save succeeded, False otherwise
|
||||
"""
|
||||
graphiti = get_graphiti_memory(spec_dir, project_dir)
|
||||
graphiti = await get_graphiti_memory(spec_dir, project_dir)
|
||||
if graphiti is None:
|
||||
return False
|
||||
|
||||
@@ -130,13 +161,27 @@ async def save_to_graphiti_async(
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to save to Graphiti: {e}")
|
||||
capture_exception(
|
||||
e,
|
||||
function="save_to_graphiti_async",
|
||||
spec_dir=str(spec_dir),
|
||||
session_num=session_num,
|
||||
project_dir=str(project_dir) if project_dir else None,
|
||||
)
|
||||
return False
|
||||
finally:
|
||||
# Always close the graphiti connection (swallow exceptions to avoid overriding)
|
||||
if graphiti is not None:
|
||||
try:
|
||||
await graphiti.close()
|
||||
except Exception as e:
|
||||
except Exception as close_error:
|
||||
logger.debug(
|
||||
"Failed to close Graphiti memory connection", exc_info=True
|
||||
)
|
||||
capture_exception(
|
||||
close_error,
|
||||
function="save_to_graphiti_async",
|
||||
context="closing_connection",
|
||||
spec_dir=str(spec_dir),
|
||||
session_num=session_num,
|
||||
)
|
||||
|
||||
@@ -57,9 +57,10 @@ def append_gotcha(spec_dir: Path, gotcha: str) -> None:
|
||||
# Also save to Graphiti if enabled
|
||||
if is_graphiti_memory_enabled():
|
||||
try:
|
||||
graphiti = get_graphiti_memory(spec_dir)
|
||||
graphiti = run_async(get_graphiti_memory(spec_dir))
|
||||
if graphiti:
|
||||
run_async(graphiti.save_gotcha(gotcha_stripped))
|
||||
run_async(graphiti.close())
|
||||
except Exception as e:
|
||||
logger.warning(f"Graphiti gotcha save failed: {e}")
|
||||
|
||||
@@ -133,9 +134,10 @@ def append_pattern(spec_dir: Path, pattern: str) -> None:
|
||||
# Also save to Graphiti if enabled
|
||||
if is_graphiti_memory_enabled():
|
||||
try:
|
||||
graphiti = get_graphiti_memory(spec_dir)
|
||||
graphiti = run_async(get_graphiti_memory(spec_dir))
|
||||
if graphiti:
|
||||
run_async(graphiti.save_pattern(pattern_stripped))
|
||||
run_async(graphiti.close())
|
||||
except Exception as e:
|
||||
logger.warning(f"Graphiti pattern save failed: {e}")
|
||||
|
||||
|
||||
@@ -0,0 +1,379 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Memory Save Verification Script
|
||||
================================
|
||||
|
||||
Tests the memory save functionality with Graphiti enabled.
|
||||
Run with DEBUG=true SENTRY_DEV=true to verify Sentry events.
|
||||
|
||||
Usage:
|
||||
cd apps/backend
|
||||
DEBUG=true python scripts/test_memory_save.py
|
||||
|
||||
# With Sentry enabled (for Sentry event verification):
|
||||
DEBUG=true SENTRY_DEV=true python scripts/test_memory_save.py
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
# Add the backend directory to the path so we can import modules
|
||||
SCRIPT_DIR = Path(__file__).resolve().parent
|
||||
BACKEND_DIR = SCRIPT_DIR.parent
|
||||
if str(BACKEND_DIR) not in sys.path:
|
||||
sys.path.insert(0, str(BACKEND_DIR))
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG if os.environ.get("DEBUG") else logging.INFO,
|
||||
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def test_memory_imports():
|
||||
"""Test that all memory-related imports work correctly."""
|
||||
print("\n=== Testing Memory System Imports ===")
|
||||
|
||||
errors = []
|
||||
|
||||
# Test memory_manager imports
|
||||
try:
|
||||
from agents.memory_manager import (
|
||||
debug_memory_system_status,
|
||||
get_graphiti_context,
|
||||
save_session_memory,
|
||||
)
|
||||
|
||||
print("[OK] agents.memory_manager imports successful")
|
||||
except ImportError as e:
|
||||
errors.append(f"agents.memory_manager: {e}")
|
||||
print(f"[FAIL] agents.memory_manager: {e}")
|
||||
|
||||
# Test graphiti_helpers imports
|
||||
try:
|
||||
from memory.graphiti_helpers import (
|
||||
get_graphiti_memory,
|
||||
is_graphiti_memory_enabled,
|
||||
save_to_graphiti_async,
|
||||
)
|
||||
|
||||
print("[OK] memory.graphiti_helpers imports successful")
|
||||
except ImportError as e:
|
||||
errors.append(f"memory.graphiti_helpers: {e}")
|
||||
print(f"[FAIL] memory.graphiti_helpers: {e}")
|
||||
|
||||
# Test graphiti_config imports
|
||||
try:
|
||||
from graphiti_config import (
|
||||
get_graphiti_status,
|
||||
is_graphiti_enabled,
|
||||
)
|
||||
|
||||
print("[OK] graphiti_config imports successful")
|
||||
except ImportError as e:
|
||||
errors.append(f"graphiti_config: {e}")
|
||||
print(f"[FAIL] graphiti_config: {e}")
|
||||
|
||||
# Test sentry imports
|
||||
try:
|
||||
from core.sentry import (
|
||||
capture_exception,
|
||||
capture_message,
|
||||
init_sentry,
|
||||
)
|
||||
from core.sentry import (
|
||||
is_enabled as sentry_is_enabled,
|
||||
)
|
||||
|
||||
print("[OK] core.sentry imports successful")
|
||||
except ImportError as e:
|
||||
errors.append(f"core.sentry: {e}")
|
||||
print(f"[FAIL] core.sentry: {e}")
|
||||
|
||||
# Test graphiti queries_pkg imports
|
||||
try:
|
||||
from integrations.graphiti.queries_pkg.client import GraphitiClient
|
||||
from integrations.graphiti.queries_pkg.graphiti import GraphitiMemory
|
||||
from integrations.graphiti.queries_pkg.queries import GraphitiQueries
|
||||
from integrations.graphiti.queries_pkg.search import GraphitiSearch
|
||||
|
||||
print("[OK] integrations.graphiti.queries_pkg imports successful")
|
||||
except ImportError as e:
|
||||
errors.append(f"integrations.graphiti.queries_pkg: {e}")
|
||||
print(f"[FAIL] integrations.graphiti.queries_pkg: {e}")
|
||||
|
||||
if errors:
|
||||
print(f"\n[FAIL] {len(errors)} import error(s) found")
|
||||
return False
|
||||
else:
|
||||
print("\n[OK] All imports successful")
|
||||
return True
|
||||
|
||||
|
||||
async def test_graphiti_status():
|
||||
"""Test Graphiti configuration status."""
|
||||
print("\n=== Testing Graphiti Status ===")
|
||||
|
||||
try:
|
||||
from graphiti_config import get_graphiti_status, is_graphiti_enabled
|
||||
|
||||
enabled = is_graphiti_enabled()
|
||||
status = get_graphiti_status()
|
||||
|
||||
print(f"Graphiti Enabled: {enabled}")
|
||||
print(f"Graphiti Available: {status.get('available')}")
|
||||
print(f" Host: {status.get('host')}")
|
||||
print(f" Port: {status.get('port')}")
|
||||
print(f" Database: {status.get('database')}")
|
||||
print(f" LLM Provider: {status.get('llm_provider')}")
|
||||
print(f" Embedder Provider: {status.get('embedder_provider')}")
|
||||
|
||||
if not status.get("available"):
|
||||
print(f" Reason: {status.get('reason')}")
|
||||
print(f" Errors: {status.get('errors')}")
|
||||
|
||||
return enabled
|
||||
except Exception as e:
|
||||
print(f"[FAIL] Error checking Graphiti status: {e}")
|
||||
return False
|
||||
|
||||
|
||||
async def test_sentry_status():
|
||||
"""Test Sentry configuration status.
|
||||
|
||||
Returns True if:
|
||||
- Sentry is enabled and ready, OR
|
||||
- Sentry is properly disabled due to configuration (no DSN, not dev mode, SDK not installed)
|
||||
|
||||
Only returns False if there's an unexpected error.
|
||||
"""
|
||||
print("\n=== Testing Sentry Status ===")
|
||||
|
||||
try:
|
||||
from core.sentry import init_sentry, is_enabled, is_initialized
|
||||
|
||||
# Check if SENTRY_DEV is set
|
||||
sentry_dev = os.environ.get("SENTRY_DEV", "").lower() in ("true", "1", "yes")
|
||||
sentry_dsn = os.environ.get("SENTRY_DSN", "")
|
||||
|
||||
print(f"SENTRY_DSN set: {bool(sentry_dsn)}")
|
||||
print(f"SENTRY_DEV: {sentry_dev}")
|
||||
|
||||
# Initialize Sentry
|
||||
init_sentry(component="memory-test")
|
||||
|
||||
print(f"Sentry Initialized: {is_initialized()}")
|
||||
print(f"Sentry Enabled: {is_enabled()}")
|
||||
|
||||
if is_enabled():
|
||||
print("[OK] Sentry is enabled and ready to capture events")
|
||||
else:
|
||||
# Sentry being disabled is OK - it just means configuration requires it
|
||||
if not sentry_dsn:
|
||||
print("[INFO] Sentry disabled - no SENTRY_DSN configured (expected)")
|
||||
elif not sentry_dev:
|
||||
print(
|
||||
"[INFO] Sentry disabled in dev mode - set SENTRY_DEV=true to enable"
|
||||
)
|
||||
else:
|
||||
print("[INFO] Sentry disabled - sentry-sdk may not be installed")
|
||||
print(
|
||||
"[OK] Sentry integration configured correctly (disabled by configuration)"
|
||||
)
|
||||
|
||||
# Return True even if disabled - we're testing that the integration works,
|
||||
# not that Sentry is necessarily enabled
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"[FAIL] Error checking Sentry status: {e}")
|
||||
return False
|
||||
|
||||
|
||||
async def test_memory_save_flow():
|
||||
"""Test the memory save flow end-to-end."""
|
||||
print("\n=== Testing Memory Save Flow ===")
|
||||
|
||||
# Create temporary directories for testing
|
||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||
tmp_path = Path(tmp_dir)
|
||||
spec_dir = tmp_path / "test_spec"
|
||||
spec_dir.mkdir(parents=True)
|
||||
project_dir = tmp_path / "project"
|
||||
project_dir.mkdir(parents=True)
|
||||
|
||||
print(f"Test spec_dir: {spec_dir}")
|
||||
print(f"Test project_dir: {project_dir}")
|
||||
|
||||
try:
|
||||
from agents.memory_manager import save_session_memory
|
||||
|
||||
# Test memory save with sample data
|
||||
subtask_id = "test-subtask-1"
|
||||
session_num = 1
|
||||
success = True
|
||||
subtasks_completed = ["test-subtask-1"]
|
||||
discoveries = {
|
||||
"files_understood": {"test.py": "Test file for memory verification"},
|
||||
"patterns_found": ["Test pattern: Always verify imports"],
|
||||
"gotchas_encountered": ["Test gotcha: Check async/await usage"],
|
||||
}
|
||||
|
||||
print("\nSaving test session memory...")
|
||||
print(f" subtask_id: {subtask_id}")
|
||||
print(f" session_num: {session_num}")
|
||||
print(f" success: {success}")
|
||||
|
||||
result, storage_type = await save_session_memory(
|
||||
spec_dir=spec_dir,
|
||||
project_dir=project_dir,
|
||||
subtask_id=subtask_id,
|
||||
session_num=session_num,
|
||||
success=success,
|
||||
subtasks_completed=subtasks_completed,
|
||||
discoveries=discoveries,
|
||||
)
|
||||
|
||||
print("\nMemory Save Result:")
|
||||
print(f" Success: {result}")
|
||||
print(f" Storage Type: {storage_type}")
|
||||
|
||||
if result:
|
||||
print(f"[OK] Memory save succeeded using {storage_type} storage")
|
||||
|
||||
# Verify file was created if file-based
|
||||
if storage_type == "file":
|
||||
memory_file = (
|
||||
spec_dir
|
||||
/ "memory"
|
||||
/ "session_insights"
|
||||
/ f"session_{session_num:03d}.json"
|
||||
)
|
||||
if memory_file.exists():
|
||||
print(f"[OK] Memory file created: {memory_file}")
|
||||
else:
|
||||
print(f"[WARN] Memory file not found: {memory_file}")
|
||||
|
||||
return True
|
||||
else:
|
||||
print("[FAIL] Memory save failed")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"[FAIL] Error during memory save test: {e}")
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
|
||||
# Test Sentry capture
|
||||
try:
|
||||
from core.sentry import capture_exception, is_enabled
|
||||
|
||||
if is_enabled():
|
||||
capture_exception(
|
||||
e,
|
||||
operation="test_memory_save",
|
||||
context="verification_script",
|
||||
)
|
||||
print("[INFO] Exception captured to Sentry")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
|
||||
async def test_sentry_capture():
|
||||
"""Test that Sentry capture works (only if Sentry is enabled)."""
|
||||
print("\n=== Testing Sentry Capture ===")
|
||||
|
||||
try:
|
||||
from core.sentry import (
|
||||
capture_exception,
|
||||
capture_message,
|
||||
is_enabled,
|
||||
)
|
||||
|
||||
if not is_enabled():
|
||||
print("[SKIP] Sentry not enabled - skipping capture test")
|
||||
print(" Set SENTRY_DSN and SENTRY_DEV=true to test Sentry capture")
|
||||
return True
|
||||
|
||||
# Test capture_message
|
||||
print("Sending test message to Sentry...")
|
||||
capture_message(
|
||||
"Memory save verification script test message",
|
||||
level="info",
|
||||
test_type="verification",
|
||||
component="memory-test",
|
||||
)
|
||||
print("[OK] Test message sent to Sentry")
|
||||
|
||||
# Test capture_exception
|
||||
print("Sending test exception to Sentry...")
|
||||
try:
|
||||
raise ValueError("Test exception for memory save verification")
|
||||
except Exception as e:
|
||||
capture_exception(
|
||||
e,
|
||||
operation="test_exception",
|
||||
context="verification_script",
|
||||
)
|
||||
print("[OK] Test exception sent to Sentry")
|
||||
|
||||
print("\n[INFO] Check your Sentry dashboard for the test events")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"[FAIL] Error testing Sentry capture: {e}")
|
||||
return False
|
||||
|
||||
|
||||
async def main():
|
||||
"""Run all memory save verification tests."""
|
||||
print("=" * 60)
|
||||
print("Memory Save Verification Script")
|
||||
print("=" * 60)
|
||||
print(f"DEBUG: {os.environ.get('DEBUG', 'not set')}")
|
||||
print(f"SENTRY_DEV: {os.environ.get('SENTRY_DEV', 'not set')}")
|
||||
print(f"GRAPHITI_ENABLED: {os.environ.get('GRAPHITI_ENABLED', 'not set')}")
|
||||
|
||||
results = {}
|
||||
|
||||
# Run tests
|
||||
results["imports"] = await test_memory_imports()
|
||||
results["graphiti_status"] = await test_graphiti_status()
|
||||
results["sentry_status"] = await test_sentry_status()
|
||||
results["memory_save"] = await test_memory_save_flow()
|
||||
results["sentry_capture"] = await test_sentry_capture()
|
||||
|
||||
# Summary
|
||||
print("\n" + "=" * 60)
|
||||
print("Test Summary")
|
||||
print("=" * 60)
|
||||
|
||||
passed = 0
|
||||
failed = 0
|
||||
for test_name, result in results.items():
|
||||
status = "[OK]" if result else "[FAIL]"
|
||||
print(f" {status} {test_name}")
|
||||
if result:
|
||||
passed += 1
|
||||
else:
|
||||
failed += 1
|
||||
|
||||
print(f"\nTotal: {passed} passed, {failed} failed")
|
||||
|
||||
if failed > 0:
|
||||
print("\n[FAIL] Some tests failed")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("\n[OK] All tests passed")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user