atman-runtime 1.9.1

atman flow execution runtime: evaluator, tool dispatch, provider dispatch, executor, memory stores
Documentation
use serde::{Deserialize, Serialize};

use crate::event::TurnId;

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(transparent)]
pub struct InjectionId(pub uuid::Uuid);

impl InjectionId {
    pub fn now() -> Self {
        Self(uuid::Uuid::now_v7())
    }
}

impl std::fmt::Display for InjectionId {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.0.fmt(f)
    }
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum InjectionState {
    Pending,
    Injected,
    Cancelled,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum InjectionLevel {
    L1Nudge,
    L2CourseCorrect,
    L3Redirect,
    L4HardStop,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "snake_case")]
pub enum InjectionSource {
    #[default]
    User,
    Watcher {
        watcher_id: String,
        kind: String,
        handle: String,
    },
}

impl InjectionLevel {
    pub fn as_str(&self) -> &'static str {
        match self {
            InjectionLevel::L1Nudge => "l1_nudge",
            InjectionLevel::L2CourseCorrect => "l2_course_correct",
            InjectionLevel::L3Redirect => "l3_redirect",
            InjectionLevel::L4HardStop => "l4_hard_stop",
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Injection {
    pub id: InjectionId,
    pub text: String,
    pub turn_id: TurnId,
    pub created_at: chrono::DateTime<chrono::Utc>,
    pub state: InjectionState,
    #[serde(default = "default_level")]
    pub level: InjectionLevel,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub redirect_target: Option<String>,
    #[serde(default)]
    pub source: InjectionSource,
}

fn default_level() -> InjectionLevel {
    InjectionLevel::L1Nudge
}

impl Injection {
    pub fn new_pending(turn_id: TurnId, text: impl Into<String>) -> Self {
        Self::with_level(turn_id, text, InjectionLevel::L1Nudge, None)
    }

    pub fn with_level(
        turn_id: TurnId,
        text: impl Into<String>,
        level: InjectionLevel,
        redirect_target: Option<String>,
    ) -> Self {
        Self {
            id: InjectionId::now(),
            text: text.into(),
            turn_id,
            created_at: chrono::Utc::now(),
            state: InjectionState::Pending,
            level,
            redirect_target,
            source: InjectionSource::User,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn injection_roundtrips_via_serde_json() {
        let inj = Injection::new_pending(TurnId::now(), "remember to check tests");
        let s = serde_json::to_string(&inj).unwrap();
        let back: Injection = serde_json::from_str(&s).unwrap();
        assert_eq!(inj, back);
    }

    #[test]
    fn injection_ids_are_unique() {
        let mut seen = std::collections::HashSet::new();
        for _ in 0..1000 {
            let id = InjectionId::now();
            assert!(seen.insert(id));
        }
    }

    #[test]
    fn state_serializes_snake_case() {
        assert_eq!(
            serde_json::to_string(&InjectionState::Pending).unwrap(),
            "\"pending\""
        );
        assert_eq!(
            serde_json::to_string(&InjectionState::Injected).unwrap(),
            "\"injected\""
        );
        assert_eq!(
            serde_json::to_string(&InjectionState::Cancelled).unwrap(),
            "\"cancelled\""
        );
    }

    #[test]
    fn new_pending_starts_in_pending_state() {
        let inj = Injection::new_pending(TurnId::now(), "x");
        assert_eq!(inj.state, InjectionState::Pending);
    }

    #[test]
    fn old_event_without_source_deserializes_as_user() {
        let json = serde_json::json!({
            "id": uuid::Uuid::now_v7(),
            "text": "old message",
            "turn_id": uuid::Uuid::now_v7().to_string(),
            "created_at": "2026-01-01T00:00:00Z",
            "state": "pending",
            "level": "l1_nudge"
        });
        let inj: Injection = serde_json::from_value(json).unwrap();
        assert_eq!(inj.source, InjectionSource::User);
    }

    #[test]
    fn watcher_source_roundtrips() {
        let inj = Injection {
            id: InjectionId::now(),
            text: "pattern found".into(),
            turn_id: TurnId::now(),
            created_at: chrono::Utc::now(),
            state: InjectionState::Pending,
            level: crate::injection::InjectionLevel::L1Nudge,
            redirect_target: None,
            source: InjectionSource::Watcher {
                watcher_id: "w_abc".into(),
                kind: "terminal".into(),
                handle: "term_x".into(),
            },
        };
        let s = serde_json::to_string(&inj).unwrap();
        let back: Injection = serde_json::from_str(&s).unwrap();
        assert_eq!(inj, back);
    }
}