# Understanding the Needle Agent Loop: How agent.run() Executes Function Calls

> Explore the agent loop mechanism in Needle and how agent.run() executes function calls. Understand the repeated invocation of language models, tool execution, and result feedback.

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

---

**The `agent.run()` method implements a closed-loop interaction that repeatedly invokes the language model, detects function calls, executes registered tools, and feeds results back until the model stops requesting tools or reaches the maximum step limit.**

The `cactus-compute/needle` repository provides a lightweight agent framework where the `Needle` class orchestrates autonomous tool use through its `run()` method. Understanding this **agent loop mechanism** is essential for building reliable applications that delegate computation to external functions. The implementation resides primarily in [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) and manage six distinct stages per iteration.

## The Six-Stage Execution Cycle

Each iteration of the **Needle agent loop** follows a strict pipeline from model invocation to result feedback. The outer loop controller resides at lines 84‑96 of [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) and manages the lifecycle through the following stages.

### Stage 1: Model Invocation via `_complete`

The loop begins by delegating to the private `_complete` method at lines 28‑31. This helper transmits the current prompt to the underlying native engine (or fine-tuned worker) and returns a raw JSON envelope containing the model's response.

```python

# Conceptual flow within the loop

response = self._complete(messages, tools=self._functions)

```

### Stage 2: Function Call Detection

The loop inspects the JSON response for a `"function_calls"` field. If the response type is not `"call"` or the field is absent, the **agent loop** terminates immediately. This check occurs in the loop header at lines 84‑92, preventing unnecessary computation when the model requires no external tools.

### Stage 3: Grounding Validation (Optional)

When `strict=True`, the loop validates argument grounding. The `_ungrounded_paths` helper (lines 37‑44) extracts any ungrounded parameters reported by the engine. If violations exist, the loop converts these into error objects before tool execution, preventing hallucinated arguments from reaching your functions.

### Stage 4: Tool Registry Lookup and Execution

For each detected call, the loop performs the following in the inner block at lines 95‑108:

- **Name resolution**: Looks up the tool in the agent's `_functions` registry
- **Error handling**: Returns an error object if the tool is unknown
- **Safe execution**: Invokes the tool with model-provided arguments inside a try/except block
- **Exception capture**: Reports any runtime exceptions as structured errors

```python

# Simplified logic from lines 95-108

for call in calls:
    if call.name not in self._functions:
        result = {"error": f"Unknown tool: {call.name}"}
    else:
        try:
            tool = self._functions[call.name]
            result = tool(**call.arguments)
        except Exception as e:
            result = {"error": str(e)}

```

### Stage 5: Result Feedback

The loop JSON-encodes all tool results (including errors) and passes them back to `_complete` as the next conversation turn. Crucially, this re-entry step at lines 110‑113 sets `ground=False` to prevent the engine from attempting additional grounding on synthetic tool outputs.

### Stage 6: Iteration Control

The entire sequence repeats for up to `max_steps` iterations (default 8). The loop accumulates all intermediate outputs in an `executed` list, which the method finally attaches to the response as `"results"` before returning to the caller.

## Configuration Parameters

Two key parameters control the **Needle agent loop** behavior:

- **`max_steps`**: Hard limit on loop iterations (default 8) defined in the outer `range(max_steps)` controller
- **`strict`**: Boolean flag enabling grounding validation via `_ungrounded_paths`

```python

# Example with explicit configuration

agent = Needle(tools=[add])
response = agent.run(
    query="Calculate 15 + 27",
    max_steps=5,
    strict=True
)

```

## Code Examples

### Basic Tool Execution

This example demonstrates the complete loop with a registered tool:

```python
from needle import Needle, tool

@tool
def add(x: int, y: int) -> int:
    """Return the sum of x and y."""
    return x + y

agent = Needle(tools=[add])

# The agent loop will detect the need for addition and execute the tool

response = agent.run(
    query="What is the total of 12 and 7?",
    max_steps=5,
    strict=True
)

print(response)

# Output includes: {'type': 'result', 'results': [19], ...}

```

### User-Defined Functions Without Decoration

The loop supports plain functions through automatic schema inference:

```python
def fetch_weather(city: str) -> str:
    """Mock weather lookup."""
    return f"The weather in {city} is sunny."

agent = Needle(tools=[fetch_weather])

out = agent.run("Give me the weather for Paris.")
print(out["results"])  

# ["The weather in Paris is sunny."]

```

## Summary

- The **Needle agent loop** in [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) implements a closed-cycle architecture: model → function detection → tool execution → results → model.
- The private `_complete` method (lines 28‑31) handles all LLM communication, while the outer loop controller (lines 84‑96) manages up to 8 iterations by default.
- **Strict mode** validates argument grounding using `_ungrounded_paths` (lines 37‑44) before executing tools.
- Tool dispatch occurs in the inner execution block (lines 95‑108), which handles registry lookups, error capture, and exception safety.
- Results feed back into the conversation with `ground=False` (lines 110‑113) to prevent recursive grounding attempts on synthetic data.

## Frequently Asked Questions

### What is the default maximum number of steps in the Needle agent loop?

The default `max_steps` is 8 iterations, as defined by the `range(max_steps)` controller in [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) (lines 84‑96). You can override this by passing a different integer to the `max_steps` parameter of `agent.run()`.

### How does Needle handle unknown or invalid tool calls?

When the loop encounters a function name not present in the `_functions` registry, it immediately generates an error object rather than raising an exception. This allows the model to receive feedback about the invalid call and potentially correct its approach in subsequent iterations.

### What happens when strict mode is enabled in agent.run()?

When `strict=True`, the loop invokes `_ungrounded_paths` (lines 37‑44) to check for hallucinated arguments before executing any tool. If ungrounded paths are detected, the loop treats these as execution errors, preventing unvalidated parameters from reaching your tool implementations.

### Can the agent loop handle exceptions thrown by tool functions?

Yes. The execution block at lines 95‑108 wraps each tool invocation in a try/except construct. Any exception raised during tool execution is caught and converted into a structured error result, which is then fed back to the model as context for the next iteration.