Chat Persistence

OrchidChatStorage ABC, the built-in in-memory default, and durable SQLite/PostgreSQL backends via plugins.

Orchid persists chat sessions and messages through the OrchidChatStorage ABC. The orchid-ai library ships one dependency-free implementation — an in-memory store used as the zero-configuration default. Durable backends come from plugin packages (orchid-storage-sqlite, orchid-storage-postgres) or from consumer projects via any importable subclass.

OrchidChatStorage interface

class OrchidChatStorage(ABC):
    async def init_db(self) -> None          # open connection + run migrations
    async def close(self) -> None

    async def create_chat(tenant_id, user_id, title) -> OrchidChatSession
    async def list_chats(tenant_id, user_id)  -> list[OrchidChatSession]
    async def get_chat(chat_id)               -> OrchidChatSession | None
    async def delete_chat(chat_id)            -> None   # cascades to messages
    async def update_title(chat_id, title)    -> None
    async def mark_shared(chat_id)            -> None

    async def add_message(chat_id, role, content, agents_used, metadata) -> OrchidChatMessage
    async def get_messages(chat_id, limit, offset) -> list[OrchidChatMessage]

The API layer depends only on this interface — swapping the backend requires no code changes in routers or agents.

Default: in-memory

When no storage backend is configured, the framework resolves storage.class to the "memory" sentinel and uses OrchidInMemoryChatStorage — sessions and messages live in process memory for the lifetime of the process. It has no external dependencies and no migration step, which keeps tests, embedded runs, and quick starts working out of the box.

In-memory means non-durable

History stored by the default backend disappears when the process exits. For anything that must survive a restart, install a durable plugin or point storage.class at your own backend.

orchid-api and orchid-cli bundle the orchid-storage-sqlite plugin and pass an explicit storage.class at startup, so both default to durable SQLite (~/.orchid/chats.db) instead of the framework in-memory store.

Pluggable backends

ClassProvided byDriverWhen to use
OrchidInMemoryChatStorageorchid-ai (built-in)—Tests, embedded runs, demos — framework default
OrchidSQLiteChatStorageorchid-storage-sqliteaiosqliteLocal development, single-process deployments, CLI — default for orchid-api / orchid-cli
OrchidPostgresChatStorageorchid-storage-postgresasyncpgProduction, multi-worker deployments

Configure via config:

storage:
class: orchid_storage_sqlite.chat_storage.OrchidSQLiteChatStorage
dsn: ~/.orchid/chats.db
storage:
class: orchid_storage_postgres.chat_storage.OrchidPostgresChatStorage
dsn: postgresql://user:pass@host:5432/db

Strict resolution — no silent fallback

A configured dotted class path is always resolved strictly. If the import fails — a typo, a missing extra, or a plugin that was never installed — startup raises immediately with the package to install:

ImportError: Cannot resolve chat storage class
'orchid_storage_sqlite.chat_storage.OrchidSQLiteChatStorage'.
Install the missing plugin: pip install orchid-storage-sqlite

Only the "memory" sentinel (or an empty value) selects the in-memory backend. The framework never falls back silently from a configured backend to in-memory, so a misconfigured deployment fails fast instead of quietly dropping persistence.

Custom backends

Subclass OrchidChatStorage in your own project and reference it by dotted import path:

storage:
class: myproject.storage.mysql.MySQLChatStorage
dsn: mysql+asyncmy://user:pass@host:3306/db

The factory resolves the class at startup via importlib. The constructor must accept keyword-only dsn: str and extra_migrations_package: str | None = None.

Migrations

Durable backends own their migration modules: orchid-storage-sqlite ships v001_initial_schema (every framework table) and v002_ingestion_manifest, while orchid-storage-postgres ships its own Postgres DDL. Migrations are versioned modules named v{NNN}_{description}.py; each exposes VERSION, up(conn, *, dialect), and down(conn, *, dialect).

Integrators can append their own migrations on top of a backend's by setting extra_migrations_package. Those run after the backend's migrations and are recorded with an "ext:" prefix in the shared _migrations table to avoid collisions.

storage:
class: orchid_storage_postgres.chat_storage.OrchidPostgresChatStorage
dsn: postgresql://...
extra_migrations_package: myapp.migrations

Persist original messages only

Always save the original user message to chat history — not the version with file content prepended. Prepending extracted text to the user message is a prompt augmentation pattern for the current LLM call. Persisting that augmented string would cause the stored message to be significantly longer, wasteful to re-load, and misleading if the underlying document changes.