1use std::fmt;
9use std::sync::Arc;
10
11use async_trait::async_trait;
12use uuid::Uuid;
13
14use crate::context::{InvestigationContext, NextAction};
15use crate::registry::{KernelError, SkillRegistry, ToolRegistry};
16use crate::skill::Skill;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
20pub struct AgentId(pub Uuid);
21
22impl AgentId {
23 pub fn new() -> Self {
24 Self(Uuid::new_v4())
25 }
26}
27
28impl Default for AgentId {
29 fn default() -> Self {
30 Self::new()
31 }
32}
33
34impl fmt::Display for AgentId {
35 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36 write!(f, "{}", self.0)
37 }
38}
39
40#[derive(Debug, Clone)]
42pub struct AgentStepResult {
43 pub skills_run: Vec<String>,
45 pub skills_skipped: Vec<String>,
48 pub confidence: f32,
50 pub concluded: bool,
52}
53
54#[async_trait]
56pub trait Agent: Send + Sync {
57 fn id(&self) -> AgentId;
58 fn name(&self) -> &str;
59
60 async fn step(&self, ctx: &mut InvestigationContext) -> Result<AgentStepResult, KernelError>;
63}
64
65pub struct GenericAgent {
70 id: AgentId,
71 name: String,
72 skills: Vec<Arc<dyn Skill>>,
73 tools: ToolRegistry,
74 pub short_circuit_on_conclude: bool,
77}
78
79impl GenericAgent {
80 pub fn builder(name: impl Into<String>) -> GenericAgentBuilder {
81 GenericAgentBuilder {
82 name: name.into(),
83 skill_ids: Vec::new(),
84 allowed_tools: None,
85 short_circuit_on_conclude: true,
86 }
87 }
88
89 pub fn skills(&self) -> &[Arc<dyn Skill>] {
90 &self.skills
91 }
92
93 pub fn tools(&self) -> &ToolRegistry {
94 &self.tools
95 }
96}
97
98#[async_trait]
99impl Agent for GenericAgent {
100 fn id(&self) -> AgentId {
101 self.id
102 }
103
104 fn name(&self) -> &str {
105 &self.name
106 }
107
108 async fn step(&self, ctx: &mut InvestigationContext) -> Result<AgentStepResult, KernelError> {
109 let mut skills_run = Vec::new();
110 let mut skills_skipped = Vec::new();
111 let mut concluded = false;
112
113 for skill in &self.skills {
114 if concluded && self.short_circuit_on_conclude {
115 skills_skipped.push(skill.id().to_string());
116 continue;
117 }
118 if !skill.applies(ctx) {
119 skills_skipped.push(skill.id().to_string());
120 continue;
121 }
122
123 let outcome = skill.execute(ctx, &self.tools).await?;
124 ctx.confidence = (ctx.confidence + outcome.confidence_delta).clamp(0.0, 1.0);
125 ctx.pending_actions = outcome.next_actions.clone();
126 if outcome
127 .next_actions
128 .iter()
129 .any(|a| matches!(a, NextAction::Conclude | NextAction::Discard))
130 {
131 concluded = true;
132 }
133 skills_run.push(skill.id().to_string());
134 }
135
136 Ok(AgentStepResult {
137 skills_run,
138 skills_skipped,
139 confidence: ctx.confidence,
140 concluded,
141 })
142 }
143}
144
145pub struct GenericAgentBuilder {
148 name: String,
149 skill_ids: Vec<String>,
150 allowed_tools: Option<Vec<String>>,
151 short_circuit_on_conclude: bool,
152}
153
154impl GenericAgentBuilder {
155 pub fn with_skills<I, S>(mut self, ids: I) -> Self
156 where
157 I: IntoIterator<Item = S>,
158 S: Into<String>,
159 {
160 self.skill_ids.extend(ids.into_iter().map(Into::into));
161 self
162 }
163
164 pub fn with_tools<I, S>(mut self, names: I) -> Self
165 where
166 I: IntoIterator<Item = S>,
167 S: Into<String>,
168 {
169 self.allowed_tools = Some(names.into_iter().map(Into::into).collect());
170 self
171 }
172
173 pub fn short_circuit_on_conclude(mut self, v: bool) -> Self {
174 self.short_circuit_on_conclude = v;
175 self
176 }
177
178 pub fn build(
179 self,
180 skills: &SkillRegistry,
181 tools: &ToolRegistry,
182 ) -> Result<GenericAgent, KernelError> {
183 let resolved = skills.resolve_chain(self.skill_ids.iter())?;
184 let scoped_tools = match self.allowed_tools {
185 Some(list) => tools.scoped(list),
186 None => tools.clone(),
187 };
188 Ok(GenericAgent {
189 id: AgentId::new(),
190 name: self.name,
191 skills: resolved,
192 tools: scoped_tools,
193 short_circuit_on_conclude: self.short_circuit_on_conclude,
194 })
195 }
196}