# How to Initialize the Needle Agent with Custom Tools and System Information

> Learn how to initialize the Needle agent with custom tools and system information. Easily configure Needle by passing callables or Pydantic models and custom system strings.

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

---

**Pass a list of callables or Pydantic models to the `tools` parameter and a string to the `system` parameter when instantiating the `Needle` class from [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py).**

The Needle SDK provides a lightweight Python interface to Cactus Compute's native inference engine. You customize an agent's capabilities and behavior by defining external functions the model can invoke and by conditioning its responses through a system prompt. This article shows how to configure both, with references to the actual source implementation in the [cactus-compute/needle](https://github.com/cactus-compute/needle) repository.

---

## Overview of the Needle Constructor

The `Needle` class in [[`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py)](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) accepts several parameters that control initialization:

| Parameter | Type | Purpose |
|-----------|------|---------|
| `tools` | `list[Callable \| BaseModel \| dict]` | Functions, Pydantic models, or raw JSON schemas describing available tools |
| `system` | `str` | System prompt encoding the agent's persona and constraints |
| `weights` | `str \| Path \| None` | Path to a fine-tuned `.cact` file for custom model weights |
| `tool_index_path` | `str \| None` | Pre-built tool index for large tool collections |
| `buffer_size` | `int` | Response buffer size in bytes (default: 32768) |

The constructor performs three core operations: **tool resolution**, **encoding**, and **engine binding**.

---

## Step 1: Define Custom Tools

Tools are Python functions decorated with `@tool` or Pydantic models that generate JSON schemas. The `@tool` decorator is implemented in [[`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py)](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py).

### Using the @tool Decorator

```python
from needle import tool, Field

@tool
def translate(text: str, target_lang: str = "es") -> str:
    """Translate *text* into *target_lang* (default Spanish)."""
    return f"[{target_lang}] {text}"

```

The `@tool` decorator calls `build_schema` to generate a JSON schema from the function signature and attaches it as `_needle_tool`.

### Using Field for Rich Metadata

```python
def summarize(
    text: str,
    max_sentences: int = Field(default=3, description="Maximum number of sentences")
):
    """Return a short summary of *text*."""
    return " … ".join(text.split(".")[:max_sentences])

```

`Field` objects enrich parameter metadata with defaults, descriptions, and constraints that appear in the generated schema.

### Raw Pydantic Models or Dictionaries

You can also pass:
- **Pydantic models** — converted via `pydantic_schema` in [`tools.py`](https://github.com/cactus-compute/needle/blob/main/tools.py)
- **Raw dictionaries** — used verbatim as JSON schemas

---

## Step 2: Create a System Prompt

The `system` parameter conditions the model's behavior. It is UTF-8 encoded and passed directly to the native engine:

```python
system_prompt = """
You are a precise technical assistant. When calling tools, explain your reasoning
before invoking the function. Keep responses concise and factual.
"""

```

In [[`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py)](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py), this becomes:

```python
self._system = (system or "").encode("utf-8")

```

If omitted, the system prompt defaults to an empty string `""`.

---

## Step 3: Initialize the Needle Agent

Combine tools and system prompt in the constructor:

```python
from needle import Needle

agent = Needle(
    tools=[translate, summarize],
    system=system_prompt,
)

```

### Internal Processing Flow

1. **`_resolve` iterates `tools`** — Each entry is classified:
   - **Callables**: wrapped by `@tool`, schema built via `build_schema`
   - **Pydantic models**: schema generated via `pydantic_schema`
   - **Dictionaries**: stored as-is

   Resolved schemas populate `self._functions` (callable storage) and `self._tools_json` (serialized JSON).

2. **Byte encoding** — `system` and tools JSON are converted to UTF-8.

3. **Engine initialization** — `_bind` loads the native library and calls `needle_init` with:
   - `self._system` (encoded system prompt)
   - `self._tools_json` (tools schema)
   - Optional `tool_index_path`

---

## Complete Working Example

```python

# example.py

from needle import Needle, tool, Field

# Define tools

@tool
def translate(text: str, target_lang: str = "es") -> str:
    """Translate *text* into *target_lang*."""
    return f"[{target_lang}] {text}"

def summarize(
    text: str,
    max_sentences: int = Field(default=3, description="Maximum sentences")
):
    """Summarize *text* to *max_sentences* sentences."""
    return " … ".join(text.split(".")[:max_sentences])

# System prompt with persona

system_prompt = """You are a helpful assistant that explains tool usage."""

# Initialize agent

agent = Needle(
    tools=[translate, summarize],
    system=system_prompt,
)

# Execute query that may trigger tools

result = agent.run(
    query="Translate and summarize: AI transforms industries."
)
print(result)

```

The `run` method drives the completion loop: it calls `needle_complete`, executes any returned tool calls, and iterates until the engine produces a final response.

---

## Advanced: Fine-Tuned Weights and Tool Indexing

### Custom Weights

Pass a `.cact` file to use fine-tuned parameters instead of the default engine:

```python
agent = Needle(
    tools=[my_tool],
    weights="./models/custom.cact",
)

```

This triggers `FineTuneWorker` from [[`needle/_worker.py`](https://github.com/cactus-compute/needle/blob/main/needle/_worker.py)](https://github.com/cactus-compute/needle/blob/main/needle/_worker.py) rather than loading the standard native library.

### Pre-Built Tool Index

For large tool collections, provide a pre-computed index:

```python
agent = Needle(
    tools=extensive_tool_list,
    tool_index_path="./indices/tools.idx",
    buffer_size=65536,  # Larger buffer for verbose tools

)

```

---

## Summary

- **Tool definitions** go to the `tools` parameter as decorated functions, Pydantic models, or raw schemas — processed by `_resolve` and stored in `self._functions`.
- **System prompts** shape model behavior via the `system` parameter, UTF-8 encoded and forwarded to `needle_init`.
- **Engine binding** happens automatically in `_bind`, selecting between standard library loading or `FineTuneWorker` for custom weights.
- **Tool execution** is handled by `agent.run()`, which manages the completion loop and callback invocation.

---

## Frequently Asked Questions

### Can I pass regular Python functions without the @tool decorator?

Yes, but they must have type annotations. The `Needle` constructor's `_resolve` method will wrap un-decorated callables with `@tool` automatically. However, explicit decoration is recommended for clarity and to leverage `Field` metadata.

### What happens if I omit the system parameter?

The agent initializes with an empty system prompt (`""`). According to the source in [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py), this is encoded as UTF-8 bytes and passed to `needle_init` without modification. The model will use default behavior without persona-conditioning.

### How are tool schemas generated from Pydantic models?

The `_resolve` method detects `BaseModel` subclasses and calls `pydantic_schema` from [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py). This extracts field types, defaults, and descriptions into JSON Schema format, stored alongside the model class in `self._functions`.