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

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, 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, 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, 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 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:

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:

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 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, 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.

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.

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 →