How to Migrate Existing Projects to Needle: A Step-by-Step Guide

To migrate an existing Python project to Needle, install cactus-needle, decorate your functions with @tool, and replace your custom execution loop with the Needle(tools=[...]).run() agent.

Needle is a lightweight Python library that transforms ordinary functions into tool-calling agents powered by the 45M-parameter Needle 2 model. If your project currently implements its own "run a command → get output" loop, you can modernize it by leveraging Needle's automatic schema generation, model-driven tool selection, and structured result handling.

This guide walks through the exact steps to migrate your codebase, with code examples and source references from the cactus-compute/needle repository.


Step 1: Add Needle as a Dependency

Install Needle via pip or Poetry:

pip install cactus-needle

# or

poetry add cactus-needle

No additional runtime components are required—the engine binary downloads automatically on first use. For CI environments, cache this binary to speed up repeated test runs.


Step 2: Import the Core Needle API

The public API lives in needle/__init__.py. Import the two classes you'll need:

from needle import Needle, tool
  • Needle: The agent class that orchestrates tool calls and model inference
  • tool: A decorator that converts Python functions into schema-backed tools

Step 3: Expose Existing Functions as Tools

Replace your manually constructed JSON schemas with the @tool decorator. The decorator introspects function signatures and docstrings to build the schema automatically.

Before migration:

import subprocess

def run_my_tool(input_path: str) -> str:
    """Execute the external binary `my-tool`."""
    proc = subprocess.run(
        ["my-tool", "--input", input_path],
        capture_output=True,
        text=True,
        check=True,
    )
    return proc.stdout

After migration:

from needle import tool

@tool
def run_my_tool(input_path: str) -> str:
    """Execute the external binary `my-tool`.

    Args:
        input_path: Path to the input file.
    Returns:
        The tool's stdout as a string.
    """
    import subprocess
    proc = subprocess.run(
        ["my-tool", "--input", input_path],
        capture_output=True,
        text=True,
        check=True,
    )
    return proc.stdout

The decorator definition in needle/__init__.py extracts:

  • Parameter names and types from the function signature
  • Descriptions from the docstring's Args section
  • Return type information from the Returns section

Step 4: Create a Needle Agent

Instantiate Needle with your tool list. The constructor signature in needle/__init__.py accepts:

  • tools: List of decorated functions
  • system (optional): Context string injected into the model
  • weights (optional): Path to a LoRA-tuned .cact file
agent = Needle(tools=[run_my_tool])

With system prompt and custom weights:

agent = Needle(
    tools=[run_my_tool],
    system="date: 2026-08-27; locale: en-US; device: laptop",
    weights="my_finetuned.cact",  # from Needle's fine-tuning pipeline

)

The system string provides contextual facts the model can reference. The weights argument loads domain-specific behavior without code changes—see [doc/finetuning.md](https://github.com/cactus-compute/needle/blob/main/doc/finetuning.md) for tuning instructions.


Step 5: Replace Your Custom Execution Loop

Call agent.run() instead of managing tool dispatch manually. The run method in needle/__init__.py implements the full agentic loop:

  1. Model inference to select the appropriate tool
  2. Python function execution
  3. Result feeding back into context
  4. Final answer generation
response = agent.run(
    query="Process the file data.txt and give me a summary.",
    max_steps=4,           # limit tool calls

    max_new_tokens=256,    # cap response length

)

Step 6: Handle Structured Results

The run method returns a dictionary with two keys:

Key Description Type
output Final text answer from the model str
results List of tool calls made (name, arguments, output) list[dict]
print("Final answer:", response["output"])
print("Tool calls made:", response["results"])

Use response["results"] exactly as you previously used stdout/stderr—it's a structured, validated replacement for ad-hoc parsing.


Optional: Configure Authentication

For remote services like Hugging Face model downloads, Needle reads credentials from:

  1. A .needle.yaml file in your project root
  2. The NEEDLE_API_KEY environment variable

See [needle/config.py](https://github.com/cactus-compute/needle/blob/main/needle/config.py) for implementation details.


Complete Migration Example

Here's the full before-and-after comparison:


# ============================================================

# AFTER: Needle-powered agent

# ============================================================

from needle import Needle, tool

@tool
def run_my_tool(input_path: str) -> str:
    """Execute the external binary `my-tool`."""
    import subprocess
    proc = subprocess.run(
        ["my-tool", "--input", input_path],
        capture_output=True,
        text=True,
        check=True,
    )
    return proc.stdout

agent = Needle(tools=[run_my_tool])

response = agent.run(
    query="Process data.txt and summarize.",
    max_steps=4,
    max_new_tokens=256,
)

print(response["output"])

Key Source Files for Migration

File Purpose Direct Link
needle/__init__.py Needle class, @tool decorator, run method View source
needle/config.py Configuration loading (.needle.yaml, env vars) View source
doc/apis.md Constructor arguments, run signature, parameters View docs
doc/finetuning.md Creating .cact weight files for weights= View docs
llms.txt API cheat-sheet for IDE autocompletion View file

Summary

  • Install Needle with pip install cactus-needle
  • Decorate functions with @tool to auto-generate JSON schemas
  • Instantiate Needle(tools=[...]) with your tool set
  • Replace custom loops with agent.run(query, max_steps=..., max_new_tokens=...)
  • Consume structured results via response["output"] and response["results"]
  • Configure auth via .needle.yaml or NEEDLE_API_KEY if needed

After migration, your project gains consistent JSON-validated tool calls, automatic session history, and optional LoRA fine-tuning for domain-specific behavior.


Frequently Asked Questions

How do I migrate multiple existing functions at once?

Group them into a list and pass to Needle(tools=[...]). Each function needs its own @tool decorator. According to the source code in needle/__init__.py, the constructor accepts any iterable of decorated callables—there's no limit on tool count.

Can I keep my existing function logic unchanged?

Yes. The @tool decorator is non-invasive—it wraps your function without modifying its behavior. Your original logic, error handling, and return types remain intact. The decorator only adds schema generation metadata for the model.

Do I need to rewrite my CI/CD pipeline?

No major changes required. The Needle engine binary auto-downloads on first use. For faster CI runs, cache the binary between jobs. No additional Docker images or system packages are needed beyond Python 3.8+.

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 →