> ## Documentation Index
> Fetch the complete documentation index at: https://rasa.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# memory.yml

> Memory schema: skill and project scopes, field types, visibility, and access control.

`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`

```yaml skills/card_replace/memory.yml theme={null}
schema:
  public:
    replacement_reason:
      type: categorical
      enum_values: [lost, stolen, damaged, not_received]
      description: Why the card is being replaced.
      llm_settable: true
    selected_card_id:
      type: text
      description: Account id of the chosen card.
  private:
    eligibility_checked:
      type: bool
      description: Whether account eligibility was verified.

access:
  deny_read: []
  deny_write: []
```

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.

```yaml memory.yml theme={null}
authenticated:
  type: bool
  description: Whether the customer has verified their identity.
caller_phone:
  type: text
  seed: true
  pii: true
  description: Phone number from the voice channel at connect time.
```

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.

Project memory is shared across skills for the rest of the session. After the
first write it cannot be changed.
Put facts every skill can rely on here (authenticated, a verified customer id,
a seeded caller phone). Keep collectable, correctable values on the owning
skill as `schema.public` (for example a chosen card label).

How set project values appear in the prompt is documented on
[System Prompt](https://rasa.com/docs/mantle/reference/system-prompt.md) and
[Prompt templates](https://rasa.com/docs/mantle/reference/prompt-templates.md).

### Seeding from session start

If you want to populate a project field from channel metadata, set `seed: true`.
By default, `seed` is `false`: the engine does not copy channel metadata into
project memory unless you opt in.

Use it for channel facts such as caller phone or language. The copy uses the
same name as the field and is the first and only write.

Channel metadata often uses the same names as project fields. A matching name
is not enough: only fields you mark are copied, so a client cannot lock
`authenticated` (or any other unmarked field) by sending that key.

Here is an example. You have these project memory fields:

```yaml memory.yml theme={null}
caller_phone:
  type: text
  seed: true
  pii: true
  description: Phone number from the voice channel at connect time.

authenticated:
  type: bool
  description: Whether the customer has verified their identity.
```

If the channel sends this metadata at connect:

```json theme={null}
{
  "metadata": {
    "caller_phone": "+15551234567",
    "authenticated": true
  }
}
```

`caller_phone` is set from metadata. `authenticated` stays unset until a tool
or hook writes it.

A missing key, or a value that does not match the field's type, leaves the
field unset. `seed: true` on skill public or private fields fails `rasa train`.

## Skill vs project memory

Use **project** memory for facts every skill should rely on. Use **skill**
memory for values that belong to one task.

| | Project | Skill |
| - | - | - |
| File | `memory.yml` at the agent root | `skills/<id>/memory.yml` |
| Put here | Session facts: authenticated, a verified customer id, a seeded caller phone | That skill's task: chosen card, card list, loaded transactions |
| Who writes | Tool, post-write hook, or session-start seeding | That skill's tools, or the LLM |
| After the first write | Locked for the rest of the session | Stays writable |
| When the owning skill starts again | Unchanged | Reset to `initial_value` |
| Other skills | Read `session.project.<name>` | Read **public** fields as `session.<skill_id>.<name>` (or `context.memory.get("<skill_id>.<name>")`). They cannot write those fields |

The LLM cannot write project fields (`set_fields`, `collect`, `correct`).
`rasa train` rejects `llm_settable: true` and `collect:` on a project field.

Typical pattern: collect the user-facing value on the skill, then have that
skill's tool or `run_after_setting_<skill_field>` hook write `project.*`.
Completing or reactivating a skill does not clear project values.

You can gate the skill that establishes a project fact with
`requires: not session.project.<fact>` so routing does not offer it again.
Downstream skills can use `requires: session.project.<fact>`.

Do not declare working state in root `memory.yml`: write-once would lock the
first value for the rest of the session.

## Field attributes

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

| Attribute | Default | Description |
| - | - | - |
| `type` | `any` | Value type: see below |
| `description` | `null` | Shown to the LLM in `set_fields` and `correct` schemas |
| `enum_values` | `[]` | Allowed values; meaningful with `type: categorical` |
| `llm_settable` | `false` | Whether the LLM may write this via `set_fields` (skill fields only; rejected on project) |
| `seed` | `false` | Project fields only: copy this field from session-start metadata. See [Seeding from session start](#seeding-from-session-start) |
| `pii` | `false` | Withholds the value from prompts: the LLM sees the entry as set, without the content |
| `initial_value` | `null` | Value the entry holds before anything writes to it. Skill fields return to this when that skill starts a new run |

### Types

| `type` | JSON schema type used for `set_fields` |
| - | - |
| `text` | `string` |
| `bool` | `boolean` |
| `int` | `integer` |
| `float` | `number` |
| `list` | `array` |
| `categorical` | `string`, plus an `enum` from `enum_values` |
| `json` | `string` |
| `any` | `string` |

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

### `enum_values`

```yaml theme={null}
replacement_reason:
  type: categorical
  enum_values: [lost, stolen, damaged, not_received]
```

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.

```yaml theme={null}
# the user's stated decision, recorded by the LLM
replacement_reason:
  type: categorical
  enum_values: [lost, stolen, damaged, not_received]
  llm_settable: true

# derived by a tool; the LLM must never invent it
account_replace_eligible:
  type: bool
```

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.

`llm_settable` is **skill memory only**. A project field with this flag fails
`rasa train`.

## Visibility

| Scope | Readable by |
| - | - |
| `schema.public` | The owning skill, and every other skill |
| `schema.private` | The owning skill only |
| project (root `memory.yml`) | Every skill |

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.

## Lifetime

Skill public and private memory is **working state for the current run**, not
session-long truth.

* After a skill **completes** or is **cancelled**, its live values **stay**.
  Other skills can still read that skill's **public** fields (for example a
  selected card id). Private fields stay set but are not readable by others.
* When the **same skill starts again** in the session (it is no longer on the
  stack), the engine resets that skill's public and private fields to
  `initial_value` (or `null` if unset). Tracker
  history still holds the old values.
* **Interrupt and resume** do not reset: the parked skill keeps its memory.
* **`project.*` is never cleared** because a skill finished or started again.
  Put facts that must last the whole conversation there (`authenticated`,
  verified customer id), not in skill public.

## Access control

```yaml theme={null}
access:
  deny_read:
    - card_replace.card_list
  deny_write:
    - project.authenticated
```

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:

| Declared in | Fully-qualified name | Referenced in conditions as | Referenced in instruction prose as | Referenced in responses as | Referenced in tools (`context.memory`) as |
| - | - | - | - | - | - |
| `skills/card_replace/memory.yml` | `card_replace.replacement_reason` | `session.card_replace.replacement_reason` | `@memory.card_replace.replacement_reason` | `{session.card_replace.replacement_reason}` | `replacement_reason` or `card_replace.replacement_reason` |
| root `memory.yml` | `project.authenticated` | `session.project.authenticated` | `@memory.project.authenticated` | `{session.project.authenticated}` | `authenticated` or `project.authenticated` |

Use **`session.*`** in structured places (`requires:`, `if:`, tool parameters).
Use **`@memory.*`** when instruction prose needs the live value inline.
Use **`{session.*}`** in response templates. Do not put `session.*` in free
prose — train fails and suggests `@memory.…` instead.

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:

```python theme={null}
reason = context.memory.get("replacement_reason")
selected_card_id = context.memory.get("card_disambiguation.selected_card_id")
```

`system.*` is reserved for engine internals and can never be declared.
`@memory.system.*` is not supported.

> [!NOTE]
>
> 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.*` / `@memory.card_replace.*`.

## See also

* [Memory concept](https://rasa.com/docs/mantle/concepts/memory.md): when to declare what
* [Conditions](https://rasa.com/docs/mantle/reference/conditions.md): referencing entries in `requires:` / `if:`
* [skill.md](https://rasa.com/docs/mantle/reference/skill-md.md): `@memory` in instruction prose
* [Tools reference](https://rasa.com/docs/mantle/reference/tools.md): `context.memory` read/write rules


## Related topics

- [Memory](https://rasa.com/docs/mantle/concepts/memory.md)
- [Authoring files](https://rasa.com/docs/mantle/reference/schemas/index.md)
- [agent.yml](https://rasa.com/docs/mantle/reference/agent-yml.md)
- [Tools reference](https://rasa.com/docs/mantle/reference/tools.md)
- [skill.md](https://rasa.com/docs/mantle/reference/skill-md.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.