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.
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.
Quick Start
- 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() - 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
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 — 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 |
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.
Recommended (fully isolated): pass memory as a config, not a live instance. Each clone re-resolves the config into its own fresh backend.
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-talkLive-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.
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 cloneCommon Patterns
Per-channel clone in a custom gateway
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).
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 preservedPer-tenant clone in a multi-tenant API
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
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 compatibilityBest 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.