How to Distinguish Between Needle 2 and Needle 3 Generations in Cactus Compute

Needle 2 and Needle 3 are distinguished by a 4-byte archive tag in .cact weight files, runtime environment variables, and the internal _generation attribute, with the Python package automatically selecting the correct native engine to prevent cross-generation incompatibility.

The cactus-compute/needle repository maintains two separate generations of its foundation model, each with distinct capabilities and binary requirements. When you load a model or initialize a Needle instance, the package inspects multiple signals to determine which generation is active, ensuring that generation-specific features like audio processing remain incompatible with wrong-version archives.

Archive Format Tag Detection

Every .cact weight archive begins with a 4-byte magic tag that maps directly to its generation. In needle/__init__.py, the _CACT_GENERATIONS dictionary (lines 23-26) defines these mappings:

  • Generation 2: Tag 0x05E12A83
  • Generation 3: Tag 0x05E12A84

The _weight_generation() function (lines 29-40) reads the first four bytes of any supplied archive and returns the corresponding integer generation. This detection runs automatically during Needle initialization before any native code loads.

import pathlib
from needle import _weight_generation

archive = pathlib.Path("my_model.cact")
gen = _weight_generation(archive)  # Returns 2 or 3

print(f"Detected Needle generation: {gen}")

If you attempt to load a generation 3 archive, the package immediately knows to prepare the Needle 3 engine pipeline.

Runtime Generation Identification

Once initialized, every Needle instance exposes its generation through the _generation attribute. When you instantiate the class without providing weights, it defaults to generation 2, reflecting the historic compact 14 MB binary format.

from needle import Needle

# Default initialization (no weights)

needle2 = Needle()
print(needle2._generation)  # Output: 2

# Explicit generation 3 archive

needle3 = Needle(weights="model_v3.cact")
print(needle3._generation)  # Output: 3

This attribute drives all subsequent capability checks throughout the API implementation.

Native Library Path Dispatch

The package dynamically selects the appropriate shared library based on the detected generation. The _library_path() function (lines 43-73 in needle/__init__.py) constructs candidate library names (libneedle2.so or libneedle3.so) and checks environment variables before falling back to the cached engine.

You can override the library selection using generation-specific environment variables:

  • NEEDLE2_LIB_PATH – Path to the generation 2 native library
  • NEEDLE3_LIB_PATH – Path to the generation 3 native library
  • NEEDLE_LIB_PATH – Historic alias that only affects generation 2
import os
import needle

# Force a specific generation 3 library location

os.environ["NEEDLE3_LIB_PATH"] = "/opt/custom/libneedle3.so"
model = needle.Needle(weights="audio_model.cact")

API Capability Constraints

Runtime checks throughout needle/__init__.py enforce generation-specific limitations. Functions requiring generation 3 capabilities—such as embed() (lines 64-68) and internal methods like _complete() (lines 30-34)—validate the instance's _generation attribute before executing.

If you invoke a generation 3 feature on a generation 2 instance, the API raises a ValueError immediately. Conversely, generation 2-specific features (like the compact binary format) are unavailable when a generation 3 archive is loaded.

from needle import Needle

# Generation 2 instance lacks embedding support

legacy = Needle()
try:
    legacy.embed("text sample")
except ValueError as e:
    print("Embeddings require Needle 3")  # Error raised here

Managing Binaries via CLI

The needle fetch command downloads generation-specific binaries into isolated cache directories. Use the --generation flag to explicitly target one version:


# Download Needle 2 engine to ~/.cache/cactus-needle/v2/

needle fetch --generation 2

# Download Needle 3 engine to ~/.cache/cactus-needle/v3/

needle fetch --generation 3

This ensures the correct native library is available before you instantiate any models, as documented in doc/apis.md around line 127.

Summary

  • Archive inspection: The _weight_generation() helper reads 4-byte tags (0x05E12A83 for generation 2, 0x05E12A84 for generation 3) from .cact files to determine compatibility.
  • Instance attribute: Query needle._generation to identify which engine is active at runtime.
  • Library control: Set NEEDLE2_LIB_PATH or NEEDLE3_LIB_PATH environment variables to override default library discovery in _library_path().
  • Feature gates: Generation 3 features (audio, embeddings) raise ValueError when called on generation 2 instances, preventing silent failures.
  • Binary isolation: Use needle fetch --generation to cache version-specific native engines in separate directories.

Frequently Asked Questions

How can I verify which generation my .cact file uses without loading it?

You can import the internal _weight_generation function from needle/__init__.py and pass it a Path object to your archive. It returns 2 or 3 based on the 4-byte header tag without initializing the full engine.

What happens if I try to use audio features on a Needle 2 model?

The API raises a ValueError during the method call. Generation 2 archives lack the necessary tensor layouts for audio processing, and the runtime checks in methods like _complete() explicitly block these operations when self._generation == 2.

Can I force Needle to use generation 3 without providing a weights file?

No. Without a .cact archive, the Needle constructor defaults to generation 2. To use generation 3 capabilities, you must supply a valid generation 3 archive, which triggers the tag detection logic and sets self._generation = 3.

Where does Needle store the downloaded native libraries for each generation?

The needle fetch command stores binaries under ~/.cache/cactus-needle/v2/ for generation 2 and ~/.cache/cactus-needle/v3/ for generation 3. The _library_path() function searches these directories automatically when resolving native library paths.

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 →