use std::sync::{Arc, Mutex};
use yoagent::provider::model::ModelConfig;
use yoagent::provider::{OpenAiCompatProvider, StreamProvider};
#[allow(unused_imports)]
use yoagent::shared_state::{FileBackend, SharedState};
use yoagent::sub_agent::SubAgentTool;
use yoagent::tools;
use yoagent::*;
#[tokio::main]
async fn main() {
let api_key = std::env::var("XAI_API_KEY").expect("Set XAI_API_KEY");
let mut model_config = ModelConfig::xai("grok-4-1-fast-reasoning", "Grok 4.1 Fast Reasoning");
model_config.reasoning = true;
let provider: Arc<dyn StreamProvider> = Arc::new(OpenAiCompatProvider);
let target_dir = std::env::args().nth(1).unwrap_or_else(|| ".".into());
println!("RLM Codebase Analyzer (Grok)");
println!("Target: {}\n", target_dir);
let state = SharedState::new();
state
.set("target_dir", target_dir.clone())
.await
.expect("store target dir");
println!("--- RLM: 2-level recursive agent delegation ---");
println!("Parent → lead_analyst (explores) → file_analyst (analyzes)\n");
let file_analyst = SubAgentTool::new("file_analyst", Arc::clone(&provider))
.with_description(
"Analyzes a single source file in depth. \
Call with a task specifying the file path to analyze.",
)
.with_system_prompt(
"You are a file-level code analyst. When given a file to analyze:\n\
1. Use read_file to read the file content\n\
2. Analyze it: purpose, key types/functions, design patterns, quality\n\
3. Write a concise summary (under 200 words) to shared state with \
key 'summary:<filepath>'\n\n\
Be specific and technical. Focus on what makes this code interesting.",
)
.with_model(&model_config.id)
.with_api_key(&api_key)
.with_model_config(model_config.clone())
.with_shared_state(state.clone())
.with_tools(vec![Arc::new(tools::ReadFileTool::new())])
.with_max_turns(5);
let lead_analyst = SubAgentTool::new("lead_analyst", Arc::clone(&provider))
.with_description("Orchestrates codebase analysis")
.with_system_prompt(
"You are a lead code analyst orchestrating a codebase review.\n\n\
IMPORTANT: Only analyze files within the target directory. Do NOT explore \
parent directories or other parts of the project.\n\n\
Steps:\n\
1. Read 'target_dir' from shared state\n\
2. Use list_files to discover source files ONLY within that directory\n\
3. Pick the 2 most important files\n\
4. For EACH chosen file, delegate to the 'file_analyst' tool: \
'Analyze <filepath>'\n\
5. After all files are analyzed, read each summary from shared state \
(keys are 'summary:<filepath>')\n\
6. Write a final synthesis report to shared state under key 'final_report'\n\n\
The final report should identify cross-cutting themes, architectural patterns, \
and how the files relate to each other. Keep it under 300 words.",
)
.with_model(&model_config.id)
.with_api_key(&api_key)
.with_model_config(model_config)
.with_shared_state(state.clone())
.with_tools(vec![
Arc::new(tools::ListFilesTool::new()),
Arc::new(tools::ReadFileTool::new()),
Arc::new(file_analyst),
])
.with_max_turns(25);
let buf: Arc<Mutex<String>> = Arc::new(Mutex::new(String::new()));
let ctx = ToolContext {
tool_call_id: "tc-rlm".into(),
tool_name: "lead_analyst".into(),
cancel: tokio_util::sync::CancellationToken::new(),
on_update: Some(Arc::new({
let buf = buf.clone();
move |result: ToolResult| {
for content in &result.content {
if let Content::Text { text } = content {
let mut b = buf.lock().unwrap();
if text.starts_with("[sub-agent calling tool:") {
if !b.is_empty() {
eprintln!("[lead] {}", b.drain(..).collect::<String>());
}
eprintln!("[lead] {}", text);
return;
}
b.push_str(text);
while let Some(pos) = b.find('\n') {
let line: String = b.drain(..=pos).collect();
eprint!("[lead] {}", line);
}
}
}
}
})),
on_progress: None,
};
let result = lead_analyst
.execute(
serde_json::json!({"task": "Explore and analyze this Rust crate."}),
ctx,
)
.await;
{
let b = buf.lock().unwrap();
if !b.is_empty() {
eprintln!("[lead] {}", *b);
}
}
match result {
Ok(result) => {
eprintln!("[lead] details: {}", result.details);
for content in &result.content {
if let Content::Text { text } = content {
if !text.is_empty() {
eprintln!("[lead] (final) {}", text);
}
}
}
}
Err(e) => {
eprintln!("\nError: {}", e);
std::process::exit(1);
}
}
println!("\n═══════════════════════════════════════════════════════════");
println!(" RLM Results");
println!("═══════════════════════════════════════════════════════════\n");
let keys = state.keys().await;
for key in &keys {
if let Some(file) = key.strip_prefix("summary:") {
println!("── {} ──\n", file);
if let Some(summary) = state.get(key).await {
println!("{}\n", summary);
}
}
}
println!("── Final Synthesis ──\n");
match state.get("final_report").await {
Some(report) => println!("{}", report),
None => println!("(lead_analyst did not produce a final report)"),
}
println!("\n═══════════════════════════════════════════════════════════");
println!(" Shared state: {}", state.summary().await);
println!("═══════════════════════════════════════════════════════════");
}