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

# Tools

> Typed functions that let a skill take action.

Tools are how a skill *does* things. Look up an account, lock a card, place an
order. A tool is a decorated Python function. Drop it in the skill's `tools/`
folder and it's automatically available; the LLM reads its name, description, and
inputs and decides when to call it.

## Defining a tool

```python skills/check_balance/tools.py theme={null}
from rasa.calm_v2.tools.decorator import ToolContext, tool
from rasa.calm_v2.tools.result import ToolResult


@tool(description="Look up account balance by account number")
async def check_balance(
    account_number: str,
    context: ToolContext = None,
) -> ToolResult:
    balance = await call_api(account_number)
    return ToolResult(
        llm_response={"balance": balance, "currency": "USD"}
    )
```

The function signature *is* the schema:

* **Name**: the function name (`check_balance`)
* **Description**: the `@tool` decorator argument
* **Inputs**: extracted from the type hints
* **`context`**: injected by the runtime, never visible to the LLM

Tools are **auto-discovered** from the skill's `tools.py` (or a `tools/` folder,
if you'd rather split them across files). No imports, no registration. The LLM
sees the tool and calls it when the instructions call for it.

The tool must be `async`, and the injected parameter must be named exactly
`context`.

## Using a tool in instructions

Reference a tool from the [instructions](/docs/maestro/concepts/instructions) body with
its plain name:

```markdown theme={null}
Ask for their account number, then call check_balance and report
the balance clearly.
```

The tool is already in the model's schema whenever the skill is active: naming
it in prose is guidance about *when* to use it, not a reference the compiler
resolves.

## Sharing state with the skill

Tools read and write [memory](/docs/maestro/concepts/memory) through `context`, so their
results are available to the rest of the skill:

```python theme={null}
context.memory.set("current_plan_id", plan.id)
```

Every key you write must be declared in the skill's `memory.yml` or the
project-root one. `rasa train` rejects an undeclared write.

A tool can also send a message to the user directly, bypassing the LLM. It is a
coroutine, so it must be awaited:

```python theme={null}
await context.send(f"Your balance is ${balance}")
```

## When a turn is cancelled

On voice, a user can start talking while your tool is still running. The runtime
ends the turn and raises `asyncio.CancelledError` inside your tool at whatever
`await` it is suspended on. Read-only tools can ignore this. For a tool that
changes something, check `context.is_cancelled` before the irreversible step.
See [Cancellation](/docs/maestro/reference/tools#cancellation).

## Shared tools

Local tools need no declaration. To use a tool defined outside the skill
folder, one in the agent-root `tools/` folder: add `import_tools` to the
frontmatter:

```yaml theme={null}
import_tools:
  - get_customer_info              # shared, from tools/ at the agent root
```

A shared tool that isn't declared is not available to the skill at all.

## Adding guarantees

By default the LLM decides when to call a tool, and it can get it wrong. Gate a
tool behind a required value with
[tool constraints](/docs/maestro/build-guide/tool-constraints):

```yaml theme={null}
tool_constraints:
  - check_balance:
      requires: "session.check_balance.account_number"
```

Now `check_balance` is invisible to the LLM until `account_number` is set.

`requires:` is a string expression using the namespaced memory form: see
[Conditions](/docs/maestro/reference/conditions).

## Reference

For `ToolContext`, `ToolResult`, invocation modes, tool resolution order, and the
built-in default tools, see the [Tools reference](/docs/maestro/reference/tools).
