Skip to main content
Concurrency controls let you limit parallel agent execution and set timeouts for tool calls to prevent resource exhaustion.
The user runs parallel work; concurrency controls cap simultaneous operations and tool timeouts.

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:
Calling acquire_sync() from an async context raises RuntimeError("acquire_sync('<agent_name>') cannot be called with a running event loop; use async acquire() in async contexts."). Use await acquire() instead.
Example error:

Tool Timeout Behavior

When tool_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

When tool_timeout values are declared, the wrapper resolves each agent’s budget independently:
  1. CLI wins for every agent. An explicit --tool-timeout N on the command line (or cli_config={"tool_timeout": N} when embedding) is used verbatim for all agents.
  2. Uniform declared values take the shared-wrap fast path. When every agent under roles: and agents: declares tool_timeout and 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.
  3. 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_timeout get no wrap (they do not inherit another agent’s declared value).
  4. 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).
The uniform path is 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.
As of PR #4477, heterogeneous per-agent tool_timeout values are honoured per agent instead of collapsing to the tightest value. A slow agent that previously inherited a fast agent’s tighter budget (masking a real timeout) may now run longer. Uniform declarations are unaffected. (Earlier, PR #3176 had reversed the collapse from max() to min().) PR #4468 follows up by tightening uniform detection: a single declared tool_timeout on one agent no longer counts as uniform — if any agent omits the field, undeclared agents get no wrap instead of silently inheriting the lone declared budget.
YAML boolean values are ignored, not coerced. Because bool subclasses int in Python, tool_timeout: yes or tool_timeout: true used to silently become a 1-second cap on every tool. As of PR #2609 the resolver explicitly rejects bool values — such entries are treated as “not declared” and fall through to the next precedence level. Use an integer or float (e.g. tool_timeout: 30).

Executor Details

  • One executor per Agent instance (lazy creation)
  • max_workers=2 threads 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_timeout is not reclaimable, so the executor is shutdown(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.
Which timeout to choose:

Common Patterns

Limit FastAPI Route Concurrency

Async Context Manager Helper

Timeout Selection by Tool Type


Best Practices

Prevents deadlocks when exceptions occur:
Keep acquisition method consistent with execution context:
Any tool that does network IO should have a timeout:
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.
For complete retry configuration and error handling strategies, see Tool Retry Policy.

Parallel tool calls inside one async turn

A single astart(...) turn can itself dispatch multiple independent tool calls concurrently when parallel_tool_calls=True.
Two situations users conflate compose cleanly: 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

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

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