This page provides comprehensive documentation for configuring tools in PraisonAI, including timeout settings, performance optimization, error handling, and resource management.
In YAML the field name is still tool_timeout:; in Python pass tool execution settings through tool_config=ToolConfig(timeout=…). The standalone tool_timeout, tool_retry_policy, and parallel_tool_calls kwargs were removed and raise TypeError.
PraisonAI now provides a unified ToolConfig dataclass that consolidates tool-related parameters into a single configuration object. This replaces the previous pattern of separate tool_timeout, parallel_tool_calls, and tool_retry_policy parameters.
When enable_artifacts=True, tool outputs that exceed output_limit are saved to disk and the agent gains tools to page through them on demand — preventing silent data loss for large web scrapes, file reads, or query results.
from praisonaiagents import Agentfrom praisonaiagents.config.feature_configs import ToolConfigagent = Agent(tool_config=ToolConfig(enable_artifacts=True))
Artifact Storage
Full documentation: how overflow is stored, the six retrieval tools the agent gains, storage layout, and best practices.
The standalone tool_timeout, parallel_tool_calls, and tool_retry_policy keyword arguments on Agent(...) have been removed. Passing them raises TypeError:
TypeError: Agent.__init__() got an unexpected keyword argument 'tool_timeout'
Pass these settings through tool_config=ToolConfig(...) instead.
YAML and the CLI are unaffected — tool_timeout: in YAML and --tool-timeout on the CLI remain valid; the wrapper translates them into a ToolConfig before constructing the Agent.
Each agent can set tool_config=ToolConfig(timeout=…) (or tool_config=True for defaults) which applies to all tool executions. When a tool times out, it returns a standardized error dict:
from praisonaiagents import Agentfrom praisonaiagents.config.feature_configs import ToolConfigagent = Agent( name="Assistant", tools=["web_search"], tool_config=ToolConfig(timeout=30) # seconds)# If web_search takes >30s, it returns:# {"error": "Tool timed out after 30s", "timeout": True}
Timeout behavior details:
Each agent has its own 2-thread tool executor (reused across calls)
Timed-out tools return {"error": "Tool timed out after Ns", "timeout": True} rather than raising
Thread names are prefixed tool-<agent_name> — useful for debugging
The consolidation groups timeout + retry + parallel execution settings into one config object instead of three loose kwargs. This provides better organization and prevents parameter conflicts.
For complete concurrency control documentation including per-agent limits and sync/async patterns, see Concurrency.
When you set tool_timeout in YAML (framework: praisonai) or pass --tool-timeout on the CLI, the wrapper now wraps every tool callable with a timeout-enforcing shim before handing the agent to the SDK.This means the tool_timeout knob is enforced even if the downstream SDK ignores it, even if the tool is a blocking C extension, and even if the tool is a subprocess that does not honour CancelledError.The wrapper handles sync and async tools differently:
Sync tools run in an instance-owned ThreadPoolExecutor (see _get_tool_timeout_executor in agents_generator.py); on timeout the future is best-effort cancelled. A call that already started cannot be interrupted, so the worker may keep running.
Async tools are wrapped with asyncio.wait_for(...), which cancels the underlying task cleanly.
Exception raised on wrapper-level timeout: on timeout the wrapper raises ToolTimeoutError (a TimeoutError subclass), not a JSON dict. This preserves each tool’s declared return-type contract — a typed return value is never silently downgraded to a string. Framework adapters catch it and translate it per framework.
from praisonaiagents import Agentfrom praisonai.agents_generator import ToolTimeoutErrordef slow_lookup(query: str) -> str: ... # a tool that may hangagent = Agent( name="Researcher", instructions="Look things up.", tools=[slow_lookup], tool_config={"timeout": 5}, # ToolConfig(timeout=5) also works)try: agent.start("Look up X")except ToolTimeoutError as e: print(e.tool_name, e.timeout_seconds, e.background_work_may_continue)
ToolTimeoutError carries three attributes:
Attribute
Type
Description
tool_name
str
Name of the tool that timed out
timeout_seconds
float
The per-call limit that was exceeded
background_work_may_continue
bool
True only when a started sync worker could not be cancelled; async tools cancel cleanly (False)
Crucially, the wrapper also keeps passingtool_timeout to the SDK Agent, so both layers are active — hence “defense in depth”. The SDK’s executor catches the common case; the wrapper catches the pathological one.Example usage:
framework: praisonaitopic: Fetch and summarise a slow upstream APIconfig: tool_timeout: 15 # Wrapper-level + SDK-level enforcementroles: fetcher: role: API Fetcher goal: Pull data without wedging the crew on a slow upstream backstory: Reliability-focused engineer. tools: - fetch_url tasks: fetch: description: Fetch {topic} expected_output: Summarised payload, or a translated timeout message if the upstream is slow.
When fetch_url blocks for more than 15 s, the wrapper raises ToolTimeoutError instead of hanging indefinitely; the framework adapter catches it and translates it into a value its framework understands, so the crew keeps moving.Precedence note: When both wrapper-level and SDK-level timeouts apply, the shorter of the two wins in practice (whichever fires first). Both currently read from the same tool_timeout value, so they fire at the same time; if a user later configures different values at different layers, the first-to-fire semantics still hold.
The wrapper resolves a single effective timeout per invocation:
Source
Precedence
--tool-timeout N on the CLI
Wins outright
tool_timeout: N inside any roles: or agents: entry in YAML
max(N) across all entries, used when the CLI does not specify one
Nothing declared
No wrapper-layer wrapping (SDK-level timeout still applies if tool_config is set in Python)
Boolean YAML values (tool_timeout: yes, tool_timeout: true) are explicitly ignored — they used to be silently coerced to a 1-second cap because bool subclasses int. Use integers or floats (e.g. tool_timeout: 30).
PRAISONAI_TOOL_TIMEOUT_WORKERS caps the named thread pool that runs sync tools under timeout (default 32). Invalid or non-positive values fall back to 32 with a warning log line. Once half the pool’s workers are permanently leaked to stuck sync tools, the pool is automatically recycled: leaked threads continue until their syscall returns, but new tool calls get a fresh pool instead of queueing behind them.