> ## Documentation Index
> Fetch the complete documentation index at: https://praison.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Project-Local Tools

> Drop a Python file in .praisonai/tools/ and praisonai run picks it up — zero config

Drop a Python file in `.praisonai/tools/` and `praisonai run` auto-loads every public function as a tool — no `--tools` flag.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent

agent = Agent(
    name="Greeter",
    instructions="Greet users by name.",
    tools=["greet.greet"],
)
agent.start("Greet Ada")
```

The tool name uses the namespaced form — `<module>.<function>` — so `greet.py::greet` becomes `greet.greet`.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    subgraph "Project-Local Tools"
        File[📄 .praisonai/tools/*.py] --> Discovery[🔍 discovery]
        Discovery --> Agent[🤖 agent]
    end

    classDef file fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef process fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef agent fill:#8B0000,stroke:#7C90A0,color:#fff

    class File file
    class Discovery process
    class Agent agent
```

## Quick Start

<Steps>
  <Step title="Scaffold the convention">
    `praisonai init` writes a commented `@tool` stub at `.praisonai/tools/example.py`:

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    # .praisonai/tools/example.py
    """Project-local tools for this .praisonai/ project.

    Every public callable in this directory is auto-discovered and made available
    to the agent on `praisonai run` — no --tools flag required.
    """

    # from praisonaiagents import tool
    #
    #
    # @tool
    # def greet(name: str) -> str:
    #     """Return a friendly greeting for the given name."""
    #     return f"Hello, {name}!"
    ```
  </Step>

  <Step title="Drop a plain function and run">
    Add a plain function at `.praisonai/tools/greet.py`:

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    # .praisonai/tools/greet.py
    def greet(name: str) -> str:
        """Return a friendly greeting for the given name."""
        return f"Hello, {name}!"
    ```

    Run — no `--tools` flag needed, just the opt-in:

    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    # Either — per-invocation flag (recommended)
    praisonai run --allow-local-tools "use the greet tool to greet Ada"
    # Or — shell-scoped env var
    PRAISONAI_ALLOW_LOCAL_TOOLS=true praisonai run "use the greet tool to greet Ada"
    ```
  </Step>

  <Step title="Promote to @tool for a rich schema">
    Decorate a function with `@tool` when you want a typed schema for the LLM:

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    # .praisonai/tools/math.py
    from praisonaiagents import tool

    @tool
    def add(a: int, b: int) -> int:
        """Add two numbers."""
        return a + b
    ```

    Run it with the opt-in as a first-class flag:

    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    # Either
    praisonai run --allow-local-tools "use the add tool: 2+3"
    # Or
    PRAISONAI_ALLOW_LOCAL_TOOLS=true praisonai run "use the add tool: 2+3"
    ```
  </Step>
</Steps>

***

## How It Works

`praisonai run` walks `.praisonai/tools/`, loads each module behind the security gate, and hands the callables to the agent.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant User
    participant Run as praisonai run
    participant Discovery as CustomDefinitionsDiscovery
    participant Loader as _safe_loader (env gate)
    participant Agent

    User->>Run: praisonai run "..."
    Run->>Discovery: discover_project_tools()
    Discovery->>Loader: load_user_module(path)
    alt PRAISONAI_ALLOW_LOCAL_TOOLS=true (or --allow-local-tools)
        Loader-->>Discovery: module → tool callables
        Discovery-->>Run: [namespaced tools]
        Run->>Agent: tools += discovered
    else opt-in unset
        Discovery->>Run: skip (no exec)
        Note over Run: prints hint naming files found +<br/>how to enable (env var / --allow-local-tools)
    end
```

***

## Discovery Rules

The rules below come straight from the SDK's `CustomDefinitionsDiscovery` and `_load_tools`.

| Rule                                       | Behaviour                                                                                                                                                                                                                                                           |
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Location                                   | `.praisonai/tools/*.py` (user-global `~/.praisonai/tools/` + project walk-up to git root)                                                                                                                                                                           |
| Precedence                                 | Later wins on collision (user-global \< project; nested closer to CWD \< nested further)                                                                                                                                                                            |
| Naming                                     | `<module-filename>.<function-name>` (e.g. `greet.py::greet` → `greet.greet`)                                                                                                                                                                                        |
| Public only                                | Callables whose name doesn't start with `_`                                                                                                                                                                                                                         |
| Module filter                              | Files starting with `_` (e.g. `_helpers.py`) are skipped                                                                                                                                                                                                            |
| Mixed file rule                            | If any function in the file is `@tool`-decorated, only decorated functions are exported                                                                                                                                                                             |
| Security gate                              | `PRAISONAI_ALLOW_LOCAL_TOOLS=true` (or `--allow-local-tools`) required — gate unset → empty discovery + a one-line hint naming the tool files found and how to enable them                                                                                          |
| Interaction with `--tools` / YAML `tools:` | Additive; explicit `--tools` items and YAML `tools:` names both compose with `.praisonai/tools/` discovery, dedup'd by callable identity. YAML path uses the raw tool name (`@tool(name=…)` or `__name__`); direct `praisonai run` uses the module-namespaced form. |
| Interaction with `--agent`                 | Same behaviour as default `run` — frontmatter `tools:` list is unioned with `--tools`/`--toolset` and auto-discovered `.praisonai/tools/*.py`, dedup'd by callable identity. Fixed in [#3047](https://github.com/MervinPraison/PraisonAI/issues/3047).              |
| Opt-out                                    | `--no-tools` on `praisonai run` skips discovery entirely                                                                                                                                                                                                            |

***

## Enabling on `run`

Two equivalent ways to opt in for a `praisonai run` invocation.

<Tabs>
  <Tab title="--allow-local-tools (per-invocation)">
    Discoverable via `praisonai run --help`. Scoped strictly to the single invocation.

    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    praisonai run --allow-local-tools "use the greet tool to greet Ada"
    ```
  </Tab>

  <Tab title="PRAISONAI_ALLOW_LOCAL_TOOLS=true (shell scope)">
    Persists for the shell session. Use when several back-to-back runs need it.

    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    export PRAISONAI_ALLOW_LOCAL_TOOLS=true
    praisonai run "use the greet tool to greet Ada"
    ```

    Accepts only `true` (case-insensitive). Values like `1`, `yes`, `on`, `false`, `0` are **not** truthy for this variable.
  </Tab>
</Tabs>

<Note>
  `--allow-local-tools` is a **per-invocation** grant — it sets `PRAISONAI_ALLOW_LOCAL_TOOLS=true` for this run only and restores the prior value in a `finally`. A later in-process `run_main()` call (embedded/notebook/test reuse) without the flag will not inherit the authorization.
</Note>

### Discovery hint when the opt-in is unset

When `.praisonai/tools/*.py` (or `~/.praisonai/tools/*.py`) files are present but the opt-in is unset, `praisonai run` prints a one-line hint on the default path (not just `--verbose`):

```
Found 3 local tool files in .praisonai/tools/ and ~/.praisonai/tools/ but local tools are disabled. Enable with PRAISONAI_ALLOW_LOCAL_TOOLS=true or --allow-local-tools.
```

The location(s) named in the hint reflect where files were actually found — `.praisonai/tools/`, `~/.praisonai/tools/`, or both. The hint is silent when there are no local tool files.

### Enabling on `run --agent`

`run --agent <name>` auto-discovers `.praisonai/tools/*.py` too — the frontmatter `tools:` list, `--tools`/`--toolset`, and discovered tools all merge, dedup'd by callable identity.

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Frontmatter tools + --tools flag + .praisonai/tools/ all merged
praisonai run --agent researcher --tools github --allow-local-tools "..."
```

The same `PRAISONAI_ALLOW_LOCAL_TOOLS=true` / `--allow-local-tools` opt-in applies — the security gate is identical to default `run`. Fixed in [#3047](https://github.com/MervinPraison/PraisonAI/issues/3047).

***

## Using from YAML agents (`agents.yaml`)

YAML-defined agents also pick up `@tool` functions from `.praisonai/tools/*.py` — reference them by tool name from any `tools:` list.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# .praisonai/tools/math.py
from praisonaiagents import tool

@tool
def add(a: int, b: int) -> int:
    """Add two numbers."""
    return a + b
```

```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# agents.yaml
roles:
  mathematician:
    role: Mathematician
    goal: Do arithmetic on request.
    backstory: A calm arithmetician.
    tools:
      - add          # resolved from .praisonai/tools/math.py
      - duckduckgo   # resolved from the built-in chain
    tasks:
      t1:
        description: What is 2 + 3?
        expected_output: The answer.
```

Run it with the same opt-in that gates `praisonai run`:

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
PRAISONAI_ALLOW_LOCAL_TOOLS=true praisonai --agent-file agents.yaml
```

Naming rule: the tool key is `@tool(name="…")` if given, else the function `__name__` — **not** the module-namespaced form (`math.add`) that direct `praisonai run` uses. The two paths differ only in the key.

| Path                                                     | Key used to reference the callable                                              |
| -------------------------------------------------------- | ------------------------------------------------------------------------------- |
| `praisonai run` (direct-prompt / `--agent`)              | `<module>.<function>` — e.g. `math.add`, `greet.greet`                          |
| YAML `resolve_all_from_yaml` (agents.yaml `tools:` list) | Tool name only — `@tool(name="…")` or `__name__` — e.g. `add`, `weather_lookup` |

Fixed in [#3107](https://github.com/MervinPraison/PraisonAI/issues/3107).

***

## `@tool`-decorated vs Plain Callable

Both a plain function and a `@tool`-decorated function become tools — but if a file has any `@tool`, only the decorated functions win.

<Tabs>
  <Tab title="Plain function">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    # .praisonai/tools/greet.py
    def greet(name: str) -> str:
        """Return a friendly greeting for the given name."""
        return f"Hello, {name}!"
    ```

    Exposed as `greet.greet`.
  </Tab>

  <Tab title="@tool-decorated">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    # .praisonai/tools/math.py
    from praisonaiagents import tool

    @tool
    def add(a: int, b: int) -> int:
        """Add two numbers."""
        return a + b

    def _round(x: float) -> int:
        """Private helper — never exported."""
        return round(x)
    ```

    Exposed as `math.add`. The plain `_round` helper is skipped (leading underscore), and if any non-underscore plain helper existed it would be dropped too because `add` is `@tool`-decorated.
  </Tab>
</Tabs>

***

## User-Global vs Project-Local

Two locations feed discovery, and they differ on the working-directory boundary.

<Note>
  User-global tools live at `~/.praisonai/tools/` and load even though they sit outside your project directory — the CWD boundary is deliberately opted out for that explicitly user-owned location (a regression fix that mirrors how an explicit `--tools` absolute path is trusted). Project-local tools walk up from your current directory and keep the strict CWD check, so an untrusted checkout cannot escape it.
</Note>

***

## Best Practices

<AccordionGroup>
  <Accordion title="Keep private helpers underscore-prefixed">
    Prefix helper functions with `_` so they stay internal. Underscore names are never exported, and modules whose filename starts with `_` are skipped entirely.
  </Accordion>

  <Accordion title="Use @tool when you want a rich schema">
    Decorate with `@tool` from `praisonaiagents` to give the LLM a typed schema. In a mixed file, only the `@tool` functions are exported — plain helpers are dropped.
  </Accordion>

  <Accordion title="Prefer --allow-local-tools over exporting the env var">
    Loading a tool module executes its code. Prefer `--allow-local-tools` on the individual `praisonai run` invocation over exporting `PRAISONAI_ALLOW_LOCAL_TOOLS=true` in your shell — the CLI flag is scoped to the single run and cannot leak to a later in-process invocation. Export the env var only when many back-to-back runs need the opt-in in a trusted shell.
  </Accordion>

  <Accordion title="Use --no-tools for one-off deterministic runs">
    Pass `--no-tools` to `praisonai run` to skip auto-discovery entirely when you want a run with no local tools loaded.
  </Accordion>
</AccordionGroup>

***

## Related

<CardGroup cols={2}>
  <Card title="Local Tools Loading" icon="wrench" href="/docs/features/local-tools-loading">
    Load your own tools.py or --tools file safely with the PRAISONAI\_ALLOW\_LOCAL\_TOOLS opt-in.
  </Card>

  <Card title="Tool Discovery Order" icon="list-tree" href="/docs/features/tool-discovery-order">
    The tier order that resolves a tool name — local files, built-ins, package, or plugin.
  </Card>

  <Card title="Tool Resolver" icon="wrench" href="/docs/features/tool-resolver">
    The resolver behind YAML `tools:` lists — merges `.praisonai/tools/` @tool functions by name.
  </Card>

  <Card title="Add Tools" icon="plus" href="/docs/cli/tools-add">
    Copy tool files into \~/.praisonai/tools/ from local files or GitHub.
  </Card>

  <Card title="Tools CLI" icon="terminal" href="/docs/cli/tools">
    List and manage the tools available to praisonai run.
  </Card>

  <Card title="Run CLI" icon="play" href="/docs/cli/run">
    How --agent composes frontmatter tools with --tools/--toolset and local tools.
  </Card>

  <Card title="Custom Agents, Commands & Tools" icon="file-code" href="/docs/features/custom-agents-commands">
    Tool composition order for praisonai run --agent.
  </Card>
</CardGroup>
