# How to Configure Multi‑Step Reasoning with `max_steps` in Needle

> Master multi-step reasoning in Needle by configuring the max_steps parameter. Control agent iteration depth for complex tool chains or disable looping to optimize performance.

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

---

**Set the `max_steps` parameter in `agent.run()` to control how many reasoning iterations Needle performs—default is 8, increase for complex tool chains, decrease to limit depth, or use 0 to disable looping entirely.**

Needle's agentic execution loop lets language models call tools, observe results, and reason across multiple turns. The `max_steps` argument in the `run()` method determines precisely how many of these iterations are allowed before the loop terminates. This article explains how to configure multi-step reasoning with `max_steps` in Needle, with practical examples from the source code.

## Understanding the Agentic Loop in Needle

The `run()` method in [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) implements Needle's core reasoning cycle. Each iteration allows the model to emit tool calls, executes the corresponding Python functions, and feeds results back for subsequent reasoning.

```python
def run(self, query: str, max_steps: int = 8, max_new_tokens: int = 256) -> dict:
    # ...implementation in needle/__init__.py

```

The loop continues until one of three conditions is met:

- The `max_steps` limit is reached
- The model returns a response type other than `"call"`
- No function calls are present in the model's output

All intermediate tool results accumulate in the final response under the `"results"` key.

## `max_steps` Configuration Options

### Default Behavior: 8 Steps

If you omit `max_steps`, Needle defaults to **8** reasoning steps. This suffices for most single-tool or shallow multi-tool tasks.

```python
from needle import Needle, tool

@tool
def get_weather(city: str):
    """Get the weather for a city."""
    return {"temp_c": 20}

agent = Needle(tools=[get_weather])
response = agent.run("What's the weather in Paris?")
print(response["type"])        # → "respond" (final answer)

print(response["results"])     # → [{'temp_c': 20}]

```

### Extended Reasoning: Higher Values

Increase `max_steps` when tasks require deeper tool chaining or sequential dependencies.

```python
@tool
def get_location():
    """Return a location that the next tool can use."""
    return {"city": "Paris"}

@tool
def get_weather(city: str):
    """Get the weather for a city."""
    return {"temp_c": 20}

agent = Needle(tools=[get_location, get_weather])

# Allow up to 15 reasoning steps for multi-hop queries

response = agent.run(
    "Tell me the current temperature where I am.",
    max_steps=15,
    max_new_tokens=256,
)
print(response["results"])

# → [{'city': 'Paris'}, {'temp_c': 20}]

```

### Limited or Disabled Looping

Use smaller values to constrain reasoning depth. Set `max_steps=0` to disable the loop entirely—only the initial completion is returned, with no tool execution.

## Manual Loop Control with `complete()`

For fine-grained control over multi-step reasoning, implement the loop manually using `complete()`. This replicates `run()`'s behavior while letting you decide termination conditions.

```python
import json
from needle import Needle, tool

@tool
def get_weather(city: str):
    """Get the weather for a city."""
    return {"temp_c": 20}

agent = Needle(tools=[get_weather])

# First turn: model emits tool call

turn = agent.complete("Weather in Tokyo?")
if turn["type"] == "call":
    # Execute the suggested call manually

    args = turn["function_calls"][0]["arguments"]
    result = get_weather(**args)

    # Feed result back and continue (you control when to stop)

    turn = agent.complete(json.dumps(result))
    print(turn)   # Final response after custom iteration

```

This approach is useful when you need custom retry logic, conditional branching, or integration with external state management.

## Key Source Files

| File | Purpose |
|------|---------|
| [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) | Implements `Needle.run()` with `max_steps` parameter |
| [`doc/apis.md`](https://github.com/cactus-compute/needle/blob/main/doc/apis.md) | Documents `agent.run(query, max_steps=…, max_new_tokens=…)` |
| [`tests/test_inference.py`](https://github.com/cactus-compute/needle/blob/main/tests/test_inference.py) | Validates default `run()` behavior |

## Summary

- **`max_steps`** controls iteration count in `Needle.run()`—default **8**, configurable per query
- **Increase** for complex multi-hop reasoning; **decrease** to limit depth; **zero** to disable looping
- Loop terminates early on non-`"call"` responses or empty function calls
- Use **`complete()`** for manual loop implementation with custom control logic
- Results from all steps collect in `response["results"]`

## Frequently Asked Questions

### What happens if `max_steps` is reached before the model finishes reasoning?

The loop stops immediately when `max_steps` iterations complete. The final response contains whatever results accumulated, with `response["type"]` reflecting the last model output—potentially another `"call"` if the model still wanted to continue. Check `response["results"]` to see executed tool outputs.

### Can I change `max_steps` dynamically between queries on the same agent?

Yes. The `max_steps` parameter is passed per-call to `run()`, not set at agent initialization. Each query can specify a different limit based on expected complexity.

```python
agent = Needle(tools=[my_tools])
simple_response = agent.run("Simple question", max_steps=3)
complex_response = agent.run("Complex research task", max_steps=20)

```

### What's the difference between `max_steps` and `max_new_tokens`?

`max_steps` limits **reasoning iterations** (how many tool-use cycles), while `max_new_tokens` limits **token generation per LLM call** (how long each response can be). They operate independently—deep reasoning with `max_steps=50` remains efficient if each step uses few tokens.

### Does `max_steps=0` prevent all tool execution?

Correct. With `max_steps=0`, `run()` skips the agentic loop entirely. The model receives the query and returns its direct response without checking for or executing any tool calls. Use this for pure text completion without tool augmentation.