Skip to main content
Async DB hooks enable non-blocking database operations in async agents through automatic async/sync detection and asyncio.to_thread fallback.
Breaking Change in PR #1829: The async_* prefixed methods have been removed from async stores. The orchestrator now uses isinstance(store, AsyncConversationStore) for dispatch instead of runtime method introspection.
The user chats asynchronously; DB hooks persist messages without blocking the event loop.

Quick Start

1

Async Context Manager

Use async DB operations with the async context manager for automatic cleanup.
2

Wire to Async Agent

Connect async DB hooks to an async agent for seamless persistence.

How It Works

Shared dispatch for both entry points

Both persistence entry points flow through the same async-store dispatch. As of PR #4394, the async-store dispatch (isinstance(store, AsyncConversationStore)await store.method(...) vs. asyncio.to_thread(store.method, ...)) lives on PraisonAIDB, in its internal _call_store / _dispatch_async helpers. PersistenceOrchestrator.on_message / aon_message / on_agent_end / aon_agent_end delegate to those helpers instead of keeping a second copy, so the async/sync-store behaviour is identical no matter which entry point you pick — MemoryConfig(db=PraisonAIDB(...)) or the orchestrator-based wrap_agent_with_persistence / PersistentAgent / create_persistent_session.
_call_store splits by op kind: writes stay fire-and-forget on the shared bridge (idempotent, uuid-keyed); reads (_READ_OPS = {"get", "get_session", "get_messages", "list_sessions"}) route through run_sync_or_offload, returning the real value on a sync path and raising loudly inside a running loop. As of PR #4861, _call_store and flush_pending_writes are instance methods on PraisonAIDB (previously staticmethods), and each instance tracks its own fire-and-forget writes in self._bg_writes — not a process-global set. _call_store, _dispatch_async, and _READ_OPS are internal plumbing on PraisonAIDB, not public API. They are named here only to describe the shared dispatch mechanism.
Because bg-write tracking is per-instance (PR #4861), a tenant’s close() / aclose() flushes only its own in-flight writes — one tenant’s stuck backend can no longer consume another tenant’s 5-second flush budget or discard its writes. See Thread Safety.

Sync reads from inside a running loop

Sync reads reached from inside a running event loop now fail loudly instead of silently dropping the read.
Sync reads from inside a running loop now fail loudly. Calling a sync hook whose store op is a read — get, get_session, get_messages, list_sessions, or any sync completion hook (on_run_end, on_trace_end, on_span_end) that does a read-modify-write — from inside a running event loop raises RuntimeError steering you to the async surface. In previous releases it silently returned None: a resumed session looked empty, and the completion hooks merged into {} and overwrote the persisted record with a partial dict. Outside a loop, sync reads resolve the coroutine transparently. Writes are unaffected — they remain uuid-keyed fire-and-forget on the bridge.Fixed in PR #4821, fixes #4820.

Which hook to call from where

Pick sync or async by whether you sit inside a running loop and whether you read or write.
Troubleshooting. Symptom in older versions: a resumed session came back empty even though messages had been persisted; a completed run/trace/span record lost run_id, started_at, input_content, or its spans/events after a sync completion hook fired from async code. Root cause: sync reads reached from a running loop were silently dropped. Fixed in PR #4821 — the same call now raises RuntimeError pointing you at the aon_* hook.

State-store lifecycle key

The wrapper writes both start and end under agent:{session_id}, so the end transition updates the same record the start wrote instead of a disconnected one. Start previously used agent:{session_id}:{agent_id or name} and end wrote a separate record. Consumers who query the state store directly must update their key format to agent:{session_id}.

Configuration Options

Signature change in PR #3857: aon_agent_start(agent_name, session_id, user_id, metadata) -> List and aon_agent_end(session_id, metadata). user_id is now persisted (previously silently dropped) and aon_agent_start returns the resumed message list. Update any custom AsyncDbAdapter implementations to match.
All async hooks support these signatures from the DB adapter:

Common Patterns

Complete Async Lifecycle

Sync Store Compatibility

Native Async Store


Best Practices

Manual aclose() is now safe and idempotent — calling it twice does nothing harmful. Prefer async with for exception-safety and readability, so cleanup runs even when an error is raised mid-block:
close() and aclose() reset the internal stores, so re-entering a with db: ... block after close cleanly re-initializes instead of dispatching to closed handles:
For high-throughput async applications, implement native async methods:
All hooks accept optional metadata dictionaries:
Both sync and async context managers are supported:

Persistence Overview

Complete persistence system documentation

Agent Architecture

Learn about async agent patterns