1pub mod cache;
13pub mod collapse_controller;
14pub mod executor;
15pub mod kubernetes_executor;
16pub mod orchestrator;
17pub mod rate_limiter;
18pub mod remote_subtask;
19pub mod result_store;
20pub mod subtask;
21
22pub use cache::{CacheConfig, CacheStats, SwarmCache};
23pub use collapse_controller::{
24 BranchEvaluation, BranchObservation, BranchRuntimeState, CoherenceScore, CollapseController,
25 CollapsePolicy, CollapseTick, KillDecision,
26};
27pub use executor::{SwarmExecutor, run_agent_loop};
28pub use orchestrator::Orchestrator;
29pub use rate_limiter::{AdaptiveRateLimiter, RateLimitInfo, RateLimitStats};
30pub use result_store::{ResultStore, ResultStoreContext, SharedResult, SubTaskStoreHandle};
31pub use subtask::{SubAgent, SubTask, SubTaskContext, SubTaskResult, SubTaskStatus};
32
33use anyhow::Result;
34use async_trait::async_trait;
35
36#[async_trait]
41pub trait Actor: Send + Sync {
42 fn actor_id(&self) -> &str;
44
45 fn actor_status(&self) -> ActorStatus;
47
48 async fn initialize(&mut self) -> Result<()>;
50
51 async fn shutdown(&mut self) -> Result<()>;
53}
54
55#[async_trait]
60pub trait Handler<M>: Actor {
61 type Response: Send + Sync;
63
64 async fn handle(&mut self, message: M) -> Result<Self::Response>;
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub enum ActorStatus {
71 Initializing,
73 Ready,
75 Busy,
77 Paused,
79 ShuttingDown,
81 Stopped,
83}
84
85impl std::fmt::Display for ActorStatus {
86 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87 match self {
88 ActorStatus::Initializing => write!(f, "initializing"),
89 ActorStatus::Ready => write!(f, "ready"),
90 ActorStatus::Busy => write!(f, "busy"),
91 ActorStatus::Paused => write!(f, "paused"),
92 ActorStatus::ShuttingDown => write!(f, "shutting_down"),
93 ActorStatus::Stopped => write!(f, "stopped"),
94 }
95 }
96}
97
98#[derive(Debug, Clone)]
100pub enum SwarmMessage {
101 ExecuteTask {
103 task_id: String,
104 instruction: String,
105 },
106 Progress {
108 task_id: String,
109 progress: f32,
110 message: String,
111 },
112 TaskCompleted { task_id: String, result: String },
114 TaskFailed { task_id: String, error: String },
116 ToolRequest {
118 tool_id: String,
119 arguments: serde_json::Value,
120 },
121 ToolResponse {
123 tool_id: String,
124 result: crate::tool::ToolResult,
125 },
126}
127
128use serde::{Deserialize, Serialize};
129
130pub const MAX_SUBAGENTS: usize = 100;
132
133pub const MAX_TOOL_CALLS: usize = 1500;
135
136#[derive(Debug, Clone, Serialize, Deserialize)]
138pub struct SwarmConfig {
139 pub max_subagents: usize,
141
142 pub max_steps_per_subagent: usize,
144
145 pub max_total_steps: usize,
147
148 pub subagent_timeout_secs: u64,
150
151 pub parallel_enabled: bool,
153
154 pub critical_path_threshold: usize,
156
157 pub model: Option<String>,
159
160 pub max_concurrent_requests: usize,
162
163 pub request_delay_ms: u64,
165
166 pub worktree_enabled: bool,
168
169 pub worktree_auto_merge: bool,
171
172 pub working_dir: Option<String>,
174
175 #[serde(default)]
177 pub execution_mode: ExecutionMode,
178
179 #[serde(default = "default_k8s_pod_budget")]
181 pub k8s_pod_budget: usize,
182
183 #[serde(default)]
185 pub k8s_subagent_image: Option<String>,
186
187 #[serde(default = "default_max_retries")]
189 pub max_retries: u32,
190
191 #[serde(default = "default_base_delay_ms")]
193 pub base_delay_ms: u64,
194
195 #[serde(default = "default_max_delay_ms")]
197 pub max_delay_ms: u64,
198}
199
200#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
202#[serde(rename_all = "snake_case")]
203#[derive(Default)]
204pub enum ExecutionMode {
205 #[default]
207 LocalThread,
208 KubernetesPod,
210}
211
212impl ExecutionMode {
213 pub fn from_cli_value(value: &str) -> Self {
214 match value {
215 "k8s" | "kubernetes" | "kubernetes-pod" | "pod" => Self::KubernetesPod,
216 _ => Self::LocalThread,
217 }
218 }
219}
220
221fn default_k8s_pod_budget() -> usize {
222 8
223}
224
225fn default_max_retries() -> u32 {
226 3
227}
228
229fn default_base_delay_ms() -> u64 {
230 500
231}
232
233fn default_max_delay_ms() -> u64 {
234 30_000
235}
236
237impl Default for SwarmConfig {
238 fn default() -> Self {
239 Self {
240 max_subagents: MAX_SUBAGENTS,
241 max_steps_per_subagent: 100,
242 max_total_steps: MAX_TOOL_CALLS,
243 subagent_timeout_secs: 600, parallel_enabled: true,
245 critical_path_threshold: 10,
246 model: None,
247 max_concurrent_requests: 3, request_delay_ms: 1000, worktree_enabled: true, worktree_auto_merge: true, working_dir: None,
252 execution_mode: ExecutionMode::LocalThread,
253 k8s_pod_budget: 8,
254 k8s_subagent_image: None,
255 max_retries: 3,
256 base_delay_ms: 500,
257 max_delay_ms: 30_000,
258 }
259 }
260}
261
262#[derive(Debug, Clone, Default, Serialize, Deserialize)]
264pub struct SwarmStats {
265 pub subagents_spawned: usize,
267
268 pub subagents_completed: usize,
270
271 pub subagents_failed: usize,
273
274 pub total_tool_calls: usize,
276
277 pub critical_path_length: usize,
279
280 pub execution_time_ms: u64,
282
283 pub sequential_time_estimate_ms: u64,
285
286 pub speedup_factor: f64,
288
289 pub stages: Vec<StageStats>,
291
292 pub rate_limit_stats: RateLimitStats,
294
295 pub cache_hits: u64,
297
298 pub cache_misses: u64,
300}
301
302#[derive(Debug, Clone, Default, Serialize, Deserialize)]
304pub struct StageStats {
305 pub stage: usize,
307
308 pub subagent_count: usize,
310
311 pub max_steps: usize,
313
314 pub total_steps: usize,
316
317 pub execution_time_ms: u64,
319}
320
321impl SwarmStats {
322 pub fn calculate_speedup(&mut self) {
324 if self.execution_time_ms > 0 {
325 self.speedup_factor =
326 self.sequential_time_estimate_ms as f64 / self.execution_time_ms as f64;
327 }
328 }
329
330 pub fn calculate_critical_path(&mut self) {
332 self.critical_path_length = self.stages.iter().map(|s| s.max_steps).sum();
333 }
334
335 pub fn merge_cache_stats(&mut self, cache_stats: &cache::CacheStats) {
337 self.cache_hits = cache_stats.hits;
338 self.cache_misses = cache_stats.misses;
339 }
340
341 pub fn cache_hit_rate(&self) -> f64 {
343 let total = self.cache_hits + self.cache_misses;
344 if total == 0 {
345 0.0
346 } else {
347 self.cache_hits as f64 / total as f64
348 }
349 }
350}
351
352#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
354#[serde(rename_all = "snake_case")]
355#[derive(Default)]
356pub enum DecompositionStrategy {
357 #[default]
359 Automatic,
360
361 ByDomain,
363
364 ByData,
366
367 ByStage,
369
370 None,
372}
373
374#[derive(Debug, Clone, Serialize, Deserialize)]
376pub struct SwarmResult {
377 pub success: bool,
379
380 pub result: String,
382
383 pub subtask_results: Vec<SubTaskResult>,
385
386 pub stats: SwarmStats,
388
389 pub artifacts: Vec<SwarmArtifact>,
391
392 pub error: Option<String>,
394}
395
396#[derive(Debug, Clone, Serialize, Deserialize)]
398pub struct SwarmArtifact {
399 pub artifact_type: String,
401
402 pub name: String,
404
405 pub content: String,
407
408 pub source_subagent: Option<String>,
410
411 pub mime_type: Option<String>,
413}