ruvswarm_core/
agent.rs

1//! Core agent trait and related types
2
3use async_trait::async_trait;
4use core::fmt;
5use serde::{Deserialize, Serialize};
6
7#[cfg(not(feature = "std"))]
8use alloc::{
9    boxed::Box,
10    string::{String, ToString},
11    vec::Vec,
12};
13
14use crate::error::Result;
15
16/// Unique identifier for an agent
17pub type AgentId = String;
18
19/// Agent status
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
21pub enum AgentStatus {
22    /// Agent is idle and ready for tasks
23    Idle,
24    /// Agent is running and ready for tasks
25    Running,
26    /// Agent is busy processing a task
27    Busy,
28    /// Agent is offline or unavailable
29    Offline,
30    /// Agent is in error state
31    Error,
32}
33
34/// Agent configuration
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct AgentConfig {
37    /// Agent ID
38    pub id: AgentId,
39    /// Agent capabilities
40    pub capabilities: Vec<String>,
41    /// Maximum concurrent tasks
42    pub max_concurrent_tasks: usize,
43    /// Resource limits
44    pub resource_limits: Option<ResourceRequirements>,
45}
46
47/// Core trait for all swarm agents
48#[async_trait]
49pub trait Agent: Send + Sync {
50    /// Input type for this agent
51    type Input: Send;
52
53    /// Output type for this agent
54    type Output: Send;
55
56    /// Error type for this agent
57    type Error: fmt::Debug + Send;
58
59    /// Process an input and produce an output
60    async fn process(
61        &mut self,
62        input: Self::Input,
63    ) -> core::result::Result<Self::Output, Self::Error>;
64
65    /// Get agent capabilities
66    fn capabilities(&self) -> &[String];
67
68    /// Get unique agent identifier
69    fn id(&self) -> &str;
70
71    /// Get agent metadata
72    fn metadata(&self) -> AgentMetadata {
73        AgentMetadata::default()
74    }
75
76    /// Check if agent can handle a specific capability
77    fn has_capability(&self, capability: &str) -> bool {
78        self.capabilities().iter().any(|c| c == capability)
79    }
80
81    /// Agent health check
82    async fn health_check(&self) -> Result<HealthStatus> {
83        Ok(HealthStatus::Healthy)
84    }
85
86    /// Get current agent status
87    fn status(&self) -> AgentStatus {
88        AgentStatus::Running
89    }
90
91    /// Check if agent can handle a specific task
92    fn can_handle(&self, task: &crate::task::Task) -> bool {
93        task.required_capabilities
94            .iter()
95            .all(|cap| self.has_capability(cap))
96    }
97
98    /// Lifecycle: Start the agent
99    async fn start(&mut self) -> Result<()> {
100        self.initialize().await
101    }
102
103    /// Lifecycle: Initialize the agent
104    async fn initialize(&mut self) -> Result<()> {
105        Ok(())
106    }
107
108    /// Lifecycle: Shutdown the agent
109    async fn shutdown(&mut self) -> Result<()> {
110        Ok(())
111    }
112}
113
114/// Agent metadata
115#[derive(Debug, Clone, Serialize, Deserialize)]
116pub struct AgentMetadata {
117    /// Agent name
118    pub name: String,
119
120    /// Agent version
121    pub version: String,
122
123    /// Agent description
124    pub description: String,
125
126    /// Cognitive pattern
127    pub cognitive_pattern: CognitivePattern,
128
129    /// Resource requirements
130    pub resources: ResourceRequirements,
131
132    /// Performance metrics
133    pub metrics: AgentMetrics,
134}
135
136impl Default for AgentMetadata {
137    fn default() -> Self {
138        AgentMetadata {
139            name: "Unknown".to_string(),
140            version: "0.0.0".to_string(),
141            description: "No description".to_string(),
142            cognitive_pattern: CognitivePattern::Convergent,
143            resources: ResourceRequirements::default(),
144            metrics: AgentMetrics::default(),
145        }
146    }
147}
148
149/// Cognitive patterns for agent behavior
150#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
151pub enum CognitivePattern {
152    /// Convergent thinking - focused, analytical
153    Convergent,
154    /// Divergent thinking - creative, exploratory
155    Divergent,
156    /// Lateral thinking - unconventional approaches
157    Lateral,
158    /// Systems thinking - holistic, interconnected
159    Systems,
160    /// Critical thinking - evaluative, questioning
161    Critical,
162    /// Abstract thinking - conceptual, theoretical
163    Abstract,
164}
165
166impl CognitivePattern {
167    /// Get all available patterns
168    pub fn all() -> &'static [CognitivePattern] {
169        &[
170            CognitivePattern::Convergent,
171            CognitivePattern::Divergent,
172            CognitivePattern::Lateral,
173            CognitivePattern::Systems,
174            CognitivePattern::Critical,
175            CognitivePattern::Abstract,
176        ]
177    }
178
179    /// Get complementary pattern
180    #[must_use]
181    pub fn complement(&self) -> CognitivePattern {
182        match self {
183            CognitivePattern::Convergent => CognitivePattern::Divergent,
184            CognitivePattern::Divergent => CognitivePattern::Convergent,
185            CognitivePattern::Lateral => CognitivePattern::Systems,
186            CognitivePattern::Systems => CognitivePattern::Lateral,
187            CognitivePattern::Critical => CognitivePattern::Abstract,
188            CognitivePattern::Abstract => CognitivePattern::Critical,
189        }
190    }
191}
192
193/// Agent health status
194#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
195pub enum HealthStatus {
196    /// Agent is healthy and ready
197    Healthy,
198    /// Agent is degraded but operational
199    Degraded,
200    /// Agent is unhealthy and should not receive tasks
201    Unhealthy,
202    /// Agent is shutting down
203    Stopping,
204}
205
206/// Resource requirements for an agent
207#[derive(Debug, Clone, Serialize, Deserialize)]
208pub struct ResourceRequirements {
209    /// Minimum memory in MB
210    pub min_memory_mb: u32,
211
212    /// Maximum memory in MB
213    pub max_memory_mb: u32,
214
215    /// CPU cores required
216    pub cpu_cores: f32,
217
218    /// GPU required
219    pub requires_gpu: bool,
220
221    /// Network bandwidth in Mbps
222    pub network_bandwidth_mbps: u32,
223}
224
225impl Default for ResourceRequirements {
226    fn default() -> Self {
227        ResourceRequirements {
228            min_memory_mb: 128,
229            max_memory_mb: 512,
230            cpu_cores: 0.5,
231            requires_gpu: false,
232            network_bandwidth_mbps: 10,
233        }
234    }
235}
236
237/// Agent performance metrics
238#[derive(Debug, Clone, Serialize, Deserialize)]
239pub struct AgentMetrics {
240    /// Total tasks processed
241    pub tasks_processed: u64,
242
243    /// Tasks succeeded
244    pub tasks_succeeded: u64,
245
246    /// Tasks failed
247    pub tasks_failed: u64,
248
249    /// Average processing time in ms
250    pub avg_processing_time_ms: f64,
251
252    /// Current queue size
253    pub queue_size: usize,
254
255    /// Uptime in seconds
256    #[cfg(feature = "std")]
257    pub uptime_seconds: u64,
258}
259
260impl Default for AgentMetrics {
261    fn default() -> Self {
262        AgentMetrics {
263            tasks_processed: 0,
264            tasks_succeeded: 0,
265            tasks_failed: 0,
266            avg_processing_time_ms: 0.0,
267            queue_size: 0,
268            #[cfg(feature = "std")]
269            uptime_seconds: 0,
270        }
271    }
272}
273
274/// Boxed agent type for dynamic dispatch
275pub type BoxedAgent<I, O, E> = Box<dyn Agent<Input = I, Output = O, Error = E>>;
276
277/// Type-erased agent trait for heterogeneous collections
278#[async_trait]
279pub trait ErasedAgent: Send + Sync {
280    /// Get unique agent identifier
281    fn id(&self) -> &str;
282
283    /// Get agent capabilities
284    fn capabilities(&self) -> &[String];
285
286    /// Check if agent has a specific capability
287    #[inline]
288    fn has_capability(&self, capability: &str) -> bool {
289        self.capabilities().iter().any(|c| c == capability)
290    }
291
292    /// Get current agent status
293    fn status(&self) -> AgentStatus;
294
295    /// Check if agent can handle a specific task
296    fn can_handle(&self, task: &crate::task::Task) -> bool {
297        task.required_capabilities
298            .iter()
299            .all(|cap| self.has_capability(cap))
300    }
301
302    /// Get agent metadata
303    fn metadata(&self) -> AgentMetadata {
304        AgentMetadata::default()
305    }
306
307    /// Agent health check
308    async fn health_check(&self) -> Result<HealthStatus> {
309        Ok(HealthStatus::Healthy)
310    }
311
312    /// Lifecycle: Start the agent
313    async fn start(&mut self) -> Result<()> {
314        Ok(())
315    }
316
317    /// Lifecycle: Shutdown the agent
318    async fn shutdown(&mut self) -> Result<()> {
319        Ok(())
320    }
321
322    /// Process a JSON value (type-erased)
323    async fn process_json(&mut self, input: serde_json::Value) -> Result<serde_json::Value>;
324}
325
326/// Agent wrapper for type erasure
327pub struct DynamicAgent {
328    id: String,
329    capabilities: Vec<String>,
330    metadata: AgentMetadata,
331    status: AgentStatus,
332    processor: Box<dyn AgentProcessor>,
333}
334
335impl DynamicAgent {
336    /// Create a new dynamic agent
337    pub fn new(id: impl Into<String>, capabilities: Vec<String>) -> Self {
338        DynamicAgent {
339            id: id.into(),
340            capabilities,
341            metadata: AgentMetadata::default(),
342            status: AgentStatus::Running,
343            processor: Box::new(DefaultProcessor),
344        }
345    }
346
347    /// Get agent ID
348    pub fn id(&self) -> &str {
349        &self.id
350    }
351
352    /// Get agent capabilities  
353    pub fn capabilities(&self) -> &[String] {
354        &self.capabilities
355    }
356
357    /// Get agent status
358    pub fn status(&self) -> AgentStatus {
359        self.status
360    }
361
362    /// Set agent status
363    pub fn set_status(&mut self, status: AgentStatus) {
364        self.status = status;
365    }
366
367    /// Check if agent can handle a task
368    pub fn can_handle(&self, task: &crate::task::Task) -> bool {
369        task.required_capabilities
370            .iter()
371            .all(|cap| self.capabilities.contains(cap))
372    }
373
374    /// Check if agent has capability
375    pub fn has_capability(&self, capability: &str) -> bool {
376        self.capabilities.iter().any(|c| c == capability)
377    }
378
379    /// Start the agent
380    /// 
381    /// # Errors
382    /// 
383    /// Currently does not return errors, but may in future implementations.
384    #[allow(clippy::unused_async)]
385    pub async fn start(&mut self) -> crate::error::Result<()> {
386        self.status = AgentStatus::Running;
387        Ok(())
388    }
389
390    /// Shutdown the agent
391    /// 
392    /// # Errors
393    /// 
394    /// Currently does not return errors, but may in future implementations.
395    #[allow(clippy::unused_async)]
396    pub async fn shutdown(&mut self) -> crate::error::Result<()> {
397        self.status = AgentStatus::Offline;
398        Ok(())
399    }
400}
401
402/// Default processor for dynamic agents
403struct DefaultProcessor;
404
405#[async_trait]
406impl AgentProcessor for DefaultProcessor {
407    async fn process_dynamic(
408        &mut self,
409        input: serde_json::Value,
410    ) -> crate::error::Result<serde_json::Value> {
411        Ok(input)
412    }
413}
414
415/// Internal trait for type-erased processing
416#[async_trait]
417trait AgentProcessor: Send + Sync {
418    async fn process_dynamic(&mut self, input: serde_json::Value) -> Result<serde_json::Value>;
419}
420
421/// Agent capability description
422#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
423pub struct Capability {
424    /// Capability name
425    pub name: String,
426    /// Capability version
427    pub version: String,
428}
429
430impl Capability {
431    /// Create a new capability
432    pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
433        Self {
434            name: name.into(),
435            version: version.into(),
436        }
437    }
438}
439
440#[cfg(test)]
441/// Mock agent for testing
442pub struct MockAgent {
443    id: String,
444    status: AgentStatus,
445    capabilities: Vec<Capability>,
446    process_result: Option<crate::error::Result<crate::task::TaskResult>>,
447}
448
449#[cfg(test)]
450impl MockAgent {
451    /// Create a new mock agent
452    pub fn new(id: impl Into<String>) -> Self {
453        Self {
454            id: id.into(),
455            status: AgentStatus::Idle,
456            capabilities: Vec::new(),
457            process_result: None,
458        }
459    }
460
461    /// Set capabilities for the mock agent
462    #[must_use]
463    pub fn with_capabilities(mut self, capabilities: Vec<Capability>) -> Self {
464        self.capabilities = capabilities;
465        self
466    }
467
468    /// Set the process result for the mock agent
469    #[must_use]
470    pub fn with_process_result(mut self, result: crate::error::Result<crate::task::TaskResult>) -> Self {
471        self.process_result = Some(result);
472        self
473    }
474
475    /// Get agent ID
476    pub fn id(&self) -> &str {
477        &self.id
478    }
479
480    /// Get agent status
481    pub fn status(&self) -> AgentStatus {
482        self.status
483    }
484
485    /// Get agent capabilities
486    pub fn capabilities(&self) -> &[Capability] {
487        &self.capabilities
488    }
489
490    /// Check if agent can handle a task
491    pub fn can_handle(&self, task: &crate::task::Task) -> bool {
492        task.required_capabilities
493            .iter()
494            .all(|cap| self.capabilities.iter().any(|c| &c.name == cap))
495    }
496
497    /// Start the agent
498    /// 
499    /// # Errors
500    /// 
501    /// Currently does not return errors, but may in future implementations.
502    #[allow(clippy::unused_async)]
503    pub async fn start(&mut self) -> crate::error::Result<()> {
504        self.status = AgentStatus::Running;
505        Ok(())
506    }
507
508    /// Shutdown the agent
509    /// 
510    /// # Errors
511    /// 
512    /// Currently does not return errors, but may in future implementations.
513    #[allow(clippy::unused_async)]
514    pub async fn shutdown(&mut self) -> crate::error::Result<()> {
515        self.status = AgentStatus::Offline;
516        Ok(())
517    }
518
519    /// Process a task
520    /// 
521    /// # Errors
522    /// 
523    /// Returns the configured result if set via `with_process_result`, otherwise returns success.
524    #[allow(clippy::unused_async)]
525    pub async fn process(&mut self, _task: crate::task::Task) -> crate::error::Result<crate::task::TaskResult> {
526        if let Some(result) = &self.process_result {
527            result.clone()
528        } else {
529            Ok(crate::task::TaskResult::success("Mock processing complete"))
530        }
531    }
532
533    /// Get agent metrics
534    pub fn metrics(&self) -> AgentMetrics {
535        AgentMetrics::default()
536    }
537    
538    /// Health check for the agent
539    /// 
540    /// # Errors
541    /// 
542    /// Currently does not return errors, but may in future implementations.
543    #[allow(clippy::unused_async)]
544    pub async fn health_check(&self) -> crate::error::Result<HealthStatus> {
545        Ok(HealthStatus::Healthy)
546    }
547    
548    /// Get agent metadata
549    pub fn metadata(&self) -> AgentMetadata {
550        AgentMetadata::default()
551    }
552}
553
554/// Agent communication message
555#[derive(Debug, Clone, Serialize, Deserialize)]
556pub struct AgentMessage<T> {
557    /// Source agent ID
558    pub from: String,
559
560    /// Target agent ID
561    pub to: String,
562
563    /// Message payload
564    pub payload: T,
565
566    /// Message type
567    pub msg_type: MessageType,
568
569    /// Correlation ID for request/response
570    pub correlation_id: Option<String>,
571}
572
573/// Types of agent messages
574#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
575pub enum MessageType {
576    /// Task assignment
577    TaskAssignment,
578    /// Task result
579    TaskResult,
580    /// Status update
581    StatusUpdate,
582    /// Coordination message
583    Coordination,
584    /// Error report
585    Error,
586}
587
588#[cfg(test)]
589mod tests {
590    use super::*;
591
592    #[test]
593    fn test_cognitive_pattern_complement() {
594        assert_eq!(
595            CognitivePattern::Convergent.complement(),
596            CognitivePattern::Divergent
597        );
598        assert_eq!(
599            CognitivePattern::Divergent.complement(),
600            CognitivePattern::Convergent
601        );
602        assert_eq!(
603            CognitivePattern::Lateral.complement(),
604            CognitivePattern::Systems
605        );
606    }
607
608    #[test]
609    fn test_agent_metadata_default() {
610        let metadata = AgentMetadata::default();
611        assert_eq!(metadata.name, "Unknown");
612        assert_eq!(metadata.cognitive_pattern, CognitivePattern::Convergent);
613    }
614}