Skip to main content

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 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).
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 for the manual integration story.

Configuration

The settings below live on agent.agno_settings and are toggled in the agent’s Memory tab in Agent Studio. Both modes require session storage to be on, since user memories are stored alongside sessions.

Configure memory in Agent Studio

UI walkthrough for toggling user memory and switching between agentic and manual modes.
SettingWhat it controlsDefault
user_memoriesTurn user-memory storage on. The agent reads memories during reasoning.False
agentic_memoryWhen 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:
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:
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.
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.
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:
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:
await db.delete_user_memory(memory_id="mem_existing_id")
For bulk removal (a “forget me” request, a compliance purge):
await db.delete_user_memories(
    memory_ids=["mem_1", "mem_2"],
    user_id="user@example.com",
)

SDK reference

MethodReturnsWhat it’s for
agent.aget_db(async_db=True)AsyncPostgresDbThe 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(...))UserMemoryInsert or update a memory by memory_id.
db.delete_user_memory(memory_id)NoneRemove one record by ID.
db.delete_user_memories(memory_ids, user_id)NoneBulk delete.
The exact API surface is Agno’s; consult the Agno docs 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

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.
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.
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.
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(...).
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 where it’s retrieved on demand instead of always-on.

Agent memories

Org-wide knowledge, the layer above user memories.

Session storage

Single-conversation memory, the layer below.

Agno framework

The full args-dict reference for what aget_args wires up.

Output Response Filtering

Trim chatty connector responses before they reach the LLM.