# How to Implement Error Handling and Recovery in AI Agent Tool Execution

> Learn to implement error handling and recovery in AI Agent tool execution. Validate code, sandbox execution, detect errors, and enable retry logic or human intervention for robust performance.

- Repository: [Bojie Li/ai-agent-book](https://github.com/bojieli/ai-agent-book)
- Tags: how-to-guide
- Published: 2026-07-22

---

**Implement robust error handling and recovery in AI Agent tool execution by validating code before registration, isolating execution in sandboxed subprocesses, detecting dangerous operations, and returning uniform error schemas that enable retry logic and human-in-the-loop recovery.**

The `bojieli/ai-agent-book` repository demonstrates a production-ready architecture for resilient AI tool execution. This guide examines the three-pillar strategy used throughout the codebase—pre-registration validation, sandboxed execution, and high-level execution APIs—to show how you can prevent crashes, isolate failures, and implement automatic recovery mechanisms.

## Pre-Registration Validation

Before any tool enters the library, the system validates both syntax and runtime behavior. In [`chapter8/self-evolving-tools/tool_manager.py`](https://github.com/bojieli/ai-agent-book/blob/main/chapter8/self-evolving-tools/tool_manager.py), the `ToolLibrary.create_tool` method enforces these checks at lines 68-71 and 82-90.

### Compile-Time Syntax Checks

The `create_tool` method immediately compiles the proposed code string using Python's built-in `compile` function. This catches syntax errors before the tool is persisted, preventing broken utilities from ever reaching the execution stage.

```python
from chapter8.self_evolving_tools.tool_manager import ToolLibrary

library = ToolLibrary()
code = """
def run(a: int, b: int) -> dict:
    return {"sum": a + b}
"""
result = library.create_tool(
    name="adder",
    description="Adds two integers.",
    parameters={"type": "object", "properties": {"a": {"type": "integer"},
                                                "b": {"type": "integer"}},
                "required": ["a", "b"]},
    code=code,
    test_args={"a": 1, "b": 2}
)

```

### Sandboxed Test Invocation

When `test_args` are provided, `create_tool` invokes the private `_run_record` method (lines 82-90) to execute the tool once in an isolated subprocess. If this test run returns a non-JSON result, crashes, or exceeds the timeout, registration aborts immediately. This ensures that only functional, well-formed tools are available to downstream agents.

## Sandboxed Execution Architecture

Runtime isolation prevents tool crashes from destabilizing the host agent. The `_run_record` method in [`chapter8/self-evolving-tools/tool_manager.py`](https://github.com/bojieli/ai-agent-book/blob/main/chapter8/self-evolving-tools/tool_manager.py) implements this sandbox at lines 71-89 and 176-188.

### Subprocess Isolation with Controlled Environment

The method writes tool code to a temporary script and launches it via `subprocess.run` with a restricted `PYTHONPATH`. Execution occurs in a separate process, so infinite loops or segmentation faults terminate the child without affecting the parent agent.

```python
record = library.get_tool("adder")
exec_result = library.execute_tool("adder", {"a": 5, "b": 7})

if not exec_result["success"]:
    # Handle crash, timeout, or missing result marker

    print("Tool failed:", exec_result["error"])
else:
    print("Tool output:", exec_result["result"])

```

### Timeout and Crash Handling

Lines 176-188 handle `subprocess.TimeoutExpired` and other execution failures. When a timeout occurs or the child process returns a non-zero exit code, `_run_record` returns a structured dictionary with `"success": False` and descriptive error fields. This uniform contract allows orchestration logic to detect failures programmatically without parsing stack traces.

## High-Level Execution Tools

For ad-hoc code snippets and shell commands, [`chapter4/execution-tools/execution_tools.py`](https://github.com/bojieli/ai-agent-book/blob/main/chapter4/execution-tools/execution_tools.py) provides the `ExecutionTools` class. These utilities add layers of safety including syntax verification, dangerous-operation vetting, and result truncation.

### Syntax Verification and Dangerous Pattern Detection

The `code_interpreter` method (lines 96-126) first validates Python syntax using `LLMHelper.verify_code_syntax`, then scans for hazardous patterns like `os.system`, `subprocess`, or `eval`. If dangerous operations are detected, the system requests human approval via `LLMHelper.request_approval` before proceeding.

```python
from chapter4.execution_tools.execution_tools import ExecutionTools
from chapter4.execution_tools.llm_helper import LLMHelper

tools = ExecutionTools(LLMHelper())
out = await tools.code_interpreter(
    code="print('hello')",
    language="python"
)

if not out["success"]:
    print("Execution error:", out["error"])
else:
    print("Stdout:", out["stdout"])

```

### Exception Wrapping and Result Truncation

Lines 166-176 implement a generic exception catch that wraps any unexpected error into the standard response format. Long outputs are automatically truncated and persisted to temporary files via `truncate_and_persist`, keeping the LLM context window bounded while preserving full logs for debugging.

### Virtual Terminal Safety

The `virtual_terminal` method applies similar safeguards to shell commands (lines 200-267). It checks for dangerous commands like `rm -rf`, executes with timeout limits, and catches both `TimeoutExpired` and generic exceptions to return consistent error structures.

## Recovery Patterns and Strategies

The uniform error schema—containing `success`, `error`, `stderr`, and `stdout_file` fields—enables sophisticated recovery workflows.

**Retry on transient failures.** Wrap calls to `execute_tool` or `code_interpreter` in a retry loop that reattempts execution when `error` indicates timeouts or temporary network issues.

**Fallback to safe implementations.** Maintain minimal-risk fallback versions of critical tools in the library. When the primary tool crashes, the agent can switch to the fallback automatically.

**Human-in-the-loop escalation.** When `request_approval` returns `approved=False` or when errors persist after retries, surface the error to the user with options to edit the code or select alternative approaches.

**Persisted logs for post-mortem analysis.** Because truncated outputs are saved to temporary files (referenced in `stdout_file`), agents can retrieve complete execution logs via file-reading tools without re-executing the original command.

## Summary

- **Validate early.** Use `ToolLibrary.create_tool` to compile-check syntax and run sandboxed tests before persisting tools (lines 68-90).
- **Isolate execution.** Run all tools in subprocesses via `_run_record` to prevent crashes from affecting the host agent (lines 71-188).
- **Vet dangerous operations.** Leverage `ExecutionTools` to scan for hazardous code patterns and require human approval before execution (lines 96-126).
- **Standardize errors.** Return dictionaries with `success` flags and structured error fields to enable programmatic recovery logic.
- **Implement recovery.** Combine retry loops, fallback tools, human approval workflows, and persisted logs to create resilient agent systems.

## Frequently Asked Questions

### How does sandboxed execution prevent system crashes in AI agents?

The `_run_record` method in [`chapter8/self-evolving-tools/tool_manager.py`](https://github.com/bojieli/ai-agent-book/blob/main/chapter8/self-evolving-tools/tool_manager.py) launches tool code in a separate Python subprocess using `subprocess.run` (lines 71-89). Because the tool runs in an isolated process with its own memory space, segmentation faults, infinite loops, or uncaught exceptions terminate only the child process. The parent agent receives a structured error response via the `__TOOL_RESULT__` marker parsing (lines 176-188) rather than crashing itself.

### What dangerous operations are blocked during tool execution?

The system scans for patterns including `os.system`, `subprocess` calls, `eval`, `exec`, `rm -rf`, and other system-modifying commands. In [`chapter4/execution-tools/execution_tools.py`](https://github.com/bojieli/ai-agent-book/blob/main/chapter4/execution-tools/execution_tools.py), the `code_interpreter` method uses `LLMHelper.verify_code_syntax` (lines 96-106) and dangerous-pattern detection (lines 108-126) to flag risks. Similarly, `virtual_terminal` checks for hazardous shell commands (lines 200-207). When detected, execution pauses for human approval unless explicitly configured to proceed.

### How can I implement automatic retry logic for failed tools?

Because all execution paths return a uniform dictionary with a `success` boolean and `error` string, you can wrap tool calls in a retry loop that inspects these fields. Retry when `error` contains timeout indicators or transient failure messages, up to a configurable maximum. For persistent failures, implement fallback logic that switches to a simpler, pre-validated tool version stored in the library.

### What is the uniform error schema used across the codebase?

Every execution method returns a dictionary containing at minimum a `success` boolean and an `error` string. Additional fields include `stderr` for error streams, `stdout` for standard output, and `stdout_file` referencing persisted logs when output exceeds length limits. This schema appears in `ToolLibrary.execute_tool`, `ExecutionTools.code_interpreter`, and `ExecutionTools.virtual_terminal`, enabling consistent error handling regardless of which execution path fails.