anya_core/ml/agents/
mod.rs

1// Machine Learning Agents Module
2//
3// This module provides a modular system of ML agents that enhance decision-making
4// across Anya's core functions, including Stacks blockchain operations, DAO governance,
5// and Web5 capabilities. All agents adhere to the core principle of "read first always"
6// to ensure informed decision-making, Bitcoin principles of decentralization, and ethical AI.
7
8use async_trait::async_trait;
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11use std::fmt;
12use std::sync::{Arc, RwLock};
13
14// System maps for global state tracking and indexing
15pub mod system_map;
16pub use system_map::*;
17
18// Re-export agents
19pub mod federated_agent;
20pub use federated_agent::FederatedAgent;
21
22pub mod dao_agent;
23pub use dao_agent::DaoAgent;
24
25// pub mod web5_agent;
26// pub use web5_agent::Web5Agent;
27
28// pub mod stacks_agent;
29// pub use stacks_agent::StacksAgent;
30
31/// Unique identifier for an agent
32#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
33pub struct AgentId(pub String);
34
35impl fmt::Display for AgentId {
36    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37        write!(f, "{}", self.0)
38    }
39}
40
41/// Observation provided to an agent
42#[derive(Debug, Clone)]
43pub enum Observation {
44    /// Text-based observation
45    Text(String),
46
47    /// Numeric observation with a specific metric name
48    Numeric(String, f64),
49
50    /// JSON-structured observation
51    Json(serde_json::Value),
52
53    /// Custom observation with binary data
54    Custom(String, Vec<u8>),
55
56    /// System state observation containing a snapshot of the system's state
57    SystemState(SystemStateRef),
58}
59
60/// Reference to system state for cloneable observations
61#[derive(Debug, Clone)]
62pub struct SystemStateRef {
63    /// Timestamp of the observation
64    pub timestamp: u64,
65    /// Reference identifier for the system state
66    pub state_id: String,
67}
68
69/// Action taken by an agent
70#[derive(Debug, Clone, Serialize, Deserialize)]
71pub enum Action {
72    /// Recommendation for a human operator
73    Recommendation(String),
74
75    /// Notification of an event or insight
76    Notification(String, String), // (title, message)
77
78    /// JSON-structured response
79    Json(serde_json::Value),
80
81    /// Custom action with binary data
82    Custom(String, Vec<u8>),
83
84    /// System update action with payload
85    SystemUpdate(SystemUpdateType, serde_json::Value),
86}
87
88/// Types of system updates
89#[derive(Debug, Clone, Serialize, Deserialize)]
90pub enum SystemUpdateType {
91    /// Update to the global index
92    IndexUpdate,
93
94    /// Update to the system map
95    MapUpdate,
96
97    /// Updates to configuration
98    ConfigUpdate,
99
100    /// Update to agent states
101    AgentStateUpdate,
102}
103
104/// System state observation
105#[derive(Debug)]
106pub struct SystemState {
107    /// Current system index snapshot
108    pub index: Option<SystemIndex>,
109    /// Current system map snapshot  
110    pub map: Option<SystemMap>,
111    /// Timestamp of the observation
112    pub timestamp: u64,
113}
114
115/// Feedback provided to an agent
116#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct Feedback {
118    /// Numerical score for the feedback (0.0 to 1.0)
119    pub score: f32,
120
121    /// Textual description of the feedback
122    pub description: Option<String>,
123
124    /// Source of the feedback (human, another agent, system)
125    pub source: FeedbackSource,
126}
127
128/// Source of feedback
129#[derive(Debug, Clone, Serialize, Deserialize)]
130pub enum FeedbackSource {
131    /// Feedback from a human
132    Human,
133
134    /// Feedback from another agent
135    Agent(AgentId),
136
137    /// Feedback from the system
138    System,
139}
140
141/// Metrics for agent performance
142#[derive(Debug, Clone, Serialize, Deserialize, Default)]
143pub struct AgentMetrics {
144    /// Number of observations processed
145    pub observations_processed: u64,
146
147    /// Number of actions taken
148    pub actions_taken: u64,
149
150    /// Average feedback score
151    pub average_feedback: f32,
152
153    /// Processing time in milliseconds (average)
154    pub avg_processing_time_ms: f64,
155
156    /// Custom metrics
157    pub custom_metrics: HashMap<String, f64>,
158}
159
160/// Error from agent operations
161#[derive(Debug, thiserror::Error)]
162pub enum AgentError {
163    /// Invalid observation format
164    #[error("Invalid observation: {0}")]
165    InvalidObservation(String),
166
167    /// Error during processing
168    #[error("Processing error: {0}")]
169    ProcessingError(String),
170
171    /// Input/output error
172    #[error("I/O error: {0}")]
173    IoError(#[from] std::io::Error),
174
175    /// Serialization/deserialization error
176    #[error("Serialization error: {0}")]
177    SerializationError(#[from] serde_json::Error),
178
179    /// Internal error
180    #[error("Internal error: {0}")]
181    InternalError(String),
182
183    /// Ethical compliance error
184    #[error("Ethical compliance error: {0}")]
185    EthicalComplianceError(String),
186
187    /// System time error
188    #[error("System time error: {0}")]
189    SystemTimeError(#[from] std::time::SystemTimeError),
190
191    /// Version parsing error
192    #[error("Version error: {0}")]
193    VersionError(#[from] semver::Error),
194}
195
196/// The core agent trait that all ML agents must implement
197#[async_trait]
198pub trait Agent: Send + Sync {
199    /// Get the agent's unique identifier
200    fn id(&self) -> &AgentId;
201
202    /// Get the agent's type
203    fn agent_type(&self) -> &str;
204
205    /// Process an observation and optionally return an action
206    async fn process(&self, observation: Observation) -> Result<Option<Action>, AgentError>;
207
208    /// Receive feedback on a previous action
209    async fn receive_feedback(&mut self, feedback: Feedback) -> Result<(), AgentError>;
210
211    /// Get the agent's performance metrics
212    fn metrics(&self) -> AgentMetrics;
213
214    /// Get the agent's ethical compliance score (0.0 to 1.0)
215    fn ethical_compliance(&self) -> f32 {
216        0.8 // Default reasonable compliance score
217    }
218
219    /// Read system state before processing (implements "read first always")
220    async fn read_system_state(&self) -> Result<SystemState, AgentError> {
221        use crate::ml::agents::system_map::{system_index, system_map};
222
223        // Check that we can read the index and map
224        system_index().read_index().await?;
225        system_map().read_map().await?;
226
227        // Return a simple state indicating success
228        Ok(SystemState {
229            index: None, // Can't return actual index due to atomic types
230            map: None,   // Can't return actual map due to non-cloneable types
231            timestamp: std::time::SystemTime::now()
232                .duration_since(std::time::UNIX_EPOCH)
233                .unwrap_or_default()
234                .as_secs(),
235        })
236    }
237
238    /// Update system state after processing
239    async fn update_system_state(&self, updates: &[SystemUpdateType]) -> Result<(), AgentError> {
240        use crate::ml::agents::system_map::{system_index, system_map};
241
242        for update_type in updates {
243            match update_type {
244                SystemUpdateType::IndexUpdate => {
245                    system_index().increment_version().await?;
246                }
247                SystemUpdateType::MapUpdate => {
248                    system_map().update_map().await?;
249                }
250                _ => {} // Other updates handled elsewhere
251            }
252        }
253        Ok(())
254    }
255}
256
257/// Orchestrates multiple agents working together
258pub struct AgentSystem {
259    /// Registered agents by their ID
260    agents: RwLock<HashMap<AgentId, Arc<dyn Agent>>>,
261
262    /// System configuration
263    config: RwLock<AgentSystemConfig>,
264
265    /// System metrics
266    metrics: RwLock<AgentSystemMetrics>,
267}
268
269/// Configuration for the agent system
270#[derive(Debug, Clone, Serialize, Deserialize)]
271pub struct AgentSystemConfig {
272    /// Whether to enforce the "read first always" principle
273    pub enforce_read_first: bool,
274
275    /// Minimum ethical compliance score required for agents
276    pub min_ethical_compliance: f32,
277
278    /// Maximum number of agents that can be registered
279    pub max_agents: usize,
280
281    /// Default timeout for agent processing in milliseconds
282    pub default_timeout_ms: u64,
283}
284
285impl Default for AgentSystemConfig {
286    fn default() -> Self {
287        Self {
288            enforce_read_first: true, // Always enforce read-first by default
289            min_ethical_compliance: 0.7,
290            max_agents: 100,
291            default_timeout_ms: 5000,
292        }
293    }
294}
295
296/// Metrics for the agent system
297#[derive(Debug, Default, Clone, Serialize, Deserialize)]
298pub struct AgentSystemMetrics {
299    /// Total number of observations processed
300    pub total_observations: u64,
301
302    /// Total number of actions taken
303    pub total_actions: u64,
304
305    /// Total number of errors
306    pub total_errors: u64,
307
308    /// Average processing time in milliseconds
309    pub avg_processing_time_ms: f64,
310
311    /// System uptime in seconds
312    pub uptime_seconds: u64,
313}
314
315impl AgentSystem {
316    /// Create a new agent system with default configuration
317    pub fn new() -> Self {
318        Self {
319            agents: RwLock::new(HashMap::new()),
320            config: RwLock::new(AgentSystemConfig::default()),
321            metrics: RwLock::new(AgentSystemMetrics::default()),
322        }
323    }
324
325    /// Create a new agent system with custom configuration
326    pub fn with_config(config: AgentSystemConfig) -> Self {
327        Self {
328            agents: RwLock::new(HashMap::new()),
329            config: RwLock::new(config),
330            metrics: RwLock::new(AgentSystemMetrics::default()),
331        }
332    }
333
334    /// Register a new agent with the system
335    pub async fn register_agent(&self, agent: Arc<dyn Agent>) -> Result<(), AgentError> {
336        let agent_id = agent.id().clone();
337        let config = self.config.read().map_err(|_| {
338            AgentError::InternalError("Failed to acquire read lock on config".to_string())
339        })?;
340
341        // Check ethical compliance
342        let compliance = agent.ethical_compliance();
343        if compliance < config.min_ethical_compliance {
344            return Err(AgentError::EthicalComplianceError(format!(
345                "Agent {} has insufficient ethical compliance score: {} < {}",
346                agent_id, compliance, config.min_ethical_compliance
347            )));
348        }
349
350        // Register the agent
351        let mut agents = self.agents.write().map_err(|_| {
352            AgentError::InternalError("Failed to acquire write lock on agents".to_string())
353        })?;
354
355        if agents.len() >= config.max_agents {
356            return Err(AgentError::ProcessingError(format!(
357                "Maximum number of agents ({}) reached",
358                config.max_agents
359            )));
360        }
361
362        agents.insert(agent_id, agent);
363
364        Ok(())
365    }
366
367    /// Unregister an agent from the system
368    pub async fn unregister_agent(&self, agent_id: &AgentId) -> Result<(), AgentError> {
369        let mut agents = self.agents.write().map_err(|_| {
370            AgentError::InternalError("Failed to acquire write lock on agents".to_string())
371        })?;
372
373        if agents.remove(agent_id).is_none() {
374            return Err(AgentError::ProcessingError(format!(
375                "Agent {agent_id} not found"
376            )));
377        }
378
379        Ok(())
380    }
381
382    /// Process an observation with a specific agent
383    pub async fn process_with_agent(
384        &self,
385        agent_id: &AgentId,
386        observation: Observation,
387    ) -> Result<Option<Action>, AgentError> {
388        // Get agent and config values without holding the locks through await points
389        let (agent, enforce_read_first) = {
390            // Get agent reference
391            let agents = self.agents.read().map_err(|_| {
392                AgentError::InternalError("Failed to acquire read lock on agents".to_string())
393            })?;
394
395            let agent = agents
396                .get(agent_id)
397                .ok_or_else(|| AgentError::ProcessingError(format!("Agent {agent_id} not found")))?
398                .clone();
399
400            // Get config value
401            let config = self.config.read().map_err(|_| {
402                AgentError::InternalError("Failed to acquire read lock on config".to_string())
403            })?;
404
405            (agent, config.enforce_read_first)
406        };
407
408        // Enforce read-first principle if configured
409        if enforce_read_first {
410            // First read the current system state
411            let _system_state = agent.read_system_state().await?;
412
413            // Then process with the original observation plus system state
414            let combined_observation = match observation {
415                Observation::SystemState(_) => observation,
416                _ => Observation::SystemState(SystemStateRef {
417                    timestamp: chrono::Utc::now().timestamp() as u64,
418                    state_id: format!("state_{}", chrono::Utc::now().timestamp()),
419                }),
420            };
421
422            let start_time = std::time::Instant::now();
423            let result = agent.process(combined_observation).await;
424            let processing_time = start_time.elapsed();
425
426            // Update metrics
427            {
428                let mut metrics = self.metrics.write().map_err(|_| {
429                    AgentError::InternalError("Failed to acquire write lock on metrics".to_string())
430                })?;
431
432                metrics.total_observations += 1;
433                if result.is_ok() && result.as_ref().unwrap().is_some() {
434                    metrics.total_actions += 1;
435                }
436                if result.is_err() {
437                    metrics.total_errors += 1;
438                }
439
440                // Update average processing time
441                let current_avg = metrics.avg_processing_time_ms;
442                let current_count = metrics.total_observations;
443                metrics.avg_processing_time_ms = (current_avg * (current_count - 1) as f64
444                    + processing_time.as_millis() as f64)
445                    / current_count as f64;
446            }
447
448            // Update system state after processing if there was an action
449            if let Ok(Some(_)) = &result {
450                agent
451                    .update_system_state(&[
452                        SystemUpdateType::IndexUpdate,
453                        SystemUpdateType::MapUpdate,
454                    ])
455                    .await?;
456            }
457
458            result
459        } else {
460            // Standard processing without enforcing read-first
461            agent.process(observation).await
462        }
463    }
464
465    /// Broadcast an observation to all agents and collect their actions
466    pub async fn broadcast(
467        &self,
468        observation: Observation,
469    ) -> HashMap<AgentId, Result<Option<Action>, AgentError>> {
470        // Get agent IDs without holding the lock through await points
471        let agent_ids = {
472            let agents = match self.agents.read() {
473                Ok(agents) => agents,
474                Err(_) => return HashMap::new(),
475            };
476
477            // Collect agent IDs to process after releasing the lock
478            agents.keys().cloned().collect::<Vec<_>>()
479        };
480
481        let mut results = HashMap::new();
482
483        for agent_id in agent_ids {
484            let result = self
485                .process_with_agent(&agent_id, observation.clone())
486                .await;
487            results.insert(agent_id, result);
488        }
489
490        results
491    }
492
493    /// Get the configuration
494    pub fn config(&self) -> Result<AgentSystemConfig, AgentError> {
495        self.config.read().map(|c| c.clone()).map_err(|_| {
496            AgentError::InternalError("Failed to acquire read lock on config".to_string())
497        })
498    }
499
500    /// Update the configuration
501    pub fn update_config(&self, config: AgentSystemConfig) -> Result<(), AgentError> {
502        let mut current_config = self.config.write().map_err(|_| {
503            AgentError::InternalError("Failed to acquire write lock on config".to_string())
504        })?;
505
506        *current_config = config;
507
508        Ok(())
509    }
510
511    /// Get the system metrics
512    pub fn metrics(&self) -> Result<AgentSystemMetrics, AgentError> {
513        self.metrics.read().map(|m| m.clone()).map_err(|_| {
514            AgentError::InternalError("Failed to acquire read lock on metrics".to_string())
515        })
516    }
517}
518
519impl Default for AgentSystem {
520    fn default() -> Self {
521        Self::new()
522    }
523}
524
525#[allow(dead_code)]
526pub struct MLAgentCoordinator {
527    agents: Vec<Box<dyn Agent>>,
528    // Add basic placeholders for missing types
529}
530
531/// Basic resource pool placeholder
532#[allow(dead_code)]
533pub struct ResourcePool {
534    capacity: usize,
535}
536
537/// Basic health monitor placeholder
538#[allow(dead_code)]
539pub struct HealthMonitor {
540    is_healthy: bool,
541}
542
543#[cfg(test)]
544mod tests {
545    #[tokio::test]
546    async fn test_agent_system_registration() {
547        // Test agent registration and unregistration
548    }
549
550    #[tokio::test]
551    async fn test_read_first_principle() {
552        // Test that the read-first principle is enforced
553    }
554
555    #[tokio::test]
556    async fn test_ethical_compliance() {
557        // Test that ethical compliance is properly enforced
558    }
559}