跪拜 Guibai
← Back to the summary

LLMs Don't Forget You — They Were Never Listening

04-stateless-llm.png

When I first started writing LLM interfaces, I had a very natural misconception:

Since I was having a continuous conversation with the AI, the model should remember what was said before.

For example, if I first tell it:

Please remember my name is Xiaolin.

And then in the next round ask:

What is my name?

If it can answer, we easily think "the model remembered me."

But from the perspective of API calls, things are not like this.

Most LLM APIs are inherently stateless. The model is not a chat partner sitting there waiting for you; it is more like a computation service that gets temporarily woken up each time. You send the information needed for this request, it generates a result based on this input, and then this request ends.

The next time you make a request, if you don't include the previous content, it won't know what happened before.

What is Stateless?

Stateless means: every request is independent, and the server does not rely on the previous request by default.

This is very similar to the HTTP protocol.

When you open a webpage, submit a form, or call an interface, these are essentially independent HTTP requests. HTTP itself does not naturally remember "what this user just did." If the server wants to identify who you are, it usually relies on mechanisms like cookies, tokens, sessions, and Authorization headers.

LLM APIs are similar.

When the model receives a request, it can only see the content given to it in this request. It will not automatically know what you said in the last round, nor will it automatically save all chat history.

So the so-called "continuous conversation" is not the model saving memories itself, but the application layer re-sending the necessary historical dialogue to the model each time.

Why are LLMs Designed to be Stateless?

Intuitively, being stateful seems more convenient: the server remembers everything the user said, and continues the chat directly next time.

But in real engineering, this brings a lot of pressure.

LLM services face a large number of user requests. If the server had to save the complete conversation state for every user long-term, several problems would arise:

The benefit of being stateless is that every request can be handled by any server.

As long as the request itself carries the necessary information, the backend doesn't need to care about "which machine handled this user before." This is very important for high concurrency, high availability, and horizontal scaling.

So statelessness is not a flaw of LLMs, but a very common backend design.

The real problem is: since the model doesn't remember, we have to decide for ourselves what content should be included in this request.

Messages are the Context the Model Sees

In interfaces like Chat Completions, we often pass a messages array:

const messages = [
  { role: "system", content: "You are a rigorous assistant" },
  { role: "user", content: "Please remember my name is Xiaolin" },
  { role: "assistant", content: "Okay, I remember, your name is Xiaolin." },
  { role: "user", content: "What is my name?" }
];

The most critical point here is:

The model doesn't know "my name is Xiaolin" from some hidden memory, but because this sentence is still inside messages.

That is to say, messages is the context the model can see for this turn.

If the second request only sends this sentence:

[
  { role: "user", content: "What is my name?" }
]

The model has no reliable basis to know who you are.

It might guess, or it might say it doesn't know. Either way, it's essentially not that it "forgot," but that you didn't give it context in this request.

A Minimal chatHistory Example

The following minimal demo uses chatHistory to simulate a continuous conversation:

const chatHistory = [
  { role: 'system', content: 'You are a rigorous assistant' }
];

chatHistory.push({
  role: 'user',
  content: 'Please remember my name is Xiaolin'
});

const response = await client.chat.completions.create({
  model: 'deepseek-v4-flash',
  messages: chatHistory
});

chatHistory.push({
  role: 'assistant',
  content: response.choices[0].message.content
});

After the first round of requests ends, the code puts the model's reply back into chatHistory.

Then in the second round, ask again:

chatHistory.push({
  role: 'user',
  content: 'What is my name?'
});

const response2 = await client.chat.completions.create({
  model: 'deepseek-v4-flash',
  messages: chatHistory
});

It looks like the model "remembered," but in reality, the program saved the previous conversation into an array and sent the whole thing again in the next request.

This detail is very important.

Many people, when first writing chat applications, only save user messages and not the model's replies. This breaks half the context. Because the model not only needs to know what the user said in the next round, but also needs to know what it replied in the last round.

The conversation history should be a complete chain:

system rules
user's first input
assistant's first reply
user's second input
assistant's second reply
...

The more complete this chain, the better the model can understand where the current conversation has developed.

chatHistory Cannot Grow Infinitely

Since history is included every time, can we just stuff all conversations into messages permanently?

Simple in theory, unrealistic in engineering.

Because the longer messages gets, the greater the token cost.

When the token count increases, several direct problems arise:

So a chat system cannot just "append history"; it must also learn to "manage history."

This moves from simple API calls into context engineering.

LRU Thinking: The Most Recent Isn't Necessarily the Most Important, But It's Usually More Useful

The concept of LRU can help understand chat history trimming.

LRU stands for Least Recently Used, meaning the content used least recently is prioritized for eviction.

In the context of chat history, it can be roughly understood as:

Recently discussed content is more likely to be used again immediately, while content from too long ago that is unimportant can be prioritized for compression or removal.

For example, if a user has been debugging an error, the error messages, code snippets, and modification records from the last 10 turns are usually more important than a casual chat from much earlier.

But you can't mechanically delete based on time here.

Some information, although old, is very critical:

This project uses Vue 3.
All interfaces go through /api.
Do not modify the user's existing code.
All answers should be in Chinese.

This information cannot be deleted just because it's "old."

So real context management usually combines several strategies:

Statelessness Shifts Responsibility to the Application Layer

After understanding statelessness, many AI applications become much clearer.

Tools like ChatGPT, Cursor, Claude Code, and Codex seem to understand the current task very well, not because the underlying model naturally remembers everything, but because they do a lot of context management outside the model.

For example:

These things are selectively organized into the model's current request.

So there is a very core saying in AI engineering:

The model is responsible for generation; the application is responsible for providing context.

The stronger the model, the better it can indeed reason; but if the context is wrong, insufficient, or messy, it will still go off track.

From Prompt to Context, to Harness

Early on, people focused heavily on Prompt Engineering, which is how to write a good prompt sentence.

But when you start building real AI applications, you find that one prompt is far from enough.

Because the model itself has several structural limitations:

So later, Context Engineering emerges.

It's not concerned with "how to ask a question," but with "what background, rules, materials, history, and tool results should this request carry."

Further down the line is Harness Engineering.

That is, building a more complete engineering system outside the model: memory, tools, loops, validation, permissions, rules, context compression, making the model's capabilities more stable and controllable.

Statelessness is the starting point of this line.

Because as long as the model doesn't automatically remember, developers must learn to design memory.

Summary

LLM APIs are typically stateless.

They don't naturally remember the previous round of conversation. The "continuous chat" we see is essentially the application layer putting the necessary history into messages each time, allowing the model to see the context again in this request.

So when writing AI applications, you can't just know how to call the interface; you must also think clearly about:

Statelessness makes services easier to scale, and it also hands the task of context management to the developer.

True AI engineering capability often hides within these trade-offs.