# What Does `agent.run()` Return in Needle? Understanding Response Structure and Tool Results

> Discover what agent.run() returns in Needle. Get details on the response structure and tool results from multi-step LLM interactions.

- Repository: [Cactus Compute, Inc./needle](https://github.com/cactus-compute/needle)
- Tags: deep-dive
- Published: 2026-09-05

---

**`agent.run()` returns a single dictionary combining the LLM's final response with an ordered history of all tool execution results from the multi-step interaction.**

In the [Needle](https://github.com/cactus-compute/needle) open-source agent framework, `agent.run()` serves as the primary entry point for driving conversations between a language model and registered tools. The method orchestrates the complete loop—sending prompts to the engine, detecting function calls, executing tools, and feeding results back—until the model produces a final answer or hits the step limit.

## The `agent.run()` Return Dictionary Structure

The return value is always a **Python dictionary** with a consistent set of keys. According to the implementation in [[`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py)](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py#L183-L205), the structure contains:

| Key | Type | Description |
|-----|------|-------------|
| `"type"` | `str` | Engine response type: `"call"` (wants more tools) or `"final"` (done). |
| `"function_calls"` | `list` | Function-call objects from the **last** completion request (empty when finished). |
| `"results"` | `list` | **Ordered list** of every tool result executed during the run. |
| Other engine fields | varies | Additional keys like `"text"`, `"confidence"` passed through from the engine. |

The **`"results"`** list is particularly important: it preserves the chronological order of all tool invocations, making it possible to trace exactly what happened during a multi-step interaction.

## How `agent.run()` Builds the Return Value

The method follows a precise execution flow to assemble this response:

1. **Initial completion**: Calls `_complete()` to get the first engine response.

2. **Tool execution loop**: Runs up to `max_steps` times:
   - Breaks immediately if `"type"` is not `"call"` or `"function_calls"` is empty.
   - Looks up each called function in `self._functions` (registered via the `@tool` decorator).
   - Executes callables with engine-provided arguments.
   - Stores successful returns; captures exceptions as `{"error": "..."}`.
   - Accumulates all results in the `executed` list.
   - JSON-encodes accumulated results and feeds them back via `_complete()`.

3. **Final assembly**: Attaches the `executed` list to the last engine response under `"results"` and returns the enriched dictionary.

This design means callers receive **both** the model's final output **and** a complete audit trail of side effects.

## Practical Code Examples

### Basic Single-Tool Execution

```python
from needle import Needle, tool

@tool
def add(a: int, b: int) -> int:
    """Return the sum of two integers."""
    return a + b

agent = Needle(tools=[add])

response = agent.run(
    query="What is 7 plus 5? Use the add tool.",
    max_steps=3,
    max_new_tokens=64
)

print(response)

```

Typical return value:

```json
{
  "type": "final",
  "text": "The answer is 12.",
  "function_calls": [],
  "results": [12]
}

```

### Multi-Step Tool Chain

```python
response = agent.run(
    query="Plan a dinner: first get a recipe, then order ingredients.",
    max_steps=5
)

# Access ordered results

print(response["results"])

```

The `"results"` list preserves execution order, e.g.:

```python
["spaghetti carbonara recipe", {"order_id": "12345"}]

```

## Key Files Supporting `agent.run()` Behavior

| File | Purpose |
|------|---------|
| [`needle/__init__.py#L183-L205`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py#L183-L205) | **Core `Needle.run()` implementation**—orchestrates LLM calls, tool execution, and response assembly. |
| [[`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py)](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) | Tool registration via `@tool` decorator, schema building, and Pydantic integration used during function lookup. |
| [[`needle/_worker.py`](https://github.com/cactus-compute/needle/blob/main/needle/_worker.py)](https://github.com/cactus-compute/needle/blob/main/needle/_worker.py) | Fallback completion implementation when custom fine-tuned weights are supplied. |

## Summary

- **`agent.run()` returns a dictionary**, not a raw string or object.
- The **`"results"`** key contains an **ordered list** of all tool outputs from the multi-step run.
- The **`"function_calls"`** key shows any pending calls from the final engine response (usually empty when `"type": "final"`).
- Tool registration happens via **`needle.agent.tools`**; execution logic lives in **[`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py)**.
- This structure enables **post-hoc analysis** of tool side effects without parsing raw text.

## Frequently Asked Questions

### What happens if a tool throws an exception during `agent.run()`?

Exceptions are captured and stored as `{"error": "..."}` in the `"results"` list. The loop continues with remaining function calls, and the error dictionary appears in its chronological position. The final response still returns normally with all results (successes and failures) included.

### Does `agent.run()` modify the original engine response?

Yes, but only by **adding** the `"results"` key. All original engine fields pass through unchanged. This enrichment happens at line ~204 in [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) where `executed` is attached to the response dictionary before return.

### How can I limit tool execution to prevent infinite loops?

Use the **`max_steps`** parameter (default varies by version). The loop breaks immediately if the engine returns `"type": "final"` or empty `"function_calls"`, so well-behaved models naturally terminate. Setting `max_steps=1` forces single-shot execution without tool feedback.

### Is the `"results"` order guaranteed?

Yes. The implementation appends to `executed` **immediately after each tool call**, maintaining strict chronological order. This matches the sequence of `"function_calls"` across all iterations, enabling precise reconstruction of the interaction timeline.