Suppose an assistant is replying in Slack. While it works, another message arrives, a background task finishes and a setting changes. Those arrivals belong to the conversation, but they cannot change the model call already in progress. They have to wait for the next call.

Meanwhile, every reply makes the conversation history longer. Eventually its early turns have to be shortened, while the newer turns remain unchanged.

Both changes wait until the current model call is over.

An agent loop repeats a simple cycle: call the model, save its response, then build the next call. The gap after one response is saved and before the next call starts is a breakpoint. prepareTurn() runs there. It combines the saved conversation with the oldest waiting events that fit, producing the input for the next model call.

Start with the new arrivals.

New work waits in order

Each arrival is an event: a message, a system change or a result from background work. It is added to an ordered queue before it can affect the conversation. In the diagram, A arrived before B, B before C and C before D.

Four colored event cards labeled A through D entering an ordered queue in the same order.

At the breakpoint, prepareTurn() reads the conversation so far and everything waiting in the queue. If A, B and C fit in the next model call, it prepares them together with the existing history. D stays in the queue. An older event is never skipped to reach a newer one.

The current history and event cards A through D passing through prepareTurn. The history and A through C form the next model call while D waits.

At this point A, B and C are still queued. A successful call records the input and response and advances the queue to D as one change.

The prepared history and event cards A through C becoming turn 43 after a successful call, while D becomes the head of the pending queue.

If this step or the model call fails, the queue remains A, B, C, D.

Older history is compacted while the conversation continues

Suppose turns 1–4 no longer fit comfortably. A background job starts shortening them. The conversation does not stop: turns 5 and 6 can be added while that work runs.

Turns 1 through 4 from epoch zero flowing through background compaction into a shorter summary while turns 5 and 6 are added later.

The ordinary, unshortened history is already epoch 0. When the summary is ready, it waits for the current model call to finish. At the next breakpoint, the conversation adopts epoch 1. prepareTurn() can then build the next call from S1 followed by the unchanged turns 5 and 6.

Epoch zero containing turns 1 through 6 above epoch one containing summary S1 followed by turns 5 and 6. S1 points back to turns 1 through 4, while turns 5 and 6 carry forward.

Later, turns 7 and 8 arrive. If the history needs shortening again, S2 can replace epoch 1 through turn 6. Epoch 2 starts with S2 and carries turns 7 and 8 forward. S2 points to epoch 1, where S1 still points to epoch 0.

Three conversation epochs. Epoch zero contains the original turns. Epoch one contains S1, turns 5 and 6, then turns 7 and 8. Epoch two contains S2 followed by turns 7 and 8. S1 points to epoch zero and S2 points to epoch one.

The original turns remain stored. Following the links through later epochs leads back to the original conversation. Over several rounds, those links form a tree similar in shape to a Merkle tree, using ranges of turns rather than content hashes.