How to Handle Tool Call Results in the Needle Agentic Loop: A Complete Guide

Needle orchestrates tool execution through an agentic loop where Needle.run() automatically detects function calls, executes registered tools, feeds results back to the model, and returns accumulated outcomes under response["results"].

The cactus-compute/needle library provides a lightweight Python framework for building LLM-powered agents. At its core lies an agentic loop that bridges model reasoning with external tool execution. Understanding how Needle handles tool call results is essential for building reliable, multi-step agent workflows.

The Needle Agentic Loop Architecture

The inference engine implements a bidirectional feedback cycle between the LLM and registered tools. The Needle.run() method in needle/__init__.py (lines 27‑47) drives this process, managing up to max_steps iterations (default 8) until the model completes its reasoning.

Step-by-Step Execution Flow

  1. Initial completionself.complete() generates the model's first response to the user query.

  2. Call detection — If the response type is "call" with a function_calls array, each call is matched against self._functions, the agent's registered tool map.

  3. Tool execution — For each detected call:

    • Unknown tools produce {"error": "unknown tool: …"}
    • Known tools execute via fn(**call.get("arguments") or {})
    • Runtime exceptions are caught and converted to {"error": str(exc)}
  4. Result collection — All return values and error objects accumulate in a results list.

  5. Feedback iteration — The results list is JSON-encoded and sent to self.complete() for the model to reason about outcomes and optionally emit new calls.

  6. Loop termination — Steps 2‑5 repeat until no more calls are emitted or max_steps is reached.

  7. Final response — The complete results list attaches to the response under "results" and returns to the caller.

Registering Tools with the @needle.tool Decorator

Tools are defined using Python functions decorated with @needle.tool. The decorator, implemented in needle/agent/tools.py (lines 64‑67), automatically extracts type hints and docstrings to build JSON schemas stored in fn._needle_tool.

import needle

@needle.tool
def get_weather(city: str):
    """Get the current weather for a city."""
    return {"city": city, "temp_c": 27, "sky": "clear"}

agent = needle.Needle(tools=[get_weather])
response = agent.run("What's the weather like in Lagos right now?")

print(response["results"])

# → [{'city': 'Lagos', 'temp_c': 27, 'sky': 'clear'}]

The schema builder enables the model to understand available tools without manual configuration. Docstrings become descriptions; type hints become parameter specifications.

Handling Multiple Tool Calls in Sequence

The agentic loop supports chained tool execution where one tool's output feeds into subsequent calls. Needle preserves execution order in response["results"], allowing you to trace the full reasoning chain.

@needle.tool
def add(a: int, b: int):
    """Add two integers."""
    return a + b

@needle.tool
def multiply(x: int, y: int):
    """Multiply two integers."""
    return x * y

agent = needle.Needle(tools=[add, multiply])
response = agent.run("First add 3 and 5, then multiply the result by 2.")

print(response["results"])

# → [8, 16]

Here the model first calls add(3, 5)8, then multiply(8, 2)16. Both results appear in sequence, reflecting the actual execution order.

Error Handling in Tool Call Results

Needle implements defensive execution that prevents individual tool failures from crashing the entire agent loop. Two error categories are captured:

  • Unregistered tools: Immediate error object with unknown tool name
  • Runtime exceptions: Full exception message stringified into error object

These error objects appear in response["results"] alongside successful outputs, giving the model context to potentially recover or explain failures.

@needle.tool
def flaky_api(query: str):
    """Sometimes fails."""
    raise ConnectionError("API timeout")

agent = needle.Needle(tools=[flaky_api])
response = agent.run("Query the flaky API")

print(response["results"])

# → [{'error': 'API timeout'}]

The model receives this error and can choose to retry, use alternative tools, or inform the user.

Accessing and Processing Final Results

The response object from agent.run() is a dictionary containing:

Key Description
results List of all tool return values and error objects in execution order
Additional metadata Model-generated text and loop metadata

Process results programmatically by iterating over the list:

response = agent.run("Complex multi-tool query")

for idx, result in enumerate(response["results"]):
    if isinstance(result, dict) and "error" in result:
        print(f"Step {idx} failed: {result['error']}")
    else:
        print(f"Step {idx} succeeded: {result}")

Key Implementation Files

  • needle/__init__.py: Contains the Needle class and run() method implementing the agentic loop (lines 27‑47)
  • needle/agent/tools.py: Provides @needle.tool decorator and build_schema for automatic schema generation (lines 64‑67)
  • needle/agent/fetch.py: Handles native engine library downloads (required dependency)

Summary

  • Needle's agentic loop in Needle.run() automates the full cycle: detect calls → execute tools → collect results → feed back to model
  • Tool registration uses @needle.tool in needle/agent/tools.py with automatic schema extraction from type hints and docstrings
  • Result accumulation preserves execution order in response["results"], mixing successful returns with structured error objects
  • Error resilience catches unknown tools and runtime exceptions, converting both to JSON-serializable error objects without breaking the loop
  • Multi-step reasoning is supported through iterative feedback: the model can chain tool calls using previous results

Frequently Asked Questions

How does Needle decide when to stop the agentic loop?

The loop terminates when the model response type is no longer "call" (indicating no more tool invocations) or when max_steps iterations complete. The default max_steps=8 prevents infinite loops with chatty models. You can configure this parameter when initializing Needle.

Can I access intermediate results during the loop, or only at the end?

Results are only available in the final response["results"] after run() completes. The implementation in needle/__init__.py accumulates outputs internally without exposing partial states. For streaming or step-wise access, you would need to subclass Needle and override run().

What happens if a tool returns None or an empty result?

None, empty strings, empty lists, and other falsy values are valid return values and append to results unchanged. Only actual Python exceptions trigger error object conversion. The model receives null in JSON encoding for None returns and can reason about these as legitimate outcomes.

How do I debug tool execution when results seem incorrect?

Inspect response["results"] to identify which step diverged from expectations. Each element corresponds positionally to a function_calls entry in the model's response. Enable verbose logging or add print statements inside tool functions—the decorator preserves original function behavior, so standard debugging techniques apply.

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 →