diff --git a/chrs-agent/Cargo.toml b/chrs-agent/Cargo.toml index 60d80d35..2759954f 100644 --- a/chrs-agent/Cargo.toml +++ b/chrs-agent/Cargo.toml @@ -11,6 +11,9 @@ chrs-council = { path = "../chrs-council" } chrs-backbeat = { path = "../chrs-backbeat" } chrs-exec = { path = "../chrs-exec" } chrs-prompts = { path = "../chrs-prompts" } +chrs-code-edit = { path = "../chrs-code-edit" } +chrs-discovery = { path = "../chrs-discovery" } +git2 = "0.18" tokio = { version = "1", features = ["full"] } serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/chrs-agent/src/lib.rs b/chrs-agent/src/lib.rs index 448c7bf7..8620d094 100644 --- a/chrs-agent/src/lib.rs +++ b/chrs-agent/src/lib.rs @@ -6,6 +6,8 @@ use chrs_council::{CouncilManager, Peer, Role}; use chrs_backbeat::{BeatFrame, StatusClaim}; use chrs_exec::{DockerExecutor, TaskRequest}; use chrs_prompts::get_system_prompt; +use chrs_code_edit::WorktreeManager; +use chrs_discovery::SwarmManager; use chrono::{Utc, DateTime}; use std::path::Path; use std::time::Duration; @@ -13,7 +15,7 @@ use tokio::time::sleep; use std::collections::HashMap; use uuid::Uuid; -/// Represents a CHORUS agent with its mailbox, graph, and council management. +/// Represents a CHORUS agent with its mailbox, graph, council management, and execution engines. pub struct CHORUSAgent { pub id: String, pub role: Role, @@ -21,6 +23,7 @@ pub struct CHORUSAgent { pub graph: DoltGraph, pub council: CouncilManager, pub executor: DockerExecutor, + pub code_edit: Option, pub peers: HashMap, pub last_heartbeat_check: DateTime, pub current_beat: u32, @@ -32,11 +35,19 @@ impl CHORUSAgent { pub async fn init(id: &str, role: Role, base_path: &Path) -> Result> { let mail_path = base_path.join("mail.sqlite"); let graph_path = base_path.join("state_graph"); + let repo_path = base_path.join("workspace_repo"); std::fs::create_dir_all(&graph_path)?; + std::fs::create_dir_all(&repo_path)?; + // Initialize Git repo if it doesn't exist for code-edit + if !repo_path.join(".git").exists() { + let _ = git2::Repository::init(&repo_path)?; + } + let mailbox = Mailbox::open(mail_path)?; let graph = DoltGraph::init(&graph_path)?; + let code_edit = WorktreeManager::open(&repo_path).ok(); // Ensure table exists let _ = graph.create_table("task_log", "id VARCHAR(255) PRIMARY KEY, topic TEXT, payload TEXT, received_at TEXT"); @@ -51,6 +62,11 @@ impl CHORUSAgent { let executor = DockerExecutor::new()?; let system_prompt = get_system_prompt(role).to_string(); + // Start P2P Discovery in background + tokio::spawn(async move { + let _ = SwarmManager::start_discovery_loop().await; + }); + Ok(Self { id: id.to_string(), role, @@ -58,6 +74,7 @@ impl CHORUSAgent { graph, council, executor, + code_edit, peers: HashMap::new(), last_heartbeat_check: Utc::now(), current_beat: 0, @@ -66,13 +83,8 @@ impl CHORUSAgent { } /// Agent's 'thinking' phase where it processes a message using its specialized prompt. - /// - /// **Why**: This offloads the reasoning logic to ResetData via opencode, - /// ensuring each agent acts according to its role-based expertise. - pub async fn think(&self, message: &str) -> String { + pub async fn think(&self, _message: &str) -> String { println!("[AGENT {}] Thinking as {:?}...", self.id, self.role); - // This is where we will call `opencode run` or direct API in the future. - // For now, we return a confirmation of the role-based prompt being active. format!("Reasoning based on: {}", self.system_prompt) } @@ -95,7 +107,7 @@ impl CHORUSAgent { Err(e) => eprintln!("Mailbox beat error: {}", e), } - // 3. Check for Peer Discovery + // 3. Check for Peer Discovery (Mailbox fallback) match self.mailbox.receive_broadcasts("heartbeat", self.last_heartbeat_check) { Ok(messages) => { for msg in messages { @@ -172,10 +184,17 @@ impl CHORUSAgent { } } - // 2. Execution logic (Coder/Developer) + // 2. Execution logic (Coder/Developer) with Isolated Worktree if msg.topic == "implementation_task" || msg.topic == "execution_task" { + println!("[AGENT {}] Preparing workspace for task...", self.id); + + // Spawn task branch + if let Some(mgr) = &self.code_edit { + let _ = mgr.spawn_task_branch(&msg.id.to_string()); + let _ = mgr.checkout_branch(&format!("task/{}", msg.id)); + } + println!("[AGENT {}] Executing task in sandbox...", self.id); - // Before executing, the agent 'thinks' about the task let _reasoning = self.think(&format!("Task: {:?}", msg.payload)).await; let req = TaskRequest {