//! Multi-agent integration test: spawn 6 agents, send messages, verify all respond. //! //! Run with: GROQ_API_KEY=gsk_... cargo test -p openfang-kernel --test multi_agent_test -- --nocapture use openfang_kernel::OpenFangKernel; use openfang_types::agent::AgentManifest; use openfang_types::config::{DefaultModelConfig, KernelConfig}; fn test_config() -> KernelConfig { let tmp = std::env::temp_dir().join("openfang-multi-agent-test"); let _ = std::fs::remove_dir_all(&tmp); std::fs::create_dir_all(&tmp).unwrap(); KernelConfig { home_dir: tmp.clone(), data_dir: tmp.join("data"), default_model: DefaultModelConfig { provider: "groq".to_string(), model: "llama-3.3-70b-versatile".to_string(), api_key_env: "GROQ_API_KEY".to_string(), base_url: None, }, ..KernelConfig::default() } } fn load_manifest(toml_str: &str) -> AgentManifest { toml::from_str(toml_str).expect("Should parse manifest") } #[tokio::test] async fn test_six_agent_fleet() { if std::env::var("GROQ_API_KEY").is_err() { eprintln!("GROQ_API_KEY not set, skipping multi-agent test"); return; } let kernel = OpenFangKernel::boot_with_config(test_config()).expect("Kernel should boot"); // Define all 6 agents with different roles and models let agents = vec![ ( "coder", r#" name = "coder" module = "builtin:chat" [model] provider = "groq" model = "llama-3.3-70b-versatile" system_prompt = "You are Coder. Reply with 'CODER:' prefix. Be concise." [capabilities] tools = ["file_read", "file_write"] memory_read = ["*"] memory_write = ["self.*"] "#, "Write a one-line Rust function that adds two numbers.", ), ( "researcher", r#" name = "researcher" module = "builtin:chat" [model] provider = "groq" model = "llama-3.3-70b-versatile" system_prompt = "You are Researcher. Reply with 'RESEARCHER:' prefix. Be concise." [capabilities] tools = ["web_fetch"] memory_read = ["*"] memory_write = ["self.*"] "#, "What is Rust's primary advantage over C++? One sentence.", ), ( "writer", r#" name = "writer" module = "builtin:chat" [model] provider = "groq" model = "llama-3.3-70b-versatile" system_prompt = "You are Writer. Reply with 'WRITER:' prefix. Be concise." [capabilities] tools = ["file_read", "file_write"] memory_read = ["*"] memory_write = ["self.*"] "#, "Write a one-sentence tagline for an Agent Operating System.", ), ( "ops", r#" name = "ops" module = "builtin:chat" [model] provider = "groq" model = "llama-3.1-8b-instant" system_prompt = "You are Ops. Reply with 'OPS:' prefix. Be concise." [capabilities] tools = ["shell_exec"] memory_read = ["*"] memory_write = ["self.*"] "#, "What would you check first if a server is running slowly?", ), ( "analyst", r#" name = "analyst" module = "builtin:chat" [model] provider = "groq" model = "llama-3.3-70b-versatile" system_prompt = "You are Analyst. Reply with 'ANALYST:' prefix. Be concise." [capabilities] tools = ["file_read"] memory_read = ["*"] memory_write = ["self.*"] "#, "What are the top 3 metrics to track for an API service?", ), ( "hello-world", r#" name = "hello-world" module = "builtin:chat" [model] provider = "groq" model = "llama-3.1-8b-instant" system_prompt = "You are a friendly greeter. Reply with 'HELLO:' prefix. Be concise." [capabilities] memory_read = ["*"] memory_write = ["self.*"] "#, "Greet the user in a fun way.", ), ]; println!("\n{}", "=".repeat(60)); println!(" OPENFANG MULTI-AGENT FLEET TEST"); println!(" Spawning {} agents...", agents.len()); println!("{}\n", "=".repeat(60)); // Spawn all agents let mut agent_ids = Vec::new(); for (name, manifest_str, _) in &agents { let manifest = load_manifest(manifest_str); let id = kernel .spawn_agent(manifest) .unwrap_or_else(|e| panic!("Failed to spawn {name}: {e}")); println!(" Spawned: {name:<12} -> {id}"); agent_ids.push(id); } assert_eq!(kernel.registry.count(), 6, "Should have 6 agents"); println!( "\n All {} agents spawned. Sending messages...\n", agents.len() ); // Send messages to each agent sequentially (to respect Groq rate limits) let mut results = Vec::new(); for (i, (name, _, message)) in agents.iter().enumerate() { let result = kernel .send_message(agent_ids[i], message) .await .unwrap_or_else(|e| panic!("Failed to message {name}: {e}")); println!("--- {name} ---"); println!(" Q: {message}"); println!(" A: {}", result.response); println!( " [{} tokens in, {} tokens out, {} iters]", result.total_usage.input_tokens, result.total_usage.output_tokens, result.iterations ); println!(); assert!( !result.response.is_empty(), "{name} response should not be empty" ); results.push(result); } // Summary let total_input: u64 = results.iter().map(|r| r.total_usage.input_tokens).sum(); let total_output: u64 = results.iter().map(|r| r.total_usage.output_tokens).sum(); println!("============================================================"); println!(" FLEET SUMMARY"); println!(" Agents: {}", agents.len()); println!(" Total input: {} tokens", total_input); println!(" Total output: {} tokens", total_output); println!(" All responded: YES"); println!("============================================================"); // Cleanup for id in agent_ids { kernel.kill_agent(id).unwrap(); } kernel.shutdown(); }