escher-execution-engine 0.1.2

Production-ready async execution engine for system commands
Documentation
# How to Use the Execution Engine

This guide shows you how to consume and use the execution engine library to run Python scripts and other commands.

## Quick Start

### 1. Run the Example

```bash
# Run the Python execution demo
cargo run --example python_execution

# Run the general standalone example
cargo run --example standalone
```

### 2. Add as a Dependency

If you're creating a new Rust project that uses this execution engine:

**In your `Cargo.toml`:**
```toml
[dependencies]
execution-engine = { path = "../v2-execution-engine-rust" }
tokio = { version = "1", features = ["full"] }
uuid = { version = "1", features = ["v4"] }
```

## Python Script Execution - 3 Methods

### Method 1: Command::Script (Recommended)

Best for executing script files with an interpreter.

```rust
use execution_engine::*;
use std::sync::Arc;
use std::collections::HashMap;

#[tokio::main]
async fn main() {
    // Create the engine
    let config = ExecutionConfig::default();
    let engine = Arc::new(ExecutionEngine::new(config).unwrap());

    // Create execution request
    let request = ExecutionRequest {
        id: Uuid::new_v4(),
        command: Command::Script {
            path: PathBuf::from("path/to/script.py"),
            interpreter: Some("python3".to_string()),
        },
        env: {
            let mut env = HashMap::new();
            env.insert("PYTHONPATH".to_string(), "/custom/path".to_string());
            env.insert("MY_VAR".to_string(), "my_value".to_string());
            env
        },
        working_dir: Some(PathBuf::from("/working/directory")),
        timeout_ms: Some(30000),  // 30 seconds
        output_log_path: None,
        metadata: ExecutionMetadata::default(),
    };

    // Execute and get execution ID
    let execution_id = engine.execute(request).await.unwrap();

    // Wait for completion and get result
    let result = engine.wait_for_completion(execution_id).await.unwrap();

    // Check result
    println!("Success: {}", result.success);
    println!("Exit code: {}", result.exit_code);
    println!("Output:\n{}", result.stdout);

    if !result.stderr.is_empty() {
        println!("Errors:\n{}", result.stderr);
    }
}
```

### Method 2: Command::Exec

Best for direct program execution with explicit arguments.

```rust
let request = ExecutionRequest {
    id: Uuid::new_v4(),
    command: Command::Exec {
        program: "python3".to_string(),
        args: vec![
            "script.py".to_string(),
            "--arg1".to_string(),
            "value1".to_string(),
            "--arg2".to_string(),
            "value2".to_string(),
        ],
    },
    env: HashMap::new(),
    working_dir: None,
    timeout_ms: Some(10000),
    output_log_path: None,
    metadata: ExecutionMetadata::default(),
};

let execution_id = engine.execute(request).await.unwrap();
let result = engine.wait_for_completion(execution_id).await.unwrap();
```

### Method 3: Command::Shell

Best for simple commands or when you need shell features (pipes, redirects, etc.).

```rust
let request = ExecutionRequest {
    id: Uuid::new_v4(),
    command: Command::Shell {
        command: "python3 script.py --verbose".to_string(),
        shell: "/bin/bash".to_string(),
    },
    env: HashMap::new(),
    working_dir: None,
    timeout_ms: Some(10000),
    output_log_path: None,
    metadata: ExecutionMetadata::default(),
};

let execution_id = engine.execute(request).await.unwrap();
let result = engine.wait_for_completion(execution_id).await.unwrap();
```

## Common Use Cases

### 1. Run Python Script with Virtual Environment

```rust
let request = ExecutionRequest {
    id: Uuid::new_v4(),
    command: Command::Shell {
        command: "source venv/bin/activate && python script.py".to_string(),
        shell: "/bin/bash".to_string(),
    },
    // ... rest of config
};
```

### 2. Run Python with pip packages

```rust
let request = ExecutionRequest {
    id: Uuid::new_v4(),
    command: Command::Exec {
        program: "python3".to_string(),
        args: vec!["-m".to_string(), "pip".to_string(), "install".to_string(), "requests".to_string()],
    },
    // ... rest of config
};
```

### 3. Check Execution Status Without Blocking

```rust
// Execute command
let execution_id = engine.execute(request).await.unwrap();

// Check status without waiting
tokio::time::sleep(Duration::from_secs(1)).await;
let status = engine.get_status(execution_id).await.unwrap();
println!("Current status: {:?}", status);

// Get result if completed (returns None if still running)
if let Some(result) = engine.get_result(execution_id).await.unwrap() {
    println!("Completed with exit code: {}", result.exit_code);
}
```

### 4. Cancel a Running Execution

```rust
let execution_id = engine.execute(request).await.unwrap();

// Later, cancel it
tokio::time::sleep(Duration::from_secs(2)).await;
engine.cancel(execution_id).await.unwrap();

let result = engine.wait_for_completion(execution_id).await.unwrap();
assert_eq!(result.status, ExecutionStatus::Cancelled);
```

### 5. Real-time Output Streaming with Event Handler

```rust
use async_trait::async_trait;

// Create custom event handler
struct MyEventHandler;

#[async_trait]
impl EventHandler for MyEventHandler {
    async fn handle_event(&self, event: ExecutionEvent) {
        match event {
            ExecutionEvent::Started { execution_id, .. } => {
                println!("[{}] Started", execution_id);
            }
            ExecutionEvent::Stdout { execution_id, line } => {
                println!("[{}] STDOUT: {}", execution_id, line);
            }
            ExecutionEvent::Stderr { execution_id, line } => {
                println!("[{}] STDERR: {}", execution_id, line);
            }
            ExecutionEvent::Completed { execution_id, success, .. } => {
                println!("[{}] Completed. Success: {}", execution_id, success);
            }
            _ => {}
        }
    }
}

// Create engine with event handler
let handler = Arc::new(MyEventHandler);
let engine = ExecutionEngine::new(config)
    .unwrap()
    .with_event_handler(handler);
```

## Configuration Options

### ExecutionConfig

```rust
let config = ExecutionConfig {
    // Default timeout if not specified in request (5 minutes)
    default_timeout_ms: 300_000,

    // Maximum allowed timeout (1 hour)
    max_timeout_ms: 3_600_000,

    // Enable line-by-line streaming
    stream_output: true,

    // Optional log directory
    log_dir: Some(PathBuf::from("/var/log/executions")),

    // Max concurrent executions (semaphore limit)
    max_concurrent_executions: 100,

    // Memory cleanup threshold
    max_in_memory_executions: 1000,

    // How long to keep completed executions in memory
    execution_retention_secs: 3600,  // 1 hour

    // Enable automatic cleanup task
    enable_auto_cleanup: true,

    // Max output size (10 MB)
    max_output_size_bytes: 10 * 1024 * 1024,

    // What to do when output is too large
    oversized_output_strategy: OversizedOutputStrategy::TruncateWithWarning,
};
```

### Output Size Strategies

When output exceeds `max_output_size_bytes`:

1. **TruncateWithWarning**: Keep first N bytes, append warning
2. **FailExecution**: Fail with `ExecutionError::OutputSizeExceeded`
3. **StreamToFile**: Stream overflow to temp file (path in `stdout_overflow_file`/`stderr_overflow_file`)

## Error Handling

```rust
match engine.execute(request).await {
    Ok(execution_id) => {
        // Execution started successfully
        match engine.wait_for_completion(execution_id).await {
            Ok(result) => {
                if result.success {
                    println!("Command succeeded");
                } else {
                    println!("Command failed with exit code: {}", result.exit_code);
                    if let Some(error) = result.error {
                        println!("Error: {}", error);
                    }
                }
            }
            Err(e) => println!("Execution error: {}", e),
        }
    }
    Err(ExecutionError::ConcurrencyLimitReached(limit)) => {
        println!("Too many concurrent executions (limit: {})", limit);
        // Queue for later or return error to user
    }
    Err(e) => println!("Failed to start execution: {}", e),
}
```

## Integration with Tauri Desktop App

This library is designed to be consumed by a Tauri application:

```rust
// In src-tauri/src/main.rs
use execution_engine::{ExecutionEngine, ExecutionConfig};
use std::sync::Arc;

#[tokio::main]
async fn main() {
    let config = ExecutionConfig::default();
    let engine = Arc::new(ExecutionEngine::new(config).unwrap());

    // Start cleanup task
    engine.clone().start_cleanup_task();

    tauri::Builder::default()
        .manage(engine)  // Share engine across all Tauri commands
        .invoke_handler(tauri::generate_handler![
            execute_python_script,
            get_execution_status,
        ])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

#[tauri::command]
async fn execute_python_script(
    script_path: String,
    engine: tauri::State<'_, Arc<ExecutionEngine>>,
) -> Result<String, String> {
    let request = ExecutionRequest {
        id: Uuid::new_v4(),
        command: Command::Script {
            path: PathBuf::from(script_path),
            interpreter: Some("python3".to_string()),
        },
        env: HashMap::new(),
        working_dir: None,
        timeout_ms: Some(60000),
        output_log_path: None,
        metadata: ExecutionMetadata::default(),
    };

    engine.execute(request)
        .await
        .map(|id| id.to_string())
        .map_err(|e| e.to_string())
}
```

## Testing

```bash
# Run all tests
cargo test

# Run specific test
cargo test test_executor_timeout

# Run with output visible
cargo test -- --nocapture
```

## Files Created for This Demo

1. **test-scripts/demo.py** - Sample Python script
2. **examples/python_execution.rs** - Demonstration of all 3 execution methods
3. **This guide** - HOW-TO-USE.md

## Next Steps

1. Read the [CLAUDE.md]CLAUDE.md for detailed architecture documentation
2. Explore [src/lib.rs]src/lib.rs for API documentation
3. Check [docs/]docs/ folder for comprehensive guides
4. Run the examples to see it in action

## Common Pitfalls

1. **Always handle `ConcurrencyLimitReached`** - Don't unwrap execute() calls
2. **Use `wait_for_completion()`** - `get_result()` returns None if not complete
3. **Timeout is not an error** - Check `result.status` for `ExecutionStatus::Timeout`
4. **Enable cleanup task** - Call `engine.start_cleanup_task()` to prevent memory leaks

Happy executing!