> ## 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.

# 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.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
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.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    subgraph "Tool Discovery Pipeline"
        A[📄 tools.py or tools/] --> B[🔧 praisonaiagents.tools.TOOL_MAPPINGS]
        B --> C[📦 praisonai-tools package]
        C --> D[🔌 plugin registry entry_points]
        D --> E[❌ not found]
    end

    classDef local fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef builtin fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef external fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef plugin fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef notfound fill:#10B981,stroke:#7C90A0,color:#fff

    class A local
    class B builtin
    class C external
    class D plugin
    class E notfound
```

## How It Works

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant User
    participant Agent
    participant Resolver as Tool Resolver

    User->>Agent: Agent(tools=["read_notes"])
    Agent->>Resolver: Resolve "read_notes"
    Resolver->>Resolver: Check local → built-in → package → plugin
    Resolver-->>Agent: First match wins
    Agent-->>User: Runs task with resolved tool
```

## Quick Start

<Steps>
  <Step title="Enable Local Tools">
    Set the environment variable to allow loading local tool files:

    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    export PRAISONAI_ALLOW_LOCAL_TOOLS=true
    ```
  </Step>

  <Step title="Create a Local Tool">
    Create `tools.py` in your project directory:

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    def read_notes(query: str) -> str:
        """Read local notes matching the query."""
        return f"Notes about: {query}"
    ```
  </Step>

  <Step title="Declare tools by name on your Agent">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    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?")
    ```
  </Step>

  <Step title="PraisonAI resolves through the 4-layer pipeline">
    PraisonAI finds `read_notes` from your local `tools.py` (Tier 1) and `internet_search` from the built-in SDK (Tier 2) — no extra configuration needed.
  </Step>
</Steps>

***

## How It Works

The user names a tool; PraisonAI walks the four tiers in order and stops at the first match.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant User
    participant Agent
    participant Resolver as tool_resolver

    User->>Agent: tools=["read_notes"]
    Agent->>Resolver: resolve("read_notes")
    Resolver->>Resolver: Tier 1 local tools.py?
    Resolver-->>Agent: Matched tool (first hit wins)
    Agent-->>User: Answer using the tool
```

***

## 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` or `tools/`        |
| 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 via `praisonai.tool_sources` entry point |

***

## 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.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
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>`                                  |

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    A[🔌 Tool registry] --> B[🔧 TOOL_MAPPINGS built-ins]
    B --> C[📦 praisonai-tools optional]
    C --> D[⚠️ Not found → warn + skip]

    classDef registry fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef builtin fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef external fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef skip fill:#6366F1,stroke:#7C90A0,color:#fff

    class A registry
    class B builtin
    class C external
    class D skip
```

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.

<Note>
  The runtime 3-tier resolver is safe to use even if `praisonai_tools` is only partially installed — a broken import no longer takes the agent down.
</Note>

<Note>
  Use the **4-tier CLI resolver** for `tools.py`/YAML config-time names, and the **3-tier runtime resolver** for `Agent(tools=["name"])` string names at execution time. They are different modules that solve different problems.
</Note>

***

## Precedence Example

<Warning>
  If you define `def search(...)` in your local `tools.py` and there is also a `search` in `praisonai-tools`, the **local one wins** — Tier 1 always beats Tier 3. To see which tier resolved a tool, run with `LOGLEVEL=INFO`:

  ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  LOGLEVEL=INFO PRAISONAI_ALLOW_LOCAL_TOOLS=true praisonai-code run --tools tools.py "Search for something"
  ```

  The log shows: `Resolved 'search' from source 'local-tools.py'`
</Warning>

***

## 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:

```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Top-level tools list
tools:
  - internet_search
  - read_notes

# Per-agent tools (also supported)
agents:
  researcher:
    tools:
      - internet_search
  note_taker:
    tools:
      - read_notes
```

Tasks nested under agents can also declare their own `tools:` lists — the resolver walks all of them.

***

## 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:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
import praisonai_code.tool_resolver as _impl
sys.modules[__name__] = _impl
```

This 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

<AccordionGroup>
  <Accordion title="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.

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    # ✅ Named tool — discoverable and overridable
    agent = Agent(tools=["internet_search"])

    # ⚠️ Inline lambda — bypasses discovery pipeline
    agent = Agent(tools=[lambda q: q.upper()])
    ```
  </Accordion>

  <Accordion title="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.
  </Accordion>

  <Accordion title="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.
  </Accordion>

  <Accordion title="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.

    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    export PRAISONAI_ALLOW_LOCAL_TOOLS=true
    ```
  </Accordion>

  <Accordion title="Understand 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.
  </Accordion>

  <Accordion title="Register third-party tools via entry_points">
    Plugins should register under the `praisonai.tool_sources` group in their `pyproject.toml`. This makes them discoverable at Tier 4 without modifying agent code.
  </Accordion>

  <Accordion title="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.

    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    LOGLEVEL=INFO PRAISONAI_ALLOW_LOCAL_TOOLS=true python agent.py
    ```
  </Accordion>
</AccordionGroup>

***

## Configuration Options

| Option                        | Type                         | Default | Description                                                                       |
| ----------------------------- | ---------------------------- | ------- | --------------------------------------------------------------------------------- |
| `PRAISONAI_ALLOW_LOCAL_TOOLS` | env var (`bool`-like string) | `false` | Opt-in flag that enables loading local `tools.py` or `tools/` directory (Tier 1). |
| `LOGLEVEL`                    | env var (`string`)           | unset   | Set to `INFO` to log which tier resolved each tool name.                          |

<Card title="Tool Resolver Python Reference" icon="code" href="/docs/sdk/reference/python/modules/tool_resolver">
  Python reference for the tool resolver module
</Card>

***

## Related

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

  <Card title="Approval Backends" icon="shield-check" href="/docs/features/approval-backends">
    Choose who approves tool calls — terminal, plan mode, or a chat channel.
  </Card>

  <Card title="Plugins" icon="puzzle-piece" href="/docs/features/plugins">
    Register tools and hooks that extend the resolver's tiers.
  </Card>

  <Card title="Config File" icon="file-code" href="/docs/features/config-file">
    Turn plugins on from `[plugins]` in `config.toml`.
  </Card>
</CardGroup>
