Skip to main content

agent_effects_store/
kind.rs

1//! Behavioral classification of effects.
2
3use serde::{Deserialize, Serialize};
4
5/// What re-executing an effect does to the outside world.
6///
7/// The kind decides how much the runtime may retry on its own (see
8/// `Capabilities::unknown_plan` in `agent-effects`).
9#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
10#[serde(rename_all = "snake_case")]
11pub enum EffectKind {
12    /// No externally visible mutation, e.g. fetching a balance.
13    Read,
14    /// Repeating the same request leaves the same end state, e.g.
15    /// `PUT /users/123`.
16    IdempotentWrite,
17    /// Changes external state, but a compensating operation exists, e.g.
18    /// reserving inventory.
19    ReversibleWrite,
20    /// Changes external state and cannot be reliably undone, e.g. sending an
21    /// email.
22    IrreversibleWrite,
23}
24
25impl EffectKind {
26    /// Whether executing the same request twice is harmless on its own,
27    /// without help from the remote system.
28    pub const fn is_naturally_idempotent(self) -> bool {
29        matches!(self, Self::Read | Self::IdempotentWrite)
30    }
31
32    /// The stable storage representation.
33    pub const fn as_str(self) -> &'static str {
34        match self {
35            Self::Read => "read",
36            Self::IdempotentWrite => "idempotent_write",
37            Self::ReversibleWrite => "reversible_write",
38            Self::IrreversibleWrite => "irreversible_write",
39        }
40    }
41
42    /// Parses the storage representation produced by [`Self::as_str`].
43    pub fn parse(s: &str) -> Option<Self> {
44        Some(match s {
45            "read" => Self::Read,
46            "idempotent_write" => Self::IdempotentWrite,
47            "reversible_write" => Self::ReversibleWrite,
48            "irreversible_write" => Self::IrreversibleWrite,
49            _ => return None,
50        })
51    }
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57
58    #[test]
59    fn storage_representation_round_trips_and_matches_serde() {
60        for kind in [
61            EffectKind::Read,
62            EffectKind::IdempotentWrite,
63            EffectKind::ReversibleWrite,
64            EffectKind::IrreversibleWrite,
65        ] {
66            assert_eq!(EffectKind::parse(kind.as_str()), Some(kind));
67            assert_eq!(
68                serde_json::to_string(&kind).unwrap(),
69                format!("\"{}\"", kind.as_str())
70            );
71        }
72    }
73}