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, run_agent_loop};
17pub use orchestrator::Orchestrator;
18pub use subtask::{SubAgent, SubTask, SubTaskContext, SubTaskResult, SubTaskStatus};
19
20use anyhow::Result;
21use async_trait::async_trait;
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 {
90 task_id: String,
91 instruction: String,
92 },
93 /// Report progress
94 Progress {
95 task_id: String,
96 progress: f32,
97 message: String,
98 },
99 /// Task completed
100 TaskCompleted { task_id: String, result: String },
101 /// Task failed
102 TaskFailed { task_id: String, error: String },
103 /// Request tool execution
104 ToolRequest {
105 tool_id: String,
106 arguments: serde_json::Value,
107 },
108 /// Tool response
109 ToolResponse {
110 tool_id: String,
111 result: crate::tool::ToolResult,
112 },
113}
114
115use serde::{Deserialize, Serialize};
116
117/// Maximum number of concurrent sub-agents
118pub const MAX_SUBAGENTS: usize = 100;
119
120/// Maximum total tool calls across all sub-agents
121pub const MAX_TOOL_CALLS: usize = 1500;
122
123/// Swarm execution configuration
124#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct SwarmConfig {
126 /// Maximum number of concurrent sub-agents
127 pub max_subagents: usize,
128
129 /// Maximum tool calls per sub-agent
130 pub max_steps_per_subagent: usize,
131
132 /// Maximum total tool calls across all sub-agents
133 pub max_total_steps: usize,
134
135 /// Timeout for individual sub-agent execution (seconds)
136 pub subagent_timeout_secs: u64,
137
138 /// Whether to enable parallel execution
139 pub parallel_enabled: bool,
140
141 /// Critical path optimization threshold
142 pub critical_path_threshold: usize,
143
144 /// Model to use for sub-agents (provider/model format)
145 pub model: Option<String>,
146
147 /// Max concurrent API requests (rate limiting)
148 pub max_concurrent_requests: usize,
149
150 /// Delay between API calls in ms (rate limiting)
151 pub request_delay_ms: u64,
152
153 /// Enable worktree isolation for sub-agents
154 pub worktree_enabled: bool,
155
156 /// Automatically merge worktree changes on success
157 pub worktree_auto_merge: bool,
158
159 /// Working directory for worktree creation
160 pub working_dir: Option<String>,
161}
162
163impl Default for SwarmConfig {
164 fn default() -> Self {
165 Self {
166 max_subagents: MAX_SUBAGENTS,
167 max_steps_per_subagent: 100,
168 max_total_steps: MAX_TOOL_CALLS,
169 subagent_timeout_secs: 600, // 10 minutes for complex tasks
170 parallel_enabled: true,
171 critical_path_threshold: 10,
172 model: None,
173 max_concurrent_requests: 3, // V1 tier allows 3 concurrent
174 request_delay_ms: 1000, // V1 tier: 60 RPM, 3 concurrent = fast
175 worktree_enabled: true, // Enable worktree isolation by default
176 worktree_auto_merge: true, // Auto-merge on success
177 working_dir: None,
178 }
179 }
180}
181
182/// Swarm execution statistics
183#[derive(Debug, Clone, Default, Serialize, Deserialize)]
184pub struct SwarmStats {
185 /// Total number of sub-agents spawned
186 pub subagents_spawned: usize,
187
188 /// Number of sub-agents that completed successfully
189 pub subagents_completed: usize,
190
191 /// Number of sub-agents that failed
192 pub subagents_failed: usize,
193
194 /// Total tool calls across all sub-agents
195 pub total_tool_calls: usize,
196
197 /// Critical path length (longest chain of dependent steps)
198 pub critical_path_length: usize,
199
200 /// Wall-clock execution time (milliseconds)
201 pub execution_time_ms: u64,
202
203 /// Estimated sequential time (milliseconds)
204 pub sequential_time_estimate_ms: u64,
205
206 /// Parallelization speedup factor
207 pub speedup_factor: f64,
208
209 /// Per-stage statistics
210 pub stages: Vec<StageStats>,
211}
212
213/// Statistics for a single execution stage
214#[derive(Debug, Clone, Default, Serialize, Deserialize)]
215pub struct StageStats {
216 /// Stage index
217 pub stage: usize,
218
219 /// Number of sub-agents in this stage
220 pub subagent_count: usize,
221
222 /// Maximum steps in this stage (critical path contribution)
223 pub max_steps: usize,
224
225 /// Total steps across all sub-agents in this stage
226 pub total_steps: usize,
227
228 /// Execution time for this stage (milliseconds)
229 pub execution_time_ms: u64,
230}
231
232impl SwarmStats {
233 /// Calculate the speedup factor
234 pub fn calculate_speedup(&mut self) {
235 if self.execution_time_ms > 0 {
236 self.speedup_factor =
237 self.sequential_time_estimate_ms as f64 / self.execution_time_ms as f64;
238 }
239 }
240
241 /// Calculate critical path from stages
242 pub fn calculate_critical_path(&mut self) {
243 self.critical_path_length = self.stages.iter().map(|s| s.max_steps).sum();
244 }
245}
246
247/// Decomposition strategy for breaking down tasks
248#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
249#[serde(rename_all = "snake_case")]
250pub enum DecompositionStrategy {
251 /// Let the AI decide how to decompose
252 Automatic,
253
254 /// Decompose by domain/specialty
255 ByDomain,
256
257 /// Decompose by data partition
258 ByData,
259
260 /// Decompose by workflow stage
261 ByStage,
262
263 /// Single agent (no decomposition)
264 None,
265}
266
267impl Default for DecompositionStrategy {
268 fn default() -> Self {
269 Self::Automatic
270 }
271}
272
273/// Result of swarm execution
274#[derive(Debug, Clone, Serialize, Deserialize)]
275pub struct SwarmResult {
276 /// Overall success status
277 pub success: bool,
278
279 /// Aggregated result from all sub-agents
280 pub result: String,
281
282 /// Individual sub-task results
283 pub subtask_results: Vec<SubTaskResult>,
284
285 /// Execution statistics
286 pub stats: SwarmStats,
287
288 /// Any artifacts produced
289 pub artifacts: Vec<SwarmArtifact>,
290
291 /// Error message if failed
292 pub error: Option<String>,
293}
294
295/// An artifact produced by the swarm
296#[derive(Debug, Clone, Serialize, Deserialize)]
297pub struct SwarmArtifact {
298 /// Artifact type
299 pub artifact_type: String,
300
301 /// Name/identifier
302 pub name: String,
303
304 /// Content or path
305 pub content: String,
306
307 /// Which sub-agent produced it
308 pub source_subagent: Option<String>,
309
310 /// MIME type
311 pub mime_type: Option<String>,
312}