Skip to main content

daimon_core/
error.rs

1//! Error types for the Daimon agent framework.
2
3use thiserror::Error;
4
5/// The central error type for all Daimon operations.
6///
7/// Provider crates should map their transport-specific errors
8/// (HTTP, gRPC, SDK) to [`DaimonError::Model`].
9#[derive(Error, Debug)]
10#[non_exhaustive]
11pub enum DaimonError {
12    /// An error originating from a model provider (API error, bad response, etc.).
13    #[error("model error: {0}")]
14    Model(String),
15
16    /// A tool failed during execution.
17    #[error("tool execution failed for '{tool}': {message}")]
18    ToolExecution {
19        /// Name of the tool that failed.
20        tool: String,
21        /// Description of the failure.
22        message: String,
23    },
24
25    /// The requested tool was not found in the registry.
26    #[error("tool '{0}' not found in registry")]
27    ToolNotFound(String),
28
29    /// Attempted to register a tool with a name that already exists.
30    #[error("duplicate tool '{0}' in registry")]
31    DuplicateTool(String),
32
33    /// The agent builder failed validation (e.g. missing required model).
34    #[error("agent builder validation failed: {0}")]
35    Builder(String),
36
37    /// The agent exceeded the configured maximum number of iterations.
38    #[error("max iterations ({0}) exceeded")]
39    MaxIterations(usize),
40
41    /// A serialization or deserialization error.
42    #[error("serialization error: {0}")]
43    Serialization(#[from] serde_json::Error),
44
45    /// Tool input failed JSON Schema validation.
46    #[error("schema validation failed for tool '{tool}': {errors}")]
47    SchemaValidation {
48        /// Name of the tool whose input failed validation.
49        tool: String,
50        /// Human-readable description of validation errors.
51        errors: String,
52    },
53
54    /// A stream was closed before completing.
55    #[error("stream closed unexpectedly")]
56    StreamClosed,
57
58    /// A request timed out.
59    #[error("request timed out after {0:?}")]
60    Timeout(std::time::Duration),
61
62    /// The operation was cancelled via a cancellation token.
63    #[error("operation cancelled")]
64    Cancelled,
65
66    /// An orchestration error (chain or graph execution failure).
67    #[error("orchestration error: {0}")]
68    Orchestration(String),
69
70    /// An MCP protocol error.
71    #[error("MCP error: {0}")]
72    Mcp(String),
73
74    /// The agent exceeded the configured spending budget.
75    #[error("budget exceeded: ${spent:.6} spent, limit was ${limit:.6}")]
76    BudgetExceeded {
77        /// How much has been spent so far (USD).
78        spent: f64,
79        /// The configured limit (USD).
80        limit: f64,
81    },
82
83    /// An input or output guardrail blocked the request.
84    #[error("guardrail blocked: {0}")]
85    GuardrailBlocked(String),
86
87    /// A storage backend operation failed (checkpoint store, memory backend,
88    /// broker state, or another persistence layer).
89    ///
90    /// `transient` distinguishes failures that may succeed on retry
91    /// (connection refused, timeout, I/O contention) from permanent ones
92    /// (corrupt data, invalid keys, misconfiguration), so callers can decide
93    /// whether retrying is worthwhile without string-matching the message.
94    #[error("storage error: {message}")]
95    Storage {
96        /// Description of the storage failure.
97        message: String,
98        /// `true` when retrying the operation may succeed.
99        transient: bool,
100    },
101
102    /// A catch-all for other errors.
103    #[error("{0}")]
104    Other(String),
105}
106
107impl DaimonError {
108    /// Creates a permanent (non-retryable) [`DaimonError::Storage`] error,
109    /// e.g. corrupt persisted data or an invalid storage key.
110    pub fn storage(message: impl Into<String>) -> Self {
111        Self::Storage {
112            message: message.into(),
113            transient: false,
114        }
115    }
116
117    /// Creates a transient (retryable) [`DaimonError::Storage`] error,
118    /// e.g. a connection failure, timeout, or I/O error.
119    pub fn storage_transient(message: impl Into<String>) -> Self {
120        Self::Storage {
121            message: message.into(),
122            transient: true,
123        }
124    }
125}
126
127/// A type alias for `Result<T, DaimonError>`.
128pub type Result<T> = std::result::Result<T, DaimonError>;
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133
134    #[test]
135    fn test_storage_constructor_is_permanent() {
136        let err = DaimonError::storage("corrupt checkpoint");
137        assert!(matches!(
138            err,
139            DaimonError::Storage {
140                transient: false,
141                ..
142            }
143        ));
144        assert_eq!(err.to_string(), "storage error: corrupt checkpoint");
145    }
146
147    #[test]
148    fn test_storage_transient_constructor_is_retryable() {
149        let err = DaimonError::storage_transient("connection refused");
150        assert!(matches!(
151            err,
152            DaimonError::Storage {
153                transient: true,
154                ..
155            }
156        ));
157        assert_eq!(err.to_string(), "storage error: connection refused");
158    }
159}