# How to Handle Multiple Needle Agents in the Same Process: Complete Guide

> Discover how to effectively manage multiple Needle agents in a single Python process. Learn to run unlimited independent agents, each with its own state and tools, while sharing a thread-safe client.

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

---

**You can safely create unlimited independent Needle `Agent` instances within a single Python process, where each agent maintains its own configuration, tools, and state while sharing a thread-safe HTTP client.**

Handling multiple Needle agents in the same process is straightforward because the **Agent** abstraction is intentionally lightweight. According to the cactus-compute/needle source code, every `Agent` encapsulates its own runtime parameters—model name, temperature, max tokens, and tool registry—without requiring heavy per-instance resources. This design makes it practical to run specialized agents side-by-side, whether you need different personalities, distinct tool sets, or concurrent execution for performance.

## How Multiple Agents Work Internally

The Needle architecture separates agent-level configuration from shared infrastructure. Understanding this helps you use multiple agents effectively without unexpected coupling.

### Agent Lifecycle and Model Runners

In [`needle/agent/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/__init__.py), each `Agent` instance holds a reference to a **model runner** (`needle.model.run.Runner`). The runner is created lazily on first use, and multiple agents can reference the same underlying model class while retaining independent runtime parameters. This means `temperature`, `max_tokens`, and similar settings are isolated per agent without duplicating model code.

### Per-Instance Tool Registration

Tools are bound to specific agents, not shared globally. The `@agent.tool` decorator in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) generates JSON schemas and stores them in the calling agent's private `_tool_registry`. This ensures that when `agent_math` has an `add` function and `agent_chat` has an `echo` function, neither agent can accidentally invoke the other's tools.

### Shared, Thread-Safe HTTP Client

The `httpx.AsyncClient` used for LLM API calls is instantiated once per process. This client is safe for concurrent async calls from multiple threads or asyncio tasks, so you never need manual locking when agents run in parallel.

## Synchronous Usage: Independent Agents Side-by-Side

The simplest pattern creates agents inline and invokes them sequentially. Each agent maintains completely separate tool sets and conversational state.

```python
from needle.agent import Agent

# Create two independent agents with different configurations

agent_a = Agent(name="alice", model="gpt-4o-mini", temperature=0.7)
agent_b = Agent(name="bob", model="gpt-4o-mini", temperature=0.2)

# Register tools to specific agents only

@agent_a.tool
def greet(name: str) -> str:
    """Return a friendly greeting."""
    return f"Hello, {name}!"

@agent_b.tool
def calculate(a: int, b: int) -> int:
    """Add two numbers."""
    return a + b

# Invoke independently—agent_a cannot see `calculate`, agent_b cannot see `greet`

resp_a = agent_a.run("Introduce yourself and greet the user named 'Sam'.")
resp_b = agent_b.run("What is 3 + 4?")

print(resp_a)
print(resp_b)  # Returns 7 via the `calculate` tool

```

The `temperature` difference means `agent_a` produces more creative output while `agent_b` stays more deterministic, even with the same underlying model.

## Asynchronous Usage: Concurrent Agent Execution

For I/O-bound workloads, run multiple Needle agents concurrently using `asyncio`. The shared HTTP client handles parallel requests efficiently without blocking.

```python
import asyncio
from needle.agent import Agent

async def main():
    agent_x = Agent(name="x", model="gpt-4")
    agent_y = Agent(name="y", model="gpt-4")

    # Launch both agents concurrently

    task1 = asyncio.create_task(agent_x.run_async("Explain recursion."))
    task2 = asyncio.create_task(agent_y.run_async("Summarize the plot of *Inception*."))

    result_x, result_y = await asyncio.gather(task1, task2)
    print("X:", result_x)
    print("Y:", result_y)

asyncio.run(main())

```

Use `.run_async()` for asyncio contexts and `.run()` for synchronous code. Both methods are safe to call from the same process because the underlying `httpx.AsyncClient` manages connection pooling automatically.

## Context Managers for Clean Resource Management

When agents are short-lived, use them as context managers to ensure proper cleanup of async resources.

```python
from needle.agent import Agent

with Agent(name="temp1") as a1, Agent(name="temp2") as a2:
    r1 = a1.run("What time is it in Tokyo?")
    r2 = a2.run("Translate 'good night' to French.")
    print(r1, r2)

# Exiting the `with` block triggers automatic teardown

```

This pattern is ideal for request-scoped agents in web applications or batch job workers.

## Sharing Models While Isolating Tools

A common pattern reuses the same LLM across specialized agents with distinct capabilities. This reduces memory overhead while maintaining clear separation of concerns.

```python
from needle.agent import Agent

shared_model = "gpt-4o-mini"

agent_math = Agent(name="math", model=shared_model)
agent_chat = Agent(name="chat", model=shared_model)

@agent_math.tool
def add(a: int, b: int) -> int:
    """Add two integers."""
    return a + b

@agent_chat.tool
def echo(message: str) -> str:
    """Echo the input unchanged."""
    return message

# Each agent only sees its own registered tools

print(agent_math.run("Add 5 and 7."))      # Calls `add`, returns 12

print(agent_chat.run("Repeat: hello!"))    # Calls `echo`, returns "hello!"

```

Even with identical `model` arguments, the agents operate independently. The tool registries are strictly per-instance as implemented in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py).

## Configuration Checklist for Multi-Agent Programs

| Configuration | Where to Set | Why It Matters |
|-------------|------------|--------------|
| **Model name** | `Agent(model="...")` constructor | Different agents can use different LLM versions or providers. |
| **Tool set** | `@agent.tool` decorator after agent creation | Ensures tools attach to exactly one agent. |
| **API style** | `.run_async()` or `.run()` | Match your program's concurrency model; both are safe. |
| **Mutable state** | Avoid sharing globals across agents | Each agent's message history and caches are isolated by design. |
| **Authentication** | Environment variables (`OPENAI_API_KEY`, etc.) | Set once globally; the shared HTTP client reads them automatically. |

## Source Files Reference

These files define the multi-agent behavior described above:

- **[`needle/agent/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/__init__.py)** — `Agent` class definition, model runner integration, and async helper methods.
- **[`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py)** — `@tool` decorator implementation and per-agent `_tool_registry` management.
- **[`needle/cli.py`](https://github.com/cactus-compute/needle/blob/main/needle/cli.py)** — Minimal single-agent setup useful as a reference implementation.

## Summary

- **Create freely**: `Agent` instances are lightweight—no significant per-agent overhead.
- **Isolate deliberately**: Tools, temperature, and message history are per-agent by design.
- **Concurrency safe**: The shared `httpx.AsyncClient` supports async and threaded usage without locks.
- **Model sharing**: Multiple agents can use the same `model` string with different configurations.
- **Clean teardown**: Use context managers for temporary agents in server or worker contexts.

## Frequently Asked Questions

### How many agents can I create in one process?

There is no hard limit. Each `Agent` stores only configuration, a tool registry, and message history—typically kilobytes of memory. The shared HTTP client handles connection pooling, so you can scale to hundreds or thousands of agents limited only by your application's memory and the API provider's rate limits.

### Can two agents share the same tools?

Not directly. Tools are registered per-agent in `_tool_registry`. To share tool logic, define the function once and register it to multiple agents with separate `@agent.tool` decorators. Each agent receives its own JSON schema copy, so modifications to one agent's tools never affect another.

### What happens if I call the same agent from multiple threads?

The `Agent` class itself is not thread-safe for concurrent modification. Multiple threads can safely call the same agent's methods only if you serialize access (e.g., with locks). For actual parallelism, create separate `Agent` instances per thread or use `asyncio.gather()` with distinct agents, which the HTTP client handles correctly.

### Do I need separate API keys for each agent?

No. The shared `httpx.AsyncClient` reads environment variables like `OPENAI_API_KEY` once at process startup. All agents automatically use these credentials. If you need per-agent authentication (uncommon), you would need to modify the runner initialization in [`needle/model/run.py`](https://github.com/cactus-compute/needle/blob/main/needle/model/run.py).