How to Feed Results Back into a Needle Agent Using `complete()`

Call agent.complete() with a JSON payload containing a "results" key holding your tool execution outputs, and the model will continue generating as if it received those results natively.

The Needle SDK from cactus-compute/needle provides both high-level automation and low-level control for tool-calling agents. While agent.run() handles the entire execute-and-feed-back cycle automatically, understanding how to manually feed results back into complete() gives you precise control over multi-turn tool interactions, custom execution logic, and external integrations.


The Two Entry Points: complete() vs run()

Needle exposes two primary methods for model interaction:

Method Purpose
agent.complete(prompt) Sends a raw text prompt or JSON payload and receives a response that may contain function_calls
agent.run(prompt) Automates the full cycle: calls complete(), executes tools, feeds results back, and repeats until done

According to the Needle source code in needle/__init__.py, the run() method (around line 202) constructs the feedback loop that gathers tool results, serializes them to JSON, and passes them to _complete() for the next generation round. For custom workflows, you replicate this pattern manually.


The Manual Feedback Pattern

To feed results back using complete(), follow this sequence:

  1. Initial call — Send your prompt with agent.complete(prompt)
  2. Execute tools — Run each function in the returned function_calls array
  3. Build payload — Create {"results": [...]} with your execution outputs
  4. Feed back — Call agent.complete(json.dumps(payload)) to continue the conversation

The engine recognizes the "results" key and treats the input as tool output rather than a new user prompt.


Code Example: Manual Tool Loop with complete()

This example shows the complete manual flow without using run():

import json
import needle

# 1️⃣  Define a tool using the decorator

@needle.tool
def add(a: int, b: int):
    """Return the sum of two integers."""
    return {"sum": a + b}

# 2️⃣  Initialize the agent with available tools

agent = needle.Needle(tools=[add])

# 3️⃣  First completion — model may request tool execution

first_response = agent.complete("What is 7 + 5?")
print(first_response)

# Contains: {"function_calls": [{"name": "add", "arguments": {"a": 7, "b": 5}}]}

# 4️⃣  Execute the requested function calls yourself

calls = first_response.get("function_calls", [])
results = [add(**call["arguments"]) for call in calls]

# results = [{"sum": 12}]

# 5️⃣  Feed results back to continue the conversation

payload = {"results": results}
final_response = agent.complete(json.dumps(payload))
print(final_response)

# Model now provides: "7 + 5 = 12"

In needle/__init__.py, the complete() method (lines 57‑59) forwards to _complete(), which handles the actual RPC to the native engine. The response parsing occurs at lines 74‑78, where the JSON envelope—including any function_calls—is extracted.


When to Use run() Instead

For standard tool-casting without custom logic, run() eliminates boilerplate:

import needle

@needle.tool
def get_weather(city: str):
    return {"city": city, "temp_c": 22, "sky": "sunny"}

agent = needle.Needle(tools=[get_weather])

# Fully automated loop

result = agent.run("What's the weather in Paris?")
print(result["results"])

# [{'city': 'Paris', 'temp_c': 22, 'sky': 'sunny'}]

The run() implementation in needle/__init__.py (around line 202) performs the same JSON serialization and _complete() call you would write manually, but wrapped in a robust retry and continuation loop.


Feeding Custom or External Results

The manual approach shines when integrating external APIs or post-processing tool outputs:

import json
import needle

def call_third_party_api(query: str):
    # Simulate external service

    return {"recommendation": "Use Needle for local LLM tool use"}

agent = needle.Needle()  # No tools registered — we're providing results externally

# Model asks for information we source elsewhere

initial = agent.complete("What tool should I use for local LLM agents?")

# Inject external knowledge directly

external_result = call_third_party_api("llm tool framework")
continuation = agent.complete(json.dumps({"results": [external_result]}))

print(continuation)

Since complete() accepts any properly formatted JSON string containing a "results" array, you're not limited to tools registered via @needle.tool.


JSON Contract and Internal Flow

The low-level RPC to the native inference engine resides in needle/_worker.py (lines 8‑13), where the complete method serializes requests. The response format expected by _complete() includes:

  • function_calls — array of requested tool invocations
  • results — when sent in a request, triggers continuation mode

As documented in doc/apis.md, maintaining this contract ensures the engine correctly interprets whether you're initiating a new turn or continuing from tool output.


Summary

  • Manual control: Use agent.complete() with {"results": [...]} to feed tool outputs back and continue generation
  • Automation: Use agent.run() when you want Needle to handle the entire execute-and-feed cycle
  • Implementation: The feedback mechanism in needle/__init__.py serializes results and passes them to _complete() (line 202) for native engine processing
  • Flexibility: The "results" payload format supports custom data from any source, not just registered tools

Frequently Asked Questions

What format must the results payload use?

The payload must be a JSON object with a single "results" key containing an array of your tool return values. Example: {"results": [{"sum": 12}]}. The Needle engine recognizes this structure as tool output rather than a new user prompt.

Can I mix manual complete() calls with run() in the same session?

Yes, though carefully. run() maintains internal state for its loop. If you interleave manual complete() calls, you may desynchronize the conversation history. For hybrid approaches, stick to manual complete() throughout or use run() exclusively.

Why would I prefer complete() over the simpler run()?

Use complete() when you need: custom tool execution logic (async, remote APIs), result transformation before feedback, selective tool calling, or visibility into each generation step. The tradeoff is explicit state management versus run()'s convenience.

Does _complete() differ from complete()?

complete() (public, lines 57‑59) adds bookkeeping before calling _complete() (internal), which performs the actual RPC. For most cases, use complete(). Access _complete() only if you're replicating run()'s loop behavior and need to bypass overhead.

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 →