Skip to main content

Persistence Overview

PraisonAI supports automatic database persistence for conversations, knowledge, and state management across 22 database backends.

Quick Start

Enable persistence in 2 lines:
from praisonaiagents import Agent

agent = Agent(
    name="Assistant",
    memory={
        "db": "postgresql://localhost/mydb",
        "session_id": "my-session"
    }
)
response = agent.start("Hello!")  # Auto-persists
print(response)

Installation

pip install "praisonaiagents[tools]"

Supported Backends

CategoryBackendsCount
ConversationPostgreSQL, MySQL (including MySQLConversationStore), SQLite, SingleStore, Supabase, SurrealDB, Turso7+
KnowledgeQdrant, ChromaDB, Pinecone, Weaviate, LanceDB, Milvus, PGVector, Redis, Cassandra, ClickHouse, MongoDB Vector, Couchbase, SingleStore Vector, SurrealDB Vector, Upstash Vector, LightRAG, LangChain, LlamaIndex, CosmosDB19
StateRedis, MongoDB, DynamoDB, Firestore, Upstash, Memory, GCS7

Backend Aliases

The persistence registry provides user-friendly aliases for common databases: Conversation stores:
  • neon, cockroachdb, xata β†’ postgres
  • asyncpg, postgres_async β†’ async_postgres
  • aiomysql, mysql_async β†’ async_mysql
  • sqlite_sync β†’ sync_sqlite
  • aiosqlite, sqlite_async β†’ async_sqlite
  • libsql β†’ turso
Knowledge stores:
  • chromadb β†’ chroma
  • mongodb_atlas, cosmos, azure_cosmos β†’ Vector store variants
  • llama_index, langchain_adapter β†’ Framework adapters
State stores:
  • motor, mongodb_async β†’ async_mongodb

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚           praisonaiagents.Agent         β”‚
β”‚         (memory={db: "..."})            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                  β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚     Memory/Knowledge/State Adapters     β”‚
β”‚         (DbAdapter Protocol)            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                  β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚        Database Backends Layer          β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Conversationβ”‚  Knowledge  β”‚    State    β”‚
β”‚   Store     β”‚    Store    β”‚    Store    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Key Features

  • Zero Config: SQLite works out of the box with memory=True
  • Session Resume: praisonai session resume <id> is a first-class restore β€” it brings back chat history, model, and agent name, not just a transcript. Pass a follow-up prompt to continue immediately: praisonai session resume <id> "<prompt>".
  • Runtime State Mirroring: lightweight per-turn artefacts for native↔plugin handoff (opt-in via SessionConfig.mirror_runtime_state)
  • Lazy Loading: No performance impact until used
  • CLI Support: praisonai persistence doctor/run/resume

Concurrency & Thread Safety

Persistence sync wrappers (get_session, add_message, get, set, delete, list_keys, clear, close, …) are safe to call from any context β€” plain sync scripts, worker threads, or code running inside a live event loop (FastAPI, Streamlit, background tasks). They route through a canonical async bridge, so you will not see RuntimeError: This event loop is already running anymore. Async hooks offloading (PR #1829): Sync conversation stores plugged into async hooks are now automatically offloaded via asyncio.to_thread() rather than blocking the event loop. The orchestrator uses isinstance(store, AsyncConversationStore) to determine whether to await the store directly or wrap it in thread execution. As of PR #1763, you can also opt into the dedicated sync_sqlite backend (mode="sync") β€” it provides per-call connection locking (threading.RLock) for multi-agent scenarios where you’d rather not depend on the legacy sync wrappers. The DbAdapter’s lazy store initialisation is also race-free: the first thread to touch the adapter constructs the stores, and subsequent threads see the ready instance. The session cache inside PersistenceOrchestrator is also thread-safe as of PR #1609. Reads return a deepcopy of the cached ConversationSession, so multiple agents sharing one orchestrator can read/update sessions concurrently without races. The JSON session store (DefaultSessionStore) reloads from disk under FileLock for every mutator as of PR #1709 (metadata) and PR #1724 (agent info, gateway info, clear). Two processes pointed at the same ~/.praisonai/sessions/ directory can now interleave add_message and set_agent_info / clear_session / set_gateway_info calls without losing messages. Reads reload from disk under lock on every get_chat_history / get_session call. HierarchicalSessionStore inherits the JSON store’s reload-under-lock guarantees and additionally preserves parent_id, children_ids, and snapshots across all mutators (PR #1745). UI hosts that fork sessions or take snapshots are safe to run alongside an agent’s auto_save writer.
These improvements were added in PraisonAI PR #1466, #1609, #1709, #1724, and #1727 to ensure robust multi-threaded operation in production environments.
The JSON session store (DefaultSessionStore) reloads from disk under FileLock for every mutator as of PR #1709 (metadata), PR #1724 (agent info, gateway info, clear), and PR #1727 (LocalManagedAgent._persist_state). PRs #1759 and #1764 extended the same FileLock-guarded reload to the read path (get_chat_history, get_session, get_sessions_by_agent), so two processes pointed at the same ~/.praisonai/sessions/ directory observe each other’s writes without any stale-cache window.

Schema Versioning (PR #1829)

New unified schema system: All SQL conversation stores now inherit from _SQLConversationStoreBase with consistent SCHEMA_VERSION = "1.0.0". This applies to PostgreSQL, MySQL (new), and all existing SQL backends uniformly. The base class provides:
  • Standardized table schemas for sessions and messages
  • Dialect-specific type mappings (_id_type, _json_type, _float_type)
  • Unified retry logic with max_retries and retry_delay parameters
  • Automatic serverless database detection and exponential backoff
  • Consistent table_prefix handling across all SQL stores
MySQL backend: The new MySQLConversationStore (from praisonai.persistence.conversation.mysql_new) includes PlanetScale auto-retry with exponential backoff for serverless cold-starts.

Schema Migration (PR #1597)

Breaking change: All async conversation stores (async_sqlite, async_postgres, async_mysql) now default to table_prefix="praison_" (previously "praisonai_"). Sync stores were already on praison_.
If you ran an async store before this release, either:
  1. Pass table_prefix="praisonai_" explicitly to keep your old tables, or
  2. Rename existing tables: ALTER TABLE praisonai_sessions RENAME TO praison_sessions; (and likewise for _messages).
New columns: Async session/message schemas now include state (sessions), and tool_calls + tool_call_id (messages). The store creates them automatically on first connect via CREATE TABLE IF NOT EXISTS. Existing tables on a pre-#1597 schema will not auto-migrate; add the columns with:
ALTER TABLE praison_sessions ADD COLUMN state TEXT;
ALTER TABLE praison_messages ADD COLUMN tool_calls TEXT;
ALTER TABLE praison_messages ADD COLUMN tool_call_id TEXT;

Custom Stores & Aliases

Register custom storage backends with the persistence registry, including optional aliases:
from praisonai.persistence import get_default_registry
from praisonaiagents import Agent

def my_custom_store(connection_string, **kwargs):
    return CustomStoreImplementation(connection_string, **kwargs)

# Register on the shared default so Agent(memory={"db": "my_store://..."}) sees it
registry = get_default_registry("conversation")
registry.register_store(
    "my_store",
    my_custom_store,
    aliases=("mine", "ms", "custom_db"),
)

# All of these now resolve to my_custom_store
agent = Agent(memory={"db": "my_store://config"})
agent = Agent(memory={"db": "mine://config"})       # alias
agent = Agent(memory={"db": "ms://config"})         # alias
agent = Agent(memory={"db": "custom_db://config"})  # alias
The shared default registry is what agents use at runtime. Constructing a fresh StoreRegistry(...) registers backends only on that isolated instance β€” use get_default_registry("conversation") when you want Agent(memory=...) to see your backend immediately.
The register_store() method is the canonical API. The register() method is preserved as a backward-compatible alias.

Next Steps