1use anyhow::Result;
20use async_trait::async_trait;
21use chrono::Utc;
22use oxi_sdk::Agent;
23use oxios_ouroboros::Seed;
24use parking_lot::RwLock;
25use std::collections::HashMap;
26use std::sync::Arc;
27use std::sync::atomic::{AtomicBool, Ordering};
28use tokio::task::JoinHandle;
29
30use crate::agent_runtime::AgentRuntime;
31use crate::config::AgentLogConfig;
32use crate::event_bus::EventBus;
33use crate::resource_monitor::ResourceMonitor;
34use crate::session_context::SessionContext;
35use crate::state_store::StateStore;
36use crate::types::{AgentId, AgentInfo, AgentStatus};
37use oxios_ouroboros::ExecutionResult;
38
39#[cfg(feature = "sqlite-memory")]
40use crate::agent_log_db::AgentLogDb;
41
42struct AgentHandle {
44 cancelled: Arc<AtomicBool>,
46 task: JoinHandle<Result<ExecutionResult>>,
48}
49
50#[derive(Default)]
57pub struct AgentPool {
58 agents: RwLock<HashMap<AgentId, Arc<Agent>>>,
59}
60
61impl AgentPool {
62 pub fn new() -> Self {
64 Self {
65 agents: RwLock::new(HashMap::new()),
66 }
67 }
68
69 pub fn insert(&self, id: AgentId, agent: Arc<Agent>) {
71 self.agents.write().insert(id, agent);
72 }
73
74 pub fn get(&self, id: &AgentId) -> Option<Arc<Agent>> {
76 self.agents.read().get(id).cloned()
77 }
78
79 pub fn remove(&self, id: &AgentId) -> Option<Arc<Agent>> {
81 self.agents.write().remove(id)
82 }
83
84 pub fn export_state(&self, id: &AgentId) -> Option<serde_json::Value> {
88 self.agents
89 .read()
90 .get(id)
91 .and_then(|agent| agent.export_state().ok())
92 }
93
94 pub fn import_state(&self, id: &AgentId, state: serde_json::Value) -> bool {
98 if let Some(agent) = self.agents.read().get(id) {
99 agent.import_state(state).is_ok()
100 } else {
101 false
102 }
103 }
104
105 pub fn len(&self) -> usize {
107 self.agents.read().len()
108 }
109
110 pub fn is_empty(&self) -> bool {
112 self.agents.read().is_empty()
113 }
114}
115
116#[async_trait]
118pub trait Supervisor: Send + Sync {
119 async fn fork(&self, spec: &Seed) -> Result<AgentId>;
121
122 async fn exec(&self, id: AgentId) -> Result<()>;
124
125 async fn run_with_seed(&self, id: AgentId, seed: &Seed) -> Result<ExecutionResult>;
128
129 async fn wait(&self, id: AgentId) -> Result<AgentStatus>;
131
132 async fn kill(&self, id: AgentId) -> Result<()>;
134
135 async fn list(&self) -> Result<Vec<AgentInfo>>;
137}
138
139pub struct BasicSupervisor {
141 agents: RwLock<HashMap<AgentId, AgentInfo>>,
142 handles: RwLock<HashMap<AgentId, AgentHandle>>,
144 agent_pool: AgentPool,
146 event_bus: EventBus,
147 runtime: Arc<AgentRuntime>,
148 resource_monitor: Option<Arc<ResourceMonitor>>,
149 session_context: Arc<tokio::sync::RwLock<SessionContext>>,
155 state_store: Option<Arc<StateStore>>,
157 #[cfg(feature = "sqlite-memory")]
159 agent_log_db: Option<Arc<AgentLogDb>>,
160 agent_log_config: AgentLogConfig,
162}
163
164impl BasicSupervisor {
165 pub fn new(event_bus: EventBus, runtime: AgentRuntime) -> Self {
167 Self {
168 agents: RwLock::new(HashMap::new()),
169 handles: RwLock::new(HashMap::new()),
170 agent_pool: AgentPool::new(),
171 event_bus,
172 runtime: Arc::new(runtime),
173 resource_monitor: None,
174 session_context: Arc::new(tokio::sync::RwLock::new(SessionContext::new())),
175 state_store: None,
176 #[cfg(feature = "sqlite-memory")]
177 agent_log_db: None,
178 agent_log_config: AgentLogConfig::default(),
179 }
180 }
181
182 pub fn set_state_store(&mut self, store: Arc<StateStore>) {
184 self.state_store = Some(store);
185 }
186
187 #[cfg(feature = "sqlite-memory")]
189 pub fn set_agent_log_db(&mut self, db: Arc<AgentLogDb>) {
190 self.agent_log_db = Some(db);
191 }
192
193 pub fn set_agent_log_config(&mut self, config: AgentLogConfig) {
195 self.agent_log_config = config;
196 }
197
198 pub fn set_resource_monitor(&mut self, rm: Arc<ResourceMonitor>) {
200 self.resource_monitor = Some(rm);
201 }
202
203 fn update_agent_count(&self) {
205 if let Some(ref rm) = self.resource_monitor {
206 let count = self.agents.read().len();
207 rm.set_active_agents(count);
208 }
209 }
210
211 pub fn pool(&self) -> &AgentPool {
213 &self.agent_pool
214 }
215}
216
217#[async_trait]
218impl Supervisor for BasicSupervisor {
219 async fn fork(&self, spec: &Seed) -> Result<AgentId> {
220 let id = AgentId::new_v4();
221 let info = AgentInfo {
222 id,
223 name: spec.goal.clone(),
224 status: AgentStatus::Starting,
225 created_at: Utc::now(),
226 seed_id: Some(spec.id),
227 project_id: spec.project_id,
228 started_at: None,
229 completed_at: None,
230 error: None,
231 steps_completed: 0,
232 steps_total: None,
233 tool_calls: vec![],
234 tokens_input: 0,
235 tokens_output: 0,
236 cost_usd: 0.0,
237 model_id: String::new(),
238 session_id: None,
239 };
240
241 {
242 let mut agents = self.agents.write();
243 agents.insert(id, info);
244 }
245
246 self.update_agent_count();
247
248 let _ = self
249 .event_bus
250 .publish(crate::event_bus::KernelEvent::AgentCreated {
251 id,
252 name: spec.goal.clone(),
253 });
254
255 tracing::info!(agent_id = %id, "Forked new agent from seed");
256 Ok(id)
257 }
258
259 async fn exec(&self, id: AgentId) -> Result<()> {
260 {
261 let mut agents = self.agents.write();
262 match agents.get_mut(&id) {
263 Some(agent) => {
264 agent.status = AgentStatus::Running;
265 }
266 None => anyhow::bail!("Agent {id} not found"),
267 }
268 }
269
270 self.update_agent_count();
271
272 let _ = self
273 .event_bus
274 .publish(crate::event_bus::KernelEvent::AgentStarted { id });
275 tracing::info!(agent_id = %id, "Agent execution started");
276
277 Ok(())
278 }
279
280 async fn run_with_seed(&self, id: AgentId, seed: &Seed) -> Result<ExecutionResult> {
281 {
283 let mut agents = self.agents.write();
284 match agents.get_mut(&id) {
285 Some(agent) => {
286 agent.status = AgentStatus::Running;
287 agent.started_at = Some(Utc::now());
288 }
289 None => anyhow::bail!("Agent {id} not found"),
290 }
291 }
292
293 let _ = self
294 .event_bus
295 .publish(crate::event_bus::KernelEvent::AgentStarted { id });
296
297 tracing::info!(agent_id = %id, seed_id = %seed.id, "Running agent task");
298
299 let cancelled = Arc::new(AtomicBool::new(false));
301 let runtime = Arc::clone(&self.runtime);
302 let seed = seed.clone();
303 let cancelled_clone = cancelled.clone();
304
305 let session_ctx = self.session_context.clone();
308
309 let handle: JoinHandle<Result<ExecutionResult>> = tokio::spawn(async move {
310 if cancelled_clone.load(Ordering::Relaxed) {
312 return Ok(ExecutionResult {
313 output: "Agent cancelled before execution".into(),
314 steps_completed: 0,
315 success: false,
316 tool_calls: vec![],
317 tokens_input: 0,
318 tokens_output: 0,
319 model_id: String::new(),
320 });
321 }
322 let mut ctx = session_ctx.write().await;
323 runtime.execute(id, &seed, &mut ctx).await
324 });
325
326 {
328 let mut handles = self.handles.write();
329 handles.insert(
330 id,
331 AgentHandle {
332 cancelled,
333 task: handle,
334 },
335 );
336 }
337
338 let result = {
340 let agent_handle = {
341 let mut handles = self.handles.write();
342 handles.remove(&id)
343 };
344 match agent_handle {
347 Some(ah) => match ah.task.await {
348 Ok(res) => res,
349 Err(join_err) => {
350 tracing::warn!(agent_id = %id, error = %join_err, "Agent task join error");
352 Ok(ExecutionResult {
353 output: format!("Agent task aborted: {join_err}"),
354 steps_completed: 0,
355 success: false,
356 tool_calls: vec![],
357 tokens_input: 0,
358 tokens_output: 0,
359 model_id: String::new(),
360 })
361 }
362 },
363 None => anyhow::bail!("Agent {id} handle disappeared"),
364 }
365 };
366
367 match result {
368 Ok(result) => {
369 tracing::info!(
370 agent_id = %id,
371 success = result.success,
372 steps = result.steps_completed,
373 "Agent task completed"
374 );
375
376 {
377 let mut agents = self.agents.write();
378 if let Some(agent) = agents.get_mut(&id) {
379 agent.status = if result.success {
380 AgentStatus::Idle
381 } else {
382 AgentStatus::Failed
383 };
384 agent.completed_at = Some(Utc::now());
385 agent.steps_completed = result.steps_completed;
386 agent.tool_calls = result
387 .tool_calls
388 .iter()
389 .map(|tc| crate::types::ToolCallRecord {
390 tool: tc.tool.clone(),
391 input: tc.input.clone(),
392 output: tc.output.clone(),
393 duration_ms: tc.duration_ms,
394 is_error: tc.is_error,
395 tool_call_id: tc.tool_call_id.clone(),
396 timestamp: tc.timestamp,
397 })
398 .collect();
399 agent.tokens_input = result.tokens_input;
400 agent.tokens_output = result.tokens_output;
401 agent.model_id = result.model_id.clone();
402 agent.cost_usd = if !result.model_id.is_empty() {
403 crate::kernel_handle::engine_api::estimate_cost(
404 &result.model_id,
405 result.tokens_input,
406 result.tokens_output,
407 )
408 } else {
409 0.0
410 };
411 if !result.success {
412 agent.error = Some(result.output.clone());
413 }
414 }
415 }
416
417 let _ = self
418 .event_bus
419 .publish(crate::event_bus::KernelEvent::AgentStopped { id });
420 self.update_agent_count();
421
422 self.persist_agent(id).await;
424
425 Ok(result)
426 }
427 Err(e) => {
428 tracing::error!(agent_id = %id, error = %e, "Agent task failed");
429
430 {
431 let mut agents = self.agents.write();
432 if let Some(agent) = agents.get_mut(&id) {
433 agent.status = AgentStatus::Failed;
434 agent.completed_at = Some(Utc::now());
435 agent.error = Some(e.to_string());
436 }
437 }
438
439 let _ = self
440 .event_bus
441 .publish(crate::event_bus::KernelEvent::AgentFailed {
442 id,
443 error: e.to_string(),
444 });
445 self.update_agent_count();
446
447 self.persist_agent(id).await;
449
450 Ok(ExecutionResult {
451 output: format!("Agent failed: {e}"),
452 steps_completed: 0,
453 success: false,
454 tool_calls: vec![],
455 tokens_input: 0,
456 tokens_output: 0,
457 model_id: String::new(),
458 })
459 }
460 }
461 }
462
463 async fn wait(&self, id: AgentId) -> Result<AgentStatus> {
464 let agents = self.agents.read();
465 match agents.get(&id) {
466 Some(info) => Ok(info.status),
467 None => anyhow::bail!("Agent {id} not found"),
468 }
469 }
470
471 async fn kill(&self, id: AgentId) -> Result<()> {
472 {
474 let mut handles = self.handles.write();
475 if let Some(agent_handle) = handles.remove(&id) {
476 agent_handle.cancelled.store(true, Ordering::Relaxed);
477 agent_handle.task.abort();
478 tracing::info!(agent_id = %id, "Agent task aborted");
479 }
480 }
481
482 {
483 let mut agents = self.agents.write();
484 if let Some(agent) = agents.get_mut(&id) {
485 agent.status = AgentStatus::Stopped;
486 agent.completed_at = Some(Utc::now());
487 } else {
488 anyhow::bail!("Agent {id} not found");
489 }
490 }
491
492 let _ = self
493 .event_bus
494 .publish(crate::event_bus::KernelEvent::AgentStopped { id });
495 self.update_agent_count();
496
497 self.persist_agent(id).await;
499
500 tracing::info!(agent_id = %id, "Agent killed");
501 Ok(())
502 }
503
504 async fn list(&self) -> Result<Vec<AgentInfo>> {
505 let agents = self.agents.read();
506 Ok(agents.values().cloned().collect())
507 }
508}
509
510impl BasicSupervisor {
511 async fn persist_agent(&self, id: AgentId) {
514 let info = {
516 let agents = self.agents.read();
517 agents.get(&id).cloned()
518 };
519
520 let Some(info) = info else { return };
521
522 if let Some(ref store) = self.state_store {
524 let store = store.clone();
525 let info = info.clone();
526 let max_entries = self.agent_log_config.max_entries;
527 let ttl_hours = self.agent_log_config.ttl_hours;
528 let batch_size = self.agent_log_config.prune_batch_size;
529 tokio::spawn(async move {
530 let _ = store
531 .save_json("agents", &id.to_string(), &info)
532 .await
533 .inspect_err(|e| tracing::warn!(agent_id = %id, error = %e, "Failed to persist agent to filesystem"));
534
535 if max_entries > 0 || ttl_hours > 0 {
537 let _ = store
538 .prune_agents_by_config(max_entries, ttl_hours, batch_size)
539 .await
540 .inspect_err(|e| tracing::warn!(error = %e, "Failed to prune agent log"));
541 }
542 });
543 }
544
545 #[cfg(feature = "sqlite-memory")]
547 if let Some(ref db) = self.agent_log_db {
548 let db = db.clone();
549 let info = info.clone();
550 let config = self.agent_log_config.clone();
551 tokio::spawn(async move {
552 let _ = db
553 .upsert_agent(&info)
554 .inspect_err(|e| tracing::warn!(agent_id = %id, error = %e, "Failed to upsert agent to SQLite"));
555
556 let _ = db
558 .prune(&config)
559 .inspect_err(|e| tracing::warn!(error = %e, "Failed to prune agent SQLite"));
560 });
561 }
562 }
563}
564
565#[cfg(test)]
566mod tests {
567 use super::*;
568 use crate::event_bus::EventBus;
569 use crate::types::AgentStatus;
570 use oxios_ouroboros::Seed;
572
573 async fn make_supervisor() -> BasicSupervisor {
578 let event_bus = EventBus::new(64);
579
580 let tmp = std::env::temp_dir().join(format!("oxios-test-{}", uuid::Uuid::new_v4()));
582 let _ = std::fs::create_dir_all(&tmp);
583
584 let state_store_2 =
585 Arc::new(crate::state_store::StateStore::new(tmp.join("state")).expect("state store"));
586 let state_store = state_store_2.clone();
587 let memory_manager = Arc::new({
588 let mut mm = crate::memory::MemoryManager::new(state_store.clone());
589 mm.set_git_layer(Arc::new(
590 crate::git_layer::GitLayer::new(tmp.join("git"), false).expect("git layer"),
591 ));
592 mm
593 });
594
595 let kernel_handle = Arc::new(crate::KernelHandle::new(
596 crate::kernel_handle::StateApi::new(state_store),
597 crate::kernel_handle::AgentApi::new(
598 Arc::new(crate::supervisor::NoOpSupervisor),
599 Arc::new(crate::budget::BudgetManager::new()),
600 memory_manager.clone(),
601 Some(event_bus.clone()),
602 ),
603 crate::kernel_handle::SecurityApi::new(
604 Arc::new(parking_lot::Mutex::new(crate::auth::AuthManager::new())),
605 Arc::new(oxi_sdk::observability::AuditTrail::new(100)),
606 Arc::new(parking_lot::Mutex::new(
607 crate::access_manager::AccessManager::new(),
608 )),
609 Arc::new(
610 crate::state_store::StateStore::new(tmp.join("state2")).expect("state store 2"),
611 ),
612 ),
613 crate::kernel_handle::PersonaApi::new(Arc::new(crate::persona::PersonaManager::new())),
614 crate::kernel_handle::ExtensionApi::new(Arc::new(crate::skill::SkillManager::new(
615 tmp.join("skills"),
616 tmp.join("share/skills"),
617 ))),
618 crate::kernel_handle::McpApi::new(Arc::new(crate::mcp::McpBridge::new())),
619 crate::kernel_handle::InfraApi::new(
620 Arc::new(
621 crate::git_layer::GitLayer::new(tmp.join("git"), false).expect("git layer"),
622 ),
623 Arc::new(crate::scheduler::AgentScheduler::new(4, 60, 300)),
624 Arc::new(crate::cron::CronScheduler::new(
625 Arc::new(
626 crate::state_store::StateStore::new(tmp.join("cron")).expect("cron state"),
627 ),
628 60,
629 )),
630 Arc::new(crate::resource_monitor::ResourceMonitor::new(60, 100)),
631 EventBus::new(64),
632 crate::config::OxiosConfig::default(),
633 std::time::Instant::now(),
634 ),
635 None,
636 crate::kernel_handle::ExecApi::new(
637 Arc::new(parking_lot::RwLock::new(
638 crate::config::ExecConfig::default(),
639 )),
640 Arc::new(parking_lot::Mutex::new(
641 crate::access_manager::AccessManager::new(),
642 )),
643 ),
644 crate::kernel_handle::A2aApi::new(Arc::new(crate::a2a::A2AProtocol::new(
645 EventBus::new(64),
646 ))),
647 crate::kernel_handle::EngineApi::new(
648 Arc::new(parking_lot::RwLock::new(
649 crate::config::OxiosConfig::default(),
650 )),
651 tmp.join("config.toml"),
652 Arc::new(crate::kernel_handle::RoutingStats::new()),
653 Arc::new(crate::engine::EngineHandle::new(Arc::new(
654 crate::OxiosEngine::new("anthropic/claude-sonnet-4-20250514"),
655 ))),
656 ),
657 Arc::new(oxios_markdown::KnowledgeBase::new(tmp.join("knowledge")).unwrap()),
658 Arc::new(
659 crate::kernel_handle::KnowledgeLens::new(
660 Arc::new(oxios_markdown::KnowledgeBase::new(tmp.join("knowledge")).unwrap()),
661 memory_manager.clone(),
662 )
663 .unwrap(),
664 ),
665 crate::kernel_handle::MarketplaceApi::new(
666 Arc::new(crate::skill::clawhub::ClawHubInstaller::new(
667 tmp.join("skills"),
668 tmp.join("state"),
669 None,
670 )),
671 Arc::new(
672 crate::skill::clawhub::ClawHubClient::new(None).expect("valid ClawHub client"),
673 ),
674 Arc::new(crate::skill::skills_sh::SkillsShInstaller::new(
675 tmp.join("skills"),
676 None,
677 None,
678 )),
679 Arc::new(
680 crate::skill::skills_sh::SkillsShClient::new(None, None)
681 .expect("valid Skills.sh client"),
682 ),
683 ),
684 None, None, ));
687
688 let engine = crate::OxiosEngine::new("mock/model");
689 let engine_handle = Arc::new(crate::engine::EngineHandle::new(Arc::new(engine)));
690 let runtime = AgentRuntime::new(engine_handle, "mock/model", kernel_handle, None);
691 BasicSupervisor::new(event_bus, runtime)
692 }
693
694 fn make_seed(goal: &str) -> Seed {
696 Seed {
697 id: uuid::Uuid::new_v4(),
698 goal: goal.to_string(),
699 constraints: vec![],
700 acceptance_criteria: vec![],
701 ontology: vec![],
702 created_at: chrono::Utc::now(),
703 generation: 0,
704 parent_seed_id: None,
705 cspace_hint: None,
706 original_request: String::new(),
707 output_schema: None,
708 project_id: None,
709 workspace_context: None,
710 mount_paths: Vec::new(),
711 }
712 }
713
714 #[tokio::test]
715 async fn test_fork_creates_agent() {
716 let supervisor = make_supervisor().await;
717 let seed = make_seed("Test agent");
718
719 let id = supervisor.fork(&seed).await.unwrap();
720
721 let agents = supervisor.list().await.unwrap();
722 assert_eq!(agents.len(), 1);
723 assert_eq!(agents[0].id, id);
724 assert_eq!(agents[0].name, "Test agent");
725 assert_eq!(agents[0].status, AgentStatus::Starting);
726 assert_eq!(agents[0].seed_id, Some(seed.id));
727 }
728
729 #[tokio::test]
730 async fn test_exec_updates_status_to_running() {
731 let supervisor = make_supervisor().await;
732 let seed = make_seed("Running agent");
733
734 let id = supervisor.fork(&seed).await.unwrap();
735 assert_eq!(supervisor.wait(id).await.unwrap(), AgentStatus::Starting);
736
737 supervisor.exec(id).await.unwrap();
738 assert_eq!(supervisor.wait(id).await.unwrap(), AgentStatus::Running);
739 }
740
741 #[tokio::test]
742 async fn test_kill_sets_stopped() {
743 let supervisor = make_supervisor().await;
744 let seed = make_seed("Doomed agent");
745
746 let id = supervisor.fork(&seed).await.unwrap();
747 supervisor.exec(id).await.unwrap();
748 assert_eq!(supervisor.wait(id).await.unwrap(), AgentStatus::Running);
749
750 supervisor.kill(id).await.unwrap();
751 assert_eq!(supervisor.wait(id).await.unwrap(), AgentStatus::Stopped);
752 }
753
754 #[tokio::test]
755 async fn test_kill_unknown_agent_returns_error() {
756 let supervisor = make_supervisor().await;
757 let unknown_id = uuid::Uuid::new_v4();
758
759 let result = supervisor.kill(unknown_id).await;
760 assert!(result.is_err());
761 assert!(result.unwrap_err().to_string().contains("not found"));
762 }
763
764 #[tokio::test]
765 async fn test_list_returns_all_agents() {
766 let supervisor = make_supervisor().await;
767
768 let id1 = supervisor.fork(&make_seed("Agent 1")).await.unwrap();
769 let id2 = supervisor.fork(&make_seed("Agent 2")).await.unwrap();
770 let id3 = supervisor.fork(&make_seed("Agent 3")).await.unwrap();
771
772 let agents = supervisor.list().await.unwrap();
773 assert_eq!(agents.len(), 3);
774
775 let ids: std::collections::HashSet<AgentId> = agents.iter().map(|a| a.id).collect();
776 assert!(ids.contains(&id1));
777 assert!(ids.contains(&id2));
778 assert!(ids.contains(&id3));
779 }
780
781 #[tokio::test]
782 async fn test_exec_unknown_agent_returns_error() {
783 let supervisor = make_supervisor().await;
784 let unknown_id = uuid::Uuid::new_v4();
785
786 let result = supervisor.exec(unknown_id).await;
787 assert!(result.is_err());
788 assert!(result.unwrap_err().to_string().contains("not found"));
789 }
790
791 #[tokio::test]
792 async fn test_wait_unknown_agent_returns_error() {
793 let supervisor = make_supervisor().await;
794 let unknown_id = uuid::Uuid::new_v4();
795
796 let result = supervisor.wait(unknown_id).await;
797 assert!(result.is_err());
798 assert!(result.unwrap_err().to_string().contains("not found"));
799 }
800}
801
802pub struct NoOpSupervisor;
808
809#[async_trait::async_trait]
810impl Supervisor for NoOpSupervisor {
811 async fn fork(&self, _spec: &Seed) -> Result<AgentId> {
812 Err(anyhow::anyhow!(
813 "NoOpSupervisor: fork not available during build"
814 ))
815 }
816 async fn exec(&self, _id: AgentId) -> Result<()> {
817 Err(anyhow::anyhow!(
818 "NoOpSupervisor: exec not available during build"
819 ))
820 }
821 async fn run_with_seed(&self, _id: AgentId, _seed: &Seed) -> Result<ExecutionResult> {
822 Err(anyhow::anyhow!(
823 "NoOpSupervisor: run_with_seed not available during build"
824 ))
825 }
826 async fn wait(&self, _id: AgentId) -> Result<AgentStatus> {
827 Err(anyhow::anyhow!(
828 "NoOpSupervisor: wait not available during build"
829 ))
830 }
831 async fn kill(&self, _id: AgentId) -> Result<()> {
832 Err(anyhow::anyhow!(
833 "NoOpSupervisor: kill not available during build"
834 ))
835 }
836 async fn list(&self) -> Result<Vec<AgentInfo>> {
837 Ok(Vec::new())
838 }
839}