Topic 2.2
Messages and Roles
In one line
A chat is a list of messages, each with a role: system (rules), user (the person) and assistant (the model). The model remembers nothing between calls, so your program stores the list and sends all of it every time, trimming it when it grows too long.
Think of it like this
Passing a notebook to a stranger. Each time you want help you hand over the whole notebook: the rules at the top, then every question and answer so far, then your new question. The stranger reads it, writes one answer and gives it back, then forgets everything. Next time you must hand over the notebook again, with the new lines added.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Message
- One entry in the conversation list: a role and its content.
- Role
- Who is speaking:
system(developer rules),user(the person) orassistant(the model). - System message
- Standing instructions that apply to the whole conversation, usually the first message.
- Conversation history
- The list of earlier messages that your program stores and re-sends.
- Stateless
- Keeping no memory between calls. Model APIs are stateless; your app holds the state.
- Context window
- The maximum number of tokens the model can read in one call, input and output together.
- Trimming
- Removing old messages so the list fits a token budget.
Step by step
01Step 1: one message is a role and content
A message is a small object: {"role": "user", "content": "Hello"}. The request holds a list of them, in order. Order matters: the model reads them top to bottom.
02Step 2: the model forgets
Call the fake model with only What is my name?. It answers that it does not know. This is not a bug: nothing in its input contains a name. A real model behaves the same way (it may guess or make something up, which is worse).
03Step 3: keep a history list
The ask function appends the user's text to history, sends the whole list, then appends the model's reply as an assistant message. The next call sends everything again, so the name is now in the input and the answer is correct.
04Step 4: see what a forgetful app does
The example also sends only the last message. The answer is "I do not know your name" again. Bugs like this appear when a web server handles each request separately and nobody saves the history between them.
05Step 5: history costs money and space
Each turn adds two messages, and all of them are sent every time. After ten turns a chat may send twenty messages. The second example estimates tokens as characters divided by 4 plus a few tokens per message, a rough rule (Topic 1.2 explains real tokenizers).
06Step 6: trim from the oldest end
With a big budget nothing is dropped. As the budget shrinks, the oldest turns go first, the system message stays, and the list never begins with an assistant message. At the smallest budget only the system message and the newest question remain, which is the least that still makes sense.
Try it yourself
- 1
Predict the forgetful app
Before running the first example, predict each of the four answers (fresh call, turn 1, turn 2, forgetful app). Then run and check.
- 2
Move the name
In the first example change turn 1 to
Hello there.(no name). What does turn 2 say now, and why? - 3
Find the breaking budget
In the second example try budgets 100, 45, 30 and 10. At which budget does the list shrink to only the system message? What would you do if even the system message plus the newest question does not fit?
Code & diagrams
The fake finds the name only if it is in the messages it receives, which is how a real model behaves too.
Expected output
fresh call: I do not know your name.
turn 1: Your name is Asha.
turn 2: Your name is Asha.
roles so far: user, assistant, user, assistant
forgetful app: I do not know your name. (sent only the last question)
lines in the model's input on turn 2: 3Token counts are estimates (4 characters per token plus overhead). Real counts come from the reply's usage (Topic 2.3).
Expected output
budget 200: kept 6 of 6 (76 tokens) -> system, user, assistant, user, assistant, user
budget 60: kept 4 of 6 (50 tokens) -> system, user, assistant, user
budget 40: kept 2 of 6 (22 tokens) -> system, user
budget 25: kept 2 of 6 (22 tokens) -> system, userBreak it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Forget to store the reply
In the first example's ask, remove the line that appends the model's reply to history (the assistant message), then run it again.
Myth vs fact
Myth
The model remembers my previous chat.
Fact
Not unless your app sends it again. Some products add memory features, but those are the app storing notes and putting them in the next request.
Myth
A longer conversation costs the same per message.
Fact
The whole history is input on every call, so later messages cost more and respond slower.
Myth
The system message cannot be overridden.
Fact
It carries more weight than user text, but it is not a security boundary. Users can still try to steer the model (Phase 10).
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
Trim by tokens, not by message count: one pasted document can outweigh twenty chat lines. Count with the provider's
usagefrom earlier calls where you can. - ▸
Many providers cache a repeated prompt prefix at a discount (prompt caching). Keep the system message and early history byte-identical between calls so the prefix can be reused (Topic 6.6).
- ▸
Store the history on the server with a conversation id, not in the browser: clients can edit what they send, including fake
assistantorsystemmessages.
Remember this
- 1
Why this exists. The model is a function (Topic 0.1): text in, text out, no memory. A conversation therefore has to live in your program. The API needs a standard way to say who wrote each line, so the input is a list of messages, each with a
roleandcontent. Without roles the model could not tell your rules from the user's words or from its own earlier answers. - 2
The three roles.
systemholds standing instructions from you, the developer ("Answer in one short sentence"); it normally comes first.useris what the person (or your program on their behalf) says.assistantis what the model said earlier; you add these back into the list yourself. Some APIs add more roles:toolfor the result of a tool the model asked for (Phase 7), and newer OpenAI models also acceptdeveloperas the name for system-style instructions. Anthropic's Messages API takes the system text as a separate top-levelsystemfield and onlyuserandassistantmessages in the list (Topic 2.5). - 3
The model remembers nothing. Ask "What is my name?" in a fresh call and the model cannot know. If your app sends only the newest message, it forgets everything else. The first example shows three calls: a fresh one (does not know), a proper conversation (the name is in the list, so it answers), and a forgetful app that sent only the last message (does not know again). When a chat seems to remember, it is because the app re-sent the history.
- 4
You pay for the history every turn. Turn 1 sends 1 message, turn 2 sends 3, turn 3 sends 5. Input tokens grow with every turn, so a long chat costs more per message than a short one, and it gets slower. At some point the list also stops fitting in the model's context window (the most tokens it can read at once, Topic 1.5).
- 5
Trimming. The simplest fix is to drop the oldest turns. The second example keeps the system message (it holds the rules), removes the oldest messages until an estimated token count fits a budget, and never leaves an
assistantmessage first (its question was dropped, so it would be confusing). Better fixes exist: summarise old turns, but the list you send is always something your code builds on purpose. - 6
Messages are data, so treat user text carefully. A user can type text that looks like instructions. Never paste user text into the
systemmessage; put it in ausermessage, and use delimiters (Topic 3.4). The role is the first line of defence, not the last (Phase 10).
Explain it without notes
Why must an application re-send the conversation on every call?
What are the roles in a chat request and who writes each?
How would you stop a long conversation from overflowing the context window?
Practice
Write add_turn(history, user_text, reply_text) that appends a user and an assistant message to history and returns it.
Write last_user_message(messages) that returns the content of the newest user message, or None if there is none.
Trade-offs
- ↔
Sending full history gives the best continuity but costs more and slows down; trimming or summarising saves money and can lose details.
- ↔
Server-side history is safer and can be shared across devices; client-side history is simpler but can be tampered with.
Done when you can
Done when you can explain why a chat app re-sends the whole conversation.
Done when you can name the roles and who writes each one.
Done when you can keep a history list and add each reply to it.
Done when you can trim a history to a token budget without dropping the system message.