AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified Apache-2.0 Self-run

Rasa Writing Custom Actions

skill-rasahq-rasa-agent-skills-rasa-writing-custom-actions · by RasaHQ

>

No reviews yet
0 installs
26 views
0.0% view→install

Install

$ agentstack add skill-rasahq-rasa-agent-skills-rasa-writing-custom-actions

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-rasahq-rasa-agent-skills-rasa-writing-custom-actions)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
3mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Rasa Writing Custom Actions? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Writing Custom Actions in Rasa

Custom actions run Python code when a flow reaches an action step — for example, calling an external API, querying a database, or computing a value. They execute on an action server that communicates with the Rasa server via HTTP or gRPC.

Key principle: keep business logic in flows, not in actions. Flows define how the conversation proceeds (branching, conditions, collect steps). Actions do the raw work (fetch data, call APIs, query databases) and return results via SlotSet events for flows to use. Keep each action focused on one responsibility — action_search_products searches, action_place_order places an order.

If all you need is a simple API call with slot-based input/output mappings and the API is available on an MCP server, consider using an MCP tool call instead — see rasa-calling-mcp-tools-from-flows.

Workflow

  1. Identify what raw work the flow needs — API call, database query, computation.

If the task can be handled with set_slots or flow logic alone, don't create an action.

  1. Review existing actions, slots, and responses — reuse before creating new ones.
  2. Create or open the Python file under actions/ (see "File organization").
  3. Name the action with a verb after action_ (see "Naming conventions").
  4. Define the action class: name() returns the action name, run() does the work

(see "Action class structure").

  1. Read slot values from tracker.get_slot(), call external services, handle errors,

then return SlotSet events for the flow to branch on (see "Working with the tracker", "Error handling", and "Returning events").

  1. Register the action in the domain file under actions:

(see "Domain registration").

  1. Validate the project.

File organization

Before creating new files, check the existing project layout. If the project already uses a different convention, follow that convention instead.

By default, the action server looks for actions in a file called actions.py or a package directory called actions/. Use --actions my_module when running the action server to specify a different module.

actions/
├── __init__.py
├── action_scheduling.py     # actions for scheduling flows
├── action_orders.py         # actions for order tracking flows
├── action_payments.py       # actions for payment flows
└── shared/
    ├── __init__.py 
    └── utils.py             # shared utilities (not action classes)

Group related actions by business domain. Keep helper functions (API clients, database connectors) in separate modules so actions stay focused on orchestration.

Naming conventions

This section proposes default conventions. If the user's project already follows different conventions, match those instead.

  • Action names: action_ prefix, then a verb — action_check_inventory,

action_fetch_order_status, action_schedule_appointment (not action_appointment)

  • Custom ask actions: action_ask_{slot_name} — Rasa runs this instead of the static

utter_ask_ response for a collect step (see "Custom ask actions")

  • Validation actions: validate_{slot_name} — Rasa runs this automatically when a slot

is collected (see "Slot validation actions")

  • Class names: PascalCaseActionScheduleAppointment, ValidatePhoneNumber

Action class structure

Every custom action subclasses Action from rasa_sdk and implements two methods:

  • name() — returns the action name as a string. Must match the name in the domain

and in flows.

  • run(dispatcher, tracker, domain) — executes the action's logic. Returns a list of

events (typically SlotSet).

from typing import Text, Dict, Any, List
from rasa_sdk import Action, Tracker
from rasa_sdk.executor import CollectingDispatcher
from rasa_sdk.events import SlotSet

class ActionCheckAvailability(Action):
    def name(self) -> Text:
        return "action_check_availability"

    async def run(
        self,
        dispatcher: CollectingDispatcher,
        tracker: Tracker,
        domain: Dict[Text, Any],
    ) -> List[Dict[Text, Any]]:
        doctor_id = tracker.get_slot("doctor_id")
        preferred_date = tracker.get_slot("preferred_date")
        slots = await scheduling_api.get_available_slots(doctor_id, preferred_date)
        return [
            SlotSet("available_slots", slots),
            SlotSet("has_availability", len(slots) > 0),
        ]

Working with the tracker

The tracker provides read access to the conversation state. In CALM assistants, the most commonly used methods are get_slot() and sender_id:

  • tracker.get_slot("slot_name") — get a slot value (returns None if unset)
  • tracker.sender_id — unique ID of the user
  • tracker.latest_message — dict with the last user message
  • tracker.events — full event history

Error handling

When an action calls external services, always handle failures. Set an error or status slot and let the flow branch on it — don't hide error-handling logic inside the action.

class ActionFetchOrderStatus(Action):
    def name(self) -> Text:
        return "action_fetch_order_status"

    async def run(
        self,
        dispatcher: CollectingDispatcher,
        tracker: Tracker,
        domain: Dict[Text, Any],
    ) -> List[Dict[Text, Any]]:
        order_id = tracker.get_slot("order_id")
        try:
            status = await orders_api.get_status(order_id)
            return [
                SlotSet("order_status", status),
                SlotSet("order_lookup_error", False),
            ]
        except Exception:
            return [SlotSet("order_lookup_error", True)]

The flow can then branch on slots.order_lookup_error to show an error message or continue — keeping the decision in the flow, not the action.

Returning events

The run method returns a list of events that update the conversation state. In CALM, SlotSet is the primary event — set boolean or categorical slots for the flow to branch on, rather than making decisions inside the action. All events must be imported from rasa_sdk.events.

| Event | Purpose | Example | |------------------------|---------|---------| | SlotSet(key, value) | Set a slot value | SlotSet("has_availability", True) | | SlotSet(key, None) | Clear a slot | SlotSet("selected_slot", None) | | AllSlotsReset() | Reset all slots to initial values | AllSlotsReset() | | FollowupAction(name) | Force a specific action to run next | FollowupAction("action_retry") | | ConversationPaused() | Pause the conversation (e.g. handoff) | ConversationPaused() |

Return an empty list if no state changes are needed: return []

Using the dispatcher

The dispatcher sends messages back to the user, but in most cases the flow should handle messaging via action: utter_* steps instead. Defining responses in the domain and referencing them in flows gives better validation support, variations, and rephrasing (see the rasa-writing-responses skill).

Use the dispatcher primarily in action_ask_* actions where content is dynamic (e.g. buttons fetched from an API). If you do use it, responses are automatically added as BotUttered events — do not return them as explicit events.

dispatcher.utter_message(response="utter_appointment_confirmed")     # domain response
dispatcher.utter_message(response="utter_greet", name="Sara")        # fills {name} in template
dispatcher.utter_message(text="Looking up your order...")             # inline text
dispatcher.utter_message(
    text="Choose a delivery option:",
    buttons=[                                                        # CALM: /SetSlots payload
        {"title": "Standard", "payload": "/SetSlots(delivery=standard)"},
        {"title": "Express", "payload": "/SetSlots(delivery=express)"},
    ],
)

Custom ask actions

When a collect step needs dynamic content (e.g. buttons fetched from a database), define an action named action_ask_{slot_name}. Rasa runs it instead of the static utter_ask_{slot_name} response. You cannot have both for the same slot — Rasa will raise a validation error. See the rasa-building-flows skill for how collect steps resolve responses.

class ActionAskDeliveryOption(Action):
    def name(self) -> Text:
        return "action_ask_delivery_option"

    async def run(
        self,
        dispatcher: CollectingDispatcher,
        tracker: Tracker,
        domain: Dict[Text, Any],
    ) -> List[Dict[Text, Any]]:
        options = await shipping_api.get_options(tracker.get_slot("zip_code"))
        buttons = [
            {"title": opt["label"], "payload": f"/SetSlots(delivery_option={opt['id']})"}
            for opt in options
        ]
        dispatcher.utter_message(text="Choose a delivery option:", buttons=buttons)
        return []

Slot validation actions

In CALM assistants, implement slot validation as a regular Action subclass named validate_{slot_name}. Rasa runs it automatically when the slot is collected. The action reads the slot value from the tracker, validates it, and returns a SlotSet — either with the accepted value or None to reject and re-ask.

For simple format or range checks, prefer domain-level validation.rejections or flow-level rejections on the collect step instead (see the rasa-managing-slots and rasa-building-flows skills). Use a validation action when the check requires external calls or complex logic.

class ValidatePhoneNumber(Action):
    def name(self) -> Text:
        return "validate_phone_number"

    async def run(
        self,
        dispatcher: CollectingDispatcher,
        tracker: Tracker,
        domain: Dict[Text, Any],
    ) -> List[Dict[Text, Any]]:
        phone = tracker.get_slot("phone_number")
        if await verification_service.is_valid_number(phone):
            return [SlotSet("phone_number", phone)]
        dispatcher.utter_message(response="utter_invalid_phone")
        return [SlotSet("phone_number", None)]        # None triggers re-ask

Domain registration

Every custom action must be listed under actions: in the domain. Actions starting with utter_ are auto-registered from responses and do not need to be listed.

actions:
  - action_check_availability
  - action_fetch_order_status
  - action_ask_delivery_option
  - validate_phone_number

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.