Skip to main content
ReAct Sub Agents are currently in beta and are available starting from Rasa 3.14.0.
ReAct sub agents are built-in autonomous agents that can dynamically utilize MCP (Model Context Protocol) tools and custom tools defined in Python code to complete tasks. They follow a ReAct (reasoning + acting) loop: a large language model (LLM) chooses which tools to invoke based on the ongoing conversation context. Two types of ReAct sub agents are currently supported:
  1. General-purpose ReAct Sub Agent: Handles open-ended tasks and requires explicit completion signaling
  2. Task-specific ReAct Sub Agent: Focuses on filling specific slots with automatic completion based on exit conditions

General-purpose ReAct Sub Agent

A general-purpose ReAct sub agent is designed for open-ended, exploratory tasks where the agent itself determines when the task is complete. Unlike task-specific agents that have predefined exit conditions, general-purpose agents require explicit completion signaling through a built-in task_completed tool.

Key Characteristics

  • Open-ended tasks: Handles exploratory conversations and complex, multi-step tasks
  • Autonomous completion: The agent decides when it has fully completed its assigned task
  • Explicit signaling: Uses the built-in task_completed tool to signal completion
  • No exit conditions: Invoked via autonomous call steps without exit conditions

The task_completed Tool

General-purpose ReAct sub agents have access to a built-in task_completed tool that ends the ReAct loop and returns control to the flow. The tool is registered automatically and always uses the name task_completed so the runtime can recognize completion. The tool has no parameters. When calling task_completed, the model should include a short, natural user-facing message in the same response. This message appears directly to the user and serves as a brief closing line for that turn. You can change or replace this tool by subclassing MCPOpenAgent and overriding get_task_completed_tool().

Tool definition

task_completed tool schema
Exact wording can change between Rasa versions; override get_task_completed_tool() if you need to give different instructions to the model.

How Completion Works

  1. Agent decision: The ReAct sub agent autonomously determines when its primary task is complete and the execution loop should finish.
  2. Tool call: When ready to complete, the model calls the task_completed tool once. The tool has no parameters; the closing line for the user is agent message in the same turn (a short follow-up to the user’s last message—not a recap of the whole conversation, and not a message argument on the tool).
  3. Completion signal: The task_completed call terminates the ReAct sub agent’s execution loop. If the model also invoked other MCP tools in that same response, Rasa still processes those tool results before the run completes. The run ends with status completed.
  4. User response: The user sees the streamed agent message from that turn. The tool itself only returns a fixed internal result (Task completed); that value is not a user-facing parameter.
  5. Control return: Control returns to the main flow, which can then proceed to the next step.

task_completed with no agent message

If the model calls task_completed with no agent message in the same turn, Rasa does not finish the run yet. Rasa adds a one-turn system message: do not call tools, only send a short closing line to the user. It calls the LLM again with tools disabled. The run then completes from that text-only reply, without calling task_completed again.

Task-specific ReAct Sub Agent

A task-specific ReAct sub agent is designed for structured data collection tasks where specific slots need to be filled with values. Unlike general-purpose agents that determine their own completion, task-specific agents automatically complete when predefined exit conditions are met, making them ideal for appointment booking, form filling, or data collection scenarios.

Key Characteristics

The set_slot_<slot_name> Tools

Task-specific ReAct sub agents automatically receive built-in set_slot_<slot_name> tools for each slot mentioned in the exit_if conditions. These tools are dynamically generated based on the slot definitions and cannot be disabled.

Tool Generation

For each slot in the exit conditions, a tool is created with the following pattern:
  • Tool name: set_slot_<slot_name> (e.g., set_slot_user_name, set_slot_appointment_date)
  • Dynamic description: Includes slot type and allowed values (for categorical slots)
  • Slot-specific parameters: Tailored to the slot’s data type and constraints

Example Tool Definition

For a slot named user_name of type text:
set_slot_user_name tool schema (example)
For a slot named category of type categorical with allowed values ["A", "B", "C"]:
set_slot_category tool schema (example, categorical)

How Slot Setting Works

  1. Tool Availability: The ReAct sub agent automatically receives set_slot_<slot_name> tools for all slots mentioned in the exit_if conditions.
  2. Value Assignment: When the ReAct sub agent calls a set_slot_<slot_name> tool, the slot value is immediately updated.
  3. Condition Evaluation: After each slot update, the system checks if all exit conditions are satisfied.
  4. Automatic Completion: Once all exit conditions are met, the ReAct sub agent automatically completes without sending a response.
  5. Control Return: Control returns to the main flow, which can proceed to the next step.

Configuration

ReAct sub agents extend the basic sub agent configuration with additional settings. In addition to the required agent section, ReAct sub agents support the following configuration options:
ReAct sub agent config.yml (illustrative)

Configuration Section

The configuration section is optional in the ReAct sub agent’s config.yml file and provides additional customization options for LLM and connection settings.
  • configuration.llm: Defines which Large Language Model (LLM) powers the ReAct sub agent’s reasoning and tool use. The configuration format matches the standard used for other Rasa LLM settings. For more information, refer to the LLM Configuration documentation. If not specified, the default OpenAI chat model (gpt-5.1-2025-11-13) is used.
  • configuration.prompt_template: Path to a Jinja2 template for customizing the prompt of this ReAct sub agent. By default, Rasa uses the shipped template for your agent type (mcp_open_agent_prompt_template.jinja2 for general-purpose, mcp_task_agent_prompt_template.jinja2 for task-specific). Conversation history is not part of that template: Rasa renders the template as the system message and appends recent user and assistant utterances as separate chat messages when calling the LLM.
  • configuration.timeout: Specifies how many seconds to wait before timing out autonomous sub agent execution. There is no timeout by default.
  • configuration.max_retries: Number of retry attempts if MCP server connection fails. The default is 3 attempts.
  • configuration.include_date_time (optional, default: true): Enable or disable datetime information in prompts. When enabled, prompts include current date, time, and day of the week to help the model understand temporal references.
  • configuration.timezone (optional, default: "UTC"): A valid IANA timezone name (e.g., "America/New_York", "Europe/London", "Asia/Tokyo").
  • configuration.enable_filler_messages (optional, default: true): Enables intermediate assistant messages for ReAct sub-agents. When enabled, the model includes a short text response alongside tool calls, which is sent to the user before tool execution to reduce perceived latency. Rasa logs this as a BotUttered event with filler metadata to keep the dialogue state consistent. Disable to skip intermediate messages and related metadata.
  • configuration.tool_timeout (optional): Maximum time in seconds for a single tool invocation.
sub_agents/stock_explorer/config.yml
Timezone FormatThe timezone parameter must be a valid IANA timezone name. Common examples include:
  • "UTC"
  • "America/New_York"
  • "Europe/London"
  • "Asia/Tokyo"
If an invalid timezone is provided, Rasa will raise a ValidationError during agent initialization.

Connections Section

The connections section is mandatory for ReAct sub agents and specifies which MCP servers the ReAct sub agent should connect to. MCP servers are configured in your endpoints.yml file. For more details, see the MCP Server Integration page.
  • connections.mcp_servers (required): A list of one or more MCP servers that this ReAct agent connects to. At least one MCP server must be specified, and each server configuration follows this structure:
    • name (required): Name to reference this MCP server
    • Tool Filtering (optional):
      • include_tools: List of specific MCP tools available to this sub agent
      • exclude_tools: List of tools to explicitly hide from this sub agent (useful for restricting certain actions)
For each MCP server, you can define which tools to include or exclude. The include_tools and exclude_tools parameters are mutually exclusive. To send per-conversation secrets on outbound MCP tool calls, configure pre_call_hook on the MCP server section in endpoints.yml. Rasa invokes the hook before each tool call from this sub agent and merges the result into MCP _meta. For custom Python tools defined on the sub agent itself, see Call-time credentials for custom tools below.

Intermediate messages

Rasa uses intermediate messages or filler messages for user-visible updates while a sub agent is still working. For ReAct sub agents, intermediate messages are currently implemented as tool acknowledgements: brief agent message that the model emits in the same LLM response as its tool calls. Rasa forwards that text to the user before MCP or custom tools run. Default behavior: enable_filler_messages defaults to true. The built-in Jinja2 prompt adds a “Tool Usage Acknowledgments” section that tells the model to keep those intermediate lines short, avoid repeating the user’s request, and vary wording. Delivery: Intermediate message text uses the same path as other generative assistant text from the ReAct loop. Rasa checks the output channel’s streaming capabilities: when the output channel supports streaming, the LLM completion runs in streaming mode and each text delta is forwarded immediately to the output channel. When streaming is not supported, Rasa uses a non-streaming completion and sends the full text once. Voice-oriented channels can apply slightly longer silence after an intermediate message before the next assistant message so pacing sounds natural. Configuration: Set enable_filler_messages: false under configuration in the sub agent’s config.yml to disable intermediate-message prompt instructions and filler-specific event metadata. For more context on streaming and channels, see Messaging and voice channels and Speech integrations.

Custom intermediate messages

ReAct sub agents have access to the output channel, so you can send additional intermediate messages beyond the default model-driven ones—for example a fixed “Working on it…” line at the start of a turn. To send custom intermediate messages, override the send_message method in your custom ReAct sub agent class:
send_message override (intermediate messages)

Resume after interruption

On resume, Rasa invokes the ReAct sub agent again with the current conversation context. The default prompt adds a system instruction that the run was interrupted and that the agent may use the new context or ask again. If you use a custom prompt template, you can branch on resumed_after_interruption and use resumed_last_request when you need the last agent message from before the interruption.

Customization

ReAct sub agents can be customized in two ways:

Modifying the Prompt Template

You can customize the prompt template of the sub agent to:
  • Include any logic for how the sub agent should operate.
  • Pass extra context via slots. You can mention any slot in the prompt template via the slots namespace, for example: {{ slots.time }}.
  • Access datetime information via the current_datetime variable (available when include_date_time is enabled). This is a datetime object that you can use with methods like strftime(), time(), tzname(), etc. For example: {{ current_datetime.strftime("%d %B, %Y") }}.
The modified prompt template can be specified in the sub agent’s configuration.

Task description (general-purpose)

For general-purpose agents, the string in agent.description in config.yml is passed into the default prompt as {{ description }} under Primary Task (or whatever heading you use in a custom template). That string is the main place to define what “done” means for the run (scope, tone, required outcomes). To change how that text is framed or combined with other context, edit configuration.prompt_template and adjust the Jinja around {{ description }}. You can also pull in slots (for example {{ slots.my_slot }}) or datetime context as described above.

Default Prompt Templates

Rasa Pro ships two default Jinja2 templates—mcp_open_agent_prompt_template.jinja2 for general-purpose agents and mcp_task_agent_prompt_template.jinja2 for task-specific ones. The tabs below show the complete shipped templates. When Rasa renders a template, it passes in variables such as description, slot_names (task-specific only), current_datetime, enable_filler_messages, resumed_after_interruption, resumed_last_request, and restarted. Do not embed conversation history in the Jinja: Rasa uses the rendered output as the system message and adds recent user and assistant turns as separate chat messages in the same LLM request.
mcp_open_agent_prompt_template.jinja2

Customizing the Runtime Engine

Runtime customization for ReAct sub agents — whether general-purpose or task-specific — follows the same pattern. Extend the appropriate base class (MCPOpenAgent or MCPTaskAgent, both inherit from MCPBaseAgent) and override the hooks you need. Register the custom class in configuration.module (see configuration file).

Customization hooks reference

Most customizations only need the hooks below. Prefer public methods; do not override private helpers (names starting with _).
Output processing for ReAct sub agentsReAct sub agents do not use process_agent_output (that hook is for external A2A agents only). To integrate tool or agent results back into the tracker, override process_tool_output or return SlotSet events from custom tool executors.
To create a custom general-purpose ReAct sub agent, inherit from MCPOpenAgent:
MCPOpenAgent subclass (skeleton)

Customizing the task_completed tool and empty final messages

Tool schema and descriptionOverride the static method get_task_completed_tool() to change what the LLM sees (for example stricter or looser closing instructions). Keep the function name task_completed and a valid function-calling schema so the completion loop still recognizes the tool. Add a deepcopy of the default tool and replace only the description string, for example on the same subclass as your other overrides:
When the model calls task_completed with no user-facing textIf the assistant turn contains a task_completed tool call but no assistant message text, Rasa does not treat that as the final reply to the user. It appends a system follow-up instruction and calls the LLM again without tools, asking for a short, natural closing message only. After that text is produced, the run completes as usual.To change that recovery behavior (tone, length, language), override the static method get_system_message_for_empty_content_at_task_completion() and return your own system message dict with the same shape (role and content keys as in the parent implementation).
Do not remove the task_completed tool or rename it unless you replace the completion mechanism with equivalent logic; the general-purpose protocol expects this signal to exit the loop.
Once implemented, the custom sub agent class can be referenced directly in the ReAct sub agent’s configuration:
config.yml (custom module)

Processing input

Override process_input() to filter which slots reach the agent or to inject derived context before the ReAct loop starts. This runs once per user turn, before send_message():
process_input — filter slots and add context
See also the common input-processing section for the full AgentInput schema.

Adding custom tools

You can add custom tools that are independent of the MCP server but essential for the sub agent to accomplish its task. To define a custom tool, implement it as a Python function. The executor always receives the LLM tool arguments and an AgentToolContext (from rasa.agents.schemas) whose metadata field is a copy of the current request’s AgentInput.metadata (or an empty dict). Use context.metadata for backend-only data such as auth or user context; do not rely on the model to populate those keys—they are not part of the tool schema the LLM sees.
Custom tool executor (example)
Note: The arguments parameter contains only the parameters defined on the tool schema. Extract structured values the model should supply from arguments; read orchestration metadata from context.metadata.

Call-time credentials for custom tools

For secrets that must not live in slots or AgentInput.metadata, override build_custom_tool_call_metadata() on your MCPOpenAgent or MCPTaskAgent subclass. Rasa calls it before each custom tool execution and merges the returned metadata into AgentToolContext.metadata. The method may be async def or def; use async def when fetching credentials over the network. External MCP tools use pre_call_hook on the MCP server in endpoints.yml instead of this method. See Call-time credential hooks. Import types from rasa.shared.agents.outbound_call_hook. The hook receives MCPOutboundCallContext with sender_id (secret-store lookup key), tool_name, and optional agent_id. Return an OutboundCallResult whose metadata dict is merged into AgentToolContext.metadata:
Call-time metadata for custom tools (example)
If the hook raises an exception or returns an invalid type, Rasa aborts the custom tool call and returns an error tool result without invoking your executor. Rasa logs metadata key names at debug level, never values.
Avoid blocking operations in async toolsTool executors are asynchronous. Do not use blocking calls (e.g. time.sleep, synchronous HTTP clients like requests, long-running CPU-bound loops) inside them. Prefer non-blocking alternatives (e.g. await asyncio.sleep(...), httpx.AsyncClient, database drivers with async support), or run CPU-bound work in an executor via asyncio.to_thread.
The function must always return an instance of AgentToolResult, which includes the following fields:
AgentToolResult fields
To add custom tools, implement the get_custom_tool_definitions method. This method should return a list of tool definitions, each adhering to the OpenAI function calling specification. Additionally, each tool definition must include a tool_executor key, which should reference the function to execute when the tool is invoked:
Custom tool definition with tool_executor (example)
Complete example wiring a custom tool on MCPOpenAgent:
get_custom_tool_definitions (full example)

Processing tool output

Override process_tool_output() to convert MCP or custom tool results into tracker events after each successful tool iteration. Returned events are appended to the agent output and included in subsequent LLM iterations within the same turn:
process_tool_output — map tool results to slots

Key Differences Between General-Purpose and Task-Specific ReAct Sub Agents