# How to Initialize the Needle 2 Engine with Tool Schemas and Weights

> Learn how to initialize the Needle 2 engine using tool schemas and weights. Instantiate the Needle class with optional tools and weights path for seamless integration.

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

---

**Initialize the Needle 2 engine by instantiating the `Needle` class with an optional `tools` list containing callables, Pydantic models, or JSON schemas, and an optional `weights` path to a `.cact` archive; the engine binds itself as a process-wide singleton via the internal `_bind()` method.**

The `cactus-compute/needle` repository provides a lightweight inference engine that powers function-calling and structured extraction workflows. When you initialize the Needle 2 engine, you are configuring a singleton instance that manages native weight archives and JSON tool schemas for the lifetime of your Python process.

## Understanding the Core Initialization Parameters

The `Needle` class defined in [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) accepts three critical arguments during instantiation that determine the engine's behavior.

### The tools Argument

The **`tools`** parameter accepts a list of definitions that the engine can invoke during inference. According to the source code, each entry may be one of three types:

- **Python callables**: Functions decorated with `@tool` or plain callables with type annotations
- **Pydantic models**: Classes inheriting from `BaseModel` used for structured extraction
- **Pre-built JSON schemas**: Raw dictionaries conforming to the JSON Schema specification

When `Needle.__init__` receives this list, it calls `_resolve()` to iterate over each entry. Callables are transformed into JSON schemas via `build_schema()` in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) (lines 15-46), while Pydantic models are processed by `pydantic_schema()` in the same file (lines 55-66). The resulting schemas are JSON-encoded and stored in `self._tools_json`, while the original Python objects are mapped by schema name in `self._functions` for later invocation.

### The weights Argument

The **`weights`** parameter specifies the filesystem path to a **`.cact`** archive—a binary weight file that must match the exact version of the compiled C++ engine. 

During initialization in `Needle._bind()` (lines 85-93 of [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py)), the code checks the module-level global `_active_weights`. If no weights are currently loaded, the file is opened in binary mode (`open(..., "rb")`) and the raw bytes are passed to the native `needle_load` function. Once loaded, these weights remain active for the entire process lifetime; subsequent `Needle` instances will reuse the already-loaded weights, and attempting to load a different weight file raises a `RuntimeError`.

### The system Argument

The **`system`** parameter accepts a string that serves as the system prompt prepended to every request. This string is encoded to UTF-8 (`self._system = (system or "").encode("utf-8")`) and passed directly to the native `needle_init` function during the binding phase.

## The Three-Step Initialization Sequence

The `Needle` constructor orchestrates a specific sequence to prepare the engine for inference.

### Step 1: Tool Resolution via _resolve()

First, `self._resolve(tools)` builds the JSON schema list. For callable functions decorated with `@tool`, the decorator stores the generated schema in `fn._needle_tool` (as implemented in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py), lines 68-71). For Pydantic models, the code detects them via `_is_pydantic_model` and extracts field metadata. Raw dictionaries are passed through unchanged. This step populates both `self._tools_json` (the schema payload) and `self._functions` (the execution mapping).

### Step 2: Weight Loading via _bind()

Next, `Needle._bind()` manages the weight archive. The code checks the module globals `_active_weights` and `_active_blob` to determine if a weight set is already resident in memory. If `weights` is provided and no active set exists, the archive bytes are loaded via `needle_load`. Because the engine cannot unload or hot-swap weight sets, the first `Needle` instance created in a process determines which model remains loaded for all subsequent instances.

### Step 3: Engine Binding and Singleton Activation

Finally, the native `needle_init` function is invoked with the UTF-8 encoded system prompt, the JSON-encoded tool list, and optional tool-index paths. The engine becomes the active singleton (stored in the module-level `_active` global), enabling the high-level APIs (`complete`, `run`, `extract`) to utilize the bound native library.

## Practical Code Examples

### Example 1: Initializing with a Decorated Python Function

Use the `@tool` decorator to register a function and initialize the engine without custom weights:

```python
from needle import Needle, tool

@tool
def add(a: int, b: int) -> int:
    """Add two numbers."""
    return a + b

# Initialize with the tool; uses default base model weights

engine = Needle(tools=[add])

resp = engine.complete("What is 2 + 3?")
print(resp["output"])

```

The decorator in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) attaches the schema metadata to the function object, which `_resolve()` detects and extracts.

### Example 2: Initializing with a Pydantic Model for Structured Extraction

Pass a Pydantic model as a tool when you need structured data extraction, along with a specific weight archive:

```python
from pydantic import BaseModel, Field
from needle import Needle

class WeatherReport(BaseModel):
    """Report the weather for a city."""
    city: str = Field(..., description="Name of the city")
    temperature: float = Field(..., description="Temperature in Celsius")
    condition: str = Field(..., description="Weather condition")

# Load fine-tuned weights and the Pydantic tool

engine = Needle(
    tools=[WeatherReport], 
    weights="models/needle-v2.cact"
)

result = engine.extract(
    "Give me the weather in Paris right now.",
    schema=WeatherReport,
)

```

Here, `pydantic_schema()` in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) converts the model class into a JSON schema compatible with the engine's function-calling protocol.

### Example 3: Initializing with Raw JSON Schemas

Supply pre-built JSON schemas directly when integrating external tool definitions:

```python
import json
from needle import Needle

calc_schema = {
    "name": "calc",
    "description": "Evaluate a basic arithmetic expression.",
    "parameters": {
        "type": "object",
        "properties": {
            "expr": {"type": "string", "description": "Expression, e.g. '3*4+5'"},
        },
        "required": ["expr"],
    },
}

engine = Needle(
    tools=[calc_schema],
    system="You are a helpful assistant.",
    weights=None,
)

resp = engine.run("What is 7*6? Then add 2.", max_steps=2)

```

When `_resolve()` encounters a dictionary, it appends it directly to the schema list without transformation.

## Critical Constraints and Process Architecture

Because the Needle 2 engine relies on module-level globals (`_active`, `_active_weights`, `_active_blob`), it operates as a process-wide singleton. You **must initialize the engine only once per process** and cannot unload or switch weight archives after the first initialization. 

If your application requires inference with different model weights, you must spawn separate Python processes or reset the interpreter. Attempting to instantiate a second `Needle` object with a different `weights` path raises a `RuntimeError` when `_bind()` detects the mismatch with `_active_weights`.

## Summary

- **`Needle(tools=..., weights=..., system=...)`** is the primary entry point in [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) for initializing the engine.
- **Tool schemas** are resolved via `_resolve()`, which delegates to `build_schema()` or `pydantic_schema()` in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) to generate JSON-compatible definitions.
- **Weight archives** (`.cact` files) are loaded once per process via `needle_load` in `_bind()`, and subsequent instances reuse the active weight set.
- **System prompts** are UTF-8 encoded and passed to the native `needle_init` function during binding.
- The engine is a **singleton**; weight files cannot be swapped after the first initialization, requiring separate processes for model switching.

## Frequently Asked Questions

### What file formats are supported for Needle 2 weights?

Needle 2 requires **`.cact`** archives—proprietary binary weight files that must match the exact version of the compiled C++ engine. The code in [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) opens these files in binary mode (`"rb"`) and passes the raw bytes directly to the native `needle_load` function.

### Can I switch weight files after initializing the Needle engine?

No. The engine maintains a process-wide singleton state via the `_active_weights` global. Once `needle_load` has been called, the weight set remains resident in memory for the lifetime of the process. Attempting to initialize a new `Needle` instance with a different `weights` path raises a `RuntimeError` during `_bind()`.

### How does Needle convert Python functions into tool schemas?

The conversion happens in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) via the `build_schema()` function (lines 15-46). When you pass a callable to `Needle(tools=[...])`, the `_resolve()` method checks for the `_needle_tool` attribute (set by the `@tool` decorator) or inspects the function's type annotations and docstring to generate a JSON Schema compliant with the engine's function-calling protocol.

### Can I use multiple Pydantic models as tools in the same engine instance?

Yes. The `tools` parameter accepts a heterogeneous list, allowing you to mix Pydantic models, decorated functions, and raw JSON schemas. Each model is converted to a schema via `pydantic_schema()` in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) and stored in `self._functions` keyed by the model name, enabling the engine to route function calls to the correct Python class during `extract()` or `run()` operations.