use agent_teams::backend::claude_code::ClaudeCodeBackend;
use agent_teams::backend::codex::CodexBackend;
use agent_teams::backend::{BackendType, SpawnConfig};
use agent_teams::messaging::{FileInboxManager, InboxManager};
use agent_teams::models::CreateTaskRequest;
use agent_teams::orchestrator::TeamOrchestrator;
#[tokio::main]
async fn main() -> agent_teams::Result<()> {
tracing_subscriber::fmt()
.with_env_filter("agent_teams=debug,info")
.init();
let tmp = tempfile::tempdir().expect("tempdir");
let teams_dir = tmp.path().join("teams");
let tasks_dir = tmp.path().join("tasks");
println!("╔══════════════════════════════════════════════╗");
println!("║ Live Mixed-Backend Test ║");
println!("║ Claude Code + Codex ║");
println!("╚══════════════════════════════════════════════╝\n");
println!("[1/7] Building orchestrator with real backends...");
let codex_backend = CodexBackend::with_path("/opt/homebrew/bin/codex");
let orch = TeamOrchestrator::builder()
.teams_base(&teams_dir)
.tasks_base(&tasks_dir)
.with_claude_code(ClaudeCodeBackend::new())
.with_codex(codex_backend)
.build()?;
println!(" ✓ Orchestrator ready\n");
println!("[2/7] Creating team...");
orch.create_team("live-test", Some("Live mixed-backend test"))
.await?;
println!(" ✓ Team 'live-test' created\n");
println!("[3/7] Creating tasks...");
let t_claude = orch
.create_task(
"live-test",
CreateTaskRequest {
subject: "Explain Rust ownership in one sentence".into(),
description: Some(
"Give a single-sentence explanation of Rust's ownership model."
.into(),
),
active_form: Some("Explaining ownership".into()),
metadata: None,
},
)
.await?;
let t_codex = orch
.create_task(
"live-test",
CreateTaskRequest {
subject: "Write a hello world function in Rust".into(),
description: Some(
"Write a simple Rust function that prints 'Hello from Codex!'. Just the function, no explanation needed."
.into(),
),
active_form: Some("Writing hello world".into()),
metadata: None,
},
)
.await?;
println!(" ✓ Task #{}: {}", t_claude.id, t_claude.subject);
println!(" ✓ Task #{}: {}\n", t_codex.id, t_codex.subject);
println!("[4/7] Spawning Claude Code agent 'thinker'...");
let claude_config = SpawnConfig {
name: "thinker".into(),
prompt: "You are a concise Rust expert. Answer in ONE sentence only. Do not use any tools.".into(),
model: Some("sonnet".into()),
cwd: Some(tmp.path().to_path_buf()),
max_turns: Some(2),
allowed_tools: vec![],
permission_mode: Some("plan".into()),
reasoning_effort: None,
env: Default::default(),
memory_config: None,
delegations: Vec::new(),
};
match orch
.spawn_teammate("live-test", claude_config, BackendType::ClaudeCode)
.await
{
Ok(()) => println!(" ✓ thinker (Claude Code) spawned"),
Err(e) => {
println!(" ✗ Failed to spawn Claude Code agent: {e}");
println!(" Skipping Claude Code agent, continuing with Codex only...");
}
}
println!("\n[5/7] Spawning Codex agent 'coder'...");
let codex_config = SpawnConfig {
name: "coder".into(),
prompt: "You are a Rust code writer. Write minimal, clean code. No explanations.".into(),
cwd: Some(tmp.path().to_path_buf()),
max_turns: Some(2),
allowed_tools: vec![],
permission_mode: None,
model: None,
reasoning_effort: None,
env: Default::default(),
memory_config: None,
delegations: Vec::new(),
};
match orch
.spawn_teammate("live-test", codex_config, BackendType::Codex)
.await
{
Ok(()) => println!(" ✓ coder (Codex) spawned"),
Err(e) => {
println!(" ✗ Failed to spawn Codex agent: {e}");
println!(" Codex app-server may not be available or API key not configured.");
}
}
println!("\n[6/7] Team configuration:");
let config = orch.read_team("live-test").await?;
for m in &config.members {
let backend = m.agent_type();
println!(" - {} ({})", m.name(), backend);
}
println!("\n[7/7] Testing cross-backend messaging...");
orch.send_message(
"live-test",
"thinker",
"coder",
"Can you write a function that demonstrates ownership transfer?",
)
.await?;
println!(" ✓ thinker (Claude) -> coder (Codex): message sent");
orch.send_message(
"live-test",
"coder",
"thinker",
"Done. Check fn take_ownership(s: String) { println!(\"{s}\"); }",
)
.await?;
println!(" ✓ coder (Codex) -> thinker (Claude): message sent");
let inbox_mgr = FileInboxManager::new(&teams_dir);
let coder_msgs = inbox_mgr.read_inbox("live-test", "coder").await?;
println!(
"\n coder's inbox: {} message(s)",
coder_msgs.len()
);
for msg in &coder_msgs {
println!(" from {}: \"{}\"", msg.from, truncate(&msg.content, 60));
}
let thinker_msgs = inbox_mgr.read_inbox("live-test", "thinker").await?;
println!(
" thinker's inbox: {} message(s)",
thinker_msgs.len()
);
for msg in &thinker_msgs {
println!(" from {}: \"{}\"", msg.from, truncate(&msg.content, 60));
}
println!("\n--- Generated JSON (task file) ---");
let task_json = std::fs::read_to_string(tasks_dir.join("live-test/1.json"))?;
println!("{task_json}");
println!("--- Cleanup ---");
for name in ["thinker", "coder"] {
match orch.shutdown_teammate("live-test", name).await {
Ok(()) => println!(" {name} shut down"),
Err(_) => println!(" {name} already stopped"),
}
}
orch.delete_team("live-test").await?;
println!(" Team deleted.\n");
println!("╔══════════════════════════════════════════════╗");
println!("║ Test complete! ║");
println!("╚══════════════════════════════════════════════╝");
Ok(())
}
fn truncate(s: &str, max: usize) -> String {
if s.len() > max {
format!("{}...", &s[..max])
} else {
s.to_string()
}
}