Quick Start
1
Limit parallel runs of an agent
Control how many instances of the same agent can run concurrently:
2
Same, async
Use async context for better resource utilization:
3
Bound tool time with ToolConfig
Prevent slow tools from blocking agent execution:
How It Works
Sync vs Async Rule
The concurrency registry enforces strict separation between sync and async contexts:
Example error:
Tool Timeout Behavior
Whentool_config=ToolConfig(timeout=...) is set, tools run in a dedicated executor with these characteristics:
In YAML the field name is still tool_timeout:; in Python use tool_config=ToolConfig(timeout=…).
Timeout Return Shape
On timeout, each layer surfaces the timeout differently:Effective Timeout Precedence
Whentool_timeout values are declared, the wrapper resolves each agent’s budget independently:
- CLI wins for every agent. An explicit
--tool-timeout Non the command line (orcli_config={"tool_timeout": N}when embedding) is used verbatim for all agents. - Uniform declared values take the shared-wrap fast path. When every agent under
roles:andagents:declarestool_timeoutand every declared value is identical, a single guard wraps the shared tool dict. If any agent omits the field, the shared wrap is skipped and the per-agent resolver runs instead. - Heterogeneous per-agent values are honoured per agent. When agents declare different values, each agent’s tools carry its own budget (tool objects stay shared; only the guard closure differs). The tightest value no longer collapses onto every agent. Agents that omit
tool_timeoutget no wrap (they do not inherit another agent’s declared value). - Otherwise, no wrapping. If nothing declares a timeout, tools run without wrapper-layer enforcement (the SDK executor-layer enforcement still applies if
tool_config=ToolConfig(timeout=…)is set in Python).
AgentsGenerator._resolve_uniform_tool_timeout(config); heterogeneous budgets use make_agent_tool_wrap_resolver(config) / resolve_agent_tool_timeout(agent_key, config) — see praisonai/agents_generator.py.
Executor Details
- One executor per
Agentinstance (lazy creation) max_workers=2threads per agent- Thread name prefix:
tool-<agent_name>— useful for log filtering - Reused across calls — no resource leak
- Recycled on timeout — a tool that hangs past
tool_timeoutis not reclaimable, so the executor isshutdown(wait=False)and the next call gets a fresh worker
Self-healing after a hang. The pool has 2 workers by default; before PR #3960, two consecutive hangs would deadlock the pool because
future.cancel() cannot stop a thread that has already started. Now the executor is recycled on timeout — the next call starts on a fresh worker — so a hung tool cannot progressively degrade throughput toward a deadlock. Recycling is bounded so repeated timeouts cannot leak an unbounded number of stuck threads.Common Patterns
Limit FastAPI Route Concurrency
Async Context Manager Helper
Timeout Selection by Tool Type
Best Practices
Always release in finally blocks
Always release in finally blocks
Prevents deadlocks when exceptions occur:
Don't mix sync and async acquire
Don't mix sync and async acquire
Keep acquisition method consistent with execution context:
Set tool_timeout for network tools
Set tool_timeout for network tools
Any tool that does network IO should have a timeout:
Use thread names for debugging
Use thread names for debugging
Filter logs by agent name using the thread prefix:
Retries
Tool failures can be automatically retried using the retry policy feature. This works alongside timeouts to handle transient errors:ToolConfig.parallel is a deprecated alias for ExecutionConfig.parallel_tool_calls. Enable parallel tool calls with execution=ExecutionConfig(parallel_tool_calls=True) alongside ToolConfig for timeout and retry. Do not set both spellings to conflicting values \u2014 that raises TypeError.Parallel tool calls inside one async turn
A singleastart(...) turn can itself dispatch multiple independent tool calls concurrently when parallel_tool_calls=True.
asyncio.gather(agent.astart(a), agent.astart(b)) runs two agents in parallel, while parallel_tool_calls=True runs several tools inside one agent turn in parallel.
Write-conflict guard for shell-like tools
Whenparallel_tool_calls=True batches two or more tool calls in one turn, PraisonAI runs them sequentially instead of concurrently if any pair could touch the same file. As of PraisonAI PR #4907, the guard also catches shell-like tools whose write target lives in a command string rather than a path/file_path argument — execute_command, acp_execute_command, and execute_code. Two calls to any of these in the same batch, or one of them alongside any other write, force sequential fallback. Path-only reads (read_file, list tools, etc.) still run concurrently.
Related
Tool Retry Policy
Automatically retry failed tool calls with exponential backoff
Tool Configuration
Tool timeout settings and performance tuning
Async Bridge
Safe sync↔async boundary crossing utilities
Thread Safety
Chat history and state protection mechanisms

