ToolResolver is the one place PraisonAI looks for tools.py — whether it ships callables or BaseTool classes.
Quick Start
Basic Usage
Drop a
tools.py next to your YAML/script, set the environment variable, and the resolver picks both kinds up automatically:Resolution Order
Tools are resolved in a specific order, with the first match winning:Registering Tools at Runtime
Register custom tools through theToolRegistry for YAML pipeline access:
agents.yaml:
How It Works
The resolver delegates to_safe_loader.load_user_module for consistent environment variable checking and CWD path-traversal guard. The loaded module is reflected to extract either plain functions or tool class instances, then cached as an immutable view for thread safety.
The wrapper now invokes resolve() once per YAML-referenced tool name, with results cached via the resolve cache to avoid repeated lookups.
Directory mode iterates each .py file and unions the results using the same security gates and extraction logic.
Multi-tenant usage
ToolResolver resolves tools.py eagerly at construction time using Path.resolve(). The path captured is the CWD when the resolver was created — not the CWD when resolve() is called later. This makes behaviour predictable in multi-tenant gateways where each tenant has a different working directory.
When to call reset_default_resolver()
| Situation | Call it? |
|---|---|
| Single-tenant CLI | ❌ No |
| Multi-tenant gateway switching CWDs per request | ✅ Yes, before each tenant |
| Long-running server with hot-reload of tools | ✅ Yes, after tools change |
| Test setup/teardown | ✅ Yes, in a fixture |
You always pass an explicit tools_py_path | ❌ No |
Two Flavours of tools.py
What’s in tools.py | Method | Returned shape |
|---|---|---|
| Plain Python functions | get_local_callables() | List[Callable] |
praisonai_tools.BaseTool / praisonai.tools.BaseTool / langchain_community.tools.* classes | get_local_tool_classes() | Dict[str, ToolInstance] (instantiated) |
| A directory of *.py files (each may contain BaseTool subclasses) | get_local_tool_classes_from_dir(tools_dir) | Dict[str, ToolInstance] (merged across files) |
Configuration Options
| Parameter | Type | Default | Description |
|---|---|---|---|
tools_py_path | Optional[str] | "tools.py" | Path to tools.py file to load |
The
tools/ directory case takes an explicit tools_dir argument and is not bound to the constructor’s tools_py_path.Common Patterns
- Loading from Custom Path
- Reloading After Edits
- Mixed Function and Class Tools
- Loading from tools/ Directory
Security
Security enforcement is handled by_safe_loader.load_user_module:
- Environment gate: Requires
PRAISONAI_ALLOW_LOCAL_TOOLS=true - CWD constraint: Refuses paths outside current working directory
- Path traversal protection: Prevents
../style attacks
Per-Context Resolver (Multi-Project Safety)
When you callresolve_tool(name) without passing a resolver, PraisonAI now uses a context-local default resolver instead of a single process-wide singleton. Each agent/task/request anchors to its own working directory.
reset_default_resolver()
| When to call | Daemons or IDE plugins that switch the working directory between projects |
| What it does | Clears the cached ToolResolver in the current context so the next call re-anchors to the new CWD |
| Import | from praisonai.tool_resolver import reset_default_resolver |
Caching Behaviour
- Per-context cache: Each context caches its own tools.py content
- First call: Loads and caches tools.py content
ToolResolver maintains two separate caches for performance:
Local tools.py cache:
- First call: Loads and caches tools.py content
- Subsequent calls: Returns cached immutable view (
MappingProxyType) - Thread safety: Uses
_local_tools_lockfor concurrent access
- Per-tool caching: Memoises
resolve(name)results for each tool name - Negative results: Unknown tool names are cached too, so repeated lookups don’t walk every source
- Thread safety: Uses
_resolve_cache_lockfor concurrent access
Resetting the Default Resolver
Callreset_default_resolver() to clear the context-local resolver cache.
Useful between tenants, on CWD change, or in test setup so that local
tools.py resolution is not affected by previous calls.
clear_cache() now clears both caches — useful after editing tools.py and after registering new tools in the wrapper ToolRegistry at runtime.
Best Practices
Keep tools.py in your CWD
Keep tools.py in your CWD
Place
tools.py in your current working directory. Paths outside CWD are refused even with the environment variable set. This prevents path traversal attacks from HTTP API callers.Prefer functions for praisonaiagents
Prefer functions for praisonaiagents
Use plain Python functions for
praisonaiagents agents. Reserve BaseTool classes for crewai-style flows or when you need complex tool state management.Import from the wrapper package
Import from the wrapper package
Don’t import
ToolResolver from praisonaiagents — it lives in the wrapper at praisonai.tool_resolver. The wrapper handles YAML-based tool resolution.Set environment variable only in trusted environments
Set environment variable only in trusted environments
Set
PRAISONAI_ALLOW_LOCAL_TOOLS=true only in development or trusted deployment environments. This prevents arbitrary code execution from untrusted working directories.Related
ToolRegistry API Reference
Complete API reference for ToolRegistry
Security Environment Variables
Environment variable security controls
Tools
General tools documentation

