Skip to main content
memory.yml declares what a skill remembers. Every value a tool writes must be declared here (or in the project-level file): rasa train rejects an undeclared write. There are two kinds of memory.yml, with different shapes.

Skill memory: skills/<id>/memory.yml

skills/card_replace/memory.yml
The top-level keys are schema: and access:. schema: splits into public: and private:; both are optional.

Project memory: memory.yml at the agent root

Values shared across skills live in a project-level file. Its shape is different: a flat map of entry name to attributes, with no schema:/public:/private: wrapper.
memory.yml
These resolve to the project. namespace, so a condition refers to session.project.authenticated. This is the mechanism behind cross-skill state: one skill writes authenticated, another gates on it, and neither references the other.

Field attributes

Every attribute is optional; a bare entry (my_field: {}) is a valid any field.

Types

Write the type name exactly as it appears in the left column.

enum_values

A categorical field with enum_values gets an enum constraint in the set_fields schema, so the LLM can only record one of the listed values. It is also what makes the field usable in if: markers with confidence about the value space.

llm_settable

This is the switch that decides whether the LLM may write a value at all.
The entries the LLM may set for a turn are the union of:
  • fields flagged llm_settable: true, and
  • fields owned by a collect: step in the active skill (settable regardless of the flag. The engine asked the user for them directly).
llm_settable also gates whether a field is offered to the correct tool. Leave engine-derived values (eligibility results, lock state, computed flags) at the default so the model can neither set nor “correct” them.
A project field with llm_settable: true is settable from any skill. A project field owned by a collect: step is settable only while its owning skill is active.

Visibility

Concretely, while skill A is active it can read A’s own public and private fields, every other skill’s public fields, and all project fields, minus anything listed in A’s deny_read. public is your skill’s API. Keep it small and stable: another skill gating on session.project.authenticated should depend on that key, not on the auth skill’s internals.

Access control

Entries are fully-qualified names, <skill_id>.<entry> or project.<entry>.
  • A denied read returns None. It does not raise.
  • A denied write raises MemoryWriteError.
That asymmetry is deliberate: reads happen during prompt construction and condition evaluation, where failing quietly is safer; writes are explicit actions whose rejection the LLM needs to see.

Undeclared writes

context.memory.set("foo", 1) where foo is in neither the skill’s schema: nor the project file fails rasa train with undeclared_memory_write. Fix it by declaring the entry with its type. The same applies to a collect: step target and to any key named in a requires: or if: condition.

Fully-qualified names

The scope an entry is declared in determines its full name: While skill card_replace is active, a tool reads its own field with the bare name and another skill’s public field with the fully-qualified name:
system.* is reserved for engine internals and can never be declared.
The namespace is the skill directory name, not the name: in frontmatter. A skill in skills/card_replace/ with name: Card Replace is always session.card_replace.*.

See also