use crate::{Dna, Policy};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use std::time::{Duration, Instant};
use uuid::Uuid;
pub type AgentId = Uuid;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum AgentState {
Created,
Running,
Paused,
Suspended,
Migrating,
Error,
Terminated,
}
impl AgentState {
pub fn can_transition_to(&self, target: &AgentState) -> bool {
use AgentState::*;
matches!(
(self, target),
(Created, Running) |
(Created, Terminated) |
(Running, Paused) |
(Running, Suspended) |
(Running, Migrating) |
(Running, Error) |
(Running, Terminated) |
(Paused, Running) |
(Paused, Suspended) |
(Paused, Migrating) |
(Paused, Terminated) |
(Suspended, Running) |
(Suspended, Paused) |
(Suspended, Migrating) |
(Suspended, Error) |
(Suspended, Terminated) |
(Migrating, Running) |
(Migrating, Error) |
(Migrating, Terminated) |
(Error, Running) | (Error, Suspended) | (Error, Terminated) )
}
pub fn is_active(&self) -> bool {
matches!(self, AgentState::Running)
}
pub fn can_accept_work(&self) -> bool {
matches!(self, AgentState::Running | AgentState::Created)
}
pub fn is_recoverable(&self) -> bool {
matches!(
self,
AgentState::Error | AgentState::Suspended | AgentState::Paused
)
}
pub fn is_terminal(&self) -> bool {
matches!(self, AgentState::Terminated)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentError {
pub message: String,
pub code: Option<u32>,
pub timestamp: u64,
pub recovery_attempts: u32,
pub fatal: bool,
}
impl AgentError {
pub fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
code: None,
timestamp: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as u64,
recovery_attempts: 0,
fatal: false,
}
}
pub fn with_code(mut self, code: u32) -> Self {
self.code = Some(code);
self
}
pub fn fatal(mut self) -> Self {
self.fatal = true;
self
}
pub fn attempt_recovery(&mut self) {
self.recovery_attempts += 1;
}
}
#[derive(Debug, Clone)]
pub struct StateTransition {
pub agent_id: AgentId,
pub from: AgentState,
pub to: AgentState,
pub timestamp: Instant,
pub reason: Option<String>,
}
pub type TransitionHook = Arc<dyn Fn(&StateTransition) + Send + Sync>;
#[derive(Debug)]
pub enum TransitionResult {
Success,
InvalidTransition { from: AgentState, to: AgentState },
Blocked { reason: String },
}
#[derive(Debug, Clone)]
pub struct Agent {
id: AgentId,
dna: Dna,
state: AgentState,
policy: Policy,
error: Option<AgentError>,
state_entered_at: Instant,
state_history: Vec<(AgentState, Instant)>,
}
impl Agent {
const MAX_STATE_HISTORY: usize = 10;
pub fn new(wasm_binary: Vec<u8>) -> Self {
let now = Instant::now();
Self {
id: Uuid::new_v4(),
dna: Dna::new(wasm_binary),
state: AgentState::Created,
policy: Policy::default(),
error: None,
state_entered_at: now,
state_history: vec![(AgentState::Created, now)],
}
}
pub fn id(&self) -> AgentId {
self.id
}
pub fn state(&self) -> &AgentState {
&self.state
}
pub fn time_in_state(&self) -> Duration {
self.state_entered_at.elapsed()
}
pub fn state_history(&self) -> &[(AgentState, Instant)] {
&self.state_history
}
pub fn transition_to(&mut self, new_state: AgentState) -> TransitionResult {
self.transition_to_with_reason(new_state, None)
}
pub fn transition_to_with_reason(
&mut self,
new_state: AgentState,
reason: Option<String>,
) -> TransitionResult {
if !self.state.can_transition_to(&new_state) {
return TransitionResult::InvalidTransition {
from: self.state.clone(),
to: new_state,
};
}
let now = Instant::now();
self.state_history.push((self.state.clone(), now));
if self.state_history.len() > Self::MAX_STATE_HISTORY {
self.state_history.remove(0);
}
if self.state == AgentState::Error && new_state != AgentState::Error {
self.error = None;
}
self.state = new_state;
self.state_entered_at = now;
let _ = reason;
TransitionResult::Success
}
pub fn set_error(&mut self, error: AgentError) -> TransitionResult {
if !self.state.can_transition_to(&AgentState::Error) {
return TransitionResult::InvalidTransition {
from: self.state.clone(),
to: AgentState::Error,
};
}
let now = Instant::now();
self.state_history.push((self.state.clone(), now));
if self.state_history.len() > Self::MAX_STATE_HISTORY {
self.state_history.remove(0);
}
self.error = Some(error);
self.state = AgentState::Error;
self.state_entered_at = now;
TransitionResult::Success
}
pub fn error(&self) -> Option<&AgentError> {
self.error.as_ref()
}
pub fn attempt_recovery(&mut self) -> TransitionResult {
if self.state != AgentState::Error {
return TransitionResult::InvalidTransition {
from: self.state.clone(),
to: AgentState::Running,
};
}
if let Some(ref mut err) = self.error {
if err.fatal {
return TransitionResult::Blocked {
reason: "Fatal error cannot be recovered".to_string(),
};
}
err.attempt_recovery();
}
self.transition_to(AgentState::Running)
}
pub fn set_state(&mut self, state: AgentState) {
let now = Instant::now();
self.state_history.push((self.state.clone(), now));
if self.state_history.len() > Self::MAX_STATE_HISTORY {
self.state_history.remove(0);
}
self.state = state;
self.state_entered_at = now;
}
pub fn dna(&self) -> &Dna {
&self.dna
}
pub fn policy(&self) -> &Policy {
&self.policy
}
pub fn set_policy(&mut self, policy: Policy) {
self.policy = policy;
}
pub fn can_pause(&self) -> bool {
self.state.can_transition_to(&AgentState::Paused)
}
pub fn pause(&mut self) -> TransitionResult {
self.transition_to(AgentState::Paused)
}
pub fn resume(&mut self) -> TransitionResult {
self.transition_to(AgentState::Running)
}
pub fn suspend(&mut self) -> TransitionResult {
self.transition_to(AgentState::Suspended)
}
pub fn start(&mut self) -> TransitionResult {
self.transition_to(AgentState::Running)
}
pub fn terminate(&mut self) -> TransitionResult {
self.transition_to(AgentState::Terminated)
}
pub fn begin_migration(&mut self) -> TransitionResult {
self.transition_to(AgentState::Migrating)
}
pub fn complete_migration(&mut self) -> TransitionResult {
self.transition_to(AgentState::Running)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_agent_state_transitions() {
let mut agent = Agent::new(vec![]);
assert!(matches!(agent.state(), AgentState::Created));
agent.set_state(AgentState::Running);
assert!(matches!(agent.state(), AgentState::Running));
}
#[test]
fn test_state_can_transition() {
assert!(AgentState::Created.can_transition_to(&AgentState::Running));
assert!(AgentState::Created.can_transition_to(&AgentState::Terminated));
assert!(!AgentState::Created.can_transition_to(&AgentState::Paused));
assert!(AgentState::Running.can_transition_to(&AgentState::Paused));
assert!(AgentState::Running.can_transition_to(&AgentState::Suspended));
assert!(AgentState::Running.can_transition_to(&AgentState::Error));
assert!(!AgentState::Running.can_transition_to(&AgentState::Created));
assert!(AgentState::Paused.can_transition_to(&AgentState::Running));
assert!(AgentState::Paused.can_transition_to(&AgentState::Suspended));
assert!(!AgentState::Paused.can_transition_to(&AgentState::Error));
assert!(AgentState::Error.can_transition_to(&AgentState::Running));
assert!(AgentState::Error.can_transition_to(&AgentState::Terminated));
assert!(!AgentState::Error.can_transition_to(&AgentState::Created));
assert!(!AgentState::Terminated.can_transition_to(&AgentState::Running));
}
#[test]
fn test_state_is_active() {
assert!(AgentState::Running.is_active());
assert!(!AgentState::Created.is_active());
assert!(!AgentState::Paused.is_active());
assert!(!AgentState::Suspended.is_active());
}
#[test]
fn test_state_is_recoverable() {
assert!(AgentState::Error.is_recoverable());
assert!(AgentState::Suspended.is_recoverable());
assert!(AgentState::Paused.is_recoverable());
assert!(!AgentState::Running.is_recoverable());
assert!(!AgentState::Terminated.is_recoverable());
}
#[test]
fn test_state_is_terminal() {
assert!(AgentState::Terminated.is_terminal());
assert!(!AgentState::Running.is_terminal());
assert!(!AgentState::Error.is_terminal());
}
#[test]
fn test_transition_to_valid() {
let mut agent = Agent::new(vec![]);
assert!(matches!(agent.start(), TransitionResult::Success));
assert_eq!(agent.state(), &AgentState::Running);
}
#[test]
fn test_transition_to_invalid() {
let mut agent = Agent::new(vec![]);
let result = agent.pause();
assert!(matches!(result, TransitionResult::InvalidTransition { .. }));
assert_eq!(agent.state(), &AgentState::Created);
}
#[test]
fn test_pause_resume() {
let mut agent = Agent::new(vec![]);
agent.start();
assert_eq!(agent.state(), &AgentState::Running);
agent.pause();
assert_eq!(agent.state(), &AgentState::Paused);
agent.resume();
assert_eq!(agent.state(), &AgentState::Running);
}
#[test]
fn test_error_state() {
let mut agent = Agent::new(vec![]);
agent.start();
let error = AgentError::new("Test error").with_code(42);
agent.set_error(error);
assert_eq!(agent.state(), &AgentState::Error);
assert!(agent.error().is_some());
assert_eq!(agent.error().unwrap().message, "Test error");
assert_eq!(agent.error().unwrap().code, Some(42));
}
#[test]
fn test_recovery_from_error() {
let mut agent = Agent::new(vec![]);
agent.start();
agent.set_error(AgentError::new("Recoverable error"));
assert_eq!(agent.state(), &AgentState::Error);
let result = agent.attempt_recovery();
assert!(matches!(result, TransitionResult::Success));
assert_eq!(agent.state(), &AgentState::Running);
assert!(agent.error().is_none()); }
#[test]
fn test_fatal_error_no_recovery() {
let mut agent = Agent::new(vec![]);
agent.start();
agent.set_error(AgentError::new("Fatal error").fatal());
let result = agent.attempt_recovery();
assert!(matches!(result, TransitionResult::Blocked { .. }));
assert_eq!(agent.state(), &AgentState::Error);
}
#[test]
fn test_state_history() {
let mut agent = Agent::new(vec![]);
assert_eq!(agent.state_history().len(), 1);
agent.start();
assert_eq!(agent.state_history().len(), 2);
agent.pause();
assert_eq!(agent.state_history().len(), 3);
agent.resume();
assert_eq!(agent.state_history().len(), 4);
let last = agent.state_history().last().unwrap();
assert_eq!(last.0, AgentState::Paused);
}
#[test]
fn test_migration_lifecycle() {
let mut agent = Agent::new(vec![]);
agent.start();
agent.begin_migration();
assert_eq!(agent.state(), &AgentState::Migrating);
agent.complete_migration();
assert_eq!(agent.state(), &AgentState::Running);
}
#[test]
fn test_agent_error_recovery_attempts() {
let mut error = AgentError::new("Test error");
assert_eq!(error.recovery_attempts, 0);
error.attempt_recovery();
assert_eq!(error.recovery_attempts, 1);
error.attempt_recovery();
assert_eq!(error.recovery_attempts, 2);
}
#[test]
fn test_time_in_state() {
let agent = Agent::new(vec![]);
let time = agent.time_in_state();
assert!(time < Duration::from_secs(1));
}
#[test]
fn test_can_accept_work() {
assert!(AgentState::Created.can_accept_work());
assert!(AgentState::Running.can_accept_work());
assert!(!AgentState::Paused.can_accept_work());
assert!(!AgentState::Suspended.can_accept_work());
assert!(!AgentState::Error.can_accept_work());
}
#[test]
fn test_can_pause() {
let mut agent = Agent::new(vec![]);
assert!(!agent.can_pause());
agent.start();
assert!(agent.can_pause());
agent.pause();
assert!(!agent.can_pause()); }
#[test]
fn test_terminate_from_any_state() {
let mut agent = Agent::new(vec![]);
agent.start();
assert!(matches!(agent.terminate(), TransitionResult::Success));
let mut agent = Agent::new(vec![]);
agent.start();
agent.pause();
assert!(matches!(agent.terminate(), TransitionResult::Success));
let mut agent = Agent::new(vec![]);
agent.start();
agent.set_error(AgentError::new("error"));
assert!(matches!(agent.terminate(), TransitionResult::Success));
}
}