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:
- Prompt generation — constructs context for the LLM based on current state and history
- Response parsing — extracts tool-call JSON payloads from model output
- 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_toolhelper usestry/exceptto intercept all exceptions - Errors become structured context —
tool_errorpayloads witherror_typeanderror_messagefeed back to the LLM - The loop continues — execution proceeds until
max_stepsor explicit termination, enabling recovery attempts - Observability is built-in — Python
loggingcaptures failures atERRORlevel
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →