Memory

Give agents persistent memory across sessions with pluggable backends

Memory lets agents remember past conversations, user preferences, and context across sessions.

Grow memory autonomously

With self_improve="background" and memory=True, the same guarded post-turn review that autonomously grows skills can also autonomously persist durable facts and preferences via store_memory — off the hot path, with zero cost to reply latency and zero context pollution during the live turn. See Self-Improving Agents.

python
from praisonaiagents import Agent

agent = Agent(
    name="Assistant",
    instructions="You are a helpful personal assistant.",
    memory=True,  # memory is isolated per instance — pass user_id="…" to share/persist across runs
)

agent.start("My name is Alice and I prefer concise answers.")

The user shares preferences in chat; the agent recalls them in later sessions via the configured memory backend.

Diagram

Memory Flow

💬 User Input

💾 Memory Store

🔍 Recall Context

✅ Contextual Response

Quick Start

  1. Level 1 — Bool (simplest)

    Turn on memory with a single flag — the agent remembers across turns using the default file backend.

    python
    from praisonaiagents import Agent
    
    agent = Agent(
        name="Assistant",
        instructions="You are a helpful assistant.",
        memory=True,  # memory is isolated per instance — pass user_id="…" to share/persist across runs
    )
    agent.start("Remember that I work in software engineering.")
  2. Level 2 — String (pick a backend)

    Pass a backend name to choose where memories are stored.

    python
    from praisonaiagents import Agent
    
    agent = Agent(
        name="Assistant",
        instructions="You are a helpful assistant.",
        memory="sqlite",
    )
    agent.start("Remember that I prefer concise answers.")
  3. Level 3 — Config class (full control)

    Use MemoryConfig to scope memory per user and auto-extract facts.

    python
    from praisonaiagents import Agent, MemoryConfig
    
    agent = Agent(
        name="Assistant",
        instructions="You are a helpful assistant.",
        memory=MemoryConfig(
            backend="sqlite",
            user_id="alice",
            auto_memory=True,
        ),
    )
    agent.start("What do you know about my preferences?")
  4. Level 4 — Config with continuous learning

    Add LearnConfig to build a long-term persona alongside session memory.

    python
    from praisonaiagents import Agent, MemoryConfig, LearnConfig
    
    agent = Agent(
        name="Assistant",
        instructions="You are a helpful assistant.",
        memory=MemoryConfig(
            backend="sqlite",
            user_id="alice",
            learn=LearnConfig(persona=True, insights=True, mode="agentic"),
        ),
    )
    agent.start("I always want answers formatted as bullet points.")

How It Works

Diagram
MemoryAgentUserMemoryAgentUserMessageStore conversation turnRetrieve relevant contextPast contextContext-aware response
PhaseWhat happens
1. StoreEach conversation turn is saved to the memory backend
2. RetrieveRelevant past context is recalled before responding
3. RespondAgent answers with full historical context available

Which Backend to Choose?

Diagram
graph TB
    Q{What's your setup?}
    Q -->|Prototype / single server| F[file backend\ndefault, zero deps]
    Q -->|Single server, persistent| S[sqlite backend]
    Q -->|Vector search / RAG| C2[chroma backend]
    Q -->|Multi-agent shared memory| M[mem0 or mongodb]
    Q -->|Self-hosted decay-weighted memory| D[dakera]
    Q -->|Redis / Postgres| DB[use db\(url=...\)]
    Q -->|Anthropic model with long context| C[claude_memory=True]

    classDef decision fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef backend fill:#6366F1,stroke:#7C90A0,color:#fff

    class Q decision
    class F,S,C2,M,D,DB,C backend

Configuration Options

Card

Full list of options, types, and defaults — MemoryConfig

The most common options at a glance:

OptionTypeDefaultDescription
backendstr"file"Storage backend: file, sqlite, chroma, mem0, mongodb, dakera, in_memory. Aliases: chromadb/rag → chroma, none → in_memory.
db`DbAdapter \None`NoneLive database adapter (e.g. db(database_url="sqlite:///path/to.db") or db.SQLiteDB(path=...)). Must implement on_agent_start / on_user_message / on_agent_message — a bare path string raises TypeError at construct. For file paths without an adapter, use backend="sqlite" with config={"short_db": "…", "long_db": "…"} instead.
user_id`str \None`NoneUser identifier for scoped memory. Leaving it None now yields a per-instance agent-<uuid> id (not a shared "default") — set it to share/persist across runs
auto_memoryboolFalseAuto-extract and store key facts
learn`bool \LearnConfig \None`NoneEnable continuous learning
historyboolFalseAuto-inject session history into context
prefetchOptional[bool]None (auto)Auto: on for query-based backends (sqlite, chroma, mem0), off for FileMemory. True / False force it on/off. See Memory Prefetch.
prefetch_limitint5Max number of memories injected per turn
prefetch_token_budgetint512Estimated-token cap for the injected memory block (truncated with … if exceeded)

Combining backend and config

Pass a config= dict alongside backend= to hand provider-specific settings to the store — the outer backend= still decides which store is built.

python
from praisonaiagents import Agent
from praisonaiagents.config import MemoryConfig

agent = Agent(
    name="Assistant",
    instructions="Remember facts across turns.",
    memory=MemoryConfig(
        backend="sqlite",
        user_id="alice",
        config={"short_db": "./mem/short.db"},
    ),
)
agent.start("My favourite colour is teal.")

An explicit provider or backend key inside config= wins; otherwise the outer backend= fills in.

What you wroteBackend actually used
MemoryConfig(backend="sqlite")sqlite
MemoryConfig(backend="sqlite", config={"short_db": "…"})sqlite
MemoryConfig(backend="sqlite", config={"provider": "file"})file (inner wins)
MemoryConfig(backend="sqlite", config={"backend": "file"})file (inner wins)
MemoryConfig(backend="file")file

Common Patterns

Pattern 1 — User-scoped memory

python
from praisonaiagents import Agent, MemoryConfig

agent = Agent(
    instructions="You are a personal assistant.",
    memory=MemoryConfig(user_id="user_alice", backend="sqlite"),
)
response = agent.start("What have I told you about my work preferences?")
print(response)

Pattern 2 — History injection for conversation continuity

python
from praisonaiagents import Agent, MemoryConfig

agent = Agent(
    instructions="You are a support agent.",
    memory=MemoryConfig(history=True, history_limit=5),
)
agent.start("Continue from where we left off.")

Pattern 3 — Recalled context at turn start

python
from praisonaiagents import Agent, MemoryConfig

writer = Agent(
    name="writer",
    instructions="Store user facts.",
    memory=MemoryConfig(backend="file", user_id="alice"),
)
writer.memory.remember("User prefers metric units and dark mode.")

reader = Agent(
    name="reader",
    instructions="Answer using recalled context.",
    memory=MemoryConfig(
        backend="file",
        user_id="alice",
        prefetch=True,          # required on file; sqlite/chroma/mem0 prefetch automatically
        prefetch_limit=5,
        prefetch_token_budget=512,
    ),
)

reader.start("What are my UI preferences?")

The agent sees a ## Recalled memories block in its system prompt before the first model call — no extra tool call needed.


Quick API — remember, recall, forget

Three verbs give you explicit control over long-term facts — store, look up, and delete.

Diagram

Quick Memory API

📝 remember

💾 Long-term Store

🔍 recall

🗑️ forget

An agent with memory=True exposes its memory instance directly, so you can store and recall facts on demand.

python
from praisonaiagents import Agent

agent = Agent(
    name="Note Taker",
    instructions="Save and recall project facts on request.",
    memory=True,
)

# The agent's memory instance is available directly
agent.memory.remember("Sprint 42 ships on Friday")
matches = agent.memory.recall("When does sprint 42 ship?")

Use Memory() standalone in a plain script — no config, no agent required.

python
from praisonaiagents.memory import Memory

mem = Memory()  # no config needed — uses local SQLite
mem.remember("API rate limit is 1000 req/min")
for m in mem.recall("rate limit"):
    print(m["text"])
mem.forget(query="rate limit")

Prompt-ready context — get_context

get_context(query=...) returns retrieved memory as a single prompt-ready string for a given query — the same string Agent.get_memory_context() injects into the system prompt.

python
from praisonaiagents import Agent

agent = Agent(name="Assistant", instructions="Help the user.", memory=True)
agent.memory.remember("User is working on the climate dashboard project.")

ctx = agent.memory.get_context(query="What projects am I working on?")
print(ctx)

An empty or whitespace-only query intentionally returns "" — this avoids a LIKE "%%" scan that would pull in unrelated (and possibly other users’) records.

python
agent.memory.get_context(query="")   # -> ""  (nothing leaked)

Pass user_id="…" (or configure it on MemoryConfig) so user-scoped memories stay isolated when a single Memory instance is shared across users.

Parameters

MethodParameterTypeDefaultDescription
Memory()config`dict \None`NoneBackend config. Optional — omit for the default local backend.
Memory()verboseint0Verbosity level.
remembercontentstr(required)Fact to store in long-term memory.
remembermetadata`dict \None`NoneKeyword-only. Extra metadata stored with the record.
remember (returns)—str—The generated memory id.
recallquerystr(required)Search string.
recalllimitint5Keyword-only. Max results returned.
recall (returns)—list[dict]—Matching records, each with at least a text field.
forgetmemory_id`str \None`NoneKeyword-only. Delete by exact id. When no memory_type is passed, deletes from both tiers — short-term and long-term ids can collide because each tier has its own AUTOINCREMENT sequence, and the returned count reflects rows removed from both. Pass memory_type="short_term" or "long_term" via delete_memory() to scope the delete to a single tier.
forgetquery`str \None`NoneKeyword-only. Delete long-term records matching this query.
forget (returns)—int—Count of records deleted across both tiers when memory_id is used without a tier.
get_contextquery`str \None`NoneSearch string. Empty/whitespace returns "".
get_contextuser_id`str \None`NoneKeyword arg. Falls back to the configured user_id.
get_context (returns)—str—Prompt-ready context string ("" when nothing matches).

agent.memory.get_all_memories() returns every stored short-term and long-term record (each with a public type field of "short_term" or "long_term"). When a memory adapter is configured, it now correctly delegates to the adapter — earlier releases returned [] for every provider.

Which memory API?

Diagram

Auto-save every turn of a chat

Explicitly store & look up facts

Both

What do you need?

Agent(memory=True)

memory.remember() / recall() / forget()

Agent(memory=True) then agent.memory.remember(…)

Interaction flow

Diagram
MemoryAgentUserMemoryAgentUser…days later, new session…“Remember our launch date is Nov 15.”remember(“launch date Nov 15”)memory_id“When is our launch?”recall(“launch date”)[{text: “launch date Nov 15”, …}]“November 15.”

Best Practices

Always set user_id when you want memory to persist or be shared

As of PraisonAI PR #3614, Agent(memory=True) without a user_id generates a fresh per-instance identifier (agent-<uuid>) and logs a warning. Memory is isolated per Agent object by default — safe, but not what you want if you re-create the agent between requests and expect it to remember the last conversation.

Pass user_id="alice" (or "tenant-42", "project-climate", etc.) to opt into a shared / persistent store that survives across processes.

As of PraisonAI #4203, this isolation now covers every backend — sqlite, chroma, mongodb, mem0, and learn derive per-agent default store paths and collection names from the same agent-<uuid> id, so two agents in one process no longer silently share one short_term.db / long_term.db / memory_store. Explicit user_id, short_db, long_db, rag_db_path, or collection_name still win. See Per-agent isolation across backends.

Diagram

No — one-shot

Yes — persist / share

Do you want memory
to survive re-creating
the Agent?

✅ Agent(memory=True)
per-instance, auto-isolated

✅ Agent(memory={‘user_id’: ‘…’})
shared across runs

Use auto_memory for fact extraction

Enable auto_memory=True to have the agent automatically identify and store important facts from each conversation — names, preferences, decisions — without extra code.

auto_memory=True runs the pattern extractor and persists the verbatim conversation turn to long-term memory, stamped with the configured user_id (and session_id). The raw turn is saved alongside the extracted facts, so arbitrary details the extractor would miss — a one-off codename, a date, an ID — still survive.

A brand-new Agent(memory=MemoryConfig(backend="sqlite", user_id="alice")) therefore recalls facts a previous instance stored under the same user_id — no prefetch=True and no explicit store_memory() or recall() call required. See Memory Prefetch → Automatic cross-instance recall.

Combine memory with learning

Set learn=True inside MemoryConfig to enable continuous learning alongside session memory. This gives agents both short-term context (memory) and long-term pattern recognition (learn).

Backend scaling path

Start with file, move to sqlite when you need durability, then mongodb (or chroma for vector search) when you deploy multiple agent instances that share memory. For Redis or Postgres, use memory=MemoryConfig(db=db(state_url="redis://...")) or db(database_url="postgresql://...") — see Redis persistence.

Choose remember/recall for facts, memory=True for conversation history

Agent(memory=True) auto-captures every conversation turn. remember() / recall() / forget() give you explicit control for fact storage — use them for structured knowledge you want to look up later. Both share the same long-term store when the agent’s memory instance is used.


Async safety

Task-callback memory writes (store_in_memory in Task.execute_callback) run on a worker thread on the async path. Parallel tasks batched into a single asyncio.gather(...) no longer stall the event loop on one slow embedding/DB write.

The built-in in-memory adapter is thread-safe: concurrent async writes from multiple parallel tasks cannot produce duplicate ids or lose entries.

No user-facing change: the async path automatically dispatches the write via asyncio.to_thread.

Session auto-save writes now run on a worker thread on the async path too. Agent(memory=MemoryConfig(auto_save="…")) persists via asyncio.to_thread on astart, _adelegate_to_backend, and each iteration of run_autonomous_async, so concurrent async agents sharing the same session store no longer serialise on one another’s file-lock + JSON write (PR #5258). An internal lock also serialises the shared save index, so two async runs sharing one Agent never double-persist the same turns.


Related