Persistence
Document version: 0.2 · Last updated: 2026-10-08 · Status: 🔴 UNDER REVIEW
Every bot application delegates durable state to one persistence_route, such as
registry/bots. It is an internal route on another mounted RoutedApplication,
not an HTTP URL. All bot instances on that application share this provider.
The route receives operation, application (the bot application’s code) and
record (the operation payload, or None). Namespace records by application,
bot code and record ID where appropriate. Two mounts may use the same provider
while retaining separate application namespaces.
Choose a backend
The examples use encrypted JSON through kajenn storage and return 404 to HTTP requests. They are useful for a single receiving process. Their thread lock does not coordinate multiple processes.
Your hosting application can implement the same route with its database. Keep credential encryption, transaction behavior and retention in that application. Changing the backend does not require changing bot classes or command handlers. The contract does not itself provide distributed conversation execution.
Required operations
Feature |
Operations |
Required guarantee |
|---|---|---|
Registrations |
|
Durable save; preserve credentials and importable class references |
Webhook deduplication |
|
Fixed expiry per accepted event; exclude expired receipts |
Conversations and admission |
|
Atomic compare-and-save of the supplied revision |
Telegram polls |
|
Durable snapshot scoped by application, bot and poll |
WhatsApp service windows |
|
Store the newest timestamp; never regress on late events |
WhatsApp delivery receipts |
|
Atomic merge of status events and matching recipient aliases |
Reminders and queued announcements use kajenn.tasks storage and do not add
registry operations. A Telegram sender doing only registration and direct sends
needs list and save. WhatsApp outbound sends also record message acceptance;
free-form sending reads the persisted service window.
Use the exact payload and result schemas in the Telegram persistence contract and WhatsApp persistence contract. Those pages specify additional operations and provider-specific fields.
Conversation revisions
Creation supplies revision zero. For every save_conversation, atomically:
Read the current revision, treating a missing record as revision zero.
Reject a mismatch with the supplied revision.
Save the record with the incremented revision.
Return the saved record, including that revision.
A read followed by an unconditional write is insufficient. The example locks these steps within one process; a database provider should use a conditional update or transaction. Treat a revision conflict as a failed operation that needs a fresh application decision, not a silent retry of stale context.
Retention and recovery
Webhook receipts are separate from completed task history. Telegram retains them for 48 hours after acceptance; WhatsApp retains them for eight days. Duplicate deliveries do not extend expiry. Tasks are stored before receipts; if the receipt write fails, preserve the staged task so the provider retry can reuse it.
Conversation, admission, poll, window and message records need an explicit retention policy in your provider. Removing admission records removes remembered access. Removing message references can break correlation for outstanding replies. Back up registry data together with the appropriate key and application namespace.
Provider sends and persistence writes are separate. Even a transactional database cannot make an external message send and a registry write one atomic operation. Inspect uncertain outcomes before resending; see operations.