Lola Send’s application layer holds no persistent data. All conversation state and message history live in Redis, and sender identity lives in backend services. This stateless design enables horizontal scaling — any application instance can handle any request without session affinity.
Two stores, two purposes
Lola Send uses Redis for two distinct concerns, each with its own store and configuration:
| Store | Class chain | Purpose |
|---|
| Conversation state | RedisChatStateStore → RedisChatStateProvider | Key-value session data per conversation (e.g., current step in a flow, working context) |
| Conversation history | RedisHistoryStore → RedisHistoryProviderAsync → ListRedisStoreAsync | Ordered message history per session, providing the context window for the AI model |
Conversation state
The state store holds working context for each active conversation — such as which step the sender is on in a multi-step flow, temporary data collected during the conversation, and agent-specific flags. Agents read and write state through a context manager:
async with ctx.state_manager() as state:
count = state.get("count", 0)
count += 1
state["count"] = count
Conversation history
The history store maintains an ordered record of messages exchanged in each session. The AI model uses this history as its context window — core_history_window_length in MacawSettings controls how many turns the model sees. History can be cleared programmatically (e.g., when a session resets via the /new command).
Stateless application design
The application itself is stateless:
- Session state lives in Redis, not in application memory
- Sender identity lives in the backend identity service, injected per-request by middleware
- Conversation history lives in Redis, accessed asynchronously
- Configuration comes from environment variables (managed by Doppler)
Any application instance can handle any inbound message. There is no session affinity, no in-memory cache of sender data, and no instance-specific state. This enables:
- Horizontal scaling — add instances to handle load without coordination
- Zero-downtime deployments — replace instances without losing state
- Fault tolerance — if an instance fails, other instances continue processing
Redis configuration
State and history use separate Redis configurations for isolation:
| Store | Environment variable | Purpose |
|---|
| Conversation state | STATE_STORE_REDIS | Connection URL for the state Redis instance |
| Conversation history | HISTORY_STORE_REDIS | Connection URL for the history Redis instance |
Separate configurations allow the bank to:
- Deploy state and history to different Redis instances for performance isolation
- Apply different persistence, eviction, and backup policies per store
- Monitor and scale each store independently
Session scoping
Redis keys are scoped by session ID, which is derived from the sender’s phone number and channel. This ensures:
- Conversations from different senders are isolated
- The same sender on different channels has separate sessions
- No cross-contamination of state between conversations
Singleton pattern
Both stores are implemented as singletons — created once at application startup and shared across all agents:
def get_state_store():
state_store = RedisChatStateStore()
return state_store.get_store()
def get_history_store():
history_store = RedisHistoryStore()
return history_store.get_store()
Every agent receives the same store instances, ensuring consistent state access regardless of which agent handles a given message.
Security considerations
- No sensitive data in state: Card numbers, full PII, and other sensitive data are never persisted in conversation state or history. Sensitive operations are handled through external encrypted widgets.
- VPC deployment: Redis instances deploy within the virtual private cloud, not exposed to the public internet.
- Ephemeral data: Conversation state has a natural lifecycle tied to the session. State does not accumulate indefinitely.
- No credentials in state: Authentication tokens and secrets are managed by Doppler, not stored in Redis.
Conversation state and history should be treated as ephemeral. Do not rely on Redis state for persistent business records — use backend services for durable storage.
Configuration and control
| Control | Description |
|---|
| Redis instances | Separate connection URLs for state and history stores |
| History window | core_history_window_length in MacawSettings controls how many turns the AI model sees |
| State lifecycle | Sessions can be cleared programmatically (e.g., on identity reset) |
| Isolation | Separate Redis instances enable independent scaling and monitoring |