Chatbot Conversation Architecture · Chapter 1 · 11 min
State machines for conversations
Model dialogue as states and transitions so behaviour is predictable and testable.
Conversations are inherently stateful
Every inbound message in a conversation system must be interpreted relative to the context established by previous interactions. If a user says "Tomorrow", the system cannot understand this without knowing that a previous turn requested a date. Implicit state models, which rely on scattered if-statements, inevitably become unmaintainable as you add more complex conversational features. Moving to an explicit finite state machine architecture ensures that every transition is documented and predictable. Transitioning to this model allows for much cleaner reasoning about complex user flows and edge cases.
The mechanics of explicit state
An explicit state machine defines a clear set of states and the specific events that trigger transitions between them. This approach forces you to map out every possible turn in the dialogue flow, leaving no room for ambiguous system behaviour. When you use this pattern, your code becomes a clean reflection of your conversational design. It allows developers to reason about the user's journey without chasing hidden logic in nested conditional branches. By formalizing these states, you create a system that is naturally more resilient to unexpected user inputs.
const machine = {
idle: { on: { BOOK: "askDate" } },
askDate: { on: { DATE: "askTime", CANCEL: "idle" } },
askTime: { on: { TIME: "confirm", CANCEL: "idle" } },
confirm: { on: { YES: "booked", NO: "askDate" } },
booked: { on: { BOOK: "askDate" } },
};Persistent state management
State must be maintained per individual conversation, using a unique identifier such as a channel and user address pair. This data should reside in a high-performance, key-value store equipped with a Time-To-Live (TTL) feature to automatically clean up. Setting a reasonable expiration time, such as twenty-four hours, prevents abandoned sessions from cluttering your system while ensuring users have a seamless experience. Proper session management is critical for maintaining a high quality of service as your user base grows.
Designing for persistence also means considering how state is recovered after a system failure. Your bot should be able to reload the conversation state instantly whenever a new message arrives for a specific user. This requires a fast and reliable database backend that can handle thousands of concurrent reads and writes per second. Without this foundation, your state machine will struggle to maintain the continuity that users expect from a modern bot.
Testing and maturity
With an explicit state machine, your testing strategy shifts from exploring unpredictable scenarios to verifying a finite set of transitions. You can treat your conversational logic like a truth table, checking that every specific input leads to the correct state transition. This methodology guarantees that there is never an unreachable state or an input that leaves the system in an undefined limbo. Systematic testing at this level dramatically reduces the occurrence of production errors in complex dialogues.
Ultimately, an explicit state machine is not just a technical choice but a strategic one for your bot's architecture. It provides the foundation for more advanced features like slot-filling and multi-turn context management. By starting with a solid state model, you ensure your bot remains manageable and reliable as it scales to meet complex user needs. Modern conversational design requires this level of precision to deliver truly helpful and engaging experiences for everyone involved.
Discussion (4)
The callout about alerting on queue age instead of depth changed how we page people. Thank you.
Same experience here. Per-route metrics were the only way we found it.
The distinction between 'sent' and 'delivered' finally clicked for me here. We had dashboards treating them as the same thing for a year.
Would love a follow-up on how this behaves under carrier throttling. We see very different numbers per route.