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}