Skip to main content
Give any single tool call a hard deadline, cancel a batch mid-flight with a token, and read a discriminated error_kind instead of an opaque error string — so your agent can decide to retry, switch tools, or abort.
Backward compatible. All new parameters are optional. With no timeout_ms and no cancel_token, execution delegates straight to the tool body — behaviour is unchanged.

Quick Start

1

Add a hard deadline

Pass timeout_ms (milliseconds) to execute_batch. A tool that runs longer is abandoned on a dedicated worker and returns a typed timeout result — the turn never hangs.
2

Add a cancel token

Pass any threading.Event-like token. Signal it from another thread to short-circuit pending calls with a typed cancelled result.
The token is duck-typed: threading.Event (.is_set()) and InterruptController-like tokens (.is_cancelled / .cancelled) both work — no concrete import required.
3

Handle structured errors

Pattern-match on error_kind and read structured_error for a discriminated payload:

How It Works

Because Python threads can’t be force-killed, a timed-out tool’s worker is abandoned (not joined) and a typed result is returned immediately — the turn keeps moving. Both executors forward timeout_ms and cancel_token:

error_kind Reference

ToolResult.error_kind is a discriminated tag; ToolResult.structured_error is None on success and a discriminated dict on failure.

Timeout, Cancel, or Both?

ToolExecutionConfig.timeout_ms is now consumed by the executor, so a configured default flows through to execute_batch without per-call wiring.
ParallelToolCallExecutor enforces the same per-tool timeout inside each worker, so one hung tool resolves to a typed timeout result instead of blocking collection of the others.
When PraisonAI wraps a framework tool with tool_timeout, the resulting proxy is an instance of a dynamic subclass of the original tool’s class — so isinstance(proxy, BaseTool) (and any framework-specific base check used by CrewAI / LangChain / praisonaiagents.tool_execution for dispatch) still returns True. A BaseTool subclass with tool_timeout set on its owning agent executes normally through the same isinstance-routed .run path as an unwrapped tool; the wrapper adds a deadline but does not change dispatch. See PraisonAI Package Integration → Cross-generator tool isolation for the multi-generator behaviour.

YAML tool_timeout precedence (multi-agent configs)

When you declare tool_timeout on agents or roles in YAML, PraisonAI resolves one effective per-tool timeout for the shared tool dict.
Precedence:
  1. CLI tool_timeout wins. An explicit value passed on the command line overrides everything.
  2. Otherwise the tightest (min) declared value applies across all roles: and agents: — here 5.0s, not 60.
  3. Any agent whose declared value isn’t the tightest gets a warning naming it.
  4. Booleans are ignored — only int/float values count. If nothing declares a timeout, no timeout is applied.
Copy this into your CI log filters:
What changed (PR #3176): the effective timeout used to be the largest (max) declared value — a fast agent could inherit a slow agent’s budget and never time out. It is now the tightest (min). If a CI config passed only because a slow agent’s max() masked a fast agent’s timeout, the fast agent may now fail sooner. Single-agent configs and configs where only one agent declares tool_timeout are unaffected — the change only matters when multiple agents declare different values.
This precedence applies to the YAML per-agent tool_timeout field resolved for the shared tool dict. It is independent of the per-call timeout_ms argument to execute_batch documented above.

YAML Validation

Catch duplicate names and unknown task→agent references

Parallel Tool Calls

Run batched tool calls concurrently

Tool Progress

Stream incremental progress from slow tools

Deferred Tools

Hand back long-running work without blocking

Tools Overview

Build and register agent tools