Understanding that a conversation needs managed state is one thing. Actually wiring it into a working multi-turn flow is another. This article is the do-this-then-that walkthrough: a sequence of concrete steps you can follow to take a stateless model and build a conversation around it that remembers, updates, and stays coherent across many turns.
We will assume you grasp the basic idea that the model is stateless and that you maintain memory yourself. If that is new, read How Conversations Remember: State Inside Your Prompts first. Here we move past the concept into implementation, step by ordered step.
The example we will use throughout is a booking assistant that collects the details needed to schedule an appointment. It is simple enough to follow and rich enough to show every part of the pattern.
Step One: Define a State Schema
Before writing any conversation logic, decide what state your dialogue needs.
List the Fields
For the booking assistant, the state might hold the service requested, the preferred date, the preferred time, the customer name, and the contact method. Each field has a value or sits empty until collected.
- Name each field explicitly
- Decide which are required to complete the task
- Mark each as filled or still open
Writing the schema first forces clarity about what the conversation is actually trying to accomplish. Skipping it leads to ad hoc memory that is impossible to maintain.
Step Two: Initialize the State
When a conversation begins, create a fresh state object with every field empty.
Tie It to the Session
Store this object somewhere that persists between turns: a session record, a database row, an in-memory map keyed by conversation id. The exact store does not matter, only that the same object survives from one message to the next. This persisted object is the memory the model lacks.
Step Three: Process Each Incoming Message
Now the per-turn loop begins. When the user sends a message, you do three things in order: update state, build the prompt, and call the model.
Extracting New Information
First, figure out what the user just told you. If they said "I'd like a haircut on Friday," you extract the service and the date. You can do this with code for structured input, or you can ask the model to read the message and return the fields it found, which handles messy natural language better.
Validating Before Committing
Do not trust an extracted value blindly. Check that a date is a real date, that a service is one you offer. If the model proposed the update, your code validates it before it touches the stored state. This guard is what stops a single misread from corrupting the conversation.
Step Four: Update the State Object
With validated values in hand, write them into the stored state.
Overwrite on Correction
If the user changes an earlier choice, replace the old value rather than keeping both. The current value must be the single source of truth. After updating, recompute which required fields are still empty, because that drives what the conversation does next.
Step Five: Build the Prompt From State
Now assemble the prompt for this turn using the state, not the raw history.
Inject a Focused Summary
Include a concise description of what is known and what is still needed: service is haircut, date is Friday, time and contact still required. The model uses this to decide what to ask next. Keep the injected state focused on the current turn so the prompt stays small and the model's attention stays sharp.
Add the Instruction
Tell the model its job for this turn: ask for the next missing piece of information, or confirm the booking if everything is collected. Pairing the state summary with a clear instruction is what produces a sensible next message.
Step Six: Drive the Conversation Forward
The model's reply asks for the next missing field. The user answers, and you loop back to step three.
Knowing When to Finish
Each turn, after updating state, check whether all required fields are filled. When they are, the model confirms and the task completes. This completion check, driven by the state object rather than by guessing from the transcript, is what gives the conversation a clear finish line. The fuller treatment of structuring this loop is in Keeping Track of Context Across a Long AI Conversation.
Step Seven: Handle the Edge Cases
A real flow needs to cope with the messy moments.
Common Situations to Build For
- The user provides several fields at once: extract and store them all in one update
- The user changes a value: overwrite and re-check what remains open
- The user asks an off-topic question: answer it without losing the state you have collected
- A long conversation: summarize older turns while preserving the state object
Building for these turns a fragile demo into something usable. The failure modes that show up when you skip them are catalogued in Seven Ways Conversational Prompts Lose Their Thread.
Step Eight: Test the Conversation Deliberately
A stateful conversation has many more paths than a single prompt, and the failures hide in the paths you did not try.
Build Test Conversations
Write out scripted conversations that exercise the messy cases, not just the happy path. Each test sends a sequence of messages and asserts the resulting state.
- A user who provides several fields in one message
- A user who corrects an earlier value partway through
- A user who wanders off topic and then returns
- A conversation long enough to trigger summarization
Because your state is an explicit object, these tests are straightforward: run the sequence, then check that the final state matches what you expect. This is far easier than trying to assert on free-form model prose, and it is one of the underrated benefits of managing state explicitly rather than relying on raw history.
Watch for Silent Drops
The most insidious bug is information that arrives and quietly fails to land in state. A test that asserts on the full expected state after a multi-fact message catches exactly this. Without such a test, the failure only surfaces when a real user notices the assistant asking for something they already said.
Step Nine: Add Observability
Once the conversation runs in production, you need to see what its state looked like when something went wrong.
Log the State, Not Just the Messages
Log the state object at each turn alongside the messages. When a user reports a confusing experience, the state log shows you exactly what the system believed at each point, which is usually enough to pinpoint where an update went wrong. Logging only the transcript leaves you guessing about what the model actually had to work with. This visibility is what turns a hard-to-reproduce conversational bug into a quick fix, and it pairs naturally with the validation step from earlier.
Frequently Asked Questions
Should I use code or the model to extract information?
Use the model for messy natural-language input and code for validation. The model is good at reading "sometime Friday afternoon" into a date and time; your code should confirm the result is valid before it touches stored state. The safe pattern is model-proposes, code-validates.
Where exactly do I store the state between turns?
Anywhere that persists for the life of the conversation: a session store, a database record keyed by conversation id, or an in-memory structure for short-lived sessions. The choice is an implementation detail. What matters is that the same object reliably survives from one turn to the next.
How do I keep the prompt from getting huge?
Inject only the state relevant to the current turn, summarized, rather than the full object or the full transcript. The state object lets you carry durable facts compactly, so the prompt stays small even as the conversation grows long.
What if the user provides information out of order?
Your extraction step should accept whatever fields appear in a message and store them, regardless of what you asked for. State is field-based, not sequence-based, so a user filling three fields in one message simply updates three fields at once.
How do I know when the conversation is done?
After each update, check whether all required fields in the state are filled. When they are, you move to confirmation. Driving completion from the state object rather than inferring it from the transcript gives the flow a reliable, explicit finish line.
Can this handle branching conversations?
Yes. Add fields to the schema for the branch conditions and let the next-step logic read them. The same update-then-decide loop works; branching just means the decision about what to ask next depends on more of the state.
Key Takeaways
- Define a state schema first, listing every field and which are required to complete the task
- Persist the state object across turns in any store, since it is the memory the model lacks
- Each turn, extract new information, validate it, then commit it to state before building the prompt
- Inject a focused summary of relevant state plus a clear instruction, not the raw transcript
- Drive completion and edge cases from the state object so the conversation has a reliable finish line