use std::sync::Arc;
use std::time::Duration;
use agent_base::llm_trait::{
Capabilities, ChatRequest, ChatResponse, ChatStream, LlmError, LlmProvider, ProviderInfo,
};
use agent_base::{Content, Language, StreamChunk, Tool, ToolContext};
use agent_works::multi_agent::{ChildOutcome, MultiAgentConfig, MultiAgentRuntime};
use async_trait::async_trait;
use tokio_util::sync::CancellationToken;
struct StubLlm;
#[async_trait]
impl LlmProvider for StubLlm {
async fn stream(&self, _request: ChatRequest) -> Result<ChatStream, LlmError> {
Ok(ChatStream::new(Box::pin(futures_util::stream::iter(vec![
Ok(StreamChunk::Text("all clear".to_string())),
Ok(StreamChunk::Stop {
finish_reason: Some("stop".to_string()),
}),
]))))
}
async fn chat(&self, _request: ChatRequest) -> Result<ChatResponse, LlmError> {
unreachable!("the react loop streams")
}
fn capabilities(&self) -> Capabilities {
Capabilities::default()
}
fn info(&self) -> ProviderInfo {
ProviderInfo {
name: "stub".into(),
model: "stub".into(),
version: None,
}
}
}
struct StubTool(&'static str);
#[async_trait]
impl Tool for StubTool {
fn name(&self) -> &'static str {
self.0
}
fn description(&self) -> &'static str {
"stub"
}
fn schema(&self) -> serde_json::Value {
serde_json::json!({ "type": "object", "properties": {} })
}
async fn call(
&self,
_args: &serde_json::Value,
_ctx: &ToolContext,
) -> agent_base::AgentResult<Vec<Content>> {
Ok(vec![Content::text(format!("{}: ok", self.0))])
}
}
fn business_tools() -> Vec<Arc<dyn Tool>> {
vec![
Arc::new(StubTool("read_file")) as Arc<dyn Tool>,
Arc::new(StubTool("list_files")) as Arc<dyn Tool>,
]
}
#[tokio::main]
async fn main() {
let runtime = Arc::new(MultiAgentRuntime::new(
MultiAgentConfig::enabled(),
Arc::new(StubLlm),
business_tools(),
CancellationToken::new(),
None,
Language::En,
None,
None,
));
let mut children = Vec::new();
for name in ["scout", "analyst", "scribe"] {
let child = runtime
.child()
.system_prompt(format!("You are the {name} sub-agent. Answer briefly."))
.spawn(name)
.await
.expect("spawn ok");
println!(
"spawned {} (tools: {:?})",
child.agent_path(),
child.spawned_tools()
);
children.push(child);
}
for (i, child) in children.iter().enumerate() {
child
.task(format!("investigate module #{i} and report one line"))
.expect("task delivered");
}
for child in &children {
match child.wait(Duration::from_secs(5)).await {
ChildOutcome::Ok { text, .. } => {
println!("{} -> {:?}", child.agent_path(), text.unwrap_or_default())
}
other => println!("{} -> {:?}", child.agent_path(), other),
}
}
let scout = &children[0];
scout.send("also summarize your finding in five words").ok();
scout.task("summarize").expect("follow-up task delivered");
if let ChildOutcome::Ok { text, .. } = scout.wait(Duration::from_secs(5)).await {
println!(
"follow-up {} -> {:?}",
scout.agent_path(),
text.unwrap_or_default()
);
}
for info in runtime.list_agents() {
println!("live: {} [{}]", info.agent_path, info.status);
}
for child in &children {
child.close().ok();
}
for _ in 0..100 {
if runtime.list_agents().is_empty() {
break;
}
tokio::time::sleep(Duration::from_millis(10)).await;
}
println!("closed; {} still registered", runtime.list_agents().len());
}