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

# User Memories

> Per-user facts that persist across sessions

User memories are the layer above session storage. While session storage keeps a single conversation coherent, user memories let the agent remember things about a specific person across every conversation they have. "Prefers concise answers." "This user is on the enterprise tier." "Their renewal is in November."

The data lives in the same Postgres schema as session storage, scoped by `user_id` instead of `session_id`. Each user's memories are completely isolated. One user's preferences never leak into another's conversations, even when they interact with the same agent. The two layers turn on independently, so you can have user memories without session storage (or vice versa).

```mermaid theme={"dark"}
graph LR
    AM[("Alice's User Memory")] --- Alice
    BM[("Bob's User Memory")] --- Bob
    Alice --- S1["Session 1"]
    Alice --- S2["Session 2"]
    Bob --- S3["Session 3"]
    S1 --- Agent["Support Bot"]
    S2 --- Agent
    S3 --- Agent
```

<Note>
  User memories are wired in automatically only for **Agno**. The DB connection and helpers on this page raise `NotImplementedError` on LangChain, OpenAI Agents SDK, and AWS Strands. See the [framework pages](/developers/frameworks/agno) for the manual integration story.
</Note>

## Configuration

The settings below live on `agent.agno_settings` and are toggled in the agent's Memory tab in [Agent Studio](https://app.xpander.ai). Both modes require session storage to be on, since user memories are stored alongside sessions.

<Card title="Configure memory in Agent Studio" icon="sliders" href="/guides/agents/memory-state">
  UI walkthrough for toggling user memory and switching between agentic and manual modes.
</Card>

| Setting          | What it controls                                                                      | Default |
| ---------------- | ------------------------------------------------------------------------------------- | ------- |
| `user_memories`  | Turn user-memory storage on. The agent reads memories during reasoning.               | `False` |
| `agentic_memory` | When on, the agent decides what to write. When off, your code writes through the SDK. | `False` |

Read which mode is active off the loaded agent:

```python theme={"dark"}
xpander_agent = await Agents().aget(agent_id="agt_01H...")
print(xpander_agent.agno_settings.user_memories)
print(xpander_agent.agno_settings.agentic_memory)
```

### Manual vs agentic mode

There are two flavors, and they're mutually exclusive:

* **Manual mode** (`user_memories = True`): the agent has access to a memory store, but it doesn't decide on its own when to write to it. Your code calls the memory APIs to add, update, and remove facts.
* **Agentic mode** (`agentic_memory = True`): the agent decides when to write memories. As conversations happen, it surfaces facts worth remembering and stores them automatically.

Pick one based on how much control you want. Agentic mode is closer to "talking to a human who pays attention"; manual mode is closer to "writing to a database the agent can read." Most product use cases benefit from agentic mode (the user gets the magical "it remembered" experience without you doing anything). Cases where you want strict control over what's stored (regulated data, customer-specific compliance rules) benefit from manual mode.

## Identifying the user

The agent identifies a user via the `User` object on the task input. When you create a task, pass the user details:

```python highlight={3,5-9} theme={"dark"}
from xpander_sdk import User

task = await agent.acreate_task(
    prompt="Remind me what we discussed last week about the budget review.",
    user_details=User(
        id="user-001",
        email="user@example.com",
        first_name="Example",
    ),
)
```

Here's what's happening:

1. **`email` is the only required field** on `User`. `id`, `first_name`, `last_name`, `additional_attributes`, and `timezone` are optional.
2. **The framework scopes the memory lookup** by these fields together, so anything stored against that user surfaces the next time they show up.
3. **Inside `@on_task`, the same object lives on `task.input.user`**: read it back when you need the active user.

If you don't pass user details on task creation, user memories are effectively off for that task: the agent has nowhere to scope the lookup. Make sure your invocation path threads a stable user identifier through every entry point (REST API, Slack, chat widget).

## Add, inspect, and edit memories

On Agno-backed agents memories are loaded into context automatically whenever `user_memories` or `agentic_memory` is on. You don't have to do anything for the agent to see them. Reach for the DB when you want to add memories yourself (manual mode), audit what's been stored, correct something the agent kept by mistake, or seed facts from another system.

`agent.aget_db()` returns the same `AsyncPostgresDb` connection the framework reads and writes through, so admin tools, migration scripts, and your handler all share one source of truth.

### Add a memory

`db.upsert_user_memory` inserts a new memory or updates an existing one by `memory_id`. In manual mode this is how your app writes memories; in agentic mode use it to seed or override what the agent stored on its own.

```python highlight={1,5,8-10} theme={"dark"}
from agno.db.schemas.memory import UserMemory
from xpander_sdk import Agents

agent = await Agents().aget(agent_id="agt_01H...")
db = await agent.aget_db(async_db=True)

await db.upsert_user_memory(
    UserMemory(
        user_id="user@example.com",
        memory="Prefers concise answers, no preamble.",
    )
)
```

Here's what's happening:

1. **`UserMemory.memory` carries the content** (not `content`). `user_id`, `topics`, `input`, and `feedback` are the other useful fields.
2. **`memory_id` is auto-generated on insert.** Pass an existing `memory_id` to update that record in place.
3. **The upsert is keyed by `memory_id`**, so the same call shape adds *or* edits depending on whether the ID exists.

A common pattern: when the user takes an action in your app that implies a preference (toggling a setting, completing onboarding with specific choices), upsert it as a memory so the agent picks it up the next time it talks to them. To seed memories from an external source (CRM, product DB) on first contact, wrap the upsert calls in an `@on_boot` hook or run them in the handler when `get_user_memories` returns empty.

### Inspect memories

`db.get_user_memories(user_id=...)` lists every memory keyed to one user. Each `UserMemory` exposes `memory_id`, `memory`, `topics`, `created_at`, and `updated_at`.

```python highlight={1,3} theme={"dark"}
memories = await db.get_user_memories(user_id="user@example.com")
for m in memories:
    print(m.memory_id, m.memory, m.created_at)
```

Use this to audit what agentic mode has accumulated, build a "your saved facts" view for the user, or check that a seed run actually wrote.

### Edit or delete

To edit, upsert with an existing `memory_id`:

```python highlight={3,5-7} theme={"dark"}
await db.upsert_user_memory(
    UserMemory(
        memory_id="mem_existing_id",
        user_id="user@example.com",
        memory="Prefers concise answers, no preamble. Avoid pleasantries.",
    )
)
```

To remove one record:

```python theme={"dark"}
await db.delete_user_memory(memory_id="mem_existing_id")
```

For bulk removal (a "forget me" request, a compliance purge):

```python theme={"dark"}
await db.delete_user_memories(
    memory_ids=["mem_1", "mem_2"],
    user_id="user@example.com",
)
```

### SDK reference

| Method                                         | Returns            | What it's for                                                                   |
| ---------------------------------------------- | ------------------ | ------------------------------------------------------------------------------- |
| `agent.aget_db(async_db=True)`                 | `AsyncPostgresDb`  | The Postgres handle scoped to this agent's schema. Sync form: `agent.get_db()`. |
| `db.get_user_memories(user_id)`                | `list[UserMemory]` | List every memory keyed to one user.                                            |
| `db.upsert_user_memory(UserMemory(...))`       | `UserMemory`       | Insert or update a memory by `memory_id`.                                       |
| `db.delete_user_memory(memory_id)`             | `None`             | Remove one record by ID.                                                        |
| `db.delete_user_memories(memory_ids, user_id)` | `None`             | Bulk delete.                                                                    |

The exact API surface is Agno's; consult the [Agno docs](https://docs.agno.com) for the full method list.

## Token cost considerations

User memories cost LLM calls. In agentic mode the agent decides when to extract memories from a conversation (one extra LLM call per session, sometimes more). On every turn, the relevant memories are loaded into context, which costs tokens.

For high-volume agents, the math matters: if your agent handles 100,000 conversations a month and each one triggers a memory-extraction call, that's 100,000 extra LLM calls. For most production cases the trade is worth it, but verify against your usage patterns.

## Troubleshooting

<AccordionGroup>
  <Accordion title="User memories don't appear in the agent's responses">
    First check that you're passing `user_details` (or `task.input.user`) on every invocation. Without an identifier, memories have nowhere to scope. Then confirm `user_memories` or `agentic_memory` is on via `xpander_agent.agno_settings`. If the user is brand new and the agent is in agentic mode, give it a few exchanges to find something worth remembering.
  </Accordion>

  <Accordion title="Agentic mode isn't writing any memories">
    The agent only writes when it decides a fact is worth remembering. Brief, transactional conversations may produce nothing. Verify `agentic_memory=True` (not `user_memories=True`; they're mutually exclusive) and that the conversation has at least a few exchanges with substantive content.
  </Accordion>

  <Accordion title="A memory the agent stored is wrong or sensitive">
    Look it up with `db.get_user_memories(user_id=...)`, find the offending row, and call `db.delete_user_memory(memory_id=...)`. For systematic prevention, switch from agentic to manual mode so the agent can no longer write on its own.
  </Accordion>

  <Accordion title="Memories I added through `db.upsert_user_memory` aren't showing up">
    Confirm the `UserMemory.user_id` matches the `User.email` (or `User.id` if you're using IDs consistently) that the agent will see at runtime. Mismatched scoping is the most common cause. Then verify the agent is loaded fresh after the write; cached `Agent` instances from before the insert won't reflect it until the next `Agents().aget(...)`.
  </Accordion>

  <Accordion title="Token usage is climbing as a user accumulates memories">
    Memories all load into context. Prune aggressively: iterate `get_user_memories`, drop stale rows with `delete_user_memory` or `delete_user_memories`, and consider moving long-form context to a [knowledge base](/developers/knowledge/semantic-search) where it's retrieved on demand instead of always-on.
  </Accordion>
</AccordionGroup>

## What to read next

<CardGroup cols={2}>
  <Card title="Agent memories" icon="building" href="/developers/memory/agent-memories">
    Org-wide knowledge, the layer above user memories.
  </Card>

  <Card title="Session storage" icon="database" href="/developers/memory/session-storage">
    Single-conversation memory, the layer below.
  </Card>

  <Card title="Agno framework" icon="cube" href="/developers/frameworks/agno">
    The full args-dict reference for what `aget_args` wires up.
  </Card>

  <Card title="Output Response Filtering" icon="filter" href="/developers/tools/output-response-filtering">
    Trim chatty connector responses before they reach the LLM.
  </Card>
</CardGroup>
