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

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, 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.

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

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

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 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, 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →