Skip to main content

vex_core/
agent.rs

1//! Agent types for VEX
2//!
3//! The core [`Agent`] struct represents a fractal agent in the hierarchy.
4
5use crate::context::ContextPacket;
6use crate::evolution::{Genome, LlmParams};
7use crate::merkle::Hash;
8use chrono::{DateTime, Utc};
9use serde::{Deserialize, Serialize};
10use std::sync::Arc;
11use tokio::sync::RwLock;
12use uuid::Uuid;
13
14/// Unique identifier for an agent
15pub type AgentId = Uuid;
16
17/// Handle to a running agent (thread-safe reference)
18pub type AgentHandle = Arc<RwLock<Agent>>;
19
20/// Configuration for creating a new agent
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct AgentConfig {
23    /// Human-readable name for this agent
24    pub name: String,
25    /// Role description (used in LLM prompts)
26    pub role: String,
27    /// Maximum depth of child agents allowed
28    pub max_depth: u8,
29    /// Whether this agent should spawn a shadow (adversarial) agent
30    pub spawn_shadow: bool,
31}
32
33impl Default for AgentConfig {
34    fn default() -> Self {
35        Self {
36            name: "Agent".to_string(),
37            role: "General purpose assistant".to_string(),
38            max_depth: 3,
39            spawn_shadow: true,
40        }
41    }
42}
43
44/// A fractal agent in the VEX hierarchy
45#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct Agent {
47    /// Unique identifier
48    pub id: AgentId,
49    /// Configuration
50    pub config: AgentConfig,
51    /// Generation number (0 = root, increases with each fork)
52    pub generation: u32,
53    /// Current depth in the hierarchy
54    pub depth: u8,
55    /// The agent's current context/memory
56    pub context: ContextPacket,
57    /// Merkle root of all child context hashes
58    pub merkle_root: Option<Hash>,
59    /// When this agent was created
60    pub created_at: DateTime<Utc>,
61    /// IDs of child agents (not serialized, reconstructed at runtime)
62    #[serde(skip)]
63    pub children: Vec<AgentId>,
64    /// ID of shadow (adversarial) agent if spawned
65    #[serde(skip)]
66    pub shadow_id: Option<AgentId>,
67    /// ID of parent agent (None for root)
68    pub parent_id: Option<AgentId>,
69    /// Fitness score from last evaluation
70    pub fitness: f64,
71    /// Genome encoding agent traits (evolved over generations)
72    pub genome: Genome,
73}
74
75impl Agent {
76    /// Create a new root agent with the given configuration
77    pub fn new(config: AgentConfig) -> Self {
78        // Create genome before moving config into struct
79        let genome = Genome::new(&config.role);
80
81        Self {
82            id: Uuid::new_v4(),
83            config,
84            generation: 0,
85            depth: 0,
86            context: ContextPacket::new(""),
87            merkle_root: None,
88            created_at: Utc::now(),
89            children: Vec::new(),
90            shadow_id: None,
91            parent_id: None,
92            fitness: 0.0,
93            genome,
94        }
95    }
96
97    /// Create a child agent from this agent (inherits parent's genome)
98    pub fn spawn_child(&self, config: AgentConfig) -> Self {
99        // Child inherits parent's genome (will be evolved by orchestrator if enabled)
100        let mut child_genome = self.genome.clone();
101        child_genome.prompt = config.role.clone();
102
103        Self {
104            id: Uuid::new_v4(),
105            config,
106            generation: self.generation + 1,
107            depth: self.depth + 1,
108            context: ContextPacket::new(""),
109            merkle_root: None,
110            created_at: Utc::now(),
111            children: Vec::new(),
112            shadow_id: None,
113            parent_id: Some(self.id),
114            fitness: 0.0,
115            genome: child_genome,
116        }
117    }
118
119    /// Check if this agent can spawn more children
120    pub fn can_spawn(&self) -> bool {
121        self.depth < self.config.max_depth
122    }
123
124    /// Check if this agent is a root agent
125    pub fn is_root(&self) -> bool {
126        self.parent_id.is_none()
127    }
128
129    /// Get LLM parameters derived from this agent's genome
130    pub fn llm_params(&self) -> LlmParams {
131        self.genome.to_llm_params()
132    }
133
134    /// Update genome from an evolved offspring
135    pub fn apply_evolved_genome(&mut self, evolved: Genome) {
136        self.genome = evolved;
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143
144    #[test]
145    fn test_create_agent() {
146        let agent = Agent::new(AgentConfig::default());
147        assert!(agent.is_root());
148        assert_eq!(agent.generation, 0);
149        assert_eq!(agent.depth, 0);
150    }
151
152    #[test]
153    fn test_spawn_child() {
154        let parent = Agent::new(AgentConfig::default());
155        let child = parent.spawn_child(AgentConfig::default());
156
157        assert!(!child.is_root());
158        assert_eq!(child.generation, 1);
159        assert_eq!(child.depth, 1);
160        assert_eq!(child.parent_id, Some(parent.id));
161    }
162}