Conversation state machine
Good for any multi-phase interaction
Problem
Conversational agents that handle all input uniformly regardless of where the user is in the interaction produce incoherent experiences. A greeting handled the same way as a mid-conversation question, or a goodbye prompt that fires at the wrong time, breaks user expectations and creates confusing exchanges. Without explicit phase tracking, branching conversation logic becomes entangled conditional spaghetti that is hard to test or extend.
Eliza needed a clean way to route user input to the right handler depending on whether the conversation had just started, was in progress, or was winding down.
Solution
Model the conversation as a finite state machine with named phases (e.g., greeting, main, goodbye). Each phase owns its own set of rules or handlers. Transitions between phases are triggered by specific user inputs or internal conditions. The agent always knows which phase it is in and dispatches input to the appropriate phase handler.
type Phase = "greeting" | "main" | "goodbye";
interface ConversationState {
phase: Phase;
turnCount: number;
}
function dispatch(state: ConversationState, input: string): string {
switch (state.phase) {
case "greeting":
state.phase = "main";
return greet(input);
case "main":
if (isGoodbye(input)) state.phase = "goodbye";
return respond(input);
case "goodbye":
return farewell();
}
}Phase transitions are explicit and testable. Each phase handler can be developed and tested in isolation.
Usage
Apply when a conversational agent has meaningfully distinct interaction stages where different rules, tones, or handler sets apply. Well-suited for:
- Onboarding flows (welcome → setup → confirmation)
- Interview-style agents (intro → question loop → summary)
- Support bots (triage → diagnosis → resolution → close)
Not worth the overhead for single-turn question-answer agents where every exchange is structurally identical.
Trade-offs
- Pro: Phases are independently testable — mock the state, drive the phase handler, assert output.
- Pro: Adding a new phase (e.g.,
escalation) is additive and does not require modifying existing phase logic. - Con: Phase transitions must be designed upfront. If real-world usage reveals unexpected flows, retrofitting new transitions can require restructuring the state machine.
- Con: Does not handle re-entry or nested sub-phases natively — those require additional state fields or a more sophisticated FSM library.
See Also
Source
Synced from ops-core learning data. Original source: Eliza