Skip to main content

llm_agent_runtime/
error.rs

1//! # Unified error type for the agent-runtime crate.
2//!
3//! ## Responsibility
4//! Provide a single, typed error enum that covers all subsystems:
5//! memory, graph, orchestration, and the ReAct agent loop.
6//!
7//! ## Guarantees
8//! - Every variant is named and carries structured context
9//! - Implements `std::error::Error` via `thiserror`
10//! - Safe to send across thread/task boundaries (`Send + Sync`)
11//! - Never panics
12
13/// Unified error type returned by all public `agent-runtime` APIs.
14#[derive(Debug, thiserror::Error)]
15pub enum AgentRuntimeError {
16    /// A memory subsystem operation failed (episodic, semantic, or working memory).
17    #[error("Memory operation failed: {0}")]
18    Memory(String),
19
20    /// A graph subsystem operation failed (entity/relationship management or traversal).
21    #[error("Graph operation failed: {0}")]
22    Graph(String),
23
24    /// The orchestration pipeline or one of its stages failed.
25    #[error("Orchestration failed: {0}")]
26    Orchestration(String),
27
28    /// The ReAct agent loop encountered an unrecoverable error.
29    #[error("Agent loop error: {0}")]
30    AgentLoop(String),
31
32    /// The runtime was used before a required subsystem was configured.
33    #[error("Runtime not configured: missing '{0}'")]
34    NotConfigured(&'static str),
35
36    /// Circuit breaker is open — fast-fail without attempting the operation.
37    #[error("Circuit breaker open for '{service}'")]
38    CircuitOpen {
39        /// Name of the service whose circuit breaker is open.
40        service: String,
41    },
42
43    /// Backpressure threshold exceeded — caller must shed or wait.
44    #[error("Backpressure threshold exceeded: queue depth {depth}/{capacity}")]
45    BackpressureShed {
46        /// Current in-flight request count at the time of rejection.
47        depth: usize,
48        /// Maximum allowed in-flight request count.
49        capacity: usize,
50    },
51
52    /// A deduplication key collision was detected.
53    #[error("Deduplication key collision: {key}")]
54    DeduplicationConflict {
55        /// The duplicated request key.
56        key: String,
57    },
58
59    /// An LLM provider call failed.
60    #[error("Provider error: {0}")]
61    Provider(String),
62
63    /// A persistence operation failed.
64    #[error("Persistence error: {0}")]
65    Persistence(String),
66
67    /// A tool argument validation failed.
68    #[error("Validation failed for field '{field}': [{code}] {message}")]
69    Validation {
70        /// The argument field that failed validation.
71        field: String,
72        /// A short machine-readable code (e.g. "out_of_range", "pattern_mismatch").
73        code: String,
74        /// Human-readable description of the failure.
75        message: String,
76    },
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    #[test]
84    fn test_memory_error_display() {
85        let e = AgentRuntimeError::Memory("store full".into());
86        assert_eq!(e.to_string(), "Memory operation failed: store full");
87    }
88
89    #[test]
90    fn test_graph_error_display() {
91        let e = AgentRuntimeError::Graph("entity not found".into());
92        assert_eq!(e.to_string(), "Graph operation failed: entity not found");
93    }
94
95    #[test]
96    fn test_orchestration_error_display() {
97        let e = AgentRuntimeError::Orchestration("pipeline stalled".into());
98        assert_eq!(e.to_string(), "Orchestration failed: pipeline stalled");
99    }
100
101    #[test]
102    fn test_agent_loop_error_display() {
103        let e = AgentRuntimeError::AgentLoop("max iterations".into());
104        assert_eq!(e.to_string(), "Agent loop error: max iterations");
105    }
106
107    #[test]
108    fn test_not_configured_error_display() {
109        let e = AgentRuntimeError::NotConfigured("memory");
110        assert_eq!(e.to_string(), "Runtime not configured: missing 'memory'");
111    }
112
113    #[test]
114    fn test_circuit_open_error_display() {
115        let e = AgentRuntimeError::CircuitOpen {
116            service: "llm-api".into(),
117        };
118        assert_eq!(e.to_string(), "Circuit breaker open for 'llm-api'");
119    }
120
121    #[test]
122    fn test_backpressure_shed_error_display() {
123        let e = AgentRuntimeError::BackpressureShed {
124            depth: 100,
125            capacity: 100,
126        };
127        assert_eq!(
128            e.to_string(),
129            "Backpressure threshold exceeded: queue depth 100/100"
130        );
131    }
132
133    #[test]
134    fn test_deduplication_conflict_display() {
135        let e = AgentRuntimeError::DeduplicationConflict {
136            key: "abc123".into(),
137        };
138        assert_eq!(e.to_string(), "Deduplication key collision: abc123");
139    }
140
141    #[test]
142    fn test_error_is_send_sync() {
143        fn assert_send_sync<T: Send + Sync>() {}
144        assert_send_sync::<AgentRuntimeError>();
145    }
146
147    #[test]
148    fn test_error_debug_format() {
149        let e = AgentRuntimeError::Memory("test".into());
150        let debug = format!("{:?}", e);
151        assert!(debug.contains("Memory"));
152    }
153
154    #[test]
155    fn test_validation_error_display() {
156        let e = AgentRuntimeError::Validation {
157            field: "n".into(),
158            code: "out_of_range".into(),
159            message: "n must be between 1 and 100".into(),
160        };
161        assert_eq!(
162            e.to_string(),
163            "Validation failed for field 'n': [out_of_range] n must be between 1 and 100"
164        );
165    }
166}