Skip to main content

agent_sdk_core/domain/
error.rs

1//! Domain primitives for stable SDK vocabulary. Use these items for IDs, refs,
2//! policy, privacy, trust, and errors that cross crate or host boundaries. They are
3//! data-only and must not perform provider, filesystem, network, or UI side effects.
4//! This file contains the error portion of that contract.
5//!
6use crate::domain::{
7    AttemptId, DestinationRef, EntityRef, EventId, PolicyRef, PrivacyClass, RunId, SourceRef,
8    SpanId, ToolCallId, TurnId,
9};
10use serde::{Deserialize, Serialize};
11use thiserror::Error;
12
13#[derive(Clone, Debug, Deserialize, Error, Eq, PartialEq, Serialize)]
14/// Enumerates the finite agent error cases.
15/// Serialized names are part of the SDK contract; update fixtures when variants change.
16pub enum AgentError {
17    #[error("missing required field: {field}")]
18    /// Use this variant when the contract needs to represent missing required field; selecting it has no side effect by itself.
19    MissingRequiredField {
20        /// Field used by this record or request.
21        field: String,
22    },
23    #[error("contract violation: {message}")]
24    /// Use this variant when the contract needs to represent contract violation; selecting it has no side effect by itself.
25    ContractViolation {
26        /// Message used by this record or request.
27        message: String,
28    },
29    #[error("host configuration needed: {message}")]
30    /// Use this variant when the contract needs to represent host configuration needed; selecting it has no side effect by itself.
31    HostConfigurationNeeded {
32        /// Message used by this record or request.
33        message: String,
34    },
35    #[error("{kind:?}: {}", context.message)]
36    /// Use this variant when the contract needs to represent classified; selecting it has no side effect by itself.
37    Classified {
38        /// Kind/category for this record, capability, event, or detected
39        /// resource.
40        kind: AgentErrorKind,
41        /// Retry used by this record or request.
42        retry: RetryClassification,
43        /// Context used by this record or request.
44        context: Box<ErrorContext>,
45        /// Identifiers used to select or correlate causal values.
46        /// Use them for typed lookup, filtering, or lineage instead of stringly typed matching.
47        causal_ids: Box<CausalIds>,
48    },
49}
50
51impl AgentError {
52    /// Creates a new domain::error value with explicit caller-provided
53    /// inputs. This constructor is data-only and performs no I/O or
54    /// external side effects.
55    pub fn new(
56        kind: AgentErrorKind,
57        retry: RetryClassification,
58        message: impl Into<String>,
59    ) -> Self {
60        Self::Classified {
61            kind,
62            retry,
63            context: Box::new(ErrorContext::new(message)),
64            causal_ids: Box::default(),
65        }
66    }
67
68    /// Builds the kind value.
69    /// This is data construction and performs no I/O, journal append, event publication, or
70    /// process work.
71    pub fn kind(&self) -> AgentErrorKind {
72        match self {
73            Self::MissingRequiredField { .. } => AgentErrorKind::InvalidPackage,
74            Self::ContractViolation { .. } => AgentErrorKind::InvalidStateTransition,
75            Self::HostConfigurationNeeded { .. } => AgentErrorKind::HostConfigurationNeeded,
76            Self::Classified { kind, .. } => *kind,
77        }
78    }
79
80    /// Builds the retry value.
81    /// This is data construction and performs no I/O, journal append, event publication, or
82    /// process work.
83    pub fn retry(&self) -> RetryClassification {
84        match self {
85            Self::MissingRequiredField { .. } | Self::HostConfigurationNeeded { .. } => {
86                RetryClassification::HostConfigurationNeeded
87            }
88            Self::ContractViolation { .. } => RetryClassification::RepairNeeded,
89            Self::Classified { retry, .. } => *retry,
90        }
91    }
92
93    /// Builds the context value.
94    /// This is data construction and performs no I/O, journal append, event publication, or
95    /// process work.
96    pub fn context(&self) -> ErrorContext {
97        match self {
98            Self::MissingRequiredField { field } => {
99                ErrorContext::new(format!("missing required field: {field}"))
100            }
101            Self::ContractViolation { message } | Self::HostConfigurationNeeded { message } => {
102                ErrorContext::new(message.clone())
103            }
104            Self::Classified { context, .. } => context.as_ref().clone(),
105        }
106    }
107
108    /// Builds the causal ids value.
109    /// This is data construction and performs no I/O, journal append, event publication, or
110    /// process work.
111    pub fn causal_ids(&self) -> CausalIds {
112        match self {
113            Self::Classified { causal_ids, .. } => causal_ids.as_ref().clone(),
114            _ => CausalIds::default(),
115        }
116    }
117
118    /// Returns this value with its policy ref setting replaced. The
119    /// method follows builder-style data construction and does not
120    /// execute external work.
121    pub fn with_policy_ref(self, policy_ref: PolicyRef) -> Self {
122        self.map_context(|context| context.policy_refs.push(policy_ref))
123    }
124
125    /// Returns this value with its source setting replaced. The method
126    /// follows builder-style data construction and does not execute
127    /// external work.
128    pub fn with_source(self, source: SourceRef) -> Self {
129        self.map_context(|context| context.source = Some(source))
130    }
131
132    /// Returns this value with its destination setting replaced. The
133    /// method follows builder-style data construction and does not
134    /// execute external work.
135    pub fn with_destination(self, destination: DestinationRef) -> Self {
136        self.map_context(|context| context.destination = Some(destination))
137    }
138
139    /// Returns this value with its subject setting replaced. The method
140    /// follows builder-style data construction and does not execute
141    /// external work.
142    pub fn with_subject(self, subject: EntityRef) -> Self {
143        self.map_context(|context| context.subject = Some(subject))
144    }
145
146    /// Returns this value with its causal ids setting replaced. The
147    /// method follows builder-style data construction and does not
148    /// execute external work.
149    pub fn with_causal_ids(self, causal_ids: CausalIds) -> Self {
150        match self {
151            Self::Classified {
152                kind,
153                retry,
154                context,
155                ..
156            } => Self::Classified {
157                kind,
158                retry,
159                context,
160                causal_ids: Box::new(causal_ids),
161            },
162            other => Self::Classified {
163                kind: other.kind(),
164                retry: other.retry(),
165                context: Box::new(other.context()),
166                causal_ids: Box::new(causal_ids),
167            },
168        }
169    }
170
171    /// Builds the missing required field value.
172    /// This is data construction and performs no I/O, journal append, event publication, or
173    /// process work.
174    pub fn missing_required_field(field: impl Into<String>) -> Self {
175        Self::MissingRequiredField {
176            field: field.into(),
177        }
178    }
179
180    /// Builds the contract violation value.
181    /// This is data construction and performs no I/O, journal append, event publication, or
182    /// process work.
183    pub fn contract_violation(message: impl Into<String>) -> Self {
184        Self::ContractViolation {
185            message: message.into(),
186        }
187    }
188
189    /// Builds the host configuration needed value.
190    /// This is data construction and performs no I/O, journal append, event publication, or
191    /// process work.
192    pub fn host_configuration_needed(message: impl Into<String>) -> Self {
193        Self::HostConfigurationNeeded {
194            message: message.into(),
195        }
196    }
197
198    fn map_context(self, update: impl FnOnce(&mut ErrorContext)) -> Self {
199        let kind = self.kind();
200        let retry = self.retry();
201        let mut context = self.context();
202        let causal_ids = self.causal_ids();
203        update(&mut context);
204        Self::Classified {
205            kind,
206            retry,
207            context: Box::new(context),
208            causal_ids: Box::new(causal_ids),
209        }
210    }
211}
212
213#[derive(Clone, Copy, Debug, Deserialize, Error, Eq, PartialEq, Serialize)]
214/// Enumerates the finite agent error kind cases.
215/// Serialized names are part of the SDK contract; update fixtures when variants change.
216pub enum AgentErrorKind {
217    #[error("invalid package")]
218    /// Use this variant when the contract needs to represent invalid package; selecting it has no side effect by itself.
219    InvalidPackage,
220    #[error("invalid state transition")]
221    /// Use this variant when the contract needs to represent invalid state transition; selecting it has no side effect by itself.
222    InvalidStateTransition,
223    #[error("provider failure")]
224    /// Use this variant when the contract needs to represent provider failure; selecting it has no side effect by itself.
225    ProviderFailure,
226    #[error("projection failure")]
227    /// Use this variant when the contract needs to represent projection failure; selecting it has no side effect by itself.
228    ProjectionFailure,
229    #[error("tool failure")]
230    /// Use this variant when the contract needs to represent tool failure; selecting it has no side effect by itself.
231    ToolFailure,
232    #[error("approval failure")]
233    /// Use this variant when the contract needs to represent approval failure; selecting it has no side effect by itself.
234    ApprovalFailure,
235    #[error("policy denial")]
236    /// Use this variant when the contract needs to represent policy denial; selecting it has no side effect by itself.
237    PolicyDenial,
238    #[error("journal failure")]
239    /// Use this variant when the contract needs to represent journal failure; selecting it has no side effect by itself.
240    JournalFailure,
241    #[error("telemetry failure")]
242    /// Use this variant when the contract needs to represent telemetry failure; selecting it has no side effect by itself.
243    TelemetryFailure,
244    #[error("isolation failure")]
245    /// Use this variant when the contract needs to represent isolation failure; selecting it has no side effect by itself.
246    IsolationFailure,
247    #[error("structured output failure")]
248    /// Use this variant when the contract needs to represent structured output failure; selecting it has no side effect by itself.
249    StructuredOutputFailure,
250    #[error("stream rule failure")]
251    /// Use this variant when the contract needs to represent stream rule failure; selecting it has no side effect by itself.
252    StreamRuleFailure,
253    #[error("subagent failure")]
254    /// Use this variant when the contract needs to represent subagent failure; selecting it has no side effect by itself.
255    SubagentFailure,
256    #[error("extension failure")]
257    /// Use this variant when the contract needs to represent extension failure; selecting it has no side effect by itself.
258    ExtensionFailure,
259    #[error("cancellation")]
260    /// Use this variant when the contract needs to represent cancellation; selecting it has no side effect by itself.
261    Cancellation,
262    #[error("child lifecycle failure")]
263    /// Use this variant when the contract needs to represent child lifecycle failure; selecting it has no side effect by itself.
264    ChildLifecycleFailure,
265    #[error("hook failure")]
266    /// Use this variant when the contract needs to represent hook failure; selecting it has no side effect by itself.
267    HookFailure,
268    #[error("timeout")]
269    /// Use this variant when the contract needs to represent timeout; selecting it has no side effect by itself.
270    Timeout,
271    #[error("recovery or repair needed")]
272    /// Use this variant when the contract needs to represent recovery repair needed; selecting it has no side effect by itself.
273    RecoveryRepairNeeded,
274    #[error("host configuration needed")]
275    /// Use this variant when the contract needs to represent host configuration needed; selecting it has no side effect by itself.
276    HostConfigurationNeeded,
277}
278
279#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
280/// Enumerates the finite retry classification cases.
281/// Serialized names are part of the SDK contract; update fixtures when variants change.
282pub enum RetryClassification {
283    /// Use this variant when the contract needs to represent retryable; selecting it has no side effect by itself.
284    Retryable,
285    /// Use this variant when the contract needs to represent not retryable; selecting it has no side effect by itself.
286    NotRetryable,
287    /// Use this variant when the contract needs to represent repair needed; selecting it has no side effect by itself.
288    RepairNeeded,
289    /// Use this variant when the contract needs to represent user action needed; selecting it has no side effect by itself.
290    UserActionNeeded,
291    /// Use this variant when the contract needs to represent host configuration needed; selecting it has no side effect by itself.
292    HostConfigurationNeeded,
293}
294
295#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
296/// Defines the error context SDK value.
297/// Construction records local state only; documented runtimes, executors, or ports own side effects.
298pub struct ErrorContext {
299    /// Message used by this record or request.
300    pub message: String,
301    /// Policy references that govern admission, projection, execution, or
302    /// delivery.
303    pub policy_refs: Vec<PolicyRef>,
304    /// Source label or ref for this item; it is metadata and does not fetch
305    /// content by itself.
306    pub source: Option<SourceRef>,
307    /// Destination label or ref for this item; it is metadata and does not
308    /// deliver content by itself.
309    pub destination: Option<DestinationRef>,
310    /// Optional subject value.
311    /// When absent, callers should use the documented default or skip that optional behavior.
312    pub subject: Option<EntityRef>,
313    /// Privacy class used for projection, telemetry, and raw-content access
314    /// decisions.
315    pub privacy: Option<PrivacyClass>,
316    /// Redacted human-readable summary safe for events, telemetry, and logs.
317    pub redacted_summary: Option<String>,
318}
319
320impl ErrorContext {
321    /// Creates a new domain::error value with explicit caller-provided
322    /// inputs. This constructor is data-only and performs no I/O or
323    /// external side effects.
324    pub fn new(message: impl Into<String>) -> Self {
325        Self {
326            message: message.into(),
327            policy_refs: Vec::new(),
328            source: None,
329            destination: None,
330            subject: None,
331            privacy: None,
332            redacted_summary: None,
333        }
334    }
335}
336
337#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
338/// Defines the causal ids SDK value.
339/// Construction records local state only; documented runtimes, executors, or ports own side effects.
340pub struct CausalIds {
341    /// Run identifier used for lineage, filtering, replay, and dedupe.
342    pub run_id: Option<RunId>,
343    /// Turn identifier for one loop turn within a run.
344    pub turn_id: Option<TurnId>,
345    /// Attempt identifier for retry, repair, provider, or tool execution
346    /// evidence.
347    pub attempt_id: Option<AttemptId>,
348    /// Event identifier used to correlate live events with journal or replay
349    /// evidence.
350    pub event_id: Option<EventId>,
351    /// Stable tool call id used for typed lineage, lookup, or dedupe.
352    pub tool_call_id: Option<ToolCallId>,
353    /// Stable span id used for typed lineage, lookup, or dedupe.
354    pub span_id: Option<SpanId>,
355    /// Collection of related values.
356    /// Ordering and membership should be treated as part of the serialized contract when
357    /// relevant.
358    pub related: Vec<EntityRef>,
359}