Needle complete() vs run(): Controlling the Agent Loop

Use complete() for single-shot text generation without tool execution, and run() to execute the full agent loop with automatic tool calling and state management.

The cactus-compute/needle library provides two distinct entry points for interacting with language models. Understanding when to use complete() versus run() is essential for building effective AI agents that can either generate standalone text or autonomously execute multi-step workflows with tool integration.

How complete() Works: Single-Shot Generation

The complete() method generates a single text completion (or embedding) from the model and returns immediately without interpreting any function_calls that may be present in the response.

According to the source code at needle/__init__.py, the public complete() method (lines 221-226) acts as a thin wrapper around the private _complete() helper. The core logic resides in lines 228-263, where the method invokes the native C library (or a fine-tuned worker defined in _worker.py) and parses the raw JSON result. This implementation returns whatever the underlying engine produces in its JSON envelope, making it ideal for straightforward generation tasks where you do not need the model to invoke external tools.

When to Use complete()

Use this method when you need:

  • Direct access to raw model outputs without automatic tool execution
  • Simple text completions or embedding generation
  • Full manual control over parsing and handling function_calls
  • Minimal latency for single-turn interactions

How run() Works: The Full Agent Loop

The run() method executes the complete agent loop: it produces a completion, resolves any returned tool calls, feeds the results back into the model, and repeats until reaching a terminal response or the max_steps limit.

In needle/__init__.py (lines 883-914), the run() implementation starts by calling _complete() once at line 886. It then iterates up to max_steps (lines 891-914). For each iteration, the method extracts function_calls, dispatches the corresponding Python tools (using utilities from agent/tools.py), gathers their results, and feeds those back into the model via another _complete() call at line 912.

Unlike complete(), the run() method returns after the loop finishes with an added results field containing the outcomes of all invoked tools, enabling stateful, autonomous agent behavior.

Key Implementation Details

  • Line 886: Initial _complete() call to generate the first response
  • Lines 891-914: Iteration logic that handles tool execution loops
  • Line 912: Subsequent _complete() calls that incorporate tool results into the context
  • max_steps parameter: Hard limit on tool-call iterations to prevent infinite loops

Practical Code Examples

Basic Completion Without Tool Execution

import needle

# Initialize with tools available but not auto-executed

needle_client = needle.Needle(tools=[my_tool])
resp = needle_client.complete("Write a haiku about sunrise.")
print(resp)  # → {"type":"text","completion":"..."}

Full Agent Loop with Tool Execution

import needle

# my_tool is a function that fetches current temperature

needle_client = needle.Needle(tools=[my_tool])
resp = needle_client.run(
    "What should I wear today?",
    max_steps=5,           # Maximum tool-call iterations

    max_new_tokens=128
)

print(resp["results"])  # → List of tool call results

print(resp["type"])     # → Final response type (e.g., "text")

Summary

  • complete() provides single-shot generation through the _complete() helper (lines 228-263 of needle/__init__.py), returning raw JSON without tool interpretation.
  • run() implements the full agent loop (lines 883-914 of needle/__init__.py), automatically executing tools and iterating up to max_steps.
  • Use complete() when you need direct model access without autonomous tool calling.
  • Use run() when building agents that must resolve function_calls and maintain state across multiple generation rounds.
  • The max_steps parameter in run() acts as a safety guard against infinite loops during tool execution.

Frequently Asked Questions

Does complete() ignore tools entirely?

No. The complete() method accepts a tools parameter and the model may still generate function_calls in its response. However, complete() returns immediately without executing those calls, leaving the interpretation and execution to your application code. In contrast, run() automatically detects and executes these calls through the agent loop.

What happens if a tool call fails during run() execution?

The run() method captures tool execution results and feeds them back into the model regardless of success or failure. Error information propagates through the results field in the final response, allowing the model to potentially recover or provide alternative responses in subsequent iterations within the max_steps limit.

Can I use complete() to manually implement what run() does?

Yes, but you would need to replicate the loop logic found in lines 891-914 of needle/__init__.py. This involves checking for function_calls in the response, dispatching to your Python tool functions, collecting results, and calling _complete() again with the updated context. The run() method encapsulates this boilerplate for production use.

Is there a performance difference between complete() and run()?

complete() makes exactly one call to the underlying engine, while run() makes at least one call plus additional calls for each tool execution step (up to max_steps). Each iteration in run() invokes _complete() at line 912, incurring additional latency equivalent to a full model generation round-trip per tool interaction.

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 →