Storage backends are Python modules that export a Provider class extending BaseStorageProvider.
Every backend must implement these. They are sufficient for a fully working system because all domain methods have default implementations built on them.
| Method | Description |
|---|---|
get(namespace, key) |
Key-value read |
set(namespace, key, value) |
Key-value upsert |
delete(namespace, key) |
Key-value delete |
append(namespace, key, item) |
Append to an ordered list, returns item ID |
get_list(namespace, key) |
Read an ordered list |
query(namespace, filters) |
Equality-filter scan |
Relational or indexed backends can override these for better performance:
get_or_create_conversation(...)— conversation lifecyclehas_conversation(...)— existence checkcreate_message(...)— message creationget_message_blocks(...)— fetch blocks grouped by messageappend_text_block(...),append_file_block(...),append_tool_block(...),append_usage_block(...)— block persistenceget_tool_call(tool_call_id)— indexed tool-call lookupupsert_heartbeat(...),get_heartbeat(...)— agent livenessget_conversations_for_export(...),get_messages_with_blocks(...)— export queriessupports_exportproperty — whether export is available
The built-in PostgreSQL and SQLite providers override all of these with proper SQL.
A Redis backend only needs the 6 primitives. All conversation management works automatically via the default implementations.
# my_storage/redis.py
from slack_agents.storage.base import BaseStorageProvider
class Provider(BaseStorageProvider):
def __init__(self, url: str):
self._url = url
async def initialize(self):
# Connect to Redis
...
async def get(self, namespace, key):
...
async def set(self, namespace, key, value):
...
async def delete(self, namespace, key):
...
async def append(self, namespace, key, item):
...
async def get_list(self, namespace, key):
...
async def query(self, namespace, filters):
...
async def close(self):
...For better performance you could override specific domain methods — for example, get_tool_call with a Redis hash lookup by tool_call_id instead of scanning all blocks.
storage:
type: my_storage.redis
url: "{REDIS_URL}"- Storage providers handle all persistence — the
ConversationManageris a thin delegation layer initialize()is called at startup,close()at shutdown- Non-relational backends only need the 6 abstract primitives
- Relational backends (PostgreSQL, SQLite) override domain methods with optimized SQL