use crate::{Agent, AgentId, AgentState};
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use uuid::Uuid;
pub type BehaviorId = Uuid;
#[derive(Debug, Clone)]
pub struct BehaviorContext {
pub agent_id: AgentId,
pub agent_state: AgentState,
pub timestamp: u64,
pub data: HashMap<String, String>,
}
impl BehaviorContext {
pub fn new(agent: &Agent) -> Self {
Self {
agent_id: agent.id(),
agent_state: agent.state().clone(),
timestamp: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as u64,
data: HashMap::new(),
}
}
pub fn with_data(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.data.insert(key.into(), value.into());
self
}
pub fn get_data(&self, key: &str) -> Option<&String> {
self.data.get(key)
}
}
#[derive(Debug, Clone)]
pub enum BehaviorResult {
Success,
Failed(String),
Skipped(String),
}
impl BehaviorResult {
pub fn is_success(&self) -> bool {
matches!(self, BehaviorResult::Success)
}
pub fn is_failed(&self) -> bool {
matches!(self, BehaviorResult::Failed(_))
}
}
pub trait Behavior: Send + Sync {
fn name(&self) -> &str;
fn description(&self) -> &str {
""
}
fn can_execute(&self, _context: &BehaviorContext) -> bool {
true
}
fn execute(&self, context: &BehaviorContext) -> BehaviorResult;
fn on_attach(&self, _agent_id: AgentId) -> BehaviorResult {
BehaviorResult::Success
}
fn on_detach(&self, _agent_id: AgentId) -> BehaviorResult {
BehaviorResult::Success
}
}
pub struct ClosureBehavior {
name: String,
description: String,
executor: Arc<dyn Fn(&BehaviorContext) -> BehaviorResult + Send + Sync>,
}
impl ClosureBehavior {
pub fn new<F>(name: impl Into<String>, executor: F) -> Self
where
F: Fn(&BehaviorContext) -> BehaviorResult + Send + Sync + 'static,
{
Self {
name: name.into(),
description: String::new(),
executor: Arc::new(executor),
}
}
pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = description.into();
self
}
}
impl Behavior for ClosureBehavior {
fn name(&self) -> &str {
&self.name
}
fn description(&self) -> &str {
&self.description
}
fn execute(&self, context: &BehaviorContext) -> BehaviorResult {
(self.executor)(context)
}
}
pub struct LoggingBehavior {
name: String,
}
impl LoggingBehavior {
pub fn new() -> Self {
Self {
name: "logging".to_string(),
}
}
}
impl Default for LoggingBehavior {
fn default() -> Self {
Self::new()
}
}
impl Behavior for LoggingBehavior {
fn name(&self) -> &str {
&self.name
}
fn description(&self) -> &str {
"Logs agent state and activity"
}
fn execute(&self, context: &BehaviorContext) -> BehaviorResult {
println!(
"[Log] Agent {} in state {:?} at {}",
context.agent_id, context.agent_state, context.timestamp
);
BehaviorResult::Success
}
}
pub struct MetricsBehavior {
name: String,
metrics: Arc<RwLock<HashMap<String, u64>>>,
}
impl MetricsBehavior {
pub fn new() -> Self {
Self {
name: "metrics".to_string(),
metrics: Arc::new(RwLock::new(HashMap::new())),
}
}
pub fn get_metric(&self, key: &str) -> Option<u64> {
self.metrics
.read()
.expect("Lock poisoned: metrics")
.get(key)
.copied()
}
pub fn all_metrics(&self) -> HashMap<String, u64> {
self.metrics.read().expect("Lock poisoned: metrics").clone()
}
}
impl Default for MetricsBehavior {
fn default() -> Self {
Self::new()
}
}
impl Behavior for MetricsBehavior {
fn name(&self) -> &str {
&self.name
}
fn description(&self) -> &str {
"Collects agent execution metrics"
}
fn execute(&self, context: &BehaviorContext) -> BehaviorResult {
let mut metrics = self.metrics.write().expect("Lock poisoned: metrics");
let key = format!("state_{:?}", context.agent_state);
*metrics.entry(key).or_insert(0) += 1;
*metrics.entry("total_executions".to_string()).or_insert(0) += 1;
BehaviorResult::Success
}
}
pub struct HealthCheckBehavior {
name: String,
healthy_states: Vec<AgentState>,
}
impl HealthCheckBehavior {
pub fn new() -> Self {
Self {
name: "health_check".to_string(),
healthy_states: vec![AgentState::Running, AgentState::Created],
}
}
pub fn with_healthy_states(mut self, states: Vec<AgentState>) -> Self {
self.healthy_states = states;
self
}
}
impl Default for HealthCheckBehavior {
fn default() -> Self {
Self::new()
}
}
impl Behavior for HealthCheckBehavior {
fn name(&self) -> &str {
&self.name
}
fn description(&self) -> &str {
"Checks agent health based on state"
}
fn execute(&self, context: &BehaviorContext) -> BehaviorResult {
if self.healthy_states.contains(&context.agent_state) {
BehaviorResult::Success
} else {
BehaviorResult::Failed(format!("Unhealthy state: {:?}", context.agent_state))
}
}
}
pub struct BehaviorComposite {
agent_id: AgentId,
behaviors: RwLock<HashMap<BehaviorId, Arc<dyn Behavior>>>,
execution_order: RwLock<Vec<BehaviorId>>,
}
impl BehaviorComposite {
pub fn new(agent_id: AgentId) -> Self {
Self {
agent_id,
behaviors: RwLock::new(HashMap::new()),
execution_order: RwLock::new(Vec::new()),
}
}
pub fn attach(&self, behavior: Arc<dyn Behavior>) -> BehaviorId {
let id = Uuid::new_v4();
behavior.on_attach(self.agent_id);
self.behaviors
.write()
.expect("Lock poisoned: behaviors")
.insert(id, behavior);
self.execution_order
.write()
.expect("Lock poisoned: execution_order")
.push(id);
id
}
pub fn detach(&self, behavior_id: &BehaviorId) -> Option<Arc<dyn Behavior>> {
let behavior = self
.behaviors
.write()
.expect("Lock poisoned: behaviors")
.remove(behavior_id)?;
behavior.on_detach(self.agent_id);
self.execution_order
.write()
.expect("Lock poisoned: execution_order")
.retain(|id| id != behavior_id);
Some(behavior)
}
pub fn get(&self, behavior_id: &BehaviorId) -> Option<Arc<dyn Behavior>> {
self.behaviors
.read()
.expect("Lock poisoned: behaviors")
.get(behavior_id)
.cloned()
}
pub fn execute_all(&self, context: &BehaviorContext) -> Vec<(BehaviorId, BehaviorResult)> {
let order = self
.execution_order
.read()
.expect("Lock poisoned: execution_order");
let behaviors = self.behaviors.read().expect("Lock poisoned: behaviors");
order
.iter()
.filter_map(|id| {
let behavior = behaviors.get(id)?;
if behavior.can_execute(context) {
let result = behavior.execute(context);
Some((*id, result))
} else {
Some((
*id,
BehaviorResult::Skipped("Preconditions not met".to_string()),
))
}
})
.collect()
}
pub fn execute(
&self,
behavior_id: &BehaviorId,
context: &BehaviorContext,
) -> Option<BehaviorResult> {
let behaviors = self.behaviors.read().expect("Lock poisoned: behaviors");
let behavior = behaviors.get(behavior_id)?;
if behavior.can_execute(context) {
Some(behavior.execute(context))
} else {
Some(BehaviorResult::Skipped("Preconditions not met".to_string()))
}
}
pub fn count(&self) -> usize {
self.behaviors
.read()
.expect("Lock poisoned: behaviors")
.len()
}
pub fn behavior_ids(&self) -> Vec<BehaviorId> {
self.execution_order
.read()
.expect("Lock poisoned: execution_order")
.clone()
}
pub fn behavior_names(&self) -> Vec<String> {
let order = self
.execution_order
.read()
.expect("Lock poisoned: execution_order");
let behaviors = self.behaviors.read().expect("Lock poisoned: behaviors");
order
.iter()
.filter_map(|id| behaviors.get(id).map(|b| b.name().to_string()))
.collect()
}
}
pub struct BehaviorRegistry {
composites: RwLock<HashMap<AgentId, Arc<BehaviorComposite>>>,
}
impl BehaviorRegistry {
pub fn new() -> Self {
Self {
composites: RwLock::new(HashMap::new()),
}
}
pub fn register_agent(&self, agent_id: AgentId) -> Arc<BehaviorComposite> {
let composite = Arc::new(BehaviorComposite::new(agent_id));
self.composites
.write()
.expect("Lock poisoned: composites")
.insert(agent_id, Arc::clone(&composite));
composite
}
pub fn get_composite(&self, agent_id: &AgentId) -> Option<Arc<BehaviorComposite>> {
self.composites
.read()
.expect("Lock poisoned: composites")
.get(agent_id)
.cloned()
}
pub fn unregister_agent(&self, agent_id: &AgentId) -> Option<Arc<BehaviorComposite>> {
self.composites
.write()
.expect("Lock poisoned: composites")
.remove(agent_id)
}
pub fn execute_for_agent(
&self,
agent_id: &AgentId,
context: &BehaviorContext,
) -> Option<Vec<(BehaviorId, BehaviorResult)>> {
let composite = self.get_composite(agent_id)?;
Some(composite.execute_all(context))
}
pub fn agent_count(&self) -> usize {
self.composites
.read()
.expect("Lock poisoned: composites")
.len()
}
}
impl Default for BehaviorRegistry {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_behavior_context() {
let agent = Agent::new(vec![0x00, 0x61, 0x73, 0x6d]);
let context = BehaviorContext::new(&agent);
assert_eq!(context.agent_id, agent.id());
assert_eq!(context.agent_state, AgentState::Created);
}
#[test]
fn test_closure_behavior() {
let behavior = ClosureBehavior::new("test", |_| BehaviorResult::Success);
let agent = Agent::new(vec![0x00, 0x61, 0x73, 0x6d]);
let context = BehaviorContext::new(&agent);
let result = behavior.execute(&context);
assert!(result.is_success());
}
#[test]
fn test_logging_behavior() {
let behavior = LoggingBehavior::new();
let agent = Agent::new(vec![0x00, 0x61, 0x73, 0x6d]);
let context = BehaviorContext::new(&agent);
let result = behavior.execute(&context);
assert!(result.is_success());
}
#[test]
fn test_metrics_behavior() {
let behavior = MetricsBehavior::new();
let agent = Agent::new(vec![0x00, 0x61, 0x73, 0x6d]);
let context = BehaviorContext::new(&agent);
behavior.execute(&context);
behavior.execute(&context);
assert_eq!(behavior.get_metric("total_executions"), Some(2));
}
#[test]
fn test_health_check_behavior() {
let behavior = HealthCheckBehavior::new();
let mut agent = Agent::new(vec![0x00, 0x61, 0x73, 0x6d]);
let context = BehaviorContext::new(&agent);
let result = behavior.execute(&context);
assert!(result.is_success());
agent.set_state(AgentState::Error);
let context = BehaviorContext::new(&agent);
let result = behavior.execute(&context);
assert!(result.is_failed());
}
#[test]
fn test_behavior_composite() {
let agent = Agent::new(vec![0x00, 0x61, 0x73, 0x6d]);
let composite = BehaviorComposite::new(agent.id());
let behavior1 = Arc::new(LoggingBehavior::new());
let behavior2 = Arc::new(MetricsBehavior::new());
let id1 = composite.attach(behavior1);
let _id2 = composite.attach(behavior2);
assert_eq!(composite.count(), 2);
let context = BehaviorContext::new(&agent);
let results = composite.execute_all(&context);
assert_eq!(results.len(), 2);
composite.detach(&id1);
assert_eq!(composite.count(), 1);
}
#[test]
fn test_behavior_registry() {
let registry = BehaviorRegistry::new();
let agent = Agent::new(vec![0x00, 0x61, 0x73, 0x6d]);
let composite = registry.register_agent(agent.id());
assert_eq!(registry.agent_count(), 1);
let behavior = Arc::new(LoggingBehavior::new());
composite.attach(behavior);
let context = BehaviorContext::new(&agent);
let results = registry.execute_for_agent(&agent.id(), &context);
assert!(results.is_some());
assert_eq!(results.unwrap().len(), 1);
registry.unregister_agent(&agent.id());
assert_eq!(registry.agent_count(), 0);
}
#[test]
fn test_behavior_names() {
let agent = Agent::new(vec![0x00, 0x61, 0x73, 0x6d]);
let composite = BehaviorComposite::new(agent.id());
composite.attach(Arc::new(LoggingBehavior::new()));
composite.attach(Arc::new(MetricsBehavior::new()));
composite.attach(Arc::new(HealthCheckBehavior::new()));
let names = composite.behavior_names();
assert_eq!(names.len(), 3);
assert!(names.contains(&"logging".to_string()));
assert!(names.contains(&"metrics".to_string()));
assert!(names.contains(&"health_check".to_string()));
}
}