A chatbot that treats every turn as if it were the first turn will frustrate users within minutes. "And why is that?" only makes sense relative to the previous answer. "Give me the formula" needs to know which topic is active. A clarification changes what the next utterance means. A conversation state machine — an explicit model of modes, intents, and routing rules — is how chatbots keep these relationships straight.

Modes vs. Intents

Two concepts are easy to confuse, and keeping them separate is the first step toward a working state machine.

A mode describes the state of the conversation: whether the bot is answering a fresh question, handling a follow-up on the previous topic, requesting clarification, or waiting for input. Modes change slowly and persist across turns.

An intent describes what the user wants in a single turn: ask for a definition, request an example, ask how something works, ask why, or switch topics. Intents are per-turn and usually short-lived.

Routing rules map a (mode, intent) pair to the next action. A follow-up intent while in answering mode routes to the previous topic; the same intent while in greeting mode routes to the default topic or triggers a clarification. This separation keeps the state space small: modes are a handful of persistent states, while intents are a vocabulary of per-turn actions.

A Minimal State Machine in JavaScript

A state machine needs three things: a state object, a transition function, and a routing table. Here is a compact version:

const modes = ['idle', 'answering', 'follow_up', 'clarifying'];

let state = {
  mode: 'idle',
  topic: null,
  lastIntent: null
};

const routes = {
  idle:        { question: 'answering', follow_up: 'clarifying' },
  answering:   { question: 'answering', follow_up: 'follow_up',  switch: 'answering' },
  follow_up:   { question: 'answering', follow_up: 'follow_up',  switch: 'answering' },
  clarifying:  { question: 'answering', follow_up: 'follow_up',  switch: 'answering' }
};

function transition(intent, payload) {
  const nextMode = routes[state.mode][intent] ?? 'clarifying';
  if (intent === 'switch') state.topic = payload.topic;
  if (intent === 'question' || intent === 'follow_up') state.lastIntent = intent;
  state.mode = nextMode;
  return state;
}

The transition function is pure routing: it consumes an intent, updates the mode, and mutates only the fields the intent touches. The state object carries the conversation context — current topic, last intent, and in a real system, the active entities and the recent turns — so that downstream components (retrieval, response generation, rendering) can read context without re-deriving it.

Follow-Up Routing

Follow-up routing is where the state machine earns its keep. The difficult part is not the routing table; it is deciding that an utterance is a follow-up at all.

Follow-ups come in recognizable shapes. Deictic references ("that", "this", "it") point at the previous response. Elliptical utterances ("how?", "why?", "and then?") carry intent but no new topic. Continuation markers ("tell me more", "go on") ask for depth on the current subject. Each shape needs a detection rule, and each detected follow-up type should map to a distinct route: a depth follow-up increases how much the bot retrieves, while a clarification follow-up re-asks a narrower version of the last question.

Two failure modes dominate. The first is over-routing: a genuine new question that happens to start with "why" gets treated as a follow-up, so the bot answers about the wrong topic. The fix is to require a topic anchor — a follow-up only routes to the previous topic if that topic exists and is recent. The second failure mode is under-routing: follow-ups that get classified as new questions, which produces answers disconnected from the thread. This is why follow-up detection should run before intent classification and why its results should be part of the state update.

How ReLU.chat Structures the State Machine

ReLU.chat makes the state machine explicit in two layers: a learned policy and a heuristic fallback.

The policy is an MLP with 25 inputs feeding two hidden layers (128 and 64 units) and six action heads — mode, intent, topic_count, frag_count, creativity, and tone — with roughly 13,079 parameters, trained with REINFORCE and auto-quantized to symmetric int8 at load time for a roughly 4x memory reduction. The mode and intent heads are the state machine's transition function: they decide, from the current context, which mode the conversation enters and what the user's intent is. The other heads turn that state into concrete behavior — how many topics to consider, how many fragments to use, how creative and how formal the response should be.

Intent classification itself is cosine similarity against 19 prototype vectors per intent category, blended 70/30 between best-match and average-match, with a temperature of 1.5 softmax calibration. During cold start, or when the policy weights fail to load, a heuristic fallback with 15 parameterized decision thresholds drives the same transitions, so the state machine works before any model is ready.

Session memory feeds the state machine. ReLU.chat keeps up to 30 turns with importance-based eviction (the most recent 5 turns are protected), compresses older responses into 120-character summaries after 5 turns, maintains an EMA summary vector with alpha 0.75, and decays entity salience with a half-life of 5 turns. The state machine reads this memory to answer the two questions routing depends on: what topic is active, and how far back does the follow-up anchor reach.

The quiet benefit of an explicit state machine is observability. When a conversation goes wrong, you can print the (mode, intent) history and see exactly where routing broke — the intent that was misclassified, the mode that never transitioned, the follow-up that lost its anchor. That trace is much easier to reason about than a pile of log lines from a free-form model. Keep the state object serializable, log it per turn in development, and you get a replayable record of every routing decision.

Key Takeaway

A conversation state machine separates persistent modes from per-turn intents and routes every utterance through a small transition table. Follow-up routing works when follow-ups are detected before intent classification and anchored to a recent topic. Learned policies can drive the transitions — ReLU.chat uses an MLP policy with mode and intent heads and a heuristic fallback — but the structure is what makes conversations coherent, and an explicit, serializable state object is what makes them debuggable.