use std::fmt;
use std::sync::Arc;
use async_trait::async_trait;
use uuid::Uuid;
use crate::context::{InvestigationContext, NextAction};
use crate::registry::{KernelError, SkillRegistry, ToolRegistry};
use crate::skill::Skill;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct AgentId(pub Uuid);
impl AgentId {
pub fn new() -> Self {
Self(Uuid::new_v4())
}
}
impl Default for AgentId {
fn default() -> Self {
Self::new()
}
}
impl fmt::Display for AgentId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone)]
pub struct AgentStepResult {
pub skills_run: Vec<String>,
pub skills_skipped: Vec<String>,
pub confidence: f32,
pub concluded: bool,
}
#[async_trait]
pub trait Agent: Send + Sync {
fn id(&self) -> AgentId;
fn name(&self) -> &str;
async fn step(&self, ctx: &mut InvestigationContext) -> Result<AgentStepResult, KernelError>;
}
pub struct GenericAgent {
id: AgentId,
name: String,
skills: Vec<Arc<dyn Skill>>,
tools: ToolRegistry,
pub short_circuit_on_conclude: bool,
}
impl GenericAgent {
pub fn builder(name: impl Into<String>) -> GenericAgentBuilder {
GenericAgentBuilder {
name: name.into(),
skill_ids: Vec::new(),
allowed_tools: None,
short_circuit_on_conclude: true,
}
}
pub fn skills(&self) -> &[Arc<dyn Skill>] {
&self.skills
}
pub fn tools(&self) -> &ToolRegistry {
&self.tools
}
}
#[async_trait]
impl Agent for GenericAgent {
fn id(&self) -> AgentId {
self.id
}
fn name(&self) -> &str {
&self.name
}
async fn step(&self, ctx: &mut InvestigationContext) -> Result<AgentStepResult, KernelError> {
let mut skills_run = Vec::new();
let mut skills_skipped = Vec::new();
let mut concluded = false;
for skill in &self.skills {
if concluded && self.short_circuit_on_conclude {
skills_skipped.push(skill.id().to_string());
continue;
}
if !skill.applies(ctx) {
skills_skipped.push(skill.id().to_string());
continue;
}
let outcome = skill.execute(ctx, &self.tools).await?;
ctx.confidence = (ctx.confidence + outcome.confidence_delta).clamp(0.0, 1.0);
ctx.pending_actions = outcome.next_actions.clone();
if outcome
.next_actions
.iter()
.any(|a| matches!(a, NextAction::Conclude | NextAction::Discard))
{
concluded = true;
}
skills_run.push(skill.id().to_string());
}
Ok(AgentStepResult {
skills_run,
skills_skipped,
confidence: ctx.confidence,
concluded,
})
}
}
pub struct GenericAgentBuilder {
name: String,
skill_ids: Vec<String>,
allowed_tools: Option<Vec<String>>,
short_circuit_on_conclude: bool,
}
impl GenericAgentBuilder {
pub fn with_skills<I, S>(mut self, ids: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.skill_ids.extend(ids.into_iter().map(Into::into));
self
}
pub fn with_tools<I, S>(mut self, names: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.allowed_tools = Some(names.into_iter().map(Into::into).collect());
self
}
pub fn short_circuit_on_conclude(mut self, v: bool) -> Self {
self.short_circuit_on_conclude = v;
self
}
pub fn build(
self,
skills: &SkillRegistry,
tools: &ToolRegistry,
) -> Result<GenericAgent, KernelError> {
let resolved = skills.resolve_chain(self.skill_ids.iter())?;
let scoped_tools = match self.allowed_tools {
Some(list) => tools.scoped(list),
None => tools.clone(),
};
Ok(GenericAgent {
id: AgentId::new(),
name: self.name,
skills: resolved,
tools: scoped_tools,
short_circuit_on_conclude: self.short_circuit_on_conclude,
})
}
}