What Happens When a Tool Call Fails During Needle's agent.run() Loop: Error Handling Explained

When a tool call fails during agent.run(), Needle catches the exception, converts it into a structured tool_error payload, injects it back into the LLM context, and continues the loop—allowing the model to recover instead of crashing the agent.

The needle framework implements resilient autonomous agents through its Agent.run() method. Understanding how this library handles tool execution failures is critical for building reliable LLM-powered applications that can gracefully degrade and self-correct when external tools malfunction.

How Needle's Agent Loop Processes Tool Calls

The agent execution cycle in [needle/__init__.py](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) follows a predictable pattern:

  1. Prompt generation — constructs context for the LLM based on current state and history
  2. Response parsing — extracts tool-call JSON payloads from model output
  3. Tool invocation — executes the requested function via [needle/agent/tools.py](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py)

The critical resilience mechanism lives in the _run_tool helper method, which wraps every tool execution in defensive error handling.

Tool Failure Handling: Step-by-Step Breakdown

Exception Interception

When a tool raises any exception, the try/except block in _run_tool immediately intercepts it. This prevents the exception from propagating upward and terminating the entire agent session.

Structured Error Payload Generation

The caught exception is converted into a standardized tool_error object containing:

  • error_type — the exception class name (e.g., ValueError, HTTPError)
  • error_message — the string representation of the exception
  • Optional traceback for debugging purposes

Context Injection for LLM Recovery

The error payload is appended to the conversation history as a system message, making the failure visible to the LLM on its next reasoning step. This enables the model to:

  • Retry the same tool with corrected arguments
  • Pivot to an alternative tool or approach
  • Terminate gracefully when recovery is impossible

Continuation and Logging

The loop proceeds to the next iteration unless max_steps is exhausted or the LLM explicitly signals completion. Simultaneously, Python's logging module records the failure at ERROR level for observability.

Code Implementation: The _run_tool Helper

Here's how the error handling is implemented in the Needle source:


# Simplified excerpt from needle/agent/__init__.py

def _run_tool(self, tool_name: str, args: dict) -> dict:
    try:
        tool_fn = self.tools[tool_name]
        result = tool_fn(**args)                # <-- actual tool execution

        return {"type": "tool_result", "result": result}
    except Exception as exc:                     # ← comprehensive failure catch

        logger.error("Tool %s failed: %s", tool_name, exc)
        return {
            "type": "tool_error",
            "error_type": type(exc).__name__,
            "error_message": str(exc),
        }

def run(self, query: str, max_steps: int = 8, max_new_tokens: int = 256) -> dict:
    for step in range(max_steps):
        # ... generate LLM prompt, parse response ...

        if payload_is_tool_call:
            tool_out = self._run_tool(payload.name, payload.arguments)
            # inject tool_out into conversation history for next LLM turn

            self.history.append({"role": "system", "content": tool_out})
        # ... handle direct LLM responses, check termination ...

The tool_error structure returned to the LLM looks like this:

{
  "type": "tool_error",
  "error_type": "ValueError",
  "error_message": "Invalid parameter 'x': expected int, got str"
}

Key Design Principles in Needle's Error Handling

Principle Implementation
Fail-safe execution Exceptions never escape _run_tool; the agent survives individual tool crashes
Observable failures Structured error types and logging enable debugging and monitoring
LLM-driven recovery Errors become part of the reasoning context, allowing model-level adaptation
Configurable limits max_steps prevents infinite loops when recovery fails

Source File Reference Map

File Responsibility
[needle/__init__.py](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) Agent class and run() loop orchestration
[needle/agent/tools.py](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) @tool decorator and schema generation
[needle/model/run.py](https://github.com/cactus-compute/needle/blob/main/needle/model/run.py) Low-level LLM inference and output processing
tests/test_fetch.py, tests/test_tools.py Validation of error handling behavior

Summary

  • Tool failures are caught, not crashed — the _run_tool helper uses try/except to intercept all exceptions
  • Errors become structured contexttool_error payloads with error_type and error_message feed back to the LLM
  • The loop continues — execution proceeds until max_steps or explicit termination, enabling recovery attempts
  • Observability is built-in — Python logging captures failures at ERROR level

Frequently Asked Questions

Does a tool failure stop the entire agent.run() execution?

No. According to the needle source code, the try/except block in _run_tool prevents exceptions from propagating. The agent converts the failure into a tool_error message, appends it to the conversation history, and continues to the next iteration. Only max_steps exhaustion or LLM-initiated termination ends the loop.

What information does the LLM receive when a tool fails?

The LLM receives a JSON object with "type": "tool_error", plus error_type (the exception class name) and error_message (the exception string). This structured format allows the model to understand what went wrong and decide whether to retry, pivot tools, or abort.

Can I customize how tool errors are handled in Needle?

The current implementation in needle/agent/__init__.py hardcodes the tool_error structure in _run_tool. To customize behavior, you would subclass Agent and override _run_tool, or modify the error payload generation before the context injection step in run().

Where are tool functions defined and registered in Needle?

Tool functions are decorated with @tool from [needle/agent/tools.py](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py), which attaches JSON schemas for LLM consumption. The Agent stores these in self.tools as a mapping from tool names to decorated functions, invoked dynamically during the run loop.

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 →