Skip to content
RasaGet a free licence
Menu

tutorial

Chapter 3 of 6

Rasa Mantle global tools: when two skills share a tool

by Rod Rivera Published

When more than one skill needs the same capability, define it once at the project root and import it explicitly.

Some capabilities genuinely are shared. Looking up the signed-in customer is one: both Check Balance and Transfer Money want the caller’s name, and neither of them owns that lookup.

That is a global tool.

tools/customer.py               <- defined once, at the project root
skills/check_balance/skill.md   <- import_tools: [get_customer_info]
skills/transfer_money/skill.md  <- import_tools: [get_customer_info]

The tool

# tools/customer.py
from lib.engine import ToolContext, ToolResult, tool

from lib.directory import customer_by_id


@tool(
    description=(
        "Look up the signed-in customer's profile (name and segment). "
        "Requires that the customer has already been authenticated."
    )
)
async def get_customer_info(context: ToolContext = None) -> ToolResult:
    """Return profile details for the authenticated customer."""
    customer_id = None
    if context is not None:
        customer_id = context.memory.get("project.customer_id")

    if not customer_id:
        return ToolResult(
            llm_response={
                "ok": False,
                "error": "not_authenticated",
                "hint": "Run the authentication skill before calling this tool.",
            }
        )

    customer = customer_by_id(str(customer_id))
    if customer is None:
        return ToolResult(llm_response={"ok": False, "error": "customer_not_found"})

    return ToolResult(
        llm_response={
            "ok": True,
            "customer_id": customer["customer_id"],
            "name": customer["name"],
            "segment": customer["segment"],
        }
    )

The import is the point

Unlike local tools, a global tool is not picked up automatically. Each skill that wants it says so:

---
name: Check Balance
description: >
  Tell the caller the balance of one of their accounts.
import_tools:
  - get_customer_info
---

The list declares which shared tools the skill needs. When you move the skill, carry those tools too. Then inspect their Python imports and memory reads: this tool also needs lib.engine, lib.directory and the project customer ID. import_tools lists tools, not every dependency required to run them.

It also keeps the model’s tool list small. A skill only sees the tools it owns and the ones it imported, so the model is choosing between a handful of options rather than everything in the project.

The decision rules

Make a tool global only when all three hold:

Testget_customer_info
More than one skill calls it?Yes — Check Balance and Transfer Money
Skill-agnostic — does it know why it was called?No, and it should not
About the end user rather than one workflow?Yes — it returns the profile

Fail any one and it stays local. A tool used by two skills that still encodes one skill’s workflow is a sign the two skills should share a sub-skill, not a tool.

Why not “always local, import when needed”

Both designs give bounded, declarative skills. Rasa chose the global root because it makes shared surface discoverable: open tools/ and you can see everything more than one skill depends on. With the alternative, sharing is implicit and you find it only by reading imports across every skill.

It also matters for how teams work. Enterprise agents are usually built by several teams, each contributing skills. Keeping each team’s working area bounded — their own folder, their own tools — reduces the surface where two teams collide. The shared surface stays small, visible, and deliberate.

A signal worth watching

If most of your tools end up global, something is off. Either the skills are cut too finely, or what you are sharing is really a library function rather than a tool. That is worth feeding back to Rasa — it questions what local tools are for.

Tools do not have to run in-process

A tool is anything the agent can do. Today these all execute locally, inside your project. Remote execution over MCP servers is the same idea with a different transport: the skill imports a tool and calls it, and where the code runs is an implementation detail. The scoping rules in this chapter do not change.