# How to Declare Custom Tools Using the @needle.tool Decorator in Needle

> Declare custom tools in Needle using the @needle.tool decorator. Automatically generate JSON schemas from your Python functions for your LLM agent.

- Repository: [Cactus Compute, Inc./needle](https://github.com/cactus-compute/needle)
- Tags: how-to-guide
- Published: 2026-09-04

---

**The @needle.tool decorator exposes Python functions as callable tools for the Needle LLM agent by automatically generating a JSON schema from type hints, default values, and docstrings.**

This guide explains how to declare custom tools using the @needle.tool decorator in the cactus-compute/needle repository. When applied to any Python function, this decorator inspects the signature and documentation to create a machine-readable schema, making the function discoverable and invocable by the LLM agent while preserving its original callable behavior.

## How the @needle.tool Decorator Works

The decorator is implemented in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py). When you apply `@needle.tool` to a function, Needle performs three distinct operations at import time.

First, it calls `build_schema(fn)` (defined at line 73) to introspect the function’s type hints, default arguments, and docstring. This generates a JSON schema describing the tool’s name, parameters, required fields, and descriptions.

Second, it attaches the generated schema to the function via the private attribute `_needle_tool`. As seen at line 74 in [`tools.py`](https://github.com/cactus-compute/needle/blob/main/tools.py), the implementation executes `fn._needle_tool = build_schema(fn)`, marking the function as a discoverable tool.

Third, the decorator returns the original function object unchanged, allowing you to call it directly in Python code without interference from the tooling infrastructure.

## Step-by-Step Guide to Declaring a Custom Tool

Follow these steps to expose a Python function as a Needle tool:

1. **Import Needle**: Access the decorator via `import needle`.

2. **Define the function**: Write a standard Python function with type hints for every parameter. Use `typing.Annotated` combined with `needle.Field` to add JSON Schema constraints like `minLength` or numerical ranges.

3. **Add a descriptive docstring**: Include a clear description and an `Args:` section. Needle parses this docstring to populate the schema’s `description` and per-parameter documentation fields.

4. **Apply the decorator**: Prefix the function with `@needle.tool`. This triggers schema generation immediately at import time.

5. **Export the function** (optional): Add the function to a module-level `TOOLS` list, or ensure it is accessible through the environment’s `agent` property. This step allows `needle._harness` to discover the tool at runtime.

## Practical Code Examples

### Basic Tool Without Parameters

The simplest tool requires no arguments and returns a static response.

```python
import needle

@needle.tool
def ping():
    """Return a simple health-check response."""
    return {"ok": True}

```

The generated schema for this function is `{"name": "ping", "parameters": {"type": "object", "properties": {}}}`, indicating it accepts no inputs.

### Tool with Type Hints and Validation

For production use, define parameters with type hints and validation constraints using `Annotated` and `Field`.

```python
import needle
from typing import Annotated, Literal

Priority = Literal["low", "medium", "high"]

@needle.tool
def create_task(
    title: Annotated[str, needle.Field(min_length=1, max_length=120)],
    due_days: int = 7,
    priority: Priority = "medium",
):
    """
    Create a new task in the user's to-do list.

    Args:
        title: Short description of the task.
        due_days: Number of days from now the task is due.
        priority: Desired priority level.
    """
    return {
        "created": True,
        "title": title,
        "due_in": due_days,
        "priority": priority
    }

```

In this example, `Annotated` maps Python types to JSON Schema constraints. The `Literal` type generates an `enum` constraint, while `needle.Field` injects `minLength` and `maxLength` validations directly into the schema.

### Exporting Tools in an Environment Module

To make tools available to the test harness, export them in a `TOOLS` list within your environment module.

```python

# needle/environments/smart_home.py

import needle
from needle import Field
from typing import Annotated, Literal

@needle.tool
def set_light(
    room: Literal["kitchen", "living_room"],
    brightness: Annotated[int, Field(ge=0, le=100)]
):
    """Set the brightness of a room's light.

    Args:
        room: The target room.
        brightness: Desired brightness percentage (0-100).
    """
    return {"room": room, "brightness": brightness, "status": "ok"}

TOOLS = [set_light]

```

The `TOOLS` list in [`needle/environments/smart_home.py`](https://github.com/cactus-compute/needle/blob/main/needle/environments/smart_home.py) signals to `needle._harness` which functions belong to this specific environment. The harness reads the `_needle_tool` attribute attached by the decorator to understand the function’s contract.

## Tool Discovery and the Harness

The `needle._harness` module handles runtime discovery of decorated functions. When an environment loads, the harness inspects module-level `TOOLS` lists (as seen in [`needle/environments/wearable.py`](https://github.com/cactus-compute/needle/blob/main/needle/environments/wearable.py) and [`smart_home.py`](https://github.com/cactus-compute/needle/blob/main/smart_home.py)) to identify candidate functions.

For each function in these lists, the harness checks for the `_needle_tool` attribute populated by the decorator. This attribute contains the complete JSON schema necessary to construct LLM-compatible tool definitions. This architecture separates schema generation (performed once at import) from runtime registration (performed by the harness).

## Summary

- **@needle.tool** generates JSON schemas automatically by inspecting type hints and docstrings in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py).
- The schema is stored in the `_needle_tool` attribute, leaving the original function callable for direct Python use.
- Use `typing.Annotated` with `needle.Field` to add validation constraints like string length or numeric ranges.
- Export decorated functions via a module-level `TOOLS` list to enable discovery by `needle._harness`.
- Clear docstrings with `Args:` sections improve the LLM’s understanding of tool parameters.

## Frequently Asked Questions

### What is the @needle.tool decorator and where is it defined?

The `@needle.tool` decorator is defined in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py). It wraps Python functions to automatically generate JSON schemas that describe the function’s interface, enabling the Needle LLM agent to understand and invoke them.

### How does Needle convert function signatures to JSON schemas?

Needle uses the `build_schema` utility function (line 73 in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py)) to introspect function signatures. It examines type hints, default values, and docstrings to construct a schema that includes parameter types, required fields, descriptions, and validation constraints.

### Can I add validation constraints like minimum string length or numeric ranges?

Yes. Use `typing.Annotated` in combination with `needle.Field`. For example, `Annotated[str, needle.Field(min_length=1, max_length=120)]` generates a schema with `minLength` and `maxLength` constraints, while `Annotated[int, Field(ge=0, le=100)]` enforces numeric bounds.

### How does the Needle harness discover my decorated functions?

The `needle._harness` module discovers tools by checking for the `_needle_tool` attribute attached by the decorator. Typically, you export decorated functions in a module-level `TOOLS` list (as demonstrated in [`needle/environments/smart_home.py`](https://github.com/cactus-compute/needle/blob/main/needle/environments/smart_home.py)), which the harness scans when loading an environment.