# Needle 2 `run()` vs `complete()`: Key Differences for Agentic and Single-Shot Generation

> Discover the key differences between Needle 2 run() and complete() methods. Understand when to use run() for agentic multi-step generation and complete() for single raw requests. Optimize your agent workflow.

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

---

**The `run()` method executes a full conversational turn with automatic tool calling and multi-step orchestration, while `complete()` sends a single raw request to the Needle engine and returns the unprocessed response.**

The cactus-compute/needle repository provides a Python client for interacting with the Needle inference engine. Understanding the difference between Needle 2's `run()` and `complete()` methods is essential for building applications that either leverage built-in agentic capabilities or require low-level control over model generation.

## Execution Model: Agentic Loop vs Single Request

The fundamental distinction lies in how each method handles the interaction lifecycle and tool execution.

### Multi-Step Orchestration with `run()`

The `run()` method implements a **conversational agent loop** that can span multiple model calls. According to the source code in [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py), this method starts by calling `_track("run", …)` to initialize telemetry, then enters a deterministic loop that executes for up to `max_steps` iterations (defaulting to 8).

During each iteration, `run()` calls the internal `self._complete` method to fetch a model response. If the response contains `function_calls`, the method:

- Looks up the corresponding Python implementations in `self._functions`
- Executes the functions locally
- Serializes the results to JSON
- Feeds the results back into the model via another `self._complete` call

This cycle repeats until the conversation ends naturally or the step limit is reached. After the loop terminates, `run()` aggregates all function results into a `"results"` field appended to the final response dictionary.

### Single-Shot Generation with `complete()`

The `complete()` method provides **direct access to the underlying inference engine** without orchestration logic. As implemented in [`needle/_worker.py`](https://github.com/cactus-compute/needle/blob/main/needle/_worker.py), this method constructs a request dictionary with `operation: "complete"`, forwards it to the worker process via `self._worker.complete`, and returns the raw `"response"` field from the worker's reply.

No post-processing, function execution, or looping occurs. The method returns exactly what the Needle engine produced for the single request, making it suitable for straightforward text generation tasks where you manage side effects manually.

## Parameter Differences and Method Signatures

Each method exposes different parameters reflecting their distinct execution models.

**`run()` accepts:**
- `query`: The initial user prompt for the conversational turn
- `max_steps`: Integer controlling maximum tool-call cycles (default 8)
- `max_new_tokens`: Generation limit applied to each step
- `strict`: Boolean flag to reject ungrounded arguments
- Audio payload options for multimodal inputs

**`complete()` accepts:**
- `text`: The raw input string for single completion
- `max_new_tokens`: Generation limit for the single request
- Audio options for payload preparation

Notably, `run()` manages conversation state internally across multiple calls, while `complete()` treats each invocation as stateless.

## Source Code Implementation Details

The implementation paths diverge significantly at the architectural level.

In [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py), the `run()` method prepares audio payloads and enters a `for` loop that repeatedly invokes internal completion logic. The method parses `function_calls` from each response, dynamically executes Python callables registered in `self._functions`, and handles JSON serialization of results back to the model context.

Conversely, `complete()` in [`needle/_worker.py`](https://github.com/cactus-compute/needle/blob/main/needle/_worker.py) serves as a thin wrapper over the IPC layer. It builds a minimal request payload and immediately dispatches to `self._worker.complete`, returning only the `"response"` field without aggregation or modification.

## Return Value Structures

**`run()` returns:**
A dictionary containing the final model response plus an additional `"results"` key. This key holds a list of all function call results executed during the conversational turn, enabling applications to audit which tools were invoked and what they returned.

**`complete()` returns:**
A dictionary or raw string matching exactly what the engine produced. No `"results"` aggregation is added, and no post-processing transforms the output.

## Practical Usage Examples

Choose `run()` when building conversational agents requiring automatic tool use:

```python
from needle import Needle

needle = Needle()
response = needle.run(
    query="What's the weather in Paris today?",
    max_steps=5,
    strict=True
)

print(response["type"])      # "answer"

print(response["results"])   # List of weather API call results

```

Choose `complete()` for low-level generation or custom orchestration:

```python
from needle import Needle

needle = Needle()
raw = needle.complete(
    text="Explain quantum tunneling in two sentences.",
    max_new_tokens=60
)

print(raw["type"])    # "answer"

print(raw["text"])    # Raw model output without processing

```

## When to Use Each Method

**Prefer `run()` when:**
- Building conversational agents that need to call tools (e.g., web search, database lookups)
- Requiring automatic aggregation of function results across multiple model turns
- Leveraging the `strict` parameter to validate grounded arguments
- Managing audio payloads within an agentic context

**Prefer `complete()` when:**
- Executing quick tests or benchmarks against the raw engine
- Manually handling tool execution and conversation state
- Implementing custom orchestration logic outside the built-in loop
- Needing minimal latency without the overhead of multi-step parsing

## Summary

- **`run()`** implements a high-level agentic loop in [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) that automatically executes tool calls, manages conversation state across up to `max_steps` iterations, and returns aggregated results in a `"results"` field.

- **`complete()`** provides low-level access to the Needle engine through [`needle/_worker.py`](https://github.com/cactus-compute/needle/blob/main/needle/_worker.py), submitting single requests without post-processing, looping, or function execution.

- **Key distinction**: `run()` manages multi-turn tool-enabled conversations automatically, while `complete()` offers raw, single-shot generation for custom implementations.

## Frequently Asked Questions

### Can I use `complete()` to manually implement the tool-calling logic that `run()` provides?

Yes. The `complete()` method returns raw model responses including any `function_calls` fields, allowing you to parse tool requests, execute functions manually, and feed results back into subsequent `complete()` calls. This approach requires you to manage the conversation loop and result serialization that `run()` handles automatically according to the logic in [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py).

### Does `run()` always execute exactly `max_steps` iterations?

No. The method enters a `for` loop configured for up to `max_steps` iterations (default 8), but terminates early if the model returns a response without function calls or if the conversation reaches a natural conclusion. The default value serves as a safety limit rather than a guaranteed execution count.

### Which method should I use for production conversational agents?

Use `run()` for production agents requiring robust tool orchestration, as it provides built-in telemetry via `_track()`, automatic function dispatch from `self._functions`, and proper state management across multiple turns. Use `complete()` only when you need to bypass the built-in agentic logic for specialized workflows or when implementing custom orchestration that the standard loop does not support.

### Are there performance differences between the two methods?

Yes. `complete()` incurs lower latency for single requests because it bypasses the preparation logic, loop overhead, and JSON serialization of function results found in `run()`. However, `run()` optimizes multi-turn tool workflows by managing the conversation state internally, potentially reducing overall latency for complex agentic tasks compared to manual implementation with multiple `complete()` calls.