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
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.

Diagram

Agent Cloning

🤖 Base Agent

📱 Telegram Agent

💬 Discord Agent

📺 Slack Agent

Quick Start

  1. Single clone
    python
    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()
  2. Multiple clones for multi-channel deployment
    python
    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()

How It Works

Diagram
CloneAgentCodeUserCloneAgentCodeUseragent.clone_for_channel()build clone_kwargsfresh locks + interrupt controllerisolated Agentreturns isolated Agent

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


What gets cloned vs. what’s reset

AttributeBehaviour in clone
name, role, goal, backstory, instructionsCopied as-is
llm, base_url, api_key, authCopied as-is
toolsShallow-copied (list(self.tools))
handoffsReset to None (channels should not share handoffs)
Feature configs: knowledge, planning, reflection, web, context, autonomy, output, execution, templates, caching, hooks, skills, learn, sandboxForwarded from the stored _<name>_config attributes
guardrails, approvalForwarded from the stored _<name>_config attributes (since PraisonAI #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_configForwarded
cli_backendForwarded (deprecated — use runtime instead)
interrupt_controllerFresh instance (no cross-channel interference)
__cache_lock (threading.RLock)Fresh instance
_cost_lock (threading.Lock)Fresh instance
LLM._current_agent_name_var (per-agent ContextVar)Fresh instance (no cross-clone bleed; PR #5332)

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.

Diagram

Isolated Memory per Clone

🤖 Base Agent

📱 Telegram Clone

💬 Discord Clone

🗄️ Own Memory Store

🗄️ Own Memory 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
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
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

Common Patterns

Per-channel clone in a custom gateway

python
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).

python
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
Diagram
Untrusted UserPer-channel CloneBot GatewayBase AgentOperatorUntrusted UserPer-channel CloneBot GatewayBase AgentOperatorpolicies travel with the clone (PRAgent(guardrails=reject_pii, approval=”read_only”)serve base agent on Slack/Telegram/webhookclone_for_channel() per channelconstructor with forwarded guardrails + approvalmessage via channelrun guardrail + approval checkspolicy-enforced response

Per-tenant clone in a multi-tenant API

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

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.

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.

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.

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.


Related