# How to Set Up a Continuous Agent Loop with Needle 2's `run()` Method

> Learn how to set up a continuous agent loop with Needle 2's run() method. Automatically call tools up to max_steps for efficient AI task execution.

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

---

**Needle 2's `run()` method implements a lightweight agent loop that automatically calls user-defined tools up to `max_steps` times, returning a final response only when the model produces no more function calls or reaches a terminal state.**

The continuous agent loop in Needle 2 allows large language models to interact with external tools iteratively until a task completes. According to the cactus-compute/needle source code, this behavior is encapsulated entirely within the `Needle.run()` method found in [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) (lines 39-60). By configuring tool definitions and loop parameters, you can create anything from single-query agents to perpetual autonomous systems.

## Understanding the Agent Loop Architecture

The core agent logic in Needle 2 follows a deterministic execution pattern orchestrated by the `run()` method. When you invoke `run(query, max_steps, max_new_tokens)`, the implementation performs the following sequence:

1. **Initial LLM call** — The method invokes `_complete` to generate a response, which may contain a `function_calls` array indicating tool requests.

2. **Iterative execution** — For each step up to `max_steps` (default 8), the agent looks up requested tools in the internal `self._functions` registry, executes them, and encodes results as JSON for the next prompt.

3. **Termination conditions** — The loop stops early when the model returns a non-`call` type or when no tool calls are present in the response.

4. **Result aggregation** — The method returns a dictionary containing the final LLM response text and a `results` field aggregating all tool outputs.

This architecture separates the orchestration logic in [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) from the underlying generation engine in [`needle/model/run.py`](https://github.com/cactus-compute/needle/blob/main/needle/model/run.py) and tool schema handling in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py).

## Step-by-Step Implementation

### Define Tools with the @tool Decorator

Register functions that the agent can invoke using the `@tool` decorator. Each tool becomes available in the `self._functions` registry during loop execution.

```python
from needle import tool, Field

@tool
def search_web(query: str = Field(description="Search query")) -> str:
    """Return a short fake web-search result for demonstration."""
    return f"Found result for '{query}'"

```

### Instantiate the Needle Agent

Pass your tool functions to the `Needle` constructor to populate the agent's internal registry. The system prompt defines the agent's behavior throughout the loop.

```python
from needle import Needle

agent = Needle(
    tools=[search_web],
    system="You are a helpful assistant with access to web search."
)

```

### Execute Single-Step Runs with Automatic Iteration

Invoke `run()` to trigger the automatic multi-step loop. The method handles all iterations internally, up to the `max_steps` limit.

```python
response = agent.run(
    query="What's the weather in Paris today?",
    max_steps=5,          # Maximum tool-calling iterations per query

    max_new_tokens=256    # LLM token budget per generation round

)

print(response["results"])  # List of all tool execution results

print(response["text"])     # Final aggregated answer from the LLM

```

### Build a Continuous Interactive Loop

For conversational applications, wrap `run()` in a `while` loop that processes sequential user inputs. Each call to `run()` maintains the agent's state through the conversation history managed by the `Needle` class.

```python
while True:
    user_input = input("User: ")
    if user_input.lower() in {"exit", "quit"}:
        break
    
    resp = agent.run(user_input)  # Uses default max_steps=8

    print("Assistant:", resp.get("text"))

```

### Create Persistent Autonomous Loops

For self-driving agents that continue processing without human input, feed previous results back as the next query. This creates a perpetual reasoning chain until you implement a custom termination condition.

```python
state = ""
while True:
    # Combine prior results with continuation prompt

    query = f"{state}\nContinue processing."
    resp = agent.run(query, max_steps=8)
    
    state = resp.get("text", "")
    print(state)
    
    # Custom break condition

    if "DONE" in state:
        break

```

## Key Configuration Parameters

The `run()` method accepts several parameters that control the continuous loop behavior:

- **`max_steps`** (int, default 8) — Hard limit on the number of tool-calling iterations. Prevents infinite loops when the model repeatedly requests tools.

- **`max_new_tokens`** (int) — Token generation budget for each individual LLM call within the loop. The native engine in [`needle/model/run.py`](https://github.com/cactus-compute/needle/blob/main/needle/model/run.py) enforces this limit during the `_complete` invocation.

- **`query`** (str) — The initial user prompt that seeds the agent loop.

Because the loop is entirely contained within `run()`, you control continuity by managing how often you invoke the method and how you structure the input queries.

## Summary

- **Needle 2's agent loop** is implemented in [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) (lines 39-60) within the `run()` method, which automatically iterates up to `max_steps` times.
- **Tool registration** occurs via the `@tool` decorator or manual function passing, populating the `self._functions` registry used during execution.
- **Continuous operation** requires wrapping `run()` in external loops—either conversational `while True` blocks for interactive use or persistent feedback loops for autonomous agents.
- **Termination** happens automatically when no function calls remain or when `max_steps` is reached, returning aggregated results and final text.
- **Underlying generation** is handled by the native engine in [`needle/model/run.py`](https://github.com/cactus-compute/needle/blob/main/needle/model/run.py), while tool schemas are managed in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py).

## Frequently Asked Questions

### What happens if the agent exceeds max_steps?

When the iteration count reaches the `max_steps` parameter (default 8), the `run()` method terminates the loop immediately and returns the current state, even if additional tool calls remain pending. The returned dictionary includes all results collected up to that point, allowing you to inspect partial progress or re-invoke `run()` with the remaining work.

### How does Needle 2 handle tool execution errors?

According to the implementation in [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py), the agent catches exceptions during tool execution and encodes error objects into the JSON results sent back to the model. This allows the LLM to receive feedback about failed operations and potentially request corrective actions in subsequent iterations within the same `run()` call.

### Can I use custom Pydantic models with Needle tools?

Yes, the `@tool` decorator in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) supports Pydantic models for complex argument schemas. You can define fields using `Field` objects with descriptions, and the schema builder automatically converts these to JSON Schema format for the LLM's function-calling interface.

### Where does the actual LLM generation occur?

The actual token generation and buffer management occur in [`needle/model/run.py`](https://github.com/cactus-compute/needle/blob/main/needle/model/run.py), which provides the `generate` and `batch_generate` functions called by the `Needle._complete` method. This separation allows the `run()` loop to focus on orchestration while the model file handles low-level inference.