How to Manually Define Tool Schemas as JSON in Needle 2: A Complete Guide

You can bypass the @needle.tool decorator and supply a JSON schema directly to needle.Needle(tools=...) using the same structure that build_schema generates internally, containing name, description, and a JSON Schema parameters object.

Needle 2 is an open-source agent framework that discovers tools through JSON-encoded schemas. While the @needle.tool decorator automatically generates these schemas from Python functions, many production scenarios require you to manually define tool schemas as JSON in Needle 2—whether the tool lives outside your codebase, is dynamically generated, or requires explicit control over the interface exposed to the LLM.

Understanding the JSON Schema Structure

According to the source code in needle/agent/tools.py, the build_schema function produces a specific JSON structure that you must replicate when defining schemas manually. The schema must be a dictionary containing three top-level keys:

  • name: The function identifier the model will invoke
  • description: Human-readable documentation shown to the LLM (optional but recommended)
  • parameters: A JSON Schema object with type: "object", a properties map, and optionally a required list

Inside each property definition, you can specify standard JSON Schema types (string, integer, boolean, etc.) and validation constraints such as description, enum, minimum, maximum, pattern, and minLength.

Creating Hand-Written Tool Schemas

Basic JSON Structure

The documentation in doc/apis.md demonstrates the canonical format for manual schema definition. Here is a complete example showing a set_lights tool:

import needle

tools = [{
    "name": "set_lights",
    "description": "Turn a room's lights on or off and set brightness",
    "parameters": {
        "type": "object",
        "properties": {
            "room": {"type": "string", "description": "which room to control"},
            "on": {"type": "boolean"},
            "brightness": {"type": "integer", "minimum": 0, "maximum": 100},
        },
        "required": ["room", "on"],
    },
}]

agent = needle.Needle(tools=tools)
agent.run("turn the kitchen lights on at 70%")

The agent treats this hand-written schema exactly like one generated by the decorator, using the same grammar-constrained decoding and validation pipeline internally.

Loading Schemas from External JSON Files

For large tool catalogs or configuration-driven deployments, store your schemas in a separate JSON file:

import json
import needle

# tools.json contains an array of schemas

with open("tools.json") as f:
    schema = json.load(f)

agent = needle.Needle(tools=schema)
print(agent.run("dim the living room lights to 30"))

This approach keeps your Python code clean while allowing non-developers to modify tool definitions.

Combining Decorator-Generated and Manual Schemas

You can mix automatically generated schemas with hand-written ones by accessing the _needle_tool attribute that the decorator attaches to functions. As implemented in needle/agent/tools.py at lines 62-65, the @needle.tool decorator stores the output of build_schema(fn) in fn._needle_tool:

@needle.tool
def send_money(amount: float, to: str):
    """Send money to a user."""
    return {"sent": amount, "to": to}

# Hand-written schema for external service

extra = [{
    "name": "set_thermostat",
    "description": "Set the thermostat temperature.",
    "parameters": {
        "type": "object",
        "properties": {
            "temperature": {"type": "integer"},
            "mode": {"type": "string", "enum": ["heat", "cool", "auto"]},
        },
        "required": ["temperature"],
    },
}]

# Merge automatic and manual schemas

all_tools = [send_money._needle_tool] + extra
agent = needle.Needle(tools=all_tools)

Key Implementation Details

The core logic resides in needle/agent/tools.py, where the build_schema function (lines 10-41) inspects function signatures and docstrings to generate compliant JSON. When you manually define tool schemas as JSON in Needle 2, you bypass this introspection but must ensure your JSON conforms to the exact structure expected by the agent's validation logic.

The parameters object follows standard JSON Schema draft specifications, allowing you to define complex nested objects, arrays, and validation constraints. The agent uses these schemas for both LLM grammar generation at inference time and runtime parameter validation before tool execution.

Summary

  • Manually define tool schemas as JSON in Needle 2 by providing a list of dictionaries with name, description, and parameters keys to needle.Needle(tools=...).
  • The parameters field must be a valid JSON Schema object with type: "object", a properties map, and a required array listing mandatory arguments.
  • Store schemas in external JSON files for dynamic loading or mix manual schemas with decorator-generated ones using the _needle_tool attribute.
  • The source implementation in needle/agent/tools.py ensures that manual and automatic schemas follow identical validation and grammar-generation pipelines.

Frequently Asked Questions

What is the exact JSON structure required for Needle 2 tool schemas?

The schema must be a dictionary containing name (string), description (string), and parameters (object). The parameters object must specify type: "object", include a properties dictionary mapping argument names to their schema definitions, and optionally list required fields in a required array. This structure mirrors the output of build_schema in needle/agent/tools.py.

Can I use JSON Schema validation keywords like minimum and pattern in hand-written schemas?

Yes. Since the parameters field accepts standard JSON Schema, you can use any validation keywords supported by the specification, including minimum, maximum, pattern, minLength, maxLength, and enum. These constraints are respected during both LLM grammar generation and runtime parameter validation.

How do I combine Python functions decorated with @needle.tool and external JSON schemas?

Access the automatically generated schema via the _needle_tool attribute attached to decorated functions, then concatenate it with your manual schema list. The decorator stores the result of build_schema(fn) at fn._needle_tool (lines 62-65 of needle/agent/tools.py), allowing seamless integration of both approaches.

Where can I find the official example of a hand-written tool schema?

The canonical example appears in doc/apis.md (lines 51-68) within the cactus-compute/needle repository. This documentation demonstrates the complete JSON structure for manual tool definitions and explains how the Needle class consumes these schemas.

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 →