# Why the Needle Engine Requires Re-Initialization for Every extract() Call

> Learn why the Needle engine re-initializes for every extract() call. Understand the limitations of the C runtime and temporary agents for schema binding. Optimize your Needle usage.

- Repository: [Cactus Compute, Inc./needle](https://github.com/cactus-compute/needle)
- Tags: internals
- Published: 2026-08-20

---

**`needle.extract()` forces a re-initialization of the native engine because the underlying C runtime can only bind to a single tool schema at a time, and the convenience wrapper creates a temporary agent with a unique schema for each one-shot extraction.**

The `extract()` function in `cactus-compute/needle` provides a streamlined interface for structured data extraction, but each call forces Needle engine re-initialization. This behavior stems from architectural constraints in the native C library, which maintains exactly one active tool definition and cannot hot-swap schemas after startup.

## The One-Shot Architecture of `needle.extract()`

`needle.extract()` is not a persistent agent. According to the source code in [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py), the function is a **one-shot** convenience wrapper that spawns a short-lived `Needle` instance for every call.

The wrapper instantiates an agent with `tools=[schema]` as its sole tool and then delegates to the shared native engine. Because this temporary agent exists only for the duration of the call, it must configure the engine from scratch to recognize its specific JSON schema.

## Why Re-Initialization Is Unavoidable

The **native engine** is bound to a **single set of tools** supplied at initialization. Once started, it cannot alter its tool set without a full reset.

### Single-Schema Limitation in the Native Runtime

The underlying C library holds exactly one set of tools in memory at any moment. When `extract()` is invoked with a new schema, the engine cannot append or swap tools dynamically. Instead, the `_bind()` method in [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) must call the native `needle_init` function to re-initialize the runtime with the new tool description.

### Preserving Loaded Weights Across Resets

Although the engine discards its old tool binding, it does not unload the model weights. The wrapper preserves the currently active weights through the `_active_weights` variable and re-uses them during re-initialization. In `_bind()` (lines **[70‑95]**), the code checks whether the current global agent (`_active`) matches the new instance. If not, it loads any required new weight file and then invokes `needle_init` (line **[91]**) to associate the engine with the updated tool list while keeping the existing model in memory.

## Source Code Walkthrough: The Re-Initialization Flow

The mechanics are implemented across three key areas of [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py).

**The `extract` wrapper (lines 166‑172).** When you call `needle.extract(text, schema)`, the wrapper creates a fresh `Needle` instance configured with `tools=[schema]`. This single-schema tool list is what triggers the subsequent engine reset.

**`Needle.__init__` (line 68).** The constructor stores the supplied schema as `self._tools_json` and immediately calls `self._bind()` to synchronize the instance with the global engine state.

**`_bind()` (lines 70‑95).** This method performs the actual re-initialization logic. It compares the incoming instance against `_active`, ensures the correct weight file is loaded, and then calls the native `needle_init` (line **[91]**) to bind the engine to the new schema. Because the native runtime can only hold one tool set, this step is mandatory for every distinct extraction schema.

## Practical `extract()` Examples

Each example below demonstrates valid usage and implicitly triggers the re-initialization sequence described above.

**Example 1: Pydantic Schema Extraction**

```python

# Example 1 – Simple extraction with a Pydantic schema

from pydantic import BaseModel, Field
import needle

class Contact(BaseModel):
    name: str = Field(..., description="Full name")
    email: str = Field(..., description="Email address")

text = "John Doe can be reached at john@doe.com"
result = needle.extract(text, Contact)

# → Contact(name='John Doe', email='john@doe.com')

```

Internally, this call instantiates a temporary `Needle` agent with the Pydantic-derived JSON schema as its only tool. The wrapper then triggers `agent.complete(...)`, parses the first function-call result, and returns the structured object.

**Example 2: Plain Dictionary Schema**

```python

# Example 2 – Extracting into a plain dict (no Pydantic model)

schema = {
    "name": "weather",
    "description": "Extract weather info",
    "type": "object",
    "properties": {
        "city": {"type": "string"},
        "condition": {"type": "string"}
    },
    "required": ["city"]
}
text = "The weather in Paris is sunny."
payload = needle.extract(text, schema)

# → {"city": "Paris", "condition": "sunny"}

```

Here, the raw JSON schema is passed directly. As with the Pydantic example, `needle.extract()` creates a one-off agent, resets the native engine via `_bind()`, and performs the extraction.

## Summary

- `needle.extract()` is a **one-shot wrapper** that creates a temporary `Needle` agent for each call.
- The native engine can only maintain **one tool schema at a time**, necessitating re-initialization when the schema changes.
- The `_bind()` method in [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) handles the reset by calling `needle_init` (line **[91]**), but it preserves existing **model weights** via `_active_weights` to avoid redundant loading.
- Each call to `extract()` internally runs `Needle(tools=[schema])`, `_bind()`, and then `agent.complete(...)` before returning the parsed result.

## Frequently Asked Questions

### Does `extract()` reload model weights on every call?

No. While the engine is re-initialized with the new schema, the `_bind()` method preserves the currently loaded weights through `_active_weights`. It only loads a new weight file if the requested weights differ from those already in memory.

### Can I use `extract()` with multiple schemas in succession?

Yes, but each invocation with a different schema forces a full engine re-initialization. The native runtime in `cactus-compute/needle` cannot hold multiple tool sets simultaneously, so alternating schemas will trigger repeated calls to `needle_init` inside `_bind()`.

### Why not initialize the engine once with all possible schemas?

The native C library is designed to hold exactly one tool definition. Supporting multiple concurrent schemas would complicate the lightweight runtime, so the library opts for a single-schema model. The `extract()` wrapper embraces this constraint by isolating each extraction to its own temporary agent.

### Is there a performance penalty for re-initializing the engine?

The primary cost is the native `needle_init` call, not model weight reloading. Because `_bind()` reuses `_active_weights` when possible, the overhead is limited to rebinding the schema rather than reloading the full model from disk.