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

# 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 theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
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.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    Agent[Agent.start] --> Hook[DbAdapter hooks]
    Hook --> Conv[(Conversation store)]
    Hook --> State[(State store)]
    Hook --> Know[(Knowledge store)]

    classDef agent fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef process fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef storage fill:#6366F1,stroke:#7C90A0,color:#fff

    class Agent agent
    class Hook process
    class Conv,State,Know storage

    classDef tool fill:#189AB4,color:#fff

    classDef agent fill:#8B0000,color:#fff
```

## Quick Start

<Steps>
  <Step title="Simple usage">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    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!")
    ```
  </Step>

  <Step title="With session continuity">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    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?")
    ```
  </Step>

  <Step title="CLI with persistence">
    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    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?"
    ```
  </Step>
</Steps>

***

## How It Works

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant User
    participant Agent
    participant DB as DbAdapter
    participant Store as Conversation store

    User->>Agent: start(prompt)
    Agent->>DB: on_agent_start(session_id)
    DB->>Store: load history (incl. tool_calls / tool_call_id)
    Store-->>Agent: previous messages
    Agent->>DB: on_user_message
    Agent->>DB: on_assistant_message (with tool_calls)
    Agent->>DB: on_tool_message (tool_call_id)
    Agent->>DB: on_agent_message (final answer)
    Agent-->>User: reply
```

| Store | Purpose | Example backends |
| - | - | - |
| Conversation | Chat history, tool calls | PostgreSQL, MySQL, SQLite, Supabase, SurrealDB |
| State | Runs, traces, metadata | Redis, MongoDB, DynamoDB, Firestore |
| Knowledge | Vectors / RAG | Qdrant, 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.

<Note>
  SQLite persistence uses the hardened [`sqlite_connect`](/docs/features/sqlite-hardened-connector) connector by default, so it works on Docker Desktop, NFS, Fly.io network volumes, and Kubernetes PVCs without corruption risk.
</Note>

***

## Configuration Options

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

| Parameter | Type | Default | Description |
| - | - | - | - |
| `database_url` | `str` | `None` | Conversation store URL |
| `state_url` | `str` | `None` | State / run history store URL |
| `knowledge_url` | `str` | `None` | Vector or knowledge store URL |
| `conversation_options` | `dict` | `None` | Backend-specific kwargs passed **only** to the conversation store factory |
| `state_options` | `dict` | `None` | Backend-specific kwargs passed **only** to the state store factory |
| `knowledge_options` | `dict` | `None` | Backend-specific kwargs passed **only** to the knowledge store factory |
| `init_retry_cooldown` | `float` | `30.0` | Seconds to wait before retrying a failed store init |

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

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
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 theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
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:

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    subgraph "Per-store options (new)"
        C1[conversation_options] --> ConvFactory[create_conversation_store]
        S1[state_options] --> StateFactory[create_state_store]
        K1[knowledge_options] --> KnowFactory[create_knowledge_store]
    end

    subgraph "Legacy **options (deprecated)"
        L[**options bag] -.->|broadcasts to all| ConvFactory
        L -.->|broadcasts to all| StateFactory
        L -.->|broadcasts to all| KnowFactory
    end

    classDef options fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef factory fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef legacy fill:#F59E0B,stroke:#7C90A0,color:#fff

    class C1,S1,K1 options
    class ConvFactory,StateFactory,KnowFactory factory
    class L legacy
```

<Warning>
  Passing backend kwargs directly to `db(**options)` is **deprecated** — it emits a `DeprecationWarning` and broadcasts each key to every store, which crashes any factory that doesn't accept it. Scope kwargs with the per-store dicts instead.

  ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  # DEPRECATED — emits DeprecationWarning, kwargs go to every store
  store = db(database_url="postgres://...", state_url="redis://...", ssl_mode="require")

  # NEW — scoped to a single backend
  store = db(
      database_url="postgres://...",
      state_url="redis://...",
      conversation_options={"sslmode": "require"},
  )
  ```
</Warning>

### Environment variables

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
export PRAISON_CONVERSATION_URL="postgresql://localhost:5432/praisonai"
export PRAISON_STATE_URL="redis://localhost:6379"
export PRAISON_KNOWLEDGE_URL="http://localhost:6333"
```

<Note>
  Database backends require the `praisonai` wrapper (`pip install praisonai`). The core SDK defines `DbAdapter`; implementations live in `praisonai.db`.
</Note>

### 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
```

<Warning>
  The auto ID ends in a per-instance random suffix, so:

  * Two `Agent(name="Assistant")` instances **never** share history — no cross-instance bleed, even when started in the same UTC hour.
  * The auto ID **cannot** resume a conversation across process restarts — a new instance gets a new suffix, hence a new session. For resumable multi-turn conversations, pass an explicit `session_id`. See [Session Persistence](/docs/features/session-persistence).
</Warning>

***

## Custom DB Adapter

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

<Note>
  `MemoryConfig(db=MyDbAdapter())` validates that the adapter implements all three hooks shown below (`on_agent_start`, `on_user_message`, `on_agent_message`). An object that implements only a subset — or exposes just a `database_url` — raises `TypeError` at construct. See [Memory Troubleshooting → `TypeError: MemoryConfig.db must be a DbAdapter instance`](/docs/features/memory-troubleshooting#why-am-i-seeing-typeerror-memoryconfigdb-must-be-a-dbadapter-instance).
</Note>

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
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 method | What 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 method | What 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](https://github.com/MervinPraison/PraisonAI/pull/5082)) let a resumed session hand the model the same tool transcript the JSON store already keeps.

### Minimal adapter

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
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):
        ...
```

<Note>
  Adapters that omit `on_assistant_message` / `on_tool_message` still work — assistant tool-call turns fall back to text-only via `on_agent_message`, and raw `role="tool"` turns are dropped. Add them to give resumed sessions the same tool transcript the JSON store already has.
</Note>

<Tip>
  Third-party code can feature-detect the capability with `isinstance`:

  ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  from praisonaiagents.db import ToolTurnDbAdapterProtocol

  isinstance(my_adapter, ToolTurnDbAdapterProtocol)  # True if it opts in
  ```
</Tip>

Reference: PraisonAI PR [#5082](https://github.com/MervinPraison/PraisonAI/pull/5082) (fixes [#5075](https://github.com/MervinPraison/PraisonAI/issues/5075)).

***

## Docker (local development)

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
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 theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
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 theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
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
)
```

| Parameter | Type | Default | Description |
| - | - | - | - |
| `init_retry_cooldown` | `float` | `30.0` | Seconds 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

<AccordionGroup>
  <Accordion title="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.
  </Accordion>

  <Accordion title="Read credentials from the environment">
    Never hardcode database URLs — use `os.getenv("PRAISON_CONVERSATION_URL")`.
  </Accordion>

  <Accordion title="Run doctor before production">
    `praisonai persistence doctor` validates connectivity for conversation, state, and knowledge stores.
  </Accordion>

  <Accordion title="Separate stores by concern">
    Conversation history, run traces, and vectors scale differently — configure `database_url`, `state_url`, and `knowledge_url` independently.
  </Accordion>
</AccordionGroup>

***

## Related

<CardGroup cols={2}>
  <Card title="Run History" icon="clock-rotate-left" href="/docs/features/run-history">
    Query persisted runs and traces
  </Card>

  <Card title="Session Persistence" icon="floppy-disk" href="/docs/features/session-persistence">
    JSON file sessions without a database
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.