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 ofneedle/__init__.py), returning raw JSON without tool interpretation.run()implements the full agent loop (lines 883-914 ofneedle/__init__.py), automatically executing tools and iterating up tomax_steps.- Use
complete()when you need direct model access without autonomous tool calling. - Use
run()when building agents that must resolvefunction_callsand maintain state across multiple generation rounds. - The
max_stepsparameter inrun()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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →