Skip to main content
Point PraisonAI at your own tools.py, tools/ folder, or explicit --tools /path/to/file.py β€” after you flip the opt-in with --allow-local-tools (per-invocation) or PRAISONAI_ALLOW_LOCAL_TOOLS=true (shell-scoped).
The user enables local tools and asks a question; PraisonAI loads your module and exposes functions to the agent.

Quick Start

1

Add a tools.py next to your script

Expose a plain Python function in tools.py:
Then use it from an Agent:
Run with the opt-in env var:
2

Use an explicit path from the CLI

Pass an absolute path with --tools β€” useful for tools files outside your project directory. Use the --allow-local-tools flag as a per-invocation alternative to the env var:
Explicit user-provided --tools paths bypass the CWD boundary check (allow_outside_cwd=True), so absolute paths outside the repo work under the opt-in flag. CWD-derived tools.py / tools/ scanning stays strict against .. traversal.

How It Works

The loader checks two conditions before executing your file:
  1. PRAISONAI_ALLOW_LOCAL_TOOLS must be set to true (case-insensitive). If not, it logs:
    Refusing to exec {module_path}: set PRAISONAI_ALLOW_LOCAL_TOOLS=true to enable.
  2. The resolved path must be inside the current working directory (unless you pass an explicit --tools argument). If the path escapes the CWD boundary, it logs:
    Refusing to exec {path}: outside working directory.
CLI --tools file.py also warns via stdout. When you pass a file path to --tools, --rewrite-tools, --expand-tools, or research --tools, the CLI additionally prints a yellow warning to stdout if the load produced zero functions:
Warning: No tools loaded from {path} (module has no public functions, or local tools loading is disabled β€” set PRAISONAI_ALLOW_LOCAL_TOOLS=true to enable).
The dual-cause phrasing is deliberate (PR #2935) β€” the same empty result covers both β€œenv-var unset” and β€œmodule has no public functions after functions_only=True, skip_private=True filtering”. When the cause is the unset env var, pass --allow-local-tools on the praisonai run invocation instead of exporting the variable. See Security Environment Variables for the full message table.

Zero-config with .praisonai/tools/

Project-local .praisonai/tools/*.py are auto-loaded on praisonai run without any --tools flag, gated by the same opt-in. Discovery walks user-global ~/.praisonai/tools/ plus the project walk-up to the git root, and each public function is exposed as <module>.<function>.
See Project-Local Tools for the full discovery rules, precedence, and @tool vs plain-callable behaviour.

Discovery Order

When you reference a tool by name (e.g. tools=["read_notes"]), PraisonAI checks four places in this fixed order β€” first match wins:
  1. Local tools.py β€” backward compatibility, custom tools, custom variables
  2. praisonaiagents.tools.TOOL_MAPPINGS β€” built-in SDK tools
  3. praisonai-tools package β€” external tools (optional install)
  4. Tool registry (plugins via entry_points) β€” third-party plugin sources
Within tier 4, built-ins still win: the plugin registry subclasses the base PluginRegistry, so since PR #4176 an entry point whose name (case-insensitive) collides with a built-in is skipped and the built-in is kept (a DEBUG line notes the collision). Runtime register(...) is the deliberate override path. See Plugin Precedence.
See Tool Discovery Order for the full breakdown. LocalManagedAgent uses this same discovery order for its tools=[...] field. Tool names you drop in tools.py are picked up by both Agent(tools=[...]) and LocalManagedAgent(config=LocalManagedConfig(tools=[...])). See Local Managed Agents.

tools.py vs tools/ Folder

When an explicit tools.py is bound via --tools (or found at the CWD root), it takes priority over a sibling tools/ folder. The resolver honours the bound path, so an explicit --tools tools.py no longer silently loses to a sibling tools/ directory. CWD-derived scans still enforce ..-traversal safety.

--tools Outside Your Project

Only explicit user-provided paths β€” those you pass via --tools /path/to/file.py β€” opt out of the CWD boundary check. Anything the framework derives (recipe metadata, API-supplied names) stays strict. From the _safe_loader docstring:
β€œWhen True, skip the CWD boundary check. Only pass this for paths the user provided explicitly (e.g. a --tools CLI argument), never for API/network-derived paths.”

Best Practices

Keep tools.py at the top of your project β€” the CWD boundary check will find it without any flag.
Only set PRAISONAI_ALLOW_LOCAL_TOOLS=true in trusted shells; a bare pip install will execute the file you point at.
For absolute paths outside your project, pass the full path via --tools β€” the resolver will honour it under the opt-in flag.

Project-Local Tools

Drop a Python file in .praisonai/tools/ and praisonai run auto-loads it β€” zero config.

Tool Discovery Order

The 4-tier pipeline that resolves tool names β€” local file, built-ins, praisonai-tools, or a plugin.

Approval Backends

Choose who approves a tool call β€” terminal prompt, plan mode, or a chat channel.