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

# 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.</Note>

## 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 theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "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 theme={"theme":{"light":"github-light","dark":"github-dark"}}
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 theme={"theme":{"light":"github-light","dark":"github-dark"}}
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.
</Warning>

### Full example: returning caller

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
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 theme={"theme":{"light":"github-light","dark":"github-dark"}}
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](/analytics/dashboards/introduction) 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.
</Note>

## 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.
</Note>

## 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.
</Warning>

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

<AccordionGroup>
  <Accordion title="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.
  </Accordion>

  <Accordion title="Can I check if any memory exists?">
    Yes. Use Python's `in` operator:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    if "booking_day" in conv.memory:
        # Memory exists for this key
    ```
  </Accordion>

  <Accordion title="Can I prevent accidental overwrites?">
    Not natively – the latest write always wins. Guard against overwrites in your code:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    if "cheese_type" not in conv.memory:
        conv.state["cheese_type"] = value
    ```
  </Accordion>

  <Accordion title="When are memory writes committed?">
    Memory is persisted at the end of the conversation, after the [end function](/tools/end-tool) executes. Values set in `conv.state` during a function are not available in `conv.memory` until the next call.
  </Accordion>

  <Accordion title="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.
  </Accordion>

  <Accordion title="How do I get all stored memory fields?">
    Use the `fields()` method:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    all_memory = conv.memory.fields()  # Returns a dict of all fields
    ```
  </Accordion>

  <Accordion title="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 theme={"theme":{"light":"github-light","dark":"github-dark"}}
    # 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.
  </Accordion>
</AccordionGroup>
