# How to Handle Tool Call Results in the Needle Agentic Loop: A Complete Guide

> Learn how to handle tool call results within the Needle agentic loop. This guide explains how Needle orchestrates tool execution, feeds results back to the model, and returns outcomes.

- Repository: [Cactus Compute, Inc./needle](https://github.com/cactus-compute/needle)
- Tags: how-to-guide
- Published: 2026-08-23

---

**Needle orchestrates tool execution through an agentic loop where `Needle.run()` automatically detects function calls, executes registered tools, feeds results back to the model, and returns accumulated outcomes under `response["results"]`.**

The **cactus-compute/needle** library provides a lightweight Python framework for building LLM-powered agents. At its core lies an **agentic loop** that bridges model reasoning with external tool execution. Understanding how Needle handles **tool call results** is essential for building reliable, multi-step agent workflows.

## The Needle Agentic Loop Architecture

The inference engine implements a bidirectional feedback cycle between the LLM and registered tools. The `Needle.run()` method in [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) (lines 27‑47) drives this process, managing up to `max_steps` iterations (default 8) until the model completes its reasoning.

### Step-by-Step Execution Flow

1. **Initial completion** — `self.complete()` generates the model's first response to the user query.

2. **Call detection** — If the response type is `"call"` with a `function_calls` array, each call is matched against `self._functions`, the agent's registered tool map.

3. **Tool execution** — For each detected call:
   - **Unknown tools** produce `{"error": "unknown tool: …"}`
   - **Known tools** execute via `fn(**call.get("arguments") or {})`
   - **Runtime exceptions** are caught and converted to `{"error": str(exc)}`

4. **Result collection** — All return values and error objects accumulate in a `results` list.

5. **Feedback iteration** — The `results` list is JSON-encoded and sent to `self.complete()` for the model to reason about outcomes and optionally emit new calls.

6. **Loop termination** — Steps 2‑5 repeat until no more calls are emitted or `max_steps` is reached.

7. **Final response** — The complete `results` list attaches to the response under `"results"` and returns to the caller.

## Registering Tools with the @needle.tool Decorator

Tools are defined using Python functions decorated with `@needle.tool`. The decorator, implemented in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) (lines 64‑67), automatically extracts type hints and docstrings to build JSON schemas stored in `fn._needle_tool`.

```python
import needle

@needle.tool
def get_weather(city: str):
    """Get the current weather for a city."""
    return {"city": city, "temp_c": 27, "sky": "clear"}

agent = needle.Needle(tools=[get_weather])
response = agent.run("What's the weather like in Lagos right now?")

print(response["results"])

# → [{'city': 'Lagos', 'temp_c': 27, 'sky': 'clear'}]

```

The schema builder enables the model to understand available tools without manual configuration. Docstrings become descriptions; type hints become parameter specifications.

## Handling Multiple Tool Calls in Sequence

The agentic loop supports **chained tool execution** where one tool's output feeds into subsequent calls. Needle preserves execution order in `response["results"]`, allowing you to trace the full reasoning chain.

```python
@needle.tool
def add(a: int, b: int):
    """Add two integers."""
    return a + b

@needle.tool
def multiply(x: int, y: int):
    """Multiply two integers."""
    return x * y

agent = needle.Needle(tools=[add, multiply])
response = agent.run("First add 3 and 5, then multiply the result by 2.")

print(response["results"])

# → [8, 16]

```

Here the model first calls `add(3, 5)` → `8`, then `multiply(8, 2)` → `16`. Both results appear in sequence, reflecting the actual execution order.

## Error Handling in Tool Call Results

Needle implements **defensive execution** that prevents individual tool failures from crashing the entire agent loop. Two error categories are captured:

- **Unregistered tools**: Immediate error object with unknown tool name
- **Runtime exceptions**: Full exception message stringified into error object

These error objects appear in `response["results"]` alongside successful outputs, giving the model context to potentially recover or explain failures.

```python
@needle.tool
def flaky_api(query: str):
    """Sometimes fails."""
    raise ConnectionError("API timeout")

agent = needle.Needle(tools=[flaky_api])
response = agent.run("Query the flaky API")

print(response["results"])

# → [{'error': 'API timeout'}]

```

The model receives this error and can choose to retry, use alternative tools, or inform the user.

## Accessing and Processing Final Results

The `response` object from `agent.run()` is a dictionary containing:

| Key | Description |
| - | - |
| `results` | List of all tool return values and error objects in execution order |
| Additional metadata | Model-generated text and loop metadata |

Process results programmatically by iterating over the list:

```python
response = agent.run("Complex multi-tool query")

for idx, result in enumerate(response["results"]):
    if isinstance(result, dict) and "error" in result:
        print(f"Step {idx} failed: {result['error']}")
    else:
        print(f"Step {idx} succeeded: {result}")

```

## Key Implementation Files

- **[`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py)**: Contains the `Needle` class and `run()` method implementing the agentic loop (lines 27‑47)
- **[`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py)**: Provides `@needle.tool` decorator and `build_schema` for automatic schema generation (lines 64‑67)
- **[`needle/agent/fetch.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/fetch.py)**: Handles native engine library downloads (required dependency)

## Summary

- **Needle's agentic loop** in `Needle.run()` automates the full cycle: detect calls → execute tools → collect results → feed back to model
- **Tool registration** uses `@needle.tool` in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) with automatic schema extraction from type hints and docstrings
- **Result accumulation** preserves execution order in `response["results"]`, mixing successful returns with structured error objects
- **Error resilience** catches unknown tools and runtime exceptions, converting both to JSON-serializable error objects without breaking the loop
- **Multi-step reasoning** is supported through iterative feedback: the model can chain tool calls using previous results

## Frequently Asked Questions

### How does Needle decide when to stop the agentic loop?

The loop terminates when the model response type is no longer `"call"` (indicating no more tool invocations) or when `max_steps` iterations complete. The default `max_steps=8` prevents infinite loops with chatty models. You can configure this parameter when initializing `Needle`.

### Can I access intermediate results during the loop, or only at the end?

Results are only available in the final `response["results"]` after `run()` completes. The implementation in [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) accumulates outputs internally without exposing partial states. For streaming or step-wise access, you would need to subclass `Needle` and override `run()`.

### What happens if a tool returns None or an empty result?

`None`, empty strings, empty lists, and other falsy values are valid return values and append to `results` unchanged. Only actual Python exceptions trigger error object conversion. The model receives `null` in JSON encoding for `None` returns and can reason about these as legitimate outcomes.

### How do I debug tool execution when results seem incorrect?

Inspect `response["results"]` to identify which step diverged from expectations. Each element corresponds positionally to a `function_calls` entry in the model's response. Enable verbose logging or add print statements inside tool functions—the decorator preserves original function behavior, so standard debugging techniques apply.