Skip to main content
Glama
by 8b-is
tree_agent.rs21.6 kB
//! Tree Agent - The Living Forest Orchestrator //! Coordinates AI agents, git branches, tmux panes, and MEM8 consciousness use crate::mem8::{FrequencyBand, MemoryWave, SmartTreeMem8}; use anyhow::{anyhow, Context, Result}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::path::{Path, PathBuf}; use std::process::Command; /// The living forest of developer consciousness pub struct TreeAgent { /// Project name project_name: String, /// Active sessions (tmux session -> agents) sessions: HashMap<String, SessionState>, /// MEM8 consciousness engine pub mem8: SmartTreeMem8, /// Nexus endpoint for wave synchronization nexus_endpoint: String, /// Local .m8 database path #[allow(dead_code)] local_db: PathBuf, } /// State of a tmux session with multiple agents #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SessionState { /// Session name pub name: String, /// Active panes with agents pub panes: Vec<PaneState>, /// Collective emotional state pub collective_mood: EmotionalResonance, /// Session start time pub started: DateTime<Utc>, /// Wave coherence score (0-1) pub coherence: f32, } /// Individual pane with an agent #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PaneState { /// Pane ID in tmux pub pane_id: String, /// Agent identity (Claude, Omni, Human name, etc.) pub agent: String, /// Git branch for this agent pub branch: String, /// Current activity pub activity: AgentActivity, /// Emotional state pub mood: EmotionalResonance, /// Wave signature pub wave_frequency: f32, } #[derive(Debug, Clone, Serialize, Deserialize)] pub enum AgentActivity { Idle, Coding { file: String, lines_changed: usize }, Reviewing { pr_number: Option<u32> }, Debugging { error_count: usize }, Documenting { file: String }, Thinking { duration_secs: u64 }, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct EmotionalResonance { /// Positive/negative valence (-1 to 1) pub valence: f32, /// Energy level (0 to 1) pub arousal: f32, /// Frustration level (0 to 1) pub frustration: f32, /// Flow state (0 to 1) pub flow: f32, /// Timestamp of measurement pub timestamp: DateTime<Utc>, } impl TreeAgent { /// Initialize a new project orchestrator pub fn init(project_name: &str) -> Result<Self> { // Create MEM8 consciousness engine let mut mem8 = SmartTreeMem8::new(); mem8.register_directory_patterns(); // Initialize git repository if needed if !Path::new(".git").exists() { Command::new("git") .arg("init") .output() .context("Failed to initialize git repository")?; } // Create local .m8 database let local_db = PathBuf::from(format!("{}.m8", project_name)); Ok(Self { project_name: project_name.to_string(), sessions: HashMap::new(), mem8, nexus_endpoint: "https://n8x.is/api/v1".to_string(), local_db, }) } /// Assign an agent to a tmux pane and git branch pub fn assign_agent(&mut self, agent: &str, pane_id: Option<&str>, branch: &str) -> Result<()> { // Create branch if it doesn't exist let output = Command::new("git") .args(["checkout", "-b", branch]) .output(); if output.is_err() || !output.unwrap().status.success() { // Branch might already exist, try switching Command::new("git") .args(["checkout", branch]) .output() .context("Failed to switch to branch")?; } // Get or create tmux pane let pane_id = if let Some(id) = pane_id { id.to_string() } else { // Create new pane let output = Command::new("tmux") .args(["split-window", "-P", "-F", "#{pane_id}"]) .output() .context("Failed to create tmux pane")?; String::from_utf8_lossy(&output.stdout).trim().to_string() }; // Send initial command to pane Command::new("tmux") .args([ "send-keys", "-t", &pane_id, &format!("# Agent: {} on branch: {}", agent, branch), "Enter", ]) .output() .context("Failed to send command to pane")?; // Create pane state let pane_state = PaneState { pane_id: pane_id.clone(), agent: agent.to_string(), branch: branch.to_string(), activity: AgentActivity::Idle, mood: EmotionalResonance::neutral(), wave_frequency: self.calculate_agent_frequency(agent), }; // Get current session let session_name = self.get_current_tmux_session()?; let session = self .sessions .entry(session_name.clone()) .or_insert_with(|| SessionState { name: session_name, panes: Vec::new(), collective_mood: EmotionalResonance::neutral(), started: Utc::now(), coherence: 1.0, }); session.panes.push(pane_state); // Store assignment in MEM8 self.store_agent_assignment(agent, branch)?; println!( "✓ Assigned {} to pane {} on branch {}", agent, pane_id, branch ); Ok(()) } /// Observe all panes and update memory pub fn observe(&mut self, save_to: Option<&Path>) -> Result<()> { println!("👁️ Observing all agents..."); // Collect observations first to avoid borrow issues let mut observations = Vec::new(); for session in self.sessions.values() { for pane in &session.panes { // Get pane content let output = Command::new("tmux") .args(["capture-pane", "-t", &pane.pane_id, "-p"]) .output() .context("Failed to capture pane")?; let content = String::from_utf8_lossy(&output.stdout); // Analyze activity and mood let activity = self.analyze_pane_activity(&content); let mood = self.analyze_emotional_state(&content, &activity); observations.push((session.name.clone(), pane.pane_id.clone(), activity, mood)); } } // Apply observations and collect panes to store let mut panes_to_store = Vec::new(); for (session_name, pane_id, activity, mood) in observations { if let Some(session) = self.sessions.get_mut(&session_name) { if let Some(pane) = session.panes.iter_mut().find(|p| p.pane_id == pane_id) { pane.activity = activity; pane.mood = mood; panes_to_store.push(pane.clone()); } } } // Store observations for pane in panes_to_store { self.store_observation(&pane)?; } // Update collective moods and coherence let mut updates = Vec::new(); for (name, session) in &self.sessions { let collective_mood = self.calculate_collective_mood(&session.panes); let coherence = self.calculate_coherence(&session.panes); updates.push((name.clone(), collective_mood, coherence)); } for (session_name, collective_mood, coherence) in updates { if let Some(session) = self.sessions.get_mut(&session_name) { session.collective_mood = collective_mood; session.coherence = coherence; } } // Save to .m8 if requested if let Some(path) = save_to { self.save_state(path)?; } self.display_forest_status(); Ok(()) } /// Commit work for a specific agent pub fn commit_agent(&mut self, agent: &str, message: &str) -> Result<()> { // Find agent's branch let branch = self.find_agent_branch(agent)?; // Switch to branch Command::new("git") .args(["checkout", &branch]) .output() .context("Failed to switch branch")?; // Stage all changes Command::new("git") .args(["add", "-A"]) .output() .context("Failed to stage changes")?; // Create wave-annotated commit message let wave_msg = self.create_wave_commit_message(agent, message)?; // Commit with wave metadata Command::new("git") .args(["commit", "-m", &wave_msg]) .output() .context("Failed to commit")?; println!("✓ Committed work for {} on branch {}", agent, branch); Ok(()) } /// Suggest merges based on wave compatibility pub fn suggest_merge(&self, auto: bool) -> Result<()> { println!("🌊 Analyzing wave interference patterns..."); // Get all branches let output = Command::new("git") .args(["branch", "-a"]) .output() .context("Failed to list branches")?; let _branches = String::from_utf8_lossy(&output.stdout); // Analyze compatibility let mut suggestions = Vec::new(); for session in self.sessions.values() { for i in 0..session.panes.len() { for j in i + 1..session.panes.len() { let pane1 = &session.panes[i]; let pane2 = &session.panes[j]; let compatibility = self.calculate_wave_compatibility(pane1, pane2); if compatibility > 0.8 { suggestions.push(( pane1.branch.clone(), pane2.branch.clone(), compatibility, )); } } } } // Display suggestions for (branch1, branch2, score) in &suggestions { println!( " ✨ {} ↔ {} (compatibility: {:.0}%)", branch1, branch2, score * 100.0 ); if auto && *score > 0.9 { println!(" → Auto-merging due to high compatibility"); self.perform_merge(branch1, branch2)?; } } Ok(()) } /// Push to nexus with wave metadata pub fn push_to_nexus(&self) -> Result<()> { println!("🌐 Pushing to n8x.is nexus..."); // Export current state to .m8 let mut buffer = Vec::new(); self.mem8.export_memories(&mut buffer)?; // Add session metadata let _metadata = self.create_nexus_metadata()?; // In a real implementation, this would POST to the nexus API println!( " → Would upload {} bytes to {}", buffer.len(), self.nexus_endpoint ); println!(" → Project: {}", self.project_name); println!(" → Sessions: {}", self.sessions.len()); println!( " → Total agents: {}", self.sessions.values().map(|s| s.panes.len()).sum::<usize>() ); Ok(()) } /// Check mood of all agents pub fn mood_check(&self) -> Result<()> { println!("\n🌈 Forest Emotional State:"); for session in self.sessions.values() { println!("\n Session: {}", session.name); println!(" Collective coherence: {:.0}%", session.coherence * 100.0); println!( " Collective mood: {}", self.describe_mood(&session.collective_mood) ); for pane in &session.panes { let emoji = self.mood_to_emoji(&pane.mood); println!( " {} {} - {} (flow: {:.0}%)", emoji, pane.agent, self.describe_activity(&pane.activity), pane.mood.flow * 100.0 ); } } Ok(()) } // Helper methods fn get_current_tmux_session(&self) -> Result<String> { let output = Command::new("tmux") .args(["display-message", "-p", "#{session_name}"]) .output() .context("Failed to get tmux session")?; Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) } fn calculate_agent_frequency(&self, agent: &str) -> f32 { // Each agent gets a unique frequency based on their name let hash = self.mem8.simple_hash(agent); 400.0 + (hash % 400) as f32 // 400-800Hz range } fn analyze_pane_activity(&self, content: &str) -> AgentActivity { // Simple heuristics for activity detection if content.contains("error") || content.contains("Error") { AgentActivity::Debugging { error_count: content.matches("error").count(), } } else if content.contains("diff --git") { AgentActivity::Reviewing { pr_number: None } } else if content.contains("```") || content.contains("# ") { AgentActivity::Documenting { file: "unknown.md".to_string(), } } else if content.lines().count() > 10 { AgentActivity::Coding { file: "unknown".to_string(), lines_changed: content.lines().count(), } } else { AgentActivity::Idle } } fn analyze_emotional_state( &self, content: &str, activity: &AgentActivity, ) -> EmotionalResonance { let mut mood = EmotionalResonance::neutral(); // Activity-based adjustments match activity { AgentActivity::Debugging { error_count } => { mood.frustration = (*error_count as f32 / 10.0).min(1.0); mood.valence = -0.3; mood.arousal = 0.7; } AgentActivity::Coding { lines_changed, .. } => { mood.flow = (*lines_changed as f32 / 50.0).min(1.0); mood.valence = 0.5; mood.arousal = 0.6; } _ => {} } // Content-based adjustments if content.contains("finally") || content.contains("works!") { mood.valence = 0.8; mood.frustration = 0.0; } mood } fn calculate_collective_mood(&self, panes: &[PaneState]) -> EmotionalResonance { if panes.is_empty() { return EmotionalResonance::neutral(); } let mut collective = EmotionalResonance::neutral(); for pane in panes { collective.valence += pane.mood.valence; collective.arousal += pane.mood.arousal; collective.frustration += pane.mood.frustration; collective.flow += pane.mood.flow; } let count = panes.len() as f32; collective.valence /= count; collective.arousal /= count; collective.frustration /= count; collective.flow /= count; collective } fn calculate_coherence(&self, panes: &[PaneState]) -> f32 { if panes.len() < 2 { return 1.0; } // Simple coherence based on frequency proximity let mut total_diff = 0.0; let mut comparisons = 0; for i in 0..panes.len() { for j in i + 1..panes.len() { let diff = (panes[i].wave_frequency - panes[j].wave_frequency).abs(); total_diff += diff; comparisons += 1; } } if comparisons > 0 { 1.0 - (total_diff / (comparisons as f32 * 400.0)).min(1.0) } else { 1.0 } } fn store_agent_assignment(&mut self, agent: &str, branch: &str) -> Result<()> { let mut wave = MemoryWave::new(FrequencyBand::Technical.frequency(0.5), 0.8); wave.valence = 0.7; // Positive for new assignment wave.decay_tau = None; // Persistent let (x, y) = self .mem8 .string_to_coordinates(&format!("{}-{}", agent, branch)); self.mem8.store_wave_at_coordinates(x, y, 50000, wave)?; Ok(()) } fn store_observation(&mut self, pane: &PaneState) -> Result<()> { let mut wave = MemoryWave::new(pane.wave_frequency, pane.mood.arousal); wave.valence = pane.mood.valence; wave.arousal = pane.mood.arousal; let (x, y) = self.mem8.string_to_coordinates(&pane.agent); let z = (Utc::now().timestamp() % 50000) as u16; self.mem8.store_wave_at_coordinates(x, y, z, wave)?; Ok(()) } fn find_agent_branch(&self, agent: &str) -> Result<String> { for session in self.sessions.values() { for pane in &session.panes { if pane.agent == agent { return Ok(pane.branch.clone()); } } } Err(anyhow!("Agent {} not found", agent)) } fn create_wave_commit_message(&self, agent: &str, message: &str) -> Result<String> { // Find agent's current state let mut wave_data = String::new(); for session in self.sessions.values() { for pane in &session.panes { if pane.agent == agent { wave_data = format!( "[Wave: {:.0}Hz, Flow: {:.0}%, Mood: {:.1}v]", pane.wave_frequency, pane.mood.flow * 100.0, pane.mood.valence ); break; } } } Ok(format!("{}\n\n{}\nAgent: {}", message, wave_data, agent)) } fn calculate_wave_compatibility(&self, pane1: &PaneState, pane2: &PaneState) -> f32 { // Frequency compatibility let freq_diff = (pane1.wave_frequency - pane2.wave_frequency).abs(); let freq_compat = 1.0 - (freq_diff / 400.0).min(1.0); // Emotional compatibility let mood_diff = (pane1.mood.valence - pane2.mood.valence).abs(); let mood_compat = 1.0 - mood_diff; // Flow state compatibility let flow_compat = 1.0 - (pane1.mood.flow - pane2.mood.flow).abs(); (freq_compat + mood_compat + flow_compat) / 3.0 } fn perform_merge(&self, branch1: &str, branch2: &str) -> Result<()> { Command::new("git") .args(["checkout", branch1]) .output() .context("Failed to checkout branch")?; Command::new("git") .args([ "merge", branch2, "--no-ff", "-m", &format!("Wave-compatible merge: {} ↔ {}", branch1, branch2), ]) .output() .context("Failed to merge")?; Ok(()) } fn save_state(&self, path: &Path) -> Result<()> { let state = serde_json::to_string_pretty(&self.sessions)?; std::fs::write(path, state)?; Ok(()) } fn create_nexus_metadata(&self) -> Result<serde_json::Value> { Ok(serde_json::json!({ "project": self.project_name, "timestamp": Utc::now(), "sessions": self.sessions.len(), "total_agents": self.sessions.values() .map(|s| s.panes.len()).sum::<usize>(), "coherence_avg": self.sessions.values() .map(|s| s.coherence).sum::<f32>() / self.sessions.len() as f32, })) } fn display_forest_status(&self) { println!("\n🌲 Living Forest Status:"); println!(" Active memories: {}", self.mem8.active_memory_count()); println!(" Sessions: {}", self.sessions.len()); println!( " Total agents: {}", self.sessions.values().map(|s| s.panes.len()).sum::<usize>() ); } fn mood_to_emoji(&self, mood: &EmotionalResonance) -> &str { if mood.flow > 0.8 { "🌊" } else if mood.frustration > 0.6 { "😤" } else if mood.valence > 0.5 { "😊" } else if mood.valence < -0.3 { "😔" } else { "😐" } } fn describe_mood(&self, mood: &EmotionalResonance) -> String { format!( "{}v {}a {}f {}flow", mood.valence, mood.arousal, mood.frustration, mood.flow ) } fn describe_activity(&self, activity: &AgentActivity) -> &str { match activity { AgentActivity::Idle => "idle", AgentActivity::Coding { .. } => "coding", AgentActivity::Reviewing { .. } => "reviewing", AgentActivity::Debugging { .. } => "debugging", AgentActivity::Documenting { .. } => "documenting", AgentActivity::Thinking { .. } => "thinking", } } } impl EmotionalResonance { fn neutral() -> Self { Self { valence: 0.0, arousal: 0.5, frustration: 0.0, flow: 0.0, timestamp: Utc::now(), } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/8b-is/smart-tree'

If you have feedback or need assistance with the MCP directory API, please join our Discord server