Tool Discovery Order
How PraisonAI resolves a tool name — local file, praisonaiagents built-ins, praisonai-tools, or a plugin
When you ask for tools=["read_notes"], PraisonAI checks four places in a fixed order and stops at the first match.
from praisonaiagents import Agent
import os
os.environ["PRAISONAI_ALLOW_LOCAL_TOOLS"] = "true"
agent = Agent(
name="Notes Helper",
instructions="Use local tools when available.",
tools=["read_notes"],
)
agent.start("Read my latest notes")The user lists a tool by name on Agent(…); PraisonAI resolves it through local files, built-ins, praisonai-tools, then plugins.
How It Works
The same pipeline runs for Agent(tools=[...]), LocalManagedAgent(config=LocalManagedConfig(tools=[...])), YAML tools:, and CLI --tools.
Quick Start
- Enable Local Tools
Set the environment variable to allow loading local tool files:
bash export PRAISONAI_ALLOW_LOCAL_TOOLS=true - Create a Local Tool
Create
tools.pyin your project directory:python def read_notes(query: str) -> str: """Read local notes matching the query.""" return f"Notes about: {query}" - Declare tools by name on your Agent
python from praisonaiagents import Agent import os os.environ["PRAISONAI_ALLOW_LOCAL_TOOLS"] = "true" agent = Agent( name="Research assistant", instructions="Use available tools to answer questions.", tools=["read_notes", "internet_search"], ) agent.start("What did I write about caching and what's new in the news?") - PraisonAI resolves through the 4-layer pipeline
PraisonAI finds
read_notesfrom your localtools.py(Tier 1) andinternet_searchfrom the built-in SDK (Tier 2) — no extra configuration needed.
How It Works
The user names a tool; PraisonAI walks the four tiers in order and stops at the first match.
The Four Tiers
From the tool_resolver.py module docstring — first match wins:
| Tier | Source | When it wins |
|---|---|---|
| 1 | Local tools.py (backward compat, custom tools, custom variables) | Your own function with that name exists in tools.py, tools/, or .praisonai/tools/*.py (project-local or user-global) |
| 2 | praisonaiagents.tools.TOOL_MAPPINGS (built-in SDK tools) | The name is a built-in like internet_search, execute_code, etc. |
| 3 | praisonai-tools package (external tools, optional) | The name exists in the optional praisonai-tools install |
| 4 | Tool registry (plugins via entry_points) | A third-party plugin registered under the canonical praisonai.tools entry-point group (or the deprecated praisonaiagents.tools / praisonai.tool_sources aliases, which still resolve but emit a one-cycle DeprecationWarning) |
Tier 1 now covers three concrete locations: (a) ./tools.py at your CWD, (b) a ./tools/ folder, and (c) .praisonai/tools/.py — walked from the project up to the git root, plus ~/.praisonai/tools/.py user-global. The .praisonai/tools/ layer is auto-loaded on praisonai run with no --tools flag; see Project-Local Tools.
Runtime string-name resolution inside an Agent
The four tiers above cover config-time resolution — tools.py/YAML names loaded by the CLI’s praisonai_code.tool_resolver. When you pass a tool name as a string to Agent(tools=[...]), the agent runtime uses a separate 3-tier resolver in praisonaiagents.tools.resolver at execution time.
from praisonaiagents import Agent
agent = Agent(
name="Research assistant",
instructions="Use available tools to answer questions.",
tools=["internet_search", "my_registered_tool"],
)
agent.start("Search the news")internet_search resolves from built-in TOOL_MAPPINGS; my_registered_tool resolves from the tool registry if you registered it. With only built-ins installed, the first name works out of the box.
The runtime resolver walks three tiers — first match wins:
| Tier | Source | SDK location |
|---|---|---|
| 1 | Tool registry (explicitly registered tools) | praisonaiagents.tools.registry.get_registry().get(name) |
| 2 | Built-in lazy tools | praisonaiagents.tools.TOOL_MAPPINGS[name] |
| 3 | praisonai-tools package (optional) | praisonai_tools.<name> |
If a name resolves nowhere, the resolver logs a warning and skips it — the agent keeps running. A partially-installed or broken praisonai-tools no longer crashes the run either: the resolver flips its “available” flag off and continues.
Publishing a tool package
Publish a pip-installable tool under the canonical praisonai.tools entry-point group and agents resolve it by name — no manual import.
[project.entry-points."praisonai.tools"]
my_tool = "my_package.tools:MyTool"Once installed, the tool auto-resolves in Agent(tools=["my_tool"]), praisonai run --tools my_tool, and YAML tools: [my_tool].
from praisonaiagents import Agent
agent = Agent(
name="Research assistant",
instructions="Use available tools to answer questions.",
tools=["my_tool"],
)
agent.start("Do the thing with my_tool")The legacy groups praisonaiagents.tools and praisonai.tool_sources still resolve tools registered under them but emit a one-cycle DeprecationWarning on first discovery. On a name collision, the canonical group wins — an alias entry with the same name is skipped, never overwritten.
Precedence ladder: praisonai.tools (canonical, wins) > praisonaiagents.tools (deprecated alias) > praisonai.tool_sources (deprecated alias).
Precedence Example
When praisonai-tools Is Not Installed
As of PR #2550, failures from the praisonai-tools package are logged, not silently skipped. You’ll see them at LOGLEVEL=INFO:
Tool 'some_tool' exists in TOOL_MAPPINGS but failed to load: <error>This means tool resolution failures are now visible and debuggable, rather than passing silently through to an unresolved tool error at runtime.
YAML tools: Blocks
resolve_all_from_yaml walks both the top-level tools: list and the agents: shape, so both formats work:
# Top-level tools list
tools:
- internet_search
- read_notes
# Per-agent tools (also supported)
agents:
researcher:
tools:
- internet_search
note_taker:
tools:
- read_notesTasks nested under agents can also declare their own tools: lists — the resolver walks all of them.
Referencing .praisonai/tools/ @tool functions
A @tool function dropped into .praisonai/tools/*.py is now resolvable by its tool name from any YAML tools: list — no explicit --tools flag, no tools.py in cwd required. The same PRAISONAI_ALLOW_LOCAL_TOOLS=true opt-in that gates praisonai run also gates the YAML resolver.
# .praisonai/tools/weather.py
from praisonaiagents import tool
@tool(name="weather_lookup")
def weather(city: str) -> str:
"""Get the weather for a city."""
return f"sunny in {city}"# agents.yaml — weather_lookup is resolved from .praisonai/tools/
roles:
researcher:
tools:
- weather_lookupThe tool is keyed by the @tool(name="…") value if given, otherwise by the function __name__ — not by the module-namespaced form weather.weather_lookup. Fixed in #3107.
Backward Compatibility
The praisonai.* import paths for tool_resolver, _safe_loader, tool_registry, and _framework_availability stay live via identity-preserving sys.modules shims. Each shim does:
import praisonai_code.tool_resolver as _impl
sys.modules[__name__] = _implThis means praisonai.tool_resolver is praisonai_code.tool_resolver — both names point at the same object. The same identity holds for _framework_availability, _safe_loader, and tool_registry, as asserted in test_c5_backward_compat.py::test_module_identity.
Best Practices
Prefer named tools over lambdas
Use string names like tools=["internet_search"] rather than inline lambdas. Named tools are resolved through the full 4-tier pipeline, making them easier to override and debug.
# ✅ Named tool — discoverable and overridable
agent = Agent(tools=["internet_search"])
# ⚠️ Inline lambda — bypasses discovery pipeline
agent = Agent(tools=[lambda q: q.upper()])Use built-in tools first
Before writing a custom tools.py, check if PraisonAI already has the tool built-in. Run praisonai tools list to see all available built-in tools.
Enable local tools only when needed
Set PRAISONAI_ALLOW_LOCAL_TOOLS=true only in environments where you trust the local tools.py. This opt-in prevents accidental execution of untrusted code.
Gate local tools with PRAISONAI_ALLOW_LOCAL_TOOLS
The PRAISONAI_ALLOW_LOCAL_TOOLS environment variable must be set to true before Tier 1 (local tools.py) is consulted. This is an opt-in security gate — without it, tools.py is not loaded and the resolver logs why it was skipped (visible at LOGLEVEL=INFO). Only SDK built-ins and plugins are then considered.
export PRAISONAI_ALLOW_LOCAL_TOOLS=trueUnderstand name collision resolution
When the same name appears in multiple tiers, Tier 1 always wins. If you define def search(...) in tools.py and a plugin also registers search, your local version takes precedence. Use this intentionally to shadow or override built-in tools.
Register third-party tools via entry_points
Plugins register under the canonical praisonai.tools entry-point group in pyproject.toml:
[project.entry-points."praisonai.tools"]
my_tool = "my_package.tools:MyTool"Once installed, the tool auto-resolves by name in Agent(tools=["my_tool"]), praisonai run --tools my_tool, and YAML tools: [my_tool] — no manual import required.
The legacy groups praisonaiagents.tools and praisonai.tool_sources still resolve tools registered under them but emit a one-cycle DeprecationWarning on first discovery. On a name collision, the canonical group wins — an alias entry with the same name is skipped, never overwritten.
Debug resolution with LOGLEVEL=INFO
Set LOGLEVEL=INFO to see exactly which tier resolved each tool. The log line Resolved 'search' from source 'local-tools.py' confirms which source won.
LOGLEVEL=INFO PRAISONAI_ALLOW_LOCAL_TOOLS=true python agent.pyConfiguration Options
| Option | Type | Default | Description |
|---|---|---|---|
PRAISONAI_ALLOW_LOCAL_TOOLS | env var (bool-like string) | false | Opt-in flag that enables loading local tools.py / tools/ (Tier 1) and .praisonai/tools/*.py @tool functions on the YAML resolver path (fixed in #3107). |
LOGLEVEL | env var (string) | unset | Set to INFO to log which tier resolved each tool name. |
Python reference for the tool resolver module
Related
Drop a Python file in .praisonai/tools/ and praisonai run auto-loads it — zero config.
Tool ResolverThe single resolver behind YAML tools: lists, --tools, and Agent(tools=[...]).
How to load your own tools.py safely with the PRAISONAI_ALLOW_LOCAL_TOOLS opt-in.
Approval BackendsChoose who approves tool calls — terminal, plan mode, or a chat channel.
PluginsRegister tools and hooks that extend the resolver’s tiers.
Config FileTurn plugins on from [plugins] in config.toml.