> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://poly-ai.ferndocs.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://poly-ai.ferndocs.com/_mcp/server.

# Agent memory

> Remember information about returning callers across separate conversations using persistent key-value storage.

Use Agent Memory when your agent needs to remember information about returning callers – preferences, past bookings, or verification status – across separate conversations. Without it, every call starts from scratch.

> **Note**
>
> Early access — contact your PolyAI representative before production use.

## How it works

Agent Memory is a key-value store attached to a user identifier (such as a phone number). Persist small, structured data between conversations so your agent can:

* Skip repeated questions for returning callers
* Greet callers by name or reference past interactions
* Resume interrupted conversations where the caller left off
* Track handoff history and resolution status

Memory is read **at the start of each turn** and cached for that turn. It is written **at the end of a conversation** – not on every turn.

You can:

* **Read memory** using `conv.memory.get("key")`
* **Write memory** by setting `conv.state["key"] = value` (if the key is listed in `state_keys`)

## Configuration

### Enable Agent Memory

Agent Memory is configured by PolyAI, not self-served. Contact your PolyAI representative to enable it for your project. They will apply a configuration equivalent to the example below on your behalf:

```json
{
  "memory": {
    "repeat_caller": {
      "analytics_enabled": true,
      "state_keys": ["booking_day", "preferred_language"],
      "identifier_blacklist": ["+440000000000"]
    }
  }
}
```

Use the field table below to decide what to request – which `state_keys` to persist, whether you need analytics, and any identifiers to exclude.

| Field                  | Description                                                                          |
| ---------------------- | ------------------------------------------------------------------------------------ |
| `analytics_enabled`    | Adds repeat caller metrics to Studio analytics dashboards                            |
| `state_keys`           | Keys from `conv.state` that should be saved to memory at the end of the conversation |
| `identifier_blacklist` | Optional list of identifiers to exclude (e.g., test phone numbers)                   |

Only include keys you explicitly need in `state_keys`. Memory is persisted at the end of the conversation, so only values set during the call are saved.

## Using memory in functions

### Reading memory

Use `conv.memory.get("key")` in any function to retrieve a previously stored value:

```python
def start_function(conv: Conversation):
    name = conv.memory.get("caller_name")
    if name:
        return {"utterance": f"Welcome back, {name}. How can I help you today?"}
```

### Writing memory

Set values in `conv.state` – they are persisted to memory at the end of the conversation if the key is listed in `state_keys`:

```python
def collect_name(conv: Conversation, caller_name: str):
    conv.state["caller_name"] = caller_name
    return {"content": f"Thanks, {caller_name}. I'll remember you next time."}
```

> **Warning**
>
> Only keys listed in `state_keys` in your config are persisted. Setting `conv.state["key"] = value` for a key not in `state_keys` will not save it to memory.

### Full example: returning caller

```python
def start_function(conv: Conversation):
    booking_day = conv.memory.get("booking_day")
    cheese_type = conv.memory.get("cheese_type")

    if booking_day and cheese_type:
        return {
            "utterance": f"I see you previously booked {cheese_type} for {booking_day}. Would you like to book again?"
        }
```

```python
def book_delivery(conv: Conversation, cheese_type: str, booking_day: str):
    conv.state["booking_day"] = booking_day
    conv.state["cheese_type"] = cheese_type
    return {"content": f"Booked {cheese_type} for {booking_day}."}
```

## Repeat caller analytics

When `analytics_enabled` is set to `true`, Agent Memory automatically tracks five metrics for each returning caller:

| Metric                         | Description                                       |
| ------------------------------ | ------------------------------------------------- |
| `REPEAT_CALLER_CONV_ID`        | Conversation ID of the caller's first interaction |
| `REPEAT_CALLER_DATETIME`       | Timestamp of the first interaction                |
| `REPEAT_CALLER_QA`             | QA outcome from the first interaction             |
| `REPEAT_CALLER_HANDOFF_REASON` | Reason for handoff in the first interaction       |
| `REPEAT_CALLER_HANDOFF_TO`     | Handoff destination from the first interaction    |

These metrics appear in your [dashboards](/agent-studio/custom-dashboards/overview) and can be used to track repeat caller patterns and resolution rates.

> **Note**
>
> Repeat caller metrics reference the **first** interaction in the retention window, not the most recent — chaining calls would allow identification beyond the retention period.

## Memory behavior

### Timing

* Memory is **fetched once per turn** from the memory service and cached. Multiple `conv.memory.get()` calls in the same turn do not trigger additional lookups.
* Memory is **written at the end of the conversation** (after the end function executes). Values set in `conv.state` during a function on turn 1 are not available through `conv.memory` on turn 2 of the same call.

### Persistence

Each write acts as a **patch** – it updates or adds specific keys without removing existing ones. If conversation 1 writes `{"cheese_type": "gouda"}` and conversation 2 writes `{"booking_day": "Friday"}`, the next lookup returns both fields.

### Expiry

All memory identifiers and fields expire at the end of your contracted retention period (for GDPR compliance). Retention periods vary by customer agreement – check your contract for specifics, or speak to your PolyAI representative. Once expired, data is automatically deleted and no longer accessible.

### Identifiers

The current supported identifier is the **caller's phone number**. This is set automatically – you do not need to configure it.

> **Note**
>
> Additional identifiers (email, account ID) and cross-channel linking are planned.

## Compliance

> **Warning**
>
> Before going live with Agent Memory, inform your clients so they can update privacy notices and confirm compliance with applicable data protection laws.

Key compliance considerations:

* Agent Memory is **not intended for automated decision-making** that significantly impacts end users
* Do not store sensitive PII in a single field – separate fields allow independent expiry
* Custom memory fields **cannot** be used to build caller profiles
* Timestamps stored in memory should be rounded to the hour to prevent linking calls across the retention window
* All data expires at the end of your contracted retention period

If your use case goes beyond analytics, call recovery, or personalisation, raise a request with your legal team before proceeding.

## FAQ

#### Can I store nested JSON?

Values are stored as JSON-encoded strings. You can store structured data in a single field, but it is recommended to keep fields flat so they can expire independently – especially for anything containing PII.

#### Can I check if any memory exists?

Yes. Use Python's `in` operator:

```python
if "booking_day" in conv.memory:
    # Memory exists for this key
```

#### Can I prevent accidental overwrites?

Not natively – the latest write always wins. Guard against overwrites in your code:

```python
if "cheese_type" not in conv.memory:
    conv.state["cheese_type"] = value
```

#### When are memory writes committed?

Memory is persisted at the end of the conversation, after the [end function](/agent-studio/tools/end-tool) executes. Values set in `conv.state` during a function are not available in `conv.memory` until the next call.

#### Does memory work across channels?

Currently, memory is scoped by phone number. Support for linking identifiers across channels (voice, SMS, webchat) is planned for future releases.

#### How do I get all stored memory fields?

Use the `fields()` method:

```python
all_memory = conv.memory.fields()  # Returns a dict of all fields
```

#### What read patterns does conv.memory support?

`conv.memory` is read-only and behaves like a mapping. Use whichever pattern is clearest for your case:

```python
# Membership check
if "booking_day" in conv.memory:
    ...

# Item access (raises KeyError if missing)
last_order = conv.memory["last_order_id"]

# Safe access with default
visits = conv.memory.get("visit_count", 0)

# All stored fields
all_memory = conv.memory.fields()

# Number of stored fields
count = len(conv.memory)
```

Writes go through `conv.state` for keys configured in `state_keys`; assigning directly to `conv.memory` is not supported.