Error Handling
Learn how to handle errors and edge cases gracefully in your application. Proper error handling ensures a better user experience and helps with debugging.
The Memory Scope API uses standard HTTP status codes and returns structured error responses. SDKs provide typed exceptions for different error types.
Malformed request or invalid data
Action: Fix the request and retry
Invalid or missing API key
Action: Check API key configuration
Purpose not allowed for scope
Action: Use allowed purpose or different scope
Resource not found (e.g., invalid token)
Action: Check resource exists or create new
Too many requests
Action: Implement exponential backoff and retry
Unexpected server error
Action: Retry with exponential backoff
Comprehensive Error Handling
from memory_scope import MemoryScopeClient
from memory_scope.exceptions import (
PolicyDeniedError,
InvalidRequestError,
AuthenticationError,
NotFoundError,
RateLimitError
)
import time
import logging
logger = logging.getLogger(__name__)
def read_memory_with_error_handling(user_id, scope, domain, purpose, max_retries=3):
"""Read memory with comprehensive error handling"""
client = MemoryScopeClient(api_key=os.getenv("MEMORY_SCOPE_API_KEY"))
for attempt in range(max_retries):
try:
result = client.read_memory(
user_id=user_id,
scope=scope,
domain=domain,
purpose=purpose
)
return {"success": True, "data": result}
except PolicyDeniedError as e:
# Policy denial - expected in some cases
logger.info(f"Policy denied: {e.message}")
return {
"success": False,
"error": "policy_denied",
"message": "Access denied for this purpose",
"fallback": get_default_preferences()
}
except InvalidRequestError as e:
# Invalid request - fix and don't retry
logger.error(f"Invalid request: {e.message}")
return {
"success": False,
"error": "invalid_request",
"message": str(e),
"retry": False
}
except AuthenticationError as e:
# Auth error - don't retry, check configuration
logger.error(f"Authentication failed: {e.message}")
return {
"success": False,
"error": "authentication",
"message": "API key is invalid",
"retry": False
}
except NotFoundError as e:
# Not found - may be expected
logger.warning(f"Resource not found: {e.message}")
return {
"success": False,
"error": "not_found",
"message": str(e),
"retry": False
}
except RateLimitError as e:
# Rate limit - retry with backoff
if attempt < max_retries - 1:
wait_time = 2 ** attempt
logger.warning(f"Rate limit exceeded. Waiting {wait_time}s...")
time.sleep(wait_time)
continue
else:
logger.error("Rate limit exceeded. Max retries reached.")
return {
"success": False,
"error": "rate_limit",
"message": "Rate limit exceeded. Please try again later."
}
except Exception as e:
# Unexpected error - log and retry if appropriate
logger.error(f"Unexpected error: {e}", exc_info=True)
if attempt < max_retries - 1:
wait_time = 2 ** attempt
time.sleep(wait_time)
continue
else:
return {
"success": False,
"error": "unexpected",
"message": "An unexpected error occurred"
}
return {"success": False, "error": "max_retries", "message": "Max retries exceeded"}Policy denials are expected in some cases. They're not errors - they're a security feature. Handle them gracefully with fallback behavior.
try:
result = client.read_memory(
user_id="user123",
scope="preferences",
domain="food",
purpose="execute task to auto-order lunch"
)
except PolicyDeniedError:
# This is expected - preferences can't be used for task execution
# Use default behavior or ask user for permission
result = {
"summary_struct": {},
"confidence": 0.0
}
# Show message to user or use defaultsImplement exponential backoff when you receive a 429 response. Don't retry immediately.
import time
def read_with_retry(max_retries=3):
for attempt in range(max_retries):
try:
return client.read_memory(...)
except RateLimitError:
if attempt < max_retries - 1:
# Exponential backoff: 1s, 2s, 4s
wait_time = 2 ** attempt
time.sleep(wait_time)
else:
raise Exception("Rate limit exceeded")When no memories exist or access has been revoked, the API returns empty structures. Handle this gracefully.
result = client.read_memory(...)
# Check if data is available
if not result.summary_struct or len(result.summary_struct) == 0:
# No data available - handle gracefully
if result.confidence == 0.0:
# No memories exist or access revoked
show_message("No preferences available. Please provide your preferences.")
# Or use defaults
preferences = get_default_preferences()
else:
# Low confidence - incomplete data
preferences = result.summary_struct
show_message("Limited preferences available.")
else:
# Data available
preferences = result.summary_struct- Handle all error types appropriately
- Implement retry logic for transient errors (429, 500)
- Don't retry for client errors (400, 401, 403, 404)
- Log errors with sufficient context
- Provide user-friendly error messages
- Handle empty results gracefully
- Monitor error rates