| """Typed exception hierarchy for sibyl-memory-client. |
| |
| Every error has a stable `code` for programmatic handling and a `recovery` |
| string suggesting what the caller should try next. |
| |
| v0.4.0 (2026-05-18): `CapExceededError` + `TierVerificationError` relocated |
| here from `_capcheck.py` so they are importable from the canonical |
| `sibyl_memory_client.exceptions` submodule path (KAPPA bug report against |
| sibyl-memory-mcp 0.1.1: server imported these from `.exceptions` but they |
| only lived on `._capcheck`). |
| """ |
| from __future__ import annotations |
|
|
|
|
| |
| |
| |
| _DEFAULT_UPGRADE_URL = "https://docs.sibyllabs.org/memory/tiers" |
|
|
|
|
| class SibylMemoryError(Exception): |
| """Base for all sibyl-memory-client errors.""" |
|
|
| code: str = "SIBYL_MEMORY_ERROR" |
| recovery: str = "See exception message for details." |
|
|
| def __init__(self, message: str, *, recovery: str | None = None) -> None: |
| super().__init__(message) |
| if recovery is not None: |
| self.recovery = recovery |
|
|
|
|
| class StorageError(SibylMemoryError): |
| code = "STORAGE_ERROR" |
| recovery = "Check disk space and file permissions on ~/.sibyl-memory/." |
|
|
|
|
| class SchemaError(SibylMemoryError): |
| code = "SCHEMA_ERROR" |
| recovery = "The schema file is missing or corrupt. Re-install sibyl-memory-client." |
|
|
|
|
| class TenantError(SibylMemoryError): |
| code = "TENANT_ERROR" |
| recovery = "Set a tenant before calling write/read operations: client.set_tenant(uuid)." |
|
|
|
|
| class NotFoundError(SibylMemoryError): |
| code = "NOT_FOUND" |
| recovery = "The requested entity / state / reference does not exist." |
|
|
|
|
| class ConflictError(SibylMemoryError): |
| code = "CONFLICT" |
| recovery = "An entity with this (tenant_id, category, name) already exists. Use update_entity() instead." |
|
|
|
|
| class ValidationError(SibylMemoryError): |
| code = "VALIDATION_ERROR" |
| recovery = "Body must be a JSON-serializable dict / list / primitive." |
|
|
|
|
| class TierGateError(SibylMemoryError): |
| """Raised when a free-tier user invokes a paid-tier-only feature. |
| |
| Carries the user's current tier + an upgrade URL so callers can render |
| a clean prompt. Self-learning + memory linter are both gated by this |
| on the free tier; upgrading to any paid tier unlocks both. |
| """ |
|
|
| code = "TIER_GATE" |
| recovery = ( |
| "Upgrade your plugin tier to unlock this feature. See " |
| "https://sibyllabs.org/plugin#tier for options " |
| "(Sibyl Stake / Sync / Lifetime / Enterprise)." |
| ) |
|
|
| def __init__( |
| self, |
| message: str, |
| *, |
| feature: str, |
| current_tier: str = "free", |
| upgrade_url: str = "https://sibyllabs.org/plugin#tier", |
| ) -> None: |
| super().__init__(message) |
| self.feature = feature |
| self.current_tier = current_tier |
| self.upgrade_url = upgrade_url |
|
|
|
|
| class CapExceededError(SibylMemoryError): |
| """Raised when a free-tier user tries to write past the 2 MB cap. |
| |
| Carries the upgrade URL so callers (CLIs, IDEs, agent frameworks) can |
| render a clean upgrade prompt. |
| |
| v0.4.0: moved from `_capcheck.py` to `exceptions.py` so the canonical |
| `sibyl_memory_client.exceptions` submodule path exports it. The class |
| contract (code, recovery, current_size, cap, proposed_delta, upgrade_url |
| attributes) is unchanged. |
| """ |
|
|
| code = "CAP_EXCEEDED" |
| recovery = ( |
| "Upgrade to remove the 2 MB cap. See " |
| "https://docs.sibyllabs.org/memory/tiers for options " |
| "(Sibyl Stake / Sync / Lifetime / Enterprise)." |
| ) |
|
|
| def __init__( |
| self, |
| message: str, |
| *, |
| current_size: int, |
| cap: int, |
| proposed_delta: int = 0, |
| upgrade_url: str = _DEFAULT_UPGRADE_URL, |
| ) -> None: |
| super().__init__(message) |
| self.current_size = current_size |
| self.cap = cap |
| self.proposed_delta = proposed_delta |
| self.upgrade_url = upgrade_url |
|
|
|
|
| class TierVerificationError(SibylMemoryError): |
| """Raised when the SDK can't verify the user's tier and has no cached |
| grace period to fall back on (offline at the cap with no recent |
| successful check). |
| |
| v0.4.0: moved from `_capcheck.py` to `exceptions.py` so the canonical |
| `sibyl_memory_client.exceptions` submodule path exports it. The class |
| contract (code, recovery) is unchanged. |
| """ |
|
|
| code = "TIER_VERIFY_FAILED" |
| recovery = ( |
| "Connect to the internet so the SDK can verify your account, or " |
| "stay under the 2 MB free-tier cap until you're online." |
| ) |
|
|