mielin_cells/
composition.rs

1//! Agent Composition and Behaviors
2//!
3//! Provides a composition-based system for extending agent capabilities
4//! through reusable behaviors, avoiding deep inheritance hierarchies.
5
6use crate::{Agent, AgentId, AgentState};
7use std::collections::HashMap;
8use std::sync::{Arc, RwLock};
9use uuid::Uuid;
10
11/// Unique identifier for behaviors
12pub type BehaviorId = Uuid;
13
14/// Behavior execution context
15#[derive(Debug, Clone)]
16pub struct BehaviorContext {
17    /// Agent ID this behavior is executing for
18    pub agent_id: AgentId,
19    /// Agent current state
20    pub agent_state: AgentState,
21    /// Execution timestamp
22    pub timestamp: u64,
23    /// Custom context data
24    pub data: HashMap<String, String>,
25}
26
27impl BehaviorContext {
28    /// Create a new behavior context for an agent
29    pub fn new(agent: &Agent) -> Self {
30        Self {
31            agent_id: agent.id(),
32            agent_state: agent.state().clone(),
33            timestamp: std::time::SystemTime::now()
34                .duration_since(std::time::UNIX_EPOCH)
35                .unwrap_or_default()
36                .as_millis() as u64,
37            data: HashMap::new(),
38        }
39    }
40
41    /// Add context data
42    pub fn with_data(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
43        self.data.insert(key.into(), value.into());
44        self
45    }
46
47    /// Get context data
48    pub fn get_data(&self, key: &str) -> Option<&String> {
49        self.data.get(key)
50    }
51}
52
53/// Result of behavior execution
54#[derive(Debug, Clone)]
55pub enum BehaviorResult {
56    /// Behavior executed successfully
57    Success,
58    /// Behavior execution failed
59    Failed(String),
60    /// Behavior was skipped (e.g., preconditions not met)
61    Skipped(String),
62}
63
64impl BehaviorResult {
65    pub fn is_success(&self) -> bool {
66        matches!(self, BehaviorResult::Success)
67    }
68
69    pub fn is_failed(&self) -> bool {
70        matches!(self, BehaviorResult::Failed(_))
71    }
72}
73
74/// Behavior trait for composable agent capabilities
75pub trait Behavior: Send + Sync {
76    /// Get behavior name
77    fn name(&self) -> &str;
78
79    /// Get behavior description
80    fn description(&self) -> &str {
81        ""
82    }
83
84    /// Check if behavior can execute in current context
85    fn can_execute(&self, _context: &BehaviorContext) -> bool {
86        true
87    }
88
89    /// Execute the behavior
90    fn execute(&self, context: &BehaviorContext) -> BehaviorResult;
91
92    /// Called when behavior is attached to an agent
93    fn on_attach(&self, _agent_id: AgentId) -> BehaviorResult {
94        BehaviorResult::Success
95    }
96
97    /// Called when behavior is detached from an agent
98    fn on_detach(&self, _agent_id: AgentId) -> BehaviorResult {
99        BehaviorResult::Success
100    }
101}
102
103/// A simple behavior defined by a closure
104pub struct ClosureBehavior {
105    name: String,
106    description: String,
107    executor: Arc<dyn Fn(&BehaviorContext) -> BehaviorResult + Send + Sync>,
108}
109
110impl ClosureBehavior {
111    /// Create a new closure-based behavior
112    pub fn new<F>(name: impl Into<String>, executor: F) -> Self
113    where
114        F: Fn(&BehaviorContext) -> BehaviorResult + Send + Sync + 'static,
115    {
116        Self {
117            name: name.into(),
118            description: String::new(),
119            executor: Arc::new(executor),
120        }
121    }
122
123    /// Set behavior description
124    pub fn with_description(mut self, description: impl Into<String>) -> Self {
125        self.description = description.into();
126        self
127    }
128}
129
130impl Behavior for ClosureBehavior {
131    fn name(&self) -> &str {
132        &self.name
133    }
134
135    fn description(&self) -> &str {
136        &self.description
137    }
138
139    fn execute(&self, context: &BehaviorContext) -> BehaviorResult {
140        (self.executor)(context)
141    }
142}
143
144/// Logging behavior - logs agent state changes
145pub struct LoggingBehavior {
146    name: String,
147}
148
149impl LoggingBehavior {
150    pub fn new() -> Self {
151        Self {
152            name: "logging".to_string(),
153        }
154    }
155}
156
157impl Default for LoggingBehavior {
158    fn default() -> Self {
159        Self::new()
160    }
161}
162
163impl Behavior for LoggingBehavior {
164    fn name(&self) -> &str {
165        &self.name
166    }
167
168    fn description(&self) -> &str {
169        "Logs agent state and activity"
170    }
171
172    fn execute(&self, context: &BehaviorContext) -> BehaviorResult {
173        println!(
174            "[Log] Agent {} in state {:?} at {}",
175            context.agent_id, context.agent_state, context.timestamp
176        );
177        BehaviorResult::Success
178    }
179}
180
181/// Metrics collection behavior
182pub struct MetricsBehavior {
183    name: String,
184    metrics: Arc<RwLock<HashMap<String, u64>>>,
185}
186
187impl MetricsBehavior {
188    pub fn new() -> Self {
189        Self {
190            name: "metrics".to_string(),
191            metrics: Arc::new(RwLock::new(HashMap::new())),
192        }
193    }
194
195    /// Get metric value
196    pub fn get_metric(&self, key: &str) -> Option<u64> {
197        self.metrics
198            .read()
199            .expect("Lock poisoned: metrics")
200            .get(key)
201            .copied()
202    }
203
204    /// Get all metrics
205    pub fn all_metrics(&self) -> HashMap<String, u64> {
206        self.metrics.read().expect("Lock poisoned: metrics").clone()
207    }
208}
209
210impl Default for MetricsBehavior {
211    fn default() -> Self {
212        Self::new()
213    }
214}
215
216impl Behavior for MetricsBehavior {
217    fn name(&self) -> &str {
218        &self.name
219    }
220
221    fn description(&self) -> &str {
222        "Collects agent execution metrics"
223    }
224
225    fn execute(&self, context: &BehaviorContext) -> BehaviorResult {
226        let mut metrics = self.metrics.write().expect("Lock poisoned: metrics");
227        let key = format!("state_{:?}", context.agent_state);
228        *metrics.entry(key).or_insert(0) += 1;
229        *metrics.entry("total_executions".to_string()).or_insert(0) += 1;
230        BehaviorResult::Success
231    }
232}
233
234/// Health check behavior
235pub struct HealthCheckBehavior {
236    name: String,
237    healthy_states: Vec<AgentState>,
238}
239
240impl HealthCheckBehavior {
241    pub fn new() -> Self {
242        Self {
243            name: "health_check".to_string(),
244            healthy_states: vec![AgentState::Running, AgentState::Created],
245        }
246    }
247
248    pub fn with_healthy_states(mut self, states: Vec<AgentState>) -> Self {
249        self.healthy_states = states;
250        self
251    }
252}
253
254impl Default for HealthCheckBehavior {
255    fn default() -> Self {
256        Self::new()
257    }
258}
259
260impl Behavior for HealthCheckBehavior {
261    fn name(&self) -> &str {
262        &self.name
263    }
264
265    fn description(&self) -> &str {
266        "Checks agent health based on state"
267    }
268
269    fn execute(&self, context: &BehaviorContext) -> BehaviorResult {
270        if self.healthy_states.contains(&context.agent_state) {
271            BehaviorResult::Success
272        } else {
273            BehaviorResult::Failed(format!("Unhealthy state: {:?}", context.agent_state))
274        }
275    }
276}
277
278/// Behavior composition - manages multiple behaviors for an agent
279pub struct BehaviorComposite {
280    agent_id: AgentId,
281    behaviors: RwLock<HashMap<BehaviorId, Arc<dyn Behavior>>>,
282    execution_order: RwLock<Vec<BehaviorId>>,
283}
284
285impl BehaviorComposite {
286    /// Create a new behavior composite for an agent
287    pub fn new(agent_id: AgentId) -> Self {
288        Self {
289            agent_id,
290            behaviors: RwLock::new(HashMap::new()),
291            execution_order: RwLock::new(Vec::new()),
292        }
293    }
294
295    /// Attach a behavior
296    pub fn attach(&self, behavior: Arc<dyn Behavior>) -> BehaviorId {
297        let id = Uuid::new_v4();
298
299        // Call on_attach hook
300        behavior.on_attach(self.agent_id);
301
302        // Store behavior
303        self.behaviors
304            .write()
305            .expect("Lock poisoned: behaviors")
306            .insert(id, behavior);
307        self.execution_order
308            .write()
309            .expect("Lock poisoned: execution_order")
310            .push(id);
311
312        id
313    }
314
315    /// Detach a behavior
316    pub fn detach(&self, behavior_id: &BehaviorId) -> Option<Arc<dyn Behavior>> {
317        let behavior = self
318            .behaviors
319            .write()
320            .expect("Lock poisoned: behaviors")
321            .remove(behavior_id)?;
322
323        // Call on_detach hook
324        behavior.on_detach(self.agent_id);
325
326        // Remove from execution order
327        self.execution_order
328            .write()
329            .expect("Lock poisoned: execution_order")
330            .retain(|id| id != behavior_id);
331
332        Some(behavior)
333    }
334
335    /// Get a behavior by ID
336    pub fn get(&self, behavior_id: &BehaviorId) -> Option<Arc<dyn Behavior>> {
337        self.behaviors
338            .read()
339            .expect("Lock poisoned: behaviors")
340            .get(behavior_id)
341            .cloned()
342    }
343
344    /// Execute all behaviors in order
345    pub fn execute_all(&self, context: &BehaviorContext) -> Vec<(BehaviorId, BehaviorResult)> {
346        let order = self
347            .execution_order
348            .read()
349            .expect("Lock poisoned: execution_order");
350        let behaviors = self.behaviors.read().expect("Lock poisoned: behaviors");
351
352        order
353            .iter()
354            .filter_map(|id| {
355                let behavior = behaviors.get(id)?;
356                if behavior.can_execute(context) {
357                    let result = behavior.execute(context);
358                    Some((*id, result))
359                } else {
360                    Some((
361                        *id,
362                        BehaviorResult::Skipped("Preconditions not met".to_string()),
363                    ))
364                }
365            })
366            .collect()
367    }
368
369    /// Execute a specific behavior
370    pub fn execute(
371        &self,
372        behavior_id: &BehaviorId,
373        context: &BehaviorContext,
374    ) -> Option<BehaviorResult> {
375        let behaviors = self.behaviors.read().expect("Lock poisoned: behaviors");
376        let behavior = behaviors.get(behavior_id)?;
377
378        if behavior.can_execute(context) {
379            Some(behavior.execute(context))
380        } else {
381            Some(BehaviorResult::Skipped("Preconditions not met".to_string()))
382        }
383    }
384
385    /// Get number of attached behaviors
386    pub fn count(&self) -> usize {
387        self.behaviors
388            .read()
389            .expect("Lock poisoned: behaviors")
390            .len()
391    }
392
393    /// Get all behavior IDs
394    pub fn behavior_ids(&self) -> Vec<BehaviorId> {
395        self.execution_order
396            .read()
397            .expect("Lock poisoned: execution_order")
398            .clone()
399    }
400
401    /// Get behavior names
402    pub fn behavior_names(&self) -> Vec<String> {
403        let order = self
404            .execution_order
405            .read()
406            .expect("Lock poisoned: execution_order");
407        let behaviors = self.behaviors.read().expect("Lock poisoned: behaviors");
408
409        order
410            .iter()
411            .filter_map(|id| behaviors.get(id).map(|b| b.name().to_string()))
412            .collect()
413    }
414}
415
416/// Registry for managing agent behaviors
417pub struct BehaviorRegistry {
418    /// Agent ID -> BehaviorComposite
419    composites: RwLock<HashMap<AgentId, Arc<BehaviorComposite>>>,
420}
421
422impl BehaviorRegistry {
423    /// Create a new behavior registry
424    pub fn new() -> Self {
425        Self {
426            composites: RwLock::new(HashMap::new()),
427        }
428    }
429
430    /// Register an agent (creates behavior composite)
431    pub fn register_agent(&self, agent_id: AgentId) -> Arc<BehaviorComposite> {
432        let composite = Arc::new(BehaviorComposite::new(agent_id));
433        self.composites
434            .write()
435            .expect("Lock poisoned: composites")
436            .insert(agent_id, Arc::clone(&composite));
437        composite
438    }
439
440    /// Get composite for an agent
441    pub fn get_composite(&self, agent_id: &AgentId) -> Option<Arc<BehaviorComposite>> {
442        self.composites
443            .read()
444            .expect("Lock poisoned: composites")
445            .get(agent_id)
446            .cloned()
447    }
448
449    /// Unregister an agent
450    pub fn unregister_agent(&self, agent_id: &AgentId) -> Option<Arc<BehaviorComposite>> {
451        self.composites
452            .write()
453            .expect("Lock poisoned: composites")
454            .remove(agent_id)
455    }
456
457    /// Execute all behaviors for an agent
458    pub fn execute_for_agent(
459        &self,
460        agent_id: &AgentId,
461        context: &BehaviorContext,
462    ) -> Option<Vec<(BehaviorId, BehaviorResult)>> {
463        let composite = self.get_composite(agent_id)?;
464        Some(composite.execute_all(context))
465    }
466
467    /// Get number of registered agents
468    pub fn agent_count(&self) -> usize {
469        self.composites
470            .read()
471            .expect("Lock poisoned: composites")
472            .len()
473    }
474}
475
476impl Default for BehaviorRegistry {
477    fn default() -> Self {
478        Self::new()
479    }
480}
481
482#[cfg(test)]
483mod tests {
484    use super::*;
485
486    #[test]
487    fn test_behavior_context() {
488        let agent = Agent::new(vec![0x00, 0x61, 0x73, 0x6d]);
489        let context = BehaviorContext::new(&agent);
490
491        assert_eq!(context.agent_id, agent.id());
492        assert_eq!(context.agent_state, AgentState::Created);
493    }
494
495    #[test]
496    fn test_closure_behavior() {
497        let behavior = ClosureBehavior::new("test", |_| BehaviorResult::Success);
498
499        let agent = Agent::new(vec![0x00, 0x61, 0x73, 0x6d]);
500        let context = BehaviorContext::new(&agent);
501
502        let result = behavior.execute(&context);
503        assert!(result.is_success());
504    }
505
506    #[test]
507    fn test_logging_behavior() {
508        let behavior = LoggingBehavior::new();
509        let agent = Agent::new(vec![0x00, 0x61, 0x73, 0x6d]);
510        let context = BehaviorContext::new(&agent);
511
512        let result = behavior.execute(&context);
513        assert!(result.is_success());
514    }
515
516    #[test]
517    fn test_metrics_behavior() {
518        let behavior = MetricsBehavior::new();
519        let agent = Agent::new(vec![0x00, 0x61, 0x73, 0x6d]);
520        let context = BehaviorContext::new(&agent);
521
522        behavior.execute(&context);
523        behavior.execute(&context);
524
525        assert_eq!(behavior.get_metric("total_executions"), Some(2));
526    }
527
528    #[test]
529    fn test_health_check_behavior() {
530        let behavior = HealthCheckBehavior::new();
531        let mut agent = Agent::new(vec![0x00, 0x61, 0x73, 0x6d]);
532
533        // Created is healthy
534        let context = BehaviorContext::new(&agent);
535        let result = behavior.execute(&context);
536        assert!(result.is_success());
537
538        // Error is unhealthy
539        agent.set_state(AgentState::Error);
540        let context = BehaviorContext::new(&agent);
541        let result = behavior.execute(&context);
542        assert!(result.is_failed());
543    }
544
545    #[test]
546    fn test_behavior_composite() {
547        let agent = Agent::new(vec![0x00, 0x61, 0x73, 0x6d]);
548        let composite = BehaviorComposite::new(agent.id());
549
550        // Attach behaviors
551        let behavior1 = Arc::new(LoggingBehavior::new());
552        let behavior2 = Arc::new(MetricsBehavior::new());
553
554        let id1 = composite.attach(behavior1);
555        let _id2 = composite.attach(behavior2);
556
557        assert_eq!(composite.count(), 2);
558
559        // Execute all
560        let context = BehaviorContext::new(&agent);
561        let results = composite.execute_all(&context);
562        assert_eq!(results.len(), 2);
563
564        // Detach
565        composite.detach(&id1);
566        assert_eq!(composite.count(), 1);
567    }
568
569    #[test]
570    fn test_behavior_registry() {
571        let registry = BehaviorRegistry::new();
572        let agent = Agent::new(vec![0x00, 0x61, 0x73, 0x6d]);
573
574        let composite = registry.register_agent(agent.id());
575        assert_eq!(registry.agent_count(), 1);
576
577        // Attach behavior
578        let behavior = Arc::new(LoggingBehavior::new());
579        composite.attach(behavior);
580
581        // Execute
582        let context = BehaviorContext::new(&agent);
583        let results = registry.execute_for_agent(&agent.id(), &context);
584        assert!(results.is_some());
585        assert_eq!(results.unwrap().len(), 1);
586
587        // Unregister
588        registry.unregister_agent(&agent.id());
589        assert_eq!(registry.agent_count(), 0);
590    }
591
592    #[test]
593    fn test_behavior_names() {
594        let agent = Agent::new(vec![0x00, 0x61, 0x73, 0x6d]);
595        let composite = BehaviorComposite::new(agent.id());
596
597        composite.attach(Arc::new(LoggingBehavior::new()));
598        composite.attach(Arc::new(MetricsBehavior::new()));
599        composite.attach(Arc::new(HealthCheckBehavior::new()));
600
601        let names = composite.behavior_names();
602        assert_eq!(names.len(), 3);
603        assert!(names.contains(&"logging".to_string()));
604        assert!(names.contains(&"metrics".to_string()));
605        assert!(names.contains(&"health_check".to_string()));
606    }
607}