> ## Documentation Index
> Fetch the complete documentation index at: https://rasa.com/docs/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.card_replace.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:
    selected_card_id:
      type: text
      description: Account id of the card the customer chose.
    order_confirmed:
      type: bool
      llm_settable: true
      description: The customer explicitly approved placing the order.
```

```yaml memory.yml theme={null}
authenticated:
  type: bool
  description: Whether the customer has verified their identity.
```

> [!NOTE]
>
> `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.

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


## Related topics

- [skill.md](https://rasa.com/docs/mantle/reference/skill-md.md)
- [Constraint Table](https://rasa.com/docs/mantle/reference/constraint-table.md)
- [Tools](https://rasa.com/docs/mantle/concepts/tools.md)
- [Tools reference](https://rasa.com/docs/mantle/reference/tools.md)
- [Guarantees & Guardrails](https://rasa.com/docs/mantle/orchestration/guarantees.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.