codetether_agent/swarm/mod.rs
1//! Swarm orchestration for parallel sub-agent execution
2//!
3//! Implements the SubAgent/SubTask paradigm for parallel task execution,
4//! similar to Kimi K2.5's Agent Swarm but generalized for the CodeTether ecosystem.
5//!
6//! Key concepts:
7//! - **Orchestrator**: Decomposes complex tasks into parallelizable subtasks
8//! - **SubAgent**: A dynamically instantiated agent for executing a subtask
9//! - **SubTask**: A unit of work that can be executed in parallel
10//! - **Critical Path**: Latency-oriented metric for parallel execution
11
12pub mod executor;
13pub mod orchestrator;
14pub mod subtask;
15
16pub use executor::SwarmExecutor;
17pub use orchestrator::Orchestrator;
18pub use subtask::{SubAgent, SubTask, SubTaskContext, SubTaskResult, SubTaskStatus};
19
20use async_trait::async_trait;
21use anyhow::Result;
22
23/// Actor trait for swarm participants
24///
25/// An Actor is an entity that can participate in the swarm by receiving
26/// and processing messages. This is the base trait for all swarm participants.
27#[async_trait]
28pub trait Actor: Send + Sync {
29 /// Get the unique identifier for this actor
30 fn actor_id(&self) -> &str;
31
32 /// Get the actor's current status
33 fn actor_status(&self) -> ActorStatus;
34
35 /// Initialize the actor for swarm participation
36 async fn initialize(&mut self) -> Result<()>;
37
38 /// Shutdown the actor gracefully
39 async fn shutdown(&mut self) -> Result<()>;
40}
41
42/// Handler trait for processing messages in the swarm
43///
44/// A Handler can receive and process messages of a specific type.
45/// This enables actors to respond to different message types.
46#[async_trait]
47pub trait Handler<M>: Actor {
48 /// The response type for this handler
49 type Response: Send + Sync;
50
51 /// Handle a message and return a response
52 async fn handle(&mut self, message: M) -> Result<Self::Response>;
53}
54
55/// Status of an actor in the swarm
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub enum ActorStatus {
58 /// Actor is initializing
59 Initializing,
60 /// Actor is ready to process messages
61 Ready,
62 /// Actor is currently processing
63 Busy,
64 /// Actor is paused
65 Paused,
66 /// Actor is shutting down
67 ShuttingDown,
68 /// Actor has stopped
69 Stopped,
70}
71
72impl std::fmt::Display for ActorStatus {
73 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74 match self {
75 ActorStatus::Initializing => write!(f, "initializing"),
76 ActorStatus::Ready => write!(f, "ready"),
77 ActorStatus::Busy => write!(f, "busy"),
78 ActorStatus::Paused => write!(f, "paused"),
79 ActorStatus::ShuttingDown => write!(f, "shutting_down"),
80 ActorStatus::Stopped => write!(f, "stopped"),
81 }
82 }
83}
84
85/// Message types for swarm coordination
86#[derive(Debug, Clone)]
87pub enum SwarmMessage {
88 /// Execute a task
89 ExecuteTask { task_id: String, instruction: String },
90 /// Report progress
91 Progress { task_id: String, progress: f32, message: String },
92 /// Task completed
93 TaskCompleted { task_id: String, result: String },
94 /// Task failed
95 TaskFailed { task_id: String, error: String },
96 /// Request tool execution
97 ToolRequest { tool_id: String, arguments: serde_json::Value },
98 /// Tool response
99 ToolResponse { tool_id: String, result: crate::tool::ToolResult },
100}
101
102use serde::{Deserialize, Serialize};
103
104/// Maximum number of concurrent sub-agents
105pub const MAX_SUBAGENTS: usize = 100;
106
107/// Maximum total tool calls across all sub-agents
108pub const MAX_TOOL_CALLS: usize = 1500;
109
110/// Swarm execution configuration
111#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct SwarmConfig {
113 /// Maximum number of concurrent sub-agents
114 pub max_subagents: usize,
115
116 /// Maximum tool calls per sub-agent
117 pub max_steps_per_subagent: usize,
118
119 /// Maximum total tool calls across all sub-agents
120 pub max_total_steps: usize,
121
122 /// Timeout for individual sub-agent execution (seconds)
123 pub subagent_timeout_secs: u64,
124
125 /// Whether to enable parallel execution
126 pub parallel_enabled: bool,
127
128 /// Critical path optimization threshold
129 pub critical_path_threshold: usize,
130
131 /// Model to use for sub-agents (provider/model format)
132 pub model: Option<String>,
133
134 /// Max concurrent API requests (rate limiting)
135 pub max_concurrent_requests: usize,
136
137 /// Delay between API calls in ms (rate limiting)
138 pub request_delay_ms: u64,
139}
140
141impl Default for SwarmConfig {
142 fn default() -> Self {
143 Self {
144 max_subagents: MAX_SUBAGENTS,
145 max_steps_per_subagent: 100,
146 max_total_steps: MAX_TOOL_CALLS,
147 subagent_timeout_secs: 600, // 10 minutes for complex tasks
148 parallel_enabled: true,
149 critical_path_threshold: 10,
150 model: None,
151 max_concurrent_requests: 3, // V1 tier allows 3 concurrent
152 request_delay_ms: 1000, // V1 tier: 60 RPM, 3 concurrent = fast
153 }
154 }
155}
156
157/// Swarm execution statistics
158#[derive(Debug, Clone, Default, Serialize, Deserialize)]
159pub struct SwarmStats {
160 /// Total number of sub-agents spawned
161 pub subagents_spawned: usize,
162
163 /// Number of sub-agents that completed successfully
164 pub subagents_completed: usize,
165
166 /// Number of sub-agents that failed
167 pub subagents_failed: usize,
168
169 /// Total tool calls across all sub-agents
170 pub total_tool_calls: usize,
171
172 /// Critical path length (longest chain of dependent steps)
173 pub critical_path_length: usize,
174
175 /// Wall-clock execution time (milliseconds)
176 pub execution_time_ms: u64,
177
178 /// Estimated sequential time (milliseconds)
179 pub sequential_time_estimate_ms: u64,
180
181 /// Parallelization speedup factor
182 pub speedup_factor: f64,
183
184 /// Per-stage statistics
185 pub stages: Vec<StageStats>,
186}
187
188/// Statistics for a single execution stage
189#[derive(Debug, Clone, Default, Serialize, Deserialize)]
190pub struct StageStats {
191 /// Stage index
192 pub stage: usize,
193
194 /// Number of sub-agents in this stage
195 pub subagent_count: usize,
196
197 /// Maximum steps in this stage (critical path contribution)
198 pub max_steps: usize,
199
200 /// Total steps across all sub-agents in this stage
201 pub total_steps: usize,
202
203 /// Execution time for this stage (milliseconds)
204 pub execution_time_ms: u64,
205}
206
207impl SwarmStats {
208 /// Calculate the speedup factor
209 pub fn calculate_speedup(&mut self) {
210 if self.execution_time_ms > 0 {
211 self.speedup_factor = self.sequential_time_estimate_ms as f64 / self.execution_time_ms as f64;
212 }
213 }
214
215 /// Calculate critical path from stages
216 pub fn calculate_critical_path(&mut self) {
217 self.critical_path_length = self.stages.iter().map(|s| s.max_steps).sum();
218 }
219}
220
221/// Decomposition strategy for breaking down tasks
222#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
223#[serde(rename_all = "snake_case")]
224pub enum DecompositionStrategy {
225 /// Let the AI decide how to decompose
226 Automatic,
227
228 /// Decompose by domain/specialty
229 ByDomain,
230
231 /// Decompose by data partition
232 ByData,
233
234 /// Decompose by workflow stage
235 ByStage,
236
237 /// Single agent (no decomposition)
238 None,
239}
240
241impl Default for DecompositionStrategy {
242 fn default() -> Self {
243 Self::Automatic
244 }
245}
246
247/// Result of swarm execution
248#[derive(Debug, Clone, Serialize, Deserialize)]
249pub struct SwarmResult {
250 /// Overall success status
251 pub success: bool,
252
253 /// Aggregated result from all sub-agents
254 pub result: String,
255
256 /// Individual sub-task results
257 pub subtask_results: Vec<SubTaskResult>,
258
259 /// Execution statistics
260 pub stats: SwarmStats,
261
262 /// Any artifacts produced
263 pub artifacts: Vec<SwarmArtifact>,
264
265 /// Error message if failed
266 pub error: Option<String>,
267}
268
269/// An artifact produced by the swarm
270#[derive(Debug, Clone, Serialize, Deserialize)]
271pub struct SwarmArtifact {
272 /// Artifact type
273 pub artifact_type: String,
274
275 /// Name/identifier
276 pub name: String,
277
278 /// Content or path
279 pub content: String,
280
281 /// Which sub-agent produced it
282 pub source_subagent: Option<String>,
283
284 /// MIME type
285 pub mime_type: Option<String>,
286}