1use std::sync::Arc;
20
21use async_trait::async_trait;
22use dashmap::DashMap;
23use serde::{Deserialize, Serialize};
24use serde_json::{Value, json};
25
26use crate::agent::Agent;
27use crate::context::{InvestigationContext, Signal};
28use crate::registry::KernelError;
29use crate::tool::{Tool, ToolSchema};
30
31pub type DelegateName = String;
34
35#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
37pub struct DelegateDescriptor {
38 pub name: DelegateName,
40}
41
42#[async_trait]
44pub trait DelegateExecutor: Send + Sync {
45 async fn invoke(&self, args: Value) -> Result<Value, KernelError>;
46}
47
48#[derive(Clone, Default)]
50pub struct DelegateRegistry {
51 inner: Arc<DashMap<DelegateName, Arc<dyn DelegateExecutor>>>,
52}
53
54impl DelegateRegistry {
55 pub fn new() -> Self {
56 Self::default()
57 }
58
59 pub fn register(&self, name: impl Into<String>, executor: Arc<dyn DelegateExecutor>) {
60 self.inner.insert(name.into(), executor);
61 }
62
63 pub fn get(&self, name: &str) -> Option<Arc<dyn DelegateExecutor>> {
64 self.inner.get(name).map(|v| v.clone())
65 }
66
67 pub fn len(&self) -> usize {
68 self.inner.len()
69 }
70
71 pub fn is_empty(&self) -> bool {
72 self.inner.is_empty()
73 }
74
75 pub fn descriptors(&self) -> Vec<DelegateDescriptor> {
77 let mut descriptors: Vec<_> = self
78 .inner
79 .iter()
80 .map(|entry| DelegateDescriptor {
81 name: entry.key().clone(),
82 })
83 .collect();
84 descriptors.sort_by(|left, right| left.name.cmp(&right.name));
85 descriptors
86 }
87}
88
89pub struct DelegateTool {
91 schema: ToolSchema,
92 executor: Arc<dyn DelegateExecutor>,
93}
94
95impl DelegateTool {
96 pub fn new(
97 name: impl Into<String>,
98 description: impl Into<String>,
99 executor: Arc<dyn DelegateExecutor>,
100 ) -> Self {
101 Self {
102 schema: ToolSchema {
103 name: name.into(),
104 description: description.into(),
105 args_schema: json!({"type": "object"}),
106 result_schema: json!({"type": "object"}),
107 },
108 executor,
109 }
110 }
111
112 pub fn with_schema(schema: ToolSchema, executor: Arc<dyn DelegateExecutor>) -> Self {
113 Self { schema, executor }
114 }
115}
116
117#[async_trait]
118impl Tool for DelegateTool {
119 fn schema(&self) -> ToolSchema {
120 self.schema.clone()
121 }
122
123 fn name(&self) -> crate::ToolName {
124 self.schema.name.clone()
125 }
126
127 async fn invoke(&self, args: Value) -> Result<Value, KernelError> {
128 self.executor.invoke(args).await
129 }
130}
131
132pub struct InProcessAgentDelegate {
134 agent: Arc<dyn Agent>,
135}
136
137impl InProcessAgentDelegate {
138 pub fn new(agent: Arc<dyn Agent>) -> Self {
139 Self { agent }
140 }
141
142 pub fn arc(agent: Arc<dyn Agent>) -> Arc<dyn DelegateExecutor> {
143 Arc::new(Self::new(agent))
144 }
145}
146
147#[async_trait]
148impl DelegateExecutor for InProcessAgentDelegate {
149 async fn invoke(&self, args: Value) -> Result<Value, KernelError> {
150 let mut ctx = context_from_args(args)?;
151 let result = self.agent.step(&mut ctx).await?;
152 Ok(json!({
153 "delegate_kind": "in_process",
154 "agent": self.agent.name(),
155 "result": {
156 "skills_run": result.skills_run,
157 "skills_skipped": result.skills_skipped,
158 "confidence": result.confidence,
159 "concluded": result.concluded,
160 },
161 "context": ctx,
162 }))
163 }
164}
165
166fn context_from_args(args: Value) -> Result<InvestigationContext, KernelError> {
167 if let Some(context) = args.get("context") {
168 return Ok(serde_json::from_value(context.clone())?);
169 }
170 if let Ok(ctx) = serde_json::from_value::<InvestigationContext>(args.clone()) {
171 return Ok(ctx);
172 }
173
174 let entity_id = args
175 .get("entity_id")
176 .and_then(Value::as_str)
177 .unwrap_or("delegate")
178 .to_string();
179 let partition = args
180 .get("partition")
181 .and_then(Value::as_str)
182 .unwrap_or("default")
183 .to_string();
184 let mut ctx = InvestigationContext::new(entity_id, partition);
185 if let Some(confidence) = args.get("confidence").and_then(Value::as_f64) {
186 ctx.confidence = (confidence as f32).clamp(0.0, 1.0);
187 }
188 if let Some(signals) = args.get("signals").and_then(Value::as_array) {
189 ctx.signals.extend(
190 signals
191 .iter()
192 .filter_map(Value::as_str)
193 .map(|s| Signal::new(s.to_string())),
194 );
195 }
196 Ok(ctx)
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202 use crate::{GenericAgent, Skill, SkillOutcome, SkillRegistry, ToolRegistry};
203
204 struct ConfidenceSkill;
205
206 #[async_trait]
207 impl Skill for ConfidenceSkill {
208 fn id(&self) -> &str {
209 "test.confidence"
210 }
211
212 fn description(&self) -> &str {
213 "raises confidence"
214 }
215
216 fn applies(&self, ctx: &InvestigationContext) -> bool {
217 ctx.has_signal("raise")
218 }
219
220 async fn execute(
221 &self,
222 _ctx: &mut InvestigationContext,
223 _tools: &ToolRegistry,
224 ) -> Result<SkillOutcome, KernelError> {
225 Ok(SkillOutcome::default().with_delta(0.4))
226 }
227 }
228
229 #[tokio::test]
230 async fn in_process_delegate_drives_child_agent() {
231 let skills = SkillRegistry::new();
232 skills.register(Arc::new(ConfidenceSkill));
233 let tools = ToolRegistry::new();
234 let agent = GenericAgent::builder("child")
235 .with_skills(["test.confidence"])
236 .build(&skills, &tools)
237 .expect("agent");
238 let executor = InProcessAgentDelegate::arc(Arc::new(agent));
239 let tool = DelegateTool::new("child_agent", "child", executor);
240 let out = tool
241 .invoke(json!({
242 "entity_id": "host-a",
243 "partition": "lab",
244 "signals": ["raise"],
245 }))
246 .await
247 .expect("invoke");
248 assert_eq!(out["delegate_kind"], "in_process");
249 assert_eq!(out["agent"], "child");
250 assert_eq!(out["result"]["skills_run"][0], "test.confidence");
251 assert!((out["result"]["confidence"].as_f64().unwrap() - 0.4).abs() < 1e-6);
252 }
253
254 #[test]
255 fn delegate_registry_descriptors_are_sorted() {
256 let registry = DelegateRegistry::new();
257 let skills = SkillRegistry::new();
258 let tools = ToolRegistry::new();
259 let agent = GenericAgent::builder("child")
260 .build(&skills, &tools)
261 .expect("agent");
262 let executor = InProcessAgentDelegate::arc(Arc::new(agent));
263
264 registry.register("zeta.delegate", executor.clone());
265 registry.register("alpha.delegate", executor);
266
267 let names: Vec<_> = registry
268 .descriptors()
269 .into_iter()
270 .map(|descriptor| descriptor.name)
271 .collect();
272 assert_eq!(names, vec!["alpha.delegate", "zeta.delegate"]);
273 }
274}