RLock pickling errors.
Fix reference: PraisonAI PR #3769 —
clone_for_channel() now isolates memory per clone so one channel’s history never leaks into another.Quick Start
1
Single clone
2
Multiple clones for multi-channel deployment
How It Works
Agent cloning creates a fresh instance with the same configuration but isolated state.What gets cloned vs. what’s reset
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.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.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.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.
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.
Common Patterns
Per-channel clone in a custom gateway
Per-tenant clone in a multi-tenant API
Use with copy.deepcopy
Best Practices
Prefer clone_for_channel() over copy.deepcopy() for channels
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.Tools are shallow-copied
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
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.Related
Bot Gateway
Multi-channel gateway using agent cloning
Thread Safety
Agent thread safety and concurrency

