Database Persistence

Persist conversations, runs, and traces to PostgreSQL, SQLite, Redis, and vector stores

Persist every agent turn to external databases — conversations resume across restarts and you can query run history later.

python
import os
from praisonaiagents import Agent, MemoryConfig
from praisonaiagents import db

agent = Agent(
    name="Assistant",
    instructions="You are a helpful assistant.",
    memory=MemoryConfig(
        db=db(database_url=os.getenv("PRAISON_CONVERSATION_URL")),
        session_id="user-123",
    ),
)
agent.start("Hello!")

The user continues a conversation; database hooks persist each turn so the session survives restarts.

Diagram

Agent.start

DbAdapter hooks

Conversation store

State store

Knowledge store

Quick Start

  1. Simple usage
    python
    import os
    from praisonaiagents import Agent
    from praisonaiagents import db
    
    agent = Agent(
        name="Assistant",
        instructions="You are a helpful assistant.",
        memory=db(database_url=os.getenv("PRAISON_CONVERSATION_URL", "sqlite:///conversations.db")),
    )
    agent.start("Hello!")
  2. With session continuity
    python
    import os
    from praisonaiagents import Agent, MemoryConfig
    from praisonaiagents import db
    
    store = db(database_url=os.getenv("PRAISON_CONVERSATION_URL"))
    
    agent = Agent(
        name="Assistant",
        memory=MemoryConfig(db=store, session_id="session-001"),
    )
    agent.start("My favourite colour is blue")
    
    # Later run — same session_id restores history
    agent2 = Agent(
        name="Assistant",
        memory=MemoryConfig(db=store, session_id="session-001"),
    )
    agent2.start("What's my favourite colour?")
  3. CLI with persistence
    bash
    export PRAISON_CONVERSATION_URL="postgresql://localhost/praisonai"
    praisonai persistence doctor
    praisonai persistence run --session-id demo "Remember my name is Alice"
    praisonai persistence resume --session-id demo --continue "What's my name?"

How It Works

Diagram
Conversation storeDbAdapterAgentUserConversation storeDbAdapterAgentUserstart(prompt)on_agent_start(session_id)load history (incl. tool_calls / tool_call_id)previous messageson_user_messageon_assistant_message (with tool_calls)on_tool_message (tool_call_id)on_agent_message (final answer)reply
StorePurposeExample backends
ConversationChat history, tool callsPostgreSQL, MySQL, SQLite, Supabase, SurrealDB
StateRuns, traces, metadataRedis, MongoDB, DynamoDB, Firestore
KnowledgeVectors / RAGQdrant, ChromaDB, Pinecone, Weaviate

On first chat, the adapter loads prior messages for the session_id. Each turn writes user and agent messages automatically — no manual save calls.


Configuration Options

Pass a db() instance via memory= (or MemoryConfig(db=…, session_id=…) for explicit sessions).

ParameterTypeDefaultDescription
database_urlstrNoneConversation store URL
state_urlstrNoneState / run history store URL
knowledge_urlstrNoneVector or knowledge store URL
conversation_optionsdictNoneBackend-specific kwargs passed only to the conversation store factory
state_optionsdictNoneBackend-specific kwargs passed only to the state store factory
knowledge_optionsdictNoneBackend-specific kwargs passed only to the knowledge store factory
init_retry_cooldownfloat30.0Seconds to wait before retrying a failed store init

The three per-store dicts mirror PersistenceConfig and each routes to exactly one store factory.

python
import os
from praisonaiagents import Agent, MemoryConfig
from praisonaiagents import db

agent = Agent(
    name="Researcher",
    memory=MemoryConfig(
        db=db(
            database_url=os.getenv("PRAISON_CONVERSATION_URL"),
            state_url=os.getenv("PRAISON_STATE_URL"),
            knowledge_url=os.getenv("PRAISON_KNOWLEDGE_URL"),
        ),
        session_id="research-42",
    ),
)

Multi-backend configuration

Per-store option dicts send backend-specific kwargs to a single store, so mixing Postgres, Redis, and Qdrant never leaks an unrelated kwarg.

python
from praisonaiagents import Agent, MemoryConfig, db

store = db(
    database_url="postgresql://localhost/praisonai",
    state_url="redis://localhost:6379",
    knowledge_url="http://localhost:6333",
    conversation_options={"sslmode": "require"},   # Postgres-only
    state_options={"decode_responses": True},       # Redis-only
    knowledge_options={"prefer_grpc": True},         # Qdrant-only
)

agent = Agent(name="Assistant", memory=MemoryConfig(db=store, session_id="s1"))

Each kwarg reaches exactly one factory:

Diagram

Legacy **options (deprecated)

Per-store options (new)

broadcasts to all

broadcasts to all

broadcasts to all

conversation_options

create_conversation_store

state_options

create_state_store

knowledge_options

create_knowledge_store

**options bag

Environment variables

bash
export PRAISON_CONVERSATION_URL="postgresql://localhost:5432/praisonai"
export PRAISON_STATE_URL="redis://localhost:6379"
export PRAISON_KNOWLEDGE_URL="http://localhost:6333"

Default session ID

If you omit session_id, PraisonAI generates a fresh ID per instance (UTC hour + agent hash + random suffix):

Format: YYYYMMDDHH-{agent_hash[:6]}-{uuid4_hex[:8]}
Example: 2026072914-a1b2c3-9f4e7d21

Custom DB Adapter

Any object with the right method shape works as a DB adapter — no base class, no import required.

python
from praisonaiagents import Agent, MemoryConfig

agent = Agent(
    name="Assistant",
    instructions="Help the user.",
    memory=MemoryConfig(db=MyDbAdapter(), session_id="chat-42"),
)
agent.start("Read a.txt and summarise it")

The adapter persists each turn and returns prior messages on on_agent_start, so the session resumes across restarts.

Required vs optional methods

Required methodWhat it does
on_agent_start(agent_name, session_id, user_id=None, metadata=None)Return the resumed message list (as DbMessage instances)
on_user_message(session_id, content, metadata=None)Persist a user turn
on_agent_message(session_id, content, metadata=None)Persist a plain assistant turn
on_tool_call(session_id, tool_name, arguments, result, metadata=None)Persist a tool invocation
on_agent_end(session_id, metadata=None)Flush / finalise the turn
Optional methodWhat it does
on_assistant_message(session_id, content, tool_calls=None, metadata=None)Persist an assistant turn with structured tool_calls
on_tool_message(session_id, content, tool_call_id, metadata=None)Persist a role="tool" result linked to tool_call_id

The two optional methods (PR #5082) let a resumed session hand the model the same tool transcript the JSON store already keeps.

Minimal adapter

python
class MyDbAdapter:
    def on_agent_start(self, agent_name, session_id, user_id=None, metadata=None):
        return []  # return DbMessage list to resume

    def on_user_message(self, session_id, content, metadata=None):
        ...

    def on_agent_message(self, session_id, content, metadata=None):
        ...

    # Optional — implement these to persist tool turns faithfully
    def on_assistant_message(self, session_id, content, tool_calls=None, metadata=None):
        ...

    def on_tool_message(self, session_id, content, tool_call_id, metadata=None):
        ...

Reference: PraisonAI PR #5082 (fixes #5075).


Docker (local development)

bash
docker run -d --name praison-postgres -p 5432:5432 \
  -e POSTGRES_PASSWORD=praison123 \
  -e POSTGRES_DB=praisonai postgres:16

docker run -d --name praison-redis -p 6379:6379 redis:7
docker run -d --name praison-qdrant -p 6333:6333 qdrant/qdrant

CLI commands

bash
praisonai persistence doctor
praisonai persistence status
praisonai persistence migrate
praisonai persistence export --session-id my-session --output backup.jsonl
praisonai persistence import --file backup.jsonl

Async-Safe Initialisation

The DatabaseAdapter‘s async callbacks (on_agent_start, on_user_message, on_agent_message, on_tool_call, on_agent_end) never block the event loop. Store construction runs off-loop via asyncio.to_thread, so FastAPI handlers, Jupyter notebooks, and async test suites all work without stalls on cold startup.


Transient Failure Handling

When a database is temporarily unavailable (bad config, network blip, cloud auth glitch), the adapter records the failure and waits before re-attempting. This prevents hammering a down backend on every agent callback while still recovering automatically once the backend is back.

The cool-down period is configurable via init_retry_cooldown (default 30 seconds):

python
from praisonai.db.adapter import DatabaseAdapter

adapter = DatabaseAdapter(
    database_url="postgresql://localhost/praisonai",
    init_retry_cooldown=10.0,  # try again 10s after a failed init
)
ParameterTypeDefaultDescription
init_retry_cooldownfloat30.0Seconds to wait before retrying a failed store init

After a soft init failure (raised during _ainit_stores()), the adapter pauses for init_retry_cooldown seconds before re-running store construction on the next callback. Persistence resumes automatically once the backend becomes reachable — no process restart needed.

Best Practices

Use explicit session_id for continuity

The auto ID is a fresh, per-instance value — a new instance gets a new random suffix and starts a new session. Set MemoryConfig(session_id=…) for any resumable case where users return to the same thread across restarts.

Read credentials from the environment

Never hardcode database URLs — use os.getenv("PRAISON_CONVERSATION_URL").

Run doctor before production

praisonai persistence doctor validates connectivity for conversation, state, and knowledge stores.

Separate stores by concern

Conversation history, run traces, and vectors scale differently — configure database_url, state_url, and knowledge_url independently.


Related