Skip to main content

velesdb_memory/
error.rs

1//! Error type for the memory layer.
2
3use velesdb_core::agent::AgentMemoryError;
4use velesdb_core::Error as CoreError;
5
6use crate::embedder::EmbedError;
7use crate::extract::ExtractError;
8
9/// The transport-neutral class of a [`MemoryError`] — the single source of
10/// truth every adapter maps onto its own error channel (JSON-RPC code, napi
11/// status, `PyO3` exception type), so the taxonomy can never drift between them.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum ErrorCategory {
14    /// The caller supplied bad input (empty fact, reserved key, malformed
15    /// filter) — a 4xx-style fault.
16    InvalidInput,
17    /// A referenced memory id does not exist.
18    NotFound,
19    /// An internal storage / embedding / extraction failure — a 5xx-style fault.
20    Internal,
21}
22
23/// Errors returned by [`crate::service::MemoryService`].
24#[derive(Debug, thiserror::Error)]
25pub enum MemoryError {
26    /// Failure in the underlying `VelesDB` storage engine.
27    #[error("storage error: {0}")]
28    Storage(#[from] CoreError),
29
30    /// Failure in the Agent Memory SDK.
31    #[error("memory error: {0}")]
32    Memory(#[from] AgentMemoryError),
33
34    /// A fact was empty or whitespace-only.
35    #[error("fact text must not be empty")]
36    EmptyFact,
37
38    /// A `remember` link or a `relate` endpoint referenced a memory id that
39    /// does not exist.
40    #[error("memory {0} does not exist")]
41    UnknownMemory(u64),
42
43    /// Caller metadata or a recall filter named a reserved key (`content` or a
44    /// `_veles_`-prefixed system key), which callers may not set or filter on.
45    #[error("metadata key '{0}' is reserved")]
46    ReservedKey(String),
47
48    /// Failure producing a text embedding.
49    #[error("embedding error: {0}")]
50    Embed(#[from] EmbedError),
51
52    /// Failure extracting facts from raw text in
53    /// [`crate::service::MemoryService::remember_extracted`].
54    #[error("extraction error: {0}")]
55    Extract(#[from] ExtractError),
56
57    /// A fused-recall filter referenced a field name that is not a plain
58    /// identifier, named a reserved key, or carried a non-scalar value.
59    #[error("invalid filter field: {0}")]
60    InvalidFilter(String),
61
62    /// A relation label supplied to [`crate::service::MemoryService::relate`] or
63    /// a [`crate::model::Link`] in
64    /// [`crate::service::MemoryService::remember`] was invalid — empty, too long,
65    /// or contained non-printable characters.
66    #[error("invalid relation label: {0}")]
67    InvalidRelation(String),
68}
69
70impl MemoryError {
71    /// Classify this error into a transport-neutral [`ErrorCategory`]. Adapters
72    /// map the *category*, not the variant, so the client-facing taxonomy stays
73    /// identical across the MCP server and every binding.
74    #[must_use]
75    pub fn category(&self) -> ErrorCategory {
76        match self {
77            Self::EmptyFact
78            | Self::ReservedKey(_)
79            | Self::InvalidFilter(_)
80            | Self::InvalidRelation(_) => ErrorCategory::InvalidInput,
81            Self::UnknownMemory(_) => ErrorCategory::NotFound,
82            Self::Storage(_) | Self::Memory(_) | Self::Embed(_) | Self::Extract(_) => {
83                ErrorCategory::Internal
84            }
85        }
86    }
87}