Context

Conversation history versus persistent memory

A model keeps nothing between calls, so an application resends the conversation and fills a context window. Follow one real conversation as the window overflows, truncation silently drops the turn that set a naming rule, and retrieved memory and pinned conventions bring it back, with every reply recorded.

Recorded call
Loading 3D view

Every call starts emptyThe model has just promised snake_case, but it keeps nothing between calls: each request starts empty, and it sees only what the application puts in its window.

Narrated with James Phoenix's AI voice.

285/ 300 tokens
In the window
turns 4 to 7
Turns sent
none
Saved facts sent
0 of 5
snake_case runs
0 of 5
Pence runs
gemini-3.8-flasht=0, 2026-09-11
Recorded with

Truncated to fit: the application kept the newest turns that fit in 300 tokens, turns 1 to 3 were silently left out, and every run went back to camelCase.

A language model is stateless. Each API call starts from nothing: the model reads the tokens it is sent, writes a reply, and keeps nothing. A chat that seems to remember is an application that stores the conversation and resends it, so the model's memory is whatever the application decides to put in the context window on the next call. This film makes that choice visible.

The scenario is one real conversation with `google/gemini-3.8-flash` at temperature 0, recorded on 11 September 2026. In turn 1 the user says the API returns every JSON field in snake_case, never camelCase, and the model promises to follow it. Turn 3 adds that money is stored as integer pence. Four unrelated questions follow, about pagination, cursor headers, caching and request ids. Then the user asks for the TypeScript type of an order response, which only comes out right if the model still knows the rules. Each chapter sends that same request with a different window and shows one of five recorded replies, with the tally across all five.

On the left of the bench is the transcript shelf: the application's stored conversation history, every turn with its real token count from the o200k_base tokenizer. Next to it is the memory store, a few saved facts from this session and earlier ones, and below it the slot for the current request. The upright column is the context window, 300 tokens to the red limit, and every card in it is as tall as its token count. The model box sees only the column. Each call copies cards into the column from the three compartments; whatever is left behind does not exist for the model.

Sent whole, the conversation is 417 tokens and every run uses snake_case. Asked after turn 3, at 192 tokens, the rule is in the window and every run uses it, and four of five name the field total_pence. After turn 7, an application with a 300-token budget does what many chat applications do by default and keeps the newest turns that fit. Turns 1 to 3 drop out without any error or warning, and all five runs switch to camelCase, exactly like a call sent with no history at all. The model did not forget. It was never told.

Persistent memory is the usual fix, and the film shows why it is not a guarantee. A memory store is still just text the application selects and resends. Here the application embeds the request, retrieves the two most similar saved facts and puts them in the system prompt. The snake_case fact comes back, and so does snake_case in every run. The pence fact scores 0.06 against a request that never mentions money units, ranks last behind facts about deploy days and the on-call rota, and is not sent, so no run says pence. Pinning the conventions that must always hold, so they ride in every call whatever the similarity, restores both rules to the window: snake_case holds in five of five runs, and the total is named in pence in two. Having the rule in the window makes it available, not obeyed, so check the output too. Pinned text is not free either: it takes the same slice of the budget on every call, which here left room for only turns 5 to 7 of the history.

The practical rules follow. Decide what must persist, and pin it into every call rather than hoping retrieval ranks it. Retrieve the rest deliberately, and log what was retrieved and what was left out. Track the token budget per call, because truncation is silent from the model's side. Summarising older turns (compaction) is another option not recorded here: it keeps more history in fewer tokens, but a summary can drop a rule just as quietly as truncation does.

The honest caveat is scale. Real context windows run to hundreds of thousands of tokens, so a 300-token budget is shrunk to make the window fill in seven turns; in production the same truncation happens after far longer conversations, or much sooner when an application caps history to save cost and latency. There are five runs per condition, one model and one conversation, and the pence check reads only field names, so a reply that keeps pence in a comment would count as a miss.

The maths

The window budget
Tsys+Tmem+iSTi+Treq    B,B=300T_{\text{sys}} + T_{\text{mem}} + \sum_{i \in S} T_i + T_{\text{req}} \;\le\; B, \qquad B = 300

Every call is rebuilt from pieces: the system prompt, any saved facts, the stored turns the application chooses (the set S) and the new request. Each card in the column is as tall as its token count, and the red bar is B. The film shrinks B to 300 so a seven-turn conversation of 417 tokens can overflow it.

Truncation keeps the newest turns that fit
S  =  {n,,7},n  =  min{m:Tfixed+i=m7TiB}S \;=\; \{\, n, \dots, 7 \,\}, \qquad n \;=\; \min\Bigl\{ m : T_{\text{fixed}} + \sum_{i=m}^{7} T_i \le B \Bigr\}

The common default walks back from the newest turn and stops at the first one that does not fit. With the system prompt and request fixed at 60 tokens, turns 4 to 7 fit in 285 and turns 1 to 3 drop out, including the one that set snake_case. Nothing in the reply says so.

Retrieval chooses by similarity, not importance
mem  =  topkcos(e(request),e(fj)),k=2\text{mem} \;=\; \operatorname{top}_k \cos\bigl(e(\text{request}),\, e(f_j)\bigr), \qquad k = 2

Each saved fact f is scored against the request with openai/text-embedding-3-small. The snake_case fact scored 0.19 and was sent; the integer-pence fact scored 0.06, last of five, below facts about deploy days and the on-call rota, so it never reached the window.

The model conditions only on the window
y    pθ(w),w=what this call sendsy \;\sim\; p_\theta(\,\cdot \mid w\,), \qquad w = \text{what this call sends}

The reply is a function of the window w and the fixed weights, nothing else. A stored turn that is not in w has exactly the influence of a turn that was never said, which is why the truncated run and the no-history run give the same camelCase reply.

Related terms

More visualisations

Building with language models?

These explainers come out of the work. If you want the same thinking applied to your own system, that is what I do.

See how I can help