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

> Easily migrate your Python projects to Needle. Learn the step by step process to install cactus needle decorate functions and replace your execution loop with the Needle agent.

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

---

**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`](https://github.com/cactus-compute/needle) repository.

---

## Step 1: Add Needle as a Dependency

Install Needle via pip or Poetry:

```bash
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`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py). Import the two classes you'll need:

```python
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:**

```python
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:**

```python
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`](https://github.com/cactus-compute/needle/blob/main/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`](https://github.com/cactus-compute/needle/blob/main/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

```python
agent = Needle(tools=[run_my_tool])

```

**With system prompt and custom weights:**

```python
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)](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`](https://github.com/cactus-compute/needle/blob/main/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

```python
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]` |

```python
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`](https://github.com/cactus-compute/needle/blob/main/.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)](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:

```python

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

# 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`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) | `Needle` class, `@tool` decorator, `run` method | [View source](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) |
| [`needle/config.py`](https://github.com/cactus-compute/needle/blob/main/needle/config.py) | Configuration loading ([`.needle.yaml`](https://github.com/cactus-compute/needle/blob/main/.needle.yaml), env vars) | [View source](https://github.com/cactus-compute/needle/blob/main/needle/config.py) |
| [`doc/apis.md`](https://github.com/cactus-compute/needle/blob/main/doc/apis.md) | Constructor arguments, `run` signature, parameters | [View docs](https://github.com/cactus-compute/needle/blob/main/doc/apis.md) |
| [`doc/finetuning.md`](https://github.com/cactus-compute/needle/blob/main/doc/finetuning.md) | Creating `.cact` weight files for `weights=` | [View docs](https://github.com/cactus-compute/needle/blob/main/doc/finetuning.md) |
| [`llms.txt`](https://github.com/cactus-compute/needle/blob/main/llms.txt) | API cheat-sheet for IDE autocompletion | [View file](https://github.com/cactus-compute/needle/blob/main/llms.txt) |

---

## 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`](https://github.com/cactus-compute/needle/blob/main/.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`](https://github.com/cactus-compute/needle/blob/main/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+.