File size: 4,708 Bytes
2cf7040
 
 
 
 
 
 
 
b2101ae
2cf7040
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
"""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 for cap / tier-related errors. Kept here as a string
# literal so the exceptions module has no dependency on _capcheck (avoids the
# circular import that motivated the v0.4.0 reorganization).
_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."
    )