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

# Tool Constraints

> Gate tools on memory state.

This is the first hard guarantee you can add. `tool_constraints` in the
frontmatter make a tool invisible until its condition holds. The instructions
body stays untouched.

## Tool gating with `requires:`

Add a `tool_constraints` entry for the tool. The framework removes it from the
LLM's schema until the condition is true. The model can't call what it can't
see.

```markdown skills/card_replace/skill.md theme={null}
---
name: Card Replace
description: Replace a credit card -- lost, stolen, damaged, or not received

tool_constraints:
  - lock_card:
      requires: "session.project.selected_card_id"
  - process_card_replacement:
      requires: "session.card_replace.order_confirmed == True"
---

Help the customer replace a credit card.

Check whether their account is eligible for replacement. If they have
multiple cards, ask which one. Ask why they need a replacement -- the valid
reasons are lost, stolen, damaged, or not received.
```

Before `selected_card_id` is set, the LLM sees:

```
Available tools: [get_customer_info, check_transactions, ...]
```

After a card is selected:

```
Available tools: [get_customer_info, check_transactions, lock_card, ...]
```

## Explicit confirmations

For a tool with irreversible side effects, add `requires_confirmation:` with
`enabled: true`. The runtime holds the call, asks the customer to approve it,
and runs the tool only if they say yes.

```yaml theme={null}
tool_constraints:
  - process_card_replacement:
      requires: "session.card_replace.order_confirmed"
      requires_confirmation:
        enabled: true
        utter_for_confirmation: utter_ask_replacement_confirmation
        utter_on_user_denial: utter_replacement_cancelled
      on_success: utter_replacement_disclaimer
      on_failure: utter_replacement_failed
```

| Field                       | Shape                               | What it does                              |
| --------------------------- | ----------------------------------- | ----------------------------------------- |
| `enabled`                   | Boolean, default `false`            | Turns the confirmation pause on           |
| `utter_for_confirmation`    | Response name from `responses.yml`  | The confirmation question the engine asks |
| `utter_on_user_denial`      | Response name from `responses.yml`  | The line delivered when the user declines |
| `on_success` / `on_failure` | Response names from `responses.yml` | Delivered after the tool runs             |

The two `utter_*` names are optional. Set `enabled: true` on its own and the LLM
phrases both the ask and the denial:

```yaml theme={null}
tool_constraints:
  - process_card_replacement:
      requires_confirmation:
        enabled: true
```

The pause and the resume are handled for you. The engine holds the pending call
while the customer answers, then runs the tool on a yes or cancels it on a no.
There is nothing further to wire up.

## How memory drives gating

Every `requires:` condition evaluates against current memory. Memory is where
tools write results and where the framework checks conditions.

**Memory must be declared.** A key a tool writes with `context.memory.set()`
has to exist in the skill's `memory.yml` or the project one, or `rasa train`
fails with `undeclared_memory_write`:

```yaml skills/card_replace/memory.yml theme={null}
schema:
  public:
    order_confirmed:
      type: bool
      llm_settable: true
      description: The customer explicitly approved placing the order.
```

```yaml memory.yml theme={null}
selected_card_id:
  type: text
  description: Account id of the card the customer chose.
```

<Info>
  `tool_constraints` are enforced regardless of who triggers the tool. Whether the
  LLM calls it or an ordered-block step runs it via `execute_tool:`, the same
  `requires:` and confirmation checks run, and gating **fails closed**, so an
  unevaluatable condition keeps the tool hidden.
</Info>

## Checking a gate

The Inspector's tools panel shows every declared tool with its current gate
result, so you can watch a condition flip as memory fills in. That is the
quickest way to confirm a `requires:` expression behaves the way you intended.

Two things to confirm when a tool is not appearing:

* The condition uses the full namespaced form,
  `session.<skill_id>.<entry>` or `session.project.<entry>`.
* The entry is one this skill can read: its own fields, another skill's `public`
  fields, or project memory.

Local tools in `tools.py` are auto-discovered. They only need a
`tool_constraints` entry when you are gating them.
