How to Initialize the Needle Agent with Custom Tools and System Information

Pass a list of callables or Pydantic models to the tools parameter and a string to the system parameter when instantiating the Needle class from needle/__init__.py.

The Needle SDK provides a lightweight Python interface to Cactus Compute's native inference engine. You customize an agent's capabilities and behavior by defining external functions the model can invoke and by conditioning its responses through a system prompt. This article shows how to configure both, with references to the actual source implementation in the cactus-compute/needle repository.


Overview of the Needle Constructor

The Needle class in [needle/__init__.py](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) accepts several parameters that control initialization:

Parameter Type Purpose
tools list[Callable | BaseModel | dict] Functions, Pydantic models, or raw JSON schemas describing available tools
system str System prompt encoding the agent's persona and constraints
weights str | Path | None Path to a fine-tuned .cact file for custom model weights
tool_index_path str | None Pre-built tool index for large tool collections
buffer_size int Response buffer size in bytes (default: 32768)

The constructor performs three core operations: tool resolution, encoding, and engine binding.


Step 1: Define Custom Tools

Tools are Python functions decorated with @tool or Pydantic models that generate JSON schemas. The @tool decorator is implemented in [needle/agent/tools.py](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py).

Using the @tool Decorator

from needle import tool, Field

@tool
def translate(text: str, target_lang: str = "es") -> str:
    """Translate *text* into *target_lang* (default Spanish)."""
    return f"[{target_lang}] {text}"

The @tool decorator calls build_schema to generate a JSON schema from the function signature and attaches it as _needle_tool.

Using Field for Rich Metadata

def summarize(
    text: str,
    max_sentences: int = Field(default=3, description="Maximum number of sentences")
):
    """Return a short summary of *text*."""
    return " … ".join(text.split(".")[:max_sentences])

Field objects enrich parameter metadata with defaults, descriptions, and constraints that appear in the generated schema.

Raw Pydantic Models or Dictionaries

You can also pass:

  • Pydantic models — converted via pydantic_schema in tools.py
  • Raw dictionaries — used verbatim as JSON schemas

Step 2: Create a System Prompt

The system parameter conditions the model's behavior. It is UTF-8 encoded and passed directly to the native engine:

system_prompt = """
You are a precise technical assistant. When calling tools, explain your reasoning
before invoking the function. Keep responses concise and factual.
"""

In [needle/__init__.py](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py), this becomes:

self._system = (system or "").encode("utf-8")

If omitted, the system prompt defaults to an empty string "".


Step 3: Initialize the Needle Agent

Combine tools and system prompt in the constructor:

from needle import Needle

agent = Needle(
    tools=[translate, summarize],
    system=system_prompt,
)

Internal Processing Flow

  1. _resolve iterates tools — Each entry is classified:

    • Callables: wrapped by @tool, schema built via build_schema
    • Pydantic models: schema generated via pydantic_schema
    • Dictionaries: stored as-is

    Resolved schemas populate self._functions (callable storage) and self._tools_json (serialized JSON).

  2. Byte encodingsystem and tools JSON are converted to UTF-8.

  3. Engine initialization_bind loads the native library and calls needle_init with:

    • self._system (encoded system prompt)
    • self._tools_json (tools schema)
    • Optional tool_index_path

Complete Working Example


# example.py

from needle import Needle, tool, Field

# Define tools

@tool
def translate(text: str, target_lang: str = "es") -> str:
    """Translate *text* into *target_lang*."""
    return f"[{target_lang}] {text}"

def summarize(
    text: str,
    max_sentences: int = Field(default=3, description="Maximum sentences")
):
    """Summarize *text* to *max_sentences* sentences."""
    return " … ".join(text.split(".")[:max_sentences])

# System prompt with persona

system_prompt = """You are a helpful assistant that explains tool usage."""

# Initialize agent

agent = Needle(
    tools=[translate, summarize],
    system=system_prompt,
)

# Execute query that may trigger tools

result = agent.run(
    query="Translate and summarize: AI transforms industries."
)
print(result)

The run method drives the completion loop: it calls needle_complete, executes any returned tool calls, and iterates until the engine produces a final response.


Advanced: Fine-Tuned Weights and Tool Indexing

Custom Weights

Pass a .cact file to use fine-tuned parameters instead of the default engine:

agent = Needle(
    tools=[my_tool],
    weights="./models/custom.cact",
)

This triggers FineTuneWorker from [needle/_worker.py](https://github.com/cactus-compute/needle/blob/main/needle/_worker.py) rather than loading the standard native library.

Pre-Built Tool Index

For large tool collections, provide a pre-computed index:

agent = Needle(
    tools=extensive_tool_list,
    tool_index_path="./indices/tools.idx",
    buffer_size=65536,  # Larger buffer for verbose tools

)

Summary

  • Tool definitions go to the tools parameter as decorated functions, Pydantic models, or raw schemas — processed by _resolve and stored in self._functions.
  • System prompts shape model behavior via the system parameter, UTF-8 encoded and forwarded to needle_init.
  • Engine binding happens automatically in _bind, selecting between standard library loading or FineTuneWorker for custom weights.
  • Tool execution is handled by agent.run(), which manages the completion loop and callback invocation.

Frequently Asked Questions

Can I pass regular Python functions without the @tool decorator?

Yes, but they must have type annotations. The Needle constructor's _resolve method will wrap un-decorated callables with @tool automatically. However, explicit decoration is recommended for clarity and to leverage Field metadata.

What happens if I omit the system parameter?

The agent initializes with an empty system prompt (""). According to the source in needle/__init__.py, this is encoded as UTF-8 bytes and passed to needle_init without modification. The model will use default behavior without persona-conditioning.

How are tool schemas generated from Pydantic models?

The _resolve method detects BaseModel subclasses and calls pydantic_schema from needle/agent/tools.py. This extracts field types, defaults, and descriptions into JSON Schema format, stored alongside the model class in self._functions.

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 →