Skip to main content

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