What Does `agent.run()` Return in Needle? Understanding Response Structure and Tool Results
agent.run() returns a single dictionary combining the LLM's final response with an ordered history of all tool execution results from the multi-step interaction.
In the Needle open-source agent framework, agent.run() serves as the primary entry point for driving conversations between a language model and registered tools. The method orchestrates the complete loop—sending prompts to the engine, detecting function calls, executing tools, and feeding results back—until the model produces a final answer or hits the step limit.
The agent.run() Return Dictionary Structure
The return value is always a Python dictionary with a consistent set of keys. According to the implementation in [needle/__init__.py](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py#L183-L205), the structure contains:
| Key | Type | Description |
|---|---|---|
"type" |
str |
Engine response type: "call" (wants more tools) or "final" (done). |
"function_calls" |
list |
Function-call objects from the last completion request (empty when finished). |
"results" |
list |
Ordered list of every tool result executed during the run. |
| Other engine fields | varies | Additional keys like "text", "confidence" passed through from the engine. |
The "results" list is particularly important: it preserves the chronological order of all tool invocations, making it possible to trace exactly what happened during a multi-step interaction.
How agent.run() Builds the Return Value
The method follows a precise execution flow to assemble this response:
-
Initial completion: Calls
_complete()to get the first engine response. -
Tool execution loop: Runs up to
max_stepstimes:- Breaks immediately if
"type"is not"call"or"function_calls"is empty. - Looks up each called function in
self._functions(registered via the@tooldecorator). - Executes callables with engine-provided arguments.
- Stores successful returns; captures exceptions as
{"error": "..."}. - Accumulates all results in the
executedlist. - JSON-encodes accumulated results and feeds them back via
_complete().
- Breaks immediately if
-
Final assembly: Attaches the
executedlist to the last engine response under"results"and returns the enriched dictionary.
This design means callers receive both the model's final output and a complete audit trail of side effects.
Practical Code Examples
Basic Single-Tool Execution
from needle import Needle, tool
@tool
def add(a: int, b: int) -> int:
"""Return the sum of two integers."""
return a + b
agent = Needle(tools=[add])
response = agent.run(
query="What is 7 plus 5? Use the add tool.",
max_steps=3,
max_new_tokens=64
)
print(response)
Typical return value:
{
"type": "final",
"text": "The answer is 12.",
"function_calls": [],
"results": [12]
}
Multi-Step Tool Chain
response = agent.run(
query="Plan a dinner: first get a recipe, then order ingredients.",
max_steps=5
)
# Access ordered results
print(response["results"])
The "results" list preserves execution order, e.g.:
["spaghetti carbonara recipe", {"order_id": "12345"}]
Key Files Supporting agent.run() Behavior
| File | Purpose |
|---|---|
needle/__init__.py#L183-L205 |
Core Needle.run() implementation—orchestrates LLM calls, tool execution, and response assembly. |
[needle/agent/tools.py](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) |
Tool registration via @tool decorator, schema building, and Pydantic integration used during function lookup. |
[needle/_worker.py](https://github.com/cactus-compute/needle/blob/main/needle/_worker.py) |
Fallback completion implementation when custom fine-tuned weights are supplied. |
Summary
agent.run()returns a dictionary, not a raw string or object.- The
"results"key contains an ordered list of all tool outputs from the multi-step run. - The
"function_calls"key shows any pending calls from the final engine response (usually empty when"type": "final"). - Tool registration happens via
needle.agent.tools; execution logic lives inneedle/__init__.py. - This structure enables post-hoc analysis of tool side effects without parsing raw text.
Frequently Asked Questions
What happens if a tool throws an exception during agent.run()?
Exceptions are captured and stored as {"error": "..."} in the "results" list. The loop continues with remaining function calls, and the error dictionary appears in its chronological position. The final response still returns normally with all results (successes and failures) included.
Does agent.run() modify the original engine response?
Yes, but only by adding the "results" key. All original engine fields pass through unchanged. This enrichment happens at line ~204 in needle/__init__.py where executed is attached to the response dictionary before return.
How can I limit tool execution to prevent infinite loops?
Use the max_steps parameter (default varies by version). The loop breaks immediately if the engine returns "type": "final" or empty "function_calls", so well-behaved models naturally terminate. Setting max_steps=1 forces single-shot execution without tool feedback.
Is the "results" order guaranteed?
Yes. The implementation appends to executed immediately after each tool call, maintaining strict chronological order. This matches the sequence of "function_calls" across all iterations, enabling precise reconstruction of the interaction timeline.
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 →