Cactus Compute logo

Cactus Needle 2

Needlev2Current
byCactus ComputeCactus Compute(startup)
Released August 11, 2026
Context--
CategoryLarge Language Model

About Cactus Needle 2

Cactus Needle 2 is a 45M-parameter, 14 MB agentic model for tiny devices. It focuses on tool calling, device control, schema-constrained JSON, and structured extraction; Cactus reports 28 MB session RAM, 800+ tok/s prefill, and 500+ tok/s decode on Raspberry Pi 5. Its 256-token sliding attention window is not a total context or output limit; those ceilings are not established by the model documentation.

Capabilities

tool usestructured outputjson schemadevice controledge inferenceoffline

Input Modalities

text

Output Modalities

text

Technical Details

API Identifier
Cactus-Compute/needle2
Category
Large Language Model

Tags

edge-llmtool-callingstructured-outputtiny-modelon-device-aiapache-2

Run this model yourself

Local and cloud setup for Cactus Needle 2, with exact configurations, sources and clear evidence limits.

Find a model for your hardware
Local hardwareDocumented setupSources checked Oct 16, 2026

Propose a fixed light-setting tool call with Needle2 on a CPU desktop

Use the exact official Needle2 engine 2.0.4 model-bearing Linux wheel through its native C API. A private serial endpoint returns a proposed light-setting call for an original fixed synthetic request; it never operates a device. The native recent window is 256 tokens plus pinned tool-schema sinks, distinct from the published positional capacity. Input token count is unknown, and native output is capped at the documented default of 256 tokens with incomplete responses rejected. Fit, speed and correctness are unmeasured.

Hardware
Intel Core i5-12400 desktop, 4 inference threads, 32 GiB RAM
Runtime
Official Needle2 engine 2.0.4 via private reader C-ABI research adapter Official engine 2.0.4; Python 3.12 ctypes; taskset four-logical-CPU affinity · CPU
Model format
model-bearing native wheel · Official embedded CQ mixed 2/4-bit, effective 2.2-bit metadata; no FP32 reimplementation · Published CQ mixed 2/4-bit weights, effective 2.2 bits; native KV/activation 8-bit
Workload
256 token context · 1 concurrent

Based on source documentation; this setup has not been executed by OpenTools. Memory: unknown. No compatible measurement available.

Read the complete setup guide

Before you start

  • Use an owned Intel Core i5-12400 desktop with Ubuntu 24.04 x86_64, 32 GiB RAM and 40 GiB free disk. Allocate four logical CPUs using the taskset affinity command below; native internal thread count is unknown. These are author planning allocations, not measured requirements.
  • Read the exact official Apache-2.0 license before download/use; preserve applicable notices. The selected snapshot is publicly readable without an access token.
  • Use the official engine 2.0.4 platform wheel below. Do not install mutable cactus-needle, import latest Needle3, use the older version-two default engine downloader, or replace this model with another Needle generation.
  • This is a fixed synthetic tool-call recommendation exercise. No devices, live services, user files, network tools, account credentials or proposed calls are executed. Python 3.12 stdlib drives the native ABI; no JAX, Torch or Hugging Face runtime package is needed.
  • The official Linux x86-64 platform is documented by the publisher. The native binary was not downloaded or executed by the author; target ABI, memory fit and performance remain unverified. Stop on loader or native status errors.

Exact model and requirements

Artifact:
Cactus Compute / Official Needle2 engine 2.0.4 Linux x86-64 model-bearing wheel · checkpoint Cactus-Compute/needle2
Publisher revision:
32e9e3a93b205f786929697446ae669cf0a84579
Artifact digest:
sha256:13a84e6c73095fd175b11d46a30a984b62123d94421b769c107074aff7f65c2b
Download size:
Unknown
Memory requirement (unknown):
Unknown — No measured peak for native engine, sinks, output buffer, Python and operating system.
System RAM / disk:
Unknown / Unknown
Placement:
single device. One CPU host; no model sharding. One native CPU engine; no GPU or disk offload.
KV cache:
Native eight-bit KV metadata; no author precision override
Workload:
256 token context; Unknown; Unknown; concurrency 1; batch 1; cache unknown; repetitions Unknown.
Tokenizer:
Engine-native Needle2 vocabulary 8192; no external tokenizer imported · Official engine 2.0.4 snapshot
Sampling / reasoning:
Native grammar-constrained completion with max_new_tokens 256 passed directly to C API. Publisher version-two Python API documents default max_new_tokens 256. No exposed temperature/top-p/seed override is invented. Reject negative, zero or cap-reaching count and incomplete JSON. Count is generated native tokens, not response bytes. Native tool-schema and user-input formatting, not chat/Jinja. Fixed original request asks to turn on living-room lights at brightness 30. A single declared tool constrains names; parser and reader check argument types and exact fixed-fixture meaning. Never execute tools. Context 256 denotes documented recent sliding window plus separately pinned tool sinks; published 2048 positional capacity is distinct. Exact input tokens and sink count remain unmeasured until reader execution. The 256-token completion cap is separate from the rolling recent window, not a total input-plus-output budget.
Runtime prerequisites:
Ubuntu 24.04 x86_64; See prerequisites above.

Official artifact · License

Official model snapshot and public runtime sources declare Apache-2.0. Retain the license, applicable copyright/patent/attribution notices and any applicable NOTICE; mark modifications when distributing. Patent termination and other license conditions apply; no trademark permission or warranty is granted. No calibration, correctness, safety or device-action authorization is implied. This guide uses an original synthetic fixture and executes no proposed tool calls.

  • Official selected wheel is 13,283,977 serialized bytes including model, executable engine and packaging. This is not standalone weight bytes, process RAM or evidence of byte equivalence to the separate CQ archive.
  • Publisher approximate 14 MB binary and 28 MB session RAM claims are not measurements for this exact guide. Four logical CPUs are an affinity allocation, not an observed native thread count. The 32 GiB RAM and 40 GiB disk allocations do not prove fit; speed remains unknown.

One-time setup and hardware

hardware
Unknown — Existing desktop purchase price and incremental hardware cost depend on the reader; unknown.
Existing desktop purchase price and incremental hardware cost depend on the reader; unknown. Tax: unknown.
setup
Unknown — Initial provisioning/build time and labor cost are unknown.
Initial provisioning/build time and labor cost are unknown. Tax: unknown.

Recurring costs

electricity
Unknown — Actual wall power, electricity rate and powered hours are unknown; no TDP-to-wall-power conversion.
Actual wall power, electricity rate and powered hours are unknown; no TDP-to-wall-power conversion. Tax: unknown.
storage
Unknown — Incremental cost of local disk capacity is unknown. The 40 GiB free-disk walkthrough allocation is an author choice, not a measured requirement.
Incremental cost of local disk capacity is unknown. The 40 GiB free-disk walkthrough allocation is an author choice, not a measured requirement. Tax: unknown.
egress
Unknown — Data transfer price, allowances and traffic volume are unknown.
Data transfer price, allowances and traffic volume are unknown. Tax: unknown.
administration
Unknown — Maintenance labor rate and hours are unknown.
Maintenance labor rate and hours are unknown. Tax: unknown.

Install the runtime

  1. 1. Prepare the CPU research host

    Use a dedicated directory and Ubuntu Python 3.12 plus standard C/C++ runtime libraries. There is no package installation from the model wheel and no build. System package revisions resolve at reader installation and should be recorded.

    Command
    set -e
    sudo apt-get update
    sudo apt-get install -y python3.12 curl ca-certificates openssl util-linux libc6 libstdc++6 libgcc-s1
    mkdir -p "$HOME/needle2-tool-call/engine"
    cd "$HOME/needle2-tool-call"
    python3.12 --version

    Expected result: Python 3.12 and standard runtime libraries are available; no model has run. util-linux provides taskset for an explicit four-logical-CPU scheduling boundary.

Download the model

  1. 1. Verify and extract the exact official engine artifact

    Download the official model-bearing wheel and exact configuration, license and header. Verify every file before opening the wheel. The pinned version-two fetch source names needle/libneedle.so; inspect that it is the unique library path, extract only that member, and record its derived hash locally. Do not import the wheel Python package, use its dependency resolver or download a separate CQ archive.

    Command
    set -e
    cd "$HOME/needle2-tool-call"
    curl --fail --location --output 'cactus_needle-2.0.4-py3-none-manylinux2014_x86_64.whl' 'https://huggingface.co/Cactus-Compute/needle2/resolve/32e9e3a93b205f786929697446ae669cf0a84579/python/cactus_needle-2.0.4-py3-none-manylinux2014_x86_64.whl'
    curl --fail --location --output NEEDLE2-LICENSE 'https://huggingface.co/Cactus-Compute/needle2/resolve/32e9e3a93b205f786929697446ae669cf0a84579/LICENSE'
    curl --fail --location --output config.json 'https://huggingface.co/Cactus-Compute/needle2/resolve/32e9e3a93b205f786929697446ae669cf0a84579/config.json'
    curl --fail --location --output needle.h 'https://huggingface.co/Cactus-Compute/needle2/resolve/32e9e3a93b205f786929697446ae669cf0a84579/linux-x86_64/needle.h'
    sha256sum --check <<'SHA256'
    13a84e6c73095fd175b11d46a30a984b62123d94421b769c107074aff7f65c2b  cactus_needle-2.0.4-py3-none-manylinux2014_x86_64.whl
    cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30  NEEDLE2-LICENSE
    df9fc3b605a7c2135303b5f2e164cf1f500e04956393c73a9596284914072295  config.json
    cb7a720eba7bc895b5bbfa5d6f6fc635906938fdc569eb2520a2c09ba06d9eed  needle.h
    SHA256
    python3.12 - <<'PYEXTRACT'
    import zipfile,pathlib,json,hashlib
    root=pathlib.Path.cwd()
    wheel=root/'cactus_needle-2.0.4-py3-none-manylinux2014_x86_64.whl'
    assert hashlib.sha256(wheel.read_bytes()).hexdigest()=='13a84e6c73095fd175b11d46a30a984b62123d94421b769c107074aff7f65c2b'
    cfg=json.load(open('config.json'))
    assert cfg['model_type']=='needle' and cfg['engine_version']=='2.0.4'
    with zipfile.ZipFile(wheel) as z:
        libs=[x for x in z.namelist() if x.endswith('/libneedle.so')]
        assert libs==['needle/libneedle.so'], 'stop on missing, duplicate or unexpected library path'
        data=z.read(libs[0])
    assert data[:4]==b'\x7fELF'
    p=root/'engine/libneedle.so'
    p.write_bytes(data)
    (root/'native-library-path.txt').write_text(str(p.resolve())+'\n')
    (root/'extracted-library.sha256').write_text(hashlib.sha256(data).hexdigest()+'  engine/libneedle.so\n')
    print('Verified official wheel; unique documented native library extracted without importing its package')
    PYEXTRACT
    sha256sum --check extracted-library.sha256

    Expected result: Verified official wheel contains the unique documented native library; a local derived library checksum is saved. No claim of equivalence to separate raw weights or CQ archive is made.

Configure

  1. 1. Write the private C-ABI adapter

    Reader-authored educational wrapper, not an official HTTP server. It uses the documented four-argument Needle2 completion ABI, one serial engine and reset plus initialization per request. Fixed synthetic input only, strict body/JSON framing and finite typed output. It never invokes a proposed function. Socket reads time out; native generation has no server deadline.

    Command
    cd "$HOME/needle2-tool-call"
    cat > reader-tool-call-server.py <<'PYWRAPPER'
    """Reader-authored fixed Needle2 C-ABI research endpoint; never executes tools."""
    import ctypes
    import hmac
    import json
    import math
    from http.server import BaseHTTPRequestHandler, HTTPServer
    from pathlib import Path
    ROOT=Path(__file__).resolve().parent
    TOOLS=[{'name':'set_lights','description':'Propose a room light setting; never operate a device','parameters':{'type':'object','properties':{'room':{'type':'string'},'on':{'type':'boolean'},'brightness':{'type':'integer'}},'required':['room','on','brightness']}}]
    PROMPT='Turn on the living room lights at brightness 30.'
    LIMIT=256
    
    def validate_native(raw, count, prefix):
        if type(count) is not int or count <= 0 or count >= LIMIT:
            raise ValueError('native error, empty generation or cap reached')
        if type(prefix) is not int or prefix < 0:
            raise ValueError('invalid prefix count')
        x=json.loads(raw, object_pairs_hook=object_pairs,parse_constant=lambda _: (_ for _ in ()).throw(ValueError('nonfinite')))
        if not isinstance(x,dict) or x.get('type')!='call':raise ValueError('expected call')
        calls=x.get('function_calls')
        if not isinstance(calls,list) or len(calls)!=1:raise ValueError('one call required')
        call=calls[0]
        if not isinstance(call,dict) or set(call)!={'name','arguments'} or call['name']!='set_lights':raise ValueError('call schema')
        args=call['arguments']
        if not isinstance(args,dict) or set(args)!={'room','on','brightness'}:raise ValueError('argument schema')
        if type(args['room']) is not str or args['room']!='living room' or type(args['on']) is not bool or type(args['brightness']) is not int or args['on'] is not True or args['brightness']!=30:raise ValueError('argument types or range')
        confidence=x.get('confidence')
        if confidence is not None and (type(confidence) not in (float,int) or not math.isfinite(confidence) or not 0<=confidence<=1):raise ValueError('confidence')
        return dict(fixture='synthetic-lights-v1',type='call',function_calls=calls,confidence=confidence,generated_tokens=count,output_cap=LIMIT,init_prefix_tokens=prefix,recent_window_tokens=256,input_tokens=None,tools_executed=False)
    
    class NativeEngine:
        def __init__(self,path):
            self.lib=ctypes.CDLL(str(path))
            self.lib.needle_init.argtypes=[ctypes.c_char_p,ctypes.c_char_p,ctypes.c_char_p]
            self.lib.needle_init.restype=ctypes.c_int
            self.lib.needle_complete.argtypes=[ctypes.c_char_p,ctypes.c_int,ctypes.c_char_p,ctypes.c_int]
            self.lib.needle_complete.restype=ctypes.c_int
            self.lib.needle_reset.argtypes=[];self.lib.needle_reset.restype=None
        def call(self):
            self.lib.needle_reset()
            prefix=self.lib.needle_init(b'',json.dumps(TOOLS,separators=(',',':')).encode(),None)
            if prefix<0:raise RuntimeError('native initialization failed')
            out=ctypes.create_string_buffer(16384)
            count=self.lib.needle_complete(PROMPT.encode(),LIMIT,out,len(out))
            if count<0:raise RuntimeError('native completion failed')
            blob=out.raw
            end=blob.find(b'\0')
            if end<0:raise RuntimeError('unterminated response')
            return validate_native(blob[:end].decode('utf-8'),count,prefix)
    
    def object_pairs(pairs):
        out = {}
        for k, v in pairs:
            if k in out:
                raise ValueError('duplicate key')
            out[k] = v
        return out
    
    
    def create_server(engine, key, port=8992):
        if len(key) < 32 or not key.isascii() or not key.isalnum():
            raise ValueError('key must be at least 32 ASCII alphanumeric characters')
        class Handler(BaseHTTPRequestHandler):
            protocol_version = 'HTTP/1.0'
            def setup(self):
                super().setup()
                self.connection.settimeout(5)
            def log_message(self, *_):
                pass
            def reply(self, code, obj):
                data = json.dumps(obj, allow_nan=False, separators=(',', ':')).encode()
                self.send_response(code)
                self.send_header('Content-Type', 'application/json')
                self.send_header('Content-Length', str(len(data)))
                self.send_header('Connection', 'close')
                self.end_headers()
                try:
                    self.wfile.write(data)
                except OSError:
                    pass
            def authorized(self):
                vals = self.headers.get_all('Authorization') or []
                if len(vals) != 1 or not hmac.compare_digest(vals[0].encode(), ('Bearer '+key).encode()):
                    self.reply(401, {'error': 'unauthorized'})
                    return False
                return True
            def do_GET(self):
                if not self.authorized(): return
                if self.path != '/health':
                    self.reply(404, {'error': 'not found'}); return
                self.reply(200, dict(status='ready',model='Needle2',engine='2.0.4',device='cpu',recent_window_tokens=256,input_tokens=None,output_cap=LIMIT,tools_executed=False))
            def do_POST(self):
                if not self.authorized(): return
                if self.path != '/v1/tool-call':
                    self.reply(404, {'error': 'not found'}); return
                if self.headers.get_all('Transfer-Encoding'):
                    self.reply(400, {'error': 'transfer encoding unsupported'}); return
                lens = self.headers.get_all('Content-Length') or []
                if len(lens) != 1 or not lens[0] or not lens[0].isascii() or not lens[0].isdigit():
                    self.reply(411, {'error': 'one ASCII content length required'}); return
                if len(lens[0]) > 4 or int(lens[0]) > 256:
                    self.reply(413, {'error': 'body too large'}); return
                if (self.headers.get_all('Content-Type') or []) != ['application/json']:
                    self.reply(415, {'error': 'application/json required'}); return
                try:
                    raw = self.rfile.read(int(lens[0]))
                    if len(raw) != int(lens[0]): raise ValueError('short body')
                    obj = json.loads(raw, object_pairs_hook=object_pairs, parse_constant=lambda _: (_ for _ in ()).throw(ValueError('nonfinite')))
                    if obj != {'fixture': 'synthetic-lights-v1'}: raise ValueError('fixed fixture only')
                except (ValueError, UnicodeError, OSError):
                    self.reply(400, {'error': 'invalid fixed-fixture request'}); return
                try:
                    payload = engine.call()
                except Exception:
                    self.reply(500, {'error': 'tool-call generation failed'}); return
                self.reply(200, payload)
        return HTTPServer(('127.0.0.1', port), Handler)
    
    
    def main():
        path=(ROOT/'native-library-path.txt').read_text().strip()
        library=Path(path).resolve(strict=True)
        assert library.is_relative_to((ROOT/'engine').resolve()) and library.name=='libneedle.so'
        engine=NativeEngine(library)
        # Initialize once for readiness; every actual request resets and initializes again.
        prefix=engine.lib.needle_init(b'',json.dumps(TOOLS,separators=(',',':')).encode(),None)
        if prefix<0:raise RuntimeError('native initialization failed')
        key=(ROOT/'server.key').read_text().strip()
        create_server(engine,key).serve_forever()
    if __name__=='__main__':main()
    PYWRAPPER
    sha256sum --check <<'SHA256'
    4c01e4621b6e4d467612b38ed3c885b29064ddb8b4db72dddce4bda82d1e4b23  reader-tool-call-server.py
    SHA256

    Expected result: Wrapper hash matches. Each native initialization or completion failure becomes a generic error; cap-reaching or unfinished responses fail closed.

Start the model

  1. 1. Load the native library and serve privately

    Run in the foreground and keep this terminal open. Preserve an existing key; an empty key file fails rather than rotates. Startup initialization must succeed before HTTP readiness. Select the first four currently allowed logical CPUs, fail if fewer are available, and apply taskset scheduling affinity. This does not configure or measure the opaque native internal thread count. No model-bearing artifact was executed by the author.

    Command
    cd "$HOME/needle2-tool-call"
    umask 077
    if [ ! -e server.key ]; then openssl rand -hex 32 > server.key; fi
    test -s server.key
    chmod 600 server.key
    CPU_SET=$(python3.12 -c 'import os; c=sorted(os.sched_getaffinity(0)); assert len(c)>=4; print(",".join(map(str,c[:4])))')
    taskset --cpu-list "$CPU_SET" python3.12 reader-tool-call-server.py

    Expected result: Official native library loads and initializes, then service binds only to loopback port 8992. Startup/fit are unmeasured. taskset restricts scheduling to four allowed logical CPUs; opaque native thread count is unknown.

Connect to your endpoint

http://127.0.0.1:8992/v1/tool-call

Same trusted desktop loopback only.

Access: loopback. Private Bearer key required for health and tool-call requests. Send a JSON object with fixture set to synthetic-lights-v1. Response contains a proposed call, optional unverified confidence, native generated and prefix token counts, unknown input count and an explicit false tools-executed flag. It is a custom research schema.

Verify the setup

  1. 1. Check authenticated readiness

    In a second server terminal, use the private key without printing it. Readiness follows native initialization; it is not a model-quality test.

    Command
    cd "$HOME/needle2-tool-call"
    NEEDLE_KEY=$(cat server.key)
    curl --fail --silent --show-error -H "Authorization: Bearer $NEEDLE_KEY" http://127.0.0.1:8992/health > health.json
    python3.12 - <<'PYHEALTH'
    import json
    x=json.load(open('health.json'))
    assert x['status']=='ready' and x['model']=='Needle2' and x['engine']=='2.0.4'
    assert x['recent_window_tokens']==256 and x['input_tokens'] is None and x['output_cap']==256 and x['tools_executed'] is False
    print('Private fixed-fixture engine initialized')
    PYHEALTH

    Expected result: Ready response reports exact engine version, recent window, unknown input count, output cap and no tool execution.

  2. 2. Check the grounded synthetic proposal

    The exact original prompt is embedded in the wrapper. Submit only its fixed selector. The intended result is one set_lights call for living room, on true and brightness 30. Native counts, finished JSON, shape, range and finite confidence checks must pass. This is an inspectable expected criterion, not an author-observed output or correctness guarantee.

    Command
    cd "$HOME/needle2-tool-call"
    NEEDLE_KEY=$(cat server.key)
    curl --fail --silent --show-error --max-time 120 -H "Authorization: Bearer $NEEDLE_KEY" -H 'Content-Type: application/json' --data '{"fixture":"synthetic-lights-v1"}' http://127.0.0.1:8992/v1/tool-call > tool-call.json
    python3.12 - <<'PYCHECK'
    import json,math
    x=json.load(open('tool-call.json'))
    assert x['fixture']=='synthetic-lights-v1' and x['type']=='call'
    assert x['function_calls']==[{'name':'set_lights','arguments':{'room':'living room','on':True,'brightness':30}}]
    assert x['tools_executed'] is False and x['input_tokens'] is None
    assert type(x['generated_tokens']) is int and 0<x['generated_tokens']<256
    assert type(x['init_prefix_tokens']) is int and x['init_prefix_tokens']>=0
    c=x['confidence']
    assert c is None or (type(c) in (int,float) and math.isfinite(c) and 0<=c<=1)
    print('Grounded light-setting proposal received as data; no device action executed')
    PYCHECK

    Expected result: Expected arguments and count checks pass without executing any call. If model output differs or reaches the cap, stop and retain evidence rather than relaxing the test. Client timeout is not native cancellation.

  3. 3. Check unauthorized access

    Health and tool calls both require the same Bearer key.

    Command
    cd "$HOME/needle2-tool-call"
    test "$(curl --silent --output unauthorized.json --write-out '%{http_code}' http://127.0.0.1:8992/health)" = 401
    python3.12 - <<'PYAUTH'
    import json
    assert json.load(open('unauthorized.json'))=={'error':'unauthorized'}
    print('Unauthenticated request rejected')
    PYAUTH

    Expected result: HTTP 401 and structured unauthorized JSON; no native completion triggered.

Performance evidence

No compatible measurement available. Documented support and estimated memory do not establish response speed.

Limits and troubleshooting

  • Only the official frozen Needle2 checkpoint identity and exact platform engine are used. The wheel is model-bearing; neither its size nor its digest establishes standalone weight bytes, raw-checkpoint equivalence, reproducible build identity or memory fit.
  • The model config identifies engine 2.0.4. Current upstream is Needle3 with a changed ABI; pinned version-two source also contains stale package and default-download versions. Use only the exact official wheel and four-argument header specified here. No mutable pip or default fetch occurs.
  • Native documented 256-token recent sliding window excludes separately pinned schema sinks; it is not a total prompt-plus-output budget. Configured 2048 positional capacity is a different quantity. Exact query/internal-format input count is unknown because the C API has no counting method. A fixed selector and 256-byte body cap do not enforce a complete token budget.
  • The native completion cap is 256 tokens. Its positive return reports generated tokens; negative is failure. The adapter conservatively rejects zero or cap-reaching counts, missing NUL termination, incomplete/duplicate-key/nonfinite JSON and invalid tool-call shapes or incorrect fixed-fixture room, on state or brightness. Reaching the cap can reject an otherwise complete response. No accuracy or completion-rate claim is made.
  • One process-global engine serves one serial fixed request at a time, resetting and initializing each independent call. No functions are executed, and no arbitrary prompts, schemas, paths, model choices, URLs or tool results are accepted. Multi-turn continuation and larger tool retrieval catalogues remain separate open jobs.
  • Confidence is optional unverified native output, not established calibration or permission to act. Grammar constraints and schema validation do not prove that arguments are correct. The check is a synthetic interface exercise; never use it as an unattended device controller.
  • Private educational HTTP wrapper has no TLS, inference deadline, request concurrency or public-service hardening. Socket read timeout and client timeout do not cancel native generation. Stop the foreground process if work hangs. No author model execution, speed, full-process RAM or target ABI validation occurred.
Library or ABI failure
Stop. Recheck the exact official wheel hash and unique extracted member; do not load latest Needle3 or a library from another platform. Required system runtime compatibility is unmeasured.
Invalid or cap-reaching native response
Retain the pinned setup and error evidence. The documented 256-token cap may be insufficient for the expected complete JSON; this guide does not claim measured completion. Do not execute partial calls or silently raise the cap.
Unexpected arguments
Treat the synthetic check as failed. No tool is executed. Source grammar conformance is not proof of the intended room, on state or brightness.
HTTP 401
Read the existing private server.key in the client shell and send one Bearer header; never print, commit or upload the key.
Timeout or insufficient resources
Stop the foreground process before retrying. Client timeout does not cancel native work, and planning allocations do not establish fit or performance.

Stop the model

  1. 1. Stop the native research process

    Press Ctrl+C in the foreground server terminal, then unset client keys and confirm the endpoint is gone.

    Command
    unset NEEDLE_KEY
    if curl --silent --max-time 2 http://127.0.0.1:8992/health >/dev/null; then echo "Port still responds; inspect the foreground process"; exit 1; fi

    Expected result: Foreground process stopped and loopback port no longer responds.

Uninstall

  1. 1. Remove dedicated research files

    Preserve desired results and license records first. Confirm this dedicated directory before removing it; shared Ubuntu packages remain.

    Command
    rm -r -- "$HOME/needle2-tool-call"

    Expected result: Official wheel, extracted library, derived checksum, configs, wrapper, key and results removed.

Sources and verification

Sources checked Oct 16, 2026. Verification source

Compare this configuration
Cloud serverDocumented setupSources checked Oct 16, 2026

Propose a fixed light-setting tool call with Needle2 on an AWS CPU VM

Use the exact official Needle2 engine 2.0.4 model-bearing Linux wheel through its native C API. A private serial endpoint returns a proposed light-setting call for an original fixed synthetic request; it never operates a device. The native recent window is 256 tokens plus pinned tool-schema sinks, distinct from the published positional capacity. Input token count is unknown, and native output is capped at the documented default of 256 tokens with incomplete responses rejected. Fit, speed and correctness are unmeasured.

Hardware
AWS r7i.xlarge, 32 GiB RAM, Ubuntu x86_64
Runtime
Official Needle2 engine 2.0.4 via private reader C-ABI research adapter Official engine 2.0.4; Python 3.12 ctypes; taskset four-logical-CPU affinity · CPU
Model format
model-bearing native wheel · Official embedded CQ mixed 2/4-bit, effective 2.2-bit metadata; no FP32 reimplementation · Published CQ mixed 2/4-bit weights, effective 2.2 bits; native KV/activation 8-bit
Workload
256 token context · 1 concurrent

Based on source documentation; this setup has not been executed by OpenTools. Memory: unknown. No compatible measurement available.

Read the complete setup guide

Before you start

  • Use an owned AWS r7i.xlarge Ubuntu 24.04 x86_64 VM in us-east-1 with 32 GiB RAM, four vCPUs and a chosen 40 GiB gp3 volume. Use existing SSH access; allow SSH only from your client and keep port 8992 closed in the security group. Provisioning is reader action and incurs the quoted and other applicable costs.
  • Read the exact official Apache-2.0 license before download/use; preserve applicable notices. The selected snapshot is publicly readable without an access token.
  • Use the official engine 2.0.4 platform wheel below. Do not install mutable cactus-needle, import latest Needle3, use the older version-two default engine downloader, or replace this model with another Needle generation.
  • This is a fixed synthetic tool-call recommendation exercise. No devices, live services, user files, network tools, account credentials or proposed calls are executed. Python 3.12 stdlib drives the native ABI; no JAX, Torch or Hugging Face runtime package is needed.
  • The official Linux x86-64 platform is documented by the publisher. The native binary was not downloaded or executed by the author; target ABI, memory fit and performance remain unverified. Stop on loader or native status errors.

Exact model and requirements

Artifact:
Cactus Compute / Official Needle2 engine 2.0.4 Linux x86-64 model-bearing wheel · checkpoint Cactus-Compute/needle2
Publisher revision:
32e9e3a93b205f786929697446ae669cf0a84579
Artifact digest:
sha256:13a84e6c73095fd175b11d46a30a984b62123d94421b769c107074aff7f65c2b
Download size:
Unknown
Memory requirement (unknown):
Unknown — No measured peak for native engine, sinks, output buffer, Python and operating system.
System RAM / disk:
Unknown / Unknown
Placement:
single device. One CPU host; no model sharding. One native CPU engine; no GPU or disk offload.
KV cache:
Native eight-bit KV metadata; no author precision override
Workload:
256 token context; Unknown; Unknown; concurrency 1; batch 1; cache unknown; repetitions Unknown.
Tokenizer:
Engine-native Needle2 vocabulary 8192; no external tokenizer imported · Official engine 2.0.4 snapshot
Sampling / reasoning:
Native grammar-constrained completion with max_new_tokens 256 passed directly to C API. Publisher version-two Python API documents default max_new_tokens 256. No exposed temperature/top-p/seed override is invented. Reject negative, zero or cap-reaching count and incomplete JSON. Count is generated native tokens, not response bytes. Native tool-schema and user-input formatting, not chat/Jinja. Fixed original request asks to turn on living-room lights at brightness 30. A single declared tool constrains names; parser and reader check argument types and exact fixed-fixture meaning. Never execute tools. Context 256 denotes documented recent sliding window plus separately pinned tool sinks; published 2048 positional capacity is distinct. Exact input tokens and sink count remain unmeasured until reader execution. The 256-token completion cap is separate from the rolling recent window, not a total input-plus-output budget.
Runtime prerequisites:
Ubuntu 24.04 x86_64; See prerequisites above.

Official artifact · License

Official model snapshot and public runtime sources declare Apache-2.0. Retain the license, applicable copyright/patent/attribution notices and any applicable NOTICE; mark modifications when distributing. Patent termination and other license conditions apply; no trademark permission or warranty is granted. No calibration, correctness, safety or device-action authorization is implied. This guide uses an original synthetic fixture and executes no proposed tool calls.

  • Official selected wheel is 13,283,977 serialized bytes including model, executable engine and packaging. This is not standalone weight bytes, process RAM or evidence of byte equivalence to the separate CQ archive.
  • Publisher approximate 14 MB binary and 28 MB session RAM claims are not measurements for this exact guide. Four logical CPUs are an affinity allocation, not an observed native thread count. The 32 GiB RAM and 40 GiB disk allocations do not prove fit; speed remains unknown.

One-time setup and hardware

hardware
Not applicable — AWS hardware acquisition is included in compute rental; no purchased GPU required. Any separate hardware cost is not applicable.
AWS hardware acquisition is included in compute rental; no purchased GPU required. Any separate hardware cost is not applicable. Tax: unknown.
setup
Unknown — Initial provisioning/build time and labor cost are unknown.
Initial provisioning/build time and labor cost are unknown. Tax: unknown.

Recurring costs

compute
$0.26 / hour
AWS public us-east-1 Linux On-Demand rate USD 0.2646 per billed instance-hour, no paid preinstalled software. Eight hours USD 2.1168; 24 hours USD 6.3504 compute. Idle running hours bill. Storage, egress, tax and administration separate; no Spot/reservation/discount assumed. Capacity/quota unverified. Region: us-east-1. SKU: r7i.xlarge Linux On-Demand. Tax: excluded. Price dated Oct 15, 2026. Price source
storage
$0.08 / GB month
US East (N. Virginia) gp3 USD 0.08 per GB-month; AWS defines GB as 1024^3 bytes. Selected 40 GiB root volume is USD 3.20 per full month, prorated by provisioned duration. Baseline 3000 IOPS / 125 MB/s; no extras assumed. Retained snapshots/resources separate; storage persists after instance stop. Tax excluded. Region: us-east-1. SKU: EBS gp3 baseline 3000 IOPS / 125 MB/s. Tax: excluded. Price dated Oct 16, 2026. Price source
egress
Unknown — Data transfer price, allowances and traffic volume are unknown.
Data transfer price, allowances and traffic volume are unknown. Tax: unknown.
administration
Unknown — Maintenance labor rate and hours are unknown.
Maintenance labor rate and hours are unknown. Tax: unknown.

Install the runtime

  1. 1. Prepare the CPU research host

    Use a dedicated directory and Ubuntu Python 3.12 plus standard C/C++ runtime libraries. There is no package installation from the model wheel and no build. System package revisions resolve at reader installation and should be recorded.

    Command
    set -e
    sudo apt-get update
    sudo apt-get install -y python3.12 curl ca-certificates openssl util-linux libc6 libstdc++6 libgcc-s1
    mkdir -p "$HOME/needle2-tool-call/engine"
    cd "$HOME/needle2-tool-call"
    python3.12 --version

    Expected result: Python 3.12 and standard runtime libraries are available; no model has run. util-linux provides taskset for an explicit four-logical-CPU scheduling boundary.

Download the model

  1. 1. Verify and extract the exact official engine artifact

    Download the official model-bearing wheel and exact configuration, license and header. Verify every file before opening the wheel. The pinned version-two fetch source names needle/libneedle.so; inspect that it is the unique library path, extract only that member, and record its derived hash locally. Do not import the wheel Python package, use its dependency resolver or download a separate CQ archive.

    Command
    set -e
    cd "$HOME/needle2-tool-call"
    curl --fail --location --output 'cactus_needle-2.0.4-py3-none-manylinux2014_x86_64.whl' 'https://huggingface.co/Cactus-Compute/needle2/resolve/32e9e3a93b205f786929697446ae669cf0a84579/python/cactus_needle-2.0.4-py3-none-manylinux2014_x86_64.whl'
    curl --fail --location --output NEEDLE2-LICENSE 'https://huggingface.co/Cactus-Compute/needle2/resolve/32e9e3a93b205f786929697446ae669cf0a84579/LICENSE'
    curl --fail --location --output config.json 'https://huggingface.co/Cactus-Compute/needle2/resolve/32e9e3a93b205f786929697446ae669cf0a84579/config.json'
    curl --fail --location --output needle.h 'https://huggingface.co/Cactus-Compute/needle2/resolve/32e9e3a93b205f786929697446ae669cf0a84579/linux-x86_64/needle.h'
    sha256sum --check <<'SHA256'
    13a84e6c73095fd175b11d46a30a984b62123d94421b769c107074aff7f65c2b  cactus_needle-2.0.4-py3-none-manylinux2014_x86_64.whl
    cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30  NEEDLE2-LICENSE
    df9fc3b605a7c2135303b5f2e164cf1f500e04956393c73a9596284914072295  config.json
    cb7a720eba7bc895b5bbfa5d6f6fc635906938fdc569eb2520a2c09ba06d9eed  needle.h
    SHA256
    python3.12 - <<'PYEXTRACT'
    import zipfile,pathlib,json,hashlib
    root=pathlib.Path.cwd()
    wheel=root/'cactus_needle-2.0.4-py3-none-manylinux2014_x86_64.whl'
    assert hashlib.sha256(wheel.read_bytes()).hexdigest()=='13a84e6c73095fd175b11d46a30a984b62123d94421b769c107074aff7f65c2b'
    cfg=json.load(open('config.json'))
    assert cfg['model_type']=='needle' and cfg['engine_version']=='2.0.4'
    with zipfile.ZipFile(wheel) as z:
        libs=[x for x in z.namelist() if x.endswith('/libneedle.so')]
        assert libs==['needle/libneedle.so'], 'stop on missing, duplicate or unexpected library path'
        data=z.read(libs[0])
    assert data[:4]==b'\x7fELF'
    p=root/'engine/libneedle.so'
    p.write_bytes(data)
    (root/'native-library-path.txt').write_text(str(p.resolve())+'\n')
    (root/'extracted-library.sha256').write_text(hashlib.sha256(data).hexdigest()+'  engine/libneedle.so\n')
    print('Verified official wheel; unique documented native library extracted without importing its package')
    PYEXTRACT
    sha256sum --check extracted-library.sha256

    Expected result: Verified official wheel contains the unique documented native library; a local derived library checksum is saved. No claim of equivalence to separate raw weights or CQ archive is made.

Configure

  1. 1. Write the private C-ABI adapter

    Reader-authored educational wrapper, not an official HTTP server. It uses the documented four-argument Needle2 completion ABI, one serial engine and reset plus initialization per request. Fixed synthetic input only, strict body/JSON framing and finite typed output. It never invokes a proposed function. Socket reads time out; native generation has no server deadline.

    Command
    cd "$HOME/needle2-tool-call"
    cat > reader-tool-call-server.py <<'PYWRAPPER'
    """Reader-authored fixed Needle2 C-ABI research endpoint; never executes tools."""
    import ctypes
    import hmac
    import json
    import math
    from http.server import BaseHTTPRequestHandler, HTTPServer
    from pathlib import Path
    ROOT=Path(__file__).resolve().parent
    TOOLS=[{'name':'set_lights','description':'Propose a room light setting; never operate a device','parameters':{'type':'object','properties':{'room':{'type':'string'},'on':{'type':'boolean'},'brightness':{'type':'integer'}},'required':['room','on','brightness']}}]
    PROMPT='Turn on the living room lights at brightness 30.'
    LIMIT=256
    
    def validate_native(raw, count, prefix):
        if type(count) is not int or count <= 0 or count >= LIMIT:
            raise ValueError('native error, empty generation or cap reached')
        if type(prefix) is not int or prefix < 0:
            raise ValueError('invalid prefix count')
        x=json.loads(raw, object_pairs_hook=object_pairs,parse_constant=lambda _: (_ for _ in ()).throw(ValueError('nonfinite')))
        if not isinstance(x,dict) or x.get('type')!='call':raise ValueError('expected call')
        calls=x.get('function_calls')
        if not isinstance(calls,list) or len(calls)!=1:raise ValueError('one call required')
        call=calls[0]
        if not isinstance(call,dict) or set(call)!={'name','arguments'} or call['name']!='set_lights':raise ValueError('call schema')
        args=call['arguments']
        if not isinstance(args,dict) or set(args)!={'room','on','brightness'}:raise ValueError('argument schema')
        if type(args['room']) is not str or args['room']!='living room' or type(args['on']) is not bool or type(args['brightness']) is not int or args['on'] is not True or args['brightness']!=30:raise ValueError('argument types or range')
        confidence=x.get('confidence')
        if confidence is not None and (type(confidence) not in (float,int) or not math.isfinite(confidence) or not 0<=confidence<=1):raise ValueError('confidence')
        return dict(fixture='synthetic-lights-v1',type='call',function_calls=calls,confidence=confidence,generated_tokens=count,output_cap=LIMIT,init_prefix_tokens=prefix,recent_window_tokens=256,input_tokens=None,tools_executed=False)
    
    class NativeEngine:
        def __init__(self,path):
            self.lib=ctypes.CDLL(str(path))
            self.lib.needle_init.argtypes=[ctypes.c_char_p,ctypes.c_char_p,ctypes.c_char_p]
            self.lib.needle_init.restype=ctypes.c_int
            self.lib.needle_complete.argtypes=[ctypes.c_char_p,ctypes.c_int,ctypes.c_char_p,ctypes.c_int]
            self.lib.needle_complete.restype=ctypes.c_int
            self.lib.needle_reset.argtypes=[];self.lib.needle_reset.restype=None
        def call(self):
            self.lib.needle_reset()
            prefix=self.lib.needle_init(b'',json.dumps(TOOLS,separators=(',',':')).encode(),None)
            if prefix<0:raise RuntimeError('native initialization failed')
            out=ctypes.create_string_buffer(16384)
            count=self.lib.needle_complete(PROMPT.encode(),LIMIT,out,len(out))
            if count<0:raise RuntimeError('native completion failed')
            blob=out.raw
            end=blob.find(b'\0')
            if end<0:raise RuntimeError('unterminated response')
            return validate_native(blob[:end].decode('utf-8'),count,prefix)
    
    def object_pairs(pairs):
        out = {}
        for k, v in pairs:
            if k in out:
                raise ValueError('duplicate key')
            out[k] = v
        return out
    
    
    def create_server(engine, key, port=8992):
        if len(key) < 32 or not key.isascii() or not key.isalnum():
            raise ValueError('key must be at least 32 ASCII alphanumeric characters')
        class Handler(BaseHTTPRequestHandler):
            protocol_version = 'HTTP/1.0'
            def setup(self):
                super().setup()
                self.connection.settimeout(5)
            def log_message(self, *_):
                pass
            def reply(self, code, obj):
                data = json.dumps(obj, allow_nan=False, separators=(',', ':')).encode()
                self.send_response(code)
                self.send_header('Content-Type', 'application/json')
                self.send_header('Content-Length', str(len(data)))
                self.send_header('Connection', 'close')
                self.end_headers()
                try:
                    self.wfile.write(data)
                except OSError:
                    pass
            def authorized(self):
                vals = self.headers.get_all('Authorization') or []
                if len(vals) != 1 or not hmac.compare_digest(vals[0].encode(), ('Bearer '+key).encode()):
                    self.reply(401, {'error': 'unauthorized'})
                    return False
                return True
            def do_GET(self):
                if not self.authorized(): return
                if self.path != '/health':
                    self.reply(404, {'error': 'not found'}); return
                self.reply(200, dict(status='ready',model='Needle2',engine='2.0.4',device='cpu',recent_window_tokens=256,input_tokens=None,output_cap=LIMIT,tools_executed=False))
            def do_POST(self):
                if not self.authorized(): return
                if self.path != '/v1/tool-call':
                    self.reply(404, {'error': 'not found'}); return
                if self.headers.get_all('Transfer-Encoding'):
                    self.reply(400, {'error': 'transfer encoding unsupported'}); return
                lens = self.headers.get_all('Content-Length') or []
                if len(lens) != 1 or not lens[0] or not lens[0].isascii() or not lens[0].isdigit():
                    self.reply(411, {'error': 'one ASCII content length required'}); return
                if len(lens[0]) > 4 or int(lens[0]) > 256:
                    self.reply(413, {'error': 'body too large'}); return
                if (self.headers.get_all('Content-Type') or []) != ['application/json']:
                    self.reply(415, {'error': 'application/json required'}); return
                try:
                    raw = self.rfile.read(int(lens[0]))
                    if len(raw) != int(lens[0]): raise ValueError('short body')
                    obj = json.loads(raw, object_pairs_hook=object_pairs, parse_constant=lambda _: (_ for _ in ()).throw(ValueError('nonfinite')))
                    if obj != {'fixture': 'synthetic-lights-v1'}: raise ValueError('fixed fixture only')
                except (ValueError, UnicodeError, OSError):
                    self.reply(400, {'error': 'invalid fixed-fixture request'}); return
                try:
                    payload = engine.call()
                except Exception:
                    self.reply(500, {'error': 'tool-call generation failed'}); return
                self.reply(200, payload)
        return HTTPServer(('127.0.0.1', port), Handler)
    
    
    def main():
        path=(ROOT/'native-library-path.txt').read_text().strip()
        library=Path(path).resolve(strict=True)
        assert library.is_relative_to((ROOT/'engine').resolve()) and library.name=='libneedle.so'
        engine=NativeEngine(library)
        # Initialize once for readiness; every actual request resets and initializes again.
        prefix=engine.lib.needle_init(b'',json.dumps(TOOLS,separators=(',',':')).encode(),None)
        if prefix<0:raise RuntimeError('native initialization failed')
        key=(ROOT/'server.key').read_text().strip()
        create_server(engine,key).serve_forever()
    if __name__=='__main__':main()
    PYWRAPPER
    sha256sum --check <<'SHA256'
    4c01e4621b6e4d467612b38ed3c885b29064ddb8b4db72dddce4bda82d1e4b23  reader-tool-call-server.py
    SHA256

    Expected result: Wrapper hash matches. Each native initialization or completion failure becomes a generic error; cap-reaching or unfinished responses fail closed.

Start the model

  1. 1. Load the native library and serve privately

    Run in the foreground and keep this terminal open. Preserve an existing key; an empty key file fails rather than rotates. Startup initialization must succeed before HTTP readiness. Select the first four currently allowed logical CPUs, fail if fewer are available, and apply taskset scheduling affinity. This does not configure or measure the opaque native internal thread count. No model-bearing artifact was executed by the author.

    Command
    cd "$HOME/needle2-tool-call"
    umask 077
    if [ ! -e server.key ]; then openssl rand -hex 32 > server.key; fi
    test -s server.key
    chmod 600 server.key
    CPU_SET=$(python3.12 -c 'import os; c=sorted(os.sched_getaffinity(0)); assert len(c)>=4; print(",".join(map(str,c[:4])))')
    taskset --cpu-list "$CPU_SET" python3.12 reader-tool-call-server.py

    Expected result: Official native library loads and initializes, then service binds only to loopback port 8992. Startup/fit are unmeasured. taskset restricts scheduling to four allowed logical CPUs; opaque native thread count is unknown.

Connect to your endpoint

http://127.0.0.1:8992/v1/tool-call

VM loopback only. Keep EC2 port 8992 closed. Use existing SSH access with ssh -N -L 8992:127.0.0.1:8992 USER@VM_IP if needed. Shown commands run on the VM; use a trusted SSH channel for any client key transfer.

Access: loopback. Private Bearer key required for health and tool-call requests. Send a JSON object with fixture set to synthetic-lights-v1. Response contains a proposed call, optional unverified confidence, native generated and prefix token counts, unknown input count and an explicit false tools-executed flag. It is a custom research schema.

Verify the setup

  1. 1. Check authenticated readiness

    In a second server terminal, use the private key without printing it. Readiness follows native initialization; it is not a model-quality test.

    Command
    cd "$HOME/needle2-tool-call"
    NEEDLE_KEY=$(cat server.key)
    curl --fail --silent --show-error -H "Authorization: Bearer $NEEDLE_KEY" http://127.0.0.1:8992/health > health.json
    python3.12 - <<'PYHEALTH'
    import json
    x=json.load(open('health.json'))
    assert x['status']=='ready' and x['model']=='Needle2' and x['engine']=='2.0.4'
    assert x['recent_window_tokens']==256 and x['input_tokens'] is None and x['output_cap']==256 and x['tools_executed'] is False
    print('Private fixed-fixture engine initialized')
    PYHEALTH

    Expected result: Ready response reports exact engine version, recent window, unknown input count, output cap and no tool execution.

  2. 2. Check the grounded synthetic proposal

    The exact original prompt is embedded in the wrapper. Submit only its fixed selector. The intended result is one set_lights call for living room, on true and brightness 30. Native counts, finished JSON, shape, range and finite confidence checks must pass. This is an inspectable expected criterion, not an author-observed output or correctness guarantee.

    Command
    cd "$HOME/needle2-tool-call"
    NEEDLE_KEY=$(cat server.key)
    curl --fail --silent --show-error --max-time 120 -H "Authorization: Bearer $NEEDLE_KEY" -H 'Content-Type: application/json' --data '{"fixture":"synthetic-lights-v1"}' http://127.0.0.1:8992/v1/tool-call > tool-call.json
    python3.12 - <<'PYCHECK'
    import json,math
    x=json.load(open('tool-call.json'))
    assert x['fixture']=='synthetic-lights-v1' and x['type']=='call'
    assert x['function_calls']==[{'name':'set_lights','arguments':{'room':'living room','on':True,'brightness':30}}]
    assert x['tools_executed'] is False and x['input_tokens'] is None
    assert type(x['generated_tokens']) is int and 0<x['generated_tokens']<256
    assert type(x['init_prefix_tokens']) is int and x['init_prefix_tokens']>=0
    c=x['confidence']
    assert c is None or (type(c) in (int,float) and math.isfinite(c) and 0<=c<=1)
    print('Grounded light-setting proposal received as data; no device action executed')
    PYCHECK

    Expected result: Expected arguments and count checks pass without executing any call. If model output differs or reaches the cap, stop and retain evidence rather than relaxing the test. Client timeout is not native cancellation.

  3. 3. Check unauthorized access

    Health and tool calls both require the same Bearer key.

    Command
    cd "$HOME/needle2-tool-call"
    test "$(curl --silent --output unauthorized.json --write-out '%{http_code}' http://127.0.0.1:8992/health)" = 401
    python3.12 - <<'PYAUTH'
    import json
    assert json.load(open('unauthorized.json'))=={'error':'unauthorized'}
    print('Unauthenticated request rejected')
    PYAUTH

    Expected result: HTTP 401 and structured unauthorized JSON; no native completion triggered.

Performance evidence

No compatible measurement available. Documented support and estimated memory do not establish response speed.

Limits and troubleshooting

  • Only the official frozen Needle2 checkpoint identity and exact platform engine are used. The wheel is model-bearing; neither its size nor its digest establishes standalone weight bytes, raw-checkpoint equivalence, reproducible build identity or memory fit.
  • The model config identifies engine 2.0.4. Current upstream is Needle3 with a changed ABI; pinned version-two source also contains stale package and default-download versions. Use only the exact official wheel and four-argument header specified here. No mutable pip or default fetch occurs.
  • Native documented 256-token recent sliding window excludes separately pinned schema sinks; it is not a total prompt-plus-output budget. Configured 2048 positional capacity is a different quantity. Exact query/internal-format input count is unknown because the C API has no counting method. A fixed selector and 256-byte body cap do not enforce a complete token budget.
  • The native completion cap is 256 tokens. Its positive return reports generated tokens; negative is failure. The adapter conservatively rejects zero or cap-reaching counts, missing NUL termination, incomplete/duplicate-key/nonfinite JSON and invalid tool-call shapes or incorrect fixed-fixture room, on state or brightness. Reaching the cap can reject an otherwise complete response. No accuracy or completion-rate claim is made.
  • One process-global engine serves one serial fixed request at a time, resetting and initializing each independent call. No functions are executed, and no arbitrary prompts, schemas, paths, model choices, URLs or tool results are accepted. Multi-turn continuation and larger tool retrieval catalogues remain separate open jobs.
  • Confidence is optional unverified native output, not established calibration or permission to act. Grammar constraints and schema validation do not prove that arguments are correct. The check is a synthetic interface exercise; never use it as an unattended device controller.
  • Private educational HTTP wrapper has no TLS, inference deadline, request concurrency or public-service hardening. Socket read timeout and client timeout do not cancel native generation. Stop the foreground process if work hangs. No author model execution, speed, full-process RAM or target ABI validation occurred.
Library or ABI failure
Stop. Recheck the exact official wheel hash and unique extracted member; do not load latest Needle3 or a library from another platform. Required system runtime compatibility is unmeasured.
Invalid or cap-reaching native response
Retain the pinned setup and error evidence. The documented 256-token cap may be insufficient for the expected complete JSON; this guide does not claim measured completion. Do not execute partial calls or silently raise the cap.
Unexpected arguments
Treat the synthetic check as failed. No tool is executed. Source grammar conformance is not proof of the intended room, on state or brightness.
HTTP 401
Read the existing private server.key in the client shell and send one Bearer header; never print, commit or upload the key.
Timeout or insufficient resources
Stop the foreground process before retrying. Client timeout does not cancel native work, and planning allocations do not establish fit or performance.

Stop the model

  1. 1. Stop the native research process

    Press Ctrl+C in the foreground server terminal, then unset client keys and confirm the endpoint is gone. Stopping this process does not stop VM billing.

    Command
    unset NEEDLE_KEY
    if curl --silent --max-time 2 http://127.0.0.1:8992/health >/dev/null; then echo "Port still responds; inspect the foreground process"; exit 1; fi

    Expected result: Foreground process stopped and loopback port no longer responds.

Uninstall

  1. 1. Remove dedicated research files

    Preserve desired results and license records first. Confirm this dedicated directory before removing it; shared Ubuntu packages remain.

    Command
    rm -r -- "$HOME/needle2-tool-call"

    Expected result: Official wheel, extracted library, derived checksum, configs, wrapper, key and results removed.

Shut down cloud resources

  1. 1. End cloud billing resources

    In EC2 us-east-1, stop for later reuse or terminate this exact VM. Review Delete on termination for its root volume. Delete unwanted detached EBS volumes, snapshots and reserved/Elastic/public IP resources. Stopped VM storage and retained resources may still bill. Check Bills/Cost Explorer later for final usage.

    Expected result: Exact instance is stopped/terminated as intended; no unwanted attached/retained billed resources remain. Instance compute stops only after its lifecycle transition, not when the Python service stops.

Sources and verification

Sources checked Oct 16, 2026. Verification source

Compare this configuration

Benchmarks

Performance scores for Cactus Needle 2 across standard benchmarks.

Mobile Actions Ordered Strict Exact Matchcactus-compute
63.7%
BFCL v4 Single-Turn Overallcactus-compute
42.6%

Pricing and access

Apache 2.0 model artifact; runtime cost depends on local hardware, not token API pricing.

Cactus Compute logo

On-device AI with cloud fallback

6 MCP Servers2 ModelsFounded 2023San Francisco, United States
View full profile

AI Models by Cactus Compute

AI models from the same organization.

ModelContext WindowPrice (In / Out per M)
NeedleCurrent---- / --

MCP Servers by Cactus Compute

Connect this tool to AI assistants via the Model Context Protocol.