The ToolExecutor is responsible for taking tool calls from the LLM and managing their execution lifecycle. It handles input validation, parallel execution, and ensures that tools respect system-wide constraints like timeouts and cancellations.

The ToolExecutor

The ToolExecutor acts as the engine for tool execution. It doesn’t hold the tools themselves (that’s the ToolRegistry’s job), but rather provides the logic to run them.

pub struct ToolExecutor {
    registry: Arc<ToolRegistry>,
    interaction_registry: Arc<UserInteractionRegistry>,
    permission_registry: Arc<PermissionRegistry>,
}

Batch Execution

One of the key features of Agent Air is the ability to execute multiple tools in parallel. If an LLM provider (like Anthropic or OpenAI) returns multiple tool calls in a single response, the ToolExecutor can run them concurrently.

The execute_batch Method

The execute_batch method takes a collection of tool calls and spawns them as separate Tokio tasks.

pub async fn execute_batch(
    &self,
    session_id: &str,
    calls: Vec<ToolCall>,
    cancellation_token: CancellationToken,
) -> ToolBatchResult {
    let mut futures = Vec::new();

    for call in calls {
        // Find the tool in the registry
        // Prepare ToolContext
        // Spawn the task
        futures.push(tokio::spawn(async move {
            tool.execute(context, call.input).await
        }));
    }

    // Wait for all tools to complete or be cancelled
    let results = join_all(futures).await;
    
    // Process and return results
}

Cancellation and Timeouts

Tools are expected to be “good citizens” and respect the CancellationToken provided in their ToolContext.

  1. User-Initiated Cancellation: If the user interrupts the agent (e.g., via a keyboard shortcut in the TUI), the CancellationToken is triggered.
  2. Timeouts: The controller can enforce a global timeout for tool execution.
  3. Internal Cancellation: If one tool in a batch fails critically, the system may choose to cancel the remaining tools in that batch.

Implementation in Tools

Tools should check the token during long-running operations:

async fn execute(&self, context: ToolContext, input: Value) -> ToolResult {
    for item in items {
        if context.cancellation_token.is_cancelled() {
            return ToolResult::error("Cancelled by user");
        }
        // ... perform work ...
    }
}

Error Handling

The execution flow distinguishes between two types of errors:

  • Tool-Level Errors: The tool ran but failed to achieve its goal (e.g., “File not found”). This is returned to the LLM as a result.
  • System-Level Errors: The tool could not be found, the input failed schema validation, or the task panicked. These are handled by the ToolExecutor and reported as execution failures.