Documentation Index
Fetch the complete documentation index at: https://docs.praison.ai/llms.txt
Use this file to discover all available pages before exploring further.
Thread-Safe Agent State
PraisonAI Agents v0.5.0+ includes thread-safe management of chat history and caches; PR #1567 makes the underlying lock re-entrant and adds a per-event-loop async lock.Thread-Safe Components
Chat History
Thechat_history property is now fully thread-safe with automatic locking. The SDK protects chat history mutations through internal helper methods and a locked setter:
What changed in PR #1488
Prior to PR #1488, chat_history mutations bypassed thread-safety locks at 31+ call sites. The SDK now uses internal helper methods that properly acquire locks:
_append_to_chat_history(message)- Thread-safe message appending_truncate_chat_history(length)- Thread-safe history truncation_replace_chat_history(new_history)- Thread-safe full replacementchat_historysetter now acquires theAsyncSafeStatelock for assignments
What changed in PR #1514
PR #1514 enhanced thread-safety in three key areas:1. Locked Memory Initialization:
Task.initialize_memory() now uses threading.Lock with double-checked locking pattern. A new async variant initialize_memory_async() uses asyncio.Lock and offloads construction with asyncio.to_thread() to prevent event loop blocking.2. Async-Locked Workflow State: New _set_workflow_finished(value) method uses async locks to safely update workflow completion status across concurrent tasks.3. Non-Mutating Task Context: Task execution no longer mutates task.description during runs. Per-execution context is stored in _execution_context field, keeping the user-facing task.description stable across multiple executions.Safe operations
Caches
Internal caches usethreading.RLock for reentrant locking:
_system_prompt_cache- Cached system prompts_formatted_tools_cache- Cached tool definitions
Rate Limiter
RateLimiter can be shared across threads and agents. Both the sync and async method families are fully locked — see Rate Limiter → Thread Safety & Multi-Agent Use for patterns.
LiteAgent Thread Safety
The lite package also provides thread-safe operations:Implementation Details
Lock Types
| Component | Lock Type | Reason |
|---|---|---|
chat_history | AsyncSafeState (DualLock: RLock + per-loop asyncio.Lock) | Re-entrant on same thread; non-blocking in async contexts |
_cache_lock | threading.RLock | Allows reentrant access from cached helpers |
PersistenceOrchestrator._cache_lock | threading.RLock | Protects _session_cache reads/writes; reads return deepcopy() to prevent shared mutable state |
RateLimiter (sync) | threading.Lock | Protects _tokens, _api_tokens, and refill state from races in multi-threaded acquire calls |
RateLimiter (async) | asyncio.Lock | Same protection for coroutine contexts |
Lock Usage Pattern
Why a re-entrant lock?
Nested calls (e.g. a helper that holds the lock and then assignschat_history, which itself acquires the lock) used to deadlock. RLock permits the same thread to re-enter. See PR #1567 for details.
Persistence Orchestrator session cache
PersistenceOrchestrator now guards its in-memory session cache with threading.RLock and returns deep copies on read, so concurrent agents can share an orchestrator without corrupting cached ConversationSession objects.
Reference: PraisonAI PR #1609. The session cache uses defensive copying to prevent shared mutable state between concurrent operations.
Best Practices
Do: Use Agent Methods
Don’t: Bypass the Property Interface
Reads and full replacements via
agent.chat_history = [...] are now safe out-of-the-box. The wrapper is only needed for custom compound operations that require atomic read-modify-write sequences.Do: Clear History Safely
Async Considerations
agent.chat_history is async-aware out of the box — no external asyncio.Lock is required when all calls are inside the same event loop.
Verifying Thread Safety
Test thread safety with concurrent access:Multi-team HTTP launch
PraisonAI provides comprehensive thread-safety for HTTP server deployment:- Multiple
Agent/Agentsinstances may call.launch(port=N)concurrently from different threads — registration is atomic. - If two launch calls use the same path on the same port, the second gets an auto-suffixed path (
/path_abc123) and a warning is logged. - Server readiness is signalled deterministically (no fixed sleep);
.launch()returns only after the port is accepting connections. The wait defaults to 5 seconds and is configurable via thePRAISONAI_SERVER_READY_TIMEOUTenvironment variable. If the server doesn’t become ready in time,.launch()still returns and a warning is logged — check server logs for startup errors. aworkflow()state lock is created inside the running async context, so workflows remain stable when invoked under pytest-asyncio or when nested inside another loop.
Wrapper-layer thread safety (praisonai package)
The praisonai wrapper layer (distinct from the praisonaiagents content above) provides thread-safe OpenAI client management and CLI command discovery.
Per-instance OpenAI client lifecycle
EachBaseAutoGenerator owns its own OpenAI client — no cross-instance sharing, no LRU eviction surprises.
The client is created lazily on first structured-completion call. The instance’s
__del__ makes a best-effort client.close(), but the canonical path is explicit close() on the generator if you build one directly. This matters in long-lived server processes that spawn many generators.Thread-safe Typer command discovery
Embeddingpython -m praisonai from multiple threads is now safe. The CLI command discovery uses a double-check lock pattern and doesn’t poison the cache on failure:
Failure-safe cache
A transient discovery error does not lock the CLI into a broken state — the next call retries instead of permanently breaking dispatch. This ensures reliable operation in multi-threaded server environments where temporary import failures might occur.New Thread-Safe Components in PR #1548
AsyncAgentScheduler is now loop-aware. Thestart() method binds its async primitives (asyncio.Event, asyncio.Lock) to the running loop, and stop() raises RuntimeError if called from a different loop than start().
Lazy loaders in praisonai/auto.py are now thread-safe. A single _load_optional(key, loader) helper with a module-level lock replaces the previous unguarded module-level globals.
inbuilt_tools lazy import (PR #1681) now routes through praisonai.auto._load_optional("inbuilt_autogen_tools", ...) instead of a hand-rolled re-entry guard. Negative results are cached, so a missing crewai or autogen install no longer pays the find_spec cost on every attribute access. PRAISONAI_TOOLS_AVAILABLE is now resolved lazily via __getattr__.
Integration registry (praisonai/integrations/registry.py) now has a per-instance threading.Lock guarding register/unregister/create/list_registered operations.
New Thread-Safe Components in PR #1673
InMemoryJobStore — locked reads and asyncget_stats()
All read methods (get, get_by_idempotency_key, list_jobs, count, get_stats) now hold an asyncio.Lock while reading internal dicts, so concurrent saves cannot tear a read.
AgentScheduler — interruptible retry backoff (sync scheduler)
stop() now becomes responsive within milliseconds even during retry backoff. The sync scheduler also adopts the shared backoff_delay() curve so sync and async retries are identical.
ToolRegistry now holds a threading.Lock around all reads and mutations, matching PluginRegistry / integration registry. Eliminates RuntimeError: dictionary changed size during iteration when registering tools concurrently with iteration. Reference: PR #1673.

