> ## Documentation Index
> Fetch the complete documentation index at: https://praison.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Agent Cloning

> Safely clone agents for multi-channel, multi-tenant, or per-session isolation

Clone agents to give each channel, tenant, or session its own isolated instance — without losing config or hitting `RLock` pickling errors.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent

base = Agent(name="Support", instructions="Help customers consistently.")
clone = base.clone(name="Support-Telegram")
clone.start("Hello from Telegram")
```

The user deploys one base agent to several channels; each clone keeps the same instructions with isolated session state.

<Note>
  **Fix reference:** [PraisonAI PR #3769](https://github.com/MervinPraison/PraisonAI/pull/3769) — `clone_for_channel()` now isolates memory per clone so one channel's history never leaks into another.
</Note>

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    subgraph "Agent Cloning"
        Base[🤖 Base Agent] --> Clone1[📱 Telegram Agent]
        Base --> Clone2[💬 Discord Agent]
        Base --> Clone3[📺 Slack Agent]
    end
    
    classDef agent fill:#8B0000,color:#fff
    classDef tool fill:#189AB4,color:#fff
    
    class Base agent
    class Clone1,Clone2,Clone3 tool
```

## Quick Start

<Steps>
  <Step title="Single clone">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    from praisonaiagents import Agent

    base = Agent(
        name="Support",
        instructions="You are a helpful support agent",
        llm="gpt-4o-mini",
    )

    # Get an isolated copy for one channel
    telegram_agent = base.clone_for_channel()
    ```
  </Step>

  <Step title="Multiple clones for multi-channel deployment">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    from praisonaiagents import Agent

    base = Agent(
        name="Support",
        instructions="You are a helpful support agent",
        llm="gpt-4o-mini",
        tools=["internet_search"],
        memory=True,
    )

    # Create isolated agents for each channel
    telegram_agent = base.clone_for_channel()
    discord_agent  = base.clone_for_channel()
    slack_agent    = base.clone_for_channel()
    ```
  </Step>
</Steps>

***

## How It Works

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant User
    participant Code
    participant Agent
    participant Clone
    
    User->>Code: agent.clone_for_channel()
    Code->>Agent: build clone_kwargs
    Agent->>Clone: fresh locks + interrupt controller
    Clone-->>Code: isolated Agent
    Code-->>User: returns isolated Agent
```

Agent cloning creates a fresh instance with the same configuration but isolated state.

***

## What gets cloned vs. what's reset

| Attribute                                                                                                                                                                  | Behaviour in clone                                                                                                                                                                                                  |
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`, `role`, `goal`, `backstory`, `instructions`                                                                                                                        | Copied as-is                                                                                                                                                                                                        |
| `llm`, `base_url`, `api_key`, `auth`                                                                                                                                       | Copied as-is                                                                                                                                                                                                        |
| `tools`                                                                                                                                                                    | Shallow-copied (`list(self.tools)`)                                                                                                                                                                                 |
| `handoffs`                                                                                                                                                                 | Reset to `None` (channels should not share handoffs)                                                                                                                                                                |
| Feature configs: `knowledge`, `planning`, `reflection`, `web`, `context`, `autonomy`, `output`, `execution`, `templates`, `caching`, `hooks`, `skills`, `learn`, `sandbox` | Forwarded from the stored `_<name>_config` attributes                                                                                                                                                               |
| `guardrails`, `approval`                                                                                                                                                   | Forwarded from the stored `_<name>_config` attributes (**since PraisonAI [#4285](https://github.com/MervinPraison/PraisonAI/pull/4285)** — earlier releases dropped both silently on the clone; see the Note below) |
| `memory=MemoryConfig(...)` / `memory=db("…")` / `memory={"…": …}`                                                                                                          | Config re-resolved per clone → **isolated backend**                                                                                                                                                                 |
| `memory=<live Memory / FileMemory instance>`                                                                                                                               | Deep-copied (or rebuilt from `type(mem)(mem.cfg)`); on failure clones share it and a `logging.warning` is emitted                                                                                                   |
| `tool_config` (which holds `timeout`, `retry_policy`, `parallel`), `runtime` / `_runtime_config`                                                                           | Forwarded                                                                                                                                                                                                           |
| `cli_backend`                                                                                                                                                              | Forwarded (**deprecated** — use `runtime` instead)                                                                                                                                                                  |
| `interrupt_controller`                                                                                                                                                     | **Fresh instance** (no cross-channel interference)                                                                                                                                                                  |
| `__cache_lock` (`threading.RLock`)                                                                                                                                         | **Fresh instance**                                                                                                                                                                                                  |
| `_cost_lock` (`threading.Lock`)                                                                                                                                            | **Fresh instance**                                                                                                                                                                                                  |

<Note>
  **Per-clone memory isolation (fixed in PraisonAI #3769):** `clone_for_channel()` now guarantees each clone gets its own memory store when memory is configured via `MemoryConfig`/`db()`/dict. If you passed a **live** `Memory`/`FileMemory` instance instead, the clone rebuilds a fresh backend from its stored config; if that isn't possible, the clones share the backend and a `WARNING` is logged: `"clone_for_channel: could not isolate the live memory backend; clones will share it. Pass memory via MemoryConfig/db() for per-channel isolation."` For guaranteed per-channel/per-user isolation, prefer `MemoryConfig` or `db("sqlite:./channel.db")` over a shared live backend.
</Note>

<Note>
  **Per-clone sandbox isolation (fixed in PraisonAI 826c2e6 + 9d6f6dc):** `clone_for_channel()` now reads `sandbox_config` correctly (previously it read `_sandbox_config`, which is never assigned, so every clone silently ran unisolated). Framework-generated `execute_python_code` / `execute_shell_command` tools are tagged with `_praison_sandbox_tool = True` and stripped from the clone before regeneration, so each clone gets its own `SandboxManager` — no shared isolation state across channels. User-defined tools with those same names lack the tag and are preserved unchanged.
</Note>

<Note>
  **Per-clone guardrail and approval forwarding (fixed in PraisonAI [#4285](https://github.com/MervinPraison/PraisonAI/pull/4285), closes [#4284](https://github.com/MervinPraison/PraisonAI/issues/4284)):** `clone_for_channel()` now forwards the configured guardrail and approval policy to every clone. Earlier releases silently dropped both — so an operator who set a PII/secret/profanity guardrail or a `read_only`/`safe`/`full` preset (or a full `ApprovalConfig`) on an agent and then *served* it got **no output guardrail** and **no approval policy** on the clone. This mattered because `clone_for_channel()` is the code path that serves untrusted traffic — one clone per channel in the [bot gateway](/docs/features/bot-gateway) (Slack/Telegram/webhook) and one clone per HTTP call in the invoke API. The policy now travels with the clone; upgrading requires no code change.
</Note>

<Note>
  **Per-clone `as_tool()` rebinding (PraisonAI PR #4146):** `clone_for_channel()` now swaps every `as_tool()`-derived callable back for its source `Handoff` before regeneration, so each clone rebinds it to itself. Without this, the closure would capture the source agent's `chat_history`/`tools`/`memory` and every channel would silently delegate through the source's state. The rebinding is tracked via a `_praison_handoff_source` attribute on the generated closure; user-defined tools with the same name lack the attribute and are preserved unchanged.
</Note>

***

## Memory isolation

Per-channel clones need per-channel memory — otherwise one user's chat history and long-term memory leaks into another. `clone_for_channel()` handles this automatically.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    subgraph "Isolated Memory per Clone"
        Base[🤖 Base Agent] --> C1[📱 Telegram Clone]
        Base --> C2[💬 Discord Clone]
        C1 --> M1[🗄️ Own Memory Store]
        C2 --> M2[🗄️ Own Memory Store]
    end

    classDef agent fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef clone fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef store fill:#10B981,stroke:#7C90A0,color:#fff

    class Base agent
    class C1,C2 clone
    class M1,M2 store
```

**Recommended (fully isolated):** pass memory as a config, not a live instance. Each clone re-resolves the config into its own fresh backend.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent, MemoryConfig

base = Agent(
    name="Support",
    instructions="Help customers.",
    memory=MemoryConfig(provider="mem0"),   # or a db() config, or a dict
)

telegram = base.clone_for_channel()  # its own mem0 backend
discord  = base.clone_for_channel()  # its own mem0 backend, no cross-talk
```

**Live-instance memory** works too — the clone rebuilds a fresh backend of the same type per clone (via `deepcopy`, or `type(mem)(mem.cfg)` as a fallback). If isolation is genuinely impossible (a custom backend without a `cfg` attribute), the clone logs a warning and falls back to sharing so `clone_for_channel()` never hard-fails.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent
from praisonaiagents.memory import Memory

base = Agent(
    name="Support",
    instructions="Help customers.",
    memory=Memory(cfg=my_cfg),   # live instance
)

telegram = base.clone_for_channel()  # fresh Memory(cfg=my_cfg) per clone
```

<Warning>
  If you see the log line `clone_for_channel: could not isolate the live memory backend; clones will share it`, switch to a `MemoryConfig` / `db()` / dict config to guarantee isolation.
</Warning>

***

## Common Patterns

### Per-channel clone in a custom gateway

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent

def create_channel_agents(base_config):
    base = Agent(**base_config)
    
    return {
        'telegram': base.clone_for_channel(),
        'discord': base.clone_for_channel(),
        'slack': base.clone_for_channel()
    }

agents = create_channel_agents({
    'name': 'Support',
    'instructions': 'You are a helpful support agent',
    'llm': 'gpt-4o-mini'
})
```

### Guardrails and approval travel with each clone

A base agent's guardrail and approval policy are preserved on every per-channel clone, so served traffic stays policy-enforced (since PraisonAI [#4285](https://github.com/MervinPraison/PraisonAI/pull/4285)).

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent

def reject_pii(output):
    """Refuse output that contains PII."""
    return False, "contains PII"

# One base agent, served on many channels
base = Agent(
    name="Support",
    instructions="Help customers.",
    llm="gpt-4o-mini",
    guardrails=reject_pii,     # output validation
    approval="read_only",      # only read-only tools
)

# The bot gateway calls clone_for_channel() per channel — both policies travel with the clone
telegram = base.clone_for_channel()
discord  = base.clone_for_channel()

assert telegram._guardrail_fn is not None        # guardrail preserved
assert telegram._approval_config == "read_only"  # approval preserved
```

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant Op as Operator
    participant Base as Base Agent
    participant GW as Bot Gateway
    participant Clone as Per-channel Clone
    participant User as Untrusted User

    Op->>Base: Agent(guardrails=reject_pii, approval="read_only")
    Op->>GW: serve base agent on Slack/Telegram/webhook
    GW->>Base: clone_for_channel() per channel
    Base->>Clone: constructor with forwarded guardrails + approval
    Note over Clone: policies travel with the clone (PR #4285)
    User->>Clone: message via channel
    Clone->>Clone: run guardrail + approval checks
    Clone-->>User: policy-enforced response

    %% classDef agent fill:#8B0000,stroke:#7C90A0,color:#fff
    %% classDef tool fill:#189AB4,stroke:#7C90A0,color:#fff
```

### Per-tenant clone in a multi-tenant API

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent

class TenantAgentManager:
    def __init__(self, base_agent):
        self.base = base_agent
        self.tenant_agents = {}
    
    def get_agent_for_tenant(self, tenant_id):
        if tenant_id not in self.tenant_agents:
            self.tenant_agents[tenant_id] = self.base.clone_for_channel()
        return self.tenant_agents[tenant_id]

base = Agent(name="Assistant", instructions="You are helpful")
manager = TenantAgentManager(base)

# Each tenant gets isolated agent
agent_a = manager.get_agent_for_tenant("tenant_a")
agent_b = manager.get_agent_for_tenant("tenant_b")
```

### Use with copy.deepcopy

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
import copy
from praisonaiagents import Agent

agent = Agent(name="Test", instructions="You are a test agent")

# Both methods work - agent supports deepcopy now
clone1 = agent.clone_for_channel()  # Preferred for channels
clone2 = copy.deepcopy(agent)       # General Python compatibility
```

***

## Best Practices

<AccordionGroup>
  <Accordion title="Prefer clone_for_channel() over copy.deepcopy() for channels">
    `clone_for_channel()` is the supported path for creating channel-safe clones. It's optimized for multi-channel scenarios and properly handles handoffs. `__deepcopy__` is provided for general Python compatibility but isn't specifically designed for channel isolation.
  </Accordion>

  <Accordion title="Don't share handoffs across channels">
    Clone drops `handoffs` by design. Each channel should have independent routing logic. If you need cross-channel handoffs, implement them at the gateway level rather than the agent level.
  </Accordion>

  <Accordion title="Tools are shallow-copied">
    If a tool holds mutable per-channel state, wrap it in a factory function or use instance-based tools. The clone shares tool instances with the original agent, which is usually fine for stateless tools.
  </Accordion>

  <Accordion title="Prefer MemoryConfig / db() over a live backend when cloning">
    A `MemoryConfig` or `db("…")` is a *description* of a backend — each clone builds its own from it. A live `Memory(...)` instance is a *single store* passed by reference; even after #3769 we fall back to sharing it if we can't rebuild. Users who want a guaranteed independent store per Telegram/Discord/tenant clone should pass config, not an instance.

    When you clone with `MemoryConfig(backend=..., config={...})`, each clone rebuilds its own store using the same backend — the outer `backend=` is preserved even when a `config=` dict is supplied.
  </Accordion>
</AccordionGroup>

***

## Related

<CardGroup cols={2}>
  <Card title="Bot Gateway" icon="server" href="/docs/features/bot-gateway">
    Multi-channel gateway using agent cloning
  </Card>

  <Card title="Thread Safety" icon="shield" href="/docs/features/thread-safety">
    Agent thread safety and concurrency
  </Card>
</CardGroup>
