Skip to content

tutorial

Chapter 2 of 5

Chapter 2 — Load the profile into memory

by Rod Rivera Published

A local tool writes the customer profile into project memory, and declaring those fields is what stops the agent inventing them.

The lookup is a plain local tool in the skill’s own folder — only this skill calls it, so it belongs there and travels with it.

# skills/default_session_start/tools.py
from datetime import datetime

from rasa.mantle.tools.decorator import ToolContext, tool
from rasa.mantle.tools.result import ToolResult

_CUSTOMER_PROFILE = {
    "preferred_name": "Jordan",
    "tier": "Premier",
    "member_since": "2019",
    "default_account_id": "acc_checking",
    "default_account_label": "Everyday Checking",
}


@tool(description="Look up the signed-in customer's profile before greeting.")
async def get_customer_profile(context: ToolContext = None) -> ToolResult:
    if context is not None:
        context.memory.set("customer_name", _CUSTOMER_PROFILE["preferred_name"])
        context.memory.set("customer_tier", _CUSTOMER_PROFILE["tier"])
        context.memory.set("member_since", _CUSTOMER_PROFILE["member_since"])
        context.memory.set("time_of_day", _time_of_day(datetime.now().hour))
        context.memory.set("default_account_id", _CUSTOMER_PROFILE["default_account_id"])
        context.memory.set(
            "default_account_label", _CUSTOMER_PROFILE["default_account_label"]
        )
    return ToolResult(llm_response={"ok": True})

The profile is hard-coded so the pattern runs with no backend. In a real deployment this is a query against your customer store, keyed on whoever the channel says is signed in. The shape of the tool does not change.

Write with bare names. context.memory.set("customer_name", …) resolves to the project entry. Writing "project.customer_name" is rejected at train time as an undeclared memory write — the qualified form is for reads only. This catches people out, because reads accept both.

Declare what you store

Nothing can be written that has not been declared. In the root memory.yml:

customer_name:
  type: text
  description: The name to address the customer by. Set at session start.
customer_tier:
  type: text
  description: The customer's membership tier, e.g. Premier.
member_since:
  type: text
  description: The year the customer joined.
time_of_day:
  type: categorical
  enum_values: [morning, afternoon, evening]
  description: Part of day, computed at session start.
default_account_id:
  type: text
  description: Account id of the customer's usual account.
default_account_label:
  type: text
  description: Human-readable label of the usual account.

Two decisions are encoded here, and both matter more than they look.

Project scope, not skill scope. These live at the project level so every skill can read them. The greeting needs the name; the transactions skill needs the default account. Putting them in one skill’s memory would hide them from the other.

Not llm_settable. Only the tool writes here. The agent cannot decide the customer has been a member since 2015 because it sounded plausible. If a fact is supposed to come from your systems, never mark it settable.

Keeping something private

If the profile carries a field the customer must never be told, project memory is the wrong place — it can flow into model context.

Declare it in the owning skill’s memory.yml under private: instead. Private entries are readable only by that skill, and the runtime enforces it rather than trusting an instruction. Chapter 4 covers the second gate: what the response template lets through.