Skip to main content
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.
skills/card_replace/skill.md
Before selected_card_id is set, the LLM sees:
After a card is selected:

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.
The two utter_* names are optional. Set enabled: true on its own and the LLM phrases both the ask and the denial:
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. If the customer asks a question or switches tasks instead of answering, the confirmation remains pending and is asked again when the conversation returns. After the customer answers, the engine rejects same-turn attempts by the LLM to re-call the tool, so the customer is not asked twice. A later ordered execute_tool step of the same tool in that turn can still open a new confirmation. There is nothing further to wire up.
Do not also instruct the model in skill.md to ask for confirmation before calling the tool. When requires_confirmation: is on, tell it to call the tool as soon as the requires: inputs are present and let the engine ask. If the prose asks as well, the customer confirms twice — once in chat, then again at the pause.Pick one confirmation mechanism per tool:
  • Engine confirmation — requires_confirmation: enabled: true, and prose that calls the tool.
  • In-flow confirmation — skill prose asks, then requires: on a confirmation memory key (as in the order_confirmed example above), with no requires_confirmation:.

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:
skills/card_replace/memory.yml
memory.yml
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.