use async_trait::async_trait;
use std::collections::HashMap;
use std::time::Instant;
use crate::traits::Result;
use super::{SwarmPatternExecutor, SwarmResult};
use crate::swarm::agent::{AgentConfig, AgentRuntime, ChatMessage, Role};
use crate::swarm::memory_bus::CerebroMemoryBus;
use crate::swarm::trace::{ExecutionTracer, RunStatus, TraceAction, TraceStep};
use crate::swarm::llm::LlmClient;
pub struct SequentialPattern {
pub agent_order: Vec<String>,
}
impl SequentialPattern {
pub fn new(agent_order: Vec<String>) -> Self {
Self { agent_order }
}
}
#[async_trait]
impl SwarmPatternExecutor for SequentialPattern {
async fn execute(
&self,
agents: &HashMap<String, AgentConfig>,
memory: &CerebroMemoryBus,
tracer: &ExecutionTracer,
llm: &LlmClient,
input: &str,
) -> Result<SwarmResult> {
let run_start = Instant::now();
let mut current_input = input.to_string();
let mut total_tokens: usize = 0;
let mut final_output = String::new();
for (pipeline_step, agent_id) in self.agent_order.iter().enumerate() {
let config = match agents.get(agent_id) {
Some(c) => c.clone(),
None => {
eprintln!("[SwarmForge] Agent '{}' not found in registry, skipping.", agent_id);
continue;
}
};
let agent_name = config.name.clone();
tracer.record(TraceStep {
agent_id: agent_id.clone(),
agent_name: agent_name.clone(),
step_number: pipeline_step,
action: TraceAction::AgentStart,
input_summary: truncate(¤t_input, 200),
output_summary: String::new(),
duration_ms: 0,
tokens_used: 0,
timestamp: chrono::Utc::now().to_rfc3339(),
});
let mut runtime = AgentRuntime::new(config.clone());
let context = memory.recall_as_context(¤t_input, 3).await;
tracer.record_memory_query(agent_id, &agent_name, ¤t_input, if context.is_empty() { 0 } else { 3 });
let user_content = if context.is_empty() {
current_input.clone()
} else {
format!("{}\n\n{}", context, current_input)
};
runtime.push_message(ChatMessage::new(Role::User, &user_content));
memory.push_message(agent_id, ChatMessage::new(Role::User, &user_content));
memory.set_state(agent_id, "status", "running").await.ok();
memory.set_state(agent_id, "pipeline_step", &pipeline_step.to_string()).await.ok();
let llm_start = Instant::now();
let response = llm.chat(&config.model, &runtime.messages).await?;
let llm_duration = llm_start.elapsed().as_millis() as u64;
let step_tokens = response.input_tokens + response.output_tokens;
total_tokens += step_tokens;
tracer.record_llm_call(
agent_id,
&agent_name,
pipeline_step,
¤t_input,
&response.content,
llm_duration,
step_tokens,
);
memory.push_message(
agent_id,
ChatMessage::new(Role::Assistant, &response.content)
.with_metadata("tokens", &step_tokens.to_string())
.with_metadata("duration_ms", &llm_duration.to_string()),
);
let doc_id = memory
.commit_to_semantic(agent_id, tracer.run_id(), &response.content)
.await
.unwrap_or_else(|_| "unknown".into());
tracer.record_memory_commit(agent_id, &agent_name, &doc_id);
memory.set_state(agent_id, "status", "completed").await.ok();
memory.set_state(agent_id, "last_output", &response.content).await.ok();
tracer.record(TraceStep {
agent_id: agent_id.clone(),
agent_name: agent_name.clone(),
step_number: pipeline_step,
action: TraceAction::AgentComplete,
input_summary: truncate(¤t_input, 200),
output_summary: truncate(&response.content, 500),
duration_ms: llm_duration,
tokens_used: step_tokens,
timestamp: chrono::Utc::now().to_rfc3339(),
});
if pipeline_step < self.agent_order.len() - 1 {
let next_id = &self.agent_order[pipeline_step + 1];
tracer.record_handoff(agent_id, &agent_name, next_id);
}
final_output = response.content.clone();
current_input = response.content;
println!(
" [{}] {} ✓ ({} tokens, {}ms)",
pipeline_step + 1,
agent_name,
step_tokens,
llm_duration
);
}
let total_duration = run_start.elapsed().as_millis() as u64;
Ok(SwarmResult {
final_output,
trace: tracer.finalize(RunStatus::Completed),
total_tokens,
total_duration_ms: total_duration,
})
}
}
fn truncate(s: &str, max: usize) -> String {
if s.len() <= max {
s.to_string()
} else {
format!("{}...", &s[..max])
}
}