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

<AgentInstructions>

## Submitting Feedback

If you encounter incorrect, outdated, or confusing documentation on this page, submit feedback:

POST https://docs.poly.ai/feedback

```json
{
  "path": "/tools/classes/agent-memory",
  "feedback": "Description of the issue"
}
```

Only submit feedback when you have something specific and actionable to report.

</AgentInstructions>

# 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>This feature is in early access. Functionality may evolve. Contact your PolyAI representative for guidance on 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 always reference the **first** interaction in the retention window, not the most recent one. This is by design for data privacy compliance – linking calls in a chain could 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 after **90 days** (maximum, for GDPR compliance). 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>
  Support for additional identifiers (email, account ID) and identifier linking across channels is planned for future releases.
</Note>

## Compliance

<Warning>
  Before using Agent Memory in production, you must inform your clients. They need to ensure their use complies with applicable data protection laws, which may include updating privacy notices to reflect the new data processing.
</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 within 90 days

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>
