Needle 2 `run()` vs `complete()`: Understanding the Difference Between These Methods

The complete() method performs a single-shot text generation without tool calls, while run() executes a multi-turn agent loop that can invoke registered Python functions and aggregate their results.

When working with the cactus-compute/needle library, understanding the difference between run() and complete() methods in Needle 2 is essential for building effective LLM applications. Both methods serve as high-level entry points for text generation, but they operate at fundamentally different levels of abstraction—one providing raw engine access, the other orchestrating complex tool-using agents.

Core Architectural Differences

The two methods represent distinct interaction patterns with the Needle engine. While both ultimately rely on the same underlying C extension for text generation, they differ significantly in how they handle the response and manage execution flow.

complete() — Single-Shot Text Generation

The complete() method provides direct access to the native Needle engine through a thin Python wrapper. Located in [needle/__init__.py](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) at lines 23–38, this function calls the C-extension needle_complete, decodes the returned buffer, and returns a dictionary containing the generated text.

This method does not perform any tool execution or iterative processing. It sends a single prompt and returns the raw response JSON as-is, optionally adding a confidence field if model weights are present in the output. Use this when you need straightforward text completion without external function integration.

run() — Multi-Turn Agent Execution

In contrast, run() (defined at lines 39–60 in the same file) implements a full agent loop capable of executing Python tools. According to the source code, this method:

  1. Invokes _track() from needle/_telemetry.py for usage metrics
  2. Calls the internal _complete() routine to get the initial response
  3. Enters a processing loop (up to max_steps iterations) that:
    • Extracts function_calls from the engine output
    • Looks up each call in self._functions
    • Executes the corresponding Python implementation
    • Feeds JSON-encoded results back to _complete() for subsequent turns
  4. Attaches a "results" field containing all tool outputs before returning

This architecture makes run() the appropriate choice when building agents that can query APIs, perform calculations, or interact with external systems during generation.

Implementation Details from Source Code

Examining the needle/__init__.py implementation reveals the exact behavioral split. The complete() method (lines 23–38) immediately returns after decoding the C-extension response, making it stateless regarding function execution. It handles the low-level buffer management and JSON parsing but deliberately avoids any interpretation of function_calls fields.

Conversely, run() (lines 39–60) maintains state across multiple turns. It leverages the tool registry defined in needle/agent/tools.py to resolve function names to Python callables. The method implements defensive iteration logic, ensuring that even if the engine requests multiple sequential tool calls, each result is properly serialized and fed back into the context window before the next generation cycle begins.

Practical Code Examples

Basic Completion with complete()

Use this approach for simple text generation tasks where no external tools are required:

from needle import Needle

needle = Needle()
prompt = "Write a short poem about sunrise."

# Single-shot generation

response = needle.complete(prompt)
print(response["text"])

In this example, the method directly returns the model's output without checking for function calls or entering any processing loops.

Tool-Enabled Workflows with run()

When your application requires the model to use external data or calculations, use run() with registered tools:

from needle import Needle

# Define a tool that fetches weather data

def get_weather(city: str) -> dict:
    return {"city": city, "temp_c": 22, "condition": "Sunny"}

needle = Needle()
needle.register_tool("get_weather", get_weather)

query = "What is the weather in Paris right now?"

# Multi-turn execution with tool calls

result = needle.run(query, max_steps=5)
print("Final answer:", result["text"])
print("Tool results:", result["results"])

In this workflow, the engine may return a function_calls entry such as {"name":"get_weather","arguments":{"city":"Paris"}}. Needle automatically invokes get_weather, feeds the JSON result back to the engine, and continues the conversation until no more calls are required or max_steps is reached.

Summary

  • complete() provides single-shot access to the Needle engine via the C-extension needle_complete, returning raw JSON without processing tool calls.
  • run() orchestrates a multi-turn agent loop defined in needle/__init__.py lines 39–60, handling tool registration, execution, and result aggregation.
  • The run() method includes telemetry tracking via _track() and stores all tool outputs in a "results" field absent from complete() responses.
  • Choose complete() for simple generation tasks and run() when building function-calling agents that interact with external Python code.

Frequently Asked Questions

When should I use complete() instead of run()?

Use complete() when you need direct, single-turn text generation without any tool execution. This method is ideal for straightforward completions, summarization tasks, or any scenario where the model does not need to invoke external functions. It offers lower overhead and simpler response handling since it bypasses the agent loop and telemetry tracking found in run().

Can complete() return function_calls in the response?

Yes, complete() may return raw function_calls in the JSON response from the engine, but it will not process or execute them. The method simply decodes the buffer and returns the dictionary as-is. If you use complete(), you must manually inspect the response for function calls and handle the tool execution and follow-up prompts yourself, whereas run() automates this entire workflow.

How does run() handle the max_steps parameter?

The max_steps parameter in run() acts as a safety limit to prevent infinite loops during agent execution. According to the source code in needle/__init__.py, the method enters a loop that continues extracting and executing function_calls until either no more calls remain or the iteration count reaches max_steps. Setting this parameter appropriately prevents runaway execution when models generate recursive or cyclical tool requests.

Is there a performance difference between the two methods?

Yes, complete() is significantly lighter since it performs a single C-extension call and returns immediately. run() incurs additional overhead from the Python-based agent loop, JSON serialization of tool results, and telemetry collection via _track(). However, for tool-enabled workflows, run() is more efficient than manually re-implementing the tool-execution loop, as it handles context management and result aggregation optimally within the Needle framework.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →