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 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}