aca 0.3.1

A Rust-based agentic tool that automates coding tasks using Claude Code and OpenAI Codex CLI integrations
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
use crate::task::types::*;
use anyhow::Result;
use chrono::{DateTime, Duration, Utc};
use futures::future::BoxFuture;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
use tracing::{debug, error, info};

/// Task execution context with all necessary state
#[derive(Debug, Clone)]
pub struct TaskExecutionContext {
    pub task_id: TaskId,
    pub claude_session_id: Option<ClaudeSessionId>,
    pub working_directory: PathBuf,
    pub environment: HashMap<String, String>,
    pub file_watchers: Vec<String>, // Simplified file watcher representation
    pub resource_allocation: ResourceAllocation,
    pub execution_start: DateTime<Utc>,
    pub timeout: Option<Duration>,
}

/// Resource allocation for task execution
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResourceAllocation {
    pub max_memory_mb: u64,
    pub max_cpu_percent: f64,
    pub max_duration: Duration,
    pub temp_storage_mb: u64,
    pub network_bandwidth_mbps: f64,
}

/// Result of task execution
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum TaskExecutionResult {
    /// Task completed successfully
    Completed {
        result: serde_json::Value,
        files_modified: Vec<PathBuf>,
        build_artifacts: Vec<PathBuf>,
        execution_metrics: ExecutionMetrics,
    },
    /// Task completed but created subtasks
    CompletedWithSubtasks {
        result: serde_json::Value,
        subtasks: Vec<TaskSpec>,
        files_modified: Vec<PathBuf>,
        execution_metrics: ExecutionMetrics,
    },
    /// Task is blocked and cannot proceed
    Blocked {
        reason: String,
        required_resources: Vec<String>,
        retry_after: Option<Duration>,
        partial_progress: Option<serde_json::Value>,
    },
    /// Task failed with error
    Failed {
        error: TaskError,
        partial_progress: Option<serde_json::Value>,
        recovery_suggestions: Vec<String>,
    },
}

/// Execution metrics for monitoring
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExecutionMetrics {
    pub duration: Duration,
    pub memory_used_mb: u64,
    pub cpu_time_seconds: f64,
    pub files_read: u32,
    pub files_written: u32,
    pub api_calls_made: u32,
    pub tokens_consumed: u32,
}

/// Task executor that handles the actual execution logic
pub struct TaskExecutor {
    config: ExecutorConfig,
    resource_limits: ResourceAllocation,
}

/// Configuration for task executor
#[derive(Debug, Clone)]
pub struct ExecutorConfig {
    pub default_timeout: Duration,
    pub max_retries: u32,
    pub enable_monitoring: bool,
    pub workspace_root: PathBuf,
    pub temp_dir: PathBuf,
}

/// Mock Claude Code interface for now
pub trait ClaudeCodeInterface: Send + Sync {
    fn execute_task_with_context(
        &self,
        prompt: String,
        context: &TaskExecutionContext,
    ) -> BoxFuture<'_, Result<String>>;

    fn create_session(&self) -> BoxFuture<'_, Result<ClaudeSessionId>>;
    fn close_session(&self, session_id: ClaudeSessionId) -> BoxFuture<'_, Result<()>>;
}

/// Simple mock implementation
pub struct MockClaudeInterface;

impl ClaudeCodeInterface for MockClaudeInterface {
    fn execute_task_with_context(
        &self,
        prompt: String,
        _context: &TaskExecutionContext,
    ) -> BoxFuture<'_, Result<String>> {
        Box::pin(async move {
            // Mock implementation - just returns a success message
            tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
            Ok(format!(
                "Task executed successfully with prompt: {}",
                prompt.chars().take(50).collect::<String>()
            ))
        })
    }

    fn create_session(&self) -> BoxFuture<'_, Result<ClaudeSessionId>> {
        Box::pin(async move { Ok(uuid::Uuid::new_v4()) })
    }

    fn close_session(&self, _session_id: ClaudeSessionId) -> BoxFuture<'_, Result<()>> {
        Box::pin(async move { Ok(()) })
    }
}

impl TaskExecutor {
    /// Create a new task executor
    pub fn new(config: ExecutorConfig, resource_limits: ResourceAllocation) -> Self {
        Self {
            config,
            resource_limits,
        }
    }

    /// Execute a task with the given context
    pub async fn execute_task(
        &self,
        task: &Task,
        claude_interface: &dyn ClaudeCodeInterface,
    ) -> Result<TaskExecutionResult> {
        let execution_start = Utc::now();

        // Set up execution context
        let context = self
            .prepare_execution_context(task, execution_start)
            .await?;

        info!("Starting execution of task {}: {}", task.id, task.title);

        // Execute the task through Claude Code
        let result = match self
            .execute_with_claude(task, &context, claude_interface)
            .await
        {
            Ok(result) => result,
            Err(error) => {
                error!("Task execution failed: {}", error);
                return Ok(TaskExecutionResult::Failed {
                    error: TaskError::Other {
                        message: error.to_string(),
                        source: Some("TaskExecutor".to_string()),
                    },
                    partial_progress: None,
                    recovery_suggestions: vec![
                        "Check task requirements".to_string(),
                        "Verify dependencies".to_string(),
                    ],
                });
            }
        };

        // Calculate execution metrics
        let duration = Utc::now().signed_duration_since(execution_start);
        let metrics = ExecutionMetrics {
            duration,
            memory_used_mb: 128, // Mock values
            cpu_time_seconds: duration.num_seconds() as f64 * 0.1,
            files_read: 5,
            files_written: 2,
            api_calls_made: 1,
            tokens_consumed: 1000,
        };

        // Process the execution result
        let execution_result = self.process_claude_response(result, task, metrics).await?;

        info!("Completed execution of task {} in {:?}", task.id, duration);
        Ok(execution_result)
    }

    /// Prepare execution context for a task
    async fn prepare_execution_context(
        &self,
        task: &Task,
        execution_start: DateTime<Utc>,
    ) -> Result<TaskExecutionContext> {
        let working_dir = self.config.workspace_root.join(format!("task_{}", task.id));

        // Create working directory if it doesn't exist
        if !working_dir.exists() {
            tokio::fs::create_dir_all(&working_dir).await?;
        }

        // Prepare environment variables
        let mut environment = HashMap::new();
        environment.insert("TASK_ID".to_string(), task.id.to_string());
        environment.insert("TASK_TITLE".to_string(), task.title.clone());
        environment.insert("WORKING_DIR".to_string(), working_dir.display().to_string());

        // Add task-specific environment variables
        for (key, value) in &task.metadata.context_requirements.environment_vars {
            environment.insert(key.clone(), value.clone());
        }

        let timeout = task
            .metadata
            .estimated_duration
            .or(Some(self.config.default_timeout));

        Ok(TaskExecutionContext {
            task_id: task.id,
            claude_session_id: None,
            working_directory: working_dir,
            environment,
            file_watchers: Vec::new(),
            resource_allocation: self.resource_limits.clone(),
            execution_start,
            timeout,
        })
    }

    /// Execute task through Claude Code interface
    async fn execute_with_claude(
        &self,
        task: &Task,
        context: &TaskExecutionContext,
        claude_interface: &dyn ClaudeCodeInterface,
    ) -> Result<String> {
        // Build task prompt with context
        let prompt = self.build_task_prompt(task, context).await?;

        debug!("Executing task with prompt length: {}", prompt.len());

        // Execute through Claude Code interface
        let response = claude_interface
            .execute_task_with_context(prompt, context)
            .await?;

        Ok(response)
    }

    /// Build a comprehensive prompt for Claude Code
    async fn build_task_prompt(
        &self,
        task: &Task,
        context: &TaskExecutionContext,
    ) -> Result<String> {
        let mut prompt = String::new();

        prompt.push_str(&format!("# Task: {}\n\n", task.title));
        prompt.push_str(&format!("## Description\n{}\n\n", task.description));

        // Add priority and complexity information
        prompt.push_str("## Task Metadata\n");
        prompt.push_str(&format!("- Priority: {:?}\n", task.metadata.priority));
        if let Some(complexity) = &task.metadata.estimated_complexity {
            prompt.push_str(&format!("- Complexity: {:?}\n", complexity));
        }
        prompt.push_str(&format!(
            "- Created: {}\n\n",
            task.created_at.format("%Y-%m-%d %H:%M:%S")
        ));

        // Add file references
        if !task.metadata.file_refs.is_empty() {
            prompt.push_str("## Relevant Files\n");
            for file_ref in &task.metadata.file_refs {
                prompt.push_str(&format!(
                    "- {} ({})\n",
                    file_ref.path.display(),
                    format!("{:?}", file_ref.importance).to_lowercase()
                ));
            }
            prompt.push('\n');
        }

        // Add repository context
        if !task.metadata.repository_refs.is_empty() {
            prompt.push_str("## Repository Context\n");
            for repo_ref in &task.metadata.repository_refs {
                prompt.push_str(&format!("- Repository: {}\n", repo_ref.name));
                if let Some(branch) = &repo_ref.branch {
                    prompt.push_str(&format!("  Branch: {}\n", branch));
                }
            }
            prompt.push('\n');
        }

        // Add context requirements
        let context_reqs = &task.metadata.context_requirements;
        if !context_reqs.is_empty() {
            prompt.push_str("## Context Requirements\n");

            if !context_reqs.required_files.is_empty() {
                prompt.push_str("### Required Files\n");
                for file in &context_reqs.required_files {
                    prompt.push_str(&format!("- {}\n", file.display()));
                }
            }

            if !context_reqs.build_dependencies.is_empty() {
                prompt.push_str("### Build Dependencies\n");
                for dep in &context_reqs.build_dependencies {
                    prompt.push_str(&format!("- {}\n", dep));
                }
            }
            prompt.push('\n');
        }

        // Add working directory information
        prompt.push_str(&format!(
            "## Working Directory\n{}\n\n",
            context.working_directory.display()
        ));

        // Add execution constraints
        prompt.push_str("## Execution Constraints\n");
        prompt.push_str(&format!(
            "- Max Memory: {} MB\n",
            context.resource_allocation.max_memory_mb
        ));
        prompt.push_str(&format!(
            "- Max CPU: {:.1}%\n",
            context.resource_allocation.max_cpu_percent
        ));
        if let Some(timeout) = context.timeout {
            prompt.push_str(&format!("- Timeout: {} minutes\n", timeout.num_minutes()));
        }

        prompt.push_str("\n## Instructions\n");
        prompt.push_str("Please execute this task according to the requirements above. ");
        prompt.push_str("If the task is complex and needs to be broken down, ");
        prompt.push_str("provide subtask specifications in your response. ");
        prompt
            .push_str("Report any files you modify and provide a summary of the work completed.\n");

        Ok(prompt)
    }

    /// Process Claude Code response and determine execution result
    async fn process_claude_response(
        &self,
        response: String,
        task: &Task,
        metrics: ExecutionMetrics,
    ) -> Result<TaskExecutionResult> {
        // Simple response processing - in practice would be more sophisticated
        debug!("Processing Claude response length: {}", response.len());

        // Check for blocking conditions
        if response.to_lowercase().contains("blocked")
            || response.to_lowercase().contains("cannot proceed")
        {
            return Ok(TaskExecutionResult::Blocked {
                reason: "Task reported as blocked by Claude Code".to_string(),
                required_resources: vec!["Manual intervention".to_string()],
                retry_after: Some(Duration::minutes(30)),
                partial_progress: Some(serde_json::json!({"response": response})),
            });
        }

        // Check for subtask creation indicators
        if response.to_lowercase().contains("subtask")
            || response.to_lowercase().contains("break down")
        {
            // In practice, would parse actual subtask specifications from response
            let subtasks = vec![TaskSpec {
                title: format!("Subtask for {}", task.title),
                description: "Auto-generated subtask based on task decomposition".to_string(),
                metadata: TaskMetadata {
                    priority: task.metadata.priority.clone(),
                    estimated_complexity: Some(ComplexityLevel::Simple),
                    estimated_duration: Some(Duration::minutes(15)),
                    repository_refs: task.metadata.repository_refs.clone(),
                    file_refs: Vec::new(),
                    tags: task.metadata.tags.clone(),
                    context_requirements: ContextRequirements::new(),
                },
                dependencies: Vec::new(),
            }];

            return Ok(TaskExecutionResult::CompletedWithSubtasks {
                result: serde_json::json!({
                    "response": response,
                    "subtasks_created": subtasks.len()
                }),
                subtasks,
                files_modified: vec![self.config.workspace_root.join("example_file.rs")],
                execution_metrics: metrics,
            });
        }

        // Default: successful completion
        Ok(TaskExecutionResult::Completed {
            result: serde_json::json!({
                "response": response,
                "status": "completed"
            }),
            files_modified: vec![self.config.workspace_root.join("example_file.rs")],
            build_artifacts: Vec::new(),
            execution_metrics: metrics,
        })
    }

    /// Estimate resource requirements for a task
    pub fn estimate_resource_requirements(&self, task: &Task) -> ResourceAllocation {
        let base_memory = 256;
        let base_cpu = 20.0;

        let complexity_multiplier = match task.metadata.estimated_complexity {
            Some(ComplexityLevel::Trivial) => 0.5,
            Some(ComplexityLevel::Simple) => 1.0,
            Some(ComplexityLevel::Moderate) => 2.0,
            Some(ComplexityLevel::Complex) => 4.0,
            Some(ComplexityLevel::Epic) => 8.0,
            None => 1.5,
        };

        let duration = task.metadata.estimated_duration.unwrap_or_else(|| {
            match task.metadata.estimated_complexity {
                Some(ref complexity) => complexity.estimated_duration(),
                None => Duration::minutes(30),
            }
        });

        ResourceAllocation {
            max_memory_mb: (base_memory as f64 * complexity_multiplier) as u64,
            max_cpu_percent: (base_cpu * complexity_multiplier).min(100.0),
            max_duration: duration,
            temp_storage_mb: (512.0 * complexity_multiplier) as u64,
            network_bandwidth_mbps: 10.0,
        }
    }
}

impl Default for ExecutorConfig {
    fn default() -> Self {
        Self {
            default_timeout: Duration::hours(1),
            max_retries: 3,
            enable_monitoring: true,
            workspace_root: PathBuf::from("/tmp/claude-agent"),
            temp_dir: PathBuf::from("/tmp"),
        }
    }
}

impl Default for ResourceAllocation {
    fn default() -> Self {
        Self {
            max_memory_mb: 1024,
            max_cpu_percent: 50.0,
            max_duration: Duration::minutes(30),
            temp_storage_mb: 512,
            network_bandwidth_mbps: 10.0,
        }
    }
}

/// Helper function to create a mock executor for testing
pub fn create_mock_executor() -> TaskExecutor {
    TaskExecutor::new(ExecutorConfig::default(), ResourceAllocation::default())
}